From a04d5c5e1e17c60f7dae0c240ac78972e8a9bf2d Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Fri, 26 Aug 2016 17:16:03 +0530 Subject: [PATCH 0001/2908] Revert "Update bubble_sort.py" --- sorts/bubble_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index 2f14fd65f2b6..3bacb306f254 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -30,7 +30,7 @@ def bubble_sort(collection): [-45, -5, -2] """ length = len(collection) - for i in range(length-1): + for i in range(length): for j in range(length-1): if collection[j] > collection[j+1]: collection[j], collection[j+1] = collection[j+1], collection[j] From aad3fca61032cfa8d81005b006466f4a1e0d22a5 Mon Sep 17 00:00:00 2001 From: Ridiculous Ate Date: Thu, 28 Sep 2017 17:35:52 -0400 Subject: [PATCH 0002/2908] Update password_generator.py increased user interactivity and more powerful usage of modules. --- other/password_generator.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/other/password_generator.py b/other/password_generator.py index 10ba77088eac..c4cfcbdcaaa5 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -12,3 +12,17 @@ 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 +# 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 +def random_number(ctbi, i): + + + +def random_letters(ctbi, i): + + + +def random_characters(ctbi, i): From 85a599d54a9833d6ca3a643c6c3c0305e38100c1 Mon Sep 17 00:00:00 2001 From: Ridiculous Ate Date: Thu, 19 Oct 2017 11:54:05 -0400 Subject: [PATCH 0003/2908] Add files via upload --- server.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 server.py diff --git a/server.py b/server.py new file mode 100644 index 000000000000..578c24285bfb --- /dev/null +++ b/server.py @@ -0,0 +1,43 @@ +# all imports - including #s +import socket +#import os +#import sys +#import subprocess +# end of imports +# the below classes will clarify what information is for the attacker and client +class Termrequire: + host = socket.gethostname() + port = 3333 # fake numeral for the moment +class Clientrequire: + host = socket.gethostname() + port = 2222 # fake numeral for the moment +#CORE REQUIREMENTS OF PROGRAM: +### host ip = server ip +### potential connection hosts info (host, port) +### user.config +### user.config +# using SERVER for connections and linux meterpreter sessions +# SERVER DETAILS: + #5 client availability for pivoting #although that is not yet available in a regular form of + #exploitation - we have to go with what we have. + +# #learnmore - USER_CONFIG +# server ip will be displayed every connection at version 2.0 +# terminal attacker socket object creation +t = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +# terminal attacker socket binding +t.bind() +# terminal attacker socket listen +t.listen() +# client socket object creation +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +# binding information with s.bind method +s.bind() +#listening for connections with s.listen method +s.listen(1) +# server_functionality waits for terminal shell and then gets client information connectivity +def func4client(): + s.accept() +# terminal functionality for attacker - I will definitely customize it soon. Maybe tkinter? +def func4term(): + t.accept() \ No newline at end of file From ba759d9573a09dff16adee4cbe61328bd02f36ed Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 25 Nov 2017 00:10:39 +0100 Subject: [PATCH 0004/2908] Add automated flake8 testing of pull requests The owner of the this repo would need to go to https://travis-ci.org/profile and flip the repository switch __on__ to enable free automated flake8 testing of each pull request. Flake8 will find bugs and code smells. --- .travis.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..5fba6987bb66 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +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=E901,E999,F821,F822,F823 --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 From 4e06949072749f424c10e99f6bebb9fdf95d13db Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 25 Nov 2017 10:23:50 +0100 Subject: [PATCH 0005/2908] Modernize Python 2 code to get ready for Python 3 --- File_Transfer_Protocol/ftp_send_receive.py | 36 ++-- Graphs/a_star.py | 9 +- Graphs/basic-graphs.py | 34 +++- Graphs/minimum_spanning_tree_kruskal.py | 1 + Graphs/scc_kosaraju.py | 1 + Multi_Hueristic_Astar.py | 38 ++-- Neural_Network/convolution_neural_network.py | 9 +- Neural_Network/perceptron.py | 7 +- Project Euler/Problem 01/sol1.py | 9 +- Project Euler/Problem 01/sol2.py | 7 +- Project Euler/Problem 01/sol3.py | 7 +- Project Euler/Problem 02/sol1.py | 8 +- Project Euler/Problem 03/sol1.py | 7 +- Project Euler/Problem 03/sol2.py | 3 +- Project Euler/Problem 04/sol1.py | 3 +- Project Euler/Problem 04/sol2.py | 3 +- Project Euler/Problem 05/sol1.py | 3 +- Project Euler/Problem 06/sol1.py | 3 +- Project Euler/Problem 06/sol2.py | 3 +- Project Euler/Problem 07/sol1.py | 3 +- Project Euler/Problem 13/sol1.py | 1 + Project Euler/Problem 14/sol1.py | 3 +- Project Euler/Problem 9/sol1.py | 3 +- ciphers/affine_cipher.py | 1 + ciphers/brute-force_caesar_cipher.py | 1 + ciphers/caesar_cipher.py | 5 +- ciphers/rabin_miller.py | 5 +- ciphers/rot13.py | 1 + ciphers/rsa_cipher.py | 1 + ciphers/rsa_key_generator.py | 1 + ciphers/simple_substitution_cipher.py | 1 + ciphers/transposition_cipher.py | 1 + ...ansposition_cipher_encrypt-decrypt_file.py | 3 +- ciphers/vigenere_cipher.py | 1 + data_structures/Binary Tree/FenwickTree.py | 57 +++--- .../Binary Tree/LazySegmentTree.py | 181 +++++++++--------- data_structures/Binary Tree/SegmentTree.py | 129 ++++++------- .../Binary Tree/binary_search_tree.py | 5 +- data_structures/Graph/Graph.py | 3 +- data_structures/Graph/even_tree.py | 3 +- data_structures/Heap/heap.py | 11 +- .../LinkedList/singly_LinkedList.py | 1 + data_structures/Queue/DeQueue.py | 1 + .../Stacks/balanced_parentheses.py | 4 +- .../Stacks/infix_to_postfix_conversion.py | 4 +- data_structures/Stacks/next.py | 1 + data_structures/Stacks/stack.py | 1 + data_structures/UnionFind/tests_union_find.py | 3 +- dynamic_programming/coin_change.py | 5 +- dynamic_programming/edit_distance.py | 13 +- dynamic_programming/fastfibonacci.py | 1 + dynamic_programming/fibonacci.py | 16 +- .../longest_common_subsequence.py | 9 +- .../longest_increasing_subsequence.py | 1 + ...longest_increasing_subsequence_O(nlogn).py | 3 +- dynamic_programming/longest_sub_array.py | 5 +- dynamic_programming/max_sub_array.py | 119 ++++++------ hashes/chaos_machine.py | 1 + hashes/md5.py | 1 + machine_learning/decision_tree.py | 1 + machine_learning/gradient_descent.py | 7 +- machine_learning/linear_regression.py | 1 + machine_learning/perceptron.py | 7 +- machine_learning/scoring_functions.py | 2 +- other/LinearCongruentialGenerator.py | 3 +- other/anagrams.py | 3 +- other/euclidean_gcd.py | 1 + other/nested_brackets.py | 5 +- other/password_generator.py | 1 + other/tower_of_hanoi.py | 3 +- other/two-sum.py | 1 + other/word_patterns.py | 3 +- searches/binary_search.py | 18 +- searches/interpolation_search.py | 19 +- searches/jump_search.py | 1 + searches/linear_search.py | 19 +- searches/ternary_search.py | 21 +- sorts/bogosort.py | 14 +- sorts/bubble_sort.py | 15 +- sorts/bucket_sort.py | 1 + sorts/cocktail_shaker_sort.py | 16 +- sorts/counting_sort.py | 13 +- sorts/countingsort.py | 1 + sorts/external-sort.py | 2 +- sorts/gnome_sort.py | 16 +- sorts/heap_sort.py | 11 +- sorts/insertion_sort.py | 14 +- sorts/merge_sort.py | 14 +- sorts/quick_sort.py | 14 +- sorts/random_normaldistribution_quicksort.py | 1 + sorts/selection_sort.py | 15 +- sorts/shell_sort.py | 15 +- sorts/timsort.py | 1 + sorts/topological_sort.py | 1 + traversals/binary_tree_traversals.py | 27 ++- 95 files changed, 583 insertions(+), 524 deletions(-) diff --git a/File_Transfer_Protocol/ftp_send_receive.py b/File_Transfer_Protocol/ftp_send_receive.py index ba1280048a62..d4919158a02e 100644 --- a/File_Transfer_Protocol/ftp_send_receive.py +++ b/File_Transfer_Protocol/ftp_send_receive.py @@ -1,34 +1,34 @@ - """ - File transfer protocol used to send and receive files using FTP server. - Use credentials to provide access to the FTP client +""" + 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 - """ + 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 = 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 - """ +""" + 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 """ LocalFile = open(FileName, 'wb') - ftp.retrbinary('RETR ' + filename, LocalFile.write, 1024) + ftp.retrbinary('RETR ' + FileName, LocalFile.write, 1024) ftp.quit() LocalFile.close() - """ - The file which will be sent via the FTP server - The file send will be send to the current working directory - """ +""" + 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 """ diff --git a/Graphs/a_star.py b/Graphs/a_star.py index 2ca9476e57a3..584222e6f62b 100644 --- a/Graphs/a_star.py +++ b/Graphs/a_star.py @@ -1,3 +1,4 @@ +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 @@ -80,7 +81,7 @@ def search(grid,init,goal,cost,heuristic): y = goal[1] 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] + x2 = x - delta[action[x][y]][0] y2 = y - delta[action[x][y]][1] x = x2 y = y2 @@ -89,13 +90,13 @@ def search(grid,init,goal,cost,heuristic): path = [] for i in range(len(invpath)): path.append(invpath[len(invpath) - 1 - i]) - print "ACTION MAP" + print("ACTION MAP") for i in range(len(action)): - print action[i] + 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 fc78e565274d..c18574fa9963 100644 --- a/Graphs/basic-graphs.py +++ b/Graphs/basic-graphs.py @@ -1,3 +1,15 @@ +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 + # Accept No. of Nodes and edges n, m = map(int, raw_input().split(" ")) @@ -48,7 +60,7 @@ def dfs(G, s): vis, S = set([s]), [s] - print s + print(s) while S: flag = 0 for i in G[S[-1]]: @@ -56,7 +68,7 @@ def dfs(G, s): S.append(i) vis.add(i) flag = 1 - print i + print(i) break if not flag: S.pop() @@ -76,14 +88,14 @@ def dfs(G, s): def bfs(G, s): vis, Q = set([s]), deque([s]) - print s + print(s) while Q: u = Q.popleft() for v in G[u]: if v not in vis: vis.add(v) Q.append(v) - print v + print(v) """ @@ -116,7 +128,7 @@ def dijk(G, s): path[v[0]] = u for i in dist: if i != s: - print dist[i] + print(dist[i]) """ @@ -140,7 +152,7 @@ def topo(G, ind=None, Q=[1]): if len(Q) == 0: return v = Q.popleft() - print v + print(v) for w in G[v]: ind[w] -= 1 if ind[w] == 0: @@ -175,7 +187,8 @@ def adjm(): """ -def floy((A, n)): +def floy(xxx_todo_changeme): + (A, n) = xxx_todo_changeme dist = list(A) path = [[0] * n for i in xrange(n)] for k in xrange(n): @@ -184,7 +197,7 @@ def floy((A, n)): if dist[i][j] > dist[i][k] + dist[k][j]: dist[i][j] = dist[i][k] + dist[k][j] path[i][k] = k - print dist + print(dist) """ @@ -246,14 +259,15 @@ def edglist(): """ -def krusk((E, n)): +def krusk(xxx_todo_changeme1): # Sort edges on the basis of distance + (E, n) = xxx_todo_changeme1 E.sort(reverse=True, key=lambda x: x[2]) s = [set([i]) for i in range(1, n + 1)] while True: if len(s) == 1: break - print s + print(s) x = E.pop() for i in xrange(len(s)): if x[0] in s[i]: diff --git a/Graphs/minimum_spanning_tree_kruskal.py b/Graphs/minimum_spanning_tree_kruskal.py index d26eb70b2e05..81d64f421a31 100644 --- a/Graphs/minimum_spanning_tree_kruskal.py +++ b/Graphs/minimum_spanning_tree_kruskal.py @@ -1,3 +1,4 @@ +from __future__ import print_function num_nodes, num_edges = list(map(int,input().split())) edges = [] diff --git a/Graphs/scc_kosaraju.py b/Graphs/scc_kosaraju.py index 09a05e9817d2..1f13ebaba36b 100644 --- a/Graphs/scc_kosaraju.py +++ b/Graphs/scc_kosaraju.py @@ -1,3 +1,4 @@ +from __future__ import print_function # n - no of nodes, m - no of edges n, m = list(map(int,input().split())) diff --git a/Multi_Hueristic_Astar.py b/Multi_Hueristic_Astar.py index 03652d35ae2f..7fbd2ff04542 100644 --- a/Multi_Hueristic_Astar.py +++ b/Multi_Hueristic_Astar.py @@ -1,8 +1,14 @@ +from __future__ import print_function import heapq import numpy as np import math import copy +try: + xrange # Python 2 +except NameError: + xrange = range # Python 3 + class PriorityQueue: def __init__(self): @@ -95,22 +101,22 @@ def do_something(back_pointer, goal, start): for i in xrange(n): for j in range(n): if (i, j) == (0, n-1): - print grid[i][j], - print "<-- End position", + print(grid[i][j], end=' ') + print("<-- End position", end=' ') else: - print grid[i][j], - print + print(grid[i][j], end=' ') + print() print("^") print("Start position") - print + 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, + print(x, end=' ') x = back_pointer[x] - print x + print(x) quit() def valid(p): @@ -239,24 +245,24 @@ def multi_a_star(start, goal, n_hueristic): 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 + print() for i in range(n-1,-1, -1): for j in range(n): if (j, i) in blocks: - print '#', + print('#', end=' ') elif (j, i) in back_pointer: if (j, i) == (n-1, n-1): - print '*', + print('*', end=' ') else: - print '-', + print('-', end=' ') else: - print '*', + print('*', end=' ') if (j, i) == (n-1, n-1): - print '<-- End position', - print + print('<-- End position', end=' ') + print() print("^") print("Start position") - print + print() print("# is an obstacle") print("- is the path taken by algorithm") -multi_a_star(start, goal, n_hueristic) \ No newline at end of file +multi_a_star(start, goal, n_hueristic) diff --git a/Neural_Network/convolution_neural_network.py b/Neural_Network/convolution_neural_network.py index d8ab0d2e572b..0dca2bc485d1 100644 --- a/Neural_Network/convolution_neural_network.py +++ b/Neural_Network/convolution_neural_network.py @@ -15,6 +15,7 @@ Date: 2017.9.20 - - - - - -- - - - - - - - - - - - - - - - - - - - - - - ''' +from __future__ import print_function import numpy as np import matplotlib.pyplot as plt @@ -192,8 +193,8 @@ def _calculate_gradient_from_pool(self,out_map,pd_pool,num_map,size_map,size_poo def trian(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)) + print((' - - Shape: Train_Data ',np.shape(datas_train))) + print((' - - Shape: Teach_Data ',np.shape(datas_teach))) rp = 0 all_mse = [] mse = 10000 @@ -262,7 +263,7 @@ def draw_error(): plt.grid(True, alpha=0.5) plt.show() print('------------------Training Complished---------------------') - print(' - - Training epoch: ', rp, ' - - Mse: %.6f' % mse) + print((' - - Training epoch: ', rp, ' - - Mse: %.6f' % mse)) if draw_e: draw_error() return mse @@ -271,7 +272,7 @@ def predict(self,datas_test): #model predict produce_out = [] print('-------------------Start Testing-------------------------') - print(' - - Shape: Test_Data ',np.shape(datas_test)) + 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, diff --git a/Neural_Network/perceptron.py b/Neural_Network/perceptron.py index 0cd89df1688f..8ac3e8fc69e9 100644 --- a/Neural_Network/perceptron.py +++ b/Neural_Network/perceptron.py @@ -9,6 +9,7 @@ p2 = 1 ''' +from __future__ import print_function import random @@ -52,7 +53,7 @@ def trannig(self): epoch_count = epoch_count + 1 # if you want controle the epoch or just by erro if erro == False: - print('\nEpoch:\n',epoch_count) + print(('\nEpoch:\n',epoch_count)) print('------------------------\n') #if epoch_count > self.epoch_number or not erro: break @@ -66,10 +67,10 @@ def sort(self, sample): y = self.sign(u) if y == -1: - print('Sample: ', sample) + print(('Sample: ', sample)) print('classification: P1') else: - print('Sample: ', sample) + print(('Sample: ', sample)) print('classification: P2') def sign(self, u): diff --git a/Project Euler/Problem 01/sol1.py b/Project Euler/Problem 01/sol1.py index 512154e296ee..27031c3cfa9a 100644 --- a/Project Euler/Problem 01/sol1.py +++ b/Project Euler/Problem 01/sol1.py @@ -4,9 +4,14 @@ 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 n = int(raw_input().strip()) -sum=0; +sum=0 for a in range(3,n): if(a%3==0 or a%5==0): sum+=a -print sum; \ No newline at end of file +print(sum) diff --git a/Project Euler/Problem 01/sol2.py b/Project Euler/Problem 01/sol2.py index 5e368c220367..d330387e98ab 100644 --- a/Project Euler/Problem 01/sol2.py +++ b/Project Euler/Problem 01/sol2.py @@ -4,6 +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 n = int(raw_input().strip()) sum = 0 terms = (n-1)/3 @@ -12,4 +17,4 @@ sum+= ((terms)*(10+(terms-1)*5))/2 terms = (n-1)/15 sum-= ((terms)*(30+(terms-1)*15))/2 -print sum \ No newline at end of file +print(sum) diff --git a/Project Euler/Problem 01/sol3.py b/Project Euler/Problem 01/sol3.py index 0caa30a53005..d3d9256b728c 100644 --- a/Project Euler/Problem 01/sol3.py +++ b/Project Euler/Problem 01/sol3.py @@ -7,6 +7,11 @@ ''' 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 +except NameError: + raw_input = input # Python 3 n = int(raw_input().strip()) sum=0; num=0; @@ -39,4 +44,4 @@ if(num>=n): break sum+=num -print sum; \ No newline at end of file +print(sum); diff --git a/Project Euler/Problem 02/sol1.py b/Project Euler/Problem 02/sol1.py index 6cf520767591..0b9eb8ea46bf 100644 --- a/Project Euler/Problem 02/sol1.py +++ b/Project Euler/Problem 02/sol1.py @@ -6,6 +6,12 @@ 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 n = int(raw_input().strip()) i=1; j=2; sum=0 @@ -15,4 +21,4 @@ temp=i i=j j=temp+i -print sum \ No newline at end of file +print(sum) diff --git a/Project Euler/Problem 03/sol1.py b/Project Euler/Problem 03/sol1.py index bd3e237e7046..dfc90982fcfd 100644 --- a/Project Euler/Problem 03/sol1.py +++ b/Project Euler/Problem 03/sol1.py @@ -3,6 +3,7 @@ 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 import math @@ -20,12 +21,12 @@ def isprime(no): max=0 n=int(input()) if(isprime(n)): - print n + print(n) else: while (n%2==0): n=n/2 if(isprime(n)): - print n + print(n) else: n1 = int(math.sqrt(n))+1 for i in range(3,n1,2): @@ -35,4 +36,4 @@ def isprime(no): break elif(isprime(i)): max=i - print max + print(max) diff --git a/Project Euler/Problem 03/sol2.py b/Project Euler/Problem 03/sol2.py index 2577892c4a3d..af4365b7b18f 100644 --- a/Project Euler/Problem 03/sol2.py +++ b/Project Euler/Problem 03/sol2.py @@ -3,6 +3,7 @@ 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 n=int(input()) prime=1 i=2 @@ -13,4 +14,4 @@ i+=1 if(n>1): prime=n -print prime +print(prime) diff --git a/Project Euler/Problem 04/sol1.py b/Project Euler/Problem 04/sol1.py index f8ed832d8c13..17e16fd5314c 100644 --- a/Project Euler/Problem 04/sol1.py +++ b/Project Euler/Problem 04/sol1.py @@ -3,6 +3,7 @@ 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()) for i in range(n-1,10000,-1): temp=str(i) @@ -10,6 +11,6 @@ j=999 while(j!=99): if((i%j==0) and (len(str(i/j))==3)): - print i + print(i) exit(0) j-=1 diff --git a/Project Euler/Problem 04/sol2.py b/Project Euler/Problem 04/sol2.py index 4d200624207a..e27e7d30471e 100644 --- a/Project Euler/Problem 04/sol2.py +++ b/Project Euler/Problem 04/sol2.py @@ -3,6 +3,7 @@ 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 arr = [] for i in range(999,100,-1): for j in range(999,100,-1): @@ -14,5 +15,5 @@ n=int(input()) for i in arr[::-1]: if(i',pre_counter,'digits') +print(('Largest Number:',largest_number,'->',pre_counter,'digits')) diff --git a/Project Euler/Problem 9/sol1.py b/Project Euler/Problem 9/sol1.py index ed8a2ab3656a..e54c543b4721 100644 --- a/Project Euler/Problem 9/sol1.py +++ b/Project Euler/Problem 9/sol1.py @@ -1,3 +1,4 @@ +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 @@ -10,5 +11,5 @@ 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)) + print(("Product of",a,"*",b,"*",c,"=",(a*b*c))) break diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index b74ec6f4ed60..6c1ba06f6850 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys, random, cryptomath_module as cryptoMath SYMBOLS = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" diff --git a/ciphers/brute-force_caesar_cipher.py b/ciphers/brute-force_caesar_cipher.py index 3e6e975c8297..3b0716442fc5 100644 --- a/ciphers/brute-force_caesar_cipher.py +++ b/ciphers/brute-force_caesar_cipher.py @@ -1,3 +1,4 @@ +from __future__ import print_function def decrypt(message): """ >>> decrypt('TMDETUX PMDVU') diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index b590f81f901b..a53dc5857fca 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,3 +1,4 @@ +from __future__ import print_function # The Caesar Cipher Algorithm def main(): @@ -12,9 +13,9 @@ def main(): translated = encdec(message, key, mode) if mode == "encrypt": - print("Encryption:", translated) + print(("Encryption:", translated)) elif mode == "decrypt": - print("Decryption:", translated) + print(("Decryption:", translated)) def encdec(message, key, mode): message = message.upper() diff --git a/ciphers/rabin_miller.py b/ciphers/rabin_miller.py index d6e5cbe8f19f..f71fb03c0051 100644 --- a/ciphers/rabin_miller.py +++ b/ciphers/rabin_miller.py @@ -1,3 +1,4 @@ +from __future__ import print_function # Primality Testing with the Rabin-Miller Algorithm import random @@ -59,5 +60,5 @@ def generateLargePrime(keysize = 1024): 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..2abf981e9d7d 100644 --- a/ciphers/rot13.py +++ b/ciphers/rot13.py @@ -1,3 +1,4 @@ +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 7e2dc5fd1228..94f69ddc2533 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys, rsa_key_generator as rkg, os DEFAULT_BLOCK_SIZE = 128 diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 7cd7163b68d5..541e90d6e884 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -1,3 +1,4 @@ +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 41ac4a6a7b98..1bdd7dc04a57 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys, random LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index 1c2ed0aa0452..dbb358315d22 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -1,3 +1,4 @@ +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 f2252de8a2e2..57620d83948c 100644 --- a/ciphers/transposition_cipher_encrypt-decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt-decrypt_file.py @@ -1,3 +1,4 @@ +from __future__ import print_function import time, os, sys import transposition_cipher as transCipher @@ -29,7 +30,7 @@ def main(): outputObj.close() totalTime = round(time.time() - startTime, 2) - print('Done (', totalTime, 'seconds )') + print(('Done (', totalTime, 'seconds )')) if __name__ == '__main__': main() diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index 95eeb431109f..5d5be0792835 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -1,3 +1,4 @@ +from __future__ import print_function LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' def main(): diff --git a/data_structures/Binary Tree/FenwickTree.py b/data_structures/Binary Tree/FenwickTree.py index 02e2d61515a9..f429161c8c36 100644 --- a/data_structures/Binary Tree/FenwickTree.py +++ b/data_structures/Binary Tree/FenwickTree.py @@ -1,28 +1,29 @@ -class FenwickTree: - - def __init__(self, SIZE): # create fenwick tree with size SIZE - self.Size = 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): - self.ft[i] += val - i += i & (-i) - - def query(self, i): # query cumulative data from index 0 to i in O(lg N) - ret = 0 - while (i > 0): - 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)) - f.update(2,-5) - print (f.query(1)) - print (f.query(3)) +from __future__ import print_function +class FenwickTree: + + def __init__(self, SIZE): # create fenwick tree with size SIZE + self.Size = 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): + self.ft[i] += val + i += i & (-i) + + def query(self, i): # query cumulative data from index 0 to i in O(lg N) + ret = 0 + while (i > 0): + 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)) + f.update(2,-5) + print (f.query(1)) + print (f.query(3)) diff --git a/data_structures/Binary Tree/LazySegmentTree.py b/data_structures/Binary Tree/LazySegmentTree.py index bbd880a06fe8..9b14b24e81fa 100644 --- a/data_structures/Binary Tree/LazySegmentTree.py +++ b/data_structures/Binary Tree/LazySegmentTree.py @@ -1,90 +1,91 @@ -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 - - def right(self, idx): - 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)]) - - # 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: - self.st[idx] = self.lazy[idx] - self.flag[idx] = False - 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 - self.flag[self.right(idx)] = True - - if r < a or l > b: - return True - if l >= a and r <= b : - self.st[idx] = val - 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)]) - 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: - self.st[idx] = self.lazy[idx] - self.flag[idx] = False - 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 - self.flag[self.right(idx)] = True - if r < a or l > 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) - - def showData(self): - showList = [] - 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] - 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.showData() +from __future__ import print_function +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 + + def right(self, idx): + 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)]) + + # 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: + self.st[idx] = self.lazy[idx] + self.flag[idx] = False + 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 + self.flag[self.right(idx)] = True + + if r < a or l > b: + return True + if l >= a and r <= b : + self.st[idx] = val + 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)]) + 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: + self.st[idx] = self.lazy[idx] + self.flag[idx] = False + 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 + self.flag[self.right(idx)] = True + if r < a or l > 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) + + def showData(self): + showList = [] + 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] + 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.showData() diff --git a/data_structures/Binary Tree/SegmentTree.py b/data_structures/Binary Tree/SegmentTree.py index cf47ca7d7bf6..a3b128c9d8b9 100644 --- a/data_structures/Binary Tree/SegmentTree.py +++ b/data_structures/Binary Tree/SegmentTree.py @@ -1,64 +1,65 @@ -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 - - def left(self, idx): - return idx*2 - - def right(self, idx): - 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)]) - - def update(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 : - self.st[idx] = val - 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)]) - return True - - def query(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 - 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): - 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] - 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.showData() +from __future__ import print_function +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 + + def left(self, idx): + return idx*2 + + def right(self, idx): + 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)]) + + def update(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 : + self.st[idx] = val + 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)]) + return True + + def query(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 + 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): + 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] + 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.showData() diff --git a/data_structures/Binary Tree/binary_search_tree.py b/data_structures/Binary Tree/binary_search_tree.py index 5290f685bb41..b4021e4f861f 100644 --- a/data_structures/Binary Tree/binary_search_tree.py +++ b/data_structures/Binary Tree/binary_search_tree.py @@ -1,6 +1,7 @@ ''' A binary search Tree ''' +from __future__ import print_function class Node: def __init__(self, label, parent): @@ -237,8 +238,8 @@ def testBinarySearchTree(): print("The label -1 doesn't exist") if(not t.empty()): - print("Max Value: ", t.getMax().getLabel()) - print("Min Value: ", t.getMin().getLabel()) + print(("Max Value: ", t.getMax().getLabel())) + print(("Min Value: ", t.getMin().getLabel())) t.delete(13) t.delete(10) diff --git a/data_structures/Graph/Graph.py b/data_structures/Graph/Graph.py index 0fa3f5593eb2..d091f713b8d9 100644 --- a/data_structures/Graph/Graph.py +++ b/data_structures/Graph/Graph.py @@ -1,3 +1,4 @@ +from __future__ import print_function # Author: OMKAR PATHAK # We can use Python's dictionary for constructing the graph @@ -15,7 +16,7 @@ def addEdge(self, fromVertex, toVertex): def printList(self): for i in self.List: - print(i,'->',' -> '.join([str(j) for j in self.List[i]])) + print((i,'->',' -> '.join([str(j) for j in self.List[i]]))) if __name__ == '__main__': al = AdjacencyList() diff --git a/data_structures/Graph/even_tree.py b/data_structures/Graph/even_tree.py index f77cb316e363..9383ea9a13c1 100644 --- a/data_structures/Graph/even_tree.py +++ b/data_structures/Graph/even_tree.py @@ -12,6 +12,7 @@ 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 @@ -66,4 +67,4 @@ def even_tree(): tree[u].append(v) tree[v].append(u) even_tree() - print len(cuts) - 1 + print(len(cuts) - 1) diff --git a/data_structures/Heap/heap.py b/data_structures/Heap/heap.py index d43cc4baa177..e66d02b6d99f 100644 --- a/data_structures/Heap/heap.py +++ b/data_structures/Heap/heap.py @@ -1,5 +1,12 @@ #!/usr/bin/python +from __future__ import print_function + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + class Heap: def __init__(self): self.h = [] @@ -68,10 +75,10 @@ def insert(self,data): curr = curr/2 def display(self): - print (self.h) + print(self.h) def main(): - l = list(map(int,raw_input().split())) + l = list(map(int, raw_input().split())) h = Heap() h.buildHeap(l) h.heapSort() diff --git a/data_structures/LinkedList/singly_LinkedList.py b/data_structures/LinkedList/singly_LinkedList.py index 8828ce6cc873..7358b3c07f35 100644 --- a/data_structures/LinkedList/singly_LinkedList.py +++ b/data_structures/LinkedList/singly_LinkedList.py @@ -1,3 +1,4 @@ +from __future__ import print_function class Node:#create a Node def __int__(self,data): self.data=data#given data diff --git a/data_structures/Queue/DeQueue.py b/data_structures/Queue/DeQueue.py index 175c8816390d..fdee64eb6ae0 100644 --- a/data_structures/Queue/DeQueue.py +++ b/data_structures/Queue/DeQueue.py @@ -1,3 +1,4 @@ +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 1c9a84843fa3..8d99358bea87 100644 --- a/data_structures/Stacks/balanced_parentheses.py +++ b/data_structures/Stacks/balanced_parentheses.py @@ -1,4 +1,6 @@ -from Stack import Stack +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 f0a8fd072c3e..75211fed258d 100644 --- a/data_structures/Stacks/infix_to_postfix_conversion.py +++ b/data_structures/Stacks/infix_to_postfix_conversion.py @@ -1,6 +1,8 @@ +from __future__ import print_function +from __future__ import absolute_import import string -from Stack import Stack +from .Stack import Stack __author__ = 'Omkar Pathak' diff --git a/data_structures/Stacks/next.py b/data_structures/Stacks/next.py index 9765900c06d7..bca83339592c 100644 --- a/data_structures/Stacks/next.py +++ b/data_structures/Stacks/next.py @@ -1,3 +1,4 @@ +from __future__ import print_function # Function to print element and NGE pair for all elements of list def printNGE(arr): diff --git a/data_structures/Stacks/stack.py b/data_structures/Stacks/stack.py index 0b100abf3cd7..66af8c025d8c 100644 --- a/data_structures/Stacks/stack.py +++ b/data_structures/Stacks/stack.py @@ -1,3 +1,4 @@ +from __future__ import print_function __author__ = 'Omkar Pathak' diff --git a/data_structures/UnionFind/tests_union_find.py b/data_structures/UnionFind/tests_union_find.py index bdcc010334bf..b0708778ddbd 100644 --- a/data_structures/UnionFind/tests_union_find.py +++ b/data_structures/UnionFind/tests_union_find.py @@ -1,4 +1,5 @@ -from union_find import UnionFind +from __future__ import absolute_import +from .union_find import UnionFind import unittest diff --git a/dynamic_programming/coin_change.py b/dynamic_programming/coin_change.py index dca01635960a..0116df0c024e 100644 --- a/dynamic_programming/coin_change.py +++ b/dynamic_programming/coin_change.py @@ -5,6 +5,7 @@ the given types of coins? https://www.hackerrank.com/challenges/coin-change/problem """ +from __future__ import print_function def dp_count(S, m, n): table = [0] * (n + 1) @@ -21,5 +22,5 @@ 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 + print(dp_count([1, 2, 3], 3, 4)) # answer 4 + print(dp_count([2, 5, 3, 6], 4, 10)) # answer 5 diff --git a/dynamic_programming/edit_distance.py b/dynamic_programming/edit_distance.py index 05682d2c66e6..4a6e4cecf18f 100644 --- a/dynamic_programming/edit_distance.py +++ b/dynamic_programming/edit_distance.py @@ -51,11 +51,10 @@ def solve(self, A, B): return self.__solveDP(len(A)-1, len(B)-1) if __name__ == '__main__': - import sys - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 solver = EditDistance() @@ -63,10 +62,10 @@ def solve(self, A, B): print() print("Enter the first string: ", end="") - S1 = input_function() + S1 = raw_input().strip() print("Enter the second string: ", end="") - S2 = input_function() + S2 = raw_input().strip() print() print("The minimum Edit Distance is: %d" % (solver.solve(S1, S2))) diff --git a/dynamic_programming/fastfibonacci.py b/dynamic_programming/fastfibonacci.py index 5957fbe0d354..cdfa2dd08084 100644 --- a/dynamic_programming/fastfibonacci.py +++ b/dynamic_programming/fastfibonacci.py @@ -2,6 +2,7 @@ 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 5eaa81b3e7c7..abe628d036f8 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -27,26 +27,22 @@ def get(self, sequence_no=None): if __name__ == '__main__': - import sys - print("\n********* Fibonacci Series Using Dynamic Programming ************\n") - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input + 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(input()) + N = eval(raw_input().strip()) fib = Fibonacci(N) print( "\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(input()) + i = eval(raw_input().strip()) if i < 0: print("\n********* Good Bye!! ************\n") break diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index bf4e5860ce3f..0a4771cb2efd 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -3,6 +3,13 @@ A subsequence is a sequence that appears in the same relative order, but not necessarily continious. 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): # find the length of strings m = len(x) @@ -27,4 +34,4 @@ def lcs_dp(x, y): if __name__=='__main__': x = 'AGGTAB' y = 'GXTXAYB' - print lcs_dp(x, y) + print(lcs_dp(x, y)) diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index c04f6673f37f..b6d165909e70 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -7,6 +7,7 @@ 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 diff --git a/dynamic_programming/longest_increasing_subsequence_O(nlogn).py b/dynamic_programming/longest_increasing_subsequence_O(nlogn).py index 3ebb4a137b6b..21122a04d69f 100644 --- a/dynamic_programming/longest_increasing_subsequence_O(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_O(nlogn).py @@ -1,3 +1,4 @@ +from __future__ import print_function ############################# # Author: Aravind Kashyap # File: lis.py @@ -37,4 +38,4 @@ def LongestIncreasingSubsequenceLength(v): v = [2, 5, 3, 7, 11, 8, 10, 13, 6] -print LongestIncreasingSubsequenceLength(v) +print(LongestIncreasingSubsequenceLength(v)) diff --git a/dynamic_programming/longest_sub_array.py b/dynamic_programming/longest_sub_array.py index 988041ed0244..de2c88a8b525 100644 --- a/dynamic_programming/longest_sub_array.py +++ b/dynamic_programming/longest_sub_array.py @@ -6,6 +6,7 @@ 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: @@ -13,7 +14,7 @@ class SubArray: def __init__(self, arr): # we need a list not a string, so do something to change the type self.array = arr.split(',') - print("the input array is:", self.array) + print(("the input array is:", self.array)) def solve_sub_array(self): rear = [int(self.array[0])]*len(self.array) @@ -28,5 +29,5 @@ def solve_sub_array(self): whole_array = input("please input some numbers:") array = SubArray(whole_array) re = array.solve_sub_array() - print("the results is:", re) + print(("the results is:", re)) diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 653bb66e0896..58711f22ce90 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -1,59 +1,60 @@ -""" -author : Mayank Kumar Jha (mk9440) -""" - -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 - else: - 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] - if summ > left_sum: - 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) - - -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) - 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 ") - plt.show() - - - - +""" +author : Mayank Kumar Jha (mk9440) +""" +from __future__ import print_function + +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 + else: + 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] + if summ > left_sum: + 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) + + +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) + 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 ") + plt.show() + + + + diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index b9d8dd750f6b..8b6c004380aa 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -1,4 +1,5 @@ """example of simple chaos machine""" +from __future__ import print_function # Chaos Machine (K, t, m) K = [0.33, 0.44, 0.55, 0.44, 0.33]; t = 3; m = 5 diff --git a/hashes/md5.py b/hashes/md5.py index ff32f4c2e190..c336b5fe49ee 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -1,3 +1,4 @@ +from __future__ import print_function import math def rearrange(bitString32): diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index 51f600cace41..71849904ccf2 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -3,6 +3,7 @@ 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. """ +from __future__ import print_function import numpy as np diff --git a/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index 1e771b072f4d..db6415999bd7 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -1,6 +1,7 @@ """ Implementation of gradient descent algorithm for minimizing cost of a linear hypothesis function. """ +from __future__ import print_function import numpy # List of input, output pairs @@ -106,13 +107,13 @@ def run_gradient_descent(): atol=absolute_error_limit, rtol=relative_error_limit): break parameter_vector = temp_parameter_vector - print("Number of iterations:", j) + print(("Number of iterations:", j)) 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__': diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index fd33c3580753..eb1f019c502c 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -7,6 +7,7 @@ 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/machine_learning/perceptron.py b/machine_learning/perceptron.py index 0cd89df1688f..8ac3e8fc69e9 100644 --- a/machine_learning/perceptron.py +++ b/machine_learning/perceptron.py @@ -9,6 +9,7 @@ p2 = 1 ''' +from __future__ import print_function import random @@ -52,7 +53,7 @@ def trannig(self): epoch_count = epoch_count + 1 # if you want controle the epoch or just by erro if erro == False: - print('\nEpoch:\n',epoch_count) + print(('\nEpoch:\n',epoch_count)) print('------------------------\n') #if epoch_count > self.epoch_number or not erro: break @@ -66,10 +67,10 @@ def sort(self, sample): y = self.sign(u) if y == -1: - print('Sample: ', sample) + print(('Sample: ', sample)) print('classification: P1') else: - print('Sample: ', sample) + print(('Sample: ', sample)) print('classification: P2') def sign(self, u): diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index 4204716cda91..64806a096950 100644 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -1,4 +1,4 @@ -import numpy +import numpy as np """ Here I implemented the scoring functions. MAE, MSE, RMSE, RMSLE are included. diff --git a/other/LinearCongruentialGenerator.py b/other/LinearCongruentialGenerator.py index b1eaa6119754..34abdf34eaf3 100644 --- a/other/LinearCongruentialGenerator.py +++ b/other/LinearCongruentialGenerator.py @@ -1,3 +1,4 @@ +from __future__ import print_function __author__ = "Tobias Carryer" from time import time @@ -31,4 +32,4 @@ def next_number( self ): # Show the LCG in action. lcg = LinearCongruentialGenerator(1664525, 1013904223, 2<<31) while True : - print lcg.next_number() \ No newline at end of file + print(lcg.next_number()) \ No newline at end of file diff --git a/other/anagrams.py b/other/anagrams.py index 6150ea8e6892..44cd96b75f62 100644 --- a/other/anagrams.py +++ b/other/anagrams.py @@ -1,3 +1,4 @@ +from __future__ import print_function import collections, pprint, time, os start_time = time.time() @@ -25,4 +26,4 @@ def anagram(myword): 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/euclidean_gcd.py b/other/euclidean_gcd.py index 13378379f286..30853e172076 100644 --- a/other/euclidean_gcd.py +++ b/other/euclidean_gcd.py @@ -1,3 +1,4 @@ +from __future__ import print_function # https://en.wikipedia.org/wiki/Euclidean_algorithm def euclidean_gcd(a, b): diff --git a/other/nested_brackets.py b/other/nested_brackets.py index f486190f48a5..76677d56439a 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -13,6 +13,7 @@ returns true if S is nested and false otherwise. ''' +from __future__ import print_function def is_balanced(S): @@ -39,10 +40,10 @@ def main(): S = input("Enter sequence of brackets: ") if is_balanced(S): - print(S, "is balanced") + print((S, "is balanced")) else: - print(S, "is not balanced") + print((S, "is not balanced")) if __name__ == "__main__": diff --git a/other/password_generator.py b/other/password_generator.py index 10ba77088eac..863474906dbd 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -1,3 +1,4 @@ +from __future__ import print_function import string import random diff --git a/other/tower_of_hanoi.py b/other/tower_of_hanoi.py index de0cb6218705..dc15b2ce8e58 100644 --- a/other/tower_of_hanoi.py +++ b/other/tower_of_hanoi.py @@ -1,3 +1,4 @@ +from __future__ import print_function def moveTower(height, fromPole, toPole, withPole): ''' >>> moveTower(3, 'A', 'B', 'C') @@ -15,7 +16,7 @@ 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: ')) diff --git a/other/two-sum.py b/other/two-sum.py index 4a522b6d4ec5..d4484aa85505 100644 --- a/other/two-sum.py +++ b/other/two-sum.py @@ -9,6 +9,7 @@ Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1]. """ +from __future__ import print_function def twoSum(nums, target): """ diff --git a/other/word_patterns.py b/other/word_patterns.py index 827c9fa8b412..c33d520087f7 100644 --- a/other/word_patterns.py +++ b/other/word_patterns.py @@ -1,3 +1,4 @@ +from __future__ import print_function import pprint, time def getWordPattern(word): @@ -32,7 +33,7 @@ def main(): fo.write(pprint.pformat(allPatterns)) totalTime = round(time.time() - startTime, 2) - print('Done! [', totalTime, 'seconds ]') + print(('Done! [', totalTime, 'seconds ]')) if __name__ == '__main__': main() diff --git a/searches/binary_search.py b/searches/binary_search.py index c54aa96a1c5d..93bf189cc08f 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -12,6 +12,11 @@ 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 @@ -137,23 +142,14 @@ def __assert_sorted(collection): if __name__ == '__main__': import sys - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by comma:\n') + 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 binary search') - target_input = input_function( - 'Enter a single number to be found in the list:\n' - ) + target_input = raw_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 068d9c554f43..7b765c454d06 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -4,6 +4,11 @@ from __future__ import print_function import bisect +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + def interpolation_search(sorted_collection, item): """Pure implementation of interpolation search algorithm in Python @@ -77,26 +82,18 @@ def __assert_sorted(collection): if __name__ == '__main__': import sys - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - user_input = input_function('Enter numbers separated by comma:\n') + 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') - target_input = input_function( - 'Enter a single number to be found in the list:\n' - ) + target_input = raw_input('Enter a single number to be found in the list:\n') target = int(target_input) result = interpolation_search(collection, target) if result is not None: print('{} found at positions: {}'.format(target, result)) else: - print('Not found') \ No newline at end of file + print('Not found') diff --git a/searches/jump_search.py b/searches/jump_search.py index e2b4008a48d3..4cff92bb585c 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -1,3 +1,4 @@ +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 ce8098b1addc..50c6eaad5e9b 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -11,6 +11,10 @@ """ 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 @@ -39,21 +43,10 @@ def linear_search(sequence, target): if __name__ == '__main__': - import sys - - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by coma:\n') + user_input = raw_input('Enter numbers separated by coma:\n').strip() sequence = [int(item) for item in user_input.split(',')] - target_input = input_function( - 'Enter a single number to be found in the list:\n' - ) + target_input = raw_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/ternary_search.py b/searches/ternary_search.py index 3b1c75314f4e..c610f9b3c6da 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -6,9 +6,15 @@ 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 @@ -81,16 +87,7 @@ def __assert_sorted(collection): if __name__ == '__main__': - - # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by coma:\n') + user_input = raw_input('Enter numbers separated by coma:\n').strip() collection = [int(item) for item in user_input.split(',')] try: @@ -98,9 +95,7 @@ def __assert_sorted(collection): except ValueError: sys.exit('Sequence must be sorted to apply the ternary search') - target_input = input_function( - 'Enter a single number to be found in the list:\n' - ) + target_input = raw_input('Enter a single number to be found in the list:\n') target = int(target_input) result1 = ite_ternary_search(collection, target) result2 = rec_ternary_search(0, len(collection)-1, collection, target) diff --git a/sorts/bogosort.py b/sorts/bogosort.py index ce1982c53218..2a3d320b00f0 100644 --- a/sorts/bogosort.py +++ b/sorts/bogosort.py @@ -39,15 +39,11 @@ def isSorted(collection): return collection if __name__ == '__main__': - import sys + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') + user_input = raw_input('Enter numbers separated by a comma:\n').stript() unsorted = [int(item) for item in user_input.split(',')] print(bogosort(unsorted)) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index d26adc89cd5e..b7f1b3c050e2 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -40,14 +40,11 @@ def bubble_sort(collection): if __name__ == '__main__': - import sys - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') + 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(bubble_sort(unsorted)) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index e378d65f4033..1c4b4577af61 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -13,6 +13,7 @@ # Time Complexity of Solution: # Best Case O(n); Average Case O(n); Worst Case O(n) +from __future__ import print_function from P26_InsertionSort import insertionSort import math diff --git a/sorts/cocktail_shaker_sort.py b/sorts/cocktail_shaker_sort.py index c09d64408aff..8ad3383bbe9f 100644 --- a/sorts/cocktail_shaker_sort.py +++ b/sorts/cocktail_shaker_sort.py @@ -21,16 +21,12 @@ def cocktail_shaker_sort(unsorted): return unsorted if __name__ == '__main__': - import sys - - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 - user_input = input_function('Enter numbers separated by a comma:\n') + user_input = raw_input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] cocktail_shaker_sort(unsorted) - print(unsorted) \ No newline at end of file + print(unsorted) diff --git a/sorts/counting_sort.py b/sorts/counting_sort.py index 13e4554aeb4c..4ca682b13cca 100644 --- a/sorts/counting_sort.py +++ b/sorts/counting_sort.py @@ -59,14 +59,11 @@ def counting_sort(collection): if __name__ == '__main__': - import sys - # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 - user_input = input_function('Enter numbers separated by a comma:\n') + user_input = raw_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/countingsort.py b/sorts/countingsort.py index c7b502d8fd3a..18ee8b851fd7 100644 --- a/sorts/countingsort.py +++ b/sorts/countingsort.py @@ -1,3 +1,4 @@ +from __future__ import print_function # Python program for counting sort # This is the main function that sort the given string arr[] in diff --git a/sorts/external-sort.py b/sorts/external-sort.py index eca26012d056..6c4adc94c0f0 100644 --- a/sorts/external-sort.py +++ b/sorts/external-sort.py @@ -158,4 +158,4 @@ def main(): if __name__ == '__main__': -main() \ No newline at end of file + main() diff --git a/sorts/gnome_sort.py b/sorts/gnome_sort.py index 4f04ff38429c..2927b097f11d 100644 --- a/sorts/gnome_sort.py +++ b/sorts/gnome_sort.py @@ -19,16 +19,12 @@ def gnome_sort(unsorted): i = 1 if __name__ == '__main__': - import sys - - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 - user_input = input_function('Enter numbers separated by a comma:\n') + user_input = raw_input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] gnome_sort(unsorted) - print(unsorted) \ No newline at end of file + print(unsorted) diff --git a/sorts/heap_sort.py b/sorts/heap_sort.py index 2d9dd844d0fa..3c72abca8059 100644 --- a/sorts/heap_sort.py +++ b/sorts/heap_sort.py @@ -54,12 +54,11 @@ def heap_sort(unsorted): return unsorted if __name__ == '__main__': - import sys - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 - user_input = input_function('Enter numbers separated by a comma:\n') + user_input = raw_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 33bd27c8f476..b7a4aa7a3c33 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -39,15 +39,11 @@ def insertion_sort(collection): if __name__ == '__main__': - import sys + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') + user_input = raw_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 ca8dbc33c1e5..ca4d319fa7f1 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -62,15 +62,11 @@ def merge_sort(collection): if __name__ == '__main__': - import sys + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') + 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)) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 52e37b5877b1..136cbc021669 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -40,15 +40,11 @@ def quick_sort(ARRAY): if __name__ == '__main__': - import sys + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') + user_input = raw_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/random_normaldistribution_quicksort.py b/sorts/random_normaldistribution_quicksort.py index 19b180578ad6..bd730b3b1e6d 100644 --- a/sorts/random_normaldistribution_quicksort.py +++ b/sorts/random_normaldistribution_quicksort.py @@ -1,3 +1,4 @@ +from __future__ import print_function from random import randint from tempfile import TemporaryFile import numpy as np diff --git a/sorts/selection_sort.py b/sorts/selection_sort.py index 752496e98487..432d14090b12 100644 --- a/sorts/selection_sort.py +++ b/sorts/selection_sort.py @@ -43,14 +43,11 @@ def selection_sort(collection): if __name__ == '__main__': - import sys - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') + 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(selection_sort(unsorted)) diff --git a/sorts/shell_sort.py b/sorts/shell_sort.py index de3d84f72db2..dc1846758243 100644 --- a/sorts/shell_sort.py +++ b/sorts/shell_sort.py @@ -44,14 +44,11 @@ def shell_sort(collection): return collection if __name__ == '__main__': - import sys - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') + 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(shell_sort(unsorted)) diff --git a/sorts/timsort.py b/sorts/timsort.py index 8c75b5191328..80c5cd1e8d3f 100644 --- a/sorts/timsort.py +++ b/sorts/timsort.py @@ -1,3 +1,4 @@ +from __future__ import print_function def binary_search(lst, item, start, end): if start == end: if lst[start] > item: diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py index 5de1cc2b703f..52dc34f4f733 100644 --- a/sorts/topological_sort.py +++ b/sorts/topological_sort.py @@ -1,3 +1,4 @@ +from __future__ import print_function # a # / \ # b c diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index 9d14a1e7e070..cbcaf08b7b03 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -4,6 +4,11 @@ from __future__ import print_function import queue +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + class TreeNode: def __init__(self, data): @@ -15,26 +20,26 @@ 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=input() - if check=='N' or check=='n': + check = raw_input().strip().lower() + if check == 'n': return None - data=int(check) + data = int(check) q = queue.Queue() tree_node = TreeNode(data) q.put(tree_node) while not q.empty(): node_found = q.get() print("Enter the left node of %s: " % node_found.data, end="") - check=input() - if check=='N' or check =='n': + check = raw_input().strip().lower() + if check == 'n': return tree_node left_data = int(check) left_node = TreeNode(left_data) node_found.left = left_node q.put(left_node) print("Enter the right node of %s: " % node_found.data, end="") - check = input() - if check == 'N' or check == 'n': + check = raw_input().strip().lower() + if check == 'n': return tree_node right_data = int(check) right_node = TreeNode(right_data) @@ -81,15 +86,7 @@ def level_order(node): if __name__ == '__main__': - import sys - print("\n********* Binary Tree Traversals ************\n") - # For python 2.x and 3.x compatibility: 3.x has no raw_input builtin - # otherwise 2.x's input builtin function is too "smart" - if sys.version_info.major < 3: - input_function = raw_input - else: - input_function = input node = build_tree() print("\n********* Pre Order Traversal ************") From e31c780d94acc0c3041f0b790dff3466262f4867 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 25 Nov 2017 12:41:55 +0100 Subject: [PATCH 0006/2908] Modernize Python 2 code to get ready for Python 3 --- ciphers/playfair_cipher.py | 2 +- data_structures/AVL/AVL.py | 1 + data_structures/Graph/BellmanFord.py | 2 ++ data_structures/Graph/BreadthFirstSearch.py | 2 ++ data_structures/Graph/DepthFirstSearch.py | 2 ++ data_structures/Graph/Dijkstra.py | 1 + data_structures/Graph/FloydWarshall.py | 11 ++++++----- data_structures/Graph/Graph_list.py | 3 +++ data_structures/Graph/Graph_matrix.py | 3 +++ data_structures/Graph/dijkstra_algorithm.py | 1 + data_structures/LinkedList/DoublyLinkedList.py | 5 ++++- dynamic_programming/edit_distance.py | 2 ++ dynamic_programming/fibonacci.py | 1 + machine_learning/k_means_clust.py | 3 ++- machine_learning/scoring_functions.py | 2 +- other/FindingPrimes.py | 3 +++ sorts/cyclesort.py | 3 +++ 17 files changed, 38 insertions(+), 9 deletions(-) diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index da547a337a94..8ef09160d36b 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -85,7 +85,7 @@ def decode(ciphertext, key): plaintext = "" # https://en.wikipedia.org/wiki/Playfair_cipher#Description - for char1, char2 in chunk(ciphertext, 2): + for char1, char2 in chunker(ciphertext, 2): row1, col1 = divmod(table.index(char1), 5) row2, col2 = divmod(table.index(char2), 5) diff --git a/data_structures/AVL/AVL.py b/data_structures/AVL/AVL.py index 9717f1b6eff7..84f79fd1c14e 100644 --- a/data_structures/AVL/AVL.py +++ b/data_structures/AVL/AVL.py @@ -1,6 +1,7 @@ ''' A AVL tree ''' +from __future__ import print_function class Node: diff --git a/data_structures/Graph/BellmanFord.py b/data_structures/Graph/BellmanFord.py index de3077f09fb5..b9207c4225bb 100644 --- a/data_structures/Graph/BellmanFord.py +++ b/data_structures/Graph/BellmanFord.py @@ -1,3 +1,5 @@ +from __future__ import print_function + def printDist(dist, V): print("\nVertex Distance") for i in range(V): diff --git a/data_structures/Graph/BreadthFirstSearch.py b/data_structures/Graph/BreadthFirstSearch.py index 16b1b20074c1..02f6af83ff66 100644 --- a/data_structures/Graph/BreadthFirstSearch.py +++ b/data_structures/Graph/BreadthFirstSearch.py @@ -1,4 +1,6 @@ # Author: OMKAR PATHAK +from __future__ import print_function + class Graph(): def __init__(self): diff --git a/data_structures/Graph/DepthFirstSearch.py b/data_structures/Graph/DepthFirstSearch.py index 94ef3cb86571..0f10a8600099 100644 --- a/data_structures/Graph/DepthFirstSearch.py +++ b/data_structures/Graph/DepthFirstSearch.py @@ -1,4 +1,6 @@ # Author: OMKAR PATHAK +from __future__ import print_function + class Graph(): def __init__(self): diff --git a/data_structures/Graph/Dijkstra.py b/data_structures/Graph/Dijkstra.py index 30ab51d76598..ae6a1a2835c9 100644 --- a/data_structures/Graph/Dijkstra.py +++ b/data_structures/Graph/Dijkstra.py @@ -1,3 +1,4 @@ +from __future__ import print_function def printDist(dist, V): print("\nVertex Distance") diff --git a/data_structures/Graph/FloydWarshall.py b/data_structures/Graph/FloydWarshall.py index f0db329d0e13..fae8b19b351a 100644 --- a/data_structures/Graph/FloydWarshall.py +++ b/data_structures/Graph/FloydWarshall.py @@ -1,3 +1,4 @@ +from __future__ import print_function def printDist(dist, V): print("\nThe shortest path matrix using Floyd Warshall algorithm\n") @@ -7,7 +8,7 @@ def printDist(dist, V): print(int(dist[i][j]),end = "\t") else: print("INF",end="\t") - print(); + print() @@ -29,19 +30,19 @@ def FloydWarshall(graph, V): #MAIN -V = int(input("Enter number of vertices: ")); -E = int(input("Enter number of edges: ")); +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; + 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; + graph[src][dst] = weight FloydWarshall(graph, V) diff --git a/data_structures/Graph/Graph_list.py b/data_structures/Graph/Graph_list.py index 8e33cf55ce2f..d67bc96c4a81 100644 --- a/data_structures/Graph/Graph_list.py +++ b/data_structures/Graph/Graph_list.py @@ -1,3 +1,6 @@ +from __future__ import print_function + + class Graph: def __init__(self, vertex): self.vertex = vertex diff --git a/data_structures/Graph/Graph_matrix.py b/data_structures/Graph/Graph_matrix.py index 1998fec8d6fe..de25301d6dd1 100644 --- a/data_structures/Graph/Graph_matrix.py +++ b/data_structures/Graph/Graph_matrix.py @@ -1,3 +1,6 @@ +from __future__ import print_function + + class Graph: def __init__(self, vertex): diff --git a/data_structures/Graph/dijkstra_algorithm.py b/data_structures/Graph/dijkstra_algorithm.py index c43ff37f5336..985c7f6c1301 100644 --- a/data_structures/Graph/dijkstra_algorithm.py +++ b/data_structures/Graph/dijkstra_algorithm.py @@ -2,6 +2,7 @@ # 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/data_structures/LinkedList/DoublyLinkedList.py b/data_structures/LinkedList/DoublyLinkedList.py index d152e465b399..6f17b7c81033 100644 --- a/data_structures/LinkedList/DoublyLinkedList.py +++ b/data_structures/LinkedList/DoublyLinkedList.py @@ -3,6 +3,9 @@ - This is an example of a double ended, doubly linked list. - Each link references the next link and the previous one. ''' +from __future__ import print_function + + class LinkedList: def __init__(self): self.head = None @@ -70,4 +73,4 @@ class Link: def __init__(self, x): self.value = x def displayLink(self): - print("{}".format(self.value), end=" ") \ No newline at end of file + print("{}".format(self.value), end=" ") diff --git a/dynamic_programming/edit_distance.py b/dynamic_programming/edit_distance.py index 4a6e4cecf18f..335e5196ed53 100644 --- a/dynamic_programming/edit_distance.py +++ b/dynamic_programming/edit_distance.py @@ -7,6 +7,8 @@ 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. """ +from __future__ import print_function + class EditDistance: """ diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index abe628d036f8..b453ce255853 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -1,6 +1,7 @@ """ This is a pure Python implementation of Dynamic Programming solution to the fibonacci sequence problem. """ +from __future__ import print_function class Fibonacci: diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index c19832726aef..368739a45fe9 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -46,6 +46,7 @@ 5. Have fun.. ''' +from __future__ import print_function from sklearn.metrics import pairwise_distances import numpy as np @@ -169,4 +170,4 @@ def kmeans(data, k, initial_centroids, maxiter=500, record_heterogeneity=None, v 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) \ No newline at end of file + plot_heterogeneity(heterogeneity, k) diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index 64806a096950..0a988737f15f 100644 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -41,7 +41,7 @@ def rmse(predict, actual): actual = np.array(actual) difference = predict - actual - square_diff = np.square(dfference) + square_diff = np.square(difference) mean_square_diff = square_diff.mean() score = np.sqrt(mean_square_diff) return score diff --git a/other/FindingPrimes.py b/other/FindingPrimes.py index 3cb10f701ab3..035a14f4a335 100644 --- a/other/FindingPrimes.py +++ b/other/FindingPrimes.py @@ -2,6 +2,9 @@ -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 diff --git a/sorts/cyclesort.py b/sorts/cyclesort.py index 2cc6354176a3..d6762b08363e 100644 --- a/sorts/cyclesort.py +++ b/sorts/cyclesort.py @@ -1,4 +1,7 @@ # Code contributed by Honey Sharma +from __future__ import print_function + + def cycle_sort(array): ans = 0 From ad6b0f1c6b3d79a8a8a17ea38ead4b5936f11917 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 25 Nov 2017 12:46:31 +0100 Subject: [PATCH 0007/2908] Modernize Python 2 code to get ready for Python 3 --- Graphs/basic-graphs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Graphs/basic-graphs.py b/Graphs/basic-graphs.py index c18574fa9963..6e433b5bd725 100644 --- a/Graphs/basic-graphs.py +++ b/Graphs/basic-graphs.py @@ -187,8 +187,8 @@ def adjm(): """ -def floy(xxx_todo_changeme): - (A, n) = xxx_todo_changeme +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): @@ -259,9 +259,9 @@ def edglist(): """ -def krusk(xxx_todo_changeme1): +def krusk(E_and_n): # Sort edges on the basis of distance - (E, n) = xxx_todo_changeme1 + (E, n) = E_and_n E.sort(reverse=True, key=lambda x: x[2]) s = [set([i]) for i in range(1, n + 1)] while True: From ad265b9b6195a1185c85bd72b599369edbce085f Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Thu, 21 Dec 2017 19:08:58 +0100 Subject: [PATCH 0008/2908] XOR cipher algorithm a class with simple cipher algorithm that uses bitwise XOR --- ciphers/XOR_cipher.py | 239 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 ciphers/XOR_cipher.py diff --git a/ciphers/XOR_cipher.py b/ciphers/XOR_cipher.py new file mode 100644 index 000000000000..daa014d9895b --- /dev/null +++ b/ciphers/XOR_cipher.py @@ -0,0 +1,239 @@ +""" + 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 + """ + + #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 + """ + + # precondition + assert (isinstance(key,int) and isinstance(content,str)) + + # testing for default arguments + if (key == 0): + if (self.__key == 0): + key = 1 + else: + key = self.__key + + # make sure key can be any size + while (key > 255): + key -= 255 + + # This will be returned + ans = [] + resultNumber = 0 + + for ch in content: + ans.append(chr(ord(ch) ^ key)) + + return ans + + 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)) + + # testing for default arguments + if (key == 0): + if (self.__key == 0): + key = 1 + else: + key = self.__key + + # make sure key can be any size + while (key > 255): + key -= 255 + + # This will be returned + ans = [] + resultNumber = 0 + + for ch in content: + ans.append(chr(ord(ch) ^ key)) + + return ans + + + 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)) + + # testing for default arguments + if (key == 0): + if (self.__key == 0): + key = 1 + else: + key = self.__key + + # make sure key can be any size + while (key > 255): + key -= 255 + + # This will be returned + ans = "" + resultNumber = 0 + + for ch in content: + ans += chr(ord(ch) ^ key) + + return ans + + 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)) + + # testing for default arguments + if (key == 0): + if (self.__key == 0): + key = 1 + else: + key = self.__key + + # make sure key can be any size + while (key > 255): + key -= 255 + + # This will be returned + ans = "" + resultNumber = 0 + + for ch in content: + ans += chr(ord(ch) ^ key) + + return ans + + + 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 + """ + + #precondition + assert (isinstance(file,str) and isinstance(key,int)) + + try: + fin = open(file,"r") + fout = open("encrypt.out","w+") + + # actual encrypt-process + for line in fin: + fout.write(self.encrypt_string(line,key)) + + fin.close() + fout.close() + + except: + return False + + return True + + + 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 + """ + + #precondition + assert (isinstance(file,str) and isinstance(key,int)) + + try: + fin = open(file,"r") + fout = open("decrypt.out","w+") + + # actual encrypt-process + for line in fin: + fout.write(self.decrypt_string(line,key)) + + fin.close() + fout.close() + + except: + return False + + return True + + + + +# Tests +# crypt = XORCipher() +# key = 67 + +# # test enrcypt +# print crypt.encrypt("hallo welt",key) +# # test decrypt +# print crypt.decrypt(crypt.encrypt("hallo welt",key), key) + +# # test encrypt_string +# print crypt.encrypt_string("hallo welt",key) + +# # test decrypt_string +# print crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key) + +# if (crypt.encrypt_file("test.txt",key)): +# print "encrypt successful" +# else: +# print "encrypt unsuccessful" + +# if (crypt.decrypt_file("a.out",key)): +# print "decrypt successful" +# else: +# print "decrypt unsuccessful" \ No newline at end of file From b25bc2cb30644276eb7415dcfdb0edce4a838310 Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Fri, 22 Dec 2017 17:30:11 +0100 Subject: [PATCH 0009/2908] improvement I improve my code --- ciphers/XOR_cipher.py | 96 +++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 63 deletions(-) diff --git a/ciphers/XOR_cipher.py b/ciphers/XOR_cipher.py index daa014d9895b..1fe00a870869 100644 --- a/ciphers/XOR_cipher.py +++ b/ciphers/XOR_cipher.py @@ -38,12 +38,7 @@ def encrypt(self, content, key): # precondition assert (isinstance(key,int) and isinstance(content,str)) - # testing for default arguments - if (key == 0): - if (self.__key == 0): - key = 1 - else: - key = self.__key + key = key or self.__key or 1 # make sure key can be any size while (key > 255): @@ -51,7 +46,6 @@ def encrypt(self, content, key): # This will be returned ans = [] - resultNumber = 0 for ch in content: ans.append(chr(ord(ch) ^ key)) @@ -69,12 +63,7 @@ def decrypt(self,content,key): # precondition assert (isinstance(key,int) and isinstance(content,list)) - # testing for default arguments - if (key == 0): - if (self.__key == 0): - key = 1 - else: - key = self.__key + key = key or self.__key or 1 # make sure key can be any size while (key > 255): @@ -82,7 +71,6 @@ def decrypt(self,content,key): # This will be returned ans = [] - resultNumber = 0 for ch in content: ans.append(chr(ord(ch) ^ key)) @@ -101,12 +89,7 @@ def encrypt_string(self,content, key = 0): # precondition assert (isinstance(key,int) and isinstance(content,str)) - # testing for default arguments - if (key == 0): - if (self.__key == 0): - key = 1 - else: - key = self.__key + key = key or self.__key or 1 # make sure key can be any size while (key > 255): @@ -114,7 +97,6 @@ def encrypt_string(self,content, key = 0): # This will be returned ans = "" - resultNumber = 0 for ch in content: ans += chr(ord(ch) ^ key) @@ -132,12 +114,7 @@ def decrypt_string(self,content,key = 0): # precondition assert (isinstance(key,int) and isinstance(content,str)) - # testing for default arguments - if (key == 0): - if (self.__key == 0): - key = 1 - else: - key = self.__key + key = key or self.__key or 1 # make sure key can be any size while (key > 255): @@ -145,8 +122,7 @@ def decrypt_string(self,content,key = 0): # This will be returned ans = "" - resultNumber = 0 - + for ch in content: ans += chr(ord(ch) ^ key) @@ -166,15 +142,12 @@ def encrypt_file(self, file, key = 0): assert (isinstance(file,str) and isinstance(key,int)) try: - fin = open(file,"r") - fout = open("encrypt.out","w+") - - # actual encrypt-process - for line in fin: - fout.write(self.encrypt_string(line,key)) + with open(file,"r") as fin: + with open("encrypt.out","w+") as fout: - fin.close() - fout.close() + # actual encrypt-process + for line in fin: + fout.write(self.encrypt_string(line,key)) except: return False @@ -195,15 +168,12 @@ def decrypt_file(self,file, key): assert (isinstance(file,str) and isinstance(key,int)) try: - fin = open(file,"r") - fout = open("decrypt.out","w+") - - # actual encrypt-process - for line in fin: - fout.write(self.decrypt_string(line,key)) + with open(file,"r") as fin: + with open("decrypt.out","w+") as fout: - fin.close() - fout.close() + # actual encrypt-process + for line in fin: + fout.write(self.decrypt_string(line,key)) except: return False @@ -214,26 +184,26 @@ def decrypt_file(self,file, key): # Tests -# crypt = XORCipher() -# key = 67 +crypt = XORCipher() +key = 67 -# # test enrcypt -# print crypt.encrypt("hallo welt",key) -# # test decrypt -# print crypt.decrypt(crypt.encrypt("hallo welt",key), key) +# test enrcypt +print crypt.encrypt("hallo welt",key) +# test decrypt +print crypt.decrypt(crypt.encrypt("hallo welt",key), key) -# # test encrypt_string -# print crypt.encrypt_string("hallo welt",key) +# test encrypt_string +print crypt.encrypt_string("hallo welt",key) -# # test decrypt_string -# print crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key) +# test decrypt_string +print crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key) -# if (crypt.encrypt_file("test.txt",key)): -# print "encrypt successful" -# else: -# print "encrypt unsuccessful" +if (crypt.encrypt_file("test.txt",key)): + print "encrypt successful" +else: + print "encrypt unsuccessful" -# if (crypt.decrypt_file("a.out",key)): -# print "decrypt successful" -# else: -# print "decrypt unsuccessful" \ No newline at end of file +if (crypt.decrypt_file("encrypt.out",key)): + print "decrypt successful" +else: + print "decrypt unsuccessful" \ No newline at end of file From 0e7d4483ca18786ab45388fb2f191cb2567f3705 Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Fri, 22 Dec 2017 17:33:09 +0100 Subject: [PATCH 0010/2908] comment out I comment out some code lines in the test-section --- ciphers/XOR_cipher.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ciphers/XOR_cipher.py b/ciphers/XOR_cipher.py index 1fe00a870869..727fac3b0703 100644 --- a/ciphers/XOR_cipher.py +++ b/ciphers/XOR_cipher.py @@ -184,26 +184,26 @@ def decrypt_file(self,file, key): # Tests -crypt = XORCipher() -key = 67 +# crypt = XORCipher() +# key = 67 -# test enrcypt -print crypt.encrypt("hallo welt",key) -# test decrypt -print crypt.decrypt(crypt.encrypt("hallo welt",key), key) +# # test enrcypt +# print crypt.encrypt("hallo welt",key) +# # test decrypt +# print crypt.decrypt(crypt.encrypt("hallo welt",key), key) -# test encrypt_string -print crypt.encrypt_string("hallo welt",key) +# # test encrypt_string +# print crypt.encrypt_string("hallo welt",key) -# test decrypt_string -print crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key) +# # test decrypt_string +# print crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key) -if (crypt.encrypt_file("test.txt",key)): - print "encrypt successful" -else: - print "encrypt unsuccessful" +# if (crypt.encrypt_file("test.txt",key)): +# print "encrypt successful" +# else: +# print "encrypt unsuccessful" -if (crypt.decrypt_file("encrypt.out",key)): - print "decrypt successful" -else: - print "decrypt unsuccessful" \ No newline at end of file +# if (crypt.decrypt_file("encrypt.out",key)): +# print "decrypt successful" +# else: +# print "decrypt unsuccessful" \ No newline at end of file From 63189fa015274cbaf506169d15cfaf5129c0e543 Mon Sep 17 00:00:00 2001 From: ashu01 Date: Sat, 30 Dec 2017 08:41:31 +0530 Subject: [PATCH 0011/2908] typo fix --- Bisection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Bisection.py b/Bisection.py index 590197cb7c6f..a6af547db03b 100644 --- a/Bisection.py +++ b/Bisection.py @@ -9,13 +9,13 @@ 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 noone of these are root and they are both possitive or negative, - # then his algorith can't find the root + 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) / 2 - while abs(start - mid) > 0.0000001: # untill we achive percise equals to 10^-7 + while abs(start - mid) > 0.0000001: # until we achieve precise equals to 10^-7 if function(mid) == 0: return mid elif function(mid) * function(start) < 0: @@ -27,7 +27,7 @@ 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 print(bisection(f, 1, 1000)) From 4fb978cf58f79f65cfd43b84ba52c457f8d710a2 Mon Sep 17 00:00:00 2001 From: ashu01 Date: Sun, 31 Dec 2017 14:30:31 +0530 Subject: [PATCH 0012/2908] 1. typo fix {playfair_cipher.py, AVL.py} 2. Corrected Logic {AVL.py, 104-107} 3. Removed unnecessary semicolons {BellmanFord.py, Dijkstra.py} --- ciphers/playfair_cipher.py | 6 +++--- data_structures/AVL/AVL.py | 14 +++++++------- data_structures/Graph/BellmanFord.py | 10 +++++----- data_structures/Graph/Dijkstra.py | 12 ++++++------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 8ef09160d36b..20449b161963 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -13,8 +13,8 @@ def chunker(seq, size): def prepare_input(dirty): """ - Prepare the plaintext by uppcasing it - and seperating repeated letters with X's + 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]) @@ -38,7 +38,7 @@ def prepare_input(dirty): def generate_table(key): - # I and J are used interchangably to allow + # 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 diff --git a/data_structures/AVL/AVL.py b/data_structures/AVL/AVL.py index 84f79fd1c14e..d01e8f825368 100644 --- a/data_structures/AVL/AVL.py +++ b/data_structures/AVL/AVL.py @@ -1,6 +1,6 @@ -''' -A AVL tree -''' +""" +An AVL tree +""" from __future__ import print_function @@ -101,10 +101,10 @@ def rebalance(self, node): if height_left > height_right: left_child = n.left if left_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) + 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 diff --git a/data_structures/Graph/BellmanFord.py b/data_structures/Graph/BellmanFord.py index b9207c4225bb..82db80546b94 100644 --- a/data_structures/Graph/BellmanFord.py +++ b/data_structures/Graph/BellmanFord.py @@ -7,11 +7,11 @@ def printDist(dist, V): print(i,"\t",int(dist[i]),end = "\t") else: print(i,"\t","INF",end="\t") - print(); + print() def BellmanFord(graph, V, E, src): mdist=[float('inf') for i in range(V)] - mdist[src] = 0.0; + mdist[src] = 0.0 for i in range(V-1): for j in range(V): @@ -35,13 +35,13 @@ def BellmanFord(graph, V, E, src): #MAIN -V = int(input("Enter number of vertices: ")); -E = int(input("Enter number of edges: ")); +V = int(input("Enter number of vertices: ")) +E = int(input("Enter number of edges: ")) 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) diff --git a/data_structures/Graph/Dijkstra.py b/data_structures/Graph/Dijkstra.py index ae6a1a2835c9..8917171417c4 100644 --- a/data_structures/Graph/Dijkstra.py +++ b/data_structures/Graph/Dijkstra.py @@ -7,7 +7,7 @@ def printDist(dist, V): print(i,"\t",int(dist[i]),end = "\t") else: print(i,"\t","INF",end="\t") - print(); + print() def minDist(mdist, vset, V): minVal = float('inf') @@ -25,7 +25,7 @@ def Dijkstra(graph, V, src): for i in range(V-1): u = minDist(mdist, vset, V) - vset[u] = True; + 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]: @@ -38,20 +38,20 @@ def Dijkstra(graph, V, src): #MAIN -V = int(input("Enter number of vertices: ")); -E = int(input("Enter number of edges: ")); +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; + 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; + graph[src][dst] = weight gsrc = int(input("\nEnter shortest path source:")) Dijkstra(graph, V, gsrc) From 06c7827a9428c5ce14be4f630e5b8082d073f9a0 Mon Sep 17 00:00:00 2001 From: ashu01 Date: Sun, 31 Dec 2017 14:33:14 +0530 Subject: [PATCH 0013/2908] 1. typo fix {Arrays| --- data_structures/Arrays | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/Arrays b/data_structures/Arrays index 03eaefac17fa..e2c1243f5f96 100644 --- a/data_structures/Arrays +++ b/data_structures/Arrays @@ -1 +1 @@ -Arrays implimentation using python programming. +Arrays implementation using python programming. From c9debdbd413170630bd0050c1a8e816fe8c5b143 Mon Sep 17 00:00:00 2001 From: ashu01 Date: Sun, 31 Dec 2017 14:36:29 +0530 Subject: [PATCH 0014/2908] 1. Removed ; --- dynamic_programming/FloydWarshall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic_programming/FloydWarshall.py b/dynamic_programming/FloydWarshall.py index bf77141244e9..038499ca03b6 100644 --- a/dynamic_programming/FloydWarshall.py +++ b/dynamic_programming/FloydWarshall.py @@ -8,7 +8,7 @@ def __init__(self, N = 0): # a graph with Node 0,1,...,N-1 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; + self.dp[u][v] = w def floyd_warshall(self): for k in range(0,self.N): From 4adf3b9492d2b9f6c049e302c853049d62583a31 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 6 Jan 2018 07:54:10 +0100 Subject: [PATCH 0015/2908] Pass flake8 tests Without these changes, Python syntax errors are raised. flake8 should be run by this repo's .travis.yml file but it is currently turned off. --- other/password_generator.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/other/password_generator.py b/other/password_generator.py index 858a24da0290..8916079fc758 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -13,17 +13,23 @@ 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 # 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... + + def random_number(ctbi, i): - - - + pass # Put your code here... + + def random_letters(ctbi, i): - - - + pass # Put your code here... + + def random_characters(ctbi, i): + pass # Put your code here... From 5d4471d35a22ef267e91cedf2daae46aaa24a7b4 Mon Sep 17 00:00:00 2001 From: Sayan Bandyopadhyay Date: Sun, 7 Jan 2018 13:21:05 +0530 Subject: [PATCH 0016/2908] Update cyclesort.py Changing for Python 3 using exception handling for robust code --- sorts/cyclesort.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sorts/cyclesort.py b/sorts/cyclesort.py index d6762b08363e..ee19a1ade360 100644 --- a/sorts/cyclesort.py +++ b/sorts/cyclesort.py @@ -44,7 +44,13 @@ def cycle_sort(array): # Main Code starts here -user_input = input('Enter numbers separated by a comma:\n') +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') unsorted = [int(item) for item in user_input.split(',')] n = len(unsorted) cycle_sort(unsorted) From 0d36dc60c50acb555e1aa7f6127ddb4fd4ee9040 Mon Sep 17 00:00:00 2001 From: damelLP Date: Sun, 7 Jan 2018 12:49:51 +0000 Subject: [PATCH 0017/2908] fixed failure function and cleaned up code in kmp + added rabin-karp --- strings/knuth-morris-pratt.py | 53 +++++++++++++++++++++++++---------- strings/rabin-karp.py | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 strings/rabin-karp.py diff --git a/strings/knuth-morris-pratt.py b/strings/knuth-morris-pratt.py index 731f75c586e6..4553944284be 100644 --- a/strings/knuth-morris-pratt.py +++ b/strings/knuth-morris-pratt.py @@ -1,4 +1,4 @@ -def kmp(pattern, text, len_p=None, len_t=None): +def kmp(pattern, text): """ The Knuth-Morris-Pratt Algorithm for finding a pattern within a piece of text with complexity O(n + m) @@ -14,14 +14,7 @@ def kmp(pattern, text, len_p=None, len_t=None): """ # 1) Construct the failure array - failure = [0] - i = 0 - for index, char in enumerate(pattern[1:]): - if pattern[i] == char: - i += 1 - else: - i = 0 - failure.append(i) + failure = get_failure_array(pattern) # 2) Step through text searching for pattern i, j = 0, 0 # index into text, pattern @@ -29,20 +22,38 @@ def kmp(pattern, text, len_p=None, len_t=None): if pattern[j] == text[i]: if j == (len(pattern) - 1): return True - i += 1 j += 1 # if this is a prefix in our pattern # just go back far enough to continue - elif failure[j] > 0: - j = failure[j] - 1 - else: - i += 1 + elif j > 0: + j = failure[j - 1] + continue + i += 1 return False -if __name__ == '__main__': +def get_failure_array(pattern): + """ + Calculates the new index we should go to if we fail a comparison + :param pattern: + :return: + """ + failure = [0] + i = 0 + j = 1 + while j < len(pattern): + if pattern[i] == pattern[j]: + i += 1 + elif i > 0: + i = failure[i-1] + continue + j += 1 + failure.append(i) + return failure + +if __name__ == '__main__': # Test 1) pattern = "abc1abc12" text1 = "alskfjaldsabc1abc1abc12k23adsfabcabc" @@ -54,4 +65,16 @@ def kmp(pattern, text, len_p=None, len_t=None): text = "ABABZABABYABABX" assert kmp(pattern, text) + # Test 3) + pattern = "AAAB" + text = "ABAAAAAB" + assert kmp(pattern, text) + + # Test 4) + pattern = "abcdabcy" + text = "abcxabcdabxabcdabcdabcy" + assert kmp(pattern, text) + # Test 5) + pattern = "aabaabaaa" + assert get_failure_array(pattern) == [0, 1, 0, 1, 2, 3, 4, 5, 2] diff --git a/strings/rabin-karp.py b/strings/rabin-karp.py new file mode 100644 index 000000000000..04a849266ead --- /dev/null +++ b/strings/rabin-karp.py @@ -0,0 +1,50 @@ +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. + + 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 + + """ + p_len = len(pattern) + p_hash = hash(pattern) + + for i in range(0, len(text) - (p_len - 1)): + + # written like this t + text_hash = hash(text[i:i + p_len]) + if text_hash == p_hash and \ + text[i:i + p_len] == pattern: + return True + return False + + +if __name__ == '__main__': + # Test 1) + pattern = "abc1abc12" + text1 = "alskfjaldsabc1abc1abc12k23adsfabcabc" + text2 = "alskfjaldsk23adsfabcabc" + assert rabin_karp(pattern, text1) and not rabin_karp(pattern, text2) + + # Test 2) + pattern = "ABABX" + text = "ABABZABABYABABX" + assert rabin_karp(pattern, text) + + # Test 3) + pattern = "AAAB" + text = "ABAAAAAB" + assert rabin_karp(pattern, text) + + # Test 4) + pattern = "abcdabcy" + text = "abcxabcdabxabcdabcdabcy" + assert rabin_karp(pattern, text) From 122cf4536dab795d6e883108985353b372a5ecf6 Mon Sep 17 00:00:00 2001 From: Sayan Bandyopadhyay Date: Sun, 7 Jan 2018 22:38:46 +0530 Subject: [PATCH 0018/2908] Update NeutonMethod.py Removing requirement of math library to make code faster --- NeutonMethod.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/NeutonMethod.py b/NeutonMethod.py index 791c256afcd6..c3d5efb47d01 100644 --- a/NeutonMethod.py +++ b/NeutonMethod.py @@ -1,5 +1,3 @@ -import math - def newton(function,function1,startingInt): #function is the f(x) and function1 is the f'(x) x_n=startingInt while True: @@ -9,9 +7,9 @@ def newton(function,function1,startingInt): #function is the f(x) and function1 x_n=x_n1 def f(x): - return math.pow(x,3)-2*x-5 + return (x**3)-2*x-5 def f1(x): - return 3*math.pow(x,2)-2 + return 3*(x**2)-2 -print(newton(f,f1,3)) \ No newline at end of file +print(newton(f,f1,3)) From 51492b78de41a6f49bdf4588360838c10329bf8d Mon Sep 17 00:00:00 2001 From: damelLP Date: Sun, 14 Jan 2018 15:50:52 +0000 Subject: [PATCH 0019/2908] Added Tarjan's algorithm for finding strongly connected components --- Graphs/tarjans_scc.py | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 Graphs/tarjans_scc.py diff --git a/Graphs/tarjans_scc.py b/Graphs/tarjans_scc.py new file mode 100644 index 000000000000..89754e593508 --- /dev/null +++ b/Graphs/tarjans_scc.py @@ -0,0 +1,78 @@ +from collections import deque + + +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). + + 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 + connected component. + + 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) + stack = deque() + on_stack = [False for _ in range(n)] + index_of = [-1 for _ in range(n)] + lowlink_of = index_of[:] + + def strong_connect(v, index, components): + index_of[v] = index # the number when this node is seen + lowlink_of[v] = index # lowest rank node reachable from here + index += 1 + stack.append(v) + on_stack[v] = True + + 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] + elif on_stack[w]: + 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 = [] + w = stack.pop() + on_stack[w] = False + component.append(w) + while w != v: + w = stack.pop() + on_stack[w] = False + component.append(w) + components.append(component) + return index + + components = [] + for v in range(n): + if index_of[v] == -1: + strong_connect(v, 0, components) + + return components + + +def create_graph(n, edges): + g = [[] for _ in range(n)] + for u, v in edges: + g[u].append(v) + return g + + +if __name__ == '__main__': + # Test + n_vertices = 7 + source = [0, 0, 1, 2, 3, 3, 4, 4, 6] + target = [1, 3, 2, 0, 1, 4, 5, 6, 5] + edges = [(u, v) for u, v in zip(source, target)] + g = create_graph(n_vertices, edges) + + assert [[5], [6], [4], [3, 2, 1, 0]] == tarjan(g) From b3873be7b55f5672fc0f9f6496358b4f802358a6 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 20 Jan 2018 12:31:12 +0100 Subject: [PATCH 0020/2908] noqa to silence flake8 on Python 3 only syntax --- data_structures/Trie/Trie.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/Trie/Trie.py b/data_structures/Trie/Trie.py index 7c886144d1f4..b60afd29db3d 100644 --- a/data_structures/Trie/Trie.py +++ b/data_structures/Trie/Trie.py @@ -21,7 +21,7 @@ def insert_many(self, words: [str]): for word in words: self.insert(word) - def insert(self, word: str): + def insert(self, word: str): # noqa: F821 This syntax is Python 3 only """ Inserts a word into the Trie :param word: word to be inserted From cc5102ab019fca9b19c69db16fa1b43412acb5d7 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 20 Jan 2018 12:33:27 +0100 Subject: [PATCH 0021/2908] noqa to silence flake8 on Python 3 only syntax --- dynamic_programming/fastfibonacci.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dynamic_programming/fastfibonacci.py b/dynamic_programming/fastfibonacci.py index cdfa2dd08084..2c3960d114ca 100644 --- a/dynamic_programming/fastfibonacci.py +++ b/dynamic_programming/fastfibonacci.py @@ -7,14 +7,14 @@ # returns F(n) -def fibonacci(n: int): +def fibonacci(n: int): # noqa: F821 This syntax is Python 3 only if n < 0: raise ValueError("Negative arguments are not supported") return _fib(n)[0] # returns (F(n), F(n-1)) -def _fib(n: int): +def _fib(n: int): # noqa: F821 This syntax is Python 3 only if n == 0: # (F(0), F(1)) return (0, 1) From 3f6760ee159d2c2e6577aab0d052b7781aa10bb2 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 20 Jan 2018 12:35:12 +0100 Subject: [PATCH 0022/2908] noqa: F821 This syntax is Python 3 only --- data_structures/Trie/Trie.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/Trie/Trie.py b/data_structures/Trie/Trie.py index b60afd29db3d..63e291b9738a 100644 --- a/data_structures/Trie/Trie.py +++ b/data_structures/Trie/Trie.py @@ -12,7 +12,7 @@ def __init__(self): self.nodes = dict() # Mapping from char to TrieNode self.is_leaf = False - def insert_many(self, words: [str]): + def insert_many(self, words: [str]): # noqa: F821 This syntax is Python 3 only """ Inserts a list of words into the Trie :param words: list of string words @@ -34,7 +34,7 @@ def insert(self, word: str): # noqa: F821 This syntax is Python 3 only curr = curr.nodes[char] curr.is_leaf = True - def find(self, word: str) -> bool: + def find(self, word: str) -> bool: # noqa: F821 This syntax is Python 3 only """ Tries to find word in a Trie :param word: word to look for @@ -48,7 +48,7 @@ def find(self, word: str) -> bool: return curr.is_leaf -def print_words(node: TrieNode, word: str): +def print_words(node: TrieNode, word: str): # noqa: F821 This syntax is Python 3 only """ Prints all the words in a Trie :param node: root node of Trie From 4ee0e620cbd15bf73143b3c5ffa63304551ddb31 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 21 Jan 2018 08:25:19 +0100 Subject: [PATCH 0023/2908] Modernize Python 2 code to get ready for Python 3 AGAIN --- File_Transfer_Protocol/ftp_client_server.py | 22 ++++++++++----------- Project Euler/Problem 29/solution.py | 17 ++++++++-------- data_structures/Trie/Trie.py | 8 ++++---- dynamic_programming/abbreviation.py | 4 +++- dynamic_programming/fastfibonacci.py | 4 ++-- machine_learning/scoring_functions.py | 6 +++--- 6 files changed, 31 insertions(+), 30 deletions(-) diff --git a/File_Transfer_Protocol/ftp_client_server.py b/File_Transfer_Protocol/ftp_client_server.py index ff7a8ec2edf1..f73f858431f3 100644 --- a/File_Transfer_Protocol/ftp_client_server.py +++ b/File_Transfer_Protocol/ftp_client_server.py @@ -8,21 +8,21 @@ s.bind((host, port)) # Bind to the port s.listen(5) # Now wait for client connection. -print 'Server listening....' +print('Server listening....') while True: conn, addr = s.accept() # Establish connection with client. - print 'Got connection from', addr + print('Got connection from', addr) data = conn.recv(1024) print('Server received', repr(data)) - filename='mytext.txt' - f = open(filename,'rb') - l = f.read(1024) - while (l): - conn.send(l) - print('Sent ',repr(l)) - l = f.read(1024) + filename = 'mytext.txt' + f = open(filename, 'rb') + in_data = f.read(1024) + while (in_data): + conn.send(in_data) + print('Sent ', repr(in_data)) + in_data = f.read(1024) f.close() print('Done sending') @@ -42,7 +42,7 @@ s.send("Hello server!") with open('received_file', 'wb') as f: - print 'file opened' + print('file opened') while True: print('receiving data...') data = s.recv(1024) @@ -55,4 +55,4 @@ f.close() print('Successfully get the file') s.close() -print('connection closed') \ No newline at end of file +print('connection closed') diff --git a/Project Euler/Problem 29/solution.py b/Project Euler/Problem 29/solution.py index 9d6148da3d87..64d35c84d9ca 100644 --- a/Project Euler/Problem 29/solution.py +++ b/Project Euler/Problem 29/solution.py @@ -1,16 +1,18 @@ def main(): - """ + """ 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: + 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? + How many distinct terms are in the sequence generated by ab + for 2 <= a <= 100 and 2 <= b <= 100? """ collectPowers = set() @@ -19,15 +21,12 @@ def main(): N = 101 # maximum limit - for a in range(2,N): - - for b in range (2,N): - + 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) + print("Number of terms ", len(collectPowers)) if __name__ == '__main__': diff --git a/data_structures/Trie/Trie.py b/data_structures/Trie/Trie.py index 63e291b9738a..b6234c6704c6 100644 --- a/data_structures/Trie/Trie.py +++ b/data_structures/Trie/Trie.py @@ -12,7 +12,7 @@ def __init__(self): self.nodes = dict() # Mapping from char to TrieNode self.is_leaf = False - def insert_many(self, words: [str]): # noqa: F821 This syntax is Python 3 only + def insert_many(self, words: [str]): # noqa: E999 This syntax is Python 3 only """ Inserts a list of words into the Trie :param words: list of string words @@ -21,7 +21,7 @@ def insert_many(self, words: [str]): # noqa: F821 This syntax is Python 3 only for word in words: self.insert(word) - def insert(self, word: str): # noqa: F821 This syntax is Python 3 only + def insert(self, word: str): # noqa: E999 This syntax is Python 3 only """ Inserts a word into the Trie :param word: word to be inserted @@ -34,7 +34,7 @@ def insert(self, word: str): # noqa: F821 This syntax is Python 3 only curr = curr.nodes[char] curr.is_leaf = True - def find(self, word: str) -> bool: # noqa: F821 This syntax is Python 3 only + def find(self, word: str) -> bool: # noqa: E999 This syntax is Python 3 only """ Tries to find word in a Trie :param word: word to look for @@ -48,7 +48,7 @@ def find(self, word: str) -> bool: # noqa: F821 This syntax is Python 3 only return curr.is_leaf -def print_words(node: TrieNode, word: str): # noqa: F821 This syntax is Python 3 only +def print_words(node: TrieNode, word: str): # noqa: E999 This syntax is Python 3 only """ Prints all the words in a Trie :param node: root node of Trie diff --git a/dynamic_programming/abbreviation.py b/dynamic_programming/abbreviation.py index 44a1689809b8..f4d07e402925 100644 --- a/dynamic_programming/abbreviation.py +++ b/dynamic_programming/abbreviation.py @@ -10,6 +10,8 @@ a=daBcd and b="ABC" daBcd -> capitalize a and c(dABCd) -> remove d (ABC) """ + + def abbr(a, b): n = len(a) m = len(b) @@ -26,4 +28,4 @@ def abbr(a, b): if __name__ == "__main__": - print abbr("daBcd", "ABC") # expect True + print(abbr("daBcd", "ABC")) # expect True diff --git a/dynamic_programming/fastfibonacci.py b/dynamic_programming/fastfibonacci.py index 2c3960d114ca..66d2b2ff0a54 100644 --- a/dynamic_programming/fastfibonacci.py +++ b/dynamic_programming/fastfibonacci.py @@ -7,14 +7,14 @@ # returns F(n) -def fibonacci(n: int): # noqa: F821 This syntax is Python 3 only +def fibonacci(n: int): # noqa: E999 This syntax is Python 3 only 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: F821 This syntax is Python 3 only +def _fib(n: int): # noqa: E999 This syntax is Python 3 only if n == 0: # (F(0), F(1)) return (0, 1) diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index 861d45c1f210..a2d97b09ded2 100755 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -70,9 +70,9 @@ def mbd(predict, actual): difference = predict - actual numerator = np.sum(difference) / len(predict) denumerator = np.sum(actual) / len(predict) - print str(numerator) - print str(denumerator) + print(numerator) + print(denumerator) score = float(numerator) / denumerator * 100 - return score \ No newline at end of file + return score From d043448fd983b49199a32c8446320bba7cbfe62b Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 21 Jan 2018 08:56:16 +0100 Subject: [PATCH 0024/2908] Fix unresolved name: insert_tail() insert_tail(Head.next, data) --> Head.next.insert_tail(data) Fixes: $ __flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics__ ``` ./data_structures/LinkedList/singly_LinkedList.py:14:13: F821 undefined name 'insert_tail' insert_tail(Head.next, data) ^ 1 F821 undefined name 'insert_tail' ``` Also formats the code to be compliant with [PEP8](http://pep8.org). --- .../LinkedList/singly_LinkedList.py | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/data_structures/LinkedList/singly_LinkedList.py b/data_structures/LinkedList/singly_LinkedList.py index 7358b3c07f35..b2e65870c0ac 100644 --- a/data_structures/LinkedList/singly_LinkedList.py +++ b/data_structures/LinkedList/singly_LinkedList.py @@ -1,61 +1,63 @@ from __future__ import print_function -class Node:#create a Node - def __int__(self,data): - self.data=data#given data - self.next=None#given next to None + + +class Node: # create a Node + def __int__(self, data): + self.data = data # given data + self.next = None # given next to None + + class Linked_List: - - pass - - def insert_tail(Head,data): + def insert_tail(Head, data): if(Head.next is None): Head.next = Node(data) else: - insert_tail(Head.next, data) + Head.next.insert_tail(data) - def insert_head(Head,data): + def insert_head(Head, data): tamp = Head - if (tamp == None): - newNod = Node()#create a new Node + if tamp is None: + newNod = Node() # create a new Node newNod.data = data newNod.next = None - Head = newNod#make new node to Head + Head = newNod # make new node to Head else: newNod = Node() newNod.data = data - newNod.next = Head#put the Head at NewNode Next - Head=newNod#make a NewNode to Head - return Head - - def printList(Head):#print every node data - tamp=Head - while tamp!=None: + newNod.next = Head # put the Head at NewNode Next + Head = newNod # make a NewNode to Head + return Head + + def printList(Head): # print every node data + tamp = Head + while tamp is not None: print(tamp.data) - tamp=tamp.next - - def delete_head(Head):#delete from head - if Head!=None: - Head=Head.next - return Head#return new Head - - def delete_tail(Head):#delete from tail - if Head!=None: + tamp = tamp.next + + def delete_head(Head): # delete from head + if Head is not None: + Head = Head.next + return Head # return new Head + + def delete_tail(Head): # delete from tail + if Head is not None: tamp = Node() tamp = Head - while (tamp.next).next!= None:#find the 2nd last element + while (tamp.next).next is not None: # find the 2nd last element tamp = tamp.next - tamp.next=None#delete the last element by give next None to 2nd last Element + # delete the last element by give next None to 2nd last Element + tamp.next = None return Head def isEmpty(Head): - return Head is None #Return if Head is none - + return Head is None # Return if Head is none + def reverse(Head): prev = None current = Head - + while(current): - # Store the current node's next node. + # Store the current node's next node. next_node = current.next # Make the current node's next point backwards current.next = prev @@ -63,5 +65,5 @@ def reverse(Head): prev = current # Make the current node the next node (to progress iteration) current = next_node - # Return prev in order to put the head at the end + # Return prev in order to put the head at the end Head = prev From a88ad60365f73c45eada1f9534f3a04aa6d3f491 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 21 Jan 2018 08:59:55 +0100 Subject: [PATCH 0025/2908] Update singly_LinkedList.py --- data_structures/LinkedList/singly_LinkedList.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/LinkedList/singly_LinkedList.py b/data_structures/LinkedList/singly_LinkedList.py index b2e65870c0ac..7285e37622b6 100644 --- a/data_structures/LinkedList/singly_LinkedList.py +++ b/data_structures/LinkedList/singly_LinkedList.py @@ -9,7 +9,7 @@ def __int__(self, data): class Linked_List: def insert_tail(Head, data): - if(Head.next is None): + if Head.next is None: Head.next = Node(data) else: Head.next.insert_tail(data) @@ -43,7 +43,7 @@ def delete_tail(Head): # delete from tail if Head is not None: tamp = Node() tamp = Head - while (tamp.next).next is not None: # find the 2nd last element + while tamp.next.next is not None: # find the 2nd last element tamp = tamp.next # delete the last element by give next None to 2nd last Element tamp.next = None @@ -56,7 +56,7 @@ def reverse(Head): prev = None current = Head - while(current): + while current: # Store the current node's next node. next_node = current.next # Make the current node's next point backwards From 1568432e82cfc69c7c02b334e0bcc89542d79881 Mon Sep 17 00:00:00 2001 From: Ben <34241521+arcsinecosine@users.noreply.github.com> Date: Sun, 21 Jan 2018 03:27:19 -0500 Subject: [PATCH 0026/2908] Add files via upload --- Project Euler/Problem 01/sol4.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Project Euler/Problem 01/sol4.py diff --git a/Project Euler/Problem 01/sol4.py b/Project Euler/Problem 01/sol4.py new file mode 100644 index 000000000000..0f5dc370b441 --- /dev/null +++ b/Project Euler/Problem 01/sol4.py @@ -0,0 +1,32 @@ +def mulitples(limit): + xmulti = [] + zmulti = [] + z = 3 + x = 5 + temp = 1 + while True: + result = z * temp + if (result < limit): + zmulti.append(result) + temp += 1 + continue + else: + temp = 1 + break + while True: + result = x * temp + if (result < limit): + xmulti.append(result) + temp += 1 + continue + else: + temp = 1 + break + return (sum(zmulti) + sum(xmulti)) + + + + + + +print (mulitples(100)) From 92e0aa29b9c0db1590b0ec032177dd8dced750ff Mon Sep 17 00:00:00 2001 From: Ben <34241521+arcsinecosine@users.noreply.github.com> Date: Sun, 21 Jan 2018 03:34:09 -0500 Subject: [PATCH 0027/2908] Add files via upload --- Project Euler/Problem 02/sol2.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Project Euler/Problem 02/sol2.py diff --git a/Project Euler/Problem 02/sol2.py b/Project Euler/Problem 02/sol2.py new file mode 100644 index 000000000000..f0502a389707 --- /dev/null +++ b/Project Euler/Problem 02/sol2.py @@ -0,0 +1,13 @@ +def fib(n): + ls = [] + a,b = 0,1 + n += 1 + for i in range(n): + if (b % 2 == 0): + ls.append(b) + else: + pass + a,b = b, a+b + print (sum(ls)) + return None +fib(10) From 53681f199c46bebeca3c019d91af79c392f570b3 Mon Sep 17 00:00:00 2001 From: Ant Fitch Date: Tue, 30 Jan 2018 18:50:07 -0800 Subject: [PATCH 0028/2908] Fixed error in binary_search_by_recursion In binary_search_by_recursion, if you search array for a value that does not exist, you will get this error: RecursionError: maximum recursion depth exceeded in comparison To fix this, first check to make sure that the value is between left and right points like this: if (right < left): return None A similar construct has already been applied to binary_search, but did not exist in the recursive alternative. --- searches/binary_search.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/searches/binary_search.py b/searches/binary_search.py index 93bf189cc08f..7df45883c09a 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -110,6 +110,9 @@ def binary_search_by_recursion(sorted_collection, item, left, right): >>> binary_search_std_lib([0, 5, 7, 10, 15], 6) """ + if (right < left): + return None + midpoint = left + (right - left) // 2 if sorted_collection[midpoint] == item: From a1c146a624cf88ba1cc5076a1e2272461d28d540 Mon Sep 17 00:00:00 2001 From: Ant Fitch Date: Wed, 31 Jan 2018 10:48:48 -0800 Subject: [PATCH 0029/2908] Updated average case for Quicksort A small edit. Average case for quicksort changed from O(n^2) to O(n log n). Sited: https://en.wikipedia.org/wiki/Quicksort --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70077e98fd5a..d20244c2aaef 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ From [Wikipedia][quick-wiki]: Quicksort (sometimes called partition-exchange sor __Properties__ * Worst case performance O(n^2) * Best case performance O(n log n) or O(n) with three-way partition -* Average case performance O(n^2) +* Average case performance O(n log n) ###### View the algorithm in [action][quick-toptal] From fe6e959ae32a7e0b7db574d18b3df40de82b16bc Mon Sep 17 00:00:00 2001 From: Mavroudo Date: Sun, 11 Feb 2018 12:18:37 +0200 Subject: [PATCH 0030/2908] Setting the files in the same folder --- Bisection.py => ArithmeticAnalysis/Bisection.py | 0 Intersection.py => ArithmeticAnalysis/Intersection.py | 0 NeutonMethod.py => ArithmeticAnalysis/NeutonMethod.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename Bisection.py => ArithmeticAnalysis/Bisection.py (100%) rename Intersection.py => ArithmeticAnalysis/Intersection.py (100%) rename NeutonMethod.py => ArithmeticAnalysis/NeutonMethod.py (100%) diff --git a/Bisection.py b/ArithmeticAnalysis/Bisection.py similarity index 100% rename from Bisection.py rename to ArithmeticAnalysis/Bisection.py diff --git a/Intersection.py b/ArithmeticAnalysis/Intersection.py similarity index 100% rename from Intersection.py rename to ArithmeticAnalysis/Intersection.py diff --git a/NeutonMethod.py b/ArithmeticAnalysis/NeutonMethod.py similarity index 100% rename from NeutonMethod.py rename to ArithmeticAnalysis/NeutonMethod.py From a48836998b46131ec7698995e0603fbf31eefd55 Mon Sep 17 00:00:00 2001 From: Mavroudo Date: Sun, 11 Feb 2018 21:43:38 +0200 Subject: [PATCH 0031/2908] LU decomposition --- ArithmeticAnalysis/LUdecomposition.py | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 ArithmeticAnalysis/LUdecomposition.py diff --git a/ArithmeticAnalysis/LUdecomposition.py b/ArithmeticAnalysis/LUdecomposition.py new file mode 100644 index 000000000000..bb0827bb39b1 --- /dev/null +++ b/ArithmeticAnalysis/LUdecomposition.py @@ -0,0 +1,34 @@ +import math +import numpy + +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: + 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 + + + + + + + +matrix =numpy.array([[2,-2,1],[0,1,2],[5,3,1]]) +L,U = LUDecompose(matrix) +print(L) +print(U) \ No newline at end of file From ce3036144bb46ee7fb3cef998ff6381c73d2eb2a Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Thu, 15 Feb 2018 21:56:51 +0530 Subject: [PATCH 0032/2908] Update Arrays Array implementation --- data_structures/Arrays | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/Arrays b/data_structures/Arrays index e2c1243f5f96..da6fbff25a76 100644 --- a/data_structures/Arrays +++ b/data_structures/Arrays @@ -1 +1 @@ -Arrays implementation using python programming. +arr = [10, 20, 30, 40, 50] From 30cf24c84661ac409ca48fb548f57d7affdd4cb6 Mon Sep 17 00:00:00 2001 From: lane Date: Tue, 20 Feb 2018 17:39:45 -0700 Subject: [PATCH 0033/2908] added quadrature trapezoidal rule to Maths --- Maths/TrapezoidalRule.py | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Maths/TrapezoidalRule.py diff --git a/Maths/TrapezoidalRule.py b/Maths/TrapezoidalRule.py new file mode 100644 index 000000000000..f926ed2569e4 --- /dev/null +++ b/Maths/TrapezoidalRule.py @@ -0,0 +1,45 @@ +''' +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' + +method 1: +"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 = makePoints(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 makePoints(a,b,h): + x = a + h + while x < (b-h): + yield x + x = x + h + +def f(x): + 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() From 537909df64a55df4b52f7b94d6cbb679a2550558 Mon Sep 17 00:00:00 2001 From: lane Date: Tue, 20 Feb 2018 17:48:22 -0700 Subject: [PATCH 0034/2908] added quadrature trapezoidal rule to Maths --- Maths/TrapezoidalRule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maths/TrapezoidalRule.py b/Maths/TrapezoidalRule.py index f926ed2569e4..2ad857390b50 100644 --- a/Maths/TrapezoidalRule.py +++ b/Maths/TrapezoidalRule.py @@ -29,7 +29,7 @@ def makePoints(a,b,h): yield x x = x + h -def f(x): +def f(x): #enter your function here y = (x-0)*(x-0) return y From 698faa9ca4bdb598aee3d2029b3c21d81fe0cc64 Mon Sep 17 00:00:00 2001 From: lane Date: Thu, 22 Feb 2018 08:40:00 -0700 Subject: [PATCH 0035/2908] Added Simpson Rule to Maths --- Maths/SimpsonRule.py | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Maths/SimpsonRule.py diff --git a/Maths/SimpsonRule.py b/Maths/SimpsonRule.py new file mode 100644 index 000000000000..51b5ed1e439a --- /dev/null +++ b/Maths/SimpsonRule.py @@ -0,0 +1,47 @@ + +''' +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' + +method 2: +"Simpson Rule" + +''' + +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 + +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_2(boundary, steps) + print 'y = {0}'.format(y) + +if __name__ == '__main__': + main() From 1506ac90393034fa17fdc2775c4cdd2f956aa3f5 Mon Sep 17 00:00:00 2001 From: shivg7706 Date: Mon, 26 Feb 2018 17:25:09 +0530 Subject: [PATCH 0036/2908] MatrixChainOrder --- dynamic_programming/matrix_chain_order.py | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 dynamic_programming/matrix_chain_order.py diff --git a/dynamic_programming/matrix_chain_order.py b/dynamic_programming/matrix_chain_order.py new file mode 100644 index 000000000000..839db03d828d --- /dev/null +++ b/dynamic_programming/matrix_chain_order.py @@ -0,0 +1,46 @@ +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)] + for i in range(1,N): + Matrix[i][i]=0 + + 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] + 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 = " ") + else: + 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 + # 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]))) + PrintOptimalSolution(OptimalSolution,1,n-1) +if __name__ == '__main__': + main() From 744803ad648d6984f00a23fd9abc3547bae6c32e Mon Sep 17 00:00:00 2001 From: shivg7706 Date: Tue, 27 Feb 2018 19:19:53 +0530 Subject: [PATCH 0037/2908] Adding Problem_08 --- Project Euler/Problem 08/sol1.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Project Euler/Problem 08/sol1.py diff --git a/Project Euler/Problem 08/sol1.py b/Project Euler/Problem 08/sol1.py new file mode 100644 index 000000000000..fb5ed25bfe93 --- /dev/null +++ b/Project Euler/Problem 08/sol1.py @@ -0,0 +1,15 @@ +import sys +def main(): + LargestProduct = -sys.maxsize-1 + number=input().strip() + for i in range(len(number)-13): + product=1 + for j in range(13): + product *= int(number[i+j]) + if product > LargestProduct: + LargestProduct = product + print(LargestProduct) + + +if __name__ == '__main__': + main() From 59c797eacec84df27369eb3dd3bc4322d69d8315 Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Thu, 1 Mar 2018 18:58:00 +0530 Subject: [PATCH 0038/2908] Update Arrays --- data_structures/Arrays | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/Arrays b/data_structures/Arrays index da6fbff25a76..5fbc08328833 100644 --- a/data_structures/Arrays +++ b/data_structures/Arrays @@ -1 +1,2 @@ -arr = [10, 20, 30, 40, 50] +arr = [10, 20, 30, 40] +arr[1] = 30 From 7a428c1cdd05771156c3eea8991510549f4dc044 Mon Sep 17 00:00:00 2001 From: Harshil Date: Fri, 2 Mar 2018 11:16:05 +0530 Subject: [PATCH 0039/2908] Update Arrays --- data_structures/Arrays | 1 + 1 file changed, 1 insertion(+) diff --git a/data_structures/Arrays b/data_structures/Arrays index 5fbc08328833..3ec9f8976673 100644 --- a/data_structures/Arrays +++ b/data_structures/Arrays @@ -1,2 +1,3 @@ arr = [10, 20, 30, 40] arr[1] = 30 +print(arr) From bae5762b095cf79ad19b2d98f11eff2467061930 Mon Sep 17 00:00:00 2001 From: Harshil Date: Fri, 2 Mar 2018 11:16:49 +0530 Subject: [PATCH 0040/2908] Rename Arrays to Arrays.py --- data_structures/{Arrays => Arrays.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data_structures/{Arrays => Arrays.py} (100%) diff --git a/data_structures/Arrays b/data_structures/Arrays.py similarity index 100% rename from data_structures/Arrays rename to data_structures/Arrays.py From b6b7784b1bc9905be33b6826929b2e70fbd68839 Mon Sep 17 00:00:00 2001 From: shivg7706 Date: Sat, 3 Mar 2018 10:30:31 +0530 Subject: [PATCH 0041/2908] another sol for problem_20 --- Project Euler/Problem 20/sol2.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Project Euler/Problem 20/sol2.py diff --git a/Project Euler/Problem 20/sol2.py b/Project Euler/Problem 20/sol2.py new file mode 100644 index 000000000000..bca9af9cb9ef --- /dev/null +++ b/Project Euler/Problem 20/sol2.py @@ -0,0 +1,5 @@ +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 From 744dd712386f1d9e86c0305a46dcf81061b769f4 Mon Sep 17 00:00:00 2001 From: Sichen Liu Date: Sat, 3 Mar 2018 21:27:05 -0500 Subject: [PATCH 0042/2908] Add sorts for python3 --- sorts/pancake_sort.py | 16 +++++++++++++++ sorts/tree_sort.py | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 sorts/pancake_sort.py create mode 100644 sorts/tree_sort.py diff --git a/sorts/pancake_sort.py b/sorts/pancake_sort.py new file mode 100644 index 000000000000..26fd40b7f67c --- /dev/null +++ b/sorts/pancake_sort.py @@ -0,0 +1,16 @@ +# Pancake sort algorithm +# Only can reverse array from 0 to i + +def pancakesort(arr): + 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)] + cur -= 1 + return arr + +print(pancakesort([0,10,15,3,2,9,14,13])) diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py new file mode 100644 index 000000000000..94cf68033b55 --- /dev/null +++ b/sorts/tree_sort.py @@ -0,0 +1,45 @@ +# 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): + if self.val: + if val < self.val: + if self.left == None: + self.left = node(val) + else: + self.left.insert(val) + elif val > self.val: + if self.right == None: + self.right = node(val) + else: + self.right.insert(val) + else: + self.val = val + +def inorder(root, res): + # Recursive travesal + if root: + inorder(root.left,res) + res.append(root.val) + inorder(root.right,res) + +def treesort(arr): + # Build BST + if len(arr) == 0: + return arr + root = node(arr[0]) + for i in range(1,len(arr)): + root.insert(arr[i]) + # Traverse BST in order. + res = [] + inorder(root,res) + return res + +print(treesort([10,1,3,2,9,14,13])) \ No newline at end of file From 0b8592918858dfc3af8cb464d475b4dac3df66fc Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Sun, 4 Mar 2018 21:25:38 +0100 Subject: [PATCH 0043/2908] Add files via upload --- linear-algebra-python/README.md | 70 ++++++ linear-algebra-python/src/lib.py | 332 +++++++++++++++++++++++++++++ linear-algebra-python/src/lib.pyc | Bin 0 -> 11098 bytes linear-algebra-python/src/tests.py | 133 ++++++++++++ 4 files changed, 535 insertions(+) create mode 100644 linear-algebra-python/README.md create mode 100644 linear-algebra-python/src/lib.py create mode 100644 linear-algebra-python/src/lib.pyc create mode 100644 linear-algebra-python/src/tests.py diff --git a/linear-algebra-python/README.md b/linear-algebra-python/README.md new file mode 100644 index 000000000000..bca0af449d8a --- /dev/null +++ b/linear-algebra-python/README.md @@ -0,0 +1,70 @@ +# Linear algebra library for Python + +This module contains some useful classes and functions for dealing with linear algebra in python 2. + +--- + +## Overview + +- class Vector + - This class represents a vector of arbitray size and operations on it. + + **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) + - size() : gets the size 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 +- class Matrix + - 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 + - height() : returns the height of the matrix + - operator + : implements the matrix-addition. + - operator - _ implements the matrix-subtraction +- function squareZeroMatrix(N) + - returns a square zero-matrix of dimension NxN +--- + +## 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)```. + +--- + +## 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. + +--- + +## 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**. diff --git a/linear-algebra-python/src/lib.py b/linear-algebra-python/src/lib.py new file mode 100644 index 000000000000..efded3a2ae4e --- /dev/null +++ b/linear-algebra-python/src/lib.py @@ -0,0 +1,332 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Feb 26 14:29:11 2018 + +@author: Christian Bender +@license: MIT-license + +This module contains some useful classes and functions for dealing +with linear algebra in python. + +Overview: + +- class Vector +- function zeroVector(dimension) +- function unitBasisVector(dimension,pos) +- function axpy(scalar,vector1,vector2) +- class Matrix +- squareZeroMatrix(N) +""" + + +import math + + +class Vector(object): + """ + This class represents a vector of arbitray 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) + size() : gets the size 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. + TODO: compare-operator + """ + def __init__(self,components): + """ + input: components or nothing + simple constructor for init the vector + """ + self.__components = components + def set(self,components): + """ + input: new components + changes the components of the vector. + replace the components with newer one. + """ + if len(components) > 0: + self.__components = components + else: + raise Exception("please give any vector") + def __str__(self): + """ + returns a string representation of the vector + """ + ans = "(" + length = len(self.__components) + for i in range(length): + if i != length-1: + ans += str(self.__components[i]) + "," + else: + ans += str(self.__components[i]) + ")" + if len(ans) == 1: + ans += ")" + return ans + def component(self,i): + """ + input: index (start at 0) + output: the i-th component of the vector. + """ + if i < len(self.__components) and i >= 0: + return self.__components[i] + else: + raise Exception("index out of range") + def size(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 + return math.sqrt(summe) + def __add__(self,other): + """ + input: other vector + assumes: other vector has the same size + returns a new vector that represents the sum. + """ + size = self.size() + result = [] + if size == other.size(): + for i in range(size): + result.append(self.__components[i] + other.component(i)) + else: + raise Exception("must have the same size") + return Vector(result) + def __sub__(self,other): + """ + input: other vector + assumes: other vector has the same size + returns a new vector that represents the differenz. + """ + size = self.size() + result = [] + if size == other.size(): + for i in range(size): + result.append(self.__components[i] - other.component(i)) + else: # error case + raise Exception("must have the same size") + return Vector(result) + def __mul__(self,other): + """ + mul implements the scalar multiplication + and the dot-product + """ + ans = [] + if isinstance(other,float) or isinstance(other,int): + for c in self.__components: + ans.append(c*other) + elif (isinstance(other,Vector) and (self.size() == other.size())): + size = self.size() + summe = 0 + for i in range(size): + summe += self.__components[i] * other.component(i) + return summe + else: # error case + raise Exception("invalide operand!") + return Vector(ans) + def copy(self): + """ + copies this vector and returns it. + """ + components = [x for x in self.__components] + return Vector(components) + def changeComponent(self,pos,value): + """ + input: an index (pos) and a value + changes the specified component (pos) with the + 'value' + """ + #precondition + assert (pos >= 0 and pos < len(self.__components)) + self.__components[pos] = value + +def zeroVector(dimension): + """ + returns a zero-vector of size 'dimension' + """ + #precondition + assert(isinstance(dimension,int)) + ans = [] + for i in range(dimension): + ans.append(0) + return Vector(ans) + + +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 = [] + for i in range(dimension): + if i != pos: + ans.append(0) + else: + ans.append(1) + return Vector(ans) + + +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) + + +class Matrix(object): + """ + class: Matrix + This class represents a arbitrary 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 + operator + : implements the matrix-addition. + operator - _ implements the matrix-subtraction + """ + def __init__(self,matrix,w,h): + """ + simple constructor for initialzes + the matrix with components. + """ + self.__matrix = matrix + self.__width = w + self.__height = h + def __str__(self): + """ + returns a string representation of this + matrix. + """ + ans = "" + for i in range(self.__height): + ans += "|" + for j in range(self.__width): + 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): + """ + 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): + """ + 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") + def width(self): + """ + getter for the width + """ + return self.__width + def height(self): + """ + getter for the height + """ + return self.__height + def __mul__(self,other): + """ + implements the matrix-vector multiplication. + implements the matrix-scalar multiplication + """ + if isinstance(other, Vector): # vector-matrix + if (other.size() == 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) + 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 = [] + for i in range(self.__height): + row = [] + for j in range(self.__width): + row.append(self.__matrix[i][j] * other) + matrix.append(row) + 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()): + matrix = [] + for i in range(self.__height): + row = [] + for j in range(self.__width): + row.append(self.__matrix[i][j] + other.component(i,j)) + matrix.append(row) + return Matrix(matrix,self.__width,self.__height) + else: + raise Exception("matrix must have the same dimension!") + def __sub__(self,other): + """ + implements the matrix-subtraction. + """ + 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)) + matrix.append(row) + 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 = [] + for i in range(N): + row = [] + for j in range(N): + row.append(0) + ans.append(row) + return Matrix(ans,N,N) + + \ No newline at end of file diff --git a/linear-algebra-python/src/lib.pyc b/linear-algebra-python/src/lib.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7aeca0e1c6d56244f23a07bdeb3a1cf9cfa4ce82 GIT binary patch literal 11098 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 v>2s_v=3^rr|+Ei`w*zP|7Yf{hL literal 0 HcmV?d00001 diff --git a/linear-algebra-python/src/tests.py b/linear-algebra-python/src/tests.py new file mode 100644 index 000000000000..b84612b4ced4 --- /dev/null +++ b/linear-algebra-python/src/tests.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Feb 26 15:40:07 2018 + +@author: Christian Bender +@license: MIT-license + +This file contains the test-suite for the linear algebra library. +""" + +import unittest +from lib import * + +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) + 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(x.__str__(),"(0,0,0,0,0,1)") + def test_size(self): + """ + test for size()-method + """ + x = Vector([1,2,3,4]) + self.assertEqual(x.size(),4) + def test_euclidLength(self): + """ + test for the eulidean length + """ + 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) + 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) + 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((x*3.0).__str__(),"(3.0,6.0,9.0)") + self.assertEqual((a*b),0) + def test_zeroVector(self): + """ + test for the global function zeroVector(...) + """ + self.assertTrue(zeroVector(10).__str__().count("0") == 10) + def test_unitBasisVector(self): + """ + test for the global function unitBasisVector(...) + """ + self.assertEqual(unitBasisVector(3,1).__str__(),"(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(axpy(2,x,y).__str__(),"(3,4,7)") + def test_copy(self): + """ + test for the copy()-method + """ + x = Vector([1,0,0,0,0,0]) + y = x.copy() + self.assertEqual(x.__str__(),y.__str__()) + def test_changeComponent(self): + """ + test for the changeComponent(...)-method + """ + x = Vector([1,0,0]) + x.changeComponent(0,0) + x.changeComponent(1,1) + self.assertEqual(x.__str__(),"(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",A.__str__()) + 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)",(A*x).__str__()) + self.assertEqual("|2,4,6|\n|8,10,12|\n|14,16,18|\n",(A*2).__str__()) + 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",A.__str__()) + 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) + 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",(A+B).__str__()) + 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",(A-B).__str__()) + 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',squareZeroMatrix(5).__str__()) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From a368d620ae41dece6382c4f14b3ec1d629502360 Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Mon, 5 Mar 2018 13:27:27 +0100 Subject: [PATCH 0044/2908] Add files via upload --- linear-algebra-python/README.md | 4 ++++ linear-algebra-python/src/lib.py | 34 +++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/linear-algebra-python/README.md b/linear-algebra-python/README.md index bca0af449d8a..ebfcdab7b179 100644 --- a/linear-algebra-python/README.md +++ b/linear-algebra-python/README.md @@ -29,6 +29,8 @@ This module contains some useful classes and functions for dealing with linear a - 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'. - class Matrix - This class represents a matrix of arbitrary size and operations on it. @@ -45,6 +47,8 @@ This module contains some useful classes and functions for dealing with linear a - 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 diff --git a/linear-algebra-python/src/lib.py b/linear-algebra-python/src/lib.py index efded3a2ae4e..66f27ff8946e 100644 --- a/linear-algebra-python/src/lib.py +++ b/linear-algebra-python/src/lib.py @@ -14,12 +14,15 @@ - function zeroVector(dimension) - function unitBasisVector(dimension,pos) - function axpy(scalar,vector1,vector2) +- function randomVector(N,a,b) - class Matrix -- squareZeroMatrix(N) +- function squareZeroMatrix(N) +- function randomMatrix(W,H,a,b) """ import math +import random class Vector(object): @@ -196,6 +199,20 @@ def axpy(scalar,x,y): return (x*scalar + 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'. + """ + ans = zeroVector(N) + random.seed(None) + for i in range(N): + ans.changeComponent(i,random.randint(a,b)) + return ans + + class Matrix(object): """ class: Matrix @@ -328,5 +345,20 @@ def squareZeroMatrix(N): row.append(0) ans.append(row) return Matrix(ans,N,N) + + +def randomMatrix(W,H,a,b): + """ + returns a random matrix WxH with integer components + between 'a' and 'b' + """ + matrix = [] + random.seed(None) + for i in range(H): + row = [] + for j in range(W): + row.append(random.randint(a,b)) + matrix.append(row) + return Matrix(matrix,W,H) \ No newline at end of file From c975cac371fbebd7a0de6c7693154da65a49a93e Mon Sep 17 00:00:00 2001 From: William Feng Date: Wed, 7 Mar 2018 15:04:37 +0800 Subject: [PATCH 0045/2908] function '__init__' miss a 'i' __int__ should be __init__ i think? --- data_structures/LinkedList/singly_LinkedList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/LinkedList/singly_LinkedList.py b/data_structures/LinkedList/singly_LinkedList.py index 7285e37622b6..eb7f48f17359 100644 --- a/data_structures/LinkedList/singly_LinkedList.py +++ b/data_structures/LinkedList/singly_LinkedList.py @@ -2,7 +2,7 @@ class Node: # create a Node - def __int__(self, data): + def __init__(self, data): self.data = data # given data self.next = None # given next to None From ab5f262fc1ba8c6952cee3d43d44d1ec7a2e9757 Mon Sep 17 00:00:00 2001 From: Sichen Liu Date: Thu, 8 Mar 2018 12:58:38 -0500 Subject: [PATCH 0046/2908] --- networking_flow/Ford-Fulkerson.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 networking_flow/Ford-Fulkerson.py diff --git a/networking_flow/Ford-Fulkerson.py b/networking_flow/Ford-Fulkerson.py new file mode 100644 index 000000000000..0be0fa5f147f --- /dev/null +++ b/networking_flow/Ford-Fulkerson.py @@ -0,0 +1,4 @@ +# Ford-Fulkerson Algorithm for Maximum Flow Problem +""" +""" + From 2f53847247366afb85d4b56b4d50d8f2ce44b678 Mon Sep 17 00:00:00 2001 From: Sichen Liu Date: Thu, 8 Mar 2018 15:52:16 -0500 Subject: [PATCH 0047/2908] --- networking_flow/Ford-Fulkerson.py | 4 --- networking_flow/Ford_Fulkerson.py | 56 +++++++++++++++++++++++++++++ networking_flow/Minimum_cut.py | 59 +++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 4 deletions(-) delete mode 100644 networking_flow/Ford-Fulkerson.py create mode 100644 networking_flow/Ford_Fulkerson.py create mode 100644 networking_flow/Minimum_cut.py diff --git a/networking_flow/Ford-Fulkerson.py b/networking_flow/Ford-Fulkerson.py deleted file mode 100644 index 0be0fa5f147f..000000000000 --- a/networking_flow/Ford-Fulkerson.py +++ /dev/null @@ -1,4 +0,0 @@ -# Ford-Fulkerson Algorithm for Maximum Flow Problem -""" -""" - diff --git a/networking_flow/Ford_Fulkerson.py b/networking_flow/Ford_Fulkerson.py new file mode 100644 index 000000000000..08749b3daff7 --- /dev/null +++ b/networking_flow/Ford_Fulkerson.py @@ -0,0 +1,56 @@ +# Ford-Fulkerson Algorithm for Maximum Flow Problem +""" +Description: + (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=[] + queue.append(s) + visited[s] = True + + while queue: + u = queue.pop(0) + for ind in range(len(graph[u])): + if visited[ind] == False and graph[u][ind] > 0: + queue.append(ind) + visited[ind] = True + 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) : + path_flow = float("Inf") + s = sink + + while(s != source): + # Find the minimum value in select path + path_flow = min (path_flow, graph[parent[s]][s]) + s = parent[s] + + max_flow += path_flow + v = sink + + 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]] + +source, sink = 0, 5 +print(FordFulkerson(graph, source, sink)) \ No newline at end of file diff --git a/networking_flow/Minimum_cut.py b/networking_flow/Minimum_cut.py new file mode 100644 index 000000000000..0a61db49bb89 --- /dev/null +++ b/networking_flow/Minimum_cut.py @@ -0,0 +1,59 @@ +# Minimum cut on Ford_Fulkerson algorithm. + +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[s] = True + + while queue: + u = queue.pop(0) + for ind in range(len(graph[u])): + if visited[ind] == False and graph[u][ind] > 0: + queue.append(ind) + visited[ind] = True + 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 + res = [] + 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): + # Find the minimum value in select path + path_flow = min (path_flow, graph[parent[s]][s]) + s = parent[s] + + max_flow += path_flow + v = sink + + while(v != source): + u = parent[v] + graph[u][v] -= path_flow + graph[v][u] += path_flow + v = parent[v] + + 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)) + + 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 From e3a03c559ead639ce34cb68d04c2a08638119de9 Mon Sep 17 00:00:00 2001 From: Sichen Liu Date: Thu, 8 Mar 2018 15:53:10 -0500 Subject: [PATCH 0048/2908] networking_flow --- networking_flow/Ford_Fulkerson.py | 2 +- networking_flow/Minimum_cut.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/networking_flow/Ford_Fulkerson.py b/networking_flow/Ford_Fulkerson.py index 08749b3daff7..d51f1f0661b3 100644 --- a/networking_flow/Ford_Fulkerson.py +++ b/networking_flow/Ford_Fulkerson.py @@ -21,7 +21,7 @@ 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)) diff --git a/networking_flow/Minimum_cut.py b/networking_flow/Minimum_cut.py index 0a61db49bb89..8ad6e03b00c6 100644 --- a/networking_flow/Minimum_cut.py +++ b/networking_flow/Minimum_cut.py @@ -16,7 +16,7 @@ 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)) From 601edf31317b2ed11377869af4a20c07244cfe35 Mon Sep 17 00:00:00 2001 From: shivg7706 Date: Mon, 12 Mar 2018 15:11:38 +0530 Subject: [PATCH 0049/2908] QuineMcCluskey --- .../Quine_McCluskey/QuineMcCluskey.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 boolean_algebra/Quine_McCluskey/QuineMcCluskey.py diff --git a/boolean_algebra/Quine_McCluskey/QuineMcCluskey.py b/boolean_algebra/Quine_McCluskey/QuineMcCluskey.py new file mode 100644 index 000000000000..eb3a11f7ee31 --- /dev/null +++ b/boolean_algebra/Quine_McCluskey/QuineMcCluskey.py @@ -0,0 +1,116 @@ +def compare_string(string1, string2): + 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): + 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): + 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): + 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): + 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): + 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__': + main() \ No newline at end of file From 7beaeae01402bd038bce9263e7f0a11665be4a4d Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Wed, 14 Mar 2018 14:35:16 -0400 Subject: [PATCH 0050/2908] Brute force solution to Problem 10 --- Project Euler/Problem 10/sol1.py | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Project Euler/Problem 10/sol1.py diff --git a/Project Euler/Problem 10/sol1.py b/Project Euler/Problem 10/sol1.py new file mode 100644 index 000000000000..ca9593afa969 --- /dev/null +++ b/Project Euler/Problem 10/sol1.py @@ -0,0 +1,33 @@ +from __future__ import print_function +from math import sqrt + +def is_prime(n): + for i in xrange(2, int(sqrt(n))+1): + if n%i == 0: + return False + + 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.') \ No newline at end of file From 301c90737674b9724be03fe7d25e5314cf406a04 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Wed, 14 Mar 2018 14:55:07 -0400 Subject: [PATCH 0051/2908] Dynamic programming solution to Problem 25 --- Project Euler/Problem 25/sol1.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Project Euler/Problem 25/sol1.py diff --git a/Project Euler/Problem 25/sol1.py b/Project Euler/Problem 25/sol1.py new file mode 100644 index 000000000000..67185774d036 --- /dev/null +++ b/Project Euler/Problem 25/sol1.py @@ -0,0 +1,26 @@ +from __future__ import print_function + +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]) + + return sequence[n] + +def fibonacci_digits_index(n): + digits = 0 + index = 2 + + while digits < n: + index += 1 + digits = len(str(fibonacci(index))) + + return index + +if __name__ == '__main__': + print(fibonacci_digits_index(1000)) \ No newline at end of file From ac14455ac0448e0fb34221a316f4d9c48ab37107 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Wed, 14 Mar 2018 15:39:52 -0400 Subject: [PATCH 0052/2908] Combinatoric solution using Pascal's Triangle to Problem 15 --- Project Euler/Problem 15/sol1.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Project Euler/Problem 15/sol1.py diff --git a/Project Euler/Problem 15/sol1.py b/Project Euler/Problem 15/sol1.py new file mode 100644 index 000000000000..9b61b37d2081 --- /dev/null +++ b/Project Euler/Problem 15/sol1.py @@ -0,0 +1,20 @@ +from __future__ import print_function +from math import factorial, ceil + +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.') From 7704151504d38433bb7490298576dfbb604280b6 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Wed, 14 Mar 2018 16:03:44 -0400 Subject: [PATCH 0053/2908] Solution to Problem 28 --- Project Euler/Problem 28/sol1.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Project Euler/Problem 28/sol1.py diff --git a/Project Euler/Problem 28/sol1.py b/Project Euler/Problem 28/sol1.py new file mode 100644 index 000000000000..7944caa2109f --- /dev/null +++ b/Project Euler/Problem 28/sol1.py @@ -0,0 +1,23 @@ +from __future__ import print_function + +def diagonal_sum(n): + total = 1 + + for i in xrange(n/2): + odd = 2*(i+1)+1 + even = 2*(i+1) + 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 From 81dc221ee28f7e34e0293d08df11214cf9d06d5e Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Wed, 14 Mar 2018 17:18:59 -0400 Subject: [PATCH 0054/2908] Solution to Problem 28 --- Project Euler/Problem 28/sol1.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Project Euler/Problem 28/sol1.py b/Project Euler/Problem 28/sol1.py index 7944caa2109f..d44d017892ce 100644 --- a/Project Euler/Problem 28/sol1.py +++ b/Project Euler/Problem 28/sol1.py @@ -1,11 +1,12 @@ from __future__ import print_function +from math import ceil def diagonal_sum(n): total = 1 - for i in xrange(n/2): - odd = 2*(i+1)+1 - even = 2*(i+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 From e6773616a67adcdb3c3d818f0a9be5d0239fdd86 Mon Sep 17 00:00:00 2001 From: Amir Naghibi Date: Wed, 14 Mar 2018 21:25:03 -0700 Subject: [PATCH 0055/2908] fixed spelling error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d20244c2aaef..9b394e001d66 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ __Properties__ ###### View the algorithm in [action][shell-toptal] -### Time-Compexity Graphs +### Time-Complexity Graphs Comparing the complexity of sorting algorithms (Bubble Sort, Insertion Sort, Selection Sort) From 46b4e51d6e027905a3ae1530dc0dfbfee644ab91 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Sun, 18 Mar 2018 13:59:01 -0400 Subject: [PATCH 0056/2908] Solution to Problem 48 --- Project Euler/Problem 48/sol1.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Project Euler/Problem 48/sol1.py diff --git a/Project Euler/Problem 48/sol1.py b/Project Euler/Problem 48/sol1.py new file mode 100644 index 000000000000..5c4bdb0f6384 --- /dev/null +++ b/Project Euler/Problem 48/sol1.py @@ -0,0 +1,21 @@ +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 +except NameError: + xrange = range + +total = 0 +for i in xrange(1, 1001): + total += i**i + + +print(str(total)[-10:]) \ No newline at end of file From 689e93439a9e8837e2597a814a0c4b3dc81c241c Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Sun, 18 Mar 2018 17:44:18 -0400 Subject: [PATCH 0057/2908] Solution to Problem 17 --- Project Euler/Problem 17/sol1.py | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Project Euler/Problem 17/sol1.py diff --git a/Project Euler/Problem 17/sol1.py b/Project Euler/Problem 17/sol1.py new file mode 100644 index 000000000000..3baf0f9f79f4 --- /dev/null +++ b/Project Euler/Problem 17/sol1.py @@ -0,0 +1,35 @@ +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 its 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 is not 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) \ No newline at end of file From 2d2644ee1761dbd98e9e8ba7e1545666709248c4 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Sun, 18 Mar 2018 17:45:28 -0400 Subject: [PATCH 0058/2908] Solution to Problem 17 --- Project Euler/Problem 17/sol1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project Euler/Problem 17/sol1.py b/Project Euler/Problem 17/sol1.py index 3baf0f9f79f4..9de5d80b9b29 100644 --- a/Project Euler/Problem 17/sol1.py +++ b/Project Euler/Problem 17/sol1.py @@ -12,7 +12,7 @@ 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 its 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 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 From 00a2b903568bfa03fa293e6e178e82fd710ca68a Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 19 Mar 2018 02:48:09 +0100 Subject: [PATCH 0059/2908] Fix Python 2 syntax error in matrix_chain_order.py --- dynamic_programming/matrix_chain_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dynamic_programming/matrix_chain_order.py b/dynamic_programming/matrix_chain_order.py index 839db03d828d..011e85755d36 100644 --- a/dynamic_programming/matrix_chain_order.py +++ b/dynamic_programming/matrix_chain_order.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import sys ''' Dynamic Programming From 705f43ad5b2995071ff370beb9b67b047524b552 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 19 Mar 2018 03:18:18 +0100 Subject: [PATCH 0060/2908] xrange() was removed in Python 3 in favor of range() @daniel-s-ingram Similar changes needed on Problems 25 and 28 so they can run on Python 3. flake8 testing of https://github.com/TheAlgorithms/Python on Python 3.6.3 $ __flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics__ ``` ./Project Euler/Problem 10/sol1.py:5:11: F821 undefined name 'xrange' for i in xrange(2, int(sqrt(n))+1): ^ ./Project Euler/Problem 10/sol1.py:17:11: F821 undefined name 'xrange' for i in xrange(3, n, 2): ^ ./Project Euler/Problem 25/sol1.py:10:12: F821 undefined name 'xrange' for i in xrange(2, n+1): ^ ./Project Euler/Problem 28/sol1.py:7:11: F821 undefined name 'xrange' for i in xrange(1, int(ceil(n/2.0))): ^ 4 F821 undefined name 'xrange' ``` --- Project Euler/Problem 10/sol1.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Project Euler/Problem 10/sol1.py b/Project Euler/Problem 10/sol1.py index ca9593afa969..eace01a46bdc 100644 --- a/Project Euler/Problem 10/sol1.py +++ b/Project Euler/Problem 10/sol1.py @@ -1,6 +1,12 @@ from __future__ import print_function from math import sqrt +try: + xrange # Python 2 +except NameError: + xrange = range # Python 3 + + def is_prime(n): for i in xrange(2, int(sqrt(n))+1): if n%i == 0: @@ -30,4 +36,4 @@ def sum_of_primes(n): n = int(sys.argv[1]) print(sum_of_primes(n)) except ValueError: - print('Invalid entry - please enter a number.') \ No newline at end of file + print('Invalid entry - please enter a number.') From 0516bde45f15755a4d0014c21e14ef52b894ec98 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 19 Mar 2018 03:25:29 +0100 Subject: [PATCH 0061/2908] from __future__ import print_function For Python 3 @ltdouthit __print()__ is a function in Python 3 --- Maths/TrapezoidalRule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Maths/TrapezoidalRule.py b/Maths/TrapezoidalRule.py index 2ad857390b50..52310c1ed3b0 100644 --- a/Maths/TrapezoidalRule.py +++ b/Maths/TrapezoidalRule.py @@ -7,6 +7,7 @@ "extended trapezoidal rule" ''' +from __future__ import print_function def method_1(boundary, steps): # "extended trapezoidal rule" @@ -39,7 +40,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('y = {0}'.format(y)) if __name__ == '__main__': main() From 361532279052619e8fb705c7351ae22f8b76c11e Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 19 Mar 2018 03:27:22 +0100 Subject: [PATCH 0062/2908] from __future__ import print_function for Python 3 @ltdouthit __print()__ is a function in Python 3 --- Maths/SimpsonRule.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Maths/SimpsonRule.py b/Maths/SimpsonRule.py index 51b5ed1e439a..ef9da5e55d6e 100644 --- a/Maths/SimpsonRule.py +++ b/Maths/SimpsonRule.py @@ -8,6 +8,8 @@ "Simpson Rule" ''' +from __future__ import print_function + def method_2(boundary, steps): # "Simpson Rule" From 4fd777e3b42d8c775497f55851b209077a34872e Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 19 Mar 2018 03:28:00 +0100 Subject: [PATCH 0063/2908] Update SimpsonRule.py --- Maths/SimpsonRule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maths/SimpsonRule.py b/Maths/SimpsonRule.py index ef9da5e55d6e..091c86c17f1b 100644 --- a/Maths/SimpsonRule.py +++ b/Maths/SimpsonRule.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_2(boundary, steps) - print 'y = {0}'.format(y) + print('y = {0}'.format(y)) if __name__ == '__main__': main() From c6c5d6231154d374a194266ede6f8a33615604f4 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Mon, 19 Mar 2018 09:29:46 -0400 Subject: [PATCH 0064/2908] Fixed xrange compatibility for Python 3 --- Project Euler/Problem 10/sol1.py | 5 +++++ Project Euler/Problem 25/sol1.py | 5 +++++ Project Euler/Problem 28/sol1.py | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/Project Euler/Problem 10/sol1.py b/Project Euler/Problem 10/sol1.py index ca9593afa969..2df3b6b59f73 100644 --- a/Project Euler/Problem 10/sol1.py +++ b/Project Euler/Problem 10/sol1.py @@ -1,6 +1,11 @@ from __future__ import print_function from math import sqrt +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + def is_prime(n): for i in xrange(2, int(sqrt(n))+1): if n%i == 0: diff --git a/Project Euler/Problem 25/sol1.py b/Project Euler/Problem 25/sol1.py index 67185774d036..f8cea3093dcf 100644 --- a/Project Euler/Problem 25/sol1.py +++ b/Project Euler/Problem 25/sol1.py @@ -1,5 +1,10 @@ from __future__ import print_function +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + def fibonacci(n): if n == 1 or type(n) is not int: return 0 diff --git a/Project Euler/Problem 28/sol1.py b/Project Euler/Problem 28/sol1.py index d44d017892ce..4942115ce537 100644 --- a/Project Euler/Problem 28/sol1.py +++ b/Project Euler/Problem 28/sol1.py @@ -1,6 +1,11 @@ from __future__ import print_function from math import ceil +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + def diagonal_sum(n): total = 1 From a4cd8978b4c10cb5482501bc7ae9df401f75c113 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Mon, 19 Mar 2018 10:10:10 -0400 Subject: [PATCH 0065/2908] Solution to Problem 11 --- Project Euler/Problem 11/grid.txt | 20 ++++++++++ Project Euler/Problem 11/sol1.py | 63 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 Project Euler/Problem 11/grid.txt create mode 100644 Project Euler/Problem 11/sol1.py diff --git a/Project Euler/Problem 11/grid.txt b/Project Euler/Problem 11/grid.txt new file mode 100644 index 000000000000..1fc75c66a314 --- /dev/null +++ b/Project Euler/Problem 11/grid.txt @@ -0,0 +1,20 @@ +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 \ No newline at end of file diff --git a/Project Euler/Problem 11/sol1.py b/Project Euler/Problem 11/sol1.py new file mode 100644 index 000000000000..d432148fbc69 --- /dev/null +++ b/Project Euler/Problem 11/sol1.py @@ -0,0 +1,63 @@ +from __future__ import print_function +''' +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 +''' + +def largest_product(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 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] + + #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] + + #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 + + return largest + +if __name__ == '__main__': + grid = [] + with open('grid.txt') as file: + for line in file: + grid.append(line.strip('\n').split(' ')) + + grid = [[int(i) for i in grid[j]] for j in range(len(grid))] + + print(largest_product(grid)) \ No newline at end of file From 924d0a0552b919f908d2ec4752d9ddb7066a4cb1 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Mon, 19 Mar 2018 11:26:47 -0400 Subject: [PATCH 0066/2908] Added xrange compatibility for Python 3 --- Project Euler/Problem 11/sol1.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Project Euler/Problem 11/sol1.py b/Project Euler/Problem 11/sol1.py index d432148fbc69..b882dc449156 100644 --- a/Project Euler/Problem 11/sol1.py +++ b/Project Euler/Problem 11/sol1.py @@ -24,6 +24,11 @@ 01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48 ''' +try: + xrange #Python 2 +except NameError: + xrange = range #Python 2 + def largest_product(grid): nColumns = len(grid[0]) nRows = len(grid) @@ -33,8 +38,8 @@ def largest_product(grid): rlDiagProduct = 0 #Check vertically, horizontally, diagonally at the same time (only works for nxn grid) - for i in range(nColumns): - for j in range(nRows-3): + 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] @@ -58,6 +63,6 @@ def largest_product(grid): for line in file: grid.append(line.strip('\n').split(' ')) - grid = [[int(i) for i in grid[j]] for j in range(len(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 From a753acf1deddf28bb6a83a77a60dc8597398f67d Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Tue, 20 Mar 2018 15:23:09 -0400 Subject: [PATCH 0067/2908] Computes minimum cost for converting one string into another --- strings/min-cost-string-conversion.py | 124 ++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 strings/min-cost-string-conversion.py diff --git a/strings/min-cost-string-conversion.py b/strings/min-cost-string-conversion.py new file mode 100644 index 000000000000..1798e81cd9e0 --- /dev/null +++ b/strings/min-cost-string-conversion.py @@ -0,0 +1,124 @@ +from __future__ import print_function + +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + +def compute_transform_tables(X, Y, cC, cR, cD, cI): +''' +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 +''' + X = list(X) + Y = list(Y) + m = len(X) + n = len(Y) + + costs = [[0 for _ in xrange(n+1)] for _ in xrange(m+1)] + ops = [[0 for _ in xrange(n+1)] for _ in xrange(m+1)] + + for i in xrange(1, m+1): + costs[i][0] = i*cD + ops[i][0] = 'D%c' % X[i-1] + + for i in xrange(1, n+1): + costs[0][i] = i*cI + ops[0][i] = 'I%c' % Y[i-1] + + for i in xrange(1, m+1): + for j in xrange(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][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 + +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__': + from time import sleep + _, 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) + + file = open('min_cost.txt', 'w') + + string = list('Python') + i = 0 + cost = 0 + 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') + + i += 1 + 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') + + i += 1 + 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') + + i += 1 + 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') + + i += 1 + cost += 2 + + print ''.join(string) + print 'Cost: ', cost + + file.write('\r\nMinimum cost: ' + str(cost)) + file.close() \ No newline at end of file From 6abab54c2b1b5d1078140ddf46a20b34324c75a9 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Tue, 20 Mar 2018 18:49:47 -0400 Subject: [PATCH 0068/2908] Minimum cost for transformation from one string to another using basic operations --- strings/min-cost-string-conversion.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/strings/min-cost-string-conversion.py b/strings/min-cost-string-conversion.py index 1798e81cd9e0..8bad9c09405c 100644 --- a/strings/min-cost-string-conversion.py +++ b/strings/min-cost-string-conversion.py @@ -5,7 +5,6 @@ except NameError: xrange = range #Python 3 -def compute_transform_tables(X, Y, cC, cR, cD, cI): ''' Algorithm for calculating the most cost-efficient sequence for converting one string into another. The only allowed operations are @@ -14,6 +13,7 @@ def compute_transform_tables(X, Y, cC, cR, cD, cI): ---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) @@ -81,13 +81,13 @@ def assemble_transformation(ops, i, j): i = 0 cost = 0 for op in sequence: - print ''.join(string) + 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') - i += 1 cost -= 1 elif op[0] == 'R': string[i] = op[2] @@ -96,7 +96,6 @@ def assemble_transformation(ops, i, j): file.write('\t\t' + ''.join(string)) file.write('\r\n') - i += 1 cost += 1 elif op[0] == 'D': string.pop(i) @@ -105,7 +104,6 @@ def assemble_transformation(ops, i, j): file.write('\t\t\t' + ''.join(string)) file.write('\r\n') - i += 1 cost += 2 else: string.insert(i, op[1]) @@ -114,11 +112,12 @@ def assemble_transformation(ops, i, j): file.write('\t\t\t' + ''.join(string)) file.write('\r\n') - i += 1 cost += 2 - print ''.join(string) - print 'Cost: ', cost + i += 1 + + print(''.join(string)) + print('Cost: ', cost) file.write('\r\nMinimum cost: ' + str(cost)) file.close() \ No newline at end of file From 0deb2277053a25616814cce00fad142e0e17e614 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Tue, 20 Mar 2018 18:50:55 -0400 Subject: [PATCH 0069/2908] Update min-cost-string-conversion.py --- strings/min-cost-string-conversion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/strings/min-cost-string-conversion.py b/strings/min-cost-string-conversion.py index 8bad9c09405c..7e3298137c05 100644 --- a/strings/min-cost-string-conversion.py +++ b/strings/min-cost-string-conversion.py @@ -1,7 +1,7 @@ from __future__ import print_function try: - xrange #Python 2 + xrange #Python 2 except NameError: xrange = range #Python 3 @@ -120,4 +120,4 @@ def assemble_transformation(ops, i, j): print('Cost: ', cost) file.write('\r\nMinimum cost: ' + str(cost)) - file.close() \ No newline at end of file + file.close() From 1fa23f57d7753bd4a879b5246b6a694d3a7bdbb6 Mon Sep 17 00:00:00 2001 From: gabriel Date: Tue, 20 Mar 2018 20:48:58 -0300 Subject: [PATCH 0070/2908] hash functions added --- data_structures/hashing/__init__.py | 6 ++ data_structures/hashing/double_hash.py | 33 ++++++++ data_structures/hashing/hash_table.py | 84 +++++++++++++++++++ .../hashing/hash_table_with_linked_list.py | 24 ++++++ .../hashing/number_theory/__init__.py | 0 .../hashing/number_theory/prime_numbers.py | 29 +++++++ data_structures/hashing/quadratic_probing.py | 26 ++++++ 7 files changed, 202 insertions(+) create mode 100644 data_structures/hashing/__init__.py create mode 100644 data_structures/hashing/double_hash.py create mode 100644 data_structures/hashing/hash_table.py create mode 100644 data_structures/hashing/hash_table_with_linked_list.py create mode 100644 data_structures/hashing/number_theory/__init__.py create mode 100644 data_structures/hashing/number_theory/prime_numbers.py create mode 100644 data_structures/hashing/quadratic_probing.py diff --git a/data_structures/hashing/__init__.py b/data_structures/hashing/__init__.py new file mode 100644 index 000000000000..b96ddd478458 --- /dev/null +++ b/data_structures/hashing/__init__.py @@ -0,0 +1,6 @@ +from .hash_table import HashTable + +class QuadraticProbing(HashTable): + + def __init__(self): + super(self.__class__, self).__init__() diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py new file mode 100644 index 000000000000..60098cda0ce1 --- /dev/null +++ b/data_structures/hashing/double_hash.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +from .hash_table import HashTable +from number_theory.prime_numbers import next_prime, check_prime + + +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 + return next_prime_gt - (data % next_prime_gt) + + 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): + i = 1 + 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 + + return new_key diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py new file mode 100644 index 000000000000..f0de128d1ad1 --- /dev/null +++ b/data_structures/hashing/hash_table.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +from number_theory.prime_numbers import next_prime + + +class HashTable: + """ + Basic Hash Table example with open addressing and linear probing + """ + + def __init__(self, size_table, charge_factor=None, lim_charge=None): + self.size_table = size_table + self.values = [None] * self.size_table + self.lim_charge = 0.75 if lim_charge is None else lim_charge + self.charge_factor = 1 if charge_factor is None else charge_factor + self.__aux_list = [] + self._keys = {} + + 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) + + def hash_function(self, key): + return key % self.size_table + + def _step_by_step(self, step_ord): + + print("step {0}".format(step_ord)) + print([i for i in range(len(self.values))]) + print(self.values) + + def bulk_insert(self, values): + i = 1 + self.__aux_list = values + for value in values: + self.insert_data(value) + self._step_by_step(i) + i += 1 + + def _set_value(self, key, data): + self.values[key] = data + self._keys[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: + + if self.values.count(None) > 0: + new_key = self.hash_function(new_key + 1) + else: + new_key = None + break + + return new_key + + 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 ;/ + map(self.insert_data, survivor_values) + + def insert_data(self, data): + key = self.hash_function(data) + + if self.values[key] is None: + self._set_value(key, data) + + elif self.values[key] == data: + pass + + else: + colision_resolution = self._colision_resolution(key, data) + if colision_resolution is not None: + self._set_value(colision_resolution, 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 new file mode 100644 index 000000000000..9689e4fc9fcf --- /dev/null +++ b/data_structures/hashing/hash_table_with_linked_list.py @@ -0,0 +1,24 @@ +from .hash_table import HashTable +from collections import deque + + +class HashTableWithLinkedList(HashTable): + 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].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 + + def _colision_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) + + 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/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py new file mode 100644 index 000000000000..8a521bc45758 --- /dev/null +++ b/data_structures/hashing/number_theory/prime_numbers.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +""" + module to operations with prime numbers +""" + + +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)]) + + +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 new file mode 100644 index 000000000000..f7a9ac1ae347 --- /dev/null +++ b/data_structures/hashing/quadratic_probing.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +from .hash_table import HashTable + + +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) + + 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 + + if new_key is None: + break + + return new_key From a5f0ae84eaae2831e94afb046efda027de22507e Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Tue, 20 Mar 2018 20:23:51 -0400 Subject: [PATCH 0071/2908] Solution to Problem 22 --- Project Euler/Problem 22/p022_names.txt | 1 + Project Euler/Problem 22/sol1.py | 37 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 Project Euler/Problem 22/p022_names.txt create mode 100644 Project Euler/Problem 22/sol1.py diff --git a/Project Euler/Problem 22/p022_names.txt b/Project Euler/Problem 22/p022_names.txt new file mode 100644 index 000000000000..7b8986bf6ce9 --- /dev/null +++ b/Project Euler/Problem 22/p022_names.txt @@ -0,0 +1 @@ +"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" \ No newline at end of file diff --git a/Project Euler/Problem 22/sol1.py b/Project Euler/Problem 22/sol1.py new file mode 100644 index 000000000000..7754306583dc --- /dev/null +++ b/Project Euler/Problem 22/sol1.py @@ -0,0 +1,37 @@ +# -*- 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. + +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? +''' +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + +with open('p022_names.txt') as file: + names = str(file.readlines()[0]) + names = names.replace('"', '').split(',') + +names.sort() + +name_score = 0 +total_score = 0 + +for i, name in enumerate(names): + for letter in name: + name_score += ord(letter) - 64 + + total_score += (i+1)*name_score + name_score = 0 + +print(total_score) \ No newline at end of file From 1ead4e0f2d228481d1095a8bb9919a4d1500dbd6 Mon Sep 17 00:00:00 2001 From: GirijaManoj kumar reddy Kalakoti Date: Thu, 22 Mar 2018 15:50:49 +0530 Subject: [PATCH 0072/2908] Added Solution to Problem 2 in a simple approach --- Project Euler/Problem 02/sol3.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Project Euler/Problem 02/sol3.py diff --git a/Project Euler/Problem 02/sol3.py b/Project Euler/Problem 02/sol3.py new file mode 100644 index 000000000000..d36b741bb4f9 --- /dev/null +++ b/Project Euler/Problem 02/sol3.py @@ -0,0 +1,20 @@ +''' +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 Date: Thu, 22 Mar 2018 09:33:54 -0400 Subject: [PATCH 0073/2908] Counting integer partitions --- dynamic_programming/integer_partition.py | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 dynamic_programming/integer_partition.py diff --git a/dynamic_programming/integer_partition.py b/dynamic_programming/integer_partition.py new file mode 100644 index 000000000000..7b27afebaa6c --- /dev/null +++ b/dynamic_programming/integer_partition.py @@ -0,0 +1,45 @@ +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[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 > 0: + memo[n][k] += memo[n-k-1][k] + + return memo[m][m-1] + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + try: + n = int(raw_input('Enter a number: ')) + 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 From 49a33969e6f303eaa060a74092e95bc6f49f456c Mon Sep 17 00:00:00 2001 From: girijamanojkumarreddy Date: Thu, 22 Mar 2018 19:29:44 +0530 Subject: [PATCH 0074/2908] Added a Solution using Euclidean Algo --- Project Euler/Problem 05/sol2.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Project Euler/Problem 05/sol2.py diff --git a/Project Euler/Problem 05/sol2.py b/Project Euler/Problem 05/sol2.py new file mode 100644 index 000000000000..cd11437f30db --- /dev/null +++ b/Project Euler/Problem 05/sol2.py @@ -0,0 +1,20 @@ +#!/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? +''' + +""" Euclidean GCD Algorithm """ +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) From 570c27cfdd892cdde15d8167a6d261c54581dde4 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Thu, 22 Mar 2018 11:27:50 -0400 Subject: [PATCH 0075/2908] Solution to Problem 21 --- Project Euler/Problem 21/sol1.py | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Project Euler/Problem 21/sol1.py diff --git a/Project Euler/Problem 21/sol1.py b/Project Euler/Problem 21/sol1.py new file mode 100644 index 000000000000..6d137a7d4332 --- /dev/null +++ b/Project Euler/Problem 21/sol1.py @@ -0,0 +1,42 @@ +#-.- coding: latin-1 -.- +from __future__ import print_function +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. + +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 +except NameError: + 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 + +sums = [] +total = 0 + +for i in xrange(1, 10000): + n = sum_of_divisors(i) + + if n < len(sums): + if sums[n-1] == i: + total += n + i + + sums.append(n) + +print(total) \ No newline at end of file From 68e5bb48041252ce72f35de35892de1d2934b1d1 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Thu, 22 Mar 2018 12:09:45 -0400 Subject: [PATCH 0076/2908] Solution to Problem 76 --- Project Euler/Problem 76/sol1.py | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Project Euler/Problem 76/sol1.py diff --git a/Project Euler/Problem 76/sol1.py b/Project Euler/Problem 76/sol1.py new file mode 100644 index 000000000000..2832f6d7afb6 --- /dev/null +++ b/Project Euler/Problem 76/sol1.py @@ -0,0 +1,35 @@ +from __future__ import print_function +''' +Counting Summations +Problem 76 + +It is possible to write five as a sum in exactly six different ways: + +4 + 1 +3 + 2 +3 + 1 + 1 +2 + 2 + 1 +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? +''' +try: + xrange #Python 2 +except NameError: + 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 + + 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) + +print(partition(100)) \ No newline at end of file From f538ea51dc78a12fd2205940a6fed9572c87e6c3 Mon Sep 17 00:00:00 2001 From: girijamanojkumarreddy Date: Fri, 23 Mar 2018 16:27:25 +0530 Subject: [PATCH 0077/2908] Added General solution for Problem 9 --- Project Euler/Problem 9/sol2.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Project Euler/Problem 9/sol2.py diff --git a/Project Euler/Problem 9/sol2.py b/Project Euler/Problem 9/sol2.py new file mode 100644 index 000000000000..13674d25875e --- /dev/null +++ b/Project Euler/Problem 9/sol2.py @@ -0,0 +1,19 @@ +"""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 +import sys + +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) From eb40f43650409e534c69b3d8e1d5bf8e39f23bd8 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Sat, 24 Mar 2018 21:48:48 -0400 Subject: [PATCH 0078/2908] Solution to Problem 53 --- Project Euler/Problem 53/sol1.py | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Project Euler/Problem 53/sol1.py diff --git a/Project Euler/Problem 53/sol1.py b/Project Euler/Problem 53/sol1.py new file mode 100644 index 000000000000..ed6d5329eb4e --- /dev/null +++ b/Project Euler/Problem 53/sol1.py @@ -0,0 +1,36 @@ +#-.- coding: latin-1 -.- +from __future__ import print_function +from math import factorial +''' +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 + +In combinatorics, we use the notation, 5C3 = 10. + +In general, + +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? +''' +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + +def combinations(n, r): + return factorial(n)/(factorial(r)*factorial(n-r)) + +total = 0 + +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 From 7b1b33a60a0289949851f0a3405f1dd74e573fb0 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Sun, 25 Mar 2018 23:14:59 -0400 Subject: [PATCH 0079/2908] Solution to Problem 19 --- Project Euler/Problem 19/sol1.py | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Project Euler/Problem 19/sol1.py diff --git a/Project Euler/Problem 19/sol1.py b/Project Euler/Problem 19/sol1.py new file mode 100644 index 000000000000..94cf117026a4 --- /dev/null +++ b/Project Euler/Problem 19/sol1.py @@ -0,0 +1,51 @@ +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. + +1 Jan 1900 was a Monday. +Thirty days has September, +April, June and November. +All the rest have thirty-one, +Saving February alone, +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. + +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 + +sundays = 0 + +while year < 2001: + 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: + month += 1 + day = day-days_per_month[month-2] + elif day > 29 and month is 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 is 1: + sundays += 1 + +print(sundays) \ No newline at end of file From 53d9989b13a14d16bff9bbe023f29d081ce7b041 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Wed, 28 Mar 2018 19:11:14 -0400 Subject: [PATCH 0080/2908] Solution to Problem 12 --- Project Euler/Problem 12/sol1.py | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Project Euler/Problem 12/sol1.py diff --git a/Project Euler/Problem 12/sol1.py b/Project Euler/Problem 12/sol1.py new file mode 100644 index 000000000000..9c4483fd62e5 --- /dev/null +++ b/Project Euler/Problem 12/sol1.py @@ -0,0 +1,46 @@ +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: + +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? +''' +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): + if n%i == 0: + nDivisors += 2 + + return nDivisors + +tNum = 1 +i = 1 + +while True: + i += 1 + tNum += i + + if count_divisors(tNum) > 500: + break + +print(tNum) \ No newline at end of file From 9ed60ba882c7b6a07b7b951447558710fcbdcc4b Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Mon, 2 Apr 2018 12:20:53 -0400 Subject: [PATCH 0081/2908] Solution to Problem 36 --- Project Euler/Problem 36/sol1.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Project Euler/Problem 36/sol1.py diff --git a/Project Euler/Problem 36/sol1.py b/Project Euler/Problem 36/sol1.py new file mode 100644 index 000000000000..d78e7e59f210 --- /dev/null +++ b/Project Euler/Problem 36/sol1.py @@ -0,0 +1,30 @@ +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. + +(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) + + if n == n[::-1]: + return True + else: + return False + +total = 0 + +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 From 6a8f1cf2325253f7643bd23d1e91cb410b3cbb00 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Mon, 2 Apr 2018 23:46:28 -0400 Subject: [PATCH 0082/2908] Solution to Problem 40 --- Project Euler/Problem 40/sol1.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Project Euler/Problem 40/sol1.py diff --git a/Project Euler/Problem 40/sol1.py b/Project Euler/Problem 40/sol1.py new file mode 100644 index 000000000000..ab4017512a1a --- /dev/null +++ b/Project Euler/Problem 40/sol1.py @@ -0,0 +1,26 @@ +#-.- coding: latin-1 -.- +from __future__ import print_function +''' +Champernowne's constant +Problem 40 +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. + +d1 × d10 × d100 × d1000 × d10000 × d100000 × d1000000 +''' + +constant = [] +i = 1 + +while len(constant) < 1e6: + constant.append(str(i)) + i += 1 + +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 From b172ec38418044cefaaa6aafcf56eee172308de2 Mon Sep 17 00:00:00 2001 From: Daniel Ingram Date: Tue, 3 Apr 2018 00:04:38 -0400 Subject: [PATCH 0083/2908] Solution to Problem 52 --- Project Euler/Problem 52/sol1.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Project Euler/Problem 52/sol1.py diff --git a/Project Euler/Problem 52/sol1.py b/Project Euler/Problem 52/sol1.py new file mode 100644 index 000000000000..376b4cfa1d63 --- /dev/null +++ b/Project Euler/Problem 52/sol1.py @@ -0,0 +1,23 @@ +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. + +Find the smallest positive integer, x, such that 2x, 3x, 4x, 5x, and 6x, contain the same digits. +''' +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))): + break + + i += 1 + +print(i) \ No newline at end of file From 18907e4a072077108f33cc0b724ac273724631bd Mon Sep 17 00:00:00 2001 From: douly Date: Fri, 13 Apr 2018 09:56:40 +0800 Subject: [PATCH 0084/2908] fix type error (except an int) in jumpmp_search line 7. --- searches/jump_search.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/searches/jump_search.py b/searches/jump_search.py index 4cff92bb585c..10cb933f2f35 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -2,11 +2,11 @@ import math def jump_search(arr, x): n = len(arr) - step = math.floor(math.sqrt(n)) + step = int(math.floor(math.sqrt(n))) prev = 0 while arr[min(step, n)-1] < x: prev = step - step += math.floor(math.sqrt(n)) + step += int(math.floor(math.sqrt(n))) if prev >= n: return -1 @@ -23,4 +23,4 @@ def jump_search(arr, x): 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)); \ No newline at end of file +print("\nNumber " + str(x) +" is at index " + str(index)); From 3a0555bdd7d9cfb4cfcf165249bb95b67cab3877 Mon Sep 17 00:00:00 2001 From: Syed Haseeb Shah Date: Fri, 13 Apr 2018 20:25:47 +0500 Subject: [PATCH 0085/2908] Create NewtonRaphsonMethod.py Newton-Raphson method is non bracketing iterative algorithm to find the nearest root of an equation from point 'a'. It's much faster because convergence to the real root is very much faster than any other methods. --- ArithmeticAnalysis/NewtonRaphsonMethod.py | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ArithmeticAnalysis/NewtonRaphsonMethod.py diff --git a/ArithmeticAnalysis/NewtonRaphsonMethod.py b/ArithmeticAnalysis/NewtonRaphsonMethod.py new file mode 100644 index 000000000000..40501134e28a --- /dev/null +++ b/ArithmeticAnalysis/NewtonRaphsonMethod.py @@ -0,0 +1,38 @@ +# Implementing Newton Raphson method in python +# Author: Haseeb + +from sympy import diff +from decimal import Decimal +from math import sin, cos, exp + +def NewtonRaphson(func, a): + ''' Finds root from the point 'a' onwards by Newton-Raphson method ''' + while True: + x = a + c = Decimal(a) - ( Decimal(eval(func)) / Decimal(eval(str(diff(func)))) ) + + x = c + 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 trignometric fucntion + # 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 0fdd2d369ea5b6b4b50b1ed18c5d6c61bf073e79 Mon Sep 17 00:00:00 2001 From: Syed Haseeb Shah Date: Fri, 13 Apr 2018 20:49:38 +0500 Subject: [PATCH 0086/2908] Create Onepad_Cipher.py In one pad algorithm length of key and length of message are equal which results in endless possibilities of false messages on bruteforce. --- ciphers/Onepad_Cipher.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 ciphers/Onepad_Cipher.py diff --git a/ciphers/Onepad_Cipher.py b/ciphers/Onepad_Cipher.py new file mode 100644 index 000000000000..4365924920f6 --- /dev/null +++ b/ciphers/Onepad_Cipher.py @@ -0,0 +1,28 @@ +class Onepad: + def encrypt(self, text): + '''Function to encrypt text using psedo-random numbers''' + plain = [] + key = [] + cipher = [] + for i in text: + plain.append(ord(i)) + for i in plain: + k = random.randint(1, 300) + 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.''' + plain = [] + for i in range(len(key)): + p = (cipher[i]-(key[i])**2)/key[i] + plain.append(chr(p)) + plain = ''.join([i for i in plain]) + return plain + +if __name__ == '__main__': + c,k = Onepad().encrypt('Hello') + print c, k + print Onepad().decrypt(c, k) From 13ebdc35fda3e4b5d7c36f382f32f702af2ceed5 Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Sun, 15 Apr 2018 23:29:21 +0200 Subject: [PATCH 0087/2908] I fixed the sol3.py of problem 1 --- Project Euler/Problem 01/sol3.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Project Euler/Problem 01/sol3.py b/Project Euler/Problem 01/sol3.py index 78b4d0e93519..f4f3aefcc5de 100644 --- a/Project Euler/Problem 01/sol3.py +++ b/Project Euler/Problem 01/sol3.py @@ -1,3 +1,5 @@ +from __future__ import print_function + ''' Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, @@ -7,7 +9,7 @@ ''' 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 except NameError: From 10b0a40b2eae2e986cffd6d4760ada01a6ee99b1 Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Sun, 15 Apr 2018 23:52:45 +0200 Subject: [PATCH 0088/2908] fixed solution 4 of problem 1 --- Project Euler/Problem 01/sol4.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Project Euler/Problem 01/sol4.py b/Project Euler/Problem 01/sol4.py index 0f5dc370b441..7941f5fcd3fe 100644 --- a/Project Euler/Problem 01/sol4.py +++ b/Project Euler/Problem 01/sol4.py @@ -9,7 +9,6 @@ def mulitples(limit): if (result < limit): zmulti.append(result) temp += 1 - continue else: temp = 1 break @@ -18,15 +17,14 @@ def mulitples(limit): if (result < limit): xmulti.append(result) temp += 1 - continue else: - temp = 1 break - return (sum(zmulti) + sum(xmulti)) + collection = list(set(xmulti+zmulti)) + return (sum(collection)) -print (mulitples(100)) +print (mulitples(1000)) From 621192998e2aef51b5ba188490b8f2d044beff9a Mon Sep 17 00:00:00 2001 From: Harshil Date: Mon, 16 Apr 2018 06:56:53 +0200 Subject: [PATCH 0089/2908] basic client-server implementation --- server.py | 43 ---------------------------------- simple-client-server/README.md | 6 +++++ simple-client-server/client.py | 14 +++++++++++ simple-client-server/server.py | 21 +++++++++++++++++ 4 files changed, 41 insertions(+), 43 deletions(-) delete mode 100644 server.py create mode 100644 simple-client-server/README.md create mode 100644 simple-client-server/client.py create mode 100644 simple-client-server/server.py diff --git a/server.py b/server.py deleted file mode 100644 index 578c24285bfb..000000000000 --- a/server.py +++ /dev/null @@ -1,43 +0,0 @@ -# all imports - including #s -import socket -#import os -#import sys -#import subprocess -# end of imports -# the below classes will clarify what information is for the attacker and client -class Termrequire: - host = socket.gethostname() - port = 3333 # fake numeral for the moment -class Clientrequire: - host = socket.gethostname() - port = 2222 # fake numeral for the moment -#CORE REQUIREMENTS OF PROGRAM: -### host ip = server ip -### potential connection hosts info (host, port) -### user.config -### user.config -# using SERVER for connections and linux meterpreter sessions -# SERVER DETAILS: - #5 client availability for pivoting #although that is not yet available in a regular form of - #exploitation - we have to go with what we have. - -# #learnmore - USER_CONFIG -# server ip will be displayed every connection at version 2.0 -# terminal attacker socket object creation -t = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -# terminal attacker socket binding -t.bind() -# terminal attacker socket listen -t.listen() -# client socket object creation -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -# binding information with s.bind method -s.bind() -#listening for connections with s.listen method -s.listen(1) -# server_functionality waits for terminal shell and then gets client information connectivity -def func4client(): - s.accept() -# terminal functionality for attacker - I will definitely customize it soon. Maybe tkinter? -def func4term(): - t.accept() \ No newline at end of file diff --git a/simple-client-server/README.md b/simple-client-server/README.md new file mode 100644 index 000000000000..f51947f2105a --- /dev/null +++ b/simple-client-server/README.md @@ -0,0 +1,6 @@ +# simple client server + +#### Note: +- Run **`server.py`** first. +- Now, run **`client.py`**. +- verify the output. diff --git a/simple-client-server/client.py b/simple-client-server/client.py new file mode 100644 index 000000000000..c91e6233ad16 --- /dev/null +++ b/simple-client-server/client.py @@ -0,0 +1,14 @@ +# client.py + +import socket + +HOST, PORT = '127.0.0.1', 1400 + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect((HOST, PORT)) + +s.send(b'Hello World') +data = s.recv(1024) + +s.close() +print(repr(data.decode('ascii'))) diff --git a/simple-client-server/server.py b/simple-client-server/server.py new file mode 100644 index 000000000000..c6b661d99044 --- /dev/null +++ b/simple-client-server/server.py @@ -0,0 +1,21 @@ +# server.py + +import socket + +HOST, PORT = '127.0.0.1', 1400 + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.bind((HOST, PORT)) +s.listen(1) + +conn, addr = s.accept() + +print('connected to:', addr) + +while 1: + data = conn.recv(1024) + if not data: + break + conn.send(data + b' [ addition by server ]') + +conn.close() From 3a7e75f329d14bad68043896b856fed36e7bc2ef Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Mon, 16 Apr 2018 13:37:04 +0200 Subject: [PATCH 0090/2908] added a option for termination the program --- hashes/chaos_machine.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 8b6c004380aa..59d0d7ffdd3f 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -1,6 +1,12 @@ """example of simple chaos machine""" from __future__ import print_function +try: + raw_input + input = raw_input +except: + pass + # Chaos Machine (K, t, m) K = [0.33, 0.44, 0.55, 0.44, 0.33]; t = 3; m = 5 @@ -91,7 +97,11 @@ def reset(): for chunk in message: push(chunk) +# for controlling +inp = "" + # Pulling Data (Output) -while True: +while inp != "e" and inp != "E": print("%s" % format(pull(), '#04x')) print(buffer_space); print(params_space) + inp = input("(e)exit? ") From c1422ec99c754e542223979a5bdc882f67ac9ac5 Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Mon, 16 Apr 2018 13:42:44 +0200 Subject: [PATCH 0091/2908] fixed the assert statments --- hashes/chaos_machine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 59d0d7ffdd3f..766dbba3c5c9 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -38,8 +38,8 @@ def push(seed): r # Saving to Parameters Space # Logistic Map - assert(max(buffer_space) < 1) - assert(max(params_space) < 4) + assert max(buffer_space) < 1 + assert max(params_space) < 4 # Machine Time machine_time += 1 From cfae621f46d6f2ae956820522e88af1bbb20475a Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Mon, 16 Apr 2018 14:13:49 +0200 Subject: [PATCH 0092/2908] I documented the md5 hash --- hashes/md5.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/hashes/md5.py b/hashes/md5.py index c336b5fe49ee..3ae13089d019 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -10,6 +10,13 @@ def rearrange(bitString32): return newString def reformatHex(i): + """[summary] + Converts the given integer into 8-digit hex number. + + Arguments: + i {[int]} -- [integer] + """ + hexrep = format(i,'08x') thing = "" for i in [3,2,1,0]: @@ -17,6 +24,16 @@ def reformatHex(i): 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: @@ -26,6 +43,15 @@ def pad(bitString): 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] @@ -34,6 +60,7 @@ def getBlock(bitString): mySplits.append(int(rearrange(currPart[32*i:32*i+32]),2)) yield mySplits currPos += 512 + def not32(i): i_str = format(i,'032b') new_str = '' @@ -48,6 +75,13 @@ 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') From 0494d48f84f74145d6eec5e4dab3daad21c490aa Mon Sep 17 00:00:00 2001 From: Christian Bender Date: Mon, 16 Apr 2018 14:18:23 +0200 Subject: [PATCH 0093/2908] added a docstring --- hashes/md5.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hashes/md5.py b/hashes/md5.py index 3ae13089d019..d3f15510874e 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -2,6 +2,19 @@ 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 = "" From 356218254263f45536bd1b053f8cb9e30450087a Mon Sep 17 00:00:00 2001 From: Bhaavik Pratyush Date: Mon, 16 Apr 2018 19:17:28 +0530 Subject: [PATCH 0094/2908] Update sol2.py --- Project Euler/Problem 01/sol2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Project Euler/Problem 01/sol2.py b/Project Euler/Problem 01/sol2.py index d330387e98ab..2b7760e0bfff 100644 --- a/Project Euler/Problem 01/sol2.py +++ b/Project Euler/Problem 01/sol2.py @@ -11,10 +11,10 @@ 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 +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) From 4b58a887324ce6c5fc9b36766be9b3b742c44d3c Mon Sep 17 00:00:00 2001 From: shivg7706 Date: Thu, 26 Apr 2018 08:49:55 +0530 Subject: [PATCH 0095/2908] another sol for 11 --- Project Euler/Problem 11/sol2.py | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Project Euler/Problem 11/sol2.py diff --git a/Project Euler/Problem 11/sol2.py b/Project Euler/Problem 11/sol2.py new file mode 100644 index 000000000000..b03395f01697 --- /dev/null +++ b/Project Euler/Problem 11/sol2.py @@ -0,0 +1,39 @@ +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 From ab9ee4064eb4a6ab7f1cab25116cc1ace996a116 Mon Sep 17 00:00:00 2001 From: shivg7706 Date: Thu, 26 Apr 2018 08:53:17 +0530 Subject: [PATCH 0096/2908] adding 22 and 24 --- Project Euler/Problem 24/sol1.py | 7 + Project Euler/Problem 24/sol2.py | 533 +++++++++++++++++++++++++++++++ 2 files changed, 540 insertions(+) create mode 100644 Project Euler/Problem 24/sol1.py create mode 100644 Project Euler/Problem 24/sol2.py diff --git a/Project Euler/Problem 24/sol1.py b/Project Euler/Problem 24/sol1.py new file mode 100644 index 000000000000..b20493cb03af --- /dev/null +++ b/Project Euler/Problem 24/sol1.py @@ -0,0 +1,7 @@ +from itertools import permutations +def main(): + result=list(map("".join, permutations('0123456789'))) + print(result[999999]) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Project Euler/Problem 24/sol2.py b/Project Euler/Problem 24/sol2.py new file mode 100644 index 000000000000..d7f9abf09d49 --- /dev/null +++ b/Project Euler/Problem 24/sol2.py @@ -0,0 +1,533 @@ +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) + + +if __name__ == '__main__': + main() From 515cf2e81a181348f4968b1944ce34af414a1f5d Mon Sep 17 00:00:00 2001 From: shivg7706 Date: Thu, 26 Apr 2018 08:54:44 +0530 Subject: [PATCH 0097/2908] changes --- Project Euler/{Problem 24 => Problem 22}/sol2.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Project Euler/{Problem 24 => Problem 22}/sol2.py (100%) diff --git a/Project Euler/Problem 24/sol2.py b/Project Euler/Problem 22/sol2.py similarity index 100% rename from Project Euler/Problem 24/sol2.py rename to Project Euler/Problem 22/sol2.py From 09088cd8351729cfa093d46c03e35e5b222d5d90 Mon Sep 17 00:00:00 2001 From: Syed Haseeb Shah Date: Sat, 19 May 2018 16:07:24 +0500 Subject: [PATCH 0098/2908] Create Fischer-Yates_Shuffle.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Fisher–Yates shuffle is an algorithm for generating a random permutation of a finite sequence. For more details visit wikipedia/Fischer-Yates-Shuffle --- other/Fischer-Yates_Shuffle.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 other/Fischer-Yates_Shuffle.py diff --git a/other/Fischer-Yates_Shuffle.py b/other/Fischer-Yates_Shuffle.py new file mode 100644 index 000000000000..28cdff75ab85 --- /dev/null +++ b/other/Fischer-Yates_Shuffle.py @@ -0,0 +1,20 @@ +''' +The Fisher–Yates shuffle is an algorithm for generating a random permutation of a finite sequence. +For more details visit +wikipedia/Fischer-Yates-Shuffle. +''' +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 + +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)) From 35110b6e44cbe5b83878cd5d64a85466723a153a Mon Sep 17 00:00:00 2001 From: irokafetzaki Date: Sun, 20 May 2018 02:27:01 +0300 Subject: [PATCH 0099/2908] Tabu Search --- searches/tabuTestData.txt | 10 ++ searches/tabu_search.py | 313 +++++++++++++++++++++++++++++++++++ searches/test_tabu_search.py | 46 +++++ 3 files changed, 369 insertions(+) create mode 100644 searches/tabuTestData.txt create mode 100644 searches/tabu_search.py create mode 100644 searches/test_tabu_search.py diff --git a/searches/tabuTestData.txt b/searches/tabuTestData.txt new file mode 100644 index 000000000000..f797ff1c627a --- /dev/null +++ b/searches/tabuTestData.txt @@ -0,0 +1,10 @@ +a b 20 +a c 18 +a d 22 +a e 26 +b c 10 +b d 11 +b e 12 +c d 23 +c e 24 +d e 40 diff --git a/searches/tabu_search.py b/searches/tabu_search.py new file mode 100644 index 000000000000..b93b3202bce7 --- /dev/null +++ b/searches/tabu_search.py @@ -0,0 +1,313 @@ +""" +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: + +node1 node2 distance_between_node1_and_node2 +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: +node1 node2 distance_between_node1_and_node2 +node2 node1 distance_between_node2_and_node1 + +For pytests run following command: +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 +e.g. python tabu_search.py -f tabudata2.txt -i 4 -s 3 +""" + +import copy +import argparse +import sys + + +def generate_neighbours(path): + """ + Pure implementation of generating a dictionary of neighbors and the cost with each + 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. + + 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. + + """ + f = open(path, "r") + + dict_of_neighbours = {} + + for line in f: + if line.split()[0] not in dict_of_neighbours: + _list = list() + _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]]) + 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]]) + f.close() + + return dict_of_neighbours + + +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 + 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. + + """ + + f = open(path, "r") + start_node = f.read(1) + end_node = start_node + + first_solution = [] + + visiting = start_node + + distance_of_first_solution = 0 + f.close() + while visiting not in first_solution: + minim = 10000 + for k in dict_of_neighbours[visiting]: + if int(k[1]) < int(minim) and k[0] not in first_solution: + minim = k[1] + best_node = k[0] + + first_solution.append(visiting) + distance_of_first_solution = distance_of_first_solution + int(minim) + visiting = best_node + + first_solution.append(end_node) + + position = 0 + for k in dict_of_neighbours[first_solution[-2]]: + if k[0] == start_node: + break + position += 1 + + 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 + + +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. + + :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 + + + 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]] + + """ + + neighborhood_of_solution = [] + + for n in solution[1:-1]: + idx1 = solution.index(n) + for kn in solution[1:-1]: + idx2 = solution.index(kn) + if n == kn: + continue + + _tmp = copy.deepcopy(solution) + _tmp[idx1] = kn + _tmp[idx2] = n + + distance = 0 + + for k in _tmp[:-1]: + next_node = _tmp[_tmp.index(k) + 1] + for i in dict_of_neighbours[k]: + if i[0] == next_node: + distance = distance + int(i[1]) + _tmp.append(distance) + + if _tmp not in neighborhood_of_solution: + neighborhood_of_solution.append(_tmp) + + indexOfLastItemInTheList = len(neighborhood_of_solution[0]) - 1 + + neighborhood_of_solution.sort(key=lambda x: x[indexOfLastItemInTheList]) + return neighborhood_of_solution + + +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. + :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_cost: The total distance that Travelling Salesman will travel, if he follows the path in best_solution + ever. + + """ + count = 1 + solution = first_solution + tabu_list = list() + best_cost = distance_of_first_solution + best_solution_ever = solution + + while count <= iters: + neighborhood = find_neighborhood(solution, dict_of_neighbours) + index_of_best_solution = 0 + best_solution = neighborhood[index_of_best_solution] + best_cost_index = len(best_solution) - 1 + + found = False + while found is False: + i = 0 + while i < len(best_solution): + + if best_solution[i] != solution[i]: + first_exchange_node = best_solution[i] + second_exchange_node = solution[i] + 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: + tabu_list.append([first_exchange_node, second_exchange_node]) + found = True + solution = best_solution[:-1] + cost = neighborhood[index_of_best_solution][best_cost_index] + if cost < best_cost: + best_cost = cost + best_solution_ever = solution + else: + index_of_best_solution = index_of_best_solution + 1 + best_solution = neighborhood[index_of_best_solution] + + if len(tabu_list) >= size: + tabu_list.pop(0) + + count = count + 1 + + return best_solution_ever, best_cost + + +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) + + 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)) + + +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) + parser.add_argument( + "-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) + + # Pass the arguments to main method + sys.exit(main(parser.parse_args())) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/searches/test_tabu_search.py b/searches/test_tabu_search.py new file mode 100644 index 000000000000..1bc2d33612ff --- /dev/null +++ b/searches/test_tabu_search.py @@ -0,0 +1,46 @@ +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__), './tabuTestData.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.assertEquals(NEIGHBOURS_DICT, neighbours) + + def test_generate_first_solutions(self): + first_solution, distance = generate_first_solution(TEST_FILE, NEIGHBOURS_DICT) + + self.assertEquals(FIRST_SOLUTION, first_solution) + self.assertEquals(DISTANCE, distance) + + def test_find_neighbours(self): + neighbour_of_solutions = find_neighborhood(FIRST_SOLUTION, NEIGHBOURS_DICT) + + self.assertEquals(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.assertEquals(['a', 'd', 'b', 'e', 'c', 'a'], best_sol) + self.assertEquals(87, best_cost) \ No newline at end of file From 237df47a31240ebf35a5de304bcc6c73e8f921b9 Mon Sep 17 00:00:00 2001 From: Syed Haseeb Shah Date: Sun, 20 May 2018 23:00:17 +0500 Subject: [PATCH 0100/2908] Create merge_sort_fastest.py Python implementation of 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) --- sorts/merge_sort_fastest.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 sorts/merge_sort_fastest.py diff --git a/sorts/merge_sort_fastest.py b/sorts/merge_sort_fastest.py new file mode 100644 index 000000000000..cb7359ce2e40 --- /dev/null +++ b/sorts/merge_sort_fastest.py @@ -0,0 +1,20 @@ +''' +Python implementation of 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 = [] + a = LIST[0] + b = LIST[-1] + while (LIST.index(a) == LIST.index(b) and len(LIST) <=2): + a = min(LIST) + b = max(LIST) + start.append(a) + end.append(b) + LIST.remove(a) + LIST.remove(b) + end.reverse() +return start + end From 8957cf7ea8873206c355f513a79a185f9888c438 Mon Sep 17 00:00:00 2001 From: Harshil Date: Mon, 21 May 2018 10:01:44 +0200 Subject: [PATCH 0101/2908] Removed empty lines (255-313) Lines 255 to 313 were just empty, so removed those lines! --- searches/tabu_search.py | 59 ----------------------------------------- 1 file changed, 59 deletions(-) diff --git a/searches/tabu_search.py b/searches/tabu_search.py index b93b3202bce7..74c23f8b8cf1 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -252,62 +252,3 @@ def main(args=None): # Pass the arguments to main method sys.exit(main(parser.parse_args())) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 7f4b240d1a9092f8e04257f26965b7bc3a4dac07 Mon Sep 17 00:00:00 2001 From: Harshil Date: Mon, 21 May 2018 10:21:33 +0200 Subject: [PATCH 0102/2908] Update merge_sort_fastest.py I have modified the code a little to make it work as expected! --- sorts/merge_sort_fastest.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sorts/merge_sort_fastest.py b/sorts/merge_sort_fastest.py index cb7359ce2e40..2e6249778d78 100644 --- a/sorts/merge_sort_fastest.py +++ b/sorts/merge_sort_fastest.py @@ -7,14 +7,15 @@ def merge_sort(LIST): start = [] end = [] - a = LIST[0] - b = LIST[-1] - while (LIST.index(a) == LIST.index(b) and len(LIST) <=2): + while LIST: a = min(LIST) b = max(LIST) start.append(a) end.append(b) - LIST.remove(a) - LIST.remove(b) + try: + LIST.remove(a) + LIST.remove(b) + except ValueError: + continue end.reverse() -return start + end + return (start + end) From 71fd719ab77329721f7b8caf50c16a145fd2c9e9 Mon Sep 17 00:00:00 2001 From: Harshil Date: Mon, 21 May 2018 10:28:37 +0200 Subject: [PATCH 0103/2908] Update merge_sort_fastest.py --- sorts/merge_sort_fastest.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sorts/merge_sort_fastest.py b/sorts/merge_sort_fastest.py index 2e6249778d78..9fc9275aacba 100644 --- a/sorts/merge_sort_fastest.py +++ b/sorts/merge_sort_fastest.py @@ -7,15 +7,13 @@ def merge_sort(LIST): start = [] end = [] - while LIST: + while len(LIST) > 1: a = min(LIST) b = max(LIST) start.append(a) end.append(b) - try: - LIST.remove(a) - LIST.remove(b) - except ValueError: - continue + LIST.remove(a) + LIST.remove(b) + if LIST: start.append(LIST[0]) end.reverse() return (start + end) From ca7eb46756f230c2c089f3b83ecaf6c596df4ff0 Mon Sep 17 00:00:00 2001 From: Hyuntae Date: Mon, 28 May 2018 19:19:15 +0900 Subject: [PATCH 0104/2908] quicksort_3_partition --- sorts/quick_sort_3partition.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 sorts/quick_sort_3partition.py diff --git a/sorts/quick_sort_3partition.py b/sorts/quick_sort_3partition.py new file mode 100644 index 000000000000..5e4e080a84bf --- /dev/null +++ b/sorts/quick_sort_3partition.py @@ -0,0 +1,33 @@ +from __future__ import print_function + +def quick_sort_3partition(sorting, left, right): + + if right <= left: + return + a = left + b = right + pivot = sorting[left] + i = left + while i <= b: + if sorting[i] < pivot: + sorting[a], sorting[i] = sorting[i], sorting[a] + a += 1 + i += 1 + elif sorting[i] > pivot: + sorting[b], sorting[i] = sorting[i], sorting[b] + b -= 1 + else: + i += 1 + quick_sort_3partition(sorting, left, a - 1) + 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() + unsorted = [ int(item) for item in user_input.split(',') ] + quick_sort_3partition(unsorted,0,len(unsorted)-1) + print(unsorted) \ No newline at end of file From f1fe4583562a1840af9b1128f33114272d7f1ed8 Mon Sep 17 00:00:00 2001 From: PyDevthon <34104945+PyDevthon@users.noreply.github.com> Date: Mon, 28 May 2018 19:16:02 +0530 Subject: [PATCH 0105/2908] Updated_caesar_cipher.py Updated --- ciphers/caesar_cipher.py | 110 ++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 43 deletions(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index a53dc5857fca..4560db03b6f9 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,45 +1,69 @@ -from __future__ import print_function -# The Caesar Cipher Algorithm +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 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 brute_force(strng): + key = 1 + decrypted = '' + while key != 96: + for x in strng: + indx = (ord(x) - key) % 256 + if indx < 32: + indx = indx + 95 + decrypted = decrypted + chr(indx) + print(decrypted) + decrypted = '' + key += 1 + return None + def main(): - message = input("Enter message: ") - key = int(input("Key [1-26]: ")) - mode = input("Encrypt or Decrypt [e/d]: ") - - if mode.lower().startswith('e'): - mode = "encrypt" - elif mode.lower().startswith('d'): - mode = "decrypt" - - translated = encdec(message, key, mode) - if mode == "encrypt": - print(("Encryption:", translated)) - elif mode == "decrypt": - print(("Decryption:", translated)) - -def encdec(message, key, mode): - message = message.upper() - translated = "" - LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - for symbol in message: - if symbol in LETTERS: - num = LETTERS.find(symbol) - if mode == "encrypt": - num = num + key - elif mode == "decrypt": - num = num - key - - if num >= len(LETTERS): - num -= len(LETTERS) - elif num < 0: - num += len(LETTERS) - - translated += LETTERS[num] - else: - translated += symbol - return translated - -if __name__ == '__main__': - import doctest - doctest.testmod() - main() + print("**Menu**") + print("1.Encrpyt") + print("2.Decrypt") + print("3.BruteForce") + print("4.Quit") + while True: + choice = input("what would you like to do") + if choice not in ['1', '2', '3', '4']: + print ("Invalid choice") + elif choice == '1': + strng = input("Please enter the string to be ecrypted:") + while True: + key = int(input("Please enter off-set between 1-94")) + if key > 0 and key <= 94: + print (encrypt(strng, key)) + main() + elif choice == '2': + strng = input("Please enter the string to be decrypted:") + while True: + key = int(input("Please enter off-set between 1-94")) + if key > 0 and key <= 94: + print(decrypt(strng, key)) + main() + elif choice == '3': + strng = input("Please enter the string to be decrypted:") + brute_force(strng) + main() + elif choice == '4': + print ("GoodBye.") + break + +main() + + From 13617225cafdec85cb067c5c7badca28d408eb92 Mon Sep 17 00:00:00 2001 From: Harshil Date: Mon, 28 May 2018 23:25:48 +0200 Subject: [PATCH 0106/2908] small improvements! --- ciphers/caesar_cipher.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 4560db03b6f9..6cd35e73db0d 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,3 +1,4 @@ +import sys def encrypt(strng, key): encrypted = '' for x in strng: @@ -20,50 +21,48 @@ def decrypt(strng, key): def brute_force(strng): key = 1 decrypted = '' - while key != 96: + while key <= 94: for x in strng: indx = (ord(x) - key) % 256 if indx < 32: indx = indx + 95 decrypted = decrypted + chr(indx) - print(decrypted) + print("Key: {}\t| Message: {}".format(key, decrypted)) decrypted = '' key += 1 return None def main(): - print("**Menu**") + print('-' * 10 + "\n**Menu**\n" + '-' * 10) print("1.Encrpyt") print("2.Decrypt") print("3.BruteForce") print("4.Quit") while True: - choice = input("what would you like to do") + choice = input("What would you like to do?: ") if choice not in ['1', '2', '3', '4']: print ("Invalid choice") elif choice == '1': - strng = input("Please enter the string to be ecrypted:") + strng = input("Please enter the string to be ecrypted: ") while True: - key = int(input("Please enter off-set between 1-94")) - if key > 0 and key <= 94: + key = int(input("Please enter off-set between 1-94: ")) + if key in range(1, 95): print (encrypt(strng, key)) main() elif choice == '2': - strng = input("Please enter the string to be decrypted:") + strng = input("Please enter the string to be decrypted: ") while True: - key = int(input("Please enter off-set between 1-94")) + key = int(input("Please enter off-set between 1-94: ")) if key > 0 and key <= 94: print(decrypt(strng, key)) main() elif choice == '3': - strng = input("Please enter the string to be decrypted:") + strng = input("Please enter the string to be decrypted: ") brute_force(strng) main() elif choice == '4': - print ("GoodBye.") - break + print ("Goodbye.") + sys.exit() main() - - From 31f968f589db99f41bffaf6a255c4bd3211dca95 Mon Sep 17 00:00:00 2001 From: Harshil Date: Mon, 28 May 2018 23:34:21 +0200 Subject: [PATCH 0107/2908] small change! --- sorts/quick_sort_3partition.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sorts/quick_sort_3partition.py b/sorts/quick_sort_3partition.py index 5e4e080a84bf..def646cdbc50 100644 --- a/sorts/quick_sort_3partition.py +++ b/sorts/quick_sort_3partition.py @@ -1,13 +1,11 @@ from __future__ import print_function def quick_sort_3partition(sorting, left, right): - if right <= left: return - a = left + a = i = left b = right pivot = sorting[left] - i = left while i <= b: if sorting[i] < pivot: sorting[a], sorting[i] = sorting[i], sorting[a] @@ -30,4 +28,4 @@ def quick_sort_3partition(sorting, left, right): user_input = raw_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) \ No newline at end of file + print(unsorted) From 16cd42c26c37a3666c7cd011e7fa436cf0eedbe3 Mon Sep 17 00:00:00 2001 From: Harshil Date: Sun, 3 Jun 2018 12:47:36 +0200 Subject: [PATCH 0108/2908] Updated sol2.py to make it work as expected --- Project Euler/Problem 02/sol2.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Project Euler/Problem 02/sol2.py b/Project Euler/Problem 02/sol2.py index f0502a389707..9bbd0c535d63 100644 --- a/Project Euler/Problem 02/sol2.py +++ b/Project Euler/Problem 02/sol2.py @@ -1,13 +1,12 @@ def fib(n): - ls = [] - a,b = 0,1 - n += 1 - for i in range(n): - if (b % 2 == 0): - ls.append(b) - else: - pass - a,b = b, a+b - print (sum(ls)) - return None -fib(10) + a, b, s = 0, 1, 0 + while b < n: + if b % 2 == 0 and b < n: s += b + a, b = b, a+b + ls.append(s) + +T = int(input().strip()) +ls = [] +for _ in range(T): + fib(int(input().strip())) +print(*ls, sep = '\n') From 0d19edbcb3baa4b2a21362977246a848aabce1ee Mon Sep 17 00:00:00 2001 From: Anshul Malik Date: Tue, 5 Jun 2018 21:39:16 +0530 Subject: [PATCH 0109/2908] refactor-segment-tree --- data_structures/Binary Tree/SegmentTree.py | 62 ++++++++++++---------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/data_structures/Binary Tree/SegmentTree.py b/data_structures/Binary Tree/SegmentTree.py index a3b128c9d8b9..001bf999f391 100644 --- a/data_structures/Binary Tree/SegmentTree.py +++ b/data_structures/Binary Tree/SegmentTree.py @@ -3,63 +3,69 @@ 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 + 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 + 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) + def build(self, idx, l, r): + if l == r: + self.st[idx] = A[l] + else: + 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)]) - 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, 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 if l == r : self.st[idx] = val 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.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)]) return True - def query(self, idx, l, r, a, b): #query(1, 1, N, a, b) for query max of [a,b] + 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] if r < a or l > 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) + 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): - showList += [self.query(1, 1, self.N, i, i)] + 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] 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 = SegmentTree(A) + 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)) + segt.update(7,8,235) segt.showData() From cc51a667e1e932e7fa4403e62642755fad480452 Mon Sep 17 00:00:00 2001 From: psyas Date: Tue, 12 Jun 2018 14:58:06 +0900 Subject: [PATCH 0110/2908] modify README.md by psyas for just test --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b394e001d66..199fe52d48f4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ### All algorithms implemented in Python (for education) These are for demonstration purposes only. There are many implementations of sorts in the Python standard library that are much better for performance reasons. - +test ## Sort Algorithms From 602eae756ebc3a1fe1b8fc04f20073ddcb32fee7 Mon Sep 17 00:00:00 2001 From: psyas Date: Tue, 12 Jun 2018 21:18:50 +0900 Subject: [PATCH 0111/2908] Elgamal cipher key generator code - (initial) --- README.md | 2 +- ciphers/elgamal_key_generator.py | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 ciphers/elgamal_key_generator.py diff --git a/README.md b/README.md index 199fe52d48f4..9b394e001d66 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ### All algorithms implemented in Python (for education) These are for demonstration purposes only. There are many implementations of sorts in the Python standard library that are much better for performance reasons. -test + ## Sort Algorithms diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py new file mode 100644 index 000000000000..0524980d64a2 --- /dev/null +++ b/ciphers/elgamal_key_generator.py @@ -0,0 +1,62 @@ +import os +import random +import sys +import rabin_miller as rabinMiller, cryptomath_module as cryptoMath + +min_primitive_root = 3 + + +def main(): + print('Making key files...') + makeKeyFiles('elgamal', 2048) + print('Key files generation successful') + + +# 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) +# and it seems to run nicely! +def primitiveRoot(p_val): + print("Generating primitive root of p") + while True: + g = random.randrange(3,p_val) + if pow(g, 2, p_val) == 1: + continue + if pow(g, p_val, p_val) == 1: + continue + return g + + +def generateKey(keySize): + 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. + e_2 = cryptoMath.findModInverse(pow(e_1, d, p), p) + + publicKey = (keySize, e_1, e_2, p) + privateKey = (keySize, 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. \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('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__': + main() \ No newline at end of file From 0c8707d11ca70fc3ccae5ff2d3cb11cca1573199 Mon Sep 17 00:00:00 2001 From: psyas Date: Tue, 12 Jun 2018 21:21:38 +0900 Subject: [PATCH 0112/2908] Elgamal cipher key generator code - (initial) --- ciphers/elgamal_key_generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 0524980d64a2..6a8751f69524 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -59,4 +59,5 @@ def makeKeyFiles(name, keySize): if __name__ == '__main__': - main() \ No newline at end of file + main() + \ No newline at end of file From 9489e8512df9e073ac019c75f827c03fe64242dd Mon Sep 17 00:00:00 2001 From: Hossam Al-Dokkani Date: Sat, 23 Jun 2018 17:01:06 +0200 Subject: [PATCH 0113/2908] Break if the collection is sorted --- sorts/bubble_sort.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index e1bdf318911d..e28e7aa4fbe2 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -32,10 +32,12 @@ def bubble_sort(collection): """ length = len(collection) for i in range(length): + swapped = False for j in range(length-1): 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. return collection From ac30a97e99a8243378837ab735e93034b8c8ccc6 Mon Sep 17 00:00:00 2001 From: PandllCom Date: Tue, 26 Jun 2018 10:55:21 +0800 Subject: [PATCH 0114/2908] typo: strip --- sorts/bogosort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/bogosort.py b/sorts/bogosort.py index 2a3d320b00f0..33eac66bf21c 100644 --- a/sorts/bogosort.py +++ b/sorts/bogosort.py @@ -44,6 +44,6 @@ def isSorted(collection): except NameError: raw_input = input # Python 3 - user_input = raw_input('Enter numbers separated by a comma:\n').stript() + user_input = raw_input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] print(bogosort(unsorted)) From d4594da5327267aebfc484d67a61bd0752765c2c Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 2 Jul 2018 10:07:25 +0200 Subject: [PATCH 0115/2908] print() is a function in Python 3 --- ciphers/Onepad_Cipher.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ciphers/Onepad_Cipher.py b/ciphers/Onepad_Cipher.py index 4365924920f6..5536285efc17 100644 --- a/ciphers/Onepad_Cipher.py +++ b/ciphers/Onepad_Cipher.py @@ -1,11 +1,12 @@ +from __future__ import print_function + + class Onepad: def encrypt(self, text): '''Function to encrypt text using psedo-random numbers''' - plain = [] + plain = [ord(i) for i in text] key = [] cipher = [] - for i in text: - plain.append(ord(i)) for i in plain: k = random.randint(1, 300) c = (i+k)*k @@ -21,8 +22,9 @@ def decrypt(self, cipher, key): plain.append(chr(p)) plain = ''.join([i for i in plain]) return plain - + + if __name__ == '__main__': - c,k = Onepad().encrypt('Hello') - print c, k - print Onepad().decrypt(c, k) + c, k = Onepad().encrypt('Hello') + print(c, k) + print(Onepad().decrypt(c, k)) From ee3b0f2e50b78f3a8a2ca1b4937502da68d3db35 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 2 Jul 2018 10:11:28 +0200 Subject: [PATCH 0116/2908] Properly define raw_input)( in Python 3 __raw_input()__ was removed in Python 3 in favor of __input()__. This change ensure similar functioning in both Python 2 and Python 3. --- hashes/chaos_machine.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 766dbba3c5c9..4b30f631af5b 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -2,10 +2,9 @@ from __future__ import print_function try: - raw_input - input = raw_input -except: - pass + raw_input # Python 2 +except NameError: + input = raw_input # Python 3 # Chaos Machine (K, t, m) K = [0.33, 0.44, 0.55, 0.44, 0.33]; t = 3; m = 5 From bec160c6dc651442a37edafe85245a7856a2a142 Mon Sep 17 00:00:00 2001 From: cclauss Date: Tue, 3 Jul 2018 08:33:38 +0200 Subject: [PATCH 0117/2908] Use strip() to deal with leading or trailing whitespace --- hashes/chaos_machine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 4b30f631af5b..f0a305bfeade 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -2,9 +2,9 @@ from __future__ import print_function try: - raw_input # Python 2 + input = raw_input # Python 2 except NameError: - input = raw_input # Python 3 + pass # Python 3 # Chaos Machine (K, t, m) K = [0.33, 0.44, 0.55, 0.44, 0.33]; t = 3; m = 5 @@ -100,7 +100,7 @@ def reset(): inp = "" # Pulling Data (Output) -while inp != "e" and inp != "E": +while inp in ("e", "E"): print("%s" % format(pull(), '#04x')) print(buffer_space); print(params_space) - inp = input("(e)exit? ") + inp = input("(e)exit? ").strip() From 2f22ea1acd72087bbb08d6f3c17c1331a21c8561 Mon Sep 17 00:00:00 2001 From: bharath-123 Date: Mon, 23 Jul 2018 00:18:58 +0530 Subject: [PATCH 0118/2908] Added test case Added a test case check knapsack.py --- dynamic_programming/knapsack.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index a1e4f0d80daf..3823b5503417 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -12,3 +12,13 @@ def knapsack(W, wt, val, n): dp[i][w] = dp[i-1][w] return dp[n][w] +if name == "__main__": + val = [3,2,4,4] + wt = [4,3,2,3] + W = 6 + n = 4 + ''' + Should give 8 + ''' + print(knapsack(W,wt,val,n)) + From d96048a7df8ee833e6f025490c17c5050f77eace Mon Sep 17 00:00:00 2001 From: bharath-123 Date: Mon, 23 Jul 2018 00:29:40 +0530 Subject: [PATCH 0119/2908] Updated test case Made a typo writing name instead of __name__ --- dynamic_programming/knapsack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 3823b5503417..6c9789c972f2 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -12,7 +12,7 @@ def knapsack(W, wt, val, n): dp[i][w] = dp[i-1][w] return dp[n][w] -if name == "__main__": +if __name__ == "__main__": val = [3,2,4,4] wt = [4,3,2,3] W = 6 From a9f906208016817c05f9daf660f7bd4823951c0d Mon Sep 17 00:00:00 2001 From: bharath-123 Date: Mon, 23 Jul 2018 00:36:53 +0530 Subject: [PATCH 0120/2908] Added code for memory function implementation Added a function which implements knapsack using memory functions. Also I updated the test cases to include test cases for the memory function implementation. --- dynamic_programming/knapsack.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index a1e4f0d80daf..a0b006a58710 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -1,6 +1,21 @@ """ 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. """ +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) + else: + 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)] @@ -12,3 +27,16 @@ def knapsack(W, wt, val, n): dp[i][w] = dp[i-1][w] return dp[n][w] + +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)] + print(knapsack(w,wt,val,n)) + print(MF_knapsack(n,wt,val,w)) # switched the n and w + From bd7054ace9404dec5116cd858b72eb3d1cad2b16 Mon Sep 17 00:00:00 2001 From: bharath-123 Date: Mon, 23 Jul 2018 13:01:02 +0530 Subject: [PATCH 0121/2908] updated testcase Changed name to __name__. Sorry for the typo! --- dynamic_programming/knapsack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index a0b006a58710..27d1cfed799b 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -28,7 +28,7 @@ def knapsack(W, wt, val, n): return dp[n][w] -if name == '__main__': +if __name__ == '__main__': ''' Adding test case for knapsack ''' From ce3e6787f4df7fa198881b1dfa80c58a5c7afbb5 Mon Sep 17 00:00:00 2001 From: HasanGokdag Date: Thu, 13 Sep 2018 13:17:36 +0200 Subject: [PATCH 0122/2908] merge sort average&worst case scenarios changed --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b394e001d66..bc31d5389dfd 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ From [Wikipedia][merge-wiki]: In computer science, merge sort (also commonly spe __Properties__ * Worst case performance O(n log n) -* Best case performance O(n) -* Average case performance O(n) +* Best case performance O(n log n) +* Average case performance O(n log n) ###### View the algorithm in [action][merge-toptal] From 4d255f4e9fbdbb07a1a4544b6bfc4148b2e49779 Mon Sep 17 00:00:00 2001 From: piyush-kgp Date: Sun, 23 Sep 2018 05:25:07 +0530 Subject: [PATCH 0123/2908] SHA1 skeleton code --- hashes/sha1.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 hashes/sha1.py diff --git a/hashes/sha1.py b/hashes/sha1.py new file mode 100644 index 000000000000..009cba1a644c --- /dev/null +++ b/hashes/sha1.py @@ -0,0 +1,90 @@ +""" +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 Welcome to Cryptography" + pyhton sha1.py --file "hello_world.txt" + Without any arguments prints the hash of the string "Hello World" +Also contains a Test class to verify that the generated Hash is same as that +returned by the hashlib library +Reference: https://deadhacker.com/2006/02/21/sha-1-illustrated/ + +The Algorithm as described in the reference: +First we start with a message. The message is padded and the length of the message +is added to the end. It is then split into blocks of 512 bits. The blocks are then +processed one at a time. Each block must be expanded and compressed. +The value after each compression is added to a 160bit buffer called the current hash +state. After the last block is processed the current hash state is returned as +the final hash. +""" + +import argparse +import hashlib #hashlib is only used inside the Test class + +class SHA1: + """ + Class to contain the entire pipeline for SHA1 Hashing Algorithm + """ + def __init__(self, data): + self.data = data + self.current_hash = '' + + def padding(self): + return + + def split_block(self): + return + + def expand_block(self): + return + + def compress_block(self): + return + + def final_hash(self): + assert True #everything done till now + # return self.current_hash + return hashlib.sha1(bytes(self.data, 'utf-8')).hexdigest() + +class SHA1Test: + """ + Test class for the SHA1 class + """ + def __init__(self, data): + self.data = data + + def calculated_hash(self): + return SHA1(self.data).final_hash() + + def hashlib_hash(self): + return hashlib.sha1(self.data.byte_encode()).hexdigest() + + def byte_encode(self): + return bytes(self.data, 'utf-8') + + def match_hashes(self): + # self.assertEqual(self.calculated_hash(), self.hashlib_hash()) + return self.calculated_hash() == self.hashlib_hash() + +def run_test_case(hash_input = 'Hello World'): + """ + Pulled this out of main because we probably dont want to run the Unit Test + each time we want to calculate hash. + """ + print(SHA1Test(hash_input).match_hashes()) + + +def main(): + parser = argparse.ArgumentParser(description='Process some strings or files') + parser.add_argument('--string', dest='input_string', default='Hello World', + 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 + if args.input_file: + hash_input = open(args.input_file, 'r').read() + else: + hash_input = input_string + print(SHA1(hash_input).final_hash()) + +if __name__ == '__main__': + main() From 7f3895cdafa91330b618018b6cb09564b87f5b57 Mon Sep 17 00:00:00 2001 From: piyush-kgp Date: Sun, 23 Sep 2018 19:44:37 +0530 Subject: [PATCH 0124/2908] moved constants inside the class --- hashes/sha1.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/hashes/sha1.py b/hashes/sha1.py index 009cba1a644c..7f87fe2435e2 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -6,7 +6,6 @@ Without any arguments prints the hash of the string "Hello World" Also contains a Test class to verify that the generated Hash is same as that returned by the hashlib library -Reference: https://deadhacker.com/2006/02/21/sha-1-illustrated/ The Algorithm as described in the reference: First we start with a message. The message is padded and the length of the message @@ -15,15 +14,25 @@ The value after each compression is added to a 160bit buffer called the current hash state. After the last block is processed the current hash state is returned as the final hash. +Reference: https://deadhacker.com/2006/02/21/sha-1-illustrated/ """ import argparse import hashlib #hashlib is only used inside the Test class -class SHA1: + + +class SHA1Hash: """ Class to contain the entire pipeline for SHA1 Hashing Algorithm """ + + H0 - 01100111010001010010001100000001 + H1 - 11101111110011011010101110001001 + H2 - 10011000101110101101110011111110 + H3 - 00010000001100100101010001110110 + H4 - 11000011110100101110000111110000 + def __init__(self, data): self.data = data self.current_hash = '' @@ -41,11 +50,9 @@ def compress_block(self): return def final_hash(self): - assert True #everything done till now - # return self.current_hash - return hashlib.sha1(bytes(self.data, 'utf-8')).hexdigest() + return 'This is in my To Do list' -class SHA1Test: +class SHA1HashTest: """ Test class for the SHA1 class """ @@ -53,7 +60,7 @@ def __init__(self, data): self.data = data def calculated_hash(self): - return SHA1(self.data).final_hash() + return SHA1Hash(self.data).final_hash() def hashlib_hash(self): return hashlib.sha1(self.data.byte_encode()).hexdigest() @@ -67,10 +74,10 @@ def match_hashes(self): def run_test_case(hash_input = 'Hello World'): """ - Pulled this out of main because we probably dont want to run the Unit Test + Pulled this out of main because we probably dont want to run the Test each time we want to calculate hash. """ - print(SHA1Test(hash_input).match_hashes()) + print(SHA1HashTest(hash_input).match_hashes()) def main(): @@ -84,7 +91,7 @@ def main(): hash_input = open(args.input_file, 'r').read() else: hash_input = input_string - print(SHA1(hash_input).final_hash()) + print(SHA1Hash(hash_input).final_hash()) if __name__ == '__main__': main() From 965fdee22d93e06a4449bce8f6ec65e35f90d9ce Mon Sep 17 00:00:00 2001 From: piyush-kgp Date: Tue, 25 Sep 2018 19:21:29 +0530 Subject: [PATCH 0125/2908] SHA1 class completed, Test passed --- hashes/sha1.py | 148 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 49 deletions(-) diff --git a/hashes/sha1.py b/hashes/sha1.py index 7f87fe2435e2..077433019f45 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -1,16 +1,22 @@ """ 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 Welcome to Cryptography" +Usage: python sha1.py --string "Hello World!!" pyhton sha1.py --file "hello_world.txt" - Without any arguments prints the hash of the string "Hello World" + 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 +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 +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 +and cryptocurrency etc. The Algorithm as described in the reference: First we start with a message. The message is padded and the length of the message -is added to the end. It is then split into blocks of 512 bits. The blocks are then -processed one at a time. Each block must be expanded and compressed. +is added to the end. It is then split into blocks of 512 bits or 64 bytes. The blocks +are then processed one at a time. Each block must be expanded and compressed. The value after each compression is added to a 160bit buffer called the current hash state. After the last block is processed the current hash state is returned as the final hash. @@ -18,79 +24,123 @@ """ import argparse +import struct import hashlib #hashlib is only used inside the Test class - +import unittest class SHA1Hash: """ Class to contain the entire pipeline for SHA1 Hashing Algorithm """ - - H0 - 01100111010001010010001100000001 - H1 - 11101111110011011010101110001001 - H2 - 10011000101110101101110011111110 - H3 - 00010000001100100101010001110110 - H4 - 11000011110100101110000111110000 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) + respectively. We will start with this as a message digest. 0x is how you write + Hexadecimal numbers in Python + """ self.data = data - self.current_hash = '' - - def padding(self): - return - - def split_block(self): - return + self.h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0] - def expand_block(self): - return + @staticmethod + def rotate(n, b): + """ + Static method to be used inside other methods. Left rotates n by b. + """ + return ((n << b) | (n >> (32 - b))) & 0xffffffff - def compress_block(self): - return + 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)) + 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)] + + # @staticmethod + def expand_block(self, block): + """ + Takes block of 64 and returns list of length 80. + It is really a static method but + we need the rotate method inside, so we will have to use self + """ + 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) + return w def final_hash(self): - return 'This is in my To Do list' - -class SHA1HashTest: + """ + Calls all the other methods to process the input. Returns SHA1 hash + """ + self.padded_data = self.padding() + self.blocks = self.split_blocks() + for block in self.blocks: + expanded_block = self.expand_block(block) + a, b, c, d, e = self.h + for i in range(0, 80): + if 0 <= i < 20: + f = (b & c) | ((~b) & d) + k = 0x5A827999 + elif 20 <= i < 40: + f = b ^ c ^ d + k = 0x6ED9EBA1 + elif 40 <= i < 60: + f = (b & c) | (b & d) | (c & d) + k = 0x8F1BBCDC + 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) + + +class SHA1HashTest(unittest.TestCase): """ - Test class for the SHA1 class + Test class for the SHA1Hash class. Inherits the TestCase class from unittest """ - def __init__(self, data): - self.data = data + def testMatchHashes(self): + msg = bytes("Hello World", 'utf-8') + self.assertEqual(SHA1Hash(msg).final_hash(), hashlib.sha1(msg).hexdigest()) - def calculated_hash(self): - return SHA1Hash(self.data).final_hash() - - def hashlib_hash(self): - return hashlib.sha1(self.data.byte_encode()).hexdigest() - - def byte_encode(self): - return bytes(self.data, 'utf-8') - - def match_hashes(self): - # self.assertEqual(self.calculated_hash(), self.hashlib_hash()) - return self.calculated_hash() == self.hashlib_hash() - -def run_test_case(hash_input = 'Hello World'): +def run_test(): """ - Pulled this out of main because we probably dont want to run the Test - each time we want to calculate hash. + Run the unit test. Pulled this out of main because we probably dont want to run + the test each time. """ - print(SHA1HashTest(hash_input).match_hashes()) - + unittest.main() def main(): + """ + Provides option string or file to take input and prints the calculated SHA1 hash + """ parser = argparse.ArgumentParser(description='Process some strings or files') - parser.add_argument('--string', dest='input_string', default='Hello World', + 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 if args.input_file: - hash_input = open(args.input_file, 'r').read() + hash_input = open(args.input_file, 'rb').read() else: - hash_input = input_string + hash_input = bytes(input_string, 'utf-8') print(SHA1Hash(hash_input).final_hash()) if __name__ == '__main__': From b62a258f8ab870ff7077a5ddde3fc9e58801c333 Mon Sep 17 00:00:00 2001 From: Anton Helm Date: Tue, 25 Sep 2018 15:12:16 +0100 Subject: [PATCH 0126/2908] renamed `NeutonMethod.py` to `NewtonMethod.py` --- ArithmeticAnalysis/{NeutonMethod.py => NewtonMethod.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ArithmeticAnalysis/{NeutonMethod.py => NewtonMethod.py} (100%) diff --git a/ArithmeticAnalysis/NeutonMethod.py b/ArithmeticAnalysis/NewtonMethod.py similarity index 100% rename from ArithmeticAnalysis/NeutonMethod.py rename to ArithmeticAnalysis/NewtonMethod.py From 59027e4bd54c9c46d531077b977fe1de434797bc Mon Sep 17 00:00:00 2001 From: piyush-kgp Date: Tue, 25 Sep 2018 22:56:24 +0530 Subject: [PATCH 0127/2908] Improved code documentation, removed uncalled function --- hashes/sha1.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/hashes/sha1.py b/hashes/sha1.py index 077433019f45..e34f87a76386 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -33,7 +33,6 @@ class SHA1Hash: """ Class to contain the entire pipeline for SHA1 Hashing Algorithm """ - def __init__(self, data): """ Inititates the variables data and h. h is a list of 5 8-digit Hexadecimal @@ -68,9 +67,8 @@ def split_blocks(self): # @staticmethod def expand_block(self, block): """ - Takes block of 64 and returns list of length 80. - It is really a static method but - we need the rotate method inside, so we will have to use self + 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 """ w = list(struct.unpack('>16L', block)) + [0] * 64 for i in range(16, 80): @@ -79,7 +77,12 @@ def expand_block(self, block): def final_hash(self): """ - Calls all the other methods to process the input. Returns SHA1 hash + 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. """ self.padded_data = self.padding() self.blocks = self.split_blocks() @@ -106,7 +109,6 @@ def final_hash(self): self.h[2] + c & 0xffffffff,\ self.h[3] + d & 0xffffffff,\ self.h[4] + e & 0xffffffff - return '%08x%08x%08x%08x%08x' %tuple(self.h) @@ -115,20 +117,17 @@ class SHA1HashTest(unittest.TestCase): Test class for the SHA1Hash class. Inherits the TestCase class from unittest """ def testMatchHashes(self): - msg = bytes("Hello World", 'utf-8') + msg = bytes('Test String', 'utf-8') self.assertEqual(SHA1Hash(msg).final_hash(), hashlib.sha1(msg).hexdigest()) -def run_test(): - """ - Run the unit test. Pulled this out of main because we probably dont want to run - the test each time. - """ - unittest.main() def main(): """ - Provides option string or file to take input and prints the calculated SHA1 hash + 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 + 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', @@ -143,5 +142,6 @@ def main(): hash_input = bytes(input_string, 'utf-8') print(SHA1Hash(hash_input).final_hash()) + if __name__ == '__main__': main() From 3a77380ed33b497648dd9beca5e9aa72615bb6e6 Mon Sep 17 00:00:00 2001 From: cclauss Date: Fri, 28 Sep 2018 06:17:28 +0200 Subject: [PATCH 0128/2908] Undefined name: import random in Onepad_Cipher.py (#339) --- ciphers/Onepad_Cipher.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ciphers/Onepad_Cipher.py b/ciphers/Onepad_Cipher.py index 5536285efc17..7e1be5fdc077 100644 --- a/ciphers/Onepad_Cipher.py +++ b/ciphers/Onepad_Cipher.py @@ -1,5 +1,7 @@ from __future__ import print_function +import random + class Onepad: def encrypt(self, text): From 3dab8e03a465397a7b671128c155c9c03f8e0154 Mon Sep 17 00:00:00 2001 From: Robert Bergers Date: Sat, 29 Sep 2018 12:28:55 -0400 Subject: [PATCH 0129/2908] Just emboldened text and standardized some formatting throughout the file. modified: README.md modified: README.md modified: README.md --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bc31d5389dfd..d840f89a492e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ These are for demonstration purposes only. There are many implementations of sor ### Bubble ![alt text][bubble-image] -From [Wikipedia][bubble-wiki]: 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. +From [Wikipedia][bubble-wiki]: **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(n^2) @@ -24,7 +24,7 @@ __Properties__ ### Insertion ![alt text][insertion-image] -From [Wikipedia][insertion-wiki]: 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. +From [Wikipedia][insertion-wiki]: **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(n^2) @@ -37,7 +37,7 @@ __Properties__ ### Merge ![alt text][merge-image] -From [Wikipedia][merge-wiki]: In computer science, 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. +From [Wikipedia][merge-wiki]: **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) @@ -50,7 +50,7 @@ __Properties__ ### Quick ![alt text][quick-image] -From [Wikipedia][quick-wiki]: 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. +From [Wikipedia][quick-wiki]: **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(n^2) @@ -62,7 +62,7 @@ __Properties__ ### Selection ![alt text][selection-image] -From [Wikipedia][selection-wiki]: The algorithm 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. +From [Wikipedia][selection-wiki]: **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(n^2) @@ -74,7 +74,7 @@ __Properties__ ### Shell ![alt text][shell-image] -From [Wikipedia][shell-wiki]: 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. +From [Wikipedia][shell-wiki]: **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(nlog2 2n) @@ -85,7 +85,7 @@ __Properties__ ### Time-Complexity Graphs -Comparing the complexity of sorting algorithms (Bubble Sort, Insertion Sort, Selection Sort) +Comparing the complexity of sorting algorithms (*Bubble Sort*, *Insertion Sort*, *Selection Sort*) [Complexity Graphs](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png) @@ -96,8 +96,7 @@ Comparing the complexity of sorting algorithms (Bubble Sort, Insertion Sort, Sel ### Linear ![alt text][linear-image] -From [Wikipedia][linear-wiki]: 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. +From [Wikipedia][linear-wiki]: **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) @@ -108,7 +107,7 @@ __Properties__ ### Binary ![alt text][binary-image] -From [Wikipedia][binary-wiki]: 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. +From [Wikipedia][binary-wiki]: **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) @@ -122,7 +121,7 @@ __Properties__ ### Caesar ![alt text][caesar]
-In cryptography, a **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.
+**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. @@ -136,7 +135,7 @@ Many people have tried to implement encryption schemes that are essentially Vige ###### Source: [Wikipedia](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher) ### Transposition -In cryptography, a **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).
+The **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) From 5fb6b31ea928162c5185d66381ae99c7454d33c0 Mon Sep 17 00:00:00 2001 From: Kelvin Salton do Prado Date: Mon, 1 Oct 2018 23:43:25 -0300 Subject: [PATCH 0130/2908] Add comb sort algorithm --- sorts/comb_sort.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 sorts/comb_sort.py diff --git a/sorts/comb_sort.py b/sorts/comb_sort.py new file mode 100644 index 000000000000..be7857c04775 --- /dev/null +++ b/sorts/comb_sort.py @@ -0,0 +1,58 @@ +""" +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. + +This is pure python implementation of counting sort algorithm +For doctests run following command: +python -m doctest -v comb_sort.py +or +python3 -m doctest -v comb_sort.py + +For manual testing run: +python comb_sort.py +""" + +def comb_sort(data): + """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 + Examples: + >>> comb_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + >>> comb_sort([]) + [] + >>> comb_sort([-2, -5, -45]) + [-45, -5, -2] + """ + shrink_factor = 1.3 + gap = len(data) + swapped = True + i = 0 + + while gap > 1 or swapped: + # Update the gap value for a next comb + gap = int(float(gap) / shrink_factor) + + swapped = False + i = 0 + + while gap + i < len(data): + if data[i] > data[i+gap]: + # Swap values + data[i], data[i+gap] = data[i+gap], data[i] + swapped = True + i += 1 + + return 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() + unsorted = [int(item) for item in user_input.split(',')] + print(comb_sort(unsorted)) From f4a80fbfcb1c3da56955b70223ef1e268edf1cbf Mon Sep 17 00:00:00 2001 From: Kelvin Salton do Prado Date: Mon, 1 Oct 2018 23:46:47 -0300 Subject: [PATCH 0131/2908] comb_sort: fix typo and indentation --- sorts/comb_sort.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sorts/comb_sort.py b/sorts/comb_sort.py index be7857c04775..22b6f66f04cc 100644 --- a/sorts/comb_sort.py +++ b/sorts/comb_sort.py @@ -2,7 +2,7 @@ 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. -This is pure python implementation of counting sort algorithm +This is pure python implementation of comb sort algorithm For doctests run following command: python -m doctest -v comb_sort.py or @@ -31,7 +31,7 @@ def comb_sort(data): i = 0 while gap > 1 or swapped: - # Update the gap value for a next comb + # Update the gap value for a next comb gap = int(float(gap) / shrink_factor) swapped = False @@ -39,7 +39,7 @@ def comb_sort(data): while gap + i < len(data): if data[i] > data[i+gap]: - # Swap values + # Swap values data[i], data[i+gap] = data[i+gap], data[i] swapped = True i += 1 From d2e77b05ef5983c3f0a7791550aac01b645a07ee Mon Sep 17 00:00:00 2001 From: Kelvin Salton do Prado Date: Tue, 2 Oct 2018 00:08:42 -0300 Subject: [PATCH 0132/2908] searches: add sentinel linear search algorithm --- searches/sentinel_linear_search.py | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 searches/sentinel_linear_search.py diff --git a/searches/sentinel_linear_search.py b/searches/sentinel_linear_search.py new file mode 100644 index 000000000000..336cc5ab3b74 --- /dev/null +++ b/searches/sentinel_linear_search.py @@ -0,0 +1,62 @@ +""" +This is pure python implementation of sentinel linear search algorithm + +For doctests run following command: +python -m doctest -v sentinel_linear_search.py +or +python3 -m doctest -v sentinel_linear_search.py + +For manual testing run: +python sentinel_linear_search.py +""" + +def sentinel_linear_search(sequence, target): + """Pure implementation of sentinel linear search algorithm in Python + + :param sequence: some sequence with comparable items + :param target: item value to search + :return: index of found item or None if item is not found + + Examples: + >>> sentinel_linear_search([0, 5, 7, 10, 15], 0) + 0 + + >>> sentinel_linear_search([0, 5, 7, 10, 15], 15) + 4 + + >>> sentinel_linear_search([0, 5, 7, 10, 15], 5) + 1 + + >>> sentinel_linear_search([0, 5, 7, 10, 15], 6) + + """ + sequence.append(target) + + index = 0 + while sequence[index] != target: + index += 1 + + sequence.pop() + + if index == len(sequence): + return None + + return index + + +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() + 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 = int(target_input) + result = sentinel_linear_search(sequence, target) + if result is not None: + print('{} found at positions: {}'.format(target, result)) + else: + print('Not found') \ No newline at end of file From ca99871b2979e043c9aaaabc1f8c0a2e162f2955 Mon Sep 17 00:00:00 2001 From: ayush1208 <43182109+ayush1208@users.noreply.github.com> Date: Tue, 2 Oct 2018 12:25:16 +0530 Subject: [PATCH 0133/2908] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index bc31d5389dfd..a2fc3a545a1d 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,10 @@ Comparing the complexity of sorting algorithms (Bubble Sort, Insertion Sort, Sel [Complexity Graphs](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png) +Choosing of a sort technique-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 From c0033f92ade8153e85162ad9a7741eee2d3550bb Mon Sep 17 00:00:00 2001 From: rafa leyva ruiz Date: Tue, 2 Oct 2018 10:46:56 +0200 Subject: [PATCH 0134/2908] Use local insertion sort (solves #334) (#370) --- sorts/bucket_sort.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 1c4b4577af61..bd4281e463eb 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -3,7 +3,7 @@ # 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 +# 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 @@ -14,7 +14,7 @@ # Best Case O(n); Average Case O(n); Worst Case O(n) from __future__ import print_function -from P26_InsertionSort import insertionSort +from insertion_sort import insertion_sort import math DEFAULT_BUCKET_SIZE = 5 @@ -46,7 +46,7 @@ def bucketSort(myList, bucketSize=DEFAULT_BUCKET_SIZE): # Sort buckets and place back into input array sortedArray = [] for i in range(0, len(buckets)): - insertionSort(buckets[i]) + insertion_sort(buckets[i]) for j in range(0, len(buckets[i])): sortedArray.append(buckets[i][j]) From a31d20acf51be53de690d2b4a22c45f861522eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Tue, 2 Oct 2018 05:57:19 -0300 Subject: [PATCH 0135/2908] Add GCD implementation (#371) --- Maths/gcd.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Maths/gcd.py diff --git a/Maths/gcd.py b/Maths/gcd.py new file mode 100644 index 000000000000..0f0ba7dce11b --- /dev/null +++ b/Maths/gcd.py @@ -0,0 +1,12 @@ +def gcd(a, b): + if a == 0 : + return b + + return gcd(b%a, a) + +def main(): + print(gcd(3, 6)) + + +if __name__ == '__main__': + main() From ff38b4a227ce7eedfe66651b71f11d70ef664623 Mon Sep 17 00:00:00 2001 From: Harshil Date: Tue, 2 Oct 2018 11:06:02 +0200 Subject: [PATCH 0136/2908] Minor edit The image of Complexity Graphs was not visible due to a missing exclamation mark in the beginning. So, Just add it with this little commit! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2fc3a545a1d..422bc9f680af 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ __Properties__ Comparing the complexity of sorting algorithms (Bubble Sort, Insertion Sort, Selection Sort) -[Complexity Graphs](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png) +![Complexity Graphs](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png) Choosing of a sort technique-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. From ccb25641ba5ebebbe27a4155f1aff381f5f035d0 Mon Sep 17 00:00:00 2001 From: S-Sanyal Date: Tue, 2 Oct 2018 14:57:37 +0530 Subject: [PATCH 0137/2908] Added Stock-Span-Problem --- data_structures/Stacks/Stock-Span-Problem.py | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 data_structures/Stacks/Stock-Span-Problem.py diff --git a/data_structures/Stacks/Stock-Span-Problem.py b/data_structures/Stacks/Stock-Span-Problem.py new file mode 100644 index 000000000000..658ac3cbfb5e --- /dev/null +++ b/data_structures/Stacks/Stock-Span-Problem.py @@ -0,0 +1,51 @@ +''' +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) + # 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], + + +# 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)) \ No newline at end of file From 0e76ee907628317b42f8c66bda49591796b24b7b Mon Sep 17 00:00:00 2001 From: camilne Date: Tue, 2 Oct 2018 03:36:24 -0600 Subject: [PATCH 0138/2908] Remove duplicate counting sort (#376) --- sorts/counting_sort.py | 6 ++++++ sorts/countingsort.py | 42 ------------------------------------------ 2 files changed, 6 insertions(+), 42 deletions(-) delete mode 100644 sorts/countingsort.py diff --git a/sorts/counting_sort.py b/sorts/counting_sort.py index 4ca682b13cca..ad98f1a0da4c 100644 --- a/sorts/counting_sort.py +++ b/sorts/counting_sort.py @@ -57,8 +57,14 @@ def counting_sort(collection): return ordered +def counting_sort_string(string): + return ''.join([chr(i) for i in counting_sort([ord(c) for c in string])]) + if __name__ == '__main__': + # Test string sort + assert "eghhiiinrsssttt" == counting_sort_string("thisisthestring") + try: raw_input # Python 2 except NameError: diff --git a/sorts/countingsort.py b/sorts/countingsort.py deleted file mode 100644 index 18ee8b851fd7..000000000000 --- a/sorts/countingsort.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import print_function -# Python program for counting sort - -# This is the main function that sort the given string arr[] in -# in the alphabetical order -def countSort(arr): - - # The output character array that will have sorted arr - output = [0 for i in range(256)] - - # Create a count array to store count of inidividul - # characters and initialize count array as 0 - count = [0 for i in range(256)] - - # For storing the resulting answer since the - # string is immutable - ans = ["" for _ in arr] - - # Store count of each character - for i in arr: - count[ord(i)] += 1 - - # Change count[i] so that count[i] now contains actual - # position of this character in output array - for i in range(256): - count[i] += count[i-1] - - # Build the output character array - for i in range(len(arr)): - output[count[ord(arr[i])]-1] = arr[i] - count[ord(arr[i])] -= 1 - - # Copy the output array to arr, so that arr now - # contains sorted characters - for i in range(len(arr)): - ans[i] = output[i] - return ans - -# Driver program to test above function -arr = "thisisthestring" -ans = countSort(arr) -print ("Sorted string array is %s" %("".join(ans))) From 8fb4df5367b5c03d2851532063f6fa781fe2f980 Mon Sep 17 00:00:00 2001 From: Purvesh Makode Date: Tue, 2 Oct 2018 18:14:53 +0530 Subject: [PATCH 0139/2908] Add Fibonacci Series Using Recursion --- Maths/fibonacciSeries.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Maths/fibonacciSeries.py diff --git a/Maths/fibonacciSeries.py b/Maths/fibonacciSeries.py new file mode 100644 index 000000000000..cf025b07d576 --- /dev/null +++ b/Maths/fibonacciSeries.py @@ -0,0 +1,16 @@ +# Fibonacci Sequence Using Recursion + +def recur_fibo(n): + if n <= 1: + return n + else: + return(recur_fibo(n-1) + recur_fibo(n-2)) + +limit = int(input("How many terms to include in fionacci series:")) + +if limit <= 0: + print("Plese enter a positive integer") +else: + print("Fibonacci series:") + for i in range(limit): + print(recur_fibo(i)) From a7948f1863f61b7feaace74b34d6d0242d86f627 Mon Sep 17 00:00:00 2001 From: Farhan Date: Tue, 2 Oct 2018 21:02:25 +0530 Subject: [PATCH 0140/2908] Extra Algorithms added --- Graphs/ArticulationPoints.py | 44 +++++++++++ Graphs/CheckBipartiteGraph_BFS.py | 43 +++++++++++ Graphs/FindingBridges.py | 31 ++++++++ Graphs/KahnsAlgorithm_long.py | 30 ++++++++ Graphs/KahnsAlgorithm_topo.py | 32 ++++++++ Graphs/MinimumSpanningTree_Prims.py | 111 ++++++++++++++++++++++++++++ Maths/BasicMaths.py | 70 ++++++++++++++++++ Maths/SegmentedSieve.py | 46 ++++++++++++ Maths/SieveOfEratosthenes.py | 24 ++++++ 9 files changed, 431 insertions(+) create mode 100644 Graphs/ArticulationPoints.py create mode 100644 Graphs/CheckBipartiteGraph_BFS.py create mode 100644 Graphs/FindingBridges.py create mode 100644 Graphs/KahnsAlgorithm_long.py create mode 100644 Graphs/KahnsAlgorithm_topo.py create mode 100644 Graphs/MinimumSpanningTree_Prims.py create mode 100644 Maths/BasicMaths.py create mode 100644 Maths/SegmentedSieve.py create mode 100644 Maths/SieveOfEratosthenes.py diff --git a/Graphs/ArticulationPoints.py b/Graphs/ArticulationPoints.py new file mode 100644 index 000000000000..9965f5cb23cd --- /dev/null +++ b/Graphs/ArticulationPoints.py @@ -0,0 +1,44 @@ +# Finding Articulation Points in Undirected Graph +def computeAP(l): + n = len(l) + outEdgeCount = 0 + low = [0] * n + visited = [False] * n + isArt = [False] * n + + def dfs(root, at, parent, outEdgeCount): + if parent == root: + outEdgeCount += 1 + visited[at] = True + low[at] = at + + for to in l[at]: + if to == parent: + pass + elif not visited[to]: + outEdgeCount = dfs(root, to, at, outEdgeCount) + low[at] = min(low[at], low[to]) + + # AP found via bridge + if at < low[to]: + isArt[at] = True + # AP found via cycle + if at == low[to]: + isArt[at] = True + else: + low[at] = min(low[at], to) + return outEdgeCount + + for i in range(n): + if not visited[i]: + outEdgeCount = 0 + outEdgeCount = dfs(i, i, -1, outEdgeCount) + isArt[i] = (outEdgeCount > 1) + + for x in range(len(isArt)): + if isArt[x] == True: + print(x, end=" ") + +# 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]} +computeAP(l) diff --git a/Graphs/CheckBipartiteGraph_BFS.py b/Graphs/CheckBipartiteGraph_BFS.py new file mode 100644 index 000000000000..1b9c32c6ccc4 --- /dev/null +++ b/Graphs/CheckBipartiteGraph_BFS.py @@ -0,0 +1,43 @@ +# 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): + queue = [] + visited = [False] * len(l) + color = [-1] * len(l) + + def bfs(): + while(queue): + u = queue.pop(0) + visited[u] = True + + for neighbour in l[u]: + + if neighbour == u: + return False + + if color[neighbour] == -1: + color[neighbour] = 1 - color[u] + queue.append(neighbour) + + elif color[neighbour] == color[u]: + return False + + return True + + for i in range(len(l)): + if not visited[i]: + queue.append(i) + color[i] = 0 + if bfs() == 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)) diff --git a/Graphs/FindingBridges.py b/Graphs/FindingBridges.py new file mode 100644 index 000000000000..56533dd48bde --- /dev/null +++ b/Graphs/FindingBridges.py @@ -0,0 +1,31 @@ +# Finding Bridges in Undirected Graph +def computeBridges(l): + id = 0 + n = len(l) # No of vertices in graph + low = [0] * n + visited = [False] * n + + def dfs(at, parent, bridges, id): + visited[at] = True + low[at] = id + id += 1 + for to in l[at]: + if to == parent: + pass + elif not visited[to]: + dfs(to, at, bridges, id) + low[at] = min(low[at], low[to]) + if at < low[to]: + bridges.append([at, to]) + else: + # This edge is a back edge and cannot be a bridge + low[at] = min(low[at], to) + + bridges = [] + for i in range(n): + 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]} +computeBridges(l) diff --git a/Graphs/KahnsAlgorithm_long.py b/Graphs/KahnsAlgorithm_long.py new file mode 100644 index 000000000000..453b5706f6da --- /dev/null +++ b/Graphs/KahnsAlgorithm_long.py @@ -0,0 +1,30 @@ +# Finding longest distance in Directed Acyclic Graph using KahnsAlgorithm +def longestDistance(l): + indegree = [0] * len(l) + queue = [] + longDist = [1] * len(l) + + for key, values in l.items(): + for i in values: + indegree[i] += 1 + + for i in range(len(indegree)): + if indegree[i] == 0: + queue.append(i) + + 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 + + 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:[]} +longestDistance(l) diff --git a/Graphs/KahnsAlgorithm_topo.py b/Graphs/KahnsAlgorithm_topo.py new file mode 100644 index 000000000000..8c182c4e902c --- /dev/null +++ b/Graphs/KahnsAlgorithm_topo.py @@ -0,0 +1,32 @@ +# Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph using BFS +def topologicalSort(l): + indegree = [0] * len(l) + queue = [] + topo = [] + cnt = 0 + + for key, values in l.items(): + for i in values: + indegree[i] += 1 + + for i in range(len(indegree)): + if indegree[i] == 0: + queue.append(i) + + while(queue): + vertex = queue.pop(0) + cnt += 1 + topo.append(vertex) + for x in l[vertex]: + indegree[x] -= 1 + if indegree[x] == 0: + queue.append(x) + + if cnt != len(l): + 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) diff --git a/Graphs/MinimumSpanningTree_Prims.py b/Graphs/MinimumSpanningTree_Prims.py new file mode 100644 index 000000000000..7b1ad0e743f7 --- /dev/null +++ b/Graphs/MinimumSpanningTree_Prims.py @@ -0,0 +1,111 @@ +import sys +from collections import defaultdict + +def PrimsAlgorithm(l): + + nodePosition = [] + def getPosition(vertex): + return nodePosition[vertex] + + def setPosition(vertex, pos): + nodePosition[vertex] = pos + + def topToBottom(heap, start, size, positions): + if start > size // 2 - 1: + return + else: + if 2 * start + 2 >= size: + m = 2 * start + 1 + else: + if heap[2 * start + 1] < heap[2 * start + 2]: + m = 2 * start + 1 + else: + m = 2 * start + 2 + if heap[m] < heap[start]: + temp, temp1 = heap[m], positions[m] + 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) + + topToBottom(heap, m, size, positions) + + # Update function if value of any node in min-heap decreases + def bottomToTop(val, index, heap, position): + temp = position[index] + + while(index != 0): + if index % 2 == 0: + parent = int( (index-2) / 2 ) + else: + parent = int( (index-1) / 2 ) + + if val < heap[parent]: + heap[index] = heap[parent] + position[index] = position[parent] + setPosition(position[parent], index) + else: + heap[index] = val + position[index] = temp + setPosition(temp, index) + break + index = parent + else: + heap[0] = val + position[0] = temp + setPosition(temp, 0) + + def heapify(heap, positions): + start = len(heap) // 2 - 1 + for i in range(start, -1, -1): + topToBottom(heap, i, len(heap), positions) + + def deleteMinimum(heap, positions): + temp = positions[0] + heap[0] = sys.maxsize + topToBottom(heap, 0, len(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 + # 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 = [] + + for x in range(len(l)): + p = sys.maxsize + Distance_TV.append(p) + Positions.append(x) + nodePosition.append(x) + + TreeEdges = [] + visited[0] = 1 + Distance_TV[0] = sys.maxsize + for x in l[0]: + Nbr_TV[ x[0] ] = 0 + Distance_TV[ x[0] ] = x[1] + heapify(Distance_TV, Positions) + + for i in range(1, len(l)): + vertex = deleteMinimum(Distance_TV, Positions) + if visited[vertex] == 0: + 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) + 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)) diff --git a/Maths/BasicMaths.py b/Maths/BasicMaths.py new file mode 100644 index 000000000000..f05cde64d5a6 --- /dev/null +++ b/Maths/BasicMaths.py @@ -0,0 +1,70 @@ +import math + +def primeFactors(n): + 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 numberOfDivisors(n): + 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): + temp = 1 + while n % i == 0: + temp += 1 + n = int(n / i) + div = div * (temp) + + return div + +def sumOfDivisors(n): + 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: + temp += 1 + n = int(n / i) + if temp > 1: + s *= (i**temp - 1) / (i - 1) + + return s + +def eulerPhi(n): + l = primeFactors(n) + l = set(l) + s = n + for x in l: + s *= (x - 1)/x + return s + +print(primeFactors(100)) +print(numberOfDivisors(100)) +print(sumOfDivisors(100)) +print(eulerPhi(100)) + + \ No newline at end of file diff --git a/Maths/SegmentedSieve.py b/Maths/SegmentedSieve.py new file mode 100644 index 000000000000..52ca6fbe601d --- /dev/null +++ b/Maths/SegmentedSieve.py @@ -0,0 +1,46 @@ +import math + +def sieve(n): + in_prime = [] + start = 2 + end = int(math.sqrt(n)) # Size of every segment + temp = [True] * (end + 1) + prime = [] + + while(start <= end): + if temp[start] == True: + in_prime.append(start) + for i in range(start*start, end+1, start): + if temp[i] == 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) + for each in in_prime: + + t = math.floor(low / each) * each + if t < low: + t += 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) + + low = high + 1 + high = low + end - 1 + if high > n: + high = n + + return prime + +print(sieve(10**6)) \ No newline at end of file diff --git a/Maths/SieveOfEratosthenes.py b/Maths/SieveOfEratosthenes.py new file mode 100644 index 000000000000..bac4341deb0e --- /dev/null +++ b/Maths/SieveOfEratosthenes.py @@ -0,0 +1,24 @@ +import math +n = int(input("Enter n: ")) + +def sieve(n): + l = [True] * (n+1) + prime = [] + start = 2 + end = int(math.sqrt(n)) + while(start <= end): + if l[start] == True: + prime.append(start) + for i in range(start*start, n+1, start): + if l[i] == True: + l[i] = False + start += 1 + + for j in range(end+1,n+1): + if l[j] == True: + prime.append(j) + + return prime + +print(sieve(n)) + \ No newline at end of file From ae7660161c38a0630fcff124a8efc0a2102a14b0 Mon Sep 17 00:00:00 2001 From: Kelvin Salton do Prado Date: Tue, 2 Oct 2018 21:11:30 -0300 Subject: [PATCH 0141/2908] strings: add levenshtein distance metric --- strings/levenshtein-distance.py | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 strings/levenshtein-distance.py diff --git a/strings/levenshtein-distance.py b/strings/levenshtein-distance.py new file mode 100644 index 000000000000..274dfd7ccf9b --- /dev/null +++ b/strings/levenshtein-distance.py @@ -0,0 +1,78 @@ +""" +This is a Python implementation of the levenshtein distance. +Levenshtein distance is a string metric for measuring the +difference between two sequences. + +For doctests run following command: +python -m doctest -v levenshtein-distance.py +or +python3 -m doctest -v levenshtein-distance.py + +For manual testing run: +python levenshtein-distance.py +""" + + +def levenshtein_distance(first_word, second_word): + """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. + :return: the levenshtein distance between the two words. + Examples: + >>> levenshtein_distance("planet", "planetary") + 3 + >>> levenshtein_distance("", "test") + 4 + >>> levenshtein_distance("book", "back") + 2 + >>> levenshtein_distance("book", "book") + 0 + >>> levenshtein_distance("test", "") + 4 + >>> levenshtein_distance("", "") + 0 + >>> levenshtein_distance("orchestration", "container") + 10 + """ + # The longer word should come first + if len(first_word) < len(second_word): + return levenshtein_distance(second_word, first_word) + + if len(second_word) == 0: + return len(first_word) + + previous_row = range(len(second_word) + 1) + + for i, c1 in enumerate(first_word): + + current_row = [i + 1] + + for j, c2 in enumerate(second_word): + + # Calculate insertions, deletions and substitutions + insertions = previous_row[j + 1] + 1 + deletions = current_row[j] + 1 + substitutions = previous_row[j] + (c1 != c2) + + # Get the minimum to append to the current row + current_row.append(min(insertions, deletions, substitutions)) + + # Store the previous row + previous_row = current_row + + # Returns the last element (distance) + return previous_row[-1] + + +if __name__ == '__main__': + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 + + first_word = raw_input('Enter the first word:\n').strip() + second_word = raw_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)) From dc584f16585b00df02301de122e4b4970ce9f505 Mon Sep 17 00:00:00 2001 From: S-Sanyal Date: Wed, 3 Oct 2018 20:13:34 +0530 Subject: [PATCH 0142/2908] Made the code Python 3 compatible --- data_structures/Stacks/Stock-Span-Problem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/Stacks/Stock-Span-Problem.py b/data_structures/Stacks/Stock-Span-Problem.py index 658ac3cbfb5e..ecef7ecc666d 100644 --- a/data_structures/Stacks/Stock-Span-Problem.py +++ b/data_structures/Stacks/Stock-Span-Problem.py @@ -37,7 +37,7 @@ 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], + print (arr[i]), # Driver program to test above function From c74b2732d9282fa68c0de666d39635e6ec018f03 Mon Sep 17 00:00:00 2001 From: S-Sanyal Date: Wed, 3 Oct 2018 21:37:46 +0530 Subject: [PATCH 0143/2908] Updated --- data_structures/Stacks/Stock-Span-Problem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/Stacks/Stock-Span-Problem.py b/data_structures/Stacks/Stock-Span-Problem.py index ecef7ecc666d..2081166bbd57 100644 --- a/data_structures/Stacks/Stock-Span-Problem.py +++ b/data_structures/Stacks/Stock-Span-Problem.py @@ -37,7 +37,7 @@ 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]), + print (arr[i],end =" ") # Driver program to test above function From 054b4b31f42a2bfe5dafed66fa88cb477aa63098 Mon Sep 17 00:00:00 2001 From: S-Sanyal Date: Wed, 3 Oct 2018 21:52:30 +0530 Subject: [PATCH 0144/2908] Made compatible for all versions of python --- .vscode/settings.json | 3 +++ data_structures/Stacks/Stock-Span-Problem.py | 1 + 2 files changed, 4 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000000..615aafb035a1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "/usr/bin/python3" +} \ No newline at end of file diff --git a/data_structures/Stacks/Stock-Span-Problem.py b/data_structures/Stacks/Stock-Span-Problem.py index 2081166bbd57..f64c712bb4a6 100644 --- a/data_structures/Stacks/Stock-Span-Problem.py +++ b/data_structures/Stacks/Stock-Span-Problem.py @@ -6,6 +6,7 @@ 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) From bce30b8179c6239191b8d46c6b925c3ad90d6d6b Mon Sep 17 00:00:00 2001 From: Volodymyr Date: Thu, 4 Oct 2018 00:34:44 -0400 Subject: [PATCH 0145/2908] Added Bucket Sort description --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 422bc9f680af..a11afcd88b8a 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,16 @@ __Properties__ ###### View the algorithm in [action][bubble-toptal] +### Bucket +![alt text][bucket-image-1] +![alt text][bucket-image-2] + +From [Wikipedia][bucket-wiki]: 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(n^2) +* Best case performance O(n+k) +* Average case performance O(n+k) ### Insertion @@ -148,6 +158,10 @@ Mathematically a bijective function is used on the characters' positions to encr [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://commons.wikimedia.org/wiki/File:Bucket_sort_1.svg#/media/File:Bucket_sort_1.svg +[bucket-image-2]: https://commons.wikimedia.org/wiki/File:Bucket_sort_2.svg#/media/File:Bucket_sort_2.svg + [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" From 313d5b695b83f4c62979a7df0328d5571b6eb303 Mon Sep 17 00:00:00 2001 From: Volodymyr Date: Thu, 4 Oct 2018 00:38:38 -0400 Subject: [PATCH 0146/2908] Added Cocktail shaker Sort description --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index a11afcd88b8a..1cde0908a583 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,16 @@ __Properties__ * Best case performance O(n+k) * Average case performance O(n+k) +### Coctail shaker +![alt text][cocktail-shaker-image] + +From [Wikipedia][cocktail-shaker-wiki]: 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(n^2) +* Best case performance O(n) +* Average case performance O(n^2) + ### Insertion ![alt text][insertion-image] @@ -162,6 +172,9 @@ Mathematically a bijective function is used on the characters' positions to encr [bucket-image-1]: https://commons.wikimedia.org/wiki/File:Bucket_sort_1.svg#/media/File:Bucket_sort_1.svg [bucket-image-2]: https://commons.wikimedia.org/wiki/File:Bucket_sort_2.svg#/media/File:Bucket_sort_2.svg +[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 + [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" From 8a8baf76d1158581f45d058114c00d395035b573 Mon Sep 17 00:00:00 2001 From: Volodymyr Date: Thu, 4 Oct 2018 00:43:27 -0400 Subject: [PATCH 0147/2908] Added Radix Sort description --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1cde0908a583..99759358f17f 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,15 @@ __Properties__ ###### View the algorithm in [action][quick-toptal] +### Radix + +From [Wikipedia][radix-wiki]: In computer science, 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) + ### Selection ![alt text][selection-image] @@ -169,11 +178,11 @@ Mathematically a bijective function is used on the characters' positions to encr [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://commons.wikimedia.org/wiki/File:Bucket_sort_1.svg#/media/File:Bucket_sort_1.svg -[bucket-image-2]: https://commons.wikimedia.org/wiki/File:Bucket_sort_2.svg#/media/File:Bucket_sort_2.svg +[bucket-image-1]: https://commons.wikimedia.org/wiki/File:Bucket_sort_1.svg#/media/File:Bucket_sort_1.svg "Bucket Sort" +[bucket-image-2]: https://commons.wikimedia.org/wiki/File:Bucket_sort_2.svg#/media/File:Bucket_sort_2.svg "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-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 @@ -183,6 +192,8 @@ Mathematically a bijective function is used on the characters' positions to encr [quick-wiki]: https://en.wikipedia.org/wiki/Quicksort [quick-image]: https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif "Quick Sort" +[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" From ea170ac0da2a4244755b46e8a7c539a526040f6b Mon Sep 17 00:00:00 2001 From: Volodymyr Date: Thu, 4 Oct 2018 00:48:58 -0400 Subject: [PATCH 0148/2908] Added Topological Sort description --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 99759358f17f..d0ea9155d564 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,10 @@ __Properties__ ###### View the algorithm in [action][shell-toptal] +### Topological + +From [Wikipedia][topological-wiki]: In the field of computer science, a 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) @@ -206,6 +210,8 @@ Mathematically a bijective function is used on the characters' positions to encr [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 From 5db4e540a795c4d82fc1a17d1ff0f2d0c25eb7b6 Mon Sep 17 00:00:00 2001 From: Volodymyr Klymenko Date: Thu, 4 Oct 2018 01:02:30 -0400 Subject: [PATCH 0149/2908] update images of Bucket Sort --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d0ea9155d564..8297bbca09a6 100644 --- a/README.md +++ b/README.md @@ -182,8 +182,8 @@ Mathematically a bijective function is used on the characters' positions to encr [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://commons.wikimedia.org/wiki/File:Bucket_sort_1.svg#/media/File:Bucket_sort_1.svg "Bucket Sort" -[bucket-image-2]: https://commons.wikimedia.org/wiki/File:Bucket_sort_2.svg#/media/File:Bucket_sort_2.svg "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" From 25c0bd3fbb866312fc6727a58494f3168cabbf7d Mon Sep 17 00:00:00 2001 From: Coregame Date: Thu, 4 Oct 2018 13:36:16 +0700 Subject: [PATCH 0150/2908] minor improvement (readability) in Insertion Sort --- sorts/insertion_sort.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index b7a4aa7a3c33..59917ac059a7 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -30,9 +30,8 @@ def insertion_sort(collection): [-45, -5, -2] """ for index in range(1, len(collection)): - while 0 < index and collection[index] < collection[index - 1]: - collection[index], collection[ - index - 1] = collection[index - 1], collection[index] + while index > 0 and collection[index - 1] > collection[index]: + collection[index], collection[index - 1] = collection[index - 1], collection[index] index -= 1 return collection From 63c7e8ede14ae7200043c59bf4faaf6104c1b13e Mon Sep 17 00:00:00 2001 From: Harshil Date: Thu, 4 Oct 2018 11:34:50 +0200 Subject: [PATCH 0151/2908] Minor update Changed two characters which were the root cause of 'SyntaxError: Non-ASCII character '\xe2' in file main.py, but no encoding declared' --- data_structures/Stacks/Stock-Span-Problem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/Stacks/Stock-Span-Problem.py b/data_structures/Stacks/Stock-Span-Problem.py index f64c712bb4a6..9628864edd10 100644 --- a/data_structures/Stacks/Stock-Span-Problem.py +++ b/data_structures/Stacks/Stock-Span-Problem.py @@ -1,8 +1,8 @@ ''' 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. +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 +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. ''' @@ -49,4 +49,4 @@ def printArray(arr, n): calculateSpan(price, S) # Print the calculated span values -printArray(S, len(price)) \ No newline at end of file +printArray(S, len(price)) From 356b51458d2ed75e8f03123124e12c141503ad7c Mon Sep 17 00:00:00 2001 From: Prateek <43875858+prateekyo@users.noreply.github.com> Date: Fri, 5 Oct 2018 14:20:19 +0530 Subject: [PATCH 0152/2908] done --- sorts/bubble_sort.py | 75 ++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 52 deletions(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index e28e7aa4fbe2..0be95af899a7 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -1,52 +1,23 @@ -""" -This is pure python implementation of bubble sort algorithm - -For doctests run following command: -python -m doctest -v bubble_sort.py -or -python3 -m doctest -v bubble_sort.py - -For manual testing run: -python bubble_sort.py -""" - -from __future__ import print_function - - -def bubble_sort(collection): - """Pure implementation of bubble sort algorithm in Python - - :param collection: some mutable ordered collection with heterogeneous - comparable items inside - :return: the same collection ordered by ascending - - Examples: - >>> bubble_sort([0, 5, 3, 2, 2]) - [0, 2, 2, 3, 5] - - >>> bubble_sort([]) - [] - - >>> bubble_sort([-2, -5, -45]) - [-45, -5, -2] - """ - length = len(collection) - for i in range(length): - swapped = False - for j in range(length-1): - 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. - 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:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] - print(bubble_sort(unsorted)) +def bubbleSort(arr): + n = len(arr) + + # Traverse through all array elements + for i in range(n): + + # Last i elements are already in place + for j in range(0, n-i-1): + + # traverse the array from 0 to n-i-1 + # Swap if the element found is greater + # than the next element + if arr[j] > arr[j+1] : + arr[j], arr[j+1] = arr[j+1], arr[j] + +# Driver code to test above +arr = [64, 34, 25, 12, 22, 11, 90] + +bubbleSort(arr) + +print ("Sorted array is:") +for i in range(len(arr)): + print ("%d" %arr[i]), From 3808d0c0f2501b265ef0adbe033f48f275b6626e Mon Sep 17 00:00:00 2001 From: fannyvieira Date: Fri, 5 Oct 2018 22:11:50 -0300 Subject: [PATCH 0153/2908] Remove pyc file --- linear-algebra-python/src/lib.pyc | Bin 11098 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 linear-algebra-python/src/lib.pyc diff --git a/linear-algebra-python/src/lib.pyc b/linear-algebra-python/src/lib.pyc deleted file mode 100644 index 7aeca0e1c6d56244f23a07bdeb3a1cf9cfa4ce82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11098 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 v>2s_v=3^rr|+Ei`w*zP|7Yf{hL From 6a6349bdb0ac3d04f12b98248b27337e2ccd7108 Mon Sep 17 00:00:00 2001 From: YasirChoudhary Date: Sun, 7 Oct 2018 14:32:48 +0530 Subject: [PATCH 0154/2908] Optimised for loop iteration --- sorts/bubble_sort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index e28e7aa4fbe2..fc1f9e537b01 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -31,9 +31,9 @@ def bubble_sort(collection): [-45, -5, -2] """ length = len(collection) - for i in range(length): + for i in range(length-1): swapped = False - for j in range(length-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] From 98cc298e61a300493fd2447922908494d028315f Mon Sep 17 00:00:00 2001 From: YasirChoudhary Date: Sun, 7 Oct 2018 14:33:56 +0530 Subject: [PATCH 0155/2908] Optimised for loop iteration --- sorts/selection_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/selection_sort.py b/sorts/selection_sort.py index 432d14090b12..c1d66c5a8b38 100644 --- a/sorts/selection_sort.py +++ b/sorts/selection_sort.py @@ -31,7 +31,7 @@ def selection_sort(collection): """ length = len(collection) - for i in range(length): + for i in range(length - 1): least = i for k in range(i + 1, length): if collection[k] < collection[least]: From 35308bdfaf8b3611ef3a7b597be411a3e7f1f3c2 Mon Sep 17 00:00:00 2001 From: Rafa leyva Date: Sun, 7 Oct 2018 17:02:03 +0200 Subject: [PATCH 0156/2908] Fixed typo in training method --- Neural_Network/perceptron.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neural_Network/perceptron.py b/Neural_Network/perceptron.py index 8ac3e8fc69e9..7856ebb9ddcf 100644 --- a/Neural_Network/perceptron.py +++ b/Neural_Network/perceptron.py @@ -25,7 +25,7 @@ def __init__(self, sample, exit, learn_rate=0.01, epoch_number=1000, bias=-1): self.col_sample = len(sample[0]) self.weight = [] - def trannig(self): + def trainnig(self): for sample in self.sample: sample.insert(0, self.bias) @@ -121,4 +121,4 @@ def sign(self, u): sample = [] for i in range(3): sample.insert(i, float(input('value: '))) - network.sort(sample) \ No newline at end of file + network.sort(sample) From a8a11ccc88a1343d68bef563c4a72b8f651848e5 Mon Sep 17 00:00:00 2001 From: timgiroux <40814794+timgiroux@users.noreply.github.com> Date: Sun, 7 Oct 2018 15:52:34 -0700 Subject: [PATCH 0157/2908] fixed some spelling and added a different print message --- Maths/fibonacciSeries.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Maths/fibonacciSeries.py b/Maths/fibonacciSeries.py index cf025b07d576..5badc6a8262f 100644 --- a/Maths/fibonacciSeries.py +++ b/Maths/fibonacciSeries.py @@ -6,11 +6,11 @@ def recur_fibo(n): else: return(recur_fibo(n-1) + recur_fibo(n-2)) -limit = int(input("How many terms to include in fionacci series:")) +limit = int(input("How many terms to include in fibonacci series: ")) if limit <= 0: - print("Plese enter a positive integer") + print("Please enter a positive integer: ") else: - print("Fibonacci series:") + print(f"The first {limit} terms of the fibonacci series are as follows") for i in range(limit): print(recur_fibo(i)) From 2a87e8625f719b564fb75bd97a5785f5231978af Mon Sep 17 00:00:00 2001 From: phaluch <43946597+phaluch@users.noreply.github.com> Date: Mon, 8 Oct 2018 03:03:53 -0300 Subject: [PATCH 0158/2908] Grammar corrections Just small corrections to grammar and spelling, no code change. --- ArithmeticAnalysis/NewtonRaphsonMethod.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ArithmeticAnalysis/NewtonRaphsonMethod.py b/ArithmeticAnalysis/NewtonRaphsonMethod.py index 40501134e28a..e7abb64e71f5 100644 --- a/ArithmeticAnalysis/NewtonRaphsonMethod.py +++ b/ArithmeticAnalysis/NewtonRaphsonMethod.py @@ -1,4 +1,4 @@ -# Implementing Newton Raphson method in python +# Implementing Newton Raphson method in Python # Author: Haseeb from sympy import diff @@ -20,8 +20,8 @@ def NewtonRaphson(func, a): # Let's Execute if __name__ == '__main__': - # Find root of trignometric fucntion - # Find value of pi + # Find root of trigonometric function + # Find value of pi print ('sin(x) = 0', NewtonRaphson('sin(x)', 2)) # Find root of polynomial @@ -30,7 +30,7 @@ def NewtonRaphson(func, a): # Find Square Root of 5 print ('x**2 - 5 = 0', NewtonRaphson('x**2 - 5', 0.1)) - # Exponential Roots + # Exponential Roots print ('exp(x) - 1 = 0', NewtonRaphson('exp(x) - 1', 0)) From ee8c01179dfe3b4261ac0e53d22a5a6b254f6a37 Mon Sep 17 00:00:00 2001 From: Himani Negi <36325656+Himani2000@users.noreply.github.com> Date: Tue, 9 Oct 2018 01:51:14 +0530 Subject: [PATCH 0159/2908] Add files via upload --- machine_learning/NaiveBayes.ipynb | 1659 +++++++++++++++++++++++++++++ 1 file changed, 1659 insertions(+) create mode 100644 machine_learning/NaiveBayes.ipynb diff --git a/machine_learning/NaiveBayes.ipynb b/machine_learning/NaiveBayes.ipynb new file mode 100644 index 000000000000..5a427c5cb965 --- /dev/null +++ b/machine_learning/NaiveBayes.ipynb @@ -0,0 +1,1659 @@ +{ + "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 +} From b9a5c221aa9bb5e630a60475b72a02e54ab28b27 Mon Sep 17 00:00:00 2001 From: Harshil Date: Wed, 10 Oct 2018 20:05:59 +0200 Subject: [PATCH 0160/2908] Update perceptron.py --- Neural_Network/perceptron.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neural_Network/perceptron.py b/Neural_Network/perceptron.py index 7856ebb9ddcf..44c98e29c52b 100644 --- a/Neural_Network/perceptron.py +++ b/Neural_Network/perceptron.py @@ -25,7 +25,7 @@ def __init__(self, sample, exit, learn_rate=0.01, epoch_number=1000, bias=-1): self.col_sample = len(sample[0]) self.weight = [] - def trainnig(self): + def training(self): for sample in self.sample: sample.insert(0, self.bias) From 0f3ab059997e5fb039cfe6457a4e5c4684ede0cb Mon Sep 17 00:00:00 2001 From: Shivansh Nalwaya Date: Thu, 11 Oct 2018 15:01:13 +0530 Subject: [PATCH 0161/2908] Use sup tag for power in README.md instead of ^ --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8297bbca09a6..15ddd22692d9 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ These are for demonstration purposes only. There are many implementations of sor From [Wikipedia][bubble-wiki]: 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(n^2) +* Worst case performance O(n2) * Best case performance O(n) -* Average case performance O(n^2) +* Average case performance O(n2) ###### View the algorithm in [action][bubble-toptal] @@ -26,7 +26,7 @@ __Properties__ From [Wikipedia][bucket-wiki]: 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(n^2) +* Worst case performance O(n2) * Best case performance O(n+k) * Average case performance O(n+k) @@ -36,9 +36,9 @@ __Properties__ From [Wikipedia][cocktail-shaker-wiki]: 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(n^2) +* Worst case performance O(n2) * Best case performance O(n) -* Average case performance O(n^2) +* Average case performance O(n2) ### Insertion @@ -47,9 +47,9 @@ __Properties__ From [Wikipedia][insertion-wiki]: 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(n^2) +* Worst case performance O(n2) * Best case performance O(n) -* Average case performance O(n^2) +* Average case performance O(n2) ###### View the algorithm in [action][insertion-toptal] @@ -73,7 +73,7 @@ __Properties__ From [Wikipedia][quick-wiki]: 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(n^2) +* 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) @@ -94,9 +94,9 @@ __Properties__ From [Wikipedia][selection-wiki]: The algorithm 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(n^2) -* Best case performance O(n^2) -* Average case performance O(n^2) +* Worst case performance O(n2) +* Best case performance O(n2) +* Average case performance O(n2) ###### View the algorithm in [action][selection-toptal] From e03426b8fd696b8794e21ef52c76a0a5140e1463 Mon Sep 17 00:00:00 2001 From: rafagarciac Date: Fri, 12 Oct 2018 00:41:57 +0200 Subject: [PATCH 0162/2908] Improve and Refactor the fibonnaciSeries.py (Recursion) --- Maths/fibonacciSeries.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Maths/fibonacciSeries.py b/Maths/fibonacciSeries.py index 5badc6a8262f..a54fb89dbc2f 100644 --- a/Maths/fibonacciSeries.py +++ b/Maths/fibonacciSeries.py @@ -1,16 +1,18 @@ # Fibonacci Sequence Using Recursion def recur_fibo(n): - if n <= 1: - return n - else: - return(recur_fibo(n-1) + recur_fibo(n-2)) + return n if n <= 1 else (recur_fibo(n-1) + recur_fibo(n-2)) -limit = int(input("How many terms to include in fibonacci series: ")) +def isPositiveInteger(limit): + return limit >= 0 -if limit <= 0: - print("Please enter a positive integer: ") -else: - print(f"The first {limit} terms of the fibonacci series are as follows") - for i in range(limit): - print(recur_fibo(i)) +def main(): + limit = int(input("How many terms to include in fibonacci series: ")) + if isPositiveInteger: + 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: ") + +if __name__ == '__main__': + main() From 3fef88a28f0c7b9135c30a3d2c6d4a402ec10212 Mon Sep 17 00:00:00 2001 From: Vikas Kumar Yadav Date: Fri, 12 Oct 2018 12:13:38 +0530 Subject: [PATCH 0163/2908] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15ddd22692d9..542e667062fa 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ __Properties__ * Best case performance O(n+k) * Average case performance O(n+k) -### Coctail shaker +### Cocktail shaker ![alt text][cocktail-shaker-image] From [Wikipedia][cocktail-shaker-wiki]: 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. From 67a31e4b2f473d55b9ee0838d93949dcabb9c8d6 Mon Sep 17 00:00:00 2001 From: Harshil Date: Fri, 12 Oct 2018 18:46:45 +0200 Subject: [PATCH 0164/2908] Reuters - OneVsRestClassifier (I'd also uploaded the same at: https://www.kaggle.com/harshildarji/reuters-onevsrestclassifier) --- .../Reuters - OneVsRestClassifier.ipynb | 412 ++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 machine_learning/Reuters - OneVsRestClassifier.ipynb diff --git a/machine_learning/Reuters - OneVsRestClassifier.ipynb b/machine_learning/Reuters - OneVsRestClassifier.ipynb new file mode 100644 index 000000000000..e703e0bd474b --- /dev/null +++ b/machine_learning/Reuters - OneVsRestClassifier.ipynb @@ -0,0 +1,412 @@ +{ + "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": [ + "# Add your code here" + ] + }, + { + "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": "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4E5t2fKqXnnl2abbZs26TyUlz6ik5Bm9/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 +} From 4fc68bbb4c6662f10014a9415ff41e03cc8170ce Mon Sep 17 00:00:00 2001 From: Harshil Date: Fri, 12 Oct 2018 18:50:00 +0200 Subject: [PATCH 0165/2908] Update Reuters - OneVsRestClassifier.ipynb --- machine_learning/Reuters - OneVsRestClassifier.ipynb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/machine_learning/Reuters - OneVsRestClassifier.ipynb b/machine_learning/Reuters - OneVsRestClassifier.ipynb index e703e0bd474b..968130a6053a 100644 --- a/machine_learning/Reuters - OneVsRestClassifier.ipynb +++ b/machine_learning/Reuters - OneVsRestClassifier.ipynb @@ -74,13 +74,6 @@ "all_categories = sorted(list(set(reuters.categories())))" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Add your code here" - ] - }, { "cell_type": "markdown", "metadata": {}, From dc4ae7d75409e36683dfbf2806be7343a162bf1a Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Fri, 12 Oct 2018 18:09:37 -0700 Subject: [PATCH 0166/2908] Added Descriptions for: * ROT13 * RSA * XOR * Interpolation search * Jump search * QuickSelect Search * Tabu Search in README.md --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 542e667062fa..397bcccbf3fe 100644 --- a/README.md +++ b/README.md @@ -151,8 +151,40 @@ __Properties__ * Worst case performance O(log n) * Best case performance O(1) * Average case performance O(log n) -* Worst case space complexity O(1) +* Worst case space complexity O(1) +## 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.[1] 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](https://en.wikipedia.org/wiki/Interpolation_search) + +## Jump Search +In computer science, a 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](https://en.wikipedia.org/wiki/Jump_search) + +## Quick Select +![alt text][QuickSelect-image] +In computer science, quickselect 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](https://en.wikipedia.org/wiki/Quickselect) + +## 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](https://en.wikipedia.org/wiki/Tabu_search) ---------------------------------------------------------------------------------------------------------------------- ## Ciphers @@ -168,15 +200,38 @@ The encryption step performed by a Caesar cipher is often incorporated as part o ### Vigenère The **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'). +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 -In cryptography, a **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).
+In cryptography, a **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 +In cryptography, the simple XOR cipher is a 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" @@ -220,3 +275,7 @@ Mathematically a bijective function is used on the characters' positions to encr [caesar]: https://upload.wikimedia.org/wikipedia/commons/4/4a/Caesar_cipher_left_shift_of_3.svg + +[ROT13-image]: https://en.wikipedia.org/wiki/File:ROT13_table_with_example.svg + +[QuickSelect-image]: https://en.wikipedia.org/wiki/File:Selecting_quickselect_frames.gif From 54697599f44cc1c5c994fe360c95f73ae35f1b45 Mon Sep 17 00:00:00 2001 From: Amit Pradhan Date: Sun, 14 Oct 2018 00:53:44 +0530 Subject: [PATCH 0167/2908] Update Maths/BasicMaths.py Add main method to 'Maths/BasicMaths' module --- Maths/BasicMaths.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Maths/BasicMaths.py b/Maths/BasicMaths.py index f05cde64d5a6..6e8c919a001d 100644 --- a/Maths/BasicMaths.py +++ b/Maths/BasicMaths.py @@ -62,9 +62,13 @@ def eulerPhi(n): s *= (x - 1)/x return s -print(primeFactors(100)) -print(numberOfDivisors(100)) -print(sumOfDivisors(100)) -print(eulerPhi(100)) +def main(): + print(primeFactors(100)) + print(numberOfDivisors(100)) + print(sumOfDivisors(100)) + print(eulerPhi(100)) + +if __name__ == '__main__': + main() \ No newline at end of file From 1a4962a589c486e7e9fd989db333a83cf3a8e123 Mon Sep 17 00:00:00 2001 From: rafagarciac Date: Sat, 13 Oct 2018 22:17:57 +0200 Subject: [PATCH 0168/2908] Refactor gdc and rename two files GreaterCommonDivisor and FibonnaciSequenceRecursive --- ...cciSeries.py => FibonacciSequenceRecursion.py} | 0 Maths/GreaterCommonDivisor.py | 15 +++++++++++++++ Maths/gcd.py | 12 ------------ 3 files changed, 15 insertions(+), 12 deletions(-) rename Maths/{fibonacciSeries.py => FibonacciSequenceRecursion.py} (100%) create mode 100644 Maths/GreaterCommonDivisor.py delete mode 100644 Maths/gcd.py diff --git a/Maths/fibonacciSeries.py b/Maths/FibonacciSequenceRecursion.py similarity index 100% rename from Maths/fibonacciSeries.py rename to Maths/FibonacciSequenceRecursion.py diff --git a/Maths/GreaterCommonDivisor.py b/Maths/GreaterCommonDivisor.py new file mode 100644 index 000000000000..15adaca1fb8d --- /dev/null +++ b/Maths/GreaterCommonDivisor.py @@ -0,0 +1,15 @@ +# Greater Common Divisor - https://en.wikipedia.org/wiki/Greatest_common_divisor +def gcd(a, b): + return b if a == 0 else gcd(b % a, a) + +def main(): + try: + nums = input("Enter two Integers separated by comma (,): ").split(',') + num1 = int(nums[0]); num2 = int(nums[1]) + except (IndexError, UnboundLocalError, ValueError): + print("Wrong Input") + print(f"gcd({num1}, {num2}) = {gcd(num1, num2)}") + +if __name__ == '__main__': + main() + diff --git a/Maths/gcd.py b/Maths/gcd.py deleted file mode 100644 index 0f0ba7dce11b..000000000000 --- a/Maths/gcd.py +++ /dev/null @@ -1,12 +0,0 @@ -def gcd(a, b): - if a == 0 : - return b - - return gcd(b%a, a) - -def main(): - print(gcd(3, 6)) - - -if __name__ == '__main__': - main() From a811a0e8499f291428b1d972d43f7b26e65a6d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Garc=C3=ADa=20Cu=C3=A9llar?= Date: Sat, 13 Oct 2018 22:22:32 +0200 Subject: [PATCH 0169/2908] Update FibonacciSequenceRecursion.py --- Maths/FibonacciSequenceRecursion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maths/FibonacciSequenceRecursion.py b/Maths/FibonacciSequenceRecursion.py index a54fb89dbc2f..a97bb8b3f124 100644 --- a/Maths/FibonacciSequenceRecursion.py +++ b/Maths/FibonacciSequenceRecursion.py @@ -8,7 +8,7 @@ def isPositiveInteger(limit): def main(): limit = int(input("How many terms to include in fibonacci series: ")) - if isPositiveInteger: + if isPositiveInteger(limit): print(f"The first {limit} terms of the fibonacci series are as follows:") print([recur_fibo(n) for n in range(limit)]) else: From fc45a6cf54b9bbe5609ab0c00d868c785f15c5da Mon Sep 17 00:00:00 2001 From: Aditya Menon Date: Tue, 16 Oct 2018 09:29:17 +0530 Subject: [PATCH 0170/2908] Updated README.md to include Heap Sort Updated README.md to include description of Heap Sort --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 397bcccbf3fe..f0c40802b1d1 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,20 @@ __Properties__ ###### View the algorithm in [action][quick-toptal] + +### Heap + +From [Wikipedia][heap-wiki]: 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) + + + +###### View the algorithm in [action](https://www.toptal.com/developers/sorting-algorithms/heap-sort) + ### Radix From [Wikipedia][radix-wiki]: In computer science, 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. From f9f5d402d390329b5b88b33c9ce4d625a87917cd Mon Sep 17 00:00:00 2001 From: Harshil Date: Tue, 16 Oct 2018 20:39:22 +0200 Subject: [PATCH 0171/2908] Update bubble_sort.py Added main method, Made it Python2 suitable, Enabled user input! --- sorts/bubble_sort.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index 0be95af899a7..359a4b21d683 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -1,23 +1,23 @@ -def bubbleSort(arr): - n = len(arr) +from __future__ import print_function + +def bubble_sort(arr): + n = len(arr) + # Traverse through all array elements + for i in range(n): + # Last i elements are already in place + for j in range(0, n-i-1): + # traverse the array from 0 to n-i-1 + # Swap if the element found is greater + # than the next element + if arr[j] > arr[j+1] : + arr[j], arr[j+1] = arr[j+1], arr[j] + return arr - # Traverse through all array elements - for i in range(n): - - # Last i elements are already in place - for j in range(0, n-i-1): - - # traverse the array from 0 to n-i-1 - # Swap if the element found is greater - # than the next element - if arr[j] > arr[j+1] : - arr[j], arr[j+1] = arr[j+1], arr[j] - -# Driver code to test above -arr = [64, 34, 25, 12, 22, 11, 90] - -bubbleSort(arr) - -print ("Sorted array is:") -for i in range(len(arr)): - print ("%d" %arr[i]), +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() + unsorted = [int(item) for item in user_input.split(',')] + print(*bubble_sort(unsorted), sep=',') From ade3ed3784fe8e34978563df76a7b4f7d5fe3665 Mon Sep 17 00:00:00 2001 From: Taru <32308101+epicalyx@users.noreply.github.com> Date: Wed, 17 Oct 2018 00:22:44 +0530 Subject: [PATCH 0172/2908] Logistic regression implementation implementation of logistic regression for binary classification --- machine_learning/logistic_regression.py | 97 +++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 machine_learning/logistic_regression.py diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py new file mode 100644 index 000000000000..70c0b2807b7f --- /dev/null +++ b/machine_learning/logistic_regression.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# coding: utf-8 + +# # Logistic Regression from scratch + +# In[62]: + + +''' Implementing logistic regression for classification problem + Helpful resources : 1.Coursera ML course 2.https://medium.com/@martinpella/logistic-regression-from-scratch-in-python-124c5636b8ac''' + + +# In[63]: + + +#importing all the required libraries +import numpy as np +import matplotlib.pyplot as plt +get_ipython().run_line_magic('matplotlib', 'inline') +from sklearn import datasets + + +# In[67]: + + +#sigmoid function or logistic function is used as a hypothesis function in classification problems +def sigmoid_function(z): + return 1/(1+np.exp(-z)) + + +def cost_function(h,y): + return (-y*np.log(h)-(1-y)*np.log(1-h)).mean() + +# here alpha is the learning rate, X is the featue matrix,y is the target matrix +def logistic_reg(alpha,X,y,max_iterations=70000): + converged=False + iterations=0 + theta=np.zeros(X.shape[1]) + + num_iterations=0 + while not converged: + z=np.dot(X,theta) + 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) + e=cost_function(h,y) + print('J=',e) + J=e + + iterations+=1 #update iterations + + + if iterations== max_iterations: + print("Maximum iterations exceeded!") + converged=True + + return theta + + + + + + + + +# In[68]: + + +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) + def predict_prob(X): + 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') + 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)) + 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.legend(); + + + From f018ddc4c00f71e7123b780d409550e101e3c673 Mon Sep 17 00:00:00 2001 From: Taru <32308101+epicalyx@users.noreply.github.com> Date: Wed, 17 Oct 2018 00:52:32 +0530 Subject: [PATCH 0173/2908] requested changes addressed --- machine_learning/logistic_regression.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 70c0b2807b7f..2e28fd96bf01 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -31,13 +31,13 @@ def sigmoid_function(z): def cost_function(h,y): return (-y*np.log(h)-(1-y)*np.log(1-h)).mean() -# here alpha is the learning rate, X is the featue matrix,y is the target matrix +# 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): converged=False iterations=0 theta=np.zeros(X.shape[1]) - num_iterations=0 + while not converged: z=np.dot(X,theta) h=sigmoid_function(z) @@ -46,9 +46,9 @@ def logistic_reg(alpha,X,y,max_iterations=70000): z=np.dot(X,theta) h=sigmoid_function(z) - e=cost_function(h,y) - print('J=',e) - J=e + J=cost_function(h,y) + + iterations+=1 #update iterations From 7105f6f648aa728c44e04e2389ad141314e2818f Mon Sep 17 00:00:00 2001 From: Taru <32308101+epicalyx@users.noreply.github.com> Date: Wed, 17 Oct 2018 01:07:29 +0530 Subject: [PATCH 0174/2908] minor changes requested changes are addressed --- machine_learning/logistic_regression.py | 1 + 1 file changed, 1 insertion(+) diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 2e28fd96bf01..de0cfd54d2c6 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -55,6 +55,7 @@ def logistic_reg(alpha,X,y,max_iterations=70000): if iterations== max_iterations: print("Maximum iterations exceeded!") + print("Minimal cost function J=",J) converged=True return theta From 250ddc23eab5df6dffab26eeaa2bc9c84d58690c Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Tue, 16 Oct 2018 19:50:45 -0400 Subject: [PATCH 0175/2908] Fix problem 09 folder name --- Project Euler/{Problem 9 => Problem 09}/sol1.py | 0 Project Euler/{Problem 9 => Problem 09}/sol2.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Project Euler/{Problem 9 => Problem 09}/sol1.py (100%) rename Project Euler/{Problem 9 => Problem 09}/sol2.py (100%) diff --git a/Project Euler/Problem 9/sol1.py b/Project Euler/Problem 09/sol1.py similarity index 100% rename from Project Euler/Problem 9/sol1.py rename to Project Euler/Problem 09/sol1.py diff --git a/Project Euler/Problem 9/sol2.py b/Project Euler/Problem 09/sol2.py similarity index 100% rename from Project Euler/Problem 9/sol2.py rename to Project Euler/Problem 09/sol2.py From 0856a6185998f664d48904a1bfd5a53f9b8aa4a8 Mon Sep 17 00:00:00 2001 From: ParthS007 Date: Thu, 18 Oct 2018 02:58:57 +0530 Subject: [PATCH 0176/2908] Remove Multiple Unused Imports and Variable --- .gitignore | 1 + .vscode/settings.json | 3 -- ArithmeticAnalysis/LUdecomposition.py | 6 ++-- ArithmeticAnalysis/NewtonRaphsonMethod.py | 4 +-- .../Multi_Hueristic_Astar.py | 2 -- Graphs/basic-graphs.py | 2 +- Project Euler/Problem 09/sol2.py | 1 - Project Euler/Problem 15/sol1.py | 2 +- ciphers/affine_cipher.py | 4 +-- .../LinkedList/DoublyLinkedList.py | 4 +-- data_structures/LinkedList/__init__.py | 2 +- .../LinkedList/singly_LinkedList.py | 1 + .../k_means_clustering_tensorflow.py | 2 +- machine_learning/linear_regression.py | 1 - other/game_of_life/game_o_life.py | 3 +- other/primelib.py | 7 ++--- searches/interpolation_search.py | 1 - searches/quick_select.py | 31 +++++++++---------- sorts/external-sort.py | 1 - sorts/random_normaldistribution_quicksort.py | 1 - sorts/tree_sort.py | 4 +-- strings/min-cost-string-conversion.py | 1 - 22 files changed, 34 insertions(+), 50 deletions(-) delete mode 100644 .vscode/settings.json rename Multi_Hueristic_Astar.py => Graphs/Multi_Hueristic_Astar.py (99%) diff --git a/.gitignore b/.gitignore index 96422cb2ec94..5f9132236c26 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ __pycache__/ *.so # Distribution / packaging +.vscode/ .Python env/ build/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 615aafb035a1..000000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.pythonPath": "/usr/bin/python3" -} \ No newline at end of file diff --git a/ArithmeticAnalysis/LUdecomposition.py b/ArithmeticAnalysis/LUdecomposition.py index bb0827bb39b1..da05fb65d0ea 100644 --- a/ArithmeticAnalysis/LUdecomposition.py +++ b/ArithmeticAnalysis/LUdecomposition.py @@ -1,7 +1,7 @@ -import math import numpy -def LUDecompose (table): #table that contains our data +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)) @@ -31,4 +31,4 @@ def LUDecompose (table): #table that contains our data matrix =numpy.array([[2,-2,1],[0,1,2],[5,3,1]]) L,U = LUDecompose(matrix) print(L) -print(U) \ No newline at end of file +print(U) diff --git a/ArithmeticAnalysis/NewtonRaphsonMethod.py b/ArithmeticAnalysis/NewtonRaphsonMethod.py index e7abb64e71f5..5e7e2f930abc 100644 --- a/ArithmeticAnalysis/NewtonRaphsonMethod.py +++ b/ArithmeticAnalysis/NewtonRaphsonMethod.py @@ -3,16 +3,14 @@ from sympy import diff from decimal import Decimal -from math import sin, cos, exp def NewtonRaphson(func, a): ''' Finds root from the point 'a' onwards by Newton-Raphson method ''' while True: - x = a c = Decimal(a) - ( Decimal(eval(func)) / Decimal(eval(str(diff(func)))) ) - x = c a = c + # This number dictates the accuracy of the answer if abs(eval(func)) < 10**-15: return c diff --git a/Multi_Hueristic_Astar.py b/Graphs/Multi_Hueristic_Astar.py similarity index 99% rename from Multi_Hueristic_Astar.py rename to Graphs/Multi_Hueristic_Astar.py index 7fbd2ff04542..1acd098f327d 100644 --- a/Multi_Hueristic_Astar.py +++ b/Graphs/Multi_Hueristic_Astar.py @@ -1,8 +1,6 @@ from __future__ import print_function import heapq import numpy as np -import math -import copy try: xrange # Python 2 diff --git a/Graphs/basic-graphs.py b/Graphs/basic-graphs.py index 6e433b5bd725..28fa3d180ffd 100644 --- a/Graphs/basic-graphs.py +++ b/Graphs/basic-graphs.py @@ -140,7 +140,7 @@ def dijk(G, s): def topo(G, ind=None, Q=[1]): - if ind == None: + if ind is None: ind = [0] * (len(G) + 1) # SInce oth Index is ignored for u in G: for v in G[u]: diff --git a/Project Euler/Problem 09/sol2.py b/Project Euler/Problem 09/sol2.py index 13674d25875e..933f5c557d71 100644 --- a/Project Euler/Problem 09/sol2.py +++ b/Project Euler/Problem 09/sol2.py @@ -3,7 +3,6 @@ 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 -import sys product=-1 d=0 diff --git a/Project Euler/Problem 15/sol1.py b/Project Euler/Problem 15/sol1.py index 9b61b37d2081..d24748011ef9 100644 --- a/Project Euler/Problem 15/sol1.py +++ b/Project Euler/Problem 15/sol1.py @@ -1,5 +1,5 @@ from __future__ import print_function -from math import factorial, ceil +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,... diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index 6c1ba06f6850..4fbe87d42e61 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -68,8 +68,8 @@ def getRandomKey(): while True: keyA = random.randint(2, len(SYMBOLS)) keyB = random.randint(2, len(SYMBOLS)) - if cryptoMath.gcd(keyA, len(SYMBOLS)) == 1: - return keyA * len(SYMBOLS) + keyB + if cryptoMath.gcd(keyA, len(SYMBOLS)) == 1: + return keyA * len(SYMBOLS) + keyB if __name__ == '__main__': import doctest diff --git a/data_structures/LinkedList/DoublyLinkedList.py b/data_structures/LinkedList/DoublyLinkedList.py index 6f17b7c81033..18eb63fea00e 100644 --- a/data_structures/LinkedList/DoublyLinkedList.py +++ b/data_structures/LinkedList/DoublyLinkedList.py @@ -24,7 +24,7 @@ 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 == None): + if(self.head is None): self.tail = None return temp @@ -58,7 +58,7 @@ def delete(self, x): current.next.previous = current.previous # 1 <--> 3 def isEmpty(self): #Will return True if the list is empty - return(self.head == None) + return(self.head is None) def display(self): #Prints contents of the list current = self.head diff --git a/data_structures/LinkedList/__init__.py b/data_structures/LinkedList/__init__.py index 1d220599fb16..6d50f23c1f1a 100644 --- a/data_structures/LinkedList/__init__.py +++ b/data_structures/LinkedList/__init__.py @@ -19,4 +19,4 @@ def remove(self): return item def is_empty(self): - return self.head == None + return self.head is None diff --git a/data_structures/LinkedList/singly_LinkedList.py b/data_structures/LinkedList/singly_LinkedList.py index eb7f48f17359..0d6157e3eb65 100644 --- a/data_structures/LinkedList/singly_LinkedList.py +++ b/data_structures/LinkedList/singly_LinkedList.py @@ -67,3 +67,4 @@ def reverse(Head): current = next_node # Return prev in order to put the head at the end Head = prev + return Head diff --git a/dynamic_programming/k_means_clustering_tensorflow.py b/dynamic_programming/k_means_clustering_tensorflow.py index ad495c71a978..b6813c6a22b3 100644 --- a/dynamic_programming/k_means_clustering_tensorflow.py +++ b/dynamic_programming/k_means_clustering_tensorflow.py @@ -1,5 +1,5 @@ import tensorflow as tf -from random import choice, shuffle +from random import shuffle from numpy import array diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index eb1f019c502c..8c23f1f77908 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -59,7 +59,6 @@ def sum_of_square_error(data_x, data_y, len_data, theta): :param theta : contains the feature vector :return : sum of square error computed from given feature's """ - error = 0.0 prod = np.dot(theta, data_x.transpose()) prod -= data_y.transpose() sum_elem = np.sum(np.square(prod)) diff --git a/other/game_of_life/game_o_life.py b/other/game_of_life/game_o_life.py index 32ebe0fc1301..1fdaa21b4a7b 100644 --- a/other/game_of_life/game_o_life.py +++ b/other/game_of_life/game_o_life.py @@ -28,9 +28,8 @@ comes a live cell, as if by reproduction. ''' import numpy as np -import random, time, sys +import random, sys from matplotlib import pyplot as plt -import matplotlib.animation as animation from matplotlib.colors import ListedColormap usage_doc='Usage of script: script_nama ' diff --git a/other/primelib.py b/other/primelib.py index 16c44a0938a5..19572f8611cb 100644 --- a/other/primelib.py +++ b/other/primelib.py @@ -290,7 +290,7 @@ def goldbach(number): while (i < lenPN and loop): - j = i+1; + j = i+1 while (j < lenPN and loop): @@ -300,9 +300,8 @@ def goldbach(number): ans.append(primeNumbers[i]) ans.append(primeNumbers[j]) - j += 1; - - + j += 1 + i += 1 # precondition diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index 7b765c454d06..db9893bdb5d4 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -2,7 +2,6 @@ This is pure python implementation of interpolation search algorithm """ from __future__ import print_function -import bisect try: raw_input # Python 2 diff --git a/searches/quick_select.py b/searches/quick_select.py index e5e2ce99c682..1596cf040e0c 100644 --- a/searches/quick_select.py +++ b/searches/quick_select.py @@ -1,8 +1,5 @@ -import collections -import sys import random -import time -import math + """ 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 @@ -25,23 +22,23 @@ def _partition(data, pivot): equal.append(element) return less, equal, greater - def quickSelect(list, k): +def quickSelect(list, k): #k = len(list) // 2 when trying to find the median (index that value would be when list is sorted) - smaller = [] - larger = [] - pivot = random.randint(0, len(list) - 1) - pivot = list[pivot] - count = 0 - smaller, equal, larger =_partition(list, pivot) - count = len(equal) - m = len(smaller) + smaller = [] + larger = [] + pivot = random.randint(0, len(list) - 1) + pivot = list[pivot] + count = 0 + smaller, equal, larger =_partition(list, pivot) + count = len(equal) + m = len(smaller) - #k is the pivot - if m <= k < m + count: + #k is the pivot + if m <= k < m + count: return pivot # must be in smaller - elif m > k: + elif m > k: return quickSelect(smaller, k) #must be in larger - else: + else: return quickSelect(larger, k - (m + count)) diff --git a/sorts/external-sort.py b/sorts/external-sort.py index 6c4adc94c0f0..dece32d48da0 100644 --- a/sorts/external-sort.py +++ b/sorts/external-sort.py @@ -4,7 +4,6 @@ # Sort large text files in a minimum amount of memory # import os -import sys import argparse class FileSplitter(object): diff --git a/sorts/random_normaldistribution_quicksort.py b/sorts/random_normaldistribution_quicksort.py index bd730b3b1e6d..dfa37da61e26 100644 --- a/sorts/random_normaldistribution_quicksort.py +++ b/sorts/random_normaldistribution_quicksort.py @@ -2,7 +2,6 @@ from random import randint from tempfile import TemporaryFile import numpy as np -import math diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index 94cf68033b55..f8ecf84c6ff8 100644 --- a/sorts/tree_sort.py +++ b/sorts/tree_sort.py @@ -11,12 +11,12 @@ def __init__(self, val): def insert(self,val): if self.val: if val < self.val: - if self.left == None: + if self.left is None: self.left = node(val) else: self.left.insert(val) elif val > self.val: - if self.right == None: + if self.right is None: self.right = node(val) else: self.right.insert(val) diff --git a/strings/min-cost-string-conversion.py b/strings/min-cost-string-conversion.py index 7e3298137c05..c233af1c81b7 100644 --- a/strings/min-cost-string-conversion.py +++ b/strings/min-cost-string-conversion.py @@ -68,7 +68,6 @@ def assemble_transformation(ops, i, j): return seq if __name__ == '__main__': - from time import sleep _, operations = compute_transform_tables('Python', 'Algorithms', -1, 1, 2, 2) m = len(operations) From 07451a6ca4f284050ec9ff152561a3e691fc635d Mon Sep 17 00:00:00 2001 From: Parth Shandilya Date: Fri, 19 Oct 2018 13:28:21 +0530 Subject: [PATCH 0177/2908] Improved Code and removed warnings (#482) Improved Code and removed warnings --- Graphs/ArticulationPoints.py | 2 +- Graphs/MinimumSpanningTree_Prims.py | 4 ++-- Maths/FibonacciSequenceRecursion.py | 2 +- Neural_Network/bpnn.py | 3 +++ Project Euler/Problem 02/sol2.py | 2 +- Project Euler/Problem 03/sol1.py | 2 +- data_structures/Graph/BreadthFirstSearch.py | 6 +++++- data_structures/Graph/DepthFirstSearch.py | 5 ++++- data_structures/Graph/Graph.py | 3 +++ data_structures/Heap/heap.py | 2 +- dynamic_programming/fastfibonacci.py | 3 +++ machine_learning/gradient_descent.py | 2 +- other/Fischer-Yates_Shuffle.py | 6 ++++-- other/sierpinski_triangle.py | 3 +++ 14 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Graphs/ArticulationPoints.py b/Graphs/ArticulationPoints.py index 9965f5cb23cd..1173c4ea373c 100644 --- a/Graphs/ArticulationPoints.py +++ b/Graphs/ArticulationPoints.py @@ -37,7 +37,7 @@ def dfs(root, at, parent, outEdgeCount): for x in range(len(isArt)): if isArt[x] == True: - print(x, end=" ") + 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]} diff --git a/Graphs/MinimumSpanningTree_Prims.py b/Graphs/MinimumSpanningTree_Prims.py index 7b1ad0e743f7..569e73e0ab7b 100644 --- a/Graphs/MinimumSpanningTree_Prims.py +++ b/Graphs/MinimumSpanningTree_Prims.py @@ -101,8 +101,8 @@ def deleteMinimum(heap, positions): return TreeEdges # < --------- Prims Algorithm --------- > -n = int(input("Enter number of vertices: ")) -e = int(input("Enter number of edges: ")) +n = int(raw_input("Enter number of vertices: ")) +e = int(raw_input("Enter number of edges: ")) adjlist = defaultdict(list) for x in range(e): l = [int(x) for x in input().split()] diff --git a/Maths/FibonacciSequenceRecursion.py b/Maths/FibonacciSequenceRecursion.py index a97bb8b3f124..b0b10fd07262 100644 --- a/Maths/FibonacciSequenceRecursion.py +++ b/Maths/FibonacciSequenceRecursion.py @@ -9,7 +9,7 @@ def isPositiveInteger(limit): def main(): limit = int(input("How many terms to include in fibonacci series: ")) if isPositiveInteger(limit): - print(f"The first {limit} terms of the fibonacci series are as follows:") + print("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: ") diff --git a/Neural_Network/bpnn.py b/Neural_Network/bpnn.py index ed5d4c8cbf79..0865e35f0b5c 100644 --- a/Neural_Network/bpnn.py +++ b/Neural_Network/bpnn.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# encoding=utf8 + ''' A Framework of Back Propagation Neural Network(BP) model diff --git a/Project Euler/Problem 02/sol2.py b/Project Euler/Problem 02/sol2.py index 9bbd0c535d63..aa8dc7f76f3b 100644 --- a/Project Euler/Problem 02/sol2.py +++ b/Project Euler/Problem 02/sol2.py @@ -9,4 +9,4 @@ def fib(n): ls = [] for _ in range(T): fib(int(input().strip())) -print(*ls, sep = '\n') +print(ls, sep = '\n') diff --git a/Project Euler/Problem 03/sol1.py b/Project Euler/Problem 03/sol1.py index ed83e87d9a5b..bb9f8ca9ad12 100644 --- a/Project Euler/Problem 03/sol1.py +++ b/Project Euler/Problem 03/sol1.py @@ -3,7 +3,7 @@ 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 +from __future__ import print_function, division import math diff --git a/data_structures/Graph/BreadthFirstSearch.py b/data_structures/Graph/BreadthFirstSearch.py index 02f6af83ff66..3992e2d4d892 100644 --- a/data_structures/Graph/BreadthFirstSearch.py +++ b/data_structures/Graph/BreadthFirstSearch.py @@ -1,4 +1,8 @@ -# Author: OMKAR PATHAK +#!/usr/bin/python +# encoding=utf8 + +""" Author: OMKAR PATHAK """ + from __future__ import print_function diff --git a/data_structures/Graph/DepthFirstSearch.py b/data_structures/Graph/DepthFirstSearch.py index 0f10a8600099..98faf61354f9 100644 --- a/data_structures/Graph/DepthFirstSearch.py +++ b/data_structures/Graph/DepthFirstSearch.py @@ -1,4 +1,7 @@ -# Author: OMKAR PATHAK +#!/usr/bin/python +# encoding=utf8 + +""" Author: OMKAR PATHAK """ from __future__ import print_function diff --git a/data_structures/Graph/Graph.py b/data_structures/Graph/Graph.py index d091f713b8d9..9bd61559dcbf 100644 --- a/data_structures/Graph/Graph.py +++ b/data_structures/Graph/Graph.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# encoding=utf8 + from __future__ import print_function # Author: OMKAR PATHAK diff --git a/data_structures/Heap/heap.py b/data_structures/Heap/heap.py index e66d02b6d99f..d0c2400eb6b5 100644 --- a/data_structures/Heap/heap.py +++ b/data_structures/Heap/heap.py @@ -1,6 +1,6 @@ #!/usr/bin/python -from __future__ import print_function +from __future__ import print_function, division try: raw_input # Python 2 diff --git a/dynamic_programming/fastfibonacci.py b/dynamic_programming/fastfibonacci.py index 66d2b2ff0a54..cbc118467b3c 100644 --- a/dynamic_programming/fastfibonacci.py +++ b/dynamic_programming/fastfibonacci.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# encoding=utf8 + """ This program calculates the nth Fibonacci number in O(log(n)). It's possible to calculate F(1000000) in less than a second. diff --git a/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index db6415999bd7..6387d4939205 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -1,7 +1,7 @@ """ Implementation of gradient descent algorithm for minimizing cost of a linear hypothesis function. """ -from __future__ import print_function +from __future__ import print_function, division import numpy # List of input, output pairs diff --git a/other/Fischer-Yates_Shuffle.py b/other/Fischer-Yates_Shuffle.py index 28cdff75ab85..d87792f45558 100644 --- a/other/Fischer-Yates_Shuffle.py +++ b/other/Fischer-Yates_Shuffle.py @@ -1,8 +1,10 @@ -''' +#!/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 wikipedia/Fischer-Yates-Shuffle. -''' +""" import random def FYshuffle(LIST): diff --git a/other/sierpinski_triangle.py b/other/sierpinski_triangle.py index e566f693f63b..6a06058fe03e 100644 --- a/other/sierpinski_triangle.py +++ b/other/sierpinski_triangle.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# encoding=utf8 + '''Author Anurag Kumar | anuragkumarak95@gmail.com | git/anuragkumarak95 Simple example of Fractal generation using recursive function. From 5d1f72604d6e36ffbc83a6b16f79086e360b0546 Mon Sep 17 00:00:00 2001 From: Parth Shandilya Date: Fri, 19 Oct 2018 14:00:31 +0530 Subject: [PATCH 0178/2908] Improved Code and removed Warnings (#483) --- Graphs/basic-graphs.py | 2 +- Graphs/minimum_spanning_tree_kruskal.py | 4 ++-- Graphs/scc_kosaraju.py | 4 ++-- Maths/SieveOfEratosthenes.py | 2 +- Neural_Network/perceptron.py | 2 +- Project Euler/Problem 02/sol3.py | 2 +- Project Euler/Problem 03/sol1.py | 2 +- Project Euler/Problem 03/sol2.py | 2 +- Project Euler/Problem 04/sol1.py | 2 +- Project Euler/Problem 04/sol2.py | 2 +- Project Euler/Problem 05/sol1.py | 2 +- Project Euler/Problem 05/sol2.py | 2 +- Project Euler/Problem 06/sol1.py | 2 +- Project Euler/Problem 06/sol2.py | 2 +- Project Euler/Problem 07/sol1.py | 2 +- Project Euler/Problem 07/sol2.py | 2 +- Project Euler/Problem 08/sol1.py | 2 +- Project Euler/Problem 09/sol2.py | 2 +- Project Euler/Problem 13/sol1.py | 4 ++-- Project Euler/Problem 16/sol1.py | 2 +- Project Euler/Problem 20/sol1.py | 2 +- boolean_algebra/Quine_McCluskey/QuineMcCluskey.py | 4 ++-- ciphers/affine_cipher.py | 6 +++--- ciphers/brute-force_caesar_cipher.py | 2 +- ciphers/caesar_cipher.py | 10 +++++----- ciphers/rsa_cipher.py | 2 +- ciphers/simple_substitution_cipher.py | 4 ++-- ciphers/transposition_cipher.py | 6 +++--- ciphers/transposition_cipher_encrypt-decrypt_file.py | 6 +++--- ciphers/vigenere_cipher.py | 6 +++--- data_structures/Graph/BellmanFord.py | 12 ++++++------ data_structures/Graph/Dijkstra.py | 12 ++++++------ data_structures/Graph/FloydWarshall.py | 10 +++++----- machine_learning/perceptron.py | 2 +- other/nested_brackets.py | 2 +- other/tower_of_hanoi.py | 2 +- 36 files changed, 67 insertions(+), 67 deletions(-) diff --git a/Graphs/basic-graphs.py b/Graphs/basic-graphs.py index 28fa3d180ffd..c9a269f1ab3f 100644 --- a/Graphs/basic-graphs.py +++ b/Graphs/basic-graphs.py @@ -168,7 +168,7 @@ def topo(G, ind=None, Q=[1]): def adjm(): - n, a = input(), [] + n, a = raw_input(), [] for i in xrange(n): a.append(map(int, raw_input().split())) return a, n diff --git a/Graphs/minimum_spanning_tree_kruskal.py b/Graphs/minimum_spanning_tree_kruskal.py index 81d64f421a31..930aab2251c4 100644 --- a/Graphs/minimum_spanning_tree_kruskal.py +++ b/Graphs/minimum_spanning_tree_kruskal.py @@ -1,10 +1,10 @@ from __future__ import print_function -num_nodes, num_edges = list(map(int,input().split())) +num_nodes, num_edges = list(map(int,raw_input().split())) edges = [] for i in range(num_edges): - node1, node2, cost = list(map(int,input().split())) + node1, node2, cost = list(map(int,raw_input().split())) edges.append((i,node1,node2,cost)) edges = sorted(edges, key=lambda edge: edge[3]) diff --git a/Graphs/scc_kosaraju.py b/Graphs/scc_kosaraju.py index 1f13ebaba36b..90d3568fa496 100644 --- a/Graphs/scc_kosaraju.py +++ b/Graphs/scc_kosaraju.py @@ -1,12 +1,12 @@ from __future__ import print_function # n - no of nodes, m - no of edges -n, m = list(map(int,input().split())) +n, m = list(map(int,raw_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())) + u, v = list(map(int,raw_input().split())) g[u].append(v) r[v].append(u) diff --git a/Maths/SieveOfEratosthenes.py b/Maths/SieveOfEratosthenes.py index bac4341deb0e..2b132405ae4d 100644 --- a/Maths/SieveOfEratosthenes.py +++ b/Maths/SieveOfEratosthenes.py @@ -1,5 +1,5 @@ import math -n = int(input("Enter n: ")) +n = int(raw_input("Enter n: ")) def sieve(n): l = [True] * (n+1) diff --git a/Neural_Network/perceptron.py b/Neural_Network/perceptron.py index 44c98e29c52b..16e632f8db4a 100644 --- a/Neural_Network/perceptron.py +++ b/Neural_Network/perceptron.py @@ -120,5 +120,5 @@ def sign(self, u): while True: sample = [] for i in range(3): - sample.insert(i, float(input('value: '))) + sample.insert(i, float(raw_input('value: '))) network.sort(sample) diff --git a/Project Euler/Problem 02/sol3.py b/Project Euler/Problem 02/sol3.py index d36b741bb4f9..ede5e196ba7d 100644 --- a/Project Euler/Problem 02/sol3.py +++ b/Project Euler/Problem 02/sol3.py @@ -7,7 +7,7 @@ e.g. for n=10, we have {2,8}, sum is 10. ''' """Python 3""" -n = int(input()) +n = int(raw_input()) a=0 b=2 count=0 diff --git a/Project Euler/Problem 03/sol1.py b/Project Euler/Problem 03/sol1.py index bb9f8ca9ad12..72aed48b8d90 100644 --- a/Project Euler/Problem 03/sol1.py +++ b/Project Euler/Problem 03/sol1.py @@ -19,7 +19,7 @@ def isprime(no): return True maxNumber = 0 -n=int(input()) +n=int(raw_input()) if(isprime(n)): print(n) else: diff --git a/Project Euler/Problem 03/sol2.py b/Project Euler/Problem 03/sol2.py index af4365b7b18f..eb5a2e9d93b2 100644 --- a/Project Euler/Problem 03/sol2.py +++ b/Project Euler/Problem 03/sol2.py @@ -4,7 +4,7 @@ e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. ''' from __future__ import print_function -n=int(input()) +n=int(raw_input()) prime=1 i=2 while(i*i<=n): diff --git a/Project Euler/Problem 04/sol1.py b/Project Euler/Problem 04/sol1.py index 77135ec7fc1a..27490130c5c8 100644 --- a/Project Euler/Problem 04/sol1.py +++ b/Project Euler/Problem 04/sol1.py @@ -4,7 +4,7 @@ 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? ")) +limit = int(raw_input("limit? ")) # fetchs the next number for number in range(limit-1,10000,-1): diff --git a/Project Euler/Problem 04/sol2.py b/Project Euler/Problem 04/sol2.py index e27e7d30471e..a7e486d38e5a 100644 --- a/Project Euler/Problem 04/sol2.py +++ b/Project Euler/Problem 04/sol2.py @@ -12,7 +12,7 @@ arr.append(i*j) arr.sort() -n=int(input()) +n=int(raw_input()) for i in arr[::-1]: if(i?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" def main(): - message = input('Enter message: ') - key = int(input('Enter key [2000 - 9000]: ')) - mode = input('Encrypt/Decrypt [E/D]: ') + message = raw_input('Enter message: ') + key = int(raw_input('Enter key [2000 - 9000]: ')) + mode = raw_input('Encrypt/Decrypt [E/D]: ') if mode.lower().startswith('e'): mode = 'encrypt' diff --git a/ciphers/brute-force_caesar_cipher.py b/ciphers/brute-force_caesar_cipher.py index 3b0716442fc5..8582249c8d27 100644 --- a/ciphers/brute-force_caesar_cipher.py +++ b/ciphers/brute-force_caesar_cipher.py @@ -44,7 +44,7 @@ def decrypt(message): print("Decryption using Key #%s: %s" % (key, translated)) def main(): - message = input("Encrypted message: ") + message = raw_input("Encrypted message: ") message = message.upper() decrypt(message) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 6cd35e73db0d..19d4478e6b68 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -40,25 +40,25 @@ def main(): print("3.BruteForce") print("4.Quit") while True: - choice = input("What would you like to do?: ") + choice = raw_input("What would you like to do?: ") if choice not in ['1', '2', '3', '4']: print ("Invalid choice") elif choice == '1': - strng = input("Please enter the string to be ecrypted: ") + strng = raw_input("Please enter the string to be ecrypted: ") while True: key = int(input("Please enter off-set between 1-94: ")) if key in range(1, 95): print (encrypt(strng, key)) main() elif choice == '2': - strng = input("Please enter the string to be decrypted: ") + strng = raw_input("Please enter the string to be decrypted: ") while True: - key = int(input("Please enter off-set between 1-94: ")) + key = raw_int(input("Please enter off-set between 1-94: ")) if key > 0 and key <= 94: print(decrypt(strng, key)) main() elif choice == '3': - strng = input("Please enter the string to be decrypted: ") + strng = raw_input("Please enter the string to be decrypted: ") brute_force(strng) main() elif choice == '4': diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 94f69ddc2533..ab8329428e95 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -6,7 +6,7 @@ def main(): filename = 'encrypted_file.txt' - response = input('Encrypte\Decrypt [e\d]: ') + response = raw_input('Encrypte\Decrypt [e\d]: ') if response.lower().startswith('e'): mode = 'encrypt' diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index 1bdd7dc04a57..fe013cd58918 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -4,9 +4,9 @@ LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' def main(): - message = input('Enter message: ') + message = raw_input('Enter message: ') key = 'LFWOAYUISVKMNXPBDCRJTQEGHZ' - resp = input('Encrypt/Decrypt [e/d]: ') + resp = raw_input('Encrypt/Decrypt [e/d]: ') checkValidKey(key) diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index dbb358315d22..07f58c632639 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -2,9 +2,9 @@ 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 = raw_input('Enter message: ') + key = int(raw_input('Enter key [2-%s]: ' % (len(message) - 1))) + mode = raw_input('Encryption/Decryption [e/d]: ') if mode.lower().startswith('e'): text = encryptMessage(key, message) diff --git a/ciphers/transposition_cipher_encrypt-decrypt_file.py b/ciphers/transposition_cipher_encrypt-decrypt_file.py index 57620d83948c..5c3eaaca6e27 100644 --- a/ciphers/transposition_cipher_encrypt-decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt-decrypt_file.py @@ -5,15 +5,15 @@ def main(): inputFile = 'Prehistoric Men.txt' outputFile = 'Output.txt' - key = int(input('Enter key: ')) - mode = input('Encrypt/Decrypt [e/d]: ') + key = int(raw_input('Enter key: ')) + mode = raw_input('Encrypt/Decrypt [e/d]: ') if not os.path.exists(inputFile): print('File %s does not exist. Quitting...' % inputFile) sys.exit() if os.path.exists(outputFile): print('Overwrite %s? [y/n]' % outputFile) - response = input('> ') + response = raw_input('> ') if not response.lower().startswith('y'): sys.exit() diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index 5d5be0792835..34f49c3c018f 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -2,9 +2,9 @@ LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' def main(): - message = input('Enter message: ') - key = input('Enter key [alphanumeric]: ') - mode = input('Encrypt/Decrypt [e/d]: ') + message = raw_input('Enter message: ') + key = raw_input('Enter key [alphanumeric]: ') + mode = raw_input('Encrypt/Decrypt [e/d]: ') if mode.lower().startswith('e'): mode = 'encrypt' diff --git a/data_structures/Graph/BellmanFord.py b/data_structures/Graph/BellmanFord.py index 82db80546b94..f5e1ac9836cb 100644 --- a/data_structures/Graph/BellmanFord.py +++ b/data_structures/Graph/BellmanFord.py @@ -35,8 +35,8 @@ def BellmanFord(graph, V, E, src): #MAIN -V = int(input("Enter number of vertices: ")) -E = int(input("Enter number of edges: ")) +V = int(raw_input("Enter number of vertices: ")) +E = int(raw_input("Enter number of edges: ")) graph = [dict() for j in range(E)] @@ -45,10 +45,10 @@ def BellmanFord(graph, V, E, src): for i in range(E): print("\nEdge ",i+1) - src = int(input("Enter source:")) - dst = int(input("Enter destination:")) - weight = float(input("Enter weight:")) + src = int(raw_input("Enter source:")) + dst = int(raw_input("Enter destination:")) + weight = float(raw_input("Enter weight:")) graph[i] = {"src": src,"dst": dst, "weight": weight} -gsrc = int(input("\nEnter shortest path source:")) +gsrc = int(raw_input("\nEnter shortest path source:")) BellmanFord(graph, V, E, gsrc) diff --git a/data_structures/Graph/Dijkstra.py b/data_structures/Graph/Dijkstra.py index 8917171417c4..2580dd2f00fc 100644 --- a/data_structures/Graph/Dijkstra.py +++ b/data_structures/Graph/Dijkstra.py @@ -38,8 +38,8 @@ def Dijkstra(graph, V, src): #MAIN -V = int(input("Enter number of vertices: ")) -E = int(input("Enter number of edges: ")) +V = int(raw_input("Enter number of vertices: ")) +E = int(raw_input("Enter number of edges: ")) graph = [[float('inf') for i in range(V)] for j in range(V)] @@ -48,10 +48,10 @@ def Dijkstra(graph, V, src): for i in range(E): print("\nEdge ",i+1) - src = int(input("Enter source:")) - dst = int(input("Enter destination:")) - weight = float(input("Enter weight:")) + src = int(raw_input("Enter source:")) + dst = int(raw_input("Enter destination:")) + weight = float(raw_input("Enter weight:")) graph[src][dst] = weight -gsrc = int(input("\nEnter shortest path source:")) +gsrc = int(raw_input("\nEnter shortest path source:")) Dijkstra(graph, V, gsrc) diff --git a/data_structures/Graph/FloydWarshall.py b/data_structures/Graph/FloydWarshall.py index fae8b19b351a..64d41c2f88e1 100644 --- a/data_structures/Graph/FloydWarshall.py +++ b/data_structures/Graph/FloydWarshall.py @@ -30,8 +30,8 @@ def FloydWarshall(graph, V): #MAIN -V = int(input("Enter number of vertices: ")) -E = int(input("Enter number of edges: ")) +V = int(raw_input("Enter number of vertices: ")) +E = int(raw_input("Enter number of edges: ")) graph = [[float('inf') for i in range(V)] for j in range(V)] @@ -40,9 +40,9 @@ def FloydWarshall(graph, V): for i in range(E): print("\nEdge ",i+1) - src = int(input("Enter source:")) - dst = int(input("Enter destination:")) - weight = float(input("Enter weight:")) + src = int(raw_input("Enter source:")) + dst = int(raw_input("Enter destination:")) + weight = float(raw_input("Enter weight:")) graph[src][dst] = weight FloydWarshall(graph, V) diff --git a/machine_learning/perceptron.py b/machine_learning/perceptron.py index 8ac3e8fc69e9..86556f2e5485 100644 --- a/machine_learning/perceptron.py +++ b/machine_learning/perceptron.py @@ -120,5 +120,5 @@ def sign(self, u): while True: sample = [] for i in range(3): - sample.insert(i, float(input('value: '))) + sample.insert(i, float(raw_input('value: '))) network.sort(sample) \ No newline at end of file diff --git a/other/nested_brackets.py b/other/nested_brackets.py index 76677d56439a..17ca011610b9 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -37,7 +37,7 @@ def is_balanced(S): def main(): - S = input("Enter sequence of brackets: ") + S = raw_input("Enter sequence of brackets: ") if is_balanced(S): print((S, "is balanced")) diff --git a/other/tower_of_hanoi.py b/other/tower_of_hanoi.py index dc15b2ce8e58..92c33156438c 100644 --- a/other/tower_of_hanoi.py +++ b/other/tower_of_hanoi.py @@ -19,7 +19,7 @@ def moveDisk(fp,tp): print(('moving disk from', fp, 'to', tp)) def main(): - height = int(input('Height of hanoi: ')) + height = int(raw_input('Height of hanoi: ')) moveTower(height, 'A', 'B', 'C') if __name__ == '__main__': From af12df9fe8e18834e1c5cd28b4fad04a44c7d57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Ng=E1=BB=8Dc=20Quang=20Nam?= Date: Fri, 19 Oct 2018 18:22:11 +0700 Subject: [PATCH 0179/2908] Update image url (#484) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f0c40802b1d1..254ce511dfe4 100644 --- a/README.md +++ b/README.md @@ -290,6 +290,6 @@ where {\displaystyle \oplus } \oplus denotes the exclusive disjunction (XOR) op [caesar]: https://upload.wikimedia.org/wikipedia/commons/4/4a/Caesar_cipher_left_shift_of_3.svg -[ROT13-image]: https://en.wikipedia.org/wiki/File:ROT13_table_with_example.svg +[ROT13-image]: https://upload.wikimedia.org/wikipedia/commons/3/33/ROT13_table_with_example.svg -[QuickSelect-image]: https://en.wikipedia.org/wiki/File:Selecting_quickselect_frames.gif +[QuickSelect-image]: https://upload.wikimedia.org/wikipedia/commons/0/04/Selecting_quickselect_frames.gif From 315a357c1b30ed3797cdca98f2a61a893bc3ccd5 Mon Sep 17 00:00:00 2001 From: Hussnain Fareed <14870938+hsfareed@users.noreply.github.com> Date: Fri, 19 Oct 2018 16:29:26 +0500 Subject: [PATCH 0180/2908] fully connected neural net (#479) Fully connected Neural Net using keras on Mnist --- Neural_Network/FCN.ipynb | 327 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 Neural_Network/FCN.ipynb diff --git a/Neural_Network/FCN.ipynb b/Neural_Network/FCN.ipynb new file mode 100644 index 000000000000..a8bcf4beeea1 --- /dev/null +++ b/Neural_Network/FCN.ipynb @@ -0,0 +1,327 @@ +{ + "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": "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\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 90979777c7fac1f375af49dd018d4221767f6a6c Mon Sep 17 00:00:00 2001 From: Vasu Tomar Date: Fri, 19 Oct 2018 17:26:08 +0530 Subject: [PATCH 0181/2908] Added PSNR calculator for image compression quality analysis (#475) * Added PSNR calculator for image compression quality analysis * Removed unused variables and redundancy --- Analysis/Compression_Analysis/PSNR.py | 38 ++++++++++++++++++ .../Compression_Analysis/compressed_image.png | Bin 0 -> 26684 bytes .../Compression_Analysis/example_image.jpg | Bin 0 -> 29986 bytes .../Compression_Analysis/orignal_image.png | Bin 0 -> 83865 bytes 4 files changed, 38 insertions(+) create mode 100644 Analysis/Compression_Analysis/PSNR.py create mode 100644 Analysis/Compression_Analysis/compressed_image.png create mode 100644 Analysis/Compression_Analysis/example_image.jpg create mode 100644 Analysis/Compression_Analysis/orignal_image.png diff --git a/Analysis/Compression_Analysis/PSNR.py b/Analysis/Compression_Analysis/PSNR.py new file mode 100644 index 000000000000..1585c8cfc962 --- /dev/null +++ b/Analysis/Compression_Analysis/PSNR.py @@ -0,0 +1,38 @@ +import numpy as np +import math +import cv2 + +def Representational(r,g,b): + return (0.299*r+0.287*g+0.114*b) + +def calculate(img): + b,g,r = cv2.split(img) + pixelAt = Representational(r,g,b) + return pixelAt + +def main(): + + #Loading images (orignal image and compressed image) + orignal_image = cv2.imread('orignal_image.png',1) + compressed_image = cv2.imread('compressed_image.png',1) + + #Getting image height and width + height,width = orignal_image.shape[:2] + + orignalPixelAt = calculate(orignal_image) + compressedPixelAt = calculate(compressed_image) + + diff = orignalPixelAt - compressedPixelAt + error = np.sum(np.abs(diff) ** 2) + + error = error/(height*width) + + #MSR = error_sum/(height*width) + PSNR = -(10*math.log10(error/(255*255))) + + print("PSNR value is {}".format(PSNR)) + + +if __name__ == '__main__': + main() + diff --git a/Analysis/Compression_Analysis/compressed_image.png b/Analysis/Compression_Analysis/compressed_image.png new file mode 100644 index 0000000000000000000000000000000000000000..75c41c21c7b579b88cedfbaee9ecaeb919db5683 GIT binary patch literal 26684 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Ð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 XP;3!Jj7l4S00000NkvXXu0mjfCMpBg literal 0 HcmV?d00001 diff --git a/Analysis/Compression_Analysis/example_image.jpg b/Analysis/Compression_Analysis/example_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a58ba3a8c338e08940c4494b4edd799294323e3 GIT binary patch literal 29986 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 z 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�Sb5QGN=^8)xhfJ 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 literal 0 HcmV?d00001 From 718b99ae397e4578455845fe52595b306f70ac58 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 19 Oct 2018 07:48:01 -0500 Subject: [PATCH 0182/2908] increment 1 --- Analysis/Compression_Analysis/PSNR.py | 38 - .../Compression_Analysis/compressed_image.png | Bin 26684 -> 0 bytes .../Compression_Analysis/example_image.jpg | Bin 29986 -> 0 bytes .../Compression_Analysis/orignal_image.png | Bin 83865 -> 0 bytes ArithmeticAnalysis/Bisection.py | 33 - ArithmeticAnalysis/Intersection.py | 16 - ArithmeticAnalysis/LUdecomposition.py | 34 - ArithmeticAnalysis/NewtonMethod.py | 15 - ArithmeticAnalysis/NewtonRaphsonMethod.py | 36 - File_Transfer_Protocol/ftp_client_server.py | 58 - File_Transfer_Protocol/ftp_send_receive.py | 36 - Graphs/ArticulationPoints.py | 44 - Graphs/CheckBipartiteGraph_BFS.py | 43 - Graphs/FindingBridges.py | 31 - Graphs/KahnsAlgorithm_long.py | 30 - Graphs/KahnsAlgorithm_topo.py | 32 - Graphs/MinimumSpanningTree_Prims.py | 111 - Graphs/Multi_Hueristic_Astar.py | 266 - Graphs/a_star.py | 102 - Graphs/basic-graphs.py | 281 - Graphs/minimum_spanning_tree_kruskal.py | 32 - Graphs/scc_kosaraju.py | 46 - Graphs/tarjans_scc.py | 78 - Maths/BasicMaths.py | 74 - Maths/FibonacciSequenceRecursion.py | 18 - Maths/GreaterCommonDivisor.py | 15 - Maths/ModularExponential.py | 20 - Maths/SegmentedSieve.py | 46 - Maths/SieveOfEratosthenes.py | 24 - Maths/SimpsonRule.py | 49 - Maths/TrapezoidalRule.py | 46 - Neural_Network/FCN.ipynb | 327 - Neural_Network/bpnn.py | 193 - Neural_Network/convolution_neural_network.py | 306 - Neural_Network/perceptron.py | 124 - Project Euler/Problem 01/sol1.py | 17 - Project Euler/Problem 01/sol2.py | 20 - Project Euler/Problem 01/sol3.py | 50 - Project Euler/Problem 01/sol4.py | 30 - Project Euler/Problem 02/sol1.py | 26 - Project Euler/Problem 02/sol2.py | 12 - Project Euler/Problem 02/sol3.py | 20 - Project Euler/Problem 03/sol1.py | 39 - Project Euler/Problem 03/sol2.py | 17 - Project Euler/Problem 04/sol1.py | 29 - Project Euler/Problem 04/sol2.py | 19 - Project Euler/Problem 05/sol1.py | 21 - Project Euler/Problem 05/sol2.py | 20 - Project Euler/Problem 06/sol1.py | 20 - Project Euler/Problem 06/sol2.py | 16 - Project Euler/Problem 07/sol1.py | 30 - Project Euler/Problem 07/sol2.py | 16 - Project Euler/Problem 08/sol1.py | 15 - Project Euler/Problem 09/sol1.py | 15 - Project Euler/Problem 09/sol2.py | 18 - Project Euler/Problem 10/sol1.py | 38 - Project Euler/Problem 11/grid.txt | 20 - Project Euler/Problem 11/sol1.py | 68 - Project Euler/Problem 11/sol2.py | 39 - Project Euler/Problem 12/sol1.py | 46 - Project Euler/Problem 13/sol1.py | 14 - Project Euler/Problem 14/sol1.py | 21 - Project Euler/Problem 15/sol1.py | 20 - Project Euler/Problem 16/sol1.py | 15 - Project Euler/Problem 17/sol1.py | 35 - Project Euler/Problem 19/sol1.py | 51 - Project Euler/Problem 20/sol1.py | 27 - Project Euler/Problem 20/sol2.py | 5 - Project Euler/Problem 21/sol1.py | 42 - Project Euler/Problem 22/p022_names.txt | 1 - Project Euler/Problem 22/sol1.py | 37 - Project Euler/Problem 22/sol2.py | 533 -- Project Euler/Problem 24/sol1.py | 7 - Project Euler/Problem 25/sol1.py | 31 - Project Euler/Problem 28/sol1.py | 29 - Project Euler/Problem 29/solution.py | 33 - Project Euler/Problem 36/sol1.py | 30 - Project Euler/Problem 40/sol1.py | 26 - Project Euler/Problem 48/sol1.py | 21 - Project Euler/Problem 52/sol1.py | 23 - Project Euler/Problem 53/sol1.py | 36 - Project Euler/Problem 76/sol1.py | 35 - Project Euler/README.md | 58 - .../Quine_McCluskey/QuineMcCluskey.py | 116 - ciphers/Onepad_Cipher.py | 32 - ciphers/Prehistoric Men.txt | 7193 ----------------- ciphers/XOR_cipher.py | 209 - ciphers/brute-force_caesar_cipher.py | 54 - ...ansposition_cipher_encrypt-decrypt_file.py | 36 - data_structures/AVL/AVL.py | 181 - data_structures/Arrays.py | 3 - data_structures/Binary Tree/FenwickTree.py | 29 - .../Binary Tree/LazySegmentTree.py | 91 - data_structures/Binary Tree/SegmentTree.py | 71 - .../Binary Tree/binary_search_tree.py | 258 - data_structures/Graph/BellmanFord.py | 54 - data_structures/Graph/BreadthFirstSearch.py | 67 - data_structures/Graph/DepthFirstSearch.py | 66 - data_structures/Graph/Dijkstra.py | 57 - data_structures/Graph/FloydWarshall.py | 48 - data_structures/Graph/Graph.py | 44 - data_structures/Graph/Graph_list.py | 31 - data_structures/Graph/Graph_matrix.py | 32 - data_structures/Graph/dijkstra_algorithm.py | 212 - data_structures/Graph/even_tree.py | 70 - data_structures/Heap/heap.py | 90 - .../LinkedList/DoublyLinkedList.py | 76 - data_structures/LinkedList/__init__.py | 22 - .../LinkedList/singly_LinkedList.py | 70 - data_structures/Queue/DeQueue.py | 40 - data_structures/Queue/QueueOnList.py | 45 - data_structures/Queue/QueueOnPseudoStack.py | 50 - data_structures/Queue/__init__.py | 0 data_structures/Stacks/Stock-Span-Problem.py | 52 - data_structures/Stacks/__init__.py | 23 - .../Stacks/balanced_parentheses.py | 23 - .../Stacks/infix_to_postfix_conversion.py | 64 - data_structures/Stacks/next.py | 17 - data_structures/Stacks/stack.py | 69 - data_structures/Trie/Trie.py | 75 - data_structures/UnionFind/__init__.py | 0 data_structures/UnionFind/tests_union_find.py | 78 - data_structures/UnionFind/union_find.py | 87 - dynamic_programming/FloydWarshall.py | 37 - linear-algebra-python/README.md | 74 - linear-algebra-python/src/lib.py | 364 - linear-algebra-python/src/tests.py | 133 - simple-client-server/README.md | 6 - simple-client-server/client.py | 14 - simple-client-server/server.py | 21 - 130 files changed, 14879 deletions(-) delete mode 100644 Analysis/Compression_Analysis/PSNR.py delete mode 100644 Analysis/Compression_Analysis/compressed_image.png delete mode 100644 Analysis/Compression_Analysis/example_image.jpg delete mode 100644 Analysis/Compression_Analysis/orignal_image.png delete mode 100644 ArithmeticAnalysis/Bisection.py delete mode 100644 ArithmeticAnalysis/Intersection.py delete mode 100644 ArithmeticAnalysis/LUdecomposition.py delete mode 100644 ArithmeticAnalysis/NewtonMethod.py delete mode 100644 ArithmeticAnalysis/NewtonRaphsonMethod.py delete mode 100644 File_Transfer_Protocol/ftp_client_server.py delete mode 100644 File_Transfer_Protocol/ftp_send_receive.py delete mode 100644 Graphs/ArticulationPoints.py delete mode 100644 Graphs/CheckBipartiteGraph_BFS.py delete mode 100644 Graphs/FindingBridges.py delete mode 100644 Graphs/KahnsAlgorithm_long.py delete mode 100644 Graphs/KahnsAlgorithm_topo.py delete mode 100644 Graphs/MinimumSpanningTree_Prims.py delete mode 100644 Graphs/Multi_Hueristic_Astar.py delete mode 100644 Graphs/a_star.py delete mode 100644 Graphs/basic-graphs.py delete mode 100644 Graphs/minimum_spanning_tree_kruskal.py delete mode 100644 Graphs/scc_kosaraju.py delete mode 100644 Graphs/tarjans_scc.py delete mode 100644 Maths/BasicMaths.py delete mode 100644 Maths/FibonacciSequenceRecursion.py delete mode 100644 Maths/GreaterCommonDivisor.py delete mode 100644 Maths/ModularExponential.py delete mode 100644 Maths/SegmentedSieve.py delete mode 100644 Maths/SieveOfEratosthenes.py delete mode 100644 Maths/SimpsonRule.py delete mode 100644 Maths/TrapezoidalRule.py delete mode 100644 Neural_Network/FCN.ipynb delete mode 100644 Neural_Network/bpnn.py delete mode 100644 Neural_Network/convolution_neural_network.py delete mode 100644 Neural_Network/perceptron.py delete mode 100644 Project Euler/Problem 01/sol1.py delete mode 100644 Project Euler/Problem 01/sol2.py delete mode 100644 Project Euler/Problem 01/sol3.py delete mode 100644 Project Euler/Problem 01/sol4.py delete mode 100644 Project Euler/Problem 02/sol1.py delete mode 100644 Project Euler/Problem 02/sol2.py delete mode 100644 Project Euler/Problem 02/sol3.py delete mode 100644 Project Euler/Problem 03/sol1.py delete mode 100644 Project Euler/Problem 03/sol2.py delete mode 100644 Project Euler/Problem 04/sol1.py delete mode 100644 Project Euler/Problem 04/sol2.py delete mode 100644 Project Euler/Problem 05/sol1.py delete mode 100644 Project Euler/Problem 05/sol2.py delete mode 100644 Project Euler/Problem 06/sol1.py delete mode 100644 Project Euler/Problem 06/sol2.py delete mode 100644 Project Euler/Problem 07/sol1.py delete mode 100644 Project Euler/Problem 07/sol2.py delete mode 100644 Project Euler/Problem 08/sol1.py delete mode 100644 Project Euler/Problem 09/sol1.py delete mode 100644 Project Euler/Problem 09/sol2.py delete mode 100644 Project Euler/Problem 10/sol1.py delete mode 100644 Project Euler/Problem 11/grid.txt delete mode 100644 Project Euler/Problem 11/sol1.py delete mode 100644 Project Euler/Problem 11/sol2.py delete mode 100644 Project Euler/Problem 12/sol1.py delete mode 100644 Project Euler/Problem 13/sol1.py delete mode 100644 Project Euler/Problem 14/sol1.py delete mode 100644 Project Euler/Problem 15/sol1.py delete mode 100644 Project Euler/Problem 16/sol1.py delete mode 100644 Project Euler/Problem 17/sol1.py delete mode 100644 Project Euler/Problem 19/sol1.py delete mode 100644 Project Euler/Problem 20/sol1.py delete mode 100644 Project Euler/Problem 20/sol2.py delete mode 100644 Project Euler/Problem 21/sol1.py delete mode 100644 Project Euler/Problem 22/p022_names.txt delete mode 100644 Project Euler/Problem 22/sol1.py delete mode 100644 Project Euler/Problem 22/sol2.py delete mode 100644 Project Euler/Problem 24/sol1.py delete mode 100644 Project Euler/Problem 25/sol1.py delete mode 100644 Project Euler/Problem 28/sol1.py delete mode 100644 Project Euler/Problem 29/solution.py delete mode 100644 Project Euler/Problem 36/sol1.py delete mode 100644 Project Euler/Problem 40/sol1.py delete mode 100644 Project Euler/Problem 48/sol1.py delete mode 100644 Project Euler/Problem 52/sol1.py delete mode 100644 Project Euler/Problem 53/sol1.py delete mode 100644 Project Euler/Problem 76/sol1.py delete mode 100644 Project Euler/README.md delete mode 100644 boolean_algebra/Quine_McCluskey/QuineMcCluskey.py delete mode 100644 ciphers/Onepad_Cipher.py delete mode 100644 ciphers/Prehistoric Men.txt delete mode 100644 ciphers/XOR_cipher.py delete mode 100644 ciphers/brute-force_caesar_cipher.py delete mode 100644 ciphers/transposition_cipher_encrypt-decrypt_file.py delete mode 100644 data_structures/AVL/AVL.py delete mode 100644 data_structures/Arrays.py delete mode 100644 data_structures/Binary Tree/FenwickTree.py delete mode 100644 data_structures/Binary Tree/LazySegmentTree.py delete mode 100644 data_structures/Binary Tree/SegmentTree.py delete mode 100644 data_structures/Binary Tree/binary_search_tree.py delete mode 100644 data_structures/Graph/BellmanFord.py delete mode 100644 data_structures/Graph/BreadthFirstSearch.py delete mode 100644 data_structures/Graph/DepthFirstSearch.py delete mode 100644 data_structures/Graph/Dijkstra.py delete mode 100644 data_structures/Graph/FloydWarshall.py delete mode 100644 data_structures/Graph/Graph.py delete mode 100644 data_structures/Graph/Graph_list.py delete mode 100644 data_structures/Graph/Graph_matrix.py delete mode 100644 data_structures/Graph/dijkstra_algorithm.py delete mode 100644 data_structures/Graph/even_tree.py delete mode 100644 data_structures/Heap/heap.py delete mode 100644 data_structures/LinkedList/DoublyLinkedList.py delete mode 100644 data_structures/LinkedList/__init__.py delete mode 100644 data_structures/LinkedList/singly_LinkedList.py delete mode 100644 data_structures/Queue/DeQueue.py delete mode 100644 data_structures/Queue/QueueOnList.py delete mode 100644 data_structures/Queue/QueueOnPseudoStack.py delete mode 100644 data_structures/Queue/__init__.py delete mode 100644 data_structures/Stacks/Stock-Span-Problem.py delete mode 100644 data_structures/Stacks/__init__.py delete mode 100644 data_structures/Stacks/balanced_parentheses.py delete mode 100644 data_structures/Stacks/infix_to_postfix_conversion.py delete mode 100644 data_structures/Stacks/next.py delete mode 100644 data_structures/Stacks/stack.py delete mode 100644 data_structures/Trie/Trie.py delete mode 100644 data_structures/UnionFind/__init__.py delete mode 100644 data_structures/UnionFind/tests_union_find.py delete mode 100644 data_structures/UnionFind/union_find.py delete mode 100644 dynamic_programming/FloydWarshall.py delete mode 100644 linear-algebra-python/README.md delete mode 100644 linear-algebra-python/src/lib.py delete mode 100644 linear-algebra-python/src/tests.py delete mode 100644 simple-client-server/README.md delete mode 100644 simple-client-server/client.py delete mode 100644 simple-client-server/server.py diff --git a/Analysis/Compression_Analysis/PSNR.py b/Analysis/Compression_Analysis/PSNR.py deleted file mode 100644 index 1585c8cfc962..000000000000 --- a/Analysis/Compression_Analysis/PSNR.py +++ /dev/null @@ -1,38 +0,0 @@ -import numpy as np -import math -import cv2 - -def Representational(r,g,b): - return (0.299*r+0.287*g+0.114*b) - -def calculate(img): - b,g,r = cv2.split(img) - pixelAt = Representational(r,g,b) - return pixelAt - -def main(): - - #Loading images (orignal image and compressed image) - orignal_image = cv2.imread('orignal_image.png',1) - compressed_image = cv2.imread('compressed_image.png',1) - - #Getting image height and width - height,width = orignal_image.shape[:2] - - orignalPixelAt = calculate(orignal_image) - compressedPixelAt = calculate(compressed_image) - - diff = orignalPixelAt - compressedPixelAt - error = np.sum(np.abs(diff) ** 2) - - error = error/(height*width) - - #MSR = error_sum/(height*width) - PSNR = -(10*math.log10(error/(255*255))) - - print("PSNR value is {}".format(PSNR)) - - -if __name__ == '__main__': - main() - diff --git a/Analysis/Compression_Analysis/compressed_image.png b/Analysis/Compression_Analysis/compressed_image.png deleted file mode 100644 index 75c41c21c7b579b88cedfbaee9ecaeb919db5683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26684 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Ð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 XP;3!Jj7l4S00000NkvXXu0mjfCMpBg diff --git a/Analysis/Compression_Analysis/example_image.jpg b/Analysis/Compression_Analysis/example_image.jpg deleted file mode 100644 index 3a58ba3a8c338e08940c4494b4edd799294323e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29986 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 z 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�Sb5QGN=^8)xhfJ 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 diff --git a/ArithmeticAnalysis/Bisection.py b/ArithmeticAnalysis/Bisection.py deleted file mode 100644 index a6af547db03b..000000000000 --- a/ArithmeticAnalysis/Bisection.py +++ /dev/null @@ -1,33 +0,0 @@ -import math - - -def bisection(function, a, b): # finds where the function becomes 0 in [a,b] using bolzano - - start = a - end = b - if function(a) == 0: # one of the a or b is a root for the function - 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, - # then his algorithm can't find the root - print("couldn't find root in [a,b]") - return - else: - mid = (start + end) / 2 - while abs(start - mid) > 0.0000001: # until we achieve precise equals to 10^-7 - if function(mid) == 0: - return mid - elif function(mid) * function(start) < 0: - end = mid - else: - start = mid - mid = (start + end) / 2 - return mid - - -def f(x): - return math.pow(x, 3) - 2*x - 5 - - -print(bisection(f, 1, 1000)) diff --git a/ArithmeticAnalysis/Intersection.py b/ArithmeticAnalysis/Intersection.py deleted file mode 100644 index 22c50f2ecafd..000000000000 --- a/ArithmeticAnalysis/Intersection.py +++ /dev/null @@ -1,16 +0,0 @@ -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 - 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)<0.00001 : - return x_n2 - x_n=x_n1 - x_n1=x_n2 - -def f(x): - return math.pow(x,3)-2*x-5 - -print(intersection(f,3,3.5)) diff --git a/ArithmeticAnalysis/LUdecomposition.py b/ArithmeticAnalysis/LUdecomposition.py deleted file mode 100644 index da05fb65d0ea..000000000000 --- a/ArithmeticAnalysis/LUdecomposition.py +++ /dev/null @@ -1,34 +0,0 @@ -import numpy - -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: - 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 - - - - - - - -matrix =numpy.array([[2,-2,1],[0,1,2],[5,3,1]]) -L,U = LUDecompose(matrix) -print(L) -print(U) diff --git a/ArithmeticAnalysis/NewtonMethod.py b/ArithmeticAnalysis/NewtonMethod.py deleted file mode 100644 index c3d5efb47d01..000000000000 --- a/ArithmeticAnalysis/NewtonMethod.py +++ /dev/null @@ -1,15 +0,0 @@ -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)<0.00001: - return x_n1 - x_n=x_n1 - -def f(x): - return (x**3)-2*x-5 - -def f1(x): - return 3*(x**2)-2 - -print(newton(f,f1,3)) diff --git a/ArithmeticAnalysis/NewtonRaphsonMethod.py b/ArithmeticAnalysis/NewtonRaphsonMethod.py deleted file mode 100644 index 5e7e2f930abc..000000000000 --- a/ArithmeticAnalysis/NewtonRaphsonMethod.py +++ /dev/null @@ -1,36 +0,0 @@ -# Implementing Newton Raphson method in Python -# Author: Haseeb - -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)) - - - - diff --git a/File_Transfer_Protocol/ftp_client_server.py b/File_Transfer_Protocol/ftp_client_server.py deleted file mode 100644 index f73f858431f3..000000000000 --- a/File_Transfer_Protocol/ftp_client_server.py +++ /dev/null @@ -1,58 +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' - f = open(filename, 'rb') - in_data = f.read(1024) - while (in_data): - conn.send(in_data) - print('Sent ', repr(in_data)) - in_data = f.read(1024) - f.close() - - 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/ftp_send_receive.py b/File_Transfer_Protocol/ftp_send_receive.py deleted file mode 100644 index d4919158a02e..000000000000 --- a/File_Transfer_Protocol/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 """ - LocalFile = open(FileName, 'wb') - ftp.retrbinary('RETR ' + FileName, LocalFile.write, 1024) - ftp.quit() - LocalFile.close() - -""" - 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 """ - ftp.storbinary('STOR ' + FileName, open(FileName, 'rb')) - ftp.quit() diff --git a/Graphs/ArticulationPoints.py b/Graphs/ArticulationPoints.py deleted file mode 100644 index 1173c4ea373c..000000000000 --- a/Graphs/ArticulationPoints.py +++ /dev/null @@ -1,44 +0,0 @@ -# Finding Articulation Points in Undirected Graph -def computeAP(l): - n = len(l) - outEdgeCount = 0 - low = [0] * n - visited = [False] * n - isArt = [False] * n - - def dfs(root, at, parent, outEdgeCount): - if parent == root: - outEdgeCount += 1 - visited[at] = True - low[at] = at - - for to in l[at]: - if to == parent: - pass - elif not visited[to]: - outEdgeCount = dfs(root, to, at, outEdgeCount) - low[at] = min(low[at], low[to]) - - # AP found via bridge - if at < low[to]: - isArt[at] = True - # AP found via cycle - if at == low[to]: - isArt[at] = True - else: - low[at] = min(low[at], to) - return outEdgeCount - - for i in range(n): - if not visited[i]: - outEdgeCount = 0 - outEdgeCount = dfs(i, i, -1, outEdgeCount) - 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]} -computeAP(l) diff --git a/Graphs/CheckBipartiteGraph_BFS.py b/Graphs/CheckBipartiteGraph_BFS.py deleted file mode 100644 index 1b9c32c6ccc4..000000000000 --- a/Graphs/CheckBipartiteGraph_BFS.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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): - queue = [] - visited = [False] * len(l) - color = [-1] * len(l) - - def bfs(): - while(queue): - u = queue.pop(0) - visited[u] = True - - for neighbour in l[u]: - - if neighbour == u: - return False - - if color[neighbour] == -1: - color[neighbour] = 1 - color[u] - queue.append(neighbour) - - elif color[neighbour] == color[u]: - return False - - return True - - for i in range(len(l)): - if not visited[i]: - queue.append(i) - color[i] = 0 - if bfs() == 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)) diff --git a/Graphs/FindingBridges.py b/Graphs/FindingBridges.py deleted file mode 100644 index 56533dd48bde..000000000000 --- a/Graphs/FindingBridges.py +++ /dev/null @@ -1,31 +0,0 @@ -# Finding Bridges in Undirected Graph -def computeBridges(l): - id = 0 - n = len(l) # No of vertices in graph - low = [0] * n - visited = [False] * n - - def dfs(at, parent, bridges, id): - visited[at] = True - low[at] = id - id += 1 - for to in l[at]: - if to == parent: - pass - elif not visited[to]: - dfs(to, at, bridges, id) - low[at] = min(low[at], low[to]) - if at < low[to]: - bridges.append([at, to]) - else: - # This edge is a back edge and cannot be a bridge - low[at] = min(low[at], to) - - bridges = [] - for i in range(n): - 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]} -computeBridges(l) diff --git a/Graphs/KahnsAlgorithm_long.py b/Graphs/KahnsAlgorithm_long.py deleted file mode 100644 index 453b5706f6da..000000000000 --- a/Graphs/KahnsAlgorithm_long.py +++ /dev/null @@ -1,30 +0,0 @@ -# Finding longest distance in Directed Acyclic Graph using KahnsAlgorithm -def longestDistance(l): - indegree = [0] * len(l) - queue = [] - longDist = [1] * len(l) - - for key, values in l.items(): - for i in values: - indegree[i] += 1 - - for i in range(len(indegree)): - if indegree[i] == 0: - queue.append(i) - - 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 - - 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:[]} -longestDistance(l) diff --git a/Graphs/KahnsAlgorithm_topo.py b/Graphs/KahnsAlgorithm_topo.py deleted file mode 100644 index 8c182c4e902c..000000000000 --- a/Graphs/KahnsAlgorithm_topo.py +++ /dev/null @@ -1,32 +0,0 @@ -# Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph using BFS -def topologicalSort(l): - indegree = [0] * len(l) - queue = [] - topo = [] - cnt = 0 - - for key, values in l.items(): - for i in values: - indegree[i] += 1 - - for i in range(len(indegree)): - if indegree[i] == 0: - queue.append(i) - - while(queue): - vertex = queue.pop(0) - cnt += 1 - topo.append(vertex) - for x in l[vertex]: - indegree[x] -= 1 - if indegree[x] == 0: - queue.append(x) - - if cnt != len(l): - 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) diff --git a/Graphs/MinimumSpanningTree_Prims.py b/Graphs/MinimumSpanningTree_Prims.py deleted file mode 100644 index 569e73e0ab7b..000000000000 --- a/Graphs/MinimumSpanningTree_Prims.py +++ /dev/null @@ -1,111 +0,0 @@ -import sys -from collections import defaultdict - -def PrimsAlgorithm(l): - - nodePosition = [] - def getPosition(vertex): - return nodePosition[vertex] - - def setPosition(vertex, pos): - nodePosition[vertex] = pos - - def topToBottom(heap, start, size, positions): - if start > size // 2 - 1: - return - else: - if 2 * start + 2 >= size: - m = 2 * start + 1 - else: - if heap[2 * start + 1] < heap[2 * start + 2]: - m = 2 * start + 1 - else: - m = 2 * start + 2 - if heap[m] < heap[start]: - temp, temp1 = heap[m], positions[m] - 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) - - topToBottom(heap, m, size, positions) - - # Update function if value of any node in min-heap decreases - def bottomToTop(val, index, heap, position): - temp = position[index] - - while(index != 0): - if index % 2 == 0: - parent = int( (index-2) / 2 ) - else: - parent = int( (index-1) / 2 ) - - if val < heap[parent]: - heap[index] = heap[parent] - position[index] = position[parent] - setPosition(position[parent], index) - else: - heap[index] = val - position[index] = temp - setPosition(temp, index) - break - index = parent - else: - heap[0] = val - position[0] = temp - setPosition(temp, 0) - - def heapify(heap, positions): - start = len(heap) // 2 - 1 - for i in range(start, -1, -1): - topToBottom(heap, i, len(heap), positions) - - def deleteMinimum(heap, positions): - temp = positions[0] - heap[0] = sys.maxsize - topToBottom(heap, 0, len(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 - # 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 = [] - - for x in range(len(l)): - p = sys.maxsize - Distance_TV.append(p) - Positions.append(x) - nodePosition.append(x) - - TreeEdges = [] - visited[0] = 1 - Distance_TV[0] = sys.maxsize - for x in l[0]: - Nbr_TV[ x[0] ] = 0 - Distance_TV[ x[0] ] = x[1] - heapify(Distance_TV, Positions) - - for i in range(1, len(l)): - vertex = deleteMinimum(Distance_TV, Positions) - if visited[vertex] == 0: - 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) - Nbr_TV[ v[0] ] = vertex - return TreeEdges - -# < --------- Prims Algorithm --------- > -n = int(raw_input("Enter number of vertices: ")) -e = int(raw_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)) diff --git a/Graphs/Multi_Hueristic_Astar.py b/Graphs/Multi_Hueristic_Astar.py deleted file mode 100644 index 1acd098f327d..000000000000 --- a/Graphs/Multi_Hueristic_Astar.py +++ /dev/null @@ -1,266 +0,0 @@ -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): - 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) - -def hueristic_2(P, goal): - # 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]) - -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: - 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 xrange(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 - -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 - -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_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 - -# start and end destination -start = (0, 0) -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") -multi_a_star(start, goal, n_hueristic) diff --git a/Graphs/a_star.py b/Graphs/a_star.py deleted file mode 100644 index 584222e6f62b..000000000000 --- a/Graphs/a_star.py +++ /dev/null @@ -1,102 +0,0 @@ -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], - [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]]''' - -init = [0, 0] -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])): - 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 - - -#the actions we can take -delta = [[-1, 0 ], # go up - [ 0, -1], # go left - [ 1, 0 ], # go down - [ 0, 1 ]] # go right - - -#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[init[0]][init[1]] = 1 - action = [[0 for col in range(len(grid[0]))] for row in range(len(grid))]#the action grid - - x = init[0] - y = init[1] - g = 0 - f = g + heuristic[init[0]][init[0]] - 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 - - 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.reverse() - next = cell.pop() - x = next[2] - y = next[3] - 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 - 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 closed[x2][y2] == 0 and grid[x2][y2] == 0: - g2 = g + cost - f2 = g2 + heuristic[x2][y2] - cell.append([f2, g2, x2, y2]) - closed[x2][y2] = 1 - action[x2][y2] = i - invpath = [] - x = goal[0] - y = goal[1] - 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] - x = x2 - y = y2 - invpath.append([x, y]) - - path = [] - for i in range(len(invpath)): - 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]) - diff --git a/Graphs/basic-graphs.py b/Graphs/basic-graphs.py deleted file mode 100644 index c9a269f1ab3f..000000000000 --- a/Graphs/basic-graphs.py +++ /dev/null @@ -1,281 +0,0 @@ -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 - -# 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) - -""" --------------------------------------------------------------------------------- - 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]) - -""" --------------------------------------------------------------------------------- - Depth First Search. - Args : G - Dictionary of edges - s - Starting Node - Vars : vis - Set of visited nodes - S - Traversal Stack --------------------------------------------------------------------------------- -""" - - -def dfs(G, s): - vis, S = set([s]), [s] - print(s) - while S: - flag = 0 - for i in G[S[-1]]: - if i not in vis: - S.append(i) - vis.add(i) - flag = 1 - print(i) - break - if not flag: - S.pop() - - -""" --------------------------------------------------------------------------------- - Breadth First Search. - Args : G - Dictionary of edges - s - Starting Node - Vars : vis - Set of visited nodes - Q - Traveral Stack --------------------------------------------------------------------------------- -""" -from collections import deque - - -def bfs(G, s): - vis, Q = set([s]), deque([s]) - print(s) - while Q: - u = Q.popleft() - for v in G[u]: - if v not in vis: - vis.add(v) - Q.append(v) - print(v) - - -""" --------------------------------------------------------------------------------- - Dijkstra's shortest path Algorithm - Args : G - Dictionary of edges - s - Starting Node - Vars : dist - Dictionary storing shortest distance from s to every other node - known - Set of knows nodes - path - Preceding node in path --------------------------------------------------------------------------------- -""" - - -def dijk(G, s): - dist, known, path = {s: 0}, set(), {s: 0} - while True: - if len(known) == len(G) - 1: - break - mini = 100000 - for i in dist: - if i not in known and dist[i] < mini: - mini = dist[i] - u = i - known.add(u) - for v in G[u]: - if v[0] not in known: - if dist[u] + v[1] < dist.get(v[0], 100000): - dist[v[0]] = dist[u] + v[1] - path[v[0]] = u - for i in dist: - if i != s: - print(dist[i]) - - -""" --------------------------------------------------------------------------------- - Topological Sort --------------------------------------------------------------------------------- -""" -from collections import deque - - -def topo(G, ind=None, Q=[1]): - if ind is None: - ind = [0] * (len(G) + 1) # SInce oth Index is ignored - for u in G: - for v in G[u]: - ind[v] += 1 - Q = deque() - for i in G: - if ind[i] == 0: - Q.append(i) - if len(Q) == 0: - return - v = Q.popleft() - print(v) - for w in G[v]: - ind[w] -= 1 - if ind[w] == 0: - Q.append(w) - topo(G, ind, Q) - - -""" --------------------------------------------------------------------------------- - Reading an Adjacency matrix --------------------------------------------------------------------------------- -""" - - -def adjm(): - n, a = raw_input(), [] - for i in xrange(n): - a.append(map(int, raw_input().split())) - return a, n - - -""" --------------------------------------------------------------------------------- - Floyd Warshall's algorithm - Args : G - Dictionary of edges - s - Starting Node - Vars : dist - Dictionary storing shortest distance from s to every other node - known - Set of knows nodes - path - Preceding node in path - --------------------------------------------------------------------------------- -""" - - -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): - if dist[i][j] > dist[i][k] + dist[k][j]: - dist[i][j] = dist[i][k] + dist[k][j] - path[i][k] = k - print(dist) - - -""" --------------------------------------------------------------------------------- - Prim's MST Algorithm - Args : G - Dictionary of edges - s - Starting Node - Vars : dist - Dictionary storing shortest distance from s to nearest node - known - Set of knows nodes - path - Preceding node in path --------------------------------------------------------------------------------- -""" - - -def prim(G, s): - dist, known, path = {s: 0}, set(), {s: 0} - while True: - if len(known) == len(G) - 1: - break - mini = 100000 - for i in dist: - if i not in known and dist[i] < mini: - mini = dist[i] - u = i - known.add(u) - for v in G[u]: - if v[0] not in known: - if v[1] < dist.get(v[0], 100000): - dist[v[0]] = v[1] - path[v[0]] = u - - -""" --------------------------------------------------------------------------------- - Accepting Edge list - Vars : n - Number of nodes - m - Number of edges - Returns : l - Edge list - n - Number of Nodes --------------------------------------------------------------------------------- -""" - - -def edglist(): - n, m = map(int, raw_input().split(" ")) - l = [] - for i in xrange(m): - l.append(map(int, raw_input().split(' '))) - return l, n - - -""" --------------------------------------------------------------------------------- - Kruskal's MST Algorithm - Args : E - Edge list - n - Number of Nodes - Vars : s - Set of all nodes as unique disjoint sets (initially) --------------------------------------------------------------------------------- -""" - - -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)] - while True: - if len(s) == 1: - break - print(s) - x = E.pop() - for i in xrange(len(s)): - if x[0] in s[i]: - break - for j in xrange(len(s)): - if x[1] in s[j]: - if i == j: - break - s[j].update(s[i]) - s.pop(i) - break diff --git a/Graphs/minimum_spanning_tree_kruskal.py b/Graphs/minimum_spanning_tree_kruskal.py deleted file mode 100644 index 930aab2251c4..000000000000 --- a/Graphs/minimum_spanning_tree_kruskal.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import print_function -num_nodes, num_edges = list(map(int,raw_input().split())) - -edges = [] - -for i in range(num_edges): - node1, node2, cost = list(map(int,raw_input().split())) - edges.append((i,node1,node2,cost)) - -edges = sorted(edges, key=lambda edge: edge[3]) - -parent = [i for i in range(num_nodes)] - -def find_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 - -print(minimum_spanning_tree_cost) -for edge in minimum_spanning_tree: - print(edge) diff --git a/Graphs/scc_kosaraju.py b/Graphs/scc_kosaraju.py deleted file mode 100644 index 90d3568fa496..000000000000 --- a/Graphs/scc_kosaraju.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import print_function -# n - no of nodes, m - no of edges -n, m = list(map(int,raw_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,raw_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 - 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 - 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 - for i in stack[::-1]: - if visit[i]: continue - component = [] - dfs2(i) - scc.append(component) - return scc - -print(kosaraju()) diff --git a/Graphs/tarjans_scc.py b/Graphs/tarjans_scc.py deleted file mode 100644 index 89754e593508..000000000000 --- a/Graphs/tarjans_scc.py +++ /dev/null @@ -1,78 +0,0 @@ -from collections import deque - - -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). - - 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 - connected component. - - 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) - stack = deque() - on_stack = [False for _ in range(n)] - index_of = [-1 for _ in range(n)] - lowlink_of = index_of[:] - - def strong_connect(v, index, components): - index_of[v] = index # the number when this node is seen - lowlink_of[v] = index # lowest rank node reachable from here - index += 1 - stack.append(v) - on_stack[v] = True - - 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] - elif on_stack[w]: - 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 = [] - w = stack.pop() - on_stack[w] = False - component.append(w) - while w != v: - w = stack.pop() - on_stack[w] = False - component.append(w) - components.append(component) - return index - - components = [] - for v in range(n): - if index_of[v] == -1: - strong_connect(v, 0, components) - - return components - - -def create_graph(n, edges): - g = [[] for _ in range(n)] - for u, v in edges: - g[u].append(v) - return g - - -if __name__ == '__main__': - # Test - n_vertices = 7 - source = [0, 0, 1, 2, 3, 3, 4, 4, 6] - target = [1, 3, 2, 0, 1, 4, 5, 6, 5] - edges = [(u, v) for u, v in zip(source, target)] - g = create_graph(n_vertices, edges) - - assert [[5], [6], [4], [3, 2, 1, 0]] == tarjan(g) diff --git a/Maths/BasicMaths.py b/Maths/BasicMaths.py deleted file mode 100644 index 6e8c919a001d..000000000000 --- a/Maths/BasicMaths.py +++ /dev/null @@ -1,74 +0,0 @@ -import math - -def primeFactors(n): - 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 numberOfDivisors(n): - 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): - temp = 1 - while n % i == 0: - temp += 1 - n = int(n / i) - div = div * (temp) - - return div - -def sumOfDivisors(n): - 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: - temp += 1 - n = int(n / i) - if temp > 1: - s *= (i**temp - 1) / (i - 1) - - return s - -def eulerPhi(n): - l = primeFactors(n) - l = set(l) - s = n - for x in l: - s *= (x - 1)/x - return s - -def main(): - print(primeFactors(100)) - print(numberOfDivisors(100)) - print(sumOfDivisors(100)) - print(eulerPhi(100)) - -if __name__ == '__main__': - main() - - \ No newline at end of file diff --git a/Maths/FibonacciSequenceRecursion.py b/Maths/FibonacciSequenceRecursion.py deleted file mode 100644 index b0b10fd07262..000000000000 --- a/Maths/FibonacciSequenceRecursion.py +++ /dev/null @@ -1,18 +0,0 @@ -# Fibonacci Sequence Using Recursion - -def recur_fibo(n): - return n if n <= 1 else (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): - print("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: ") - -if __name__ == '__main__': - main() diff --git a/Maths/GreaterCommonDivisor.py b/Maths/GreaterCommonDivisor.py deleted file mode 100644 index 15adaca1fb8d..000000000000 --- a/Maths/GreaterCommonDivisor.py +++ /dev/null @@ -1,15 +0,0 @@ -# Greater Common Divisor - https://en.wikipedia.org/wiki/Greatest_common_divisor -def gcd(a, b): - return b if a == 0 else gcd(b % a, a) - -def main(): - try: - nums = input("Enter two Integers separated by comma (,): ").split(',') - num1 = int(nums[0]); num2 = int(nums[1]) - except (IndexError, UnboundLocalError, ValueError): - print("Wrong Input") - print(f"gcd({num1}, {num2}) = {gcd(num1, num2)}") - -if __name__ == '__main__': - main() - diff --git a/Maths/ModularExponential.py b/Maths/ModularExponential.py deleted file mode 100644 index b3f4c00bd5d8..000000000000 --- a/Maths/ModularExponential.py +++ /dev/null @@ -1,20 +0,0 @@ -def modularExponential(base, power, mod): - 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)) - - -if __name__ == '__main__': - main() diff --git a/Maths/SegmentedSieve.py b/Maths/SegmentedSieve.py deleted file mode 100644 index 52ca6fbe601d..000000000000 --- a/Maths/SegmentedSieve.py +++ /dev/null @@ -1,46 +0,0 @@ -import math - -def sieve(n): - in_prime = [] - start = 2 - end = int(math.sqrt(n)) # Size of every segment - temp = [True] * (end + 1) - prime = [] - - while(start <= end): - if temp[start] == True: - in_prime.append(start) - for i in range(start*start, end+1, start): - if temp[i] == 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) - for each in in_prime: - - t = math.floor(low / each) * each - if t < low: - t += 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) - - low = high + 1 - high = low + end - 1 - if high > n: - high = n - - return prime - -print(sieve(10**6)) \ No newline at end of file diff --git a/Maths/SieveOfEratosthenes.py b/Maths/SieveOfEratosthenes.py deleted file mode 100644 index 2b132405ae4d..000000000000 --- a/Maths/SieveOfEratosthenes.py +++ /dev/null @@ -1,24 +0,0 @@ -import math -n = int(raw_input("Enter n: ")) - -def sieve(n): - l = [True] * (n+1) - prime = [] - start = 2 - end = int(math.sqrt(n)) - while(start <= end): - if l[start] == True: - prime.append(start) - for i in range(start*start, n+1, start): - if l[i] == True: - l[i] = False - start += 1 - - for j in range(end+1,n+1): - if l[j] == True: - prime.append(j) - - return prime - -print(sieve(n)) - \ No newline at end of file diff --git a/Maths/SimpsonRule.py b/Maths/SimpsonRule.py deleted file mode 100644 index 091c86c17f1b..000000000000 --- a/Maths/SimpsonRule.py +++ /dev/null @@ -1,49 +0,0 @@ - -''' -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' - -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 - -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_2(boundary, steps) - print('y = {0}'.format(y)) - -if __name__ == '__main__': - main() diff --git a/Maths/TrapezoidalRule.py b/Maths/TrapezoidalRule.py deleted file mode 100644 index 52310c1ed3b0..000000000000 --- a/Maths/TrapezoidalRule.py +++ /dev/null @@ -1,46 +0,0 @@ -''' -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' - -method 1: -"extended trapezoidal rule" - -''' -from __future__ import print_function - -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 = makePoints(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 makePoints(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() diff --git a/Neural_Network/FCN.ipynb b/Neural_Network/FCN.ipynb deleted file mode 100644 index a8bcf4beeea1..000000000000 --- a/Neural_Network/FCN.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": "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\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 -} diff --git a/Neural_Network/bpnn.py b/Neural_Network/bpnn.py deleted file mode 100644 index 0865e35f0b5c..000000000000 --- a/Neural_Network/bpnn.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/python -# encoding=utf8 - -''' - -A Framework of Back Propagation Neural Network(BP) model - -Easy to use: - * add many layers as you want !!! - * clearly see how the loss decreasing -Easy to expand: - * more activation functions - * more loss functions - * more optimization method - -Author: Stephen Lee -Github : https://github.com/RiptideBo -Date: 2017.11.23 - -''' - -import numpy as np -import matplotlib.pyplot as plt - - -def sigmoid(x): - return 1 / (1 + np.exp(-1 * x)) - -class DenseLayer(): - ''' - Layers of BP neural network - ''' - 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 - self.activation = activation - if learning_rate is None: - learning_rate = 0.3 - 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 - 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_activation = np.diag(np.diag(gradient_mat)) - else: - gradient_activation = 1 - return gradient_activation - - def forward_propagation(self,xdata): - self.xdata = xdata - if self.is_input_layer: - # input layer - self.wx_plus_b = xdata - self.output = xdata - return xdata - else: - 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): - - 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_bias = gradient * self._gradient_bias - self.gradient = np.dot(gradient,self._gradient_x).T - # ----------------------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 - - return self.gradient - - -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) - - def add_layer(self,layer): - self.layers.append(layer) - - def build(self): - for i,layer in enumerate(self.layers[:]): - if i < 1: - layer.is_input_layer = True - else: - 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)) - - def train(self,xdata,ydata,train_round,accuracy): - self.train_round = train_round - self.accuracy = accuracy - - self.ax_loss.hlines(self.accuracy, 0, self.train_round * 1.1) - - x_shape = np.shape(xdata) - 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 - - # forward propagation - for layer in self.layers: - _xdata = layer.forward_propagation(_xdata) - - loss, gradient = self.cal_loss(_ydata, _xdata) - all_loss = all_loss + loss - - # back propagation - # the input_layer does not upgrade - for layer in self.layers[:0:-1]: - gradient = layer.back_propagation(gradient) - - mse = all_loss/x_shape[0] - self.train_mse.append(mse) - - self.plot_loss() - - if mse < self.accuracy: - print('----达到精度----') - return mse - - 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 - - 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-') - plt.ion() - 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]]) - - model = BPNN() - model.add_layer(DenseLayer(10)) - model.add_layer(DenseLayer(20)) - model.add_layer(DenseLayer(30)) - model.add_layer(DenseLayer(2)) - - model.build() - - model.summary() - - model.train(xdata=x,ydata=y,train_round=100,accuracy=0.01) - -if __name__ == '__main__': - example() diff --git a/Neural_Network/convolution_neural_network.py b/Neural_Network/convolution_neural_network.py deleted file mode 100644 index 0dca2bc485d1..000000000000 --- a/Neural_Network/convolution_neural_network.py +++ /dev/null @@ -1,306 +0,0 @@ -#-*- coding: utf-8 -*- - -''' - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - - Name - - CNN - Convolution Neural Network For Photo Recognizing - Goal - - Recognize Handing Writting Word Photo - Detail:Total 5 layers neural network - * Convolution layer - * Pooling layer - * Input layer layer of BP - * Hiden layer of BP - * Output layer of BP - Author: Stephen Lee - Github: 245885195@qq.com - Date: 2017.9.20 - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - - ''' -from __future__ import print_function - -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): - ''' - :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 - :param bp_num2: units number of hidden layer - :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 - self.conv1 = conv1_get[:2] - self.step_conv1 = conv1_get[2] - 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.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 - - - 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} - with open(save_path, 'wb') as f: - pickle.dump(model_dic, f) - - print('Model saved: %s'% save_path) - - @classmethod - def ReadModel(cls,model_path): - #read saved model - import pickle - 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') - return conv_ins - - - def sig(self,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 - size_conv = convs[0] - num_conv =convs[1] - size_data = np.shape(data)[0] - #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] - data_focus.append(focus) - #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] - featuremap.append(self.sig(net_focus)) - featuremap = np.asmatrix(featuremap).reshape(Size_FeatureMap, Size_FeatureMap) - data_featuremap.append(featuremap) - - #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 - - def pooling(self,featuremaps,size_pooling,type='average_pool'): - #pooling process - size_map = len(featuremaps[0]) - 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 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 - map_pooled.append(np.average(focus)) - elif type == 'max_pooling': - #max pooling - map_pooled.append(np.max(focus)) - 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 - 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 = 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 - 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): - ''' - 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] - i_pool = i_pool + 1 - 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 trian(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 - while rp < n_repeat and mse >= error_accuracy: - alle = 0 - print('-------------Learning Time %d--------------'%rp) - for p in range(len(datas_train)): - #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) - 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_out2 = self.sig(bp_net_j) - bp_net_k = np.dot(bp_out2 ,self.wkj.T) - self.thre_bp3 - bp_out3 = self.sig(bp_net_k) - - #--------------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_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 - 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) - - 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.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 - 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 - #print(' ----Teach ',data_teach) - #print(' ----BP_output ',bp_out3) - rp = rp + 1 - 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.grid(True, alpha=0.5) - plt.show() - print('------------------Training Complished---------------------') - print((' - - Training epoch: ', rp, ' - - Mse: %.6f' % mse)) - if draw_e: - draw_error() - return mse - - def predict(self,datas_test): - #model predict - produce_out = [] - 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_pooled1 = self.pooling(data_conved1, self.size_pooling1) - data_bp_input = self._expand(data_pooled1) - - bp_out1 = data_bp_input - bp_net_j = bp_out1 * self.vji.T - self.thre_bp2 - bp_out2 = self.sig(bp_net_j) - 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] - return np.asarray(res) - - 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) - data_pooled1 = self.pooling(data_conved1, self.size_pooling1) - - return data_conved1,data_pooled1 - - -if __name__ == '__main__': - pass - ''' - I will put the example on other file - ''' \ No newline at end of file diff --git a/Neural_Network/perceptron.py b/Neural_Network/perceptron.py deleted file mode 100644 index 16e632f8db4a..000000000000 --- a/Neural_Network/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 training(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(raw_input('value: '))) - network.sort(sample) diff --git a/Project Euler/Problem 01/sol1.py b/Project Euler/Problem 01/sol1.py deleted file mode 100644 index 27031c3cfa9a..000000000000 --- a/Project Euler/Problem 01/sol1.py +++ /dev/null @@ -1,17 +0,0 @@ -''' -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 -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) diff --git a/Project Euler/Problem 01/sol2.py b/Project Euler/Problem 01/sol2.py deleted file mode 100644 index 2b7760e0bfff..000000000000 --- a/Project Euler/Problem 01/sol2.py +++ /dev/null @@ -1,20 +0,0 @@ -''' -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 -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) diff --git a/Project Euler/Problem 01/sol3.py b/Project Euler/Problem 01/sol3.py deleted file mode 100644 index f4f3aefcc5de..000000000000 --- a/Project Euler/Problem 01/sol3.py +++ /dev/null @@ -1,50 +0,0 @@ -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. -''' - -try: - 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); diff --git a/Project Euler/Problem 01/sol4.py b/Project Euler/Problem 01/sol4.py deleted file mode 100644 index 7941f5fcd3fe..000000000000 --- a/Project Euler/Problem 01/sol4.py +++ /dev/null @@ -1,30 +0,0 @@ -def mulitples(limit): - xmulti = [] - zmulti = [] - z = 3 - x = 5 - temp = 1 - while True: - result = z * temp - if (result < limit): - zmulti.append(result) - temp += 1 - else: - temp = 1 - break - while True: - result = x * temp - if (result < limit): - xmulti.append(result) - temp += 1 - else: - break - collection = list(set(xmulti+zmulti)) - return (sum(collection)) - - - - - - -print (mulitples(1000)) diff --git a/Project Euler/Problem 02/sol1.py b/Project Euler/Problem 02/sol1.py deleted file mode 100644 index f8257fb615fb..000000000000 --- a/Project Euler/Problem 02/sol1.py +++ /dev/null @@ -1,26 +0,0 @@ -''' -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 - -n = int(raw_input().strip()) -i=1 -j=2 -sum=0 -while(j<=n): - if((j&1)==0): #can also use (j%2==0) - sum+=j - temp=i - i=j - j=temp+i -print(sum) diff --git a/Project Euler/Problem 02/sol2.py b/Project Euler/Problem 02/sol2.py deleted file mode 100644 index aa8dc7f76f3b..000000000000 --- a/Project Euler/Problem 02/sol2.py +++ /dev/null @@ -1,12 +0,0 @@ -def fib(n): - a, b, s = 0, 1, 0 - while b < n: - if b % 2 == 0 and b < n: s += b - a, b = b, a+b - ls.append(s) - -T = int(input().strip()) -ls = [] -for _ in range(T): - fib(int(input().strip())) -print(ls, sep = '\n') diff --git a/Project Euler/Problem 02/sol3.py b/Project Euler/Problem 02/sol3.py deleted file mode 100644 index ede5e196ba7d..000000000000 --- a/Project Euler/Problem 02/sol3.py +++ /dev/null @@ -1,20 +0,0 @@ -''' -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(raw_input()) -a=0 -b=2 -count=0 -while 4*b+a1): - prime=n -print(prime) diff --git a/Project Euler/Problem 04/sol1.py b/Project Euler/Problem 04/sol1.py deleted file mode 100644 index 27490130c5c8..000000000000 --- a/Project Euler/Problem 04/sol1.py +++ /dev/null @@ -1,29 +0,0 @@ -''' -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. -''' -from __future__ import print_function -limit = int(raw_input("limit? ")) - -# fetchs the next number -for number in range(limit-1,10000,-1): - - # converts number into string. - strNumber = str(number) - - # checks whether 'strNumber' is a palindrome. - if(strNumber == strNumber[::-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(number / divisor)) == 3)): - - print(number) - exit(0) - - divisor -=1 \ No newline at end of file diff --git a/Project Euler/Problem 04/sol2.py b/Project Euler/Problem 04/sol2.py deleted file mode 100644 index a7e486d38e5a..000000000000 --- a/Project Euler/Problem 04/sol2.py +++ /dev/null @@ -1,19 +0,0 @@ -''' -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. -''' -from __future__ import print_function -arr = [] -for i in range(999,100,-1): - for j in range(999,100,-1): - t = str(i*j) - if t == t[::-1]: - arr.append(i*j) -arr.sort() - -n=int(raw_input()) -for i in arr[::-1]: - if(i LargestProduct: - LargestProduct = product - print(LargestProduct) - - -if __name__ == '__main__': - main() diff --git a/Project Euler/Problem 09/sol1.py b/Project Euler/Problem 09/sol1.py deleted file mode 100644 index e54c543b4721..000000000000 --- a/Project Euler/Problem 09/sol1.py +++ /dev/null @@ -1,15 +0,0 @@ -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 diff --git a/Project Euler/Problem 09/sol2.py b/Project Euler/Problem 09/sol2.py deleted file mode 100644 index c02662d89714..000000000000 --- a/Project Euler/Problem 09/sol2.py +++ /dev/null @@ -1,18 +0,0 @@ -"""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 - -product=-1 -d=0 -N = int(raw_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) diff --git a/Project Euler/Problem 10/sol1.py b/Project Euler/Problem 10/sol1.py deleted file mode 100644 index 94e5b7362114..000000000000 --- a/Project Euler/Problem 10/sol1.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import print_function -from math import sqrt - -try: - xrange #Python 2 -except NameError: - xrange = range #Python 3 - -def is_prime(n): - for i in xrange(2, int(sqrt(n))+1): - if n%i == 0: - return False - - 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.') diff --git a/Project Euler/Problem 11/grid.txt b/Project Euler/Problem 11/grid.txt deleted file mode 100644 index 1fc75c66a314..000000000000 --- a/Project Euler/Problem 11/grid.txt +++ /dev/null @@ -1,20 +0,0 @@ -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 \ No newline at end of file diff --git a/Project Euler/Problem 11/sol1.py b/Project Euler/Problem 11/sol1.py deleted file mode 100644 index b882dc449156..000000000000 --- a/Project Euler/Problem 11/sol1.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import print_function -''' -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 -''' - -try: - xrange #Python 2 -except NameError: - xrange = range #Python 2 - -def largest_product(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] - - #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 - - return largest - -if __name__ == '__main__': - grid = [] - with open('grid.txt') as file: - for line in file: - grid.append(line.strip('\n').split(' ')) - - grid = [[int(i) for i in grid[j]] for j in xrange(len(grid))] - - print(largest_product(grid)) \ No newline at end of file diff --git a/Project Euler/Problem 11/sol2.py b/Project Euler/Problem 11/sol2.py deleted file mode 100644 index b03395f01697..000000000000 --- a/Project Euler/Problem 11/sol2.py +++ /dev/null @@ -1,39 +0,0 @@ -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 diff --git a/Project Euler/Problem 12/sol1.py b/Project Euler/Problem 12/sol1.py deleted file mode 100644 index 9c4483fd62e5..000000000000 --- a/Project Euler/Problem 12/sol1.py +++ /dev/null @@ -1,46 +0,0 @@ -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: - -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? -''' -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): - if n%i == 0: - nDivisors += 2 - - return nDivisors - -tNum = 1 -i = 1 - -while True: - i += 1 - tNum += i - - if count_divisors(tNum) > 500: - break - -print(tNum) \ No newline at end of file diff --git a/Project Euler/Problem 13/sol1.py b/Project Euler/Problem 13/sol1.py deleted file mode 100644 index f041f01650c6..000000000000 --- a/Project Euler/Problem 13/sol1.py +++ /dev/null @@ -1,14 +0,0 @@ -''' -Problem Statement: -Work out the first ten digits of the sum of the N 50-digit numbers. -''' -from __future__ import print_function - -n = int(raw_input().strip()) - -array = [] -for i in range(n): - array.append(int(raw_input().strip())) - -print(str(sum(array))[:10]) - diff --git a/Project Euler/Problem 14/sol1.py b/Project Euler/Problem 14/sol1.py deleted file mode 100644 index 9037f6eb8bd5..000000000000 --- a/Project Euler/Problem 14/sol1.py +++ /dev/null @@ -1,21 +0,0 @@ -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')) diff --git a/Project Euler/Problem 15/sol1.py b/Project Euler/Problem 15/sol1.py deleted file mode 100644 index d24748011ef9..000000000000 --- a/Project Euler/Problem 15/sol1.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import print_function -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.') diff --git a/Project Euler/Problem 16/sol1.py b/Project Euler/Problem 16/sol1.py deleted file mode 100644 index dfd7c9ae3258..000000000000 --- a/Project Euler/Problem 16/sol1.py +++ /dev/null @@ -1,15 +0,0 @@ -power = int(raw_input("Enter the power of 2: ")) -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) - -print("Sum of the digits are:",sum_of_num) diff --git a/Project Euler/Problem 17/sol1.py b/Project Euler/Problem 17/sol1.py deleted file mode 100644 index 9de5d80b9b29..000000000000 --- a/Project Euler/Problem 17/sol1.py +++ /dev/null @@ -1,35 +0,0 @@ -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 is not 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) \ No newline at end of file diff --git a/Project Euler/Problem 19/sol1.py b/Project Euler/Problem 19/sol1.py deleted file mode 100644 index 94cf117026a4..000000000000 --- a/Project Euler/Problem 19/sol1.py +++ /dev/null @@ -1,51 +0,0 @@ -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. - -1 Jan 1900 was a Monday. -Thirty days has September, -April, June and November. -All the rest have thirty-one, -Saving February alone, -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. - -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 - -sundays = 0 - -while year < 2001: - 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: - month += 1 - day = day-days_per_month[month-2] - elif day > 29 and month is 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 is 1: - sundays += 1 - -print(sundays) \ No newline at end of file diff --git a/Project Euler/Problem 20/sol1.py b/Project Euler/Problem 20/sol1.py deleted file mode 100644 index b347eb1f0cd8..000000000000 --- a/Project Euler/Problem 20/sol1.py +++ /dev/null @@ -1,27 +0,0 @@ -# Finding the factorial. -def factorial(n): - fact = 1 - for i in range(1,n+1): - fact *= i - return fact - -# Spliting the digits and adding it. -def split_and_add(number): - sum_of_digits = 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. - return sum_of_digits - -# Taking the user input. -number = int(raw_input("Enter the Number: ")) - -# Assigning the factorial from the factorial function. -factorial = factorial(number) - -# Spliting and adding the factorial into answer. -answer = split_and_add(factorial) - -# Printing the answer. -print(answer) diff --git a/Project Euler/Problem 20/sol2.py b/Project Euler/Problem 20/sol2.py deleted file mode 100644 index bca9af9cb9ef..000000000000 --- a/Project Euler/Problem 20/sol2.py +++ /dev/null @@ -1,5 +0,0 @@ -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 diff --git a/Project Euler/Problem 21/sol1.py b/Project Euler/Problem 21/sol1.py deleted file mode 100644 index 6d137a7d4332..000000000000 --- a/Project Euler/Problem 21/sol1.py +++ /dev/null @@ -1,42 +0,0 @@ -#-.- coding: latin-1 -.- -from __future__ import print_function -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. - -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 -except NameError: - 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 - -sums = [] -total = 0 - -for i in xrange(1, 10000): - n = sum_of_divisors(i) - - if n < len(sums): - if sums[n-1] == i: - total += n + i - - sums.append(n) - -print(total) \ No newline at end of file diff --git a/Project Euler/Problem 22/p022_names.txt b/Project Euler/Problem 22/p022_names.txt deleted file mode 100644 index 7b8986bf6ce9..000000000000 --- a/Project Euler/Problem 22/p022_names.txt +++ /dev/null @@ -1 +0,0 @@ -"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" \ No newline at end of file diff --git a/Project Euler/Problem 22/sol1.py b/Project Euler/Problem 22/sol1.py deleted file mode 100644 index 7754306583dc..000000000000 --- a/Project Euler/Problem 22/sol1.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- 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. - -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? -''' -try: - xrange #Python 2 -except NameError: - xrange = range #Python 3 - -with open('p022_names.txt') as file: - names = str(file.readlines()[0]) - names = names.replace('"', '').split(',') - -names.sort() - -name_score = 0 -total_score = 0 - -for i, name in enumerate(names): - for letter in name: - name_score += ord(letter) - 64 - - total_score += (i+1)*name_score - name_score = 0 - -print(total_score) \ No newline at end of file diff --git a/Project Euler/Problem 22/sol2.py b/Project Euler/Problem 22/sol2.py deleted file mode 100644 index d7f9abf09d49..000000000000 --- a/Project Euler/Problem 22/sol2.py +++ /dev/null @@ -1,533 +0,0 @@ -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) - - -if __name__ == '__main__': - main() diff --git a/Project Euler/Problem 24/sol1.py b/Project Euler/Problem 24/sol1.py deleted file mode 100644 index b20493cb03af..000000000000 --- a/Project Euler/Problem 24/sol1.py +++ /dev/null @@ -1,7 +0,0 @@ -from itertools import permutations -def main(): - result=list(map("".join, permutations('0123456789'))) - print(result[999999]) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/Project Euler/Problem 25/sol1.py b/Project Euler/Problem 25/sol1.py deleted file mode 100644 index f8cea3093dcf..000000000000 --- a/Project Euler/Problem 25/sol1.py +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import print_function - -try: - xrange #Python 2 -except NameError: - 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]) - - return sequence[n] - -def fibonacci_digits_index(n): - digits = 0 - index = 2 - - while digits < n: - index += 1 - digits = len(str(fibonacci(index))) - - return index - -if __name__ == '__main__': - print(fibonacci_digits_index(1000)) \ No newline at end of file diff --git a/Project Euler/Problem 28/sol1.py b/Project Euler/Problem 28/sol1.py deleted file mode 100644 index 4942115ce537..000000000000 --- a/Project Euler/Problem 28/sol1.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import print_function -from math import ceil - -try: - xrange #Python 2 -except NameError: - 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 diff --git a/Project Euler/Problem 29/solution.py b/Project Euler/Problem 29/solution.py deleted file mode 100644 index 64d35c84d9ca..000000000000 --- a/Project Euler/Problem 29/solution.py +++ /dev/null @@ -1,33 +0,0 @@ -def main(): - """ - 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 - - 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)) - - -if __name__ == '__main__': - main() diff --git a/Project Euler/Problem 36/sol1.py b/Project Euler/Problem 36/sol1.py deleted file mode 100644 index d78e7e59f210..000000000000 --- a/Project Euler/Problem 36/sol1.py +++ /dev/null @@ -1,30 +0,0 @@ -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. - -(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) - - if n == n[::-1]: - return True - else: - return False - -total = 0 - -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 diff --git a/Project Euler/Problem 40/sol1.py b/Project Euler/Problem 40/sol1.py deleted file mode 100644 index ab4017512a1a..000000000000 --- a/Project Euler/Problem 40/sol1.py +++ /dev/null @@ -1,26 +0,0 @@ -#-.- coding: latin-1 -.- -from __future__ import print_function -''' -Champernowne's constant -Problem 40 -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. - -d1 × d10 × d100 × d1000 × d10000 × d100000 × d1000000 -''' - -constant = [] -i = 1 - -while len(constant) < 1e6: - constant.append(str(i)) - i += 1 - -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 diff --git a/Project Euler/Problem 48/sol1.py b/Project Euler/Problem 48/sol1.py deleted file mode 100644 index 5c4bdb0f6384..000000000000 --- a/Project Euler/Problem 48/sol1.py +++ /dev/null @@ -1,21 +0,0 @@ -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 -except NameError: - xrange = range - -total = 0 -for i in xrange(1, 1001): - total += i**i - - -print(str(total)[-10:]) \ No newline at end of file diff --git a/Project Euler/Problem 52/sol1.py b/Project Euler/Problem 52/sol1.py deleted file mode 100644 index 376b4cfa1d63..000000000000 --- a/Project Euler/Problem 52/sol1.py +++ /dev/null @@ -1,23 +0,0 @@ -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. - -Find the smallest positive integer, x, such that 2x, 3x, 4x, 5x, and 6x, contain the same digits. -''' -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))): - break - - i += 1 - -print(i) \ No newline at end of file diff --git a/Project Euler/Problem 53/sol1.py b/Project Euler/Problem 53/sol1.py deleted file mode 100644 index ed6d5329eb4e..000000000000 --- a/Project Euler/Problem 53/sol1.py +++ /dev/null @@ -1,36 +0,0 @@ -#-.- coding: latin-1 -.- -from __future__ import print_function -from math import factorial -''' -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 - -In combinatorics, we use the notation, 5C3 = 10. - -In general, - -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? -''' -try: - xrange #Python 2 -except NameError: - xrange = range #Python 3 - -def combinations(n, r): - return factorial(n)/(factorial(r)*factorial(n-r)) - -total = 0 - -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 diff --git a/Project Euler/Problem 76/sol1.py b/Project Euler/Problem 76/sol1.py deleted file mode 100644 index 2832f6d7afb6..000000000000 --- a/Project Euler/Problem 76/sol1.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import print_function -''' -Counting Summations -Problem 76 - -It is possible to write five as a sum in exactly six different ways: - -4 + 1 -3 + 2 -3 + 1 + 1 -2 + 2 + 1 -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? -''' -try: - xrange #Python 2 -except NameError: - 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 - - 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) - -print(partition(100)) \ No newline at end of file diff --git a/Project Euler/README.md b/Project Euler/README.md deleted file mode 100644 index 9f77f719f0f1..000000000000 --- a/Project Euler/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# ProjectEuler - -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. - -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? - -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. - -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? - -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? -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! diff --git a/boolean_algebra/Quine_McCluskey/QuineMcCluskey.py b/boolean_algebra/Quine_McCluskey/QuineMcCluskey.py deleted file mode 100644 index ff2df5117ce9..000000000000 --- a/boolean_algebra/Quine_McCluskey/QuineMcCluskey.py +++ /dev/null @@ -1,116 +0,0 @@ -def compare_string(string1, string2): - 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): - 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): - 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): - 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): - 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): - 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(raw_input("Enter the no. of variables\n")) - minterms = [int(x) for x in raw_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__': - main() \ No newline at end of file diff --git a/ciphers/Onepad_Cipher.py b/ciphers/Onepad_Cipher.py deleted file mode 100644 index 7e1be5fdc077..000000000000 --- a/ciphers/Onepad_Cipher.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import print_function - -import random - - -class Onepad: - def encrypt(self, text): - '''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 - cipher.append(c) - key.append(k) - return cipher, key - - def decrypt(self, cipher, key): - '''Function to decrypt text using psedo-random numbers.''' - plain = [] - for i in range(len(key)): - p = (cipher[i]-(key[i])**2)/key[i] - plain.append(chr(p)) - plain = ''.join([i for i in plain]) - return plain - - -if __name__ == '__main__': - c, k = Onepad().encrypt('Hello') - print(c, k) - print(Onepad().decrypt(c, k)) diff --git a/ciphers/Prehistoric Men.txt b/ciphers/Prehistoric Men.txt deleted file mode 100644 index 86c4de821bfc..000000000000 --- a/ciphers/Prehistoric Men.txt +++ /dev/null @@ -1,7193 +0,0 @@ -The Project Gutenberg eBook, Prehistoric Men, by Robert J. (Robert John) -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 -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 -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. - - -Title: Prehistoric Men -Author: Robert J. (Robert John) Braidwood -Release Date: July 28, 2016 [eBook #52664] -Language: English -Character set encoding: UTF-8 - - -***START OF THE PROJECT GUTENBERG EBOOK PREHISTORIC MEN*** - - -E-text prepared by Stephen Hutcheson, Dave Morgan, Charlie Howard, and the -Online Distributed Proofreading Team (http://www.pgdp.net) - - - -Note: Project Gutenberg also has an HTML version of this - file which includes the original illustrations. - See 52664-h.htm or 52664-h.zip: - (http://www.gutenberg.org/files/52664/52664-h/52664-h.htm) - or - (http://www.gutenberg.org/files/52664/52664-h.zip) - - -Transcriber's note: - - Some characters might not display in this UTF-8 text - version. If so, the reader should consult the HTML - version referred to above. One example of this might - occur in the second paragraph under "Choppers and - Adze-like Tools", page 46, which contains the phrase - �an adze cutting edge is ? shaped�. The symbol before - �shaped� looks like a sharply-italicized sans-serif �L�. - Devices that cannot display that symbol may substitute - a question mark, a square, or other symbol. - - -PREHISTORIC MEN - -by - -ROBERT J. BRAIDWOOD - -Research Associate, Old World Prehistory - -Professor -Oriental Institute and Department of Anthropology -University of Chicago - -Drawings by Susan T. Richert - - -[Illustration] - -Chicago Natural History Museum -Popular Series -Anthropology, Number 37 - -Third Edition Issued in Co-operation with -The Oriental Institute, The University of Chicago - -Edited by Lillian A. Ross - -Printed in the United States of America -by Chicago Natural History Museum Press - -Copyright 1948, 1951, and 1957 by Chicago Natural History Museum - -First edition 1948 -Second edition 1951 -Third edition 1957 -Fourth edition 1959 - - -Preface - -[Illustration] - - -Like the writing of most professional archeologists, mine has been -confined to so-called learned papers. Good, bad, or indifferent, these -papers were in a jargon that only my colleagues and a few advanced -students could understand. Hence, when I was asked to do this little -book, I soon found it extremely difficult to say what I meant in simple -fashion. The style is new to me, but I hope the reader will not find it -forced or pedantic; at least I have done my very best to tell the story -simply and clearly. - -Many friends have aided in the preparation of the book. The whimsical -charm of Miss Susan Richert�s illustrations add enormously to the -spirit I wanted. She gave freely of her own time on the drawings and -in planning the book with me. My colleagues at the University of -Chicago, especially Professor Wilton M. Krogman (now of the University -of Pennsylvania), and also Mrs. Linda Braidwood, Associate of the -Oriental Institute, and Professors Fay-Cooper Cole and Sol Tax, of -the Department of Anthropology, gave me counsel in matters bearing on -their special fields, and the Department of Anthropology bore some of -the expense of the illustrations. From Mrs. Irma Hunter and Mr. Arnold -Maremont, who are not archeologists at all and have only an intelligent -layman�s notion of archeology, I had sound advice on how best to tell -the story. I am deeply indebted to all these friends. - -While I was preparing the second edition, I had the great fortune -to be able to rework the third chapter with Professor Sherwood L. -Washburn, now of the Department of Anthropology of the University of -California, and the fourth, fifth, and sixth chapters with Professor -Hallum L. Movius, Jr., of the Peabody Museum, Harvard University. The -book has gained greatly in accuracy thereby. In matters of dating, -Professor Movius and the indications of Professor W. F. Libby�s Carbon -14 chronology project have both encouraged me to choose the lowest -dates now current for the events of the Pleistocene Ice Age. There is -still no certain way of fixing a direct chronology for most of the -Pleistocene, but Professor Libby�s method appears very promising for -its end range and for proto-historic dates. In any case, this book -names �periods,� and new dates may be written in against mine, if new -and better dating systems appear. - -I wish to thank Dr. Clifford C. Gregg, Director of Chicago Natural -History Museum, for the opportunity to publish this book. My old -friend, Dr. Paul S. Martin, Chief Curator in the Department of -Anthropology, asked me to undertake the job and inspired me to complete -it. I am also indebted to Miss Lillian A. Ross, Associate Editor of -Scientific Publications, and to Mr. George I. Quimby, Curator of -Exhibits in Anthropology, for all the time they have given me in -getting the manuscript into proper shape. - - ROBERT J. BRAIDWOOD - _June 15, 1950_ - - - - -Preface to the Third Edition - - -In preparing the enlarged third edition, many of the above mentioned -friends have again helped me. I have picked the brains of Professor F. -Clark Howell of the Department of Anthropology of the University of -Chicago in reworking the earlier chapters, and he was very patient in -the matter, which I sincerely appreciate. - -All of Mrs. Susan Richert Allen�s original drawings appear, but a few -necessary corrections have been made in some of the charts and some new -drawings have been added by Mr. John Pfiffner, Staff Artist, Chicago -Natural History Museum. - - ROBERT J. BRAIDWOOD - _March 1, 1959_ - - - - -Contents - - - PAGE - How We Learn about Prehistoric Men 7 - - The Changing World in Which Prehistoric Men Lived 17 - - Prehistoric Men Themselves 22 - - Cultural Beginnings 38 - - More Evidence of Culture 56 - - Early Moderns 70 - - End and Prelude 92 - - The First Revolution 121 - - The Conquest of Civilization 144 - - End of Prehistory 162 - - Summary 176 - - List of Books 180 - - Index 184 - - - - -HOW WE LEARN about Prehistoric Men - -[Illustration] - - -Prehistory means the time before written history began. Actually, more -than 99 per cent of man�s story is prehistory. Man is at least half a -million years old, but he did not begin to write history (or to write -anything) until about 5,000 years ago. - -The men who lived in prehistoric times left us no history books, but -they did unintentionally leave a record of their presence and their way -of life. This record is studied and interpreted by different kinds of -scientists. - - -SCIENTISTS WHO FIND OUT ABOUT PREHISTORIC MEN - -The scientists who study the bones and teeth and any other parts -they find of the bodies of prehistoric men, are called _physical -anthropologists_. Physical anthropologists are trained, much like -doctors, to know all about the human body. They study living people, -too; they know more about the biological facts of human �races� than -anybody else. If the police find a badly decayed body in a trunk, -they ask a physical anthropologist to tell them what the person -originally looked like. The physical anthropologists who specialize in -prehistoric men work with fossils, so they are sometimes called _human -paleontologists_. - - -ARCHEOLOGISTS - -There is a kind of scientist who studies the things that prehistoric -men made and did. Such a scientist is called an _archeologist_. It is -the archeologist�s business to look for the stone and metal tools, the -pottery, the graves, and the caves or huts of the men who lived before -history began. - -But there is more to archeology than just looking for things. In -Professor V. Gordon Childe�s words, archeology �furnishes a sort of -history of human activity, provided always that the actions have -produced concrete results and left recognizable material traces.� You -will see that there are at least three points in what Childe says: - - 1. The archeologists have to find the traces of things left behind by - ancient man, and - - 2. Only a few objects may be found, for most of these were probably - too soft or too breakable to last through the years. However, - - 3. The archeologist must use whatever he can find to tell a story--to - make a �sort of history�--from the objects and living-places and - graves that have escaped destruction. - -What I mean is this: Let us say you are walking through a dump yard, -and you find a rusty old spark plug. If you want to think about what -the spark plug means, you quickly remember that it is a part of an -automobile motor. This tells you something about the man who threw -the spark plug on the dump. He either had an automobile, or he knew -or lived near someone who did. He can�t have lived so very long ago, -you�ll remember, because spark plugs and automobiles are only about -sixty years old. - -When you think about the old spark plug in this way you have -just been making the beginnings of what we call an archeological -_interpretation_; you have been making the spark plug tell a story. -It is the same way with the man-made things we archeologists find -and put in museums. Usually, only a few of these objects are pretty -to look at; but each of them has some sort of story to tell. Making -the interpretation of his finds is the most important part of the -archeologist�s job. It is the way he gets at the �sort of history of -human activity� which is expected of archeology. - - -SOME OTHER SCIENTISTS - -There are many other scientists who help the archeologist and the -physical anthropologist find out about prehistoric men. The geologists -help us tell the age of the rocks or caves or gravel beds in which -human bones or man-made objects are found. There are other scientists -with names which all begin with �paleo� (the Greek word for �old�). The -_paleontologists_ study fossil animals. There are also, for example, -such scientists as _paleobotanists_ and _paleoclimatologists_, who -study ancient plants and climates. These scientists help us to know -the kinds of animals and plants that were living in prehistoric times -and so could be used for food by ancient man; what the weather was -like; and whether there were glaciers. Also, when I tell you that -prehistoric men did not appear until long after the great dinosaurs had -disappeared, I go on the say-so of the paleontologists. They know that -fossils of men and of dinosaurs are not found in the same geological -period. The dinosaur fossils come in early periods, the fossils of men -much later. - -Since World War II even the atomic scientists have been helping the -archeologists. By testing the amount of radioactivity left in charcoal, -wood, or other vegetable matter obtained from archeological sites, they -have been able to date the sites. Shell has been used also, and even -the hair of Egyptian mummies. The dates of geological and climatic -events have also been discovered. Some of this work has been done from -drillings taken from the bottom of the sea. - -This dating by radioactivity has considerably shortened the dates which -the archeologists used to give. If you find that some of the dates -I give here are more recent than the dates you see in other books -on prehistory, it is because I am using one of the new lower dating -systems. - -[Illustration: RADIOCARBON CHART - -The rate of disappearance of radioactivity as time passes.[1]] - - [1] It is important that the limitations of the radioactive carbon - �dating� system be held in mind. As the statistics involved in - the system are used, there are two chances in three that the - �date� of the sample falls within the range given as plus or - minus an added number of years. For example, the �date� for the - Jarmo village (see chart), given as 6750 � 200 B.C., really - means that there are only two chances in three that the real - date of the charcoal sampled fell between 6950 and 6550 B.C. - We have also begun to suspect that there are ways in which the - samples themselves may have become �contaminated,� either on - the early or on the late side. We now tend to be suspicious of - single radioactive carbon determinations, or of determinations - from one site alone. But as a fabric of consistent - determinations for several or more sites of one archeological - period, we gain confidence in the �dates.� - - -HOW THE SCIENTISTS FIND OUT - -So far, this chapter has been mainly about the people who find out -about prehistoric men. We also need a word about _how_ they find out. - -All our finds came by accident until about a hundred years ago. Men -digging wells, or digging in caves for fertilizer, often turned up -ancient swords or pots or stone arrowheads. People also found some odd -pieces of stone that didn�t look like natural forms, but they also -didn�t look like any known tool. As a result, the people who found them -gave them queer names; for example, �thunderbolts.� The people thought -the strange stones came to earth as bolts of lightning. We know now -that these strange stones were prehistoric stone tools. - -Many important finds still come to us by accident. In 1935, a British -dentist, A. T. Marston, found the first of two fragments of a very -important fossil human skull, in a gravel pit at Swanscombe, on the -River Thames, England. He had to wait nine months, until the face of -the gravel pit had been dug eight yards farther back, before the second -fragment appeared. They fitted! Then, twenty years later, still another -piece appeared. In 1928 workmen who were blasting out rock for the -breakwater in the port of Haifa began to notice flint tools. Thus the -story of cave men on Mount Carmel, in Palestine, began to be known. - -Planned archeological digging is only about a century old. Even before -this, however, a few men realized the significance of objects they dug -from the ground; one of these early archeologists was our own Thomas -Jefferson. The first real mound-digger was a German grocer�s clerk, -Heinrich Schliemann. Schliemann made a fortune as a merchant, first -in Europe and then in the California gold-rush of 1849. He became an -American citizen. Then he retired and had both money and time to test -an old idea of his. He believed that the heroes of ancient Troy and -Mycenae were once real Trojans and Greeks. He proved it by going to -Turkey and Greece and digging up the remains of both cities. - -Schliemann had the great good fortune to find rich and spectacular -treasures, and he also had the common sense to keep notes and make -descriptions of what he found. He proved beyond doubt that many ancient -city mounds can be _stratified_. This means that there may be the -remains of many towns in a mound, one above another, like layers in a -cake. - -You might like to have an idea of how mounds come to be in layers. -The original settlers may have chosen the spot because it had a good -spring and there were good fertile lands nearby, or perhaps because -it was close to some road or river or harbor. These settlers probably -built their town of stone and mud-brick. Finally, something would have -happened to the town--a flood, or a burning, or a raid by enemies--and -the walls of the houses would have fallen in or would have melted down -as mud in the rain. Nothing would have remained but the mud and debris -of a low mound of _one_ layer. - -The second settlers would have wanted the spot for the same reasons -the first settlers did--good water, land, and roads. Also, the second -settlers would have found a nice low mound to build their houses on, -a protection from floods. But again, something would finally have -happened to the second town, and the walls of _its_ houses would have -come tumbling down. This makes the _second_ layer. And so on.... - -In Syria I once had the good fortune to dig on a large mound that had -no less than fifteen layers. Also, most of the layers were thick, and -there were signs of rebuilding and repairs within each layer. The mound -was more than a hundred feet high. In each layer, the building material -used had been a soft, unbaked mud-brick, and most of the debris -consisted of fallen or rain-melted mud from these mud-bricks. - -This idea of _stratification_, like the cake layers, was already a -familiar one to the geologists by Schliemann�s time. They could show -that their lowest layer of rock was oldest or earliest, and that the -overlying layers became more recent as one moved upward. Schliemann�s -digging proved the same thing at Troy. His first (lowest and earliest) -city had at least nine layers above it; he thought that the second -layer contained the remains of Homer�s Troy. We now know that Homeric -Troy was layer VIIa from the bottom; also, we count eleven layers or -sub-layers in total. - -Schliemann�s work marks the beginnings of modern archeology. Scholars -soon set out to dig on ancient sites, from Egypt to Central America. - - -ARCHEOLOGICAL INFORMATION - -As time went on, the study of archeological materials--found either -by accident or by digging on purpose--began to show certain things. -Archeologists began to get ideas as to the kinds of objects that -belonged together. If you compared a mail-order catalogue of 1890 with -one of today, you would see a lot of differences. If you really studied -the two catalogues hard, you would also begin to see that certain -objects �go together.� Horseshoes and metal buggy tires and pieces of -harness would begin to fit into a picture with certain kinds of coal -stoves and furniture and china dishes and kerosene lamps. Our friend -the spark plug, and radios and electric refrigerators and light bulbs -would fit into a picture with different kinds of furniture and dishes -and tools. You won�t be old enough to remember the kind of hats that -women wore in 1890, but you�ve probably seen pictures of them, and you -know very well they couldn�t be worn with the fashions of today. - -This is one of the ways that archeologists study their materials. -The various tools and weapons and jewelry, the pottery, the kinds -of houses, and even the ways of burying the dead tend to fit into -pictures. Some archeologists call all of the things that go together to -make such a picture an _assemblage_. The assemblage of the first layer -of Schliemann�s Troy was as different from that of the seventh layer as -our 1900 mail-order catalogue is from the one of today. - -The archeologists who came after Schliemann began to notice other -things and to compare them with occurrences in modern times. The -idea that people will buy better mousetraps goes back into very -ancient times. Today, if we make good automobiles or radios, we can -sell some of them in Turkey or even in Timbuktu. This means that a -few present-day types of American automobiles and radios form part -of present-day �assemblages� in both Turkey and Timbuktu. The total -present-day �assemblage� of Turkey is quite different from that of -Timbuktu or that of America, but they have at least some automobiles -and some radios in common. - -Now these automobiles and radios will eventually wear out. Let us -suppose we could go to some remote part of Turkey or to Timbuktu in a -dream. We don�t know what the date is, in our dream, but we see all -sorts of strange things and ways of living in both places. Nobody -tells us what the date is. But suddenly we see a 1936 Ford; so we -know that in our dream it has to be at least the year 1936, and only -as many years after that as we could reasonably expect a Ford to keep -in running order. The Ford would probably break down in twenty years� -time, so the Turkish or Timbuktu �assemblage� we�re seeing in our dream -has to date at about A.D. 1936-56. - -Archeologists not only �date� their ancient materials in this way; they -also see over what distances and between which peoples trading was -done. It turns out that there was a good deal of trading in ancient -times, probably all on a barter and exchange basis. - - -EVERYTHING BEGINS TO FIT TOGETHER - -Now we need to pull these ideas all together and see the complicated -structure the archeologists can build with their materials. - -Even the earliest archeologists soon found that there was a very long -range of prehistoric time which would yield only very simple things. -For this very long early part of prehistory, there was little to be -found but the flint tools which wandering, hunting and gathering -people made, and the bones of the wild animals they ate. Toward the -end of prehistoric time there was a general settling down with the -coming of agriculture, and all sorts of new things began to be made. -Archeologists soon got a general notion of what ought to appear with -what. Thus, it would upset a French prehistorian digging at the bottom -of a very early cave if he found a fine bronze sword, just as much as -it would upset him if he found a beer bottle. The people of his very -early cave layer simply could not have made bronze swords, which came -later, just as do beer bottles. Some accidental disturbance of the -layers of his cave must have happened. - -With any luck, archeologists do their digging in a layered, stratified -site. They find the remains of everything that would last through -time, in several different layers. They know that the assemblage in -the bottom layer was laid down earlier than the assemblage in the next -layer above, and so on up to the topmost layer, which is the latest. -They look at the results of other �digs� and find that some other -archeologist 900 miles away has found ax-heads in his lowest layer, -exactly like the ax-heads of their fifth layer. This means that their -fifth layer must have been lived in at about the same time as was the -first layer in the site 200 miles away. It also may mean that the -people who lived in the two layers knew and traded with each other. Or -it could mean that they didn�t necessarily know each other, but simply -that both traded with a third group at about the same time. - -You can see that the more we dig and find, the more clearly the main -facts begin to stand out. We begin to be more sure of which people -lived at the same time, which earlier and which later. We begin to -know who traded with whom, and which peoples seemed to live off by -themselves. We begin to find enough skeletons in burials so that the -physical anthropologists can tell us what the people looked like. We -get animal bones, and a paleontologist may tell us they are all bones -of wild animals; or he may tell us that some or most of the bones are -those of domesticated animals, for instance, sheep or cattle, and -therefore the people must have kept herds. - -More important than anything else--as our structure grows more -complicated and our materials increase--is the fact that �a sort -of history of human activity� does begin to appear. The habits or -traditions that men formed in the making of their tools and in the -ways they did things, begin to stand out for us. How characteristic -were these habits and traditions? What areas did they spread over? -How long did they last? We watch the different tools and the traces -of the way things were done--how the burials were arranged, what -the living-places were like, and so on. We wonder about the people -themselves, for the traces of habits and traditions are useful to us -only as clues to the men who once had them. So we ask the physical -anthropologists about the skeletons that we found in the burials. The -physical anthropologists tell us about the anatomy and the similarities -and differences which the skeletons show when compared with other -skeletons. The physical anthropologists are even working on a -method--chemical tests of the bones--that will enable them to discover -what the blood-type may have been. One thing is sure. We have never -found a group of skeletons so absolutely similar among themselves--so -cast from a single mould, so to speak--that we could claim to have a -�pure� race. I am sure we never shall. - -We become particularly interested in any signs of change--when new -materials and tool types and ways of doing things replace old ones. We -watch for signs of social change and progress in one way or another. - -We must do all this without one word of written history to aid us. -Everything we are concerned with goes back to the time _before_ men -learned to write. That is the prehistorian�s job--to find out what -happened before history began. - - - - -THE CHANGING WORLD in which Prehistoric Men Lived - -[Illustration] - - -Mankind, we�ll say, is at least a half million years old. It is very -hard to understand how long a time half a million years really is. -If we were to compare this whole length of time to one day, we�d get -something like this: The present time is midnight, and Jesus was -born just five minutes and thirty-six seconds ago. Earliest history -began less than fifteen minutes ago. Everything before 11:45 was in -prehistoric time. - -Or maybe we can grasp the length of time better in terms of -generations. As you know, primitive peoples tend to marry and have -children rather early in life. So suppose we say that twenty years -will make an average generation. At this rate there would be 25,000 -generations in a half-million years. But our United States is much less -than ten generations old, twenty-five generations take us back before -the time of Columbus, Julius Caesar was alive just 100 generations ago, -David was king of Israel less than 150 generations ago, 250 generations -take us back to the beginning of written history. And there were 24,750 -generations of men before written history began! - -I should probably tell you that there is a new method of prehistoric -dating which would cut the earliest dates in my reckoning almost -in half. Dr. Cesare Emiliani, combining radioactive (C14) and -chemical (oxygen isotope) methods in the study of deep-sea borings, -has developed a system which would lower the total range of human -prehistory to about 300,000 years. The system is still too new to have -had general examination and testing. Hence, I have not used it in this -book; it would mainly affect the dates earlier than 25,000 years ago. - - -CHANGES IN ENVIRONMENT - -The earth probably hasn�t changed much in the last 5,000 years (250 -generations). Men have built things on its surface and dug into it and -drawn boundaries on maps of it, but the places where rivers, lakes, -seas, and mountains now stand have changed very little. - -In earlier times the earth looked very different. Geologists call the -last great geological period the _Pleistocene_. It began somewhere -between a half million and a million years ago, and was a time of great -changes. Sometimes we call it the Ice Age, for in the Pleistocene -there were at least three or four times when large areas of earth -were covered with glaciers. The reason for my uncertainty is that -while there seem to have been four major mountain or alpine phases of -glaciation, there may only have been three general continental phases -in the Old World.[2] - - [2] This is a complicated affair and I do not want to bother you - with its details. Both the alpine and the continental ice sheets - seem to have had minor fluctuations during their _main_ phases, - and the advances of the later phases destroyed many of the - traces of the earlier phases. The general textbooks have tended - to follow the names and numbers established for the Alps early - in this century by two German geologists. I will not bother you - with the names, but there were _four_ major phases. It is the - second of these alpine phases which seems to fit the traces of - the earliest of the great continental glaciations. In this book, - I will use the four-part system, since it is the most familiar, - but will add the word _alpine_ so you may remember to make the - transition to the continental system if you wish to do so. - -Glaciers are great sheets of ice, sometimes over a thousand feet -thick, which are now known only in Greenland and Antarctica and in -high mountains. During several of the glacial periods in the Ice Age, -the glaciers covered most of Canada and the northern United States and -reached down to southern England and France in Europe. Smaller ice -sheets sat like caps on the Rockies, the Alps, and the Himalayas. The -continental glaciation only happened north of the equator, however, so -remember that �Ice Age� is only half true. - -As you know, the amount of water on and about the earth does not vary. -These large glaciers contained millions of tons of water frozen into -ice. Because so much water was frozen and contained in the glaciers, -the water level of lakes and oceans was lowered. Flooded areas were -drained and appeared as dry land. There were times in the Ice Age when -there was no English Channel, so that England was not an island, and a -land bridge at the Dardanelles probably divided the Mediterranean from -the Black Sea. - -A very important thing for people living during the time of a -glaciation was the region adjacent to the glacier. They could not, of -course, live on the ice itself. The questions would be how close could -they live to it, and how would they have had to change their way of -life to do so. - - -GLACIERS CHANGE THE WEATHER - -Great sheets of ice change the weather. When the front of a glacier -stood at Milwaukee, the weather must have been bitterly cold in -Chicago. The climate of the whole world would have been different, and -you can see how animals and men would have been forced to move from one -place to another in search of food and warmth. - -On the other hand, it looks as if only a minor proportion of the whole -Ice Age was really taken up by times of glaciation. In between came -the _interglacial_ periods. During these times the climate around -Chicago was as warm as it is now, and sometimes even warmer. It may -interest you to know that the last great glacier melted away less than -10,000 years ago. Professor Ernst Antevs thinks we may be living in an -interglacial period and that the Ice Age may not be over yet. So if you -want to make a killing in real estate for your several hundred times -great-grandchildren, you might buy some land in the Arizona desert or -the Sahara. - -We do not yet know just why the glaciers appeared and disappeared, as -they did. It surely had something to do with an increase in rainfall -and a fall in temperature. It probably also had to do with a general -tendency for the land to rise at the beginning of the Pleistocene. We -know there was some mountain-building at that time. Hence, rain-bearing -winds nourished the rising and cooler uplands with snow. An increase -in all three of these factors--if they came together--would only have -needed to be slight. But exactly why this happened we do not know. - -The reason I tell you about the glaciers is simply to remind you of the -changing world in which prehistoric men lived. Their surroundings--the -animals and plants they used for food, and the weather they had to -protect themselves from--were always changing. On the other hand, this -change happened over so long a period of time and was so slow that -individual people could not have noticed it. Glaciers, about which they -probably knew nothing, moved in hundreds of miles to the north of them. -The people must simply have wandered ever more southward in search -of the plants and animals on which they lived. Or some men may have -stayed where they were and learned to hunt different animals and eat -different foods. Prehistoric men had to keep adapting themselves to new -environments and those who were most adaptive were most successful. - - -OTHER CHANGES - -Changes took place in the men themselves as well as in the ways they -lived. As time went on, they made better tools and weapons. Then, too, -we begin to find signs of how they started thinking of other things -than food and the tools to get it with. We find that they painted on -the walls of caves, and decorated their tools; we find that they buried -their dead. - -At about the time when the last great glacier was finally melting away, -men in the Near East made the first basic change in human economy. -They began to plant grain, and they learned to raise and herd certain -animals. This meant that they could store food in granaries and �on the -hoof� against the bad times of the year. This first really basic change -in man�s way of living has been called the �food-producing revolution.� -By the time it happened, a modern kind of climate was beginning. Men -had already grown to look as they do now. Know-how in ways of living -had developed and progressed, slowly but surely, up to a point. It was -impossible for men to go beyond that point if they only hunted and -fished and gathered wild foods. Once the basic change was made--once -the food-producing revolution became effective--technology leaped ahead -and civilization and written history soon began. - - - - -Prehistoric Men THEMSELVES - -[Illustration] - - -DO WE KNOW WHERE MAN ORIGINATED? - -For a long time some scientists thought the �cradle of mankind� was in -central Asia. Other scientists insisted it was in Africa, and still -others said it might have been in Europe. Actually, we don�t know -where it was. We don�t even know that there was only _one_ �cradle.� -If we had to choose a �cradle� at this moment, we would probably say -Africa. But the southern portions of Asia and Europe may also have been -included in the general area. The scene of the early development of -mankind was certainly the Old World. It is pretty certain men didn�t -reach North or South America until almost the end of the Ice Age--had -they done so earlier we would certainly have found some trace of them -by now. - -The earliest tools we have yet found come from central and south -Africa. By the dating system I�m using, these tools must be over -500,000 years old. There are now reports that a few such early tools -have been found--at the Sterkfontein cave in South Africa--along with -the bones of small fossil men called �australopithecines.� - -Not all scientists would agree that the australopithecines were �men,� -or would agree that the tools were made by the australopithecines -themselves. For these sticklers, the earliest bones of men come from -the island of Java. The date would be about 450,000 years ago. So far, -we have not yet found the tools which we suppose these earliest men in -the Far East must have made. - -Let me say it another way. How old are the earliest traces of men we -now have? Over half a million years. This was a time when the first -alpine glaciation was happening in the north. What has been found so -far? The tools which the men of those times made, in different parts -of Africa. It is now fairly generally agreed that the �men� who made -the tools were the australopithecines. There is also a more �man-like� -jawbone at Kanam in Kenya, but its find-spot has been questioned. The -next earliest bones we have were found in Java, and they may be almost -a hundred thousand years younger than the earliest African finds. We -haven�t yet found the tools of these early Javanese. Our knowledge of -tool-using in Africa spreads quickly as time goes on: soon after the -appearance of tools in the south we shall have them from as far north -as Algeria. - -Very soon after the earliest Javanese come the bones of slightly more -developed people in Java, and the jawbone of a man who once lived in -what is now Germany. The same general glacial beds which yielded the -later Javanese bones and the German jawbone also include tools. These -finds come from the time of the second alpine glaciation. - -So this is the situation. By the time of the end of the second alpine -or first continental glaciation (say 400,000 years ago) we have traces -of men from the extremes of the more southerly portions of the Old -World--South Africa, eastern Asia, and western Europe. There are also -some traces of men in the middle ground. In fact, Professor Franz -Weidenreich believed that creatures who were the immediate ancestors -of men had already spread over Europe, Africa, and Asia by the time -the Ice Age began. We certainly have no reason to disbelieve this, but -fortunate accidents of discovery have not yet given us the evidence to -prove it. - - -MEN AND APES - -Many people used to get extremely upset at the ill-formed notion -that �man descended from the apes.� Such words were much more likely -to start fights or �monkey trials� than the correct notion that all -living animals, including man, ascended or evolved from a single-celled -organism which lived in the primeval seas hundreds of millions of years -ago. Men are mammals, of the order called Primates, and man�s living -relatives are the great apes. Men didn�t �descend� from the apes or -apes from men, and mankind must have had much closer relatives who have -since become extinct. - -Men stand erect. They also walk and run on their two feet. Apes are -happiest in trees, swinging with their arms from branch to branch. -Few branches of trees will hold the mighty gorilla, although he still -manages to sleep in trees. Apes can�t stand really erect in our sense, -and when they have to run on the ground, they use the knuckles of their -hands as well as their feet. - -A key group of fossil bones here are the south African -australopithecines. These are called the _Australopithecinae_ or -�man-apes� or sometimes even �ape-men.� We do not _know_ that they were -directly ancestral to men but they can hardly have been so to apes. -Presently I�ll describe them a bit more. The reason I mention them -here is that while they had brains no larger than those of apes, their -hipbones were enough like ours so that they must have stood erect. -There is no good reason to think they couldn�t have walked as we do. - - -BRAINS, HANDS, AND TOOLS - -Whether the australopithecines were our ancestors or not, the proper -ancestors of men must have been able to stand erect and to walk on -their two feet. Three further important things probably were involved, -next, before they could become men proper. These are: - - 1. The increasing size and development of the brain. - - 2. The increasing usefulness (specialization) of the thumb and hand. - - 3. The use of tools. - -Nobody knows which of these three is most important, or which came -first. Most probably the growth of all three things was very much -blended together. If you think about each of the things, you will see -what I mean. Unless your hand is more flexible than a paw, and your -thumb will work against (or oppose) your fingers, you can�t hold a tool -very well. But you wouldn�t get the idea of using a tool unless you had -enough brain to help you see cause and effect. And it is rather hard to -see how your hand and brain would develop unless they had something to -practice on--like using tools. In Professor Krogman�s words, �the hand -must become the obedient servant of the eye and the brain.� It is the -_co-ordination_ of these things that counts. - -Many other things must have been happening to the bodies of the -creatures who were the ancestors of men. Our ancestors had to develop -organs of speech. More than that, they had to get the idea of letting -_certain sounds_ made with these speech organs have _certain meanings_. - -All this must have gone very slowly. Probably everything was developing -little by little, all together. Men became men very slowly. - - -WHEN SHALL WE CALL MEN MEN? - -What do I mean when I say �men�? People who looked pretty much as we -do, and who used different tools to do different things, are men to me. -We�ll probably never know whether the earliest ones talked or not. They -probably had vocal cords, so they could make sounds, but did they know -how to make sounds work as symbols to carry meanings? But if the fossil -bones look like our skeletons, and if we find tools which we�ll agree -couldn�t have been made by nature or by animals, then I�d say we had -traces of _men_. - -The australopithecine finds of the Transvaal and Bechuanaland, in -south Africa, are bound to come into the discussion here. I�ve already -told you that the australopithecines could have stood upright and -walked on their two hind legs. They come from the very base of the -Pleistocene or Ice Age, and a few coarse stone tools have been found -with the australopithecine fossils. But there are three varieties -of the australopithecines and they last on until a time equal to -that of the second alpine glaciation. They are the best suggestion -we have yet as to what the ancestors of men _may_ have looked like. -They were certainly closer to men than to apes. Although their brain -size was no larger than the brains of modern apes their body size and -stature were quite small; hence, relative to their small size, their -brains were large. We have not been able to prove without doubt that -the australopithecines were _tool-making_ creatures, even though the -recent news has it that tools have been found with australopithecine -bones. The doubt as to whether the australopithecines used the tools -themselves goes like this--just suppose some man-like creature (whose -bones we have not yet found) made the tools and used them to kill -and butcher australopithecines. Hence a few experts tend to let -australopithecines still hang in limbo as �man-apes.� - - -THE EARLIEST MEN WE KNOW - -I�ll postpone talking about the tools of early men until the next -chapter. The men whose bones were the earliest of the Java lot have -been given the name _Meganthropus_. The bones are very fragmentary. We -would not understand them very well unless we had the somewhat later -Javanese lot--the more commonly known _Pithecanthropus_ or �Java -man�--against which to refer them for study. One of the less well-known -and earliest fragments, a piece of lower jaw and some teeth, rather -strongly resembles the lower jaws and teeth of the australopithecine -type. Was _Meganthropus_ a sort of half-way point between the -australopithecines and _Pithecanthropus_? It is still too early to say. -We shall need more finds before we can be definite one way or the other. - -Java man, _Pithecanthropus_, comes from geological beds equal in age -to the latter part of the second alpine glaciation; the _Meganthropus_ -finds refer to beds of the beginning of this glaciation. The first -finds of Java man were made in 1891-92 by Dr. Eugene Dubois, a Dutch -doctor in the colonial service. Finds have continued to be made. There -are now bones enough to account for four skulls. There are also four -jaws and some odd teeth and thigh bones. Java man, generally speaking, -was about five feet six inches tall, and didn�t hold his head very -erect. His skull was very thick and heavy and had room for little more -than two-thirds as large a brain as we have. He had big teeth and a big -jaw and enormous eyebrow ridges. - -No tools were found in the geological deposits where bones of Java man -appeared. There are some tools in the same general area, but they come -a bit later in time. One reason we accept the Java man as man--aside -from his general anatomical appearance--is that these tools probably -belonged to his near descendants. - -Remember that there are several varieties of men in the whole early -Java lot, at least two of which are earlier than the _Pithecanthropus_, -�Java man.� Some of the earlier ones seem to have gone in for -bigness, in tooth-size at least. _Meganthropus_ is one of these -earlier varieties. As we said, he _may_ turn out to be a link to -the australopithecines, who _may_ or _may not_ be ancestral to men. -_Meganthropus_ is best understandable in terms of _Pithecanthropus_, -who appeared later in the same general area. _Pithecanthropus_ is -pretty well understandable from the bones he left us, and also because -of his strong resemblance to the fully tool-using cave-dwelling �Peking -man,� _Sinanthropus_, about whom we shall talk next. But you can see -that the physical anthropologists and prehistoric archeologists still -have a lot of work to do on the problem of earliest men. - - -PEKING MEN AND SOME EARLY WESTERNERS - -The earliest known Chinese are called _Sinanthropus_, or �Peking man,� -because the finds were made near that city. In World War II, the United -States Marine guard at our Embassy in Peking tried to help get the -bones out of the city before the Japanese attack. Nobody knows where -these bones are now. The Red Chinese accuse us of having stolen them. -They were last seen on a dock-side at a Chinese port. But should you -catch a Marine with a sack of old bones, perhaps we could achieve peace -in Asia by returning them! Fortunately, there is a complete set of -casts of the bones. - -Peking man lived in a cave in a limestone hill, made tools, cracked -animal bones to get the marrow out, and used fire. Incidentally, the -bones of Peking man were found because Chinese dig for what they call -�dragon bones� and �dragon teeth.� Uneducated Chinese buy these things -in their drug stores and grind them into powder for medicine. The -�dragon teeth� and �bones� are really fossils of ancient animals, and -sometimes of men. The people who supply the drug stores have learned -where to dig for strange bones and teeth. Paleontologists who get to -China go to the drug stores to buy fossils. In a roundabout way, this -is how the fallen-in cave of Peking man at Choukoutien was discovered. - -Peking man was not quite as tall as Java man but he probably stood -straighter. His skull looked very much like that of the Java skull -except that it had room for a slightly larger brain. His face was less -brutish than was Java man�s face, but this isn�t saying much. - -Peking man dates from early in the interglacial period following the -second alpine glaciation. He probably lived close to 350,000 years -ago. There are several finds to account for in Europe by about this -time, and one from northwest Africa. The very large jawbone found -near Heidelberg in Germany is doubtless even earlier than Peking man. -The beds where it was found are of second alpine glacial times, and -recently some tools have been said to have come from the same beds. -There is not much I need tell you about the Heidelberg jaw save that it -seems certainly to have belonged to an early man, and that it is very -big. - -Another find in Germany was made at Steinheim. It consists of the -fragmentary skull of a man. It is very important because of its -relative completeness, but it has not yet been fully studied. The bone -is thick, but the back of the head is neither very low nor primitive, -and the face is also not primitive. The forehead does, however, have -big ridges over the eyes. The more fragmentary skull from Swanscombe in -England (p. 11) has been much more carefully studied. Only the top and -back of that skull have been found. Since the skull rounds up nicely, -it has been assumed that the face and forehead must have been quite -�modern.� Careful comparison with Steinheim shows that this was not -necessarily so. This is important because it bears on the question of -how early truly �modern� man appeared. - -Recently two fragmentary jaws were found at Ternafine in Algeria, -northwest Africa. They look like the jaws of Peking man. Tools were -found with them. Since no jaws have yet been found at Steinheim or -Swanscombe, but the time is the same, one wonders if these people had -jaws like those of Ternafine. - - -WHAT HAPPENED TO JAVA AND PEKING MEN - -Professor Weidenreich thought that there were at least a dozen ways in -which the Peking man resembled the modern Mongoloids. This would seem -to indicate that Peking man was really just a very early Chinese. - -Several later fossil men have been found in the Java-Australian area. -The best known of these is the so-called Solo man. There are some finds -from Australia itself which we now know to be quite late. But it looks -as if we may assume a line of evolution from Java man down to the -modern Australian natives. During parts of the Ice Age there was a land -bridge all the way from Java to Australia. - - -TWO ENGLISHMEN WHO WEREN�T OLD - -The older textbooks contain descriptions of two English finds which -were thought to be very old. These were called Piltdown (_Eoanthropus -dawsoni_) and Galley Hill. The skulls were very modern in appearance. -In 1948-49, British scientists began making chemical tests which proved -that neither of these finds is very old. It is now known that both -�Piltdown man� and the tools which were said to have been found with -him were part of an elaborate fake! - - -TYPICAL �CAVE MEN� - -The next men we have to talk about are all members of a related group. -These are the Neanderthal group. �Neanderthal man� himself was found in -the Neander Valley, near D�sseldorf, Germany, in 1856. He was the first -human fossil to be recognized as such. - -[Illustration: PRINCIPAL KNOWN TYPES OF FOSSIL MEN - - CRO-MAGNON - NEANDERTHAL - MODERN SKULL - COMBE-CAPELLE - SINANTHROPUS - PITHECANTHROPUS] - -Some of us think that the neanderthaloids proper are only those people -of western Europe who didn�t get out before the beginning of the last -great glaciation, and who found themselves hemmed in by the glaciers -in the Alps and northern Europe. Being hemmed in, they intermarried -a bit too much and developed into a special type. Professor F. Clark -Howell sees it this way. In Europe, the earliest trace of men we -now know is the Heidelberg jaw. Evolution continued in Europe, from -Heidelberg through the Swanscombe and Steinheim types to a group of -pre-neanderthaloids. There are traces of these pre-neanderthaloids -pretty much throughout Europe during the third interglacial period--say -100,000 years ago. The pre-neanderthaloids are represented by such -finds as the ones at Ehringsdorf in Germany and Saccopastore in Italy. -I won�t describe them for you, since they are simply less extreme than -the neanderthaloids proper--about half way between Steinheim and the -classic Neanderthal people. - -Professor Howell believes that the pre-neanderthaloids who happened to -get caught in the pocket of the southwest corner of Europe at the onset -of the last great glaciation became the classic Neanderthalers. Out in -the Near East, Howell thinks, it is possible to see traces of people -evolving from the pre-neanderthaloid type toward that of fully modern -man. Certainly, we don�t see such extreme cases of �neanderthaloidism� -outside of western Europe. - -There are at least a dozen good examples in the main or classic -Neanderthal group in Europe. They date to just before and in the -earlier part of the last great glaciation (85,000 to 40,000 years ago). -Many of the finds have been made in caves. The �cave men� the movies -and the cartoonists show you are probably meant to be Neanderthalers. -I�m not at all sure they dragged their women by the hair; the women -were probably pretty tough, too! - -Neanderthal men had large bony heads, but plenty of room for brains. -Some had brain cases even larger than the average for modern man. Their -faces were heavy, and they had eyebrow ridges of bone, but the ridges -were not as big as those of Java man. Their foreheads were very low, -and they didn�t have much chin. They were about five feet three inches -tall, but were heavy and barrel-chested. But the Neanderthalers didn�t -slouch as much as they�ve been blamed for, either. - -One important thing about the Neanderthal group is that there is a fair -number of them to study. Just as important is the fact that we know -something about how they lived, and about some of the tools they made. - - -OTHER MEN CONTEMPORARY WITH THE NEANDERTHALOIDS - -We have seen that the neanderthaloids seem to be a specialization -in a corner of Europe. What was going on elsewhere? We think that -the pre-neanderthaloid type was a generally widespread form of men. -From this type evolved other more or less extreme although generally -related men. The Solo finds in Java form one such case. Another was the -Rhodesian man of Africa, and the more recent Hopefield finds show more -of the general Rhodesian type. It is more confusing than it needs to be -if these cases outside western Europe are called neanderthaloids. They -lived during the same approximate time range but they were all somewhat -different-looking people. - - -EARLY MODERN MEN - -How early is modern man (_Homo sapiens_), the �wise man�? Some people -have thought that he was very early, a few still think so. Piltdown -and Galley Hill, which were quite modern in anatomical appearance and -_supposedly_ very early in date, were the best �evidence� for very -early modern men. Now that Piltdown has been liquidated and Galley Hill -is known to be very late, what is left of the idea? - -The backs of the skulls of the Swanscombe and Steinheim finds look -rather modern. Unless you pay attention to the face and forehead of the -Steinheim find--which not many people have--and perhaps also consider -the Ternafine jaws, you might come to the conclusion that the crown of -the Swanscombe head was that of a modern-like man. - -Two more skulls, again without faces, are available from a French -cave site, Font�chevade. They come from the time of the last great -interglacial, as did the pre-neanderthaloids. The crowns of the -Font�chevade skulls also look quite modern. There is a bit of the -forehead preserved on one of these skulls and the brow-ridge is not -heavy. Nevertheless, there is a suggestion that the bones belonged to -an immature individual. In this case, his (or even more so, if _her_) -brow-ridges would have been weak anyway. The case for the Font�chevade -fossils, as modern type men, is little stronger than that for -Swanscombe, although Professor Vallois believes it a good case. - -It seems to add up to the fact that there were people living in -Europe--before the classic neanderthaloids--who looked more modern, -in some features, than the classic western neanderthaloids did. Our -best suggestion of what men looked like--just before they became fully -modern--comes from a cave on Mount Carmel in Palestine. - - -THE FIRST MODERNS - -Professor T. D. McCown and the late Sir Arthur Keith, who studied the -Mount Carmel bones, figured out that one of the two groups involved -was as much as 70 per cent modern. There were, in fact, two groups or -varieties of men in the Mount Carmel caves and in at least two other -Palestinian caves of about the same time. The time would be about that -of the onset of colder weather, when the last glaciation was beginning -in the north--say 75,000 years ago. - -The 70 per cent modern group came from only one cave, Mugharet es-Skhul -(�cave of the kids�). The other group, from several caves, had bones of -men of the type we�ve been calling pre-neanderthaloid which we noted -were widespread in Europe and beyond. The tools which came with each -of these finds were generally similar, and McCown and Keith, and other -scholars since their study, have tended to assume that both the Skhul -group and the pre-neanderthaloid group came from exactly the same time. -The conclusion was quite natural: here was a population of men in the -act of evolving in two different directions. But the time may not be -exactly the same. It is very difficult to be precise, within say 10,000 -years, for a time some 75,000 years ago. If the Skhul men are in fact -later than the pre-neanderthaloid group of Palestine, as some of us -think, then they show how relatively modern some men were--men who -lived at the same time as the classic Neanderthalers of the European -pocket. - -Soon after the first extremely cold phase of the last glaciation, we -begin to get a number of bones of completely modern men in Europe. -We also get great numbers of the tools they made, and their living -places in caves. Completely modern skeletons begin turning up in caves -dating back to toward 40,000 years ago. The time is about that of the -beginning of the second phase of the last glaciation. These skeletons -belonged to people no different from many people we see today. Like -people today, not everybody looked alike. (The positions of the more -important fossil men of later Europe are shown in the chart on page -72.) - - -DIFFERENCES IN THE EARLY MODERNS - -The main early European moderns have been divided into two groups, the -Cro-Magnon group and the Combe Capelle-Br�nn group. Cro-Magnon people -were tall and big-boned, with large, long, and rugged heads. They -must have been built like many present-day Scandinavians. The Combe -Capelle-Br�nn people were shorter; they had narrow heads and faces, and -big eyebrow-ridges. Of course we don�t find the skin or hair of these -people. But there is little doubt they were Caucasoids (�Whites�). - -Another important find came in the Italian Riviera, near Monte Carlo. -Here, in a cave near Grimaldi, there was a grave containing a woman -and a young boy, buried together. The two skeletons were first called -�Negroid� because some features of their bones were thought to resemble -certain features of modern African Negro bones. But more recently, -Professor E. A. Hooton and other experts questioned the use of the word -�Negroid� in describing the Grimaldi skeletons. It is true that nothing -is known of the skin color, hair form, or any other fleshy feature of -the Grimaldi people, so that the word �Negroid� in its usual meaning is -not proper here. It is also not clear whether the features of the bones -claimed to be �Negroid� are really so at all. - -From a place called Wadjak, in Java, we have �proto-Australoid� skulls -which closely resemble those of modern Australian natives. Some of -the skulls found in South Africa, especially the Boskop skull, look -like those of modern Bushmen, but are much bigger. The ancestors of -the Bushmen seem to have once been very widespread south of the Sahara -Desert. True African Negroes were forest people who apparently expanded -out of the west central African area only in the last several thousand -years. Although dark in skin color, neither the Australians nor the -Bushmen are Negroes; neither the Wadjak nor the Boskop skulls are -�Negroid.� - -As we�ve already mentioned, Professor Weidenreich believed that Peking -man was already on the way to becoming a Mongoloid. Anyway, the -Mongoloids would seem to have been present by the time of the �Upper -Cave� at Choukoutien, the _Sinanthropus_ find-spot. - - -WHAT THE DIFFERENCES MEAN - -What does all this difference mean? It means that, at one moment in -time, within each different area, men tended to look somewhat alike. -From area to area, men tended to look somewhat different, just as -they do today. This is all quite natural. People _tended_ to mate -near home; in the anthropological jargon, they made up geographically -localized breeding populations. The simple continental division of -�stocks�--black = Africa, yellow = Asia, white = Europe--is too simple -a picture to fit the facts. People became accustomed to life in some -particular area within a continent (we might call it a �natural area�). -As they went on living there, they evolved towards some particular -physical variety. It would, of course, have been difficult to draw -a clear boundary between two adjacent areas. There must always have -been some mating across the boundaries in every case. One thing human -beings don�t do, and never have done, is to mate for �purity.� It is -self-righteous nonsense when we try to kid ourselves into thinking that -they do. - -I am not going to struggle with the whole business of modern stocks and -races. This is a book about prehistoric men, not recent historic or -modern men. My physical anthropologist friends have been very patient -in helping me to write and rewrite this chapter--I am not going to -break their patience completely. Races are their business, not mine, -and they must do the writing about races. I shall, however, give two -modern definitions of race, and then make one comment. - - Dr. William G. Boyd, professor of Immunochemistry, School of - Medicine, Boston University: �We may define a human race as a - population which differs significantly from other human populations - in regard to the frequency of one or more of the genes it - possesses.� - - Professor Sherwood L. Washburn, professor of Physical Anthropology, - Department of Anthropology, the University of California: �A �race� - is a group of genetically similar populations, and races intergrade - because there are always intermediate populations.� - -My comment is that the ideas involved here are all biological: they -concern groups, _not_ individuals. Boyd and Washburn may differ a bit -on what they want to consider a �population,� but a population is a -group nevertheless, and genetics is biology to the hilt. Now a lot of -people still think of race in terms of how people dress or fix their -food or of other habits or customs they have. The next step is to talk -about racial �purity.� None of this has anything whatever to do with -race proper, which is a matter of the biology of groups. - -Incidentally, I�m told that if man very carefully _controls_ -the breeding of certain animals over generations--dogs, cattle, -chickens--he might achieve a �pure� race of animals. But he doesn�t do -it. Some unfortunate genetic trait soon turns up, so this has just as -carefully to be bred out again, and so on. - - -SUMMARY OF PRESENT KNOWLEDGE OF FOSSIL MEN - -The earliest bones of men we now have--upon which all the experts -would probably agree--are those of _Meganthropus_, from Java, of about -450,000 years ago. The earlier australopithecines of Africa were -possibly not tool-users and may not have been ancestral to men at all. -But there is an alternate and evidently increasingly stronger chance -that some of them may have been. The Kanam jaw from Kenya, another -early possibility, is not only very incomplete but its find-spot is -very questionable. - -Java man proper, _Pithecanthropus_, comes next, at about 400,000 years -ago, and the big Heidelberg jaw in Germany must be of about the same -date. Next comes Swanscombe in England, Steinheim in Germany, the -Ternafine jaws in Algeria, and Peking man, _Sinanthropus_. They all -date to the second great interglacial period, about 350,000 years ago. - -Piltdown and Galley Hill are out, and with them, much of the starch -in the old idea that there were two distinct lines of development -in human evolution: (1) a line of �paleoanthropic� development from -Heidelberg to the Neanderthalers where it became extinct, and (2) a -very early �modern� line, through Piltdown, Galley Hill, Swanscombe, to -us. Swanscombe, Steinheim, and Ternafine are just as easily cases of -very early pre-neanderthaloids. - -The pre-neanderthaloids were very widespread during the third -interglacial: Ehringsdorf, Saccopastore, some of the Mount Carmel -people, and probably Font�chevade are cases in point. A variety of -their descendants can be seen, from Java (Solo), Africa (Rhodesian -man), and about the Mediterranean and in western Europe. As the acute -cold of the last glaciation set in, the western Europeans found -themselves surrounded by water, ice, or bitter cold tundra. To vastly -over-simplify it, they �bred in� and became classic neanderthaloids. -But on Mount Carmel, the Skhul cave-find with its 70 per cent modern -features shows what could happen elsewhere at the same time. - -Lastly, from about 40,000 or 35,000 years ago--the time of the onset -of the second phase of the last glaciation--we begin to find the fully -modern skeletons of men. The modern skeletons differ from place to -place, just as different groups of men living in different places still -look different. - -What became of the Neanderthalers? Nobody can tell me for sure. I�ve a -hunch they were simply �bred out� again when the cold weather was over. -Many Americans, as the years go by, are no longer ashamed to claim they -have �Indian blood in their veins.� Give us a few more generations -and there will not be very many other Americans left to whom we can -brag about it. It certainly isn�t inconceivable to me to imagine a -little Cro-Magnon boy bragging to his friends about his tough, strong, -Neanderthaler great-great-great-great-grandfather! - - - - -Cultural BEGINNINGS - -[Illustration] - - -Men, unlike the lower animals, are made up of much more than flesh and -blood and bones; for men have �culture.� - - -WHAT IS CULTURE? - -�Culture� is a word with many meanings. The doctors speak of making a -�culture� of a certain kind of bacteria, and ants are said to have a -�culture.� Then there is the Emily Post kind of �culture�--you say a -person is �cultured,� or that he isn�t, depending on such things as -whether or not he eats peas with his knife. - -The anthropologists use the word too, and argue heatedly over its finer -meanings; but they all agree that every human being is part of or has -some kind of culture. Each particular human group has a particular -culture; that is one of the ways in which we can tell one group of -men from another. In this sense, a CULTURE means the way the members -of a group of people think and believe and live, the tools they make, -and the way they do things. Professor Robert Redfield says a culture -is an organized or formalized body of conventional understandings. -�Conventional understandings� means the whole set of rules, beliefs, -and standards which a group of people lives by. These understandings -show themselves in art, and in the other things a people may make and -do. The understandings continue to last, through tradition, from one -generation to another. They are what really characterize different -human groups. - - -SOME CHARACTERISTICS OF CULTURE - -A culture lasts, although individual men in the group die off. On -the other hand, a culture changes as the different conventions and -understandings change. You could almost say that a culture lives in the -minds of the men who have it. But people are not born with it; they -get it as they grow up. Suppose a day-old Hungarian baby is adopted by -a family in Oshkosh, Wisconsin, and the child is not told that he is -Hungarian. He will grow up with no more idea of Hungarian culture than -anyone else in Oshkosh. - -So when I speak of ancient Egyptian culture, I mean the whole body -of understandings and beliefs and knowledge possessed by the ancient -Egyptians. I mean their beliefs as to why grain grew, as well as their -ability to make tools with which to reap the grain. I mean their -beliefs about life after death. What I am thinking about as culture is -a thing which lasted in time. If any one Egyptian, even the Pharaoh, -died, it didn�t affect the Egyptian culture of that particular moment. - - -PREHISTORIC CULTURES - -For that long period of man�s history that is all prehistory, we have -no written descriptions of cultures. We find only the tools men made, -the places where they lived, the graves in which they buried their -dead. Fortunately for us, these tools and living places and graves all -tell us something about the ways these men lived and the things they -believed. But the story we learn of the very early cultures must be -only a very small part of the whole, for we find so few things. The -rest of the story is gone forever. We have to do what we can with what -we find. - -For all of the time up to about 75,000 years ago, which was the time -of the classic European Neanderthal group of men, we have found few -cave-dwelling places of very early prehistoric men. First, there is the -fallen-in cave where Peking man was found, near Peking. Then there are -two or three other _early_, but not _very early_, possibilities. The -finds at the base of the French cave of Font�chevade, those in one of -the Makapan caves in South Africa, and several open sites such as Dr. -L. S. B. Leakey�s Olorgesailie in Kenya doubtless all lie earlier than -the time of the main European Neanderthal group, but none are so early -as the Peking finds. - -You can see that we know very little about the home life of earlier -prehistoric men. We find different kinds of early stone tools, but we -can�t even be really sure which tools may have been used together. - - -WHY LITTLE HAS LASTED FROM EARLY TIMES - -Except for the rare find-spots mentioned above, all our very early -finds come from geological deposits, or from the wind-blown surfaces -of deserts. Here is what the business of geological deposits really -means. Let us say that a group of people was living in England about -300,000 years ago. They made the tools they needed, lived in some sort -of camp, almost certainly built fires, and perhaps buried their dead. -While the climate was still warm, many generations may have lived in -the same place, hunting, and gathering nuts and berries; but after some -few thousand years, the weather began very gradually to grow colder. -These early Englishmen would not have known that a glacier was forming -over northern Europe. They would only have noticed that the animals -they hunted seemed to be moving south, and that the berries grew larger -toward the south. So they would have moved south, too. - -The camp site they left is the place we archeologists would really have -liked to find. All of the different tools the people used would have -been there together--many broken, some whole. The graves, and traces -of fire, and the tools would have been there. But the glacier got -there first! The front of this enormous sheet of ice moved down over -the country, crushing and breaking and plowing up everything, like a -gigantic bulldozer. You can see what happened to our camp site. - -Everything the glacier couldn�t break, it pushed along in front of it -or plowed beneath it. Rocks were ground to gravel, and soil was caught -into the ice, which afterwards melted and ran off as muddy water. Hard -tools of flint sometimes remained whole. Human bones weren�t so hard; -it�s a wonder _any_ of them lasted. Gushing streams of melt water -flushed out the debris from underneath the glacier, and water flowed -off the surface and through great crevasses. The hard materials these -waters carried were even more rolled and ground up. Finally, such -materials were dropped by the rushing waters as gravels, miles from -the front of the glacier. At last the glacier reached its greatest -extent; then it melted backward toward the north. Debris held in the -ice was dropped where the ice melted, or was flushed off by more melt -water. When the glacier, leaving the land, had withdrawn to the sea, -great hunks of ice were broken off as icebergs. These icebergs probably -dropped the materials held in their ice wherever they floated and -melted. There must be many tools and fragmentary bones of prehistoric -men on the bottom of the Atlantic Ocean and the North Sea. - -Remember, too, that these glaciers came and went at least three or four -times during the Ice Age. Then you will realize why the earlier things -we find are all mixed up. Stone tools from one camp site got mixed up -with stone tools from many other camp sites--tools which may have been -made tens of thousands or more years apart. The glaciers mixed them -all up, and so we cannot say which particular sets of tools belonged -together in the first place. - - -�EOLITHS� - -But what sort of tools do we find earliest? For almost a century, -people have been picking up odd bits of flint and other stone in the -oldest Ice Age gravels in England and France. It is now thought these -odd bits of stone weren�t actually worked by prehistoric men. The -stones were given a name, _eoliths_, or �dawn stones.� You can see them -in many museums; but you can be pretty sure that very few of them were -actually fashioned by men. - -It is impossible to pick out �eoliths� that seem to be made in any -one _tradition_. By �tradition� I mean a set of habits for making one -kind of tool for some particular job. No two �eoliths� look very much -alike: tools made as part of some one tradition all look much alike. -Now it�s easy to suppose that the very earliest prehistoric men picked -up and used almost any sort of stone. This wouldn�t be surprising; you -and I do it when we go camping. In other words, some of these �eoliths� -may actually have been used by prehistoric men. They must have used -anything that might be handy when they needed it. We could have figured -that out without the �eoliths.� - - -THE ROAD TO STANDARDIZATION - -Reasoning from what we know or can easily imagine, there should have -been three major steps in the prehistory of tool-making. The first step -would have been simple _utilization_ of what was at hand. This is the -step into which the �eoliths� would fall. The second step would have -been _fashioning_--the haphazard preparation of a tool when there was a -need for it. Probably many of the earlier pebble tools, which I shall -describe next, fall into this group. The third step would have been -_standardization_. Here, men began to make tools according to certain -set traditions. Counting the better-made pebble tools, there are four -such traditions or sets of habits for the production of stone tools in -earliest prehistoric times. Toward the end of the Pleistocene, a fifth -tradition appears. - - -PEBBLE TOOLS - -At the beginning of the last chapter, you�ll remember that I said there -were tools from very early geological beds. The earliest bones of men -have not yet been found in such early beds although the Sterkfontein -australopithecine cave approaches this early date. The earliest tools -come from Africa. They date back to the time of the first great -alpine glaciation and are at least 500,000 years old. The earliest -ones are made of split pebbles, about the size of your fist or a bit -bigger. They go under the name of pebble tools. There are many natural -exposures of early Pleistocene geological beds in Africa, and the -prehistoric archeologists of south and central Africa have concentrated -on searching for early tools. Other finds of early pebble tools have -recently been made in Algeria and Morocco. - -[Illustration: SOUTH AFRICAN PEBBLE TOOL] - -There are probably early pebble tools to be found in areas of the -Old World besides Africa; in fact, some prehistorians already claim -to have identified a few. Since the forms and the distinct ways of -making the earlier pebble tools had not yet sufficiently jelled into -a set tradition, they are difficult for us to recognize. It is not -so difficult, however, if there are great numbers of �possibles� -available. A little later in time the tradition becomes more clearly -set, and pebble tools are easier to recognize. So far, really large -collections of pebble tools have only been found and examined in Africa. - - -CORE-BIFACE TOOLS - -The next tradition we�ll look at is the _core_ or biface one. The tools -are large pear-shaped pieces of stone trimmed flat on the two opposite -sides or �faces.� Hence �biface� has been used to describe these tools. -The front view is like that of a pear with a rather pointed top, and -the back view looks almost exactly the same. Look at them side on, and -you can see that the front and back faces are the same and have been -trimmed to a thin tip. The real purpose in trimming down the two faces -was to get a good cutting edge all around. You can see all this in the -illustration. - -[Illustration: ABBEVILLIAN BIFACE] - -We have very little idea of the way in which these core-bifaces were -used. They have been called �hand axes,� but this probably gives the -wrong idea, for an ax, to us, is not a pointed tool. All of these early -tools must have been used for a number of jobs--chopping, scraping, -cutting, hitting, picking, and prying. Since the core-bifaces tend to -be pointed, it seems likely that they were used for hitting, picking, -and prying. But they have rough cutting edges, so they could have been -used for chopping, scraping, and cutting. - - -FLAKE TOOLS - -The third tradition is the _flake_ tradition. The idea was to get a -tool with a good cutting edge by simply knocking a nice large flake off -a big block of stone. You had to break off the flake in such a way that -it was broad and thin, and also had a good sharp cutting edge. Once you -really got on to the trick of doing it, this was probably a simpler way -to make a good cutting tool than preparing a biface. You have to know -how, though; I�ve tried it and have mashed my fingers more than once. - -The flake tools look as if they were meant mainly for chopping, -scraping, and cutting jobs. When one made a flake tool, the idea seems -to have been to produce a broad, sharp, cutting edge. - -[Illustration: CLACTONIAN FLAKE] - -The core-biface and the flake traditions were spread, from earliest -times, over much of Europe, Africa, and western Asia. The map on page -52 shows the general area. Over much of this great region there was -flint. Both of these traditions seem well adapted to flint, although -good core-bifaces and flakes were made from other kinds of stone, -especially in Africa south of the Sahara. - - -CHOPPERS AND ADZE-LIKE TOOLS - -The fourth early tradition is found in southern and eastern Asia, from -northwestern India through Java and Burma into China. Father Maringer -recently reported an early group of tools in Japan, which most resemble -those of Java, called Patjitanian. The prehistoric men in this general -area mostly used quartz and tuff and even petrified wood for their -stone tools (see illustration, p. 46). - -This fourth early tradition is called the _chopper-chopping tool_ -tradition. It probably has its earliest roots in the pebble tool -tradition of African type. There are several kinds of tools in this -tradition, but all differ from the western core-bifaces and flakes. -There are broad, heavy scrapers or cleavers, and tools with an -adze-like cutting edge. These last-named tools are called �hand adzes,� -just as the core-bifaces of the west have often been called �hand -axes.� The section of an adze cutting edge is ? shaped; the section of -an ax is < shaped. - -[Illustration: ANYATHIAN ADZE-LIKE TOOL] - -There are also pointed pebble tools. Thus the tool kit of these early -south and east Asiatic peoples seems to have included tools for doing -as many different jobs as did the tools of the Western traditions. - -Dr. H. L. Movius has emphasized that the tools which were found in the -Peking cave with Peking man belong to the chopper-tool tradition. This -is the only case as yet where the tools and the man have been found -together from very earliest times--if we except Sterkfontein. - - -DIFFERENCES WITHIN THE TOOL-MAKING TRADITIONS - -The latter three great traditions in the manufacture of stone -tools--and the less clear-cut pebble tools before them--are all we have -to show of the cultures of the men of those times. Changes happened in -each of the traditions. As time went on, the tools in each tradition -were better made. There could also be slight regional differences in -the tools within one tradition. Thus, tools with small differences, but -all belonging to one tradition, can be given special group (facies) -names. - -This naming of special groups has been going on for some time. Here are -some of these names, since you may see them used in museum displays -of flint tools, or in books. Within each tradition of tool-making -(save the chopper tools), the earliest tool type is at the bottom -of the list, just as it appears in the lowest beds of a geological -stratification.[3] - - [3] Archeologists usually make their charts and lists with the - earliest materials at the bottom and the latest on top, since - this is the way they find them in the ground. - - Chopper tool (all about equally early): - Anyathian (Burma) - Choukoutienian (China) - Patjitanian (Java) - Soan (India) - - Flake: - �Typical Mousterian� - Levalloiso-Mousterian - Levalloisian - Tayacian - Clactonian (localized in England) - - Core-biface: - Some blended elements in �Mousterian� - Micoquian (= Acheulean 6 and 7) - Acheulean - Abbevillian (once called �Chellean�) - - Pebble tool: - Oldowan - Ain Hanech - pre-Stellenbosch - Kafuan - -The core-biface and the flake traditions appear in the chart (p. 65). - -The early archeologists had many of the tool groups named before they -ever realized that there were broader tool preparation traditions. This -was understandable, for in dealing with the mixture of things that come -out of glacial gravels the easiest thing to do first is to isolate -individual types of tools into groups. First you put a bushel-basketful -of tools on a table and begin matching up types. Then you give names to -the groups of each type. The groups and the types are really matters of -the archeologists� choice; in real life, they were probably less exact -than the archeologists� lists of them. We now know pretty well in which -of the early traditions the various early groups belong. - - -THE MEANING OF THE DIFFERENT TRADITIONS - -What do the traditions really mean? I see them as the standardization -of ways to make tools for particular jobs. We may not know exactly what -job the maker of a particular core-biface or flake tool had in mind. We -can easily see, however, that he already enjoyed a know-how, a set of -persistent habits of tool preparation, which would always give him the -same type of tool when he wanted to make it. Therefore, the traditions -show us that persistent habits already existed for the preparation of -one type of tool or another. - -This tells us that one of the characteristic aspects of human culture -was already present. There must have been, in the minds of these -early men, a notion of the ideal type of tool for a particular job. -Furthermore, since we find so many thousands upon thousands of tools -of one type or another, the notion of the ideal types of tools _and_ -the know-how for the making of each type must have been held in common -by many men. The notions of the ideal types and the know-how for their -production must have been passed on from one generation to another. - -I could even guess that the notions of the ideal type of one or the -other of these tools stood out in the minds of men of those times -somewhat like a symbol of �perfect tool for good job.� If this were -so--remember it�s only a wild guess of mine--then men were already -symbol users. Now let�s go on a further step to the fact that the words -men speak are simply sounds, each different sound being a symbol for a -different meaning. If standardized tool-making suggests symbol-making, -is it also possible that crude word-symbols were also being made? I -suppose that it is not impossible. - -There may, of course, be a real question whether tool-utilizing -creatures--our first step, on page 42--were actually men. Other -animals utilize things at hand as tools. The tool-fashioning creature -of our second step is more suggestive, although we may not yet feel -sure that many of the earlier pebble tools were man-made products. But -with the step to standardization and the appearance of the traditions, -I believe we must surely be dealing with the traces of culture-bearing -_men_. The �conventional understandings� which Professor Redfield�s -definition of culture suggests are now evidenced for us in the -persistent habits for the preparation of stone tools. Were we able to -see the other things these prehistoric men must have made--in materials -no longer preserved for the archeologist to find--I believe there would -be clear signs of further conventional understandings. The men may have -been physically primitive and pretty shaggy in appearance, but I think -we must surely call them men. - - -AN OLDER INTERPRETATION OF THE WESTERN TRADITIONS - -In the last chapter, I told you that many of the older archeologists -and human paleontologists used to think that modern man was very old. -The supposed ages of Piltdown and Galley Hill were given as evidence -of the great age of anatomically modern man, and some interpretations -of the Swanscombe and Font�chevade fossils were taken to support -this view. The conclusion was that there were two parallel lines or -�phyla� of men already present well back in the Pleistocene. The -first of these, the more primitive or �paleoanthropic� line, was -said to include Heidelberg, the proto-neanderthaloids and classic -Neanderthal. The more anatomically modern or �neanthropic� line was -thought to consist of Piltdown and the others mentioned above. The -Neanderthaler or paleoanthropic line was thought to have become extinct -after the first phase of the last great glaciation. Of course, the -modern or neanthropic line was believed to have persisted into the -present, as the basis for the world�s population today. But with -Piltdown liquidated, Galley Hill known to be very late, and Swanscombe -and Font�chevade otherwise interpreted, there is little left of the -so-called parallel phyla theory. - -While the theory was in vogue, however, and as long as the European -archeological evidence was looked at in one short-sighted way, the -archeological materials _seemed_ to fit the parallel phyla theory. It -was simply necessary to believe that the flake tools were made only -by the paleoanthropic Neanderthaler line, and that the more handsome -core-biface tools were the product of the neanthropic modern-man line. - -Remember that _almost_ all of the early prehistoric European tools -came only from the redeposited gravel beds. This means that the tools -were not normally found in the remains of camp sites or work shops -where they had actually been dropped by the men who made and used -them. The tools came, rather, from the secondary hodge-podge of the -glacial gravels. I tried to give you a picture of the bulldozing action -of glaciers (p. 40) and of the erosion and weathering that were -side-effects of a glacially conditioned climate on the earth�s surface. -As we said above, if one simply plucks tools out of the redeposited -gravels, his natural tendency is to �type� the tools by groups, and to -think that the groups stand for something _on their own_. - -In 1906, M. Victor Commont actually made a rare find of what seems -to have been a kind of workshop site, on a terrace above the Somme -river in France. Here, Commont realized, flake tools appeared clearly -in direct association with core-biface tools. Few prehistorians paid -attention to Commont or his site, however. It was easier to believe -that flake tools represented a distinct �culture� and that this -�culture� was that of the Neanderthaler or paleoanthropic line, and -that the core-bifaces stood for another �culture� which was that of the -supposed early modern or neanthropic line. Of course, I am obviously -skipping many details here. Some later sites with Neanderthal fossils -do seem to have only flake tools, but other such sites have both types -of tools. The flake tools which appeared _with_ the core-bifaces -in the Swanscombe gravels were never made much of, although it -was embarrassing for the parallel phyla people that Font�chevade -ran heavily to flake tools. All in all, the parallel phyla theory -flourished because it seemed so neat and easy to understand. - - -TRADITIONS ARE TOOL-MAKING HABITS, NOT CULTURES - -In case you think I simply enjoy beating a dead horse, look in any -standard book on prehistory written twenty (or even ten) years ago, or -in most encyclopedias. You�ll find that each of the individual tool -types, of the West, at least, was supposed to represent a �culture.� -The �cultures� were believed to correspond to parallel lines of human -evolution. - -In 1937, Mr. Harper Kelley strongly re-emphasized the importance -of Commont�s workshop site and the presence of flake tools with -core-bifaces. Next followed Dr. Movius� clear delineation of the -chopper-chopping tool tradition of the Far East. This spoiled the nice -symmetry of the flake-tool = paleoanthropic, core-biface = neanthropic -equations. Then came increasing understanding of the importance of -the pebble tools in Africa, and the location of several more workshop -sites there, especially at Olorgesailie in Kenya. Finally came the -liquidation of Piltdown and the deflation of Galley Hill�s date. So it -is at last possible to picture an individual prehistoric man making a -flake tool to do one job and a core-biface tool to do another. Commont -showed us this picture in 1906, but few believed him. - -[Illustration: DISTRIBUTION OF TOOL-PREPARATION TRADITIONS - -Time approximately 100,000 years ago] - -There are certainly a few cases in which flake tools did appear with -few or no core-bifaces. The flake-tool group called Clactonian in -England is such a case. Another good, but certainly later case is -that of the cave on Mount Carmel in Palestine, where the blended -pre-neanderthaloid, 70 per cent modern-type skulls were found. Here, in -the same level with the skulls, were 9,784 flint tools. Of these, only -three--doubtless strays--were core-bifaces; all the rest were flake -tools or flake chips. We noted above how the Font�chevade cave ran to -flake tools. The only conclusion I would draw from this is that times -and circumstances did exist in which prehistoric men needed only flake -tools. So they only made flake tools for those particular times and -circumstances. - - -LIFE IN EARLIEST TIMES - -What do we actually know of life in these earliest times? In the -glacial gravels, or in the terrace gravels of rivers once swollen by -floods of melt water or heavy rains, or on the windswept deserts, we -find stone tools. The earliest and coarsest of these are the pebble -tools. We do not yet know what the men who made them looked like, -although the Sterkfontein australopithecines probably give us a good -hint. Then begin the more formal tool preparation traditions of the -west--the core-bifaces and the flake tools--and the chopper-chopping -tool series of the farther east. There is an occasional roughly worked -piece of bone. From the gravels which yield the Clactonian flakes of -England comes the fire-hardened point of a wooden spear. There are -also the chance finds of the fossil human bones themselves, of which -we spoke in the last chapter. Aside from the cave of Peking man, none -of the earliest tools have been found in caves. Open air or �workshop� -sites which do not seem to have been disturbed later by some geological -agency are very rare. - -The chart on page 65 shows graphically what the situation in -west-central Europe seems to have been. It is not yet certain whether -there were pebble tools there or not. The Font�chevade cave comes -into the picture about 100,000 years ago or more. But for the earlier -hundreds of thousands of years--below the red-dotted line on the -chart--the tools we find come almost entirely from the haphazard -mixture within the geological contexts. - -The stone tools of each of the earlier traditions are the simplest -kinds of all-purpose tools. Almost any one of them could be used for -hacking, chopping, cutting, and scraping; so the men who used them must -have been living in a rough and ready sort of way. They found or hunted -their food wherever they could. In the anthropological jargon, they -were �food-gatherers,� pure and simple. - -Because of the mixture in the gravels and in the materials they -carried, we can�t be sure which animals these men hunted. Bones of -the larger animals turn up in the gravels, but they could just as -well belong to the animals who hunted the men, rather than the other -way about. We don�t know. This is why camp sites like Commont�s and -Olorgesailie in Kenya are so important when we do find them. The animal -bones at Olorgesailie belonged to various mammals of extremely large -size. Probably they were taken in pit-traps, but there are a number of -groups of three round stones on the site which suggest that the people -used bolas. The South American Indians used three-ball bolas, with the -stones in separate leather bags connected by thongs. These were whirled -and then thrown through the air so as to entangle the feet of a fleeing -animal. - -Professor F. Clark Howell recently returned from excavating another -important open air site at Isimila in Tanganyika. The site yielded -the bones of many fossil animals and also thousands of core-bifaces, -flakes, and choppers. But Howell�s reconstruction of the food-getting -habits of the Isimila people certainly suggests that the word �hunting� -is too dignified for what they did; �scavenging� would be much nearer -the mark. - -During a great part of this time the climate was warm and pleasant. The -second interglacial period (the time between the second and third great -alpine glaciations) lasted a long time, and during much of this time -the climate may have been even better than ours is now. We don�t know -that earlier prehistoric men in Europe or Africa lived in caves. They -may not have needed to; much of the weather may have been so nice that -they lived in the open. Perhaps they didn�t wear clothes, either. - - -WHAT THE PEKING CAVE-FINDS TELL US - -The one early cave-dwelling we have found is that of Peking man, in -China. Peking man had fire. He probably cooked his meat, or used -the fire to keep dangerous animals away from his den. In the cave -were bones of dangerous animals, members of the wolf, bear, and cat -families. Some of the cat bones belonged to beasts larger than tigers. -There were also bones of other wild animals: buffalo, camel, deer, -elephants, horses, sheep, and even ostriches. Seventy per cent of the -animals Peking man killed were fallow deer. It�s much too cold and dry -in north China for all these animals to live there today. So this list -helps us know that the weather was reasonably warm, and that there was -enough rain to grow grass for the grazing animals. The list also helps -the paleontologists to date the find. - -Peking man also seems to have eaten plant food, for there are hackberry -seeds in the debris of the cave. His tools were made of sandstone and -quartz and sometimes of a rather bad flint. As we�ve already seen, they -belong in the chopper-tool tradition. It seems fairly clear that some -of the edges were chipped by right-handed people. There are also many -split pieces of heavy bone. Peking man probably split them so he could -eat the bone marrow, but he may have used some of them as tools. - -Many of these split bones were the bones of Peking men. Each one of the -skulls had already had the base broken out of it. In no case were any -of the bones resting together in their natural relation to one another. -There is nothing like a burial; all of the bones are scattered. Now -it�s true that animals could have scattered bodies that were not cared -for or buried. But splitting bones lengthwise and carefully removing -the base of a skull call for both the tools and the people to use them. -It�s pretty clear who the people were. Peking man was a cannibal. - - * * * * * - -This rounds out about all we can say of the life and times of early -prehistoric men. In those days life was rough. You evidently had to -watch out not only for dangerous animals but also for your fellow men. -You ate whatever you could catch or find growing. But you had sense -enough to build fires, and you had already formed certain habits for -making the kinds of stone tools you needed. That�s about all we know. -But I think we�ll have to admit that cultural beginnings had been made, -and that these early people were really _men_. - - - - -MORE EVIDENCE of Culture - -[Illustration] - - -While the dating is not yet sure, the material that we get from caves -in Europe must go back to about 100,000 years ago; the time of the -classic Neanderthal group followed soon afterwards. We don�t know why -there is no earlier material in the caves; apparently they were not -used before the last interglacial phase (the period just before the -last great glaciation). We know that men of the classic Neanderthal -group were living in caves from about 75,000 to 45,000 years ago. -New radioactive carbon dates even suggest that some of the traces of -culture we�ll describe in this chapter may have lasted to about 35,000 -years ago. Probably some of the pre-neanderthaloid types of men had -also lived in caves. But we have so far found their bones in caves only -in Palestine and at Font�chevade. - - -THE CAVE LAYERS - -In parts of France, some peasants still live in caves. In prehistoric -time, many generations of people lived in them. As a result, many -caves have deep layers of debris. The first people moved in and lived -on the rock floor. They threw on the floor whatever they didn�t want, -and they tracked in mud; nobody bothered to clean house in those days. -Their debris--junk and mud and garbage and what not--became packed -into a layer. As time went on, and generations passed, the layer grew -thicker. Then there might have been a break in the occupation of the -cave for a while. Perhaps the game animals got scarce and the people -moved away; or maybe the cave became flooded. Later on, other people -moved in and began making a new layer of their own on top of the first -layer. Perhaps this process of layering went on in the same cave for a -hundred thousand years; you can see what happened. The drawing on this -page shows a section through such a cave. The earliest layer is on the -bottom, the latest one on top. They go in order from bottom to top, -earliest to latest. This is the _stratification_ we talked about (p. -12). - -[Illustration: SECTION OF SHELTER ON LOWER TERRACE, LE MOUSTIER] - -While we may find a mix-up in caves, it�s not nearly as bad as the -mixing up that was done by glaciers. The animal bones and shells, the -fireplaces, the bones of men, and the tools the men made all belong -together, if they come from one layer. That�s the reason why the cave -of Peking man is so important. It is also the reason why the caves in -Europe and the Near East are so important. We can get an idea of which -things belong together and which lot came earliest and which latest. - -In most cases, prehistoric men lived only in the mouths of caves. -They didn�t like the dark inner chambers as places to live in. They -preferred rock-shelters, at the bases of overhanging cliffs, if there -was enough overhang to give shelter. When the weather was good, they no -doubt lived in the open air as well. I�ll go on using the term �cave� -since it�s more familiar, but remember that I really mean rock-shelter, -as a place in which people actually lived. - -The most important European cave sites are in Spain, France, and -central Europe; there are also sites in England and Italy. A few caves -are known in the Near East and Africa, and no doubt more sites will be -found when the out-of-the-way parts of Europe, Africa, and Asia are -studied. - - -AN �INDUSTRY� DEFINED - -We have already seen that the earliest European cave materials are -those from the cave of Font�chevade. Movius feels certain that the -lowest materials here date back well into the third interglacial stage, -that which lay between the Riss (next to the last) and the W�rm I -(first stage of the last) alpine glaciations. This material consists -of an _industry_ of stone tools, apparently all made in the flake -tradition. This is the first time we have used the word �industry.� -It is useful to call all of the different tools found together in one -layer and made of _one kind of material_ an industry; that is, the -tools must be found together as men left them. Tools taken from the -glacial gravels (or from windswept desert surfaces or river gravels -or any geological deposit) are not �together� in this sense. We might -say the latter have only �geological,� not �archeological� context. -Archeological context means finding things just as men left them. We -can tell what tools go together in an �industrial� sense only if we -have archeological context. - -Up to now, the only things we could have called �industries� were the -worked stone industry and perhaps the worked (?) bone industry of the -Peking cave. We could add some of the very clear cases of open air -sites, like Olorgesailie. We couldn�t use the term for the stone tools -from the glacial gravels, because we do not know which tools belonged -together. But when the cave materials begin to appear in Europe, we can -begin to speak of industries. Most of the European caves of this time -contain industries of flint tools alone. - - -THE EARLIEST EUROPEAN CAVE LAYERS - -We�ve just mentioned the industry from what is said to be the oldest -inhabited cave in Europe; that is, the industry from the deepest layer -of the site at Font�chevade. Apparently it doesn�t amount to much. The -tools are made of stone, in the flake tradition, and are very poorly -worked. This industry is called _Tayacian_. Its type tool seems to be -a smallish flake tool, but there are also larger flakes which seem to -have been fashioned for hacking. In fact, the type tool seems to be -simply a smaller edition of the Clactonian tool (pictured on p. 45). - -None of the Font�chevade tools are really good. There are scrapers, -and more or less pointed tools, and tools that may have been used -for hacking and chopping. Many of the tools from the earlier glacial -gravels are better made than those of this first industry we see in -a European cave. There is so little of this material available that -we do not know which is really typical and which is not. You would -probably find it hard to see much difference between this industry and -a collection of tools of the type called Clactonian, taken from the -glacial gravels, especially if the Clactonian tools were small-sized. - -The stone industry of the bottommost layer of the Mount Carmel cave, -in Palestine, where somewhat similar tools were found, has also been -called Tayacian. - -I shall have to bring in many unfamiliar words for the names of the -industries. The industries are usually named after the places where -they were first found, and since these were in most cases in France, -most of the names which follow will be of French origin. However, -the names have simply become handles and are in use far beyond the -boundaries of France. It would be better if we had a non-place-name -terminology, but archeologists have not yet been able to agree on such -a terminology. - - -THE ACHEULEAN INDUSTRY - -Both in France and in Palestine, as well as in some African cave -sites, the next layers in the deep caves have an industry in both the -core-biface and the flake traditions. The core-biface tools usually -make up less than half of all the tools in the industry. However, -the name of the biface type of tool is generally given to the whole -industry. It is called the _Acheulean_, actually a late form of it, as -�Acheulean� is also used for earlier core-biface tools taken from the -glacial gravels. In western Europe, the name used is _Upper Acheulean_ -or _Micoquian_. The same terms have been borrowed to name layers E and -F in the Tabun cave, on Mount Carmel in Palestine. - -The Acheulean core-biface type of tool is worked on two faces so as -to give a cutting edge all around. The outline of its front view may -be oval, or egg-shaped, or a quite pointed pear shape. The large -chip-scars of the Acheulean core-bifaces are shallow and flat. It is -suspected that this resulted from the removal of the chips with a -wooden club; the deep chip-scars of the earlier Abbevillian core-biface -came from beating the tool against a stone anvil. These tools are -really the best and also the final products of the core-biface -tradition. We first noticed the tradition in the early glacial gravels -(p. 43); now we see its end, but also its finest examples, in the -deeper cave levels. - -The flake tools, which really make up the greater bulk of this -industry, are simple scrapers and chips with sharp cutting edges. The -habits used to prepare them must have been pretty much the same as -those used for at least one of the flake industries we shall mention -presently. - -There is very little else in these early cave layers. We do not have -a proper �industry� of bone tools. There are traces of fire, and of -animal bones, and a few shells. In Palestine, there are many more -bones of deer than of gazelle in these layers; the deer lives in a -wetter climate than does the gazelle. In the European cave layers, the -animal bones are those of beasts that live in a warm climate. They -belonged in the last interglacial period. We have not yet found the -bones of fossil men definitely in place with this industry. - -[Illustration: ACHEULEAN BIFACE] - - -FLAKE INDUSTRIES FROM THE CAVES - -Two more stone industries--the _Levalloisian_ and the -�_Mousterian_�--turn up at approximately the same time in the European -cave layers. Their tools seem to be mainly in the flake tradition, -but according to some of the authorities their preparation also shows -some combination with the habits by which the core-biface tools were -prepared. - -Now notice that I don�t tell you the Levalloisian and the �Mousterian� -layers are both above the late Acheulean layers. Look at the cave -section (p. 57) and you�ll find that some �Mousterian of Acheulean -tradition� appears above some �typical Mousterian.� This means that -there may be some kinds of Acheulean industries that are later than -some kinds of �Mousterian.� The same is true of the Levalloisian. - -There were now several different kinds of habits that men used in -making stone tools. These habits were based on either one or the other -of the two traditions--core-biface or flake--or on combinations of -the habits used in the preparation techniques of both traditions. All -were popular at about the same time. So we find that people who made -one kind of stone tool industry lived in a cave for a while. Then they -gave up the cave for some reason, and people with another industry -moved in. Then the first people came back--or at least somebody with -the same tool-making habits as the first people. Or maybe a third group -of tool-makers moved in. The people who had these different habits for -making their stone tools seem to have moved around a good deal. They no -doubt borrowed and exchanged tricks of the trade with each other. There -were no patent laws in those days. - -The extremely complicated interrelationships of the different habits -used by the tool-makers of this range of time are at last being -systematically studied. M. Fran�ois Bordes has developed a statistical -method of great importance for understanding these tool preparation -habits. - - -THE LEVALLOISIAN AND MOUSTERIAN - -The easiest Levalloisian tool to spot is a big flake tool. The trick -in making it was to fashion carefully a big chunk of stone (called -the Levalloisian �tortoise core,� because it resembles the shape of -a turtle-shell) and then to whack this in such a way that a large -flake flew off. This large thin flake, with sharp cutting edges, is -the finished Levalloisian tool. There were various other tools in a -Levalloisian industry, but this is the characteristic _Levalloisian_ -tool. - -There are several �typical Mousterian� stone tools. Different from -the tools of the Levalloisian type, these were made from �disc-like -cores.� There are medium-sized flake �side scrapers.� There are also -some small pointed tools and some small �hand axes.� The last of these -tool types is often a flake worked on both of the flat sides (that -is, bifacially). There are also pieces of flint worked into the form -of crude balls. The pointed tools may have been fixed on shafts to -make short jabbing spears; the round flint balls may have been used as -bolas. Actually, we don�t _know_ what either tool was used for. The -points and side scrapers are illustrated (pp. 64 and 66). - -[Illustration: LEVALLOIS FLAKE] - - -THE MIXING OF TRADITIONS - -Nowadays the archeologists are less and less sure of the importance -of any one specific tool type and name. Twenty years ago, they used -to speak simply of Acheulean or Levalloisian or Mousterian tools. -Now, more and more, _all_ of the tools from some one layer in a -cave are called an �industry,� which is given a mixed name. Thus we -have �Levalloiso-Mousterian,� and �Acheuleo-Levalloisian,� and even -�Acheuleo-Mousterian� (or �Mousterian of Acheulean tradition�). Bordes� -systematic work is beginning to clear up some of our confusion. - -The time of these late Acheuleo-Levalloiso-Mousterioid industries -is from perhaps as early as 100,000 years ago. It may have lasted -until well past 50,000 years ago. This was the time of the first -phase of the last great glaciation. It was also the time that the -classic group of Neanderthal men was living in Europe. A number of -the Neanderthal fossil finds come from these cave layers. Before the -different habits of tool preparation were understood it used to be -popular to say Neanderthal man was �Mousterian man.� I think this is -wrong. What used to be called �Mousterian� is now known to be a variety -of industries with tools of both core-biface and flake habits, and -so mixed that the word �Mousterian� used alone really doesn�t mean -anything. The Neanderthalers doubtless understood the tool preparation -habits by means of which Acheulean, Levalloisian and Mousterian type -tools were produced. We also have the more modern-like Mount Carmel -people, found in a cave layer of Palestine with tools almost entirely -in the flake tradition, called �Levalloiso-Mousterian,� and the -Font�chevade-Tayacian (p. 59). - -[Illustration: MOUSTERIAN POINT] - - -OTHER SUGGESTIONS OF LIFE IN THE EARLY CAVE LAYERS - -Except for the stone tools, what do we know of the way men lived in the -time range after 100,000 to perhaps 40,000 years ago or even later? -We know that in the area from Europe to Palestine, at least some of -the people (some of the time) lived in the fronts of caves and warmed -themselves over fires. In Europe, in the cave layers of these times, -we find the bones of different animals; the bones in the lowest layers -belong to animals that lived in a warm climate; above them are the -bones of those who could stand the cold, like the reindeer and mammoth. -Thus, the meat diet must have been changing, as the glacier crept -farther south. Shells and possibly fish bones have lasted in these -cave layers, but there is not a trace of the vegetable foods and the -nuts and berries and other wild fruits that must have been eaten when -they could be found. - -[Illustration: CHART SHOWING PRESENT UNDERSTANDING OF RELATIONSHIPS AND -SUCCESSION OF TOOL-PREPARATION TRADITIONS, INDUSTRIES, AND ASSEMBLAGES -OF WEST-CENTRAL EUROPE - -Wavy lines indicate transitions in industrial habits. These transitions -are not yet understood in detail. The glacial and climatic scheme shown -is the alpine one.] - -Bone tools have also been found from this period. Some are called -scrapers, and there are also long chisel-like leg-bone fragments -believed to have been used for skinning animals. Larger hunks of bone, -which seem to have served as anvils or chopping blocks, are fairly -common. - -Bits of mineral, used as coloring matter, have also been found. We -don�t know what the color was used for. - -[Illustration: MOUSTERIAN SIDE SCRAPER] - -There is a small but certain number of cases of intentional burials. -These burials have been found on the floors of the caves; in other -words, the people dug graves in the places where they lived. The holes -made for the graves were small. For this reason (or perhaps for some -other?) the bodies were in a curled-up or contracted position. Flint or -bone tools or pieces of meat seem to have been put in with some of the -bodies. In several cases, flat stones had been laid over the graves. - - -TOOLS FROM AFRICA AND ASIA ABOUT 100,000 YEARS AGO - -Professor Movius characterizes early prehistoric Africa as a continent -showing a variety of stone industries. Some of these industries were -purely local developments and some were practically identical with -industries found in Europe at the same time. From northwest Africa -to Capetown--excepting the tropical rain forest region of the west -center--tools of developed Acheulean, Levalloisian, and Mousterian -types have been recognized. Often they are named after African place -names. - -In east and south Africa lived people whose industries show a -development of the Levalloisian technique. Such industries are -called Stillbay. Another industry, developed on the basis of the -Acheulean technique, is called Fauresmith. From the northwest comes -an industry with tanged points and flake-blades; this is called the -Aterian. The tropical rain forest region contained people whose stone -tools apparently show adjustment to this peculiar environment; the -so-called Sangoan industry includes stone picks, adzes, core-bifaces -of specialized Acheulean type, and bifacial points which were probably -spearheads. - -In western Asia, even as far as the east coast of India, the tools of -the Eurafrican core-biface and flake tool traditions continued to be -used. But in the Far East, as we noted in the last chapter, men had -developed characteristic stone chopper and chopping tools. This tool -preparation tradition--basically a pebble tool tradition--lasted to the -very end of the Ice Age. - -When more intact open air sites such as that of an earlier time at -Olorgesailie, and more stratified cave sites are found and excavated -in Asia and Africa, we shall be able to get a more complete picture. -So far, our picture of the general cultural level of the Old World at -about 100,000 years ago--and soon afterwards--is best from Europe, but -it is still far from complete there, too. - - -CULTURE AT THE BEGINNING OF THE LAST GREAT GLACIAL PERIOD - -The few things we have found must indicate only a very small part -of the total activities of the people who lived at the time. All of -the things they made of wood and bark, of skins, of anything soft, -are gone. The fact that burials were made, at least in Europe and -Palestine, is pretty clear proof that the people had some notion of a -life after death. But what this notion really was, or what gods (if -any) men believed in, we cannot know. Dr. Movius has also reminded me -of the so-called bear cults--cases in which caves have been found which -contain the skulls of bears in apparently purposeful arrangement. This -might suggest some notion of hoarding up the spirits or the strength of -bears killed in the hunt. Probably the people lived in small groups, -as hunting and food-gathering seldom provide enough food for large -groups of people. These groups probably had some kind of leader or -�chief.� Very likely the rude beginnings of rules for community life -and politics, and even law, were being made. But what these were, we -do not know. We can only guess about such things, as we can only guess -about many others; for example, how the idea of a family must have been -growing, and how there may have been witch doctors who made beginnings -in medicine or in art, in the materials they gathered for their trade. - -The stone tools help us most. They have lasted, and we can find -them. As they come to us, from this cave or that, and from this -layer or that, the tool industries show a variety of combinations -of the different basic habits or traditions of tool preparation. -This seems only natural, as the groups of people must have been very -small. The mixtures and blendings of the habits used in making stone -tools must mean that there were also mixtures and blends in many of -the other ideas and beliefs of these small groups. And what this -probably means is that there was no one _culture_ of the time. It is -certainly unlikely that there were simply three cultures, �Acheulean,� -�Levalloisian,� and �Mousterian,� as has been thought in the past. -Rather there must have been a great variety of loosely related cultures -at about the same stage of advancement. We could say, too, that here -we really begin to see, for the first time, that remarkable ability -of men to adapt themselves to a variety of conditions. We shall see -this adaptive ability even more clearly as time goes on and the record -becomes more complete. - -Over how great an area did these loosely related cultures reach in -the time 75,000 to 45,000 or even as late as 35,000 years ago? We -have described stone tools made in one or another of the flake and -core-biface habits, for an enormous area. It covers all of Europe, all -of Africa, the Near East, and parts of India. It is perfectly possible -that the flake and core-biface habits lasted on after 35,000 years ago, -in some places outside of Europe. In northern Africa, for example, we -are certain that they did (see chart, p. 72). - -On the other hand, in the Far East (China, Burma, Java) and in northern -India, the tools of the old chopper-tool tradition were still being -made. Out there, we must assume, there was a different set of loosely -related cultures. At least, there was a different set of loosely -related habits for the making of tools. But the men who made them must -have looked much like the men of the West. Their tools were different, -but just as useful. - -As to what the men of the West looked like, I�ve already hinted at all -we know so far (pp. 29 ff.). The Neanderthalers were present at -the time. Some more modern-like men must have been about, too, since -fossils of them have turned up at Mount Carmel in Palestine, and at -Teshik Tash, in Trans-caspian Russia. It is still too soon to know -whether certain combinations of tools within industries were made -only by certain physical types of men. But since tools of both the -core-biface and the flake traditions, and their blends, turn up from -South Africa to England to India, it is most unlikely that only one -type of man used only one particular habit in the preparation of tools. -What seems perfectly clear is that men in Africa and men in India were -making just as good tools as the men who lived in western Europe. - - - - -EARLY MODERNS - -[Illustration] - - -From some time during the first inter-stadial of the last great -glaciation (say some time after about 40,000 years ago), we have -more accurate dates for the European-Mediterranean area and less -accurate ones for the rest of the Old World. This is probably -because the effects of the last glaciation have been studied in the -European-Mediterranean area more than they have been elsewhere. - - -A NEW TRADITION APPEARS - -Something new was probably beginning to happen in the -European-Mediterranean area about 40,000 years ago, though all the -rest of the Old World seems to have been going on as it had been. I -can�t be sure of this because the information we are using as a basis -for dates is very inaccurate for the areas outside of Europe and the -Mediterranean. - -We can at least make a guess. In Egypt and north Africa, men were still -using the old methods of making stone tools. This was especially true -of flake tools of the Levalloisian type, save that they were growing -smaller and smaller as time went on. But at the same time, a new -tradition was becoming popular in westernmost Asia and in Europe. This -was the blade-tool tradition. - - -BLADE TOOLS - -A stone blade is really just a long parallel-sided flake, as the -drawing shows. It has sharp cutting edges, and makes a very useful -knife. The real trick is to be able to make one. It is almost -impossible to make a blade out of any stone but flint or a natural -volcanic glass called obsidian. And even if you have flint or obsidian, -you first have to work up a special cone-shaped �blade-core,� from -which to whack off blades. - -[Illustration: PLAIN BLADE] - -You whack with a hammer stone against a bone or antler punch which is -directed at the proper place on the blade-core. The blade-core has to -be well supported or gripped while this is going on. To get a good -flint blade tool takes a great deal of know-how. - -Remember that a tradition in stone tools means no more than that some -particular way of making the tools got started and lasted a long time. -Men who made some tools in one tradition or set of habits would also -make other tools for different purposes by means of another tradition -or set of habits. It was even possible for the two sets of habits to -become combined. - - -THE EARLIEST BLADE TOOLS - -The oldest blade tools we have found were deep down in the layers of -the Mount Carmel caves, in Tabun Eb and Ea. Similar tools have been -found in equally early cave levels in Syria; their popularity there -seems to fluctuate a bit. Some more or less parallel-sided flakes are -known in the Levalloisian industry in France, but they are probably -no earlier than Tabun E. The Tabun blades are part of a local late -�Acheulean� industry, which is characterized by core-biface �hand -axes,� but which has many flake tools as well. Professor F. E. -Zeuner believes that this industry may be more than 120,000 years old; -actually its date has not yet been fixed, but it is very old--older -than the fossil finds of modern-like men in the same caves. - -[Illustration: SUCCESSION OF ICE AGE FLINT TYPES, INDUSTRIES, AND -ASSEMBLAGES, AND OF FOSSIL MEN, IN NORTHWESTERN EURAFRASIA] - -For some reason, the habit of making blades in Palestine and Syria was -interrupted. Blades only reappeared there at about the same time they -were first made in Europe, some time after 45,000 years ago; that is, -after the first phase of the last glaciation was ended. - -[Illustration: BACKED BLADE] - -We are not sure just where the earliest _persisting_ habits for the -production of blade tools developed. Impressed by the very early -momentary appearance of blades at Tabun on Mount Carmel, Professor -Dorothy A. Garrod first favored the Near East as a center of origin. -She spoke of �some as yet unidentified Asiatic centre,� which she -thought might be in the highlands of Iran or just beyond. But more -recent work has been done in this area, especially by Professor Coon, -and the blade tools do not seem to have an early appearance there. When -the blade tools reappear in the Syro-Palestinian area, they do so in -industries which also include Levalloiso-Mousterian flake tools. From -the point of view of form and workmanship, the blade tools themselves -are not so fine as those which seem to be making their appearance -in western Europe about the same time. There is a characteristic -Syro-Palestinian flake point, possibly a projectile tip, called the -Emiran, which is not known from Europe. The appearance of blade tools, -together with Levalloiso-Mousterian flakes, continues even after the -Emiran point has gone out of use. - -It seems clear that the production of blade tools did not immediately -swamp the set of older habits in Europe, too; the use of flake -tools also continued there. This was not so apparent to the older -archeologists, whose attention was focused on individual tool types. It -is not, in fact, impossible--although it is certainly not proved--that -the technique developed in the preparation of the Levalloisian tortoise -core (and the striking of the Levalloisian flake from it) might have -followed through to the conical core and punch technique for the -production of blades. Professor Garrod is much impressed with the speed -of change during the later phases of the last glaciation, and its -probable consequences. She speaks of �the greater number of industries -having enough individual character to be classified as distinct ... -since evolution now starts to outstrip diffusion.� Her �evolution� here -is of course an industrial evolution rather than a biological one. -Certainly the people of Europe had begun to make blade tools during -the warm spell after the first phase of the last glaciation. By about -40,000 years ago blades were well established. The bones of the blade -tool makers we�ve found so far indicate that anatomically modern men -had now certainly appeared. Unfortunately, only a few fossil men have -so far been found from the very beginning of the blade tool range in -Europe (or elsewhere). What I certainly shall _not_ tell you is that -conquering bands of fine, strong, anatomically modern men, armed with -superior blade tools, came sweeping out of the East to exterminate the -lowly Neanderthalers. Even if we don�t know exactly what happened, I�d -lay a good bet it wasn�t that simple. - -We do know a good deal about different blade industries in Europe. -Almost all of them come from cave layers. There is a great deal of -complication in what we find. The chart (p. 72) tries to simplify -this complication; in fact, it doubtless simplifies it too much. But -it may suggest all the complication of industries which is going -on at this time. You will note that the upper portion of my much -simpler chart (p. 65) covers the same material (in the section -marked �Various Blade-Tool Industries�). That chart is certainly too -simplified. - -You will realize that all this complication comes not only from -the fact that we are finding more material. It is due also to the -increasing ability of men to adapt themselves to a great variety of -situations. Their tools indicate this adaptiveness. We know there was -a good deal of climatic change at this time. The plants and animals -that men used for food were changing, too. The great variety of tools -and industries we now find reflect these changes and the ability of men -to keep up with the times. Now, for example, is the first time we are -sure that there are tools to _make_ other tools. They also show men�s -increasing ability to adapt themselves. - - -SPECIAL TYPES OF BLADE TOOLS - -The most useful tools that appear at this time were made from blades. - - 1. The �backed� blade. This is a knife made of a flint blade, with - one edge purposely blunted, probably to save the user�s fingers - from being cut. There are several shapes of backed blades (p. - 73). - - [Illustration: TWO BURINS] - - 2. The _burin_ or �graver.� The burin was the original chisel. Its - cutting edge is _transverse_, like a chisel�s. Some burins are - made like a screw-driver, save that burins are sharp. Others have - edges more like the blade of a chisel or a push plane, with - only one bevel. Burins were probably used to make slots in wood - and bone; that is, to make handles or shafts for other tools. - They must also be the tools with which much of the engraving on - bone (see p. 83) was done. There is a bewildering variety of - different kinds of burins. - -[Illustration: TANGED POINT] - - 3. The �tanged� point. These stone points were used to tip arrows or - light spears. They were made from blades, and they had a long tang - at the bottom where they were fixed to the shaft. At the place - where the tang met the main body of the stone point, there was - a marked �shoulder,� the beginnings of a barb. Such points had - either one or two shoulders. - -[Illustration: NOTCHED BLADE] - - 4. The �notched� or �strangulated� blade. Along with the points for - arrows or light spears must go a tool to prepare the arrow or - spear shaft. Today, such a tool would be called a �draw-knife� or - a �spoke-shave,� and this is what the notched blades probably are. - Our spoke-shaves have sharp straight cutting blades and really - �shave.� Notched blades of flint probably scraped rather than cut. - - 5. The �awl,� �drill,� or �borer.� These blade tools are worked out - to a spike-like point. They must have been used for making holes - in wood, bone, shell, skin, or other things. - -[Illustration: DRILL OR AWL] - - 6. The �end-scraper on a blade� is a tool with one or both ends - worked so as to give a good scraping edge. It could have been used - to hollow out wood or bone, scrape hides, remove bark from trees, - and a number of other things (p. 78). - -There is one very special type of flint tool, which is best known from -western Europe in an industry called the Solutrean. These tools were -usually made of blades, but the best examples are so carefully worked -on both sides (bifacially) that it is impossible to see the original -blade. This tool is - - 7. The �laurel leaf� point. Some of these tools were long and - dagger-like, and must have been used as knives or daggers. Others - were small, called �willow leaf,� and must have been mounted on - spear or arrow shafts. Another typical Solutrean tool is the - �shouldered� point. Both the �laurel leaf� and �shouldered� point - types are illustrated (see above and p. 79). - -[Illustration: END-SCRAPER ON A BLADE] - -[Illustration: LAUREL LEAF POINT] - -The industries characterized by tools in the blade tradition also -yield some flake and core tools. We will end this list with two types -of tools that appear at this time. The first is made of a flake; the -second is a core tool. - -[Illustration: SHOULDERED POINT] - - 8. The �keel-shaped round scraper� is usually small and quite round, - and has had chips removed up to a peak in the center. It is called - �keel-shaped� because it is supposed to look (when upside down) - like a section through a boat. Actually, it looks more like a tent - or an umbrella. Its outer edges are sharp all the way around, and - it was probably a general purpose scraping tool (see illustration, - p. 81). - - 9. The �keel-shaped nosed scraper� is a much larger and heavier tool - than the round scraper. It was made on a core with a flat bottom, - and has one nicely worked end or �nose.� Such tools are usually - large enough to be easily grasped, and probably were used like - push planes (see illustration, p. 81). - -[Illustration: KEEL-SHAPED ROUND SCRAPER] - -[Illustration: KEEL-SHAPED NOSED SCRAPER] - -The stone tools (usually made of flint) we have just listed are among -the most easily recognized blade tools, although they show differences -in detail at different times. There are also many other kinds. Not -all of these tools appear in any one industry at one time. Thus the -different industries shown in the chart (p. 72) each have only some -of the blade tools we�ve just listed, and also a few flake tools. Some -industries even have a few core tools. The particular types of blade -tools appearing in one cave layer or another, and the frequency of -appearance of the different types, tell which industry we have in each -layer. - - -OTHER KINDS OF TOOLS - -By this time in Europe--say from about 40,000 to about 10,000 years -ago--we begin to find other kinds of material too. Bone tools begin -to appear. There are knives, pins, needles with eyes, and little -double-pointed straight bars of bone that were probably fish-hooks. The -fish-line would have been fastened in the center of the bar; when the -fish swallowed the bait, the bar would have caught cross-wise in the -fish�s mouth. - -One quite special kind of bone tool is a long flat point for a light -spear. It has a deep notch cut up into the breadth of its base, and is -called a �split-based bone point� (p. 82). We know examples of bone -beads from these times, and of bone handles for flint tools. Pierced -teeth of some animals were worn as beads or pendants, but I am not sure -that elks� teeth were worn this early. There are even spool-shaped -�buttons� or toggles. - -[Illustration: SPLIT-BASED BONE POINT] - -[Illustration: SPEAR-THROWER] - -[Illustration: BONE HARPOON] - -Antler came into use for tools, especially in central and western -Europe. We do not know the use of one particular antler tool that -has a large hole bored in one end. One suggestion is that it was -a thong-stropper used to strop or work up hide thongs (see -illustration, below); another suggestion is that it was an arrow-shaft -straightener. - -Another interesting tool, usually of antler, is the spear-thrower, -which is little more than a stick with a notch or hook on one end. -The hook fits into the butt end of the spear, and the length of the -spear-thrower allows you to put much more power into the throw (p. -82). It works on pretty much the same principle as the sling. - -Very fancy harpoons of antler were also made in the latter half of -the period in western Europe. These harpoons had barbs on one or both -sides and a base which would slip out of the shaft (p. 82). Some have -engraved decoration. - - -THE BEGINNING OF ART - -[Illustration: THONG-STROPPER] - -In western Europe, at least, the period saw the beginning of several -kinds of art work. It is handy to break the art down into two great -groups: the movable art, and the cave paintings and sculpture. The -movable art group includes the scratchings, engravings, and modeling -which decorate tools and weapons. Knives, stroppers, spear-throwers, -harpoons, and sometimes just plain fragments of bone or antler are -often carved. There is also a group of large flat pebbles which seem -almost to have served as sketch blocks. The surfaces of these various -objects may show animals, or rather abstract floral designs, or -geometric designs. - -[Illustration: �VENUS� FIGURINE FROM WILLENDORF] - -Some of the movable art is not done on tools. The most remarkable -examples of this class are little figures of women. These women seem to -be pregnant, and their most female characteristics are much emphasized. -It is thought that these �Venus� or �Mother-goddess� figurines may be -meant to show the great forces of nature--fertility and the birth of -life. - - -CAVE PAINTINGS - -In the paintings on walls and ceilings of caves we have some examples -that compare with the best art of any time. The subjects were usually -animals, the great cold-weather beasts of the end of the Ice Age: the -mammoth, the wooly rhinoceros, the bison, the reindeer, the wild horse, -the bear, the wild boar, and wild cattle. As in the movable art, there -are different styles in the cave art. The really great cave art is -pretty well restricted to southern France and Cantabrian (northwestern) -Spain. - -There are several interesting things about the �Franco-Cantabrian� cave -art. It was done deep down in the darkest and most dangerous parts of -the caves, although the men lived only in the openings of caves. If you -think what they must have had for lights--crude lamps of hollowed stone -have been found, which must have burned some kind of oil or grease, -with a matted hair or fiber wick--and of the animals that may have -lurked in the caves, you�ll understand the part about danger. Then, -too, we�re sure the pictures these people painted were not simply to be -looked at and admired, for they painted one picture right over other -pictures which had been done earlier. Clearly, it was the _act_ of -_painting_ that counted. The painter had to go way down into the most -mysterious depths of the earth and create an animal in paint. Possibly -he believed that by doing this he gained some sort of magic power over -the same kind of animal when he hunted it in the open air. It certainly -doesn�t look as if he cared very much about the picture he painted--as -a finished product to be admired--for he or somebody else soon went -down and painted another animal right over the one he had done. - -The cave art of the Franco-Cantabrian style is one of the great -artistic achievements of all time. The subjects drawn are almost always -the larger animals of the time: the bison, wild cattle and horses, the -wooly rhinoceros, the mammoth, the wild boar, and the bear. In some of -the best examples, the beasts are drawn in full color and the paintings -are remarkably alive and charged with energy. They come from the hands -of men who knew the great animals well--knew the feel of their fur, the -tremendous drive of their muscles, and the danger one faced when he -hunted them. - -Another artistic style has been found in eastern Spain. It includes -lively drawings, often of people hunting with bow and arrow. The East -Spanish art is found on open rock faces and in rock-shelters. It is -less spectacular and apparently more recent than the Franco-Cantabrian -cave art. - - -LIFE AT THE END OF THE ICE AGE IN EUROPE - -Life in these times was probably as good as a hunter could expect it -to be. Game and fish seem to have been plentiful; berries and wild -fruits probably were, too. From France to Russia, great pits or -piles of animal bones have been found. Some of this killing was done -as our Plains Indians killed the buffalo--by stampeding them over -steep river banks or cliffs. There were also good tools for hunting, -however. In western Europe, people lived in the openings of caves and -under overhanging rocks. On the great plains of eastern Europe, very -crude huts were being built, half underground. The first part of this -time must have been cold, for it was the middle and end phases of the -last great glaciation. Northern Europe from Scotland to Scandinavia, -northern Germany and Russia, and also the higher mountains to the -south, were certainly covered with ice. But people had fire, and the -needles and tools that were used for scraping hides must mean that they -wore clothing. - -It is clear that men were thinking of a great variety of things beside -the tools that helped them get food and shelter. Such burials as we -find have more grave-gifts than before. Beads and ornaments and often -flint, bone, or antler tools are included in the grave, and sometimes -the body is sprinkled with red ochre. Red is the color of blood, which -means life, and of fire, which means heat. Professor Childe wonders if -the red ochre was a pathetic attempt at magic--to give back to the body -the heat that had gone from it. But pathetic or not, it is sure proof -that these people were already moved by death as men still are moved by -it. - -Their art is another example of the direction the human mind was -taking. And when I say human, I mean it in the fullest sense, for this -is the time in which fully modern man has appeared. On page 34, we -spoke of the Cro-Magnon group and of the Combe Capelle-Br�nn group of -Caucasoids and of the Grimaldi �Negroids,� who are no longer believed -to be Negroid. I doubt that any one of these groups produced most of -the achievements of the times. It�s not yet absolutely sure which -particular group produced the great cave art. The artists were almost -certainly a blend of several (no doubt already mixed) groups. The pair -of Grimaldians were buried in a grave with a sprinkling of red ochre, -and were provided with shell beads and ornaments and with some blade -tools of flint. Regardless of the different names once given them by -the human paleontologists, each of these groups seems to have shared -equally in the cultural achievements of the times, for all that the -archeologists can say. - - -MICROLITHS - -One peculiar set of tools seems to serve as a marker for the very last -phase of the Ice Age in southwestern Europe. This tool-making habit is -also found about the shore of the Mediterranean basin, and it moved -into northern Europe as the last glaciation pulled northward. People -began making blade tools of very small size. They learned how to chip -very slender and tiny blades from a prepared core. Then they made these -little blades into tiny triangles, half-moons (�lunates�), trapezoids, -and several other geometric forms. These little tools are called -�microliths.� They are so small that most of them must have been fixed -in handles or shafts. - -[Illustration: MICROLITHS - - BLADE FRAGMENT - BURIN - LUNATE - TRAPEZOID - SCALENE TRIANGLE - ARROWHEAD] - -We have found several examples of microliths mounted in shafts. In -northern Europe, where their use soon spread, the microlithic triangles -or lunates were set in rows down each side of a bone or wood point. -One corner of each little triangle stuck out, and the whole thing -made a fine barbed harpoon. In historic times in Egypt, geometric -trapezoidal microliths were still in use as arrowheads. They were -fastened--broad end out--on the end of an arrow shaft. It seems queer -to give an arrow a point shaped like a �T.� Actually, the little points -were very sharp, and must have pierced the hides of animals very -easily. We also think that the broader cutting edge of the point may -have caused more bleeding than a pointed arrowhead would. In hunting -fleet-footed animals like the gazelle, which might run for miles after -being shot with an arrow, it was an advantage to cause as much bleeding -as possible, for the animal would drop sooner. - -We are not really sure where the microliths were first invented. There -is some evidence that they appear early in the Near East. Their use -was very common in northwest Africa but this came later. The microlith -makers who reached south Russia and central Europe possibly moved up -out of the Near East. Or it may have been the other way around; we -simply don�t yet know. - -Remember that the microliths we are talking about here were made from -carefully prepared little blades, and are often geometric in outline. -Each microlithic industry proper was made up, in good part, of such -tiny blade tools. But there were also some normal-sized blade tools and -even some flake scrapers, in most microlithic industries. I emphasize -this bladelet and the geometric character of the microlithic industries -of the western Old World, since there has sometimes been confusion in -the matter. Sometimes small flake chips, utilized as minute pointed -tools, have been called �microliths.� They may be _microlithic_ in size -in terms of the general meaning of the word, but they do not seem to -belong to the sub-tradition of the blade tool preparation habits which -we have been discussing here. - - -LATER BLADE-TOOL INDUSTRIES OF THE NEAR EAST AND AFRICA - -The blade-tool industries of normal size we talked about earlier spread -from Europe to central Siberia. We noted that blade tools were made -in western Asia too, and early, although Professor Garrod is no longer -sure that the whole tradition originated in the Near East. If you look -again at my chart (p. 72) you will note that in western Asia I list -some of the names of the western European industries, but with the -qualification �-like� (for example, �Gravettian-like�). The western -Asiatic blade-tool industries do vaguely recall some aspects of those -of western Europe, but we would probably be better off if we used -completely local names for them. The �Emiran� of my chart is such an -example; its industry includes a long spike-like blade point which has -no western European counterpart. - -When we last spoke of Africa (p. 66), I told you that stone tools -there were continuing in the Levalloisian flake tradition, and were -becoming smaller. At some time during this process, two new tool -types appeared in northern Africa: one was the Aterian point with -a tang (p. 67), and the other was a sort of �laurel leaf� point, -called the �Sbaikian.� These two tool types were both produced from -flakes. The Sbaikian points, especially, are roughly similar to some -of the Solutrean points of Europe. It has been suggested that both the -Sbaikian and Aterian points may be seen on their way to France through -their appearance in the Spanish cave deposits of Parpallo, but there is -also a rival �pre-Solutrean� in central Europe. We still do not know -whether there was any contact between the makers of these north African -tools and the Solutrean tool-makers. What does seem clear is that the -blade-tool tradition itself arrived late in northern Africa. - - -NETHER AFRICA - -Blade tools and �laurel leaf� points and some other probably late -stone tool types also appear in central and southern Africa. There -are geometric microliths on bladelets and even some coarse pottery in -east Africa. There is as yet no good way of telling just where these -items belong in time; in broad geological terms they are �late.� -Some people have guessed that they are as early as similar European -and Near Eastern examples, but I doubt it. The makers of small-sized -Levalloisian flake tools occupied much of Africa until very late in -time. - - -THE FAR EAST - -India and the Far East still seem to be going their own way. In India, -some blade tools have been found. These are not well dated, save that -we believe they must be post-Pleistocene. In the Far East it looks as -if the old chopper-tool tradition was still continuing. For Burma, -Dr. Movius feels this is fairly certain; for China he feels even more -certain. Actually, we know very little about the Far East at about the -time of the last glaciation. This is a shame, too, as you will soon -agree. - - -THE NEW WORLD BECOMES INHABITED - -At some time toward the end of the last great glaciation--almost -certainly after 20,000 years ago--people began to move over Bering -Strait, from Asia into America. As you know, the American Indians have -been assumed to be basically Mongoloids. New studies of blood group -types make this somewhat uncertain, but there is no doubt that the -ancestors of the American Indians came from Asia. - -The stone-tool traditions of Europe, Africa, the Near and Middle East, -and central Siberia, did _not_ move into the New World. With only a -very few special or late exceptions, there are _no_ core-bifaces, -flakes, or blade tools of the Old World. Such things just haven�t been -found here. - -This is why I say it�s a shame we don�t know more of the end of the -chopper-tool tradition in the Far East. According to Weidenreich, -the Mongoloids were in the Far East long before the end of the last -glaciation. If the genetics of the blood group types do demand a -non-Mongoloid ancestry for the American Indians, who else may have been -in the Far East 25,000 years ago? We know a little about the habits -for making stone tools which these first people brought with them, -and these habits don�t conform with those of the western Old World. -We�d better keep our eyes open for whatever happened to the end of -the chopper-tool tradition in northern China; already there are hints -that it lasted late there. Also we should watch future excavations -in eastern Siberia. Perhaps we shall find the chopper-tool tradition -spreading up that far. - - -THE NEW ERA - -Perhaps it comes in part from the way I read the evidence and perhaps -in part it is only intuition, but I feel that the materials of this -chapter suggest a new era in the ways of life. Before about 40,000 -years ago, people simply �gathered� their food, wandering over large -areas to scavenge or to hunt in a simple sort of way. But here we -have seen them �settling-in� more, perhaps restricting themselves in -their wanderings and adapting themselves to a given locality in more -intensive ways. This intensification might be suggested by the word -�collecting.� The ways of life we described in the earlier chapters -were �food-gathering� ways, but now an era of �food-collecting� has -begun. We shall see further intensifications of it in the next chapter. - - - - -End and PRELUDE - -[Illustration] - - -Up to the end of the last glaciation, we prehistorians have a -relatively comfortable time schedule. The farther back we go the less -exact we can be about time and details. Elbow-room of five, ten, -even fifty or more thousands of years becomes available for us to -maneuver in as we work backward in time. But now our story has come -forward to the point where more exact methods of dating are at hand. -The radioactive carbon method reaches back into the span of the last -glaciation. There are other methods, developed by the geologists and -paleobotanists, which supplement and extend the usefulness of the -radioactive carbon dates. And, happily, as our means of being more -exact increases, our story grows more exciting. There are also more -details of culture for us to deal with, which add to the interest. - - -CHANGES AT THE END OF THE ICE AGE - -The last great glaciation of the Ice Age was a two-part affair, with a -sub-phase at the end of the second part. In Europe the last sub-phase -of this glaciation commenced somewhere around 15,000 years ago. Then -the glaciers began to melt back, for the last time. Remember that -Professor Antevs (p. 19) isn�t sure the Ice Age is over yet! This -melting sometimes went by fits and starts, and the weather wasn�t -always changing for the better; but there was at least one time when -European weather was even better than it is now. - -The melting back of the glaciers and the weather fluctuations caused -other changes, too. We know a fair amount about these changes in -Europe. In an earlier chapter, we said that the whole Ice Age was a -matter of continual change over long periods of time. As the last -glaciers began to melt back some interesting things happened to mankind. - -In Europe, along with the melting of the last glaciers, geography -itself was changing. Britain and Ireland had certainly become islands -by 5000 B.C. The Baltic was sometimes a salt sea, sometimes a large -fresh-water lake. Forests began to grow where the glaciers had been, -and in what had once been the cold tundra areas in front of the -glaciers. The great cold-weather animals--the mammoth and the wooly -rhinoceros--retreated northward and finally died out. It is probable -that the efficient hunting of the earlier people of 20,000 or 25,000 -to about 12,000 years ago had helped this process along (see p. 86). -Europeans, especially those of the post-glacial period, had to keep -changing to keep up with the times. - -The archeological materials for the time from 10,000 to 6000 B.C. seem -simpler than those of the previous five thousand years. The great cave -art of France and Spain had gone; so had the fine carving in bone and -antler. Smaller, speedier animals were moving into the new forests. New -ways of hunting them, or ways of getting other food, had to be found. -Hence, new tools and weapons were necessary. Some of the people who -moved into northern Germany were successful reindeer hunters. Then the -reindeer moved off to the north, and again new sources of food had to -be found. - - -THE READJUSTMENTS COMPLETED IN EUROPE - -After a few thousand years, things began to look better. Or at least -we can say this: By about 6000 B.C. we again get hotter archeological -materials. The best of these come from the north European area: -Britain, Belgium, Holland, Denmark, north Germany, southern Norway and -Sweden. Much of this north European material comes from bogs and swamps -where it had become water-logged and has kept very well. Thus we have -much more complete _assemblages_[4] than for any time earlier. - - [4] �Assemblage� is a useful word when there are different kinds of - archeological materials belonging together, from one area and of - one time. An assemblage is made up of a number of �industries� - (that is, all the tools in chipped stone, all the tools in - bone, all the tools in wood, the traces of houses, etc.) and - everything else that manages to survive, such as the art, the - burials, the bones of the animals used as food, and the traces - of plant foods; in fact, everything that has been left to us - and can be used to help reconstruct the lives of the people to - whom it once belonged. Our own present-day �assemblage� would be - the sum total of all the objects in our mail-order catalogues, - department stores and supply houses of every sort, our churches, - our art galleries and other buildings, together with our roads, - canals, dams, irrigation ditches, and any other traces we might - leave of ourselves, from graves to garbage dumps. Not everything - would last, so that an archeologist digging us up--say 2,000 - years from now--would find only the most durable items in our - assemblage. - -The best known of these assemblages is the _Maglemosian_, named after a -great Danish peat-swamp where much has been found. - -[Illustration: SKETCH OF MAGLEMOSIAN ASSEMBLAGE - - CHIPPED STONE - HEMP - GROUND STONE - BONE AND ANTLER - WOOD] - -In the Maglemosian assemblage the flint industry was still very -important. Blade tools, tanged arrow points, and burins were still -made, but there were also axes for cutting the trees in the new -forests. Moreover, the tiny microlithic blades, in a variety of -geometric forms, are also found. Thus, a specialized tradition that -possibly began east of the Mediterranean had reached northern Europe. -There was also a ground stone industry; some axes and club-heads were -made by grinding and polishing rather than by chipping. The industries -in bone and antler show a great variety of tools: axes, fish-hooks, -fish spears, handles and hafts for other tools, harpoons, and clubs. -A remarkable industry in wood has been preserved. Paddles, sled -runners, handles for tools, and bark floats for fish-nets have been -found. There are even fish-nets made of plant fibers. Canoes of some -kind were no doubt made. Bone and antler tools were decorated with -simple patterns, and amber was collected. Wooden bows and arrows are -found. - -It seems likely that the Maglemosian bog finds are remains of summer -camps, and that in winter the people moved to higher and drier regions. -Childe calls them the �Forest folk�; they probably lived much the -same sort of life as did our pre-agricultural Indians of the north -central states. They hunted small game or deer; they did a great deal -of fishing; they collected what plant food they could find. In fact, -their assemblage shows us again that remarkable ability of men to adapt -themselves to change. They had succeeded in domesticating the dog; he -was still a very wolf-like dog, but his long association with mankind -had now begun. Professor Coon believes that these people were direct -descendants of the men of the glacial age and that they had much the -same appearance. He believes that most of the Ice Age survivors still -extant are living today in the northwestern European area. - - -SOUTH AND CENTRAL EUROPE PERHAPS AS READJUSTED AS THE NORTH - -There is always one trouble with things that come from areas where -preservation is exceptionally good: The very quantity of materials in -such an assemblage tends to make things from other areas look poor -and simple, although they may not have been so originally at all. The -assemblages of the people who lived to the south of the Maglemosian -area may also have been quite large and varied; but, unfortunately, -relatively little of the southern assemblages has lasted. The -water-logged sites of the Maglemosian area preserved a great deal -more. Hence the Maglemosian itself _looks_ quite advanced to us, when -we compare it with the few things that have happened to last in other -areas. If we could go back and wander over the Europe of eight thousand -years ago, we would probably find that the peoples of France, central -Europe, and south central Russia were just as advanced as those of the -north European-Baltic belt. - -South of the north European belt the hunting-food-collecting peoples -were living on as best they could during this time. One interesting -group, which seems to have kept to the regions of sandy soil and scrub -forest, made great quantities of geometric microliths. These are the -materials called _Tardenoisian_. The materials of the �Forest folk� of -France and central Europe generally are called _Azilian_; Dr. Movius -believes the term might best be restricted to the area south of the -Loire River. - - -HOW MUCH REAL CHANGE WAS THERE? - -You can see that no really _basic_ change in the way of life has yet -been described. Childe sees the problem that faced the Europeans of -10,000 to 3000 B.C. as a problem in readaptation to the post-glacial -forest environment. By 6000 B.C. some quite successful solutions of -the problem--like the Maglemosian--had been made. The upsets that came -with the melting of the last ice gradually brought about all sorts of -changes in the tools and food-getting habits, but the people themselves -were still just as much simple hunters, fishers, and food-collectors as -they had been in 25,000 B.C. It could be said that they changed just -enough so that they would not have to change. But there is a bit more -to it than this. - -Professor Mathiassen of Copenhagen, who knows the archeological remains -of this time very well, poses a question. He speaks of the material -as being neither rich nor progressive, in fact �rather stagnant,� but -he goes on to add that the people had a certain �receptiveness� and -were able to adapt themselves quickly when the next change did come. -My own understanding of the situation is that the �Forest folk� made -nothing as spectacular as had the producers of the earlier Magdalenian -assemblage and the Franco-Cantabrian art. On the other hand, they -_seem_ to have been making many more different kinds of tools for many -more different kinds of tasks than had their Ice Age forerunners. I -emphasize �seem� because the preservation in the Maglemosian bogs -is very complete; certainly we cannot list anywhere near as many -different things for earlier times as we did for the Maglemosians -(p. 94). I believe this experimentation with all kinds of new tools -and gadgets, this intensification of adaptiveness (p. 91), this -�receptiveness,� even if it is still only pointed toward hunting, -fishing, and food-collecting, is an important thing. - -Remember that the only marker we have handy for the _beginning_ of -this tendency toward �receptiveness� and experimentation is the -little microlithic blade tools of various geometric forms. These, we -saw, began before the last ice had melted away, and they lasted on -in use for a very long time. I wish there were a better marker than -the microliths but I do not know of one. Remember, too, that as yet -we can only use the microliths as a marker in Europe and about the -Mediterranean. - - -CHANGES IN OTHER AREAS? - -All this last section was about Europe. How about the rest of the world -when the last glaciers were melting away? - -We simply don�t know much about this particular time in other parts -of the world except in Europe, the Mediterranean basin and the Middle -East. People were certainly continuing to move into the New World by -way of Siberia and the Bering Strait about this time. But for the -greater part of Africa and Asia, we do not know exactly what was -happening. Some day, we shall no doubt find out; today we are without -clear information. - - -REAL CHANGE AND PRELUDE IN THE NEAR EAST - -The appearance of the microliths and the developments made by the -�Forest folk� of northwestern Europe also mark an end. They show us -the terminal phase of the old food-collecting way of life. It grows -increasingly clear that at about the same time that the Maglemosian and -other �Forest folk� were adapting themselves to hunting, fishing, and -collecting in new ways to fit the post-glacial environment, something -completely new was being made ready in western Asia. - -Unfortunately, we do not have as much understanding of the climate and -environment of the late Ice Age in western Asia as we have for most -of Europe. Probably the weather was never so violent or life quite -so rugged as it was in northern Europe. We know that the microliths -made their appearance in western Asia at least by 10,000 B.C. and -possibly earlier, marking the beginning of the terminal phase of -food-collecting. Then, gradually, we begin to see the build-up towards -the first _basic change_ in human life. - -This change amounted to a revolution just as important as the -Industrial Revolution. In it, men first learned to domesticate -plants and animals. They began _producing_ their food instead of -simply gathering or collecting it. When their food-production -became reasonably effective, people could and did settle down in -village-farming communities. With the appearance of the little farming -villages, a new way of life was actually under way. Professor Childe -has good reason to speak of the �food-producing revolution,� for it was -indeed a revolution. - - -QUESTIONS ABOUT CAUSE - -We do not yet know _how_ and _why_ this great revolution took place. We -are only just beginning to put the questions properly. I suspect the -answers will concern some delicate and subtle interplay between man and -nature. Clearly, both the level of culture and the natural condition of -the environment must have been ready for the great change, before the -change itself could come about. - -It is going to take years of co-operative field work by both -archeologists and the natural scientists who are most helpful to them -before the _how_ and _why_ answers begin to appear. Anthropologically -trained archeologists are fascinated with the cultures of men in times -of great change. About ten or twelve thousand years ago, the general -level of culture in many parts of the world seems to have been ready -for change. In northwestern Europe, we saw that cultures �changed -just enough so that they would not have to change.� We linked this to -environmental changes with the coming of post-glacial times. - -In western Asia, we archeologists can prove that the food-producing -revolution actually took place. We can see _the_ important consequence -of effective domestication of plants and animals in the appearance of -the settled village-farming community. And within the village-farming -community was the seed of civilization. The way in which effective -domestication of plants and animals came about, however, must also be -linked closely with the natural environment. Thus the archeologists -will not solve the _how_ and _why_ questions alone--they will need the -help of interested natural scientists in the field itself. - - -PRECONDITIONS FOR THE REVOLUTION - -Especially at this point in our story, we must remember how culture and -environment go hand in hand. Neither plants nor animals domesticate -themselves; men domesticate them. Furthermore, men usually domesticate -only those plants and animals which are useful. There is a good -question here: What is cultural usefulness? But I shall side-step it to -save time. Men cannot domesticate plants and animals that do not exist -in the environment where the men live. Also, there are certainly some -animals and probably some plants that resist domestication, although -they might be useful. - -This brings me back again to the point that _both_ the level of culture -and the natural condition of the environment--with the proper plants -and animals in it--must have been ready before domestication could -have happened. But this is precondition, not cause. Why did effective -food-production happen first in the Near East? Why did it happen -independently in the New World slightly later? Why also in the Far -East? Why did it happen at all? Why are all human beings not still -living as the Maglemosians did? These are the questions we still have -to face. - - -CULTURAL �RECEPTIVENESS� AND PROMISING ENVIRONMENTS - -Until the archeologists and the natural scientists--botanists, -geologists, zoologists, and general ecologists--have spent many more -years on the problem, we shall not have full _how_ and _why_ answers. I -do think, however, that we are beginning to understand what to look for. - -We shall have to learn much more of what makes the cultures of men -�receptive� and experimental. Did change in the environment alone -force it? Was it simply a case of Professor Toynbee�s �challenge and -response?� I cannot believe the answer is quite that simple. Were it -so simple, we should want to know why the change hadn�t come earlier, -along with earlier environmental changes. We shall not know the answer, -however, until we have excavated the traces of many more cultures of -the time in question. We shall doubtless also have to learn more about, -and think imaginatively about, the simpler cultures still left today. -The �mechanics� of culture in general will be bound to interest us. - -It will also be necessary to learn much more of the environments of -10,000 to 12,000 years ago. In which regions of the world were the -natural conditions most promising? Did this promise include plants and -animals which could be domesticated, or did it only offer new ways of -food-collecting? There is much work to do on this problem, but we are -beginning to get some general hints. - -Before I begin to detail the hints we now have from western Asia, I -want to do two things. First, I shall tell you of an old theory as to -how food-production might have appeared. Second, I will bother you with -some definitions which should help us in our thinking as the story goes -on. - - -AN OLD THEORY AS TO THE CAUSE OF THE REVOLUTION - -The idea that change would result, if the balance between nature -and culture became upset, is of course not a new one. For at least -twenty-five years, there has been a general theory as to _how_ the -food-producing revolution happened. This theory depends directly on the -idea of natural change in the environment. - -The five thousand years following about 10,000 B.C. must have been -very difficult ones, the theory begins. These were the years when -the most marked melting of the last glaciers was going on. While the -glaciers were in place, the climate to the south of them must have been -different from the climate in those areas today. You have no doubt read -that people once lived in regions now covered by the Sahara Desert. -This is true; just when is not entirely clear. The theory is that -during the time of the glaciers, there was a broad belt of rain winds -south of the glaciers. These rain winds would have kept north Africa, -the Nile Valley, and the Middle East green and fertile. But when the -glaciers melted back to the north, the belt of rain winds is supposed -to have moved north too. Then the people living south and east of the -Mediterranean would have found that their water supply was drying up, -that the animals they hunted were dying or moving away, and that the -plant foods they collected were dried up and scarce. - -According to the theory, all this would have been true except in the -valleys of rivers and in oases in the growing deserts. Here, in the -only places where water was left, the men and animals and plants would -have clustered. They would have been forced to live close to one -another, in order to live at all. Presently the men would have seen -that some animals were more useful or made better food than others, -and so they would have begun to protect these animals from their -natural enemies. The men would also have been forced to try new plant -foods--foods which possibly had to be prepared before they could be -eaten. Thus, with trials and errors, but by being forced to live close -to plants and animals, men would have learned to domesticate them. - - -THE OLD THEORY TOO SIMPLE FOR THE FACTS - -This theory was set up before we really knew anything in detail about -the later prehistory of the Near and Middle East. We now know that -the facts which have been found don�t fit the old theory at all well. -Also, I have yet to find an American meteorologist who feels that we -know enough about the changes in the weather pattern to say that it can -have been so simple and direct. And, of course, the glacial ice which -began melting after 12,000 years ago was merely the last sub-phase of -the last great glaciation. There had also been three earlier periods -of great alpine glaciers, and long periods of warm weather in between. -If the rain belt moved north as the glaciers melted for the last time, -it must have moved in the same direction in earlier times. Thus, the -forced neighborliness of men, plants, and animals in river valleys and -oases must also have happened earlier. Why didn�t domestication happen -earlier, then? - -Furthermore, it does not seem to be in the oases and river valleys -that we have our first or only traces of either food-production -or the earliest farming villages. These traces are also in the -hill-flanks of the mountains of western Asia. Our earliest sites of the -village-farmers do not seem to indicate a greatly different climate -from that which the same region now shows. In fact, everything we now -know suggests that the old theory was just too simple an explanation to -have been the true one. The only reason I mention it--beyond correcting -the ideas you may get in the general texts--is that it illustrates the -kind of thinking we shall have to do, even if it is doubtless wrong in -detail. - -We archeologists shall have to depend much more than we ever have on -the natural scientists who can really help us. I can tell you this from -experience. I had the great good fortune to have on my expedition staff -in Iraq in 1954-55, a geologist, a botanist, and a zoologist. Their -studies added whole new bands of color to my spectrum of thinking about -_how_ and _why_ the revolution took place and how the village-farming -community began. But it was only a beginning; as I said earlier, we are -just now learning to ask the proper questions. - - -ABOUT STAGES AND ERAS - -Now come some definitions, so I may describe my material more easily. -Archeologists have always loved to make divisions and subdivisions -within the long range of materials which they have found. They often -disagree violently about which particular assemblage of material -goes into which subdivision, about what the subdivisions should be -named, about what the subdivisions really mean culturally. Some -archeologists, probably through habit, favor an old scheme of Grecized -names for the subdivisions: paleolithic, mesolithic, neolithic. I -refuse to use these words myself. They have meant too many different -things to too many different people and have tended to hide some pretty -fuzzy thinking. Probably you haven�t even noticed my own scheme of -subdivision up to now, but I�d better tell you in general what it is. - -I think of the earliest great group of archeological materials, from -which we can deduce only a food-gathering way of culture, as the -_food-gathering stage_. I say �stage� rather than �age,� because it -is not quite over yet; there are still a few primitive people in -out-of-the-way parts of the world who remain in the _food-gathering -stage_. In fact, Professor Julian Steward would probably prefer to call -it a food-gathering _level_ of existence, rather than a stage. This -would be perfectly acceptable to me. I also tend to find myself using -_collecting_, rather than _gathering_, for the more recent aspects or -era of the stage, as the word �collecting� appears to have more sense -of purposefulness and specialization than does �gathering� (see p. -91). - -Now, while I think we could make several possible subdivisions of the -food-gathering stage--I call my subdivisions of stages _eras_[5]--I -believe the only one which means much to us here is the last or -_terminal sub-era of food-collecting_ of the whole food-gathering -stage. The microliths seem to mark its approach in the northwestern -part of the Old World. It is really shown best in the Old World by -the materials of the �Forest folk,� the cultural adaptation to the -post-glacial environment in northwestern Europe. We talked about -the �Forest folk� at the beginning of this chapter, and I used the -Maglemosian assemblage of Denmark as an example. - - [5] It is difficult to find words which have a sequence or gradation - of meaning with respect to both development and a range of time - in the past, or with a range of time from somewhere in the past - which is perhaps not yet ended. One standard Webster definition - of _stage_ is: �One of the steps into which the material - development of man ... is divided.� I cannot find any dictionary - definition that suggests which of the words, _stage_ or _era_, - has the meaning of a longer span of time. Therefore, I have - chosen to let my eras be shorter, and to subdivide my stages - into eras. Webster gives _era_ as: �A signal stage of history, - an epoch.� When I want to subdivide my eras, I find myself using - _sub-eras_. Thus I speak of the _eras_ within a _stage_ and of - the _sub-eras_ within an _era_; that is, I do so when I feel - that I really have to, and when the evidence is clear enough to - allow it. - -The food-producing revolution ushers in the _food-producing stage_. -This stage began to be replaced by the _industrial stage_ only about -two hundred years ago. Now notice that my stage divisions are in terms -of technology and economics. We must think sharply to be sure that the -subdivisions of the stages, the eras, are in the same terms. This does -not mean that I think technology and economics are the only important -realms of culture. It is rather that for most of prehistoric time the -materials left to the archeologists tend to limit our deductions to -technology and economics. - -I�m so soon out of my competence, as conventional ancient history -begins, that I shall only suggest the earlier eras of the -food-producing stage to you. This book is about prehistory, and I�m not -a universal historian. - - -THE TWO EARLIEST ERAS OF THE FOOD-PRODUCING STAGE - -The food-producing stage seems to appear in western Asia with really -revolutionary suddenness. It is seen by the relative speed with which -the traces of new crafts appear in the earliest village-farming -community sites we�ve dug. It is seen by the spread and multiplication -of these sites themselves, and the remarkable growth in human -population we deduce from this increase in sites. We�ll look at some -of these sites and the archeological traces they yield in the next -chapter. When such village sites begin to appear, I believe we are in -the _era of the primary village-farming community_. I also believe this -is the second era of the food-producing stage. - -The first era of the food-producing stage, I believe, was an _era of -incipient cultivation and animal domestication_. I keep saying �I -believe� because the actual evidence for this earlier era is so slight -that one has to set it up mainly by playing a hunch for it. The reason -for playing the hunch goes about as follows. - -One thing we seem to be able to see, in the food-collecting era in -general, is a tendency for people to begin to settle down. This -settling down seemed to become further intensified in the terminal -era. How this is connected with Professor Mathiassen�s �receptiveness� -and the tendency to be experimental, we do not exactly know. The -evidence from the New World comes into play here as well as that from -the Old World. With this settling down in one place, the people of the -terminal era--especially the �Forest folk� whom we know best--began -making a great variety of new things. I remarked about this earlier in -the chapter. Dr. Robert M. Adams is of the opinion that this atmosphere -of experimentation with new tools--with new ways of collecting food--is -the kind of atmosphere in which one might expect trials at planting -and at animal domestication to have been made. We first begin to find -traces of more permanent life in outdoor camp sites, although caves -were still inhabited at the beginning of the terminal era. It is not -surprising at all that the �Forest folk� had already domesticated the -dog. In this sense, the whole era of food-collecting was becoming ready -and almost �incipient� for cultivation and animal domestication. - -Northwestern Europe was not the place for really effective beginnings -in agriculture and animal domestication. These would have had to take -place in one of those natural environments of promise, where a variety -of plants and animals, each possible of domestication, was available in -the wild state. Let me spell this out. Really effective food-production -must include a variety of items to make up a reasonably well-rounded -diet. The food-supply so produced must be trustworthy, even though -the food-producing peoples themselves might be happy to supplement -it with fish and wild strawberries, just as we do when such things -are available. So, as we said earlier, part of our problem is that -of finding a region with a natural environment which includes--and -did include, some ten thousand years ago--a variety of possibly -domesticable wild plants and animals. - - -NUCLEAR AREAS - -Now comes the last of my definitions. A region with a natural -environment which included a variety of wild plants and animals, -both possible and ready for domestication, would be a central -or core or _nuclear area_, that is, it would be when and _if_ -food-production took place within it. It is pretty hard for me to -imagine food-production having ever made an independent start outside -such a nuclear area, although there may be some possible nuclear areas -in which food-production never took place (possibly in parts of Africa, -for example). - -We know of several such nuclear areas. In the New World, Middle America -and the Andean highlands make up one or two; it is my understanding -that the evidence is not yet clear as to which. There seems to have -been a nuclear area somewhere in southeastern Asia, in the Malay -peninsula or Burma perhaps, connected with the early cultivation of -taro, breadfruit, the banana and the mango. Possibly the cultivation -of rice and the domestication of the chicken and of zebu cattle and -the water buffalo belong to this southeast Asiatic nuclear area. We -know relatively little about it archeologically, as yet. The nuclear -area which was the scene of the earliest experiment in effective -food-production was in western Asia. Since I know it best, I shall use -it as my example. - - -THE NUCLEAR NEAR EAST - -The nuclear area of western Asia is naturally the one of greatest -interest to people of the western cultural tradition. Our cultural -heritage began within it. The area itself is the region of the hilly -flanks of rain-watered grass-land which build up to the high mountain -ridges of Iran, Iraq, Turkey, Syria, and Palestine. The map on page -125 indicates the region. If you have a good atlas, try to locate the -zone which surrounds the drainage basin of the Tigris and Euphrates -Rivers at elevations of from approximately 2,000 to 5,000 feet. The -lower alluvial land of the Tigris-Euphrates basin itself has very -little rainfall. Some years ago Professor James Henry Breasted called -the alluvial lands of the Tigris-Euphrates a part of the �fertile -crescent.� These alluvial lands are very fertile if irrigated. Breasted -was most interested in the oriental civilizations of conventional -ancient history, and irrigation had been discovered before they -appeared. - -The country of hilly flanks above Breasted�s crescent receives from -10 to 20 or more inches of winter rainfall each year, which is about -what Kansas has. Above the hilly-flanks zone tower the peaks and ridges -of the Lebanon-Amanus chain bordering the coast-line from Palestine -to Turkey, the Taurus Mountains of southern Turkey, and the Zagros -range of the Iraq-Iran borderland. This rugged mountain frame for our -hilly-flanks zone rises to some magnificent alpine scenery, with peaks -of from ten to fifteen thousand feet in elevation. There are several -gaps in the Mediterranean coastal portion of the frame, through which -the winter�s rain-bearing winds from the sea may break so as to carry -rain to the foothills of the Taurus and the Zagros. - -The picture I hope you will have from this description is that of an -intermediate hilly-flanks zone lying between two regions of extremes. -The lower Tigris-Euphrates basin land is low and far too dry and hot -for agriculture based on rainfall alone; to the south and southwest, it -merges directly into the great desert of Arabia. The mountains which -lie above the hilly-flanks zone are much too high and rugged to have -encouraged farmers. - - -THE NATURAL ENVIRONMENT OF THE NUCLEAR NEAR EAST - -The more we learn of this hilly-flanks zone that I describe, the -more it seems surely to have been a nuclear area. This is where we -archeologists need, and are beginning to get, the help of natural -scientists. They are coming to the conclusion that the natural -environment of the hilly-flanks zone today is much as it was some eight -to ten thousand years ago. There are still two kinds of wild wheat and -a wild barley, and the wild sheep, goat, and pig. We have discovered -traces of each of these at about nine thousand years ago, also traces -of wild ox, horse, and dog, each of which appears to be the probable -ancestor of the domesticated form. In fact, at about nine thousand -years ago, the two wheats, the barley, and at least the goat, were -already well on the road to domestication. - -The wild wheats give us an interesting clue. They are only available -together with the wild barley within the hilly-flanks zone. While the -wild barley grows in a variety of elevations and beyond the zone, -at least one of the wild wheats does not seem to grow below the hill -country. As things look at the moment, the domestication of both the -wheats together could _only_ have taken place within the hilly-flanks -zone. Barley seems to have first come into cultivation due to its -presence as a weed in already cultivated wheat fields. There is also -a suggestion--there is still much more to learn in the matter--that -the animals which were first domesticated were most at home up in the -hilly-flanks zone in their wild state. - -With a single exception--that of the dog--the earliest positive -evidence of domestication includes the two forms of wheat, the barley, -and the goat. The evidence comes from within the hilly-flanks zone. -However, it comes from a settled village proper, Jarmo (which I�ll -describe in the next chapter), and is thus from the era of the primary -village-farming community. We are still without positive evidence of -domesticated grain and animals in the first era of the food-producing -stage, that of incipient cultivation and animal domestication. - - -THE ERA OF INCIPIENT CULTIVATION AND ANIMAL DOMESTICATION - -I said above (p. 105) that my era of incipient cultivation and animal -domestication is mainly set up by playing a hunch. Although we cannot -really demonstrate it--and certainly not in the Near East--it would -be very strange for food-collectors not to have known a great deal -about the plants and animals most useful to them. They do seem to have -domesticated the dog. We can easily imagine them remembering to go -back, season after season, to a particular patch of ground where seeds -or acorns or berries grew particularly well. Most human beings, unless -they are extremely hungry, are attracted to baby animals, and many wild -pups or fawns or piglets must have been brought back alive by hunting -parties. - -In this last sense, man has probably always been an incipient -cultivator and domesticator. But I believe that Adams is right in -suggesting that this would be doubly true with the experimenters of -the terminal era of food-collecting. We noticed that they also seem -to have had a tendency to settle down. Now my hunch goes that _when_ -this experimentation and settling down took place within a potential -nuclear area--where a whole constellation of plants and animals -possible of domestication was available--the change was easily made. -Professor Charles A. Reed, our field colleague in zoology, agrees that -year-round settlement with plant domestication probably came before -there were important animal domestications. - - -INCIPIENT ERAS AND NUCLEAR AREAS - -I have put this scheme into a simple chart (p. 111) with the names -of a few of the sites we are going to talk about. You will see that my -hunch means that there are eras of incipient cultivation _only_ within -nuclear areas. In a nuclear area, the terminal era of food-collecting -would probably have been quite short. I do not know for how long a time -the era of incipient cultivation and domestication would have lasted, -but perhaps for several thousand years. Then it passed on into the era -of the primary village-farming community. - -Outside a nuclear area, the terminal era of food-collecting would last -for a long time; in a few out-of-the-way parts of the world, it still -hangs on. It would end in any particular place through contact with -and the spread of ideas of people who had passed on into one of the -more developed eras. In many cases, the terminal era of food-collecting -was ended by the incoming of the food-producing peoples themselves. -For example, the practices of food-production were carried into Europe -by the actual movement of some numbers of peoples (we don�t know how -many) who had reached at least the level of the primary village-farming -community. The �Forest folk� learned food-production from them. There -was never an era of incipient cultivation and domestication proper in -Europe, if my hunch is right. - - -ARCHEOLOGICAL DIFFICULTIES IN SEEING THE INCIPIENT ERA - -The way I see it, two things were required in order that an era of -incipient cultivation and domestication could begin. First, there had -to be the natural environment of a nuclear area, with its whole group -of plants and animals capable of domestication. This is the aspect of -the matter which we�ve said is directly given by nature. But it is -quite possible that such an environment with such a group of plants -and animals in it may have existed well before ten thousand years ago -in the Near East. It is also quite possible that the same promising -condition may have existed in regions which never developed into -nuclear areas proper. Here, again, we come back to the cultural factor. -I think it was that �atmosphere of experimentation� we�ve talked about -once or twice before. I can�t define it for you, other than to say that -by the end of the Ice Age, the general level of many cultures was ready -for change. Ask me how and why this was so, and I�ll tell you we don�t -know yet, and that if we did understand this kind of question, there -would be no need for me to go on being a prehistorian! - -[Illustration: POSSIBLE RELATIONSHIPS OF STAGES AND ERAS IN WESTERN -ASIA AND NORTHEASTERN AFRICA] - -Now since this was an era of incipience, of the birth of new ideas, -and of experimentation, it is very difficult to see its traces -archeologically. New tools having to do with the new ways of getting -and, in fact, producing food would have taken some time to develop. -It need not surprise us too much if we cannot find hoes for planting -and sickles for reaping grain at the very beginning. We might expect -a time of making-do with some of the older tools, or with make-shift -tools, for some of the new jobs. The present-day wild cousin of the -domesticated sheep still lives in the mountains of western Asia. It has -no wool, only a fine down under hair like that of a deer, so it need -not surprise us to find neither the whorls used for spinning nor traces -of woolen cloth. It must have taken some time for a wool-bearing sheep -to develop and also time for the invention of the new tools which go -with weaving. It would have been the same with other kinds of tools for -the new way of life. - -It is difficult even for an experienced comparative zoologist to tell -which are the bones of domesticated animals and which are those of -their wild cousins. This is especially so because the animal bones the -archeologists find are usually fragmentary. Furthermore, we do not have -a sort of library collection of the skeletons of the animals or an -herbarium of the plants of those times, against which the traces which -the archeologists find may be checked. We are only beginning to get -such collections for the modern wild forms of animals and plants from -some of our nuclear areas. In the nuclear area in the Near East, some -of the wild animals, at least, have already become extinct. There are -no longer wild cattle or wild horses in western Asia. We know they were -there from the finds we�ve made in caves of late Ice Age times, and -from some slightly later sites. - - -SITES WITH ANTIQUITIES OF THE INCIPIENT ERA - -So far, we know only a very few sites which would suit my notion of the -incipient era of cultivation and animal domestication. I am closing -this chapter with descriptions of two of the best Near Eastern examples -I know of. You may not be satisfied that what I am able to describe -makes a full-bodied era of development at all. Remember, however, that -I�ve told you I�m largely playing a kind of a hunch, and also that the -archeological materials of this era will always be extremely difficult -to interpret. At the beginning of any new way of life, there will be a -great tendency for people to make-do, at first, with tools and habits -they are already used to. I would suspect that a great deal of this -making-do went on almost to the end of this era. - - -THE NATUFIAN, AN ASSEMBLAGE OF THE INCIPIENT ERA - -The assemblage called the Natufian comes from the upper layers of a -number of caves in Palestine. Traces of its flint industry have also -turned up in Syria and Lebanon. We don�t know just how old it is. I -guess that it probably falls within five hundred years either way of -about 5000 B.C. - -Until recently, the people who produced the Natufian assemblage were -thought to have been only cave dwellers, but now at least three open -air Natufian sites have been briefly described. In their best-known -dwelling place, on Mount Carmel, the Natufian folk lived in the open -mouth of a large rock-shelter and on the terrace in front of it. On the -terrace, they had set at least two short curving lines of stones; but -these were hardly architecture; they seem more like benches or perhaps -the low walls of open pens. There were also one or two small clusters -of stones laid like paving, and a ring of stones around a hearth or -fireplace. One very round and regular basin-shaped depression had been -cut into the rocky floor of the terrace, and there were other less -regular basin-like depressions. In the newly reported open air sites, -there seem to have been huts with rounded corners. - -Most of the finds in the Natufian layer of the Mount Carmel cave were -flints. About 80 per cent of these flint tools were microliths made -by the regular working of tiny blades into various tools, some having -geometric forms. The larger flint tools included backed blades, burins, -scrapers, a few arrow points, some larger hacking or picking tools, and -one special type. This last was the sickle blade. - -We know a sickle blade of flint when we see one, because of a strange -polish or sheen which seems to develop on the cutting edge when the -blade has been used to cut grasses or grain, or--perhaps--reeds. In -the Natufian, we have even found the straight bone handles in which a -number of flint sickle blades were set in a line. - -There was a small industry in ground or pecked stone (that is, abraded -not chipped) in the Natufian. This included some pestle and mortar -fragments. The mortars are said to have a deep and narrow hole, -and some of the pestles show traces of red ochre. We are not sure -that these mortars and pestles were also used for grinding food. In -addition, there were one or two bits of carving in stone. - - -NATUFIAN ANTIQUITIES IN OTHER MATERIALS; BURIALS AND PEOPLE - -The Natufian industry in bone was quite rich. It included, beside the -sickle hafts mentioned above, points and harpoons, straight and curved -types of fish-hooks, awls, pins and needles, and a variety of beads and -pendants. There were also beads and pendants of pierced teeth and shell. - -A number of Natufian burials have been found in the caves; some burials -were grouped together in one grave. The people who were buried within -the Mount Carmel cave were laid on their backs in an extended position, -while those on the terrace seem to have been �flexed� (placed in their -graves in a curled-up position). This may mean no more than that it was -easier to dig a long hole in cave dirt than in the hard-packed dirt of -the terrace. The people often had some kind of object buried with them, -and several of the best collections of beads come from the burials. On -two of the skulls there were traces of elaborate head-dresses of shell -beads. - -[Illustration: SKETCH OF NATUFIAN ASSEMBLAGE - - MICROLITHS - ARCHITECTURE? - BURIAL - CHIPPED STONE - GROUND STONE - BONE] - -The animal bones of the Natufian layers show beasts of a �modern� type, -but with some differences from those of present-day Palestine. The -bones of the gazelle far outnumber those of the deer; since gazelles -like a much drier climate than deer, Palestine must then have had much -the same climate that it has today. Some of the animal bones were those -of large or dangerous beasts: the hyena, the bear, the wild boar, -and the leopard. But the Natufian people may have had the help of a -large domesticated dog. If our guess at a date for the Natufian is -right (about 7750 B.C.), this is an earlier dog than was that in the -Maglemosian of northern Europe. More recently, it has been reported -that a domesticated goat is also part of the Natufian finds. - -The study of the human bones from the Natufian burials is not yet -complete. Until Professor McCown�s study becomes available, we may note -Professor Coon�s assessment that these people were of a �basically -Mediterranean type.� - - -THE KARIM SHAHIR ASSEMBLAGE - -Karim Shahir differs from the Natufian sites in that it shows traces -of a temporary open site or encampment. It lies on the top of a bluff -in the Kurdish hill-country of northeastern Iraq. It was dug by Dr. -Bruce Howe of the expedition I directed in 1950-51 for the Oriental -Institute and the American Schools of Oriental Research. In 1954-55, -our expedition located another site, M�lefaat, with general resemblance -to Karim Shahir, but about a hundred miles north of it. In 1956, Dr. -Ralph Solecki located still another Karim Shahir type of site called -Zawi Chemi Shanidar. The Zawi Chemi site has a radiocarbon date of 8900 -� 300 B.C. - -Karim Shahir has evidence of only one very shallow level of occupation. -It was probably not lived on very long, although the people who lived -on it spread out over about three acres of area. In spots, the single -layer yielded great numbers of fist-sized cracked pieces of limestone, -which had been carried up from the bed of a stream at the bottom of the -bluff. We think these cracked stones had something to do with a kind of -architecture, but we were unable to find positive traces of hut plans. -At M�lefaat and Zawi Chemi, there were traces of rounded hut plans. - -As in the Natufian, the great bulk of small objects of the Karim Shahir -assemblage was in chipped flint. A large proportion of the flint tools -were microlithic bladelets and geometric forms. The flint sickle blade -was almost non-existent, being far scarcer than in the Natufian. The -people of Karim Shahir did a modest amount of work in the grinding of -stone; there were milling stone fragments of both the mortar and the -quern type, and stone hoes or axes with polished bits. Beads, pendants, -rings, and bracelets were made of finer quality stone. We found a few -simple points and needles of bone, and even two rather formless unbaked -clay figurines which seemed to be of animal form. - -[Illustration: SKETCH OF KARIM SHAHIR ASSEMBLAGE - - CHIPPED STONE - GROUND STONE - UNBAKED CLAY - SHELL - BONE - �ARCHITECTURE�] - -Karim Shahir did not yield direct evidence of the kind of vegetable -food its people ate. The animal bones showed a considerable -increase in the proportion of the bones of the species capable of -domestication--sheep, goat, cattle, horse, dog--as compared with animal -bones from the earlier cave sites of the area, which have a high -proportion of bones of wild forms like deer and gazelle. But we do not -know that any of the Karim Shahir animals were actually domesticated. -Some of them may have been, in an �incipient� way, but we have no means -at the moment that will tell us from the bones alone. - - -WERE THE NATUFIAN AND KARIM SHAHIR PEOPLES FOOD-PRODUCERS? - -It is clear that a great part of the food of the Natufian people -must have been hunted or collected. Shells of land, fresh-water, and -sea animals occur in their cave layers. The same is true as regards -Karim Shahir, save for sea shells. But on the other hand, we have -the sickles, the milling stones, the possible Natufian dog, and the -goat, and the general animal situation at Karim Shahir to hint at an -incipient approach to food-production. At Karim Shahir, there was the -tendency to settle down out in the open; this is echoed by the new -reports of open air Natufian sites. The large number of cracked stones -certainly indicates that it was worth the peoples� while to have some -kind of structure, even if the site as a whole was short-lived. - -It is a part of my hunch that these things all point toward -food-production--that the hints we seek are there. But in the sense -that the peoples of the era of the primary village-farming community, -which we shall look at next, are fully food-producing, the Natufian -and Karim Shahir folk had not yet arrived. I think they were part of -a general build-up to full scale food-production. They were possibly -controlling a few animals of several kinds and perhaps one or two -plants, without realizing the full possibilities of this �control� as a -new way of life. - -This is why I think of the Karim Shahir and Natufian folk as being at -a level, or in an era, of incipient cultivation and domestication. But -we shall have to do a great deal more excavation in this range of time -before we�ll get the kind of positive information we need. - - -SUMMARY - -I am sorry that this chapter has had to be so much more about ideas -than about the archeological traces of prehistoric men themselves. -But the antiquities of the incipient era of cultivation and animal -domestication will not be spectacular, even when we do have them -excavated in quantity. Few museums will be interested in these -antiquities for exhibition purposes. The charred bits or impressions -of plants, the fragments of animal bone and shell, and the varied -clues to climate and environment will be as important as the artifacts -themselves. It will be the ideas to which these traces lead us that -will be important. I am sure that this unspectacular material--when we -have much more of it, and learn how to understand what it says--will -lead us to how and why answers about the first great change in human -history. - -We know the earliest village-farming communities appeared in western -Asia, in a nuclear area. We do not yet know why the Near Eastern -experiment came first, or why it didn�t happen earlier in some other -nuclear area. Apparently, the level of culture and the promise of the -natural environment were ready first in western Asia. The next sites -we look at will show a simple but effective food-production already -in existence. Without effective food-production and the settled -village-farming communities, civilization never could have followed. -How effective food-production came into being by the end of the -incipient era, is, I believe, one of the most fascinating questions any -archeologist could face. - -It now seems probable--from possibly two of the Palestinian sites with -varieties of the Natufian (Jericho and Nahal Oren)--that there were -one or more local Palestinian developments out of the Natufian into -later times. In the same way, what followed after the Karim Shahir type -of assemblage in northeastern Iraq was in some ways a reflection of -beginnings made at Karim Shahir and Zawi Chemi. - - - - -THE First Revolution - -[Illustration] - - -As the incipient era of cultivation and animal domestication passed -onward into the era of the primary village-farming community, the first -basic change in human economy was fully achieved. In southwestern Asia, -this seems to have taken place about nine thousand years ago. I am -going to restrict my description to this earliest Near Eastern case--I -do not know enough about the later comparable experiments in the Far -East and in the New World. Let us first, once again, think of the -contrast between food-collecting and food-producing as ways of life. - - -THE DIFFERENCE BETWEEN FOOD-COLLECTORS AND FOOD-PRODUCERS - -Childe used the word �revolution� because of the radical change that -took place in the habits and customs of man. Food-collectors--that is, -hunters, fishers, berry- and nut-gatherers--had to live in small groups -or bands, for they had to be ready to move wherever their food supply -moved. Not many people can be fed in this way in one area, and small -children and old folks are a burden. There is not enough food to store, -and it is not the kind that can be stored for long. - -Do you see how this all fits into a picture? Small groups of people -living now in this cave, now in that--or out in the open--as they moved -after the animals they hunted; no permanent villages, a few half-buried -huts at best; no breakable utensils; no pottery; no signs of anything -for clothing beyond the tools that were probably used to dress the -skins of animals; no time to think of much of anything but food and -protection and disposal of the dead when death did come: an existence -which takes nature as it finds it, which does little or nothing to -modify nature--all in all, a savage�s existence, and a very tough one. -A man who spends his whole life following animals just to kill them to -eat, or moving from one berry patch to another, is really living just -like an animal himself. - - -THE FOOD-PRODUCING ECONOMY - -Against this picture let me try to draw another--that of man�s life -after food-production had begun. His meat was stored �on the hoof,� -his grain in silos or great pottery jars. He lived in a house: it was -worth his while to build one, because he couldn�t move far from his -fields and flocks. In his neighborhood enough food could be grown -and enough animals bred so that many people were kept busy. They all -lived close to their flocks and fields, in a village. The village was -already of a fair size, and it was growing, too. Everybody had more to -eat; they were presumably all stronger, and there were more children. -Children and old men could shepherd the animals by day or help with -the lighter work in the fields. After the crops had been harvested the -younger men might go hunting and some of them would fish, but the food -they brought in was only an addition to the food in the village; the -villagers wouldn�t starve, even if the hunters and fishermen came home -empty-handed. - -There was more time to do different things, too. They began to modify -nature. They made pottery out of raw clay, and textiles out of hair -or fiber. People who became good at pottery-making traded their pots -for food and spent all of their time on pottery alone. Other people -were learning to weave cloth or to make new tools. There were already -people in the village who were becoming full-time craftsmen. - -Other things were changing, too. The villagers must have had -to agree on new rules for living together. The head man of the -village had problems different from those of the chief of the small -food-collectors� band. If somebody�s flock of sheep spoiled a wheat -field, the owner wanted payment for the grain he lost. The chief of -the hunters was never bothered with such questions. Even the gods -had changed. The spirits and the magic that had been used by hunters -weren�t of any use to the villagers. They needed gods who would watch -over the fields and the flocks, and they eventually began to erect -buildings where their gods might dwell, and where the men who knew most -about the gods might live. - - -WAS FOOD-PRODUCTION A �REVOLUTION�? - -If you can see the difference between these two pictures--between -life in the food-collecting stage and life after food-production -had begun--you�ll see why Professor Childe speaks of a revolution. -By revolution, he doesn�t mean that it happened over night or that -it happened only once. We don�t know exactly how long it took. Some -people think that all these changes may have occurred in less than -500 years, but I doubt that. The incipient era was probably an affair -of some duration. Once the level of the village-farming community had -been established, however, things did begin to move very fast. By -six thousand years ago, the descendants of the first villagers had -developed irrigation and plow agriculture in the relatively rainless -Mesopotamian alluvium and were living in towns with temples. Relative -to the half million years of food-gathering which lay behind, this had -been achieved with truly revolutionary suddenness. - - -GAPS IN OUR KNOWLEDGE OF THE NEAR EAST - -If you�ll look again at the chart (p. 111) you�ll see that I have -very few sites and assemblages to name in the incipient era of -cultivation and domestication, and not many in the earlier part of -the primary village-farming level either. Thanks in no small part -to the intelligent co-operation given foreign excavators by the -Iraq Directorate General of Antiquities, our understanding of the -sequence in Iraq is growing more complete. I shall use Iraq as my main -yard-stick here. But I am far from being able to show you a series of -Sears Roebuck catalogues, even century by century, for any part of -the nuclear area. There is still a great deal of earth to move, and a -great mass of material to recover and interpret before we even begin to -understand �how� and �why.� - -Perhaps here, because this kind of archeology is really my specialty, -you�ll excuse it if I become personal for a moment. I very much look -forward to having further part in closing some of the gaps in knowledge -of the Near East. This is not, as I�ve told you, the spectacular -range of Near Eastern archeology. There are no royal tombs, no gold, -no great buildings or sculpture, no writing, in fact nothing to -excite the normal museum at all. Nevertheless it is a range which, -idea-wise, gives the archeologist tremendous satisfaction. The country -of the hilly flanks is an exciting combination of green grasslands -and mountainous ridges. The Kurds, who inhabit the part of the area -in which I�ve worked most recently, are an extremely interesting and -hospitable people. Archeologists don�t become rich, but I�ll forego -the Cadillac for any bright spring morning in the Kurdish hills, on a -good site with a happy crew of workmen and an interested and efficient -staff. It is probably impossible to convey the full feeling which life -on such a dig holds--halcyon days for the body and acute pleasurable -stimulation for the mind. Old things coming newly out of the good dirt, -and the pieces of the human puzzle fitting into place! I think I am -an honest man; I cannot tell you that I am sorry the job is not yet -finished and that there are still gaps in this part of the Near Eastern -archeological sequence. - - -EARLIEST SITES OF THE VILLAGE FARMERS - -So far, the Karim Shahir type of assemblage, which we looked at in the -last chapter, is the earliest material available in what I take to -be the nuclear area. We do not believe that Karim Shahir was a village -site proper: it looks more like the traces of a temporary encampment. -Two caves, called Belt and Hotu, which are outside the nuclear area -and down on the foreshore of the Caspian Sea, have been excavated -by Professor Coon. These probably belong in the later extension of -the terminal era of food-gathering; in their upper layers are traits -like the use of pottery borrowed from the more developed era of the -same time in the nuclear area. The same general explanation doubtless -holds true for certain materials in Egypt, along the upper Nile and in -the Kharga oasis: these materials, called Sebilian III, the Khartoum -�neolithic,� and the Khargan microlithic, are from surface sites, -not from caves. The chart (p. 111) shows where I would place these -materials in era and time. - -[Illustration: THE HILLY FLANKS OF THE CRESCENT AND EARLY SITES OF THE -NEAR EAST] - -Both M�lefaat and Dr. Solecki�s Zawi Chemi Shanidar site appear to have -been slightly more �settled in� than was Karim Shahir itself. But I do -not think they belong to the era of farming-villages proper. The first -site of this era, in the hills of Iraqi Kurdistan, is Jarmo, on which -we have spent three seasons of work. Following Jarmo comes a variety of -sites and assemblages which lie along the hilly flanks of the crescent -and just below it. I am going to describe and illustrate some of these -for you. - -Since not very much archeological excavation has yet been done on sites -of this range of time, I shall have to mention the names of certain -single sites which now alone stand for an assemblage. This does not -mean that I think the individual sites I mention were unique. In the -times when their various cultures flourished, there must have been -many little villages which shared the same general assemblage. We are -only now beginning to locate them again. Thus, if I speak of Jarmo, -or Jericho, or Sialk as single examples of their particular kinds of -assemblages, I don�t mean that they were unique at all. I think I could -take you to the sites of at least three more Jarmos, within twenty -miles of the original one. They are there, but they simply haven�t yet -been excavated. In 1956, a Danish expedition discovered material of -Jarmo type at Shimshara, only two dozen miles northeast of Jarmo, and -below an assemblage of Hassunan type (which I shall describe presently). - - -THE GAP BETWEEN KARIM SHAHIR AND JARMO - -As we see the matter now, there is probably still a gap in the -available archeological record between the Karim Shahir-M�lefaat-Zawi -Chemi group (of the incipient era) and that of Jarmo (of the -village-farming era). Although some items of the Jarmo type materials -do reflect the beginnings of traditions set in the Karim Shahir group -(see p. 120), there is not a clear continuity. Moreover--to the -degree that we may trust a few radiocarbon dates--there would appear -to be around two thousand years of difference in time. The single -available Zawi Chemi �date� is 8900 � 300 B.C.; the most reasonable -group of �dates� from Jarmo average to about 6750 � 200 B.C. I am -uncertain about this two thousand years--I do not think it can have -been so long. - -This suggests that we still have much work to do in Iraq. You can -imagine how earnestly we await the return of political stability in the -Republic of Iraq. - - -JARMO, IN THE KURDISH HILLS, IRAQ - -The site of Jarmo has a depth of deposit of about twenty-seven feet, -and approximately a dozen layers of architectural renovation and -change. Nevertheless it is a �one period� site: its assemblage remains -essentially the same throughout, although one or two new items are -added in later levels. It covers about four acres of the top of a -bluff, below which runs a small stream. Jarmo lies in the hill country -east of the modern oil town of Kirkuk. The Iraq Directorate General of -Antiquities suggested that we look at it in 1948, and we have had three -seasons of digging on it since. - -The people of Jarmo grew the barley plant and two different kinds of -wheat. They made flint sickles with which to reap their grain, mortars -or querns on which to crack it, ovens in which it might be parched, and -stone bowls out of which they might eat their porridge. We are sure -that they had the domesticated goat, but Professor Reed (the staff -zoologist) is not convinced that the bones of the other potentially -domesticable animals of Jarmo--sheep, cattle, pig, horse, dog--show -sure signs of domestication. We had first thought that all of these -animals were domesticated ones, but Reed feels he must find out much -more before he can be sure. As well as their grain and the meat from -their animals, the people of Jarmo consumed great quantities of land -snails. Botanically, the Jarmo wheat stands about half way between -fully bred wheat and the wild forms. - - -ARCHITECTURE: HALL-MARK OF THE VILLAGE - -The sure sign of the village proper is in its traces of architectural -permanence. The houses of Jarmo were only the size of a small cottage -by our standards, but each was provided with several rectangular rooms. -The walls of the houses were made of puddled mud, often set on crude -foundations of stone. (The puddled mud wall, which the Arabs call -_touf_, is built by laying a three to six inch course of soft mud, -letting this sun-dry for a day or two, then adding the next course, -etc.) The village probably looked much like the simple Kurdish farming -village of today, with its mud-walled houses and low mud-on-brush -roofs. I doubt that the Jarmo village had more than twenty houses at -any one moment of its existence. Today, an average of about seven -people live in a comparable Kurdish house; probably the population of -Jarmo was about 150 people. - -[Illustration: SKETCH OF JARMO ASSEMBLAGE - - CHIPPED STONE - UNBAKED CLAY - GROUND STONE - POTTERY _UPPER THIRD OF SITE ONLY._ - REED MATTING - BONE - ARCHITECTURE] - -It is interesting that portable pottery does not appear until the -last third of the life of the Jarmo village. Throughout the duration -of the village, however, its people had experimented with the plastic -qualities of clay. They modeled little figurines of animals and of -human beings in clay; one type of human figurine they favored was that -of a markedly pregnant woman, probably the expression of some sort of -fertility spirit. They provided their house floors with baked-in-place -depressions, either as basins or hearths, and later with domed ovens of -clay. As we�ve noted, the houses themselves were of clay or mud; one -could almost say they were built up like a house-sized pot. Then, -finally, the idea of making portable pottery itself appeared, although -I very much doubt that the people of the Jarmo village discovered the -art. - -On the other hand, the old tradition of making flint blades and -microlithic tools was still very strong at Jarmo. The sickle-blade was -made in quantities, but so also were many of the much older tool types. -Strangely enough, it is within this age-old category of chipped stone -tools that we see one of the clearest pointers to a newer age. Many of -the Jarmo chipped stone tools--microliths--were made of obsidian, a -black volcanic natural glass. The obsidian beds nearest to Jarmo are -over three hundred miles to the north. Already a bulk carrying trade -had been established--the forerunner of commerce--and the routes were -set by which, in later times, the metal trade was to move. - -There are now twelve radioactive carbon �dates� from Jarmo. The most -reasonable cluster of determinations averages to about 6750 � 200 -B.C., although there is a completely unreasonable range of �dates� -running from 3250 to 9250 B.C.! _If_ I am right in what I take to be -�reasonable,� the first flush of the food-producing revolution had been -achieved almost nine thousand years ago. - - -HASSUNA, IN UPPER MESOPOTAMIAN IRAQ - -We are not sure just how soon after Jarmo the next assemblage of Iraqi -material is to be placed. I do not think the time was long, and there -are a few hints that detailed habits in the making of pottery and -ground stone tools were actually continued from Jarmo times into the -time of the next full assemblage. This is called after a site named -Hassuna, a few miles to the south and west of modern Mosul. We also -have Hassunan type materials from several other sites in the same -general region. It is probably too soon to make generalizations about -it, but the Hassunan sites seem to cluster at slightly lower elevations -than those we have been talking about so far. - -The catalogue of the Hassuna assemblage is of course more full and -elaborate than that of Jarmo. The Iraqi government�s archeologists -who dug Hassuna itself, exposed evidence of increasing architectural -know-how. The walls of houses were still formed of puddled mud; -sun-dried bricks appear only in later periods. There were now several -different ways of making and decorating pottery vessels. One style of -pottery painting, called the Samarran style, is an extremely handsome -one and must have required a great deal of concentration and excellence -of draftsmanship. On the other hand, the old habits for the preparation -of good chipped stone tools--still apparent at Jarmo--seem to have -largely disappeared by Hassunan times. The flint work of the Hassunan -catalogue is, by and large, a wretched affair. We might guess that the -kinaesthetic concentration of the Hassuna craftsmen now went into other -categories; that is, they suddenly discovered they might have more fun -working with the newer materials. It�s a shame, for example, that none -of their weaving is preserved for us. - -The two available radiocarbon determinations from Hassunan contexts -stand at about 5100 and 5600 B.C. � 250 years. - - -OTHER EARLY VILLAGE SITES IN THE NUCLEAR AREA - -I�ll now name and very briefly describe a few of the other early -village assemblages either in or adjacent to the hilly flanks of the -crescent. Unfortunately, we do not have radioactive carbon dates for -many of these materials. We may guess that some particular assemblage, -roughly comparable to that of Hassuna, for example, must reflect a -culture which lived at just about the same time as that of Hassuna. We -do this guessing on the basis of the general similarity and degree of -complexity of the Sears Roebuck catalogues of the particular assemblage -and that of Hassuna. We suppose that for sites near at hand and of a -comparable cultural level, as indicated by their generally similar -assemblages, the dating must be about the same. We may also know that -in a general stratigraphic sense, the sites in question may both appear -at the bottom of the ascending village sequence in their respective -areas. Without a number of consistent radioactive carbon dates, we -cannot be precise about priorities. - -[Illustration: SKETCH OF HASSUNA ASSEMBLAGE - - POTTERY - POTTERY OBJECTS - CHIPPED STONE - BONE - GROUND STONE - ARCHITECTURE - REED MATTING - BURIAL] - -The ancient mound at Jericho, in the Dead Sea valley in Palestine, -yields some very interesting material. Its catalogue somewhat resembles -that of Jarmo, especially in the sense that there is a fair depth -of deposit without portable pottery vessels. On the other hand, the -architecture of Jericho is surprisingly complex, with traces of massive -stone fortification walls and the general use of formed sun-dried -mud brick. Jericho lies in a somewhat strange and tropically lush -ecological niche, some seven hundred feet below sea level; it is -geographically within the hilly-flanks zone but environmentally not -part of it. - -Several radiocarbon �dates� for Jericho fall within the range of those -I find reasonable for Jarmo, and their internal statistical consistency -is far better than that for the Jarmo determinations. It is not yet -clear exactly what this means. - -The mound at Jericho (Tell es-Sultan) contains a remarkably -fine sequence, which perhaps does not have the gap we noted in -Iraqi-Kurdistan between the Karim Shahir group and Jarmo. While I am -not sure that the Jericho sequence will prove valid for those parts -of Palestine outside the special Dead Sea environmental niche, the -sequence does appear to proceed from the local variety of Natufian into -that of a very well settled community. So far, we have little direct -evidence for the food-production basis upon which the Jericho people -subsisted. - -There is an early village assemblage with strong characteristics of its -own in the land bordering the northeast corner of the Mediterranean -Sea, where Syria and the Cilician province of Turkey join. This early -Syro-Cilician assemblage must represent a general cultural pattern -which was at least in part contemporary with that of the Hassuna -assemblage. These materials from the bases of the mounds at Mersin, and -from Judaidah in the Amouq plain, as well as from a few other sites, -represent the remains of true villages. The walls of their houses were -built of puddled mud, but some of the house foundations were of stone. -Several different kinds of pottery were made by the people of these -villages. None of it resembles the pottery from Hassuna or from the -upper levels of Jarmo or Jericho. The Syro-Cilician people had not -lost their touch at working flint. An important southern variation of -the Syro-Cilician assemblage has been cleared recently at Byblos, a -port town famous in later Phoenician times. There are three radiocarbon -determinations which suggest that the time range for these developments -was in the sixth or early fifth millennium B.C. - -It would be fascinating to search for traces of even earlier -village-farming communities and for the remains of the incipient -cultivation era, in the Syro-Cilician region. - - -THE IRANIAN PLATEAU AND THE NILE VALLEY - -The map on page 125 shows some sites which lie either outside or in -an extension of the hilly-flanks zone proper. From the base of the -great mound at Sialk on the Iranian plateau came an assemblage of -early village material, generally similar, in the kinds of things it -contained, to the catalogues of Hassuna and Judaidah. The details of -how things were made are different; the Sialk assemblage represents -still another cultural pattern. I suspect it appeared a bit later -in time than did that of Hassuna. There is an important new item in -the Sialk catalogue. The Sialk people made small drills or pins of -hammered copper. Thus the metallurgist�s specialized craft had made its -appearance. - -There is at least one very early Iranian site on the inward slopes -of the hilly-flanks zone. It is the earlier of two mounds at a place -called Bakun, in southwestern Iran; the results of the excavations -there are not yet published and we only know of its coarse and -primitive pottery. I only mention Bakun because it helps us to plot the -extent of the hilly-flanks zone villages on the map. - -The Nile Valley lies beyond the peculiar environmental zone of the -hilly flanks of the crescent, and it is probable that the earliest -village-farming communities in Egypt were established by a few people -who wandered into the Nile delta area from the nuclear area. The -assemblage which is most closely comparable to the catalogue of Hassuna -or Judaidah, for example, is that from little settlements along the -shore of the Fayum lake. The Fayum materials come mainly from grain -bins or silos. Another site, Merimde, in the western part of the Nile -delta, shows the remains of a true village, but it may be slightly -later than the settlement of the Fayum. There are radioactive carbon -�dates� for the Fayum materials at about 4275 B.C. � 320 years, which -is almost fifteen hundred years later than the determinations suggested -for the Hassunan or Syro-Cilician assemblages. I suspect that this -is a somewhat over-extended indication of the time it took for the -generalized cultural pattern of village-farming community life to -spread from the nuclear area down into Egypt, but as yet we have no way -of testing these matters. - -In this same vein, we have two radioactive carbon dates for an -assemblage from sites near Khartoum in the Sudan, best represented by -the mound called Shaheinab. The Shaheinab catalogue roughly corresponds -to that of the Fayum; the distance between the two places, as the Nile -flows, is roughly 1,500 miles. Thus it took almost a thousand years for -the new way of life to be carried as far south into Africa as Khartoum; -the two Shaheinab �dates� average about 3300 B.C. � 400 years. - -If the movement was up the Nile (southward), as these dates suggest, -then I suspect that the earliest available village material of middle -Egypt, the so-called Tasian, is also later than that of the Fayum. The -Tasian materials come from a few graves near a village called Deir -Tasa, and I have an uncomfortable feeling that the Tasian �assemblage� -may be mainly an artificial selection of poor examples of objects which -belong in the following range of time. - - -SPREAD IN TIME AND SPACE - -There are now two things we can do; in fact, we have already begun to -do them. We can watch the spread of the new way of life upward through -time in the nuclear area. We can also see how the new way of life -spread outward in space from the nuclear area, as time went on. There -is good archeological evidence that both these processes took place. -For the hill country of northeastern Iraq, in the nuclear area, we -have already noticed how the succession (still with gaps) from Karim -Shahir, through M�lefaat and Jarmo, to Hassuna can be charted (see -chart, p. 111). In the next chapter, we shall continue this charting -and description of what happened in Iraq upward through time. We also -watched traces of the new way of life move through space up the Nile -into Africa, to reach Khartoum in the Sudan some thirty-five hundred -years later than we had seen it at Jarmo or Jericho. We caught glimpses -of it in the Fayum and perhaps at Tasa along the way. - -For the remainder of this chapter, I shall try to suggest briefly for -you the directions taken by the spread of the new way of life from the -nuclear area in the Near East. First, let me make clear again that -I _do not_ believe that the village-farming community way of life -was invented only once and in the Near East. It seems to me that the -evidence is very clear that a separate experiment arose in the New -World. For China, the question of independence or borrowing--in the -appearance of the village-farming community there--is still an open -one. In the last chapter, we noted the probability of an independent -nuclear area in southeastern Asia. Professor Carl Sauer strongly -champions the great importance of this area as _the_ original center -of agricultural pursuits, as a kind of �cradle� of all incipient eras -of the Old World at least. While there is certainly not the slightest -archeological evidence to allow us to go that far, we may easily expect -that an early southeast Asian development would have been felt in -China. However, the appearance of the village-farming community in the -northwest of India, at least, seems to have depended on the earlier -development in the Near East. It is also probable that ideas of the new -way of life moved well beyond Khartoum in Africa. - - -THE SPREAD OF THE VILLAGE-FARMING COMMUNITY WAY OF LIFE INTO EUROPE - -How about Europe? I won�t give you many details. You can easily imagine -that the late prehistoric prelude to European history is a complicated -affair. We all know very well how complicated an area Europe is now, -with its welter of different languages and cultures. Remember, however, -that a great deal of archeology has been done on the late prehistory of -Europe, and very little on that of further Asia and Africa. If we knew -as much about these areas as we do of Europe, I expect we�d find them -just as complicated. - -This much is clear for Europe, as far as the spread of the -village-community way of life is concerned. The general idea and much -of the know-how and the basic tools of food-production moved from the -Near East to Europe. So did the plants and animals which had been -domesticated; they were not naturally at home in Europe, as they were -in western Asia. I do not, of course, mean that there were traveling -salesmen who carried these ideas and things to Europe with a commercial -gleam in their eyes. The process took time, and the ideas and things -must have been passed on from one group of people to the next. There -was also some actual movement of peoples, but we don�t know the size of -the groups that moved. - -The story of the �colonization� of Europe by the first farmers is -thus one of (1) the movement from the eastern Mediterranean lands -of some people who were farmers; (2) the spread of ideas and things -beyond the Near East itself and beyond the paths along which the -�colonists� moved; and (3) the adaptations of the ideas and things -by the indigenous �Forest folk�, about whose �receptiveness� Professor -Mathiassen speaks (p. 97). It is important to note that the resulting -cultures in the new European environment were European, not Near -Eastern. The late Professor Childe remarked that �the peoples of the -West were not slavish imitators; they adapted the gifts from the East -... into a new and organic whole capable of developing on its own -original lines.� - - -THE WAYS TO EUROPE - -Suppose we want to follow the traces of those earliest village-farmers -who did travel from western Asia into Europe. Let us start from -Syro-Cilicia, that part of the hilly-flanks zone proper which lies in -the very northeastern corner of the Mediterranean. Three ways would be -open to us (of course we could not be worried about permission from the -Soviet authorities!). We would go north, or north and slightly east, -across Anatolian Turkey, and skirt along either shore of the Black Sea -or even to the east of the Caucasus Mountains along the Caspian Sea, -to reach the plains of Ukrainian Russia. From here, we could march -across eastern Europe to the Baltic and Scandinavia, or even hook back -southwestward to Atlantic Europe. - -Our second way from Syro-Cilicia would also lie over Anatolia, to the -northwest, where we would have to swim or raft ourselves over the -Dardanelles or the Bosphorus to the European shore. Then we would bear -left toward Greece, but some of us might turn right again in Macedonia, -going up the valley of the Vardar River to its divide and on down -the valley of the Morava beyond, to reach the Danube near Belgrade -in Jugoslavia. Here we would turn left, following the great river -valley of the Danube up into central Europe. We would have a number of -tributary valleys to explore, or we could cross the divide and go down -the valley of the Rhine to the North Sea. - -Our third way from Syro-Cilicia would be by sea. We would coast along -southern Anatolia and visit Cyprus, Crete, and the Aegean islands on -our way to Greece, where, in the north, we might meet some of those who -had taken the second route. From Greece, we would sail on to Italy and -the western isles, to reach southern France and the coasts of Spain. -Eventually a few of us would sail up the Atlantic coast of Europe, to -reach western Britain and even Ireland. - -[Illustration: PROBABLE ROUTES AND TIMING IN THE SPREAD OF THE -VILLAGE-FARMING COMMUNITY WAY OF LIFE FROM THE NEAR EAST TO EUROPE] - -Of course none of us could ever take these journeys as the first -farmers took them, since the whole course of each journey must have -lasted many lifetimes. The date given to the assemblage called Windmill -Hill, the earliest known trace of village-farming communities in -England, is about 2500 B.C. I would expect about 5500 B.C. to be a -safe date to give for the well-developed early village communities of -Syro-Cilicia. We suspect that the spread throughout Europe did not -proceed at an even rate. Professor Piggott writes that �at a date -probably about 2600 B.C., simple agricultural communities were being -established in Spain and southern France, and from the latter region a -spread northwards can be traced ... from points on the French seaboard -of the [English] Channel ... there were emigrations of a certain number -of these tribes by boat, across to the chalk lands of Wessex and Sussex -[in England], probably not more than three or four generations later -than the formation of the south French colonies.� - -New radiocarbon determinations are becoming available all the -time--already several suggest that the food-producing way of life -had reached the lower Rhine and Holland by 4000 B.C. But not all -prehistorians accept these �dates,� so I do not show them on my map -(p. 139). - - -THE EARLIEST FARMERS OF ENGLAND - -To describe the later prehistory of all Europe for you would take -another book and a much larger one than this is. Therefore, I have -decided to give you only a few impressions of the later prehistory of -Britain. Of course the British Isles lie at the other end of Europe -from our base-line in western Asia. Also, they received influences -along at least two of the three ways in which the new way of life -moved into Europe. We will look at more of their late prehistory in a -following chapter: here, I shall speak only of the first farmers. - -The assemblage called Windmill Hill, which appears in the south of -England, exhibits three different kinds of structures, evidence of -grain-growing and of stock-breeding, and some distinctive types of -pottery and stone implements. The most remarkable type of structure -is the earthwork enclosures which seem to have served as seasonal -cattle corrals. These enclosures were roughly circular, reached over -a thousand feet in diameter, and sometimes included two or three -concentric sets of banks and ditches. Traces of oblong timber houses -have been found, but not within the enclosures. The second type of -structure is mine-shafts, dug down into the chalk beds where good -flint for the making of axes or hoes could be found. The third type -of structure is long simple mounds or �unchambered barrows,� in one -end of which burials were made. It has been commonly believed that the -Windmill Hill assemblage belonged entirely to the cultural tradition -which moved up through France to the Channel. Professor Piggott is now -convinced, however, that important elements of Windmill Hill stem from -northern Germany and Denmark--products of the first way into Europe -from the east. - -The archeological traces of a second early culture are to be found -in the west of England, western and northern Scotland, and most of -Ireland. The bearers of this culture had come up the Atlantic coast -by sea from southern France and Spain. The evidence they have left us -consists mainly of tombs and the contents of tombs, with only very -rare settlement sites. The tombs were of some size and received the -bodies of many people. The tombs themselves were built of stone, heaped -over with earth; the stones enclosed a passage to a central chamber -(�passage graves�), or to a simple long gallery, along the sides of -which the bodies were laid (�gallery graves�). The general type of -construction is called �megalithic� (= great stone), and the whole -earth-mounded structure is often called a _barrow_. Since many have -proper chambers, in one sense or another, we used the term �unchambered -barrow� above to distinguish those of the Windmill Hill type from these -megalithic structures. There is some evidence for sacrifice, libations, -and ceremonial fires, and it is clear that some form of community -ritual was focused on the megalithic tombs. - -The cultures of the people who produced the Windmill Hill assemblage -and of those who made the megalithic tombs flourished, at least in -part, at the same time. Although the distributions of the two different -types of archeological traces are in quite different parts of the -country, there is Windmill Hill pottery in some of the megalithic -tombs. But the tombs also contain pottery which seems to have arrived -with the tomb builders themselves. - -The third early British group of antiquities of this general time -(following 2500 B.C.) comes from sites in southern and eastern England. -It is not so certain that the people who made this assemblage, called -Peterborough, were actually farmers. While they may on occasion have -practiced a simple agriculture, many items of their assemblage link -them closely with that of the �Forest folk� of earlier times in -England and in the Baltic countries. Their pottery is decorated with -impressions of cords and is quite different from that of Windmill Hill -and the megalithic builders. In addition, the distribution of their -finds extends into eastern Britain, where the other cultures have left -no trace. The Peterborough people had villages with semi-subterranean -huts, and the bones of oxen, pigs, and sheep have been found in a few -of these. On the whole, however, hunting and fishing seem to have been -their vital occupations. They also established trade routes especially -to acquire the raw material for stone axes. - -A probably slightly later culture, whose traces are best known from -Skara Brae on Orkney, also had its roots in those cultures of the -Baltic area which fused out of the meeting of the �Forest folk� and -the peoples who took the eastern way into Europe. Skara Brae is very -well preserved, having been built of thin stone slabs about which -dune-sand drifted after the village died. The individual houses, the -bedsteads, the shelves, the chests for clothes and oddments--all built -of thin stone-slabs--may still be seen in place. But the Skara Brae -people lived entirely by sheep- and cattle-breeding, and by catching -shellfish. Neither grain nor the instruments of agriculture appeared at -Skara Brae. - - -THE EUROPEAN ACHIEVEMENT - -The above is only a very brief description of what went on in Britain -with the arrival of the first farmers. There are many interesting -details which I have omitted in order to shorten the story. - -I believe some of the difficulty we have in understanding the -establishment of the first farming communities in Europe is with -the word �colonization.� We have a natural tendency to think of -�colonization� as it has happened within the last few centuries. In the -case of the colonization of the Americas, for example, the colonists -came relatively quickly, and in increasingly vast numbers. They had -vastly superior technical, political, and war-making skills, compared -with those of the Indians. There was not much mixing with the Indians. -The case in Europe five or six thousand years ago must have been very -different. I wonder if it is even proper to call people �colonists� -who move some miles to a new region, settle down and farm it for some -years, then move on again, generation after generation? The ideas and -the things which these new people carried were only _potentially_ -superior. The ideas and things and the people had to prove themselves -in their adaptation to each new environment. Once this was done another -link to the chain would be added, and then the forest-dwellers and -other indigenous folk of Europe along the way might accept the new -ideas and things. It is quite reasonable to expect that there must have -been much mixture of the migrants and the indigenes along the way; the -Peterborough and Skara Brae assemblages we mentioned above would seem -to be clear traces of such fused cultures. Sometimes, especially if the -migrants were moving by boat, long distances may have been covered in -a short time. Remember, however, we seem to have about three thousand -years between the early Syro-Cilician villages and Windmill Hill. - -Let me repeat Professor Childe again. �The peoples of the West were -not slavish imitators: they adapted the gifts from the East ... into -a new and organic whole capable of developing on its own original -lines.� Childe is of course completely conscious of the fact that his -�peoples of the West� were in part the descendants of migrants who came -originally from the �East,� bringing their �gifts� with them. This -was the late prehistoric achievement of Europe--to take new ideas and -things and some migrant peoples and, by mixing them with the old in its -own environments, to forge a new and unique series of cultures. - -What we know of the ways of men suggests to us that when the details -of the later prehistory of further Asia and Africa are learned, their -stories will be just as exciting. - - - - -THE Conquest of Civilization - -[Illustration] - - -Now we must return to the Near East again. We are coming to the point -where history is about to begin. I am going to stick pretty close -to Iraq and Egypt in this chapter. These countries will perhaps be -the most interesting to most of us, for the foundations of western -civilization were laid in the river lands of the Tigris and Euphrates -and of the Nile. I shall probably stick closest of all to Iraq, because -things first happened there and also because I know it best. - -There is another interesting thing, too. We have seen that the first -experiment in village-farming took place in the Near East. So did -the first experiment in civilization. Both experiments �took.� The -traditions we live by today are based, ultimately, on those ancient -beginnings in food-production and civilization in the Near East. - - -WHAT �CIVILIZATION� MEANS - -I shall not try to define �civilization� for you; rather, I shall -tell you what the word brings to my mind. To me civilization means -urbanization: the fact that there are cities. It means a formal -political set-up--that there are kings or governing bodies that the -people have set up. It means formal laws--rules of conduct--which the -government (if not the people) believes are necessary. It probably -means that there are formalized projects--roads, harbors, irrigation -canals, and the like--and also some sort of army or police force -to protect them. It means quite new and different art forms. It -also usually means there is writing. (The people of the Andes--the -Incas--had everything which goes to make up a civilization but formal -writing. I can see no reason to say they were not civilized.) Finally, -as the late Professor Redfield reminded us, civilization seems to bring -with it the dawn of a new kind of moral order. - -In different civilizations, there may be important differences in the -way such things as the above are managed. In early civilizations, it is -usual to find religion very closely tied in with government, law, and -so forth. The king may also be a high priest, or he may even be thought -of as a god. The laws are usually thought to have been given to the -people by the gods. The temples are protected just as carefully as the -other projects. - - -CIVILIZATION IMPOSSIBLE WITHOUT FOOD-PRODUCTION - -Civilizations have to be made up of many people. Some of the people -live in the country; some live in very large towns or cities. Classes -of society have begun. There are officials and government people; there -are priests or religious officials; there are merchants and traders; -there are craftsmen, metal-workers, potters, builders, and so on; there -are also farmers, and these are the people who produce the food for the -whole population. It must be obvious that civilization cannot exist -without food-production and that food-production must also be at a -pretty efficient level of village-farming before civilization can even -begin. - -But people can be food-producing without being civilized. In many -parts of the world this is still the case. When the white men first -came to America, the Indians in most parts of this hemisphere were -food-producers. They grew corn, potatoes, tomatoes, squash, and many -other things the white men had never eaten before. But only the Aztecs -of Mexico, the Mayas of Yucatan and Guatemala, and the Incas of the -Andes were civilized. - - -WHY DIDN�T CIVILIZATION COME TO ALL FOOD-PRODUCERS? - -Once you have food-production, even at the well-advanced level of -the village-farming community, what else has to happen before you -get civilization? Many men have asked this question and have failed -to give a full and satisfactory answer. There is probably no _one_ -answer. I shall give you my own idea about how civilization _may_ have -come about in the Near East alone. Remember, it is only a guess--a -putting together of hunches from incomplete evidence. It is _not_ meant -to explain how civilization began in any of the other areas--China, -southeast Asia, the Americas--where other early experiments in -civilization went on. The details in those areas are quite different. -Whether certain general principles hold, for the appearance of any -early civilization, is still an open and very interesting question. - - -WHERE CIVILIZATION FIRST APPEARED IN THE NEAR EAST - -You remember that our earliest village-farming communities lay along -the hilly flanks of a great �crescent.� (See map on p. 125.) -Professor Breasted�s �fertile crescent� emphasized the rich river -valleys of the Nile and the Tigris-Euphrates Rivers. Our hilly-flanks -area of the crescent zone arches up from Egypt through Palestine and -Syria, along southern Turkey into northern Iraq, and down along the -southwestern fringe of Iran. The earliest food-producing villages we -know already existed in this area by about 6750 B.C. (� 200 years). - -Now notice that this hilly-flanks zone does not include southern -Mesopotamia, the alluvial land of the lower Tigris and Euphrates in -Iraq, or the Nile Valley proper. The earliest known villages of classic -Mesopotamia and Egypt seem to appear fifteen hundred or more years -after those of the hilly-flanks zone. For example, the early Fayum -village which lies near a lake west of the Nile Valley proper (see p. -135) has a radiocarbon date of 4275 B.C. � 320 years. It was in the -river lands, however, that the immediate beginnings of civilization -were made. - -We know that by about 3200 B.C. the Early Dynastic period had begun -in southern Mesopotamia. The beginnings of writing go back several -hundred years earlier, but we can safely say that civilization had -begun in Mesopotamia by 3200 B.C. In Egypt, the beginning of the First -Dynasty is slightly later, at about 3100 B.C., and writing probably -did not appear much earlier. There is no question but that history and -civilization were well under way in both Mesopotamia and Egypt by 3000 -B.C.--about five thousand years ago. - - -THE HILLY-FLANKS ZONE VERSUS THE RIVER LANDS - -Why did these two civilizations spring up in these two river -lands which apparently were not even part of the area where the -village-farming community began? Why didn�t we have the first -civilizations in Palestine, Syria, north Iraq, or Iran, where we�re -sure food-production had had a long time to develop? I think the -probable answer gives a clue to the ways in which civilization began in -Egypt and Mesopotamia. - -The land in the hilly flanks is of a sort which people can farm without -too much trouble. There is a fairly fertile coastal strip in Palestine -and Syria. There are pleasant mountain slopes, streams running out to -the sea, and rain, at least in the winter months. The rain belt and the -foothills of the Turkish mountains also extend to northern Iraq and on -to the Iranian plateau. The Iranian plateau has its mountain valleys, -streams, and some rain. These hilly flanks of the �crescent,� through -most of its arc, are almost made-to-order for beginning farmers. The -grassy slopes of the higher hills would be pasture for their herds -and flocks. As soon as the earliest experiments with agriculture and -domestic animals had been successful, a pleasant living could be -made--and without too much trouble. - -I should add here again, that our evidence points increasingly to a -climate for those times which is very little different from that for -the area today. Now look at Egypt and southern Mesopotamia. Both are -lands without rain, for all intents and purposes. Both are lands with -rivers that have laid down very fertile soil--soil perhaps superior to -that in the hilly flanks. But in both lands, the rivers are of no great -aid without some control. - -The Nile floods its banks once a year, in late September or early -October. It not only soaks the narrow fertile strip of land on either -side; it lays down a fresh layer of new soil each year. Beyond the -fertile strip on either side rise great cliffs, and behind them is the -desert. In its natural, uncontrolled state, the yearly flood of the -Nile must have caused short-lived swamps that were full of crocodiles. -After a short time, the flood level would have dropped, the water and -the crocodiles would have run back into the river, and the swamp plants -would have become parched and dry. - -The Tigris and the Euphrates of Mesopotamia are less likely to flood -regularly than the Nile. The Tigris has a shorter and straighter course -than the Euphrates; it is also the more violent river. Its banks are -high, and when the snows melt and flow into all of its tributary rivers -it is swift and dangerous. The Euphrates has a much longer and more -curving course and few important tributaries. Its banks are lower and -it is less likely to flood dangerously. The land on either side and -between the two rivers is very fertile, south of the modern city of -Baghdad. Unlike the Nile Valley, neither the Tigris nor the Euphrates -is flanked by cliffs. The land on either side of the rivers stretches -out for miles and is not much rougher than a poor tennis court. - - -THE RIVERS MUST BE CONTROLLED - -The real trick in both Egypt and Mesopotamia is to make the rivers work -for you. In Egypt, this is a matter of building dikes and reservoirs -that will catch and hold the Nile flood. In this way, the water is held -and allowed to run off over the fields as it is needed. In Mesopotamia, -it is a matter of taking advantage of natural river channels and branch -channels, and of leading ditches from these onto the fields. - -Obviously, we can no longer find the first dikes or reservoirs of -the Nile Valley, or the first canals or ditches of Mesopotamia. The -same land has been lived on far too long for any traces of the first -attempts to be left; or, especially in Egypt, it has been covered by -the yearly deposits of silt, dropped by the river floods. But we�re -pretty sure the first food-producers of Egypt and southern Mesopotamia -must have made such dikes, canals, and ditches. In the first place, -there can�t have been enough rain for them to grow things otherwise. -In the second place, the patterns for such projects seem to have been -pretty well set by historic times. - - -CONTROL OF THE RIVERS THE BUSINESS OF EVERYONE - -Here, then, is a _part_ of the reason why civilization grew in Egypt -and Mesopotamia first--not in Palestine, Syria, or Iran. In the latter -areas, people could manage to produce their food as individuals. It -wasn�t too hard; there were rain and some streams, and good pasturage -for the animals even if a crop or two went wrong. In Egypt and -Mesopotamia, people had to put in a much greater amount of work, and -this work couldn�t be individual work. Whole villages or groups of -people had to turn out to fix dikes or dig ditches. The dikes had to be -repaired and the ditches carefully cleared of silt each year, or they -would become useless. - -There also had to be hard and fast rules. The person who lived nearest -the ditch or the reservoir must not be allowed to take all the water -and leave none for his neighbors. It was not only a business of -learning to control the rivers and of making their waters do the -farmer�s work. It also meant controlling men. But once these men had -managed both kinds of controls, what a wonderful yield they had! The -soil was already fertile, and the silt which came in the floods and -ditches kept adding fertile soil. - - -THE GERM OF CIVILIZATION IN EGYPT AND MESOPOTAMIA - -This learning to work together for the common good was the real germ of -the Egyptian and the Mesopotamian civilizations. The bare elements of -civilization were already there: the need for a governing hand and for -laws to see that the communities� work was done and that the water was -justly shared. You may object that there is a sort of chicken and egg -paradox in this idea. How could the people set up the rules until they -had managed to get a way to live, and how could they manage to get a -way to live until they had set up the rules? I think that small groups -must have moved down along the mud-flats of the river banks quite -early, making use of naturally favorable spots, and that the rules grew -out of such cases. It would have been like the hand-in-hand growth of -automobiles and paved highways in the United States. - -Once the rules and the know-how did get going, there must have been a -constant interplay of the two. Thus, the more the crops yielded, the -richer and better-fed the people would have been, and the more the -population would have grown. As the population grew, more land would -have needed to be flooded or irrigated, and more complex systems of -dikes, reservoirs, canals, and ditches would have been built. The more -complex the system, the more necessity for work on new projects and for -the control of their use.... And so on.... - -What I have just put down for you is a guess at the manner of growth of -some of the formalized systems that go to make up a civilized society. -My explanation has been pointed particularly at Egypt and Mesopotamia. -I have already told you that the irrigation and water-control part of -it does not apply to the development of the Aztecs or the Mayas, or -perhaps anybody else. But I think that a fair part of the story of -Egypt and Mesopotamia must be as I�ve just told you. - -I am particularly anxious that you do _not_ understand me to mean that -irrigation _caused_ civilization. I am sure it was not that simple at -all. For, in fact, a complex and highly engineered irrigation system -proper did not come until later times. Let�s say rather that the simple -beginnings of irrigation allowed and in fact encouraged a great number -of things in the technological, political, social, and moral realms of -culture. We do not yet understand what all these things were or how -they worked. But without these other aspects of culture, I do not -think that urbanization and civilization itself could have come into -being. - - -THE ARCHEOLOGICAL SEQUENCE TO CIVILIZATION IN IRAQ - -We last spoke of the archeological materials of Iraq on page 130, -where I described the village-farming community of Hassunan type. The -Hassunan type villages appear in the hilly-flanks zone and in the -rolling land adjacent to the Tigris in northern Iraq. It is probable -that even before the Hassuna pattern of culture lived its course, a -new assemblage had been established in northern Iraq and Syria. This -assemblage is called Halaf, after a site high on a tributary of the -Euphrates, on the Syro-Turkish border. - -[Illustration: SKETCH OF SELECTED ITEMS OF HALAFIAN ASSEMBLAGE - - BEADS AND PENDANTS - POTTERY MOTIFS - POTTERY] - -The Halafian assemblage is incompletely known. The culture it -represents included a remarkably handsome painted pottery. -Archeologists have tended to be so fascinated with this pottery that -they have bothered little with the rest of the Halafian assemblage. We -do know that strange stone-founded houses, with plans like those of the -popular notion of an Eskimo igloo, were built. Like the pottery of the -Samarran style, which appears as part of the Hassunan assemblage (see -p. 131), the Halafian painted pottery implies great concentration and -excellence of draftsmanship on the part of the people who painted it. - -We must mention two very interesting sites adjacent to the mud-flats of -the rivers, half way down from northern Iraq to the classic alluvial -Mesopotamian area. One is Baghouz on the Euphrates; the other is -Samarra on the Tigris (see map, p. 125). Both these sites yield the -handsome painted pottery of the style called Samarran: in fact it -is Samarra which gives its name to the pottery. Neither Baghouz nor -Samarra have completely Hassunan types of assemblages, and at Samarra -there are a few pots of proper Halafian style. I suppose that Samarra -and Baghouz give us glimpses of those early farmers who had begun to -finger their way down the mud-flats of the river banks toward the -fertile but yet untilled southland. - - -CLASSIC SOUTHERN MESOPOTAMIA FIRST OCCUPIED - -Our next step is into the southland proper. Here, deep in the core of -the mound which later became the holy Sumerian city of Eridu, Iraqi -archeologists uncovered a handsome painted pottery. Pottery of the same -type had been noticed earlier by German archeologists on the surface -of a small mound, awash in the spring floods, near the remains of the -Biblical city of Erich (Sumerian = Uruk; Arabic = Warka). This �Eridu� -pottery, which is about all we have of the assemblage of the people who -once produced it, may be seen as a blend of the Samarran and Halafian -painted pottery styles. This may over-simplify the case, but as yet we -do not have much evidence to go on. The idea does at least fit with my -interpretation of the meaning of Baghouz and Samarra as way-points on -the mud-flats of the rivers half way down from the north. - -My colleague, Robert Adams, believes that there were certainly -riverine-adapted food-collectors living in lower Mesopotamia. The -presence of such would explain why the Eridu assemblage is not simply -the sum of the Halafian and Samarran assemblages. But the domesticated -plants and animals and the basic ways of food-production must have -come from the hilly-flanks country in the north. - -Above the basal Eridu levels, and at a number of other sites in the -south, comes a full-fledged assemblage called Ubaid. Incidentally, -there is an aspect of the Ubaidian assemblage in the north as well. It -seems to move into place before the Halaf manifestation is finished, -and to blend with it. The Ubaidian assemblage in the south is by far -the more spectacular. The development of the temple has been traced -at Eridu from a simple little structure to a monumental building some -62 feet long, with a pilaster-decorated fa�ade and an altar in its -central chamber. There is painted Ubaidian pottery, but the style is -hurried and somewhat careless and gives the _impression_ of having been -a cheap mass-production means of decoration when compared with the -carefully drafted styles of Samarra and Halaf. The Ubaidian people made -other items of baked clay: sickles and axes of very hard-baked clay -are found. The northern Ubaidian sites have yielded tools of copper, -but metal tools of unquestionable Ubaidian find-spots are not yet -available from the south. Clay figurines of human beings with monstrous -turtle-like faces are another item in the southern Ubaidian assemblage. - -[Illustration: SKETCH OF SELECTED ITEMS OF UBAIDIAN ASSEMBLAGE] - -There is a large Ubaid cemetery at Eridu, much of it still awaiting -excavation. The few skeletons so far tentatively studied reveal a -completely modern type of �Mediterraneanoid�; the individuals whom the -skeletons represent would undoubtedly blend perfectly into the modern -population of southern Iraq. What the Ubaidian assemblage says to us is -that these people had already adapted themselves and their culture to -the peculiar riverine environment of classic southern Mesopotamia. For -example, hard-baked clay axes will chop bundles of reeds very well, or -help a mason dress his unbaked mud bricks, and there were only a few -soft and pithy species of trees available. The Ubaidian levels of Eridu -yield quantities of date pits; that excellent and characteristically -Iraqi fruit was already in use. The excavators also found the clay -model of a ship, with the stepping-point for a mast, so that Sinbad the -Sailor must have had his antecedents as early as the time of Ubaid. -The bones of fish, which must have flourished in the larger canals as -well as in the rivers, are common in the Ubaidian levels and thereafter. - - -THE UBAIDIAN ACHIEVEMENT - -On present evidence, my tendency is to see the Ubaidian assemblage -in southern Iraq as the trace of a new era. I wish there were more -evidence, but what we have suggests this to me. The culture of southern -Ubaid soon became a culture of towns--of centrally located towns with -some rural villages about them. The town had a temple and there must -have been priests. These priests probably had political and economic -functions as well as religious ones, if the somewhat later history of -Mesopotamia may suggest a pattern for us. Presently the temple and its -priesthood were possibly the focus of the market; the temple received -its due, and may already have had its own lands and herds and flocks. -The people of the town, undoubtedly at least in consultation with the -temple administration, planned and maintained the simple irrigation -ditches. As the system flourished, the community of rural farmers would -have produced more than sufficient food. The tendency for specialized -crafts to develop--tentative at best at the cultural level of the -earlier village-farming community era--would now have been achieved, -and probably many other specialists in temple administration, water -control, architecture, and trade would also have appeared, as the -surplus food-supply was assured. - -Southern Mesopotamia is not a land rich in natural resources other -than its fertile soil. Stone, good wood for construction, metal, and -innumerable other things would have had to be imported. Grain and -dates--although both are bulky and difficult to transport--and wool and -woven stuffs must have been the mediums of exchange. Over what area did -the trading net-work of Ubaid extend? We start with the idea that the -Ubaidian assemblage is most richly developed in the south. We assume, I -think, correctly, that it represents a cultural flowering of the south. -On the basis of the pottery of the still elusive �Eridu� immigrants -who had first followed the rivers into alluvial Mesopotamia, we get -the notion that the characteristic painted pottery style of Ubaid -was developed in the southland. If this reconstruction is correct -then we may watch with interest where the Ubaid pottery-painting -tradition spread. We have already mentioned that there is a substantial -assemblage of (and from the southern point of view, _fairly_ pure) -Ubaidian material in northern Iraq. The pottery appears all along the -Iranian flanks, even well east of the head of the Persian Gulf, and -ends in a later and spectacular flourish in an extremely handsome -painted style called the �Susa� style. Ubaidian pottery has been noted -up the valleys of both of the great rivers, well north of the Iraqi -and Syrian borders on the southern flanks of the Anatolian plateau. -It reaches the Mediterranean Sea and the valley of the Orontes in -Syria, and it may be faintly reflected in the painted style of a -site called Ghassul, on the east bank of the Jordan in the Dead Sea -Valley. Over this vast area--certainly in all of the great basin of -the Tigris-Euphrates drainage system and its natural extensions--I -believe we may lay our fingers on the traces of a peculiar way of -decorating pottery, which we call Ubaidian. This cursive and even -slap-dash decoration, it appears to me, was part of a new cultural -tradition which arose from the adjustments which immigrant northern -farmers first made to the new and challenging environment of southern -Mesopotamia. But exciting as the idea of the spread of influences of -the Ubaid tradition in space may be, I believe you will agree that the -consequences of the growth of that tradition in southern Mesopotamia -itself, as time passed, are even more important. - - -THE WARKA PHASE IN THE SOUTH - -So far, there are only two radiocarbon determinations for the Ubaidian -assemblage, one from Tepe Gawra in the north and one from Warka in the -south. My hunch would be to use the dates 4500 to 3750 B.C., with a -plus or more probably a minus factor of about two hundred years for -each, as the time duration of the Ubaidian assemblage in southern -Mesopotamia. - -Next, much to our annoyance, we have what is almost a temporary -black-out. According to the system of terminology I favor, our next -�assemblage� after that of Ubaid is called the _Warka_ phase, from -the Arabic name for the site of Uruk or Erich. We know it only from -six or seven levels in a narrow test-pit at Warka, and from an even -smaller hole at another site. This �assemblage,� so far, is known only -by its pottery, some of which still bears Ubaidian style painting. The -characteristic Warkan pottery is unpainted, with smoothed red or gray -surfaces and peculiar shapes. Unquestionably, there must be a great -deal more to say about the Warkan assemblage, but someone will first -have to excavate it! - - -THE DAWN OF CIVILIZATION - -After our exasperation with the almost unknown Warka interlude, -following the brilliant �false dawn� of Ubaid, we move next to an -assemblage which yields traces of a preponderance of those elements -which we noted (p. 144) as meaning civilization. This assemblage -is that called _Proto-Literate_; it already contains writing. On -the somewhat shaky principle that writing, however early, means -history--and no longer prehistory--the assemblage is named for the -historical implications of its content, and no longer after the name of -the site where it was first found. Since some of the older books used -site-names for this assemblage, I will tell you that the Proto-Literate -includes the latter half of what used to be called the �Uruk period� -_plus_ all of what used to be called the �Jemdet Nasr period.� It shows -a consistent development from beginning to end. - -I shall, in fact, leave much of the description and the historic -implications of the Proto-Literate assemblage to the conventional -historians. Professor T. J. Jacobsen, reaching backward from the -legends he finds in the cuneiform writings of slightly later times, can -in fact tell you a more complete story of Proto-Literate culture than -I can. It should be enough here if I sum up briefly what the excavated -archeological evidence shows. - -We have yet to dig a Proto-Literate site in its entirety, but the -indications are that the sites cover areas the size of small cities. -In architecture, we know of large and monumental temple structures, -which were built on elaborate high terraces. The plans and decoration -of these temples follow the pattern set in the Ubaid phase: the chief -difference is one of size. The German excavators at the site of Warka -reckoned that the construction of only one of the Proto-Literate temple -complexes there must have taken 1,500 men, each working a ten-hour day, -five years to build. - - -ART AND WRITING - -If the architecture, even in its monumental forms, can be seen to -stem from Ubaidian developments, this is not so with our other -evidence of Proto-Literate artistic expression. In relief and applied -sculpture, in sculpture in the round, and on the engraved cylinder -seals--all of which now make their appearance--several completely -new artistic principles are apparent. These include the composition -of subject-matter in groups, commemorative scenes, and especially -the ability and apparent desire to render the human form and face. -Excellent as the animals of the Franco-Cantabrian art may have been -(see p. 85), and however handsome were the carefully drafted -geometric designs and conventionalized figures on the pottery of the -early farmers, there seems to have been, up to this time, a mental -block about the drawing of the human figure and especially the human -face. We do not yet know what caused this self-consciousness about -picturing themselves which seems characteristic of men before the -appearance of civilization. We do know that with civilization, the -mental block seems to have been removed. - -Clay tablets bearing pictographic signs are the Proto-Literate -forerunners of cuneiform writing. The earliest examples are not well -understood but they seem to be �devices for making accounts and -for remembering accounts.� Different from the later case in Egypt, -where writing appears fully formed in the earliest examples, the -development from simple pictographic signs to proper cuneiform writing -may be traced, step by step, in Mesopotamia. It is most probable -that the development of writing was connected with the temple and -the need for keeping account of the temple�s possessions. Professor -Jacobsen sees writing as a means for overcoming space, time, and the -increasing complications of human affairs: �Literacy, which began -with ... civilization, enhanced mightily those very tendencies in its -development which characterize it as a civilization and mark it off as -such from other types of culture.� - -[Illustration: RELIEF ON A PROTO-LITERATE STONE VASE, WARKA - -Unrolled drawing, with restoration suggested by figures from -contemporary cylinder seals] - -While the new principles in art and the idea of writing are not -foreshadowed in the Ubaid phase, or in what little we know of the -Warkan, I do not think we need to look outside southern Mesopotamia -for their beginnings. We do know something of the adjacent areas, -too, and these beginnings are not there. I think we must accept them -as completely new discoveries, made by the people who were developing -the whole new culture pattern of classic southern Mesopotamia. Full -description of the art, architecture, and writing of the Proto-Literate -phase would call for many details. Men like Professor Jacobsen and Dr. -Adams can give you these details much better than I can. Nor shall I do -more than tell you that the common pottery of the Proto-Literate phase -was so well standardized that it looks factory made. There was also -some handsome painted pottery, and there were stone bowls with inlaid -decoration. Well-made tools in metal had by now become fairly common, -and the metallurgist was experimenting with the casting process. Signs -for plows have been identified in the early pictographs, and a wheeled -chariot is shown on a cylinder seal engraving. But if I were forced to -a guess in the matter, I would say that the development of plows and -draft-animals probably began in the Ubaid period and was another of the -great innovations of that time. - -The Proto-Literate assemblage clearly suggests a highly developed and -sophisticated culture. While perhaps not yet fully urban, it is on -the threshold of urbanization. There seems to have been a very dense -settlement of Proto-Literate sites in classic southern Mesopotamia, -many of them newly founded on virgin soil where no earlier settlements -had been. When we think for a moment of what all this implies, of the -growth of an irrigation system which must have existed to allow the -flourish of this culture, and of the social and political organization -necessary to maintain the irrigation system, I think we will agree that -at last we are dealing with civilization proper. - - -FROM PREHISTORY TO HISTORY - -Now it is time for the conventional ancient historians to take over -the story from me. Remember this when you read what they write. Their -real base-line is with cultures ruled over by later kings and emperors, -whose writings describe military campaigns and the administration of -laws and fully organized trading ventures. To these historians, the -Proto-Literate phase is still a simple beginning for what is to follow. -If they mention the Ubaid assemblage at all--the one I was so lyrical -about--it will be as some dim and fumbling step on the path to the -civilized way of life. - -I suppose you could say that the difference in the approach is that as -a prehistorian I have been looking forward or upward in time, while the -historians look backward to glimpse what I�ve been describing here. My -base-line was half a million years ago with a being who had little more -than the capacity to make tools and fire to distinguish him from the -animals about him. Thus my point of view and that of the conventional -historian are bound to be different. You will need both if you want to -understand all of the story of men, as they lived through time to the -present. - - - - -End of PREHISTORY - -[Illustration] - - -You�ll doubtless easily recall your general course in ancient history: -how the Sumerian dynasties of Mesopotamia were supplanted by those of -Babylonia, how the Hittite kingdom appeared in Anatolian Turkey, and -about the three great phases of Egyptian history. The literate kingdom -of Crete arose, and by 1500 B.C. there were splendid fortified Mycenean -towns on the mainland of Greece. This was the time--about the whole -eastern end of the Mediterranean--of what Professor Breasted called the -�first great internationalism,� with flourishing trade, international -treaties, and royal marriages between Egyptians, Babylonians, and -Hittites. By 1200 B.C., the whole thing had fragmented: �the peoples of -the sea were restless in their isles,� and the great ancient centers in -Egypt, Mesopotamia, and Anatolia were eclipsed. Numerous smaller states -arose--Assyria, Phoenicia, Israel--and the Trojan war was fought. -Finally Assyria became the paramount power of all the Near East, -presently to be replaced by Persia. - -A new culture, partaking of older west Asiatic and Egyptian elements, -but casting them with its own tradition into a new mould, arose in -mainland Greece. - -I once shocked my Classical colleagues to the core by referring to -Greece as �a second degree derived civilization,� but there is much -truth in this. The principles of bronze- and then of iron-working, of -the alphabet, and of many other elements in Greek culture were borrowed -from western Asia. Our debt to the Greeks is too well known for me even -to mention it, beyond recalling to you that it is to Greece we owe the -beginnings of rational or empirical science and thought in general. But -Greece fell in its turn to Rome, and in 55 B.C. Caesar invaded Britain. - -I last spoke of Britain on page 142; I had chosen it as my single -example for telling you something of how the earliest farming -communities were established in Europe. Now I will continue with -Britain�s later prehistory, so you may sense something of the end of -prehistory itself. Remember that Britain is simply a single example -we select; the same thing could be done for all the other countries -of Europe, and will be possible also, some day, for further Asia and -Africa. Remember, too, that prehistory in most of Europe runs on for -three thousand or more years _after_ conventional ancient history -begins in the Near East. Britain is a good example to use in showing -how prehistory ended in Europe. As we said earlier, it lies at the -opposite end of Europe from the area of highest cultural achievement in -those times, and should you care to read more of the story in detail, -you may do so in the English language. - - -METAL USERS REACH ENGLAND - -We left the story of Britain with the peoples who made three different -assemblages--the Windmill Hill, the megalith-builders, and the -Peterborough--making adjustments to their environments, to the original -inhabitants of the island, and to each other. They had first arrived -about 2500 B.C., and were simple pastoralists and hoe cultivators who -lived in little village communities. Some of them planted little if any -grain. By 2000 B.C., they were well settled in. Then, somewhere in the -range from about 1900 to 1800 B.C., the traces of the invasion of a new -series of peoples began to appear. - -The first newcomers are called the Beaker folk, after the name of a -peculiar form of pottery they made. The beaker type of pottery seems -oldest in Spain, where it occurs with great collective tombs of -megalithic construction and with copper tools. But the Beaker folk who -reached England seem already to have moved first from Spain(?) to the -Rhineland and Holland. While in the Rhineland, and before leaving for -England, the Beaker folk seem to have mixed with the local population -and also with incomers from northeastern Europe whose culture included -elements brought originally from the Near East by the eastern way -through the steppes. This last group has also been named for a peculiar -article in its assemblage; the group is called the Battle-axe folk. A -few Battle-axe folk elements, including, in fact, stone battle-axes, -reached England with the earliest Beaker folk,[6] coming from the -Rhineland. - - [6] The British authors use the term �Beaker folk� to mean both - archeological assemblage and human physical type. They speak - of a �... tall, heavy-boned, rugged, and round-headed� strain - which they take to have developed, apparently in the Rhineland, - by a mixture of the original (Spanish?) beaker-makers and - the northeast European battle-axe makers. However, since the - science of physical anthropology is very much in flux at the - moment, and since I am not able to assess the evidence for these - physical types, I _do not_ use the term �folk� in this book with - its usual meaning of standardized physical type. When I use - �folk� here, I mean simply _the makers of a given archeological - assemblage_. The difficulty only comes when assemblages are - named for some item in them; it is too clumsy to make an - adjective of the item and refer to a �beakerian� assemblage. - -The Beaker folk settled earliest in the agriculturally fertile south -and east. There seem to have been several phases of Beaker folk -invasions, and it is not clear whether these all came strictly from the -Rhineland or Holland. We do know that their copper daggers and awls -and armlets are more of Irish or Atlantic European than of Rhineland -origin. A few simple habitation sites and many burials of the Beaker -folk are known. They buried their dead singly, sometimes in conspicuous -individual barrows with the dead warrior in his full trappings. The -spectacular element in the assemblage of the Beaker folk is a group -of large circular monuments with ditches and with uprights of wood or -stone. These �henges� became truly monumental several hundred years -later; while they were occasionally dedicated with a burial, they were -not primarily tombs. The effect of the invasion of the Beaker folk -seems to cut across the whole fabric of life in Britain. - -[Illustration: BEAKER] - -There was, however, a second major element in British life at this -time. It shows itself in the less well understood traces of a group -again called after one of the items in their catalogue, the Food-vessel -folk. There are many burials in these �food-vessel� pots in northern -England, Scotland, and Ireland, and the pottery itself seems to -link back to that of the Peterborough assemblage. Like the earlier -Peterborough people in the highland zone before them, the makers of -the food-vessels seem to have been heavily involved in trade. It is -quite proper to wonder whether the food-vessel pottery itself was made -by local women who were married to traders who were middlemen in the -transmission of Irish metal objects to north Germany and Scandinavia. -The belt of high, relatively woodless country, from southwest to -northeast, was already established as a natural route for inland trade. - - -MORE INVASIONS - -About 1500 B.C., the situation became further complicated by the -arrival of new people in the region of southern England anciently -called Wessex. The traces suggest the Brittany coast of France as a -source, and the people seem at first to have been a small but �heroic� -group of aristocrats. Their �heroes� are buried with wealth and -ceremony, surrounded by their axes and daggers of bronze, their gold -ornaments, and amber and jet beads. These rich finds show that the -trade-linkage these warriors patronized spread from the Baltic sources -of amber to Mycenean Greece or even Egypt, as evidenced by glazed blue -beads. - -The great visual trace of Wessex achievement is the final form of -the spectacular sanctuary at Stonehenge. A wooden henge or circular -monument was first made several hundred years earlier, but the site -now received its great circles of stone uprights and lintels. The -diameter of the surrounding ditch at Stonehenge is about 350 feet, the -diameter of the inner circle of large stones is about 100 feet, and -the tallest stone of the innermost horseshoe-shaped enclosure is 29 -feet 8 inches high. One circle is made of blue stones which must have -been transported from Pembrokeshire, 145 miles away as the crow flies. -Recently, many carvings representing the profile of a standard type of -bronze axe of the time, and several profiles of bronze daggers--one of -which has been called Mycenean in type--have been found carved in the -stones. We cannot, of course, describe the details of the religious -ceremonies which must have been staged in Stonehenge, but we can -certainly imagine the well-integrated and smoothly working culture -which must have been necessary before such a great monument could have -been built. - - -�THIS ENGLAND� - -The range from 1900 to about 1400 B.C. includes the time of development -of the archeological features usually called the �Early Bronze Age� -in Britain. In fact, traces of the Wessex warriors persisted down to -about 1200 B.C. The main regions of the island were populated, and the -adjustments to the highland and lowland zones were distinct and well -marked. The different aspects of the assemblages of the Beaker folk and -the clearly expressed activities of the Food-vessel folk and the Wessex -warriors show that Britain was already taking on her characteristic -trading role, separated from the European continent but conveniently -adjacent to it. The tin of Cornwall--so important in the production -of good bronze--as well as the copper of the west and of Ireland, -taken with the gold of Ireland and the general excellence of Irish -metal work, assured Britain a trader�s place in the then known world. -Contacts with the eastern Mediterranean may have been by sea, with -Cornish tin as the attraction, or may have been made by the Food-vessel -middlemen on their trips to the Baltic coast. There they would have -encountered traders who traveled the great north-south European road, -by which Baltic amber moved southward to Greece and the Levant, and -ideas and things moved northward again. - -There was, however, the Channel between England and Europe, and this -relative isolation gave some peace and also gave time for a leveling -and further fusion of culture. The separate cultural traditions began -to have more in common. The growing of barley, the herding of sheep and -cattle, and the production of woolen garments were already features -common to all Britain�s inhabitants save a few in the remote highlands, -the far north, and the distant islands not yet fully touched by -food-production. The �personality of Britain� was being formed. - - -CREMATION BURIALS BEGIN - -Along with people of certain religious faiths, archeologists are -against cremation (for other people!). Individuals to be cremated seem -in past times to have been dressed in their trappings and put upon a -large pyre: it takes a lot of wood and a very hot fire for a thorough -cremation. When the burning had been completed, the few fragile scraps -of bone and such odd beads of stone or other rare items as had resisted -the great heat seem to have been whisked into a pot and the pot buried. -The archeologist is left with the pot and the unsatisfactory scraps in -it. - -Tentatively, after about 1400 B.C. and almost completely over the whole -island by 1200 B.C., Britain became the scene of cremation burials -in urns. We know very little of the people themselves. None of their -settlements have been identified, although there is evidence that they -grew barley and made enclosures for cattle. The urns used for the -burials seem to have antecedents in the pottery of the Food-vessel -folk, and there are some other links with earlier British traditions. -In Lancashire, a wooden circle seems to have been built about a grave -with cremated burials in urns. Even occasional instances of cremation -may be noticed earlier in Britain, and it is not clear what, if any, -connection the British cremation burials in urns have with the classic -_Urnfields_ which were now beginning in the east Mediterranean and -which we shall mention below. - -The British cremation-burial-in-urns folk survived a long time in the -highland zone. In the general British scheme, they make up what is -called the �Middle Bronze Age,� but in the highland zone they last -until after 900 B.C. and are considered to be a specialized highland -�Late Bronze Age.� In the highland zone, these later cremation-burial -folk seem to have continued the older Food-vessel tradition of being -middlemen in the metal market. - -Granting that our knowledge of this phase of British prehistory is -very restricted because the cremations have left so little for the -archeologist, it does not appear that the cremation-burial-urn folk can -be sharply set off from their immediate predecessors. But change on a -grander scale was on the way. - - -REVERBERATIONS FROM CENTRAL EUROPE - -In the centuries immediately following 1000 B.C., we see with fair -clarity two phases of a cultural process which must have been going -on for some time. Certainly several of the invasions we have already -described in this chapter were due to earlier phases of the same -cultural process, but we could not see the details. - -[Illustration: SLASHING SWORD] - -Around 1200 B.C. central Europe was upset by the spread of the -so-called Urnfield folk, who practiced cremation burial in urns and -whom we also know to have been possessors of long, slashing swords and -the horse. I told you above that we have no idea that the Urnfield -folk proper were in any way connected with the people who made -cremation-burial-urn cemeteries a century or so earlier in Britain. It -has been supposed that the Urnfield folk themselves may have shared -ideas with the people who sacked Troy. We know that the Urnfield -pressure from central Europe displaced other people in northern France, -and perhaps in northwestern Germany, and that this reverberated into -Britain about 1000 B.C. - -Soon after 750 B.C., the same thing happened again. This time, the -pressure from central Europe came from the Hallstatt folk who were iron -tool makers: the reverberation brought people from the western Alpine -region across the Channel into Britain. - -At first it is possible to see the separate results of these folk -movements, but the developing cultures soon fused with each other and -with earlier British elements. Presently there were also strains of -other northern and western European pottery and traces of Urnfield -practices themselves which appeared in the finished British product. I -hope you will sense that I am vastly over-simplifying the details. - -The result seems to have been--among other things--a new kind of -agricultural system. The land was marked off by ditched divisions. -Rectangular fields imply the plow rather than hoe cultivation. We seem -to get a picture of estate or tribal boundaries which included village -communities; we find a variety of tools in bronze, and even whetstones -which show that iron has been honed on them (although the scarce iron -has not been found). Let me give you the picture in Professor S. -Piggott�s words: �The ... Late Bronze Age of southern England was but -the forerunner of the earliest Iron Age in the same region, not only in -the techniques of agriculture, but almost certainly in terms of ethnic -kinship ... we can with some assurance talk of the Celts ... the great -early Celtic expansion of the Continent is recognized to be that of the -Urnfield people.� - -Thus, certainly by 500 B.C., there were people in Britain, some of -whose descendants we may recognize today in name or language in remote -parts of Wales, Scotland, and the Hebrides. - - -THE COMING OF IRON - -Iron--once the know-how of reducing it from its ore in a very hot, -closed fire has been achieved--produces a far cheaper and much more -efficient set of tools than does bronze. Iron tools seem first to -have been made in quantity in Hittite Anatolia about 1500 B.C. In -continental Europe, the earliest, so-called Hallstatt, iron-using -cultures appeared in Germany soon after 750 B.C. Somewhat later, -Greek and especially Etruscan exports of _objets d�art_--which moved -with a flourishing trans-Alpine wine trade--influenced the Hallstatt -iron-working tradition. Still later new classical motifs, together with -older Hallstatt, oriental, and northern nomad motifs, gave rise to a -new style in metal decoration which characterizes the so-called La T�ne -phase. - -A few iron users reached Britain a little before 400 B.C. Not long -after that, a number of allied groups appeared in southern and -southeastern England. They came over the Channel from France and must -have been Celts with dialects related to those already in England. A -second wave of Celts arrived from the Marne district in France about -250 B.C. Finally, in the second quarter of the first century B.C., -there were several groups of newcomers, some of whom were Belgae of -a mixed Teutonic-Celtic confederacy of tribes in northern France and -Belgium. The Belgae preceded the Romans by only a few years. - - -HILL-FORTS AND FARMS - -The earliest iron-users seem to have entrenched themselves temporarily -within hill-top forts, mainly in the south. Gradually, they moved -inland, establishing _individual_ farm sites with extensive systems -of rectangular fields. We recognize these fields by the �lynchets� or -lines of soil-creep which plowing left on the slopes of hills. New -crops appeared; there were now bread wheat, oats, and rye, as well as -barley. - -At Little Woodbury, near the town of Salisbury, a farmstead has been -rather completely excavated. The rustic buildings were within a -palisade, the round house itself was built of wood, and there were -various outbuildings and pits for the storage of grain. Weaving was -done on the farm, but not blacksmithing, which must have been a -specialized trade. Save for the lack of firearms, the place might -almost be taken for a farmstead on the American frontier in the early -1800�s. - -Toward 250 B.C. there seems to have been a hasty attempt to repair the -hill-forts and to build new ones, evidently in response to signs of -restlessness being shown by remote relatives in France. - - -THE SECOND PHASE - -Perhaps the hill-forts were not entirely effective or perhaps a -compromise was reached. In any case, the newcomers from the Marne -district did establish themselves, first in the southeast and then to -the north and west. They brought iron with decoration of the La T�ne -type and also the two-wheeled chariot. Like the Wessex warriors of -over a thousand years earlier, they made �heroes�� graves, with their -warriors buried in the war-chariots and dressed in full trappings. - -[Illustration: CELTIC BUCKLE] - -The metal work of these Marnian newcomers is excellent. The peculiar -Celtic art style, based originally on the classic tendril motif, -is colorful and virile, and fits with Greek and Roman descriptions -of Celtic love of color in dress. There is a strong trace of these -newcomers northward in Yorkshire, linked by Ptolemy�s description to -the Parisii, doubtless part of the Celtic tribe which originally gave -its name to Paris on the Seine. Near Glastonbury, in Somerset, two -villages in swamps have been excavated. They seem to date toward the -middle of the first century B.C., which was a troubled time in Britain. -The circular houses were built on timber platforms surrounded with -palisades. The preservation of antiquities by the water-logged peat of -the swamp has yielded us a long catalogue of the materials of these -villagers. - -In Scotland, which yields its first iron tools at a date of about 100 -B.C., and in northern Ireland even slightly earlier, the effects of the -two phases of newcomers tend especially to blend. Hill-forts, �brochs� -(stone-built round towers) and a variety of other strange structures -seem to appear as the new ideas develop in the comparative isolation of -northern Britain. - - -THE THIRD PHASE - -For the time of about the middle of the first century B.C., we again -see traces of frantic hill-fort construction. This simple military -architecture now took some new forms. Its multiple ramparts must -reflect the use of slings as missiles, rather than spears. We probably -know the reason. In 56 B.C., Julius Caesar chastised the Veneti of -Brittany for outraging the dignity of Roman ambassadors. The Veneti -were famous slingers, and doubtless the reverberations of escaping -Veneti were felt across the Channel. The military architecture suggests -that some Veneti did escape to Britain. - -Also, through Caesar, we learn the names of newcomers who arrived in -two waves, about 75 B.C. and about 50 B.C. These were the Belgae. Now, -at last, we can even begin to speak of dynasties and individuals. -Some time before 55 B.C., the Catuvellauni, originally from the Marne -district in France, had possessed themselves of a large part of -southeastern England. They evidently sailed up the Thames and built a -town of over a hundred acres in area. Here ruled Cassivellaunus, �the -first man in England whose name we know,� and whose town Caesar sacked. -The town sprang up elsewhere again, however. - - -THE END OF PREHISTORY - -Prehistory, strictly speaking, is now over in southern Britain. -Claudius� effective invasion took place in 43 A.D.; by 83 A.D., a raid -had been made as far north as Aberdeen in Scotland. But by 127 A.D., -Hadrian had completed his wall from the Solway to the Tyne, and the -Romans settled behind it. In Scotland, Romanization can have affected -the countryside very little. Professor Piggott adds that �... it is -when the pressure of Romanization is relaxed by the break-up of the -Dark Ages that we see again the Celtic metal-smiths handling their -material with the same consummate skill as they had before the Roman -Conquest, and with traditional styles that had not even then forgotten -their Marnian and Belgic heritage.� - -In fact, many centuries go by, in Britain as well as in the rest of -Europe, before the archeologist�s task is complete and the historian on -his own is able to describe the ways of men in the past. - - -BRITAIN AS A SAMPLE OF THE GENERAL COURSE OF PREHISTORY IN EUROPE - -In giving this very brief outline of the later prehistory of Britain, -you will have noticed how often I had to refer to the European -continent itself. Britain, beyond the English Channel for all of her -later prehistory, had a much simpler course of events than did most of -the rest of Europe in later prehistoric times. This holds, in spite -of all the �invasions� and �reverberations� from the continent. Most -of Europe was the scene of an even more complicated ebb and flow of -cultural change, save in some of its more remote mountain valleys and -peninsulas. - -The whole course of later prehistory in Europe is, in fact, so very -complicated that there is no single good book to cover it all; -certainly there is none in English. There are some good regional -accounts and some good general accounts of part of the range from about -3000 B.C. to A.D. 1. I suspect that the difficulty of making a good -book that covers all of its later prehistory is another aspect of what -makes Europe so very complicated a continent today. The prehistoric -foundations for Europe�s very complicated set of civilizations, -cultures, and sub-cultures--which begin to appear as history -proceeds--were in themselves very complicated. - -Hence, I selected the case of Britain as a single example of how -prehistory ends in Europe. It could have been more complicated than we -found it to be. Even in the subject matter on Britain in the chapter -before the last, we did not see direct traces of the effect on Britain -of the very important developments which took place in the Danubian -way from the Near East. Apparently Britain was not affected. Britain -received the impulses which brought copper, bronze, and iron tools from -an original east Mediterranean homeland into Europe, almost at the ends -of their journeys. But by the same token, they had had time en route to -take on their characteristic European aspects. - -Some time ago, Sir Cyril Fox wrote a famous book called _The -Personality of Britain_, sub-titled �Its Influence on Inhabitant and -Invader in Prehistoric and Early Historic Times.� We have not gone -into the post-Roman early historic period here; there are still the -Anglo-Saxons and Normans to account for as well as the effects of -the Romans. But what I have tried to do was to begin the story of -how the personality of Britain was formed. The principles that Fox -used, in trying to balance cultural and environmental factors and -interrelationships would not be greatly different for other lands. - - - - -Summary - -[Illustration] - - -In the pages you have read so far, you have been brought through the -earliest 99 per cent of the story of man�s life on this planet. I have -left only 1 per cent of the story for the historians to tell. - - -THE DRAMA OF THE PAST - -Men first became men when evolution had carried them to a certain -point. This was the point where the eye-hand-brain co-ordination was -good enough so that tools could be made. When tools began to be made -according to sets of lasting habits, we know that men had appeared. -This happened over a half million years ago. The stage for the play -may have been as broad as all of Europe, Africa, and Asia. At least, -it seems unlikely that it was only one little region that saw the -beginning of the drama. - -Glaciers and different climates came and went, to change the settings. -But the play went on in the same first act for a very long time. The -men who were the players had simple roles. They had to feed themselves -and protect themselves as best they could. They did this by hunting, -catching, and finding food wherever they could, and by taking such -protection as caves, fire, and their simple tools would give them. -Before the first act was over, the last of the glaciers was melting -away, and the players had added the New World to their stage. If -we want a special name for the first act, we could call it _The -Food-Gatherers_. - -There were not many climaxes in the first act, so far as we can see. -But I think there may have been a few. Certainly the pace of the -first act accelerated with the swing from simple gathering to more -intensified collecting. The great cave art of France and Spain was -probably an expression of a climax. Even the ideas of burying the dead -and of the �Venus� figurines must also point to levels of human thought -and activity that were over and above pure food-getting. - - -THE SECOND ACT - -The second act began only about ten thousand years ago. A few of the -players started it by themselves near the center of the Old World part -of the stage, in the Near East. It began as a plant and animal act, but -it soon became much more complicated. - -But the players in this one part of the stage--in the Near East--were -not the only ones to start off on the second act by themselves. Other -players, possibly in several places in the Far East, and certainly in -the New World, also started second acts that began as plant and animal -acts, and then became complicated. We can call the whole second act -_The Food-Producers_. - - -THE FIRST GREAT CLIMAX OF THE SECOND ACT - -In the Near East, the first marked climax of the second act happened -in Mesopotamia and Egypt. The play and the players reached that great -climax that we call civilization. This seems to have come less than -five thousand years after the second act began. But it could never have -happened in the first act at all. - -There is another curious thing about the first act. Many of the players -didn�t know it was over and they kept on with their roles long after -the second act had begun. On the edges of the stage there are today -some players who are still going on with the first act. The Eskimos, -and the native Australians, and certain tribes in the Amazon jungle are -some of these players. They seem perfectly happy to keep on with the -first act. - -The second act moved from climax to climax. The civilizations of -Mesopotamia and Egypt were only the earliest of these climaxes. The -players to the west caught the spirit of the thing, and climaxes -followed there. So also did climaxes come in the Far Eastern and New -World portions of the stage. - -The greater part of the second act should really be described to you -by a historian. Although it was a very short act when compared to the -first one, the climaxes complicate it a great deal. I, a prehistorian, -have told you about only the first act, and the very beginning of the -second. - - -THE THIRD ACT - -Also, as a prehistorian I probably should not even mention the third -act--it began so recently. The third act is _The Industrialization_. -It is the one in which we ourselves are players. If the pace of the -second act was so much faster than that of the first, the pace of the -third act is terrific. The danger is that it may wear down the players -completely. - -What sort of climaxes will the third act have, and are we already in -one? You have seen by now that the acts of my play are given in terms -of modes or basic patterns of human economy--ways in which people -get food and protection and safety. The climaxes involve more than -human economy. Economics and technological factors may be part of the -climaxes, but they are not all. The climaxes may be revolutions in -their own way, intellectual and social revolutions if you like. - -If the third act follows the pattern of the second act, a climax should -come soon after the act begins. We may be due for one soon if we are -not already in it. Remember the terrific pace of this third act. - - -WHY BOTHER WITH PREHISTORY? - -Why do we bother about prehistory? The main reason is that we think it -may point to useful ideas for the present. We are in the troublesome -beginnings of the third act of the play. The beginnings of the second -act may have lessons for us and give depth to our thinking. I know -there are at least _some_ lessons, even in the present incomplete -state of our knowledge. The players who began the second act--that of -food-production--separately, in different parts of the world, were not -all of one �pure race� nor did they have �pure� cultural traditions. -Some apparently quite mixed Mediterraneans got off to the first start -on the second act and brought it to its first two climaxes as well. -Peoples of quite different physical type achieved the first climaxes in -China and in the New World. - -In our British example of how the late prehistory of Europe worked, we -listed a continuous series of �invasions� and �reverberations.� After -each of these came fusion. Even though the Channel protected Britain -from some of the extreme complications of the mixture and fusion of -continental Europe, you can see how silly it would be to refer to a -�pure� British race or a �pure� British culture. We speak of the United -States as a �melting pot.� But this is nothing new. Actually, Britain -and all the rest of the world have been �melting pots� at one time or -another. - -By the time the written records of Mesopotamia and Egypt begin to turn -up in number, the climaxes there are well under way. To understand the -beginnings of the climaxes, and the real beginnings of the second act -itself, we are thrown back on prehistoric archeology. And this is as -true for China, India, Middle America, and the Andes, as it is for the -Near East. - -There are lessons to be learned from all of man�s past, not simply -lessons of how to fight battles or win peace conferences, but of how -human society evolves from one stage to another. Many of these lessons -can only be looked for in the prehistoric past. So far, we have only -made a beginning. There is much still to do, and many gaps in the story -are yet to be filled. The prehistorian�s job is to find the evidence, -to fill the gaps, and to discover the lessons men have learned in the -past. As I see it, this is not only an exciting but a very practical -goal for which to strive. - - - - -List of Books - - -BOOKS OF GENERAL INTEREST - -(Chosen from a variety of the increasingly useful list of cheap -paperbound books.) - - Childe, V. Gordon - _What Happened in History._ 1954. Penguin. - _Man Makes Himself._ 1955. Mentor. - _The Prehistory of European Society._ 1958. Penguin. - - Dunn, L. C., and Dobzhansky, Th. - _Heredity, Race, and Society._ 1952. Mentor. - - Frankfort, Henri, Frankfort, H. A., Jacobsen, Thorkild, and Wilson, - John A. - _Before Philosophy._ 1954. Penguin. - - Simpson, George G. - _The Meaning of Evolution._ 1955. Mentor. - - Wheeler, Sir Mortimer - _Archaeology from the Earth._ 1956. Penguin. - - -GEOCHRONOLOGY AND THE ICE AGE - -(Two general books. Some Pleistocene geologists disagree with Zeuner�s -interpretation of the dating evidence, but their points of view appear -in professional journals, in articles too cumbersome to list here.) - - Flint, R. F. - _Glacial Geology and the Pleistocene Epoch._ 1947. John Wiley - and Sons. - - Zeuner, F. E. - _Dating the Past._ 1952 (3rd ed.). Methuen and Co. - - -FOSSIL MEN AND RACE - -(The points of view of physical anthropologists and human -paleontologists are changing very quickly. Two of the different points -of view are listed here.) - - Clark, W. E. Le Gros - _History of the Primates._ 1956 (5th ed.). British Museum - (Natural History). (Also in Phoenix edition, 1957.) - - Howells, W. W. - _Mankind So Far._ 1944. Doubleday, Doran. - - -GENERAL ANTHROPOLOGY - -(These are standard texts not absolutely up to date in every detail, or -interpretative essays concerned with cultural change through time as -well as in space.) - - Kroeber, A. L. - _Anthropology._ 1948. Harcourt, Brace. - - Linton, Ralph - _The Tree of Culture._ 1955. Alfred A. Knopf, Inc. - - Redfield, Robert - _The Primitive World and Its Transformations._ 1953. Cornell - University Press. - - Steward, Julian H. - _Theory of Culture Change._ 1955. University of Illinois Press. - - White, Leslie - _The Science of Culture._ 1949. Farrar, Strauss. - - -GENERAL PREHISTORY - -(A sampling of the more useful and current standard works in English.) - - Childe, V. Gordon - _The Dawn of European Civilization._ 1957. Kegan Paul, Trench, - Trubner. - _Prehistoric Migrations in Europe._ 1950. Instituttet for - Sammenlignende Kulturforskning. - - Clark, Grahame - _Archaeology and Society._ 1957. Harvard University Press. - - Clark, J. G. D. - _Prehistoric Europe: The Economic Basis._ 1952. Methuen and Co. - - Garrod, D. A. E. - _Environment, Tools, and Man._ 1946. Cambridge University - Press. - - Movius, Hallam L., Jr. - �Old World Prehistory: Paleolithic� in _Anthropology Today_. - Kroeber, A. L., ed. 1953. University of Chicago Press. - - Oakley, Kenneth P. - _Man the Tool-Maker._ 1956. British Museum (Natural History). - (Also in Phoenix edition, 1957.) - - Piggott, Stuart - _British Prehistory._ 1949. Oxford University Press. - - Pittioni, Richard - _Die Urgeschichtlichen Grundlagen der Europ�ischen Kultur._ - 1949. Deuticke. (A single book which does attempt to cover the - whole range of European prehistory to ca. 1 A.D.) - - -THE NEAR EAST - - Adams, Robert M. - �Developmental Stages in Ancient Mesopotamia,� _in_ Steward, - Julian, _et al_, _Irrigation Civilizations: A Comparative - Study_. 1955. Pan American Union. - - Braidwood, Robert J. - _The Near East and the Foundations for Civilization._ 1952. - University of Oregon. - - Childe, V. Gordon - _New Light on the Most Ancient East._ 1952. Oriental Dept., - Routledge and Kegan Paul. - - Frankfort, Henri - _The Birth of Civilization in the Near East._ 1951. University - of Indiana Press. (Also in Anchor edition, 1956.) - - Pallis, Svend A. - _The Antiquity of Iraq._ 1956. Munksgaard. - - Wilson, John A. - _The Burden of Egypt._ 1951. University of Chicago Press. (Also - in Phoenix edition, called _The Culture of Ancient Egypt_, - 1956.) - - -HOW DIGGING IS DONE - - Braidwood, Linda - _Digging beyond the Tigris._ 1953. Schuman, New York. - - Wheeler, Sir Mortimer - _Archaeology from the Earth._ 1954. Oxford, London. - - - - -Index - - - Abbevillian, 48; - core-biface tool, 44, 48 - - Acheulean, 48, 60 - - Acheuleo-Levalloisian, 63 - - Acheuleo-Mousterian, 63 - - Adams, R. M., 106 - - Adzes, 45 - - Africa, east, 67, 89; - north, 70, 89; - south, 22, 25, 34, 40, 67 - - Agriculture, incipient, in England, 140; - in Near East, 123 - - Ain Hanech, 48 - - Amber, taken from Baltic to Greece, 167 - - American Indians, 90, 142 - - Anatolia, used as route to Europe, 138 - - Animals, in caves, 54, 64; - in cave art, 85 - - Antevs, Ernst, 19 - - Anyathian, 47 - - Archeological interpretation, 8 - - Archeology, defined, 8 - - Architecture, at Jarmo, 128; - at Jericho, 133 - - Arrow, points, 94; - shaft straightener, 83 - - Art, in caves, 84; - East Spanish, 85; - figurines, 84; - Franco-Cantabrian, 84, 85; - movable (engravings, modeling, scratchings), 83; - painting, 83; - sculpture, 83 - - Asia, western, 67 - - Assemblage, defined, 13, 14; - European, 94; - Jarmo, 129; - Maglemosian, 94; - Natufian, 113 - - Aterian, industry, 67; - point, 89 - - Australopithecinae, 24 - - Australopithecine, 25, 26 - - Awls, 77 - - Axes, 62, 94 - - Ax-heads, 15 - - Azilian, 97 - - Aztecs, 145 - - - Baghouz, 152 - - Bakun, 134 - - Baltic sea, 93 - - Banana, 107 - - Barley, wild, 108 - - Barrow, 141 - - Battle-axe folk, 164; - assemblage, 164 - - Beads, 80; - bone, 114 - - Beaker folk, 164; - assemblage, 164-165 - - Bear, in cave art, 85; - cult, 68 - - Belgium, 94 - - Belt cave, 126 - - Bering Strait, used as route to New World, 98 - - Bison, in cave art, 85 - - Blade, awl, 77; - backed, 75; - blade-core, 71; - end-scraper, 77; - stone, defined, 71; - strangulated (notched), 76; - tanged point, 76; - tools, 71, 75-80, 90; - tool tradition, 70 - - Boar, wild, in cave art, 85 - - Bogs, source of archeological materials, 94 - - Bolas, 54 - - Bordes, Fran�ois, 62 - - Borer, 77 - - Boskop skull, 34 - - Boyd, William C., 35 - - Bracelets, 118 - - Brain, development of, 24 - - Breadfruit, 107 - - Breasted, James H., 107 - - Brick, at Jericho, 133 - - Britain, 94; - late prehistory, 163-175; - invaders, 173 - - Broch, 172 - - Buffalo, in China, 54; - killed by stampede, 86 - - Burials, 66, 86; - in �henges,� 164; - in urns, 168 - - Burins, 75 - - Burma, 90 - - Byblos, 134 - - - Camel, 54 - - Cannibalism, 55 - - Cattle, wild, 85, 112; - in cave art, 85; - domesticated, 15; - at Skara Brae, 142 - - Caucasoids, 34 - - Cave men, 29 - - Caves, 62; - art in, 84 - - Celts, 170 - - Chariot, 160 - - Chicken, domestication of, 107 - - Chiefs, in food-gathering groups, 68 - - Childe, V. Gordon, 8 - - China, 136 - - Choukoutien, 28, 35 - - Choukoutienian, 47 - - Civilization, beginnings, 144, 149, 157; - meaning of, 144 - - Clactonian, 45, 47 - - Clay, used in modeling, 128; - baked, used for tools, 153 - - Club-heads, 82, 94 - - Colonization, in America, 142; - in Europe, 142 - - Combe Capelle, 30 - - Combe Capelle-Br�nn group, 34 - - Commont, Victor, 51 - - Coon, Carlton S., 73 - - Copper, 134 - - Corn, in America, 145 - - Corrals for cattle, 140 - - �Cradle of mankind,� 136 - - Cremation, 167 - - Crete, 162 - - Cro-Magnon, 30, 34 - - Cultivation, incipient, 105, 109, 111 - - Culture, change, 99; - characteristics, defined, 38, 49; - prehistoric, 39 - - - Danube Valley, used as route from Asia, 138 - - Dates, 153 - - Deer, 54, 96 - - Dog, domesticated, 96 - - Domestication, of animals, 100, 105, 107; - of plants, 100 - - �Dragon teeth� fossils in China, 28 - - Drill, 77 - - Dubois, Eugene, 26 - - - Early Dynastic Period, Mesopotamia, 147 - - East Spanish art, 72, 85 - - Egypt, 70, 126 - - Ehringsdorf, 31 - - Elephant, 54 - - Emiliani, Cesare, 18 - - Emiran flake point, 73 - - England, 163-168; - prehistoric, 19, 40; - farmers in, 140 - - Eoanthropus dawsoni, 29 - - Eoliths, 41 - - Erich, 152 - - Eridu, 152 - - Euphrates River, floods in, 148 - - Europe, cave dwellings, 58; - at end of Ice Age, 93; - early farmers, 140; - glaciers in, 40; - huts in, 86; - routes into, 137-140; - spread of food-production to, 136 - - - Far East, 69, 90 - - Farmers, 103 - - Fauresmith industry, 67 - - Fayum, 135; - radiocarbon date, 146 - - �Fertile Crescent,� 107, 146 - - Figurines, �Venus,� 84; - at Jarmo, 128; - at Ubaid, 153 - - Fire, used by Peking man, 54 - - First Dynasty, Egypt, 147 - - Fish-hooks, 80, 94 - - Fishing, 80; - by food-producers, 122 - - Fish-lines, 80 - - Fish spears, 94 - - Flint industry, 127 - - Font�chevade, 32, 56, 58 - - Food-collecting, 104, 121; - end of, 104 - - Food-gatherers, 53, 176 - - Food-gathering, 99, 104; - in Old World, 104; - stages of, 104 - - Food-producers, 176 - - Food-producing economy, 122; - in America, 145; - in Asia, 105 - - Food-producing revolution, 99, 105; - causes of, 101; - preconditions for, 100 - - Food-production, beginnings of, 99; - carried to Europe, 110 - - Food-vessel folk, 164 - - �Forest folk,� 97, 98, 104, 110 - - Fox, Sir Cyril, 174 - - France, caves in, 56 - - - Galley Hill (fossil type), 29 - - Garrod, D. A., 73 - - Gazelle, 114 - - Germany, 94 - - Ghassul, 156 - - Glaciers, 18, 30; - destruction by, 40 - - Goat, wild, 108; - domesticated, 128 - - Grain, first planted, 20 - - Graves, passage, 141; - gallery, 141 - - Greece, civilization in, 163; - as route to western Europe, 138; - towns in, 162 - - Grimaldi skeletons, 34 - - - Hackberry seeds used as food, 55 - - Halaf, 151; - assemblage, 151 - - Hallstatt, tradition, 169 - - Hand, development of, 24, 25 - - Hand adzes, 46 - - Hand axes, 44 - - Harpoons, antler, 83, 94; - bone, 82, 94 - - Hassuna, 131; - assemblage, 131, 132 - - Heidelberg, fossil type, 28 - - Hill-forts, in England, 171; - in Scotland, 172 - - Hilly flanks of Near East, 107, 108, 125, 131, 146, 147 - - History, beginning of, 7, 17 - - Hoes, 112 - - Holland, 164 - - Homo sapiens, 32 - - Hooton, E. A., 34 - - Horse, 112; - wild, in cave art, 85; - in China, 54 - - Hotu cave, 126 - - Houses, 122; - at Jarmo, 128; - at Halaf, 151 - - Howe, Bruce, 116 - - Howell, F. Clark, 30 - - Hunting, 93 - - - Ice Age, in Asia, 99; - beginning of, 18; - glaciers in, 41; - last glaciation, 93 - - Incas, 145 - - India, 90, 136 - - Industrialization, 178 - - Industry, blade-tool, 88; - defined, 58; - ground stone, 94 - - Internationalism, 162 - - Iran, 107, 147 - - Iraq, 107, 124, 127, 136, 147 - - Iron, introduction of, 170 - - Irrigation, 123, 149, 155 - - Italy, 138 - - - Jacobsen, T. J., 157 - - Jarmo, 109, 126, 128, 130; - assemblage, 129 - - Java, 23, 29 - - Java man, 26, 27, 29 - - Jefferson, Thomas, 11 - - Jericho, 119, 133 - - Judaidah, 134 - - - Kafuan, 48 - - Kanam, 23, 36 - - Karim Shahir, 116-119, 124; - assemblage, 116, 117 - - Keith, Sir Arthur, 33 - - Kelley, Harper, 51 - - Kharga, 126 - - Khartoum, 136 - - Knives, 80 - - Krogman, W. M., 3, 25 - - - Lamps, 85 - - Land bridges in Mediterranean, 19 - - La T�ne phase, 170 - - Laurel leaf point, 78, 89 - - Leakey, L. S. B., 40 - - Le Moustier, 57 - - Levalloisian, 47, 61, 62 - - Levalloiso-Mousterian, 47, 63 - - Little Woodbury, 170 - - - Magic, used by hunters, 123 - - Maglemosian, assemblage, 94, 95; - folk, 98 - - Makapan, 40 - - Mammoth, 93; - in cave art, 85 - - �Man-apes,� 26 - - Mango, 107 - - Mankind, age, 17 - - Maringer, J., 45 - - Markets, 155 - - Marston, A. T., 11 - - Mathiassen, T., 97 - - McCown, T. D., 33 - - Meganthropus, 26, 27, 36 - - Men, defined, 25; - modern, 32 - - Merimde, 135 - - Mersin, 133 - - Metal-workers, 160, 163, 167, 172 - - Micoquian, 48, 60 - - Microliths, 87; - at Jarmo, 130; - �lunates,� 87; - trapezoids, 87; - triangles, 87 - - Minerals used as coloring matter, 66 - - Mine-shafts, 140 - - M�lefaat, 126, 127 - - Mongoloids, 29, 90 - - Mortars, 114, 118, 127 - - Mounds, how formed, 12 - - Mount Carmel, 11, 33, 52, 59, 64, 69, 113, 114 - - �Mousterian man,� 64 - - �Mousterian� tools, 61, 62; - of Acheulean tradition, 62 - - Movius, H. L., 47 - - - Natufian, animals in, 114; - assemblage, 113, 114, 115; - burials, 114; - date of, 113 - - Neanderthal man, 29, 30, 31, 56 - - Near East, beginnings of civilization in, 20, 144; - cave sites, 58; - climate in Ice Age, 99; - �Fertile Crescent,� 107, 146; - food-production in, 99; - Natufian assemblage in, 113-115; - stone tools, 114 - - Needles, 80 - - Negroid, 34 - - New World, 90 - - Nile River valley, 102, 134; - floods in, 148 - - Nuclear area, 106, 110; - in Near East, 107 - - - Obsidian, used for blade tools, 71; - at Jarmo, 130 - - Ochre, red, with burials, 86 - - Oldowan, 48 - - Old World, 67, 70, 90; - continental phases in, 18 - - Olorgesailie, 40, 51 - - Ostrich, in China, 54 - - Ovens, 128 - - Oxygen isotopes, 18 - - - Paintings in caves, 83 - - Paleoanthropic man, 50 - - Palestine, burials, 56; - cave sites, 52; - types of man, 69 - - Parpallo, 89 - - Patjitanian, 45, 47 - - Pebble tools, 42 - - Peking cave, 54; - animals in, 54 - - Peking man, 27, 28, 29, 54, 58 - - Pendants, 80; - bone, 114 - - Pestle, 114 - - Peterborough, 141; - assemblage, 141 - - Pictographic signs, 158 - - Pig, wild, 108 - - �Piltdown man,� 29 - - Pins, 80 - - Pithecanthropus, 26, 27, 30, 36 - - Pleistocene, 18, 25 - - Plows developed, 123 - - Points, arrow, 76; - laurel leaf, 78; - shouldered, 78, 79; - split-based bone, 80, 82; - tanged, 76; - willow leaf, 78 - - Potatoes, in America, 145 - - Pottery, 122, 130, 156; - decorated, 142; - painted, 131, 151, 152; - Susa style, 156; - in tombs, 141 - - Prehistory, defined, 7; - range of, 18 - - Pre-neanderthaloids, 30, 31, 37 - - Pre-Solutrean point, 89 - - Pre-Stellenbosch, 48 - - Proto-Literate assemblage, 157-160 - - - Race, 35; - biological, 36; - �pure,� 16 - - Radioactivity, 9, 10 - - Radioactive carbon dates, 18, 92, 120, 130, 135, 156 - - Redfield, Robert, 38, 49 - - Reed, C. A., 128 - - Reindeer, 94 - - Rhinoceros, 93; - in cave art, 85 - - Rhodesian man, 32 - - Riss glaciation, 58 - - Rock-shelters, 58; - art in, 85 - - - Saccopastore, 31 - - Sahara Desert, 34, 102 - - Samarra, 152; - pottery, 131, 152 - - Sangoan industry, 67 - - Sauer, Carl, 136 - - Sbaikian point, 89 - - Schliemann, H., 11, 12 - - Scotland, 171 - - Scraper, flake, 79; - end-scraper on blade, 77, 78; - keel-shaped, 79, 80, 81 - - Sculpture in caves, 83 - - Sebilian III, 126 - - Shaheinab, 135 - - Sheep, wild, 108; - at Skara Brae, 142; - in China, 54 - - Shellfish, 142 - - Ship, Ubaidian, 153 - - Sialk, 126, 134; - assemblage, 134 - - Siberia, 88; - pathway to New World, 98 - - Sickle, 112, 153; - blade, 113, 130 - - Silo, 122 - - Sinanthropus, 27, 30, 35 - - Skara Brae, 142 - - Snails used as food, 128 - - Soan, 47 - - Solecki, R., 116 - - Solo (fossil type), 29, 32 - - Solutrean industry, 77 - - Spear, shaft, 78; - thrower, 82, 83 - - Speech, development of organs of, 25 - - Squash, in America, 145 - - Steinheim fossil skull, 28 - - Stillbay industry, 67 - - Stonehenge, 166 - - Stratification, in caves, 12, 57; - in sites, 12 - - Swanscombe (fossil type), 11, 28 - - Syria, 107 - - - Tabun, 60, 71 - - Tardenoisian, 97 - - Taro, 107 - - Tasa, 135 - - Tayacian, 47, 59 - - Teeth, pierced, in beads and pendants, 114 - - Temples, 123, 155 - - Tepe Gawra, 156 - - Ternafine, 29 - - Teshik Tash, 69 - - Textiles, 122 - - Thong-stropper, 80 - - Tigris River, floods in, 148 - - Toggle, 80 - - Tomatoes, in America, 145 - - Tombs, megalithic, 141 - - Tool-making, 42, 49 - - Tool-preparation traditions, 65 - - Tools, 62; - antler, 80; - blade, 70, 71, 75; - bone, 66; - chopper, 47; - core-biface, 43, 48, 60, 61; - flake, 44, 47, 51, 60, 64; - flint, 80, 127; - ground stone, 68, 127; - handles, 94; - pebble, 42, 43, 48, 53; - use of, 24 - - Touf (mud wall), 128 - - Toynbee, A. J., 101 - - Trade, 130, 155, 162 - - Traders, 167 - - Traditions, 15; - blade tool, 70; - definition of, 51; - interpretation of, 49; - tool-making, 42, 48; - chopper-tool, 47; - chopper-chopping tool, 45; - core-biface, 43, 48; - flake, 44, 47; - pebble tool, 42, 48 - - Tool-making, prehistory of, 42 - - Turkey, 107, 108 - - - Ubaid, 153; - assemblage, 153-155 - - Urnfields, 168, 169 - - - Village-farming community era, 105, 119 - - - Wad B, 72 - - Wadjak, 34 - - Warka phase, 156; - assemblage, 156 - - Washburn, Sherwood L., 36 - - Water buffalo, domestication of, 107 - - Weidenreich, F., 29, 34 - - Wessex, 166, 167 - - Wheat, wild, 108; - partially domesticated, 127 - - Willow leaf point, 78 - - Windmill Hill, 138; - assemblage, 138, 140 - - Witch doctors, 68 - - Wool, 112; - in garments, 167 - - Writing, 158; - cuneiform, 158 - - W�rm I glaciation, 58 - - - Zebu cattle, domestication of, 107 - - Zeuner, F. E., 73 - - - - - * * * * * * - - - - -Transcriber�s note: - -Punctuation, hyphenation, and spelling were made consistent when a -predominant preference was found in this book; otherwise they were not -changed. - -Simple typographical errors were corrected; occasional unbalanced -quotation marks retained. - -Ambiguous hyphens at the ends of lines were retained. - -Index not checked for proper alphabetization or correct page references. - -In the original book, chapter headings were accompanied by -illustrations, sometimes above, sometimes below, and sometimes -adjacent. In this eBook those ilustrations always appear below the -headings. - - - -***END OF THE PROJECT GUTENBERG EBOOK PREHISTORIC MEN*** - - -******* This file should be named 52664-0.txt or 52664-0.zip ******* - - -This and all associated files of various formats will be found in: -http://www.gutenberg.org/dirs/5/2/6/6/52664 - - -Updated editions will replace the previous one--the old editions will -be renamed. - -Creating the works from print editions not protected by U.S. copyright -law means that no one owns a United States copyright in these works, -so the Foundation (and you!) can copy and distribute it in the United -States without permission and without paying copyright -royalties. Special rules, set forth in the General Terms of Use part -of this license, apply to copying and distributing Project -Gutenberg-tm electronic works to protect the PROJECT GUTENBERG-tm -concept and trademark. Project Gutenberg is a registered trademark, -and may not be used if you charge for the eBooks, unless you receive -specific permission. If you do not charge anything for copies of this -eBook, complying with the rules is very easy. You may use this eBook -for nearly any purpose such as creation of derivative works, reports, -performances and research. They may be modified and printed and given -away--you may do practically ANYTHING in the United States with eBooks -not protected by U.S. copyright law. Redistribution is subject to the -trademark license, especially commercial redistribution. - -START: FULL LICENSE - -THE FULL PROJECT GUTENBERG LICENSE -PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK - -To protect the Project Gutenberg-tm mission of promoting the free -distribution of electronic works, by using or distributing this work -(or any other work associated in any way with the phrase "Project -Gutenberg"), you agree to comply with all the terms of the Full -Project Gutenberg-tm License available with this file or online at -www.gutenberg.org/license. - -Section 1. General Terms of Use and Redistributing Project -Gutenberg-tm electronic works - -1.A. By reading or using any part of this Project Gutenberg-tm -electronic work, you indicate that you have read, understand, agree to -and accept all the terms of this license and intellectual property -(trademark/copyright) agreement. If you do not agree to abide by all -the terms of this agreement, you must cease using and return or -destroy all copies of Project Gutenberg-tm electronic works in your -possession. If you paid a fee for obtaining a copy of or access to a -Project Gutenberg-tm electronic work and you do not agree to be bound -by the terms of this agreement, you may obtain a refund from the -person or entity to whom you paid the fee as set forth in paragraph -1.E.8. - -1.B. "Project Gutenberg" is a registered trademark. It may only be -used on or associated in any way with an electronic work by people who -agree to be bound by the terms of this agreement. There are a few -things that you can do with most Project Gutenberg-tm electronic works -even without complying with the full terms of this agreement. See -paragraph 1.C below. There are a lot of things you can do with Project -Gutenberg-tm electronic works if you follow the terms of this -agreement and help preserve free future access to Project Gutenberg-tm -electronic works. See paragraph 1.E below. - -1.C. The Project Gutenberg Literary Archive Foundation ("the -Foundation" or PGLAF), owns a compilation copyright in the collection -of Project Gutenberg-tm electronic works. Nearly all the individual -works in the collection are in the public domain in the United -States. If an individual work is unprotected by copyright law in the -United States and you are located in the United States, we do not -claim a right to prevent you from copying, distributing, performing, -displaying or creating derivative works based on the work as long as -all references to Project Gutenberg are removed. Of course, we hope -that you will support the Project Gutenberg-tm mission of promoting -free access to electronic works by freely sharing Project Gutenberg-tm -works in compliance with the terms of this agreement for keeping the -Project Gutenberg-tm name associated with the work. You can easily -comply with the terms of this agreement by keeping this work in the -same format with its attached full Project Gutenberg-tm License when -you share it without charge with others. - -1.D. The copyright laws of the place where you are located also govern -what you can do with this work. Copyright laws in most countries are -in a constant state of change. If you are outside the United States, -check the laws of your country in addition to the terms of this -agreement before downloading, copying, displaying, performing, -distributing or creating derivative works based on this work or any -other Project Gutenberg-tm work. The Foundation makes no -representations concerning the copyright status of any work in any -country outside the United States. - -1.E. Unless you have removed all references to Project Gutenberg: - -1.E.1. The following sentence, with active links to, or other -immediate access to, the full Project Gutenberg-tm License must appear -prominently whenever any copy of a Project Gutenberg-tm work (any work -on which the phrase "Project Gutenberg" appears, or with which the -phrase "Project Gutenberg" is associated) is accessed, displayed, -performed, viewed, copied or distributed: - - 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 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 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. - -1.E.2. If an individual Project Gutenberg-tm electronic work is -derived from texts not protected by U.S. copyright law (does not -contain a notice indicating that it is posted with permission of the -copyright holder), the work can be copied and distributed to anyone in -the United States without paying any fees or charges. If you are -redistributing or providing access to a work with the phrase "Project -Gutenberg" associated with or appearing on the work, you must comply -either with the requirements of paragraphs 1.E.1 through 1.E.7 or -obtain permission for the use of the work and the Project Gutenberg-tm -trademark as set forth in paragraphs 1.E.8 or 1.E.9. - -1.E.3. If an individual Project Gutenberg-tm electronic work is posted -with the permission of the copyright holder, your use and distribution -must comply with both paragraphs 1.E.1 through 1.E.7 and any -additional terms imposed by the copyright holder. Additional terms -will be linked to the Project Gutenberg-tm License for all works -posted with the permission of the copyright holder found at the -beginning of this work. - -1.E.4. Do not unlink or detach or remove the full Project Gutenberg-tm -License terms from this work, or any files containing a part of this -work or any other work associated with Project Gutenberg-tm. - -1.E.5. Do not copy, display, perform, distribute or redistribute this -electronic work, or any part of this electronic work, without -prominently displaying the sentence set forth in paragraph 1.E.1 with -active links or immediate access to the full terms of the Project -Gutenberg-tm License. - -1.E.6. You may convert to and distribute this work in any binary, -compressed, marked up, nonproprietary or proprietary form, including -any word processing or hypertext form. However, if you provide access -to or distribute copies of a Project Gutenberg-tm work in a format -other than "Plain Vanilla ASCII" or other format used in the official -version posted on the official Project Gutenberg-tm web site -(www.gutenberg.org), you must, at no additional cost, fee or expense -to the user, provide a copy, a means of exporting a copy, or a means -of obtaining a copy upon request, of the work in its original "Plain -Vanilla ASCII" or other form. Any alternate format must include the -full Project Gutenberg-tm License as specified in paragraph 1.E.1. - -1.E.7. Do not charge a fee for access to, viewing, displaying, -performing, copying or distributing any Project Gutenberg-tm works -unless you comply with paragraph 1.E.8 or 1.E.9. - -1.E.8. You may charge a reasonable fee for copies of or providing -access to or distributing Project Gutenberg-tm electronic works -provided that - -* You pay a royalty fee of 20% of the gross profits you derive from - the use of Project Gutenberg-tm works calculated using the method - you already use to calculate your applicable taxes. The fee is owed - to the owner of the Project Gutenberg-tm trademark, but he has - agreed to donate royalties under this paragraph to the Project - Gutenberg Literary Archive Foundation. Royalty payments must be paid - within 60 days following each date on which you prepare (or are - legally required to prepare) your periodic tax returns. Royalty - payments should be clearly marked as such and sent to the Project - Gutenberg Literary Archive Foundation at the address specified in - Section 4, "Information about donations to the Project Gutenberg - Literary Archive Foundation." - -* You provide a full refund of any money paid by a user who notifies - you in writing (or by e-mail) within 30 days of receipt that s/he - does not agree to the terms of the full Project Gutenberg-tm - License. You must require such a user to return or destroy all - copies of the works possessed in a physical medium and discontinue - all use of and all access to other copies of Project Gutenberg-tm - works. - -* You provide, in accordance with paragraph 1.F.3, a full refund of - any money paid for a work or a replacement copy, if a defect in the - electronic work is discovered and reported to you within 90 days of - receipt of the work. - -* You comply with all other terms of this agreement for free - distribution of Project Gutenberg-tm works. - -1.E.9. If you wish to charge a fee or distribute a Project -Gutenberg-tm electronic work or group of works on different terms than -are set forth in this agreement, you must obtain permission in writing -from both the Project Gutenberg Literary Archive Foundation and The -Project Gutenberg Trademark LLC, the owner of the Project Gutenberg-tm -trademark. Contact the Foundation as set forth in Section 3 below. - -1.F. - -1.F.1. Project Gutenberg volunteers and employees expend considerable -effort to identify, do copyright research on, transcribe and proofread -works not protected by U.S. copyright law in creating the Project -Gutenberg-tm collection. Despite these efforts, Project Gutenberg-tm -electronic works, and the medium on which they may be stored, may -contain "Defects," such as, but not limited to, incomplete, inaccurate -or corrupt data, transcription errors, a copyright or other -intellectual property infringement, a defective or damaged disk or -other medium, a computer virus, or computer codes that damage or -cannot be read by your equipment. - -1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right -of Replacement or Refund" described in paragraph 1.F.3, the Project -Gutenberg Literary Archive Foundation, the owner of the Project -Gutenberg-tm trademark, and any other party distributing a Project -Gutenberg-tm electronic work under this agreement, disclaim all -liability to you for damages, costs and expenses, including legal -fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT -LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE -PROVIDED IN PARAGRAPH 1.F.3. YOU AGREE THAT THE FOUNDATION, THE -TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE -LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR -INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH -DAMAGE. - -1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a -defect in this electronic work within 90 days of receiving it, you can -receive a refund of the money (if any) you paid for it by sending a -written explanation to the person you received the work from. If you -received the work on a physical medium, you must return the medium -with your written explanation. The person or entity that provided you -with the defective work may elect to provide a replacement copy in -lieu of a refund. If you received the work electronically, the person -or entity providing it to you may choose to give you a second -opportunity to receive the work electronically in lieu of a refund. If -the second copy is also defective, you may demand a refund in writing -without further opportunities to fix the problem. - -1.F.4. Except for the limited right of replacement or refund set forth -in paragraph 1.F.3, this work is provided to you 'AS-IS', WITH NO -OTHER WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PURPOSE. - -1.F.5. Some states do not allow disclaimers of certain implied -warranties or the exclusion or limitation of certain types of -damages. If any disclaimer or limitation set forth in this agreement -violates the law of the state applicable to this agreement, the -agreement shall be interpreted to make the maximum disclaimer or -limitation permitted by the applicable state law. The invalidity or -unenforceability of any provision of this agreement shall not void the -remaining provisions. - -1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the -trademark owner, any agent or employee of the Foundation, anyone -providing copies of Project Gutenberg-tm electronic works in -accordance with this agreement, and any volunteers associated with the -production, promotion and distribution of Project Gutenberg-tm -electronic works, harmless from all liability, costs and expenses, -including legal fees, that arise directly or indirectly from any of -the following which you do or cause to occur: (a) distribution of this -or any Project Gutenberg-tm work, (b) alteration, modification, or -additions or deletions to any Project Gutenberg-tm work, and (c) any -Defect you cause. - -Section 2. Information about the Mission of Project Gutenberg-tm - -Project Gutenberg-tm is synonymous with the free distribution of -electronic works in formats readable by the widest variety of -computers including obsolete, old, middle-aged and new computers. It -exists because of the efforts of hundreds of volunteers and donations -from people in all walks of life. - -Volunteers and financial support to provide volunteers with the -assistance they need are critical to reaching Project Gutenberg-tm's -goals and ensuring that the Project Gutenberg-tm collection will -remain freely available for generations to come. In 2001, the Project -Gutenberg Literary Archive Foundation was created to provide a secure -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 - -Section 3. Information about the Project Gutenberg Literary -Archive Foundation - -The Project Gutenberg Literary Archive Foundation is a non profit -501(c)(3) educational corporation organized under the laws of the -state of Mississippi and granted tax exempt status by the Internal -Revenue Service. The Foundation's EIN or federal tax identification -number is 64-6221541. Contributions to the Project Gutenberg Literary -Archive Foundation are tax deductible to the full extent permitted by -U.S. federal laws and your state's laws. - -The Foundation's principal office is in Fairbanks, Alaska, with the -mailing address: PO Box 750175, Fairbanks, AK 99775, but its -volunteers and employees are scattered throughout numerous -locations. Its business office is located at 809 North 1500 West, Salt -Lake City, UT 84116, (801) 596-1887. Email contact links and up to -date contact information can be found at the Foundation's web site and -official page at www.gutenberg.org/contact - -For additional contact information: - - Dr. Gregory B. Newby - Chief Executive and Director - gbnewby@pglaf.org - -Section 4. Information about Donations to the Project Gutenberg -Literary Archive Foundation - -Project Gutenberg-tm depends upon and cannot survive without wide -spread public support and donations to carry out its mission of -increasing the number of public domain and licensed works that can be -freely distributed in machine readable form accessible by the widest -array of equipment including outdated equipment. Many small donations -($1 to $5,000) are particularly important to maintaining tax exempt -status with the IRS. - -The Foundation is committed to complying with the laws regulating -charities and charitable donations in all 50 states of the United -States. Compliance requirements are not uniform and it takes a -considerable effort, much paperwork and many fees to meet and keep up -with these requirements. We do not solicit donations in locations -where we have not received written confirmation of compliance. To SEND -DONATIONS or determine the status of compliance for any particular -state visit www.gutenberg.org/donate - -While we cannot and do not solicit contributions from states where we -have not met the solicitation requirements, we know of no prohibition -against accepting unsolicited donations from donors in such states who -approach us with offers to donate. - -International donations are gratefully accepted, but we cannot make -any statements concerning tax treatment of donations received from -outside the United States. U.S. laws alone swamp our small staff. - -Please check the Project Gutenberg Web pages for current donation -methods and addresses. Donations are accepted in a number of other -ways including checks, online payments and credit card donations. To -donate, please visit: www.gutenberg.org/donate - -Section 5. General Information About Project Gutenberg-tm electronic works. - -Professor Michael S. Hart was the originator of the Project -Gutenberg-tm concept of a library of electronic works that could be -freely shared with anyone. For forty years, he produced and -distributed Project Gutenberg-tm eBooks with only a loose network of -volunteer support. - -Project Gutenberg-tm eBooks are often created from several printed -editions, all of which are confirmed as not protected by copyright in -the U.S. unless a copyright notice is included. Thus, we do not -necessarily keep eBooks in compliance with any particular paper -edition. - -Most people start at our Web site which has the main PG search -facility: www.gutenberg.org - -This Web site includes information about Project Gutenberg-tm, -including how to make donations to the Project Gutenberg Literary -Archive Foundation, how to help produce our new eBooks, and how to -subscribe to our email newsletter to hear about new eBooks. diff --git a/ciphers/XOR_cipher.py b/ciphers/XOR_cipher.py deleted file mode 100644 index 727fac3b0703..000000000000 --- a/ciphers/XOR_cipher.py +++ /dev/null @@ -1,209 +0,0 @@ -""" - 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 - """ - - #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 - """ - - # precondition - assert (isinstance(key,int) and isinstance(content,str)) - - key = key or self.__key or 1 - - # make sure key can be any size - while (key > 255): - key -= 255 - - # This will be returned - ans = [] - - for ch in content: - ans.append(chr(ord(ch) ^ key)) - - return ans - - 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)) - - key = key or self.__key or 1 - - # make sure key can be any size - while (key > 255): - key -= 255 - - # This will be returned - ans = [] - - for ch in content: - ans.append(chr(ord(ch) ^ key)) - - return ans - - - 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)) - - key = key or self.__key or 1 - - # make sure key can be any size - while (key > 255): - key -= 255 - - # This will be returned - ans = "" - - for ch in content: - ans += chr(ord(ch) ^ key) - - return ans - - 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 - - # make sure key can be any size - while (key > 255): - key -= 255 - - # This will be returned - ans = "" - - for ch in content: - ans += chr(ord(ch) ^ key) - - return ans - - - 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 - """ - - #precondition - assert (isinstance(file,str) and isinstance(key,int)) - - try: - with open(file,"r") as fin: - with open("encrypt.out","w+") as fout: - - # actual encrypt-process - for line in fin: - fout.write(self.encrypt_string(line,key)) - - except: - return False - - return True - - - 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 - """ - - #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)) - - except: - return False - - return True - - - - -# Tests -# crypt = XORCipher() -# key = 67 - -# # test enrcypt -# print crypt.encrypt("hallo welt",key) -# # test decrypt -# print crypt.decrypt(crypt.encrypt("hallo welt",key), key) - -# # test encrypt_string -# print crypt.encrypt_string("hallo welt",key) - -# # test decrypt_string -# print crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key) - -# if (crypt.encrypt_file("test.txt",key)): -# print "encrypt successful" -# else: -# print "encrypt unsuccessful" - -# if (crypt.decrypt_file("encrypt.out",key)): -# print "decrypt successful" -# else: -# print "decrypt unsuccessful" \ No newline at end of file diff --git a/ciphers/brute-force_caesar_cipher.py b/ciphers/brute-force_caesar_cipher.py deleted file mode 100644 index 8582249c8d27..000000000000 --- a/ciphers/brute-force_caesar_cipher.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import print_function -def decrypt(message): - """ - >>> decrypt('TMDETUX PMDVU') - Decryption using Key #0: TMDETUX PMDVU - Decryption using Key #1: SLCDSTW OLCUT - Decryption using Key #2: RKBCRSV NKBTS - Decryption using Key #3: QJABQRU MJASR - Decryption using Key #4: PIZAPQT LIZRQ - Decryption using Key #5: OHYZOPS KHYQP - Decryption using Key #6: NGXYNOR JGXPO - Decryption using Key #7: MFWXMNQ IFWON - Decryption using Key #8: LEVWLMP HEVNM - Decryption using Key #9: KDUVKLO GDUML - Decryption using Key #10: JCTUJKN FCTLK - Decryption using Key #11: IBSTIJM EBSKJ - Decryption using Key #12: HARSHIL DARJI - Decryption using Key #13: GZQRGHK CZQIH - Decryption using Key #14: FYPQFGJ BYPHG - Decryption using Key #15: EXOPEFI AXOGF - Decryption using Key #16: DWNODEH ZWNFE - Decryption using Key #17: CVMNCDG YVMED - Decryption using Key #18: BULMBCF XULDC - Decryption using Key #19: ATKLABE WTKCB - Decryption using Key #20: ZSJKZAD VSJBA - Decryption using Key #21: YRIJYZC URIAZ - Decryption using Key #22: XQHIXYB TQHZY - Decryption using Key #23: WPGHWXA SPGYX - Decryption using Key #24: VOFGVWZ ROFXW - Decryption using Key #25: UNEFUVY QNEWV - """ - LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - for key in range(len(LETTERS)): - translated = "" - for symbol in message: - if symbol in LETTERS: - num = LETTERS.find(symbol) - num = num - key - if num < 0: - num = num + len(LETTERS) - translated = translated + LETTERS[num] - else: - translated = translated + symbol - print("Decryption using Key #%s: %s" % (key, translated)) - -def main(): - message = raw_input("Encrypted message: ") - message = message.upper() - decrypt(message) - -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 deleted file mode 100644 index 5c3eaaca6e27..000000000000 --- a/ciphers/transposition_cipher_encrypt-decrypt_file.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import print_function -import time, os, sys -import transposition_cipher as transCipher - -def main(): - inputFile = 'Prehistoric Men.txt' - outputFile = 'Output.txt' - key = int(raw_input('Enter key: ')) - mode = raw_input('Encrypt/Decrypt [e/d]: ') - - if not os.path.exists(inputFile): - print('File %s does not exist. Quitting...' % inputFile) - sys.exit() - if os.path.exists(outputFile): - print('Overwrite %s? [y/n]' % outputFile) - response = raw_input('> ') - if not response.lower().startswith('y'): - sys.exit() - - startTime = time.time() - if mode.lower().startswith('e'): - content = open(inputFile).read() - translated = transCipher.encryptMessage(key, content) - elif mode.lower().startswith('d'): - content = open(outputFile).read() - translated =transCipher .decryptMessage(key, content) - - outputObj = open(outputFile, 'w') - outputObj.write(translated) - outputObj.close() - - totalTime = round(time.time() - startTime, 2) - print(('Done (', totalTime, 'seconds )')) - -if __name__ == '__main__': - main() diff --git a/data_structures/AVL/AVL.py b/data_structures/AVL/AVL.py deleted file mode 100644 index d01e8f825368..000000000000 --- a/data_structures/AVL/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/Arrays.py b/data_structures/Arrays.py deleted file mode 100644 index 3ec9f8976673..000000000000 --- a/data_structures/Arrays.py +++ /dev/null @@ -1,3 +0,0 @@ -arr = [10, 20, 30, 40] -arr[1] = 30 -print(arr) diff --git a/data_structures/Binary Tree/FenwickTree.py b/data_structures/Binary Tree/FenwickTree.py deleted file mode 100644 index f429161c8c36..000000000000 --- a/data_structures/Binary Tree/FenwickTree.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import print_function -class FenwickTree: - - def __init__(self, SIZE): # create fenwick tree with size SIZE - self.Size = 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): - self.ft[i] += val - i += i & (-i) - - def query(self, i): # query cumulative data from index 0 to i in O(lg N) - ret = 0 - while (i > 0): - 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)) - f.update(2,-5) - print (f.query(1)) - print (f.query(3)) diff --git a/data_structures/Binary Tree/LazySegmentTree.py b/data_structures/Binary Tree/LazySegmentTree.py deleted file mode 100644 index 9b14b24e81fa..000000000000 --- a/data_structures/Binary Tree/LazySegmentTree.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import print_function -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 - - def right(self, idx): - 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)]) - - # 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: - self.st[idx] = self.lazy[idx] - self.flag[idx] = False - 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 - self.flag[self.right(idx)] = True - - if r < a or l > b: - return True - if l >= a and r <= b : - self.st[idx] = val - 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)]) - 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: - self.st[idx] = self.lazy[idx] - self.flag[idx] = False - 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 - self.flag[self.right(idx)] = True - if r < a or l > 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) - - def showData(self): - showList = [] - 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] - 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.showData() diff --git a/data_structures/Binary Tree/SegmentTree.py b/data_structures/Binary Tree/SegmentTree.py deleted file mode 100644 index 001bf999f391..000000000000 --- a/data_structures/Binary Tree/SegmentTree.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import print_function -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 - - def right(self, idx): - return idx * 2 + 1 - - def build(self, idx, l, r): - if l == r: - self.st[idx] = A[l] - else: - 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)]) - - 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 - if l == r : - self.st[idx] = val - return True - 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)]) - 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] - if r < a or l > b: - return -math.inf - if l >= a and r <= b: - return self.st[idx] - 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): - 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] - 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) - print (segt.query(1, 15)) - segt.update(7,8,235) - segt.showData() diff --git a/data_structures/Binary Tree/binary_search_tree.py b/data_structures/Binary Tree/binary_search_tree.py deleted file mode 100644 index b4021e4f861f..000000000000 --- a/data_structures/Binary Tree/binary_search_tree.py +++ /dev/null @@ -1,258 +0,0 @@ -''' -A binary search Tree -''' -from __future__ import print_function -class Node: - - def __init__(self, label, parent): - self.label = label - 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 - -class BinarySearchTree: - - def __init__(self): - self.root = None - - 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) - else: - parent_node.setRight(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 - 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 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() - 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 - else: - #We go deep on the left branch - curr_node = self.getRoot() - 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) - 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) - 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 = [] - if curr_node is not None: - nodeList = nodeList + InPreOrder(curr_node.getLeft()) - nodeList.insert(0, curr_node.getLabel()) - nodeList = nodeList + InPreOrder(curr_node.getRight()) - return nodeList - -def testBinarySearchTree(): - ''' - Example - 8 - / \ - 3 10 - / \ \ - 1 6 14 - / \ / - 4 7 13 - ''' - - ''' - Example After Deletion - 7 - / \ - 1 4 - - ''' - 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) - - #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") - else: - print("The label 6 doesn't exist") - - if(t.getNode(-1) is not None): - 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) - t.delete(3) - t.delete(6) - t.delete(14) - - #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/Graph/BellmanFord.py b/data_structures/Graph/BellmanFord.py deleted file mode 100644 index f5e1ac9836cb..000000000000 --- a/data_structures/Graph/BellmanFord.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import print_function - -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 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"] - - 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"] - - if mdist[u] != float('inf') and mdist[u] + w < mdist[v]: - print("Negative cycle found. Solution not possible.") - return - - printDist(mdist, V) - - - -#MAIN -V = int(raw_input("Enter number of vertices: ")) -E = int(raw_input("Enter number of edges: ")) - -graph = [dict() for j in range(E)] - -for i in range(V): - graph[i][i] = 0.0 - -for i in range(E): - print("\nEdge ",i+1) - src = int(raw_input("Enter source:")) - dst = int(raw_input("Enter destination:")) - weight = float(raw_input("Enter weight:")) - graph[i] = {"src": src,"dst": dst, "weight": weight} - -gsrc = int(raw_input("\nEnter shortest path source:")) -BellmanFord(graph, V, E, gsrc) diff --git a/data_structures/Graph/BreadthFirstSearch.py b/data_structures/Graph/BreadthFirstSearch.py deleted file mode 100644 index 3992e2d4d892..000000000000 --- a/data_structures/Graph/BreadthFirstSearch.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/python -# encoding=utf8 - -""" Author: OMKAR PATHAK """ - -from __future__ import print_function - - -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]])) - - # for adding the edge beween two vertexes - 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 BFS(self, startVertex): - # Take a list for stoting already visited vertexes - visited = [False] * len(self.vertex) - - # create a list to store all the vertexes for BFS - queue = [] - - # mark the source node as visited and enqueue it - visited[startVertex] = True - queue.append(startVertex) - - while queue: - startVertex = queue.pop(0) - print(startVertex, end = ' ') - - # 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 - -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('BFS:') - g.BFS(2) - - # OUTPUT: - # 0  ->  1 -> 2 - # 1  ->  2 - # 2  ->  0 -> 3 - # 3  ->  3 - # BFS: - # 2 0 3 1 diff --git a/data_structures/Graph/DepthFirstSearch.py b/data_structures/Graph/DepthFirstSearch.py deleted file mode 100644 index 98faf61354f9..000000000000 --- a/data_structures/Graph/DepthFirstSearch.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/python -# encoding=utf8 - -""" Author: OMKAR PATHAK """ -from __future__ import print_function - - -class Graph(): - def __init__(self): - self.vertex = {} - - # for printing the Graph vertexes - 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 - 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 vertexes 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/data_structures/Graph/Dijkstra.py b/data_structures/Graph/Dijkstra.py deleted file mode 100644 index 2580dd2f00fc..000000000000 --- a/data_structures/Graph/Dijkstra.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import print_function - -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 - -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] - - - - printDist(mdist, V) - - - -#MAIN -V = int(raw_input("Enter number of vertices: ")) -E = int(raw_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 - -for i in range(E): - print("\nEdge ",i+1) - src = int(raw_input("Enter source:")) - dst = int(raw_input("Enter destination:")) - weight = float(raw_input("Enter weight:")) - graph[src][dst] = weight - -gsrc = int(raw_input("\nEnter shortest path source:")) -Dijkstra(graph, V, gsrc) diff --git a/data_structures/Graph/FloydWarshall.py b/data_structures/Graph/FloydWarshall.py deleted file mode 100644 index 64d41c2f88e1..000000000000 --- a/data_structures/Graph/FloydWarshall.py +++ /dev/null @@ -1,48 +0,0 @@ -from __future__ import print_function - -def printDist(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() - - - -def FloydWarshall(graph, V): - dist=[[float('inf') 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): - 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) - - - -#MAIN -V = int(raw_input("Enter number of vertices: ")) -E = int(raw_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 - -for i in range(E): - print("\nEdge ",i+1) - src = int(raw_input("Enter source:")) - dst = int(raw_input("Enter destination:")) - weight = float(raw_input("Enter weight:")) - graph[src][dst] = weight - -FloydWarshall(graph, V) diff --git a/data_structures/Graph/Graph.py b/data_structures/Graph/Graph.py deleted file mode 100644 index 9bd61559dcbf..000000000000 --- a/data_structures/Graph/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/data_structures/Graph/Graph_list.py b/data_structures/Graph/Graph_list.py deleted file mode 100644 index d67bc96c4a81..000000000000 --- a/data_structures/Graph/Graph_list.py +++ /dev/null @@ -1,31 +0,0 @@ -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() - diff --git a/data_structures/Graph/Graph_matrix.py b/data_structures/Graph/Graph_matrix.py deleted file mode 100644 index de25301d6dd1..000000000000 --- a/data_structures/Graph/Graph_matrix.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import print_function - - -class Graph: - - def __init__(self, vertex): - self.vertex = vertex - self.graph = [[0] * vertex for i in range(vertex) ] - - def add_edge(self, u, v): - self.graph[u - 1][v - 1] = 1 - self.graph[v - 1][u - 1] = 1 - - def show(self): - - for i in self.graph: - for j in i: - 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.show() - diff --git a/data_structures/Graph/dijkstra_algorithm.py b/data_structures/Graph/dijkstra_algorithm.py deleted file mode 100644 index 985c7f6c1301..000000000000 --- a/data_structures/Graph/dijkstra_algorithm.py +++ /dev/null @@ -1,212 +0,0 @@ -# Title: Dijkstra's Algorithm for finding single source shortest path from scratch -# 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 - - -class PriorityQueue: - # Based on Min Heap - def __init__(self): - self.cur_size = 0 - self.array = [] - self.pos = {} # To store the pos of node in array - - def isEmpty(self): - return self.cur_size == 0 - - def min_heapify(self, idx): - lc = self.left(idx) - rc = self.right(idx) - if lc < self.cur_size and self.array(lc)[0] < self.array(idx)[0]: - smallest = lc - else: - smallest = idx - if rc < self.cur_size and self.array(rc)[0] < self.array(smallest)[0]: - smallest = rc - if smallest != idx: - self.swap(idx, smallest) - self.min_heapify(smallest) - - def insert(self, tup): - # Inserts a node into the Priority Queue - self.pos[tup[1]] = self.cur_size - self.cur_size += 1 - self.array.append((sys.maxsize, tup[1])) - self.decrease_key((sys.maxsize, tup[1]), tup[0]) - - def extract_min(self): - # Removes and returns the min element at top of priority queue - min_node = self.array[0][1] - self.array[0] = self.array[self.cur_size - 1] - self.cur_size -= 1 - self.min_heapify(1) - del self.pos[min_node] - return min_node - - def left(self, i): - # returns the index of left child - return 2 * i + 1 - - def right(self, i): - # returns the index of right child - return 2 * i + 2 - - def par(self, i): - # returns the index of parent - return math.floor(i / 2) - - def swap(self, i, j): - # swaps array elements at indices i and j - # update the pos{} - self.pos[self.array[i][1]] = j - self.pos[self.array[j][1]] = i - temp = self.array[i] - self.array[i] = self.array[j] - self.array[j] = temp - - def decrease_key(self, tup, new_d): - idx = self.pos[tup[1]] - # assuming the new_d is atmost old_d - self.array[idx] = (new_d, tup[1]) - while idx > 0 and self.array[self.par(idx)][0] > self.array[idx][0]: - self.swap(idx, self.par(idx)) - idx = self.par(idx) - - -class Graph: - def __init__(self, num): - 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 - - def add_edge(self, u, v, w): - # Edge going from node u to v and v to u with weight w - # u (w)-> v, v (w) -> u - # Check if u already in graph - if u in self.adjList.keys(): - self.adjList[u].append((v, w)) - else: - self.adjList[u] = [(v, w)] - - # Assuming undirected graph - if v in self.adjList.keys(): - self.adjList[v].append((u, w)) - else: - self.adjList[v] = [(u, 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])) - - def dijkstra(self, src): - # Flush old junk values in par[] - self.par = [-1] * self.num_nodes - # src is the source node - self.dist[src] = 0 - Q = PriorityQueue() - Q.insert((0, src)) # (dist from src, node) - for u in self.adjList.keys(): - if u != src: - self.dist[u] = sys.maxsize # Infinity - self.par[u] = -1 - - while not Q.isEmpty(): - u = Q.extract_min() # Returns node with the min dist from source - # Update the distance of all the neighbours of u and - # if their prev dist was INFINITY then push them in Q - for v, w in self.adjList[u]: - new_dist = self.dist[u] + w - if self.dist[v] > new_dist: - if self.dist[v] == sys.maxsize: - Q.insert((new_dist, v)) - else: - Q.decrease_key((self.dist[v], v), new_dist) - self.dist[v] = new_dist - self.par[v] = u - - # Show the shortest distances from src - self.show_distances(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])) - - def show_path(self, src, dest): - # To show the shortest path from src to dest - # WARNING: Use it *after* calling dijkstra - path = [] - cost = 0 - temp = dest - # Backtracking from dest to src - while self.par[temp] != -1: - path.append(temp) - if temp != src: - for v, w in self.adjList[temp]: - if v == self.par[temp]: - cost += w - break - temp = self.par[temp] - path.append(src) - path.reverse() - - print('----Path to reach {} from {}----'.format(dest, src)) - for u in path: - print('{}'.format(u), end=' ') - if u != dest: - print('-> ', end='') - - print('\nTotal cost of path: ', cost) - - -if __name__ == '__main__': - graph = Graph(9) - graph.add_edge(0, 1, 4) - graph.add_edge(0, 7, 8) - graph.add_edge(1, 2, 8) - graph.add_edge(1, 7, 11) - graph.add_edge(2, 3, 7) - graph.add_edge(2, 8, 2) - graph.add_edge(2, 5, 4) - graph.add_edge(3, 4, 9) - graph.add_edge(3, 5, 14) - graph.add_edge(4, 5, 10) - graph.add_edge(5, 6, 2) - graph.add_edge(6, 7, 1) - graph.add_edge(6, 8, 6) - graph.add_edge(7, 8, 7) - graph.show_graph() - graph.dijkstra(0) - graph.show_path(0, 4) - -# OUTPUT -# 0 -> 1(4) -> 7(8) -# 1 -> 0(4) -> 2(8) -> 7(11) -# 7 -> 0(8) -> 1(11) -> 6(1) -> 8(7) -# 2 -> 1(8) -> 3(7) -> 8(2) -> 5(4) -# 3 -> 2(7) -> 4(9) -> 5(14) -# 8 -> 2(2) -> 6(6) -> 7(7) -# 5 -> 2(4) -> 3(14) -> 4(10) -> 6(2) -# 4 -> 3(9) -> 5(10) -# 6 -> 5(2) -> 7(1) -> 8(6) -# Distance from node: 0 -# Node 0 has distance: 0 -# Node 1 has distance: 4 -# Node 2 has distance: 12 -# Node 3 has distance: 19 -# Node 4 has distance: 21 -# Node 5 has distance: 11 -# Node 6 has distance: 9 -# Node 7 has distance: 8 -# Node 8 has distance: 14 -# ----Path to reach 4 from 0---- -# 0 -> 7 -> 6 -> 5 -> 4 -# Total cost of path: 21 diff --git a/data_structures/Graph/even_tree.py b/data_structures/Graph/even_tree.py deleted file mode 100644 index 9383ea9a13c1..000000000000 --- a/data_structures/Graph/even_tree.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -You are given a tree(a simple connected graph with no cycles). The tree has N -nodes numbered from 1 to N and is rooted at node 1. - -Find the maximum number of edges you can remove from the tree to get a forest -such that each connected component of the forest contains an even number of -nodes. - -Constraints -2 <= 2 <= 100 - -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 - - -def dfs(start): - """DFS traversal""" - # pylint: disable=redefined-outer-name - ret = 1 - visited[start] = True - for v in tree.get(start): - if v not in visited: - ret += dfs(v) - if ret % 2 == 0: - cuts.append(start) - return ret - - -def even_tree(): - """ - 2 1 - 3 1 - 4 3 - 5 2 - 6 1 - 7 2 - 8 6 - 9 8 - 10 8 - On removing edges (1,3) and (1,6), we can get the desired result 2. - """ - dfs(1) - - -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), - ] - for u, v in edges: - tree[u].append(v) - tree[v].append(u) - even_tree() - print(len(cuts) - 1) diff --git a/data_structures/Heap/heap.py b/data_structures/Heap/heap.py deleted file mode 100644 index d0c2400eb6b5..000000000000 --- a/data_structures/Heap/heap.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/python - -from __future__ import print_function, division - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - -class Heap: - def __init__(self): - self.h = [] - self.currsize = 0 - - 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 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): - self.currsize = len(a) - self.h = list(a) - for i in range(self.currsize/2,-1,-1): - self.maxHeapify(i) - - def getMax(self): - 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): - 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): - 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): - print(self.h) - -def main(): - l = list(map(int, raw_input().split())) - h = Heap() - h.buildHeap(l) - h.heapSort() - h.display() - -if __name__=='__main__': - main() - - diff --git a/data_structures/LinkedList/DoublyLinkedList.py b/data_structures/LinkedList/DoublyLinkedList.py deleted file mode 100644 index 18eb63fea00e..000000000000 --- a/data_structures/LinkedList/DoublyLinkedList.py +++ /dev/null @@ -1,76 +0,0 @@ -''' -- 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. -''' -from __future__ import print_function - - -class LinkedList: - 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 - self.tail = newLink - 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) - 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 - 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 - 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 - def displayLink(self): - print("{}".format(self.value), end=" ") diff --git a/data_structures/LinkedList/__init__.py b/data_structures/LinkedList/__init__.py deleted file mode 100644 index 6d50f23c1f1a..000000000000 --- a/data_structures/LinkedList/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -class Node: - def __init__(self, item, next): - self.item = item - self.next = next - -class LinkedList: - def __init__(self): - self.head = None - - def add(self, item): - self.head = Node(item, self.head) - - def remove(self): - if self.is_empty(): - return None - else: - item = self.head.item - self.head = self.head.next - return item - - def is_empty(self): - return self.head is None diff --git a/data_structures/LinkedList/singly_LinkedList.py b/data_structures/LinkedList/singly_LinkedList.py deleted file mode 100644 index 0d6157e3eb65..000000000000 --- a/data_structures/LinkedList/singly_LinkedList.py +++ /dev/null @@ -1,70 +0,0 @@ -from __future__ import print_function - - -class Node: # create a Node - def __init__(self, data): - self.data = data # given data - self.next = None # given next to None - - -class Linked_List: - def insert_tail(Head, data): - if Head.next is None: - Head.next = Node(data) - else: - Head.next.insert_tail(data) - - def insert_head(Head, data): - tamp = Head - if tamp is None: - newNod = Node() # create a new Node - newNod.data = data - newNod.next = None - Head = newNod # make new node to Head - else: - newNod = Node() - newNod.data = data - newNod.next = Head # put the Head at NewNode Next - Head = newNod # make a NewNode to Head - return Head - - def printList(Head): # print every node data - tamp = Head - while tamp is not None: - print(tamp.data) - tamp = tamp.next - - def delete_head(Head): # delete from head - if Head is not None: - Head = Head.next - return Head # return new Head - - def delete_tail(Head): # delete from tail - if Head is not None: - tamp = Node() - tamp = Head - while tamp.next.next is not None: # find the 2nd last element - tamp = tamp.next - # delete the last element by give next None to 2nd last Element - tamp.next = None - return Head - - def isEmpty(Head): - return Head is None # Return if Head is none - - def reverse(Head): - prev = None - current = Head - - while current: - # Store the current node's next node. - next_node = current.next - # Make the current node's next point backwards - current.next = prev - # Make the previous node be the current node - prev = current - # Make the current node the next node (to progress iteration) - current = next_node - # Return prev in order to put the head at the end - Head = prev - return Head diff --git a/data_structures/Queue/DeQueue.py b/data_structures/Queue/DeQueue.py deleted file mode 100644 index fdee64eb6ae0..000000000000 --- a/data_structures/Queue/DeQueue.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import print_function -# 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 -# 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 -# 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) - -# 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) - -# using reverse() to reverse the deque -de.reverse() - -# printing modified deque -print ("The deque after reversing deque is : ") -print (de) diff --git a/data_structures/Queue/QueueOnList.py b/data_structures/Queue/QueueOnList.py deleted file mode 100644 index c8d0b41de5d5..000000000000 --- a/data_structures/Queue/QueueOnList.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Queue represented by a python list""" -class Queue(): - def __init__(self): - self.entries = [] - self.length = 0 - self.front=0 - - def __str__(self): - 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:] - 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/QueueOnPseudoStack.py b/data_structures/Queue/QueueOnPseudoStack.py deleted file mode 100644 index b69fbcc988f7..000000000000 --- a/data_structures/Queue/QueueOnPseudoStack.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Queue represented by a pseudo stack (represented by a list with pop and append)""" -class Queue(): - def __init__(self): - self.stack = [] - self.length = 0 - - def __str__(self): - 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 - - """Dequeues {@code item} - @requirement: |self.length| > 0 - @return dequeued - item that was dequeued""" - def get(self): - self.rotate(1) - dequeued = self.stack[self.length-1] - self.stack = self.stack[:-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] - self.stack = self.stack[1:] - self.put(temp) - self.length = self.length - 1 - - """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) - return front - - """Returns the length of this.stack""" - def size(self): - return self.length 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/Stock-Span-Problem.py b/data_structures/Stacks/Stock-Span-Problem.py deleted file mode 100644 index 9628864edd10..000000000000 --- a/data_structures/Stacks/Stock-Span-Problem.py +++ /dev/null @@ -1,52 +0,0 @@ -''' -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. -''' -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)) diff --git a/data_structures/Stacks/__init__.py b/data_structures/Stacks/__init__.py deleted file mode 100644 index f7e92ae2d269..000000000000 --- a/data_structures/Stacks/__init__.py +++ /dev/null @@ -1,23 +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/balanced_parentheses.py b/data_structures/Stacks/balanced_parentheses.py deleted file mode 100644 index 8d99358bea87..000000000000 --- a/data_structures/Stacks/balanced_parentheses.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -from .Stack import Stack - -__author__ = 'Omkar Pathak' - - -def balanced_parentheses(parentheses): - """ Use a stack to check if a string of parentheses are balanced.""" - stack = Stack(len(parentheses)) - for parenthesis in parentheses: - if parenthesis == '(': - stack.push(parenthesis) - elif parenthesis == ')': - stack.pop() - return not stack.is_empty() - - -if __name__ == '__main__': - examples = ['((()))', '((())'] - print('Balanced parentheses demonstration:\n') - for example in examples: - 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 deleted file mode 100644 index 75211fed258d..000000000000 --- a/data_structures/Stacks/infix_to_postfix_conversion.py +++ /dev/null @@ -1,64 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -import string - -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 - order of operation. - - https://en.wikipedia.org/wiki/Order_of_operations - """ - dictionary = {'+': 1, '-': 1, - '*': 2, '/': 2, - '^': 3} - return dictionary.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 - """ - stack = Stack(len(expression)) - postfix = [] - 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())): - 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() - while not stack.is_empty(): - postfix.append(stack.pop()) - return ' '.join(postfix) - - -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)) diff --git a/data_structures/Stacks/next.py b/data_structures/Stacks/next.py deleted file mode 100644 index bca83339592c..000000000000 --- a/data_structures/Stacks/next.py +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 66af8c025d8c..000000000000 --- a/data_structures/Stacks/stack.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import print_function -__author__ = 'Omkar Pathak' - - -class Stack(object): - """ 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 - Last In, First Out (LIFO). - - https://en.wikipedia.org/wiki/Stack_(abstract_data_type) - """ - - def __init__(self, limit=10): - self.stack = [] - self.limit = limit - - def __bool__(self): - return not bool(self.stack) - - def __str__(self): - return str(self.stack) - - def push(self, data): - """ Push an element to the top of the stack.""" - if len(self.stack) >= self.limit: - raise StackOverflowError - self.stack.append(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') - - def peek(self): - """ Peek at the top-most element of the stack.""" - if self.stack: - return self.stack[-1] - - def is_empty(self): - """ Check if a stack is empty.""" - return not bool(self.stack) - - def size(self): - """ Return the size of the stack.""" - return len(self.stack) - - -class StackOverflowError(BaseException): - pass - - -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())) - 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())) diff --git a/data_structures/Trie/Trie.py b/data_structures/Trie/Trie.py deleted file mode 100644 index b6234c6704c6..000000000000 --- a/data_structures/Trie/Trie.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -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. - -""" - - -class TrieNode: - 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 - """ - Inserts a list of words into the Trie - :param words: list of string words - :return: None - """ - for word in words: - self.insert(word) - - def insert(self, word: str): # noqa: E999 This syntax is Python 3 only - """ - Inserts a word into the Trie - :param word: word to be inserted - :return: None - """ - curr = self - for char in word: - if char not in curr.nodes: - curr.nodes[char] = TrieNode() - curr = curr.nodes[char] - curr.is_leaf = True - - def find(self, word: str) -> bool: # noqa: E999 This syntax is Python 3 only - """ - Tries to find word in a Trie - :param word: word to look for - :return: Returns True if word is found, False otherwise - """ - curr = self - for char in word: - if char not in curr.nodes: - return False - curr = curr.nodes[char] - return curr.is_leaf - - -def print_words(node: TrieNode, word: str): # noqa: E999 This syntax is Python 3 only - """ - Prints all the words in a Trie - :param node: root node of Trie - :param word: Word variable should be empty at start - :return: None - """ - if node.is_leaf: - 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'] - 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') - -test() diff --git a/data_structures/UnionFind/__init__.py b/data_structures/UnionFind/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/data_structures/UnionFind/tests_union_find.py b/data_structures/UnionFind/tests_union_find.py deleted file mode 100644 index b0708778ddbd..000000000000 --- a/data_structures/UnionFind/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/UnionFind/union_find.py b/data_structures/UnionFind/union_find.py deleted file mode 100644 index 40eea67ac944..000000000000 --- a/data_structures/UnionFind/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/dynamic_programming/FloydWarshall.py b/dynamic_programming/FloydWarshall.py deleted file mode 100644 index 038499ca03b6..000000000000 --- a/dynamic_programming/FloydWarshall.py +++ /dev/null @@ -1,37 +0,0 @@ -import math - -class Graph: - - 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 - - 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): - 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__': - 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.floyd_warshall() - graph.showMin(1,4) - graph.showMin(0,3) diff --git a/linear-algebra-python/README.md b/linear-algebra-python/README.md deleted file mode 100644 index ebfcdab7b179..000000000000 --- a/linear-algebra-python/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Linear algebra library for Python - -This module contains some useful classes and functions for dealing with linear algebra in python 2. - ---- - -## Overview - -- class Vector - - This class represents a vector of arbitray size and operations on it. - - **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) - - size() : gets the size 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'. -- class Matrix - - 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 - - height() : returns the height of the matrix - - 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 - -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)```. - ---- - -## 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. - ---- - -## 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**. diff --git a/linear-algebra-python/src/lib.py b/linear-algebra-python/src/lib.py deleted file mode 100644 index 66f27ff8946e..000000000000 --- a/linear-algebra-python/src/lib.py +++ /dev/null @@ -1,364 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Feb 26 14:29:11 2018 - -@author: Christian Bender -@license: MIT-license - -This module contains some useful classes and functions for dealing -with linear algebra in python. - -Overview: - -- class Vector -- function zeroVector(dimension) -- function unitBasisVector(dimension,pos) -- function axpy(scalar,vector1,vector2) -- function randomVector(N,a,b) -- class Matrix -- function squareZeroMatrix(N) -- function randomMatrix(W,H,a,b) -""" - - -import math -import random - - -class Vector(object): - """ - This class represents a vector of arbitray 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) - size() : gets the size 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. - TODO: compare-operator - """ - def __init__(self,components): - """ - input: components or nothing - simple constructor for init the vector - """ - self.__components = components - def set(self,components): - """ - input: new components - changes the components of the vector. - replace the components with newer one. - """ - if len(components) > 0: - self.__components = components - else: - raise Exception("please give any vector") - def __str__(self): - """ - returns a string representation of the vector - """ - ans = "(" - length = len(self.__components) - for i in range(length): - if i != length-1: - ans += str(self.__components[i]) + "," - else: - ans += str(self.__components[i]) + ")" - if len(ans) == 1: - ans += ")" - return ans - def component(self,i): - """ - input: index (start at 0) - output: the i-th component of the vector. - """ - if i < len(self.__components) and i >= 0: - return self.__components[i] - else: - raise Exception("index out of range") - def size(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 - return math.sqrt(summe) - def __add__(self,other): - """ - input: other vector - assumes: other vector has the same size - returns a new vector that represents the sum. - """ - size = self.size() - result = [] - if size == other.size(): - for i in range(size): - result.append(self.__components[i] + other.component(i)) - else: - raise Exception("must have the same size") - return Vector(result) - def __sub__(self,other): - """ - input: other vector - assumes: other vector has the same size - returns a new vector that represents the differenz. - """ - size = self.size() - result = [] - if size == other.size(): - for i in range(size): - result.append(self.__components[i] - other.component(i)) - else: # error case - raise Exception("must have the same size") - return Vector(result) - def __mul__(self,other): - """ - mul implements the scalar multiplication - and the dot-product - """ - ans = [] - if isinstance(other,float) or isinstance(other,int): - for c in self.__components: - ans.append(c*other) - elif (isinstance(other,Vector) and (self.size() == other.size())): - size = self.size() - summe = 0 - for i in range(size): - summe += self.__components[i] * other.component(i) - return summe - else: # error case - raise Exception("invalide operand!") - return Vector(ans) - def copy(self): - """ - copies this vector and returns it. - """ - components = [x for x in self.__components] - return Vector(components) - def changeComponent(self,pos,value): - """ - input: an index (pos) and a value - changes the specified component (pos) with the - 'value' - """ - #precondition - assert (pos >= 0 and pos < len(self.__components)) - self.__components[pos] = value - -def zeroVector(dimension): - """ - returns a zero-vector of size 'dimension' - """ - #precondition - assert(isinstance(dimension,int)) - ans = [] - for i in range(dimension): - ans.append(0) - return Vector(ans) - - -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 = [] - for i in range(dimension): - if i != pos: - ans.append(0) - else: - ans.append(1) - return Vector(ans) - - -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) - - -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'. - """ - ans = zeroVector(N) - random.seed(None) - for i in range(N): - ans.changeComponent(i,random.randint(a,b)) - return ans - - -class Matrix(object): - """ - class: Matrix - This class represents a arbitrary 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 - operator + : implements the matrix-addition. - operator - _ implements the matrix-subtraction - """ - def __init__(self,matrix,w,h): - """ - simple constructor for initialzes - the matrix with components. - """ - self.__matrix = matrix - self.__width = w - self.__height = h - def __str__(self): - """ - returns a string representation of this - matrix. - """ - ans = "" - for i in range(self.__height): - ans += "|" - for j in range(self.__width): - 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): - """ - 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): - """ - 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") - def width(self): - """ - getter for the width - """ - return self.__width - def height(self): - """ - getter for the height - """ - return self.__height - def __mul__(self,other): - """ - implements the matrix-vector multiplication. - implements the matrix-scalar multiplication - """ - if isinstance(other, Vector): # vector-matrix - if (other.size() == 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) - 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 = [] - for i in range(self.__height): - row = [] - for j in range(self.__width): - row.append(self.__matrix[i][j] * other) - matrix.append(row) - 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()): - matrix = [] - for i in range(self.__height): - row = [] - for j in range(self.__width): - row.append(self.__matrix[i][j] + other.component(i,j)) - matrix.append(row) - return Matrix(matrix,self.__width,self.__height) - else: - raise Exception("matrix must have the same dimension!") - def __sub__(self,other): - """ - implements the matrix-subtraction. - """ - 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)) - matrix.append(row) - 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 = [] - for i in range(N): - row = [] - for j in range(N): - row.append(0) - ans.append(row) - return Matrix(ans,N,N) - - -def randomMatrix(W,H,a,b): - """ - returns a random matrix WxH with integer components - between 'a' and 'b' - """ - matrix = [] - random.seed(None) - for i in range(H): - row = [] - for j in range(W): - row.append(random.randint(a,b)) - matrix.append(row) - return Matrix(matrix,W,H) - - \ No newline at end of file diff --git a/linear-algebra-python/src/tests.py b/linear-algebra-python/src/tests.py deleted file mode 100644 index b84612b4ced4..000000000000 --- a/linear-algebra-python/src/tests.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Feb 26 15:40:07 2018 - -@author: Christian Bender -@license: MIT-license - -This file contains the test-suite for the linear algebra library. -""" - -import unittest -from lib import * - -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) - 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(x.__str__(),"(0,0,0,0,0,1)") - def test_size(self): - """ - test for size()-method - """ - x = Vector([1,2,3,4]) - self.assertEqual(x.size(),4) - def test_euclidLength(self): - """ - test for the eulidean length - """ - 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) - 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) - 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((x*3.0).__str__(),"(3.0,6.0,9.0)") - self.assertEqual((a*b),0) - def test_zeroVector(self): - """ - test for the global function zeroVector(...) - """ - self.assertTrue(zeroVector(10).__str__().count("0") == 10) - def test_unitBasisVector(self): - """ - test for the global function unitBasisVector(...) - """ - self.assertEqual(unitBasisVector(3,1).__str__(),"(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(axpy(2,x,y).__str__(),"(3,4,7)") - def test_copy(self): - """ - test for the copy()-method - """ - x = Vector([1,0,0,0,0,0]) - y = x.copy() - self.assertEqual(x.__str__(),y.__str__()) - def test_changeComponent(self): - """ - test for the changeComponent(...)-method - """ - x = Vector([1,0,0]) - x.changeComponent(0,0) - x.changeComponent(1,1) - self.assertEqual(x.__str__(),"(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",A.__str__()) - 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)",(A*x).__str__()) - self.assertEqual("|2,4,6|\n|8,10,12|\n|14,16,18|\n",(A*2).__str__()) - 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",A.__str__()) - 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) - 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",(A+B).__str__()) - 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",(A-B).__str__()) - 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',squareZeroMatrix(5).__str__()) - - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/simple-client-server/README.md b/simple-client-server/README.md deleted file mode 100644 index f51947f2105a..000000000000 --- a/simple-client-server/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-server/client.py b/simple-client-server/client.py deleted file mode 100644 index c91e6233ad16..000000000000 --- a/simple-client-server/client.py +++ /dev/null @@ -1,14 +0,0 @@ -# client.py - -import socket - -HOST, PORT = '127.0.0.1', 1400 - -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.connect((HOST, PORT)) - -s.send(b'Hello World') -data = s.recv(1024) - -s.close() -print(repr(data.decode('ascii'))) diff --git a/simple-client-server/server.py b/simple-client-server/server.py deleted file mode 100644 index c6b661d99044..000000000000 --- a/simple-client-server/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) -s.bind((HOST, PORT)) -s.listen(1) - -conn, addr = s.accept() - -print('connected to:', addr) - -while 1: - data = conn.recv(1024) - if not data: - break - conn.send(data + b' [ addition by server ]') - -conn.close() From 564179a0eca7ea56d640bcc1b2669113adbeec9e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 19 Oct 2018 07:48:28 -0500 Subject: [PATCH 0183/2908] increment 1 --- analysis/Compression_Analysis/PSNR.py | 38 + .../Compression_Analysis/compressed_image.png | Bin 0 -> 26684 bytes .../Compression_Analysis/example_image.jpg | Bin 0 -> 29986 bytes .../Compression_Analysis/orignal_image.png | Bin 0 -> 83865 bytes arithmetic_analysis/bisection.py | 33 + arithmetic_analysis/intersection.py | 16 + arithmetic_analysis/lu_decomposition.py | 34 + arithmetic_analysis/newton_method.py | 15 + arithmetic_analysis/newton_raphson_method.py | 36 + boolean_algebra/quine_mc_cluskey.py | 116 + ciphers/brute_force_caesar_cipher.py | 54 + ciphers/onepad_cipher.py | 32 + ciphers/prehistoric_men.txt | 7193 +++++++++++++++++ ...ansposition_cipher_encrypt_decrypt_file.py | 36 + ciphers/xor_cipher.py | 209 + data_structures/arrays.py | 3 + data_structures/avl.py | 181 + data_structures/binary tree/FenwickTree.py | 29 + .../binary tree/LazySegmentTree.py | 91 + data_structures/binary tree/SegmentTree.py | 71 + .../binary tree/binary_search_tree.py | 258 + data_structures/graph/bellman_ford.py | 54 + data_structures/graph/breadth_first_search.py | 67 + data_structures/graph/depth_first_search.py | 66 + data_structures/graph/dijkstra.py | 57 + data_structures/graph/dijkstra_algorithm.py | 212 + data_structures/graph/even_tree.py | 70 + data_structures/graph/floyd_warshall.py | 48 + data_structures/graph/graph.py | 44 + data_structures/graph/graph_list.py | 31 + data_structures/graph/graph_matrix.py | 32 + data_structures/heap/heap.py | 90 + .../linked_list/DoublyLinkedList.py | 76 + data_structures/linked_list/__init__.py | 22 + .../linked_list/singly_LinkedList.py | 70 + data_structures/queue/DeQueue.py | 40 + data_structures/queue/QueueOnList.py | 45 + data_structures/queue/QueueOnPseudoStack.py | 50 + data_structures/queue/__init__.py | 0 data_structures/stacks/Stock-Span-Problem.py | 52 + data_structures/stacks/__init__.py | 23 + .../stacks/balanced_parentheses.py | 23 + .../stacks/infix_to_postfix_conversion.py | 64 + data_structures/stacks/next.py | 17 + data_structures/stacks/stack.py | 69 + data_structures/trie/Trie.py | 75 + data_structures/union_find/__init__.py | 0 .../union_find/tests_union_find.py | 78 + data_structures/union_find/union_find.py | 87 + dynamic_programming/floyd_warshall.py | 37 + file_transfer_protocol/ftp_client_server.py | 58 + file_transfer_protocol/ftp_send_receive.py | 36 + graphs/ArticulationPoints.py | 44 + graphs/CheckBipartiteGraph_BFS.py | 43 + graphs/FindingBridges.py | 31 + graphs/KahnsAlgorithm_long.py | 30 + graphs/KahnsAlgorithm_topo.py | 32 + graphs/MinimumSpanningTree_Prims.py | 111 + graphs/Multi_Hueristic_Astar.py | 266 + graphs/a_star.py | 102 + graphs/basic-graphs.py | 281 + graphs/minimum_spanning_tree_kruskal.py | 32 + graphs/scc_kosaraju.py | 46 + graphs/tarjans_scc.py | 78 + linear_algebra_python/README.md | 74 + linear_algebra_python/src/lib.py | 364 + linear_algebra_python/src/tests.py | 133 + maths/BasicMaths.py | 74 + maths/FibonacciSequenceRecursion.py | 18 + maths/GreaterCommonDivisor.py | 15 + maths/ModularExponential.py | 20 + maths/SegmentedSieve.py | 46 + maths/SieveOfEratosthenes.py | 24 + maths/SimpsonRule.py | 49 + maths/TrapezoidalRule.py | 46 + neural_network/FCN.ipynb | 327 + neural_network/bpnn.py | 193 + neural_network/convolution_neural_network.py | 306 + neural_network/perceptron.py | 124 + project_euler/Problem 01/sol1.py | 17 + project_euler/Problem 01/sol2.py | 20 + project_euler/Problem 01/sol3.py | 50 + project_euler/Problem 01/sol4.py | 30 + project_euler/Problem 02/sol1.py | 26 + project_euler/Problem 02/sol2.py | 12 + project_euler/Problem 02/sol3.py | 20 + project_euler/Problem 03/sol1.py | 39 + project_euler/Problem 03/sol2.py | 17 + project_euler/Problem 04/sol1.py | 29 + project_euler/Problem 04/sol2.py | 19 + project_euler/Problem 05/sol1.py | 21 + project_euler/Problem 05/sol2.py | 20 + project_euler/Problem 06/sol1.py | 20 + project_euler/Problem 06/sol2.py | 16 + project_euler/Problem 07/sol1.py | 30 + project_euler/Problem 07/sol2.py | 16 + project_euler/Problem 08/sol1.py | 15 + project_euler/Problem 09/sol1.py | 15 + project_euler/Problem 09/sol2.py | 18 + project_euler/Problem 10/sol1.py | 38 + project_euler/Problem 11/grid.txt | 20 + project_euler/Problem 11/sol1.py | 68 + project_euler/Problem 11/sol2.py | 39 + project_euler/Problem 12/sol1.py | 46 + project_euler/Problem 13/sol1.py | 14 + project_euler/Problem 14/sol1.py | 21 + project_euler/Problem 15/sol1.py | 20 + project_euler/Problem 16/sol1.py | 15 + project_euler/Problem 17/sol1.py | 35 + project_euler/Problem 19/sol1.py | 51 + project_euler/Problem 20/sol1.py | 27 + project_euler/Problem 20/sol2.py | 5 + project_euler/Problem 21/sol1.py | 42 + project_euler/Problem 22/p022_names.txt | 1 + project_euler/Problem 22/sol1.py | 37 + project_euler/Problem 22/sol2.py | 533 ++ project_euler/Problem 24/sol1.py | 7 + project_euler/Problem 25/sol1.py | 31 + project_euler/Problem 28/sol1.py | 29 + project_euler/Problem 29/solution.py | 33 + project_euler/Problem 36/sol1.py | 30 + project_euler/Problem 40/sol1.py | 26 + project_euler/Problem 48/sol1.py | 21 + project_euler/Problem 52/sol1.py | 23 + project_euler/Problem 53/sol1.py | 36 + project_euler/Problem 76/sol1.py | 35 + project_euler/README.md | 58 + simple_client_server/README.md | 6 + simple_client_server/client.py | 14 + simple_client_server/server.py | 21 + tags | 1373 ++++ 131 files changed, 16252 insertions(+) create mode 100644 analysis/Compression_Analysis/PSNR.py create mode 100644 analysis/Compression_Analysis/compressed_image.png create mode 100644 analysis/Compression_Analysis/example_image.jpg create mode 100644 analysis/Compression_Analysis/orignal_image.png create mode 100644 arithmetic_analysis/bisection.py create mode 100644 arithmetic_analysis/intersection.py create mode 100644 arithmetic_analysis/lu_decomposition.py create mode 100644 arithmetic_analysis/newton_method.py create mode 100644 arithmetic_analysis/newton_raphson_method.py create mode 100644 boolean_algebra/quine_mc_cluskey.py create mode 100644 ciphers/brute_force_caesar_cipher.py create mode 100644 ciphers/onepad_cipher.py create mode 100644 ciphers/prehistoric_men.txt create mode 100644 ciphers/transposition_cipher_encrypt_decrypt_file.py create mode 100644 ciphers/xor_cipher.py create mode 100644 data_structures/arrays.py create mode 100644 data_structures/avl.py create mode 100644 data_structures/binary tree/FenwickTree.py create mode 100644 data_structures/binary tree/LazySegmentTree.py create mode 100644 data_structures/binary tree/SegmentTree.py create mode 100644 data_structures/binary tree/binary_search_tree.py create mode 100644 data_structures/graph/bellman_ford.py create mode 100644 data_structures/graph/breadth_first_search.py create mode 100644 data_structures/graph/depth_first_search.py create mode 100644 data_structures/graph/dijkstra.py create mode 100644 data_structures/graph/dijkstra_algorithm.py create mode 100644 data_structures/graph/even_tree.py create mode 100644 data_structures/graph/floyd_warshall.py create mode 100644 data_structures/graph/graph.py create mode 100644 data_structures/graph/graph_list.py create mode 100644 data_structures/graph/graph_matrix.py create mode 100644 data_structures/heap/heap.py create mode 100644 data_structures/linked_list/DoublyLinkedList.py create mode 100644 data_structures/linked_list/__init__.py create mode 100644 data_structures/linked_list/singly_LinkedList.py create mode 100644 data_structures/queue/DeQueue.py create mode 100644 data_structures/queue/QueueOnList.py create mode 100644 data_structures/queue/QueueOnPseudoStack.py create mode 100644 data_structures/queue/__init__.py create mode 100644 data_structures/stacks/Stock-Span-Problem.py create mode 100644 data_structures/stacks/__init__.py create mode 100644 data_structures/stacks/balanced_parentheses.py create mode 100644 data_structures/stacks/infix_to_postfix_conversion.py create mode 100644 data_structures/stacks/next.py create mode 100644 data_structures/stacks/stack.py create mode 100644 data_structures/trie/Trie.py create mode 100644 data_structures/union_find/__init__.py create mode 100644 data_structures/union_find/tests_union_find.py create mode 100644 data_structures/union_find/union_find.py create mode 100644 dynamic_programming/floyd_warshall.py create mode 100644 file_transfer_protocol/ftp_client_server.py create mode 100644 file_transfer_protocol/ftp_send_receive.py create mode 100644 graphs/ArticulationPoints.py create mode 100644 graphs/CheckBipartiteGraph_BFS.py create mode 100644 graphs/FindingBridges.py create mode 100644 graphs/KahnsAlgorithm_long.py create mode 100644 graphs/KahnsAlgorithm_topo.py create mode 100644 graphs/MinimumSpanningTree_Prims.py create mode 100644 graphs/Multi_Hueristic_Astar.py create mode 100644 graphs/a_star.py create mode 100644 graphs/basic-graphs.py create mode 100644 graphs/minimum_spanning_tree_kruskal.py create mode 100644 graphs/scc_kosaraju.py create mode 100644 graphs/tarjans_scc.py create mode 100644 linear_algebra_python/README.md create mode 100644 linear_algebra_python/src/lib.py create mode 100644 linear_algebra_python/src/tests.py create mode 100644 maths/BasicMaths.py create mode 100644 maths/FibonacciSequenceRecursion.py create mode 100644 maths/GreaterCommonDivisor.py create mode 100644 maths/ModularExponential.py create mode 100644 maths/SegmentedSieve.py create mode 100644 maths/SieveOfEratosthenes.py create mode 100644 maths/SimpsonRule.py create mode 100644 maths/TrapezoidalRule.py create mode 100644 neural_network/FCN.ipynb create mode 100644 neural_network/bpnn.py create mode 100644 neural_network/convolution_neural_network.py create mode 100644 neural_network/perceptron.py create mode 100644 project_euler/Problem 01/sol1.py create mode 100644 project_euler/Problem 01/sol2.py create mode 100644 project_euler/Problem 01/sol3.py create mode 100644 project_euler/Problem 01/sol4.py create mode 100644 project_euler/Problem 02/sol1.py create mode 100644 project_euler/Problem 02/sol2.py create mode 100644 project_euler/Problem 02/sol3.py create mode 100644 project_euler/Problem 03/sol1.py create mode 100644 project_euler/Problem 03/sol2.py create mode 100644 project_euler/Problem 04/sol1.py create mode 100644 project_euler/Problem 04/sol2.py create mode 100644 project_euler/Problem 05/sol1.py create mode 100644 project_euler/Problem 05/sol2.py create mode 100644 project_euler/Problem 06/sol1.py create mode 100644 project_euler/Problem 06/sol2.py create mode 100644 project_euler/Problem 07/sol1.py create mode 100644 project_euler/Problem 07/sol2.py create mode 100644 project_euler/Problem 08/sol1.py create mode 100644 project_euler/Problem 09/sol1.py create mode 100644 project_euler/Problem 09/sol2.py create mode 100644 project_euler/Problem 10/sol1.py create mode 100644 project_euler/Problem 11/grid.txt create mode 100644 project_euler/Problem 11/sol1.py create mode 100644 project_euler/Problem 11/sol2.py create mode 100644 project_euler/Problem 12/sol1.py create mode 100644 project_euler/Problem 13/sol1.py create mode 100644 project_euler/Problem 14/sol1.py create mode 100644 project_euler/Problem 15/sol1.py create mode 100644 project_euler/Problem 16/sol1.py create mode 100644 project_euler/Problem 17/sol1.py create mode 100644 project_euler/Problem 19/sol1.py create mode 100644 project_euler/Problem 20/sol1.py create mode 100644 project_euler/Problem 20/sol2.py create mode 100644 project_euler/Problem 21/sol1.py create mode 100644 project_euler/Problem 22/p022_names.txt create mode 100644 project_euler/Problem 22/sol1.py create mode 100644 project_euler/Problem 22/sol2.py create mode 100644 project_euler/Problem 24/sol1.py create mode 100644 project_euler/Problem 25/sol1.py create mode 100644 project_euler/Problem 28/sol1.py create mode 100644 project_euler/Problem 29/solution.py create mode 100644 project_euler/Problem 36/sol1.py create mode 100644 project_euler/Problem 40/sol1.py create mode 100644 project_euler/Problem 48/sol1.py create mode 100644 project_euler/Problem 52/sol1.py create mode 100644 project_euler/Problem 53/sol1.py create mode 100644 project_euler/Problem 76/sol1.py create mode 100644 project_euler/README.md create mode 100644 simple_client_server/README.md create mode 100644 simple_client_server/client.py create mode 100644 simple_client_server/server.py create mode 100644 tags diff --git a/analysis/Compression_Analysis/PSNR.py b/analysis/Compression_Analysis/PSNR.py new file mode 100644 index 000000000000..1585c8cfc962 --- /dev/null +++ b/analysis/Compression_Analysis/PSNR.py @@ -0,0 +1,38 @@ +import numpy as np +import math +import cv2 + +def Representational(r,g,b): + return (0.299*r+0.287*g+0.114*b) + +def calculate(img): + b,g,r = cv2.split(img) + pixelAt = Representational(r,g,b) + return pixelAt + +def main(): + + #Loading images (orignal image and compressed image) + orignal_image = cv2.imread('orignal_image.png',1) + compressed_image = cv2.imread('compressed_image.png',1) + + #Getting image height and width + height,width = orignal_image.shape[:2] + + orignalPixelAt = calculate(orignal_image) + compressedPixelAt = calculate(compressed_image) + + diff = orignalPixelAt - compressedPixelAt + error = np.sum(np.abs(diff) ** 2) + + error = error/(height*width) + + #MSR = error_sum/(height*width) + PSNR = -(10*math.log10(error/(255*255))) + + print("PSNR value is {}".format(PSNR)) + + +if __name__ == '__main__': + main() + diff --git a/analysis/Compression_Analysis/compressed_image.png b/analysis/Compression_Analysis/compressed_image.png new file mode 100644 index 0000000000000000000000000000000000000000..75c41c21c7b579b88cedfbaee9ecaeb919db5683 GIT binary patch literal 26684 zcmV)GK)%0;P)&Hi*00004XF*Lt006O% z3;baP00001b5ch_0Itp)=>Px%%1}&HMMrQ<0}B8E0RRF900IaA2@(MS0RSX76#xPN zE=Lpr1poj8000620097iz5oIX009L64kG{p3jk%R3$M<51P}mywjO@I0NLQse!T_= zCIET42S1Sjf4%@om;gA708O0$NR9isr~?;s0PXMW`1tsm%sK*30JXKXrKY9&`ug(n^4HkZ)z#Ja_xF#SjPmmG|NsAI zuK-e@03?C{3?~2%b^rhY0D-^&P@ez_A^`sW{R$occ)9>>vjA$a0RR8~b+-TvD*#uc z02F!vWvu}D`T1R_09&R2aJ2vqF#sZy0PyhdEr|d(j{sq*0DQavJd*(S_V!1U02+P( z5I+F&^73-F00?scD3t&yhX5ad01r0+PMQDdVQ{3V3|K#TxltN`NS;vJ6w>gwt`hyXo`002M$=;-I&-Q75Z0N~%?6N&)T)YFQ` z056vSN1OoB(a}?#01-t1?d|OXO90g#0oK;m&CJaMR{#fW05pLBF?|5Oz`Y@T0NdN! z1!4e}%>c&6#ksn<+1c3<5D&@9$*-`kw6(MtWB?r=9>T)Hv%#zyZ2+jNrxIBJSdB^^ zb^w2$cPw=PDJLeNq@Vt11!j_4C2jzhr;`3p0E(o8bC+sXR#r%o8v}s=0+IlJety^~ zAcu&DMMXtsW@f6oqn)^mH8eDll96(9a{lx2`{3Ni*SUhRWBuyqP>(m2ymFbDnPHko z>Yhs^sXr&+M3o-skOU{M)v9)6Ubbbu&L4L_3;Rj0tc)_pfvRK-> zuTM_K?u8=3Bs9SH`Cag7R=sWQ-PebQ$40}@{lGv}d6FSEIjz zk1}xFH1mPTnIbqr>-*@pzbE*@1fR+e>Gg3j_fgs1 z(Acq1w3+bK*zjbmx1BOW=(BWOzN(6k@EbvVM0}_D1{kr6$LbQRO^>j%&?h9S{lx0B z*5wDv^Qp2UMGH0y!i$Xr0+U^xlqp7Sx}&oTY=gj4@r5ENzJhqdH-H8M9S?<9X?kjQ zaoVcCOJ*$HU93*tSvUL8G}m8Y$0EB-Xmo72Se)#!(&pHwfPv%Ebs|0;-)LMF-*Ml7 z6XJSBC@+9#SG%H~`l2f2a5_sEvVbOxzt&!GW;Euif6ym(xBAFf zBPJs{Jg9+e;QA%g)GcbJ!`d~mvGLQ+^YZ@mOd3`xpWENxFYgWb99(7RgVEsOO-#3& z=Xtx`E)omj-5yV`)9dxvQL=6v5nLDvS&9rO?t(O^x~n>*f6pF;z+lN>K0GVF)$kiW`ZOswM!CUNvYD%-2sQS+g_-^ z12WpGJkY*@FLvJTWMUp^rz)M?-62%3Il+uHr>=h!(Dn0yQbur?%toDf9Tra3Ub*gWHxkYs8BZ<}J z^|(D=D?{1rn)KpfOnF)K{cJJT)j^rXaN&e}L!dE0hv2HK24N}%HLaBP_ihAh`LQ*X ztUpnaC3)=bg*k)s@*>%e0VklXUbGdv(}qRtBm*HE!H!3Shj|k`!z|Ln;b9;p2wIUI zTPi&)W(1f@&+PAs!IQXZ^myzCU>v*$atoR?)u9GJ5T?gk6IaAw{9a5I9vKxUuUSn( z4;r`Q(|YW1$<-205i2h5)X4AjRU=8giorgB9f$1TIVb)P9D|;Lk(|xk8&icxqz8q| zn4@qJ-Vng6$F9nc;7cV+rJ1EYu^GNvl4=zLJsV|n6Lybyj^Y@aq(>sXa7?ueReB?4 zDO{K$9D~^K8qurpBkC*V^Rwk6!}!>08S@@+4v?aqq(OXlo1I86K0d4s*@&+gyVheUrj0VGrE) zE|jfo0G@_V6<>aKX=(ZF_{3nQuvgBdt8b?cGx`1U=FeY#{YP_!JvhVxNdV;;qNm}* z&Nq{LurmLgJtMwg?N!JGWq5e36~J(4f7_m6n>wJ$ISm zE+nxG>A3Vj9dcuQI=!Wb3#$gj#|HP(Xc0!PODX>chY8Z&N$FJ-*tB#ZFhk>8Sbb(l zd;l-n)Ymn9kcMVHF#$g4dQnNEM|2?zTZeCVw{fRRZb~mt=quU$^cIEo=d*5_dm!JNxIlRnX$^g7-?dE24P`pfw7p||Q6)v3F z_8T~SVf$bm*4p8=3bZc0RE6GR^U@6-%@~1K72nMC((LWl_FbBy2jEg;*aM^8Iyl4t zD{XJReYdv0{;n>*_ah6?Ynqso;=aLIMy~W!7-afe`M*Wg!c7r+M00Cfnn0A@cYyCr zoqdBE?IFEqi?7+HO-~Y}Go?u=-IbMRz80oOL*Wjp^dun)ybej}rqUpRZG2egl#lhj zXN%1mU+|~|gR~Ll$-4O9wO8imZ(CZpcENEIN=kJNFbfbH+Q#%Z-_Ht~6i=<)lk|2P zCcb2{Y^-iEFU!@&_i*l+uXTMF+a6RPsoXd3)(-&I;KOGFF`o0Q@ZcXxj=96@zaL%B1gH#_nQQAjb!W|m^2kZOMEMHtE;!^RuvzPX;Gx7 zXsB{8fZMCZZ~g!D|0jW^yDJD%{lJ2$0s{-sV;fHyQe%41IJVwEabvHAr1?hDZDV!y zX{$zNwA@@Yf%GJm-qLseub%jU-4p+dys)*l{`M4l((zHF3B4p+ZgRH{(&pjXu0VW$ zjU-J)r?>pLzg2w$>7{^PRHp~p`wG}0zi-l-_T+juHpmtWZV~{!Mu9JQBZrf8L&Jc+ zL3-@lf2_e;Km0#)=ljxDn#FNpcM$;sFw3%~EDU8~n7;1I?XbWu>@au(sDyiOL%1=4 z(0g?uc_mr~GZ>Mg1dCb1C_oSrHPT3spoTQr0849kM#o9Fh-E2*wG{jx_MG!P&;60y zBz9&Y_e5;~srt$HobP#_=jPn$Fw)yx-6`cgo4P=65$Ivz<8|-5v8UVcvHf(c$dkE! zu`3SMc;cdI6n1yZ_j_pXRjl{cHEYzLy^W0qa%0!1zN3pUYgv%qZ#_&e9%F@%d+q_G z?)KBCWB*K257V*Qa2)8GiMxB)OM7paFZGfhAUnGLEU- z8^_c|R_L(#gE5TftD^zz_a3y-v%%wzONm>o`FfSTa8G*FYXZD6@cHhMUqW_M|udQ+T>JJ^WEe*W24;x#jPn z4kfoIt8$596dWg z!}|U?*gepDc0SErBbWQjh&NLg`n|tpm$O`axUYw;D#pphWyc;%zz&ZYu8rB#q-TX8jRWo>4^eAu3hqZ=+^S_L7H-LTViPSy% z_WI!9n&3gVOgmH=(00MA^oG$kk}JvOtiTr-4Lso25(Mm*D>K~@ylfl1V!2kik`e}n z&qaVhFBa=3K0L>xigCnOYP8f?-|#X$_^od5rMJ>1J_eA<%>?IQ2f|xUrV$=<43J)} zUJa$9kh&JXaT`Em{D*9o(+hOnpW^r!LRjX;vaIaoc0EgPWayK7Z&tT9)8rhCAA4tz zJs3J-hr$DRo2=&Lc+qRL{5sGhcEBi4Pop^yK99^v9DxfN%Pf;}*+hpf~Kvy#0fmkY4388NdgP3-LpMg5fm1 zF$mFdc3>AMJSV)?+*(Kq8b%vG6QKNR9scuJKYXG+GB;jHdgWT}D=*R;85+JTM-xi) zcxMOjtt^847(YfZF2Heg%SlXH5FTW$Fbsg#*q@u5iKuP#kRMkdw97xF)5}>4U*HCB z24TnSg<`E<`^tmeGQKBP0d@)LY%f>ZK<{AuvRP;_2qJ!o0Xaa2vP&a7OLzj_{M_7x zuE>Tt3Wsc^i!?QAK*Wx0fUN0Y3zY1kG4ts)Kqb z*zv68@R~F_1J)X{J3jDW`Bi3%H>R>E23pBRrE=SI8+_=uK_!a# zr1yu%$Ax@meGB;^z-2^;0nd^qI$~!EkK7jEO@Z_#)u4>wu0NlAKexWc)yGUd&=K~I zav9;Vysb2|0UgSY>&^zRO7LiE zi$M=}fQIohlRy=rw(xp)o&w{Sp|aYTP)xil4IyBcC6%8_vaDbOh`(|vW3rZ5up6C zw8Pt?tko8Ur$tX>Hbtx%&(M}N5s5u-~CELSAf5G>t?A5K6bQ0UjEH`x#u{d*f))VoCOp(-G zZCn{N6Jo?e@P7SSg4hXy+EoSI4f*BrdBC-^K@S3FqT}qi@S0*ypBi-1;|XjhC`ZqZ z$=A15SYJf89;1iN*vXuDOH@2Wc>9fa2?K|@c;{>bTRqhT$0(+*8W6~Dw~#C4MU+Yw zIg=d;Z=S(}qL*lcXA!jUC6s!ZU{viOpK~nu`e=HUqpY} z#Fi+-*je!QtFNPmkEUIDrE7uL@KzMSsRAG|R8yhX)hgq62mp5r44V)gpaa>N?C1^1 z)?{#3fc69qs_*Jp81iIHXiJ-BkUMd2V(iK_0Z)?od-A;^KCp~0qN+U}^_5Cn!8_@q{^gRfMpt)`f5xJGwGU}LR=-T843mgT7aj@Q)^GbYQK zcJg*p=dOhDUqv=Y40%I*u*$%!l{x?-zKO3}93T>;fK~>+Hc)l@=oLbEcsGt{&=EZa z7*zDvBJTt4%{<@3<3+>1(*Ds}QxB@L&^GzlOP zM22W4_}8?WfbOBFcpyAUu_@2auM5DSGWpu_r9vTx-c+z`NvvUn@LG=mo;3}}6XF{g z`dm^Z2-PiUcH7j{0uXHN6GZ$oTCL`SAjjEFg76Xm&yAn#!jIIaMq2xfU#*PBw98GY zts5eEt!79|*x-Tq2H*4HK@dr=3qXygXaTB+Qwxt578V{qUV!HhO>~@HN(JFz;IP3C zg02DtehD@558@|$saz}zR}!`)*v-spQQ1DH&I1b$Y?L{=F8ria#6LqbQ6AF6;>OUe z`DZ6JNsTgij9dw!4$1^tlH`O3_y)e! z0#)}@N!28+CqOARIqRQbliWnCt<6mMLy^f8d^2vUQuG7Z>A`?c53SApy=gZl=1ZJh zlUWiTlsK=50o%PbSn=T+P3eH=!cWs9A^*h0%o@fFfJ1V+CQ)Y?wS|LU(A|UEk~f7@ zsORP##>A{?ex6<@=!NLIn6@ioKzsu|;TRqo{#24Q7QF#VT1uacL}oD}04}B3&^ZA1 z6rc;hAPr?@gGHwncudW++CX(xS6wg3f+qvrWM-)rdj%^3haY%Q7eVV{v zl;pQI*AY-x{gD*j(9&?bB+EC@j@co-n+VXk61GP>z(mgsmYo(tb8Tn+ZTt`&wUL~i z^O7vzz?&8{Tt!Q1+Q$QA8-Z#Me$oCOYzi<){30PySgEpxX2Fv1KD37Ok{FAh&o@Bd zfL`xYP0~8!W&$*KQtbdg%0g@<5C?iI;7yaWA&6NjtaJ-smqR(500-zBBSXKsr=%c$ zE(c?fBSM^v4L$2~ZZdSj5|cGJWV6Xwq{T=uD zJdY$xvLwrHt^3H|qB@1X`u==*KF{+=-U9r2To)46rI=(|PyzB>EUT)b-FZy`@-F-) z2@pQz_1m}vnN1qApQYr(L7FZ-lV1RUd@d)bikcPNldx?)Lcko%3HcE`pI)^4ikN)! zx8aw3SSeQ#TGkG5k_W@MiIbF)DhXai498F7!G&CoA2iK-;P|wn9rM8!=M8^;BeJk~ zEp|1VwZaP|KrWr;)sj0tPJgiioFKp)#}9aNf4h8;**F&g;*Al)79W-?)vT1wP79Dv z=hITD4EULLnqgpY%)KOE}PD+HYku?i5Lsjyua8++BPY0@+R z`E)d1P%Ewde!ti24Z$=pRULpyl3*$RtZu5epdF6Pi=xsW7}E3T1p3!7fG-}h>tbWi zl%A&m-90!4dttY0eNuF*}0*xqP#kmgF_o#?RG~$(7Zc22ubzAObZSGeSlwRoGsWIPS#B z5T7D2+%^^N%^>jX{IXs-*bb0S$D8rA)VG5omtO#0Kq2SZZH!aMXC9?3V>s1jawZ*9fXm~IN zKJeY)2cVb4q=7d)oUWJe1Q=q?g2i6eSQOw5&{Kd_As2;za&y4W;<`>7nqkjlI@k@M zV?H>Q53gpy20^t*@dXj!7(hMnaeB?to*iAA(2M5_6I-cn{46;*A6m^4S-5yTn=$eE z3eYtN>wzzB1ip6LWWm~Tw#Q)E&n7YDV%QrEKc-j_V|D`5srW}BT}~65$^fsJDUM8> zDg^3*uSo;n>lY&|ZJkYCH5qNd*{M&4Qz}YKF|%7kvQw2@oqjBk;XG zJv$=YUZ1>X@gk*^%?l(+u3)AUY>(bXG?VNK(o5%jTB*SzU|4+?>bSZUXA+>0qZJ>9 zy`$svgAQ> z-#XaaeR}`SLtN~>{p{h!&R(~B@O*21{TyhX!>R3V=lo!MXZN^!ez5y&=Nvr!tlN3P zt`3@*asw<#@b}xa^wCN8#~X|?>_nG*W~TS-Gh@(cW?WcO%i&}6*1F$a5#Yv zmwAuDn<|9>Kb_v-CPcgLs~;Zz@apYbvUja}+&SxXj=?%-XJ_QY@weZ0{`dCn+gIc& zCF?soJ73(papT^`_U_ixJL{+HFs{>^VNIKL7?%no@L{Q(RWq4M0DV1}#=SUXjhnp+ z-XHij*k9kgx%DO5zleQF?bfZ||NYN@g5!3*Ua82X|Nr>of4~2pY!}>n{%i~A<)cWC z8()&eO;rTu!`QqLS-5tAN*l7PI|ed?ozq zx(p3~rUsE;NmF~^x`kAMb6M3fT?zs5&A-RDyl|1pFoyZ?^An)NrQ%VX*yw}ukllzJ zeGqUBLw~w2*X{fWJ{82*A5eNR-&MVni@^NcCpRJs%a@9b)qo!}CBUr2<)bNX2(ppO z7S%NfJHt$xh^|q0>W|`6G!Wk~4b`%-gJvQ?IxRd7FrAuvy$l15kL~oP_F#C&t6~jORc@sm9`J?FH6^$PBE1akp6 zF$7Y0xY;uu>#=KxEwIha)}{>|Wk;DA*|uf|JtO%V_++I7;ZuO5Ypze7xUs=Yfb&ip z5LT}g6S^_edocY$;tIUh|K@Oizh#$Q%`~J|v%spD?-jyMFJ4IfS2=t<UYa?ruWNFOnTD`kPJ^$vC45`Wgl$QNe*8v`l2GM8=FS!=DS(>RqF7om0E8Y zfpnYJbw0Oy@HAnYZWM8b!kFF*oM6$M^W8X&v|Esc#}SF`%(h?$Q;u~Xl~|Akgh z#r?nb-3`G#*cismH*JuxmvGuN9*p!PK~O6Y@fwt!#aE4D&ZI+JvuwQ4(OlU^uhM88 z9{y*)RTA`_t~2h=Bnz~1b6voU!?a?;#e(KwIIU(RNt7F{&6-i?nc(UpD#Wl)0Eq!} zLs>t_fLJbDtd95&Te8UWPVu?%qBUZvun48J8 zBV#f6u>wJKvn1s>uL~r20(2CC`D;t_qlaOS@jw?S2a{77wN$1d%jT#f8oDV8Jcwz^ z$gCjEFPCerRz>9t{Cn(fMN7?dQWvYejDpgm$CLt8^%I(GO#u#YN~vTZg99HnBc}*O z10?yfT-&TELLtY$*Y;qJ$E3SF$FP?Hc#2Ny(G&t?GVsp87*ExR=NUoYrZ@{W-BbrX zW`xnuB>73M*C1~cxY&E`PtS3XbR)~lb4k27$W5U36gU#flpai_$7qT@Zdt(*772|} zN5AB5cBCrRvnFvIKid@&43N3sz`t^Nc^>>Q>?Hu(qY^}SKXx8fG`_;0P>*vV2OJX2 z7&n)K(PXG<#+R_cuRs~j$0qJi_c6WzN%uk|G7tGdJiWw#>@dld6O)Y@H-u*k{W`e> zC{D9*%S<*kV*xCRz)#CaF}DkJ1Olk9=D)VMu!Q&`9~@s~Vew<6_o%$tk{R!~=Lb;7 zr;M@#Mdw=Lc=YwbGUN#d8u>{SRaL~RGX=Xqf;&7RcJ<=I($WPgzYjj8@d;imMuri- zJR0pj^2O+cr|I}~aHFp{6j!A1L{H1;PwQlzSspe67H=9(OY&VF2_Wju}|2(vnF7`)7eQT&Yz@Khbu+FKuLN9Jgxg z)++n%z7nl1EWH4wluKXke=r6pVPLN?Bon|IyjcY9B_!hza}zP4M&!m&R7hOGbuDhS zy_d8QSXfwl(RPg%TLTw@fL7=qai8ZoXC^brWM-7vlh#I?+UC>m^W&WJobwPoRvUNw zS{LEF>22bNxiZAtyqYLhaxLD>=SF~j6TgPV`mS0LqgQ1aN-KF?KHgj6R(%>g+^7Fb zUso@~=c)K^c68nMtNFZ|D(z$?pLIla0N74<%9Sf(^vWz#T{(b&0PNcD>G0rt-x=t= z1^B!c9|aAd7tfX}2}^p8{91jXt~}qBYuc!&tqEUPE)>EeZsP`nM;<|vT8L4km@KCompug1z%b?(V52NrmLfT2#BI7v^x!X&X#T87 zgPomyw>&moJlW@0)qEU+M$YGQXtwKb=GOuc*RP~dI!*5yrnnvFpt-wLePj@jvkV;UeYqWI3^>!G<_Hu;U5|R8NcS) z8#mQEd}uL4Qj6*cT{;1tAco$+4ZQ+eFZ4aS+uu*7>mj|Kj)6E{DVxYtY(sAAw2XPb zsP%%N7N=v{1Z#x-Fpcl<>NMgbhuBo3k<8$KB+h^uQyutx>OU%B4V<5!e?I#B`8?og zxF9`R{D~F{_dKUZAs6gcohZ5qxhBI!aM64;nvYsbPw`1|vvDyQ@(}~+!N@lT`}4R; z5C%X;d>kJi@59*M+haBM_;VY3Z5*d<=%rb_1?jORv%)tw`YC7ejvg-V(QU>MG>VrC z8MGq6k`~<0rDbNtoYgQH`D0B3Ob_4ocb`E(8lWQvNFS?*hlh*QNeDX%VavcdY`93B znHClnS5~lB<-Tk~FC1cPt$f`v>Kp#5ztd}a-NX3^*lxD8m2v0x6aob7^5ieE{ms7` zBd*Ywna1E< zP_4!cd*Qs+PHjWS=RhwSiKzOLcT~Fn2WSsME)NzIM$fhE+8%p&42+JLaAieRN z%b<}z`f+`IbA?>d+8XQsYEX|Ekm>2nyd$2c*w5HBW@in35KWB`@obhYy+BxrWOA8= zwnwE5f7R)2)PO19-8dRk%Hn9`_$uF<|4rq~V+Jf_?1=8c}jP0Au7_iqDB2iLYGBMsTL4Ji1FWwQeuzT^}ZTxk}rm zQB%x7dW$O@-psVAL8~pGF=)u>09xw3W zUM|v0K&-iX(4tp;knz(yu4CNTk2!6q$%RC* zTuekI`Th;h_cb8hdkE5V9W+RLWValij!f$*&lv=RPLP6a83o%79pCgcGu{1|!x9w+ zj%=xvDkz2f{k>lAsr7UWOkgj_qRW{EYVXCy@*1|3f2zS_xPV_$AHm7VWYQXs{Seql z!qcRAFnsXWG4ai!0yHsh{Afs_^4lu3i#S2J?IJHL-C>GV+k|S za}Xa+cN2-I{Aj4dTXFXL=9wUhHc~U-q>qAd0`R!>kY7y@Mg?uK*XZDs(y1n*wwZ;|s0;9(bm(xSD``aYEwOMq3KQD`z zTO+HD0zqvL1m^K(YGmo)e#VFjf3 zc$LLV!4Zh_ z7fAA->CA@b^onH&8o0k0wZ^CK-{Je1QhN+jL&Y+uFT{kgrs>a;+8H7Q(|LcuzhVEtk_1u0Ri8QafEb zU8+NL0L!hS+heXy6oPdfWqa){FKkPGyatBj3$#6Pfb>F)o~JQ)U}7Bdm93PY9kU?$ z*}#iL{_aHY@#9T5^ngp1Qk2uGFp5zl37>IW6aSKB6~Os=O+ZQEK#> z&#~|$Xb3Nr`iC7o@gv^&>uk9q%Z(|l>aT@g!Rd`cUMzgT#M$Kbth5L8&L;3gpizA- zYgB1ov>O}jzJBex_w;sN6#-3LC48;)G>=D@nx($C)!rM-mK~!8{O-XC!o%r=VLma6 z@`LfAzSJBvtYhx#rwN#ZAwE5Jw62fRc@3n8hpjwZSq6Hy(d}(z=wzrGZ!ul&@ghuj zc#jX#TWdp46kZ@bC+&&25=C}DgGuw&`|5qr+P_r1LDafatS#U`)OHXqNo zd+s^+?8;LF2J}Go9={<6&S9-UcMlOhWho1Thxo2tzF_TMczZjc_lj6G9pR&$D!?z6 z0D5>Z-1Muone62;Vi7n~cr<~6(VR=;Ob(w)$>rVVvIq3OdqdCbWAs$AC+l{J0=-Mt z(|fhFI!)*$wDiO?Ly%t@@V%OzUNfP`af{93Jt>Xn)p?PN+dDiyX3DD!mN{8+Sw`=t zf4w1RT@Ow~L2R>^s4p5D9-D4L&#?_Iw>W2F z_aK&IxjT*M5y5=Bx3{-@xS@3$di3UIe)lFfef4FRA^&Ze`z4>5mb!nIcBt-aBZi8)Rz;DjuclX<(p+qiWX7|R6c6(Vcce!!8 ze{uL$Euth^CiyP!2aWJ)>CH~g=KuTXx{O?xUe7`FJn21ry}Fc+u>;Fux)C34XLR(~ zobFDyCmI_x^SP5v=>d>9^BW&IwPkaO>)qXBy``G8CCMsTGWFA5aSpGh?N8omg%{AX zwC=X8wUy9&ooLVt=|p_wiu#7S+FT zC7TchjF!inuGBDhEc%Xy!}v({hp20T#oF<%S~TVKcz)#M6PY|j&u!2gmO;#()Z`bL z=)UzwtLe3T{qW`Ls~8pMeI9>jpZ`TBqm?}u&Cf4Tdc=CUnn)ipZF|&r zxab|a59ES%Fio2p(gS=cfcO1XCrX&a&STu<2lPI_as>lE%jmVWe0`VFqi3roM7N=n zUnb3>fUmQ&p47@7d!Lx!+Zt)4-i>UZgUnqvcV2qyG|lTY{V^|`y~ zaSz&VU76yZ7rk}`z2US`%+kT`5PZ(lDPFrP6r4?lNruW^P6aYTylGy3?|))4z=4T* z*WuG+B7As}%_5{{VE2R+f!FDGdV2Lco#`64hqOr2l6jF{`}xt+S6$jHO7Qu>_6_aN zg5oIYB`fRYo$Ui0oVHcjtZi0H3%r^5;U*fX`Po3_l{UjSKD^%(a|PPPMnm=j{64^Q zb{U?JAy-`5A&K-Xw|jSCsX^$CG2zoy-A3NPXLI-ajx?Hkpc)+sS0&smNxF9^QDXUI z%nuxX*w7SWsOW`9jwn2F>!MkAE-dA&r`N{li4zIZdJ&%rUvHP&=Bx`!dX9>HYkhrw zzKEYiEJcIB&otM9u`zMI1Rp-xMSL4l5s*4_DvOrG!qL5}6rRm+CsHnYn8P}6dSgh> zP<6$t$K!Ml?DL>Kda@_Gn#mq+V7xt~DQ4Ucasa-@r6|Kt$zHgAuaChq>9eBrsOUvN z_P+W2^7+y`lkoX$ew(i@DD56!s_cNdTUn`;YnulPrR{1d)pTK;!54QNbd`4S85Wygm@~vvDcNI(=gQ455gA^6h7Ah8kGug zTsV~y;HfvrsWi`UXA-NF*<Cy{&gY`}@dd&}x6Ge}n zFb-bh&KK6wYgf|C5qf74K96(YG(&5?2npmI2t|wuMxdLHe~G1mzON= zFQw6Ph|tUBRKjNxwb+Jyb&Keo^`i9R!oI(+zw6hC9^GkW^epX=yr`g;QwA-@^agr8 z9Y=xRpPo1Ft@}IOPTQG9k4~~YM|wXt=;f4d*CcB3WPcWdzdt?h(C%WaUo*RhyLFGmJsG}qk$&;9*WP^$WN{T3KTKG>L^*AYCjb^6b! zx6#Jo?Sx+J%L^7Ch|zNUG0iPE=qZHHaJkz%@Qx2EtG>1G*0|Ncz$YtjH@?HgWRE+! z>U5t0o?P@ug-HbI-MC=s0ceEY+Uim|luk2xa(pKIfZpi|vQxZQbtGaEr0BlHVFZ$d z;{eawO!VaNsOZu9958#4*o~I+ptm|4VwDyI->{^+5#vJ%eI2K~H-Kx-%y=?PA*%Sr zz4FK8CD=jgrK^g$qy`^6P0a?SH85UYw5-yW8pos6YX(oDw=o&LzqVN1+uU;d;o8z_ zPIMbMf=}|h3ViBQ&V1SZRK9Xh-Q29L*ETDql|oW(&ESvGYIP>RSSl6r%ZsJzPUWDG zU#L_URw^rDpj5Tj@+y4r z%N-2eY_YNkNTkqjjrBXYXi%a+%oUmy(x~f64d!*jEba3Gv zxV=Ju2)|gVN#9W}gt3Jvcr;`dtw=S>EVsgjrBJ644L!Qqa`B?IXO7-}`0VA6;B*s| zUQUUxF|R=UkS^bT+5TtJ6e(Tn)qI%B6RqScO4-u+VzG!1FzW}*r?_SNI#%d96vYDE z9R9Rrd?b`=O5!`CNO#}o)=b(^HD;)c9;Ju%rJ+Y(Sx@i5vzMz&31;}F>HN$wmZYbZ z;Ms`kLfOk+uU82S5W3B>lvUoz(E4Mtv%6lTN>c13DA~#F3tri z?8RH<@)m7eEl+Z;!2UqL{-h15FGD=!mg#N7>`lb7UtMgqp57WMWFe|Mom1e8$!2(E z$%n`<`sCfn0f5+CEaXSRsls-Z$x4|dlmSW}IThDs+FdL&6bqn(6%f9ipuCfc!$O30 zdlkG&7>kMQwaUu!Y(N;Dn%RcmguONevNzzQRq#6VB3OxXj|v_$d)<*(_S(hv);5dj z-6e)kR9%E8Dz7+~YEU)@EtI9+6ujo_x#hn@g+X>T6o<$Ux(&*?^q450q zOrcO%o)p4DW@@=Gjwz`H_~!K=WU5Qr6+oS_WE>w97gCbX_Ab{@Dg*KTjxMPPUNty^ z-1Vv8B_ze|9*o_;cCpQRdT96*sw>*QG#kbGD8xRc@m>>8PDjI4c#j8O&MO8g!Bn9J z_~4dtl99Q z9F3;7o9$EvhJc^m!!V+5v9yUnCq$%Xx(OB&M ztqYÐ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 XP;3!Jj7l4S00000NkvXXu0mjfCMpBg literal 0 HcmV?d00001 diff --git a/analysis/Compression_Analysis/example_image.jpg b/analysis/Compression_Analysis/example_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a58ba3a8c338e08940c4494b4edd799294323e3 GIT binary patch literal 29986 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 z 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�Sb5QGN=^8)xhfJ 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 literal 0 HcmV?d00001 diff --git a/arithmetic_analysis/bisection.py b/arithmetic_analysis/bisection.py new file mode 100644 index 000000000000..a6af547db03b --- /dev/null +++ b/arithmetic_analysis/bisection.py @@ -0,0 +1,33 @@ +import math + + +def bisection(function, a, b): # finds where the function becomes 0 in [a,b] using bolzano + + start = a + end = b + if function(a) == 0: # one of the a or b is a root for the function + 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, + # then his algorithm can't find the root + print("couldn't find root in [a,b]") + return + else: + mid = (start + end) / 2 + while abs(start - mid) > 0.0000001: # until we achieve precise equals to 10^-7 + if function(mid) == 0: + return mid + elif function(mid) * function(start) < 0: + end = mid + else: + start = mid + mid = (start + end) / 2 + return mid + + +def f(x): + return math.pow(x, 3) - 2*x - 5 + + +print(bisection(f, 1, 1000)) diff --git a/arithmetic_analysis/intersection.py b/arithmetic_analysis/intersection.py new file mode 100644 index 000000000000..22c50f2ecafd --- /dev/null +++ b/arithmetic_analysis/intersection.py @@ -0,0 +1,16 @@ +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 + 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)<0.00001 : + return x_n2 + x_n=x_n1 + x_n1=x_n2 + +def f(x): + return math.pow(x,3)-2*x-5 + +print(intersection(f,3,3.5)) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py new file mode 100644 index 000000000000..da05fb65d0ea --- /dev/null +++ b/arithmetic_analysis/lu_decomposition.py @@ -0,0 +1,34 @@ +import numpy + +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: + 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 + + + + + + + +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 new file mode 100644 index 000000000000..c3d5efb47d01 --- /dev/null +++ b/arithmetic_analysis/newton_method.py @@ -0,0 +1,15 @@ +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)<0.00001: + return x_n1 + x_n=x_n1 + +def f(x): + return (x**3)-2*x-5 + +def f1(x): + return 3*(x**2)-2 + +print(newton(f,f1,3)) diff --git a/arithmetic_analysis/newton_raphson_method.py b/arithmetic_analysis/newton_raphson_method.py new file mode 100644 index 000000000000..5e7e2f930abc --- /dev/null +++ b/arithmetic_analysis/newton_raphson_method.py @@ -0,0 +1,36 @@ +# Implementing Newton Raphson method in Python +# Author: Haseeb + +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)) + + + + diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py new file mode 100644 index 000000000000..ff2df5117ce9 --- /dev/null +++ b/boolean_algebra/quine_mc_cluskey.py @@ -0,0 +1,116 @@ +def compare_string(string1, string2): + 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): + 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): + 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): + 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): + 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): + 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(raw_input("Enter the no. of variables\n")) + minterms = [int(x) for x in raw_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__': + main() \ No newline at end of file diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py new file mode 100644 index 000000000000..8582249c8d27 --- /dev/null +++ b/ciphers/brute_force_caesar_cipher.py @@ -0,0 +1,54 @@ +from __future__ import print_function +def decrypt(message): + """ + >>> decrypt('TMDETUX PMDVU') + Decryption using Key #0: TMDETUX PMDVU + Decryption using Key #1: SLCDSTW OLCUT + Decryption using Key #2: RKBCRSV NKBTS + Decryption using Key #3: QJABQRU MJASR + Decryption using Key #4: PIZAPQT LIZRQ + Decryption using Key #5: OHYZOPS KHYQP + Decryption using Key #6: NGXYNOR JGXPO + Decryption using Key #7: MFWXMNQ IFWON + Decryption using Key #8: LEVWLMP HEVNM + Decryption using Key #9: KDUVKLO GDUML + Decryption using Key #10: JCTUJKN FCTLK + Decryption using Key #11: IBSTIJM EBSKJ + Decryption using Key #12: HARSHIL DARJI + Decryption using Key #13: GZQRGHK CZQIH + Decryption using Key #14: FYPQFGJ BYPHG + Decryption using Key #15: EXOPEFI AXOGF + Decryption using Key #16: DWNODEH ZWNFE + Decryption using Key #17: CVMNCDG YVMED + Decryption using Key #18: BULMBCF XULDC + Decryption using Key #19: ATKLABE WTKCB + Decryption using Key #20: ZSJKZAD VSJBA + Decryption using Key #21: YRIJYZC URIAZ + Decryption using Key #22: XQHIXYB TQHZY + Decryption using Key #23: WPGHWXA SPGYX + Decryption using Key #24: VOFGVWZ ROFXW + Decryption using Key #25: UNEFUVY QNEWV + """ + LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + for key in range(len(LETTERS)): + translated = "" + for symbol in message: + if symbol in LETTERS: + num = LETTERS.find(symbol) + num = num - key + if num < 0: + num = num + len(LETTERS) + translated = translated + LETTERS[num] + else: + translated = translated + symbol + print("Decryption using Key #%s: %s" % (key, translated)) + +def main(): + message = raw_input("Encrypted message: ") + message = message.upper() + decrypt(message) + +if __name__ == '__main__': + import doctest + doctest.testmod() + main() diff --git a/ciphers/onepad_cipher.py b/ciphers/onepad_cipher.py new file mode 100644 index 000000000000..7e1be5fdc077 --- /dev/null +++ b/ciphers/onepad_cipher.py @@ -0,0 +1,32 @@ +from __future__ import print_function + +import random + + +class Onepad: + def encrypt(self, text): + '''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 + cipher.append(c) + key.append(k) + return cipher, key + + def decrypt(self, cipher, key): + '''Function to decrypt text using psedo-random numbers.''' + plain = [] + for i in range(len(key)): + p = (cipher[i]-(key[i])**2)/key[i] + plain.append(chr(p)) + plain = ''.join([i for i in plain]) + return plain + + +if __name__ == '__main__': + c, k = Onepad().encrypt('Hello') + print(c, k) + print(Onepad().decrypt(c, k)) diff --git a/ciphers/prehistoric_men.txt b/ciphers/prehistoric_men.txt new file mode 100644 index 000000000000..86c4de821bfc --- /dev/null +++ b/ciphers/prehistoric_men.txt @@ -0,0 +1,7193 @@ +The Project Gutenberg eBook, Prehistoric Men, by Robert J. (Robert John) +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 +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 +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. + + +Title: Prehistoric Men +Author: Robert J. (Robert John) Braidwood +Release Date: July 28, 2016 [eBook #52664] +Language: English +Character set encoding: UTF-8 + + +***START OF THE PROJECT GUTENBERG EBOOK PREHISTORIC MEN*** + + +E-text prepared by Stephen Hutcheson, Dave Morgan, Charlie Howard, and the +Online Distributed Proofreading Team (http://www.pgdp.net) + + + +Note: Project Gutenberg also has an HTML version of this + file which includes the original illustrations. + See 52664-h.htm or 52664-h.zip: + (http://www.gutenberg.org/files/52664/52664-h/52664-h.htm) + or + (http://www.gutenberg.org/files/52664/52664-h.zip) + + +Transcriber's note: + + Some characters might not display in this UTF-8 text + version. If so, the reader should consult the HTML + version referred to above. One example of this might + occur in the second paragraph under "Choppers and + Adze-like Tools", page 46, which contains the phrase + �an adze cutting edge is ? shaped�. The symbol before + �shaped� looks like a sharply-italicized sans-serif �L�. + Devices that cannot display that symbol may substitute + a question mark, a square, or other symbol. + + +PREHISTORIC MEN + +by + +ROBERT J. BRAIDWOOD + +Research Associate, Old World Prehistory + +Professor +Oriental Institute and Department of Anthropology +University of Chicago + +Drawings by Susan T. Richert + + +[Illustration] + +Chicago Natural History Museum +Popular Series +Anthropology, Number 37 + +Third Edition Issued in Co-operation with +The Oriental Institute, The University of Chicago + +Edited by Lillian A. Ross + +Printed in the United States of America +by Chicago Natural History Museum Press + +Copyright 1948, 1951, and 1957 by Chicago Natural History Museum + +First edition 1948 +Second edition 1951 +Third edition 1957 +Fourth edition 1959 + + +Preface + +[Illustration] + + +Like the writing of most professional archeologists, mine has been +confined to so-called learned papers. Good, bad, or indifferent, these +papers were in a jargon that only my colleagues and a few advanced +students could understand. Hence, when I was asked to do this little +book, I soon found it extremely difficult to say what I meant in simple +fashion. The style is new to me, but I hope the reader will not find it +forced or pedantic; at least I have done my very best to tell the story +simply and clearly. + +Many friends have aided in the preparation of the book. The whimsical +charm of Miss Susan Richert�s illustrations add enormously to the +spirit I wanted. She gave freely of her own time on the drawings and +in planning the book with me. My colleagues at the University of +Chicago, especially Professor Wilton M. Krogman (now of the University +of Pennsylvania), and also Mrs. Linda Braidwood, Associate of the +Oriental Institute, and Professors Fay-Cooper Cole and Sol Tax, of +the Department of Anthropology, gave me counsel in matters bearing on +their special fields, and the Department of Anthropology bore some of +the expense of the illustrations. From Mrs. Irma Hunter and Mr. Arnold +Maremont, who are not archeologists at all and have only an intelligent +layman�s notion of archeology, I had sound advice on how best to tell +the story. I am deeply indebted to all these friends. + +While I was preparing the second edition, I had the great fortune +to be able to rework the third chapter with Professor Sherwood L. +Washburn, now of the Department of Anthropology of the University of +California, and the fourth, fifth, and sixth chapters with Professor +Hallum L. Movius, Jr., of the Peabody Museum, Harvard University. The +book has gained greatly in accuracy thereby. In matters of dating, +Professor Movius and the indications of Professor W. F. Libby�s Carbon +14 chronology project have both encouraged me to choose the lowest +dates now current for the events of the Pleistocene Ice Age. There is +still no certain way of fixing a direct chronology for most of the +Pleistocene, but Professor Libby�s method appears very promising for +its end range and for proto-historic dates. In any case, this book +names �periods,� and new dates may be written in against mine, if new +and better dating systems appear. + +I wish to thank Dr. Clifford C. Gregg, Director of Chicago Natural +History Museum, for the opportunity to publish this book. My old +friend, Dr. Paul S. Martin, Chief Curator in the Department of +Anthropology, asked me to undertake the job and inspired me to complete +it. I am also indebted to Miss Lillian A. Ross, Associate Editor of +Scientific Publications, and to Mr. George I. Quimby, Curator of +Exhibits in Anthropology, for all the time they have given me in +getting the manuscript into proper shape. + + ROBERT J. BRAIDWOOD + _June 15, 1950_ + + + + +Preface to the Third Edition + + +In preparing the enlarged third edition, many of the above mentioned +friends have again helped me. I have picked the brains of Professor F. +Clark Howell of the Department of Anthropology of the University of +Chicago in reworking the earlier chapters, and he was very patient in +the matter, which I sincerely appreciate. + +All of Mrs. Susan Richert Allen�s original drawings appear, but a few +necessary corrections have been made in some of the charts and some new +drawings have been added by Mr. John Pfiffner, Staff Artist, Chicago +Natural History Museum. + + ROBERT J. BRAIDWOOD + _March 1, 1959_ + + + + +Contents + + + PAGE + How We Learn about Prehistoric Men 7 + + The Changing World in Which Prehistoric Men Lived 17 + + Prehistoric Men Themselves 22 + + Cultural Beginnings 38 + + More Evidence of Culture 56 + + Early Moderns 70 + + End and Prelude 92 + + The First Revolution 121 + + The Conquest of Civilization 144 + + End of Prehistory 162 + + Summary 176 + + List of Books 180 + + Index 184 + + + + +HOW WE LEARN about Prehistoric Men + +[Illustration] + + +Prehistory means the time before written history began. Actually, more +than 99 per cent of man�s story is prehistory. Man is at least half a +million years old, but he did not begin to write history (or to write +anything) until about 5,000 years ago. + +The men who lived in prehistoric times left us no history books, but +they did unintentionally leave a record of their presence and their way +of life. This record is studied and interpreted by different kinds of +scientists. + + +SCIENTISTS WHO FIND OUT ABOUT PREHISTORIC MEN + +The scientists who study the bones and teeth and any other parts +they find of the bodies of prehistoric men, are called _physical +anthropologists_. Physical anthropologists are trained, much like +doctors, to know all about the human body. They study living people, +too; they know more about the biological facts of human �races� than +anybody else. If the police find a badly decayed body in a trunk, +they ask a physical anthropologist to tell them what the person +originally looked like. The physical anthropologists who specialize in +prehistoric men work with fossils, so they are sometimes called _human +paleontologists_. + + +ARCHEOLOGISTS + +There is a kind of scientist who studies the things that prehistoric +men made and did. Such a scientist is called an _archeologist_. It is +the archeologist�s business to look for the stone and metal tools, the +pottery, the graves, and the caves or huts of the men who lived before +history began. + +But there is more to archeology than just looking for things. In +Professor V. Gordon Childe�s words, archeology �furnishes a sort of +history of human activity, provided always that the actions have +produced concrete results and left recognizable material traces.� You +will see that there are at least three points in what Childe says: + + 1. The archeologists have to find the traces of things left behind by + ancient man, and + + 2. Only a few objects may be found, for most of these were probably + too soft or too breakable to last through the years. However, + + 3. The archeologist must use whatever he can find to tell a story--to + make a �sort of history�--from the objects and living-places and + graves that have escaped destruction. + +What I mean is this: Let us say you are walking through a dump yard, +and you find a rusty old spark plug. If you want to think about what +the spark plug means, you quickly remember that it is a part of an +automobile motor. This tells you something about the man who threw +the spark plug on the dump. He either had an automobile, or he knew +or lived near someone who did. He can�t have lived so very long ago, +you�ll remember, because spark plugs and automobiles are only about +sixty years old. + +When you think about the old spark plug in this way you have +just been making the beginnings of what we call an archeological +_interpretation_; you have been making the spark plug tell a story. +It is the same way with the man-made things we archeologists find +and put in museums. Usually, only a few of these objects are pretty +to look at; but each of them has some sort of story to tell. Making +the interpretation of his finds is the most important part of the +archeologist�s job. It is the way he gets at the �sort of history of +human activity� which is expected of archeology. + + +SOME OTHER SCIENTISTS + +There are many other scientists who help the archeologist and the +physical anthropologist find out about prehistoric men. The geologists +help us tell the age of the rocks or caves or gravel beds in which +human bones or man-made objects are found. There are other scientists +with names which all begin with �paleo� (the Greek word for �old�). The +_paleontologists_ study fossil animals. There are also, for example, +such scientists as _paleobotanists_ and _paleoclimatologists_, who +study ancient plants and climates. These scientists help us to know +the kinds of animals and plants that were living in prehistoric times +and so could be used for food by ancient man; what the weather was +like; and whether there were glaciers. Also, when I tell you that +prehistoric men did not appear until long after the great dinosaurs had +disappeared, I go on the say-so of the paleontologists. They know that +fossils of men and of dinosaurs are not found in the same geological +period. The dinosaur fossils come in early periods, the fossils of men +much later. + +Since World War II even the atomic scientists have been helping the +archeologists. By testing the amount of radioactivity left in charcoal, +wood, or other vegetable matter obtained from archeological sites, they +have been able to date the sites. Shell has been used also, and even +the hair of Egyptian mummies. The dates of geological and climatic +events have also been discovered. Some of this work has been done from +drillings taken from the bottom of the sea. + +This dating by radioactivity has considerably shortened the dates which +the archeologists used to give. If you find that some of the dates +I give here are more recent than the dates you see in other books +on prehistory, it is because I am using one of the new lower dating +systems. + +[Illustration: RADIOCARBON CHART + +The rate of disappearance of radioactivity as time passes.[1]] + + [1] It is important that the limitations of the radioactive carbon + �dating� system be held in mind. As the statistics involved in + the system are used, there are two chances in three that the + �date� of the sample falls within the range given as plus or + minus an added number of years. For example, the �date� for the + Jarmo village (see chart), given as 6750 � 200 B.C., really + means that there are only two chances in three that the real + date of the charcoal sampled fell between 6950 and 6550 B.C. + We have also begun to suspect that there are ways in which the + samples themselves may have become �contaminated,� either on + the early or on the late side. We now tend to be suspicious of + single radioactive carbon determinations, or of determinations + from one site alone. But as a fabric of consistent + determinations for several or more sites of one archeological + period, we gain confidence in the �dates.� + + +HOW THE SCIENTISTS FIND OUT + +So far, this chapter has been mainly about the people who find out +about prehistoric men. We also need a word about _how_ they find out. + +All our finds came by accident until about a hundred years ago. Men +digging wells, or digging in caves for fertilizer, often turned up +ancient swords or pots or stone arrowheads. People also found some odd +pieces of stone that didn�t look like natural forms, but they also +didn�t look like any known tool. As a result, the people who found them +gave them queer names; for example, �thunderbolts.� The people thought +the strange stones came to earth as bolts of lightning. We know now +that these strange stones were prehistoric stone tools. + +Many important finds still come to us by accident. In 1935, a British +dentist, A. T. Marston, found the first of two fragments of a very +important fossil human skull, in a gravel pit at Swanscombe, on the +River Thames, England. He had to wait nine months, until the face of +the gravel pit had been dug eight yards farther back, before the second +fragment appeared. They fitted! Then, twenty years later, still another +piece appeared. In 1928 workmen who were blasting out rock for the +breakwater in the port of Haifa began to notice flint tools. Thus the +story of cave men on Mount Carmel, in Palestine, began to be known. + +Planned archeological digging is only about a century old. Even before +this, however, a few men realized the significance of objects they dug +from the ground; one of these early archeologists was our own Thomas +Jefferson. The first real mound-digger was a German grocer�s clerk, +Heinrich Schliemann. Schliemann made a fortune as a merchant, first +in Europe and then in the California gold-rush of 1849. He became an +American citizen. Then he retired and had both money and time to test +an old idea of his. He believed that the heroes of ancient Troy and +Mycenae were once real Trojans and Greeks. He proved it by going to +Turkey and Greece and digging up the remains of both cities. + +Schliemann had the great good fortune to find rich and spectacular +treasures, and he also had the common sense to keep notes and make +descriptions of what he found. He proved beyond doubt that many ancient +city mounds can be _stratified_. This means that there may be the +remains of many towns in a mound, one above another, like layers in a +cake. + +You might like to have an idea of how mounds come to be in layers. +The original settlers may have chosen the spot because it had a good +spring and there were good fertile lands nearby, or perhaps because +it was close to some road or river or harbor. These settlers probably +built their town of stone and mud-brick. Finally, something would have +happened to the town--a flood, or a burning, or a raid by enemies--and +the walls of the houses would have fallen in or would have melted down +as mud in the rain. Nothing would have remained but the mud and debris +of a low mound of _one_ layer. + +The second settlers would have wanted the spot for the same reasons +the first settlers did--good water, land, and roads. Also, the second +settlers would have found a nice low mound to build their houses on, +a protection from floods. But again, something would finally have +happened to the second town, and the walls of _its_ houses would have +come tumbling down. This makes the _second_ layer. And so on.... + +In Syria I once had the good fortune to dig on a large mound that had +no less than fifteen layers. Also, most of the layers were thick, and +there were signs of rebuilding and repairs within each layer. The mound +was more than a hundred feet high. In each layer, the building material +used had been a soft, unbaked mud-brick, and most of the debris +consisted of fallen or rain-melted mud from these mud-bricks. + +This idea of _stratification_, like the cake layers, was already a +familiar one to the geologists by Schliemann�s time. They could show +that their lowest layer of rock was oldest or earliest, and that the +overlying layers became more recent as one moved upward. Schliemann�s +digging proved the same thing at Troy. His first (lowest and earliest) +city had at least nine layers above it; he thought that the second +layer contained the remains of Homer�s Troy. We now know that Homeric +Troy was layer VIIa from the bottom; also, we count eleven layers or +sub-layers in total. + +Schliemann�s work marks the beginnings of modern archeology. Scholars +soon set out to dig on ancient sites, from Egypt to Central America. + + +ARCHEOLOGICAL INFORMATION + +As time went on, the study of archeological materials--found either +by accident or by digging on purpose--began to show certain things. +Archeologists began to get ideas as to the kinds of objects that +belonged together. If you compared a mail-order catalogue of 1890 with +one of today, you would see a lot of differences. If you really studied +the two catalogues hard, you would also begin to see that certain +objects �go together.� Horseshoes and metal buggy tires and pieces of +harness would begin to fit into a picture with certain kinds of coal +stoves and furniture and china dishes and kerosene lamps. Our friend +the spark plug, and radios and electric refrigerators and light bulbs +would fit into a picture with different kinds of furniture and dishes +and tools. You won�t be old enough to remember the kind of hats that +women wore in 1890, but you�ve probably seen pictures of them, and you +know very well they couldn�t be worn with the fashions of today. + +This is one of the ways that archeologists study their materials. +The various tools and weapons and jewelry, the pottery, the kinds +of houses, and even the ways of burying the dead tend to fit into +pictures. Some archeologists call all of the things that go together to +make such a picture an _assemblage_. The assemblage of the first layer +of Schliemann�s Troy was as different from that of the seventh layer as +our 1900 mail-order catalogue is from the one of today. + +The archeologists who came after Schliemann began to notice other +things and to compare them with occurrences in modern times. The +idea that people will buy better mousetraps goes back into very +ancient times. Today, if we make good automobiles or radios, we can +sell some of them in Turkey or even in Timbuktu. This means that a +few present-day types of American automobiles and radios form part +of present-day �assemblages� in both Turkey and Timbuktu. The total +present-day �assemblage� of Turkey is quite different from that of +Timbuktu or that of America, but they have at least some automobiles +and some radios in common. + +Now these automobiles and radios will eventually wear out. Let us +suppose we could go to some remote part of Turkey or to Timbuktu in a +dream. We don�t know what the date is, in our dream, but we see all +sorts of strange things and ways of living in both places. Nobody +tells us what the date is. But suddenly we see a 1936 Ford; so we +know that in our dream it has to be at least the year 1936, and only +as many years after that as we could reasonably expect a Ford to keep +in running order. The Ford would probably break down in twenty years� +time, so the Turkish or Timbuktu �assemblage� we�re seeing in our dream +has to date at about A.D. 1936-56. + +Archeologists not only �date� their ancient materials in this way; they +also see over what distances and between which peoples trading was +done. It turns out that there was a good deal of trading in ancient +times, probably all on a barter and exchange basis. + + +EVERYTHING BEGINS TO FIT TOGETHER + +Now we need to pull these ideas all together and see the complicated +structure the archeologists can build with their materials. + +Even the earliest archeologists soon found that there was a very long +range of prehistoric time which would yield only very simple things. +For this very long early part of prehistory, there was little to be +found but the flint tools which wandering, hunting and gathering +people made, and the bones of the wild animals they ate. Toward the +end of prehistoric time there was a general settling down with the +coming of agriculture, and all sorts of new things began to be made. +Archeologists soon got a general notion of what ought to appear with +what. Thus, it would upset a French prehistorian digging at the bottom +of a very early cave if he found a fine bronze sword, just as much as +it would upset him if he found a beer bottle. The people of his very +early cave layer simply could not have made bronze swords, which came +later, just as do beer bottles. Some accidental disturbance of the +layers of his cave must have happened. + +With any luck, archeologists do their digging in a layered, stratified +site. They find the remains of everything that would last through +time, in several different layers. They know that the assemblage in +the bottom layer was laid down earlier than the assemblage in the next +layer above, and so on up to the topmost layer, which is the latest. +They look at the results of other �digs� and find that some other +archeologist 900 miles away has found ax-heads in his lowest layer, +exactly like the ax-heads of their fifth layer. This means that their +fifth layer must have been lived in at about the same time as was the +first layer in the site 200 miles away. It also may mean that the +people who lived in the two layers knew and traded with each other. Or +it could mean that they didn�t necessarily know each other, but simply +that both traded with a third group at about the same time. + +You can see that the more we dig and find, the more clearly the main +facts begin to stand out. We begin to be more sure of which people +lived at the same time, which earlier and which later. We begin to +know who traded with whom, and which peoples seemed to live off by +themselves. We begin to find enough skeletons in burials so that the +physical anthropologists can tell us what the people looked like. We +get animal bones, and a paleontologist may tell us they are all bones +of wild animals; or he may tell us that some or most of the bones are +those of domesticated animals, for instance, sheep or cattle, and +therefore the people must have kept herds. + +More important than anything else--as our structure grows more +complicated and our materials increase--is the fact that �a sort +of history of human activity� does begin to appear. The habits or +traditions that men formed in the making of their tools and in the +ways they did things, begin to stand out for us. How characteristic +were these habits and traditions? What areas did they spread over? +How long did they last? We watch the different tools and the traces +of the way things were done--how the burials were arranged, what +the living-places were like, and so on. We wonder about the people +themselves, for the traces of habits and traditions are useful to us +only as clues to the men who once had them. So we ask the physical +anthropologists about the skeletons that we found in the burials. The +physical anthropologists tell us about the anatomy and the similarities +and differences which the skeletons show when compared with other +skeletons. The physical anthropologists are even working on a +method--chemical tests of the bones--that will enable them to discover +what the blood-type may have been. One thing is sure. We have never +found a group of skeletons so absolutely similar among themselves--so +cast from a single mould, so to speak--that we could claim to have a +�pure� race. I am sure we never shall. + +We become particularly interested in any signs of change--when new +materials and tool types and ways of doing things replace old ones. We +watch for signs of social change and progress in one way or another. + +We must do all this without one word of written history to aid us. +Everything we are concerned with goes back to the time _before_ men +learned to write. That is the prehistorian�s job--to find out what +happened before history began. + + + + +THE CHANGING WORLD in which Prehistoric Men Lived + +[Illustration] + + +Mankind, we�ll say, is at least a half million years old. It is very +hard to understand how long a time half a million years really is. +If we were to compare this whole length of time to one day, we�d get +something like this: The present time is midnight, and Jesus was +born just five minutes and thirty-six seconds ago. Earliest history +began less than fifteen minutes ago. Everything before 11:45 was in +prehistoric time. + +Or maybe we can grasp the length of time better in terms of +generations. As you know, primitive peoples tend to marry and have +children rather early in life. So suppose we say that twenty years +will make an average generation. At this rate there would be 25,000 +generations in a half-million years. But our United States is much less +than ten generations old, twenty-five generations take us back before +the time of Columbus, Julius Caesar was alive just 100 generations ago, +David was king of Israel less than 150 generations ago, 250 generations +take us back to the beginning of written history. And there were 24,750 +generations of men before written history began! + +I should probably tell you that there is a new method of prehistoric +dating which would cut the earliest dates in my reckoning almost +in half. Dr. Cesare Emiliani, combining radioactive (C14) and +chemical (oxygen isotope) methods in the study of deep-sea borings, +has developed a system which would lower the total range of human +prehistory to about 300,000 years. The system is still too new to have +had general examination and testing. Hence, I have not used it in this +book; it would mainly affect the dates earlier than 25,000 years ago. + + +CHANGES IN ENVIRONMENT + +The earth probably hasn�t changed much in the last 5,000 years (250 +generations). Men have built things on its surface and dug into it and +drawn boundaries on maps of it, but the places where rivers, lakes, +seas, and mountains now stand have changed very little. + +In earlier times the earth looked very different. Geologists call the +last great geological period the _Pleistocene_. It began somewhere +between a half million and a million years ago, and was a time of great +changes. Sometimes we call it the Ice Age, for in the Pleistocene +there were at least three or four times when large areas of earth +were covered with glaciers. The reason for my uncertainty is that +while there seem to have been four major mountain or alpine phases of +glaciation, there may only have been three general continental phases +in the Old World.[2] + + [2] This is a complicated affair and I do not want to bother you + with its details. Both the alpine and the continental ice sheets + seem to have had minor fluctuations during their _main_ phases, + and the advances of the later phases destroyed many of the + traces of the earlier phases. The general textbooks have tended + to follow the names and numbers established for the Alps early + in this century by two German geologists. I will not bother you + with the names, but there were _four_ major phases. It is the + second of these alpine phases which seems to fit the traces of + the earliest of the great continental glaciations. In this book, + I will use the four-part system, since it is the most familiar, + but will add the word _alpine_ so you may remember to make the + transition to the continental system if you wish to do so. + +Glaciers are great sheets of ice, sometimes over a thousand feet +thick, which are now known only in Greenland and Antarctica and in +high mountains. During several of the glacial periods in the Ice Age, +the glaciers covered most of Canada and the northern United States and +reached down to southern England and France in Europe. Smaller ice +sheets sat like caps on the Rockies, the Alps, and the Himalayas. The +continental glaciation only happened north of the equator, however, so +remember that �Ice Age� is only half true. + +As you know, the amount of water on and about the earth does not vary. +These large glaciers contained millions of tons of water frozen into +ice. Because so much water was frozen and contained in the glaciers, +the water level of lakes and oceans was lowered. Flooded areas were +drained and appeared as dry land. There were times in the Ice Age when +there was no English Channel, so that England was not an island, and a +land bridge at the Dardanelles probably divided the Mediterranean from +the Black Sea. + +A very important thing for people living during the time of a +glaciation was the region adjacent to the glacier. They could not, of +course, live on the ice itself. The questions would be how close could +they live to it, and how would they have had to change their way of +life to do so. + + +GLACIERS CHANGE THE WEATHER + +Great sheets of ice change the weather. When the front of a glacier +stood at Milwaukee, the weather must have been bitterly cold in +Chicago. The climate of the whole world would have been different, and +you can see how animals and men would have been forced to move from one +place to another in search of food and warmth. + +On the other hand, it looks as if only a minor proportion of the whole +Ice Age was really taken up by times of glaciation. In between came +the _interglacial_ periods. During these times the climate around +Chicago was as warm as it is now, and sometimes even warmer. It may +interest you to know that the last great glacier melted away less than +10,000 years ago. Professor Ernst Antevs thinks we may be living in an +interglacial period and that the Ice Age may not be over yet. So if you +want to make a killing in real estate for your several hundred times +great-grandchildren, you might buy some land in the Arizona desert or +the Sahara. + +We do not yet know just why the glaciers appeared and disappeared, as +they did. It surely had something to do with an increase in rainfall +and a fall in temperature. It probably also had to do with a general +tendency for the land to rise at the beginning of the Pleistocene. We +know there was some mountain-building at that time. Hence, rain-bearing +winds nourished the rising and cooler uplands with snow. An increase +in all three of these factors--if they came together--would only have +needed to be slight. But exactly why this happened we do not know. + +The reason I tell you about the glaciers is simply to remind you of the +changing world in which prehistoric men lived. Their surroundings--the +animals and plants they used for food, and the weather they had to +protect themselves from--were always changing. On the other hand, this +change happened over so long a period of time and was so slow that +individual people could not have noticed it. Glaciers, about which they +probably knew nothing, moved in hundreds of miles to the north of them. +The people must simply have wandered ever more southward in search +of the plants and animals on which they lived. Or some men may have +stayed where they were and learned to hunt different animals and eat +different foods. Prehistoric men had to keep adapting themselves to new +environments and those who were most adaptive were most successful. + + +OTHER CHANGES + +Changes took place in the men themselves as well as in the ways they +lived. As time went on, they made better tools and weapons. Then, too, +we begin to find signs of how they started thinking of other things +than food and the tools to get it with. We find that they painted on +the walls of caves, and decorated their tools; we find that they buried +their dead. + +At about the time when the last great glacier was finally melting away, +men in the Near East made the first basic change in human economy. +They began to plant grain, and they learned to raise and herd certain +animals. This meant that they could store food in granaries and �on the +hoof� against the bad times of the year. This first really basic change +in man�s way of living has been called the �food-producing revolution.� +By the time it happened, a modern kind of climate was beginning. Men +had already grown to look as they do now. Know-how in ways of living +had developed and progressed, slowly but surely, up to a point. It was +impossible for men to go beyond that point if they only hunted and +fished and gathered wild foods. Once the basic change was made--once +the food-producing revolution became effective--technology leaped ahead +and civilization and written history soon began. + + + + +Prehistoric Men THEMSELVES + +[Illustration] + + +DO WE KNOW WHERE MAN ORIGINATED? + +For a long time some scientists thought the �cradle of mankind� was in +central Asia. Other scientists insisted it was in Africa, and still +others said it might have been in Europe. Actually, we don�t know +where it was. We don�t even know that there was only _one_ �cradle.� +If we had to choose a �cradle� at this moment, we would probably say +Africa. But the southern portions of Asia and Europe may also have been +included in the general area. The scene of the early development of +mankind was certainly the Old World. It is pretty certain men didn�t +reach North or South America until almost the end of the Ice Age--had +they done so earlier we would certainly have found some trace of them +by now. + +The earliest tools we have yet found come from central and south +Africa. By the dating system I�m using, these tools must be over +500,000 years old. There are now reports that a few such early tools +have been found--at the Sterkfontein cave in South Africa--along with +the bones of small fossil men called �australopithecines.� + +Not all scientists would agree that the australopithecines were �men,� +or would agree that the tools were made by the australopithecines +themselves. For these sticklers, the earliest bones of men come from +the island of Java. The date would be about 450,000 years ago. So far, +we have not yet found the tools which we suppose these earliest men in +the Far East must have made. + +Let me say it another way. How old are the earliest traces of men we +now have? Over half a million years. This was a time when the first +alpine glaciation was happening in the north. What has been found so +far? The tools which the men of those times made, in different parts +of Africa. It is now fairly generally agreed that the �men� who made +the tools were the australopithecines. There is also a more �man-like� +jawbone at Kanam in Kenya, but its find-spot has been questioned. The +next earliest bones we have were found in Java, and they may be almost +a hundred thousand years younger than the earliest African finds. We +haven�t yet found the tools of these early Javanese. Our knowledge of +tool-using in Africa spreads quickly as time goes on: soon after the +appearance of tools in the south we shall have them from as far north +as Algeria. + +Very soon after the earliest Javanese come the bones of slightly more +developed people in Java, and the jawbone of a man who once lived in +what is now Germany. The same general glacial beds which yielded the +later Javanese bones and the German jawbone also include tools. These +finds come from the time of the second alpine glaciation. + +So this is the situation. By the time of the end of the second alpine +or first continental glaciation (say 400,000 years ago) we have traces +of men from the extremes of the more southerly portions of the Old +World--South Africa, eastern Asia, and western Europe. There are also +some traces of men in the middle ground. In fact, Professor Franz +Weidenreich believed that creatures who were the immediate ancestors +of men had already spread over Europe, Africa, and Asia by the time +the Ice Age began. We certainly have no reason to disbelieve this, but +fortunate accidents of discovery have not yet given us the evidence to +prove it. + + +MEN AND APES + +Many people used to get extremely upset at the ill-formed notion +that �man descended from the apes.� Such words were much more likely +to start fights or �monkey trials� than the correct notion that all +living animals, including man, ascended or evolved from a single-celled +organism which lived in the primeval seas hundreds of millions of years +ago. Men are mammals, of the order called Primates, and man�s living +relatives are the great apes. Men didn�t �descend� from the apes or +apes from men, and mankind must have had much closer relatives who have +since become extinct. + +Men stand erect. They also walk and run on their two feet. Apes are +happiest in trees, swinging with their arms from branch to branch. +Few branches of trees will hold the mighty gorilla, although he still +manages to sleep in trees. Apes can�t stand really erect in our sense, +and when they have to run on the ground, they use the knuckles of their +hands as well as their feet. + +A key group of fossil bones here are the south African +australopithecines. These are called the _Australopithecinae_ or +�man-apes� or sometimes even �ape-men.� We do not _know_ that they were +directly ancestral to men but they can hardly have been so to apes. +Presently I�ll describe them a bit more. The reason I mention them +here is that while they had brains no larger than those of apes, their +hipbones were enough like ours so that they must have stood erect. +There is no good reason to think they couldn�t have walked as we do. + + +BRAINS, HANDS, AND TOOLS + +Whether the australopithecines were our ancestors or not, the proper +ancestors of men must have been able to stand erect and to walk on +their two feet. Three further important things probably were involved, +next, before they could become men proper. These are: + + 1. The increasing size and development of the brain. + + 2. The increasing usefulness (specialization) of the thumb and hand. + + 3. The use of tools. + +Nobody knows which of these three is most important, or which came +first. Most probably the growth of all three things was very much +blended together. If you think about each of the things, you will see +what I mean. Unless your hand is more flexible than a paw, and your +thumb will work against (or oppose) your fingers, you can�t hold a tool +very well. But you wouldn�t get the idea of using a tool unless you had +enough brain to help you see cause and effect. And it is rather hard to +see how your hand and brain would develop unless they had something to +practice on--like using tools. In Professor Krogman�s words, �the hand +must become the obedient servant of the eye and the brain.� It is the +_co-ordination_ of these things that counts. + +Many other things must have been happening to the bodies of the +creatures who were the ancestors of men. Our ancestors had to develop +organs of speech. More than that, they had to get the idea of letting +_certain sounds_ made with these speech organs have _certain meanings_. + +All this must have gone very slowly. Probably everything was developing +little by little, all together. Men became men very slowly. + + +WHEN SHALL WE CALL MEN MEN? + +What do I mean when I say �men�? People who looked pretty much as we +do, and who used different tools to do different things, are men to me. +We�ll probably never know whether the earliest ones talked or not. They +probably had vocal cords, so they could make sounds, but did they know +how to make sounds work as symbols to carry meanings? But if the fossil +bones look like our skeletons, and if we find tools which we�ll agree +couldn�t have been made by nature or by animals, then I�d say we had +traces of _men_. + +The australopithecine finds of the Transvaal and Bechuanaland, in +south Africa, are bound to come into the discussion here. I�ve already +told you that the australopithecines could have stood upright and +walked on their two hind legs. They come from the very base of the +Pleistocene or Ice Age, and a few coarse stone tools have been found +with the australopithecine fossils. But there are three varieties +of the australopithecines and they last on until a time equal to +that of the second alpine glaciation. They are the best suggestion +we have yet as to what the ancestors of men _may_ have looked like. +They were certainly closer to men than to apes. Although their brain +size was no larger than the brains of modern apes their body size and +stature were quite small; hence, relative to their small size, their +brains were large. We have not been able to prove without doubt that +the australopithecines were _tool-making_ creatures, even though the +recent news has it that tools have been found with australopithecine +bones. The doubt as to whether the australopithecines used the tools +themselves goes like this--just suppose some man-like creature (whose +bones we have not yet found) made the tools and used them to kill +and butcher australopithecines. Hence a few experts tend to let +australopithecines still hang in limbo as �man-apes.� + + +THE EARLIEST MEN WE KNOW + +I�ll postpone talking about the tools of early men until the next +chapter. The men whose bones were the earliest of the Java lot have +been given the name _Meganthropus_. The bones are very fragmentary. We +would not understand them very well unless we had the somewhat later +Javanese lot--the more commonly known _Pithecanthropus_ or �Java +man�--against which to refer them for study. One of the less well-known +and earliest fragments, a piece of lower jaw and some teeth, rather +strongly resembles the lower jaws and teeth of the australopithecine +type. Was _Meganthropus_ a sort of half-way point between the +australopithecines and _Pithecanthropus_? It is still too early to say. +We shall need more finds before we can be definite one way or the other. + +Java man, _Pithecanthropus_, comes from geological beds equal in age +to the latter part of the second alpine glaciation; the _Meganthropus_ +finds refer to beds of the beginning of this glaciation. The first +finds of Java man were made in 1891-92 by Dr. Eugene Dubois, a Dutch +doctor in the colonial service. Finds have continued to be made. There +are now bones enough to account for four skulls. There are also four +jaws and some odd teeth and thigh bones. Java man, generally speaking, +was about five feet six inches tall, and didn�t hold his head very +erect. His skull was very thick and heavy and had room for little more +than two-thirds as large a brain as we have. He had big teeth and a big +jaw and enormous eyebrow ridges. + +No tools were found in the geological deposits where bones of Java man +appeared. There are some tools in the same general area, but they come +a bit later in time. One reason we accept the Java man as man--aside +from his general anatomical appearance--is that these tools probably +belonged to his near descendants. + +Remember that there are several varieties of men in the whole early +Java lot, at least two of which are earlier than the _Pithecanthropus_, +�Java man.� Some of the earlier ones seem to have gone in for +bigness, in tooth-size at least. _Meganthropus_ is one of these +earlier varieties. As we said, he _may_ turn out to be a link to +the australopithecines, who _may_ or _may not_ be ancestral to men. +_Meganthropus_ is best understandable in terms of _Pithecanthropus_, +who appeared later in the same general area. _Pithecanthropus_ is +pretty well understandable from the bones he left us, and also because +of his strong resemblance to the fully tool-using cave-dwelling �Peking +man,� _Sinanthropus_, about whom we shall talk next. But you can see +that the physical anthropologists and prehistoric archeologists still +have a lot of work to do on the problem of earliest men. + + +PEKING MEN AND SOME EARLY WESTERNERS + +The earliest known Chinese are called _Sinanthropus_, or �Peking man,� +because the finds were made near that city. In World War II, the United +States Marine guard at our Embassy in Peking tried to help get the +bones out of the city before the Japanese attack. Nobody knows where +these bones are now. The Red Chinese accuse us of having stolen them. +They were last seen on a dock-side at a Chinese port. But should you +catch a Marine with a sack of old bones, perhaps we could achieve peace +in Asia by returning them! Fortunately, there is a complete set of +casts of the bones. + +Peking man lived in a cave in a limestone hill, made tools, cracked +animal bones to get the marrow out, and used fire. Incidentally, the +bones of Peking man were found because Chinese dig for what they call +�dragon bones� and �dragon teeth.� Uneducated Chinese buy these things +in their drug stores and grind them into powder for medicine. The +�dragon teeth� and �bones� are really fossils of ancient animals, and +sometimes of men. The people who supply the drug stores have learned +where to dig for strange bones and teeth. Paleontologists who get to +China go to the drug stores to buy fossils. In a roundabout way, this +is how the fallen-in cave of Peking man at Choukoutien was discovered. + +Peking man was not quite as tall as Java man but he probably stood +straighter. His skull looked very much like that of the Java skull +except that it had room for a slightly larger brain. His face was less +brutish than was Java man�s face, but this isn�t saying much. + +Peking man dates from early in the interglacial period following the +second alpine glaciation. He probably lived close to 350,000 years +ago. There are several finds to account for in Europe by about this +time, and one from northwest Africa. The very large jawbone found +near Heidelberg in Germany is doubtless even earlier than Peking man. +The beds where it was found are of second alpine glacial times, and +recently some tools have been said to have come from the same beds. +There is not much I need tell you about the Heidelberg jaw save that it +seems certainly to have belonged to an early man, and that it is very +big. + +Another find in Germany was made at Steinheim. It consists of the +fragmentary skull of a man. It is very important because of its +relative completeness, but it has not yet been fully studied. The bone +is thick, but the back of the head is neither very low nor primitive, +and the face is also not primitive. The forehead does, however, have +big ridges over the eyes. The more fragmentary skull from Swanscombe in +England (p. 11) has been much more carefully studied. Only the top and +back of that skull have been found. Since the skull rounds up nicely, +it has been assumed that the face and forehead must have been quite +�modern.� Careful comparison with Steinheim shows that this was not +necessarily so. This is important because it bears on the question of +how early truly �modern� man appeared. + +Recently two fragmentary jaws were found at Ternafine in Algeria, +northwest Africa. They look like the jaws of Peking man. Tools were +found with them. Since no jaws have yet been found at Steinheim or +Swanscombe, but the time is the same, one wonders if these people had +jaws like those of Ternafine. + + +WHAT HAPPENED TO JAVA AND PEKING MEN + +Professor Weidenreich thought that there were at least a dozen ways in +which the Peking man resembled the modern Mongoloids. This would seem +to indicate that Peking man was really just a very early Chinese. + +Several later fossil men have been found in the Java-Australian area. +The best known of these is the so-called Solo man. There are some finds +from Australia itself which we now know to be quite late. But it looks +as if we may assume a line of evolution from Java man down to the +modern Australian natives. During parts of the Ice Age there was a land +bridge all the way from Java to Australia. + + +TWO ENGLISHMEN WHO WEREN�T OLD + +The older textbooks contain descriptions of two English finds which +were thought to be very old. These were called Piltdown (_Eoanthropus +dawsoni_) and Galley Hill. The skulls were very modern in appearance. +In 1948-49, British scientists began making chemical tests which proved +that neither of these finds is very old. It is now known that both +�Piltdown man� and the tools which were said to have been found with +him were part of an elaborate fake! + + +TYPICAL �CAVE MEN� + +The next men we have to talk about are all members of a related group. +These are the Neanderthal group. �Neanderthal man� himself was found in +the Neander Valley, near D�sseldorf, Germany, in 1856. He was the first +human fossil to be recognized as such. + +[Illustration: PRINCIPAL KNOWN TYPES OF FOSSIL MEN + + CRO-MAGNON + NEANDERTHAL + MODERN SKULL + COMBE-CAPELLE + SINANTHROPUS + PITHECANTHROPUS] + +Some of us think that the neanderthaloids proper are only those people +of western Europe who didn�t get out before the beginning of the last +great glaciation, and who found themselves hemmed in by the glaciers +in the Alps and northern Europe. Being hemmed in, they intermarried +a bit too much and developed into a special type. Professor F. Clark +Howell sees it this way. In Europe, the earliest trace of men we +now know is the Heidelberg jaw. Evolution continued in Europe, from +Heidelberg through the Swanscombe and Steinheim types to a group of +pre-neanderthaloids. There are traces of these pre-neanderthaloids +pretty much throughout Europe during the third interglacial period--say +100,000 years ago. The pre-neanderthaloids are represented by such +finds as the ones at Ehringsdorf in Germany and Saccopastore in Italy. +I won�t describe them for you, since they are simply less extreme than +the neanderthaloids proper--about half way between Steinheim and the +classic Neanderthal people. + +Professor Howell believes that the pre-neanderthaloids who happened to +get caught in the pocket of the southwest corner of Europe at the onset +of the last great glaciation became the classic Neanderthalers. Out in +the Near East, Howell thinks, it is possible to see traces of people +evolving from the pre-neanderthaloid type toward that of fully modern +man. Certainly, we don�t see such extreme cases of �neanderthaloidism� +outside of western Europe. + +There are at least a dozen good examples in the main or classic +Neanderthal group in Europe. They date to just before and in the +earlier part of the last great glaciation (85,000 to 40,000 years ago). +Many of the finds have been made in caves. The �cave men� the movies +and the cartoonists show you are probably meant to be Neanderthalers. +I�m not at all sure they dragged their women by the hair; the women +were probably pretty tough, too! + +Neanderthal men had large bony heads, but plenty of room for brains. +Some had brain cases even larger than the average for modern man. Their +faces were heavy, and they had eyebrow ridges of bone, but the ridges +were not as big as those of Java man. Their foreheads were very low, +and they didn�t have much chin. They were about five feet three inches +tall, but were heavy and barrel-chested. But the Neanderthalers didn�t +slouch as much as they�ve been blamed for, either. + +One important thing about the Neanderthal group is that there is a fair +number of them to study. Just as important is the fact that we know +something about how they lived, and about some of the tools they made. + + +OTHER MEN CONTEMPORARY WITH THE NEANDERTHALOIDS + +We have seen that the neanderthaloids seem to be a specialization +in a corner of Europe. What was going on elsewhere? We think that +the pre-neanderthaloid type was a generally widespread form of men. +From this type evolved other more or less extreme although generally +related men. The Solo finds in Java form one such case. Another was the +Rhodesian man of Africa, and the more recent Hopefield finds show more +of the general Rhodesian type. It is more confusing than it needs to be +if these cases outside western Europe are called neanderthaloids. They +lived during the same approximate time range but they were all somewhat +different-looking people. + + +EARLY MODERN MEN + +How early is modern man (_Homo sapiens_), the �wise man�? Some people +have thought that he was very early, a few still think so. Piltdown +and Galley Hill, which were quite modern in anatomical appearance and +_supposedly_ very early in date, were the best �evidence� for very +early modern men. Now that Piltdown has been liquidated and Galley Hill +is known to be very late, what is left of the idea? + +The backs of the skulls of the Swanscombe and Steinheim finds look +rather modern. Unless you pay attention to the face and forehead of the +Steinheim find--which not many people have--and perhaps also consider +the Ternafine jaws, you might come to the conclusion that the crown of +the Swanscombe head was that of a modern-like man. + +Two more skulls, again without faces, are available from a French +cave site, Font�chevade. They come from the time of the last great +interglacial, as did the pre-neanderthaloids. The crowns of the +Font�chevade skulls also look quite modern. There is a bit of the +forehead preserved on one of these skulls and the brow-ridge is not +heavy. Nevertheless, there is a suggestion that the bones belonged to +an immature individual. In this case, his (or even more so, if _her_) +brow-ridges would have been weak anyway. The case for the Font�chevade +fossils, as modern type men, is little stronger than that for +Swanscombe, although Professor Vallois believes it a good case. + +It seems to add up to the fact that there were people living in +Europe--before the classic neanderthaloids--who looked more modern, +in some features, than the classic western neanderthaloids did. Our +best suggestion of what men looked like--just before they became fully +modern--comes from a cave on Mount Carmel in Palestine. + + +THE FIRST MODERNS + +Professor T. D. McCown and the late Sir Arthur Keith, who studied the +Mount Carmel bones, figured out that one of the two groups involved +was as much as 70 per cent modern. There were, in fact, two groups or +varieties of men in the Mount Carmel caves and in at least two other +Palestinian caves of about the same time. The time would be about that +of the onset of colder weather, when the last glaciation was beginning +in the north--say 75,000 years ago. + +The 70 per cent modern group came from only one cave, Mugharet es-Skhul +(�cave of the kids�). The other group, from several caves, had bones of +men of the type we�ve been calling pre-neanderthaloid which we noted +were widespread in Europe and beyond. The tools which came with each +of these finds were generally similar, and McCown and Keith, and other +scholars since their study, have tended to assume that both the Skhul +group and the pre-neanderthaloid group came from exactly the same time. +The conclusion was quite natural: here was a population of men in the +act of evolving in two different directions. But the time may not be +exactly the same. It is very difficult to be precise, within say 10,000 +years, for a time some 75,000 years ago. If the Skhul men are in fact +later than the pre-neanderthaloid group of Palestine, as some of us +think, then they show how relatively modern some men were--men who +lived at the same time as the classic Neanderthalers of the European +pocket. + +Soon after the first extremely cold phase of the last glaciation, we +begin to get a number of bones of completely modern men in Europe. +We also get great numbers of the tools they made, and their living +places in caves. Completely modern skeletons begin turning up in caves +dating back to toward 40,000 years ago. The time is about that of the +beginning of the second phase of the last glaciation. These skeletons +belonged to people no different from many people we see today. Like +people today, not everybody looked alike. (The positions of the more +important fossil men of later Europe are shown in the chart on page +72.) + + +DIFFERENCES IN THE EARLY MODERNS + +The main early European moderns have been divided into two groups, the +Cro-Magnon group and the Combe Capelle-Br�nn group. Cro-Magnon people +were tall and big-boned, with large, long, and rugged heads. They +must have been built like many present-day Scandinavians. The Combe +Capelle-Br�nn people were shorter; they had narrow heads and faces, and +big eyebrow-ridges. Of course we don�t find the skin or hair of these +people. But there is little doubt they were Caucasoids (�Whites�). + +Another important find came in the Italian Riviera, near Monte Carlo. +Here, in a cave near Grimaldi, there was a grave containing a woman +and a young boy, buried together. The two skeletons were first called +�Negroid� because some features of their bones were thought to resemble +certain features of modern African Negro bones. But more recently, +Professor E. A. Hooton and other experts questioned the use of the word +�Negroid� in describing the Grimaldi skeletons. It is true that nothing +is known of the skin color, hair form, or any other fleshy feature of +the Grimaldi people, so that the word �Negroid� in its usual meaning is +not proper here. It is also not clear whether the features of the bones +claimed to be �Negroid� are really so at all. + +From a place called Wadjak, in Java, we have �proto-Australoid� skulls +which closely resemble those of modern Australian natives. Some of +the skulls found in South Africa, especially the Boskop skull, look +like those of modern Bushmen, but are much bigger. The ancestors of +the Bushmen seem to have once been very widespread south of the Sahara +Desert. True African Negroes were forest people who apparently expanded +out of the west central African area only in the last several thousand +years. Although dark in skin color, neither the Australians nor the +Bushmen are Negroes; neither the Wadjak nor the Boskop skulls are +�Negroid.� + +As we�ve already mentioned, Professor Weidenreich believed that Peking +man was already on the way to becoming a Mongoloid. Anyway, the +Mongoloids would seem to have been present by the time of the �Upper +Cave� at Choukoutien, the _Sinanthropus_ find-spot. + + +WHAT THE DIFFERENCES MEAN + +What does all this difference mean? It means that, at one moment in +time, within each different area, men tended to look somewhat alike. +From area to area, men tended to look somewhat different, just as +they do today. This is all quite natural. People _tended_ to mate +near home; in the anthropological jargon, they made up geographically +localized breeding populations. The simple continental division of +�stocks�--black = Africa, yellow = Asia, white = Europe--is too simple +a picture to fit the facts. People became accustomed to life in some +particular area within a continent (we might call it a �natural area�). +As they went on living there, they evolved towards some particular +physical variety. It would, of course, have been difficult to draw +a clear boundary between two adjacent areas. There must always have +been some mating across the boundaries in every case. One thing human +beings don�t do, and never have done, is to mate for �purity.� It is +self-righteous nonsense when we try to kid ourselves into thinking that +they do. + +I am not going to struggle with the whole business of modern stocks and +races. This is a book about prehistoric men, not recent historic or +modern men. My physical anthropologist friends have been very patient +in helping me to write and rewrite this chapter--I am not going to +break their patience completely. Races are their business, not mine, +and they must do the writing about races. I shall, however, give two +modern definitions of race, and then make one comment. + + Dr. William G. Boyd, professor of Immunochemistry, School of + Medicine, Boston University: �We may define a human race as a + population which differs significantly from other human populations + in regard to the frequency of one or more of the genes it + possesses.� + + Professor Sherwood L. Washburn, professor of Physical Anthropology, + Department of Anthropology, the University of California: �A �race� + is a group of genetically similar populations, and races intergrade + because there are always intermediate populations.� + +My comment is that the ideas involved here are all biological: they +concern groups, _not_ individuals. Boyd and Washburn may differ a bit +on what they want to consider a �population,� but a population is a +group nevertheless, and genetics is biology to the hilt. Now a lot of +people still think of race in terms of how people dress or fix their +food or of other habits or customs they have. The next step is to talk +about racial �purity.� None of this has anything whatever to do with +race proper, which is a matter of the biology of groups. + +Incidentally, I�m told that if man very carefully _controls_ +the breeding of certain animals over generations--dogs, cattle, +chickens--he might achieve a �pure� race of animals. But he doesn�t do +it. Some unfortunate genetic trait soon turns up, so this has just as +carefully to be bred out again, and so on. + + +SUMMARY OF PRESENT KNOWLEDGE OF FOSSIL MEN + +The earliest bones of men we now have--upon which all the experts +would probably agree--are those of _Meganthropus_, from Java, of about +450,000 years ago. The earlier australopithecines of Africa were +possibly not tool-users and may not have been ancestral to men at all. +But there is an alternate and evidently increasingly stronger chance +that some of them may have been. The Kanam jaw from Kenya, another +early possibility, is not only very incomplete but its find-spot is +very questionable. + +Java man proper, _Pithecanthropus_, comes next, at about 400,000 years +ago, and the big Heidelberg jaw in Germany must be of about the same +date. Next comes Swanscombe in England, Steinheim in Germany, the +Ternafine jaws in Algeria, and Peking man, _Sinanthropus_. They all +date to the second great interglacial period, about 350,000 years ago. + +Piltdown and Galley Hill are out, and with them, much of the starch +in the old idea that there were two distinct lines of development +in human evolution: (1) a line of �paleoanthropic� development from +Heidelberg to the Neanderthalers where it became extinct, and (2) a +very early �modern� line, through Piltdown, Galley Hill, Swanscombe, to +us. Swanscombe, Steinheim, and Ternafine are just as easily cases of +very early pre-neanderthaloids. + +The pre-neanderthaloids were very widespread during the third +interglacial: Ehringsdorf, Saccopastore, some of the Mount Carmel +people, and probably Font�chevade are cases in point. A variety of +their descendants can be seen, from Java (Solo), Africa (Rhodesian +man), and about the Mediterranean and in western Europe. As the acute +cold of the last glaciation set in, the western Europeans found +themselves surrounded by water, ice, or bitter cold tundra. To vastly +over-simplify it, they �bred in� and became classic neanderthaloids. +But on Mount Carmel, the Skhul cave-find with its 70 per cent modern +features shows what could happen elsewhere at the same time. + +Lastly, from about 40,000 or 35,000 years ago--the time of the onset +of the second phase of the last glaciation--we begin to find the fully +modern skeletons of men. The modern skeletons differ from place to +place, just as different groups of men living in different places still +look different. + +What became of the Neanderthalers? Nobody can tell me for sure. I�ve a +hunch they were simply �bred out� again when the cold weather was over. +Many Americans, as the years go by, are no longer ashamed to claim they +have �Indian blood in their veins.� Give us a few more generations +and there will not be very many other Americans left to whom we can +brag about it. It certainly isn�t inconceivable to me to imagine a +little Cro-Magnon boy bragging to his friends about his tough, strong, +Neanderthaler great-great-great-great-grandfather! + + + + +Cultural BEGINNINGS + +[Illustration] + + +Men, unlike the lower animals, are made up of much more than flesh and +blood and bones; for men have �culture.� + + +WHAT IS CULTURE? + +�Culture� is a word with many meanings. The doctors speak of making a +�culture� of a certain kind of bacteria, and ants are said to have a +�culture.� Then there is the Emily Post kind of �culture�--you say a +person is �cultured,� or that he isn�t, depending on such things as +whether or not he eats peas with his knife. + +The anthropologists use the word too, and argue heatedly over its finer +meanings; but they all agree that every human being is part of or has +some kind of culture. Each particular human group has a particular +culture; that is one of the ways in which we can tell one group of +men from another. In this sense, a CULTURE means the way the members +of a group of people think and believe and live, the tools they make, +and the way they do things. Professor Robert Redfield says a culture +is an organized or formalized body of conventional understandings. +�Conventional understandings� means the whole set of rules, beliefs, +and standards which a group of people lives by. These understandings +show themselves in art, and in the other things a people may make and +do. The understandings continue to last, through tradition, from one +generation to another. They are what really characterize different +human groups. + + +SOME CHARACTERISTICS OF CULTURE + +A culture lasts, although individual men in the group die off. On +the other hand, a culture changes as the different conventions and +understandings change. You could almost say that a culture lives in the +minds of the men who have it. But people are not born with it; they +get it as they grow up. Suppose a day-old Hungarian baby is adopted by +a family in Oshkosh, Wisconsin, and the child is not told that he is +Hungarian. He will grow up with no more idea of Hungarian culture than +anyone else in Oshkosh. + +So when I speak of ancient Egyptian culture, I mean the whole body +of understandings and beliefs and knowledge possessed by the ancient +Egyptians. I mean their beliefs as to why grain grew, as well as their +ability to make tools with which to reap the grain. I mean their +beliefs about life after death. What I am thinking about as culture is +a thing which lasted in time. If any one Egyptian, even the Pharaoh, +died, it didn�t affect the Egyptian culture of that particular moment. + + +PREHISTORIC CULTURES + +For that long period of man�s history that is all prehistory, we have +no written descriptions of cultures. We find only the tools men made, +the places where they lived, the graves in which they buried their +dead. Fortunately for us, these tools and living places and graves all +tell us something about the ways these men lived and the things they +believed. But the story we learn of the very early cultures must be +only a very small part of the whole, for we find so few things. The +rest of the story is gone forever. We have to do what we can with what +we find. + +For all of the time up to about 75,000 years ago, which was the time +of the classic European Neanderthal group of men, we have found few +cave-dwelling places of very early prehistoric men. First, there is the +fallen-in cave where Peking man was found, near Peking. Then there are +two or three other _early_, but not _very early_, possibilities. The +finds at the base of the French cave of Font�chevade, those in one of +the Makapan caves in South Africa, and several open sites such as Dr. +L. S. B. Leakey�s Olorgesailie in Kenya doubtless all lie earlier than +the time of the main European Neanderthal group, but none are so early +as the Peking finds. + +You can see that we know very little about the home life of earlier +prehistoric men. We find different kinds of early stone tools, but we +can�t even be really sure which tools may have been used together. + + +WHY LITTLE HAS LASTED FROM EARLY TIMES + +Except for the rare find-spots mentioned above, all our very early +finds come from geological deposits, or from the wind-blown surfaces +of deserts. Here is what the business of geological deposits really +means. Let us say that a group of people was living in England about +300,000 years ago. They made the tools they needed, lived in some sort +of camp, almost certainly built fires, and perhaps buried their dead. +While the climate was still warm, many generations may have lived in +the same place, hunting, and gathering nuts and berries; but after some +few thousand years, the weather began very gradually to grow colder. +These early Englishmen would not have known that a glacier was forming +over northern Europe. They would only have noticed that the animals +they hunted seemed to be moving south, and that the berries grew larger +toward the south. So they would have moved south, too. + +The camp site they left is the place we archeologists would really have +liked to find. All of the different tools the people used would have +been there together--many broken, some whole. The graves, and traces +of fire, and the tools would have been there. But the glacier got +there first! The front of this enormous sheet of ice moved down over +the country, crushing and breaking and plowing up everything, like a +gigantic bulldozer. You can see what happened to our camp site. + +Everything the glacier couldn�t break, it pushed along in front of it +or plowed beneath it. Rocks were ground to gravel, and soil was caught +into the ice, which afterwards melted and ran off as muddy water. Hard +tools of flint sometimes remained whole. Human bones weren�t so hard; +it�s a wonder _any_ of them lasted. Gushing streams of melt water +flushed out the debris from underneath the glacier, and water flowed +off the surface and through great crevasses. The hard materials these +waters carried were even more rolled and ground up. Finally, such +materials were dropped by the rushing waters as gravels, miles from +the front of the glacier. At last the glacier reached its greatest +extent; then it melted backward toward the north. Debris held in the +ice was dropped where the ice melted, or was flushed off by more melt +water. When the glacier, leaving the land, had withdrawn to the sea, +great hunks of ice were broken off as icebergs. These icebergs probably +dropped the materials held in their ice wherever they floated and +melted. There must be many tools and fragmentary bones of prehistoric +men on the bottom of the Atlantic Ocean and the North Sea. + +Remember, too, that these glaciers came and went at least three or four +times during the Ice Age. Then you will realize why the earlier things +we find are all mixed up. Stone tools from one camp site got mixed up +with stone tools from many other camp sites--tools which may have been +made tens of thousands or more years apart. The glaciers mixed them +all up, and so we cannot say which particular sets of tools belonged +together in the first place. + + +�EOLITHS� + +But what sort of tools do we find earliest? For almost a century, +people have been picking up odd bits of flint and other stone in the +oldest Ice Age gravels in England and France. It is now thought these +odd bits of stone weren�t actually worked by prehistoric men. The +stones were given a name, _eoliths_, or �dawn stones.� You can see them +in many museums; but you can be pretty sure that very few of them were +actually fashioned by men. + +It is impossible to pick out �eoliths� that seem to be made in any +one _tradition_. By �tradition� I mean a set of habits for making one +kind of tool for some particular job. No two �eoliths� look very much +alike: tools made as part of some one tradition all look much alike. +Now it�s easy to suppose that the very earliest prehistoric men picked +up and used almost any sort of stone. This wouldn�t be surprising; you +and I do it when we go camping. In other words, some of these �eoliths� +may actually have been used by prehistoric men. They must have used +anything that might be handy when they needed it. We could have figured +that out without the �eoliths.� + + +THE ROAD TO STANDARDIZATION + +Reasoning from what we know or can easily imagine, there should have +been three major steps in the prehistory of tool-making. The first step +would have been simple _utilization_ of what was at hand. This is the +step into which the �eoliths� would fall. The second step would have +been _fashioning_--the haphazard preparation of a tool when there was a +need for it. Probably many of the earlier pebble tools, which I shall +describe next, fall into this group. The third step would have been +_standardization_. Here, men began to make tools according to certain +set traditions. Counting the better-made pebble tools, there are four +such traditions or sets of habits for the production of stone tools in +earliest prehistoric times. Toward the end of the Pleistocene, a fifth +tradition appears. + + +PEBBLE TOOLS + +At the beginning of the last chapter, you�ll remember that I said there +were tools from very early geological beds. The earliest bones of men +have not yet been found in such early beds although the Sterkfontein +australopithecine cave approaches this early date. The earliest tools +come from Africa. They date back to the time of the first great +alpine glaciation and are at least 500,000 years old. The earliest +ones are made of split pebbles, about the size of your fist or a bit +bigger. They go under the name of pebble tools. There are many natural +exposures of early Pleistocene geological beds in Africa, and the +prehistoric archeologists of south and central Africa have concentrated +on searching for early tools. Other finds of early pebble tools have +recently been made in Algeria and Morocco. + +[Illustration: SOUTH AFRICAN PEBBLE TOOL] + +There are probably early pebble tools to be found in areas of the +Old World besides Africa; in fact, some prehistorians already claim +to have identified a few. Since the forms and the distinct ways of +making the earlier pebble tools had not yet sufficiently jelled into +a set tradition, they are difficult for us to recognize. It is not +so difficult, however, if there are great numbers of �possibles� +available. A little later in time the tradition becomes more clearly +set, and pebble tools are easier to recognize. So far, really large +collections of pebble tools have only been found and examined in Africa. + + +CORE-BIFACE TOOLS + +The next tradition we�ll look at is the _core_ or biface one. The tools +are large pear-shaped pieces of stone trimmed flat on the two opposite +sides or �faces.� Hence �biface� has been used to describe these tools. +The front view is like that of a pear with a rather pointed top, and +the back view looks almost exactly the same. Look at them side on, and +you can see that the front and back faces are the same and have been +trimmed to a thin tip. The real purpose in trimming down the two faces +was to get a good cutting edge all around. You can see all this in the +illustration. + +[Illustration: ABBEVILLIAN BIFACE] + +We have very little idea of the way in which these core-bifaces were +used. They have been called �hand axes,� but this probably gives the +wrong idea, for an ax, to us, is not a pointed tool. All of these early +tools must have been used for a number of jobs--chopping, scraping, +cutting, hitting, picking, and prying. Since the core-bifaces tend to +be pointed, it seems likely that they were used for hitting, picking, +and prying. But they have rough cutting edges, so they could have been +used for chopping, scraping, and cutting. + + +FLAKE TOOLS + +The third tradition is the _flake_ tradition. The idea was to get a +tool with a good cutting edge by simply knocking a nice large flake off +a big block of stone. You had to break off the flake in such a way that +it was broad and thin, and also had a good sharp cutting edge. Once you +really got on to the trick of doing it, this was probably a simpler way +to make a good cutting tool than preparing a biface. You have to know +how, though; I�ve tried it and have mashed my fingers more than once. + +The flake tools look as if they were meant mainly for chopping, +scraping, and cutting jobs. When one made a flake tool, the idea seems +to have been to produce a broad, sharp, cutting edge. + +[Illustration: CLACTONIAN FLAKE] + +The core-biface and the flake traditions were spread, from earliest +times, over much of Europe, Africa, and western Asia. The map on page +52 shows the general area. Over much of this great region there was +flint. Both of these traditions seem well adapted to flint, although +good core-bifaces and flakes were made from other kinds of stone, +especially in Africa south of the Sahara. + + +CHOPPERS AND ADZE-LIKE TOOLS + +The fourth early tradition is found in southern and eastern Asia, from +northwestern India through Java and Burma into China. Father Maringer +recently reported an early group of tools in Japan, which most resemble +those of Java, called Patjitanian. The prehistoric men in this general +area mostly used quartz and tuff and even petrified wood for their +stone tools (see illustration, p. 46). + +This fourth early tradition is called the _chopper-chopping tool_ +tradition. It probably has its earliest roots in the pebble tool +tradition of African type. There are several kinds of tools in this +tradition, but all differ from the western core-bifaces and flakes. +There are broad, heavy scrapers or cleavers, and tools with an +adze-like cutting edge. These last-named tools are called �hand adzes,� +just as the core-bifaces of the west have often been called �hand +axes.� The section of an adze cutting edge is ? shaped; the section of +an ax is < shaped. + +[Illustration: ANYATHIAN ADZE-LIKE TOOL] + +There are also pointed pebble tools. Thus the tool kit of these early +south and east Asiatic peoples seems to have included tools for doing +as many different jobs as did the tools of the Western traditions. + +Dr. H. L. Movius has emphasized that the tools which were found in the +Peking cave with Peking man belong to the chopper-tool tradition. This +is the only case as yet where the tools and the man have been found +together from very earliest times--if we except Sterkfontein. + + +DIFFERENCES WITHIN THE TOOL-MAKING TRADITIONS + +The latter three great traditions in the manufacture of stone +tools--and the less clear-cut pebble tools before them--are all we have +to show of the cultures of the men of those times. Changes happened in +each of the traditions. As time went on, the tools in each tradition +were better made. There could also be slight regional differences in +the tools within one tradition. Thus, tools with small differences, but +all belonging to one tradition, can be given special group (facies) +names. + +This naming of special groups has been going on for some time. Here are +some of these names, since you may see them used in museum displays +of flint tools, or in books. Within each tradition of tool-making +(save the chopper tools), the earliest tool type is at the bottom +of the list, just as it appears in the lowest beds of a geological +stratification.[3] + + [3] Archeologists usually make their charts and lists with the + earliest materials at the bottom and the latest on top, since + this is the way they find them in the ground. + + Chopper tool (all about equally early): + Anyathian (Burma) + Choukoutienian (China) + Patjitanian (Java) + Soan (India) + + Flake: + �Typical Mousterian� + Levalloiso-Mousterian + Levalloisian + Tayacian + Clactonian (localized in England) + + Core-biface: + Some blended elements in �Mousterian� + Micoquian (= Acheulean 6 and 7) + Acheulean + Abbevillian (once called �Chellean�) + + Pebble tool: + Oldowan + Ain Hanech + pre-Stellenbosch + Kafuan + +The core-biface and the flake traditions appear in the chart (p. 65). + +The early archeologists had many of the tool groups named before they +ever realized that there were broader tool preparation traditions. This +was understandable, for in dealing with the mixture of things that come +out of glacial gravels the easiest thing to do first is to isolate +individual types of tools into groups. First you put a bushel-basketful +of tools on a table and begin matching up types. Then you give names to +the groups of each type. The groups and the types are really matters of +the archeologists� choice; in real life, they were probably less exact +than the archeologists� lists of them. We now know pretty well in which +of the early traditions the various early groups belong. + + +THE MEANING OF THE DIFFERENT TRADITIONS + +What do the traditions really mean? I see them as the standardization +of ways to make tools for particular jobs. We may not know exactly what +job the maker of a particular core-biface or flake tool had in mind. We +can easily see, however, that he already enjoyed a know-how, a set of +persistent habits of tool preparation, which would always give him the +same type of tool when he wanted to make it. Therefore, the traditions +show us that persistent habits already existed for the preparation of +one type of tool or another. + +This tells us that one of the characteristic aspects of human culture +was already present. There must have been, in the minds of these +early men, a notion of the ideal type of tool for a particular job. +Furthermore, since we find so many thousands upon thousands of tools +of one type or another, the notion of the ideal types of tools _and_ +the know-how for the making of each type must have been held in common +by many men. The notions of the ideal types and the know-how for their +production must have been passed on from one generation to another. + +I could even guess that the notions of the ideal type of one or the +other of these tools stood out in the minds of men of those times +somewhat like a symbol of �perfect tool for good job.� If this were +so--remember it�s only a wild guess of mine--then men were already +symbol users. Now let�s go on a further step to the fact that the words +men speak are simply sounds, each different sound being a symbol for a +different meaning. If standardized tool-making suggests symbol-making, +is it also possible that crude word-symbols were also being made? I +suppose that it is not impossible. + +There may, of course, be a real question whether tool-utilizing +creatures--our first step, on page 42--were actually men. Other +animals utilize things at hand as tools. The tool-fashioning creature +of our second step is more suggestive, although we may not yet feel +sure that many of the earlier pebble tools were man-made products. But +with the step to standardization and the appearance of the traditions, +I believe we must surely be dealing with the traces of culture-bearing +_men_. The �conventional understandings� which Professor Redfield�s +definition of culture suggests are now evidenced for us in the +persistent habits for the preparation of stone tools. Were we able to +see the other things these prehistoric men must have made--in materials +no longer preserved for the archeologist to find--I believe there would +be clear signs of further conventional understandings. The men may have +been physically primitive and pretty shaggy in appearance, but I think +we must surely call them men. + + +AN OLDER INTERPRETATION OF THE WESTERN TRADITIONS + +In the last chapter, I told you that many of the older archeologists +and human paleontologists used to think that modern man was very old. +The supposed ages of Piltdown and Galley Hill were given as evidence +of the great age of anatomically modern man, and some interpretations +of the Swanscombe and Font�chevade fossils were taken to support +this view. The conclusion was that there were two parallel lines or +�phyla� of men already present well back in the Pleistocene. The +first of these, the more primitive or �paleoanthropic� line, was +said to include Heidelberg, the proto-neanderthaloids and classic +Neanderthal. The more anatomically modern or �neanthropic� line was +thought to consist of Piltdown and the others mentioned above. The +Neanderthaler or paleoanthropic line was thought to have become extinct +after the first phase of the last great glaciation. Of course, the +modern or neanthropic line was believed to have persisted into the +present, as the basis for the world�s population today. But with +Piltdown liquidated, Galley Hill known to be very late, and Swanscombe +and Font�chevade otherwise interpreted, there is little left of the +so-called parallel phyla theory. + +While the theory was in vogue, however, and as long as the European +archeological evidence was looked at in one short-sighted way, the +archeological materials _seemed_ to fit the parallel phyla theory. It +was simply necessary to believe that the flake tools were made only +by the paleoanthropic Neanderthaler line, and that the more handsome +core-biface tools were the product of the neanthropic modern-man line. + +Remember that _almost_ all of the early prehistoric European tools +came only from the redeposited gravel beds. This means that the tools +were not normally found in the remains of camp sites or work shops +where they had actually been dropped by the men who made and used +them. The tools came, rather, from the secondary hodge-podge of the +glacial gravels. I tried to give you a picture of the bulldozing action +of glaciers (p. 40) and of the erosion and weathering that were +side-effects of a glacially conditioned climate on the earth�s surface. +As we said above, if one simply plucks tools out of the redeposited +gravels, his natural tendency is to �type� the tools by groups, and to +think that the groups stand for something _on their own_. + +In 1906, M. Victor Commont actually made a rare find of what seems +to have been a kind of workshop site, on a terrace above the Somme +river in France. Here, Commont realized, flake tools appeared clearly +in direct association with core-biface tools. Few prehistorians paid +attention to Commont or his site, however. It was easier to believe +that flake tools represented a distinct �culture� and that this +�culture� was that of the Neanderthaler or paleoanthropic line, and +that the core-bifaces stood for another �culture� which was that of the +supposed early modern or neanthropic line. Of course, I am obviously +skipping many details here. Some later sites with Neanderthal fossils +do seem to have only flake tools, but other such sites have both types +of tools. The flake tools which appeared _with_ the core-bifaces +in the Swanscombe gravels were never made much of, although it +was embarrassing for the parallel phyla people that Font�chevade +ran heavily to flake tools. All in all, the parallel phyla theory +flourished because it seemed so neat and easy to understand. + + +TRADITIONS ARE TOOL-MAKING HABITS, NOT CULTURES + +In case you think I simply enjoy beating a dead horse, look in any +standard book on prehistory written twenty (or even ten) years ago, or +in most encyclopedias. You�ll find that each of the individual tool +types, of the West, at least, was supposed to represent a �culture.� +The �cultures� were believed to correspond to parallel lines of human +evolution. + +In 1937, Mr. Harper Kelley strongly re-emphasized the importance +of Commont�s workshop site and the presence of flake tools with +core-bifaces. Next followed Dr. Movius� clear delineation of the +chopper-chopping tool tradition of the Far East. This spoiled the nice +symmetry of the flake-tool = paleoanthropic, core-biface = neanthropic +equations. Then came increasing understanding of the importance of +the pebble tools in Africa, and the location of several more workshop +sites there, especially at Olorgesailie in Kenya. Finally came the +liquidation of Piltdown and the deflation of Galley Hill�s date. So it +is at last possible to picture an individual prehistoric man making a +flake tool to do one job and a core-biface tool to do another. Commont +showed us this picture in 1906, but few believed him. + +[Illustration: DISTRIBUTION OF TOOL-PREPARATION TRADITIONS + +Time approximately 100,000 years ago] + +There are certainly a few cases in which flake tools did appear with +few or no core-bifaces. The flake-tool group called Clactonian in +England is such a case. Another good, but certainly later case is +that of the cave on Mount Carmel in Palestine, where the blended +pre-neanderthaloid, 70 per cent modern-type skulls were found. Here, in +the same level with the skulls, were 9,784 flint tools. Of these, only +three--doubtless strays--were core-bifaces; all the rest were flake +tools or flake chips. We noted above how the Font�chevade cave ran to +flake tools. The only conclusion I would draw from this is that times +and circumstances did exist in which prehistoric men needed only flake +tools. So they only made flake tools for those particular times and +circumstances. + + +LIFE IN EARLIEST TIMES + +What do we actually know of life in these earliest times? In the +glacial gravels, or in the terrace gravels of rivers once swollen by +floods of melt water or heavy rains, or on the windswept deserts, we +find stone tools. The earliest and coarsest of these are the pebble +tools. We do not yet know what the men who made them looked like, +although the Sterkfontein australopithecines probably give us a good +hint. Then begin the more formal tool preparation traditions of the +west--the core-bifaces and the flake tools--and the chopper-chopping +tool series of the farther east. There is an occasional roughly worked +piece of bone. From the gravels which yield the Clactonian flakes of +England comes the fire-hardened point of a wooden spear. There are +also the chance finds of the fossil human bones themselves, of which +we spoke in the last chapter. Aside from the cave of Peking man, none +of the earliest tools have been found in caves. Open air or �workshop� +sites which do not seem to have been disturbed later by some geological +agency are very rare. + +The chart on page 65 shows graphically what the situation in +west-central Europe seems to have been. It is not yet certain whether +there were pebble tools there or not. The Font�chevade cave comes +into the picture about 100,000 years ago or more. But for the earlier +hundreds of thousands of years--below the red-dotted line on the +chart--the tools we find come almost entirely from the haphazard +mixture within the geological contexts. + +The stone tools of each of the earlier traditions are the simplest +kinds of all-purpose tools. Almost any one of them could be used for +hacking, chopping, cutting, and scraping; so the men who used them must +have been living in a rough and ready sort of way. They found or hunted +their food wherever they could. In the anthropological jargon, they +were �food-gatherers,� pure and simple. + +Because of the mixture in the gravels and in the materials they +carried, we can�t be sure which animals these men hunted. Bones of +the larger animals turn up in the gravels, but they could just as +well belong to the animals who hunted the men, rather than the other +way about. We don�t know. This is why camp sites like Commont�s and +Olorgesailie in Kenya are so important when we do find them. The animal +bones at Olorgesailie belonged to various mammals of extremely large +size. Probably they were taken in pit-traps, but there are a number of +groups of three round stones on the site which suggest that the people +used bolas. The South American Indians used three-ball bolas, with the +stones in separate leather bags connected by thongs. These were whirled +and then thrown through the air so as to entangle the feet of a fleeing +animal. + +Professor F. Clark Howell recently returned from excavating another +important open air site at Isimila in Tanganyika. The site yielded +the bones of many fossil animals and also thousands of core-bifaces, +flakes, and choppers. But Howell�s reconstruction of the food-getting +habits of the Isimila people certainly suggests that the word �hunting� +is too dignified for what they did; �scavenging� would be much nearer +the mark. + +During a great part of this time the climate was warm and pleasant. The +second interglacial period (the time between the second and third great +alpine glaciations) lasted a long time, and during much of this time +the climate may have been even better than ours is now. We don�t know +that earlier prehistoric men in Europe or Africa lived in caves. They +may not have needed to; much of the weather may have been so nice that +they lived in the open. Perhaps they didn�t wear clothes, either. + + +WHAT THE PEKING CAVE-FINDS TELL US + +The one early cave-dwelling we have found is that of Peking man, in +China. Peking man had fire. He probably cooked his meat, or used +the fire to keep dangerous animals away from his den. In the cave +were bones of dangerous animals, members of the wolf, bear, and cat +families. Some of the cat bones belonged to beasts larger than tigers. +There were also bones of other wild animals: buffalo, camel, deer, +elephants, horses, sheep, and even ostriches. Seventy per cent of the +animals Peking man killed were fallow deer. It�s much too cold and dry +in north China for all these animals to live there today. So this list +helps us know that the weather was reasonably warm, and that there was +enough rain to grow grass for the grazing animals. The list also helps +the paleontologists to date the find. + +Peking man also seems to have eaten plant food, for there are hackberry +seeds in the debris of the cave. His tools were made of sandstone and +quartz and sometimes of a rather bad flint. As we�ve already seen, they +belong in the chopper-tool tradition. It seems fairly clear that some +of the edges were chipped by right-handed people. There are also many +split pieces of heavy bone. Peking man probably split them so he could +eat the bone marrow, but he may have used some of them as tools. + +Many of these split bones were the bones of Peking men. Each one of the +skulls had already had the base broken out of it. In no case were any +of the bones resting together in their natural relation to one another. +There is nothing like a burial; all of the bones are scattered. Now +it�s true that animals could have scattered bodies that were not cared +for or buried. But splitting bones lengthwise and carefully removing +the base of a skull call for both the tools and the people to use them. +It�s pretty clear who the people were. Peking man was a cannibal. + + * * * * * + +This rounds out about all we can say of the life and times of early +prehistoric men. In those days life was rough. You evidently had to +watch out not only for dangerous animals but also for your fellow men. +You ate whatever you could catch or find growing. But you had sense +enough to build fires, and you had already formed certain habits for +making the kinds of stone tools you needed. That�s about all we know. +But I think we�ll have to admit that cultural beginnings had been made, +and that these early people were really _men_. + + + + +MORE EVIDENCE of Culture + +[Illustration] + + +While the dating is not yet sure, the material that we get from caves +in Europe must go back to about 100,000 years ago; the time of the +classic Neanderthal group followed soon afterwards. We don�t know why +there is no earlier material in the caves; apparently they were not +used before the last interglacial phase (the period just before the +last great glaciation). We know that men of the classic Neanderthal +group were living in caves from about 75,000 to 45,000 years ago. +New radioactive carbon dates even suggest that some of the traces of +culture we�ll describe in this chapter may have lasted to about 35,000 +years ago. Probably some of the pre-neanderthaloid types of men had +also lived in caves. But we have so far found their bones in caves only +in Palestine and at Font�chevade. + + +THE CAVE LAYERS + +In parts of France, some peasants still live in caves. In prehistoric +time, many generations of people lived in them. As a result, many +caves have deep layers of debris. The first people moved in and lived +on the rock floor. They threw on the floor whatever they didn�t want, +and they tracked in mud; nobody bothered to clean house in those days. +Their debris--junk and mud and garbage and what not--became packed +into a layer. As time went on, and generations passed, the layer grew +thicker. Then there might have been a break in the occupation of the +cave for a while. Perhaps the game animals got scarce and the people +moved away; or maybe the cave became flooded. Later on, other people +moved in and began making a new layer of their own on top of the first +layer. Perhaps this process of layering went on in the same cave for a +hundred thousand years; you can see what happened. The drawing on this +page shows a section through such a cave. The earliest layer is on the +bottom, the latest one on top. They go in order from bottom to top, +earliest to latest. This is the _stratification_ we talked about (p. +12). + +[Illustration: SECTION OF SHELTER ON LOWER TERRACE, LE MOUSTIER] + +While we may find a mix-up in caves, it�s not nearly as bad as the +mixing up that was done by glaciers. The animal bones and shells, the +fireplaces, the bones of men, and the tools the men made all belong +together, if they come from one layer. That�s the reason why the cave +of Peking man is so important. It is also the reason why the caves in +Europe and the Near East are so important. We can get an idea of which +things belong together and which lot came earliest and which latest. + +In most cases, prehistoric men lived only in the mouths of caves. +They didn�t like the dark inner chambers as places to live in. They +preferred rock-shelters, at the bases of overhanging cliffs, if there +was enough overhang to give shelter. When the weather was good, they no +doubt lived in the open air as well. I�ll go on using the term �cave� +since it�s more familiar, but remember that I really mean rock-shelter, +as a place in which people actually lived. + +The most important European cave sites are in Spain, France, and +central Europe; there are also sites in England and Italy. A few caves +are known in the Near East and Africa, and no doubt more sites will be +found when the out-of-the-way parts of Europe, Africa, and Asia are +studied. + + +AN �INDUSTRY� DEFINED + +We have already seen that the earliest European cave materials are +those from the cave of Font�chevade. Movius feels certain that the +lowest materials here date back well into the third interglacial stage, +that which lay between the Riss (next to the last) and the W�rm I +(first stage of the last) alpine glaciations. This material consists +of an _industry_ of stone tools, apparently all made in the flake +tradition. This is the first time we have used the word �industry.� +It is useful to call all of the different tools found together in one +layer and made of _one kind of material_ an industry; that is, the +tools must be found together as men left them. Tools taken from the +glacial gravels (or from windswept desert surfaces or river gravels +or any geological deposit) are not �together� in this sense. We might +say the latter have only �geological,� not �archeological� context. +Archeological context means finding things just as men left them. We +can tell what tools go together in an �industrial� sense only if we +have archeological context. + +Up to now, the only things we could have called �industries� were the +worked stone industry and perhaps the worked (?) bone industry of the +Peking cave. We could add some of the very clear cases of open air +sites, like Olorgesailie. We couldn�t use the term for the stone tools +from the glacial gravels, because we do not know which tools belonged +together. But when the cave materials begin to appear in Europe, we can +begin to speak of industries. Most of the European caves of this time +contain industries of flint tools alone. + + +THE EARLIEST EUROPEAN CAVE LAYERS + +We�ve just mentioned the industry from what is said to be the oldest +inhabited cave in Europe; that is, the industry from the deepest layer +of the site at Font�chevade. Apparently it doesn�t amount to much. The +tools are made of stone, in the flake tradition, and are very poorly +worked. This industry is called _Tayacian_. Its type tool seems to be +a smallish flake tool, but there are also larger flakes which seem to +have been fashioned for hacking. In fact, the type tool seems to be +simply a smaller edition of the Clactonian tool (pictured on p. 45). + +None of the Font�chevade tools are really good. There are scrapers, +and more or less pointed tools, and tools that may have been used +for hacking and chopping. Many of the tools from the earlier glacial +gravels are better made than those of this first industry we see in +a European cave. There is so little of this material available that +we do not know which is really typical and which is not. You would +probably find it hard to see much difference between this industry and +a collection of tools of the type called Clactonian, taken from the +glacial gravels, especially if the Clactonian tools were small-sized. + +The stone industry of the bottommost layer of the Mount Carmel cave, +in Palestine, where somewhat similar tools were found, has also been +called Tayacian. + +I shall have to bring in many unfamiliar words for the names of the +industries. The industries are usually named after the places where +they were first found, and since these were in most cases in France, +most of the names which follow will be of French origin. However, +the names have simply become handles and are in use far beyond the +boundaries of France. It would be better if we had a non-place-name +terminology, but archeologists have not yet been able to agree on such +a terminology. + + +THE ACHEULEAN INDUSTRY + +Both in France and in Palestine, as well as in some African cave +sites, the next layers in the deep caves have an industry in both the +core-biface and the flake traditions. The core-biface tools usually +make up less than half of all the tools in the industry. However, +the name of the biface type of tool is generally given to the whole +industry. It is called the _Acheulean_, actually a late form of it, as +�Acheulean� is also used for earlier core-biface tools taken from the +glacial gravels. In western Europe, the name used is _Upper Acheulean_ +or _Micoquian_. The same terms have been borrowed to name layers E and +F in the Tabun cave, on Mount Carmel in Palestine. + +The Acheulean core-biface type of tool is worked on two faces so as +to give a cutting edge all around. The outline of its front view may +be oval, or egg-shaped, or a quite pointed pear shape. The large +chip-scars of the Acheulean core-bifaces are shallow and flat. It is +suspected that this resulted from the removal of the chips with a +wooden club; the deep chip-scars of the earlier Abbevillian core-biface +came from beating the tool against a stone anvil. These tools are +really the best and also the final products of the core-biface +tradition. We first noticed the tradition in the early glacial gravels +(p. 43); now we see its end, but also its finest examples, in the +deeper cave levels. + +The flake tools, which really make up the greater bulk of this +industry, are simple scrapers and chips with sharp cutting edges. The +habits used to prepare them must have been pretty much the same as +those used for at least one of the flake industries we shall mention +presently. + +There is very little else in these early cave layers. We do not have +a proper �industry� of bone tools. There are traces of fire, and of +animal bones, and a few shells. In Palestine, there are many more +bones of deer than of gazelle in these layers; the deer lives in a +wetter climate than does the gazelle. In the European cave layers, the +animal bones are those of beasts that live in a warm climate. They +belonged in the last interglacial period. We have not yet found the +bones of fossil men definitely in place with this industry. + +[Illustration: ACHEULEAN BIFACE] + + +FLAKE INDUSTRIES FROM THE CAVES + +Two more stone industries--the _Levalloisian_ and the +�_Mousterian_�--turn up at approximately the same time in the European +cave layers. Their tools seem to be mainly in the flake tradition, +but according to some of the authorities their preparation also shows +some combination with the habits by which the core-biface tools were +prepared. + +Now notice that I don�t tell you the Levalloisian and the �Mousterian� +layers are both above the late Acheulean layers. Look at the cave +section (p. 57) and you�ll find that some �Mousterian of Acheulean +tradition� appears above some �typical Mousterian.� This means that +there may be some kinds of Acheulean industries that are later than +some kinds of �Mousterian.� The same is true of the Levalloisian. + +There were now several different kinds of habits that men used in +making stone tools. These habits were based on either one or the other +of the two traditions--core-biface or flake--or on combinations of +the habits used in the preparation techniques of both traditions. All +were popular at about the same time. So we find that people who made +one kind of stone tool industry lived in a cave for a while. Then they +gave up the cave for some reason, and people with another industry +moved in. Then the first people came back--or at least somebody with +the same tool-making habits as the first people. Or maybe a third group +of tool-makers moved in. The people who had these different habits for +making their stone tools seem to have moved around a good deal. They no +doubt borrowed and exchanged tricks of the trade with each other. There +were no patent laws in those days. + +The extremely complicated interrelationships of the different habits +used by the tool-makers of this range of time are at last being +systematically studied. M. Fran�ois Bordes has developed a statistical +method of great importance for understanding these tool preparation +habits. + + +THE LEVALLOISIAN AND MOUSTERIAN + +The easiest Levalloisian tool to spot is a big flake tool. The trick +in making it was to fashion carefully a big chunk of stone (called +the Levalloisian �tortoise core,� because it resembles the shape of +a turtle-shell) and then to whack this in such a way that a large +flake flew off. This large thin flake, with sharp cutting edges, is +the finished Levalloisian tool. There were various other tools in a +Levalloisian industry, but this is the characteristic _Levalloisian_ +tool. + +There are several �typical Mousterian� stone tools. Different from +the tools of the Levalloisian type, these were made from �disc-like +cores.� There are medium-sized flake �side scrapers.� There are also +some small pointed tools and some small �hand axes.� The last of these +tool types is often a flake worked on both of the flat sides (that +is, bifacially). There are also pieces of flint worked into the form +of crude balls. The pointed tools may have been fixed on shafts to +make short jabbing spears; the round flint balls may have been used as +bolas. Actually, we don�t _know_ what either tool was used for. The +points and side scrapers are illustrated (pp. 64 and 66). + +[Illustration: LEVALLOIS FLAKE] + + +THE MIXING OF TRADITIONS + +Nowadays the archeologists are less and less sure of the importance +of any one specific tool type and name. Twenty years ago, they used +to speak simply of Acheulean or Levalloisian or Mousterian tools. +Now, more and more, _all_ of the tools from some one layer in a +cave are called an �industry,� which is given a mixed name. Thus we +have �Levalloiso-Mousterian,� and �Acheuleo-Levalloisian,� and even +�Acheuleo-Mousterian� (or �Mousterian of Acheulean tradition�). Bordes� +systematic work is beginning to clear up some of our confusion. + +The time of these late Acheuleo-Levalloiso-Mousterioid industries +is from perhaps as early as 100,000 years ago. It may have lasted +until well past 50,000 years ago. This was the time of the first +phase of the last great glaciation. It was also the time that the +classic group of Neanderthal men was living in Europe. A number of +the Neanderthal fossil finds come from these cave layers. Before the +different habits of tool preparation were understood it used to be +popular to say Neanderthal man was �Mousterian man.� I think this is +wrong. What used to be called �Mousterian� is now known to be a variety +of industries with tools of both core-biface and flake habits, and +so mixed that the word �Mousterian� used alone really doesn�t mean +anything. The Neanderthalers doubtless understood the tool preparation +habits by means of which Acheulean, Levalloisian and Mousterian type +tools were produced. We also have the more modern-like Mount Carmel +people, found in a cave layer of Palestine with tools almost entirely +in the flake tradition, called �Levalloiso-Mousterian,� and the +Font�chevade-Tayacian (p. 59). + +[Illustration: MOUSTERIAN POINT] + + +OTHER SUGGESTIONS OF LIFE IN THE EARLY CAVE LAYERS + +Except for the stone tools, what do we know of the way men lived in the +time range after 100,000 to perhaps 40,000 years ago or even later? +We know that in the area from Europe to Palestine, at least some of +the people (some of the time) lived in the fronts of caves and warmed +themselves over fires. In Europe, in the cave layers of these times, +we find the bones of different animals; the bones in the lowest layers +belong to animals that lived in a warm climate; above them are the +bones of those who could stand the cold, like the reindeer and mammoth. +Thus, the meat diet must have been changing, as the glacier crept +farther south. Shells and possibly fish bones have lasted in these +cave layers, but there is not a trace of the vegetable foods and the +nuts and berries and other wild fruits that must have been eaten when +they could be found. + +[Illustration: CHART SHOWING PRESENT UNDERSTANDING OF RELATIONSHIPS AND +SUCCESSION OF TOOL-PREPARATION TRADITIONS, INDUSTRIES, AND ASSEMBLAGES +OF WEST-CENTRAL EUROPE + +Wavy lines indicate transitions in industrial habits. These transitions +are not yet understood in detail. The glacial and climatic scheme shown +is the alpine one.] + +Bone tools have also been found from this period. Some are called +scrapers, and there are also long chisel-like leg-bone fragments +believed to have been used for skinning animals. Larger hunks of bone, +which seem to have served as anvils or chopping blocks, are fairly +common. + +Bits of mineral, used as coloring matter, have also been found. We +don�t know what the color was used for. + +[Illustration: MOUSTERIAN SIDE SCRAPER] + +There is a small but certain number of cases of intentional burials. +These burials have been found on the floors of the caves; in other +words, the people dug graves in the places where they lived. The holes +made for the graves were small. For this reason (or perhaps for some +other?) the bodies were in a curled-up or contracted position. Flint or +bone tools or pieces of meat seem to have been put in with some of the +bodies. In several cases, flat stones had been laid over the graves. + + +TOOLS FROM AFRICA AND ASIA ABOUT 100,000 YEARS AGO + +Professor Movius characterizes early prehistoric Africa as a continent +showing a variety of stone industries. Some of these industries were +purely local developments and some were practically identical with +industries found in Europe at the same time. From northwest Africa +to Capetown--excepting the tropical rain forest region of the west +center--tools of developed Acheulean, Levalloisian, and Mousterian +types have been recognized. Often they are named after African place +names. + +In east and south Africa lived people whose industries show a +development of the Levalloisian technique. Such industries are +called Stillbay. Another industry, developed on the basis of the +Acheulean technique, is called Fauresmith. From the northwest comes +an industry with tanged points and flake-blades; this is called the +Aterian. The tropical rain forest region contained people whose stone +tools apparently show adjustment to this peculiar environment; the +so-called Sangoan industry includes stone picks, adzes, core-bifaces +of specialized Acheulean type, and bifacial points which were probably +spearheads. + +In western Asia, even as far as the east coast of India, the tools of +the Eurafrican core-biface and flake tool traditions continued to be +used. But in the Far East, as we noted in the last chapter, men had +developed characteristic stone chopper and chopping tools. This tool +preparation tradition--basically a pebble tool tradition--lasted to the +very end of the Ice Age. + +When more intact open air sites such as that of an earlier time at +Olorgesailie, and more stratified cave sites are found and excavated +in Asia and Africa, we shall be able to get a more complete picture. +So far, our picture of the general cultural level of the Old World at +about 100,000 years ago--and soon afterwards--is best from Europe, but +it is still far from complete there, too. + + +CULTURE AT THE BEGINNING OF THE LAST GREAT GLACIAL PERIOD + +The few things we have found must indicate only a very small part +of the total activities of the people who lived at the time. All of +the things they made of wood and bark, of skins, of anything soft, +are gone. The fact that burials were made, at least in Europe and +Palestine, is pretty clear proof that the people had some notion of a +life after death. But what this notion really was, or what gods (if +any) men believed in, we cannot know. Dr. Movius has also reminded me +of the so-called bear cults--cases in which caves have been found which +contain the skulls of bears in apparently purposeful arrangement. This +might suggest some notion of hoarding up the spirits or the strength of +bears killed in the hunt. Probably the people lived in small groups, +as hunting and food-gathering seldom provide enough food for large +groups of people. These groups probably had some kind of leader or +�chief.� Very likely the rude beginnings of rules for community life +and politics, and even law, were being made. But what these were, we +do not know. We can only guess about such things, as we can only guess +about many others; for example, how the idea of a family must have been +growing, and how there may have been witch doctors who made beginnings +in medicine or in art, in the materials they gathered for their trade. + +The stone tools help us most. They have lasted, and we can find +them. As they come to us, from this cave or that, and from this +layer or that, the tool industries show a variety of combinations +of the different basic habits or traditions of tool preparation. +This seems only natural, as the groups of people must have been very +small. The mixtures and blendings of the habits used in making stone +tools must mean that there were also mixtures and blends in many of +the other ideas and beliefs of these small groups. And what this +probably means is that there was no one _culture_ of the time. It is +certainly unlikely that there were simply three cultures, �Acheulean,� +�Levalloisian,� and �Mousterian,� as has been thought in the past. +Rather there must have been a great variety of loosely related cultures +at about the same stage of advancement. We could say, too, that here +we really begin to see, for the first time, that remarkable ability +of men to adapt themselves to a variety of conditions. We shall see +this adaptive ability even more clearly as time goes on and the record +becomes more complete. + +Over how great an area did these loosely related cultures reach in +the time 75,000 to 45,000 or even as late as 35,000 years ago? We +have described stone tools made in one or another of the flake and +core-biface habits, for an enormous area. It covers all of Europe, all +of Africa, the Near East, and parts of India. It is perfectly possible +that the flake and core-biface habits lasted on after 35,000 years ago, +in some places outside of Europe. In northern Africa, for example, we +are certain that they did (see chart, p. 72). + +On the other hand, in the Far East (China, Burma, Java) and in northern +India, the tools of the old chopper-tool tradition were still being +made. Out there, we must assume, there was a different set of loosely +related cultures. At least, there was a different set of loosely +related habits for the making of tools. But the men who made them must +have looked much like the men of the West. Their tools were different, +but just as useful. + +As to what the men of the West looked like, I�ve already hinted at all +we know so far (pp. 29 ff.). The Neanderthalers were present at +the time. Some more modern-like men must have been about, too, since +fossils of them have turned up at Mount Carmel in Palestine, and at +Teshik Tash, in Trans-caspian Russia. It is still too soon to know +whether certain combinations of tools within industries were made +only by certain physical types of men. But since tools of both the +core-biface and the flake traditions, and their blends, turn up from +South Africa to England to India, it is most unlikely that only one +type of man used only one particular habit in the preparation of tools. +What seems perfectly clear is that men in Africa and men in India were +making just as good tools as the men who lived in western Europe. + + + + +EARLY MODERNS + +[Illustration] + + +From some time during the first inter-stadial of the last great +glaciation (say some time after about 40,000 years ago), we have +more accurate dates for the European-Mediterranean area and less +accurate ones for the rest of the Old World. This is probably +because the effects of the last glaciation have been studied in the +European-Mediterranean area more than they have been elsewhere. + + +A NEW TRADITION APPEARS + +Something new was probably beginning to happen in the +European-Mediterranean area about 40,000 years ago, though all the +rest of the Old World seems to have been going on as it had been. I +can�t be sure of this because the information we are using as a basis +for dates is very inaccurate for the areas outside of Europe and the +Mediterranean. + +We can at least make a guess. In Egypt and north Africa, men were still +using the old methods of making stone tools. This was especially true +of flake tools of the Levalloisian type, save that they were growing +smaller and smaller as time went on. But at the same time, a new +tradition was becoming popular in westernmost Asia and in Europe. This +was the blade-tool tradition. + + +BLADE TOOLS + +A stone blade is really just a long parallel-sided flake, as the +drawing shows. It has sharp cutting edges, and makes a very useful +knife. The real trick is to be able to make one. It is almost +impossible to make a blade out of any stone but flint or a natural +volcanic glass called obsidian. And even if you have flint or obsidian, +you first have to work up a special cone-shaped �blade-core,� from +which to whack off blades. + +[Illustration: PLAIN BLADE] + +You whack with a hammer stone against a bone or antler punch which is +directed at the proper place on the blade-core. The blade-core has to +be well supported or gripped while this is going on. To get a good +flint blade tool takes a great deal of know-how. + +Remember that a tradition in stone tools means no more than that some +particular way of making the tools got started and lasted a long time. +Men who made some tools in one tradition or set of habits would also +make other tools for different purposes by means of another tradition +or set of habits. It was even possible for the two sets of habits to +become combined. + + +THE EARLIEST BLADE TOOLS + +The oldest blade tools we have found were deep down in the layers of +the Mount Carmel caves, in Tabun Eb and Ea. Similar tools have been +found in equally early cave levels in Syria; their popularity there +seems to fluctuate a bit. Some more or less parallel-sided flakes are +known in the Levalloisian industry in France, but they are probably +no earlier than Tabun E. The Tabun blades are part of a local late +�Acheulean� industry, which is characterized by core-biface �hand +axes,� but which has many flake tools as well. Professor F. E. +Zeuner believes that this industry may be more than 120,000 years old; +actually its date has not yet been fixed, but it is very old--older +than the fossil finds of modern-like men in the same caves. + +[Illustration: SUCCESSION OF ICE AGE FLINT TYPES, INDUSTRIES, AND +ASSEMBLAGES, AND OF FOSSIL MEN, IN NORTHWESTERN EURAFRASIA] + +For some reason, the habit of making blades in Palestine and Syria was +interrupted. Blades only reappeared there at about the same time they +were first made in Europe, some time after 45,000 years ago; that is, +after the first phase of the last glaciation was ended. + +[Illustration: BACKED BLADE] + +We are not sure just where the earliest _persisting_ habits for the +production of blade tools developed. Impressed by the very early +momentary appearance of blades at Tabun on Mount Carmel, Professor +Dorothy A. Garrod first favored the Near East as a center of origin. +She spoke of �some as yet unidentified Asiatic centre,� which she +thought might be in the highlands of Iran or just beyond. But more +recent work has been done in this area, especially by Professor Coon, +and the blade tools do not seem to have an early appearance there. When +the blade tools reappear in the Syro-Palestinian area, they do so in +industries which also include Levalloiso-Mousterian flake tools. From +the point of view of form and workmanship, the blade tools themselves +are not so fine as those which seem to be making their appearance +in western Europe about the same time. There is a characteristic +Syro-Palestinian flake point, possibly a projectile tip, called the +Emiran, which is not known from Europe. The appearance of blade tools, +together with Levalloiso-Mousterian flakes, continues even after the +Emiran point has gone out of use. + +It seems clear that the production of blade tools did not immediately +swamp the set of older habits in Europe, too; the use of flake +tools also continued there. This was not so apparent to the older +archeologists, whose attention was focused on individual tool types. It +is not, in fact, impossible--although it is certainly not proved--that +the technique developed in the preparation of the Levalloisian tortoise +core (and the striking of the Levalloisian flake from it) might have +followed through to the conical core and punch technique for the +production of blades. Professor Garrod is much impressed with the speed +of change during the later phases of the last glaciation, and its +probable consequences. She speaks of �the greater number of industries +having enough individual character to be classified as distinct ... +since evolution now starts to outstrip diffusion.� Her �evolution� here +is of course an industrial evolution rather than a biological one. +Certainly the people of Europe had begun to make blade tools during +the warm spell after the first phase of the last glaciation. By about +40,000 years ago blades were well established. The bones of the blade +tool makers we�ve found so far indicate that anatomically modern men +had now certainly appeared. Unfortunately, only a few fossil men have +so far been found from the very beginning of the blade tool range in +Europe (or elsewhere). What I certainly shall _not_ tell you is that +conquering bands of fine, strong, anatomically modern men, armed with +superior blade tools, came sweeping out of the East to exterminate the +lowly Neanderthalers. Even if we don�t know exactly what happened, I�d +lay a good bet it wasn�t that simple. + +We do know a good deal about different blade industries in Europe. +Almost all of them come from cave layers. There is a great deal of +complication in what we find. The chart (p. 72) tries to simplify +this complication; in fact, it doubtless simplifies it too much. But +it may suggest all the complication of industries which is going +on at this time. You will note that the upper portion of my much +simpler chart (p. 65) covers the same material (in the section +marked �Various Blade-Tool Industries�). That chart is certainly too +simplified. + +You will realize that all this complication comes not only from +the fact that we are finding more material. It is due also to the +increasing ability of men to adapt themselves to a great variety of +situations. Their tools indicate this adaptiveness. We know there was +a good deal of climatic change at this time. The plants and animals +that men used for food were changing, too. The great variety of tools +and industries we now find reflect these changes and the ability of men +to keep up with the times. Now, for example, is the first time we are +sure that there are tools to _make_ other tools. They also show men�s +increasing ability to adapt themselves. + + +SPECIAL TYPES OF BLADE TOOLS + +The most useful tools that appear at this time were made from blades. + + 1. The �backed� blade. This is a knife made of a flint blade, with + one edge purposely blunted, probably to save the user�s fingers + from being cut. There are several shapes of backed blades (p. + 73). + + [Illustration: TWO BURINS] + + 2. The _burin_ or �graver.� The burin was the original chisel. Its + cutting edge is _transverse_, like a chisel�s. Some burins are + made like a screw-driver, save that burins are sharp. Others have + edges more like the blade of a chisel or a push plane, with + only one bevel. Burins were probably used to make slots in wood + and bone; that is, to make handles or shafts for other tools. + They must also be the tools with which much of the engraving on + bone (see p. 83) was done. There is a bewildering variety of + different kinds of burins. + +[Illustration: TANGED POINT] + + 3. The �tanged� point. These stone points were used to tip arrows or + light spears. They were made from blades, and they had a long tang + at the bottom where they were fixed to the shaft. At the place + where the tang met the main body of the stone point, there was + a marked �shoulder,� the beginnings of a barb. Such points had + either one or two shoulders. + +[Illustration: NOTCHED BLADE] + + 4. The �notched� or �strangulated� blade. Along with the points for + arrows or light spears must go a tool to prepare the arrow or + spear shaft. Today, such a tool would be called a �draw-knife� or + a �spoke-shave,� and this is what the notched blades probably are. + Our spoke-shaves have sharp straight cutting blades and really + �shave.� Notched blades of flint probably scraped rather than cut. + + 5. The �awl,� �drill,� or �borer.� These blade tools are worked out + to a spike-like point. They must have been used for making holes + in wood, bone, shell, skin, or other things. + +[Illustration: DRILL OR AWL] + + 6. The �end-scraper on a blade� is a tool with one or both ends + worked so as to give a good scraping edge. It could have been used + to hollow out wood or bone, scrape hides, remove bark from trees, + and a number of other things (p. 78). + +There is one very special type of flint tool, which is best known from +western Europe in an industry called the Solutrean. These tools were +usually made of blades, but the best examples are so carefully worked +on both sides (bifacially) that it is impossible to see the original +blade. This tool is + + 7. The �laurel leaf� point. Some of these tools were long and + dagger-like, and must have been used as knives or daggers. Others + were small, called �willow leaf,� and must have been mounted on + spear or arrow shafts. Another typical Solutrean tool is the + �shouldered� point. Both the �laurel leaf� and �shouldered� point + types are illustrated (see above and p. 79). + +[Illustration: END-SCRAPER ON A BLADE] + +[Illustration: LAUREL LEAF POINT] + +The industries characterized by tools in the blade tradition also +yield some flake and core tools. We will end this list with two types +of tools that appear at this time. The first is made of a flake; the +second is a core tool. + +[Illustration: SHOULDERED POINT] + + 8. The �keel-shaped round scraper� is usually small and quite round, + and has had chips removed up to a peak in the center. It is called + �keel-shaped� because it is supposed to look (when upside down) + like a section through a boat. Actually, it looks more like a tent + or an umbrella. Its outer edges are sharp all the way around, and + it was probably a general purpose scraping tool (see illustration, + p. 81). + + 9. The �keel-shaped nosed scraper� is a much larger and heavier tool + than the round scraper. It was made on a core with a flat bottom, + and has one nicely worked end or �nose.� Such tools are usually + large enough to be easily grasped, and probably were used like + push planes (see illustration, p. 81). + +[Illustration: KEEL-SHAPED ROUND SCRAPER] + +[Illustration: KEEL-SHAPED NOSED SCRAPER] + +The stone tools (usually made of flint) we have just listed are among +the most easily recognized blade tools, although they show differences +in detail at different times. There are also many other kinds. Not +all of these tools appear in any one industry at one time. Thus the +different industries shown in the chart (p. 72) each have only some +of the blade tools we�ve just listed, and also a few flake tools. Some +industries even have a few core tools. The particular types of blade +tools appearing in one cave layer or another, and the frequency of +appearance of the different types, tell which industry we have in each +layer. + + +OTHER KINDS OF TOOLS + +By this time in Europe--say from about 40,000 to about 10,000 years +ago--we begin to find other kinds of material too. Bone tools begin +to appear. There are knives, pins, needles with eyes, and little +double-pointed straight bars of bone that were probably fish-hooks. The +fish-line would have been fastened in the center of the bar; when the +fish swallowed the bait, the bar would have caught cross-wise in the +fish�s mouth. + +One quite special kind of bone tool is a long flat point for a light +spear. It has a deep notch cut up into the breadth of its base, and is +called a �split-based bone point� (p. 82). We know examples of bone +beads from these times, and of bone handles for flint tools. Pierced +teeth of some animals were worn as beads or pendants, but I am not sure +that elks� teeth were worn this early. There are even spool-shaped +�buttons� or toggles. + +[Illustration: SPLIT-BASED BONE POINT] + +[Illustration: SPEAR-THROWER] + +[Illustration: BONE HARPOON] + +Antler came into use for tools, especially in central and western +Europe. We do not know the use of one particular antler tool that +has a large hole bored in one end. One suggestion is that it was +a thong-stropper used to strop or work up hide thongs (see +illustration, below); another suggestion is that it was an arrow-shaft +straightener. + +Another interesting tool, usually of antler, is the spear-thrower, +which is little more than a stick with a notch or hook on one end. +The hook fits into the butt end of the spear, and the length of the +spear-thrower allows you to put much more power into the throw (p. +82). It works on pretty much the same principle as the sling. + +Very fancy harpoons of antler were also made in the latter half of +the period in western Europe. These harpoons had barbs on one or both +sides and a base which would slip out of the shaft (p. 82). Some have +engraved decoration. + + +THE BEGINNING OF ART + +[Illustration: THONG-STROPPER] + +In western Europe, at least, the period saw the beginning of several +kinds of art work. It is handy to break the art down into two great +groups: the movable art, and the cave paintings and sculpture. The +movable art group includes the scratchings, engravings, and modeling +which decorate tools and weapons. Knives, stroppers, spear-throwers, +harpoons, and sometimes just plain fragments of bone or antler are +often carved. There is also a group of large flat pebbles which seem +almost to have served as sketch blocks. The surfaces of these various +objects may show animals, or rather abstract floral designs, or +geometric designs. + +[Illustration: �VENUS� FIGURINE FROM WILLENDORF] + +Some of the movable art is not done on tools. The most remarkable +examples of this class are little figures of women. These women seem to +be pregnant, and their most female characteristics are much emphasized. +It is thought that these �Venus� or �Mother-goddess� figurines may be +meant to show the great forces of nature--fertility and the birth of +life. + + +CAVE PAINTINGS + +In the paintings on walls and ceilings of caves we have some examples +that compare with the best art of any time. The subjects were usually +animals, the great cold-weather beasts of the end of the Ice Age: the +mammoth, the wooly rhinoceros, the bison, the reindeer, the wild horse, +the bear, the wild boar, and wild cattle. As in the movable art, there +are different styles in the cave art. The really great cave art is +pretty well restricted to southern France and Cantabrian (northwestern) +Spain. + +There are several interesting things about the �Franco-Cantabrian� cave +art. It was done deep down in the darkest and most dangerous parts of +the caves, although the men lived only in the openings of caves. If you +think what they must have had for lights--crude lamps of hollowed stone +have been found, which must have burned some kind of oil or grease, +with a matted hair or fiber wick--and of the animals that may have +lurked in the caves, you�ll understand the part about danger. Then, +too, we�re sure the pictures these people painted were not simply to be +looked at and admired, for they painted one picture right over other +pictures which had been done earlier. Clearly, it was the _act_ of +_painting_ that counted. The painter had to go way down into the most +mysterious depths of the earth and create an animal in paint. Possibly +he believed that by doing this he gained some sort of magic power over +the same kind of animal when he hunted it in the open air. It certainly +doesn�t look as if he cared very much about the picture he painted--as +a finished product to be admired--for he or somebody else soon went +down and painted another animal right over the one he had done. + +The cave art of the Franco-Cantabrian style is one of the great +artistic achievements of all time. The subjects drawn are almost always +the larger animals of the time: the bison, wild cattle and horses, the +wooly rhinoceros, the mammoth, the wild boar, and the bear. In some of +the best examples, the beasts are drawn in full color and the paintings +are remarkably alive and charged with energy. They come from the hands +of men who knew the great animals well--knew the feel of their fur, the +tremendous drive of their muscles, and the danger one faced when he +hunted them. + +Another artistic style has been found in eastern Spain. It includes +lively drawings, often of people hunting with bow and arrow. The East +Spanish art is found on open rock faces and in rock-shelters. It is +less spectacular and apparently more recent than the Franco-Cantabrian +cave art. + + +LIFE AT THE END OF THE ICE AGE IN EUROPE + +Life in these times was probably as good as a hunter could expect it +to be. Game and fish seem to have been plentiful; berries and wild +fruits probably were, too. From France to Russia, great pits or +piles of animal bones have been found. Some of this killing was done +as our Plains Indians killed the buffalo--by stampeding them over +steep river banks or cliffs. There were also good tools for hunting, +however. In western Europe, people lived in the openings of caves and +under overhanging rocks. On the great plains of eastern Europe, very +crude huts were being built, half underground. The first part of this +time must have been cold, for it was the middle and end phases of the +last great glaciation. Northern Europe from Scotland to Scandinavia, +northern Germany and Russia, and also the higher mountains to the +south, were certainly covered with ice. But people had fire, and the +needles and tools that were used for scraping hides must mean that they +wore clothing. + +It is clear that men were thinking of a great variety of things beside +the tools that helped them get food and shelter. Such burials as we +find have more grave-gifts than before. Beads and ornaments and often +flint, bone, or antler tools are included in the grave, and sometimes +the body is sprinkled with red ochre. Red is the color of blood, which +means life, and of fire, which means heat. Professor Childe wonders if +the red ochre was a pathetic attempt at magic--to give back to the body +the heat that had gone from it. But pathetic or not, it is sure proof +that these people were already moved by death as men still are moved by +it. + +Their art is another example of the direction the human mind was +taking. And when I say human, I mean it in the fullest sense, for this +is the time in which fully modern man has appeared. On page 34, we +spoke of the Cro-Magnon group and of the Combe Capelle-Br�nn group of +Caucasoids and of the Grimaldi �Negroids,� who are no longer believed +to be Negroid. I doubt that any one of these groups produced most of +the achievements of the times. It�s not yet absolutely sure which +particular group produced the great cave art. The artists were almost +certainly a blend of several (no doubt already mixed) groups. The pair +of Grimaldians were buried in a grave with a sprinkling of red ochre, +and were provided with shell beads and ornaments and with some blade +tools of flint. Regardless of the different names once given them by +the human paleontologists, each of these groups seems to have shared +equally in the cultural achievements of the times, for all that the +archeologists can say. + + +MICROLITHS + +One peculiar set of tools seems to serve as a marker for the very last +phase of the Ice Age in southwestern Europe. This tool-making habit is +also found about the shore of the Mediterranean basin, and it moved +into northern Europe as the last glaciation pulled northward. People +began making blade tools of very small size. They learned how to chip +very slender and tiny blades from a prepared core. Then they made these +little blades into tiny triangles, half-moons (�lunates�), trapezoids, +and several other geometric forms. These little tools are called +�microliths.� They are so small that most of them must have been fixed +in handles or shafts. + +[Illustration: MICROLITHS + + BLADE FRAGMENT + BURIN + LUNATE + TRAPEZOID + SCALENE TRIANGLE + ARROWHEAD] + +We have found several examples of microliths mounted in shafts. In +northern Europe, where their use soon spread, the microlithic triangles +or lunates were set in rows down each side of a bone or wood point. +One corner of each little triangle stuck out, and the whole thing +made a fine barbed harpoon. In historic times in Egypt, geometric +trapezoidal microliths were still in use as arrowheads. They were +fastened--broad end out--on the end of an arrow shaft. It seems queer +to give an arrow a point shaped like a �T.� Actually, the little points +were very sharp, and must have pierced the hides of animals very +easily. We also think that the broader cutting edge of the point may +have caused more bleeding than a pointed arrowhead would. In hunting +fleet-footed animals like the gazelle, which might run for miles after +being shot with an arrow, it was an advantage to cause as much bleeding +as possible, for the animal would drop sooner. + +We are not really sure where the microliths were first invented. There +is some evidence that they appear early in the Near East. Their use +was very common in northwest Africa but this came later. The microlith +makers who reached south Russia and central Europe possibly moved up +out of the Near East. Or it may have been the other way around; we +simply don�t yet know. + +Remember that the microliths we are talking about here were made from +carefully prepared little blades, and are often geometric in outline. +Each microlithic industry proper was made up, in good part, of such +tiny blade tools. But there were also some normal-sized blade tools and +even some flake scrapers, in most microlithic industries. I emphasize +this bladelet and the geometric character of the microlithic industries +of the western Old World, since there has sometimes been confusion in +the matter. Sometimes small flake chips, utilized as minute pointed +tools, have been called �microliths.� They may be _microlithic_ in size +in terms of the general meaning of the word, but they do not seem to +belong to the sub-tradition of the blade tool preparation habits which +we have been discussing here. + + +LATER BLADE-TOOL INDUSTRIES OF THE NEAR EAST AND AFRICA + +The blade-tool industries of normal size we talked about earlier spread +from Europe to central Siberia. We noted that blade tools were made +in western Asia too, and early, although Professor Garrod is no longer +sure that the whole tradition originated in the Near East. If you look +again at my chart (p. 72) you will note that in western Asia I list +some of the names of the western European industries, but with the +qualification �-like� (for example, �Gravettian-like�). The western +Asiatic blade-tool industries do vaguely recall some aspects of those +of western Europe, but we would probably be better off if we used +completely local names for them. The �Emiran� of my chart is such an +example; its industry includes a long spike-like blade point which has +no western European counterpart. + +When we last spoke of Africa (p. 66), I told you that stone tools +there were continuing in the Levalloisian flake tradition, and were +becoming smaller. At some time during this process, two new tool +types appeared in northern Africa: one was the Aterian point with +a tang (p. 67), and the other was a sort of �laurel leaf� point, +called the �Sbaikian.� These two tool types were both produced from +flakes. The Sbaikian points, especially, are roughly similar to some +of the Solutrean points of Europe. It has been suggested that both the +Sbaikian and Aterian points may be seen on their way to France through +their appearance in the Spanish cave deposits of Parpallo, but there is +also a rival �pre-Solutrean� in central Europe. We still do not know +whether there was any contact between the makers of these north African +tools and the Solutrean tool-makers. What does seem clear is that the +blade-tool tradition itself arrived late in northern Africa. + + +NETHER AFRICA + +Blade tools and �laurel leaf� points and some other probably late +stone tool types also appear in central and southern Africa. There +are geometric microliths on bladelets and even some coarse pottery in +east Africa. There is as yet no good way of telling just where these +items belong in time; in broad geological terms they are �late.� +Some people have guessed that they are as early as similar European +and Near Eastern examples, but I doubt it. The makers of small-sized +Levalloisian flake tools occupied much of Africa until very late in +time. + + +THE FAR EAST + +India and the Far East still seem to be going their own way. In India, +some blade tools have been found. These are not well dated, save that +we believe they must be post-Pleistocene. In the Far East it looks as +if the old chopper-tool tradition was still continuing. For Burma, +Dr. Movius feels this is fairly certain; for China he feels even more +certain. Actually, we know very little about the Far East at about the +time of the last glaciation. This is a shame, too, as you will soon +agree. + + +THE NEW WORLD BECOMES INHABITED + +At some time toward the end of the last great glaciation--almost +certainly after 20,000 years ago--people began to move over Bering +Strait, from Asia into America. As you know, the American Indians have +been assumed to be basically Mongoloids. New studies of blood group +types make this somewhat uncertain, but there is no doubt that the +ancestors of the American Indians came from Asia. + +The stone-tool traditions of Europe, Africa, the Near and Middle East, +and central Siberia, did _not_ move into the New World. With only a +very few special or late exceptions, there are _no_ core-bifaces, +flakes, or blade tools of the Old World. Such things just haven�t been +found here. + +This is why I say it�s a shame we don�t know more of the end of the +chopper-tool tradition in the Far East. According to Weidenreich, +the Mongoloids were in the Far East long before the end of the last +glaciation. If the genetics of the blood group types do demand a +non-Mongoloid ancestry for the American Indians, who else may have been +in the Far East 25,000 years ago? We know a little about the habits +for making stone tools which these first people brought with them, +and these habits don�t conform with those of the western Old World. +We�d better keep our eyes open for whatever happened to the end of +the chopper-tool tradition in northern China; already there are hints +that it lasted late there. Also we should watch future excavations +in eastern Siberia. Perhaps we shall find the chopper-tool tradition +spreading up that far. + + +THE NEW ERA + +Perhaps it comes in part from the way I read the evidence and perhaps +in part it is only intuition, but I feel that the materials of this +chapter suggest a new era in the ways of life. Before about 40,000 +years ago, people simply �gathered� their food, wandering over large +areas to scavenge or to hunt in a simple sort of way. But here we +have seen them �settling-in� more, perhaps restricting themselves in +their wanderings and adapting themselves to a given locality in more +intensive ways. This intensification might be suggested by the word +�collecting.� The ways of life we described in the earlier chapters +were �food-gathering� ways, but now an era of �food-collecting� has +begun. We shall see further intensifications of it in the next chapter. + + + + +End and PRELUDE + +[Illustration] + + +Up to the end of the last glaciation, we prehistorians have a +relatively comfortable time schedule. The farther back we go the less +exact we can be about time and details. Elbow-room of five, ten, +even fifty or more thousands of years becomes available for us to +maneuver in as we work backward in time. But now our story has come +forward to the point where more exact methods of dating are at hand. +The radioactive carbon method reaches back into the span of the last +glaciation. There are other methods, developed by the geologists and +paleobotanists, which supplement and extend the usefulness of the +radioactive carbon dates. And, happily, as our means of being more +exact increases, our story grows more exciting. There are also more +details of culture for us to deal with, which add to the interest. + + +CHANGES AT THE END OF THE ICE AGE + +The last great glaciation of the Ice Age was a two-part affair, with a +sub-phase at the end of the second part. In Europe the last sub-phase +of this glaciation commenced somewhere around 15,000 years ago. Then +the glaciers began to melt back, for the last time. Remember that +Professor Antevs (p. 19) isn�t sure the Ice Age is over yet! This +melting sometimes went by fits and starts, and the weather wasn�t +always changing for the better; but there was at least one time when +European weather was even better than it is now. + +The melting back of the glaciers and the weather fluctuations caused +other changes, too. We know a fair amount about these changes in +Europe. In an earlier chapter, we said that the whole Ice Age was a +matter of continual change over long periods of time. As the last +glaciers began to melt back some interesting things happened to mankind. + +In Europe, along with the melting of the last glaciers, geography +itself was changing. Britain and Ireland had certainly become islands +by 5000 B.C. The Baltic was sometimes a salt sea, sometimes a large +fresh-water lake. Forests began to grow where the glaciers had been, +and in what had once been the cold tundra areas in front of the +glaciers. The great cold-weather animals--the mammoth and the wooly +rhinoceros--retreated northward and finally died out. It is probable +that the efficient hunting of the earlier people of 20,000 or 25,000 +to about 12,000 years ago had helped this process along (see p. 86). +Europeans, especially those of the post-glacial period, had to keep +changing to keep up with the times. + +The archeological materials for the time from 10,000 to 6000 B.C. seem +simpler than those of the previous five thousand years. The great cave +art of France and Spain had gone; so had the fine carving in bone and +antler. Smaller, speedier animals were moving into the new forests. New +ways of hunting them, or ways of getting other food, had to be found. +Hence, new tools and weapons were necessary. Some of the people who +moved into northern Germany were successful reindeer hunters. Then the +reindeer moved off to the north, and again new sources of food had to +be found. + + +THE READJUSTMENTS COMPLETED IN EUROPE + +After a few thousand years, things began to look better. Or at least +we can say this: By about 6000 B.C. we again get hotter archeological +materials. The best of these come from the north European area: +Britain, Belgium, Holland, Denmark, north Germany, southern Norway and +Sweden. Much of this north European material comes from bogs and swamps +where it had become water-logged and has kept very well. Thus we have +much more complete _assemblages_[4] than for any time earlier. + + [4] �Assemblage� is a useful word when there are different kinds of + archeological materials belonging together, from one area and of + one time. An assemblage is made up of a number of �industries� + (that is, all the tools in chipped stone, all the tools in + bone, all the tools in wood, the traces of houses, etc.) and + everything else that manages to survive, such as the art, the + burials, the bones of the animals used as food, and the traces + of plant foods; in fact, everything that has been left to us + and can be used to help reconstruct the lives of the people to + whom it once belonged. Our own present-day �assemblage� would be + the sum total of all the objects in our mail-order catalogues, + department stores and supply houses of every sort, our churches, + our art galleries and other buildings, together with our roads, + canals, dams, irrigation ditches, and any other traces we might + leave of ourselves, from graves to garbage dumps. Not everything + would last, so that an archeologist digging us up--say 2,000 + years from now--would find only the most durable items in our + assemblage. + +The best known of these assemblages is the _Maglemosian_, named after a +great Danish peat-swamp where much has been found. + +[Illustration: SKETCH OF MAGLEMOSIAN ASSEMBLAGE + + CHIPPED STONE + HEMP + GROUND STONE + BONE AND ANTLER + WOOD] + +In the Maglemosian assemblage the flint industry was still very +important. Blade tools, tanged arrow points, and burins were still +made, but there were also axes for cutting the trees in the new +forests. Moreover, the tiny microlithic blades, in a variety of +geometric forms, are also found. Thus, a specialized tradition that +possibly began east of the Mediterranean had reached northern Europe. +There was also a ground stone industry; some axes and club-heads were +made by grinding and polishing rather than by chipping. The industries +in bone and antler show a great variety of tools: axes, fish-hooks, +fish spears, handles and hafts for other tools, harpoons, and clubs. +A remarkable industry in wood has been preserved. Paddles, sled +runners, handles for tools, and bark floats for fish-nets have been +found. There are even fish-nets made of plant fibers. Canoes of some +kind were no doubt made. Bone and antler tools were decorated with +simple patterns, and amber was collected. Wooden bows and arrows are +found. + +It seems likely that the Maglemosian bog finds are remains of summer +camps, and that in winter the people moved to higher and drier regions. +Childe calls them the �Forest folk�; they probably lived much the +same sort of life as did our pre-agricultural Indians of the north +central states. They hunted small game or deer; they did a great deal +of fishing; they collected what plant food they could find. In fact, +their assemblage shows us again that remarkable ability of men to adapt +themselves to change. They had succeeded in domesticating the dog; he +was still a very wolf-like dog, but his long association with mankind +had now begun. Professor Coon believes that these people were direct +descendants of the men of the glacial age and that they had much the +same appearance. He believes that most of the Ice Age survivors still +extant are living today in the northwestern European area. + + +SOUTH AND CENTRAL EUROPE PERHAPS AS READJUSTED AS THE NORTH + +There is always one trouble with things that come from areas where +preservation is exceptionally good: The very quantity of materials in +such an assemblage tends to make things from other areas look poor +and simple, although they may not have been so originally at all. The +assemblages of the people who lived to the south of the Maglemosian +area may also have been quite large and varied; but, unfortunately, +relatively little of the southern assemblages has lasted. The +water-logged sites of the Maglemosian area preserved a great deal +more. Hence the Maglemosian itself _looks_ quite advanced to us, when +we compare it with the few things that have happened to last in other +areas. If we could go back and wander over the Europe of eight thousand +years ago, we would probably find that the peoples of France, central +Europe, and south central Russia were just as advanced as those of the +north European-Baltic belt. + +South of the north European belt the hunting-food-collecting peoples +were living on as best they could during this time. One interesting +group, which seems to have kept to the regions of sandy soil and scrub +forest, made great quantities of geometric microliths. These are the +materials called _Tardenoisian_. The materials of the �Forest folk� of +France and central Europe generally are called _Azilian_; Dr. Movius +believes the term might best be restricted to the area south of the +Loire River. + + +HOW MUCH REAL CHANGE WAS THERE? + +You can see that no really _basic_ change in the way of life has yet +been described. Childe sees the problem that faced the Europeans of +10,000 to 3000 B.C. as a problem in readaptation to the post-glacial +forest environment. By 6000 B.C. some quite successful solutions of +the problem--like the Maglemosian--had been made. The upsets that came +with the melting of the last ice gradually brought about all sorts of +changes in the tools and food-getting habits, but the people themselves +were still just as much simple hunters, fishers, and food-collectors as +they had been in 25,000 B.C. It could be said that they changed just +enough so that they would not have to change. But there is a bit more +to it than this. + +Professor Mathiassen of Copenhagen, who knows the archeological remains +of this time very well, poses a question. He speaks of the material +as being neither rich nor progressive, in fact �rather stagnant,� but +he goes on to add that the people had a certain �receptiveness� and +were able to adapt themselves quickly when the next change did come. +My own understanding of the situation is that the �Forest folk� made +nothing as spectacular as had the producers of the earlier Magdalenian +assemblage and the Franco-Cantabrian art. On the other hand, they +_seem_ to have been making many more different kinds of tools for many +more different kinds of tasks than had their Ice Age forerunners. I +emphasize �seem� because the preservation in the Maglemosian bogs +is very complete; certainly we cannot list anywhere near as many +different things for earlier times as we did for the Maglemosians +(p. 94). I believe this experimentation with all kinds of new tools +and gadgets, this intensification of adaptiveness (p. 91), this +�receptiveness,� even if it is still only pointed toward hunting, +fishing, and food-collecting, is an important thing. + +Remember that the only marker we have handy for the _beginning_ of +this tendency toward �receptiveness� and experimentation is the +little microlithic blade tools of various geometric forms. These, we +saw, began before the last ice had melted away, and they lasted on +in use for a very long time. I wish there were a better marker than +the microliths but I do not know of one. Remember, too, that as yet +we can only use the microliths as a marker in Europe and about the +Mediterranean. + + +CHANGES IN OTHER AREAS? + +All this last section was about Europe. How about the rest of the world +when the last glaciers were melting away? + +We simply don�t know much about this particular time in other parts +of the world except in Europe, the Mediterranean basin and the Middle +East. People were certainly continuing to move into the New World by +way of Siberia and the Bering Strait about this time. But for the +greater part of Africa and Asia, we do not know exactly what was +happening. Some day, we shall no doubt find out; today we are without +clear information. + + +REAL CHANGE AND PRELUDE IN THE NEAR EAST + +The appearance of the microliths and the developments made by the +�Forest folk� of northwestern Europe also mark an end. They show us +the terminal phase of the old food-collecting way of life. It grows +increasingly clear that at about the same time that the Maglemosian and +other �Forest folk� were adapting themselves to hunting, fishing, and +collecting in new ways to fit the post-glacial environment, something +completely new was being made ready in western Asia. + +Unfortunately, we do not have as much understanding of the climate and +environment of the late Ice Age in western Asia as we have for most +of Europe. Probably the weather was never so violent or life quite +so rugged as it was in northern Europe. We know that the microliths +made their appearance in western Asia at least by 10,000 B.C. and +possibly earlier, marking the beginning of the terminal phase of +food-collecting. Then, gradually, we begin to see the build-up towards +the first _basic change_ in human life. + +This change amounted to a revolution just as important as the +Industrial Revolution. In it, men first learned to domesticate +plants and animals. They began _producing_ their food instead of +simply gathering or collecting it. When their food-production +became reasonably effective, people could and did settle down in +village-farming communities. With the appearance of the little farming +villages, a new way of life was actually under way. Professor Childe +has good reason to speak of the �food-producing revolution,� for it was +indeed a revolution. + + +QUESTIONS ABOUT CAUSE + +We do not yet know _how_ and _why_ this great revolution took place. We +are only just beginning to put the questions properly. I suspect the +answers will concern some delicate and subtle interplay between man and +nature. Clearly, both the level of culture and the natural condition of +the environment must have been ready for the great change, before the +change itself could come about. + +It is going to take years of co-operative field work by both +archeologists and the natural scientists who are most helpful to them +before the _how_ and _why_ answers begin to appear. Anthropologically +trained archeologists are fascinated with the cultures of men in times +of great change. About ten or twelve thousand years ago, the general +level of culture in many parts of the world seems to have been ready +for change. In northwestern Europe, we saw that cultures �changed +just enough so that they would not have to change.� We linked this to +environmental changes with the coming of post-glacial times. + +In western Asia, we archeologists can prove that the food-producing +revolution actually took place. We can see _the_ important consequence +of effective domestication of plants and animals in the appearance of +the settled village-farming community. And within the village-farming +community was the seed of civilization. The way in which effective +domestication of plants and animals came about, however, must also be +linked closely with the natural environment. Thus the archeologists +will not solve the _how_ and _why_ questions alone--they will need the +help of interested natural scientists in the field itself. + + +PRECONDITIONS FOR THE REVOLUTION + +Especially at this point in our story, we must remember how culture and +environment go hand in hand. Neither plants nor animals domesticate +themselves; men domesticate them. Furthermore, men usually domesticate +only those plants and animals which are useful. There is a good +question here: What is cultural usefulness? But I shall side-step it to +save time. Men cannot domesticate plants and animals that do not exist +in the environment where the men live. Also, there are certainly some +animals and probably some plants that resist domestication, although +they might be useful. + +This brings me back again to the point that _both_ the level of culture +and the natural condition of the environment--with the proper plants +and animals in it--must have been ready before domestication could +have happened. But this is precondition, not cause. Why did effective +food-production happen first in the Near East? Why did it happen +independently in the New World slightly later? Why also in the Far +East? Why did it happen at all? Why are all human beings not still +living as the Maglemosians did? These are the questions we still have +to face. + + +CULTURAL �RECEPTIVENESS� AND PROMISING ENVIRONMENTS + +Until the archeologists and the natural scientists--botanists, +geologists, zoologists, and general ecologists--have spent many more +years on the problem, we shall not have full _how_ and _why_ answers. I +do think, however, that we are beginning to understand what to look for. + +We shall have to learn much more of what makes the cultures of men +�receptive� and experimental. Did change in the environment alone +force it? Was it simply a case of Professor Toynbee�s �challenge and +response?� I cannot believe the answer is quite that simple. Were it +so simple, we should want to know why the change hadn�t come earlier, +along with earlier environmental changes. We shall not know the answer, +however, until we have excavated the traces of many more cultures of +the time in question. We shall doubtless also have to learn more about, +and think imaginatively about, the simpler cultures still left today. +The �mechanics� of culture in general will be bound to interest us. + +It will also be necessary to learn much more of the environments of +10,000 to 12,000 years ago. In which regions of the world were the +natural conditions most promising? Did this promise include plants and +animals which could be domesticated, or did it only offer new ways of +food-collecting? There is much work to do on this problem, but we are +beginning to get some general hints. + +Before I begin to detail the hints we now have from western Asia, I +want to do two things. First, I shall tell you of an old theory as to +how food-production might have appeared. Second, I will bother you with +some definitions which should help us in our thinking as the story goes +on. + + +AN OLD THEORY AS TO THE CAUSE OF THE REVOLUTION + +The idea that change would result, if the balance between nature +and culture became upset, is of course not a new one. For at least +twenty-five years, there has been a general theory as to _how_ the +food-producing revolution happened. This theory depends directly on the +idea of natural change in the environment. + +The five thousand years following about 10,000 B.C. must have been +very difficult ones, the theory begins. These were the years when +the most marked melting of the last glaciers was going on. While the +glaciers were in place, the climate to the south of them must have been +different from the climate in those areas today. You have no doubt read +that people once lived in regions now covered by the Sahara Desert. +This is true; just when is not entirely clear. The theory is that +during the time of the glaciers, there was a broad belt of rain winds +south of the glaciers. These rain winds would have kept north Africa, +the Nile Valley, and the Middle East green and fertile. But when the +glaciers melted back to the north, the belt of rain winds is supposed +to have moved north too. Then the people living south and east of the +Mediterranean would have found that their water supply was drying up, +that the animals they hunted were dying or moving away, and that the +plant foods they collected were dried up and scarce. + +According to the theory, all this would have been true except in the +valleys of rivers and in oases in the growing deserts. Here, in the +only places where water was left, the men and animals and plants would +have clustered. They would have been forced to live close to one +another, in order to live at all. Presently the men would have seen +that some animals were more useful or made better food than others, +and so they would have begun to protect these animals from their +natural enemies. The men would also have been forced to try new plant +foods--foods which possibly had to be prepared before they could be +eaten. Thus, with trials and errors, but by being forced to live close +to plants and animals, men would have learned to domesticate them. + + +THE OLD THEORY TOO SIMPLE FOR THE FACTS + +This theory was set up before we really knew anything in detail about +the later prehistory of the Near and Middle East. We now know that +the facts which have been found don�t fit the old theory at all well. +Also, I have yet to find an American meteorologist who feels that we +know enough about the changes in the weather pattern to say that it can +have been so simple and direct. And, of course, the glacial ice which +began melting after 12,000 years ago was merely the last sub-phase of +the last great glaciation. There had also been three earlier periods +of great alpine glaciers, and long periods of warm weather in between. +If the rain belt moved north as the glaciers melted for the last time, +it must have moved in the same direction in earlier times. Thus, the +forced neighborliness of men, plants, and animals in river valleys and +oases must also have happened earlier. Why didn�t domestication happen +earlier, then? + +Furthermore, it does not seem to be in the oases and river valleys +that we have our first or only traces of either food-production +or the earliest farming villages. These traces are also in the +hill-flanks of the mountains of western Asia. Our earliest sites of the +village-farmers do not seem to indicate a greatly different climate +from that which the same region now shows. In fact, everything we now +know suggests that the old theory was just too simple an explanation to +have been the true one. The only reason I mention it--beyond correcting +the ideas you may get in the general texts--is that it illustrates the +kind of thinking we shall have to do, even if it is doubtless wrong in +detail. + +We archeologists shall have to depend much more than we ever have on +the natural scientists who can really help us. I can tell you this from +experience. I had the great good fortune to have on my expedition staff +in Iraq in 1954-55, a geologist, a botanist, and a zoologist. Their +studies added whole new bands of color to my spectrum of thinking about +_how_ and _why_ the revolution took place and how the village-farming +community began. But it was only a beginning; as I said earlier, we are +just now learning to ask the proper questions. + + +ABOUT STAGES AND ERAS + +Now come some definitions, so I may describe my material more easily. +Archeologists have always loved to make divisions and subdivisions +within the long range of materials which they have found. They often +disagree violently about which particular assemblage of material +goes into which subdivision, about what the subdivisions should be +named, about what the subdivisions really mean culturally. Some +archeologists, probably through habit, favor an old scheme of Grecized +names for the subdivisions: paleolithic, mesolithic, neolithic. I +refuse to use these words myself. They have meant too many different +things to too many different people and have tended to hide some pretty +fuzzy thinking. Probably you haven�t even noticed my own scheme of +subdivision up to now, but I�d better tell you in general what it is. + +I think of the earliest great group of archeological materials, from +which we can deduce only a food-gathering way of culture, as the +_food-gathering stage_. I say �stage� rather than �age,� because it +is not quite over yet; there are still a few primitive people in +out-of-the-way parts of the world who remain in the _food-gathering +stage_. In fact, Professor Julian Steward would probably prefer to call +it a food-gathering _level_ of existence, rather than a stage. This +would be perfectly acceptable to me. I also tend to find myself using +_collecting_, rather than _gathering_, for the more recent aspects or +era of the stage, as the word �collecting� appears to have more sense +of purposefulness and specialization than does �gathering� (see p. +91). + +Now, while I think we could make several possible subdivisions of the +food-gathering stage--I call my subdivisions of stages _eras_[5]--I +believe the only one which means much to us here is the last or +_terminal sub-era of food-collecting_ of the whole food-gathering +stage. The microliths seem to mark its approach in the northwestern +part of the Old World. It is really shown best in the Old World by +the materials of the �Forest folk,� the cultural adaptation to the +post-glacial environment in northwestern Europe. We talked about +the �Forest folk� at the beginning of this chapter, and I used the +Maglemosian assemblage of Denmark as an example. + + [5] It is difficult to find words which have a sequence or gradation + of meaning with respect to both development and a range of time + in the past, or with a range of time from somewhere in the past + which is perhaps not yet ended. One standard Webster definition + of _stage_ is: �One of the steps into which the material + development of man ... is divided.� I cannot find any dictionary + definition that suggests which of the words, _stage_ or _era_, + has the meaning of a longer span of time. Therefore, I have + chosen to let my eras be shorter, and to subdivide my stages + into eras. Webster gives _era_ as: �A signal stage of history, + an epoch.� When I want to subdivide my eras, I find myself using + _sub-eras_. Thus I speak of the _eras_ within a _stage_ and of + the _sub-eras_ within an _era_; that is, I do so when I feel + that I really have to, and when the evidence is clear enough to + allow it. + +The food-producing revolution ushers in the _food-producing stage_. +This stage began to be replaced by the _industrial stage_ only about +two hundred years ago. Now notice that my stage divisions are in terms +of technology and economics. We must think sharply to be sure that the +subdivisions of the stages, the eras, are in the same terms. This does +not mean that I think technology and economics are the only important +realms of culture. It is rather that for most of prehistoric time the +materials left to the archeologists tend to limit our deductions to +technology and economics. + +I�m so soon out of my competence, as conventional ancient history +begins, that I shall only suggest the earlier eras of the +food-producing stage to you. This book is about prehistory, and I�m not +a universal historian. + + +THE TWO EARLIEST ERAS OF THE FOOD-PRODUCING STAGE + +The food-producing stage seems to appear in western Asia with really +revolutionary suddenness. It is seen by the relative speed with which +the traces of new crafts appear in the earliest village-farming +community sites we�ve dug. It is seen by the spread and multiplication +of these sites themselves, and the remarkable growth in human +population we deduce from this increase in sites. We�ll look at some +of these sites and the archeological traces they yield in the next +chapter. When such village sites begin to appear, I believe we are in +the _era of the primary village-farming community_. I also believe this +is the second era of the food-producing stage. + +The first era of the food-producing stage, I believe, was an _era of +incipient cultivation and animal domestication_. I keep saying �I +believe� because the actual evidence for this earlier era is so slight +that one has to set it up mainly by playing a hunch for it. The reason +for playing the hunch goes about as follows. + +One thing we seem to be able to see, in the food-collecting era in +general, is a tendency for people to begin to settle down. This +settling down seemed to become further intensified in the terminal +era. How this is connected with Professor Mathiassen�s �receptiveness� +and the tendency to be experimental, we do not exactly know. The +evidence from the New World comes into play here as well as that from +the Old World. With this settling down in one place, the people of the +terminal era--especially the �Forest folk� whom we know best--began +making a great variety of new things. I remarked about this earlier in +the chapter. Dr. Robert M. Adams is of the opinion that this atmosphere +of experimentation with new tools--with new ways of collecting food--is +the kind of atmosphere in which one might expect trials at planting +and at animal domestication to have been made. We first begin to find +traces of more permanent life in outdoor camp sites, although caves +were still inhabited at the beginning of the terminal era. It is not +surprising at all that the �Forest folk� had already domesticated the +dog. In this sense, the whole era of food-collecting was becoming ready +and almost �incipient� for cultivation and animal domestication. + +Northwestern Europe was not the place for really effective beginnings +in agriculture and animal domestication. These would have had to take +place in one of those natural environments of promise, where a variety +of plants and animals, each possible of domestication, was available in +the wild state. Let me spell this out. Really effective food-production +must include a variety of items to make up a reasonably well-rounded +diet. The food-supply so produced must be trustworthy, even though +the food-producing peoples themselves might be happy to supplement +it with fish and wild strawberries, just as we do when such things +are available. So, as we said earlier, part of our problem is that +of finding a region with a natural environment which includes--and +did include, some ten thousand years ago--a variety of possibly +domesticable wild plants and animals. + + +NUCLEAR AREAS + +Now comes the last of my definitions. A region with a natural +environment which included a variety of wild plants and animals, +both possible and ready for domestication, would be a central +or core or _nuclear area_, that is, it would be when and _if_ +food-production took place within it. It is pretty hard for me to +imagine food-production having ever made an independent start outside +such a nuclear area, although there may be some possible nuclear areas +in which food-production never took place (possibly in parts of Africa, +for example). + +We know of several such nuclear areas. In the New World, Middle America +and the Andean highlands make up one or two; it is my understanding +that the evidence is not yet clear as to which. There seems to have +been a nuclear area somewhere in southeastern Asia, in the Malay +peninsula or Burma perhaps, connected with the early cultivation of +taro, breadfruit, the banana and the mango. Possibly the cultivation +of rice and the domestication of the chicken and of zebu cattle and +the water buffalo belong to this southeast Asiatic nuclear area. We +know relatively little about it archeologically, as yet. The nuclear +area which was the scene of the earliest experiment in effective +food-production was in western Asia. Since I know it best, I shall use +it as my example. + + +THE NUCLEAR NEAR EAST + +The nuclear area of western Asia is naturally the one of greatest +interest to people of the western cultural tradition. Our cultural +heritage began within it. The area itself is the region of the hilly +flanks of rain-watered grass-land which build up to the high mountain +ridges of Iran, Iraq, Turkey, Syria, and Palestine. The map on page +125 indicates the region. If you have a good atlas, try to locate the +zone which surrounds the drainage basin of the Tigris and Euphrates +Rivers at elevations of from approximately 2,000 to 5,000 feet. The +lower alluvial land of the Tigris-Euphrates basin itself has very +little rainfall. Some years ago Professor James Henry Breasted called +the alluvial lands of the Tigris-Euphrates a part of the �fertile +crescent.� These alluvial lands are very fertile if irrigated. Breasted +was most interested in the oriental civilizations of conventional +ancient history, and irrigation had been discovered before they +appeared. + +The country of hilly flanks above Breasted�s crescent receives from +10 to 20 or more inches of winter rainfall each year, which is about +what Kansas has. Above the hilly-flanks zone tower the peaks and ridges +of the Lebanon-Amanus chain bordering the coast-line from Palestine +to Turkey, the Taurus Mountains of southern Turkey, and the Zagros +range of the Iraq-Iran borderland. This rugged mountain frame for our +hilly-flanks zone rises to some magnificent alpine scenery, with peaks +of from ten to fifteen thousand feet in elevation. There are several +gaps in the Mediterranean coastal portion of the frame, through which +the winter�s rain-bearing winds from the sea may break so as to carry +rain to the foothills of the Taurus and the Zagros. + +The picture I hope you will have from this description is that of an +intermediate hilly-flanks zone lying between two regions of extremes. +The lower Tigris-Euphrates basin land is low and far too dry and hot +for agriculture based on rainfall alone; to the south and southwest, it +merges directly into the great desert of Arabia. The mountains which +lie above the hilly-flanks zone are much too high and rugged to have +encouraged farmers. + + +THE NATURAL ENVIRONMENT OF THE NUCLEAR NEAR EAST + +The more we learn of this hilly-flanks zone that I describe, the +more it seems surely to have been a nuclear area. This is where we +archeologists need, and are beginning to get, the help of natural +scientists. They are coming to the conclusion that the natural +environment of the hilly-flanks zone today is much as it was some eight +to ten thousand years ago. There are still two kinds of wild wheat and +a wild barley, and the wild sheep, goat, and pig. We have discovered +traces of each of these at about nine thousand years ago, also traces +of wild ox, horse, and dog, each of which appears to be the probable +ancestor of the domesticated form. In fact, at about nine thousand +years ago, the two wheats, the barley, and at least the goat, were +already well on the road to domestication. + +The wild wheats give us an interesting clue. They are only available +together with the wild barley within the hilly-flanks zone. While the +wild barley grows in a variety of elevations and beyond the zone, +at least one of the wild wheats does not seem to grow below the hill +country. As things look at the moment, the domestication of both the +wheats together could _only_ have taken place within the hilly-flanks +zone. Barley seems to have first come into cultivation due to its +presence as a weed in already cultivated wheat fields. There is also +a suggestion--there is still much more to learn in the matter--that +the animals which were first domesticated were most at home up in the +hilly-flanks zone in their wild state. + +With a single exception--that of the dog--the earliest positive +evidence of domestication includes the two forms of wheat, the barley, +and the goat. The evidence comes from within the hilly-flanks zone. +However, it comes from a settled village proper, Jarmo (which I�ll +describe in the next chapter), and is thus from the era of the primary +village-farming community. We are still without positive evidence of +domesticated grain and animals in the first era of the food-producing +stage, that of incipient cultivation and animal domestication. + + +THE ERA OF INCIPIENT CULTIVATION AND ANIMAL DOMESTICATION + +I said above (p. 105) that my era of incipient cultivation and animal +domestication is mainly set up by playing a hunch. Although we cannot +really demonstrate it--and certainly not in the Near East--it would +be very strange for food-collectors not to have known a great deal +about the plants and animals most useful to them. They do seem to have +domesticated the dog. We can easily imagine them remembering to go +back, season after season, to a particular patch of ground where seeds +or acorns or berries grew particularly well. Most human beings, unless +they are extremely hungry, are attracted to baby animals, and many wild +pups or fawns or piglets must have been brought back alive by hunting +parties. + +In this last sense, man has probably always been an incipient +cultivator and domesticator. But I believe that Adams is right in +suggesting that this would be doubly true with the experimenters of +the terminal era of food-collecting. We noticed that they also seem +to have had a tendency to settle down. Now my hunch goes that _when_ +this experimentation and settling down took place within a potential +nuclear area--where a whole constellation of plants and animals +possible of domestication was available--the change was easily made. +Professor Charles A. Reed, our field colleague in zoology, agrees that +year-round settlement with plant domestication probably came before +there were important animal domestications. + + +INCIPIENT ERAS AND NUCLEAR AREAS + +I have put this scheme into a simple chart (p. 111) with the names +of a few of the sites we are going to talk about. You will see that my +hunch means that there are eras of incipient cultivation _only_ within +nuclear areas. In a nuclear area, the terminal era of food-collecting +would probably have been quite short. I do not know for how long a time +the era of incipient cultivation and domestication would have lasted, +but perhaps for several thousand years. Then it passed on into the era +of the primary village-farming community. + +Outside a nuclear area, the terminal era of food-collecting would last +for a long time; in a few out-of-the-way parts of the world, it still +hangs on. It would end in any particular place through contact with +and the spread of ideas of people who had passed on into one of the +more developed eras. In many cases, the terminal era of food-collecting +was ended by the incoming of the food-producing peoples themselves. +For example, the practices of food-production were carried into Europe +by the actual movement of some numbers of peoples (we don�t know how +many) who had reached at least the level of the primary village-farming +community. The �Forest folk� learned food-production from them. There +was never an era of incipient cultivation and domestication proper in +Europe, if my hunch is right. + + +ARCHEOLOGICAL DIFFICULTIES IN SEEING THE INCIPIENT ERA + +The way I see it, two things were required in order that an era of +incipient cultivation and domestication could begin. First, there had +to be the natural environment of a nuclear area, with its whole group +of plants and animals capable of domestication. This is the aspect of +the matter which we�ve said is directly given by nature. But it is +quite possible that such an environment with such a group of plants +and animals in it may have existed well before ten thousand years ago +in the Near East. It is also quite possible that the same promising +condition may have existed in regions which never developed into +nuclear areas proper. Here, again, we come back to the cultural factor. +I think it was that �atmosphere of experimentation� we�ve talked about +once or twice before. I can�t define it for you, other than to say that +by the end of the Ice Age, the general level of many cultures was ready +for change. Ask me how and why this was so, and I�ll tell you we don�t +know yet, and that if we did understand this kind of question, there +would be no need for me to go on being a prehistorian! + +[Illustration: POSSIBLE RELATIONSHIPS OF STAGES AND ERAS IN WESTERN +ASIA AND NORTHEASTERN AFRICA] + +Now since this was an era of incipience, of the birth of new ideas, +and of experimentation, it is very difficult to see its traces +archeologically. New tools having to do with the new ways of getting +and, in fact, producing food would have taken some time to develop. +It need not surprise us too much if we cannot find hoes for planting +and sickles for reaping grain at the very beginning. We might expect +a time of making-do with some of the older tools, or with make-shift +tools, for some of the new jobs. The present-day wild cousin of the +domesticated sheep still lives in the mountains of western Asia. It has +no wool, only a fine down under hair like that of a deer, so it need +not surprise us to find neither the whorls used for spinning nor traces +of woolen cloth. It must have taken some time for a wool-bearing sheep +to develop and also time for the invention of the new tools which go +with weaving. It would have been the same with other kinds of tools for +the new way of life. + +It is difficult even for an experienced comparative zoologist to tell +which are the bones of domesticated animals and which are those of +their wild cousins. This is especially so because the animal bones the +archeologists find are usually fragmentary. Furthermore, we do not have +a sort of library collection of the skeletons of the animals or an +herbarium of the plants of those times, against which the traces which +the archeologists find may be checked. We are only beginning to get +such collections for the modern wild forms of animals and plants from +some of our nuclear areas. In the nuclear area in the Near East, some +of the wild animals, at least, have already become extinct. There are +no longer wild cattle or wild horses in western Asia. We know they were +there from the finds we�ve made in caves of late Ice Age times, and +from some slightly later sites. + + +SITES WITH ANTIQUITIES OF THE INCIPIENT ERA + +So far, we know only a very few sites which would suit my notion of the +incipient era of cultivation and animal domestication. I am closing +this chapter with descriptions of two of the best Near Eastern examples +I know of. You may not be satisfied that what I am able to describe +makes a full-bodied era of development at all. Remember, however, that +I�ve told you I�m largely playing a kind of a hunch, and also that the +archeological materials of this era will always be extremely difficult +to interpret. At the beginning of any new way of life, there will be a +great tendency for people to make-do, at first, with tools and habits +they are already used to. I would suspect that a great deal of this +making-do went on almost to the end of this era. + + +THE NATUFIAN, AN ASSEMBLAGE OF THE INCIPIENT ERA + +The assemblage called the Natufian comes from the upper layers of a +number of caves in Palestine. Traces of its flint industry have also +turned up in Syria and Lebanon. We don�t know just how old it is. I +guess that it probably falls within five hundred years either way of +about 5000 B.C. + +Until recently, the people who produced the Natufian assemblage were +thought to have been only cave dwellers, but now at least three open +air Natufian sites have been briefly described. In their best-known +dwelling place, on Mount Carmel, the Natufian folk lived in the open +mouth of a large rock-shelter and on the terrace in front of it. On the +terrace, they had set at least two short curving lines of stones; but +these were hardly architecture; they seem more like benches or perhaps +the low walls of open pens. There were also one or two small clusters +of stones laid like paving, and a ring of stones around a hearth or +fireplace. One very round and regular basin-shaped depression had been +cut into the rocky floor of the terrace, and there were other less +regular basin-like depressions. In the newly reported open air sites, +there seem to have been huts with rounded corners. + +Most of the finds in the Natufian layer of the Mount Carmel cave were +flints. About 80 per cent of these flint tools were microliths made +by the regular working of tiny blades into various tools, some having +geometric forms. The larger flint tools included backed blades, burins, +scrapers, a few arrow points, some larger hacking or picking tools, and +one special type. This last was the sickle blade. + +We know a sickle blade of flint when we see one, because of a strange +polish or sheen which seems to develop on the cutting edge when the +blade has been used to cut grasses or grain, or--perhaps--reeds. In +the Natufian, we have even found the straight bone handles in which a +number of flint sickle blades were set in a line. + +There was a small industry in ground or pecked stone (that is, abraded +not chipped) in the Natufian. This included some pestle and mortar +fragments. The mortars are said to have a deep and narrow hole, +and some of the pestles show traces of red ochre. We are not sure +that these mortars and pestles were also used for grinding food. In +addition, there were one or two bits of carving in stone. + + +NATUFIAN ANTIQUITIES IN OTHER MATERIALS; BURIALS AND PEOPLE + +The Natufian industry in bone was quite rich. It included, beside the +sickle hafts mentioned above, points and harpoons, straight and curved +types of fish-hooks, awls, pins and needles, and a variety of beads and +pendants. There were also beads and pendants of pierced teeth and shell. + +A number of Natufian burials have been found in the caves; some burials +were grouped together in one grave. The people who were buried within +the Mount Carmel cave were laid on their backs in an extended position, +while those on the terrace seem to have been �flexed� (placed in their +graves in a curled-up position). This may mean no more than that it was +easier to dig a long hole in cave dirt than in the hard-packed dirt of +the terrace. The people often had some kind of object buried with them, +and several of the best collections of beads come from the burials. On +two of the skulls there were traces of elaborate head-dresses of shell +beads. + +[Illustration: SKETCH OF NATUFIAN ASSEMBLAGE + + MICROLITHS + ARCHITECTURE? + BURIAL + CHIPPED STONE + GROUND STONE + BONE] + +The animal bones of the Natufian layers show beasts of a �modern� type, +but with some differences from those of present-day Palestine. The +bones of the gazelle far outnumber those of the deer; since gazelles +like a much drier climate than deer, Palestine must then have had much +the same climate that it has today. Some of the animal bones were those +of large or dangerous beasts: the hyena, the bear, the wild boar, +and the leopard. But the Natufian people may have had the help of a +large domesticated dog. If our guess at a date for the Natufian is +right (about 7750 B.C.), this is an earlier dog than was that in the +Maglemosian of northern Europe. More recently, it has been reported +that a domesticated goat is also part of the Natufian finds. + +The study of the human bones from the Natufian burials is not yet +complete. Until Professor McCown�s study becomes available, we may note +Professor Coon�s assessment that these people were of a �basically +Mediterranean type.� + + +THE KARIM SHAHIR ASSEMBLAGE + +Karim Shahir differs from the Natufian sites in that it shows traces +of a temporary open site or encampment. It lies on the top of a bluff +in the Kurdish hill-country of northeastern Iraq. It was dug by Dr. +Bruce Howe of the expedition I directed in 1950-51 for the Oriental +Institute and the American Schools of Oriental Research. In 1954-55, +our expedition located another site, M�lefaat, with general resemblance +to Karim Shahir, but about a hundred miles north of it. In 1956, Dr. +Ralph Solecki located still another Karim Shahir type of site called +Zawi Chemi Shanidar. The Zawi Chemi site has a radiocarbon date of 8900 +� 300 B.C. + +Karim Shahir has evidence of only one very shallow level of occupation. +It was probably not lived on very long, although the people who lived +on it spread out over about three acres of area. In spots, the single +layer yielded great numbers of fist-sized cracked pieces of limestone, +which had been carried up from the bed of a stream at the bottom of the +bluff. We think these cracked stones had something to do with a kind of +architecture, but we were unable to find positive traces of hut plans. +At M�lefaat and Zawi Chemi, there were traces of rounded hut plans. + +As in the Natufian, the great bulk of small objects of the Karim Shahir +assemblage was in chipped flint. A large proportion of the flint tools +were microlithic bladelets and geometric forms. The flint sickle blade +was almost non-existent, being far scarcer than in the Natufian. The +people of Karim Shahir did a modest amount of work in the grinding of +stone; there were milling stone fragments of both the mortar and the +quern type, and stone hoes or axes with polished bits. Beads, pendants, +rings, and bracelets were made of finer quality stone. We found a few +simple points and needles of bone, and even two rather formless unbaked +clay figurines which seemed to be of animal form. + +[Illustration: SKETCH OF KARIM SHAHIR ASSEMBLAGE + + CHIPPED STONE + GROUND STONE + UNBAKED CLAY + SHELL + BONE + �ARCHITECTURE�] + +Karim Shahir did not yield direct evidence of the kind of vegetable +food its people ate. The animal bones showed a considerable +increase in the proportion of the bones of the species capable of +domestication--sheep, goat, cattle, horse, dog--as compared with animal +bones from the earlier cave sites of the area, which have a high +proportion of bones of wild forms like deer and gazelle. But we do not +know that any of the Karim Shahir animals were actually domesticated. +Some of them may have been, in an �incipient� way, but we have no means +at the moment that will tell us from the bones alone. + + +WERE THE NATUFIAN AND KARIM SHAHIR PEOPLES FOOD-PRODUCERS? + +It is clear that a great part of the food of the Natufian people +must have been hunted or collected. Shells of land, fresh-water, and +sea animals occur in their cave layers. The same is true as regards +Karim Shahir, save for sea shells. But on the other hand, we have +the sickles, the milling stones, the possible Natufian dog, and the +goat, and the general animal situation at Karim Shahir to hint at an +incipient approach to food-production. At Karim Shahir, there was the +tendency to settle down out in the open; this is echoed by the new +reports of open air Natufian sites. The large number of cracked stones +certainly indicates that it was worth the peoples� while to have some +kind of structure, even if the site as a whole was short-lived. + +It is a part of my hunch that these things all point toward +food-production--that the hints we seek are there. But in the sense +that the peoples of the era of the primary village-farming community, +which we shall look at next, are fully food-producing, the Natufian +and Karim Shahir folk had not yet arrived. I think they were part of +a general build-up to full scale food-production. They were possibly +controlling a few animals of several kinds and perhaps one or two +plants, without realizing the full possibilities of this �control� as a +new way of life. + +This is why I think of the Karim Shahir and Natufian folk as being at +a level, or in an era, of incipient cultivation and domestication. But +we shall have to do a great deal more excavation in this range of time +before we�ll get the kind of positive information we need. + + +SUMMARY + +I am sorry that this chapter has had to be so much more about ideas +than about the archeological traces of prehistoric men themselves. +But the antiquities of the incipient era of cultivation and animal +domestication will not be spectacular, even when we do have them +excavated in quantity. Few museums will be interested in these +antiquities for exhibition purposes. The charred bits or impressions +of plants, the fragments of animal bone and shell, and the varied +clues to climate and environment will be as important as the artifacts +themselves. It will be the ideas to which these traces lead us that +will be important. I am sure that this unspectacular material--when we +have much more of it, and learn how to understand what it says--will +lead us to how and why answers about the first great change in human +history. + +We know the earliest village-farming communities appeared in western +Asia, in a nuclear area. We do not yet know why the Near Eastern +experiment came first, or why it didn�t happen earlier in some other +nuclear area. Apparently, the level of culture and the promise of the +natural environment were ready first in western Asia. The next sites +we look at will show a simple but effective food-production already +in existence. Without effective food-production and the settled +village-farming communities, civilization never could have followed. +How effective food-production came into being by the end of the +incipient era, is, I believe, one of the most fascinating questions any +archeologist could face. + +It now seems probable--from possibly two of the Palestinian sites with +varieties of the Natufian (Jericho and Nahal Oren)--that there were +one or more local Palestinian developments out of the Natufian into +later times. In the same way, what followed after the Karim Shahir type +of assemblage in northeastern Iraq was in some ways a reflection of +beginnings made at Karim Shahir and Zawi Chemi. + + + + +THE First Revolution + +[Illustration] + + +As the incipient era of cultivation and animal domestication passed +onward into the era of the primary village-farming community, the first +basic change in human economy was fully achieved. In southwestern Asia, +this seems to have taken place about nine thousand years ago. I am +going to restrict my description to this earliest Near Eastern case--I +do not know enough about the later comparable experiments in the Far +East and in the New World. Let us first, once again, think of the +contrast between food-collecting and food-producing as ways of life. + + +THE DIFFERENCE BETWEEN FOOD-COLLECTORS AND FOOD-PRODUCERS + +Childe used the word �revolution� because of the radical change that +took place in the habits and customs of man. Food-collectors--that is, +hunters, fishers, berry- and nut-gatherers--had to live in small groups +or bands, for they had to be ready to move wherever their food supply +moved. Not many people can be fed in this way in one area, and small +children and old folks are a burden. There is not enough food to store, +and it is not the kind that can be stored for long. + +Do you see how this all fits into a picture? Small groups of people +living now in this cave, now in that--or out in the open--as they moved +after the animals they hunted; no permanent villages, a few half-buried +huts at best; no breakable utensils; no pottery; no signs of anything +for clothing beyond the tools that were probably used to dress the +skins of animals; no time to think of much of anything but food and +protection and disposal of the dead when death did come: an existence +which takes nature as it finds it, which does little or nothing to +modify nature--all in all, a savage�s existence, and a very tough one. +A man who spends his whole life following animals just to kill them to +eat, or moving from one berry patch to another, is really living just +like an animal himself. + + +THE FOOD-PRODUCING ECONOMY + +Against this picture let me try to draw another--that of man�s life +after food-production had begun. His meat was stored �on the hoof,� +his grain in silos or great pottery jars. He lived in a house: it was +worth his while to build one, because he couldn�t move far from his +fields and flocks. In his neighborhood enough food could be grown +and enough animals bred so that many people were kept busy. They all +lived close to their flocks and fields, in a village. The village was +already of a fair size, and it was growing, too. Everybody had more to +eat; they were presumably all stronger, and there were more children. +Children and old men could shepherd the animals by day or help with +the lighter work in the fields. After the crops had been harvested the +younger men might go hunting and some of them would fish, but the food +they brought in was only an addition to the food in the village; the +villagers wouldn�t starve, even if the hunters and fishermen came home +empty-handed. + +There was more time to do different things, too. They began to modify +nature. They made pottery out of raw clay, and textiles out of hair +or fiber. People who became good at pottery-making traded their pots +for food and spent all of their time on pottery alone. Other people +were learning to weave cloth or to make new tools. There were already +people in the village who were becoming full-time craftsmen. + +Other things were changing, too. The villagers must have had +to agree on new rules for living together. The head man of the +village had problems different from those of the chief of the small +food-collectors� band. If somebody�s flock of sheep spoiled a wheat +field, the owner wanted payment for the grain he lost. The chief of +the hunters was never bothered with such questions. Even the gods +had changed. The spirits and the magic that had been used by hunters +weren�t of any use to the villagers. They needed gods who would watch +over the fields and the flocks, and they eventually began to erect +buildings where their gods might dwell, and where the men who knew most +about the gods might live. + + +WAS FOOD-PRODUCTION A �REVOLUTION�? + +If you can see the difference between these two pictures--between +life in the food-collecting stage and life after food-production +had begun--you�ll see why Professor Childe speaks of a revolution. +By revolution, he doesn�t mean that it happened over night or that +it happened only once. We don�t know exactly how long it took. Some +people think that all these changes may have occurred in less than +500 years, but I doubt that. The incipient era was probably an affair +of some duration. Once the level of the village-farming community had +been established, however, things did begin to move very fast. By +six thousand years ago, the descendants of the first villagers had +developed irrigation and plow agriculture in the relatively rainless +Mesopotamian alluvium and were living in towns with temples. Relative +to the half million years of food-gathering which lay behind, this had +been achieved with truly revolutionary suddenness. + + +GAPS IN OUR KNOWLEDGE OF THE NEAR EAST + +If you�ll look again at the chart (p. 111) you�ll see that I have +very few sites and assemblages to name in the incipient era of +cultivation and domestication, and not many in the earlier part of +the primary village-farming level either. Thanks in no small part +to the intelligent co-operation given foreign excavators by the +Iraq Directorate General of Antiquities, our understanding of the +sequence in Iraq is growing more complete. I shall use Iraq as my main +yard-stick here. But I am far from being able to show you a series of +Sears Roebuck catalogues, even century by century, for any part of +the nuclear area. There is still a great deal of earth to move, and a +great mass of material to recover and interpret before we even begin to +understand �how� and �why.� + +Perhaps here, because this kind of archeology is really my specialty, +you�ll excuse it if I become personal for a moment. I very much look +forward to having further part in closing some of the gaps in knowledge +of the Near East. This is not, as I�ve told you, the spectacular +range of Near Eastern archeology. There are no royal tombs, no gold, +no great buildings or sculpture, no writing, in fact nothing to +excite the normal museum at all. Nevertheless it is a range which, +idea-wise, gives the archeologist tremendous satisfaction. The country +of the hilly flanks is an exciting combination of green grasslands +and mountainous ridges. The Kurds, who inhabit the part of the area +in which I�ve worked most recently, are an extremely interesting and +hospitable people. Archeologists don�t become rich, but I�ll forego +the Cadillac for any bright spring morning in the Kurdish hills, on a +good site with a happy crew of workmen and an interested and efficient +staff. It is probably impossible to convey the full feeling which life +on such a dig holds--halcyon days for the body and acute pleasurable +stimulation for the mind. Old things coming newly out of the good dirt, +and the pieces of the human puzzle fitting into place! I think I am +an honest man; I cannot tell you that I am sorry the job is not yet +finished and that there are still gaps in this part of the Near Eastern +archeological sequence. + + +EARLIEST SITES OF THE VILLAGE FARMERS + +So far, the Karim Shahir type of assemblage, which we looked at in the +last chapter, is the earliest material available in what I take to +be the nuclear area. We do not believe that Karim Shahir was a village +site proper: it looks more like the traces of a temporary encampment. +Two caves, called Belt and Hotu, which are outside the nuclear area +and down on the foreshore of the Caspian Sea, have been excavated +by Professor Coon. These probably belong in the later extension of +the terminal era of food-gathering; in their upper layers are traits +like the use of pottery borrowed from the more developed era of the +same time in the nuclear area. The same general explanation doubtless +holds true for certain materials in Egypt, along the upper Nile and in +the Kharga oasis: these materials, called Sebilian III, the Khartoum +�neolithic,� and the Khargan microlithic, are from surface sites, +not from caves. The chart (p. 111) shows where I would place these +materials in era and time. + +[Illustration: THE HILLY FLANKS OF THE CRESCENT AND EARLY SITES OF THE +NEAR EAST] + +Both M�lefaat and Dr. Solecki�s Zawi Chemi Shanidar site appear to have +been slightly more �settled in� than was Karim Shahir itself. But I do +not think they belong to the era of farming-villages proper. The first +site of this era, in the hills of Iraqi Kurdistan, is Jarmo, on which +we have spent three seasons of work. Following Jarmo comes a variety of +sites and assemblages which lie along the hilly flanks of the crescent +and just below it. I am going to describe and illustrate some of these +for you. + +Since not very much archeological excavation has yet been done on sites +of this range of time, I shall have to mention the names of certain +single sites which now alone stand for an assemblage. This does not +mean that I think the individual sites I mention were unique. In the +times when their various cultures flourished, there must have been +many little villages which shared the same general assemblage. We are +only now beginning to locate them again. Thus, if I speak of Jarmo, +or Jericho, or Sialk as single examples of their particular kinds of +assemblages, I don�t mean that they were unique at all. I think I could +take you to the sites of at least three more Jarmos, within twenty +miles of the original one. They are there, but they simply haven�t yet +been excavated. In 1956, a Danish expedition discovered material of +Jarmo type at Shimshara, only two dozen miles northeast of Jarmo, and +below an assemblage of Hassunan type (which I shall describe presently). + + +THE GAP BETWEEN KARIM SHAHIR AND JARMO + +As we see the matter now, there is probably still a gap in the +available archeological record between the Karim Shahir-M�lefaat-Zawi +Chemi group (of the incipient era) and that of Jarmo (of the +village-farming era). Although some items of the Jarmo type materials +do reflect the beginnings of traditions set in the Karim Shahir group +(see p. 120), there is not a clear continuity. Moreover--to the +degree that we may trust a few radiocarbon dates--there would appear +to be around two thousand years of difference in time. The single +available Zawi Chemi �date� is 8900 � 300 B.C.; the most reasonable +group of �dates� from Jarmo average to about 6750 � 200 B.C. I am +uncertain about this two thousand years--I do not think it can have +been so long. + +This suggests that we still have much work to do in Iraq. You can +imagine how earnestly we await the return of political stability in the +Republic of Iraq. + + +JARMO, IN THE KURDISH HILLS, IRAQ + +The site of Jarmo has a depth of deposit of about twenty-seven feet, +and approximately a dozen layers of architectural renovation and +change. Nevertheless it is a �one period� site: its assemblage remains +essentially the same throughout, although one or two new items are +added in later levels. It covers about four acres of the top of a +bluff, below which runs a small stream. Jarmo lies in the hill country +east of the modern oil town of Kirkuk. The Iraq Directorate General of +Antiquities suggested that we look at it in 1948, and we have had three +seasons of digging on it since. + +The people of Jarmo grew the barley plant and two different kinds of +wheat. They made flint sickles with which to reap their grain, mortars +or querns on which to crack it, ovens in which it might be parched, and +stone bowls out of which they might eat their porridge. We are sure +that they had the domesticated goat, but Professor Reed (the staff +zoologist) is not convinced that the bones of the other potentially +domesticable animals of Jarmo--sheep, cattle, pig, horse, dog--show +sure signs of domestication. We had first thought that all of these +animals were domesticated ones, but Reed feels he must find out much +more before he can be sure. As well as their grain and the meat from +their animals, the people of Jarmo consumed great quantities of land +snails. Botanically, the Jarmo wheat stands about half way between +fully bred wheat and the wild forms. + + +ARCHITECTURE: HALL-MARK OF THE VILLAGE + +The sure sign of the village proper is in its traces of architectural +permanence. The houses of Jarmo were only the size of a small cottage +by our standards, but each was provided with several rectangular rooms. +The walls of the houses were made of puddled mud, often set on crude +foundations of stone. (The puddled mud wall, which the Arabs call +_touf_, is built by laying a three to six inch course of soft mud, +letting this sun-dry for a day or two, then adding the next course, +etc.) The village probably looked much like the simple Kurdish farming +village of today, with its mud-walled houses and low mud-on-brush +roofs. I doubt that the Jarmo village had more than twenty houses at +any one moment of its existence. Today, an average of about seven +people live in a comparable Kurdish house; probably the population of +Jarmo was about 150 people. + +[Illustration: SKETCH OF JARMO ASSEMBLAGE + + CHIPPED STONE + UNBAKED CLAY + GROUND STONE + POTTERY _UPPER THIRD OF SITE ONLY._ + REED MATTING + BONE + ARCHITECTURE] + +It is interesting that portable pottery does not appear until the +last third of the life of the Jarmo village. Throughout the duration +of the village, however, its people had experimented with the plastic +qualities of clay. They modeled little figurines of animals and of +human beings in clay; one type of human figurine they favored was that +of a markedly pregnant woman, probably the expression of some sort of +fertility spirit. They provided their house floors with baked-in-place +depressions, either as basins or hearths, and later with domed ovens of +clay. As we�ve noted, the houses themselves were of clay or mud; one +could almost say they were built up like a house-sized pot. Then, +finally, the idea of making portable pottery itself appeared, although +I very much doubt that the people of the Jarmo village discovered the +art. + +On the other hand, the old tradition of making flint blades and +microlithic tools was still very strong at Jarmo. The sickle-blade was +made in quantities, but so also were many of the much older tool types. +Strangely enough, it is within this age-old category of chipped stone +tools that we see one of the clearest pointers to a newer age. Many of +the Jarmo chipped stone tools--microliths--were made of obsidian, a +black volcanic natural glass. The obsidian beds nearest to Jarmo are +over three hundred miles to the north. Already a bulk carrying trade +had been established--the forerunner of commerce--and the routes were +set by which, in later times, the metal trade was to move. + +There are now twelve radioactive carbon �dates� from Jarmo. The most +reasonable cluster of determinations averages to about 6750 � 200 +B.C., although there is a completely unreasonable range of �dates� +running from 3250 to 9250 B.C.! _If_ I am right in what I take to be +�reasonable,� the first flush of the food-producing revolution had been +achieved almost nine thousand years ago. + + +HASSUNA, IN UPPER MESOPOTAMIAN IRAQ + +We are not sure just how soon after Jarmo the next assemblage of Iraqi +material is to be placed. I do not think the time was long, and there +are a few hints that detailed habits in the making of pottery and +ground stone tools were actually continued from Jarmo times into the +time of the next full assemblage. This is called after a site named +Hassuna, a few miles to the south and west of modern Mosul. We also +have Hassunan type materials from several other sites in the same +general region. It is probably too soon to make generalizations about +it, but the Hassunan sites seem to cluster at slightly lower elevations +than those we have been talking about so far. + +The catalogue of the Hassuna assemblage is of course more full and +elaborate than that of Jarmo. The Iraqi government�s archeologists +who dug Hassuna itself, exposed evidence of increasing architectural +know-how. The walls of houses were still formed of puddled mud; +sun-dried bricks appear only in later periods. There were now several +different ways of making and decorating pottery vessels. One style of +pottery painting, called the Samarran style, is an extremely handsome +one and must have required a great deal of concentration and excellence +of draftsmanship. On the other hand, the old habits for the preparation +of good chipped stone tools--still apparent at Jarmo--seem to have +largely disappeared by Hassunan times. The flint work of the Hassunan +catalogue is, by and large, a wretched affair. We might guess that the +kinaesthetic concentration of the Hassuna craftsmen now went into other +categories; that is, they suddenly discovered they might have more fun +working with the newer materials. It�s a shame, for example, that none +of their weaving is preserved for us. + +The two available radiocarbon determinations from Hassunan contexts +stand at about 5100 and 5600 B.C. � 250 years. + + +OTHER EARLY VILLAGE SITES IN THE NUCLEAR AREA + +I�ll now name and very briefly describe a few of the other early +village assemblages either in or adjacent to the hilly flanks of the +crescent. Unfortunately, we do not have radioactive carbon dates for +many of these materials. We may guess that some particular assemblage, +roughly comparable to that of Hassuna, for example, must reflect a +culture which lived at just about the same time as that of Hassuna. We +do this guessing on the basis of the general similarity and degree of +complexity of the Sears Roebuck catalogues of the particular assemblage +and that of Hassuna. We suppose that for sites near at hand and of a +comparable cultural level, as indicated by their generally similar +assemblages, the dating must be about the same. We may also know that +in a general stratigraphic sense, the sites in question may both appear +at the bottom of the ascending village sequence in their respective +areas. Without a number of consistent radioactive carbon dates, we +cannot be precise about priorities. + +[Illustration: SKETCH OF HASSUNA ASSEMBLAGE + + POTTERY + POTTERY OBJECTS + CHIPPED STONE + BONE + GROUND STONE + ARCHITECTURE + REED MATTING + BURIAL] + +The ancient mound at Jericho, in the Dead Sea valley in Palestine, +yields some very interesting material. Its catalogue somewhat resembles +that of Jarmo, especially in the sense that there is a fair depth +of deposit without portable pottery vessels. On the other hand, the +architecture of Jericho is surprisingly complex, with traces of massive +stone fortification walls and the general use of formed sun-dried +mud brick. Jericho lies in a somewhat strange and tropically lush +ecological niche, some seven hundred feet below sea level; it is +geographically within the hilly-flanks zone but environmentally not +part of it. + +Several radiocarbon �dates� for Jericho fall within the range of those +I find reasonable for Jarmo, and their internal statistical consistency +is far better than that for the Jarmo determinations. It is not yet +clear exactly what this means. + +The mound at Jericho (Tell es-Sultan) contains a remarkably +fine sequence, which perhaps does not have the gap we noted in +Iraqi-Kurdistan between the Karim Shahir group and Jarmo. While I am +not sure that the Jericho sequence will prove valid for those parts +of Palestine outside the special Dead Sea environmental niche, the +sequence does appear to proceed from the local variety of Natufian into +that of a very well settled community. So far, we have little direct +evidence for the food-production basis upon which the Jericho people +subsisted. + +There is an early village assemblage with strong characteristics of its +own in the land bordering the northeast corner of the Mediterranean +Sea, where Syria and the Cilician province of Turkey join. This early +Syro-Cilician assemblage must represent a general cultural pattern +which was at least in part contemporary with that of the Hassuna +assemblage. These materials from the bases of the mounds at Mersin, and +from Judaidah in the Amouq plain, as well as from a few other sites, +represent the remains of true villages. The walls of their houses were +built of puddled mud, but some of the house foundations were of stone. +Several different kinds of pottery were made by the people of these +villages. None of it resembles the pottery from Hassuna or from the +upper levels of Jarmo or Jericho. The Syro-Cilician people had not +lost their touch at working flint. An important southern variation of +the Syro-Cilician assemblage has been cleared recently at Byblos, a +port town famous in later Phoenician times. There are three radiocarbon +determinations which suggest that the time range for these developments +was in the sixth or early fifth millennium B.C. + +It would be fascinating to search for traces of even earlier +village-farming communities and for the remains of the incipient +cultivation era, in the Syro-Cilician region. + + +THE IRANIAN PLATEAU AND THE NILE VALLEY + +The map on page 125 shows some sites which lie either outside or in +an extension of the hilly-flanks zone proper. From the base of the +great mound at Sialk on the Iranian plateau came an assemblage of +early village material, generally similar, in the kinds of things it +contained, to the catalogues of Hassuna and Judaidah. The details of +how things were made are different; the Sialk assemblage represents +still another cultural pattern. I suspect it appeared a bit later +in time than did that of Hassuna. There is an important new item in +the Sialk catalogue. The Sialk people made small drills or pins of +hammered copper. Thus the metallurgist�s specialized craft had made its +appearance. + +There is at least one very early Iranian site on the inward slopes +of the hilly-flanks zone. It is the earlier of two mounds at a place +called Bakun, in southwestern Iran; the results of the excavations +there are not yet published and we only know of its coarse and +primitive pottery. I only mention Bakun because it helps us to plot the +extent of the hilly-flanks zone villages on the map. + +The Nile Valley lies beyond the peculiar environmental zone of the +hilly flanks of the crescent, and it is probable that the earliest +village-farming communities in Egypt were established by a few people +who wandered into the Nile delta area from the nuclear area. The +assemblage which is most closely comparable to the catalogue of Hassuna +or Judaidah, for example, is that from little settlements along the +shore of the Fayum lake. The Fayum materials come mainly from grain +bins or silos. Another site, Merimde, in the western part of the Nile +delta, shows the remains of a true village, but it may be slightly +later than the settlement of the Fayum. There are radioactive carbon +�dates� for the Fayum materials at about 4275 B.C. � 320 years, which +is almost fifteen hundred years later than the determinations suggested +for the Hassunan or Syro-Cilician assemblages. I suspect that this +is a somewhat over-extended indication of the time it took for the +generalized cultural pattern of village-farming community life to +spread from the nuclear area down into Egypt, but as yet we have no way +of testing these matters. + +In this same vein, we have two radioactive carbon dates for an +assemblage from sites near Khartoum in the Sudan, best represented by +the mound called Shaheinab. The Shaheinab catalogue roughly corresponds +to that of the Fayum; the distance between the two places, as the Nile +flows, is roughly 1,500 miles. Thus it took almost a thousand years for +the new way of life to be carried as far south into Africa as Khartoum; +the two Shaheinab �dates� average about 3300 B.C. � 400 years. + +If the movement was up the Nile (southward), as these dates suggest, +then I suspect that the earliest available village material of middle +Egypt, the so-called Tasian, is also later than that of the Fayum. The +Tasian materials come from a few graves near a village called Deir +Tasa, and I have an uncomfortable feeling that the Tasian �assemblage� +may be mainly an artificial selection of poor examples of objects which +belong in the following range of time. + + +SPREAD IN TIME AND SPACE + +There are now two things we can do; in fact, we have already begun to +do them. We can watch the spread of the new way of life upward through +time in the nuclear area. We can also see how the new way of life +spread outward in space from the nuclear area, as time went on. There +is good archeological evidence that both these processes took place. +For the hill country of northeastern Iraq, in the nuclear area, we +have already noticed how the succession (still with gaps) from Karim +Shahir, through M�lefaat and Jarmo, to Hassuna can be charted (see +chart, p. 111). In the next chapter, we shall continue this charting +and description of what happened in Iraq upward through time. We also +watched traces of the new way of life move through space up the Nile +into Africa, to reach Khartoum in the Sudan some thirty-five hundred +years later than we had seen it at Jarmo or Jericho. We caught glimpses +of it in the Fayum and perhaps at Tasa along the way. + +For the remainder of this chapter, I shall try to suggest briefly for +you the directions taken by the spread of the new way of life from the +nuclear area in the Near East. First, let me make clear again that +I _do not_ believe that the village-farming community way of life +was invented only once and in the Near East. It seems to me that the +evidence is very clear that a separate experiment arose in the New +World. For China, the question of independence or borrowing--in the +appearance of the village-farming community there--is still an open +one. In the last chapter, we noted the probability of an independent +nuclear area in southeastern Asia. Professor Carl Sauer strongly +champions the great importance of this area as _the_ original center +of agricultural pursuits, as a kind of �cradle� of all incipient eras +of the Old World at least. While there is certainly not the slightest +archeological evidence to allow us to go that far, we may easily expect +that an early southeast Asian development would have been felt in +China. However, the appearance of the village-farming community in the +northwest of India, at least, seems to have depended on the earlier +development in the Near East. It is also probable that ideas of the new +way of life moved well beyond Khartoum in Africa. + + +THE SPREAD OF THE VILLAGE-FARMING COMMUNITY WAY OF LIFE INTO EUROPE + +How about Europe? I won�t give you many details. You can easily imagine +that the late prehistoric prelude to European history is a complicated +affair. We all know very well how complicated an area Europe is now, +with its welter of different languages and cultures. Remember, however, +that a great deal of archeology has been done on the late prehistory of +Europe, and very little on that of further Asia and Africa. If we knew +as much about these areas as we do of Europe, I expect we�d find them +just as complicated. + +This much is clear for Europe, as far as the spread of the +village-community way of life is concerned. The general idea and much +of the know-how and the basic tools of food-production moved from the +Near East to Europe. So did the plants and animals which had been +domesticated; they were not naturally at home in Europe, as they were +in western Asia. I do not, of course, mean that there were traveling +salesmen who carried these ideas and things to Europe with a commercial +gleam in their eyes. The process took time, and the ideas and things +must have been passed on from one group of people to the next. There +was also some actual movement of peoples, but we don�t know the size of +the groups that moved. + +The story of the �colonization� of Europe by the first farmers is +thus one of (1) the movement from the eastern Mediterranean lands +of some people who were farmers; (2) the spread of ideas and things +beyond the Near East itself and beyond the paths along which the +�colonists� moved; and (3) the adaptations of the ideas and things +by the indigenous �Forest folk�, about whose �receptiveness� Professor +Mathiassen speaks (p. 97). It is important to note that the resulting +cultures in the new European environment were European, not Near +Eastern. The late Professor Childe remarked that �the peoples of the +West were not slavish imitators; they adapted the gifts from the East +... into a new and organic whole capable of developing on its own +original lines.� + + +THE WAYS TO EUROPE + +Suppose we want to follow the traces of those earliest village-farmers +who did travel from western Asia into Europe. Let us start from +Syro-Cilicia, that part of the hilly-flanks zone proper which lies in +the very northeastern corner of the Mediterranean. Three ways would be +open to us (of course we could not be worried about permission from the +Soviet authorities!). We would go north, or north and slightly east, +across Anatolian Turkey, and skirt along either shore of the Black Sea +or even to the east of the Caucasus Mountains along the Caspian Sea, +to reach the plains of Ukrainian Russia. From here, we could march +across eastern Europe to the Baltic and Scandinavia, or even hook back +southwestward to Atlantic Europe. + +Our second way from Syro-Cilicia would also lie over Anatolia, to the +northwest, where we would have to swim or raft ourselves over the +Dardanelles or the Bosphorus to the European shore. Then we would bear +left toward Greece, but some of us might turn right again in Macedonia, +going up the valley of the Vardar River to its divide and on down +the valley of the Morava beyond, to reach the Danube near Belgrade +in Jugoslavia. Here we would turn left, following the great river +valley of the Danube up into central Europe. We would have a number of +tributary valleys to explore, or we could cross the divide and go down +the valley of the Rhine to the North Sea. + +Our third way from Syro-Cilicia would be by sea. We would coast along +southern Anatolia and visit Cyprus, Crete, and the Aegean islands on +our way to Greece, where, in the north, we might meet some of those who +had taken the second route. From Greece, we would sail on to Italy and +the western isles, to reach southern France and the coasts of Spain. +Eventually a few of us would sail up the Atlantic coast of Europe, to +reach western Britain and even Ireland. + +[Illustration: PROBABLE ROUTES AND TIMING IN THE SPREAD OF THE +VILLAGE-FARMING COMMUNITY WAY OF LIFE FROM THE NEAR EAST TO EUROPE] + +Of course none of us could ever take these journeys as the first +farmers took them, since the whole course of each journey must have +lasted many lifetimes. The date given to the assemblage called Windmill +Hill, the earliest known trace of village-farming communities in +England, is about 2500 B.C. I would expect about 5500 B.C. to be a +safe date to give for the well-developed early village communities of +Syro-Cilicia. We suspect that the spread throughout Europe did not +proceed at an even rate. Professor Piggott writes that �at a date +probably about 2600 B.C., simple agricultural communities were being +established in Spain and southern France, and from the latter region a +spread northwards can be traced ... from points on the French seaboard +of the [English] Channel ... there were emigrations of a certain number +of these tribes by boat, across to the chalk lands of Wessex and Sussex +[in England], probably not more than three or four generations later +than the formation of the south French colonies.� + +New radiocarbon determinations are becoming available all the +time--already several suggest that the food-producing way of life +had reached the lower Rhine and Holland by 4000 B.C. But not all +prehistorians accept these �dates,� so I do not show them on my map +(p. 139). + + +THE EARLIEST FARMERS OF ENGLAND + +To describe the later prehistory of all Europe for you would take +another book and a much larger one than this is. Therefore, I have +decided to give you only a few impressions of the later prehistory of +Britain. Of course the British Isles lie at the other end of Europe +from our base-line in western Asia. Also, they received influences +along at least two of the three ways in which the new way of life +moved into Europe. We will look at more of their late prehistory in a +following chapter: here, I shall speak only of the first farmers. + +The assemblage called Windmill Hill, which appears in the south of +England, exhibits three different kinds of structures, evidence of +grain-growing and of stock-breeding, and some distinctive types of +pottery and stone implements. The most remarkable type of structure +is the earthwork enclosures which seem to have served as seasonal +cattle corrals. These enclosures were roughly circular, reached over +a thousand feet in diameter, and sometimes included two or three +concentric sets of banks and ditches. Traces of oblong timber houses +have been found, but not within the enclosures. The second type of +structure is mine-shafts, dug down into the chalk beds where good +flint for the making of axes or hoes could be found. The third type +of structure is long simple mounds or �unchambered barrows,� in one +end of which burials were made. It has been commonly believed that the +Windmill Hill assemblage belonged entirely to the cultural tradition +which moved up through France to the Channel. Professor Piggott is now +convinced, however, that important elements of Windmill Hill stem from +northern Germany and Denmark--products of the first way into Europe +from the east. + +The archeological traces of a second early culture are to be found +in the west of England, western and northern Scotland, and most of +Ireland. The bearers of this culture had come up the Atlantic coast +by sea from southern France and Spain. The evidence they have left us +consists mainly of tombs and the contents of tombs, with only very +rare settlement sites. The tombs were of some size and received the +bodies of many people. The tombs themselves were built of stone, heaped +over with earth; the stones enclosed a passage to a central chamber +(�passage graves�), or to a simple long gallery, along the sides of +which the bodies were laid (�gallery graves�). The general type of +construction is called �megalithic� (= great stone), and the whole +earth-mounded structure is often called a _barrow_. Since many have +proper chambers, in one sense or another, we used the term �unchambered +barrow� above to distinguish those of the Windmill Hill type from these +megalithic structures. There is some evidence for sacrifice, libations, +and ceremonial fires, and it is clear that some form of community +ritual was focused on the megalithic tombs. + +The cultures of the people who produced the Windmill Hill assemblage +and of those who made the megalithic tombs flourished, at least in +part, at the same time. Although the distributions of the two different +types of archeological traces are in quite different parts of the +country, there is Windmill Hill pottery in some of the megalithic +tombs. But the tombs also contain pottery which seems to have arrived +with the tomb builders themselves. + +The third early British group of antiquities of this general time +(following 2500 B.C.) comes from sites in southern and eastern England. +It is not so certain that the people who made this assemblage, called +Peterborough, were actually farmers. While they may on occasion have +practiced a simple agriculture, many items of their assemblage link +them closely with that of the �Forest folk� of earlier times in +England and in the Baltic countries. Their pottery is decorated with +impressions of cords and is quite different from that of Windmill Hill +and the megalithic builders. In addition, the distribution of their +finds extends into eastern Britain, where the other cultures have left +no trace. The Peterborough people had villages with semi-subterranean +huts, and the bones of oxen, pigs, and sheep have been found in a few +of these. On the whole, however, hunting and fishing seem to have been +their vital occupations. They also established trade routes especially +to acquire the raw material for stone axes. + +A probably slightly later culture, whose traces are best known from +Skara Brae on Orkney, also had its roots in those cultures of the +Baltic area which fused out of the meeting of the �Forest folk� and +the peoples who took the eastern way into Europe. Skara Brae is very +well preserved, having been built of thin stone slabs about which +dune-sand drifted after the village died. The individual houses, the +bedsteads, the shelves, the chests for clothes and oddments--all built +of thin stone-slabs--may still be seen in place. But the Skara Brae +people lived entirely by sheep- and cattle-breeding, and by catching +shellfish. Neither grain nor the instruments of agriculture appeared at +Skara Brae. + + +THE EUROPEAN ACHIEVEMENT + +The above is only a very brief description of what went on in Britain +with the arrival of the first farmers. There are many interesting +details which I have omitted in order to shorten the story. + +I believe some of the difficulty we have in understanding the +establishment of the first farming communities in Europe is with +the word �colonization.� We have a natural tendency to think of +�colonization� as it has happened within the last few centuries. In the +case of the colonization of the Americas, for example, the colonists +came relatively quickly, and in increasingly vast numbers. They had +vastly superior technical, political, and war-making skills, compared +with those of the Indians. There was not much mixing with the Indians. +The case in Europe five or six thousand years ago must have been very +different. I wonder if it is even proper to call people �colonists� +who move some miles to a new region, settle down and farm it for some +years, then move on again, generation after generation? The ideas and +the things which these new people carried were only _potentially_ +superior. The ideas and things and the people had to prove themselves +in their adaptation to each new environment. Once this was done another +link to the chain would be added, and then the forest-dwellers and +other indigenous folk of Europe along the way might accept the new +ideas and things. It is quite reasonable to expect that there must have +been much mixture of the migrants and the indigenes along the way; the +Peterborough and Skara Brae assemblages we mentioned above would seem +to be clear traces of such fused cultures. Sometimes, especially if the +migrants were moving by boat, long distances may have been covered in +a short time. Remember, however, we seem to have about three thousand +years between the early Syro-Cilician villages and Windmill Hill. + +Let me repeat Professor Childe again. �The peoples of the West were +not slavish imitators: they adapted the gifts from the East ... into +a new and organic whole capable of developing on its own original +lines.� Childe is of course completely conscious of the fact that his +�peoples of the West� were in part the descendants of migrants who came +originally from the �East,� bringing their �gifts� with them. This +was the late prehistoric achievement of Europe--to take new ideas and +things and some migrant peoples and, by mixing them with the old in its +own environments, to forge a new and unique series of cultures. + +What we know of the ways of men suggests to us that when the details +of the later prehistory of further Asia and Africa are learned, their +stories will be just as exciting. + + + + +THE Conquest of Civilization + +[Illustration] + + +Now we must return to the Near East again. We are coming to the point +where history is about to begin. I am going to stick pretty close +to Iraq and Egypt in this chapter. These countries will perhaps be +the most interesting to most of us, for the foundations of western +civilization were laid in the river lands of the Tigris and Euphrates +and of the Nile. I shall probably stick closest of all to Iraq, because +things first happened there and also because I know it best. + +There is another interesting thing, too. We have seen that the first +experiment in village-farming took place in the Near East. So did +the first experiment in civilization. Both experiments �took.� The +traditions we live by today are based, ultimately, on those ancient +beginnings in food-production and civilization in the Near East. + + +WHAT �CIVILIZATION� MEANS + +I shall not try to define �civilization� for you; rather, I shall +tell you what the word brings to my mind. To me civilization means +urbanization: the fact that there are cities. It means a formal +political set-up--that there are kings or governing bodies that the +people have set up. It means formal laws--rules of conduct--which the +government (if not the people) believes are necessary. It probably +means that there are formalized projects--roads, harbors, irrigation +canals, and the like--and also some sort of army or police force +to protect them. It means quite new and different art forms. It +also usually means there is writing. (The people of the Andes--the +Incas--had everything which goes to make up a civilization but formal +writing. I can see no reason to say they were not civilized.) Finally, +as the late Professor Redfield reminded us, civilization seems to bring +with it the dawn of a new kind of moral order. + +In different civilizations, there may be important differences in the +way such things as the above are managed. In early civilizations, it is +usual to find religion very closely tied in with government, law, and +so forth. The king may also be a high priest, or he may even be thought +of as a god. The laws are usually thought to have been given to the +people by the gods. The temples are protected just as carefully as the +other projects. + + +CIVILIZATION IMPOSSIBLE WITHOUT FOOD-PRODUCTION + +Civilizations have to be made up of many people. Some of the people +live in the country; some live in very large towns or cities. Classes +of society have begun. There are officials and government people; there +are priests or religious officials; there are merchants and traders; +there are craftsmen, metal-workers, potters, builders, and so on; there +are also farmers, and these are the people who produce the food for the +whole population. It must be obvious that civilization cannot exist +without food-production and that food-production must also be at a +pretty efficient level of village-farming before civilization can even +begin. + +But people can be food-producing without being civilized. In many +parts of the world this is still the case. When the white men first +came to America, the Indians in most parts of this hemisphere were +food-producers. They grew corn, potatoes, tomatoes, squash, and many +other things the white men had never eaten before. But only the Aztecs +of Mexico, the Mayas of Yucatan and Guatemala, and the Incas of the +Andes were civilized. + + +WHY DIDN�T CIVILIZATION COME TO ALL FOOD-PRODUCERS? + +Once you have food-production, even at the well-advanced level of +the village-farming community, what else has to happen before you +get civilization? Many men have asked this question and have failed +to give a full and satisfactory answer. There is probably no _one_ +answer. I shall give you my own idea about how civilization _may_ have +come about in the Near East alone. Remember, it is only a guess--a +putting together of hunches from incomplete evidence. It is _not_ meant +to explain how civilization began in any of the other areas--China, +southeast Asia, the Americas--where other early experiments in +civilization went on. The details in those areas are quite different. +Whether certain general principles hold, for the appearance of any +early civilization, is still an open and very interesting question. + + +WHERE CIVILIZATION FIRST APPEARED IN THE NEAR EAST + +You remember that our earliest village-farming communities lay along +the hilly flanks of a great �crescent.� (See map on p. 125.) +Professor Breasted�s �fertile crescent� emphasized the rich river +valleys of the Nile and the Tigris-Euphrates Rivers. Our hilly-flanks +area of the crescent zone arches up from Egypt through Palestine and +Syria, along southern Turkey into northern Iraq, and down along the +southwestern fringe of Iran. The earliest food-producing villages we +know already existed in this area by about 6750 B.C. (� 200 years). + +Now notice that this hilly-flanks zone does not include southern +Mesopotamia, the alluvial land of the lower Tigris and Euphrates in +Iraq, or the Nile Valley proper. The earliest known villages of classic +Mesopotamia and Egypt seem to appear fifteen hundred or more years +after those of the hilly-flanks zone. For example, the early Fayum +village which lies near a lake west of the Nile Valley proper (see p. +135) has a radiocarbon date of 4275 B.C. � 320 years. It was in the +river lands, however, that the immediate beginnings of civilization +were made. + +We know that by about 3200 B.C. the Early Dynastic period had begun +in southern Mesopotamia. The beginnings of writing go back several +hundred years earlier, but we can safely say that civilization had +begun in Mesopotamia by 3200 B.C. In Egypt, the beginning of the First +Dynasty is slightly later, at about 3100 B.C., and writing probably +did not appear much earlier. There is no question but that history and +civilization were well under way in both Mesopotamia and Egypt by 3000 +B.C.--about five thousand years ago. + + +THE HILLY-FLANKS ZONE VERSUS THE RIVER LANDS + +Why did these two civilizations spring up in these two river +lands which apparently were not even part of the area where the +village-farming community began? Why didn�t we have the first +civilizations in Palestine, Syria, north Iraq, or Iran, where we�re +sure food-production had had a long time to develop? I think the +probable answer gives a clue to the ways in which civilization began in +Egypt and Mesopotamia. + +The land in the hilly flanks is of a sort which people can farm without +too much trouble. There is a fairly fertile coastal strip in Palestine +and Syria. There are pleasant mountain slopes, streams running out to +the sea, and rain, at least in the winter months. The rain belt and the +foothills of the Turkish mountains also extend to northern Iraq and on +to the Iranian plateau. The Iranian plateau has its mountain valleys, +streams, and some rain. These hilly flanks of the �crescent,� through +most of its arc, are almost made-to-order for beginning farmers. The +grassy slopes of the higher hills would be pasture for their herds +and flocks. As soon as the earliest experiments with agriculture and +domestic animals had been successful, a pleasant living could be +made--and without too much trouble. + +I should add here again, that our evidence points increasingly to a +climate for those times which is very little different from that for +the area today. Now look at Egypt and southern Mesopotamia. Both are +lands without rain, for all intents and purposes. Both are lands with +rivers that have laid down very fertile soil--soil perhaps superior to +that in the hilly flanks. But in both lands, the rivers are of no great +aid without some control. + +The Nile floods its banks once a year, in late September or early +October. It not only soaks the narrow fertile strip of land on either +side; it lays down a fresh layer of new soil each year. Beyond the +fertile strip on either side rise great cliffs, and behind them is the +desert. In its natural, uncontrolled state, the yearly flood of the +Nile must have caused short-lived swamps that were full of crocodiles. +After a short time, the flood level would have dropped, the water and +the crocodiles would have run back into the river, and the swamp plants +would have become parched and dry. + +The Tigris and the Euphrates of Mesopotamia are less likely to flood +regularly than the Nile. The Tigris has a shorter and straighter course +than the Euphrates; it is also the more violent river. Its banks are +high, and when the snows melt and flow into all of its tributary rivers +it is swift and dangerous. The Euphrates has a much longer and more +curving course and few important tributaries. Its banks are lower and +it is less likely to flood dangerously. The land on either side and +between the two rivers is very fertile, south of the modern city of +Baghdad. Unlike the Nile Valley, neither the Tigris nor the Euphrates +is flanked by cliffs. The land on either side of the rivers stretches +out for miles and is not much rougher than a poor tennis court. + + +THE RIVERS MUST BE CONTROLLED + +The real trick in both Egypt and Mesopotamia is to make the rivers work +for you. In Egypt, this is a matter of building dikes and reservoirs +that will catch and hold the Nile flood. In this way, the water is held +and allowed to run off over the fields as it is needed. In Mesopotamia, +it is a matter of taking advantage of natural river channels and branch +channels, and of leading ditches from these onto the fields. + +Obviously, we can no longer find the first dikes or reservoirs of +the Nile Valley, or the first canals or ditches of Mesopotamia. The +same land has been lived on far too long for any traces of the first +attempts to be left; or, especially in Egypt, it has been covered by +the yearly deposits of silt, dropped by the river floods. But we�re +pretty sure the first food-producers of Egypt and southern Mesopotamia +must have made such dikes, canals, and ditches. In the first place, +there can�t have been enough rain for them to grow things otherwise. +In the second place, the patterns for such projects seem to have been +pretty well set by historic times. + + +CONTROL OF THE RIVERS THE BUSINESS OF EVERYONE + +Here, then, is a _part_ of the reason why civilization grew in Egypt +and Mesopotamia first--not in Palestine, Syria, or Iran. In the latter +areas, people could manage to produce their food as individuals. It +wasn�t too hard; there were rain and some streams, and good pasturage +for the animals even if a crop or two went wrong. In Egypt and +Mesopotamia, people had to put in a much greater amount of work, and +this work couldn�t be individual work. Whole villages or groups of +people had to turn out to fix dikes or dig ditches. The dikes had to be +repaired and the ditches carefully cleared of silt each year, or they +would become useless. + +There also had to be hard and fast rules. The person who lived nearest +the ditch or the reservoir must not be allowed to take all the water +and leave none for his neighbors. It was not only a business of +learning to control the rivers and of making their waters do the +farmer�s work. It also meant controlling men. But once these men had +managed both kinds of controls, what a wonderful yield they had! The +soil was already fertile, and the silt which came in the floods and +ditches kept adding fertile soil. + + +THE GERM OF CIVILIZATION IN EGYPT AND MESOPOTAMIA + +This learning to work together for the common good was the real germ of +the Egyptian and the Mesopotamian civilizations. The bare elements of +civilization were already there: the need for a governing hand and for +laws to see that the communities� work was done and that the water was +justly shared. You may object that there is a sort of chicken and egg +paradox in this idea. How could the people set up the rules until they +had managed to get a way to live, and how could they manage to get a +way to live until they had set up the rules? I think that small groups +must have moved down along the mud-flats of the river banks quite +early, making use of naturally favorable spots, and that the rules grew +out of such cases. It would have been like the hand-in-hand growth of +automobiles and paved highways in the United States. + +Once the rules and the know-how did get going, there must have been a +constant interplay of the two. Thus, the more the crops yielded, the +richer and better-fed the people would have been, and the more the +population would have grown. As the population grew, more land would +have needed to be flooded or irrigated, and more complex systems of +dikes, reservoirs, canals, and ditches would have been built. The more +complex the system, the more necessity for work on new projects and for +the control of their use.... And so on.... + +What I have just put down for you is a guess at the manner of growth of +some of the formalized systems that go to make up a civilized society. +My explanation has been pointed particularly at Egypt and Mesopotamia. +I have already told you that the irrigation and water-control part of +it does not apply to the development of the Aztecs or the Mayas, or +perhaps anybody else. But I think that a fair part of the story of +Egypt and Mesopotamia must be as I�ve just told you. + +I am particularly anxious that you do _not_ understand me to mean that +irrigation _caused_ civilization. I am sure it was not that simple at +all. For, in fact, a complex and highly engineered irrigation system +proper did not come until later times. Let�s say rather that the simple +beginnings of irrigation allowed and in fact encouraged a great number +of things in the technological, political, social, and moral realms of +culture. We do not yet understand what all these things were or how +they worked. But without these other aspects of culture, I do not +think that urbanization and civilization itself could have come into +being. + + +THE ARCHEOLOGICAL SEQUENCE TO CIVILIZATION IN IRAQ + +We last spoke of the archeological materials of Iraq on page 130, +where I described the village-farming community of Hassunan type. The +Hassunan type villages appear in the hilly-flanks zone and in the +rolling land adjacent to the Tigris in northern Iraq. It is probable +that even before the Hassuna pattern of culture lived its course, a +new assemblage had been established in northern Iraq and Syria. This +assemblage is called Halaf, after a site high on a tributary of the +Euphrates, on the Syro-Turkish border. + +[Illustration: SKETCH OF SELECTED ITEMS OF HALAFIAN ASSEMBLAGE + + BEADS AND PENDANTS + POTTERY MOTIFS + POTTERY] + +The Halafian assemblage is incompletely known. The culture it +represents included a remarkably handsome painted pottery. +Archeologists have tended to be so fascinated with this pottery that +they have bothered little with the rest of the Halafian assemblage. We +do know that strange stone-founded houses, with plans like those of the +popular notion of an Eskimo igloo, were built. Like the pottery of the +Samarran style, which appears as part of the Hassunan assemblage (see +p. 131), the Halafian painted pottery implies great concentration and +excellence of draftsmanship on the part of the people who painted it. + +We must mention two very interesting sites adjacent to the mud-flats of +the rivers, half way down from northern Iraq to the classic alluvial +Mesopotamian area. One is Baghouz on the Euphrates; the other is +Samarra on the Tigris (see map, p. 125). Both these sites yield the +handsome painted pottery of the style called Samarran: in fact it +is Samarra which gives its name to the pottery. Neither Baghouz nor +Samarra have completely Hassunan types of assemblages, and at Samarra +there are a few pots of proper Halafian style. I suppose that Samarra +and Baghouz give us glimpses of those early farmers who had begun to +finger their way down the mud-flats of the river banks toward the +fertile but yet untilled southland. + + +CLASSIC SOUTHERN MESOPOTAMIA FIRST OCCUPIED + +Our next step is into the southland proper. Here, deep in the core of +the mound which later became the holy Sumerian city of Eridu, Iraqi +archeologists uncovered a handsome painted pottery. Pottery of the same +type had been noticed earlier by German archeologists on the surface +of a small mound, awash in the spring floods, near the remains of the +Biblical city of Erich (Sumerian = Uruk; Arabic = Warka). This �Eridu� +pottery, which is about all we have of the assemblage of the people who +once produced it, may be seen as a blend of the Samarran and Halafian +painted pottery styles. This may over-simplify the case, but as yet we +do not have much evidence to go on. The idea does at least fit with my +interpretation of the meaning of Baghouz and Samarra as way-points on +the mud-flats of the rivers half way down from the north. + +My colleague, Robert Adams, believes that there were certainly +riverine-adapted food-collectors living in lower Mesopotamia. The +presence of such would explain why the Eridu assemblage is not simply +the sum of the Halafian and Samarran assemblages. But the domesticated +plants and animals and the basic ways of food-production must have +come from the hilly-flanks country in the north. + +Above the basal Eridu levels, and at a number of other sites in the +south, comes a full-fledged assemblage called Ubaid. Incidentally, +there is an aspect of the Ubaidian assemblage in the north as well. It +seems to move into place before the Halaf manifestation is finished, +and to blend with it. The Ubaidian assemblage in the south is by far +the more spectacular. The development of the temple has been traced +at Eridu from a simple little structure to a monumental building some +62 feet long, with a pilaster-decorated fa�ade and an altar in its +central chamber. There is painted Ubaidian pottery, but the style is +hurried and somewhat careless and gives the _impression_ of having been +a cheap mass-production means of decoration when compared with the +carefully drafted styles of Samarra and Halaf. The Ubaidian people made +other items of baked clay: sickles and axes of very hard-baked clay +are found. The northern Ubaidian sites have yielded tools of copper, +but metal tools of unquestionable Ubaidian find-spots are not yet +available from the south. Clay figurines of human beings with monstrous +turtle-like faces are another item in the southern Ubaidian assemblage. + +[Illustration: SKETCH OF SELECTED ITEMS OF UBAIDIAN ASSEMBLAGE] + +There is a large Ubaid cemetery at Eridu, much of it still awaiting +excavation. The few skeletons so far tentatively studied reveal a +completely modern type of �Mediterraneanoid�; the individuals whom the +skeletons represent would undoubtedly blend perfectly into the modern +population of southern Iraq. What the Ubaidian assemblage says to us is +that these people had already adapted themselves and their culture to +the peculiar riverine environment of classic southern Mesopotamia. For +example, hard-baked clay axes will chop bundles of reeds very well, or +help a mason dress his unbaked mud bricks, and there were only a few +soft and pithy species of trees available. The Ubaidian levels of Eridu +yield quantities of date pits; that excellent and characteristically +Iraqi fruit was already in use. The excavators also found the clay +model of a ship, with the stepping-point for a mast, so that Sinbad the +Sailor must have had his antecedents as early as the time of Ubaid. +The bones of fish, which must have flourished in the larger canals as +well as in the rivers, are common in the Ubaidian levels and thereafter. + + +THE UBAIDIAN ACHIEVEMENT + +On present evidence, my tendency is to see the Ubaidian assemblage +in southern Iraq as the trace of a new era. I wish there were more +evidence, but what we have suggests this to me. The culture of southern +Ubaid soon became a culture of towns--of centrally located towns with +some rural villages about them. The town had a temple and there must +have been priests. These priests probably had political and economic +functions as well as religious ones, if the somewhat later history of +Mesopotamia may suggest a pattern for us. Presently the temple and its +priesthood were possibly the focus of the market; the temple received +its due, and may already have had its own lands and herds and flocks. +The people of the town, undoubtedly at least in consultation with the +temple administration, planned and maintained the simple irrigation +ditches. As the system flourished, the community of rural farmers would +have produced more than sufficient food. The tendency for specialized +crafts to develop--tentative at best at the cultural level of the +earlier village-farming community era--would now have been achieved, +and probably many other specialists in temple administration, water +control, architecture, and trade would also have appeared, as the +surplus food-supply was assured. + +Southern Mesopotamia is not a land rich in natural resources other +than its fertile soil. Stone, good wood for construction, metal, and +innumerable other things would have had to be imported. Grain and +dates--although both are bulky and difficult to transport--and wool and +woven stuffs must have been the mediums of exchange. Over what area did +the trading net-work of Ubaid extend? We start with the idea that the +Ubaidian assemblage is most richly developed in the south. We assume, I +think, correctly, that it represents a cultural flowering of the south. +On the basis of the pottery of the still elusive �Eridu� immigrants +who had first followed the rivers into alluvial Mesopotamia, we get +the notion that the characteristic painted pottery style of Ubaid +was developed in the southland. If this reconstruction is correct +then we may watch with interest where the Ubaid pottery-painting +tradition spread. We have already mentioned that there is a substantial +assemblage of (and from the southern point of view, _fairly_ pure) +Ubaidian material in northern Iraq. The pottery appears all along the +Iranian flanks, even well east of the head of the Persian Gulf, and +ends in a later and spectacular flourish in an extremely handsome +painted style called the �Susa� style. Ubaidian pottery has been noted +up the valleys of both of the great rivers, well north of the Iraqi +and Syrian borders on the southern flanks of the Anatolian plateau. +It reaches the Mediterranean Sea and the valley of the Orontes in +Syria, and it may be faintly reflected in the painted style of a +site called Ghassul, on the east bank of the Jordan in the Dead Sea +Valley. Over this vast area--certainly in all of the great basin of +the Tigris-Euphrates drainage system and its natural extensions--I +believe we may lay our fingers on the traces of a peculiar way of +decorating pottery, which we call Ubaidian. This cursive and even +slap-dash decoration, it appears to me, was part of a new cultural +tradition which arose from the adjustments which immigrant northern +farmers first made to the new and challenging environment of southern +Mesopotamia. But exciting as the idea of the spread of influences of +the Ubaid tradition in space may be, I believe you will agree that the +consequences of the growth of that tradition in southern Mesopotamia +itself, as time passed, are even more important. + + +THE WARKA PHASE IN THE SOUTH + +So far, there are only two radiocarbon determinations for the Ubaidian +assemblage, one from Tepe Gawra in the north and one from Warka in the +south. My hunch would be to use the dates 4500 to 3750 B.C., with a +plus or more probably a minus factor of about two hundred years for +each, as the time duration of the Ubaidian assemblage in southern +Mesopotamia. + +Next, much to our annoyance, we have what is almost a temporary +black-out. According to the system of terminology I favor, our next +�assemblage� after that of Ubaid is called the _Warka_ phase, from +the Arabic name for the site of Uruk or Erich. We know it only from +six or seven levels in a narrow test-pit at Warka, and from an even +smaller hole at another site. This �assemblage,� so far, is known only +by its pottery, some of which still bears Ubaidian style painting. The +characteristic Warkan pottery is unpainted, with smoothed red or gray +surfaces and peculiar shapes. Unquestionably, there must be a great +deal more to say about the Warkan assemblage, but someone will first +have to excavate it! + + +THE DAWN OF CIVILIZATION + +After our exasperation with the almost unknown Warka interlude, +following the brilliant �false dawn� of Ubaid, we move next to an +assemblage which yields traces of a preponderance of those elements +which we noted (p. 144) as meaning civilization. This assemblage +is that called _Proto-Literate_; it already contains writing. On +the somewhat shaky principle that writing, however early, means +history--and no longer prehistory--the assemblage is named for the +historical implications of its content, and no longer after the name of +the site where it was first found. Since some of the older books used +site-names for this assemblage, I will tell you that the Proto-Literate +includes the latter half of what used to be called the �Uruk period� +_plus_ all of what used to be called the �Jemdet Nasr period.� It shows +a consistent development from beginning to end. + +I shall, in fact, leave much of the description and the historic +implications of the Proto-Literate assemblage to the conventional +historians. Professor T. J. Jacobsen, reaching backward from the +legends he finds in the cuneiform writings of slightly later times, can +in fact tell you a more complete story of Proto-Literate culture than +I can. It should be enough here if I sum up briefly what the excavated +archeological evidence shows. + +We have yet to dig a Proto-Literate site in its entirety, but the +indications are that the sites cover areas the size of small cities. +In architecture, we know of large and monumental temple structures, +which were built on elaborate high terraces. The plans and decoration +of these temples follow the pattern set in the Ubaid phase: the chief +difference is one of size. The German excavators at the site of Warka +reckoned that the construction of only one of the Proto-Literate temple +complexes there must have taken 1,500 men, each working a ten-hour day, +five years to build. + + +ART AND WRITING + +If the architecture, even in its monumental forms, can be seen to +stem from Ubaidian developments, this is not so with our other +evidence of Proto-Literate artistic expression. In relief and applied +sculpture, in sculpture in the round, and on the engraved cylinder +seals--all of which now make their appearance--several completely +new artistic principles are apparent. These include the composition +of subject-matter in groups, commemorative scenes, and especially +the ability and apparent desire to render the human form and face. +Excellent as the animals of the Franco-Cantabrian art may have been +(see p. 85), and however handsome were the carefully drafted +geometric designs and conventionalized figures on the pottery of the +early farmers, there seems to have been, up to this time, a mental +block about the drawing of the human figure and especially the human +face. We do not yet know what caused this self-consciousness about +picturing themselves which seems characteristic of men before the +appearance of civilization. We do know that with civilization, the +mental block seems to have been removed. + +Clay tablets bearing pictographic signs are the Proto-Literate +forerunners of cuneiform writing. The earliest examples are not well +understood but they seem to be �devices for making accounts and +for remembering accounts.� Different from the later case in Egypt, +where writing appears fully formed in the earliest examples, the +development from simple pictographic signs to proper cuneiform writing +may be traced, step by step, in Mesopotamia. It is most probable +that the development of writing was connected with the temple and +the need for keeping account of the temple�s possessions. Professor +Jacobsen sees writing as a means for overcoming space, time, and the +increasing complications of human affairs: �Literacy, which began +with ... civilization, enhanced mightily those very tendencies in its +development which characterize it as a civilization and mark it off as +such from other types of culture.� + +[Illustration: RELIEF ON A PROTO-LITERATE STONE VASE, WARKA + +Unrolled drawing, with restoration suggested by figures from +contemporary cylinder seals] + +While the new principles in art and the idea of writing are not +foreshadowed in the Ubaid phase, or in what little we know of the +Warkan, I do not think we need to look outside southern Mesopotamia +for their beginnings. We do know something of the adjacent areas, +too, and these beginnings are not there. I think we must accept them +as completely new discoveries, made by the people who were developing +the whole new culture pattern of classic southern Mesopotamia. Full +description of the art, architecture, and writing of the Proto-Literate +phase would call for many details. Men like Professor Jacobsen and Dr. +Adams can give you these details much better than I can. Nor shall I do +more than tell you that the common pottery of the Proto-Literate phase +was so well standardized that it looks factory made. There was also +some handsome painted pottery, and there were stone bowls with inlaid +decoration. Well-made tools in metal had by now become fairly common, +and the metallurgist was experimenting with the casting process. Signs +for plows have been identified in the early pictographs, and a wheeled +chariot is shown on a cylinder seal engraving. But if I were forced to +a guess in the matter, I would say that the development of plows and +draft-animals probably began in the Ubaid period and was another of the +great innovations of that time. + +The Proto-Literate assemblage clearly suggests a highly developed and +sophisticated culture. While perhaps not yet fully urban, it is on +the threshold of urbanization. There seems to have been a very dense +settlement of Proto-Literate sites in classic southern Mesopotamia, +many of them newly founded on virgin soil where no earlier settlements +had been. When we think for a moment of what all this implies, of the +growth of an irrigation system which must have existed to allow the +flourish of this culture, and of the social and political organization +necessary to maintain the irrigation system, I think we will agree that +at last we are dealing with civilization proper. + + +FROM PREHISTORY TO HISTORY + +Now it is time for the conventional ancient historians to take over +the story from me. Remember this when you read what they write. Their +real base-line is with cultures ruled over by later kings and emperors, +whose writings describe military campaigns and the administration of +laws and fully organized trading ventures. To these historians, the +Proto-Literate phase is still a simple beginning for what is to follow. +If they mention the Ubaid assemblage at all--the one I was so lyrical +about--it will be as some dim and fumbling step on the path to the +civilized way of life. + +I suppose you could say that the difference in the approach is that as +a prehistorian I have been looking forward or upward in time, while the +historians look backward to glimpse what I�ve been describing here. My +base-line was half a million years ago with a being who had little more +than the capacity to make tools and fire to distinguish him from the +animals about him. Thus my point of view and that of the conventional +historian are bound to be different. You will need both if you want to +understand all of the story of men, as they lived through time to the +present. + + + + +End of PREHISTORY + +[Illustration] + + +You�ll doubtless easily recall your general course in ancient history: +how the Sumerian dynasties of Mesopotamia were supplanted by those of +Babylonia, how the Hittite kingdom appeared in Anatolian Turkey, and +about the three great phases of Egyptian history. The literate kingdom +of Crete arose, and by 1500 B.C. there were splendid fortified Mycenean +towns on the mainland of Greece. This was the time--about the whole +eastern end of the Mediterranean--of what Professor Breasted called the +�first great internationalism,� with flourishing trade, international +treaties, and royal marriages between Egyptians, Babylonians, and +Hittites. By 1200 B.C., the whole thing had fragmented: �the peoples of +the sea were restless in their isles,� and the great ancient centers in +Egypt, Mesopotamia, and Anatolia were eclipsed. Numerous smaller states +arose--Assyria, Phoenicia, Israel--and the Trojan war was fought. +Finally Assyria became the paramount power of all the Near East, +presently to be replaced by Persia. + +A new culture, partaking of older west Asiatic and Egyptian elements, +but casting them with its own tradition into a new mould, arose in +mainland Greece. + +I once shocked my Classical colleagues to the core by referring to +Greece as �a second degree derived civilization,� but there is much +truth in this. The principles of bronze- and then of iron-working, of +the alphabet, and of many other elements in Greek culture were borrowed +from western Asia. Our debt to the Greeks is too well known for me even +to mention it, beyond recalling to you that it is to Greece we owe the +beginnings of rational or empirical science and thought in general. But +Greece fell in its turn to Rome, and in 55 B.C. Caesar invaded Britain. + +I last spoke of Britain on page 142; I had chosen it as my single +example for telling you something of how the earliest farming +communities were established in Europe. Now I will continue with +Britain�s later prehistory, so you may sense something of the end of +prehistory itself. Remember that Britain is simply a single example +we select; the same thing could be done for all the other countries +of Europe, and will be possible also, some day, for further Asia and +Africa. Remember, too, that prehistory in most of Europe runs on for +three thousand or more years _after_ conventional ancient history +begins in the Near East. Britain is a good example to use in showing +how prehistory ended in Europe. As we said earlier, it lies at the +opposite end of Europe from the area of highest cultural achievement in +those times, and should you care to read more of the story in detail, +you may do so in the English language. + + +METAL USERS REACH ENGLAND + +We left the story of Britain with the peoples who made three different +assemblages--the Windmill Hill, the megalith-builders, and the +Peterborough--making adjustments to their environments, to the original +inhabitants of the island, and to each other. They had first arrived +about 2500 B.C., and were simple pastoralists and hoe cultivators who +lived in little village communities. Some of them planted little if any +grain. By 2000 B.C., they were well settled in. Then, somewhere in the +range from about 1900 to 1800 B.C., the traces of the invasion of a new +series of peoples began to appear. + +The first newcomers are called the Beaker folk, after the name of a +peculiar form of pottery they made. The beaker type of pottery seems +oldest in Spain, where it occurs with great collective tombs of +megalithic construction and with copper tools. But the Beaker folk who +reached England seem already to have moved first from Spain(?) to the +Rhineland and Holland. While in the Rhineland, and before leaving for +England, the Beaker folk seem to have mixed with the local population +and also with incomers from northeastern Europe whose culture included +elements brought originally from the Near East by the eastern way +through the steppes. This last group has also been named for a peculiar +article in its assemblage; the group is called the Battle-axe folk. A +few Battle-axe folk elements, including, in fact, stone battle-axes, +reached England with the earliest Beaker folk,[6] coming from the +Rhineland. + + [6] The British authors use the term �Beaker folk� to mean both + archeological assemblage and human physical type. They speak + of a �... tall, heavy-boned, rugged, and round-headed� strain + which they take to have developed, apparently in the Rhineland, + by a mixture of the original (Spanish?) beaker-makers and + the northeast European battle-axe makers. However, since the + science of physical anthropology is very much in flux at the + moment, and since I am not able to assess the evidence for these + physical types, I _do not_ use the term �folk� in this book with + its usual meaning of standardized physical type. When I use + �folk� here, I mean simply _the makers of a given archeological + assemblage_. The difficulty only comes when assemblages are + named for some item in them; it is too clumsy to make an + adjective of the item and refer to a �beakerian� assemblage. + +The Beaker folk settled earliest in the agriculturally fertile south +and east. There seem to have been several phases of Beaker folk +invasions, and it is not clear whether these all came strictly from the +Rhineland or Holland. We do know that their copper daggers and awls +and armlets are more of Irish or Atlantic European than of Rhineland +origin. A few simple habitation sites and many burials of the Beaker +folk are known. They buried their dead singly, sometimes in conspicuous +individual barrows with the dead warrior in his full trappings. The +spectacular element in the assemblage of the Beaker folk is a group +of large circular monuments with ditches and with uprights of wood or +stone. These �henges� became truly monumental several hundred years +later; while they were occasionally dedicated with a burial, they were +not primarily tombs. The effect of the invasion of the Beaker folk +seems to cut across the whole fabric of life in Britain. + +[Illustration: BEAKER] + +There was, however, a second major element in British life at this +time. It shows itself in the less well understood traces of a group +again called after one of the items in their catalogue, the Food-vessel +folk. There are many burials in these �food-vessel� pots in northern +England, Scotland, and Ireland, and the pottery itself seems to +link back to that of the Peterborough assemblage. Like the earlier +Peterborough people in the highland zone before them, the makers of +the food-vessels seem to have been heavily involved in trade. It is +quite proper to wonder whether the food-vessel pottery itself was made +by local women who were married to traders who were middlemen in the +transmission of Irish metal objects to north Germany and Scandinavia. +The belt of high, relatively woodless country, from southwest to +northeast, was already established as a natural route for inland trade. + + +MORE INVASIONS + +About 1500 B.C., the situation became further complicated by the +arrival of new people in the region of southern England anciently +called Wessex. The traces suggest the Brittany coast of France as a +source, and the people seem at first to have been a small but �heroic� +group of aristocrats. Their �heroes� are buried with wealth and +ceremony, surrounded by their axes and daggers of bronze, their gold +ornaments, and amber and jet beads. These rich finds show that the +trade-linkage these warriors patronized spread from the Baltic sources +of amber to Mycenean Greece or even Egypt, as evidenced by glazed blue +beads. + +The great visual trace of Wessex achievement is the final form of +the spectacular sanctuary at Stonehenge. A wooden henge or circular +monument was first made several hundred years earlier, but the site +now received its great circles of stone uprights and lintels. The +diameter of the surrounding ditch at Stonehenge is about 350 feet, the +diameter of the inner circle of large stones is about 100 feet, and +the tallest stone of the innermost horseshoe-shaped enclosure is 29 +feet 8 inches high. One circle is made of blue stones which must have +been transported from Pembrokeshire, 145 miles away as the crow flies. +Recently, many carvings representing the profile of a standard type of +bronze axe of the time, and several profiles of bronze daggers--one of +which has been called Mycenean in type--have been found carved in the +stones. We cannot, of course, describe the details of the religious +ceremonies which must have been staged in Stonehenge, but we can +certainly imagine the well-integrated and smoothly working culture +which must have been necessary before such a great monument could have +been built. + + +�THIS ENGLAND� + +The range from 1900 to about 1400 B.C. includes the time of development +of the archeological features usually called the �Early Bronze Age� +in Britain. In fact, traces of the Wessex warriors persisted down to +about 1200 B.C. The main regions of the island were populated, and the +adjustments to the highland and lowland zones were distinct and well +marked. The different aspects of the assemblages of the Beaker folk and +the clearly expressed activities of the Food-vessel folk and the Wessex +warriors show that Britain was already taking on her characteristic +trading role, separated from the European continent but conveniently +adjacent to it. The tin of Cornwall--so important in the production +of good bronze--as well as the copper of the west and of Ireland, +taken with the gold of Ireland and the general excellence of Irish +metal work, assured Britain a trader�s place in the then known world. +Contacts with the eastern Mediterranean may have been by sea, with +Cornish tin as the attraction, or may have been made by the Food-vessel +middlemen on their trips to the Baltic coast. There they would have +encountered traders who traveled the great north-south European road, +by which Baltic amber moved southward to Greece and the Levant, and +ideas and things moved northward again. + +There was, however, the Channel between England and Europe, and this +relative isolation gave some peace and also gave time for a leveling +and further fusion of culture. The separate cultural traditions began +to have more in common. The growing of barley, the herding of sheep and +cattle, and the production of woolen garments were already features +common to all Britain�s inhabitants save a few in the remote highlands, +the far north, and the distant islands not yet fully touched by +food-production. The �personality of Britain� was being formed. + + +CREMATION BURIALS BEGIN + +Along with people of certain religious faiths, archeologists are +against cremation (for other people!). Individuals to be cremated seem +in past times to have been dressed in their trappings and put upon a +large pyre: it takes a lot of wood and a very hot fire for a thorough +cremation. When the burning had been completed, the few fragile scraps +of bone and such odd beads of stone or other rare items as had resisted +the great heat seem to have been whisked into a pot and the pot buried. +The archeologist is left with the pot and the unsatisfactory scraps in +it. + +Tentatively, after about 1400 B.C. and almost completely over the whole +island by 1200 B.C., Britain became the scene of cremation burials +in urns. We know very little of the people themselves. None of their +settlements have been identified, although there is evidence that they +grew barley and made enclosures for cattle. The urns used for the +burials seem to have antecedents in the pottery of the Food-vessel +folk, and there are some other links with earlier British traditions. +In Lancashire, a wooden circle seems to have been built about a grave +with cremated burials in urns. Even occasional instances of cremation +may be noticed earlier in Britain, and it is not clear what, if any, +connection the British cremation burials in urns have with the classic +_Urnfields_ which were now beginning in the east Mediterranean and +which we shall mention below. + +The British cremation-burial-in-urns folk survived a long time in the +highland zone. In the general British scheme, they make up what is +called the �Middle Bronze Age,� but in the highland zone they last +until after 900 B.C. and are considered to be a specialized highland +�Late Bronze Age.� In the highland zone, these later cremation-burial +folk seem to have continued the older Food-vessel tradition of being +middlemen in the metal market. + +Granting that our knowledge of this phase of British prehistory is +very restricted because the cremations have left so little for the +archeologist, it does not appear that the cremation-burial-urn folk can +be sharply set off from their immediate predecessors. But change on a +grander scale was on the way. + + +REVERBERATIONS FROM CENTRAL EUROPE + +In the centuries immediately following 1000 B.C., we see with fair +clarity two phases of a cultural process which must have been going +on for some time. Certainly several of the invasions we have already +described in this chapter were due to earlier phases of the same +cultural process, but we could not see the details. + +[Illustration: SLASHING SWORD] + +Around 1200 B.C. central Europe was upset by the spread of the +so-called Urnfield folk, who practiced cremation burial in urns and +whom we also know to have been possessors of long, slashing swords and +the horse. I told you above that we have no idea that the Urnfield +folk proper were in any way connected with the people who made +cremation-burial-urn cemeteries a century or so earlier in Britain. It +has been supposed that the Urnfield folk themselves may have shared +ideas with the people who sacked Troy. We know that the Urnfield +pressure from central Europe displaced other people in northern France, +and perhaps in northwestern Germany, and that this reverberated into +Britain about 1000 B.C. + +Soon after 750 B.C., the same thing happened again. This time, the +pressure from central Europe came from the Hallstatt folk who were iron +tool makers: the reverberation brought people from the western Alpine +region across the Channel into Britain. + +At first it is possible to see the separate results of these folk +movements, but the developing cultures soon fused with each other and +with earlier British elements. Presently there were also strains of +other northern and western European pottery and traces of Urnfield +practices themselves which appeared in the finished British product. I +hope you will sense that I am vastly over-simplifying the details. + +The result seems to have been--among other things--a new kind of +agricultural system. The land was marked off by ditched divisions. +Rectangular fields imply the plow rather than hoe cultivation. We seem +to get a picture of estate or tribal boundaries which included village +communities; we find a variety of tools in bronze, and even whetstones +which show that iron has been honed on them (although the scarce iron +has not been found). Let me give you the picture in Professor S. +Piggott�s words: �The ... Late Bronze Age of southern England was but +the forerunner of the earliest Iron Age in the same region, not only in +the techniques of agriculture, but almost certainly in terms of ethnic +kinship ... we can with some assurance talk of the Celts ... the great +early Celtic expansion of the Continent is recognized to be that of the +Urnfield people.� + +Thus, certainly by 500 B.C., there were people in Britain, some of +whose descendants we may recognize today in name or language in remote +parts of Wales, Scotland, and the Hebrides. + + +THE COMING OF IRON + +Iron--once the know-how of reducing it from its ore in a very hot, +closed fire has been achieved--produces a far cheaper and much more +efficient set of tools than does bronze. Iron tools seem first to +have been made in quantity in Hittite Anatolia about 1500 B.C. In +continental Europe, the earliest, so-called Hallstatt, iron-using +cultures appeared in Germany soon after 750 B.C. Somewhat later, +Greek and especially Etruscan exports of _objets d�art_--which moved +with a flourishing trans-Alpine wine trade--influenced the Hallstatt +iron-working tradition. Still later new classical motifs, together with +older Hallstatt, oriental, and northern nomad motifs, gave rise to a +new style in metal decoration which characterizes the so-called La T�ne +phase. + +A few iron users reached Britain a little before 400 B.C. Not long +after that, a number of allied groups appeared in southern and +southeastern England. They came over the Channel from France and must +have been Celts with dialects related to those already in England. A +second wave of Celts arrived from the Marne district in France about +250 B.C. Finally, in the second quarter of the first century B.C., +there were several groups of newcomers, some of whom were Belgae of +a mixed Teutonic-Celtic confederacy of tribes in northern France and +Belgium. The Belgae preceded the Romans by only a few years. + + +HILL-FORTS AND FARMS + +The earliest iron-users seem to have entrenched themselves temporarily +within hill-top forts, mainly in the south. Gradually, they moved +inland, establishing _individual_ farm sites with extensive systems +of rectangular fields. We recognize these fields by the �lynchets� or +lines of soil-creep which plowing left on the slopes of hills. New +crops appeared; there were now bread wheat, oats, and rye, as well as +barley. + +At Little Woodbury, near the town of Salisbury, a farmstead has been +rather completely excavated. The rustic buildings were within a +palisade, the round house itself was built of wood, and there were +various outbuildings and pits for the storage of grain. Weaving was +done on the farm, but not blacksmithing, which must have been a +specialized trade. Save for the lack of firearms, the place might +almost be taken for a farmstead on the American frontier in the early +1800�s. + +Toward 250 B.C. there seems to have been a hasty attempt to repair the +hill-forts and to build new ones, evidently in response to signs of +restlessness being shown by remote relatives in France. + + +THE SECOND PHASE + +Perhaps the hill-forts were not entirely effective or perhaps a +compromise was reached. In any case, the newcomers from the Marne +district did establish themselves, first in the southeast and then to +the north and west. They brought iron with decoration of the La T�ne +type and also the two-wheeled chariot. Like the Wessex warriors of +over a thousand years earlier, they made �heroes�� graves, with their +warriors buried in the war-chariots and dressed in full trappings. + +[Illustration: CELTIC BUCKLE] + +The metal work of these Marnian newcomers is excellent. The peculiar +Celtic art style, based originally on the classic tendril motif, +is colorful and virile, and fits with Greek and Roman descriptions +of Celtic love of color in dress. There is a strong trace of these +newcomers northward in Yorkshire, linked by Ptolemy�s description to +the Parisii, doubtless part of the Celtic tribe which originally gave +its name to Paris on the Seine. Near Glastonbury, in Somerset, two +villages in swamps have been excavated. They seem to date toward the +middle of the first century B.C., which was a troubled time in Britain. +The circular houses were built on timber platforms surrounded with +palisades. The preservation of antiquities by the water-logged peat of +the swamp has yielded us a long catalogue of the materials of these +villagers. + +In Scotland, which yields its first iron tools at a date of about 100 +B.C., and in northern Ireland even slightly earlier, the effects of the +two phases of newcomers tend especially to blend. Hill-forts, �brochs� +(stone-built round towers) and a variety of other strange structures +seem to appear as the new ideas develop in the comparative isolation of +northern Britain. + + +THE THIRD PHASE + +For the time of about the middle of the first century B.C., we again +see traces of frantic hill-fort construction. This simple military +architecture now took some new forms. Its multiple ramparts must +reflect the use of slings as missiles, rather than spears. We probably +know the reason. In 56 B.C., Julius Caesar chastised the Veneti of +Brittany for outraging the dignity of Roman ambassadors. The Veneti +were famous slingers, and doubtless the reverberations of escaping +Veneti were felt across the Channel. The military architecture suggests +that some Veneti did escape to Britain. + +Also, through Caesar, we learn the names of newcomers who arrived in +two waves, about 75 B.C. and about 50 B.C. These were the Belgae. Now, +at last, we can even begin to speak of dynasties and individuals. +Some time before 55 B.C., the Catuvellauni, originally from the Marne +district in France, had possessed themselves of a large part of +southeastern England. They evidently sailed up the Thames and built a +town of over a hundred acres in area. Here ruled Cassivellaunus, �the +first man in England whose name we know,� and whose town Caesar sacked. +The town sprang up elsewhere again, however. + + +THE END OF PREHISTORY + +Prehistory, strictly speaking, is now over in southern Britain. +Claudius� effective invasion took place in 43 A.D.; by 83 A.D., a raid +had been made as far north as Aberdeen in Scotland. But by 127 A.D., +Hadrian had completed his wall from the Solway to the Tyne, and the +Romans settled behind it. In Scotland, Romanization can have affected +the countryside very little. Professor Piggott adds that �... it is +when the pressure of Romanization is relaxed by the break-up of the +Dark Ages that we see again the Celtic metal-smiths handling their +material with the same consummate skill as they had before the Roman +Conquest, and with traditional styles that had not even then forgotten +their Marnian and Belgic heritage.� + +In fact, many centuries go by, in Britain as well as in the rest of +Europe, before the archeologist�s task is complete and the historian on +his own is able to describe the ways of men in the past. + + +BRITAIN AS A SAMPLE OF THE GENERAL COURSE OF PREHISTORY IN EUROPE + +In giving this very brief outline of the later prehistory of Britain, +you will have noticed how often I had to refer to the European +continent itself. Britain, beyond the English Channel for all of her +later prehistory, had a much simpler course of events than did most of +the rest of Europe in later prehistoric times. This holds, in spite +of all the �invasions� and �reverberations� from the continent. Most +of Europe was the scene of an even more complicated ebb and flow of +cultural change, save in some of its more remote mountain valleys and +peninsulas. + +The whole course of later prehistory in Europe is, in fact, so very +complicated that there is no single good book to cover it all; +certainly there is none in English. There are some good regional +accounts and some good general accounts of part of the range from about +3000 B.C. to A.D. 1. I suspect that the difficulty of making a good +book that covers all of its later prehistory is another aspect of what +makes Europe so very complicated a continent today. The prehistoric +foundations for Europe�s very complicated set of civilizations, +cultures, and sub-cultures--which begin to appear as history +proceeds--were in themselves very complicated. + +Hence, I selected the case of Britain as a single example of how +prehistory ends in Europe. It could have been more complicated than we +found it to be. Even in the subject matter on Britain in the chapter +before the last, we did not see direct traces of the effect on Britain +of the very important developments which took place in the Danubian +way from the Near East. Apparently Britain was not affected. Britain +received the impulses which brought copper, bronze, and iron tools from +an original east Mediterranean homeland into Europe, almost at the ends +of their journeys. But by the same token, they had had time en route to +take on their characteristic European aspects. + +Some time ago, Sir Cyril Fox wrote a famous book called _The +Personality of Britain_, sub-titled �Its Influence on Inhabitant and +Invader in Prehistoric and Early Historic Times.� We have not gone +into the post-Roman early historic period here; there are still the +Anglo-Saxons and Normans to account for as well as the effects of +the Romans. But what I have tried to do was to begin the story of +how the personality of Britain was formed. The principles that Fox +used, in trying to balance cultural and environmental factors and +interrelationships would not be greatly different for other lands. + + + + +Summary + +[Illustration] + + +In the pages you have read so far, you have been brought through the +earliest 99 per cent of the story of man�s life on this planet. I have +left only 1 per cent of the story for the historians to tell. + + +THE DRAMA OF THE PAST + +Men first became men when evolution had carried them to a certain +point. This was the point where the eye-hand-brain co-ordination was +good enough so that tools could be made. When tools began to be made +according to sets of lasting habits, we know that men had appeared. +This happened over a half million years ago. The stage for the play +may have been as broad as all of Europe, Africa, and Asia. At least, +it seems unlikely that it was only one little region that saw the +beginning of the drama. + +Glaciers and different climates came and went, to change the settings. +But the play went on in the same first act for a very long time. The +men who were the players had simple roles. They had to feed themselves +and protect themselves as best they could. They did this by hunting, +catching, and finding food wherever they could, and by taking such +protection as caves, fire, and their simple tools would give them. +Before the first act was over, the last of the glaciers was melting +away, and the players had added the New World to their stage. If +we want a special name for the first act, we could call it _The +Food-Gatherers_. + +There were not many climaxes in the first act, so far as we can see. +But I think there may have been a few. Certainly the pace of the +first act accelerated with the swing from simple gathering to more +intensified collecting. The great cave art of France and Spain was +probably an expression of a climax. Even the ideas of burying the dead +and of the �Venus� figurines must also point to levels of human thought +and activity that were over and above pure food-getting. + + +THE SECOND ACT + +The second act began only about ten thousand years ago. A few of the +players started it by themselves near the center of the Old World part +of the stage, in the Near East. It began as a plant and animal act, but +it soon became much more complicated. + +But the players in this one part of the stage--in the Near East--were +not the only ones to start off on the second act by themselves. Other +players, possibly in several places in the Far East, and certainly in +the New World, also started second acts that began as plant and animal +acts, and then became complicated. We can call the whole second act +_The Food-Producers_. + + +THE FIRST GREAT CLIMAX OF THE SECOND ACT + +In the Near East, the first marked climax of the second act happened +in Mesopotamia and Egypt. The play and the players reached that great +climax that we call civilization. This seems to have come less than +five thousand years after the second act began. But it could never have +happened in the first act at all. + +There is another curious thing about the first act. Many of the players +didn�t know it was over and they kept on with their roles long after +the second act had begun. On the edges of the stage there are today +some players who are still going on with the first act. The Eskimos, +and the native Australians, and certain tribes in the Amazon jungle are +some of these players. They seem perfectly happy to keep on with the +first act. + +The second act moved from climax to climax. The civilizations of +Mesopotamia and Egypt were only the earliest of these climaxes. The +players to the west caught the spirit of the thing, and climaxes +followed there. So also did climaxes come in the Far Eastern and New +World portions of the stage. + +The greater part of the second act should really be described to you +by a historian. Although it was a very short act when compared to the +first one, the climaxes complicate it a great deal. I, a prehistorian, +have told you about only the first act, and the very beginning of the +second. + + +THE THIRD ACT + +Also, as a prehistorian I probably should not even mention the third +act--it began so recently. The third act is _The Industrialization_. +It is the one in which we ourselves are players. If the pace of the +second act was so much faster than that of the first, the pace of the +third act is terrific. The danger is that it may wear down the players +completely. + +What sort of climaxes will the third act have, and are we already in +one? You have seen by now that the acts of my play are given in terms +of modes or basic patterns of human economy--ways in which people +get food and protection and safety. The climaxes involve more than +human economy. Economics and technological factors may be part of the +climaxes, but they are not all. The climaxes may be revolutions in +their own way, intellectual and social revolutions if you like. + +If the third act follows the pattern of the second act, a climax should +come soon after the act begins. We may be due for one soon if we are +not already in it. Remember the terrific pace of this third act. + + +WHY BOTHER WITH PREHISTORY? + +Why do we bother about prehistory? The main reason is that we think it +may point to useful ideas for the present. We are in the troublesome +beginnings of the third act of the play. The beginnings of the second +act may have lessons for us and give depth to our thinking. I know +there are at least _some_ lessons, even in the present incomplete +state of our knowledge. The players who began the second act--that of +food-production--separately, in different parts of the world, were not +all of one �pure race� nor did they have �pure� cultural traditions. +Some apparently quite mixed Mediterraneans got off to the first start +on the second act and brought it to its first two climaxes as well. +Peoples of quite different physical type achieved the first climaxes in +China and in the New World. + +In our British example of how the late prehistory of Europe worked, we +listed a continuous series of �invasions� and �reverberations.� After +each of these came fusion. Even though the Channel protected Britain +from some of the extreme complications of the mixture and fusion of +continental Europe, you can see how silly it would be to refer to a +�pure� British race or a �pure� British culture. We speak of the United +States as a �melting pot.� But this is nothing new. Actually, Britain +and all the rest of the world have been �melting pots� at one time or +another. + +By the time the written records of Mesopotamia and Egypt begin to turn +up in number, the climaxes there are well under way. To understand the +beginnings of the climaxes, and the real beginnings of the second act +itself, we are thrown back on prehistoric archeology. And this is as +true for China, India, Middle America, and the Andes, as it is for the +Near East. + +There are lessons to be learned from all of man�s past, not simply +lessons of how to fight battles or win peace conferences, but of how +human society evolves from one stage to another. Many of these lessons +can only be looked for in the prehistoric past. So far, we have only +made a beginning. There is much still to do, and many gaps in the story +are yet to be filled. The prehistorian�s job is to find the evidence, +to fill the gaps, and to discover the lessons men have learned in the +past. As I see it, this is not only an exciting but a very practical +goal for which to strive. + + + + +List of Books + + +BOOKS OF GENERAL INTEREST + +(Chosen from a variety of the increasingly useful list of cheap +paperbound books.) + + Childe, V. Gordon + _What Happened in History._ 1954. Penguin. + _Man Makes Himself._ 1955. Mentor. + _The Prehistory of European Society._ 1958. Penguin. + + Dunn, L. C., and Dobzhansky, Th. + _Heredity, Race, and Society._ 1952. Mentor. + + Frankfort, Henri, Frankfort, H. A., Jacobsen, Thorkild, and Wilson, + John A. + _Before Philosophy._ 1954. Penguin. + + Simpson, George G. + _The Meaning of Evolution._ 1955. Mentor. + + Wheeler, Sir Mortimer + _Archaeology from the Earth._ 1956. Penguin. + + +GEOCHRONOLOGY AND THE ICE AGE + +(Two general books. Some Pleistocene geologists disagree with Zeuner�s +interpretation of the dating evidence, but their points of view appear +in professional journals, in articles too cumbersome to list here.) + + Flint, R. F. + _Glacial Geology and the Pleistocene Epoch._ 1947. John Wiley + and Sons. + + Zeuner, F. E. + _Dating the Past._ 1952 (3rd ed.). Methuen and Co. + + +FOSSIL MEN AND RACE + +(The points of view of physical anthropologists and human +paleontologists are changing very quickly. Two of the different points +of view are listed here.) + + Clark, W. E. Le Gros + _History of the Primates._ 1956 (5th ed.). British Museum + (Natural History). (Also in Phoenix edition, 1957.) + + Howells, W. W. + _Mankind So Far._ 1944. Doubleday, Doran. + + +GENERAL ANTHROPOLOGY + +(These are standard texts not absolutely up to date in every detail, or +interpretative essays concerned with cultural change through time as +well as in space.) + + Kroeber, A. L. + _Anthropology._ 1948. Harcourt, Brace. + + Linton, Ralph + _The Tree of Culture._ 1955. Alfred A. Knopf, Inc. + + Redfield, Robert + _The Primitive World and Its Transformations._ 1953. Cornell + University Press. + + Steward, Julian H. + _Theory of Culture Change._ 1955. University of Illinois Press. + + White, Leslie + _The Science of Culture._ 1949. Farrar, Strauss. + + +GENERAL PREHISTORY + +(A sampling of the more useful and current standard works in English.) + + Childe, V. Gordon + _The Dawn of European Civilization._ 1957. Kegan Paul, Trench, + Trubner. + _Prehistoric Migrations in Europe._ 1950. Instituttet for + Sammenlignende Kulturforskning. + + Clark, Grahame + _Archaeology and Society._ 1957. Harvard University Press. + + Clark, J. G. D. + _Prehistoric Europe: The Economic Basis._ 1952. Methuen and Co. + + Garrod, D. A. E. + _Environment, Tools, and Man._ 1946. Cambridge University + Press. + + Movius, Hallam L., Jr. + �Old World Prehistory: Paleolithic� in _Anthropology Today_. + Kroeber, A. L., ed. 1953. University of Chicago Press. + + Oakley, Kenneth P. + _Man the Tool-Maker._ 1956. British Museum (Natural History). + (Also in Phoenix edition, 1957.) + + Piggott, Stuart + _British Prehistory._ 1949. Oxford University Press. + + Pittioni, Richard + _Die Urgeschichtlichen Grundlagen der Europ�ischen Kultur._ + 1949. Deuticke. (A single book which does attempt to cover the + whole range of European prehistory to ca. 1 A.D.) + + +THE NEAR EAST + + Adams, Robert M. + �Developmental Stages in Ancient Mesopotamia,� _in_ Steward, + Julian, _et al_, _Irrigation Civilizations: A Comparative + Study_. 1955. Pan American Union. + + Braidwood, Robert J. + _The Near East and the Foundations for Civilization._ 1952. + University of Oregon. + + Childe, V. Gordon + _New Light on the Most Ancient East._ 1952. Oriental Dept., + Routledge and Kegan Paul. + + Frankfort, Henri + _The Birth of Civilization in the Near East._ 1951. University + of Indiana Press. (Also in Anchor edition, 1956.) + + Pallis, Svend A. + _The Antiquity of Iraq._ 1956. Munksgaard. + + Wilson, John A. + _The Burden of Egypt._ 1951. University of Chicago Press. (Also + in Phoenix edition, called _The Culture of Ancient Egypt_, + 1956.) + + +HOW DIGGING IS DONE + + Braidwood, Linda + _Digging beyond the Tigris._ 1953. Schuman, New York. + + Wheeler, Sir Mortimer + _Archaeology from the Earth._ 1954. Oxford, London. + + + + +Index + + + Abbevillian, 48; + core-biface tool, 44, 48 + + Acheulean, 48, 60 + + Acheuleo-Levalloisian, 63 + + Acheuleo-Mousterian, 63 + + Adams, R. M., 106 + + Adzes, 45 + + Africa, east, 67, 89; + north, 70, 89; + south, 22, 25, 34, 40, 67 + + Agriculture, incipient, in England, 140; + in Near East, 123 + + Ain Hanech, 48 + + Amber, taken from Baltic to Greece, 167 + + American Indians, 90, 142 + + Anatolia, used as route to Europe, 138 + + Animals, in caves, 54, 64; + in cave art, 85 + + Antevs, Ernst, 19 + + Anyathian, 47 + + Archeological interpretation, 8 + + Archeology, defined, 8 + + Architecture, at Jarmo, 128; + at Jericho, 133 + + Arrow, points, 94; + shaft straightener, 83 + + Art, in caves, 84; + East Spanish, 85; + figurines, 84; + Franco-Cantabrian, 84, 85; + movable (engravings, modeling, scratchings), 83; + painting, 83; + sculpture, 83 + + Asia, western, 67 + + Assemblage, defined, 13, 14; + European, 94; + Jarmo, 129; + Maglemosian, 94; + Natufian, 113 + + Aterian, industry, 67; + point, 89 + + Australopithecinae, 24 + + Australopithecine, 25, 26 + + Awls, 77 + + Axes, 62, 94 + + Ax-heads, 15 + + Azilian, 97 + + Aztecs, 145 + + + Baghouz, 152 + + Bakun, 134 + + Baltic sea, 93 + + Banana, 107 + + Barley, wild, 108 + + Barrow, 141 + + Battle-axe folk, 164; + assemblage, 164 + + Beads, 80; + bone, 114 + + Beaker folk, 164; + assemblage, 164-165 + + Bear, in cave art, 85; + cult, 68 + + Belgium, 94 + + Belt cave, 126 + + Bering Strait, used as route to New World, 98 + + Bison, in cave art, 85 + + Blade, awl, 77; + backed, 75; + blade-core, 71; + end-scraper, 77; + stone, defined, 71; + strangulated (notched), 76; + tanged point, 76; + tools, 71, 75-80, 90; + tool tradition, 70 + + Boar, wild, in cave art, 85 + + Bogs, source of archeological materials, 94 + + Bolas, 54 + + Bordes, Fran�ois, 62 + + Borer, 77 + + Boskop skull, 34 + + Boyd, William C., 35 + + Bracelets, 118 + + Brain, development of, 24 + + Breadfruit, 107 + + Breasted, James H., 107 + + Brick, at Jericho, 133 + + Britain, 94; + late prehistory, 163-175; + invaders, 173 + + Broch, 172 + + Buffalo, in China, 54; + killed by stampede, 86 + + Burials, 66, 86; + in �henges,� 164; + in urns, 168 + + Burins, 75 + + Burma, 90 + + Byblos, 134 + + + Camel, 54 + + Cannibalism, 55 + + Cattle, wild, 85, 112; + in cave art, 85; + domesticated, 15; + at Skara Brae, 142 + + Caucasoids, 34 + + Cave men, 29 + + Caves, 62; + art in, 84 + + Celts, 170 + + Chariot, 160 + + Chicken, domestication of, 107 + + Chiefs, in food-gathering groups, 68 + + Childe, V. Gordon, 8 + + China, 136 + + Choukoutien, 28, 35 + + Choukoutienian, 47 + + Civilization, beginnings, 144, 149, 157; + meaning of, 144 + + Clactonian, 45, 47 + + Clay, used in modeling, 128; + baked, used for tools, 153 + + Club-heads, 82, 94 + + Colonization, in America, 142; + in Europe, 142 + + Combe Capelle, 30 + + Combe Capelle-Br�nn group, 34 + + Commont, Victor, 51 + + Coon, Carlton S., 73 + + Copper, 134 + + Corn, in America, 145 + + Corrals for cattle, 140 + + �Cradle of mankind,� 136 + + Cremation, 167 + + Crete, 162 + + Cro-Magnon, 30, 34 + + Cultivation, incipient, 105, 109, 111 + + Culture, change, 99; + characteristics, defined, 38, 49; + prehistoric, 39 + + + Danube Valley, used as route from Asia, 138 + + Dates, 153 + + Deer, 54, 96 + + Dog, domesticated, 96 + + Domestication, of animals, 100, 105, 107; + of plants, 100 + + �Dragon teeth� fossils in China, 28 + + Drill, 77 + + Dubois, Eugene, 26 + + + Early Dynastic Period, Mesopotamia, 147 + + East Spanish art, 72, 85 + + Egypt, 70, 126 + + Ehringsdorf, 31 + + Elephant, 54 + + Emiliani, Cesare, 18 + + Emiran flake point, 73 + + England, 163-168; + prehistoric, 19, 40; + farmers in, 140 + + Eoanthropus dawsoni, 29 + + Eoliths, 41 + + Erich, 152 + + Eridu, 152 + + Euphrates River, floods in, 148 + + Europe, cave dwellings, 58; + at end of Ice Age, 93; + early farmers, 140; + glaciers in, 40; + huts in, 86; + routes into, 137-140; + spread of food-production to, 136 + + + Far East, 69, 90 + + Farmers, 103 + + Fauresmith industry, 67 + + Fayum, 135; + radiocarbon date, 146 + + �Fertile Crescent,� 107, 146 + + Figurines, �Venus,� 84; + at Jarmo, 128; + at Ubaid, 153 + + Fire, used by Peking man, 54 + + First Dynasty, Egypt, 147 + + Fish-hooks, 80, 94 + + Fishing, 80; + by food-producers, 122 + + Fish-lines, 80 + + Fish spears, 94 + + Flint industry, 127 + + Font�chevade, 32, 56, 58 + + Food-collecting, 104, 121; + end of, 104 + + Food-gatherers, 53, 176 + + Food-gathering, 99, 104; + in Old World, 104; + stages of, 104 + + Food-producers, 176 + + Food-producing economy, 122; + in America, 145; + in Asia, 105 + + Food-producing revolution, 99, 105; + causes of, 101; + preconditions for, 100 + + Food-production, beginnings of, 99; + carried to Europe, 110 + + Food-vessel folk, 164 + + �Forest folk,� 97, 98, 104, 110 + + Fox, Sir Cyril, 174 + + France, caves in, 56 + + + Galley Hill (fossil type), 29 + + Garrod, D. A., 73 + + Gazelle, 114 + + Germany, 94 + + Ghassul, 156 + + Glaciers, 18, 30; + destruction by, 40 + + Goat, wild, 108; + domesticated, 128 + + Grain, first planted, 20 + + Graves, passage, 141; + gallery, 141 + + Greece, civilization in, 163; + as route to western Europe, 138; + towns in, 162 + + Grimaldi skeletons, 34 + + + Hackberry seeds used as food, 55 + + Halaf, 151; + assemblage, 151 + + Hallstatt, tradition, 169 + + Hand, development of, 24, 25 + + Hand adzes, 46 + + Hand axes, 44 + + Harpoons, antler, 83, 94; + bone, 82, 94 + + Hassuna, 131; + assemblage, 131, 132 + + Heidelberg, fossil type, 28 + + Hill-forts, in England, 171; + in Scotland, 172 + + Hilly flanks of Near East, 107, 108, 125, 131, 146, 147 + + History, beginning of, 7, 17 + + Hoes, 112 + + Holland, 164 + + Homo sapiens, 32 + + Hooton, E. A., 34 + + Horse, 112; + wild, in cave art, 85; + in China, 54 + + Hotu cave, 126 + + Houses, 122; + at Jarmo, 128; + at Halaf, 151 + + Howe, Bruce, 116 + + Howell, F. Clark, 30 + + Hunting, 93 + + + Ice Age, in Asia, 99; + beginning of, 18; + glaciers in, 41; + last glaciation, 93 + + Incas, 145 + + India, 90, 136 + + Industrialization, 178 + + Industry, blade-tool, 88; + defined, 58; + ground stone, 94 + + Internationalism, 162 + + Iran, 107, 147 + + Iraq, 107, 124, 127, 136, 147 + + Iron, introduction of, 170 + + Irrigation, 123, 149, 155 + + Italy, 138 + + + Jacobsen, T. J., 157 + + Jarmo, 109, 126, 128, 130; + assemblage, 129 + + Java, 23, 29 + + Java man, 26, 27, 29 + + Jefferson, Thomas, 11 + + Jericho, 119, 133 + + Judaidah, 134 + + + Kafuan, 48 + + Kanam, 23, 36 + + Karim Shahir, 116-119, 124; + assemblage, 116, 117 + + Keith, Sir Arthur, 33 + + Kelley, Harper, 51 + + Kharga, 126 + + Khartoum, 136 + + Knives, 80 + + Krogman, W. M., 3, 25 + + + Lamps, 85 + + Land bridges in Mediterranean, 19 + + La T�ne phase, 170 + + Laurel leaf point, 78, 89 + + Leakey, L. S. B., 40 + + Le Moustier, 57 + + Levalloisian, 47, 61, 62 + + Levalloiso-Mousterian, 47, 63 + + Little Woodbury, 170 + + + Magic, used by hunters, 123 + + Maglemosian, assemblage, 94, 95; + folk, 98 + + Makapan, 40 + + Mammoth, 93; + in cave art, 85 + + �Man-apes,� 26 + + Mango, 107 + + Mankind, age, 17 + + Maringer, J., 45 + + Markets, 155 + + Marston, A. T., 11 + + Mathiassen, T., 97 + + McCown, T. D., 33 + + Meganthropus, 26, 27, 36 + + Men, defined, 25; + modern, 32 + + Merimde, 135 + + Mersin, 133 + + Metal-workers, 160, 163, 167, 172 + + Micoquian, 48, 60 + + Microliths, 87; + at Jarmo, 130; + �lunates,� 87; + trapezoids, 87; + triangles, 87 + + Minerals used as coloring matter, 66 + + Mine-shafts, 140 + + M�lefaat, 126, 127 + + Mongoloids, 29, 90 + + Mortars, 114, 118, 127 + + Mounds, how formed, 12 + + Mount Carmel, 11, 33, 52, 59, 64, 69, 113, 114 + + �Mousterian man,� 64 + + �Mousterian� tools, 61, 62; + of Acheulean tradition, 62 + + Movius, H. L., 47 + + + Natufian, animals in, 114; + assemblage, 113, 114, 115; + burials, 114; + date of, 113 + + Neanderthal man, 29, 30, 31, 56 + + Near East, beginnings of civilization in, 20, 144; + cave sites, 58; + climate in Ice Age, 99; + �Fertile Crescent,� 107, 146; + food-production in, 99; + Natufian assemblage in, 113-115; + stone tools, 114 + + Needles, 80 + + Negroid, 34 + + New World, 90 + + Nile River valley, 102, 134; + floods in, 148 + + Nuclear area, 106, 110; + in Near East, 107 + + + Obsidian, used for blade tools, 71; + at Jarmo, 130 + + Ochre, red, with burials, 86 + + Oldowan, 48 + + Old World, 67, 70, 90; + continental phases in, 18 + + Olorgesailie, 40, 51 + + Ostrich, in China, 54 + + Ovens, 128 + + Oxygen isotopes, 18 + + + Paintings in caves, 83 + + Paleoanthropic man, 50 + + Palestine, burials, 56; + cave sites, 52; + types of man, 69 + + Parpallo, 89 + + Patjitanian, 45, 47 + + Pebble tools, 42 + + Peking cave, 54; + animals in, 54 + + Peking man, 27, 28, 29, 54, 58 + + Pendants, 80; + bone, 114 + + Pestle, 114 + + Peterborough, 141; + assemblage, 141 + + Pictographic signs, 158 + + Pig, wild, 108 + + �Piltdown man,� 29 + + Pins, 80 + + Pithecanthropus, 26, 27, 30, 36 + + Pleistocene, 18, 25 + + Plows developed, 123 + + Points, arrow, 76; + laurel leaf, 78; + shouldered, 78, 79; + split-based bone, 80, 82; + tanged, 76; + willow leaf, 78 + + Potatoes, in America, 145 + + Pottery, 122, 130, 156; + decorated, 142; + painted, 131, 151, 152; + Susa style, 156; + in tombs, 141 + + Prehistory, defined, 7; + range of, 18 + + Pre-neanderthaloids, 30, 31, 37 + + Pre-Solutrean point, 89 + + Pre-Stellenbosch, 48 + + Proto-Literate assemblage, 157-160 + + + Race, 35; + biological, 36; + �pure,� 16 + + Radioactivity, 9, 10 + + Radioactive carbon dates, 18, 92, 120, 130, 135, 156 + + Redfield, Robert, 38, 49 + + Reed, C. A., 128 + + Reindeer, 94 + + Rhinoceros, 93; + in cave art, 85 + + Rhodesian man, 32 + + Riss glaciation, 58 + + Rock-shelters, 58; + art in, 85 + + + Saccopastore, 31 + + Sahara Desert, 34, 102 + + Samarra, 152; + pottery, 131, 152 + + Sangoan industry, 67 + + Sauer, Carl, 136 + + Sbaikian point, 89 + + Schliemann, H., 11, 12 + + Scotland, 171 + + Scraper, flake, 79; + end-scraper on blade, 77, 78; + keel-shaped, 79, 80, 81 + + Sculpture in caves, 83 + + Sebilian III, 126 + + Shaheinab, 135 + + Sheep, wild, 108; + at Skara Brae, 142; + in China, 54 + + Shellfish, 142 + + Ship, Ubaidian, 153 + + Sialk, 126, 134; + assemblage, 134 + + Siberia, 88; + pathway to New World, 98 + + Sickle, 112, 153; + blade, 113, 130 + + Silo, 122 + + Sinanthropus, 27, 30, 35 + + Skara Brae, 142 + + Snails used as food, 128 + + Soan, 47 + + Solecki, R., 116 + + Solo (fossil type), 29, 32 + + Solutrean industry, 77 + + Spear, shaft, 78; + thrower, 82, 83 + + Speech, development of organs of, 25 + + Squash, in America, 145 + + Steinheim fossil skull, 28 + + Stillbay industry, 67 + + Stonehenge, 166 + + Stratification, in caves, 12, 57; + in sites, 12 + + Swanscombe (fossil type), 11, 28 + + Syria, 107 + + + Tabun, 60, 71 + + Tardenoisian, 97 + + Taro, 107 + + Tasa, 135 + + Tayacian, 47, 59 + + Teeth, pierced, in beads and pendants, 114 + + Temples, 123, 155 + + Tepe Gawra, 156 + + Ternafine, 29 + + Teshik Tash, 69 + + Textiles, 122 + + Thong-stropper, 80 + + Tigris River, floods in, 148 + + Toggle, 80 + + Tomatoes, in America, 145 + + Tombs, megalithic, 141 + + Tool-making, 42, 49 + + Tool-preparation traditions, 65 + + Tools, 62; + antler, 80; + blade, 70, 71, 75; + bone, 66; + chopper, 47; + core-biface, 43, 48, 60, 61; + flake, 44, 47, 51, 60, 64; + flint, 80, 127; + ground stone, 68, 127; + handles, 94; + pebble, 42, 43, 48, 53; + use of, 24 + + Touf (mud wall), 128 + + Toynbee, A. J., 101 + + Trade, 130, 155, 162 + + Traders, 167 + + Traditions, 15; + blade tool, 70; + definition of, 51; + interpretation of, 49; + tool-making, 42, 48; + chopper-tool, 47; + chopper-chopping tool, 45; + core-biface, 43, 48; + flake, 44, 47; + pebble tool, 42, 48 + + Tool-making, prehistory of, 42 + + Turkey, 107, 108 + + + Ubaid, 153; + assemblage, 153-155 + + Urnfields, 168, 169 + + + Village-farming community era, 105, 119 + + + Wad B, 72 + + Wadjak, 34 + + Warka phase, 156; + assemblage, 156 + + Washburn, Sherwood L., 36 + + Water buffalo, domestication of, 107 + + Weidenreich, F., 29, 34 + + Wessex, 166, 167 + + Wheat, wild, 108; + partially domesticated, 127 + + Willow leaf point, 78 + + Windmill Hill, 138; + assemblage, 138, 140 + + Witch doctors, 68 + + Wool, 112; + in garments, 167 + + Writing, 158; + cuneiform, 158 + + W�rm I glaciation, 58 + + + Zebu cattle, domestication of, 107 + + Zeuner, F. E., 73 + + + + + * * * * * * + + + + +Transcriber�s note: + +Punctuation, hyphenation, and spelling were made consistent when a +predominant preference was found in this book; otherwise they were not +changed. + +Simple typographical errors were corrected; occasional unbalanced +quotation marks retained. + +Ambiguous hyphens at the ends of lines were retained. + +Index not checked for proper alphabetization or correct page references. + +In the original book, chapter headings were accompanied by +illustrations, sometimes above, sometimes below, and sometimes +adjacent. In this eBook those ilustrations always appear below the +headings. + + + +***END OF THE PROJECT GUTENBERG EBOOK PREHISTORIC MEN*** + + +******* This file should be named 52664-0.txt or 52664-0.zip ******* + + +This and all associated files of various formats will be found in: +http://www.gutenberg.org/dirs/5/2/6/6/52664 + + +Updated editions will replace the previous one--the old editions will +be renamed. + +Creating the works from print editions not protected by U.S. copyright +law means that no one owns a United States copyright in these works, +so the Foundation (and you!) can copy and distribute it in the United +States without permission and without paying copyright +royalties. Special rules, set forth in the General Terms of Use part +of this license, apply to copying and distributing Project +Gutenberg-tm electronic works to protect the PROJECT GUTENBERG-tm +concept and trademark. Project Gutenberg is a registered trademark, +and may not be used if you charge for the eBooks, unless you receive +specific permission. If you do not charge anything for copies of this +eBook, complying with the rules is very easy. You may use this eBook +for nearly any purpose such as creation of derivative works, reports, +performances and research. They may be modified and printed and given +away--you may do practically ANYTHING in the United States with eBooks +not protected by U.S. copyright law. Redistribution is subject to the +trademark license, especially commercial redistribution. + +START: FULL LICENSE + +THE FULL PROJECT GUTENBERG LICENSE +PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK + +To protect the Project Gutenberg-tm mission of promoting the free +distribution of electronic works, by using or distributing this work +(or any other work associated in any way with the phrase "Project +Gutenberg"), you agree to comply with all the terms of the Full +Project Gutenberg-tm License available with this file or online at +www.gutenberg.org/license. + +Section 1. General Terms of Use and Redistributing Project +Gutenberg-tm electronic works + +1.A. By reading or using any part of this Project Gutenberg-tm +electronic work, you indicate that you have read, understand, agree to +and accept all the terms of this license and intellectual property +(trademark/copyright) agreement. If you do not agree to abide by all +the terms of this agreement, you must cease using and return or +destroy all copies of Project Gutenberg-tm electronic works in your +possession. If you paid a fee for obtaining a copy of or access to a +Project Gutenberg-tm electronic work and you do not agree to be bound +by the terms of this agreement, you may obtain a refund from the +person or entity to whom you paid the fee as set forth in paragraph +1.E.8. + +1.B. "Project Gutenberg" is a registered trademark. It may only be +used on or associated in any way with an electronic work by people who +agree to be bound by the terms of this agreement. There are a few +things that you can do with most Project Gutenberg-tm electronic works +even without complying with the full terms of this agreement. See +paragraph 1.C below. There are a lot of things you can do with Project +Gutenberg-tm electronic works if you follow the terms of this +agreement and help preserve free future access to Project Gutenberg-tm +electronic works. See paragraph 1.E below. + +1.C. The Project Gutenberg Literary Archive Foundation ("the +Foundation" or PGLAF), owns a compilation copyright in the collection +of Project Gutenberg-tm electronic works. Nearly all the individual +works in the collection are in the public domain in the United +States. If an individual work is unprotected by copyright law in the +United States and you are located in the United States, we do not +claim a right to prevent you from copying, distributing, performing, +displaying or creating derivative works based on the work as long as +all references to Project Gutenberg are removed. Of course, we hope +that you will support the Project Gutenberg-tm mission of promoting +free access to electronic works by freely sharing Project Gutenberg-tm +works in compliance with the terms of this agreement for keeping the +Project Gutenberg-tm name associated with the work. You can easily +comply with the terms of this agreement by keeping this work in the +same format with its attached full Project Gutenberg-tm License when +you share it without charge with others. + +1.D. The copyright laws of the place where you are located also govern +what you can do with this work. Copyright laws in most countries are +in a constant state of change. If you are outside the United States, +check the laws of your country in addition to the terms of this +agreement before downloading, copying, displaying, performing, +distributing or creating derivative works based on this work or any +other Project Gutenberg-tm work. The Foundation makes no +representations concerning the copyright status of any work in any +country outside the United States. + +1.E. Unless you have removed all references to Project Gutenberg: + +1.E.1. The following sentence, with active links to, or other +immediate access to, the full Project Gutenberg-tm License must appear +prominently whenever any copy of a Project Gutenberg-tm work (any work +on which the phrase "Project Gutenberg" appears, or with which the +phrase "Project Gutenberg" is associated) is accessed, displayed, +performed, viewed, copied or distributed: + + 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 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 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. + +1.E.2. If an individual Project Gutenberg-tm electronic work is +derived from texts not protected by U.S. copyright law (does not +contain a notice indicating that it is posted with permission of the +copyright holder), the work can be copied and distributed to anyone in +the United States without paying any fees or charges. If you are +redistributing or providing access to a work with the phrase "Project +Gutenberg" associated with or appearing on the work, you must comply +either with the requirements of paragraphs 1.E.1 through 1.E.7 or +obtain permission for the use of the work and the Project Gutenberg-tm +trademark as set forth in paragraphs 1.E.8 or 1.E.9. + +1.E.3. If an individual Project Gutenberg-tm electronic work is posted +with the permission of the copyright holder, your use and distribution +must comply with both paragraphs 1.E.1 through 1.E.7 and any +additional terms imposed by the copyright holder. Additional terms +will be linked to the Project Gutenberg-tm License for all works +posted with the permission of the copyright holder found at the +beginning of this work. + +1.E.4. Do not unlink or detach or remove the full Project Gutenberg-tm +License terms from this work, or any files containing a part of this +work or any other work associated with Project Gutenberg-tm. + +1.E.5. Do not copy, display, perform, distribute or redistribute this +electronic work, or any part of this electronic work, without +prominently displaying the sentence set forth in paragraph 1.E.1 with +active links or immediate access to the full terms of the Project +Gutenberg-tm License. + +1.E.6. You may convert to and distribute this work in any binary, +compressed, marked up, nonproprietary or proprietary form, including +any word processing or hypertext form. However, if you provide access +to or distribute copies of a Project Gutenberg-tm work in a format +other than "Plain Vanilla ASCII" or other format used in the official +version posted on the official Project Gutenberg-tm web site +(www.gutenberg.org), you must, at no additional cost, fee or expense +to the user, provide a copy, a means of exporting a copy, or a means +of obtaining a copy upon request, of the work in its original "Plain +Vanilla ASCII" or other form. Any alternate format must include the +full Project Gutenberg-tm License as specified in paragraph 1.E.1. + +1.E.7. Do not charge a fee for access to, viewing, displaying, +performing, copying or distributing any Project Gutenberg-tm works +unless you comply with paragraph 1.E.8 or 1.E.9. + +1.E.8. You may charge a reasonable fee for copies of or providing +access to or distributing Project Gutenberg-tm electronic works +provided that + +* You pay a royalty fee of 20% of the gross profits you derive from + the use of Project Gutenberg-tm works calculated using the method + you already use to calculate your applicable taxes. The fee is owed + to the owner of the Project Gutenberg-tm trademark, but he has + agreed to donate royalties under this paragraph to the Project + Gutenberg Literary Archive Foundation. Royalty payments must be paid + within 60 days following each date on which you prepare (or are + legally required to prepare) your periodic tax returns. Royalty + payments should be clearly marked as such and sent to the Project + Gutenberg Literary Archive Foundation at the address specified in + Section 4, "Information about donations to the Project Gutenberg + Literary Archive Foundation." + +* You provide a full refund of any money paid by a user who notifies + you in writing (or by e-mail) within 30 days of receipt that s/he + does not agree to the terms of the full Project Gutenberg-tm + License. You must require such a user to return or destroy all + copies of the works possessed in a physical medium and discontinue + all use of and all access to other copies of Project Gutenberg-tm + works. + +* You provide, in accordance with paragraph 1.F.3, a full refund of + any money paid for a work or a replacement copy, if a defect in the + electronic work is discovered and reported to you within 90 days of + receipt of the work. + +* You comply with all other terms of this agreement for free + distribution of Project Gutenberg-tm works. + +1.E.9. If you wish to charge a fee or distribute a Project +Gutenberg-tm electronic work or group of works on different terms than +are set forth in this agreement, you must obtain permission in writing +from both the Project Gutenberg Literary Archive Foundation and The +Project Gutenberg Trademark LLC, the owner of the Project Gutenberg-tm +trademark. Contact the Foundation as set forth in Section 3 below. + +1.F. + +1.F.1. Project Gutenberg volunteers and employees expend considerable +effort to identify, do copyright research on, transcribe and proofread +works not protected by U.S. copyright law in creating the Project +Gutenberg-tm collection. Despite these efforts, Project Gutenberg-tm +electronic works, and the medium on which they may be stored, may +contain "Defects," such as, but not limited to, incomplete, inaccurate +or corrupt data, transcription errors, a copyright or other +intellectual property infringement, a defective or damaged disk or +other medium, a computer virus, or computer codes that damage or +cannot be read by your equipment. + +1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right +of Replacement or Refund" described in paragraph 1.F.3, the Project +Gutenberg Literary Archive Foundation, the owner of the Project +Gutenberg-tm trademark, and any other party distributing a Project +Gutenberg-tm electronic work under this agreement, disclaim all +liability to you for damages, costs and expenses, including legal +fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT +LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE +PROVIDED IN PARAGRAPH 1.F.3. YOU AGREE THAT THE FOUNDATION, THE +TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE +LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR +INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH +DAMAGE. + +1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a +defect in this electronic work within 90 days of receiving it, you can +receive a refund of the money (if any) you paid for it by sending a +written explanation to the person you received the work from. If you +received the work on a physical medium, you must return the medium +with your written explanation. The person or entity that provided you +with the defective work may elect to provide a replacement copy in +lieu of a refund. If you received the work electronically, the person +or entity providing it to you may choose to give you a second +opportunity to receive the work electronically in lieu of a refund. If +the second copy is also defective, you may demand a refund in writing +without further opportunities to fix the problem. + +1.F.4. Except for the limited right of replacement or refund set forth +in paragraph 1.F.3, this work is provided to you 'AS-IS', WITH NO +OTHER WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PURPOSE. + +1.F.5. Some states do not allow disclaimers of certain implied +warranties or the exclusion or limitation of certain types of +damages. If any disclaimer or limitation set forth in this agreement +violates the law of the state applicable to this agreement, the +agreement shall be interpreted to make the maximum disclaimer or +limitation permitted by the applicable state law. The invalidity or +unenforceability of any provision of this agreement shall not void the +remaining provisions. + +1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the +trademark owner, any agent or employee of the Foundation, anyone +providing copies of Project Gutenberg-tm electronic works in +accordance with this agreement, and any volunteers associated with the +production, promotion and distribution of Project Gutenberg-tm +electronic works, harmless from all liability, costs and expenses, +including legal fees, that arise directly or indirectly from any of +the following which you do or cause to occur: (a) distribution of this +or any Project Gutenberg-tm work, (b) alteration, modification, or +additions or deletions to any Project Gutenberg-tm work, and (c) any +Defect you cause. + +Section 2. Information about the Mission of Project Gutenberg-tm + +Project Gutenberg-tm is synonymous with the free distribution of +electronic works in formats readable by the widest variety of +computers including obsolete, old, middle-aged and new computers. It +exists because of the efforts of hundreds of volunteers and donations +from people in all walks of life. + +Volunteers and financial support to provide volunteers with the +assistance they need are critical to reaching Project Gutenberg-tm's +goals and ensuring that the Project Gutenberg-tm collection will +remain freely available for generations to come. In 2001, the Project +Gutenberg Literary Archive Foundation was created to provide a secure +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 + +Section 3. Information about the Project Gutenberg Literary +Archive Foundation + +The Project Gutenberg Literary Archive Foundation is a non profit +501(c)(3) educational corporation organized under the laws of the +state of Mississippi and granted tax exempt status by the Internal +Revenue Service. The Foundation's EIN or federal tax identification +number is 64-6221541. Contributions to the Project Gutenberg Literary +Archive Foundation are tax deductible to the full extent permitted by +U.S. federal laws and your state's laws. + +The Foundation's principal office is in Fairbanks, Alaska, with the +mailing address: PO Box 750175, Fairbanks, AK 99775, but its +volunteers and employees are scattered throughout numerous +locations. Its business office is located at 809 North 1500 West, Salt +Lake City, UT 84116, (801) 596-1887. Email contact links and up to +date contact information can be found at the Foundation's web site and +official page at www.gutenberg.org/contact + +For additional contact information: + + Dr. Gregory B. Newby + Chief Executive and Director + gbnewby@pglaf.org + +Section 4. Information about Donations to the Project Gutenberg +Literary Archive Foundation + +Project Gutenberg-tm depends upon and cannot survive without wide +spread public support and donations to carry out its mission of +increasing the number of public domain and licensed works that can be +freely distributed in machine readable form accessible by the widest +array of equipment including outdated equipment. Many small donations +($1 to $5,000) are particularly important to maintaining tax exempt +status with the IRS. + +The Foundation is committed to complying with the laws regulating +charities and charitable donations in all 50 states of the United +States. Compliance requirements are not uniform and it takes a +considerable effort, much paperwork and many fees to meet and keep up +with these requirements. We do not solicit donations in locations +where we have not received written confirmation of compliance. To SEND +DONATIONS or determine the status of compliance for any particular +state visit www.gutenberg.org/donate + +While we cannot and do not solicit contributions from states where we +have not met the solicitation requirements, we know of no prohibition +against accepting unsolicited donations from donors in such states who +approach us with offers to donate. + +International donations are gratefully accepted, but we cannot make +any statements concerning tax treatment of donations received from +outside the United States. U.S. laws alone swamp our small staff. + +Please check the Project Gutenberg Web pages for current donation +methods and addresses. Donations are accepted in a number of other +ways including checks, online payments and credit card donations. To +donate, please visit: www.gutenberg.org/donate + +Section 5. General Information About Project Gutenberg-tm electronic works. + +Professor Michael S. Hart was the originator of the Project +Gutenberg-tm concept of a library of electronic works that could be +freely shared with anyone. For forty years, he produced and +distributed Project Gutenberg-tm eBooks with only a loose network of +volunteer support. + +Project Gutenberg-tm eBooks are often created from several printed +editions, all of which are confirmed as not protected by copyright in +the U.S. unless a copyright notice is included. Thus, we do not +necessarily keep eBooks in compliance with any particular paper +edition. + +Most people start at our Web site which has the main PG search +facility: www.gutenberg.org + +This Web site includes information about Project Gutenberg-tm, +including how to make donations to the Project Gutenberg Literary +Archive Foundation, how to help produce our new eBooks, and how to +subscribe to our email newsletter to hear about new eBooks. diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py new file mode 100644 index 000000000000..5c3eaaca6e27 --- /dev/null +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -0,0 +1,36 @@ +from __future__ import print_function +import time, os, sys +import transposition_cipher as transCipher + +def main(): + inputFile = 'Prehistoric Men.txt' + outputFile = 'Output.txt' + key = int(raw_input('Enter key: ')) + mode = raw_input('Encrypt/Decrypt [e/d]: ') + + if not os.path.exists(inputFile): + print('File %s does not exist. Quitting...' % inputFile) + sys.exit() + if os.path.exists(outputFile): + print('Overwrite %s? [y/n]' % outputFile) + response = raw_input('> ') + if not response.lower().startswith('y'): + sys.exit() + + startTime = time.time() + if mode.lower().startswith('e'): + content = open(inputFile).read() + translated = transCipher.encryptMessage(key, content) + elif mode.lower().startswith('d'): + content = open(outputFile).read() + translated =transCipher .decryptMessage(key, content) + + outputObj = open(outputFile, 'w') + outputObj.write(translated) + outputObj.close() + + totalTime = round(time.time() - startTime, 2) + print(('Done (', totalTime, 'seconds )')) + +if __name__ == '__main__': + main() diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py new file mode 100644 index 000000000000..727fac3b0703 --- /dev/null +++ b/ciphers/xor_cipher.py @@ -0,0 +1,209 @@ +""" + 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 + """ + + #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 + """ + + # precondition + assert (isinstance(key,int) and isinstance(content,str)) + + key = key or self.__key or 1 + + # make sure key can be any size + while (key > 255): + key -= 255 + + # This will be returned + ans = [] + + for ch in content: + ans.append(chr(ord(ch) ^ key)) + + return ans + + 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)) + + key = key or self.__key or 1 + + # make sure key can be any size + while (key > 255): + key -= 255 + + # This will be returned + ans = [] + + for ch in content: + ans.append(chr(ord(ch) ^ key)) + + return ans + + + 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)) + + key = key or self.__key or 1 + + # make sure key can be any size + while (key > 255): + key -= 255 + + # This will be returned + ans = "" + + for ch in content: + ans += chr(ord(ch) ^ key) + + return ans + + 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 + + # make sure key can be any size + while (key > 255): + key -= 255 + + # This will be returned + ans = "" + + for ch in content: + ans += chr(ord(ch) ^ key) + + return ans + + + 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 + """ + + #precondition + assert (isinstance(file,str) and isinstance(key,int)) + + try: + with open(file,"r") as fin: + with open("encrypt.out","w+") as fout: + + # actual encrypt-process + for line in fin: + fout.write(self.encrypt_string(line,key)) + + except: + return False + + return True + + + 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 + """ + + #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)) + + except: + return False + + return True + + + + +# Tests +# crypt = XORCipher() +# key = 67 + +# # test enrcypt +# print crypt.encrypt("hallo welt",key) +# # test decrypt +# print crypt.decrypt(crypt.encrypt("hallo welt",key), key) + +# # test encrypt_string +# print crypt.encrypt_string("hallo welt",key) + +# # test decrypt_string +# print crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key) + +# if (crypt.encrypt_file("test.txt",key)): +# print "encrypt successful" +# else: +# print "encrypt unsuccessful" + +# if (crypt.decrypt_file("encrypt.out",key)): +# print "decrypt successful" +# else: +# print "decrypt unsuccessful" \ No newline at end of file diff --git a/data_structures/arrays.py b/data_structures/arrays.py new file mode 100644 index 000000000000..3ec9f8976673 --- /dev/null +++ b/data_structures/arrays.py @@ -0,0 +1,3 @@ +arr = [10, 20, 30, 40] +arr[1] = 30 +print(arr) diff --git a/data_structures/avl.py b/data_structures/avl.py new file mode 100644 index 000000000000..d01e8f825368 --- /dev/null +++ b/data_structures/avl.py @@ -0,0 +1,181 @@ +""" +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/binary tree/FenwickTree.py b/data_structures/binary tree/FenwickTree.py new file mode 100644 index 000000000000..f429161c8c36 --- /dev/null +++ b/data_structures/binary tree/FenwickTree.py @@ -0,0 +1,29 @@ +from __future__ import print_function +class FenwickTree: + + def __init__(self, SIZE): # create fenwick tree with size SIZE + self.Size = 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): + self.ft[i] += val + i += i & (-i) + + def query(self, i): # query cumulative data from index 0 to i in O(lg N) + ret = 0 + while (i > 0): + 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)) + f.update(2,-5) + print (f.query(1)) + print (f.query(3)) diff --git a/data_structures/binary tree/LazySegmentTree.py b/data_structures/binary tree/LazySegmentTree.py new file mode 100644 index 000000000000..9b14b24e81fa --- /dev/null +++ b/data_structures/binary tree/LazySegmentTree.py @@ -0,0 +1,91 @@ +from __future__ import print_function +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 + + def right(self, idx): + 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)]) + + # 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: + self.st[idx] = self.lazy[idx] + self.flag[idx] = False + 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 + self.flag[self.right(idx)] = True + + if r < a or l > b: + return True + if l >= a and r <= b : + self.st[idx] = val + 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)]) + 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: + self.st[idx] = self.lazy[idx] + self.flag[idx] = False + 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 + self.flag[self.right(idx)] = True + if r < a or l > 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) + + def showData(self): + showList = [] + 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] + 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.showData() diff --git a/data_structures/binary tree/SegmentTree.py b/data_structures/binary tree/SegmentTree.py new file mode 100644 index 000000000000..001bf999f391 --- /dev/null +++ b/data_structures/binary tree/SegmentTree.py @@ -0,0 +1,71 @@ +from __future__ import print_function +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 + + def right(self, idx): + return idx * 2 + 1 + + def build(self, idx, l, r): + if l == r: + self.st[idx] = A[l] + else: + 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)]) + + 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 + if l == r : + self.st[idx] = val + return True + 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)]) + 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] + if r < a or l > b: + return -math.inf + if l >= a and r <= b: + return self.st[idx] + 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): + 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] + 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) + print (segt.query(1, 15)) + segt.update(7,8,235) + segt.showData() diff --git a/data_structures/binary tree/binary_search_tree.py b/data_structures/binary tree/binary_search_tree.py new file mode 100644 index 000000000000..b4021e4f861f --- /dev/null +++ b/data_structures/binary tree/binary_search_tree.py @@ -0,0 +1,258 @@ +''' +A binary search Tree +''' +from __future__ import print_function +class Node: + + def __init__(self, label, parent): + self.label = label + 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 + +class BinarySearchTree: + + def __init__(self): + self.root = None + + 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) + else: + parent_node.setRight(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 + 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 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() + 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 + else: + #We go deep on the left branch + curr_node = self.getRoot() + 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) + 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) + 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 = [] + if curr_node is not None: + nodeList = nodeList + InPreOrder(curr_node.getLeft()) + nodeList.insert(0, curr_node.getLabel()) + nodeList = nodeList + InPreOrder(curr_node.getRight()) + return nodeList + +def testBinarySearchTree(): + ''' + Example + 8 + / \ + 3 10 + / \ \ + 1 6 14 + / \ / + 4 7 13 + ''' + + ''' + Example After Deletion + 7 + / \ + 1 4 + + ''' + 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) + + #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") + else: + print("The label 6 doesn't exist") + + if(t.getNode(-1) is not None): + 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) + t.delete(3) + t.delete(6) + t.delete(14) + + #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/graph/bellman_ford.py b/data_structures/graph/bellman_ford.py new file mode 100644 index 000000000000..f5e1ac9836cb --- /dev/null +++ b/data_structures/graph/bellman_ford.py @@ -0,0 +1,54 @@ +from __future__ import print_function + +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 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"] + + 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"] + + if mdist[u] != float('inf') and mdist[u] + w < mdist[v]: + print("Negative cycle found. Solution not possible.") + return + + printDist(mdist, V) + + + +#MAIN +V = int(raw_input("Enter number of vertices: ")) +E = int(raw_input("Enter number of edges: ")) + +graph = [dict() for j in range(E)] + +for i in range(V): + graph[i][i] = 0.0 + +for i in range(E): + print("\nEdge ",i+1) + src = int(raw_input("Enter source:")) + dst = int(raw_input("Enter destination:")) + weight = float(raw_input("Enter weight:")) + graph[i] = {"src": src,"dst": dst, "weight": weight} + +gsrc = int(raw_input("\nEnter shortest path source:")) +BellmanFord(graph, V, E, gsrc) diff --git a/data_structures/graph/breadth_first_search.py b/data_structures/graph/breadth_first_search.py new file mode 100644 index 000000000000..3992e2d4d892 --- /dev/null +++ b/data_structures/graph/breadth_first_search.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# encoding=utf8 + +""" Author: OMKAR PATHAK """ + +from __future__ import print_function + + +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]])) + + # for adding the edge beween two vertexes + 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 BFS(self, startVertex): + # Take a list for stoting already visited vertexes + visited = [False] * len(self.vertex) + + # create a list to store all the vertexes for BFS + queue = [] + + # mark the source node as visited and enqueue it + visited[startVertex] = True + queue.append(startVertex) + + while queue: + startVertex = queue.pop(0) + print(startVertex, end = ' ') + + # 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 + +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('BFS:') + g.BFS(2) + + # OUTPUT: + # 0  ->  1 -> 2 + # 1  ->  2 + # 2  ->  0 -> 3 + # 3  ->  3 + # BFS: + # 2 0 3 1 diff --git a/data_structures/graph/depth_first_search.py b/data_structures/graph/depth_first_search.py new file mode 100644 index 000000000000..98faf61354f9 --- /dev/null +++ b/data_structures/graph/depth_first_search.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# encoding=utf8 + +""" Author: OMKAR PATHAK """ +from __future__ import print_function + + +class Graph(): + def __init__(self): + self.vertex = {} + + # for printing the Graph vertexes + 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 + 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 vertexes 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/data_structures/graph/dijkstra.py b/data_structures/graph/dijkstra.py new file mode 100644 index 000000000000..2580dd2f00fc --- /dev/null +++ b/data_structures/graph/dijkstra.py @@ -0,0 +1,57 @@ +from __future__ import print_function + +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 + +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] + + + + printDist(mdist, V) + + + +#MAIN +V = int(raw_input("Enter number of vertices: ")) +E = int(raw_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 + +for i in range(E): + print("\nEdge ",i+1) + src = int(raw_input("Enter source:")) + dst = int(raw_input("Enter destination:")) + weight = float(raw_input("Enter weight:")) + graph[src][dst] = weight + +gsrc = int(raw_input("\nEnter shortest path source:")) +Dijkstra(graph, V, gsrc) diff --git a/data_structures/graph/dijkstra_algorithm.py b/data_structures/graph/dijkstra_algorithm.py new file mode 100644 index 000000000000..985c7f6c1301 --- /dev/null +++ b/data_structures/graph/dijkstra_algorithm.py @@ -0,0 +1,212 @@ +# Title: Dijkstra's Algorithm for finding single source shortest path from scratch +# 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 + + +class PriorityQueue: + # Based on Min Heap + def __init__(self): + self.cur_size = 0 + self.array = [] + self.pos = {} # To store the pos of node in array + + def isEmpty(self): + return self.cur_size == 0 + + def min_heapify(self, idx): + lc = self.left(idx) + rc = self.right(idx) + if lc < self.cur_size and self.array(lc)[0] < self.array(idx)[0]: + smallest = lc + else: + smallest = idx + if rc < self.cur_size and self.array(rc)[0] < self.array(smallest)[0]: + smallest = rc + if smallest != idx: + self.swap(idx, smallest) + self.min_heapify(smallest) + + def insert(self, tup): + # Inserts a node into the Priority Queue + self.pos[tup[1]] = self.cur_size + self.cur_size += 1 + self.array.append((sys.maxsize, tup[1])) + self.decrease_key((sys.maxsize, tup[1]), tup[0]) + + def extract_min(self): + # Removes and returns the min element at top of priority queue + min_node = self.array[0][1] + self.array[0] = self.array[self.cur_size - 1] + self.cur_size -= 1 + self.min_heapify(1) + del self.pos[min_node] + return min_node + + def left(self, i): + # returns the index of left child + return 2 * i + 1 + + def right(self, i): + # returns the index of right child + return 2 * i + 2 + + def par(self, i): + # returns the index of parent + return math.floor(i / 2) + + def swap(self, i, j): + # swaps array elements at indices i and j + # update the pos{} + self.pos[self.array[i][1]] = j + self.pos[self.array[j][1]] = i + temp = self.array[i] + self.array[i] = self.array[j] + self.array[j] = temp + + def decrease_key(self, tup, new_d): + idx = self.pos[tup[1]] + # assuming the new_d is atmost old_d + self.array[idx] = (new_d, tup[1]) + while idx > 0 and self.array[self.par(idx)][0] > self.array[idx][0]: + self.swap(idx, self.par(idx)) + idx = self.par(idx) + + +class Graph: + def __init__(self, num): + 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 + + def add_edge(self, u, v, w): + # Edge going from node u to v and v to u with weight w + # u (w)-> v, v (w) -> u + # Check if u already in graph + if u in self.adjList.keys(): + self.adjList[u].append((v, w)) + else: + self.adjList[u] = [(v, w)] + + # Assuming undirected graph + if v in self.adjList.keys(): + self.adjList[v].append((u, w)) + else: + self.adjList[v] = [(u, 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])) + + def dijkstra(self, src): + # Flush old junk values in par[] + self.par = [-1] * self.num_nodes + # src is the source node + self.dist[src] = 0 + Q = PriorityQueue() + Q.insert((0, src)) # (dist from src, node) + for u in self.adjList.keys(): + if u != src: + self.dist[u] = sys.maxsize # Infinity + self.par[u] = -1 + + while not Q.isEmpty(): + u = Q.extract_min() # Returns node with the min dist from source + # Update the distance of all the neighbours of u and + # if their prev dist was INFINITY then push them in Q + for v, w in self.adjList[u]: + new_dist = self.dist[u] + w + if self.dist[v] > new_dist: + if self.dist[v] == sys.maxsize: + Q.insert((new_dist, v)) + else: + Q.decrease_key((self.dist[v], v), new_dist) + self.dist[v] = new_dist + self.par[v] = u + + # Show the shortest distances from src + self.show_distances(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])) + + def show_path(self, src, dest): + # To show the shortest path from src to dest + # WARNING: Use it *after* calling dijkstra + path = [] + cost = 0 + temp = dest + # Backtracking from dest to src + while self.par[temp] != -1: + path.append(temp) + if temp != src: + for v, w in self.adjList[temp]: + if v == self.par[temp]: + cost += w + break + temp = self.par[temp] + path.append(src) + path.reverse() + + print('----Path to reach {} from {}----'.format(dest, src)) + for u in path: + print('{}'.format(u), end=' ') + if u != dest: + print('-> ', end='') + + print('\nTotal cost of path: ', cost) + + +if __name__ == '__main__': + graph = Graph(9) + graph.add_edge(0, 1, 4) + graph.add_edge(0, 7, 8) + graph.add_edge(1, 2, 8) + graph.add_edge(1, 7, 11) + graph.add_edge(2, 3, 7) + graph.add_edge(2, 8, 2) + graph.add_edge(2, 5, 4) + graph.add_edge(3, 4, 9) + graph.add_edge(3, 5, 14) + graph.add_edge(4, 5, 10) + graph.add_edge(5, 6, 2) + graph.add_edge(6, 7, 1) + graph.add_edge(6, 8, 6) + graph.add_edge(7, 8, 7) + graph.show_graph() + graph.dijkstra(0) + graph.show_path(0, 4) + +# OUTPUT +# 0 -> 1(4) -> 7(8) +# 1 -> 0(4) -> 2(8) -> 7(11) +# 7 -> 0(8) -> 1(11) -> 6(1) -> 8(7) +# 2 -> 1(8) -> 3(7) -> 8(2) -> 5(4) +# 3 -> 2(7) -> 4(9) -> 5(14) +# 8 -> 2(2) -> 6(6) -> 7(7) +# 5 -> 2(4) -> 3(14) -> 4(10) -> 6(2) +# 4 -> 3(9) -> 5(10) +# 6 -> 5(2) -> 7(1) -> 8(6) +# Distance from node: 0 +# Node 0 has distance: 0 +# Node 1 has distance: 4 +# Node 2 has distance: 12 +# Node 3 has distance: 19 +# Node 4 has distance: 21 +# Node 5 has distance: 11 +# Node 6 has distance: 9 +# Node 7 has distance: 8 +# Node 8 has distance: 14 +# ----Path to reach 4 from 0---- +# 0 -> 7 -> 6 -> 5 -> 4 +# Total cost of path: 21 diff --git a/data_structures/graph/even_tree.py b/data_structures/graph/even_tree.py new file mode 100644 index 000000000000..9383ea9a13c1 --- /dev/null +++ b/data_structures/graph/even_tree.py @@ -0,0 +1,70 @@ +""" +You are given a tree(a simple connected graph with no cycles). The tree has N +nodes numbered from 1 to N and is rooted at node 1. + +Find the maximum number of edges you can remove from the tree to get a forest +such that each connected component of the forest contains an even number of +nodes. + +Constraints +2 <= 2 <= 100 + +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 + + +def dfs(start): + """DFS traversal""" + # pylint: disable=redefined-outer-name + ret = 1 + visited[start] = True + for v in tree.get(start): + if v not in visited: + ret += dfs(v) + if ret % 2 == 0: + cuts.append(start) + return ret + + +def even_tree(): + """ + 2 1 + 3 1 + 4 3 + 5 2 + 6 1 + 7 2 + 8 6 + 9 8 + 10 8 + On removing edges (1,3) and (1,6), we can get the desired result 2. + """ + dfs(1) + + +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), + ] + for u, v in edges: + tree[u].append(v) + tree[v].append(u) + even_tree() + print(len(cuts) - 1) diff --git a/data_structures/graph/floyd_warshall.py b/data_structures/graph/floyd_warshall.py new file mode 100644 index 000000000000..64d41c2f88e1 --- /dev/null +++ b/data_structures/graph/floyd_warshall.py @@ -0,0 +1,48 @@ +from __future__ import print_function + +def printDist(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() + + + +def FloydWarshall(graph, V): + dist=[[float('inf') 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): + 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) + + + +#MAIN +V = int(raw_input("Enter number of vertices: ")) +E = int(raw_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 + +for i in range(E): + print("\nEdge ",i+1) + src = int(raw_input("Enter source:")) + dst = int(raw_input("Enter destination:")) + weight = float(raw_input("Enter weight:")) + graph[src][dst] = weight + +FloydWarshall(graph, V) diff --git a/data_structures/graph/graph.py b/data_structures/graph/graph.py new file mode 100644 index 000000000000..9bd61559dcbf --- /dev/null +++ b/data_structures/graph/graph.py @@ -0,0 +1,44 @@ +#!/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/data_structures/graph/graph_list.py b/data_structures/graph/graph_list.py new file mode 100644 index 000000000000..d67bc96c4a81 --- /dev/null +++ b/data_structures/graph/graph_list.py @@ -0,0 +1,31 @@ +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() + diff --git a/data_structures/graph/graph_matrix.py b/data_structures/graph/graph_matrix.py new file mode 100644 index 000000000000..de25301d6dd1 --- /dev/null +++ b/data_structures/graph/graph_matrix.py @@ -0,0 +1,32 @@ +from __future__ import print_function + + +class Graph: + + def __init__(self, vertex): + self.vertex = vertex + self.graph = [[0] * vertex for i in range(vertex) ] + + def add_edge(self, u, v): + self.graph[u - 1][v - 1] = 1 + self.graph[v - 1][u - 1] = 1 + + def show(self): + + for i in self.graph: + for j in i: + 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.show() + diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py new file mode 100644 index 000000000000..d0c2400eb6b5 --- /dev/null +++ b/data_structures/heap/heap.py @@ -0,0 +1,90 @@ +#!/usr/bin/python + +from __future__ import print_function, division + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + +class Heap: + def __init__(self): + self.h = [] + self.currsize = 0 + + 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 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): + self.currsize = len(a) + self.h = list(a) + for i in range(self.currsize/2,-1,-1): + self.maxHeapify(i) + + def getMax(self): + 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): + 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): + 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): + print(self.h) + +def main(): + l = list(map(int, raw_input().split())) + h = Heap() + h.buildHeap(l) + h.heapSort() + h.display() + +if __name__=='__main__': + main() + + diff --git a/data_structures/linked_list/DoublyLinkedList.py b/data_structures/linked_list/DoublyLinkedList.py new file mode 100644 index 000000000000..18eb63fea00e --- /dev/null +++ b/data_structures/linked_list/DoublyLinkedList.py @@ -0,0 +1,76 @@ +''' +- 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. +''' +from __future__ import print_function + + +class LinkedList: + 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 + self.tail = newLink + 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) + 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 + 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 + 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 + def displayLink(self): + print("{}".format(self.value), end=" ") diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py new file mode 100644 index 000000000000..6d50f23c1f1a --- /dev/null +++ b/data_structures/linked_list/__init__.py @@ -0,0 +1,22 @@ +class Node: + def __init__(self, item, next): + self.item = item + self.next = next + +class LinkedList: + def __init__(self): + self.head = None + + def add(self, item): + self.head = Node(item, self.head) + + def remove(self): + if self.is_empty(): + return None + else: + item = self.head.item + self.head = self.head.next + return item + + def is_empty(self): + return self.head is None diff --git a/data_structures/linked_list/singly_LinkedList.py b/data_structures/linked_list/singly_LinkedList.py new file mode 100644 index 000000000000..0d6157e3eb65 --- /dev/null +++ b/data_structures/linked_list/singly_LinkedList.py @@ -0,0 +1,70 @@ +from __future__ import print_function + + +class Node: # create a Node + def __init__(self, data): + self.data = data # given data + self.next = None # given next to None + + +class Linked_List: + def insert_tail(Head, data): + if Head.next is None: + Head.next = Node(data) + else: + Head.next.insert_tail(data) + + def insert_head(Head, data): + tamp = Head + if tamp is None: + newNod = Node() # create a new Node + newNod.data = data + newNod.next = None + Head = newNod # make new node to Head + else: + newNod = Node() + newNod.data = data + newNod.next = Head # put the Head at NewNode Next + Head = newNod # make a NewNode to Head + return Head + + def printList(Head): # print every node data + tamp = Head + while tamp is not None: + print(tamp.data) + tamp = tamp.next + + def delete_head(Head): # delete from head + if Head is not None: + Head = Head.next + return Head # return new Head + + def delete_tail(Head): # delete from tail + if Head is not None: + tamp = Node() + tamp = Head + while tamp.next.next is not None: # find the 2nd last element + tamp = tamp.next + # delete the last element by give next None to 2nd last Element + tamp.next = None + return Head + + def isEmpty(Head): + return Head is None # Return if Head is none + + def reverse(Head): + prev = None + current = Head + + while current: + # Store the current node's next node. + next_node = current.next + # Make the current node's next point backwards + current.next = prev + # Make the previous node be the current node + prev = current + # Make the current node the next node (to progress iteration) + current = next_node + # Return prev in order to put the head at the end + Head = prev + return Head diff --git a/data_structures/queue/DeQueue.py b/data_structures/queue/DeQueue.py new file mode 100644 index 000000000000..fdee64eb6ae0 --- /dev/null +++ b/data_structures/queue/DeQueue.py @@ -0,0 +1,40 @@ +from __future__ import print_function +# 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 +# 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 +# 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) + +# 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) + +# using reverse() to reverse the deque +de.reverse() + +# printing modified deque +print ("The deque after reversing deque is : ") +print (de) diff --git a/data_structures/queue/QueueOnList.py b/data_structures/queue/QueueOnList.py new file mode 100644 index 000000000000..c8d0b41de5d5 --- /dev/null +++ b/data_structures/queue/QueueOnList.py @@ -0,0 +1,45 @@ +"""Queue represented by a python list""" +class Queue(): + def __init__(self): + self.entries = [] + self.length = 0 + self.front=0 + + def __str__(self): + 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:] + 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/QueueOnPseudoStack.py b/data_structures/queue/QueueOnPseudoStack.py new file mode 100644 index 000000000000..b69fbcc988f7 --- /dev/null +++ b/data_structures/queue/QueueOnPseudoStack.py @@ -0,0 +1,50 @@ +"""Queue represented by a pseudo stack (represented by a list with pop and append)""" +class Queue(): + def __init__(self): + self.stack = [] + self.length = 0 + + def __str__(self): + 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 + + """Dequeues {@code item} + @requirement: |self.length| > 0 + @return dequeued + item that was dequeued""" + def get(self): + self.rotate(1) + dequeued = self.stack[self.length-1] + self.stack = self.stack[:-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] + self.stack = self.stack[1:] + self.put(temp) + self.length = self.length - 1 + + """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) + return front + + """Returns the length of this.stack""" + def size(self): + return self.length 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/stacks/Stock-Span-Problem.py b/data_structures/stacks/Stock-Span-Problem.py new file mode 100644 index 000000000000..9628864edd10 --- /dev/null +++ b/data_structures/stacks/Stock-Span-Problem.py @@ -0,0 +1,52 @@ +''' +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. +''' +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)) diff --git a/data_structures/stacks/__init__.py b/data_structures/stacks/__init__.py new file mode 100644 index 000000000000..f7e92ae2d269 --- /dev/null +++ b/data_structures/stacks/__init__.py @@ -0,0 +1,23 @@ +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/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py new file mode 100644 index 000000000000..8d99358bea87 --- /dev/null +++ b/data_structures/stacks/balanced_parentheses.py @@ -0,0 +1,23 @@ +from __future__ import print_function +from __future__ import absolute_import +from .Stack import Stack + +__author__ = 'Omkar Pathak' + + +def balanced_parentheses(parentheses): + """ Use a stack to check if a string of parentheses are balanced.""" + stack = Stack(len(parentheses)) + for parenthesis in parentheses: + if parenthesis == '(': + stack.push(parenthesis) + elif parenthesis == ')': + stack.pop() + return not stack.is_empty() + + +if __name__ == '__main__': + examples = ['((()))', '((())'] + print('Balanced parentheses demonstration:\n') + for example in examples: + 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 new file mode 100644 index 000000000000..75211fed258d --- /dev/null +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -0,0 +1,64 @@ +from __future__ import print_function +from __future__ import absolute_import +import string + +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 + order of operation. + + https://en.wikipedia.org/wiki/Order_of_operations + """ + dictionary = {'+': 1, '-': 1, + '*': 2, '/': 2, + '^': 3} + return dictionary.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 + """ + stack = Stack(len(expression)) + postfix = [] + 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())): + 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() + while not stack.is_empty(): + postfix.append(stack.pop()) + return ' '.join(postfix) + + +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)) diff --git a/data_structures/stacks/next.py b/data_structures/stacks/next.py new file mode 100644 index 000000000000..bca83339592c --- /dev/null +++ b/data_structures/stacks/next.py @@ -0,0 +1,17 @@ +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 new file mode 100644 index 000000000000..66af8c025d8c --- /dev/null +++ b/data_structures/stacks/stack.py @@ -0,0 +1,69 @@ +from __future__ import print_function +__author__ = 'Omkar Pathak' + + +class Stack(object): + """ 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 + Last In, First Out (LIFO). + + https://en.wikipedia.org/wiki/Stack_(abstract_data_type) + """ + + def __init__(self, limit=10): + self.stack = [] + self.limit = limit + + def __bool__(self): + return not bool(self.stack) + + def __str__(self): + return str(self.stack) + + def push(self, data): + """ Push an element to the top of the stack.""" + if len(self.stack) >= self.limit: + raise StackOverflowError + self.stack.append(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') + + def peek(self): + """ Peek at the top-most element of the stack.""" + if self.stack: + return self.stack[-1] + + def is_empty(self): + """ Check if a stack is empty.""" + return not bool(self.stack) + + def size(self): + """ Return the size of the stack.""" + return len(self.stack) + + +class StackOverflowError(BaseException): + pass + + +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())) + 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())) diff --git a/data_structures/trie/Trie.py b/data_structures/trie/Trie.py new file mode 100644 index 000000000000..b6234c6704c6 --- /dev/null +++ b/data_structures/trie/Trie.py @@ -0,0 +1,75 @@ +""" +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. + +""" + + +class TrieNode: + 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 + """ + Inserts a list of words into the Trie + :param words: list of string words + :return: None + """ + for word in words: + self.insert(word) + + def insert(self, word: str): # noqa: E999 This syntax is Python 3 only + """ + Inserts a word into the Trie + :param word: word to be inserted + :return: None + """ + curr = self + for char in word: + if char not in curr.nodes: + curr.nodes[char] = TrieNode() + curr = curr.nodes[char] + curr.is_leaf = True + + def find(self, word: str) -> bool: # noqa: E999 This syntax is Python 3 only + """ + Tries to find word in a Trie + :param word: word to look for + :return: Returns True if word is found, False otherwise + """ + curr = self + for char in word: + if char not in curr.nodes: + return False + curr = curr.nodes[char] + return curr.is_leaf + + +def print_words(node: TrieNode, word: str): # noqa: E999 This syntax is Python 3 only + """ + Prints all the words in a Trie + :param node: root node of Trie + :param word: Word variable should be empty at start + :return: None + """ + if node.is_leaf: + 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'] + 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') + +test() diff --git a/data_structures/union_find/__init__.py b/data_structures/union_find/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/union_find/tests_union_find.py b/data_structures/union_find/tests_union_find.py new file mode 100644 index 000000000000..b0708778ddbd --- /dev/null +++ b/data_structures/union_find/tests_union_find.py @@ -0,0 +1,78 @@ +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 new file mode 100644 index 000000000000..40eea67ac944 --- /dev/null +++ b/data_structures/union_find/union_find.py @@ -0,0 +1,87 @@ +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/dynamic_programming/floyd_warshall.py b/dynamic_programming/floyd_warshall.py new file mode 100644 index 000000000000..038499ca03b6 --- /dev/null +++ b/dynamic_programming/floyd_warshall.py @@ -0,0 +1,37 @@ +import math + +class Graph: + + 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 + + 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): + 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__': + 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.floyd_warshall() + graph.showMin(1,4) + graph.showMin(0,3) diff --git a/file_transfer_protocol/ftp_client_server.py b/file_transfer_protocol/ftp_client_server.py new file mode 100644 index 000000000000..f73f858431f3 --- /dev/null +++ b/file_transfer_protocol/ftp_client_server.py @@ -0,0 +1,58 @@ +# 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' + f = open(filename, 'rb') + in_data = f.read(1024) + while (in_data): + conn.send(in_data) + print('Sent ', repr(in_data)) + in_data = f.read(1024) + f.close() + + 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/ftp_send_receive.py b/file_transfer_protocol/ftp_send_receive.py new file mode 100644 index 000000000000..d4919158a02e --- /dev/null +++ b/file_transfer_protocol/ftp_send_receive.py @@ -0,0 +1,36 @@ +""" + 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 """ + LocalFile = open(FileName, 'wb') + ftp.retrbinary('RETR ' + FileName, LocalFile.write, 1024) + ftp.quit() + LocalFile.close() + +""" + 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 """ + ftp.storbinary('STOR ' + FileName, open(FileName, 'rb')) + ftp.quit() diff --git a/graphs/ArticulationPoints.py b/graphs/ArticulationPoints.py new file mode 100644 index 000000000000..1173c4ea373c --- /dev/null +++ b/graphs/ArticulationPoints.py @@ -0,0 +1,44 @@ +# Finding Articulation Points in Undirected Graph +def computeAP(l): + n = len(l) + outEdgeCount = 0 + low = [0] * n + visited = [False] * n + isArt = [False] * n + + def dfs(root, at, parent, outEdgeCount): + if parent == root: + outEdgeCount += 1 + visited[at] = True + low[at] = at + + for to in l[at]: + if to == parent: + pass + elif not visited[to]: + outEdgeCount = dfs(root, to, at, outEdgeCount) + low[at] = min(low[at], low[to]) + + # AP found via bridge + if at < low[to]: + isArt[at] = True + # AP found via cycle + if at == low[to]: + isArt[at] = True + else: + low[at] = min(low[at], to) + return outEdgeCount + + for i in range(n): + if not visited[i]: + outEdgeCount = 0 + outEdgeCount = dfs(i, i, -1, outEdgeCount) + 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]} +computeAP(l) diff --git a/graphs/CheckBipartiteGraph_BFS.py b/graphs/CheckBipartiteGraph_BFS.py new file mode 100644 index 000000000000..1b9c32c6ccc4 --- /dev/null +++ b/graphs/CheckBipartiteGraph_BFS.py @@ -0,0 +1,43 @@ +# 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): + queue = [] + visited = [False] * len(l) + color = [-1] * len(l) + + def bfs(): + while(queue): + u = queue.pop(0) + visited[u] = True + + for neighbour in l[u]: + + if neighbour == u: + return False + + if color[neighbour] == -1: + color[neighbour] = 1 - color[u] + queue.append(neighbour) + + elif color[neighbour] == color[u]: + return False + + return True + + for i in range(len(l)): + if not visited[i]: + queue.append(i) + color[i] = 0 + if bfs() == 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)) diff --git a/graphs/FindingBridges.py b/graphs/FindingBridges.py new file mode 100644 index 000000000000..56533dd48bde --- /dev/null +++ b/graphs/FindingBridges.py @@ -0,0 +1,31 @@ +# Finding Bridges in Undirected Graph +def computeBridges(l): + id = 0 + n = len(l) # No of vertices in graph + low = [0] * n + visited = [False] * n + + def dfs(at, parent, bridges, id): + visited[at] = True + low[at] = id + id += 1 + for to in l[at]: + if to == parent: + pass + elif not visited[to]: + dfs(to, at, bridges, id) + low[at] = min(low[at], low[to]) + if at < low[to]: + bridges.append([at, to]) + else: + # This edge is a back edge and cannot be a bridge + low[at] = min(low[at], to) + + bridges = [] + for i in range(n): + 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]} +computeBridges(l) diff --git a/graphs/KahnsAlgorithm_long.py b/graphs/KahnsAlgorithm_long.py new file mode 100644 index 000000000000..453b5706f6da --- /dev/null +++ b/graphs/KahnsAlgorithm_long.py @@ -0,0 +1,30 @@ +# Finding longest distance in Directed Acyclic Graph using KahnsAlgorithm +def longestDistance(l): + indegree = [0] * len(l) + queue = [] + longDist = [1] * len(l) + + for key, values in l.items(): + for i in values: + indegree[i] += 1 + + for i in range(len(indegree)): + if indegree[i] == 0: + queue.append(i) + + 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 + + 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:[]} +longestDistance(l) diff --git a/graphs/KahnsAlgorithm_topo.py b/graphs/KahnsAlgorithm_topo.py new file mode 100644 index 000000000000..8c182c4e902c --- /dev/null +++ b/graphs/KahnsAlgorithm_topo.py @@ -0,0 +1,32 @@ +# Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph using BFS +def topologicalSort(l): + indegree = [0] * len(l) + queue = [] + topo = [] + cnt = 0 + + for key, values in l.items(): + for i in values: + indegree[i] += 1 + + for i in range(len(indegree)): + if indegree[i] == 0: + queue.append(i) + + while(queue): + vertex = queue.pop(0) + cnt += 1 + topo.append(vertex) + for x in l[vertex]: + indegree[x] -= 1 + if indegree[x] == 0: + queue.append(x) + + if cnt != len(l): + 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) diff --git a/graphs/MinimumSpanningTree_Prims.py b/graphs/MinimumSpanningTree_Prims.py new file mode 100644 index 000000000000..569e73e0ab7b --- /dev/null +++ b/graphs/MinimumSpanningTree_Prims.py @@ -0,0 +1,111 @@ +import sys +from collections import defaultdict + +def PrimsAlgorithm(l): + + nodePosition = [] + def getPosition(vertex): + return nodePosition[vertex] + + def setPosition(vertex, pos): + nodePosition[vertex] = pos + + def topToBottom(heap, start, size, positions): + if start > size // 2 - 1: + return + else: + if 2 * start + 2 >= size: + m = 2 * start + 1 + else: + if heap[2 * start + 1] < heap[2 * start + 2]: + m = 2 * start + 1 + else: + m = 2 * start + 2 + if heap[m] < heap[start]: + temp, temp1 = heap[m], positions[m] + 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) + + topToBottom(heap, m, size, positions) + + # Update function if value of any node in min-heap decreases + def bottomToTop(val, index, heap, position): + temp = position[index] + + while(index != 0): + if index % 2 == 0: + parent = int( (index-2) / 2 ) + else: + parent = int( (index-1) / 2 ) + + if val < heap[parent]: + heap[index] = heap[parent] + position[index] = position[parent] + setPosition(position[parent], index) + else: + heap[index] = val + position[index] = temp + setPosition(temp, index) + break + index = parent + else: + heap[0] = val + position[0] = temp + setPosition(temp, 0) + + def heapify(heap, positions): + start = len(heap) // 2 - 1 + for i in range(start, -1, -1): + topToBottom(heap, i, len(heap), positions) + + def deleteMinimum(heap, positions): + temp = positions[0] + heap[0] = sys.maxsize + topToBottom(heap, 0, len(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 + # 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 = [] + + for x in range(len(l)): + p = sys.maxsize + Distance_TV.append(p) + Positions.append(x) + nodePosition.append(x) + + TreeEdges = [] + visited[0] = 1 + Distance_TV[0] = sys.maxsize + for x in l[0]: + Nbr_TV[ x[0] ] = 0 + Distance_TV[ x[0] ] = x[1] + heapify(Distance_TV, Positions) + + for i in range(1, len(l)): + vertex = deleteMinimum(Distance_TV, Positions) + if visited[vertex] == 0: + 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) + Nbr_TV[ v[0] ] = vertex + return TreeEdges + +# < --------- Prims Algorithm --------- > +n = int(raw_input("Enter number of vertices: ")) +e = int(raw_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)) diff --git a/graphs/Multi_Hueristic_Astar.py b/graphs/Multi_Hueristic_Astar.py new file mode 100644 index 000000000000..1acd098f327d --- /dev/null +++ b/graphs/Multi_Hueristic_Astar.py @@ -0,0 +1,266 @@ +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): + 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) + +def hueristic_2(P, goal): + # 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]) + +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: + 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 xrange(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 + +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 + +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_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 + +# start and end destination +start = (0, 0) +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") +multi_a_star(start, goal, n_hueristic) diff --git a/graphs/a_star.py b/graphs/a_star.py new file mode 100644 index 000000000000..584222e6f62b --- /dev/null +++ b/graphs/a_star.py @@ -0,0 +1,102 @@ +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], + [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]]''' + +init = [0, 0] +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])): + 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 + + +#the actions we can take +delta = [[-1, 0 ], # go up + [ 0, -1], # go left + [ 1, 0 ], # go down + [ 0, 1 ]] # go right + + +#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[init[0]][init[1]] = 1 + action = [[0 for col in range(len(grid[0]))] for row in range(len(grid))]#the action grid + + x = init[0] + y = init[1] + g = 0 + f = g + heuristic[init[0]][init[0]] + 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 + + 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.reverse() + next = cell.pop() + x = next[2] + y = next[3] + 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 + 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 closed[x2][y2] == 0 and grid[x2][y2] == 0: + g2 = g + cost + f2 = g2 + heuristic[x2][y2] + cell.append([f2, g2, x2, y2]) + closed[x2][y2] = 1 + action[x2][y2] = i + invpath = [] + x = goal[0] + y = goal[1] + 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] + x = x2 + y = y2 + invpath.append([x, y]) + + path = [] + for i in range(len(invpath)): + 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]) + diff --git a/graphs/basic-graphs.py b/graphs/basic-graphs.py new file mode 100644 index 000000000000..c9a269f1ab3f --- /dev/null +++ b/graphs/basic-graphs.py @@ -0,0 +1,281 @@ +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 + +# 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) + +""" +-------------------------------------------------------------------------------- + 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]) + +""" +-------------------------------------------------------------------------------- + Depth First Search. + Args : G - Dictionary of edges + s - Starting Node + Vars : vis - Set of visited nodes + S - Traversal Stack +-------------------------------------------------------------------------------- +""" + + +def dfs(G, s): + vis, S = set([s]), [s] + print(s) + while S: + flag = 0 + for i in G[S[-1]]: + if i not in vis: + S.append(i) + vis.add(i) + flag = 1 + print(i) + break + if not flag: + S.pop() + + +""" +-------------------------------------------------------------------------------- + Breadth First Search. + Args : G - Dictionary of edges + s - Starting Node + Vars : vis - Set of visited nodes + Q - Traveral Stack +-------------------------------------------------------------------------------- +""" +from collections import deque + + +def bfs(G, s): + vis, Q = set([s]), deque([s]) + print(s) + while Q: + u = Q.popleft() + for v in G[u]: + if v not in vis: + vis.add(v) + Q.append(v) + print(v) + + +""" +-------------------------------------------------------------------------------- + Dijkstra's shortest path Algorithm + Args : G - Dictionary of edges + s - Starting Node + Vars : dist - Dictionary storing shortest distance from s to every other node + known - Set of knows nodes + path - Preceding node in path +-------------------------------------------------------------------------------- +""" + + +def dijk(G, s): + dist, known, path = {s: 0}, set(), {s: 0} + while True: + if len(known) == len(G) - 1: + break + mini = 100000 + for i in dist: + if i not in known and dist[i] < mini: + mini = dist[i] + u = i + known.add(u) + for v in G[u]: + if v[0] not in known: + if dist[u] + v[1] < dist.get(v[0], 100000): + dist[v[0]] = dist[u] + v[1] + path[v[0]] = u + for i in dist: + if i != s: + print(dist[i]) + + +""" +-------------------------------------------------------------------------------- + Topological Sort +-------------------------------------------------------------------------------- +""" +from collections import deque + + +def topo(G, ind=None, Q=[1]): + if ind is None: + ind = [0] * (len(G) + 1) # SInce oth Index is ignored + for u in G: + for v in G[u]: + ind[v] += 1 + Q = deque() + for i in G: + if ind[i] == 0: + Q.append(i) + if len(Q) == 0: + return + v = Q.popleft() + print(v) + for w in G[v]: + ind[w] -= 1 + if ind[w] == 0: + Q.append(w) + topo(G, ind, Q) + + +""" +-------------------------------------------------------------------------------- + Reading an Adjacency matrix +-------------------------------------------------------------------------------- +""" + + +def adjm(): + n, a = raw_input(), [] + for i in xrange(n): + a.append(map(int, raw_input().split())) + return a, n + + +""" +-------------------------------------------------------------------------------- + Floyd Warshall's algorithm + Args : G - Dictionary of edges + s - Starting Node + Vars : dist - Dictionary storing shortest distance from s to every other node + known - Set of knows nodes + path - Preceding node in path + +-------------------------------------------------------------------------------- +""" + + +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): + if dist[i][j] > dist[i][k] + dist[k][j]: + dist[i][j] = dist[i][k] + dist[k][j] + path[i][k] = k + print(dist) + + +""" +-------------------------------------------------------------------------------- + Prim's MST Algorithm + Args : G - Dictionary of edges + s - Starting Node + Vars : dist - Dictionary storing shortest distance from s to nearest node + known - Set of knows nodes + path - Preceding node in path +-------------------------------------------------------------------------------- +""" + + +def prim(G, s): + dist, known, path = {s: 0}, set(), {s: 0} + while True: + if len(known) == len(G) - 1: + break + mini = 100000 + for i in dist: + if i not in known and dist[i] < mini: + mini = dist[i] + u = i + known.add(u) + for v in G[u]: + if v[0] not in known: + if v[1] < dist.get(v[0], 100000): + dist[v[0]] = v[1] + path[v[0]] = u + + +""" +-------------------------------------------------------------------------------- + Accepting Edge list + Vars : n - Number of nodes + m - Number of edges + Returns : l - Edge list + n - Number of Nodes +-------------------------------------------------------------------------------- +""" + + +def edglist(): + n, m = map(int, raw_input().split(" ")) + l = [] + for i in xrange(m): + l.append(map(int, raw_input().split(' '))) + return l, n + + +""" +-------------------------------------------------------------------------------- + Kruskal's MST Algorithm + Args : E - Edge list + n - Number of Nodes + Vars : s - Set of all nodes as unique disjoint sets (initially) +-------------------------------------------------------------------------------- +""" + + +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)] + while True: + if len(s) == 1: + break + print(s) + x = E.pop() + for i in xrange(len(s)): + if x[0] in s[i]: + break + for j in xrange(len(s)): + if x[1] in s[j]: + if i == j: + break + s[j].update(s[i]) + s.pop(i) + break diff --git a/graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py new file mode 100644 index 000000000000..930aab2251c4 --- /dev/null +++ b/graphs/minimum_spanning_tree_kruskal.py @@ -0,0 +1,32 @@ +from __future__ import print_function +num_nodes, num_edges = list(map(int,raw_input().split())) + +edges = [] + +for i in range(num_edges): + node1, node2, cost = list(map(int,raw_input().split())) + edges.append((i,node1,node2,cost)) + +edges = sorted(edges, key=lambda edge: edge[3]) + +parent = [i for i in range(num_nodes)] + +def find_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 + +print(minimum_spanning_tree_cost) +for edge in minimum_spanning_tree: + print(edge) diff --git a/graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py new file mode 100644 index 000000000000..90d3568fa496 --- /dev/null +++ b/graphs/scc_kosaraju.py @@ -0,0 +1,46 @@ +from __future__ import print_function +# n - no of nodes, m - no of edges +n, m = list(map(int,raw_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,raw_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 + 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 + 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 + for i in stack[::-1]: + if visit[i]: continue + component = [] + dfs2(i) + scc.append(component) + return scc + +print(kosaraju()) diff --git a/graphs/tarjans_scc.py b/graphs/tarjans_scc.py new file mode 100644 index 000000000000..89754e593508 --- /dev/null +++ b/graphs/tarjans_scc.py @@ -0,0 +1,78 @@ +from collections import deque + + +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). + + 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 + connected component. + + 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) + stack = deque() + on_stack = [False for _ in range(n)] + index_of = [-1 for _ in range(n)] + lowlink_of = index_of[:] + + def strong_connect(v, index, components): + index_of[v] = index # the number when this node is seen + lowlink_of[v] = index # lowest rank node reachable from here + index += 1 + stack.append(v) + on_stack[v] = True + + 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] + elif on_stack[w]: + 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 = [] + w = stack.pop() + on_stack[w] = False + component.append(w) + while w != v: + w = stack.pop() + on_stack[w] = False + component.append(w) + components.append(component) + return index + + components = [] + for v in range(n): + if index_of[v] == -1: + strong_connect(v, 0, components) + + return components + + +def create_graph(n, edges): + g = [[] for _ in range(n)] + for u, v in edges: + g[u].append(v) + return g + + +if __name__ == '__main__': + # Test + n_vertices = 7 + source = [0, 0, 1, 2, 3, 3, 4, 4, 6] + target = [1, 3, 2, 0, 1, 4, 5, 6, 5] + edges = [(u, v) for u, v in zip(source, target)] + g = create_graph(n_vertices, edges) + + assert [[5], [6], [4], [3, 2, 1, 0]] == tarjan(g) diff --git a/linear_algebra_python/README.md b/linear_algebra_python/README.md new file mode 100644 index 000000000000..ebfcdab7b179 --- /dev/null +++ b/linear_algebra_python/README.md @@ -0,0 +1,74 @@ +# Linear algebra library for Python + +This module contains some useful classes and functions for dealing with linear algebra in python 2. + +--- + +## Overview + +- class Vector + - This class represents a vector of arbitray size and operations on it. + + **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) + - size() : gets the size 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'. +- class Matrix + - 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 + - height() : returns the height of the matrix + - 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 + +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)```. + +--- + +## 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. + +--- + +## 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**. diff --git a/linear_algebra_python/src/lib.py b/linear_algebra_python/src/lib.py new file mode 100644 index 000000000000..66f27ff8946e --- /dev/null +++ b/linear_algebra_python/src/lib.py @@ -0,0 +1,364 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Feb 26 14:29:11 2018 + +@author: Christian Bender +@license: MIT-license + +This module contains some useful classes and functions for dealing +with linear algebra in python. + +Overview: + +- class Vector +- function zeroVector(dimension) +- function unitBasisVector(dimension,pos) +- function axpy(scalar,vector1,vector2) +- function randomVector(N,a,b) +- class Matrix +- function squareZeroMatrix(N) +- function randomMatrix(W,H,a,b) +""" + + +import math +import random + + +class Vector(object): + """ + This class represents a vector of arbitray 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) + size() : gets the size 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. + TODO: compare-operator + """ + def __init__(self,components): + """ + input: components or nothing + simple constructor for init the vector + """ + self.__components = components + def set(self,components): + """ + input: new components + changes the components of the vector. + replace the components with newer one. + """ + if len(components) > 0: + self.__components = components + else: + raise Exception("please give any vector") + def __str__(self): + """ + returns a string representation of the vector + """ + ans = "(" + length = len(self.__components) + for i in range(length): + if i != length-1: + ans += str(self.__components[i]) + "," + else: + ans += str(self.__components[i]) + ")" + if len(ans) == 1: + ans += ")" + return ans + def component(self,i): + """ + input: index (start at 0) + output: the i-th component of the vector. + """ + if i < len(self.__components) and i >= 0: + return self.__components[i] + else: + raise Exception("index out of range") + def size(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 + return math.sqrt(summe) + def __add__(self,other): + """ + input: other vector + assumes: other vector has the same size + returns a new vector that represents the sum. + """ + size = self.size() + result = [] + if size == other.size(): + for i in range(size): + result.append(self.__components[i] + other.component(i)) + else: + raise Exception("must have the same size") + return Vector(result) + def __sub__(self,other): + """ + input: other vector + assumes: other vector has the same size + returns a new vector that represents the differenz. + """ + size = self.size() + result = [] + if size == other.size(): + for i in range(size): + result.append(self.__components[i] - other.component(i)) + else: # error case + raise Exception("must have the same size") + return Vector(result) + def __mul__(self,other): + """ + mul implements the scalar multiplication + and the dot-product + """ + ans = [] + if isinstance(other,float) or isinstance(other,int): + for c in self.__components: + ans.append(c*other) + elif (isinstance(other,Vector) and (self.size() == other.size())): + size = self.size() + summe = 0 + for i in range(size): + summe += self.__components[i] * other.component(i) + return summe + else: # error case + raise Exception("invalide operand!") + return Vector(ans) + def copy(self): + """ + copies this vector and returns it. + """ + components = [x for x in self.__components] + return Vector(components) + def changeComponent(self,pos,value): + """ + input: an index (pos) and a value + changes the specified component (pos) with the + 'value' + """ + #precondition + assert (pos >= 0 and pos < len(self.__components)) + self.__components[pos] = value + +def zeroVector(dimension): + """ + returns a zero-vector of size 'dimension' + """ + #precondition + assert(isinstance(dimension,int)) + ans = [] + for i in range(dimension): + ans.append(0) + return Vector(ans) + + +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 = [] + for i in range(dimension): + if i != pos: + ans.append(0) + else: + ans.append(1) + return Vector(ans) + + +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) + + +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'. + """ + ans = zeroVector(N) + random.seed(None) + for i in range(N): + ans.changeComponent(i,random.randint(a,b)) + return ans + + +class Matrix(object): + """ + class: Matrix + This class represents a arbitrary 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 + operator + : implements the matrix-addition. + operator - _ implements the matrix-subtraction + """ + def __init__(self,matrix,w,h): + """ + simple constructor for initialzes + the matrix with components. + """ + self.__matrix = matrix + self.__width = w + self.__height = h + def __str__(self): + """ + returns a string representation of this + matrix. + """ + ans = "" + for i in range(self.__height): + ans += "|" + for j in range(self.__width): + 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): + """ + 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): + """ + 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") + def width(self): + """ + getter for the width + """ + return self.__width + def height(self): + """ + getter for the height + """ + return self.__height + def __mul__(self,other): + """ + implements the matrix-vector multiplication. + implements the matrix-scalar multiplication + """ + if isinstance(other, Vector): # vector-matrix + if (other.size() == 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) + 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 = [] + for i in range(self.__height): + row = [] + for j in range(self.__width): + row.append(self.__matrix[i][j] * other) + matrix.append(row) + 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()): + matrix = [] + for i in range(self.__height): + row = [] + for j in range(self.__width): + row.append(self.__matrix[i][j] + other.component(i,j)) + matrix.append(row) + return Matrix(matrix,self.__width,self.__height) + else: + raise Exception("matrix must have the same dimension!") + def __sub__(self,other): + """ + implements the matrix-subtraction. + """ + 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)) + matrix.append(row) + 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 = [] + for i in range(N): + row = [] + for j in range(N): + row.append(0) + ans.append(row) + return Matrix(ans,N,N) + + +def randomMatrix(W,H,a,b): + """ + returns a random matrix WxH with integer components + between 'a' and 'b' + """ + matrix = [] + random.seed(None) + for i in range(H): + row = [] + for j in range(W): + row.append(random.randint(a,b)) + matrix.append(row) + return Matrix(matrix,W,H) + + \ No newline at end of file diff --git a/linear_algebra_python/src/tests.py b/linear_algebra_python/src/tests.py new file mode 100644 index 000000000000..b84612b4ced4 --- /dev/null +++ b/linear_algebra_python/src/tests.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Feb 26 15:40:07 2018 + +@author: Christian Bender +@license: MIT-license + +This file contains the test-suite for the linear algebra library. +""" + +import unittest +from lib import * + +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) + 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(x.__str__(),"(0,0,0,0,0,1)") + def test_size(self): + """ + test for size()-method + """ + x = Vector([1,2,3,4]) + self.assertEqual(x.size(),4) + def test_euclidLength(self): + """ + test for the eulidean length + """ + 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) + 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) + 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((x*3.0).__str__(),"(3.0,6.0,9.0)") + self.assertEqual((a*b),0) + def test_zeroVector(self): + """ + test for the global function zeroVector(...) + """ + self.assertTrue(zeroVector(10).__str__().count("0") == 10) + def test_unitBasisVector(self): + """ + test for the global function unitBasisVector(...) + """ + self.assertEqual(unitBasisVector(3,1).__str__(),"(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(axpy(2,x,y).__str__(),"(3,4,7)") + def test_copy(self): + """ + test for the copy()-method + """ + x = Vector([1,0,0,0,0,0]) + y = x.copy() + self.assertEqual(x.__str__(),y.__str__()) + def test_changeComponent(self): + """ + test for the changeComponent(...)-method + """ + x = Vector([1,0,0]) + x.changeComponent(0,0) + x.changeComponent(1,1) + self.assertEqual(x.__str__(),"(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",A.__str__()) + 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)",(A*x).__str__()) + self.assertEqual("|2,4,6|\n|8,10,12|\n|14,16,18|\n",(A*2).__str__()) + 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",A.__str__()) + 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) + 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",(A+B).__str__()) + 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",(A-B).__str__()) + 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',squareZeroMatrix(5).__str__()) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/maths/BasicMaths.py b/maths/BasicMaths.py new file mode 100644 index 000000000000..6e8c919a001d --- /dev/null +++ b/maths/BasicMaths.py @@ -0,0 +1,74 @@ +import math + +def primeFactors(n): + 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 numberOfDivisors(n): + 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): + temp = 1 + while n % i == 0: + temp += 1 + n = int(n / i) + div = div * (temp) + + return div + +def sumOfDivisors(n): + 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: + temp += 1 + n = int(n / i) + if temp > 1: + s *= (i**temp - 1) / (i - 1) + + return s + +def eulerPhi(n): + l = primeFactors(n) + l = set(l) + s = n + for x in l: + s *= (x - 1)/x + return s + +def main(): + print(primeFactors(100)) + print(numberOfDivisors(100)) + print(sumOfDivisors(100)) + print(eulerPhi(100)) + +if __name__ == '__main__': + main() + + \ No newline at end of file diff --git a/maths/FibonacciSequenceRecursion.py b/maths/FibonacciSequenceRecursion.py new file mode 100644 index 000000000000..b0b10fd07262 --- /dev/null +++ b/maths/FibonacciSequenceRecursion.py @@ -0,0 +1,18 @@ +# Fibonacci Sequence Using Recursion + +def recur_fibo(n): + return n if n <= 1 else (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): + print("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: ") + +if __name__ == '__main__': + main() diff --git a/maths/GreaterCommonDivisor.py b/maths/GreaterCommonDivisor.py new file mode 100644 index 000000000000..15adaca1fb8d --- /dev/null +++ b/maths/GreaterCommonDivisor.py @@ -0,0 +1,15 @@ +# Greater Common Divisor - https://en.wikipedia.org/wiki/Greatest_common_divisor +def gcd(a, b): + return b if a == 0 else gcd(b % a, a) + +def main(): + try: + nums = input("Enter two Integers separated by comma (,): ").split(',') + num1 = int(nums[0]); num2 = int(nums[1]) + except (IndexError, UnboundLocalError, ValueError): + print("Wrong Input") + print(f"gcd({num1}, {num2}) = {gcd(num1, num2)}") + +if __name__ == '__main__': + main() + diff --git a/maths/ModularExponential.py b/maths/ModularExponential.py new file mode 100644 index 000000000000..b3f4c00bd5d8 --- /dev/null +++ b/maths/ModularExponential.py @@ -0,0 +1,20 @@ +def modularExponential(base, power, mod): + 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)) + + +if __name__ == '__main__': + main() diff --git a/maths/SegmentedSieve.py b/maths/SegmentedSieve.py new file mode 100644 index 000000000000..52ca6fbe601d --- /dev/null +++ b/maths/SegmentedSieve.py @@ -0,0 +1,46 @@ +import math + +def sieve(n): + in_prime = [] + start = 2 + end = int(math.sqrt(n)) # Size of every segment + temp = [True] * (end + 1) + prime = [] + + while(start <= end): + if temp[start] == True: + in_prime.append(start) + for i in range(start*start, end+1, start): + if temp[i] == 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) + for each in in_prime: + + t = math.floor(low / each) * each + if t < low: + t += 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) + + low = high + 1 + high = low + end - 1 + if high > n: + high = n + + return prime + +print(sieve(10**6)) \ No newline at end of file diff --git a/maths/SieveOfEratosthenes.py b/maths/SieveOfEratosthenes.py new file mode 100644 index 000000000000..2b132405ae4d --- /dev/null +++ b/maths/SieveOfEratosthenes.py @@ -0,0 +1,24 @@ +import math +n = int(raw_input("Enter n: ")) + +def sieve(n): + l = [True] * (n+1) + prime = [] + start = 2 + end = int(math.sqrt(n)) + while(start <= end): + if l[start] == True: + prime.append(start) + for i in range(start*start, n+1, start): + if l[i] == True: + l[i] = False + start += 1 + + for j in range(end+1,n+1): + if l[j] == True: + prime.append(j) + + return prime + +print(sieve(n)) + \ No newline at end of file diff --git a/maths/SimpsonRule.py b/maths/SimpsonRule.py new file mode 100644 index 000000000000..091c86c17f1b --- /dev/null +++ b/maths/SimpsonRule.py @@ -0,0 +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' + +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 + +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_2(boundary, steps) + print('y = {0}'.format(y)) + +if __name__ == '__main__': + main() diff --git a/maths/TrapezoidalRule.py b/maths/TrapezoidalRule.py new file mode 100644 index 000000000000..52310c1ed3b0 --- /dev/null +++ b/maths/TrapezoidalRule.py @@ -0,0 +1,46 @@ +''' +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' + +method 1: +"extended trapezoidal rule" + +''' +from __future__ import print_function + +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 = makePoints(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 makePoints(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() diff --git a/neural_network/FCN.ipynb b/neural_network/FCN.ipynb new file mode 100644 index 000000000000..a8bcf4beeea1 --- /dev/null +++ b/neural_network/FCN.ipynb @@ -0,0 +1,327 @@ +{ + "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 +} diff --git a/neural_network/bpnn.py b/neural_network/bpnn.py new file mode 100644 index 000000000000..0865e35f0b5c --- /dev/null +++ b/neural_network/bpnn.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# encoding=utf8 + +''' + +A Framework of Back Propagation Neural Network(BP) model + +Easy to use: + * add many layers as you want !!! + * clearly see how the loss decreasing +Easy to expand: + * more activation functions + * more loss functions + * more optimization method + +Author: Stephen Lee +Github : https://github.com/RiptideBo +Date: 2017.11.23 + +''' + +import numpy as np +import matplotlib.pyplot as plt + + +def sigmoid(x): + return 1 / (1 + np.exp(-1 * x)) + +class DenseLayer(): + ''' + Layers of BP neural network + ''' + 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 + self.activation = activation + if learning_rate is None: + learning_rate = 0.3 + 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 + 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_activation = np.diag(np.diag(gradient_mat)) + else: + gradient_activation = 1 + return gradient_activation + + def forward_propagation(self,xdata): + self.xdata = xdata + if self.is_input_layer: + # input layer + self.wx_plus_b = xdata + self.output = xdata + return xdata + else: + 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): + + 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_bias = gradient * self._gradient_bias + self.gradient = np.dot(gradient,self._gradient_x).T + # ----------------------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 + + return self.gradient + + +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) + + def add_layer(self,layer): + self.layers.append(layer) + + def build(self): + for i,layer in enumerate(self.layers[:]): + if i < 1: + layer.is_input_layer = True + else: + 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)) + + def train(self,xdata,ydata,train_round,accuracy): + self.train_round = train_round + self.accuracy = accuracy + + self.ax_loss.hlines(self.accuracy, 0, self.train_round * 1.1) + + x_shape = np.shape(xdata) + 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 + + # forward propagation + for layer in self.layers: + _xdata = layer.forward_propagation(_xdata) + + loss, gradient = self.cal_loss(_ydata, _xdata) + all_loss = all_loss + loss + + # back propagation + # the input_layer does not upgrade + for layer in self.layers[:0:-1]: + gradient = layer.back_propagation(gradient) + + mse = all_loss/x_shape[0] + self.train_mse.append(mse) + + self.plot_loss() + + if mse < self.accuracy: + print('----达到精度----') + return mse + + 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 + + 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-') + plt.ion() + 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]]) + + model = BPNN() + model.add_layer(DenseLayer(10)) + model.add_layer(DenseLayer(20)) + model.add_layer(DenseLayer(30)) + model.add_layer(DenseLayer(2)) + + model.build() + + model.summary() + + model.train(xdata=x,ydata=y,train_round=100,accuracy=0.01) + +if __name__ == '__main__': + example() diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py new file mode 100644 index 000000000000..0dca2bc485d1 --- /dev/null +++ b/neural_network/convolution_neural_network.py @@ -0,0 +1,306 @@ +#-*- coding: utf-8 -*- + +''' + - - - - - -- - - - - - - - - - - - - - - - - - - - - - - + Name - - CNN - Convolution Neural Network For Photo Recognizing + Goal - - Recognize Handing Writting Word Photo + Detail:Total 5 layers neural network + * Convolution layer + * Pooling layer + * Input layer layer of BP + * Hiden layer of BP + * Output layer of BP + Author: Stephen Lee + Github: 245885195@qq.com + Date: 2017.9.20 + - - - - - -- - - - - - - - - - - - - - - - - - - - - - - + ''' +from __future__ import print_function + +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): + ''' + :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 + :param bp_num2: units number of hidden layer + :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 + self.conv1 = conv1_get[:2] + self.step_conv1 = conv1_get[2] + 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.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 + + + 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} + with open(save_path, 'wb') as f: + pickle.dump(model_dic, f) + + print('Model saved: %s'% save_path) + + @classmethod + def ReadModel(cls,model_path): + #read saved model + import pickle + 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') + return conv_ins + + + def sig(self,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 + size_conv = convs[0] + num_conv =convs[1] + size_data = np.shape(data)[0] + #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] + data_focus.append(focus) + #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] + featuremap.append(self.sig(net_focus)) + featuremap = np.asmatrix(featuremap).reshape(Size_FeatureMap, Size_FeatureMap) + data_featuremap.append(featuremap) + + #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 + + def pooling(self,featuremaps,size_pooling,type='average_pool'): + #pooling process + size_map = len(featuremaps[0]) + 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 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 + map_pooled.append(np.average(focus)) + elif type == 'max_pooling': + #max pooling + map_pooled.append(np.max(focus)) + 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 + 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 = 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 + 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): + ''' + 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] + i_pool = i_pool + 1 + 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 trian(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 + while rp < n_repeat and mse >= error_accuracy: + alle = 0 + print('-------------Learning Time %d--------------'%rp) + for p in range(len(datas_train)): + #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) + 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_out2 = self.sig(bp_net_j) + bp_net_k = np.dot(bp_out2 ,self.wkj.T) - self.thre_bp3 + bp_out3 = self.sig(bp_net_k) + + #--------------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_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 + 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) + + 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.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 + 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 + #print(' ----Teach ',data_teach) + #print(' ----BP_output ',bp_out3) + rp = rp + 1 + 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.grid(True, alpha=0.5) + plt.show() + print('------------------Training Complished---------------------') + print((' - - Training epoch: ', rp, ' - - Mse: %.6f' % mse)) + if draw_e: + draw_error() + return mse + + def predict(self,datas_test): + #model predict + produce_out = [] + 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_pooled1 = self.pooling(data_conved1, self.size_pooling1) + data_bp_input = self._expand(data_pooled1) + + bp_out1 = data_bp_input + bp_net_j = bp_out1 * self.vji.T - self.thre_bp2 + bp_out2 = self.sig(bp_net_j) + 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] + return np.asarray(res) + + 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) + data_pooled1 = self.pooling(data_conved1, self.size_pooling1) + + return data_conved1,data_pooled1 + + +if __name__ == '__main__': + pass + ''' + I will put the example on other file + ''' \ No newline at end of file diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py new file mode 100644 index 000000000000..16e632f8db4a --- /dev/null +++ b/neural_network/perceptron.py @@ -0,0 +1,124 @@ +''' + + 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 training(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(raw_input('value: '))) + network.sort(sample) diff --git a/project_euler/Problem 01/sol1.py b/project_euler/Problem 01/sol1.py new file mode 100644 index 000000000000..27031c3cfa9a --- /dev/null +++ b/project_euler/Problem 01/sol1.py @@ -0,0 +1,17 @@ +''' +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 +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) diff --git a/project_euler/Problem 01/sol2.py b/project_euler/Problem 01/sol2.py new file mode 100644 index 000000000000..2b7760e0bfff --- /dev/null +++ b/project_euler/Problem 01/sol2.py @@ -0,0 +1,20 @@ +''' +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 +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) diff --git a/project_euler/Problem 01/sol3.py b/project_euler/Problem 01/sol3.py new file mode 100644 index 000000000000..f4f3aefcc5de --- /dev/null +++ b/project_euler/Problem 01/sol3.py @@ -0,0 +1,50 @@ +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. +''' + +try: + 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); diff --git a/project_euler/Problem 01/sol4.py b/project_euler/Problem 01/sol4.py new file mode 100644 index 000000000000..7941f5fcd3fe --- /dev/null +++ b/project_euler/Problem 01/sol4.py @@ -0,0 +1,30 @@ +def mulitples(limit): + xmulti = [] + zmulti = [] + z = 3 + x = 5 + temp = 1 + while True: + result = z * temp + if (result < limit): + zmulti.append(result) + temp += 1 + else: + temp = 1 + break + while True: + result = x * temp + if (result < limit): + xmulti.append(result) + temp += 1 + else: + break + collection = list(set(xmulti+zmulti)) + return (sum(collection)) + + + + + + +print (mulitples(1000)) diff --git a/project_euler/Problem 02/sol1.py b/project_euler/Problem 02/sol1.py new file mode 100644 index 000000000000..f8257fb615fb --- /dev/null +++ b/project_euler/Problem 02/sol1.py @@ -0,0 +1,26 @@ +''' +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 + +n = int(raw_input().strip()) +i=1 +j=2 +sum=0 +while(j<=n): + if((j&1)==0): #can also use (j%2==0) + sum+=j + temp=i + i=j + j=temp+i +print(sum) diff --git a/project_euler/Problem 02/sol2.py b/project_euler/Problem 02/sol2.py new file mode 100644 index 000000000000..aa8dc7f76f3b --- /dev/null +++ b/project_euler/Problem 02/sol2.py @@ -0,0 +1,12 @@ +def fib(n): + a, b, s = 0, 1, 0 + while b < n: + if b % 2 == 0 and b < n: s += b + a, b = b, a+b + ls.append(s) + +T = int(input().strip()) +ls = [] +for _ in range(T): + fib(int(input().strip())) +print(ls, sep = '\n') diff --git a/project_euler/Problem 02/sol3.py b/project_euler/Problem 02/sol3.py new file mode 100644 index 000000000000..ede5e196ba7d --- /dev/null +++ b/project_euler/Problem 02/sol3.py @@ -0,0 +1,20 @@ +''' +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(raw_input()) +a=0 +b=2 +count=0 +while 4*b+a1): + prime=n +print(prime) diff --git a/project_euler/Problem 04/sol1.py b/project_euler/Problem 04/sol1.py new file mode 100644 index 000000000000..27490130c5c8 --- /dev/null +++ b/project_euler/Problem 04/sol1.py @@ -0,0 +1,29 @@ +''' +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. +''' +from __future__ import print_function +limit = int(raw_input("limit? ")) + +# fetchs the next number +for number in range(limit-1,10000,-1): + + # converts number into string. + strNumber = str(number) + + # checks whether 'strNumber' is a palindrome. + if(strNumber == strNumber[::-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(number / divisor)) == 3)): + + print(number) + exit(0) + + divisor -=1 \ No newline at end of file diff --git a/project_euler/Problem 04/sol2.py b/project_euler/Problem 04/sol2.py new file mode 100644 index 000000000000..a7e486d38e5a --- /dev/null +++ b/project_euler/Problem 04/sol2.py @@ -0,0 +1,19 @@ +''' +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. +''' +from __future__ import print_function +arr = [] +for i in range(999,100,-1): + for j in range(999,100,-1): + t = str(i*j) + if t == t[::-1]: + arr.append(i*j) +arr.sort() + +n=int(raw_input()) +for i in arr[::-1]: + if(i LargestProduct: + LargestProduct = product + print(LargestProduct) + + +if __name__ == '__main__': + main() diff --git a/project_euler/Problem 09/sol1.py b/project_euler/Problem 09/sol1.py new file mode 100644 index 000000000000..e54c543b4721 --- /dev/null +++ b/project_euler/Problem 09/sol1.py @@ -0,0 +1,15 @@ +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 diff --git a/project_euler/Problem 09/sol2.py b/project_euler/Problem 09/sol2.py new file mode 100644 index 000000000000..c02662d89714 --- /dev/null +++ b/project_euler/Problem 09/sol2.py @@ -0,0 +1,18 @@ +"""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 + +product=-1 +d=0 +N = int(raw_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) diff --git a/project_euler/Problem 10/sol1.py b/project_euler/Problem 10/sol1.py new file mode 100644 index 000000000000..94e5b7362114 --- /dev/null +++ b/project_euler/Problem 10/sol1.py @@ -0,0 +1,38 @@ +from __future__ import print_function +from math import sqrt + +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + +def is_prime(n): + for i in xrange(2, int(sqrt(n))+1): + if n%i == 0: + return False + + 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.') diff --git a/project_euler/Problem 11/grid.txt b/project_euler/Problem 11/grid.txt new file mode 100644 index 000000000000..1fc75c66a314 --- /dev/null +++ b/project_euler/Problem 11/grid.txt @@ -0,0 +1,20 @@ +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 \ No newline at end of file diff --git a/project_euler/Problem 11/sol1.py b/project_euler/Problem 11/sol1.py new file mode 100644 index 000000000000..b882dc449156 --- /dev/null +++ b/project_euler/Problem 11/sol1.py @@ -0,0 +1,68 @@ +from __future__ import print_function +''' +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 +''' + +try: + xrange #Python 2 +except NameError: + xrange = range #Python 2 + +def largest_product(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] + + #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 + + return largest + +if __name__ == '__main__': + grid = [] + with open('grid.txt') as file: + for line in file: + grid.append(line.strip('\n').split(' ')) + + grid = [[int(i) for i in grid[j]] for j in xrange(len(grid))] + + print(largest_product(grid)) \ No newline at end of file diff --git a/project_euler/Problem 11/sol2.py b/project_euler/Problem 11/sol2.py new file mode 100644 index 000000000000..b03395f01697 --- /dev/null +++ b/project_euler/Problem 11/sol2.py @@ -0,0 +1,39 @@ +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 diff --git a/project_euler/Problem 12/sol1.py b/project_euler/Problem 12/sol1.py new file mode 100644 index 000000000000..9c4483fd62e5 --- /dev/null +++ b/project_euler/Problem 12/sol1.py @@ -0,0 +1,46 @@ +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: + +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? +''' +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): + if n%i == 0: + nDivisors += 2 + + return nDivisors + +tNum = 1 +i = 1 + +while True: + i += 1 + tNum += i + + if count_divisors(tNum) > 500: + break + +print(tNum) \ No newline at end of file diff --git a/project_euler/Problem 13/sol1.py b/project_euler/Problem 13/sol1.py new file mode 100644 index 000000000000..f041f01650c6 --- /dev/null +++ b/project_euler/Problem 13/sol1.py @@ -0,0 +1,14 @@ +''' +Problem Statement: +Work out the first ten digits of the sum of the N 50-digit numbers. +''' +from __future__ import print_function + +n = int(raw_input().strip()) + +array = [] +for i in range(n): + array.append(int(raw_input().strip())) + +print(str(sum(array))[:10]) + diff --git a/project_euler/Problem 14/sol1.py b/project_euler/Problem 14/sol1.py new file mode 100644 index 000000000000..9037f6eb8bd5 --- /dev/null +++ b/project_euler/Problem 14/sol1.py @@ -0,0 +1,21 @@ +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')) diff --git a/project_euler/Problem 15/sol1.py b/project_euler/Problem 15/sol1.py new file mode 100644 index 000000000000..d24748011ef9 --- /dev/null +++ b/project_euler/Problem 15/sol1.py @@ -0,0 +1,20 @@ +from __future__ import print_function +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.') diff --git a/project_euler/Problem 16/sol1.py b/project_euler/Problem 16/sol1.py new file mode 100644 index 000000000000..dfd7c9ae3258 --- /dev/null +++ b/project_euler/Problem 16/sol1.py @@ -0,0 +1,15 @@ +power = int(raw_input("Enter the power of 2: ")) +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) + +print("Sum of the digits are:",sum_of_num) diff --git a/project_euler/Problem 17/sol1.py b/project_euler/Problem 17/sol1.py new file mode 100644 index 000000000000..9de5d80b9b29 --- /dev/null +++ b/project_euler/Problem 17/sol1.py @@ -0,0 +1,35 @@ +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 is not 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) \ No newline at end of file diff --git a/project_euler/Problem 19/sol1.py b/project_euler/Problem 19/sol1.py new file mode 100644 index 000000000000..94cf117026a4 --- /dev/null +++ b/project_euler/Problem 19/sol1.py @@ -0,0 +1,51 @@ +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. + +1 Jan 1900 was a Monday. +Thirty days has September, +April, June and November. +All the rest have thirty-one, +Saving February alone, +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. + +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 + +sundays = 0 + +while year < 2001: + 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: + month += 1 + day = day-days_per_month[month-2] + elif day > 29 and month is 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 is 1: + sundays += 1 + +print(sundays) \ No newline at end of file diff --git a/project_euler/Problem 20/sol1.py b/project_euler/Problem 20/sol1.py new file mode 100644 index 000000000000..b347eb1f0cd8 --- /dev/null +++ b/project_euler/Problem 20/sol1.py @@ -0,0 +1,27 @@ +# Finding the factorial. +def factorial(n): + fact = 1 + for i in range(1,n+1): + fact *= i + return fact + +# Spliting the digits and adding it. +def split_and_add(number): + sum_of_digits = 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. + return sum_of_digits + +# Taking the user input. +number = int(raw_input("Enter the Number: ")) + +# Assigning the factorial from the factorial function. +factorial = factorial(number) + +# Spliting and adding the factorial into answer. +answer = split_and_add(factorial) + +# Printing the answer. +print(answer) diff --git a/project_euler/Problem 20/sol2.py b/project_euler/Problem 20/sol2.py new file mode 100644 index 000000000000..bca9af9cb9ef --- /dev/null +++ b/project_euler/Problem 20/sol2.py @@ -0,0 +1,5 @@ +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 diff --git a/project_euler/Problem 21/sol1.py b/project_euler/Problem 21/sol1.py new file mode 100644 index 000000000000..6d137a7d4332 --- /dev/null +++ b/project_euler/Problem 21/sol1.py @@ -0,0 +1,42 @@ +#-.- coding: latin-1 -.- +from __future__ import print_function +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. + +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 +except NameError: + 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 + +sums = [] +total = 0 + +for i in xrange(1, 10000): + n = sum_of_divisors(i) + + if n < len(sums): + if sums[n-1] == i: + total += n + i + + sums.append(n) + +print(total) \ No newline at end of file diff --git a/project_euler/Problem 22/p022_names.txt b/project_euler/Problem 22/p022_names.txt new file mode 100644 index 000000000000..7b8986bf6ce9 --- /dev/null +++ b/project_euler/Problem 22/p022_names.txt @@ -0,0 +1 @@ +"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" \ No newline at end of file diff --git a/project_euler/Problem 22/sol1.py b/project_euler/Problem 22/sol1.py new file mode 100644 index 000000000000..7754306583dc --- /dev/null +++ b/project_euler/Problem 22/sol1.py @@ -0,0 +1,37 @@ +# -*- 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. + +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? +''' +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + +with open('p022_names.txt') as file: + names = str(file.readlines()[0]) + names = names.replace('"', '').split(',') + +names.sort() + +name_score = 0 +total_score = 0 + +for i, name in enumerate(names): + for letter in name: + name_score += ord(letter) - 64 + + total_score += (i+1)*name_score + name_score = 0 + +print(total_score) \ No newline at end of file diff --git a/project_euler/Problem 22/sol2.py b/project_euler/Problem 22/sol2.py new file mode 100644 index 000000000000..d7f9abf09d49 --- /dev/null +++ b/project_euler/Problem 22/sol2.py @@ -0,0 +1,533 @@ +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) + + +if __name__ == '__main__': + main() diff --git a/project_euler/Problem 24/sol1.py b/project_euler/Problem 24/sol1.py new file mode 100644 index 000000000000..b20493cb03af --- /dev/null +++ b/project_euler/Problem 24/sol1.py @@ -0,0 +1,7 @@ +from itertools import permutations +def main(): + result=list(map("".join, permutations('0123456789'))) + print(result[999999]) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/project_euler/Problem 25/sol1.py b/project_euler/Problem 25/sol1.py new file mode 100644 index 000000000000..f8cea3093dcf --- /dev/null +++ b/project_euler/Problem 25/sol1.py @@ -0,0 +1,31 @@ +from __future__ import print_function + +try: + xrange #Python 2 +except NameError: + 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]) + + return sequence[n] + +def fibonacci_digits_index(n): + digits = 0 + index = 2 + + while digits < n: + index += 1 + digits = len(str(fibonacci(index))) + + return index + +if __name__ == '__main__': + print(fibonacci_digits_index(1000)) \ No newline at end of file diff --git a/project_euler/Problem 28/sol1.py b/project_euler/Problem 28/sol1.py new file mode 100644 index 000000000000..4942115ce537 --- /dev/null +++ b/project_euler/Problem 28/sol1.py @@ -0,0 +1,29 @@ +from __future__ import print_function +from math import ceil + +try: + xrange #Python 2 +except NameError: + 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 diff --git a/project_euler/Problem 29/solution.py b/project_euler/Problem 29/solution.py new file mode 100644 index 000000000000..64d35c84d9ca --- /dev/null +++ b/project_euler/Problem 29/solution.py @@ -0,0 +1,33 @@ +def main(): + """ + 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 + + 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)) + + +if __name__ == '__main__': + main() diff --git a/project_euler/Problem 36/sol1.py b/project_euler/Problem 36/sol1.py new file mode 100644 index 000000000000..d78e7e59f210 --- /dev/null +++ b/project_euler/Problem 36/sol1.py @@ -0,0 +1,30 @@ +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. + +(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) + + if n == n[::-1]: + return True + else: + return False + +total = 0 + +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 diff --git a/project_euler/Problem 40/sol1.py b/project_euler/Problem 40/sol1.py new file mode 100644 index 000000000000..ab4017512a1a --- /dev/null +++ b/project_euler/Problem 40/sol1.py @@ -0,0 +1,26 @@ +#-.- coding: latin-1 -.- +from __future__ import print_function +''' +Champernowne's constant +Problem 40 +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. + +d1 × d10 × d100 × d1000 × d10000 × d100000 × d1000000 +''' + +constant = [] +i = 1 + +while len(constant) < 1e6: + constant.append(str(i)) + i += 1 + +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 diff --git a/project_euler/Problem 48/sol1.py b/project_euler/Problem 48/sol1.py new file mode 100644 index 000000000000..5c4bdb0f6384 --- /dev/null +++ b/project_euler/Problem 48/sol1.py @@ -0,0 +1,21 @@ +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 +except NameError: + xrange = range + +total = 0 +for i in xrange(1, 1001): + total += i**i + + +print(str(total)[-10:]) \ No newline at end of file diff --git a/project_euler/Problem 52/sol1.py b/project_euler/Problem 52/sol1.py new file mode 100644 index 000000000000..376b4cfa1d63 --- /dev/null +++ b/project_euler/Problem 52/sol1.py @@ -0,0 +1,23 @@ +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. + +Find the smallest positive integer, x, such that 2x, 3x, 4x, 5x, and 6x, contain the same digits. +''' +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))): + break + + i += 1 + +print(i) \ No newline at end of file diff --git a/project_euler/Problem 53/sol1.py b/project_euler/Problem 53/sol1.py new file mode 100644 index 000000000000..ed6d5329eb4e --- /dev/null +++ b/project_euler/Problem 53/sol1.py @@ -0,0 +1,36 @@ +#-.- coding: latin-1 -.- +from __future__ import print_function +from math import factorial +''' +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 + +In combinatorics, we use the notation, 5C3 = 10. + +In general, + +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? +''' +try: + xrange #Python 2 +except NameError: + xrange = range #Python 3 + +def combinations(n, r): + return factorial(n)/(factorial(r)*factorial(n-r)) + +total = 0 + +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 diff --git a/project_euler/Problem 76/sol1.py b/project_euler/Problem 76/sol1.py new file mode 100644 index 000000000000..2832f6d7afb6 --- /dev/null +++ b/project_euler/Problem 76/sol1.py @@ -0,0 +1,35 @@ +from __future__ import print_function +''' +Counting Summations +Problem 76 + +It is possible to write five as a sum in exactly six different ways: + +4 + 1 +3 + 2 +3 + 1 + 1 +2 + 2 + 1 +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? +''' +try: + xrange #Python 2 +except NameError: + 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 + + 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) + +print(partition(100)) \ No newline at end of file diff --git a/project_euler/README.md b/project_euler/README.md new file mode 100644 index 000000000000..9f77f719f0f1 --- /dev/null +++ b/project_euler/README.md @@ -0,0 +1,58 @@ +# ProjectEuler + +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. + +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? + +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. + +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? + +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? +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! diff --git a/simple_client_server/README.md b/simple_client_server/README.md new file mode 100644 index 000000000000..f51947f2105a --- /dev/null +++ b/simple_client_server/README.md @@ -0,0 +1,6 @@ +# simple client server + +#### Note: +- Run **`server.py`** first. +- Now, run **`client.py`**. +- verify the output. diff --git a/simple_client_server/client.py b/simple_client_server/client.py new file mode 100644 index 000000000000..c91e6233ad16 --- /dev/null +++ b/simple_client_server/client.py @@ -0,0 +1,14 @@ +# client.py + +import socket + +HOST, PORT = '127.0.0.1', 1400 + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect((HOST, PORT)) + +s.send(b'Hello World') +data = s.recv(1024) + +s.close() +print(repr(data.decode('ascii'))) diff --git a/simple_client_server/server.py b/simple_client_server/server.py new file mode 100644 index 000000000000..c6b661d99044 --- /dev/null +++ b/simple_client_server/server.py @@ -0,0 +1,21 @@ +# server.py + +import socket + +HOST, PORT = '127.0.0.1', 1400 + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.bind((HOST, PORT)) +s.listen(1) + +conn, addr = s.accept() + +print('connected to:', addr) + +while 1: + data = conn.recv(1024) + if not data: + break + conn.send(data + b' [ addition by server ]') + +conn.close() diff --git a/tags b/tags new file mode 100644 index 000000000000..46f6330e8afa --- /dev/null +++ b/tags @@ -0,0 +1,1373 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/ +!_TAG_PROGRAM_AUTHOR Universal Ctags Team // +!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/ +!_TAG_PROGRAM_URL https://ctags.io/ /official site/ +!_TAG_PROGRAM_VERSION 0.0.0 /619a6fac/ +- returns a random matrix WxH with integer components between 'a' and 'b' linear_algebra_python/README.md /^ - returns a random matrix WxH with integer components between 'a' and 'b' $/;" s +A data_structures/binary tree/LazySegmentTree.py /^ A = [1,2,-4,7,3,-5,6,11,-20,9,14,15,5,2,-8]$/;" v +A data_structures/binary tree/SegmentTree.py /^ A = [1,2,-4,7,3,-5,6,11,-20,9,14,15,5,2,-8]$/;" v +AVL data_structures/avl.py /^class AVL:$/;" c +AdjacencyList data_structures/graph/graph.py /^class AdjacencyList(object):$/;" c +All algorithms implemented in Python (for education) README.md /^### All algorithms implemented in Python (for education)$/;" S +Array Elements sorts/normal_distribution_QuickSort_README.md /^## Array Elements$/;" s +BFS data_structures/graph/breadth_first_search.py /^ def BFS(self, startVertex):$/;" m class:Graph +BFS networking_flow/Ford_Fulkerson.py /^def BFS(graph, s, t, parent):$/;" f +BFS networking_flow/Minimum_cut.py /^def BFS(graph, s, t, parent):$/;" f +BLOCK_FILENAME_FORMAT sorts/external-sort.py /^ BLOCK_FILENAME_FORMAT = 'block_{0}.dat'$/;" v class:FileSplitter +BPNN neural_network/bpnn.py /^class BPNN():$/;" c +BYTE_SIZE ciphers/rsa_cipher.py /^BYTE_SIZE = 256$/;" v +BellmanFord data_structures/graph/bellman_ford.py /^def BellmanFord(graph, V, E, src):$/;" f +Binary README.md /^### Binary$/;" S +BinarySearchTree data_structures/binary tree/binary_search_tree.py /^class BinarySearchTree:$/;" c +Bubble README.md /^### Bubble$/;" S +Bucket README.md /^### Bucket$/;" S +CNN neural_network/convolution_neural_network.py /^class CNN():$/;" c +Caesar README.md /^### Caesar$/;" S +CeilIndex dynamic_programming/longest_increasing_subsequence_O(nlogn).py /^def CeilIndex(v,l,r,key):$/;" f +Ciphers README.md /^## Ciphers$/;" s +Cocktail shaker README.md /^### Cocktail shaker$/;" S +DEFAULT_BLOCK_SIZE ciphers/rsa_cipher.py /^DEFAULT_BLOCK_SIZE = 128$/;" v +DEFAULT_BUCKET_SIZE sorts/bucket_sort.py /^DEFAULT_BUCKET_SIZE = 5$/;" v +DFS data_structures/graph/depth_first_search.py /^ def DFS(self):$/;" m class:Graph +DFSRec data_structures/graph/depth_first_search.py /^ def DFSRec(self, startVertex, visited):$/;" m class:Graph +DISTANCE searches/test_tabu_search.py /^DISTANCE = 105$/;" v +Decision_Tree machine_learning/decision_tree.py /^class Decision_Tree:$/;" c +DenseLayer neural_network/bpnn.py /^class DenseLayer():$/;" c +Dijkstra data_structures/graph/dijkstra.py /^def Dijkstra(graph, V, src):$/;" f +Documentation linear_algebra_python/README.md /^## Documentation $/;" s +DoubleHash data_structures/hashing/double_hash.py /^class DoubleHash(HashTable):$/;" c +E data_structures/graph/bellman_ford.py /^E = int(raw_input("Enter number of edges: "))$/;" v +E data_structures/graph/dijkstra.py /^E = int(raw_input("Enter number of edges: "))$/;" v +E data_structures/graph/floyd_warshall.py /^E = int(raw_input("Enter number of edges: "))$/;" v +ENGLISH_WORDS other/detecting_english_programmatically.py /^ENGLISH_WORDS = loadDictionary()$/;" v +ETAOIN other/frequency_finder.py /^ETAOIN = 'ETAOINSHRDLCUMWFGYPBVKJXQZ'$/;" v +EditDistance dynamic_programming/edit_distance.py /^class EditDistance:$/;" c +ExternalSort sorts/external-sort.py /^class ExternalSort(object):$/;" c +F dynamic_programming/knapsack.py /^ F = [[0]*(w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)]$/;" v +FIRST_SOLUTION searches/test_tabu_search.py /^FIRST_SOLUTION = ['a', 'c', 'b', 'd', 'e', 'a']$/;" v +FYshuffle other/Fischer-Yates_Shuffle.py /^def FYshuffle(LIST):$/;" f +FenwickTree data_structures/binary tree/FenwickTree.py /^class FenwickTree:$/;" c +Fibonacci dynamic_programming/fibonacci.py /^class Fibonacci:$/;" c +FileMerger sorts/external-sort.py /^class FileMerger(object):$/;" c +FileSplitter sorts/external-sort.py /^class FileSplitter(object):$/;" c +FilesArray sorts/external-sort.py /^class FilesArray(object):$/;" c +FloydWarshall data_structures/graph/floyd_warshall.py /^def FloydWarshall(graph, V):$/;" f +FordFulkerson networking_flow/Ford_Fulkerson.py /^def FordFulkerson(graph, source, sink):$/;" f +Graph data_structures/graph/breadth_first_search.py /^class Graph():$/;" c +Graph data_structures/graph/depth_first_search.py /^class Graph():$/;" c +Graph data_structures/graph/dijkstra_algorithm.py /^class Graph:$/;" c +Graph data_structures/graph/graph_list.py /^class Graph:$/;" c +Graph data_structures/graph/graph_matrix.py /^class Graph:$/;" c +Graph dynamic_programming/floyd_warshall.py /^class Graph:$/;" c +HOST simple_client_server/client.py /^HOST, PORT = '127.0.0.1', 1400$/;" v +HOST simple_client_server/server.py /^HOST, PORT = '127.0.0.1', 1400$/;" v +HashTable data_structures/hashing/hash_table.py /^class HashTable:$/;" c +HashTableWithLinkedList data_structures/hashing/hash_table_with_linked_list.py /^class HashTableWithLinkedList(HashTable):$/;" c +Heap README.md /^### Heap$/;" S +Heap data_structures/heap/heap.py /^class Heap:$/;" c +InPreOrder data_structures/binary tree/binary_search_tree.py /^def InPreOrder(curr_node):$/;" f +Insertion README.md /^### Insertion$/;" S +Interpolation README.md /^## Interpolation$/;" s +Jump Search README.md /^## Jump Search$/;" s +K hashes/chaos_machine.py /^K = [0.33, 0.44, 0.55, 0.44, 0.33]; t = 3; m = 5$/;" v +L arithmetic_analysis/lu_decomposition.py /^L,U = LUDecompose(matrix)$/;" v +LEARNING_RATE machine_learning/gradient_descent.py /^LEARNING_RATE = 0.009$/;" v +LETTERS ciphers/simple_substitution_cipher.py /^LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'$/;" v +LETTERS ciphers/vigenere_cipher.py /^LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'$/;" v +LETTERS other/frequency_finder.py /^LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'$/;" v +LETTERS_AND_SPACE other/detecting_english_programmatically.py /^LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + ' \\t\\n'$/;" v +LUDecompose arithmetic_analysis/lu_decomposition.py /^def LUDecompose (table):$/;" f +Linear README.md /^### Linear$/;" S +Linear algebra library for Python linear_algebra_python/README.md /^# Linear algebra library for Python $/;" c +LinearCongruentialGenerator other/LinearCongruentialGenerator.py /^class LinearCongruentialGenerator(object):$/;" c +Link data_structures/linked_list/DoublyLinkedList.py /^class Link:$/;" c +LinkedList data_structures/linked_list/DoublyLinkedList.py /^class LinkedList:$/;" c +LinkedList data_structures/linked_list/__init__.py /^class LinkedList:$/;" c +Linked_List data_structures/linked_list/singly_LinkedList.py /^class Linked_List:$/;" c +LongestIncreasingSubsequenceLength dynamic_programming/longest_increasing_subsequence_O(nlogn).py /^def LongestIncreasingSubsequenceLength(v):$/;" f +M sorts/random_normaldistribution_quicksort.py /^M = np.load(outfile)$/;" v +MF_knapsack dynamic_programming/knapsack.py /^def MF_knapsack(i,wt,val,j):$/;" f +Matrix linear_algebra_python/src/lib.py /^class Matrix(object):$/;" c +MatrixChainOrder dynamic_programming/matrix_chain_order.py /^def MatrixChainOrder(array):$/;" f +Merge README.md /^### Merge$/;" S +N data_structures/binary tree/LazySegmentTree.py /^ N = 15$/;" v +N data_structures/binary tree/SegmentTree.py /^ N = 15$/;" v +N project_euler/Problem 09/sol2.py /^N = int(raw_input())$/;" v +NEIGHBOURHOOD_OF_SOLUTIONS searches/test_tabu_search.py /^NEIGHBOURHOOD_OF_SOLUTIONS = [['a', 'e', 'b', 'd', 'c', 'a', 90],$/;" v +NEIGHBOURS_DICT searches/test_tabu_search.py /^NEIGHBOURS_DICT = {'a': [['b', '20'], ['c', '18'], ['d', '22'], ['e', '26']],$/;" v +NWayMerge sorts/external-sort.py /^class NWayMerge(object):$/;" c +NewtonRaphson arithmetic_analysis/newton_raphson_method.py /^def NewtonRaphson(func, a):$/;" f +Node data_structures/avl.py /^class Node:$/;" c +Node data_structures/binary tree/binary_search_tree.py /^class Node:$/;" c +Node data_structures/linked_list/__init__.py /^class Node:$/;" c +Node data_structures/linked_list/singly_LinkedList.py /^class Node: # create a Node$/;" c +Normal Distribution QuickSort sorts/normal_distribution_QuickSort_README.md /^# Normal Distribution QuickSort$/;" c +Note: simple_client_server/README.md /^#### Note:$/;" t +Onepad ciphers/onepad_cipher.py /^class Onepad:$/;" c +Overview linear_algebra_python/README.md /^## Overview $/;" s +PORT simple_client_server/client.py /^HOST, PORT = '127.0.0.1', 1400$/;" v +PORT simple_client_server/server.py /^HOST, PORT = '127.0.0.1', 1400$/;" v +PROGNAME other/sierpinski_triangle.py /^PROGNAME = 'Sierpinski Triangle'$/;" v +Perceptron machine_learning/perceptron.py /^class Perceptron:$/;" c +Perceptron neural_network/perceptron.py /^class Perceptron:$/;" c +Plotting the function for Checking 'The Number of Comparisons' taking place between Normal Distribution QuickSort and Ordinary QuickSort sorts/normal_distribution_QuickSort_README.md /^## Plotting the function for Checking 'The Number of Comparisons' taking place between Normal Di/;" s +PrimsAlgorithm graphs/MinimumSpanningTree_Prims.py /^def PrimsAlgorithm(l):$/;" f +PrintOptimalSolution dynamic_programming/matrix_chain_order.py /^def PrintOptimalSolution(OptimalSolution,i,j):$/;" f +PriorityQueue data_structures/graph/dijkstra_algorithm.py /^class PriorityQueue:$/;" c +PriorityQueue graphs/Multi_Hueristic_Astar.py /^class PriorityQueue:$/;" c +ProjectEuler project_euler/README.md /^# ProjectEuler$/;" c +QuadraticProbing data_structures/hashing/__init__.py /^class QuadraticProbing(HashTable):$/;" c +QuadraticProbing data_structures/hashing/quadratic_probing.py /^class QuadraticProbing(HashTable):$/;" c +Queue data_structures/queue/QueueOnList.py /^class Queue():$/;" c +Queue data_structures/queue/QueueOnPseudoStack.py /^class Queue():$/;" c +Quick README.md /^### Quick$/;" S +Quick Select README.md /^## Quick Select$/;" s +ROT13 README.md /^## ROT13$/;" s +RSA (Rivest–Shamir–Adleman) README.md /^### RSA (Rivest–Shamir–Adleman)$/;" S +Radix README.md /^### Radix$/;" S +ReadModel neural_network/convolution_neural_network.py /^ def ReadModel(cls,model_path):$/;" m class:CNN +ReceiveFile file_transfer_protocol/ftp_send_receive.py /^def ReceiveFile():$/;" f +Representational analysis/Compression_Analysis/PSNR.py /^def Representational(r,g,b):$/;" f +S data_structures/stacks/Stock-Span-Problem.py /^S = [0 for i in range(len(price)+1)] $/;" v +S1 dynamic_programming/edit_distance.py /^ S1 = raw_input().strip()$/;" v +S2 dynamic_programming/edit_distance.py /^ S2 = raw_input().strip()$/;" v +SHA1Hash hashes/sha1.py /^class SHA1Hash:$/;" c +SHA1HashTest hashes/sha1.py /^class SHA1HashTest(unittest.TestCase):$/;" c +SOE other/FindingPrimes.py /^def SOE(n):$/;" f +SYMBOLS ciphers/affine_cipher.py /^SYMBOLS = """ !"#$%&'()*+,-.\/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnop/;" v +Search Algorithms README.md /^## Search Algorithms$/;" s +SegmentTree data_structures/binary tree/LazySegmentTree.py /^class SegmentTree:$/;" c +SegmentTree data_structures/binary tree/SegmentTree.py /^class SegmentTree:$/;" c +Selection README.md /^### Selection$/;" S +SendFile file_transfer_protocol/ftp_send_receive.py /^def SendFile():$/;" f +Shell README.md /^### Shell$/;" S +Sort Algorithms README.md /^## Sort Algorithms$/;" s +Source: [Wikipedia](https://en.wikipedia.org/wiki/Caesar_cipher) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/Caesar_cipher)$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/Interpolation_search) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/Interpolation_search)$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/Jump_search) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/Jump_search)$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/Quickselect) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/Quickselect)$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/ROT13) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/ROT13)$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/RSA_(cryptosystem)) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/RSA_(cryptosystem))$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/Tabu_search) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/Tabu_search)$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/Transposition_cipher) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/Transposition_cipher)$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/Vigen%C3%A8re_cipher)$/;" u +Source: [Wikipedia](https://en.wikipedia.org/wiki/XOR_cipher) README.md /^###### Source: [Wikipedia](https:\/\/en.wikipedia.org\/wiki\/XOR_cipher)$/;" u +Stack data_structures/stacks/__init__.py /^class Stack:$/;" c +Stack data_structures/stacks/stack.py /^class Stack(object):$/;" c +StackOverflowError data_structures/stacks/stack.py /^class StackOverflowError(BaseException):$/;" c +SubArray dynamic_programming/longest_sub_array.py /^class SubArray:$/;" c +T project_euler/Problem 02/sol2.py /^T = int(input().strip())$/;" v +TAG machine_learning/k_means_clust.py /^TAG = 'K-MEANS-CLUST\/ '$/;" v +TEST_FILE searches/test_tabu_search.py /^TEST_FILE = os.path.join(os.path.dirname(__file__), '.\/tabuTestData.txt')$/;" v +TFKMeansCluster dynamic_programming/k_means_clustering_tensorflow.py /^def TFKMeansCluster(vectors, noofclusters):$/;" f +Tabu README.md /^## Tabu$/;" s +Test linear_algebra_python/src/tests.py /^class Test(unittest.TestCase):$/;" c +TestClass searches/test_tabu_search.py /^class TestClass(unittest.TestCase):$/;" c +TestUnionFind data_structures/union_find/tests_union_find.py /^class TestUnionFind(unittest.TestCase):$/;" c +Tests linear_algebra_python/README.md /^## Tests $/;" s +The Algorithms - Python README.md /^# The Algorithms - Python README.md /^# The Algorithms - Python README.md /^# The Algorithms - Python README.md /^# The Algorithms - Python 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 + 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 + 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): @@ -57,10 +58,10 @@ def delete(self, x): 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 + 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): current.displayLink() @@ -68,8 +69,8 @@ def display(self): #Prints contents of the list 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): From 506172279a15d4fac19b11a38e31ff9c21d24d44 Mon Sep 17 00:00:00 2001 From: Shivam Arora Date: Wed, 31 Oct 2018 15:17:11 +0530 Subject: [PATCH 0226/2908] Addition and multiplication algorithm of two square matrix --- matrix/matrix_multiplication_addition.py | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 matrix/matrix_multiplication_addition.py diff --git a/matrix/matrix_multiplication_addition.py b/matrix/matrix_multiplication_addition.py new file mode 100644 index 000000000000..c387c43d4a85 --- /dev/null +++ b/matrix/matrix_multiplication_addition.py @@ -0,0 +1,36 @@ +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 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 main(): + matrix_a = [[12, 10], [3, 9]] + matrix_b = [[3, 4], [7, 4]] + print(add(matrix_a, matrix_b)) + print(multiply(matrix_a, matrix_b)) + + +if __name__ == '__main__': + main() From 1a5df6bc466c05be4a271e93e8bf4687ab9e62e6 Mon Sep 17 00:00:00 2001 From: Shivam Arora Date: Wed, 31 Oct 2018 16:14:30 +0530 Subject: [PATCH 0227/2908] Added the method to find the isolated nodes in the graph --- graphs/basic_graphs.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index c9a269f1ab3f..3b3abeb1720d 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -1,14 +1,14 @@ from __future__ import print_function try: - raw_input # Python 2 + raw_input # Python 2 except NameError: raw_input = input # Python 3 try: - xrange # Python 2 + xrange # Python 2 except NameError: - xrange = range # Python 3 + xrange = range # Python 3 # Accept No. of Nodes and edges n, m = map(int, raw_input().split(" ")) @@ -141,7 +141,7 @@ def dijk(G, s): def topo(G, ind=None, Q=[1]): if ind is None: - ind = [0] * (len(G) + 1) # SInce oth Index is ignored + ind = [0] * (len(G) + 1) # SInce oth Index is ignored for u in G: for v in G[u]: ind[v] += 1 @@ -279,3 +279,12 @@ def krusk(E_and_n): s[j].update(s[i]) s.pop(i) break + + +# find the isolated node in the graph +def find_isolated_nodes(graph): + isolated = [] + for node in graph: + if not graph[node]: + isolated.append(node) + return isolated From 11d0d641adf32a7e976bf9df8c4dc9ba19bba3b4 Mon Sep 17 00:00:00 2001 From: Shivam Arora Date: Wed, 31 Oct 2018 17:28:49 +0530 Subject: [PATCH 0228/2908] Binary graph algorithms to find height of binary tree and to check whether the given binary tree is full binary or not --- binary_tree/basic_binary_tree.py | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 binary_tree/basic_binary_tree.py diff --git a/binary_tree/basic_binary_tree.py b/binary_tree/basic_binary_tree.py new file mode 100644 index 000000000000..6cdeb1a6938c --- /dev/null +++ b/binary_tree/basic_binary_tree.py @@ -0,0 +1,47 @@ +class Node: + def __init__(self, data): + self.data = data + self.left = None + self.right = None + + +def depth_of_tree(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 is_full_binary_tree(tree): + 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)) + else: + return False + + +def main(): + tree = Node(1) + tree.left = Node(2) + tree.right = Node(3) + tree.left.left = Node(4) + tree.left.right = Node(5) + tree.left.right.left = Node(6) + tree.right.left = Node(7) + tree.right.left.left = Node(8) + tree.right.left.left.right = Node(9) + + print(is_full_binary_tree(tree)) + print(depth_of_tree(tree)) + + +if __name__ == '__main__': + main() From 32db8be93e2821900917daec200a0c914b7cc00c Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Thu, 1 Nov 2018 19:25:28 +0530 Subject: [PATCH 0229/2908] Update FindMax.py (#588) Issues in previous code: `Line 3` math module is not used so we don't have to import it `Line 5`, `max = 0` will give wrong answer (0) when the list contains only -ve elements, as 0 will always be larger than the elements. --- Maths/FindMax.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Maths/FindMax.py b/Maths/FindMax.py index 451ba03634a2..0ce49a68c348 100644 --- a/Maths/FindMax.py +++ b/Maths/FindMax.py @@ -1,8 +1,7 @@ # NguyenU -import math def find_max(nums): - max = 0 + max = nums[0] for x in nums: if x > max: max = x From 48aa4c4a01982159c7c5084f9090505cff92905e Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Thu, 1 Nov 2018 21:04:05 +0530 Subject: [PATCH 0230/2908] Update singly_linked_list.py --- .../linked_list/singly_linked_list.py | 105 +++++++++++------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 0d6157e3eb65..a19e05fe90a3 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -8,53 +8,53 @@ def __init__(self, data): class Linked_List: - def insert_tail(Head, data): - if Head.next is None: - Head.next = Node(data) + def __init__(self): + self.Head = None # Initialize Head to None + + def insert_tail(self, data): + if(self.Head == None): self.insert_head(data) #If this is first node, call insert_head else: - Head.next.insert_tail(data) + 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(Head, data): - tamp = Head - if tamp is None: - newNod = Node() # create a new Node - newNod.data = data - newNod.next = None - Head = newNod # make new node to Head - else: - newNod = Node() - newNod.data = data - newNod.next = Head # put the Head at NewNode Next - Head = newNod # make a NewNode to Head - return Head + 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 - def printList(Head): # print every node data - tamp = Head + def printList(self): # print every node data + tamp = self.Head while tamp is not None: print(tamp.data) tamp = tamp.next - def delete_head(Head): # delete from head - if Head is not None: - Head = Head.next - return Head # return new Head - - def delete_tail(Head): # delete from tail - if Head is not None: - tamp = Node() - tamp = Head - while tamp.next.next is not None: # find the 2nd last element - tamp = tamp.next - # delete the last element by give next None to 2nd last Element - tamp.next = None - return Head + def delete_head(self): # delete from head + 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 + 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 + return tamp - def isEmpty(Head): - return Head is None # Return if Head is none + def isEmpty(self): + return self.Head == None # Return if Head is none - def reverse(Head): + def reverse(self): prev = None - current = Head + current = self.Head while current: # Store the current node's next node. @@ -66,5 +66,32 @@ def reverse(Head): # Make the current node the next node (to progress iteration) current = next_node # Return prev in order to put the head at the end - Head = prev - return Head + self.Head = prev + +def main(): + A = Linked_List() + print("Inserting 10 at Head") + A.insert_head(10) + print("Inserting 0 at Head") + A.insert_head(0) + print("\nPrint List : ") + A.printList() + print("\nInserting 100 at Tail") + A.insert_tail(100) + print("Inserting 1000 at Tail") + A.insert_tail(1000) + print("\nPrint List : ") + A.printList() + print("\nDelete Head") + A.delete_head() + print("Delete Tail") + A.delete_tail() + print("\nPrint List : ") + A.printList() + print("\nReverse Linked List") + A.reverse() + print("\nPrint List : ") + A.printList() + +if __name__ == '__main__': + main() From f89d3a9ec3f4686b22251c8e05e02773727c3937 Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Thu, 1 Nov 2018 21:25:45 +0530 Subject: [PATCH 0231/2908] Update singly_linked_list.py --- data_structures/linked_list/singly_linked_list.py | 4 ++-- 1 file 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 a19e05fe90a3..0b9e44768e15 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -12,7 +12,7 @@ def __init__(self): self.Head = None # Initialize Head to None def insert_tail(self, data): - if(self.Head == 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 @@ -50,7 +50,7 @@ def delete_tail(self): # delete from tail return tamp def isEmpty(self): - return self.Head == None # Return if Head is none + return self.Head is None # Return if Head is none def reverse(self): prev = None From a834326e0ea14e854acb67974b4ee8f7c9cf4417 Mon Sep 17 00:00:00 2001 From: gerroo Date: Sat, 3 Nov 2018 12:08:13 -0800 Subject: [PATCH 0232/2908] Added b16, b32, a85, abs, absMax, absMin --- Maths/3n+1.py | 7 ++++--- Maths/abs.py | 18 ++++++++++++++++++ Maths/absMax.py | 22 ++++++++++++++++++++++ Maths/absMin.py | 20 ++++++++++++++++++++ ciphers/base16.py | 11 +++++++++++ ciphers/base32.py | 11 +++++++++++ ciphers/base85.py | 11 +++++++++++ data_structures/arrays.py | 2 +- maths/fibonacci_sequence_recursion.py | 7 +++++-- 9 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 Maths/abs.py create mode 100644 Maths/absMax.py create mode 100644 Maths/absMin.py create mode 100644 ciphers/base16.py create mode 100644 ciphers/base32.py create mode 100644 ciphers/base85.py diff --git a/Maths/3n+1.py b/Maths/3n+1.py index 894820ec5479..1f2fb55d55c8 100644 --- a/Maths/3n+1.py +++ b/Maths/3n+1.py @@ -9,10 +9,11 @@ def n31(a):# a = initial number a = 3*a +1 c += 1#counter l += [a] - print(a)#optional print - print("It took {0} steps.".format(c))#optional finish + return l , c print(n31(43)) - + print(n31(98)[0][-1])# = a + print("It took {0} steps.".format(n31(13))[1]))#optional finish + if __name__ == '__main__': main() diff --git a/Maths/abs.py b/Maths/abs.py new file mode 100644 index 000000000000..5b758f8389b4 --- /dev/null +++ b/Maths/abs.py @@ -0,0 +1,18 @@ +def absVal(num): + """ + Function to fins absolute value of numbers. + >>>absVal(-5) + 5 + >>>absVal(0) + 0 + """ + if num < 0: + return -num + else: + return num + +def main(): + print(absVal(-34)) # = 34 + +if __name__ == '__main__': + main() diff --git a/Maths/absMax.py b/Maths/absMax.py new file mode 100644 index 000000000000..432734ec02c0 --- /dev/null +++ b/Maths/absMax.py @@ -0,0 +1,22 @@ +from abs import absVal +def absMax(x): + """ + >>>absMax([0,5,1,11]) + 11 + >>absMax([3,-10,-2]) + -10 + """ + j = x[0] + for i in x: + if absVal(i) < j: + j = i + return j + #BUG: i is apparently a list, TypeError: '<' not supported between instances of 'list' and 'int' in absVal + + +def main(): + a = [1,2,-11] + print(absVal(a)) # = -11 + +if __name__ == '__main__': + main() diff --git a/Maths/absMin.py b/Maths/absMin.py new file mode 100644 index 000000000000..8047d63acb52 --- /dev/null +++ b/Maths/absMin.py @@ -0,0 +1,20 @@ +from abs import absVal +def absMin(x): + """ + >>>absMin([0,5,1,11]) + 0 + >>absMin([3,-10,-2]) + -2 + """ + j = x[0] + for i in x: + if absVal(i) < j: + j = i + return j + +def main(): + a = [1,2,-11] + print(absMin(a)) # = 1 + +if __name__ == '__main__': + main() diff --git a/ciphers/base16.py b/ciphers/base16.py new file mode 100644 index 000000000000..9bc0e5d8337a --- /dev/null +++ b/ciphers/base16.py @@ -0,0 +1,11 @@ +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 + print(b16encoded) + print(base64.b16decode(b16encoded).decode('utf-8'))#decoded it + +if __name__ == '__main__': + main() diff --git a/ciphers/base32.py b/ciphers/base32.py new file mode 100644 index 000000000000..2ac29f441e94 --- /dev/null +++ b/ciphers/base32.py @@ -0,0 +1,11 @@ +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 + print(b32encoded) + print(base64.b32decode(b32encoded).decode('utf-8'))#decoded it + +if __name__ == '__main__': + main() diff --git a/ciphers/base85.py b/ciphers/base85.py new file mode 100644 index 000000000000..5fd13837f662 --- /dev/null +++ b/ciphers/base85.py @@ -0,0 +1,11 @@ +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 + print(a85encoded) + print(base64.a85decode(a85encoded).decode('utf-8'))#decoded it + +if __name__ == '__main__': + main() diff --git a/data_structures/arrays.py b/data_structures/arrays.py index 3ec9f8976673..feb061013556 100644 --- a/data_structures/arrays.py +++ b/data_structures/arrays.py @@ -1,3 +1,3 @@ arr = [10, 20, 30, 40] -arr[1] = 30 +arr[1] = 30 # set element 1 (20) of array to 30 print(arr) diff --git a/maths/fibonacci_sequence_recursion.py b/maths/fibonacci_sequence_recursion.py index b0b10fd07262..9190e7fc7a40 100644 --- a/maths/fibonacci_sequence_recursion.py +++ b/maths/fibonacci_sequence_recursion.py @@ -1,8 +1,11 @@ # Fibonacci Sequence Using Recursion def recur_fibo(n): - return n if n <= 1 else (recur_fibo(n-1) + recur_fibo(n-2)) - + if n <= 1: + return n + else: + (recur_fibo(n-1) + recur_fibo(n-2)) + def isPositiveInteger(limit): return limit >= 0 From 14b5ac625bd676518997ec4d7073ac01efdcfbb8 Mon Sep 17 00:00:00 2001 From: Harshil Date: Sun, 4 Nov 2018 15:54:17 +0100 Subject: [PATCH 0233/2908] Update 3n+1.py Removed SyntaxError on Line 16! --- Maths/3n+1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maths/3n+1.py b/Maths/3n+1.py index 1f2fb55d55c8..6424fe0d8f15 100644 --- a/Maths/3n+1.py +++ b/Maths/3n+1.py @@ -13,7 +13,7 @@ def n31(a):# a = initial number return l , c print(n31(43)) print(n31(98)[0][-1])# = a - print("It took {0} steps.".format(n31(13))[1]))#optional finish + print("It took {0} steps.".format(n31(13)[1]))#optional finish if __name__ == '__main__': main() From 39912aed5781faa77e2c85e4fb79acd7170cabda Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Mon, 5 Nov 2018 08:31:00 +0000 Subject: [PATCH 0234/2908] Update max_sub_array.py (#597) --- dynamic_programming/max_sub_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 58711f22ce90..5d48882427c0 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -50,7 +50,7 @@ def find_max_cross_sum(A,low,mid,high): tim.append(end-strt) print("No of Inputs Time Taken") for i in range(len(inputs)): - print((inputs[i],'\t\t',tim[i])) + print(inputs[i],'\t\t',tim[i]) plt.plot(inputs,tim) plt.xlabel("Number of Inputs");plt.ylabel("Time taken in seconds ") plt.show() From beafe3656fed73a0adff4cd9ad3dfd5c0b1d294b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Garc=C3=ADa=20Cu=C3=A9llar?= Date: Mon, 5 Nov 2018 18:19:08 +0100 Subject: [PATCH 0235/2908] Re-design psnr.py code and change image names (#592) * Change some Image File names & re-code the psnr algorithm (conserving both methods). Also Added new example. * Selected psnr method and reformat some code from arithmetic_analysis --- .../PSNR-example-base.png | Bin 0 -> 4515013 bytes .../PSNR-example-comp-10.jpg | Bin 0 -> 106150 bytes .../example_wikipedia_image.jpg | Bin 0 -> 486981 bytes .../{orignal_image.png => original_image.png} | Bin analysis/compression_analysis/psnr.py | 53 +++++++++--------- arithmetic_analysis/bisection.py | 6 +- arithmetic_analysis/intersection.py | 7 ++- arithmetic_analysis/lu_decomposition.py | 24 ++++---- arithmetic_analysis/newton_method.py | 11 ++-- data_structures/graph/dijkstra.py | 2 +- 10 files changed, 53 insertions(+), 50 deletions(-) create mode 100644 analysis/compression_analysis/PSNR-example-base.png create mode 100644 analysis/compression_analysis/PSNR-example-comp-10.jpg create mode 100644 analysis/compression_analysis/example_wikipedia_image.jpg rename analysis/compression_analysis/{orignal_image.png => original_image.png} (100%) diff --git a/analysis/compression_analysis/PSNR-example-base.png b/analysis/compression_analysis/PSNR-example-base.png new file mode 100644 index 0000000000000000000000000000000000000000..bf12a5450b96e17c239dab14ebed66b201eddb7c GIT binary patch literal 4515013 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 z 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%0DL@9ZY#9Dx*&hU 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 @8X@9Lt}Ju`qNf6oGx+d}mg$PB=b1CJmchh@ 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

XzKvz~^4K920>@am~zNZ?%QZuT&b^(|?+b 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 z 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 z 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

jx(hv`o!n0 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­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 F&{AX6#FMHpr2)32nqPe2NYPI9#>yI%m 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 DSfge+8gf|l`-ejBEcs}7Hx+*K>OC)R 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 @YR&4jZo{d~dIR>H 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

r7)Z&O&XB^vr0AbEt?1lthI9OVH)@G^L!? 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%0D@8~XPD_8cK43(Yr;&yt;{0qjV?JxJ1_osCWvy%~ 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

QA)EcAL?0#w=MP2~3qKs~UO9m=cnvn@q3u z99dPw^TMAxAX+|CZKzb{b6|Bc=sbwhYGW5}hAc}1xe*Dd$acO`o7fw9LRE{`XHnN4 zyKG_+ex9>e2Nkjo8|MFSN(pkpmjd2=Aq7M9*dJ;`m8gkE3nfMv2hNW_Q8@F%<0pRn z;d{=fXJWZ>EhE_}q3uvSlO!ewlL{$ob>z(jhmtkfGgTGc9>IIDY$n9if14*^Sp?LL zNU6(#sj3|U4lf=J9ZlEK5^+K3+lE6Q`09SoS8v|%@Nmc7n>YOOZOi?^6NrrGfs_&_ zCyZkINTzS4+xlWOfkI!7L7p~`% ziOFK36iCjKvCO10M*3j+R*EB)!Wa`nOpIybbQ!Hl^L0R(wPr}Jc()v{c;8mlkxa-0x`Hzz|2K5=mspBi9sYc1=({`G5cuNld$h|q zg&;fj_YXA3mb=FBn>TNWdEke~4?I4fXu1Pmef=AbP0wM!C-}yYS&ev;V=i#m~ zJgyT@X&j*slquq4;jrs*T^*pu%oH=Ocy?}wZv(AUn*BZdHt@w4Uve4}p*Y_49dWwi zHFMXs9Crr}yE}-9m*>QDc_NLYsjIW)%D0D}r_+f~FDH_9R=7sUNf=}1Szq{v@BYkv zFZ}80nVbuU`+FWA&rFvKox7Nr&)1xIse{<8H9Jtqr6p%!%CRc8tr+)GqYl&>j`_V@ zs;b?3dD}E8;6osUmXwuP(Q-K_BkE{gM2z!0UP~S1I2Lh8-?q?r)SKr>awdd;>l?zZ zBlyg49v~&g;lkzm!jw{7J_x=@&8eQV1fAWJQc=#{SL1bc)On#r=yyHE+t?>i2uw6$ zL+lt5BdIR8l-5}TJ7yRu?DND<6{i!;5%x_-(atcBvmj5T3K;EG+5{WEJ7C0d8is{} z)Zhq32%>a8(>5)~{d>leEH_f^diB1cbDl^+(!iV8^WpkHKECC0Im7vpGL3vZM?Ou7 z|K(r)gMav6|2O{jfBup#Kqm2=C;EQR;rNF8{w;T3wnPMJ8nO#y*RX3kdhckx;u^=} z>4hJ@{}YebE0?DyoMg)b7Q>;r>ZG-IzoFcm$1>3t0jt#}-UYljCra1v`I|StRL(u9qjSL4O}C?O zq{clo^`5uEnlBKXEbazs*7j06)Ouq~hVFD;NGV!QWUhl>Ul##IH?GIkIk||$o!jKD ztpqM{D|%ID%)cyY(sjU`SE5oKD$ji!i@V+yW4A{64dT~rcDejK2JJ8AuBwo{5iyp< zNm+jen;trvw&(8d$onsU#l!tQpB{fA{`p%j=MyB*ponjb-Vtqhr8(2q#dHzt@tmx# zW33C5g$lJ1#BParY+cXu88knLQZ4ZM|D8Ac`a0+)>sdF7bltd9Z|cx_F%e9RJr7b0 zKq0GOb+go$+V^gu__GVTT9Vg3eiH<+h&goQs+Tp(d5Nj2NKiLtbk~JI*&v9;%VMaX z#q^6|p_J<4cT2rmGWGs9x4+h*k@t>LBB|Iy+|T#+yr9Sh%{B6^Qi5z5Z1;Dr)HmJQ z0aWq1_JrmStz@{0^VU)Ynvm3RnK$BM?>I_8aGdsg@?p2c3XPXD-d*s1B4(St*w5*! zi_+@Rvbe6+$brS-yS?75QHM9qXW9Ntn|+twCRmGet2tvM05{jvJR33hy4Hofmu(cr z^17}Obh-#B*x<3loHZ}jhge^%i|ch=?APgy-lD_xCb0T78hRB6Ehd~lXV8)U`?y}cZ#)rGIivY+MIva@ntfRv38+%A;S9I7W*#f&8t zTe{3-s&i5rWsDOMn-iMQ0Cr7J*SBOc(sgvlJ>mHot`lB`?40%bv!LRY6MK#{alY0u z6-;<@wy3-&ey`X6)xyx#Yq6c3NX7qq?<-n-JwxY6p?N;TG6UIWde7P8+k9|ci~ww1 z(RC(R77CwnKxc=x<-op@9>^^MeIcMyiJG6au5X;@D@0(kK%bEpZux8?x_P&4#UY!8 z=M8k>lk)OYDmwdeWTQom6qJTLX(#=$c%{^I|#YfrVVX8=a zb=hyD3brSUeYW{bb+gb7#T6E7k0=`<#5$9-F-jsk`+KDt^+cqK4p~G=%yBS_=tb6y z_F3FEf6tmXVhjCQg2iHP(zr!9LXZf-KUg9xi(LHIU*UayWtZ5aQA|RP0YqWvrJcC?w7Rr`&(%J`Daq)|V!t9{bCaE_?P*?x}}M=Zt6Fycsp#gdg|_mop&fvBFgX;7bt z<3Ji6N-{G+T~x2%=q+45t^)&(BQPGdT`Oi3G{C z-m<+X=SaRqW3tZ-MCTDvo5wj@k;IbibEAwHDM81PGEH3c1JX7WaZDu>f=8r*#^K0_ z3pCx4tyIn_9DSRmjQ&RGj@Hz7T@bzX*k|f!6=67{5Gv6X2Gi&Rr zW;dEc3`h=z5A*+j!n|-qW)Tbsu+dvBS(zCTzL=SA^Kezo%(Du7Xn~-+Dl@{}7QOeJ z^PR!;41>q^17<(axei+s>K3P@3S)8B(fJ;S;r#KL+1x1I5o0~W(>wNWzvA7OU*Vl6 zhCqtB&H#F!Y88SSVe)Oe z;v6tB;*8-so_W2z5G9gikM)Jl)o^*#z(WkF>S!~`CY(6D6}n18lukS3IWs@qdt`ldSF*Is*pY>2j@>(t2_g1TJv zETt6zHfM0Q4yPbFQc^~W3JYzR+YW-FCl$Q4RWMys53Mf;AawfomaM<`D%{^MI4MP4 zm|2Iq_LWHT(7{MmD$J~xWF z|8He8vIz^4ckLM}^^my^__Y>(xlc;%;+ezDT-e)6G%S3zmrWg6TXaHQ^E9W17f^jCzA2A}_rjg5aM2zKn9?7xrdcHBG!Zm=sdQwO{ z&w(I4Q%;oJF-=!qZ<$Z8uMn=75OJm>8IN>*74v3GVHz}x}GGuaO!)DxJ2jSbU3i{J9c(pH+1awo_?@kEpsZ&u`s`0nJ#Bt zR2C>$yc=+0=uBbfJdeYUH^&3U_Z08RVu>Y@#1T&*r$-KAn8$$go`?N`uO6TH`1FFT8yEfd{iA8;7_7J2=RR@fL`Tyj*X5IKT44ho6~Z!23Z>S*5TB zV`?rdWr{dDWA*1Q!d?vT_6MF$C+yI3O$k>l&vPbrk%R9z^gUnnj_*zbCv)OFX;)kd zfiTX5al+VwvwA2CIUp3KFyVsb!~yFp&N&cUX)n-^THg~xBF>5H;$`G^ z9l3_UIM4V}IPLblIUYFlM|LLBxt>c1yga`rjAtIY9Z$OxZy%p{zD0ic@n_Dj7s51B zaw3FDO%Q#@<$C2ZP9#yINk8m~2y;waUo-#h|Ne=WpTFnun{Rph_A6dxV3d!1y!^oB ze5NblBqOE}rEpENx>~cPOe)NK?}#bY#YQ5=w4C>>*t&$Um1+@5C944KEJJ7U)?&?o zlSCq|3YqDAArp?Z_GCam4NR7!ehHD|#%ZxTIcE4unxj2}X2?i$R#9V);zb^Y)?Z zu`=NMp5E$W>Jlezw~6U;fv7m%k_@DouD6u#h@U&s^ThQzK#9zkh?ysTdcE-HIPnLk zRXApv8L}{7Ihu~%dhD*J^9JWY9ApCCK!;#0%mSud`0)A>cX?$OBFVN?EJ!vBjYyZc zvh>L!ZT>LiEQAP`Tj0$x;`|=p4cLAl`NHY66S8A#CYe8s@#r8dmexiFSgyYAVsXh5_RoDMU(2EvU^_pIeNqo64=Fj^WO1P=%DOIwE>vwhO-b-xlS6jkdSW z8D%x0O8F(ASep5wp53j%I9l>H6V%|+9beU?}H+A-|R6o-?zpFTF4a9vZUs$n8moZRI64~g{`w=o71Oxlw=uP%*MKxYTE;w0vhH zd*o%I{5f>K?Jd~U2d*9KTII3a!*X8(u=Q&u8_ zF|r|LtR_H9#%=s$sfJFZCP4Kw(lcBsYREE017%VX%5y0dt*`fzku}}XcJAvt-+V?w zScxBZz4rAbC1rh1+Kpi)*Y7e>2u;K;mi7c#Xp}{%qPf;TCOD?w#R;;+U>SubBxLu8G(m|JM6QAlgJr-sK`NGL|#uVTS_Tkr3@ ze7E`zan+o((q3eJ#8Q&(u1x!!B!j8MqgH3LIpHo&8XWE>~LNhSrwFbB$^I5$E zwiHZJ+Rb91sMoIKbZ-_bm4IrLe5Iy7O~OY5F=s*)<_NC$xUS<^A_b4NJ-e<)#BzDL z@_i{d6M1-hqJQ%>yZw%N4osb6v;)QW%*HX>^|@o~bs<%3HkA+&b5eXbhOU~2GtfK7 z-YVt8I9F#Tvpj#5G*|VRW;||$8WK4bOmVf!DC62ZBXypw|6VFlg8+cZ8DB|z*$I=a z;(N(;4%%W&r0ND{sWe$HhN_Ef-k~jGRdTKd2D=z(8ewDE6*pbYnB^VV{ynLtH;_eu zEmdbpNwt|)Gtgs}`Dq+4S~cd>obRk9#mI7%6v0YIkt&E1kupK2q_>;QSQ~+nSPYl* zg+Dz%^YZe{G+lL1Ed_(7(~VdxN~t0x?ewkzzxJp~wc6A=i>nk#izCg2YY3c8fhj5K z-hdfr`k`A*av7{~isr9mCAuD$_KcxW#vAO8IM?B=29Lyfj;DcG9AX~`Mab5Xtw-EI z%u-X=9U})yih5Q~YKAu@t63liN|>-DN(yT56(OfY2s1g%qt55D~Xcy-j87-hQK@p<`9_1nGk1g z@*9w~TBzxE#^@~RYzM`tU>^c=N@|jxqiU+16@C0x`BTG#85**&e)}znVN9l zxsDP8MhMy{~2 zYsTU>oq+YFzZe|Kf{bgTtf4K3GLoxMdN&iw9iFf`e2YLxkaJvrz8o&?a);S zajt3AOs-BgZv}IBQ?bj1BAz*0PWwH7_x6$RzWatZUw)1C7Ej=D@A>kpclf?A%pdW& z@G|X5*O^b3XWsw(6PM=?ygMFwczje7h_U!XkMkYhJU-GnO?rm!8&(AO9EHz!F z!;iD-UNk61@A0r>=y#fyuP7?#Gy7e~yQjD8x&wpj=zWLp41K30kjri2`T4>>{qdjq z)1Uvybh%VFAxLpJ5e^m}hK{eE9{BCIU*r0LSsd9o9UQ{MH+j@&$TZ=N!`P0euY~>M zBd;&7eE9K4Y`;g0cA>Z44d*Ox_9sl&5nV?xR)x+2mk=4_%+D{MczHdOQo%}nf0|m= zkQ4!Br9Q6=h&Ua#Ql&O;&Lt#|d)8lz_>RaeU=^8JNbA>2}rOGDcF+|Cnqj zc308+mfm#?RyYoO_T7=b8!*O_OVp*Gl_dr0tp5M)KdpA7B>==2{n=bn2YiXxtPAM{ z{8I|P>lEKa!S;shbY@D9*YSmGCv<+`t-G?b9~gF?r;|s<6Hkvv-afvgI~{rcbml@J z-_DfVD_t+7l8|J9j?V8GoZeG|L~Oj_By!yEa3zvc!IeNV0dEBB^sqWot7~S1y%xNI z;~+dec=m^mqwjfx;lueEDX+|S<}^6ooH`zN9b=jiH<9f`u-Yj#OBKXZW=ts7OSfT^ z6!6~ScZNjZr}xjeF!T3c|Bf%d{*G{b$7m1y`02{+7O*kUiK6Z44P_E;Va6oEM$rym z>oL|*j9&kVq=$Wk&KbJC!*|+gCNW~Rg1r-ZE4Z%5ItMl)ogl`xO2L+)sobb?hve;R zBEjt}-Oy9SD6p&)T-ovX_ywoOCw_eW#9!WjPr3&DEn*EsErcgUh)EB#YIf9NK2Z?s zlnhjd(Oj24z3vD(=5Dy{{tUizQ=Sz2oc6tb`Rh3@BiT+Aj6;k{1={YBct=gIb&Wf zluH8Rw4A~0Yd?#QLWbAQ)#W;W$gi75=SN1 zg`ZpMq(ThPbv1F47i8}?k(E-^vx?}96=Kx+uw-)7nX8}+3}+3}`6caRtzV3_>*Abc znOthR%52d24Q)<6BkCSV@5fXzscku3NR=R}zp$y;P_4UC$A7NLxRMLml)Jg({)jBU zV5Hq);y*h(H*Ln$Ik2TJTPv}M8e=V5Z<15wdbuDLF5{I<#t$Cn2dv+bt>YF0@z!w| z1|FWCAj9*A4}@vl*1u|x{U$%g*4kUm5`x^3;WBAc1tpt*p1X9{=jY|m#d_HgtIOy8 z&{8&dZa%{<*Oh!uYDUYNCNi6T>ZK;EWRlUDLa6EUk~T)Y)zDi?A#6nP<$BLmoUG{i zCT_0vZEQqeS?0c0J+tgE${J!&%J4M=+N)FGqUvZLKO{DTKc(N*J+i`&@=3J^j(Ly zmN4HaMYvqg%=4rqx+dtAN>R+U_HZKtWm`p*rAk2V_5)4um)Z}hgtARIq9zAjH6v#> z`1vZ9nzHG(E)AvMD)#J>qP)jqOBKYlYODR3v5Kg+o~>Q$+|?*KF`3oua3_eX$$hyG z{>|scD!kp9`8Ptc7$q(>8r=1ICCoD^rJ4|I{a;%;qipKa);cF$z_SZi;SKPUF3AYLdlUEbOB2c%#v+h;R=6Nep?rJ%u zw4GNoJeax@G)hyf^cCv&+`3#T>+`W)cT&SureVEh#h)u#pj1I~Lw~$WvgbPp`nFg7 zSB+nHT@EbI;}(KyiD!LhtmeS&b=-_yrJ>ZxHSxNN^~>(D?o5|T)b`1@(9EVY{@>nJ zwdaM@vrnm1N)_Izbz7xUD%ESzy4F{-1JbA~vUyjr6oN>liiojl%v>n3D)MJp%{B|K zLzGg{s5^qxzIip?N;ShZvzBdpzO46=cCfoD{;j^YR)t`U5}1}sgG#l@m2gxPDYHD& z7gYRG*xEMMWv$QLo>*FqYON;8xh#J|CTlf}Y^pkzdQ=u&ATQ>&y3<{PH{cexgjwvS24Et*Z1tYj)&bN zIUD?b;P~YU*Lm_SaC!fpKmGSlyuE(SFzqQr52YtZ{hSBaVSUeFg9>w^b#h9fqb8zN zaCT<>+@04MA;yd~5pPD-Nko`atU8OWbhb@R!9pi(HCi2&X07J5QWo-^P>5NlB4>o0 z-AYKR&&3wd8a77MR(IFVSdFlgN+TS}9fe_`J+W3aG{S<(S!<50+0^FBTAd=>dqva$ zXIGPJt1)WT(^Ab^sV06^Q7`x}?^`)Z63o*@vd8)#8LgJ)EovKePyLdx=k3^iQRRJX{{W>P% zG}qvW(%DK$^uEV+j>B+ zk{Noll55&~x)de;m5jH3eFqht<#WnPaw`d+XH2bua=PcSQ`=)$>QupoWN1L$L2vqpI(%!FF`cXwLslrRzI@}l zzG#P%QkG#y#rloYxtP_Vm=$BExK3SYjb%-4wBvre_++JlX)>asq1Lqiz1g}Ak0$5p zkiW~{BduqAn*i8$iHQNbO{FaKD0TO>17|xFZ5_15EJ&>=x(Lom4?-y$JPxt7CrMYS zdLfz(+J}s}Yko2t{LpHsSUST#6R+C=$I9w9l)6kz^$=sl-1n?C{B<)dEDNjqeo5{0 zG;6oJG?BAiW9nuUBP*@4)-aUnPG55OBKkcV6|zIpw_L14o!;EJ_N>?T#!clxPDK-B zA=E_0J({tUdMGz?kGsDc7PzYkNI7o?o{DX==FYvmMPQbi?D;j9`mfLezd?i5ltb&99BX$}OFtMXbsvwCSqVtvdP&+C` z>b0Z<3#q6}=QNFY*WXoE*H?-vCg#PDTyd&*%h8rzO4U7FJLB3Crs!wfe%jn#fJ%E> zzEf*gRHbS7*ZM7LZj*{oc%As^!|UlIWdb5Fz=1_YQ;Lov+IS>e4-uaw0~l z2+Bf<^&n@2eCnViT#VYcm<)bjGoF!q7W`o_UaRkvtTMQkC?1=M3>}utPaqL{j zWg7V6xaaBdfy*s0y!FYDv3Evy0O2Q~n%|Scw@ZRCN4p%?3_RiNqR-aF) zCWu8KiI9k-toOh*P5gX*p?Gkm=XIWW9xq(e3^{Vkk=Oag$7!Y?C!Sx=yj;$DkPQKo z3*I`0VNZ`G8AF`3XZy52^6vD+oBfGsGm)gp@R;-<9|IvLiWD`A^d3L-gaT=fO8;5j zIl5kuid38DfTX&(E+{6wChrrv{2x;SDa=@^Nx9% z2_HUie!X(NOpEUt4qDEn%VK%B=+3!PuyR>?H}#xUmNSL1dWMTBW31({8+d#;@NhV=@BJbm&LL8A#+igj zMai`U$s+g|hQ6$b*Z1~hAH>7>ph*~Qmw`{mdxr(+a z$2vQ01ZUJllG6q+o@*kkCJItevr4Sy*|n;z8X)iR%7sm_(J%1UTSA-w(U|5F*5%v= zauwB`V50FD4^XdtXaQi=d{6R9ELR75r%ezFAGXtsi#5 zJ+scXZC|hz6HKG%NeeSn!C5qgEl!u@$q2;^i@tj0N(|ojc;DkhlQmWZNeMz^ z$U0AYb)sk0i^e<85bp|WB(&6%!?Yv3u<|TpK=mBk!UDmjIH3O7I z7}32{zU%g0ME;`ttA$vQ(^}V8tLkFOHLa(FLDwoZ(9UKJc@(w`G+RX-efFkxiNERT zmd~CoB4zFOt-ohWiJFZ8A;v(C@lLodThzL2kjv$lhW%ewQONTAHub)@n|ic`3)vnK zrqcDqF4vi`QY4$0VU59d4(ly8<(dXka)v|*W5jfx({9Iczh}Q27`ncuuAo%JFV5ZV zyyUYA^s)%M+s^p%i|2Mz3Cae=z1&Ajb;jIcPX~MbBz|b z8MG@kD|KO8xlt(O9GP=9hU5*CGOwaWHhMltl^!5vVOrLENABJ!Y$Wm`rHYNMq@gU& z=w&ZeR)WbQ36?c+FBptjpQ-J+mvjDQ(BqCyBX<=MMSk7-R<=O`)}4yf{b3S|H7rIb z+n%>5RW#*vN8prZLTKV=$?Gnq3G=O5v}ONIu1R#AQB5VIiLeCJwtKj3NVDnO`;OyoPv@Qf-dTxpDXQqu-jkU6ofsiIv!K?co?)7)*czeldiuVnAA0((tE92O zZ5+AYZrtWrgMJArl6-Ny$r{>QR@zeT1npLxudWIY4bfSA-XNekd8-9<>(^ISL#h@$lT%QvOF$F71Dh<;JqC1_KBS8Qrii}Gjv(ygv_Pyq$B!yDi zdA6QqL~6fzUd1nX?w2U~{y9%D5r zF2-T|j)G$jM>Sn`N*-0>WyU!T%s8ikV=48X$3ltZcq5Dvo352mB{d+t(o~wU6*Nhm zP;zX_8gfktW2v+O1AFJ_oyF}S$4TGoF><|Kbhkw2HV1N1lJz(SJ|@vUTpqEdAXRhC zQTI{SJ7R`d^$Tlt?_rES`39$^kXF@gXqBjX<2XB2GC+L}iJseI(Ew@J@2VD{1O%P0 zTGfymYean)I z3~SI3a!Cn{#rUoU5(|49)eu!=F_}qO4YwLP(MnRC0rNti77=`iU;?vuL?=8ye`0S2 zY#xxQ@b-AZieksqlyfBI#l~O1qOkK#BJ4bRA?@CK8eVyw?SeJ+DT(8)I#dXhuXlnA-UDBf+%2l;r7KzRna@GfR+636; zGw_+InEkDjKj+$O*%G)_#Kf5DT4sa9mgRRUWrOVcm0qd^qqf0%$FF$d$f|&*LMAoH7stE$gLYB0e zA98i#iO|`NnY*Sl$Cm0TikvgDqOIDbCU;St`);z<_Xr@~*Df1Vu`+ttry}c^!_xo0 z2>x44R@r_RzZ}4HdDoUdEH3_~|Epw5D7x@X`mm4H&1H+u*hZE?c8#Cu zOVO1~&i0=Ekv0gucF=3zQN0&Ri3{4&Ex${Du z0KP5tbJ-*U+UH;G)XB)U1gn;bu=KhbR_DA~I&6RC*->9a)>uVf)&#$4ooLdW-kSVe z7D=h##Sm|SpMLn6e>8%1@RJBXg_)nvue{y@m>u1*W142haAU9**YC03Rm8W@d9OW6 zeNRkRrmWpEC1+At7gIv1xI%rIm%4nyL+2gNc}f@QoTfW+jO3{JuuqqOd_R(Za(sAx zVV*A>cOAa#`T1pJ8b=5d2KrEuO& zeEoRDeYE_Sul_T?-#rj|51l?UcRu2L!t5dC88MyqZh1}jW_nV^t{Q8}rvu|{B!BuD zUV3ur2=hSBiOzdE-{HNZ_nyA%IqU}>Pfzsyp6fUgVx;dagZG*Y4RbXfL`s=RQOMDe zLt*gn_0y4`-=2`$GuPJ(^PI5O;f(g{O!LI2*B9P@{F!&1cG$<%7El3OGQGD@GUGI9 zVlpK9Jo6v_@H^c8#Q*hAf8=l1zu-(JWO)Db%>VeyUrFb{joa) ze92ee{*K{%rhECsJWYgYs&yt+OzSKrslyYi3uI$6)@5~ZVzAaTr$8z&r^uYNBkMYk z+~$dpv_sBVT@+0z5vPd~=C#tqc(NB_C3&Qjh|V%O%bYar%%XHf3C?{{xXitVdRj7$I~N^R=bi-uKmGQ?@i+@w`t@&1`>jI zhFuJldBR|M{qQrl>y^W9$HUN*Qz72KnjW0t!}ZE7UwCsk;H9ISN3Jgy{#FI&UH1v+ z6M6r{uJ3qycVxHU^T!{b`R5;>xsC}-WPjYT?*Wt8_RQx!rC|W2j4FOD+b);f&XGel?9r zO=QFwp{E}@+-Z+H^pwss*U#A$AlCp|I0_>dNd;R&n@7e8phW!)4 zcI=Z-x+{PF+mC$w@PRbV7-Opmh_$weNGhT!qP&_zTS7=GUQ0XubP-jttYR(RxoR$~i(%82$%&F-D1xyn{40V|=@Pk~y=84XUT3Z9 zSWhXDl##UVcgspXYu(%+h$~38)kshl{H|2dqG)OB()CT937pjq$rx08j1}96C_U{i z)!Y#7Wt(Iz3m|r>K58go5iDAbwcOt#L|d41CJ`xF@F6iv;&MqO50eZ`vQweg2$HYN zvt^n`teNO-!sdY~)ox%SrbHZ`SXQEfS9cJ|*riFf;`b?KlWeVGYLRLVT_q|bYof0dv*HMwA!S4EG7@ku z)fuUtmD4JSui`cvvD+*pFKev6=PM4qJ?m{XPEsJ2FMfXb7)H_%<`0ypcZPv1hQqz)Ra(yqb zEOqiCn%<|-TY_{!OV{7opx9p(UNr&U$TCe3X)|VNwFh-(Ep={H<3w2mS*?`OxwA92 zVzPyp(^Av5JPj%$-SD|EUw#eUv9!ea@1*A0~6iGx3=;g^i>)N~^)d6!p|c^2IZVQftq)$gz7 zZ>7@iFm|Czu(|hS5eDTB%U+g*ajA*qQg=aGIjD*U&6JgTc(?Ov#K}@>2Xk5XR@~Q_P_LD{aBU;ttyr{WY&5a5e?+jGftt_l%-5(EU_ilTY+fX z%~qYwRNQ^%Tul?|Tu}@Jt74{X%$L;fEQG)~j&&|!W1`US#EIZCi^1gJJa6_phJMg% zG-ak~Cd@Nan20gzZrs!zumPN22MdzAg{4Zt8KLW7HyHZCup9K>nS-8r;}jW5^|@0; z(=4kINmldJT{Wi2@|hc_bN~;nz~n&< z5i!<%TzdlL%|1IX3;tG*)~YJJ>6ZW9w5!*Yl``9s%f_r{>Uy19I&2i=76|zRFXJbs zSKW$_sZPr@|CwLdc{fgfOd#q+;(wnAbB= zqmkAiLr!^tr5K~;hGvqGO=rt`=DVl-N|qHpkG6klI=`$&%eud{)?&R|s1K^0ta}ov z`j4+?o^15}wd8UL>10gk_8e>#rOEqPOIO|nuitfHs?F2V!uCacn z2asG3BF=8Ao|VFzvmmLEB_Od!=8iZzZsQBq%(!mH;o*VZ;~R$KUd2}H5u-D?^F4?C z0p~hGPB`z`ANII@M|KBAPa~7`GiPtm}L&7GV8B)oV zRtE#*6!qQT>Q1Vu5o7Bf&|;hglNfx5GnSp~@!7C*PUlbO31pmg%sCQ^&{?macg_TB zm~B=XgGpL3UbW9+v@QY3^j?U$(xS^*tHN`_i>Y-!ZZQ|N=dN!RhNSw;(Z6$?bcPmV z>U`tW zfovc-hZ%Y#$AzG$RZCId1x3hM-E|gIsj4Uu!aHz1F$RKo{BEbDg{g44-6+`*Bc!4` z;F3z!LR4?DH5}Ti0Gd`ZSYM4cN*E|vX$q|(J!ieYbE_cJk|Q-lP?%FdY{5UB*ms7` z3g_{{&mTS!OXA&|k>hTM#H4#Y6M5(bn>#`$#0McotqlPUiEbe#}m?2tqDn=49 z%aXXJE`Eyf%aTtIS*D6_&4IlN)%thmMXc9>eigVjFtKH*b=76(oLv^3x?fXasSyTp z6=`!>Ur1S243V(*gth~Uwe_Kw&FT!TP8=0*b&q*KR!(k?bb*K&=cY8|y|DXsJ@VNfz8(H9y~w|B_eeQNmZ0rRKBKelXDAtBvl-5D*C%73O45|>Z(AJVp@LoR0_sv z+AFrwvbOZ8WPrrXW_;CVg~FYA!kX4opI3Bis}U(J8CI8xDXmM;ws6a(ey4VEqlnlQ z>DVel?#OcM!mA1RrMWEIa#S;3T31q^joTK{EOh{{J$eguL{yBo*5R`dr7k|2led(L z?ORc8QW2w<+r*!~|B08|4d;b%zvF6^$gv-Gc-K>`BgUC2-q_iW$6oL@lTyG+W^lSB z!6zc8~POFOY6eci3Zkhm(QI{vl^w z_@r=!9PqAa)x>^}_l~abaKLPgF2K`)ec{;6F+}^=5#u;d(^I>R1w;?E(t?`jKd9<7)IiF z7Q}l@$GNE;y?WM8@~MFALt$||MIVYA)IG&aHPCrvW9pq zyv!5b1pQ5~$J;bB&4J9NiqD!nn8q6+%~DulU2;Cw}*b-*bBR#6SJ@FJxx zgo4K)&M@R&v5ZCEBO;=LrI<|$P!`;%9zD|9E!Z}ytv9}&7mFA@r?n-2(fh_A1S}B^ znOT!JsNqZ!;gUwCm&COsj{AY9OgpaPJTSWBw z$+}c}nGGpFk>>CCTbOy7Bcuu4NT^|`)C5qe3)@^;_hwz_W__l2{f^V=#N*>5r_&S1 znlI~_#=P)%b$38J+t?FU4%)}=rN{NGUt+cvEa#ma}jxPTI}~hI=8U)OQj!@tc82p;!a9BU?wTUB$D}KGHcy#vCar zGnS6gdZKY$vT#j6viNKXxiDv~Dl;N=2CMMnqGw+=z*d&NU%4X$G?ObELDZOyvAQl+ z$okG-D$wqT0}ac&5hUAeV>WYjTPn+@rm1ye*J_gmIbJ_cvk@1!DIjaj9YL)9yt#f~ zVhn>X9QTH980h<9nR#MOr<4UNT$52%KzCNfA>$m@cY3{d9$!Um?{taftg8rDA%?{D zHu8KvGfp#eh~#;OFs_OJO&57?L^aVMgd&J@1E6Azo_DstTa2rzp-4^quo^HZYe%Ja z5F4V{)fh%4dlVH4rPW~-t)Z8cRFI7kkxXYYN3YVrG~Kveubj_k=6S|-ie;S9WhTkg z0m&?=^hO7wA(0nBY)zQ83c#htU~3$y^IaoEm<>WWH~OBnl?Y&KN?*wgwkAYl$#-e$ zRsq(8`Oa&{ZHiIP!L+)K!dx<;Xs4~m;KV99PTDTaaEo#2E-tIC%9i!<8CU+U2fQtP zMa+^MTJO?TNY>j;>V9TR(QCooOu#a4|f=9;jZ&Lp=6`7x|eFg(NJp0hH4I{6s*<6UMZUt zcCK@D$&27^jcpAHOLAH^>2o$Bxu%vbHc$e-0KwzQ(z)JxL7V^$NWER;b$t0Ys5(vIg?< z=Qco9sh>yQQVEtilWJyIo<*`$qvjab&`Ld{T5A`hR->O;Yw^zNv)JkmtyIA=7=e^= zNpOH{LET>|MXQbLaKz-Mrvx$9wX$IR*p00^N72tv5lOWryJ+#N=DPh z#_DsSb&{I4$*nv1EStdnVrUbs)aV8dhXY;LF^{v>usFwVzpF+w-FfGdYNvOs$ZfOS z?^aqxY^&oG<^c1gwMp4mN-+OFTW``NNs?sgeZnH5YG%Gfefdg^kvSikQ{JNYR7|{@WY7=}v|1=Gn$J0Jb5=7k zf;Kn#_~N-WiwIP;Wu8bN$;cgdq2U&@<+!|(zxmuvH&=WNibU&^taggSrMbNeW=IRx z4z=QR(Xe}(WZ%Oy7wHA3au_uu-qrDU;x=(HTJ-xe>_SwqxwplhPDHN{qwX}-ZLvPy z&y-MUPD5+%_IE>R>$`#LuGZe`YQL}hJ&6d*PH&wxCi{FtGlaRn?_Imi!h1KW7`h3h zN4$yx%d${vvHbSh+^}m|J}^%+hw1313;TUel9?pX^ozN=i=D^1 zKu;EdpeWbYcd7pbo=w*eTAdnVvbKH28o^E!h`M$^6vd%%9)4L zgOTT6zp`F0EY~yIpOJX#9l={KAFXdqz9s1%b4{@5=cbiP zF=@2N2vD6s*gLf?^lJTi3c@@;^2^sZ-oCz3`$Ei#B+4u%3YK2TT{#BGX`-GU%*@%k zi5*5nI}6&*yj))piKOKC1h(9l@Gis2a?G=39(SQ_3P5jaV**jM7IW3pX$@r*`c1mj$|GqbM>Na3_??zBDSHMt{M)HQjwp_4CGFFAvyo-Ys=ErTT_k+)?-8oeDa<&$qu@Qg6%oF{B=t7tiVnTMm{-6a)}gQELQ3 z+#0&LHN&z!U*_nrUvZwY=P0&K-L7}}U11cYcMAIV2(ew2ybUfK2Oq?6y5ef57~|Gl zOW5&tqTD4Q_5J%$wFA|0rXdXGyIHsUaQLYZROl{;vEiIw-cBtrrvi4c1-sOm45yV1q!w7T+=2e`ncLrV#6% zO%=xP&wmHjJMY7erXA_7a#y#K!os9s|S+hy#_e8m)M9oh1SHl){R%>$0L zw)J)!@0ae5qOJi^A+qOU-U}VJuXWdk3wIp2@$>h?~T^-SXxbfOk7ys$=4Vn*ZuAriBlf#P|4HXNud z>zVWQ%;kEawoXWiCWd^ILw_15!E+*h}Lc;UI ziHC=y=ORKrWLmlL$G`rGm(M@*yZ`VFA3uD{H_zWu4m0I;vnSsbsMp(-+j1jUE8e$( zSqOAjR@*>Y_hupl$4pk@q{PsXR!CY&bV7tCooiV*UoXgQ;m}q9PRH5uoz_TMu2gNz z5uPPd*Ogc+Kc8Rt`O8;+mYK_k@A>YB-*Wn31Nb>4;{3pz4m>?nzO9wh!z1%yB2U80 z>nqpum1}TOV3;_i6O$asQ8*oroE{!HoDPI2gcKn~uBGznW#-eDiST9SdaKr)Yo$&L zK$-|-KB{||f@Dy4UBU*GuY?JuOZaFfJa6ZFz(eI=$s zo>y~s)XwTlitDmkEz}#!vVg$hbfnXGd|mmfjeq*%Ph77zVymRS@?nmwAD;QWcvnI-C9j?elfo${o{k6R7&+v`G?}Bkb^HB(cz)&||INSR-~Ho1 z^6)hC<8tN?|L1?@pa1Y9KYe+jwLoSf)<}+%#~ArQAVAed(~2ZR+P2;iwSntEERO8Q zFz&7`n{c0I28l$^;nq9a(B_mA%UIj$UFBUpiYl`vvO=#N4b}{(2r3f=L3*I{!0PuA zs-)0~(Q2EuRa>Ncl#|^V_M8uFr(h~`nGU-UO)VQdbRmXB@MXQ^MC*I~o+WaeX1@;u z-+c4Tb+ugcH0NCqA#rCY`BDWjoM4baCAu37cdH(kR=Jfz>V}>_PMO0rIi`J~^=f~& z6HcN;&)tM7fnzJoth~m+tzSvujn0`HR|?9bb`HH+D|R>%KM0}=EzHyw(DRk>dZTGJ zag!)*^2LA{_ExlSS+UQL4}=)e>jl2P(90Xdiu8zJLW3B0x>4|gbUXto35SCX%bq_z z@%Z?}ln;;byWU_7w?tuz ziRZ(KkB1Y7AjGZ&R$F0+QX}Y;2(pt*K$!$`7?||ovo;|{cV}hv0q~UKz6crp+lIqd zztg?GiwO}&vyPh1>1bPD<4XS#C#2!NMv7j~nCPhc-5edizOIYkYd3DI(%MR`PEeD* z?+XLHwJoANH(jll)otN1>iV=h#0loE-`tnOfvj7Ox{5su^L*gr(=*TCf6J|`Oev$S z^Z5M8>6>p^nUGQm^+KGpMKTnNJgBviJ?elvL}u9f5!t~^|E(p8{^~yum%+UlwKaFF z-eq#?o(;W);)OdwJvKnsUR|&zL}4Ffx==@49BRuDcd_5~dO-}wxCu%}rt)2OFkTC) zBML{a^mv;2{=1JHk0++XjKt(a!9s0~TB?&RJP+4<&E&XmbMe_MBSrCOE;~D8u)9E7 zvM8Es<@|Qz^~;6pZLxgNUJ>crxvty^gYN**Z8$c3ncmeYT$YEo4Tc*^*?Q-_#~%H# z`oOi1Ow|z4FkJYu^`q*UecC%!edygxFdvd^8NO8;MQka!Hl<4hUrzL74(sa6>OoKp z+TFEo=7}cij?li%RrFoFmu}y?-Um#?F>6Shklz*{qj$0AKS)NhQ6GEjgkmCr7^2mH zVhhdWu2`JN5L8J)nWDKFvqOQ|h>Zik9EsiT@g$w-{aI66uqDOva9xls*pOB>;gR0S ztmECn)cU%VWd98MT{NRcry;tVD+H@g{p^gJ;2^&fgWS86C5k&S?eyOk_AV43&OLmt za8HNCYoDrZ_&n}J7rw09a>aMj!Jra~QweKyCz!{BXtdg~5}4x5G(V806G0{phX)=` z54MQQFi$h3+V8dd+(Ab~#-K>LpxmMgtQM$FxzJxR*oQFZ{{Q+mmI_AT^0RMSx(`>Z z|7*mJSYB`6;sWkTh}))I#*%#(o7f|9M-1VPnlSFSJ+#t)KA&lABZl3P|BgBlcE@+v z&WW7}GaRKOifKcBevWvT*Keu@5g^3chNC_%b9rZxaosT z6uP6n{k;tAORUX}=aPm^j1>?`8)uYXMP>#!iEX3^*_A5e=d9JK9HEk8q>E4qeB!^p z1EG5~=(gbG9Z9o{``1M;>$;i%suZR?GliKcXAb#D6eDwVj9yT0H#7<@;_+Ui797r3 z%R9IEpe=*<_#Ielr`(ivQPwJyYB30<3Cj|=UMeC5DUAb0SG`@&)aEk|wbN*xA`q$F z+{g2rm}2%NTJspzZ1J9sN*+s3VMP0Mw07E9kh-X_OCTjX=TjVZwOtfBQ1X`cqb?@T z1Sux!h?9vp6T~Sa9#et_CJMO(GAm^%^k$2*93vkepO}v`w-iZh;2*yGfycwdFE6jO zAOFfvfBh#|g?V})e|V;Z8?^>v>4e^>(kUTulK1D&poGwEmttqNZ_;(ArKUMCO@TwN zNID^#yW8$itM?qSt#><@-rwyEthSOnii*vZw}pMP`bY1gYlGessC+>;_7Pab;;#v- z&8fSsHOJL=liU}-kGu{mwNlHv&H8Jtv|hH@WpNYUM$1Ua7y7!HVMQW&&|CdI6p0oi zSM7H==ZTI|J#L|_^^PcFHT}A-Ow)vFqIBCqL@{zjoeuPDOH%3GqMTHTt)i_Np|jh5 zZ|f~Qj?&^v3q4$^{k##l8fpTLYRmK3mC&2hj~nH7<9a=_l$&LHr_LdL%bboRj)XQ* zi;-wUi$u+=Ez*m!+^+WXdcP>~g=w0Y=}hwsMABhm8FZ)=w%D1&lu0o%PZ@26r>BfI z>r2v{2);yDf5#wrED~Gqzmvogt#w3_N6SwB`*edwS8^7#H(N}LSdZK7RvGc#={Q;4 z?PqKBjCaPqMMv~q*yi^VK=VwXA%}p{h`JNQY~N?ZtXfv9>n1lkHrvtER5k*0NCNe| z8A59cMq$kndZrISQnMQ9`SD0;ftS|{pFh9w^Dlqp?M?Zv6`sHONS+U*lHmNT}V!UPH`9=xEptVxwQ^f-R2MX~QjHp-oz||bOG7kM z8(i1U_1d^D1)1!3q<(KMYhi7j^R=+Hz*?bntJ_t@?uAYCok{tTho?kug+#Q$jrxMq z)ZFIyX`c1b9qH6-w`G_Q%_14Xgh*uxmDZG;qC0xb!6PzI9M%|UYc^0GO`R_gWg9%{ zjzt^abhE@h22Fg!vG*NqKJs?dzJJ|R>EUm%(4&arZup~jZt#d_fH=-N=>1n2D$a0+ zrFZAU&N#nQnCZKWa$5*CpX_!*+d=iZ;q$}asP*r&&IkHMMrOCWR(8eiUBhK9b#8;ytyM17FNy*GWkly?l)_|3Ws&K+~X z-D;z;v3k=K?va322(cq#g^wiw25w#H#3IUv4SCbc1_$=%9|S9n_2O!4tq@NCJ^sx z&#-0aWG|3SS*5MsVK==g@R*2kQH492K~o>@cr1me;e^zincB;p7Q~jw>Ur&!y&haZ zGE`BX4QK(IlvXxkNOYW|J^J03(mn`wM?~;N`52V-V4CXt{4|DGK;vCt#=X@2zQ>E( zKxd0n(9Z6gaeve{#Mwt$@5mtoZ|UEo`Y>zcdG(<$_uLl=caG0rchug%mD0%3i{7xG zE5q^9@g~~8dgwv{F0I405o;yC+3%nwp=xtgqp)g0=@x5%P+Q@;Tu7=Mf@Nj4(0J>O zx9e(ZL4u>=#(COZOcgzX!0(2xE*uF+I&eH3kXkH9D`eJnB~y8PIPtm^zJB_JRu_0Y zlIH`jFE=jN#qc!R>Cs&C#AJ7sHx>bWU}H<8G1Mgl&$Vh$J0S(i!--GFBOgxRaG1WM z@yN?6e0obfx0%Da^7!dY*H@~pWNAE3fht0ewkU}yv8)S!{>xwa@lStYSyoOb!?XoS z9FLFGrmSlvNMf3f9CEi@`+P(jUL?YNOq?DcczBpN9Br8^u@j^kL9#Y3w;Px1nakzE z*S8Dn+Q~T+M&X$P;V^Od_ze%wA307(;*<#jYcDhvG*o&&Bc<`6orOpZ6Co7Ba$zoC zILT)uf8{EL9)w;azxnP5KK%CYi4T8AmhYHUSo2Jk&Wl98^o7^k8?7}Cak65zcXRb% z;wlsPnNl>SJ=$j|wS(GBI0Lg}vSh=;#YAt)trS|lG3!DPFol)JSCk@<+k&Xf<<$mhsB&pgTl6?lGr;`#XlKm6wJxZGCGmxb%) z#@qSIWhwCTN`HAZXJ{xCuJqO1t08B?bYSU<*3O(WA3lD=L(hmz1WlA)X;e~5`w<2N7a`NW@pdg06U%%@K`h>sjU2#JKo#FVoSu-oP`X{|F&iBky7$A#xU zFsLRZkZjHLcOpp0v~}UzrxW4v5z)q+E$-wHgvW;`(!&!qCDzuNf@8L&k@Ad&nU}Z1 zbgE4A1K)i7mUOK2veMVZYDcxm46TLZ@yNga?tA{-4?pm^UiiQN%YWiO{ipxR_0~Aw zDrn?%dM1W!4gjkymI@|3|pkJc)+nR~1`+Aqb#l&mS8az>j{OY^0@yK5!50Jv|CITP|3 zk!IwniNd)CrcrkENK zj2)F`3!@&1wYfM`Si*!5m`7%(+%02tcU_wpAe(buA_+lzp*h}g>nn!1R(3q?M#}O& z2bH_w+;H@UjmERj8N;?H-g~GKJ!MPo6fJVC69^g+eUD>T&w?BS42f~eoZVd8k`0Ad zst^8c=`tM}aehD!Z^Y}BT#5~02}FOtwCd&n9|QQV%^YaGxl^?7xbxw9+Dg44ifATW zam~K;K=s-!w?M9NlE}x?iSIvr;8s_r(CM`?KOV>riP$q!7UnFRrpzG=WijH9sv!(J z$%p@8qr|<-jsLatanQ1ctKdFMjom#v968koh%wwbf45xHyXBF)gtq)}AJpp*xoUKj zy*L%X#Hykop4W;GP0anTpsRnTwtyMKa_##}K)m*99!C{`V@}*&Z{&m32oShk%{kPZ z2*Z1Nv$GZKtjZy9bPWE8Ucv`!GWxD)=+R}ZthI4liz9bUI9QVBHHr}aW+k=%o=_7+ zceN;;;C%qyk(DtTDsv;9?L@h)cikv4CI%TUsPVHycqbfJ7p|x1S)wR8>L#`jwX?3Q zE4S-vSD4X z5YHi?Yw=}>kS6h={Yna!|EaxN$fth~1`zbM=vp=bljx2;*D-$acfUDKy*nYKDqECM zzY}>Ik~?fmgS&jYyZ&vb|NXbD#zA*+w(hpmEu;FLf-(O6AV==bU|Z-XZ_G_#;;!kS zbh)cP?%wI(5kVHiG!_q|RUDb$;_q{CIG*Lxk5~n(6BERaQkY2QI86bKf!k6jRe5|o z@cr|N?;j8R?wbc5rUU=*{YU=g=|I+igv{*x<8ryusx36YD55qG(K{4#+#@r`9O5p15R^2|H*9el zquu+%{IR|JTP$C=6YYE7z0nT$Dl^{&!1z7jA?dd*ky6uc>^# zDZ^y&j#_y)E(qcNvECfb_CMZ5J>379yL-G7?i`};Z>v7e4n}r!@cGp~s~85JyOP3} zw{bgT#)YTv1ZBU9;q&y7I{n=V8y?XRy)r~M99 z_q!>WF)2Xu$P0;)DJD*7MxqI#hv-UCQVQe{-Tm#fDPN?9yNIw9CU+gnj=$I4A-t}u zoA?@~SJocTsI+KQn;@MS8!23&f%F@qE4?(<75_|hkB*VB#d4@Z+i1IkGUDbL&zbJ) zik3oX%_C_8(qLUKT%{7)g-#;8z9L;&TcfQju@{>GUEf$8z1g+d-@A&5sCy@7o9PZQ z#kk)rQk9*!x|wWTOts~zD@3+uZ2(yf!T(%Zt9&p&gxW|rkjzIFce=il?)^8=q=UwHlbE8nNcshlaFej-22 z99m~-%A}1HDlG>VRi+v?H>)E1IT3UdWWq=;QPyVh3$2)N;kEXyj&04J8#88#)xEk~ zBD*i_?Q9+9rn}fJ-RqEQlD@DaI|-1j>*Yc%3%A>q);r5`qXk%6VO>{? zmRN4A9<4D*0Wl_eFd=RXYI}fE5sy_0+DZQTU(O5H^+E_R%@cj;EbYe2+7WrRUanQ< zG%@FCi>VUvg=$Qc-pSK}!|}k=(<5n_Ii4OUixSdAr6C^eee_6!&;uz?)KUmKZP5UI zF~Ui0-DA_#`%R&0C&ry7H^^M7CMHnrWT`~rMu_Tk+f(*t=k_LCPP^b)g_MmdI*Gl@ z+8UH)kCOLAHQLq53H>*aHlj$VBv#7#p8m$907}m$8Pu0aAt{so#m&_YC#k zJ68Mb%k=(ksn)k8TJVBZeA5*jXIlHZ<&14k@y(HL2YwobqVDVeG2|FIK-%vvgYH>D z+Uc!Y&h;3k-Z}qA^Dzp5Ee~#7=ANG&B96XhQ-JqvVJbf4a_9Y6X7^~HdI%dA{6qElx&FV8;ZASVw$ghbx{@wP+MEp`c&<)oYm~Yd?kamEMaLFf;yH$~w=MU^ z3Jh%NA8e?gnjEEV&us|M#Dqd4Ul4cCjA6w%)bT;q$az;6dr05Wo4%FV4qI_`n%p_H zxBOQf&qm*#MRml32r6t-A3HPDv)D(oHgfdFV9Fc}!V?rXRBUn|K(#-cCf5XG^ zBc;QwbgoO`mrtMh)1QCh^ZCLhEU3b5E%eJPty%*Sb0)-$N+iuEPNzq{{qVqiJTV`R z%+rC=;j-E?ifF$V#9|5O-f8oJKm7ca^5f53TIKWg%A$`PK7MNgOe#6Mg19-MAecZi z$Be|tESyjNz}Fwr-)Lyl$EzIk@Ccc=ZST#hUH2^ zNy@o&rV{AqLiy_#!hEFn;CI-B91kpOWhvGG+o9fQ%a!H)m6xAB5kKGf#}7YnJHf5P z(&1K>%k9eRaz;a7o@XC=H*dm)(5$iA>}B_LOt+=A4D!qn@kQ}W=eFh9ckyRylTL=M zp1U8CZ{!uP<$~Xn)**Sbpt?CGdm95j8;FRD09(aG1Y)?>#C;5q|9wd|@qom5=gPEt zGP+1E(ut#Hk;oxudoNU3ig3Q&j5L%YrFEp4<1hiKjx4lhD7tQWlqp)>QG2x=|ppF`?QkxfWtyDSf5(gXHrPHM&eMoMw-QS&(3nEL7$E`o`MLV*yzeY5}S*{yjp?hwQRNX8Ei@ z4Y$4t?1Bk2!~JmEkChdQn&z3%m4m9$i==N0*Ly15aI$n4K?Q~E&rI*?sNm>} zlunao14A*v^;n#4aop|}?aqY`YJvi*Romut+{9V~s~#sqJm*X;h2_?0w;TQQE6w}n z;)~Lzjk;DNC2X55U+jqyGFMkqYPM6ITrSg|4TMZ)8H8V);59v zyLb)niBj;nhELv>FfgsrvBGkvOBqzR?q`5nmS(q~w+?|s?{r*9;ftT5ou!mbJU~dh zQNr2y=IrY}`wH*qjrSYQ@XmV?HUYifWqEJCm@n+dKzy5{+(ko#J7L6d+*%D{Gq%kU zuU?CUofLu(N5}UX!~J4d_nM=dNX?vPgBD})4)*n{IXkH&?NB_bvV+vdL?pJ#^74f* zKmCQD=OZt#D?k77UwHfS3*S6sf;6(u+q0#2{b|RBwGWdm+NT^X`&cbQsPy_i9&orK z!>^s`<`6#26A!18yZQ^X&Kh7!P8&%RB58|Xw0dn~nj);pZXjW5?pXZ+7Fc7=Hg5+=W5j-A2hlu5!Y>17{(C;i%+tPkSv3mX7ec?V# zB4OAgW&eGQh1GC?2Zo_y+zodE72RVo&5-5u9!J4Tw3q;YupId7uqI>ga2(1THPil2)yhzZ#T zb)iF1k6azIc7K1O*J9nz-mXe%)kJjujIAkB9DkoB+1Ks5eT~tZwT<#(&$lf*$1K0? zb!Jef5!kGzcBSe{3k8i%n00DL>kUdJRi*cpc5P17w79+1pZnAr?Q*62avmHT-h4qX zmQ~#yZ$9c9%UXAIyHkaRh~_>zDz~_aLe=jodp&fYf&S`D8w5(Qd6a6St^zSoqnSTi zh%B{ImJ6j8BxXXg7{%6Be);sq=`bN*KT|{G59`90Q|79bHOxGJ`&+s$^vi{Z2FJq_ zmld>Ex-Qfl2(9p_dtE=?pY0xO9hrg&#A8Go+@x^6-gv!Sc{`u^(_70J zVorO&&gy~t#%K+#*}}{S1V#}X#9Q2H9HRFt{a#cw=KHddyM}mekYPtY`*?TJI{`m7 zTZt$#O#M-iX~cC}zJ1J*oMww#nhoim)4avQ#b}Oii9(HiLyz3kIclwhuEZq%E*tr{ z7Bj^i4hLK2P4>LkQjIo>iG8BqL+X@>*33{CQzlP|ATw2!6f?aiYHQ4L0Rt&;NOc4!{e6Xmd`EX*M4m?bgMPi)}O!L8OD2r=I$%GO#6JUS( z@)KXayztAXue91(ma@h3+VkY~ClOv8@SKI9o zT^)-`EtREse6d7UoF*pGo=q2-SNFGMfuIY^O?i7;_~B^p zoRMWMN<-CmG+s-yGkA7;rPm=@>n7sPQH-z|1HCt@!XZaeoH-n37h)-;tdwFihboD= zRIaaAn!d7@&$PahCm~OfQdZ7a=ryq1OgwtN6$39t_J*n$+Qc2uA;lfj-7TA%ENBuIZA?9rdf4*E)p0MQ0SYlXcG1gsh1`98ufv(L zC$g!3W`f*d>m5x$@LZv910+)%SmCQ1=1Q!XqW=n)za!Q+`o0$XU3Rgr69&@6KF4)) z=tqAEOvwhOC<wvq{PGV#KY-f%l+#C zKcrW~d(_Ulnj=08*MsbCrWOLNnR2*};i+V*xQEr%t(|1T6BLzbSY2NZB(c2HEekcu zmXj&b7GE(&!tAcWwQyT*lu|suGHy=&?w`wW+jO-&($@OE%=T!3p~n%&BgPboNjB$& zy2xiBd26>O!jzMZqDqhMR#b7pgnjR#L#PsV`l-lAg#KTNaHUNmNbH1EesZ3MiI3GCV$&hz?q%SLRspM#X=V?-w zW2I$Nq8_H1IZykrP68=~Ee|XOD+*IIoajRid^jW%goH$ovlTbh+)3y7g{iEp7iguK zQ8wF6raiL8KuwLPh1`neSu@06p2r_Z(5uMJIR=8ao;d0~qzYF|_ z|KabE(}`=C$kUPIlu06_oH?ClK0MES^Wl*XAHO3Xp3M0e8eL%Nfj@uq$U`cW+nL%d zd-RwyA0LjKPA5nQQl2bCngv9s1`3>@|^25`&oF1P@=|~WB?He-bN-b6xmu8Eg+8ax) zoNrfNFBiVPzHNl6ch$K#RtFq5a~ZUv#Y z$w@ZuZtfkemAAFBEUS|v46okZDV7j4MCvxf8^~hI@9K`$RnIaM5nmiOYIT>Sn8SRY zCZwB?LL^eLzb{1-oaAE2<>;{wRfQS^ZSYbJzkXZKbiQ)<@GVnJ%qjA4Jn{MKE9dK( z)*x%=w%+)|AAZkr{z_ey+~9D0q@ILp?VL*`mx9E}s0Fp4?PjXx4TkZZ>>@H3jRmLGNZ8i&Th6LQPL3vMIOk*&c6xbB8puO2dX74xywOqVNM&0- zbpld5Nrmh*lH-%4d=2i?@iVE$0%AsTG!6A)FWFvQpn}XlcZlkqrGHEXT<4@4w@BhX>-HbFIpkpZ>y^pZ-9oZ%kr{JJq&1%*O%~ z$8stVQY3opQt7Z>GVA3)pC{sUWGz=-&KEv^J@fU;3vcHO1R+T@M4j%rkTMq8zCcmG z-@~AX-N{4%7he=!Uf-DKObAaLbG9KVLa8ui>+D*;bK3PmUWhy0=3Nf2zQei>`_J&b zj9R^`*ISyLa5UTi)-j|JSJAuGX6~LG0>s|UB@?5$7)n`o*QquTBSncj_`8}Ydaae( z8=baADTR^s?Te+6=~`9k?LBffI7w%>F3P)Q*@$$KX#DIj00UxzbqN893C)=+jjzj% zv|PB>8|P+sjkJYnvA-fqLt|hPC5O%&1BW?rJRB&Sa6p{Y)Exp5HaONsM!M^vcUt%E zDC|Svqvp;`$p$N-?!*OkC&aJ9ioIR+XY)^;) z03ZNKL_t*MZ*yVnaQ1Ibxj|6~``@{d7ot%krr7y-R34w6nP(f=wJq;Dx(LIDnb+%$ z>vf@26T!x4_usV*fkQ|9`Hwz$3%)oUOP9nRg{Kk`)G}BHje$U~j*IWUuo(AAuoxv1 zWgF42#l#vCO`wZ8q?2c72N6nBBcz2f=(Bo1qJ(JgXRFcsH>>B9M-q%-zQqmn&4rv| zVvUh{zVLFracQuYm8BHJRz`8chLF1uonE*(?Yl+&1%LlE*bqwIVXpgKaDw&Zqk?UbwhJk{x(l+oc&&}P zIBqkr;5EJVV4iE=^==vI$n)Nf6yaH-v3vDZt1o^A4OO=OVPs<0veE$2wwZdGw#~fO z-88p}81FvV?xwfa%)vUI%i1jdXInmQ;#@bg=)Lh{Jc+h&D!pxIg?8PSpO&BHQ8gZM zh3=*%8F!Oz1fu9!&4fVncda-1HmN1qZ8Pe*?E_{0*Ae15x8g7TmL z@*~IRCq6v?7G?H*LZG)m(oBfeGnaW6rJ=1`Pu#_gkU|NSPBUS5PUJcBKx9gtS`+k6 z&Nk~wmQ_nFRx3xfx=DMdw_>xPU}4Nd6k%dU-9En<-izEM$hLdv?r87Rh}7N1G(&i> z<=KZl4kmbQ6rAd7oME5Y`oAPbQp_HY;Pq4xLKmu^$2MDzEUM%fskLq-oftA}S+{pi zFmb0SXtW4iO$m*WSk;VtanIhC7|ak@3%#w-)aU5-{;f9%F;Tk`b9D!`8F{MD@A4rN zr$9a>6H~O+g#<@}3vjx26F^q$fkgs23R6nVX(FeHuC@q&dwb!>KmAC^LWqf{Z$I+! z;}hRJ3dhGMT37OXqN}hL<>BEOf{^n}N;@4hril=;-A1bDrysdpZ(QEaNDM5?73~#m z3o!)NWu>=5ki=hq{+X{Y7yk0|Cm>QvrE5WAG_j4l;A8Z;YEbf&5bdnxN{Wfzy2Ta5 zL5=yJm8o1fefKR>Zd~4eA?QLWD_Ih&3mH%IOpB4Wu5^#JO#NVIT8xN1k%AE+ zQdFnPDYd_!AJn3(x0P1xAr?(;KDHWB7$T-obH|NPd><6o@1u5rq^rm23DVxjqcdVF zyq9fV(Nby4N-M=?6D`o!%JTNYdU<2LeWk8fp1*tK!^bD)d18`Cs~wthyIttZmD85mpziVv zG{;v4cao1G;ZVQ@#%@|&N$U@iyGTZJKh)=*VmB`A- zmU^#%4l)+Trf7NRA{aCbfzV|`kq@_0lWt2i-CZRq7~&CcFn09mj>ZeYmQ1dl@_e{T zrMuJI+d$vlrzFhtY{RJDOx2qcG9A&BDO^}C3$0qFMeJ5I1&!P1BA#!Wf(>1BNEB7B zy_&;ys8C(7ATg3OS!v$!43)4IM(X+2UG9Bko|h4Jq(%T@b_&7BmveP-Q>BeuaWDQP z?8@HJ_rA}C^B2vkaaSDO^SX3HyKhf~FYL`F6rDbhK-%WI=(;OAb!0eh5e&NF$Hmo; zNp|^sz*U=}+w*^yE7sll-AbXB%CZ(vWx3sGq3@0(Ul`b;(!R&qix=8=`DXr^7%8BU z=)@4n2uTA{7W#EGCZ=4jt+VuM9zgAcZnzpH(n9j4H@R@9+u}g&pE9OZ+7L{g9D%6C zv(wF;slMcn5}0CQ^1)FGLe@bHFr4{eo|&hK!|8z#g=I1KS6MClC*I}I*XjtQ-ZwOT zlSYr-mN7m&iLOu!cUZXS4#u(6?!D7OrK&Yl<`UnXyEe?-NeeQbA3q<|1qy)?E1+x^ zmb?6*4Y_!qH|URN^SB8eif)l@!9TKsM^2B`ftGP&`SNj_zSvTr5@`%ozz_u4G1E7M zSa){dSmc@$2 zI*>%6u1u~x5Dm$SU(bj}C z`*{~GHY{r9e0Z&$OIKF4vsV2%P?hby>n^IX&wu1bC39V-NY0VdG4pgfayrhW=|CfL zT^nI(#2P5m#K*&tfB)bAYyS8D?SJKmfBSDJArhwp5v4AR31kFvGDlXFMAOO01llRR zBTYFSl*gkfyj3z?BXbg-PMK+nbg{^m(pOju)u|=s7Tm=x+O`qQT@-~lO4zVt$HUCi z!x0Ic+KM;ZCS(ESwk&+QE&RE|DJBjN2U2fF<`J<4pF1X(#e(~=9G%j#I)hc ziH3WuS}k57th~;F<0YX2m)`htxo|6mwJm6eueUe8eEBP1|B|`&#>>lBpKI82Y{*4( z>({O{8X+I}?ce_cPsay-^YDSAM3UHGW9>KAw$Rp<<#yqAyRhDFlv-HI%57aZ-!A<0 z`7>Y7XU>;5TFa#LY{LSDlobhy)|GW>^je5(;L?@XVibT|OU!dcLZk#(>5jT~$&dga3iwVuxg0UqJ)haCBFO%&EBbQ{Q50sDEURL_xZ(Go}c=nY~K;SBVq#g@=^ z-*rbxO6a6ugMmmO#jp{J5TSIqu7%pnt3{j3`H%nn|M8bU{}X@x{Xf%=-*8i? z%Y`o4P{KBDoC; zZv(~Fyyo=fT@)^t7ruP?La8e`XUgiCj$L*~+o1J~p$vC}^`P6b>E7w7C`+yM>xKC| zaX3siR0+uuqLnFSt2aFlaztYc(vJ*>X4orzg1Erh(VV&qqT5(fb){817g96_RqG~T zG7)hlXx%flUASG_ph4N^RcrP9$!3llCxDs2dmo+=B0WT^HkMLsi5eXzERK`JFm=(F zv2~;t#jx>{l4ALJA#TDqiP7-Ga+k9mx@DMlHiTafwiwi|bZ_pdP?{n!k>o(0t8F;M z-Ybm|^F*x4`s{ARix?9zCrT6>eER8Sj38TjmT*TybMnlFYz|?g)@@*Q-DBEPOguh4 zQ`W+AyRqCBv=ql+J7W7CC)xVCloEN$Oer#_@x8`vaWPz;yBpbZ1&6qwyUg+)($?#a zg{W%VV;5XBI|zU36ta{Or_+gVzyFr+zWavX{Puf}k2AHbtn144wy@k9m$!x2w;Qj& zd}h%zO$$o3%05QQ%8sPmmO<>u_A!g_-nKg>Z=jCPK5j3Yxw`~C(R=nVRwS=PD&>O?4Y9=(J!QOE`j5|QH(bT5%Xydr(3?m_y%7xz(M578 zVmB=6t@}_2G=Z!Et)R)GHMZ7KDqT0e{ow;I-+$zr!+W}BUaP`%;?e@LR71u_49Q9~ zl3*O-olaN7>&K8N$qWG7w(a!HK|IkLckiB{I0%<0eQT_3qb(lm;liw*tu~X?{0<4b zK%Q;`Zqa6Qw5Zo`ObuQOg}u+#;7)M8o)=?)j>pCbS7f~mRYDlfVfJS{sQ!qU68)Ys z@lv@uOg$2(q(Rx-EbhfcE{0FXk+aR+XJd|_gwU=3&DWSoy`hE1JvTD!Jo-WKw12Ao zINyF4RQ%@_=}1x}4@*M2i6(UsNEj@gLFT!#YeR@8u=aUe9HP|S>=5@kOY++5DjXPw ztmqDQlV+&ntWB(=J97CJ+yCGC?_mhV{Z0sRNQ2@|iHy5RNe71!P08jjqCbKas45a% z{FB&w6Y;vA;L!>$M2L==kAAL&6zSSsBt67oVJA53@o=7h5CcB{bpoRZsm}zBbhRBo zNQT}Ha;1MpH%b@{!G%0-sFGft_!!;kY&(QDa@^JOp)9$Bf#(VC1X@366mFI~Mi3DUbzBJYG{0u$Z@lt=sCQp3z@(n+EI+BhNEqAu`ZJo3vQYbu?A5(<)DBE5= zzRTvpF-40@ZXIGG9}8~}58Q=HkkfwVrR0JKkDU`Sa_}%cwcYS(W)zL$bWam{rSX4L z+6k>DLdtthGM-~2$hz}guJoln1|-=%)WHm0-G~a^2{d8E(R!cK86sfoU1PM~7pjXM zucCbofe;!RwmpUr7jUGo?R8&?t_1cx@NpLPu5{VRy84W&?IPRW8Y<0ZHZB4ZHOj%( zRyxhaZV7FZ3kU~C*+jf4WspwWR(hF`Hdz!v+`Ja+MvMxtMzIi^H)j%sY|#ng5i>DS zXh^+~Ln8#?@ig)IC+~Q?J978%$jfCVmLtT8natzcx5QYO%S0}Ppn)kRj|G8cS!k{D zp`3WVTzGnZp|{4eUg*7|y%B?wvd5KarOl6|hXX%-_>88uDA%yq`PsUOFI+%36_cW+ zE^1+>~uN;{%M@aN0r81~33b(7QBGsvKsdp{ilR!X>#Qlq3!j?L~GX~xCXhDal3n{P^p z7Asw6J5wZ(r4hqqcQy81Pt)YNM?#7o=@QI1T+||0qSFDOgIr_Et-N(3?=3C;Cc0dL z;rVM5kkwp49cXV^K>;?6F^xiwQ zHxF|(lw9Z#)N=fKO0>-nbX8L^A~wLlFpj}9cntmNsE8nh9DT{}S6wotlV#kA`SSxL z&u$|Gk_?sFo*KHloT9oqZ=gn1{lU3jm+40Ncy;cxXF1&Fx7i?D{h|o2F6YM`Lx{V| z^@@`p!!1R$SuUL8h_k!zWvCxL-%8Y6T865Kz9h9`XNUvln2~6T=8_`QROkUF5=CvG zVGcw`>~vF=rqCdCT2PL$qoL9qi)uOF$p%@fbeav(#8lfF0|{3GiZE%~3&YW^ieE@+ zpt(lTEvA6Q^Eg5Xw61&X!G7TQ0&(ON-R6c}5sx-F=ph=~c~BH|xAE^egQEf8^SpfN zZdhgiYtI@Bdm|X*)snIc>jw(Y2mB=lM(UYSnVU6Qd&XV#MX}QS#g1Fu6;Vb^Fzn%o zTiB64!;LYpW;;^%YWmsb$iu~=oy?gK7at7k4hK-{irJYXItUP*oRkv%`&Yv`$++yp z$oXonI4q=Ks&{J`EbF_w63U(dM^0^)4=rPO<%;mu8@V62)a^&8tlqjUD^lF^hqg8{ zT!-j|9Zk};`x4JH{Ni|rWJgYR?R%#1z>SS8pWY+VJ%M#s>-&&N>_Rl$W%2=gx1use z6E%%zZWPd6=uQ1XbVo)|A8Pp}GM4eqD+%s|83S(n{s*>Dcaeg|=!h~?7t^e1YIf3l zv7g+DV#`f)Kbhj5WaH@Gl$?YTg*jTDm-Y^^(?f+>5LaDm540+KMp;RLl9ee3rV{B1 zdhCV_FGmjZ(eQ=S#C(`2IWo_Ml+9&um}gfI7IU9P%=O$lQ!Y%gP|`@HvaBA(7HGu6 zvI*aP|B+gi^U`=;J1^^sERD5R7FSfyF>^Q^SXGf=|8C3!9qqGnA4Y|o$s=zjS~g=! z6R}Lp)6D5mxVtO7dvnj@{T+FpS-bM|x^Vf}s4odkGx_+Ihj%~a;pe}keDWFf3+>~V#`gWn!g;Hjw zX(neI^iH|#xrV6N-#^aGb1^cAh!?VPBr2HWIwe|`9mN@9nHB!&pZ^tq@n`>x zfBu*Mg3sPQ@ada7PIo7cQ(>AiaXa(pTKJbAH!d%Mum1jbEa#P__B{(w)v^X9beAL* zL+UeJ51CaOOI^veu!a@km0XF6Yak+1kze?Z$Kq24rd<2euJ zNXapg%7N){B&LZFO=Y}o9qmRm(Ah*W6Oq_D8KdG<7wd=$t9DnnTY*^8M5vMM%J(R3 z$9Ic#&w{r5(K0w~&~M@>7hNVBl+3wMR5+`hMO)w4v{}u9pDjTMQ)F!yK0G}m9r83Y z<%yVtIVvSK-W-nn{PTCrr_9sqg)iTK&2NAI53F4{Z`PQ|;YbUC7wrT({j!lxZz=Lj zb}^nT!e9RBpYd=0**|6e^%hM})TG(=9i$Y{3GVK{*rs6i*jr2%YVO`g2 zhT=dqilY;y(In8-k=xybG9d&%4~s=dt*ghmC|j*2Y#56j6;f{|SVc&OiMMax@bK_J z4A8a}x-#dL@HX-37hiDRRzAG{iZ8$Xk`EtVh>pQi@yso`nrC!Gwy5rLKBHbxA4O!{ zhy+(TTiuqk4OqG=%U0Q%p*dTvXtT`ac`D2)ahfLboO%ELJx?#shK=-rUy2#}$ZHl_ zTMaGig07Y$&rvy_&#adX(ZG~v4)g3}0UH#SoH)*f`Cz$r*%5gu7IHXnUN^pe`pAdN zMoULZ&Rlv&Yv-5mzTmI^@?W!@;s5zR{%?N&tN%>d>^c5^-T2TNTW=5z`*t~3Yl6M@ zEFm(-04?#_;iCv7MP#x%SG>aZT1=9AH5wv;}3pU)_dJWum@zZtD-=p;rhCz;Kn7p+0p}eR$okw($ccz0J zQuaJqcY?{acMUGi)ex+2VdUEwJ~oF)Cffh1iEL|jfVLkb5UEDjck42WGv>xJAJ=hr7<>n8q70ZEDKw6)lTDa4(u zhKi9^Vgmm}?Pu{tMz}8Z#$v_vUPedS@%lk&K^z4Ud}}uV<3Baxv(ul zTZDeD#3GGETUt*k2Ct`}0Wf-m;24scO*7k=1{Dg)ae#g@zL|7<6!^*M$QH!4JovqkS|pjG90JJU(@4po%3 z3v-^F6lC+oCLP@ue?x=-tyfA4y8syKOPL_q%d%wQ+L?2B8qy1MId$J;$2Q!(*o zjPw{ys2SX(QZg_>YqtDW$HWGOXiJZnZqVQ1O7jy7E(4tYqTPvjwifAX=iedZ8&QaQ zM1t;1YjH>Bh&KqngbOX!CW3)INi#Q$6_Ath<$3EKf%ABcgzVPX`xy-M# zC>$>zoeH`~jO;sT*=JIEqeD@>p-qh3`5Tf=%pQR?YNl%@fDxJe{QHq}e?<4F3m(Q& zUARVJdQT;OKlUDOXcvO`>~iaEr!V%d*Ek`snNvzX5)TbAnfM&3S|Xl_3Y;jATc6Hc`3~{N&t`sLpu;YdOTnvyx^`G10BzmXTjcwg%-O${v2`MS9RVPh4 zSwI819gQ3n746mMa}82PbK2=k{l-i+&ND(lqTS;$3_6Z|$gM+_PFL7!bAywimsJ{7 zI*T+kTWm&36IzPhIla++!Og|QkV zmuJ><=iF^E*QO)+c%najARlMs{KU4t@Y&BkLv-S@tV{xJYpmPGd^mCGLSL>+y%3^{ z<-)$W4TZ(H9ZkE+vXyFzUZ4U;ds$NlEQ zVmBzoot$?wtG%Hcr|d=t!&&&9>jpEA8^J$s>Eh(&n|h-6ome74c7pV%XQWvz7DJ$T z-+DEYDw>uXA&xT7Go@sU&dtR{3Ec>^>dx7L{>BXzAr+5oD877ddwj}xW<;SBGaJQX zy-v=BqyIeSvJS|cZ-6zO)*OrOpTW8=_B(dNb+`7xV`8pI?;O)^0G@I|1Pbu+@saP} ze`oXY+6b+aMVSeN`G}^8SdIvZBoOInHHvVpM#kx_QbMHF)$jBbq%zMlbv|%-`x!k( z(litEL@oz%F`7W0W;3-;lg+_P;x05hFPhMsof)lGV~;c@xXs=G03ZNKL_t)Wjdc;D zl&X+ZVO=-Sz`9jhDtknd_G;3wZ2^o3J2R7WAk2YnsdkpNhH7=^sYS^~?A&O5AksUn zSI*}P%eJyE8(V8^&Ciz@FxD01VVty2&UQqO_P4pA?n^91kyLJ+uqz>Cx@M>WaiU+V z5$VbI4Uyw~Fe+J^cv&kkWDbX!<@p1#pU_;m^o2Togu5*|YAD$PMmnY6o@2RH)({vyQBY_UFjn(K* zkSBR$hS&No9JJrR7>`{VO;)sdBn*M3$%U0ROQ_>7v52}Tpnaujqi019ff^bqDX$;C zWoF~u7hl-V5j&!REh-ucOKm8D$z#l=HLG(=CMqV#X@Pa^R1xwo|MaI!u3FWYsXg-T zhi9IiS6a0qBM?kg)7y2Rs|}(lYkRO4Yr;_YO(V?@-k^BKySl;*jgZc_F}aj1?*K6n@XQEtF(Q5beL6xWQbf%w~% zjP9nSi65hqM(oxYg8OhvcbBjl8g?oi=9$BMF!!2QeW&BmGj#)% z&gJpWhPwkHRuU};)s%zzLg_rY%^ST-;4^zYD z5bbAIwM^=LSw9@b_QFKR!Hls-cQmkydM>)Vg2p8$H-d>#fR4{^WT`7xb)u@dGeY*v zOZSrxoSr?gUhH`moG&~}2(F5>!aKUc`IaAN-?`r4`_MOBoxnPio4SH($%lU8E}syH z(n(&-q#c*cJ`j${q6Sne&Er2z39qEI*@DawYOzF^f=7us@~K#Ura5%10JMVrs+Mfh zu6R+s=E#ZYjYA{sxqia}vDHeDjOf5A8D`ABSLHO7eK?rM5?rx!Kw(PeBClPHrZG+A zd}5kSZ8uFbIZeb+n99sN9}GJ>Wu|#zo+t8QW7XoL@d5-4W$}c5nAHWH4-+*#FQrv)68K?98%_xGRIPwris)o z|ESGJwwk!S2p0*oWckY5dEx1Gp%I8`8JptGhpDI%A!gJU`q7uEkO=ueDkowSgoGw5 z#`5F~+qIL_A_t;8GEGMt4wu9<6>`a*ZD+qwDT%{jB8TYjh254VM`W=DS{M6E)%*x1 zlnYGrfj1BL{NyKJ@Tb50Q$GFT6Bcc}t`}aA_aCX3l^;I7XVJ=MKl_wiFO<7CO!JH!51y9`Wy;Lu zNDTLmSW5(K$-dOi@4x(t^!*b}-?OxCq9Cy*us7kdn43s6kjljI;f}YTe9Gx~=ef;B z@X5i2kHv=PMpB;hdVyq@XF z7I%O6{g-_C^*8+fyYKnkn$TV>(y0#t6OQK9 z9TRtV_dsh_d&Xqnr)r=N8uMv+$Px{eSyE z`G5cVxBT?oFS%%9zJJSo&K!fVHA72+8iF}wQk;ki6P+mtxlFuno!|Z8_k8sa|B1X@ zNTGXea~-&bI7VkCkPJT5mfoqh=H#`mysQhyZDLy^Aqw+6@zbBaB;?G*@wL-yK9s9?z6L6xZijl+;aB z9(-v(`mC|YurAZYq)|tFk_!fc(6b8#yf@vKU)@Bfb=f?J*lOr)t2R_=jV1!qfzT_6 z2~lhapAfbBYMy3uX>NsBpE;JqISa1=sKQTsq?U~yF7$dP9S%&XvubB9fs_^4=>0+_ z6T%%Li7rr8*gPV^G8dKJu04|V>T%COx2<0`*P$j#i^#=lszo!QWCY7;4x19X4bBO$ z_U=8RE!09)a;+S~jVM_EYXoWZ8cW77&lmQNej_BgMuEjRp#4{8{(vvLOJu}Tg$}JP zykEZ|_l?%hT(txwt~4Hg%lui2Ji8zBx8KbRnn7vVpO$>vT8~W3oZGM-{ea#EE&HkuGfQ z*-Qu3`uo0Ku1kRsklqaWkFxplUl3n5T^G`O^oowpD(wq49q|Tg%f*Pf?Z(T#lhJO7 z48vtO2B#y}xepQeI6JzK)SQEZ#9(Ord=sVMS=rIaAL9A-VuBv^GhrWWJGss%A+pEF z#C;yr_wSHkLWr>d(;u%CeSzrFWgeAc{j2Uc^e`gxhOPY;i~f)PXS^TQQ`&n~eUWU( zTSvPa9yk4=o#wbu@Dxs zV#{J%21{2oik-P18*&K39w* zMvp;s^mxWJj3sGbYN>>sL}~yE{YQ@BF&G!5kNPKGDVsM&17T1|{b!CQuoM40bF`?r zsT9x8)m=0`W_LlZVudlAi=H>#U05)tb71`u9%VTk?9GSndzLsRTDKV#`&?*<%d$&x{`8k=>d2BYeMqe}2ahAmqvKznho?6?YvfWtzR zi1-=nmX!SXd0+3kaIFY zvu8wVyb@1@z|x~@jyyvid%r{iTki;gtt*;c*g4cBwUMjg?Wa&Vgvx1}A(=4u(hE(X z#>}<`UvOrlqahR$DMfm&PP1^Vy2tVv2HnMs3`Zum0PWSYN8BSt?9VHc9k4Nm>)nFYG=}w07E9-GMJajh zXKgrZIPPbG)XsdE%|U#yIV=+C-R6kBxp6@RP1X}_-D-J@LoTG4Xzhkbg*cg@gVXm? zJkSNU9yrPOCf0M)>h=DF+WJ)}l(W76(c|C7-YtG+rcYhzVtDZ;UgHXE-G1JsB|<)N zsS%;@e5vr=dvkMZAx;N0L}FfP5@`L^&h5GpRcXrwX%%UWoUA5GIkU0aylk3CAv5Iz zu@p{^59BiY%pmQ=ikSWGb>m%>BWc8E39MUVSr&^Nc4xK4&2*6U%Y!ud3Z~enNxWnPm#W^e&?ENeO*_kQoN=&HMX^%9-S2eX^VuMk(g-7 zBKsJI*&z~E+g-Na*p|ZOa$&2RQ312E_0FQgrU{)+bV|I2@emWfRxuS?_0Z$B{o;al{NKk)H+qi;~F z(rZL}b~R;2bh7M$6bUqxYU&2H#8_r1)Pyyu1L<#hh$Am$ zpvk&i4bb)0X?Q&u6?i-V1)m^v53yBCblqN(z?i z7u}h&@7=vv??XMlqdev20C6li*IY^&svy6($3#>{JFs^x@tO_hI4yfF-JRw5PjM&5 z$Oq2JoEcX1+QC(PxqEFKJR2>9-CeJ{$) zC6U}YlRX0}gy;j0urIEoH(_Ilkdpm<^#&?>MtSmKR7#05P(Cqv9+sfN21>R7)19O- za*o38uDX&0#PLt#{jprd&bn>1zR=o2TQ`p-FqL0WL;1+SA}gjG>q6g>yC0J+?gh$} zxVt-Xe}Ce1cVwDv(RMi8`Cs>(?(R7rkM?^Oi{v;>nK!2+_YaS}efyTTZ{BhL_+UfJ zlF2#vlHN0wdb6U_F}HPjpFl!5|yac3f~lH{=D=Z86HR)cGYv@M(m=VX=W2SWb?Cs-Oz1AlF=5lH$q>@ zvXDeM#1n5%6Neng5$@;A(v{GZM6mzgPP9R66xRKYnL;4u#1<0UTsW6ZxY#p;56K_q ziR0Rxm?0MObo8b)Fil5J$2Xkr9ylFOgj&h5aym>*cZom$`7ik=Km7%t-o2sc&enqo z7x4^At#s*J>c;!anWqmgJiT6E%FIjP`3&o6W=cn*IPoGHr6Fnq-M}-DTJ!v+t+76R z$K~lg>*Z``M4lhrKJQR*qc|tuRN+ z?Q5^LCj_Kh@&xuu5Bv&+Tl#+Ou3U>uGCl)4NFK530bZPK~tfJiLKdpTQ!pf=3dDmdoG;pILkn=dumd0nwTbYN9A}$KbwF{_oY;D zv0P5s-=FBpe2X!<33J|+`~)MA^lsU7$GLDg%$DWdsxR1;G!>qnFNBhLeEgJD?)lkI zx}o=iVbOvF$KmOYk{-3zh?Ge;ZaZRN3pUsYs-&Phvbc#*#e^|iw|Z8&MHzc;8(1E& zi!t==e*g6!`SST2rfK6Be{#>mQTX+*|2_ZZ55FPbXTCUo&K44S{}#%`FC#RK z#9TaQ&|+v58!VRI2z6yT9C%tQZGGnb9~yy`DF$kYMjP_4HVz8U4rGODN+>6|7^W{| z6QR-rQ}O)mc3_?ha~W98&e-#*=eLNOFi(j);cLg|%8h7vxTRD`v2ZvZxqEovba%&` z14Ut78{4w7ZH9Xc$%LDctD9KIaxh*Ui@Ttif8@yb_@uo|6Kq-OPXF!;b33PPxiD+9 zntLat+3+Yvr3`n>P5&r;b=04@Q!CNh>dtG~o%&t-F2=>9)56|!8Z}A)iR#$d<^y#f z{)>r4>t+t5m@NKn+uH8v6%|TbOt6_9y&P=CGTgyNCKq1X$|8+V-oE3rH+LMjjim5U zD{Vfpy*?p5^X~qEpa10NkWYMgS^4Svf5L}vzvTV5UzxC}XSyV`yJO1V|2*87z4>D2 z2KgP*-lwEAoQ#fQl}av!Prvw-!(m3nhEflACnCYza<%fq$B$k+J2k`ek0*aYQ@mz< zs{Uh_@Kwo?V%nY1F(`AEy*G<7{8&66ZXzIJ^k*sUZpRn{(=l+GXL9L0Je)WkGH>sX zym`3ii!VOs?*5+R>5fl7|J)b5BZJf|t)cup6$@n9@D~G|Z|AAA>J3 zATUj3N3{0dt)ao%*G0|i9#c>C<XpX@CwT7qn!$HcBkiUP-O-a=!3!d!=u6cf{skiwQ^W(FAk#0LhH@q;7Kt zRFT-+v2DWZki7N`*9`Hff=Jxs6awtatrV}=_N3!ZG*OK`Q??{YIqx~IX$NCNyUC@s zS;&YSFadbk(csuDWGn=Ck9PkNkv=*(WcLCcuwSc$UAj$%qVupvW_@O#{_#sQPONZXvzl95zCLpRcE6X?U5b(M~6DXIS!iYSXPH?WN6u$le$MyYP?3ETCH_`Uq@7tn^@WM zO)c!m{HwQIjR+k^c71S~py8^e2WG53oJOzfi)tJtuQtBk@$BOT8We_3yGE{v7=1v( z9u2sAiNwuCA?!0k6FryGDIZOh^SU@G(TyG=d+c1ufx4Z|(duK#iO9^xgqQ=+QP(Q&GWTc2 zmhUDSRGn<85i_Ywc3-*S%7pNRoZ{}RHg&!i(%)_?1HKxT)t9gy3-&`RIjN}QsCwJiOuI__ix_v_~sqc!y`+xT0;EX zk4`K@Iw?oaIYAZfjwfb}GMI}wyVtgII?O!YotTq3X4~bJB#GWTb4aXPqpmhL4iQo* z_C1+p0U+3P@4SI|Syj{fAm{Q);bZ@cqI1RxfcyQ{QcXl4Z+C$z6dRJ4GQ%OVG z<4{J8q#^7SS6hb9L^qXo=^UoX-h%+ZKtI16chO}Ik-LZHJ6Wan?&J?4M-%M!W(e{a z0@9iZ_LC8CaCCU@jaoagSGHwgT{f2bJ?mzeZ;Kv_G+)%O%}8t}v~x0>pDC>gTeW;` z5hF2Mtd#6KsMu}po>Qi^m0DMx*2;7!eDn0eH%}i~-aj!PY>8`hv}osUvX}zApN`s5 zF?yr!GxkDyxHpp@it0++Y_`^(z9z+BWS$(EbKa?;;${X-nHt{^iV3c!5G^0R1R&GX z5F<})+iJC87sB1a`h);4`a)1UE9Pn1&y}`rCi0Ci9}cX`LX*lg6(nA1G`){`laR#C zz{)QA?Y((~b0Fs6_e3{=Hqa=X#w<-+~liLbtS&sHnnzJDQ1C-UKeJl&I1;QJrG;c~e^ z_dRcyeg9j%X5J}dNdJ!5XBwwAZp-$mQ;6h?y%7x|KnRVUB|Mp+~ z_bmO)^W}-}-oNtld}hv>k_#c2njUv>YZL!4ZZbF>gOezv2q{}>WzIocjEZBK_hE|< zY-C4U8^-%8hqkP2lkoo6nL<}*{lH9nj)*rDq5~<#uw(BS5xGj(d>~^vRME3KH2RV* z<|{&FV2jWl#W&nhR`A>`dxL`+WekmE$H5zTZ6J?)@Fj+{ykhnJFc;agn+&63E{%CI z-0^XqndZq*E_+imnyF@4RpJF|w1zmVsdVpi;_fg*P--uxD$aqaD2HQVJ{*WKvDV1? z5P5l%cz&JPYGtdH*UJmfPYbWlm6|r1Bs9#l*4gS}gD)MpC|}?=*|XGAyf#&(n98<` zT!jiF_b3dd?#K$~I-DGzcXZ@)aD1@ZL22&ygeIsMg0zZZ*IR2y6E((gLCEgV8SbML?4s5E z-0|c*>GWpt0n2K|iL^7@y0KgqmSy$qnaa!S!pG+q)^%g6oy!o^_&b$iwkVU>nM>q; zO1zmea|}#UnYULa*~rV2HKb1yI;DNk(gLhWxtu2A-96#%o_>GNaymJZ&5Mk9CH0L= zyy0M~DYE2b?gJ9FL&wm>ra-%6nlsJMk-ls!TzJ1ke*5hUugitLZfq}$xralfwgfTL zleyD-U6HzxMjVF^*hCFY8#TIGHta%k%qpcU#^NZsfXQq@Wbmj!dc_Pn8 zk0rozvVu}+d%l_>h;S#B-H~mpes@N5esrZu;|gA(001BWNkl!h~v{SV)Ae0R_B&AlxoMM*NzV?eXHptf3(ocPHX2ma)XL}KFU zFaCu8>sN(;_wT;tAHMpQzyJFuK$s4N$2WJpd-sli^Dq7t4|jKb_ws?i`G?=~*T4CS z_aD!sRY++fgpAd>jf{Z*r+P-DN+n|NYEt1e74D`&j(CiNAa$iLjpaG>{>xvpe35v( z|BO&8%i2sxf|9E8Fdu23zGZ&<$m7=e@cc?Wue_NGkEaKY$C*4Ik?H6Jl$A|7%lU=Z z=a0NRJ#l_{WnHUB|0K?9rELq3hfFLJB^K^VA(VpVY}i>W(&4VmOfhnQIB-18kW2wB z+NoP*xoljvGs{-(jze}doLgu}Il23(^X{`xA!XiweB!*VEM17wDcbBVj-Hd*l;f0% zsY4D%a!7^>&8d^PpuKZBzmm2K4=6=CQ$KTFKhSHXw!$TxsWQ__;{5!IZqH1iGGStZ zPwwtGJbuEfJn;M9{f4i;`+@UvCQq5BN}lfs6iS@;e7=+>%6x>B&20kiB2ymn%%6Ss z1%LJDf5FFZzT*$S`_H_8dPOAg`P)x8q!Y({p!UwHN==DK=OAaxSSuFk(0yj zS!oM|;tsys!TXN4RerS49L;}kja2gPG9JTX4S}ZkV$y2t=vc>~0cn|9t7nX5)>;kK zni4rqgdCkqGZ2-LG?@@A_&qPa#|Qv-E#Ee(eP&7B^@9<#qTeqi2UCzA3iEMd zI?T+|(a0Io#N*+CseE9oD^Et z1`Y|PsL&gVxwW>w(S@6;+={u*&A-a1V8Uydi7^&n`1f1Iq6W-)ZiFH*)OaY7 zGFwDS&c&B&_!7+O@R(e`o9&(3RM;f3R%LYz{0aEQpZ*Dd_0Rt;|I`2cKhgtSHmlh_ zeEAh0*OmFZuaVCe{^`$t&cFO;f5qoN`6p}=`1-rA`TM{76~F%5zvlVr%+i!D5$#Hs zMjaWrVmN$h%@^G4F0?W1wYDi;2Z$oHZDBr6obK+pzdw>u9;d?3KK+dQX=dFvzW?xn zzx(a)xUA-oYg;F|$f?I}Zj;@?iPz=g@nEsmH+RE!1*qS1-v&d?WlwLVbiN`51%}3H7kBjL$ZKETwAM_l=|l$cIF(u}+q!aDH$HC4^V19e{%?N8-~Fe* z=lk~`ZJFmpJ3JRTdEMM=<@KXEF6%-J6)6$Y zgzX%eJma%1QRS9YWhlg$N`#I zZx_{;KB0F~w0Tf#vd?D_|4at4&rdJ)c)sLs;kdUTVU%(oAvy_8_2$_R@ru{?^J^GS z){zaaH}W~{1YatRizhhwFWmh59SG6S$ZnC2wO+B=Mn3fap5pGrw;A5mHLNk&P;(by zc~16T5xJAADse7X14iA5y^(ydxePJJ6*pzGrs2*Klu@@!`CS=b$)txAra$?FEojS=Ej$Z_Ck9=(-0}xYiD}#O%I;O&$GNNkHQf^?X+lI7Gi1kXnq?2>D zMb4muc~omm$%I=mk&{ukO3BPeqtF%?;>~%on)zmq+*O>iqLiFSin(+{U}GitofLhh zo1NGqz3)p>^$4FKJW<~R9FEs0l<1Tz75ffjNZ#`~Jv5DZawDZk>%zq|u!jTnHrB%y zw`G4{Yin%VMr)02tE}tBl#Ui{_2CCz&o87Hxxahh%_r|T-M^u0vc;>LWV$bdB4Ue9 z5wtb#%8{&!IIVNjm861nSkD`24rDcleH^S=Rf@?cs!f<;f@TX(NEY+uae4O61yr-| zfE39mdb%1}hQN26H(k{zFyhg?DH(aiYwJzTEF*h#l0C=4jj2JeWE+qj$-O@(x>{6p zP%?qytS~lP;AMO{?@nJ4m__#3jf5F>TCdQpj_TbAh#HLC(H&vmtC<3})@jQ|T^5#} zSr;StE$g<=rd5@#%3jBcQ$x1a_dCW2C~c?mMU@>r-Ut0DMx_SY)$w*nPi|_R8jDE-uJCTVY?s5Sn zqD&?2X1rRfiP5Kt*2QNfw);pyU2p-tR$5)rR_S8>+_r8W3uOd@;G~5ZvWs}2N$@=m zESu5)V)T7nH#4nv+3&>=B3UjL2{}!WqJRFvx^0|aR*P{6$&3ur&~3AMbvn{|B!^{Ue7k5tz6fI~x&+d8Z!q zS`lf`JH0mOLJG>7I+w7KfBhf-p4XQr&aW?g|MAT4zx}{>-@kBvUa70ln<<)7v`w_- zC1QCTQG9R{5lw!`s$HZdvsOcnA$?bL>P-%}dRgMf;#J{17T2za<|ecHNB`Vzb+%@u z%QeG(M_ozZhdwHDgSKZs_(KTpgwZRi%n$6bG#{6~;eN$jF2bH+5Hz3OkJfNjA)0McI1eQdgc{Ua4!R*N)0&L)yMiAot82!_f!-^TUsXBQco+A>~Yr zoe-K2EUzluG2n^unje-z;Ft@?sc@VR9OsESC(rrp#FD&d(OrF?gXMdTKaXfFRCk47 zANWsKX^ohL=qj{<&XV5El`*DbUaV|DPli2?{;uB8x^1l6LWqG}GSY1*)vG;|hK!u~|Hyi?9$B(1JMTOD3})sY5qHd?GOMzy)nc_D)14#S-OSFUhrP|*Z|k8@Rhe10JUqZ?Cc)#P; zf+6WQ2YznGk;c zR9A&+)y(3Ma9&7ZK#W|`=u~q!B8k!+5=O*+_Y#D0Oh^!X3J^TMeEPue-+$mg{OM23 zPfv`k<12^@bUknhT^DODIL>9Il$GRkV3{|ELGFktIW=QWbI@GV!9lZSTX2C=g`5lX zvO;gntup6@O1EM#FdhyJhdW54E=ygo=1v!(fll*^pr&UXhV($~p56;CYv$_HJabxR z&hv$ikfxF64-Y(hm`E|w<4Cc4qc#U&)L~lN#AR*I*{k;Ki#W@?-89A7u)~Ex91qjP zc4XHmL*)~X z{P_4s9xrb>)iZC;XG$FS`uRQo{2%=jUVrf|U;XCS9FKvtyx?8%4FBm5)VCk;QHLb8 zIGhB$sS%w;Su{~6Os$O=Ar1kLDvyH+M%r5KJ8CTRg`eKM=Z`=C!f!r)VtjqnCd<^w zr}K%+a;9~7I7~c`34G-7Y3A~H!k5gmDe~+vavTFSWJ>F-4K4*P#q)Odygdo;-WTRe z-m|}h7nW6((0yHTZY5SvaN}NVTLdL*ca+{(#ZY{r?y)r&mL;>yE0@zrb3)BU&@FaB z@pPRSBI7vn?Ki*XumAkdadG5N@89wB<0F6n^Ur*G`-c0=i9RUyum_KCBf>=Q8c))E zWbK`@HgYLkmYHQ;xGW3Lh80M(ape2+iQm8Zg<2{v?(cZ%9e3U{pB8@l`7M{ng@@^m z;qZ*%{*L48ueg7Bz0O~!?R>G;4f zM2m~?u#JyhZ zyHL-I+jC1hjwQFNXl|o4-8#CQqd1}3`>{o}^gSb47r~oQD;O1LuXVkri;&Tby4tla zblB2vYd_2uxScr_)Qyd(bFFuC((l1TMijD~aOYG6WN|vKHmF6hxVv-H*>WFz)qig3 zt45qO_TjmBUC_`9HQVqksKZl&p7V2UEKet%@&_eh90erk0vE@e7ahnX9T@IB6cfw^ zQ8j*Vk)^H7bLH}}^Wo<+r>Dwg3jF->#1HR2@!`{3e)zC3-o2pb1Brp)k6b=g+MpZb zmRHJ_eRfj=)|S@b2AP{@;&3;4hyb zEDTBbZ(a}li?1K}^>1F{hCBYtu`zx;65c)VHWqU8T=KSDjGVjQ(dfNaty%lFNbUOq zVq3O(r&#&6ErQ#Ki^AdIz}@jcpfiXt_J)*ZKVGgm6^(J@DmJ0fmLo4)M41r|mf%Upd!8L$5W7Mp8&YSEdH zZzc#g`r-DFJJ+esaNHZ2W6Mc5ft;AwYjgK+&z}Ma8n|RbY(v0{3DdXbSW|l)CTaz7!-?s=1WT)-yZubxnQSraC z4_a%5%W^Z2?|T-rSl@NASZ!;nJ5U4fpb7>Jr@ZRVQO2vu2 z=p13o)#}jAG}d}UOz4Y)kTjk!O(PEv4;nl8{K3%N6HXk{cqFBP+8Qa1`a81sV2cOX z)RQ`IRY8=q&zx5!u{yMW)(9+J{CUf=-j?~!7NJ|6gm|3a=n$UnR0Qh1ki5>e>bmMo z;e2A5XL4P3F;a)5orvA;qv7wpf*7kd!NWIhSbGrC$E{y<-%dq^Z4}iiS;sD9AaRaCem0xPI>0EoH^d#GsMU@Uw_SuXD>O7&-8}vmGgQgpMN1= zE-cGJ=^6)$%>&CewX)C8z$Y-%jaTg0dn z(r`30nTgxIl6ZpGsNC)T-eNyQ5ea*Y&Jf%#hSGVTMd;l0@K=MU?lQdF%zll4k}I^= z*+gy{wZqLUd9NR>9^N8;l8ZLW$`)Ix=98M&{SFV&QA^(=LR#;Hm}tH6a6jS2v&@;i z6nd%pyLOI3rFlmNBu2Uqq&V8Msb&tb`KW4|isIB|bNlMzo`i{b62(MeyNC&mbQQPV z2TJT~wU|Il=`iQZnYFB3mcqQwq$G^Tfrsf!#&M!{%^=4K^Rgl_Fn0Z}x806D{|>2D zdtEPh(YvQLSc>*(6^9!~T2B-a{a&5M3p`yGhINZU7LdxaMJS6?GOmw!i8vofY0&*h z@DvK&I~w-?**j7bTfj%F&0>(u9HC1Xaw}KS8jZZ3sdmTo9(8_cCbHg?N?b~(Hplst z&2(8cCb(-XkK69opm$=od2hGo|FW+3{JC9BQA(jKt46dn?RD??r09J6eKwj>yi5@E z@vKYx^|I1S#sPH3m7|C14ADOPL}#bvWIX#>&j&_KuG*%Aw=CJM_@`@O6Jw&q04?8SvREN4*Q~DYZopuh zGLe5!up0(@^J=sm!6>^*$$hqDcQ+~WHw$<#H|Rza1nfoDbwIWKEMlnnt*BQsf~T}X zcjcPfzP+jrQy81u!5mmyj!4^){Qm&HW*6F?SG}o>o1(T|?_4eyTFzWzFqfMrMO6`c zQ3Yu3O|zVO!#7p>wqEH166_^h7LtP)JZs6!C2LdcBhg1KE*ko>7!q|UitH@;Ebpgplr;frso}h2> z3_7&&UO&Tr=(+8$$<;*={Eji%o;`QdFe{40@N}>%LB0K%rD>tEKWE#p(vW5}2vHI8 ztv8l6D>=b?!-N+ksT)58(L=?Fs=1n~S+`4mR{&bmCO!~{VRwkclo$-j)2gC{#&J|v z$eM`~I2?`~$0K*gI|z=a^@;QO%;|D6WRNPq)@9M}xiv)uhoBBFbHu2j+I@es4y?rlhuFYis=V9nXdbxJ}kCGudK_;ddWNXxNpP;yD&C1m;TQ-hrhF&WK|d@NPph@v5x!!DGx$9WL2@AQF&aAi9RXTxg|}ye9>9xO6Jc zsjJkr>i|!B!<#y_8YW*F)FwM2W?e6=ts=)eyr6mP$C2^)uw$cr>$sv$Tc0Z-c+wEL ztg|BQdo%o)3Yn-D>!PcEU3Q66VMfi=^O-iUh~=iPy(+fI=rhA%-}C){`dfnY1m`GXZXUNe&6_T3DyC>}h*Wy>`fTTg zRx)8!p@{QQop+`4_UXhgAD@_W=l*zPEsb-2qV%duHm8|X(MJ+ObP>EGB+s&3SROyI z9uh-PlvHiqvi$;w;~lSkVmjP2O-DXGo_O=~8$LX}SBKl>LaCjc3!`)N=y8|E(>iln zE-X2dmlfX|!ODeVOXPy%JnI0DX6n&iIm7{PO|f`ktt+J$QgnRv>^WaQ9P#ge;j6EI z#o^h=AZH%WnRh?@!sDm24*o;pa5yrgQ3ZDCh))CE2OXBgNOi)vWY#4U$BFx4;9(l6 zDRGPsd~^7Um(QQ`^5KptX&&5U)W{lfR_ z!ufRO=bt~|+&~xtgwDk^EJZ;>Z|#}`z6lq-Wx#F(roJx^EOXDRaHF-ZZUGyZ1{-K%i+JfKD&1>t zXDBtxuijk8Tg}>=5=r)2L^IB|yv4O=axZ}B(9@hJyW7e#x3HY`%Bslb-dM}#;PAAX zcL#|s9V&CqoG(v2oz8>;EqYp1bX_-*?phqqHFdmMwzxP?^a1BR9Ytm4nyDpo$qP^J z#Ix@nNFTr9FTegZU%vjDA08uf7`glIl`f2mGa>0G0z&Zz#4zBSz@mwcfF0(hM(d8j zb-sAgI0=Piy!df)1Y!Js%Dr>Ez+(=P}vCRj? za{EN^qt?PrbZu^f;*Ff(*s^1{O=#bj-x0~ZR~zEWj`oy2_xd^t+hp0-2{`+*p3j2PoS|a3zqk2T^Jc_kePpa;NSlrAGn_e-oJmxr_-5>BtHK91HBe}bWCk!Dl_HNJ63t( zcYpUc{NX?U9rM#C>Uv=qh9Z}0001BWNkl*NicX#9Z9o9Vx%!CkJ z9YlLhyJ&5mWJK_kQkmB??>;C3f61BCvhtyJs`t1U$zEwBu67QyW!uX&&S}e8-&*p` zVbnJsh+}tOg(6%D>RVp8Ihb16mtoFme6k_!yFl<9zkR{$*RT2dn_qF5Mjq}CjLCC1 zjU*a(Y2f+&fu1wPdwR}x1ZQ)0uKP^EZJoW@ANMJPEq^<<{HYG#1;Mih%T2V5+q$Tj z3SSZ5@kt#!u}zHiDo7RyiVP16VL0+vUHJI4(8I{v4^N!V7v8@Az~j>+^Sm%$7ISKP zGz>)hsjq*1&%!Vg;%dfuW!$FI4 zl5hO0? zh}40I_TXikFW75dEgQU=h-Aa$bmz?_s0-z*h^QMeNf$g~`i|Nw`>oM$&q((fa({E6 zIp=oq{gv2r{ciS%k!>-(pObwjo`};pDl8Tw8d2|d;a^J8+OaB~&6@1p$zGh`n#0jZ zKA7N62vO%uyTYe=Y0`% zOBrb9)bGw>Jw!8pqxF&Eu90T$b_;z=Q@M#jyp2u31p8ea!6lq)MCVDiG!`7m1;u06 z&J?u<(CCQ@wGv8T65)Oq5jKm{*#tIapW$Jo7hj7;WR<*U$>yN%O|AB?QpxLTxzIXS zP{;8`nkzQLP%>%D7Lj3QpK1{})v2>ge4slW`uFPgrS=vxQ+=jy&qULJh${`ETlDEh zwKO4us0zh3M;{{2oZ%KNt#do0a@3-6P>OK2$U*Pbu+=+5(mpf!V2gTh^@{AmQ_YXn z8DX^-_ppf!lDffF3$Z1JE4IAanbdEcUfUAG+=fGo6cf7PY?<4dW)SO;iFH}PoQ}Qa zU9=}#KJY%Xv&d8vRUwUP2ygwqHNq;mh9LOv$Ys?yyPKEC-AtH6O8D;gT1XwcD4B3w zc_@a7Pl`@%alwrPg9tHRbf(!Ffu_q`FXk@Svuq6w--RG*uBa6HYB*)9pPUnL^-ANq zS=x)+#6nx5t>=nW^vw3Wh7f33DJxG;Pl`(}g{PwU=2Ejh7qy$aUC*^rD=dqdpZo^S z-Rr{O2aN;SVtzzjlImd9<#uTrBk5zLRb3#~S~V7s%T88t_#HW4N+r+B=Mf~f^xuR; z>Nqay6g4tJv#jGyfW3(UORJc*PlY>Oe{NS7ytf!mi(7MSvAj;*qZXMM{MC)RWX;;1 zV&-r-(3+kp)t0_%N~B@nd^%HVW6jGhLTNGLhJjiHAF2u?oD%;+OoSm(N~6wj_uMlK zfxG9PXU`+vb=Ec0dm;PGqmL}1Qt;&7DF}pw^FgC1(L20~r+9~p+ULx*QJb*Jz|xA{ z#hT5Ic#7y;u}N2lv%ylGPd3j6=e^Bd4AV|j6J8U0T(`&z=)HiEj%BMCfaX@bm)*f0 zcB7`gXss0`&$Mc$)S$Jt#n@c>qVM~yGru*Z-FZ(hbr-BUXG=*V=d7*boRVEz$%uzq zvXWG)X0_MWh%sG7*Igfu9X&WEn8E{_TYnx#CF`G-H*zlF)`9reBKb9 z|J!BhS*=#vGW9eV^V`~S0a0NvcLPznc%8B2A_D2zDFSjetPo@7ba%;KpO^* zuYSXqzxoxWH2mSjebsl(`$%hge|6idY?S6Lrd`BA340vdR%feFQO{(or`aMNbziJ= zmD(!xRJg1QkL!h>9)DpeC&tP1eENnjUOq#L4o;S`(5RHyRIE@fesVf$9hgUb$E+{O zS!=~?w36_rAKp-%BRfZNUhzH>6x&#;y1whC&fC=8hD8*wjY%u&ETtQUx0}LCQ9Qfa z$g$@%Z0cPRXx*BHn>_lx$?4az&hC!bYnpyjo{K3Y`&Qy#Gx7S3Vt!MHZh33oP?c`O zFJc$Be%=sN*;qDpsRo*M zsSHcy>HSBR^$CB7)F#ZQ!r+CYzmTNSWWuGSL!W6P91ai%s_S&|G^gr9pDPi~^iXI+ zqh+U8sHxQyX{8F=5QHH{#-z%ArmG5B#E|4cSo?x2ndB>NxzI12LrRS9K$<-6E>a~j z7lCJ^BAZ*bY@#je&JOy%6`__RXF2Bde}D;%#eR;e0-`E;FuH%P`gFH0R8^X6AXd!c-6Dby=@Df<`XL zwUfId+DgrPelX4%Wx^KrH<)D232$`EbL^Hc)Y^?}MEpK**#=wATV`X`pBU6Nl8Y{E zQm{pO&6HY9eA4mJvaC1DNb81RQun@!kxi^)s_&jmktmA8&Ko8+28c-=VF{y{L;A6%|#d zWvpgpflGodA7)qit~kxV>vDS@%x|n(;t53hwtC<_&C#>=Zoj}eD~Ww;~#(bHNSrKg4g#G zzOB^rLX^rNI@BoZ0$~8Z2(%XrptSWYh0cbmFua-={Twog+gqN@RkH7ytzx(5V=JDM-O76IEqAUUhT!=-Ldbyx=UF}ha+9pE9{+TXabGYj@ zilv%6NHJ%cMTrF4)v7CVqcm(@!+inT&4*;e_N`uxH%orkMHdfFsW+P=c0&d$D!J{; z+!&)awM{8Qo1j)~q1LN|rVVngPMtFyeD~ZR?-*jj11YJh-QA!lr(wj2x0PB%O0JaBxzw3;ITO4v z3@X5j3UO2dk2xsihREWq+3{ARI&Sdi_I}*DQ)L%UyWMr~Y|UP8>Sb#CxemQ`fV}m6TYt83g*F7UzPnvXLVe4f+_2*Px)izY$*8U3dtqzu z=#I%S-Cp#2DAs3YCv@+r0s0W|&kh{EdP#cyKst^@ucFU9M?PJOA}zber-||5o+_RX zr!zmkdBgWV|H6EJV#$Rm>N~#W==LVpNDgzmVG%sdT`2+0n^V2BBd!ab)>eG?hFV>~ z#L?@Pi)=OBSFc9Wcwjo-BPnrNXC5CYMqQgQ!ns7K>h~X2%aNu~~m}=*E7WP1i-A)l{2P^!h9mI!*Ug(rCUClIM6h@~poya)S-?#haVX(PSg0+442` z-F@yuz{f}$AMg@5pJzUO)ZzNa$0vUJ`6qt-`A2^L$G_*}r;mJkRFu7!z#xej6USlX zufBUhKYWW{kNhB>zy7mt`JevzKjp>iulc(lpLl*4xtj)j*NJ2B9zvqf5Z^hjDp(6e zvnH!=)H)-r5WUVp#xe1b)J1=cf;Z7qaSlOI@!R>|Hsr3`a+;yY7R?~ngQ9gxhuM3h znH4%=H%;hW04*zSa>G^zi>2s6MLSEY_U`I)&CStLT0y*nCbpQ5DgZOo?M86WzOL{0 zb+^N`^$mfni#xsky~a&6U5++)B}`xu2F)1?e>HD_nw5o_LA~K6P&u(KcKQrqXQ+P)cTSn`Kc;E9`P=RH= z4Qc&MB*Bi~-=44Sc@jD464MF3sDsMmXxXvMj{N9Z)3C=xNU!9Q>8&sh15>ZIY;)$^ zZ8R(*qm{khi!tf~yJ~s4Kmt{PFw@`4?U5f9aY+)UWA-TkFxC8wyxm$IK1CJ^%aTx`zMS}M5|bzrwjDHh+b zMU|SkOY1AIdCFZQ_qM$3>nvIgJ!-gEAs3u1$kU1Rbix7C{5Paw;CMLhF*3uL_FVfl z&no;B{hmWK+Nu4{B&wJn@6FT_$h9Z|#psr`n`pueB%41;&zkEJ-8FrFRd^RH;=)|9 zUPV_TLZO?eBT(CF^Hxtv$rhBsob(!f)+_YVt@dP})0I*Pp+TEk`U)6Q6bh5G7t|wnl#GUCh`O@Ig&q znxpLPUFvuj^nF>*`e&y|h*3X-xzW8Byw`=Uw@mA;UM{6_Sr_UwF^oD3jy9W9F`&MG zYgR$xn)7wGESf3bJCxAy-4P{_oM%WQ!!R9$}h~q9!y#477^Z87#jwFub zi)Vyf7^}uj6t8iAE)FzNb08*sR_cu|f@3z(lDpG)Ya_q7E!(^86d$n}7~+gFr|&6V z7@W@VHN#!+(IgI9)s0InDjsxA`*(|1+sG5%Vr08f19nldE{`l~(}W}Tb10?D>U{hz zjBPh!Vije2>tAefjm`nPE%1}EBt$l&K;L^Fjqwn|ur2GYCfbN!T_b9r*?U#&bC<5M zr)`UcF~~2isGz^8I4RFFd6{|4ol9|C*2=OLYToXyt8j%@_PDXOMO)b&EM1?k?ki5( z?N$!&qCQt4GG83M4V>perzSoS(><+Rs8TsEv-TVgLQIr3)0{BHk>jky;-6%|tFR+J#(I*m&0l}e)}$7Lxj5iX^XQev%z zc+?o6@)4FfkV~SLNbU>Wby9ECT!=yAOiR)G8xxQWVPKGf?voLZ0#1U}*DA1xF5-M7 zAf!?2?!hm#@<=OsHys#xH9lv}6~78DI*Ww#l&UnojgGYmW4AirnV3#Yu)iC_Ew<9p z2+7gyUT-a5cgn4*_?v{Ik}1^SnAb|`ftoYR<5?p*x)M-J06!jaA(GS&K#9S6*+#Va ze9eWnEcDiS`tXTd6X8#N#nleE(PcxQ7S_t11FeUWAtJlECHj^rqK*UCvNb9~>kBon zv|0#OsMf6++ECV81LOOyQom+y?NG$6^fru5`wUsehJNI8)YzV<#nq|1Asx3&BW+f$ znOWktjL4hul56CyC}=h`c5MU`AK-=66?z4!ibmMxt<{>04KG=35V$omTF_%KHg$uT z`AG4FDE+SDEZ?!?y)Z~*7>|s{1AZE)-VtaFU2}<+YS94p1qbICeBjx1;9;5=qGlWU z!K)kJdz!3NPIx~trUQeEj9wjSi>o}&nYSMwS$k)E@sgGvc=P6jUuO>OJuc6zMICD? z1jaZLQ_^O)nu^GjOszLXK8Uj{E&T-sbM$M5h_{Op&GxPtUvw}Wya(^7ealvI5QWq` zrKu9pwT1QaNU9^j7rd+t=}74gZ|;WCReJPzNvhzB?W)v9_AIMyK)8>Xb|k;yG&hW` z|6ED$`i6#8ia>CVTq`~*7Jg+RFU!98Z`NvO>79>P=e?J-RU4t0t_)Saa9QW zT?M$ISY^L#8qM%gs+Pt?CL3bS?M_K`RODK0NT>yHEzMjOnzQGF=i%W_mB&ue_Js~1 zP~FuDluI@BZ@~L#Im1ndhr?)jPx>DD4z*OC&L`fVA9*~VkXA^w@*)QAQlztzDLjAv zz?b(AeDUfvuV234i=951HmO(F@T>LUbJBK&|k6IPmK31?A#+7GJO~3-dBl zduQ;0sSEdqi5B5wR^mV?1<9sT+%pc2^+d@c%T%t*c{@(G>%N;ME61wNK2;p zK$A$Lak?yg`gnq;6?d0(8Mo0AY(b`xGJSUxI&;2ybQt2qF%BasB}y)eSaO=pQ`bW9 zi7`!Jm?^vv!l1cSS%(H9FpN=20Om{#L6JkPc}48S$kXYGHy@w)(@!6G_vyl%Eeln2 zQ7o==H$;B*>IMJk&%Wa?|EoXe{xEV`W*Um=F6)XH6=%4%YVl1434A}gM zBmeeqe&p|eoS6=KmqM;#c2(Nx1)r`{8F!AD^f`ur$xII&g(jL6`|iD3=o< zCe}5RbK#Oc;CtiH8*A;HmxZ$n(?2;*Ni)5^c>NXW z_!U3Dd!%PipM>GnfgwyZ9BE86?{G;Gi{3%IEJ#t@Z*Yzm&z^Dgkyqi4d49ubS#XDm z>Z1-LOV%Yq6Q&k$bS|!tU14%%&uMMDuX3wHO$=hqt24CZ78SdTs&A-J>a(l6=+te> zcyrO?JQ=7?>zQbS<3`?xT{>9TJIRae1Lkchqz()nEQ`4Vt1WSN(W=$mZi^AJs?!;| zdJ_`JmWAA5wX9y;mbt3$*FG3`qApW&Zns<&{ca8~ZpcF3vW8pj>gc|~O-J#_cwkBc zy{&qd8Io0VAJxsJh+f+&+*)gzinAPzX7yuq!u>ch-Q7dUe0lf4aXNCjT#&lps(92Oy*M#_Wc%#@TT#(k2e&;A%z8=sKXA~y z+pf;sDO;V5AuQ!HSO4_@vOrD0-Q2*)bthnL%edX@*sne3=JwvHOWP;wSG>O5W*oN- zQ|tCvEJr6uyt3Y$Q2e0(JFNF=jaCf>N@ch^5MDgz+rRo3eD}}(d;a>L{bR-$c|1Sz z```VZfBk>{U$|b#>l1D~FpQ2NI(&fER-ANV@{}SVQ4zb<>cOHeF(r2x$!Uvf6NfiP z%1(aRA_`hnA+G~Dcq%JTPiK7V9LA9$M$Owrm$be)4q@P7I`Hb*bH=+P?@k~2sVsay zjXSzqozW^15w|Vk?I6)G@z2B^dS=ucN3msfvSm~oWw{PGLs z%^TLI6LnckbfWdMPXj|~xIcVgdR}<>>5rV=|BmwgC*J%`;nVN$`Q49CeBm#Ah((u9 z>w;sYEobece1}zQrVCGo6Bcs->#(->PK=TB$G5l+r}NWQbXFVP_g%ogz3n?%-KoGT zmtu4PcNIq9%t5E(P$y2EQb7<&c29ZMTn&YL6=asuDYZ~*GGmf zttQ0jk-f#(S~t!A-hwMO$*a;O9s8G2yA zBTajzmg}Ac?B)yWI;$&JATKtFt1HyV>eqrAj39S#iR#PRNqahga$mrN2g zgZ$8S=Hr9j!zslb*WFDBRh`2Hjcsv;7H3=TVgmo>_WOP#{dp&>>w0r{Zi1AutNT*F znu=^+u*W`O=UNO=anK>`=Xx?np9^893Ml2K8I>bk1s)sKajQiw{-T>GsyEW#dTEr>SmuRV8o8{yOS{$rrCOY6qZv8&B5Jaz zwoK{GStlT-X7MUY^7{9@Ic2vQh&YWpldh{93u4_*>V>ikDY}V(z4fa$YSAopTVN)y zb3I)!%PtHkMZ1gIba_y#I>Q%W@Qx*Wn*(IRVtC_hwRx*nTkG6FEEdr@NJW%7BDx55 zK2lm^INTA(L>j=wKnN2)#+|_G&6spCv5Vku=IuI14bF0wMRai$6AdM%ba4zRF*&8i zYK)}{)T3oer<6Vu`0cRVt~to`&ftwGn=Q)Gbu*KgTeMb6tJIRsxT2)fQuQ4u%Zq&x z>%G6}VML!9=XB|AVv`+_Zc#mLTgKhEZ=H$L#Ns34FsLA0JTXNzO$4QB6&aOu8lwta z_J0qE=zn&>GsoM&>;M2D07*naR1BkvekAUnV_h>T9CdD&i!R#NWp{yk@2O?Q2TwLv zd^4h}_iE%QuHs5Vikh+<;DWHM3$w+cMDdi%%y@WCmlNp_aZ7<%d664SuPWSfLUtbK zBQ8!F2Nn-IJ-8X=(+7cM&i5LvFLV2D#EH9#)r_{&d)TQC8f{~uN7=Hk zl38nmU=bfF(VHW94fyjhu~ebuZsLP(wmhRCI&X2^o{P&m$Jk?cJh2yAiwKVSe4&@F zv`ruBN#7sSTjNVHBdykWd*sg+X<#J7x|5Cc$!SgRlq?BnQ8U`>xU124dq$$)qhVA` zSyjAO8)wlwsn%*n0rtBb#E2pK-=fiNducjDs)b*ckIeII0zF;Wd+#lV*XI2u?%d*G zdaGLB*UGY9R5)H1n&`dlf~Oh*VC|JQ35Rq?Ulw9aOucgd>=~sNO7XN-c{+KDTnI^P zpMglu4p%+i5A-HV@e}=QnSq(Wlsi7?EURyEEv=a2y%2B=;<AQ+SyAt|8qg`!tf@-3z5W`*{w^A+kpi^t1 zl}5-<)cFLhFpURlZyXN?j&e^b9uF)%Q(NJ2t@zk!r;Jpg&y`Ox?9?wMpy)YPdjfXdg=(1?-gO)HnZw>x z!p@3JY(SFy6_wa;vOqCBv7+!~9}I1c&bDyh6+ec0uB}^B!R}=3Hl!41DqPuqfue30 z%Qkbnvbs{LX3x-c_!tb)uB;vf=;QmKk(amTLiVBLJ4IudwBf(;|ds zbc3d!wU>g#$`k{4(}WC>6??F45WKmqtL6J{525d!B%ZMpi)KTGNJHH}gB(b0 zAmTM=!d2oyxF5gd>uJ<@PGW@|C;m1H^lM|*PmFjw!lLg@Q59neb32| zjW}TUPZ0esvB6w3b-p-f%X{{G5HCWCqWN248>HzlE=B#_#u8zec&dTfLunVXG)Ssc zy3~qykz*KDDdvS)Js|d6UX^RAxUE-r&Q8(lJKEEE+0{6`RaoopoOT<6(kc2xoMUtj zXDDn}R(#8(Ee6jCYr8h~nnmAetyqy+tT664S8eDU7?S8>y{kGfqPRKNJHA2kf&0Ue z>_~J#W21Ugq!TUYKN#|^hmQ|hCT4p_;n4Kb8DxGszd|R%? zRIk;B5Zbtl9m1vQqP@1dtB+mpxU$+%tkFtQ(MjDrP>PhPhP2XL$b~g$TSzz7qK$R0 zmhDtE;;l2{Dp} zL4^htE4Fn=7rn1%1Z197Pdt$j+NVqumwK{ccxm=1T82%!=Zv4w8oyJhNyXb`xPKmCI$O zmx6QSXM(d$4rH-vIH!evb(+V7D2BiE$~cUSY2tDINLdy_g0d_WpBNLg(m0)-RM-(C zL=%R!mEH=`3xm7U%rK`vw|FJwREzAay)Zw1Mi5A@X&okN;646QI<7W}$gbA8*2cwt^n z%(YPFGtC<+cO00GcSvo#eDRX|XZJjR{fhB0@$mdP)8VLtCy9jnm)sSP3{Qk65GRTo zsdFKkD}_s=y+7f+Ix)RO#H%wU$js6mi_PkFF4c z;QGoxetqJ*ufHLAc>MT=lV2Dfbnu&VA;o~iV42`57R5I{eEP_{r$C!O5)odG5By;` zvc^cMjZnc$!^vV%MuCFoNeW&n(i+Da$R@nI8qqC_ZbK990#6+V1VhH|_$-JIgj&RCia``<~}~12LB^5@+XQazpPi$7j1Xg3g)a$hFVvlzx-Z#f=5=kyVqIo+X+;fwt|1V@s9AGP-S^>! z#3gQ9G%FFsJEf$E)6bdf3}qz=3|@z&(nZgMrYpwUbb%X1okcMQq9{^-8Y3@W-0|w= zGwzQg=ZkSd#H)z0drkdy=FYI6TV13ZdsKGh`{o+#o1=3hf%RK~v#{pOmxEnY9XUli{UiZa|MKatl4G`I!^;^`?HWagZ z+sXgWed#t~w5{GnbPCpPT&T@C`sV(02G{M^5!zmJo2&({WV8K^l+8KqX}#V^dHU|q z2*K{y-U$s{(}AUQPimdJ`+L5;f615sA7Ag%D@k@`>3#b=%*@^IBO>yhNfuS=>TY$n zP-s9s5TF`qpx>&22KoUS2!hmrG!P)E)v78Li%ll;9T7L~!`;p7yfoPRn0t^7<$y?# z$(wQA&CX-5wZ3)x2?yEXR;$}ORqp+s;r11~yI06=;{MGoaevRLY439p&v zQZh?Hh2DALbY76hcMNS|ISTKde&WOP6YzQy;+?Z$lRD`%b!J2R(k{sFtBl; zIW^tunsvxaG0e0X_fJM1;x zU@==26`h&!3(Ii9xVug4Qd)s`-k$Mb1THoGm#mlBI7E`m+c4jmAmt6YTyvqN1&Z$X z2(0VEY0V_-v;X;zf6L>CC;smL{`b5*A6ZU`5678v5q^F;lH-oYr7~CDucbgui6Fv$ zhw1y`4o@kt6p&D9=OZCT)^*kU z^x(IF^m(4O$2SDx%d;YvYcp{}tAMIeXgYz#Ijv^vr7$HYHShPe0L3Wxq-|6cOJ@hs zArf$w<^pJ5C^pC}O-=VL+g(pGwFVXDYQTyGu2T`jYi`ybe9p zK8S}yuTHS}=_dHpzF&{J8#kTLHlvuByG!Sbyu<^UORitXcp2w!UaR z*qTiYXy?>#}R zKF#h=Rwk+jb}0H3q zI$hCJwwP8Vbw=E%TW{0n&Url(R?+(ftr6rRD0-V;cBACBJ{jSw(`<{~JA9ux+8w9= zb7Gap4xxzFJJ+jwey8S0Pte!8kwRTI(P!m*CF%@cYJOKVD;>~!i@RlIgpbpPLs)E9zPU*e^ks*93#s}D5s z1Q(cwiP$o&+^Jw}H4n{#0>MmKts2>7t9{MeeLOo8Gh0!*YhIOoW54 z@=UI`@R*2WV7D6?2Q{L^FtX-MACiuKV4af%_#n5e=5uLSY2x1Xgg+{-0yYW-Aw}r20~P$SV@UxIrHwN@bt25Ra4OL z!3Z7*;s*P9W~hp0$_#;>_eehDyx%IFhH1bLQRBox&uCl4nwc1bz%)flDV$$@V4hE` zCGj}FF!_nybl`CF3HSRuXrACE8#?H`1(%4U8fPa7TOC=oaDh%!fKpiJOm*kJ zLGjP2;Dh2>ia02G;MWc!C}b7+isFP?blZiaCv9@6io@7kQc#>+A+TiYdv?{E^SPR? zVvF3?L9auNK+!BiyRi1Pt(QN;ZMDYMN2{1Uz_KN-+BUeYwQ6smbF}8c zYb$WIFI%_>@Vfi!;K>;(>hfo>VQXuYmW?tX7p`T!mV=&`;-NBoH~Txy!nj(@}K?{|K^)d z`Q`PQU;ZEX`=8%&2n#O2GVB?A;IP|4QD?FG>!_8D648;aD#WMlpzpSNgRGa+ z-aX|VF6ciWgJ+C`rR1ajtkKdO*835o5MyARCU!T6O*rsg7`$$Dr4)6X3TfNjiKvN*-Cd*^x6dNfkITEjRh zA`Hb>4U;q5>eWz9-l zIG;)9GbLsA)5x#Bi0p4BMTfV_Y0cUj83Nv;U3xxSs-$lC(khwYBV$?^+<=ePI}>0g zGwdq2!wtXs{J^@bn$Ehr=XO8w=F`{2n~7;R@#*2UUP|1crY~u%X=NNEVjV$>FB@2! zkjn?0L_!#N{rWW%Bfb`vv#^-Iy>IxX>3XDul-A)L8?IMG5r5tcUH#n*;_U#a6a=iU zz?+CsoFn@FUO8N-EJbT5(z-Hu&ln?X)dQmwOPo5ZJfLorm}bVZob}K@#K_IzhLSVq z0%?z7gidg!c@w5?^s`Unw~Qhqo!>lxxrd0=bU-__B{{pK4>kC z1ic*CrJ$Shv`6)UZ~yQu=MNwFll5`Kc?UxZsw;W&j@RSBuRr^gH@62K*O{6d zE@~(5l2*ut-8e7~1FdCp$-K-bK0G~gw?A+@9XN!M5zlG^9cPHO;O%qqdg+;VBfI^? z;bzbN?x1v!L_0M2`*DH4yMLuZEv#Xk;lom!b)i6yPawH%&}9BvsBY zFRXdxIKQx}X<9pILqj^P!aGj$!cqz^i*+?l5tdcYu_3FcFgA$M0-@r6~&R3Tc}HPhiSN~I7y7md)Agg?S=AQn zM~UKImC|K(Jwtj+^w&*1{0KuXy`sl&#zg&h%9fB6r^&pIfs=N%K-!qGcYyWWk$#?r z7^sbs=S>_GOWACu+TJ1BQTblBv_T&x1n&*SYF*LoA{01xIk#VbM_VNk6&LLMT(w&< zpJ&$NnU~`WzE!+yTJ^Kv(LzAlv<>7WJH!<=0(Bg`cUub4BZO9oK?n7@uAH79czJwB z%V(m8!S(^ngq)=z#Y6=!jKio@lv41maa$Py{L@$Lrjb-vt!W5>)#4Z0S?^4s zEehk4VaQfak zOCEc}GJ2?M{c5d&6=q>A<~FJV4m?qW?nN*JRe>P^87%n zj#?+4&Y9D)vL8pJW`ZJW8PXsAuLNS}`MP1}}9r zA>K@{_I&xC!bE5$aP{tUu{&1}W;p4UacU&VX7;b*5Gh30&!{%c?s#42a4oAzwic!_ zG7h2wqi;+xFb;}#ZcR3k#o0RV5OR(7J{uLlP^HC$7{S|F)5IGS{?~P7Nh|9~ld-vU zLr>k(;qFpvdNt^mZO3o4KNH0_W10z?-Ul;6=zEO6szq^P^#hg=b!L?5n?;2fF7+AZ zquLVvj#E;TFV$uk=u9gc)?4ox!LYt{6KtLIG+KaQZ-)dRY{+SG4T*(TW=gCS7szvD zbr8mZWnCG&wJgZdTG@n+loHc0=rDZExP(5Fxw6C+SM0N>Zd=2vm>}57)=RpX z+G-b=9l@|P>s$L(t4MAr)X<3n`0hpXu&gb*i6|T>t);%sMD@m~yn%8?t&BbasRGe`^C{1gx zs?CV2rPI5qOhg#P{Yl-WzO~uJ*l(n!2bUn{zS5 zHLOO+Dw{CqiQdFxv0B-hsk0h$iipnQywV57JsMHgZNj~NUY9ezcqLGZ=p7*Vp!n?4 zsNU+XoY%G1d!urByWh)JmDK0K#pb}ZR%T0(mQ*OK2AmWbuk{6`!jiM5ze`2@pcRQe znlM?nU_~)1Wot_NXqLFuf4|maHakyulTFK=U{zUGCDWLnK)aC9JC3-%_jH}XQo5a7 z+~8XA>P)Vg!eFK6BIj+TsE9(qnVkmYbIjmKxyS8v)fi9x;4}|wicev!7LxW@7Yx| zm+HSGNU$0wwa(>S5NFjm{SLBq87rE;_m*$>^FASw?&$ApDqI0@%n+~l&Q50YeXQHe zK)s3kZiDXkfT6S6E4Fm(yMN zsbWhO+uGP@#2SnO11*Rm&TBQwl73Qyhrvah?Aaf_;??ULhADEmy=6!X-s{?yQr3H^ z5js*XR=si|E;tu1L^%=il?hljgS(!+w7*?D0c!4gRJ$?-R?cps87)OC3G@z7`r}e% zyN~L3s(WDb7gB><)y;GrMyfs=sU+>H4*|cw)9UCp5K^}4!|O&Rzulo!XY|ktyWPNU z9IY2#i6>MT#>Q}W&)xZ+V0wktnizH0DgC>fRmAC8&dJ5-UrQwn-uAL$)ji(s+!l+f zx>gUtk(PzQdxqff!Li=la6BFP`Tbjd_xO&}GQ*nrWdDl$R};aF3}Li-g-pX+6?)Y< zzPnURE44)Lij}66-v;TpFgVYN#!6)zJiA?FFECO$&qt)JxY|fX!*J1ov}`n%y3w+o za~6j17ZZ(iI*_92op+ml(5SRh)nHLHa9#6CT2_`Naekh8`*7m%`N;lu$L-yh+}^xm zO~S*&3uSp`S(Z(Y?E&#R&$v8OSP9NCtY_{A&)5oskNB$Epi48kR>FVw&;ODvo)1gq zmv?XZ<#gix!z0U*w>}{6R2ZZQAx3Im5j&LS+8BbT3Jg(QCQ0pyDpXnO!F!?R46fm< z(@`Htb1hm2;MQ+uh}n+gsD`E5dO_^+)cdVU%aB#%>Id3w@uEAcdXmyalB!=)>tIO} zQgG_@t#Uy)HFMfg+ID#IUa=8)M>?nN-*?Goh+!eCVMQ$g+vI{=HFRKY)sUgGEyC`~ zO{N{Tc2GxMM0%LkMYI;}*si6}a;DhlAYPMKYgx%{B=|@e95JflzQ;h-*}rS*0)%)NP)cCZAIx(nP@CQkWuo?+GNNRThFd?&J#fOCjA76;f?YVg(*ac87GQA5I%w*~`(UUa2+>jhGS^|GIe4I)mIVZAPP3YC?10BTZA&CaT5TP|Mke)a3zu7xh zllH0Qva&3T^?7AV$}IDVbzTvK93$VQA35CJQ=20-m~*vFFXDaRcskR)(%x%#c8H4b z+l}Drd%R3|26oeq-TnqQ-0*b>xH#d*iJS`iamQhpXtk=yCXi_y=Q9CE;7rR}J#spo zc}6$J=)B>i9djxC<8S|gcRzg3Bn`hCXvMQeJD7_NH(Fs%E30Hu&6_i__q(>OUsVYt zh{|6bH{DZI#nnqfEM-kh1#cMLVscp_Q+a%OI?2}@A&-n8)h;mzvG)P zf6b?#{v+?c{gG1h)-|MV=(?q~nxV3E`wohfixKY!#TKGHk*+qVnK5|Yyn4kqUw+Q( zyIYoBU~MaTQ4FdF_CutJ8YN=RoR23?r=zCJcLye~3HTteiggeLX9u!o7l%v$cENMM z+w)I;{TBrB{P^x2Z~X(u^BH_aN~_}Kjxr4V!+-cafB61;hW&xlIn%09i)v5&xJwGXi@sEK z^6;U}+9(;gZlXmwxVUcB@os?ebGDy>@KooU3`k_P<|OJGE9m8Yui(x>)>n zF0C8=RW|2&YrU(_^bl!RoODZ~IW`2mtHq2;7c_21^0}sDA7|azJ?YZyG64|tC1KTv zbgdJ5Hq5;BSobnj*JyS=a$=ZObdb>KG%c%1!r6Jh3%D2ZR%?|QtM;OoMk@|{z{fxh zij5CJ7+vLNbhIEWteUXJ27(>O+thp{EhuKZUC0GFSDIvUOMLk7o}YgHp7ZeuN6|f@ zgSD&^ua_dZUPV(fZOul<*{M;v6y{~2wqj(m%xQV%cucH|p5ez~0I6En)y0_WdyHI! z@V3cAY`Dihxak8~iGkhWz!>!G?tf45%yB70X zyDEX-M0=YHqIV+QKavv7>})8Vt8dTL6=uR}ZESGuguIa@uCM|dTAD5Scm3B!itt8} zYgepxOKNIGOpjxl_}DI&|7KJy@s-+Cz^Go=Nbi+8HZbz)=AKuFd-lWL63&&=T6jDk zN%PA6{VSsPeDdZscl!g&%QNwC$6*+VlDYrQS3EtxQ06Bd2Blgxr^`JCsu@$dhy|CbYyl0E5sqJ}-S z={fb%0d51_9{UUC?LJgvsV$>Z>eA+HVD&1sW&8bqc9FS z@N^#yjP?9{I-RwSYh8GHd?Dq`)A5CA+VOIH!FkV`5_9;9}5;Fr_i}Kr)NSdZ-dx&F80O`D^r@V2O zSx~djSiGHUh?>r`nLwYNg%DK$3XW2f)r8coZgrX3 zPokfbnf6>-2t$LqK%J?1HS954|8cMSX;y_&D3+S8TgXQ`lGr}+A_^WvP|*MXV27_n(vtC0hwY$*l8AE-OJi=k=(D z?4oPSXTt--LU7tMNb?(zUjHJMm0*+)aaJgRXh6#1T&@3XnwHYO-*JoM#2zmhSroY<@EgQgJ%fQkni5iOo}1ap7LPi ziY+9ofsv?{iP~YVpGE)7nomqtOCu<$uXnO{5lGh#3lC+z=l6;YM2#USB}>z4*L^Ol z&Ps}sJ%c!c182diS`ySmll$$gk!Y+vbGC8#wB}N1WVQrJRs8v#yDC&8(*d zT4Pz0-g8>9s;pjG)6BS`<^!F9wM}?xt*W`m=@~2KjQBv=>d0ghsJloMhAZptmRQPFl&sGiLyi)(1^Hy89Y6z##p6zO> zYq)1U&)fY9s+rU@!P{Epxka0e>&#I@8lIcOo|~IJ({8lyOwWfgj>KUk2Avl~QImti zYptg^#0RQ{7fWfhEDS#2#~Y>~$WtPfqY|0jV6-F^>obOtM4!WK?|Z$v)4FaZ_lgh^ zGla`l!(6v{ynKXrw|(EL+O#rKJ~D}`wo{QS^O0=i580llZ9S5Y2{+Q$K07n^N|!Fi z_q&9yvAvpZg<()b#a1`g32K(e9*s`Wj6&CIeR};^ z>FWxR>z+7{oaPx9H7w=wqMDbFD(G7Eo%2D{+{3Vib=fKb-FD77O9Sa5v{-0B1?NXlG9p4WEl`8fy7vZB z&StRC;B>8(Y;%DjM0?gfSPg_29lYdq^Xsc_1hF=vi?nkkd+;v{_3?$68;8T5=X7G) z2gbdlwG*^NtE(dBZBE#X_~yO0u!N^)WTk01Q)v`R_uUM21u}dc&p5O8I-2-2L z`XzbRKbI#M`mYjv)b*%Tyaa;dqq}{->orjWiWuRxpNpL6xnXEQY3n(&uB*{EJbBH` zr5*ezm`^jO=QHo$e&BREL9N?9(Dxe=IHT{)L#^Bn5nrpC z4XWs$Cq^oV2{V?Wv=CrIl zoX@QDnUWIYsOTJNJL~c@hl?G?H}ssh9HdxJsJW9aX$tozJt4C7WQYyzJ2vE;6Z3iI z`23`3w05~TUmV?+-Y%$DDff$;$(tY`=)tozO@2tN7e}rQ33Sl4Ztm4>ShS(XWE-Lh zmjmi18gWUVIM+TJBXvi%Gtqy0)~qw5ltjZ(Br7I-$*egsr&`1)9Eor?$3zQm3t@4s#Ug6>C#Baa*Enj_e&&x9N_F-i|-E&$B zX?@|jKGSmHw7+E>25Q#+rDf3vJQc$**4_`{wvM~jnqs?clWv&tz!pwtNhoJ{ASar5 za87YTRVu^SxSJpl7^*{x@UQ>Vulbw*>aR)r&-udx{QigUi9^G+g(U}CyHzJ+TeJi9 z!owx#$k;kR>o$bAcJC=6%kcGWC7^C{CV?^1TF?uz_2|@o(DIk0UcZR1t>F!?-0?Gd z5i(&|DvpfghD(|1ttYzZyVI;^snSR`1lI(8#d}XmD`{4Ivbj&#N^1qmpa+s^ni%86 zFif}*Kq}D(nx6>cz>T}t?jvywA*!Lr2O#P*-TUz5f&x^>qF(lUC#o)x*6#$2J@sTA zMy;yLE`*>L!-6HO{bjQi!yDGZ*phCJIc+^_KG^qFwEsD!v~9+M(?c4G+Ex}5x_j@K zYl5VX5>gCh6Hm#8GYRI#t4b7ko;3yQMV-UmX$QRvf$X{|K>LxL4~V&)OIpe6OlGFm z!O&tVWVpum`-(aLa?a$G%z#uWrLyMChm&4}vN`ssl{Kxb>!Ro{s~}MFLuOr}BsEj) zr^pvBvTxd9@4|qKgQf(V$F)Em?ucwXr`m6-X2G}MLn9EKKH6r7*T(bl$PaJd^TSW?_`?rB z^4lMN;_1Awq%*(z53y;tyA04uVDNihO$Ye!%*}DZ-R(KPyy#~Z96nZ7 z*ElUlN?MtJnR$6RlTK$PU!)akBDaNe)+C0AIzHUxreSkHN( zP@TO~qUit~f+*=BmBNb`{?8wOW;o3}ofpn)b)?%3^iy#&0h&&6G-8}|$lwjn=NdWJjXJQU8Lo=co73S_ zF{(_p&am1$C^3|<)pf`=;L+nrwo9iHcdpWh)dqq6v(Uw!jIf#mKKRJ6_pBvTWu>H< zwQ5S!j|1y`CZ{t_l-N=2QdrkwsZKEi0JKu@S%pWWvbKfqe)^7|-u;YZtv1Sa){}z_ zI2TMrFWQ6FfVz@qO@Z!WU|m-}JU=tO^X$d|LHE|26OWG{`1zM7e);)(PRC~*UIZQL z>J7Yk z+{Yb<@xTx#LKwg`yfor8DOq98G_Od~)A6Llq?^VV9Osvr)AJKQ{P+{!|M7SH^5b{R zrx&8L4skAnG`Vs%^mI*6Qe3Zbazz!%mU1o&9FMc67x#{@zIns#&5oP<124-1ZVarh zQF6t*fx?Olqb7g!#r%knAXmN8y)#mWL5jI=h8Z5sz|fkSA)O0bA~uIijQ;ALe)&C| z(=_b5W}Y8kDC2;jL!;IlhjGW@Ca~L0Ow*_Xm>5{rMUnsWk=<@WmSXpoONBrmj5;T@ zoQN@)sXT47h+?SOl13Hja$;Q*rGc-NF*-Qxm;~adZ*V^F>U3t>A9#9sp&^{-GbxQM z>n+PV6MUqUl^86|j_ZM7rL}djs}K030S8*jxZ<@UD-K8qnzqd=Psaz=;Alb9&Q3f7 z9&dx78!xPD(j;uZk8f+b?wM&_wWcj)@~ZvNwPeauPfot@kvme_9FK)=wk?TfV|tUDy48@JjfSa$+haS?F=D6CcKCdmLgxg;d1G0FRI zsaB~MLs7x@^QLD4$@(njoT;5~RMlA7O%2@yU)L4FNKPtxiBnTbv7iQCXB1eP+qJf( z=v}W9abc_Z!$B^UOlELOV{1~k&g^SqiL>5YFU68j#a24q8){A3(VZ(X2ApeFIZ|*w z;6qSiWOdu!*?C8;nI112Er{b>uNd)+F)$3#L`z3<5!VXA75y`dBWJD7%r5e)&%WgT z(@%N*>NA4d@jv|!|0Bl_@7N82bUwqV`=#D(u8c2N$vNj8sc4N+J1?A4R`fTGIolk< zkg`sC%F%7_Yd_C+%yqNhsWneZRh@(dj%m@_IK#=8QaBwG??1d~V$OkcT17cV#r?Z! zPYjV5y{=(>EwkSvXEn>^R81|>bbPB>hsI9DQs)9VN6KpW8V?7;FyY5NaopiTq~NG- z&>#R>!?BO2?cG%)hrdKgLa+3&6sN_=npG5e=csehvjoj5H5?F=ARakM$>OoUY2w^$E!*%F`IT0Yk8o%VjPxgJ&n=v3G-vkYE4eSHYq=bP+2 zR8lbtpmt?rvtO-B%dD%KCbYuCnW*K#EjEvqErl4k+_!shN;=t~>1Pov;a<1wrED{j zAx5jAgK@Xh{jaB(og)S%zXfq5um6tVy$QA|7g9A6;m5x__ly z)YvVJTvWI^uPf`kXwv_~d))cV@$D}>*O}wXGxI#_Y1gT6=$?` zob5kqd~hadPD3;zUsFQqFsR@j)I>2_4XF1Tz!N9LFxp+!Zz8+*V${u`ffZ3=3%9rW z90jj?>u$f1C1Qy9D?(LI=V}G!pxr_xNUuoIlyjv8x9JIDVwafg1YZ@FNJ1dO}9~4nvtFx zZ8HvSMl?I)>ulN#3*Dx))!$vhgc>lo%p-J;QB8R6;lsG;4h^T(PEOC^&RxO*&F18) zSz)xxMv=wnC0B&B+Ad^5xtd)ftaD*qDs^tGDdSwR8lZxAo|>zzgGT0>lNo@T5xlF> ztkux6)T{H6XqZJrt4lj!%g1PI7Zx0}N&)9^X1;S)ui(vS5p6BghMKaa)YVw!H|qBo zZj|C#3MD5sQo75W)`ej+%y-gpjgi)j9+0yJ1UAEo&800cSWzOD5*;IA2!DN+9FD+f zl^0SPQT#T8a8>WnzBWWWf+IU6CkA_7f}IPSRe^V^N$`$n^yr#0xj3cMOIxUhwuDFW5$^Vu?~=(*m< z0I{bG9G=L4C*%gcu&#~gsS#wxl@p^l4!42(anEk39FLKC%|@D2GlcoVIM!Q^bLRB) zp8DYfhon>r zb6QwZ0`#)bv1c2;k+!{fAE%_cU|;{N&#aG2>xOEu4p6ThIOq99l|u8v(lj;QvLiM1 zGrAbq?bN}!4}qHyd3|%s?P15%(b{@NHO$4r5N2G_fnu(nRuZ##&Q*AtXQ-Jsd-&y- z@2PmA-?OF@sm!GHkzyDy=K@l*CJzV2?B&#`Q7o}zjzy~Rk3^m}Jl z$Gx*9>%CKqVRHc&_k{Y0!?9l{-n{vQ|K`8`FZip!`D^fB@XyYU|MtKC?>Ohgk{10L zse*45SMVAUyu#M@9$B{D+TL;02lrM{XbGP#0IO3*QB8`bthtd|we+uE-de4i9B|sl zqMcoVWnOVn(K=;mIso=4vQ<1DMPXMGO_FwHqA2Qa3P?F$o}*H=;~H4j)jl`h^-5aP zOMy2r-8cPQ$6+K4qrL}D4{Ktbf3<0+_!uJ&6rB}qL+Z|DdwQk!<=O#E`T?gEnv@Oo zm-Av9yEhMGrDSMH(QbK7*0H9GdoEfzP+HaZy_^i2ms!(l!i+buHE-y)zF}z9jsBe4 z8dZu73>$-wHt=q`30o(MHwpwIjN@R~e#PY0d_+XpjXU=H8>VUG;08Q?6CYI1bNHYl zNfs69GT{AW?}Rt(x<)iQ{>M8VfN0V`;#$>2jKI)@!;L4e-88_K-c!`A)iD6uKqS9( zfsIB@D<$bAXg3VJ+Ko(ygC6WcWEgkWo!haX&IX8$5Oi;Fn+rbk!|{dV%bD9@Wc0#g zJM;L{TTY9nv+r*2cy)VE@DcHjwXCc;QA)DsPoIUF^}AZ7afr{gof{PdRN z(;1rQbY3Xoz?=)iZX!@D8EorDv$SPD{8n4`dl&WQ+%+tocXnvD&RRp5c^?=C9a!nb zt@qiA;WHI!JBto(@QK*a5Bm zulV2?L!ca=siz&M2Vr7izaMEyMdMa8p&oGJDM9<>Rw-nsU2VJl9iRW|hIPDU{NyWs zb3F6o?|#QrDu4Igw=`e)i?=IpZeH>ACtu*h$d8ZD{P6V1|N3wL7yj{Y|BmVIHPu<_ zPE^>Frl}$i6gQG7l$I=A=#>89JmLbmR92~)J#83BB0R5W=2K)@XIvPF;)r9! z<2bD+-hX)J_$=Y7@~(Y^XaQQqK8*^ zd%pPO6{pieog1h5%<*{A&ZOgs^+{7Vh$F2Tzfn@EB6&k(ZJrpQ)fbl46T(0Uo-$4N z!=A}krb!WYE5d0#vnK1>TPnL4?SQ_JMevdpy(ex%hIg#G&bZ9gyy3q*NF|y0B&BMH zpiT6g*MW-j+VR|-#~RZJtVuh(vaj&+fx1>EKXMl$zQQhyyt=#Ny%V0!FWL*)&w^>e z)r`wZ&^W&=yngeBufF(_cP|e(7YT0sNHACPmer!5mxF5cEr0r#bgU+Sl8Co*^*(`$`vS(FDQy{K8tY3JXTRskJhWBR)iCt3Xl|*WO__B`q&2f99U5wy&v#0rKCgE9>oE2)R#p)bj)6cZS^;og z5^I}Tk53GB;cgtcje)vm=9-iSL}Y1&vKG>;M5x><**c9WPQ~e{>s_v%5?|pDUvheR z;F!;R;+&1Q+l8oAl3oydA7fhyE)qf{i6d87*GzPcUDz|kJrAdahvkJoKKzz%fBbt~ z&WLv`L0Ayn>X?eAjgu=hmw-(617j1S;aB_fgQ4M@Yt*d5#cmiFEJ0DKSk;7pSYNm6 zDWIM(mP@+jPpJZx{YcqNLgSAm&Gr z){RVkkX`Hp_gnh5)WR|s%6dk;u+ABZ5WV&S;sh^+FzpZ#{_uz2GX&3aI`iiBD}MF$ zSBQ(G+&Hg^fBfC=_`@H+=er+&;FtH0hzrCRSWEk;Vx!uW|yn>U~E>1VI`;ln$YmuJQZcei`y<3dh};1xHmgDyK0iEJsUF0{5z zov~qwh$hu)Q*`0zMol;Of^0y#cx+~5`KW}94ZG_^IOH?K=$S^(?QX|34eY0T?r#sg zy1(V?Z@%Qq&p%Tb;yk!hDIM^V45bTabg@aO;q}KYSnP9>HFQh z4!L8^+dHl#zGjt#eQ2#oX+$ zrZK^xBXnC6vTqa_nAb%~7Dc<+TWd7Krq`m#O6Qa&ruA=@WOZtODpu3gX9~S$&)XE; zS(@B=L+DyqW$XEFO6YPa&HZQmCtv;rpMUlhrF#DU z<`r-GfisP?3#`ks;jJ4N0z);zjl=6b1kYN8b6!|dp_zE-s?|``YQ{RNHFQchC@F2T zoMwY_?>xbQ*jSz>4G1xi9?%Arn>u`5j z7HV^P=S^yi@m?tuBH*JUrPV;um2!;F+!Ks_@#OQF?p5kM$dLzLFt;4_x&E488?2=;DdJ&~U zZkS3gRi;}F6^cu*){p9~cH3(OTn64!{uODo0_dX~U6xkAzV()V;1z)tPDlH3nUDfQH60)weW#M5> zlN@nP7E5+M81B0SjDW0pgQky z;wWNbU~8A)T5B6IwdVTK`MN((%?uXB4A|ZxF*L2D&>F^uD>{D;3J|7b4U42=6|*Q! z%+MHo?A{z%G>Lh<+rDLKrINJX77lLMc5A(=`-TKnA-@_mGL6%r#9sCXjd2KP66Q}yi zo81&ank{!@)S)z2IC;%nXL&RSDt035_LtBau6tE6b%=zK}hw`vA5>G^+um8@?U z<7}rzw}KPG{Q>8ck{gCWO(3GrUCx;`B@?2R5bUMl!cL9cUg?@^ zEqEU{3KvVCTNPc$oX#m*%SsZdf$9SxI&xl!!2ubfzUReVDt2)m7b4C_t>1N?$&W}; z?Z*ul@=>iivok0hjY};Q&XH5mce|=0C)Muj{rk3eubOt!3Bmo@-;7sD2^C(@neqm6 z)wx@)#Y{R;?^RU|06L=!798s|HlvprUA>`{o35dzk`aMiEhM>~IW39zk7t&ax!+B^ zl93qpwBm4nur;S@@X|S38RYGI?A?B#N@RY|DPU*GZUwKAn7HBgq6na{K8CsG-Pz&=FP zJ4z2ih&~_Db55S*9btb%aUk)Ao7Z1)b8{e-%5E;aESclnC{^{&l(W&868CFlJTEji z^7P?7tL#BE$n%if6M9NK%Pd*nqh3LOCP#dwZnm(BU@u6`jeUiWtzw#@ng zUGFhL9TewsW?dG@eL=4q0;Er3syX!CmVoLmblu+&7Yuy=d>5V$`a zxVgRIaI>6s?=i`+Aj)m8sO8SLbmiE>Tj z(!~La7OZGKa;|pBRwz|*W>d*5Id9pkicVL8g{p13p(!C4uC_Kq{x@x$oGE&{DJ#7= ze2SFnm@FsQd&elIs1#w!3(hIZ=$4m42)E(1y>pCSmAHdfOxF;SDQp{J3)Q78OqV0A zR^o22`GF$%B#b*9N|maacl!A?HW9CMdTHAdt6B5ZvZ_+gC6lv?Pl}+SXE?Liu z<+{kshL39^)CR}wEw9^#B8E{^1kZJ0pR<1M-fLd>+R%2bI`I532$$bE|};L z-S)h$D%kB}8}_&^gmFh0M`9ZAgR1)ImD&vD*O zhWCODNu7JGTFvixhi0exZb+>aHRn_{SX$YBtvI3N%(5tED|*l117R3c2+=wcJSCS+ z3Fl4dv*vlNe}6B9JgeK`>6ee>{hpisfx4Xe>C;C}vpI(HLK0!z?TF(}^KpZaeA4VO zH>e=M+i@3_bAf+Z%@cJAxaiwe!OdzwmH8^7P>sYIU^fOqnxnsrIw?BrojK zj={rx(eIUOnj^nryjKM_=3J6ZLFwqeZ-aplbT}@Sg`~?4jl8(xv>dJJqPUa-ts*Yq zyV4kFjYic4y%pwFIz4o}AI+6vgCrYL;ya~g=F2l7cak;H%lSm^mAUGGY(5<|qdn`O z)k{~Dky8|%^WMI*s!T7nP@86o)e6s3)pt&{^HM=tKMPX|H)VU_{Tr+w}1WD{ICDl zU-JI$mO&g}zxj$32c9lxemp+$fByX+SlkZZ!{*48F8F4s<~3w07#%^h02OickSIQq zUBLOkR6FOna>@li1d8{Bn>}tEb(o0`;Y1w4>gCfk@%@iK^YHPJxu98dK5|(ykC%zp zxmx7LN)B3G2f}$>}W07fGp#k z=Fbk}ExkH^EDPt$Y*$5LlvA8@Tv%EFvA zr@Eu{SvL%J>rn7E1Sxc9i%_>flPw=nOsKIAzSR91IP4DG-@IlV_gu=9Tpxe9@;l8#;+x6%S6 z*Vu`6rK|~JB2S^zibS^|T)mSo7CmAaz#A&MDg7cizYRnmYC*c=r$7FN|NTGwCFSua z9v>(C;J7S~;yn(pPEr>DM=gzE*y(V8ennm`y3qH+=!y;>TV@H4!~PA+G!lj#t~rtq zj62A2t%S&NG(%p-!iFJw0v6gWH)rpT1WUzJT05R-+Fzy zx(#K~I23VgPR|&E^`3g}s2j=uvuF<$HxLJ-@@YR@YqceWVRu_&snz;7uNlrwbG$_y za~GOVDk}9+7w@hTQZV60+ZKVrD;Y=Jl@2uaqDx69lCp?ajY>vO7y#8{-2+p`8dr=;c1O7N(xwU?^vW?uUUu7 zW}=BP5aWQiJmk_k(^=!_bY=njVbCREwk4`jEW$95Oel|sAvngQSo*gyFotO7XSF51 z*2sM|9Qnv%S?o&bjP_hv3`J{rI(3?HbbRfM(K95dk3TbcM;4_iEUH8SGY~{uz^G}W zZ;Rs9+~g!|HHg-c*9>|QVR5?^8EKifPWve5^aeFCi)Oj&vM|JedP6)xF4uX8zY6Eq zuwz@`4#(H&cHx>|e|7rYlwVG6aKHh|1-tGYCq!_dgvUA&%Iu>o(uFLZf zRfM&=8Jkw<;&&$Qul7E!#;O$|E#fWOX^q{p6<==*Zro7ZofkRws0pRkY;jdeV_P%U zYpdEy)Vj@yn?)TKn>PwxgDP6JXr+qA3$LO_70GUAg1gS2S3z58Twjyu8NWJxTdPLw zYMaB-bzPinLOaWv7pJdPf(p45&EBCcYPhn`s^(0uN}4D+bDk%1&P>xx&e@!dP>W_| zANH>mBmd?dJ~&*|IcFLYAw^w~nXA=@K=i@x8gsyhxYfD32-1c$PZP^LF-wGZy|M2eA}B>jk6CAhG;i{o~dc{@AZwKqUE zXMHuXkI4G$>CeX{w?z)TtgRc3+AGVt=&vT)vPDi8RTt%w!LbR;OH~?dYaVf)S|^)j z8vS*Rg_QLkGt9naWNQp*U`QiNg(lb6w=TQ;x^VApBQ0#Twp_)u8v#oCRz7$B*}kOp z7gP?mDA=`!Xsut}`YTng*Xnv%d(iy$6H~WsQ^Pl#z7d8$~%@z;+LWsE$ z7pukl2;D@{xhR=ioX5vTsDf`Q1X@Le!8@W?9Dg@@&@v12qDGHh)IHWoW`5ZTB?_Iu zxnw>cpYcOvC?jKXN-mW`ai+L40d4PEt5%~1O1&CiS{IhK60AjIPbAS?qIOS{#L|S) zl_VxL(MwU1uaCNODSAisYMn%I3!PWDq4;dJ8Lz% zJNc;lfplQEpII(vo}Ok7yAw#D;dwr1E_3HNcXA2T=9r6~bhUL-xv(pZES?e~wP}QO z2zz$vK=OK~2k#m|0^f{pHuwCpEY#YWrVB_#a-qx<^V1U!D2t-am}mC$#CJD)etP?k z4EZLeB+}t`b=u^FIFMwI)P=`C{(0fL(`e?ZN>hGtiu3xy=nHQRjs}eTBG?$uO~{cI&e-AO!(5QpxFh_u)C#J zp|_b7J>mzZxid{C#Xd{GxuA>c+}Q0C<9OgaSNc4Y#(~4QV~l}u80fumn$FZ#3Enfr zghO+utM=NC$wO~r&YQx)Q&boYcfx&B5Q}l|1Y^==&R1Gt-Y*clp zNb4__K!Z=w2D%7VXx>iyXf%O0?jv~c+HuQK_BhoLKN8V_Uy+&qYk_d zpu3s|nWl-;`Gf-w`#rlf=%7eoW}z&VWnQ3naxMfPczu7*;V=@TVz3M@Ys*M=`8%f~ z7ahu}qAyzhm2+2;#v0LqG+Wq1h=vAUeQ`EaS}7nN&1#h{gzdlS&n22WMFetQwm1)Z zHT{o$U!7x&Nn;h9qxM42MKg$3*F*!dMgsJjtr0Ca35r~D-SYF=<^pIoe4m$vr*h({ z9PyWhPfr6rI@0Ly`#rsM_CsKd-h?5YycD9S;mYo%0K&VZJzc?*A6H6`YnDJ?SBN;0&w3(#GqxeQf>My(Vcj}LskJcB>bv*L|QZ|UCg?*2Q*-LLqkpF5v_%$$Gzo}C|fzI5K)zvf&sPfKI} z<~=N#(u5v7G>sIfbbNQTs-h=PQ8l@P5S$Ka&23hl7Jkj{_F9`Z@4e#p5g80|8iP5O zwdpEN#b)Q}%u;63?v62bj(K7#6Q}9Ol+XBaq*ryEsQO-C1NbfDOqZpZX{mg8c;Y-w ze0X@|bUAZ8U+8Cbh+U?I^EByVe^S@+v=kfg4f@;(Bw0~Tq=gh;#TM zGNwprl_@W@tnXoAQ9SOdob5tv!f*P(`**)$qVnl->$j<~B6_r*|Qllout zRh@U1uM1SM{hkgwyk+jLXg{$9f3P!u-5Xv9BeuATVNR3!gtf@%iCF^F5OeMs?5`$fvUk3LOM8LSb4?JU#r(=Z6n0=QH9XxeGmx zj5n`{!=8X3PI3K1jNFV`Un;R8vKtQ!L&D8xCV}jPsd*}$(B$htbG9`#p!-TgICkrhe9%(Am;2SZ*sy z4r{Gw4J+#u>jjuz6l1%(7`1;*D-rV0Srr9-?3Fc)c2S;>D_USKUAq;cq%?Z?B zkkQ(BYMWV z6`$;{aS|`E@G81-FOZSf0e0t0>KSrPhcvU0vJ;wo=|WtcnA^f$pH*k?r-x1o9V8Kk zfh?WpJn_7oc~>&@g49avdQSiLyKkX&zIywf_g{TY3}K@!4MXJDzxj?=@7}UM+_L-S z1D`%UG3RH!<56dUB^0vC#RPRJ)0KWAwj`D<-b0-hBaYlFy%&(mTq-ld_uv0W`dN|Q z=hk@t>)&#F_lo?+@#*%KzyJL|;B*{qgXjK29H9%>P3o|^gBmaBa?UdBn=ys;*X34` zVu;GxY-uMij_AH+O!i(lg`J+G-O(;u$I5hMI0YW3g`4|3;xJHiW;ve-UjLvF1Epqq zPg;|CM=81>sz42KBjpJ%aGzX}|FWhsn0pe;f%w89x<)UEkzMGWi~YY4Jn!ObdWZ9F zk58U<@RXXE=@b`fc#Hng=P)=AL*=kPAg%H}a5+xoGPCzdk-bR;G;{S_^1|@yEyI3K zJ!@S*_eSpE;-0%Kj4siVl8Y8m`jyW2RE9$>-0Tm$I^5DT@c9F^bRcQQ^9#aI@Ews% zuTbpl>=f{Qa|f+N1LOt4Vs(_&YsAC)`T6bdSifVk{T?rx+v%Du_#B3@UKe|Qqo#>m zMe)wQj+J^Hh_Avs5h8wzXJmBTyHOW@PPU(8?+|^qMSv~TFfy6=&B@$_%`wyr?Yoh8 zDn>)Q#*28b8QF@}Hs`1JDirY+2WVL8h&M5@qZMJPm6F>wrzrNide`rubJoK(tr|+3 zaZX+Az3Gz8A?$~!i*g$Cbk+g9bHwEF!I>DL5lgkW251@ProvL3ATjgv^Jkv^{t>Ai zT;z1h>|*48xTV%YiPh>mAr8YeJK7=yhN#Ok=N-c^aK0HC$x7oXnV<5&QVKB)8-3Ax z&%7*(6Bk7b-`?GFe}6~s!nCMKV85tH*V({(7YCc+2w_OLl)yWDO5~bpy)ZkmjgKFWM%OBq z0gJ`~^lnjLZWE7Hb3*rRo)swNqMwV88)2obr?zu=E?*c{#1<0kg29Kd36X=-EcF<@ z8c)O#t9~!!1!YnU>FvvpZ0Un*gkDv@~Z}_O|^uc@YV)nmNP=29CH=n9nCf z0*EJzqP+#BBhaoUpKijcwWrMaL@8OH?^;<0d#!hJ&Ku5K1uhn$*sca9v3Zq7%4!am zk_3!`XpZPESL%m~vV&pI7lIRA!iR_-2F{j z+B1kPv+Fv?-|BF#F8NK^sPAXZklq&ECg}7|k=oL(lnwb}A&DB5EQ)j$!nA&+=9d0t z++FKmI`PHkwiq4DtjJLnY^wG~6BY4XBY+k2E-wXCUhlJRuWj^h z>!#K^M4SoZbTMaVh{e$PYk$>T!)^EFi%w=WIU;J9Sl_#uD_U)oUg#xjFKI6Mwnm87 zMqM(?yfDu*=kuA%bYjj6^D=1!&}zVx6S)>H(~0wRrqqR;XLT$~x7fx)3W?x#E?#>j zo8f6TBE;o%;c1%jQW;X@W`AH9lhGnYgY;Q&oQp!k-G%gf5(do=MdzOS4HKw^W zC3@A~l`^@_1+$Gp^*j(CHsQlchHiESiFgxqJKJM4VmHIF^~ow|(1_u2x6{|Lex@d} z^Dlb@BQ$L1ip|zmf!!L3dc`xb{Tbc|n$t+i!N|8;>Z_5S^SscF=-knLLAD625E9~b zCg%J~&1)1y$%A!49E>qAPcyk?&eM_e`K<4!%-r0*Hxk^yd4A&akB#@g`3-Llw@j&X zzGQ02crgN@X#d>&N@nj{Ode)H)X%7B6q@MFCPaq}JLoVi6EOrDYIMWq7RAnh+A8A^ zc=zU>(RH4H<#goZPnjT@aUAib;G1J8Kn5w`%R+D(r`>8Nq=Y|=?04@qHlpayC&kEe zIWx}}!Y}Y2x_&Of7{{7O)?>4rjH{Uvehr%0i${nKoyhKR%GB zndqb9VZ1jSr!JwIAvk!E3+O!5VMU@{T?;SG3T|zBUSzoRt`7kY$g9Fxv0+NllT9)I zr2*oK=fh#x7*w%i%22Vxz?maIuFE$sl&%ZYo-46DuzU=tDC~qFr{WsPPY4UtA5SpG){j$Ma`%R-NVF&)%#xHNV_xQU5ZcrHKx!mIDz^3^xrv3v6^r8qo~ z44k3pg^^NXKkT$IE{bsv;aY^QKjP-EiuBdV9h^{{GrYSYWV)ery3^u+#etY}Q!Lvo z+<01zWK;jNGVzDM`5T`8>2HxZ@R)`CbX0|)8x&J+j^cK$%v@9DkJE@y%*W(N_XTl7F#E?NlER5jF9t?qU^eAFSU5Y#PSZK$6M!6{Cr zlnPA`c^?9C95%OQHRbLl7rJ3ra@GdAtqo08*N`BLX`ptcbNCbqDe6M-a>8dt@{Y;V z@Z_MEo;n4?7i56aDvhqoqhX-adHwJUt_ydETVCDVZt80vBRCQ}Vx7muFmQ8o!_Cbe?;)m@zMy}17f~gmh+%-Q_*Um-TiSpq9(T=E zvc{~L`n&_PlxwDOZ}eK#h1q0_kHB0fwN}KpEn=kig5aUBM!$@huQX3MYHo~VP)A53rC%8%~>1=ow=wGq?i)juUfA)f3DZf_1-t_d?i;CB@}f>&Lyh?w^_VP z_qgVCm}tv`Pahw6dVJEmsV=INGlX>Oic7@`&Q*0RHn^cdU34DVS2wV8_?U3rF+|NS zeDn4lsaKbUvSeB<+rXuja?S3x94RaV6A|VF!9~qD>+tySnbYaS z`FP@7F4O?aoDo+U#vNrY>~02n$sBeA?(WPk1O}H#VW(oRlo-a5{b9#`aKwIOckxJ% zluN-qJ`&GoZgJG{j=S4eeDm%bUcY(6ah@5<#5sJ=$AA1sf(v@Qn#(|29j_Vg!LVb! zAr+*VGP>(1)_cvX8iH_0s$4&eBfDL~1z76BbZL4{MNf(eN+B1@O^t)b4r#6Rokq|p?UcY19&%A!qX_qI!aXg*) z@bI7l5T|h&A-k=v5OY88_X8;oHk@`izo!{VBqh&bR9AgUi7xQz`7^mUet!JK$I~;p zfgcmisf#+U8D_m(cA_`Ikpr)hJeZ<5tc0Oz4PI>o9~B24JOx-NM4#{>l3GIseGV&? zEZWRaJF+Y)gdA?kweUP05f4FZ@ZPnFYBt2^9YTusZ1`>0%?;FXzEir= zrMee<)7PGCNTRh|r8-^4p8HIwjgg=(ZE?Er&6Q{-{ZD&J1Z_7&NQfIboiPE+BiQi!jn zn>)gA;O6F@-FQn%nmrxy>{8(kzk*H4AER!jRmcpYN;%(@mwk&s65oh|M(Yw z!Jq$|Kj**yAO1Vi+t>WRfBYWzcw)KC47;eiMfe!?uV_Y2>gM4!5s}`x~C0CUrP; zTa7oRBGI9U;ty3STDSUOtyR194ZT=vVH_fN`yFF+ROzHHI4R`iL}8~2bZ;(e>vzQZ zmenD>W{z)p=`URGblF^&|J-4}lFr&1>#$yS&hj*y#RfPf7Iky3_>O%aO<--5wQkv- z%?6m=207Skbo{fVvGy`g-NX|j+cVv|f5G8=nPaJig4=#=x9BU2(|AE`Tbh7{uQ^kp1Zwb6#bGJQsn;jMlr~*ZrSe!hM=fI=Q}rJWEgIE z^ZJI{n~~k%A(pkR=w$K zQ=yN)E+K5GzmfnEAsTKxCeQ3(7zbkX-0t^$^ZE^O9H9wOJfU^=V`9l?2B&!GvMMfI zGeN9T(kQI>2>Bvf!9*r2W^v1ttyUAuwcaw~No`FdpRA8r#XqezT%MSgnalC40vYFd z{puCZmkXggF6Wuv3x{z;Y9oqbuVY<^-m#07DP&H^XOKPjw+G@7$ga~WJkOa%MEZcM zUNdQ(r-;z{$XA0Sh8@#f$YrKF&64)rGfj&w4Z3h65qw~2l~OXn>F{(2@hW?9Y*Yd1 z*Xg))ulx-|ZHqjOxnM10?81QJc+F~=joBha;9Mk#8Q=OP<#koo{q&K22tESRAd{^;66iQi!4qV_A8xr}r=t7bnYdtyclQ2*nK?9+qD&WRS@gS+XLjSB-+uRN4tKY-*7^Vb z_{^t=&v-L6+#C+v+}v_LpYcv8d7;yJ^Y$&T-@Ij+RTL`{7>1E?@3=i2@ExY(kvv}* z(vH_}-VnzfaW}HG!t?1!tC{FJqj&6v#Fzqe)Hzxd6hGaXMxS(b5&M`l>w6RWh`Hyi zSCOWXd&8gu-(vthHT z^;s|^{NR;-v@M(0^P1IJs`qcLbvuiFR6~(CN6&_Zcj!FIey_8|XSY~E7d+xnGFa2u z3*FO_rvy(eo$egP$Oa+mJf<7wzH9>MwuuvZ?_6Ti=f%hr8sX=W5EXgs1{@J(i+U4x zSr2S8l@ZV;6hmElsAcZn4diu7bqt8M)3ToT1*8j8x4cq5xvd^}h;?H+;8I zNLtmIea(uFFFEVEX5WocRoLq643sop$zUrr#fU0f)Q6dHlxTO>B}>jUvbrA|NO4GW zHs-(H!|N=6C8A`fc=wz$rL+x&EiU19ar=9GP=|PkM)5G}jr5)6wkyfNZMc1mKw~&oOzzgwu+UQH|8eBv}7sKn4Zkyw~D|%lY+a6bozE``qvMpg16Q)=O8B}i-rJT{@6gZiODJ9N_yA&+bMw~x=y2N6n5(@ZN|4*9p^33e9?(_fw?vEJaaiu zoK9z+kIyD74&+?%E|BxWykv%yI2`uuZ!2%^_UejrFboY}jttX8pNknobU|Lx&&mb; zp8MW$C27{^$SYKPuuPU+6CI{Cs4| zm1&x<4Q0(x*M(Figg`Bw%QVp&JYO#Sa5?hy>4DSJkueUC2Ba4xL>!&LJ4%7%BR!7f z)W}6qMP7v4wwPnVijC{;-WE@6d5X?*B1Pp5lv?O@A%7O8$0x&zC#Z@)cWaissTfUj zwc+p)A=xm=NidH_w}w&_L%k}pZNSvlKfCqKNz*rwwJlpZ1Zz0OE}AWe6wM14rvunx zgCET_Fx1-B<6%BA%bAQp)vQ4s0o^%D7^y%n3)Pmhvk0y_hA8YLQT<2{Bh{-bxs--C zs&MOtg$g0k3-s=BUI=vjTyakqzWX=#{Ja19zv5XO|LKR1{N3Y;I6dLph2Cxm-jSSR z2wt1W4a}ln?scJM{+TN(uxr+RGh9Zu>>mknQ=@wquL6&*KZ`42u`|s_lHcKdV#BRG=ANc0gp19xhkUMo4NyC9D&-fVhV$K;U?O$fsZaFgRoYieVSI_m*eVL1Ug_+p* zZOz^a8dWf+NE{O}CIumQb9T0lOf%2Z6UWPib~*9()hqTxVqxZ7&cvFz4+r|_$>++Y zTv%G9`$i2F?+QbL!x$Jsz?dR5S1Enrcz!0ICvM`v+t+W{@AQyw6kH67LFt{67s^ub zqj~^yo@vDqh5-qlVMxTN`SspAS}&X~7qVf%=Vc=2#njQNM%Ja0Sk9%Q^#`vMuv)V! z#a-8nYE*|+bsfnjk?;`Qby4K1x-?gmdk2Q`PM6$api9)@OjVb4YmgC|;Z#b_%msDK zEt&&g+srbZaRj`JIG>0i(QBubYO`f6v`ejoxoyRYbDq{FVvL(e$XT9Kuc{nvO$XrW zlI)wx-8n7VtLxh=P`Xh>=rjt=)Uw@38$NiLvljGa9o{<|7V39#RbZ};(rCGdAtZVi zlJjIYFs42G-JTxZiztul>{^sbYTspbXk7>RI%M7LcD(a%8C@jc8HRx_FrCjFPcu>q zqZ@g19}uGE7v*f(MPiW|iTSK9jV4`}=iYHDm7gD<`QeubK0lx6-O-)r@hMYUBE$hl zr^!hbaJ`bL+qqs1gI1;MfNXQuZI=roLbQ{X75VRM7-DX(wqgyP$Av@~Miu*%rn#L) zF*(jBjciyJcz%Y%8@(@U)hsjnZh0SZqUU;-st&%Pmdk}@zHC`jtE&5@dbz$JowU{{mlGu~ob!d6D>+}7 z&QJJdp)Y6sectiax8L&W{uQt9@44IGGL8egVWbTUyENhnhzSdP03;JAoS#0Bmzm%H z<6m<+Uijh15B%Lf{gF?$^yZwR zr$p70&UwRTI>w|3q1p?rLmUR2k4h~GLUe_jaUhNZZ{NIzq%PL4Z+6_@-q3nwp7YwD ztNVPJS>}ad7B-iP!-EnYeu>$$qm_ubB3t3Gr{iM=A4e7>@1TN zeJ;?0qxR0RF1QldM|~}&2(47hLPoY6(ZB&61`X;WJ^Cvwk)S{f}xYFUU$<1<=m)KavbDvi2SYFAQDE}1E3 za<61MbI9~16D1(%P${LP3V&1S#}sjk9&)f|yqjuRhp%gD zD5Aq4@144`yCU$W%Y~9N$pvbw>JkTT#*yFx<8B9+L}Dq-fc<9*>arbwlh)S-9j2=gY#Q+wmN4crzrv{`z;k|N0w-VUG_<7Ye;0 zty6PhnG4S{=_1DmoMoWbTDi;@&ZRK7iYz)5dd$yEeL+Iuc8E+*pE-W|!0-Om@Ay~$ z`cL^k{^MU!TB8kuznnCqIL{n&M#LgLTBDU0bez_ZpaY-SFANkyU0AFpU1u(9#-&E` zSagjza8Y{-7a4X3hMPO~x37UkhdEW3K_ZFf2U^$>oxY#iZ28r@f!?o}EHM0u{!WBMS7Ex!#Z{Y8@G>aKTOQyQ)2 zUr~~)qjc-Tv_J5o2*4F!>0Y?k^)nCFyY=mLRe6d1T_XsrX6Y|uDBPDgeRB|W**x_W z=U;ShSM)uW7r929)U`2nownlhSIVJt`0kNvzE&fJiE}E1&od9lM?OA%;_3K?UAV({ zPnj-IGEa}6dGq>}i3BP!jJiarmE=0Ef&|au{a3Ul^Yr->wPrqj{*0eHxiw@J%C0n- z*32z#wMK8-K)Cl#tC|rX2^&cS2}n#FyTk29e60-Q050(JKmAC%s2KR;(<4%jxc&t7 zpbpel^bU~1ToxmsDfS!`1Fc!p8j%v5Slol(qTjAAL#agSXF!1_zWEc}^mv}yB{Fn&d5puBMOeO_K&YET2 zOayhsxmwDw4Sd(Eauv4)VqAp<%`lW&JJ0jHXE%I*HZS9kZ^+}?6H-1BhGgdaZg z`yW3MN>KP)6?Xf3X4iSn@VQLH+;}|AOf3@+fn5qT88~~-X=&Vzx1^hUT5z08WuY@f zPqcyTD!^bhJQ$i*Oh{x;wu)bxqj{w=sR$_Rg4>*E2xL2lbT90;?z(Qu0B_m1I;_Q+ zt9M1!+W*_;KXxGZe#MDyuG!|lq}gDClHRUFHTsrG)5YSB+)A_Y)T%kO&dy9)CpSHZ zn`PbTvaDL3i7Wf~*3U0BK)DI>j9T|!q7BWXI+g_Uz9FO4!!z>#iG zH;lVIrNi?uvdjye3ZXN^&KMediS&yn*JcZxjIT2?XKqT^@_B>hd|wGoomR5NsPy%* zz`Y>Uh}R#UGZ?5hVa6ePhFW=@(&kVVuS4Ei*VwzDXv&Q$s?Ow9*ydx{#JDdYJSN=k zxGoG%HZjX;tN}I}PP2QnYGAUyKPTJ5Ja~1HYp#0Mr9|(z=xsituQLU804~cy&WkPV zf#^v?QegwaJk9hDhr^y2l&Iy+F}vnXuSosc8et4yM6mRxtLR`^-(p0TE{+0U+H~o( z9yX*sMxa^+iH>#HuY-C7B{;g(ecp-YknC+0WLQ0B#(*Z;)vMX58s=MxMnbVAQ|)ylF|}%Rwx;))tPv&l zzH6x_yl4z#qP502CKX7;i1R843Ib6CiTZis)OA|A{<*CwZB@EM#PZXdF#C!)ok?$t z{uPX5OV#dFaNz905~BdwKqkL6r~qpqx{eEt;Iht)y(b1+a<`^2edb?BtaHt)&uXO8yz)lP z6GbL!U#LrEsg0uv<&`d>KT}%c;ytHf;Pdmu^Yew$FraYk%9S?i1*1q>Yffni#J${43>Y zoqw%@&$WA6e=Z#uM1_az{d}co8P%gT-QU<}sA8FwaH<9^aaRJ)DqKu)U>rtbibNl5 z*{?Cq+5W8BJHDQAy>9ckTAR)|ES@9A!D16kG_s~!SPWI|US$eR4Fq)~6e;btHao*! zP{*1PJwuAr=Itz3ddRwXF0D|j=9YH{V)c6niNQ>yOR2=BpWnC}bSKq~s%tR6UM}Rx(Jk_;v}Pi;04^9cPzZjs*qH%*Kw=;U zPqhATzrR6TB*aKeJ6cn^aI-U|xu|o0I>7~!?d4P`vl0u`=QD8+mU#SOyKIcH*w!;0$9a<)$RcAg=%y&aSQ9f zZmli6G+exjftqxvGgU5>TIpGPz-G@+t&Q_E@q9e-`1H)<(=+qD*mB#9EaHeUa@ZX> zWFbEOOpFy5g3@7qfUdkdkAv=!5XikVEi>XhL$vz>p)pvjbl>PJIH5X6bDk6iZo}M(yG!nx@gVb>PpXAi5C|AhzaWY8b6?O9p`1E zAFWXh(qv^~nyA(c5lu}<4W*K&bbvj0DnTm8=M&GLKeHQ0LQF)G8bC^;qw^~7L42eY zjl*R*lS?7z!eyE`o-gFy$PL1O_{;x?Rx0PGBaAM@FA+Q z%Z6H-FY308_cgP#OII|ZWjN5Q9s)EP30We2S-6~!kMjcDo(pm~cK&Z2?qrR)tc|TUOhc0ynn@(io|t7qW;&3`n=1hdNYK z6mppfs35>R5A0vP;@|zZ{|&gxC^P@XZ@=Mh{=@&KI`O3;NzokM7K4gf>f6w${|hu? zx5l>7t5EBr!_A@!mS8xs-q)Xe|25Bl z{E1&4A9(9VEg)pgSPMv5HkX9FCD*rD{PQWx#uNZYJ*)i)PJNDX{cH zs)10=`Q%3$nTlsBisteTWXXyq5=Cov6bD$E4l%_=ea=gx7SCm#h#_;h5z-Lxv6Abo zIbjl`R$wyd{*gWV@+cUenoN#!It}4RIiaB>V(TECM(xU zsa26GXdJLAii_sa+vUEVaa%rO*9B)95-C=8X(WWe{`Nphy2$MRVxavc($+bLtPvDe z%&yPMFeLU#6@tMEUPc^jY)hTVa|{O1D@RmYl&W21=abJH3zU z&Jk6O2At;j85;cf@R8@^k>}Hec_}Qdb3R{aqA@4IM?#2v`~GX*zIx3!@4se0jtn81 zSRoT!pjCD3{Qq3NS&t;ycBS|2Gntutcnq0&vx>#xHYu`OQ1^rM(2`mL)cOf}=pWj@ zK!P9we3d{z5L>)pi@dkS%$R&KJEI=cfOG|0&r=8M3FieBTi*PrMeEiKv9=`b_4j(_M04bHpX{AGz22QV~ zmK-fda$2xrINaP2Y@jsHaQA`pybx;Rb`0#pfN8=aNeBOR3DTM#^mA53lXW=LkT_#g zW`A?Qd*SnM?l9Jo-95{7!CpOSsJz$<=Q6Y8gzeo6bD4EvT^c1r$+C4uMlL;Z90=l( z!Gc#@UlO5MP1^flI9Smxj;cux*TC8x*SXd=8`780IO zu1JM_7|1aaLSXcQ361O;>AWBw#@w(~yMg^Uaj^m81Dr2hOC**?%oq0KL|P)wS>{rR zEwf}jh??t+6(MJ>fcWOSZ~3#o`g4M-{QUY8-~aRvyuALR1`ZqvHCeT+C`#TTCv8tH z(3df*I8*waYHaKfMq9?ceeq@P|Kq z&*S4GoadgplefO_(mFy@@wL_(dfWeFO4$wke3Mm$1f!O<`(MpC>$%V(ZKZhVVtreBZze{wb+1dSY6z8X@^b;HYLr=Pv#5e? zo|+n?7Y5t-{NW>i^v$0!?LN}%NUW8`jKs@{5YWmv$+W?cZGSHl zg|DwOKOQffmV%9yn6sLUS|cV+3>s^xDN#ii(YZugR|!)Cn_z=oVu`}X|q zqE=-XM!9qANca+E2;ElYN}eX)&d_t+%tt6 z4u=PVAK2eaeEZF3{>y*$UvWx}U;fv>p?o_((HikvuJ8^i8~yZcYvSi^4miI?jUXB$&b#nje&R#)>(??~2=b?&LsUioBt zKd$NP^JX@|(HcGXTU)kDf%R>#ydzI_5=SkHmzHKUO^5VqpQiP3O^pEUy}@U#x7ps$ zclWM-_BU4Vfkxbx%rVCH-eTKPxoY&=jY_!5Stt3ek9PZWY8Co-%!kGoz&&J5Uh}W zK(N{=U9TKpuEhDwvpEr*kn)8XXROf#VXlqR`p!o{L=nUxj5Iq^#|gsFiA_pqGC2{P zXB-0aJTsR@&6Y$Ug+T>87NpirjDcn>#X6$ET!c%Dj8?1k9EGr4*gMOo+kw}ck@{xq?)!n(?`K2sOw+{SuxFYkrrk&wG=SuS)?k^W zWJ%{bio2&N*Rx~mv>ay~&V;_7szh-#V{p}M{myMonu{hc)lh1>VRe1h=;y|j(kVd= zXAQVM`$)&58#S62jAEEuPeu2Avzj#OTV2WeTri?^tG;_QUO#`ei<>1IN;CL~X?s}~`F`Kw+I>OzpM20@s7^~zv-64za>3T>?#NpPul>TfiM$e)- zE-cq8B`%m!8LY4y0_Wok`Fv)+9Jw5iq&Vw57#C8`%*#wHx=UR9t&Lb5DkMpRWw|02 zhH)fj;XFf?f!Eh3PR($A;>zkS)x@wGZj=^L`Ut7} zj?rZX-{+D(a~Nwknt?THF6`{*P9IbKq|tDL2E1HwxG-X!L&P#KS58;mpIM`!3pE2O zov<0YDc$s$GLv)FD!N*rDy`Z3c{@*O0=~WVU;xzyr3sWuPFer%_I4WEs@yv9WYtfs zwYUH`He$PBAlJ9b!p$7r-HpvOoLA$mb81ox){^W%F&5jv`+k;^s$K3WF<;J{jwjN5 z!M1`WLrJufXf;vmwG$~DsVppY(fiRUk#gM5&(f(?QcCY*H%hXpGqlDO47+J!ce7_2 zcPfN7;c#=uH0>CMkuXe1KijTUh-Q>DAtmEF$&-}ybJ9&a`o3jqT4*_Aa-VH#qtuKo z8hVI%!7Lu*96lGE37c5Ho+^!13J%@CIvdqG4R14QtL+`}ReHc`<-gD=9cqGv%((ke?%T<4i3Mq-ZGn34KsdSCi|AePLWG_Y1h)A94=f)&Hfeou*p z=lM+Qf`1r?ey@yuFK+s-Ujwp5w2~DOj57ppdzJPMBYxY*87r9Jl=LT#Y68&&l6qsQ zHxGon6TutCy=Q;;j+^^$l=_)7-sm-&bJQJ(>y;Ay^J5Vl9_hPC==Bf=J@>95=}zF* zwl*~&ti+iZBj@YPycEJxN%IUTV^YB_8cr=GA*Mobq~y5FnbY~q`4ZJ6A&ExSV6^m# zjaIfwjhHjnWl=o@!Trzw_Rokp@rRc$TrOv>DKpP=U$!U3*X4{ciV|9`k!#F+{rbdG z8`tH`IVEcQK&o(!>h^R?X0R8=pxA`LC!FnWmif~059ST~V*lANyJGZuPy0qbS)Eo@ z4?0d6Pvn%K7InNBJ&08!y?5|kJz;mbvZX7ehbYudjPzlkRUHya)_xuDwr&>s0=N`) zTG1B|(zNq}ref*ZZ&r$SGSqEE-`I`a^&$Ph)3$ke{o2;FjOq0b>!Bc6VHYen)5I_Y za#g&036^_*35{+oZvzxr?fTgD&1^1uD{Uvs^D zWygYddzPeVu3#GJS3Q=z>*{Qq)A%icT&YzNVi-k9mTcY@fZKsb6FIpLM8;WCQm0c~ zqTXzaB3x@~5FEF+AJ|bj><-i-#8eP*jBdg<;d;%~EO_w@1UwGgJi!L6I6bV@O0kMW zH^yO|SAnPs_tODy2GWvMgcL{4l}zmjS3{}V<7l09tgbHmoI$(bI+Cd3)j27xBbW5^ z9KxUr6ois9n5+qY5p~&#=_qkchm_v`qA#H9f~(b{s@3#hgtZ!;Y<-BRfK=+{k~jT9(P~4ICLNb~=>wPE3v#QZUWJirp>_BEIF7xB zMIVA~y3u88AL6CH9lWiz3{I2p)}W>lbeWo4Wl4+D1bV7zP36BW&sUsJL*5rdG#xOk zv+S%TU{zeKLb8o8O$;Hh^8=$*@h_!>Si=zXeYc`wMG@_r3$6Qq+XgoTl?_ z>v*%GX-zZFbf1W(*yZh7G-Al)txJ!dQk0IvlU@_Bc6^zwr|Qjg<9e5@^#69w^)9Hg z9dKxh{`&CYj?bSz@!fad!sp*0Utjb<<}IeJ3Agpxp@+`e)BL8pJzF0J=J~1umG_Ky zmX<56Bwm@hKL3QfI4*G^l}ws-Ahe<)%`lGagSz@#s(9NgTZ(=SrZ>sdj`_MQGso+N zYg&k<&_wW|k;}p$2~!pw=zsVf>3G90KmI+3{Y1@K4Loj9g9#7|eSWBDwbcK zo|(mR&IXxoxtSdJ3pa;bmg6sc{ju=){Xb%yXKupN%M;T$aDR78OW;kwJ5NQhet_O@ z8@%TUL(7_q3B!(Y9Pz+33=BT7pGNL(4t#p}!2b507&ER_?T$lO+Kixx`P{qstf-mJ z8xPeWP1|~F%)FdV_}z}Zx0qaUe&3g}4P#437Z!#AVxGx);hGa|0Hm^{7lgp$A5OH~ zs3~$fpUI_h{QgI#>%uO6#TFr<3H6=zq@se59|PkMbifse;{yYR``r!JJMJDH2*z;O z-O`L@vW7V(+{=j^lcLy+E;xMFc|oi&TA>Kcu_3lG`3{owol|es{yX`kc-2(6 zf>;h#7;g1&I!usSVy=-(y6Cl6g=U;qQKU1DO1z$Vyv(!(uKl%g&XBK*E+J}VPKhOD z1QmE%%s6L=t;{8|u_<8#8aci+nk%*%r zrZy2mD?uud@i}Tnw<)WwsbtAKzI^SN z$fn)LYdQcG$a2y{Diy2?jA6n#PYUXoKQ4(lXYw*5zHqa<<^KMjaCl&rf#+l4>Ff9W z^z)f&4>U&R61digal-5eu0zAfjub*4(+f)&sBR#~%*!!yIzDl|yii5hefWfzqBvem zN3HhCCZp*qWBv9#lA_BDvAv7X)a_7MTSi)A2m^=P1E=$awnRmd_5_%Y1|6^1rRpa7 zZ*{F~>xx{(@>~jOj$BVyj`kIE^^{;pRgtvQs76w6oGxIE3a#s(H}6b*vei6m%9mO+ zF0dtA+B;#qi)w3X-KZJ1PtS_Eqjse8TUx)ZLzAdzwi;3B?d*c+{HP7>M9iBG(cY3L z`sI{g2^AtM*1jj=09V*}V67+p*QoEBjI_OmLQ&dU!yx_lnPz%=rfy<=$&J>uU(j2} zK8)Po?77?TxZUj-y(cXTLgnBBbAlllPLD@SaXftZu!#yU&#(OO!%v*f7k+tu=I5su zUN0BEyu4t2;F1@8uW}-%jEG`TV=T0)eb_4%p_qbk6$&`1DtxqMt3$v*F>Nge2U*QH zV1j44M(`ayEWI;0Rh%(+=h(HxGz1R2J>wME?e-jBj(qw0$m`21DMK<=@2AFK?ewm{ z;9bYyMr07QO#Rn}i}$3n65J-rI1DW}{^5t8`Qgh~cHS{M{ozCyy`B@sap)q8qxggm zo`<`8e)F5(aCdj#dlqf40j!u-yAhz*yOg?#w6$04si8Qt%#j$=R_P^J71@$5!&90y z$=EBt#ynALW2hdBV;Cn=t?Wj}v_FusJiZ?J>FXn@&)#uP*jTvEh0psN9&R2GMqZAu z7`L!o&eZFfyXg*@XLjCWg6EVLZoT7XH*$+mhlxLWxQ7a_FBj(1!XD3`+t*5bvT(Ybu*(^58-5(f#%U;|WMZzQ<}tZ2m`2Sil;oT_Uar{Zh_SEa zq7*emaMJkn@e|?G4kr^%^v3J$p4t-C6&BO9(rcx;b+cBjjpiCPSe!IUjvUWNj@LWf z?Vf`hIfRMhG?B!x+mGzWK$&MGG$b!v&M({?0(ZNS8v;85;vF@g`0~p?QkyX6!g-!a zRRt3(5S-=q_Kv%|d&D|y6{Ux^j0=i5E!LAw>ve%@T3N5Jx3>8HtmxNh_p4~5XSM6L zwg;9#n(1hELCRbGjP&+USye^ply`bXMB7%2+u}av@~)O$eicL!V@Ra!`fL4sz5dm8 zOYZhGV6P69cRK=y&Vd*WN%^X~TkkcE{6_xEJw4qM?@d#TCJzJ#eANm=kD8ZEZsfG! zhDPZX1G!}FoZYBviltSAyz5lOqCdBaax2$K@BHH@y*sza-{XW-8R4(QNrUo%XP{^sjUZ3-Y7@b zT5>B$@FXr+Y2@XCZyuWr^`d)5idM@F!>DzNt!`qobA7gSuJ_DGR;F%3lk24?+r4pb ziPMhpN9f5(ozWalfj~yK*GL;8jLzXj37@`oAw1XZ-&<)48osh5H?rCyT6kC0S`qjsLSkCD&KIupOe#su z9!;-_xsK#IQpq$EX%)P=E--n=VH_Eph3SDg7M9DATFywjvYUiy3aSV6 zAljPl)j84j`>7##)q=D(&}>GiH-qQecfXPz`X0>cHD;{t=-&G74aO?pyVdqC?QHvJ zqlf0u&r9f4i-?9KjJ3TwY}KEv(xf}$9;4w7>4d0KJ+%tHDiLEPTS)&L+I5%w{(D-@ zH@RTYuJ}zGEdPHuG}BHd)Ax)~k+9b;CceATe4R!Q`|a;rG&^!x3mp#6T2 zH%e*DIYG-ksFIoU!sUA9`uN1xFJCxaE=q+Et*N0W@x5H|0WeO_s@)h}Yoo=4G<_C3 zV{yi)k++kVOlvH;6I7ev%)qp}<8b%DH0|+WQ1X%SIP0`JtQ+FmN*<_-zE-6hDq|$i zSFYz1xh|Zq7h;Z7DWtS?y;12L6s-JAyGR=L@F{rzK&6%dnq@o>RH~3?K7BBQ(recR#9VZX>XJy=`IQB=AY8n9>GDYLdu>(WyzWA zB~w$udtrCjv)>1nxNthZ@OrxP^nB!ez7lh!v`8uupDM0(a+?vviIS15r8Jmhq|}D{ zH~+)GXUUoS(+kf}&&>18vSd=KiYzt*%dB1Xty-Q>CoXej-v7w;>6ztx!k*8#IpTK{ z7RYfSr-htm=DaW_M{t_*X;qOXDUbV6xh25OfE1l+uB84oZ6||P^40SLsX#b4}#kHcDbX#YLV_Fu1HHd#&kT|+T$Uv zQ6=?dnxe^bv!r-bypFpiE`lE%|MuVgk8t;yKXsq@?%Ut zSQqpLX)N=+FqwgGKYq*P9oUHx;l@S6=WhtpK(RWk?4}9tExAR-;uSrh+!jly18v&T zoFe)!UOR%EN4uz6!&P-7X@Wo$YcD#uuTHO;>y~Oj=U>vS58d_c&D*e`L<;StGZa}d zrM6>-w#N3p$Ti);*AH>#ooH;WI=FgcYcraZFMWAhfmZdvLs56_Dl8kT2jx`DMo6Fw zan9P_GcL?lC>E}1##Fd(w|X7tyrBv)6{Q&I11A@=IW4bq-H?sjBGK5b3ZR+Rd#vi# zeO^SxhnxyeFR!GVW>+Bq03ZNKL_t)fiJhpt{>>SwsUe3L6e-%E`pnR)37U5_@0Eh$ zL+{|#fy`NT>4yIO_Iw&OMQWbA$;r`--vsJ^UJtQ;k88iW&NS}ipy>QQnb zrAS>C=JLd79KjlPH=0DGG^Ax>Ojt4GwqOy?RTm-F80H+AUteIpGFijzIItK^5H#sv zyT0_?`GGtP#9@J&D7jISnv-(QNL6%^(6;By8#K2ka(KVx=;^@LeYLgHa-pQGJ?|pK znu$Hp-g49dKw7Vja4=uGU}icrr}p)SE*{l6U(`KU-z^cFAYvJYk%y0;FvHmIrM(}q zY2v~K&%vtjQQAR8W~=EOZ$T)Wo}RcIUwM9f)YoH~$w`NX?OSQNQp7Mzql|%M!GsCx z95=(L&j>m2xT8ZSO&hRF-({?IN3=5TXoe5poS9<>jJo zTFI50Y2eetJ);loZx5UsUh_dyf8 zt&+1I&a^ArP_y*pqRw()iP_MMQ%}4R?Pzq47IabXt#;BoTWH48b|1K!fop-;8@LTv z@3|@9Oki@_jke2)eN%ii-Z5q&8-dYb#IQGx-8g9Xc+J>S@Z-q$FBO(Znm#jrYx(VW z{}QW3E{Y{>?^B9;pfuvhDffhi9uiw$2&^O{XN198Zl)bK)5z<~GfUR=Md(N{(}Y$E zc})<~a9C^;s#J`3w7Iavq?kYN5jWCYp^ZXt6ZW=f&%5*BJjN*5X|RnH6W3UvR19y+ zJ?W_j75Ms+vw#(+&paELT>@jdpCe0(Rj zAsmq054|JZiC!>vhi2O=!%jOpug42dKYhhC&tM1qIO3;?DeNe+=(E2Glul;I8Lscp=mQUq?kJOJEnO5*SQR{TQ9YR~k%xXJ}pj zUel|q5XbvChxWkg3|2+W&})aiqUL!A7gzMR=?jTk+t%6H);``|9kxE7Wu;;LGh@Jp z!+w9rUb)xrNI_ehWm{{FItqS6!nbz<^2&RY?R($8`mJZOb%L7Wcvp&#e!m6NEMxHO zhY{O^bX_22taECP%bDu;*fDd-l`&W(1eOM42$URoIX!b-j+DAEhRSd52R`0rWr}NAi;Y&B$IwRDS^(@s2cQ<>ywcOn784jNP;fCG* zK&guHZRV|Oej_sUufg`^+B8l59=cNaH2rn6qnmfxlGsW$NbhP18rF#>6P@RJy;7Ei zm&cz;KP<#W>pz;YeE;(oe*Vw@$mQjUk^t*SCUI>IZwBrkAKBeoE-z2~{`ddH=Z^<= z`NTggSB7wpeLZsM=Lt{3Yn*rO24WzUVnbf6FG&n;u zjaVX?L^68{>#jv;O&7k>4H(i?yvVt9tZUU8p?=P5M$xSz4PW##PR)vYwOZZR`Ywbn z*ss-vrfC|zb(HWb7C9fFS(-5z<1rnRY5J1WcBFKxg{4Q0rO75VQ42qP{{z?aiJSd^ z15(S77pO}o#A!0MIZrOR?{*v-XjOqB<=U&^*v#IoON8|0eyDy`SM}#jBiNC_?b#0p zZiYKXKQY=-Q$Y!8?Pmf5#YE~FcImZcnMyU5WocM5Aq85Alyp@Zl`-UGFcE5;F~Ks5 z;ertCNRvQyAcH50&@+ro#v-oa^i4y$0l79s>Tg_1#*onFVEE0IOfdD z`2}x3@;N3I2?im#%H#FS>FY1d*M&q34tv#k|)t=BjFD)-S-(Ca;U?J93QZP@xO)_Nj%ea@M!9-*IQ z&6|;pt>QzG!fG<;BIb(MeS_?8$71OMkgQ*L{~D5Lsx#9Kg`HBFo0^!l?nDVgR6QH5 z78QLxy0J-IzlRudX?iB=7nC!WhVB(g$%r*LuT?oIClFz19#`Ea7#T76z5}v!f#0km zyc%ozXhLn7Ts4IcZKM2Xu;vY^Em3JPDMuBUd*y{1xK!}cACdVwbG^)5=b0Fno~l-% zY!yLxhf5c;8V#EU+-%35cXV~_hJQV0_s^Fz7&GCVo^R}m=kD0zAqpr;(B}b&p3}!W+EwnUa^97SH$Z;VImT-AN<_ozg$*?vd8>?oC zS|~l>^v1mI*H!=j_q9x0NU^rfJkm<-^M{a2=5o4nJf4*D78A$InI$SpK9zzW1`c=k z+}=M>ML5qh^AgpNw1!Z^K=Bi)G-{clS-jEnTd9y`=6qea*a0yWxw&Ki=`#=e1EY9; z^N?BQGxNM~nKQ?0PutD!s0CKeq3LORN39Q#uX$?S3!SyaiZ$ouv`8 zQ8?br9&24i?*3+Z)24cQzm(Evoz|;KdQi4jmU&SFN7Y^Quo4zZC6|sT?*vg=Q#5_a zl*^TPJu_d74M zNryFM2CH|71L6i735zR-pdJ4}qms@a%EEs>J4z6VZ?l%`g$ zZXnsvjar2&N==;or2E6kkvhFKZS&6dvQVnLnKNo5i=k;JC^Z+=KTE+T-FLU9p^rCu zx79OFYZ$9FOtkep^LCCGz#FYra7JGf-2qCcZm0p?+D`7SO0QEQlxF;H9q3V8trs04bGneCupI=}3-9P=Fw4B+yJO237frtBhe6LWjV)Z(z zZKJfera>TM4NXR94ogEy)@!fpBNQ-M4f=Jtl8#SYj$c{I%r_r?%ZJ-rf)p;7XKK6< zR-H7pH|?l4HOH*tuc_O4w>Ge?Yt!$}z8h6xtzYkY>>9)t1Jf=rPM$IuE@MQV;q~}L z%nQ5QJ>Pu$!2Wh*jth4&bD1+AkCE#V$t96XWSP&LQf02KlP|R%tUA+4T8^oz>Bs%w z|LOPSQuyKNmB*(ia!TaX(Lfoz50tDfjg%{|moukj=H=yu%k@M_k=hE=AQ(F!RE&c* zR}%U{H7Q(6L2AcRH^p?;hIXABgU{s+IMSUXdSli8#@vM~x)=s#!}>|B81v4#*?-S^ zW3%$lo`J%ZChd~Hbs*&-aE+MnN&*ud)np{&rLB5 zt>dj)7n`@G$vflYx{PYP{TcJ%U`5@T`H8fZ?h zHrb2^f|CUoZm2ELj3RBy6?_u@1TkAT_WHtwfwXGx~e-1%hp)s)zKo zyFKTuoh~Ben7RO}*OsEC%!Ua|#!#bPd$fiVhwnESgLIs;_L*ymaqsZ3>blH;w-HK4 z+K5EQeYhunc_G*xqa7Frr+s8pN;|5kofN-xH4WEqr23K<#cEpZ=5tPaXQksua;bRR z9YFn{Z;b7jb?wQjt8}Oqd?{dx^aapNLQjyzvQi=LnSLtp#%Q;3( zunq&+yh||kzWcI{Bcft<*FedwVmi*W^aZWnfQ^C?dM`<>ilAGO@T`mZK5%GCq9YNj zsEuPiZ1zrQYqUE}QtHd0wQWH`YeA6DiR-|{Q zDZ;Cneov}US|67B!^^hP(=^%iH}Q1+|v-YVW(tW*Z)Xf3mwMkcvIrjL3!>`A$m zZnCCP*27I}Z&eZ7{dJ`v^lRRt>2XS8Nf*+5CE37!2n?el88Z#Bh7_ar*mqoF>&{;* zo|qG^Iv)17I6LxTDVpraRS&_Z3s;1cD+7gTm>9xBjFlxWnjWtxl3adOw9*BEzVK*@ z{As4p4jCM&&MY-BUuI1`r_#kJ!zm>&hH+r zT+f$H{3mN)r@?3%t5#kdw5<2=OE^(VVhH`ddH=vKapuuvuGh?uk6&p>7-A*_jm)?&hn4pzDF=n2wkslt9+)fAf?u>H*S8Gpd>UF*p zrJMx6r?h8^Oq^3-slwy2^7GFsu*~y|rbZCPaimG(VcaoHd+zS;*zLw%fiw1wbQSEa z)j{}J8u@&sTqC(wt%4Mk)3guITZ11|=v(r_qnnu}yv?Y8xD;Wj_7U zXYL*z*!#xrkN*WrX?!?bD37n4#z#!4OtQM@ZLcPgUm21*riW6soAM2#t@Q(9H3eDG z-brWOw#sx?@A)QcFW+t)`t!FE_LjY9H;X}gM}w@X-SoWW3|1t&VoTqneAk)TxI&967!@;N%mU0h zqc#=g4LY+6N@pmQ&BT@cfHj`xEN;-=dgnc1XoS5c23zm_7SThR-IAw5ZJD%0T2y?j z6UF~J?|PqcqO_umM(gwdR=eS`2;bO9Xr{{+v6t7)=gY)cmdv5O@Xh|T?h;@80H%Q(S{0EMozT@l5SH$o5@%x|2`Db!m z2({pFN;7d5*JfQIiJB{1>(_PbKi3Q~XO3|}l4o>=5y4tTfSV?LuF-yGTV%UVSL2}c zRF?E}Op~=fOb_sn}M?Tly8YWXKn3+x{8i z-cY-Fq4(3ZU)TE7o-A330aWv@E7tT>eeKQFjd#ktex2*@$^2tB7uGAXg=x)oSs6B$5e}$2mQ--yZgi!Sir` z%WfL^@bHnt*qFw^{rx@r-5wi)25!1}#EN}q=wFwpigOIXDLTIs16Am=ovv5jQc=Dk zXs@_lhieME@id=EW#N2$;`sE1n~wgXKXOFH8NG!nmj z`H@^LFRzcBF3+4o;pOBhS%~?nRH|B;b7eMa#Inv}TmZA%#A6pmt<^{w*J*0DR`$jN zDz4=9%tWKLL`{j7XIhP#N^6x`qoSQr(LXP1Uo_A>&JXN&dv5M-*&hy!yBo%Fj~hmY zFyY;x&#U%E=AbFI(zI@9?k4q=6PN3izl|rppI`7bb;^etPNE{Vdf#lpk3|J^9L6f4 zN6(jfuS->hwnpnarJkI0rq}7c1wFQ0>l~RMzw$WGT>LMas8>s29K5FaMLTU%TC|HZ z>)Fp3hd76uZZP(SlnfMuH_+;=f|UrDSXrXLSeSe0yE9=soV|KELR*qHG4Q)~pJ3TY`7tdVN)sc3!ACQcEE%N}Nb(A*Q$yEt)kO zo;NtJ;gVW#eLrwHEyVf4Q_lQ;rgy|)!b>NS3`6Q zd4C3&>U*W9ehpU4%9(gW?5@wsR{Qg__1YcL9c@!2aB12puH+euv`(IA+Eq;zOzqWX zqSbfJTzi*mMM}e%8Do>qP<;ndaJ|+gDQV9*TGfEf()Ww&-Y6wao;pj%#W#xS#>G@A zC6UrX?c|QwvB9-}PHQWjW>mM(SIKY5S4Bssj?WfNr5l6DK(m21f$U&+FYJ04r8Mn1 zx6ZsncPk#*vIFs~jz8&jHg+|HSgQ%(P7PU&U~0lulaLX+RfuR`2iW=GI1ZM5~E@gW^otR$3X^@(sQ$dS!b1Uev5gIc#r&eeDV0b#7P*uG{?CMNexj zEoo=?IxAvCJLIhznoCYvcjW!{-c(U?ODcwvpDE?cm)9>Wx#`(Kyc$=H8o)HDsQa=N zdE7)pi@E5i{`OgYGx@9{>q_NS&r9w7^H~kXgPNvuE;Jm*3^Wm(cwX|repc~tUV`55 zJDt;e$K-YdH{ffbl>tHV@VjwBY+!$=>~9z57#Rl7H=jOWa#li9*g?q{nXp4*JXlPD zp;&Hm;}8qiB1E7$*NIK*wWKM2v4)zjO8*o!V2ial7kW6egT&jqyX*8a)-=5mm=qAR8q_&7o=>SzSP4m5}Uf!}a5{qja;$JIm5aG-HT#QscV6G%>0PTT8tz%C8`{=m`b?nHOkOL>dSZK{t)NCE zK}|Z+nuZmMQbDWKlQe8NrStb{O%u2Mrw> z(OTVpCMfG!dA((>AlPl+-ghIdl2alC-^sBBZ)~qG>Gx;Tse3ikdY!9Y=XpEr8sk_S z8Knovv;tSJuZq$gOgDV-w&Lm)iSi~Wbc}i1s+&X&{#~;XS+BmLw!YT75rA%3F&ibO z@=woC{Nc-wl$1E!-|_JA19$g#gbr@8AkQD zwO*<=Jts9^i7jzXSE?<%#4Beh>`cRu@V1guV@ZWpJBd|Hub0p&nM$Q}Z9_xNts9cn z7-M^V(0V^^7_HXol?HFs*=C#Hf)i{oEYft3HeWDp&$zo`e{;*u7^)da#WF`j?)&9Z zD{;B-+A^;ta(sT`dOl%+UGR*pboDo%~84&yz}dqx)+ zePA5=65V?4UC|CVtD-Gp$wsjX*6KhrjUMY1!%|Hn)B7)5p_B{9SxC)syYon%F)efF z_x$U>_)B)*egks?>oH7lopJe!H8Yoa2BX;SoHNc_GL>bSX`==}Pbu7Dr14Gj<`h)VKRuRY@A7*P3+E&-U)2Eh$tQ;+Br=(*a=WLVp!? zgXff~QW?AjFXUn=g^v2O1hKud%wU|sId!xaryb3uG`)sJ2;OP;OViHNDeN`3>Tg^+G^G zQ*2k&Ro5J|ZDxj9X6j+ih}?&Q9xhNgbxiKejJ2j2;~R8cuLF%O3sHDEPdq*!8J9}v zd&a5Ig^BC^ffPE1ZctaC_c+TIZEhunv1^w!fGm+^R)2!>6Id$m!&d}MT@+kYBT}noXJ&l+nQzC zIGQzX>p)3=reDeBku@f)8zTH;9M(_g@iwuTGR@$I3P- zWucW!$(fup(Sdi7_uu}ST!d+9ELrcN+GJDT;vznI9SYSMM025EW8}d*PEiRKK^%vW zG`oIC!Uow4Y$Vvf*C(4 zc~(^5^Yb$$7s@!(r-3deU2?jp#d%n>>^i0()M;UvW}1VkOe$zx7QMm_iEqFEhIhMf zxE>Dt>ESbVvfthxGVk{@(+~JoUvFl0w0Byl}=;{t%g}yC( ze*8s;>0Ln12iz`FC(SeqKJmj3f5Fx5*X)NqyKcucpMl2n^CRS$a{QSZcQ82mFz7j7 z_0Nh=uup=t001BWNklOh=4<2tru3%9{E=8JeTDuU~6L@)g=5TXm z`Ps73Thzg+^pR#YwhP)Ax;3w~W z^NRQe!7+%VNF@bre6KoBNzl5&xfTx5;j=y)g`UPWT@8d0I;lu<_?Gc)CahU?qF7DF zKsg>cKYixq^#kMSBiE_WbwS-2Arq?h8hKG_jNUCOC{10wM6gJJ*64zcL|iLK(HNXE zpHvJIJv-m=-St2^&Peoprt$H7q@B(rySJ)94T&5emZ-??6~}K*y>)P5_ zxVqllz}0KaM+ne@kcc*{b@-~w-fi@60swXPg;frz3yJn-u+N)e#^%<3+kd^u3Kd~m zR9U`eeTSfRznGA&ihQwzXT8BUdfFTJyIZqwF40!oqTa-tU&LRDx9Bk&*toVi4=%!< zH@L0sPY1SNw&(xb~u%6gKT}1|_vfc94H~pZAUu|JcQ~A$fKk)wUhFw3XYcCh(apV-$%|Xqa&Lhhh zd3Ze21m;{g%?syQ#V}fnHi8$TVL1tDBO$ErDQ|R!TB}7}*>_9i%XmCsYU;%DMG1U$ zDfr;Izq{t<>cH*0I}V2(x3{-kT_3ogiY`TlexUP8s941TwN|3VDQMlRHE=LT zs=Rp?R_fapA=Q+uvkvgqTzgvCvMC!yX9Bi7*tvo0-2pc?xNZd3(H(A>=0bfv!s79J zM=g$Kq6hDwPoAA`eEnyCMQNU~dxqg#o*sVTvn$-o$ivGYc^dynTMDUN!?Ex@3-kTN zQ8bVIpXWx-!g+LfU-{+yGsoRPk;I>V{LJh5gq(JC?i>1ESf*#(@&e+Rn+|+~kL-4P z4u>mJr}?429~gF5IKv6Ms90;IKm}q@Px>-1`hJQat&sD=`Si-`>kFsTiFwHs8n>cvKE;jaq(4|6($%d+S9*74~aa9Ozb48A`#>VV@ zq0MKFFbjcN+GaX&YyZv6=)hOD*^^(nAkCJoA{(-KjdfVfDPpIoci^OH5A!tf z{PIi+VOtuARBo=XxEcmLjd?!vcs|k^Zq@E#l+#NdZZV&WKbk*UraFv|SlDeK&BC3soVNS;i4Tv@1PPQW<7355XWUr1ZE*PJYj#(+6cJA2L~Fv(Ck}nbJ|%X$ zM3;J6ZA^2f6(J&YzOhTnw%>kEn+ei`4UxpA%$*E zQdtMfCKd`hQ`msIt6`>S9G{BlnhEbNn|RmSCYtg#x3Sgjc3+EOsFh5kgKy`G3GxET z8z;7ALNqL|ZvVGAXt2Fwu{>G75`wxbhQ5ao$WpoL12r09wN`>HXk2x)rgOOHqKZhX z(8rzeNtI$0LL&HNSaH9ZDR6AWRqs0^(Sb`&sd8Kl6gz&v!7PUqIAxEr}vZbZmyMkHHaBG`#Uj@uFW;n6+Db^(JZHDwe~8}G&1oEJwP;r- zTwwlK_IB0iOxl(uuCqjq6!LDT^9&!zO=y-~?Sn2%ma=RJZ;__+#C7Q;kxlAk6yIi9 z-_CoMC9~w2Qu0O*h~{3@T6$6Hi1S-iPmGB!c6700Sr+a8L)Lk8>hUom&QXe^-`}u= z4p}0(f%6?9^k8}OIDK6)1SCb>gRCxZfi7S)&dW2u)y`bZb^m|=g%!hYVpVS>8I6Gm z=AJcbM3t?E28}ZfX)vQ?M%S7;TMxdYX(lNyDhmCu8~FJ12R{7xf#=sFQXETgEH2WT zCs!lh;t8E+aE{)4T=W~YbzR5?G5SGg1~fCVfred+oke2kemw>)c8YcWYC=s=6H)R@6$VwNr>y|ZfGC|^FQ zQs3$O91~qXPzCy4h@m6KjzTfgjN76>qEiEh&PCluQ_uzeg@@trv`}m>A=~VHosqYo z-!Gb&V8^1cebr)11eX%fS#98~w~cSeUS6ZIeJ~-Op1W?NfT@_wxl0_P-mihMx#L$Q z!G)9~c7{uI}nr5;y@-LzNflGYenx- z@2JhwBp4Y(pZyd9xPoppR>6Zefv0}Yz4K6oc|P%)NBX;K?!La~>gEdifkj}RGjpz- z&$9|q;(%Xwj207ssG+LZ&u`-mjVi7}T6cI3iz&FwG{l};=il_6=Du$TODl|JVXm2T zS$I0XaDRH?aJ!>RJ6w$TrUE<_5gHwdygVyi6r#xzGv9bMEPdWcB} z&X)DQ9;P&`k<;ak^X90w!#B2~i$YUdRBH%oZAf%T)$F=if-M{^TSD~GmMRtvRlXR& zs6*s3T5_U;EzLO91xyHFB{Q&y@p?`f?`q9n10&=SB|c8#oW#gkM4Hk$pxeO!F(_(g?$m7drs^7ydKv1<%O}BVA@Ou2jU5upE6Z(M|78OqTW`&R9`+d~qwKptJUa~ec zIBG5oMq$WdBKV|v=%S;YS}n7@HeG18M)P9sXFU{ZgIsk{P!_TF-Ww)V9q~m}vA>E~ zT3OWD6*V)`qloF+nyU4rL3D;*0a=Qw?5g6Jq*P*XxLS#QBss^Va6f_wg9G0h1A*8D zN~v^C7kp*Sj}6*<1*dN6YUOhE`s>~WeZ9dcsuGCiQc;DaWkUw14MTL6yS^^lwP|Wi ztauDG0x<+tJUdhQ;>oSh$DZaYDI_a6Y~{TwnGuE|ZtBZbS?zJmx$=5E^YHkD+bFPHg6GTzfJ|r#VQea*(oeRX!Bjy%Y zC(VS^!m>=vvu44~=M(dEA}=GgEo3R2^T<*%b6GU<02L1Tlx#Vs3#PUfK`W&J2USsZ zfuFJU(j>CyuX=8i^7#^KiGnLTV(Xfm|BiN9Lu_sxIl5d8970 zEgu~7QgFdh4bwOn-pfVsoznuMI7(fZ%ED~U16gL=IMUV1To!iTvAezEo7;DM_szFl zzq`ThJN&NWI`#}P;EKBF%QE6)5648qvrH57xbXYm{{yX_8DIaz@#&F=Tq(Kn zb&RC_4TqaM>U;(~_qTWIsF$F5?+%LHm%)ZwF*#~!jPp!Nk*n(+*ZYp|-`(-u?ScFK zp8eGgyW1PO+XK79f#e$3LAdVD^!|$JbRr9!#}h;EI2;C+Qt5|1r47_shX8%wbG5(W zcrJW=IZ^n9E(PAd^Yp`B(ZHPzWpPA=6eHigyJL~e*I!-p`QeG$UMb~@;76WbchH33 z9ml69qJ!l;BDM1J_&}XA0gqJC{21$3L`36VLNGFfqa@GRiaL~oBXx zrc0^mc%+3;yl@@Za;B-~O7X<0Jpi^B?%|^Bwdf7RwYo*$Jm*;c9n4#t|uSv)i*wi#{6`iD3pRT@b8} z1h2z{VCe7-P2QS{>TF3in}eX4V_BMJl2%igJLhn%p)=~RjdVkG_Inj`Tqr775J6@| z(8l%dv!Py~ZgDGBR@7?Su&q+o0fr4+E$2-vJ<;@-lKhKJgqgee774SBO1Mdgknzmd20&_;>vCAODHZ4@aiQ2V$+H7+JEq z%vy&zZcGedc-?KGWKmxb^%VgmTW5qTM;ZcJ8tZA$0JHXN(Ag40Y`Q7Tjn zxZo(QaGXY-POm&1pZWOd$bNSZ-7RzL@$SGh3qCu73`FmdY<2LWgRB$+J})ZHJw1^e zjE^r&A3xAdNA^+}b2TiY+cF*n%h%inLk=JECBmv*dbAZkJ69D&-t6z#-QE!qLQRbA z1U}GOR*<@yfS_0)Z2N*!@lHxOfQ=xw(xKC7?z6x0pbgmGFp zokt#?URiRbvwV6bMymK_rH`!wjc9WpPtb+96Wh}&VG&!7$=Y3-1IyJy4Jz={o7d^` zjmxrnE_HMBAP}31@l%MjI54`Ag+}p#xxg}49EI1ZvWo?g0Vkd@tGlyGCUudi33G$4 zM*610tf@4LWLlkxb;h-sWjs-riMou;=8TdGxilm%mJg~?dd&!$>*AwofGl2wT%csl z(_X15C1-NdMFXu7L&639H#}~Od7-MSQ|WX1IZew#*L57a%Da@=#mrU61QxuD+#-aW zx%Zigj4Q>cJf87o;&nXZlV?7^BK=My1g4o>E4A;Cp=XJnF?RS6$R?hbR_KDzCt=?Q z==X?M+EuU^jrB^Z?Sfz4ygch@T2_R;BAIm=E4sOkQ^(Nla*wW^Nu4*S8Q-{QUixY7c zix7>zAw^x!X|P#>ZI`3>L~ohYK3?+MTZ0zuP6DE*GumNDwlJ-gT^zOr^y-e?(9a?p zMm)u+3j%XpuNdjtlme*>FlWPgGv=6+y4oD%k_X+kdVXEzi!I#NB}lEAwk&E&G52?E ziYxbaMl`2J0Hu#wmOZenh-=Rm;;vTfwzPK6s{`L^zG?<2%-j~eE*)!+iw|$J?2tW;r=#nzeL-{q@P!9 zb*n#Dx45%9TQ0tGo!gtk6)sT&Y)g0{isq27(NowRDYadq9Q-Omv3Lv<4LI-6-J1|& zkN4Q~XJXcdlAU~`i$cwr#=_V#FH#S^Eyif` z8*2xgGgrU$Fh*Y1Wx6ioovd-GN&@&I(!>%(WFw~NcWP@oq-tHUl8-~sndRy>x3dkS zsEIw7%}5DxW2!ow({oe5zixBwiDAf9tl}x&QL1j%oOnuhP>aU?SbT4>v!+<~`aCb3 z&lB^qZ2PNHDl(lRkNQlEC)~J@>{(Ohb+h-x=Krg6enr09ekDc~(%>DX(;YsSQ4M^ut0y=}(;txK@#%pQ$$y&^n{{DAiPr z$Vz3}NF>|5ylvmZb&*SJ8uQ{kL|vfkT1Wp4S-1WskSacsok!aGYvp1NHu0W!VuH;~ zEsF}bYBuUlQBwyF@8V`^RsylU))bNw7Mwm`+1_7~MjxS1N(UAfXwHL=1QRWJJZ)K6 zrjhyl$~>JZb*8q3*XIX@A@G-f_1E+szP`WX{oOrRy8}T)MIZ{$!U+~7h0*~Kf;5{e z=nT`Oi+X3eri4~1=kdgckH3(YnQy-N72kjVD{gMC@eMwIc%sf4C0bp=_Z^`d^z6#j zB53PHfNsK_x}FhUP2$yC?QI5(l|)%;(cUj8)!ehzp?beI6E}6|iQ)F1<1+E-@rm>& zN9^F;SN9BkL9$bWu;Iw|?nQ@+ z%RDoVBhKlvFc@AdSl+yIL47r)QE23{P+BF-igZ#nf8%(3wcoEO>at|cr-{6%!^ep? zrDU+|>xz0E4gGGznbs7YE#BVXS~nL)^uZLL!buwE^O@JDXXa_ryjeS7BY|Sb@6|rN zq8#nq-3EEu$r@g#UR=d(+p^oj-D_rsxT4GC;h>6*R`inG8q4(pF$A(HyIX6P4WR{C z^2rJ+wtR_TDjFXl1i^_p8gipbfYgzk4kk~wd|zr&XKC>C!$222sqg5AVRMYGhj%j! zxTo@YDfHbfOQ|^D(+vaT8UR*9CYuIfzvjFVkNAq2cD=2=mMP%x)-3hD@6oeJIx%nO=U@4G}r(hcXl zFvLB+FNjP0`gi{&eDxL1E&Rpre#7CLulSFD`+qT>CS0BI&ND5-6xGGiHk7C0pOWZe zwwc3cn#N6$d_JD(L!@(ox^u*knWlxgWV{QMdC|N!9Ia*@-t2aC-2l;3n(+Mc!pqAG zT`8N|vF|z#hXXMLUdJ=bl9^2Py{hKEy1V86=8A5=gH~B&E%p?#u1ZP)X79yy)QH@HW!CS%6 zHh#U^6xQC>8G4DgOKuh5#BUC_CXF^`YANKUP%R@}6~0>hMyJl|(0fHMI(2!bUBdg0 z8hR`CG%mt9Z}qO*lz3f87q?s~nhDn$!7bDpFU~P5D)fDmOCFOU#?}TP1RZ9UrQk($ zkRg?3-ZSqYijcZa2?*X(XUzcIJ5TQwkyfP9RXBK|2ECx^8_P2D{PM!%<0HrCS1lCh zO0JEuXjXE|6G-M|ns|7A;Cz}`=83)=w(REC8rfW|Yi8R@Msd#5oFj;P6CLozMdw5j zPGSx^?{w&H8IjHwWH=$`nUn^8{rz`*^YvGj0r#dRU;lf9)6rBuN0p$6X)CZph=hKJ z>jqpJ$h9Ix^PN-YIiCuBESyef<~j3l`pk45nZ}WMnM{5 zTF^*cI-iiJMd(P}A8z^X{yqQVw|~ah-+hDIcVsWjoQW<_=Y?c0 z&n_m0s2Q)C5#5u^!qe*ukDod|#>V0His$Kt*xln}p`}|Mj+w{v&onQ*o+jSE`wCZ^ zx>zk&D~skJVh!pZuIz+o?39~|#*BKs@P)y+Vag;FMhgNIK) zBUj(+9iBS9m)8ga?-hd=W_(I?o$$`zQC&wN@W-Ei;-}{)etmnx_4O^geI%qr=u92! z8Xg^-w5E&SAdQ`aAKtyA6ZroAj)#*jfLm>xUZ0U%Sf(?S#ypNpr?W127t43DJiAh% z22bZILkPM&Nr^6Ko?-HV5Ci?q)mA@EITMG0ci()^vM3HQ$Ay>R8J9p^3a_Q3Tz$>b zC(F|kyd-?AJx2^@YFx z$A9GM<(Ve1GzhU{DwSR8nM>9jL_@pPrs5E=;bYU9Luy4lG!sN9T}d4aK`!Zd&`d6} z3Ndw|yZ+wdS1+d1*qznQ;8Cm^m+Uj&JPD6qw5eOHb#ZlVYE~2rqy~4cN2UTUQEfKk zL&Oyu5LgblIN6?eRZQzZa&;!GjlT{^v~Fz(b!{@&HD)t3s=wqouM1N7VtMN??wo4v zxm+StoZ&2k1AM%o(^rLZRYkLk47C$q5p`=G?s|Z#+esTsX9H_#jo=fmDKfki#jB0S z6VrHR9|Xrlmm)Dn=4Hk=j}PG5OicGgr;f;Kh*{@Uoack1>l5Gn>N^&f`T38T@!`b0 z%=97AY6jmd-_~4m&T#$GXtLmavFbL^+r3*`Wtt}ZYoqQJAzeiCLeFF7j~_nq_rL!; ze*cGmWIQb_H4-{?2edWzt0@kC{k)w~!?f;c<|@%ekkm?e%;thGC~mrmGvi{S};+nOYY5F0r`8aVn%v|IO$VKB#je7wsp+ zS?`uBQ&p_q`QazV@ys9o_%pwJdgS>u^E!@HA`&$wZ6&v@%V+0oINjKW@GHu(iQ61K zO@z>Cj&p9p%V{FdBMDEQCtA)F5BPu!iT!oY)ow=@l`yl^%EQYm&*w9_HGTf6wBm41 zMgM6P7n#^$9jG+1Sa2hms2E0tr``pKcBT|Zs~QDI(Q&AA@bdJ)$5d$Z6Sr3bA%Dm9 z^$ot>Lg*-V_Sg2t(X>W0vwcCfe8-K{zx}>7547UlnpvL}mu!m;X_-e5-C=`gXBQ6mRjMwT-}j5snm3j59xLZLBHQ9+F&euZ2r&wuz^uFH{}9p|TygfP*1 z&lD27{XN~>(a%RxxS{t~%(>Cu-_Qpb%ECSbqQ9ccC_g$q04JLjL<001BW zNkl45xk?7Tp>wUuc zq@r0{YluR~)fo~OQfg+V@ah&D=i!M`xfJ7C#1W8aAt1%Q4 z?!=o=%Hq^SO&HPpEuwOZF-eO2jnCD5!Pss6! zc0N=5GtG5;b`{qa&Q*Ar8*zVNoF*P6bA5GSEQRCK5kF0+zces64#}Y zTO-E6&;@pbmr$LMyUh_ zEc@DtMx8iYWUVzlyZYhTR??qtXmRqI$*Fie;Ctxw9&s_m;JL zS-@>|nNzgA_f|8Tvs^axwAjKy1yRlE!qCgbJ-pI2-DYw^`&V0b1aX%Vu<3x`1#el< z5WPmDuEY2BxnBQ1@74ve&LLVY8sRBoXHcV>2y&|%HX5Y7wbN>C+YGC}k)Z6eX(j~l zI*(O@nQUD2mwY z(t9O0U|IFDZWTkcUtdMBMtKaur~rxzUhAhd1KP3H2};t{sElAHH6i$@iy|{$cpT9O z6=uydI!kPExh~c>Pmk?lvQacu0OYp_O06->3TW=@x3nJ-RMcTa zD6M}3F{Vux;9I4YiY#p-B1_XKEh*}Z_M$V`<2ds8_{4Eqpub{?dveW$E?FcC+uF@} zrGzG%K}s-+jGc{&#MjrBywhdgDvW!>Ng_6# zS=Xxh>SbJ*mYHSIWnw8+7xt$U<1}iGVRP76kHmUr&Y2}=GSvkB-ULUNsM;?wR>WdP zP%%s^8m+ui3^zfwiTKyQZzZ5@1nZ`vMwu5vX=Yy3I&<_4KLG#l(C*^Z4+I=f}@HJ%0u-^!tw8)xfvkf6uRW_v~Zh&}sbb z;Dj_`rNV#7T1~7n;OLjtNq($Ua>~2EX%^Y%!q?-=;>YBgnV)1 z>;+QX;cvgDooCwV%%@LBjt@tkZ(i8-SNJ9@Gc1deiRNh`=b4;m#@txMGcS>O>6r7( zX+AO6tn}$Fs)+4>`#1j@hhw%kt&~Du77qJib7cloSBhoQJ1@*R<9#64nJRF0x~R*O z7D1msKk{-KIZZQt4giAba_)s_L#h6ueLY_+^uw5j_iiK0YW6Z+_( zmoJJI&B7i@%L1-a%B&5TT^6MlyrGgW5e|;9De2$F;~C_!*~7&fER)yCK{O?;HtT`p z6H6$THDc&H!{4t;J?FxPzX{F}d^MbvsVaSsi>j`zEpIq22`i4{k}>HCh>vuBpt+vv zdPVj&&8jc0=)zDMy{UzA&Ks_a^$?P33o4IviJTWw=%`^wZh>##-}Be^KR_4N3U2u` zyWzlZ{^DQpc>Kh>c;Lp}!2X`DOR7>uRW-|!zfj7q%f8?h6Drn-c<;%JHa3WC17p_| zeI8@fhA#xovZ~ecS^So5bCwbOfvNW7+0o68PzIi!BJa;P&%?&Qn*Cvq?-b2gbHO#CPpbK8wc=9X1^hGoMe-{NeLQaw*hY z=tATigm=lo6d9cGIL~}~dSo6)%|`Tr{jh`6k<@iuCmn3L;1I73iStluV<`*FIv_7q zKj+wWw5Is|Wt`~yoeEp5Ac#R5lyRAOJst@zBc;-(fg$vo`;rE3Zf*$4Gi60qEv0fg zoteg&!)`~}HR@7$dj3d13=F+xcFF2=*R0GuPrN+8FfKD!R|jH@v{?t$d6`I^=l%OT zf(ZMeCv~1uXQruG?z^50k`>!pTETfy7i-ka>(#Zj9wFodn>Ob!OB|6#2%1MWOWah@(dnS$GGG+Clx%Iqkb5>7}l5#I&Id8A3>bUIO&#bOIOeCSC-&w)Vj6HoPtY0jMLnQ@+Ix#CE`Vhvo< zysWA?x)?pXz9;8^w8%?@5qMq-7K&5CX5FXx<%(*cIkwCXV2T#3GF;J6>l;{CK4Df%7sm z=MhikYQN*Z_yPX>ul^d}_w>7-=o-N_Vu&1`_kcsw%hBhxtYdWwwWOhw5- zQ`R~yBb0_l(0I*qj4l(Ukx*xqY2eTgI7A5qH`k0l5^|-?6~|uBwB{7= z=%7!5&N~uKo#~;+haQJhw5s!}w61{?GpXz71p1g%q!0q71-dlQ%uTUO6Q@O>ddgDy zx&(6VxN#ZjgzI;Eiu24_C{rWMndm!~0%LjPH{b91o4@)U_cwc%1%CeNPyB~}|G)Ee znki0ff#ayDy%tfjnVq<` zwr#mwYuoVRpxO4Tb6460wbwRB-MU+R6KS$GrS8iG&jHyTW)IIu;3xYOSH>o6Z_8yQEwY>lQ1-QY0Q;=D;KAS)OEFKu9np?WSM$&njPQj zHiUMD)q6EY>NK+ioCTkju410 zsu(qD^R*ie#I7dA@`ZZaPai*$$1~U0S4!vs`k}wLd#XiKsZwL34Q!e1Z+fWJRV@Cp=ytMx z-(*9q^PdekSVhl473ACCRLnJHXXh%UyA&Tbz*${V$hN%SvMb*#?k|64U9_viQ0klh z%(;zDWa^S_(XQ_!m+ZPXj>9j0xD|D2ZYqrYQhmTRw?&$y5LHYSd-jI|-F~2m(7Ta8 z{^1`8^_3*olo1YxD~4W4MAiV=U|P=+k42l+#^et?ESV1{;du$1TThFcOW#D4EMz#q z)&0}FeO4ZlB*W}CbA@R?>2c~vY>mz*#+O&-msh&zE#A(E6rJ7+{jdjd^f&ihU0t)^ zU$Nwc<3Ii$H)gyP8-}ezn#*F#ZP;adzb`sqT2rcCD_XyZs~6``2n1%$LUk}TC6m0K z3g^@@oeHlp^KE~CQTXnV$g^WNM5tn7i>eU-`2SCEo?G$M79CJcB(V-*MTF?f_D6~h zd0Nx#bzRnHaw+OAww^qMKz%*oj%TVxvq&i%KR?lyOs&G}I5AcD`SUZ!msb-pRf?#) z+5>Yfyc{R~@%R74PshwZKR=VBe&-fRoStWX)26ron64@3ZxlwmOw3mV%gRr3&LWx zxhRn)IFfi=70Tw^s`fcoeDFkPInptalE(AZrpvcnGsQI%1O}xbnZR0XaoRjc5C~#U zz|}Qw8KbqBXu#%5E*N#uD;diND#B6=B^UOs;i4|l$~n!;V&jFcpGx>{DRhDc1PuIJ3-W;;`f9{)TRuSZc?7`h;^cF?P6qr-)aX znMmlK=b>#kih?D~$bzeUoXUF?}?yw;I^-!t5QP3abv{0d>Ai;fsOx)^a$ z-DSjpc#n@7M;U`x;XuvwUf2&EeU~`+NU~m9%-EyiNfkwT8!$R&x!$65nbw-Q`kd7p z>oQ{vZE%}2zpeN2_Kc`OAb3w{UbA7%v2Op%T)Yj*mAqtft#qa6&k+M&RFo0&Oc2?Y z+)adKod>jb5y!mAR#qK=zRui=)uXF{VHLo=sWpADWuen~gVz~E2mu!cWUT`>Q6Crd zwK=j^A;c=S!8@WEBt#iZD!#ROMO$ldbM#VG7_=-i^Eg}7LWhV^Q9{`AV%c!nZ=93% zl3H#WrDuzy>coWjP6eJx>p5>ER9BigI4uviR-z9&^YBo8P*a+$@34^%?3`PBhpm1O zVH1ma@4yX+OKO0r>ukAgb81-^{VO@fzv(H=1M4qpNW&02oxP&}hGwtpV%IxIN^$FJ zoD`*Anc+-D!fyMn=yI=YqKp#so+b*_S8|z%b)gqwNFCiWkvxPt>+-E?Tt;P`J$gzj z8`|GnOoPXLvFKfemg}qQM9P!KtWkbJKgbk1jav8+;p|Pb}rGxO4MysU~X02!Xg`rd|i~R-`6!FX~o-LbS?V)uapSkP3(+) zpEr1u&81m|8@$Qva+j=%)M<1qOJQkBm5Tln73ze`xxR_50?{=h^$U{NMl@LSuT7M+ z60FuZ4UI185U2CeDorU3CL|IXXNv>~A*!Lo>x-(=D7A6CJPH=0YD6A0ZHb+OvZ+Ay z`Sc+XW1XsuA|qOp6mP?nie7D}F(#!*E#CwMx}yc{RWal%y< zEmT%frf$Mkv7R*9T=$z%wPcHTP?D~D^D&F{EE==o%=KS3M}CMM#H7Z>3ZkwjTntnn z@o69pJt-w(KOnv%S6FIAT%<#nx;oqO8>NMf*dZ6fwuv7~s}{S_aMj_P|04dQHWNs! z0zBTB8rOIdBTQ#$EKPr&zVFOTs050Q0@50_=zL*u9VI$kr|5ncteC{xJ*2gDwmVr; zNA3n{O!~}AhqyuS{171qiXu96{VeMpA9UVcgp!46syw`me0VxDOW`2*{N~$lxVw7K zp+B%624Yh(Wt|thZy_(4W`ey^RDf>NC!?q6`&f*6H)POeEx(inLhNKThQ;l-Ho-%8)1N4Vtl#$`bsQzC>`0^TyEWfRDs-@5|`Q< zi|KHQizDBAf{YB|Ox!n?>yC&0j^YC^r7^ldLB*WTp1-j)mM&6bL{d+jz?VQ5_Z)VG zahjkVX?dhk*>@fH?{0|qckebwXs|a+i^$9JW_4Yxs3cjP4Vx0slZOtLLa@HnLqsV z-%=kRNns?+iPX}TnO<_$C9XNE%93&0He!(tbOW+Lp^Jq+ws=Jq( zZCM$t=npLIO!b*K+>*yh(ff1a_y6<@zx&r6F7=e^IR}vLj{oC7{NKd+C%iO@bo_9) zaP#htxEm0!&fF-DT|ba4F|(>$vi z->_Rz9G&x&(zMwoa>jEGfw>=${WVS;!_@)V?~&_U-0ck^?Fs!zKV0EUrSCOct{m<- zg@N7;^eItl*5R=g6`|?0z%@t3^Q+(e4!qaAUV*^}`qWc^d2G~}*x$Wle9eV#h@0<$Pk!g{#)la^<^kzhZZF&FAN5rZN*dN54N%EKkO_Lgzh^o)$X1 z7Xq2!6y3bk1s?<-JxE!B9_Gl@0=m{p2uaU3r^V$GpxEF)_<-}#Y(CM%KFtPE#;K)k zXK@t=MTsNP1y%HCEf7<*rN5}tdR^WSxTxoYC#G&|;@0!Nw)!UTZ7m*k8NQ-oR$Ni3 z*{C8=MGV#BXcBS7)A#*`N!;9VTf9Ob?RLZvHU7ee8v~x3>pQ-BU#QuW`jOLVWX?iv zdX^^K|I5~!^~jQ4S$f~u=Nuc{J%&4GCWj)snx!hWpaCtQp}(V_r$@aBdg><$&_kD0 zLRFVaELJl)<#30X+}+I1q=&uD+%MGxh)iUX5jQ-{&an1c-_jv?=oQ!P7u{?t>XOdc zx|J_I^0Mw4hY6?Iky9~I+jeN)}o={2?zl~f6;5rT z^NG&wDW#BDSn`CcgxO%aOSiyui?Te)WrAviqAouim_3wrZT%3a+Ryo^w$w@RFF$ zM@E0O@R}1WU7*jC2rM zVsSHb=SUfRcGMPmNi%WikO05^;fd!@i6vBUg?F#+NMYoM=ZPgJo=-EaYQNLnz31Ql zSHIwI{)@lllmmbJ&;QIn{-6JWm+$_Cm;R2aU`ZYm{+tnW+I1&&yL2G83*I_kVe_tM zi_OkrZ6$IvefKYkRA&gyx{V2)(T*%aZYer z!OMEvnz~NcWP!6w>$058$$z5c8CoHFN8bm=*j;tDHmz2wZA0lfAc^2&08x}+A3Jhu z8HiR^dpXHeoJ~5_&=$ z@zRLSv7oq7opU!s%dctNhPZ(Doe6M}zVGR}ju?7|E`VqDRT($P;&sVXZdbc7!f$p6P0-C+}1+@M{uv#jCqZm#!GK zqOOW;bz6#unC>Yn#+?m6_4+x^b&WsT|*CD-k_x;#H_duEz* zEICn1wWOPB4av;ArRcu6;(A+yoYF>*Tg5xoRsDhjZqf3mridTH+{+V#|jBO$tlS4Bd{K zv1eWu`~t7LKo~mamx+0qxqWru&0*l_sW7Z(|0N++?;F8Pftf<9j#{-3n5t*zAVL#0 zvn@qUw8hf2KCB5&8=_EEEHmZ2;%E}K^HMcqzMi4IrR}06bG2Ge>dBrZSp(V|)lI}- zdW2UrrmIKzsTHA`#^#W1*+p9D+Nr|onjs!)iF}?5DN1^qe!w``! zvP7*vyW1b|t)rH}ejHhHBam>+?0d&y>~Ss=!eL=`**y)`-=N` z$lLubKelJiea7{kyMvmjVpRNn97p8@ z$!tc*YGU7|QsvLnuPWpxvQ-DJ{qfF12bEG3^S)y29Z+(kCe=)%corul`@YU(R96aR zQF7mOI&wN5p=4WYH26^0F7b9!f@fPpPL}vyQ-x+aZ}l=T+q!mZH4~PaWSXJ&*I`76 zaT^?l;OU3XOzi{1ZloVNM&}4deZpPHCCNKM2mSe*g-vSC+Yq>VU31o&BOU0i)PhS5z*?c=L)1z` z5pphs*x|eWfP0j7_xmK*_>J zScFTuy11>wT$+X{ym$+{B}x(9KXW$Yb2T5ynh6I%wi-}uxTucpWB>pl07*naRJW3_ z>PjamO)F{aZm`m6N=a6e6!jRrRyu6v@J8@KD+#8F+rtfCy>q;rGIMISr1!el?H6JZL5)~Ri*S+8(^<=wc;{mBSz`IDm1HS z6}G~M3u@}BW=usMI{xRd9CU9QZMVGCYcQ@Z$!9aGbFuB>)a+~-5+J0uU%Im zuSo9g+4NVGixPx>SuH@wkU$#Stq^U-T@I$7ahF>z4MuA zAEQ$>`#hY3AVJre;*|KD)-_OdV$zPuojKLascJ1DRuAn=I}c8)R9Y)mJ!rau7}>1q zyvHMoa^ETl?EYC)kKAr)Mi6z*Q;iI>?wdH(K11m7G13n^b~iV4{h(ne@sw7TDD5hK z)k406scdQZ?RH?Hja<~IS#nW&PHME-p5>C2tWw*Bc5#)q&qn{go?{X_n(a9oInP0r zi1+#oAQ9t8LNQ&>ixRgf8CNsW zReEW-T-f)G@%5f^9!Y7(pPn8#o==?S#PPh)xgE(VrKU8cxQawIHKd|Pf7 zYd^#lgs`r|aT5Xc(54r)Vh>0VM_Cq1&dgKdk3amt@4owQH?QtmJNh_1v;^BwHfL?a`WmhT%Btw(b#r#oEbUUQuTd{W?aw+5wX8xN(ACK{ zNXx6^@Cv9{4R2CgRWpMWaFw|BNE`a9y2_MhJ)jhQQJuM%@KG^5VX&SI`!h5{n2I4$ zC2!)cW9?bhi?I(uy&&dB_s(nTH%17K;tR!rM8n5>Vdx@#=!k}=Z>^CjNLek1ZF6oy zBj*#njHKf9g6%7HnfQNyc;@N<`de=Q-M@ihz!mLlc>U%*^OE@Xx8L&8-IC|u5<5-l z1nZ4GFAG6DF?7^ih+R-BLrxs06VIok_AJG~{qBBC z11?7BOg!vHx)8O6eLAsw*C@C5lvWvB$L-;cVRt}!j|8EYh}#|56&26LHKa6_^9fI5 z*zFm1d!*}-vGEfhxO?-KP&|~v?cs)9-!bk+#-Zoo>6zm)vE*W(MZgv4y<_x|P6U=h zt_ywFG1`!*O_fqJu?Aip?&)rC@qNcqDtSrN)0wUU=Sh1T#dCl2N_)g|VK?p=V&ueG z`+G$@eKrTD;dX-aioPEXTQX2YpH1t8AIPXZVQfeaQMDGV;-xK?eICM<&o>FEG zw{Ph~{=LB!PznyfHlAyS4P1uk2Af^{P?fR(|Qt3G54EFP`xTaA?-oJiN9|j(ujyya*^7!<^ z^XbeyCm;ja4zON_>Q%9t4eeyATQ7C9!~B}8TL*eNh<9eb@-~ojrQyS+3r{v7VHJt> z-vuYs5SgaT)A17@KYrqLp18M!jvAw`2ZEQOVY7i^TwSN4My*y0=a)y0PY*21Sq%?P zUGAkOj?2R0L7 zaFCA^!3Uf;rjm(X=z^sOy;IVFcbxsqxzEgGhAwjZ>cIWiw|w>0TZS=mdRfROsEpB5 zXYGtWolng3L_VFE>zSu>;q&t&saD3r9o=|9;-HDH=9zOts@1%R(qf`GmVDOEbg!t# zloENGaLb97CZZR{-Hzj&ab4zo%AAc@;#}6`YN_~A@nK{Tb^XikHKR1{L&v@!)Cv+R z-RSY%NRd6yldwGfK&VHOG-`Li#lQ_6sdwN#xmpUMBubf?(nMJb&Nnr$Nds3%MF(if zgn_(X%z`7hz$wj4c_xR7-|eVQ$aBWKN^}mXkc;D?CeD*%31_~(zoRadLkLWv=RRqPxol_zEYt3Z(1B{PNE(3)Y4Mx8XR{P1xEHgjWu7 zMUbw8HgD*|%f(mF$}8>G)bn*qN0#%EVd*?Tz&bDpKClASGJpiQT|>xTP#F z1j$4)lC|Hl;gCy(=*m0ROM9ql?lT;&tJ+5=rQmBoMDJbVJ5p)*CQ8T1j*vCUpIQO& zcYqNHHQY&{6H|CP?slZa0HwF=RX&($H-o z_ANbN2+}n@ERL(eK88o!tPE!M*1z*1YARDM$nkaXrNcY>{%u8Px2@aHo9JGhRo$@C z9QXpkSe>>S?t^{pDPHJ>_5DT(JVawccq%=dZ|4E@lsD?m{}S?!is0Gn46* z6EDlmbDH@6=@TD5KX9BE=3Hsc6S^J|MV5xD!b^0Onv;rlKCsLaE;n3BEH6jq=LcLm zl1>kl=>$c)HFGiK_9{NPOFyy-?oI18)?~ZWq@43kE7tcdt2w6^_g1L znm5{xgXn}?5xyP=e$fwn6C z5uJ)k&8x&(n&bP)F_(_Hc}iXA4J}(1P4udNAt*%9X_vKP4){JCD z*hJ+uCApG4WPRgBDR1JHQsO08eTl>8o@VLnN~TFQ-CN5u%i7Z{UiV;k>2p@|xTe5V z@3C4Cs@+9knv`rHx#4`$>8PRJZI;d#hj{_CYlAzU|=mvTZixcMDNFpj$6B%(NS6JM{>`u7W7(~f6 z^YYAd`ou1JN)9}IK2es;c{(xYg-j&(f&+5m#~**-561^`S@h0k-R4C^ajYo)AU3h}L6}o!niI=1<7#E+ z8aFiFy?%oXulVXc{BXG8U;gEvnU@oHvE!?;=c}6m>ELNT(Oe=0xRpXr;-+i7-Z^q= z+;yItU9ZXXl8I%ZFC7Ca@U{fr6 zS`w7ZsRfo5A5GJ$t*Y-z?I|Enw9N&N+f#Pesm>;!vEUx8!lxMV1zs zYon7u-)TVT(i88l%z0}~kQmX@^kWnmUTW4gaA}mYit)7+#cyw6Gb0#Ljkc<^SFt3q z4$M_2s~bwXT@8;k!@TZQdaY=>r_~iAq#8x^mV&l>Lbl%AjR1s_+8h~Ynv+Pln28O? zoU`6NMPxIdX{DC^I~yFeQgtSv_%W?E5DlFqqt2{@lJ)GrR>{UrYgIeb8l9L~s!R61 z=vp8WmDVZh?o@q4zlYKU^w|wov)OD<4p#r*y%2+%AiQ-}d#7~}YT&9y+tK|#x{g3V znhq1A7t(U%2T>DpDj zt&bNYd~HVBupX!C9K~Z5Q){3=HVTC^+Ga;0NK!hY5q4J7a2LFqbAi579d)fvs15pT zbnCRfww)!ZarwDPZ1=Be}DS5yJJ)`MePG@t~tXC_^&aK1dAo}xNg%wiKH2+$ii&b)$oN*#_!@zF8 zV;D!R{%wuw)llb=%^=^!j@Wg?u4nWeUFdWja5}r#Dtge`Yt<2yp0b*WS28YUHqd=t zX`A95p{<{zn$}ti+g6V6^3uCEt%Zca*x)@M@c%c+MU$rJ4aj$N={Id@fO&zySpoDjH+#m(uLr0 z-9XHdlGKRdEx1`yBjwEbd?L+9C?{NXoL^?z@qxi5Vrzu5usna}CXjme6PpyvU=QF31t_kybM^2vzpMUosZLv{ji1wmsUy8G2L$N-u);8sC zL&K%CP;%qtWxjM;!5eDHIgq-FOvO5mE?6Q(ZmSVsiIK;tQaewsmE-A= z+P&fUbmo8hU;jP-Tmwq&{Bn``GNoY+rQ_-@Be}S;lKSW?bUEM?1!&;=Vv5(Y7|_rLK#cu;0Jb{ z;)NgQ!2O$%WI|DoFya{{FnWD9)6h}9URqZaddr2p>?m`h>pRANr#PTe7!M=$&=cKA z&6(XeGQ_YA81MVYAc|KXhn>1?Q!-Sgxqu}oa$X$8i*(%94En(ort4SpG8}f?_%#AJr!$?UBwWPHo zMmNExUB$2NUXyFWmB#D4do>0GMZuR=!ADwkntl*b7kqOJeUJBnvG1t$p20_TIv1fP zOM@dwarn@&+wF+85;}D~2WgBukCO<~i!PFnGfRQad;qN0>< ziR?VmqPok_%8*hFFYNSP5S;$*EP59$T_(Y+K=0SXruDD2N^O}^FSsLQ#S3W>RIN^N zn%j8f>t!Vbx~^mBB8R=B>#TRr4oTQy(pl`xS@+nwUO4or;%J5gYWPh&udT66M@upr*~L1_mz-E;?T=~Rk;E}Mm>eu3 z3W46;e5bJL0UE}kzx7l!lYkV2T;}NAW0qkeltX@So7cwStg?EH_iqt zo)&s$DR{4Ig>xQaN7t*{P|0HUR(P>q)YTxjc0P+bJ)LJ>CeF)=#riv!X(s1NvTmhg zJ8BPXtt|6QE-K3QK^PAmU5xDa`nl(lIqU|S^J*{w`WV@DihqpWQ_Dg%tmZjq&b5FK zq@tL(lo~rPJe?E2|KkVh46on5h9OX6pyLxU28sh>B#R^Tk>1U`=`wF`9slaxub7+T z@pR(%zx#&2``drucb{f{{qO&dU%vkb4t)e7VI29V-~XP^4=+lQnF`GYOCCoc;6#1m zK6+dbcFhjvW5D;qxQpB$Mh>@k>~CN5>gJXZdftRUk%sRh-QXGfj-UMOC!D?KPd|Lz z%qdA4sdVb%UM6lj2ScQe@5s*cMm*vDTfRP>aX#>q-gEcrUNK;G6@0CdqbZJknHQ$> znVjdXm+my3_2+1gokV=!@iHwupBL(~Af*tp3J=~1o#=3(1|7VGejxN6hjGu?2fQ?j zL}sUFy8Y18hk@!k9zQ?x;q-*dFEnx7zr9B)bUq?MbC$si*)`^DXrLB!AYX(gy$vQz z1PNXbWE<}=TaC)x!5lzDS|;OBq!E57}g-_SWxs)-Xy6`c)O50_Fsvkm0e z^qaft#@5;p`7`7^S4n$WfoOK=UN7itYQMUbHg%Pt-#V=QhPvER$L=x`T!-K7YRLJY zgM43x_3r=Ivt62vLS;=EUtIc^H26BSzx$d{GrQe^@4x?%)0Aj3aC37*P7`BLcYbj?@J>w$UBQ8rs;FM)NK<0m z?fK@XKj$$vmWM`6CzvnYnYBV$Y?xQgNbJ3)A6i!OQCUrX&9L$ig6o>YCu)oV3~OOFpbpW4%<$ zUcaicA~TDjHLvObOtRieel}jHY`A>qF1Xlsf!<$Xa4*Rm{TQzL?wblHx;CgOaK-kn z2^Vi@%6{lL+#cx1K@Fb6NOw38c0Ieg$NQjyc!5mTG%+@~Yn7509!^i>W#)7`ae6s2 zPZK{pJn+M(&m4~`2$Uu`A9YBc>vnd(ZYWs$O$&{@)*YCsQSV=lUW8FRw-R*#;Jlhr za#M`B=o#~5y`aGd;;u(RM-$W>w?iMi~`nFELn{ho2~JRgrdk}t>bHJ zm0i>4y{-&qCz{}thMUhUPoMb1uU~lheNc4u(9!ihVTinX^NQWgfxG*AhOwu`gC?6- zvxmr4x2S%96+Lx%x;9riGg7ivLv1+a%kW-%CjAwC)opm=)*8+a;3LQLiQ|&^`7b_0 z=y`g2rsPIC&$L`j6tp^Pr%84vf$nx7JI{xQ&pe$@;3B)5JC;Rwd73y+6Cnb!u;f50 z&v;JMJX33je|n_SIL(l2;onE?3U9DEc6?_9IM@5%P!>=?tGigML#1^$yflO%&#Vf(0ivBz_**+I%WD5yF1?8xCR~_Ozx*$L% zmqu%e*hPBrP#S4bQ)D-c+Ity0#!)F}yW3YvuSu2W14R}j9$1{`D1j!0m*tryd4!Hu zJ$-T1l3CKi&U-pv+4;iQCVWl0J{85-cH&4y?^>#bX$P=xD!?o4veKm|Mvf$zsm`(Z zjx3|Ib9QUi{70)ZiPbW6f9k>p5eiShL--v8tqe)|4ByEotPG*_ncGs6%l1)>L$ z!n8bce9#)K>NRX(g;RGeNI3q6W0m$f7koLNhv1+p0jab+KwirkY`#7d>0pp5?0! za=mkS!+$%#1tI!ggB7v2q@JcYvkk8F%qCRpfL*cOBXi0TX1f``CgN(Yv=Qo{dFB~H5-^a8{%K+Pnp)T)n2T$ z7{j=4H7`!b15Hze(JQ7lMy0w{Gqu%hYfe#{gm#WMO0oUQG;6mFB!Rl%+JbMHQYMPs zH)_`3UrI%>y6vLTtex)BI^P>s=ase6E#nRekVkh7}E|M6YSw zwVyp@H7WTJ)N~bsnwG?ovJT#yh=#!|9qzqoLOe!d=#bcJ<(c;zC)@!k1Qt<(Vi$rw zk2xDvA((l3tx>EzJv{R8=_C1c4BiL#@M zJ7OoSzJh`d&AO%r{sxq+w3M8c6yUw5>pS|W=NM=2idO#=eNNXp$X1PZp;C<(!PQD$ z7W-Pl(-WM(pZL=cANl0cXynTDiFaEM8#GX1Oa?=V+#6pjbOR19G&(-i}M2HQU zU9DEu&X?8MIoqD??c5>T5Kh;a%f(WE6t@{OwbnqcjJ9hV@M>&Vj5_OkztTZggJ-MS zp=B{A%rbez5ZEz5C#dR zeyOdy)-;+`Vy;!f)kvYP2(1Q6U2xS~I9FJ*kXvAGRqH>iXK@`hfRjRuJ+d2-S~tU$ z(zq@(Lr0;OL{5uo0PBrP@33kRhiK-g^%>9_Y9WTWa`AEE2%Xi_bRAu%v;n*t!@To& z7l^(i_-OjO&ErDYG;7s!SBjodm$))pw|dcruE6T+rY3e*3D!>FU9f;s-8AiX6@XX` z7Nr!`g4bu%jA>#>ew{5?qCXU)nLE=<95nqbSI*r^veAmAykX{XZCk%Lx{VX6w5^JF z4WoGP@vhNHV+c{dg4I1XGpMI!p)QHjr_a1RJ}}pr@qW+S*KhgB*I)DM?v{Pj-<89t z9!_kpvU#c*ZarZmvz3jaAowa8kaLbORPM(;ZaOlQ8U3I;mIM#egqst~bVh=jH}%!M_N{=lV7eog7h$h^*`Wbb1AL;E~zY=&`N_e zJzW+n5p`s)f{Ak`)*9Ziu9(F}2zn6@zHNw=irW@y33|yM`$7AnO}rXnN8d-oa=>l~ z^qn3Ef@p7S!7-=Gv=q*@@p4Ygx#2^ki-w^T2c?plc2-31Xf1Ji(KJO6&kzD3czpF# zA6Ut0-a`l|LZ=#{XT@R}GIR@vS#Qp2z;I5!80yU~(?UlUO=Ir5o~1b+mW7X{QuZUk zC4?S;Wu9nt=FPjew7SF1F!t(B_RerDt*MEi=>a%kePlS|(6bAH+=`)VjVfRWf9*)} z?D{~6o@kc<5e#$V3=>`n9v)@n#z#8eAY%Cj;Q#<207*naR1Q=bcqJowsZbsBQ{^|m z{zv}d|M=he@4xv4;j6QDzNJ7HcJ!`Ot=)R31xM?m4oRX8tM9s=vFgk1Lsa*Y38!)H z6zOl~mf#z%3m`gVl&)jxG7=*$_OK*E(F;SVIy}y;L7Auo>p4+`%^=#%7+p22xjHdD z*<4xbd|ZXq75C$5h!;iP)T-i+10N&N4(^(gwr)#v@3t$-#yXU6Heme%!)uii+HZE9 zN4j*Kwq}=Gger>S4&Ea^BF@o=j^SotE(@_a?eKGs{eGtd#8Q}+MK|x@8RN)MdOC!$ z>vU6ajeh7fDdLR&(W;6mwKBIeDOXZ%3Ove{Ke*TPe z9lI{_?e89V{rWArD&o&mpca~VWZZLe7`ff|-0mD*w+`zw=XvJ%a#l=c(ZsissR~{k z<8I_|Gct}ldf%}dx~PX-swDxEy(zXC9_A57UXKv~X;N<~>!6y5P@*D1^>aOCWT? zF6LtBNo-iH0%5DGQ{4__YXyOc5xURCty2;QUEk>ic&#QV6_VoIrt?Hf!kaf=@rz&l zD{f!CR=3K$s3X&x$a;m;btc@@H6iFxXtY6 zH@v#N<@Kvq+#POt`}!^Weq`(evY_F>@G3rndiwP+vrRcDhE#m6t^aILK}<-&v`#GY7ff2`1s6Z3T9d^(cm z+1~L>D!WuQV|gES2-qUE9Z5?=x=x3KRn326th~S3;fGfo{7Ak(aC-WYl(Pz!(X&WH z=7nlKi?u>LNy|i?g_b7`{{JB~ z&oBS=U-IAm;+HI?@Q@a!k57sw6mM9owq01Atx3))a`4N6J)01_2`L*I+FZX*FG|fi zKDB@Aa`9gg(ioDkwt6L&$Ob59BQLp2ulRZ?xE3caP;O0cu1GU3siEtASZk>LCGXqh z3uNiFxj<|sG_9z zc478(al|cWYu#178ynVxc2M?X_@w?wLOQJWPK@o6KlK`4* zZiw}PRx*=h67czSMjp-t{|cXXghM2Ro=!4dHxc(U;r2ugl~dE9eTgcpdF!rR5%sOv zfKV<8bYT^_8i=~qrE|Q#+wtbry*~f75%NTkqL)?)oAJPXfh2cV|GgyD+*N1qb=qrv zHe0(&7JkuRZ-PXT9(NU8t=U81B!@DlS9&M%nuRFhYj6H6b|15Ie1V8g?AU9lK%FbyMK&t9$D49WQ3G zKA+BZHw*NAr(NGp6Y5K?e0X@|#~*&=@$s3DA3t(Fo_Ssp$7QyD)K2k`&a<>c6K^=t zdUc*x!!zhGQ!{(E_E_tZubO_Q!t=wU-nrI6d9AvKcs275-s3}Lzuysp8lIPwY5p@) z5mHf?mso|KG_#l#kS3HMmO5^h&Rfx~my!~tiWEbB)~$9<=|?K422lgMbA=imM@dXM z^Ef3!nc20>Kuo+iRj*DIRt{y~Ibe{P1@qu|> zSk8+gadYJ~pIKOtr65Iv5WeZ6n{6T=M3Z*St!$9I8EIP;Grf6BU@iD6bX~xAXa%0_ zZ)MwCOuR?w3~S&*2NCWCFmrQnzuI+d=4%~E}U4W;@?>Rmv z@_8aiCB1k~&f~1(b1BslO?;7@r54rMEKL@>o_-uDuJKYlbJ*!EOoZOh%DD)OQ!+w~ zJv-OYHBX6wT;U;|aDC+F^&NNjcii9H5gdH~`A5?8BZn@~%ME?slWOMk@qyEH#)~k< z9aW+w31?yuh8l={$9Oo<4PJYSP*#xft}cgWGRORQ;DLwOPz8T5Y|)LgdIwin6`4)Js6hC?h3n@-sF+DBSxz}%-M8j z0k63FQZzkVvg7o!Fr6z)O{AC49Qwo?f6v}e>_j+ap$1QK5fK>sjwpdHIzn(8ioKM8 zZ;8mHM6>3ouCkEHRI5(O_>_rSaB6$5bw^YU_0?_IwVqH5ODoU{B_~Qr8fMIeWvLve z6F+`_;D=9-yt;L~oL)GckBsazNRbwNboeVW?pB#X+lW=IDK%4Us8pM+G5(5-whsLq zSE*e)vzjSoHCv>43^kIi zcwsv~yOm6NnWfOQesjG$R9h>mA+*g|e`VZUE3}NLwpJ!@LpR%EwsYcT*xS}&s1Z*c zg7|0^5@zxiQM7o4!Bv7}tJ7P}f@U!He)~CETPp+Dbitf6QVX3ZHK1+knx*rv?+op7 zp>g5}t|5{&Smeje&9CdOo*zV4Z0mb38#%35t+O$d@TGyuFxVVdsRG1; zf8t?`13RbWrt_@xK#ZJFYt}ncv)e)|w463-hl?6yGQSi#+j-vZc_kMM5s1~0s5z@S z)oDZ3$``TO)N0sV2knaKS3?+PjCD?pzrkOT1Vjj4w)aH?U~5*%Oe1y{Yqd~n#x;-6 zozk$|N&wLJV12r3tDBj$ZKfOjOKqOo^q#g}HQVLpqIzI;TXjUP+K;~e{|gQ8EK(w? z#vU%lzO|~{p6gB9!dZ=j-Zx84Gz!kG)eCAy&`hn@yPe2p0`vCuOUoR~LXT)hx-}9S zzRu^F`RRq{PmesFPJDkpQujT-_^Y4u`sSY7+mXIgGhE79f8WK{qS{Ea&K|1OV>f&M zoH)~_)uf2`L?;9bQx7<#A9(FYrSh~!FOE4k9-p51?!yn9mI*&Z-o3fyuj6}OfAtMR z-}Cg8A!Vxrk*)GsD@ z?9}OkS3&4E|MFXY{hQw~Pai?Fw@F+BnJxqPb)(5mdz7mRhKdvuJJkC1HL?SkUi5X) z5-5457De@Y@fQTK9Tu^nu=Z9$AEKsXDEd9^U?yIZmL5lxKoCvB4nxn-_r$21?AQx^ zjC3)oxxqQb|J6*5+Uc%tV|}1&Zq%v|LaT+=I$hXHRjgo|@Dx0X8C-IuRP+IFnlusb z2vHrN2eG~>=hP9}AOvSWufCu$T2E$e+orP)0{Y+yctumz1?MU;?ilZOeDkxP;DR?X zP?6o|hadRa>swCUMC{*FPZM46HmEC`>k}H}j5Md6z&UR{&^}aLDTZy-i+>E-y-`b| zi)uXZb)fI}7qpynIKL*uD$;r0Pa{6S>=Hva!ZY|-dHLbj9DW{oe0bv9|M+*@))^7q z^lrvGa#69Tn8>|ir+m{FwADt` zb1Zmu-j-~FaAC>n^7qYeC>1f6lh}fs^NgUMkx+2f9p*)}r?6GZ>f$pTmbXJwDV1Q| z#J&|pl-7z{7Fw-jw_=e@C~M7x6CD63l3l(OWlP->f37Navl#9q_=tGB*p$2p_%hV(@}w0V)Ss3! zPtPypmm~G*NGXXh4jk_9dGlt+ONHNkI`Qed@A>}wAL;sm+rH=N_J#9YSz1sBX3Myy zJ(S(yK;sobc8pz*56yZmlMdXbBWc#8Seh5@lvm3x-gO9t;48uD0Q-`L)Wf2p&1^^> zf~6v@S=|s}I(P~6_KY~!Xq^CI+vrP4w8IUp6^7UmHUyCnqGQ)(6H-;wD$RP#$y;}= z_g|ycl@rx4{g|-PEUDRQGo-$f2AqA33jnoJ{l31n!4$o(?eoLovhnzVC%0l zgO`a%F+?+2X*X3`5^c#y=vbal^i;mOzsHYz$W^;et@`D1ife1m#u9nZtaDTq5C=69 z;(<4BdUNSQ-w%2@@P2ds20P3ak99m1YHq3(wWg@53Pt_~B2yd|s%jv80)F)^2FpLKiFPZQ$;%(hre5X-{3<(rS*O zAKCBk$p`^(0lym!8|~N++IJ}8 znU;y;dE$AQIM;>6RpyeJ%1n_)N`+Dsp+C>Fx;t@J7Zd1X;Sh8k?T0(08=)qC`pcj3 z^Y`zN@t(u&9ZydmVS420`Aj-L(&EfHFVr+?@2(-=&T}G_iVqzwcybDq@GNvs!sN!h-x=S25LUYQ4RZYAa2LuWSZfK~k!T zD0o`Q+K1erB_#-W#b`?&cf$Te;(@d+5 zTXV}HABg725~Cz5om1mZ#ds%3_98~QS_`c-IGCo zBx!87Vdd(zu{zCI$!_MAX@Hez@#@#ECINHp4*Z7~rM-&An00;TABFg<`INPnSVcbb zdLiIijnT{%=Sy3I7@Bsf4TsdxOs6x)&(E}PzrhbZeYMx z5suodxU*|V*xwKik#wB7WW)2EIp<1ckh6iYq7i4YgG(Bvski-leFzlVB*Lwv9=4B>N3%&_#8(4M;J!zIEoP{c=~=Ibe<48`rcEVBQ?)ut~gieTEdv3 zlsvib$eXA=z#C~o2RvKNp2aG%+IS&@6us-HTqWo9^|RNG_1uc4K@H6YED2i{sEzNx zdf@wS-s617WnTCn|M?d>9fTfFXAE9U7O67!18?s3{O;{ra;bd!^qG(ThPV4$e*f-! zJ|82WZY-bfdd{(@)E#*;^i7g3CmN5gFa|NmQ6_xJ^sc9|qjX?`BleB6ZA`{9xE;;h zfblfnlbociZS0BFu&{8+Vp1*(ij)*H!BCy- zE3OLtC|60286`(RLn=$J^Cz_KtLhMwCZa;KE1g6JytF11FMX%22{;odD(DMUppZ-^ zYuF^Y#?%=i9ur1R$on{F>CtX!5h21kYRNpOg~w%~0>e1q#(~f|m=;Vw(2XO;WK4-L zUzp;BmntUdUddxiDJ*0PGV7?-V3csSbtTs(0Te9zAn#Z)5IPAzbSC1NX~rscxWl@@ z5JpDVvm9rh&ognpK+U{+^N!zt{~hlh?#Vgw@BjAi{Q2Mhe_|18=gs~A-g8a~e8Ucb zE_j0PpmoHOIi4eSnHj8S>^;4$^tMvHRD_nIgZ(BXwYBfHQH7FY3a!YUHd_&+h4b)g zPO|NJGQ{4D47&4dXR5aO$rT=Awj`z1bCQT14O{KjSuK^lDjb{Znkd5XY68^`Xhlw& zCg}5^Nl(_9;<`^*Sz}wtm{xE;$gkHGrH}SF%b&BfO2E+>M=qKqS4zt&bkoEfo;04i zVt>)Kn}>H3b)2f4o$n5Tv5G6~^y=oz|BsHJFM_dnbNXt2b@JV$EIlw9@BZ;8c`4Afw7 zUB}S(65O$t5IV+Tpm!bAN=gftNvJudRrpk@mhm>M}!cA`QD*Wd>CZwsay)u_z^{301d=34Wz6v?{6h4gRF( zD(RJ$pq15gjZr~M=6QvjG9@>1h8Q8G#_1e+dO0%1iLZxyZfy&Em>C~@l!H(Ke}lw#0~p&Cdv>7Fe5kFgeV z+5Z2G27j6wimWj>t@n(p*Ar3Yl*l0_%5)-i;h-Ed1TNvaO4^cTsw zLw~;33syB?4oWc@2-IcS&yzY z-83-X5%t~Phzv-T6dPkgp7B~0VPC}vYS-bj$yx)dgc{?v?`x&2wJq#qw_w1!Y1{1` zYuo2vmAGq-k)VJYmFr4tYga1WN{*4shj~S3;A-xBbx&W78&sntzd~t+lopn>C>^gc z&olF7lAvFM&UyOKi4kul>@+yXM42Z@RX^`Wj)@W@%gdQhA3qXlEDmnEp4;s3r^w6Y z7mm3=TsD-y1UZ#plI1*rUH5D?Ls&KERXMxhijXpOer>I2m%j6azQlrTmv~3sD{=u zsZLVsZY#^w^Wn$H-{K=rMLwt9;PC(Yhi|dQlF^f8K~x!LaKuzfqUlyb4o<*HVV$N zAA0ut9jPl+C)mp=H$sf;yTD-_*>ye5+v{M;NCKr|DPE95ota6sl5Ee;-_q(c_d96k z$itg^Xm`>JVKZ>%^z;L7tiurW$fzF)q`Yh;3)A3*5iC;mz+JV3)b=c7&Ti zJVt7Krf*>VKxYE9OvQ_vs>-FMV)QaE0*2K;gELl#)^d1Xop>%Nen}^drdDxQwnj;j z5~Cb4ja>L7IW5m=ExM>XNl2p|$;`iQcgPD5TQN{k_P5gifv2A zS6&_3)(f6q9uc=- zrv+0B6>&NF)>xJcb4lC`J53i_N-d-`v((Cfq#!V&Z^jr2#?jkO7hn;yy%$$yZ!>-J z_!4kthiMHz4h;K|zH|7?nXliB+_?vS^KQ>>II#1UZ{ObX@aCRa9oax|mBERCZhfHZ zJqIt2NFN;83(_#S9f#epVR_avEg6&-hGEdzqB!MaDO=^m`aMc=s6%Rq%(i(8PP2&F z34;$EhDBV$ia%Yo;k7fYB`ul|Rivb8Qen|L9SWH3ziZZrARGcNco89;6UUiZ z)HRvOIdhuNT+WmBlxbH^k&8+Qy(GT97>0&nU>tWyy`+`j-(_maV$e~;Sk|B2ThIM4 za_D;A-`-PFCB`gb_>xHT!oJ>eiVL5fo_Ksd@%Vfs@yz9TA*Dq{jYh6TOj)*S3QgU> zO`Mu_b?Uc@EkzEZz}OtVHFAo~Nz6jiyb#ku%aNrE9CjW5`mpC;-rUh)ST3^&>`nTC zny2<;7Z3 z&df_EeX@F>bvg+0*3$cq-~+K)Vv88E4w($KIbL2Sel!av?f}(5D!)k6GgE8CV!*+? zK&zfmEa7-2T1$u>ALogb7v||ginAsRgSz!S&!-t{3^(_0Dg6QF1(!Y5c1#AQc;Ql= z?A@h7E~I#&RQXP3WFKxaRVUZN$x0nXbdFdvkxXpxFlWY>2_HQF@s|%|Q?;i(v+sMp zd3fND-~E=|J@mcBA$1Pk$+O=|W||jhmdD2nmw91|iBFFwUM>q$F1T)F%#D3(I4=%j zN$~2kwRTY=Mg#RkQJr?C)yk<agoXeBAOJ~3K~#-A+}&^(J(pyt=ZS0!&O`N$Vk6B! z=M9*`ujfaO>BN#w+}!lM`RakM-rezWe&T~!IF*@xE(F`roX`hyKVYrnCiGY=OR9W0 zpZW3e7rrX*`R?Js_ix^E`r!jhj8p_$?9rii)4}3)qJ53BH&-L)b_-e)=JncTZ>}k~ zwM%pztXjotn{|+=0(boaNq6n>M$v$6h1soU_aP{AnRuBfIdYj!qr!E_hC?l|Z+#6ipwbk{J5PZr`|TMgHD3Yoa7m z(n70IP_rw3wPrn|t|=h75~bS2%wR1!UnxhWs8P}wYOx&Wf;%ttoz!Mo?Haa9+j>oJ zSdEW%Eeq!#lIXl180v^`?IPdKoUc;#?TX@DpM4S73e?wxiM9#CTYq4CT_d4JMbwnJ zibC35dHvsYNV!&0yo%AQq1b$ZUw>U2aedd?7ka+=qHFzX>{$f_QD3Z9-H0`>O%uJh zeE$5%$6tTt<>@PSV-PX93-sR0JtPTD${Ka^E9TQ#L({&;x9{F?b9YO>+tH0XK7D%R z@#RP^A}E$DBHr4kx#5NFHDU0&16X=O%{DY@MTaYzDoLdYdNOo^@pl#cVn9aydP-+x3j&9%DOvH&St=1g-VdBE|(VMmRAS3_HGh zGx7HJBcFFC(is&JwOYhBN|}fy6BA5JAuix*$8CR$x0PQWUO2_bR1CEqNasLbJomdm zc8O~nmm|ME8TQU_JU){?pD4aCw@9BNd+SLw&eKF* zGBsr!5V}C2vdkA5$TkfNHzDDqCI)|J{YJ8=RB~E7oUNpiw^|r0iFqfT@Yd<2wn~?) zHFZnr8!fFgn~O%AHKhviNMr$>F<4FEg&>CNVs%I>0fJWRHi5SRyz|roU6h)%gw^|- zpvFzj)OJOlu9)Gid%Io@M#UO2Um1@NJ>57k9uC~x-mx1;A-^cbb?dLybaU1;vC)Bi zF@}u8g@GXq*lMK~BLwhre;ACzmclt1E^S~K2j1M@vpd|7mdxM(`UCOh!mW{R_Wk{y zoAFz6Ug*7Jup-bJC4W>h&I#6EjE4b(C*{mCFU-@KI8W3Z>Gd6SgAge@=W)Kr*xf6l ziQFB%>F~yL#B#ixX{Pb!_J(io9{BFfTYmqW@0dP);%`6wz=!9L>~0T2B^!673NPmc z?;YR1f8d*kdqz9p!wpa8%zymwf#=VkN$EtnINGPkbQ(xm`g?0ds`>iNexCk8Pqowl_WMXv$d81!#;$Kgm#LN8kgRU@c=Sbc)cP;*fmT>`Bz|_3vh-ubQ%>WcN^EQN$Ewc}68s0X%le_!=cEqP0 zX6cA8h9~d&1xWKm-!h}O?Dr$JIBL#x-cd`!lv!%FOx1b^ssC(vnhipSt3oFYp{MJD zitb{fNKr8Tq3dW~LO88fn(-K?Da`fHZ!E_5I5QAJhckh=WO7b?diu!AaVF-(-NOz4 z^v{3fpa1oL$P^Gs#v=kvtJux zH!{_hCFND`cHIoDZT-EkD&g9zyP`2tgJ$ftkKWj;`KX#k%^B}U_BS0*=Y?`!u+B?p z@*fBv>kNBEOjjjrG$n~zedlQfThb=#ZCX>i-f96_f3|JcGA*+S!%a!HMvVtjui-6JYsuEXqC^{gj*OCot~5AaegCFi z?;Aq-yv`QZ6nAMEQ^dfrhR{sbJpp?S1K72)OLVK1mTUBVwnoSj&RZq5%DZzzfL3aktya}G=v2chtz2DcYhXdEa|{)0@^r-y1>eSLFP#n!2hQ$=Zg)zb5P@=cadOd!?jorW#~#Uc(!& zEjpF89xNy!Gchj`0>#4R963IpsafXbrd6q@vF?jIZ;p}Y(+kJ*i4qrFOt`2)NDtLp z77DxD17UvR)4%;n-*wo|GtZ0AX1Nk5ychbe_ev=lh2uH#A8q0>f5O^lQd(<5L_1pVa;xLMnKfkMq%zk# zLT7MQ>Qy{iub{LZi)h}IL`f4hEn-$$W=hJm8u4}7YNX1`ksF^`f}t~&>G_clrz5+r zCoPFm#oSg?!s^;S7cu)bNORIv_X(#5pCawnP9 zH6sg7u7#kejcL)XPg8`W86?S~7sB)PTah)f4k7<<@ z6W%Tu>&Ue+#mrK(m?pX&$H*=Wf<~#0+%hR=GT>WfKMpt-z)8>P+S8;8_=eN)cEEQ% z-Z?>&)sa>lyWzlZzWxfQ7v@$QON;#Zzx|n36E}yErFaoAYa=+%&<|8oImbmh(~ZS- zvdFg>WieQLg^a};C+MS`F-9-M7J_rSfI3ZNJ6U*5r5d{}XeCR3P0B^kr_EvQfVF|z zdBIZpo^QVYBR@ZW;N{cIkSfc`V4K64LJK=w7_^&7<2`!7sOzO=O{u)PP&G}+_eLD! zk^>8ybB$7Htx#$bHxH`|&uZ7Tq<*a_TX)pzo?fvw&RG$wlwyGo8Y_{1rd%+=6IM5~ zF|-iK)@oW=4jS6rP0mTuDEb&U>u|ag)OBEtIPFmJ)F`UH>4DR#SO$&|JlT|MUiww% zna~`>W$Vw0Kbu-hqdIZw>w)iX3K&8aXcKndcdvUNYz7B()H!l4Bx-z+tz$ zqVsnn)(5-}?&7kLYo%Xgjjbv!VXo+JW2|%pS;6TP+vqwOT&9w!rEq5gmmK-&X3vki zPo$Q)Oef0oLRv0juTeD4hDp-PLem}{YueUByCwzKLvJZXus&O23QB3rF)_`N+yw$) zoWpk=cIXA`iwIond(*1CbX6t0ElIeE9r}JDy??a`X@FFiFEepjD7oOBV>ldewqa6c zF$TOC4^}sAtArjpB|mk@}l5VPTBiX5lJ(_HwpG>+2( z&0#Zw!PUvdshlA-YO*Y)aG7V0FGu1$6M$a3_nh^Bqu7qATBb{;Hjj5;O(NIKFgO}L z?NTuIKuL+X1X{_8_Oe7{C^&I3TR9u#T1d_j>A2*9v?my;r`wCv?f%HRR|FCo}_H%m}%8uY{$6k>GqCc|CWa(fpv8K$bDD2zrCSiUYVyv zfURn5DJ4nmO^^=XQU$@kUK9u0k$o>1%Tma@J!LK&(@bZ@`RQASZylY@Bp8uRDemy?&Uw`^LAD=$454Y58iFu*KOpaOb{c4B{ z-jMQzlq-ef^>St5=CG&b!eJO$rbsG{oMgyes3Ig(54qqQ?0iQE1KAo*vGU>P&;0uK zSHAu2_x$PIx13Yr!ry7fM9PU+BdzmfV=%c$|CX^lmB{(!iJzZ-B^^K0yPmuKj{RXJ)?%EN$rrEaSD3uVMwO-59u%IMm+DVFQ)UVGoA|5+T{&G_vsfeK-3MY*xADt)VB*`M}*)6r`Wvr7s#+E5rjc3w* zo>Sy9O}t!AT#hfq`OMe{-rV1V?ddy1Zjy#EMpy{7N!mQOMkyWhd=WuF$wYBkxGWcr zmuF6wM@(K=jt?CE_(w+P$e(}UG|%{lH?#&z^pqq*x^)g?wZo)H$DTFdw0=VVzL2L2 zEk0A`Pef;M1UW^mwN1`yFuUr$FJ0TV$_LnYfvyjX9xEHil1VV#H}e(e;;T}_!98GkLH zu2}L_`fh8#rBZTUr;g3~kelm(a(ivsit*FB1uIzLbw17zwK==)uY!mzI3agwSKl#3 zp64^4PcM8tK5=++VDvlAapL8C&(F-$M2dxZiCoSTAAc2UNwtlfC4EsgjOeTNOM7gaZ6ZRLCa7-XuqudJguv#J zKan;+L04QYr-3<7}X5iopP(Dyq~rYLkSzwL!=dRg#HK zQ(@L#d)?aB1#32V+j<^$fL9}MDTQ;41n;?+BiuG%YOgTNn^aAsXh(VeH$)$3>ocQ6VzYyoha`{Z(66yIfpZ@wc zitn+h;F}0>Vc28Lh))hT4wRN?X@Q}qg+k&Y-I?A)A1F1lq|7qU1n-$kqQ=DY@yO}) zNFkAJW!ECfG-~i#M`Llp$vj}UBb!PJBA9z4iDhR!nM$fn-yf}W(t&<12@vCCRw3M4 z>74h@v)_&MLlE&S%N|v#YWKPuTNOIj_t-h7Kf95)nnbTcXU2O&=L2b}JUe6m^maXO68iEt^hB0Dft-9VhWC+(sC<_t(p1=IQZ~xZ9J` z%ru<|&d__O0fEY#GcGkkSnpXGQ!5=p(pik*6lbOui7nA*PdQ#lPe=McSlsP_-J3hY zbjEZ&VRxX>b2*-wPnp5ME)4ws>)-J0-FxotzTxN7Lc#Ihj*q;2{K%;mni|+@DdbXV z#$kN`>jg^eJ>GX3YJ%O^ac?cA?{UqGFw?9U-J08k{i?NPrCJdd*My?0Ta}WvPSzuSXw?#9VqPM(fp^da z5kzt-V(@5U)EajKIYo}Aqt-&Vn!WEB z2Ejg;HKt$<_>OM3$997vmn|)*StnP9y$HYt*pDM!2r3#`t-Vt1L@OVi>*)3laVzT? zqDifxNn);c-q8h1Fb&r-wg?>wASX*KMMDb(ZyLomYLTv9(;C)FXw6iqv@k<2X6+F0 ztQLPQ!7D4Isipj56%%>z#5^8E^*;}Idi;RxGXc-G!8ch-o1av{lgpjVW1hote=;K z>1E=4KCvt_wPl9Ev)he$x1%ISEEVI6%w?1gd^p^2b2w;%Ue=a*S%iq&8okb-eegKz zh1OUirNk=^-Fm4;q$l`+&Ku6tg<8cdQd;BY=7z6-^Bp(0x0EdBKOdZG7j~-wvtJ12 z?H?#_V(4uW&fW~KqI;{ ztyz2N`L+&&$2iMjyx~u~d#wF|Uq3%?XLS)+*Lu0D*l(-|nP!dNuW9&|9%kxR$!VKb z$!(Q%ug~9YyR)(a=#4O!*UbL*puQ#uV02#=p}rUq+E)>_v!0sEW}d3Gf|E~xCLzMI z)qgo9p1h*;y{_d_bB$06lq9kmHY!_!`)Up@n6_Z5)BA<}q5^@Y`iYwR*HDF2=eQLz zzx4z(uJZ+%DX7tzim65`^tM-2O!Y3xXS8G?!e*I*)D)?4p`?XcGDS_r)FL%6CChWA zA~RIc6s*yD+V(|Nf^%-8qb(_ku&$!;IE;i&jYuwFt(g8q3$`pN%U*2Vx)Mo6MJwQC zK46{f&~uR0++{f}tJ)H5iHwLV_+y&f*NT!BK~9 zdshugn;xb1H-wJbDqR&Lfw7h_^n@S*OYaw8ZNuiHYq8J*)3ne&eqzoe zoeMm?x#!z|`kuZUi7|6I&lu#6ajoVko7r_u_pf&~=M3IEH7QE%g46lbR*+&K<;3Ou zLW~k{x?IjYJv|fCnaUAcJ8E*!Jl$@G+doiS=CUL{KRuFD;p5X2rPu$3=*DPs{qFxMom$=0aetDCe? z0q;HEefvG#?vAN8E-~}-r$?S%j{NwypLltGrp+@oTJ6_0)SS10rlFB+7N?4D6stHe zDoOl@;&7+YvK*3qTZb<0%Y?_;jYNag^pbfMjaGMdQOBlthLXiywk^oU61;RH6?0wO zORbb#weM99h{lKjkXI3^+08)EbO={kBNa(xmRbqMVp(7NyB& zXhbAUxl*ijtJGSzMSpV|nmyFWY@#H3Qzb4J{_ns1!0>PbHt;Y1{I7gC{>Y#I`~SoF z?;r3N!;X%fH{9HP%kK7p-F}DlP93AtN!rXShn%_rOUZ&(!QhQ0Rt1MzDKglGo37$J z%PvS#+PiCy(YoP?`9Dej__~F+YnO_3o>U4xu%$l)J7QE2k60%tSy~fU`-X>C{8+gj zCQ7SxO@#QA3e_og--_GJ7$beARt^QZWW0BH>t1y(8bxC1Kiwb#R4$U3T054MKv1h( zmz*BbYy}dOMf#crL!v`M=NMeTcOAVKlwF)>j0s|7QQ_Y^D@aWT!5BJk@K%J^^;r+z zP`%U{jEorT>6VJC0qb{A44H~;6PNRuN?|eLI3I#%K297Ck*PUS1KSF&CGCf|3?Xnk zcnFqZ?7%xxEll&m%jubymuJqW6M2zZ2yYxiAGo&?!k+B3$2j7>bW?S~Fis1HW#RtzhN17+HR;?IXQMdZY~Q$XkOl?32u=Dzs)$?pm?PQG$lY(b)lgml-+vhS3`)klH4|Zkba>EHvtYF z_O8;Kim92LqVykIw+%x~Ggr4=6B8P%=w^*#28CphN@gHzj4x+A#v8257&n9W8g88#xkcytZ<%Za>{gyUnD1}lM*-@b1GD8DHxKeB*e&K`<|F3v2*D5 z;($&`QVMFcXvS%$q&Sn~a^Z41(gFA5j=OQ-&<75KXMgB<`%Z=dZ{9xe?*5+LFl?^% zTq-S3)EJ2ku$^>hnX0s%fl@O4a7*XCpb=Z;<@s3;(F=i|jukOwS)0c4dLXwV)-~HO z-pj?eH5sPXiiwvEVOg@I(~J&4O3C`{Xq`sqC@fI37z<2ejuWM1Zr{A)&D{;R@89zK z_is2@$Ncz-OLaUyKJx3QN9I(xzyFH6@g2C1DbF&XtChB<;_+J#Tk=9FCG5_3M%B9bL}shk@zo%#;6>@Bir! zq(AkX=gdp}OqnuMvkY{Es>382Ie1cM$;-@#AAjMe|M4T={P9nGeSgajw|C6PSr7bT zZY*_0p&3ni$o|&U-D*~}oL>5!UN%;mkA&-sCZ=BLmRBC;Mtw400>iIRnbxj@gLROw zrhXL_D^#=9b?y|Asy%#W19R&mV7>?tOX}Ky%_8?N&@VEBQzMV$Sw>3oR8)^NOz25sVHEigu3Mre{wlpRrMbiPOXvt_f+diuzZDA??AU~hV)l~ztEs9_Sb-hU+RVsZ4{Kx>&PK4Ck@BdF^oX40Igr7{j9_Rg`S$fZc9bZF8&C)N}Z zM`#|qCIg%dhaS+@0dy^a!bMlp@*jUsKOXBZWvH~cU_bHo%$ zS^C{qgHjiyuI6t4p6}m%%k$$4pYKn+R9H?I3SNwZI%sqSf)0zPm~p}5teBgceT5Dc zaEY$FWq5BHJ~9fm#Db~KFC zrxm%gmDX^kU~FYRzwr6|!sT@2m!E%SzI-63j~FxQyU$$6<1F|O&8x%Doqk!H;!uid6?OTo!A z*I}h<#1x$KJU$-z^MCy}9)JCnWsZ2S_h>5y{Uv2=i8827R@2gH!y-zF|d# znT?HqvFDj+rtBOG`xR%W?DQ`0s0=>8m<*gaHLkLR-WCi zIr?HKK9ijo#wZo*CF$5V!_bPHp>t$O2U#CW;e5Jqd^s|o7v^{-wF@~XN=mp|8Es&2 zf$A*JG4k>B!tr>**UD}1qX*+{rrM6hTE_sPldPU|#{Apo# zQ`xz~@7~?={M~y#{&nCpiN!jM-$|GNkLx=^H)5=q|9d~s^}S%1TNKm5;BbA13;ueh zH|re5ijb*JHETUKxNGln<=PXi`m^Y;FMVHlKZ zF{C1^&>^aUE>P4fo`XU1K;DqsHsU2QJdBG!FidnRjVd=71C5ttTbXy zSpeS{}R+CsS3DaODXhXrfB56LIi8kjL`#}-Rh7dYhgOoG9bM)i5nI#OB;ADQB zU1l6cZtw0eK&N!qyv#D|YKWm$&tMp-PU)(_;3UxIi#LMZ-=`=%w{m^sR8E$t0*DOn( z$^N87OUG`slu|YDp|xJC=eZPWjIzgr%oIUHzuKD%woVFSR492Nxqm*lZ`73jw0jw)f)U2vHK{b}<5V{KiZyaf<1n1bho>Q)*D)lm* z^TaB&nPL!Q%zC$O21>!M!82DYLJ{ zU=598JjHrBa#x}4dK)xVEvAcg?p@I;(AubKbaV!s(xU6?Z200cRGR6U60g@;MN{*B zPC|W2OQIB+W!92O%S_IZm?mn?q#Vg5DcxG$u~H3WzjozHP^A`XPBP~TPHU?iUDvbf z(wBK=SrU1f*pa!ryXXG?fpNDd7)MEsWy!=;~LT6};9{TeF_Oo#+g$t?l({0J!4mwZhM>Vz3x1oY5!4Tn*l}eOVc| zJ{|5=y1r?^BsRCv0c2Tj*PMpc(22d81nRmvuhhFHhULyW{II9@nK>qgU60+}k#lAl zXO?A>pz~_9(E8b$Er)(j?G6NYL*AX~vaAK!RFVgVz)}k@rQ`l)&wkwVaDPX?+jDUq zXXQO;N^R@Afn7h~oMoApjo|CMj$s_J-ff_})ud$Bx}4VNe86KVG1F=$gg{D>lX+p` zNb@5P_xF7F+wb`PPu~%&=XgBx@_58shYy_^FcZ!V+uEZ86s?tzbE~bWiFcJ7uB?)_ zk`ZYXKMmP|Yy`TWaAzW(Zgx9{E%OTpZH#d$H@e;4Rt#rQy_ zlIkq8_|g$-rA=qLn)&?U@6@^wOCx*BaJR>=y@Iw%5@5|CNuRb{Mrq{y{h#FhN(O9S$*CY{a47+|{==X$f$2V^?=UjN67RK%u{%(Ha(}z#uvc~B{S_PBW z%;r>*h2L+Bcc&tZca^a(oco>}XULNBGTOgvUP+`YVq9L9`fAH7TOwq`)|l(wmTS|F zs`VFjn9>ZTT8b;%i*B63HZOu`t~!hrWED<&+)R7r!mmv>E^EOC>B21;<2;=ep-UX- zULLe{u;%s9RSMPzsu8sM`e$zja;+>W;!VJKUD%z)8>@pdF(q{Lgf3{BBIAw68Ba-x zzy0+;_%HGQB#cf5H%sKNfB7r(`320tk|)O27+hh<278_b6`dA{adTv~Qi(AUiw=K< zUxZDC*~no&CqWLaAHNTtE_C?5lLJ5N*(PWQk)~b?*t&2wk?fv`(epVa{_{V74&F}ui+4lUuKm0)bbmG?b+=PMOe`op1SbT5bTXo!F7y=OaQg<+B7F!-rkk)$+>|l*)Cw`4IbV))z&OtAO~=FG7UMjpRQTb;fAGuC zKXLl>gkLguuH(&agG6T}2@5i^eUOfQSz@rHrARO!1y+^wa^AYal3 ztJ}4{x+K=^!nvMYoRP7dDEnF=P!e{DgcR8sxa$X&{egLZCY~pusF~M2@@Rrp*t=M2Op`%#O=mxTJR2<79l!(imskz}zhoj;J2ST%+ zZs;-H4HXd!sA9}86k1L4{k=Safz#=Q)C3QkakOHYsum&PTfPf?Y1ev@n-Us)gM!;<`=-W==5C2D^1B z#i^2GWS%GD+*qayzx?_ur{jrQEAQXDqvo#(eJ^+|V|D$PgT1jm(Dd*x&%GqTE%Oq2 zF*9?E9Oo0~^Mxge5wUp#K}isJHMGh4nydUCXDxlJ+#YUu|K@AnefK-Q`t5ggWu{X3 z??3&W&vW9}l<_9;hp+!gX9xOlpm@)M;A9<8W6|WIbSX6}gSORR=$s{VaC0}(1t*vE z+%SD6G=pm$CEpO^#B*V*|sp>Rbt*R6Wyl>t_7B9^PG+H4VfJxLn2F5|V& z*&4g`E}E^tUQvg&skzSSb5tocV>17ft~cwEWXrDfzI`S$^C2Q5GV@MVMOKj_39=h% zKu|+^)PtZ${aXS3A2dNC*c6MbV%<5%7~S25GwER;bC1iGZapO`hX@a|Gwik2w@}HW zb=k|hcF83buyz@?Z*5INRso+AcFv?F>N&d>EY7EJB9zWFuRH^1SF+qcwk%Xs&ZlR$I<<6C!G6Bu8j zwrlI-lO{r0W0h$-)gVlBYYUp|W>V`MBw_NY` zh!?EHO=)Ggp2l@~oC*m?w$vV6;7s1}F==YcDo+?e`ZV9qfnC6+1jyP8dzQ_A^ zkF4uEa;bt?hhFuZ!fN84HHcmN%~z6J<=@UPMP7pGR}mGxUzBngYP7+?Md-Kf;(a;0 zzRWnu8C7~o^q=eAUQpi33ljz}s)yEI!z|i-E&@L1hK8+5La+|PfC-&`*D=9dAqHyp z)T;Z!I8Vf!h^-6WT0<4z>VpzB7aXpc1PV3?gC9T~DOHyA%xRifN+DUpk_)8>*||2z z)M=S**7TAugA&d?qxBbby_nTNqUV!KvdKs2s%8KAnNu;CDtvu)LvT zrm_|t!~`ESO_AHsN*cDm8RvCYThG;SXS%_tFGg zfAwGU+duy^-rU}>-3;{JDkim7B#qSN*iur0^S+7oYi(23q+3pkQ0@aUw=jWNa+wwR ziJGB|k&9q2g5c=-o)itvIr@z!r$mgCz6ZrZ5fYVRVXBVj;y9QNzW5b4yEmlU-w-}O zargP2@pz)dM4lt_IFhWv_&wG3R0d8@cRbILaXj$(aNu})RuOHQaXILDxEjRFcvl(f zD+bw8w~F%hT}RmTn69JLSJa#p4c!m;3Z1jOxxMAz{oUX4^*7(}PyhJ${NYc(XDL*6l0 zqc#;-maIv+Rz+ehcyG_of{18J7t~nWOsBaDQ&A(ml)`C=91c2k>|9U3>8VDajZ|06 zZR>rlYsHPBSc`LpTvbF75nNT&XsMNm(jBd}%!}gvi!q#}kVq`UNEfz*;+e;p!{Nl; z>4*)1lJ)N6tx)qqt$J^q#c;}sUd_%b;*;Fa{dz~uIVt6%Dox6F9nKnZsEjgWIN(i1 zs-pj^^&p7|q(+hv-?Isc(rJ1$A{>_m#PE9minqI4Z2X?O%=k^A<^@P3lbFYe&v$p+ z-QTkvdX6>I?{64_<$AwmbG2g-%kB22z3YXNGyO2I+g;&9r%Cf+0|IFtNpYs8NbrW8 z6`T{Wg;gT4(w+~U$mza!?rbe*Az$Z6YMzRe*3DZfVp~O{iXuzOB=R{GYB4Mrt$wNn zQ!SW4v5ry`;so0lJ$E~;U9o*fv7SW?#R|6H5+t03sVF*=B0%Sd^8kI$Z2%wovq({m zWttP?^sH5o2&E))0b>KER+c66^mrhpL`Co+w5r5Lo|6k%&Ke;E*UVbB)lKMUVS-TN zOv-xRLm;O`#rCz%#|W-g97QWOMeFTWN@Q*OXelZr8rv|rYw|H?eAT)%a#69SCGJf7 ze4KZUB&3H9+s+udKusANGj>fPClx{*D)?I4sz0jUy;ov})~2@ed5aNO(O{3UzMX@N ziWs6H2{GCwZk@$iP5u@UX0Lc|XNr;&Sv$X*U}#%hrcv0%DxoD6oz2=}UU-r(1lbB0 zt0}~le50*Dx{lV?wQg%}z14b8S-Y+k<)6vux;URsEaRxN`m*3%VK;Q#+}zL)TZV2= zsy#U>bz(DgynTDctJ^)x0=@4E&SE69>jO?abKFwh8$G`oO9;MErL2Y=M2S~juV*i< zqSUIMJkL#g(D{#TGuG8KQF8{-YM?Y9sm9W81Giti=IfZ4QX$hbjfJc0fjQ>Z)h(^c zLT9V%+1Ch&^^G~EW)f2bS<(f!e%=NzCRfwa!7KD$>dT&T6|`1a+B7n3Eh~}8C|!X| zZ5qvxNEHg1EK8e9T@2;x`PVtCnn8tO8yNa6#R`W{pP5gO*UCv6}ZMPi_kgjS*#aA^U9*;*3&yO6B&rHXo8jV*BKY~8y`dKWwvZPF0 zlCJ6Y1lF_UiWLnIxy?1w8bmY%@7Qd0f4k}v&M67Qny#6_bp6zOt+c*zY4x*3 zL%5BKlSFkra;uuD6@H|gEJU((bJTWs9wvMlFwrqEMPMyw;ODRaM z`WyqD(`PJbnTzgQPD6b4#rsf;MMHlg*ydC@Zp36x+O^&HYlngb_?5MXMH}b5CnsBNT6NF0lF^@-#yyESf*BWXm4kM~bNzOP5QX|0&b2{LiCoh5H>A<^g%lAJ& zz&vra>FGkjofmxjqPtKoJ(#QGy|ja*F(|SXutIa^WQ=%-dYLy2zkU1n%<+kA zGGQ8dFoxcBNEN0yGp5M0_N=Zh(o&eS_D@gagjoumafGfT^aCkH6@(g=qBh*8@$I5& z*K9{V4}icBr-&6xwmoBksboqLKE02m=M8>)P0p3S{oQ}$Z~pdw;6MLgf8^&M{*jM! z>YhTsQ!-}9Iz#%aPBqmCq_0|M`x%ge3L-TICeQe8;Ph@_15g5=iI zlUG-=4$CD)O^ntGaUsq#CMUd8f!ha9O_e21%*#ZKb4!*rQ%DuO@6bZmc1dZS;HH9! zxn0P0$mZH1K~@yJ5yTDJon!Sdx1@|%OV{m@O{9-WGt5guC|q>|%aW;Bj?qvhV5tPa zdrdK9JT7b*k26cMOtYcPGaV7iiEg_^I?HjKxxfF&yU#yyS{Cx0_-1zvWg&GPKP`zr z{`@2V`o}+V_wFNMsr+hp&D*Ohz9_f6>MW1bky*UeS~Yz^{ZRTn=43}q2G{= zC@n#R!3L!0;N=)+rnum$US@-H1S8m?r#Fsqo{1&v&^~JSyxnZ|;B6iAJZm>xGX#|& zh_is24otP$KQC7QZf6LsD>6cNFjUvrTURY|OFO|L* zdQ%jwDC>dTwhK2=7EDLc-gy7n+z_$x=J$D_aoTw?0qant0 z-bc?ymvv2P7wU7zS99GL(UM|g8k)zpL5krrP(ny4Lz?xFyev$onHXCIi5OCaB@20p zoR-8X!-CUGv{BSuA~6GV2;|;kaa8LNZ*fldUS~WpSskp;kvPrl_B*bxdb)l~-|y*q za6z==&KZL9I*4xfrzJ*mt$e=!%;9k4ba=*ZwAVCrTfTnv1-G|vG5v1LBG<#7ytmkJ#j6TqDm%R;S|**NxZZusi2 z{+v^q`1JWROh>o!E=7^`8zkxj#w6^pN)oYZh&tkpAB z`y8a|*W(P7c_BVOQcg!U7KUv{FitPaR)6j}M@mePavK6_RSH%IvztD!-MwPFzh$?- zCUuq)I?^bN;{@A*SN&JKBIq+>pg280`dYC`D*|GHdCnZ}?^#YqLI~s%IgLk3O^oA# z$EQ22sceV9CU|;ha3Wf{(j4?yhYf1XGi$;fb2c@vRUxGm6^e~Mk2&j}n9_{PASPg4 zX_X8s7XMN!@iH<0%kKD#IC?hGov&u*+=W@nMYy+XjYV!kyRn)dt!CYl&o=~`%VI%Cbz`0yL8_M?-@d`H9r`xudev! zSKpAA1Je?5-jR!*rF1B7+5jP+6IURbhKVf=R|?OMk9_$2i9h}D1Mfb6;^FzoVVv6V z_c8>Mnvt5-WZ&M`Y#iAJsD&b;#)EP(v}2vtXf=UF31dp9NlWCz{R8Rwi8pV);LVqB zD9*ARN5(o*eC60V=34YlbevhF;JThXMQTzSL=`3Y*hasr<|T2+T$)+ij3TdSSJAVk zYeOse%Pb_id^TP-F5a_#r^G#fkff^mDokxw`ar5SueHCmN zn3lrZhey8s)AuanKk;<`1Jh|_nNBS8q-mQl;Dk__-ggwYBUTkcJMV~DI7X%Rbym;cOM+Q4-bL!S zKjEX}Uw-(^pB|qW>qO{1(>(KV|A5IeU7iR)=m&~1%yA^w0;VHpHS!s&B1+7OIYpMD zXPU0-xPASatKE(?Pe{sK^&7p@IfDm?iDex1nF}7gXQA>rfOUnMBUU2$^u+Y}BPAF% zlBjm(KE7Mo1$}{+CoO!2$GpkqjsX^bVj?j zCU`3V03ZNKL_t*Nb4^>(*<+Pv%rY--#M)t>SHgt|5twM{?NZ1hoNB?Yl$@H?B!JO* zW)quBt3o`_lAUdo2w@XE&UwUGt>h3DiAyZHUs*#jmx?G2gIQ|T8VKt|j{hTiMNLKp%eI5oAR6k3I3 zHG&FlrYOyrq^N)<=xFfEHS>YB8un2_&?@4zTFaJ5UK85V?i4G1Mw$quYl~pSx3ilH zbtxx(I*XD4tT>(jr;4$1nX`);Nmk=VEh`zxUN}Q7a8?nbcuh@LP3+c2r#0l(6|DrH z-oo4K0dHRO=2c)x5o;>l^*{=h;&s1~lCTu6hRUm}J=R$SPuJ~~BDiQpkVs;V70gz{ zNbTSC-eJmmPH6NNBj;P$D!gS9sbpPGZ96e$HL>TSIc(PgElSY}YfHBNNtHttMG(SZdY2=GM(E4f#$fNHLd?Z2bk@{@iC@+V>VTE{u79OqgeT4-=JxV5|QYML!2k`cPl(OE~$k!4<(j|XhcI3q;k z+Bx`qQo~fR#^8)m5}HwRhjOE;?t2YU@0zKJMvtlM8Lr(?*D5Stq}pH70M7qwCN?Xo zYe|DA)4nIus$*Bfx`>`ls#(p%N>4Smt;5Ft0$*Ov0@-TpUkw%UOlQ8Bi<@BFK9Q2u zs9f6Ia-A*G4Dzy8Z>FL;RxaeckaJXWhigAu(fdhmL};lMQv17`kgJJyr!<~gE=H7Q z7Lukf1nUFOv{G}IWOmTwf=&t*6yWkUt0~6F(!`Fk<;sMX8Yx07VjQL2A*|KfLf7^HX~ah{YS8Liz`+{DvrBsySCw)lU#<&9UTHJd zVok+z(opXTp2M8YRnNwY4#9t>Yw6 zripRM9Cs6koDee8>7Fk1jHi*%LO#0Rsz$cP!S&xtlo-#%2_u4U70RYnDpXS`R_n{9 zWUc-&T45bbLhMYG6Jip_^gu~pQJoSxLN}bB*Q5w*ty^z13A4U4Em*Kt;no+GRg`+E z`-_=UT2)GgP?~YCv`WoVsAM{1gxrz6%B zY@V5xh&a!fGKVR0iZiFUFdiy)UT`H>oLH)D2g$njvR~Rc*U9F2<6en_Rh^^W2a>p! z9v4!K#9YbQ;Bdr6I6giwpH6)I@R7UwJAV549S?^mre)#j@XV17Dc7}tO#AHHhf+nz zl=I=?Jp7|*=WhE!4j~YIpw2VGCD~!x;=E#z&l~odew5mf;f4BdW-`*m+H<#U6K<=y z3|VzC^W^HagG4lmUW|Y>29)lqyu$29;Ub;$TTmIM#{B zlp!Ye-N5Ui!|gV(6p~#KF?g#BHAWYl>TSbXmL?vz9+_GRdxIB`SdX*N)kHTNGCgGu zkR49o3i#W<{_puu+yBJGKQq>m*%o(7X2Z?ZD{iiYtDR@) z5)bpp>2#n&n1+lP6~3-E!Vn-Q!!k#5nqWC<&rB3W26`}lhe$_s7AHkzxvZ{zI?LVt zdk*&>C`+VHnX9hndfRdPf+_(&-WLm&9+^;^Dr`xRk-Lnzu& zC|=t8q>21m3)M>-?$&efueL4d1z4JaC(oK#UY5iX^-z*?CT78RUY(9si32ux#%baC zaAH25NYhB~gx%01V%WaArn5co?mqDF>6zo>38{tKs}AQfzy9qN%bfWB{U_dkc*mzt z?>S8q8#i#}_l)9+;<=wj-cOYuj+uuELK1@X_;AH`^9H|tjk$V*HB-rVk&eMTj~x zz@HQBO$3v*Y=ukTq_p>@2uVsa6SS|V=ndM|^+0?jrHG2-bo1zYNBgwz3B z6A)<*W0U2Ky4DMW?!W3J)q(E?!>apZ=oK|+tEOSEZ@y;t>KpBzDr#-cDKkzZr|E<} z9tdtiQqn%#T#;0fTyPuMc8)L%)Z&@uMf<4h!ZMGPSeXxzx(J;(Lht$N?UvvC`PY2$ zayt1z6#@9c=zd!e3*Fm;V0g``=G)vl`r3Z&A#Pi_< z)-o@V`_o7@mi^5&``b74`&SqvOs5l)tL_^*syV0&Y>i_| zl{qCcE%{{Cq~%1&iL(V?&0&_96|Yq!VW^~86OO@mn5u_!ZBa`^$?B+fUOS47L%eo7 zMiCMQq+^;AQ;s}M3r>VLS63{i@H{4}6&)Teg(>NN=t||rb=>~>TmIYs=5P4zU;dU4 z?>_STfB8Ls_|v!CKRyz=LGgw$oyD_!25PK>D09Z-uL*}=oco&7*2v|Zl(nl^Uf}Mj z=RUyocBj9m*+kbw|2iINsnFVxd(!@FX@ADl(nfdEl2?juYo68C*oKw6%IUxlVfm*_#Rt4pY2u7n(2`0;sGPh29jhDHSmRb90o2%cKg2|-;W(w&vQSa|5(}^4$ zsYK%GiFp~B=M(AhL@W_IbeLg4`i|l(**YEK_=>GUC>axVqbhA6Ev!QCWypTEO^aYc z$HsbU-Rb&awa2wKR3#1L$V;-B=n#zdKI(f}&Z+v#d$*?aOY8rWb5%uisGP0V{Gx)O zG$O_?Q~9N(_g^9kS6qMP-*kk&h|x%U&(ERy{|{`b4)k*~rO4&3cQy+cTthFeL&e}c z{V;$n?Dso{ZBIdsvYIO3Z1h^`c5tc6q75vMrLYvR zJ~R?r8>FG1EQ{nu& z8`v2~t%Z{mW^eWV&BEiuGpA`Lc&CHa_QP0B4Z>xR%rBBf^^C_?$GhHbORFn0tC_wu z(<7R6etdk-@Bi_hpZ^pvHL(kh!5em)p0D1%=K6~-2)t5Ed#T)gzQO8%cS&chsl0WsZ)Z9mJSG?(?(cZ_{vE+N-n@CumtVZWcn8^2 z#v{k42huX>dXu!iXjwAHaxW)p5ZaJ{|a%l$RO{)%EfMI1}kbdgcDfYQwLhK$8>cmFeg z|M&kp-~a2s^4(AGn8d3|t_?!fpsFaz(h_jS=^!??{@+|HrD5s~>1?bkE%74jydr1j zQrPvH6s}flpDvLLOKwA6eLPxMuXDtu|JzxLkD^Bwf z``$25%cWk!B#h}vIf1PW8C>aVI4>M$5vA>0G>f^l>%QLb$mjYyy*uX^d461AnK(Xv z=6lynGGd4osptCRB79u1XVIC$(%O3m_+N!q^L9@wa|Hob6P1? z8xAOo?wR>Tn5kBfTvcQit?0A%rAd;rz(Cc+JMHsx-eQX2vuTE?_Sv<5Vy)3z#rxVs zlO~FbqgEA>yw^E(DzyRslnhl6VlFS%GSvv|+DF}pZ^i1|SnoPYX{yGZt8J=iC6C|k zsWGy39d>)moD?J6`$}+IPNPOP`-d|DPvEr=AB1hsaWIJE`qbtWJ&QA%pGw1RC$ zhL=n6*Qx* zA}pjCh>_CH*5zV4uWCS0-N8{!XwO-rezYXKrnt3QrKEZm(hr9tpFTe^%?0lV?O86W zx2)%7)9$v?h=BD)&634jYSLWsj5#gqx_-a5txfXcZjRw%N@yg`EHqNB)6cfC z(~W5{O=B7Xwx~hCNUM5mq>x%#uwP!#yyOD@d_k`@EzxhJTz+rUip)w&SnmyO&X5su zGi(%NG%Qdvwkk=cw)*LuGbyE33#S#4#%gDMm3p3?mt0@W4mE^XD~MB+I}MX>oYQqZ zwIEFu#9JhEYH%qAUp2Ht*0YPRYjCnOQbkoVPrMio^lWa3ix6w3%n6eW-u86ej#4L< z7#WvH%}V~H>bn=bCC6Fm5k9Her`55PTyR)I7YIIR7$^j$ab$%!jPr@7$B6Ns-SJC? zeqq>Fg6ly%B3{LBhrVA%f3_h6hRud947kwgT2=NRMRrWk0VlR>N~D zoR-2ods2dNio_V9%ngg5bseE-%HMz`QyURMsZU_@jCYREIlQr~-ZW{hoLX0cSgVw1 zbCs0_lSJpDC9nEkBUOl2%2*dPcm~W%WKOZozjW$UZBVmLBZcR+;&n-?#Y$m3EsTrD zUVpQ4cEDid<88)PxNu2#<6u=ATet6UNb_Y7el1w(D#PF

ju-1@UcVZ>fR?

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�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
  • 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

    Q9Hsi>y>-hXnKI3+G%YXl` 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%0AoP8w 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%0DH=ZI}T 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

    u$JsejrQ`Vu@9&T_@OYg#kHR=XsexRR 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 Y7Txg)7514(x+Y<%~u%nEF-ybb$ zhS6;et0D+wPI%^43}O3zv@{~A3rgGP6TENUOnzc0{!I-T_SlXB)5U8LV0dx`G_WV1 zUF2=U8z*OQbVyGZYX_fv*u7u3nU=V`+Px(P}L zfPa!9-88J=JQOvb< zQz`-jc{!UYL~V}|;w@HTyEdBGNHBYk_<%+$iV}j;tC~3}1ck(8_d{SLr>%+FfTA`b zNX4#y-Uw|BBZr|qm$e}Yb!?XG14J|wrsM8HySD@TXUbT0#j;j6Uj<9`%`+s-sFYB3 zK`tf^C?d2135*1_nld63GGjSKBj6|p6T~?z}l;dPYZIA&=AD-9Z zRK5)}9(-+P1&+mxI#skAxvUgO+@M(dAey09l^*ETLGUCZ9BD3YYPVZAsG@eonKz-A z{5&(z)3~Rww!YJbFd2=HL|g1jhWgc}UG~2>LbJU_prsRKG$izA*FZzl4Yy81I;-u) z*h!2;vG>DjTkp7$*Nm!^2$%pf>b0N8Yy^=Ip($dXCzRcU1VO!=@bTe{shn}MPuT7D z;CZ%Z>X5V#n?uj{DZ?60mX38v(7H64-BIa6do2htL25xM1?!S=KAy3kU*qjv!f%-I zoJ&u}iwn(mhIL&?TOX)B=W7xFjWdLc+xD;bC#D^-58Rw2Gmr|K?gQc>UFO%9Y{4Oh zlT7>zleWM1^kl0p=q3(mCo)jqClAvN-&eP7gp3p`-*>k`aW9}_0ⅅ{V_e(wJ>do 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�wrVD1uQxGrpVry3YGb{pM#*b96_KI!av2AD6ZO0lHEXM~d zhZT>H-{A570Zd2FX#^sQ<&+-+DwE+#Ho+;Y=RzvBVA*_o-VIaf4Jo4`A**9%)pKUJ z=Vw9NVdb$I>gl;2JrJ84L0S&dz!#G*%So!EzXSl4Kx)5QD|XM>+Dk#sJFUg@?rOMJX*~-3S%Z|e52zIwDCMB|Vzpb&7`j4H7n56}P{mxS-W`GEU}I~>xA5Ez%s1;4&L}~uIo6lihzM`{6*(8A6cLsLVa5W&DIIXP9H5c$n$IYEH3wBNWj(JE%My*^ z5fjem7kv5rGd}MZyio+w$uNBjBZiDfN1U@^0qf@{ln5N}KH&Si2P7o?`f@=*#36mg z!oly%fU;*BIJ3F5LRe6_v3l-G+s2kkCZ}q99i3(XymLe2 z25rqe9E+HS-t7NkXblFbyO(4lz&m-!X+!P&Hvy!6mESjKG%CGc;7_P@az*ibe3{}F z{p!tabM_3;0gasBrkM2(lA4m7sEbiYGaY(n;1E#K6e%(q-R59po?S0I2~w#?2Mvk> z_t=2ucV?bK7N?J#*gn6glX_wbuO}C@kmw|YalCrJYk(-!MYRC`|Af8SlPt+~=J%cB z?h%<;S#{S28eB#sxM0l2G&7tL&GaBW>2H^fUPd-X%1jc5AP5p@G|SD{Uz{;*~IDgx}h87`F2VqbfYG)Nk!q@X@a0W|3 zCl6E_2@e#_Dq9h!4^dqzsXE%TereJLA?mg3fb*|`ObvVKauaPE$2z1+o~s7Lig6}@ zV(7yfx)nNAu^xz=u3@6wK(Rs(QPV%g6e5I7m$Q$5zpBYwa>s1j~)YW1PNkoN#} zP(BRHgobDjvd>CNHgVs8bX;*xcIRxK&!h3gYbd5z5fD3yCVcl zbA%VoZNUkpfo_)jkcShPec0_e@~VO?8JtWcsZPXef*Bw5lr1C0h_wVLMHE&%y*wjB zaXK&9E*tKrJ&G8WLv#Z{a6eC=ZNmjd3Cl5K3Nz+3;kvB2O2!>e&|Gkp6A%E+8Jhx0 z#TMzh;N|*^D=mnJgoif|_@Dpt-{Rl@#n15N?jB!VpYYu;{}G>m{cF&2!OWJ|mz7Y; zib8}E0_NQw$G2~BcmIIh{tnabfDjX=;5ns%Jj#mA0c&XsJ~)>aO#mT;n4p2cl#ty0 z8$8M)`0zwrH7CRk5ga)V!XLE7A46~GCqn&Nbs2Zh=WGIawq>XmVop!u`&;Pa0u137nT+l{4X=qb(!DBQU)Toor z83BU!`=*j)RGMg0RB*mLV<}hc-|TSrW{)tNfrCTzGYlxj$bL3_Wk^tj9KglSg#v;4 z0;7O!9-zf3aw}p09`5%@X~I{({ubYS{fGs7gfulLxSbuPVNCln!10}ghI_BV(>hG8 z?*C3GVXqm69A+_ew2AFZeBLQhGSpZ!OpLha!HW2@&T9+Z8lJX;dIwQMBiDpBxUAw* z`U|pGeffqs)t46HHSDTYU{xbq(8Llt7CdzsrRK1&Lei|IBbTXv=5@IyWE74X z0$A$d;rDW?WKQkFY!mCK$JVhgI<*P+tX#LA)S@X$o1uk@eWyyB^!ZxRhG)&5`^}WF z=4*3+s}PWl_E_(a;7ekz(ICoDHcY%o72H%296Sy}Y&McJAQ>v9DrKM`WKknhI&OW1w5xKBG)AlTPB)7Krg+pl18~b-pP-qNTK(=TUt78BK1-r}0 z=U2UfXMmg9N{?Hu zJvrG;6H#e9) z$GDS@S=U>6e$KK%jNAU^W znhtJo^L0N`W8VgGcY*B+yme~*oh_p~)vnep;f68MLx-y2Oop7h`acEc@U10TZF?+|Jg)#JbswX%+bEnO2ce|osRyl2Z;I| zJCOE=9<=Az+t(3?pbYCuT=+eLq6(q4 zbP^ha8(kC9XK(e655B`FMQ%@M>a(LDY*?_71b$yJKT IJwcVGLRmT|GyX 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܎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

    uW?%tmfx6)NJY$+Clz9g2 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�VondV_Z0oP<8^ShP03WE~&n-dian;iHM5-0jD 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 z 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

    !=i0w2GM*+Jl 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 b?ZE#JHedHD0vg=s00000NkvXXu0mjfyWDWu literal 0 HcmV?d00001 diff --git a/analysis/compression_analysis/PSNR-example-comp-10.jpg b/analysis/compression_analysis/PSNR-example-comp-10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a137ebb1100f6d9d774d9366b7ca7fb63febaf47 GIT binary patch literal 106150 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�ihU{<{^3 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 literal 0 HcmV?d00001 diff --git a/analysis/compression_analysis/example_wikipedia_image.jpg b/analysis/compression_analysis/example_wikipedia_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e983e8456516a4805ebc8049b94af544ec1fb0c GIT binary patch literal 486981 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 s9am|bRRv=;_t!^V@h8*7d?f;ZVh7rx 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%0A+#UL&&mho07?U*D6J;hQ1F=JqT`7O4W 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
    =>J#rO)?a%I-AN-KLx#MCO+N!O%%ntC%r8#+nb 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 literal 0 HcmV?d00001 diff --git a/analysis/compression_analysis/orignal_image.png b/analysis/compression_analysis/original_image.png similarity index 100% rename from analysis/compression_analysis/orignal_image.png rename to analysis/compression_analysis/original_image.png diff --git a/analysis/compression_analysis/psnr.py b/analysis/compression_analysis/psnr.py index 1585c8cfc962..abc98fbfeb2e 100644 --- a/analysis/compression_analysis/psnr.py +++ b/analysis/compression_analysis/psnr.py @@ -1,38 +1,39 @@ -import numpy as np +""" + 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/ +""" + import math + import cv2 +import numpy as np -def Representational(r,g,b): - return (0.299*r+0.287*g+0.114*b) +def psnr(original, contrast): + mse = np.mean((original - contrast) ** 2) + if mse == 0: + return 100 + PIXEL_MAX = 255.0 + PSNR = 20 * math.log10(PIXEL_MAX / math.sqrt(mse)) + return PSNR -def calculate(img): - b,g,r = cv2.split(img) - pixelAt = Representational(r,g,b) - return pixelAt def main(): - - #Loading images (orignal image and compressed image) - orignal_image = cv2.imread('orignal_image.png',1) - compressed_image = cv2.imread('compressed_image.png',1) - #Getting image height and width - height,width = orignal_image.shape[:2] + # Loading images (original image and compressed image) + original = cv2.imread('original_image.png') + contrast = cv2.imread('compressed_image.png', 1) - orignalPixelAt = calculate(orignal_image) - compressedPixelAt = calculate(compressed_image) + original2 = cv2.imread('PSNR-example-base.png') + contrast2 = cv2.imread('PSNR-example-comp-10.jpg', 1) - diff = orignalPixelAt - compressedPixelAt - error = np.sum(np.abs(diff) ** 2) - - error = error/(height*width) - - #MSR = error_sum/(height*width) - PSNR = -(10*math.log10(error/(255*255))) - - print("PSNR value is {}".format(PSNR)) + # Value expected: 29.73dB + print("-- First Test --") + print(f"PSNR value is {psnr(original, contrast)} dB") + + # # Value expected: 31.53dB (Wikipedia Example) + print("\n-- Second Test --") + print(f"PSNR value is {psnr(original2, contrast2)} dB") if __name__ == '__main__': - main() - + main() diff --git a/arithmetic_analysis/bisection.py b/arithmetic_analysis/bisection.py index a6af547db03b..c81fa84f81e1 100644 --- a/arithmetic_analysis/bisection.py +++ b/arithmetic_analysis/bisection.py @@ -15,7 +15,7 @@ def bisection(function, a, b): # finds where the function becomes 0 in [a,b] us return else: mid = (start + end) / 2 - while abs(start - mid) > 0.0000001: # 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: @@ -29,5 +29,5 @@ 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 - -print(bisection(f, 1, 1000)) +if __name__ == "__main__": + print(bisection(f, 1, 1000)) diff --git a/arithmetic_analysis/intersection.py b/arithmetic_analysis/intersection.py index 22c50f2ecafd..2f25f76ebd96 100644 --- a/arithmetic_analysis/intersection.py +++ b/arithmetic_analysis/intersection.py @@ -5,12 +5,13 @@ def intersection(function,x0,x1): #function is the f we want to find its root an 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)<0.00001 : + if abs(x_n2 - x_n1) < 10**-5: return 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 -print(intersection(f,3,3.5)) +if __name__ == "__main__": + print(intersection(f,3,3.5)) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index da05fb65d0ea..f291d2dfe003 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -1,13 +1,14 @@ +# lower–upper (LU) decomposition - https://en.wikipedia.org/wiki/LU_decomposition import numpy def LUDecompose (table): - #table that contains our data - #table has to be a square array so we need to check first + # 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: - return + return [] for i in range (columns): for j in range(i-1): sum=0 @@ -22,13 +23,10 @@ def LUDecompose (table): U[i][j]=table[i][j]-sum1 return L,U - - - - - - -matrix =numpy.array([[2,-2,1],[0,1,2],[5,3,1]]) -L,U = LUDecompose(matrix) -print(L) -print(U) +if __name__ == "__main__": + 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 c3d5efb47d01..2ed29502522e 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -1,15 +1,18 @@ +# 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)<0.00001: + 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 -print(newton(f,f1,3)) +if __name__ == "__main__": + print(newton(f,f1,3)) diff --git a/data_structures/graph/dijkstra.py b/data_structures/graph/dijkstra.py index 8917171417c4..a6c340e8a68d 100644 --- a/data_structures/graph/dijkstra.py +++ b/data_structures/graph/dijkstra.py @@ -21,7 +21,7 @@ def minDist(mdist, vset, 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; + mdist[src] = 0.0 for i in range(V-1): u = minDist(mdist, vset, V) From 8a667e8b22075652945f88d7e85b38d25e3a282b Mon Sep 17 00:00:00 2001 From: yzzhang-cs <44668197+yzzhang-cs@users.noreply.github.com> Date: Tue, 6 Nov 2018 08:09:07 -0800 Subject: [PATCH 0236/2908] Minor changes to README.md (#599) - Usually "Sorting Algorithm" is used instead of "Sort Algorithm" - Sorting algorithms are usually discussed under the field of computing science so there is no need to mention it. Some descriptions from Wikipedia are difficult for beginners to understand, so revisions are suggested. --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 45c2a3945cf2..b172fd04eaf4 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ ### All algorithms implemented in Python (for education) -These are for demonstration purposes only. There are many implementations of sorts in the Python standard library that are much better for performance reasons. +These implementations are for demonstration purposes. They are less efficient than the implementations in the Python standard library. -## Sort Algorithms +## Sorting Algorithms ### Bubble Sort ![alt text][bubble-image] -From [Wikipedia][bubble-wiki]: **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. +From [Wikipedia][bubble-wiki]: **Bubble sort**, sometimes referred to as *sinking sort*, is a simple sorting algorithm that repeatedly steps through the list, compares adjacent pairs 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) @@ -23,7 +23,7 @@ __Properties__ ![alt text][bucket-image-1] ![alt text][bucket-image-2] -From [Wikipedia][bucket-wiki]: 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. +From [Wikipedia][bucket-wiki]: Bucket sort, or bin sort, is a sorting algorithm that distributes 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) @@ -57,7 +57,7 @@ __Properties__ ### Merge Sort ![alt text][merge-image] -From [Wikipedia][merge-wiki]: **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. +From [Wikipedia][merge-wiki]: **Merge sort** (also commonly spelled *mergesort*) is an efficient, general-purpose, comparison-based sorting algorithm. Most implementations produce a stable sort, which means the order of equal items is the same in the input and 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) @@ -95,7 +95,7 @@ __Properties__ ### Radix -From [Wikipedia][radix-wiki]: In computer science, 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. +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) @@ -128,7 +128,7 @@ __Properties__ ### Topological -From [Wikipedia][topological-wiki]: In the field of computer science, a 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. +From [Wikipedia][topological-wiki]: A 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 @@ -136,7 +136,7 @@ Comparing the complexity of sorting algorithms (*Bubble Sort*, *Insertion Sort*, ![Complexity Graphs](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png) -Selecting a sort technique: Quicksort is a very fast algorithm but can be pretty tricky to implement while bubble sort is a slow algorithm which is very easy to implement. For a small value of n, 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. +Selecting a sort technique: Quicksort is a very fast algorithm but can be pretty tricky to implement while bubble sort is a slow algorithm which is very easy to implement. For a small datasets 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. @@ -147,7 +147,7 @@ Selecting a sort technique: Quicksort is a very fast algorithm but can be pretty ### Linear ![alt text][linear-image] -From [Wikipedia][linear-wiki]: **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. +From [Wikipedia][linear-wiki]: **Linear search** or *sequential search* is a method for finding an element in a list. It sequentially checks each element of the list until a match is found or all the elements have been searched. __Properties__ * Worst case performance O(n) From 840aa6209b674a00dce1ea90734e98a7d3e4e7fe Mon Sep 17 00:00:00 2001 From: Yasser A Date: Tue, 6 Nov 2018 17:19:51 -0500 Subject: [PATCH 0237/2908] fix division by float issue in range heap.py --- data_structures/heap/heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index d0c2400eb6b5..8187af101308 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -40,7 +40,7 @@ def maxHeapify(self,node): def buildHeap(self,a): self.currsize = len(a) self.h = list(a) - for i in range(self.currsize/2,-1,-1): + for i in range(self.currsize//2,-1,-1): self.maxHeapify(i) def getMax(self): From 265ea0eccb7314d8076bd524d7b6f11f1c2ff36a Mon Sep 17 00:00:00 2001 From: gpapadok <38889721+gpapadok@users.noreply.github.com> Date: Sat, 10 Nov 2018 20:02:36 +0200 Subject: [PATCH 0238/2908] Update FindMin.py (#601) Fixed indentation error. --- Maths/FindMin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maths/FindMin.py b/Maths/FindMin.py index 19371859c7fe..86207984e3da 100644 --- a/Maths/FindMin.py +++ b/Maths/FindMin.py @@ -4,7 +4,7 @@ def findMin(x): for i in x: if minNum > i: minNum = i - return minNum + return minNum print(findMin([0,1,2,3,4,5,-3,24,-56])) # = -56 From 70a6d98e0fef8452cc4a4b68b8adcb896856414a Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Sun, 11 Nov 2018 22:57:36 +0530 Subject: [PATCH 0239/2908] Update absMin.py --- Maths/absMin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maths/absMin.py b/Maths/absMin.py index 8047d63acb52..a353be4ceb8f 100644 --- a/Maths/absMin.py +++ b/Maths/absMin.py @@ -6,7 +6,7 @@ def absMin(x): >>absMin([3,-10,-2]) -2 """ - j = x[0] + j = absVal(x[0]) for i in x: if absVal(i) < j: j = i From 63b2c4efe08c3d248e633c755d181ddb2ea0a9f7 Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Mon, 12 Nov 2018 23:08:07 +0530 Subject: [PATCH 0240/2908] Update lib.py replaced size() with __len__ built-in changed self.__components = components to self.__components = list(components) replacing for loops with list comprehension allowing -ve indexing in component() and changeComponent() --- linear_algebra_python/src/lib.py | 87 ++++++++++---------------------- 1 file changed, 26 insertions(+), 61 deletions(-) diff --git a/linear_algebra_python/src/lib.py b/linear_algebra_python/src/lib.py index 66f27ff8946e..252218844f8c 100644 --- a/linear_algebra_python/src/lib.py +++ b/linear_algebra_python/src/lib.py @@ -36,7 +36,7 @@ class Vector(object): set(components : list) : changes the vector components. __str__() : toString method component(i : int): gets the i-th component (start by 0) - size() : gets the size of the vector (number of components) + __len__() : gets the size of the vector (number of components) euclidLength() : returns the eulidean length of the vector. operator + : vector addition operator - : vector subtraction @@ -50,7 +50,7 @@ def __init__(self,components): input: components or nothing simple constructor for init the vector """ - self.__components = components + self.__components = list(components) def set(self,components): """ input: new components @@ -58,33 +58,24 @@ def set(self,components): replace the components with newer one. """ if len(components) > 0: - self.__components = components + self.__components = list(components) else: raise Exception("please give any vector") def __str__(self): """ returns a string representation of the vector """ - ans = "(" - length = len(self.__components) - for i in range(length): - if i != length-1: - ans += str(self.__components[i]) + "," - else: - ans += str(self.__components[i]) + ")" - if len(ans) == 1: - ans += ")" - return ans + return "(" + ",".join(map(str, self.__components)) + ")" def component(self,i): """ input: index (start at 0) output: the i-th component of the vector. """ - if i < len(self.__components) and i >= 0: + 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 size(self): + def __len__(self): """ returns the size of the vector """ @@ -103,9 +94,9 @@ def __add__(self,other): assumes: other vector has the same size returns a new vector that represents the sum. """ - size = self.size() + size = len(self) result = [] - if size == other.size(): + if size == len(other): for i in range(size): result.append(self.__components[i] + other.component(i)) else: @@ -117,9 +108,9 @@ def __sub__(self,other): assumes: other vector has the same size returns a new vector that represents the differenz. """ - size = self.size() + size = len(self) result = [] - if size == other.size(): + if size == len(other): for i in range(size): result.append(self.__components[i] - other.component(i)) else: # error case @@ -134,8 +125,8 @@ def __mul__(self,other): if isinstance(other,float) or isinstance(other,int): for c in self.__components: ans.append(c*other) - elif (isinstance(other,Vector) and (self.size() == other.size())): - size = self.size() + 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) @@ -147,8 +138,7 @@ def copy(self): """ copies this vector and returns it. """ - components = [x for x in self.__components] - return Vector(components) + return Vector(self.__components) def changeComponent(self,pos,value): """ input: an index (pos) and a value @@ -156,7 +146,7 @@ def changeComponent(self,pos,value): 'value' """ #precondition - assert (pos >= 0 and pos < len(self.__components)) + assert (-len(self.__components) <= pos < len(self.__components)) self.__components[pos] = value def zeroVector(dimension): @@ -165,10 +155,7 @@ def zeroVector(dimension): """ #precondition assert(isinstance(dimension,int)) - ans = [] - for i in range(dimension): - ans.append(0) - return Vector(ans) + return Vector([0]*dimension) def unitBasisVector(dimension,pos): @@ -178,12 +165,8 @@ def unitBasisVector(dimension,pos): """ #precondition assert(isinstance(dimension,int) and (isinstance(pos,int))) - ans = [] - for i in range(dimension): - if i != pos: - ans.append(0) - else: - ans.append(1) + ans = [0]*dimension + ans[pos] = 1 return Vector(ans) @@ -206,11 +189,9 @@ def randomVector(N,a,b): output: returns a random vector of size N, with random integer components between 'a' and 'b'. """ - ans = zeroVector(N) random.seed(None) - for i in range(N): - ans.changeComponent(i,random.randint(a,b)) - return ans + ans = [random.randint(a,b) for i in range(N)] + return Vector(ans) class Matrix(object): @@ -220,7 +201,7 @@ class Matrix(object): 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. @@ -284,7 +265,7 @@ def __mul__(self,other): implements the matrix-scalar multiplication """ if isinstance(other, Vector): # vector-matrix - if (other.size() == self.__width): + if (len(other) == self.__width): ans = zeroVector(self.__height) for i in range(self.__height): summe = 0 @@ -294,15 +275,9 @@ def __mul__(self,other): summe = 0 return ans else: - raise Exception("vector must have the same size as the " - + "number of columns of the matrix!") + 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 = [] - for i in range(self.__height): - row = [] - for j in range(self.__width): - row.append(self.__matrix[i][j] * other) - matrix.append(row) + 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): """ @@ -338,12 +313,7 @@ def squareZeroMatrix(N): """ returns a square zero-matrix of dimension NxN """ - ans = [] - for i in range(N): - row = [] - for j in range(N): - row.append(0) - ans.append(row) + ans = [[0]*N for i in range(N)] return Matrix(ans,N,N) @@ -352,13 +322,8 @@ def randomMatrix(W,H,a,b): returns a random matrix WxH with integer components between 'a' and 'b' """ - matrix = [] random.seed(None) - for i in range(H): - row = [] - for j in range(W): - row.append(random.randint(a,b)) - matrix.append(row) + matrix = [[random.randint(a,b) for j in range(W)] for i in range(H)] return Matrix(matrix,W,H) - \ No newline at end of file + From b2b34e2cda6340c9baee0029df499c7e5daef161 Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Mon, 12 Nov 2018 23:33:22 +0530 Subject: [PATCH 0241/2908] Update lib.py providing default value to components parameter of __init__() --- linear_algebra_python/src/lib.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/linear_algebra_python/src/lib.py b/linear_algebra_python/src/lib.py index 252218844f8c..281991a93b2d 100644 --- a/linear_algebra_python/src/lib.py +++ b/linear_algebra_python/src/lib.py @@ -45,7 +45,7 @@ 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 @@ -95,13 +95,11 @@ def __add__(self,other): returns a new vector that represents the sum. """ size = len(self) - result = [] if size == len(other): - for i in range(size): - result.append(self.__components[i] + other.component(i)) + result = [self.__components[i] + other.component(i) for i in range(size)] + return Vector(result) else: raise Exception("must have the same size") - return Vector(result) def __sub__(self,other): """ input: other vector @@ -109,22 +107,19 @@ def __sub__(self,other): returns a new vector that represents the differenz. """ size = len(self) - result = [] if size == len(other): - for i in range(size): - result.append(self.__components[i] - other.component(i)) + result = [self.__components[i] - other.component(i) for i in range(size)] + return result else: # error case raise Exception("must have the same size") - return Vector(result) def __mul__(self,other): """ mul implements the scalar multiplication and the dot-product """ - ans = [] if isinstance(other,float) or isinstance(other,int): - for c in self.__components: - ans.append(c*other) + ans = [c*other for c in self.__components] + return ans elif (isinstance(other,Vector) and (len(self) == len(other))): size = len(self) summe = 0 @@ -133,7 +128,6 @@ def __mul__(self,other): return summe else: # error case raise Exception("invalide operand!") - return Vector(ans) def copy(self): """ copies this vector and returns it. From 93e096f0c13276e313f7ce5c937a72217dbfe396 Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Mon, 12 Nov 2018 23:34:31 +0530 Subject: [PATCH 0242/2908] Update tests.py changing tests.py according to changes in lib.py and replaced A.__str__() with str(A) --- linear_algebra_python/src/tests.py | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/linear_algebra_python/src/tests.py b/linear_algebra_python/src/tests.py index b84612b4ced4..a26eb92653e2 100644 --- a/linear_algebra_python/src/tests.py +++ b/linear_algebra_python/src/tests.py @@ -29,13 +29,13 @@ def test_str(self): test for toString() method """ x = Vector([0,0,0,0,0,1]) - self.assertEqual(x.__str__(),"(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(x.size(),4) + self.assertEqual(len(x),4) def test_euclidLength(self): """ test for the eulidean length @@ -67,32 +67,32 @@ def test_mul(self): x = Vector([1,2,3]) a = Vector([2,-1,4]) # for test of dot-product b = Vector([1,-2,-1]) - self.assertEqual((x*3.0).__str__(),"(3.0,6.0,9.0)") + 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(zeroVector(10).__str__().count("0") == 10) + self.assertTrue(str(zeroVector(10)).count("0") == 10) def test_unitBasisVector(self): """ test for the global function unitBasisVector(...) """ - self.assertEqual(unitBasisVector(3,1).__str__(),"(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(axpy(2,x,y).__str__(),"(3,4,7)") + 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]) y = x.copy() - self.assertEqual(x.__str__(),y.__str__()) + self.assertEqual(str(x),str(y)) def test_changeComponent(self): """ test for the changeComponent(...)-method @@ -100,34 +100,34 @@ def test_changeComponent(self): x = Vector([1,0,0]) x.changeComponent(0,0) x.changeComponent(1,1) - self.assertEqual(x.__str__(),"(0,1,0)") + 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",A.__str__()) + 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)",(A*x).__str__()) - self.assertEqual("|2,4,6|\n|8,10,12|\n|14,16,18|\n",(A*2).__str__()) + 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",A.__str__()) + 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) 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",(A+B).__str__()) + 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",(A-B).__str__()) + 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',squareZeroMatrix(5).__str__()) + +'\n|0,0,0,0,0|\n',str(squareZeroMatrix(5))) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From 737bb2c525ebe33f0c9dae9d9bd6ff1b10ba7f5d Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Mon, 12 Nov 2018 23:38:08 +0530 Subject: [PATCH 0243/2908] Update README.md --- linear_algebra_python/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/linear_algebra_python/README.md b/linear_algebra_python/README.md index ebfcdab7b179..1e34d0bd7805 100644 --- a/linear_algebra_python/README.md +++ b/linear_algebra_python/README.md @@ -13,9 +13,9 @@ This module contains some useful classes and functions for dealing with linear a - constructor(components : list) : init the vector - set(components : list) : changes the vector components. - - __str__() : toString method + - \_\_str\_\_() : toString method - component(i : int): gets the i-th component (start by 0) - - size() : gets the size of the vector (number of components) + - \_\_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 @@ -31,12 +31,13 @@ This module contains some useful classes and functions for dealing with linear a - computes the axpy operation - function randomVector(N,a,b) - returns a random vector of size N, with random integer components between 'a' and 'b'. + - class Matrix - This class represents a matrix of arbitrary size and operations on it. **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. @@ -45,6 +46,7 @@ This module contains some useful classes and functions for dealing with linear a - height() : returns the height of the matrix - 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) From 74a65017ca114a1e070d45e8a2c3cf59d4391b8f Mon Sep 17 00:00:00 2001 From: P-Shreyas-Shetty Date: Tue, 13 Nov 2018 05:27:26 +0530 Subject: [PATCH 0244/2908] Added axes label to the plot --- neural_network/bpnn.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/neural_network/bpnn.py b/neural_network/bpnn.py index 0865e35f0b5c..92deaee19c6e 100644 --- a/neural_network/bpnn.py +++ b/neural_network/bpnn.py @@ -164,6 +164,8 @@ def plot_loss(self): self.ax_loss.lines.remove(self.ax_loss.lines[0]) self.ax_loss.plot(self.train_mse, 'r-') plt.ion() + plt.xlabel('step') + plt.ylabel('loss') plt.show() plt.pause(0.1) From 84ae00197f66d98e50b03416e0a420cfeaed6baa Mon Sep 17 00:00:00 2001 From: dilson Date: Wed, 14 Nov 2018 21:08:43 -0300 Subject: [PATCH 0245/2908] Fixed typo error in perceptron.py --- .vs/Python/v15/.suo | Bin 0 -> 16896 bytes .vs/slnx.sqlite | Bin 0 -> 176128 bytes neural_network/perceptron.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .vs/Python/v15/.suo create mode 100644 .vs/slnx.sqlite diff --git a/.vs/Python/v15/.suo b/.vs/Python/v15/.suo new file mode 100644 index 0000000000000000000000000000000000000000..0e3f4807567d1e684e2302bf9cfb6cc11e1c136d GIT binary patch literal 16896 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 literal 0 HcmV?d00001 diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..2fe4a449f121e419a91e4b74caa326bd9c11d003 GIT binary patch literal 176128 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 literal 0 HcmV?d00001 diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 44c98e29c52b..eb8b04e855d3 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -115,7 +115,7 @@ def sign(self, u): network = Perceptron(sample=samples, exit = exit, learn_rate=0.01, epoch_number=1000, bias=-1) -network.trannig() +network.training() while True: sample = [] From 16cc96a0920cb3714baf9a27bdcf69c1799cf9de Mon Sep 17 00:00:00 2001 From: Awfifcuihc <21129044@zju.edu.cn> Date: Tue, 20 Nov 2018 03:34:44 +0800 Subject: [PATCH 0246/2908] Create AVLtree.py --- data_structures/binary tree/AVLtree.py | 219 +++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 data_structures/binary tree/AVLtree.py diff --git a/data_structures/binary tree/AVLtree.py b/data_structures/binary tree/AVLtree.py new file mode 100644 index 000000000000..43fff96451a0 --- /dev/null +++ b/data_structures/binary tree/AVLtree.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- +import math +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): + 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]) + +class my_node: + 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): + self.data = data + return + def setleft(self,node): + self.left = node + return + def setright(self,node): + self.right = node + return + def setheight(self,height): + self.height = height + return + +def getheight(node): +# print("xxx") + if node is None: + return 0 + return node.getheight() + +def my_max(a,b): + if a > b: + return a + return b + + + +def leftrotation(node): + 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) + return ret + +def rightrotation(node): + 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) + return ret + +def lrrotation(node): + node.setright(leftrotation(node.getright())) + return rightrotation(node) + +def rlrotation(node): + node.setleft(rightrotation(node.getleft())) + return leftrotation(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 getheight(node.getleft()) - getheight(node.getright()) == 2: + if data < node.getleft().getdata(): + node = leftrotation(node) + else: + node = rlrotation(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) + else: + node = rightrotation(node) + h1 = my_max(getheight(node.getright()),getheight(node.getleft())) + 1 + node.setheight(h1) + return node + +class AVLtree: + def __init__(self): + self.root = None + def getheight(self): +# print("yyy") + return getheight(self.root) + def insert(self,data): + self.root = insert_node(self.root,data) + def traversale(self): + q = my_queue() + q.push(self.root) + layer = self.getheight() + cnt = 0 + while not q.isEmpty(): + node = q.pop() + space = " "*int(math.pow(2,layer) - 1) + print(space,end = " ") + if node is None: + print("*",end = " ") + else: + print(node.getdata(),end = " ") + q.push(node.getleft()) + q.push(node.getright()) + print(space,end = " ") + cnt = cnt + 1 + for i in range(100): + if cnt == math.pow(2,i) - 1: + layer = layer -1 + print() + break + print() + print("*************************************") + return + + def test(self): + getheight(None) + print("****") + self.getheight() +if __name__ == "__main__": +# t = AVLtree() +# t.test() +# q = my_queue() +## q.push(1) +## q.push(2) +# for i in range(10): +# q.push(i) +# q.print() +# q.push(90) +# q.print() +# +# q.pop() +# q.print() + t = AVLtree() + t.traversale() +# t.insert(7) +# t.traversale() + + t.insert(8) + t.traversale() + t.insert(3) + t.traversale() + t.insert(6) + t.traversale() + t.insert(1) + t.traversale() + t.insert(10) + t.traversale() + t.insert(14) + t.traversale() + t.insert(13) + t.traversale() +# t.traversale() + t.insert(4) + t.traversale() + t.insert(7) + t.traversale() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6001215b600caabcd4ce567952e2482382f58b90 Mon Sep 17 00:00:00 2001 From: Awfifcuihc <21129044@zju.edu.cn> Date: Tue, 20 Nov 2018 03:41:52 +0800 Subject: [PATCH 0247/2908] Update AVLtree.py An auto balanced binary tree with no delete node function leave for latter --- data_structures/binary tree/AVLtree.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data_structures/binary tree/AVLtree.py b/data_structures/binary tree/AVLtree.py index 43fff96451a0..cd867906f834 100644 --- a/data_structures/binary tree/AVLtree.py +++ b/data_structures/binary tree/AVLtree.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +''' +An auto-balanced binary tree! +''' import math class my_queue: def __init__(self): From 9f96c155bea977cc80a7a1110ce1f46760f57904 Mon Sep 17 00:00:00 2001 From: Awfifcuihc <21129044@zju.edu.cn> Date: Wed, 21 Nov 2018 03:25:07 +0800 Subject: [PATCH 0248/2908] Update AVLtree.py add delete function add demo with shuffled list add print lines to trace the addition or deletion --- data_structures/binary tree/AVLtree.py | 145 +++++++++++++++++++++---- 1 file changed, 121 insertions(+), 24 deletions(-) diff --git a/data_structures/binary tree/AVLtree.py b/data_structures/binary tree/AVLtree.py index cd867906f834..c0b37ab08f8d 100644 --- a/data_structures/binary tree/AVLtree.py +++ b/data_structures/binary tree/AVLtree.py @@ -3,6 +3,7 @@ An auto-balanced binary tree! ''' import math +import random class my_queue: def __init__(self): self.data = [] @@ -65,6 +66,7 @@ def my_max(a,b): def leftrotation(node): + print("left rotation node:",node.getdata()) ret = node.getleft() node.setleft(ret.getright()) ret.setright(node) @@ -75,6 +77,7 @@ def leftrotation(node): return ret def rightrotation(node): + print("right rotation node:",node.getdata()) ret = node.getright() node.setright(ret.getleft()) ret.setleft(node) @@ -113,6 +116,52 @@ def insert_node(node,data): 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): + 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() + else: + root = root.getright() + elif root.getdata() > data: + if root.getleft() 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: + return root + else: + root.setright(del_node(root.getright(),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) + else: + root = lrrotation(root) + elif getheight(root.getright()) - getheight(root.getleft()) == -2: + if getheight(root.getleft().getleft()) > getheight(root.getleft().getright()): + root = leftrotation(root) + else: + root = rlrotation(root) + height = my_max(getheight(root.getright()),getheight(root.getleft())) + 1 + root.setheight(height) + return root + class AVLtree: def __init__(self): self.root = None @@ -120,27 +169,43 @@ def getheight(self): # 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)) + if self.root is None: + print("Tree is empty!") + return + self.root = del_node(self.root,data) def traversale(self): q = my_queue() q.push(self.root) layer = self.getheight() + if layer == 0: + return 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 layer == 0: + print() + print("*************************************") + return print() break print() @@ -168,27 +233,59 @@ def test(self): t = AVLtree() t.traversale() # t.insert(7) +## t.traversale() +# +# t.insert(8) # t.traversale() - - t.insert(8) - t.traversale() - t.insert(3) - t.traversale() - t.insert(6) - t.traversale() - t.insert(1) - t.traversale() - t.insert(10) - t.traversale() - t.insert(14) - t.traversale() - t.insert(13) - t.traversale() +# t.insert(3) # t.traversale() - t.insert(4) - t.traversale() - t.insert(7) - t.traversale() +# t.insert(6) +# t.traversale() +# t.insert(1) +# t.traversale() +# t.insert(10) +# t.traversale() +# t.insert(14) +# t.traversale() +# t.insert(13) +# t.traversale() +## t.traversale() +# t.insert(4) +# t.traversale() +# t.insert(7) +# t.traversale() +# +# +# t.del_node(8) +# t.traversale() +# t.del_node(3) +# t.traversale() +# t.del_node(6) +# t.traversale() +# t.del_node(1) +# t.traversale() +# t.del_node(10) +# t.traversale() +# t.del_node(14) +# t.traversale() +# t.del_node(13) +# t.traversale() +## t.traversale() +# t.del_node(4) +# t.traversale() +# t.del_node(7) +# t.traversale() + l = list(range(10)) + random.shuffle(l) + for i in l: + t.insert(i) + t.traversale() + + random.shuffle(l) + for i in l: + t.del_node(i) + t.traversale() + From d1dba51326abc2b1cf8d621b0fd85c84611f4d28 Mon Sep 17 00:00:00 2001 From: Awfifcuihc <21129044@zju.edu.cn> Date: Wed, 21 Nov 2018 12:46:32 +0800 Subject: [PATCH 0249/2908] remove commented lines --- data_structures/binary tree/AVLtree.py | 56 -------------------------- 1 file changed, 56 deletions(-) diff --git a/data_structures/binary tree/AVLtree.py b/data_structures/binary tree/AVLtree.py index c0b37ab08f8d..4670176560b2 100644 --- a/data_structures/binary tree/AVLtree.py +++ b/data_structures/binary tree/AVLtree.py @@ -217,64 +217,8 @@ def test(self): print("****") self.getheight() if __name__ == "__main__": -# t = AVLtree() -# t.test() -# q = my_queue() -## q.push(1) -## q.push(2) -# for i in range(10): -# q.push(i) -# q.print() -# q.push(90) -# q.print() -# -# q.pop() -# q.print() t = AVLtree() t.traversale() -# t.insert(7) -## t.traversale() -# -# t.insert(8) -# t.traversale() -# t.insert(3) -# t.traversale() -# t.insert(6) -# t.traversale() -# t.insert(1) -# t.traversale() -# t.insert(10) -# t.traversale() -# t.insert(14) -# t.traversale() -# t.insert(13) -# t.traversale() -## t.traversale() -# t.insert(4) -# t.traversale() -# t.insert(7) -# t.traversale() -# -# -# t.del_node(8) -# t.traversale() -# t.del_node(3) -# t.traversale() -# t.del_node(6) -# t.traversale() -# t.del_node(1) -# t.traversale() -# t.del_node(10) -# t.traversale() -# t.del_node(14) -# t.traversale() -# t.del_node(13) -# t.traversale() -## t.traversale() -# t.del_node(4) -# t.traversale() -# t.del_node(7) -# t.traversale() l = list(range(10)) random.shuffle(l) for i in l: From 9bbc4d9021c439ad5d3a3f5103073438acf8e0f2 Mon Sep 17 00:00:00 2001 From: Awfifcuihc <21129044@zju.edu.cn> Date: Thu, 22 Nov 2018 14:33:50 +0800 Subject: [PATCH 0250/2908] Update AVLtree.py add comments --- data_structures/binary tree/AVLtree.py | 40 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/data_structures/binary tree/AVLtree.py b/data_structures/binary tree/AVLtree.py index 4670176560b2..5c72e273f17f 100644 --- a/data_structures/binary tree/AVLtree.py +++ b/data_structures/binary tree/AVLtree.py @@ -53,7 +53,6 @@ def setheight(self,height): return def getheight(node): -# print("xxx") if node is None: return 0 return node.getheight() @@ -66,6 +65,17 @@ def my_max(a,b): def leftrotation(node): + ''' + A B + / \ / \ + B C Bl A + / \ --> / / \ + Bl Br UB Br C + / + UB + + UB = unbalanced node + ''' print("left rotation node:",node.getdata()) ret = node.getleft() node.setleft(ret.getright()) @@ -77,6 +87,9 @@ def leftrotation(node): return ret def rightrotation(node): + ''' + a mirror symmetry rotation of the leftrotation + ''' print("right rotation node:",node.getdata()) ret = node.getright() node.setright(ret.getleft()) @@ -87,24 +100,35 @@ def rightrotation(node): ret.setheight(h2) return ret +def rlrotation(node): + ''' + A A Br + / \ / \ / \ + B C RR Br C LR B A + / \ --> / \ --> / / \ + Bl Br B UB Bl UB C + \ / + 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 rlrotation(node): - node.setleft(rightrotation(node.getleft())) - return leftrotation(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 getheight(node.getleft()) - getheight(node.getright()) == 2: - if data < node.getleft().getdata(): + 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) + node = rlrotation(node) #new node is the right child of the left child else: node.setright(insert_node(node.getright(),data)) if getheight(node.getright()) - getheight(node.getleft()) == 2: @@ -178,7 +202,7 @@ def del_node(self,data): print("Tree is empty!") return self.root = del_node(self.root,data) - def traversale(self): + def traversale(self): #a level traversale, gives a more intuitive look on the tree q = my_queue() q.push(self.root) layer = self.getheight() From 768a39d8322730cf8f89a84b9d2d1086118fc8cc Mon Sep 17 00:00:00 2001 From: Shivam Arora Date: Fri, 23 Nov 2018 22:21:07 +0530 Subject: [PATCH 0251/2908] Program for finding the HCF,LCM and Palindrome using and recursion and non recursion --- Maths/find_hcf.py | 22 ++++++++++++++++++++++ Maths/find_lcm.py | 17 +++++++++++++++++ other/palindrome.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 Maths/find_hcf.py create mode 100644 Maths/find_lcm.py create mode 100644 other/palindrome.py diff --git a/Maths/find_hcf.py b/Maths/find_hcf.py new file mode 100644 index 000000000000..e4315d8d37a7 --- /dev/null +++ b/Maths/find_hcf.py @@ -0,0 +1,22 @@ +# Program to find the HCF of two Numbers +def find_hcf(num_1, num_2): + if num_1 == 0: + return num_2 + if num_2 == 0: + return num_1 + # Base Case + if num_1 == num_2: + return num_1 + if num_1 > num_2: + return find_hcf(num_1 - num_2, num_2) + return find_hcf(num_1, num_2 - num_1) + + +def main(): + num_1 = 24 + num_2 = 34 + print('HCF of %s and %s is %s:' % (num_1, num_2, find_hcf(num_1, num_2))) + + +if __name__ == '__main__': + main() diff --git a/Maths/find_lcm.py b/Maths/find_lcm.py new file mode 100644 index 000000000000..58beb3e37649 --- /dev/null +++ b/Maths/find_lcm.py @@ -0,0 +1,17 @@ +def find_lcm(num_1, num_2): + max = num_1 if num_1 > num_2 else num_2 + while (True): + if ((max % num_1 == 0) and (max % num_2 == 0)): + break + max += 1 + return max + + +def main(): + num_1 = 12 + num_2 = 76 + print(find_lcm(num_1, num_2)) + + +if __name__ == '__main__': + main() diff --git a/other/palindrome.py b/other/palindrome.py new file mode 100644 index 000000000000..990ec844f9fb --- /dev/null +++ b/other/palindrome.py @@ -0,0 +1,31 @@ +# Program to find whether given string is palindrome or not +def is_palindrome(str): + start_i = 0 + end_i = len(str) - 1 + while start_i < end_i: + if str[start_i] == str[end_i]: + start_i += 1 + end_i -= 1 + else: + return False + return True + + +# Recursive method +def recursive_palindrome(str): + if len(str) <= 1: + return True + if str[0] == str[len(str) - 1]: + return recursive_palindrome(str[1:-1]) + else: + return False + + +def main(): + str = 'ama' + print(recursive_palindrome(str.lower())) + print(is_palindrome(str.lower())) + + +if __name__ == '__main__': + main() From 72c217c37eb2ad694eeb9cb360d1cd65b2d52e8d Mon Sep 17 00:00:00 2001 From: Harshil Date: Fri, 23 Nov 2018 18:23:06 +0100 Subject: [PATCH 0252/2908] Update AVLtree.py Removed blank lines (256 to 287)! --- data_structures/binary tree/AVLtree.py | 32 -------------------------- 1 file changed, 32 deletions(-) diff --git a/data_structures/binary tree/AVLtree.py b/data_structures/binary tree/AVLtree.py index 5c72e273f17f..cb27004b6ac3 100644 --- a/data_structures/binary tree/AVLtree.py +++ b/data_structures/binary tree/AVLtree.py @@ -253,35 +253,3 @@ def test(self): for i in l: t.del_node(i) t.traversale() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 98db791d372f6ceab301ff029d3dd82924dcda71 Mon Sep 17 00:00:00 2001 From: A Safari Date: Wed, 28 Nov 2018 15:54:19 -0500 Subject: [PATCH 0253/2908] Create Non-crossing-lines-to-connect-points-in-a-circle --- .../Non-crossing-lines-to-connect-points-in-a-circle | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 dynamic_programming/Non-crossing-lines-to-connect-points-in-a-circle diff --git a/dynamic_programming/Non-crossing-lines-to-connect-points-in-a-circle b/dynamic_programming/Non-crossing-lines-to-connect-points-in-a-circle new file mode 100644 index 000000000000..a2eec04547eb --- /dev/null +++ b/dynamic_programming/Non-crossing-lines-to-connect-points-in-a-circle @@ -0,0 +1,10 @@ +def count(n): + if (n & 1): + return 0 + val = n // 2 + dy_lst = [1 for i in range(val + 1)] + for i in range(2, val + 1): + dy_lst[i] = 0 + for j in range(i): + dy_lst[i] += (dy_lst[j] * dy_lst[i - j - 1]) + return dy_lst[val] From 1c29a455b855232f7309380b0ad822fb351f1076 Mon Sep 17 00:00:00 2001 From: A Safari Date: Wed, 28 Nov 2018 15:56:10 -0500 Subject: [PATCH 0254/2908] Delete Non-crossing-lines-to-connect-points-in-a-circle --- .../Non-crossing-lines-to-connect-points-in-a-circle | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 dynamic_programming/Non-crossing-lines-to-connect-points-in-a-circle diff --git a/dynamic_programming/Non-crossing-lines-to-connect-points-in-a-circle b/dynamic_programming/Non-crossing-lines-to-connect-points-in-a-circle deleted file mode 100644 index a2eec04547eb..000000000000 --- a/dynamic_programming/Non-crossing-lines-to-connect-points-in-a-circle +++ /dev/null @@ -1,10 +0,0 @@ -def count(n): - if (n & 1): - return 0 - val = n // 2 - dy_lst = [1 for i in range(val + 1)] - for i in range(2, val + 1): - dy_lst[i] = 0 - for j in range(i): - dy_lst[i] += (dy_lst[j] * dy_lst[i - j - 1]) - return dy_lst[val] From d75bec8a786a3fa695d1adc5151bedc3276fdffa Mon Sep 17 00:00:00 2001 From: ahviplc!~LC Date: Wed, 5 Dec 2018 01:18:32 +0800 Subject: [PATCH 0255/2908] absMax.py and absMin.py bugs fixed. (#624) --- Maths/abs.py | 4 ++-- Maths/absMax.py | 19 ++++++++++++------- Maths/absMin.py | 14 +++++++------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Maths/abs.py b/Maths/abs.py index 5b758f8389b4..6d0596478d5f 100644 --- a/Maths/abs.py +++ b/Maths/abs.py @@ -1,9 +1,9 @@ def absVal(num): """ Function to fins absolute value of numbers. - >>>absVal(-5) + >>absVal(-5) 5 - >>>absVal(0) + >>absVal(0) 0 """ if num < 0: diff --git a/Maths/absMax.py b/Maths/absMax.py index 432734ec02c0..a7bb2882dcd8 100644 --- a/Maths/absMax.py +++ b/Maths/absMax.py @@ -1,22 +1,27 @@ -from abs import absVal +from Maths.abs import absVal + def absMax(x): """ - >>>absMax([0,5,1,11]) + #>>>absMax([0,5,1,11]) 11 >>absMax([3,-10,-2]) -10 """ - j = x[0] + j =x[0] for i in x: - if absVal(i) < j: + if absVal(i) > absVal(j): j = i return j #BUG: i is apparently a list, TypeError: '<' not supported between instances of 'list' and 'int' in absVal - + #BUG fix def main(): - a = [1,2,-11] - print(absVal(a)) # = -11 + a = [-13, 2, -11, -12] + print(absMax(a)) # = -13 if __name__ == '__main__': main() + +""" +print abs Max +""" \ No newline at end of file diff --git a/Maths/absMin.py b/Maths/absMin.py index a353be4ceb8f..7eaecc060222 100644 --- a/Maths/absMin.py +++ b/Maths/absMin.py @@ -1,20 +1,20 @@ -from abs import absVal +from Maths.abs import absVal def absMin(x): """ - >>>absMin([0,5,1,11]) + # >>>absMin([0,5,1,11]) 0 - >>absMin([3,-10,-2]) + # >>absMin([3,-10,-2]) -2 """ - j = absVal(x[0]) + j = x[0] for i in x: - if absVal(i) < j: + if absVal(i) < absVal(j): j = i return j def main(): - a = [1,2,-11] - print(absMin(a)) # = 1 + a = [-3,-1,2,-11] + print(absMin(a)) # = -1 if __name__ == '__main__': main() From dab312e0e7b65d7ed64f110c8ccc55bc78174a8e Mon Sep 17 00:00:00 2001 From: wanderer <19929284+AlpineBlack@users.noreply.github.com> Date: Wed, 5 Dec 2018 01:32:53 +0800 Subject: [PATCH 0256/2908] bugs fixed (#623) * bugs fixed * bugs fixed * bugs fixed --- Maths/absMax.py | 2 +- Maths/absMin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Maths/absMax.py b/Maths/absMax.py index a7bb2882dcd8..2f719e32e645 100644 --- a/Maths/absMax.py +++ b/Maths/absMax.py @@ -24,4 +24,4 @@ def main(): """ print abs Max -""" \ No newline at end of file +""" diff --git a/Maths/absMin.py b/Maths/absMin.py index 7eaecc060222..67d510551907 100644 --- a/Maths/absMin.py +++ b/Maths/absMin.py @@ -17,4 +17,4 @@ def main(): print(absMin(a)) # = -1 if __name__ == '__main__': - main() + main() \ No newline at end of file From 362270c19f5448cdd44a8d1b453f1f4c2b84a66f Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Wed, 5 Dec 2018 21:25:01 +0800 Subject: [PATCH 0257/2908] Project Euler problem 2 pyhtonic solution (#629) * Project Euler problem 2 pyhtonic solution * Project Euler problem 2 made small changes --- project_euler/problem_02/sol1.py | 6 ++---- project_euler/problem_02/sol3.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/project_euler/problem_02/sol1.py b/project_euler/problem_02/sol1.py index f8257fb615fb..44ea980f2df0 100644 --- a/project_euler/problem_02/sol1.py +++ b/project_euler/problem_02/sol1.py @@ -18,9 +18,7 @@ j=2 sum=0 while(j<=n): - if((j&1)==0): #can also use (j%2==0) + if j%2 == 0: sum+=j - temp=i - i=j - j=temp+i + i , j = j, i+j print(sum) diff --git a/project_euler/problem_02/sol3.py b/project_euler/problem_02/sol3.py index d36b741bb4f9..0eb46d879704 100644 --- a/project_euler/problem_02/sol3.py +++ b/project_euler/problem_02/sol3.py @@ -12,9 +12,7 @@ b=2 count=0 while 4*b+a Date: Wed, 5 Dec 2018 21:25:46 +0800 Subject: [PATCH 0258/2908] Project Euler problem 1 pyhtonic solution (#628) --- project_euler/problem_01/sol5.py | 16 ++++++++++++++++ 1 file changed, 16 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..e261cc8fc729 --- /dev/null +++ b/project_euler/problem_01/sol5.py @@ -0,0 +1,16 @@ +''' +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 +except NameError: + pass #python 2 + +"""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])) + From 77f72fbe1f8d2f12c4d6c6a91b1a8afefa5bed7a Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 6 Dec 2018 23:19:28 +0800 Subject: [PATCH 0259/2908] Create sol2.py --- project_euler/problem_10/sol2.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 project_euler/problem_10/sol2.py diff --git a/project_euler/problem_10/sol2.py b/project_euler/problem_10/sol2.py new file mode 100644 index 000000000000..22df95c063e2 --- /dev/null +++ b/project_euler/problem_10/sol2.py @@ -0,0 +1,22 @@ +#from Python.Math import prime_generator +import math +from itertools import takewhile + +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()))) + +if __name__ == '__main__': + main() From 60ec25ef7a1f0294c066eaba568011752727f7c7 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Fri, 7 Dec 2018 02:29:04 +0800 Subject: [PATCH 0260/2908] Update sol1.py --- project_euler/problem_12/sol1.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/project_euler/problem_12/sol1.py b/project_euler/problem_12/sol1.py index 9c4483fd62e5..73d48a2ec897 100644 --- a/project_euler/problem_12/sol1.py +++ b/project_euler/problem_12/sol1.py @@ -30,7 +30,9 @@ def count_divisors(n): 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 @@ -43,4 +45,4 @@ def count_divisors(n): if count_divisors(tNum) > 500: break -print(tNum) \ No newline at end of file +print(tNum) From e5f130c1f006d2b96ca81be5a9f66c15b97b8793 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Fri, 7 Dec 2018 03:28:33 +0800 Subject: [PATCH 0261/2908] Create sol2.py --- project_euler/problem_12/sol2.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 project_euler/problem_12/sol2.py diff --git a/project_euler/problem_12/sol2.py b/project_euler/problem_12/sol2.py new file mode 100644 index 000000000000..479ab2b900cb --- /dev/null +++ b/project_euler/problem_12/sol2.py @@ -0,0 +1,8 @@ +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)) From fa2eecdc30d766197dad634a9f87bd4b7fad58ef Mon Sep 17 00:00:00 2001 From: A Safari Date: Fri, 14 Dec 2018 10:22:18 +0330 Subject: [PATCH 0262/2908] Directed graph with optional weight assignment . Containing graph auto-fill, dfs and bfs. --- graphs/Directed (Weighted) Graph | 95 ++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 graphs/Directed (Weighted) Graph diff --git a/graphs/Directed (Weighted) Graph b/graphs/Directed (Weighted) Graph new file mode 100644 index 000000000000..03ae572e8009 --- /dev/null +++ b/graphs/Directed (Weighted) Graph @@ -0,0 +1,95 @@ +from collections import deque +import random as rand +import math as math + +# the dfault weight is 1 if not assigend but all the implementation is weighted + +class DirectedGraph: + # enter True or False for this constructor + def __init__(self): + self.graph = {} + + # adding vertices and edges + # note that self loops are not supported in undirected simpl graphs but it is in multigraphs + 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 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 From 687af17d470e1b8b4d6e2a96afa6f512f9bcd519 Mon Sep 17 00:00:00 2001 From: A Safari Date: Fri, 14 Dec 2018 10:31:45 +0330 Subject: [PATCH 0263/2908] Added some examples. Added examples and comments for more readable code. --- graphs/Directed (Weighted) Graph | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/graphs/Directed (Weighted) Graph b/graphs/Directed (Weighted) Graph index 03ae572e8009..d3bafd103ebc 100644 --- a/graphs/Directed (Weighted) Graph +++ b/graphs/Directed (Weighted) Graph @@ -5,12 +5,12 @@ import math as math # the dfault weight is 1 if not assigend but all the implementation is weighted class DirectedGraph: - # enter True or False for this constructor def __init__(self): self.graph = {} # adding vertices and edges - # note that self loops are not supported in undirected simpl graphs but it is in multigraphs + # 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: @@ -19,6 +19,8 @@ class DirectedGraph: self.graph[u] = [[w, v]] if not self.graph.get(v): self.graph[v] = [] + + # handels if the input does not exist def remove_pair(self, u, v): if self.graph.get(u): for _ in self.graph[u]: @@ -93,3 +95,13 @@ class DirectedGraph: d.append(__[1]) visited.append(__[1]) return visited + +if __name__ == "__main__": + g = DirectedGraph() + # add 50 random nodes to the graph + g.fill_graph_randomly(50) + # you can add or remove any edge and vertex + g.add_pair(3, 5) + g.remove_pair(3,5) + g.dfs() + g.bgs() From 691f4c0a25e26acc259e4f9e589cd09ed9d70b18 Mon Sep 17 00:00:00 2001 From: A Safari Date: Fri, 14 Dec 2018 10:33:01 +0330 Subject: [PATCH 0264/2908] Python version 3 From e97565d21f1a6ec7d88ce9665817b9dd3d2978d4 Mon Sep 17 00:00:00 2001 From: A Safari Date: Fri, 14 Dec 2018 15:08:37 +0330 Subject: [PATCH 0265/2908] Added (Weighted) Undirected graph Python version 3 --- graphs/Directed (Weighted) Graph | 115 ++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 9 deletions(-) diff --git a/graphs/Directed (Weighted) Graph b/graphs/Directed (Weighted) Graph index d3bafd103ebc..0b3b3a2cb463 100644 --- a/graphs/Directed (Weighted) Graph +++ b/graphs/Directed (Weighted) Graph @@ -96,12 +96,109 @@ class DirectedGraph: visited.append(__[1]) return visited -if __name__ == "__main__": - g = DirectedGraph() - # add 50 random nodes to the graph - g.fill_graph_randomly(50) - # you can add or remove any edge and vertex - g.add_pair(3, 5) - g.remove_pair(3,5) - g.dfs() - g.bgs() + +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 From 889f8fba3d10a91803b76a49421cf103bfe479e6 Mon Sep 17 00:00:00 2001 From: A Safari Date: Fri, 14 Dec 2018 15:28:45 +0330 Subject: [PATCH 0266/2908] Added getting node degree functionality to both directed and undirected graph Easy to use directed and undirected graph in python 3 --- ...raph => Directed and Undirected (Weighted) Graph} | 12 ++++++++++++ 1 file changed, 12 insertions(+) rename graphs/{Directed (Weighted) Graph => Directed and Undirected (Weighted) Graph} (95%) diff --git a/graphs/Directed (Weighted) Graph b/graphs/Directed and Undirected (Weighted) Graph similarity index 95% rename from graphs/Directed (Weighted) Graph rename to graphs/Directed and Undirected (Weighted) Graph index 0b3b3a2cb463..74d741f5e3f4 100644 --- a/graphs/Directed (Weighted) Graph +++ b/graphs/Directed and Undirected (Weighted) Graph @@ -95,6 +95,16 @@ class DirectedGraph: 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]) class Graph: @@ -202,3 +212,5 @@ class Graph: d.append(__[1]) visited.append(__[1]) return visited + def degree(self, u): + return len(self.graph[u]) From b3a15175bddb4d63551830b80b3840264ad99c4b Mon Sep 17 00:00:00 2001 From: A Safari Date: Fri, 14 Dec 2018 23:14:35 +0330 Subject: [PATCH 0267/2908] Added more functionality Added topological sort, cycle detection and a function to report the nodes participating in cycles in graph(for a use case I myself needed ). --- .../Directed and Undirected (Weighted) Graph | 222 ++++++++++++++++++ 1 file changed, 222 insertions(+) diff --git a/graphs/Directed and Undirected (Weighted) Graph b/graphs/Directed and Undirected (Weighted) Graph index 74d741f5e3f4..7e7063823447 100644 --- a/graphs/Directed and Undirected (Weighted) Graph +++ b/graphs/Directed and Undirected (Weighted) Graph @@ -106,6 +106,133 @@ class DirectedGraph: 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 + 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 + 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 class Graph: def __init__(self): @@ -214,3 +341,98 @@ class Graph: 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 + 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 + 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 From 069d2b9cb6907014898c2ced90cfe9e735d2dd94 Mon Sep 17 00:00:00 2001 From: Safari Date: Sun, 16 Dec 2018 22:19:40 +0330 Subject: [PATCH 0268/2908] All Python Version 3 Added functions to get all nodes for some algorithms and time calculation for dfs and bfs. --- .../Directed and Undirected (Weighted) Graph | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/graphs/Directed and Undirected (Weighted) Graph b/graphs/Directed and Undirected (Weighted) Graph index 7e7063823447..68977de8d311 100644 --- a/graphs/Directed and Undirected (Weighted) Graph +++ b/graphs/Directed and Undirected (Weighted) Graph @@ -1,6 +1,7 @@ from collections import deque import random as rand import math as math +import time # the dfault weight is 1 if not assigend but all the implementation is weighted @@ -19,7 +20,10 @@ class DirectedGraph: 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): @@ -234,6 +238,18 @@ class DirectedGraph: 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 = {} @@ -436,3 +452,17 @@ class Graph: # 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 From 2d082cf19c91b40a0808af0acdc0970f93f536b9 Mon Sep 17 00:00:00 2001 From: Mikael Souza Date: Mon, 17 Dec 2018 10:44:38 -0400 Subject: [PATCH 0269/2908] Changed import from .Stack to stack --- data_structures/stacks/balanced_parentheses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 02efa8980291..ef470781de6a 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -1,6 +1,6 @@ from __future__ import print_function from __future__ import absolute_import -from .Stack import Stack +from stack import Stack __author__ = 'Omkar Pathak' From a8cfc14737bab805a7102accf24afafe565ef3f9 Mon Sep 17 00:00:00 2001 From: Mikael Souza Date: Mon, 17 Dec 2018 10:45:16 -0400 Subject: [PATCH 0270/2908] Added more parentheses examples --- data_structures/stacks/balanced_parentheses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index ef470781de6a..96a4a04325fa 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -17,7 +17,7 @@ def balanced_parentheses(parentheses): if __name__ == '__main__': - examples = ['((()))', '((())'] + examples = ['((()))', '((())', '(()))'] print('Balanced parentheses demonstration:\n') for example in examples: print(example + ': ' + str(balanced_parentheses(example))) From 2e2fadf4db224e53874b01fde0cf79e15eabc3b4 Mon Sep 17 00:00:00 2001 From: Mikael Souza Date: Mon, 17 Dec 2018 10:45:54 -0400 Subject: [PATCH 0271/2908] Fixed bug where an empty stack would cause error --- data_structures/stacks/balanced_parentheses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 96a4a04325fa..3229d19c8621 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -12,8 +12,10 @@ def balanced_parentheses(parentheses): if parenthesis == '(': stack.push(parenthesis) elif parenthesis == ')': + if stack.is_empty(): + return False stack.pop() - return not stack.is_empty() + return stack.is_empty() if __name__ == '__main__': From d26311424d2d996a66c56fcb52d97faa14fc2dff Mon Sep 17 00:00:00 2001 From: Jithendra Yenugula Date: Tue, 25 Dec 2018 18:39:36 +0530 Subject: [PATCH 0272/2908] Adding a program for swap nodes in linkedlist (#667) * Adding a program for swap nodes in linkedlist * Updating swapNodes --- data_structures/linked_list/swapNodes.py | 75 ++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 data_structures/linked_list/swapNodes.py diff --git a/data_structures/linked_list/swapNodes.py b/data_structures/linked_list/swapNodes.py new file mode 100644 index 000000000000..ce2543bc46d8 --- /dev/null +++ b/data_structures/linked_list/swapNodes.py @@ -0,0 +1,75 @@ +class Node: + def __init__(self, data): + self.data = data; + self.next = None + + +class Linkedlist: + def __init__(self): + self.head = None + + def print_list(self): + temp = self.head + while temp is not None: + print(temp.data) + temp = temp.next + +# adding nodes + def push(self, new_data): + 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: + 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 + +# swapping code ends here + + + +if __name__ == '__main__': + list = Linkedlist() + list.push(5) + list.push(4) + list.push(3) + list.push(2) + list.push(1) + + list.print_list() + + list.swapNodes(1, 4) + print("After swapping") + list.print_list() + + + From f6d241e52d87bf4b020609bd8831922297b5b655 Mon Sep 17 00:00:00 2001 From: Robert Bergers Date: Tue, 25 Dec 2018 15:50:49 -0500 Subject: [PATCH 0273/2908] Clean up (#488) * Cleaning up the README Spell-check, citing sources, standardizing the format * Cited missing source * cleaning up the readme * Update README.md * Update README.md * standardized spacing * README is all neat and tidy --- README.md | 144 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index b172fd04eaf4..f6b5a17bba84 100644 --- a/README.md +++ b/README.md @@ -10,89 +10,97 @@ These implementations are for demonstration purposes. They are less efficient th ### Bubble Sort ![alt text][bubble-image] -From [Wikipedia][bubble-wiki]: **Bubble sort**, sometimes referred to as *sinking sort*, is a simple sorting algorithm that repeatedly steps through the list, compares adjacent pairs 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. +**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 Sort +### Bucket ![alt text][bucket-image-1] ![alt text][bucket-image-2] -From [Wikipedia][bucket-wiki]: Bucket sort, or bin sort, is a sorting algorithm that distributes 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. +**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] -From [Wikipedia][cocktail-shaker-wiki]: 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. +**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] -From [Wikipedia][insertion-wiki]: **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. +**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] -From [Wikipedia][merge-wiki]: **Merge sort** (also commonly spelled *mergesort*) is an efficient, general-purpose, comparison-based sorting algorithm. Most implementations produce a stable sort, which means the order of equal items is the same in the input and output. Mergesort is a divide and conquer algorithm that was invented by John von Neumann in 1945. +**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 Sort +### Quick ![alt text][quick-image] -From [Wikipedia][quick-wiki]: **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. +**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 -From [Wikipedia](https://en.wikipedia.org/wiki/Heapsort): 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 +**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. @@ -102,33 +110,40 @@ __Properties__ * Best case performance O(wn) * Average case performance O(wn) +###### Source: [Wikipedia][radix-wiki] + + ### Selection ![alt text][selection-image] -From [Wikipedia][selection-wiki]: **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. +**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] -From [Wikipedia][shell-wiki]: **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. +**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]: A 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. +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 @@ -136,9 +151,9 @@ Comparing the complexity of sorting algorithms (*Bubble Sort*, *Insertion Sort*, ![Complexity Graphs](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png) -Selecting a sort technique: Quicksort is a very fast algorithm but can be pretty tricky to implement while bubble sort is a slow algorithm which is very easy to implement. For a small datasets 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. - - +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. ---------------------------------------------------------------------------------- @@ -147,7 +162,7 @@ Selecting a sort technique: Quicksort is a very fast algorithm but can be pretty ### Linear ![alt text][linear-image] -From [Wikipedia][linear-wiki]: **Linear search** or *sequential search* is a method for finding an element in a list. It sequentially checks each element of the list until a match is found or all the elements have been searched. +**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) @@ -155,10 +170,13 @@ __Properties__ * Average case performance O(n) * Worst case space complexity O(1) iterative +###### Source: [Wikipedia][linear-wiki] + + ### Binary ![alt text][binary-image] -From [Wikipedia][binary-wiki]: **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. +**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) @@ -166,88 +184,114 @@ __Properties__ * 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. +**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.[1] 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](https://en.wikipedia.org/wiki/Interpolation_search) + +###### Source: [Wikipedia][interpolation-wiki] + ## Jump Search -![alt text][JumpSearch-image] -In computer science, a jump search or block search refers to a search algorithm for ordered lists. It works by first checking all items Lkm, where ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/2a5bc4b7383031ba693b7433198ead7170954c1d) 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]. +**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](https://en.wikipedia.org/wiki/Jump_search) +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] -In computer science, quickselect 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. 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. + +**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](https://en.wikipedia.org/wiki/Quickselect) + +###### Source: [Wikipedia][quick-wiki] + ## Tabu -Tabu search uses a local or neighborhood search procedure to iteratively move from one potential solution ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/87f9e315fd7e2ba406057a97300593c4802b53e4) to an improved solution ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/0ac74959896052e160a5953102e4bc3850fe93b2) in the neighborhood of ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/87f9e315fd7e2ba406057a97300593c4802b53e4), 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, ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/4db1b4a2cfa6f356afe0738e999f0af2bed27f45), are determined through the use of memory structures. Using these memory structures, the search progresses by iteratively moving from the current solution ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/87f9e315fd7e2ba406057a97300593c4802b53e4) to an improved solution ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/0ac74959896052e160a5953102e4bc3850fe93b2) in ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/4db1b4a2cfa6f356afe0738e999f0af2bed27f45). +**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] -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 ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/4db1b4a2cfa6f356afe0738e999f0af2bed27f45) 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 ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/a601995d55609f2d9f5e233e36fbe9ea26011b3b) iterations ago, where ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/a601995d55609f2d9f5e233e36fbe9ea26011b3b) 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](https://en.wikipedia.org/wiki/Tabu_search) ---------------------------------------------------------------------------------------------------------------------- ## 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.
    +![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 -The **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**.
    + +**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 -In cryptography, a **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).
    +**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. +**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. -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. 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. +**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 -In cryptography, the simple XOR cipher is a type of additive cipher, an encryption algorithm that operates according to the principles: +**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. -A ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) 0 = A, -A ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) A = 0, -(A ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) B) ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) C = A ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) (B ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) C), -(B ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) A) ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) A = B ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) 0 = B, -where ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9eed86d866e6eba3ac47c710f60) denotes the exclusive disjunction (XOR) operation. This operation is sometimes called modulus 2 addition (or subtraction, which is identical). 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" @@ -267,6 +311,8 @@ where ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9 [quick-wiki]: https://en.wikipedia.org/wiki/Quicksort [quick-image]: https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif "Quick 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 @@ -290,7 +336,13 @@ where ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/8b16e2bdaefee9 [binary-image]: https://upload.wikimedia.org/wikipedia/commons/f/f7/Binary_search_into_array.png "Binary Search" -[caesar]: https://upload.wikimedia.org/wikipedia/commons/4/4a/Caesar_cipher_left_shift_of_3.svg "Caesar" +[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" From ad0bc2bb8b0a6f4d947998b1c428c336fa1b8a30 Mon Sep 17 00:00:00 2001 From: raksa Date: Wed, 26 Dec 2018 20:06:34 +0700 Subject: [PATCH 0274/2908] make images' path specific (#671) fixed wrong image's path while debuggin in VSCode --- analysis/compression_analysis/psnr.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/analysis/compression_analysis/psnr.py b/analysis/compression_analysis/psnr.py index abc98fbfeb2e..0f21aac07d34 100644 --- a/analysis/compression_analysis/psnr.py +++ b/analysis/compression_analysis/psnr.py @@ -4,6 +4,7 @@ """ import math +import os import cv2 import numpy as np @@ -18,18 +19,18 @@ 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('original_image.png') - contrast = cv2.imread('compressed_image.png', 1) + original = cv2.imread(os.path.join(dir_path, 'original_image.png')) + contrast = cv2.imread(os.path.join(dir_path, 'compressed_image.png'), 1) - original2 = cv2.imread('PSNR-example-base.png') - contrast2 = cv2.imread('PSNR-example-comp-10.jpg', 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) # Value expected: 29.73dB print("-- First Test --") print(f"PSNR value is {psnr(original, contrast)} dB") - + # # Value expected: 31.53dB (Wikipedia Example) print("\n-- Second Test --") print(f"PSNR value is {psnr(original2, contrast2)} dB") From f4806eb48cabe811ebc48117f787b55829108739 Mon Sep 17 00:00:00 2001 From: Jitendra_Sharma Date: Wed, 2 Jan 2019 16:47:56 +0530 Subject: [PATCH 0275/2908] manacher's algorithm to find palindromic string (#676) manacher's algorithm to find palindromic string in linear time complexity --- strings/manacher.py | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 strings/manacher.py diff --git a/strings/manacher.py b/strings/manacher.py new file mode 100644 index 000000000000..9a44b19ba77a --- /dev/null +++ b/strings/manacher.py @@ -0,0 +1,52 @@ +# calculate palindromic length from center with incresmenting 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. + + 1. first this conver 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 "|" + """ + 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] : + new_input_string += i + "|" + #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)) : + + # get palindromic length from ith position + length = palindromic_length(i, 1, new_input_string) + + # update max_length and start position + 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] : + if i != "|": + output_string += i + + return output_string + + +if __name__ == '__main__': + n = input() + print(palindromic_string(n)) From 61285333192d6ac38b0caf97d00b77bf9b635a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Sun, 6 Jan 2019 02:12:17 +0100 Subject: [PATCH 0276/2908] Fix use of deprecated assertEquals() in tests (#680) --- searches/test_tabu_search.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/searches/test_tabu_search.py b/searches/test_tabu_search.py index 95d2fea407ce..e6f73e6a9002 100644 --- a/searches/test_tabu_search.py +++ b/searches/test_tabu_search.py @@ -26,21 +26,21 @@ class TestClass(unittest.TestCase): def test_generate_neighbours(self): neighbours = generate_neighbours(TEST_FILE) - self.assertEquals(NEIGHBOURS_DICT, neighbours) + self.assertEqual(NEIGHBOURS_DICT, neighbours) def test_generate_first_solutions(self): first_solution, distance = generate_first_solution(TEST_FILE, NEIGHBOURS_DICT) - self.assertEquals(FIRST_SOLUTION, first_solution) - self.assertEquals(DISTANCE, distance) + 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.assertEquals(NEIGHBOURHOOD_OF_SOLUTIONS, neighbour_of_solutions) + 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.assertEquals(['a', 'd', 'b', 'e', 'c', 'a'], best_sol) - self.assertEquals(87, best_cost) + self.assertEqual(['a', 'd', 'b', 'e', 'c', 'a'], best_sol) + self.assertEqual(87, best_cost) From 3dc50529ca1192c59050e63ef3b432630d993a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Tue, 8 Jan 2019 09:58:47 +0100 Subject: [PATCH 0277/2908] Fix DeprecationWarning: invalid escape sequence (#679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mickaël Schoentgen --- ciphers/affine_cipher.py | 2 +- ciphers/rsa_cipher.py | 2 +- data_structures/binary tree/AVLtree.py | 4 ++-- data_structures/binary tree/binary_search_tree.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index 4fbe87d42e61..af5f4e0ff4c6 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -1,7 +1,7 @@ from __future__ import print_function import sys, random, cryptomath_module as cryptoMath -SYMBOLS = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" +SYMBOLS = r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" def main(): message = input('Enter message: ') diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 94f69ddc2533..3f8fb96e1ef4 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -6,7 +6,7 @@ def main(): filename = 'encrypted_file.txt' - response = input('Encrypte\Decrypt [e\d]: ') + response = input(r'Encrypte\Decrypt [e\d]: ') if response.lower().startswith('e'): mode = 'encrypt' diff --git a/data_structures/binary tree/AVLtree.py b/data_structures/binary tree/AVLtree.py index cb27004b6ac3..ff44963d1690 100644 --- a/data_structures/binary tree/AVLtree.py +++ b/data_structures/binary tree/AVLtree.py @@ -65,7 +65,7 @@ def my_max(a,b): def leftrotation(node): - ''' + r''' A B / \ / \ B C Bl A @@ -101,7 +101,7 @@ def rightrotation(node): return ret def rlrotation(node): - ''' + r''' A A Br / \ / \ / \ B C RR Br C LR B A diff --git a/data_structures/binary tree/binary_search_tree.py b/data_structures/binary tree/binary_search_tree.py index b4021e4f861f..cef5b55f245d 100644 --- a/data_structures/binary tree/binary_search_tree.py +++ b/data_structures/binary tree/binary_search_tree.py @@ -195,7 +195,7 @@ def InPreOrder(curr_node): return nodeList def testBinarySearchTree(): - ''' + r''' Example 8 / \ @@ -206,7 +206,7 @@ def testBinarySearchTree(): 4 7 13 ''' - ''' + r''' Example After Deletion 7 / \ From 2d70e9f7475d1a23b89fd1bd7b0af01f3173b0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Tue, 8 Jan 2019 09:59:23 +0100 Subject: [PATCH 0278/2908] Fix ResourceWarning: unclosed file (#681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mickaël Schoentgen --- ciphers/rsa_cipher.py | 14 ++-- ...ansposition_cipher_encrypt_decrypt_file.py | 11 +-- file_transfer_protocol/ftp_client_server.py | 13 ++- file_transfer_protocol/ftp_send_receive.py | 8 +- hashes/sha1.py | 3 +- other/anagrams.py | 3 +- other/detecting_english_programmatically.py | 7 +- searches/tabu_search.py | 34 ++++---- sorts/external-sort.py | 38 +++++---- strings/min_cost_string_conversion.py | 79 +++++++++---------- 10 files changed, 102 insertions(+), 108 deletions(-) diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 3f8fb96e1ef4..d81f1ffc1a1e 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -80,9 +80,8 @@ def decryptMessage(encryptedBlocks, messageLength, key, blockSize=DEFAULT_BLOCK_ def readKeyFile(keyFilename): - fo = open(keyFilename) - content = fo.read() - fo.close() + with open(keyFilename) as fo: + content = fo.read() keySize, n, EorD = content.split(',') return (int(keySize), int(n), int(EorD)) @@ -98,16 +97,15 @@ def encryptAndWriteToFile(messageFilename, keyFilename, message, blockSize=DEFAU encryptedBlocks[i] = str(encryptedBlocks[i]) encryptedContent = ','.join(encryptedBlocks) encryptedContent = '%s_%s_%s' % (len(message), blockSize, encryptedContent) - fo = open(messageFilename, 'w') - fo.write(encryptedContent) - fo.close() + with open(messageFilename, 'w') as fo: + fo.write(encryptedContent) return encryptedContent def readFromFileAndDecrypt(messageFilename, keyFilename): keySize, n, d = readKeyFile(keyFilename) - fo = open(messageFilename) - content = fo.read() + with open(messageFilename) as fo: + content = fo.read() messageLength, blockSize, encryptedMessage = content.split('_') messageLength = int(messageLength) blockSize = int(blockSize) diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index 57620d83948c..a186cf81cde7 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -19,15 +19,16 @@ def main(): startTime = time.time() if mode.lower().startswith('e'): - content = open(inputFile).read() + with open(inputFile) as f: + content = f.read() translated = transCipher.encryptMessage(key, content) elif mode.lower().startswith('d'): - content = open(outputFile).read() + with open(outputFile) as f: + content = f.read() translated =transCipher .decryptMessage(key, content) - outputObj = open(outputFile, 'w') - outputObj.write(translated) - outputObj.close() + with open(outputFile, 'w') as outputObj: + outputObj.write(translated) totalTime = round(time.time() - startTime, 2) print(('Done (', totalTime, 'seconds )')) diff --git a/file_transfer_protocol/ftp_client_server.py b/file_transfer_protocol/ftp_client_server.py index f73f858431f3..414c336dee9f 100644 --- a/file_transfer_protocol/ftp_client_server.py +++ b/file_transfer_protocol/ftp_client_server.py @@ -17,13 +17,12 @@ print('Server received', repr(data)) filename = 'mytext.txt' - f = open(filename, 'rb') - in_data = f.read(1024) - while (in_data): - conn.send(in_data) - print('Sent ', repr(in_data)) - in_data = f.read(1024) - f.close() + 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') diff --git a/file_transfer_protocol/ftp_send_receive.py b/file_transfer_protocol/ftp_send_receive.py index d4919158a02e..6050c83f2253 100644 --- a/file_transfer_protocol/ftp_send_receive.py +++ b/file_transfer_protocol/ftp_send_receive.py @@ -20,10 +20,9 @@ def ReceiveFile(): FileName = 'example.txt' """ Enter the location of the file """ - LocalFile = open(FileName, 'wb') - ftp.retrbinary('RETR ' + FileName, LocalFile.write, 1024) + with open(FileName, 'wb') as LocalFile: + ftp.retrbinary('RETR ' + FileName, LocalFile.write, 1024) ftp.quit() - LocalFile.close() """ The file which will be sent via the FTP server @@ -32,5 +31,6 @@ def ReceiveFile(): def SendFile(): FileName = 'example.txt' """ Enter the name of the file """ - ftp.storbinary('STOR ' + FileName, open(FileName, 'rb')) + with open(FileName, 'rb') as LocalFile: + ftp.storbinary('STOR ' + FileName, LocalFile) ftp.quit() diff --git a/hashes/sha1.py b/hashes/sha1.py index e34f87a76386..4c78ad3a89e5 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -137,7 +137,8 @@ def main(): input_string = args.input_string #In any case hash input should be a bytestring if args.input_file: - hash_input = open(args.input_file, 'rb').read() + with open(args.input_file, 'rb') as f: + hash_input = f.read() else: hash_input = bytes(input_string, 'utf-8') print(SHA1Hash(hash_input).final_hash()) diff --git a/other/anagrams.py b/other/anagrams.py index 44cd96b75f62..29b34fbdc5d3 100644 --- a/other/anagrams.py +++ b/other/anagrams.py @@ -4,7 +4,8 @@ start_time = time.time() print('creating word list...') path = os.path.split(os.path.realpath(__file__)) -word_list = sorted(list(set([word.strip().lower() for word in open(path[0] + '/words')]))) +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)) diff --git a/other/detecting_english_programmatically.py b/other/detecting_english_programmatically.py index 305174e31cf7..005fd3c10ca3 100644 --- a/other/detecting_english_programmatically.py +++ b/other/detecting_english_programmatically.py @@ -5,11 +5,10 @@ def loadDictionary(): path = os.path.split(os.path.realpath(__file__)) - dictionaryFile = open(path[0] + '/Dictionary.txt') englishWords = {} - for word in dictionaryFile.read().split('\n'): - englishWords[word] = None - dictionaryFile.close() + with open(path[0] + '/Dictionary.txt') as dictionaryFile: + for word in dictionaryFile.read().split('\n'): + englishWords[word] = None return englishWords ENGLISH_WORDS = loadDictionary() diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 74c23f8b8cf1..e21ddd53cc78 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -45,24 +45,23 @@ def generate_neighbours(path): the node 'c' with distance 18, the node 'd' with distance 22 and the node 'e' with distance 26. """ - f = open(path, "r") dict_of_neighbours = {} - for line in f: - if line.split()[0] not in dict_of_neighbours: - _list = list() - _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]]) - 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]]) - f.close() + with open(path) as f: + for line in f: + if line.split()[0] not in dict_of_neighbours: + _list = list() + _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]]) + 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]]) return dict_of_neighbours @@ -84,8 +83,8 @@ def generate_first_solution(path, dict_of_neighbours): """ - f = open(path, "r") - start_node = f.read(1) + with open(path) as f: + start_node = f.read(1) end_node = start_node first_solution = [] @@ -93,7 +92,6 @@ def generate_first_solution(path, dict_of_neighbours): visiting = start_node distance_of_first_solution = 0 - f.close() while visiting not in first_solution: minim = 10000 for k in dict_of_neighbours[visiting]: diff --git a/sorts/external-sort.py b/sorts/external-sort.py index dece32d48da0..1638e9efafee 100644 --- a/sorts/external-sort.py +++ b/sorts/external-sort.py @@ -15,31 +15,29 @@ def __init__(self, filename): def write_block(self, data, block_number): filename = self.BLOCK_FILENAME_FORMAT.format(block_number) - file = open(filename, 'w') - file.write(data) - file.close() + with open(filename, 'w') as file: + file.write(data) self.block_filenames.append(filename) def get_block_filenames(self): return self.block_filenames def split(self, block_size, sort_key=None): - file = open(self.filename, 'r') i = 0 + with open(self.filename) as file: + while True: + lines = file.readlines(block_size) - while True: - lines = file.readlines(block_size) + if lines == []: + break - if lines == []: - break + if sort_key is None: + lines.sort() + else: + lines.sort(key=sort_key) - if sort_key is None: - lines.sort() - else: - lines.sort(key=sort_key) - - self.write_block(''.join(lines), i) - i += 1 + self.write_block(''.join(lines), i) + i += 1 def cleanup(self): map(lambda f: os.remove(f), self.block_filenames) @@ -74,6 +72,7 @@ def refresh(self): if self.buffers[i] == '': self.empty.add(i) + self.files[i].close() if len(self.empty) == self.num_buffers: return False @@ -92,12 +91,11 @@ def __init__(self, merge_strategy): self.merge_strategy = merge_strategy def merge(self, filenames, outfilename, buffer_size): - outfile = open(outfilename, 'w', buffer_size) buffers = FilesArray(self.get_file_handles(filenames, buffer_size)) - - while buffers.refresh(): - min_index = self.merge_strategy.select(buffers.get_dict()) - outfile.write(buffers.unshift(min_index)) + 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)) def get_file_handles(self, filenames, buffer_size): files = {} diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index c233af1c81b7..de7f9f727283 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -73,50 +73,49 @@ def assemble_transformation(ops, i, j): m = len(operations) n = len(operations[0]) sequence = assemble_transformation(operations, m-1, n-1) - - file = open('min_cost.txt', 'w') string = list('Python') i = 0 cost = 0 - 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]) + + 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 + file.write('%-16s' % 'Insert %c' % op[1]) + file.write('\t\t\t' + ''.join(string)) + file.write('\r\n') + + cost += 2 - i += 1 + i += 1 - print(''.join(string)) - print('Cost: ', cost) - - file.write('\r\nMinimum cost: ' + str(cost)) - file.close() + print(''.join(string)) + print('Cost: ', cost) + + file.write('\r\nMinimum cost: ' + str(cost)) From ac281250605169a737bc9d4bddd057548ea0c45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Guimar=C3=A3es?= Date: Sat, 19 Jan 2019 00:50:59 -0300 Subject: [PATCH 0279/2908] Add median filter algorithm (#675) --- digital_image_processing/__init__.py | 0 digital_image_processing/filters/__init__.py | 0 .../filters/median_filter.py | 42 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 digital_image_processing/__init__.py create mode 100644 digital_image_processing/filters/__init__.py create mode 100644 digital_image_processing/filters/median_filter.py diff --git a/digital_image_processing/__init__.py b/digital_image_processing/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/filters/__init__.py b/digital_image_processing/filters/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/filters/median_filter.py b/digital_image_processing/filters/median_filter.py new file mode 100644 index 000000000000..eea4295632a1 --- /dev/null +++ b/digital_image_processing/filters/median_filter.py @@ -0,0 +1,42 @@ +""" +Implementation of median filter algorithm +""" + +from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey +from numpy import zeros_like, ravel, sort, multiply, divide, int8 + + +def median_filter(gray_img, mask=3): + """ + :param gray_img: gray image + :param mask: mask size + :return: image with median filter + """ + # set image borders + bd = int(mask / 2) + # copy image size + median_img = zeros_like(gray) + 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]) + # calculate mask median + median = sort(kernel)[int8(divide((multiply(mask, mask)), 2) + 1)] + median_img[i, j] = median + return median_img + + +if __name__ == '__main__': + # read original image + img = imread('lena.jpg') + # turn image in gray scale value + gray = cvtColor(img, COLOR_BGR2GRAY) + + # get values with two different mask size + median3x3 = median_filter(gray, 3) + median5x5 = median_filter(gray, 5) + + # show result images + imshow('median filter with 3x3 mask', median3x3) + imshow('median filter with 5x5 mask', median5x5) + waitKey(0) From c92b02cfa35b1e4cf76daf913d71b27394790ff8 Mon Sep 17 00:00:00 2001 From: Michael Fried Date: Sat, 19 Jan 2019 22:49:06 +0200 Subject: [PATCH 0280/2908] Editing base64, Adding average file, Editing find_lcm (#673) * avrage.py calculate and print the avrage of number list. * Update base64_cipher.py encoding and decoding base64 without any module. * Update and rename avrage.py to average.py * update find_lcm algorithm I made find_lcm more efficient form O(num1*num2) to O(min{num1,num2}). --- Maths/average.py | 14 +++++++++ Maths/find_lcm.py | 7 +++-- ciphers/base64_cipher.py | 65 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 Maths/average.py diff --git a/Maths/average.py b/Maths/average.py new file mode 100644 index 000000000000..dc70836b5e83 --- /dev/null +++ b/Maths/average.py @@ -0,0 +1,14 @@ +def average(nums): + sum = 0 + n = 0 + for x in nums: + sum += x + n += 1 + avg = sum / n + print(avg) + +def main(): + average([2, 4, 6, 8, 20, 50, 70]) + +if __name__ == '__main__': + main() diff --git a/Maths/find_lcm.py b/Maths/find_lcm.py index 58beb3e37649..126242699ab7 100644 --- a/Maths/find_lcm.py +++ b/Maths/find_lcm.py @@ -1,10 +1,11 @@ def find_lcm(num_1, num_2): max = num_1 if num_1 > num_2 else num_2 + lcm = max while (True): - if ((max % num_1 == 0) and (max % num_2 == 0)): + if ((lcm % num_1 == 0) and (lcm % num_2 == 0)): break - max += 1 - return max + lcm += max + return lcm def main(): diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py index 975f24d6bb5d..fa3451c0cbae 100644 --- a/ciphers/base64_cipher.py +++ b/ciphers/base64_cipher.py @@ -1,11 +1,64 @@ -import base64 +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 + 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]) + + n1 = (n >> 18) & 63 + n2 = (n >> 12) & 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 + +def decodeBase64(text): + base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + s = "" + + for i in text: + if i in base64chars: + s += i + c = "" + else: + if i == '=': + c += '=' + + p = "" + if c == "=": + 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]) + + r += chr((n >> 16) & 255) + chr((n >> 8) & 255) + chr(n & 255) + + i += 4 + + return r[0: len(r) - len(p)] def main(): - inp = input('->') - encoded = inp.encode('utf-8') #encoded the input (we need a bytes like object) - b64encoded = base64.b64encode(encoded) #b64encoded the encoded string - print(b64encoded) - print(base64.b64decode(b64encoded).decode('utf-8'))#decoded it + print(encodeBase64("WELCOME to base64 encoding")) + print(decodeBase64(encodeBase64("WELCOME to base64 encoding"))) + if __name__ == '__main__': main() From c6be53e1c43f870f5364eef1499ee1b411c966fb Mon Sep 17 00:00:00 2001 From: Rizwan Hasan Date: Sat, 26 Jan 2019 19:09:18 +0600 Subject: [PATCH 0281/2908] Rename Directed and Undirected (Weighted) Graph to Directed and Undirected (Weighted) Graph.py (#686) --- ...eighted) Graph => Directed and Undirected (Weighted) Graph.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename graphs/{Directed and Undirected (Weighted) Graph => Directed and Undirected (Weighted) Graph.py} (100%) diff --git a/graphs/Directed and Undirected (Weighted) Graph b/graphs/Directed and Undirected (Weighted) Graph.py similarity index 100% rename from graphs/Directed and Undirected (Weighted) Graph rename to graphs/Directed and Undirected (Weighted) Graph.py From faf16d7ced7f563bb0c92265bef27b6664156fb5 Mon Sep 17 00:00:00 2001 From: Gini5 Date: Sat, 9 Feb 2019 09:54:07 +0800 Subject: [PATCH 0282/2908] Add iteration version (#322) --- traversals/binary_tree_traversals.py | 58 +++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index cbcaf08b7b03..9afe0cac44c2 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -46,7 +46,6 @@ def build_tree(): node_found.right = right_node q.put(right_node) - def pre_order(node): if not isinstance(node, TreeNode) or not node: return @@ -54,7 +53,6 @@ def pre_order(node): pre_order(node.left) pre_order(node.right) - def in_order(node): if not isinstance(node, TreeNode) or not node: return @@ -84,6 +82,50 @@ def level_order(node): if node_dequeued.right: q.put(node_dequeued.right) +#iteration version +def pre_order_iter(node): + if not isinstance(node, TreeNode) or not node: + return + stack = [] + n = node + while n or stack: + while n: #start from root node, find its left child + print(n.data, end=" ") + stack.append(n) + n = n.left + #end of while means current node doesn't have left child + n = stack.pop() + #start to traverse its right child + n = n.right + +def in_order_iter(node): + if not isinstance(node, TreeNode) or not node: + return + stack = [] + n = node + while n or stack: + while n: + stack.append(n) + n = n.left + n = stack.pop() + print(n.data, end=" ") + n = n.right + +def post_order_iter(node): + if not isinstance(node, TreeNode) or not node: + return + stack1, stack2 = [], [] + n = node + stack1.append(n) + while stack1: #to find the reversed order of post order, store it in stack2 + n = stack1.pop() + if n.left: + stack1.append(n.left) + if n.right: + stack1.append(n.right) + stack2.append(n) + while stack2: #pop up from stack2 will be the post order + print(stack2.pop().data, end=" ") if __name__ == '__main__': print("\n********* Binary Tree Traversals ************\n") @@ -104,3 +146,15 @@ def level_order(node): print("\n********* Level Order Traversal ************") level_order(node) print("\n******************************************\n") + + print("\n********* Pre Order Traversal - Iteration Version ************") + pre_order_iter(node) + print("\n******************************************\n") + + print("\n********* In Order Traversal - Iteration Version ************") + in_order_iter(node) + print("\n******************************************\n") + + print("\n********* Post Order Traversal - Iteration Version ************") + post_order_iter(node) + print("\n******************************************\n") \ No newline at end of file From 17a6d1c1a7835cc3bf6adec7482d96a7ba391e9a Mon Sep 17 00:00:00 2001 From: Inno Fang Date: Sat, 9 Feb 2019 10:14:23 +0800 Subject: [PATCH 0283/2908] Fix sorts/radix_sort (#338) --- sorts/radix_sort.py | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index b0b4b41ab24f..e4cee61f35e3 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -1,28 +1,26 @@ def radixsort(lst): - RADIX = 10 - maxLength = False - tmp , placement = -1, 1 + RADIX = 10 + placement = 1 - while not maxLength: - maxLength = True - # declare and initialize buckets - buckets = [list() for _ in range( RADIX )] + # get the maximum number + max_digit = max(lst) - # split lst between lists - for i in lst: - tmp = int((i / placement) % RADIX) - buckets[tmp].append(i) + while placement < max_digit: + # declare and initialize buckets + buckets = [list() for _ in range( RADIX )] - if maxLength and tmp > 0: - maxLength = False + # 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 From dc302be505e2ddc3333066dcc50c161981d66cdf Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Sat, 9 Feb 2019 02:57:23 +0000 Subject: [PATCH 0284/2908] Create rod_cutting.py (#373) --- dynamic_programming/rod_cutting.py | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 dynamic_programming/rod_cutting.py diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py new file mode 100644 index 000000000000..34350cb8202b --- /dev/null +++ b/dynamic_programming/rod_cutting.py @@ -0,0 +1,58 @@ +### 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 4c14ad9dd65c5b68cae259c50543578492317c0f Mon Sep 17 00:00:00 2001 From: Anand Gaurav <32712456+anand31@users.noreply.github.com> Date: Sat, 9 Feb 2019 09:30:10 +0530 Subject: [PATCH 0285/2908] minor update hacktoberfest (#466) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6b5a17bba84..7cf1f4f64ffd 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ __Properties__ ## 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.[1] 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. +**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. From 8d4d95099f6715b42361dcb2f4913018aa830ad2 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sun, 10 Feb 2019 00:35:52 +0800 Subject: [PATCH 0286/2908] Project Euler problem 3 small fix (#631) --- project_euler/problem_03/sol2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_euler/problem_03/sol2.py b/project_euler/problem_03/sol2.py index cbef08e26126..44f9c63dfb6a 100644 --- a/project_euler/problem_03/sol2.py +++ b/project_euler/problem_03/sol2.py @@ -11,7 +11,7 @@ while(i*i<=n): while(n%i==0): prime=i - n/=i + n//=i i+=1 if(n>1): prime=n From 16e95a3de5f81bbc0ce9d8a96f42e1a67aa7187c Mon Sep 17 00:00:00 2001 From: Ethan Vieira Date: Sat, 9 Feb 2019 10:59:43 -0600 Subject: [PATCH 0287/2908] p2 sol2 fixed (#669) --- project_euler/problem_02/sol2.py | 19 +++++++++++-------- .../{Problem 31 => problem_31}/sol1.py | 0 2 files changed, 11 insertions(+), 8 deletions(-) rename project_euler/{Problem 31 => problem_31}/sol1.py (100%) diff --git a/project_euler/problem_02/sol2.py b/project_euler/problem_02/sol2.py index aa8dc7f76f3b..a2772697bb79 100644 --- a/project_euler/problem_02/sol2.py +++ b/project_euler/problem_02/sol2.py @@ -1,12 +1,15 @@ def fib(n): - a, b, s = 0, 1, 0 + """ + Returns a list of all the even terms in the Fibonacci sequence that are less than n. + """ + ls = [] + a, b = 0, 1 while b < n: - if b % 2 == 0 and b < n: s += b + if b % 2 == 0: + ls.append(b) a, b = b, a+b - ls.append(s) + return ls -T = int(input().strip()) -ls = [] -for _ in range(T): - fib(int(input().strip())) -print(ls, sep = '\n') +if __name__ == '__main__': + n = int(input("Enter max number: ").strip()) + print(sum(fib(n))) diff --git a/project_euler/Problem 31/sol1.py b/project_euler/problem_31/sol1.py similarity index 100% rename from project_euler/Problem 31/sol1.py rename to project_euler/problem_31/sol1.py From d689b4b083d98a8e3471ecb4067ad6502b2599a5 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sun, 10 Feb 2019 01:49:57 +0800 Subject: [PATCH 0288/2908] Project Euler problem 7 solution 3 (#642) --- project_euler/problem_07/sol3.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 project_euler/problem_07/sol3.py diff --git a/project_euler/problem_07/sol3.py b/project_euler/problem_07/sol3.py new file mode 100644 index 000000000000..0001e4318cc9 --- /dev/null +++ b/project_euler/problem_07/sol3.py @@ -0,0 +1,28 @@ +''' +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 Python.Math import PrimeCheck +import math +import itertools +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 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))) + + +if __name__ == '__main__': + main() \ No newline at end of file From dbe3f062ad66e1d8d05a3e7cb21a4a1b8165b181 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sun, 10 Feb 2019 02:00:05 +0800 Subject: [PATCH 0289/2908] Project Euler Problem 14 Solution 2 (#651) --- project_euler/problem_14/sol2.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 project_euler/problem_14/sol2.py diff --git a/project_euler/problem_14/sol2.py b/project_euler/problem_14/sol2.py new file mode 100644 index 000000000000..b9de42be1108 --- /dev/null +++ b/project_euler/problem_14/sol2.py @@ -0,0 +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. + 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 From 42d42c3136e7f3592380bfe7b4c4d99f053fdc29 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Mon, 11 Feb 2019 10:42:43 +0800 Subject: [PATCH 0290/2908] Project Euler problem 4 sol 2 small fix (#632) --- project_euler/problem_04/sol2.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/project_euler/problem_04/sol2.py b/project_euler/problem_04/sol2.py index 68284ed3435c..70810c38986f 100644 --- a/project_euler/problem_04/sol2.py +++ b/project_euler/problem_04/sol2.py @@ -4,16 +4,14 @@ Find the largest palindrome made from the product of two 3-digit numbers which is less than N. ''' from __future__ import print_function -arr = [] -for i in range(999,100,-1): - for j in range(999,100,-1): +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]: - arr.append(i*j) -arr.sort() + if t == t[::-1] and i*j < n: + answer = max(answer,i*j) +print(answer) +exit(0) + -n=int(input()) -for i in arr[::-1]: - if(i Date: Mon, 11 Feb 2019 09:52:14 +0000 Subject: [PATCH 0291/2908] Added BFS and DFS (graph algorithms) (#408) * feat: Add Breadth First Search (graph algorithm) * feat: Add Depth First Search (graph algorithm) --- Graphs/BFS.py | 39 +++++++++++++++++++++++++++++++++++++++ Graphs/DFS.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 Graphs/BFS.py create mode 100644 Graphs/DFS.py diff --git a/Graphs/BFS.py b/Graphs/BFS.py new file mode 100644 index 000000000000..bf9b572cec50 --- /dev/null +++ b/Graphs/BFS.py @@ -0,0 +1,39 @@ +"""pseudo-code""" + +""" +BFS(graph G, start vertex s): +// all nodes initially unexplored +mark s as explored +let Q = queue data structure, initialized with s +while Q is non-empty: + remove the first node of Q, call it v + for each edge(v, w): // for w in graph[v] + if w unexplored: + mark w as explored + add w to Q (at the end) + +""" + +import collections + + +def bfs(graph, start): + explored, queue = set(), [start] # collections.deque([start]) + explored.add(start) + while queue: + v = queue.pop(0) # queue.popleft() + for w in graph[v]: + if w not in explored: + explored.add(w) + queue.append(w) + 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')) diff --git a/Graphs/DFS.py b/Graphs/DFS.py new file mode 100644 index 000000000000..d3c34fabb7b3 --- /dev/null +++ b/Graphs/DFS.py @@ -0,0 +1,36 @@ +"""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] + explored.add(start) + while stack: + v = stack.pop() # the only 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'], + 'B': ['A', 'D', 'E'], + 'C': ['A', 'F'], + 'D': ['B'], + 'E': ['B', 'F'], + 'F': ['C', 'E']} + +print(dfs(G, 'A')) From a0d5c9aaf0f573ff11beacb6a30a91f90312dd08 Mon Sep 17 00:00:00 2001 From: Aruj Sharma Date: Mon, 11 Feb 2019 15:23:49 +0530 Subject: [PATCH 0292/2908] Create BitonicSort.py (#386) --- sorts/BitonicSort.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 sorts/BitonicSort.py diff --git a/sorts/BitonicSort.py b/sorts/BitonicSort.py new file mode 100644 index 000000000000..bae95b4346f6 --- /dev/null +++ b/sorts/BitonicSort.py @@ -0,0 +1,56 @@ +# 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 bitonicMerge(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) + + # This funcion 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): + if cnt > 1: + k = int(cnt / 2) + bitonicSort(a, low, k, 1) + bitonicSort(a, low + k, k, 0) + bitonicMerge(a, low, cnt, dire) + + # Caller of bitonicSort for sorting the entire array of length N + + +# in ASCENDING order +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]) From 02f850965d4f36c58b5cb52fc8aba41454cd2f12 Mon Sep 17 00:00:00 2001 From: P-Shreyas-Shetty Date: Mon, 11 Feb 2019 21:45:49 +0530 Subject: [PATCH 0293/2908] Implementation of Newton-Raphson method (#650) Implemented Newton-Raphson method using pure python. Third party library is used only for visualizing error variation with each iteration. --- maths/newton_raphson.py | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 maths/newton_raphson.py diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py new file mode 100644 index 000000000000..c08bcedc9a4d --- /dev/null +++ b/maths/newton_raphson.py @@ -0,0 +1,50 @@ +''' + 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 + x[n+1] = x[n] + f(x[n])/f'(x[n]) + 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) + +def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6,logsteps=False): + + 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) + for _ in range(maxiter): + if f1(a) == 0: + raise ValueError("No converging solution found") + a = a - f(a)/f1(a) #Calculate the next estimate + if logsteps: + steps.append(a) + error = abs(f(a)) + if error < maxerror: + break + else: + raise ValueError("Itheration limit reached, no converging solution found") + if logsteps: + #If logstep is true, then log intermediate steps + return a, error, steps + return a, error + +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) + plt.plot([abs(f(x)) for x in steps]) + plt.xlabel("step") + plt.ylabel("error") + plt.show() + print("solution = {%f}, error = {%f}" % (solution, error)) \ No newline at end of file From 02155def00f7bcc16e4c6df6b7891a90696e44fe Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Wed, 13 Feb 2019 00:40:05 +0800 Subject: [PATCH 0294/2908] Create project Euler problem 9 sol3.py (#645) --- project_euler/problem_09/sol3.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 project_euler/problem_09/sol3.py diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_09/sol3.py new file mode 100644 index 000000000000..5ebf38e76e1a --- /dev/null +++ b/project_euler/problem_09/sol3.py @@ -0,0 +1,6 @@ +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() From 60418a6fd7a2f35aef237279bc721e6470af936f Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Wed, 13 Feb 2019 14:55:48 +0800 Subject: [PATCH 0295/2908] Create project Euler problem 8 sol2.py (#644) intuitive solution using functional programming --- project_euler/problem_08/sol2.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 project_euler/problem_08/sol2.py diff --git a/project_euler/problem_08/sol2.py b/project_euler/problem_08/sol2.py new file mode 100644 index 000000000000..ae03f3ad0aa6 --- /dev/null +++ b/project_euler/problem_08/sol2.py @@ -0,0 +1,8 @@ +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() From 9417091dabf9d8e9ebec078942a2fbba0966e42f Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Wed, 13 Feb 2019 18:02:32 +0800 Subject: [PATCH 0296/2908] Update sol1.py (#643) small off by one error. Boundary condition: if len(number) =13 , we would need to check exactly 1 combination, namely number itself. However for i in range(len(number)-13): will iterate 0 times. --- project_euler/problem_08/sol1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_euler/problem_08/sol1.py b/project_euler/problem_08/sol1.py index fb5ed25bfe93..817fd3f87507 100644 --- a/project_euler/problem_08/sol1.py +++ b/project_euler/problem_08/sol1.py @@ -2,7 +2,7 @@ def main(): LargestProduct = -sys.maxsize-1 number=input().strip() - for i in range(len(number)-13): + for i in range(len(number)-12): product=1 for j in range(13): product *= int(number[i+j]) From 3c8036432c7d50c892d6463c8ba8470aa5222e4e Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 14 Feb 2019 12:08:21 +0800 Subject: [PATCH 0297/2908] Project Euler problem 6 solution 3 (#640) --- project_euler/problem_06/sol3.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 project_euler/problem_06/sol3.py diff --git a/project_euler/problem_06/sol3.py b/project_euler/problem_06/sol3.py new file mode 100644 index 000000000000..b2d9f444d9a9 --- /dev/null +++ b/project_euler/problem_06/sol3.py @@ -0,0 +1,20 @@ +''' +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. +''' +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)) + return square_of_sum - sum_of_squares +def main(): + print(problem6()) + +if __name__ == '__main__': + main() \ No newline at end of file From 301493094ef1638fae09c24a2fc4880812839e87 Mon Sep 17 00:00:00 2001 From: gpapadok <38889721+gpapadok@users.noreply.github.com> Date: Fri, 15 Feb 2019 18:01:10 +0200 Subject: [PATCH 0298/2908] Update absMax.py (#602) * Update absMax.py Fixed two bugs * changed to abs instead of absVal --- Maths/absMax.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Maths/absMax.py b/Maths/absMax.py index 2f719e32e645..7ff9e4d3ca09 100644 --- a/Maths/absMax.py +++ b/Maths/absMax.py @@ -1,5 +1,3 @@ -from Maths.abs import absVal - def absMax(x): """ #>>>absMax([0,5,1,11]) @@ -9,15 +7,15 @@ def absMax(x): """ j =x[0] for i in x: - if absVal(i) > absVal(j): + if abs(i) > abs(j): j = i return j - #BUG: i is apparently a list, TypeError: '<' not supported between instances of 'list' and 'int' in absVal - #BUG fix + def main(): - a = [-13, 2, -11, -12] - print(absMax(a)) # = -13 + a = [1,2,-11] + print(absMax(a)) # = -11 + if __name__ == '__main__': main() From 6f283336ae249b07f22b9e3c13af420177a98d18 Mon Sep 17 00:00:00 2001 From: Vivek Date: Sat, 16 Feb 2019 21:46:43 +0530 Subject: [PATCH 0299/2908] Adding function for actual level order traversal (#699) --- traversals/binary_tree_traversals.py | 48 ++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index 9afe0cac44c2..393664579146 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -2,10 +2,11 @@ This is pure python implementation of tree traversal algorithms """ from __future__ import print_function + import queue try: - raw_input # Python 2 + raw_input # Python 2 except NameError: raw_input = input # Python 3 @@ -22,7 +23,7 @@ def build_tree(): print("Enter the value of the root node: ", end="") check = raw_input().strip().lower() if check == 'n': - return None + return None data = int(check) q = queue.Queue() tree_node = TreeNode(data) @@ -46,6 +47,7 @@ def build_tree(): node_found.right = right_node q.put(right_node) + def pre_order(node): if not isinstance(node, TreeNode) or not node: return @@ -53,6 +55,7 @@ def pre_order(node): pre_order(node.left) pre_order(node.right) + def in_order(node): if not isinstance(node, TreeNode) or not node: return @@ -82,22 +85,43 @@ def level_order(node): if node_dequeued.right: q.put(node_dequeued.right) -#iteration version + +def level_order_actual(node): + if not isinstance(node, TreeNode) or not node: + return + q = queue.Queue() + q.put(node) + while not q.empty(): + list = [] + while not q.empty(): + node_dequeued = q.get() + print(node_dequeued.data, end=" ") + if node_dequeued.left: + list.append(node_dequeued.left) + if node_dequeued.right: + list.append(node_dequeued.right) + print() + for node in list: + q.put(node) + + +# iteration version def pre_order_iter(node): if not isinstance(node, TreeNode) or not node: return stack = [] n = node while n or stack: - while n: #start from root node, find its left child + while n: # start from root node, find its left child print(n.data, end=" ") stack.append(n) n = n.left - #end of while means current node doesn't have left child + # end of while means current node doesn't have left child n = stack.pop() - #start to traverse its right child + # start to traverse its right child n = n.right + def in_order_iter(node): if not isinstance(node, TreeNode) or not node: return @@ -111,22 +135,24 @@ def in_order_iter(node): print(n.data, end=" ") n = n.right + def post_order_iter(node): if not isinstance(node, TreeNode) or not node: return stack1, stack2 = [], [] n = node stack1.append(n) - while stack1: #to find the reversed order of post order, store it in stack2 + while stack1: # to find the reversed order of post order, store it in stack2 n = stack1.pop() if n.left: stack1.append(n.left) if n.right: stack1.append(n.right) stack2.append(n) - while stack2: #pop up from stack2 will be the post order + while stack2: # pop up from stack2 will be the post order print(stack2.pop().data, end=" ") + if __name__ == '__main__': print("\n********* Binary Tree Traversals ************\n") @@ -147,6 +173,10 @@ def post_order_iter(node): level_order(node) print("\n******************************************\n") + print("\n********* Actual Level Order Traversal ************") + level_order_actual(node) + print("\n******************************************\n") + print("\n********* Pre Order Traversal - Iteration Version ************") pre_order_iter(node) print("\n******************************************\n") @@ -157,4 +187,4 @@ def post_order_iter(node): print("\n********* Post Order Traversal - Iteration Version ************") post_order_iter(node) - print("\n******************************************\n") \ No newline at end of file + print("\n******************************************\n") From af08a67b20886ebd90de29c3efc4ccb11b667a4a Mon Sep 17 00:00:00 2001 From: Reshad Hasan Date: Mon, 18 Feb 2019 21:35:46 +0600 Subject: [PATCH 0300/2908] added the dijkstra algorithm (#704) --- graphs/dijkstra.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 graphs/dijkstra.py diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py new file mode 100644 index 000000000000..6b08b28fcfd3 --- /dev/null +++ b/graphs/dijkstra.py @@ -0,0 +1,47 @@ +"""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) +""" +import heapq + + +def dijkstra(graph, start, end): + heap = [(0, start)] # cost from start node,end node + visited = [] + while heap: + (cost, u) = heapq.heappop(heap) + if u in visited: + continue + visited.append(u) + if u == end: + return cost + for v, c in G[u]: + if v in visited: + continue + next = cost + c + heapq.heappush(heap, (next, v)) + return (-1, -1) + + +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(G, 'E', 'C') +print(shortDistance) From ad68eed73e2cf39ee347e36a4e0fdf54310bd79a Mon Sep 17 00:00:00 2001 From: Abhi Kampurath <35632653+abhikmp@users.noreply.github.com> Date: Tue, 19 Feb 2019 20:56:09 +0530 Subject: [PATCH 0301/2908] Update caesar_cipher.py (#702) --- ciphers/caesar_cipher.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 8012042fc6c4..39c069c95a7c 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -42,16 +42,16 @@ def main(): print("4.Quit") choice = input("What would you like to do?: ") if choice not in ['1', '2', '3', '4']: - print ("Invalid choice") + print ("Invalid choice, please enter a valid choice") elif choice == '1': - strng = input("Please enter the string to be ecrypted: ") + 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': strng = input("Please enter the string to be decrypted: ") key = int(input("Please enter off-set between 1-94: ")) - if key > 0 and key <= 94: + if key in range(1,95): print(decrypt(strng, key)) elif choice == '3': strng = input("Please enter the string to be decrypted: ") From 98a149e41eca20f4ee3a2c031ee04987f64e34eb Mon Sep 17 00:00:00 2001 From: pradyotd Date: Wed, 20 Feb 2019 11:54:26 -0500 Subject: [PATCH 0302/2908] Update coin_change.py (#706) Cleanup PEP-8 , additional comments , using coin_val, instead of range and index. --- dynamic_programming/coin_change.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dynamic_programming/coin_change.py b/dynamic_programming/coin_change.py index 0116df0c024e..74d86661f52d 100644 --- a/dynamic_programming/coin_change.py +++ b/dynamic_programming/coin_change.py @@ -6,21 +6,26 @@ https://www.hackerrank.com/challenges/coin-change/problem """ from __future__ import print_function + + def dp_count(S, m, n): + + # table[i] represents the number of ways to get to amount i table = [0] * (n + 1) - # Base case (If given value is 0) + # There is exactly 1 way to get to zero(You pick no coins). table[0] = 1 # Pick all coins one by one and update table[] values # after the index greater than or equal to the value of the # picked coin - for i in range(0, m): - for j in range(S[i], n + 1): - table[j] += table[j - S[i]] + for coin_val in S: + for j in range(coin_val, n + 1): + table[j] += table[j - coin_val] 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 From 74e94ab5e2846c32f6a0d1a9a00529c21e84a35a Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 21 Feb 2019 00:57:48 +0800 Subject: [PATCH 0303/2908] Create project Euler problem 25 sol2.py (#658) --- project_euler/problem_25/sol2.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 project_euler/problem_25/sol2.py diff --git a/project_euler/problem_25/sol2.py b/project_euler/problem_25/sol2.py new file mode 100644 index 000000000000..35147a9bfb14 --- /dev/null +++ b/project_euler/problem_25/sol2.py @@ -0,0 +1,10 @@ +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 From 2b27086c0068e7a8548236a408070683ed10ba1c Mon Sep 17 00:00:00 2001 From: Sravan Kumar Vinakota <35956127+JekyllAndHyde8999@users.noreply.github.com> Date: Thu, 21 Feb 2019 21:49:28 +0530 Subject: [PATCH 0304/2908] Added hill_cipher.py (#696) A python class that implements the Hill cipher algorithm. --- ciphers/hill_cipher.py | 167 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 ciphers/hill_cipher.py diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py new file mode 100644 index 000000000000..89b88beed17e --- /dev/null +++ b/ciphers/hill_cipher.py @@ -0,0 +1,167 @@ +""" + +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 encoding and decoding your text. + +Algortihm: +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 +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. + +While decrypting, the decrypting key is found which is the inverse of the +encrypting key modular 36. The same process is repeated for decrypting to get +the original message back. + +Constraints: +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. + +References: +https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf +https://www.youtube.com/watch?v=kfmNeskzs2o +https://www.youtube.com/watch?v=4RhLNDqcjpA + +""" + +import numpy + + +def gcd(a, b): + if a == 0: + return b + return gcd(b%a, a) + + +class HillCipher: + key_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + # 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) + + 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.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)) + + def processText(self, text): + text = list(text.upper()) + text = [char for char in text if char in self.key_string] + + last = text[-1] + while len(text) % self.break_key != 0: + text.append(last) + + return ''.join(text) + + def encrypt(self, text): + text = self.processText(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 = 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 += 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 + for i in range(len(self.key_string)): + if (det * i) % len(self.key_string) == 1: + det_inv = i + break + + 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 = '' + + 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 = 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 += decrypted_batch + + return decrypted + + +def main(): + N = int(input("Enter the order of the encryption key: ")) + hill_matrix = [] + + print("Enter each row of the encryption key with space separated integers") + for i in range(N): + row = list(map(int, input().split())) + hill_matrix.append(row) + + hc = HillCipher(numpy.matrix(hill_matrix)) + + print("Would you like to encrypt or decrypt some text? (1 or 2)") + option = input(""" +1. Encrypt +2. Decrypt +""" + ) + + 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': + text_d = input("What text would you like to decrypt?: ") + print("Your decrypted text is:") + print(hc.decrypt(text_d)) + + +if __name__ == "__main__": + main() From e6e5f4b301a82dfbb8245add087789019c516c9c Mon Sep 17 00:00:00 2001 From: Reshad Hasan Date: Sat, 23 Feb 2019 20:18:21 +0600 Subject: [PATCH 0305/2908] Added naive string search algorithm (#715) --- strings/naiveStringSearch.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 strings/naiveStringSearch.py diff --git a/strings/naiveStringSearch.py b/strings/naiveStringSearch.py new file mode 100644 index 000000000000..04c0d8157b24 --- /dev/null +++ b/strings/naiveStringSearch.py @@ -0,0 +1,29 @@ +""" +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 + +Complexity : O(n*m) + 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 + for j in range(patLen): + 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) +print("Pattern found in position ") +for x in position: + print(x) \ No newline at end of file From 9a44eb44798900e5d9b8a307a78ab8057f9e3b92 Mon Sep 17 00:00:00 2001 From: Reshad Hasan Date: Mon, 25 Feb 2019 15:35:24 +0600 Subject: [PATCH 0306/2908] Organize graph algorithms (#719) * organized graph algorithms * all graph algorithms in Graphs/ folder * all graph algorithms are in one folder * Rename number theory/factorial_python.py to maths/factorial_python.py --- {graphs => Graphs}/Directed and Undirected (Weighted) Graph.py | 0 {graphs => Graphs}/a_star.py | 0 {graphs => Graphs}/articulation_points.py | 0 {graphs => Graphs}/basic_graphs.py | 0 {data_structures/graph => Graphs}/bellman_ford.py | 0 {data_structures/graph => Graphs}/breadth_first_search.py | 0 {graphs => Graphs}/check_bipartite_graph_bfs.py | 0 {data_structures/graph => Graphs}/depth_first_search.py | 0 {graphs => Graphs}/dijkstra.py | 0 data_structures/graph/dijkstra.py => Graphs/dijkstra_2.py | 0 {data_structures/graph => Graphs}/dijkstra_algorithm.py | 0 {data_structures/graph => Graphs}/even_tree.py | 0 {graphs => Graphs}/finding_bridges.py | 0 {data_structures/graph => Graphs}/floyd_warshall.py | 0 {data_structures/graph => Graphs}/graph.py | 0 {data_structures/graph => Graphs}/graph_list.py | 0 {data_structures/graph => Graphs}/graph_matrix.py | 0 {graphs => Graphs}/kahns_algorithm_long.py | 0 {graphs => Graphs}/kahns_algorithm_topo.py | 0 {graphs => Graphs}/minimum_spanning_tree_kruskal.py | 0 {graphs => Graphs}/minimum_spanning_tree_prims.py | 0 {graphs => Graphs}/multi_hueristic_astar.py | 0 {graphs => Graphs}/scc_kosaraju.py | 0 {graphs => Graphs}/tarjans_scc.py | 0 factorial_python.py => maths/factorial_python.py | 0 25 files changed, 0 insertions(+), 0 deletions(-) rename {graphs => Graphs}/Directed and Undirected (Weighted) Graph.py (100%) rename {graphs => Graphs}/a_star.py (100%) rename {graphs => Graphs}/articulation_points.py (100%) rename {graphs => Graphs}/basic_graphs.py (100%) rename {data_structures/graph => Graphs}/bellman_ford.py (100%) rename {data_structures/graph => Graphs}/breadth_first_search.py (100%) rename {graphs => Graphs}/check_bipartite_graph_bfs.py (100%) rename {data_structures/graph => Graphs}/depth_first_search.py (100%) rename {graphs => Graphs}/dijkstra.py (100%) rename data_structures/graph/dijkstra.py => Graphs/dijkstra_2.py (100%) rename {data_structures/graph => Graphs}/dijkstra_algorithm.py (100%) rename {data_structures/graph => Graphs}/even_tree.py (100%) rename {graphs => Graphs}/finding_bridges.py (100%) rename {data_structures/graph => Graphs}/floyd_warshall.py (100%) rename {data_structures/graph => Graphs}/graph.py (100%) rename {data_structures/graph => Graphs}/graph_list.py (100%) rename {data_structures/graph => Graphs}/graph_matrix.py (100%) rename {graphs => Graphs}/kahns_algorithm_long.py (100%) rename {graphs => Graphs}/kahns_algorithm_topo.py (100%) rename {graphs => Graphs}/minimum_spanning_tree_kruskal.py (100%) rename {graphs => Graphs}/minimum_spanning_tree_prims.py (100%) rename {graphs => Graphs}/multi_hueristic_astar.py (100%) rename {graphs => Graphs}/scc_kosaraju.py (100%) rename {graphs => Graphs}/tarjans_scc.py (100%) rename factorial_python.py => maths/factorial_python.py (100%) 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/a_star.py b/Graphs/a_star.py similarity index 100% rename from graphs/a_star.py rename to Graphs/a_star.py diff --git a/graphs/articulation_points.py b/Graphs/articulation_points.py similarity index 100% rename from graphs/articulation_points.py rename to Graphs/articulation_points.py diff --git a/graphs/basic_graphs.py b/Graphs/basic_graphs.py similarity index 100% rename from graphs/basic_graphs.py rename to Graphs/basic_graphs.py diff --git a/data_structures/graph/bellman_ford.py b/Graphs/bellman_ford.py similarity index 100% rename from data_structures/graph/bellman_ford.py rename to Graphs/bellman_ford.py diff --git a/data_structures/graph/breadth_first_search.py b/Graphs/breadth_first_search.py similarity index 100% rename from data_structures/graph/breadth_first_search.py rename to Graphs/breadth_first_search.py diff --git a/graphs/check_bipartite_graph_bfs.py b/Graphs/check_bipartite_graph_bfs.py similarity index 100% rename from graphs/check_bipartite_graph_bfs.py rename to Graphs/check_bipartite_graph_bfs.py diff --git a/data_structures/graph/depth_first_search.py b/Graphs/depth_first_search.py similarity index 100% rename from data_structures/graph/depth_first_search.py rename to Graphs/depth_first_search.py diff --git a/graphs/dijkstra.py b/Graphs/dijkstra.py similarity index 100% rename from graphs/dijkstra.py rename to Graphs/dijkstra.py diff --git a/data_structures/graph/dijkstra.py b/Graphs/dijkstra_2.py similarity index 100% rename from data_structures/graph/dijkstra.py rename to Graphs/dijkstra_2.py diff --git a/data_structures/graph/dijkstra_algorithm.py b/Graphs/dijkstra_algorithm.py similarity index 100% rename from data_structures/graph/dijkstra_algorithm.py rename to Graphs/dijkstra_algorithm.py diff --git a/data_structures/graph/even_tree.py b/Graphs/even_tree.py similarity index 100% rename from data_structures/graph/even_tree.py rename to Graphs/even_tree.py diff --git a/graphs/finding_bridges.py b/Graphs/finding_bridges.py similarity index 100% rename from graphs/finding_bridges.py rename to Graphs/finding_bridges.py diff --git a/data_structures/graph/floyd_warshall.py b/Graphs/floyd_warshall.py similarity index 100% rename from data_structures/graph/floyd_warshall.py rename to Graphs/floyd_warshall.py diff --git a/data_structures/graph/graph.py b/Graphs/graph.py similarity index 100% rename from data_structures/graph/graph.py rename to Graphs/graph.py diff --git a/data_structures/graph/graph_list.py b/Graphs/graph_list.py similarity index 100% rename from data_structures/graph/graph_list.py rename to Graphs/graph_list.py diff --git a/data_structures/graph/graph_matrix.py b/Graphs/graph_matrix.py similarity index 100% rename from data_structures/graph/graph_matrix.py rename to Graphs/graph_matrix.py diff --git a/graphs/kahns_algorithm_long.py b/Graphs/kahns_algorithm_long.py similarity index 100% rename from graphs/kahns_algorithm_long.py rename to Graphs/kahns_algorithm_long.py diff --git a/graphs/kahns_algorithm_topo.py b/Graphs/kahns_algorithm_topo.py similarity index 100% rename from graphs/kahns_algorithm_topo.py rename to Graphs/kahns_algorithm_topo.py diff --git a/graphs/minimum_spanning_tree_kruskal.py b/Graphs/minimum_spanning_tree_kruskal.py similarity index 100% rename from graphs/minimum_spanning_tree_kruskal.py rename to Graphs/minimum_spanning_tree_kruskal.py diff --git a/graphs/minimum_spanning_tree_prims.py b/Graphs/minimum_spanning_tree_prims.py similarity index 100% rename from graphs/minimum_spanning_tree_prims.py rename to Graphs/minimum_spanning_tree_prims.py diff --git a/graphs/multi_hueristic_astar.py b/Graphs/multi_hueristic_astar.py similarity index 100% rename from graphs/multi_hueristic_astar.py rename to Graphs/multi_hueristic_astar.py diff --git a/graphs/scc_kosaraju.py b/Graphs/scc_kosaraju.py similarity index 100% rename from graphs/scc_kosaraju.py rename to Graphs/scc_kosaraju.py diff --git a/graphs/tarjans_scc.py b/Graphs/tarjans_scc.py similarity index 100% rename from graphs/tarjans_scc.py rename to Graphs/tarjans_scc.py diff --git a/factorial_python.py b/maths/factorial_python.py similarity index 100% rename from factorial_python.py rename to maths/factorial_python.py From 2bbf8cd10935c620400ba213bba4b92c8b2cb5c0 Mon Sep 17 00:00:00 2001 From: "S. Sharma" <36388139+s1l3ntcat@users.noreply.github.com> Date: Wed, 27 Feb 2019 08:28:59 -0600 Subject: [PATCH 0307/2908] Added extended euclidean algorithm (#720) * Added extended euclidean algorithm * Fixed extended euclidean algorithm --- Maths/extended_euclidean_algorithm.py | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Maths/extended_euclidean_algorithm.py diff --git a/Maths/extended_euclidean_algorithm.py b/Maths/extended_euclidean_algorithm.py new file mode 100644 index 000000000000..f5a3cc88e474 --- /dev/null +++ b/Maths/extended_euclidean_algorithm.py @@ -0,0 +1,51 @@ +# @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 + +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 + 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 = aprime + aprime = a + a = t - q*a + + t = bprime + bprime = b + b = t - q*b + + pair = None + if m > n: + pair = (a,b) + else: + pair = (b,a) + return pair + +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)) + +if __name__ == '__main__': + main() From dd9f0b3f2e859d24a2dd442fe2bd45fd08a9ac86 Mon Sep 17 00:00:00 2001 From: overclockedllama Date: Wed, 27 Feb 2019 15:33:29 +0100 Subject: [PATCH 0308/2908] fix comma spelling from coma to comma (#722) --- 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 50c6eaad5e9b..058322f21d09 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -43,7 +43,7 @@ def linear_search(sequence, target): if __name__ == '__main__': - user_input = raw_input('Enter numbers separated by coma:\n').strip() + user_input = raw_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') From e6eb6dbb82623c8377230419a8633c3b2a0b3b63 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 28 Feb 2019 01:49:13 +0800 Subject: [PATCH 0309/2908] Delete Maths/find_hcf.py (#636) --- Maths/find_hcf.py | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 Maths/find_hcf.py diff --git a/Maths/find_hcf.py b/Maths/find_hcf.py deleted file mode 100644 index e4315d8d37a7..000000000000 --- a/Maths/find_hcf.py +++ /dev/null @@ -1,22 +0,0 @@ -# Program to find the HCF of two Numbers -def find_hcf(num_1, num_2): - if num_1 == 0: - return num_2 - if num_2 == 0: - return num_1 - # Base Case - if num_1 == num_2: - return num_1 - if num_1 > num_2: - return find_hcf(num_1 - num_2, num_2) - return find_hcf(num_1, num_2 - num_1) - - -def main(): - num_1 = 24 - num_2 = 34 - print('HCF of %s and %s is %s:' % (num_1, num_2, find_hcf(num_1, num_2))) - - -if __name__ == '__main__': - main() From 88b6caa30aeb2d0e780039c13703af56c8ffdaa7 Mon Sep 17 00:00:00 2001 From: Ashwek Swamy <39827514+ashwek@users.noreply.github.com> Date: Fri, 1 Mar 2019 22:23:29 +0530 Subject: [PATCH 0310/2908] fixed balanced_parentheses, Added infix-prefix & postfix evaluation (#621) * Create infix_to_prefix_conversion.py * Create postfix_evaluation.py * Update balanced_parentheses.py --- .../stacks/infix_to_prefix_conversion.py | 61 +++++++++++++++++++ data_structures/stacks/postfix_evaluation.py | 50 +++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 data_structures/stacks/infix_to_prefix_conversion.py create mode 100644 data_structures/stacks/postfix_evaluation.py diff --git a/data_structures/stacks/infix_to_prefix_conversion.py b/data_structures/stacks/infix_to_prefix_conversion.py new file mode 100644 index 000000000000..da5fc261fb9f --- /dev/null +++ b/data_structures/stacks/infix_to_prefix_conversion.py @@ -0,0 +1,61 @@ +""" +Output: + +Enter an Infix Equation = a + b ^c + Symbol | Stack | Postfix +---------------------------- + c | | c + ^ | ^ | c + b | ^ | cb + + | + | cb^ + a | + | cb^a + | | cb^a+ + + 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 + + # Print table header for output + 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 + Stack.pop() + 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 + 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 + + 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 + +def infix_2_prefix(Infix): + 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 __name__ == "__main__": + 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/postfix_evaluation.py b/data_structures/stacks/postfix_evaluation.py new file mode 100644 index 000000000000..1786e71dd383 --- /dev/null +++ b/data_structures/stacks/postfix_evaluation.py @@ -0,0 +1,50 @@ +""" +Output: + +Enter a Postfix Equation (space separated) = 5 6 9 * + + Symbol | Action | Stack +----------------------------------- + 5 | push(5) | 5 + 6 | push(6) | 5,6 + 9 | push(9) | 5,6,9 + | pop(9) | 5,6 + | pop(6) | 5 + * | push(6*9) | 5,54 + | pop(54) | 5 + | pop(5) | + + | push(5+54) | 59 + + Result = 59 +""" + +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 + + # print table header + 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 + else: + 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 + + 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(' ') + print("\n\tResult = ", Solve(Postfix)) From 6f6510623c7250ebea78afbd3d6eab1bfe467ada Mon Sep 17 00:00:00 2001 From: Akash Ali <45498607+AkashAli506@users.noreply.github.com> Date: Mon, 4 Mar 2019 00:49:36 -0800 Subject: [PATCH 0311/2908] Update heap.py (#726) Added comments for the better understanding of heap. --- data_structures/heap/heap.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 8187af101308..39778f725c3a 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -7,8 +7,9 @@ except NameError: raw_input = input # Python 3 +#This heap class start from here. class Heap: - def __init__(self): + def __init__(self): #Default constructor of heap class. self.h = [] self.currsize = 0 @@ -37,13 +38,13 @@ def maxHeapify(self,node): self.h[m] = temp self.maxHeapify(m) - def buildHeap(self,a): + 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): + 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] @@ -54,7 +55,7 @@ def getMax(self): return me return None - def heapSort(self): + def heapSort(self): #This function is used to sort the heap. size = self.currsize while self.currsize-1 >= 0: temp = self.h[0] @@ -64,7 +65,7 @@ def heapSort(self): self.maxHeapify(0) self.currsize = size - def insert(self,data): + def insert(self,data): #This function is used to insert data in the heap. self.h.append(data) curr = self.currsize self.currsize+=1 @@ -74,7 +75,7 @@ def insert(self,data): self.h[curr] = temp curr = curr/2 - def display(self): + def display(self): #This function is used to print the heap. print(self.h) def main(): From 2c67f6161ca4385d9728951f71c4d11fda2ef7df Mon Sep 17 00:00:00 2001 From: Akash Ali <45498607+AkashAli506@users.noreply.github.com> Date: Thu, 7 Mar 2019 20:53:29 +0500 Subject: [PATCH 0312/2908] Update basic_binary_tree.py (#725) I have added the comments for better understanding. --- binary_tree/basic_binary_tree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/binary_tree/basic_binary_tree.py b/binary_tree/basic_binary_tree.py index 6cdeb1a6938c..5738e4ee114f 100644 --- a/binary_tree/basic_binary_tree.py +++ b/binary_tree/basic_binary_tree.py @@ -1,11 +1,11 @@ -class Node: +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 depth_of_tree(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: @@ -17,7 +17,7 @@ def depth_of_tree(tree): return 1 + depth_r_tree -def is_full_binary_tree(tree): +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): @@ -28,7 +28,7 @@ def is_full_binary_tree(tree): return False -def main(): +def main(): # Main func for testing. tree = Node(1) tree.left = Node(2) tree.right = Node(3) From 8e67ac3b7672c1aa05c59bef29ad1b2c5520e07d Mon Sep 17 00:00:00 2001 From: Maxim Semenyuk <33791308+semenuk@users.noreply.github.com> Date: Sun, 10 Mar 2019 07:10:29 +0500 Subject: [PATCH 0313/2908] Fix '__bool__' method (#735) The method returns the truth when the stack is empty --- data_structures/stacks/stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 66af8c025d8c..7f979d927d08 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -17,7 +17,7 @@ def __init__(self, limit=10): self.limit = limit def __bool__(self): - return not bool(self.stack) + return bool(self.stack) def __str__(self): return str(self.stack) From 96c36f828689ab1c610311c7f191f4289fd7ff6c Mon Sep 17 00:00:00 2001 From: Ishani Date: Sun, 17 Mar 2019 23:42:22 +0530 Subject: [PATCH 0314/2908] added wiggle_sort.py (#734) * Wiggle_sort * Rename Wiggle_Sort to wiggle_sort.py --- sorts/wiggle_sort.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 sorts/wiggle_sort.py diff --git a/sorts/wiggle_sort.py b/sorts/wiggle_sort.py new file mode 100644 index 000000000000..cc83487bdeb1 --- /dev/null +++ b/sorts/wiggle_sort.py @@ -0,0 +1,21 @@ +""" +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] +one possible Wiggle Sorted answer is [3, 5, 1, 6, 2, 4]. +""" +def wiggle_sort(nums): + 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] + + +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 d27968b78d721193f3dd7e11c8d7c50b3160167c Mon Sep 17 00:00:00 2001 From: Ishani Date: Wed, 20 Mar 2019 21:29:35 +0530 Subject: [PATCH 0315/2908] Create Searching in sorted matrix (#738) * Create Searching in sorted matrix * Rename Searching in sorted matrix to searching_in_sorted_matrix.py --- matrix/searching_in_sorted_matrix.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 matrix/searching_in_sorted_matrix.py diff --git a/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py new file mode 100644 index 000000000000..54913b350803 --- /dev/null +++ b/matrix/searching_in_sorted_matrix.py @@ -0,0 +1,27 @@ +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)) + return + if key < mat[i][j]: + i -= 1 + else: + j += 1 + print('Key %s not found' % (key)) + + +def main(): + 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__': + main() From 8b8a6d881cbda0e6d64aacf5284fbccf27772eec Mon Sep 17 00:00:00 2001 From: louisparis <48298425+louisparis@users.noreply.github.com> Date: Wed, 27 Mar 2019 19:46:46 +0200 Subject: [PATCH 0316/2908] reduce indentation (#741) --- file_transfer_protocol/ftp_send_receive.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/file_transfer_protocol/ftp_send_receive.py b/file_transfer_protocol/ftp_send_receive.py index 6050c83f2253..6a9819ef3f21 100644 --- a/file_transfer_protocol/ftp_send_receive.py +++ b/file_transfer_protocol/ftp_send_receive.py @@ -1,11 +1,11 @@ """ - File transfer protocol used to send and receive files using FTP server. - Use credentials to provide access to the FTP client +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 +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 @@ -14,8 +14,8 @@ 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 +The file which will be received via the FTP server +Enter the location of the file where the file is received """ def ReceiveFile(): @@ -25,8 +25,8 @@ def ReceiveFile(): ftp.quit() """ - The file which will be sent via the FTP server - The file send will be send to the current working directory +The file which will be sent via the FTP server +The file send will be send to the current working directory """ def SendFile(): From 441b82a95f38827cee550215f7cf6018d0258c57 Mon Sep 17 00:00:00 2001 From: RayCurse <38709018+RayCurse@users.noreply.github.com> Date: Wed, 27 Mar 2019 13:50:43 -0400 Subject: [PATCH 0317/2908] More matrix algorithms (#745) * added matrix minor * added matrix determinant * added inverse,scalar multiply, identity, transpose --- matrix/matrix_multiplication_addition.py | 41 +++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/matrix/matrix_multiplication_addition.py b/matrix/matrix_multiplication_addition.py index c387c43d4a85..dd50db729e43 100644 --- a/matrix/matrix_multiplication_addition.py +++ b/matrix/matrix_multiplication_addition.py @@ -10,6 +10,8 @@ def add(matrix_a, matrix_b): matrix_c.append(list_1) return matrix_c +def scalarMultiply(matrix , n): + return [[x * n for x in row] for row in matrix] def multiply(matrix_a, matrix_b): matrix_c = [] @@ -24,13 +26,50 @@ def multiply(matrix_a, matrix_b): 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 transpose(matrix): + return map(list , zip(*matrix)) + +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] + + res = 0 + for x in range(len(matrix)): + res += matrix[0][x] * determinant(minor(matrix , 0 , x)) * (-1) ** x + return res + +def inverse(matrix): + det = determinant(matrix) + if det == 0: return None + + matrixMinor = [[] 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))) + + cofactors = [[x * (-1) ** (row + col) for col, x in enumerate(matrixMinor[row])] for row in range(len(matrix))] + adjugate = transpose(cofactors) + return scalarMultiply(adjugate , 1/det) 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_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)) if __name__ == '__main__': main() From bb29dc55faf5b98b1e9f0b1f11a9dd15525386a3 Mon Sep 17 00:00:00 2001 From: Aditya Haridas Menon Date: Wed, 27 Mar 2019 23:29:31 +0530 Subject: [PATCH 0318/2908] Bitmasking and DP added (#705) --- dynamic_programming/bitmask.py | 90 ++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 dynamic_programming/bitmask.py diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py new file mode 100644 index 000000000000..213b22fe9051 --- /dev/null +++ b/dynamic_programming/bitmask.py @@ -0,0 +1,90 @@ +""" + +This is a python implementation for questions involving task assignments between people. +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. +Find the total no of ways in which the tasks can be distributed. + + +""" +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< self.total_tasks: + return 0 + + #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) + + # 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< Date: Wed, 3 Apr 2019 21:27:36 +0200 Subject: [PATCH 0319/2908] Added Trafid Cipher (#746) --- ciphers/trafid_cipher.py | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 ciphers/trafid_cipher.py diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py new file mode 100644 index 000000000000..0453272f26a0 --- /dev/null +++ b/ciphers/trafid_cipher.py @@ -0,0 +1,86 @@ +#https://en.wikipedia.org/wiki/Trifid_cipher + +def __encryptPart(messagePart, character2Number): + one, two, three = "", "", "" + tmp = [] + + for character in messagePart: + tmp.append(character2Number[character]) + + for each in tmp: + one += each[0] + two += each[1] + three += each[2] + + return one+two+three + +def __decryptPart(messagePart, character2Number): + tmp, thisPart = "", "" + result = [] + + for character in messagePart: + thisPart += character2Number[character] + + for digit in thisPart: + tmp += digit + if len(tmp) == len(messagePart): + result.append(tmp) + tmp = "" + + return result[0], result[1], result[2] + +def __prepare(message, alphabet): + #Validate message and alphabet, set to upper and remove spaces + alphabet = alphabet.replace(" ", "").upper() + message = message.replace(" ", "").upper() + + #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") + character2Number = {} + number2Character = {} + 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): + 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(encrypted_numeric), 3): + encrypted += number2Character[encrypted_numeric[i:i+3]] + + return encrypted + +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 j in range(0, len(a)): + decrypted_numeric.append(a[j]+b[j]+c[j]) + + for each in decrypted_numeric: + decrypted += number2Character[each] + + return decrypted + +if __name__ == '__main__': + 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 From 15bc87fb416908230315a5f18adc94f950080c13 Mon Sep 17 00:00:00 2001 From: Ishani Date: Thu, 4 Apr 2019 16:40:11 +0530 Subject: [PATCH 0320/2908] Create is_Palindrome (#740) --- data_structures/linked_list/is_Palindrome | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 data_structures/linked_list/is_Palindrome diff --git a/data_structures/linked_list/is_Palindrome b/data_structures/linked_list/is_Palindrome new file mode 100644 index 000000000000..acc87c1c272b --- /dev/null +++ b/data_structures/linked_list/is_Palindrome @@ -0,0 +1,77 @@ +def is_palindrome(head): + if not head: + return True + # split the list to two parts + fast, slow = head.next, head + while fast and fast.next: + fast = fast.next.next + slow = slow.next + second = slow.next + slow.next = None # Don't forget here! But forget still works! + # reverse the second part + node = None + while second: + nxt = second.next + second.next = node + node = second + second = nxt + # compare two parts + # second part has the same or one less node + while node: + if node.val != head.val: + return False + node = node.next + head = head.next + return True + + +def is_palindrome_stack(head): + if not head or not head.next: + return True + + # 1. Get the midpoint (slow) + slow = fast = cur = head + while fast and fast.next: + fast, slow = fast.next.next, slow.next + + # 2. Push the second half into the stack + stack = [slow.val] + while slow.next: + slow = slow.next + stack.append(slow.val) + + # 3. Comparison + while stack: + if stack.pop() != cur.val: + return False + cur = cur.next + + return True + + +def is_palindrome_dict(head): + if not head or not head.next: + return True + d = {} + pos = 0 + while head: + if head.val in d.keys(): + d[head.val].append(pos) + else: + d[head.val] = [pos] + head = head.next + pos += 1 + checksum = pos - 1 + middle = 0 + for v in d.values(): + if len(v) % 2 != 0: + middle += 1 + else: + step = 0 + for i in range(0, len(v)): + if v[i] + v[len(v) - 1 - step] != checksum: + return False + step += 1 + if middle > 1: + return False + return True From 56de3df784a8ff2bca54946b9218ca039776a2d7 Mon Sep 17 00:00:00 2001 From: Ahish Date: Sun, 7 Apr 2019 21:23:50 +0530 Subject: [PATCH 0321/2908] Update basic_binary_tree.py (#748) --- binary_tree/basic_binary_tree.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/binary_tree/basic_binary_tree.py b/binary_tree/basic_binary_tree.py index 5738e4ee114f..7c6240fb4dd4 100644 --- a/binary_tree/basic_binary_tree.py +++ b/binary_tree/basic_binary_tree.py @@ -4,6 +4,20 @@ def __init__(self, data): self.left = None self.right = None +def display(tree): #In Order traversal of the tree + + if tree is None: + return + + if tree.left is not None: + display(tree.left) + + print(tree.data) + + if tree.right is not None: + 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: @@ -41,6 +55,8 @@ def main(): # Main func for testing. print(is_full_binary_tree(tree)) print(depth_of_tree(tree)) + print("Tree is: ") + display(tree) if __name__ == '__main__': From 137871bfef005f8c53d9c2f20cc8ba7b5d944eeb Mon Sep 17 00:00:00 2001 From: rohan11074 <34051577+rohan11074@users.noreply.github.com> Date: Sun, 7 Apr 2019 21:25:32 +0530 Subject: [PATCH 0322/2908] feature to add input (#749) --- .../linked_list/singly_linked_list.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 0b9e44768e15..5ae97523b9a1 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -70,16 +70,20 @@ def reverse(self): def main(): A = Linked_List() - print("Inserting 10 at Head") - A.insert_head(10) - print("Inserting 0 at Head") - A.insert_head(0) + 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 100 at Tail") - A.insert_tail(100) - print("Inserting 1000 at Tail") - A.insert_tail(1000) + 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() print("\nDelete Head") From 52d2fbf3cfd646b2dafd5a451c8863887a261e85 Mon Sep 17 00:00:00 2001 From: Reshad Hasan Date: Wed, 10 Apr 2019 21:59:49 +0600 Subject: [PATCH 0323/2908] Add lowest common ancestor to data structures (#732) * add longest common ancestor in data structures * add lowest common ancestor to data structures --- data_structures/LCA.py | 91 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 data_structures/LCA.py diff --git a/data_structures/LCA.py b/data_structures/LCA.py new file mode 100644 index 000000000000..9c9d8ca629c7 --- /dev/null +++ b/data_structures/LCA.py @@ -0,0 +1,91 @@ +import queue + + +def swap(a, b): + a ^= b + b ^= a + a ^= b + return a, b + + +# creating sparse table which saves each nodes 2^ith parent +def creatSparse(max_node, parent): + j = 1 + while (1 << j) < max_node: + for i in range(1, max_node + 1): + parent[j][i] = parent[j - 1][parent[j - 1][i]] + j += 1 + return parent + + +# returns lca of node u,v +def LCA(u, v, level, parent): + # u must be deeper in the tree than v + if level[u] < level[v]: + u, v = swap(u, v) + # making depth of u same as depth of v + for i in range(18, -1, -1): + if level[u] - (1 << i) >= level[v]: + u = parent[i][u] + # at the same depth if u==v that mean lca is found + if u == v: + return u + # moving both nodes upwards till lca in found + for i in range(18, -1, -1): + if parent[i][u] != 0 and parent[i][u] != parent[i][v]: + u, v = parent[i][u], parent[i][v] + # returning longest common ancestor of u,v + return parent[0][u] + + +# 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): + level[root] = 0 + q = queue.Queue(maxsize=max_node) + q.put(root) + while q.qsize() != 0: + u = q.get() + for v in graph[u]: + if level[v] == -1: + level[v] = level[u] + 1 + q.put(v) + parent[0][v] = u + return level, parent + + +def main(): + max_node = 13 + # initializing with 0 + parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] + # initializing with -1 which means every node is unvisited + level = [-1 for _ in range(max_node + 10)] + graph = { + 1: [2, 3, 4], + 2: [5], + 3: [6, 7], + 4: [8], + 5: [9, 10], + 6: [11], + 7: [], + 8: [12, 13], + 9: [], + 10: [], + 11: [], + 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)) + + +if __name__ == "__main__": + main() From b2f1d9c337a104f1f2f52d87f03f53c5431313f2 Mon Sep 17 00:00:00 2001 From: WILFRIED NJANGUI Date: Sun, 14 Apr 2019 13:58:16 +0200 Subject: [PATCH 0324/2908] implementation of tower_of_hanoi algorithm (#756) --- maths/Hanoi.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 maths/Hanoi.py diff --git a/maths/Hanoi.py b/maths/Hanoi.py new file mode 100644 index 000000000000..dd04d0fa58d8 --- /dev/null +++ b/maths/Hanoi.py @@ -0,0 +1,24 @@ +# @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, mouvement): + if n == 0: + return n + elif n == 1: + mouvement += 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 + else: + + mouvement = mouvement + 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 From a170997eafc15733baa70a858600a47c34daacf2 Mon Sep 17 00:00:00 2001 From: jfeng43 Date: Fri, 19 Apr 2019 11:31:06 -0400 Subject: [PATCH 0325/2908] Add animation for heap sort --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7cf1f4f64ffd..faebd313507a 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ __Properties__ ### 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. @@ -311,6 +312,7 @@ where {\displaystyle \oplus } \oplus denotes the exclusive disjunction (XOR) op [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 From a91f0e7ca07fbd176af6eb4f89d0d592a6fff620 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sat, 20 Apr 2019 00:00:40 +0800 Subject: [PATCH 0326/2908] Updated Euler problem 21 sol1.py --- project_euler/problem_21/sol1.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/project_euler/problem_21/sol1.py b/project_euler/problem_21/sol1.py index 6d137a7d4332..da29a5c7b631 100644 --- a/project_euler/problem_21/sol1.py +++ b/project_euler/problem_21/sol1.py @@ -24,19 +24,7 @@ def sum_of_divisors(n): total += i + n//i elif i == sqrt(n): total += i - return total-n -sums = [] -total = 0 - -for i in xrange(1, 10000): - n = sum_of_divisors(i) - - if n < len(sums): - if sums[n-1] == i: - total += n + i - - sums.append(n) - -print(total) \ No newline at end of file +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)) From 48bba495ae1007c9e21a0c11d5b95585825f9a9e Mon Sep 17 00:00:00 2001 From: John Law Date: Sat, 20 Apr 2019 15:13:02 +0800 Subject: [PATCH 0327/2908] Rename is_Palindrome to is_Palindrome.py (#752) --- data_structures/linked_list/{is_Palindrome => is_Palindrome.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data_structures/linked_list/{is_Palindrome => is_Palindrome.py} (100%) diff --git a/data_structures/linked_list/is_Palindrome b/data_structures/linked_list/is_Palindrome.py similarity index 100% rename from data_structures/linked_list/is_Palindrome rename to data_structures/linked_list/is_Palindrome.py From df04d9454332dc0ea916a6dbe6da2b71d7ae9782 Mon Sep 17 00:00:00 2001 From: Vysor Date: Tue, 23 Apr 2019 00:53:56 +1000 Subject: [PATCH 0328/2908] Some directories had a capital in their name [fixed]. Added a recursive factorial algorithm. (#763) * Renaming directories * Adding a recursive factorial algorithm --- {Graphs => graphs}/BFS.py | 0 {Graphs => graphs}/DFS.py | 0 .../Directed and Undirected (Weighted) Graph.py | 0 {Graphs => graphs}/a_star.py | 0 {Graphs => graphs}/articulation_points.py | 0 {Graphs => graphs}/basic_graphs.py | 0 {Graphs => graphs}/bellman_ford.py | 0 {Graphs => graphs}/breadth_first_search.py | 0 {Graphs => graphs}/check_bipartite_graph_bfs.py | 0 {Graphs => graphs}/depth_first_search.py | 0 {Graphs => graphs}/dijkstra.py | 0 {Graphs => graphs}/dijkstra_2.py | 0 {Graphs => graphs}/dijkstra_algorithm.py | 0 {Graphs => graphs}/even_tree.py | 0 {Graphs => graphs}/finding_bridges.py | 0 {Graphs => graphs}/floyd_warshall.py | 0 {Graphs => graphs}/graph.py | 0 {Graphs => graphs}/graph_list.py | 0 {Graphs => graphs}/graph_matrix.py | 0 {Graphs => graphs}/kahns_algorithm_long.py | 0 {Graphs => graphs}/kahns_algorithm_topo.py | 0 {Graphs => graphs}/minimum_spanning_tree_kruskal.py | 0 {Graphs => graphs}/minimum_spanning_tree_prims.py | 0 {Graphs => graphs}/multi_hueristic_astar.py | 0 {Graphs => graphs}/scc_kosaraju.py | 0 {Graphs => graphs}/tarjans_scc.py | 0 {Maths => maths}/3n+1.py | 0 {Maths => maths}/FindMax.py | 0 {Maths => maths}/FindMin.py | 0 {Maths => maths}/abs.py | 0 {Maths => maths}/absMax.py | 0 {Maths => maths}/absMin.py | 0 {Maths => maths}/average.py | 0 {Maths => maths}/extended_euclidean_algorithm.py | 0 maths/factorial_recursive.py | 13 +++++++++++++ {Maths => maths}/find_lcm.py | 0 36 files changed, 13 insertions(+) rename {Graphs => graphs}/BFS.py (100%) rename {Graphs => graphs}/DFS.py (100%) rename {Graphs => graphs}/Directed and Undirected (Weighted) Graph.py (100%) rename {Graphs => graphs}/a_star.py (100%) rename {Graphs => graphs}/articulation_points.py (100%) rename {Graphs => graphs}/basic_graphs.py (100%) rename {Graphs => graphs}/bellman_ford.py (100%) rename {Graphs => graphs}/breadth_first_search.py (100%) rename {Graphs => graphs}/check_bipartite_graph_bfs.py (100%) rename {Graphs => graphs}/depth_first_search.py (100%) rename {Graphs => graphs}/dijkstra.py (100%) rename {Graphs => graphs}/dijkstra_2.py (100%) rename {Graphs => graphs}/dijkstra_algorithm.py (100%) rename {Graphs => graphs}/even_tree.py (100%) rename {Graphs => graphs}/finding_bridges.py (100%) rename {Graphs => graphs}/floyd_warshall.py (100%) rename {Graphs => graphs}/graph.py (100%) rename {Graphs => graphs}/graph_list.py (100%) rename {Graphs => graphs}/graph_matrix.py (100%) rename {Graphs => graphs}/kahns_algorithm_long.py (100%) rename {Graphs => graphs}/kahns_algorithm_topo.py (100%) rename {Graphs => graphs}/minimum_spanning_tree_kruskal.py (100%) rename {Graphs => graphs}/minimum_spanning_tree_prims.py (100%) rename {Graphs => graphs}/multi_hueristic_astar.py (100%) rename {Graphs => graphs}/scc_kosaraju.py (100%) rename {Graphs => graphs}/tarjans_scc.py (100%) rename {Maths => maths}/3n+1.py (100%) rename {Maths => maths}/FindMax.py (100%) rename {Maths => maths}/FindMin.py (100%) rename {Maths => maths}/abs.py (100%) rename {Maths => maths}/absMax.py (100%) rename {Maths => maths}/absMin.py (100%) rename {Maths => maths}/average.py (100%) rename {Maths => maths}/extended_euclidean_algorithm.py (100%) create mode 100644 maths/factorial_recursive.py rename {Maths => maths}/find_lcm.py (100%) 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/a_star.py b/graphs/a_star.py similarity index 100% rename from Graphs/a_star.py rename to graphs/a_star.py diff --git a/Graphs/articulation_points.py b/graphs/articulation_points.py similarity index 100% rename from Graphs/articulation_points.py rename to graphs/articulation_points.py diff --git a/Graphs/basic_graphs.py b/graphs/basic_graphs.py similarity index 100% rename from Graphs/basic_graphs.py rename to graphs/basic_graphs.py diff --git a/Graphs/bellman_ford.py b/graphs/bellman_ford.py similarity index 100% rename from Graphs/bellman_ford.py rename to graphs/bellman_ford.py diff --git a/Graphs/breadth_first_search.py b/graphs/breadth_first_search.py similarity index 100% rename from Graphs/breadth_first_search.py rename to graphs/breadth_first_search.py diff --git a/Graphs/check_bipartite_graph_bfs.py b/graphs/check_bipartite_graph_bfs.py similarity index 100% rename from Graphs/check_bipartite_graph_bfs.py rename to graphs/check_bipartite_graph_bfs.py diff --git a/Graphs/depth_first_search.py b/graphs/depth_first_search.py similarity index 100% rename from Graphs/depth_first_search.py rename to graphs/depth_first_search.py diff --git a/Graphs/dijkstra.py b/graphs/dijkstra.py similarity index 100% rename from Graphs/dijkstra.py rename to graphs/dijkstra.py diff --git a/Graphs/dijkstra_2.py b/graphs/dijkstra_2.py similarity index 100% rename from Graphs/dijkstra_2.py rename to graphs/dijkstra_2.py diff --git a/Graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py similarity index 100% rename from Graphs/dijkstra_algorithm.py rename to graphs/dijkstra_algorithm.py diff --git a/Graphs/even_tree.py b/graphs/even_tree.py similarity index 100% rename from Graphs/even_tree.py rename to graphs/even_tree.py diff --git a/Graphs/finding_bridges.py b/graphs/finding_bridges.py similarity index 100% rename from Graphs/finding_bridges.py rename to graphs/finding_bridges.py diff --git a/Graphs/floyd_warshall.py b/graphs/floyd_warshall.py similarity index 100% rename from Graphs/floyd_warshall.py rename to graphs/floyd_warshall.py diff --git a/Graphs/graph.py b/graphs/graph.py similarity index 100% rename from Graphs/graph.py rename to graphs/graph.py diff --git a/Graphs/graph_list.py b/graphs/graph_list.py similarity index 100% rename from Graphs/graph_list.py rename to graphs/graph_list.py diff --git a/Graphs/graph_matrix.py b/graphs/graph_matrix.py similarity index 100% rename from Graphs/graph_matrix.py rename to graphs/graph_matrix.py diff --git a/Graphs/kahns_algorithm_long.py b/graphs/kahns_algorithm_long.py similarity index 100% rename from Graphs/kahns_algorithm_long.py rename to graphs/kahns_algorithm_long.py diff --git a/Graphs/kahns_algorithm_topo.py b/graphs/kahns_algorithm_topo.py similarity index 100% rename from Graphs/kahns_algorithm_topo.py rename to graphs/kahns_algorithm_topo.py diff --git a/Graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py similarity index 100% rename from Graphs/minimum_spanning_tree_kruskal.py rename to graphs/minimum_spanning_tree_kruskal.py diff --git a/Graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py similarity index 100% rename from Graphs/minimum_spanning_tree_prims.py rename to graphs/minimum_spanning_tree_prims.py diff --git a/Graphs/multi_hueristic_astar.py b/graphs/multi_hueristic_astar.py similarity index 100% rename from Graphs/multi_hueristic_astar.py rename to graphs/multi_hueristic_astar.py diff --git a/Graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py similarity index 100% rename from Graphs/scc_kosaraju.py rename to graphs/scc_kosaraju.py diff --git a/Graphs/tarjans_scc.py b/graphs/tarjans_scc.py similarity index 100% rename from Graphs/tarjans_scc.py rename to graphs/tarjans_scc.py diff --git a/Maths/3n+1.py b/maths/3n+1.py similarity index 100% rename from Maths/3n+1.py rename to maths/3n+1.py diff --git a/Maths/FindMax.py b/maths/FindMax.py similarity index 100% rename from Maths/FindMax.py rename to maths/FindMax.py diff --git a/Maths/FindMin.py b/maths/FindMin.py similarity index 100% rename from Maths/FindMin.py rename to maths/FindMin.py diff --git a/Maths/abs.py b/maths/abs.py similarity index 100% rename from Maths/abs.py rename to maths/abs.py diff --git a/Maths/absMax.py b/maths/absMax.py similarity index 100% rename from Maths/absMax.py rename to maths/absMax.py diff --git a/Maths/absMin.py b/maths/absMin.py similarity index 100% rename from Maths/absMin.py rename to maths/absMin.py diff --git a/Maths/average.py b/maths/average.py similarity index 100% rename from Maths/average.py rename to maths/average.py diff --git a/Maths/extended_euclidean_algorithm.py b/maths/extended_euclidean_algorithm.py similarity index 100% rename from Maths/extended_euclidean_algorithm.py rename to maths/extended_euclidean_algorithm.py diff --git a/maths/factorial_recursive.py b/maths/factorial_recursive.py new file mode 100644 index 000000000000..41391a2718f6 --- /dev/null +++ b/maths/factorial_recursive.py @@ -0,0 +1,13 @@ +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) + +""" +Shown factorial for i, +where i ranges from 1 to 20. +""" +for i in range(1,21): + print(i, ": ", fact(i), sep='') diff --git a/Maths/find_lcm.py b/maths/find_lcm.py similarity index 100% rename from Maths/find_lcm.py rename to maths/find_lcm.py From 2fc2ae3f32fad16226c88358cb7c9e4e5c790a8f Mon Sep 17 00:00:00 2001 From: Viraat Das Date: Thu, 25 Apr 2019 07:48:14 -0400 Subject: [PATCH 0329/2908] Created a generalized algo to edmonds karp (#724) Edmonds Karp algorithm is traditionally with only one source and one sink. What do you do if you have multiple sources and sinks? This algorithm is a generalized algorithm that regardless of however many sinks and sources you have, will allow you to use this algorithm. It does this by using the traditional algorithm but adding an artificial source and sink that allows with "infinite" weight. --- Graphs/edmonds_karp_Multiple_SourceAndSink.py | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 Graphs/edmonds_karp_Multiple_SourceAndSink.py diff --git a/Graphs/edmonds_karp_Multiple_SourceAndSink.py b/Graphs/edmonds_karp_Multiple_SourceAndSink.py new file mode 100644 index 000000000000..d231ac2c4cc3 --- /dev/null +++ b/Graphs/edmonds_karp_Multiple_SourceAndSink.py @@ -0,0 +1,182 @@ +class FlowNetwork: + def __init__(self, graph, sources, sinks): + self.sourceIndex = None + self.sinkIndex = None + self.graph = graph + + self._normalizeGraph(sources, sinks) + self.verticesCount = len(graph) + self.maximumFlowAlgorithm = None + + # make only one source and one sink + def _normalizeGraph(self, sources, sinks): + if sources is int: + sources = [sources] + if sinks is int: + sinks = [sinks] + + if len(sources) == 0 or len(sinks) == 0: + return + + self.sourceIndex = sources[0] + self.sinkIndex = sinks[0] + + # make fake vertex if there are more + # than one source or sink + if len(sources) > 1 or len(sinks) > 1: + maxInputFlow = 0 + 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 + + size = len(self.graph) + 1 + for room in self.graph: + room.append(0) + self.graph.append([0] * size) + for i in 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.") + if self.sourceIndex is None or self.sinkIndex is None: + return 0 + + self.maximumFlowAlgorithm.execute() + return self.maximumFlowAlgorithm.getMaximumFlow() + + def setMaximumFlowAlgorithm(self, Algorithm): + self.maximumFlowAlgorithm = Algorithm(self) + + +class FlowNetworkAlgorithmExecutor(object): + def __init__(self, flowNetwork): + self.flowNetwork = flowNetwork + self.verticesCount = flowNetwork.verticesCount + self.sourceIndex = flowNetwork.sourceIndex + self.sinkIndex = flowNetwork.sinkIndex + # it's just a reference, so you shouldn't change + # it in your algorithms, use deep copy before doing that + self.graph = flowNetwork.graph + self.executed = False + + def execute(self): + if not self.executed: + self._algorithm() + self.executed = True + + # You should override it + def _algorithm(self): + pass + + + +class MaximumFlowAlgorithmExecutor(FlowNetworkAlgorithmExecutor): + def __init__(self, flowNetwork): + super(MaximumFlowAlgorithmExecutor, self).__init__(flowNetwork) + # use this to save your result + self.maximumFlow = -1 + + def getMaximumFlow(self): + if not self.executed: + raise Exception("You should execute algorithm before using its result!") + + return self.maximumFlow + +class PushRelabelExecutor(MaximumFlowAlgorithmExecutor): + def __init__(self, flowNetwork): + super(PushRelabelExecutor, self).__init__(flowNetwork) + + self.preflow = [[0] * self.verticesCount for i in range(self.verticesCount)] + + self.heights = [0] * self.verticesCount + self.excesses = [0] * self.verticesCount + + def _algorithm(self): + self.heights[self.sourceIndex] = self.verticesCount + + # push some substance to graph + for nextVertexIndex, bandwidth in enumerate(self.graph[self.sourceIndex]): + self.preflow[self.sourceIndex][nextVertexIndex] += bandwidth + self.preflow[nextVertexIndex][self.sourceIndex] -= bandwidth + 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] + + # move through list + i = 0 + while i < len(verticesList): + vertexIndex = verticesList[i] + previousHeight = self.heights[vertexIndex] + self.processVertex(vertexIndex) + if self.heights[vertexIndex] > previousHeight: + # if it was relabeled, swap elements + # and start from 0 index + verticesList.insert(0, verticesList.pop(i)) + i = 0 + else: + i += 1 + + self.maximumFlow = sum(self.preflow[self.sourceIndex]) + + 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]: + 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]) + self.preflow[fromIndex][toIndex] += preflowDelta + self.preflow[toIndex][fromIndex] -= preflowDelta + self.excesses[fromIndex] -= preflowDelta + self.excesses[toIndex] += preflowDelta + + def relabel(self, vertexIndex): + minHeight = None + for toIndex in range(self.verticesCount): + 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__': + entrances = [0] + exits = [3] + # graph = [ + # [0, 0, 4, 6, 0, 0], + # [0, 0, 5, 2, 0, 0], + # [0, 0, 0, 0, 4, 4], + # [0, 0, 0, 0, 6, 6], + # [0, 0, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0], + # ] + graph = [[0, 7, 0, 0], [0, 0, 6, 0], [0, 0, 0, 8], [9, 0, 0, 0]] + + # prepare our network + flowNetwork = FlowNetwork(graph, entrances, exits) + # set algorithm + flowNetwork.setMaximumFlowAlgorithm(PushRelabelExecutor) + # and calculate + maximumFlow = flowNetwork.findMaximumFlow() + + print("maximum flow is {}".format(maximumFlow)) From 48553da785f9233311ae59dd6eb93f79cf675965 Mon Sep 17 00:00:00 2001 From: sakuralethe Date: Fri, 26 Apr 2019 17:43:51 +0800 Subject: [PATCH 0330/2908] variable in function should be lowercase (#768) --- sorts/quick_sort.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 136cbc021669..e01d319a4b29 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -12,7 +12,7 @@ from __future__ import print_function -def quick_sort(ARRAY): +def quick_sort(collection): """Pure implementation of quick sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous @@ -29,14 +29,14 @@ def quick_sort(ARRAY): >>> quick_sort([-2, -5, -45]) [-45, -5, -2] """ - ARRAY_LENGTH = len(ARRAY) - if( ARRAY_LENGTH <= 1): - return ARRAY + length = len(collection) + if length <= 1: + return collection else: - PIVOT = ARRAY[0] - GREATER = [ element for element in ARRAY[1:] if element > PIVOT ] - LESSER = [ element for element in ARRAY[1:] if element <= PIVOT ] - return quick_sort(LESSER) + [PIVOT] + quick_sort(GREATER) + pivot = collection[0] + 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) if __name__ == '__main__': From 06dbef04a0700095b156c26b113bf08466a46c90 Mon Sep 17 00:00:00 2001 From: Gattlin Walker Date: Tue, 30 Apr 2019 08:16:42 -0500 Subject: [PATCH 0331/2908] Adding quick sort where random pivot point is chosen (#774) --- sorts/random_pivot_quick_sort.py | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 sorts/random_pivot_quick_sort.py diff --git a/sorts/random_pivot_quick_sort.py b/sorts/random_pivot_quick_sort.py new file mode 100644 index 000000000000..fc8f90486ee6 --- /dev/null +++ b/sorts/random_pivot_quick_sort.py @@ -0,0 +1,33 @@ +""" +Picks the random index as the pivot +""" +import random + +def partition(A, left_index, right_index): + pivot = A[left_index] + i = left_index + 1 + for j in range(left_index + 1, right_index): + if A[j] < pivot: + A[j], A[i] = A[i], A[j] + i += 1 + A[left_index], A[i - 1] = A[i - 1], A[left_index] + return i - 1 + +def quick_sort_random(A, left, right): + if left < right: + pivot = random.randint(left, right - 1) + A[pivot], A[left] = A[left], A[pivot] #switches the pivot with the left most bound + pivot_index = partition(A, left, right) + quick_sort_random(A, left, pivot_index) #recursive quicksort to the left of the pivot point + quick_sort_random(A, pivot_index + 1, right) #recursive quicksort to the right of the pivot point + +def main(): + user_input = input('Enter numbers separated by a comma:\n').strip() + arr = [int(item) for item in user_input.split(',')] + + quick_sort_random(arr, 0, len(arr)) + + print(arr) + +if __name__ == "__main__": + main() \ No newline at end of file From 7b89d03dd7d80087fa95bcf7a1983fe3d8b424ca Mon Sep 17 00:00:00 2001 From: yolstatrisch Date: Thu, 2 May 2019 00:44:21 +0800 Subject: [PATCH 0332/2908] Added an O(1) solution to problem 002 (#776) * Added an O(1) solution to problem 002 * Removed comments from sol3.py that were accidentally added to sol4.py --- project_euler/problem_02/sol4.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 project_euler/problem_02/sol4.py diff --git a/project_euler/problem_02/sol4.py b/project_euler/problem_02/sol4.py new file mode 100644 index 000000000000..64bae65f49b4 --- /dev/null +++ b/project_euler/problem_02/sol4.py @@ -0,0 +1,13 @@ +import math +from decimal import * + +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 = round(phi ** Decimal(index + 1)) / (phi + 2) +sum = num // 2 + +print(int(sum)) From c5c3a74f8fbeed522288a63099a2121f9fe6bddb Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sat, 4 May 2019 15:43:37 +0530 Subject: [PATCH 0333/2908] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index faebd313507a..1e43deb6bdef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # 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) + ### All algorithms implemented in Python (for education) From e22ea7e380d75012990c8bb1648081b229cdd6fa Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sat, 4 May 2019 21:53:06 +0530 Subject: [PATCH 0334/2908] Update Directed and Undirected (Weighted) Graph.py --- graphs/Directed and Undirected (Weighted) Graph.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graphs/Directed and Undirected (Weighted) Graph.py b/graphs/Directed and Undirected (Weighted) Graph.py index 68977de8d311..a31a4a96d6d0 100644 --- a/graphs/Directed and Undirected (Weighted) Graph.py +++ b/graphs/Directed and Undirected (Weighted) Graph.py @@ -152,6 +152,7 @@ def cycle_nodes(self): parent = -2 indirect_parents = [] ss = s + on_the_way_back = False anticipating_nodes = set() while True: @@ -199,6 +200,7 @@ def has_cycle(self): parent = -2 indirect_parents = [] ss = s + on_the_way_back = False anticipating_nodes = set() while True: @@ -367,6 +369,7 @@ def cycle_nodes(self): parent = -2 indirect_parents = [] ss = s + on_the_way_back = False anticipating_nodes = set() while True: @@ -414,6 +417,7 @@ def has_cycle(self): parent = -2 indirect_parents = [] ss = s + on_the_way_back = False anticipating_nodes = set() while True: From 7677c370115faa49759b72f5d7c9debfe1081e35 Mon Sep 17 00:00:00 2001 From: weixuanhu <44716380+weixuanhu@users.noreply.github.com> Date: Mon, 6 May 2019 17:54:31 +0800 Subject: [PATCH 0335/2908] update 'sorted' to 'ascending sorted' in comments (#789) To avoid confusion all 'sorted' to 'ascending sorted' in comments --- searches/binary_search.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 7df45883c09a..1d5da96586cd 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -21,10 +21,10 @@ def binary_search(sorted_collection, item): """Pure implementation of binary 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 @@ -60,10 +60,10 @@ def binary_search(sorted_collection, item): def binary_search_std_lib(sorted_collection, item): """Pure implementation of binary search algorithm in Python using stdlib - 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 @@ -89,11 +89,11 @@ def binary_search_by_recursion(sorted_collection, item, left, right): """Pure implementation of binary 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 @@ -123,11 +123,11 @@ 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 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]) @@ -136,10 +136,10 @@ def __assert_sorted(collection): >>> __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 @@ -150,7 +150,7 @@ def __assert_sorted(collection): try: __assert_sorted(collection) except ValueError: - sys.exit('Sequence must be sorted to apply binary search') + 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 = int(target_input) From 30a358298385e0e29b70a841d0b4019dc235f3a3 Mon Sep 17 00:00:00 2001 From: Lorenz Nickel Date: Wed, 8 May 2019 21:48:30 +0200 Subject: [PATCH 0336/2908] 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 0337/2908] 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 0338/2908] [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 0339/2908] 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 0340/2908] 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 0341/2908] 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 0342/2908] 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 0343/2908] 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 0344/2908] 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 0345/2908] 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 0346/2908] 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 0347/2908] 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 0348/2908] 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 0349/2908] 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 0350/2908] 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 0351/2908] 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 0352/2908] 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 0353/2908] 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 0354/2908] 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 0355/2908] 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 0356/2908] 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 0357/2908] 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 0358/2908] 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 0359/2908] 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 0360/2908] 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 0361/2908] 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 0362/2908] 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 0363/2908] 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 0364/2908] 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 0365/2908] 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 0366/2908] 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 0367/2908] 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 0368/2908] 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 0369/2908] 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 0370/2908] 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 0371/2908] 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 0372/2908] 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 0373/2908] 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 0374/2908] 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 0375/2908] 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 0376/2908] 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 0377/2908] 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 0378/2908] 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 0379/2908] 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 0380/2908] 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 0381/2908] 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 0382/2908] 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 0383/2908] .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 0384/2908] 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 0385/2908] 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 0386/2908] 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 0387/2908] 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 0388/2908] 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 0389/2908] 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 0390/2908] 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 0391/2908] 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 0392/2908] 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 0393/2908] 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 0394/2908] 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 0395/2908] 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 0396/2908] 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 0397/2908] 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 0398/2908] 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 0399/2908] 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 0400/2908] 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 0401/2908] 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 0402/2908] 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 0403/2908] 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 0404/2908] 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 0405/2908] 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 0406/2908] 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 0407/2908] 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 0408/2908] 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 0409/2908] 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 0410/2908] 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 0411/2908] #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 0412/2908] 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 zwK#gDo{y*77tXM!J}A-tglbG%f2W@O2id*un{IT#pogB=KahQ~cQXViRlnbeN8{lv z<>hcT!Xf$EvVK__2NPRoKp~Q&p_R}R`QKo{=(RON8vYHD?jwLFLK4EoGY$nagYx0I zP6lu_ClwK%vk+4Y27v;nfl|^E$oG%J$f69);?|=mwd_e0w((Koh=l*pn2XSZe+B3U z5uy)?^D|z|0FZ@^5N94m*=y{>;UtVO&oSC~j;3P0r3rtRGL7<+*7`nB6%oX$i=~t~ z#5*tGeV{;(VWq%^Cs&PqWjaljaZI4gc{^~xVHZ^xK8%OyGybiRF2tSD;GHzNkE4ev zA>1#V>}_n4Z7MNAf76gE+DAgKM>uJ4Go3ulEX6>EilIX~FCyc3#5AOgYau)DwFo$5 zsh41LY<9>5YGktYc_iWgU8AvisghxQ!YnJ&+JTf4`(9d2XSFJZ|RW zoq!0hSkE(e-;+4LB^wL0d;F!+aLR!fAtyrZkY^KB_V^ic%uDqW7wy0PMNWPRe~XEDuwbR7_`FNTKU4VYSf|jJ$C!;e9_%fudZ<05z2b2O~K$ z0taJqoRW_#ekqloEVy7XYANzm^h3SKmQk`)3MUh)(wxK|3lT1qSY^Ho_vREk>t*wrm$~B-E#_#Hf zX|PEK>3pl~uX#&WOS>;IJypq?PML40hOv2o@{;TAm~T>j23yxCuUF4@q%&g<3p_;P~L{cB~rsn@dtdUQPB06K8E?8Be zRS3tXY6v`QJFqdKkm;Cfph$`QoM>=gTc61&k*q#eL34|WpU>hQ?^1uKaj6&713bAo zTuxoO#2f2%vOd|S;r`OmQPWpPU{}gdzS5<5(=RM(RYPo92*&K%$^)T06C5e)ziJu$ z(sftFM7s`^sM13ATM-0qfYiQ&<`I#P{;ZENSbPI))%UP5@Gr&0*>&y{KDrO{**b)! zOsZ9Kq&KUorWhws<5D6E_if`+G__D4BGGUR(}_-jBkq&c*J_?=>~dz(m2i|-2itj^ zVnETaj%GIp9F1Nn8W{rj$0FY~yER+tr6!~c1h-d5vNW8-pf^q5@jO$-mL#O(4^O0W z8RiKSoFkm>MSlM5NGO&)C?`m_q^|LdulAM5HA6p>=)o$lX1S|&@ad<&)lB?+u%ij0 z0rdborq_*uRZ3;na!&QDolB=VsWO7*M4c-u(=9EnQ|AyDtxMr9tZjPF=hWcF4ay!? z{le}(XCutTBUi(@tTB^3OpA?sTo>Ja_-)FcUUOE}1)cBb`aV%NTW_qc66Mi~p>!Lo zZLV$NBnW+;Q44$cL+oNKx(YGt$lH5Lt<~UtPFiUXZ?A7`&7&&Bx{KsMBMPqUpU4+YLId}f1J4WKPwBKVIRxC5s{fe&wK4Vhf}>VDb2_% zuIeP0A!9VixRou&TG$gCv4Zx!JiW5cu#2Li&Pn|8mhX)=TDEa^3IjW*4r`oIPI1|6 z(N&iYH_x^A-9R85?_St#t2#f->l5X!H7^b~FIC^1)MfMS#_Z=bfsAhVkAq{Ex##3) zUb14k)l-KA&)8}(Y4r%ks9x;J%1EP=fb5g7dF&|zKK!HPQn#@ChsE}>Szq6>M-TF* z`~D+fC7e(zKVA-dJrmAjKCEYg4IFC*Qq$~lpySFa`gmUx>^a+bY@`C3rk7ZUMNc=yp%plYOHN_d>=r;RS~MO|etRT1(8;{#pmh0H&NI6y?r zKgT4%Er3>J_Att9j7s(dRau zb%|1_>0OxGQ^@(ZaDC!%XzM41@RN}B|IoqyKOZpKa5$01Wc7Kl{{%+!gre|dI-E_1 zndJ5ur!ts;3#e+XIX=%U)~iiUQZ3n7try!79y52<&a_wiFht0~HrJI72Y+EH*(NtU zHcwW{3x!9$Ul)&8^TBeZE8P`WS0}42EMB}mo>#i-oj-r}R=z#2oauGP;&=GgecZck zIDH{h$@A|(bl-G?B}~%`g2Y1252~hU!wW)JJi!l!w>-&L*erh74HpYx$BQ6eGu|_x zXepbIoN6a8jHI7q-dEN*EHhGK(s)`hBHMCbh!N&oBnrd)F;Wnxf~s*4k13m`7AFmJ zNE|N@7FP=kSI{7lSG!0nR@EY1JxtI;I6M@X2_a8UvgmIr5wJ;l#K>~!U?$VR?C>B< z_2>yF6?Rd5EKLtMYAW^AOfV}mawmOJOyi89I4uS!j_*sv8%7qCXlMXWi}OTDfMpRW zmc*t01wddWxNey8tg30r@~pb;sN$@q>j8LH`{xH$RaOuJ_DR8TNw{?bG3^8>Y?`++ zGj|R~!ls!?H>x^n&9<^CdyCWEuypr`)wc35)Qz^_v_i|e^rRS?Mwu`Op}=?{*=a?q}8zV zd>YUDVH%wsO2LA@i3ZgoW2)w00t0`8Jt1P|3J^!zB!+I2nWb*1%s*~zN@ zV=*hZg1~E1A>T%*T6vq`i*4(U5+XvvZg}RhOS+TQp!xYSTgc5Z{Ma`xLnwtabkJ)}wVz zrNi@NU>Eyg{CLS%3kdAT)IGnH|!s~ zBpvXkzX;y~Hi7><2^0Y2T{k!kE+yNNewEZ&e>48<6y1MejoaH(a%Tbc#rv_rMSlY&1G`5kYmR z_yvVyyt%cpC@J}{K!Zi(X+JS>XTg|^<3)T9w-lLB1d)6K5={y>N&QaQcvDeBvW+px zf7Nb%0;AGP4dm$)hbOVA-3QqS#pdJsF~+Hd%%;R965{-n$bh;&bf(->R>Wt=j0FI6 zTX4EezICHV0*I8T&|``&a^Cu2VVZs>Le{DxF`M`Kh-$Zd8fQ*P{uF$0#8aOz=`&uR z3r2ya|Mt>lh}1;#jpyX*ru=UwLsR~F7MXAe=5&PUK_;5Z3Fr1$UlvMvGIkUmKiQ3f zO6Y=@C_q9&&SS3Oi&JrtwMDWEw5mv+_mL-4?IMJ?wJ~1w#CcI2f<={vl8LRRzHS205Omsjj{O>_rq92!6>bfc zxG*5;I(@C|(C0iy9-^YT1Iu@NK3`OAoF$%4CwI>jT*`uA1)ncsJMELTEM}l~OjLt= ziK*!IA*$B6DBTH5VGe^xkaois?F6CFHbQPw49aZO<8f`*!alGJZ~K-jA-NgA-e(G^ z-&#uVUFxsY0i2k}F~e*W*OT_t=-4wgqS%MW6<1tRzaQO4kD80)t&k``Tph+JS&|d$ zp-j@4(?>(Q9Q%U@RH#Q3CO8ioBFVa#=%t({;A%|4aW<4DcpcAwjB;)O>wqIRtJKV8-*1wU*l3h326;{whT55%CMzt6AvN` zC?jrFwWp^>-U)aJ@iUWzJH{mEbA24|>ZS|c(wt>dCwc7rSO;*{6xCU{gLxUw z!K-m8WUsZ7O`pa;y03Tv2^*GjER5@J)rC2LV2rq2O<-@OVE3Ms@m~ii7t9{1hwx!l?v-=BT3oiS5yw>vQ!L(xkB_y3@^U zktqmP?6i~JWM?1|nQgj@!SQe)Q%da($MfoR1zN4uH?0B)+93i@zfHF^KmOgF=fjEJ z+BvE?Se)P+?V;&^JU*YLBSY$%6 zC5z<$BntwOvJzrSPD^nd)+|c$Qa%f`h?EMTU}-_cVg$6B=yxFh#4vCJ_I}fy&nu6bx|Wi zm_8Eii??=eP(cR=MgAsDAGOY@liN~8F2BwXzL6$<+ z+e(hhR;L-N$PLsm>Tfg&f7K9u8fG)m-jnBu8K;ouR0X<=7G&sfQy0~l#9Wq#eBfQ} z3}bz?R!zS_G1;0c0i29%#NJvA9k{sN3|;8F-M|U5L7u0w9^0ty<$h<(PfI>ZTJEx8 z1B-1XQTuo7$(p^`9C8tU=^itz7uhX(*+Op~Ci%5I9Tvs&-Y-_=w_P0ytg^cD?X@3m zr67n|-BCL*g&^Z*l`-)v3)RR>7US7@pwgTYwmy=PfDK2b$4bsZ965 zQf5qd)1Z>9;iLMtcejFB3}5`qp4?*3D`H=5huZCYF4fdYYf>+Gpg`fHWSHe2&Kb8K z!PsAs!&|>I$~zr;<(c)63E)=$lz&_w{h-812%549!g&jaq;sz?R)`BA0_3{GVDu*x z(FZ{?=YP{;=R$Lo3Pmg;P~=bJ!Uo1sg1M!CMaVu4#6U0zvM=Z&#ySofGCM?c5#^gJ zry&xmUWL?ft-DVhK)-ettlW#zHBEt5F@8K#+@wJVeCXIvYp-9ut+Aj_`|aB31;YipX%< z#{BXqAgdJ-=Y6qHa+W5R7LFK4T2dgXvNjpze=Z`JOVABVI+XkzsN&jcNafunp_kDZ zH^9108ZyaCNo@?FlS=Q07gULDl)MPjtOs|64Bda4&}#b|Elv?E`(36fW-57Z~;Ta zH<2QIY@mE-1(INWl{ub`Mi0Q!ISxmqK#VVPFiy0o2;Ie00sup$=K5c627EODvMv982PJzA`FRH?eOhIuq4>a>?#s6eU$ z9xg1XUA!e%@GmARJ&b4cYaZ8$pipZJuwjh|LN@Rvfea}sF(+(cYp#u{x#>7Ge?+z< z+F|N1%TAy#8b&vJIDo4c^e4zYJE+QpS%@qbRo60_;G3lTY4lM$SXX--LjRB?*mJd! z?bp~>xZ$oi3e0sBy|GqyW?aZjoNUMwqIaGUf{r3nwCfPmEcZ|;J^Hv7ZW(F1+q2DS zHYa!=fUrGxnu_(F5VgZCk1*sWI2)S|67GyB zp>^G~)ah`sw|XBu@QV-?&$xH2mrVl+g(FRn3gR^hl($6}&xvC5_fzUnk> zCf%-48~a{d1P;6xIl73f?Be*Yo>^}_OS&#Q1(Dg9retAeyQk3+d7mh=HEFnMXg>um zsLthKkrq)(oc-f`myb~;C1I4vGTh``M1F3ZveGtB^vqnAzP=)usJu|PWossL&WrRo zKN{)W%%4he7Ja0i&^%1d=!B~yL0L61`o@y|K>^~yf6n^DYg+GRHBT0klsC<5Qd$T< zt6X@$cED8Lycub83-nxv-YT!x>97{o>fG2-h3c5!6fr$EU6Ge&t=%NEQ+2A}GXFu^ zW6(Y6v+cP&r+bil3A#w(>Gb6xX={&mwGSWknA?w0AArwRg%#Z}Kz>x|NAJF>dCuIp zx@e4Dp_@Kd3VX|YQ#~44ow`Zsd^@C%ZJ#ugz0FvEJ7S+{pLUzR&3S!0=I`}r=;d_I zMbjx2!~fiRNIt~p0v@1hdFFA+nv@$uIX>0!$7J?tdT8$_>7F zHZ1^nI-3=`@WPa5B*+3yic|WZ4{Xw1fw=*Y20)5{pgEJ1G8fwlf{Mh7@qfPz0@V$o zS%KR;q2|pXov3P$HCtHS)-6eO%eI#Y&69$I2IHFS(h_Xl z&d*1NnyyGk;Dzf|zsO~GF6q;f?amF=e4G9k>AF9jxY7jHUx>OC222>M>IQ^my_5UC zDtepyF<(?(s|C1ZU5>zOn}ke6nmL|a={*i%cKdU+Zj zZi5(OJM2P#JY7P|7>aQ zE;D6u76Yt`y8P8BcsCg0o|s|*Z{B~sa!o)0PO)x-1a+9i9bjdjGF*+q*~<7Sd)%|4 z<#;pK4YYbp4l^1+yIUsNmK4I}Gz1CtJD>;oOTiq{R>L-BW8TpWp+L+DF-A?~%q&@9%((FJDS~?xTst zW3lqS$u#qa3SW646YGB;O5k5$p%20j(1&z*6!`UZSRg#(??957;15CC5c0vDY4hcN z)El@^wj_gIu&U7ZP+c&X=>T`uf0P&P;d7&ic<+~9*#JqyMK~_v?hhuyJ~+vEuM!19 z6nsaKYaZEfEpEB5bY(;In)U!92wlvn6G8g5qDX7+Y}p+QF-}i=4+oqGT#4`zZr@ol zFVOC@b^$lr%dC$zlN!O#Q}W~D`tSiGVzD2m#6*&cL`lYDgmfHrEHJgPSqtL7V~@w= zZ%QKUg^DSHKADf2xrl1&L#lG5KN^8WDJ|9qC{7$bIyVhov2aVfzR;M+i1X9~+$CxW z<&%bj2&q%bg}?GKr-C48(-z-~V(QXl^0enuH?=n*>zT&w`x7YaD9zZ}RDQX*CT6pK zE9T&{5D$o(Prd~^hi8J4dujuia^Y8xD_%TW{Jtgy#RMY>l%I+PGz{5O50LWXO7@k6 zPv$`;n+niG&H&c_=Het5i``>zxy3N&VBc$r$#EhURVNjLgUH3Z&1a#_ zBPXZMnathgyiS~~nJ_9r1lbgv$m3Imgow)AEa3{P7-?m;$Ylvt6~ao4DomEBg^mW6 z@}G8tk{qj2t#b_mKlj$U@uV;wZ7Za8P2|8HP0DG=q{+@VTs z;Jc#Usisi>_^pZm_k5$X_N0Lf`v(2`Dr0o2ZWGa^$SjsqF*Xa)h2IG_hbYy5q@(7Y zx9($-LcoMpw{A22kvGjpuu~Q$shS&eHZ2Sy)piokE0zgU6+=S0%`us(S;nJUli$yL zlBB9nGMcGoHd9Jdd=xrTcH7cao>&21Fsy_+}%&(jLO-&3( zCOV*bU?0m3!1#sUTJrv$7@j@0Vph%G7-6y$)p@pTKDE-w61o&!k8NaR;>I5_zh3Jr zUC|{`qb98Tm+kNq!!@-UYq7XlRK&^r&E)Y%=RCDceB&=Qm&1gvDN$;(wE+rQ!(=-u z6&PQsLY)Oav6N4-dSXYKo}&~njo-vHb_FNN;|G{Yy1$)-f=leCr0)$QzLSFM-ai~~ zP2fnO%s?z#&!G35D^_AA@bWhQ!gU{D0A?=Rqz1C)rjHjRn3pNx_N(YQP6qifGZ@$a zC!G_Qo+_FgbgGeLdefJ+lkY3J)F(vIC&Y(_?{XrlK}-djPNTN0f`~kQ=6qf~%Lo}T zavaMyzMkuK@KrWHVJ=vrUI)c!T198hZAvRL+rn`lC8}j?ROi>{tA>HZ#nqEu$6Res zIj(U-b{V{wddX3-k{EV8&?{2+yr>;4U@eqzEVWMG*Fa~B{h*7k4?up{@qQOI!Jk_3OLU#h))BSg zv)B;hbcu(IQs($1-^BeKpspWmD#_l*HpL*ruh<^G_$(tEw_OgG>;o+p} zo9!j{awya0F=~c)nq6ypQpn*sxt|3rE`2(V&3Q4}+qNPzZWFS z4h^+@FxKObc>xb_qu(7s`5^4Ea(CDnj+qsV$#P>fsY{`=+QM-*U24w~ih4MGc3Ph; zQyQVR(X0e6*WKq>zcYU}I=C8{!&9BDqHDQKuXwiD?y9?kiKVP~sO>k$S?OFZ2;H8> zr!wWQ5H{VfgOf}y;Qfm6%}$T7odGQXXO89b?G!2{k~RugI%7W=Xd1 zX17_2{~Kai9{-QfkUSAsQFZB{G!pemeJG zvI1Xf&%?c_>!FoJ0CSF+QH+@ZRBkNy*Y8ChLOJmk$zHc%HM8M1y%pfdx5D>eRnRT8!Dv&T)%^J6B^)^>t@J-!;47m?l`tfv6c z4qz}rl^fyWfvWOtL!>VTiAzlwdvHss(?gKlq*XJ240t=#(gqRV$aD0rHA<5`-rrUT zIW;xPi{xfBiHbos-<8P~uGJYS_^maKFqfh^)v3unU7KrtTX$LH*_w3uOA+srw`y3Z zldy)+@J=;GYnfFssXF`K3>I*ei+0=c;!@#iCrsV-8U|+ zJ*)Tew{L_6U=# zXZ~=~ipQnCe_P3`3Sx?Bd&IrR{Wy43n~4YCTj2}8ly;t<&-i}n+I%Mc`sX{$FCqw( zoG-{v{Np~fu8oyD%8Y3}a}IJr61zLTv>3d<0waRmu=7v1%=)v!>-g}j4fb)!(O|~y zxmZ8LpVH6pS@tQ`;d(Se9d?+=!E^==G52>CV z^~5GVYT|b5OP@Fr-CiuffT8f?uX+nribJ5DapCI*d%}+#g_wfEJuy(5D0c~Mh=1Td z?)%EG>1zkA2SQ`b&%qhQ=8|}g4S3?I{!s}{2JRFmBFhqloi>3VeXx~ zEB*I0;X=%cZM$M872CG4V;dFQcEz?`v2EM7cRl;pea>{B?moSypFd{iE!=C}>-u~z zjur;iB^HsAQGW$ZB#l-jRY}8qGoni2n7t*h?>NW?H3B-{)TK}ZO_pQ@oKr1VhwMG5##Y(TJL;yec!n7$xb9TgL zMuw^i4(IG+P3u=MN~b99l`XpF;O>cY+2F@@Es>O_1y(8zJ*mYxLHYKcbo#3rOPsz$ z4E5UuB5xxjz31Jh_KOEu-x#TicSRNn^tP+@Pds{0Fw{LjI=a9l_U&s;SI%XFY6ogB zO$19!%4LZvH?n~pgvqhr&CoX?VM@DBY;FS-CJ2#lEC$6Mm@Od+jL|5Kt3l(Kx$9pi zL2VjFD0-<$6h#xUbvJcd?gFQ31Ie}g>j0{Y@ zOb@rfs^M`NX0f_-!=c2JsPI$5SVZUIhE zI3RU8&csQi^=vy@bGg8sJUQRje^;91tRn@1nhTch1jb*pKNtd=mY1$V$#tTnU@))z zvyO?}V%t1orgKgqTaNP9_x?Qb0e6B^s$5mqG)Q z+jgS8Cv}{2tDm0o+u88nu^)JqMlLa$p(&nw;k#^&6Vg|bY@Fc!40ozUXyxND7!My9 z>|Qk0-V-sh(zGk2+!kiPm(YZq414q3Weu*oQr)?tIojOl-fFM@et+rPJK2al)z(*_ zYi)}M+LJSV96ES$bBpVw^N1x4XTMr&~1`p@>&5IcRwyBj#w}rFWhvq=Oz)ivN_%T(v3 z>y#&WF6L8rxA(O*|NAP2UdsQ-|NVc5-ud6Ucn-Befk(s#Z8cEfkyD}>6nJ!&T95hxN#?xD=olcgC6xFOAJsz(N z^MR_xr+6Li0c&(VtUSQ?r;2MS29v3cPVcsxJ+;DWVAmhRmlH8GU&;?Ny`R??YI=+p zSfRN72Y4W_4^W09F$lFS#6TX$6iPRQ-|v{nh4@l=H*`EJbT^!~>3BEd`yl0BB+H`t zUKF4dR1SyzS2-NgU*&Lc&=UJ`kXT^_f|B&D#tE|c&?bpOvJwX|s=B2I$=WU_2Pyi& zREMd?$$-N&^Pf>L5=pegMD0$g& zUL@P;aek~Q^+`dZs>MlRs_Ch^2QyMaVUeA^1x`srn8$2!!6Bt?F`wT9@3~a9$N>r4jD3 z>;f>{RCeAR5p;IavX5^{Uv+}tNZ)qx<*~{>y3SIiqPbVAx+`&Kt+dnji}YsMFwA;U zj|CDBZLcL$W@7j6=oNi2l(%%+PE*y6xWQgsT&;9mGgJrjenaiUVU*l)jS+(UQU_rM z%fVagATC_nUh*xt^a(DK0mmuP4==#@w*hIE=}G#M_!&u(%9$Ai#*LggRQAf01rZ|| z)^W|HMQ0)TGwq6LqB!s4<#!L0rd;zQ+{IPaIapUQN5oZ^v9h*KXJE5Z@#DiLNzljr z*3S^qmf=7Q;LHvtW0jj=v=l1mELR>E=X6qWr~PgWbE&(VhuVhg5vB<~msrY^71yS2 zj4anOESor?Of?D`_fGwi&D(L^)@9oz+W`XiRpwV;)|+ku(vBMCJ>-(_mjz;5W?A^2i}I>9bZ`zQfQe2 zesx3-_(lM8Al92$A^(%kcm!4hLn!6Pcpz&*MsIdfU*`I^Kp^k^CPF8g@CiJlmWXs7 za>{T&IhQ?)u&_AH#E=kG-E~->(ZZd1sK~qs($DYbyFz79qV#+>Tmd6+SOZXm-h~R5 zmZ&7yXZeE+b@tKTGq_FaH@om&0?vv=HX7Oq`O=%@p7gP<}UZdR62%|5?zvsWZDOoqL8DUJxnI0#$4B2V)_W|*;xU=%#*#${KX<^= z))%ij=n6BD%7>WV{caqb+ajzhEyp$&`Z!gj*;l%+FR76BMIq#UAKim?6)Ud{Q21a; zCG%3p!x)FkF%XWq)xUtsdQEj_1#(~DEtJ6J9f1?xZ0vPf*=Z2HF-OY@~pwN`Mx z@}8+&duMs>R}h+vH8|ep{b_CMVPTa6_1U=1sPf=Ft91AS<|j;=83$f;kTcmrEnro> zJ@Z~PjaQA2Q%j@Yi><&miPl7^t!`}9x#6c1uu|&_&QgAxL}}wknYnoe%No9P8OR^m zcxfT9q6ppGxnOPX#!Yw73*9;)Qw~UtpgXdGt~JTDBB__qsR>N3^qr_8W7p)`%{yqX zb}n*j)>`!jHgpOKFzENSD=#0yc0Eg!X)0N6Fc>sr=wC)DtXJ*{O^!!9&XR?0YWN&ZK3(|%XBTkcpDCL-{S0<&HnHmDicz78i zrUpzLc0OgTQQ*W(kFc_USDqi8Qe)5fh1cc5i)n(&fcwC;twa6B%>`m+TVo*-3!I_* z<(DMp#6*jVji^k1=eBXAj63BkJ)FJ|dMrxVC|7cxifT`#E4N?nRjOspsk%;z6{+fw zC(t#S>~W4wsyZUYeONhs&@P>{JysJGTN_SQuMHNNHNKr&Q*tLwUNt&Bl%?+|H^**J zR+cspKwcT7W5gdN?{rE)T}m|9Y{`Iepn!J?`@`ODL&>%DVoY8KbHDF=i)rbnki7}l zdf&yIY8hmoy!qD-x1>!Sp$tQ=t=#x`_=`Lt?9~R7a(~mX z5Zk8z9lf*AcoB5uj|Ls9kP6y4vpy6QKBxK5{ItkK_xDhjUY(<$`i$7*ZSVi)Vk!m9@K` z6)$|DnCj|quN=(qUm9`q@w~VyT^rKi|M>Xx;(e4O|Dkceau9t z|0{$Fp(Iihd#)E6KACRtr>>`k5JC&eJT=l}aa|vhs^VQg+C0#4@iYJ`oi*qTfsN)- zEd&)QP(Of*6b#KaqeaMH=3*re!E(bUStayB$ciWrq~z+%4wC3hT6ZJ07xxd`w1NSq z;zrq}hr)oWlOiGOE-EuY`$@o2fbu4lVwT71h*^#U5V|D8AEC@NH~7E2E6PraGF?vp zF;`Z0T2|5Y&pgDz;cDbH$gua`{<-s9pSZSTaCYNr4~9NUn0tWOMSNb)>A~B znb`AKR9@aI&KPOdIWFjx*uQ63a$Li!xlTI7<5596%n_bRI>Mf=O*+a_CS5SLPWPs6 zMOzJBFUYzla#tI+IgUCNenDHG$48QQrz6P!W-Hl+xp6`5 zE331RmeP8ohtL&PQ@hby>VueBe%7Ofw%o(R+Htt`;<^(vFEVh`_KXgB3-E@AynRfZmDsI2BUu6^55 zYoPcM|E=byX#oD0dNdG|a>DcCsQ*fM%n#i}>3ChgEyYtT&|p^+V8clW#Z26bR9EUp z?v(vSX*mhG?mFmuWGZ#tQ9_LLWwYmMO3)T{U!_vvdF%SeS`)J!F|nAC!YUzryU_Eqw!?{mSnjopg}&w zhIpgL$c(=em=^8}#^7mEod12jApv`x=#O}W2o+jV2=_&iT`DFE0t?b>{IG7AMY^Z} zIQ6esPGTJXHx|yjqy#I$!+Jrp23hm_6gSSJaz(fLh4(5nj4KTa>8443mRhv!iP>s= z(kU%qlKNh0H2M$;l${M^lu<8(rEp4V6~>3360mxjp@*YUZu8kFM8_gS#p09OiXPY+ zWCV2U(y(6+>D|dD`Ke1Xj{=Lu=H15~8tdcPfZ-JE?XGklE_3O}J%`*+R8v0qu$k#? zlODdvnIoEhiAs!S$^#)7m32E&z58* z3VvxU5GTw%_v zntQ&l>Zy3VP+Di}P2~zgrnB9U!RW@5YR`i2zn*ZGXuTlLDC*=3*-0^ryuYb)SrC>8I*b+ zjoBX4G-%a8!F@5L%AK$z+W1$l%*OL=Z5yGvWk}}IA>(cRShl%sY2wnQ{cYnqy1C3IOfm zW3>0-+1*DKeC(14a527zxC51OJj)uczI;c+oNiIAKOrWq)j0XZJY&1Djq*QJi<^eL%5@_{h%YcfxN75`o zdu;9Ywd?Hx)t4KKwxB~HB&Yv^yY+6mKc|it0sZ;z4ADd`Wpjb+@yZ_!$bFxk$n$&$ z)`)ZDv%uqVe+u44LCse?@boN{-JgW&@c#aZzCq9TK|xvnCzL1)p&}43bvpo#j&jxS zj{wL-Yr$%^6Kp>*v=g!cfZ7dR2ov88Q+5A~fOsv)vqNhNosZ}lpv+fbxike;)E|_r zMe}?)FtYvuaiSErWD=AcC&p!-8;^c7L@F=N2WcXrD7&)nO`++N%dp;nD20kCosrh)p7j1Q=p1slPf80T$@xPsR6Zinxdz7C(g(7{niTTOM`0L?Y zC<=Ibv>!bep$0ls5A;a2zx5jdMBi;2Xc{a4-wPT_$gMdDJ~nWbITuU;QvgjdHi$QG z651e@0;?)ESZkL6p_{1}FT5ZG$Oi`fEr1dQ9{DCz{O<(BU+tj8E9B2V@;BtewexpR zt6W8BfuhdTXT<12kM$THah@Qs#>_Bh#DkfVH5=EMa@lX;khF~!l}0=`9~Ox}krD9R z&qw=!Dh``hi1m0kLSir#rNp0&9n;uDBA|+MAX4uJgL!8Wk?jCE5sStWl-~BOrniQH?Rv6 zHem}+OMu&FGN2MS!-q^4ZXoxpDHWE(E&$B;hmx%v%Fx|9M103GTOH*T+5eotn&DME zv79F2nx_$=y>t_P~_H3-OQ7 z7>+mRr)v8i(L3gHICsqWup%oByv`j)#Wm)j01XGD_|X*VODWwdei>BGCize!)bUVA z#SblXTR)KCPgktpNG#CaJ9-KYS6b09Qv$D~Ld~HbEO}%JSEjSzZ-egATFX&8}?9_JxZ&~#$7gPaZ!KXS0OK=%2Y#4 zU6~{Tq%%~3U2$w&tO#Vfm^ZeZ1ac-b7nmd&YeZD-_z#0-Bj|<+HAxOeVOrrFZ7P0A z)$B51$VKs*V~-sRF11}%&0i=A*-8y6x?nU50%Y7`xOLC`8?x6gRy`=1$6g#L+bJ7~ zD8?+&9&}UWpF1gihw3-VpVi8LS)6ln5=-|2*@KQQlKlJH;vm*se>bOJ<273BKn8&I zHO+p<=EPNwaZi;6pG*qvUKv0j!E~XLT}bzDA6~`A^sP*5ON5l%8PwpUeB6jlcEuK@&aWz`BV_`X z4ps~|=0!Jo(M^Tb;+Qa%&y1@G-^un`<^FN2nzUxi$@v3&EcnH4S{j zMt++dWzi4X&6w4_`xff1ZEO^eQ1TjdkHby3I`7BT>ye9BBttic39GQ+Qj}e!Q#qnj z^$L*hq|aRFKl-MovZR84o5%cdCj7KG){6kB6_FWdzIt|V1!cxh+Ko-QohQL*YXb_c z*^Z;@milj-ntHONtWr4jrUrktK#y2D(pV0N@HJ@`M=ma~xtHH<+I3oMN9H4VMqV5= zci&Z-kHNebUy}cgfY4U>L(2+FVq;rPj%$Y|M?CNn)gOh^n#N%|Qe^G@Y#cYo6-p+i zRPv{@C$ZdkpWDeDT7h>V;*KpEzu9Qg-o{bXH#3aex8>gx5dXwqf=?)?H+vQe=E+Ki z;9Pq*bM7MSiM!w<+T=NNaRCGMkC%t}XS#oWdHiRJf@*eGB%bo`^N(aQy_pjKvWsJ% z4i;PgFY}LIHmDl@W&V+#bTARnQ`Da*3EIUul`K(!7Hk~-YyMHH*}@v82{ivGS<6Qj z(FB@*WLb!I2F;#tw3CMIZnVoYY;~iWe8K0LW-#aoM!b`$z@*)bVc^N#M@A=AH>2`e_ z8eS!sRdtuS+-*Hz%=_+uQ*O(F`I~L~6&)0B`NN4)?gXN$n(qW*n*LRNa|=;Ng~OxS+Xa-XZjz*K`ft*Q`%aSKrkH8kZB`h$r$v>!ZidbO59tF$QS@guB}j~Y z)+Z@8cYg(l!S3gc$Trf}r;4-}HcHBHTQyF?gr8~TZjM^17Wj-=)E9?yRaG1&qtTa~Rjtyi zUum+?w`NY3)6|}9!j?8Y<#}4^bizbdrhJ0*veSOgTfOepCii0K|Bfat-6p_Aeba*4 zZOl0Mrwqfc#LGLgDt9+pTCM6=VFlAzek%{tI9*>kQx}UFx#MrnltIU-vJW^G3qeE~ z%^6X(BimVI=kG2cgatoCorFbS2i}Aw_rVfdS@9U&(&btY6_-_^{0o{+ zDMaI@0-Ne{oAoafcmUuA#}!WXx)n1#n<)2CsQsY7Qf0-&IJy(Z+#oT6>W(cxf;(dX z%E)s~bby!pp^RbD{_vRdK!Nyba9zIh3D*nm_ z^%?<*!Ao(FIr06buN-6hb{rPmTX0UycK*y(YYOYqz@*ySe4VD??TOgHmi=YhlK?cV z(O%;A7Tt;X$K-ZuEBoqEOw|{tb2HJc1$@&h-5lim+(h~lqGRm^CiO?k&niD0cS10b z0TtS!iVvMf3Y_`x^N&Q)j~JXt@u7iJOa|fdp`7Stu_R=x+puzdzcC~OgLmq4hrX2v zf!)-E0O5)C5&Yb~#Va5NFbU@%`GyE%ATzomDuv! zZ2D{h14$_L%h2d#(rpsEa3LKLme^U#c)0J^*=NiZW<4f^)To4mLuJA7OsqLw!*%#g zO{fX;@Y(cI&!WD`=45?E$Bgwrcs7P&8RsCB0w-$%45Qw zm?A%8#axJcbn@h5NBxy%zM3(2gEXvX4#3iD8P zY@(cOgpwqj649_x!hB8C(j&)Gc{fQSRbrVkfu2+O%Jgw$L$Px5T?;iObEU#2@d~34 z)9dW7i)gtj#nXT@yG3-FpxTV^p414-SbQ}V7srF7i(at(W?Wo|6= zv^7O5{mNN&ZeBJbpM^7e=<*){BM6n&c_NP(X6!lTlDVQ0TFOJOCsA+Q#tC^;|*7`s+RBK#-jg!ZjHll=D<&)Pkasv@jS*-PmD_8n)*%1+AF%9Fw8Z3osfNA8vyJgTt|m zLPm)z%Y>UMOnwPk#t4h?IfpuXNh@z#vNo;V8lrnCRN{Jc`RL<1O@t!{a1bk$0sTK@ zAm-B<7AvQ3t9)apbGT-3V_$}F{y|JCLK6nIw)N|-bACzV!VV;Pk zBu_L{IK}Wrp1e;;%}Us871MpJ%I-bRm&i7jsacaAx2AWacr|*LdRb`jD>@7=Bv#sA z=qX=mb~j8CMmctt81%h*4$|L2Cg7POGI3AfSKv$7wVXgmdXmo510butcfsRVhdcSU z5k-crW%uA_Cf{9-6ECcs$L^M4otyc0tUJw~6gR<5*}=Yk`A1kc`tM-Tg^_N zEi8%dcuV>2U@VDL_V_I$sJ+BMb8@`(Zg?VFC}I-I1LSCcmCDxzRXCq5)fj>1?%F{f z&KAqjiHVFoo6>Gi*c412z|GDu7CwI)PwQQcRSdQ2M+evJ2~(wIb@;~ai`UKZfo1E?WeVR%_NDbitT_gS-{!;VW=YR`@^fm^PpG261=)GBV|_7L zrsItO)Ogg~z+;t?9We~amYtAqfMy4U(g?02oLc`339_7NDX$wlZiOt8+HS>6f~D*jnWVXFrfBmt z1E{aySZjQO9KZVQjb>%n0R>xB(*sZNX!jdwE$B*E3A^cgTbwSf!eD^)`1b;QF)*eU z95qcQVG2m{y3sfM%=Ea-&Uf=jI<yISeD z{f2HNv5mQTCK~Gp?>auZ^Tn^D=MNQN(G#H+pT{Vi1I)f{x2xgEcpGB=q>M4Pic7Ln|yy2P{jJK<0vYP%x=6Z(^* zC^<5Zf&wYv0;q#Z7w-5+txx?~%^y#q#DeKPI_X}tvbS^e~6T_=3mHYBq%AGr^t}KDKB! z0cR1GzTR&_PxLG!psJL~PJ2X@fiY*{p%BaVInkx*R&M{=J`dh;|$10Wu_x#LWP%<-!ue2H#WQ@Kco8^0G1pI7r%kWk*gA9+K6J%(#(ga@cA*E?l%R4B9yCcrY(k!D` zpjBToqc27K9MYn!n$#+OvRjZuSkX>@Irb6)5 zh{uizLr~20j2Ip7`oQi;Nwl;ZRn`>JOKau%eDCSsyVfj(+@+UoO8Stg3PGgS5edHo zNcW}xY<%238)&hl(gihq!x>^-rMO-1+D9Y>7%leJ5B}+>U3T!I=0rC(u~GSEFD1>! z#<$X<9CRkx#BChN`O-JdS}xGcb3l(WKdQf2RkF1`Y%&^;G(oHCsM%N%&sL2&Jce_S zG*O{Hclgo-=yJ?4W8^Ger|sREj$h5+sP7iv46ZGw^)S6jaWUu zs36L{sD~N1>0pk>XL)O$SIPpxAF}NGaa(B|^0DGHbZ1QC9B#{ zI&IL?{=94`%5OY15hEkfzPmDPoq8lV%`0WBX;yo&9}3Z;VnTn%s#gF0ar91&Z*PpL zO2|{?-ib*s-APT#a>*&?`&rn=Olavl<6?p;9^R54K!<5L%7b@iUL_2Vc_l2J4rx`n z^a5i&NVT7NBwoNELC+c;ztU7wyNe&W0k>!(GnuVz-=}@);1{?KyRX zxK;N0duzSxjHXBJSi3d594uA|>{< z5!tWAn0w@djE7Mnw5WMlcZ!3o$Q4mGiI}*1&$*nlH_>k6`S?$6_}sTMM{<2+Q}_tB`pL832JYF#lo*6ZN8V(zhus#1eO5uIz)-dFonPPGt#8#ZC~qk2`=WmL(=EXWqtPX!9qFimuS^i5XNG^2NeaS#Y5_vnh9m*4 z;+eEM2X)bvPrOckIJXb42eamN2Nl|iogpn**tckqhS^t!YNybug@>R46X@AWK#S(4ia6MpEoWKkK09hE>j2Jq&{~kS(F{!W;#4|?zCQd(FAFC zK)X;!1j+m2ylye)xhj7xx^UTQ+e!v&5rF3VE)G+X2pIy?-G6;WA0QT zm$ymer!J2(%MiloZ0J$Q(bcX~JBQ7qCD^sxH(JP;v(B}Mikz7RM^39Z9G${E%KJsPAS%(tJOrk5HdFlqm)Kgg$8eXmc1dhs(uM z-&$p>TS;zdVR|zO`a}?AfRjkG@=dHE32+wZKjZtJ=xrgMMl~ty4rBc=9%h;Oi;g%^ zt72CSvp;N@6OlVjg^gAXgxBwjCx%a?b7bdBx$k2?b1MjnR!k=6S!QFb1C}&aE2>P> zDk#>E60=m6Dny|xUU1Gw4!P?4e6~pP;iRG&J(DH}9WwJfk2!4JS7w84nu1nOjD71z z`9!LlEf);!pI&tiE+1QaU~ELczAYBa)is*+@40siZ$6Tr##?4xRYW&#(S_Z%c#~Z_ zdsb~fsodB*)`kKlhp!2jo$x`e~8fe5m{6Y$@8v;Pkd(=&~=f(ZDy50&v-oI#XG2U)<)he*=Xf@og zO;R)FOKQA3!zJ--jP86BzR+m}dJ>i$eL+Sww>>^d3HWI?3VQ{ z@lHG$XwGr`fPToa`;4GYXaW1h`IpB4)H1pYJhsY?r2=@$TF|%4exd*@3y!%Eyj@{p zz;u|KVHj&Alx8?~+wiUwlLv&69qVKXi3IzXmc5v@AWYm?;b0152N80Ojkp=A6H-N9 z;^+NDP7#TNBz_GL=94px;HT&(7nr0P>mD9>niQ?XhkpfOw=`qZ%V8!~EhkZy+ZXhs zEceHk*etmhDoQ1@P4?s5KrS-kyhwZ&)BIRI7^?huSuQ|fGE6u~`nMUS%Fg!TGAqeD z#5xFdNNxjlQXG_=x&bTx${uVCXwFnL+^QW1mmap2`FFlz7X|vkyph*xVx56r%lNRg zITQa=LeYB~$d1Yg0cwlq2t$opLwypbULbo#0 zgV0|2{T1En+8FAt^tv~Ob=;N_QD{B2|C3T=(Lj_+rnxGLV`Sf$p}$C zPwFT{?RwpqtwdX`y7X*j%0w^|Sk|Q5%{j?r_$FL~mY|e`lhSvB%Ec;4oDarNGTfba zBdYnK&Wj8-6OB>2zLi)Vh9utmLdGdspjbmlWz&iXVx|15t(IGpkkb+!DsauMUB`OU zqmS2d+vrWk&G09Ijp{DFT4jrVH2q5R+ zWbz4bN|uLq#n{KgncYB@(?+h^L2FN|QlsbZPKL0n!{ElrrK=B!Klj%bIiWmq#X{#D zJNi@%m-pjqCf+T}3HVEo+b-1}y4#j-`a(wxmk)PoMcuJ)h1GJr&DYhgjxXOIhPnzL zNanlaR!F2ijXm%OpiWc2X6QYmQpSEDU6Jg1Zfg)z_SDGN`0KS2LR|TG!>bks;Ag@> z2`y(I$4xs7a_`)54gEl)XD~u=%9S*y5Ne;U32rpn`DAaNjC31I2_%0v(Db4d&H@dC za&YF|+l2N%pGk4A6Zi{*+ck#26iCVZxFRf2GPp zT54}=RUnSNg^AkLSS#TK7LIqoQNVM`7!8Ue5CBM-sSzA(g4TD5l;Xuj=;Otr0(VJl zktHN#k@+>(cgVEv`){n{nX}$WahU-U3O@9S6r{=7ij?FfjiYftlnz||p#;=MVUoEL z3WcN1X@rucQjm5E>1>0f(m$gR_eN%fhVG0xX^4f@<6{5f(P%ZBWeTgzIE1Jik%TmH zlq;OaVK>1@eDh04kHfV-E$MXR^R5kfCxIYD~;k>Gj*l-}U!8$t5ay|ed=JEA#R zLGpFE^ZUGT^y39EP`QEP_Hw?@H$#OS5YHUQRDR5T6)NseO`lIhgyWeWMjxAQn(x(N z9w?tKl?<5A@8Q9dm@=8w>_tsuG6CG8hl>J$jvPmDBYCnM3Xb^7M5|yU@)GLvE@h~t z0xGAtLd(iI_zOOl9aPHT5+Lkx91zV+&7WK z(hd{$F9$?THvPt<*K*1r9H<*^R+6%zh{!-cWjb&1PN1)=;1=kbEd@O9(NF&_Up>D? z58Ll76YVdrzxQ|U+}z0Y)QebNOjqQ0&`I~cf{mkjrR@s1ul9L)o9#${~@V&BQ`~W5e%TJUZ1(-HfzRi3_KVhs{pBl$)*3`D5G0JmJNx`NS z9alE$g<#Ls=Z5MJ{c7)9rPzz`p&+<6VGzl;E?h{W%9e35sgc1VUvB}Z_>Gm<9LQ39 zhMO*T#WL@Q|F9~Z#UQgavk-I1a)aC%&j%U3NL|~=fd}7->Wj2QHRHVTvob|ojoJCj zW%bVQ@u~~@=ikL94>OrrdL~2nN}hCfWBg> zT`81IOUPokUPkM~hy=?WcQ8&oF%h6>eEH12iZQ#_U8XXB84pR5p=p2RqeEmr?!j>x zXY)vlw4p<$8<*b1ckfGk3}S|r3?7dbg3@~nip~M?ZsWOT-jSdGuG-;W?SxGK6H)&^ z-4y?a_iX;Vo5C_M4kYU1c{W)uG}@lwXL&Z;PIQ*n0eS16ceWs-v?9Z$+;sNAlW;<8 zyU&xA{8E9tW#0}k7YW@CM5TYm^+PZ%Squg`AMb>cCY$euQ5Kc_ zEzg)82DKMSIs(ca5Ul(|o)Pu4TTYNL0)SA)3Xrm&uz=M{phGAKIEXjq1RNwO;!}Q$ z*Ak>MPL+$6AdfT6hBi*OxE(hA$2_B%knN?rVUABSl=&|b1WEJYVEli`Gs2NcmU=7c za^)AMpAG_wo4jHE*PhKk%^e7m8mj6)wqXVvel9Pp3Hz`(R~qIjI4XvD^EpJ`h62d8gT3HDrSkxsI_tVuHrylm5hexdKs87ZglbXhs0>w1wro9=k` zKvP#`R*{M<`FzA;rwpzZVb{yEAa*SQ!4GFI1IxW|U5Bm*r!**IZDloN^9q~pH7h%@5pQW}F*pl%{F}e}L!*VIPSWu2ODo4->i}!C^(>vgM17Nh zAofrXq0wel(~nA%?HGjYJBNkQjfdILBXP#Hl=Qf@y+^eO#d>(4;uz2NH-fF4s%gxAgEm}QO8`Q~O zE-x)Bk^*|A_wzVW-uD-)vYijGemUNrYkFI}S9@;-UfQ2M`1fDI^Dg*eue%rcUTX>T z&ac!q7Y9jRUKytKhzF`_+KbL=8@5la7jgAEm}5K_!PTt@`!*H;{PVW6TSOrY zIam`aMihe>cZ^I15f>(Czl)TMiAseW7cN>$j2>m!6346HsdulA;nye}l!T;Vl(~%B z(;~`fF{uv;YcxAaS;4|29lelG(gD{b0>f4n^ZSj2a1NqU0C`qDn1__WmmCkaH_q2x zIhur^m8YB4!9TIWxPuA1U><6@YMl-Rz24G5|j84^uu@KjzEJ}OsD|)ao7CE)=$#`naQNlkEab7IQ609nz z8-ka7K8xx*ZV}H$U$9aIW7-o{O7OAOR6?_48L?2QH$2E^tZuKvimg!;|L14)G%^-M{K{vJ8jD4Mpd!%vyT;jEUzcjCP|^h*qM%kW zHfd>x}PUVj%Xj}gRP#=&j8y3 zvTkMW84WE3}n62 z{$}{Fa51k9m z#ZI4x6?K^$c-NKBn9@jjeah9?*9=j&z7<|oN&r*4R}UW2(?~2gvD3+56F82hCm#l| z@Ew)MDC=2~A4OCoJh3l&m-i`S>> zWvDt8Gt!-CimZ%j4LTM*1)C@fxr`BMG;V8N9IlnM&98GgS1RF|yUnK5HPBmEbAFuI z4nB?ayPRp7eDKi4SWae`xXfq58=B{9u9kl}O0h0GH{a77|03E{GURn(>&jF_(f^p4 zu6B}Jq9uF2X7bJVZuLC&sx{`o6izpi?!FL}6z> zm^7pr>c)uVR*(MjNT2@*;MZ=qM@RP1!4*diYNqFzIR|^`1LO|yY5JM#EPC@_nk0t{1&;QLC@t>)Qf9?(ZyNTf6 zyn+8)0fL6A^;BcQf|oMXN)|55}$`1a|+#5VojRx z#oYjgMK{$*wnKIFXs*X$v=}~!5uGqW^itwDQS!9;$O)p-{RCD<_pAhY)sy(dADX2m zV(N;m2PwqPVK}MAb;>%)CPi2?X}Tb4Lewf6L`~oh0C2P1OpVP{-5*zCQ(a%Fjs64E*70J~Q2ed+@;_1&8AhQ9Dil>_`)tOSt(k>m z({1M#hxGQ-1MMpvFg=zGW|UPA=Uu4}86~-&%*bo<$I#c8d)`0Fznj3RrC;~C8L-ld z&?{=!4j`PR)egGYuixkq1i+;X`z>nJjhKjx*O}6H<6e(4`Jy{cByB|Ajc>^WUQPBW zK+R13$QRk3MxRx8n&y=fzMrLo{g|GUCs;}BQc-KNTuiW@a5mQU-B4dP)N9%uFfmH~ zz6wC(y!~yB=(N}a71(x~qV)ff_ZCcXFzS}>mk=OG zAh<(tclQ9n-5r9vy9amo#-)))8h1$u?$)@wyGv*I?wmb$pR?zjsi~T(TT}O6tarU@ zJ!_>+9?Zs6M`EN7pfRO+V%`8yW7pJo~jC1(Rq!FL#j$J++aGcQ@U26a4x9e4Tdp zFx@@Q3O1TES$evXtbA9u7ZAt4af-f>_C8D@Xf-$zHn9I^=MZi1=+VadH+W7ol(S%T zfpi2^9GcH$Hle#BMmHzoJ-w7AVO#>pC@wVIcq$3u&?52-r1srP5_i}zxSP$p!Z4vP zWO9*?B3rNp57@NtDa%TNnB!JZ%Q7% z#na6-ed*0}QiyZznJN%u@ivQxfQ?i*fCuMH6-*&T8tXYY^XSAYW?O70ge@I^!eVz` zPLW=g(IWSL+AX^>i~5sxifo!)N{nmPXJOi*>=?=9s+$b=?|36?qe-cau0!-rnX>GD z;c1;Wn(wDzM)lmoGdhqkP^~6D870m1HiwzzMho^li?3N=HCur+xGP+57?Zp+tS(*y>XDdJtU6;bB6SvpK z2p|xr9m1t6kCwnFxx=KD!U7FuE3mj7-5ki{;Lb#ht9^rEr`Zdm6V4%PWRYmxRYm)# z=6{u9#!LxPG^2y`&8pdI*KJIhF3#43xP}y{sc4aYgp}BsdJb{OkQFp&WLvBA69pb? zD=zf33`aN`r!Fa%?r9c`-b%Z5+Gr2(X@z5|QijXV4f<|VdNirm(uu8`*i284hJ@E8 zuhNh%%FNZ-r%~nfjao3)>Ey-DRhykc>A0zrb?VjA8k^nh6pgYLZ*$zK8XM(oN-=d- z4cu$D`6D!ukP3Fln(~8IiC_aN1P`(i9>zChDgeiUXlx0(7)d*CO?St!eu z^UJyH2n=jhVTasrYMk9+Q*%rx1Ublz0mHr1ylEO>$iJJ`vRJzdTbsXExd{)$J;scH znh^{!qatah_P}P%z7?RRrR0k>POHn4-nIM+rk;*(QU-n!BwfL78wV_Ll<|-4vbq3N z3UghrglDZB{uXS;d|%QFTJ=Xwg~^PS^m$zr&<+W9_yo z#nasT4EFO0GETlq67)M^&({O#PtC}+Vj68YSDas1N4VHlicna-k5uh^E!2f9aP9bv z`VD%RP}G7-n%hIE&!~?))=tkT+BuK6^6c4^UZd(hy;E>Z97`iV5Nzy$McLTp^Iai` z^lYPM8V-DJ+9;gn>G=69aH_`ovW;_p-@Gkf6C$}3R(QmK9q_3kWB+N)@72}2&W|zc zetAfy_;K7f_bg#lpe&O7X;9kPwW2zEOS;P|*<{+JT#<-E4EhLh^D?P$5CrSbx3S4q zpP6LMo*aquYk5J@>Qv)TzdGFcZu&d6dSo{nY7WC)+mlRRy^i|-c@B8b?AZ$7mMC-K zM?*E->Qxl7%{HY|uC-Phwz!IDEb=rh&y~C@LZjss6Tf1W_2tRbzoUO$m=~P*>wY zp+3Di)pm>{#Bwo~=gt%7Z%ojUr^Y9&wWuBxrY2)HL3Fl7DMJlz)%l zcRSe4Ky#y^%CuxNrOHq$IiwP|4bwJamFnW%1t4S3mE`<8_!CW_8}X4@VU+V*_fqHW z0h5-b(DnwRRC()Os@M;gC+GTuOv?&&-sm}^xly2aP<50^1+=>Dqs>1Ly`l5jfG1uN zE1h~!2)1+k-J@*(neC0wXWS@g5Pcf6WS?c+{1!X6ZxQL?!HL`vwJa3nE6K`eg=suK zGrB;0gERy~bVoZM{%yd71z>@~p!W+d9A`fpp*zg+HwyC!X)iUPn7NNZr-P~g+$x4? z;J_;zYY*xyjb8$733JOoG*5t5W<;t z*K>#`^bmEKP-}*+dJMeOE;7BdBpS!B&Pm=-+8t|*{LviZsTzeb*0RYXIIgzSgpK0i zn^m9nF!#xXX-c6YF*pC9wh$%pFsmL~yOIx6`6O0-{i%JZbO3sO@)BvwFVaZg>r?)_ z;;{Wo$y7%4kr6z?8%<8#1CaJt+w_Azq~4*A+58kNKSk(aEVJ$8L$j+B=3-_7o^t z^Wb4KDTPB9vsUi>J-^DlL!%K$i9!9;_zly)I8x!{BW6)cBCQH5xkdw~ubxL=v#qkJ zJ|;n33$8?iFPaB4!*)XdM+r`MN>BR}B*N{Mx1qO7$)WZltE-lNvZKWKO@6YwzqC^s z?xGUZSSi4td9lQ~5QWCy^7sp3l~P@%Nw~ojR6|_7A{x%gR>u^#9JDg&sVeafDamxN z5yKMI&Z$)lBlJdiBh+JTa*YkUjIfy_22WS%KaopVQcb0Qr^KZjG3*7$4GkO7xmb*H zEU`?OiN)ixdyHZ3vD0J9m%KVVGJc?pfXk4l3vyxKB3yk@@*3YMu9rKWHQ@(dk3;&d zAi+I^2qwin$YMN^|e(qRA9^HeX^cD=*aK!xY6vvlm{GfsVC*ag1A`og}eLIA8&6! znj**PC{fcyX`BJ#>NYYzbuenZF(iGCLFWL;5-RXVGQQpcgB#|_5-ADE*>xaFZ9 zf2KkeJ#%9$dj9?=NNs`%g+KwXcDE>Ueh6%iQEB+XkPvD^EY_(ByNu3|J6F`U3N&hL zZElXckBhcm-ST^dn{M%rMqxWp!y*1^*>~KwMV78qNwJ+;#ruw%dxZXBdV2*F)+;!@ zOY9~rtsVLOzABgDZ3PHV|01rgb>+>X=K-u*;h*OpEz|0GAgmi*MRZ~OcCZ7w89o8j zyFJ{_A5Oy8{~T%l{B(FOv#6ynN7e{amKwGD_Cj60oyXAenYc|j$7SWg>+ODMR?c}vuHn5N6=ts2`e6W#PQI*8TX=$xJ3y z_lSB5;e7K(D~*$u^is3|N(kuC)92Bs(e31tI}%7sQGQ>Y4oXtP5a_Z z@mEQ}0hAWrh-BNQu48ja<<;e< zvKb`Yy5r3b9?AUZB0a3P`lpiDOHs&0%5P;V=M!ItEYUz-y!L?A95c6QU#K0oG+@@t z(8V!Yi;s0_jZZ%C?{*&c!TS%+i^+ebN&eM&`Tw%MvF^w4=DZZJz40LAlK-nf!S| zXnb;gbLBRgQG(ZZ=N9cy-M9KiYUgwHwBLEm#1h5pJ=6JmE^`xUK+^-H zIM43S)NY>ND=g2ONFX+A*%xW_uDuPzkMDUl!_bWyRow~09umVz;;6SGkwn!C zBWaoj(Ie^GXo_T5(s;IGi6ILHehga&MPht(AGYEd;W8%^zU5j{#2-)|nrg`xgzY5L zR9l+GtC>qrrfOOrzELUrA9mA?qIgTv%$7XOlVpjDwL)#u=1W8zsBrhgT(H$j!#or+ zs57*B7Wdr)P#Q`DgX4J33K^M(4z+zy`ehipb#hrb$*(oA3!LQ-|ne8R|iN z1Yhc=WS)r|eRb$dbH=QF2whga2?$#tQL@CXi-2~8wW$&6@w$lJH#z*JDIxN{bL#jLhEhCqbN;VaSijQ?8 z%mi32DeUoj?88LOmDl6GtD~4c7lqNOrQ^K5B~y|Cr8gOJ!mEz0KPBXETQrZY-P#7F zPl0h9%YAwaN+Y&=HYR^6nHH=uY^fIw>tc7UttMWpSLE9?JfLeejX9Xvk`|0v^8(9) zSQ~-lrv#gRzdoUFMN%_9Sj5Hic(F#(cM)uhJjY`01j>$cTCNz_V?a*K6zKL!o^_uN zgbQ+AK#3E;)?V;BG5<-qMHlu7@_v`MPR9fkbl!v-Uwa`l6bB5JgVAr5{H6)dcmbzK zGIO2F{z$sNX8kAoGD|e1>I@;eTluzBKi>atby-eGVxK-9{Bz^CZ|!jvNy;B({AC>I z(|+1tM>aneen-E%4*hvg>lpB0ye>-d5u3J;*#J{u`lV-v>$ZwEcufpzy~9}K_bO6| z{ZhWR3{THTkYf;Y<#%{yEil|tJbmca?BWNhpX1EUPz!IB_EG+)~9fj75C$0!+Ai;pN*ywXPQAa8&lck<6hHyi=6ksGV zwU~60}xJhGI*O#~~HKRZ5v0kWLdJlfQ17uG*JU-I!MPOIK|g+fDJuV^LYJ z+pv_TlW(&F^@ zu@U`hrJqBnFvToPEJX>Qls=-V6~gbDi%Zuj?U<=$5cqAe2X0A4k-6kNBJ7rlB8s`J z6&K^{18Lyjt0o}2mb@=7C&q}H2|{7aXTU<2*)CK`hifd&My3->&{2D{S(L>5lvO3Y z`%|HN&Nxqn>gVrEE90Ch?lDU=D`eVpdf+R*HyZ2wiJYs8x;FJ(rXG)Z!kX-COq5SO zRy?$pGXgZQI+-2o*{m4g`pI6l+hgVDi#zi@9zAw>o8D1Lz23ALwUJoF@If_a%<$gpXCa3;gN}HPJ*Q_rq;Q{a5pXBycJbNZH(FoXcX)Rf3DgOdJC9bV`du4aUI036OLO(PJM%GgV;}@x(n{c-*g(%?K@UI<>jqz){)yDf<$qc# zhk>yA^BBjO!?b{txFQovVRC$`*wf?mp&JWCefzO|J=e7NL{N)<_$jWT<$z5$F_+TQrt`A5Y3pE5>VcfR1 z0c5%!!%deF*9a#T+#F+1V~!Oc_4G-&#v)FaNvr?(lsU|#{CG#`;Xk5pqf=`!w_@JR z4V0gRo@vh9rFam-pPm`F>zGZS9`VKYpV%F69Zj)aqA`^SCHi4=p?jmush}1$~^K6z=@?ab{Nc z?iO^oD@D z*l}#{j)fi67AO2~8gZUZwYTJC&eo_p!5!`I|D~z=Up$%q6+rQyOPzn%FaK{-_1^&~ z{*N?O|MkiACUxFAFRB&)y{THU+mq|3#dW&uZ@0bXr^$W3ZiXccXE4frncHJVoG0t? zII)ABgUZ3wNp+JvU9LG_?z`c*vD6G*Mkc{OR=C=VFZ^S>w)1o&y6$1`pe*!~cgH;g z&iLa$4$VA;|LX@73wPSfm=tr(1CGn`bC4}<#)b$opQRD&XD*M)pjG{Y0*^C!T#dIL z%tJvqo;f9HBr3pj%N?tWW?O=F+_LB!=USPG*c*U?TvQNhSrjRPPDA1Ftt}%VQF1HI z)D?wwk-|>VcpxD~wMTslCA8e?20wVjXm;$C(@4_;Xq@uoJ7WjGmKNosWUVwqHw1f5Pos?OhOR%>s3aX0%$e_c;WwaUt2aWW~w;BmesMeylnth^zxNB+CpATq9U6ekw6{ z=q__Zax{65+M)Fej^s`11RwTO|89+ZVCnVcjiK*L)n1|RPqR8XXqyMpP4=3fHq?Mf z!HulOR9QZD!=q!ejw8UJ4&X3Jy{Dk`DVPe^s%*~A z;~CCl_?uQWRH9qRsQZ&cjnY2;U5ddqk~;($Pw;4=FEeLp8uJ&U$zpgNRrAvHRL(in z+GB(fca5ZVm3rxI@CR?hmvk+01GWGku;nSzg|_{H7X`56Zj_6*`=xHRWu4E*pZ9>n ziMaV72P2p9FhP3sR@cx)pm0=$9IO3gbd$lQuf*l#X}I$??(&=$-jMsuvTjW?3e&~_ z>-salBGwJEtT5Isf-W=G-J!8IzwDCO@$0cf=%15^pY&!z^#?fMuB-c|?&q6}`r~WS z`|gP*-}Q>j0MS2UGNBKWT}H4q-wJdLmu|Sav0Bfy;Tcx;mF@}Qgo*=9qc%Q}0>8d! z*T^0grM*lB{j!U_V4&=sLl{fyv8k$IU~HH}`h_m$qOOI z-^K$@_i^s^MZI_3(yqzC<8_9Q3#lbfy?784Vp5Ff;?Vp2E>cWXLpedCphzAQ0Y{>Z zl{(IMN1lvU`WUw^DfEV%ihF2C!TTyX|HDQL4KEs}%!qTp&-ZHyuc6C~TEnu2XxbvI zk)qCNk7i^_J@fLB{ENEuhapN9X_`@^w8~WG)>Rg1gq}{kYgf>96kFiXXqa4mI;11% zBKI(z6qJ-@cUgk-D>H){)>w~~CdY~iuoj;IswRFm6{7XgvMyMV}~ zAMcTi&O3~yd=8YP5;@4Am@xw?ph~*KxR?PL6FgyWsocy1;O9uI@8*c)S^dbR?{0%0PLPy|8rXpXw~<@fz8rDXs0~`(vU);HV z-Vr)-gd=U-Dlm0(^p)sE8m+_dYmvjwF@N(hrl##r^S_{6GNvyTuZ!z#(LQpF6pPWp zu8{4s6=UTPMrOsuQ0T{Hb5hptSS*=tk>;5@mN~@SkwAHr7iTJs=SEJWV4odA2z3?# zGAPkWN@O>RQ+#)+U}gxu=?6ks20D<98N+#dOen6Qx5mDEN+7TN?uaJWFc(N*(d z+nLXa4HaKcl@IAYOgmz54o~YIDd?(xD|U|nc|g_@hh-fD_c&;5R;P5gy*o6Mn(cK%r$QhXr+%GQ*-N{q4*$_CjcWfvS zk?l%X2)i^apMB$wOeeHgJqD~~rp>i>3Z6%y$E`Lq+{fI->S>YSE#67A6g=lxM#X9^ zKA=E+FxerBLN5>ikmr#@Xg+lNY-=2weE>Aidu3ztT#W7R(Vo+Gna2d#qNTnW9;h-% z@9HpQW#CndWprfQUD@sQcc19Kafw_8>_496HsL8;C#-uP&>FP0Wf3bEQ2XgpbDxN|CR@mDnQ|y@k|78x z6U0}1if1s*&|s3ob`Fipk8UQ6!v18>Q~Rm1YTdbZ&#~v?1Mz1DCqJL=R@0UJ3r@rJ zzuml}`Qi^PFyHXF=EO}4LSjlM@rFMwCJ4i7axd^jv!WplC#t?94M2~?{mx2}ma!BG zFIr5dzZrNyX3rvuTPRJi;)z7*(7G&<0sq zKb3%>1+}1siW+~air9v^rNyf1@$w3*j{b~eaNh0!b!{)6&u;o4w8S+{=-OL z6*`OPdeSI~GQ!y8;oqL$y!BqvzO^GK`pg`)uC3Z@r0to>_tKy5-0pPA=REmVqTQj+ z$NZVuHU!1i{^usX97|6Uot*A36iJ55UQA8f%RXF-%FBL2kCV%RFX0UN9b~c+&O-?~ zF>EGuK9y&>%)x{%y6o)?7vDKOvg@_^9ah-2gy~n<^y;ZbFDD(aRxbvm+JPJ;q};1> zGc>ZX*bqc9TkDu->r+72uJgMbwd>u^H;_ znrGEKg|sVpyO!zPd9W_aPw@Z=Q9L>?h!FcUwPlgWd%vBcos;ZgjpO4n?B~(P`#TBe z!OH}&&G8hKvJLUpE?;l({taGMf7YyB=4jt({te+*FQ$jIh=BLv>@TE0bz^U&6Li%z zRpv4Iq#$CE@C^)-17Gwa6+Jp9rr6hK?jTWyJoOXY@%|3Q1P(RN?3__vPk8=@sW0O* zhJRixH@mx_N88bVZgA??{d%4Ml8e8&X!@D|g(a}p=1n;tB*Kd(_yD659%GDuA&EK! zLy|WX`O1XPd3r;LE3;!+LV7TJ>_aVW3Xq)O#h^LNKiEG+BmYtP{ZbVZ1SO-*{}}x~ z|11zbf)tJHW3oywavXf*7v(~N=dVSUa3i^* zweg64)M=C$$-wnKzEZJg{e(CUQxgIRH+g=jLzLT4IV_d*t*H>cgrVdW>1b!v_z#t8w+UW>4d<>jO*dT z!NiDK-zkPp51H~66`Jx8oCezh{^{HI&+D}?mT#911SePHx$Vf&`8naPbE@; z)2YTRWMEt-QV~&sOm09S6GWNMZOJ7q4OV=zQK~G`=Ou!$QMU|a)A=ZkWa6iX!AVQ# zB|X;VB(Mf*lyK^~SGFLuiX$<*aP@F2&g|;rBQuR5os*i8cq#&zROJy7@?{6Ws4G}Rqe?^j>I-^ROf>bNC z=;{vmf@g$CPWd}IQ#(?;Lunz& z-EWpL&r=+-(n(y-%j#YftxZdT3GX7n)5YTNu|h}r$WwEVP!~Q#E{d-?-!H6kWy&dF zB7?q2C2X#$42)Ts3}XX(E?t#MX>E%p9)hFM*`a%bFQ)8ynNxIWZ72XiMH8maK~pUHd6Nkjsh+kBZb zQFjfgB6gSpFo0PR)<*5^m?P3|FGwV|YMjS`6h*N0oS?0plwBvS9)Zpv%g|MkFA=#U zdBl7wnR`)S4yEb*DWr{w1(ct&?56VeDk|YhJ3_Lv<(6CiNp#oLcGEoO zcUE1XIe;p%Ut;FFOBkdXi=23$Lau*~x0bbbX;1CJsNdvq45@pH;|hbVstx%49P&Qz z{Pgce*v&B8)`39>826yV~a`&dPtZGsVG=FE|IxfnsH zwtE=ekz;>CuY>PR_n(hiaRU9p9fh1q-*8fO3(jqa9?o35 z-uhekt?OE-L2yKKh;y>RD&X%sG=5V>VEq|}`}+ki9^v|d)lhq|PwP3;f}SSunDM@y zLovt225F5o zR_(R_D-H9n*30AM{~Qt~{Xf{Bb^oSeqS1BR8osC+OBcPdKOx(yWgqf1R{v&y8f)gT zy~@0?KXZ-h;{->Vs&tdA;H~{+-`Jlq-_DO#N4SmFx_-bcztXqV7}bTK(i;$RvThIj z4GH^SXqYS~!;v|Hw=Yxk+n0$e%cuFgJd)^-d5h|UW85z@;&|RXCy6@ zi`adW$kizNs86dP))-c5+ltx!W-Eq&JlsS|I11O)N-)TRNc1Z)!lG&HtLTZ=n~CdE4tTo{_9OuA@^?2Ha&$WL`QSG*jfXX?g z;_;OvCz!8ml7Le145|UfyqXDEdCfIcwD;|T@O3BM^`!mrQ#r8c?9w||6cFkd7w6`nig%O@d)P+e)G2MtR!lvX7+>TgOf{iec;wV%I2xxcNV z_>uhvhaA|KKFgz!e-I02P$rkulljb;1|%gji>gtEpk_rB)7{n3Yc)hdb9r!?oogA4 zDu-~ZJQ>-?9X%;2M)13}$XOFvDXof!2qzBmc{KVw9J1wzrw$}Rf7P)E$9#bohUe$n zt4`PpDpo_WYI}8aO0+($}1JP>dGs}BLSQd(m8Y(dmjpv(qZhDvv}3Np3*_73m7pW2N^rl~aFn(5|v@E{56 z!BnE6fQi+woYb~S^h)H`V%4HDWzs_xh7QB?*eG|VKgJm! zSh-Xk$01DZ4a0O?wTxRQCV0_`W6q!&ovaew^&^U@v(fq~c4*ZPz2*6)tB8iW%tXbi zw^iE%jV7;pMlXLPow=XbwRg*Q^WMk!TXkIQZlUX%V$n;p=-VyddKn&4mibb3G}{Xz ztG|}X=%4#*MvcavxcP{$VNT7}^iX1Y0(IwzI&mTnVM}MmEBLQku-n;I&pn=>bN=k% z{5aSF^K3se4|LuA+`_1FL+BuaVP60s5MKn5coQPI@W@{iF+1J{ua{z{yAq6l2{|V- zAQr-*y3n7Au$VUdD*(GwVw=hDy4<9rc%hCSsvDutpq<@|t0M?E6^~%Ny#Q)*VK<-j zi_$SIEle&RVYT?iQMUWTT|1>TTUWUIr4o?vm}`*>gl6_tPH^mB^?Y`!=dZO*rVLgu z5-Gd}fC)_#IWl!E#Od-j8HrG6!< zGV-2@K%p`{fQmR!uKi%dNp1z>3@zw@xQ#=T~;RYBc=`=zxMxQEm*xt&}H zQ}Kv3m|u70;`6Z2+<*j`d?|rq)biEV{3!N&*mXTqj*TDIP7n`C=!_X7?wA-B1hc?j!-vm_5eQ`1aQq$X(8S za?1G{so&7O*d7>F_YxYRWyDDs0DgqelX@r0`6aLlZxGuD;eE}Q;0j(ToDc*BDI;ra z^H8H7`1Ukg|6GYyz%hFgRbtVWJM@IpyNcr`2kZM1zQ(7f#TwY<>aR)dC7r{OseyvBh z(E?TA&%X64?eI%;2Sk1F>vNN=8B?dZWUHE%?-uQvX2NXD8~3j6Y}?v{MwlFryL1`a zuIfs3>uwF)!Qe*&B$BRIm8SEWch6b$KVc=gE^-hEMp=43u{YI(qh)b9^_9J8ozNS5 zJ)I6>O*rSltZHs&3*^z4_q}$tP@jEbP8@*+fOQXuXr;FB)T*rOTw=3`Kpm z<8vs2XcMADr_~)Yvc0ZK3hB@GvwvaxX+YLQ<*5=>Vfq?dd)RheOpMaCnnI+a)3_pS z@)&F9Z2DK-Egj-5@1ZkW{S0L}w&#RKlXcT}87tiHKk@i^d4xVh{$RHE)beM)Yl(}3 z8GG0X(yw;k3zB?L+WxiLnYqtA)qFShq2O$Fr=mX2V&1$wj_EL|Z7u247b)bqu&utg z9n$5(bl%-hB2L^SMP<1BYaFNXrDaT0=>GQ<_ZZuC^{WBa)toA)pX#ss-Oh)}aAC+B z>ULcJE-)3G^HfJo5umtV?-QW#h;-`jaFM(Ub)4=_T4nmdovHrr5wpFT=5rwQm}u8F zOy~(~JP5(&izaczuOO>QI>O@buh<{aIcuZjU`9XAC5iRKXE>5FHw+_2&xpqNPWscl zUhX6N$#E<>O8Hew-vF0KE9jBQ%TpzNn?0ybBzBoK=Vg6z$tL zN{rTdA=7HJ>|z{O4Eao2Bs1HB!(~qq5@J(?7b_)1L`E6PWn@TLASKwmQA_20qD-=U zB?-<(&l+h|;FyX6K*L~K>s~8toS{5fIn`!AW$o?e z=%LFgfUW?4-_TA_O(mNwR;s;vE^-B;xTd{}g^1v&k_R)*GG~(kxJf`#p)#kbn4u+N z^;}aiROUriy=9U>^eJGZYq2&uwM=l~bSktM$aIPzXTmrBeHxQ_wxgVMq7@Tq0bO%XThnTHdCw5!!Q=P8A-|+@<#pnZa{Zg?X7Hiec@-Ch!EP6~TJS7GOv&-0 z*cC&JjMQ6`0(za42xGc@4=butH0RQ(bDJ@qFdICA@}l;Oe0?eR^pO zS-fDCxV7}!Wi%@4>P}AW&)^>j^lJtWvYsW8xz7mL29KFN6Ym`4eVX^{ahE2%`5kgA zAP4bxc++GmH&nFp2Vem}OM;D)>Z&PGs^~PIEip2!ovO8RclfBQ-JFAF!_i&C(m?Vk zGwASzRnq}7C)a?fqCj+_wv0XB1H;wAQ6;Dqz^+iwq@0k7Zl)NVvPcbZR{@=3$(t;H zTO#JluBuvfkmVo1vNgf{C4TQ2*oRkl_s-V4t8 zk36jhz`16>gYtz1(1cBuB>ap%ydWSDn5*S#3ZK#rjJS~0nw3E{8VrG_|2mH&f#+0Q%p&h?nfQJRjq;CagSs(@@20c3?WyyQ=iKg z-N#mc{)YZntM6pgQr)uaz%>N>>ZUBck-%pDvVEcSHr2aR_>%TDJdzu7jMa0ItjWb& z880R06f%=YI+t|pD;W`P^LfYte-(3hjlYVQxLf@_KJ zl$-3J=bY=V`!BTq$HDP0e1Fy+y-2zg1aY22RY>l~*6Hs$t#kb{^hg$0Cr0j^;GkK+ zPlMBvhmVHY(8Owz0UL{#X9)EELe^>RAU?n;bWp2dCigLBARuWsAhuM|$AjX1gtq2= zK+?PqLJQh=%^yD`y-{iWKSX$8Ecn7(1`y!-6D|0WWcZOTc>PDX-G7Op|5u0U|Hg1n zt`m<_!rPZ@-_qAhE*dtA--E`_+i4f3LRYiQ&!=~1vJ)zM!sK6gKE1~hB;eKUhQ#un1jly`*g21UKNzls^~ z?)1QP7-3X;K_GV6U{*LBWd@l$e7WV8C5^%jqt5%6|WuiSeRi9;H3l@$ds34 zMg>0XWjm$ulmgt&Jg9Tnhg+#LyvFaTbM4%DX|hA(yvovS+gdFnV$&XJiX*Rh4sGb- zJP++N*;)^sbLwu(JqyrzKyE-b&m$W=>oz)jXqETVkyC9HUxh~_!28&>71T!WXr*$$ z3$BD+P5}10n=^~@-I}> zG~zxijJ=Cq0*lebJ|dq=yutmbn2mnitQef3W6%rU2tvCY{wUkEE?W=hgq~GlaQQK) zC$T>UIO+GuP%$jB&5$vLzAwPh13Bw!s8*m}y%=+pKdqQKBI;yIp&fa4(H#qYJzKC$ zC1RV0Dey~M(yI4MST_H)TGvrNt)B+9Q!dJCp6~Qp;BF1z?rhi)IM?NY1YYLwbVWP( zwNwRDS1bqVW0yCtnClCD-_y1q=hezRhv%Crj6YL9yw2%L+XU^0k?rMok9(fT%-bd9 zO`X~AoAOT)d;GK^zI&a5O zCl>A~LQ#F9yN<0gpX3UdAtzhZjRI#_005uD{kCEu^j1vf&r37fQ`H0=48`~PzdV0} zC@w^g$d(LtO3}m64(%JRH11nOf2C+$SP@yvz`NCnY>SVokypxnfKBd6^i43?TOLE? z#1;c})P!a%uFKz){7&!q90YwQfQTCWkW3;|7S4%h43lWw3rCtrCDW0k0?^8*9;b*( z%0k4V{`M_T?aW%cr$FqC{r8oY6QyCtm#N-`&urE;Va>cIuq5;CWVMN;_LxQp^^m^Y zVzdxz*Zk69nnIzyop=`HZDReTfd|Gb)1Blk%^IE|5&yG9a+^E~O$({OK~~DxSz@Yk z;1I)ERdU}0g^Z~5;G!v*DP?EpU1+!nq=U_-OpQ`n>#<*F={V##bAzVzqny5q#P|Nn zA)+ZQIkm0QOj+b{mgFEg3sE_jd6yBk{RsK{cSwM&6e_|#yf)iu3BGFn=y$)%e}0qs z920ev-J68K;yS00a}8}UhjgVVc(EnqAU0U+P1Y%>HmM4uXHm&F9ioTXOK>AnZt@Ia zE0aFj6i!#ru(DbcML1*9pqNQZv2QCI%9s_ea2&qz_oma@Zh$PYhcY^u{1ye6dEN|* z@(%Sgs(Ux~f^I+wN-eg5xe}JgoNgN8+xTV+nYk78x zw2nz?b=GJzX+f5ei`B?)!K4vSJAcZ_*9q%HA3v*Wq&lUZ>E=X4gg$AgF1CS`m2qAv znsDkpHCWsT%2+QWE!#K__r|8C=d|*XUJZh4UaL4U@Y3xXEre_Nf>q5Hxy-k#!C7N? zeQKCVFLUS(*UB=q_9^F?=!hwza`T8DL$+1_IBl+dUA_XjkAGDPy{M!AmehOZ+29Nx zuKmi5+1KuP;ITxrN#>V>*bvevpntA=9rmIi2(ddbu)b?g9N@<~{T*hW3xr%!9qL11 z_*=n~42$bX4?tj)Y_`xz$!Ov9Sh1L=hntIPTdS)XQ-eFgL&L056zgzei~?5vEs!e{ z7v8k(%PaSj_eq}<5?xMS&mAY@UlSC}FF^5!Yj~ByDLil2^Z@<9spWs(fmq}^>(wZ!M(>sHJA1^mBS?#in@a%`QUp7}6) zM~rUaTbx;g){M2YO;!4qsxI2o3n(541A~(*$<2vE8c5Hp^^-N-gUbqz5Z(+vWXF#Jf zEMd9{)=1`C;KdD6f3H1e88Nx%fKSg~=%#&{|HucL$zLb>)b5a>?C6cH>iGWw*QCJl zDn{nwl!S+F%LtdwLZiF8Zy;Y-f7m(I7vt_c{#Yn)JD7RkJa>WsD8^wn#zFv8&md4Ho?96y52 zsy%H9qKo1Iv<6dQiM8a_do3K_4dw}FW1P&p0ds)~-R{!OW8(OJ@awYuWPo`+j@QsM zD%&0GtDj?(aKIY(epKDcrF&e}%gG->^>r}xlO*)zbI(Kb68PuXsUcK(H=w9D-~+{v zIq@G*lOM(5KOS62oJuuO2K^qIKcltyVzkKQUj1-a5BwD#fV|+NQXE)x6*zzsg#M9* zVlZ%aUV}Cxkh+DL(eo?WTbvQc`H;&$jaq~Aqw#%%&k&9({{scLRN!BOydn$wVr8Ex zv=qOVdR*-V^R@(vM}&NR%LIxBy@v!chlakGhTiss^6iEivxk0845i0a|Bu@4|8hFe zzYlKLn24p&`%s)Ni8yRS%X~a0*1EXgquRUhTt@cCgY}TTT%1$KQukA(S-ki5X{%&W zhq9k6PG}mT4u2L_jV&)`#^S@za*UtH4CSpr=;wE$PS2Q24_pRo@}$h)sKZKyN`PoY#Yq9jOinneN9-Ph(qJ zjRrD(#-xX>P*!wy6AjKf&+Yh=Q;Hqwuh^Mu37GihreabktvkuYoU{}vYSt8HN$Pb? z1+f_Vj|OSmc84Ys1|WDUKjUdjvrK`U$dU*>nO^fO`!Iz4jDTA0{oK&Q%!Fv)C$3V_ zA8=92`2qb_2jLNSWqXC(T!4bYgf1FN4Hb>29iWmKs5~i~Yxl5JJZyNjtQcxZ_g7ho z45bD%w5^S}GG(}pqRMmWiLP4U2tWz$e2Q9*3z?&1u<4}&9M^&=k1cCPd-hI>MnRS5MXXhIF#W9qoP5^j+a|&$g8> z5@A-!eHsLg)-dGS7e(-f^mM=O{u^~~*%SxZe`!Vt4#8c5ySpbijk`NE-ndKS?(Xg$ z+({s~d*i_!f_t+}ZPnE7|EZm+c{5Y{A?~VE=XdVw5?!sgMi9Wi{4?Q(c-e^8sd1US zV7JCLK)^uMFhY^^!a92XGyBSlG|l32tbE|bpo%jI*qFie6~STB_e{=gN)(EGV<0*w z==Frh#>}TAPCmwcFyt(yBm)i_I$!WpUTo?ZX3dwN`uH z`sWS%DNeCz2cRI-ibMB=T(^C1(kkNPA9ZpM!*$-iIj50xAc0e#oEXpk=Ibu*b6!%S zm+AhzNS?utJxhL_Cw3p(YfAR@nlqa(%nY}tI$w2e=T{i|J>MbwiVrC!4Ido2D)TcmP`ylJ1wr~P4(^VDZsS#KBY z{V2T*0JI)#RVe=gmI;zzuakZN_TYmbqQ2?y(u#a6{0ELBAoB-U)?;R#1llS8MT#ou z&%Z$nfr`Ayppo+a$;D#xmzqaFb?3$ZI>Jh^GE0J~O*2rhHbbNA@)zsNw)lq)$|#IA za_nYEJdNaVuz|P{ZruYRXTxPEJ)*AqbW3j*6C0gNFxp&V8}-&JO&suJvHzXNP&T{@ zS(r~Wamals?=~nlO58vc;|LqDGLkTAN~ywVg{Gi0kaWXl8nO}I%c}B4>)b1W_AzM? zHq(i|JUNYCH-k{eX(m-9Vy_1%11PFah_0Wfss=RyHvTrGlc4Xj#w9h~Y|Ya6Fe0&^ z%*qikbHs#$5nb_onPq(SB>AuR|zo5t)~4ajAbV=|MDz$lo)O_ca6?WB3*A z&eG^&OUG@E!$hn`+||#vQLEaXp^>B1Y$IuxsCzSz>YwAg>R_X?X3vD{H^O8~RV1KfoZH;ap}g-} zF{X49i`~gObMD^0ihUW#C7hpp4)ZhB;5Ik0I{~@YyFGB@Ve+2wcCTFNLRS7KU_gAT zISV{@HU5DD^h@UBKS!o0qhi{J)=@iFeWWfUC^Jn1Y|FnNMrp<8WAk`)6oJ_eZQQZJ7d|6)4GV_1J_TCwI0(OIxcC&Wy8SiopEE%yo>Z)NI zvv_HFe_>FAO0HZJt*mE~XbW0`p|^RqR(rDeEt&`VyQX!oE!t}$0-8R?xI`{?IL0d2 znCZy3FVmoH?$lzE+bew_5Z4B9h&4)oKHV>x_Y|D!pDn=0m=}Gs9?0Q5D#YYfmHJFD zOX_x0Jd(XFuZl89a+;XOvUa4*Kr@rb?V8noD*B30)4R%f@!1ds_tS_@Os8v9O*fLL zPOte=hfhJBYtEpqDqX8gOB4E;uGDLTj(Vk=bVJ^m3XpJdnUWi$p_%ARn7nRg^Zd@L z<*Z1iHO(@k)Qz^i#+DR1`5P!6^fP9gWyg*rV$q8`2n6%A3|c`9&DgkO(N|qI|+5ZMascjCcC(PVpy% z!zC~6Wzbu+<8xkEbLkL%4nV~9K6wp&etEi#i*yk(rG3|*D^!dRR=FLXL~ooDI$s#< zOgv{UvRLswBhPbMQHJp#eq2BEVd2$OV$M=4)ZQ?_E!x4ys2a9+yz)9{+Uo}yk84gh zWnMHi&9!?DJ46565WV=S*16BXEzVcwv7CIqcoJ-e);84%iQ<@guB+;Jbl-R91c1G^ z-j-7a?Rl>Pdl&m_;=TAI@qPA|5;@URY`{x7>bh`w$zXe-h`KJtm3*fZ+2YmW8 zeDd6VKS=u%!UUWz_~Vot;8}t((v6Cj0l7$<9h;IrPy7Ou-e)1@a9ok?|B1B60J4gS@V}Kj+;k7!# zDALWVZRn7ggHS-N-zI%a?vs{Uy@}#_1V^t0WyA8ATI#*->0|73-yDR&FN8;B%V3m8 zcT#^!gmnA>WPE9oz{iCH{2(Mg2uUyk;BYOV%h3ZZ1TXXfBb+=Hk`qQtyX&Swm%^gK*G>ggzQDl-G1bS z|C8{|d2fY;Zb2{1yB8=8o~C-a4`Ka;)c}ox^74FE%4SV_%9`b0HHyP)BIqbLEK5M=uH+?LZ^`nu|7wwYY_ zI+zb`o%5HfE)!SRk@>{+0+I%bE1N{!?TwK+P4?Y)o7M&H9{XP3)U}gAVtQkjG@EZ_{EJ%xA66%u#+P|fiuWOz7KQt*%^Vc75( z-ussFRNNra`MfG7_X6w_QF^TNnT;BM=~%*k`WX|DteU#~=>P`~@sAgy?zheT7GZN@ z=D<%3*s$IvKN^0#|8tjHfOl5u!ydHt$1l+M$oECeX2F4+eg1F5jZ`}!fHDk9eyaIF zxm@Ul>`OR3Rv~(X78)senJ(;2{;Sz;UqVw&B)6jpupTdwA-A4h=MOUOkL>}JOseQK z{2i%L>EA8+GwjxSQ@Es-gAHBE4!WOBN%q9s1TaP&#gb7VUQW$F_FW`MafFCQxVx=%KDa%uNss7tb=0f%*lfe!b3Io`bG%;2R z6Nk?0C)$@sSzF7jE~y&HVAIW+kq`J)E{vEpSJmy(YYkQ6l_zqsY6_i@O;j!hYc$tv zc-vT2i!mlb*_llTc7CZM_EDo)>T=pV(6vF#x!as z7{E^E+om%*%XRXpjTyuzCYOaQe*jlWvO6 zF@5C`U{v_`$AB>xPXuDgS^Aa#9!sv0J*^pr@bWAfphxcq;hbS`iVh|+BxRUui8W#O z%>%6G%V=AEuqAPH*29Ep(dyzuno8{*pJa5-pn%sSf+mBd_mNMo8&hSR#ReHo#}!Z} zQxcr_7GM7GBTbLnfv6I=%=QsVIo}tyPWD+Zz4TJ|lgDIKdC!Hrh^QTEA$Na`apf}r zovkw>P>s@~bV~*&o!+wA%@S3vV!j_bx}iwa9gEnGCO z$a+R)nQ>L+U3Zb8bvu)+xjYy{5Y@>UXi>al z>$Ux~H7^^KaLTmBpOVMxubm6yxFr$tITBP*UJI;J<`0=$g7H;o&@9I^_I!<(pC;j0YXOE)) zSLQuTj7J?Vo(ug6Co~S?wQykPtwk^8>#3sOGjjqILNojD`5hPfck^j6FcaEPnEHC= z?!2)@3g)TZ?7D4zd%nyA+i1K|%rAoRx2!(W`tM3P!zDSx4y(Mm`yMp-pBMY~?>nJ| ziviu;Pq=-LaAnZQ7{^`mX76c|7)o2|1fs^Z?=H_SJd`@Ez`z;eC}?eD=pydNhT+ri|ih-H!^b<_2o7} z9EIWtZ^K^oB+qs(zHj^Jei)C~6si-yYt*7C)C8_h$oe*VqjwJZ%}Ee2u{z>jXQL(b ze78ooKScEQI&`zSPJ`=PRH2#Rgo!VW9QX-UD&G>39ZxyFaEKC_ZkNtiqMH!?ubmE77zn1cDEbVyBq8yR9(!)Nk z#1qeW?8?c|zn36HeC?$1Duv;DVjmXM_tJFv=l47fx$%HvrlVS=sni;@`$34cbbA@d zCGADoqAJ>nDel|&Br)FzDoXQ)KYC^5`(o&n=i-td=M|3Ocf79|MXirY(t-co*R6wV zUY-^Upf4`X1z1*AZjZeCx>@`9XsaXLWKK$3HZrYhJ5uRw3cGax7Hnp)oon?21iC~G zjr88OS%f6X)s0p2NFmmS13DUe@1*j!~@kh%C0c{itKGj(_UqTGX9zS=;ok zbFTp71YK4nEV#I#2HFQT?&~?XC^wt_&eRW+!Hp;@TP8H;j`Pl#NOh|Tjit=%k(!`Z z-((x;?QZ(mnP)&gQq%pRr#|X!vr3&$M|}z5pElE>Zq$n71~JsU6OWU~&X^7h4eyIq zDAe?y{AW<7W@FVJuj*70v+^~7irMRM%uvo(WszaN`(}>fZQ&k1;VV>ZmBPH|$4Q=H z&uECMgzr-mW}enah9Ot)!`?vM?HE*d6sm=rd7AMByfjDph9)XfwEUF~UVQ8WX-W+o zcWHnazQKp}Av9z}iJ-@wZ{mV@SZLHr!H2qk)#bwbV&c(g8LLyi`1a9%jukiLmV+R> ztMn5e!-PSFv*l66X8Tc`;*<$E$h|{(NvTMJBi??HV+nB#P;1vmswbMD(YaBExhVT~ zdz1a{f&6Bu_(!FTLGf$24UHSf8f~$bL5RsdgwdDapHQ`POj0t)$geEo(S&ANA707; zLXSbeF(ESK7#1Trj?*VJBqy958aNA!cV(xf^yD33hrQy7Su1wr#P6f%t4khwHfv@+ z8kUKeb*U+qr&(UZNZ7nPftsn$aM$ zkFeD@{*mq?!|U064MufB*#I+Zs7a5>)p^t^(lYz!5wPe3y!>c6X2#(zrND$$hC3{C zt_YNoCiveGfj5S79@9=)Yal)r*z4MUBa14Mbs0Tqu-G>TDJL0k6g{jDrVt~BHkS5S zG(dyJOHqxsXN68AE)%0*`g6HMaRp;)X=321qgDO8kvly!th8r!UzUODTQ-hj+3n8? zS04aFwvG!s5jJI$bVsoR&|0fqrZOWHpcui2t*T^4X8v7LsRlK)O77NrTAY4yy`rhQ zM!=@HK4Njozp+Nmj6sj@da?a(D&0+;kf9T?pX4aMuDCNvZDwcblYu%%0dm##29??t zF*ipe~xDsSwyX*2uKLD}G6QRk~2xCwLQ z-n-;jXQnEy|6xw~S1)mWfadI`E5G}1s?54T3X}C&?uXDX8aP@sa&qzf@SQZP4(hC< z6lhU$e+F*rvE|p;uoOf71e;$D=yho&b6@_Jrn2c=EUU>nde9SeOO9LUsQVUl*Xvur z_A7dPWi3{6MB>gN?sMg+$5zXzed15k`rzF*cu*)_b^WJ>&-uP4_|BT3Q)o2%3odh> z=a>adqtrmQY~?4#i36W&>$!1lk!^^Bo8M9Vfu}|8?4dQ>4sK2spX^7>+rbb1_OG_5 zi;}VrGs-Y$;=Wc|&HM!AsIe|-o`Y7?w2yPGFV29h>Vxvs6)sg^exM-z5f6r^{LhV@ zYF$q2*6;XBqQd{GFNu0I#QqIs3A3Ms+SzLJWU=-#tJXRX*&2vER}E{-Hk}1?N=phb zMb&E6a}dl=>)fqcBDkxjbk`XA(f|&HqdJU*Kw6=)EeZ3Mdiz338r~6WTwFY1n$O&R zbzXn@e0f;Znb5hx;&B=C?6f0Y4#Cgbfx!93-&B}~_cGbOq!tQ;(1>o`P(}5vO-?>> zBIcxo`}TOUub^%lM(aey)V)uccTzl^_StaN`)_`Z#+wa}Dvi&3QU#29kqQJkd02-+ zah@6F0Ob5-oYIx%2q!@2 z`sUS_=XAUGN%UyvX~Cp>83vO~g(2r^)z$OIk8xYNrjh>)jQNL&FBs%8b1r8}1k1k* zJ^F><2ZV15%<2w!{)Q90zDD-Vsq^hr%Zt~{Q0U(8C3k})8*JvWlpXI)dP%I4{7pO& z`d1SD7&+OkiaRNNhLZjAf=Jp&Vbuf9`}!I$-3#%l6PBwz-84A10q-U2K<^1jpgVDf zsZw8`9_RG^F6w&wk8t0fjn6OiFR;$8H_c!4W8Yplyx!dVY?lwfS~Gq=l>RWhdbeQ$ zpLo3xEbZwn+#KD(`(?VS92|%<;I9x*99%~v2qQsTz>}LkX`8JUO<-x5*Y`5NkikG& z4=({%!*6&3iLESX7IrZq0$d&}JeIy3hh_r2EJAq3e9}P!JUn6#4kG35F!zCIkN`Gr z#eR(7c@r^0?hvU%o>2D?VofI{cwLR~?^^E?7|hT;&QSEjU_WZ@tJVO#+0d^Kq1ff2 zdfS{1>HbcKewNt(k^BA!_SgTc@ctwB{ckq4{b%lT-hH>f<|$0*+#D~~%ag{+6W*Mx zNFQvwRd~0~Z1kvq`iknpdzmm8cOqB&LUFy%jK7(h?Ye&*+-ji^HmUMtJF%b$HIPcz z@02*_>(1LG+Sq;w+MAdYpSl*1curr{^s#5{@qD_iu}JsNID-2C{_@Pr;j^}q( zpq3Pb>?Lk&j3((Z7lIA?vt@)o+G?y%d?i^3B!ki{MNpXxkOS$M+e~zs7Te;zFgB#V zIkBu9?)dZJ1nu}x`92gmiBX5=NlTK*=q19~9T>+cEW%U8izm*TrTiSArAjl3+}%qw zPOVsqMF(2#XHvwpQ)D@e2A769E{>R~Eh^*Bqz%|w9azFXjg;kqLvhM;(NH2S3p$Ab zMUs)iqvcMq!UM|1sS5u+_l45Go7zBw78Tj=@bS{h+EL4Q_&5~7rdoZHpP^=A89-gj zef&gS*L^-ZUGod}z$Six9bnlY!aP!0boVhG2g5HU*T3{yU zX_HnwZnM6`=zq&b-KB27Y{7diJ?0la%WJGlD0$GOZ>91)Gl=^Ztjk_V2%t5CBTZh< zuxqCD?Wp^uhq|~QBz&XEC(<6KsfHUa`<0$CZDmE6gBC{>{JD*Xl_F6ci}_JqkL`7D zu3jevlTX|Rt!D;h=ip70+6%|`FWeBRMpvJP!F$`)f4_%Tx-^P!NxDs&!)zSyG#q=+ z?%Mi=y?6f(LDTOcsUpI6TdFmFS1Gc9E`SxgUEAZ|g*B)l<7A%7t41TCm0pWT@6Cmm zAe-jYQXEmz+k;YW? z;2V92v`)dGSz9*l>=7@MoGhw9U>~8Id=NBynM}%ywh-@=vL%w79$dLhMc`d?Aj3-Px zo7?WZp7RzlDu{;{j}{UCkQQV5c^MZHo%ZcZc{}XW9}|BW%1)eRX(7ZfiQ$4K0O1Cn1lSncL`qjGDRW0gMh$~7-A(ZyA)Fp8lBkkl(tg{ZC=@L{oMG}%3Wh- z$49{2fC`m0LVO^5rF`duYVllpZ$_%JPVX-I})`oM9((D zpphGH2RJV_q-+)Mt3l}G-1pgYouKmtEMSaL=e{{-l!Sctx!Ec4vn2gb5+(YG^vyxJ z3hv9Xurv1-eOt>PMzZ40Zkg@DAO_s)+*YO3?1 zs`U#l**r`NvZ}c9i2KQT0={&IW|6fCBaL23x<{q9rAh%*3nfJ+mCL!KUCCT2!T}7a zYFVz*n&|2d=Zp)QEITu6ugTozUlz2`8`b?oC(9pQ7$U3bms(+Ea$M~Q^{-bly%TL} z+%Ij~K6EO%px#xRaaFg%6XO16#`&2lQ-xvGkyq3alJq;lR`VuE^J@?K_sp|G836{Y z@h9pg2cjDD+YE6V*9E3*eJGQT+=;Ld8#iU_t)3HTRbFG!wKaa>NMs#xEw_)VHI1QG zC9X=V$B(17%M~P3K&W>F-M614 zqwlL{a!`GdT?;o)vv{~Y2wfmFJP1YaAGQTa3%?X$dZ+iI{$Q5Y8fW6?#vJ^pei`j3 z$4}@SUCs`Lj|q}!)cB-5B)Wg863UnjH&mRD!km_vWU{5!bC*ML&TNyOok(vAGx{T? zAr)?CFTy{gPnGx@x|7W)?;YAMYAy_LGftnOGfbX%Q*Tr_d{Hi6q@F}*YNU(E)O4zsT#(4z365$qVKWIaO(f@SJa25-#A1(T1Mwpltrz|n- zztU&UrD<4}YPKEWccdBqguRUrpEyRGX{pn6V}*+wGOvq1;Tp)TCB>g5BwuVTpOSPd z3By3dL5{%7S+Zp*{V+Tl2rcj*`olGOd6U9q*U)&XcvwSs@9VD@>s zi2JtdY<*+G9DB1H7$11PZUMM{UbaDPQ~e6P2&9_rV(x<59Rl@cR0Ff zw(O8=SpI=h^c8%|qSGnMO)Qo~O-Q78+)b5lmz0$L>z~ynM|k3_86?~J6x(~AJgz_Q^0@J|b;I>kR=TlUN!U40GUcTnwvlKAem#yB z^G-=-xJz%TU7X7C2(&}GOZkO(PWDE!z5u#!J?PAF>ixHoS9?ng#<Jp3ZMvYV&j{GMfUp@Ku;r)zu9SBlrC9owhZ z#szRD9KSLw-CctBc8Lo19S|QK4?f+0RotZG1_X2EkJhGu;b99$>vQiO5aecrj3*&62h} zh>ACuS3Q{2BUswQOQc+~e<@g|G*}4|ph9b}W~ron81SdjkC-|*1Q?>(rq4hdXu=!X zJrES?9%>2iVa*$23kj8kcxbh$=+T6smRY|A3Al%cu}FuR^o7YDgx#hFt&@dwn@NRx zYRSan%KMgwAH#;iT5>9vg?n&OrbvgU9a4wFM+oBvRSsI@>_*(sM4${uBuhoYqyIkw z57W|&S1DJ&F;FW z>*qMgYU{w3bO?pJ)*A2Bohp_ag7{`N%PeXXDG z!`;r*ko#ATSH~yVgPk~ryT;qMKV6G!?|$#rP5D`E*nHPdqRm=?a0cc^7D)U{My_W+ z@QeWj3K|8Dxa_xog9-cQ3(QELaL7MUG^lS!IY%7o{doIrNg?xj#bPZ+Fff_IXYrPn zQc7e)b0b=enol_a21m6hQOPNJTY+XUT|Jpq!Ad3NvttC6i;m8&Ih&~;pI$n4jCN^8 zX951Vi1FVe``Pm|5(ha1H4z6nUgvxkd48D)mifUr)`tb*)c>~wxLh_!SmzxfFZqs; zf7Xwr^UVZBF55hLS|0@$L}t=eoMm`Xlvh+DQdB`DJ2H1Yaz?!fYMfR&NNSmnJ4ox? zo+3%>H4z1Dss=Z-tQ-2t$4;ZhggdJmXJcV)GTRd)tg9E?+|IJP32y9K^L?w<+IGEw zpq9f@hSe5GXs1ToOa~$U&ufzs`=56z(r1y+qpNkm2OWHe`p@pnXGz9xRCl^ zUpJr0?3Xp7w+Cc_)jv(?Oyn+y2sLTh`zTd$oQ5dM)*wkz+{^W2*}vtKL*eGC9pYEF zi0UU{u8FRlk>8Lxf!qQ|*F93;<8MP*e6|cp0zIovqiWClHAAEp`WMD3npG5Sv}tQ* zb3)FtR7+vL`j-%65y`U|herAI)vrswu22(?vgb3fqbgUv=)p?O;U+Rc&(dE6>>l@` zaNTlVgJ_CfkL7ep)a_k&b-(Go7q80p$n4d<_O{d1+Czx1W@;1UJNfd;$!vo#k7bjH zjX-Pp-sI`I#U?#xJCh0X&&wopV&0sAFi^^EUcI1F~fl0!?b`Q=M&-vXz}F%d?$SV#DMg6YEd&kGiA~hq_vG& zp7AEZq&HSI^)ET~dlNcb;F;JI%+691g2X>6nVr8gg6Y2tY+3zrPD%QN4-?S}|EVFv zM8U^xmo^VfTk)6~yU`z}5`Zd#p)tysXi4m1MGs1HVsg=F_J=9tID6rj$9Wxqvi>Yy zWm+Xl!jBRhr3_bL5%;Ux-fer$UjU9aJU82NPihk3oKy04)v~yT=|mR;Gi3JSbHtnM zQY1PdF)}bRB>hcNtv75Q`X4FrwArPZup5~2b(hr!Lx&(_i42%X#c#Pl_DZD&vOG>% zYTS`b_9yhHRLT`mQSDUaTy);ju3b!3ZJFkoOWIm23Kx?Kz~c-?axYd1!*cOp0_|0b zgJ*HC0)DdTaiUXZwLF`|_V}!L689Xk3|ETB1jKAk%IzIcCc{TmN#{(y8T|gxOcfmW z_fk{%XiQex6*7w(ozJ1;$U+HLqCnyL+~VZ}ju?2w(P2zwN|x}B)t2q|9nq)CJ zKzca9x{%k0c7oNKFtpD;(4xIe(Y9RKGQb7=ZWYJC=4V=NmMAujCYSKu6-XIXVG}W{ z@KK=umCVgnMx~x8HKM8-)uC8MMSrZsgFkQDj^!$CH=)v5G+&-NTM1Wbt$NF+T*Pc4 z$eLrJS@y(W9fECZIzS)y>sYn4menH5q)No5D7(?5On)eg&h~gkgKSG(*SZ<9*%RJ0%w`Ap zmen_9?iQAB2ep$@i^5nOz4P>;IJMiM)KS}TY4zORwZ9Nm@%@-a#y5Hf$Z*p;F_vz% zr(?DwqgjJINp#dLs5_wtt#5E^rf+32t8G5%X-DEJ67#cw`tjJ7OL;nJc9z^m4)j&K zTYE^fmA!SnH)~eJd*1@>3nQP=ZUJUJn^dg!uUS|yJet2z*#3PPvsqsi#cTg084(oc zy~TgTm9X%7F8TCqj3DIJK8VdGE=`ne>zekP&diZRA9)_D%Huay%%PDv{Z`b9{1B(4 zV;oX*p-&24aLYotNyPXJD+b#LzBYS7WS2pp=zWeR5GN%k8jEg|aSX-I$siATKi)&T z5Fhm%N{f_T_0f%W0)K=mOKN`~y^pVlqu){V7`cgz5V;l|)QBIa$0QQQHWg5MozF2! zPD?l{Ul6n5P!U!t&C@X+8^~!Prq>Cu&+My5Qnf|1+nkk249x1ccln@y%qfbpP^{`2 z6;oO%_KDW6B9zS|L)S*NH*+eMthwCQj>AatrzWw647goOI2U&Q`^f%uocS5{cfL;l z;o3{t)U!;W2p3mXL`!H{jZL^$r?q!TOADCX3x1D{0r{)8*n5yaHB%S}krq0^&T}!V0J+#(=Li`q@*&G^To=U^ zAEGel6-?CJv^=LCaM2vB^&Tj7+Mo>>ew^Q!SaKJ7Bfb#1zxa!*`H;~mMD0J-xqV^V zVvy+4%g5&-%c)4vt-sRcu>S%OXCas}C26tN%pagId-|}GEg4JQ2(iipG!QOo2Q>?E+fv;Ru6{}&Haqcoe((uQA4{9PI?XK}=E;5m`u6$l>u(H_i^M4d440@=QN zqqGbZx*?-05421mXF3d|RVQPk4PyTj$UWr4%`3y3?!w;|)FU3m_uz8}7i`HBC}!y^ zDJ>>cuDsD2e7WF{)#6A6AEM5!yC4yw5l-J$}9EugD%jGQyUc!YG?RcAeL{5gV zy3sn+r{h$#an5K)wMer>Nk`THFCDD^|4>|Bue}$Sb2U{Ru19mVrr=Pf&KBTGOUm|S z4m-}bi%iYZSYPpQ30efChlc2B^`jJkpdGJ$~1#j*?()QU&YC4Idm z7AwV<p?SE&KNQsj8$n?U`Rl6W6Vb6W=Srw%cr&-!5Epg9lF*`YbwXa-n1Rf3sTz=<=H+|0?dRKseJuQVOai?29)jB6w{=f}2ZVXlsbXIWm~M}C?-7L3utf`Kl+@kc;@;Wv?S}?L*uD<3x>fO8CutX@-Z!*TFz;?3A;U_l?n|NJ{cmy&pBGtXWf! z;Yc-5m`_|Q6$0bO@gtiA{OIMdy)diNo+Y=t5S zUk)Jn`X;+uicB!>!1IBycD0lA>G#g8QBA@Vc3v3|4#*Ag_Nzy2ysC@)yPu582`7sV zayM!^+EX>Z5U*$7=|9G{t2RHcq_1r7{%fM)_Bq}mcQJtAjl0w<-#o|BJ(U^;RD6ZuVNvS}5I3`Sch1VXn9S zMbRY!^Y4cMweR&r|J>m9{)(UTzkTU~tug=B@QzwVHpYvLXgaA{h_! z7X3yYEeWC>AGre@iePlqQ`5Zs)dhO=w>Gi*LN!OWPJpGNEFsjP?BW|}#1wr~-_U8U z>8226uEld{l>@$H7QVW)^)pDui|Am{{x4rSQI&KHe_t(24UA1ibEG{IAdHAsu3n#JSpk{d%+ zMM{!qBn`xoY%?F~g>%s*BTq(YEW)~}49>&UMN!BzD9JYU%~Q}^h;ho;M_EZ`l7Fft zFx3Q(?KC^3SqD%t$#RTMV_$i-t!lFUE*+O~Q%>v|SNeveIb=sNoG8R=RN|ELpcc*bb!pn)0E-cQeMFLjW6ao(s+b?24hVRfNn++?p*J^% z>gZsbD*(M|UB^BeT8k3mU|CDtSW46t2J0EU0h>;MxB90Bz&I(~!YqXwQuX%vcj%qbGe1u!GP6Cx~Gi2o-a;q2i z`teaiMBO&3_?~Xg11HI0V=%&zK8KHY0HZ(;80L zzewRFqwL%Bmbq-jB_sbD)ue$)?@-lfVY{#1Ah!+@BYxzK}3I9m-*? z96>a+nXgk_Ei7^KSfZh~BRzkSAy5xhk zv<;tcbz?sfu&GvA;l;`CcWY!7PoWkzX1wOu$#$-ez&1Cd>$W%=5`tUzTOzw_PRF9T zA!WJE(YND99~6?pO@POpXIB?yrOUqn3?2)gV(p35y*A;(-v6SE70iwqualq@< z9;U8vjRH;yA?pVw^Jj_u5nby0o^h;O|2vr8Wc|#7I5AWefh)t z99`vrkAkM3g&!Jl{ZtYZeP-ly4|IEdkJ7?4Vkp74lW@)(R`w1Z!gZ?MVUf3=UAxPS z;!OKHHSZp3xSm}$R|m7)Z?1NPLcf&XM0@D1gExG_*cGMjKN1%iN}XQDe0ka5ynH0L z;Loana^HhB-Wn(K_{2|8<*#Zlt`FpqcQ>cDtbC-#@M|%tdL|SO$NV|MZ z58##zkkk|)J#@C&RU*LiCz$aKB%{OC0@3pZV$fnS!#jM&4FXXHDoO<^x%fSPlKo)r zZH(iqE9RCoD|^~%UeDu87OEzWM+2aBh#vquSOm|*gf!9wBdGi5TlkUD*m@oW5wnMg zrDG{Tz|zAk^tU2%!+|cip;@z`g6|(vhCNmKEkM#hcUs5q;S?^sLG+7ZL}_7WZGq50 z%P@xrBJd%-tZR^as{JRJfCNiVL-B}|bQi0jhzx0wI7oO7E%7cK*c{flKw7Q{&yLeI z(xSkjGTg2D5U17>RD~C{92U~#=4JaOBIAJ+)iR0;#-tZM8daR8UpiWwHee_`nxHKD zW++aA%%C7?FUO;rih2Pp0ktsXds=6Nkbnmaak?*q_K`h2X#?4SYza z|F7S1{{Oz=B=~>b@&(=-*VUV?72|_#)C{hZ({*9j&-W04^%?tzGm(^53#IZ(KI2G@tJ4&ec)p>SoT_sYad>-? z|MCs=88%%lpoz>fUm3A)@o(S{q2bJ6G>!6Uxd;990&*M4&UDqH$S8b({~e7$ zsa8hHaXB8L>$%N+Mpt7Q%!!IwP486KdEU3R<|Pwc)hlITJ0+`r z`K>k4g_=9)`g&mr&zi}$^|GA(=UU?qGEKF%E%+@I+$}v0KZ@dx^I> z@jKzU9c_lWJO*A}g;KeYBfLfs=r~(szayp521x*tvfxL$hp8FneS-6P#xwB=_w+fA z1_6xIK^ExqvKtvEjCWmN{o*5*I;4Pqn_uPW8}%;y!n^Od(0SPkw4lC$9Doh{OitX_ zdycxQ6dSj$rCy+u_n&h`{vGwpy|AVwoEPkAWKw*)I_ANLp+{qVrpRd|!9)L?I}m_X zZ3Oci_}kl#l?lEn-#>h)Bk6CT38|nFJU*{5Mnk+CHlJ~)ipOtk*!qykBisj*@IGAV zKq!>w1?d(?8W|rn9){6WU=|85M4X4!yFnH6D(7t-2gw-)W+sv)D)GqJB&%t^CiG1+km8sys!3yQ+zgp znLMIrir6ALS7bB-Ma{PK85Mkj>U-R^#~$fas!KX1Af{d48Jh9INMe+kqt!1QNor^H zikpw1S8h+%5WY8<e3MCsRimFZd4wvyuZK@@>%JPt#=lY_fOM$jfCU=9X=6RjF zCSM_IqayLu&E>`Vwh$(5^QfE+5U6!cuf}1)i)LpZvFa^=**ASyVYn8deR_1utV)-9 zTk;*JyvJN$Oh|e>%K0P8 zIeX#nUD~f${CY1~%Q_L`Rxae&w4e!0V>fxN>emxun-N*=e>zKgxUkBtel`=ce(D7o zrnUZ3r~3I0Ew!{8`nG)4>C5q2In+=Dz|P8cC!}pi*z>2X+o|Tap}ir_4cpieX=S|0 zhhJ_S3PnzRyBMs1u+bG}9w;)6I_Rp88%+- zGv|xx7q`vn6CRwmZW|mfA6=NulD?UUf0^IcM(!r<@t_BSiE(e4yYw|IUbu)?<|k}B zvfjMovbkGrWX;2mZn~2r5{^w1dX(53dKr0))=raayZq2cU7_*oELG*BYhkY`*I1{n zO1*8}Yr4rQ=9#(2+Mp=ES}C}mwd8*u2T_+^uD>=8cl`KM9Jrnrqc-@OdBV<6{RA|& z4f4v0h{xPmP&W!t>>C7p_XutTme8GZ>J(W||B!S76RF$>=7?E*_a8DgU_=TfNXNZ>Ygty`E41w6ABD?B;Rjf&j1xYWR?9+530Kz}K&_Mj-^+fUGH%Gd zZ%onKlbMc)<7(dNz2=)Xevyc{8Hz*9p5aT%lENN^{n;h>2p8)`(^0FH5f&ai2vjVR z@UK7ce}dJV9ki*Y5qdhX_%Q65;T#YihUq;YQ0w9kb@JG00g-uNQdkC}Ne2Gnv6)!{ z|8WNs4&$=7>V0eTZzv96h2U98agxkAvT1t3O9VHg2e)wq<4F@Ol=@<58HuNPA+}-C zehHD5CIm=_^ic;YJ_K>%@jDNN+yX-k4+AOCLv{*67qDHvnFrS;g=7x-+T!_;g!4Nc zdb^~DEn0YE4eR=}frZq=v=74Wm-L_k!}g#<=aE*P?7OhnYL?r?yG?ETpORP0Imp7WXAllN+XWG(k;K6GMK6F7VYMVQzt~EyE zqaqu8tRq=$xm0X!F=#wI_NYzKVK~;UI5reMjx#Mn*4_JNSo01ZLpm*Ph)Xcr7;;-TD#B@61D54nDedlt`SfK;^l(gtruU~WhDR|p>=Xp8XuNbu5 ze0Z0;d`L5O$?4|!3qCl1{Ncy7Ek>yI#nNR<28!NXCjgcUQz;Nm5Hl|bvrX6_1b3&= zAe6B8&LE7`@Ln&R66j_Kq>VNv^0{dm+KHfvr^t@vfD$hdWIPZjj=IO2D~KWd!bvD7 zgfhGx218ax>Mu>Kv5`O}O0}1$oNQhsAZu!|nGET2Oy==%c+Lnmn!rp-Q=fq}NLQ); zn9GIEgDFXNoG6RU0)(g^WU?Hm=;s6xQ|jl1-Gm=y0qN6C3!*(>EDFcHhcN986}i%j z_6=5wi+dbA3Ni%H6R1ja!zB*`i#{^3X5ext$&)y?>K98(-!HI~8i5;Jy47v4&& zKG4>+DP0=0u7kwf<-Zc>Pqi8*MqsPzXSlv;H!kD1&^50cKH1c414ju!`v>gwb;s0) z#|==OG}i64!&WvOZ^x2$ozFfa)K$e7Z46cq7t1x-5W60=RUx{icD+f0N7Q|9N5~P31iGLYd@k zXm*Mcpka3|6LETEo|fnqYfv*IUcVET1E8_~R#J*;&{NY(Z4y?|o4;Qqz^h1IM)@4Y zxDr-}5V!K9T_$x6@xv`$57-`$1+T-Lo~LX=N%7hj=y-N&DYqC)wRC$~g|zH!LA^Qr zL1s^CX^fELtJtRFc6qK(i}9-3YU0(sZOc?z;214+YUUcL8XaR<{-gWEr40(iZQpH} zt-ff@e)ZwFN`~8XFDu3&SZ&Lp*~Grh46dSTN?-J_yVDxXI_ywXwaa)URbpt{+tnlI zE!xqOn|ijZyEK2{G4*A;>D;_GxCoq;^U!--mCE5fe>U#OCWB49svz zSALJYKSgF9+_tk2w4h8Vn7IQ7@sy>nh}0r9hI$8L+p&+Zc*IxyY@@umAR8Mj6CQt@ zgM{#>H&c06pQa}Q z=yI0-esh#cM`jfvdYp%~uGr6~*&mxlTo_7vEXn{C@0p>vLxfTGLuBBZHY(G|h!Cbm zvMnq=URjb7G8{{>dL)tfdhgp`%AYeGvbtD5$i6ZLNXef#rdVjH(`&$t)x*rD^wFA% zc|7;%Q7c)^2a++|4-P{)qS%kSN3l@!{vO?e&JcZ_V+(|-btn%u-{Og8Q!0zM)jY^p z6*6M$AD&2TX<)hnI|E5}>*e-HZ?lLKN+!V?0%py0xwoW8YA+IKaEys@P%0+E*XDKM z>67_l?nj(7QKaI**y+xWO-a{p z<#d0jRj1vWD$7Z%2Jf(x(eZ4=-Z`!EHeQH74JXMvi>U3V2dJ#=u{s(nTGfFi>Fp$3 z@@%|Gr$0c-g$G47r2cU-F}E|hzaA1_jjV^8(|h|=Me5PZ&h&((eoV4C*CnWEij50u zK26PdR&|1NEl}N;%GVsi&(hB24dJq-^5a0ht+Zl(I0>tYwmiM--y94K*T&IqO)K{E z{J4EVPH0566B{{QeY+&(iE^_cd|N^ZvAyZJuk(Ag+eEB2tZHI(-#i=unr2g1=L z;Z<+{i$^aA=PZZqiqSiXSLa)LTzjVD=0TlJ8o}?Sj~c2j_Uf>G@g}AJ17bFB4NaL7 zLv2& zoV~}FZoBQY#mSjAp?ZGo@b<1zS6f+kRHcuDSlslTpO;76%t-$1wd01=v!*^;O?x9`6k~#VVm4&nBWfPLSByxIoc=(cpi}Xg2uIUQJq!H4hfIl zhv0NM=B9C{@y)765GJ-u*+T*xm0n^s&W<%SJx(NmC5eCl7dfkSSE#cWqp0yK3%1u& zsmGUW)_9j#yY~x#&B3Tbg?wE_Yc8CfI+_(9S4V~Slzq)p#jv}zw(sPL6I=WG^n^?4 zBp#D>jQ%DaziUdwh4PhE>L}vh-mfq2*H|71N4v|%)9m;+-2O%r3`ltMr%ZPlPVcK0 z-MF(fx(6LdX7{Rg&kHf&{`y1iGk}rjx81I*woP8m_#BOVW|#S&-PWCPU|lR^)6JJB zpYx{{Ijl=Lyr(cUe(Yg>tlJ!LO2CzpqN=-mI#T~s?1#`oiH7O3BH~BV<8##PKT*d@s2(UT#Ea2X+? z#MK+1%NP{a8}#EaNau5~lSyD4q@tj@q2QelikUkqX)yayu(Oy)eQ+?3v(K7wNB~g) z+O4M^r8gaE2-X)TyFVd4BB7G~9Pw&&=59trL!tVVeiL89{Ggpz15^Ej&BOSG9mCCe z=X=AV!onO&!-j~%#o|o)#XJK-!hhw3zq(q5hx%t@+GMgjw7dEjh5g9oaO_zKuMjsZ zX$h|^2G*sb%ZLSn#C4*-g|@etCLZZ`hq-kZh5T^~>lY6h4Es*#=4#sk@%qS)7eQ)?}WkP(WeF^!%A1ws@=GYv$eA4b1^iJ`)9 zI1CHqG>LH}ay}1>HWrFuA&tF;jQ!35oToH=p~U@`8VhKNrT!d&Smy9O%^ra>?muL9 z|G^Hn`LDQp{~XQzr_$H|>$1Pc&A-e}^z2NW+q3uo=_q?athRSE-VqHY;Wx*9ax5Il zX|Rd7YH`kttXd4Idgq!sn+OLxAzgY?yG2Y67BJ}D^GNQUY&PhfWFOo-Jsj#qRc{jh z1+%_G=l=SFc%-R#0%cp-7pqmRe+V3@YB=>-xTrW8uu6$# z-liz*OxpI0?<%-yPwT!EY%Twths<*kpG^#(-h0eMTk&VLN6MjMM%T+~06TERVGz>_ z?#A(1P~FZEJ%(0cSU#I4ar8?=CEb|7v~|tcwriTBHrrqXvj)N8Jag|)QQSMd*YY>{ zX(70z#u)_0PA6SCAv#rMeks+a`A!M=3Vji$_qe$5U^-VZV-nMcWt<|Nr4?(lJvIaT zLEh?9J%g^-nGooZu&?I5}DO-Q{9O@`W7jO(q@6|A!570Insbk{_ZhyMvt&eT?x7Z^a?cCHM{tt~dFaMV z_FwEx_3Lb;=(pZ+0`7*JNj|;Wp-je@_x=T;?spqHrEcp-QQytw(~jO^F3qFE@~)>Q z2>zSb>Ax54BX=1&R~N!oIk!UR5U^7Ts=e!tM!`g2?4g885Yvc#`^ zOK(R*G3y0}^ewq&owlxB?+OqD_Vrapus)X<_Xagi(NLZ#eWQ3{M_IlI)(Ov>=%whj z*p^oTvn6hx@TCgoPJfqO{F4ib+R|G`RxA4P$C0ETYM_{*1d9DYMy*n3rf>chA(=RZ zUi1x*0fj1@0mlfvVn%q<*hJ}UH)m=LGn4&AGFq%`OL-*>i`x-CMs&JB^JoZ%KOHGn z2)R)4$qh>=#6jy%JCHb`cgWP8A(L&q#-%Mzx}1b)|*;jB#X zTOsdm(4=B-Lhxgz8Q0;{pd;cOZ7EkNH9@#+Ft9Fn!nsrr1vWQ?Ui{a)SCK$ZVtu43 zbADQH=`Thj1&Hv($U>@PKUvrjgD7RS!bGA^%CHzQ#T3Qd*03_0hO@6*jkXQK7HKyn zf|WLnaSXw8a#65~^JhQE|J;eo>D?3N#ov{-2pfqPQ&)>0up||^pGf5cn+xL$xwTb* z>N%`(+Q;T0;k%QIcdrG*Mj6@xD$Dcy!DB>s>E!87S7f7o0q zfi5YJza`~5${RxcqG&s5;kr6+l-<+jPfU3$eiLp?h8K3G6GVwGSHm_N@h5^$v}81d zLx-4~AX%!^MuA$zVfD=jt#k&XXj&VHYQ&96RG%PLw6xF`tHw2p3=~2nKQVI%rF-Le?>?zFexL6JSlauuG8yLJ zsyCyt8T&EPY7Tyq_7OT=R|)<$w{>&gr*xO@YUs6#*brgIdY~WUeWn|TJk7tCbZv51 zuJjAmF7If?AwINYweP2Uk8nt#n!}np9(3fOVCGLwi?ywGh1QXqFE?Pk82CQgl$JE} zi<1m1N0Z>A?>eYy%qp&rD;;H6`KY(Zg!k6jgAdC-x+3!lm+g8ZSDb(H%NpR8Bh5s` z+WatSnEwK{>z7^r&lvK8JQLC*Cycb#;z125G9 z`f{>FhOB)Q?P8(RO@<8C1;x|qnSD0^rBa*9qqDYg=dDXH=Q` z!LWbtJQ%3=sZ>4Okh;^yA3>KQ81KP{_?GkCaOW~RN$;LIe`Vr*Sf7cbpMQb5PuFQW z_InA=uPb~V&lXvpH~zLfS%KZ}^t4NYLGL#u_O_3YUf@NMfnwX8L1zeav{!)XJG&no zrs;Z$pKzTaHin8bh5rG-0PULvMwq{>kpCx;!(Uccd^f)`MHciS09mRyVJhISSiloR z;FX9v!V?YtzWNt8Po^;A)O&ANr@-9(KnI8*UJh@pJ8hk5|J2Z+*Sr9Tp&;0!ATV^W zHEHm^Sa7dFFv^hv`Bx8`pH~8}yTSL*IYfi^GHAEc9PR z4&O_VxM(R16ybu|Pr{^XEp%2jJT?&%MQ0{U(o3dNNHt0?J4n-cKtD*gM+rJe{HXYw z_3=u zxOKhNN5wyFf)5TKpsu1-&{)A`ao)TXP=4M#?+QoKw6#G=U$>e3y3_jaD9b*QwgCqV zT5FvYC!ToS5HP0{i6gXjtC(2OG2phcI4@k}{a-}GUIa@*3}lzy@dB&dGciDbWc+Km+hTkpg#in#CF=|sD0rQ3Dg zEyT}{)B5FSsI=7;9^z%5d@GS(;3yvRj5E+l1s&j`aN=%zX(O113{SzQfF46$6d~vE-~9(iqZtXJR^+lN6HMm0>W-M32

    *nO~I~nPKp^a3hFd*cJfSF14Cq5P7iJF zJ3^C&$o!N7Z^?}QF=LrDs~Mfv+-#}OUc3fNfy!%u3X-kLPaV2lKSS(<4D47f4LmCu z3yX!q?tdr6O|!&{$LX(Y6J%(`CYIG_z?bz60^lk$m3HI> zAF+lA5d+&xjJe4HlSDZt8yin4t<<#}@!omybuS!}En=!xEDc)+#@Bi#fk%cu8{k9q z>->Q$Gs)VxWux`%I{g7_JVr(&Vs(fHeX3_MXT#l5Va~%`x5ndwedC`qZ@vj z!Bc2dYH3HM9RuOi2!Utt6AEVK+c-%8fwpKba3Ki3ddwxbcg;k$NmPJYBS?nbu-&<( zQWo+mfM#?lTVaU-2XQ1E0%v3uU!jko88+%u#N-fJgZrwK&T7lj?=sfwiQ_xmaVzFm>`nX|;{$d%-Ujr% zIvHJEKbn5|z2*;ri_tkrKdoC-ldgrdjZxz7tPWbv079aQm zipi^+D#9I|KWrTrAY0)%VVz$wzmvVFoH1=>Gh&DQaK+}`Yx;Nd`v^`(3qcDzTGr`` zSQl?;%KlQ?#mm)@%l<-THP4{CW-DUZG%#-|88NK@) zqzB$d+I-&+u6x{_Tz;P2OnB(_eHyRvJr6q1b$Spw0eJmF_KUmq-96;%CH0Gi^n-Kr zgIMzNy7zkr{rrk)@&k?Yg*L?)D;N_&31M zuPuw0H6|7OW!@*x_hxgxcOP9V`L8G`TmJNPM;U?eqT&f2Xabbm!8i)yk-?a*lyM=r z9+ZYcgxDNA;RH!T1VCET!fjdZ;H8~lhFf+(Ey)ZLh`=jJ5QzaA(^BZbl3 z31XygO7{}Ju~M0M$qRDsC#jf9OeZN?rje!Scvz4n875=j7(P+R!60CphlZfT&S!>prIhAG94u4X)bO^M~jnmkt(+zX+mNsHbrlb1x8@*7rEkm|a7ug5dtD7w+J`b9p zb3RPMj_aL=i^h8>z-7rZn2Vw7-LiS9Qe(T{_BTXBx~&HEV@7Q;0t3kYH!iQ26(jO4 zJ8eIShPHh%MhY9_;P=ld*9wf$()CpYIL?l4?ByLyLv#h2PKH!=JWlFdvpgCTM3-Yu zQxKinKc?8>@ZzVhu0O`!nKnf(XD38#7Uz^xteXr!nVsJ+>W9)jEE%WRJS>}+R6VR% zgU%mT?FZ=|*PIt^9@pIts~*7{HU{GWeV@PUYFh!9B5vD?fT$;3*vH6P339t}j=p%I zh(VY;^A$?As zlXGHU?6c%rZ^!C=W8YfcHHlvCXZ{{MHF)lW`&Uq>??o3=Kh>N2)k|*FeK*H}M2YZE zW+Vg>s$;=e9s`J-w4XXcdLGgceU(qQw1^-1&f=tl?q>eA@aZN>0@m6`>@VaN*X@lWr1o zdRjox^JR#c&MNkUTK)Dle5ksyJkHyll#~J>Dk|$3^guF42J;6Ojtd#6`eUw_hbnDe z!7foOD4Hq)l3%e+Hd^w;m^!0jMCN5b_@ZuK&Hh+Y=jF`l@S04`jZ?}%cRz50u~>Pl zNZh3NEY{08o;j{pnp+mt1ZZ`@8X7NSbwizCacAoO`&rcCr6PW+Fqth_e2}`T;n&fe zam$8A1~=L*MLgK6h;X%4RN&!;=-GKJ^9f7LcjMM=uW{_xwK|rk=(I14O=;n`5sc6f zl>F@di)pY z;xxs2pupRPfR3wh_CsPpK@Sy0sAWSe=6xwcRFoog!EAm$a`|zG1%l3yax&+Axq7hi z{ez2mPP9$s-3nJutJLaL8*QPr}Q_AAtq2|62V zfOjdM&y`}8ardqZr|LQ&?5=;Lv->mBuJ z&4@ytN^{IiFuk;onO1r_dhYoCnWNK}k~1b%iiLCAS&xXgE#P8XT7R`|Q+wwf*JACa zOPJ@RC)EQYM$(2h!~$pKhzt*rN9t`uH~$uW^9tGX9tb zf;n{gu>wguIf7W!pM&dKL#efbh!@d5UNQ$?y|(Qy0L)YY9a2i6O2yI>RhDVcHwvU$lgnfyaXa4vEmTd~?%Mv!*E=!;+w-4DZ65^zt!8nK8%e0547R7@H zORG5NzL7CYYvCQMozl#;{>^E>bwvg)^rm<~LeT8tj5eRoe7a$H;IVJ3!np9^hyqEiFnLTY#BA80&t=Xsq5Q5nDFdplV2 zv#shluK7%6ZKCttu4ll{WM8h6yF9*bl1b@RA^9xc@$#7HRX>&eY`b2Q=Gp5UeUed~ zG+LFdUfOkimM5Cs517s8LIR^>6KerX;03hnswxpqDL$!S2^5^uOkWetUgMSKe5QX46 z783d4g?yU{eGE4@l!kqP`skU<3V#3w2TD$wmY2;OeA(P%a|3Tw%@c)A(#$<@F7EL1V?xl&BoeqZ zEi6+s6RC1DniniYaOZB0ERIZ8H)Mw}P8z$;*KTOF-PX1n zg*?xs*X7HAb=Ku1;9VJ$lpAoKjpIdV>XwkRxt~`ztGZv%2K=+^fs;J1T6O7(s#*(I zS!Z0g-F~kGZI<+mn zZhXOKP2z_h@0d0-fH9jsXQJU1T0DIBx_?s5=oH+1?0irEz?c|3jig(G#Ww*AbT9M} zZG6=?vxi-v-@Awy_rRuT{+qqsj~OFqX25=on;jNm?i;8I0`@y~1Tn$c#oq5u$|+^| zvE=ncrtb1z@cs)c`iflQNk}1~LHGiB%F?BHXddE0kp@KB`ffikqV3&Kov}X$ilL3x zjk|;4zUJCqB>XA9mkSfP-bRY~A&f685-vqMfSg5%Mp=x&D}oD)016Qy#qJN8KOsir zZ4s#oSMW9YT+lf2t+%Gs!ERSd?>;|ufMWp3!}$*Otx$b{2Uz06?^!^AS1%@j7*C`D zA5Ua-Ai_X5!w`NqP4bE|j4$KpYQ!@ABMqeolePX=2B|28*4^-@V8=vS>LOoR4N9ys z6#mM$gq&=Ec=yk;WbalodI8Zx|{4GmAEZPKk^Our3putQbm1`?JWd zY|J$a*MV@^X^172dpe7>m<$$061GBdKyfL487U8^$^4|gi?PBwwIx$aT8$}f<#yk$ zdix1yIlXWQ@^iKp)v)UHIBOxt*Iex!kEyowi)u&G3S9qEzAjVI+V(GJc%S{$RX#U( z06V3O0?{;jnHFl?ylD-bQk2I{(d(BR^) z=M{su=t0G!e2T@UopQ$1xFoBy$|25TFzM;8U|U8$oAy;VG8a%j`lDLrLA?7Jd~ddM z&)|N_9rp@kYMijXj5IHAd)k1`*tem(?&s#3}1K3a%~5`z04@z~Ya}>VFiSLN7FG z{U+fJ4~i|vDTH9qJ8}fMHHaS<*0B#2i4rE7WQFB<# z<`ebla0p6C!NK}0Jx(_MN)}W!%0FF1Qe!2EElV7iXQ8-QNk|?nL{5WdNwHWJE%2;} zecU-~FPv(bBTq?knun#bYiknTlaxtl`8*en>uR)5h#0g+l|8MC;l|D~#aBf7eU=`Y z>EiSn--lhVpvK^Wh&+RsEXtyql zok6RrS7Jsu+fUW^wVB4-+5|jnkI^l?sIpgYmfEvVAuaV6?o}*2k1Oot-SQ)UqZ}g( zEcT%`Q$`2BI)$d#9Yps%j~^9X$S43u#LHTt4(aA&79|g*P>7}sr0 zuC$3eS-9*#td~z>Vs>~W8lcC;U22z~p5AB1mz;HwQ4h`S?;j+@_BBp1b4xYv3zF*B z3eM=q%9HgDDLZnH{53#?kjG#!(unPH_LDq?+F8`()Gii*`@J;yED6-%LX#M)Xofl#YXk@oUG<@-IIHxt2_KWP%mKt zdh@AE41AH~>v@2|NBD{8`70#sr~Xg0|5xer&ljKN=^c@HN~Ln`;rgP{6f&9KOo@i# z@eB^L^nbhf?5zE7;}HAn?|&GFpZ=F|DE=>uLy>Z^ zKXz~=x??RBI|H%*$~XkK-CiHfW=f3;kGC~gMpPMD`FJZf+l(~2Z}8Co0DT+K{;#6c zUL1dRoQ|<6=w01~_8{`OAE~@OZYsZ3XoZ$7-PvFEWPt4m+B^uK)(;o!@#OH?dhVLH z#V{tjOdoG%_u=MrefPZ|7mn57ecxM!^}Tyx)N>`F>r)7Q;8y0g+>pL}lp*ePI5s_g z2$T{@quQz&*bpSU{ZOSeHQxbZ7Kaf>{M^|y1Trp&&qgw=lp1Msl(+20pah#|O9+0! zGK%Xz3o4AGAucP7k!Ct3jguFJ*-J#Hv)E6DrW)E$L2!pTNc|QragZjY8Fr9v;4x(C zi!h+EpRq7Jye+6XGn}3^g2!HxJ$fCUl>_)9xt)Xa5KfUF!Zl=Gpnm{!933xfX;hSq z|7>2IXgg9Cm+E6_Seox6Zdrzr4YUlO-!DEX2j?_Nu2$5#lAl^NF0?IImG75ZnRX-} zoYn-uaOBr!qeP_D-LBuE!AHt(oockz`neME^)T%vjRXDa=wslRM8T3jMHNsIwWr&sKSOYtqw=;niF= z-Gp*p39K4#o)_z`N?Ud0KX?A$7ay<3^T*An83c|Q08(_?RtPsPU?-G_FKKC)OBa1_ zO%f4(e?e0=eMZsh0_|YRLl(_DUr4h}p^yRp>eym^m@BsiI>}=@tHKu4S~r{KsgUd2 z_IjWWdV4+0tg-cM?0@C%FdDvW=aG-+<5#>)>85qKiejMe6rY38o1Q0SLw~;>GvZI1 zSf^m+J9jP813$`#PAT+OW zIYx!_=M%2li(}%|n|p$lR&z~$oBi7S#7KA2p;So- zJBi*+>}c(JZL&KOK$W>M_a8edWzZ9?KAvcZ@ z+{+;7RuRj+ns$?!Mk16b9S~|`q>A?1Qy`|2*nq5$cgrIszl`s1Rw+vmk|sgtryBX@ zk7E0i+oVUbHRGhT-a92y@w1C6u?)Rg@qiD)k|+0%j64HiBYcnoRkDm3r{Ff`t<6TINAYE|v81mbm+R<``#IitKPDv;BGG z829P@NckA5?mbvok_FT#cj+Ey{!WTd0)syF_|9y$-zOvJt0G?w|1qi>G%pl|Ims;3 zn1n_)D>9!vZRUC{k4&~D$bj10&N4?qac?G|$c3ETY!(5cqN0WN=!zq@3;IcyCnM9Q z6ufhn`&v+Dd_$o;XD*e3cwYKzJ7p+);Vy-!C|VJrZTcyQrkr<}LMkj>X&D!dPV-)l zUI*A+G2q~=L{`e=PrZ=puB{=d=}5}n zXG~N36%M=Cut~Z#62|b4nat!`0&_HPg(!S`+%VX=M%CE5Qv7*|UgV)R``*yRhD>ub zUbO*h8+>M})IMXQTP#qrK17@bs|CdiE43x9WWWDhx@Kt%>Z{Gn{b?wOLktD#KdZu@ z#n#$wE(QIjiyF0-(tgmuAQ@7*ukuhlC>>q9+%WH$pk)A@gBDg0=UR({ z%bhLqC5T7P)N`|py+Ls5Z~0@DC+8Br?N5642q0q!^lC$xN}X@?4UMyKR~{K$dNH4t zs%+yd{*J+EypwZKz#Uu#@}6zMl8E+YGS_-Dbm(Q7KM{d-jzT}p=eaW`-o|Ru+nA>9}k~Ah?M| z=1tESGrhBcpB^(Ayl`5unPNsqjXs+pngzQ}X)Z&-RaRzwoj(g-qCg|*l%^|TI$qQu z%S=(&>+e#km2zUSKeIsL$clZqwjoi_w7C4jTA-k@TN{gA)Q-4fWl?2qxM;C*SoXu- zxZ+1Q7k6ab#b{+iRhy043gw1vydrPBDTy?t{h&wf(gH>YC#jwHOh)uW zbaRO5xPq0Qwai!0cZg#|{(GEN9bB#N!9DpSY$ENhDadY3hc!~Gmj;73IC`u3jXLYA}E+@pNe436Ktz zJyauT-DEU-32@eS&H;3+N`lJka+F+DNO&1E_z~=t=X-M(qlJuXDwk8(9>|k<&*H6L zGdJm7x)wa;EiSKC*oSR6rPeN{5bq*&q@AN6z%>=JuA$wCEnQB_SE1dlF7LW0L#s{) z2F-6;8{RJC1Q@s04_;avNbb|O;{8CAHQdi`$EOjC;tvH6AtFb7Iplrzgw;+3<&A-)QY#x6_qtcHdB5Suj6$$$;0 z*jvlIay&|k-oojC^{g$biF>pCik#d|dU=6nx$|YZg2CV|K>57;D~9z|&j%0rsu!Ms z)BaC2FFd0aid>~;Kf33s-9S_J@U`7%-$>0N;tWy8;WLj&CTq+Zc+F9IPbjCc%yH@4 zCY;T&!!MCL{zUDx7~z0Nw$a>Umy%FzW4&SEwfnmEd%jdd z22n-kZJUNwVeiPxg4qf)5HkjtAT>>b$oFuSW7V<9U;4sTuMr|R%!Q9t7y~7(VSii# z`pc~2s1>yhG0@mWi@Y4D4dx87A}ED+)#<~Wq~@^s?*<447=4|jCD0Yz#CV#oDLlOl z2(TPQqrTSLj6`VR+a(V*|8O*n`msaMjXvDKgJPJ(GD^m|(1mj|ZNT!?}06MhMA-Q-RD@%FBU*M?0sq?@o{PcC(#ArT(;Ib8LZ%hjNLm^7Jzw*~ zWumDKHrKQTwR4v6ow-PHzbu7FVy?{Dk%7DiMY3dlA&gTQnF=*^nq_=(cD;p$^GTr@ z>_ADod}&i= zI?q%%YH=U&DR>Mh+X$AdGDD_R5rdo`9&)MpQ+axGw!9E~VpYwKW~KS-WMUa6IX_ec zHUhR{tS^4LPE^C1Dp&iA_8xi*|y~gLX(TQ8#Ct$bx8bSr5$E z{l7Fw2=C1+GQ}@pQ;CkuH(W4P6^BKT z9a>7%h}Y3ErNOa&QeTt?t(Dn(j;zl_Csv8~UMMue5wfP8YN0b>IQPtqME{JcOK`aK zEI`wHDhDy%7byU?(=gvJ9y4#=86F&uKW<0KOi+ARfknbwgQ69J5CaStf2a+>uyj4! z=hA-cJ6pFQOV#{Fn%ElcV+(Upoq>>fKqU7b?AxiTAY2A?6P_)!0@>h@{%b+~%pXwW zEzz`yxzQkyC@~FNKX0)^X7Sk$IRjP{VU~DMi85$?3_};rWhj{u6NQ0h$;7~i0-Ue8 zY=Y+Pu46I57@8tt8k_purx9@*yEwMR#(g->ZbcbPlWxM;4L9It%aAvIy6t@492Lgr zl>JqPGMX z4L$Nzc*Hn__Vbfy7>QNk0=Cw&&I;1Jc9u2MTF|(fIV-_a%g#ilV@Ba&m!;>1`T{8X zh1c9lFJ(QMsD%sC_Q;bTv=*qq(R@mG_`ajXiXul@OhB!1tjenN<*X%kr(4p5KN>ge zg|B%}_53=JfMt#J@k84oZlvJw9Jq?Q4Z|VQCplGC8@c`%Z-sT{+w7>}0M$%5k=?99XEG@!c@`K}w9gtkT1;3uYKWm(%2yw2}- zximWkT~L5Loo^?cJEb7!Tpqc*Bwrr5zhqwqm%+-2H*yKu70D7)3A zZvWdk8b{+*`1R|5lso_3yM%53VRY`_0pvEKG}tdU6TBe^s?|BJcO%Sjn!|XurFX|e znYz5++}DpJN@xbVI*6Swgg;_I1+r@%T34b?%wlradw_>uo40I#=ZM^&==Lv*Ynryb z-_&&$aea~Aq4gzSwncNjcknQk0-ETK^a2r8IZ%SoOekAV)GSQ2xUoP zBs}q!6~>Blv6Cg8X)F^Y%To)Jrj(8s;wQl8iIY2;0?l&Mt&$hX(|2vKOp+aT*eSB? zcG4(3J!gdFa}2;R1euUg7Dx7Bh?a->IxjGB`LQ<(=70cI&F!M|>rxA+uAVfN+)N)2 zjna5(mXqwFbWN;sa4=irNoG|WHf~tHsEB2Ga|qOF()aNd)2hy$hmGnwbhwSW%A0ap z$FZ4{vxYtq?z6_ZFJ@I~eWp^WiP;-4WJMbr>6HSYXHnbM7=J0Vc)bNI`u2j>464-M z)lzm2OT}>cU9b2+o8K9T(pUcL_+zC#uy9_sf4*}k+V>4GNi({l>fve)V1ou61{)>5 z)n{L5@YDy8@q0PCP(ida0f@V$Z#tL?pzX&^_<+o#ESI?J<9z&g4pSKjGR~%=1m5Mt zl6p|ivzZ_Mn5m*%l*YWOR*1`j`0fbHVvR=?(^A$uHpB8g@SJI-tK6IIpJG8DM2Vn=QRpG`E_tnDwQdX0|R@lh}6Z=hbsTiKqNa7*D&!-?p~qhi%vwsjJ)x z7s~qvdC?6gGIfZ_r>uVv{m#s`5mPSy2X}ATRR_2ydnO@};KAM99fAjUcMt9k8;9Ty z!C~X>8+UhicXxN+45#m`S@-rieb(w%Ju_e8d20Qu>Q|!0md#eYKK(E9?cXkzFVsk) z)vhiQ**!HUzV*ExaN;0(-UQ0%0k-=>GSaWZ{0ZuJN*oZ|9)~n0unN~n(B2d-Qv2GU z%=#Gcp7&+(Y2TW5qLWVVAtF+xUC5823%rzX^kiWMv+N)*g?4wa?i0H^$-K^e*#v%@ zFk;5QF!%Z|?dVzl{8{|Dq(aG4@%M@FAhyv_wCn0jmZS{|3l@2QVDgC$oJJ!t_uSuL zu!5!PS7+u9qK|KbE<6by$-k!r^(s(LAV*{riiJo}!JV6x7}6_Q*cGeT$rqS|e&Hh6 z(HRyVTc*}xfhs)g1G;pIj`s4ydDzGolqKN?O* z@tFFHj5W_xH}jF#)4c9HKJEwp55xo zyNcgm4%1_Z_4tW2i2vk#6M}#@j(}^H^PP^%A~v9O`SC0t*esrdiDQi6VU(3TrWA`t zW(w89Spydi#$=rR`jtq@eF|a7G+f8LlCZSL^fZE)qNie%RuQZ15lcT$`_!6_ z=J>c+B3vJuK$oV{bhJ471)(@Cz1XzEVQDBTF+q>F{6(>J`L?pYPS6)jW5rFpg5V7B zK-)_+NOfBIxnJJ^17XAYnMT_Wt%0K#t-L>~rFMh*!Ke;?0vDomVy}Ikex|PY_XFA* zzU)HfW2=pJwUo{_Vgduu&!$7ptki#tZ!-{bw=q61!=l zhlBk4_H2Q)3=|As+jOt0bn`|lDC|kA4PqIIK8G98kSvU_d^rO*(H}7}i*XK(={Nv6 zT1=Rd3erhFGZPf^ATe5o_8N{`rzp)xf1Z8{v@$77ZGbcVwd9th-Cva@AiX8Z_e`kH z2b00QZLp$^og9O&*Z#U>Uh(C5BJzH;;70T4$x|~WQS&xPD;;yO%hJ7?=1z}|!V+hR ztGyAu0wl7nDE)M}ASc2S9w>8?hKdtta$k_Qihe3zSiJPp4Umw#Mr1^zzBD<`R{;_W@Uz$q?VGR%GP``bY{!FSp5oc>99n#RX25AjHqwu z9Lf`LXQ>~;WLM}x0-4`DX>1b#T%qVd`#$eZYu~3H{lP!I&7Qc-wh1w+hWHn+5;&B7 z0_mM{q()ETxo^0c3a>}&c(t;hs17#Q@B7})K8^)0Ac3T&05_XkOA6RS2VKAr$@=Y_ zEXU|P$UP$)?#RWmjZCw@HlstA5ERaPV)QP)C~$T+FIcqMM@#6)`*QCkmfV{xEiuCJ@R;W*|_kgOFf{w6k#B5e*>@9wiq4Y&6jWCbv(wFCVemfTEluM9Xc%d083}B z?EDN%7Y6MIECcRays}T670G)+MqVc@vF^%Jy7x5~H>Er4kE;}`(#FwV+g=?=pG)x{ zdp92U^BSdq>g+vGPAtoJ#@jg%J*f|w+qu0Gow);|{%ET4Z8fB)&`AoOS9 zo9{Sbo#(xSS{acas&lSFZ0yg|luk>R7>#>XS?QuM2X4||LpAjBfZMu`~ zwosC9-kwi(;p6_y_jqlX&c0ox18E@b7X3r%RIo?G5ZBnPrYi#UPeKXEX25SPWU9t2arpZCO(hhGpG4wlC4g*I zq=JU65L9CE?NGrFif`C!$_J5*qxkzy1ksTAP9*M9$W9c>(cn%r!qfNNzgq(0yD|9C zp}Vof=7YO&-#DT7;tx~A_7Y$uKHLUPo2Q*%-5_X_WUVNP{REQ?PGg`&x0zm&HT`2j zi2eA_!UX4O*8>l?ChiJp^EiIN|D>eKk{NE+0B&_ zuBj5kQ)26>n9|o5eiURQvzLSgf{)Bf!2Tc$QsMG#EXuOzA+8glI(K&(P~=fqws0Fz zGrzF2zl27#5B=7%I%H{Kw{%_s*4kj2+>}aSLKjD^o-pCq+IZ8O^t>@H;RUyPRWtm& z<+7jVqV;y(@}e!>IlRK>{9G%&9n7_8+d1-C+AinG*WJzn`l7kAa|N`2*@LebPTd#9 z#zWih4xgr4gWFK;6f~4MQM24oBYDNL^zIIf_5x~}>;ll2;AWH(zZl)J)UAJiB5eZmM zRTyK}x9Ol$Uv+p9aaju&i2TT2&l!wyn7F*Zm9P65O1W+YLV71|-z36g>|Aq!(RZ&T z&(ZfTH8arnFDwvL9Wx3gZ;Z1l+Aa?jZD}))zUAs_9HCU4Hs{p@;UAwS*+)I9HD9c& zT(oOJdR{8SchsB_JcW36j=1FUoLF&WlH49E*w)|T@Dj9aE?VZb9Xsqq#WzF8Al5&M zr4ZzodUHg!D;$3t|uQW1r53K~BG>lLyG5e}+l z+8BM|aI-JX%+N6ee*}T+V)UidlTio&KSHXJwufFRx+K;n14xMtVl4`LeycFCDmvk# z#T2pA?~{kmD5WE}F!#6rVe-)G%`O6c2eP;`M9NFcVC^suL^j44IL{8^5hM;ooL)Iu zk-{LtE{Ju)O!WZt9OaSScfV( zW9Z_!mMJD?hNduM{uGyqQ=xDS!?!s&SAFfI-BKNaUaMAX!Ih@DA!Egq~|6lSdQ3$0IWyyAE<%d#T;(WwHYditgYn(Czz? zB;g?`duQDwX^oX5X96fiSZ=1mWyQ1J{mC!!ISJ#tCk$!e$pj^KreC_83Sjk*ye+6v zDf*n^HiZ%7@*QWgfKmmbc$>Mu0&DW9RXC)rOE@x*6HmpROO$0alv9CoDjTXv+GMx*S8aO)WPJHLWctkY8=7(v^j3)Aby^(Ho7?EA`?KQlc(N z=Q_0#IX|Qss{k|5(2}EJ| zl3t$}ZO2A8e@$%J7e$R+oTam2628d)F)Bv3sxnHKPs3km($M{aYl5_WrPUP!u zOTXixl^E$Fa4&Q2yk@x+Yo^v{BNEHYl(7CWgwMUxCu5X>O>LU^s6J=g3GPy z#_cZ&_9&G+4pZxmU;_SOYfT&X>&wyjQc|dFrGri~(C0+QWP3%k^EUOf@4GBqRNI%G z&$&N&`9&T6%1~|iggR9*^()2Zx9)bsJ-NuOGw?v*u2Sow_57qlvxfj0%Z^2l5R1-% zlio{FnQD?oxx6(}F3g!2Dl~e{>~Fj@I87h2o)iJPj_d9`CTHp&&BihtAjI3AfY|jo zo+y%lwRBmi>J!JU&i|CP+cNb5VFoDR@qA`Fsh8NWgC3%d)0Z?to ztH*=0w8DdYfTEIjATGqVAVyl>q@}rUuT`} z5V$OJfhYELy#dT2bc!MC2a&eASze`cI$)>8)zTfV$zEUAr2R<`cLz<4)fW6^Bty-Q zXV>!0U?BDFDsBO%>0}>nXu{ATspGX9hhkxP%SzWjS7WCg*>x?f%)(ztar;M#TTtk_ zWiCcDkz^+0j1Ru`=CanY<9#j5M(ah^!D~ffm2-%g_Q_5NueRX3)*_v?m5*-vsiiGj z?grm>BSh-_Nlr`f&{6C}&fQ7ZPK$X57k_o;qI&WTHxE34elLsG71+qrPl@jl+$?rY zxXZpDUivhK(|D6XFjRoW|J;y(|51rSBpUi?amRx{>frBRp$xS= zPdK6!QrB>q!`-+g5$GI2B}JyhJ32;%z|k74Z62%Tjh7ATxu_&A~6;vUDl zZhFD8Dh5xAwsgWGp0>u5v*on*yS~(!&V1BSS_7_mi?z;Lf%)IY4IuY!dkE#XT?ge%eE$-J-cPqZNGYVcBxoj&myT3gb!L^^G6ft{X-x=v-SG>DZtaiG-c0 zRz|~Wsvj#A(t%0cO?zsP$cN2gh&(sVL5)Vpn3;*bBTb`8V$SNuf$(@WW`bL4k)@ya zGhVIKPZHzXsR#Hrt0`d?-kE7pFY3Dqg&pI&IUEI@*?Dc#-8%W-!Msz80twatVT&Hx zn^sH9S#~wrZ@4bufD08DaF`2>&o-B>YEHv}ecI2CP9#3e4+gj&7nYB^n}a%Rp8MoRY+8hqHr7?oo0T%0FOshi zc+|TQ<>prV9|AqEJ?JK0b;kKC)VClG&RnllMQmB_Rm|U+9|n~8e?BU?WimgBal+di z?R(_&9cMRO0CqcA==s++vJsEpRNE?EinecH-qAUJrGK4tg>04hg4ED?glp#tpC|G4 zqJTfbtkiF%oACU2p!DvuoIg?%;Wwpk-PfHLL}a%rFnGk>%S$$X4u*?upZYoWrpEoB z+_JtHQ_|wkp9Fy^cH>pqsEkNq-XPs|iYEah(Qp zO`QAoOn~lC62C-^_pFsJ>jkTfF}_)W4_ByJlAl8^ztGpoACUA(QB-EZ2*VR|l(lkhxMsolVMIZEETWRV zC|nXIBObai(I%%+jep&ygnVxWNzRHk_v5A@^H~MHlSxb2*DA!i&$2R!z*e&_3pm1C zrp3^jn*(5G;h)WQmuU5b$by*Cr=SCQ>ZE18T*`~^cZ+Fjsf`P$s|zd=oK9}d6i2#M z8g^Mo4a?VMK7SpL$xC1|wjR+CxSsFrMWb_k6W8&foTaW;A@#W1UcqOtX_sW642D(I zUvO8?hp!I`H?}g2j8*qfv8q;%q%lBA<7-izJ9d&s zcLbN~T;HmFsGlPI8af(#9IdtHz372XNpMdSq(Lh`xvttF5x^M5S!1BDde6u}PafvdinTrp z*N7&|q?FJ)`HYLzbp~l?>PL%3(f~L7t^=Qty0=Rs_H$U1{c5NAl!;U-c_@CV8mwV1 zZ4^Z=K8Vw=nHu`QH{mSSYl@TZwS!B~O2O=tfO6BmNV_+ok4EM0NrX8t<$1V1NQIVrzABt~JFCoyfpvrXb z0@1IPS!$oC4m1Z>($`)A%9nKIo)_M@SgO5nRQVWCoV~ zJiTS2#`(3_%;a~Iuw32!-2d*i%~;LFQ? z@m$J}NB00;P`?J&HiIA!g;o(v$A!Pw~1~p>*>KwH`oy_cHAK>Z-SLyZhOe z2NbB|Hbvil^BK%@crbQ5bzpQ*5X`x+bYU`%cyL*r$o*U2mV5c7|GuJ!cSpz8eGMn| zt`o8SGFa|)TPXg4UVs?Y#meOO0<>2+2}`m!zt^Vp-u zMjQJ+Q_b%)GhG3et?78v@Zo>GiF$pSA^lI;?*Fe}l$vXgW{Q+bb;p|PP8KQ+M@%?f z?t7Q(E4Ihq>^Lrp+kL{zX<98Vx4%b|JPiGGxGI|rWia(l&T`zEs*(G)-1>ZfI@JJ1 zxs#1~b-0}DQ)*DZe73zEo*PM^_aS$G64=3LK*Z;lf9t*~O!wjY{FwS@3#l>Ax0NV4 z!|z9bZt?2u zfN)%5QaSAYqs)lN{lv6s`S?eL)zh=3X&1JKR&gnehn4Y=O%qa;6X(mRQSrIaaUH<8 zbf$i_d(T>SGw5W#@n?c1z2@Py(@}Hi5iq8ieF~OA`1;GejpF?}cUc{+x1>r3m^uDJ z_-jYJU6&tq=|a0W-RPy&H@Q)*KJ=9!`~GU{Q6?*VA8GntB?;Qmj=Gi0V#S z#I$Nf`hhgXF}ew>8%v^#4+D%Z3Cd|Q_rta`6D1fk7kOwZs+o@GEW#a^5BE@<`L3XI z*QEAvz&fghA)_~~&k=;PWIVvl-f8XuU|+Fzysz)E&kf3$cRu)lLe01t)^jX+eQ?Ou zeFyGbN&}Nb9}UB!ms|~_9kJXD;`B7!^pkpqI4n{q&J+AI3ZdK&3nOKlw{>Q`+)WES z<~fVZ80w#O6AVTI%YLOZC`WYx_t}EH8&ronMp2I+}S_r?~Gu z?wBE>+DsN~Zg7(j=bvT|%E4-e@y{VO5q@BZf>a7Z z<^q2J6_O{tz5xnJP9B-8oVQY5)+cy_z89h^GnD`Xsk~&75lp7v(mncDy^*$X)4wk%NjR3S7#g1pjbADIqo&0EO3dPaLVHRVLU)jh*xe=(*dG*M zHg^o!l_I2gN)s@?4onQl^(*KfI{I}SXG>&E`I(Jd8D=6bUwfR&(2dC?A1VABP@~+g ziAZ${DPAj{wAi(nNUx-d&0sknCfjI49WRb;$O@f_&S=zpoE+GrO`ec3#HG1Dm5RDU zZt8t7-1VyHvN6~ znZ;7_bs#hOr|-2!jH?uaw#Ab^%<(?CUn%u(m)b)WZv|lQazr(5uHI$?E7U z|W$E2`no4@HsyR%Hk%UiPA z?_p$`4Nt$aqLhFPX&Ci{7l7jfRBmHST1QO_LGiPME@(0a(FTq*o9(i8jW`)U`pcM4VP#&sowIMo*@%th zh?WmBb%Pw;skKnEc6iuqr8TmZTF}bmdO>BAl_1ET`bkrn3^h;6pH4I?SEt0P+Hd-9 z!)1bICFCzhoA-_l#E4X^_cOKr1^l_iBDdaSMqm3wPetaTN6!n0z0&a(h%Xeoao-Qc zcdasH+*G6cT`agWtjg}s4(C?2w{p*9SLHG-tz?TJMh-NTJwTY}7kmA?*UD2=_wn8wQ48^~X(~Vg# zH*K9tD}Pw3zgo)QLIOO0(sHWk08aLo#-!tB?`FGl;G26jWWp%wVK_PWd!RMqAsL$p zn{#xHv||4S8#S&n>YGhXJ_m~0y(+K~LNhOltj`1i z%XM~~#-<2S-J;70*pdkX`Dv`}PP9JH3d`n~&`?V&LwvZ&z4Y3cd3@aO8qUM0^&mHm zQ`KPc1d$w8@ep3iR4yFhNt>)%e!(*pt(JM);d-{+R4avyo*3On~tGUC1SUTl9ewM3ddn7@SJCpm(+2Nh%*a9A|S*zqOYGyT*Z`mEc zMfSR)h2~V3qh)zv{6A`KgG5}b!9BN48@ML`g0R(__R``To%9rNOYn;ppetB-I*7j| z3xyxGmxXV{+vhdvgx(EWto{1Djc1>x-7Winj~o44Jkh@uH2-`9`X`I*f0}9jF9c0} z&tQGW{CL|Bj*HS3ACg}h z&meldCK$*SM)czLNFK;%iFyZ=EXVmEW=}bsX8s$Aj=~rwi90*9 z`i>umO|qdX=Y8^8Gop4D_jlFm|iz8zuO3_EM zczr0&f(T=5H$kC5ElsZjFSz6 z7gm(h&16xO`zaaP+$@$@*jy=5_gSMr(Pp{0(DO`myC~1kwPcjzsb z-0(xSX~2w>ifzKnmZBFv4w?mtUVujATk)s3W7@8dzm_N>^QAI$*sIPl)V$Nz*(yS) zX;pT8R!XR>fR%G*R74chR;kcyvS3mr*dB@P$0-b`QX>rZv>5<@fh!vlj9gV2qLtGu zA7GN(cNnW&i>R^Wx)}Y3pb>SPO!^Qs#-aq)j>D2QqNH6iYWKC>3R2}Kt8l`aP9y}He3BL*?g=92)p=88-CxQ@#y>fKJ^ z{AFGYYc^!BcAH1WJuYlhV7V^Ipyjx(#IX>$uO+D_xNa=)Hi~X1Pg)3FkI9Y~FLh0P zR_;39``QxM->E5*w_e!V6ZTTSwVwr)>lwe)G^cROU;7++E`&|lUV-DT^m;#0VtuBM z_|xdI@dPE#E5r7K0V-|&&-FmeOJJa1YB4+%XMFpp)uca)C!qpYND5*iW5CD?-YbYq zaNVpjkh_XdUD`FJbMTU=bVu)waytWui!>O_%@YDI(TG9_#l*==nj;(GDttaT6s+&D zcB45!#>B}a$ch_-*s7cSV%|)*VNLCoCH@WR{2k72<^ua&ErCm@uOW5){W0 z_C&KdP%h~+tsAL4dO=(sYAn?bMVol{l*IayG9Jbw1$|$vV$Ezuj#Ubt26Ar#eSk58 z{gafOU})0(RX#y#NQZQNp-fW*ISY$mjhBcWPyuyMxUBe>jsZ&W_$)DNxH-Qea}5w) ztAMM+4bSmdTouLeK%A>C)peMK^cMX3m-m8-SW`4F<7k&c;u?D54@|9g2r!qJ!@0=c zMmSnsn^LHUN-jk8G#h2EKnUJoD(*Bo8z9 z%$$N8S0GSzB$N7mN+qba5Vh~9Ui6qK5~eOfU2z9B8iET{;k;Zm9Fv`9%OTerFBA$Syy6R z@rB4z^mRG80#uNz+`aOXz)*dTFcIX!bVx9Oy&&%pCV#H1k&?*15VAkV99>8J6{CfB z*n8eFeT_nsREsMxW1bM*2~4LsN3PXk!%~ArdSscas^z&2AZfEMw=sQ5-*AvGu6Ne5 zdpQ~d(sHmR1lqttVo+wBv764Y*}M_101tD5v}W&=Tq;INPYakELg^UnV<2?T>LptX zeJ>CzBo}iO*u;8~$`p1}$42TUI_R@8yepu7XJ2Ai7US7CNJ*_b*UtbT5r%zJR`@6a7Iy~Q`pU`D-Pbh5|*(|cA8!+iAS(RZVk@w?SRecpZ ztwlR9C?79s@Fb$!msnyT!)pjhonLCCS?#EaaJIQ*JH)0s8=v=!jIge& zCyAsQAMs-Gs(4<2pQ1+7AU~se{~1#Qe#lsMoW-%{H0`uKHdFg~tidlmjsjbrDoxK1 zy}7TB3O%DJ0%}s69`nHTEoLPm$rQ6VgbZf7$h|6KV0dcKS-$_hIY$szQYR@v7NnB7 zg3I!xK*nhnlGnVt)X6ACso#X~o&CWlU?zytVNyDC`WxQy9C1>c=h` zJjQI^Q#$SewM@RlF4(1$CgvuxI&xlS(|X44=mNP`=fGjN*rw{7nY!-e zLE5?(VGFq9c6tUkY<-bEUq{kjy8dcXGE!joa9+7Z#z)y3Gd3N10nGn`SSpSLvafPz z-O;7{Irxh|_A2-MC6!{pWVo>D+A+~{*GZMNPyamDa{6N!pyNr(0d&Izs5#*D;+zyB zyUu=BIuwI^yZnrG#Ch{y?AStxIbp; zUN~Rhetkr~P^-{W;0M2>ps6K0d_sSW`GbmNw&{mWq#p6XI&A#w|3lI&TL6{%Bc*YA z_3e)jPRRn1@?jMkaqtn|E>Q@D4Tpge+4Nja0E6FyZWv2*h+z~@kg-AZFY;c4zeETU zq+$H{#k;Z6>_@wC@}g9G@ydM4`U#M_p?f}>HduN|x>4Uul9M8vVq%PBO$tQK3b2-d z*Jp*N%IVEw*~46kOi!%S)zppifTdf0q+I81R-#5cC%B8|U_ z$GJH$8tzo;DJFB1c|hiptwPTj>LQtv9O~o}uxGVINoh+Lwz*XK^m0;pLv?&9$m2@0 zM5^P92L`|={W-m;{{!6fXBYw1vc{B`CdPk8Q7f?yNk6=(-rF8FrlHMGE4>gWhP$ju zCwt|0?HA(_knCl99(~P?`<{*H!+wi(8~xo$g?#IfRBSK=h00|EFHzd10&LO>W9TP- zPo>@y@NQ+_*B!GA$11Q&uTs5W{6G zCgH{I*AsS-GRKp`EylNb;uMkd(_!pqPUXmQc*_H-EFYm}Z`BWEwwBLWQlkMxdG@R^ zzZam-oVx`uVx0=_;^#P^qp=8h@3#Z5b6<>c7`wj1f3OZQY#z7%!%)J_wx-?s;G41T z_DIOG?!C|Ju4S+W^b<3Fb!F5-HeS4<@08O zK938LHw2!l3MsCaOU}M#u4{XIM6;{W8W^vfaWR60TPQkPULhlrS+51Gmgl!&XV~cY zd(J2V$8p4EeA;{S5}%uuQ%iXMl$zK9M{9qvXQ8W>GXRQcN# z9YZlRC^=0=dlNW0N0a|I-%DU(?WKS?S=nR4}a<>8_+Q>sbEdN-Yt+J4>V)#ID=^AXNt z>{74LeJ+dXM2(_{P8l`$8D~rou}_!OG+^Q#p19I7oX7e&Xoqd9LNuDkIl(&aWMH38 zNQ4C%hMs`Vam09nyo-q9TGBgZUIS=7zZZ}>aoI2*=JCaBu&9XUi$OB zvqBmSOrqCvF`37)QrHr!wIHuWVTAcuPWNCUy_5wTN0?0VHfoV;>@mNk=3FXQ0;YO8 z3P8FD83?TPHIfO{C=5|W+&3z3!ztA|s*~RxLQ7GyhSqj2REa<^1||5F=#7Ldluy%D z_|MV&9tvLU1?5#l<)47RTB5;IDN35d8?VvhpsafGkw`^H z3a+JPa>mA5;CTQ#Y~PBaREv}hy-Lo?S{l1t`fqM*W1W%}V7apDdL6;ce7NEXmuSXE zTGcc_R1X9ObX@Qtiqe{@D;VgDzpH1uWSIZpR1hM6?$4bFfMGqyV`~>%7R3F8^rdO-T@yW$vQ2=~} zs|*ovWT`gLAtesa&?0zcrL6D1rVjqlyd$~luwLCwa9&X1;(kW%BaN-}Xv4j=BJo^* zQ~q*hsAh_xa7+GBi>#CNCt7Q9zum=CuV}gGO0x>By z?17w{xzp#(eciFKak?w$aKT`&fFd?W_O) zC13u#LZunv{}w72*E<|J8{WEB&wo$q8cbjCjXY0ox)wGFZkl?wUm-D({(M8C-1LRT zQvd7soxL&30iNk_GXPof>sA0JbG)7^7T|C)2tTMO+vCd`g+UNuM$xwBZG!lA7-K)> zP6X?`*-qro{i2;Hp8La{=wDD&yMKkz&39wO$^Ri#rW=*QWS|~)!^uU5mgDKrmGTox z^ouhS85}fHlW?1p^OL<|H1yTg>yGyQ!P%inKn#=J{q)vyszNoV{$iwzKZzO<86FOE z6wW?S?i6Vu=+E=nOT~qUsb7(fDOKdX1JF{E5E7}}r@tsRABR~UH0(`(y=c5P*ab;FNTyy0ztqoG zG(%{H%XhXfOVV`_m4{Z=!*C2UiX+UV&Xyv}ohtM#^1-6^$5=>Lb$#QtLK!3`z+qx~ zFYv@1q-!}n8fF@iwouAh#>F08I+kAQbjhu3G+y=-h-_!K3erNn zby_MOo^0A0H7(8t5@ zD34g~>BvhqLO6YqFOLL>20?)*Jt;6k--UwWC+XQDHik_GtKr9YgTT&1TcbvULh4UH zbm&_neh4-65RWs`6^CoXq+<$FdYgyJ0EyA!9(e@xVOLpOW5N4x@ zAyVRb+Drdp3-7x2E)KNijl`C2?PAN^42eoQgr`g6kfD4Trq6bW&!^g>h9H%c(KU}L zpOwZe<`CUMjS-RVlBK7r8R<@*X6qnLVvJq_8pxI=bxWAIs`eLA4;O}dV;2ype3$-U zkR{GBNmBL;ec!}Nl@Bj9ue2c z5{~<_BI^SjIpUp!{MzFT1$p@ID{>J9A;E*}Eo8hKV$QKJ*%Cg?z(i4s;VEY~RCz+{ z!)a0MHs1_Zc`9DYd-W2Y@Ys|5_~tb%-bZ34t}0*(E!8FfW?TqnxNzFjWDpK{ngj_A zxKT{5NYOZJ(|lg6!mFED@GPurQkN@uhNH?stpcK-`y~U9tQv={Yzt)5LE>H3bQ4!> zXs;_$O*mZJU!DW5JIC0QVe2(5P5)_E(PO1s`CWdr&>?vDI}|NdZlrmjvcNW*cjYhp z6stxB-!((>9!VQJO~E*}W6(A=u{-OCbY_Zbab`L=#Dax;dH(pc?$77}k}1vdYz#mm ze6QU2N_=h{lB2d~P2HZ&MSHe{RdA$D+tHauDU$ZD%?z5EGmFDoW8_>-GU^ea?|yAY zk)x^A9rSzSY0NR|LG#dA%S|6g7p~vM`ev*ga8XKiSB(@|R(p1f4YfJ4?cDi=#;y_} zVl!bAy$HTP-EV_p^Lx(%C>l>fQzxjJetEWA6W&aQJ*^snV6vl%g-KeRr)|S8xdqL; zN=BR3Fm$r9|3?#36lCrW-U?$s-#*4)|It`{l%-Crd3Y_2S1j@*;~`4#=(rULwYF=s z;pk#!b$(hBS{6_R`CClV8Oq*Qm&vx*1UoRHamwx|mPIC<4B51~{{4!5*FkAdTiIRh zec~Ccq)}D}g?&#G*MgK7OQ_paA~h33sa- z4|aG7`i2DO@O6aZlxK5Z#QYh_AZN)RhZbz%T)b7~>3IW<$0pV)BU=GZ3UI7LS{G*v zzoTS9dg+J!_s`w&Yyhi7YfJ8vg|X=j6**tLTI<5+5g$B9=hk#Oh`0nyn z$WM@#nrEOn?&Z1C0x;a`%Zj{EdWyVMtTOGFyUuGHA3V-~xumVBL=`naETsLW@(BH0 zaaNiu6Yl%)JpV)QF$yv2%2(-iZQR>PYNF-Zua##%M(}BjqHxT zv?&yQRx_P`r)$i5x+i?#TasP`;XgnDENqhKzaS2JKwpWM#F=&RU{{>I-W6&(7HWt3av7VokG#S}9+O`f|Xr@G`bn^69nbpp40*%whpqcr}$Do;7$$I-A zi1$~T#>%af07RM(E@9Qqz`x`Y;{V5W2C$Z+%d5h6vLL3@lL4>Hezw92P0>GQ&tbjB z<>r6Oi_<8w)#FdXVT0@@wq2Pr-~`s@bS|;0NE!pJ)!#>~2ZDp&>Fs%JZgvTB3fuU2 zd+yJ75Ce{yshysdj~p3HHn_l#&)IjXN)K-b+JB_d^Ob#}V1lz$Am~3T=cwIc`u^y; zk9tAa!vp%k=U(55-Qc1^^g_wM{L~M_$obejrfL7LL{Bvtv8W23hLN&lVp858sqr}@ zVL3r8V{IitWn(|J;e>p5EkJRi6DDutUdoTK6tonSlu(ld3!AZWpO@3pX(6-APU^i%NXuG zDugF0IVvjjo-K(2`|?@j7wP3FpM+P*vaYAduMHc3WXGFJDu|IRi7S&~aJJ&wo|{Ok zHMv?&)8(*ea_fqNpv{UE3|=zo#oB zyG{LY9Tf0rUYXh`;u5X}$ZA%r)hShZSmfA9Ja2S3H(FRGm~RSiCbUB>S*9YOBCGPm zTgqw&_&CxJW~jyJ?vzClz3L3!vRm%5zbd}f#p`?U-W%v}xUer6m$udGlXu{$t~!iR z0oLrNB00eGE*BqM!av{WA2$_GWFF;%2>8@D!fvbqI{-2SH#Jp!Ue2OiO>Z}`7xVTf z(R9l<&c1|jzUD!j%=6owl4vrw6S_8p7s=`gdYYN8Z5t0sC3imei-L=Bw@Z~1dLG5z zo;1&q2#Pn?&`JD#kCw6r)HhG?9R1uarCPM(J+q!3=7TVsPwZnJpDkbF$hZ1-@xxpK z%%}X%w$A$3mS!DSVS7GX;9Q4@ck0=86&aI1nADrK2;ccGprOcr;4ZzPAN-n_w533b z*o4H$;cd0LB*zNa{Dv~!)e=!jPUNYfESZcAIZhS`=}2U-zL1Pk9S}$hz4rA6U2tmC zjttDTOEAfND1<#<;KXet1rx5`RrQtWkC%plnRihv1-@ulqCgh>Oj2`!xk3T zvM?&Pg0vCp_5;whhE))W(x6x2bsQ7#y-_q!&FVMCmB_%tkNqen+8x-tpXq6p3Vc4R zi1=Hg;=#QNETPKEgmZ&QocM|&(GYoGshTjid8*@*M!$c#+8*L_=#O7i(xd#WG_3OR zx;a_o8EG~R>~%WGRgV%=zqoLSfTHCyZzUKP45;+hrwKb(^Vg!>QVb+xQ+hP9Sif<~ z4F7ag>NuTYTu_%H2RNETesDYMp$9A!MdOfnWS3!5Xa#vg(|#%x+(x9xQkLHU&cUq* zv}2rDPG5epJ&)?}t@Mu%49+luzfuZZaZcuSq6ECgOAF35$?LGOW_k1&aQ8?Ih_~Nl ziAk zR8dJF8mvJgYRRyBreazbC6;-H|qJ>q&G@VoL!Cas=n8e}$uOxj~F7;9q=PKMa zYG}NdGKulJV!)n-Nt=bnm_v1#UE~b00EVXTymjphwKX@xN#A&ok-HlkSXnMwtsQ?F z5Ln-K)a#4u3BQXlhSw%?ZUEwo*#b~6lTzm)1n^$dv*S@)m=@X zv4w*A3};WT9Bl`t#nt%REbFgUs{r>UHVZ)NoaiYM6R@&+pVk)I&zjBlYy6O9PTW4~U-k%)lj%=R-*qzK08633ndi0z( zbb&o=xxJbmuf9*@>Ro8QxgVa5bY$w$@@YQbo-y=g>a}}-N8i2?I)6d(>re`s*1>LCi)lpT&|cfI;V0hG*hvWwk$sCUYv!D#@__xsKC8nS|&p( zNqv8g$P{Ea8#et6B%O8xjpxM`BurtJI(GZ?-lkA|He*kTUd(DD>bBXC@-q};nWW<7!&GV^`T~LZL&VO2D{H=EXTMQNw z1(rmXB4eB6>)B%;+f@^n7?nq(TUdhH0h*_UZJKwx`Th7Oe{*mJV2Nttn~&pa5fxi# z1Q$(at^JnAfpK-bUY5Tb$Hzly3V+bxS589GFt!WlY) zprz@%g0XmLyCD%hPrBf6RM4c~MXmC3(X@DGdr^hT?DcV7a8C!~vw`QOQL(GG)!_wY z4!X45sMp;Lmd4f9aAQ0UV_Ivg)rwd*w6~MO7}mE_;uPh#)6yJgw=?n|N?=x5&DuE! zK@U%LQj;Xfd4b&r{$5!-S|=T}0z8ZJF)tlMS=nyZ!CAH+X=7ibSdmc%FS#4`x@@R> zz`1rBAo8hi1(nW{)P#1-0R$sB)@jEvMb;m;qou%(>&&Kn6&8sv5*AxPN%UvcEDvwR zqn+jFhc#!MME6s=s`kHU(dnSM)3lZVkGNb|@8olhE7_;wt{JPzi%28_(n;bD+0!eF z5`5ch?uUHGTZ|dL;yV|niG_P&mkp2c&6)7GOUxuWlV{~FpNhKa24U}keI>oZS9wr{ z*UO_;%H_|U*38vt1FK>^b(rrhdBSqO>w>r@n2T``f|It$t#FVVP`|#y$h&1M5z5et zm*H&5_&8K$eJ7jmin+QVx8v5+RDSHP`+G^tY@}bMYF3PXedz_Bbj;jwFl#^h92B$; zI|?5hB3Rs88EPd!jP!E-D|2kmuYG6TxCFE2$@d2%VlN!V{}rhNR{RZB!@~`bFJau` z7>V)2)n)LCTC~g>*;vMFz0r#vl;awWr#Y3(k&k`!%^u6sMz#HskaY~Co(9>cLYi%f z%Zr{P*_T9x+zg8-f#j7WnMeZi?~->kVGsi-B~Dh7HAe#F8B^Fo;E8OKvioGRFv$`N znsSODhbldDu@t3a4ed@}Kz{^O@b`Bf@;Zgg_ui{D4>)zN}} zRA&&S=sSy4(!4d(ne=m5IW5#uho1DB4^!z#HFT(jqHLZypr#G_?ZdpD1#x z8NcW*wzh6$@=D{&z7WI(`-sWYd6J8G*&`9Ely0UTEsxMwKZY>Eo^5?+uGXpwzwT9L7|#k z$n93Fy)~vjyBJ)KG_<%;{Vj5r%iQKV_qouGu5_nM-Rf$IxYnJ>KC`P`>~a?(+x;$h zy-U(rIyNhC&CPkmqDs2rQH<-|nt2fmGx`c9ys87033qj=}LvL_5)x($T~NZm~8S>EWlM7=$WrGTDGE z8D-j$!1|&wWUO2+15YNrU!JCgbL8bJYxyx;W>>+PYkX$Mq04P6RCyBIh)ZZygw+g(_tO`kBgV4s@eYH|YvK`p#EY-lAbVW<34{ zot0J>mI?M(P$QW}qXt;1-&~Y98)(&HPOy4Y%~8mjy3>AKb5^&BXeXXJOTScdR6_ls zVE201za}cL|4VES8JpS39xjc++~rw%xQXH*UMl+gj_Zh{YXZa)a31A4d0x u!Trc))0W+1gE!dZO|^gk03rDV1quMg04x9i000310RR991OST#1OPkWbdkyc 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 0413/2908] 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 0414/2908] 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 0415/2908] 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 0416/2908] 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 0417/2908] 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 0418/2908] 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 0419/2908] 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 0420/2908] 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 0421/2908] 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 0422/2908] 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 0423/2908] 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 0424/2908] 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 0425/2908] 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 0426/2908] 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 0427/2908] 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 0428/2908] 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 0429/2908] 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 0430/2908] 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 0431/2908] 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 0432/2908] 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 0433/2908] 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 0434/2908] 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 0435/2908] 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 0436/2908] 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 0437/2908] 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 0438/2908] 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 0439/2908] 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 0440/2908] #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 0441/2908] 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 0442/2908] 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 0443/2908] 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 0444/2908] 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 0445/2908] 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 0446/2908] 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 0447/2908] 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 0448/2908] 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": "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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 628794d89de355e87ae51ed022cf242dac30ea47 Mon Sep 17 00:00:00 2001 From: obelisk0114 Date: Sat, 13 Jul 2019 22:45:54 -0700 Subject: [PATCH 0449/2908] 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 0450/2908] 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 0451/2908] 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 0452/2908] 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 0453/2908] 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 0454/2908] 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 0455/2908] 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 0456/2908] 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 0457/2908] 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 0458/2908] 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 0459/2908] 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 0460/2908] 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 0461/2908] 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 0462/2908] 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 0463/2908] 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 0464/2908] 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 0465/2908] 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 0466/2908] 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 0467/2908] 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 0468/2908] 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 0469/2908] 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 0470/2908] 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 0471/2908] 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 0472/2908] 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 0473/2908] 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 0474/2908] 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 0475/2908] 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 0476/2908] 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 0477/2908] 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 0478/2908] 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 0479/2908] 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 0480/2908] 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 0481/2908] 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 0482/2908] 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 0483/2908] 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 0484/2908] 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 0485/2908] 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 0486/2908] 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 0487/2908] 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 0488/2908] 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 0489/2908] 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 0490/2908] 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 0491/2908] 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 0492/2908] 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 0493/2908] 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 0494/2908] 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 0495/2908] 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 0496/2908] 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 0497/2908] 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 0498/2908] 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 0499/2908] 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 0500/2908] 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 0501/2908] 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 0502/2908] 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 0503/2908] 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 0504/2908] 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 0505/2908] 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 0506/2908] 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 0507/2908] 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 0508/2908] 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 0509/2908] 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 0510/2908] 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 0511/2908] 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 0512/2908] 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 0513/2908] 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 0514/2908] 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 0515/2908] 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 0516/2908] 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 0517/2908] 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 0518/2908] 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 0519/2908] 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 0520/2908] 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 0521/2908] =?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 0522/2908] 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 0523/2908] 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 0524/2908] 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 0525/2908] 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 0526/2908] 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 0527/2908] 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 0528/2908] 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 0529/2908] 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 0530/2908] 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 0531/2908] 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 0532/2908] 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 0533/2908] 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 0534/2908] 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 0535/2908] 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 0536/2908] 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 0537/2908] 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 0538/2908] 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 0539/2908] 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 0540/2908] 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 0541/2908] 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 0542/2908] 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 0543/2908] 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 0544/2908] 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 0545/2908] 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 0546/2908] 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 0547/2908] 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 0548/2908] 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 0549/2908] 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 0550/2908] 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 0551/2908] 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 0552/2908] 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 0553/2908] 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 0554/2908] 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 0555/2908] 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 0556/2908] 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 0557/2908] 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 0558/2908] 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 0559/2908] 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 0560/2908] 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 0561/2908] 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 0562/2908] 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 0563/2908] 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 0564/2908] 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 0565/2908] 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 0566/2908] 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 0567/2908] 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 0568/2908] 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 0569/2908] 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 0570/2908] 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 0571/2908] 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 0572/2908] 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 0573/2908] 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 0574/2908] 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 0575/2908] 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 0576/2908] 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 0577/2908] 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 0578/2908] 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 0579/2908] 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 0580/2908] 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 0581/2908] 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 0582/2908] 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 0583/2908] 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 0584/2908] 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 0585/2908] 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 0586/2908] 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 0587/2908] 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 0588/2908] 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 0589/2908] 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 0590/2908] 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 0591/2908] 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 0592/2908] 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 0593/2908] 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 0594/2908] 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 0595/2908] 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 0596/2908] 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 0597/2908] 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 0598/2908] 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 0599/2908] 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 0600/2908] 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 0601/2908] 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 0602/2908] 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 0603/2908] #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 0604/2908] 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 0605/2908] 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 0606/2908] 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 0607/2908] 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 0608/2908] 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 0609/2908] 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 0610/2908] 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 0611/2908] 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 0612/2908] 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 0613/2908] 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 0614/2908] 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 0615/2908] 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 0616/2908] 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 0617/2908] 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 0618/2908] 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 0619/2908] 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 0620/2908] 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 0621/2908] 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 0622/2908] 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 0623/2908] 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 0624/2908] 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 0625/2908] 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": "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\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": "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4E5t2fKqXnnl2abbZs26TyUlz6ik5Bm9/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": "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\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 0626/2908] 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 0627/2908] 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 0628/2908] 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 0629/2908] 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 0630/2908] 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 0631/2908] 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 0632/2908] 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 0633/2908] 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 0634/2908] 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 0635/2908] 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 0636/2908] 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 0637/2908] 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 0638/2908] 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 0639/2908] 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 0640/2908] 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 0641/2908] 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 0642/2908] 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 0643/2908] 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 0644/2908] 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 0645/2908] 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 0646/2908] 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 0647/2908] 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 0648/2908] 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 0649/2908] 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 0650/2908] 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 0651/2908] 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 0652/2908] 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 0653/2908] 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 0654/2908] 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 0655/2908] 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 0656/2908] 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 0657/2908] 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 0658/2908] 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 0659/2908] 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 0660/2908] 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 0661/2908] 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 0662/2908] :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 0663/2908] 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 0664/2908] 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 0665/2908] 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 0666/2908] 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 0667/2908] 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 0668/2908] 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 0669/2908] 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 0670/2908] 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 0671/2908] 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 0672/2908] 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 0673/2908] 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 0674/2908] 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 0675/2908] 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 0676/2908] 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 0677/2908] 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 0678/2908] 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 0679/2908] 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 0680/2908] 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 0681/2908] 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 0682/2908] 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 0683/2908] 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 0684/2908] 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 0685/2908] 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 0686/2908] 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 0687/2908] 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 0688/2908] 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 0689/2908] 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+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": "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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 0690/2908] 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 0691/2908] 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 0692/2908] 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 0693/2908] 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 0694/2908] 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 0695/2908] 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 0696/2908] 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 0697/2908] || 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 0698/2908] 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 0699/2908] 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 0700/2908] 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 0701/2908] 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 0702/2908] 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 0703/2908] 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 0704/2908] 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 0705/2908] 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 0706/2908] 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 0707/2908] 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 0708/2908] 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 0709/2908] 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 0710/2908] 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/%7Bsymbol%7D?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 0711/2908] 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 0712/2908] 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 0713/2908] 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 0714/2908] 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 0715/2908] 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 0716/2908] 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 0717/2908] 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 0718/2908] 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 0719/2908] 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 0720/2908] 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 0721/2908] 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 0722/2908] 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 0723/2908] 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 0724/2908] 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 0725/2908] 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 0726/2908] 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 0727/2908] 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 0728/2908] 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 0729/2908] 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 0730/2908] 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 0731/2908] 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 0732/2908] 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 0733/2908] 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 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 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 0734/2908] 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 0735/2908] 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 0736/2908] 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 0737/2908] 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 0738/2908] 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 0739/2908] 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 0740/2908] 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 0741/2908] 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 0742/2908] 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 0743/2908] 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 0744/2908] 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 0745/2908] 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 0746/2908] 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 0747/2908] 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 0748/2908] 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 0749/2908] 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 0750/2908] 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 0751/2908] 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 0752/2908] 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 0753/2908] 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 0754/2908] 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 0755/2908] 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 0756/2908] 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 0757/2908] 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 0758/2908] 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 0759/2908] 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 0760/2908] 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 0761/2908] 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 0762/2908] 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 0763/2908] 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 0764/2908] 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 0765/2908] 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 0766/2908] 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 0767/2908] 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 0768/2908] 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 0769/2908] 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 0770/2908] 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 0771/2908] 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 0772/2908] 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 0773/2908] 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 0774/2908] 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 0775/2908] 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 0776/2908] 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 0777/2908] 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 0778/2908] 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 0779/2908] 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 0780/2908] 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 0781/2908] 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 0782/2908] 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 0783/2908] 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 0784/2908] 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 0785/2908] 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 0786/2908] 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 0787/2908] 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 0788/2908] 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 0789/2908] 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 0790/2908] =?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 0791/2908] 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 0792/2908] 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 0793/2908] 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 0794/2908] 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 0795/2908] 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 0796/2908] 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 0797/2908] 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 0798/2908] 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 0799/2908] 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 0800/2908] 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 0801/2908] 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 0802/2908] 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 0803/2908] 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 0804/2908] 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 0805/2908] 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 0806/2908] 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 0807/2908] 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 0808/2908] 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 0809/2908] 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 0810/2908] 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 0811/2908] 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 0812/2908] 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(/service/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 0813/2908] 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 0814/2908] 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 0815/2908] 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 0816/2908] 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 0817/2908] 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 0818/2908] 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 0819/2908] 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 0820/2908] 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://$%7BGITHUB_ACTOR%7D:$%7B%7Bsecrets.GITHUB_TOKEN%7D%7D@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 0821/2908] 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://$%7BGITHUB_ACTOR%7D:$%7B%7Bsecrets.GITHUB_TOKEN%7D%7D@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 0822/2908] 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 0823/2908] 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 0824/2908] 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 0825/2908] '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 0826/2908] 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 0827/2908] 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 0828/2908] 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 0829/2908] 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 0830/2908] 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 0831/2908] 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 0832/2908] 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 0833/2908] 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 0834/2908] 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 0835/2908] 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 0836/2908] 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 0837/2908] 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 0838/2908] 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 0839/2908] 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 0840/2908] 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 0841/2908] 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 0842/2908] 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 0843/2908] 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 0844/2908] 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 0845/2908] 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 0846/2908] 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 0847/2908] 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 0848/2908] 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 0849/2908] 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 0850/2908] 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 0851/2908] 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 0852/2908] 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 0853/2908] 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 0854/2908] 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 0855/2908] 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 0856/2908] 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 0857/2908] 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 0858/2908] 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 0859/2908] 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 0860/2908] 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 0861/2908] 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 0862/2908] 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 0863/2908] 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 0864/2908] 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 0865/2908] 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 0866/2908] 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 0867/2908] 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 0868/2908] 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 0869/2908] 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 0870/2908] 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 0871/2908] 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 0872/2908] 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 0873/2908] 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 0874/2908] 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 0875/2908] 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 0876/2908] 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 0877/2908] 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 0878/2908] 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 0879/2908] 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 0880/2908] 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 0881/2908] 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 0882/2908] 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 0883/2908] 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 0884/2908] 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 0885/2908] 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 0886/2908] 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 0887/2908] 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 0888/2908] 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 0889/2908] 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 0890/2908] 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 0891/2908] 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 0892/2908] 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 0893/2908] 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 0894/2908] 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 0895/2908] 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 0896/2908] 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 0897/2908] 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 0898/2908] 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 0899/2908] 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 0900/2908] 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 0901/2908] 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 0902/2908] 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 0903/2908] 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 0904/2908] 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 0905/2908] 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 0906/2908] 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 0907/2908] 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 0908/2908] 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 0909/2908] 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 0910/2908] 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 0911/2908] 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 0912/2908] 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 0913/2908] 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 0914/2908] 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 0915/2908] 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 0916/2908] 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 0917/2908] 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 0918/2908] 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 0919/2908] 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 0920/2908] 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 0921/2908] 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 0922/2908] .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 0923/2908] 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 0924/2908] 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 0925/2908] 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 0926/2908] 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 0927/2908] 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 0928/2908] 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 0929/2908] 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 0930/2908] 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 0931/2908] 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 0932/2908] 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 0933/2908] 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 0934/2908] 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 0935/2908] 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 0936/2908] 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 0937/2908] 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 0938/2908] 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 0939/2908] 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 0940/2908] 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 0941/2908] 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 0942/2908] 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 0943/2908] 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 0944/2908] 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 0945/2908] 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 0946/2908] 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 0947/2908] 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 0948/2908] 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 0949/2908] 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 0950/2908] 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 0951/2908] 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 0952/2908] 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 0953/2908] 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 0954/2908] 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 0955/2908] 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 0956/2908] 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 0957/2908] 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 0958/2908] 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 0959/2908] 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 0960/2908] 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 0961/2908] 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 0962/2908] 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 0963/2908] 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 0964/2908] 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 0965/2908] 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 0966/2908] 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 0967/2908] 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 0968/2908] 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 0969/2908] 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 0970/2908] 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 0971/2908] 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 0972/2908] 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 0973/2908] 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 0974/2908] 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 0975/2908] 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 0976/2908] 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 0977/2908] 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 0978/2908] 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 0979/2908] 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 0980/2908] 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 0981/2908] 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 0982/2908] 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 0983/2908] 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 0984/2908] 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 0985/2908] 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 0986/2908] 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 0987/2908] 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 0988/2908] 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 0989/2908] 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 0990/2908] 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 0991/2908] 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 0992/2908] 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 0993/2908] 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 0994/2908] 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 0995/2908] 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 0996/2908] 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 0997/2908] 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 0998/2908] 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 0999/2908] 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 1000/2908] 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 1001/2908] 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 1002/2908] 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 1003/2908] 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 1004/2908] 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 1005/2908] 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 1006/2908] 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 1007/2908] 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 1008/2908] 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 1009/2908] 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 1010/2908] 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 1011/2908] 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 1012/2908] 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 1013/2908] 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 1014/2908] 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 1015/2908] 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 1016/2908] 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 1017/2908] 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 1018/2908] 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 1019/2908] 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 1020/2908] 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 1021/2908] 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 1022/2908] 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 1023/2908] 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 1024/2908] 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 1025/2908] 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 1026/2908] 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 1027/2908] 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 1028/2908] 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 1029/2908] 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 1030/2908] 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 1031/2908] 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 1032/2908] 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 1033/2908] 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 1034/2908] 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 1035/2908] 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 1036/2908] 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 1037/2908] 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 1038/2908] 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 1039/2908] 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 1040/2908] 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 1041/2908] 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 1042/2908] 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 1043/2908] 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 1044/2908] 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 1045/2908] 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 1046/2908] 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 1047/2908] 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 1048/2908] 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 1049/2908] 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 1050/2908] 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 1051/2908] 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 1052/2908] 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 1053/2908] 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 1054/2908] 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 1055/2908] 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 1056/2908] 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 1057/2908] 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 1058/2908] 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 1059/2908] 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 1060/2908] 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 1061/2908] 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 1062/2908] 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 1063/2908] 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 1064/2908] 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 1065/2908] 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 1066/2908] 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 1067/2908] 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 1068/2908] 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 1069/2908] 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 1070/2908] 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 1071/2908] 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 1072/2908] 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 1073/2908] 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 1074/2908] 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 1075/2908] 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 1076/2908] 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 1077/2908] 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 1078/2908] 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 1079/2908] 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 1080/2908] 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 1081/2908] 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 1082/2908] 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 1083/2908] 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 1084/2908] 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 1085/2908] 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 1086/2908] 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 1087/2908] 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 1088/2908] 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 1089/2908] 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 1090/2908] 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 1091/2908] 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 1092/2908] 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 1093/2908] 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 1094/2908] 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 1095/2908] 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 1096/2908] 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 1097/2908] 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 1098/2908] 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 1099/2908] 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 1100/2908] 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 1101/2908] 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 1102/2908] 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 1103/2908] 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 1104/2908] 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 1105/2908] 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 1106/2908] 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 1107/2908] 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 1108/2908] 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 1109/2908] 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 1110/2908] 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 1111/2908] 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 1112/2908] 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 1113/2908] 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 1114/2908] 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 1115/2908] 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 1116/2908] 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 1117/2908] 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 1118/2908] 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 1119/2908] 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 1120/2908] 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 1121/2908] 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 1122/2908] 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 1123/2908] 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 1124/2908] 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 1125/2908] 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 1126/2908] 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 1127/2908] 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 1128/2908] 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 1129/2908] 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 1130/2908] 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 1131/2908] 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 1132/2908] 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 1133/2908] 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 1134/2908] 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 1135/2908] 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 1136/2908] 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 1137/2908] 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 1138/2908] 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 1139/2908] 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 1140/2908] 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 1141/2908] 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 1142/2908] 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 1143/2908] 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 1144/2908] 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 1145/2908] 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 1146/2908] 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 1147/2908] 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/%7Busername%7D/" + 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(https://github.com/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 1148/2908] 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 1149/2908] 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 1150/2908] 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 1151/2908] 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 1152/2908] 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 1153/2908] 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 1154/2908] 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 1155/2908] 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 1156/2908] 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 1157/2908] 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 1158/2908] 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 1159/2908] 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 1160/2908] 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 1161/2908] 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 1162/2908] 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 1163/2908] 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 1164/2908] 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 1165/2908] 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 1166/2908] 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 1167/2908] 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 1168/2908] 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 1169/2908] 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 1170/2908] 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 1171/2908] 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 1172/2908] 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 1173/2908] 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 1174/2908] 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 1175/2908] 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 1176/2908] 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 1177/2908] 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 1178/2908] 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 1179/2908] 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 1180/2908] 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 1181/2908] 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 1182/2908] 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 1183/2908] 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 1184/2908] [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 1185/2908] 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 1186/2908] 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 1187/2908] 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 1188/2908] 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 1189/2908] 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 1190/2908] 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 1191/2908] 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 1192/2908] [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 1193/2908] 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 1194/2908] 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 1195/2908] 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 1196/2908] 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 1197/2908] [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 1198/2908] 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 1199/2908] [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 1200/2908] 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 1201/2908] [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 1202/2908] 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 1203/2908] 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 1204/2908] 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 1205/2908] 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 1206/2908] 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 1207/2908] 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 1208/2908] 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 1209/2908] 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 1210/2908] 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 1211/2908] [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 1212/2908] 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 1213/2908] 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 1214/2908] 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 1215/2908] 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 1216/2908] 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 1217/2908] 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 1218/2908] 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 1219/2908] 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 1220/2908] 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 1221/2908] 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 1222/2908] 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 1223/2908] 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 1224/2908] 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 1225/2908] 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 1226/2908] 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 1227/2908] 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 1228/2908] 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 1229/2908] 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 1230/2908] 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 1231/2908] 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 1232/2908] 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 1233/2908] 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 1234/2908] 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 1235/2908] 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 1236/2908] 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 1237/2908] 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 1238/2908] 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 1239/2908] 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 1240/2908] 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 1241/2908] 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 1242/2908] 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 1243/2908] 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 1244/2908] 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 1245/2908] 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 1246/2908] 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 1247/2908] 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 1248/2908] .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 1249/2908] 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 1250/2908] 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 1251/2908] 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 1252/2908] 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 1253/2908] 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 1254/2908] 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 1255/2908] 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 1256/2908] 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 1257/2908] 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 1258/2908] 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 1259/2908] 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 1260/2908] 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 1261/2908] 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 1262/2908] 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 1263/2908] 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 1264/2908] 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 1265/2908] 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 1266/2908] 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 1267/2908] 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 1268/2908] 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 1269/2908] 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 1270/2908] 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 1271/2908] 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 1272/2908] 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 1273/2908] 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 1274/2908] 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 1275/2908] 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 1276/2908] 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 1277/2908] 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 1278/2908] 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 1279/2908] 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 1280/2908] 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 1281/2908] 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 1282/2908] 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 1283/2908] 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 1284/2908] 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 1285/2908] 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 1286/2908] 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 1287/2908] 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 1288/2908] 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 1289/2908] 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 1290/2908] 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 1291/2908] 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 1292/2908] 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 1293/2908] 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 1294/2908] 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 1295/2908] 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 1296/2908] 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 1297/2908] 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 1298/2908] 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 1299/2908] 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 1300/2908] 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 1301/2908] 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 1302/2908] 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 1303/2908] 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 1304/2908] 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 1305/2908] 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 1306/2908] 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 1307/2908] 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 1308/2908] 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 1309/2908] 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 1310/2908] 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 1311/2908] 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 1312/2908] 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 1313/2908] 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 1314/2908] 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 1315/2908] 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 1316/2908] 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 1317/2908] 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 1318/2908] 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 1319/2908] 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 1320/2908] 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 1321/2908] 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 1322/2908] 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 1323/2908] 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 1324/2908] 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 1325/2908] 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 1326/2908] 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 1327/2908] 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 1328/2908] 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 1329/2908] 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 1330/2908] 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 1331/2908] 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 1332/2908] 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 1333/2908] 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 1334/2908] 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 1335/2908] 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 1336/2908] 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 1337/2908] 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 1338/2908] 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 1339/2908] 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 1340/2908] 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 1341/2908] 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 1342/2908] 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 1343/2908] 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 1344/2908] 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 1345/2908] 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 1346/2908] 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 1347/2908] 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 1348/2908] 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 1349/2908] 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 1350/2908] 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 1351/2908] 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 1352/2908] 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 1353/2908] 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 1354/2908] [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 1355/2908] 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 1356/2908] 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 1357/2908] 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 1358/2908] 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 1359/2908] 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 1360/2908] 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 1361/2908] 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 1362/2908] 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 1363/2908] 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 1364/2908] 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 1365/2908] 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 1366/2908] 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 zcmeFYg;!lW*Efn5cQ0C6ibL^Y8+Rz~?(XhVqD}1) zN_+bB%6SXUJF62cxL?5thR0v{m2^Ca2q&Gl9|??f94w>*Yrbk89ql6*7bXc~>x(8} z8G5|OZOM#uw*B>0TPUDAwgTLz3+>yE=sX^5y6_dJ`lmPuAAVe1y4aA<4LG_*xRj_H z8*n>PjBcdJEUqDi(EwSrHrQlvAM#2SJtDAAFg@RZMwdUo=1}-s`V;t~ZfN*(FL+?X zY$*8~8ygsWJA>^q8uSkCepDYZaFxUkCEqR7`V4(a{`1GA>hLSD|Gb^-qb6%=H8 zHUTC>uZ*>t(T|jX!OQME)sDoQAh-Lfj6G8$2@j0C#O@1(s8gwQVvbQtKd?Nmh!{MU zQH@~lZRSv>kK2uMJ#As8M#07xLz7t`CqO)9WO{dGNa~IS-^i0`T#iuS$lcx0v7V}b zi&xo~apTX{J}tM1gW3;RUqJ4;PX#2drzMFGjr@I*l42<>tjKdf?>=(16Z&5d1NOm_ z-JqA{E8a%UUxbuv)I!MHBda#|Op=DsLDEb=AUsGQ`8&WhNZt*SVB|njW=i| z!~|A$|09Y`Ump)*{0bM{T9wo)3*v>741_`koP+O&FOW(d z$X~&o*O8w1zQ77_1jB@a!N7E)*$|{aU9S@~K$ZC= zXJB0U1ZAKg`YG!`d4TDoLT!*R3i>jC;2yxO@;@TM8Nj#*;?IPoAQJa;`HA|Ak2(`c z23!FumJcR_Y7eIXtqDpqs43%7hKCudHK477A^}QXmv9~`4YIid@hen>A5JINSA;QN z`;Af?N)2%0?}zIWXHpF?C*QR@vnhP!cqe2YD5zr)hH$U~#d&ixdJ1L=JQU!HAQZow z@z-WrWk1Mt7XVFYlKk?rOUF<4NF11x!pHc_g{}GGGb1w&$4mE?_PnlOA26GcXefge%qCyiF$kNN9|GnaTUf~G-2>VFOg0>ECDS)se zV$ zj}#bTa8~|bAc87VWJJFTnvgD&BBJ;Gpc0hNf?)_r5Z4f)A)7@lMQKV4(nrS1IslCA@7HhzFK}Pn=Ag44(T#jiUuuqsM$@;-P8e>52Ldg#6p79>; zKAKc$LM^IrKvpALrA#G%zVuElIK!Z%M@FqACR-uPL3}@MvVxIsL2yAiF}FpnQKM1z zKJB6aUZ_`zOp;8%Fmy;Z_K=ER!M5mg(Mu7*jK<9OqIg9Z3W|^qz42Xn78wLt8kvZ4 zl?w7y(MjU88ASO`G9uFoGX&Fm({Y8d#i`1N*_kGvnLpE}Dv5H6HA_5Zm1U2~>=s!o zmCK$gy%sP?bgO?ajw+Wep2$8IiZ0tPPt0r7tk$}$#8w;TH}{>DEA>=Ut_d`jHEsmz z0f*8Z?Peh@RhIZU`P~C<_>P^9WeGJ?c?qM&tWt+Lx|^6atydIQ^i~j>1l%yWrMZ2z zDp!SC;~iKyzNdYo&#j*nxvIVXer5OobH#bpk0^|oA|u4^K(={U2#cg>AFs;&ia_IG-EMe|2 z*3dYi`W}kjPkjo#9%0v^v?0f#$r$xiCq^wwEhKFpz~fP)ZNBO{WWIK#BEVn}VqBu1 z(lKh*SWH~^s)bJBq-$r@_}6YLpns-h-8Y$3R>CdzV@Vp3T#e@Hx-mS}Yu|LS4`CP3 zK(Ru*bqx^_mmNY#gRzFO{&Ct%zk!0_0d45VR>)WQD9@7@kgJ#%p5LE0XSB}ri}8Ix zQ5UnWd7jly)d*cwE`{`TJSeV~$y|_t6hPzqDfAPP92Fx??eK^JzTxvi+5%`HJ+6<2 zyH?UL(MI*rE)I3;x(h?JkHF830X^v=?ODHiM?yP}<%^L=h( z3ge5zj3$ud$Lptuw!pibnW zwi{P3i*TIi$B>{A9osbS?9O8MX69{HiK)DgTrF=NuF_Y%>mqx?5!=w(@w}TssXNU> zx5%gJ!rdb29s_q@ZWvypck(}`YXa&$gP$)vEcTU`=O(Rp){@$SJiM9H7yxv7!K%^O zVDPeE$)J7eW5IrIMJ;4vfNgda;A1T$zQ})~ym6AR6dkAuzGnu!e1iegzyMq8+1k=~@9%FXX7Rv6lh)PZ_-McSk;X0Wq<+r@X$M zp`n$%iM7Kwg}BqVl{L4if{KHRlq83~wI!Xdfwi6?or|T-ZxS#r7ml~0rJ;i^iHoI$ zl|6?GH|bwJINr*?tLaHe{_5gj&P}Q!B|{=$ZD&ZrLdQ(^g%ki!LPEl2XJEu3Cn)?M z^0)uENlhFaY&huYot>TOoSEpX?TqOe*xA|XzcA7>GSa^FptX0ka?o|5wX*;CH3=0z+5hLY-d>RYcMClO-52`* z6Pbgl(f@_)cgx>of4$e=;<$b@<509Wv=gwlv^2DG0Q}duTz^UWqw&9b{!J)j>SAc2 zB53+XY5(?405)dk|4{w!mj5HA>VGMj8Cm{K`LC9LQT~1f4rx2nH$8QK+YrFOMgRY* z`%ix^`riirm%;xY&A)2jh7$nKMgKpp0f476nf?F<#tSAU$fw`}ev}I1AwPrDFKucn zp=z0FF|_Lb(S1Nk22)5D&ES+9njowlwt!*)ec>xAIE){O`)O-tO^lo{+sc-=7n|n? zsc2@)DY^9&Ao-xXbNgvCJ^Sslz4Z~g$^dVR=QGmSwhSyR_y-awe;-s-2!38zSl=+x zb=4Z3s;p`7Ts?`Qia@H2(j8w!#?DnRU`;#sLKRt>tSj+nXDAUTvj?+ykkf*bsZ^U12TZX|KCq~pPe>&4AE3m&;I2ZYz{(|$Ym z<5a-bP%Xa0{!pz$Y!q4|3NfeH%PnyNjoYKQ``*jSZ`ze=A$%1>j8@6B4O#mp6?>X&W&i)8zVeUSD6y6W*)RnPkp;T zZ+(&x(Yb9CsL?y7KiJ;L27Az*c~Hn+xVUwCc#iazOQp|dqEWAm_?FuGvm~jZ!9}g{ zMw!FrPNk~h3hJr#nTNxk&ed}^2JU1X1upVO4)NTRWTtK&OxkQr{Df&+0#hVlZzUcA z0@6}TQK`Yp-m`xPhZ-9GFIVhf5?c>Y7|$%|xWFSq<72a4@%n7NTs^tx+|(*ejqO>Q zgb>PT^eN4`vK}a096y#nr+ut5C9%@zPMivGW{@)Ub62X}OK!^e9uzU2*)2jz#WAKX)mEYxxP-TXJ!0_Yv_LN~I?6q+-wUrhk@txxVGu2YSDN_Fq-ZRj3@F5`~dE!_@liQWu zlVg6~?)G+P?IF(OLno;B`Q>D%p-S8{Yhrz|eUeLCpRm1d}f3ooV`u zHBqU?Q}_@h&X2Tbk?|Bf`9c{!6t#9;3`;|!hOrAniOW`eVPxbc5g3r=IsgL=Pwd)k zE<+cRNAA{fexA|;xRw!kZHA#TQ>GezL(Cz3er`KmVk>Ykb*ZU{^DdOB?J4l&o+pE= z(O&ktD0ab&Kv2FfQgrgAaH>70+3RK7`4m8?JC_uoJz6#IjL39*#G_#g^ZMXuCU=Um zDm&r*r8xD-D~2|iyY)ug-K$o9qoTyzbCOI1+3mJ;u0ZiJ2gt7@gX(zj^sce7aWXu) zzQA*d^$K|7T{t0uWcs#mq#)(;bGs(w#i7?Uva;PyVxtcyVh?L-zs@VtA8krExn8^; zSq;_Dup962@-zW!XSG`7x8Ly;Kp{F<{?6g-8Cqow{FeSLRnk(+Dz~dYEL~6}f{5JR z-QBIxQ6lMlfq5vFDW9tTQ2lh0+o|~AX6LZR;$9r9PH#c4NV&d_gVp1EGH}-0@wV19 z`(e8M_L1tnCz&72yZV+EO2CJ-IQo?QNnLF6u!U{lZr`o(CsW`Eo!9I{TyhE#5Mzk4 zA4m%YvR-b9B?=0rL{)X?Yk z#9nK%_~Y>jFqAkI&~)h>bl%EHBM}ksR~T4dAUWe#8^1l_=@HlTG+QWPAmnsa^Uhxf zRM-x$l_ap`P6#|s1+nx!eGkK#?TR_L~A8u}zIOC0zi*xnp3CA+Ftz1)-K^(i`?LAErVN`I{&bykS==g3UCbt~t* zb}6RbZ5A({v(AV(R-*<@zP<<~4G;NO3RWOM#sDaUWcY_;=)~-I1@?a^41F0gSTTiuS>L$=d<6=m;Q|dUm^(@tW8%VlLBa7bD{iEoK$aO&*(rF>KenXRI-P z#bEo`Fs(qLtTdy4hy5NqPve`}Oo6a|5Q>8Mma+C8&0}Q=udoRDb$_5-@SJA5dkv`k zn5)AjGEa&i_Z@8V>tY`L!`}JSbSMly_o`c}Yl>cc{3R9Lk!{0fFh+%+9U$j5=*ldJ z)eIXP#1GhI<;5dl@&Kb&3ZH2eQ4crNNk^IP$e}}l_#E~=D~tA;u={E1bC_KI`YT<% zd^AQraI)aT^GwHPe7HIV6^ELT)Gli#5{3wh;M)v<25G`7NAAuY{9BHZrVh1^-2-tLmP*t(qZc&P)A2%lHW z^IG-}=#utjKA3=h5S5wA^?|emd z%1#SLf~^xk$$&?5$5Y!iiFlgKHi<740uf6Q+KchCVZfuKygyBAmp}2W1zO)upwElV5iC+ zXqXeUi4vb`&H?dzOHP9@d3E>GZMbtLvyKS^O=I8qQfCNg0FNMqRM?fk*mrf zL)6Dhmumiq?cblzYTN>*u6ZIIz(>GoTIvg zqJ8j?VH>`KpOEM1-B(Q?Un|crtj^LrDsjK!y3A~;{@8l={q|^q@X8YdI6*}OCU91= z@t8x7GV+0pD|BLzX?L1owPc?YhfY&*jWEG>%JWnq2L&c9V~ZSkhNx($I9mZuT<9*} zq9S{(p|inpQv2iYwZ8< zO^2eOy`@h&LtgTz7_t-6I8+&RAZHpo`@V)8bFl%Xli~yvPYJm+&plcYf8HRW(!oSu zn?Ro#-vLImEic+!&tYApSuu8CommLX5JghK840%$6v+T&W!?(ahxX1^@GQ;9JOkaL zl_bK}#Y#pf>W@aB*H69|KCGTP8;4q41QRwOn{19<`|VoR#qqNTxQSu( z8J$a`50E$9uAi)DP(z+qP!aREhPZi*tCNY`C+YA`@8!Om&Qg_MY;`6MPrj=x@;=yx zyRvdlB`wZnc@bO-=n#KCfoZ*(sQB~|v5YaPWtx|*qqlLEr}pb{br>H{C-=Pb*$-?X z+!S-awlF-3kqDg(q(Squqh_1*DJWocoG`b!2M^02@Ow;MsRJaKzv`58jj`(EDqp+E ztH|0MrbDUq2LeA;YP8?GM;}y|I=B`acn+HQvyXvGFrTDPmWTY1UpFC$?tfV%8#fdn z^Ry&bv0}=X!3LgF5}FU4bKg+U#)wY`6WhuA5j#BbN~d$ID%1cF#mN@od-?Hkb>0)j zSy32PFq&tn{*3Bpn>gkV9o_>Hu&2cGuq`$vd(O-~wY(k9Q9et4*L_Y3Al6;1#rj5| z(E;Q6+Ld*Zsvzh(lW&d`H-8bQzvR(uiNlXIJgub*)QL?wAcJx4i#Fevx3i& zQE#X3y9q6XfK7iVum6D@KexI3J^9_WbUY!~Uwr)R64a>O7L!=)!pLmmVMGVu(6=oXz<%**996M7+>KaJ%~n9I4PKMiR47Nqa45U);XCY7vs>&q#Rd;d z>a6O#*Q#?AtRPxgmQJ6t*9zN%m+2xcp?iq#NecvUX9}Im_E)}9unetbe0vDCbc7aU zaO}zH8$y+a$VzpncUf_tf~u!+P^=Ic3G8XC+~eY(8iZF+Mz%9vXLf2aN&f~y&YsX+GHLdfoQ~w+<1z!@ zhal6ay}r~d(?a+YpWF^BQ5u4K6)d9gXuPi&z?hv{c|@k-QR}v&aYo~8C0qKsqM+5m zX4w}%=g~k` zJMYqz_iG4OubVW}^2slASMgRla@dl%yYE_oEkmYQOCcegB_JV;vA3)wd*BVtqo1fF zY@R`2qDj&juM2iQ?I@!`y_440wQ%#dhFKs}3#mi^DzDMX9`k$qNw?Q&H`)@O)%TBa zK?X)DbB^WYo$72bm7&0I!LEY$|I#_`s!6mjT$4$q#+p^n(9Zj`Xn_P+J3Xg6UU2te zwY(Q-T^B(nc7XM$Lm}XjbiXB(*|m|v<1vbnn^pvCMaq#|+ui5LpToCR(^ z3l~iF&2qoD$~6W)_-r8{s5hk$twBIHzOZ*#xww;TIiO++ZMhcFxC?Q3`sBNA!aozZ zTBsuetCifS*tqx@Z&ppRSU-v^Mp+-6P6bQ{ZeAAuqCffp#{aPN{NvazK(5R zmlaI|@-$rf@d&o$wLV{do~Tbg(<6g9;4*IUJE3e1?RAZVOasp*PwK&Rnc}YiPc<$8 zPby113`LpQ&8JV8lE2D{JHkXABps?QqKGZ~x+K*v-{6(fArO2u5vm0~Ts^);q3O4< zIRcd>=W%PBH6+ij(HUQxia?>2#jCubrd@CCd&%zp6`675CmcQt3q1s zYaE^U(yL{R<*BbbenKOZa1W-)L?fzO0DYCX(l#a;p1QzVrMvsh%#0tX<7n$%6z$iO zc>Wzsg6jcQ8S3uZ85BwK*yqW{;d&O9nra+(0Cj4a%~H$nlpuoZb&kpGd6gw4`Teli z%xMb3z9Dua%(f>ZG;I{w_;1Hjit(Qi=Pbq9MMzb4&Kb!}s%@L4awRNrZwgod+>2~p&hAU^RcGP! zxpZsi!a(WkO=Takgzmab^z$;3GwNUot0*^+7@P?A2lVrObE#mcFq>DwP@FYNy}RPX3i*bW?3-ygY`z(mD0 zpyH74US84I&vmOU&|ivUEu4Gh1LKB=&6;ocnjAwO@NYc(4eOjnwW+Ajf4bKw)gWxLuj*YE-Yh@i9SS)HenV8EG8-ZM;e+`-o zA6$owMvVZHW2l6YyG_L?kEi@gVaMme+|5T zdUFr8pB{fh$p4*(Ki@$V-tfJeFVS^n$=s!Ax7tc52CrzG!HE1AUi|g5HkrKi56LbY za5;;95#CoR?9Q1GFueryKO)Pu<5oL+Upqfi?L|q~Dz5&oDXK*F+i*A#WGgBe1nFpZmP-gkITDia(kRD{0MH^PmZp#k z3Qc@RN*;U-n!yBZnO{_8pFW^zMM|ev?3^5kDZqDuOaHN7w<5F z=XjBtwz0nQI4NV+)3MI=AqF`8syDbI)&2&_mLOKrvW4*qfbXmempL#VQ%|rW<7SGr zuX$drigmbMBKs;?&0Be!$P&3p6eJ&&`8=r+@%G{Wm#ezmz|C7k?a?Teos$dqK?t?V zH!CsQ$yg^bd7^s)Tw~fp4EA5lDy^26-))mK>al6?J&WiR{ApdvpW z+Nw_#Dczf8a1m%q)9Y(uwGGo=X6lq?aaYqj^{mgSt_XV{9#fXoWIhjlD^58*dq+sG z%(-8z$IY=;Mh$(snm(&Hy5oMe%LB!hF5>;9&pLWZ6orQfkzn7EzC>Yj7_m#3uaW-u zG|chsC=%#ob_yp(Qhx);7?npT(?vP#n3gq|2{#+;ooNp3jF@;J(QMD*F!AGSUt1>g z;5kkP%RGaoiSZJv^70*kM@Mqvx|$xs)canc-Z}0PN-A>+%sKoEFFa*Z0^i2EQt1*a zga8+LIGTrSDg2RXqS&cvK9iS5zs!^46fQ0Y_bGQ0$EXq0_Ls=RaefaS7Y zO_6!RwHOi4S;k=sGQ#HCyK)s{OQy+`lhw&s%=9iv|J<(2Ix}Gwl+%oZ=9e4@?zInH zd*mN{J=$5Tp+EQM3faw+@fqTbk?r<)$4Ds&zaN%T`lbfSnE8Tk5v_)S*flU$5bKNt% z+gMiROBoW|tY_%8k3-3%`sfs&NVPO0KLm~D3n(t!3T&C&N#7Ri<7lZ(TrCaCP_b_$ zQPuCw7hesZqhifD#}czJh|am4SQcI0N7uDJl%hDQJBVmU(4sI3^3A=66(Wqg>Ma@X z0(zV!6pEV$As{-rcD^m{-aF2?U+N2!=0}4@<_N}$)V9~|= z?f@V1{qkU?3fje!3k}FhMmZozyQPGlDeQDKmyIW5>j=WWMb5Knypa?7lsjYd`^$<@ zz8OolyknBQM^ChA8B7ZkN*9pzIAd~!0Kk{pjiQbr0n!`K90D8oYGb z^mu*KAwKq0R7p=tEU2h)TJo6`^7Se<$gjXKAO*8q^`P?NJtmPz@jI;iL~HDv<9z1Sfclugay1-?lTdO znH;I>N2wGC)!WD{Ms&&jF&TLvJ2`Kw%YbItY5#7ztUx!VD``^19vm>74 zy9)UB3DmGypGRb%R}>^NYk(=m>M|jz>KB*kw;f#(UwRhQEQ)g8j*Htp!b_7)>bO8V zdLOBaA+0ShfQXfiC zyzeywIAbaDy0Sb@i!S%vZ)aS^K}PmYpE*TfV#!^kH+6hGw-hxswAW|771=j->CcI| zpwGuxBPm7h?H(R#zvHgnzjO6_S&~rUO++P>f@5e3I)33PohMSLQKrwjGAoiX8+}fs=ez@acY;fWF@}P8g{A-QuxufL?}|`6 z_VlDa6iZ@6qenzoR@_2HgoXz)zS&z)H-D_iX`{)8_JfGUVUmFhx7~k|On|JuAQ-;_ zXlme1$qbUkhOm}}RFEPe1a(zvHld{`vX;)Qy3Lrxe`zaGO1ZMJ$}V> zDRKOhp$fgXFWgG3e(wC6YF3_qx~X0N5Xp*5^UceYuNiv&>U1$82j6m z%@|XYJ)bAqV|oI$sb`Oj+sex2K%<9)0Jccxa46j$0!cBS7Eg(&$9Lt#jTixRej*+v zJ;IL$p-0~16Ft& zTko~p1I!5Kg=-^@6d)rqddy2JobYAX*)(A1$FKI(buPr7$lUZyZcn58d@fa2*%%;)beAaqA-3zw^`6_-K~^$$6#asJsU z=t!>TIOkNk5I3Ol%>WwtUYCYu9k543yjIzM!8aQ#rEMN}_<91I^WQyGZ*R^f;r;;E`+$5N>Lt8~}`FX)#=xQm@? z+$Un{{v;RMXPGo(Yd7B5er@@3L(X}bOt?Mhmhla4vknG^pU0H7~fKwa_El{PS z{W_4#{Gm^559_4m+u=>1TQ}dYft3JmEi(z!Y7tMWN{gd{cwMnntm7xX$smXwk`G{! zSdd$a-{kLB08s40+81j0{(MJ=MG1E>qFU`HKKQE_rrHpXjwn!XcTZF(=vtXCD)<(; zZR>XKC98`7!+Bz2lWX-@I1;EePEUgbuu-85kx5uttYf56^DY6^DwL@Qv0|RxI=PJ~ z*@?ocm&@jTPsV?Bogy@Z3lhTLTr6sQV7?7Br@96iKkWVdE;7wPvx}x&UWfxx)wrGE zvT95O>LY79(m|DT7?Ftu+)7*zhSx(PJsIb7)c2~FHPse$z^q&gsdn$2H-=JqrsaiG zSYP%X;JRC!{0Avg9Y{yzE+@bXY$AeAi?53vbd(QjlDN zG`}nBPkfZDYvcQiuj4GQ6wmC-?3*f`Eac(CI~hmp$1|*9-YLkLaD?0nlV-gt`^IGR zkdV=i*Z3BO>)o@FvFHwhDs8Xnx)Z&{NykkuA2KOntHP+TdGV z-5l*{)}n4onVc@U->;XmV)#DUgBJVn<*T{IFK)Ql5}~Wqk|*qhd*RltlzyHoONwGv zKUh>n-$Tg1?J~{gl8_`UArO)Y*h%79D7Gn!efyF4})0j8byeU24>YMHj{ou zcqG{&E2HOB3YU~lHPBF$MFhahjbw|6GYEOil(zqhHnwHa@i~Oro;dX`isumX5FjXN zxYs()xH0Uv{Yo7Hu3lG!>6%=*Y4U1-Sq$ps?Mbq`tQ)6Hv*8Zbox@dmV1Tuy0~Ic! z+wJ|(a&@+Ttx{?k1RmXAlEKYkYEW#grh7$wLMGq9DVO1*zX~v*SK#$Dv!;usdfR(3S#2A$QSxDJ3zlK1pu>wfjHI!dMShTO68QnL7Yw;vHpWiFGs5 zU3EebbM($$*B|UC0(Mk<*eeCRKo@rX^hxk8dnGgFRS*wbV_;qUlSU;y$i1xL8f9er zGxM==9jB?+E)7+@@9k4%`Z&F|qz)gjKT@n~S9`mA-s}e|7#2UvI`MQujF<_fnx3ig zV5QBnHC)E*@O362y_mi0NQFTnep(+cHNDS1vca;KDdc*vCYibgs-Qes0X z)lGkxXuq3s*{S~S+=(|5Du8oK)pc|s6B+1g%6{p~LNw&&L}b!o`qDKD`*4S~vffj{ zrB0Zy#%DdC#HTXG0wO?~xM3)l%|$QGD=EpyM^PVvJPA{R#Z=TMBV5R+Fdu3(Qj{+I zYJSK}*B@RFpn1Ng328eNF-~@tn{eKx&y=$V%r&llIysQ@xWH|t4%Nk5NOAczVtP?Q z?>5kxef9H|gq&i+xTWJ!x}+=6HAW&b7NnPf`nJ^sRJSV$HPl%N(_szm!yuk{1}U!O zsu^}u$#DgeB6P0jT(P}M#H~if+Pz{OD!N#7ME8D#{c&+F6{iJV;V#DSfcTiU-!@Xx z>KquLI{Xe28BxP#4Uuf|)pw)tKd`b7YR2ff!b(bgTYZgS(VR=tG)$`-R&Fh{qEB50p8u&BfbB_2CvRfYFpNowWVhGc}#Z6EdG2ZC8A_$=7iS!boXq}ja-Aq^ ziVGFrLtZatwmT47*Cxsm0f8?A!PqR&hb!M#fiyU>Kc>_wS3@68ZxrPFJi_hNjF7rS7{Eb zcD%BLNRR8H(#+hu&P-O*eqt@g2rlKJVH*n+4#VHKHiBm8$Ze=Qv*e^?%5HH$6zjGG z?8d;jk(E#5J&q=>=l6#`PtUHP+U*Bmt!KzVq^%D) zjFt_MvWJdGM~@Zo*M4SCc)~FH@bU^&FYH*cHU{^p$RYf*Ro= z3)!f|j>Ah=DZp(w=E*HseR1K?J=d+Q?B46EIQNH+HCA^Zg;7>^dTl6~Y-nH9TBDhp z9gQW!Z%*W0&$kpSp`B~hSI^B5a1Hcskk0}wH7dYe=ZiNrsi_wY8EB*lBgNpOlw9sE zw)3$UE6KsL#MpA#k{L^GKv%p4Z79GBn@c~=BUhv2Ar#^X7Ccb4EUd}t2-)8JiTUE? zNrFX`LA4Ugcea9Zx;@@1qeIDk#ACjBM-khRIwbznKM>6by3eF(NecdFr!lMkkZNCH zOI%Z%Fi)WJ5u6Miz?XS+@J}DHv~r710v)bcBT}6Z~eKdw8qikVCG*RB*IinJP2 zdYR)$%g?&LJrnP%h?ttqaoX}y1WG!EtW7Hvxhz5IN^4hi#H^`so z$OHCEnX-p!n>3eeEA2z{H~joHH6`IRqBdP6#1ZwLyfp*>OuLkzhS|!T@1uL1&ao&Q zlMDPMy5E_dzD1Y~OQ=s%8vYmFR}J-HUtyl1qk9YToAb?2LKsKPdf|R$l$(8k-97}7 zTJMsY9tK)<8Z|1Q0>O*zbmzk$xV%sT;&6eTl#&tEPuk@%Ly5$Yi6GrChNEHcUZQ5= z^pNm#(EeE>lR#;4K`?5kg}9&0<^?Dgo|dYZ+wm3D*bF&;mKQ|sOeMzNUk=Qu%DPGdiTM>X<% z@cZSJS-o!wA3ofp4abxop4)i`g42J4RlB`#WsLCmXfE#X=sPM`sJY;#^d&T*m|YU&^ToYpcT<4P3WszM*e%#O}PxP?RzKa~%r8=FjpgcI~| zx_mV>A(U(QH!#mT`|(ZDwCkjVYc>DpDunCDFWv70tE7$xQ+2V#JO6RPzi=?sz_*hN z(-y2dWzop`<~)Y_dy6}2w;0+#E;^j)p}(De5#F};TtyZRRDkbOd@!9Q6?lx=owgN& z1WEoISziwz)tQ8O4j}zG78)RTysfJ63~6Y=tCJ+&y*7@M&Kz>#xF3`E^h@jyqWcdd z*bey15)`v6H!o6TfW(0zH``(fK{cTC=Yp0xr7>bI55d>t1!)Eb2Bmr!IP29WsgLB6 z+5Z0i3ZUU1Jt7h5S@t+|#)1=xN}tPg2lfUrZ|-dKW)c@eh|W9g#s`Am(y?==jcTbp zh2yOiKfc|pJY?LHTBB7Ix?K~6fGo_j-Hq*dTbxw zsM$5Un<~;ez=8lmfmdcM?Xl|iV?`3JOdp=_+h3UsE4g``K)l5>(Ii8{xQip`3;8iK zMGEcRo0w`NNBo(zmIN$w@671s2p6RqZL0}|bTA(5_Lmo!(L@GCVj~w+0(9})Ct?cd zm55tUAgpTRi84G&WQMON;EO6(%mQ9@`v>|{PK6c~w4~?z{?Aj~2)?*`Jylh4Gtlsc z9v&WnEKJ$M^+Mg23&IsW_LyD3=t_e=CL@h}mfgA*FY|-pJ?1`O+WGA_qm`hT#i$8_ zD!qFQuS;?f7|unWHUlM#v>Gv312~AR4C4;V0oCgF)N9hG#JuMM?hi-MRrU4x-Y+Y$ zf_x~jh@aZsZ<*GMO6e~m%89wXEEe6+@$rYTjZ^v4B4L<;P0S_A$!9}D5M!acz_E(Y zMh^FNCU@22!s;6Njf`pihiqOqG@hpkTY8~qC6mbN8lf>Ce&4wGeYfRO#aK=15t&GF zcHFCVHJgm5EVCm+$IgCKqTTW(S=*cP@DAoVPt4Lb}01FuLNv4} zW}P0FgGXJIp{VY}9q(7j-&y=^t97MGQxPj1x7qJ!sHT3tv<|!_%EvmNU(7hHpD{oL z6~$5)_#z&-JciRzX}M38RSrsAo4#gdD~xx4Z%bha7+EQQy8u%1&T?(!MUvXD??US( zSKLj``=x(T3sPjVXF{M*F2HWv`xSij-U#jfVMV_zV!Fr`!|}NR2U0{b11>vI1{4C? ze~_YoIP$(U*B=qgJ9mDQZ;kR^XW`?)$K4*X=`lDv{u>doWwHUXT4>ZMlips z3C=FG*hgnJe$nIbKCe3HH|hWSqV0FIP+hFid_$CTjn67;VuFiBsqu3thN^S!Qs%na z8k;T~mk2CEOk+MAfaF|84@j0YDc8jpk$#AIl;mrAOU!9nJB*|MG7OC+A}l;n(zu3R zh6L=QKGy!$0)I*D{ZcK!9m&8@sZb_KnbgzF*4y7(+%|RCdNNIVuAM5K%3Yq8Yi;CR zmZsm0E>$~)?Fj~HD4&wOU^uo2SU=e@t3q6+mItcrMp&BOC?mXmDTv$BkrhhC$=-9< z@ZbTr!;|hPXUgTHo7xwIu6-llMds~<yZwYm{e2WobSV0v)9!^zrG@C%x!`(`0MTdC&w4)di=QnRX&efTtNIv zvt#8vvd8O2v*Fv-A_bl0D;HFesVq=BENH_?433WW)2=VbUM)qVOh&}jqj;jFd3K*}&)_yp$GvXK?-Cr(BD1|x>|W_rSR+G0~IO$@kPBih8dlN%?7 zT3gb>hP}%h@v$5oGw5GjczZCy9EY37XE(?OI@3hg#;BLEPdGs#?8Q#lV~09bwDwI{ zIfLzM`KPx~au9QhIT;_H0R>-Uccsxj=!E#(Wh7@^gatiU>&MICCbO9q)Mst|(z06c zU#zt%!~9W*AuKy=6i%Z>iD^y2f~6vx%Axy7kxDzRO|wRWQg7hv>5`Bc$=P|s#ueQu zlJ{GN-{N2hxxw3Ls|GtN5513xMM{3zCT?&qscFk!d?$owNuiWeAzt3PA!4}LBP(7x z(MdoZ{&DDaulfJ5_s-vyE!+Qh$41AtZ9AQ$W7{2dY}-c19ox2T z+qP|fcb|LDz4v^^^Cvt%t+B`6Yp<$WvoLGEU$fvK8S-KCO?nGDiy*86Ie*ybI4DsZeEAyEtEAX6vr)|Wt7BNCj+`UM~o{Gbs|m)w2+5e+OEpGDA? zO~?Dv+4{rpi}f{f^zqHzZO$F}uk%HM+AW`3*%JOOHXgyK@B!vVB4T35@yDT=V>_Fv zE-7Qs#NeDw58h5^1!;72Z@HIc;6M~0ARsmy?pq8p^Y6h-Mn!4FYgHa;si)W>%qUu) za5~69VBjDPNNpI{{tc3<6*E&n?elt9BlV{&kE{Ju*dAew1hnJ+QQ`(c0J)OCG6hCV z))r#FQu-Bk1?qY5jMbXxT@o@6hxz2^j|kszQaTER5l(O#3>4w+)dqWZKiZMR&W2f^ zXg)dDm`88>PQ~{QJs|AhM*#2nIEA}7=#a*BzkIgK2bjY9vCTvZem*JCE!CX}N=SN|O zCO{JY+uwa|Z?Z$qDBD8VcYA9kGL^+c4}bpilHX%UGV@CIj>5_6!G)yFcS!xMPrFg$yZt*#r5qP|jVhNwvrhd~MfbI5ZhS5m+O))b zN>TnhYEiMu;92a=*Pg9a}2+%9U-z7BESSJk;sK_EFc1|qLIBgvM9P@A^K6Fce&Sf6!~w!mLTvH8XUy$ zO^+=RsCA;EqVDiCqY3e=?r~Hw-Z;>xS3(cGIkf|OQ(%nVoF7_)!K$T8jSZ6`%W=;J zkWf)YsFIl(Cp88-*R z<0?l&4j~Dd?`tx!N@f80D+;-x53pv3h3UJ{#;nO?Em%ie2aP>M;!BV%-dKlT45;so z{l^ZfTj^mhWVHn}M*m&o|N?+bjmE#uGN zUtelf7{09CwUkc=+uLt)8wA~}zPEu_!o4IxjK0F-Y%dF}-xj0F0kExnNCN>MUz!f| z>BXzF9v@#Pb}KbmNtvKYQt+3QWV1ow`+sutm^^~g)AOm64lxC9;H?ycw|as(O! z$jy5OIRacmd~aQY1O1&N!jo2fPnb=@iV9bnf&9C%)K+@ADZ=O_4TOVSmj>c?V556T z&51$B>nu?thuSbtjh5+}Phh2KYByBElYWr4WKg%E5f_k##LU}BDTVV6+x($<(&F>$ zO8tzzU#l4_N+ZQI$L7l^AEqYJB+qqmt`^#tYP9I}#cAQtIBW{q&u%lkl3M%vNHTb)cBpva1@7@NQRrGJHqs?;B;4QunJC884=JwY1uJTIl z$#E*}0MQgLKu&m1krC~08H!d&>uqh2ZyWrAq?)MKK$m!M{4p1TF3T$1jmKlA5NqN5 z?!eDsga79^ z)fa=#AwWbjjwxTFL|7_wQ9@~Iyq}nGrI_-NW3Yl_o=UYZ8%YZpWl7UwJIF4wzpTnl zNI%dBE79Fvfx$K=z(p;U(Gea>DNG#IF@jy{w|NEqi$E?2Yy^w=u~+>TW*k7*M!AUH zG>t)pqJhb7JJuF;i>fQkvc@Mqw%f-@QeYUv4 z;$v(KHB&!#pVyCDYt6fnGBgP45_W2;E&ZiIJxl4+6c}fJR>Z`npXYwukz_`2FBxdY zZ>CG15&%}L0?vwk9E&~6h?cCSv_mQkuZK|@sLsOLD|r=T_C}~ zjoQ29G3fSznLN2xG>H$yvm8yB4U7aHoQH)2k985<`T%G2po<)3;tNK_*~ARmtZnkT zXEzfMNIVA)|bC@gjL?@DRA6R^=o3_A-fI+mMY|DT3Qx_O}>*nyg zl=QgFXJIa-V!l1N4>7@h1}sSmAXwa)TN5z8fB(jV+kq+wqVg(FpjkRBgZ?{JTjH)q zAE5r75#$VJP_eVCCXY9g32Qo8+pi&pDRe%(OX(5;=eO11Hg`V({~A)MI-^d?eUN_& zGGxAlruORjhE#O(WUgUb@^;J9g$VWzg~aJ5)Rkqz4;7zllGG*Bu(1=IC%C@y65fSE zJ1ikK7iIG zP^shRUn@_M3$C|x>5w}bs#dlju=yi`?sL`Ny1Ip6n|DiVnNaCR(2~C~q9cgRs$K1d zA3IBDu?UI>0z>-F-v%q6CWJ>D|6Z8Ucn!r@?QOA@PV~5n9IbcgjAN zaM0*4n0(u_2;MI|JdkB_6%Ju^x*DhrM zs2%y#3)5H`sD9jW*?L=H_h3t+Xzxw>?<;35j$)qpThEL#*2(O=gO08XFc{|QJaFh3@{zQT%v-Rq?0es)}O%5J~hvep_pL7|u2 z_!`Ttw9xzOA(Fn>qj&S|10HuC)x9Powqa5)dW_G+*%PqN)2++!6@NbhnwA2?dPO@HYf)h%%kC(e ziZKn3q-nIn+m)bTACdgi^$#vg7;b&q=`xn5z8*%Jx|xTUAa6>vwQ{3O8@1o1wVI%y z9mz2sZ7!pWld_)0!qd{>0+<0DS7x|RXx_C~!G`@^MqfIzxBT$gC?={L%wrYW65%tK zLbu17!zE1pW`YAVfBLf_`pqLR`Z1-p-5HjwRz<`|>65m*y_3BzRH}r11QatXjKq>` z%)AQ;QOq@c1WKwRy`^0{J(WguWwuC@bACK8Ua^U?83+pMCvLBCv7!Vi-GmLlfxaFdPNyx17EzYG@T2_~sR zZqSLoP$`~o1I?3wbYkJoH-2P_eQ8T?hWs;E)?{3HQx*f1IBJ!KfMBR1h}K|qf1y$Q0y za5V`tc14rXNTgsi1Zx)h!Sy0$r8ip7D!_%C)BJb zz86G-odWAU_YtkrAJArH8#t_L3l(2}9GJiC$pL4pnRQ&MMYI93XWV(+zXXUvQ1It> z{2~dTe*d^sTn1if;1$Y!KH4{o?3)V;_F5AeTK5n&m! zicEH$O#Rq|w#X~j(bEe1jBb{;OSG?WgR&|Od`na^pjq5{&*0#LL{{Y-#8 z*|^BKmT$}Qy9;f(EvF&mDo7_n8VeBAsCws^gB+REu2$W*D3r3_hU(MRm3+Ff-|88u zAw+P2A9>eufuC=0v$P*;ja@!t2A47S4ALc;p7gI1qmD;a1sX)2qFR6e2WNH0aKvYtP(yCfaJD@*0Z6=xk| z1s6H6Q6-?LAHqKWS=VZXGnka{t?ns3SCS&XWi)M-Y9r7&mXo76o{LTSa%>T1%26kc zYYReme?VC4VyqG7tCMH57k|pcW<=I|*;%Is@AH^ZEcnncjBr;}xmOF}GmOo!3B6A4 zNy2FR7uA#-(jQoy+@CSqlJi*Iz%)->Sf5LJzJz6GD{Q(0MuhlfM+5g+tKBzIA~wfg z)ufhhc|$ANXteO*u*&0M-+Y4Xv4ou?<9BH5A5l0o-%Jnk_LM=RgjAx_D0ejL zdS4fJsA-{BP?md6BB9^WQphYMMw)lkW9UNA40;Ad$S0~dh31J^OheGvHZ({V(}GN* z$6K0XYOKQN)y_Z4n4PdBEGTg^Tp_^0Fu!V-B>1;AHFEif6sVY0x5$pU>~N1fy<$}- z?$F#@+CPHTLK0?Y4}+sX4q8ZRkn*HB@9vbt-yVF|0N3SPH!8LNs$#)v#98jRM1+r4 z_G7f4NA#RUmCZmNMyr}NFZ*hu#q3qImx0rZ5i)sJy5H~P$J(c%>hBeNe#HeG6ln7V z&V8KggPZMO;(AK~*8#tE5E`fz6Y<|Nlp9SJb*l=l1ILQ?@aD%Qj@WYz;c8+&#inX! zb?dkaiiw;T#grfm#}V;&b<+x)-MlqQPHvT>3SI^x+@xM|2a&g3H&N|n)#x4DV^wJjAnn@=PZZLui8(@0_y9bQ&AJ$ZbI@& z5FoW2cpSb7D^}&Wv%VZ{O^Iubmcr{$N_`qHe=v;MMH_d<``t&Dl?R%IP!YT2?P)Y? zKA1%xM`S=6kn96#9R+s{ei+L{wtvN|1XduEYqP@rGT1U=CI2_HBLJBPh!2AC7Tc?~ zl*EkvBlraPke39AKOK`&JpTi`2=IY}3h}cNPFDCosLOYHfP*wEDR=X)><|CpORxa} zs`Vg-e2RaPIWanbm_}hz*4|&*^#6t|*+>YWTj2C1)Oc_KkK@O^P$>OpsTsrp zZSw#B{Qt9My1EUEKnubJl%_DD+uIYbFwt?Prn`MnSQyFoiaf*tGOYFAStb5s;pphtcfNzpjWLg%^zvQ(%Ek%yit~MeLDTEc;m1OU7btg^bc*66 z|BT#%5d#d4%lfHz)?oN@ODzm8TGn2Cy2H!CbLwZy>-i2#5+UY43?~EOPxsp8d%-`I z6kKe~%MHj=*O%^O_0pK>a!3OtjUFo_X5mOt~lt_xj+fL?`5r0A|z$?5=_~Ty6z#BzeO8$usFZw zDxoJWcu*>{c$<6yz!@?U!%RFn9#gX+7>T!2tM0SOs~!zv*q;Q2Ie~w|+Xu+`h`WtH zIiDvq1>&#z(DC>#P#5X)aZpDKW4O)5Al~XDg&yz=0+!V5F4qWqvE9Aj!Na_9WSUPG z36sncnA;0yP2cg19&?F8m7oJ?($5?dHUzUzVEhz}eR1&sh`0-J(`Fl!k@G{#4 zfV{1UU$vTxmXfno_J4f?3_3iYpk!|cvQh9(b>1GsBb~;KZXbCM>sgv51lVX;&g3blj8j0HzNpMEu4z!n9H9PN{-&vDY*J9-ByU8 z-LNjccuXkPq#X|F`q?FUWTt7A-j;$-r+)jRk$lRhzWce02F>d>V=?aBsh#8GW@|Y+ zV}WJ=6#xSfZZ>Zo=bWDn@tMek!NBA;#mGH4dIy`GeFoQ{Uoj+U_XG+AAzta;BZFCf zM%%F$5kt2?_2~1sOvm3c-F6ZzCMkZNrBbN){WXH3LA9xqBW#n#-a`Tg&=GgPbq%~U zJZrWj10B_yqG!_mtDlK9a1F|R)SuYtP-r5T$MzIEHC86*yL}tX2XY!HcfkN{TG)ur zlz+*4?XQ~s5pjJ$*lYiyJwfDs`k=EIkP&BK{W>A1NxpZQF3E7|VV7|m!(i^Z=2DNIHL$Jth= zsHnY#>_|o^&cEgnLr7o#i6HsOd}oTS)vywercMW4yWk7{5W-fRVd(e~7qnKUaf=?2U3ZM~F+eIPKNotm_>pEJ~WECrT)ai-&78*W4Qw0GI7 z7V~88+-8cj=;h~{5jCr)c54Ws`Ox^fg|BH9S;55P-i3(uIZ!-vo-16AVS{1e-IK%g?Ylexh>75Q=Jh-jQVQLRoVO(v&YP>c8I%sEqor){6W~Ud@Fn^FID#wt^6PO zU{m#c!uMLXaF2;)Q-`d43~i6TpJ#cq^!4)l+k7vdO>E*f@h{WQ@YZru6TDQifP7&d z+U3U|DVcHc+39)>V(Z=&V)yk0xI^YQhw+G!#d1V|1jnk2A?BmI`j-a13k3+^Ft6AUkqn>K_>M}rDDilA z)@(J} z6y+9Z7gk-*A28BBimoOt(ym`)*=>A1Tgb8Bot;sC`R3_Zu$d>OZ(C=lXZw<84)l`b zc^F*go+F0Se3$ITW~Y2DR{&8l-Jd_VpnBjw`w_0sl7#vqf{#j?*@a;?5Z09K198&n z#Gm36MJG-dKqr}i#XG1rc`adyw`WeDpu$0O~1q8?Hd;o^+J>+EpSi- zC$)I@5sP^N6Ih@|w;lI~^#BroUQeXPBVy*;N9*SwFDy~I?eo1<;A?x_dhVx|Bi(9R z2oPu=vnHRU@qcrIhrhd8lzyrCLk&EIe9!06mmCIAiY(2S-0`(Al^x?oq;zKcY}*(( zIq&$01s|-wJ%!Dn1T-V7!EIz-5(#W&3gTtO_%QsHZJWB@+Il|bt*i$IblgEV0C7R7 zj3*fY4enobD`J7auu}T*V%Yyh0=Mk0N#+Si43O%MulC@V8UC5jBStXL6Nh}JzsiF4zzAu zP8~-QXGP$c6i&pZoiBNhP1d~b5|UpLlwTt@>70(UFG!bH}^?E^xCc7NNrY?;|P**z*ACF&8RC?Yik3fZ^1G>m{J%JSRRp?DeuFQ4{ zc=!7h$$`Gl55#?YRhU@WzZTQtz*Rbr?}W#Xq}K)Z4;HxWiT>sjq`Zho(|#9f<7kJ53KLe`}gXiq(IXjmalGes{u4h1&CA8F>-Jfio|2(sZt zY#K2TJl{QX!CIxubR#vceipmoHoT?E$OI-0HE3w~(+&7((6WPpQ zlaws@45-mKoROzm>r8^~&0%YFDlfj+H&C)WP}1h2LjWvVbrc=Yx&cZ+`EzE7qJ?4J z5;_VTFJqcU01z-uou9y7p5LMxr8qq;tmFz9Bjv&hxVG6T4vo+~^T9!%TiXl#s@32n zSo%_4tfO|FYKdTsS{Fijm0r()M%Jq`MlKs|e!t9ZAnoiP&#bm5*unSLo#=K_81dK) zS}B9aAFPl+Dnx>;LWbo@CP<*zF&szusi)}|dAWvb) zIx3^=LUrOqd9ED*i&4DVFDg5_lfswOG1*9GC=SSi>J=+sO<$x0I~Tpm zd+bmoyhSzW2xf9V`<{-{Lf$KHi43Fwa_eg2YRpF4d}^;YC>(G5>pZTAQ*oJ9sl8u$>-Xs^U0uu$R}wzZbZPcn5VCtx@<8`&xQF-$lK5#aME;YDUFs z(jwsxg!81d(Wa9=n)=5(Bj5!siJ!Mb7IK zzN@Y=(IoO0|Al>aqd!6gkNwq2$QIurbR?ohVtn}qJ~W!WT)6mqZX>eJ@ak-iA56Oj zZxWx;@LP*7C@>Y8lfw3P8IPt}DLR5iNUHqe*2)N~O&)+4yW1D7p2vex|5`zL7^2m` z@Gv#xV#a)n)_h$Lr|m5ZCv=Bt>5_$wv{cn~*jzN&(SonBYu@K4rwC99ACILAPy5X) zU2f>bToKY!+?SQ5UaxhG%Ns}B99_11|iq&6o&GP_`UOf1hUS!Ihg$o4~V?UhXM z)y6;5zAf2h(j8Z@qB*imc?}HPYTVfkq8@eGCiEP(zK*798nSW9)RQEScWxpi&>iy+ z9(e~F;qN1~)@kPkDGUEvmKB`NQN9zg*&}n(v%XTVFao;+gK#9x>7qhFm^f-cs*ZdH zzO&NO%+=4@^&{$K+d6Z-SD3Okq)y3u&eA%e^QrHbTe#OUn0@?@^l|matWwao1MWj^ zqxt|MoxlY0vl^gbutq{OSU~L6HR;CLQ<>5O%>5W#f@BQPBF|x%uD_mfN&^eYNT;(H)=VW%LB4ynHR2A$`k?gUO!57 z(w4406?-0`5_Jw0NF5HzHh2r86wiBQ*km&L6OeGnR-J$7<;`0bv-vNM`{Z)w~U%a6&`P1LOq$+8f(n=dOc7TqcVWhY+qo*Xt9BGgp~k6Unh{wtjW z4;(P+G7&sL0@7Fzuft*!T&$q<*y?gWdb9V7`elRj&0;=E zF|6hH9+|&6%HgG))ug3Lk>uWJ#Y`7DF0>2k17;&h;tCdovL6IdRZU~bQBIhzGEN0m zC%Ri{#X0&)n4i@# zJa)ebSUMy0bo!6R#5;+OiZ+|Mz6rXoZKslEU>ESpOFb~=js&vxz4%zrPzBTP-fHD* z#(Mg}dlb4Vx@`(`*-hO@6AM4l{b?~-Np%#tVumwlYb2Is^Fb5PLQ(R;} z6}acTMX{a^PC%XsVjnGo1qj(u)NCUAr}fCO)Vk(-lvSpXT^y{W-}Q2vhe-6hn4zuC zHL+C-wm&$l$0PBE%xHG=l(hJfun&p-t%Z$6aV@Od|LN zK_ZF+>=BDCjh2APzD$VhOa%!n;}-Lg*aFp2QSa?;HE7b;CN}+6xwfpPq%_X{lm57c zHBHtm;lMSS_)4e?avj98=E~4~uLvN#w}b5zKRe}REvsxk{31&P-i-^1XqQbdiX$8I z8IQ^8b&|CPhFZeELN!y;olY?X-n4l zZPr5HN0aVp((;~H79ykCs&4UGL+^|i)>9J zOVLD!;gs_M>)!H06o6fKh0EJhg5iP$MIg#gDGIXEku4dTS>z|{*cK~w8-A@WOH-$o zlk#&qR~)`=pOH}ytTuqvQPX;Q8gqr#!#~{lG`Zy3%BASIYJ>uXjFAu$KWcx}$!8Y< zrCtI- zPhhgFT4YN3Zay}@=f)|L7wS=)$VZ&`>p^`bE&vD)7N=BMyhHsua;!EvppK2byH zw>6c?uLw?Sl%4O=SOHZ|ss4KEFLuYf^zxbD)Irx-q{A)^WVQoAk4AeNWN;KmJc%Nv z;9fjKxtOqMSjKytI}d6t3MQLmsF2t~#U$!AeUJ1$#wly=4$A&4UOLHHn;IQx*{O}b z=wEluVE|#caU@$_(`9Y92iR~JC`Pt@ZA3&+P+uBCy}?dO!%%CpRvRyF8kb5Gou_lG z6i?4z@WAOOocg?AziUvl2!`wNlLQ}iE3zg`SYX6eQrb>(DP5TJ+c(z0W}O4F5Wtjc zSW6V-<_mwbCR=bDf!%R>>%gV*Q|5xh(snfGNyOhy1aIneuwyW2i=4#Go}PB%C#o!~ zyG1@KlONLljAr$h%r7}Ay%4F2uDiON`I7sfy3bG+&fTGl7hoC+LtRhSES)((S3FVM zALS0)hgQ$K{raJaVfE8=qRE<7=!J>xecyp7>50oOub4db@qC7vcw?Dwku zTe|G;exG}rq2C-7Un`^HeciS3py2W-hLRE1~v2elj3wEQ^z^;=(r}H59h+W zT;m%_f1mZT_b@`GIR0*m_WPSA6WJ>{#jH37m_@7v*aW4Yf^dO~G}rmg5w!R!CHqO@ z2*QI|uqbI!!`P~%s}9IbSn6V`if*&FSg4JdB6zBrMD5RwPE%u$FBg+$5}iLWB+Y{q zyQoKfcwi2*!4e~g8@$o|tlXvW)gsS04U0hw+jkut*ePni=|mIljo*upAa$#2FG;|E zBBF0#<}fwdlze+Q!6MNy^2(N?QD>dl>1lCDgnWYi)X3jAm}HJaVSAv`sM&ef$+cIZ zU~;Nc7vqrIGynlsmIox5QmRdzerOMFj}G?_q7pLhA;^qXvGK>7e2|(KdAO)C3~Aq! z>Qc|?vRv2b&}er_m~9i`9TslUPCuQSLxD{xAzJA+W%!*{tD5Gx_$-m6)URe{cNiPJ zazLQf+ZL>#yZQNz_Mw*&>9(m!?#nf%BOe4#fl>cUCv=pN9wl5M$DS0w2c4t=hpv~X zZT+06XeMzZ-TAy;Q0<=0iGX#OsorsL^Yxegg)r8WlqGka7;p*ye)fQD}b zMmF84&t}U8B6%h-84FSRc!&?xsCIXf9m&#EbeCrVxkRCvOoK%E;|h&RTox)h z6vuh60yz_MNNWqE8|&++@bt5hCGJ03)>opFpEii7!h{9x)nq2a-X_2B|-tPJB~ zPidFX4VAqv=TXunb*lU11qJs3_ADnjo3UbuhtEEXGCE>K9;rz)B*Ip5S}jZN$_{6N zJ@Xs;bvR9Rc?6$W#on(2bxbxg0G%k+mPyKtTJV;~vK)35ixcknvi4~fZestH^qEqy z7IUDDX0ab6KA!6$Vr_LMw?hs@e0GaPi5H}8yv0WNA>r3y(M?)$Ni+|n5z%;eewleC zho#@mRT3Vf7B2!1hQT{0vYXe08e0G7=F(W9+k8|XzbNk8uhYiS!x#sd8Ec6h=E@uI z-QkdVO5Xh72BqxJ2RhJ(?WT+tfyRK_=D?){b4=OIU_9gj{VR<3MMz%t9Q zC`Rv0?x~7cCeFn?)}kZp$6g2VVKnHWq_H|x#;hVJW$Aq+!J(4P`% zE?7$Y)UMVm(+ZrDuT9>7|Di;z$-6<+q>7vO#B_@~IwkYHphe!Ui(oyFnz>D)EU$Yi z9#P;;6C|@{j5ZX^C?1)PidfTpIJ3`G>Z;C{_^InQ`BifGLe7{VDxU#?N4g~HW$byJ zQ-{F1dM;%=&kl`kjrO*AI1)k+&ngEc&l8#8)T`}#wKl17R?zxy-2eq8Xe5rgH(YFfAE{mPZf&ExD!S!;b4I9F1Btsk zU48Kd5#y{4_qUhEfWfq->i?Fo|!bfzcsBysNKj2#Ls_yiE??M3j)ns^`fnLmv7AV;`va`G( zyBlcoZRH0pJ}1%N)dSSBD>n+zG8SvwIdFx&V#X*2{ z;ApvR-Dd4RX}hq= z{js9{huNozZge-W#&P_4(&k4Om4;us<$`Yb)%d5E!YaaP=F^jl2ZskJ-7IwU0u67Q zU9Y1?YXe04=9_D^k?w-kQbZ=XbfZy%Lek%F=W2?801JSiLNDaqQa{a61Z1R!2XOET z*V|>-%jEx*6c3NSRQ7m%cljo^DcC4;KJ^%=gZ9c~zaga6wCBIU+tR(UvGD`bfILns zJ(1Fc+xr^n>`ZF6B_IIw>QtZYLRUZ!v4=80pL7&br+J%;R(rL#YVuEMyeo8Wdz)dB zEY$Po{@p1EAbD6IAq5+^%k`|eI)4a76ZV)?xJ7qj2Q4wo0*iu`C<);e3Q@?_*Ju#V zErcU7>OZ@&xbB3b2YQc1vQ(bE^^g8sE#YiI3YDGgz<+M;C#rn(+}Qb8t6|P{*?%}4 zqkR;9q5SV_BLkS_VOLz2DeCQfaeCXq#xVauU*S}hjYIYsJ$>gAb|pN9MLiVXKuuIV zc7xvT0&X&^Be2uiMzF}J3ek}m1$2@oA(jZtd#R>VOF zm;nHqk*qX{f9%7Z(dld@cxbh8Td4_=&!sVwDoKai`J!*joBu;7<72cW{UUv}uw-@V zmwX$OeY)uS8=mJOJa`*FEN|wSAFBC>h?Epf82WABR5yrxSy6D$8A4B)ME?zk%N|FI zgGG3f>43q<&i+!95kyW-u6*A*s&)GcM7^1kva&AG8=2HIF1w#qj)3o*Sxmj{sm$10 zq_H&8s`+%?SEq~R=o~yejMw!fz$pTd2Z$Mse!^R4X923ZZfMfj^Q!mnt)|!?I&TOV z?6T2BF6Ec@MVh7HJwFGwgRkShV>u*T7~##8>7e4bvwulRbR*Ti7;Oseq!d@q;CVQC zd)#zoHZ>-r;laW8U=q4-E;9Mrw(%I4cPd_L&12ttpTXmaW@~#*ajXoS3G0e(RY6yB zIgr`Z#1WA-?_w<`;tvt0TA?%eq+3Wx!U7w3ynMX&0mXBxgr3{f_MednffqPO(EG?* zet>(a$Sa*hC~ZL<(Ejpyr6RLITb5)@il52hBdFAReTDehc)!j%(dZOkn=`ILzJL3+ zA){>M@-2YK&cg=^FR9gT9wF1?i7thuES&YzD#%+Pi|ewr8X0BT{dGKv@n_emnN62} zz|zu^(*4Db1m53~nvMwxkgis*#kFx;E5-qoL22PV>na{=7A9|gt)n2m;O83~@ zOkt|V6|+UDo9bu|k5)KnpS4(?8Q!9BOg+8C*ZV{GN{k(ekUeA33_pZcn?141)H|U7 zXrx$h1MU1)|9n4~f!7PFFT3Ad)=4TOzkE9s#zVKjomWe14+j~U=)6f?W2)U5Gn7~3 zr+HYJOfrxEg{7+p>zlArxsqW2R~rvt$C zEx3pmI}ARIC4|9G?m$QE#v-S&PlixDz8@?^$Ip7epF)>~wg5c$s;jpOnS3(KF`Vp# z1Nj`Y_Mli;DzE%L5+>r=6sg`_LmP&lnWyW??T#Z?<9^~Ad5!P!epHrEfDZx!OHHc4 zHxhfm^AXj>r{O76x#2pSN|Y+3D8=Oh5X-1~`N>l%7 zF-cCl0Px%a3RYZdDgQ|8!3YS5W2^yYL@-?3`N8p3=WOU-KMfjk+~y@*@;~+V!RQrB z@`#Rgy>7BwoE?v0@xD|yuCHylJU^499ggV&LlBjV()3X8@tOd)UvB9W9Tf1bx1g4_ zT?>ckVGr1XOLh1Q3l9_PAlDX3v%r(OoBT$cjPiJ#CuiZ7Q^KWI9MN>Wjr2>PidU?K zeEV$zMkbq?ztMQUVqR~pL)t<$$+wd?XJ)FTY{-Emhl^i z#N4WDr*6NfZ+#bAWM2guX1=Y#QcD_+rho+p?1;s(X%RicrV^GiK(^F7`B`l!_($A? z`or@z3O(1n87tNC)j?etX0$}CEc;4 zn*^cmO|C0sdBDRS5Ui3?+Qhy2T`k%E`V67aF5`0CoH5cn2Ev3#QdO7<#fVUl-1Ny| zTVhr#T0i1)SgsoPy-FT)llUS|aKhD_HLRY2J#hl#K)Ee>#jsgc!?V_bJ{G}0{Uk7n}YZ+P9JCRo{`^$WMH9CFrY-oL~Re#(m1vCY)BJM?QP6>AZSJ6A#v z+91hPaBU*`H zBB8a;=jrp}aNA1kSDWN;{{j}mj2?hH@y#bX+dp7Q!C4!=L507N9!+5>5$leVr}`cb z!mn<3C-M215O92S9Y>Y?G*tbmbbucvsApg|)_mFdycunl3i9n%ajPod<9Ur7p2+B& zKh9$z0^#`%8`UOFrSnNm!*f?1C$Vm<@QVsOlF=F8=Yjj%)975DnR>@ulc4s@jx(|6 zn~I}l*{Sz?I>IrgT7fYh;0U&Yk=j8WslQjgL$rX=jJI*EnC4DVa!l3gSZUUx({v=0 zbt9Dm84_Z#dY&K@EY8fUdil5I?P1?~7Sfyi@9N1Xi#BXW8@b&)Q|9VeG|rJYa)cMY zO=@+5Z262$bsa`OKcL@7)qsrC!+FKBW~wFpqTHDXxDl12k;+`wbaOT8#QA}Xb$5d~ z3s)m!js+(e=Ud%SCRO?de($JY%o?p|3mFg_P2#@s-jsG@n2sDv^H-wLFJ>48W69;< zgg62tfV>@wdX%ZtvLG-UgP3+o#>u3k$^w*^<_k@}H)PCTD>a%GVns8>ufr$#%0p;7 znK8X~(+NGoMz{=IivBo}Y2}Bjlfge&zTRc)Mku+fkIY@(^NIJD@nW0aN6LPr0S9n$ zj~3yR6}3R4(uBxT4iM(sMt}_BRW-Z^|I*D8Yn)JPauAvE)42bs5|Rn>pH)0TBSF^m zU0sGISL(VK9={`AeM!E|aniaQe!?y1l5C+jPM4|qu0s@E{7xkR^C~fW*kjVi=Rd#} z`JkyER2NO2V#=N@tCk98T}_+c6~uhp9{0sBJ|j8<9H`upJuO=)`?eX&e3+bc=-~HR zrXhwD&n5BF>u7Vc^)k3{xEYHbu>ABQQvb{IVJ?7@x0xSm|yc9SFSum4CJfF^Zg`TMy?eG z5c|oWCi8A1k~$(_@ik zwX=`(xBK>Q&)0X&58!33*%jtx{@pbH@-m~rfQ6>;!&hn0|7Ud%_(_cBA7@wdBK}VC z?>_dI1HDx(AOQJdV4J#6^w$p5f4yoL;MhMskn*_jZwKkGwFW@uJ(c4X(%xITCfJD$ za~1wb+BZuLH1#Z^yfkfg)HcFV*GK zF&ay~`ZLN}qa~(umm=LuxnpTDl|229c1yJ1^ZvGoW}*!foP0Gd)gWd=_@~o=cdY0Ya{TY z-}jPOKPlu(Q7@vQs62f7qwkjh_Bd0AL|i{qLjT6;kX|4bZ6Q$+b0K}R-8{Cn>Wq{L z=fyI1xG&}{FrQ(1nk1c2_RVr7D#s4o6(Ir?_)%z6f}T8fXs!aTTm<4LEB2Q??L{@b z-Wp8oWJCvS9}4AvzRx8{pxDYIpv5eFLZUWTRGU4DUq&}SQ#FBz_9noNMOJK7W7Ay{ zz$Fri)g?@Sl1luFT8qo0_?E1$I}yOcYhcOMINEaWb2|0@oz8wd*pL}L8)E%S&vyl6 zEckD7oEH)tUmtNHvTsYJzWyxd1{z-l*{6#0YX8{~+Jbm@XO)bD{;y4*Aw*y-^&n^o z!c^j{xaBs5VmZX7O~@E@&9Aqft67y#*c|S^+t!Su+2ZV+iS{nOg^a3#mn;UVG$)#- zvTkD%?krSREYyxdBH$yF7$6BWU_nsHrpKTzJHj9iG zj`u_pWGc>g{ck=q1Sjm@FFo}j?(%DWkWDU4N0*<^h3pc3e9I`E1I4QIQK>AZ)MG{% zmbz#ZkrPd39+dXvZHeYN;}bx1T~vp$jv(cM7CK#s8+w_Y;^>NJJyCf>UuY^uynFRD z$mjiQc)^ngJej`r6l`Bcn4pl;m%wDXhkz zg%adn>tsA+XUDDxHDW;nB^gk)h~#iwq~ zD7*gn$BmG^TQ823X>vbxlQUMK7fH_lW9uEmD~r;t?Mf=PQx)5+*tTs~Y-h))*!E5; zwyR>>wr$(r?tZ(^=|0!@Yya8T@*Hcf=Nb2yRn-P??@vZERf%TnaR+<#K@W2(Q=T2N zPIwt$TZU>Z^#gK*JjgBZ(CA_R64oWWfSHZ{grAx0)w(pY=wO@HU;}%e8T)&=L|2ML zD1@=RAN*8JX8d>@6#;tRAoUZDMa_2GcpXCn>GN0kqMP!NxtKNDJ02xk(WBp>8@nB6 z1spiv3D1P3@`0R3VU=pA@sn*aY1(peh7Gr5 z5*N!01*UI+&Xa`@rS8=0q&@R(6Gq{jl4fm1o&1TQK+suY;kV9ChCY6Y8!+Fg=PA- zA+rAOEC4(-&T^Sz{esil3VHOvj}l%OC;We1!T+an6);cwL|UVq77|peu+z#(Jj}m2>3B%<5F@9oab-eOsR!GfLGdwovghBpDghXhuv4>s5H>4aNUf z<^FlpODF-KNbwCO{Fc?Ea>*%V_D-F#0amW`3~MI6DTEzma6BS@0F=8Fg}4o1zDYn& zXpGQi9S#h<)PlivE@hAi|NoI^h=3sni%~P7+h+OhI8~GFHc5|bdg=V7tLaowO?fK& z#{%yrvgdsns~~YDJR@(5#b0eX%&dKi>0i0Hl0&Wk^+xqSM_Fpb#Hp?TglbYbDz&o# zVszZWvUF`0#qBCeJ^7B-fnOuIGd(Y{p?qd(j=F`UG)N)%VBwbkXPWWPc})#0_;e%n zpDh7F!J1O;*ubRIH^{F1Uvg!W_sI=jaGvxeCrRTWat58?SM4x;iSjwJGjufH2-*c6 zlKf^TCR}FgJzcMp>RE}qoqQ)M7N`&ZB?P=c&}c(g!4`9#X@ z#7D6^EIoZ|Sy`t^5C*%yo_^{yo(pPT0s8va^L*YAe?q_VPE4KgDOKB{dw6-3JH3TU z@kfFv8cHD&+GPTm-x+*LN?ZN5z87{3o2RbxhsYF z{&Mb^EU2=SuP3V0D2}*G?!v{J^tnp(h+;oVVmJ@;cjw4Qb~Ip1l-YNY${iw1*IY#< zcv0^UT-ln;Yuf3$ZX|Q^&Nrdx!|Ygfe)CRcHYzyp%c^t_p4s=NgwZRZAqgen^2vvG zSfjh#R9J)5d>p!4v_fvtEY4X#K)`0b1uyv+zS@Jhr=*RL0>& zR?Wp?lNt6K&v*=#=gpje6ttA^9`|gSSWkeMN+0&_jpi+Ut2}!s8BvcO9{P$)39~4x~=>6Nr?YTWY z2Y33gv{hyp_t9AW&(i?AY#O7_0C>$7R*XsBTf9nsugTjFCSr82M+TRSps_*c*ng`K zGCla&y?&$0;N-1q69%|A;+GYUKO)2u8Pk-OH$W5agQ{s}=RZ&yl=$sDMHHJ1@;E>% zkr~|^Oh8j1|HqX6+wNNJX@?*TVA2vxSZlqA?AXC43$@nsTq)V=E3`$@2m~KRfK#5q zFZK2g6qoWaK5%G{GNu?&zg9N}@k2LLHHiD=dfzLC?O#J3_o(&zXg6xNqQNa2Fo*~u zQ_&8p<;q#CUW+x>lXk53D^VRNvyVW7ma`z|Lq<(eI!1cIsVZCC$icdndfQNj0)?o4 zAij>Fz5NJjMtVz6Pfu;AItvfrhOA7jGH@Uow|jj4t{?UL_i#`sOkPqWc9|M!r7qT3 zNiSzIbBVkwCawD1Qu4(sv`EO#imL#fJ`*xD5>eMhgquYKO2O2M{_Q6{^6p_h1P zVEDng8lD=BM$sR_G12USZi%z?NdbQ#pHpLD4Ng)=Sr||AamCWwv&@#)J`O)2`?pgL z+wSMvRHmbIj@r*Dr8iJwhPdxNEF5OR^TX>;cgeKZ$2?O`#~aoSkA3pF5_K*K?G98I zbT4E)VFw4Q6VMcJg-z;|iUMYj2%O)So1Jhf8S1V5kj=$TNI*9HKQV~;xdTQ1eqaXep+f7> zDfo!Ck7^JtXSJ{MNxRAZtdKFyASg1i3~SGw1R5tfoLw(cL_cvaw0d#nPuj%The66s z%|K4?g66J$;)BIDezdPbGAe;*^9X3^uvADMbd<<-Y?v+4^5b)Vs6zOqq8#x;P;!;< z&XngG=G#K!H*Qr|(d>Bsv^QlXR$7H_G%iDC@{y?^8E%^MmI$w|ednoslHlCKFAK|=R+*kszc$==N;;!eTKKH&|Izd9~O5mKC&whv?pxTq4Mu5M*VM&<8gvnSuaBe@Mf~sPg;|ByMT`{I*nf@ptzO?0V2|2?SH_mb@ETN&H$FlEvtW>L!2n`Gno;|*CyPo)8n{+CuGvpISaVw|TuNGnY zrZWLMR4?8I3+YU}`CVTS-o9F7MADJ5%HGowF->u1oxKOeh7{pQUbE2y1&}dmk`&Y* zj9$AQNr1li2V+_Bg;nPpUMRE+6_lk|@8D_6tM(lU6haEQ-@p0R>a||P-eNPB+XC4l zM!RxVG5>DooVPw0p2n;nO;@sj`cA5Bek?B%4VKFYl{yKB!%%J)Jz`M=Jg7)_el5BA zr0n*q;SYDR9W#eoxN{7k@2M12L;B|q6`U51#pxRHlS}>&ZW|?q{7YwaDOo*G9Cd^5 zXwQ8MN&KWcxODPV7x#6g=v%4{Np^E}QJ70J8d>7;7Z)o#Eslx3x25m+qwKe5Ojq8N zh<|8wtfpP)Wm_5(rFSdr!R2Q(AfU6@t1T0D<|-^sSp1?Pls-SmVJAHk`fF@L_j#hr zPLE{Yx13~09;aoyM?h%TigTpQ-y@F}wkvf;IVty;ocY>_7($}9rfg*(H#gh-R8{z~ zwz7A>78FmUS1*Zw`=A`oX9gx1hk0}dDOIH8!MnkuCyEDRm^|5h%pTnh9dN(x@b*!{ z@W%6I0N3F5ZC;M3V}1fL-wUgG(F{jNCRWsV?K}o-Vp!qF5_T)@arP~}KblHJ8NkTxz zQSa06P$5uUJdJxAY6k)N`;#X~H6T^r1IYWTV7OZ(_vF=0a_4g|hqux=`ZBD=sa$EL z)^Lnvb zzz#C;bVB^$vBwXTaT-{=8Ljv=qttI~fC3KZ@>ZCUL}Gx^-~lJHeYBGyy|I5(-RS-$ zTI%uopwci3?-qSLX}v!QHXWCWkk1!Q;Qa>IOg=l#YHNTmll5Ey=SrU(uS0r%I_X_f zjp!?nZ_Yb*Ob%x-tciqauFDOH`*8HjK^O6p#}!YB8tLPtU5$DAur-F|;3!JzUo0D~ zUldHsiNK#2C{}jNwJmUX`<{8mtS=M(B=w0{$I_D@PUeZlUhwh92H4ECbJMiJsv(nH zPTGSFu8k)kVqHZXI+Y}LA(%jv5Y`K6UV$nbaU2vqCF9E}UaRC$Uku%k_@#F2H8-B#F zimX1Ef2}16Ja5_v^E%{pC6RvNa_jr8aJyOF3*-+jQ9ADMGWn4zuIWjvLJ5|83RGm@r8;1<~wT0D-nms0?ESI0u01>dMuCm1T~4B z{0yR#@8KPnLwXk3QD`ilu*DPs?`l5H!$oTtH(T4*i8_qAfl6#5pjj2FV2?u z_Rxki405z+Aadl0JCltzgX^{1%K})X{gLD@#=n0~7ij}G3RNhR z=vB1OUoZRwP?ilMQw@A!{U)-4UR+NNM+(BzxV(^3-s-`zhwBdy4njB#{2;44U*I|j zU(r*lHN&q3KKuuwamu(o1va6DL~A%IbP9)DROdio=dR=mvu{teX2VZvP3@k7r);oy zx)G}ntqx%+6HmTd){dt$bT#`~is-JsY*P|CFGt5BdpTUKVdae@*&u9RwK7dGLc~`L z9=9`9h<1}y%I)p#06_hxVr8XvJ68VSn}4;0cRK^pkrpg(omisahW2o8!b9rvVV(H}dj+yw(ma+cXF6?jfran>OKL{|Y&g&Gl&sR+a*s!(>Jwtiu^PD8#$0 zZ7j3#GLQPGRWze)=e$wBkope%r0u)vC(s8vIjI6vaA@-3!C@=c5V{1GLT|P{oDI%k zmZQ3`X!a}czW+3jc(p)&NHQq!>VG)sLT0`T3J_%~o-X~u#cL~B+?CrdhNa?-u4!| z$m*4mvf@2$i6PpC!4w}Rn=!tY>#?=H6%-W^`f_l#rXv~nIXOX1Z{>!C@JYBYB2@<$Jv`NI(1C~Y` zI@3$fqIGa6K9c>%p#wZc-&Wb#&pE1wQ!eS)JWs!8$dyRee`yWaU$rnZt`EW4N1KX& z(Q1sKR!rH2LKv2BId=xi68O|O5{%OgDalb5f9MpZef>Dm0H^nB64t-gk%Gu>-^-zE z4q!?s;pAR6(Y05&t1|euS=k&6d$-e&4h)*y-L10wwlIt@f(^Z0{l4Ag#Y6B6ziP6f z(?4AoNP02j-)}txpd;7|R>wTf8@Jiqw`gPSB}D!0IHWN|@O8CmYnI`T$ZQc#k>^av z+~mx{YJlAiT_yR312q7y-dE&&At9@T~Y(bixSnB zk1B4N8!So%4G7V-{%9V@t1}hkex0OZ!cd(WXLoTJzP65}$l3n>co%>hmBDhR_@vpU zlh)bQ_?AvY*!#^X2CL;7%6KM6f6G1YRd3UE($__>OJtl9W$u{wYx5i4Y(bD6R`of# ze6a+}$ESL0?szt0@5l1p?PDnMqh_lc-$xc3F5=02|4?-&ne90}Nl$M#_id8f-I3#; z(`EhP@X=T@)8$rY+;@u%sISzM_5J3ryhXJ&@pTFNv>zY7Qfi?Rzsjv+jN^?#%W;H# z8ARfoNhR6+V>oRb{0Dm60^#|}=U2$DopbliuU^#v#oG~F`)z@l-A4OOEOJYVy~FTC zRRa=qzwMdR)4^YlGp3`|uL?SC>!0*^{GJ$-?Nh03y6*7{@5f73_&(;VSb7Ov^aBY5Gzl(;*m~L$%rhaHQ8-8r%Jj(d*6Mmio)3K{@^Qr1I}m`v6?Ixlt?Dz zPlo#VgBtB7-8=iz9dn=29aM5b&BkJUSIbk~TLI_hgq;d~JjfYrmj2r!vi1CDnEWD- zXW72hr2$Yf>J+lTs*DI?iav-r#w#eO2S0rLmA}%b`ssX`DEBJQ)L!jD;pnG~Pq)wtHQI1s~s~JJ()z z8Mz!Usk8OgvU)S_9IrEVY&a}%#c8$Xr22Sw6P(YGw_7+|Gi)0mnOF9We!IVEy{5bN zasyHl;K}C&Ks3HCPd!?O)bWCBAmOKE<;EsX6G8PH)E9vC+%D;OScDqyQRHXhBSIkeFpJ#A=d{8flx5vuQ~6m_1#XS^>LK5!zmv( zM}uz^S3!m*UX|Z{+{Gw~vh*pd`nS$E-=~sXc4YYP#GOf2K#P$MQg?o9o9}^SY%IbR z*3*_7)VDqe><=YjvetnKad+$ue3a>_%;od^G<%UdxFku*5Y6<3OSpeYr}m{f&cI&n zpbA1>X*K3WM3H}`3{0!|Ng;5>8Um(7;5sR6=@GC3n!G=%E>idl(`5Np?}N9HAGFiD7a-$2aZ-L&^Jgi}JsoNfN5lc7xRF zm*F~?0T~AMFOk9OJiW{^82IAP-|Tgco3Y}H46N8v0~_6Xs~oi8!vYQ0bE#5^#b`>r z!Pr1A^>~?wp_mslhz0wl+zG&gR>~?_W7ujDp~b}Og#KGH+3^KT)Kspy~YlD~ExPJlExIS91-zxHk_RZlld_!@iR+ zlHaC}c6wL6pB*auP)ImYm8+K-{o z@KNwGx*^D~ob5#w1c%*2JVA0SB)Va2A0YMXi~W2R&+Y{}xN~S!X#N30<@4X^Ha2Ld z7Jg;qdzD42T^ECJ`V7cF+GzgjmU!5vog5)mhw)(1d4D01QkMIj+MIlIi_w?DN5S>D z1cvId*rOQ z&KB3N<84o*kdcF5$18`=9+d$VU^-)A=L7VDakV|9s(?jKFz{a3!#EPzxD}CzG5Opk^~iuKIOFn5DTt}mW>wM~$r0#GI4qd&Tyv=) z;*!@IK^3{tL-KDhlg(SMA2%`4pveI`8d4>iOoUF@ejDh{tT5+j#1(Z&wdHW^&#U4l zv3#3=NF<1K8Bg~WAM=xb!t-@}5yN4ev8O?Il^*oa==HqGBP7OeHu7Ez9WXlEKQvTl zAN1yy#+sMhwS5`$6Df_+*~XD_cafz;?krj_CuK^@o}!RK=E<+L?9n3WO?S|=OZ;AL zAT|iE#|gR5H>P>6zM#{axzQEL>UFgVz#nb=5_x?8O0%-a5B6>em8*K~b*O1G8{fsk zEh{e__p^6Fy%Hk&ZD;~wz;O7tqy-XAhu?t6Iqu@>*FUjhhR4&A-qZ8=rjcn!cD+A} zphNgh-*(I|u>eyBmM6cMNA1eM3d_1i2=j1Y!XqT+)*-y-y5-s{Fviy|U|IlYE#|ur zkazEt5bF@3M|m4=Jrvt;7NC?k z>eo}dYds|(Y?82CO6CB2U4thg1T5dq^wyqssj*fWB$n5QJE9aBohiMYMKVhHI(ID< zZahn56mNMw90dUUCI{15%!eZzh2DqwrdwR=>fW|XDO*k80oj_H%)<$w1Q-c~jVq^wZQgvj0Y@*ydMFg34KEC! z%-sHu?HiZ-g>cbDtfRi!<}=_sho|U_Wymj+`4&V?Av9D9FWHGSJlaV~<>4q>4^J#9 znj@{gY%>S&xx=_A#X0s_SeA6w0!ArHGCPOXGbujFy)lo0=Qz}oj5_a;Ng)pV(vEs@ z&tZY`MEn@}xBEluy{*YRo8=L<5uS;F1~nQrS@pH~`Mb}+PeMnZ;3o<8X;pQi^{1bn zopM2~0tau|-sVvJ^JHsToWUXKj+wK*--{n=vJP{St>@{)`fv7U5T4k|N#-e#sWcgS z7mdFSjy^p@;}EK}uigM|3NLZp#J!o$Mt`0^L9Y>&CLRM*^1q|^j-=9>*zo_jWaWzF z6@6Kv9ZSGoU@2}m-FwfL#W_0_B10!e_V66oOKqP%)A;?V?cyhA?$AZi*d1FsjpTKG z8t;;C#D?hgN&5YO0bgz8i)*=+)S;Gj>^>gUV1p_}3^;7M%JrF`lG9WQO-kG4e!D*! zkgS|2!8iMjTh@1(%gWB1KT(tbfeFBY3{9hSVC?R1?bunTAcrd6?)TIxO>F~H$ZOAY?1{$wj-FjVGbyY|IP*7J;CbK z7l4QSw=Sro2)=4Jo6vxs(n&5TYb7Vs_cDroLTftPfHHi@f~Lz={5fTa&v0q0@R zx}Vul{!5FuU$7n3m9jH-l-~r)x;b?;jgdqQ8()&RNjNdDc6=U9t~!jTcFFQi+H<{q zhizobY)S8ED{J86Yd+0!hTUH}*kUI!r7p@Z=W8Rs^~asUitPhv7wwH7KU6H{D&Xvr z9D-!@dG2!UV`0#G_3n_xeI+#R&UuKU)$4pM?!F}?qi_*k@0*iFTSz3N`Y~JGJxXEL zAq2y|yah)(KAdsEsh|HYdP{@@J!v#;$10Sx_|G5j(9GA1!zXPq_$zrvK~TdHi^S(V zi5?5avG~sRTi4GT1}5Jvw~OD|sr3+FveG+g^%|8Nw}peF$5k(Fwg2=TU3RD)BdHXf znf&Mxn1a%RmkeSmC|l}5BKRVk{YGo2oUITC^n%wjpBm4+qEdz+kHDs^eqh7f=nVD6 zlSL**7pEZ6jX7`AD(kt$W}vVpuI%sx%rbt}>V3Jt9*o+Y`zsWPn2@^hZuBhd7B#Ff zk`$38@35VKNp^1$Z7zj8fpZ!_h%hP7QaZ8E&ZtuJ+lVgM@Un?~*HCtWq*I`VP!oRA zlx2oc1kF^JP=>fJaejYOPAB}Xs{a(DV534xL`H9Xj-O(=Q-L6-)cJ>U8uVBEl|MxY ze4jmsBbel$+qZ$1hL>WNqRTy!JO)HLmj0z~nolZovns7^lg zU`#!gT#sFQ?O%;EOt4&~V)&VFh6kgS@FM|_7ZOhZdX6$r*1%6{K=f+qyYyF|3`v&m zu7Ffov)LaHbu7n^q{0eDgfzw6sfTSRZMmq$@;1ODDf8;*`g1nkb@|>lwJyfUu!2jn zXILNCbg#NutH~8+O6lXH1JPqN_#^4^_c`=O-@SA?XtdIO>wSM^B*i~JtM1TyTvch| z+oo0W>xz_lubMEWC%x0;oxanL2M9w-3u*mMDqLY{jD_apW$7Ma&N*!vF|3bfec**x ziuqV=zeU)up``czUhTDL`3F)zNfPnun1pHwTO*uPqgrOe=(ICk{j|=3C}rrZI#VRs zZH*Xt92{db)P&6Wnoy@(_;{{$BOBWveh;w8)P`Y|Iu^SOT7C0c_w=iKK9qc`houWw z^1SIw4AWc71D3WbRc_n0c=}k!VY_2?k+Sf`eiWk`0ghuMhDr07I_-WXah$R8mL!2G1e|4C^-D1|uFQGS{Iyl9xP?J68pCE(QCCu66Do>yH|=43=*L$zI~`o? zzBn9Lr46;~uDBMG zV5NFF!2zRYL7kz)7I`KJBJ&iORY6^tw_Y&n>Jmz0b0^sNydo8wNZ(?&o>?kcIN?Fp zuSNsmdGciDb!N|Sdm3Pt)WckD3sg9N(c z4w0Ij_0W`2M4X?bC7JcSb%D#S>*<_wJyL}V;p6NiMhQ0--7a%u!^;SP4)8md!x0LF zTzUi|E=MBeyO!GLeEDMB#yhosUK7rh?31Z-T{`UTLw((N5Hh>3`FA9*Xj1#l328O; zNz2t1$U^fr(Ulv(Qk^Y!$D3Q!d|i})4uR8Pu(EewL8+fMy)_<{tn;bFCr_!%i8p?s zWbwyhULnSsrx@aec+B_M-#LEP7z&R*hZjCuZX1+8r!}2z{$TMc<59`oZnnlr zQI_tj=f^0Kihy2_yX7gy{#osYFf5uezpy2yzjs&R0-HhpuppHw*Wy$bJL~}Q{&>(3 z*{N)ns?rC!{5oFLw$(5`lf;gAtd^<{Tli&`{%)sR$SulsFDv`t0UxorR8FL*muu1; zfBJe_p~3S+dG=o0da(7IVgMBUp+$w?NnTak>{;_c_dr8GH&iT}WoKo!edN3Bm{wJ< z=My=u3j%JAf46}0)d~NNB>E=)C-JaO$KPImbGD@7ZvW>mqgJnlqayBk$BBKbrP*BK z9N8|iT5NeFuReLTug0vtYbX^?iJ`yN`qwz*zCP&R!xQtn$hQmLEm zIMjy6Zaso1T#{fW?NCH`%obwi^G)QidOJAgp88znOU#@5ce_2uFxerIl@^a*ahgmC zd88xQlf{9|dh2{Y*2Ls5hs?~!xU{)$-`$~iuHsE-T4B7jbBOrpt<%TPwPrqIKA*9Z zw+r1WwGToQ{=PnXeWeWRdtZZ6w*twKu z_`!WmDRvN5$Mrtp=`aB{u{frGhJn6Y!Sz~ptedl*7*YIU-j#ctx9$Dn z70!=8l&$d-_ntn!D?#$|0|9T!0Pn?wTu|*+iUZpXP2yQXKC+z zxq4B@6-H|*;af^mx{IedTwIpKuZ(@viqg-dKs~PeEsgwvvLC!xub_w&E1vykspK@< zw?CkCtOk=s{;N?YB>zg4@{pIbI)Z#ZaV^>!a{P%Cs7(o=+=~+i7ZzG{`|Z2IW#%5U zdF2C%w9)Hv*yYjpQJJ8f^U%PH7ir`uB_WdXW76$xJ)8ZCqUpjkTBpSPgziUZA;o!SoJ0yZ`RDTQZL;{T zgFkXFj!4RDQZL83iip)N!Eive_P znSH$^^v`^D2AG7DMtg=jN2M}Wi8}C;5QqH7;{wU21u34S%@JcsPvE0qZYa==| z{iK8zYV}0=HlknSD{#2t@DygFk&syCx3s=xU~h}p2jKwd?>rR&p_ovXrlpksi)4V9#a)d9zCc*impk~PxXjlS~&oPIAT#}^EV=4~{VgF6o7 zTUc@$Vlxef2)-}59PDR2(WX2Kxpc5coYbA@^g3=AD~Q?&%kfy!4nm#XIVxR+u8fXu zjQ&Bm=@wKz*6cL9E`b%W2h`_L-%f=Y^;%@XJPI+X$A`GMltNT^TUz1{HXR=KL`lP4 zZ~R>CV$H!2Vsin`-NeMwnV*k=!P0hpU@dg?Kj_*!ZuqwR4ctivsp;Ead%tr#fqz~2 zQGeFAJWOqxTKE18@qYCL`IN8HN~x-6w7uX#4z*4447dJ8(X>@T#!XuFhB#n!m}j%TQalI( zec1GQz(7<~WA-DJ8m5j?%0Suo-ANq7E_U{%Q~2DG-+|pTipd=luu1K@gUi=Y;II0y zGpQx_id-w&r)sb+&HduST@E>qXm&>)$8%~? zM%)Z@>~!>u9oa5r`b@k_P8wV4dBb{YN4hQ0@~>^bLCtG4)cT^_#`Z5p3B|Is4fe!l z%~L`~8r3oC*!}=93_g+Po3+^&=#atQ`DrA&#NjAM+%vJsA^#uao{ZT-XHy(;pMg0v zMfYb=wp->?(n)B6Kne#f-V!XDJl?}IE`{{UexhRn#=|vXF@Pmbw%-t^q~pE7t>aan zW#9DS+a0}90tr7H#)QK39zZeQiCVV<>(}leLr-BC%bH~e0~Kf1hY5Gr^fLOq)toq} zu68Ul@=d`E6@`mlgZOIk4H661v#KTRb!u5eBGv?apPW$$9sv;7aKs9^{sR?{O6PMtNSO zni?5TXh|f@^<=4h%XmzqLTTRo&=zLHUfEJ#JZkzQhH_(f>N&O_XiW}c>gw*9q1x`UhdUx}8(DV{6lCGhGI!Nkg# z#6;(!oG9eGM{CH#V9v4-oXk8cmQl|>4rHq z4v>Z&>^*Em3qQ66ZwcQ9R-)nM&Nl1z1zQA0zpQ-SvZST4#gK@5;HnKdqrgJ z-jpPkJN>pKJ@J!-AITiM814{2 z>tE-<=bGfOmytZk+IA5UGH*5*Jx~5ZhK#Q(eg6sl48ehOOKIOh?J-Z<=Vh&|+)uG9 z!T~dr=EW2+y37lA);y4^OFUf&8-hDkm_n#M9;$fudHsG9Htdim>cx9ZC3at1cryXo zEyd)e=li?!{!lNvj`hVa)&C@C)h6W%;5~x&VffwDh;HSP>aSp~(3Dc)&#%X$5_2aE z2eI>jAvN)x3`7p#C;Q3j?24BA%4@3sr;VrU;M33>HBq##qqQ1D|4rI%%@@bXyyWi!&?1Z(E85Sh^>Y zNJ@La`U%_IXj9)}bTq~#8cGH@$}`pfnxdCQ{P%EQR2UNy9pJXSM5K7md~?De^zTdX z3vXTX??Fphi&J>%{XJf$v*z*wUrAz~iis!4QTA|JzJ=G1bG{P}jU(;5e0UCjyy>b(<&47)wN7c-rd+{acQF^0{`_3JMHh znf)+u|GSg@TkUm70Qir`?xGp=f#ub!Vq4ayqiTWF9I z+DybpSc36-|8fZb>xW(3zb@&hx(yjcYw_b5_bPurJ8$>*_(x>e3D?midO-E^7l$IX zj5EoP1RZ*cjH+{*1~z!=Y!W?6(;6l9E&@-|n=IsSNUBv*!xNV2TC@}p>u^kUAaLwI zVpF;XV3?#XUm0BV>7DnzEwG1!@MHq=d8DA6!qtCXEJJ&a$_pODHRMs4X0{TJmad$j zQOgk6#SeURicDdBN|l!o{Qe%)C(CB{{F}oP={=gbC>?xZv)D*@YW9l+(*Kfi|FqP9 z3wRT4Gx+wU-Gkac{H)fb%XY+R2OG-v=I2(I8yt-fY>7T$l2eQ_EB~Hob~s>2ZsgGy zb!A|GYN3)SPhQ`JeS+b5AFAD1;i#Qj44P3qDL^O`#U&W{hWa08B#6>v4^so(Z0mls zQ5YcRC%9T49Vj6pYx(W*Q=&Z9S3V#KG1vXrHX+C(B{$d`PC$1i^_%v3e0cLFrnZL# ze#8B!99WIGr+aZ*gOe2`$wBpWpj>u9&&2l-jr@Kaf=toHe*i!rI^+jJ3yd3s+TF42T=u%;hq6EK^ck>Yav%~>Oc5(1 zPN!?Xe#*^0@UIE_@fhjleLA?1QXb6R!8sN58!g#lsidd#DE~DG5&5mo#trPDOO;oW z{6Dw?3^-SypwLZ}fY{2dHX<0w@lq@1iYXyUPc4Pd3)oG`Ec`}Q1C6E1(S4>0RMJS1 zSd8Q>62AbWinjEjh4t>de15*aB#tN(Gx8Q>DiYW$70+mCJ52zy_^>CQO4vi3-10(!l2^;*R@tY(}Ci&4Bingy%% zaZ9v89*%i`5-~6dlHLjpP@1A?gpfY8irqbeMTh0Fi?(1GG{jwt$ayI?>g|ceCVe=@#vx)lX4w4TF< z7jbv+pRmMDFDb{AInL(Wor6L(;^vAui4MdE44n(x6Hg)vSY5*1JQ;`qPr;_BFn^Y@ z-&#mIjD&i)yRTJh;=AMg5t1DrkTi_-6^DWd9|{c#j_eDrrP6O0>V0LBp8Rhek%hr{ zgvj`t8$}f`k{R=rC)a=%@f^wrsW3Q(1XLTHu<=}m7N0uBxY<^9y0e&<$DpwH40zj9 zqs)_Mq?ppiGmSQ~=jtRGQkfv*4L0Egh~0(%*_VgRZH(L1<+RGME{-}W71U)CJ|sgOlsJ@67#Jgb6Y`&>&s0bQQg}I5>Dqvai*F_((n}puZKtMc=T*%_LXZH8D$k-s)9A zc*-`mbnZXY9wcpX=rD1=An#^)9>}UWDyO8H2k+s{bM@L`bZ>v#(14IM>U?Qg1lC>= z-_*AK3T6#Pk~2E%TA8HQn!R8Ze(tFG|{k^nPhVzK{O_{T=Q}*2+3_Jxjg5@HY`SZG{ z6g;|%yqBa<0XKA_6W;_w(Oopd1%`w4434>HV*<{#FA*ukyKgR??f-f3dBL-s;tdK% z$gOF^yC_#*uY8F*JN_G1{f7YZ(t$F7{4_6q-TeeV0#SUUCjS%u@vqJEivHvO zVRj)dV*hn2AfPyqVem0Y8%Kkk$_{YH^e*@(^ zAwNOwiQZbqJ3mB#f(>^;L6ujqVT~@5|L2hcsll>y{efU;f%Gb|q5MF!EyYfX{#u#R zfcGk#eWM;_VG$Vw2`OpfdQidFI^zGG5X54FKNuwPF=*g@!$3fGUzGT8LPTz9)>vDL zA(aD6MdQN2!;6TDLV%Ke;y0(`?@lOo?EFoV4ky3IH`%PTVfuc2P^r}wD2bIl3R*pG z`f(WJxA*k)ZYveQ+#D8@yA31Z^GEGc%I|>s_oe!%sM}Qf72jeActY|AKZEdx-F5XW zrSw_gVG;ZPp7S{FpMXrlDl2D_6L#WaV)=tCliM?4 z)~R%hy_7hd-!(&mgY#gFZ0+rH_C1mDc*A_ZZm2bZy7t7Q_e(c@CY17}hb3XHne^q! zEoi7$_w8jLozGJ|oyM0N!SM9K<7h2efB)g$ozQ!oDo}dQvKyheuhwn^J7u>K+6}q4 zQ8sX+Y4%+wlu4;B`mhlmHIu7TNtT-gt>*w#knj#|b#yGz?5LP;Y5p{bc_T=sNeRw8 z&HwbW#f0IC`_+~1uUWx~6HcmOJVzVzvynXO){F}jMQTio3xWg`-II{Zl7~WX2mngv z1;|!_LlLRQHuZ_2jf>1iVa2&#J~eU@M=40Di%nE?+{pG-_pW?RCDn2z5j{mt)ScXN z4HRrw+-cFV-4X;H0%+)8*lvM*@haPkpyy1cZeyq0Z3BGZ&ezN>e7F2N9 zG{4q){+8UTd^|fZ*hIZwAO38GS9<31n-Bz!>DExCr`eO z)~SH*_sYqx;WpmHTacYpurh;f++Am_18|G=((wSb(&z(Di2N*=3=4~>vIroh8rtDP zHa_g5o}C6YnN+JZKZIC6hZ?!uyV0`v{FL5vc9z*FG=t%>dyc9Fg6k7iV)XsId0|h{ zoMId7K!x?YPY!eDVVEuNoR0SoN(`E#umd?9^Sz{3ZP2>g0usks-rl_96h#`+F#-%Q zW1EBl!>9J^I?bVk#5d?||Iac%jDqkVhIxeKPTWbUAF{~52@z4=dUt-f0O{ciC);e6 zS4`A3bdwl`=gV081Hy-1wY}K0Ah!< zSIbVMlXW~^td{7NV`0(h2fChqh<&<48Lb}pF+a7tqkj8V;_z~k8DDl5#>lUdBjb@Q z`LOpU5fn~Ld+pj=TiK#0U%UbJ`xU2ZiPWp~qxrlZcj<5Z zFE`uy)=!KruLho8^qUjxmZ8;%(t!J|?z@WZ()-Z!!4e&M1{u*-bGZyqh#n$(r=U=Z ztJ~_?8nbnZwkO?5y!MZPcm2`n7h64mpdkw26?ds+MRu_f#0-JL-n!H5JN>Qm>d9x5 z_ohJUSzHgT)iagrqvf2w@5g?}DT&~*-@#eIyiJVBU8m2KKHc`^#+JTCp)ovsyAyq3 zq5r3!dulID7MaU_k+YYw|3 zrfb)&A+9dI-91%TcQbQtZm=&&rL&k3o3RApReIdz=OF*}4!dO)GCqGGJT^nFX{4Ey zq#uhF>&?Ei;HCb8R96R&;|NTlchnzQXxTESp>k-pwVj#J`7CT?*|`HYU7qQQ==iy zn~UGccM#)933$@7`{6JJeY8E3Ou6O{i_{ZDXx*Hm$aq>?be4 zi)*qKx1)-cyZiRQewkFG;Tb=sn@m)T#lO+q=3BYlWYJtPgo8eyo>m%^I0(m7Jfwf} zvQusc0}D8M_a@BZb4QBCSr{B7^z|=OD=*pgdc^N)%JEfaA-1!#JDMpa<$vEZA(wgq zx!?`&GyZ&glJ}DZ{*aR@_q;y~PzdpbfX?V2uE^$f70(7i9j3Yu*Zn1gzUn`nEIlme7ZP zZf}mS9xZF;kMP7}Ki_NUi@tOJQGExcnKAj!us*#)(+sQU9C{jo3?fG}we;M7t%IsN zaf{o!Wp|#mGJb4(;k@bg&TX0pnNw&U+~2^rPuDeD6181?p6Nk*M=#>~qQV->kcfoH zo$0k!DyiyJs!7{wtBEN^taxGmQM1J#FeqKdQS(yqLJ!9bJd1nVJY4hUFsSmfw<95& zetdtwXy0^y08DUgK4v|(9j*zuPn~?e|L!q!bt$OIUQu3FCRbe=1}%p?p4m9ZpP)=0 zp)@(kqaqG;pltk-dUXYJLvQCnO!9tXD($FaN;Zf6!+Y$fll!EM-b$m=;~|4?LHOn* zs97dHtA}J#DK{VooX{LpBzk;(4M2u5lMZ@$%;Rl#E`7BRi7GJq`kGK&4DEWl94qa> zlf&<|p;%-rR&uGa4nWHRl91+G9JB?y+!*wp=!~DJ!I+sV0B#n_B{b9+-WQ8(S1r#k zan-m%exp1E9k${VHQI8bg&SD!tGJ?ZUBN~B4OJufYtFZqa*&PoV9aIo^Y08?>K(oG z>hhQ!Vq-SYFec~bYw)jMN|%hS355?|p=QnBOJ<h?`s%&&obz~S-g6|?>I^EB zTEqupjlQULcF+00`t*9*mt!y(v`d>%$QM_8`A)#4dEKq9+_Rp(g5I1r$j;bKufNLK z2Rw9ZJhfPn``orfievLYbr;9t40O91b>1n- zso&(EW^9>Pb>Y$}XWuQ9S+K3tiE*PhzDdqL^$yO&k7{6-+UKv$eP;8{+0XN7J6dEQkH&pb<>o`#TmjGXTDjom??`<^aO)I(sS7tqaRh+twOo@0M9H6H;2dN5EZSyDqX(!c09EZh; z6J10jja*h&Sod=-|^jK|$c3H}h#p=`T5Ig{9Cmz+aZ+V$rtxKZPtFH0jZx|G_jl)XPR2e^em>vHYT0&m#q@C~!|dRf+|c`* zKVSU*4Zd*+v))wARpL6@7$qN3`&e3g2Y~$(xEfFA&gZ2YXtFrZwwG`Z7o%P0zSqfZ z^6@;k`!&xscjS?Pv(L3wv*AniR$})Hxh6}J-6U62@g<>H!2yBtAhc(a;!k3vJtHa= z3Wt>R)n?hb(nw0KJ2@W$gfBb^*)FqSu54}v2(Q2s6S8!~!PH?efPV=W1vrPzbafFC z=KVS3RKp4Q-O5j(0dd{$TB)pYnGoFXS&B1qBYgx5n> zN2pr*H;)Jo>-(eTS3vjX%_1Bn_A6}f+H0_)r<^vlXa>%`vy^l?XUVTL;?!Z%8M1x4 zD{a)iCN)4GsBT~mwt|$(OG)h2eQ6^7?97^L4m?iyfm5ck+4+#~cC9$?*V7sIv*H2+ zYQbbXm)h~fo`G}$dQ{%FGK&rJ zeSrw`#xb3eE2zh0c$|9dq2YYjg&GB)`!@D{yX^jzc3op=4u>;8N}KDbHDZZVOmy_# zxi{AMv8x(HBL&By4rc(BZRLWRF;Sw6q*7d+Er|!IzE27vGqLJX)mRQmIw6naPtyN!j{FO15PCp%dr5-&)f1s`3wx~ zW9MKZ=@$Z}vmoUCHJ2U}Qo7i*@nqY_VAtx`^pr{nZnqKCZn3_bvRh&cS1i=%Fy;up zXLF8kWYj5m*ZXlg5T18Ayh=1l$;$U*BD2Y+&hHdHV-F&-`(HSUtgPm$nYLg3Uzb#d z#Y!T%vU;2GrhI}L4-?bVWo5*dfK?| z+1~>A!IKF;OUxz_rwwhZN*;5oNwnB}jGDRJ= zyz_WYHBQ6DL#42r0yVFj3KOwVQn_t-%Cw%F8CKhL?z5jtIqH;}98G0BpAa+4mfvz= zcZVXr>CR)Te#kJmZ6!1mcf? zp8d367#x31UHOp+Dq#1d%JH9N(ewo=nwXH&G(EjG2r+%(vHxtf2y$Q?IM}Qfiso_27!Na_xXN4b zv)v{Wh13HbZ;0uxFdc2RaCf$2Fi|JZ0;*kvAS^3a0I6%nsfPml=fkyYITjF5VQ65nONp8La!m-lR zUa~sSwS4;H8DbLdXZ)3n+S5qI{5ZPA{a7O7iRv|7DGt0l9k00<@x&XYOOf#30uyU> zF9#ogt`~ZJ6%vrXAUlk03z~(_h5LREVs9>@e-K#jB@ZVj61A9IF%#xJ1t*!wV0jEd zE+t5^E}c{UMkbY77TaYA@#Uwfp~ZY@Fwd8sH_2lmXbKB)YL5q&R@jNRp)q z?=3+ghIT(I-UYs5j;EHBS!3xy`wyb3ber>t8VvZX6_4D51TiIBnu71#aV?WR=#!GM zQ88sc(nhaK7=@S!Zu&vaP{MfeTfQ%}%vwIb+H@PUPy`r%{~k5GVwnPd9cR!lGr8Py zxsu={lJC-|FaKmbYOYA%jwmBvZ{HNd?DRkoNZzHJ2j+>ZUtU3lxT^A@B8jjSaowU& z6P2>$6V+F1JKO?9QYi%6L#D4^O5iAEG`3uEj>vibg`n zfqO3W@YZp`SKqM@tVuIQC=r;CAGnOTH3%#wc*|0L_m#C)q<@<009AklXN`Yjrc7xo zPt@=%4fIClB|M<>)256#9-jF7H}$i~s~6%`@<%M!gx8jPp)nQHf!tp;MTK z#rN_RWW26svFz&Z?w)x)oHjAtS-31N9U{DgyJbL6>j3uyMH5*4LSQ6S_{#OX5&b6K zrt5&FgYrjnt-+z{Ic_XpzMBV_F%FTv57k=SbdPWxeH3`5Np0YJKJQ66CG`JH*Sb%1 zgxi)ecKVfy{xF$mHq#Xx%<4%GRlUVAdC4(M3}WZCZ%;}L-E)S$0fR&&Y ztxjW~XTON6Z)W2KiE64T|M!Rxey}^<@o|J-!ee&7{n=UsL(wS;y>4~r6`p<%(ocO@ zOH$2>G$prqB;ZnJpsh5<`YG(vL!+HrDgH)@Ds!rc!gWqRr`P2OklM|>{K}Xa1#43D zYP*C2v83N>7^>A+jV6PSF|V4}sT#0|xq~Z{L~qw&C<0h%bhz3HN_u%=6-=LVF|CHNtL`UI?HOQ z6rs(_08vvxz66pf?xxXjR7bUV*p zFj5PFNyjpnHa%B}WNS$IBFaX3?rcYOH>@nU-u~Y{${inZn3=`^LPHyXbSrA)fM6hE}AdgAWVW6Ltcd>;UoQAy6f)mZOx=*|01v$N#GqJacq@ z_@rg`<25BLq7iB$%H!c(pamL$Hpl}OZ6^nFJ0l`}<|^L6;TN~%Gki^4!f2}8$GMlS z>2g8LE~+|B*N>CcES@bNmTS#&m@nyzI`S3HRkuuN;iGqubNb}@S+&%Fx(Ke7t4^n! z=NI;^R>Sh#ZAUpk7Kw+RWc?NjyNyi3(#KUZTB^Y3)LL^&K+L+AMp+m0{NmL8`Lf2M z9{zVHzXV#HPLaXW-SzmHqwT%RsqR76RJKwDx*rRpGLK%IskPyIZNH05`L>6Jnu>`I^QNYg)m^Y9qCh3=RAriuS91nsoGgO3xN36NW_3VLB()}qVifWwgMO;$iNo7wU+O^vl0 zrDJHXEbuNd{`u69%s=e?aA3#ju@exJsnh16l&s)WDPT)NJmQ%dFI_0*1GDsL8!OvP zN}_5GZJ~p!(2ltK$A=xvB}FOb(KfoMD6JJ6Lg;v=f^2Fqvt z>C%-QBc23IwrKzL>)WizhKV$F?{ww-R@$ByB86gy$g6(uM|DFf9~x*|FMOF@!=?FJ z+of{~)~^D1^zPFnS=CRKk2uPrbC*B9rFpY_cayXxAeF!(Lrgdd2tiRM*IkZ^SAkXz zuQp=gYDci+?Ma81R0TP;JvDK`6xPt7`h28chP~T}%l>hv4Xs+7lzT^fH8uN2HD9vOWe zWwfl!;)mi0HiJDGEL)7k5x;zKK31lQCl+-|9Q&GqnCSqsw<|kf;1!eJZY#DJM-D7f z#MIK-67q;hoK|^z2@|p}?=@ST;%mX6(A{!xXBjpF8knIBTcHv%ESF4^5qu#`MITVE1{pdY< zl4ZIHF0S#3&2)ts3}*a%J8$)iH-CB-@~5n&kyJ?jC0j}u=3Fa{T3wtCB%{8~;nY`V zFv5>9FF!{)f9?<@oMP6LF|$Gl)I393%<+pB)CBz`2Xuq2N64F*rU<0rYyTpra~m87 z47M)@J%L;JFf3w?>@luafBoKPxli{r5m3Yw5AQJP2N|)TH*NN8twS-5y>mRI{+vj< z01^6D-Vb&gzyUqL+~X^T0_?8r4N@B=S!;o%P;b!nv+}!}WZwCeVw`Z) zx|C1SY+;{r&#BuL!l2L=t_L{lIX9V#)N(gHLUf2vmg-!Hr>8;i5)^}#iU8o<>9wn^B=9E7ZUW;TMl=ZWVA(`ltHTEtZ{zV+g&RbLcO&pdmW0qQ}VQ% zKl2QTCrkrdza4HH3h!3uimyC_BNKiKkuhWa1n#wf7RJr6&;fTH!`ec)sHrngnnycc zRTP8zZUe3C!(64T95C7URZ>s=>IbwT&K2Ex;s;~;uhZm1NJQUGD)tbD;Yj62o zotiU5p2TnMUCBA^9=NPlUOcb#B6^s z@18n@v>7R2C0aTuCJu6vFvp^L!Jz1r-Jvi(RFH+_pslX06|x4Hv+hr%`KPs?7-0A| z3Wu(6w+=+HXkdfC9CW4g!jM58o-$tfFHe}&Fom_dn6kBBFe>29UF13pi=3s~eF5|U z@wJZ@unH5kb#8T0w9_!gGDg2_#2lfaTG|J6v}`{QqTY7S*5<1*-F9S9n1Nf_%#Zy( z=n90pQ9h||+B10y+s5KGyRD+sXa`%b-V2kdv9s47-QRZMXBpWjjLZsG;(>p+j$V=t z-Nxs4$+F^cdU0uKc&eT)R90I2ZE!Vll7kqrxwd2Pb+h>iXne}KG`b`Q4Lr5tG3#B* ze#pnm5&oQ4;dW0PhY60Fh;11fkoTA=^72WcRO@Qr;b=6Zje75erG9TTb?N~IfDTrs zQxUmWIuhJ=uoi(qr&j)QJRfpB&Em_zMO)d3kNlH*C$a8bP#`V|r zgFb{IBk~+1uL#Hbo-7>kuAK+={K#V|5&`@UG|Q7Ndw40aiPM?v3Y;;cpX6%v8U4xy^?nH$a+7bH6Ml09DrMoI4XZan9l)V)je#ILdzevu#pXrsmud z#DhWXjkSMM-&DF@8O0Jkj!oNY+i}a$_oM(_|2)cIX@9Q}mU|u#4{C_iB;Va3o%x9& zB@38T6*-L8K_*H9Z}gpaOb{vqX;Tr!1C%jQ(au{SIVLKAnY1{%eFR#+Wf9f4wm(Wo z@Fx9eygXz(`(=Rfq>ewq4jh(wIN_;@mO~q|K^mlD6IdFLZpXm?AuCu+ z-rp@q?E4CXF}HzC8t9c=_L#8-<$IBZl2bZM&1aY3Taew;#P4$u*ObVKNoS%;LEd0u z1#_TJ3Nhm+Yp%%Ay zH0E0+sYOz!-KXx#@3P3mj<}(}iAI#QZqizEKl~sn6IRGr9uc zqu<9kXES1RdOLTJ0SMEBl%l)|FAV%*@P4zuPF_CyYTcD!B$dWdt7(4a4{3Q{xGEr8 zSLlzDt()H)%Aq$wl&Z0y4JT!rK}SWN-p}FT>O+^7?)1WQAfuXFLNuU38z*P7vY<)l z@phP(P1D^KOzla51E{iD7}c_r^~}I*6H<6b#wT%E($oh?yh3k)kefBjm#Us^rCBiy zI~@t1mxzzY47}Qn#boFKV&MdcrjxafD5&$yCyQ_@g+LpRUZk#HP^)M~^EW(*7U2$8 zgM?A4?&w|KuXxxS6=VF)hVtnwD%L}TI3e1-R2aD^AT?LQyHdMeatgo1YMLF7x2_D+ zqkAYCa^M}i$*LmXh3%FfjlLPSJZrTOtv^!_SHAFy)c zbZcm&9Xo?2kjU!T7Nl`;EUOr%VpY3WzVHwH)Ox`Fk%FIkP ztRS%JCQMA*KGtE!psX}uNbe{;iBkLgls#km%L=GciOCdb>(^} zhHT+VLdxx6_ru>BA)finSlZuOowQaV`MHQDmy3htAb;0o^$Y~#`0)@&sT3<^W1hEk zB(-P2{@b+{Vn1z}Q%$rszN$)>KH1h#!^>uEAVCvl=sm0iU>$B)s-^^qfW9LyaF+JHX!^Z}X2lhGFcMmGOZ7Kr@>CIPXE0cm~}$0sj_4xJS1fHstt zs;s(qP4rJZX$2Apy;Fg7q7sk(HpbmZ!b$cxC0@=b#`*W3wTwZl*5!Ktws=jMr%}gF zx^8#Mm#}fdtf83=-oxEliB6zlAPN6FD9De`{*Q7Fhpc3vnUAUnz$wLqk~$Mj>>rS& z4r*}yEqx3OyMF>Eks#!uKo3RX-@HTl7Xs*C&yys?2i>Hm+Vf8o7ax)+HVC#<@cAsZ z*4t0|MI-82yn-U~G^m|NA)qK&;e2Eu{tu;r^3u8^Sk^pAXt? zXoI%#pOnzh;Goh4KkNVNAbj6fctfC88Z#J0|Ns5KL%-}pHzkT_3Ul}w83@QvN+}qEI?-9Qo+&r zaC~QDR}3T<60J~kPx0H|f02FbpQy)+bpZmc&%@q%L*(O{xl?U{fZC?XEImi(UK_{M zkjLp~Uor9JF$%=SpJpPo8O)g3=_C-Ov43CUW{6-CD~ikqbPEafnABwN*w@LGNMruFmZw<3tHqd$PVY~!wQ$&90tXv;+}{r!6jOT4+9T~Prap73 z=W~KUM@R9B=b9zbj->)tu-p!`Vvj9#wlS8C9ugk^ut6|C0p9*= zMd$U|Ve*_>`!Ow5EjY2#ncd=%QPFhHa0nEeU!;yVVB9-ixgs9owS&lW9=Y=H8cZ6$ z4oPiTNJ%W)l#GpXPQ^9rk&w0itFsLyVuI8ks38xl=q0fp>Js|Zp-s*iR<$Y$YX})6 zWa=%IlP!lgmuH@Ft;R|@&#f6MI2BTKx+wMzD~hm>_oY`unz;nK$Iqm@g-TkhI#1bj z+>7qfS67qKQEzJ!gyhn4cg69AAhesuE8Qv<2Y;_rBH>34&l|i9Q3Dy@l6i&n{PURk z4KK&t5->_U>5|J0$J(2fCTD%7IM17h4&>|uQ&0~`=xPriGqBG~uL3Fh;=KeF*CyrU zAhM%wY$DSo5!TXoo_ViJ;K|oL64SZe1R%YWL>Eg`2p2SU&NzJh5Wgw`@ZINI;#!qi zuJAa0rWQ?@1Bur>7j{b=TcuQB_jQ^zIn}I>VMk;Jr}GSZr#VJV z837dxqaBy#D#H>FaZzP0WdnIhPEH`378=RX-*3s^6!NCf4xJ@pG~clu#KLgC;$=;7 z%{MXi{`hi|WWWy3f~=bzc|-K^bKM1U{fpAez_PQ_le(K1z*}}Rj`)h(J7xpQFT`W4 z&QfMGvD|zuRF{hBYrK=s0qfArud5Te7Z~eEx zbWXwjWhW;65T`zlN0n!Uy$d-Hp0`iqq@H>6eEw40^H~SL#?UkaBHPzy21?5(>SP!k zb^aHb_IHN(wJKjm!*St-%#!nF_LBFj#McZ79o)ao?JhDBYT^$d7;|3&tM(CKuzN{s z{U;TLU|p%3$xnE;2SP~!6l_}!vm#VjlQH}++5_93!O$s=q8%$jM{LeH&NCHn$3yd$ zm}3F+RgN&lwof0F*d3y)m5K@nwrf?dOg@k2E?S6sQ+ivnSu~kYGL{W+ppIlq?8){z z1P|oQ?_Y}6Gn5CynRX6;eNDYC%@lxmZJfS%o~I?|a6W%b7RPbrn@-2_z0-_wh}>y* zgNa{z{DiK>no+ydOYtI(`&b`Gwp2Ef4yp!USdVLTK}M(69qVGZw?esmny4Bgb*~+Q z9k&;8k_$@XKqFIeW%*#A_wG!|^QMRKwkva%Woe@*?*&$aQK<7ePm>PRo&NUAV=}|I zxVrm&DSDC;EAoKaTOVDAUYK5pe=D#CmI$ z&J}k@2Ke$3`VHvki)PT-)jaG)=il1~^WKLk@QFbs6N}ku1Y| zLm{AROx`QvG#+Lxj@Y)I;BSvPD(kQMi$$`M0figkAv?FLY(p~7Ze4pK<^mlEkol1e z8zb{iEhqLnvZqi?7^6VahXK1+6huexj7x4%s%STuK3{(X{&Nbvr`nSt4s(Gbbi2n|xi|d%_{JY6o5Q0ezB? z9s`~IRNA&lKBjvVz7}jqJZ`=jUft$ofCqYXUjnPvjxp>#0vMQ+au&Obu>vhh8V<`g z!X(=RQio0O8(Y8{>88a(rw%@6&@iY1k`|h7pm;oqFs?+8y9CCNLT=>Xd}G(F1%cZk zz1kp;pS)9@S5K;5D}6;6e_>8g_Y7r_jO-!M^sE6YW_&gifp$Bj4i>yW{AIi*1?yTr z7$--c93h4Ly^D-n@w+%+7%(~8dyv}8RxT|p()d?>m@gVg>&J~Kh2Tg#Q!0z!MM{j5 zsOwCXct1tUQ2=HsrJ&I<(L9CQw5R_C`fe@( zBIT|UE>SfVM<*3`bKiy{bo6ZEfOn}jn8*gQCk)kz!Atv+i)MwL#YVt&%|`Uz75vI{ zpSMNtXH<1a`;;@Zl8-PRmTELKSrbA!-q5|-#z%v~5i$UZiy^J%#KySGmP~jhh~as) z9%ZZ_4-`>55YcQO^dyd??*AcNRBh@Gh6G|BTvUEjn;@>G%N$uNGyf^fOF?;e-L-!d z?JHBsaK}Q6DCtR0;rnCdLZSWYKcdah;6-p?40d8a+)09{El9ow;P!A)?BEJXNT!b~ z1%?oaQb4F1M01U@t};^tSEI=-<^2@oFFK2e*a2FvgaGubP*gzz{^fV&g1(NlKJO*V zRNy{kc6`N273(6sYvb*!rBSgzzzom&ZCbo^zqRu6h7$D>CxRtQpGTUvPij)u zk?B7~#1*qpS&l5EkJ#L6DO=Q>W5h8_uor-^zEX{36A2bp!3ug4PEn6DU;E|T)RB11 z$j7)o74-l5tF(phNDw4yN!KW)uWC^g@}}eB9rSQd!Cy3FnD{);LNnPo|pEAl%E?6Y-FkNK~Nd$ERrOJyk`0YT~X z{10Re58uQLm+sE2L2kLQ629s9*Wvg*c;F{^u`18+FJ&;w(2)xSSpySnelhluYFPhe-4{2i6P2x01cIBr+?6iwGbh#xq`MreJgJb=1zElN( zlZKL$yl`;+_j|UeE5r}~^k1w42e>BBsj|$kL9(WkEK*H}eUkxGVv^~XQ=zji+thZ6 zb@(L)D68&{5ttBchf_EsoP4aULGZGy6ZGVrG2zi%_^kOa+Yb$bqD>2D`q@(bP}1wO z!!zvxpvwP9t6aM+SYYF24|CgLD-WVp59maSRR}|*Fwi#fk6=xleZ7usLW0q>N1wzC zG?tM|RkuSK@0MDQYlJ=-^i-i?$l#-9z3Ay1b;LgTOp7*k7MwU3^EIck_+$j|bc;pbXgbMYTT3H-hz8PtWO-`@B0JQ7 z7kX-AgC{f;xw;3dD3=1`k>5)DG5pGr?WW_Ip?7M22ZTg*}PLCOLQHgIB(Z%K|ZWDlKn} zMxJoZzv~}HDXK6Qy4iqi5r&oeCj9TO`ID=!I#tjYd8B-E|+TtiujOMKiXQR zi_mpv^pG%`XZ!6RuWJ3)`CTZAqWbTH|6eB{!6-4PVHl}jOpRL2but2y%h?20JA_1k zO*kl&bFnT6Iw4V(ao<})c(cWxb~8Lb9gZ}@83SB9 z-jy3Y=Xr1F4`S5bF@L@|EwDcs-Ja8>NQU;b+uw5D;?c*wBU0SHRhZhaXrlt^JzMoF<1JeYx>p^&ix9_z>R8->wu z2SP}=wgr&moqEUg?(?0_sw=$eA+A4{EMIl)=DGkbvD|bG=1sfV3&3-H3qSg7hJCuy z%GP;N#KGt2$h-+iA*G39VA&=&LWP*(1a~rP1ux2bImE>scm(Vnq1wNoLB6$x2uT0l z^PXvuk-_Iu>ZW>4Bz_r0{MYZ~Q%r)%U`Y$oH}*Wk#ht~_1^hPQ1( zr01a9^?c*fU3?}pPAy{xq@Byo=3BGwJzg`u3_QgaQ@tZzLo}rgw|dJbFulVz(JVhn zYMCj(2GZ5$tl!wFx}5LJ8tf&n^I5)*@@sj*I;apqf!`@O!Q~;d%~%5>pJ}=7U5YR( zD~`$p;mJ>?i9)XBTVSxrL>&I!WFs8rAqY3Ov$-ek-~JeCZ#Z6lbb&gbrI{QHbU)`; zs^F|GQCL<^7*7=ORIYgT&(2Pl-}HMt)L5%L*0re^f&lpDQK`rg$TZmR3`n1R<9+B+%#t)^FbY-iImtXMv#F&mxWSH^m%tBfmy@Z4;AX*>dLk zedSr<(L#IA+W*}$i27>ua^ zzSO7JQb@=BGwz`@4pb#d9p%Q<=ju7(Y4(V3XThR4eoY-SrZ2zvfx4Hltn-YecOFZ% z1xGmF90YFbY9r@{W;d_b(2$Y&E#__VX}r&}A(_Z~kJrDIo@sI@A(^&B;eO|zCXWoogfy0pr;5vqO7+Zq`cXjF zmcKN+p6QeUl-+Eye>YppW!N424agsVi@_NGElk`m1|^*3T^P;{!?N=%q|GM|6N`XRC;c28hPIcU)&{MGC3vEH>HI{_nRq zUd0vqwH5~*6_0AszsbgvN+hirje$A#c@pxV;vu_bAxbOneW$V_<626}H$B}!mysmq zl;xkOTCGlkw-mWyUg04dx2g4LZ#1cGHsPOYa?Nnh&sv|0E84Y6IA1}~D0OC&BM15< zDuXQWHRFsL7i~SEt^`IRVu|sLM!3XD8tv9E3=J8Sh)W=dL3khhyYgyO2B!k|^V7{i z@!9U5oAMMCC_@p;xda@CPc?ux!-0^;nblYSpd?t%XXOAe?Pi|Jw$QxV*T7BYY!pFugiuD-TE?B#&5je%kD8b~s)Vj}NrtrPUS-gngQ*_jG5pqxb<*DXHO<_|#uGTqS^>k}@=?>Xmls^*DW15$C)S!(xRuSKlLkw zcZnZW5@^3WG6=Z0e9?5#_gEuXoi?G9?F8qc)B<9X1tV>+d90wF-A@ixV9R)1eD2&C zO-o9cTpsqP<~Bi95VjZ(*B62w!{fGYx(7}KJ+-ivbFVtB#h0h?e!2uyyBF{$W?q^o_08Pe zUAWuMR}9lhi>6Lyng$j6&D1v1Er`#X=)-iv8FSm%2l)Kpq^Ix^#+EPs!Eh@t$7bC2 zrf-F=Es#Q1=2F!a`VxFCQSF}Sgq!mf(DY5HFARhBM`hXUuup|M5-@4tQ;H)}!ybNH z0iu3O&|R71;MNh(!6fn1&r!FZtzj_VnBA-z-lApH+`SGCV1(kXT$9#$Kj@!yvu-1q zzPzpI5$McKb%{gf8Z0LWr`er%<;z72ND>h&(8GdrL>~u4bX;m;Zq)qT3b9!P$lRIv~&(|o9%Bd;pxeXTE9AwA#xTH ziC5Qocj>2rrh{r1s_GUt7)A7YSkuix|Lapj6(U8{4-d6&MTx$t1|MEqtd(}Q_k5M@ zi<`#lp|#gC^;t#7QZ&7AfyzXvT$4@o?(DA}S<(Kc=+ZL*QC44v-|{nqGnD~WC4Fx)0qG&!{ccm4 z=@+yog-T7jz3Xe_77<|XMF53_#M{z9K+o&zQX{N@YPwPyQsaP^!zM|>?B^F@=j!sP zMF*-2X_0i-I5%t&R+D(dU|Q?mfdIfjC+uCK2tNX6ebX$xvSC*E#t4gfBGndlx@CE3 z!rxRX%qTau%Z5JJz?oWSu{>vw*FS9pG^wI+S5bAS@-v0sRg%&qrK$of*j}LZfSkmo z1;NdXDpOm#kt|A+XP2n}6?|qe4hKUn=D}Ff8X~TSesKs^&!gk87VC#q`4kaNiyhoe z2D{216R76IJ@rbKMfGJrJ!0(DjsdD6NnD-?s5>~t>gqUVuj}$!>lTVKi>^6z-6Zg* zVdy9dzSy87^~u8E^h_-InuXW8=(B2=*UL`(WnZY7=Ja!EWTY^;ra`Y9`*N5kP(fQFP-CcXM-6oY&hpZ78bbZz8!OdEiVCIH(5V1}sgFkB7Zuma zQZ%M5>$n`%*ynm=pTH2Ykeu6KFO`ueyNWh%eyZ0O3gwOr*l9$!~FMJNC!=Jhyp zDcDgQsPk#Y)ij1oPm_JGx;-urLKMWe*Bd=8SYmunIncn3Bp%gEvL8UqEPRwg1ky?| z;&9)3#lL_P@XW3k7FyP4QS{~uw(==JSv^c;s1)UCM(1Ybkjqur1ReQfSMV!lB|lo7 zLe+M{l3_6IzZ%}ijPz=>>_oxKuSYQA8U#z3&;7z4fvk){mzXEWssv?MoftcNgotIo z;Y&(KUYL>75sBhREGK!qRJ(oEWAp{``&IIzr~c5RLYxM-)C_2?BJG4j3BXquO1ej* z(d2`a5?9Z6RV>q#Gi$DL^QrUKqLz#%OY3K zM4@VqTu=Uk-Nqo6WV7;H-5hgM?=#zC+{b(odZog9WtyRFFg2 zV*bTxdLUml<%zuCQy_^5QDPh1iy&@#AB)xStuw_oTdSgx3hPt-`J=TDauv`AXEKcna@N4i4r2~v|j=qg~a2AajD6PuY8NZ>n{ zJzBIAw0b@urrU}SWbd{kfijbEl$~@~*ZuWbcoVra+Ha{u1TGZLdNjzudeB8t_jHHx zXw5y-O-*nc67}P+Moz4i?Mhj&pE?Vr1)G~EaT-<~0c_GmzOS$2l1uzSgq4s`a!lkM zofI;7PsRhLyYLYvpqFexT|$D1Zo+nE%9y!U;ubE|!}12=akp0?#F)pEP~6rjidgIlsIBcP*x8CBjCk0>Mz(ot90Lx> z1^XrCy(t47CkiT$beHlk{0~-fF7Y5Fz4$-tNmz@~Z)$goG=e)rON(?Jg@*wEcBJAN zv|UO1Fl;p_?Gj0dU9X*KBmopdhGv=s+&6S-TsKM}@l0BRefoKYF1y>G0-holc*%BL z$a7DGl7qG=@njvy<1Ch(eN4gtm+e6kEdwm4eg|u`_hzQCEuFXHFSI(+KZoGS5RH2! z)#uV{M7t9h)!mH=;7;b?2gq^@3RH<$=Vo(d{ zUQCWsl0vJ=ID=}tVT2d!w$HKF_>4dIr{pF@Eqmjk+CG<0jwZmp`z$lGmucbCjhE}( zg>5<{lnI-h>VBs4<;8?LEA@@)w6TZA=f$qD%s0D&=rGr|4Es7=Ea;3LA(Qk=uPng~ zrSCaliV+HE-$|aOdg;kPHwTs4t0{qg>PrtJCScWrBV#auI100saxD?*My^P!eHfx= zRoAFb!PdhzPreD`yw+mLP|ktK^CS~N!E?Wsb`RIA)^!z#I!CGtGab+exqRAxgUv3P z7f{#B4pe$rnD`M`&!PpyPgnAAi)Vm+e>VSIv1Nk#{^0{vkk}7?#dPd*tJca}j#ODU zF;8RNv}~&%pmZ<6PeR?0)KDmHc*$&SCuFPI;TE^k{A%|=3_!-U`6F72-YXf6#!x7L z19+WRB?+%#j;nXCR`8^pBj_G$ikt&PBKWrw3G_Q#-^{-K%#7x|YB~jsTX>@hOHa9wM1UZb` zjS!6}>n6T1_MjvTCaZw9l8S&nA^m0L<&pHXTR!!POapY3d?t1Zm_0+)%}HquaY?Ve zCtLk33q8QoNQWv0h>Oq{=+DPFLOdmHr&F5y|G|B9sAJw;EG=ESjdM-wYyxQPbDn;d zqF_nbcJAStL~g6Lw|E2)yuAoMqY_KlO@)4!w5$YL4!)3<%?J)Xnj_P{Agf@dX19nX zGjg;njp*=O!$iO>gm(A9-AxyXT?AV{;6t5UcJ#EO;47IA((hn{s_0J$6&L{ud;wRz(CtZ19RqtX^@p5}A_X{9}}0{2h$o{84zRw`i!CHI=_|{_#(c_r~~V_OVw; zRsQ>L)F3mCb`eGXS7&@ff1oQywuzemG%o~L4X7nHxG?tr20l{wgXTZ3Q_CLxZ&v)X zQbs_*_f=}@{{p*#w!F`O?gvoLgG&D}Fdq_qm=BWU|8s-THJMYwr6ICC-IIcabrXn@ zi^1sr6-*GFfK*vuPrlFPI?zpHqfS{D_n+aoSb?yh>A&(JIS>&Lh;3}>z>AQPA!6Zt zr%lFWVRY7a#A)RYe+Uc5wzu>4$};adI7ldJ!hi?>LAcX@*>5n)C#iu1e7fTqaiF_f zYrvI=9*|jt4<^aFy1Gh1f1!6oR~je#@7a4m{9`iYh!<*3b-Z$=FAHn`wKlFd>3ZkL< ztMUJNYKo{o3&G|ih3|hYLfs!AxGpm6-@pU^)0#XU=nbVvJ)# zudgp1#V0XkB>Je1-yjN)^Hui|_co|!oGl{(n?o^rJiO6fckmB8ycK?H;*TNsY zlb^hhuEJ^8-Fdv^hq7hnmu_o2zCE2@pjz;2H?1G#$c)TA=b5C9VB>PQ#es-K;zx?w z(g(k`BTFC5=cC+)n<7NzTohu#M*hklT*fQ3n6?#b^`2x%nehOioEt?x=f;t<^dS4a z!KxE-aixKn_l1txEM?nMLCS#eIB%KF_h+qsD^|lq%*+9_T5UZLtK)UhbQEaNoI}1L z;i5-^x|`XgigjB!HxG8WgN=6EWRD57G8*qMRJZ8t$fTT3FC_5ToLtBZ^3D_3-|?mr znCt>PVB25FEm?WXp(>hXi09Vp8wF&NRX;zT(!Sb1RE^pJeUeCA;2R`+pLFtzyUSyT z6ERJPGVFGz5_dUKkJ5S9XS1h*6bN^ww*4-(n@zdbGt8wMvR2h!R=rr`+)5h^CUwp) z7q7nWT=hb3_nT}O9W^|#`YN{mx>0ovu1iB;O@%lL)}*(5!PNPavZl#TbJB-u~=JOVGuv8hfT=7;cWe_2|1lFpM<45Aa%P4}Y1` zPjNDU>2|XU>Sy}l;LXtH0n^)%j9qvWGrp2m}K`gis-^D zbq)w3E=!?zg^l>aqnJf+cmPeQRg5-Up|Q^!Dt=UY7xm7S-PDVF_;sBc=Kpl{)lqRg z&Dwz=0RkjJf&@vhKyY_=3GVLh5FCONERe+(*+s(Q?z#aI+}#Q8uym@xR^zv^p<5zOQpBgN?X8sL z$Ae7;e4x0q0D+_>(5fSd;m702uFU=<-TMi&{M<^FAzPYLVN!w@K=`WIXchn->6@Ua zR^~>GosoRaVT~2`Gf_Rre@XA~X}9IoExf|Wf7hyoH%N59FR0cEMn?Mq?SYQZ!pp1v z!)`w$@WvyBqhP%&i^q|-wU6u^24km@;LFIko|r099q8+e28jeHK0cyomFcL=>WM-& z`&8A|yCaL`lRuPBr?Z(tJaVo^5pklomi+c~Q_LWK`Ch%KU6Ab{dErab~nn*yq@HyWt zcb)B+gl?sg`f8z4Iz1*Prr@A=Tzst7jdqzq>e2CW>XUQtCbFXb9JVf{%`g@S(yFz> z+QzJl>WRh!G&S*|qoYrB++7uW6e@0=oKQN#H~WrZ@$K_s^tV8V?~4tN>a*qgSk3_( zzp=1im{pKjf`xoone>ql&miy~pGyRf(a+h|R?brH`%k=_Hua_1iap}Fhw)^Zdh-=B z?Q*1blS$?Ud-stzTY$A_4E#M+Z6@7~OQ=T% zCZ;CTwX-nf@}$5i-m}Rir^Gl*RV(A-@F^GQxjRu|_^6fA*ELM4$ygPKkS~L4DFY5Ig;xzSiP;_FLn9;W^N9h&cqQ9Q#erB+2$2D>w6?2n`Kl+ydn zl!pPn8nV48aPN*~uT+GvJD22ZuL@?c)`0AYEi9Ijz&aiD*3KxdeJfjJU3d1HCA5BD z>*Mj!Vaa6lY)4=0l9#iS1aenB!`cTbu8p_fP|k7KV!z#IJ35|BHpCOUIP><&jTHqJ z?`w%IhVaCf689^Ic8!sg8F~${Wpi46RxJ>7gCy42i$C$_G9ZDCK;hb4P_e{A>-pSe4tHGN z;en}6@ij%87tPwnpAuKF$$cVrVbcL`u6J2T&VK-L59P***3zl(P|=?
    PRXcf+TwXz$0(Yxn3eVg#EKAXWAFTM+Ql^q|1Q>K>FVk#UMbM;x) zU|}I)@XVK&$T>dER?Kl9cvx1zCz?yw*8J3)2<_>CPeDSY>_k=X!5lVCs2}!!XP|0* z+2RSVY|$K%#E=pH!!cK4*>E)dFk*zp#T)-UF5a{?et)m7$zuw3g-B}I1@NAny$sWH zuoV{KvXYkzAPFC57%SFDW2gr&tdDRSN^MEkA>VOMdvlU?|5cZMw;xy1Kgb3mn4l zn{)g5P=zh?V?4T#PdeISe_4KTKbW>}9oFl{KX$)3AewB#+&Ec+nzk&Q{Ty0Y_%`r> zeEPzUOUbE~yo-7hG>y~50&p7(JgN7ZKxHz8y!1Yq zH3oWwCSi(jwhK43q81ei-*BXYI-oX3Rg+;Yb;WnP#`gCS16~<-yo=t~dmPh`Q@r)Q z>%tA`mfb%~m;@(pqrdruqTcNH`PDJ*ozY~9_;lJ&>P(Genl!nHWEC7k)^u14IB25F z>e&a-N~*Qwf&z^~>TW5W%>y>ESd8ps4fz%W-p4Pp;i+|3US8XuFw)G$$Bp^_+ zXdbq0af)m6Q(M_cDO%Moy<|!4!7UBJgqN}P8qgn{0j{uxko~6cS0GJ%6%!gzYO=rH`V;+ z?M-o5`g3m^P0A58j10ExLzINa{%??=14V|)C=EFmA>?7MZ5~>8v#|dW{|HDRr&O!t z9h-jw*^J#%BlIKZ#Sp6%5BghG&jtK&X`R?K>*v+w8baW>OMt|2<` zWh%LX^mPW@tS3ZwVYD1utCHV*~~-kd)vuHsF*pRmka zRU|ORN`B zbM|a@MR?ox+1aZt-4uVy-_pI~E}&xO2VttzOSfDUCuEVMsEX}!z6RyxIk_tTGD#7} z8bW$D-p-P6?5)zid3NSu8F*$i4@BjgXm;PS+h4r+x3}U2(=Y%WdB5yoQEowU1#4cH z!*5v^-g?^`Z8z#|gIB^U{wg!$(BG_3qyt^c;~1t?BIg^y;%q<~S1@g+>13 zCWu-)zz}R~ldG8e8LNy&!@6clCr|O3xF|dtk5T3J@j0f}I)XivRTIL!Jq*-bF>Uu% zf0!*-4<7(=h3cIww|r73x-hF?lP_-KOEaCC;Gl<6r&b?E(Z3-RY{X=u6S@_V~vIe>AR##{R_vZM<*WA0h%PAf< z#{*;rj3s6)<$f5Klsd#?@s-r5G7*_wwSrK@Pe}DLg#?2JxIoko0hdSSA8d*b>1Icz zO0_D>>o@u>H+xl-o2xYTCkx{j<~bOvhUm%0Ur1aYuN4J5&Xvme!MD^GFOG%2eB=uZ zeYI*nB1~_3!A}XBJ@6TNA|_(h>L133}s zQRRVOnT%D40_P-eiRb5Yr81_ z76h%&OMEO3b?#n-84_$O zn)>OoJ+lG48eB*0yxbmX(B$x%%}8rnGuVVu%aXVZ2~BZ-$VAzPSt5ggY#nV$%YCO} z_*j-J(sw{czl4cT331R~i%BPT`C??oITYl|RYhiPQQ=5o;%D#+jXCAcO41U0(bZY` zp?SAv^1QLaLGOWdjBLG5SiM^j-%31u`JlC0Nw!qZ;?Oq4gmDOkPlK`(!0qZ{D#PZdS&v()eKxvXwk-;E5uq+tVCWmNN5(;EPmof7kb zQO!Hv+GrvX3he?4AH{yxjM=$z8@iioqP_B(|0@+1_L5B7mCDr{A3^mK3+|-y?udd= zK#nBkE!xV$06Tvu`4(vwSRD06=9xt2?wG0C+yuu&?AH1gnTi^3LiT0~h22Ine!l=3 zK}mU{{!A(ANrOuHy19ApH$ojTYJ5wC79ZQ063yS@EE5x60XY-aWz{t^mDcq1^a*L4 zPvS1Z5@-U^W|OE>c}nS3^UC9{tLK{TIQ#VcJf+&&E_MKO8rdl%lzKc_2XX7d)7D{hsQ;pO#f%`yi~Br?p~1+RS0cv}rKq`85TV4M!Q%)CV}I+nU`ZSx;UH*%H9Sg6&g&w5b_-H-soErT-zqs7?b0MQ# zw(5L}1lz8wocfv1Q3*wsRJ<*M$mzaJjh1WVX*vO3!~3Q-Zp1~r*AW44rxO^U7h^vi zAGYx2PsSmD0D|4ES;hdUDq2q#$E78DhjnBUK9dO)L4on>NU-5_w`vKjH4CrfTW|j+ zb*Z3N7oS6=4jU;pT1;(m1%KL%9K^;;31}3JX5F6*a>%*=w|3^k$JflI{>;9y%qtNB2gCQR&cE? zKvGkSQfa=dZ@x8c=T@d0Wl4MZjo}+ral}kLXTUATs~`IN&w9EzbBPuuZ=kt72`U#k zi^wrw`ZpCQ?f{)Y45I=P6UXQi^P=WjCkO-@%Te~)9d(Ao&)K7TJlbfgG=yV}M!*JZYLcJuG(Fd}{m|%K4c@ zC^HI=^k>?2^h&n?UnqdJ4|31oSF z3RfO*$Iw}nHG((4V=QTW9LijdE5QD!8Jp~cDR$Hp;A9KRfphK$tE1TKxAMtTFk)Iv z(|kcJ+yc7Vtz6qMmJI}88KHhL^eEpq-E>@p8M_piNiEAfqZu?3lzKDA?2?)9yUQ_b zUhn{?3B5Ryil7+&eTIeO`JcCDFz3Zx@A)!XYbm_MO!oB#lpc~A%Td<+=_3w8LIA8B zvt(G&VA*Fi*l+$61@3L10E)_k;+Mqz6?~MpiO$*R^B_MmL>09xyPqvK$=P{oP9oxZ&+Wb8+mP92Ik(nsdhjwx z%sOARxV2aGVeX|I&dtiz)Qi=I^`+X_ZWY zaXJ_L2Bz-0xznuku821^8{ciw+}ApUL>_5p6jRVT$}eWnXgt$wTc zQI5IMXo;?<wV*Vqr*gX?8V0{`|S1nUxLCMjq12ygqT5DJ@ZngN2(F z&};~9yXW6t9C&2-q!LqP2H0H?e8SqcRq~$?#Kbad^L*Wi?1x6g0VusUoNxG)}qbcQh}KofW83pO6dm^(;+uA>|zqZ>=m(2uaPS z4`#Rb8?a%)nkn%r`B4g1$>Pv-CNr#cLO+#g9va#rN?l>vVAF3jN=8uGjBsSVh~-#M#T-s0C4%Z-^hXqOQ? zKC2o2K2kP&V3+CYY?28c;qB&sPWlBTrJrSx$VcjciinYMDoaSPa}If_wuKGBRq<7L z*SImh+Ev-OlK+f}?;w+SWXF_2PH6E@eTuC zZmwhr#CK=M^OQCiu)OosmWBYs0g-NA}!NXT^NZku=hX2wARt5+atx57N~Zh24_clU{45 z_V}Hh?WY}AMz@QOr&kJ#OPyEQ2oSjMSht|;GG8n`jSv=$w%34 zJD&Cnzo69w&k@CJ2af8`xxfIVnNc)TBvlLOh#d@QZ3yD#PwKqTV|%a3l~KtS&k(ii zU*3M4O?92~rtBgcSmBdL$1ivZW72o{GWUxjML8DAtDI@$$*PFj&2~TXl^UVL)A0f#~Ggl6y7EoL6Pzb~r1<+-h5GnunF zoVp4etY~v$XI&cb3K_iO?^|iZ`+L)2F*9=MN5~T;xk8WQ-z^sv`!3bB)%<>UMt8&x zq8~w&oaTu=#j3Ga*MZr&n#Jo}8@%PL<`kweQD!&E_rRk^HNUfaymniNT)<5(_|3OB zJUAcDO8O8pFpp7dGi_~-k+>>WbWd=h$eRz;5LA+h{%{UyssA&~HJk}2z9$gt@hy3F z*L=QJi{GlpuGWAhn&Oqk|5DUyY&y-0EtVkdA@}AvVsU3=c1)OG-y)|L&gq!Ci{|90 z320zyXgsM6tp<#Oy{0P|DjzymPnh?ERw`8k>dQsEo;qxZ!sMue?%BR<`0XAyExeR+ zvVDEuP37xW@c^9QXKCD*dsW`(Hlg7;aSm)84*ZCoFhy1_c5MoNXtTG=j{Nxeey6)c zGm*6_ntbzylFkS;R+!L|KFl%4Y~tYASx15KXSBU}0>lUGC0X7j(4EbUYFx3|$q||J z$7QYzkuobHc!a_ic@}R~o%_pXEhviU4Aj>Ne9_Y6 zEQtwyfIA!(k*#7@^q*%z-z^xq08Ooz-vl)q!JcmPWTDTys^+>?q9sFSd|ngoS-mid zxD z1omvj<9OyXNoC-y&g7)Zc2omuYaPZz&?X*M%~{lb`$lF-R2e2FfIMn6UQgY&9G23h z*}E)J9r3&d=}gVIiRG)-0?sVms18M-Oi3d&dp)OvR;{dJ5|d#@R;y#Xi$rDmb*zcZ zkNx3Sz{t4tg<)l8H^xdTrkxazck{Mxa-%v1Zq$FXmrGt;#pr=k&iFm&3n}MM2m`q`g^o8`vU zoetu*qDY1pY9rCmf&ySnOr0Yh^JaLGEtl&tZgUQD$%-A=ChAv~LcU*cfzd9@cRr_! zHJ0Fo>oMKG8a!ei8g@cjU!fRX4hvi>jK4-Nywg{MU5f+|3`M&uN90SZ((If1s~7fu zOW2N*shE7e5*hrm$@euEuA$U$)W^A zTW%go9!!=fu(r0APmbaEK&~`RLK@M6>8i7R@SKV@o7+8%+tR;)QI7a|=hwmQ=3PDC za0pSBJ#A3rP=~Eb#$Uq<)uB3kfPZ*zVv~7ie+>RkH9I+42+255BIedH1MAN^!2HRnoq$eo*LjHTGv|p z!BV&{tn~o~xbt~z?>b(1gjj=t+}E#ECHDe?VP&k%7b!N*ptNptn8M{gj%GV<__n9s z*8n%UdOLhJ3a6m#@Fm1e*4@Trhi8_%bM@NrR{-{J+~(5_QNLxsPPCuHO}z7rnBU^NbVl>8_C_WZa{a`(%o~avVm=g!djSVEl2zI)jwU@5 zRp8yn6}{}R(Jy;%rWU?w#do-HR61WN$fTnne;n(@BBE^slqegmz%Gq0-&PVlf}N{X zyh+Lj>mE{B9E08mi2}~ zFwUeE<9cq_$ludX=jU4R-2y*auVERehjF{dfDLC~i+2chT>- z{68XO2VFrN+h2l`*jo`zTKqpPR}V{Gf^C+j%02Gu3#9sgH$qwz#yxaDy=lhRuTcKe zLM`a1(N{M6x&FNOG*OZ^%Y9w#x2IGGWw*&qFx-6o?{T4$wEr4Z9A8?}oh5WNpMK!0 zrK$eW9Co;;gKX z!&*}Rndg5^_zev;x(G+TgWf-+_ec+1*+R(vp&*F_>49%~+CBe}BGqEIigC^SuTzk- z4*mT?UPAR{1sU=Gxawb>5M-PlfF)7?vG~7yODl_vfrZ^n^*^N71W92hW6C`L8y2Lw z)Er0!1X5*DGXE1SWak(7E1_42P3imp4)wpy`}R613`AnU!~75FBU-GY1REo%MQdBz z1LOZ^$@zPfgP+qX1wV@k&y!0$Jaad5Q_hRIB$ocK5uI<5VT1CIn=^gVNWLSFK$Wr9 zcsxR|4L@GuP0h>@VM&w!!#ZjOR6063NGWF|IJSRZW|rp76Svg`8w g-y2C&r~P_DxiLxqJYbt7@eFw>$b6Qrlr#(be=t97vH$=8 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 1367/2908] 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 1368/2908] 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 1369/2908] 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 1370/2908] 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 1371/2908] 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 1372/2908] 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 1373/2908] 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 1374/2908] 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 1375/2908] 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 1376/2908] 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 1377/2908] 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 1378/2908] 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 1379/2908] 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 1380/2908] 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 1381/2908] 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 1382/2908] 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 1383/2908] 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 1384/2908] 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 1385/2908] 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 1386/2908] 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 1387/2908] 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 1388/2908] 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 1389/2908] 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 1390/2908] 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 1391/2908] 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 1392/2908] 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 1393/2908] 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 1394/2908] 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 1395/2908] 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 1396/2908] 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 1397/2908] 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 1398/2908] 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 1399/2908] 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 1400/2908] [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 1401/2908] 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 1402/2908] [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 1403/2908] 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 1404/2908] [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 1405/2908] 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 1406/2908] 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) = }") From 677d48d6c06234750a04e75ee0f9d5a349d78c54 Mon Sep 17 00:00:00 2001 From: Kanak <63765823+Cosmicoppai@users.noreply.github.com> Date: Mon, 11 Jan 2021 16:55:15 +0530 Subject: [PATCH 1407/2908] Rename coin_change.py to minimum_coin_change.py (#4108) --- dynamic_programming/{coin_change.py => minimum_coin_change.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dynamic_programming/{coin_change.py => minimum_coin_change.py} (100%) diff --git a/dynamic_programming/coin_change.py b/dynamic_programming/minimum_coin_change.py similarity index 100% rename from dynamic_programming/coin_change.py rename to dynamic_programming/minimum_coin_change.py From 03d34350f63fea41bac77264b7380f22c4c912f2 Mon Sep 17 00:00:00 2001 From: Nwachukwu Chidiebere Godwin Date: Tue, 12 Jan 2021 14:41:48 +0100 Subject: [PATCH 1408/2908] Graph list patch (#4113) * new implementation for adjacency list graph * add example code for undirected graph * reduce length to 88 columns max to fix build errors7 * fix pre commit issues * replace print_list method with __str__ * return object in add_edge method to enable fluent syntax * improve class docstring and include doctests * add end of file line * fix pre-commit issues * remove __str__ method * trigger build * Update graph_list.py * Update graph_list.py Co-authored-by: gnc Co-authored-by: Christian Clauss --- graphs/graph_list.py | 169 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 134 insertions(+), 35 deletions(-) diff --git a/graphs/graph_list.py b/graphs/graph_list.py index a812fecd961e..bab6d6893a89 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,44 +1,143 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# Author: OMKAR PATHAK +# Author: OMKAR PATHAK, Nwachukwu Chidiebere -# We can use Python's dictionary for constructing the graph. +# Use a Python dictionary to construct the graph. +from pprint import pformat -class AdjacencyList: - def __init__(self): - self.adj_list = {} - def add_edge(self, from_vertex: int, to_vertex: int) -> None: - # check if vertex is already present - if from_vertex in self.adj_list: - self.adj_list[from_vertex].append(to_vertex) - else: - self.adj_list[from_vertex] = [to_vertex] +class GraphAdjacencyList: + """ + Adjacency List type Graph Data Structure that accounts for directed and undirected + Graphs. Initialize graph object indicating whether it's directed or undirected. - def print_list(self) -> None: - for i in self.adj_list: - print((i, "->", " -> ".join([str(j) for j in self.adj_list[i]]))) + Directed graph example: + >>> d_graph = GraphAdjacencyList() + >>> d_graph + {} + >>> d_graph.add_edge(0, 1) + {0: [1], 1: []} + >>> d_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) + {0: [1], 1: [2, 4, 5], 2: [], 4: [], 5: []} + >>> d_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + >>> print(d_graph) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + >>> print(repr(d_graph)) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + + Undirected graph example: + >>> u_graph = GraphAdjacencyList(directed=False) + >>> u_graph.add_edge(0, 1) + {0: [1], 1: [0]} + >>> u_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) + {0: [1], 1: [0, 2, 4, 5], 2: [1], 4: [1], 5: [1]} + >>> u_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) + {0: [1, 2], 1: [0, 2, 4, 5], 2: [1, 0, 6, 7], 4: [1], 5: [1], 6: [2], 7: [2]} + >>> u_graph.add_edge(4, 5) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> print(u_graph) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> print(repr(u_graph)) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + """ + + def __init__(self, directed: bool = True): + """ + Parameters: + directed: (bool) Indicates if graph is directed or undirected. Default is True. + """ + + self.adj_list = {} # dictionary of lists + self.directed = directed + + def add_edge(self, source_vertex: int, destination_vertex: int) -> object: + """ + Connects vertices together. Creates and Edge from source vertex to destination + vertex. + Vertices will be created if not found in graph + """ + + if not self.directed: # For undirected graphs + # if both source vertex and destination vertex are both present in the + # adjacency list, add destination vertex to source vertex list of adjacent + # vertices and add source vertex to destination vertex list of adjacent + # vertices. + if source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex].append(source_vertex) + # if only source vertex is present in adjacency list, add destination vertex + # to source vertex list of adjacent vertices, then create a new vertex with + # destination vertex as key and assign a list containing the source vertex + # as it's first adjacent vertex. + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [source_vertex] + # if only destination vertex is present in adjacency list, add source vertex + # to destination vertex list of adjacent vertices, then create a new vertex + # with source vertex as key and assign a list containing the source vertex + # as it's first adjacent vertex. + elif destination_vertex in self.adj_list: + self.adj_list[destination_vertex].append(source_vertex) + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and assign a list + # containing the destination vertex as it's first adjacent vertex also + # create a new vertex with destination vertex as key and assign a list + # containing the source vertex as it's first adjacent vertex. + else: + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [source_vertex] + else: # For directed graphs + # if both source vertex and destination vertex are present in adjacency + # list, add destination vertex to source vertex list of adjacent vertices. + if source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + # if only source vertex is present in adjacency list, add destination + # vertex to source vertex list of adjacent vertices and create a new vertex + # with destination vertex as key, which has no adjacent vertex + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [] + # if only destination vertex is present in adjacency list, create a new + # vertex with source vertex as key and assign a list containing destination + # vertex as first adjacent vertex + elif destination_vertex in self.adj_list: + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and a list containing + # destination vertex as it's first adjacent vertex. Then create a new vertex + # with destination vertex as key, which has no adjacent vertex + else: + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [] + + return self + + def __repr__(self) -> str: + return pformat(self.adj_list) if __name__ == "__main__": - al = AdjacencyList() - 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 - # 1 -> 0 -> 4 -> 3 -> 2 - # 2 -> 3 - # 3 -> 4 - # 4 -> 1 -> 3 + import doctest + + doctest.testmod() From 0728cf1128486a1c67e7c7efe42628d7a5eb4c1b Mon Sep 17 00:00:00 2001 From: Gaurav Jindal <54955413+jindal2309@users.noreply.github.com> Date: Sun, 17 Jan 2021 23:38:22 -0800 Subject: [PATCH 1409/2908] Added code to merge two trees (#4121) * Added code to merge two trees * Added doctest and type hints * Added pre-commit --- .../binary_tree/merge_two_binary_trees.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 data_structures/binary_tree/merge_two_binary_trees.py diff --git a/data_structures/binary_tree/merge_two_binary_trees.py b/data_structures/binary_tree/merge_two_binary_trees.py new file mode 100644 index 000000000000..6b202adb3cf5 --- /dev/null +++ b/data_structures/binary_tree/merge_two_binary_trees.py @@ -0,0 +1,93 @@ +#!/usr/local/bin/python3 +""" +Problem Description: Given two binary tree, return the merged tree. +The rule for merging is that if two nodes overlap, then put the value sum of +both nodes to the new value of the merged node. Otherwise, the NOT null node +will be used as the node of new tree. +""" +from typing import Optional + + +class Node: + """ + A binary node has value variable and pointers to its left and right node. + """ + + def __init__(self, value: int = 0) -> None: + self.value = value + self.left: Optional[Node] = None + self.right: Optional[Node] = None + + +def merge_two_binary_trees(tree1: Optional[Node], tree2: Optional[Node]) -> Node: + """ + Returns root node of the merged tree. + + >>> tree1 = Node(5) + >>> tree1.left = Node(6) + >>> tree1.right = Node(7) + >>> tree1.left.left = Node(2) + >>> tree2 = Node(4) + >>> tree2.left = Node(5) + >>> tree2.right = Node(8) + >>> tree2.left.right = Node(1) + >>> tree2.right.right = Node(4) + >>> merged_tree = merge_two_binary_trees(tree1, tree2) + >>> print_preorder(merged_tree) + 9 + 11 + 2 + 1 + 15 + 4 + """ + if tree1 is None: + return tree2 + if tree2 is None: + return tree1 + + tree1.value = tree1.value + tree2.value + tree1.left = merge_two_binary_trees(tree1.left, tree2.left) + tree1.right = merge_two_binary_trees(tree1.right, tree2.right) + return tree1 + + +def print_preorder(root: Optional[Node]) -> None: + """ + Print pre-order traversal of the tree. + + >>> root = Node(1) + >>> root.left = Node(2) + >>> root.right = Node(3) + >>> print_preorder(root) + 1 + 2 + 3 + >>> print_preorder(root.right) + 3 + """ + if root: + print(root.value) + print_preorder(root.left) + print_preorder(root.right) + + +if __name__ == "__main__": + tree1 = Node(1) + tree1.left = Node(2) + tree1.right = Node(3) + tree1.left.left = Node(4) + + tree2 = Node(2) + tree2.left = Node(4) + tree2.right = Node(6) + tree2.left.right = Node(9) + tree2.right.right = Node(5) + + print("Tree1 is: ") + print_preorder(tree1) + print("Tree2 is: ") + print_preorder(tree2) + merged_tree = merge_two_binary_trees(tree1, tree2) + print("Merged Tree is: ") + print_preorder(merged_tree) From 7d26ba707559842a8caebe7fb6e68011df593c84 Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Thu, 21 Jan 2021 08:30:47 +0530 Subject: [PATCH 1410/2908] Added diffie-hellman algorithm (#4128) * updating DIRECTORY.md * feat: added diffie-hellman key exchange algorithm * fix: enforce maxline length = 88 * fix: fixed import order * fix: used flake to correct styling * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 +- ciphers/diffie_hellman.py | 271 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 ciphers/diffie_hellman.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 4f17cf9c03ed..d487b39490ed 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -59,6 +59,7 @@ * [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) + * [Diffie Hellman](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie_hellman.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) @@ -224,7 +225,6 @@ * [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) @@ -243,6 +243,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 Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_coin_change.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) diff --git a/ciphers/diffie_hellman.py b/ciphers/diffie_hellman.py new file mode 100644 index 000000000000..ea35b67b483e --- /dev/null +++ b/ciphers/diffie_hellman.py @@ -0,0 +1,271 @@ +from binascii import hexlify +from hashlib import sha256 +from os import urandom + +# RFC 3526 - More Modular Exponential (MODP) Diffie-Hellman groups for +# Internet Key Exchange (IKE) https://tools.ietf.org/html/rfc3526 + +primes = { + # 1536-bit + 5: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 2048-bit + 14: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 3072-bit + 15: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 4096-bit + 16: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + + "FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 6144-bit + 17: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DCC4024FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 8192-bit + 18: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" + + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" + + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" + + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" + + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" + + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" + + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" + + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" + + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, +} + + +class DiffieHellman: + """ + Class to represent the Diffie-Hellman key exchange protocol + + + >>> alice = DiffieHellman() + >>> bob = DiffieHellman() + + >>> alice_private = alice.get_private_key() + >>> alice_public = alice.generate_public_key() + + >>> bob_private = bob.get_private_key() + >>> bob_public = bob.generate_public_key() + + >>> # generating shared key using the DH object + >>> alice_shared = alice.generate_shared_key(bob_public) + >>> bob_shared = bob.generate_shared_key(alice_public) + + >>> assert alice_shared == bob_shared + + >>> # generating shared key using static methods + >>> alice_shared = DiffieHellman.generate_shared_key_static( + ... alice_private, bob_public + ... ) + >>> bob_shared = DiffieHellman.generate_shared_key_static( + ... bob_private, alice_public + ... ) + + >>> assert alice_shared == bob_shared + """ + + # Current minimum recommendation is 2048 bit (group 14) + def __init__(self, group: int = 14) -> None: + if group not in primes: + raise ValueError("Unsupported Group") + self.prime = primes[group]["prime"] + self.generator = primes[group]["generator"] + + self.__private_key = int(hexlify(urandom(32)), base=16) + + def get_private_key(self) -> str: + return hex(self.__private_key)[2:] + + def generate_public_key(self) -> str: + public_key = pow(self.generator, self.__private_key, self.prime) + return hex(public_key)[2:] + + def is_valid_public_key(self, key: int) -> bool: + # check if the other public key is valid based on NIST SP800-56 + if 2 <= key and key <= self.prime - 2: + if pow(key, (self.prime - 1) // 2, self.prime) == 1: + return True + return False + + def generate_shared_key(self, other_key_str: str) -> str: + other_key = int(other_key_str, base=16) + if not self.is_valid_public_key(other_key): + raise ValueError("Invalid public key") + shared_key = pow(other_key, self.__private_key, self.prime) + return sha256(str(shared_key).encode()).hexdigest() + + @staticmethod + def is_valid_public_key_static( + local_private_key_str: str, remote_public_key_str: str, prime: int + ) -> bool: + # check if the other public key is valid based on NIST SP800-56 + if 2 <= remote_public_key_str and remote_public_key_str <= prime - 2: + if pow(remote_public_key_str, (prime - 1) // 2, prime) == 1: + return True + return False + + @staticmethod + def generate_shared_key_static( + local_private_key_str: str, remote_public_key_str: str, group: int = 14 + ) -> str: + local_private_key = int(local_private_key_str, base=16) + remote_public_key = int(remote_public_key_str, base=16) + prime = primes[group]["prime"] + if not DiffieHellman.is_valid_public_key_static( + local_private_key, remote_public_key, prime + ): + raise ValueError("Invalid public key") + shared_key = pow(remote_public_key, local_private_key, prime) + return sha256(str(shared_key).encode()).hexdigest() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d594f4556cdc0c964c76daf5d9c891c32c5823bf Mon Sep 17 00:00:00 2001 From: Steve Kim <54872857+SteveKimSR@users.noreply.github.com> Date: Fri, 22 Jan 2021 13:40:21 +0900 Subject: [PATCH 1411/2908] [mypy] Add/fix type annotations for similarity search in machine learning (#4088) * [mypy] Add/fix type annotations for similarity search in machine learning * fix annotation * fix annotation (Union) * isort --- machine_learning/similarity_search.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py index 6bfb12ed88cb..af845c9109b1 100644 --- a/machine_learning/similarity_search.py +++ b/machine_learning/similarity_search.py @@ -8,6 +8,7 @@ 2. distance between the vector and the nearest vector (float) """ import math +from typing import List, Union import numpy as np @@ -30,7 +31,9 @@ def euclidean(input_a: np.ndarray, input_b: np.ndarray) -> float: 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: +def similarity_search( + dataset: np.ndarray, value_array: np.ndarray +) -> List[List[Union[List[float], float]]]: """ :param dataset: Set containing the vectors. Should be ndarray. :param value_array: vector/vectors we want to know the nearest vector from dataset. From 3f1e376bbc41fc3855bb9562e3e7797342304035 Mon Sep 17 00:00:00 2001 From: ayushbisht2001 <61404154+ayushbisht2001@users.noreply.github.com> Date: Wed, 27 Jan 2021 15:54:57 +0530 Subject: [PATCH 1412/2908] add reverse_bits.py (#4120) * add reverse_bits.py * check * Delete binary_xor_operator_new.py * Fix All the errors Co-authored-by: xcodz-dot --- bit_manipulation/reverse_bits.py | 85 ++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 bit_manipulation/reverse_bits.py diff --git a/bit_manipulation/reverse_bits.py b/bit_manipulation/reverse_bits.py new file mode 100644 index 000000000000..55608ae12908 --- /dev/null +++ b/bit_manipulation/reverse_bits.py @@ -0,0 +1,85 @@ +def get_reverse_bit_string(number: int) -> str: + """ + return the bit string of an integer + + >>> get_reverse_bit_string(9) + '10010000000000000000000000000000' + >>> get_reverse_bit_string(43) + '11010100000000000000000000000000' + >>> get_reverse_bit_string(2873) + '10011100110100000000000000000000' + >>> get_reverse_bit_string("this is not a number") + Traceback (most recent call last): + ... + TypeError: operation can not be conducted on a object of type str + """ + if not isinstance(number, int): + raise TypeError( + "operation can not be conducted on a object of type " + f"{type(number).__name__}" + ) + bit_string = "" + for _ in range(0, 32): + bit_string += str(number % 2) + number = number >> 1 + return bit_string + + +def reverse_bit(number: int) -> str: + """ + Take in an 32 bit integer, reverse its bits, + return a string of reverse bits + + result of a reverse_bit and operation on the integer provided. + + >>> reverse_bit(25) + '00000000000000000000000000011001' + >>> reverse_bit(37) + '00000000000000000000000000100101' + >>> reverse_bit(21) + '00000000000000000000000000010101' + >>> reverse_bit(58) + '00000000000000000000000000111010' + >>> reverse_bit(0) + '00000000000000000000000000000000' + >>> reverse_bit(256) + '00000000000000000000000100000000' + >>> reverse_bit(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must be positive + + >>> reverse_bit(1.1) + Traceback (most recent call last): + ... + TypeError: Input value must be a 'int' type + + >>> reverse_bit("0") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if number < 0: + raise ValueError("the value of input must be positive") + elif isinstance(number, float): + raise TypeError("Input value must be a 'int' type") + elif isinstance(number, str): + raise TypeError("'<' not supported between instances of 'str' and 'int'") + result = 0 + # iterator over [1 to 32],since we are dealing with 32 bit integer + for _ in range(1, 33): + # left shift the bits by unity + result = result << 1 + # get the end bit + end_bit = number % 2 + # right shift the bits by unity + number = number >> 1 + # add that bit to our ans + result = result | end_bit + return get_reverse_bit_string(result) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 97b6ca2b19a3ef92016e586569aa514e53f01067 Mon Sep 17 00:00:00 2001 From: Ayush Raj Date: Thu, 4 Feb 2021 22:28:29 +0530 Subject: [PATCH 1413/2908] [mypy] Add/fix type annotations for boolean_algebra (#4172) * [mypy] Add/fix type annotations for boolean_algebra * [mypy] Add/fix type annotations for boolean_algebra * [mypy] Add/fix annotations for boolean_algebra --- boolean_algebra/quine_mc_cluskey.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 19bac336f6c5..70cdf25a701d 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,10 +1,13 @@ +from typing import List + + def compare_string(string1: str, string2: str) -> str: """ >>> compare_string('0010','0110') '0_10' >>> compare_string('0110','1101') - -1 + 'X' """ l1 = list(string1) l2 = list(string2) @@ -14,12 +17,12 @@ def compare_string(string1: str, string2: str) -> str: count += 1 l1[i] = "_" if count > 1: - return -1 + return "X" else: return "".join(l1) -def check(binary: [str]) -> [str]: +def check(binary: List[str]) -> List[str]: """ >>> check(['0.00.01.5']) ['0.00.01.5'] @@ -31,7 +34,7 @@ def check(binary: [str]) -> [str]: for i in range(len(binary)): for j in range(i + 1, len(binary)): k = compare_string(binary[i], binary[j]) - if k != -1: + if k != "X": check1[i] = "*" check1[j] = "*" temp.append(k) @@ -43,7 +46,7 @@ def check(binary: [str]) -> [str]: binary = list(set(temp)) -def decimal_to_binary(no_of_variable: int, minterms: [float]) -> [str]: +def decimal_to_binary(no_of_variable: int, minterms: List[float]) -> List[str]: """ >>> decimal_to_binary(3,[1.5]) ['0.00.01.5'] @@ -79,7 +82,7 @@ def is_for_table(string1: str, string2: str, count: int) -> bool: return False -def selection(chart: [[int]], prime_implicants: [str]) -> [str]: +def selection(chart: List[List[int]], prime_implicants: List[str]) -> List[str]: """ >>> selection([[1]],['0.00.01.5']) ['0.00.01.5'] @@ -126,7 +129,9 @@ def selection(chart: [[int]], prime_implicants: [str]) -> [str]: chart[j][i] = 0 -def prime_implicant_chart(prime_implicants: [str], binary: [str]) -> [[int]]: +def prime_implicant_chart( + prime_implicants: List[str], binary: List[str] +) -> List[List[int]]: """ >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) [[1]] From 2595cf059d677c39513a9d75f1736bc5b84d6298 Mon Sep 17 00:00:00 2001 From: Hao LI <8520588+Leo-LiHao@users.noreply.github.com> Date: Fri, 5 Feb 2021 00:59:38 +0800 Subject: [PATCH 1414/2908] [mypy] Add/fix type annotations for binary trees in data structures (#4085) * fix mypy: data_structures:binary_tree * mypy --strict for binary_trees in data_structures * fix pre-commit Co-authored-by: LiHao --- .../binary_search_tree_recursive.py | 99 ++++++++++++------- .../binary_tree/lazy_segment_tree.py | 9 +- data_structures/binary_tree/treap.py | 33 ++++--- 3 files changed, 84 insertions(+), 57 deletions(-) diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index f1e46e33cd24..a05e28a7bd54 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -8,21 +8,22 @@ python binary_search_tree_recursive.py """ import unittest +from typing import Iterator, Optional class Node: - def __init__(self, label: int, parent): + def __init__(self, label: int, parent: Optional["Node"]) -> None: self.label = label self.parent = parent - self.left = None - self.right = None + self.left: Optional[Node] = None + self.right: Optional[Node] = None class BinarySearchTree: - def __init__(self): - self.root = None + def __init__(self) -> None: + self.root: Optional[Node] = None - def empty(self): + def empty(self) -> None: """ Empties the tree @@ -46,7 +47,7 @@ def is_empty(self) -> bool: """ return self.root is None - def put(self, label: int): + def put(self, label: int) -> None: """ Put a new node in the tree @@ -65,7 +66,9 @@ def put(self, label: int): """ self.root = self._put(self.root, label) - def _put(self, node: Node, label: int, parent: Node = None) -> Node: + def _put( + self, node: Optional[Node], label: int, parent: Optional[Node] = None + ) -> Node: if node is None: node = Node(label, parent) else: @@ -95,7 +98,7 @@ def search(self, label: int) -> Node: """ return self._search(self.root, label) - def _search(self, node: Node, label: int) -> Node: + def _search(self, node: Optional[Node], label: int) -> Node: if node is None: raise Exception(f"Node with label {label} does not exist") else: @@ -106,7 +109,7 @@ def _search(self, node: Node, label: int) -> Node: return node - def remove(self, label: int): + def remove(self, label: int) -> None: """ Removes a node in the tree @@ -122,13 +125,7 @@ def remove(self, label: int): 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: + if node.right and node.left: lowest_node = self._get_lowest_node(node.right) lowest_node.left = node.left lowest_node.right = node.right @@ -136,8 +133,14 @@ def remove(self, label: int): if node.right: node.right.parent = lowest_node self._reassign_nodes(node, lowest_node) + 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: + self._reassign_nodes(node, None) - def _reassign_nodes(self, node: Node, new_children: Node): + def _reassign_nodes(self, node: Node, new_children: Optional[Node]) -> None: if new_children: new_children.parent = node.parent @@ -192,7 +195,7 @@ def get_max_label(self) -> int: >>> t.get_max_label() 10 """ - if self.is_empty(): + if self.root is None: raise Exception("Binary search tree is empty") node = self.root @@ -216,7 +219,7 @@ def get_min_label(self) -> int: >>> t.get_min_label() 8 """ - if self.is_empty(): + if self.root is None: raise Exception("Binary search tree is empty") node = self.root @@ -225,7 +228,7 @@ def get_min_label(self) -> int: return node.label - def inorder_traversal(self) -> list: + def inorder_traversal(self) -> Iterator[Node]: """ Return the inorder traversal of the tree @@ -241,13 +244,13 @@ def inorder_traversal(self) -> list: """ return self._inorder_traversal(self.root) - def _inorder_traversal(self, node: Node) -> list: + def _inorder_traversal(self, node: Optional[Node]) -> Iterator[Node]: 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: + def preorder_traversal(self) -> Iterator[Node]: """ Return the preorder traversal of the tree @@ -263,7 +266,7 @@ def preorder_traversal(self) -> list: """ return self._preorder_traversal(self.root) - def _preorder_traversal(self, node: Node) -> list: + def _preorder_traversal(self, node: Optional[Node]) -> Iterator[Node]: if node is not None: yield node yield from self._preorder_traversal(node.left) @@ -272,7 +275,7 @@ def _preorder_traversal(self, node: Node) -> list: class BinarySearchTreeTest(unittest.TestCase): @staticmethod - def _get_binary_search_tree(): + def _get_binary_search_tree() -> BinarySearchTree: r""" 8 / \ @@ -298,7 +301,7 @@ def _get_binary_search_tree(): return t - def test_put(self): + def test_put(self) -> None: t = BinarySearchTree() assert t.is_empty() @@ -306,6 +309,7 @@ def test_put(self): r""" 8 """ + assert t.root is not None assert t.root.parent is None assert t.root.label == 8 @@ -315,6 +319,7 @@ def test_put(self): \ 10 """ + assert t.root.right is not None assert t.root.right.parent == t.root assert t.root.right.label == 10 @@ -324,6 +329,7 @@ def test_put(self): / \ 3 10 """ + assert t.root.left is not None assert t.root.left.parent == t.root assert t.root.left.label == 3 @@ -335,6 +341,7 @@ def test_put(self): \ 6 """ + assert t.root.left.right is not None assert t.root.left.right.parent == t.root.left assert t.root.left.right.label == 6 @@ -346,13 +353,14 @@ def test_put(self): / \ 1 6 """ + assert t.root.left.left is not None 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): + def test_search(self) -> None: t = self._get_binary_search_tree() node = t.search(6) @@ -364,7 +372,7 @@ def test_search(self): with self.assertRaises(Exception): t.search(2) - def test_remove(self): + def test_remove(self) -> None: t = self._get_binary_search_tree() t.remove(13) @@ -379,6 +387,9 @@ def test_remove(self): \ 5 """ + assert t.root is not None + assert t.root.right is not None + assert t.root.right.right is not None assert t.root.right.right.right is None assert t.root.right.right.left is None @@ -394,6 +405,9 @@ def test_remove(self): \ 5 """ + assert t.root.left is not None + assert t.root.left.right is not None + assert t.root.left.right.left is not None assert t.root.left.right.right is None assert t.root.left.right.left.label == 4 @@ -407,6 +421,8 @@ def test_remove(self): \ 5 """ + assert t.root.left.left is not None + assert t.root.left.right.right is not None assert t.root.left.left.label == 1 assert t.root.left.right.label == 4 assert t.root.left.right.right.label == 5 @@ -422,6 +438,7 @@ def test_remove(self): / \ \ 1 5 14 """ + assert t.root is not None assert t.root.left.label == 4 assert t.root.left.right.label == 5 assert t.root.left.left.label == 1 @@ -437,13 +454,15 @@ def test_remove(self): / \ 1 14 """ + assert t.root.left is not None + assert t.root.left.left is not None 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): + def test_remove_2(self) -> None: t = self._get_binary_search_tree() t.remove(3) @@ -456,6 +475,12 @@ def test_remove_2(self): / \ / 5 7 13 """ + assert t.root is not None + assert t.root.left is not None + assert t.root.left.left is not None + assert t.root.left.right is not None + assert t.root.left.right.left is not None + assert t.root.left.right.right is not None assert t.root.left.label == 4 assert t.root.left.right.label == 6 assert t.root.left.left.label == 1 @@ -466,25 +491,25 @@ def test_remove_2(self): assert t.root.left.left.parent == t.root.left assert t.root.left.right.left.parent == t.root.left.right - def test_empty(self): + def test_empty(self) -> None: t = self._get_binary_search_tree() t.empty() assert t.root is None - def test_is_empty(self): + def test_is_empty(self) -> None: t = self._get_binary_search_tree() assert not t.is_empty() t.empty() assert t.is_empty() - def test_exists(self): + def test_exists(self) -> None: t = self._get_binary_search_tree() assert t.exists(6) assert not t.exists(-1) - def test_get_max_label(self): + def test_get_max_label(self) -> None: t = self._get_binary_search_tree() assert t.get_max_label() == 14 @@ -493,7 +518,7 @@ def test_get_max_label(self): with self.assertRaises(Exception): t.get_max_label() - def test_get_min_label(self): + def test_get_min_label(self) -> None: t = self._get_binary_search_tree() assert t.get_min_label() == 1 @@ -502,20 +527,20 @@ def test_get_min_label(self): with self.assertRaises(Exception): t.get_min_label() - def test_inorder_traversal(self): + def test_inorder_traversal(self) -> None: 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): + def test_preorder_traversal(self) -> None: 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(): +def binary_search_tree_example() -> None: r""" Example 8 diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 5bc79e74efcd..9066db294613 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -1,6 +1,7 @@ from __future__ import annotations import math +from typing import List, Union class SegmentTree: @@ -37,7 +38,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] @@ -88,7 +89,7 @@ def update( # query with O(lg n) def query( self, idx: int, left_element: int, right_element: int, a: int, b: int - ) -> int: + ) -> Union[int, float]: """ 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] @@ -118,8 +119,8 @@ def query( q2 = self.query(self.right(idx), mid + 1, right_element, a, b) return max(q1, q2) - def __str__(self) -> None: - return [self.query(1, 1, self.size, i, i) for i in range(1, self.size + 1)] + def __str__(self) -> str: + return str([self.query(1, 1, self.size, i, i) for i in range(1, self.size + 1)]) if __name__ == "__main__": diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 26648f7aba61..a09dcc928143 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -3,6 +3,7 @@ from __future__ import annotations from random import random +from typing import Optional, Tuple class Node: @@ -11,13 +12,13 @@ class Node: Treap is a binary tree by value and heap by priority """ - def __init__(self, value: int = None): + def __init__(self, value: Optional[int] = None): self.value = value self.prior = random() - self.left = None - self.right = None + self.left: Optional[Node] = None + self.right: Optional[Node] = None - def __repr__(self): + def __repr__(self) -> str: from pprint import pformat if self.left is None and self.right is None: @@ -27,14 +28,14 @@ def __repr__(self): {f"{self.value}: {self.prior:.5}": (self.left, self.right)}, indent=1 ) - def __str__(self): + def __str__(self) -> str: 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]: +def split(root: Optional[Node], value: int) -> Tuple[Optional[Node], Optional[Node]]: """ We split current tree into 2 trees with value: @@ -42,9 +43,9 @@ def split(root: Node, value: int) -> tuple[Node, Node]: 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) + return None, None elif root.value is None: - return (None, None) + return None, None else: if value < root.value: """ @@ -54,16 +55,16 @@ def split(root: Node, value: int) -> tuple[Node, Node]: Right tree's left son: right part of that split """ left, root.left = split(root.left, value) - return (left, root) + return left, root else: """ Just symmetric to previous case """ root.right, right = split(root.right, value) - return (root, right) + return root, right -def merge(left: Node, right: Node) -> Node: +def merge(left: Optional[Node], right: Optional[Node]) -> Optional[Node]: """ We merge 2 trees into one. Note: all left tree's values must be less than all right tree's @@ -85,7 +86,7 @@ def merge(left: Node, right: Node) -> Node: return right -def insert(root: Node, value: int) -> Node: +def insert(root: Optional[Node], value: int) -> Optional[Node]: """ Insert element @@ -98,7 +99,7 @@ def insert(root: Node, value: int) -> Node: return merge(merge(left, node), right) -def erase(root: Node, value: int) -> Node: +def erase(root: Optional[Node], value: int) -> Optional[Node]: """ Erase element @@ -111,7 +112,7 @@ def erase(root: Node, value: int) -> Node: return merge(left, right) -def inorder(root: Node): +def inorder(root: Optional[Node]) -> None: """ Just recursive print of a tree """ @@ -123,7 +124,7 @@ def inorder(root: Node): inorder(root.right) -def interactTreap(root, args): +def interactTreap(root: Optional[Node], args: str) -> Optional[Node]: """ Commands: + value to add value into treap @@ -160,7 +161,7 @@ def interactTreap(root, args): return root -def main(): +def main() -> None: """After each command, program prints treap""" root = None print( From 4903a657799738bb16d43a763e055e3ef7e68e23 Mon Sep 17 00:00:00 2001 From: Abdeldjaouad Nusayr Medakene <31663979+MrGeek1337@users.noreply.github.com> Date: Tue, 9 Feb 2021 17:13:48 +0100 Subject: [PATCH 1415/2908] Create slowsort.py (#3865) * Create slowsort.py added slowsort algorithm implementation to sorts * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py * Update slowsort.py --- sorts/slowsort.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 sorts/slowsort.py diff --git a/sorts/slowsort.py b/sorts/slowsort.py new file mode 100644 index 000000000000..53bb14554ee2 --- /dev/null +++ b/sorts/slowsort.py @@ -0,0 +1,63 @@ +""" +Slowsort is a sorting algorithm. It is of humorous nature and not useful. +It's based on the principle of multiply and surrender, +a tongue-in-cheek joke of divide and conquer. +It was published in 1986 by Andrei Broder and Jorge Stolfi +in their paper Pessimal Algorithms and Simplexity Analysis +(a parody of optimal algorithms and complexity analysis). + +Source: https://en.wikipedia.org/wiki/Slowsort +""" + +from typing import Optional + + +def slowsort( + sequence: list, start: Optional[int] = None, end: Optional[int] = None +) -> None: + """ + Sorts sequence[start..end] (both inclusive) in-place. + start defaults to 0 if not given. + end defaults to len(sequence) - 1 if not given. + It returns None. + >>> seq = [1, 6, 2, 5, 3, 4, 4, 5]; slowsort(seq); seq + [1, 2, 3, 4, 4, 5, 5, 6] + >>> seq = []; slowsort(seq); seq + [] + >>> seq = [2]; slowsort(seq); seq + [2] + >>> seq = [1, 2, 3, 4]; slowsort(seq); seq + [1, 2, 3, 4] + >>> seq = [4, 3, 2, 1]; slowsort(seq); seq + [1, 2, 3, 4] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, 2, 7); seq + [9, 8, 2, 3, 4, 5, 6, 7, 1, 0] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, end = 4); seq + [5, 6, 7, 8, 9, 4, 3, 2, 1, 0] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, start = 5); seq + [9, 8, 7, 6, 5, 0, 1, 2, 3, 4] + """ + if start is None: + start = 0 + + if end is None: + end = len(sequence) - 1 + + if start >= end: + return + + mid = (start + end) // 2 + + slowsort(sequence, start, mid) + slowsort(sequence, mid + 1, end) + + if sequence[end] < sequence[mid]: + sequence[end], sequence[mid] = sequence[mid], sequence[end] + + slowsort(sequence, start, end - 1) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From f66325a01bd790107c535f922aa0d9cfd75935e3 Mon Sep 17 00:00:00 2001 From: Ocean Monjur <75680423+OCM-7898@users.noreply.github.com> Date: Thu, 11 Feb 2021 22:49:53 +0600 Subject: [PATCH 1416/2908] odd_even_sort.py (#4199) * odd_even_sort.py * Update odd_even_sort.py * Update odd_even_sort.py --- sorts/odd_even_sort.py | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 sorts/odd_even_sort.py diff --git a/sorts/odd_even_sort.py b/sorts/odd_even_sort.py new file mode 100644 index 000000000000..557337ee77bc --- /dev/null +++ b/sorts/odd_even_sort.py @@ -0,0 +1,47 @@ +"""For reference +https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort +""" + + +def odd_even_sort(input_list: list) -> list: + """this algorithm uses the same idea of bubblesort, + but by first dividing in two phase (odd and even). + Originally developed for use on parallel processors + with local interconnections. + :param collection: mutable ordered sequence of elements + :return: same collection in ascending order + Examples: + >>> odd_even_sort([5 , 4 ,3 ,2 ,1]) + [1, 2, 3, 4, 5] + >>> odd_even_sort([]) + [] + >>> odd_even_sort([-10 ,-1 ,10 ,2]) + [-10, -1, 2, 10] + >>> odd_even_sort([1 ,2 ,3 ,4]) + [1, 2, 3, 4] + """ + sorted = False + while sorted is False: # Until all the indices are traversed keep looping + sorted = True + for i in range(0, len(input_list) - 1, 2): # iterating over all even indices + if input_list[i] > input_list[i + 1]: + + input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] + # swapping if elements not in order + sorted = False + + for i in range(1, len(input_list) - 1, 2): # iterating over all odd indices + if input_list[i] > input_list[i + 1]: + input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] + # swapping if elements not in order + sorted = False + return input_list + + +if __name__ == "__main__": + print("Enter list to be sorted") + input_list = [int(x) for x in input().split()] + # inputing elements of the list in one line + sorted_list = odd_even_sort(input_list) + print("The sorted list is") + print(sorted_list) From d3ac521b63aad890b1fdcb9ae7321b658d5c4764 Mon Sep 17 00:00:00 2001 From: Ayush Bisht <61404154+ayushbisht2001@users.noreply.github.com> Date: Fri, 12 Feb 2021 07:59:24 +0530 Subject: [PATCH 1417/2908] add count_number_of_one_bits.py (#4195) * count-bits * update --- bit_manipulation/count_number_of_one_bits.py | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 bit_manipulation/count_number_of_one_bits.py diff --git a/bit_manipulation/count_number_of_one_bits.py b/bit_manipulation/count_number_of_one_bits.py new file mode 100644 index 000000000000..51fd2b630483 --- /dev/null +++ b/bit_manipulation/count_number_of_one_bits.py @@ -0,0 +1,34 @@ +def get_set_bits_count(number: int) -> int: + """ + Count the number of set bits in a 32 bit integer + >>> get_set_bits_count(25) + 3 + >>> get_set_bits_count(37) + 3 + >>> get_set_bits_count(21) + 3 + >>> get_set_bits_count(58) + 4 + >>> get_set_bits_count(0) + 0 + >>> get_set_bits_count(256) + 1 + >>> get_set_bits_count(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must be positive + """ + if number < 0: + raise ValueError("the value of input must be positive") + result = 0 + while number: + if number % 2 == 1: + result += 1 + number = number >> 1 + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 78ddb460660bc2c3b14f1b7197aa52fe0e9ac542 Mon Sep 17 00:00:00 2001 From: MarineJoker Date: Wed, 17 Feb 2021 22:28:50 +0800 Subject: [PATCH 1418/2908] Quick sort with lomuto partition (#3875) * add quick sort algorithm with Lomuto partition * fix(lomuto_partition): fix snake_case --- sorts/quick_sort_3_partition.py | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py index 18c6e0f876d2..1a6db6a364f0 100644 --- a/sorts/quick_sort_3_partition.py +++ b/sorts/quick_sort_3_partition.py @@ -18,6 +18,53 @@ def quick_sort_3partition(sorting: list, left: int, right: int) -> None: quick_sort_3partition(sorting, b + 1, right) +def quick_sort_lomuto_partition(sorting: list, left: int, right: int) -> None: + """ + A pure Python implementation of quick sort algorithm(in-place) + with Lomuto partition scheme: + https://en.wikipedia.org/wiki/Quicksort#Lomuto_partition_scheme + + :param sorting: sort list + :param left: left endpoint of sorting + :param right: right endpoint of sorting + :return: None + + Examples: + >>> nums1 = [0, 5, 3, 1, 2] + >>> quick_sort_lomuto_partition(nums1, 0, 4) + >>> nums1 + [0, 1, 2, 3, 5] + >>> nums2 = [] + >>> quick_sort_lomuto_partition(nums2, 0, 0) + >>> nums2 + [] + >>> nums3 = [-2, 5, 0, -4] + >>> quick_sort_lomuto_partition(nums3, 0, 3) + >>> nums3 + [-4, -2, 0, 5] + """ + if left < right: + pivot_index = lomuto_partition(sorting, left, right) + quick_sort_lomuto_partition(sorting, left, pivot_index - 1) + quick_sort_lomuto_partition(sorting, pivot_index + 1, right) + + +def lomuto_partition(sorting: list, left: int, right: int) -> int: + """ + Example: + >>> lomuto_partition([1,5,7,6], 0, 3) + 2 + """ + pivot = sorting[right] + store_index = left + for i in range(left, right): + if sorting[i] < pivot: + sorting[store_index], sorting[i] = sorting[i], sorting[store_index] + store_index += 1 + sorting[right], sorting[store_index] = sorting[store_index], sorting[right] + return store_index + + def three_way_radix_quicksort(sorting: list) -> list: """ Three-way radix quicksort: From bfb5700c67d629140070212ad58457ea34e769b9 Mon Sep 17 00:00:00 2001 From: Liu Baolin--CN Date: Sat, 20 Feb 2021 02:15:19 +0800 Subject: [PATCH 1419/2908] Update LICENSE.md (#4210) --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index 3b7951527ab3..c3c2857cd312 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 The Algorithms +Copyright (c) 2016-2021 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 6bb9a027bb4368d53d6bb3716007f67bdc8748f6 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Sat, 20 Feb 2021 18:39:39 +0530 Subject: [PATCH 1420/2908] Implementation of the algorithm for the Koch snowflake (#4207) * Add files via upload Implementation of the algorithm for the Koch snowflake * added underscore to variable names * added newline and comment I fixed the sorting of the imports and I added a comment to the plot-function to explain what it does and why it doesn't use a doctest. Thank you to user mrmaxguns for suggesting these changes. * fixed accidental newline in the middle of expression * improved looping * moved "koch_snowflake.py" from "other" to "graphics" * Update koch_snowflake.py Co-authored-by: Christian Clauss --- graphics/koch_snowflake.py | 116 +++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 graphics/koch_snowflake.py diff --git a/graphics/koch_snowflake.py b/graphics/koch_snowflake.py new file mode 100644 index 000000000000..07c1835b41ed --- /dev/null +++ b/graphics/koch_snowflake.py @@ -0,0 +1,116 @@ +""" +Description + The Koch snowflake is a fractal curve and one of the earliest fractals to + have been described. The Koch snowflake can be built up iteratively, in a + sequence of stages. The first stage is an equilateral triangle, and each + successive stage is formed by adding outward bends to each side of the + previous stage, making smaller equilateral triangles. + This can be achieved through the following steps for each line: + 1. divide the line segment into three segments of equal length. + 2. draw an equilateral triangle that has the middle segment from step 1 + as its base and points outward. + 3. remove the line segment that is the base of the triangle from step 2. + (description adapted from https://en.wikipedia.org/wiki/Koch_snowflake ) + (for a more detailed explanation and an implementation in the + Processing language, see https://natureofcode.com/book/chapter-8-fractals/ + #84-the-koch-curve-and-the-arraylist-technique ) + +Requirements (pip): + - matplotlib + - numpy +""" + + +from __future__ import annotations + +import matplotlib.pyplot as plt # type: ignore +import numpy + +# initial triangle of Koch snowflake +VECTOR_1 = numpy.array([0, 0]) +VECTOR_2 = numpy.array([0.5, 0.8660254]) +VECTOR_3 = numpy.array([1, 0]) +INITIAL_VECTORS = [VECTOR_1, VECTOR_2, VECTOR_3, VECTOR_1] + +# uncomment for simple Koch curve instead of Koch snowflake +# INITIAL_VECTORS = [VECTOR_1, VECTOR_3] + + +def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndarray]: + """ + Go through the number of iterations determined by the argument "steps". + Be careful with high values (above 5) since the time to calculate increases + exponentially. + >>> iterate([numpy.array([0, 0]), numpy.array([1, 0])], 1) + [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \ +0.28867513]), array([0.66666667, 0. ]), array([1, 0])] + """ + vectors = initial_vectors + for i in range(steps): + vectors = iteration_step(vectors) + return vectors + + +def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]: + """ + Loops through each pair of adjacent vectors. Each line between two adjacent + vectors is divided into 4 segments by adding 3 additional vectors in-between + the original two vectors. The vector in the middle is constructed through a + 60 degree rotation so it is bent outwards. + >>> iteration_step([numpy.array([0, 0]), numpy.array([1, 0])]) + [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \ +0.28867513]), array([0.66666667, 0. ]), array([1, 0])] + """ + new_vectors = [] + for i, start_vector in enumerate(vectors[:-1]): + end_vector = vectors[i + 1] + new_vectors.append(start_vector) + difference_vector = end_vector - start_vector + new_vectors.append(start_vector + difference_vector / 3) + new_vectors.append( + start_vector + difference_vector / 3 + rotate(difference_vector / 3, 60) + ) + new_vectors.append(start_vector + difference_vector * 2 / 3) + new_vectors.append(vectors[-1]) + return new_vectors + + +def rotate(vector: numpy.ndarray, angle_in_degrees: float) -> numpy.ndarray: + """ + Standard rotation of a 2D vector with a rotation matrix + (see https://en.wikipedia.org/wiki/Rotation_matrix ) + >>> rotate(numpy.array([1, 0]), 60) + array([0.5 , 0.8660254]) + >>> rotate(numpy.array([1, 0]), 90) + array([6.123234e-17, 1.000000e+00]) + """ + theta = numpy.radians(angle_in_degrees) + c, s = numpy.cos(theta), numpy.sin(theta) + rotation_matrix = numpy.array(((c, -s), (s, c))) + return numpy.dot(rotation_matrix, vector) + + +def plot(vectors: list[numpy.ndarray]) -> None: + """ + Utility function to plot the vectors using matplotlib.pyplot + No doctest was implemented since this function does not have a return value + """ + # avoid stretched display of graph + axes = plt.gca() + axes.set_aspect("equal") + + # matplotlib.pyplot.plot takes a list of all x-coordinates and a list of all + # y-coordinates as inputs, which are constructed from the vector-list using + # zip() + x_coordinates, y_coordinates = zip(*vectors) + plt.plot(x_coordinates, y_coordinates) + plt.show() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + processed_vectors = iterate(INITIAL_VECTORS, 5) + plot(processed_vectors) From 81c46dfd55ad9c7326a0d0d231d2cf0caa691d34 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 20 Feb 2021 22:10:23 +0000 Subject: [PATCH 1421/2908] [mypy] Add/fix type annotations for quick_sort(#4085) (#4215) Co-authored-by: goodm2 <4qjpngu8mem8cz> --- sorts/quick_sort.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index c6687a7fa8d5..6f51f6eca7db 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -7,6 +7,7 @@ For manual testing run: python3 quick_sort.py """ +from typing import List def quick_sort(collection: list) -> list: @@ -26,8 +27,8 @@ def quick_sort(collection: list) -> list: if len(collection) < 2: return collection 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 + greater: List[int] = [] # All elements greater than pivot + lesser: List[int] = [] # 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) From 2a6e4bbdb6593767d9c790a3cd35dcc6fed65739 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Mon, 22 Feb 2021 05:24:29 +0530 Subject: [PATCH 1422/2908] [mypy] Add/fix type annotations for "conways_game_of_life.py" & "one_dimensional.py" (#4216) Related Issue: #4052 --- cellular_automata/conways_game_of_life.py | 2 +- cellular_automata/one_dimensional.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cellular_automata/conways_game_of_life.py b/cellular_automata/conways_game_of_life.py index 321baa3a3794..dc349b7ac507 100644 --- a/cellular_automata/conways_game_of_life.py +++ b/cellular_automata/conways_game_of_life.py @@ -7,7 +7,7 @@ from typing import List -from PIL import Image +from PIL import Image # type: ignore # Define glider example GLIDER = [ diff --git a/cellular_automata/one_dimensional.py b/cellular_automata/one_dimensional.py index da77e444502f..5de2c5b994e3 100644 --- a/cellular_automata/one_dimensional.py +++ b/cellular_automata/one_dimensional.py @@ -6,7 +6,7 @@ from __future__ import annotations -from PIL import Image +from PIL import Image # type: ignore # Define the first generation of cells # fmt: off From f680806894d39265f810e7257d50aa0beaf2152e Mon Sep 17 00:00:00 2001 From: Hao LI <8520588+Leo-LiHao@users.noreply.github.com> Date: Mon, 22 Feb 2021 07:58:17 +0800 Subject: [PATCH 1423/2908] add type hints for avl_tree (#4214) Co-authored-by: LiHao --- data_structures/binary_tree/avl_tree.py | 155 +++++++++++++----------- 1 file changed, 87 insertions(+), 68 deletions(-) diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 3362610b9303..e0d3e4d438a8 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -8,84 +8,85 @@ import math import random +from typing import Any, List, Optional class my_queue: - def __init__(self): - self.data = [] - self.head = 0 - self.tail = 0 + def __init__(self) -> None: + self.data: List[Any] = [] + self.head: int = 0 + self.tail: int = 0 - def is_empty(self): + def is_empty(self) -> bool: return self.head == self.tail - def push(self, data): + def push(self, data: Any) -> None: self.data.append(data) self.tail = self.tail + 1 - def pop(self): + def pop(self) -> Any: ret = self.data[self.head] self.head = self.head + 1 return ret - def count(self): + def count(self) -> int: return self.tail - self.head - def print(self): + def print(self) -> None: print(self.data) print("**************") print(self.data[self.head : self.tail]) class my_node: - def __init__(self, data): + def __init__(self, data: Any) -> None: self.data = data - self.left = None - self.right = None - self.height = 1 + self.left: Optional[my_node] = None + self.right: Optional[my_node] = None + self.height: int = 1 - def get_data(self): + def get_data(self) -> Any: return self.data - def get_left(self): + def get_left(self) -> Optional["my_node"]: return self.left - def get_right(self): + def get_right(self) -> Optional["my_node"]: return self.right - def get_height(self): + def get_height(self) -> int: return self.height - def set_data(self, data): + def set_data(self, data: Any) -> None: self.data = data return - def set_left(self, node): + def set_left(self, node: Optional["my_node"]) -> None: self.left = node return - def set_right(self, node): + def set_right(self, node: Optional["my_node"]) -> None: self.right = node return - def set_height(self, height): + def set_height(self, height: int) -> None: self.height = height return -def get_height(node): +def get_height(node: Optional["my_node"]) -> int: if node is None: return 0 return node.get_height() -def my_max(a, b): +def my_max(a: int, b: int) -> int: if a > b: return a return b -def right_rotation(node): +def right_rotation(node: my_node) -> my_node: r""" A B / \ / \ @@ -98,6 +99,7 @@ def right_rotation(node): """ print("left rotation node:", node.get_data()) ret = node.get_left() + assert ret is not None node.set_left(ret.get_right()) ret.set_right(node) h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 @@ -107,12 +109,13 @@ def right_rotation(node): return ret -def left_rotation(node): +def left_rotation(node: my_node) -> my_node: """ a mirror symmetry rotation of the left_rotation """ print("right rotation node:", node.get_data()) ret = node.get_right() + assert ret is not None node.set_right(ret.get_left()) ret.set_left(node) h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 @@ -122,7 +125,7 @@ def left_rotation(node): return ret -def lr_rotation(node): +def lr_rotation(node: my_node) -> my_node: r""" A A Br / \ / \ / \ @@ -133,16 +136,20 @@ def lr_rotation(node): UB Bl RR = right_rotation LR = left_rotation """ - node.set_left(left_rotation(node.get_left())) + left_child = node.get_left() + assert left_child is not None + node.set_left(left_rotation(left_child)) return right_rotation(node) -def rl_rotation(node): - node.set_right(right_rotation(node.get_right())) +def rl_rotation(node: my_node) -> my_node: + right_child = node.get_right() + assert right_child is not None + node.set_right(right_rotation(right_child)) return left_rotation(node) -def insert_node(node, data): +def insert_node(node: Optional["my_node"], data: Any) -> Optional["my_node"]: if node is None: return my_node(data) if data < node.get_data(): @@ -150,8 +157,10 @@ def insert_node(node, data): if ( get_height(node.get_left()) - get_height(node.get_right()) == 2 ): # an unbalance detected + left_child = node.get_left() + assert left_child is not None if ( - data < node.get_left().get_data() + data < left_child.get_data() ): # new node is the left child of the left child node = right_rotation(node) else: @@ -159,7 +168,9 @@ def insert_node(node, data): else: 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(): + right_child = node.get_right() + assert right_child is not None + if data < right_child.get_data(): node = rl_rotation(node) else: node = left_rotation(node) @@ -168,52 +179,59 @@ def insert_node(node, data): return node -def get_rightMost(root): - while root.get_right() is not None: - root = root.get_right() +def get_rightMost(root: my_node) -> Any: + while True: + right_child = root.get_right() + if right_child is None: + break + root = right_child return root.get_data() -def get_leftMost(root): - while root.get_left() is not None: - root = root.get_left() +def get_leftMost(root: my_node) -> Any: + while True: + left_child = root.get_left() + if left_child is None: + break + root = left_child return root.get_data() -def del_node(root, data): +def del_node(root: my_node, data: Any) -> Optional["my_node"]: + left_child = root.get_left() + right_child = root.get_right() 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()) + if left_child is not None and right_child is not None: + temp_data = get_leftMost(right_child) 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() + root.set_right(del_node(right_child, temp_data)) + elif left_child is not None: + root = left_child + elif right_child is not None: + root = right_child else: - root = root.get_right() + return None elif root.get_data() > data: - if root.get_left() is None: + if left_child is None: print("No such data") return root else: - root.set_left(del_node(root.get_left(), data)) - elif root.get_data() < data: - if root.get_right() is None: + root.set_left(del_node(left_child, data)) + else: # root.get_data() < data + if right_child is None: return root else: - root.set_right(del_node(root.get_right(), 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() - ): + root.set_right(del_node(right_child, data)) + + if get_height(right_child) - get_height(left_child) == 2: + assert right_child is not None + if get_height(right_child.get_right()) > get_height(right_child.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() - ): + elif get_height(right_child) - get_height(left_child) == -2: + assert left_child is not None + if get_height(left_child.get_left()) > get_height(left_child.get_right()): root = right_rotation(root) else: root = lr_rotation(root) @@ -256,25 +274,26 @@ class AVLtree: ************************************* """ - def __init__(self): - self.root = None + def __init__(self) -> None: + self.root: Optional[my_node] = None - def get_height(self): - # print("yyy") + def get_height(self) -> int: return get_height(self.root) - def insert(self, data): + def insert(self, data: Any) -> None: print("insert:" + str(data)) self.root = insert_node(self.root, data) - def del_node(self, data): + def del_node(self, data: Any) -> None: print("delete:" + str(data)) if self.root is None: print("Tree is empty!") return self.root = del_node(self.root, data) - def __str__(self): # a level traversale, gives a more intuitive look on the tree + def __str__( + self, + ) -> str: # a level traversale, gives a more intuitive look on the tree output = "" q = my_queue() q.push(self.root) @@ -308,7 +327,7 @@ def __str__(self): # a level traversale, gives a more intuitive look on the tre return output -def _test(): +def _test() -> None: import doctest doctest.testmod() From 61f3119467584de53a2f4395e3c03a8e12d67d30 Mon Sep 17 00:00:00 2001 From: CarsonHam Date: Mon, 22 Feb 2021 23:53:49 -0600 Subject: [PATCH 1424/2908] Change occurrences of str.format to f-strings (#4118) * f-string update rsa_cipher.py * f-string update rsa_key_generator.py * f-string update burrows_wheeler.py * f-string update non_recursive_segment_tree.py * f-string update red_black_tree.py * f-string update deque_doubly.py * f-string update climbing_stairs.py * f-string update iterating_through_submasks.py * f-string update knn_sklearn.py * f-string update 3n_plus_1.py * f-string update quadratic_equations_complex_numbers.py * f-string update nth_fibonacci_using_matrix_exponentiation.py * f-string update sherman_morrison.py * f-string update levenshtein_distance.py * fix lines that were too long --- ciphers/rsa_cipher.py | 2 +- ciphers/rsa_key_generator.py | 4 ++-- compression/burrows_wheeler.py | 13 +++++++------ .../binary_tree/non_recursive_segment_tree.py | 2 +- data_structures/binary_tree/red_black_tree.py | 8 +++++--- data_structures/linked_list/deque_doubly.py | 4 ++-- dynamic_programming/climbing_stairs.py | 5 +++-- dynamic_programming/iterating_through_submasks.py | 5 +++-- machine_learning/knn_sklearn.py | 4 ++-- maths/3n_plus_1.py | 2 +- maths/quadratic_equations_complex_numbers.py | 4 ++-- matrix/nth_fibonacci_using_matrix_exponentiation.py | 10 +++++----- matrix/sherman_morrison.py | 6 ++---- strings/levenshtein_distance.py | 6 +----- 14 files changed, 37 insertions(+), 38 deletions(-) diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 57c916a44d4b..0df37d6ea3ff 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -118,7 +118,7 @@ def encryptAndWriteToFile( for i in range(len(encryptedBlocks)): encryptedBlocks[i] = str(encryptedBlocks[i]) encryptedContent = ",".join(encryptedBlocks) - encryptedContent = "{}_{}_{}".format(len(message), blockSize, encryptedContent) + encryptedContent = f"{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 5693aa637ee9..e456d9d9f6f1 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -49,11 +49,11 @@ def makeKeyFiles(name: int, keySize: int) -> None: publicKey, privateKey = generateKey(keySize) print("\nWriting public key to file %s_pubkey.txt..." % name) with open("%s_pubkey.txt" % name, "w") as out_file: - out_file.write("{},{},{}".format(keySize, publicKey[0], publicKey[1])) + out_file.write(f"{keySize},{publicKey[0]},{publicKey[1]}") print("Writing private key to file %s_privkey.txt..." % name) with open("%s_privkey.txt" % name, "w") as out_file: - out_file.write("{},{},{}".format(keySize, privateKey[0], privateKey[1])) + out_file.write(f"{keySize},{privateKey[0]},{privateKey[1]}") if __name__ == "__main__": diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 1a6610915e65..7d705af7428e 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -157,11 +157,12 @@ 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 transform for string '{}' results in '{}'" - print(bwt_output_msg.format(s, result["bwt_string"])) + print( + f"Burrows Wheeler transform for string '{s}' results " + f"in '{result['bwt_string']}'" + ) original_string = reverse_bwt(result["bwt_string"], result["idx_original_string"]) - fmt = ( - "Reversing Burrows Wheeler transform for entry '{}' we get original" - " string '{}'" + print( + f"Reversing Burrows Wheeler transform for entry '{result['bwt_string']}' " + f"we get original string '{original_string}'" ) - print(fmt.format(result["bwt_string"], original_string)) diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 064e5aded7b4..c914079e0a8d 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -49,7 +49,7 @@ def __init__(self, arr: list[T], fnc: Callable[[T, T], T]) -> None: :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) + >>> SegmentTree(['a', 'b', 'c'], lambda a, b: f'{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) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 5d721edfa45b..de971a712fc1 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -475,11 +475,13 @@ def __repr__(self) -> str: from pprint import pformat if self.left is None and self.right is None: - return "'{} {}'".format(self.label, (self.color and "red") or "blk") + return f"'{self.label} {(self.color and 'red') or 'blk'}'" return pformat( { - "%s %s" - % (self.label, (self.color and "red") or "blk"): (self.left, self.right) + f"{self.label} {(self.color and 'red') or 'blk'}": ( + self.left, + self.right, + ) }, indent=1, ) diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index 894f91d561cc..c9ae8b3d1ba2 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -20,8 +20,8 @@ def __init__(self, link_p, element, link_n): self._next = link_n def has_next_and_prev(self): - return " Prev -> {}, Next -> {}".format( - self._prev is not None, self._next is not None + return ( + f" Prev -> {self._prev is not None}, Next -> {self._next is not None}" ) def __init__(self): diff --git a/dynamic_programming/climbing_stairs.py b/dynamic_programming/climbing_stairs.py index 79605261f981..048d57aed1be 100644 --- a/dynamic_programming/climbing_stairs.py +++ b/dynamic_programming/climbing_stairs.py @@ -25,8 +25,9 @@ def climb_stairs(n: int) -> int: ... 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) + assert ( + isinstance(n, int) and n > 0 + ), f"n needs to be positive integer, your input {n}" if n == 1: return 1 dp = [0] * (n + 1) diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index 855af61d6707..21c64dba4ecc 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -37,8 +37,9 @@ def list_of_submasks(mask: int) -> list[int]: """ - fmt = "mask needs to be positive integer, your input {}" - assert isinstance(mask, int) and mask > 0, fmt.format(mask) + assert ( + isinstance(mask, int) and mask > 0 + ), f"mask needs to be positive integer, your input {mask}" """ first submask iterated will be mask itself then operation will be performed diff --git a/machine_learning/knn_sklearn.py b/machine_learning/knn_sklearn.py index 9a9114102ff3..4a621a4244b6 100644 --- a/machine_learning/knn_sklearn.py +++ b/machine_learning/knn_sklearn.py @@ -26,6 +26,6 @@ prediction = knn.predict(X_new) print( - "\nNew array: \n {}" - "\n\nTarget Names Prediction: \n {}".format(X_new, iris["target_names"][prediction]) + f"\nNew array: \n {X_new}\n\nTarget Names Prediction: \n" + f" {iris['target_names'][prediction]}" ) diff --git a/maths/3n_plus_1.py b/maths/3n_plus_1.py index 28c9fd7b426f..e455a158e619 100644 --- a/maths/3n_plus_1.py +++ b/maths/3n_plus_1.py @@ -9,7 +9,7 @@ def n31(a: int) -> tuple[list[int], int]: """ if not isinstance(a, int): - raise TypeError("Must be int, not {}".format(type(a).__name__)) + raise TypeError(f"Must be int, not {type(a).__name__}") if a < 1: raise ValueError(f"Given integer must be greater than 1, not {a}") diff --git a/maths/quadratic_equations_complex_numbers.py b/maths/quadratic_equations_complex_numbers.py index 01a411bc560d..1035171e4ec3 100644 --- a/maths/quadratic_equations_complex_numbers.py +++ b/maths/quadratic_equations_complex_numbers.py @@ -30,8 +30,8 @@ def quadratic_roots(a: int, b: int, c: int) -> tuple[complex, complex]: def main(): - solutions = quadratic_roots(a=5, b=6, c=1) - print("The solutions are: {} and {}".format(*solutions)) + solution1, solution2 = quadratic_roots(a=5, b=6, c=1) + print(f"The solutions are: {solution1} and {solution2}") if __name__ == "__main__": diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 296c36e88691..8c39de0f23b6 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -71,13 +71,13 @@ def nth_fibonacci_bruteforce(n): 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_matrix(n), nth_fibonacci_bruteforce(n))) + print( + f"{ordinal} fibonacci number using matrix exponentiation is " + f"{nth_fibonacci_matrix(n)} and using bruteforce is " + f"{nth_fibonacci_bruteforce(n)}\n" + ) # from timeit import timeit # print(timeit("nth_fibonacci_matrix(1000000)", # "from main import nth_fibonacci_matrix", number=5)) diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 4920ec6c13db..3466b3d4a01f 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -175,9 +175,7 @@ def __mul__(self, another): result[r, c] += self[r, i] * another[i, c] return result else: - raise TypeError( - "Unsupported type given for another ({})".format(type(another)) - ) + raise TypeError(f"Unsupported type given for another ({type(another)})") def transpose(self): """ @@ -260,7 +258,7 @@ def test1(): print(f"v is {v}") print("uv^T is %s" % (u * v.transpose())) # Sherman Morrison - print("(a + uv^T)^(-1) is {}".format(ainv.ShermanMorrison(u, v))) + print(f"(a + uv^T)^(-1) is {ainv.ShermanMorrison(u, v)}") def test2(): import doctest diff --git a/strings/levenshtein_distance.py b/strings/levenshtein_distance.py index 54948a96670b..540a21c93da3 100644 --- a/strings/levenshtein_distance.py +++ b/strings/levenshtein_distance.py @@ -69,8 +69,4 @@ def levenshtein_distance(first_word: str, second_word: str) -> int: 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(f"Levenshtein distance between {first_word} and {second_word} is {result}") From 02d9bc66c16a9cc851200f149fabbb07df611525 Mon Sep 17 00:00:00 2001 From: Leyza <56138111+Leyza@users.noreply.github.com> Date: Tue, 23 Feb 2021 01:15:00 -0500 Subject: [PATCH 1425/2908] Added binary shifts and twos complement functions to bit manipulation (#4068) * Added binary shifts and twos complement functions to bit manipulation package * Fixed problem representing 0 wrong * More testing * Fixed problems * Fixed formatting * More format fixes * Format fixes * Fixed docstrings and added url * Minor change to url --- bit_manipulation/binary_shifts.py | 111 +++++++++++++++++++++ bit_manipulation/binary_twos_complement.py | 43 ++++++++ 2 files changed, 154 insertions(+) create mode 100644 bit_manipulation/binary_shifts.py create mode 100644 bit_manipulation/binary_twos_complement.py diff --git a/bit_manipulation/binary_shifts.py b/bit_manipulation/binary_shifts.py new file mode 100644 index 000000000000..fe62880f941c --- /dev/null +++ b/bit_manipulation/binary_shifts.py @@ -0,0 +1,111 @@ +# Information on binary shifts: +# https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types +# https://www.interviewcake.com/concept/java/bit-shift + + +def logical_left_shift(number: int, shift_amount: int) -> str: + """ + Take in 2 positive integers. + 'number' is the integer to be logically left shifted 'shift_amount' times. + i.e. (number << shift_amount) + Return the shifted binary representation. + + >>> logical_left_shift(0, 1) + '0b00' + >>> logical_left_shift(1, 1) + '0b10' + >>> logical_left_shift(1, 5) + '0b100000' + >>> logical_left_shift(17, 2) + '0b1000100' + >>> logical_left_shift(1983, 4) + '0b111101111110000' + >>> logical_left_shift(1, -1) + Traceback (most recent call last): + ... + ValueError: both inputs must be positive integers + """ + if number < 0 or shift_amount < 0: + raise ValueError("both inputs must be positive integers") + + binary_number = str(bin(number)) + binary_number += "0" * shift_amount + return binary_number + + +def logical_right_shift(number: int, shift_amount: int) -> str: + """ + Take in positive 2 integers. + 'number' is the integer to be logically right shifted 'shift_amount' times. + i.e. (number >>> shift_amount) + Return the shifted binary representation. + + >>> logical_right_shift(0, 1) + '0b0' + >>> logical_right_shift(1, 1) + '0b0' + >>> logical_right_shift(1, 5) + '0b0' + >>> logical_right_shift(17, 2) + '0b100' + >>> logical_right_shift(1983, 4) + '0b1111011' + >>> logical_right_shift(1, -1) + Traceback (most recent call last): + ... + ValueError: both inputs must be positive integers + """ + if number < 0 or shift_amount < 0: + raise ValueError("both inputs must be positive integers") + + binary_number = str(bin(number))[2:] + if shift_amount >= len(binary_number): + return "0b0" + shifted_binary_number = binary_number[: len(binary_number) - shift_amount] + return "0b" + shifted_binary_number + + +def arithmetic_right_shift(number: int, shift_amount: int) -> str: + """ + Take in 2 integers. + 'number' is the integer to be arithmetically right shifted 'shift_amount' times. + i.e. (number >> shift_amount) + Return the shifted binary representation. + + >>> arithmetic_right_shift(0, 1) + '0b00' + >>> arithmetic_right_shift(1, 1) + '0b00' + >>> arithmetic_right_shift(-1, 1) + '0b11' + >>> arithmetic_right_shift(17, 2) + '0b000100' + >>> arithmetic_right_shift(-17, 2) + '0b111011' + >>> arithmetic_right_shift(-1983, 4) + '0b111110000100' + """ + if number >= 0: # Get binary representation of positive number + binary_number = "0" + str(bin(number)).strip("-")[2:] + else: # Get binary (2's complement) representation of negative number + binary_number_length = len(bin(number)[3:]) # Find 2's complement of number + binary_number = bin(abs(number) - (1 << binary_number_length))[3:] + binary_number = ( + ("1" + "0" * (binary_number_length - len(binary_number)) + binary_number) + if number < 0 + else "0" + ) + + if shift_amount >= len(binary_number): + return "0b" + binary_number[0] * len(binary_number) + return ( + "0b" + + binary_number[0] * shift_amount + + binary_number[: len(binary_number) - shift_amount] + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/binary_twos_complement.py b/bit_manipulation/binary_twos_complement.py new file mode 100644 index 000000000000..2c064ec142d7 --- /dev/null +++ b/bit_manipulation/binary_twos_complement.py @@ -0,0 +1,43 @@ +# Information on 2's complement: https://en.wikipedia.org/wiki/Two%27s_complement + + +def twos_complement(number: int) -> str: + """ + Take in a negative integer 'number'. + Return the two's complement representation of 'number'. + + >>> twos_complement(0) + '0b0' + >>> twos_complement(-1) + '0b11' + >>> twos_complement(-5) + '0b1011' + >>> twos_complement(-17) + '0b101111' + >>> twos_complement(-207) + '0b100110001' + >>> twos_complement(1) + Traceback (most recent call last): + ... + ValueError: input must be a negative integer + """ + if number > 0: + raise ValueError("input must be a negative integer") + binary_number_length = len(bin(number)[3:]) + twos_complement_number = bin(abs(number) - (1 << binary_number_length))[3:] + twos_complement_number = ( + ( + "1" + + "0" * (binary_number_length - len(twos_complement_number)) + + twos_complement_number + ) + if number < 0 + else "0" + ) + return "0b" + twos_complement_number + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a4726ca248b3cf0470e5453ac1d9878eded38d27 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 23 Feb 2021 09:02:30 +0000 Subject: [PATCH 1426/2908] [mypy]Correction of all errors in the sorts directory (#4224) * [mypy] Add/fix type annotations for recursive_insertion_sort(#4085) * [mypy] Add/fix type annotations for bucket_sort(#4085) * [mypy] Reworked code for cocktail_shaker_sort so that missing return statement error is resolved(#4085) * [mypy] Add/fix type annotations for patience_sort(#4085) * [mypy] Add/fix type annotations for radix_sort(#4085) Co-authored-by: goodm2 <4qjpngu8mem8cz> --- sorts/bucket_sort.py | 3 ++- sorts/cocktail_shaker_sort.py | 3 ++- sorts/patience_sort.py | 3 ++- sorts/radix_sort.py | 2 +- sorts/recursive_insertion_sort.py | 8 +++++--- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index a0566be662e3..1ac76774f4ba 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -27,6 +27,7 @@ Source: https://en.wikipedia.org/wiki/Bucket_sort """ +from typing import List def bucket_sort(my_list: list) -> list: @@ -51,7 +52,7 @@ def bucket_sort(my_list: list) -> list: 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)] + buckets: List[list] = [[] for _ in range(bucket_count)] for i in range(len(my_list)): buckets[(int(my_list[i] - min_value) // bucket_count)].append(my_list[i]) diff --git a/sorts/cocktail_shaker_sort.py b/sorts/cocktail_shaker_sort.py index 42015abc5f97..b738ff31d768 100644 --- a/sorts/cocktail_shaker_sort.py +++ b/sorts/cocktail_shaker_sort.py @@ -33,7 +33,8 @@ def cocktail_shaker_sort(unsorted: list) -> list: swapped = True if not swapped: - return unsorted + break + return unsorted if __name__ == "__main__": diff --git a/sorts/patience_sort.py b/sorts/patience_sort.py index f4e35d9a0ac6..87f5a4078612 100644 --- a/sorts/patience_sort.py +++ b/sorts/patience_sort.py @@ -1,6 +1,7 @@ from bisect import bisect_left from functools import total_ordering from heapq import merge +from typing import List """ A pure Python implementation of the patience sort algorithm @@ -43,7 +44,7 @@ def patience_sort(collection: list) -> list: >>> patience_sort([-3, -17, -48]) [-48, -17, -3] """ - stacks = [] + stacks: List[Stack] = [] # sort into stacks for element in collection: new_stacks = Stack([element]) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index 57dbbaa79076..b802b5278119 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -30,7 +30,7 @@ def radix_sort(list_of_ints: List[int]) -> List[int]: max_digit = max(list_of_ints) while placement <= max_digit: # declare and initialize empty buckets - buckets = [list() for _ in range(RADIX)] + buckets: List[list] = [list() for _ in range(RADIX)] # split list_of_ints between the buckets for i in list_of_ints: tmp = int((i / placement) % RADIX) diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index 66dd08157df1..89f88b4a961b 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import List + def rec_insertion_sort(collection: list, n: int): """ @@ -70,6 +72,6 @@ def insert_next(collection: list, index: int): if __name__ == "__main__": numbers = input("Enter integers separated by spaces: ") - numbers = [int(num) for num in numbers.split()] - rec_insertion_sort(numbers, len(numbers)) - print(numbers) + number_list: List[int] = [int(num) for num in numbers.split()] + rec_insertion_sort(number_list, len(number_list)) + print(number_list) From 7df393f123d4d23808de7ba367c4dbf3d76851ee Mon Sep 17 00:00:00 2001 From: algobytewise Date: Tue, 23 Feb 2021 14:45:04 +0530 Subject: [PATCH 1427/2908] mypy-fix for bezier_curve.py (#4220) --- graphics/bezier_curve.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 295ff47e8cdc..2bb764fdc916 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -2,7 +2,7 @@ # https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm from __future__ import annotations -from scipy.special import comb +from scipy.special import comb # type: ignore class BezierCurve: @@ -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. """ - from matplotlib import pyplot as plt + from matplotlib import pyplot as plt # type: ignore to_plot_x: list[float] = [] # x coordinates of points to plot to_plot_y: list[float] = [] # y coordinates of points to plot From 4c6b92f30f1fb964b08123adce4345efd017b1fe Mon Sep 17 00:00:00 2001 From: algobytewise Date: Tue, 23 Feb 2021 17:59:56 +0530 Subject: [PATCH 1428/2908] Add Mandelbrot algorithm --- graphics/mandelbrot.py | 150 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 graphics/mandelbrot.py diff --git a/graphics/mandelbrot.py b/graphics/mandelbrot.py new file mode 100644 index 000000000000..21a70a56f17b --- /dev/null +++ b/graphics/mandelbrot.py @@ -0,0 +1,150 @@ +""" +The Mandelbrot set is the set of complex numbers "c" for which the series +"z_(n+1) = z_n * z_n + c" does not diverge, i.e. remains bounded. Thus, a +complex number "c" is a member of the Mandelbrot set if, when starting with +"z_0 = 0" and applying the iteration repeatedly, the absolute value of +"z_n" remains bounded for all "n > 0". Complex numbers can be written as +"a + b*i": "a" is the real component, usually drawn on the x-axis, and "b*i" +is the imaginary component, usually drawn on the y-axis. Most visualizations +of the Mandelbrot set use a color-coding to indicate after how many steps in +the series the numbers outside the set diverge. Images of the Mandelbrot set +exhibit an elaborate and infinitely complicated boundary that reveals +progressively ever-finer recursive detail at increasing magnifications, making +the boundary of the Mandelbrot set a fractal curve. +(description adapted from https://en.wikipedia.org/wiki/Mandelbrot_set ) +(see also https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set ) +""" + + +import colorsys + +from PIL import Image # type: ignore + + +def getDistance(x: float, y: float, max_step: int) -> float: + """ + Return the relative distance (= step/max_step) after which the complex number + constituted by this x-y-pair diverges. Members of the Mandelbrot set do not + diverge so their distance is 1. + + >>> getDistance(0, 0, 50) + 1.0 + >>> getDistance(0.5, 0.5, 50) + 0.061224489795918366 + >>> getDistance(2, 0, 50) + 0.0 + """ + a = x + b = y + for step in range(max_step): + a_new = a * a - b * b + x + b = 2 * a * b + y + a = a_new + + # divergence happens for all complex number with an absolute value + # greater than 4 + if a * a + b * b > 4: + break + return step / (max_step - 1) + + +def get_black_and_white_rgb(distance: float) -> tuple: + """ + Black&white color-coding that ignores the relative distance. The Mandelbrot + set is black, everything else is white. + + >>> get_black_and_white_rgb(0) + (255, 255, 255) + >>> get_black_and_white_rgb(0.5) + (255, 255, 255) + >>> get_black_and_white_rgb(1) + (0, 0, 0) + """ + if distance == 1: + return (0, 0, 0) + else: + return (255, 255, 255) + + +def get_color_coded_rgb(distance: float) -> tuple: + """ + Color-coding taking the relative distance into account. The Mandelbrot set + is black. + + >>> get_color_coded_rgb(0) + (255, 0, 0) + >>> get_color_coded_rgb(0.5) + (0, 255, 255) + >>> get_color_coded_rgb(1) + (0, 0, 0) + """ + if distance == 1: + return (0, 0, 0) + else: + return tuple(round(i * 255) for i in colorsys.hsv_to_rgb(distance, 1, 1)) + + +def get_image( + image_width: int = 800, + image_height: int = 600, + figure_center_x: float = -0.6, + figure_center_y: float = 0, + figure_width: float = 3.2, + max_step: int = 50, + use_distance_color_coding: bool = True, +) -> Image.Image: + """ + Function to generate the image of the Mandelbrot set. Two types of coordinates + are used: image-coordinates that refer to the pixels and figure-coordinates + that refer to the complex numbers inside and outside the Mandelbrot set. The + figure-coordinates in the arguments of this function determine which section + of the Mandelbrot set is viewed. The main area of the Mandelbrot set is + roughly between "-1.5 < x < 0.5" and "-1 < y < 1" in the figure-coordinates. + + >>> get_image().load()[0,0] + (255, 0, 0) + >>> get_image(use_distance_color_coding = False).load()[0,0] + (255, 255, 255) + """ + img = Image.new("RGB", (image_width, image_height)) + pixels = img.load() + + # loop through the image-coordinates + for image_x in range(image_width): + for image_y in range(image_height): + + # determine the figure-coordinates based on the image-coordinates + figure_height = figure_width / image_width * image_height + figure_x = figure_center_x + (image_x / image_width - 0.5) * figure_width + figure_y = figure_center_y + (image_y / image_height - 0.5) * figure_height + + distance = getDistance(figure_x, figure_y, max_step) + + # color the corresponding pixel based on the selected coloring-function + if use_distance_color_coding: + pixels[image_x, image_y] = get_color_coded_rgb(distance) + else: + pixels[image_x, image_y] = get_black_and_white_rgb(distance) + + return img + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # colored version, full figure + img = get_image() + + # uncomment for colored version, different section, zoomed in + # img = get_image(figure_center_x = -0.6, figure_center_y = -0.4, + # figure_width = 0.8) + + # uncomment for black and white version, full figure + # img = get_image(use_distance_color_coding = False) + + # uncomment to save the image + # img.save("mandelbrot.png") + + img.show() From 7bf1d622ef13bb67de262c7ad2e5061bd501880b Mon Sep 17 00:00:00 2001 From: algobytewise Date: Tue, 23 Feb 2021 18:04:42 +0530 Subject: [PATCH 1429/2908] snake_case-fix --- graphics/mandelbrot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/mandelbrot.py b/graphics/mandelbrot.py index 21a70a56f17b..a0cb8fe88027 100644 --- a/graphics/mandelbrot.py +++ b/graphics/mandelbrot.py @@ -21,7 +21,7 @@ from PIL import Image # type: ignore -def getDistance(x: float, y: float, max_step: int) -> float: +def get_distance(x: float, y: float, max_step: int) -> float: """ Return the relative distance (= step/max_step) after which the complex number constituted by this x-y-pair diverges. Members of the Mandelbrot set do not From 71b1202d040c2ed1d57e6650a400a1a9502b15ce Mon Sep 17 00:00:00 2001 From: algobytewise Date: Tue, 23 Feb 2021 18:08:16 +0530 Subject: [PATCH 1430/2908] fixed-renaming --- graphics/mandelbrot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphics/mandelbrot.py b/graphics/mandelbrot.py index a0cb8fe88027..de795bb3fc6f 100644 --- a/graphics/mandelbrot.py +++ b/graphics/mandelbrot.py @@ -27,11 +27,11 @@ def get_distance(x: float, y: float, max_step: int) -> float: constituted by this x-y-pair diverges. Members of the Mandelbrot set do not diverge so their distance is 1. - >>> getDistance(0, 0, 50) + >>> get_distance(0, 0, 50) 1.0 - >>> getDistance(0.5, 0.5, 50) + >>> get_distance(0.5, 0.5, 50) 0.061224489795918366 - >>> getDistance(2, 0, 50) + >>> get_distance(2, 0, 50) 0.0 """ a = x @@ -118,7 +118,7 @@ def get_image( figure_x = figure_center_x + (image_x / image_width - 0.5) * figure_width figure_y = figure_center_y + (image_y / image_height - 0.5) * figure_height - distance = getDistance(figure_x, figure_y, max_step) + distance = get_distance(figure_x, figure_y, max_step) # color the corresponding pixel based on the selected coloring-function if use_distance_color_coding: From 67b33a295bc0a18f9fbdeb393724835b2645dbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9C=E8=BF=9C=E8=B6=85?= Date: Fri, 26 Feb 2021 09:01:50 +0800 Subject: [PATCH 1431/2908] Optimization shell sort (#4119) * optimization * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- sorts/shell_sort.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sorts/shell_sort.py b/sorts/shell_sort.py index 2e749e43d056..10ae9ba407ec 100644 --- a/sorts/shell_sort.py +++ b/sorts/shell_sort.py @@ -26,7 +26,8 @@ def shell_sort(collection): while j >= gap and collection[j - gap] > insert_value: collection[j] = collection[j - gap] j -= gap - collection[j] = insert_value + if j != i: + collection[j] = insert_value return collection From 4c76e3cba07b3252e97113bbf809d921058482fd Mon Sep 17 00:00:00 2001 From: algobytewise Date: Fri, 26 Feb 2021 19:00:35 +0530 Subject: [PATCH 1432/2908] [mypy] Added/fixed type annotations for "rotate_matrix.py" & "test_matrix_operation.py" (#4221) * [mypy] Added/fixed type annotations for "rotate_matrix.py" * [mypy] Added/fixed type annotations for "test_matrix_operation.py" --- matrix/rotate_matrix.py | 18 ++++++++++-------- matrix/tests/test_matrix_operation.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/matrix/rotate_matrix.py b/matrix/rotate_matrix.py index 6daf7e0cf2c5..f638597ae35d 100644 --- a/matrix/rotate_matrix.py +++ b/matrix/rotate_matrix.py @@ -5,8 +5,10 @@ https://stackoverflow.com/questions/42519/how-do-you-rotate-a-two-dimensional-array """ +from __future__ import annotations -def make_matrix(row_size: int = 4) -> [[int]]: + +def make_matrix(row_size: int = 4) -> list[list]: """ >>> make_matrix() [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] @@ -23,7 +25,7 @@ def make_matrix(row_size: int = 4) -> [[int]]: return [[1 + x + y * row_size for x in range(row_size)] for y in range(row_size)] -def rotate_90(matrix: [[]]) -> [[]]: +def rotate_90(matrix: list[list]) -> list[list]: """ >>> rotate_90(make_matrix()) [[4, 8, 12, 16], [3, 7, 11, 15], [2, 6, 10, 14], [1, 5, 9, 13]] @@ -35,7 +37,7 @@ def rotate_90(matrix: [[]]) -> [[]]: # OR.. transpose(reverse_column(matrix)) -def rotate_180(matrix: [[]]) -> [[]]: +def rotate_180(matrix: list[list]) -> list[list]: """ >>> rotate_180(make_matrix()) [[16, 15, 14, 13], [12, 11, 10, 9], [8, 7, 6, 5], [4, 3, 2, 1]] @@ -47,7 +49,7 @@ def rotate_180(matrix: [[]]) -> [[]]: # OR.. reverse_column(reverse_row(matrix)) -def rotate_270(matrix: [[]]) -> [[]]: +def rotate_270(matrix: list[list]) -> list[list]: """ >>> rotate_270(make_matrix()) [[13, 9, 5, 1], [14, 10, 6, 2], [15, 11, 7, 3], [16, 12, 8, 4]] @@ -59,22 +61,22 @@ def rotate_270(matrix: [[]]) -> [[]]: # OR.. transpose(reverse_row(matrix)) -def transpose(matrix: [[]]) -> [[]]: +def transpose(matrix: list[list]) -> list[list]: matrix[:] = [list(x) for x in zip(*matrix)] return matrix -def reverse_row(matrix: [[]]) -> [[]]: +def reverse_row(matrix: list[list]) -> list[list]: matrix[:] = matrix[::-1] return matrix -def reverse_column(matrix: [[]]) -> [[]]: +def reverse_column(matrix: list[list]) -> list[list]: matrix[:] = [x[::-1] for x in matrix] return matrix -def print_matrix(matrix: [[]]) -> [[]]: +def print_matrix(matrix: list[list]) -> None: for i in matrix: print(*i) diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index 3500dfeb0641..65b35fd7e78b 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -12,7 +12,7 @@ import sys import numpy as np -import pytest +import pytest # type: ignore # Custom/local libraries from matrix import matrix_operation as matop From 0435128cc097726bf54db2b34bdda61b546ecbe2 Mon Sep 17 00:00:00 2001 From: Ayush Bisht <61404154+ayushbisht2001@users.noreply.github.com> Date: Tue, 2 Mar 2021 03:00:16 +0530 Subject: [PATCH 1433/2908] Add geometric_mean.py (#4244) * geometric_mean3 * update-GM.PY * update-AM.PY * Revert "update-AM.PY" This reverts commit 11792ec9747bec5d00e51edcd462bf87150cdba9. --- maths/series/geometric_mean.py | 75 ++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 maths/series/geometric_mean.py diff --git a/maths/series/geometric_mean.py b/maths/series/geometric_mean.py new file mode 100644 index 000000000000..50ae54ad6574 --- /dev/null +++ b/maths/series/geometric_mean.py @@ -0,0 +1,75 @@ +""" +GEOMETRIC MEAN : https://en.wikipedia.org/wiki/Geometric_mean +""" + + +def is_geometric_series(series: list) -> bool: + """ + checking whether the input series is geometric series or not + + >>> is_geometric_series([2, 4, 8]) + True + >>> is_geometric_series([3, 6, 12, 24]) + True + >>> is_geometric_series([1, 2, 3]) + False + >>> is_geometric_series([0, 0, 3]) + False + + """ + if len(series) == 1: + return True + try: + common_ratio = series[1] / series[0] + for index in range(len(series) - 1): + if series[index + 1] / series[index] != common_ratio: + return False + except ZeroDivisionError: + return False + return True + + +def geometric_mean(series: list) -> float: + """ + return the geometric mean of series + + >>> geometric_mean([2, 4, 8]) + 3.9999999999999996 + >>> geometric_mean([3, 6, 12, 24]) + 8.48528137423857 + >>> geometric_mean([4, 8, 16]) + 7.999999999999999 + >>> geometric_mean(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 8] + >>> geometric_mean([1, 2, 3]) + Traceback (most recent call last): + ... + ValueError: Input list is not a geometric series + >>> geometric_mean([0, 2, 3]) + Traceback (most recent call last): + ... + ValueError: Input list is not a geometric series + >>> geometric_mean([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + + """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 8]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") + if not is_geometric_series(series): + raise ValueError("Input list is not a geometric series") + answer = 1 + for value in series: + answer *= value + return pow(answer, 1 / len(series)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a796ccf1ce2594bffdb938156987a0cbb16ee52e Mon Sep 17 00:00:00 2001 From: ulwlu Date: Tue, 2 Mar 2021 21:24:41 +0900 Subject: [PATCH 1434/2908] Add graham scan algorithm (#4205) * Add graham scan algorithm * Fix argument name p with point * Add tests in inner function * updating DIRECTORY.md * Fix graham scan for isort --profile=black Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 7 ++ other/graham_scan.py | 171 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 other/graham_scan.py diff --git a/DIRECTORY.md b/DIRECTORY.md index d487b39490ed..61e20eeb571d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -30,6 +30,8 @@ * [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) + * [Count Number Of One Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_number_of_one_bits.py) + * [Reverse Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py) ## Blockchain @@ -122,6 +124,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) * [Lowest Common Ancestor](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lowest_common_ancestor.py) + * [Merge Two Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/merge_two_binary_trees.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) @@ -274,6 +277,7 @@ ## Graphics * [Bezier Curve](https://github.com/TheAlgorithms/Python/blob/master/graphics/bezier_curve.py) + * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/graphics/koch_snowflake.py) * [Vector3 For 2D Rendering](https://github.com/TheAlgorithms/Python/blob/master/graphics/vector3_for_2d_rendering.py) ## Graphs @@ -520,6 +524,7 @@ * [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) + * [Graham Scan](https://github.com/TheAlgorithms/Python/blob/master/other/graham_scan.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) @@ -840,6 +845,7 @@ * [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 Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_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) @@ -856,6 +862,7 @@ * [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) + * [Slowsort](https://github.com/TheAlgorithms/Python/blob/master/sorts/slowsort.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) diff --git a/other/graham_scan.py b/other/graham_scan.py new file mode 100644 index 000000000000..67c5cd8ab9d8 --- /dev/null +++ b/other/graham_scan.py @@ -0,0 +1,171 @@ +""" +This is a pure Python implementation of the merge-insertion sort algorithm +Source: https://en.wikipedia.org/wiki/Graham_scan + +For doctests run following command: +python3 -m doctest -v graham_scan.py +""" + +from __future__ import annotations + +from collections import deque +from enum import Enum +from math import atan2, degrees +from sys import maxsize + + +def graham_scan(points: list[list[int, int]]) -> list[list[int, int]]: + """Pure implementation of graham scan algorithm in Python + + :param points: The unique points on coordinates. + :return: The points on convex hell. + + Examples: + >>> graham_scan([(9, 6), (3, 1), (0, 0), (5, 5), (5, 2), (7, 0), (3, 3), (1, 4)]) + [(0, 0), (7, 0), (9, 6), (5, 5), (1, 4)] + + >>> graham_scan([(0, 0), (1, 0), (1, 1), (0, 1)]) + [(0, 0), (1, 0), (1, 1), (0, 1)] + + >>> graham_scan([(0, 0), (1, 1), (2, 2), (3, 3), (-1, 2)]) + [(0, 0), (1, 1), (2, 2), (3, 3), (-1, 2)] + + >>> graham_scan([(-100, 20), (99, 3), (1, 10000001), (5133186, -25), (-66, -4)]) + [(5133186, -25), (1, 10000001), (-100, 20), (-66, -4)] + """ + + if len(points) <= 2: + # There is no convex hull + raise ValueError("graham_scan: argument must contain more than 3 points.") + if len(points) == 3: + return points + # find the lowest and the most left point + minidx = 0 + miny, minx = maxsize, maxsize + for i, point in enumerate(points): + x = point[0] + y = point[1] + if y < miny: + miny = y + minx = x + minidx = i + if y == miny: + if x < minx: + minx = x + minidx = i + + # remove the lowest and the most left point from points for preparing for sort + points.pop(minidx) + + def angle_comparer(point: list[int, int], minx: int, miny: int) -> float: + """Return the angle toward to point from (minx, miny) + + :param point: The target point + minx: The starting point's x + miny: The starting point's y + :return: the angle + + Examples: + >>> angle_comparer([1,1], 0, 0) + 45.0 + + >>> angle_comparer([100,1], 10, 10) + -5.710593137499642 + + >>> angle_comparer([5,5], 2, 3) + 33.690067525979785 + """ + # sort the points accorgind to the angle from the lowest and the most left point + x = point[0] + y = point[1] + angle = degrees(atan2(y - miny, x - minx)) + return angle + + sorted_points = sorted(points, key=lambda point: angle_comparer(point, minx, miny)) + # This insert actually costs complexity, + # and you should insteadly add (minx, miny) into stack later. + # I'm using insert just for easy understanding. + sorted_points.insert(0, (minx, miny)) + + # traversal from the lowest and the most left point in anti-clockwise direction + # if direction gets right, the previous point is not the convex hull. + class Direction(Enum): + left = 1 + straight = 2 + right = 3 + + def check_direction( + starting: list[int, int], via: list[int, int], target: list[int, int] + ) -> Direction: + """Return the direction toward to the line from via to target from starting + + :param starting: The starting point + via: The via point + target: The target point + :return: the Direction + + Examples: + >>> check_direction([1,1], [2,2], [3,3]) + Direction.straight + + >>> check_direction([60,1], [-50,199], [30,2]) + Direction.left + + >>> check_direction([0,0], [5,5], [10,0]) + Direction.right + """ + x0, y0 = starting + x1, y1 = via + x2, y2 = target + via_angle = degrees(atan2(y1 - y0, x1 - x0)) + if via_angle < 0: + via_angle += 360 + target_angle = degrees(atan2(y2 - y0, x2 - x0)) + if target_angle < 0: + target_angle += 360 + # t- + # \ \ + # \ v + # \| + # s + # via_angle is always lower than target_angle, if direction is left. + # If they are same, it means they are on a same line of convex hull. + if target_angle > via_angle: + return Direction.left + if target_angle == via_angle: + return Direction.straight + if target_angle < via_angle: + return Direction.right + + stack = deque() + stack.append(sorted_points[0]) + stack.append(sorted_points[1]) + stack.append(sorted_points[2]) + # In any ways, the first 3 points line are towards left. + # Because we sort them the angle from minx, miny. + current_direction = Direction.left + + for i in range(3, len(sorted_points)): + while True: + starting = stack[-2] + via = stack[-1] + target = sorted_points[i] + next_direction = check_direction(starting, via, target) + + if next_direction == Direction.left: + current_direction = Direction.left + break + if next_direction == Direction.straight: + if current_direction == Direction.left: + # We keep current_direction as left. + # Because if the straight line keeps as straight, + # we want to know if this straight line is towards left. + break + elif current_direction == Direction.right: + # If the straight line is towards right, + # every previous points on those straigh line is not convex hull. + stack.pop() + if next_direction == Direction.right: + stack.pop() + stack.append(sorted_points[i]) + return list(stack) From ecf9b8164fc3046ad5e6bbabb4abf3ac6a8a3cbb Mon Sep 17 00:00:00 2001 From: fpringle Date: Sat, 6 Mar 2021 14:29:52 +0100 Subject: [PATCH 1435/2908] Added solution for Project Euler problem 109 (#4080) * Added solution for Project Euler problem 109 * New subscriptable builtin types * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 6 ++ project_euler/problem_109/__init__.py | 0 project_euler/problem_109/sol1.py | 89 +++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 project_euler/problem_109/__init__.py create mode 100644 project_euler/problem_109/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 61e20eeb571d..dfb673dea829 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -29,6 +29,8 @@ * [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 Shifts](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_shifts.py) + * [Binary Twos Complement](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_twos_complement.py) * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) * [Count Number Of One Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_number_of_one_bits.py) * [Reverse Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/reverse_bits.py) @@ -278,6 +280,7 @@ ## Graphics * [Bezier Curve](https://github.com/TheAlgorithms/Python/blob/master/graphics/bezier_curve.py) * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/graphics/koch_snowflake.py) + * [Mandelbrot](https://github.com/TheAlgorithms/Python/blob/master/graphics/mandelbrot.py) * [Vector3 For 2D Rendering](https://github.com/TheAlgorithms/Python/blob/master/graphics/vector3_for_2d_rendering.py) ## Graphs @@ -469,6 +472,7 @@ * [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 Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_mean.py) * [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) @@ -757,6 +761,8 @@ * [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 109 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_109/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_109/__init__.py b/project_euler/problem_109/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_109/sol1.py b/project_euler/problem_109/sol1.py new file mode 100644 index 000000000000..91c71eb9f4cb --- /dev/null +++ b/project_euler/problem_109/sol1.py @@ -0,0 +1,89 @@ +""" +In the game of darts a player throws three darts at a target board which is +split into twenty equal sized sections numbered one to twenty. + +The score of a dart is determined by the number of the region that the dart +lands in. A dart landing outside the red/green outer ring scores zero. The black +and cream regions inside this ring represent single scores. However, the red/green +outer ring and middle ring score double and treble scores respectively. + +At the centre of the board are two concentric circles called the bull region, or +bulls-eye. The outer bull is worth 25 points and the inner bull is a double, +worth 50 points. + +There are many variations of rules but in the most popular game the players will +begin with a score 301 or 501 and the first player to reduce their running total +to zero is a winner. However, it is normal to play a "doubles out" system, which +means that the player must land a double (including the double bulls-eye at the +centre of the board) on their final dart to win; any other dart that would reduce +their running total to one or lower means the score for that set of three darts +is "bust". + +When a player is able to finish on their current score it is called a "checkout" +and the highest checkout is 170: T20 T20 D25 (two treble 20s and double bull). + +There are exactly eleven distinct ways to checkout on a score of 6: + +D3 +D1 D2 +S2 D2 +D2 D1 +S4 D1 +S1 S1 D2 +S1 T1 D1 +S1 S3 D1 +D1 D1 D1 +D1 S2 D1 +S2 S2 D1 + +Note that D1 D2 is considered different to D2 D1 as they finish on different +doubles. However, the combination S1 T1 D1 is considered the same as T1 S1 D1. + +In addition we shall not include misses in considering combinations; for example, +D3 is the same as 0 D3 and 0 0 D3. + +Incredibly there are 42336 distinct ways of checking out in total. + +How many distinct ways can a player checkout with a score less than 100? + +Solution: + We first construct a list of the possible dart values, separated by type. + We then iterate through the doubles, followed by the possible 2 following throws. + If the total of these three darts is less than the given limit, we increment + the counter. +""" + +from itertools import combinations_with_replacement + + +def solution(limit: int = 100) -> int: + """ + Count the number of distinct ways a player can checkout with a score + less than limit. + >>> solution(171) + 42336 + >>> solution(50) + 12577 + """ + singles: list[int] = [x for x in range(1, 21)] + [25] + doubles: list[int] = [2 * x for x in range(1, 21)] + [50] + triples: list[int] = [3 * x for x in range(1, 21)] + all_values: list[int] = singles + doubles + triples + [0] + + num_checkouts: int = 0 + double: int + throw1: int + throw2: int + checkout_total: int + + for double in doubles: + for throw1, throw2 in combinations_with_replacement(all_values, 2): + checkout_total = double + throw1 + throw2 + if checkout_total < limit: + num_checkouts += 1 + + return num_checkouts + + +if __name__ == "__main__": + print(f"{solution() = }") From ced83bed2cda5a1a4353f3ced2871a884d380879 Mon Sep 17 00:00:00 2001 From: Ayush Bisht <61404154+ayushbisht2001@users.noreply.github.com> Date: Fri, 12 Mar 2021 12:55:54 +0530 Subject: [PATCH 1436/2908] Add arithmetic_mean.py (#4243) * arithmetic_mean * arithmetic_mean * checks * checked * Revert "checked" This reverts commit 3913a39ae2c4ee183443eed67ee7427e3d322ad4. * checks-3 * update-1 --- maths/series/arithmetic_mean.py | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 maths/series/arithmetic_mean.py diff --git a/maths/series/arithmetic_mean.py b/maths/series/arithmetic_mean.py new file mode 100644 index 000000000000..b5d64b63ac3f --- /dev/null +++ b/maths/series/arithmetic_mean.py @@ -0,0 +1,66 @@ +""" +ARITHMETIC MEAN : https://en.wikipedia.org/wiki/Arithmetic_mean + +""" + + +def is_arithmetic_series(series: list) -> bool: + """ + checking whether the input series is arithmetic series or not + + >>> is_arithmetic_series([2, 4, 6]) + True + >>> is_arithmetic_series([3, 6, 12, 24]) + False + >>> is_arithmetic_series([1, 2, 3]) + True + """ + if len(series) == 1: + return True + common_diff = series[1] - series[0] + for index in range(len(series) - 1): + if series[index + 1] - series[index] != common_diff: + return False + return True + + +def arithmetic_mean(series: list) -> float: + """ + return the arithmetic mean of series + + >>> arithmetic_mean([2, 4, 6]) + 4.0 + >>> arithmetic_mean([3, 6, 9, 12]) + 7.5 + >>> arithmetic_mean(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 6] + >>> arithmetic_mean([4, 8, 1]) + Traceback (most recent call last): + ... + ValueError: Input list is not an arithmetic series + >>> arithmetic_mean([1, 2, 3]) + 2.0 + >>> arithmetic_mean([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + + """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 6]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") + if not is_arithmetic_series(series): + raise ValueError("Input list is not an arithmetic series") + answer = 0 + for val in series: + answer += val + return answer / len(series) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 4f6a929503ac4ee427e85896d1354b50f465ddb4 Mon Sep 17 00:00:00 2001 From: Shantanu Joshi <42472191+shan7030@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:09:53 +0530 Subject: [PATCH 1437/2908] [mypy] Add/fix type annotations for electronics algorithms (#4247) * 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 * Fix mypy errors for electronics/ohms_law.py * Fix mypy errors for electronics/electric_power.py * Fix black errors --- electronics/electric_power.py | 3 ++- electronics/ohms_law.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/electronics/electric_power.py b/electronics/electric_power.py index 768c3d5c7232..8f0293bd2d10 100644 --- a/electronics/electric_power.py +++ b/electronics/electric_power.py @@ -1,8 +1,9 @@ # https://en.m.wikipedia.org/wiki/Electric_power from collections import namedtuple +from typing import Tuple -def electric_power(voltage: float, current: float, power: float) -> float: +def electric_power(voltage: float, current: float, power: float) -> Tuple: """ This function can calculate any one of the three (voltage, current, power), fundamental value of electrical system. diff --git a/electronics/ohms_law.py b/electronics/ohms_law.py index a7b37b635397..c53619a10935 100644 --- a/electronics/ohms_law.py +++ b/electronics/ohms_law.py @@ -1,7 +1,8 @@ # https://en.wikipedia.org/wiki/Ohm%27s_law +from typing import Dict -def ohms_law(voltage: float, current: float, resistance: float) -> float: +def ohms_law(voltage: float, current: float, resistance: float) -> Dict[str, 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. From 8e488dd53d4fd3e09e71aa5e09098f0792626938 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Fri, 19 Mar 2021 10:57:32 +0530 Subject: [PATCH 1438/2908] mypy-fix for "covid_stats_via_xpath.py" (#4233) --- web_programming/covid_stats_via_xpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_programming/covid_stats_via_xpath.py b/web_programming/covid_stats_via_xpath.py index d22ed017878c..85ea5d940d85 100644 --- a/web_programming/covid_stats_via_xpath.py +++ b/web_programming/covid_stats_via_xpath.py @@ -7,7 +7,7 @@ from collections import namedtuple import requests -from lxml import html +from lxml import html # type: ignore covid_data = namedtuple("covid_data", "cases deaths recovered") From ffa53c02a7da4ac4149a8ee1b14d4f023d2e2d78 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Fri, 19 Mar 2021 15:59:54 +0530 Subject: [PATCH 1439/2908] Include mypy instructions in CONTRIBUTING.md (#4271) * reupload * Include mypy instructions * delete file * fixed trailing whitespaces * options before file path Co-authored-by: Christian Clauss --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4c81a5ecd98..76ee1312f345 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,6 +155,8 @@ We want your work to be readable by others; therefore, we encourage you to note return a + b ``` + Instructions on how to install mypy can be found [here](https://github.com/python/mypy). Please use the command `mypy --ignore-missing-imports .` to test all files or `mypy --ignore-missing-imports path/to/file.py` to test a specific file. + - [__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. From b8a19ccfea0ceca9f83912aa2f5ad2c15114416a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 20 Mar 2021 06:09:56 +0100 Subject: [PATCH 1440/2908] GitHub Actions: fast-fail on black formatting issues (#4268) * GitHub Actions: fast-fail on black formatting issues Give fast feedback to contributors https://github.com/psf/black#github-actions * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/pre-commit.yml | 1 + DIRECTORY.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 96175cfecea5..17fdad1204e9 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,6 +14,7 @@ jobs: ~/.cache/pip key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/setup-python@v2 + - uses: psf/black@stable - name: Install pre-commit run: | python -m pip install --upgrade pip diff --git a/DIRECTORY.md b/DIRECTORY.md index dfb673dea829..136825e41976 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -472,6 +472,7 @@ * [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 + * [Arithmetic Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/arithmetic_mean.py) * [Geometric Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_mean.py) * [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) From 987567360e53da1bef786364580e9d5c6dce3fc6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 20 Mar 2021 06:12:17 +0100 Subject: [PATCH 1441/2908] Update our pre-commit dependencies (#4273) * .pre-commit-config.yaml: mypy directories that pass * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 12 ++++++------ machine_learning/k_nearest_neighbours.py | 2 +- maths/polynomial_evaluation.py | 4 ++-- searches/hill_climbing.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3288e1c5eef..d6be7f60f714 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v3.4.0 hooks: - id: check-executables-have-shebangs - id: check-yaml @@ -13,17 +13,17 @@ repos: )$ - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: stable + rev: 20.8b1 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.5.3 + rev: 5.7.0 hooks: - id: isort args: - --profile=black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.9.0 hooks: - id: flake8 args: @@ -38,11 +38,11 @@ repos: # args: # - --ignore-missing-imports - repo: https://github.com/codespell-project/codespell - rev: v1.17.1 + rev: v2.0.0 hooks: - id: codespell args: - - --ignore-words-list=ans,fo,followings,hist,iff,mater,secant,som,tim + - --ignore-words-list=ans,crate,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/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py index e90ea09a58c1..2a90cfe5987a 100644 --- a/machine_learning/k_nearest_neighbours.py +++ b/machine_learning/k_nearest_neighbours.py @@ -32,7 +32,7 @@ def classifier(train_data, train_target, classes, point, k=5): :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 + :point: The data point that needs to be classified >>> 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] diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index e929a2d02972..68ff97ddd25d 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -5,7 +5,7 @@ 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 + poly -- the coefficients of a polynomial as an iterable in order of ascending degree x -- the point at which to evaluate the polynomial @@ -26,7 +26,7 @@ def horner(poly: Sequence[float], x: float) -> float: https://en.wikipedia.org/wiki/Horner's_method Arguments: - poly -- the coeffiecients of a polynomial as an iterable in order of + poly -- the coefficients of a polynomial as an iterable in order of ascending degree x -- the point at which to evaluate the polynomial diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index 70622ebefb4e..bb24e781a6c1 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -60,7 +60,7 @@ def get_neighbors(self): def __hash__(self): """ - hash the string represetation of the current search state. + hash the string representation of the current search state. """ return hash(str(self)) From 8f5f32bc00e9b06ec71ae4f06ed388c607af3be5 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Sat, 20 Mar 2021 11:19:30 +0530 Subject: [PATCH 1442/2908] New fractals folder (#4277) * reupload * delete file * Move koch_snowflake.py to fractals-folder * Move mandelbrot.py to fractals-folder * Move sierpinski_triangle.py to fractals-folder --- {graphics => fractals}/koch_snowflake.py | 0 {graphics => fractals}/mandelbrot.py | 0 {other => fractals}/sierpinski_triangle.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {graphics => fractals}/koch_snowflake.py (100%) rename {graphics => fractals}/mandelbrot.py (100%) rename {other => fractals}/sierpinski_triangle.py (100%) diff --git a/graphics/koch_snowflake.py b/fractals/koch_snowflake.py similarity index 100% rename from graphics/koch_snowflake.py rename to fractals/koch_snowflake.py diff --git a/graphics/mandelbrot.py b/fractals/mandelbrot.py similarity index 100% rename from graphics/mandelbrot.py rename to fractals/mandelbrot.py diff --git a/other/sierpinski_triangle.py b/fractals/sierpinski_triangle.py similarity index 100% rename from other/sierpinski_triangle.py rename to fractals/sierpinski_triangle.py From 89a43c81e50e0b5b46da78143a239967e198c1fa Mon Sep 17 00:00:00 2001 From: fpringle Date: Sat, 20 Mar 2021 06:59:48 +0100 Subject: [PATCH 1443/2908] feat: Add solution for Project Euler Problem 121 (#4261) * Added solution for Project Euler problem 121 * Updated typing for 3.9 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_121/__init__.py | 0 project_euler/problem_121/sol1.py | 64 +++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 project_euler/problem_121/__init__.py create mode 100644 project_euler/problem_121/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 136825e41976..070119f2f674 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -772,6 +772,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 121 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_121/sol1.py) * Problem 123 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_123/sol1.py) * Problem 125 diff --git a/project_euler/problem_121/__init__.py b/project_euler/problem_121/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_121/sol1.py b/project_euler/problem_121/sol1.py new file mode 100644 index 000000000000..93679cf4a897 --- /dev/null +++ b/project_euler/problem_121/sol1.py @@ -0,0 +1,64 @@ +""" +A bag contains one red disc and one blue disc. In a game of chance a player takes a +disc at random and its colour is noted. After each turn the disc is returned to the +bag, an extra red disc is added, and another disc is taken at random. + +The player pays £1 to play and wins if they have taken more blue discs than red +discs at the end of the game. + +If the game is played for four turns, the probability of a player winning is exactly +11/120, and so the maximum prize fund the banker should allocate for winning in this +game would be £10 before they would expect to incur a loss. Note that any payout will +be a whole number of pounds and also includes the original £1 paid to play the game, +so in the example given the player actually wins £9. + +Find the maximum prize fund that should be allocated to a single game in which +fifteen turns are played. + + +Solution: + For each 15-disc sequence of red and blue for which there are more red than blue, + we calculate the probability of that sequence and add it to the total probability + of the player winning. The inverse of this probability gives an upper bound for + the prize if the banker wants to avoid an expected loss. +""" + +from itertools import product + + +def solution(num_turns: int = 15) -> int: + """ + Find the maximum prize fund that should be allocated to a single game in which + fifteen turns are played. + >>> solution(4) + 10 + >>> solution(10) + 225 + """ + total_prob: float = 0.0 + prob: float + num_blue: int + num_red: int + ind: int + col: int + series: tuple[int, ...] + + for series in product(range(2), repeat=num_turns): + num_blue = series.count(1) + num_red = num_turns - num_blue + if num_red >= num_blue: + continue + prob = 1.0 + for ind, col in enumerate(series, 2): + if col == 0: + prob *= (ind - 1) / ind + else: + prob *= 1 / ind + + total_prob += prob + + return int(1 / total_prob) + + +if __name__ == "__main__": + print(f"{solution() = }") From 8d7ef6a7f59be10cce808484949d2ab4f0429a63 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 20 Mar 2021 07:01:13 +0100 Subject: [PATCH 1444/2908] build.yml: Run mypy --ignore-missing-imports (#4276) * build.yml: Run mypy --ignore-missing-imports * pip install mypy * Remove failing directories * Add fractals and drop python-m --- .github/workflows/build.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9e15d18ade8e..1e8d04126002 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,22 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools six wheel - python -m pip install pytest-cov -r requirements.txt + python -m pip install mypy pytest-cov -r requirements.txt + # FIXME: #4052 fix mypy errors in other directories and add them here + - run: mypy --ignore-missing-imports + backtracking + bit_manipulation + blockchain + boolean_algebra + cellular_automata + computer_vision + fractals + fuzzy_logic + genetic_algorithm + geodesy + knapsack + networking_flow + scheduling sorts - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} From 2dc2c99f2b9ec3c1fe02064720718f65f19e7292 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Sat, 20 Mar 2021 11:41:10 +0530 Subject: [PATCH 1445/2908] refactor: Rename explicit_euler.py to euler_method.py (#4275) * reupload * Rename explicit_euler.py to euler_method.py * Delete file --- maths/{explicit_euler.py => euler_method.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename maths/{explicit_euler.py => euler_method.py} (100%) diff --git a/maths/explicit_euler.py b/maths/euler_method.py similarity index 100% rename from maths/explicit_euler.py rename to maths/euler_method.py From dd757dce383b575021c1765f84d8a40acff16799 Mon Sep 17 00:00:00 2001 From: DevanshiPatel18 <61454611+DevanshiPatel18@users.noreply.github.com> Date: Sat, 20 Mar 2021 11:48:38 +0530 Subject: [PATCH 1446/2908] feat: Add greedy_coin_change.py algorithm (#3805) * Hacktoberfest: Add greedy_coin_change.py file added the file in greedy_methods folder to implement the same method Altered the code according to the changes that were requested. * Added doctests. doctests added to the function find_minimum_change. * Added Greedy change file in Maths folder. * updating DIRECTORY.md * Deleted Greedy Method Folder * updating DIRECTORY.md * Update greedy_coin_change.py * fix: black formatting issues Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Dhruv Manilawala --- DIRECTORY.md | 9 ++-- maths/greedy_coin_change.py | 102 ++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 maths/greedy_coin_change.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 070119f2f674..2f57a9db5769 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -267,6 +267,11 @@ * Tests * [Test Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/tests/test_send_file.py) +## Fractals + * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/fractals/koch_snowflake.py) + * [Mandelbrot](https://github.com/TheAlgorithms/Python/blob/master/fractals/mandelbrot.py) + * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/fractals/sierpinski_triangle.py) + ## Fuzzy Logic * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) @@ -279,8 +284,6 @@ ## Graphics * [Bezier Curve](https://github.com/TheAlgorithms/Python/blob/master/graphics/bezier_curve.py) - * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/graphics/koch_snowflake.py) - * [Mandelbrot](https://github.com/TheAlgorithms/Python/blob/master/graphics/mandelbrot.py) * [Vector3 For 2D Rendering](https://github.com/TheAlgorithms/Python/blob/master/graphics/vector3_for_2d_rendering.py) ## Graphs @@ -432,6 +435,7 @@ * [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) + * [Greedy Coin Change](https://github.com/TheAlgorithms/Python/blob/master/maths/greedy_coin_change.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) @@ -547,7 +551,6 @@ * [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) * [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) diff --git a/maths/greedy_coin_change.py b/maths/greedy_coin_change.py new file mode 100644 index 000000000000..5a7d9e8d84ae --- /dev/null +++ b/maths/greedy_coin_change.py @@ -0,0 +1,102 @@ +""" +Test cases: +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make in Indian Currency: 987 +Following is minimal change for 987 : +500 100 100 100 100 50 20 10 5 2 + +Do you want to enter your denominations ? (Y/N) :Y +Enter number of denomination:10 +1 +5 +10 +20 +50 +100 +200 +500 +1000 +2000 +Enter the change you want to make: 18745 +Following is minimal change for 18745 : +2000 2000 2000 2000 2000 2000 2000 2000 2000 500 200 20 20 5 + +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make: 0 +The total value cannot be zero or negative. +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make: -98 +The total value cannot be zero or negative. + +Do you want to enter your denominations ? (Y/N) :Y +Enter number of denomination:5 +1 +5 +100 +500 +1000 +Enter the change you want to make: 456 +Following is minimal change for 456 : +100 100 100 100 5 5 5 5 5 5 5 5 5 5 5 1 +""" + + +def find_minimum_change(denominations: list[int], value: int) -> list[int]: + """ + Find the minimum change from the given denominations and value + >>> find_minimum_change([1, 5, 10, 20, 50, 100, 200, 500, 1000,2000], 18745) + [2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 500, 200, 20, 20, 5] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], 987) + [500, 100, 100, 100, 100, 50, 20, 10, 5, 2] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], 0) + [] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], -98) + [] + >>> find_minimum_change([1, 5, 100, 500, 1000], 456) + [100, 100, 100, 100, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1] + """ + total_value = int(value) + + # Initialize Result + answer = [] + + # Traverse through all denomination + for denomination in reversed(denominations): + + # Find denominations + while int(total_value) >= int(denomination): + total_value -= int(denomination) + answer.append(denomination) # Append the "answers" array + + return answer + + +# Driver Code +if __name__ == "__main__": + + denominations = list() + value = 0 + + if ( + input("Do you want to enter your denominations ? (yY/n): ").strip().lower() + == "y" + ): + n = int(input("Enter the number of denominations you want to add: ").strip()) + + for i in range(0, n): + denominations.append(int(input(f"Denomination {i}: ").strip())) + value = input("Enter the change you want to make in Indian Currency: ").strip() + else: + # All denominations of Indian Currency if user does not enter + denominations = [1, 2, 5, 10, 20, 50, 100, 500, 2000] + value = input("Enter the change you want to make: ").strip() + + if int(value) == 0 or int(value) < 0: + print("The total value cannot be zero or negative.") + + else: + print(f"Following is minimal change for {value}: ") + answer = find_minimum_change(denominations, value) + # Print result + for i in range(len(answer)): + print(answer[i], end=" ") From 2c6f553ccb671918eb057f1eddea8e9f1b171fb1 Mon Sep 17 00:00:00 2001 From: "Novice :)" <72334601+noviicee@users.noreply.github.com> Date: Sat, 20 Mar 2021 12:02:16 +0530 Subject: [PATCH 1447/2908] [mypy] Fix type annotations for cellular_automata (#4236) * [mypy] Fix type annotations for cellullar_automata * mypy --ignore-missing-imports * mypy --ignore-missing-imports * Blank lines * Blank lines Co-authored-by: Christian Clauss --- cellular_automata/conways_game_of_life.py | 2 +- cellular_automata/one_dimensional.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cellular_automata/conways_game_of_life.py b/cellular_automata/conways_game_of_life.py index dc349b7ac507..321baa3a3794 100644 --- a/cellular_automata/conways_game_of_life.py +++ b/cellular_automata/conways_game_of_life.py @@ -7,7 +7,7 @@ from typing import List -from PIL import Image # type: ignore +from PIL import Image # Define glider example GLIDER = [ diff --git a/cellular_automata/one_dimensional.py b/cellular_automata/one_dimensional.py index 5de2c5b994e3..da77e444502f 100644 --- a/cellular_automata/one_dimensional.py +++ b/cellular_automata/one_dimensional.py @@ -6,7 +6,7 @@ from __future__ import annotations -from PIL import Image # type: ignore +from PIL import Image # Define the first generation of cells # fmt: off From 99a42f2b5821356718fb7d011ac711a6b4a63a83 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Sun, 21 Mar 2021 16:35:10 +0530 Subject: [PATCH 1448/2908] Move files to strings folder (#4283) * Move files to strings-folder * moved the file "words" back to the original folder * moved "anagram.py" also back * fix the codespell ignore-list --- .pre-commit-config.yaml | 4 ++-- {other => strings}/autocomplete_using_trie.py | 0 {other => strings}/detecting_english_programmatically.py | 0 {other => strings}/dictionary.txt | 0 {other => strings}/frequency_finder.py | 0 {other => strings}/palindrome.py | 0 {other => strings}/word_patterns.py | 0 7 files changed, 2 insertions(+), 2 deletions(-) rename {other => strings}/autocomplete_using_trie.py (100%) rename {other => strings}/detecting_english_programmatically.py (100%) rename {other => strings}/dictionary.txt (100%) rename {other => strings}/frequency_finder.py (100%) rename {other => strings}/palindrome.py (100%) rename {other => strings}/word_patterns.py (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d6be7f60f714..ee422e61a03b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,11 +43,11 @@ repos: - id: codespell args: - --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,tim - - --skip="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" + - --skip="./.*,./strings/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" - --quiet-level=2 exclude: | (?x)^( - other/dictionary.txt | + strings/dictionary.txt | other/words | project_euler/problem_022/p022_names.txt )$ diff --git a/other/autocomplete_using_trie.py b/strings/autocomplete_using_trie.py similarity index 100% rename from other/autocomplete_using_trie.py rename to strings/autocomplete_using_trie.py diff --git a/other/detecting_english_programmatically.py b/strings/detecting_english_programmatically.py similarity index 100% rename from other/detecting_english_programmatically.py rename to strings/detecting_english_programmatically.py diff --git a/other/dictionary.txt b/strings/dictionary.txt similarity index 100% rename from other/dictionary.txt rename to strings/dictionary.txt diff --git a/other/frequency_finder.py b/strings/frequency_finder.py similarity index 100% rename from other/frequency_finder.py rename to strings/frequency_finder.py diff --git a/other/palindrome.py b/strings/palindrome.py similarity index 100% rename from other/palindrome.py rename to strings/palindrome.py diff --git a/other/word_patterns.py b/strings/word_patterns.py similarity index 100% rename from other/word_patterns.py rename to strings/word_patterns.py From 14bcb580d55ee90614ad258ea31ef8fe4e4b5c40 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Mon, 22 Mar 2021 12:29:51 +0530 Subject: [PATCH 1449/2908] fix(mypy): Fix annotations for 13 cipher algorithms (#4278) * Initial fix for mypy errors in some cipher algorithms * fix(mypy): Update type hints * fix(mypy): Update type hints for enigma_machine2.py * Update as per the suggestion Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- ciphers/a1z26.py | 8 ++--- ciphers/affine_cipher.py | 42 +++++++++++----------- ciphers/atbash.py | 4 +-- ciphers/base32.py | 2 +- ciphers/base85.py | 2 +- ciphers/beaufort_cipher.py | 2 +- ciphers/brute_force_caesar_cipher.py | 2 +- ciphers/cryptomath_module.py | 4 +-- ciphers/decrypt_caesar_with_chi_squared.py | 30 ++++++++-------- ciphers/diffie.py | 25 ++++++++----- ciphers/elgamal_key_generator.py | 36 +++++++++---------- ciphers/enigma_machine2.py | 24 ++++++++----- ciphers/rsa_key_generator.py | 9 +++-- 13 files changed, 101 insertions(+), 89 deletions(-) diff --git a/ciphers/a1z26.py b/ciphers/a1z26.py index 92710ec44b0e..e6684fb1e6fc 100644 --- a/ciphers/a1z26.py +++ b/ciphers/a1z26.py @@ -7,7 +7,7 @@ """ -def encode(plain: str) -> list: +def encode(plain: str) -> list[int]: """ >>> encode("myname") [13, 25, 14, 1, 13, 5] @@ -15,7 +15,7 @@ def encode(plain: str) -> list: return [ord(elem) - 96 for elem in plain] -def decode(encoded: list) -> str: +def decode(encoded: list[int]) -> str: """ >>> decode([13, 25, 14, 1, 13, 5]) 'myname' @@ -23,8 +23,8 @@ def decode(encoded: list) -> str: return "".join(chr(elem + 96) for elem in encoded) -def main(): - encoded = encode(input("->").strip().lower()) +def main() -> None: + encoded = encode(input("-> ").strip().lower()) print("Encoded: ", encoded) print("Decoded:", decode(encoded)) diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index cf8c0d5f4c1d..d3b806ba1eeb 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -9,26 +9,6 @@ ) -def main(): - """ - >>> 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.startswith("e"): - mode = "encrypt" - translated = encrypt_message(key, message) - elif mode.startswith("d"): - mode = "decrypt" - translated = decrypt_message(key, message) - print(f"\n{mode.title()}ed text: \n{translated}") - - def check_keys(keyA: int, keyB: int, mode: str) -> None: if mode == "encrypt": if keyA == 1: @@ -80,7 +60,7 @@ def decrypt_message(key: int, message: str) -> str: keyA, keyB = divmod(key, len(SYMBOLS)) check_keys(keyA, keyB, "decrypt") plainText = "" - modInverseOfkeyA = cryptomath.findModInverse(keyA, len(SYMBOLS)) + modInverseOfkeyA = cryptomath.find_mod_inverse(keyA, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: symIndex = SYMBOLS.find(symbol) @@ -98,6 +78,26 @@ def get_random_key() -> int: return keyA * len(SYMBOLS) + keyB +def main() -> None: + """ + >>> 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.startswith("e"): + mode = "encrypt" + translated = encrypt_message(key, message) + elif mode.startswith("d"): + mode = "decrypt" + translated = decrypt_message(key, message) + print(f"\n{mode.title()}ed text: \n{translated}") + + if __name__ == "__main__": import doctest diff --git a/ciphers/atbash.py b/ciphers/atbash.py index c17d1e34f37a..5c2aea610bff 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -61,6 +61,6 @@ def benchmark() -> None: if __name__ == "__main__": - for sequence in ("ABCDEFGH", "123GGjj", "testStringtest", "with space"): - print(f"{sequence} encrypted in atbash: {atbash(sequence)}") + for example in ("ABCDEFGH", "123GGjj", "testStringtest", "with space"): + print(f"{example} encrypted in atbash: {atbash(example)}") benchmark() diff --git a/ciphers/base32.py b/ciphers/base32.py index 5bba8c4dd685..da289a7210e8 100644 --- a/ciphers/base32.py +++ b/ciphers/base32.py @@ -1,7 +1,7 @@ import base64 -def main(): +def main() -> None: inp = input("->") encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) b32encoded = base64.b32encode(encoded) # b32encoded the encoded string diff --git a/ciphers/base85.py b/ciphers/base85.py index ebfd0480f794..9740299b9771 100644 --- a/ciphers/base85.py +++ b/ciphers/base85.py @@ -1,7 +1,7 @@ import base64 -def main(): +def main() -> None: inp = input("->") encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) a85encoded = base64.a85encode(encoded) # a85encoded the encoded string diff --git a/ciphers/beaufort_cipher.py b/ciphers/beaufort_cipher.py index c885dec74001..8eae847a7ff7 100644 --- a/ciphers/beaufort_cipher.py +++ b/ciphers/beaufort_cipher.py @@ -66,7 +66,7 @@ def original_text(cipher_text: str, key_new: str) -> str: return or_txt -def main(): +def main() -> None: message = "THE GERMAN ATTACK" key = "SECRET" key_new = generate_key(message, key) diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index 13a165245403..8ab6e77307b4 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -43,7 +43,7 @@ def decrypt(message: str) -> None: print(f"Decryption using Key #{key}: {translated}") -def main(): +def main() -> None: message = input("Encrypted message: ") message = message.upper() decrypt(message) diff --git a/ciphers/cryptomath_module.py b/ciphers/cryptomath_module.py index ffeac1617f64..be8764ff38c3 100644 --- a/ciphers/cryptomath_module.py +++ b/ciphers/cryptomath_module.py @@ -4,9 +4,9 @@ def gcd(a: int, b: int) -> int: return b -def findModInverse(a: int, m: int) -> int: +def find_mod_inverse(a: int, m: int) -> int: if gcd(a, m) != 1: - return None + raise ValueError(f"mod inverse of {a!r} and {m!r} does not exist") u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 41b4a12ba453..e7faeae73773 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 -from typing import Tuple +from typing import Optional def decrypt_caesar_with_chi_squared( ciphertext: str, - cipher_alphabet: str = None, - frequencies_dict: str = None, + cipher_alphabet: Optional[list[str]] = None, + frequencies_dict: Optional[dict[str, float]] = None, case_sensetive: bool = False, -) -> Tuple[int, float, str]: +) -> tuple[int, float, str]: """ Basic Usage =========== @@ -123,9 +123,9 @@ def decrypt_caesar_with_chi_squared( 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 == {}: + # If the argument is None or the user provided an empty dictionary + if not frequencies_dict: # Frequencies of letters in the english language (how much they show up) frequencies = { "a": 0.08497, @@ -163,7 +163,7 @@ def decrypt_caesar_with_chi_squared( ciphertext = ciphertext.lower() # Chi squared statistic values - chi_squared_statistic_values = {} + chi_squared_statistic_values: dict[int, tuple[float, str]] = {} # cycle through all of the shifts for shift in range(len(alphabet_letters)): @@ -215,22 +215,22 @@ def decrypt_caesar_with_chi_squared( chi_squared_statistic += chi_letter_value # Add the data to the chi_squared_statistic_values dictionary - chi_squared_statistic_values[shift] = [ + 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( + most_likely_cipher: int = min( chi_squared_statistic_values, key=chi_squared_statistic_values.get - ) + ) # type: ignore # First argument to `min` is not optional # 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] + ( + most_likely_cipher_chi_squared_value, + decoded_most_likely_cipher, + ) = chi_squared_statistic_values[most_likely_cipher] # Return the data on the most likely shift return ( diff --git a/ciphers/diffie.py b/ciphers/diffie.py index 44b12bf9d103..a23a8104afe2 100644 --- a/ciphers/diffie.py +++ b/ciphers/diffie.py @@ -1,4 +1,7 @@ -def find_primitive(n: int) -> int: +from typing import Optional + + +def find_primitive(n: int) -> Optional[int]: for r in range(1, n): li = [] for x in range(n - 1): @@ -8,18 +11,22 @@ def find_primitive(n: int) -> int: li.append(val) else: return r + return None 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) + if a is None: + print(f"Cannot find the primitive for the value: {a!r}") + else: + 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) + 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/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 52cf69074187..f557b0e0dc91 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -2,24 +2,18 @@ import random import sys -from . import cryptomath_module as cryptoMath -from . import rabin_miller as rabinMiller +from . import cryptomath_module as cryptomath +from . import rabin_miller min_primitive_root = 3 -def main(): - print("Making key files...") - makeKeyFiles("elgamal", 2048) - print("Key files generation successful") - - # 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) # and it seems to run nicely! -def primitiveRoot(p_val: int) -> int: +def primitive_root(p_val: int) -> int: print("Generating primitive root of p") while True: g = random.randrange(3, p_val) @@ -30,20 +24,20 @@ def primitiveRoot(p_val: int) -> int: return g -def generateKey(keySize: int) -> ((int, int, int, int), (int, int)): +def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, int]]: print("Generating prime p...") - p = rabinMiller.generateLargePrime(keySize) # select large prime number. - e_1 = primitiveRoot(p) # one primitive root on modulo p. + p = rabin_miller.generateLargePrime(key_size) # select large prime number. + e_1 = primitive_root(p) # one primitive root on modulo p. d = random.randrange(3, p) # private_key -> have to be greater than 2 for safety. - e_2 = cryptoMath.findModInverse(pow(e_1, d, p), p) + e_2 = cryptomath.find_mod_inverse(pow(e_1, d, p), p) - publicKey = (keySize, e_1, e_2, p) - privateKey = (keySize, d) + public_key = (key_size, e_1, e_2, p) + private_key = (key_size, d) - return publicKey, privateKey + return public_key, private_key -def makeKeyFiles(name: str, keySize: int): +def make_key_files(name: str, keySize: int) -> None: if os.path.exists("%s_pubkey.txt" % name) or os.path.exists( "%s_privkey.txt" % name ): @@ -55,7 +49,7 @@ def makeKeyFiles(name: str, keySize: int): ) sys.exit() - publicKey, privateKey = generateKey(keySize) + publicKey, privateKey = generate_key(keySize) print("\nWriting public key to file %s_pubkey.txt..." % name) with open("%s_pubkey.txt" % name, "w") as fo: fo.write( @@ -67,5 +61,11 @@ def makeKeyFiles(name: str, keySize: int): fo.write("%d,%d" % (privateKey[0], privateKey[1])) +def main() -> None: + print("Making key files...") + make_key_files("elgamal", 2048) + print("Key files generation successful") + + if __name__ == "__main__": main() diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 4344db0056fd..f4ce5a075f46 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -15,6 +15,10 @@ Created by TrapinchO """ +RotorPositionT = tuple[int, int, int] +RotorSelectionT = tuple[str, str, str] + + # used alphabet -------------------------- # from string.ascii_uppercase abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -63,7 +67,9 @@ rotor9 = "KOAEGVDHXPQZMLFTYWJNBRCIUS" -def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: +def _validator( + rotpos: RotorPositionT, rotsel: RotorSelectionT, pb: str +) -> tuple[RotorPositionT, RotorSelectionT, dict[str, str]]: """ Checks if the values can be used for the 'enigma' function @@ -99,12 +105,12 @@ def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: ) # Validates string and returns dict - pb = _plugboard(pb) + pbdict = _plugboard(pb) - return rotpos, rotsel, pb + return rotpos, rotsel, pbdict -def _plugboard(pbstring: str) -> dict: +def _plugboard(pbstring: str) -> dict[str, str]: """ https://en.wikipedia.org/wiki/Enigma_machine#Plugboard @@ -145,17 +151,17 @@ def _plugboard(pbstring: str) -> dict: # 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] + for j in range(0, len(pbstring) - 1, 2): + pb[pbstring[j]] = pbstring[j + 1] + pb[pbstring[j + 1]] = pbstring[j] return pb def enigma( text: str, - rotor_position: tuple, - rotor_selection: tuple = (rotor1, rotor2, rotor3), + rotor_position: RotorPositionT, + rotor_selection: RotorSelectionT = (rotor1, rotor2, rotor3), plugb: str = "", ) -> str: """ diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index e456d9d9f6f1..584066d8970f 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -1,19 +1,18 @@ import os import random import sys -from typing import Tuple from . import cryptomath_module as cryptoMath from . import rabin_miller as rabinMiller -def main(): +def main() -> None: print("Making key files...") makeKeyFiles("rsa", 1024) print("Key files generation successful.") -def generateKey(keySize: int) -> Tuple[Tuple[int, int], Tuple[int, int]]: +def generateKey(keySize: int) -> tuple[tuple[int, int], tuple[int, int]]: print("Generating prime p...") p = rabinMiller.generateLargePrime(keySize) print("Generating prime q...") @@ -27,14 +26,14 @@ def generateKey(keySize: int) -> Tuple[Tuple[int, int], Tuple[int, int]]: break print("Calculating d that is mod inverse of e...") - d = cryptoMath.findModInverse(e, (p - 1) * (q - 1)) + d = cryptoMath.find_mod_inverse(e, (p - 1) * (q - 1)) publicKey = (n, e) privateKey = (n, d) return (publicKey, privateKey) -def makeKeyFiles(name: int, keySize: int) -> None: +def makeKeyFiles(name: str, keySize: int) -> None: if os.path.exists("%s_pubkey.txt" % (name)) or os.path.exists( "%s_privkey.txt" % (name) ): From 8d51c2cfd917b85b95de4488ebb157549b42af3c Mon Sep 17 00:00:00 2001 From: algobytewise Date: Mon, 22 Mar 2021 15:22:26 +0530 Subject: [PATCH 1450/2908] move-files-and-2-renames (#4285) --- {other => maths}/binary_exponentiation_2.py | 0 .../binary_exponentiation_3.py | 0 {other => maths}/euclidean_gcd.py | 0 .../integration_by_simpson_approx.py | 0 {other => maths}/largest_subarray_sum.py | 0 {other => maths}/max_sum_sliding_window.py | 90 ++++----- {other => maths}/median_of_two_arrays.py | 0 {other => maths}/primelib.py | 0 {other => maths}/triplet_sum.py | 178 +++++++++--------- {other => maths}/two_pointer.py | 0 {other => maths}/two_sum.py | 0 11 files changed, 134 insertions(+), 134 deletions(-) rename {other => maths}/binary_exponentiation_2.py (100%) rename other/binary_exponentiation.py => maths/binary_exponentiation_3.py (100%) rename {other => maths}/euclidean_gcd.py (100%) rename other/integeration_by_simpson_approx.py => maths/integration_by_simpson_approx.py (100%) rename {other => maths}/largest_subarray_sum.py (100%) rename {other => maths}/max_sum_sliding_window.py (96%) rename {other => maths}/median_of_two_arrays.py (100%) rename {other => maths}/primelib.py (100%) rename {other => maths}/triplet_sum.py (96%) rename {other => maths}/two_pointer.py (100%) rename {other => maths}/two_sum.py (100%) diff --git a/other/binary_exponentiation_2.py b/maths/binary_exponentiation_2.py similarity index 100% rename from other/binary_exponentiation_2.py rename to maths/binary_exponentiation_2.py diff --git a/other/binary_exponentiation.py b/maths/binary_exponentiation_3.py similarity index 100% rename from other/binary_exponentiation.py rename to maths/binary_exponentiation_3.py diff --git a/other/euclidean_gcd.py b/maths/euclidean_gcd.py similarity index 100% rename from other/euclidean_gcd.py rename to maths/euclidean_gcd.py diff --git a/other/integeration_by_simpson_approx.py b/maths/integration_by_simpson_approx.py similarity index 100% rename from other/integeration_by_simpson_approx.py rename to maths/integration_by_simpson_approx.py diff --git a/other/largest_subarray_sum.py b/maths/largest_subarray_sum.py similarity index 100% rename from other/largest_subarray_sum.py rename to maths/largest_subarray_sum.py diff --git a/other/max_sum_sliding_window.py b/maths/max_sum_sliding_window.py similarity index 96% rename from other/max_sum_sliding_window.py rename to maths/max_sum_sliding_window.py index 4be7d786f215..593cb5c8bd67 100644 --- a/other/max_sum_sliding_window.py +++ b/maths/max_sum_sliding_window.py @@ -1,45 +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)}") +""" +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)}") diff --git a/other/median_of_two_arrays.py b/maths/median_of_two_arrays.py similarity index 100% rename from other/median_of_two_arrays.py rename to maths/median_of_two_arrays.py diff --git a/other/primelib.py b/maths/primelib.py similarity index 100% rename from other/primelib.py rename to maths/primelib.py diff --git a/other/triplet_sum.py b/maths/triplet_sum.py similarity index 96% rename from other/triplet_sum.py rename to maths/triplet_sum.py index 0e78bb52bb72..22fab17d30c2 100644 --- a/other/triplet_sum.py +++ b/maths/triplet_sum.py @@ -1,89 +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 __future__ import annotations - -from itertools import permutations -from random import randint -from timeit import repeat - - -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 the 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 the 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 - 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]}.") +""" +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 __future__ import annotations + +from itertools import permutations +from random import randint +from timeit import repeat + + +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 the 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 the 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 + 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]}.") diff --git a/other/two_pointer.py b/maths/two_pointer.py similarity index 100% rename from other/two_pointer.py rename to maths/two_pointer.py diff --git a/other/two_sum.py b/maths/two_sum.py similarity index 100% rename from other/two_sum.py rename to maths/two_sum.py From ce99859ad54cd4f0a43a78fdc6cf41cb3e336dc2 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Mon, 22 Mar 2021 15:24:04 +0530 Subject: [PATCH 1451/2908] Move files to various folders (#4286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move files to cellular_automata * Rename other/davis–putnam–logemann–loveland.py to backtracking/davis–putnam–logemann–loveland.py * Rename other/markov_chain.py to graphs/markov_chain.py * undid rename: need to fix mypy first --- {other => cellular_automata}/game_of_life.py | 0 {other => graphs}/markov_chain.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {other => cellular_automata}/game_of_life.py (100%) rename {other => graphs}/markov_chain.py (100%) diff --git a/other/game_of_life.py b/cellular_automata/game_of_life.py similarity index 100% rename from other/game_of_life.py rename to cellular_automata/game_of_life.py diff --git a/other/markov_chain.py b/graphs/markov_chain.py similarity index 100% rename from other/markov_chain.py rename to graphs/markov_chain.py From 0ee8f792e3105cda122ecb8696575eb0354b09bb Mon Sep 17 00:00:00 2001 From: algobytewise Date: Mon, 22 Mar 2021 16:10:23 +0530 Subject: [PATCH 1452/2908] Moved "other/anagrams.py" to the string folder (#4289) * move&rename, changed code accordingly * adjusted codespell ignore-list --- .pre-commit-config.yaml | 4 ++-- {other => strings}/anagrams.py | 2 +- other/words => strings/words.txt | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename {other => strings}/anagrams.py (95%) rename other/words => strings/words.txt (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee422e61a03b..b48da86ee57d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,12 +43,12 @@ repos: - id: codespell args: - --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,tim - - --skip="./.*,./strings/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" + - --skip="./.*,./strings/dictionary.txt,./strings/words.txt,./project_euler/problem_022/p022_names.txt" - --quiet-level=2 exclude: | (?x)^( strings/dictionary.txt | - other/words | + strings/words.txt | project_euler/problem_022/p022_names.txt )$ - repo: local diff --git a/other/anagrams.py b/strings/anagrams.py similarity index 95% rename from other/anagrams.py rename to strings/anagrams.py index 0be013d5bc47..1a7c675d6719 100644 --- a/other/anagrams.py +++ b/strings/anagrams.py @@ -6,7 +6,7 @@ start_time = time.time() print("creating word list...") path = os.path.split(os.path.realpath(__file__)) -with open(path[0] + "/words") as f: +with open(path[0] + "/words.txt") as f: word_list = sorted(list({word.strip().lower() for word in f})) diff --git a/other/words b/strings/words.txt similarity index 100% rename from other/words rename to strings/words.txt From a8db5d4b93410d8ad3c281d1edd9ce26c6e087e0 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Mon, 22 Mar 2021 23:54:05 +0530 Subject: [PATCH 1453/2908] [mypy] fix compression folder (#4290) * Update lempel_ziv.py * Update build.yml * updating DIRECTORY.md * fix doctest in 2_hidden_layers_neural_network.py * one more doctest * simplified tests Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 1 + DIRECTORY.md | 40 +++++++++---------- compression/lempel_ziv.py | 2 +- .../2_hidden_layers_neural_network.py | 8 ++-- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e8d04126002..c85b82330c67 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,7 @@ jobs: blockchain boolean_algebra cellular_automata + compression computer_vision fractals fuzzy_logic diff --git a/DIRECTORY.md b/DIRECTORY.md index 2f57a9db5769..f5297db05fe1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -46,6 +46,7 @@ ## Cellular Automata * [Conways Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/conways_game_of_life.py) + * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/game_of_life.py) * [One Dimensional](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/one_dimensional.py) ## Ciphers @@ -322,6 +323,7 @@ * [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) + * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/graphs/markov_chain.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) @@ -407,6 +409,8 @@ * [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) + * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation_2.py) + * [Binary Exponentiation 3](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation_3.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) @@ -417,8 +421,9 @@ * [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) + * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_gcd.py) + * [Euler Method](https://github.com/TheAlgorithms/Python/blob/master/maths/euler_method.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 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) @@ -437,6 +442,7 @@ * [Greatest Common Divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greatest_common_divisor.py) * [Greedy Coin Change](https://github.com/TheAlgorithms/Python/blob/master/maths/greedy_coin_change.py) * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) + * [Integration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/maths/integration_by_simpson_approx.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) @@ -444,11 +450,14 @@ * [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) + * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_subarray_sum.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) + * [Max Sum Sliding Window](https://github.com/TheAlgorithms/Python/blob/master/maths/max_sum_sliding_window.py) + * [Median Of Two Arrays](https://github.com/TheAlgorithms/Python/blob/master/maths/median_of_two_arrays.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) @@ -467,6 +476,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) + * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/maths/primelib.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) @@ -491,6 +501,9 @@ * [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) + * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/triplet_sum.py) + * [Two Pointer](https://github.com/TheAlgorithms/Python/blob/master/maths/two_pointer.py) + * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/two_sum.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) @@ -520,42 +533,23 @@ ## 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) * [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) - * [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) * [Gauss Easter](https://github.com/TheAlgorithms/Python/blob/master/other/gauss_easter.py) * [Graham Scan](https://github.com/TheAlgorithms/Python/blob/master/other/graham_scan.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) * [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) - * [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) - * [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) * [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) ## Project Euler * Problem 001 @@ -885,11 +879,15 @@ ## Strings * [Aho Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho_corasick.py) + * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/anagrams.py) + * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/strings/autocomplete_using_trie.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) + * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/strings/detecting_english_programmatically.py) + * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/strings/frequency_finder.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) @@ -898,6 +896,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) + * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/palindrome.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) @@ -907,6 +906,7 @@ * [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) + * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/strings/word_patterns.py) * [Z Function](https://github.com/TheAlgorithms/Python/blob/master/strings/z_function.py) ## Traversals diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py index 2d0601b27b34..6743dc42d56e 100644 --- a/compression/lempel_ziv.py +++ b/compression/lempel_ziv.py @@ -26,7 +26,7 @@ def read_file_binary(file_path: str) -> str: def add_key_to_lexicon( - lexicon: dict, curr_string: str, index: int, last_match_id: int + lexicon: dict, curr_string: str, index: int, last_match_id: str ) -> None: """ Adds new strings (curr_string + "0", curr_string + "1") to the lexicon diff --git a/neural_network/2_hidden_layers_neural_network.py b/neural_network/2_hidden_layers_neural_network.py index baa4316200d9..1cf78ec4c7c0 100644 --- a/neural_network/2_hidden_layers_neural_network.py +++ b/neural_network/2_hidden_layers_neural_network.py @@ -196,8 +196,8 @@ def predict(self, input: numpy.ndarray) -> int: >>> 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 + >>> nn.predict([0,1,0]) in (0, 1) + True """ # Input values for which the predictions are to be made. @@ -260,8 +260,8 @@ def example() -> int: 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 + >>> example() in (0, 1) + True """ # Input values. input = numpy.array( From 959507901ac8f10cd605c51c305d13b27d105536 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Tue, 23 Mar 2021 21:21:50 +0530 Subject: [PATCH 1454/2908] [mypy] fix small folders (#4292) * add final else-statement * fix file_transfer * fix quantum folder * fix divide_and_conquer-folder * Update build.yml * updating DIRECTORY.md * Update ripple_adder_classic.py * Update .github/workflows/build.yml * removed imports from typing * removed conversion to string * Revert "removed conversion to string" This reverts commit 2f7c4731d103f24c73fb98c9a6898525998774c5. * implemented suggested changes * Update receive_file.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 7 ++++++- divide_and_conquer/max_difference_pair.py | 5 +---- divide_and_conquer/strassen_matrix_multiplication.py | 2 +- electronics/electric_power.py | 2 ++ electronics/ohms_law.py | 2 ++ file_transfer/receive_file.py | 2 +- file_transfer/send_file.py | 2 +- quantum/ripple_adder_classic.py | 2 +- 8 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c85b82330c67..74b885b90343 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,13 +30,18 @@ jobs: cellular_automata compression computer_vision + divide_and_conquer + electronics + file_transfer fractals fuzzy_logic genetic_algorithm geodesy knapsack networking_flow - scheduling sorts + quantum + scheduling + sorts - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/divide_and_conquer/max_difference_pair.py b/divide_and_conquer/max_difference_pair.py index b976aca43137..ffc4b76a7154 100644 --- a/divide_and_conquer/max_difference_pair.py +++ b/divide_and_conquer/max_difference_pair.py @@ -1,7 +1,4 @@ -from typing import List - - -def max_difference(a: List[int]) -> (int, int): +def max_difference(a: list[int]) -> tuple[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 diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index 29a174daebf9..ca10e04abcbc 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -121,7 +121,7 @@ def strassen(matrix1: list, matrix2: list) -> list: dimension2 = matrix_dimensions(matrix2) if dimension1[0] == dimension1[1] and dimension2[0] == dimension2[1]: - return matrix1, matrix2 + return [matrix1, matrix2] maximum = max(max(dimension1), max(dimension2)) maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) diff --git a/electronics/electric_power.py b/electronics/electric_power.py index 8f0293bd2d10..e4e685bbd0f0 100644 --- a/electronics/electric_power.py +++ b/electronics/electric_power.py @@ -42,6 +42,8 @@ def electric_power(voltage: float, current: float, power: float) -> Tuple: return result("current", power / voltage) elif power == 0: return result("power", float(round(abs(voltage * current), 2))) + else: + raise ValueError("Exactly one argument must be 0") if __name__ == "__main__": diff --git a/electronics/ohms_law.py b/electronics/ohms_law.py index c53619a10935..41bffa9f87c8 100644 --- a/electronics/ohms_law.py +++ b/electronics/ohms_law.py @@ -32,6 +32,8 @@ def ohms_law(voltage: float, current: float, resistance: float) -> Dict[str, flo return {"current": voltage / resistance} elif resistance == 0: return {"resistance": voltage / current} + else: + raise ValueError("Exactly one argument must be 0") if __name__ == "__main__": diff --git a/file_transfer/receive_file.py b/file_transfer/receive_file.py index cfba6ed88484..37a503036dc2 100644 --- a/file_transfer/receive_file.py +++ b/file_transfer/receive_file.py @@ -13,7 +13,7 @@ print("Receiving data...") while True: data = sock.recv(1024) - print(f"data={data}") + print(f"{data = }") if not data: break out_file.write(data) # Write data to a file diff --git a/file_transfer/send_file.py b/file_transfer/send_file.py index 5b53471dfb50..1c56e48f47a1 100644 --- a/file_transfer/send_file.py +++ b/file_transfer/send_file.py @@ -13,7 +13,7 @@ def send_file(filename: str = "mytext.txt", testing: bool = False) -> None: conn, addr = sock.accept() # Establish connection with client. print(f"Got connection from {addr}") data = conn.recv(1024) - print(f"Server received {data}") + print(f"Server received: {data = }") with open(filename, "rb") as in_file: data = in_file.read(1024) diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py index f5b0a980c8e2..dc0c2103b2e5 100644 --- a/quantum/ripple_adder_classic.py +++ b/quantum/ripple_adder_classic.py @@ -6,7 +6,7 @@ from qiskit.providers import BaseBackend -def store_two_classics(val1: int, val2: int) -> (QuantumCircuit, str, str): +def store_two_classics(val1: int, val2: int) -> tuple[QuantumCircuit, str, str]: """ Generates a Quantum Circuit which stores two classical integers Returns the circuit and binary representation of the integers From 9b60be67afca18f0d5e50e532096a68605d61b81 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Fri, 26 Mar 2021 16:51:16 +0530 Subject: [PATCH 1455/2908] [mypy] fix small folders 2 (#4293) * Update perceptron.py * Update binary_tree_traversals.py * fix machine_learning * Update build.yml * Update perceptron.py * Update machine_learning/forecasting/run.py Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 3 +++ machine_learning/forecasting/run.py | 3 +-- machine_learning/k_means_clust.py | 2 +- machine_learning/word_frequency_functions.py | 2 +- neural_network/perceptron.py | 21 +++++++++++++------- traversals/binary_tree_traversals.py | 4 ++-- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74b885b90343..87cc8b67341d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,10 +38,13 @@ jobs: genetic_algorithm geodesy knapsack + machine_learning networking_flow + neural_network quantum scheduling sorts + traversals - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py index 0e11f958825f..b11a230129eb 100644 --- a/machine_learning/forecasting/run.py +++ b/machine_learning/forecasting/run.py @@ -29,8 +29,7 @@ def linear_regression_prediction( >>> 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) + x = np.array([[1, item, train_mtch[i]] for i, item in enumerate(train_dt)]) 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]) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index f155d4845f41..c45be8a4c064 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -200,7 +200,7 @@ def kmeans( def ReportGenerator( - df: pd.DataFrame, ClusteringVariables: np.array, FillMissingReport=None + df: pd.DataFrame, ClusteringVariables: np.ndarray, FillMissingReport=None ) -> pd.DataFrame: """ Function generates easy-erading clustering report. It takes 2 arguments as an input: diff --git a/machine_learning/word_frequency_functions.py b/machine_learning/word_frequency_functions.py index 9cf7b694c6be..3e8faf39cf07 100644 --- a/machine_learning/word_frequency_functions.py +++ b/machine_learning/word_frequency_functions.py @@ -61,7 +61,7 @@ def term_frequency(term: str, document: str) -> int: return len([word for word in tokenize_document if word.lower() == term.lower()]) -def document_frequency(term: str, corpus: str) -> int: +def document_frequency(term: str, corpus: str) -> tuple[int, int]: """ Calculate the number of documents in a corpus that contain a given term diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 23b409b227c4..063be5ea554c 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -11,7 +11,14 @@ class Perceptron: - def __init__(self, sample, target, learning_rate=0.01, epoch_number=1000, bias=-1): + def __init__( + self, + sample: list[list[float]], + target: list[int], + learning_rate: float = 0.01, + epoch_number: int = 1000, + bias: float = -1, + ) -> None: """ Initializes a Perceptron network for oil analysis :param sample: sample dataset of 3 parameters with shape [30,3] @@ -46,7 +53,7 @@ def __init__(self, sample, target, learning_rate=0.01, epoch_number=1000, bias=- self.bias = bias self.number_sample = len(sample) self.col_sample = len(sample[0]) # number of columns in dataset - self.weight = [] + self.weight: list = [] def training(self) -> None: """ @@ -94,7 +101,7 @@ def training(self) -> None: # if epoch_count > self.epoch_number or not error: break - def sort(self, sample) -> None: + def sort(self, sample: list[float]) -> None: """ :param sample: example row to classify as P1 or P2 :return: None @@ -221,11 +228,11 @@ def sign(self, u: float) -> int: print("Finished training perceptron") print("Enter values to predict or q to exit") while True: - sample = [] + sample: list = [] for i in range(len(samples[0])): - observation = input("value: ").strip() - if observation == "q": + user_input = input("value: ").strip() + if user_input == "q": break - observation = float(observation) + observation = float(user_input) sample.insert(i, observation) network.sort(sample) diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index cb471ba55bac..f919a2962354 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -188,7 +188,7 @@ def pre_order_iter(node: TreeNode) -> None: """ if not isinstance(node, TreeNode) or not node: return - stack: List[TreeNode] = [] + stack: list[TreeNode] = [] n = node while n or stack: while n: # start from root node, find its left child @@ -218,7 +218,7 @@ def in_order_iter(node: TreeNode) -> None: """ if not isinstance(node, TreeNode) or not node: return - stack: List[TreeNode] = [] + stack: list[TreeNode] = [] n = node while n or stack: while n: From 35901eb6febe856007d9a0f34f3881eeb58663d6 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Sat, 27 Mar 2021 14:48:48 +0530 Subject: [PATCH 1456/2908] Move: traversals/binary_tree_traversals.py --> searches/binary_tree_traversal.py (#4295) * Rename traversals/binary_tree_traversals.py to searches/binary_tree_traversal.py * updating DIRECTORY.md * Delete traversals directory * Update build.yml Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 1 - DIRECTORY.md | 4 +--- .../binary_tree_traversal.py | 0 traversals/__init__.py | 0 4 files changed, 1 insertion(+), 4 deletions(-) rename traversals/binary_tree_traversals.py => searches/binary_tree_traversal.py (100%) delete mode 100644 traversals/__init__.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87cc8b67341d..7273119302e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,6 @@ jobs: quantum scheduling sorts - traversals - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/DIRECTORY.md b/DIRECTORY.md index f5297db05fe1..42a6c49c735f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -817,6 +817,7 @@ ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) + * [Binary Tree Traversal](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_tree_traversal.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) @@ -909,9 +910,6 @@ * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/strings/word_patterns.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) - ## 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) diff --git a/traversals/binary_tree_traversals.py b/searches/binary_tree_traversal.py similarity index 100% rename from traversals/binary_tree_traversals.py rename to searches/binary_tree_traversal.py diff --git a/traversals/__init__.py b/traversals/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 From c22c7d503be5f48ae257c648f7b83b8a80a02738 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 31 Mar 2021 05:02:25 +0200 Subject: [PATCH 1457/2908] mypy: Use a --exclude list (#4296) * mypy: Use a --exclude list * Graphics works on my machine * A few more... * A few more... * Update build.yml --- .github/workflows/build.yml | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7273119302e2..f544c02b1c35 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,29 +21,9 @@ jobs: run: | python -m pip install --upgrade pip setuptools six wheel python -m pip install mypy pytest-cov -r requirements.txt - # FIXME: #4052 fix mypy errors in other directories and add them here + # FIXME: #4052 fix mypy errors in the exclude directories and remove them below - run: mypy --ignore-missing-imports - backtracking - bit_manipulation - blockchain - boolean_algebra - cellular_automata - compression - computer_vision - divide_and_conquer - electronics - file_transfer - fractals - fuzzy_logic - genetic_algorithm - geodesy - knapsack - machine_learning - networking_flow - neural_network - quantum - scheduling - sorts + --exclude '(arithmetic_analysis|ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|hashes|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings|web_programming*)/$' . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} From 895bca36541598a04dba525568a20d2282e0ffd9 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 31 Mar 2021 05:18:07 +0200 Subject: [PATCH 1458/2908] [mypy] Fix web_programming directory (#4297) * Update world_covid19_stats.py * Delete monkeytype_config.py * updating DIRECTORY.md * Apply pyannotate suggestions to emails_from_url.py * mypy web_programming/emails_from_url.py * super().__init__() * mypy --ignore-missing-imports web_programming/emails_from_url.py * Update emails_from_url.py * self.urls: list[str] = [] * mypy: Fix web_programming directory Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Dhruv Manilawala --- .github/workflows/build.yml | 2 +- web_programming/currency_converter.py | 2 +- web_programming/emails_from_url.py | 19 ++++++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f544c02b1c35..76c6357fe0ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: python -m pip install mypy pytest-cov -r requirements.txt # FIXME: #4052 fix mypy errors in the exclude directories and remove them below - run: mypy --ignore-missing-imports - --exclude '(arithmetic_analysis|ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|hashes|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings|web_programming*)/$' . + --exclude '(arithmetic_analysis|ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|hashes|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py index 6aed2a5578a5..447595b0b646 100644 --- a/web_programming/currency_converter.py +++ b/web_programming/currency_converter.py @@ -9,7 +9,7 @@ URL_BASE = "https://www.amdoren.com/api/currency.php" TESTING = os.getenv("CI", False) -API_KEY = os.getenv("AMDOREN_API_KEY") +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.") diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index 01dee274f015..0571ac3313a3 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -8,18 +8,19 @@ import re from html.parser import HTMLParser +from typing import Optional from urllib import parse import requests class Parser(HTMLParser): - def __init__(self, domain: str): - HTMLParser.__init__(self) - self.data = [] + def __init__(self, domain: str) -> None: + super().__init__() + self.urls: list[str] = [] self.domain = domain - def handle_starttag(self, tag: str, attrs: str) -> None: + def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]) -> None: """ This function parse html to take takes url from tags """ @@ -29,10 +30,10 @@ def handle_starttag(self, tag: str, attrs: str) -> None: 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: + # If not already in urls. + if value not in self.urls: url = parse.urljoin(self.domain, value) - self.data.append(url) + self.urls.append(url) # Get main domain name (example.com) @@ -59,7 +60,7 @@ def get_sub_domain_name(url: str) -> str: return parse.urlparse(url).netloc -def emails_from_url(/service/url: str = "https://github.com/") -> list: +def emails_from_url(/service/url: str = "https://github.com/") -> list[str]: """ This function takes url and return all valid urls """ @@ -78,7 +79,7 @@ def emails_from_url(/service/url: str = "https://github.com/") -> list: # Get links and loop through valid_emails = set() - for link in parser.data: + for link in parser.urls: # open URL. # read = requests.get(link) try: From 5229c749553d9ec65d455e0183a574e45ac3e73e Mon Sep 17 00:00:00 2001 From: algobytewise Date: Fri, 2 Apr 2021 13:02:12 +0530 Subject: [PATCH 1459/2908] [mypy] Fix directory arithmetic_analysis (#4304) * fix directory arithmetic_analysis * Update build.yml * temporary fix for psf/black bug see https://github.com/psf/black/issues/2079 * Update in_static_equilibrium.py --- .github/workflows/build.yml | 2 +- .github/workflows/pre-commit.yml | 2 +- arithmetic_analysis/gaussian_elimination.py | 6 +++--- arithmetic_analysis/in_static_equilibrium.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76c6357fe0ca..e66b94b1a074 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: python -m pip install mypy pytest-cov -r requirements.txt # FIXME: #4052 fix mypy errors in the exclude directories and remove them below - run: mypy --ignore-missing-imports - --exclude '(arithmetic_analysis|ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|hashes|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . + --exclude '(ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|hashes|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 17fdad1204e9..dd1a8a945092 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,7 +14,7 @@ jobs: ~/.cache/pip key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/setup-python@v2 - - uses: psf/black@stable + - uses: psf/black@20.8b1 - name: Install pre-commit run: | python -m pip install --upgrade pip diff --git a/arithmetic_analysis/gaussian_elimination.py b/arithmetic_analysis/gaussian_elimination.py index 51207686c12a..2dada4fbf9b1 100644 --- a/arithmetic_analysis/gaussian_elimination.py +++ b/arithmetic_analysis/gaussian_elimination.py @@ -7,7 +7,7 @@ import numpy as np -def retroactive_resolution(coefficients: np.matrix, vector: np.array) -> np.array: +def retroactive_resolution(coefficients: np.matrix, vector: np.ndarray) -> np.ndarray: """ This function performs a retroactive linear system resolution for triangular matrix @@ -38,7 +38,7 @@ def retroactive_resolution(coefficients: np.matrix, vector: np.array) -> np.arra return x -def gaussian_elimination(coefficients: np.matrix, vector: np.array) -> np.array: +def gaussian_elimination(coefficients: np.matrix, vector: np.ndarray) -> np.ndarray: """ This function performs Gaussian elimination method @@ -57,7 +57,7 @@ def gaussian_elimination(coefficients: np.matrix, vector: np.array) -> np.array: # coefficients must to be a square matrix so we need to check first rows, columns = np.shape(coefficients) if rows != columns: - return [] + return np.array((), dtype=float) # augmented matrix augmented_mat = np.concatenate((coefficients, vector), axis=1) diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index 9b2892151850..7b5006a1a82c 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -3,7 +3,7 @@ """ from typing import List -from numpy import array, cos, cross, radians, sin +from numpy import array, cos, cross, ndarray, radians, sin def polar_force( @@ -23,7 +23,7 @@ def polar_force( def in_static_equilibrium( - forces: array, location: array, eps: float = 10 ** -1 + forces: ndarray, location: ndarray, eps: float = 10 ** -1 ) -> bool: """ Check if a system is in equilibrium. @@ -42,7 +42,7 @@ def in_static_equilibrium( False """ # summation of moments is zero - moments: array = cross(location, forces) + moments: ndarray = cross(location, forces) sum_moments: float = sum(moments) return abs(sum_moments) < eps From a53fcf221bc1d361b26c055aa12be38c4e8b2022 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Sat, 3 Apr 2021 13:31:46 +0530 Subject: [PATCH 1460/2908] [mypy] fix hashes folder (#4305) * fix hashes-folder * Update build.yml * fix doctests * return-values to int * Update hashes/adler32.py * type hints for elements Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 2 +- hashes/adler32.py | 4 ++-- hashes/chaos_machine.py | 3 ++- hashes/enigma_machine.py | 7 +++---- hashes/sdbm.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e66b94b1a074..c1aeaa031565 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: python -m pip install mypy pytest-cov -r requirements.txt # FIXME: #4052 fix mypy errors in the exclude directories and remove them below - run: mypy --ignore-missing-imports - --exclude '(ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|hashes|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . + --exclude '(ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/hashes/adler32.py b/hashes/adler32.py index fad747abe3c3..4a61b97e3590 100644 --- a/hashes/adler32.py +++ b/hashes/adler32.py @@ -9,10 +9,10 @@ """ -def adler32(plain_text: str) -> str: +def adler32(plain_text: str) -> int: """ Function implements adler-32 hash. - Itterates and evaluates new value for each character + Iterates and evaluates a new value for each character >>> adler32('Algorithms') 363791387 diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 1bdf984b68de..7ef4fdb3ca51 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -6,7 +6,8 @@ m = 5 # Buffer Space (with Parameters Space) -buffer_space, params_space = [], [] +buffer_space: list[float] = [] +params_space: list[float] = [] # Machine Time machine_time = 0 diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py index 5420bacc1409..d1cb6efc2e8d 100644 --- a/hashes/enigma_machine.py +++ b/hashes/enigma_machine.py @@ -41,8 +41,7 @@ def engine(input_character): if __name__ == "__main__": - decode = input("Type your message:\n") - decode = list(decode) + decode = list(input("Type your message:\n")) while True: try: token = int(input("Please set token:(must be only digits)\n")) @@ -51,8 +50,8 @@ def engine(input_character): print(error) for i in range(token): rotator() - for i in decode: - engine(i) + for j in decode: + engine(j) print("\n" + "".join(code)) print( f"\nYour Token is {token} please write it down.\nIf you want to decode " diff --git a/hashes/sdbm.py b/hashes/sdbm.py index 86d47a1d9967..daf292717f75 100644 --- a/hashes/sdbm.py +++ b/hashes/sdbm.py @@ -19,7 +19,7 @@ """ -def sdbm(plain_text: str) -> str: +def sdbm(plain_text: str) -> int: """ 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 From e7e6cbfb8ff4a85be123c124d8ea0a449afe9f9c Mon Sep 17 00:00:00 2001 From: Elisha Hollander Date: Sun, 4 Apr 2021 06:55:57 +0300 Subject: [PATCH 1461/2908] refactor: Remove "redefinition" of dict element (#4309) --- project_euler/problem_074/sol1.py | 1 - 1 file changed, 1 deletion(-) diff --git a/project_euler/problem_074/sol1.py b/project_euler/problem_074/sol1.py index 5e6aff6f52f2..38d4e1439307 100644 --- a/project_euler/problem_074/sol1.py +++ b/project_euler/problem_074/sol1.py @@ -51,7 +51,6 @@ 871: 2, 45361: 2, 872: 2, - 45361: 2, } From 0992498a107267ccbfc7ab2c72045c31a537ab25 Mon Sep 17 00:00:00 2001 From: Elisha Hollander Date: Sun, 4 Apr 2021 07:00:17 +0300 Subject: [PATCH 1462/2908] refactor: Remove unnecessary if else condition (#4307) All the operation is being done in an else condition for "if number >= 0" --- bit_manipulation/binary_shifts.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bit_manipulation/binary_shifts.py b/bit_manipulation/binary_shifts.py index fe62880f941c..50dc27ffeef6 100644 --- a/bit_manipulation/binary_shifts.py +++ b/bit_manipulation/binary_shifts.py @@ -91,9 +91,7 @@ def arithmetic_right_shift(number: int, shift_amount: int) -> str: binary_number_length = len(bin(number)[3:]) # Find 2's complement of number binary_number = bin(abs(number) - (1 << binary_number_length))[3:] binary_number = ( - ("1" + "0" * (binary_number_length - len(binary_number)) + binary_number) - if number < 0 - else "0" + "1" + "0" * (binary_number_length - len(binary_number)) + binary_number ) if shift_amount >= len(binary_number): From 806b3864c36e24459cb4685b400e74c5685f5674 Mon Sep 17 00:00:00 2001 From: Elisha Hollander Date: Sun, 4 Apr 2021 07:02:36 +0300 Subject: [PATCH 1463/2908] refactor: Remove default value of exponential_term (#4308) exponential_term doesn't need a default value --- maths/bailey_borwein_plouffe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/maths/bailey_borwein_plouffe.py b/maths/bailey_borwein_plouffe.py index febf7e975516..b647ae56dbac 100644 --- a/maths/bailey_borwein_plouffe.py +++ b/maths/bailey_borwein_plouffe.py @@ -70,7 +70,6 @@ def _subsum( 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; From 60895366c0f50844af2737130ed98c2510e90060 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 4 Apr 2021 10:52:12 +0530 Subject: [PATCH 1464/2908] fix(mypy): type annotations for cipher algorithms (#4306) * fix(mypy): type annotations for cipher algorithms * Update mypy workflow to include cipher directory * fix: mypy errors in hill_cipher.py * fix build errors --- .github/workflows/build.yml | 2 +- ciphers/diffie_hellman.py | 8 +- ciphers/hill_cipher.py | 23 +- ciphers/mixed_keyword_cypher.py | 18 +- ciphers/mono_alphabetic_ciphers.py | 8 +- ciphers/morse_code_implementation.py | 2 +- ciphers/onepad_cipher.py | 9 +- ciphers/playfair_cipher.py | 5 +- ciphers/porta_cipher.py | 49 ++--- ciphers/rail_fence_cipher.py | 10 +- ciphers/rot13.py | 2 +- ciphers/rsa_cipher.py | 206 +++++++++--------- ciphers/rsa_factorization.py | 2 +- ciphers/shuffled_shift_cipher.py | 19 +- ciphers/simple_keyword_cypher.py | 8 +- ciphers/simple_substitution_cipher.py | 4 +- ciphers/trafid_cipher.py | 10 +- ciphers/transposition_cipher.py | 2 +- ...ansposition_cipher_encrypt_decrypt_file.py | 2 +- ciphers/vigenere_cipher.py | 2 +- ciphers/xor_cipher.py | 4 +- 21 files changed, 196 insertions(+), 199 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1aeaa031565..ac5f80206e35 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: python -m pip install mypy pytest-cov -r requirements.txt # FIXME: #4052 fix mypy errors in the exclude directories and remove them below - run: mypy --ignore-missing-imports - --exclude '(ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . + --exclude '(conversions|data_structures|digital_image_processing|dynamic_programming|graphs|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/ciphers/diffie_hellman.py b/ciphers/diffie_hellman.py index ea35b67b483e..072f4aaaa6da 100644 --- a/ciphers/diffie_hellman.py +++ b/ciphers/diffie_hellman.py @@ -241,9 +241,7 @@ def generate_shared_key(self, other_key_str: str) -> str: return sha256(str(shared_key).encode()).hexdigest() @staticmethod - def is_valid_public_key_static( - local_private_key_str: str, remote_public_key_str: str, prime: int - ) -> bool: + def is_valid_public_key_static(remote_public_key_str: int, prime: int) -> bool: # check if the other public key is valid based on NIST SP800-56 if 2 <= remote_public_key_str and remote_public_key_str <= prime - 2: if pow(remote_public_key_str, (prime - 1) // 2, prime) == 1: @@ -257,9 +255,7 @@ def generate_shared_key_static( local_private_key = int(local_private_key_str, base=16) remote_public_key = int(remote_public_key_str, base=16) prime = primes[group]["prime"] - if not DiffieHellman.is_valid_public_key_static( - local_private_key, remote_public_key, prime - ): + if not DiffieHellman.is_valid_public_key_static(remote_public_key, prime): raise ValueError("Invalid public key") shared_key = pow(remote_public_key, local_private_key, prime) return sha256(str(shared_key).encode()).hexdigest() diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 8237abf6aa5d..bc8f5b41b624 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -64,13 +64,12 @@ class HillCipher: to_int = numpy.vectorize(lambda x: round(x)) - def __init__(self, encrypt_key: int): + def __init__(self, encrypt_key: numpy.ndarray) -> None: """ 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 replace_letters(self, letter: str) -> int: @@ -139,8 +138,8 @@ def encrypt(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.replace_letters(char) for char in batch] - batch_vec = numpy.array([batch_vec]).T + vec = [self.replace_letters(char) for char in batch] + batch_vec = numpy.array([vec]).T batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[ 0 ] @@ -151,7 +150,7 @@ def encrypt(self, text: str) -> str: return encrypted - def make_decrypt_key(self): + def make_decrypt_key(self) -> numpy.ndarray: """ >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.make_decrypt_key() @@ -184,17 +183,15 @@ def decrypt(self, text: str) -> str: >>> hill_cipher.decrypt('85FF00') 'HELLOO' """ - self.decrypt_key = self.make_decrypt_key() + 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 = [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 - ] + vec = [self.replace_letters(char) for char in batch] + batch_vec = numpy.array([vec]).T + batch_decrypted = self.modulus(decrypt_key.dot(batch_vec)).T.tolist()[0] decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted ) @@ -203,12 +200,12 @@ def decrypt(self, text: str) -> str: return decrypted -def main(): +def main() -> None: N = int(input("Enter the order of the encryption key: ")) hill_matrix = [] print("Enter each row of the encryption key with space separated integers") - for i in range(N): + for _ in range(N): row = [int(x) for x in input().split()] hill_matrix.append(row) diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index 59298d310ce0..178902173477 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -29,8 +29,8 @@ def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: # print(temp) alpha = [] modalpha = [] - for i in range(65, 91): - t = chr(i) + for j in range(65, 91): + t = chr(j) alpha.append(t) if t not in temp: temp.append(t) @@ -38,23 +38,23 @@ def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: r = int(26 / 4) # print(r) k = 0 - for i in range(r): - t = [] + for _ in range(r): + s = [] for j in range(len_temp): - t.append(temp[k]) + s.append(temp[k]) if not (k < 25): break k += 1 - modalpha.append(t) + modalpha.append(s) # print(modalpha) d = {} j = 0 k = 0 for j in range(len_temp): - for i in modalpha: - if not (len(i) - 1 >= j): + for m in modalpha: + if not (len(m) - 1 >= j): break - d[alpha[k]] = i[j] + d[alpha[k]] = m[j] if not k < 25: break k += 1 diff --git a/ciphers/mono_alphabetic_ciphers.py b/ciphers/mono_alphabetic_ciphers.py index 0a29d6442896..46013f4936bc 100644 --- a/ciphers/mono_alphabetic_ciphers.py +++ b/ciphers/mono_alphabetic_ciphers.py @@ -1,7 +1,11 @@ +from typing import Literal + LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -def translate_message(key, message, mode): +def translate_message( + key: str, message: str, mode: Literal["encrypt", "decrypt"] +) -> str: """ >>> translate_message("QWERTYUIOPASDFGHJKLZXCVBNM","Hello World","encrypt") 'Pcssi Bidsm' @@ -40,7 +44,7 @@ def decrypt_message(key: str, message: str) -> str: return translate_message(key, message, "decrypt") -def main(): +def main() -> None: message = "Hello World" key = "QWERTYUIOPASDFGHJKLZXCVBNM" mode = "decrypt" # set to 'encrypt' or 'decrypt' diff --git a/ciphers/morse_code_implementation.py b/ciphers/morse_code_implementation.py index 1cce2ef8b386..eec4183fa56e 100644 --- a/ciphers/morse_code_implementation.py +++ b/ciphers/morse_code_implementation.py @@ -83,7 +83,7 @@ def decrypt(message: str) -> str: return decipher -def main(): +def main() -> None: message = "Morse code here" result = encrypt(message.upper()) print(result) diff --git a/ciphers/onepad_cipher.py b/ciphers/onepad_cipher.py index a91f2b4d31c5..3ace9b098cba 100644 --- a/ciphers/onepad_cipher.py +++ b/ciphers/onepad_cipher.py @@ -2,7 +2,8 @@ class Onepad: - def encrypt(self, text: str) -> ([str], [int]): + @staticmethod + def encrypt(text: str) -> tuple[list[int], list[int]]: """Function to encrypt text using pseudo-random numbers""" plain = [ord(i) for i in text] key = [] @@ -14,14 +15,14 @@ def encrypt(self, text: str) -> ([str], [int]): key.append(k) return cipher, key - def decrypt(self, cipher: [str], key: [int]) -> str: + @staticmethod + def decrypt(cipher: list[int], key: list[int]) -> str: """Function to decrypt text using pseudo-random numbers.""" plain = [] for i in range(len(key)): p = int((cipher[i] - (key[i]) ** 2) / key[i]) plain.append(chr(p)) - plain = "".join([i for i in plain]) - return plain + return "".join([i for i in plain]) if __name__ == "__main__": diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 219437448e53..7c0ee5bd5ae1 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -1,8 +1,9 @@ import itertools import string +from typing import Generator, Iterable -def chunker(seq, size): +def chunker(seq: Iterable[str], size: int) -> Generator[tuple[str, ...], None, None]: it = iter(seq) while True: chunk = tuple(itertools.islice(it, size)) @@ -37,7 +38,7 @@ def prepare_input(dirty: str) -> str: return clean -def generate_table(key: str) -> [str]: +def generate_table(key: str) -> list[str]: # I and J are used interchangeably to allow # us to use a 5x5 table (25 letters) diff --git a/ciphers/porta_cipher.py b/ciphers/porta_cipher.py index 29043c4c9fac..498ae294041e 100644 --- a/ciphers/porta_cipher.py +++ b/ciphers/porta_cipher.py @@ -28,7 +28,7 @@ } -def generate_table(key: str) -> [(str, str)]: +def generate_table(key: str) -> list[tuple[str, str]]: """ >>> generate_table('marvin') # doctest: +NORMALIZE_WHITESPACE [('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), @@ -60,30 +60,21 @@ def decrypt(key: str, words: str) -> str: return encrypt(key, words) -def get_position(table: [(str, str)], char: str) -> (int, int) or (None, None): +def get_position(table: tuple[str, str], char: str) -> tuple[int, int]: """ - >>> table = [ - ... ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), - ... ('ABCDEFGHIJKLM', 'STUVWXYZNOPQR'), ('ABCDEFGHIJKLM', 'QRSTUVWXYZNOP'), - ... ('ABCDEFGHIJKLM', 'WXYZNOPQRSTUV'), ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST')] - >>> get_position(table, 'A') - (None, None) + >>> get_position(generate_table('marvin')[0], 'M') + (0, 12) """ - 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)) + # `char` is either in the 0th row or the 1st row + row = 0 if char in table[0] else 1 + col = table[row].index(char) + return row, col -def get_opponent(table: [(str, str)], char: str) -> str: +def get_opponent(table: tuple[str, str], char: str) -> str: """ - >>> table = [ - ... ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), - ... ('ABCDEFGHIJKLM', 'STUVWXYZNOPQR'), ('ABCDEFGHIJKLM', 'QRSTUVWXYZNOP'), - ... ('ABCDEFGHIJKLM', 'WXYZNOPQRSTUV'), ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST')] - >>> get_opponent(table, 'A') - 'A' + >>> get_opponent(generate_table('marvin')[0], 'M') + 'T' """ row, col = get_position(table, char.upper()) if row == 1: @@ -97,14 +88,16 @@ def get_opponent(table: [(str, str)], char: str) -> str: doctest.testmod() # Fist ensure that all our tests are passing... """ - ENTER KEY: marvin - ENTER TEXT TO ENCRYPT: jessica - ENCRYPTED: QRACRWU - DECRYPTED WITH KEY: JESSICA + Demo: + + 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() + 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)}") + print(f"Encrypted: {cipher_text}") + print(f"Decrypted with key: {decrypt(key, cipher_text)}") diff --git a/ciphers/rail_fence_cipher.py b/ciphers/rail_fence_cipher.py index 2596415207ae..cba593ca7335 100644 --- a/ciphers/rail_fence_cipher.py +++ b/ciphers/rail_fence_cipher.py @@ -20,7 +20,7 @@ def encrypt(input_string: str, key: int) -> str: ... TypeError: sequence item 0: expected str instance, int found """ - grid = [[] for _ in range(key)] + temp_grid: list[list[str]] = [[] for _ in range(key)] lowest = key - 1 if key <= 0: @@ -31,8 +31,8 @@ def encrypt(input_string: str, key: int) -> str: 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] + temp_grid[num].append(character) + grid = ["".join(row) for row in temp_grid] output_string = "".join(grid) return output_string @@ -63,7 +63,7 @@ def decrypt(input_string: str, key: int) -> str: if key == 1: return input_string - temp_grid = [[] for _ in range(key)] # generates template + temp_grid: list[list[str]] = [[] 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 @@ -84,7 +84,7 @@ def decrypt(input_string: str, key: int) -> str: return output_string -def bruteforce(input_string: str) -> dict: +def bruteforce(input_string: str) -> dict[int, str]: """Uses decrypt function by guessing every key >>> bruteforce("HWe olordll")[4] diff --git a/ciphers/rot13.py b/ciphers/rot13.py index 21dbda98eecc..b367c3215127 100644 --- a/ciphers/rot13.py +++ b/ciphers/rot13.py @@ -20,7 +20,7 @@ def dencrypt(s: str, n: int = 13) -> str: return out -def main(): +def main() -> None: s0 = input("Enter message: ") s1 = dencrypt(s0, 13) diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 0df37d6ea3ff..b1e8a73f33c6 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -7,144 +7,144 @@ BYTE_SIZE = 256 -def main(): - filename = "encrypted_file.txt" - response = input(r"Encrypt\Decrypt [e\d]: ") - - 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) - - message = input("\nEnter message: ") - pubKeyFilename = "rsa_pubkey.txt" - print("Encrypting and writing to %s..." % (filename)) - encryptedText = encryptAndWriteToFile(filename, pubKeyFilename, message) - - print("\nEncrypted text:") - print(encryptedText) - - 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: - dec.write(decryptedText) - - print("\nDecryption:") - print(decryptedText) - - -def getBlocksFromText(message: int, blockSize: int = DEFAULT_BLOCK_SIZE) -> [int]: - messageBytes = message.encode("ascii") - - blockInts = [] - for blockStart in range(0, len(messageBytes), blockSize): - blockInt = 0 - for i in range(blockStart, min(blockStart + blockSize, len(messageBytes))): - blockInt += messageBytes[i] * (BYTE_SIZE ** (i % blockSize)) - blockInts.append(blockInt) - return blockInts - - -def getTextFromBlocks( - blockInts: [int], messageLength: int, blockSize: int = DEFAULT_BLOCK_SIZE +def get_blocks_from_text( + message: str, block_size: int = DEFAULT_BLOCK_SIZE +) -> list[int]: + message_bytes = message.encode("ascii") + + block_ints = [] + for block_start in range(0, len(message_bytes), block_size): + block_int = 0 + for i in range(block_start, min(block_start + block_size, len(message_bytes))): + block_int += message_bytes[i] * (BYTE_SIZE ** (i % block_size)) + block_ints.append(block_int) + return block_ints + + +def get_text_from_blocks( + block_ints: list[int], message_length: int, block_size: int = DEFAULT_BLOCK_SIZE ) -> str: - message = [] - for blockInt in blockInts: - blockMessage = [] - for i in range(blockSize - 1, -1, -1): - if len(message) + i < messageLength: - asciiNumber = blockInt // (BYTE_SIZE ** i) - blockInt = blockInt % (BYTE_SIZE ** i) - blockMessage.insert(0, chr(asciiNumber)) - message.extend(blockMessage) + message: list[str] = [] + for block_int in block_ints: + block_message: list[str] = [] + for i in range(block_size - 1, -1, -1): + if len(message) + i < message_length: + ascii_number = block_int // (BYTE_SIZE ** i) + block_int = block_int % (BYTE_SIZE ** i) + block_message.insert(0, chr(ascii_number)) + message.extend(block_message) return "".join(message) -def encryptMessage( - message: str, key: (int, int), blockSize: int = DEFAULT_BLOCK_SIZE -) -> [int]: - encryptedBlocks = [] +def encrypt_message( + message: str, key: tuple[int, int], blockSize: int = DEFAULT_BLOCK_SIZE +) -> list[int]: + encrypted_blocks = [] n, e = key - for block in getBlocksFromText(message, blockSize): - encryptedBlocks.append(pow(block, e, n)) - return encryptedBlocks + for block in get_blocks_from_text(message, blockSize): + encrypted_blocks.append(pow(block, e, n)) + return encrypted_blocks -def decryptMessage( - encryptedBlocks: [int], - messageLength: int, - key: (int, int), - blockSize: int = DEFAULT_BLOCK_SIZE, +def decrypt_message( + encrypted_blocks: list[int], + message_length: int, + key: tuple[int, int], + block_size: int = DEFAULT_BLOCK_SIZE, ) -> str: - decryptedBlocks = [] + decrypted_blocks = [] n, d = key - for block in encryptedBlocks: - decryptedBlocks.append(pow(block, d, n)) - return getTextFromBlocks(decryptedBlocks, messageLength, blockSize) + for block in encrypted_blocks: + decrypted_blocks.append(pow(block, d, n)) + return get_text_from_blocks(decrypted_blocks, message_length, block_size) -def readKeyFile(keyFilename: str) -> (int, int, int): - with open(keyFilename) as fo: +def read_key_file(key_filename: str) -> tuple[int, int, int]: + with open(key_filename) as fo: content = fo.read() - keySize, n, EorD = content.split(",") - return (int(keySize), int(n), int(EorD)) + key_size, n, EorD = content.split(",") + return (int(key_size), int(n), int(EorD)) -def encryptAndWriteToFile( - messageFilename: str, - keyFilename: str, +def encrypt_and_write_to_file( + message_filename: str, + key_filename: str, message: str, - blockSize: int = DEFAULT_BLOCK_SIZE, + block_size: int = DEFAULT_BLOCK_SIZE, ) -> str: - keySize, n, e = readKeyFile(keyFilename) - if keySize < blockSize * 8: + key_size, n, e = read_key_file(key_filename) + if key_size < block_size * 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) + % (block_size * 8, key_size) ) - encryptedBlocks = encryptMessage(message, (n, e), blockSize) + encrypted_blocks = [str(i) for i in encrypt_message(message, (n, e), block_size)] - for i in range(len(encryptedBlocks)): - encryptedBlocks[i] = str(encryptedBlocks[i]) - encryptedContent = ",".join(encryptedBlocks) - encryptedContent = f"{len(message)}_{blockSize}_{encryptedContent}" - with open(messageFilename, "w") as fo: - fo.write(encryptedContent) - return encryptedContent + encrypted_content = ",".join(encrypted_blocks) + encrypted_content = f"{len(message)}_{block_size}_{encrypted_content}" + with open(message_filename, "w") as fo: + fo.write(encrypted_content) + return encrypted_content -def readFromFileAndDecrypt(messageFilename: str, keyFilename: str) -> str: - keySize, n, d = readKeyFile(keyFilename) - with open(messageFilename) as fo: +def read_from_file_and_decrypt(message_filename: str, key_filename: str) -> str: + key_size, n, d = read_key_file(key_filename) + with open(message_filename) as fo: content = fo.read() - messageLength, blockSize, encryptedMessage = content.split("_") - messageLength = int(messageLength) - blockSize = int(blockSize) + message_length_str, block_size_str, encrypted_message = content.split("_") + message_length = int(message_length_str) + block_size = int(block_size_str) - if keySize < blockSize * 8: + if key_size < block_size * 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) + % (block_size * 8, key_size) ) - encryptedBlocks = [] - for block in encryptedMessage.split(","): - encryptedBlocks.append(int(block)) + encrypted_blocks = [] + for block in encrypted_message.split(","): + encrypted_blocks.append(int(block)) + + return decrypt_message(encrypted_blocks, message_length, (n, d), block_size) + + +def main() -> None: + filename = "encrypted_file.txt" + response = input(r"Encrypt\Decrypt [e\d]: ") + + if response.lower().startswith("e"): + mode = "encrypt" + elif response.lower().startswith("d"): + mode = "decrypt" - return decryptMessage(encryptedBlocks, messageLength, (n, d), blockSize) + if mode == "encrypt": + if not os.path.exists("rsa_pubkey.txt"): + rkg.makeKeyFiles("rsa", 1024) + + message = input("\nEnter message: ") + pubkey_filename = "rsa_pubkey.txt" + print("Encrypting and writing to %s..." % (filename)) + encryptedText = encrypt_and_write_to_file(filename, pubkey_filename, message) + + print("\nEncrypted text:") + print(encryptedText) + + elif mode == "decrypt": + privkey_filename = "rsa_privkey.txt" + print("Reading from %s and decrypting..." % (filename)) + decrypted_text = read_from_file_and_decrypt(filename, privkey_filename) + print("writing decryption to rsa_decryption.txt...") + with open("rsa_decryption.txt", "w") as dec: + dec.write(decrypted_text) + + print("\nDecryption:") + print(decrypted_text) if __name__ == "__main__": diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py index b18aab609e2d..6df32b6cc887 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) -> [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/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index 22628f3c9d9e..01d099641dd2 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -1,5 +1,6 @@ import random import string +from typing import Optional class ShuffledShiftCipher: @@ -26,7 +27,7 @@ class ShuffledShiftCipher: cip2 = ShuffledShiftCipher() """ - def __init__(self, passcode: str = None): + def __init__(self, passcode: Optional[str] = None) -> None: """ Initializes a cipher object with a passcode as it's entity Note: No new passcode is generated if user provides a passcode @@ -36,13 +37,13 @@ def __init__(self, passcode: str = None): self.__key_list = self.__make_key_list() self.__shift_key = self.__make_shift_key() - def __str__(self): + def __str__(self) -> str: """ :return: passcode of the cipher object """ return "Passcode is: " + "".join(self.__passcode) - def __neg_pos(self, iterlist: list) -> list: + def __neg_pos(self, iterlist: list[int]) -> list[int]: """ Mutates the list by changing the sign of each alternate element @@ -54,7 +55,7 @@ def __neg_pos(self, iterlist: list) -> list: iterlist[i] *= -1 return iterlist - def __passcode_creator(self) -> list: + def __passcode_creator(self) -> list[str]: """ Creates a random password from the selection buffer of 1. uppercase letters of the English alphabet @@ -65,10 +66,10 @@ def __passcode_creator(self) -> 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))] + password = [random.choice(choices) for _ in range(random.randint(10, 20))] return password - def __make_key_list(self) -> list: + def __make_key_list(self) -> list[str]: """ Shuffles the ordered character choices by pivoting at breakpoints Breakpoints are the set of characters in the passcode @@ -99,7 +100,7 @@ def __make_key_list(self) -> list: # 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 = [] + temp_list: list[str] = [] # algorithm for creating a new shuffled list, keys_l, out of key_list_options for i in key_list_options: @@ -109,7 +110,7 @@ def __make_key_list(self) -> list: # keys_l if i in breakpoints or i == key_list_options[-1]: keys_l.extend(temp_list[::-1]) - temp_list = [] + temp_list.clear() # returning a shuffled keys_l to prevent brute force guessing of shift key return keys_l @@ -167,7 +168,7 @@ def encrypt(self, plaintext: str) -> str: return encoded_message -def test_end_to_end(msg: str = "Hello, this is a modified Caesar cipher"): +def test_end_to_end(msg: str = "Hello, this is a modified Caesar cipher") -> str: """ >>> test_end_to_end() 'Hello, this is a modified Caesar cipher' diff --git a/ciphers/simple_keyword_cypher.py b/ciphers/simple_keyword_cypher.py index 71c3083e9dfc..447bacfc2e6c 100644 --- a/ciphers/simple_keyword_cypher.py +++ b/ciphers/simple_keyword_cypher.py @@ -15,7 +15,7 @@ def remove_duplicates(key: str) -> str: return key_no_dups -def create_cipher_map(key: str) -> dict: +def create_cipher_map(key: str) -> dict[str, str]: """ Returns a cipher map given a keyword. :param key: keyword to use @@ -40,7 +40,7 @@ def create_cipher_map(key: str) -> dict: return cipher_alphabet -def encipher(message: str, cipher_map: dict) -> str: +def encipher(message: str, cipher_map: dict[str, str]) -> str: """ Enciphers a message given a cipher map. :param message: Message to encipher @@ -52,7 +52,7 @@ def encipher(message: str, cipher_map: dict) -> str: return "".join(cipher_map.get(ch, ch) for ch in message.upper()) -def decipher(message: str, cipher_map: dict) -> str: +def decipher(message: str, cipher_map: dict[str, str]) -> str: """ Deciphers a message given a cipher map :param message: Message to decipher @@ -67,7 +67,7 @@ def decipher(message: str, cipher_map: dict) -> str: return "".join(rev_cipher_map.get(ch, ch) for ch in message.upper()) -def main(): +def main() -> None: """ Handles I/O :return: void diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index 646ea449fc06..a763bd6b6b48 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -4,7 +4,7 @@ LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -def main(): +def main() -> None: message = input("Enter message: ") key = "LFWOAYUISVKMNXPBDCRJTQEGHZ" resp = input("Encrypt/Decrypt [e/d]: ") @@ -68,7 +68,7 @@ def translateMessage(key: str, message: str, mode: str) -> str: return translated -def getRandomKey(): +def getRandomKey() -> str: key = list(LETTERS) random.shuffle(key) return "".join(key) diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py index 328814f97744..1c8ea3024d33 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: str, character2Number: dict) -> str: +def __encryptPart(messagePart: str, character2Number: dict[str, str]) -> str: one, two, three = "", "", "" tmp = [] @@ -16,7 +16,9 @@ def __encryptPart(messagePart: str, character2Number: dict) -> str: return one + two + three -def __decryptPart(messagePart: str, character2Number: dict) -> (str, str, str): +def __decryptPart( + messagePart: str, character2Number: dict[str, str] +) -> tuple[str, str, str]: tmp, thisPart = "", "" result = [] @@ -32,7 +34,9 @@ def __decryptPart(messagePart: str, character2Number: dict) -> (str, str, str): return result[0], result[1], result[2] -def __prepare(message: str, alphabet: str) -> (str, str, dict, dict): +def __prepare( + message: str, alphabet: str +) -> tuple[str, str, dict[str, str], dict[str, str]]: # Validate message and alphabet, set to upper and remove spaces alphabet = alphabet.replace(" ", "").upper() message = message.replace(" ", "").upper() diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index 6a0a22d3e31d..589bb8cb5cd5 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -8,7 +8,7 @@ """ -def main(): +def main() -> None: message = input("Enter message: ") key = int(input("Enter key [2-%s]: " % (len(message) - 1))) mode = input("Encryption/Decryption [e/d]: ") diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index 45aab056109a..b91c73c9f2ad 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -5,7 +5,7 @@ from . import transposition_cipher as transCipher -def main(): +def main() -> None: inputFile = "Prehistoric Men.txt" outputFile = "Output.txt" key = int(input("Enter key: ")) diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index eb523d078005..d97a96949fb8 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -1,7 +1,7 @@ LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -def main(): +def main() -> None: message = input("Enter message: ") key = input("Enter key [alphanumeric]: ") mode = input("Encrypt/Decrypt [e/d]: ") diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 32a350d4e61c..12d580e720bc 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -28,7 +28,7 @@ def __init__(self, key: int = 0): # private field self.__key = key - def encrypt(self, content: str, key: int) -> [str]: + def encrypt(self, content: str, key: int) -> list[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: str, key: int) -> [str]: return ans - def decrypt(self, content: str, key: int) -> [str]: + def decrypt(self, content: str, key: int) -> list[str]: """ input: 'content' of type list and 'key' of type int output: decrypted string 'content' as a list of chars From 536fb4bca48f69cb66cfbd03aeb02550def07977 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Sun, 4 Apr 2021 16:53:48 +0530 Subject: [PATCH 1465/2908] Add algorithm for N-body simulation - retry (#4298) * add n_body_simulation.py * updating DIRECTORY.md * Rename other/n_body_simulation.py to physics/n_body_simulation.py * updating DIRECTORY.md * Update build.yml * refactor examples & add doctests * removed type-hints from self-parameter * Apply suggestions from code review * Update physics/n_body_simulation.py * Update physics/n_body_simulation.py * Update physics/n_body_simulation.py * Don't forget self * Fix velocity Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Dhruv Manilawala Co-authored-by: Christian Clauss --- DIRECTORY.md | 3 + physics/n_body_simulation.py | 348 +++++++++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 physics/n_body_simulation.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 42a6c49c735f..e6ce3ae718b3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -551,6 +551,9 @@ * [Sdes](https://github.com/TheAlgorithms/Python/blob/master/other/sdes.py) * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) +## Physics + * [N Body Simulation](https://github.com/TheAlgorithms/Python/blob/master/physics/n_body_simulation.py) + ## Project Euler * Problem 001 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_001/sol1.py) diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py new file mode 100644 index 000000000000..045a49f7ff00 --- /dev/null +++ b/physics/n_body_simulation.py @@ -0,0 +1,348 @@ +""" +In physics and astronomy, a gravitational N-body simulation is a simulation of a +dynamical system of particles under the influence of gravity. The system +consists of a number of bodies, each of which exerts a gravitational force on all +other bodies. These forces are calculated using Newton's law of universal +gravitation. The Euler method is used at each time-step to calculate the change in +velocity and position brought about by these forces. Softening is used to prevent +numerical divergences when a particle comes too close to another (and the force +goes to infinity). +(Description adapted from https://en.wikipedia.org/wiki/N-body_simulation ) +(See also http://www.shodor.org/refdesk/Resources/Algorithms/EulersMethod/ ) +""" + + +from __future__ import annotations + +import random + +from matplotlib import animation +from matplotlib import pyplot as plt + + +class Body: + def __init__( + self, + position_x: float, + position_y: float, + velocity_x: float, + velocity_y: float, + mass: float = 1.0, + size: float = 1.0, + color: str = "blue", + ) -> None: + """ + The parameters "size" & "color" are not relevant for the simulation itself, + they are only used for plotting. + """ + self.position_x = position_x + self.position_y = position_y + self.velocity_x = velocity_x + self.velocity_y = velocity_y + self.mass = mass + self.size = size + self.color = color + + @property + def position(self) -> tuple[float, float]: + return self.position_x, self.position_y + + @property + def velocity(self) -> tuple[float, float]: + return self.velocity_x, self.velocity_y + + def update_velocity( + self, force_x: float, force_y: float, delta_time: float + ) -> None: + """ + Euler algorithm for velocity + + >>> body_1 = Body(0.,0.,0.,0.) + >>> body_1.update_velocity(1.,0.,1.) + >>> body_1.velocity + (1.0, 0.0) + + >>> body_1.update_velocity(1.,0.,1.) + >>> body_1.velocity + (2.0, 0.0) + + >>> body_2 = Body(0.,0.,5.,0.) + >>> body_2.update_velocity(0.,-10.,10.) + >>> body_2.velocity + (5.0, -100.0) + + >>> body_2.update_velocity(0.,-10.,10.) + >>> body_2.velocity + (5.0, -200.0) + """ + self.velocity_x += force_x * delta_time + self.velocity_y += force_y * delta_time + + def update_position(self, delta_time: float) -> None: + """ + Euler algorithm for position + + >>> body_1 = Body(0.,0.,1.,0.) + >>> body_1.update_position(1.) + >>> body_1.position + (1.0, 0.0) + + >>> body_1.update_position(1.) + >>> body_1.position + (2.0, 0.0) + + >>> body_2 = Body(10.,10.,0.,-2.) + >>> body_2.update_position(1.) + >>> body_2.position + (10.0, 8.0) + + >>> body_2.update_position(1.) + >>> body_2.position + (10.0, 6.0) + """ + self.position_x += self.velocity_x * delta_time + self.position_y += self.velocity_y * delta_time + + +class BodySystem: + """ + This class is used to hold the bodies, the gravitation constant, the time + factor and the softening factor. The time factor is used to control the speed + of the simulation. The softening factor is used for softening, a numerical + trick for N-body simulations to prevent numerical divergences when two bodies + get too close to each other. + """ + + def __init__( + self, + bodies: list[Body], + gravitation_constant: float = 1.0, + time_factor: float = 1.0, + softening_factor: float = 0.0, + ) -> None: + self.bodies = bodies + self.gravitation_constant = gravitation_constant + self.time_factor = time_factor + self.softening_factor = softening_factor + + def __len__(self) -> int: + return len(self.bodies) + + def update_system(self, delta_time: float) -> None: + """ + For each body, loop through all other bodies to calculate the total + force they exert on it. Use that force to update the body's velocity. + + >>> body_system_1 = BodySystem([Body(0,0,0,0), Body(10,0,0,0)]) + >>> len(body_system_1) + 2 + >>> body_system_1.update_system(1) + >>> body_system_1.bodies[0].position + (0.01, 0.0) + >>> body_system_1.bodies[0].velocity + (0.01, 0.0) + + >>> body_system_2 = BodySystem([Body(-10,0,0,0), Body(10,0,0,0, mass=4)], 1, 10) + >>> body_system_2.update_system(1) + >>> body_system_2.bodies[0].position + (-9.0, 0.0) + >>> body_system_2.bodies[0].velocity + (0.1, 0.0) + """ + for body1 in self.bodies: + force_x = 0.0 + force_y = 0.0 + for body2 in self.bodies: + if body1 != body2: + dif_x = body2.position_x - body1.position_x + dif_y = body2.position_y - body1.position_y + + # Calculation of the distance using Pythagoras's theorem + # Extra factor due to the softening technique + distance = (dif_x ** 2 + dif_y ** 2 + self.softening_factor) ** ( + 1 / 2 + ) + + # Newton's law of universal gravitation. + force_x += ( + self.gravitation_constant * body2.mass * dif_x / distance ** 3 + ) + force_y += ( + self.gravitation_constant * body2.mass * dif_y / distance ** 3 + ) + + # Update the body's velocity once all the force components have been added + body1.update_velocity(force_x, force_y, delta_time * self.time_factor) + + # Update the positions only after all the velocities have been updated + for body in self.bodies: + body.update_position(delta_time * self.time_factor) + + +def update_step( + body_system: BodySystem, delta_time: float, patches: list[plt.Circle] +) -> None: + """ + Updates the body-system and applies the change to the patch-list used for plotting + + >>> body_system_1 = BodySystem([Body(0,0,0,0), Body(10,0,0,0)]) + >>> patches_1 = [plt.Circle((body.position_x, body.position_y), body.size, + ... fc=body.color)for body in body_system_1.bodies] #doctest: +ELLIPSIS + >>> update_step(body_system_1, 1, patches_1) + >>> patches_1[0].center + (0.01, 0.0) + + >>> body_system_2 = BodySystem([Body(-10,0,0,0), Body(10,0,0,0, mass=4)], 1, 10) + >>> patches_2 = [plt.Circle((body.position_x, body.position_y), body.size, + ... fc=body.color)for body in body_system_2.bodies] #doctest: +ELLIPSIS + >>> update_step(body_system_2, 1, patches_2) + >>> patches_2[0].center + (-9.0, 0.0) + """ + # Update the positions of the bodies + body_system.update_system(delta_time) + + # Update the positions of the patches + for patch, body in zip(patches, body_system.bodies): + patch.center = (body.position_x, body.position_y) + + +def plot( + title: str, + body_system: BodySystem, + x_start: float = -1, + x_end: float = 1, + y_start: float = -1, + y_end: float = 1, +) -> None: + """ + Utility function to plot how the given body-system evolves over time. + No doctest provided since this function does not have a return value. + """ + + INTERVAL = 20 # Frame rate of the animation + DELTA_TIME = INTERVAL / 1000 # Time between time steps in seconds + + fig = plt.figure() + fig.canvas.set_window_title(title) + ax = plt.axes( + xlim=(x_start, x_end), ylim=(y_start, y_end) + ) # Set section to be plotted + plt.gca().set_aspect("equal") # Fix aspect ratio + + # Each body is drawn as a patch by the plt-function + patches = [ + plt.Circle((body.position_x, body.position_y), body.size, fc=body.color) + for body in body_system.bodies + ] + + for patch in patches: + ax.add_patch(patch) + + # Function called at each step of the animation + def update(frame: int) -> list[plt.Circle]: + update_step(body_system, DELTA_TIME, patches) + return patches + + anim = animation.FuncAnimation( # noqa: F841 + fig, update, interval=INTERVAL, blit=True + ) + + plt.show() + + +def example_1() -> BodySystem: + """ + Example 1: figure-8 solution to the 3-body-problem + This example can be seen as a test of the implementation: given the right + initial conditions, the bodies should move in a figure-8. + (initial conditions taken from http://www.artcompsci.org/vol_1/v1_web/node56.html) + >>> body_system = example_1() + >>> len(body_system) + 3 + """ + + position_x = 0.9700436 + position_y = -0.24308753 + velocity_x = 0.466203685 + velocity_y = 0.43236573 + + bodies1 = [ + Body(position_x, position_y, velocity_x, velocity_y, size=0.2, color="red"), + Body(-position_x, -position_y, velocity_x, velocity_y, size=0.2, color="green"), + Body(0, 0, -2 * velocity_x, -2 * velocity_y, size=0.2, color="blue"), + ] + return BodySystem(bodies1, time_factor=3) + + +def example_2() -> BodySystem: + """ + Example 2: Moon's orbit around the earth + This example can be seen as a test of the implementation: given the right + initial conditions, the moon should orbit around the earth as it actually does. + (mass, velocity and distance taken from https://en.wikipedia.org/wiki/Earth + and https://en.wikipedia.org/wiki/Moon) + No doctest provided since this function does not have a return value. + """ + + moon_mass = 7.3476e22 + earth_mass = 5.972e24 + velocity_dif = 1022 + earth_moon_distance = 384399000 + gravitation_constant = 6.674e-11 + + # Calculation of the respective velocities so that total impulse is zero, + # i.e. the two bodies together don't move + moon_velocity = earth_mass * velocity_dif / (earth_mass + moon_mass) + earth_velocity = moon_velocity - velocity_dif + + moon = Body(-earth_moon_distance, 0, 0, moon_velocity, moon_mass, 10000000, "grey") + earth = Body(0, 0, 0, earth_velocity, earth_mass, 50000000, "blue") + return BodySystem([earth, moon], gravitation_constant, time_factor=1000000) + + +def example_3() -> BodySystem: + """ + Example 3: Random system with many bodies. + No doctest provided since this function does not have a return value. + """ + + bodies = [] + for i in range(10): + velocity_x = random.uniform(-0.5, 0.5) + velocity_y = random.uniform(-0.5, 0.5) + + # Bodies are created pairwise with opposite velocities so that the + # total impulse remains zero + bodies.append( + Body( + random.uniform(-0.5, 0.5), + random.uniform(-0.5, 0.5), + velocity_x, + velocity_y, + size=0.05, + ) + ) + bodies.append( + Body( + random.uniform(-0.5, 0.5), + random.uniform(-0.5, 0.5), + -velocity_x, + -velocity_y, + size=0.05, + ) + ) + return BodySystem(bodies, 0.01, 10, 0.1) + + +if __name__ == "__main__": + plot("Figure-8 solution to the 3-body-problem", example_1(), -2, 2, -2, 2) + plot( + "Moon's orbit around the earth", + example_2(), + -430000000, + 430000000, + -430000000, + 430000000, + ) + plot("Random system with many bodies", example_3(), -1.5, 1.5, -1.5, 1.5) From 20c7518028efbb6e8ae46a42b28c3f2e27acb2a2 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 4 Apr 2021 18:55:49 +0530 Subject: [PATCH 1466/2908] fix(mypy): type annotations for conversions algorithms (#4314) * fix(mypy): type annotations for conversions algorithms * refactor(CI): include conversions algorithms for mypy tests --- .github/workflows/build.yml | 2 +- conversions/binary_to_octal.py | 2 +- conversions/decimal_to_binary.py | 6 +++--- conversions/decimal_to_hexadecimal.py | 3 ++- conversions/decimal_to_octal.py | 4 ++-- conversions/prefix_conversions.py | 14 ++++++++------ conversions/weight_conversion.py | 4 ++-- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac5f80206e35..eb54d85ea035 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: python -m pip install mypy pytest-cov -r requirements.txt # FIXME: #4052 fix mypy errors in the exclude directories and remove them below - run: mypy --ignore-missing-imports - --exclude '(conversions|data_structures|digital_image_processing|dynamic_programming|graphs|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . + --exclude '(data_structures|digital_image_processing|dynamic_programming|graphs|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/conversions/binary_to_octal.py b/conversions/binary_to_octal.py index 8b594887867e..35ede95b134d 100644 --- a/conversions/binary_to_octal.py +++ b/conversions/binary_to_octal.py @@ -28,7 +28,7 @@ def bin_to_octal(bin_string: str) -> str: bin_string = "0" + bin_string bin_string_in_3_list = [ bin_string[index : index + 3] - for index, value in enumerate(bin_string) + for index in range(len(bin_string)) if index % 3 == 0 ] for bin_group in bin_string_in_3_list: diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index 7e83aee4f7a5..c21cdbcaec68 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -28,9 +28,9 @@ def decimal_to_binary(num: int) -> str: TypeError: 'str' object cannot be interpreted as an integer """ - if type(num) == float: + if isinstance(num, float): raise TypeError("'float' object cannot be interpreted as an integer") - if type(num) == str: + if isinstance(num, str): raise TypeError("'str' object cannot be interpreted as an integer") if num == 0: @@ -42,7 +42,7 @@ def decimal_to_binary(num: int) -> str: negative = True num = -num - binary = [] + binary: list[int] = [] while num > 0: binary.insert(0, num % 2) num >>= 1 diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py index 433f78dfecb7..2389c6d1f2a1 100644 --- a/conversions/decimal_to_hexadecimal.py +++ b/conversions/decimal_to_hexadecimal.py @@ -21,7 +21,7 @@ } -def decimal_to_hexadecimal(decimal): +def decimal_to_hexadecimal(decimal: float) -> str: """ take integer decimal value, return hexadecimal representation as str beginning with 0x @@ -58,6 +58,7 @@ def decimal_to_hexadecimal(decimal): True """ assert type(decimal) in (int, float) and decimal == int(decimal) + decimal = int(decimal) hexadecimal = "" negative = False if decimal < 0: diff --git a/conversions/decimal_to_octal.py b/conversions/decimal_to_octal.py index 8dc04830ad87..4c313bddf64c 100644 --- a/conversions/decimal_to_octal.py +++ b/conversions/decimal_to_octal.py @@ -17,14 +17,14 @@ def decimal_to_octal(num: int) -> str: counter = 0 while num > 0: remainder = num % 8 - octal = octal + (remainder * math.pow(10, counter)) + octal = octal + (remainder * math.floor(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 f"0o{int(octal)}" -def main(): +def main() -> None: """Print octal equivalents of decimal numbers.""" print("\n2 in octal is:") print(decimal_to_octal(2)) # = 2 diff --git a/conversions/prefix_conversions.py b/conversions/prefix_conversions.py index c2440d1cf886..78db4a91709c 100644 --- a/conversions/prefix_conversions.py +++ b/conversions/prefix_conversions.py @@ -59,10 +59,12 @@ def convert_si_prefix( 1000 """ if isinstance(known_prefix, str): - known_prefix: SI_Unit = SI_Unit[known_prefix.lower()] + known_prefix = 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)) + unknown_prefix = SI_Unit[unknown_prefix.lower()] + unknown_amount: float = known_amount * ( + 10 ** (known_prefix.value - unknown_prefix.value) + ) return unknown_amount @@ -85,10 +87,10 @@ def convert_binary_prefix( 1024 """ if isinstance(known_prefix, str): - known_prefix: Binary_Unit = Binary_Unit[known_prefix.lower()] + known_prefix = Binary_Unit[known_prefix.lower()] if isinstance(unknown_prefix, str): - unknown_prefix: Binary_Unit = Binary_Unit[unknown_prefix.lower()] - unknown_amount = known_amount * ( + unknown_prefix = Binary_Unit[unknown_prefix.lower()] + unknown_amount: float = known_amount * ( 2 ** ((known_prefix.value - unknown_prefix.value) * 10) ) return unknown_amount diff --git a/conversions/weight_conversion.py b/conversions/weight_conversion.py index 85515f2f6f88..c344416be5f5 100644 --- a/conversions/weight_conversion.py +++ b/conversions/weight_conversion.py @@ -29,7 +29,7 @@ -> Wikipedia reference: https://en.wikipedia.org/wiki/Dalton_(unit) """ -KILOGRAM_CHART = { +KILOGRAM_CHART: dict[str, float] = { "kilogram": 1, "gram": pow(10, 3), "milligram": pow(10, 6), @@ -42,7 +42,7 @@ "atomic-mass-unit": 6.022136652e26, } -WEIGHT_TYPE_CHART = { +WEIGHT_TYPE_CHART: dict[str, float] = { "kilogram": 1, "gram": pow(10, -3), "milligram": pow(10, -6), From 8c2986026bc42d81a6d9386c9fe621fea8ff2d15 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Mon, 5 Apr 2021 19:07:38 +0530 Subject: [PATCH 1467/2908] fix(mypy): type annotations for linear algebra algorithms (#4317) * fix(mypy): type annotations for linear algebra algorithms * refactor: remove linear algebra directory from mypy exclude --- .github/workflows/build.yml | 2 +- linear_algebra/src/conjugate_gradient.py | 17 +++-- linear_algebra/src/lib.py | 83 ++++++++++++++--------- linear_algebra/src/polynom_for_points.py | 13 ++-- linear_algebra/src/power_iteration.py | 7 +- linear_algebra/src/rayleigh_quotient.py | 10 ++- linear_algebra/src/test_linear_algebra.py | 40 +++++------ linear_algebra/src/transformations_2d.py | 2 - 8 files changed, 100 insertions(+), 74 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb54d85ea035..ca3e8092276e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: python -m pip install mypy pytest-cov -r requirements.txt # FIXME: #4052 fix mypy errors in the exclude directories and remove them below - run: mypy --ignore-missing-imports - --exclude '(data_structures|digital_image_processing|dynamic_programming|graphs|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . + --exclude '(data_structures|digital_image_processing|dynamic_programming|graphs|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/linear_algebra/src/conjugate_gradient.py b/linear_algebra/src/conjugate_gradient.py index 1a65b8ccf019..418ae88a5f41 100644 --- a/linear_algebra/src/conjugate_gradient.py +++ b/linear_algebra/src/conjugate_gradient.py @@ -3,10 +3,12 @@ - https://en.wikipedia.org/wiki/Conjugate_gradient_method - https://en.wikipedia.org/wiki/Definite_symmetric_matrix """ +from typing import Any + import numpy as np -def _is_matrix_spd(matrix: np.array) -> bool: +def _is_matrix_spd(matrix: np.ndarray) -> bool: """ Returns True if input matrix is symmetric positive definite. Returns False otherwise. @@ -38,10 +40,11 @@ def _is_matrix_spd(matrix: np.array) -> bool: eigen_values, _ = np.linalg.eigh(matrix) # Check sign of all eigenvalues. - return np.all(eigen_values > 0) + # np.all returns a value of type np.bool_ + return bool(np.all(eigen_values > 0)) -def _create_spd_matrix(dimension: np.int64) -> np.array: +def _create_spd_matrix(dimension: int) -> Any: """ Returns a symmetric positive definite matrix given a dimension. @@ -64,11 +67,11 @@ def _create_spd_matrix(dimension: np.int64) -> np.array: def conjugate_gradient( - spd_matrix: np.array, - load_vector: np.array, + spd_matrix: np.ndarray, + load_vector: np.ndarray, max_iterations: int = 1000, tol: float = 1e-8, -) -> np.array: +) -> Any: """ Returns solution to the linear system np.dot(spd_matrix, x) = b. @@ -141,6 +144,8 @@ def conjugate_gradient( # Update number of iterations. iterations += 1 + if iterations > max_iterations: + break return x diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 353c8334093b..5e2f82018f38 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -22,6 +22,7 @@ import math import random +from typing import Collection, Optional, Union, overload class Vector: @@ -45,7 +46,7 @@ class Vector: TODO: compare-operator """ - def __init__(self, components=None): + def __init__(self, components: Optional[Collection[float]] = None) -> None: """ input: components or nothing simple constructor for init the vector @@ -54,7 +55,7 @@ def __init__(self, components=None): components = [] self.__components = list(components) - def set(self, components): + def set(self, components: Collection[float]) -> None: """ input: new components changes the components of the vector. @@ -65,13 +66,13 @@ def set(self, components): else: raise Exception("please give any vector") - def __str__(self): + def __str__(self) -> str: """ returns a string representation of the vector """ return "(" + ",".join(map(str, self.__components)) + ")" - def component(self, i): + def component(self, i: int) -> float: """ input: index (start at 0) output: the i-th component of the vector. @@ -81,22 +82,22 @@ def component(self, i): else: raise Exception("index out of range") - def __len__(self): + def __len__(self) -> int: """ returns the size of the vector """ return len(self.__components) - def euclidLength(self): + def euclidLength(self) -> float: """ returns the euclidean length of the vector """ - summe = 0 + summe: float = 0 for c in self.__components: summe += c ** 2 return math.sqrt(summe) - def __add__(self, other): + def __add__(self, other: "Vector") -> "Vector": """ input: other vector assumes: other vector has the same size @@ -109,7 +110,7 @@ def __add__(self, other): else: raise Exception("must have the same size") - def __sub__(self, other): + def __sub__(self, other: "Vector") -> "Vector": """ input: other vector assumes: other vector has the same size @@ -122,7 +123,15 @@ def __sub__(self, other): else: # error case raise Exception("must have the same size") - def __mul__(self, other): + @overload + def __mul__(self, other: float) -> "Vector": + ... + + @overload + def __mul__(self, other: "Vector") -> float: + ... + + def __mul__(self, other: Union[float, "Vector"]) -> Union[float, "Vector"]: """ mul implements the scalar multiplication and the dot-product @@ -132,20 +141,20 @@ def __mul__(self, other): return Vector(ans) elif isinstance(other, Vector) and (len(self) == len(other)): size = len(self) - summe = 0 + summe: float = 0 for i in range(size): summe += self.__components[i] * other.component(i) return summe else: # error case raise Exception("invalid operand!") - def copy(self): + def copy(self) -> "Vector": """ copies this vector and returns it. """ return Vector(self.__components) - def changeComponent(self, pos, value): + def changeComponent(self, pos: int, value: float) -> None: """ input: an index (pos) and a value changes the specified component (pos) with the @@ -156,7 +165,7 @@ def changeComponent(self, pos, value): self.__components[pos] = value -def zeroVector(dimension): +def zeroVector(dimension: int) -> Vector: """ returns a zero-vector of size 'dimension' """ @@ -165,7 +174,7 @@ def zeroVector(dimension): return Vector([0] * dimension) -def unitBasisVector(dimension, pos): +def unitBasisVector(dimension: int, pos: int) -> Vector: """ returns a unit basis vector with a One at index 'pos' (indexing at 0) @@ -177,7 +186,7 @@ def unitBasisVector(dimension, pos): return Vector(ans) -def axpy(scalar, x, y): +def axpy(scalar: float, x: Vector, y: Vector) -> Vector: """ input: a 'scalar' and two vectors 'x' and 'y' output: a vector @@ -192,7 +201,7 @@ def axpy(scalar, x, y): return x * scalar + y -def randomVector(N, a, b): +def randomVector(N: int, a: int, b: int) -> Vector: """ input: size (N) of the vector. random range (a,b) @@ -200,7 +209,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 _ in range(N)] return Vector(ans) @@ -222,7 +231,7 @@ class Matrix: operator - _ implements the matrix-subtraction """ - def __init__(self, matrix, w, h): + def __init__(self, matrix: list[list[float]], w: int, h: int) -> None: """ simple constructor for initializing the matrix with components. @@ -231,7 +240,7 @@ def __init__(self, matrix, w, h): self.__width = w self.__height = h - def __str__(self): + def __str__(self) -> str: """ returns a string representation of this matrix. @@ -246,7 +255,7 @@ def __str__(self): ans += str(self.__matrix[i][j]) + "|\n" return ans - def changeComponent(self, x, y, value): + def changeComponent(self, x: int, y: int, value: float) -> None: """ changes the x-y component of this matrix """ @@ -255,7 +264,7 @@ def changeComponent(self, x, y, value): else: raise Exception("changeComponent: indices out of bounds") - def component(self, x, y): + def component(self, x: int, y: int) -> float: """ returns the specified (x,y) component """ @@ -264,13 +273,13 @@ def component(self, x, y): else: raise Exception("changeComponent: indices out of bounds") - def width(self): + def width(self) -> int: """ getter for the width """ return self.__width - def height(self): + def height(self) -> int: """ getter for the height """ @@ -303,7 +312,15 @@ def determinate(self) -> float: else: raise Exception("matrix is not square") - def __mul__(self, other): + @overload + def __mul__(self, other: float) -> "Matrix": + ... + + @overload + def __mul__(self, other: Vector) -> Vector: + ... + + def __mul__(self, other: Union[float, Vector]) -> Union[Vector, "Matrix"]: """ implements the matrix-vector multiplication. implements the matrix-scalar multiplication @@ -312,7 +329,7 @@ def __mul__(self, other): if len(other) == self.__width: ans = zeroVector(self.__height) for i in range(self.__height): - summe = 0 + summe: float = 0 for j in range(self.__width): summe += other.component(j) * self.__matrix[i][j] ans.changeComponent(i, summe) @@ -330,7 +347,7 @@ def __mul__(self, other): ] return Matrix(matrix, self.__width, self.__height) - def __add__(self, other): + def __add__(self, other: "Matrix") -> "Matrix": """ implements the matrix-addition. """ @@ -345,7 +362,7 @@ def __add__(self, other): else: raise Exception("matrix must have the same dimension!") - def __sub__(self, other): + def __sub__(self, other: "Matrix") -> "Matrix": """ implements the matrix-subtraction. """ @@ -361,19 +378,21 @@ def __sub__(self, other): raise Exception("matrix must have the same dimension!") -def squareZeroMatrix(N): +def squareZeroMatrix(N: int) -> Matrix: """ returns a square zero-matrix of dimension NxN """ - ans = [[0] * N for i in range(N)] + ans: list[list[float]] = [[0] * N for _ in range(N)] return Matrix(ans, N, N) -def randomMatrix(W, H, a, b): +def randomMatrix(W: int, H: int, a: int, b: int) -> Matrix: """ 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)] + matrix: list[list[float]] = [ + [random.randint(a, b) for _ in range(W)] for _ in range(H) + ] return Matrix(matrix, W, H) diff --git a/linear_algebra/src/polynom_for_points.py b/linear_algebra/src/polynom_for_points.py index 7a363723d9d2..091849542ffe 100644 --- a/linear_algebra/src/polynom_for_points.py +++ b/linear_algebra/src/polynom_for_points.py @@ -1,6 +1,3 @@ -from __future__ import annotations - - def points_to_polynomial(coordinates: list[list[int]]) -> str: """ coordinates is a two dimensional matrix: [[x, y], [x, y], ...] @@ -55,12 +52,12 @@ def points_to_polynomial(coordinates: list[list[int]]) -> str: if check == 1: count_of_line = 0 - matrix = [] + matrix: list[list[float]] = [] # 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: list[int] = [] + count_line: list[float] = [] while count_in_line < x: count_line.append(a ** (x - (count_in_line + 1))) count_in_line += 1 @@ -69,7 +66,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[float] = [] while count_of_line < x: vector.append(coordinates[count_of_line][1]) count_of_line += 1 @@ -96,14 +93,14 @@ def points_to_polynomial(coordinates: list[list[int]]) -> str: # make solutions solution: list[str] = [] while count < x: - solution.append(vector[count] / matrix[count][count]) + solution.append(str(vector[count] / matrix[count][count])) count += 1 count = 0 solved = "f(x)=" while count < x: - remove_e: list[str] = str(solution[count]).split("E") + remove_e: list[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/power_iteration.py b/linear_algebra/src/power_iteration.py index 476361e0d433..2cf22838e4a1 100644 --- a/linear_algebra/src/power_iteration.py +++ b/linear_algebra/src/power_iteration.py @@ -2,8 +2,11 @@ def power_iteration( - input_matrix: np.array, vector: np.array, error_tol=1e-12, max_iterations=100 -) -> [float, np.array]: + input_matrix: np.ndarray, + vector: np.ndarray, + error_tol: float = 1e-12, + max_iterations: int = 100, +) -> tuple[float, np.ndarray]: """ Power Iteration. Find the largest eignevalue and corresponding eigenvector diff --git a/linear_algebra/src/rayleigh_quotient.py b/linear_algebra/src/rayleigh_quotient.py index 69bbbac119e8..78083aa755f1 100644 --- a/linear_algebra/src/rayleigh_quotient.py +++ b/linear_algebra/src/rayleigh_quotient.py @@ -1,10 +1,12 @@ """ https://en.wikipedia.org/wiki/Rayleigh_quotient """ +from typing import Any + import numpy as np -def is_hermitian(matrix: np.array) -> bool: +def is_hermitian(matrix: np.ndarray) -> bool: """ Checks if a matrix is Hermitian. >>> import numpy as np @@ -24,7 +26,7 @@ def is_hermitian(matrix: np.array) -> bool: return np.array_equal(matrix, matrix.conjugate().T) -def rayleigh_quotient(A: np.array, v: np.array) -> float: +def rayleigh_quotient(A: np.ndarray, v: np.ndarray) -> Any: """ Returns the Rayleigh quotient of a Hermitian matrix A and vector v. @@ -43,7 +45,9 @@ def rayleigh_quotient(A: np.array, v: np.array) -> float: array([[3.]]) """ v_star = v.conjugate().T - return (v_star.dot(A).dot(v)) / (v_star.dot(v)) + v_star_dot = v_star.dot(A) + assert isinstance(v_star_dot, np.ndarray) + return (v_star_dot.dot(v)) / (v_star.dot(v)) def tests() -> None: diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 6eba3a1638bd..0954a2d932b7 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -12,7 +12,7 @@ class Test(unittest.TestCase): - def test_component(self): + def test_component(self) -> None: """ test for method component """ @@ -21,28 +21,28 @@ def test_component(self): self.assertEqual(x.component(2), 3) _ = Vector() - def test_str(self): + def test_str(self) -> None: """ 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): + def test_size(self) -> None: """ test for size()-method """ x = Vector([1, 2, 3, 4]) self.assertEqual(len(x), 4) - def test_euclidLength(self): + def test_euclidLength(self) -> None: """ test for the eulidean length """ x = Vector([1, 2]) self.assertAlmostEqual(x.euclidLength(), 2.236, 3) - def test_add(self): + def test_add(self) -> None: """ test for + operator """ @@ -52,7 +52,7 @@ def test_add(self): self.assertEqual((x + y).component(1), 3) self.assertEqual((x + y).component(2), 4) - def test_sub(self): + def test_sub(self) -> None: """ test for - operator """ @@ -62,7 +62,7 @@ def test_sub(self): self.assertEqual((x - y).component(1), 1) self.assertEqual((x - y).component(2), 2) - def test_mul(self): + def test_mul(self) -> None: """ test for * operator """ @@ -72,19 +72,19 @@ def test_mul(self): self.assertEqual(str(x * 3.0), "(3.0,6.0,9.0)") self.assertEqual((a * b), 0) - def test_zeroVector(self): + def test_zeroVector(self) -> None: """ test for the global function zeroVector(...) """ self.assertTrue(str(zeroVector(10)).count("0") == 10) - def test_unitBasisVector(self): + def test_unitBasisVector(self) -> None: """ test for the global function unitBasisVector(...) """ self.assertEqual(str(unitBasisVector(3, 1)), "(0,1,0)") - def test_axpy(self): + def test_axpy(self) -> None: """ test for the global function axpy(...) (operation) """ @@ -92,7 +92,7 @@ def test_axpy(self): y = Vector([1, 0, 1]) self.assertEqual(str(axpy(2, x, y)), "(3,4,7)") - def test_copy(self): + def test_copy(self) -> None: """ test for the copy()-method """ @@ -100,7 +100,7 @@ def test_copy(self): y = x.copy() self.assertEqual(str(x), str(y)) - def test_changeComponent(self): + def test_changeComponent(self) -> None: """ test for the changeComponent(...)-method """ @@ -109,43 +109,43 @@ def test_changeComponent(self): x.changeComponent(1, 1) self.assertEqual(str(x), "(0,1,0)") - def test_str_matrix(self): + def test_str_matrix(self) -> None: 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): + def test_determinate(self) -> None: """ 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): + def test__mul__matrix(self) -> None: 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): + def test_changeComponent_matrix(self) -> None: 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): + def test_component_matrix(self) -> None: 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): + def test__add__matrix(self) -> None: 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): + def test__sub__matrix(self) -> None: 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): + def test_squareZeroMatrix(self) -> None: 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)), diff --git a/linear_algebra/src/transformations_2d.py b/linear_algebra/src/transformations_2d.py index 6a15189c5676..cdf42100d5d9 100644 --- a/linear_algebra/src/transformations_2d.py +++ b/linear_algebra/src/transformations_2d.py @@ -11,8 +11,6 @@ reflection(45) = [[0.05064397763545947, 0.893996663600558], [0.893996663600558, 0.7018070490682369]] """ -from __future__ import annotations - from math import cos, sin From c49fa088a0beb989f7553d697ec706f1af3a663d Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 6 Apr 2021 16:24:26 +0530 Subject: [PATCH 1468/2908] feat: Add mypy configuration file (#4315) * feat: Add mypy config file * refactor: Remove mypy options from build workflow * Remove linear_algebra Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 4 +--- mypy.ini | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 mypy.ini diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca3e8092276e..2ffc2aa293b0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,9 +21,7 @@ jobs: run: | python -m pip install --upgrade pip setuptools six wheel python -m pip install mypy pytest-cov -r requirements.txt - # FIXME: #4052 fix mypy errors in the exclude directories and remove them below - - run: mypy --ignore-missing-imports - --exclude '(data_structures|digital_image_processing|dynamic_programming|graphs|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . + - run: mypy . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000000..cf0ba26cfc82 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +ignore_missing_imports = True + +; FIXME: #4052 fix mypy errors in the exclude directories and remove them below +exclude = (data_structures|digital_image_processing|dynamic_programming|graphs|maths|matrix|other|project_euler|scripts|searches|strings*)/$ From 531d2d6d7e36b7aa793c4382fa14aa5977af0d7e Mon Sep 17 00:00:00 2001 From: algobytewise Date: Tue, 6 Apr 2021 19:04:18 +0530 Subject: [PATCH 1469/2908] Mypy fix rotation.py (#4319) * fix type-hints arguments * fix matrices & image-path * Update build.yml * Revert "Update build.yml" This reverts commit c2d04aef65292af6b5f9ca9581c25ac8aadf4a50. * use pathlib * feat: Add mypy configuration file (#4315) * feat: Add mypy config file * refactor: Remove mypy options from build workflow * Remove linear_algebra Co-authored-by: Christian Clauss * rebase & update mypy.ini * fix pre-commit errors Co-authored-by: Dhruv Manilawala Co-authored-by: Christian Clauss --- digital_image_processing/rotation/rotation.py | 18 +++++++++++------- mypy.ini | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/digital_image_processing/rotation/rotation.py b/digital_image_processing/rotation/rotation.py index 2951f18fc0ec..958d16fafb91 100644 --- a/digital_image_processing/rotation/rotation.py +++ b/digital_image_processing/rotation/rotation.py @@ -1,11 +1,13 @@ +from pathlib import Path + import cv2 import numpy as np from matplotlib import pyplot as plt def get_rotation( - img: np.array, pt1: np.float32, pt2: np.float32, rows: int, cols: int -) -> np.array: + img: np.ndarray, pt1: np.ndarray, pt2: np.ndarray, rows: int, cols: int +) -> np.ndarray: """ Get image rotation :param img: np.array @@ -21,17 +23,19 @@ def get_rotation( if __name__ == "__main__": # read original image - image = cv2.imread("lena.jpg") + image = cv2.imread( + str(Path(__file__).resolve().parent.parent / "image_data" / "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]]) + pts1 = np.array([[50, 50], [200, 50], [50, 200]], np.float32) + pts2 = np.array([[10, 100], [200, 50], [100, 250]], np.float32) + pts3 = np.array([[50, 50], [150, 50], [120, 200]], np.float32) + pts4 = np.array([[10, 100], [80, 50], [180, 250]], np.float32) # add all rotated images in a list images = [ diff --git a/mypy.ini b/mypy.ini index cf0ba26cfc82..b6c4d6fe2785 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True ; FIXME: #4052 fix mypy errors in the exclude directories and remove them below -exclude = (data_structures|digital_image_processing|dynamic_programming|graphs|maths|matrix|other|project_euler|scripts|searches|strings*)/$ +exclude = (data_structures|dynamic_programming|graphs|maths|matrix|other|project_euler|scripts|searches|strings*)/$ From 252df0a149502143a14e7283424d40b785dd451c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 7 Apr 2021 04:42:56 +0200 Subject: [PATCH 1470/2908] fix(mypy): Fix files in scripts/ (#4320) --- mypy.ini | 2 +- scripts/validate_filenames.py | 2 +- scripts/validate_solutions.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy.ini b/mypy.ini index b6c4d6fe2785..9eec22e22717 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True ; FIXME: #4052 fix mypy errors in the exclude directories and remove them below -exclude = (data_structures|dynamic_programming|graphs|maths|matrix|other|project_euler|scripts|searches|strings*)/$ +exclude = (data_structures|dynamic_programming|graphs|maths|matrix|other|project_euler|searches|strings*)/$ diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index 419295fe679d..ed23f3907114 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -4,7 +4,7 @@ try: from .build_directory_md import good_file_paths except ImportError: - from build_directory_md import good_file_paths + from build_directory_md import good_file_paths # type: ignore filepaths = list(good_file_paths()) assert filepaths, "good_file_paths() failed!" diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index fd804ea5aa31..364c71e2fb1c 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -22,7 +22,7 @@ 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) + spec.loader.exec_module(module) # type: ignore return module @@ -89,5 +89,5 @@ def test_project_euler(solution_path: pathlib.Path) -> None: 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()) + answer = str(solution_module.solution()) # type: ignore assert answer == expected, f"Expected {expected} but got {answer}" From cbe4d5f952e0596211643251817a822714a9fc1b Mon Sep 17 00:00:00 2001 From: Oliver Dewitz <65554808+OliverDew@users.noreply.github.com> Date: Mon, 12 Apr 2021 13:40:10 +0200 Subject: [PATCH 1471/2908] Fixed typo in docstring (#4326) --- strings/lower.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strings/lower.py b/strings/lower.py index b7abe9fc957d..9ae419123ceb 100644 --- a/strings/lower.py +++ b/strings/lower.py @@ -1,6 +1,6 @@ def lower(word: str) -> str: """ - Will convert the entire string to lowecase letters + Will convert the entire string to lowercase letters >>> lower("wow") 'wow' From ba974810d601254091d2f8076b96d7bb584a80f1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 22 Apr 2021 10:52:54 +0200 Subject: [PATCH 1472/2908] Simplify password_generator() (#4333) --- other/password_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/password_generator.py b/other/password_generator.py index 35e11e4dfb78..cf7250c814ff 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -16,7 +16,7 @@ def password_generator(length=8): >>> len(password_generator(-1)) 0 """ - chars = tuple(ascii_letters) + tuple(digits) + tuple(punctuation) + chars = ascii_letters + digits + punctuation return "".join(choice(chars) for x in range(length)) From 1a3997053875ca5e5e9d813176ed2f98c4933307 Mon Sep 17 00:00:00 2001 From: algobytewise Date: Fri, 23 Apr 2021 11:54:01 +0530 Subject: [PATCH 1473/2908] Add rgb_hsv_conversion.py (#4334) * Add rgb_hsv_conversion.py * updating DIRECTORY.md * snake-case Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + conversions/rgb_hsv_conversion.py | 159 ++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 conversions/rgb_hsv_conversion.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e6ce3ae718b3..26929255d1a0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -112,6 +112,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) + * [Rgb Hsv Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/rgb_hsv_conversion.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) diff --git a/conversions/rgb_hsv_conversion.py b/conversions/rgb_hsv_conversion.py new file mode 100644 index 000000000000..081cfe1d75e0 --- /dev/null +++ b/conversions/rgb_hsv_conversion.py @@ -0,0 +1,159 @@ +""" +The RGB color model is an additive color model in which red, green, and blue light +are added together in various ways to reproduce a broad array of colors. The name +of the model comes from the initials of the three additive primary colors, red, +green, and blue. Meanwhile, the HSV representation models how colors appear under +light. In it, colors are represented using three components: hue, saturation and +(brightness-)value. This file provides functions for converting colors from one +representation to the other. + +(description adapted from https://en.wikipedia.org/wiki/RGB_color_model and +https://en.wikipedia.org/wiki/HSL_and_HSV). +""" + + +def hsv_to_rgb(hue: float, saturation: float, value: float) -> list[int]: + """ + Conversion from the HSV-representation to the RGB-representation. + Expected RGB-values taken from + https://www.rapidtables.com/convert/color/hsv-to-rgb.html + + >>> hsv_to_rgb(0, 0, 0) + [0, 0, 0] + >>> hsv_to_rgb(0, 0, 1) + [255, 255, 255] + >>> hsv_to_rgb(0, 1, 1) + [255, 0, 0] + >>> hsv_to_rgb(60, 1, 1) + [255, 255, 0] + >>> hsv_to_rgb(120, 1, 1) + [0, 255, 0] + >>> hsv_to_rgb(240, 1, 1) + [0, 0, 255] + >>> hsv_to_rgb(300, 1, 1) + [255, 0, 255] + >>> hsv_to_rgb(180, 0.5, 0.5) + [64, 128, 128] + >>> hsv_to_rgb(234, 0.14, 0.88) + [193, 196, 224] + >>> hsv_to_rgb(330, 0.75, 0.5) + [128, 32, 80] + """ + if hue < 0 or hue > 360: + raise Exception("hue should be between 0 and 360") + + if saturation < 0 or saturation > 1: + raise Exception("saturation should be between 0 and 1") + + if value < 0 or value > 1: + raise Exception("value should be between 0 and 1") + + chroma = value * saturation + hue_section = hue / 60 + second_largest_component = chroma * (1 - abs(hue_section % 2 - 1)) + match_value = value - chroma + + if hue_section >= 0 and hue_section <= 1: + red = round(255 * (chroma + match_value)) + green = round(255 * (second_largest_component + match_value)) + blue = round(255 * (match_value)) + elif hue_section > 1 and hue_section <= 2: + red = round(255 * (second_largest_component + match_value)) + green = round(255 * (chroma + match_value)) + blue = round(255 * (match_value)) + elif hue_section > 2 and hue_section <= 3: + red = round(255 * (match_value)) + green = round(255 * (chroma + match_value)) + blue = round(255 * (second_largest_component + match_value)) + elif hue_section > 3 and hue_section <= 4: + red = round(255 * (match_value)) + green = round(255 * (second_largest_component + match_value)) + blue = round(255 * (chroma + match_value)) + elif hue_section > 4 and hue_section <= 5: + red = round(255 * (second_largest_component + match_value)) + green = round(255 * (match_value)) + blue = round(255 * (chroma + match_value)) + else: + red = round(255 * (chroma + match_value)) + green = round(255 * (match_value)) + blue = round(255 * (second_largest_component + match_value)) + + return [red, green, blue] + + +def rgb_to_hsv(red: int, green: int, blue: int) -> list[float]: + """ + Conversion from the RGB-representation to the HSV-representation. + The tested values are the reverse values from the hsv_to_rgb-doctests. + Function "approximately_equal_hsv" is needed because of small deviations due to + rounding for the RGB-values. + + >>> approximately_equal_hsv(rgb_to_hsv(0, 0, 0), [0, 0, 0]) + True + >>> approximately_equal_hsv(rgb_to_hsv(255, 255, 255), [0, 0, 1]) + True + >>> approximately_equal_hsv(rgb_to_hsv(255, 0, 0), [0, 1, 1]) + True + >>> approximately_equal_hsv(rgb_to_hsv(255, 255, 0), [60, 1, 1]) + True + >>> approximately_equal_hsv(rgb_to_hsv(0, 255, 0), [120, 1, 1]) + True + >>> approximately_equal_hsv(rgb_to_hsv(0, 0, 255), [240, 1, 1]) + True + >>> approximately_equal_hsv(rgb_to_hsv(255, 0, 255), [300, 1, 1]) + True + >>> approximately_equal_hsv(rgb_to_hsv(64, 128, 128), [180, 0.5, 0.5]) + True + >>> approximately_equal_hsv(rgb_to_hsv(193, 196, 224), [234, 0.14, 0.88]) + True + >>> approximately_equal_hsv(rgb_to_hsv(128, 32, 80), [330, 0.75, 0.5]) + True + """ + if red < 0 or red > 255: + raise Exception("red should be between 0 and 255") + + if green < 0 or green > 255: + raise Exception("green should be between 0 and 255") + + if blue < 0 or blue > 255: + raise Exception("blue should be between 0 and 255") + + float_red = red / 255 + float_green = green / 255 + float_blue = blue / 255 + value = max(max(float_red, float_green), float_blue) + chroma = value - min(min(float_red, float_green), float_blue) + saturation = 0 if value == 0 else chroma / value + + if chroma == 0: + hue = 0.0 + elif value == float_red: + hue = 60 * (0 + (float_green - float_blue) / chroma) + elif value == float_green: + hue = 60 * (2 + (float_blue - float_red) / chroma) + else: + hue = 60 * (4 + (float_red - float_green) / chroma) + + hue = (hue + 360) % 360 + + return [hue, saturation, value] + + +def approximately_equal_hsv(hsv_1: list[float], hsv_2: list[float]) -> bool: + """ + Utility-function to check that two hsv-colors are approximately equal + + >>> approximately_equal_hsv([0, 0, 0], [0, 0, 0]) + True + >>> approximately_equal_hsv([180, 0.5, 0.3], [179.9999, 0.500001, 0.30001]) + True + >>> approximately_equal_hsv([0, 0, 0], [1, 0, 0]) + False + >>> approximately_equal_hsv([180, 0.5, 0.3], [179.9999, 0.6, 0.30001]) + False + """ + check_hue = abs(hsv_1[0] - hsv_2[0]) < 0.2 + check_saturation = abs(hsv_1[1] - hsv_2[1]) < 0.002 + check_value = abs(hsv_1[2] - hsv_2[2]) < 0.002 + + return check_hue and check_saturation and check_value From 2ce6be009a2f374c0c6e5ab1f7f7c7274c5e0c55 Mon Sep 17 00:00:00 2001 From: David Leal Date: Sun, 25 Apr 2021 23:31:34 -0500 Subject: [PATCH 1474/2908] feat: Add Discord badge in `README.md` (#4357) * feat: Add Discord badge in `README.md` * Update README.md Co-authored-by: Dhruv Manilawala --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f81031b53ebb..1e85ed0daa7c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # 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)  +[![Discord chat](https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA&style=flat-square)](https://discord.gg/c7MnfGFGa6)  [![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  [![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)  From 69457357e8c6a3530034aca9707e22ce769da067 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 26 Apr 2021 06:45:26 +0200 Subject: [PATCH 1475/2908] binary_tree_traversals.py: Simplify with dataclasses (#4336) * binary_tree_traversals.py: Simplify with dataclasses * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: Dhruv Manilawala * Optional["Node"] Co-authored-by: Dhruv Manilawala --- .../binary_tree/binary_tree_traversals.py | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 7c0ee1dbbc2a..7857880dada9 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -1,24 +1,17 @@ # https://en.wikipedia.org/wiki/Tree_traversal +from dataclasses import dataclass +from typing import Optional +@dataclass 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 + data: int + left: Optional["Node"] = None + right: Optional["Node"] = None 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 + return Node(1, Node(2, Node(4), Node(5)), Node(3)) def preorder(root: Node): From 6f21f76696ff6657bff6fc2239315a1650924190 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Mon, 26 Apr 2021 11:16:50 +0530 Subject: [PATCH 1476/2908] fix(ci): Update pre-commit hooks and apply new black (#4359) * fix(ci): Update pre-commit hooks and apply new black * remove empty docstring --- .github/workflows/pre-commit.yml | 2 +- .pre-commit-config.yaml | 6 +++--- data_structures/binary_tree/binary_search_tree.py | 2 +- data_structures/heap/heap.py | 10 +++++----- data_structures/heap/max_heap.py | 10 +++++----- data_structures/linked_list/deque_doubly.py | 2 +- data_structures/stacks/stack.py | 10 +++++----- digital_image_processing/sepia.py | 2 +- hashes/md5.py | 1 - machine_learning/linear_discriminant_analysis.py | 2 +- machine_learning/linear_regression.py | 2 +- maths/monte_carlo_dice.py | 2 +- other/least_recently_used.py | 2 +- 13 files changed, 26 insertions(+), 27 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index dd1a8a945092..27a5a97c0b6c 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,7 +14,7 @@ jobs: ~/.cache/pip key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/setup-python@v2 - - uses: psf/black@20.8b1 + - uses: psf/black@21.4b0 - name: Install pre-commit run: | python -m pip install --upgrade pip diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b48da86ee57d..b666e88aa162 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,17 +13,17 @@ repos: )$ - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 21.4b0 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.7.0 + rev: 5.8.0 hooks: - id: isort args: - --profile=black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.0 + rev: 3.9.1 hooks: - id: flake8 args: diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 45c3933fe899..a1ed1d0ac2a5 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -150,7 +150,7 @@ def inorder(self, arr: list, node: Node): 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 """ + """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] diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 8592362c23b9..65a70e468d1c 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -32,7 +32,7 @@ def __repr__(self) -> str: return str(self.h) def parent_index(self, child_idx: int) -> Optional[int]: - """ return the parent index of given child """ + """return the parent index of given child""" if child_idx > 0: return (child_idx - 1) // 2 return None @@ -78,7 +78,7 @@ def max_heapify(self, index: int) -> None: self.max_heapify(violation) def build_max_heap(self, collection: Iterable[float]) -> None: - """ build max heap from an unsorted array""" + """build max heap from an unsorted array""" self.h = list(collection) self.heap_size = len(self.h) if self.heap_size > 1: @@ -87,14 +87,14 @@ def build_max_heap(self, collection: Iterable[float]) -> None: self.max_heapify(i) def max(self) -> float: - """ return the max in the heap """ + """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 """ + """get and remove max from heap""" if self.heap_size >= 2: me = self.h[0] self.h[0] = self.h.pop(-1) @@ -108,7 +108,7 @@ def extract_max(self) -> float: raise Exception("Empty heap") def insert(self, value: float) -> None: - """ insert a new value into the max heap """ + """insert a new value into the max heap""" self.h.append(value) idx = (self.heap_size - 1) // 2 self.heap_size += 1 diff --git a/data_structures/heap/max_heap.py b/data_structures/heap/max_heap.py index 2a08f8fa2cd1..fbc8eed09226 100644 --- a/data_structures/heap/max_heap.py +++ b/data_structures/heap/max_heap.py @@ -21,7 +21,7 @@ def __init__(self): self.__size = 0 def __swap_up(self, i: int) -> None: - """ Swap the element up """ + """Swap the element up""" temporary = self.__heap[i] while i // 2 > 0: if self.__heap[i] > self.__heap[i // 2]: @@ -30,13 +30,13 @@ def __swap_up(self, i: int) -> None: i //= 2 def insert(self, value: int) -> None: - """ Insert new element """ + """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 """ + """Swap the element down""" while self.__size >= 2 * i: if 2 * i + 1 > self.__size: bigger_child = 2 * i @@ -52,7 +52,7 @@ def __swap_down(self, i: int) -> None: i = bigger_child def pop(self) -> int: - """ Pop the root element """ + """Pop the root element""" max_value = self.__heap[1] self.__heap[1] = self.__heap[self.__size] self.__size -= 1 @@ -65,7 +65,7 @@ def get_list(self): return self.__heap[1:] def __len__(self): - """ Length of the array """ + """Length of the array""" return self.__size diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index c9ae8b3d1ba2..2b9d70c223c4 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -9,7 +9,7 @@ class _DoublyLinkedBase: - """ A Private class (to be inherited) """ + """A Private class (to be inherited)""" class _Node: __slots__ = "_prev", "_data", "_next" diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 840cde099d38..276684e12184 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -22,28 +22,28 @@ def __str__(self) -> str: return str(self.stack) def push(self, data): - """ Push an element to the top of the stack.""" + """Push an element to the top of the stack.""" if len(self.stack) >= self.limit: raise StackOverflowError self.stack.append(data) def pop(self): - """ Pop an element off of the top of the stack.""" + """Pop an element off of the top of the stack.""" return self.stack.pop() def peek(self): - """ Peek at the top-most element of the stack.""" + """Peek at the top-most element of the stack.""" return self.stack[-1] def is_empty(self) -> bool: - """ Check if a stack is empty.""" + """Check if a stack is empty.""" return not bool(self.stack) def is_full(self) -> bool: return self.size() == self.limit def size(self) -> int: - """ Return the size of the stack.""" + """Return the size of the stack.""" return len(self.stack) def __contains__(self, item) -> bool: diff --git a/digital_image_processing/sepia.py b/digital_image_processing/sepia.py index dfb5951676aa..e9dd2c06066d 100644 --- a/digital_image_processing/sepia.py +++ b/digital_image_processing/sepia.py @@ -19,7 +19,7 @@ def to_grayscale(blue, green, red): 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""" + """Helper function to normalize R/G/B value -> return 255 if value > 255""" return min(value, 255) for i in range(pixel_h): diff --git a/hashes/md5.py b/hashes/md5.py index b7888fb610ac..b08ab957340a 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -94,7 +94,6 @@ def not32(i): def sum32(a, b): - """""" return (a + b) % 2 ** 32 diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 0d19e970e973..18553a77ad1c 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -283,7 +283,7 @@ def valid_input( # Main Function def main(): - """ This function starts execution phase """ + """This function starts execution phase""" while True: print(" Linear Discriminant Analysis ".center(50, "*")) print("*" * 50, "\n") diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index a726629efe00..b0bbc7b904c3 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -88,7 +88,7 @@ def run_linear_regression(data_x, data_y): def main(): - """ Driver function """ + """Driver function""" data = collect_dataset() len_data = data.shape[0] diff --git a/maths/monte_carlo_dice.py b/maths/monte_carlo_dice.py index e8e3abe83a99..17cedbdbcb18 100644 --- a/maths/monte_carlo_dice.py +++ b/maths/monte_carlo_dice.py @@ -7,7 +7,7 @@ class Dice: NUM_SIDES = 6 def __init__(self): - """ Initialize a six sided dice """ + """Initialize a six sided dice""" self.sides = list(range(1, Dice.NUM_SIDES + 1)) def roll(self): diff --git a/other/least_recently_used.py b/other/least_recently_used.py index 213339636469..d0e27efc6dc8 100644 --- a/other/least_recently_used.py +++ b/other/least_recently_used.py @@ -4,7 +4,7 @@ class LRUCache: - """ Page Replacement Algorithm, Least Recently Used (LRU) Caching.""" + """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 From 727341e3db4a28dae3f1bbf166522844f3de8f6d Mon Sep 17 00:00:00 2001 From: ngxingyu Date: Tue, 4 May 2021 14:49:41 +0800 Subject: [PATCH 1477/2908] Create check_pangram.py (#4389) --- strings/check_pangram.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/strings/check_pangram.py b/strings/check_pangram.py index e3695b918524..81384bfd4cc6 100644 --- a/strings/check_pangram.py +++ b/strings/check_pangram.py @@ -38,9 +38,9 @@ def check_pangram_faster( """ >>> check_pangram_faster("The quick brown fox jumps over the lazy dog") True - >>> check_pangram("Waltz, bad nymph, for quick jigs vex.") + >>> check_pangram_faster("Waltz, bad nymph, for quick jigs vex.") True - >>> check_pangram("Jived fox nymph grabs quick waltz.") + >>> check_pangram_faster("Jived fox nymph grabs quick waltz.") True >>> check_pangram_faster("The quick brown fox jumps over the la_y dog") False @@ -50,7 +50,9 @@ def check_pangram_faster( flag = [False] * 26 for char in input_str: if char.islower(): - flag[ord(char) - ord("a")] = True + flag[ord(char) - 97] = True + elif char.isupper(): + flag[ord(char) - 65] = True return all(flag) From deb71167e7d5aabe24ae4a5c33e8e73dd3af8ece Mon Sep 17 00:00:00 2001 From: Ahmed Haj Abdel Khaleq <31858489+AhmedHaj@users.noreply.github.com> Date: Wed, 12 May 2021 02:22:42 -0400 Subject: [PATCH 1478/2908] [mypy] Fix type annotations for linked_stack.py, evaluate_postfix_notations.py, stack.py in data structures (#4409) * [mypy] Fix type annotations for linked_stack.py, next_greater_element.py, stack.py * Reformatted files according to black --- data_structures/stacks/evaluate_postfix_notations.py | 4 +++- data_structures/stacks/linked_stack.py | 6 ++++-- data_structures/stacks/stack.py | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/data_structures/stacks/evaluate_postfix_notations.py b/data_structures/stacks/evaluate_postfix_notations.py index a03cb43bb020..2a4baf9d6b52 100644 --- a/data_structures/stacks/evaluate_postfix_notations.py +++ b/data_structures/stacks/evaluate_postfix_notations.py @@ -1,3 +1,5 @@ +from typing import Any, List + """ The Reverse Polish Nation also known as Polish postfix notation or simply postfix notation. @@ -21,7 +23,7 @@ def evaluate_postfix(postfix_notation: list) -> int: return 0 operations = {"+", "-", "*", "/"} - stack = [] + stack: List[Any] = [] for token in postfix_notation: if token in operations: diff --git a/data_structures/stacks/linked_stack.py b/data_structures/stacks/linked_stack.py index 1a2d07f20e7c..0b9c9d45e61f 100644 --- a/data_structures/stacks/linked_stack.py +++ b/data_structures/stacks/linked_stack.py @@ -1,5 +1,5 @@ """ A Stack using a linked list like structure """ -from typing import Any +from typing import Any, Optional class Node: @@ -42,7 +42,7 @@ class LinkedStack: """ def __init__(self) -> None: - self.top = None + self.top: Optional[Node] = None def __iter__(self): node = self.top @@ -134,6 +134,8 @@ def peek(self) -> Any: """ if self.is_empty(): raise IndexError("peek from empty stack") + + assert self.top is not None return self.top.data def clear(self) -> None: diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 276684e12184..245d39b32c07 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -1,3 +1,6 @@ +from typing import List + + class StackOverflowError(BaseException): pass @@ -12,7 +15,7 @@ class Stack: """ def __init__(self, limit: int = 10): - self.stack = [] + self.stack: List[int] = [] self.limit = limit def __bool__(self) -> bool: From 03d9b6747bee1ef3537da204d09771c911093a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20J=C3=BCrgens?= Date: Wed, 12 May 2021 07:48:23 +0000 Subject: [PATCH 1479/2908] feat(ci): Hash project euler solutions (#4411) * hash project euler solutions * fix errors * Return missing annotation * Fix typo * Extract variable to shorten excessively long line * Update scripts/validate_solutions.py * Update scripts/validate_solutions.py * Simplify with str.encode() * PEP 585: type hinting generics in standard collections; * str().encode() * Texas two step to placate black, flake8, mypy Co-authored-by: Andrii Siriak Co-authored-by: Christian Clauss --- scripts/project_euler_answers.json | 1450 ++++++++++++++-------------- scripts/validate_solutions.py | 15 +- 2 files changed, 734 insertions(+), 731 deletions(-) diff --git a/scripts/project_euler_answers.json b/scripts/project_euler_answers.json index 05c144d1e06a..6d354363ee5f 100644 --- a/scripts/project_euler_answers.json +++ b/scripts/project_euler_answers.json @@ -1,727 +1,727 @@ { - "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", - "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" + "001": "c0b20f4665d0388d564f0b6ecf3edc9f9480cb15fff87198b95701d9f5fe1f7b", + "002": "1f5882e19314ac13acca52ad5503184b3cb1fd8dbeea82e0979d799af2361704", + "003": "5c09f0554518a413e58e6bc5964ba90655713483d0b2bbc94572ad6b0b4dda28", + "004": "aa74f52b4c428d89606b411bc165eb81a6266821ecc9b4f30cdb70c5c930f4d9", + "005": "1ba90ab11bfb2d2400545337212b0de2a5c7f399215175ade6396e91388912b1", + "006": "537942be3eb323c507623a6a73fa87bf5aeb97b7c7422993a82aa7c15f6d9cd6", + "007": "ecbe74e25cfa4763dbc304ccac2ffb9912e9625cd9993a84bd0dd6d7dc0ca021", + "008": "b9fb30b6553415e9150051ce5710a93d0f55b22557c0068d8e16619a388f145a", + "009": "d912d9d473ef86f12da1fb2011c5c0c155bd3a0ebdb4bbd7ea275cecdcb63731", + "010": "bed2d160e02f0540f19a64ca738aacb79cfcd08ba7e2421567b16cb6e7e3e90e", + "011": "9ded5bc849d33e477aa9c944138d34f0aacc485a372e84464e8a572712a5b7da", + "012": "3e7be445b6c19e6db58c2482005c1f78cb74011a4279249ca632011a9f1b61a2", + "013": "3cb265a96c5645a9ad11d47551f015c25f3f99792c951617656d84626fbc4868", + "014": "78a262dd40eba0f7195686ec7f3891a39437523456f8d16fa9065a34409eeac6", + "015": "7b8f812ca89e311e1b16b903de76fa7b0800a939b3028d9dc4d35f6fa4050281", + "016": "a6f988d30328bd706c66f8ac0d92aac21dd732149cdd69cb31f459dca20c5abe", + "017": "1a455b216c6e916943acf3fa4c7e57a7a5cac66d97cc51befca810c223ef9c23", + "018": "fde3f2e7127f6810eb4160bf7bb0563240d78c9d75a9a590b6d6244748a7f4ff", + "019": "284de502c9847342318c17d474733ef468fbdbe252cddf6e4b4be0676706d9d0", + "020": "c86a2932e1c79343a3c16fb218b9944791aaeedd3e30c87d1c7f505c0e588f7c", + "021": "e8c6ef4a1736a245b5682e0262c5c43862cfb233ca5e286be2f5bb4d8a974ecf", + "022": "85148c096c25e3ed3da55c7e9c89448018b0f5f53ad8d042129c33d9beac6736", + "023": "42e2552a2f589e021824339e2508629ffa00b3489ea467f47e77a1ea97e735c9", + "024": "4677b3d9daa3b30a9665e4558f826e04f7833dda886b8ef24f7176519a0db537", + "025": "7d398da8791745001b3d1c41030676d1c036687eb1ab32e0b5a1832e7579c073", + "026": "fbe10beedf9d29cf53137ba38859ffd1dbe7642cedb7ef0a102a3ab109b47842", + "027": "e4110e0852a2f70703f0081fc91c4a20f595919a038729cb37c564d68b875c6f", + "028": "261171a770d594f6a7fc76c1a839eda7f6dd4e9495e00e75048578fc86d8adf0", + "029": "a207c35d8417aeed4c9e78bcf83f936cd8191c702893be62aa690ce16bc909ca", + "030": "46e68e4199ab0a663ab306651528b06756556c9f0d8b819095af45e036dfbe6b", + "031": "8de34b4ba97b184c7a2096b9266776175242b87d67bc8d77d7289be6f70cd105", + "032": "0d246750daa7f1b367a21f55da454ddc8f62e0a95d163062e9b9273320d5130f", + "033": "ad57366865126e55649ecb23ae1d48887544976efea46a48eb5d85a6eeb4d306", + "034": "728b8d7d6d5d34cad9cbb7c3ea15f807ae57144594b1740b3c73b82314ccd1ed", + "035": "02d20bbd7e394ad5999a4cebabac9619732c343a4cac99470c03e23ba2bdc2bc", + "036": "9480c0160719234b57defc0681c0949a175ffb3ff4a3bf5e8163ac843f383f35", + "037": "e9800abda89919edac504e90dac91f95e0778e3ba0f21a0bac4e77a84766eaaf", + "038": "b2004522103364a6e842b9d042c0707d79af68dec7810078729d061fb7948912", + "039": "fd0f7e53c5b02b688a57ee37f3d52065cb168a7b9fd5a3abd93d37e1559fbd30", + "040": "d29d53701d3c859e29e1b90028eec1ca8e2f29439198b6e036c60951fb458aa1", + "041": "bf05020e70de94e26dba112bb6fb7b0755db5ca88c7225e99187c5a08c8a0428", + "042": "79d6eaa2676189eb927f2e16a70091474078e2117c3fc607d35cdc6b591ef355", + "043": "6512f20c244844b6130204379601855098826afa1b55ff91c293c853ddf67db5", + "044": "97e2524fd3796e83b06c0f89fdcb16e4c544e76e9c0496f57ac84834869f4cc3", + "045": "8b0300d71656b9cf0716318be9453c99a13bb8644d227fd683d06124e6a28b35", + "046": "8485ee802cc628b8cbd82476133d11b57af87e00711516a703525a9af0193b12", + "047": "c7274da71333bd93201fa1e05b1ed54e0074d83f259bd7148c70ddc43082bde1", + "048": "743d17cbff06ab458b99ecbb32e1d6bb9a7ff2ac804118f7743177dd969cfc61", + "049": "47c6094ff1ff6e37788def89190c8256619ef1511681c503fea02c171569d16e", + "050": "6ee74ef623df9fb69facd30b91ed78fe70370462bb267097f0dfeef9d9b057bb", + "051": "d17cec28356b4f9a7f1ec0f20cca4c89e270aeb0e75d70d485b05bb1f28e9f6d", + "052": "ebd72b510911af3e254a030cd891cb804e1902189eee7a0f6199472eb5e4dba2", + "053": "9705cc6128a60cc22581217b715750a6053b2ddda67cc3af7e14803b27cf0c1f", + "054": "12e2c8df501501b2bb531e941a737ffa7a2a491e849c5c5841e3b6132291bc35", + "055": "9f484139a27415ae2e8612bf6c65a8101a18eb5e9b7809e74ca63a45a65f17f4", + "056": "3658d7fa3c43456f3c9c87db0490e872039516e6375336254560167cc3db2ea2", + "057": "620c9c332101a5bae955c66ae72268fbcd3972766179522c8deede6a249addb7", + "058": "196f327021627b6a48db9c6e0a3388d110909d4bb957eb3fbc90ff1ecbda42cb", + "059": "0295239a9d71f7452b93e920b7e0e462f712af5444579d25e06b9614ed77de74", + "060": "ad7c26db722221bfb1bf7e3c36b501bedf8be857b1cfa8664fccb074b54354f9", + "061": "94e4fb283c1abcccae4b8b28e39a294a323cdc9732c3d3ce1133c518d0a286f6", + "062": "d25a595036aa8722157aca38f90084acb369b00df1070f49e203d5a3b7a0736d", + "063": "0e17daca5f3e175f448bacace3bc0da47d0655a74c8dd0dc497a3afbdad95f1f", + "064": "6d62aa4b52071e39f064a930d190b85ab327eb1a5045a8050ac538666ee765ca", + "065": "1c6c0bb2c7ecdc3be8e134f79b9de45155258c1f554ae7542dce48f5cc8d63f0", + "066": "316c0f93c7fe125865d85d6e7e7a31b79e9a46c414c45078b732080fa22ef2a3", + "067": "53f66b6783cb7552d83015df01b0d5229569fce1dd7d1856335c7244b9a3ded6", + "068": "4bf689d09a156621220881a2264dc031b2bfb181213b26d6ff2c338408cf94c3", + "069": "79555e4b891e2885525c136f8b834cc0b1e9416960b12e371111a5cb2da0479f", + "070": "08c6a7c8c06a01d2b17993ada398084b0707652bcfbd580f9173bcddf120ac2c", + "071": "63f032489227c969135c6a6571fe9b33d6970dc6eca32c2086c61a4a099c98fa", + "072": "9ef8a4249d4b8f24147ab6e9ad2536eb04f10fb886a8099e88e0e7c41cf7c616", + "073": "ae9f9c786cd0f24fe03196d5061545862d87a208580570d46e2cfb371319aa68", + "074": "b7c7470e59e2a2df1bfd0a4705488ee6fe0c5c125de15cccdfab0e00d6c03dc0", + "075": "8a426e100572b8e2ea7c1b404a1ee694699346632cf4942705c54f05162bc07a", + "076": "81c54809c3bdfc23f844fde21ae645525817b6e1bee1525270f49282888a5546", + "077": "7f2253d7e228b22a08bda1f09c516f6fead81df6536eb02fa991a34bb38d9be8", + "078": "71374036b661ac8ffe4b78c191050c3ccd1c956ca8a5f465ea1956f7ce571f63", + "079": "2df095aea1862ebfed8df7fb26e8c4a518ca1a8f604a31cfba9da991fc1d6422", + "080": "58bfe3a44f8ae452aaa6ef6267bafc3e841cfe7f9672bdfeb841d2e3a62c1587", + "081": "04bad90d08bdf11010267ec9d1c9bbb49a813194dace245868ea8140aec9a1f7", + "082": "52c42c55daea3131d5357498b8a0ddcf99d1babd16f6ccaee67cb3d0a665b772", + "083": "a825281bc5ce8fe70d66a04e96314e7de070f11fed0f78bc81e007ca7c92e8b0", + "084": "692a776beae0e92d1121fed36427c10d0860344614ead6b4760d1b7091a6ab1f", + "085": "7b2e7211fb4f4d8352c9215c591252344775c56d58b9a5ff88bda8358628ec4e", + "086": "8ffe8459134b46975acd31df13a50c51dbeacf1c19a764bf1602ba7c73ffc8fb", + "087": "cec1917df3b3ee1f43b3468596ed3042df700dc7a752fefc06c4142a2832995d", + "088": "c06356fdcaff01810e1f794263f3e44a75f28e8902a145a0d01a1fff77614722", + "089": "0df5486b7bca884d5f00c502e216f734b2865b202397f24bca25ac9b8a95ab4a", + "090": "cb69775effd93fc34ef38dfbfcdc4c593b1a3d8e7ab70c0f05d627dbc5cbd298", + "091": "327f057e054d1e6a9a1be4ac6acc4b1dedc63d8a88222396ffe98b3194067347", + "092": "538cd20a275b610698691d714b2adf4e4c321915def05667f4d25d97413ec076", + "093": "d8ed8ca27d83a63df6982905ea53b4613b9d7974edcee06f301cf43d63177f47", + "094": "d1b79281d95ce5bfa848060de4e0c80af2c3cae1ff7453cca31ff31e2d67ac14", + "095": "0a3ddcd71cf30a567070630f947ab79fc168865ba0bf112aed9b71fb4e76c32f", + "096": "9c527d233befbf357335e18e6dd5b14ef3a62e19ef34f90bd3fb9e5a2a0a0111", + "097": "f0e2911e303617c9648692ee8056beeb045d89e469315716abed47cd94a3cd56", + "098": "ededac5db280586f534cde4f69ce2c134d2360d6b5da3c3ebc400494cc016e78", + "099": "92c5fd0421c1d619cbf1bdba83a207261f2c5f764aed46db9b4d2de03b72b654", + "100": "993189cbf49fef4c913aa081f2ef44d360b84bf33d19df93fce4663ac34e9927", + "101": "e8539f8b271851cad65d551354874d3086fa9ff7b6f6a2ab9890d63f5ba16c68", + "102": "9d693eeee1d1899cbc50b6d45df953d3835acf28ee869879b45565fccc814765", + "103": "1f17277005b8d58ad32f2cbee4c482cb8c0f3687c3cfe764ec30ee99827c3b1d", + "104": "87dfcf5471e77980d098ff445701dbada0f6f7bac2fa5e43fa7685ec435040e1", + "105": "a76f4e7fa1357a955743d5c0acb2e641c50bcaf0eec27eb4aaffebb45fe12994", + "106": "197f5e68d1e83af7e40a7c7717acc6a99767bf8c53eece9253131a3790a02063", + "107": "bf13bc90121776d7de3c4c3ca4c400a4c12284c3da684b3d530113236813ce81", + "108": "3dea386e2c4a8a0633b667fdd4beacd8bb3fe27c282f886c828ad7d6b42c2d73", + "109": "735cc3e619b9a1e3ac503ba5195c43c02d968113fd3795373ca085ed7777b54d", + "110": "01b4e8163485356b46f612c9d40ed4b8602621d4d02289623e7dbb3dcbe03395", + "111": "97c1b054c094337ec1397cd5ccdf6c9efe1067ad16f531824a94eaadb3c0953b", + "112": "c99c843e0f6d6566132d97c829780332218e005efc14b633e25a5badb912d63a", + "113": "8dbc8319e5d8923ef7ab42108341ee2c32a34ffc0d19d5ae5677f1564813314a", + "114": "b3b9ebc9f9ddadb6b630eeef5d7ba724b3bb4d071b249318093eb7547949bbb9", + "115": "80c3cd40fa35f9088b8741bd8be6153de05f661cfeeb4625ffbf5f4a6c3c02c4", + "116": "a39208d7130682b772d6206acd746bc3779cc1bc0033f0a472e97993d0a32d5b", + "117": "54201fbc7a70d21c1b0acede7708f1658d8e87032ab666001e888e7887c67d50", + "118": "834e6235764ae632737ebf7cd0be66634c4fb70fe1e55e858efd260a66a0e3a9", + "119": "bcabd9609d7293a3a3f1640c2937e302fa52ff03a95c117f87f2c465817eba5e", + "120": "2bd8cabf5aecfcadde03beda142ac26c00b6ccfc59fdcb685672cd79a92f63a6", + "121": "5292478e83f6b244c7c7c5c1fe272121abdc2982f66ed11fcbc6ea7e73af124d", + "122": "6d78b19a042a64f08cc4df0d42fb91cd757829718e60e82a54e3498f03f3ef32", + "123": "057b9b6e49d03958b3f38e812d2cfdd0f500e35e537b4fa9afedd2f3444db8a2", + "124": "d251170c5287da10bffc1ac8af344e0c434ef5f649fd430fcf1049f90d45cf45", + "125": "e9b7a676dc359ffce7075886373af79e3348ddbf344502614d9940eecd0532c1", + "126": "38752ed2e711a3c001d5139cb3c945c0f780939db4ea80d68f31e6763b11cfba", + "127": "e707d9f315269a34d94d9d9fa4f8b29328e66b53447ef38419c6033e57d5d123", + "128": "5e15922fba7f61ddccb2ee579b5ec35034cc32def25ff156ae2b0a3e98c4414e", + "129": "3cc4ad1254491787f52a66e595dbb573e13ceb554c51d81e42d5490a575da070", + "130": "7a6e9899cccb6a01e05013c622422717f54853f7f2581bc3b88a78b25981da08", + "131": "4a8596a7790b5ca9e067da401c018b3206befbcf95c38121854d1a0158e7678a", + "132": "ed77e05f47f7f19f09cae9b272bfd6daa1682b426d39dcb7f473234c0c9381c5", + "133": "e456d3fec55d88653dd88c3f8bbde1f8240c8ceb7882016d19e6f329e412a4ae", + "134": "b144116982f4f0930038299efbdd56afc1528ef59047fb75bade8777309fde4b", + "135": "0709e1008834c2ca8648376ac62d74ac8df5457069cbfedf2b0776dab07a3c5b", + "136": "84692ebaa4fc17e9cfce27126b3fc5a92c1e33e1d94658de0544f8b35a597592", + "137": "6eca481578c967fb9373fe4ce7930b39d8eefe1c0c9c7cb5af241a766bd4dfbc", + "138": "1b5f0f504917592dea2e878497b0e12655366f2a7a163e0a081d785124982d2c", + "139": "0d2f26ec4004c99171fc415282ec714afa617208480b45aeb104c039dc653f5d", + "140": "78ceab5e434a86a6a6bb4f486707bffaf536ef6cb2cc5b45a90b3edd89a03283", + "141": "d74ae4b07f05779065fb038b35d85a21444ed3bed2373f51d9e22d85a16a704c", + "142": "f59af8b0b63a3d0eb580405092c1539261aec18890ea5b6d6e2d93697d67cd38", + "143": "66e9d1093f00eef9a32e704232219f462138f13c493cc0775c507cf51cb231ed", + "144": "09a1b036b82baba3177d83c27c1f7d0beacaac6de1c5fdcc9680c49f638c5fb9", + "145": "b910b9b7bf3c3f071e410e0474958931a022d20c717a298a568308250ed2b0da", + "146": "5292f0166523ea1a89c9f7f2d69064dee481a7d3c119841442cd36f03c42b657", + "147": "cdb162a8a376b1df385dac44ce7b10777c9fea049961cb303078ebbd08d70de8", + "148": "54f631973f7bc002a958b818a1e99e2fc1a91c41eafe19b9136fac9a4eb8d7b8", + "149": "c49382eb9fc41e750239ac7b209513a266e80a268c83cf4d0c79f571629bac48", + "150": "c89b0574a2e2f4a63845fe0fd9d51122d9d4149d887995051c3e53d2244bba41", + "151": "5d09e3b57ced9fd215acc96186743e422ce48900b8992c9b6c74d3e4117e4140", + "152": "c3ea99f86b2f8a74ef4145bb245155ff5f91cd856f287523481c15a1959d5fd1", + "153": "fb57f89f289ee59c36cede64d2d13828b8997766b49aa4530aabfc18ff4a4f17", + "154": "c877d90a178511a52ae2b2119e99e0b8b643cec44d5fd864bd3ef3e0d7f1f4bb", + "155": "58801bebc14c905b79c209affab74e176e2e971c1d9799a1a342ae6a3c2afbc1", + "156": "983d2222220ab7ffa243f48274f6eb82f92258445b93b23724770995575d77fe", + "157": "023344e94ad747fbc529e3e68b95e596badcc445c85c1c7c8fa590e3d492779a", + "158": "d1b58f4c07d1db5eb97785807b6d97a0d1ee1340e7dbcc7bb289f3547559f2fc", + "159": "cd3a3d2cf8973c5f2c97ebed2460784818513e7d0fee8f98f4fdcf510295e159", + "160": "3a926519b024ea9df5e7ad79d0b1c4400f78f58d07834f5ecd7be522112b676d", + "161": "2b3d09a4c76b282db1428342c82c5a55c0ab57c7a7640e0850d82021164477e9", + "162": "d50ce1ab3a25a5c5e020517092128ab3ec4a3bd5b58673b2e6cda86bcc0c48a0", + "163": "7e17ce0fca5d559f76015765c652d76b8468f9ddc91c2069d7799867b9d52769", + "164": "5c680d0b2c4dfac8aade87be60cb4d04a4c3d4db398f51e2cbf131d699b630a8", + "165": "304de2e63f91f8f74faaebae7a7ec2e0c6e0d8d322d8a747e4e3be88de2d3505", + "166": "14212843872dab188a86eb1f0f7012e1b72ea1097545f29377b3b9b52822af76", + "167": "18c18f8710f831a82eb74ae979bd36d609bee818c972ff88f8d8fa065270f951", + "168": "66640021d592f79b510f9d6101bd8eca89893187d23919c8edff4075e73ae390", + "169": "819b01e0394727fd089f84b9351243176920f03d0c6e33e5ff136016da5d8d4e", + "170": "e68fadd33a8c41d9a259577a278d8518aeb8b81c67b8cf03ccf43fc451ec8bd8", + "171": "33bf9ed4714b0e5da8828f8b3d9d3e9d0cf55c1d496324acb04a3f195252749c", + "172": "b9a27b513dc15448772cac5e914de61f02efe323f528892c0bff86d19913a6bd", + "173": "1b2a5e44fda5dfee3ce230f44fe73c672249f6620cdbaa343ba0ba808034958c", + "174": "98aabf085c6c8647f5e8a4775dc1d036513742d8e03b8c5c51e41bdfc9c3e7ae", + "175": "c03dcb22b7faf121d99542018dd10a07a778abee2678d35c03394a8d207b826b", + "176": "4fff1a7beda4291446d76e5ed5177c3f36e52a10481009fdaf2976da39e802ae", + "177": "614d3df0ba5fdffab2328eff8e9ca2d76b09bbc447c06bf1fab0419ae278fae9", + "178": "094a2ba3011118efdd9d4c1f839e6747dee8ba953b52e9012fe2977e32599375", + "179": "9f5563a5ea90ca7023f0304acba78005ee6b7351245a8a668a94dfef160f8d29", + "180": "dbef09115a57784ea4ea14c1fe35571301b0d6139bea29d1b9e0babf2c2aae05", + "181": "3920627e86db42beb1cdf61d026f9f7798082f1221db25a17fb7feb6c1d49027", + "182": "58096166bb8199abf4e07a9ef0f960065e5a635443c1937a1a3c527ade51d594", + "183": "bdf84a73b16a5dd5ece189dc970ab2c8f0cb5334c96bdd1d6ba2bad6e7f8a213", + "184": "c1e8c0f1b1eb3c258923e9daa46ef055bd79512b485c7dc73a9c5e395d5e6911", + "185": "0ea72907eb6c1120740cd80ee6b9a935cd754edcf69379328f24dfc3f09b9986", + "186": "3c0078aeae0362b6b7561518d3eb28400193fec73aab35980f138c28b6579433", + "187": "f2bc655b33e35669ee9adc841cbda98e0834083eb0657d10f7170e70081db7e0", + "188": "38e0291a3f5438779b157e5efcae6cef9db21cbac5607cd08882844cf981febd", + "189": "9b2a65ac4c35f6b392501dee2a28221a3975aac5f0866b476f5e6a5a59f3fcc2", + "190": "606fe2cb6525dabfcdab93afb594dbc8399cb967fc05f0ca93f6084d3f8fb591", + "191": "ea1977e7b22df383de61bded2a4bb3064cf17fcc0170be452330256f938b8d55", + "192": "91d614f139082168d730003f04b87135c64e13709ced2a96001ed60796867825", + "193": "65648f18a50a7f9195fe56bb8cb9e25421c6d777ad2447a3b566dc8c54f3399a", + "194": "cdd31847c6138853597261756d5e795884566220a9361217daa5ba7f87560404", + "195": "d12224510de6c98076f6289cbe342a7ec7ea3c5453f6e3cf8d37d9eea74bd07e", + "196": "1349b472d2821dff215e54d683dbfca49f0b490ade6a30b1db9667bc94e5312d", + "197": "e2aa8f7cb3ba893af8bddbffa6240e7eb71a4f4c4498f2a360f3db7b513094df", + "198": "a29d9edd0dceca9a72d2238a50dbb728846cd06594baec35a1b2c053faeab93d", + "199": "50a6b9725ef854537a554584ca74612a4d37d0ec35d85d04812c3ae730a4c8cc", + "200": "5b439098a3081d99496a7b1b2df6500ca5f66c2f170c709a57b27c6be717538a", + "201": "b4e86186652a11df0b9ec8f601c68b4823ae0bafd96357051371fde5d11a25ed", + "202": "057243f52fd25fa90a16219d945950ed5523ddb7eb6f2f361b33f8b85af25930", + "203": "2742f7af8ce9e20185e940bb4e27afc5fefe8cd7d01d7d8e16c7a5aaf3ad47aa", + "204": "15f5e9ae4636a6bf8bdd52f582774b9696b485671f4a64ab8c717166dc085205", + "205": "e03c2f4ceabf677ec502d235064a62271ce2ee91132b33f57382c4150c295784", + "206": "16bb96da8f20d738bbd735404ea148818ef5942d4d1bc4c707171f9e5e476b1e", + "207": "133fea765d0b055894d8aba573f47891c1f7f715f53edeefb200fbda758a1884", + "208": "90831cd89b4cceacaf099c9bae2452132cfa2f2b5553c651ef4948460e53d1f3", + "209": "570fab1574a3fd9aca6404083dec1c150e858e137692ee0c8011e702ec3e902f", + "210": "ae9a76ce3418c06d0eac3375a82744fb4250a2f380e723c404334d8572faead0", + "211": "aa4b2bc3a136b27bf10a858ac6a8d48f41e40f769182c444df89c5b9f0ed84e5", + "212": "81489bf56605b69cc48f0bce22615d4415b2eea882a11a33e1b317c4facba6eb", + "213": "a497e789f49b77d647de0e80cd2699acd3d384cc29c534d6609c700968124d14", + "214": "409520c6a94de382003db04a3dfee85a6dbb71324f8bd033e566e510ad47e747", + "215": "0eccb27846f417380a12dfd588a353e151b328425ecf3794c9cf7b7eec1a1110", + "216": "f735b4b441635ecded989bdc2862e35c75f5179d118d0533ae827a84ed29e81b", + "217": "9aa88ac109aefaa7ce79c7b085495863a70679058b106a5deb76b2772a531faa", + "218": "5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9", + "219": "9da1307fd12f4c9a21a34e612920cec286d937418a2d5040676408ba0c47f3d8", + "220": "a262318d02a14747ed2137c701f94248bf8651a23d1f82826952e66c25029588", + "221": "bfb4e53578fa42f83eda027da0467a351298dd65e3e8e84a987d69fc275e9f2d", + "222": "4308f4374b84e657aa7a21e5f5fe42ed16386b6dc7a74bff0d24d08ad62acd26", + "223": "3790f82f65ce7bc071b4096ca22544548b3413a755f58bfc401eff3ddf487a86", + "224": "96356c050fa66d919c75212d789544db81b012bbaf1f7915b053cb9ba2d67de7", + "225": "f37f3f2b0dc57a86dee4ba6ff855283bb4d2f0dea1c5bd1b708853444c2ffcec", + "226": "49bd28d97a79875007a6713aa9700df14464217c28a6e76bc93ded57b75a33f5", + "227": "b1f73471a3e6ea1dfb04610bd89ccb110994780084280fae872d42a2786f9131", + "228": "e38da154f6cccd06cd0001924ec2dad8de5bdcd0b78b68e8d8347768d99ac0bd", + "229": "098ffc6baaa32734053275ce38f4bbe58efe0ff946bf31e0e2df4c6a169e23d8", + "230": "2c72b887a8638941b85765645b45c5cdb73255427e14d5114f6830f847a6b861", + "231": "4aa0c92e77eeed08994650ac6129d77db9f026ae2aee78ad5c9fde132fac0505", + "232": "5f7905b71cb897bc7cc6db7e29cc38ee459e2fd8f5d58ba4746d3acd4e46d444", + "233": "8d986e287ad21475728b0dbd9e935623d69db4e5fdca0d42bc32d70eda48985b", + "234": "2d9d03b778af897e305caa8a1a14a117737bbdd549003c6d7477dd3be8331694", + "235": "7168cff545d365b09e8997bb9450343c7090913876c8f7eb9f0e9849c6fc7dd5", + "236": "ceb3002bad36c22c5da82fd422b36bad91b97a7d3f5409ed5d16aa9b42dc137a", + "237": "c857d8fa78c8fde91f29b3fbe332c2e781f7e8b54532f4c352693d6676fda2a8", + "238": "3e2edae8b8ddbcfaecd5aa6c69cb5960b84cc16f7b3232f3386aae9ecbd23f20", + "239": "49df3a63ca6509687cabb3d208e92b057630231e66b87fe8c781baabb12a55f8", + "240": "5034a21557b2de1c5c2d2aadfe8ffe62162c23f42d1aaabc705ed8519e91a3c1", + "241": "85abbe1913df41c57d1e6f00cecea416edb19c64681d1bb43fb5024e2f48d409", + "242": "4da30e6198a3d9ae6a538c2366e08ee218de6efe2c5b8f231493e21489c21a7e", + "243": "7404bb7881a010271831847e71162ee7a393843922ee93cf7cf3455a0074279c", + "244": "21aa3213adeb0a562ec7161e1cfcb5f1701303f1c9f03ed726c536283e080db6", + "245": "22b9cfa9ab97c84eb64e3478a43acd4d95b90cae8c3453c968457a89c6387a81", + "246": "729e3de7687fc597be2eb4c592d697ff29c78cff6945c9690cfb1ee67550eeed", + "247": "f49b98df95a1f826c24cf622ba4d80427a0e0767dffcc28a9206c58508863cca", + "248": "44b8116c29dafbdfa984b3751c1dfd057b3e93fc57c8cd18495f1c0f605496bc", + "249": "49e96b6ba41e88901dbd118139ef6f013b4fc59e2347058a7e726cf6a2f14067", + "250": "f0e0dc05fb555ae5ba9820586bef3bb8a3a82905ece3d8a998a3015fc91c1c3e", + "251": "8c1ece1b350c210380456da2bab70054f717731b0dfb34bc3cf4abfacf696f15", + "252": "ad20a49374f9176bd26460d35f96f30d734df3cf6fc63b4642380b4e866848de", + "253": "ba1a2bbccabbcddbf29ee0b56d0d56b4f026e8a7b97e97de2d48c133ccbdf2a1", + "254": "381a2eac64a984a81671722bd95ca5b8b6508a6f490af46780e9f393c6797223", + "255": "5e6ece13372bad4a6ea011c933389dfaefedad5860aefba2ab97fe5f59156c42", + "256": "068d4a3c845803bf66a9e5320261a0fd7b6292a8230b271a6a83f0dc8c73e907", + "257": "d80ac9215ffa7adacb22711cc88f5b580272d0d65c49e1ea48e69d17e264d91a", + "258": "256c4d399703b7f16dadef9201efc0ef9f6aa6ee05ddfa2d3e26ff6efe09704d", + "259": "275a4e84039a1596ac7e8bbe186163dcfb02bfa99c209653ff5d505a29b4cb10", + "260": "f461ff2df66653be1a2e579b1aea515d4a84f2ae5ebea3aa21fb2477a53699f4", + "261": "178ecd56cd79c7aaec1353093571ce89845130991d64c5a715a02da83a2705ab", + "262": "2e0cb5e8fc8ef04c50a5b9ab9a9eecad446598ebc2527b19c447143e5ae09736", + "263": "c870fd75ed0d5ed92ec35789c522d029f364328a16282a1c5eb9b3b7e121eff3", + "264": "da5d6bdd89eacf70a88810935f80e4725da4feaf2aa86adb13985d7d9e1c247f", + "265": "13f16351c3971c286fae5e9cfbaf6f0a128a6507804fd280971a600019e352e8", + "266": "4f39cdd293598de9259231592e99bfc5fde82a0bc1820a4c5faeb54f96037f00", + "267": "3e054d92034d3d32c3d4e7acadf1c09232e468fc2520d23d2c7d183ec0735aa3", + "268": "2d47c47a2b19178cef9e4eba1a53dd39b5f8657bbe011a71c8d402d294d50132", + "269": "4448f310ab9bff796ca70c7b7d0cd3b9c517f72744a8615112f65ba30a6d61f7", + "270": "ce71f5bd1db540762e4bc6c4798d8b7f3d2b7068e91c300fd271a46298aea2aa", + "271": "5a05e212b9b6ccf6092081f567aa73d27da399d45418f674628a8154f9182b6b", + "272": "a326c2d7121d80861aaf110826615e560aa4efdec0cc9fdfce051c6b9038e781", + "273": "d32b75411f407c5da6a9a6b4a3907b9a9ebbca6b651324c03432d549671bb837", + "274": "b5740ac928d58f53537b05ecc80b7463dc1fd5a53400f57aa55573ecbd5faa56", + "275": "e1c843ff0e97692a180e384c1a9c03c7de06ef92ccad5aa6157fabf0dbe5b804", + "276": "2edf523574e0a062cacf21f51ed6f81128537f27a3cd27b84a8b5d2478d0092d", + "277": "130c990ad499345b7638e57dce365442e2ab2d2571546aae60a9fa6ed3834b8d", + "278": "2204d89df74e664621dfe8892277d50e7774f60613561d70ae06ee0eb4c483d4", + "279": "4618456c7239784964b8fcd27155e01cf5417a16cdca7b163cc885d598ba93f4", + "280": "4b2d9501483d450371ec4413519b0b3461502aabb970fb2b07766d0a3d3a3f85", + "281": "b04a4a02fa0ae20b657dcfe3f80ef84fd446daa1521aabae006b61bb8fa5a7da", + "282": "6dab2ee10b0dc8db525aeaa2f000f3bd35580ba91e71fe92bcd120ad83cf82c5", + "283": "c964c01082a258f4c6bb66a983615686cb4ae363f4d25bd0bdad14cd628cfce8", + "284": "df960dabff27b2404555d6b83aed7a58ef9a887104d85b6d5296f1c379b28494", + "285": "087de77e5f379e7733505f196e483390596050c75dad56a999b1079ea89246ed", + "286": "8f3e5fda508a37403238471d09494dde8c206beadfa0a71381bd8c6ac93abaf4", + "287": "5d834d4c0ca68d0dca107ffe9dbaddac7fc038b0ad6ccc7ba3cfb53920236103", + "288": "20a3ef9e411065c7265deff5e9b4d398cab6f71faa134353ccea35d2b279af04", + "289": "9dda7eb623939f599551ad1d39dbf405211352ae4e65ddd87fe3e31899ca571b", + "290": "a629c35ad526f4a6c0bb42f35f3e3fa1077c34e1898eac658775072730c40d6b", + "291": "81b1e5196bec98afe72f4412cf907a2816515bad0680bd4062f2b2c715643088", + "292": "614950a1cff05f4cf403f55393ed9d7807febbae49522ef83b97e0390038ae03", + "293": "9e4067ac93c6febda554d724d453d78bf3e28a7742cdec57ee47c5c706fbe940", + "294": "9ac900bf0fbb4c3c7e26986ac33698c46c6c3e8102ab75b40b8df94fc3a0c7a1", + "295": "2fdcd631f3c68bef3c90f8679b7aef685fa33f20c2d6eb5965cd2a62267c2ffa", + "296": "dfc947e61ea2138ebe47234ba503cf5246ecec530b12e363acb240822ddf0b34", + "297": "4d5af88ba8a28b49a79773d3e6521e8731ff07d49765203b157481469e6ae6d0", + "298": "94aa77eadafaad7327acb8e653888f00e114cca2fbe04691dabdafa2a0c8cd64", + "299": "0f221ba58a8ea417e13847ef91af9ff029561ac18c74bbeeb3f5509af81a3b03", + "300": "50a79fb6e417fb4a34e155a9af683aa9a74ee922a6c156a58bfedd22cf3185c4", + "301": "eb09a0097a47e7a95b892ad7230475a1a28343b47db4faeb3e47f227aeb04738", + "302": "fcf9736fe8c20a6d02f00e9b1e719de06aff4afa99d2eba166592aeff1b8f3b7", + "303": "e6266f575c94d805a67fcd3f2391d0961b4b121b8a66afbfbae76dfc34e5c06b", + "304": "189bd2a8daf5b638ede7c50035fcf426d125de87a401382f66ab75f35b2ac1f7", + "305": "0ac58c6eb8513f4ffe911bf0f044e0153862ee89c04939fd9b294860a37ec9ce", + "306": "335998d7e2a3fae2da75a5192d62c37dd006be96831fd37e7438ec6d84451c44", + "307": "4f1f2695b1b6b1660f3ef6ac31a81630ca74da9368eafbfb214ec1980705c13c", + "308": "bc5ae127f8690ba7f6e9ddad98a49137acb45abf4e187eaf3648f197c72fbe90", + "309": "6b78ed4c4bfc942b9b5dc340961f19c690d56f9d721b6b764d1db31da53139db", + "310": "0d183ec2ff1cbc768a9eb7eb06c2a354f6c8bab00e64ca2aed2da00825f33c05", + "311": "3ae7fdad095eed78e0c63cfe4e28ab7ba2b977259590ed38722e9c44727e598b", + "312": "329d107b5743a96e3551084832587a6346e410aa89641d83f03f1242a7244473", + "313": "ecc63ee12cbe487e5390007271890b8aa5df2cf48b8e692404895d3c2af20758", + "314": "5fa65495795c52818aea910c24e4d3176c71817f5268c34e1cb259b256737352", + "315": "95bd03b9913be37d24809d30e7bfd31a1b7a127d03d65e52086857bb3a364b5d", + "316": "ca6ec6c9159e10719cd8d2cfcfaf2fe2d3637fb3d497e2c80866de6b593632e6", + "317": "5b0d72d34b406ce20714a59f1c4d5340c5559285e340497dbcad68729a9db786", + "318": "3e2b479fafb86b8ab097588b8fa12ae8a078f8b5801e15c7faa1ef23d87a631b", + "319": "e04b18947b36771937dea491f47b75fedf42a6db684035f5690e6c2bd7e6031e", + "320": "e546e4a4c9020669c78a095aa5c5038242dd78e0f98517c0e23c43aefeb58138", + "321": "3da0198df2f98a7306ee6d2e12b96ba9a6ad837a6c2d4f316d3cd8589b6af308", + "322": "07e511e9002147c33739c924c17a61126d12823d143069535a615a97f86d936f", + "323": "be514911dd6258f860c2773253f6df6c22ca975a10c4e34db5903269f2975faf", + "324": "53ed94369b59a84d003ff3155edbf481a0eef362325539d6ab1a7f370ce919c8", + "325": "43c8dc1907d3e1eb30deb565475ec1ad4f807baf6ef34178508ec85071722f0a", + "326": "b08d72606988ea5a82e0caf15e68d81b4f2e8dbb4af6a22437916f3fc53e3dea", + "327": "f70bb9cb351daf610a91a3c769d84bbb3f3b8f1169b10839196b65b8585e7c38", + "328": "6e26ed661a0add2e583229066d304f7e765a0ea337b6a93bf979e4027b70b94e", + "329": "89d8b56a1e05d90ccde0df482ff2fec3d44270739810f3c5d06856c38d801380", + "330": "2dfac8e04d08dc5eefcbba4e475164103d339f844896a75ef3af2229185118f9", + "331": "a20f9b06c126f4ee65e3f3a0bf345007b35ecb69d035dd0ad848e09300130fcb", + "332": "6593d40f4e3f53a73191c704d388c7cd1639403da6e679c8e4169b26ade19f3f", + "333": "7499bc84f6bd2211365fec34943d64f6be80a53ee2efb21c099c1c910ca29967", + "334": "f24dd99fe5b46bb7a7a30c5eff61e71cab21e05f1b03132d7da9c943f65713f6", + "335": "8e2111c24160d92b1b29dd010b8b3a0a4f9af55f1d30bd5892756c58ffaec201", + "336": "eed2e8d970c1c5031220476e6b700d16e5065d7893a2766a53600825b4ad3ae5", + "337": "44e298d1b55c51c9f127989da1149ccf6bda24c40041f777d35d5b8f192753d2", + "338": "b3a60e80296f79cfdfc02354acc674162faefcb3fb78b9672254c9cfc6eb113f", + "339": "2b55688ba27d72202632783186211ee24ea39c53915066578291fffd9db73128", + "340": "15765221271275022a6ef57634d836b052ffbab6d7d5a6899992972143841e3c", + "341": "f7340563f85e057709a2fcc71bd448fed8d6de6907d8ba5f91fefa2abffda6cf", + "342": "f252eec230c2e92ed1fa04834bc0738b79597c3b0d2a66c787fdd520e63cb3d3", + "343": "1d65c53a04f7eea94ebf76d797c0f79fe3d251bd33e5edc16c780715531b4345", + "344": "86d3fb095439bddbc0d6e6e8e433d54aff04350e2da2ad05f53d607113075c8b", + "345": "21db551743591f9cd20fffcedf3bda17f9f178bc9fbca528a56c2c61b9e7c731", + "346": "f326e2241b7e57320914aa279f9ba2e155ea77f809a188958e0b590bea9c3ada", + "347": "0fb6749b98280cc8c26950a2cb9c9dbecac18f8760e161e9bab887dcb0077653", + "348": "0cdb77330ae73fbbd0f287240f82b7547a0ef42d37004003a9c759f86b686d61", + "349": "690ad38e4357b34368966b9de08d89e0c095246bf55969842f373f1976f86062", + "350": "5b427d47f98e296cb78875619fe67d42f41868b78886d560d8fcac89043fe945", + "351": "93dcda27a0c12f0c32cc35f0de161e7f7792d11abe5d4c50d7fd5192ab8b11c0", + "352": "d01c0cd49e7649289a1f13162757de494bb9104b20ac8bdb30a4180df5225889", + "353": "3d856f38821d7b221aaaa9baa3d7927f6e360919e8f8505d7499f9bbd85c44b8", + "354": "36dd3030dec4a8050d2079678250c9c6c86c66c64fdbe7f5b82e79024bb8d5a7", + "355": "b0a915b700e415ba3acc3ef261128680b921b5df9bd6fb1d35c2d1180e7f61d7", + "356": "a309814f13708f2eb5ee8dd1a3114e8f8b15646b8c797bc7119ceaa3f6911f0e", + "357": "61c9c81a41fd294a8f07033c8373706694faab4df3652d310e84904356cf5e6c", + "358": "7d59500b8883d81040173b88462a73849e0d386a53830d599e6a042f4c1c165f", + "359": "0793805920db4896155cbce40fb58570a3cc952d0c15ee57393fa3c6ca7a8222", + "360": "ee8cacd40fb7515e510cbbe7deb6005369ce7d9800ecff897f3fd8721fd6ef71", + "361": "e96f225fa470174b4ac787b21579ad1556804de85c0c83da99a92ddc2c56c7ac", + "362": "9a4ce079c1a882a306e21e0c145dab75a2698cba3860152f03dafc802ad9006e", + "363": "258a6e6ea10385ca3c0cf08377d13ef31135bd9479d5a4983beadf158e19ccc6", + "364": "13aefde214541fab44d2a3013c532637a3da82199fb6c0a1a941c3108f76b9cf", + "365": "0cd978902035027c6898d6b5fc11fb5931f06f8e8ec9c24b4706143c91de9450", + "366": "47495a92574a6d7b150eb3f4338748ba03672ff93162139f98e03847f96551cb", + "367": "fad9203cd26fccb99f0f89fdc569c230eda46cd72ed3fb7e5f6fbcce78ced1a9", + "368": "a237e13fa6c32b66695b8c8de6472d5c93c7650989f047f62a17438c07036845", + "369": "da4c450ba0c4f76556fce54bc3f6b2a754a626cf1f87ba3280f545e023942640", + "370": "5000899cd3070e1937d42a68766c840bdb9629a49c6112bea5cff52fdb4e9f7a", + "371": "7afb55ee21c0447f7b961265abe7ccf87f59af6206949bb1da19fd36334b09df", + "372": "fcf734716ed1fa724e7228a489304e3c55e813734fb5792a83f806ab04e40485", + "373": "83c98f0431cf944440dfe0a9831275ed451b0d16856aba4100f53170c55c2e6c", + "374": "d998ea6616a5a7a9f7beb3ec02f8cbed4a9c5f17be978c31f32ac0f9f4e4460d", + "375": "6a72aba5c61e27e281235b1f001ab68b840f6e8bef0e6bbd7bfd8eec1abf844e", + "376": "980dce9435a9fc03250df4e809c2f68c48601b64c30d32c6e67bf1faa35fe274", + "377": "7b4a0b6958cf23951636b5e27af8041dd9901256c53de44c9be313ffd0a01ea0", + "378": "a1b13bda78da3ccab1af6c330d3e768fce62841f924933285e7b1f7a8b7dcd5f", + "379": "c957fcbb90e1afe9a342e95608ca035596a7dfd4cef398ada55e05a2462aba14", + "380": "b794fae83475a77832f46e69799419f9881bd774e1bfda56773b587c42591039", + "381": "e7208f3630a20b01a5e1bf5d0537be9dae9fd7529773cac12b96c4ac2b0f8dbf", + "382": "70480c0d26a6d76eba0faf3ee047d6214b2ca4d1442070ae5e79893192ffa699", + "383": "3c814d251089cb2a92a78ec3424b2a729cfbbfc6a996fd48148261312364a9a8", + "384": "f709015ae0f8ad20bd2efd94d01af3858234e381942b5b15391ff7f77211b116", + "385": "0bca6cad1f4ff336b93c9f86c4ac872bda67ee0cd41b1862a7a663852717535d", + "386": "3e1748647b60bbf292aacae65b3608ccce8e55e203a36ff062ee787cd8c14480", + "387": "cf592fa81780e727a56553df32410beba6de9c33543dd1ef1155b368ba9a9b9f", + "388": "911326fcfb0638154f69eabb87e4c0c141df09e56274c8522e9c13b7b577f00f", + "389": "cdd56fb06838a10149f2c7229bbc76f78b4a5a58945fb70a47261f1bf635c404", + "390": "07dde4848eb878808635fb7b366261b1e9cb158635e76577eecc48ccf941323f", + "391": "76cd3def1eea8e2631d333798f4d282bf40f6254b2d18c02c78cb56b33462093", + "392": "c4f7ecf21a8738c3ad0114a1ee6a2d16668e71b499741381f30827ed451dc817", + "393": "7bbc419f89fde57d2862bfb3678ddab96614693dfca109d0f444e4762a2b7a8f", + "394": "7781ca3332d6da18b1b9be5e2eff634b526ae9e8088f6e479b49d657f4f41525", + "395": "5b5de0def2c4a989a54ae3e748362c78cd018778d5adc4dec13c1bde6ffdc139", + "396": "d42c389d6abc7d8102b8cd1b906e4600da08394388d4dcd432ec955e6d8b311d", + "397": "629e23dc358ed2a8c202e1b870e270e401aecc5d726a679b542df8e6becb4200", + "398": "c30114e73097c3fa4efb203915f3b828b1b8c432ddeab2b7e1ba3fe63c50e190", + "399": "a681ef7bdb22145a3e051ecf7bfb694c18b255c80dae6fb8d49f187d28f3c58f", + "400": "c993a792804e09c9f60313f4144953eec072ca6a8a27f44d8718ce53d9429585", + "401": "074b576ae2054cd030ffcfa132b1465f8f49b836f505cd4bb01af4a98f4f5337", + "402": "d45f88fc3c00673ef7e628d867a54a4ea281b3b2620735cea85a8da3b06321df", + "403": "a09086d3cdab7d6ff8a9fba1746c5d236e0ad0abe088be99bb172e80c6f0f8f3", + "404": "55a774ac3423440dda50d73e472887195940d5e9df605b30deeb0f2528b001a4", + "405": "ee9fa61ae8153df7979be3afe6377e584fbdad624833424a5cff64f6ea94c9da", + "406": "584cba4abd5711b8f558fde97620b8ff0fe91586bad052ccff87c49c13f72555", + "407": "ac50b37409f7ea91f90856bbfa716731013deffb5f5b51540a99736e08e5378e", + "408": "2c12c3cf062c3d9cf2c53e6e4dafce70ca5c7a38c97479c3b013cd91076ecf4a", + "409": "5a55b5fb584c359f4b6ee2d21deb62923b0b25e1b4c3da0a6f351079ce657173", + "410": "9e224b6ab0b7f20759b63d1799b426a8652c9e637b1f38d3eaf8beff73c80c67", + "411": "66c0c1ab79e9887b5daf2c510f2c2c4097044b69fee6bd4ffcff73ad4816b8c7", + "412": "27f1768d99e22f8b55d010b8b7acd904e8b66751d5310d32c4d017a0ad34d650", + "413": "7c99634a1161e424a14d60b516291655096eb90ed055326325d7f5de7a44a3e7", + "414": "4e03e038e99870b1faf45a0a29d6124379d05a0a3553a11aaaa91b8ba56eac5f", + "415": "955e433ea745016af2a5df015f1cc223ddd84ddccaee60d5302b7ad61542d9e1", + "416": "8d07a87b9012a166f5bec4dcd646d5957c9b3633a1a37c40c584ede75cb7ad22", + "417": "3f738338cef45597e3b839536953104186f11d94d16877c77abd8a067c152dc3", + "418": "0c813356b30108f89fb37e8774a98af4f9eca3df49e963f985ecea82a88b1437", + "419": "8ea8d93a9e874f8c8ceeb240f1f1245a077a7c0a62287d3044feaf855b5dae78", + "420": "af7ac1e90e07f189afbb284ae24614d9e713e32098bc39bb81d6484d47351444", + "421": "f45e155846624f37cf2ee08be2a63cb1ca35bf795fb0f770b4c91ab549f22b25", + "422": "69d728f7e25055dbebd41684bc6de61be6b4db4119d7ecdcef5b5d8ead976537", + "423": "3e78c62395be704a59a3a6a65e457725105619e0a6f9f3aa6b311c4f7762b0a0", + "424": "fbd6edb36c3754a35e7de936839c4fd0564db873924ba97b35cd43e065835582", + "425": "ee5bb631b2a9edf8ed05781b192f42e24ae748f3aa4ba5e635374c094d28ddac", + "426": "3e913e088a689d2d33bc797040cea94512bf54a61f96501f60576ab22ed0304b", + "427": "415e6da4c7f92da36e2d8c43fa8056d0050ae127e648451e2fada49bf2c936d1", + "428": "389bded7b0c14212fb69b559fd1ade4f5b235b976c9655365c45481c3afda486", + "429": "3007beefa50c509b89b86c54f53757ff701f795dc5f7ed47a1520c2b092455f7", + "430": "59ec8ec2866ca502ad558ade9f8a06a9ff815a1ed649bd1cb513f417f1d4727c", + "431": "d3f28dffa4e22b3bed74c3c2c9ded1e4a8be49d3757368e4e3efaf7f79affb15", + "432": "59fd80dbc8eb4af9596e4ce8a87313d363da41313351a69ab3525faeb905c27e", + "433": "471a7ddde597fbaaaed1941f42ca1fc0f4f047e17f2197f8999dea98b38213f3", + "434": "319cb430c66d9f418aa90a3d6f9c2dfc8171383d6f4af5803a73684afcf18e15", + "435": "aa29c0119ca84133617c8bc7455afdfcf5b05a569393ff21ebcb10d32ffde2c8", + "436": "928f772ad7a9fc501f71cdef6dfe60e2d8cb5d5c5800b519d01afeae0681dd08", + "437": "ea70162a014b8294ede65af6fcdc11fb365ab2b126aef8d47983d58816fd6a54", + "438": "43633662392854b5d9f9f0fa564605212d016c9ea9377d2a6ab52137238d4191", + "439": "42f7e88fab5c9cb31d4bb34403d7958abd5023e9cf9ac05cd29626c5df763584", + "440": "cd08ef4f14b804e3106ee88f9d2b24864d5e2fec6c7cd7dddfa2713e1431375a", + "441": "daa69bac44ce5f57b4b43ab6ece3b2b3561292c0f4c6e82a506ce2973713f749", + "442": "910d2abf184cfd7b1964cec906a79f3e45f59e3d42ec20b22f56de59c9018927", + "443": "7a14ac86724d318e6d40464e710c17625d441d1e7adf83d3062305de2f85d445", + "444": "390877dded07897360921e8d0d126bf45d6a379d47292c90826d775bd1897f2f", + "445": "5ee5723341b0b81c9e0172fcb654f8b24322244bc2d1b55afcb78b180ada180b", + "446": "8b2dcb0168e8701dc9da286489a1e68e43e1b17638e5990edd882196d7fd5a29", + "447": "179af1c75faa5f42e89ce3b41496a64b2d2846361f76dd5d87f4ce97ec2bec07", + "448": "18173b14e0c0bf403b5f0d4aa23515ecf44622b3a860d80e866cd498f107123c", + "449": "22d7739bccf54ea1159ce6aca3e215482deba85a4db0676cf86d82a760c44a6c", + "450": "938bf7cdedab94bd7208b69047014e3d9ab7b54d1223bd649eb3de0bd61ab47e", + "451": "abd88e378f54b649e818d6e1d8e06c9f8cf225ac96b4085523acbb1f0c1df24b", + "452": "4119701c51dd8c457b74a19ed7ae3bdf069f5fd915c8085e9a15d909a39db036", + "453": "381ba093e8ece9e14efc965ee94bb8adbd1c0bf140875ef95f8f11050d5ed489", + "454": "b7613128b0401fdbc07a4015eb3935f6677b84dff936fc5e7e9f424d0ba1006e", + "455": "35ee11c9763f48a68f2b60b4b9c3919d3a895afc7071e8dcac5abd5845dfe79f", + "456": "8b129a3c7163dae86f1f43c18557296240a02bdac70ad29538eb5dce00e51f4d", + "457": "629c99f9af0e962f00b812057c0967861a9b6db9dd652233ac4b37f368d09206", + "458": "02df8a1d11130bde8af932dfc5cafe7d8e6c2fc12b82df5d222a91e4eed8e2f8", + "459": "062b225facc7a897e0e42e6b0f95deeb8b02de64267bf5cea4cb5280ccec1562", + "460": "a05f9a7cb049c40760ea2196eb41df1826ad492e6e5fc4696ce7bfcf7a842811", + "461": "95e5e99da04c0cd73e1818a62be3fc0de98c76d5cbdc81261672824ed5b8c1a7", + "462": "69eafed1b3d4022fc245a8416c1120bdcd039716db8cd43351a96e6c7d10691d", + "463": "018efbd353bb456112cf2c760b4d96aef02aa899ef74d4aadfb3dcf374a22987", + "464": "cd4447e836cdbed7f6a3998b50c4ab467aedaeb8e54c377da34245e90fddbe12", + "465": "da0612471988c89ea2fb190838f9f5e9029fd106330a801e66280c967ff1c52b", + "466": "8d16100c0148ed7bd41003b4a0612cbc5fa150ddabe5f9916ed6eac3fcfdefa4", + "467": "d6ea164cb91d14d6aba2d482926cb6cbd1a3644737a0530abac635083a97b8a4", + "468": "8d0e3f6bff322ff11d1267f1f8303a8ce1e2d796b7dc2d9eb3e3da939dd850b5", + "469": "35e2072f22c7cb980fbe797e30c25e9224328813eb81d07d3c88820492ce9a1f", + "470": "4993f275946ae0d444410821faa3ef4a448f10888c50ff59f7ae01d0b50328d9", + "471": "b9af9323a0237fbf88fdb14b8bce95c084351325249629ffd4fbb32fe9d6da5d", + "472": "5b278c08ab97d82c1779411fb1018b07feac7ddf38a69e4d398240a495c54271", + "473": "4448b03417a784f554c44eb15ad2d4cc022bd9cb5abe2547811eb8085355aaaa", + "474": "1c64fc4076d6b00aff86a180fd9af927b7c1c9ba87a2ca3c83dd80ba5e5ea973", + "475": "e571b4b8218a2961ed2b04f62f816eb18686d82b7f2693694b9c774acef4a0ff", + "476": "a6383ed918d7851ed7503921a64201a032a33c9e1cbd4e08d1233f543bd21be9", + "477": "c871da03e684e099190c4ce787a9588ae85841246ad7bcc9cb4c302d617f881d", + "478": "96d8bec6b787a7aea2da8dfa8a1226e00881afc218c211fc59da830775d55acb", + "479": "b35720df96afbd98c6a4f081ae1173fdce21d63f75f7b455f4c2b9fc0aa672c2", + "480": "2db876e9625c8638c66103ad0206c9a51b68d4c6a3222f403b195a81837856e3", + "481": "bac35824e79af403a2058b08cbc84f8e4df93a21d1766e4ea1de6414e2a8a926", + "482": "0f9797e2f3691bc7291d81d1ddd5d88cb4e10b0be555e2ebfbd3c5b12b7cd2b2", + "483": "8f3348df383ec9ee00e18d41c419370d42ca6ebf71c510690aa5435a679b7e4f", + "484": "3b3bac32669c5b66faaa42b89a2dcb4de0bb9aa0bd279d60061dbe9e7039f5dc", + "485": "25d0335a0576f974617351ef5aec889f311fc8d7cddb997862b10b2496842d4d", + "486": "93b9a59a937594d2196271416ea3b2221d32b3b40a04bbebbdf97e8bdc557e0a", + "487": "a643c75a8d062b87a1c8635fdf439c04d949ce01f75dde10ab6edba90cbaee77", + "488": "984593c12abbff5d009091cd3c1883c87efc535f760727ed12f06df0902bfa75", + "489": "926ac61244f94e10270a2d40169de025be6db342b3de7f0db33a50b07176c143", + "490": "e2c8142e501b0b0b808d2d36f5f38266f99cd3aaca7d2f70f4bba386ae1d2025", + "491": "1a1c8b472424f8057c94a9f5e0c0b673551fbe9ea4cde5ca2d90df1de76a5c76", + "492": "345a83966ead821efa2a9de93aeb0fd5bd60a8f50e162caae2447f1f4d9462bd", + "493": "ee7018d63b08bc7226d6f77c2345a87e09fc7cc87b0a003aaf3a4a3f622edffd", + "494": "3d69e540997d79f21f249d4d8f73cd75119d81bcfb8bd80782863249f0d7c62c", + "495": "b717f1088b0ce24851c30d54bc8dad9f3ae93402b91c874e385e5c699323a5e2", + "496": "fbe77ec1978ad86e73e5a3f494fa7c198fe334b511298f5a0f2d04d6a7f51d01", + "497": "a4a66d6c7c555a2997ca59a8dbab512388adf20902293a5617132a16df76d954", + "498": "d71813b8175fa2d70181d87ae8f839e79792516a1cfa99a7e6b29500c057617f", + "499": "477d5b817df8c0b6f0928d02a58fc39fde2224493cec89393bd6dc349e5235bf", + "500": "3ac8e26d4864c538936efa7c5920435107a50c01306adaee5a4aeaa2ef378f7d", + "501": "766448b05b248ac3d6e991baa3e4b2d53b02aac426bda312c2299b2b983e145e", + "502": "50218b55f5b7207438137f2b0c71e3f6d37afd76aa5b1f2106111f3432b4cef8", + "503": "1d7c24799a287d42e97dd4ccc5bbd3713ce139e6294896cc5fe2efb80a1be7ad", + "504": "9878db5eb2218b18568dc8cfa13bc8363a1c93e6a59a05cc76da0588fd54af46", + "505": "872fc20275833f09c8aaef277abfe77f67be6bd443b489e0cb8bdf9d4ca9fac7", + "506": "d66834cc7ebe58cce2ee1c02bb11ae69672d711ead6a0a58ab592339cddbf02e", + "507": "ae955394665befbbc89e2ba85b5e520cb293b8d03209b1f71d78ce2cc807a437", + "508": "3917ce4173af47bfaf8525f0917736bde3f4bee0ed5fae721c3e2fa957ab1675", + "509": "2f64571cd71f0e59006da84808abf3d3ccff9a38884321533d448b3e8e3cae05", + "510": "41ce72f4701e786427413b68fb70bd77d921c06648ca15033ce1926a9f1224cb", + "511": "c9fc787389265492e60d5503f279714d5b19760ea7b2e1a720e6fc0251fe087c", + "512": "a8af6acf3744af13cde63540e37bb9bc722ea19a012656e3a3c5bfff8292c423", + "513": "506b90816555d1083be7d211f02a5db364e5c2337fc85b1ba845c1a806689373", + "514": "e4e9536766181eda627721723bfbdbca85859a3ba92d439f58ac0009c102430c", + "515": "16daaa62fa87776bc4843d226988cc83ee846ceef7b885ab63e10789b30071ae", + "516": "44b6de4eb51dd8f762142f284b154d3153592549cdea3b94467fa95484a4f172", + "517": "bb72c6d437197a8c1f1132626b3b47adb9827f4f9b912d1069cfcc75575371b5", + "518": "ff57c1f518651af805bb4b258130c7c5b0726422c3390327217562088785b4ba", + "519": "159b59f1261b7a31d7172cdc28d9515d0731e5117cb30f34a497bc3bd0496da2", + "520": "bdfb7f17c8c841c0b61ee7f00e51f09e4c78c90f7977548b72050a7aa12dfa3f", + "521": "bd27ca9292c19160cbb0568f750b247fbb805b85f4a2316fcf2c3a35d3ae031d", + "522": "98e0ef155297aac8a4060d204614753f26f6ba5357deb78c683783dc7ae30191", + "523": "9bfc344c80d1200fe12bea3ba4cacf8d5ac9693258962f2f15f42b30ce8ef3ef", + "524": "8df22d8716d7ca6354ea42b8e522d286ff9362cfa5881f527efcf1a953ed1151", + "525": "13dc6d869fbe2c3d95f715e55f02bc3d5787874b4c88d7da1d05360afd2025fa", + "526": "dfbe442040ce9afba654773fb14f307d67ab614267d3feb6b18df03182b5b60f", + "527": "ba634833af68fcf0ca7bcb08fa699b2c5fab934eb81ecd85e7464b01bca131ca", + "528": "016f7b569dc1c3466c97754c7dcc0f76c2a32a76c22357cc521bcc330d86daf5", + "529": "4960ff863e3d21a58f9e81c3d94075cb7a4daea5fcf396812382111e462fc57f", + "530": "6a2e45fdfcad65e0ee84d206d59cbac998026d7415d16a5c0b8c55e4a7d6bb3f", + "531": "95ec72fa8c409255d43e7c8d4e957bcb9239534973187b3b4cc2557b09bdba98", + "532": "fee6802490757983c499a08831d9bdc75a9eff08700bd29e8e5c134583ee07b3", + "533": "8a056666bd75d853a12d22b8317042a3f5500cfb21f6698d90ab41e01edcf81d", + "534": "8f65c9feb935e09a04c87143d1b2c63e38f08738199ebcc2758f67ee914d8a48", + "535": "ed8970f8ef1e2374289fc735aedff90b010c311a3b80d16df6bca2d3c250fdeb", + "536": "f82635851b442ec0ee95c5c2b7377ba382aa364cc49ff4e981d509ef324bb356", + "537": "54fc97bb6f3d7c724d4e245df37111c20334972300297fe38b590354fb9dfe92", + "538": "650c7f5f382c295cf6e7fb092db6fdfff164c861bcfcfe1fb38a50268f53f50a", + "539": "0bfb3df290912d8a70dc5e1e2761151cdf2c4b75d4b37c8fdcbed7483ada85fd", + "540": "08f1b2bffa88a9d01eecb8c9da6636b5e668a5478d8876a63ec3a74d7f932205", + "541": "5e59cf440336e86b67c17ed61f7bee7e548c434f475c415294b3b652d1aec606", + "542": "1257b6a3ad900df97f5aabc1e18b9f7ddae8c7d7ad60216ae21b5b7310cbda84", + "543": "8a783bfbe11c7f7b24431a15a0eb582f6fe5f75d1d21a3d55f8d8d81ba6b411c", + "544": "ce93bedef94ffbf62ad449cb0c68e8103a0bd005563ab854daa5e470664b4d7b", + "545": "40c253003d601fd2c90908bffcd8133e77489fe247e74ec03901895318fe69de", + "546": "d40739115f18fee96817266232ff1b8845e7966778fdcc644028fe5c759469be", + "547": "fa32a8de8fdcfc551d808c5dd0ff5545a199027acd32e380959b91f3b3d04643", + "548": "72e66168068b6ffcd2988e24124c8b1dba9a5b52a383a937397575e3c1e3f031", + "549": "a23baaa745a976b4f212836beb81a0a7b42d9f2e923c2412e2c07c63ff660ceb", + "550": "f58ff320639b2c47c76ed8aba487e31da0fd4656c3be6e33807cd00f77456e5d", + "551": "0449ec4d6d5b2e88603e62f3ec0287ed711cff682bbdfe298a197144ab24e80b", + "552": "f125761e8a0d02b17b1dc4be40216f2791727fd4e4bc56f60ebea1925c2fbf36", + "553": "dbb93b2a6cbf972bb1f94d1f8656cd113a09a02cbc44f25737e7d75c986646e1", + "554": "dcfd1e7a4a32ff0fae296b8211b5c9e91ab81844a0308933f598c712c1bc313d", + "555": "cebbca914f917f990202f110e77285132d2a5a3ba9a7475c93e3561d8ba88ea0", + "556": "0d5518ef165979b758fcc8df9c8cf536861f376f8640541ba6112ee7610ed82e", + "557": "0547c86b57c7c8c590f6d7a5131778f5b6ab2eeccc5e819e5fd095a6d4e68b08", + "558": "e763aa1dd494e097251484381ddb057c7d79b739c3f8644b1759e786e12f5b40", + "559": "e48eea4c3b4c9d58fe02739accf31bb64dd9c31623ad4cc06c740463d664c098", + "560": "77a09dc1ea6f1ae669004b8c9429dd83ead1148c62e0d945173edac45d9000a4", + "561": "756d226727e611d4bd22aa33747da2f635eeec070906dbc3262ef29e341e2a6d", + "562": "29de450d6e440c528287b98bcb4b76fb5155ab573df4721467446114661936ed", + "563": "7703d943dbbfdccb90acad65ed7c0eb13a10034ad01809472a55eb3162b7e53b", + "564": "65712c105411e6fc0ed35b9347de8cbaea33b0c5e57cf162f48dc257dd4f05b5", + "565": "2945ef4779089c9e49a9a9f5e2a67ba7e393aa20a955ed9302da6677cb03a9cd", + "566": "95d936e1d454df2e1e7d486c43af387b39a50cb57e57c7712d967bc9ec556f41", + "567": "2abe8af9ee20c6b8ad5034bc31fc1f4f16769595d5b4fc2837db3e76a90ac405", + "568": "fdc104338866e50ae2bffc1ea19719136f639df6c25f38a8680a70e9375a9378", + "569": "25677266de2b900788dfa047cb53f5585c37b564b3a711243fad52e186ec184a", + "570": "9101edb48d98c3742ceb713de591d261b79e90481d28f83f2d2c74d7034f4b46", + "571": "c364dde8cce2080d073eb1f9666cca97ccdeba61b2bf19ca0c84987e6f8d3576", + "572": "9cc3049e9464376b95fb88d6fff4331e0e40196f92a0a9aa1c5d10dfe33079f7", + "573": "2ade73491e183608b340f312d08cfd39c10ecb581c87b873443590452580a43e", + "574": "96325b210d18a7a1d6873e00a859648c4754bd4c91c324aa812ed78bd047118b", + "575": "33942a261a9150e2b5ce2ffe5b934a81f3972cf5aa5a9414a9d5f63f6b55324b", + "576": "7fca01a835681914b5fe5014d5649b5170faf459375ccc2bf9ad71ebaa73940c", + "577": "2bdc7a0e8adacf885c6ea0f6534b935b8a9dd338c5dcff05a74c162c3e9dd531", + "578": "ce6b6de1d907c8839b84f5f3967f6af7e9a3644a0bd7dffe80cfe531de08f8ca", + "579": "03dbff2575902a3c56a64483c8e8ca38d9888f72c6a71a6236eb07b808fb24ab", + "580": "96892003c30358ed55a39e13e6159fad09ebc3916e34492b91d63832fa86f731", + "581": "4fc5533c52133e54f8b54dcbfc4555638ae809676dbfec9d1400ab032f30648d", + "582": "7ba9b154acf699c8a123df5471fd40ad556cf6fc630136c686c87b09c88ff546", + "583": "ec10ea6801eadac9ae8ead5f222e0580f419b67d2ad5cd5c8ac914dcf5cfd69f", + "584": "510001c4104c80517a13f967df6ee071f15fb7b65e97229bc91b2925cbe4e93e", + "585": "ced737da53940337c5dff81720024fbaf4cee38aed1d3514d2a75c7b1271acf8", + "586": "9ab074d1d480d718930c9abac8b616a0bc5c30846381d6d9bce1741e9bca1991", + "587": "ec3fdcd8136188e3b476270894351cdc05dc44a4df50d1c4ed727294fb89430f", + "588": "31400607f95129fcc531604b7b0478a748d2495746280dc07ff30e39cd6f4a97", + "589": "3051de9b2a7ced941140aa1074952029f532e133beb41c18bfd990f43bfbd9ae", + "590": "4af295f83800334d77a04d56be7524ff6241e3d8b2f23820c9c54580b7996086", + "591": "2ecf2c1ab8d9e5cef5224842732af17bd2259598e4363e1d46cb172dccc39022", + "592": "2e71a26370d45781f31ede0c7810c2705706ce63291a52d5cd6f060ae16aeb01", + "593": "423867f77b64f725f823204796301ae09b427190cdbb62d472bc1395507da9a2", + "594": "6c28830e35913c59000dfce4432db255f7dd34809285881f05a9e9749f5d8452", + "595": "53fc00ae32e0b0d701175ac17ac0b91e05859ae6d7f3e5bf0548dad36e3d68f9", + "596": "9ccbee33387383d458e7ffa2c9c0cb9e4f5bbe3d1b949463a98232ae67d29956", + "597": "921102754e24e8ba99480e77652d88764020202e6dcd67adddbb1660204e8e78", + "598": "430f975f490ce37df74bc346556cb2186f7a47a58d3b282ab42f35b33a812f7c", + "599": "b603988248769444a1566b058ef3660cac528086b8193efd6d0be4080b834780", + "600": "dd539cd38fade63aa0d14899c7c75ff459ab839148b15b4efacd4bdfa0408dae", + "601": "571c5ade4cd89b460b7d2568a44d1efb05e2927ec840d8ecf149dc9e0ff09734", + "602": "edef32d6c2c7193b4b30a0e2c7d3ab37e0ec21db62543f4bf78169b683792e41", + "603": "cce7491b7ddf0e3ebde191e0e57614e61602cfaa2b52be5c2d657d9ae5e1f1b1", + "604": "d08fe0e5c0fc10640043f9d645446e23fa8efbfdf29c93c87794e5b6405ff51e", + "605": "1bdd74af73e2434db6149fd8089bd294defe3cedfaaf92f532568ddc6c48e2ea", + "606": "30e44b49f18048323d1c1bf4631587df8f0dbd477ebc79b7ef860a792953d932", + "607": "2d9b6a1b4810a39471e5dae85eadf595fc108097eeda746c8925a7be057464de", + "608": "cd3fdc5ee5b6e606349b9e5775d6e632e0424d6190f632632bd7435d5622b20d", + "609": "8b86933e27e64e6840bedc8087fa31326d9527a424c63ecc61823894c81f867d", + "610": "a781fd7cb6970e8f6f679296be5bb0fe7ea62207caa7ce86635257186a5a70d9", + "611": "4a3a0b9877d68deb8d7db624ec2d7f4b1c467fe337f803a220292ac6131acc05", + "612": "6e95bb170c3a521fc7befa446cad879a36b7b3d0e0e8eab1df6ddbd753156ab7", + "613": "afe8c7002c5e15859be829b4b69f0da00c1298971d5afa469b050016fc021978", + "614": "f85495a58ad9d5c4d16167084bbc3581ea22e6dfc39423b70d7fe486e316d951", + "615": "8da9fc3356df220081c71ccfc9c67251e6dd7058fb11258ecfc88ea9b8c00c92", + "616": "0fadf4975e2c27aae12447e080505d604258102f61c8667a5c2594ee033567e8", + "617": "06d9e8723de7ffd20129f1d8b5993926a97cad1261dc0cf01a37d8fa728ee996", + "618": "04d0dc62694f26c61871d8129259540884ad2296a3cf455f6b92fc911f98c336", + "619": "a93d0ec83cbbd4ec0866c97b372e4374a9d6724cc3767f5230e8316734cbb0eb", + "620": "071da5dc1dd87c2558b45247c29a92092bc5a00ef3cd46d70d08e18b791d2926", + "621": "458ca388a6b74c57ae13d1233984d5b66abb1f18dbfa12aa14ba868a9b5a708d", + "622": "1ad0227dc5f8c259ada5120d9db05ac7a013bd1bd84cbbca2f0ae6b174dac129", + "623": "d82d0401e10767b022417dbb64d348bc6c03ed4bb7e4553493e8d9e65525d229", + "624": "1d25005c86a9635d3483ea63ce95fa097f95792ebab86319c12bc66ea1d2ac83", + "625": "3fc397ed884cabc16bf30bb7487c8211424a08279a166d4fa4da6dc151a02cd1", + "626": "7c42e09e504cb269512dae989ee7fffe1f3bfea499c990e8edea796761331ccb", + "627": "5062b75aa39c974a579b0a3360c4da32e481d2242de72106f651c7d7de631cf1", + "628": "dc656eef13928f18d14a9265be6a923bc7d76048b861cdf1523e397801a8ef52", + "629": "9eefedc5b5995658be337f48146e37020db4ed3bb61e2af1fc57f698bd398b0d", + "630": "6e17ecc4a4d07ffbd67c49a59d31b7efeabd3bfead49fdf1ec005836e6030ebf", + "631": "781372694518c122f62566aec8867772e492fefef32c00e24b5604297dc1d44c", + "632": "c978055ae1d71dfdfd8bb4e845bb82fc4211b14560bf6001edefa4367e1d4403", + "633": "af4ff4b546369974642b3f68d4d3e90f0a0496b3b5d1572b638378fb49c7b4fa", + "634": "f6af89331ee087a2fc03e0bddd738e2716b49ed616ceb3b47743cf3806c6d8c2", + "635": "e4251ea6989571d8b83993560b537b7a9d0777ba54e6941757580cbfc14aab5f", + "636": "dc15b5ccabd8fd3141c244b7dbc6fe95078299ea3ce3016cbb483893fcdd4236", + "637": "053571be83ed06ab23a96d4e8fa129a4ce7e740de17dc35b000fb56c35a5ab80", + "638": "df57e1f418a24e38b39011048084c6b5cc91a56c1deb643ab605e0350f329b4b", + "639": "56930902baea90d1a8e505a227e5d7ac4da6b60f6c370ab75a0011cb3746818f", + "640": "c105d171242fa8e35f26491ba2f932d1577dfab2a4a6e75034ae69f062e8aa71", + "641": "0f6c3873a87ce630accf7f3b19feb764aac3fa0c3933042a817a82e6a9963aea", + "642": "c20081830b70a00d1bcc6f4b6572d511d534986c10ea3c057db304a1f26df2da", + "643": "143de023a92c7c8ce5fc0b839644e897267c44c8ba4e715743dc99686415a8b5", + "644": "7a8c9f1db1b1bce9a3b8d91e5b1a39a92a478029d975f5e45d593b7ca81a7134", + "645": "932a51e4c0cc5e30041ca5db1fd0674820638563a9df1300bece7df12c23017e", + "646": "a49cbd2966ea8248816b0a53b6eedea4aef2525aac45272b862d7d52e604625a", + "647": "40ad98735f3b5417ea1916f6146b69b7659963263caed186abf0790de0d9dae9", + "648": "53b288529a83c376f2399e986e5ca25c5993a6640063386fdb2de491afba2e81", + "649": "c0bebda0473186148087feb9828a418ab8d50726a1ff5c39ec69c4a6232c6b67", + "650": "98ac68d2bc42f89dbe97b3392ac691ed6c2c4f36a44665555bf7f816ca97cd27", + "651": "81f8287532f504b4f4a21e6d6ed573845bff197c479fb52e4c5b6f2fc1cfc40f", + "652": "fa32d8e7c1c766a6126b0f1cdd9d752cad55f54d0c05839e89d4da238615d9ed", + "653": "311cec39a42837f803ce8cfa5e6df32cc27fe541de108e3e7cf7ba3242e414ee", + "654": "f2b3d205c2da66cdf9a596e2caa1098132b832758eea2b14da071b8dd9584ec9", + "655": "39517ea688972769cccd46ad15b4f06ac2a6175d053dc97f849fa11a63a163e8", + "656": "2a21e5e89d9b019c1195c50af7c6e1864cbab05068d10e11519fb6d4766ceae5", + "657": "bbf54db41dc18753a3caa5001aae99c0c998e8a07b6e7390932054d7882498e3", + "658": "ca8e7b53b095939e5fafefa56e9b45b40c396145acf2a767f9f2430fbba75a79", + "659": "3d6c8492fbfe1c76e3f9d66485a7447489b89763623127deb6ed327a0c2a011b", + "660": "23d754ebe35981ad5de850f66bb2294a22280a8ad0b4160b1c29dfb5487505d9", + "661": "fd7eaca9690ee0384770e855ed600c96080c5c23565bfdae01c6045a87d9550a", + "662": "b93bc0a52860ee0a1fdc28adeca7b39288b1119e0f318467f0a193236e00f99c", + "663": "f73e3335b21c11b78987deb5a6eace1cef327981322f53a070adfbe31b56e7d0", + "664": "f3f0de955603850bd411690d11a5391e63f515a29e31e9241c66c62d688bcf72", + "665": "f2650e75f39098e5a114077b6e07bc15325adce22e1ab4b20569a4eeda5c6ca6", + "666": "a01a34d1c29aff5618a96046605adb74fa49b834975051d4ac82672567727a21", + "667": "2db51646a4038b38c88512738f79bb21776d39c7bfa3086538cccba0b63024db", + "668": "2f3336b7f1211fcc180cd76dc6442fecb412771aa45ef1a7675aa437d04e582b", + "669": "28bf022d827392eff1ec8ec121767ec24778f1b69da8605b4ab059023b8ad28a", + "670": "38db5dbb2a3ce2d31d1958f5b3ca4c3555eb0ac4193ebffa3f42ffd6bb4806e3", + "671": "0580cf2ef8abd3afbf91fba2032c2d51e43306bebb7f979bb750c3d7bd14c961", + "672": "394f1b74ccfac5a4fa958d813b5932371c5f8c2f3dbd1eb7202af2223aa08afb", + "673": "61c90400cd197b8ea6d7de90fcd1af0959fc37625fe163363fdae0ac4a724bfd", + "674": "6037c38f696b10fb531c26396890cd3b48d5408c5b37e61d03a72ae2f7b64ed6", + "675": "39c8b1bd1d534381b811bd8050e54b753106c1bfaf5d3cc63d8fe92a94471915", + "676": "346d1e2de9915fa2f4ce3675ccebadccb8e9d14239f1e53b6d08d09f5c26297d", + "677": "36841bba8f77d669e9d8f4e09ec580ce2c7a36c37da719815e65cc641eb1fdeb", + "678": "09532ddbaffb710f02964e270f8658bd8a09149744726a82f618708b35a5fa26", + "679": "774f8d6f89a5875342b21e8337aa5e3ab0539960a5b42554bc8d8a0fffce7d65", + "680": "48d62baa62c2a9c561612192ec39a7dbcecc8badadc0ddc755191648597a42f9", + "681": "7adc09dd86f3e73979d9f8a4b3232102ca52bc41d043614fe989cd908ed88c76", + "682": "522f0ff3ae2f1761dca78207dec1c9b52556eba2db7503ca03441abf62f65c76", + "683": "376e3c3e4b88ee76cb0f02390751a7248fcf1562013b1390b1e276a3f3d7da63", + "684": "6363f306f081683781908acd4bedd92b3a75c796243cdacadc4b9896d8cfaaaa", + "685": "29f2c4c5325cf626b392a910e6e22b6d2a989bfbb38439c20162b7b786b5e2f8", + "686": "990ae3583a1f7a32b7581a8ace626511c812e0bd910b8064fefb31e625b9c22d", + "687": "7e78b4b91851b976f5cc2a1341b9389ae7bdd0248ae7f7c53e7ebb2d86bbc73c", + "688": "1ada92e769892b4bb540d75cbf40017a24b5b309b28a097ed62eb7f2727518e7", + "689": "17a0ba5b100d0a92f3f82e2e6f31c71a6ca53a0f043094a6419331e22036150b", + "690": "f9658a8f0687d69f420f655c500304c3c0888f298a68075ab6a2165a3bc47c53", + "691": "3ff8aa53eb2f7e700fdc7cb838ca7f7b495948bb997ef70d196c10592fa64680", + "692": "c01c3e579b2743866cd3d0c1d9039871356143a99c572593d2702f387e9f629f", + "693": "c08e2dd3686459c2989cd6a367d2cc64b2bc2af460417102e9856e91b5f78fa4", + "694": "063e59bfd9cbed08afa508954ac9c1c313b80331d6a917fd2202e15e1eeb00e9", + "695": "c3259eeed96a5837a6630fd9d1245de7c77e10d0733b6129a3dc99548bd92800", + "696": "9ab20a4d8c3c0de897a1c8afa95733d0f7f79870c6379064ef4cf1f5baae67e6", + "697": "62c07adf4da24a20a723f6c32e35a51f2b942e363dc9fa35070e34991a5a9c1d", + "698": "632f1a4eba12f5c80401d82c4bad7c5679f55ccc89bf2da3e3930ff3d6671ba1", + "699": "8c40c5c92fad7ed2774080ddd39f62cdc94ca05dde4273344497ab4206499484", + "700": "3dccee8e873d2c9c2f8359417e666b702f97b60b90b229e3c41190909ff9388b", + "701": "65a57fc7ebcdab77821276a1eba1c1a625bf2bae575b025359de492592ded205", + "702": "c1b0ade78aadbf0d5576489c2200439ef825fe74452115edbc908e9ff955efc0", + "703": "1e5ea7fffdcdbca5fc91694b200db8e2e3737e829b7694e4dcf3b937b41be330", + "704": "9ddf38880f294ac1a759c764c394cacd4635735880f326a0b5e4a896e4fdce8c", + "705": "2bb033d9eeb9157fc6ae835e99b9523bfb1d61173cfb34941cbfdc4c0d3ea67e", + "706": "51a0e8daacbd6537efd583c48c5815a9bd22fef0eb9b8e15dbe2ee87c76e2a6b", + "707": "9f50d3b52dc4ebae279c6f6021258ca8cd60b8cd13e358f29a2879caa390a774", + "708": "42e0a9be7737aaab1fd27543c0273f4c97dd3bd6471e6ec04b1fc7b79542db71", + "709": "ac2605c16873ea2b5f0ce5008089a55e37588f45313ad06ccc7dfd96f407eb8a", + "710": "09214942caed4184e7155b4016b1e0de37c0a142deaebee3879c770438a28276", + "711": "8d8ea19a78bcb10e502f91a057bac1b200ab17db66e11cdf42b63ec65a8e6c18", + "712": "001493340cc232a48125f958308be6d0567ff2684e0625e55af8b0a024c4ccca", + "713": "98a124df4ffa11cca86fbd959f4d091665fc871a4a86cc1024429d1c116b556e", + "714": "cd175b00873a9a3369c628861c1f20df57a4ca75074530ebf5b974d04b8b93c4", + "715": "cdb954d8620ad2d95915f94243cdcf71170cfc363334b2f831544f55f0d15746", + "716": "abb62293fb9df9bc7a6e80ea24f0da1049f894ade937367e24563a3277f953ef", + "717": "319369720bf1831be4c73600c26f5d08dcf6cf85fd32340c28263e39c1dda5e6", + "718": "412ce061b1ae228d2226fdb3bf2cb68421870465d6a8cf7ae58515c02fe54684", + "719": "c461587d4f3a41c375628e94fb9f971cc2829b8608d3c7aca840e62a6c8f1929", + "720": "3651d0d1f023c90e42be5c6ccf28ca71203d1c67d85249323d35db28f146786f", + "721": "8430fc43038ba44efb6e9ecbd5aa3dfeaeaf73f2d04a2d5596855c7de5de9c20", + "722": "9687101dfe209fd65f57a10603baa38ba83c9152e43a8b802b96f1e07f568e0e", + "723": "74832787e7d4e0cb7991256c8f6d02775dffec0684de234786f25f898003f2de", + "724": "fa05e2b497e7eafa64574017a4c45aadef6b163d907b03d63ba3f4021096d329", + "725": "005c873563f51bbebfdb1f8dbc383259e9a98e506bc87ae8d8c9044b81fc6418" } diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index 364c71e2fb1c..68461dca6710 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 +import hashlib import importlib.util import json import os import pathlib from types import ModuleType -from typing import Dict, List import pytest import requests @@ -15,7 +15,7 @@ ) with open(PROJECT_EULER_ANSWERS_PATH) as file_handle: - PROBLEM_ANSWERS: Dict[str, str] = json.load(file_handle) + PROBLEM_ANSWERS: dict[str, str] = json.load(file_handle) def convert_path_to_module(file_path: pathlib.Path) -> ModuleType: @@ -26,7 +26,7 @@ def convert_path_to_module(file_path: pathlib.Path) -> ModuleType: return module -def all_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(): @@ -46,7 +46,7 @@ def get_files_url() -> str: return event["pull_request"]["url"] + "/files" -def added_solution_file_path() -> List[pathlib.Path]: +def added_solution_file_path() -> list[pathlib.Path]: """Collects only the solution file path which got added in the current pull request. @@ -70,7 +70,7 @@ def added_solution_file_path() -> List[pathlib.Path]: return solution_file_paths -def collect_solution_file_paths() -> List[pathlib.Path]: +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(): @@ -90,4 +90,7 @@ def test_project_euler(solution_path: pathlib.Path) -> None: expected: str = PROBLEM_ANSWERS[problem_number] solution_module = convert_path_to_module(solution_path) answer = str(solution_module.solution()) # type: ignore - assert answer == expected, f"Expected {expected} but got {answer}" + answer = hashlib.sha256(answer.encode()).hexdigest() + assert ( + answer == expected + ), f"Expected solution to {problem_number} to have hash {expected}, got {answer}" From 4ab39c20019b1fe41c1b24d2bc667fb852aa62a3 Mon Sep 17 00:00:00 2001 From: The Data Lady Date: Mon, 17 May 2021 04:36:14 -0700 Subject: [PATCH 1480/2908] Removed keras dependency from requirements.txt (#4374) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 349d88944656..76eb109ee3b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ beautifulsoup4 fake_useragent -keras; python_version < '3.9' +keras lxml matplotlib numpy From 002c545aee474626a6ffdaf05af2d431645e14f8 Mon Sep 17 00:00:00 2001 From: onlinejudge95 <44158581+onlinejudge95@users.noreply.github.com> Date: Mon, 17 May 2021 17:28:51 +0530 Subject: [PATCH 1481/2908] Removes python_version condition on tensorflow (#4435) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 76eb109ee3b9..8bbb8d524ed4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ scikit-fuzzy sklearn statsmodels sympy -tensorflow; python_version < '3.9' +tensorflow xgboost From 7d7c7972aeba412c14384fa1a6c9009c1260b1ab Mon Sep 17 00:00:00 2001 From: Jenil Shah <60750701+Jenil-S@users.noreply.github.com> Date: Mon, 17 May 2021 17:58:04 +0530 Subject: [PATCH 1482/2908] Updated name from lstm_prediction.py_tf to lstm_prediction.py and also imported keras (#4422) * Updated name from lstm_prediction.py_lf to lstm_prediction.py and also imported keras * Edited the changes * tensorflow 2.5 is has shipped!!! * Update lstm_prediction.py * Update lstm_prediction.py * One blank line, not two? Co-authored-by: Christian Clauss --- .../lstm/{lstm_prediction.py_tf => lstm_prediction.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename machine_learning/lstm/{lstm_prediction.py_tf => lstm_prediction.py} (95%) diff --git a/machine_learning/lstm/lstm_prediction.py_tf b/machine_learning/lstm/lstm_prediction.py similarity index 95% rename from machine_learning/lstm/lstm_prediction.py_tf rename to machine_learning/lstm/lstm_prediction.py index 5452f0443f62..6fd3cf29131d 100644 --- a/machine_learning/lstm/lstm_prediction.py_tf +++ b/machine_learning/lstm/lstm_prediction.py @@ -6,9 +6,9 @@ """ import numpy as np import pandas as pd -from keras.layers import LSTM, Dense -from keras.models import Sequential from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import LSTM, Dense +from tensorflow.keras.models import Sequential if __name__ == "__main__": """ From 8d173438c38cb7d92f6daf2c0e3405067bf4166f Mon Sep 17 00:00:00 2001 From: Cere Blanco <743526+cereblanco@users.noreply.github.com> Date: Tue, 18 May 2021 22:54:34 +0800 Subject: [PATCH 1483/2908] Bit manipulation: get the bit at a given position (#4438) --- .../single_bit_manipulation_operations.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bit_manipulation/single_bit_manipulation_operations.py b/bit_manipulation/single_bit_manipulation_operations.py index e4a54028d9ee..b43ff07b776f 100644 --- a/bit_manipulation/single_bit_manipulation_operations.py +++ b/bit_manipulation/single_bit_manipulation_operations.py @@ -74,6 +74,26 @@ def is_bit_set(number: int, position: int) -> bool: return ((number >> position) & 1) == 1 +def get_bit(number: int, position: int) -> int: + """ + Get the bit at the given position + + Details: perform bitwise and for the given number and X, + Where X is a number with all the bits – zeroes and bit on given position – one. + If the result is not equal to 0, then the bit on the given position is 1, else 0. + + >>> get_bit(0b1010, 0) + 0 + >>> get_bit(0b1010, 1) + 1 + >>> get_bit(0b1010, 2) + 0 + >>> get_bit(0b1010, 3) + 1 + """ + return int((number & (1 << position)) != 0) + + if __name__ == "__main__": import doctest From 368ce7aecc2acd5d3b472dc9141d160d98164353 Mon Sep 17 00:00:00 2001 From: TANMAY SRIVASTAVA <77936821+ktsrivastava29@users.noreply.github.com> Date: Thu, 20 May 2021 13:58:00 +0530 Subject: [PATCH 1484/2908] Added a hex-bin.py file in conversion.py (#4433) * Added a file that converts hexa to binary * Added file to convert hexadecimal to binary * Update hex-bin.py * added type hint in the code * Added doctest * Added code to handle exception * Resolved doctest issue * Update hex-bin.py * Modified convert function * Added WhiteSpace around operators. * Made more pythonic * removed whitespace * Updated doctest command * Removed whitespace * imported union * Replaced flag with is_negative * updated return type * removed pip command * Resolved doctest issue * Resolved doctest error * Reformated the code * Changes function name * Changed exception handling statements * Update and rename hex-bin.py to hex_to_bin.py * Update newton_method.py * Update matrix_operation.py * Update can_string_be_rearranged_as_palindrome.py * Update hex_to_bin.py Co-authored-by: Christian Clauss --- conversions/hex_to_bin.py | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 conversions/hex_to_bin.py diff --git a/conversions/hex_to_bin.py b/conversions/hex_to_bin.py new file mode 100644 index 000000000000..e358d810b581 --- /dev/null +++ b/conversions/hex_to_bin.py @@ -0,0 +1,56 @@ +def hex_to_bin(hex_num: str) -> int: + """ + Convert a hexadecimal value to its binary equivalent + #https://stackoverflow.com/questions/1425493/convert-hex-to-binary + Here, we have used the bitwise right shift operator: >> + Shifts the bits of the number to the right and fills 0 on voids left as a result. + Similar effect as of dividing the number with some power of two. + Example: + a = 10 + a >> 1 = 5 + + >>> hex_to_bin("AC") + 10101100 + >>> hex_to_bin("9A4") + 100110100100 + >>> hex_to_bin(" 12f ") + 100101111 + >>> hex_to_bin("FfFf") + 1111111111111111 + >>> hex_to_bin("-fFfF") + -1111111111111111 + >>> hex_to_bin("F-f") + Traceback (most recent call last): + ... + ValueError: Invalid value was passed to the function + >>> hex_to_bin("") + Traceback (most recent call last): + ... + ValueError: No value was passed to the function + """ + + hex_num = hex_num.strip() + if not hex_num: + raise ValueError("No value was passed to the function") + + is_negative = hex_num[0] == "-" + if is_negative: + hex_num = hex_num[1:] + + try: + int_num = int(hex_num, 16) + except ValueError: + raise ValueError("Invalid value was passed to the function") + + bin_str = "" + while int_num > 0: + bin_str = str(int_num % 2) + bin_str + int_num >>= 1 + + return int(("-" + bin_str) if is_negative else bin_str) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b11e5314b76c66709f1c2620e43827e56ea07ef5 Mon Sep 17 00:00:00 2001 From: Tobias <38182275+Txbias@users.noreply.github.com> Date: Thu, 20 May 2021 19:15:51 +0000 Subject: [PATCH 1485/2908] Added implementation for MSD radix sort algorithm based on binary representation (#4441) * Added MSD radix sort algorithm * Fixed typos * Added doctests * Added link to wikipedia * Added doctest and improved code --- sorts/msd_radix_sort.py | 80 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 sorts/msd_radix_sort.py diff --git a/sorts/msd_radix_sort.py b/sorts/msd_radix_sort.py new file mode 100644 index 000000000000..ee152bbc696a --- /dev/null +++ b/sorts/msd_radix_sort.py @@ -0,0 +1,80 @@ +""" +Python implementation of the MSD radix sort algorithm. +It used the binary representation of the integers to sort +them. +https://en.wikipedia.org/wiki/Radix_sort +""" +from typing import List + + +def msd_radix_sort(list_of_ints: List[int]) -> List[int]: + """ + Implementation of the MSD radix sort algorithm. Only works + with positive integers + :param list_of_ints: A list of integers + :return: Returns the sorted list + >>> msd_radix_sort([40, 12, 1, 100, 4]) + [1, 4, 12, 40, 100] + >>> msd_radix_sort([]) + [] + >>> msd_radix_sort([123, 345, 123, 80]) + [80, 123, 123, 345] + >>> msd_radix_sort([1209, 834598, 1, 540402, 45]) + [1, 45, 1209, 540402, 834598] + >>> msd_radix_sort([-1, 34, 45]) + Traceback (most recent call last): + ... + ValueError: All numbers must be positive + """ + if not list_of_ints: + return [] + + if min(list_of_ints) < 0: + raise ValueError("All numbers must be positive") + + most_bits = max(len(bin(x)[2:]) for x in list_of_ints) + return _msd_radix_sort(list_of_ints, most_bits) + + +def _msd_radix_sort(list_of_ints: List[int], bit_position: int) -> List[int]: + """ + Sort the given list based on the bit at bit_position. Numbers with a + 0 at that position will be at the start of the list, numbers with a + 1 at the end. + :param list_of_ints: A list of integers + :param bit_position: the position of the bit that gets compared + :return: Returns a partially sorted list + >>> _msd_radix_sort([45, 2, 32], 1) + [2, 32, 45] + >>> _msd_radix_sort([10, 4, 12], 2) + [4, 12, 10] + """ + if bit_position == 0 or len(list_of_ints) in [0, 1]: + return list_of_ints + + zeros = list() + ones = list() + # Split numbers based on bit at bit_position from the right + for number in list_of_ints: + if (number >> (bit_position - 1)) & 1: + # number has a one at bit bit_position + ones.append(number) + else: + # number has a zero at bit bit_position + zeros.append(number) + + # recursively split both lists further + zeros = _msd_radix_sort(zeros, bit_position - 1) + ones = _msd_radix_sort(ones, bit_position - 1) + + # recombine lists + res = zeros + res.extend(ones) + + return res + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 92836d57f6efa8b619d71ac5824189d74b93876f Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Fri, 21 May 2021 14:31:56 +0530 Subject: [PATCH 1486/2908] feat: action to approve workflow run (#4444) --- .github/workflows/approve_workflow_run.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/approve_workflow_run.yml diff --git a/.github/workflows/approve_workflow_run.yml b/.github/workflows/approve_workflow_run.yml new file mode 100644 index 000000000000..ff12d892532f --- /dev/null +++ b/.github/workflows/approve_workflow_run.yml @@ -0,0 +1,21 @@ +# https://docs.github.com/en/rest/reference/actions#approve-a-workflow-run-for-a-fork-pull-request + +name: Approve Workflow Run + +on: + workflow_run: + types: + - completed + +jobs: + approve: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == "action_required" }} + steps: + - name: Automatically approve a workflow run + run: | + curl \ + --request POST \ + --header "Accept: application/vnd.github.v3+json" \ + --header "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + --url "https://api.github.com/repos/$%7B%7B%20github.repository%20%7D%7D/actions/runs/$%7B%7B%20github.event.workflow_run.id%20%7D%7D/approve" From ac29f707555baaf0c38963312b291cd9a210b535 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Fri, 21 May 2021 19:03:56 +0530 Subject: [PATCH 1487/2908] fix(action): correct indentation for types key (#4445) * fix(action): correct indentation for types key * updating DIRECTORY.md * refactor: add quotes around name key Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/approve_workflow_run.yml | 8 ++++---- DIRECTORY.md | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/approve_workflow_run.yml b/.github/workflows/approve_workflow_run.yml index ff12d892532f..ff6ebd99653e 100644 --- a/.github/workflows/approve_workflow_run.yml +++ b/.github/workflows/approve_workflow_run.yml @@ -1,18 +1,18 @@ # https://docs.github.com/en/rest/reference/actions#approve-a-workflow-run-for-a-fork-pull-request -name: Approve Workflow Run +name: "Approve Workflow Run" on: workflow_run: - types: - - completed + types: + - completed jobs: approve: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == "action_required" }} steps: - - name: Automatically approve a workflow run + - name: "Automatically approve a workflow run" run: | curl \ --request POST \ diff --git a/DIRECTORY.md b/DIRECTORY.md index 26929255d1a0..59365e047061 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -108,6 +108,7 @@ * [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) + * [Hex To Bin](https://github.com/TheAlgorithms/Python/blob/master/conversions/hex_to_bin.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) @@ -382,6 +383,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) @@ -855,6 +858,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) + * [Msd Radix Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/msd_radix_sort.py) * [Natural Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/natural_sort.py) * [Odd Even Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_sort.py) * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) From 32e9072627bd39e5937bd8b9003a8db956a4cdaa Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Fri, 21 May 2021 19:25:59 +0530 Subject: [PATCH 1488/2908] fix(action): testing and fixing errors (#4446) * fix(action): testing and fixing errors * fix: testing if all is a valid entry for workflows * fix: more events to trigger workflow tests * fix: double quotes -> single quotes * fix: add workflows name to the list * revert: remove added events This reverts commit 3daeeb2ba34b8a9cde93fce2cac682378aea5e9a --- .github/workflows/approve_workflow_run.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/approve_workflow_run.yml b/.github/workflows/approve_workflow_run.yml index ff6ebd99653e..7c5e64452d5f 100644 --- a/.github/workflows/approve_workflow_run.yml +++ b/.github/workflows/approve_workflow_run.yml @@ -4,13 +4,14 @@ name: "Approve Workflow Run" on: workflow_run: + workflows: ['build', 'project_euler', 'pre-commit', 'directory_writer'] types: - completed jobs: approve: runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == "action_required" }} + if: ${{ github.event.workflow_run.conclusion == 'action_required' }} steps: - name: "Automatically approve a workflow run" run: | From b913a0d83aaf2328108c2fb8b47c5d253116cef3 Mon Sep 17 00:00:00 2001 From: Tobias <38182275+Txbias@users.noreply.github.com> Date: Mon, 24 May 2021 20:36:57 +0000 Subject: [PATCH 1489/2908] Implemented MSD radix sort algorithm in-place (#4449) * Implemented MSD radix sort algorithm inplace * Fixed formatting --- sorts/msd_radix_sort.py | 80 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/sorts/msd_radix_sort.py b/sorts/msd_radix_sort.py index ee152bbc696a..4c3cea30ef68 100644 --- a/sorts/msd_radix_sort.py +++ b/sorts/msd_radix_sort.py @@ -74,6 +74,86 @@ def _msd_radix_sort(list_of_ints: List[int], bit_position: int) -> List[int]: return res +def msd_radix_sort_inplace(list_of_ints: List[int]): + """ + Inplace implementation of the MSD radix sort algorithm. + Sorts based on the binary representation of the integers. + >>> lst = [1, 345, 23, 89, 0, 3] + >>> msd_radix_sort_inplace(lst) + >>> lst == sorted(lst) + True + >>> lst = [1, 43, 0, 0, 0, 24, 3, 3] + >>> msd_radix_sort_inplace(lst) + >>> lst == sorted(lst) + True + >>> lst = [] + >>> msd_radix_sort_inplace(lst) + >>> lst == [] + True + >>> lst = [-1, 34, 23, 4, -42] + >>> msd_radix_sort_inplace(lst) + Traceback (most recent call last): + ... + ValueError: All numbers must be positive + """ + + length = len(list_of_ints) + if not list_of_ints or length == 1: + return + + if min(list_of_ints) < 0: + raise ValueError("All numbers must be positive") + + most_bits = max(len(bin(x)[2:]) for x in list_of_ints) + _msd_radix_sort_inplace(list_of_ints, most_bits, 0, length) + + +def _msd_radix_sort_inplace( + list_of_ints: List[int], bit_position: int, begin_index: int, end_index: int +): + """ + Sort the given list based on the bit at bit_position. Numbers with a + 0 at that position will be at the start of the list, numbers with a + 1 at the end. + >>> lst = [45, 2, 32, 24, 534, 2932] + >>> _msd_radix_sort_inplace(lst, 1, 0, 3) + >>> lst == [32, 2, 45, 24, 534, 2932] + True + >>> lst = [0, 2, 1, 3, 12, 10, 4, 90, 54, 2323, 756] + >>> _msd_radix_sort_inplace(lst, 2, 4, 7) + >>> lst == [0, 2, 1, 3, 12, 4, 10, 90, 54, 2323, 756] + True + """ + if bit_position == 0 or end_index - begin_index <= 1: + return + + bit_position -= 1 + + i = begin_index + j = end_index - 1 + while i <= j: + changed = False + if not ((list_of_ints[i] >> bit_position) & 1): + # found zero at the beginning + i += 1 + changed = True + if (list_of_ints[j] >> bit_position) & 1: + # found one at the end + j -= 1 + changed = True + + if changed: + continue + + list_of_ints[i], list_of_ints[j] = list_of_ints[j], list_of_ints[i] + j -= 1 + if not j == i: + i += 1 + + _msd_radix_sort_inplace(list_of_ints, bit_position, begin_index, i) + _msd_radix_sort_inplace(list_of_ints, bit_position, i, end_index) + + if __name__ == "__main__": import doctest From 650039a279f0eef4f2e267d27c6b2d5be2e18fa0 Mon Sep 17 00:00:00 2001 From: Benjamin Fein Date: Sun, 30 May 2021 11:27:42 -0400 Subject: [PATCH 1490/2908] Add a recursive merge sort algorithm that accepts an array as input. (#4462) This is a different recursive implementation of the merge sort algorithm. * Recursive Merge Sort That Accepts an Array Recursive Merge Sort That Accepts an Array * Add Wikipedia Link * Fixes naming conventions * Update sorts/recursive_mergesort_array.py Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> * Update sorts/recursive_mergesort_array.py Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> * Update sorts/recursive_mergesort_array.py Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> * Update sorts/recursive_mergesort_array.py Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> * Update sorts/recursive_mergesort_array.py Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> * Adds black format * Removes unused variables * Fixes variable names and adds documentation * Fixes variable names to use snake_case. * Removes double #. * Update sorts/recursive_mergesort_array.py Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> Co-authored-by: Benjamin Fein --- sorts/recursive_mergesort_array.py | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 sorts/recursive_mergesort_array.py diff --git a/sorts/recursive_mergesort_array.py b/sorts/recursive_mergesort_array.py new file mode 100644 index 000000000000..f714d02380cf --- /dev/null +++ b/sorts/recursive_mergesort_array.py @@ -0,0 +1,64 @@ +"""A merge sort which accepts an array as input and recursively +splits an array in half and sorts and combines them. +""" + +"""https://en.wikipedia.org/wiki/Merge_sort""" + + +def merge(arr: list[int]) -> list[int]: + """Return a sorted array. + >>> merge([10,9,8,7,6,5,4,3,2,1]) + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + >>> merge([1,2,3,4,5,6,7,8,9,10]) + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + >>> merge([10,22,1,2,3,9,15,23]) + [1, 2, 3, 9, 10, 15, 22, 23] + >>> merge([100]) + [100] + >>> merge([]) + [] + """ + if len(arr) > 1: + middle_length = len(arr) // 2 # Finds the middle of the array + left_array = arr[ + :middle_length + ] # Creates an array of the elements in the first half. + right_array = arr[ + middle_length: + ] # Creates an array of the elements in the second half. + left_size = len(left_array) + right_size = len(right_array) + merge(left_array) # Starts sorting the left. + merge(right_array) # Starts sorting the right + left_index = 0 # Left Counter + right_index = 0 # Right Counter + index = 0 # Position Counter + while ( + left_index < left_size and right_index < right_size + ): # Runs until the lowers size of the left and right are sorted. + if left_array[left_index] < right_array[right_index]: + arr[index] = left_array[left_index] + left_index = left_index + 1 + else: + arr[index] = right_array[right_index] + right_index = right_index + 1 + index = index + 1 + while ( + left_index < left_size + ): # Adds the left over elements in the left half of the array + arr[index] = left_array[left_index] + left_index = left_index + 1 + index = index + 1 + while ( + right_index < right_size + ): # Adds the left over elements in the right half of the array + arr[index] = right_array[right_index] + right_index = right_index + 1 + index = index + 1 + return arr + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b3b89d9460a56d88ac4ba528363bbdcc55bb3fa6 Mon Sep 17 00:00:00 2001 From: Grigoriy Hanin <43445998+haningrisha@users.noreply.github.com> Date: Mon, 31 May 2021 02:41:07 +0300 Subject: [PATCH 1491/2908] Armstrong number definition fix (#4466) This is a documentation fix. This fix patches a mistake in the description of the Armstrong number. * * Doc fix * Armstrong number is not the sum of cube's of number digits, but the sum of number's digits each raised to the power of the number of digits * Update armstrong_numbers.py Line shorten --- maths/armstrong_numbers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index d30ed2e430a0..af25688dbacc 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -1,5 +1,6 @@ """ -An Armstrong number is equal to the sum of the cubes of its digits. +An Armstrong number is equal to the sum of its own digits each raised +to the power of the number of 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. """ From 04f156a8973d6156a4357e0717d9eb0aa264d086 Mon Sep 17 00:00:00 2001 From: Vivian Dai <38384400+vivian-dai@users.noreply.github.com> Date: Mon, 31 May 2021 04:56:11 -0400 Subject: [PATCH 1492/2908] markdown consistency (#4461) * markdown consistency * Swap ** for __ Co-authored-by: Christian Clauss --- CONTRIBUTING.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76ee1312f345..d93b5db67fe8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## 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). +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 @@ -15,9 +15,9 @@ 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 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. +__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. +__Improving comments__ and __writing proper tests__ are also highly welcome. ### Contribution @@ -33,7 +33,7 @@ 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()). +* 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. @@ -42,7 +42,7 @@ Algorithms should: * 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 +* 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 @@ -66,10 +66,10 @@ pre-commit run --all-files --show-diff-on-failure We want your work to be readable by others; therefore, we encourage you to note the following: -- 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 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. + - 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 they make the code easier to read. @@ -81,7 +81,7 @@ 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 . --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. +- 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 python3 -m pip install flake8 # only required the first time @@ -134,7 +134,7 @@ We want your work to be readable by others; therefore, we encourage you to note python3 -m doctest -v my_submission.py ``` - The use of the Python builtin __input()__ function is **not** encouraged: + The use of the Python builtin `input()` function is __not__ encouraged: ```python input('Enter your input:') @@ -142,7 +142,7 @@ 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()__ 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()) @@ -175,8 +175,8 @@ We want your work to be readable by others; therefore, we encourage you to note - 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!** + - __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. From 71b458cefe20226c2904223265589dd788c110e0 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 31 May 2021 17:49:09 +0200 Subject: [PATCH 1493/2908] Rename harriscorner.py to harris_corner.py (#4470) * Rename harriscorner.py to harris_corner.py * updating DIRECTORY.md * Rename meanthreshold.py to mean_threshold.py * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 +++-- computer_vision/{harriscorner.py => harris_corner.py} | 0 computer_vision/{meanthreshold.py => mean_threshold.py} | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename computer_vision/{harriscorner.py => harris_corner.py} (100%) rename computer_vision/{meanthreshold.py => mean_threshold.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 59365e047061..9905753b2d24 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -97,8 +97,8 @@ * [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) - * [Meanthreshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/meanthreshold.py) + * [Harris Corner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harris_corner.py) + * [Mean Threshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mean_threshold.py) ## Conversions * [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py) @@ -874,6 +874,7 @@ * [Random Pivot Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_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 Mergesort Array](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_mergesort_array.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) diff --git a/computer_vision/harriscorner.py b/computer_vision/harris_corner.py similarity index 100% rename from computer_vision/harriscorner.py rename to computer_vision/harris_corner.py diff --git a/computer_vision/meanthreshold.py b/computer_vision/mean_threshold.py similarity index 100% rename from computer_vision/meanthreshold.py rename to computer_vision/mean_threshold.py From cb0a5480a7c198a34069e4e65707c18f0ee6b7b9 Mon Sep 17 00:00:00 2001 From: Lakshay Akula Date: Mon, 31 May 2021 20:55:01 -0400 Subject: [PATCH 1494/2908] Add catalan_numbers.py (#4455) Reviewed by @mrmaxguns. This is an implementation of Catalan Numbers. --- dynamic_programming/catalan_numbers.py | 79 ++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 dynamic_programming/catalan_numbers.py diff --git a/dynamic_programming/catalan_numbers.py b/dynamic_programming/catalan_numbers.py new file mode 100644 index 000000000000..7b74f2763d43 --- /dev/null +++ b/dynamic_programming/catalan_numbers.py @@ -0,0 +1,79 @@ +""" +Print all the Catalan numbers from 0 to n, n being the user input. + + * The Catalan numbers are a sequence of positive integers that + * appear in many counting problems in combinatorics [1]. Such + * problems include counting [2]: + * - The number of Dyck words of length 2n + * - The number well-formed expressions with n pairs of parentheses + * (e.g., `()()` is valid but `())(` is not) + * - The number of different ways n + 1 factors can be completely + * parenthesized (e.g., for n = 2, C(n) = 2 and (ab)c and a(bc) + * are the two valid ways to parenthesize. + * - The number of full binary trees with n + 1 leaves + + * A Catalan number satisfies the following recurrence relation + * which we will use in this algorithm [1]. + * C(0) = C(1) = 1 + * C(n) = sum(C(i).C(n-i-1)), from i = 0 to n-1 + + * In addition, the n-th Catalan number can be calculated using + * the closed form formula below [1]: + * C(n) = (1 / (n + 1)) * (2n choose n) + + * Sources: + * [1] https://brilliant.org/wiki/catalan-numbers/ + * [2] https://en.wikipedia.org/wiki/Catalan_number +""" + + +def catalan_numbers(upper_limit: int) -> "list[int]": + """ + Return a list of the Catalan number sequence from 0 through `upper_limit`. + + >>> catalan_numbers(5) + [1, 1, 2, 5, 14, 42] + >>> catalan_numbers(2) + [1, 1, 2] + >>> catalan_numbers(-1) + Traceback (most recent call last): + ValueError: Limit for the Catalan sequence must be ≥ 0 + """ + if upper_limit < 0: + raise ValueError("Limit for the Catalan sequence must be ≥ 0") + + catalan_list = [0] * (upper_limit + 1) + + # Base case: C(0) = C(1) = 1 + catalan_list[0] = 1 + if upper_limit > 0: + catalan_list[1] = 1 + + # Recurrence relation: C(i) = sum(C(j).C(i-j-1)), from j = 0 to i + for i in range(2, upper_limit + 1): + for j in range(i): + catalan_list[i] += catalan_list[j] * catalan_list[i - j - 1] + + return catalan_list + + +if __name__ == "__main__": + print("\n********* Catalan Numbers Using Dynamic Programming ************\n") + print("\n*** Enter -1 at any time to quit ***") + print("\nEnter the upper limit (≥ 0) for the Catalan number sequence: ", end="") + try: + while True: + N = int(input().strip()) + if N < 0: + print("\n********* Goodbye!! ************") + break + else: + print(f"The Catalan numbers from 0 through {N} are:") + print(catalan_numbers(N)) + print("Try another upper limit for the sequence: ", end="") + except (NameError, ValueError): + print("\n********* Invalid input, goodbye! ************\n") + + import doctest + + doctest.testmod() From 40e357f688c512c0c587f7455b75902c574336a7 Mon Sep 17 00:00:00 2001 From: Grigoriy Hanin <43445998+haningrisha@users.noreply.github.com> Date: Fri, 4 Jun 2021 23:16:32 +0300 Subject: [PATCH 1495/2908] Mistake in maths/average_mode.py fixed. (#4464) A serious bug was addressed with this pull request. The mode function previously didn't return the mode of the input list. Instead, it always returned the first value. Due to lacking tests, the bug was never caught. This pull request also adds new functionality to the function, allowing support for more than one mode. See #4464 for details. * * Mistake in average_mode.py fixed. The previous solution was to returnthe value on the first loop iteration, which is not correct, more than that it used to delete repeating values, so result's array and check array lost relation between each other * Type hint added * redundant check_list deleted Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> * Suggestions resolved * output typing changed to Any * test cases added * Black done File formatted * Unused statistics import statistics only used in doctest, now they are imported in doctest * Several modes support added Several modes support added * Comment fix * Update maths/average_mode.py Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> * Suggestions added Co-authored-by: Maxim R. <49735721+mrmaxguns@users.noreply.github.com> --- maths/average_mode.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/maths/average_mode.py b/maths/average_mode.py index d472dc04d4bf..83db820072bf 100644 --- a/maths/average_mode.py +++ b/maths/average_mode.py @@ -1,7 +1,4 @@ -import statistics - - -def mode(input_list): # Defining function "mode." +def mode(input_list: list) -> list: # Defining function "mode." """This function returns the mode(Mode as in the measures of central tendency) of the input data. @@ -9,23 +6,32 @@ def mode(input_list): # Defining function "mode." >>> 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 + [2] + >>> input_list = [3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 4, 2, 2, 2] + >>> mode(input_list) + [2] + >>> input_list = [3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 4, 4, 2, 2, 4, 2] + >>> mode(input_list) + [2, 4] + >>> input_list = ["x", "y", "y", "z"] + >>> mode(input_list) + ['y'] + >>> input_list = ["x", "x" , "y", "y", "z"] + >>> mode(input_list) + ['x', 'y'] """ - # 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: 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 not result: + return [] + y = max(result) # Gets the maximum value in the result list. + # Gets values of modes + result = {input_list[i] for i, value in enumerate(result) if value == y} + return sorted(result) 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)) + import doctest + + doctest.testmod() From f37d415227a21017398144a090a66f1c690705eb Mon Sep 17 00:00:00 2001 From: Anderson Torres Date: Fri, 4 Jun 2021 17:28:26 -0300 Subject: [PATCH 1496/2908] Add new algorithm for Armstrong numbers (#4474) * Add a new algorithm for Armstrong numbers * FAILING = (-153, -1, 0, 1.2, 200, "A", [], {}, None) Co-authored-by: Christian Clauss --- maths/armstrong_numbers.py | 70 +++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index af25688dbacc..ce8c62182fd9 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -1,26 +1,24 @@ """ -An Armstrong number is equal to the sum of its own digits each raised -to the power of the number of digits. +An Armstrong number is equal to the sum of its own digits each raised to the +power of the number of 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. + +Armstrong numbers are also called Narcissistic numbers and Pluperfect numbers. + +On-Line Encyclopedia of Integer Sequences entry: https://oeis.org/A005188 """ +PASSING = (1, 153, 370, 371, 1634, 24678051, 115132219018763992565095597973971522401) +FAILING = (-153, -1, 0, 1.2, 200, "A", [], {}, None) def armstrong_number(n: int) -> bool: """ Return True if n is an Armstrong number or False if it is not. - >>> armstrong_number(153) + >>> all(armstrong_number(n) for n in PASSING) True - >>> armstrong_number(200) - False - >>> armstrong_number(1634) - True - >>> armstrong_number(0) - False - >>> armstrong_number(-1) - False - >>> armstrong_number(1.2) + >>> any(armstrong_number(n) for n in FAILING) False """ if not isinstance(n, int) or n < 1: @@ -43,15 +41,46 @@ def armstrong_number(n: int) -> bool: return n == sum -def narcissistic_number(n: int) -> bool: - """Return True if n is a narcissistic number or False if it is not""" +def pluperfect_number(n: int) -> bool: + """Return True if n is a pluperfect number or False if it is not + + >>> all(armstrong_number(n) for n in PASSING) + True + >>> any(armstrong_number(n) for n in FAILING) + False + """ + if not isinstance(n, int) or n < 1: + return False + + # Init a "histogram" of the digits + digit_histogram = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + digit_total = 0 + sum = 0 + temp = n + while temp > 0: + temp, rem = divmod(temp, 10) + digit_histogram[rem] += 1 + digit_total += 1 + + for (cnt, i) in zip(digit_histogram, range(len(digit_histogram))): + sum += cnt * i ** digit_total + + return n == sum - 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 narcissistic_number(n: int) -> bool: + """Return True if n is a narcissistic number or False if it is not. + + >>> all(armstrong_number(n) for n in PASSING) + True + >>> any(armstrong_number(n) for n in FAILING) + False + """ + if not isinstance(n, int) or n < 1: + return False + expo = len(str(n)) # the power that all digits will be raised to + # check if sum of each digit multiplied expo times is equal to number + return n == sum(int(i) ** expo for i in str(n)) def main(): @@ -61,6 +90,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.") + print(f"{num} is {'' if pluperfect_number(num) else 'not '}an Armstrong number.") if __name__ == "__main__": From b743e442599a5bf7e1cb14d9dc41bd17bde1504c Mon Sep 17 00:00:00 2001 From: Aniruddha Bhattacharjee Date: Wed, 9 Jun 2021 02:19:33 +0530 Subject: [PATCH 1497/2908] Wavelet tree (#4267) * Added the matrix_exponentiation.py file in maths directory * Implemented the requested changes * Update matrix_exponentiation.py * resolve merge conflict with upstream branch * add new line at end of file * add wavelet_tree * fix isort issue * updating DIRECTORY.md * fix variable names in wavelet_tree and correct typo * Add type hints and variable renaming * Update data_structures/binary_tree/wavelet_tree.py Add doctests to placate the algorithm-bot, thanks to @cclauss. Co-authored-by: Christian Clauss * Move doctest to individual functions and reformat code * Move common test array to the global scope and reuse in tests * MMove test array to global scope and minor linting changes * Correct the failing pytest tests * MUse built-in list for type annotation * Update wavelet_tree.py * types-requests * updating DIRECTORY.md * Update wavelet_tree.py * # type: ignore * # type: ignore * Update decrypt_caesar_with_chi_squared.py * , * Update decrypt_caesar_with_chi_squared.py Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Aniruddha Bhattacharjee --- DIRECTORY.md | 2 + ciphers/decrypt_caesar_with_chi_squared.py | 7 +- data_structures/binary_tree/wavelet_tree.py | 206 ++++++++++++++++++++ requirements.txt | 1 + scripts/validate_solutions.py | 2 +- 5 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 data_structures/binary_tree/wavelet_tree.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 9905753b2d24..e5ca6d62fe45 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -136,6 +136,7 @@ * [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) + * [Wavelet Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/wavelet_tree.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) @@ -232,6 +233,7 @@ ## 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) + * [Catalan Numbers](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/catalan_numbers.py) * [Climbing Stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.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) diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index e7faeae73773..7e3705b8f71f 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -222,9 +222,10 @@ def decrypt_caesar_with_chi_squared( # Get the most likely cipher by finding the cipher with the smallest chi squared # statistic - most_likely_cipher: int = min( - chi_squared_statistic_values, key=chi_squared_statistic_values.get - ) # type: ignore # First argument to `min` is not optional + most_likely_cipher: int = min( # type: ignore + chi_squared_statistic_values, # type: ignore + key=chi_squared_statistic_values.get, # type: ignore + ) # type: ignore # Get all the data from the most likely cipher (key, decoded message) ( diff --git a/data_structures/binary_tree/wavelet_tree.py b/data_structures/binary_tree/wavelet_tree.py new file mode 100644 index 000000000000..1607244f74ed --- /dev/null +++ b/data_structures/binary_tree/wavelet_tree.py @@ -0,0 +1,206 @@ +""" +Wavelet tree is a data-structure designed to efficiently answer various range queries +for arrays. Wavelets trees are different from other binary trees in the sense that +the nodes are split based on the actual values of the elements and not on indices, +such as the with segment trees or fenwick trees. You can read more about them here: +1. https://users.dcc.uchile.cl/~jperez/papers/ioiconf16.pdf +2. https://www.youtube.com/watch?v=4aSv9PcecDw&t=811s +3. https://www.youtube.com/watch?v=CybAgVF-MMc&t=1178s +""" + +from typing import Optional + +test_array = [2, 1, 4, 5, 6, 0, 8, 9, 1, 2, 0, 6, 4, 2, 0, 6, 5, 3, 2, 7] + + +class Node: + def __init__(self, length: int) -> None: + self.minn: int = -1 + self.maxx: int = -1 + self.map_left: list[int] = [-1] * length + self.left: Optional[Node] = None + self.right: Optional[Node] = None + + def __repr__(self) -> str: + """ + >>> node = Node(length=27) + >>> repr(node) + 'min_value: -1, max_value: -1' + >>> repr(node) == str(node) + True + """ + return f"min_value: {self.minn}, max_value: {self.maxx}" + + +def build_tree(arr: list[int]) -> Node: + """ + Builds the tree for arr and returns the root + of the constructed tree + + >>> build_tree(test_array) + min_value: 0, max_value: 9 + """ + root = Node(len(arr)) + root.minn, root.maxx = min(arr), max(arr) + # Leaf node case where the node contains only one unique value + if root.minn == root.maxx: + return root + """ + Take the mean of min and max element of arr as the pivot and + partition arr into left_arr and right_arr with all elements <= pivot in the + left_arr and the rest in right_arr, maintaining the order of the elements, + then recursively build trees for left_arr and right_arr + """ + pivot = (root.minn + root.maxx) // 2 + left_arr, right_arr = [], [] + for index, num in enumerate(arr): + if num <= pivot: + left_arr.append(num) + else: + right_arr.append(num) + root.map_left[index] = len(left_arr) + root.left = build_tree(left_arr) + root.right = build_tree(right_arr) + return root + + +def rank_till_index(node: Node, num: int, index: int) -> int: + """ + Returns the number of occurrences of num in interval [0, index] in the list + + >>> root = build_tree(test_array) + >>> rank_till_index(root, 6, 6) + 1 + >>> rank_till_index(root, 2, 0) + 1 + >>> rank_till_index(root, 1, 10) + 2 + >>> rank_till_index(root, 17, 7) + 0 + >>> rank_till_index(root, 0, 9) + 1 + """ + if index < 0: + return 0 + # Leaf node cases + if node.minn == node.maxx: + return index + 1 if node.minn == num else 0 + pivot = (node.minn + node.maxx) // 2 + if num <= pivot: + # go the left subtree and map index to the left subtree + return rank_till_index(node.left, num, node.map_left[index] - 1) + else: + # go to the right subtree and map index to the right subtree + return rank_till_index(node.right, num, index - node.map_left[index]) + + +def rank(node: Node, num: int, start: int, end: int) -> int: + """ + Returns the number of occurrences of num in interval [start, end] in the list + + >>> root = build_tree(test_array) + >>> rank(root, 6, 3, 13) + 2 + >>> rank(root, 2, 0, 19) + 4 + >>> rank(root, 9, 2 ,2) + 0 + >>> rank(root, 0, 5, 10) + 2 + """ + if start > end: + return 0 + rank_till_end = rank_till_index(node, num, end) + rank_before_start = rank_till_index(node, num, start - 1) + return rank_till_end - rank_before_start + + +def quantile(node: Node, index: int, start: int, end: int) -> int: + """ + Returns the index'th smallest element in interval [start, end] in the list + index is 0-indexed + + >>> root = build_tree(test_array) + >>> quantile(root, 2, 2, 5) + 5 + >>> quantile(root, 5, 2, 13) + 4 + >>> quantile(root, 0, 6, 6) + 8 + >>> quantile(root, 4, 2, 5) + -1 + """ + if index > (end - start) or start > end: + return -1 + # Leaf node case + if node.minn == node.maxx: + return node.minn + # Number of elements in the left subtree in interval [start, end] + num_elements_in_left_tree = node.map_left[end] - ( + node.map_left[start - 1] if start else 0 + ) + if num_elements_in_left_tree > index: + return quantile( + node.left, + index, + (node.map_left[start - 1] if start else 0), + node.map_left[end] - 1, + ) + else: + return quantile( + node.right, + index - num_elements_in_left_tree, + start - (node.map_left[start - 1] if start else 0), + end - node.map_left[end], + ) + + +def range_counting( + node: Node, start: int, end: int, start_num: int, end_num: int +) -> int: + """ + Returns the number of elememts in range [start_num, end_num] + in interval [start, end] in the list + + >>> root = build_tree(test_array) + >>> range_counting(root, 1, 10, 3, 7) + 3 + >>> range_counting(root, 2, 2, 1, 4) + 1 + >>> range_counting(root, 0, 19, 0, 100) + 20 + >>> range_counting(root, 1, 0, 1, 100) + 0 + >>> range_counting(root, 0, 17, 100, 1) + 0 + """ + if ( + start > end + or start_num > end_num + or node.minn > end_num + or node.maxx < start_num + ): + return 0 + if start_num <= node.minn and node.maxx <= end_num: + return end - start + 1 + left = range_counting( + node.left, + (node.map_left[start - 1] if start else 0), + node.map_left[end] - 1, + start_num, + end_num, + ) + right = range_counting( + node.right, + start - (node.map_left[start - 1] if start else 0), + end - node.map_left[end], + start_num, + end_num, + ) + return left + right + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/requirements.txt b/requirements.txt index 8bbb8d524ed4..4867de26f8f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,5 @@ sklearn statsmodels sympy tensorflow +types-requests xgboost diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index 68461dca6710..ca4af5261a8f 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -21,7 +21,7 @@ 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) + module = importlib.util.module_from_spec(spec) # type: ignore spec.loader.exec_module(module) # type: ignore return module From c824b90ead698da4f10ac38e431844d96af109b6 Mon Sep 17 00:00:00 2001 From: GDWR <57012020+GDWR@users.noreply.github.com> Date: Thu, 10 Jun 2021 17:44:41 +0100 Subject: [PATCH 1498/2908] Remove redundent function in Backtracking Sudoku (#4499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove redundent function After reviewing this code, I've noticed that the `is_completed` function is a redundant operation. Increasing the number of loops required for each step of the sudoku solver. This should remove n² operations where n is the width of the grid. * Update sudoku.py Remove additional newline --- backtracking/sudoku.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index 3bfaddd6e56f..593fa52d6d8a 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -59,27 +59,6 @@ def is_safe(grid: Matrix, row: int, column: int, n: int) -> bool: return True -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. - - >>> 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 - """ - return all(all(cell != 0 for cell in row) for row in grid) - - def find_empty_location(grid: Matrix) -> Optional[Tuple[int, int]]: """ This function finds an empty location so that we can assign a number @@ -111,12 +90,7 @@ def sudoku(grid: Matrix) -> Optional[Matrix]: >>> sudoku(no_solution) is None True """ - - if is_completed(grid): - return grid - - location = find_empty_location(grid) - if location is not None: + if location := find_empty_location(grid): row, column = location else: # If the location is ``None``, then the grid is solved. From 977511b3a3711ad9067cc1e8478c696e9f5f157d Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Thu, 10 Jun 2021 23:06:41 +0600 Subject: [PATCH 1499/2908] Add/fix mypy type annotations at BFS, DFS in graphs (#4488) --- graphs/breadth_first_search.py | 4 ++-- graphs/depth_first_search.py | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index ee9855bd0c2d..305db01e19e4 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -2,12 +2,12 @@ """ Author: OMKAR PATHAK """ -from typing import Set +from typing import Dict, List, Set class Graph: def __init__(self) -> None: - self.vertices = {} + self.vertices: Dict[int, List[int]] = {} def print_graph(self) -> None: """ diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 907cc172f253..5d74a6db9c6b 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -2,20 +2,21 @@ from __future__ import annotations +from typing import Set -def depth_first_search(graph: dict, start: str) -> set[int]: + +def depth_first_search(graph: dict, start: str) -> Set[str]: """Depth First Search on Graph :param graph: directed graph in dictionary format - :param vertex: starting vertex as a string + :param start: starting vertex as a string :returns: the trace of the search - >>> G = { "A": ["B", "C", "D"], "B": ["A", "D", "E"], + >>> input_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"))) + >>> all(x in output_G for x in list(depth_first_search(input_G, "A"))) True - >>> all(x in output_G for x in list(depth_first_search(G, "G"))) + >>> all(x in output_G for x in list(depth_first_search(input_G, "G"))) True """ explored, stack = set(start), [start] From daeb6a7e08c3350f0fb08e9fa787ab28a3805f69 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 10 Jun 2021 22:48:40 +0530 Subject: [PATCH 1500/2908] fix(action): delete approve workflow as it does not work (#4453) --- .github/workflows/approve_workflow_run.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/approve_workflow_run.yml diff --git a/.github/workflows/approve_workflow_run.yml b/.github/workflows/approve_workflow_run.yml deleted file mode 100644 index 7c5e64452d5f..000000000000 --- a/.github/workflows/approve_workflow_run.yml +++ /dev/null @@ -1,22 +0,0 @@ -# https://docs.github.com/en/rest/reference/actions#approve-a-workflow-run-for-a-fork-pull-request - -name: "Approve Workflow Run" - -on: - workflow_run: - workflows: ['build', 'project_euler', 'pre-commit', 'directory_writer'] - types: - - completed - -jobs: - approve: - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'action_required' }} - steps: - - name: "Automatically approve a workflow run" - run: | - curl \ - --request POST \ - --header "Accept: application/vnd.github.v3+json" \ - --header "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - --url "https://api.github.com/repos/$%7B%7B%20github.repository%20%7D%7D/actions/runs/$%7B%7B%20github.event.workflow_run.id%20%7D%7D/approve" From 10d38eae6746c451d64ac592200b647480b20b5a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 13 Jun 2021 06:29:06 +0200 Subject: [PATCH 1501/2908] CONTRIBUTING.md: Write for current Python (#4507) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d93b5db67fe8..13d330a90dc5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ pre-commit run --all-files --show-diff-on-failure We want your work to be readable by others; therefore, we encourage you to note the following: -- 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 write in Python 3.9+. 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 95a4957d9ed3913c9d73bbafa9f398afb31a839d Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Sun, 13 Jun 2021 23:19:44 +0500 Subject: [PATCH 1502/2908] Luhn algorithm (#4487) * Luhn algorithm Perform Luhn validation on input string Algorithm: * Double every other digit starting from 2nd last digit. * Subtract 9 if number is greater than 9. * Sum the numbers https://en.wikipedia.org/wiki/Luhn_algorithm * Update DIRECTORY.md * Update luhn.py * Update luhn.py * Update luhn.py * Update luhn.py * Update DIRECTORY.md --- hashes/luhn.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 hashes/luhn.py diff --git a/hashes/luhn.py b/hashes/luhn.py new file mode 100644 index 000000000000..69e7b4ccf59b --- /dev/null +++ b/hashes/luhn.py @@ -0,0 +1,46 @@ +""" Luhn Algorithm """ +from typing import List + + +def is_luhn(string: str) -> bool: + """ + Perform Luhn validation on input string + Algorithm: + * Double every other digit starting from 2nd last digit. + * Subtract 9 if number is greater than 9. + * Sum the numbers + * + >>> test_cases = [79927398710, 79927398711, 79927398712, 79927398713, + ... 79927398714, 79927398715, 79927398716, 79927398717, 79927398718, + ... 79927398719] + >>> test_cases = list(map(str, test_cases)) + >>> list(map(is_luhn, test_cases)) + [False, False, False, True, False, False, False, False, False, False] + """ + check_digit: int + _vector: List[str] = list(string) + __vector, check_digit = _vector[:-1], int(_vector[-1]) + vector: List[int] = [*map(int, __vector)] + + vector.reverse() + for idx, i in enumerate(vector): + + if idx & 1 == 0: + doubled: int = vector[idx] * 2 + if doubled > 9: + doubled -= 9 + + check_digit += doubled + else: + check_digit += i + + if (check_digit) % 10 == 0: + return True + return False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + assert is_luhn("79927398713") From 0eabdb54b181f3a0ef28c912fb7ff5b99147093d Mon Sep 17 00:00:00 2001 From: Grigoriy Hanin <43445998+haningrisha@users.noreply.github.com> Date: Mon, 14 Jun 2021 23:39:51 +0300 Subject: [PATCH 1503/2908] Average median type hint (#4483) * Update average_median.py * Wikipediad link added --- maths/average_median.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/maths/average_median.py b/maths/average_median.py index 0257e3f76f1a..57e01368b7b2 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -1,6 +1,10 @@ -def median(nums): +from typing import Union + + +def median(nums: Union[int, float]) -> Union[int, float]: """ Find median of a list of numbers. + Wiki: https://en.wikipedia.org/wiki/Median >>> median([0]) 0 From 7d19d54f6f247bb16b4299d2a20548d6a279d635 Mon Sep 17 00:00:00 2001 From: Grigoriy Hanin <43445998+haningrisha@users.noreply.github.com> Date: Wed, 16 Jun 2021 09:33:23 +0300 Subject: [PATCH 1504/2908] Average mean refactor (#4485) * Average mean refactor Added doctests and type hints to average_mean * Wiki link added * Empty list check added Empty list check added Type hint changed to typing.List --- maths/average_mean.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/maths/average_mean.py b/maths/average_mean.py index 4beca1f741a0..e02e307f20c8 100644 --- a/maths/average_mean.py +++ b/maths/average_mean.py @@ -1,20 +1,28 @@ -"""Find mean of a list of numbers.""" +from typing import List -def average(nums): - """Find mean of a list of numbers.""" - return sum(nums) / len(nums) - - -def test_average(): +def mean(nums: List) -> float: """ - >>> test_average() + Find mean of a list of numbers. + Wiki: https://en.wikipedia.org/wiki/Mean + + >>> mean([3, 6, 9, 12, 15, 18, 21]) + 12.0 + >>> mean([5, 10, 15, 20, 25, 30, 35]) + 20.0 + >>> mean([1, 2, 3, 4, 5, 6, 7, 8]) + 4.5 + >>> mean([]) + Traceback (most recent call last): + ... + ValueError: List is empty """ - 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 not nums: + raise ValueError("List is empty") + return sum(nums) / len(nums) if __name__ == "__main__": - """Call average module to find mean of a specific list of numbers.""" - print(average([2, 4, 6, 8, 20, 50, 70])) + import doctest + + doctest.testmod() From 4f9ee4330aedd20485a7923c634b3d27f7a82c8b Mon Sep 17 00:00:00 2001 From: Grigoriy Hanin <43445998+haningrisha@users.noreply.github.com> Date: Wed, 16 Jun 2021 09:34:32 +0300 Subject: [PATCH 1505/2908] basic_maths input check (#4486) * Prime factor input check * number_of_divisors input check * sum_of_divisors input check --- maths/basic_maths.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/maths/basic_maths.py b/maths/basic_maths.py index 07ee3b3df296..47d3d91b397d 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -6,7 +6,17 @@ def prime_factors(n: int) -> list: """Find Prime Factors. >>> prime_factors(100) [2, 2, 5, 5] + >>> prime_factors(0) + Traceback (most recent call last): + ... + ValueError: Only positive integers have prime factors + >>> prime_factors(-10) + Traceback (most recent call last): + ... + ValueError: Only positive integers have prime factors """ + if n <= 0: + raise ValueError("Only positive integers have prime factors") pf = [] while n % 2 == 0: pf.append(2) @@ -24,7 +34,17 @@ def number_of_divisors(n: int) -> int: """Calculate Number of Divisors of an Integer. >>> number_of_divisors(100) 9 + >>> number_of_divisors(0) + Traceback (most recent call last): + ... + ValueError: Only positive numbers are accepted + >>> number_of_divisors(-10) + Traceback (most recent call last): + ... + ValueError: Only positive numbers are accepted """ + if n <= 0: + raise ValueError("Only positive numbers are accepted") div = 1 temp = 1 while n % 2 == 0: @@ -44,7 +64,17 @@ def sum_of_divisors(n: int) -> int: """Calculate Sum of Divisors. >>> sum_of_divisors(100) 217 + >>> sum_of_divisors(0) + Traceback (most recent call last): + ... + ValueError: Only positive numbers are accepted + >>> sum_of_divisors(-10) + Traceback (most recent call last): + ... + ValueError: Only positive numbers are accepted """ + if n <= 0: + raise ValueError("Only positive numbers are accepted") s = 1 temp = 1 while n % 2 == 0: @@ -74,7 +104,6 @@ def euler_phi(n: int) -> int: if __name__ == "__main__": - print(prime_factors(100)) - print(number_of_divisors(100)) - print(sum_of_divisors(100)) - print(euler_phi(100)) + import doctest + + doctest.testmod() From 2899cdac207822f6f3ca454f27f55d7b73eaac9e Mon Sep 17 00:00:00 2001 From: Harshit Agarwal <43147421+9harshit@users.noreply.github.com> Date: Thu, 24 Jun 2021 11:58:23 +0530 Subject: [PATCH 1506/2908] feat: CNN classification added to computer vision (#4350) * cnn classification file * black formatted * flake8 corrected * added cnn classification * Delete requirements.txt * Update cnn_classification.py * Create cnn_classification.py * using keras from tensorflow only * update tensorflow * Update cnn_classification.py * Delete computer_vision/cnn_classification directory --- computer_vision/cnn_classification.py | 98 +++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 computer_vision/cnn_classification.py diff --git a/computer_vision/cnn_classification.py b/computer_vision/cnn_classification.py new file mode 100644 index 000000000000..6d4f19639c24 --- /dev/null +++ b/computer_vision/cnn_classification.py @@ -0,0 +1,98 @@ +""" +Convolutional Neural Network + +Objective : To train a CNN model detect if TB is present in Lung X-ray or not. + +Resources CNN Theory : + https://en.wikipedia.org/wiki/Convolutional_neural_network +Resources Tensorflow : https://www.tensorflow.org/tutorials/images/cnn + +Download dataset from : +https://lhncbc.nlm.nih.gov/LHC-publications/pubs/TuberculosisChestXrayImageDataSets.html + +1. Download the dataset folder and create two folder training set and test set +in the parent dataste folder +2. Move 30-40 image from both TB positive and TB Negative folder +in the test set folder +3. The labels of the iamges will be extracted from the folder name +the image is present in. + +""" + +# Part 1 - Building the CNN + +import numpy as np + +# Importing the Keras libraries and packages +import tensorflow as tf +from tensorflow.keras import layers, models + +if __name__ == "__main__": + + # Initialising the CNN + classifier = models.Sequential() + + # Step 1 - Convolution + classifier.add( + layers.Conv2D(32, (3, 3), input_shape=(64, 64, 3), activation="relu") + ) + + # Step 2 - Pooling + classifier.add(layers.MaxPooling2D(pool_size=(2, 2))) + + # Adding a second convolutional layer + classifier.add(layers.Conv2D(32, (3, 3), activation="relu")) + classifier.add(layers.MaxPooling2D(pool_size=(2, 2))) + + # Step 3 - Flattening + classifier.add(layers.Flatten()) + + # Step 4 - Full connection + classifier.add(layers.Dense(units=128, activation="relu")) + classifier.add(layers.Dense(units=1, activation="sigmoid")) + + # Compiling the CNN + classifier.compile( + optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"] + ) + + # Part 2 - Fitting the CNN to the images + + # Load Trained model weights + + # from keras.models import load_model + # regressor=load_model('cnn.h5') + + train_datagen = tf.keras.preprocessing.image.ImageDataGenerator( + rescale=1.0 / 255, shear_range=0.2, zoom_range=0.2, horizontal_flip=True + ) + + test_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1.0 / 255) + + training_set = train_datagen.flow_from_directory( + "dataset/training_set", target_size=(64, 64), batch_size=32, class_mode="binary" + ) + + test_set = test_datagen.flow_from_directory( + "dataset/test_set", target_size=(64, 64), batch_size=32, class_mode="binary" + ) + + classifier.fit_generator( + training_set, steps_per_epoch=5, epochs=30, validation_data=test_set + ) + + classifier.save("cnn.h5") + + # Part 3 - Making new predictions + + test_image = tf.keras.preprocessing.image.load_img( + "dataset/single_prediction/image.png", target_size=(64, 64) + ) + test_image = tf.keras.preprocessing.image.img_to_array(test_image) + test_image = np.expand_dims(test_image, axis=0) + result = classifier.predict(test_image) + training_set.class_indices + if result[0][0] == 0: + prediction = "Normal" + if result[0][0] == 1: + prediction = "Abnormality detected" From 3ea5a13334f2d573167456c0c9ee4c90497c9466 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Thu, 24 Jun 2021 12:50:23 +0600 Subject: [PATCH 1507/2908] Add doctest and fix mypy type annotation in bellman ford (#4506) --- graphs/bellman_ford.py | 85 +++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index ace7985647bb..d6d6b2ac7349 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -1,56 +1,73 @@ from __future__ import annotations -def printDist(dist, V): - 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 print_distance(distance: list[float], src): + print(f"Vertex\tShortest Distance from vertex {src}") + for i, d in enumerate(distance): + print(f"{i}\t\t{d}") -def BellmanFord(graph: list[dict[str, int]], V: int, E: int, src: int) -> int: +def check_negative_cycle( + graph: list[dict[str, int]], distance: list[float], edge_count: int +): + for j in range(edge_count): + u, v, w = [graph[j][k] for k in ["src", "dst", "weight"]] + if distance[u] != float("inf") and distance[u] + w < distance[v]: + return True + return False + + +def bellman_ford( + graph: list[dict[str, int]], vertex_count: int, edge_count: int, src: int +) -> list[float]: """ Returns shortest paths from a vertex src to all other vertices. + >>> edges = [(2, 1, -10), (3, 2, 3), (0, 3, 5), (0, 1, 4)] + >>> g = [{"src": s, "dst": d, "weight": w} for s, d, w in edges] + >>> bellman_ford(g, 4, 4, 0) + [0.0, -2.0, 8.0, 5.0] + >>> g = [{"src": s, "dst": d, "weight": w} for s, d, w in edges + [(1, 3, 5)]] + >>> bellman_ford(g, 4, 5, 0) + Traceback (most recent call last): + ... + Exception: Negative cycle found """ - mdist = [float("inf") for i in range(V)] - mdist[src] = 0.0 + distance = [float("inf")] * vertex_count + distance[src] = 0.0 - for i in range(V - 1): - for j in range(E): - u = graph[j]["src"] - v = graph[j]["dst"] - w = graph[j]["weight"] + for i in range(vertex_count - 1): + for j in range(edge_count): + u, v, w = [graph[j][k] for k in ["src", "dst", "weight"]] - if mdist[u] != float("inf") and mdist[u] + w < mdist[v]: - mdist[v] = mdist[u] + w - for j in range(E): - u = graph[j]["src"] - v = graph[j]["dst"] - w = graph[j]["weight"] + if distance[u] != float("inf") and distance[u] + w < distance[v]: + distance[v] = distance[u] + w - if mdist[u] != float("inf") and mdist[u] + w < mdist[v]: - print("Negative cycle found. Solution not possible.") - return + negative_cycle_exists = check_negative_cycle(graph, distance, edge_count) + if negative_cycle_exists: + raise Exception("Negative cycle found") - printDist(mdist, V) - return src + return distance if __name__ == "__main__": + import doctest + + doctest.testmod() + V = int(input("Enter number of vertices: ").strip()) E = int(input("Enter number of edges: ").strip()) - graph = [dict() for j in range(E)] + graph: list[dict[str, int]] = [dict() for j in range(E)] for i in range(E): - graph[i][i] = 0.0 + print("Edge ", i + 1) + src, dest, weight = [ + int(x) + for x in input("Enter source, destination, weight: ").strip().split(" ") + ] + graph[i] = {"src": src, "dst": dest, "weight": 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[i] = {"src": src, "dst": dst, "weight": weight} - - gsrc = int(input("\nEnter shortest path source:").strip()) - BellmanFord(graph, V, E, gsrc) + source = int(input("\nEnter shortest path source:").strip()) + shortest_distance = bellman_ford(graph, V, E, source) + print_distance(shortest_distance, 0) From 62d44188516521fd14e9a4e1f18957ea4eaeeb37 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Tue, 29 Jun 2021 17:44:35 +0600 Subject: [PATCH 1508/2908] Fix mypy error and add more doctest on bfs_shortest_path (#4512) --- graphs/bfs_shortest_path.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/graphs/bfs_shortest_path.py b/graphs/bfs_shortest_path.py index 754ba403537e..b0c8d353ba04 100644 --- a/graphs/bfs_shortest_path.py +++ b/graphs/bfs_shortest_path.py @@ -4,7 +4,7 @@ Manual test: python bfs_shortest_path.py """ -graph = { +demo_graph = { "A": ["B", "C", "E"], "B": ["A", "D", "E"], "C": ["A", "F", "G"], @@ -15,7 +15,7 @@ } -def bfs_shortest_path(graph: dict, start, goal) -> str: +def bfs_shortest_path(graph: dict, start, goal) -> list[str]: """Find shortest path between `start` and `goal` nodes. Args: graph (dict): node/list of neighboring nodes key/value pairs. @@ -25,8 +25,12 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: 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") + >>> bfs_shortest_path(demo_graph, "G", "D") ['G', 'C', 'A', 'B', 'D'] + >>> bfs_shortest_path(demo_graph, "G", "G") + ['G'] + >>> bfs_shortest_path(demo_graph, "G", "Unknown") + [] """ # keep track of explored nodes explored = set() @@ -35,7 +39,7 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: # return path if start is goal if start == goal: - return "That was easy! Start = goal" + return [start] # keeps looping until all possible paths have been checked while queue: @@ -59,7 +63,7 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: explored.add(node) # in case there's no path between the 2 nodes - return "So sorry, but a connecting path doesn't exist :(" + return [] def bfs_shortest_path_distance(graph: dict, start, target) -> int: @@ -72,11 +76,11 @@ def bfs_shortest_path_distance(graph: dict, start, target) -> int: Number of edges in shortest path between `start` and `target` nodes. -1 if no path exists. Example: - >>> bfs_shortest_path_distance(graph, "G", "D") + >>> bfs_shortest_path_distance(demo_graph, "G", "D") 4 - >>> bfs_shortest_path_distance(graph, "A", "A") + >>> bfs_shortest_path_distance(demo_graph, "A", "A") 0 - >>> bfs_shortest_path_distance(graph, "A", "H") + >>> bfs_shortest_path_distance(demo_graph, "A", "Unknown") -1 """ if not graph or start not in graph or target not in graph: @@ -102,5 +106,5 @@ def bfs_shortest_path_distance(graph: dict, start, target) -> int: 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 + print(bfs_shortest_path(demo_graph, "G", "D")) # returns ['G', 'C', 'A', 'B', 'D'] + print(bfs_shortest_path_distance(demo_graph, "G", "D")) # returns 4 From 86baec0bc9d790e2be6f49492e2e4f0f788060af Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Fri, 2 Jul 2021 17:52:26 +0600 Subject: [PATCH 1509/2908] Fix mypy errors at bfs_zero_one_shortest_path (#4521) --- graphs/bfs_zero_one_shortest_path.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/graphs/bfs_zero_one_shortest_path.py b/graphs/bfs_zero_one_shortest_path.py index a725fae7e48f..a68b5602c2d1 100644 --- a/graphs/bfs_zero_one_shortest_path.py +++ b/graphs/bfs_zero_one_shortest_path.py @@ -1,6 +1,7 @@ from collections import deque +from collections.abc import Iterator from dataclasses import dataclass -from typing import Iterator, List +from typing import Optional, Union """ Finding the shortest path in 0-1-graph in O(E + V) which is faster than dijkstra. @@ -21,7 +22,7 @@ class AdjacencyList: """Graph adjacency list.""" def __init__(self, size: int): - self._graph: List[List[Edge]] = [[] for _ in range(size)] + self._graph: list[list[Edge]] = [[] for _ in range(size)] self._size = size def __getitem__(self, vertex: int) -> Iterator[Edge]: @@ -58,7 +59,7 @@ def add_edge(self, from_vertex: int, to_vertex: int, weight: int): self._graph[from_vertex].append(Edge(to_vertex, weight)) - def get_shortest_path(self, start_vertex: int, finish_vertex: int) -> int: + def get_shortest_path(self, start_vertex: int, finish_vertex: int) -> Optional[int]: """ Return the shortest distance from start_vertex to finish_vertex in 0-1-graph. 1 1 1 @@ -106,18 +107,21 @@ def get_shortest_path(self, start_vertex: int, finish_vertex: int) -> int: ValueError: No path from start_vertex to finish_vertex. """ queue = deque([start_vertex]) - distances = [None for i in range(self.size)] + distances: list[Union[int, None]] = [None] * self.size distances[start_vertex] = 0 while queue: current_vertex = queue.popleft() current_distance = distances[current_vertex] + if current_distance is None: + continue for edge in self[current_vertex]: new_distance = current_distance + edge.weight + dest_vertex_distance = distances[edge.destination_vertex] if ( - distances[edge.destination_vertex] is not None - and new_distance >= distances[edge.destination_vertex] + isinstance(dest_vertex_distance, int) + and new_distance >= dest_vertex_distance ): continue distances[edge.destination_vertex] = new_distance From 95862303a6527f4bf111e6f3f783fd66b7b426f3 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Mon, 5 Jul 2021 12:23:18 +0600 Subject: [PATCH 1510/2908] Fix mypy at prims_algo_2 (#4527) --- graphs/minimum_spanning_tree_prims2.py | 56 +++++++++++++------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 10ed736c9d17..c3444c36f1cf 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -8,7 +8,9 @@ """ from sys import maxsize -from typing import Dict, Optional, Tuple, Union +from typing import Generic, Optional, TypeVar + +T = TypeVar("T") def get_parent_position(position: int) -> int: @@ -43,7 +45,7 @@ def get_child_right_position(position: int) -> int: return (2 * position) + 2 -class MinPriorityQueue: +class MinPriorityQueue(Generic[T]): """ Minimum Priority Queue Class @@ -80,9 +82,9 @@ class MinPriorityQueue: """ def __init__(self) -> None: - self.heap = [] - self.position_map = {} - self.elements = 0 + self.heap: list[tuple[T, int]] = [] + self.position_map: dict[T, int] = {} + self.elements: int = 0 def __len__(self) -> int: return self.elements @@ -94,14 +96,14 @@ 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: + def push(self, elem: T, 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]: + def extract_min(self) -> T: # Remove and return the element with lowest weight (highest priority) if self.elements > 1: self._swap_nodes(0, self.elements - 1) @@ -113,7 +115,7 @@ def extract_min(self) -> Union[int, str]: self._bubble_down(bubble_down_elem) return elem - def update_key(self, elem: Union[int, str], weight: int) -> None: + def update_key(self, elem: T, weight: int) -> None: # Update the weight of the given key position = self.position_map[elem] self.heap[position] = (elem, weight) @@ -127,7 +129,7 @@ def update_key(self, elem: Union[int, str], weight: int) -> None: else: self._bubble_down(elem) - def _bubble_up(self, elem: Union[int, str]) -> None: + def _bubble_up(self, elem: T) -> None: # Place a node at the proper position (upward movement) [to be used internally # only] curr_pos = self.position_map[elem] @@ -141,7 +143,7 @@ def _bubble_up(self, elem: Union[int, str]) -> None: return self._bubble_up(elem) return - def _bubble_down(self, elem: Union[int, str]) -> None: + def _bubble_down(self, elem: T) -> None: # Place a node at the proper position (downward movement) [to be used # internally only] curr_pos = self.position_map[elem] @@ -182,7 +184,7 @@ def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None: self.position_map[node2_elem] = node1_pos -class GraphUndirectedWeighted: +class GraphUndirectedWeighted(Generic[T]): """ Graph Undirected Weighted Class @@ -192,8 +194,8 @@ class GraphUndirectedWeighted: """ def __init__(self) -> None: - self.connections = {} - self.nodes = 0 + self.connections: dict[T, dict[T, int]] = {} + self.nodes: int = 0 def __repr__(self) -> str: return str(self.connections) @@ -201,15 +203,13 @@ def __repr__(self) -> str: def __len__(self) -> int: return self.nodes - def add_node(self, node: Union[int, str]) -> None: + def add_node(self, node: T) -> 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: + def add_edge(self, node1: T, node2: T, weight: int) -> None: # Add an edge between 2 nodes in the graph self.add_node(node1) self.add_node(node2) @@ -218,8 +218,8 @@ def add_edge( def prims_algo( - graph: GraphUndirectedWeighted, -) -> Tuple[Dict[str, int], Dict[str, Optional[str]]]: + graph: GraphUndirectedWeighted[T], +) -> tuple[dict[T, int], dict[T, Optional[T]]]: """ >>> graph = GraphUndirectedWeighted() @@ -239,10 +239,13 @@ def prims_algo( 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()] + dist: dict[T, int] = {node: maxsize for node in graph.connections} + parent: dict[T, Optional[T]] = {node: None for node in graph.connections} + + priority_queue: MinPriorityQueue[T] = MinPriorityQueue() + for node, weight in dist.items(): + priority_queue.push(node, weight) + if priority_queue.is_empty(): return dist, parent @@ -254,6 +257,7 @@ def prims_algo( 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() @@ -263,9 +267,3 @@ def prims_algo( priority_queue.update_key(neighbour, dist[neighbour]) parent[neighbour] = node return dist, parent - - -if __name__ == "__main__": - from doctest import testmod - - testmod() From 4412eafaac315764ca43e2b63913772776689fa0 Mon Sep 17 00:00:00 2001 From: strambake <86815109+strambake@users.noreply.github.com> Date: Tue, 6 Jul 2021 12:38:33 +0530 Subject: [PATCH 1511/2908] [mypy] Fix mypy error (#4524) --- graphs/scc_kosaraju.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py index 573c1bf5e363..2b34170149bc 100644 --- a/graphs/scc_kosaraju.py +++ b/graphs/scc_kosaraju.py @@ -1,25 +1,28 @@ +from typing import List + + def dfs(u): - global g, r, scc, component, visit, stack + global graph, reversedGraph, scc, component, visit, stack if visit[u]: return visit[u] = True - for v in g[u]: + for v in graph[u]: dfs(v) stack.append(u) def dfs2(u): - global g, r, scc, component, visit, stack + global graph, reversedGraph, scc, component, visit, stack if visit[u]: return visit[u] = True component.append(u) - for v in r[u]: + for v in reversedGraph[u]: dfs2(v) def kosaraju(): - global g, r, scc, component, visit, stack + global graph, reversedGraph, scc, component, visit, stack for i in range(n): dfs(i) visit = [False] * n @@ -36,16 +39,16 @@ def kosaraju(): # 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 + graph: List[List[int]] = [[] for i in range(n)] # graph + reversedGraph: List[List[int]] = [[] 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) + graph[u].append(v) + reversedGraph[v].append(u) - stack = [] - visit = [False] * n - scc = [] - component = [] + stack: List[int] = [] + visit: List[bool] = [False] * n + scc: List[int] = [] + component: List[int] = [] print(kosaraju()) From 256c319ce231eb0a158ec3506e2236d48ca4d6a5 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Thu, 8 Jul 2021 12:46:43 +0600 Subject: [PATCH 1512/2908] Fix mypy errors at kruskal_2 (#4528) --- graphs/minimum_spanning_tree_kruskal2.py | 104 +++++++++++++---------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/graphs/minimum_spanning_tree_kruskal2.py b/graphs/minimum_spanning_tree_kruskal2.py index dfb87efeb89a..0ddb43ce8e6e 100644 --- a/graphs/minimum_spanning_tree_kruskal2.py +++ b/graphs/minimum_spanning_tree_kruskal2.py @@ -1,78 +1,93 @@ from __future__ import annotations +from typing import Generic, TypeVar -class DisjointSetTreeNode: +T = TypeVar("T") + + +class DisjointSetTreeNode(Generic[T]): # Disjoint Set Node to store the parent and rank - def __init__(self, key: int) -> None: - self.key = key + def __init__(self, data: T) -> None: + self.data = data self.parent = self self.rank = 0 -class DisjointSetTree: +class DisjointSetTree(Generic[T]): # Disjoint Set DataStructure - def __init__(self): + def __init__(self) -> None: # map from node name to the node object - self.map = {} + self.map: dict[T, DisjointSetTreeNode[T]] = {} - def make_set(self, x: int) -> None: + def make_set(self, data: T) -> None: # create a new set with x as its member - self.map[x] = DisjointSetTreeNode(x) + self.map[data] = DisjointSetTreeNode(data) - def find_set(self, x: int) -> DisjointSetTreeNode: + def find_set(self, data: T) -> DisjointSetTreeNode[T]: # find the set x belongs to (with path-compression) - elem_ref = self.map[x] + elem_ref = self.map[data] if elem_ref != elem_ref.parent: - elem_ref.parent = self.find_set(elem_ref.parent.key) + elem_ref.parent = self.find_set(elem_ref.parent.data) return elem_ref.parent - def link(self, x: int, y: int) -> None: + def link( + self, node1: DisjointSetTreeNode[T], node2: DisjointSetTreeNode[T] + ) -> None: # helper function for union operation - if x.rank > y.rank: - y.parent = x + if node1.rank > node2.rank: + node2.parent = node1 else: - x.parent = y - if x.rank == y.rank: - y.rank += 1 + node1.parent = node2 + if node1.rank == node2.rank: + node2.rank += 1 - def union(self, x: int, y: int) -> None: + def union(self, data1: T, data2: T) -> None: # merge 2 disjoint sets - self.link(self.find_set(x), self.find_set(y)) + self.link(self.find_set(data1), self.find_set(data2)) -class GraphUndirectedWeighted: - def __init__(self): +class GraphUndirectedWeighted(Generic[T]): + def __init__(self) -> None: # connections: map from the node to the neighbouring nodes (with weights) - self.connections = {} + self.connections: dict[T, dict[T, int]] = {} - def add_node(self, node: int) -> None: + def add_node(self, node: T) -> 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: + def add_edge(self, node1: T, node2: T, 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: + def kruskal(self) -> GraphUndirectedWeighted[T]: # 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() + >>> g1 = GraphUndirectedWeighted[int]() + >>> g1.add_edge(1, 2, 1) + >>> g1.add_edge(2, 3, 2) + >>> g1.add_edge(3, 4, 1) + >>> g1.add_edge(3, 5, 100) # Removed in MST + >>> g1.add_edge(4, 5, 5) + >>> assert 5 in g1.connections[3] + >>> mst = g1.kruskal() >>> assert 5 not in mst.connections[3] + + >>> g2 = GraphUndirectedWeighted[str]() + >>> g2.add_edge('A', 'B', 1) + >>> g2.add_edge('B', 'C', 2) + >>> g2.add_edge('C', 'D', 1) + >>> g2.add_edge('C', 'E', 100) # Removed in MST + >>> g2.add_edge('D', 'E', 5) + >>> assert 'E' in g2.connections["C"] + >>> mst = g2.kruskal() + >>> assert 'E' not in mst.connections['C'] """ # getting the edges in ascending order of weights @@ -84,26 +99,23 @@ def kruskal(self) -> GraphUndirectedWeighted: 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] + disjoint_set = DisjointSetTree[T]() + for node in self.connections: + disjoint_set.make_set(node) + # MST generation num_edges = 0 index = 0 - graph = GraphUndirectedWeighted() + graph = GraphUndirectedWeighted[T]() 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: + parent_u = disjoint_set.find_set(u) + parent_v = disjoint_set.find_set(v) + if parent_u != parent_v: num_edges += 1 graph.add_edge(u, v, w) disjoint_set.union(u, v) return graph - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From 8c13a7786f4fe15af4d133fed14e5e2fb0888926 Mon Sep 17 00:00:00 2001 From: fpringle Date: Thu, 8 Jul 2021 10:35:10 +0200 Subject: [PATCH 1513/2908] feat: add solution for Project Euler problem 144 (#4280) * Added solution for Project Euler problem 144 * updating DIRECTORY.md * Better variable names * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_144/__init__.py | 0 project_euler/problem_144/sol1.py | 101 ++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 project_euler/problem_144/__init__.py create mode 100644 project_euler/problem_144/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e5ca6d62fe45..adc9bb9e4699 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -788,6 +788,8 @@ * [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 144 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_144/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_144/__init__.py b/project_euler/problem_144/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_144/sol1.py b/project_euler/problem_144/sol1.py new file mode 100644 index 000000000000..3f7a766be20f --- /dev/null +++ b/project_euler/problem_144/sol1.py @@ -0,0 +1,101 @@ +""" +In laser physics, a "white cell" is a mirror system that acts as a delay line for the +laser beam. The beam enters the cell, bounces around on the mirrors, and eventually +works its way back out. + +The specific white cell we will be considering is an ellipse with the equation +4x^2 + y^2 = 100 + +The section corresponding to −0.01 ≤ x ≤ +0.01 at the top is missing, allowing the +light to enter and exit through the hole. + +The light beam in this problem starts at the point (0.0,10.1) just outside the white +cell, and the beam first impacts the mirror at (1.4,-9.6). + +Each time the laser beam hits the surface of the ellipse, it follows the usual law of +reflection "angle of incidence equals angle of reflection." That is, both the incident +and reflected beams make the same angle with the normal line at the point of incidence. + +In the figure on the left, the red line shows the first two points of contact between +the laser beam and the wall of the white cell; the blue line shows the line tangent to +the ellipse at the point of incidence of the first bounce. + +The slope m of the tangent line at any point (x,y) of the given ellipse is: m = −4x/y + +The normal line is perpendicular to this tangent line at the point of incidence. + +The animation on the right shows the first 10 reflections of the beam. + +How many times does the beam hit the internal surface of the white cell before exiting? +""" + + +from math import isclose, sqrt + + +def next_point( + point_x: float, point_y: float, incoming_gradient: float +) -> tuple[float, float, float]: + """ + Given that a laser beam hits the interior of the white cell at point + (point_x, point_y) with gradient incoming_gradient, return a tuple (x,y,m1) + where the next point of contact with the interior is (x,y) with gradient m1. + >>> next_point(5.0, 0.0, 0.0) + (-5.0, 0.0, 0.0) + >>> next_point(5.0, 0.0, -2.0) + (0.0, -10.0, 2.0) + """ + # normal_gradient = gradient of line through which the beam is reflected + # outgoing_gradient = gradient of reflected line + normal_gradient = point_y / 4 / point_x + s2 = 2 * normal_gradient / (1 + normal_gradient * normal_gradient) + c2 = (1 - normal_gradient * normal_gradient) / ( + 1 + normal_gradient * normal_gradient + ) + outgoing_gradient = (s2 - c2 * incoming_gradient) / (c2 + s2 * incoming_gradient) + + # to find the next point, solve the simultaeneous equations: + # y^2 + 4x^2 = 100 + # y - b = m * (x - a) + # ==> A x^2 + B x + C = 0 + quadratic_term = outgoing_gradient ** 2 + 4 + linear_term = 2 * outgoing_gradient * (point_y - outgoing_gradient * point_x) + constant_term = (point_y - outgoing_gradient * point_x) ** 2 - 100 + + x_minus = ( + -linear_term - sqrt(linear_term ** 2 - 4 * quadratic_term * constant_term) + ) / (2 * quadratic_term) + x_plus = ( + -linear_term + sqrt(linear_term ** 2 - 4 * quadratic_term * constant_term) + ) / (2 * quadratic_term) + + # two solutions, one of which is our input point + next_x = x_minus if isclose(x_plus, point_x) else x_plus + next_y = point_y + outgoing_gradient * (next_x - point_x) + + return next_x, next_y, outgoing_gradient + + +def solution(first_x_coord: float = 1.4, first_y_coord: float = -9.6) -> int: + """ + Return the number of times that the beam hits the interior wall of the + cell before exiting. + >>> solution(0.00001,-10) + 1 + >>> solution(5, 0) + 287 + """ + num_reflections: int = 0 + point_x: float = first_x_coord + point_y: float = first_y_coord + gradient: float = (10.1 - point_y) / (0.0 - point_x) + + while not (-0.01 <= point_x <= 0.01 and point_y > 0): + point_x, point_y, gradient = next_point(point_x, point_y, gradient) + num_reflections += 1 + + return num_reflections + + +if __name__ == "__main__": + print(f"{solution() = }") From 307ffd8c29d1b2b156c349fde424e62e8493428a Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Mon, 12 Jul 2021 12:10:07 +0600 Subject: [PATCH 1514/2908] Fix mypy errors at bidirectional_bfs (#4531) --- graphs/bidirectional_breadth_first_search.py | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/graphs/bidirectional_breadth_first_search.py b/graphs/bidirectional_breadth_first_search.py index 39d8dc7d4187..9b84ab21bf7f 100644 --- a/graphs/bidirectional_breadth_first_search.py +++ b/graphs/bidirectional_breadth_first_search.py @@ -5,6 +5,9 @@ from __future__ import annotations import time +from typing import Optional + +Path = list[tuple[int, int]] grid = [ [0, 0, 0, 0, 0, 0, 0], @@ -20,7 +23,9 @@ class Node: - def __init__(self, pos_x, pos_y, goal_x, goal_y, parent): + def __init__( + self, pos_x: int, pos_y: int, goal_x: int, goal_y: int, parent: Optional[Node] + ): self.pos_x = pos_x self.pos_y = pos_y self.pos = (pos_y, pos_x) @@ -45,14 +50,14 @@ class BreadthFirstSearch: (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)] """ - def __init__(self, start, goal): + def __init__(self, start: tuple[int, int], goal: tuple[int, int]): 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]]: + def search(self) -> Optional[Path]: while self.node_queue: current_node = self.node_queue.pop(0) @@ -65,8 +70,9 @@ def search(self) -> list[tuple[int]]: for node in successors: self.node_queue.append(node) - if not (self.reached): - return [(self.start.pos)] + if not self.reached: + return [self.start.pos] + return None def get_successors(self, parent: Node) -> list[Node]: """ @@ -87,7 +93,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: Optional[Node]) -> Path: """ Retrace the path from parents to parents until start node """ @@ -119,7 +125,7 @@ def __init__(self, start, goal): self.bwd_bfs = BreadthFirstSearch(goal, start) self.reached = False - def search(self) -> list[tuple[int]]: + def search(self) -> Optional[Path]: 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) @@ -144,10 +150,9 @@ def search(self) -> list[tuple[int]]: if not self.reached: return [self.fwd_bfs.start.pos] + return None - def retrace_bidirectional_path( - self, fwd_node: Node, bwd_node: Node - ) -> list[tuple[int]]: + def retrace_bidirectional_path(self, fwd_node: Node, bwd_node: Node) -> Path: fwd_path = self.fwd_bfs.retrace_path(fwd_node) bwd_path = self.bwd_bfs.retrace_path(bwd_node) bwd_path.pop() From 2cbadc88ab1f3252364465c7660cdb96a377eace Mon Sep 17 00:00:00 2001 From: dpittaluga76 Date: Mon, 12 Jul 2021 03:16:31 -0300 Subject: [PATCH 1515/2908] Improves readability and processing time (#4510) * Removes overuse of lambdas, improves readability and processing time when it finds bitstring to print out. Removes overuse of lambdas, uses dictionary instead. This improves readability and processing time when it finds the bitstring to print out. * Update huffman.py --- compression/huffman.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/compression/huffman.py b/compression/huffman.py index 3a3cbfa4b0c6..b6cc4de1e8e6 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -5,7 +5,7 @@ class Letter: def __init__(self, letter, freq): self.letter = letter self.freq = freq - self.bitstring = "" + self.bitstring = {} def __repr__(self): return f"{self.letter}:{self.freq}" @@ -51,10 +51,10 @@ def build_tree(letters): def traverse_tree(root, bitstring): """ Recursively traverse the Huffman Tree to set each - Letter's bitstring, and return the list of Letters + Letter's bitstring dictionary, and return the list of Letters """ if type(root) is Letter: - root.bitstring = bitstring + root.bitstring[root.letter] = bitstring return [root] letters = [] letters += traverse_tree(root.left, bitstring + "0") @@ -65,20 +65,21 @@ def traverse_tree(root, bitstring): 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 letters dictionary 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}: ") + letters = { + k: v for letter in traverse_tree(root, "") for k, v in letter.bitstring.items() + } + 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(letters[c], end=" ") print() From 7046fdcdc800badef84ee06f801386d62a3914e4 Mon Sep 17 00:00:00 2001 From: bum_fuzzle <72404701+bumfuzzle33@users.noreply.github.com> Date: Mon, 19 Jul 2021 19:36:43 +0530 Subject: [PATCH 1516/2908] fixed #4529 (#4547) fixed an indentation mistake --- hashes/sha1.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hashes/sha1.py b/hashes/sha1.py index cca38b7c3fdc..dde1efc557bb 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -118,13 +118,13 @@ def final_hash(self): 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, - ) + 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) From eca37b1537ce9df227e1c2a915b2841093b9c028 Mon Sep 17 00:00:00 2001 From: Lucifer <63491234+ashish-patwal@users.noreply.github.com> Date: Mon, 19 Jul 2021 21:10:18 +0530 Subject: [PATCH 1517/2908] Random anime character info (#4553) * fixed colons and spaces * fixed colons and spaces * random anime character python script * more tests passed * type hint updated Co-authored-by: Christian Clauss * type hint updated again Co-authored-by: Christian Clauss * Update random_anime_character.py Co-authored-by: Christian Clauss --- web_programming/random_anime_character.py | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 web_programming/random_anime_character.py diff --git a/web_programming/random_anime_character.py b/web_programming/random_anime_character.py new file mode 100644 index 000000000000..f15a9c05d9e5 --- /dev/null +++ b/web_programming/random_anime_character.py @@ -0,0 +1,37 @@ +import os + +import requests +from bs4 import BeautifulSoup +from fake_useragent import UserAgent + +headers = {"UserAgent": UserAgent().random} +URL = "https://www.mywaifulist.moe/random" + + +def save_image(image_url: str, image_title: str) -> None: + """ + Saves the image of anime character + """ + image = requests.get(image_url, headers=headers) + with open(image_title, "wb") as file: + file.write(image.content) + + +def random_anime_character() -> tuple[str, str, str]: + """ + Returns the Title, Description, and Image Title of a random anime character . + """ + soup = BeautifulSoup(requests.get(URL, headers=headers).text, "html.parser") + title = soup.find("meta", attrs={"property": "og:title"}).attrs["content"] + image_url = soup.find("meta", attrs={"property": "og:image"}).attrs["content"] + description = soup.find("p", id="description").get_text() + _, image_extension = os.path.splitext(os.path.basename(image_url)) + image_title = title.strip().replace(" ", "_") + image_title = f"{image_title}{image_extension}" + save_image(image_url, image_title) + return (title, description, image_title) + + +if __name__ == "__main__": + title, desc, image_title = random_anime_character() + print(f"{title}\n\n{desc}\n\nImage saved : {image_title}") From 72aa4cc315ca0a70e09026884556cb724145d12e Mon Sep 17 00:00:00 2001 From: SURYAPRATAP SINGH SURYAVANSHI <67123991+suryapratapsinghsuryavanshi@users.noreply.github.com> Date: Tue, 20 Jul 2021 13:05:21 +0530 Subject: [PATCH 1518/2908] add phone_validator method (#4552) * add phone_validator method * change the phone_validator to indian_phone_validator * Unnecessary comments removed * all comments deleted * Fixes: #{} new line issue * code reformatted using black --- strings/indian_phone_validator.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 strings/indian_phone_validator.py diff --git a/strings/indian_phone_validator.py b/strings/indian_phone_validator.py new file mode 100644 index 000000000000..d544e92661b1 --- /dev/null +++ b/strings/indian_phone_validator.py @@ -0,0 +1,28 @@ +import re + + +def indian_phone_validator(phone: str) -> bool: + """ + Determine whether the string is a valid phone number or not + :param phone: + :return: Boolean + >>> indian_phone_validator("+91123456789") + False + >>> indian_phone_validator("+919876543210") + True + >>> indian_phone_validator("01234567896") + False + >>> indian_phone_validator("919876543218") + True + >>> indian_phone_validator("+91-1234567899") + False + """ + pat = re.compile(r"^(\+91[\-\s]?)?[0]?(91)?[789]\d{9}$") + match = re.search(pat, phone) + if match: + return match.string == phone + return False + + +if __name__ == "__main__": + print(indian_phone_validator("+918827897895")) From 4a2216b69a941b39ce279e475e383db44836df1d Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Tue, 20 Jul 2021 13:36:14 +0600 Subject: [PATCH 1519/2908] Fix mypy errors at bidirectional_a_star (#4556) --- graphs/bidirectional_a_star.py | 42 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/graphs/bidirectional_a_star.py b/graphs/bidirectional_a_star.py index 72ff4fa65ff0..729d8957bdef 100644 --- a/graphs/bidirectional_a_star.py +++ b/graphs/bidirectional_a_star.py @@ -8,6 +8,8 @@ from math import sqrt # 1 for manhattan, 0 for euclidean +from typing import Optional + HEURISTIC = 0 grid = [ @@ -22,6 +24,8 @@ delta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # up, left, down, right +TPosition = tuple[int, int] + class Node: """ @@ -39,7 +43,15 @@ class Node: True """ - def __init__(self, pos_x, pos_y, goal_x, goal_y, g_cost, parent): + def __init__( + self, + pos_x: int, + pos_y: int, + goal_x: int, + goal_y: int, + g_cost: int, + parent: Optional[Node], + ) -> None: self.pos_x = pos_x self.pos_y = pos_y self.pos = (pos_y, pos_x) @@ -61,7 +73,7 @@ def calculate_heuristic(self) -> float: else: return sqrt(dy ** 2 + dx ** 2) - def __lt__(self, other) -> bool: + def __lt__(self, other: Node) -> bool: return self.f_cost < other.f_cost @@ -81,23 +93,22 @@ class AStar: (4, 3), (4, 4), (5, 4), (5, 5), (6, 5), (6, 6)] """ - def __init__(self, start, goal): + def __init__(self, start: TPosition, goal: TPosition): 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.closed_nodes: list[Node] = [] self.reached = False - def search(self) -> list[tuple[int]]: + def search(self) -> list[TPosition]: 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) @@ -118,8 +129,7 @@ def search(self) -> list[tuple[int]]: else: self.open_nodes.append(better_node) - if not (self.reached): - return [(self.start.pos)] + return [self.start.pos] def get_successors(self, parent: Node) -> list[Node]: """ @@ -147,7 +157,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: Optional[Node]) -> list[TPosition]: """ Retrace the path from parents to parents until start node """ @@ -173,12 +183,12 @@ class BidirectionalAStar: (2, 5), (3, 5), (4, 5), (5, 5), (5, 6), (6, 6)] """ - def __init__(self, start, goal): + def __init__(self, start: TPosition, goal: TPosition) -> None: self.fwd_astar = AStar(start, goal) self.bwd_astar = AStar(goal, start) self.reached = False - def search(self) -> list[tuple[int]]: + def search(self) -> list[TPosition]: while self.fwd_astar.open_nodes or self.bwd_astar.open_nodes: self.fwd_astar.open_nodes.sort() self.bwd_astar.open_nodes.sort() @@ -186,7 +196,6 @@ def search(self) -> list[tuple[int]]: current_bwd_node = self.bwd_astar.open_nodes.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 ) @@ -220,12 +229,11 @@ def search(self) -> list[tuple[int]]: else: astar.open_nodes.append(better_node) - if not self.reached: - return [self.fwd_astar.start.pos] + return [self.fwd_astar.start.pos] def retrace_bidirectional_path( self, fwd_node: Node, bwd_node: Node - ) -> list[tuple[int]]: + ) -> list[TPosition]: fwd_path = self.fwd_astar.retrace_path(fwd_node) bwd_path = self.bwd_astar.retrace_path(bwd_node) bwd_path.pop() @@ -236,9 +244,6 @@ def retrace_bidirectional_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: @@ -252,6 +257,5 @@ def retrace_bidirectional_path( 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") From bc09ba9abfa220c893b23969a4b8de05f5ced1e1 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Tue, 20 Jul 2021 17:24:27 +0600 Subject: [PATCH 1520/2908] Fix mypy errors at graph_list (#4557) --- graphs/graph_list.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/graphs/graph_list.py b/graphs/graph_list.py index bab6d6893a89..f04b7a92390d 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -3,11 +3,15 @@ # Author: OMKAR PATHAK, Nwachukwu Chidiebere # Use a Python dictionary to construct the graph. +from __future__ import annotations from pprint import pformat +from typing import Generic, TypeVar +T = TypeVar("T") -class GraphAdjacencyList: + +class GraphAdjacencyList(Generic[T]): """ Adjacency List type Graph Data Structure that accounts for directed and undirected Graphs. Initialize graph object indicating whether it's directed or undirected. @@ -59,18 +63,27 @@ class GraphAdjacencyList: 5: [1, 4], 6: [2], 7: [2]} + >>> char_graph = GraphAdjacencyList(directed=False) + >>> char_graph.add_edge('a', 'b') + {'a': ['b'], 'b': ['a']} + >>> char_graph.add_edge('b', 'c').add_edge('b', 'e').add_edge('b', 'f') + {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} + >>> print(char_graph) + {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} """ - def __init__(self, directed: bool = True): + def __init__(self, directed: bool = True) -> None: """ Parameters: directed: (bool) Indicates if graph is directed or undirected. Default is True. """ - self.adj_list = {} # dictionary of lists + self.adj_list: dict[T, list[T]] = {} # dictionary of lists self.directed = directed - def add_edge(self, source_vertex: int, destination_vertex: int) -> object: + def add_edge( + self, source_vertex: T, destination_vertex: T + ) -> GraphAdjacencyList[T]: """ Connects vertices together. Creates and Edge from source vertex to destination vertex. @@ -135,9 +148,3 @@ def add_edge(self, source_vertex: int, destination_vertex: int) -> object: def __repr__(self) -> str: return pformat(self.adj_list) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From 7342b336587af1e57eb9888203a1ae80832bde28 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Wed, 21 Jul 2021 11:59:18 +0600 Subject: [PATCH 1521/2908] Fix mypy erros at strongly connected component (#4558) --- graphs/strongly_connected_components.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/graphs/strongly_connected_components.py b/graphs/strongly_connected_components.py index d469df0c625b..325e5c1f33a3 100644 --- a/graphs/strongly_connected_components.py +++ b/graphs/strongly_connected_components.py @@ -10,7 +10,9 @@ 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: +def topology_sort( + graph: dict[int, list[int]], vert: int, visited: list[bool] +) -> list[int]: """ Use depth first search to sort graph At this time graph is the same as input @@ -32,7 +34,9 @@ def topology_sort(graph: dict, vert: int, visited: list) -> list: return order -def find_components(reversed_graph: dict, vert: int, visited: list) -> list: +def find_components( + reversed_graph: dict[int, list[int]], vert: int, visited: list[bool] +) -> list[int]: """ Use depth first search to find strongliy connected vertices. Now graph is reversed @@ -52,7 +56,7 @@ def find_components(reversed_graph: dict, vert: int, visited: list) -> list: return component -def strongly_connected_components(graph: dict) -> list: +def strongly_connected_components(graph: dict[int, list[int]]) -> list[list[int]]: """ This function takes graph as a parameter and then returns the list of strongly connected components @@ -63,7 +67,7 @@ def strongly_connected_components(graph: dict) -> list: """ visited = len(graph) * [False] - reversed_graph = {vert: [] for vert in range(len(graph))} + reversed_graph: dict[int, list[int]] = {vert: [] for vert in range(len(graph))} for vert, neighbours in graph.items(): for neighbour in neighbours: @@ -84,9 +88,3 @@ def strongly_connected_components(graph: dict) -> list: components_list.append(component) return components_list - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From 407c97906393ddaea43e2c21174c6cdaeb57dcfa Mon Sep 17 00:00:00 2001 From: Suyash Shrivastava <65887107+suyash2796@users.noreply.github.com> Date: Wed, 21 Jul 2021 11:31:55 +0530 Subject: [PATCH 1522/2908] [Mypy fix] fix secant method (#4501) * case switch using python * review comments * added type hints * general code format * [mypy] Fix type annotations for secant_method.py * remove bad push --- arithmetic_analysis/secant_method.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arithmetic_analysis/secant_method.py b/arithmetic_analysis/secant_method.py index 7eb1dd8f5c6b..45bcb185fc3e 100644 --- a/arithmetic_analysis/secant_method.py +++ b/arithmetic_analysis/secant_method.py @@ -26,4 +26,4 @@ def secant_method(lower_bound: float, upper_bound: float, repeats: int) -> float if __name__ == "__main__": - print(f"Example: {secant_method(1, 3, 2) = }") + print(f"Example: {secant_method(1, 3, 2)}") From 7634cf0d60d15986456e35d82d3f3eb1c6a53c26 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Mon, 26 Jul 2021 18:45:40 +0600 Subject: [PATCH 1523/2908] Fix mypy errors at gale_shapely_bigraph (#4568) --- graphs/gale_shapley_bigraph.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graphs/gale_shapley_bigraph.py b/graphs/gale_shapley_bigraph.py index 59baf8296ea6..56b8c6c77bcb 100644 --- a/graphs/gale_shapley_bigraph.py +++ b/graphs/gale_shapley_bigraph.py @@ -1,7 +1,9 @@ from __future__ import annotations -def stable_matching(donor_pref: list[int], recipient_pref: list[int]) -> list[int]: +def stable_matching( + donor_pref: list[list[int]], recipient_pref: list[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 @@ -19,11 +21,13 @@ def stable_matching(donor_pref: list[int], recipient_pref: list[int]) -> list[in [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] @@ -31,6 +35,7 @@ def stable_matching(donor_pref: list[int], recipient_pref: list[int]) -> list[in 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 From 6732fa013119aa2003f24d1dac1bdc4ee435ee5e Mon Sep 17 00:00:00 2001 From: arfy slowy Date: Mon, 26 Jul 2021 19:52:52 +0700 Subject: [PATCH 1524/2908] [fixed] module 'numpy' is imported with both 'import' and 'import from' (#4544) * [fixed] module 'numy' is imported with both 'import' and 'import from' * remove commented --- arithmetic_analysis/lu_decomposition.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index ef37d1b7b4ef..5bb631758c21 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -6,10 +6,9 @@ from typing import Tuple import numpy as np -from numpy import ndarray -def lower_upper_decomposition(table: ndarray) -> Tuple[ndarray, ndarray]: +def lower_upper_decomposition(table: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """Lower-Upper (LU) Decomposition Example: From c5003a2c462c7c775992ad9a733c360126702dd4 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Tue, 27 Jul 2021 14:09:17 +0600 Subject: [PATCH 1525/2908] Fix mypy errors at bfs_shortest_path algo (#4572) --- graphs/breadth_first_search_shortest_path.py | 28 +++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index b43479d4659c..48f8ab1a4956 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -3,6 +3,8 @@ """ from __future__ import annotations +from typing import Optional + graph = { "A": ["B", "C", "E"], "B": ["A", "D", "E"], @@ -15,17 +17,19 @@ class Graph: - def __init__(self, graph: dict[str, str], source_vertex: str) -> None: - """Graph is implemented as dictionary of adjacency lists. Also, + def __init__(self, graph: dict[str, list[str]], source_vertex: str) -> None: + """ + Graph is implemented as dictionary of adjacency 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.parent: dict[str, Optional[str]] = {} self.source_vertex = source_vertex def breath_first_search(self) -> None: - """This function is a helper for running breath first search on this graph. + """ + This function is a helper for running breath first search on this graph. >>> g = Graph(graph, "G") >>> g.breath_first_search() >>> g.parent @@ -44,7 +48,8 @@ def breath_first_search(self) -> None: queue.append(adjacent_vertex) def shortest_path(self, target_vertex: str) -> str: - """This shortest path function returns a string, describing the result: + """ + 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 @@ -64,17 +69,16 @@ def shortest_path(self, target_vertex: str) -> str: 'G' """ if target_vertex == self.source_vertex: - return f"{self.source_vertex}" - elif not self.parent.get(target_vertex): + return self.source_vertex + + target_vertex_parent = self.parent.get(target_vertex) + if target_vertex_parent is None: 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}" + return self.shortest_path(target_vertex_parent) + f"->{target_vertex}" -if __name__ == "__main__": - import doctest - doctest.testmod() +if __name__ == "__main__": g = Graph(graph, "G") g.breath_first_search() print(g.shortest_path("D")) From a4b7d12262d41969cf1fc9d5dbf9b9a01f165f4c Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Tue, 27 Jul 2021 17:21:00 +0600 Subject: [PATCH 1526/2908] Fix mypy errors at greedy best first algo (#4575) --- graphs/greedy_best_first.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index 4b80a6853d3f..d5e80247a9b4 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -4,6 +4,10 @@ from __future__ import annotations +from typing import Optional + +Path = list[tuple[int, int]] + grid = [ [0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles @@ -33,7 +37,15 @@ class Node: True """ - def __init__(self, pos_x, pos_y, goal_x, goal_y, g_cost, parent): + def __init__( + self, + pos_x: int, + pos_y: int, + goal_x: int, + goal_y: int, + g_cost: float, + parent: Optional[Node], + ): self.pos_x = pos_x self.pos_y = pos_y self.pos = (pos_y, pos_x) @@ -72,16 +84,16 @@ class GreedyBestFirst: (6, 2), (6, 3), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)] """ - def __init__(self, start, goal): + def __init__(self, start: tuple[int, int], goal: tuple[int, int]): 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.closed_nodes: list[Node] = [] self.reached = False - def search(self) -> list[tuple[int]]: + def search(self) -> Optional[Path]: """ Search for the path, if a path is not found, only the starting position is returned @@ -113,8 +125,9 @@ def search(self) -> list[tuple[int]]: else: self.open_nodes.append(better_node) - if not (self.reached): + if not self.reached: return [self.start.pos] + return None def get_successors(self, parent: Node) -> list[Node]: """ @@ -143,7 +156,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: Optional[Node]) -> Path: """ Retrace the path from parents to parents until start node """ @@ -166,9 +179,9 @@ def retrace_path(self, node: Node) -> list[tuple[int]]: greedy_bf = GreedyBestFirst(init, goal) path = greedy_bf.search() + if path: + for pos_x, pos_y in path: + grid[pos_x][pos_y] = 2 - for elem in path: - grid[elem[0]][elem[1]] = 2 - - for elem in grid: - print(elem) + for elem in grid: + print(elem) From 40d85d54433eccff19b4434c7073f2a7b6127426 Mon Sep 17 00:00:00 2001 From: Milton Chandro Bhowmick Date: Wed, 28 Jul 2021 16:50:21 +0600 Subject: [PATCH 1527/2908] Modified the a_star [dot] py for making readable (#4576) --- graphs/a_star.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphs/a_star.py b/graphs/a_star.py index cb5b2fcd16e8..d3657cb19540 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -44,7 +44,7 @@ def search(grid, init, goal, cost, heuristic): x = init[0] y = init[1] g = 0 - f = g + heuristic[init[0]][init[0]] + f = g + heuristic[x][y] # cost from starting cell to destination cell cell = [[f, g, x, y]] found = False # flag that is set when search is complete From a5bcf0f6749a93a44f7a981edc9b0e35fbd066f2 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Thu, 29 Jul 2021 19:14:35 +0600 Subject: [PATCH 1528/2908] Fix mypy errors at even_tree algo (#4579) --- graphs/even_tree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphs/even_tree.py b/graphs/even_tree.py index c9aef6e7861f..92ffb4b232f7 100644 --- a/graphs/even_tree.py +++ b/graphs/even_tree.py @@ -16,12 +16,12 @@ from collections import defaultdict -def dfs(start): +def dfs(start: int) -> int: """DFS traversal""" # pylint: disable=redefined-outer-name ret = 1 visited[start] = True - for v in tree.get(start): + for v in tree[start]: if v not in visited: ret += dfs(v) if ret % 2 == 0: @@ -48,8 +48,8 @@ def even_tree(): if __name__ == "__main__": n, m = 10, 9 tree = defaultdict(list) - visited = {} - cuts = [] + visited: dict[int, bool] = {} + cuts: list[int] = [] count = 0 edges = [(2, 1), (3, 1), (4, 3), (5, 2), (6, 1), (7, 2), (8, 6), (9, 8), (10, 8)] for u, v in edges: From da71184b04837d2bc934f9947b4c262da096f349 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Mon, 2 Aug 2021 18:40:48 +0600 Subject: [PATCH 1529/2908] Fix mypy errors at mst_kruskal (#4581) --- graphs/minimum_spanning_tree_kruskal.py | 15 +++++++-------- graphs/tests/test_min_spanning_tree_kruskal.py | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py index a51f970341f7..f21a87a7d534 100644 --- a/graphs/minimum_spanning_tree_kruskal.py +++ b/graphs/minimum_spanning_tree_kruskal.py @@ -1,15 +1,14 @@ -from typing import List, Tuple - - -def kruskal(num_nodes: int, num_edges: int, edges: List[Tuple[int, int, int]]) -> int: +def kruskal( + num_nodes: int, edges: list[tuple[int, int, int]] +) -> list[tuple[int, int, int]]: """ - >>> kruskal(4, 3, [(0, 1, 3), (1, 2, 5), (2, 3, 1)]) + >>> kruskal(4, [(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)]) + >>> kruskal(4, [(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), + >>> kruskal(4, [(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)] """ @@ -44,4 +43,4 @@ def find_parent(i): node1, node2, cost = [int(x) for x in input().strip().split()] edges.append((node1, node2, cost)) - kruskal(num_nodes, num_edges, edges) + kruskal(num_nodes, edges) diff --git a/graphs/tests/test_min_spanning_tree_kruskal.py b/graphs/tests/test_min_spanning_tree_kruskal.py index 3a527aef384f..d6df242ec6d1 100644 --- a/graphs/tests/test_min_spanning_tree_kruskal.py +++ b/graphs/tests/test_min_spanning_tree_kruskal.py @@ -2,7 +2,7 @@ def test_kruskal_successful_result(): - num_nodes, num_edges = 9, 14 + num_nodes = 9 edges = [ [0, 1, 4], [0, 7, 8], @@ -20,7 +20,7 @@ def test_kruskal_successful_result(): [1, 7, 11], ] - result = kruskal(num_nodes, num_edges, edges) + result = kruskal(num_nodes, edges) expected = [ [7, 6, 1], From 5957eabd3e0c92650dba0962779b8729d2875209 Mon Sep 17 00:00:00 2001 From: jonabtc <39396756+jonabtc@users.noreply.github.com> Date: Tue, 3 Aug 2021 01:03:22 -0500 Subject: [PATCH 1530/2908] Adding the double factorial algorithm (#4550) --- maths/double_factorial_recursive.py | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 maths/double_factorial_recursive.py diff --git a/maths/double_factorial_recursive.py b/maths/double_factorial_recursive.py new file mode 100644 index 000000000000..05c9b29680a7 --- /dev/null +++ b/maths/double_factorial_recursive.py @@ -0,0 +1,31 @@ +def double_factorial(n: int) -> int: + """ + Compute double factorial using recursive method. + Recursion can be costly for large numbers. + + To learn about the theory behind this algorithm: + https://en.wikipedia.org/wiki/Double_factorial + + >>> import math + >>> all(double_factorial(i) == math.prod(range(i, 0, -2)) for i in range(20)) + True + >>> double_factorial(0.1) + Traceback (most recent call last): + ... + ValueError: double_factorial() only accepts integral values + >>> double_factorial(-1) + Traceback (most recent call last): + ... + ValueError: double_factorial() not defined for negative values + """ + if not isinstance(n, int): + raise ValueError("double_factorial() only accepts integral values") + if n < 0: + raise ValueError("double_factorial() not defined for negative values") + return 1 if n <= 1 else n * double_factorial(n - 2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f432bc76a6a8851b8bd2f29a45761a4ec538561f Mon Sep 17 00:00:00 2001 From: SURYAPRATAP SINGH SURYAVANSHI <67123991+suryapratapsinghsuryavanshi@users.noreply.github.com> Date: Fri, 6 Aug 2021 15:45:42 +0530 Subject: [PATCH 1531/2908] add alternative_string_arrange method (#4595) * add alternative_string_arrange method * fix issue * fix one more issue * changed the variable name li to output_list --- strings/alternative_string_arrange.py | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 strings/alternative_string_arrange.py diff --git a/strings/alternative_string_arrange.py b/strings/alternative_string_arrange.py new file mode 100644 index 000000000000..d81ddd8a1574 --- /dev/null +++ b/strings/alternative_string_arrange.py @@ -0,0 +1,31 @@ +def alternative_string_arrange(first_str: str, second_str: str) -> str: + """ + Return the alternative arrangements of the two strings. + :param first_str: + :param second_str: + :return: String + >>> alternative_string_arrange("ABCD", "XY") + 'AXBYCD' + >>> alternative_string_arrange("XY", "ABCD") + 'XAYBCD' + >>> alternative_string_arrange("AB", "XYZ") + 'AXBYZ' + >>> alternative_string_arrange("ABC", "") + 'ABC' + """ + first_str_length: int = len(first_str) + second_str_length: int = len(second_str) + abs_length: int = ( + first_str_length if first_str_length > second_str_length else second_str_length + ) + output_list: list = [] + for char_count in range(abs_length): + if char_count < first_str_length: + output_list.append(first_str[char_count]) + if char_count < second_str_length: + output_list.append(second_str[char_count]) + return "".join(output_list) + + +if __name__ == "__main__": + print(alternative_string_arrange("AB", "XYZ"), end=" ") From 63ac09eeae6805ae25a3f994cb18e5df254ba4b6 Mon Sep 17 00:00:00 2001 From: Shubham Ganar <67952129+shubhamsg199@users.noreply.github.com> Date: Sun, 8 Aug 2021 23:51:26 +0530 Subject: [PATCH 1532/2908] Created check_valid_ip_address.py (#4602) * Created check_valid_ip_address.py * fixed typos error Co-authored-by: root --- maths/check_valid_ip_address.py | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 maths/check_valid_ip_address.py diff --git a/maths/check_valid_ip_address.py b/maths/check_valid_ip_address.py new file mode 100644 index 000000000000..6e8d35ebc44c --- /dev/null +++ b/maths/check_valid_ip_address.py @@ -0,0 +1,46 @@ +""" +Checking valid Ip Address. +A valid IP address must be in the form of A.B.C.D, +where A,B,C and D are numbers from 0-254 +for example: 192.168.23.1, 172.254.254.254 are valid IP address + 192.168.255.0, 255.192.3.121 are Invalid IP address +""" + + +def check_valid_ip(ip: str) -> bool: + """ + print "Valid IP address" If IP is valid. + or + print "Invalid IP address" If IP is Invalid. + + >>> check_valid_ip("192.168.0.23") + True + + >>> check_valid_ip("192.255.15.8") + False + + >>> check_valid_ip("172.100.0.8") + True + + >>> check_valid_ip("254.255.0.255") + False + """ + ip1 = ip.replace(".", " ") + list1 = [int(i) for i in ip1.split() if i.isdigit()] + count = 0 + for i in list1: + if i > 254: + count += 1 + break + if count: + return False + return True + + +if __name__ == "__main__": + ip = input() + output = check_valid_ip(ip) + if output is True: + print(f"{ip} is a Valid IP address") + else: + print(f"{ip} is an Invalid IP address") From d668c172b07bf9f54d63dc295016a96ec782a541 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Thu, 12 Aug 2021 02:48:53 +0600 Subject: [PATCH 1533/2908] Refactor graph_initialization at basic_graph.py (#4601) --- graphs/basic_graphs.py | 97 +++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 0f73d8d07b2a..9cd6dd0f9635 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -1,42 +1,69 @@ from collections import deque + +def _input(message): + return input(message).strip().split(" ") + + +def initialize_unweighted_directed_graph( + node_count: int, edge_count: int +) -> dict[int, list[int]]: + graph: dict[int, list[int]] = {} + for i in range(node_count): + graph[i + 1] = [] + + for e in range(edge_count): + x, y = [int(i) for i in _input(f"Edge {e + 1}: ")] + graph[x].append(y) + return graph + + +def initialize_unweighted_undirected_graph( + node_count: int, edge_count: int +) -> dict[int, list[int]]: + graph: dict[int, list[int]] = {} + for i in range(node_count): + graph[i + 1] = [] + + for e in range(edge_count): + x, y = [int(i) for i in _input(f"Edge {e + 1}: ")] + graph[x].append(y) + graph[y].append(x) + return graph + + +def initialize_weighted_undirected_graph( + node_count: int, edge_count: int +) -> dict[int, list[tuple[int, int]]]: + graph: dict[int, list[tuple[int, int]]] = {} + for i in range(node_count): + graph[i + 1] = [] + + for e in range(edge_count): + x, y, w = [int(i) for i in _input(f"Edge {e + 1}: ")] + graph[x].append((y, w)) + graph[y].append((x, w)) + return graph + + if __name__ == "__main__": - # Accept No. of Nodes and edges - n, m = map(int, input().split(" ")) + n, m = [int(i) for i in _input("Number of nodes and edges: ")] + + graph_choice = int( + _input( + "Press 1 or 2 or 3 \n" + "1. Unweighted directed \n" + "2. Unweighted undirected \n" + "3. Weighted undirected \n" + )[0] + ) + + g = { + 1: initialize_unweighted_directed_graph, + 2: initialize_unweighted_undirected_graph, + 3: initialize_weighted_undirected_graph, + }[graph_choice](n, m) - # Initialising Dictionary of edges - g = {} - for i in range(n): - g[i + 1] = [] - - """ - ---------------------------------------------------------------------------- - Accepting edges of Unweighted Directed Graphs - ---------------------------------------------------------------------------- - """ - for _ in range(m): - x, y = map(int, input().strip().split(" ")) - g[x].append(y) - - """ - ---------------------------------------------------------------------------- - Accepting edges of Unweighted Undirected Graphs - ---------------------------------------------------------------------------- - """ - for _ in range(m): - x, y = map(int, input().strip().split(" ")) - g[x].append(y) - g[y].append(x) - - """ - ---------------------------------------------------------------------------- - Accepting edges of Weighted Undirected Graphs - ---------------------------------------------------------------------------- - """ - for _ in range(m): - x, y, r = map(int, input().strip().split(" ")) - g[x].append([y, r]) - g[y].append([x, r]) """ -------------------------------------------------------------------------------- From cd987372e4c3a9f87d65b757ab46a48527fc9fa9 Mon Sep 17 00:00:00 2001 From: Hasanul Islam Date: Fri, 13 Aug 2021 13:10:24 +0600 Subject: [PATCH 1534/2908] Fix multi heuristic astar algo (#4612) --- graphs/multi_heuristic_astar.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index 77ca5760d5f0..8607f51d8f52 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -2,6 +2,8 @@ import numpy as np +TPos = tuple[int, int] + class PriorityQueue: def __init__(self): @@ -53,24 +55,24 @@ def get(self): return (priority, item) -def consistent_heuristic(P, goal): +def consistent_heuristic(P: TPos, goal: TPos): # euclidean distance a = np.array(P) b = np.array(goal) return np.linalg.norm(a - b) -def heuristic_2(P, goal): +def heuristic_2(P: TPos, goal: TPos): # integer division by time variable return consistent_heuristic(P, goal) // t -def heuristic_1(P, goal): +def heuristic_1(P: TPos, goal: TPos): # manhattan distance return abs(P[0] - goal[0]) + abs(P[1] - goal[1]) -def key(start, i, goal, g_function): +def key(start: TPos, i: int, goal: TPos, g_function: dict[TPos, float]): ans = g_function[start] + W1 * heuristics[i](start, goal) return ans @@ -117,7 +119,7 @@ def do_something(back_pointer, goal, start): quit() -def valid(p): +def valid(p: TPos): if p[0] < 0 or p[0] > n - 1: return False if p[1] < 0 or p[1] > n - 1: @@ -215,7 +217,6 @@ def make_common_ground(): (18, 1), (19, 1), ] -blocks_no = [] blocks_all = make_common_ground() @@ -233,7 +234,7 @@ def make_common_ground(): t = 1 -def multi_a_star(start, goal, n_heuristic): +def multi_a_star(start: TPos, goal: TPos, n_heuristic: int): g_function = {start: 0, goal: float("inf")} back_pointer = {start: -1, goal: -1} open_list = [] @@ -243,8 +244,8 @@ def multi_a_star(start, goal, n_heuristic): open_list.append(PriorityQueue()) open_list[i].put(start, key(start, i, goal, g_function)) - close_list_anchor = [] - close_list_inad = [] + close_list_anchor: list[int] = [] + close_list_inad: list[int] = [] while open_list[0].minkey() < float("inf"): for i in range(1, n_heuristic): # print(open_list[0].minkey(), open_list[i].minkey()) From 3c225247b843233a306a94907f862bece6e637dc Mon Sep 17 00:00:00 2001 From: Shubham Ganar <67952129+shubhamsg199@users.noreply.github.com> Date: Fri, 13 Aug 2021 12:40:52 +0530 Subject: [PATCH 1535/2908] [mypy] Fix type annotations for strings/naive_string_search.py (#4611) --- strings/naive_string_search.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/strings/naive_string_search.py b/strings/naive_string_search.py index f28950264121..31599008720c 100644 --- a/strings/naive_string_search.py +++ b/strings/naive_string_search.py @@ -1,10 +1,8 @@ """ 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 - Complexity : O(n*m) n=length of main string m=length of pattern string @@ -39,4 +37,4 @@ def naive_pattern_search(s: str, pattern: str) -> list: if __name__ == "__main__": assert naive_pattern_search("ABCDEFG", "DE") == [3] - print(f"{naive_pattern_search('ABAAABCDBBABCDDEBCABC', 'ABC') = }") + print(naive_pattern_search("ABAAABCDBBABCDDEBCABC", "ABC")) From 032999f36ed6eef61752e6bc5e399020988b06bd Mon Sep 17 00:00:00 2001 From: Bonnie <58572137+bonbon99@users.noreply.github.com> Date: Sun, 15 Aug 2021 01:43:05 -0400 Subject: [PATCH 1536/2908] Create exchange_sort.py (#4600) * Create exchange_sort.py added exchange sort * Fixed doctest in exchange_sort.py * Fixed formatting error and added new length variable added empty line at end of exchange_sort.py and turned len(numbers) into a variable * Fixed formatting errors with black added empty line --- sorts/exchange_sort.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 sorts/exchange_sort.py diff --git a/sorts/exchange_sort.py b/sorts/exchange_sort.py new file mode 100644 index 000000000000..1ce78a9dc0cb --- /dev/null +++ b/sorts/exchange_sort.py @@ -0,0 +1,27 @@ +def exchange_sort(numbers: list[int]) -> list[int]: + """ + Uses exchange sort to sort a list of numbers. + Source: https://en.wikipedia.org/wiki/Sorting_algorithm#Exchange_sort + >>> exchange_sort([5, 4, 3, 2, 1]) + [1, 2, 3, 4, 5] + >>> exchange_sort([-1, -2, -3]) + [-3, -2, -1] + >>> exchange_sort([1, 2, 3, 4, 5]) + [1, 2, 3, 4, 5] + >>> exchange_sort([0, 10, -2, 5, 3]) + [-2, 0, 3, 5, 10] + >>> exchange_sort([]) + [] + """ + numbers_length = len(numbers) + for i in range(numbers_length): + for j in range(i + 1, numbers_length): + if numbers[j] < numbers[i]: + numbers[i], numbers[j] = numbers[j], numbers[i] + return numbers + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(exchange_sort(unsorted)) From d009cea391414bfef17520ba6b64e4c2d97163ed Mon Sep 17 00:00:00 2001 From: imp Date: Mon, 16 Aug 2021 03:15:53 +0800 Subject: [PATCH 1537/2908] Fix mypy error at maths (#4613) * Fix mypy errors for maths/greedy_coin_change.py * Fix mypy errors for maths/two_sum.py * Fix mypy errors for maths/triplet_sum.py * Fix the format of maths/greedy_coin_change.py * Fix the format of maths/greedy_coin_change.py * Fix format with pre-commit --- maths/greedy_coin_change.py | 4 ++-- maths/triplet_sum.py | 2 +- maths/two_sum.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/maths/greedy_coin_change.py b/maths/greedy_coin_change.py index 5a7d9e8d84ae..5233ee1cbc12 100644 --- a/maths/greedy_coin_change.py +++ b/maths/greedy_coin_change.py @@ -41,7 +41,7 @@ """ -def find_minimum_change(denominations: list[int], value: int) -> list[int]: +def find_minimum_change(denominations: list[int], value: str) -> list[int]: """ Find the minimum change from the given denominations and value >>> find_minimum_change([1, 5, 10, 20, 50, 100, 200, 500, 1000,2000], 18745) @@ -75,7 +75,7 @@ def find_minimum_change(denominations: list[int], value: int) -> list[int]: if __name__ == "__main__": denominations = list() - value = 0 + value = "0" if ( input("Do you want to enter your denominations ? (yY/n): ").strip().lower() diff --git a/maths/triplet_sum.py b/maths/triplet_sum.py index 22fab17d30c2..af77ed145bce 100644 --- a/maths/triplet_sum.py +++ b/maths/triplet_sum.py @@ -19,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, ...]: """ Returns a triplet in the array with sum equal to target, else (0, 0, 0). diff --git a/maths/two_sum.py b/maths/two_sum.py index 5209acbc7e44..12ad332d6c4e 100644 --- a/maths/two_sum.py +++ b/maths/two_sum.py @@ -31,7 +31,7 @@ def two_sum(nums: list[int], target: int) -> list[int]: >>> two_sum([3 * i for i in range(10)], 19) [] """ - chk_map = {} + chk_map: dict[int, int] = {} for index, val in enumerate(nums): compl = target - val if compl in chk_map: From 4545270ace03411ec861361329345a36195b881d Mon Sep 17 00:00:00 2001 From: imp Date: Wed, 18 Aug 2021 18:44:26 +0800 Subject: [PATCH 1538/2908] [mypy] Fix type annotations for graphs (#4622) * Fix mypy error for frequent_pattern_graph_miner.py * Fix mypy error for markov_chain.py --- graphs/frequent_pattern_graph_miner.py | 2 +- graphs/markov_chain.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index ff7063082267..8f344b7bd3ae 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -227,6 +227,6 @@ def preprocess(edge_array): support = get_support(cluster) graph = construct_graph(cluster, nodes) find_freq_subgraph_given_support(60, cluster, graph) - paths = [] + paths: list = [] freq_subgraph_edge_list = freq_subgraphs_edge_list(paths) print_all() diff --git a/graphs/markov_chain.py b/graphs/markov_chain.py index b93c408cd288..0b6659822dc4 100644 --- a/graphs/markov_chain.py +++ b/graphs/markov_chain.py @@ -35,6 +35,7 @@ def transition(self, node: str) -> str: current_probability += self.connections[node][dest] if current_probability > random_value: return dest + return "" def get_transitions( From af0810fca133dde19b39fc7735572b6989ea269b Mon Sep 17 00:00:00 2001 From: imp Date: Wed, 18 Aug 2021 18:45:07 +0800 Subject: [PATCH 1539/2908] [mypy] Fix type annotations for maths (#4617) * Fix mypy errors for armstrong_numbers.py * Fix mypy errors for harmonic_series.py * Fix mypy errors for average_median.py --- maths/armstrong_numbers.py | 2 +- maths/average_median.py | 4 ++-- maths/series/harmonic_series.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index ce8c62182fd9..4e62737e1333 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -9,7 +9,7 @@ On-Line Encyclopedia of Integer Sequences entry: https://oeis.org/A005188 """ PASSING = (1, 153, 370, 371, 1634, 24678051, 115132219018763992565095597973971522401) -FAILING = (-153, -1, 0, 1.2, 200, "A", [], {}, None) +FAILING: tuple = (-153, -1, 0, 1.2, 200, "A", [], {}, None) def armstrong_number(n: int) -> bool: diff --git a/maths/average_median.py b/maths/average_median.py index 57e01368b7b2..497bf0c3a714 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -1,14 +1,14 @@ from typing import Union -def median(nums: Union[int, float]) -> Union[int, float]: +def median(nums: list) -> Union[int, float]: """ Find median of a list of numbers. Wiki: https://en.wikipedia.org/wiki/Median >>> median([0]) 0 - >>> median([4,1,3,2]) + >>> median([4, 1, 3, 2]) 2.5 >>> median([2, 70, 6, 50, 20, 8, 4]) 8 diff --git a/maths/series/harmonic_series.py b/maths/series/harmonic_series.py index 91b5944583e4..d42d13d912f1 100644 --- a/maths/series/harmonic_series.py +++ b/maths/series/harmonic_series.py @@ -33,8 +33,8 @@ def harmonic_series(n_term: str) -> list: ['1'] """ if n_term == "": - return n_term - series = [] + return [] + series: list = [] for temp in range(int(n_term)): series.append(f"1/{temp + 1}" if series else "1") return series From 9cb5760e895179f8aaa97dd577442189064c724d Mon Sep 17 00:00:00 2001 From: SURYAPRATAP SINGH SURYAVANSHI <67123991+suryapratapsinghsuryavanshi@users.noreply.github.com> Date: Wed, 18 Aug 2021 17:35:41 +0530 Subject: [PATCH 1540/2908] add date_to_weekday finder method (#4599) * add date_to_weekday finder method * reformat date_to_weekday method * remove time * remove hardcode weekdays list * fix return type error * fixing fail issue * Finding the test failing issue * after testing the pre-commit in local environment --- other/date_to_weekday.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 other/date_to_weekday.py diff --git a/other/date_to_weekday.py b/other/date_to_weekday.py new file mode 100644 index 000000000000..bb17130c0da5 --- /dev/null +++ b/other/date_to_weekday.py @@ -0,0 +1,27 @@ +from calendar import day_name +from datetime import datetime + + +def date_to_weekday(inp_date: str) -> str: + """ + It returns the day name of the given date string. + :param inp_date: + :return: String + >>> date_to_weekday("7/8/2035") + 'Tuesday' + >>> date_to_weekday("7/8/2021") + 'Saturday' + >>> date_to_weekday("1/1/2021") + 'Friday' + """ + day, month, year = [int(x) for x in inp_date.split("/")] + if year % 100 == 0: + year = "00" + new_base_date: str = f"{day}/{month}/{year%100} 0:0:0" + date_time_obj: datetime.date = datetime.strptime(new_base_date, "%d/%m/%y %H:%M:%S") + out_put_day: int = date_time_obj.weekday() + return day_name[out_put_day] + + +if __name__ == "__main__": + print(date_to_weekday("1/1/2021"), end=" ") From 20a4fdf38465c2731100c3fbd1aac847cd0b9322 Mon Sep 17 00:00:00 2001 From: imp Date: Thu, 19 Aug 2021 20:08:20 +0800 Subject: [PATCH 1541/2908] [mypy] Fix type annotations for strings (#4637) * Fix mypy error for can_string_be_rearranged_as_pal * Fix mypy error for levenshtein_distance.py * Fix mypy error for word_patterns.py * Fix mypy error for word_occurrence.py --- strings/can_string_be_rearranged_as_palindrome.py | 2 +- strings/levenshtein_distance.py | 2 +- strings/word_occurrence.py | 2 +- strings/word_patterns.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/strings/can_string_be_rearranged_as_palindrome.py b/strings/can_string_be_rearranged_as_palindrome.py index 7fedc5877e26..ddc4828c773b 100644 --- a/strings/can_string_be_rearranged_as_palindrome.py +++ b/strings/can_string_be_rearranged_as_palindrome.py @@ -43,7 +43,7 @@ def can_string_be_rearranged_as_palindrome(input_str: str = "") -> bool: 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 = {} + character_freq_dict: dict[str, int] = {} for character in lower_case_input_str: character_freq_dict[character] = character_freq_dict.get(character, 0) + 1 diff --git a/strings/levenshtein_distance.py b/strings/levenshtein_distance.py index 540a21c93da3..9f7a7e3e65c4 100644 --- a/strings/levenshtein_distance.py +++ b/strings/levenshtein_distance.py @@ -41,7 +41,7 @@ def levenshtein_distance(first_word: str, second_word: str) -> int: if len(second_word) == 0: return len(first_word) - previous_row = range(len(second_word) + 1) + previous_row = list(range(len(second_word) + 1)) for i, c1 in enumerate(first_word): diff --git a/strings/word_occurrence.py b/strings/word_occurrence.py index ef612e12dfa4..4acfa41adf11 100644 --- a/strings/word_occurrence.py +++ b/strings/word_occurrence.py @@ -14,7 +14,7 @@ def word_occurence(sentence: str) -> dict: >>> dict(word_occurence("Two spaces")) {'Two': 1, 'spaces': 1} """ - occurrence = defaultdict(int) + occurrence: dict = defaultdict(int) # Creating a dictionary containing count of each word for word in sentence.split(): occurrence[word] += 1 diff --git a/strings/word_patterns.py b/strings/word_patterns.py index d229954dea93..90b092a20dc8 100644 --- a/strings/word_patterns.py +++ b/strings/word_patterns.py @@ -28,7 +28,7 @@ def get_word_pattern(word: str) -> str: with open("dictionary.txt") as in_file: wordList = in_file.read().splitlines() - all_patterns = {} + all_patterns: dict = {} for word in wordList: pattern = get_word_pattern(word) if pattern in all_patterns: From 4ed7c7f09c74c358f9c31d7a13a29285264bd261 Mon Sep 17 00:00:00 2001 From: Shiva Rama Krishna <45482631+srkchowdary2000@users.noreply.github.com> Date: Mon, 23 Aug 2021 16:05:20 +0530 Subject: [PATCH 1542/2908] =?UTF-8?q?Added=20Bor=C5=AFvka's=20algorithm.?= =?UTF-8?q?=20(#4645)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added Borůvka's algorithm. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Solved Test Cases Errors. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Solved Test Cases Errors. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Solved Test Cases Errors. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Solved Test Cases Errors. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Solved Test Cases Errors. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Solved Test Cases Errors. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Solved Test Cases Errors.Removed WhiteSpaces. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Code Changes. * Added Borůvka's algorithm, a graph algorithm that finds the minimum spanning tree. Code Changes. --- graphs/boruvka.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 graphs/boruvka.py diff --git a/graphs/boruvka.py b/graphs/boruvka.py new file mode 100644 index 000000000000..b95bcc39850e --- /dev/null +++ b/graphs/boruvka.py @@ -0,0 +1,198 @@ +"""Borůvka's algorithm. + + Determines the minimum spanning tree(MST) of a graph using the Borůvka's algorithm. + Borůvka's algorithm is a greedy algorithm for finding a minimum spanning tree in a + graph,or a minimum spanning forest in the case of a graph that is not connected. + + The time complexity of this algorithm is O(ELogV), where E represents the number + of edges, while V represents the number of nodes. + + The space complexity of this algorithm is O(V + E), since we have to keep a couple + of lists whose sizes are equal to the number of nodes, as well as keep all the + edges of a graph inside of the data structure itself. + + Borůvka's algorithm gives us pretty much the same result as other MST Algorithms - + they all find the minimum spanning tree, and the time complexity is approximately + the same. + + One advantage that Borůvka's algorithm has compared to the alternatives is that it + doesn't need to presort the edges or maintain a priority queue in order to find the + minimum spanning tree. + Even though that doesn't help its complexity, since it still passes the edges logE + times, it is a bit more simple to code. + + Details: https://en.wikipedia.org/wiki/Bor%C5%AFvka%27s_algorithm +""" + + +class Graph: + def __init__(self, num_of_nodes: int) -> None: + """ + Arguments: + num_of_nodes - the number of nodes in the graph + Attributes: + m_v - the number of nodes in the graph. + m_edges - the list of edges. + m_component - the dictionary which stores the index of the component which + a node belongs to. + """ + + self.m_v = num_of_nodes + self.m_edges = [] + self.m_component = {} + + def add_edge(self, u_node: int, v_node: int, weight: int) -> None: + """Adds an edge in the format [first, second, edge weight] to graph.""" + + self.m_edges.append([u_node, v_node, weight]) + + def find_component(self, u_node: int) -> int: + """Propagates a new component throughout a given component.""" + + if self.m_component[u_node] == u_node: + return u_node + return self.find_component(self.m_component[u_node]) + + def set_component(self, u_node: int) -> None: + """Finds the component index of a given node""" + + if self.m_component[u_node] != u_node: + for k in self.m_component.keys(): + self.m_component[k] = self.find_component(k) + + def union(self, component_size: list, u_node: int, v_node: int) -> None: + """Union finds the roots of components for two nodes, compares the components + in terms of size, and attaches the smaller one to the larger one to form + single component""" + + if component_size[u_node] <= component_size[v_node]: + self.m_component[u_node] = v_node + component_size[v_node] += component_size[u_node] + self.set_component(u_node) + + elif component_size[u_node] >= component_size[v_node]: + self.m_component[v_node] = self.find_component(u_node) + component_size[u_node] += component_size[v_node] + self.set_component(v_node) + + def boruvka(self) -> None: + """Performs Borůvka's algorithm to find MST.""" + + # Initialize additional lists required to algorithm. + component_size = [] + mst_weight = 0 + + minimum_weight_edge = [-1] * self.m_v + + # A list of components (initialized to all of the nodes) + for node in range(self.m_v): + self.m_component.update({node: node}) + component_size.append(1) + + num_of_components = self.m_v + + while num_of_components > 1: + l_edges = len(self.m_edges) + for i in range(l_edges): + + u = self.m_edges[i][0] + v = self.m_edges[i][1] + w = self.m_edges[i][2] + + u_component = self.m_component[u] + v_component = self.m_component[v] + + if u_component != v_component: + """If the current minimum weight edge of component u doesn't + exist (is -1), or if it's greater than the edge we're + observing right now, we will assign the value of the edge + we're observing to it. + + If the current minimum weight edge of component v doesn't + exist (is -1), or if it's greater than the edge we're + observing right now, we will assign the value of the edge + we're observing to it""" + + if ( + minimum_weight_edge[u_component] == -1 + or minimum_weight_edge[u_component][2] > w + ): + minimum_weight_edge[u_component] = [u, v, w] + if ( + minimum_weight_edge[v_component] == -1 + or minimum_weight_edge[v_component][2] > w + ): + minimum_weight_edge[v_component] = [u, v, w] + + for node in range(self.m_v): + if minimum_weight_edge[node] != -1: + u = minimum_weight_edge[node][0] + v = minimum_weight_edge[node][1] + w = minimum_weight_edge[node][2] + + u_component = self.m_component[u] + v_component = self.m_component[v] + + if u_component != v_component: + mst_weight += w + self.union(component_size, u_component, v_component) + print( + "Added edge [" + + str(u) + + " - " + + str(v) + + "]\n" + + "Added weight: " + + str(w) + + "\n" + ) + num_of_components -= 1 + + minimum_weight_edge = [-1] * self.m_v + print("The total weight of the minimal spanning tree is: " + str(mst_weight)) + + +def test_vector() -> None: + """ + >>> g=Graph(8) + >>> g.add_edge(0, 1, 10) + >>> g.add_edge(0, 2, 6) + >>> g.add_edge(0, 3, 5) + >>> g.add_edge(1, 3, 15) + >>> g.add_edge(2, 3, 4) + >>> g.add_edge(3, 4, 8) + >>> g.add_edge(4, 5, 10) + >>> g.add_edge(4, 6, 6) + >>> g.add_edge(4, 7, 5) + >>> g.add_edge(5, 7, 15) + >>> g.add_edge(6, 7, 4) + >>> g.boruvka() + Added edge [0 - 3] + Added weight: 5 + + Added edge [0 - 1] + Added weight: 10 + + Added edge [2 - 3] + Added weight: 4 + + Added edge [4 - 7] + Added weight: 5 + + Added edge [4 - 5] + Added weight: 10 + + Added edge [6 - 7] + Added weight: 4 + + Added edge [3 - 4] + Added weight: 8 + + The total weight of the minimal spanning tree is: 46 + """ + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 78a5d3a5587ef649c2b4d2286cfb949886095467 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 24 Aug 2021 15:27:31 +0200 Subject: [PATCH 1543/2908] boruvka.py: A few simplifications and f-strings (#4660) * boruvka.py: A few simplifications and f-strings Python f-strings simplify the code and [should speed up execution](https://www.scivision.dev/python-f-string-speed). @srkchowdary2000 Your review, please. * updating DIRECTORY.md * fixup! Streamline the test Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 10 ++++++ graphs/boruvka.py | 84 ++++++++++++++++------------------------------- 2 files changed, 39 insertions(+), 55 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index adc9bb9e4699..41485f6f0ca4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -97,6 +97,7 @@ * [Peak Signal To Noise Ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) ## Computer Vision + * [Cnn Classification](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/cnn_classification.py) * [Harris Corner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harris_corner.py) * [Mean Threshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mean_threshold.py) @@ -300,6 +301,7 @@ * [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) + * [Boruvka](https://github.com/TheAlgorithms/Python/blob/master/graphs/boruvka.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) @@ -349,6 +351,7 @@ * [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) + * [Luhn](https://github.com/TheAlgorithms/Python/blob/master/hashes/luhn.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) @@ -421,10 +424,12 @@ * [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) + * [Check Valid Ip Address](https://github.com/TheAlgorithms/Python/blob/master/maths/check_valid_ip_address.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) * [Decimal Isolate](https://github.com/TheAlgorithms/Python/blob/master/maths/decimal_isolate.py) + * [Double Factorial Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/double_factorial_recursive.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) * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_gcd.py) @@ -539,6 +544,7 @@ ## Other * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) + * [Date To Weekday](https://github.com/TheAlgorithms/Python/blob/master/other/date_to_weekday.py) * [Davis–Putnam–Logemann–Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davis–putnam–logemann–loveland.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) @@ -854,6 +860,7 @@ * [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) + * [Exchange Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/exchange_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) @@ -893,6 +900,7 @@ ## Strings * [Aho Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho_corasick.py) + * [Alternative String Arrange](https://github.com/TheAlgorithms/Python/blob/master/strings/alternative_string_arrange.py) * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/anagrams.py) * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/strings/autocomplete_using_trie.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) @@ -902,6 +910,7 @@ * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/strings/detecting_english_programmatically.py) * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/strings/frequency_finder.py) + * [Indian Phone Validator](https://github.com/TheAlgorithms/Python/blob/master/strings/indian_phone_validator.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) @@ -941,6 +950,7 @@ * [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) + * [Random Anime Character](https://github.com/TheAlgorithms/Python/blob/master/web_programming/random_anime_character.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/graphs/boruvka.py b/graphs/boruvka.py index b95bcc39850e..3fa5c6fd2a26 100644 --- a/graphs/boruvka.py +++ b/graphs/boruvka.py @@ -1,11 +1,12 @@ """Borůvka's algorithm. - Determines the minimum spanning tree(MST) of a graph using the Borůvka's algorithm. + Determines the minimum spanning tree (MST) of a graph using the Borůvka's algorithm. Borůvka's algorithm is a greedy algorithm for finding a minimum spanning tree in a - graph,or a minimum spanning forest in the case of a graph that is not connected. + connected graph, or a minimum spanning forest if a graph that is not connected. The time complexity of this algorithm is O(ELogV), where E represents the number of edges, while V represents the number of nodes. + O(number_of_edges Log number_of_nodes) The space complexity of this algorithm is O(V + E), since we have to keep a couple of lists whose sizes are equal to the number of nodes, as well as keep all the @@ -19,7 +20,7 @@ doesn't need to presort the edges or maintain a priority queue in order to find the minimum spanning tree. Even though that doesn't help its complexity, since it still passes the edges logE - times, it is a bit more simple to code. + times, it is a bit simpler to code. Details: https://en.wikipedia.org/wiki/Bor%C5%AFvka%27s_algorithm """ @@ -31,13 +32,13 @@ def __init__(self, num_of_nodes: int) -> None: Arguments: num_of_nodes - the number of nodes in the graph Attributes: - m_v - the number of nodes in the graph. + m_num_of_nodes - the number of nodes in the graph. m_edges - the list of edges. m_component - the dictionary which stores the index of the component which a node belongs to. """ - self.m_v = num_of_nodes + self.m_num_of_nodes = num_of_nodes self.m_edges = [] self.m_component = {} @@ -57,7 +58,7 @@ def set_component(self, u_node: int) -> None: """Finds the component index of a given node""" if self.m_component[u_node] != u_node: - for k in self.m_component.keys(): + for k in self.m_component: self.m_component[k] = self.find_component(k) def union(self, component_size: list, u_node: int, v_node: int) -> None: @@ -82,22 +83,18 @@ def boruvka(self) -> None: component_size = [] mst_weight = 0 - minimum_weight_edge = [-1] * self.m_v + minimum_weight_edge = [-1] * self.m_num_of_nodes # A list of components (initialized to all of the nodes) - for node in range(self.m_v): + for node in range(self.m_num_of_nodes): self.m_component.update({node: node}) component_size.append(1) - num_of_components = self.m_v + num_of_components = self.m_num_of_nodes while num_of_components > 1: - l_edges = len(self.m_edges) - for i in range(l_edges): - - u = self.m_edges[i][0] - v = self.m_edges[i][1] - w = self.m_edges[i][2] + for edge in self.m_edges: + u, v, w = edge u_component = self.m_component[u] v_component = self.m_component[v] @@ -113,22 +110,16 @@ def boruvka(self) -> None: observing right now, we will assign the value of the edge we're observing to it""" - if ( - minimum_weight_edge[u_component] == -1 - or minimum_weight_edge[u_component][2] > w - ): - minimum_weight_edge[u_component] = [u, v, w] - if ( - minimum_weight_edge[v_component] == -1 - or minimum_weight_edge[v_component][2] > w - ): - minimum_weight_edge[v_component] = [u, v, w] - - for node in range(self.m_v): - if minimum_weight_edge[node] != -1: - u = minimum_weight_edge[node][0] - v = minimum_weight_edge[node][1] - w = minimum_weight_edge[node][2] + for component in (u_component, v_component): + if ( + minimum_weight_edge[component] == -1 + or minimum_weight_edge[component][2] > w + ): + minimum_weight_edge[component] = [u, v, w] + + for edge in minimum_weight_edge: + if edge != -1: + u, v, w = edge u_component = self.m_component[u] v_component = self.m_component[v] @@ -136,36 +127,19 @@ def boruvka(self) -> None: if u_component != v_component: mst_weight += w self.union(component_size, u_component, v_component) - print( - "Added edge [" - + str(u) - + " - " - + str(v) - + "]\n" - + "Added weight: " - + str(w) - + "\n" - ) + print(f"Added edge [{u} - {v}]\nAdded weight: {w}\n") num_of_components -= 1 - minimum_weight_edge = [-1] * self.m_v - print("The total weight of the minimal spanning tree is: " + str(mst_weight)) + minimum_weight_edge = [-1] * self.m_num_of_nodes + print(f"The total weight of the minimal spanning tree is: {mst_weight}") def test_vector() -> None: """ - >>> g=Graph(8) - >>> g.add_edge(0, 1, 10) - >>> g.add_edge(0, 2, 6) - >>> g.add_edge(0, 3, 5) - >>> g.add_edge(1, 3, 15) - >>> g.add_edge(2, 3, 4) - >>> g.add_edge(3, 4, 8) - >>> g.add_edge(4, 5, 10) - >>> g.add_edge(4, 6, 6) - >>> g.add_edge(4, 7, 5) - >>> g.add_edge(5, 7, 15) - >>> g.add_edge(6, 7, 4) + >>> g = Graph(8) + >>> for u_v_w in ((0, 1, 10), (0, 2, 6), (0, 3, 5), (1, 3, 15), (2, 3, 4), + ... (3, 4, 8), (4, 5, 10), (4, 6, 6), (4, 7, 5), (5, 7, 15), (6, 7, 4)): + ... g.add_edge(*u_v_w) >>> g.boruvka() Added edge [0 - 3] Added weight: 5 From 5e7eed610ce81fa96e033f4d2a1781ed8637cb41 Mon Sep 17 00:00:00 2001 From: imp Date: Wed, 25 Aug 2021 19:35:36 +0800 Subject: [PATCH 1544/2908] [mypy] Fix type annotations for strings (#4641) * Fix mypy error for min_cost_string_conversion.py * Fix mypy error for manacher.py * Fix mypy error for aho_corasick.py --- strings/aho_corasick.py | 22 +++++++++++++--------- strings/manacher.py | 25 +++++++++++++------------ strings/min_cost_string_conversion.py | 8 +++----- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/strings/aho_corasick.py b/strings/aho_corasick.py index b959dbd58c32..712cb338aa7e 100644 --- a/strings/aho_corasick.py +++ b/strings/aho_corasick.py @@ -3,8 +3,8 @@ class Automaton: - def __init__(self, keywords: List[str]): - self.adlist = list() + def __init__(self, keywords: list[str]): + self.adlist: list[dict] = list() self.adlist.append( {"value": "", "next_states": [], "fail_state": 0, "output": []} ) @@ -22,9 +22,8 @@ def find_next_state(self, current_state: int, char: str) -> Union[int, None]: def add_keyword(self, keyword: str) -> None: 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: + next_state = self.find_next_state(current_state, character) + if next_state is None: self.adlist.append( { "value": character, @@ -35,10 +34,12 @@ def add_keyword(self, keyword: str) -> None: ) self.adlist[current_state]["next_states"].append(len(self.adlist) - 1) current_state = len(self.adlist) - 1 + else: + current_state = next_state self.adlist[current_state]["output"].append(keyword) def set_fail_transitions(self) -> None: - q = deque() + q: deque = deque() for node in self.adlist[0]["next_states"]: q.append(node) self.adlist[node]["fail_state"] = 0 @@ -68,7 +69,9 @@ def search_in(self, string: str) -> Dict[str, List[int]]: >>> 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 occurrences + result: dict = ( + dict() + ) # returns a dict with keywords and list of its occurrences current_state = 0 for i in range(len(string)): while ( @@ -76,10 +79,11 @@ def search_in(self, string: str) -> Dict[str, List[int]]: 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: + next_state = self.find_next_state(current_state, string[i]) + if next_state is None: current_state = 0 else: + current_state = next_state for key in self.adlist[current_state]["output"]: if not (key in result): result[key] = [] diff --git a/strings/manacher.py b/strings/manacher.py index 5476e06839b7..e6ea71cde12f 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -35,27 +35,28 @@ def palindromic_string(input_string: str) -> str: 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) + start = 0 + for j in range(len(new_input_string)): + k = 1 if j > r else min(length[l + r - j] // 2, r - j + 1) while ( - i - k >= 0 - and i + k < len(new_input_string) - and new_input_string[k + i] == new_input_string[i - k] + j - k >= 0 + and j + k < len(new_input_string) + and new_input_string[k + j] == new_input_string[j - k] ): k += 1 - length[i] = 2 * k - 1 + length[j] = 2 * k - 1 # 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 # noqa: E741 - r = i + k - 1 + if j + k - 1 > r: + l = j - k + 1 # noqa: E741 + r = j + k - 1 # update max_length and start position - if max_length < length[i]: - max_length = length[i] - start = i + if max_length < length[j]: + max_length = length[j] + start = j # create that string s = new_input_string[start - max_length // 2 : start + max_length // 2 + 1] diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index e990aaa2679b..147bc6fc740a 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -1,5 +1,3 @@ -from typing import List, Tuple - """ Algorithm for calculating the most cost-efficient sequence for converting one string into another. @@ -18,7 +16,7 @@ def compute_transform_tables( replace_cost: int, delete_cost: int, insert_cost: int, -) -> Tuple[List[int], List[str]]: +) -> tuple[list[list[int]], list[list[str]]]: source_seq = list(source_string) destination_seq = list(destination_string) len_source_seq = len(source_seq) @@ -28,7 +26,7 @@ def compute_transform_tables( [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) + ["0" for _ in range(len_destination_seq + 1)] for _ in range(len_source_seq + 1) ] for i in range(1, len_source_seq + 1): @@ -59,7 +57,7 @@ def compute_transform_tables( return costs, ops -def assemble_transformation(ops: List[str], i: int, j: int) -> List[str]: +def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: if i == 0 and j == 0: return [] else: From 46e56fa6f2e473d1300846b2b96e56f498872400 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 27 Aug 2021 11:45:14 +0200 Subject: [PATCH 1545/2908] luhn.py: Favor list comprehensions over maps (#4663) * luhn.py: Favor list comprehensions over maps As discussed in CONTRIBUTING.md. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- hashes/luhn.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/hashes/luhn.py b/hashes/luhn.py index 69e7b4ccf59b..81014120dd80 100644 --- a/hashes/luhn.py +++ b/hashes/luhn.py @@ -4,39 +4,34 @@ def is_luhn(string: str) -> bool: """ - Perform Luhn validation on input string + Perform Luhn validation on an input string Algorithm: * Double every other digit starting from 2nd last digit. * Subtract 9 if number is greater than 9. * Sum the numbers * - >>> test_cases = [79927398710, 79927398711, 79927398712, 79927398713, + >>> test_cases = (79927398710, 79927398711, 79927398712, 79927398713, ... 79927398714, 79927398715, 79927398716, 79927398717, 79927398718, - ... 79927398719] - >>> test_cases = list(map(str, test_cases)) - >>> list(map(is_luhn, test_cases)) + ... 79927398719) + >>> [is_luhn(str(test_case)) for test_case in test_cases] [False, False, False, True, False, False, False, False, False, False] """ check_digit: int _vector: List[str] = list(string) __vector, check_digit = _vector[:-1], int(_vector[-1]) - vector: List[int] = [*map(int, __vector)] + vector: List[int] = [int(digit) for digit in __vector] vector.reverse() - for idx, i in enumerate(vector): - - if idx & 1 == 0: - doubled: int = vector[idx] * 2 + for i, digit in enumerate(vector): + if i & 1 == 0: + doubled: int = digit * 2 if doubled > 9: doubled -= 9 - check_digit += doubled else: - check_digit += i + check_digit += digit - if (check_digit) % 10 == 0: - return True - return False + return check_digit % 10 == 0 if __name__ == "__main__": @@ -44,3 +39,4 @@ def is_luhn(string: str) -> bool: doctest.testmod() assert is_luhn("79927398713") + assert not is_luhn("79927398714") From 8e5c3536c728dd7451ca301dc2d5bfb3f68b0e1a Mon Sep 17 00:00:00 2001 From: arfy slowy Date: Sun, 29 Aug 2021 01:07:10 +0700 Subject: [PATCH 1546/2908] [fixed] unused variable, standalone running, import doctest module (#4673) * [fixed] unused variable, standalone running, import doctest module information [standalone running](https://www.geeksforgeeks.org/what-does-the-if-__name__-__main__-do/) Signed-off-by: slowy07 * Update other/fischer_yates_shuffle.py Co-authored-by: Christian Clauss * [fixed] change to tuple and fixing callfunction Signed-off-by: slowy07 * Update matrix/spiral_print.py Co-authored-by: Christian Clauss * Update matrix/spiral_print.py Co-authored-by: Christian Clauss * fixing Co-authored-by: Christian Clauss * [fixed] sprial matrix Signed-off-by: slowy07 * Update spiral_print.py * Update spiral_print.py * Update spiral_print.py * Update spiral_print.py Co-authored-by: Christian Clauss --- ...h_fibonacci_using_matrix_exponentiation.py | 3 ++ matrix/spiral_print.py | 34 +++++++++---------- other/fischer_yates_shuffle.py | 6 ++-- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 8c39de0f23b6..341a02e1a95d 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -88,4 +88,7 @@ def main(): if __name__ == "__main__": + import doctest + + doctest.testmod() main() diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 21dab76156e9..6f699c1ab662 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -4,36 +4,35 @@ Matrix must satisfy below conditions i) matrix should be only one or two dimensional - ii)column of all the row should be equal + ii) number of column of all rows should be equal """ +from collections.abc import Iterable -def checkMatrix(a): + +def check_matrix(matrix): # 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): + if matrix and isinstance(matrix, Iterable): + if isinstance(matrix[0], Iterable): + prev_len = 0 + for row in matrix: + if prev_len == 0: + prev_len = len(row) result = True else: - result = False + result = prev_len == len(row) else: result = True else: result = False + return result def spiralPrint(a): - - if checkMatrix(a) and len(a) > 0: - + if check_matrix(a) and len(a) > 0: matRow = len(a) - if type(a[0]) == list: + if isinstance(a[0], Iterable): matCol = len(a[0]) else: for dat in a: @@ -64,5 +63,6 @@ def spiralPrint(a): # driver code -a = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] -spiralPrint(a) +if __name__ == "__main__": + a = ([1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]) + spiralPrint(a) diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index 6eec738c02e1..035fcb482380 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -8,8 +8,8 @@ import random -def FYshuffle(list): - for i in range(len(list)): +def fisher_yates_shuffle(data: list) -> list: + for _ 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] @@ -21,4 +21,4 @@ def FYshuffle(list): strings = ["python", "says", "hello", "!"] print("Fisher-Yates Shuffle:") print("List", integers, strings) - print("FY Shuffle", FYshuffle(integers), FYshuffle(strings)) + print("FY Shuffle", fisher_yates_shuffle(integers), fisher_yates_shuffle(strings)) From 3acca3d1d188f89c6aa0fb4c0139ac62426ea296 Mon Sep 17 00:00:00 2001 From: Aswin Murali Date: Mon, 30 Aug 2021 13:36:59 +0530 Subject: [PATCH 1547/2908] Fix type annotations for integer_partition.py #4052 (#4689) --- dynamic_programming/integer_partition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dynamic_programming/integer_partition.py b/dynamic_programming/integer_partition.py index 4eb06348ce84..8ed2e51bd4bd 100644 --- a/dynamic_programming/integer_partition.py +++ b/dynamic_programming/integer_partition.py @@ -6,8 +6,8 @@ """ -def partition(m): - memo = [[0 for _ in range(m)] for _ in range(m + 1)] +def partition(m: int) -> int: + memo: list[list[int]] = [[0 for _ in range(m)] for _ in range(m + 1)] for i in range(m + 1): memo[i][0] = 1 From 097f83023866d625a38c891a46ce3a70d73d7f63 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 31 Aug 2021 06:56:15 +0200 Subject: [PATCH 1548/2908] Avoid mutable default arguments (#4691) --- graphs/eulerian_path_and_circuit_for_undirected_graph.py | 4 ++-- graphs/frequent_pattern_graph_miner.py | 4 ++-- maths/radix2_fft.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/graphs/eulerian_path_and_circuit_for_undirected_graph.py b/graphs/eulerian_path_and_circuit_for_undirected_graph.py index 7850933b0201..fa4f73abd86f 100644 --- a/graphs/eulerian_path_and_circuit_for_undirected_graph.py +++ b/graphs/eulerian_path_and_circuit_for_undirected_graph.py @@ -6,8 +6,8 @@ # using dfs for finding eulerian path traversal -def dfs(u, graph, visited_edge, path=[]): - path = path + [u] +def dfs(u, graph, visited_edge, path=None): + path = (path or []) + [u] for v in graph[u]: if visited_edge[u][v] is False: visited_edge[u][v], visited_edge[v][u] = True, True diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index 8f344b7bd3ae..548ce3c54ffe 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -168,11 +168,11 @@ def construct_graph(cluster, nodes): return graph -def myDFS(graph, start, end, path=[]): +def myDFS(graph, start, end, path=None): """ find different DFS walk from given node to Header node """ - path = path + [start] + path = (path or []) + [start] if start == end: paths.append(path) for node in graph[start]: diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index de87071e5440..9fc9f843e685 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -49,10 +49,10 @@ class FFT: 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]): + def __init__(self, polyA=None, polyB=None): # Input as list - self.polyA = list(polyA)[:] - self.polyB = list(polyB)[:] + self.polyA = list(polyA or [0])[:] + self.polyB = list(polyB or [0])[:] # Remove leading zero coefficients while self.polyA[-1] == 0: From ef9827166e778879e0bc5847c4bcee6720073657 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 31 Aug 2021 07:56:19 +0200 Subject: [PATCH 1549/2908] Approve functions used as default arguments (#4699) * Approve functions used as default argumenets * The default value for **seed** is the result of a function call The default value for **seed** is the result of a function call which is not normally recommended and causes flake8-bugbear to raise a B008 error. However, in this case, it is accptable because `LinearCongruentialGenerator.__init__()` will only be called once per instance and it ensures that each instance will generate a unique sequence of numbers. * The default value for **backend** is the result of a function call The default value for **backend** is the result of a function call which is not normally recommended and causes flake8-bugbear to raise a B008 error. However, in this case, it is accptable because `Aer.get_backend()` is called when the function is definition and that same backend is then reused for function calls. * Update linear_congruential_generator.py * Update ripple_adder_classic.py * Update ripple_adder_classic.py * Update ripple_adder_classic.py * Update ripple_adder_classic.py * Update ripple_adder_classic.py --- other/linear_congruential_generator.py | 8 +++++++- quantum/ripple_adder_classic.py | 14 +++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/other/linear_congruential_generator.py b/other/linear_congruential_generator.py index f8b604b8562d..777ee6355b9b 100644 --- a/other/linear_congruential_generator.py +++ b/other/linear_congruential_generator.py @@ -8,7 +8,13 @@ class LinearCongruentialGenerator: A pseudorandom number generator. """ - def __init__(self, multiplier, increment, modulo, seed=int(time())): + # The default value for **seed** is the result of a function call which is not + # normally recommended and causes flake8-bugbear to raise a B008 error. However, + # in this case, it is accptable because `LinearCongruentialGenerator.__init__()` + # will only be called once per instance and it ensures that each instance will + # generate a unique sequence of numbers. + + def __init__(self, multiplier, increment, modulo, seed=int(time())): # noqa: B008 """ These parameters are saved and used when nextNumber() is called. diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py index dc0c2103b2e5..8539a62afd52 100644 --- a/quantum/ripple_adder_classic.py +++ b/quantum/ripple_adder_classic.py @@ -53,8 +53,16 @@ def full_adder( circuit.cx(input1_loc, input2_loc) +# The default value for **backend** is the result of a function call which is not +# normally recommended and causes flake8-bugbear to raise a B008 error. However, +# in this case, this is accptable because `Aer.get_backend()` is called when the +# function is defined and that same backend is then reused for all function calls. + + def ripple_adder( - val1: int, val2: int, backend: BaseBackend = Aer.get_backend("qasm_simulator") + val1: int, + val2: int, + backend: BaseBackend = Aer.get_backend("qasm_simulator"), # noqa: B008 ) -> int: """ Quantum Equivalent of a Ripple Adder Circuit @@ -63,7 +71,7 @@ def ripple_adder( Currently only adds 'emulated' Classical Bits but nothing prevents us from doing this with hadamard'd bits :) - Only supports adding +ve Integers + Only supports adding positive integers >>> ripple_adder(3, 4) 7 @@ -99,7 +107,7 @@ def ripple_adder( 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) + return int(list(res.get_counts())[0], 2) if __name__ == "__main__": From 757d4fb84f77865fc569aae4129999faf75c970f Mon Sep 17 00:00:00 2001 From: Kiran Hipparagi <49370990+KiranHipparagi@users.noreply.github.com> Date: Wed, 1 Sep 2021 01:36:49 +0530 Subject: [PATCH 1550/2908] Added Dutch National Flag algorithm #4636 (#4639) * Added Dutch national flag sort Algorithm * Changed file name to dnf_sort.py * Added descriptive name and type hint Added descriptive name and type hint for parameter with doctest for the function dnf_sort. * Added test cases * Added doctest cases * Update sorts/dnf_sort.py * Added doctest for dutch_national_flag_sort sorts/dnf_sort.py * Update sorts/dnf_sort.py * Added doctest for the function dutch_national_flag_sort * update file as per black code formatter * Update dnf_sort.py * Update and rename dnf_sort.py to dutch_national_flag_sort.py Co-authored-by: Christian Clauss --- sorts/dutch_national_flag_sort.py | 100 ++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 sorts/dutch_national_flag_sort.py diff --git a/sorts/dutch_national_flag_sort.py b/sorts/dutch_national_flag_sort.py new file mode 100644 index 000000000000..79afefa73afe --- /dev/null +++ b/sorts/dutch_national_flag_sort.py @@ -0,0 +1,100 @@ +""" +A pure implementation of Dutch national flag (DNF) sort algorithm in Python. +Dutch National Flag algorithm is an algorithm originally designed by Edsger Dijkstra. +It is the most optimal sort for 3 unique values (eg. 0, 1, 2) in a sequence. DNF can +sort a sequence of n size with [0 <= a[i] <= 2] at guaranteed O(n) complexity in a +single pass. + +The flag of the Netherlands consists of three colors: white, red, and blue. +The task is to randomly arrange balls of white, red, and blue in such a way that balls +of the same color are placed together. DNF sorts a sequence of 0, 1, and 2's in linear +time that does not consume any extra space. This algorithm can be implemented only on +a sequence that contains three unique elements. + +1) Time complexity is O(n). +2) Space complexity is O(1). + +More info on: https://en.wikipedia.org/wiki/Dutch_national_flag_problem + +For doctests run following command: +python3 -m doctest -v dutch_national_flag_sort.py + +For manual testing run: +python dnf_sort.py +""" + + +# Python program to sort a sequence containing only 0, 1 and 2 in a single pass. +red = 0 # The first color of the flag. +white = 1 # The second color of the flag. +blue = 2 # The third color of the flag. +colors = (red, white, blue) + + +def dutch_national_flag_sort(sequence: list) -> list: + """ + A pure Python implementation of Dutch National Flag sort algorithm. + :param data: 3 unique integer values (e.g., 0, 1, 2) in an sequence + :return: The same collection in ascending order + + >>> dutch_national_flag_sort([]) + [] + >>> dutch_national_flag_sort([0]) + [0] + >>> dutch_national_flag_sort([2, 1, 0, 0, 1, 2]) + [0, 0, 1, 1, 2, 2] + >>> dutch_national_flag_sort([0, 1, 1, 0, 1, 2, 1, 2, 0, 0, 0, 1]) + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2] + >>> dutch_national_flag_sort("abacab") + Traceback (most recent call last): + ... + ValueError: The elements inside the sequence must contains only (0, 1, 2) values + >>> dutch_national_flag_sort("Abacab") + Traceback (most recent call last): + ... + ValueError: The elements inside the sequence must contains only (0, 1, 2) values + >>> dutch_national_flag_sort([3, 2, 3, 1, 3, 0, 3]) + Traceback (most recent call last): + ... + ValueError: The elements inside the sequence must contains only (0, 1, 2) values + >>> dutch_national_flag_sort([-1, 2, -1, 1, -1, 0, -1]) + Traceback (most recent call last): + ... + ValueError: The elements inside the sequence must contains only (0, 1, 2) values + >>> dutch_national_flag_sort([1.1, 2, 1.1, 1, 1.1, 0, 1.1]) + Traceback (most recent call last): + ... + ValueError: The elements inside the sequence must contains only (0, 1, 2) values + """ + if not sequence: + return [] + if len(sequence) == 1: + return list(sequence) + low = 0 + high = len(sequence) - 1 + mid = 0 + while mid <= high: + if sequence[mid] == colors[0]: + sequence[low], sequence[mid] = sequence[mid], sequence[low] + low += 1 + mid += 1 + elif sequence[mid] == colors[1]: + mid += 1 + elif sequence[mid] == colors[2]: + sequence[mid], sequence[high] = sequence[high], sequence[mid] + high -= 1 + else: + raise ValueError( + f"The elements inside the sequence must contains only {colors} values" + ) + return sequence + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + user_input = input("Enter numbers separated by commas:\n").strip() + unsorted = [int(item.strip()) for item in user_input.split(",")] + print(f"{dutch_national_flag_sort(unsorted)}") From c1b15a86baabd110347bd4d478d88bc2820824e5 Mon Sep 17 00:00:00 2001 From: imp Date: Fri, 3 Sep 2021 17:49:23 +0800 Subject: [PATCH 1551/2908] [mypy] Fix type annotations for dynamic programming (#4687) * Fix mypy error for knapsack.py * Fix mypy error for longest_increasing_subsequence * Fix mypy error for fractional_knapsack_2.py --- dynamic_programming/fractional_knapsack_2.py | 19 ++++++------------- dynamic_programming/knapsack.py | 2 +- .../longest_increasing_subsequence.py | 2 +- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/dynamic_programming/fractional_knapsack_2.py b/dynamic_programming/fractional_knapsack_2.py index cae57738311b..bd776723c146 100644 --- a/dynamic_programming/fractional_knapsack_2.py +++ b/dynamic_programming/fractional_knapsack_2.py @@ -7,7 +7,7 @@ def fractional_knapsack( value: list[int], weight: list[int], capacity: int -) -> tuple[int, list[int]]: +) -> tuple[float, list[float]]: """ >>> value = [1, 3, 5, 7, 9] >>> weight = [0.9, 0.7, 0.5, 0.3, 0.1] @@ -32,8 +32,8 @@ def fractional_knapsack( 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) + max_value: float = 0 + fractions: list[float] = [0] * len(value) for i in index: if weight[i] <= capacity: fractions[i] = 1 @@ -48,13 +48,6 @@ def fractional_knapsack( 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) + import doctest + + doctest.testmod() diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 69e54c00aa4e..804d7d4f12f5 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -91,7 +91,7 @@ def knapsack_with_example_solution(W: int, wt: list, val: list): ) optimal_val, dp_table = knapsack(W, wt, val, num_items) - example_optional_set = set() + example_optional_set: set = set() _construct_solution(dp_table, wt, num_items, W, example_optional_set) return optimal_val, example_optional_set diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index f5ca8a2b5cdc..a029f9be7d98 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -36,7 +36,7 @@ def longest_subsequence(array: list[int]) -> list[int]: # This function is recu pivot = array[0] isFound = False i = 1 - longest_subseq = [] + longest_subseq: list[int] = [] while not isFound and i < array_length: if array[i] < pivot: isFound = True From 5d5831bdd07fff31278c84c4e7b313d633abf752 Mon Sep 17 00:00:00 2001 From: Aviv Faraj <73610201+avivfaraj@users.noreply.github.com> Date: Mon, 6 Sep 2021 17:57:18 -0400 Subject: [PATCH 1552/2908] Physics new code (#4709) * added gamma_function * Add files via upload * Resolved issue with str.format And also changed output to math notation * Update gamma_function.py * Rename physics/gamma_function.py to maths/gamma_recursive.py * Fixes: #4709 Fixed issues for pre-commit test * Fixes: #4709 solved issues with doctests And comments * Fixes: #4709 Added failed tests to doctest * Align with Python's Standard Library math.gamma() Replicate the exceptions of https://docs.python.org/3/library/math.html#math.gamma * Update gamma_recursive.py * Update gamma_recursive.py * Update gamma_recursive.py * Update gamma_recursive.py Co-authored-by: Christian Clauss --- maths/gamma_recursive.py | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 maths/gamma_recursive.py diff --git a/maths/gamma_recursive.py b/maths/gamma_recursive.py new file mode 100644 index 000000000000..683d7adb1aa8 --- /dev/null +++ b/maths/gamma_recursive.py @@ -0,0 +1,78 @@ +""" +Gamma function is a very useful tool in math and physics. +It helps calculating complex integral in a convenient way. +for more info: https://en.wikipedia.org/wiki/Gamma_function + +Python's Standard Library math.gamma() function overflows around gamma(171.624). +""" +from math import pi, sqrt + + +def gamma(num: float) -> float: + """ + Calculates the value of Gamma function of num + where num is either an integer (1, 2, 3..) or a half-integer (0.5, 1.5, 2.5 ...). + Implemented using recursion + Examples: + >>> from math import isclose, gamma as math_gamma + >>> gamma(0.5) + 1.7724538509055159 + >>> gamma(2) + 1.0 + >>> gamma(3.5) + 3.3233509704478426 + >>> gamma(171.5) + 9.483367566824795e+307 + >>> all(isclose(gamma(num), math_gamma(num)) for num in (0.5, 2, 3.5, 171.5)) + True + >>> gamma(0) + Traceback (most recent call last): + ... + ValueError: math domain error + >>> gamma(-1.1) + Traceback (most recent call last): + ... + ValueError: math domain error + >>> gamma(-4) + Traceback (most recent call last): + ... + ValueError: math domain error + >>> gamma(172) + Traceback (most recent call last): + ... + OverflowError: math range error + >>> gamma(1.1) + Traceback (most recent call last): + ... + NotImplementedError: num must be an integer or a half-integer + """ + if num <= 0: + raise ValueError("math domain error") + if num > 171.5: + raise OverflowError("math range error") + elif num - int(num) not in (0, 0.5): + raise NotImplementedError("num must be an integer or a half-integer") + elif num == 0.5: + return sqrt(pi) + else: + return 1.0 if num == 1 else (num - 1) * gamma(num - 1) + + +def test_gamma() -> None: + """ + >>> test_gamma() + """ + assert gamma(0.5) == sqrt(pi) + assert gamma(1) == 1.0 + assert gamma(2) == 1.0 + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + num = 1 + while num: + num = float(input("Gamma of: ")) + print(f"gamma({num}) = {gamma(num)}") + print("\nEnter 0 to exit...") From cecf43d6481173e831af829da77911e1a3868a6c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 7 Sep 2021 13:37:03 +0200 Subject: [PATCH 1553/2908] Pyupgrade to Python 3.9 (#4718) * Pyupgrade to Python 3.9 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 ++- arithmetic_analysis/in_static_equilibrium.py | 4 +-- arithmetic_analysis/lu_decomposition.py | 4 +-- .../newton_forward_interpolation.py | 4 +-- arithmetic_analysis/newton_raphson.py | 5 ++-- backtracking/all_combinations.py | 12 ++++----- backtracking/all_permutations.py | 14 +++++----- backtracking/all_subsequences.py | 10 ++++--- backtracking/coloring.py | 7 +++-- backtracking/hamiltonian_cycle.py | 7 +++-- backtracking/knight_tour.py | 10 +++---- backtracking/minimax.py | 5 ++-- backtracking/n_queens.py | 8 +++--- backtracking/n_queens_math.py | 12 ++++----- backtracking/rat_in_maze.py | 6 ++--- backtracking/sudoku.py | 8 +++--- backtracking/sum_of_subsets.py | 14 +++++----- blockchain/chinese_remainder_theorem.py | 4 +-- blockchain/diophantine_equation.py | 6 ++--- blockchain/modular_division.py | 6 ++--- boolean_algebra/quine_mc_cluskey.py | 12 ++++----- cellular_automata/conways_game_of_life.py | 5 +--- ciphers/caesar_cipher.py | 9 ++++--- ciphers/decrypt_caesar_with_chi_squared.py | 7 +++-- ciphers/diffie.py | 4 +-- ciphers/shuffled_shift_cipher.py | 5 ++-- compression/huffman.py | 2 +- conversions/molecular_chemistry.py | 2 +- conversions/prefix_conversions.py | 11 ++++---- data_structures/binary_tree/avl_tree.py | 25 +++++++++--------- .../binary_tree/basic_binary_tree.py | 10 +++---- .../binary_search_tree_recursive.py | 24 ++++++++--------- .../binary_tree/binary_tree_traversals.py | 7 ++--- .../binary_tree/lazy_segment_tree.py | 5 ++-- .../binary_tree/merge_two_binary_trees.py | 10 +++---- data_structures/binary_tree/red_black_tree.py | 26 ++++++++++--------- data_structures/binary_tree/treap.py | 21 +++++++-------- data_structures/binary_tree/wavelet_tree.py | 7 +++-- data_structures/hashing/hash_table.py | 2 +- .../hashing/hash_table_with_linked_list.py | 2 +- data_structures/heap/heap.py | 12 +++++---- data_structures/heap/randomized_heap.py | 16 ++++++------ data_structures/heap/skew_heap.py | 14 +++++----- .../linked_list/merge_two_lists.py | 5 ++-- data_structures/linked_list/print_reverse.py | 4 +-- data_structures/linked_list/skip_list.py | 7 +++-- .../stacks/evaluate_postfix_notations.py | 7 ++--- data_structures/stacks/linked_stack.py | 6 +++-- data_structures/stacks/stack.py | 4 +-- divide_and_conquer/convex_hull.py | 19 +++++++------- divide_and_conquer/kth_order_statistic.py | 5 ++-- divide_and_conquer/mergesort.py | 6 ++--- divide_and_conquer/peak.py | 4 +-- electronics/electric_power.py | 5 ++-- electronics/ohms_law.py | 4 +-- graphs/basic_graphs.py | 8 +++--- graphs/bellman_ford.py | 8 +++--- graphs/bfs_zero_one_shortest_path.py | 14 +++++----- graphs/bidirectional_a_star.py | 7 ++--- graphs/bidirectional_breadth_first_search.py | 10 +++---- graphs/breadth_first_search.py | 7 +++-- graphs/breadth_first_search_shortest_path.py | 4 +-- graphs/depth_first_search.py | 5 +--- graphs/greedy_best_first.py | 8 +++--- graphs/minimum_spanning_tree_kruskal.py | 2 +- graphs/minimum_spanning_tree_prims2.py | 7 ++--- graphs/page_rank.py | 2 +- graphs/scc_kosaraju.py | 14 +++++----- hashes/luhn.py | 6 ++--- knapsack/knapsack.py | 5 ++-- linear_algebra/src/lib.py | 26 +++++++++---------- machine_learning/similarity_search.py | 5 ++-- maths/area_under_curve.py | 9 ++++--- maths/average_mean.py | 4 +-- maths/average_median.py | 4 +-- maths/entropy.py | 6 ++--- maths/euclidean_distance.py | 2 ++ maths/extended_euclidean_algorithm.py | 4 +-- maths/hardy_ramanujanalgo.py | 2 +- maths/line_length.py | 10 ++++--- maths/max_sum_sliding_window.py | 4 +-- maths/median_of_two_arrays.py | 4 +-- maths/numerical_integration.py | 9 ++++--- maths/sieve_of_eratosthenes.py | 5 ++-- maths/volume.py | 5 ++-- matrix/searching_in_sorted_matrix.py | 4 +-- other/date_to_weekday.py | 2 +- .../davisb_putnamb_logemannb_loveland.py | 24 ++++++++--------- other/lfu_cache.py | 6 +++-- other/lru_cache.py | 6 +++-- project_euler/problem_001/sol1.py | 2 +- project_euler/problem_001/sol5.py | 2 +- project_euler/problem_006/sol3.py | 2 +- project_euler/problem_008/sol2.py | 5 +--- project_euler/problem_012/sol2.py | 2 +- project_euler/problem_013/sol1.py | 2 +- project_euler/problem_014/sol2.py | 6 ++--- project_euler/problem_020/sol2.py | 2 +- project_euler/problem_021/sol1.py | 8 +++--- project_euler/problem_033/sol1.py | 5 ++-- project_euler/problem_036/sol1.py | 5 ++-- project_euler/problem_038/sol1.py | 5 ++-- project_euler/problem_049/sol1.py | 2 +- project_euler/problem_050/sol1.py | 4 +-- project_euler/problem_051/sol1.py | 6 ++--- project_euler/problem_054/sol1.py | 4 +-- project_euler/problem_056/sol1.py | 8 +++--- project_euler/problem_059/sol1.py | 23 ++++++++-------- project_euler/problem_070/sol1.py | 4 +-- project_euler/problem_074/sol1.py | 2 +- project_euler/problem_077/sol1.py | 8 +++--- project_euler/problem_080/sol1.py | 2 +- project_euler/problem_081/sol1.py | 2 +- project_euler/problem_085/sol1.py | 5 ++-- project_euler/problem_089/sol1.py | 2 +- project_euler/problem_101/sol1.py | 18 ++++++------- project_euler/problem_102/sol1.py | 14 +++++----- project_euler/problem_107/sol1.py | 21 ++++++++------- project_euler/problem_119/sol1.py | 2 +- project_euler/problem_123/sol1.py | 5 ++-- project_euler/problem_180/sol1.py | 7 +++-- project_euler/problem_203/sol1.py | 12 ++++----- scheduling/first_come_first_served.py | 12 ++++----- scheduling/round_robin.py | 9 ++++--- scheduling/shortest_job_first.py | 12 ++++----- searches/binary_search.py | 19 +++++++------- searches/fibonacci_search.py | 2 +- searches/ternary_search.py | 8 +++--- sorts/bitonic_sort.py | 8 +++--- sorts/bucket_sort.py | 4 +-- sorts/msd_radix_sort.py | 10 +++---- sorts/patience_sort.py | 7 ++--- sorts/pigeon_sort.py | 4 +-- sorts/quick_sort.py | 6 ++--- sorts/radix_sort.py | 6 ++--- sorts/recursive_insertion_sort.py | 5 +--- sorts/slowsort.py | 7 ++--- strings/aho_corasick.py | 7 ++--- strings/boyer_moore_search.py | 4 +-- strings/knuth_morris_pratt.py | 4 +-- web_programming/emails_from_url.py | 5 ++-- web_programming/fetch_github_info.py | 6 +++-- 142 files changed, 523 insertions(+), 530 deletions(-) rename "other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" => other/davisb_putnamb_logemannb_loveland.py (94%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 41485f6f0ca4..0c00d5ca7f70 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -545,7 +545,7 @@ ## Other * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) * [Date To Weekday](https://github.com/TheAlgorithms/Python/blob/master/other/date_to_weekday.py) - * [Davis–Putnam–Logemann–Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davis–putnam–logemann–loveland.py) + * [Davisb Putnamb Logemannb Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davisb_putnamb_logemannb_loveland.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) * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) @@ -860,6 +860,7 @@ * [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) + * [Dutch National Flag Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/dutch_national_flag_sort.py) * [Exchange Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/exchange_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) diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index 7b5006a1a82c..6e8d1d043036 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -1,14 +1,14 @@ """ Checks if a system of forces is in static equilibrium. """ -from typing import List +from __future__ import annotations from numpy import array, cos, cross, ndarray, 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 5bb631758c21..b488b1bb3211 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -3,12 +3,12 @@ Reference: - https://en.wikipedia.org/wiki/LU_decomposition """ -from typing import Tuple +from __future__ import annotations import numpy as np -def lower_upper_decomposition(table: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: +def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """Lower-Upper (LU) Decomposition Example: diff --git a/arithmetic_analysis/newton_forward_interpolation.py b/arithmetic_analysis/newton_forward_interpolation.py index 66cde4b73c4f..490e0687f15f 100644 --- a/arithmetic_analysis/newton_forward_interpolation.py +++ b/arithmetic_analysis/newton_forward_interpolation.py @@ -1,7 +1,7 @@ # https://www.geeksforgeeks.org/newton-forward-backward-interpolation/ +from __future__ import annotations import math -from typing import List # for calculating u value @@ -22,7 +22,7 @@ def ucal(u: float, p: int) -> float: def main() -> None: n = int(input("enter the numbers of values: ")) - y: List[List[float]] = [] + 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 146bb0aa5adf..1a820538630f 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -2,15 +2,16 @@ # 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 __future__ import annotations + from decimal import Decimal from math import * # noqa: F401, F403 -from typing import Union from sympy import diff def newton_raphson( - func: str, a: Union[float, Decimal], precision: float = 10 ** -10 + func: str, a: float | Decimal, precision: float = 10 ** -10 ) -> float: """Finds root from the point 'a' onwards by Newton-Raphson method >>> newton_raphson("sin(x)", 2) diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 76462837ce35..bde60f0328ba 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -3,16 +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 +from __future__ import annotations -def generate_all_combinations(n: int, k: int) -> List[List[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: List[List[int]] = [] + result: list[list[int]] = [] create_all_state(1, n, k, [], result) return result @@ -21,8 +21,8 @@ def create_all_state( increment: int, total_number: int, level: int, - current_list: List[int], - total_list: List[List[int]], + current_list: list[int], + total_list: list[list[int]], ) -> None: if level == 0: total_list.append(current_list[:]) @@ -34,7 +34,7 @@ def create_all_state( current_list.pop() -def print_all_state(total_list: List[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 a0032c5ca814..ff8a53e0dd0e 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -5,18 +5,18 @@ Time complexity: O(n! * n), where n denotes the length of the given sequence. """ -from typing import List, Union +from __future__ import annotations -def generate_all_permutations(sequence: List[Union[int, str]]) -> None: +def generate_all_permutations(sequence: list[int | str]) -> None: create_state_space_tree(sequence, [], 0, [0 for i in range(len(sequence))]) def create_state_space_tree( - sequence: List[Union[int, str]], - current_sequence: List[Union[int, str]], + sequence: list[int | str], + current_sequence: list[int | str], index: int, - index_used: List[int], + index_used: list[int], ) -> None: """ Creates a state space tree to iterate through each branch using DFS. @@ -44,8 +44,8 @@ def create_state_space_tree( sequence = list(map(int, input().split())) """ -sequence: List[Union[int, str]] = [3, 1, 2, 4] +sequence: list[int | str] = [3, 1, 2, 4] generate_all_permutations(sequence) -sequence_2: List[Union[int, str]] = ["A", "B", "C"] +sequence_2: list[int | str] = ["A", "B", "C"] generate_all_permutations(sequence_2) diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index 99db4ea46589..c465fc542407 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -5,15 +5,17 @@ Time complexity: O(2^n), where n denotes the length of the given sequence. """ -from typing import Any, List +from __future__ import annotations +from typing import Any -def generate_all_subsequences(sequence: List[Any]) -> None: + +def generate_all_subsequences(sequence: list[Any]) -> None: create_state_space_tree(sequence, [], 0) def create_state_space_tree( - sequence: List[Any], current_subsequence: List[Any], index: int + sequence: list[Any], current_subsequence: list[Any], index: int ) -> None: """ Creates a state space tree to iterate through each branch using DFS. @@ -32,7 +34,7 @@ def create_state_space_tree( if __name__ == "__main__": - seq: List[Any] = [3, 1, 2, 4] + seq: list[Any] = [3, 1, 2, 4] generate_all_subsequences(seq) seq.clear() diff --git a/backtracking/coloring.py b/backtracking/coloring.py index 3956b21a9182..8bda4b5871df 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -5,11 +5,10 @@ Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring """ -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 +34,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 +85,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..19751b347320 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -6,11 +6,10 @@ 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] + 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 +46,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 +107,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 8e6613e07d8b..6e9b31bd1133 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, int], n: int) -> List[Tuple[int, 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, int], n: int) -> List[Tuple[int, 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. @@ -47,7 +47,7 @@ def is_complete(board: List[List[int]]) -> bool: def open_knight_tour_helper( - board: List[List[int]], pos: Tuple[int, int], curr: int + board: list[list[int]], pos: tuple[int, int], curr: int ) -> bool: """ Helper function to solve knight tour problem. @@ -68,7 +68,7 @@ def open_knight_tour_helper( 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 dda29b47d6cc..6e310131e069 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -7,12 +7,13 @@ leaves of game tree is stored in scores[] height is maximum height of Game tree """ +from __future__ import annotations + 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 diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index 29b8d819acf3..b8ace59781f5 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -7,12 +7,12 @@ diagonal lines. """ -from typing import List +from __future__ import annotations solution = [] -def isSafe(board: List[List[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. @@ -40,7 +40,7 @@ def isSafe(board: List[List[int]], row: int, column: int) -> bool: return True -def solve(board: List[List[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 @@ -70,7 +70,7 @@ def solve(board: List[List[int]], row: int) -> bool: return False -def printboard(board: List[List[int]]) -> None: +def printboard(board: list[list[int]]) -> None: """ Prints the boards that have a successful combination. """ diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py index a8651c5c362e..c12aa6c3387d 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: """ @@ -139,7 +139,7 @@ def depth_first_search( def n_queens_solution(n: int) -> None: - boards: List[List[str]] = [] + boards: list[list[str]] = [] depth_first_search([], [], [], boards, n) # Print all the boards diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index cd2a8f41daa8..2860880db540 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def solve_maze(maze: List[List[int]]) -> bool: +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. @@ -70,7 +70,7 @@ def solve_maze(maze: List[List[int]]) -> bool: return solved -def run_maze(maze: List[List[int]], i: int, j: int, solutions: List[List[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. diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index 593fa52d6d8a..698dedcc2125 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -9,9 +9,9 @@ have solved the puzzle. else, we backtrack and place another number in that cell and repeat this process. """ -from typing import List, Optional, Tuple +from __future__ import annotations -Matrix = List[List[int]] +Matrix = list[list[int]] # assigning initial values to the grid initial_grid: Matrix = [ @@ -59,7 +59,7 @@ def is_safe(grid: Matrix, row: int, column: int, n: int) -> bool: return True -def find_empty_location(grid: Matrix) -> Optional[Tuple[int, int]]: +def find_empty_location(grid: Matrix) -> tuple[int, int] | None: """ This function finds an empty location so that we can assign a number for that particular row and column. @@ -71,7 +71,7 @@ def find_empty_location(grid: Matrix) -> Optional[Tuple[int, int]]: return None -def sudoku(grid: Matrix) -> Optional[Matrix]: +def sudoku(grid: Matrix) -> Matrix | None: """ Takes a partially filled-in grid and attempts to assign values to all unassigned locations in such a way to meet the requirements diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index f695b8f7a80e..8348544c0175 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -6,12 +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 +from __future__ import annotations -def generate_sum_of_subsets_soln(nums: List[int], max_sum: int) -> List[List[int]]: - result: List[List[int]] = [] - path: List[int] = [] +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) @@ -19,11 +19,11 @@ def generate_sum_of_subsets_soln(nums: List[int], max_sum: int) -> List[List[int def create_state_space_tree( - nums: List[int], + nums: list[int], max_sum: int, num_index: int, - path: List[int], - result: List[List[int]], + path: list[int], + result: list[list[int]], remaining_nums_sum: int, ) -> None: """ diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py index b50147ac1215..54d861dd9f10 100644 --- a/blockchain/chinese_remainder_theorem.py +++ b/blockchain/chinese_remainder_theorem.py @@ -11,11 +11,11 @@ 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 +from __future__ import annotations # Extended Euclid -def extended_euclid(a: int, b: int) -> Tuple[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 7df674cb1438..22b0cad75c63 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -1,7 +1,7 @@ -from typing import Tuple +from __future__ import annotations -def diophantine(a: int, b: int, c: int) -> Tuple[float, float]: +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) @@ -95,7 +95,7 @@ def greatest_common_divisor(a: int, b: int) -> int: return b -def extended_gcd(a: int, b: int) -> Tuple[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) diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py index 4f7f50a92ad0..a9d0f65c5b27 100644 --- a/blockchain/modular_division.py +++ b/blockchain/modular_division.py @@ -1,4 +1,4 @@ -from typing import Tuple +from __future__ import annotations def modular_division(a: int, b: int, n: int) -> int: @@ -73,7 +73,7 @@ def modular_division2(a: int, b: int, n: int) -> int: return x -def extended_gcd(a: int, b: int) -> Tuple[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) @@ -101,7 +101,7 @@ def extended_gcd(a: int, b: int) -> Tuple[int, int, int]: return (d, x, y) -def extended_euclid(a: int, b: int) -> Tuple[int, int]: +def extended_euclid(a: int, b: int) -> tuple[int, int]: """ Extended Euclid >>> extended_euclid(10, 6) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 70cdf25a701d..9cc99b1eeabb 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,4 +1,4 @@ -from typing import List +from __future__ import annotations def compare_string(string1: str, string2: str) -> str: @@ -22,7 +22,7 @@ def compare_string(string1: str, string2: str) -> str: return "".join(l1) -def check(binary: List[str]) -> List[str]: +def check(binary: list[str]) -> list[str]: """ >>> check(['0.00.01.5']) ['0.00.01.5'] @@ -46,7 +46,7 @@ def check(binary: List[str]) -> List[str]: binary = list(set(temp)) -def decimal_to_binary(no_of_variable: int, minterms: List[float]) -> List[str]: +def decimal_to_binary(no_of_variable: int, minterms: list[float]) -> list[str]: """ >>> decimal_to_binary(3,[1.5]) ['0.00.01.5'] @@ -82,7 +82,7 @@ def is_for_table(string1: str, string2: str, count: int) -> bool: return False -def selection(chart: List[List[int]], prime_implicants: List[str]) -> List[str]: +def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]: """ >>> selection([[1]],['0.00.01.5']) ['0.00.01.5'] @@ -130,8 +130,8 @@ def selection(chart: List[List[int]], prime_implicants: List[str]) -> List[str]: def prime_implicant_chart( - prime_implicants: List[str], binary: List[str] -) -> List[List[int]]: + prime_implicants: list[str], binary: list[str] +) -> list[list[int]]: """ >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) [[1]] diff --git a/cellular_automata/conways_game_of_life.py b/cellular_automata/conways_game_of_life.py index 321baa3a3794..079fb4d04499 100644 --- a/cellular_automata/conways_game_of_life.py +++ b/cellular_automata/conways_game_of_life.py @@ -2,11 +2,8 @@ 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 @@ -25,7 +22,7 @@ BLINKER = [[0, 1, 0], [0, 1, 0], [0, 1, 0]] -def new_generation(cells: List[List[int]]) -> List[List[int]]: +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) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 4b2f76c7d873..8cd9fab58471 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,8 +1,9 @@ +from __future__ import annotations + from string import ascii_letters -from typing import Dict, Optional -def encrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str: +def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str: """ encrypt ======= @@ -80,7 +81,7 @@ def encrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str: return result -def decrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str: +def decrypt(input_string: str, key: int, alphabet: str | None = None) -> str: """ decrypt ======= @@ -145,7 +146,7 @@ def decrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str: return encrypt(input_string, key, alphabet) -def brute_force(input_string: str, alphabet: Optional[str] = None) -> Dict[int, str]: +def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str]: """ brute_force =========== diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 7e3705b8f71f..89477914a030 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 - -from typing import Optional +from __future__ import annotations def decrypt_caesar_with_chi_squared( ciphertext: str, - cipher_alphabet: Optional[list[str]] = None, - frequencies_dict: Optional[dict[str, float]] = None, + cipher_alphabet: list[str] | None = None, + frequencies_dict: dict[str, float] | None = None, case_sensetive: bool = False, ) -> tuple[int, float, str]: """ diff --git a/ciphers/diffie.py b/ciphers/diffie.py index a23a8104afe2..4ff90be009c1 100644 --- a/ciphers/diffie.py +++ b/ciphers/diffie.py @@ -1,7 +1,7 @@ -from typing import Optional +from __future__ import annotations -def find_primitive(n: int) -> Optional[int]: +def find_primitive(n: int) -> int | None: for r in range(1, n): li = [] for x in range(n - 1): diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index 01d099641dd2..3b84f97f6769 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import random import string -from typing import Optional class ShuffledShiftCipher: @@ -27,7 +28,7 @@ class ShuffledShiftCipher: cip2 = ShuffledShiftCipher() """ - def __init__(self, passcode: Optional[str] = None) -> None: + def __init__(self, passcode: str | None = None) -> None: """ Initializes a cipher object with a passcode as it's entity Note: No new passcode is generated if user provides a passcode diff --git a/compression/huffman.py b/compression/huffman.py index b6cc4de1e8e6..8f37a53ce2b7 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -30,7 +30,7 @@ def parse_file(file_path): if not c: break 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) + return sorted((Letter(c, f) for c, f in chars.items()), key=lambda l: l.freq) def build_tree(letters): diff --git a/conversions/molecular_chemistry.py b/conversions/molecular_chemistry.py index 8c68459965b0..0024eb5cb5b8 100644 --- a/conversions/molecular_chemistry.py +++ b/conversions/molecular_chemistry.py @@ -20,7 +20,7 @@ def molarity_to_normality(nfactor: int, moles: float, volume: float) -> float: >>> molarity_to_normality(4, 11.4, 5.7) 8 """ - return round((float(moles / volume) * nfactor)) + return round(float(moles / volume) * nfactor) def moles_to_pressure(volume: float, moles: float, temperature: float) -> float: diff --git a/conversions/prefix_conversions.py b/conversions/prefix_conversions.py index 78db4a91709c..a77556433c66 100644 --- a/conversions/prefix_conversions.py +++ b/conversions/prefix_conversions.py @@ -1,8 +1,9 @@ """ Convert International System of Units (SI) and Binary prefixes """ +from __future__ import annotations + from enum import Enum -from typing import Union class SI_Unit(Enum): @@ -41,8 +42,8 @@ class Binary_Unit(Enum): def convert_si_prefix( known_amount: float, - known_prefix: Union[str, SI_Unit], - unknown_prefix: Union[str, SI_Unit], + known_prefix: str | SI_Unit, + unknown_prefix: str | SI_Unit, ) -> float: """ Wikipedia reference: https://en.wikipedia.org/wiki/Binary_prefix @@ -70,8 +71,8 @@ def convert_si_prefix( def convert_binary_prefix( known_amount: float, - known_prefix: Union[str, Binary_Unit], - unknown_prefix: Union[str, Binary_Unit], + known_prefix: str | Binary_Unit, + unknown_prefix: str | Binary_Unit, ) -> float: """ Wikipedia reference: https://en.wikipedia.org/wiki/Metric_prefix diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index e0d3e4d438a8..1ab13777b7a6 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -5,15 +5,16 @@ For testing run: python avl_tree.py """ +from __future__ import annotations import math import random -from typing import Any, List, Optional +from typing import Any class my_queue: def __init__(self) -> None: - self.data: List[Any] = [] + self.data: list[Any] = [] self.head: int = 0 self.tail: int = 0 @@ -41,17 +42,17 @@ def print(self) -> None: class my_node: def __init__(self, data: Any) -> None: self.data = data - self.left: Optional[my_node] = None - self.right: Optional[my_node] = None + self.left: my_node | None = None + self.right: my_node | None = None self.height: int = 1 def get_data(self) -> Any: return self.data - def get_left(self) -> Optional["my_node"]: + def get_left(self) -> my_node | None: return self.left - def get_right(self) -> Optional["my_node"]: + def get_right(self) -> my_node | None: return self.right def get_height(self) -> int: @@ -61,11 +62,11 @@ def set_data(self, data: Any) -> None: self.data = data return - def set_left(self, node: Optional["my_node"]) -> None: + def set_left(self, node: my_node | None) -> None: self.left = node return - def set_right(self, node: Optional["my_node"]) -> None: + def set_right(self, node: my_node | None) -> None: self.right = node return @@ -74,7 +75,7 @@ def set_height(self, height: int) -> None: return -def get_height(node: Optional["my_node"]) -> int: +def get_height(node: my_node | None) -> int: if node is None: return 0 return node.get_height() @@ -149,7 +150,7 @@ def rl_rotation(node: my_node) -> my_node: return left_rotation(node) -def insert_node(node: Optional["my_node"], data: Any) -> Optional["my_node"]: +def insert_node(node: my_node | None, data: Any) -> my_node | None: if node is None: return my_node(data) if data < node.get_data(): @@ -197,7 +198,7 @@ def get_leftMost(root: my_node) -> Any: return root.get_data() -def del_node(root: my_node, data: Any) -> Optional["my_node"]: +def del_node(root: my_node, data: Any) -> my_node | None: left_child = root.get_left() right_child = root.get_right() if root.get_data() == data: @@ -275,7 +276,7 @@ class AVLtree: """ def __init__(self) -> None: - self.root: Optional[my_node] = None + self.root: my_node | None = None def get_height(self) -> int: return get_height(self.root) diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 575b157ee78a..65dccf247b51 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations class Node: @@ -8,11 +8,11 @@ class Node: def __init__(self, data: int) -> None: self.data = data - self.left: Optional[Node] = None - self.right: Optional[Node] = None + self.left: Node | None = None + self.right: Node | None = None -def display(tree: Optional[Node]) -> None: # In Order traversal of the tree +def display(tree: Node | None) -> None: # In Order traversal of the tree """ >>> root = Node(1) >>> root.left = Node(0) @@ -30,7 +30,7 @@ def display(tree: Optional[Node]) -> None: # In Order traversal of the tree display(tree.right) -def depth_of_tree(tree: Optional[Node]) -> int: +def depth_of_tree(tree: Node | None) -> int: """ Recursive function that returns the depth of a binary tree. diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index a05e28a7bd54..4bdf4e33dcc3 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -7,21 +7,23 @@ To run an example: python binary_search_tree_recursive.py """ +from __future__ import annotations + import unittest -from typing import Iterator, Optional +from typing import Iterator class Node: - def __init__(self, label: int, parent: Optional["Node"]) -> None: + def __init__(self, label: int, parent: Node | None) -> None: self.label = label self.parent = parent - self.left: Optional[Node] = None - self.right: Optional[Node] = None + self.left: Node | None = None + self.right: Node | None = None class BinarySearchTree: def __init__(self) -> None: - self.root: Optional[Node] = None + self.root: Node | None = None def empty(self) -> None: """ @@ -66,9 +68,7 @@ def put(self, label: int) -> None: """ self.root = self._put(self.root, label) - def _put( - self, node: Optional[Node], label: int, parent: Optional[Node] = None - ) -> Node: + def _put(self, node: Node | None, label: int, parent: Node | None = None) -> Node: if node is None: node = Node(label, parent) else: @@ -98,7 +98,7 @@ def search(self, label: int) -> Node: """ return self._search(self.root, label) - def _search(self, node: Optional[Node], label: int) -> Node: + def _search(self, node: Node | None, label: int) -> Node: if node is None: raise Exception(f"Node with label {label} does not exist") else: @@ -140,7 +140,7 @@ def remove(self, label: int) -> None: else: self._reassign_nodes(node, None) - def _reassign_nodes(self, node: Node, new_children: Optional[Node]) -> None: + def _reassign_nodes(self, node: Node, new_children: Node | None) -> None: if new_children: new_children.parent = node.parent @@ -244,7 +244,7 @@ def inorder_traversal(self) -> Iterator[Node]: """ return self._inorder_traversal(self.root) - def _inorder_traversal(self, node: Optional[Node]) -> Iterator[Node]: + def _inorder_traversal(self, node: Node | None) -> Iterator[Node]: if node is not None: yield from self._inorder_traversal(node.left) yield node @@ -266,7 +266,7 @@ def preorder_traversal(self) -> Iterator[Node]: """ return self._preorder_traversal(self.root) - def _preorder_traversal(self, node: Optional[Node]) -> Iterator[Node]: + def _preorder_traversal(self, node: Node | None) -> Iterator[Node]: if node is not None: yield node yield from self._preorder_traversal(node.left) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 7857880dada9..de9e9d60d272 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -1,13 +1,14 @@ # https://en.wikipedia.org/wiki/Tree_traversal +from __future__ import annotations + from dataclasses import dataclass -from typing import Optional @dataclass class Node: data: int - left: Optional["Node"] = None - right: Optional["Node"] = None + left: Node | None = None + right: Node | None = None def make_tree() -> Node: diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 9066db294613..94329cb43a76 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -1,7 +1,6 @@ from __future__ import annotations import math -from typing import List, Union class SegmentTree: @@ -38,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] @@ -89,7 +88,7 @@ def update( # query with O(lg n) def query( self, idx: int, left_element: int, right_element: int, a: int, b: int - ) -> Union[int, float]: + ) -> int | float: """ 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] diff --git a/data_structures/binary_tree/merge_two_binary_trees.py b/data_structures/binary_tree/merge_two_binary_trees.py index 6b202adb3cf5..d169e0e75b82 100644 --- a/data_structures/binary_tree/merge_two_binary_trees.py +++ b/data_structures/binary_tree/merge_two_binary_trees.py @@ -5,7 +5,7 @@ both nodes to the new value of the merged node. Otherwise, the NOT null node will be used as the node of new tree. """ -from typing import Optional +from __future__ import annotations class Node: @@ -15,11 +15,11 @@ class Node: def __init__(self, value: int = 0) -> None: self.value = value - self.left: Optional[Node] = None - self.right: Optional[Node] = None + self.left: Node | None = None + self.right: Node | None = None -def merge_two_binary_trees(tree1: Optional[Node], tree2: Optional[Node]) -> Node: +def merge_two_binary_trees(tree1: Node | None, tree2: Node | None) -> Node: """ Returns root node of the merged tree. @@ -52,7 +52,7 @@ def merge_two_binary_trees(tree1: Optional[Node], tree2: Optional[Node]) -> Node return tree1 -def print_preorder(root: Optional[Node]) -> None: +def print_preorder(root: Node | None) -> None: """ Print pre-order traversal of the tree. diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index de971a712fc1..e27757f20062 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -2,7 +2,9 @@ python/black : true flake8 : passed """ -from typing import Iterator, Optional +from __future__ import annotations + +from typing import Iterator class RedBlackTree: @@ -21,11 +23,11 @@ class RedBlackTree: def __init__( self, - label: Optional[int] = None, + label: int | None = None, color: int = 0, - parent: Optional["RedBlackTree"] = None, - left: Optional["RedBlackTree"] = None, - right: Optional["RedBlackTree"] = None, + parent: RedBlackTree | None = None, + left: RedBlackTree | None = None, + right: RedBlackTree | None = None, ) -> None: """Initialize a new Red-Black Tree node with the given values: label: The value associated with this node @@ -42,7 +44,7 @@ def __init__( # Here are functions which are specific to red-black trees - def rotate_left(self) -> "RedBlackTree": + 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). @@ -62,7 +64,7 @@ def rotate_left(self) -> "RedBlackTree": right.parent = parent return right - def rotate_right(self) -> "RedBlackTree": + 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). @@ -82,7 +84,7 @@ def rotate_right(self) -> "RedBlackTree": left.parent = parent return left - def insert(self, label: int) -> "RedBlackTree": + 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). @@ -139,7 +141,7 @@ def _insert_repair(self) -> None: self.grandparent.color = 1 self.grandparent._insert_repair() - def remove(self, label: int) -> "RedBlackTree": + def remove(self, label: int) -> RedBlackTree: """Remove label from this tree.""" if self.label == label: if self.left and self.right: @@ -337,7 +339,7 @@ def __contains__(self, label) -> bool: """ return self.search(label) is not None - def search(self, label: int) -> "RedBlackTree": + 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. @@ -411,7 +413,7 @@ def get_min(self) -> int: return self.label @property - def grandparent(self) -> "RedBlackTree": + def grandparent(self) -> RedBlackTree: """Get the current node's grandparent, or None if it doesn't exist.""" if self.parent is None: return None @@ -419,7 +421,7 @@ def grandparent(self) -> "RedBlackTree": return self.parent.parent @property - def sibling(self) -> "RedBlackTree": + def sibling(self) -> RedBlackTree: """Get the current node's sibling, or None if it doesn't exist.""" if self.parent is None: return None diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index a09dcc928143..0526b139b3c7 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -1,9 +1,6 @@ -# flake8: noqa - from __future__ import annotations from random import random -from typing import Optional, Tuple class Node: @@ -12,11 +9,11 @@ class Node: Treap is a binary tree by value and heap by priority """ - def __init__(self, value: Optional[int] = None): + def __init__(self, value: int | None = None): self.value = value self.prior = random() - self.left: Optional[Node] = None - self.right: Optional[Node] = None + self.left: Node | None = None + self.right: Node | None = None def __repr__(self) -> str: from pprint import pformat @@ -35,7 +32,7 @@ def __str__(self) -> str: return value + left + right -def split(root: Optional[Node], value: int) -> Tuple[Optional[Node], Optional[Node]]: +def split(root: Node | None, value: int) -> tuple[Node | None, Node | None]: """ We split current tree into 2 trees with value: @@ -64,7 +61,7 @@ def split(root: Optional[Node], value: int) -> Tuple[Optional[Node], Optional[No return root, right -def merge(left: Optional[Node], right: Optional[Node]) -> Optional[Node]: +def merge(left: Node | None, right: Node | None) -> Node | None: """ We merge 2 trees into one. Note: all left tree's values must be less than all right tree's @@ -86,7 +83,7 @@ def merge(left: Optional[Node], right: Optional[Node]) -> Optional[Node]: return right -def insert(root: Optional[Node], value: int) -> Optional[Node]: +def insert(root: Node | None, value: int) -> Node | None: """ Insert element @@ -99,7 +96,7 @@ def insert(root: Optional[Node], value: int) -> Optional[Node]: return merge(merge(left, node), right) -def erase(root: Optional[Node], value: int) -> Optional[Node]: +def erase(root: Node | None, value: int) -> Node | None: """ Erase element @@ -112,7 +109,7 @@ def erase(root: Optional[Node], value: int) -> Optional[Node]: return merge(left, right) -def inorder(root: Optional[Node]) -> None: +def inorder(root: Node | None) -> None: """ Just recursive print of a tree """ @@ -124,7 +121,7 @@ def inorder(root: Optional[Node]) -> None: inorder(root.right) -def interactTreap(root: Optional[Node], args: str) -> Optional[Node]: +def interactTreap(root: Node | None, args: str) -> Node | None: """ Commands: + value to add value into treap diff --git a/data_structures/binary_tree/wavelet_tree.py b/data_structures/binary_tree/wavelet_tree.py index 1607244f74ed..173a88ab7316 100644 --- a/data_structures/binary_tree/wavelet_tree.py +++ b/data_structures/binary_tree/wavelet_tree.py @@ -7,8 +7,7 @@ 2. https://www.youtube.com/watch?v=4aSv9PcecDw&t=811s 3. https://www.youtube.com/watch?v=CybAgVF-MMc&t=1178s """ - -from typing import Optional +from __future__ import annotations test_array = [2, 1, 4, 5, 6, 0, 8, 9, 1, 2, 0, 6, 4, 2, 0, 6, 5, 3, 2, 7] @@ -18,8 +17,8 @@ def __init__(self, length: int) -> None: self.minn: int = -1 self.maxx: int = -1 self.map_left: list[int] = [-1] * length - self.left: Optional[Node] = None - self.right: Optional[Node] = None + self.left: Node | None = None + self.right: Node | None = None def __repr__(self) -> str: """ diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index fd9e6eec134c..f4422de53821 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -19,7 +19,7 @@ def keys(self): return self._keys def balanced_factor(self): - return sum([1 for slot in self.values if slot is not None]) / ( + return sum(1 for slot in self.values if slot is not None) / ( self.size_table * self.charge_factor ) diff --git a/data_structures/hashing/hash_table_with_linked_list.py b/data_structures/hashing/hash_table_with_linked_list.py index fe838268fce8..f404c5251246 100644 --- a/data_structures/hashing/hash_table_with_linked_list.py +++ b/data_structures/hashing/hash_table_with_linked_list.py @@ -14,7 +14,7 @@ def _set_value(self, key, data): def balanced_factor(self): return ( - sum([self.charge_factor - len(slot) for slot in self.values]) + sum(self.charge_factor - len(slot) for slot in self.values) / self.size_table * self.charge_factor ) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 65a70e468d1c..550439edd239 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -1,4 +1,6 @@ -from typing import Iterable, List, Optional +from __future__ import annotations + +from typing import Iterable class Heap: @@ -25,19 +27,19 @@ class Heap: """ def __init__(self) -> None: - self.h: List[float] = [] + self.h: list[float] = [] self.heap_size: int = 0 def __repr__(self) -> str: return str(self.h) - def parent_index(self, child_idx: int) -> Optional[int]: + def parent_index(self, child_idx: int) -> int | None: """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]: + def left_child_idx(self, parent_idx: int) -> int | None: """ return the left child index if the left child exists. if not, return None. @@ -47,7 +49,7 @@ def left_child_idx(self, parent_idx: int) -> Optional[int]: return left_child_index return None - def right_child_idx(self, parent_idx: int) -> Optional[int]: + def right_child_idx(self, parent_idx: int) -> int | None: """ return the right child index if the right child exists. if not, return None. diff --git a/data_structures/heap/randomized_heap.py b/data_structures/heap/randomized_heap.py index 0ddc2272efe8..f584f5cb3342 100644 --- a/data_structures/heap/randomized_heap.py +++ b/data_structures/heap/randomized_heap.py @@ -3,7 +3,7 @@ from __future__ import annotations import random -from typing import Generic, Iterable, List, Optional, TypeVar +from typing import Generic, Iterable, TypeVar T = TypeVar("T") @@ -16,8 +16,8 @@ class RandomizedHeapNode(Generic[T]): def __init__(self, value: T) -> None: self._value: T = value - self.left: Optional[RandomizedHeapNode[T]] = None - self.right: Optional[RandomizedHeapNode[T]] = None + self.left: RandomizedHeapNode[T] | None = None + self.right: RandomizedHeapNode[T] | None = None @property def value(self) -> T: @@ -26,8 +26,8 @@ def value(self) -> T: @staticmethod def merge( - root1: Optional[RandomizedHeapNode[T]], root2: Optional[RandomizedHeapNode[T]] - ) -> Optional[RandomizedHeapNode[T]]: + root1: RandomizedHeapNode[T] | None, root2: RandomizedHeapNode[T] | None + ) -> RandomizedHeapNode[T] | None: """Merge 2 nodes together.""" if not root1: return root2 @@ -69,13 +69,13 @@ class RandomizedHeap(Generic[T]): [-1, 0, 1] """ - def __init__(self, data: Optional[Iterable[T]] = ()) -> None: + def __init__(self, data: Iterable[T] | None = ()) -> None: """ >>> rh = RandomizedHeap([3, 1, 3, 7]) >>> rh.to_sorted_list() [1, 3, 3, 7] """ - self._root: Optional[RandomizedHeapNode[T]] = None + self._root: RandomizedHeapNode[T] | None = None for item in data: self.insert(item) @@ -151,7 +151,7 @@ def clear(self): """ self._root = None - def to_sorted_list(self) -> List[T]: + def to_sorted_list(self) -> list[T]: """ Returns sorted list containing all the values in the heap. diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 417a383f733e..b59441389a91 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Generic, Iterable, Iterator, Optional, TypeVar +from typing import Generic, Iterable, Iterator, TypeVar T = TypeVar("T") @@ -15,8 +15,8 @@ class SkewNode(Generic[T]): def __init__(self, value: T) -> None: self._value: T = value - self.left: Optional[SkewNode[T]] = None - self.right: Optional[SkewNode[T]] = None + self.left: SkewNode[T] | None = None + self.right: SkewNode[T] | None = None @property def value(self) -> T: @@ -25,8 +25,8 @@ def value(self) -> T: @staticmethod def merge( - root1: Optional[SkewNode[T]], root2: Optional[SkewNode[T]] - ) -> Optional[SkewNode[T]]: + root1: SkewNode[T] | None, root2: SkewNode[T] | None + ) -> SkewNode[T] | None: """Merge 2 nodes together.""" if not root1: return root2 @@ -69,13 +69,13 @@ class SkewHeap(Generic[T]): [-1, 0, 1] """ - def __init__(self, data: Optional[Iterable[T]] = ()) -> None: + def __init__(self, data: Iterable[T] | None = ()) -> None: """ >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] """ - self._root: Optional[SkewNode[T]] = None + self._root: SkewNode[T] | None = None for item in data: self.insert(item) diff --git a/data_structures/linked_list/merge_two_lists.py b/data_structures/linked_list/merge_two_lists.py index 96ec6b8abc85..43dd461867f1 100644 --- a/data_structures/linked_list/merge_two_lists.py +++ b/data_structures/linked_list/merge_two_lists.py @@ -5,7 +5,6 @@ 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) @@ -14,12 +13,12 @@ @dataclass class Node: data: int - next: Optional[Node] + next: Node | None class SortedLinkedList: def __init__(self, ints: Iterable[int]) -> None: - self.head: Optional[Node] = None + self.head: Node | None = None for i in reversed(sorted(ints)): self.head = Node(i, self.head) diff --git a/data_structures/linked_list/print_reverse.py b/data_structures/linked_list/print_reverse.py index c46f228e7260..f83d5607ffdd 100644 --- a/data_structures/linked_list/print_reverse.py +++ b/data_structures/linked_list/print_reverse.py @@ -1,4 +1,4 @@ -from typing import List +from __future__ import annotations class Node: @@ -16,7 +16,7 @@ def __repr__(self): return "->".join(string_rep) -def make_linked_list(elements_list: 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. >>> make_linked_list([]) diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index 8f06e6193d52..ee0b4460730c 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -2,11 +2,10 @@ Based on "Skip Lists: A Probabilistic Alternative to Balanced Trees" by William Pugh https://epaperpress.com/sortsearch/download/skiplist.pdf """ - from __future__ import annotations from random import random -from typing import Generic, Optional, TypeVar +from typing import Generic, TypeVar KT = TypeVar("KT") VT = TypeVar("VT") @@ -124,7 +123,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[Node[KT, VT] | None, list[Node[KT, VT]]]: """ :param key: Searched key, :return: Tuple with searched node (or None if given key is not present) @@ -222,7 +221,7 @@ def insert(self, key: KT, value: VT): else: update_node.forward[i] = new_node - def find(self, key: VT) -> Optional[VT]: + def find(self, key: VT) -> VT | None: """ :param key: Search key. :return: Value associated with given key or None if given key is not present. diff --git a/data_structures/stacks/evaluate_postfix_notations.py b/data_structures/stacks/evaluate_postfix_notations.py index 2a4baf9d6b52..51ea353b17de 100644 --- a/data_structures/stacks/evaluate_postfix_notations.py +++ b/data_structures/stacks/evaluate_postfix_notations.py @@ -1,5 +1,3 @@ -from typing import Any, List - """ The Reverse Polish Nation also known as Polish postfix notation or simply postfix notation. @@ -8,6 +6,9 @@ Valid operators are +, -, *, /. Each operand may be an integer or another expression. """ +from __future__ import annotations + +from typing import Any def evaluate_postfix(postfix_notation: list) -> int: @@ -23,7 +24,7 @@ def evaluate_postfix(postfix_notation: list) -> int: return 0 operations = {"+", "-", "*", "/"} - stack: List[Any] = [] + stack: list[Any] = [] for token in postfix_notation: if token in operations: diff --git a/data_structures/stacks/linked_stack.py b/data_structures/stacks/linked_stack.py index 0b9c9d45e61f..85b59a940e39 100644 --- a/data_structures/stacks/linked_stack.py +++ b/data_structures/stacks/linked_stack.py @@ -1,5 +1,7 @@ """ A Stack using a linked list like structure """ -from typing import Any, Optional +from __future__ import annotations + +from typing import Any class Node: @@ -42,7 +44,7 @@ class LinkedStack: """ def __init__(self) -> None: - self.top: Optional[Node] = None + self.top: Node | None = None def __iter__(self): node = self.top diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 245d39b32c07..c62412150626 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -1,4 +1,4 @@ -from typing import List +from __future__ import annotations class StackOverflowError(BaseException): @@ -15,7 +15,7 @@ class Stack: """ def __init__(self, limit: int = 10): - self.stack: List[int] = [] + self.stack: list[int] = [] self.limit = limit def __bool__(self) -> bool: diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 9c096f671385..63f8dbb20cc0 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -12,8 +12,9 @@ which have not been implemented here, yet. """ +from __future__ import annotations -from typing import Iterable, List, Set, Union +from typing import Iterable class Point: @@ -84,8 +85,8 @@ def __hash__(self): def _construct_points( - list_of_tuples: Union[List[Point], List[List[float]], Iterable[List[float]]] -) -> List[Point]: + list_of_tuples: list[Point] | list[list[float]] | Iterable[list[float]], +) -> list[Point]: """ constructs a list of points from an array-like object of numbers @@ -114,7 +115,7 @@ def _construct_points( [] """ - points: List[Point] = [] + points: list[Point] = [] if list_of_tuples: for p in list_of_tuples: if isinstance(p, Point): @@ -130,7 +131,7 @@ def _construct_points( return points -def _validate_input(points: Union[List[Point], List[List[float]]]) -> List[Point]: +def _validate_input(points: list[Point] | list[list[float]]) -> list[Point]: """ validates an input instance before a convex-hull algorithms uses it @@ -218,7 +219,7 @@ def _det(a: Point, b: Point, c: Point) -> float: return det -def convex_hull_bf(points: List[Point]) -> List[Point]: +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 @@ -291,7 +292,7 @@ def convex_hull_bf(points: List[Point]) -> List[Point]: return sorted(convex_set) -def convex_hull_recursive(points: List[Point]) -> List[Point]: +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 @@ -362,7 +363,7 @@ def convex_hull_recursive(points: List[Point]) -> List[Point]: def _construct_hull( - points: List[Point], left: Point, right: Point, convex_set: Set[Point] + points: list[Point], left: Point, right: Point, convex_set: set[Point] ) -> None: """ @@ -405,7 +406,7 @@ def _construct_hull( _construct_hull(candidate_points, extreme_point, right, convex_set) -def convex_hull_melkman(points: List[Point]) -> List[Point]: +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 diff --git a/divide_and_conquer/kth_order_statistic.py b/divide_and_conquer/kth_order_statistic.py index f6e81a306bff..666ad1a39b8a 100644 --- a/divide_and_conquer/kth_order_statistic.py +++ b/divide_and_conquer/kth_order_statistic.py @@ -8,8 +8,9 @@ For more information of this algorithm: https://web.stanford.edu/class/archive/cs/cs161/cs161.1138/lectures/08/Small08.pdf """ +from __future__ import annotations + from random import choice -from typing import List def random_pivot(lst): @@ -21,7 +22,7 @@ def random_pivot(lst): return choice(lst) -def kth_number(lst: List[int], k: int) -> int: +def kth_number(lst: list[int], k: int) -> int: """ Return the kth smallest number in lst. >>> kth_number([2, 1, 3, 4, 5], 3) diff --git a/divide_and_conquer/mergesort.py b/divide_and_conquer/mergesort.py index 46a46941cab3..628080cefc9b 100644 --- a/divide_and_conquer/mergesort.py +++ b/divide_and_conquer/mergesort.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def merge(left_half: List, right_half: List) -> List: +def merge(left_half: list, right_half: list) -> list: """Helper function for mergesort. >>> left_half = [-2] @@ -57,7 +57,7 @@ def merge(left_half: List, right_half: List) -> List: return sorted_array -def merge_sort(array: List) -> List: +def merge_sort(array: list) -> list: """Returns a list of sorted array elements using merge sort. >>> from random import shuffle diff --git a/divide_and_conquer/peak.py b/divide_and_conquer/peak.py index f94f83ed3fcb..e60f28bfbe29 100644 --- a/divide_and_conquer/peak.py +++ b/divide_and_conquer/peak.py @@ -7,10 +7,10 @@ (From Kleinberg and Tardos. Algorithm Design. Addison Wesley 2006: Chapter 5 Solved Exercise 1) """ -from typing import List +from __future__ import annotations -def peak(lst: List[int]) -> int: +def peak(lst: list[int]) -> int: """ Return the peak value of `lst`. >>> peak([1, 2, 3, 4, 5, 4, 3, 2, 1]) diff --git a/electronics/electric_power.py b/electronics/electric_power.py index e4e685bbd0f0..ac673d7e3a94 100644 --- a/electronics/electric_power.py +++ b/electronics/electric_power.py @@ -1,9 +1,10 @@ # https://en.m.wikipedia.org/wiki/Electric_power +from __future__ import annotations + from collections import namedtuple -from typing import Tuple -def electric_power(voltage: float, current: float, power: float) -> Tuple: +def electric_power(voltage: float, current: float, power: float) -> tuple: """ This function can calculate any one of the three (voltage, current, power), fundamental value of electrical system. diff --git a/electronics/ohms_law.py b/electronics/ohms_law.py index 41bffa9f87c8..66e737c1f909 100644 --- a/electronics/ohms_law.py +++ b/electronics/ohms_law.py @@ -1,8 +1,8 @@ # https://en.wikipedia.org/wiki/Ohm%27s_law -from typing import Dict +from __future__ import annotations -def ohms_law(voltage: float, current: float, resistance: float) -> Dict[str, float]: +def ohms_law(voltage: float, current: float, resistance: float) -> dict[str, 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. diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 9cd6dd0f9635..db0ef8e7b3ac 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -13,7 +13,7 @@ def initialize_unweighted_directed_graph( graph[i + 1] = [] for e in range(edge_count): - x, y = [int(i) for i in _input(f"Edge {e + 1}: ")] + x, y = (int(i) for i in _input(f"Edge {e + 1}: ")) graph[x].append(y) return graph @@ -26,7 +26,7 @@ def initialize_unweighted_undirected_graph( graph[i + 1] = [] for e in range(edge_count): - x, y = [int(i) for i in _input(f"Edge {e + 1}: ")] + x, y = (int(i) for i in _input(f"Edge {e + 1}: ")) graph[x].append(y) graph[y].append(x) return graph @@ -40,14 +40,14 @@ def initialize_weighted_undirected_graph( graph[i + 1] = [] for e in range(edge_count): - x, y, w = [int(i) for i in _input(f"Edge {e + 1}: ")] + x, y, w = (int(i) for i in _input(f"Edge {e + 1}: ")) graph[x].append((y, w)) graph[y].append((x, w)) return graph if __name__ == "__main__": - n, m = [int(i) for i in _input("Number of nodes and edges: ")] + n, m = (int(i) for i in _input("Number of nodes and edges: ")) graph_choice = int( _input( diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index d6d6b2ac7349..0f654a510b59 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -11,7 +11,7 @@ def check_negative_cycle( graph: list[dict[str, int]], distance: list[float], edge_count: int ): for j in range(edge_count): - u, v, w = [graph[j][k] for k in ["src", "dst", "weight"]] + u, v, w = (graph[j][k] for k in ["src", "dst", "weight"]) if distance[u] != float("inf") and distance[u] + w < distance[v]: return True return False @@ -38,7 +38,7 @@ def bellman_ford( for i in range(vertex_count - 1): for j in range(edge_count): - u, v, w = [graph[j][k] for k in ["src", "dst", "weight"]] + u, v, w = (graph[j][k] for k in ["src", "dst", "weight"]) if distance[u] != float("inf") and distance[u] + w < distance[v]: distance[v] = distance[u] + w @@ -62,10 +62,10 @@ def bellman_ford( for i in range(E): print("Edge ", i + 1) - src, dest, weight = [ + src, dest, weight = ( int(x) for x in input("Enter source, destination, weight: ").strip().split(" ") - ] + ) graph[i] = {"src": src, "dst": dest, "weight": weight} source = int(input("\nEnter shortest path source:").strip()) diff --git a/graphs/bfs_zero_one_shortest_path.py b/graphs/bfs_zero_one_shortest_path.py index a68b5602c2d1..78047c5d2237 100644 --- a/graphs/bfs_zero_one_shortest_path.py +++ b/graphs/bfs_zero_one_shortest_path.py @@ -1,13 +1,13 @@ -from collections import deque -from collections.abc import Iterator -from dataclasses import dataclass -from typing import Optional, Union - """ 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 """ +from __future__ import annotations + +from collections import deque +from collections.abc import Iterator +from dataclasses import dataclass @dataclass @@ -59,7 +59,7 @@ def add_edge(self, from_vertex: int, to_vertex: int, weight: int): self._graph[from_vertex].append(Edge(to_vertex, weight)) - def get_shortest_path(self, start_vertex: int, finish_vertex: int) -> Optional[int]: + def get_shortest_path(self, start_vertex: int, finish_vertex: int) -> int | None: """ Return the shortest distance from start_vertex to finish_vertex in 0-1-graph. 1 1 1 @@ -107,7 +107,7 @@ def get_shortest_path(self, start_vertex: int, finish_vertex: int) -> Optional[i ValueError: No path from start_vertex to finish_vertex. """ queue = deque([start_vertex]) - distances: list[Union[int, None]] = [None] * self.size + distances: list[int | None] = [None] * self.size distances[start_vertex] = 0 while queue: diff --git a/graphs/bidirectional_a_star.py b/graphs/bidirectional_a_star.py index 729d8957bdef..071f1cd685b1 100644 --- a/graphs/bidirectional_a_star.py +++ b/graphs/bidirectional_a_star.py @@ -1,15 +1,12 @@ """ https://en.wikipedia.org/wiki/Bidirectional_search """ - from __future__ import annotations import time from math import sqrt # 1 for manhattan, 0 for euclidean -from typing import Optional - HEURISTIC = 0 grid = [ @@ -50,7 +47,7 @@ def __init__( goal_x: int, goal_y: int, g_cost: int, - parent: Optional[Node], + parent: Node | None, ) -> None: self.pos_x = pos_x self.pos_y = pos_y @@ -157,7 +154,7 @@ def get_successors(self, parent: Node) -> list[Node]: ) return successors - def retrace_path(self, node: Optional[Node]) -> list[TPosition]: + def retrace_path(self, node: Node | None) -> list[TPosition]: """ Retrace the path from parents to parents until start node """ diff --git a/graphs/bidirectional_breadth_first_search.py b/graphs/bidirectional_breadth_first_search.py index 9b84ab21bf7f..27e4f0b16bbf 100644 --- a/graphs/bidirectional_breadth_first_search.py +++ b/graphs/bidirectional_breadth_first_search.py @@ -1,11 +1,9 @@ """ https://en.wikipedia.org/wiki/Bidirectional_search """ - from __future__ import annotations import time -from typing import Optional Path = list[tuple[int, int]] @@ -24,7 +22,7 @@ class Node: def __init__( - self, pos_x: int, pos_y: int, goal_x: int, goal_y: int, parent: Optional[Node] + self, pos_x: int, pos_y: int, goal_x: int, goal_y: int, parent: Node | None ): self.pos_x = pos_x self.pos_y = pos_y @@ -57,7 +55,7 @@ def __init__(self, start: tuple[int, int], goal: tuple[int, int]): self.node_queue = [self.start] self.reached = False - def search(self) -> Optional[Path]: + def search(self) -> Path | None: while self.node_queue: current_node = self.node_queue.pop(0) @@ -93,7 +91,7 @@ def get_successors(self, parent: Node) -> list[Node]: ) return successors - def retrace_path(self, node: Optional[Node]) -> Path: + def retrace_path(self, node: Node | None) -> Path: """ Retrace the path from parents to parents until start node """ @@ -125,7 +123,7 @@ def __init__(self, start, goal): self.bwd_bfs = BreadthFirstSearch(goal, start) self.reached = False - def search(self) -> Optional[Path]: + def search(self) -> Path | None: 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) diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index 305db01e19e4..7c626429e5c0 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -1,13 +1,12 @@ #!/usr/bin/python """ Author: OMKAR PATHAK """ - -from typing import Dict, List, Set +from __future__ import annotations class Graph: def __init__(self) -> None: - self.vertices: Dict[int, List[int]] = {} + self.vertices: dict[int, list[int]] = {} def print_graph(self) -> None: """ @@ -35,7 +34,7 @@ def add_edge(self, from_vertex: int, to_vertex: int) -> None: else: self.vertices[from_vertex] = [to_vertex] - def bfs(self, start_vertex: int) -> Set[int]: + def bfs(self, start_vertex: int) -> set[int]: """ >>> g = Graph() >>> g.add_edge(0, 1) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 48f8ab1a4956..697a8c634859 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -3,8 +3,6 @@ """ from __future__ import annotations -from typing import Optional - graph = { "A": ["B", "C", "E"], "B": ["A", "D", "E"], @@ -24,7 +22,7 @@ def __init__(self, graph: dict[str, list[str]], source_vertex: str) -> None: """ self.graph = graph # mapping node to its parent in resulting breadth first tree - self.parent: dict[str, Optional[str]] = {} + self.parent: dict[str, str | None] = {} self.source_vertex = source_vertex def breath_first_search(self) -> None: diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 5d74a6db9c6b..f20a503ca395 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -1,11 +1,8 @@ """Non recursive implementation of a DFS algorithm.""" - from __future__ import annotations -from typing import Set - -def depth_first_search(graph: dict, start: str) -> Set[str]: +def depth_first_search(graph: dict, start: str) -> set[str]: """Depth First Search on Graph :param graph: directed graph in dictionary format :param start: starting vertex as a string diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index d5e80247a9b4..d49e65b9d814 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -4,8 +4,6 @@ from __future__ import annotations -from typing import Optional - Path = list[tuple[int, int]] grid = [ @@ -44,7 +42,7 @@ def __init__( goal_x: int, goal_y: int, g_cost: float, - parent: Optional[Node], + parent: Node | None, ): self.pos_x = pos_x self.pos_y = pos_y @@ -93,7 +91,7 @@ def __init__(self, start: tuple[int, int], goal: tuple[int, int]): self.reached = False - def search(self) -> Optional[Path]: + def search(self) -> Path | None: """ Search for the path, if a path is not found, only the starting position is returned @@ -156,7 +154,7 @@ def get_successors(self, parent: Node) -> list[Node]: ) return successors - def retrace_path(self, node: Optional[Node]) -> Path: + def retrace_path(self, node: Node | None) -> Path: """ Retrace the path from parents to parents until start node """ diff --git a/graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py index f21a87a7d534..85d937010489 100644 --- a/graphs/minimum_spanning_tree_kruskal.py +++ b/graphs/minimum_spanning_tree_kruskal.py @@ -40,7 +40,7 @@ def find_parent(i): edges = [] for _ in range(num_edges): - node1, node2, cost = [int(x) for x in input().strip().split()] + node1, node2, cost = (int(x) for x in input().strip().split()) edges.append((node1, node2, cost)) kruskal(num_nodes, edges) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index c3444c36f1cf..d924ee3db1e5 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -6,9 +6,10 @@ at a time, from an arbitrary starting vertex, at each step adding the cheapest possible connection from the tree to another vertex. """ +from __future__ import annotations from sys import maxsize -from typing import Generic, Optional, TypeVar +from typing import Generic, TypeVar T = TypeVar("T") @@ -219,7 +220,7 @@ def add_edge(self, node1: T, node2: T, weight: int) -> None: def prims_algo( graph: GraphUndirectedWeighted[T], -) -> tuple[dict[T, int], dict[T, Optional[T]]]: +) -> tuple[dict[T, int], dict[T, T | None]]: """ >>> graph = GraphUndirectedWeighted() @@ -240,7 +241,7 @@ def prims_algo( """ # prim's algorithm for minimum spanning tree dist: dict[T, int] = {node: maxsize for node in graph.connections} - parent: dict[T, Optional[T]] = {node: None for node in graph.connections} + parent: dict[T, T | None] = {node: None for node in graph.connections} priority_queue: MinPriorityQueue[T] = MinPriorityQueue() for node, weight in dist.items(): diff --git a/graphs/page_rank.py b/graphs/page_rank.py index 0f5129146ddf..672405b7345b 100644 --- a/graphs/page_rank.py +++ b/graphs/page_rank.py @@ -43,7 +43,7 @@ def page_rank(nodes, limit=3, d=0.85): 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] + ranks[ib] / outbounds[ib] for ib in node.inbound ) print(ranks) diff --git a/graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py index 2b34170149bc..fa182aa2faf1 100644 --- a/graphs/scc_kosaraju.py +++ b/graphs/scc_kosaraju.py @@ -1,4 +1,4 @@ -from typing import List +from __future__ import annotations def dfs(u): @@ -39,16 +39,16 @@ def kosaraju(): # n - no of nodes, m - no of edges n, m = list(map(int, input().strip().split())) - graph: List[List[int]] = [[] for i in range(n)] # graph - reversedGraph: List[List[int]] = [[] for i in range(n)] # reversed graph + graph: list[list[int]] = [[] for i in range(n)] # graph + reversedGraph: list[list[int]] = [[] for i in range(n)] # reversed graph # input graph data (edges) for i in range(m): u, v = list(map(int, input().strip().split())) graph[u].append(v) reversedGraph[v].append(u) - stack: List[int] = [] - visit: List[bool] = [False] * n - scc: List[int] = [] - component: List[int] = [] + stack: list[int] = [] + visit: list[bool] = [False] * n + scc: list[int] = [] + component: list[int] = [] print(kosaraju()) diff --git a/hashes/luhn.py b/hashes/luhn.py index 81014120dd80..bb77fd05c556 100644 --- a/hashes/luhn.py +++ b/hashes/luhn.py @@ -1,5 +1,5 @@ """ Luhn Algorithm """ -from typing import List +from __future__ import annotations def is_luhn(string: str) -> bool: @@ -17,9 +17,9 @@ def is_luhn(string: str) -> bool: [False, False, False, True, False, False, False, False, False, False] """ check_digit: int - _vector: List[str] = list(string) + _vector: list[str] = list(string) __vector, check_digit = _vector[:-1], int(_vector[-1]) - vector: List[int] = [int(digit) for digit in __vector] + vector: list[int] = [int(digit) for digit in __vector] vector.reverse() for i, digit in enumerate(vector): diff --git a/knapsack/knapsack.py b/knapsack/knapsack.py index 756443ea6163..18a36c3bcdda 100644 --- a/knapsack/knapsack.py +++ b/knapsack/knapsack.py @@ -1,11 +1,10 @@ -from typing import List - """ A naive recursive implementation of 0-1 Knapsack Problem https://en.wikipedia.org/wiki/Knapsack_problem """ +from __future__ import annotations -def knapsack(capacity: int, weights: List[int], values: List[int], counter: int) -> int: +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. diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 5e2f82018f38..74aeb9137666 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -18,11 +18,11 @@ - function squareZeroMatrix(N) - function randomMatrix(W,H,a,b) """ - +from __future__ import annotations import math import random -from typing import Collection, Optional, Union, overload +from typing import Collection, overload class Vector: @@ -46,7 +46,7 @@ class Vector: TODO: compare-operator """ - def __init__(self, components: Optional[Collection[float]] = None) -> None: + def __init__(self, components: Collection[float] | None = None) -> None: """ input: components or nothing simple constructor for init the vector @@ -97,7 +97,7 @@ def euclidLength(self) -> float: summe += c ** 2 return math.sqrt(summe) - def __add__(self, other: "Vector") -> "Vector": + def __add__(self, other: Vector) -> Vector: """ input: other vector assumes: other vector has the same size @@ -110,7 +110,7 @@ def __add__(self, other: "Vector") -> "Vector": else: raise Exception("must have the same size") - def __sub__(self, other: "Vector") -> "Vector": + def __sub__(self, other: Vector) -> Vector: """ input: other vector assumes: other vector has the same size @@ -124,14 +124,14 @@ def __sub__(self, other: "Vector") -> "Vector": raise Exception("must have the same size") @overload - def __mul__(self, other: float) -> "Vector": + def __mul__(self, other: float) -> Vector: ... @overload - def __mul__(self, other: "Vector") -> float: + def __mul__(self, other: Vector) -> float: ... - def __mul__(self, other: Union[float, "Vector"]) -> Union[float, "Vector"]: + def __mul__(self, other: float | Vector) -> float | Vector: """ mul implements the scalar multiplication and the dot-product @@ -148,7 +148,7 @@ def __mul__(self, other: Union[float, "Vector"]) -> Union[float, "Vector"]: else: # error case raise Exception("invalid operand!") - def copy(self) -> "Vector": + def copy(self) -> Vector: """ copies this vector and returns it. """ @@ -313,14 +313,14 @@ def determinate(self) -> float: raise Exception("matrix is not square") @overload - def __mul__(self, other: float) -> "Matrix": + def __mul__(self, other: float) -> Matrix: ... @overload def __mul__(self, other: Vector) -> Vector: ... - def __mul__(self, other: Union[float, Vector]) -> Union[Vector, "Matrix"]: + def __mul__(self, other: float | Vector) -> Vector | Matrix: """ implements the matrix-vector multiplication. implements the matrix-scalar multiplication @@ -347,7 +347,7 @@ def __mul__(self, other: Union[float, Vector]) -> Union[Vector, "Matrix"]: ] return Matrix(matrix, self.__width, self.__height) - def __add__(self, other: "Matrix") -> "Matrix": + def __add__(self, other: Matrix) -> Matrix: """ implements the matrix-addition. """ @@ -362,7 +362,7 @@ def __add__(self, other: "Matrix") -> "Matrix": else: raise Exception("matrix must have the same dimension!") - def __sub__(self, other: "Matrix") -> "Matrix": + def __sub__(self, other: Matrix) -> Matrix: """ implements the matrix-subtraction. """ diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py index af845c9109b1..ec1b9f9e3e13 100644 --- a/machine_learning/similarity_search.py +++ b/machine_learning/similarity_search.py @@ -7,8 +7,9 @@ 1. the nearest vector 2. distance between the vector and the nearest vector (float) """ +from __future__ import annotations + import math -from typing import List, Union import numpy as np @@ -33,7 +34,7 @@ def euclidean(input_a: np.ndarray, input_b: np.ndarray) -> float: def similarity_search( dataset: np.ndarray, value_array: np.ndarray -) -> List[List[Union[List[float], float]]]: +) -> list[list[list[float] | float]]: """ :param dataset: Set containing the vectors. Should be ndarray. :param value_array: vector/vectors we want to know the nearest vector from dataset. diff --git a/maths/area_under_curve.py b/maths/area_under_curve.py index 2d01e414b63b..ce0932426ef6 100644 --- a/maths/area_under_curve.py +++ b/maths/area_under_curve.py @@ -1,14 +1,15 @@ """ Approximates the area under the curve using the trapezoidal rule """ +from __future__ import annotations -from typing import Callable, Union +from typing import Callable def trapezoidal_area( - fnc: Callable[[Union[int, float]], Union[int, float]], - x_start: Union[int, float], - x_end: Union[int, float], + fnc: Callable[[int | float], int | float], + x_start: int | float, + x_end: int | float, steps: int = 100, ) -> float: """ diff --git a/maths/average_mean.py b/maths/average_mean.py index e02e307f20c8..274c434ab885 100644 --- a/maths/average_mean.py +++ b/maths/average_mean.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def mean(nums: List) -> float: +def mean(nums: list) -> float: """ Find mean of a list of numbers. Wiki: https://en.wikipedia.org/wiki/Mean diff --git a/maths/average_median.py b/maths/average_median.py index 497bf0c3a714..cd1ec1574893 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -1,7 +1,7 @@ -from typing import Union +from __future__ import annotations -def median(nums: list) -> Union[int, float]: +def median(nums: list) -> int | float: """ Find median of a list of numbers. Wiki: https://en.wikipedia.org/wiki/Median diff --git a/maths/entropy.py b/maths/entropy.py index 43bb3860fc12..498c28f31bc4 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("{:.1f}".format(round(-1 * my_fir_sum))) + print(f"{round(-1 * my_fir_sum):.1f}") # 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("{:.1f}".format(round(-1 * my_sec_sum))) + print(f"{round(-1 * my_sec_sum):.1f}") # print the difference between them - print("{:.1f}".format(round((-1 * my_sec_sum) - (-1 * my_fir_sum)))) + print(f"{round((-1 * my_sec_sum) - (-1 * my_fir_sum)):.1f}") def analyze_text(text: str) -> tuple[dict, dict]: diff --git a/maths/euclidean_distance.py b/maths/euclidean_distance.py index 6e0da6370219..a2078161374b 100644 --- a/maths/euclidean_distance.py +++ b/maths/euclidean_distance.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Iterable, Union import numpy as np diff --git a/maths/extended_euclidean_algorithm.py b/maths/extended_euclidean_algorithm.py index e7087636ce09..72afd40aa707 100644 --- a/maths/extended_euclidean_algorithm.py +++ b/maths/extended_euclidean_algorithm.py @@ -12,12 +12,12 @@ # @Email: silentcat@protonmail.com # @Last modified by: pikulet # @Last modified time: 2020-10-02 +from __future__ import annotations import sys -from typing import Tuple -def extended_euclidean_algorithm(a: int, b: int) -> Tuple[int, int]: +def extended_euclidean_algorithm(a: int, b: int) -> tuple[int, int]: """ Extended Euclidean Algorithm. diff --git a/maths/hardy_ramanujanalgo.py b/maths/hardy_ramanujanalgo.py index 90e4913c70a7..e36f763da19e 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 {:.4f}".format(math.log(math.log(n)))) + print(f"The value of log(log(n)) is {math.log(math.log(n)):.4f}") """ The number of distinct prime factors is/are 3 diff --git a/maths/line_length.py b/maths/line_length.py index 1d386b44b50d..c4d986279cda 100644 --- a/maths/line_length.py +++ b/maths/line_length.py @@ -1,11 +1,13 @@ +from __future__ import annotations + import math -from typing import Callable, Union +from typing import Callable def line_length( - fnc: Callable[[Union[int, float]], Union[int, float]], - x_start: Union[int, float], - x_end: Union[int, float], + fnc: Callable[[int | float], int | float], + x_start: int | float, + x_end: int | float, steps: int = 100, ) -> float: diff --git a/maths/max_sum_sliding_window.py b/maths/max_sum_sliding_window.py index 593cb5c8bd67..c6f9b4ed0ad7 100644 --- a/maths/max_sum_sliding_window.py +++ b/maths/max_sum_sliding_window.py @@ -6,10 +6,10 @@ called 'Window sliding technique' where the nested loops can be converted to a single loop to reduce time complexity. """ -from typing import List +from __future__ import annotations -def max_sum_in_array(array: List[int], k: int) -> int: +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] diff --git a/maths/median_of_two_arrays.py b/maths/median_of_two_arrays.py index cde12f5d7e3b..55aa587a9c4b 100644 --- a/maths/median_of_two_arrays.py +++ b/maths/median_of_two_arrays.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def median_of_two_arrays(nums1: List[float], nums2: List[float]) -> float: +def median_of_two_arrays(nums1: list[float], nums2: list[float]) -> float: """ >>> median_of_two_arrays([1, 2], [3]) 2 diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index 87184a76b740..577c41a4440e 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -1,14 +1,15 @@ """ Approximates the area under the curve using the trapezoidal rule """ +from __future__ import annotations -from typing import Callable, Union +from typing import Callable def trapezoidal_area( - fnc: Callable[[Union[int, float]], Union[int, float]], - x_start: Union[int, float], - x_end: Union[int, float], + fnc: Callable[[int | float], int | float], + x_start: int | float, + x_end: int | float, steps: int = 100, ) -> float: diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index 47a086546900..3cd6ce0b4d9d 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -10,13 +10,12 @@ doctest provider: Bruno Simas Hadlich (https://github.com/brunohadlich) Also thanks to Dmitry (https://github.com/LizardWizzard) for finding the problem """ - +from __future__ import annotations import math -from typing import List -def prime_sieve(num: int) -> List[int]: +def prime_sieve(num: int) -> list[int]: """ Returns a list with all prime numbers up to n. diff --git a/maths/volume.py b/maths/volume.py index 41d2331db3cb..51b2b9fc0334 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -3,11 +3,12 @@ Wikipedia reference: https://en.wikipedia.org/wiki/Volume """ +from __future__ import annotations + from math import pi, pow -from typing import Union -def vol_cube(side_length: Union[int, float]) -> float: +def vol_cube(side_length: int | float) -> float: """ Calculate the Volume of a Cube. diff --git a/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py index ca6263a32f50..ae81361499e5 100644 --- a/matrix/searching_in_sorted_matrix.py +++ b/matrix/searching_in_sorted_matrix.py @@ -1,10 +1,8 @@ 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: int | float ) -> None: """ >>> search_in_a_sorted_matrix( diff --git a/other/date_to_weekday.py b/other/date_to_weekday.py index bb17130c0da5..9dc68666e3b4 100644 --- a/other/date_to_weekday.py +++ b/other/date_to_weekday.py @@ -14,7 +14,7 @@ def date_to_weekday(inp_date: str) -> str: >>> date_to_weekday("1/1/2021") 'Friday' """ - day, month, year = [int(x) for x in inp_date.split("/")] + day, month, year = (int(x) for x in inp_date.split("/")) if year % 100 == 0: year = "00" new_base_date: str = f"{day}/{month}/{year%100} 0:0:0" diff --git "a/other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" b/other/davisb_putnamb_logemannb_loveland.py similarity index 94% rename from "other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" rename to other/davisb_putnamb_logemannb_loveland.py index d16de6dd988b..00068930b89e 100644 --- "a/other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" +++ b/other/davisb_putnamb_logemannb_loveland.py @@ -8,9 +8,9 @@ For more information about the algorithm: https://en.wikipedia.org/wiki/DPLL_algorithm """ +from __future__ import annotations import random -from typing import Dict, List class Clause: @@ -27,7 +27,7 @@ class Clause: True """ - def __init__(self, literals: List[int]) -> None: + def __init__(self, literals: list[int]) -> None: """ Represent the literals and an assignment in a clause." """ @@ -52,7 +52,7 @@ def __len__(self) -> int: """ return len(self.literals) - def assign(self, model: Dict[str, bool]) -> None: + def assign(self, model: dict[str, bool]) -> None: """ Assign values to literals of the clause as given by model. """ @@ -68,7 +68,7 @@ def assign(self, model: Dict[str, bool]) -> None: value = not value self.literals[literal] = value - def evaluate(self, model: Dict[str, bool]) -> bool: + def evaluate(self, model: dict[str, bool]) -> bool: """ Evaluates the clause with the assignments in model. This has the following steps: @@ -97,7 +97,7 @@ class Formula: {{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: + def __init__(self, clauses: list[Clause]) -> None: """ Represent the number of clauses and the clauses themselves. """ @@ -146,7 +146,7 @@ def generate_formula() -> Formula: return Formula(set(clauses)) -def generate_parameters(formula: Formula) -> (List[Clause], List[str]): +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. @@ -173,8 +173,8 @@ def generate_parameters(formula: Formula) -> (List[Clause], List[str]): def find_pure_symbols( - clauses: List[Clause], symbols: List[str], model: Dict[str, bool] -) -> (List[str], Dict[str, bool]): + 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 @@ -225,8 +225,8 @@ def find_pure_symbols( def find_unit_clauses( - clauses: List[Clause], model: Dict[str, bool] -) -> (List[str], Dict[str, bool]): + 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: @@ -273,8 +273,8 @@ def find_unit_clauses( def dpll_algorithm( - clauses: List[Clause], symbols: List[str], model: Dict[str, bool] -) -> (bool, Dict[str, bool]): + 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: diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 40268242f564..88167ac1f2cb 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -1,4 +1,6 @@ -from typing import Callable, Optional +from __future__ import annotations + +from typing import Callable class DoubleLinkedListNode: @@ -119,7 +121,7 @@ def __contains__(self, key: int) -> bool: """ return key in self.cache - def get(self, key: int) -> Optional[int]: + def get(self, key: int) -> int | None: """ Returns the value for the input key and updates the Double Linked List. Returns None if key is not present in cache diff --git a/other/lru_cache.py b/other/lru_cache.py index 2a9d7e49b279..b74c0a45caf9 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -1,4 +1,6 @@ -from typing import Callable, Optional +from __future__ import annotations + +from typing import Callable class DoubleLinkedListNode: @@ -125,7 +127,7 @@ def __contains__(self, key: int) -> bool: return key in self.cache - def get(self, key: int) -> Optional[int]: + def get(self, key: int) -> int | None: """ Returns the value for the input key and updates the Double Linked List. Returns None if key is not present in cache diff --git a/project_euler/problem_001/sol1.py b/project_euler/problem_001/sol1.py index 85ad32294c9b..fcc24c86ec54 100644 --- a/project_euler/problem_001/sol1.py +++ b/project_euler/problem_001/sol1.py @@ -26,7 +26,7 @@ def solution(n: int = 1000) -> int: 0 """ - return sum([e for e in range(3, n) if e % 3 == 0 or e % 5 == 0]) + return sum(e for e in range(3, n) if e % 3 == 0 or e % 5 == 0) if __name__ == "__main__": diff --git a/project_euler/problem_001/sol5.py b/project_euler/problem_001/sol5.py index 7f0b0bd1bc7c..3edc6f245a67 100644 --- a/project_euler/problem_001/sol5.py +++ b/project_euler/problem_001/sol5.py @@ -25,7 +25,7 @@ def solution(n: int = 1000) -> int: 83700 """ - return sum([i for i in range(n) if i % 3 == 0 or i % 5 == 0]) + return sum(i for i in range(n) if i % 3 == 0 or i % 5 == 0) if __name__ == "__main__": diff --git a/project_euler/problem_006/sol3.py b/project_euler/problem_006/sol3.py index c87931309574..529f233c9f8e 100644 --- a/project_euler/problem_006/sol3.py +++ b/project_euler/problem_006/sol3.py @@ -33,7 +33,7 @@ def solution(n: int = 100) -> int: 1582700 """ - sum_of_squares = sum([i * i for i in range(1, n + 1)]) + 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 diff --git a/project_euler/problem_008/sol2.py b/project_euler/problem_008/sol2.py index d2c1b4f7ca48..7f0540263278 100644 --- a/project_euler/problem_008/sol2.py +++ b/project_euler/problem_008/sol2.py @@ -70,10 +70,7 @@ def solution(n: str = N) -> int: """ return max( - [ - reduce(lambda x, y: int(x) * int(y), n[i : i + 13]) - for i in range(len(n) - 12) - ] + reduce(lambda x, y: int(x) * int(y), n[i : i + 13]) for i in range(len(n) - 12) ) diff --git a/project_euler/problem_012/sol2.py b/project_euler/problem_012/sol2.py index 5ff0d8349b90..7578caa98938 100644 --- a/project_euler/problem_012/sol2.py +++ b/project_euler/problem_012/sol2.py @@ -29,7 +29,7 @@ def triangle_number_generator(): 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(): diff --git a/project_euler/problem_013/sol1.py b/project_euler/problem_013/sol1.py index 1ea08b12ee93..7a414a9379e0 100644 --- a/project_euler/problem_013/sol1.py +++ b/project_euler/problem_013/sol1.py @@ -18,7 +18,7 @@ def solution(): """ file_path = os.path.join(os.path.dirname(__file__), "num.txt") with open(file_path) as file_hand: - return str(sum([int(line) for line in file_hand]))[:10] + return str(sum(int(line) for line in file_hand))[:10] if __name__ == "__main__": diff --git a/project_euler/problem_014/sol2.py b/project_euler/problem_014/sol2.py index 20ad96327498..7ed68273bcd7 100644 --- a/project_euler/problem_014/sol2.py +++ b/project_euler/problem_014/sol2.py @@ -25,10 +25,10 @@ Which starting number, under one million, produces the longest chain? """ -from typing import List +from __future__ import annotations -def collatz_sequence(n: int) -> List[int]: +def collatz_sequence(n: int) -> list[int]: """Returns the Collatz sequence for n.""" sequence = [n] while n != 1: @@ -54,7 +54,7 @@ def solution(n: int = 1000000) -> int: 13255 """ - result = max([(len(collatz_sequence(i)), i) for i in range(1, n)]) + result = max((len(collatz_sequence(i)), i) for i in range(1, n)) return result[1] diff --git a/project_euler/problem_020/sol2.py b/project_euler/problem_020/sol2.py index 92e1e724a647..676e96e7836a 100644 --- a/project_euler/problem_020/sol2.py +++ b/project_euler/problem_020/sol2.py @@ -28,7 +28,7 @@ def solution(num: int = 100) -> int: >>> solution(1) 1 """ - return sum([int(x) for x in str(factorial(num))]) + return sum(int(x) for x in str(factorial(num))) if __name__ == "__main__": diff --git a/project_euler/problem_021/sol1.py b/project_euler/problem_021/sol1.py index 3fac79156e41..353510ae8f94 100644 --- a/project_euler/problem_021/sol1.py +++ b/project_euler/problem_021/sol1.py @@ -41,11 +41,9 @@ def solution(n: int = 10000) -> int: 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 - ] + i + for i in range(1, n) + if sum_of_divisors(sum_of_divisors(i)) == i and sum_of_divisors(i) != i ) return total diff --git a/project_euler/problem_033/sol1.py b/project_euler/problem_033/sol1.py index ba6e553d8689..e0c9a058af53 100644 --- a/project_euler/problem_033/sol1.py +++ b/project_euler/problem_033/sol1.py @@ -14,8 +14,9 @@ If the product of these four fractions is given in its lowest common terms, find the value of the denominator. """ +from __future__ import annotations + from fractions import Fraction -from typing import List def is_digit_cancelling(num: int, den: int) -> bool: @@ -26,7 +27,7 @@ def is_digit_cancelling(num: int, den: int) -> bool: return False -def fraction_list(digit_len: int) -> List[str]: +def fraction_list(digit_len: int) -> list[str]: """ >>> fraction_list(2) ['16/64', '19/95', '26/65', '49/98'] diff --git a/project_euler/problem_036/sol1.py b/project_euler/problem_036/sol1.py index 13a749862e5f..425c41221395 100644 --- a/project_euler/problem_036/sol1.py +++ b/project_euler/problem_036/sol1.py @@ -14,11 +14,10 @@ (Please note that the palindromic number, in either base, may not include leading zeros.) """ +from __future__ import annotations -from typing import Union - -def is_palindrome(n: Union[int, str]) -> bool: +def is_palindrome(n: int | str) -> bool: """ Return true if the input n is a palindrome. Otherwise return false. n can be an integer or a string. diff --git a/project_euler/problem_038/sol1.py b/project_euler/problem_038/sol1.py index 6d54f6df7ff8..e4a6d09f8f7d 100644 --- a/project_euler/problem_038/sol1.py +++ b/project_euler/problem_038/sol1.py @@ -37,8 +37,7 @@ => 100 <= a < 334, candidate = a * 10^6 + 2a * 10^3 + 3a = 1002003 * a """ - -from typing import Union +from __future__ import annotations def is_9_pandigital(n: int) -> bool: @@ -55,7 +54,7 @@ def is_9_pandigital(n: int) -> bool: return len(s) == 9 and set(s) == set("123456789") -def solution() -> Union[int, None]: +def solution() -> 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. diff --git a/project_euler/problem_049/sol1.py b/project_euler/problem_049/sol1.py index c0d0715be91c..dd2ef71a38a8 100644 --- a/project_euler/problem_049/sol1.py +++ b/project_euler/problem_049/sol1.py @@ -132,7 +132,7 @@ def solution(): for seq in passed: answer.add("".join([str(i) for i in seq])) - return max([int(x) for x in answer]) + return max(int(x) for x in answer) if __name__ == "__main__": diff --git a/project_euler/problem_050/sol1.py b/project_euler/problem_050/sol1.py index 7d142e5ffc91..cfb1911df5de 100644 --- a/project_euler/problem_050/sol1.py +++ b/project_euler/problem_050/sol1.py @@ -15,10 +15,10 @@ Which prime, below one-million, can be written as the sum of the most consecutive primes? """ -from typing import List +from __future__ import annotations -def prime_sieve(limit: int) -> List[int]: +def prime_sieve(limit: int) -> list[int]: """ Sieve of Erotosthenes Function to return all the prime numbers up to a number 'limit' diff --git a/project_euler/problem_051/sol1.py b/project_euler/problem_051/sol1.py index b160b5a2dbd4..5f607e3ffb42 100644 --- a/project_euler/problem_051/sol1.py +++ b/project_euler/problem_051/sol1.py @@ -15,12 +15,12 @@ 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 __future__ import annotations from collections import Counter -from typing import List -def prime_sieve(n: int) -> List[int]: +def prime_sieve(n: int) -> list[int]: """ Sieve of Erotosthenes Function to return all the prime numbers up to a certain number @@ -52,7 +52,7 @@ def prime_sieve(n: int) -> List[int]: return primes -def digit_replacements(number: int) -> List[List[int]]: +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 diff --git a/project_euler/problem_054/sol1.py b/project_euler/problem_054/sol1.py index d2fd810d1b69..9af7aef5a716 100644 --- a/project_euler/problem_054/sol1.py +++ b/project_euler/problem_054/sol1.py @@ -135,7 +135,7 @@ def hand(self): """Returns the self hand""" return self._hand - def compare_with(self, other: "PokerHand") -> str: + 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 @@ -220,7 +220,7 @@ def hand_name(self) -> str: else: return name + f", {high}" - def _compare_cards(self, other: "PokerHand") -> str: + 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]: diff --git a/project_euler/problem_056/sol1.py b/project_euler/problem_056/sol1.py index 8eaa6e553342..f1ec03c497be 100644 --- a/project_euler/problem_056/sol1.py +++ b/project_euler/problem_056/sol1.py @@ -30,11 +30,9 @@ def solution(a: int = 100, b: int = 100) -> 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) - ] + sum(int(x) for x in str(base ** power)) + for base in range(a) + for power in range(b) ) diff --git a/project_euler/problem_059/sol1.py b/project_euler/problem_059/sol1.py index 1f55029b2613..b795dd243b08 100644 --- a/project_euler/problem_059/sol1.py +++ b/project_euler/problem_059/sol1.py @@ -25,23 +25,22 @@ must contain common English words, decrypt the message and find the sum of the ASCII values in the original text. """ - +from __future__ import annotations 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} +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"] +COMMON_WORDS: list[str] = ["the", "be", "to", "of", "and", "in", "that", "have"] -def try_key(ciphertext: List[int], key: Tuple[int, ...]) -> Optional[str]: +def try_key(ciphertext: list[int], key: tuple[int, ...]) -> str | None: """ 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, @@ -66,7 +65,7 @@ def try_key(ciphertext: List[int], key: Tuple[int, ...]) -> Optional[str]: return decoded -def filter_valid_chars(ciphertext: List[int]) -> List[str]: +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. @@ -77,7 +76,7 @@ def filter_valid_chars(ciphertext: List[int]) -> List[str]: >>> text in filter_valid_chars(encoded) True """ - possibles: List[str] = [] + possibles: list[str] = [] for key in product(LOWERCASE_INTS, repeat=3): encoded = try_key(ciphertext, key) if encoded is not None: @@ -85,7 +84,7 @@ def filter_valid_chars(ciphertext: List[int]) -> List[str]: return possibles -def filter_common_word(possibles: List[str], common_word: str) -> List[str]: +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 @@ -106,8 +105,8 @@ def solution(filename: str = "p059_cipher.txt") -> int: >>> solution("test_cipher.txt") 3000 """ - ciphertext: List[int] - possibles: List[str] + ciphertext: list[int] + possibles: list[str] common_word: str decoded_text: str data: str = Path(__file__).parent.joinpath(filename).read_text(encoding="utf-8") @@ -121,7 +120,7 @@ def solution(filename: str = "p059_cipher.txt") -> int: break decoded_text = possibles[0] - return sum([ord(char) for char in decoded_text]) + return sum(ord(char) for char in decoded_text) if __name__ == "__main__": diff --git a/project_euler/problem_070/sol1.py b/project_euler/problem_070/sol1.py index 9d27119ba95c..e106800d5716 100644 --- a/project_euler/problem_070/sol1.py +++ b/project_euler/problem_070/sol1.py @@ -28,10 +28,10 @@ Finding totients https://en.wikipedia.org/wiki/Euler's_totient_function#Euler's_product_formula """ -from typing import List +from __future__ import annotations -def get_totients(max_one: int) -> List[int]: +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. diff --git a/project_euler/problem_074/sol1.py b/project_euler/problem_074/sol1.py index 38d4e1439307..a40a629033fa 100644 --- a/project_euler/problem_074/sol1.py +++ b/project_euler/problem_074/sol1.py @@ -66,7 +66,7 @@ def sum_digit_factorials(n: int) -> int: """ if n in CACHE_SUM_DIGIT_FACTORIALS: return CACHE_SUM_DIGIT_FACTORIALS[n] - ret = sum([DIGIT_FACTORIALS[let] for let in str(n)]) + ret = sum(DIGIT_FACTORIALS[let] for let in str(n)) CACHE_SUM_DIGIT_FACTORIALS[n] = ret return ret diff --git a/project_euler/problem_077/sol1.py b/project_euler/problem_077/sol1.py index e92992a90ab3..214e258793f6 100644 --- a/project_euler/problem_077/sol1.py +++ b/project_euler/problem_077/sol1.py @@ -12,10 +12,10 @@ What is the first value which can be written as the sum of primes in over five thousand different ways? """ +from __future__ import annotations from functools import lru_cache from math import ceil -from typing import Optional, Set NUM_PRIMES = 100 @@ -30,7 +30,7 @@ @lru_cache(maxsize=100) -def partition(number_to_partition: int) -> Set[int]: +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, @@ -47,7 +47,7 @@ def partition(number_to_partition: int) -> Set[int]: elif number_to_partition == 0: return {1} - ret: Set[int] = set() + ret: set[int] = set() prime: int sub: int @@ -60,7 +60,7 @@ def partition(number_to_partition: int) -> Set[int]: return ret -def solution(number_unique_partitions: int = 5000) -> Optional[int]: +def solution(number_unique_partitions: int = 5000) -> int | None: """ Return the smallest integer that can be written as the sum of primes in over m unique ways. diff --git a/project_euler/problem_080/sol1.py b/project_euler/problem_080/sol1.py index db69d7e8451c..517be3fc0ba8 100644 --- a/project_euler/problem_080/sol1.py +++ b/project_euler/problem_080/sol1.py @@ -27,7 +27,7 @@ def solution() -> int: 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]) + answer += sum(int(x) for x in sqrt_number) return answer diff --git a/project_euler/problem_081/sol1.py b/project_euler/problem_081/sol1.py index afa143f23b33..aef6106b54df 100644 --- a/project_euler/problem_081/sol1.py +++ b/project_euler/problem_081/sol1.py @@ -22,7 +22,7 @@ def solution(filename: str = "matrix.txt") -> int: >>> solution() 427337 """ - with open(os.path.join(os.path.dirname(__file__), filename), "r") as in_file: + with open(os.path.join(os.path.dirname(__file__), filename)) as in_file: data = in_file.read() grid = [[int(cell) for cell in row.split(",")] for row in data.strip().splitlines()] diff --git a/project_euler/problem_085/sol1.py b/project_euler/problem_085/sol1.py index 74e36b1301a4..d0f29796498c 100644 --- a/project_euler/problem_085/sol1.py +++ b/project_euler/problem_085/sol1.py @@ -44,10 +44,9 @@ Reference: https://en.wikipedia.org/wiki/Triangular_number https://en.wikipedia.org/wiki/Quadratic_formula """ - +from __future__ import annotations from math import ceil, floor, sqrt -from typing import List def solution(target: int = 2000000) -> int: @@ -61,7 +60,7 @@ def solution(target: int = 2000000) -> int: >>> solution(2000000000) 86595 """ - triangle_numbers: List[int] = [0] + triangle_numbers: list[int] = [0] idx: int for idx in range(1, ceil(sqrt(target * 2) * 1.1)): diff --git a/project_euler/problem_089/sol1.py b/project_euler/problem_089/sol1.py index 11582aa4ab1a..1c4e2600f847 100644 --- a/project_euler/problem_089/sol1.py +++ b/project_euler/problem_089/sol1.py @@ -125,7 +125,7 @@ def solution(roman_numerals_filename: str = "/p089_roman.txt") -> int: savings = 0 - file1 = open(os.path.dirname(__file__) + roman_numerals_filename, "r") + file1 = open(os.path.dirname(__file__) + roman_numerals_filename) lines = file1.readlines() for line in lines: original = line.strip() diff --git a/project_euler/problem_101/sol1.py b/project_euler/problem_101/sol1.py index e66316090fb2..553f8f442bb8 100644 --- a/project_euler/problem_101/sol1.py +++ b/project_euler/problem_101/sol1.py @@ -41,11 +41,11 @@ Find the sum of FITs for the BOPs. """ +from __future__ import annotations +from typing import Callable, Union -from typing import Callable, List, Union - -Matrix = List[List[Union[float, int]]] +Matrix = list[list[Union[float, int]]] def solve(matrix: Matrix, vector: Matrix) -> Matrix: @@ -78,9 +78,9 @@ def solve(matrix: Matrix, vector: Matrix) -> Matrix: col = 0 while row < size and col < size: # pivoting - pivot_row = max( - [(abs(augmented[row2][col]), row2) for row2 in range(col, size)] - )[1] + pivot_row = max((abs(augmented[row2][col]), row2) for row2 in range(col, size))[ + 1 + ] if augmented[pivot_row][col] == 0: col += 1 continue @@ -109,7 +109,7 @@ def solve(matrix: Matrix, vector: Matrix) -> Matrix: ] -def interpolate(y_list: List[int]) -> Callable[[int], int]: +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 @@ -195,9 +195,9 @@ def solution(func: Callable[[int], int] = question_function, order: int = 10) -> >>> solution(lambda n: n ** 3, 3) 74 """ - data_points: List[int] = [func(x_val) for x_val in range(1, order + 1)] + data_points: list[int] = [func(x_val) for x_val in range(1, order + 1)] - polynomials: List[Callable[[int], int]] = [ + polynomials: list[Callable[[int], int]] = [ interpolate(data_points[:max_coeff]) for max_coeff in range(1, order + 1) ] diff --git a/project_euler/problem_102/sol1.py b/project_euler/problem_102/sol1.py index 00af726656ce..4f6e6361e3e8 100644 --- a/project_euler/problem_102/sol1.py +++ b/project_euler/problem_102/sol1.py @@ -18,12 +18,12 @@ NOTE: The first two examples in the file represent the triangles in the example given above. """ +from __future__ import annotations from pathlib import Path -from typing import List, Tuple -def vector_product(point1: Tuple[int, int], point2: Tuple[int, int]) -> int: +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)) @@ -43,9 +43,9 @@ def contains_origin(x1: int, y1: int, x2: int, y2: int, x3: int, y3: int) -> boo >>> 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) + 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 ) @@ -64,12 +64,12 @@ def solution(filename: str = "p102_triangles.txt") -> int: """ data: str = Path(__file__).parent.joinpath(filename).read_text(encoding="utf-8") - triangles: List[List[int]] = [] + 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] + triangle: list[int] for triangle in triangles: ret += contains_origin(*triangle) diff --git a/project_euler/problem_107/sol1.py b/project_euler/problem_107/sol1.py index 80a10e499f76..6a411a11473d 100644 --- a/project_euler/problem_107/sol1.py +++ b/project_euler/problem_107/sol1.py @@ -27,11 +27,12 @@ We use Prim's algorithm to find a Minimum Spanning Tree. Reference: https://en.wikipedia.org/wiki/Prim%27s_algorithm """ +from __future__ import annotations import os -from typing import Dict, List, Mapping, Set, Tuple +from typing import Mapping -EdgeT = Tuple[int, int] +EdgeT = tuple[int, int] class Graph: @@ -39,9 +40,9 @@ 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] = { + 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() } @@ -59,7 +60,7 @@ def add_edge(self, edge: EdgeT, weight: int) -> None: self.vertices.add(edge[1]) self.edges[(min(edge), max(edge))] = weight - def prims_algorithm(self) -> "Graph": + def prims_algorithm(self) -> Graph: """ Run Prim's algorithm to find the minimum spanning tree. Reference: https://en.wikipedia.org/wiki/Prim%27s_algorithm @@ -98,13 +99,13 @@ def solution(filename: str = "p107_network.txt") -> int: """ 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] + adjacency_matrix: list[list[str]] + edges: dict[EdgeT, int] = dict() + data: list[str] edge1: int edge2: int - with open(network_file, "r") as f: + with open(network_file) as f: data = f.read().strip().split("\n") adjaceny_matrix = [line.split(",") for line in data] diff --git a/project_euler/problem_119/sol1.py b/project_euler/problem_119/sol1.py index 7f343ac242e9..60ec16cda1aa 100644 --- a/project_euler/problem_119/sol1.py +++ b/project_euler/problem_119/sol1.py @@ -23,7 +23,7 @@ def digit_sum(n: int) -> int: >>> digit_sum(78910) 25 """ - return sum([int(digit) for digit in str(n)]) + return sum(int(digit) for digit in str(n)) def solution(n: int = 30) -> int: diff --git a/project_euler/problem_123/sol1.py b/project_euler/problem_123/sol1.py index 85350c8bae49..91913222759b 100644 --- a/project_euler/problem_123/sol1.py +++ b/project_euler/problem_123/sol1.py @@ -37,8 +37,9 @@ r = 2pn when n is odd r = 2 when n is even. """ +from __future__ import annotations -from typing import Dict, Generator +from typing import Generator def sieve() -> Generator[int, None, None]: @@ -60,7 +61,7 @@ def sieve() -> Generator[int, None, None]: >>> next(primes) 13 """ - factor_map: Dict[int, int] = {} + factor_map: dict[int, int] = {} prime = 2 while True: factor = factor_map.pop(prime, None) diff --git a/project_euler/problem_180/sol1.py b/project_euler/problem_180/sol1.py index 6112db2ea370..f7c097323c62 100644 --- a/project_euler/problem_180/sol1.py +++ b/project_euler/problem_180/sol1.py @@ -44,11 +44,10 @@ Reference: https://en.wikipedia.org/wiki/Fermat%27s_Last_Theorem """ - +from __future__ import annotations from fractions import Fraction from math import gcd, sqrt -from typing import Tuple def is_sq(number: int) -> bool: @@ -68,7 +67,7 @@ def is_sq(number: int) -> bool: def add_three( x_num: int, x_den: int, y_num: int, y_den: int, z_num: int, z_den: int -) -> Tuple[int, int]: +) -> tuple[int, int]: """ Given the numerators and denominators of three fractions, return the numerator and denominator of their sum in lowest form. @@ -100,7 +99,7 @@ def solution(order: int = 35) -> int: unique_s: set = set() hcf: int total: Fraction = Fraction(0) - fraction_sum: Tuple[int, int] + fraction_sum: tuple[int, int] for x_num in range(1, order + 1): for x_den in range(x_num + 1, order + 1): diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py index 227b476da131..030cf12f2a85 100644 --- a/project_euler/problem_203/sol1.py +++ b/project_euler/problem_203/sol1.py @@ -27,12 +27,12 @@ References: - https://en.wikipedia.org/wiki/Pascal%27s_triangle """ +from __future__ import annotations import math -from typing import List, Set -def get_pascal_triangle_unique_coefficients(depth: int) -> Set[int]: +def get_pascal_triangle_unique_coefficients(depth: int) -> set[int]: """ Returns the unique coefficients of a Pascal's triangle of depth "depth". @@ -61,7 +61,7 @@ def get_pascal_triangle_unique_coefficients(depth: int) -> Set[int]: return coefficients -def get_primes_squared(max_number: int) -> List[int]: +def get_primes_squared(max_number: int) -> list[int]: """ Calculates all primes between 2 and round(sqrt(max_number)) and returns them squared up. @@ -92,7 +92,7 @@ def get_primes_squared(max_number: int) -> List[int]: def get_squared_primes_to_use( - num_to_look: int, squared_primes: List[int], previous_index: int + num_to_look: int, squared_primes: list[int], previous_index: int ) -> int: """ Returns an int indicating the last index on which squares of primes @@ -128,8 +128,8 @@ def get_squared_primes_to_use( def get_squarefree( - unique_coefficients: Set[int], squared_primes: List[int] -) -> Set[int]: + unique_coefficients: set[int], squared_primes: list[int] +) -> set[int]: """ Calculates the squarefree numbers inside unique_coefficients given a list of square of primes. 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 100644 --- 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 a49d037d6a23..17409108a34e 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 ) -> None: """ This function calculates the average of the waiting & turnaround times diff --git a/searches/binary_search.py b/searches/binary_search.py index 35e0dd0596d2..0966cd8de857 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -9,12 +9,13 @@ For manual testing run: python3 binary_search.py """ +from __future__ import annotations + import bisect -from typing import List, Optional def bisect_left( - sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 + 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 @@ -60,7 +61,7 @@ def bisect_left( def bisect_right( - sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 + 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. @@ -105,7 +106,7 @@ def bisect_right( def insort_left( - sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 + 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. @@ -148,7 +149,7 @@ def insort_left( def insort_right( - sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 + 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. @@ -190,7 +191,7 @@ def insort_right( sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item) -def binary_search(sorted_collection: List[int], item: int) -> Optional[int]: +def binary_search(sorted_collection: list[int], item: int) -> int | None: """Pure implementation of binary search algorithm in Python Be careful collection must be ascending sorted, otherwise result will be @@ -228,7 +229,7 @@ def binary_search(sorted_collection: List[int], item: int) -> Optional[int]: return None -def binary_search_std_lib(sorted_collection: List[int], item: int) -> Optional[int]: +def binary_search_std_lib(sorted_collection: list[int], item: int) -> int | None: """Pure implementation of binary search algorithm in Python using stdlib Be careful collection must be ascending sorted, otherwise result will be @@ -258,8 +259,8 @@ def binary_search_std_lib(sorted_collection: List[int], item: int) -> Optional[i def binary_search_by_recursion( - sorted_collection: List[int], item: int, left: int, right: int -) -> Optional[int]: + sorted_collection: list[int], item: int, left: int, right: int +) -> int | None: """Pure implementation of binary search algorithm in Python by recursion diff --git a/searches/fibonacci_search.py b/searches/fibonacci_search.py index ac8ecc99a187..55fc05d39eeb 100644 --- a/searches/fibonacci_search.py +++ b/searches/fibonacci_search.py @@ -13,7 +13,7 @@ from functools import lru_cache -@lru_cache() +@lru_cache def fibonacci(k: int) -> int: """Finds fibonacci number in index k. diff --git a/searches/ternary_search.py b/searches/ternary_search.py index 9422a4ccb966..01e437723473 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -6,7 +6,7 @@ Time Complexity : O(log3 N) Space Complexity : O(1) """ -from typing import List +from __future__ import annotations # 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. @@ -16,7 +16,7 @@ # This is the linear search that will occur after the search space has become smaller. -def lin_search(left: int, right: int, array: List[int], target: int) -> int: +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 @@ -58,7 +58,7 @@ def lin_search(left: int, right: int, array: List[int], target: int) -> int: return -1 -def ite_ternary_search(array: List[int], target: int) -> int: +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) @@ -110,7 +110,7 @@ def ite_ternary_search(array: List[int], target: int) -> int: return -1 -def rec_ternary_search(left: int, right: int, array: List[int], target: int) -> int: +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] diff --git a/sorts/bitonic_sort.py b/sorts/bitonic_sort.py index c718973e5ecb..201fecd2ce86 100644 --- a/sorts/bitonic_sort.py +++ b/sorts/bitonic_sort.py @@ -3,10 +3,10 @@ Note that this program works only when size of input is a power of 2. """ -from typing import List +from __future__ import annotations -def comp_and_swap(array: List[int], index1: int, index2: int, direction: int) -> None: +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. @@ -37,7 +37,7 @@ def comp_and_swap(array: List[int], index1: int, index2: int, direction: int) -> array[index1], array[index2] = array[index2], array[index1] -def bitonic_merge(array: List[int], low: int, length: int, direction: int) -> None: +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. @@ -61,7 +61,7 @@ def bitonic_merge(array: List[int], low: int, length: int, direction: int) -> No bitonic_merge(array, low + middle, middle, direction) -def bitonic_sort(array: List[int], low: int, length: int, direction: int) -> None: +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 diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 1ac76774f4ba..58242a1cb1f8 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -27,7 +27,7 @@ Source: https://en.wikipedia.org/wiki/Bucket_sort """ -from typing import List +from __future__ import annotations def bucket_sort(my_list: list) -> list: @@ -52,7 +52,7 @@ def bucket_sort(my_list: list) -> list: return [] min_value, max_value = min(my_list), max(my_list) bucket_count = int(max_value - min_value) + 1 - buckets: List[list] = [[] for _ in range(bucket_count)] + buckets: list[list] = [[] for _ in range(bucket_count)] for i in range(len(my_list)): buckets[(int(my_list[i] - min_value) // bucket_count)].append(my_list[i]) diff --git a/sorts/msd_radix_sort.py b/sorts/msd_radix_sort.py index 4c3cea30ef68..3cdec4bd0711 100644 --- a/sorts/msd_radix_sort.py +++ b/sorts/msd_radix_sort.py @@ -4,10 +4,10 @@ them. https://en.wikipedia.org/wiki/Radix_sort """ -from typing import List +from __future__ import annotations -def msd_radix_sort(list_of_ints: List[int]) -> List[int]: +def msd_radix_sort(list_of_ints: list[int]) -> list[int]: """ Implementation of the MSD radix sort algorithm. Only works with positive integers @@ -36,7 +36,7 @@ def msd_radix_sort(list_of_ints: List[int]) -> List[int]: return _msd_radix_sort(list_of_ints, most_bits) -def _msd_radix_sort(list_of_ints: List[int], bit_position: int) -> List[int]: +def _msd_radix_sort(list_of_ints: list[int], bit_position: int) -> list[int]: """ Sort the given list based on the bit at bit_position. Numbers with a 0 at that position will be at the start of the list, numbers with a @@ -74,7 +74,7 @@ def _msd_radix_sort(list_of_ints: List[int], bit_position: int) -> List[int]: return res -def msd_radix_sort_inplace(list_of_ints: List[int]): +def msd_radix_sort_inplace(list_of_ints: list[int]): """ Inplace implementation of the MSD radix sort algorithm. Sorts based on the binary representation of the integers. @@ -109,7 +109,7 @@ def msd_radix_sort_inplace(list_of_ints: List[int]): def _msd_radix_sort_inplace( - list_of_ints: List[int], bit_position: int, begin_index: int, end_index: int + list_of_ints: list[int], bit_position: int, begin_index: int, end_index: int ): """ Sort the given list based on the bit at bit_position. Numbers with a diff --git a/sorts/patience_sort.py b/sorts/patience_sort.py index 87f5a4078612..845db517420b 100644 --- a/sorts/patience_sort.py +++ b/sorts/patience_sort.py @@ -1,7 +1,8 @@ +from __future__ import annotations + from bisect import bisect_left from functools import total_ordering from heapq import merge -from typing import List """ A pure Python implementation of the patience sort algorithm @@ -44,7 +45,7 @@ def patience_sort(collection: list) -> list: >>> patience_sort([-3, -17, -48]) [-48, -17, -3] """ - stacks: List[Stack] = [] + stacks: list[Stack] = [] # sort into stacks for element in collection: new_stacks = Stack([element]) @@ -55,7 +56,7 @@ def patience_sort(collection: list) -> list: stacks.append(new_stacks) # use a heap-based merge to merge stack efficiently - collection[:] = merge(*[reversed(stack) for stack in stacks]) + collection[:] = merge(*(reversed(stack) for stack in stacks)) return collection diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index 3d81f0643865..3e6d4c09c46f 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -9,10 +9,10 @@ For manual testing run: python pigeon_sort.py """ -from typing import List +from __future__ import annotations -def pigeon_sort(array: List[int]) -> List[int]: +def pigeon_sort(array: list[int]) -> list[int]: """ Implementation of pigeon hole sort algorithm :param array: Collection of comparable items diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 6f51f6eca7db..b099c78861ba 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -7,7 +7,7 @@ For manual testing run: python3 quick_sort.py """ -from typing import List +from __future__ import annotations def quick_sort(collection: list) -> list: @@ -27,8 +27,8 @@ def quick_sort(collection: list) -> list: if len(collection) < 2: return collection pivot = collection.pop() # Use the last element as the first pivot - greater: List[int] = [] # All elements greater than pivot - lesser: List[int] = [] # All elements less than or equal to pivot + greater: list[int] = [] # All elements greater than pivot + lesser: list[int] = [] # 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) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index b802b5278119..e433bc507a1e 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -9,10 +9,8 @@ """ 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]: """ Examples: >>> radix_sort([0, 5, 3, 2, 2]) @@ -30,7 +28,7 @@ def radix_sort(list_of_ints: List[int]) -> List[int]: max_digit = max(list_of_ints) while placement <= max_digit: # declare and initialize empty buckets - buckets: List[list] = [list() for _ in range(RADIX)] + buckets: list[list] = [list() for _ in range(RADIX)] # split list_of_ints between the buckets for i in list_of_ints: tmp = int((i / placement) % RADIX) diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index 89f88b4a961b..ab2716f8eae5 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -1,11 +1,8 @@ """ A recursive implementation of the insertion sort algorithm """ - from __future__ import annotations -from typing import List - def rec_insertion_sort(collection: list, n: int): """ @@ -72,6 +69,6 @@ def insert_next(collection: list, index: int): if __name__ == "__main__": numbers = input("Enter integers separated by spaces: ") - number_list: List[int] = [int(num) for num in numbers.split()] + number_list: list[int] = [int(num) for num in numbers.split()] rec_insertion_sort(number_list, len(number_list)) print(number_list) diff --git a/sorts/slowsort.py b/sorts/slowsort.py index 53bb14554ee2..a5f4e873ebb2 100644 --- a/sorts/slowsort.py +++ b/sorts/slowsort.py @@ -8,13 +8,10 @@ Source: https://en.wikipedia.org/wiki/Slowsort """ +from __future__ import annotations -from typing import Optional - -def slowsort( - sequence: list, start: Optional[int] = None, end: Optional[int] = None -) -> None: +def slowsort(sequence: list, start: int | None = None, end: int | None = None) -> None: """ Sorts sequence[start..end] (both inclusive) in-place. start defaults to 0 if not given. diff --git a/strings/aho_corasick.py b/strings/aho_corasick.py index 712cb338aa7e..b9a6a80728f6 100644 --- a/strings/aho_corasick.py +++ b/strings/aho_corasick.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from collections import deque -from typing import Dict, List, Union class Automaton: @@ -13,7 +14,7 @@ def __init__(self, keywords: list[str]): self.add_keyword(keyword) self.set_fail_transitions() - def find_next_state(self, current_state: int, char: str) -> Union[int, None]: + def find_next_state(self, current_state: int, char: str) -> int | None: for state in self.adlist[current_state]["next_states"]: if char == self.adlist[state]["value"]: return state @@ -63,7 +64,7 @@ def set_fail_transitions(self) -> None: + self.adlist[self.adlist[child]["fail_state"]]["output"] ) - def search_in(self, string: str) -> Dict[str, List[int]]: + 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 a3e6cf614eab..8d8ff22f67bd 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -17,7 +17,7 @@ n=length of main string m=length of pattern string """ -from typing import List +from __future__ import annotations class BoyerMooreSearch: @@ -59,7 +59,7 @@ def mismatch_in_text(self, currentPos: int) -> int: return currentPos + i return -1 - def bad_character_heuristic(self) -> List[int]: + 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 a205ce37e3e5..a488c171a93b 100644 --- a/strings/knuth_morris_pratt.py +++ b/strings/knuth_morris_pratt.py @@ -1,4 +1,4 @@ -from typing import List +from __future__ import annotations def kmp(pattern: str, text: str) -> bool: @@ -36,7 +36,7 @@ def kmp(pattern: str, text: str) -> bool: return False -def get_failure_array(pattern: str) -> List[int]: +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/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index 0571ac3313a3..afaee5bbe854 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -1,4 +1,6 @@ """Get the site emails from URL.""" +from __future__ import annotations + __author__ = "Muhammad Umer Farooq" __license__ = "MIT" __version__ = "1.0.0" @@ -8,7 +10,6 @@ import re from html.parser import HTMLParser -from typing import Optional from urllib import parse import requests @@ -20,7 +21,7 @@ def __init__(self, domain: str) -> None: self.urls: list[str] = [] self.domain = domain - def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]) -> None: + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: """ This function parse html to take takes url from tags """ diff --git a/web_programming/fetch_github_info.py b/web_programming/fetch_github_info.py index c9198460f211..aa4e1d7b1963 100644 --- a/web_programming/fetch_github_info.py +++ b/web_programming/fetch_github_info.py @@ -17,8 +17,10 @@ #!/usr/bin/env bash export USER_TOKEN="" """ +from __future__ import annotations + import os -from typing import Any, Dict +from typing import Any import requests @@ -31,7 +33,7 @@ USER_TOKEN = os.environ.get("USER_TOKEN", "") -def fetch_github_info(auth_token: str) -> Dict[Any, Any]: +def fetch_github_info(auth_token: str) -> dict[Any, Any]: """ Fetch GitHub info of a user using the requests module """ From 01d58562ccbfbea9d16aca6a876676b603026238 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 18 Sep 2021 22:33:03 +0300 Subject: [PATCH 1554/2908] Fix typos in Project Euler problem 034 solution 1 (#4748) * Fix comment * Fix output --- project_euler/problem_034/sol1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project_euler/problem_034/sol1.py b/project_euler/problem_034/sol1.py index 78b318b76d06..11c84ab96ac6 100644 --- a/project_euler/problem_034/sol1.py +++ b/project_euler/problem_034/sol1.py @@ -11,7 +11,7 @@ def sum_of_digit_factorial(n: int) -> int: """ - Returns the sum of the digits in n + Returns the sum of the factorial of digits in n >>> sum_of_digit_factorial(15) 121 >>> sum_of_digit_factorial(0) @@ -33,4 +33,4 @@ def solution() -> int: if __name__ == "__main__": - print(f"{solution()} = ") + print(f"{solution() = }") From 4761fef1a5a3904167285af8819091018c8e04b1 Mon Sep 17 00:00:00 2001 From: jonabtc <39396756+jonabtc@users.noreply.github.com> Date: Sat, 18 Sep 2021 20:22:47 -0500 Subject: [PATCH 1555/2908] Double factorial iterative (#4760) * Adding the double factorial algorithm * Adding the double factorial algorithm Co-authored-by: Jonathan Ocles --- maths/double_factorial_iterative.py | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 maths/double_factorial_iterative.py diff --git a/maths/double_factorial_iterative.py b/maths/double_factorial_iterative.py new file mode 100644 index 000000000000..b2b58aa04c28 --- /dev/null +++ b/maths/double_factorial_iterative.py @@ -0,0 +1,33 @@ +def double_factorial(num: int) -> int: + """ + Compute double factorial using iterative method. + + To learn about the theory behind this algorithm: + https://en.wikipedia.org/wiki/Double_factorial + + >>> import math + >>> all(double_factorial(i) == math.prod(range(i, 0, -2)) for i in range(20)) + True + >>> double_factorial(0.1) + Traceback (most recent call last): + ... + ValueError: double_factorial() only accepts integral values + >>> double_factorial(-1) + Traceback (most recent call last): + ... + ValueError: double_factorial() not defined for negative values + """ + if not isinstance(num, int): + raise ValueError("double_factorial() only accepts integral values") + if num < 0: + raise ValueError("double_factorial() not defined for negative values") + value = 1 + for i in range(num, 0, -2): + value *= i + return value + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a7b9e28bc34478850ea22e31f1d5a022502e2350 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 21 Sep 2021 14:28:27 +0300 Subject: [PATCH 1556/2908] Improve Project Euler problem 009 solution 1 (#4749) * Improve solution * Uncomment code that has been commented due to slow execution affecting Travis --- project_euler/problem_009/sol1.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/project_euler/problem_009/sol1.py b/project_euler/problem_009/sol1.py index a58ea943e48b..c50dfeecfd22 100644 --- a/project_euler/problem_009/sol1.py +++ b/project_euler/problem_009/sol1.py @@ -25,18 +25,16 @@ def solution() -> int: 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 + >>> solution() + 31875000 """ for a in range(300): - for b in range(400): - for c in range(500): - if a < b < c: + for b in range(a + 1, 400): + for c in range(b + 1, 500): + if (a + b + c) == 1000: if (a ** 2) + (b ** 2) == (c ** 2): - if (a + b + c) == 1000: - return a * b * c + return a * b * c def solution_fast() -> int: @@ -47,9 +45,8 @@ def solution_fast() -> int: 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 + >>> solution_fast() + 31875000 """ for a in range(300): From abc725f12de0ef186db67e2d6bc48161ed894644 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 22 Sep 2021 19:37:18 +0200 Subject: [PATCH 1557/2908] mypy --install-types --non-interactive . (#4530) * mypy --install-types --non-interactive . @dhruvmanila Is this useful/needed given that we do not pin our dependencies? https://mypy-lang.blogspot.com/2021/06/mypy-0910-released.html * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ffc2aa293b0..7c2255275091 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: run: | python -m pip install --upgrade pip setuptools six wheel python -m pip install mypy pytest-cov -r requirements.txt - - run: mypy . + - run: mypy --install-types --non-interactive . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} From dc07a850763d8154e012c9d1be7f8fe78326e8fb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 22 Sep 2021 20:03:11 +0200 Subject: [PATCH 1558/2908] Update and rename check_valid_ip_address.py to is_ip_v4_address_valid.py (#4665) * Update and rename check_valid_ip_address.py to is_ip_v4_address_valid.py New test cases that the algorithm must detect: * [ ] an octet much bigger than 255 * [ ] an octet is negative * [ ] number of octets is less than 4 * [ ] number of octets is greater than 4 * [ ] an octet is a letter * updating DIRECTORY.md * Add two more tests to is_ip_v4_address_valid.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 2 +- maths/check_valid_ip_address.py | 46 --------------------------- maths/is_ip_v4_address_valid.py | 56 +++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 47 deletions(-) delete mode 100644 maths/check_valid_ip_address.py create mode 100644 maths/is_ip_v4_address_valid.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 0c00d5ca7f70..0d44e10ad50a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -424,7 +424,6 @@ * [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) - * [Check Valid Ip Address](https://github.com/TheAlgorithms/Python/blob/master/maths/check_valid_ip_address.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) @@ -454,6 +453,7 @@ * [Greedy Coin Change](https://github.com/TheAlgorithms/Python/blob/master/maths/greedy_coin_change.py) * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) * [Integration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/maths/integration_by_simpson_approx.py) + * [Is Ip V4 Address Valid](https://github.com/TheAlgorithms/Python/blob/master/maths/is_ip_v4_address_valid.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) diff --git a/maths/check_valid_ip_address.py b/maths/check_valid_ip_address.py deleted file mode 100644 index 6e8d35ebc44c..000000000000 --- a/maths/check_valid_ip_address.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Checking valid Ip Address. -A valid IP address must be in the form of A.B.C.D, -where A,B,C and D are numbers from 0-254 -for example: 192.168.23.1, 172.254.254.254 are valid IP address - 192.168.255.0, 255.192.3.121 are Invalid IP address -""" - - -def check_valid_ip(ip: str) -> bool: - """ - print "Valid IP address" If IP is valid. - or - print "Invalid IP address" If IP is Invalid. - - >>> check_valid_ip("192.168.0.23") - True - - >>> check_valid_ip("192.255.15.8") - False - - >>> check_valid_ip("172.100.0.8") - True - - >>> check_valid_ip("254.255.0.255") - False - """ - ip1 = ip.replace(".", " ") - list1 = [int(i) for i in ip1.split() if i.isdigit()] - count = 0 - for i in list1: - if i > 254: - count += 1 - break - if count: - return False - return True - - -if __name__ == "__main__": - ip = input() - output = check_valid_ip(ip) - if output is True: - print(f"{ip} is a Valid IP address") - else: - print(f"{ip} is an Invalid IP address") diff --git a/maths/is_ip_v4_address_valid.py b/maths/is_ip_v4_address_valid.py new file mode 100644 index 000000000000..0ae8e021ead1 --- /dev/null +++ b/maths/is_ip_v4_address_valid.py @@ -0,0 +1,56 @@ +""" +Is IP v4 address valid? +A valid IP address must be four octets in the form of A.B.C.D, +where A,B,C and D are numbers from 0-254 +for example: 192.168.23.1, 172.254.254.254 are valid IP address + 192.168.255.0, 255.192.3.121 are invalid IP address +""" + + +def is_ip_v4_address_valid(ip_v4_address: str) -> bool: + """ + print "Valid IP address" If IP is valid. + or + print "Invalid IP address" If IP is invalid. + + >>> is_ip_v4_address_valid("192.168.0.23") + True + + >>> is_ip_v4_address_valid("192.255.15.8") + False + + >>> is_ip_v4_address_valid("172.100.0.8") + True + + >>> is_ip_v4_address_valid("254.255.0.255") + False + + >>> is_ip_v4_address_valid("1.2.33333333.4") + False + + >>> is_ip_v4_address_valid("1.2.-3.4") + False + + >>> is_ip_v4_address_valid("1.2.3") + False + + >>> is_ip_v4_address_valid("1.2.3.4.5") + False + + >>> is_ip_v4_address_valid("1.2.A.4") + False + + >>> is_ip_v4_address_valid("0.0.0.0") + True + + >>> is_ip_v4_address_valid("1.2.3.") + False + """ + octets = [int(i) for i in ip_v4_address.split(".") if i.isdigit()] + return len(octets) == 4 and all(0 <= int(octet) <= 254 for octet in octets) + + +if __name__ == "__main__": + ip = input().strip() + valid_or_invalid = "valid" if is_ip_v4_address_valid(ip) else "invalid" + print(f"{ip} is a {valid_or_invalid} IP v4 address.") From 15d1cfabb15903c5e9d4d103e2390876efb3f85f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 22 Sep 2021 23:11:51 +0200 Subject: [PATCH 1559/2908] from __future__ import annotations (#4763) * from __future__ import annotations * updating DIRECTORY.md * from __future__ import annotations * from __future__ import annotations * Update xor_cipher.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + ciphers/a1z26.py | 1 + ciphers/enigma_machine2.py | 1 + ciphers/trafid_cipher.py | 1 + ciphers/xor_cipher.py | 27 +++++++-------------------- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 0d44e10ad50a..2e9942b5bf93 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -448,6 +448,7 @@ * [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) + * [Gamma Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/gamma_recursive.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) * [Greedy Coin Change](https://github.com/TheAlgorithms/Python/blob/master/maths/greedy_coin_change.py) diff --git a/ciphers/a1z26.py b/ciphers/a1z26.py index e6684fb1e6fc..0f0eb7c5c083 100644 --- a/ciphers/a1z26.py +++ b/ciphers/a1z26.py @@ -5,6 +5,7 @@ https://www.dcode.fr/letter-number-cipher http://bestcodes.weebly.com/a1z26.html """ +from __future__ import annotations def encode(plain: str) -> list[int]: diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index f4ce5a075f46..9252dd0edbf7 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -14,6 +14,7 @@ Created by TrapinchO """ +from __future__ import annotations RotorPositionT = tuple[int, int, int] RotorSelectionT = tuple[str, str, str] diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py index 1c8ea3024d33..b12ceff72907 100644 --- a/ciphers/trafid_cipher.py +++ b/ciphers/trafid_cipher.py @@ -1,4 +1,5 @@ # https://en.wikipedia.org/wiki/Trifid_cipher +from __future__ import annotations def __encryptPart(messagePart: str, character2Number: dict[str, str]) -> str: diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 12d580e720bc..ca9dfe20f7b6 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -16,6 +16,7 @@ - encrypt_file : boolean - decrypt_file : boolean """ +from __future__ import annotations class XORCipher: @@ -41,17 +42,10 @@ def encrypt(self, content: str, key: int) -> list[str]: key = key or self.__key or 1 - # make sure key can be any size - while key > 255: - key -= 255 - - # This will be returned - ans = [] - - for ch in content: - ans.append(chr(ord(ch) ^ key)) + # make sure key is an appropriate size + key %= 255 - return ans + return [chr(ord(ch) ^ key) for ch in content] def decrypt(self, content: str, key: int) -> list[str]: """ @@ -66,17 +60,10 @@ def decrypt(self, content: str, key: int) -> list[str]: key = key or self.__key or 1 - # make sure key can be any size - while key > 255: - key -= 255 - - # This will be returned - ans = [] - - for ch in content: - ans.append(chr(ord(ch) ^ key)) + # make sure key is an appropriate size + key %= 255 - return ans + return [chr(ord(ch) ^ key) for ch in content] def encrypt_string(self, content: str, key: int = 0) -> str: """ From 66a528b171b433a9cb298fba395180445fe1f3e1 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 23 Sep 2021 21:55:18 +0300 Subject: [PATCH 1560/2908] Improve Project Euler problem 014 solution 2 (#4752) --- project_euler/problem_014/sol2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/project_euler/problem_014/sol2.py b/project_euler/problem_014/sol2.py index 7ed68273bcd7..0a58f8d9a05a 100644 --- a/project_euler/problem_014/sol2.py +++ b/project_euler/problem_014/sol2.py @@ -28,16 +28,16 @@ from __future__ import annotations -def collatz_sequence(n: int) -> list[int]: - """Returns the Collatz sequence for n.""" - sequence = [n] +def collatz_sequence_length(n: int) -> int: + """Returns the Collatz sequence length for n.""" + sequence_length = 1 while n != 1: if n % 2 == 0: n //= 2 else: n = 3 * n + 1 - sequence.append(n) - return sequence + sequence_length += 1 + return sequence_length def solution(n: int = 1000000) -> int: @@ -54,7 +54,7 @@ def solution(n: int = 1000000) -> int: 13255 """ - result = max((len(collatz_sequence(i)), i) for i in range(1, n)) + result = max((collatz_sequence_length(i), i) for i in range(1, n)) return result[1] From 5d02103b273dffb2637264c0fc2136ca4fd41b57 Mon Sep 17 00:00:00 2001 From: Jogendra Singh <58473917+Joe-Sin7h@users.noreply.github.com> Date: Fri, 24 Sep 2021 16:24:38 +0530 Subject: [PATCH 1561/2908] Fixed #4764 (#4779) * Fixed #4764 * Fixes #4764 --- data_structures/disjoint_set/disjoint_set.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data_structures/disjoint_set/disjoint_set.py b/data_structures/disjoint_set/disjoint_set.py index a93b89621c4a..bf5ab415d5e4 100644 --- a/data_structures/disjoint_set/disjoint_set.py +++ b/data_structures/disjoint_set/disjoint_set.py @@ -26,7 +26,10 @@ def union_set(x, y): disjoint set tree will be more flat. """ x, y = find_set(x), find_set(y) - if x.rank > y.rank: + if x == y: + return + + elif x.rank > y.rank: y.parent = x else: x.parent = y From 02bc4bf4171497277354c01387c96e044f2dedfe Mon Sep 17 00:00:00 2001 From: Alexandre De Zotti Date: Wed, 29 Sep 2021 06:42:11 +0100 Subject: [PATCH 1562/2908] Add Julia sets to fractals (#4382) * Added Julia sets drawing * Forgot the .py extension * Update julia_sets.py Added online sources for comparison. Added more examples of fractal Julia sets. Added all type hints. Only show one picture Silented RuntuleWarning's (there's no way of avoiding them and they're not an issue per se) * Added doctest example for "show_results" * Filtering Nan's and infinites * added 1 missing type hint * in iterate_function, convert to dtype=complex64 * RuntimeWarning (fine) filtering * Type hint, test for ignore_warnings function, typo in header * Update julia_sets.py Type of expected output value for iterate function int array -> complex array (throws an error on test) * Update julia_sets.py - More accurate type for tests cases in eval_quadratic_polynomial and iterate_function - added more characters for variables c & z in eval_quadratic_polynomial and eval_exponential to silent bot warnings * Function def formatting Blocked by black * Update julia_sets.py * Update fractals/julia_sets.py Co-authored-by: John Law * Update fractals/julia_sets.py Co-authored-by: John Law * Update fractals/julia_sets.py Co-authored-by: John Law * Update fractals/julia_sets.py Co-authored-by: John Law * Update fractals/julia_sets.py Co-authored-by: John Law * Update fractals/julia_sets.py Co-authored-by: John Law * Update fractals/julia_sets.py Co-authored-by: John Law * added more doctests for eval_exponential * Update fractals/julia_sets.py Co-authored-by: John Law Co-authored-by: John Law --- fractals/julia_sets.py | 219 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 fractals/julia_sets.py diff --git a/fractals/julia_sets.py b/fractals/julia_sets.py new file mode 100644 index 000000000000..0168a0153de1 --- /dev/null +++ b/fractals/julia_sets.py @@ -0,0 +1,219 @@ +"""Author Alexandre De Zotti + +Draws Julia sets of quadratic polynomials and exponential maps. + More specifically, this iterates the function a fixed number of times + then plots whether the absolute value of the last iterate is greater than + a fixed threshold (named "escape radius"). For the exponential map this is not + really an escape radius but rather a convenient way to approximate the Julia + set with bounded orbits. + +The examples presented here are: +- The Cauliflower Julia set, see e.g. +https://en.wikipedia.org/wiki/File:Julia_z2%2B0,25.png +- Other examples from https://en.wikipedia.org/wiki/Julia_set +- An exponential map Julia set, ambiantly homeomorphic to the examples in +http://www.math.univ-toulouse.fr/~cheritat/GalII/galery.html + and +https://ddd.uab.cat/pub/pubmat/02141493v43n1/02141493v43n1p27.pdf + +Remark: Some overflow runtime warnings are suppressed. This is because of the + way the iteration loop is implemented, using numpy's efficient computations. + Overflows and infinites are replaced after each step by a large number. +""" + +import warnings +from typing import Any, Callable + +import numpy +from matplotlib import pyplot + +c_cauliflower = 0.25 + 0.0j +c_polynomial_1 = -0.4 + 0.6j +c_polynomial_2 = -0.1 + 0.651j +c_exponential = -2.0 +nb_iterations = 56 +window_size = 2.0 +nb_pixels = 666 + + +def eval_exponential(c_parameter: complex, z_values: numpy.ndarray) -> numpy.ndarray: + """ + Evaluate $e^z + c$. + >>> eval_exponential(0, 0) + 1.0 + >>> abs(eval_exponential(1, numpy.pi*1.j)) < 1e-15 + True + >>> abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15 + True + """ + return numpy.exp(z_values) + c_parameter + + +def eval_quadratic_polynomial( + c_parameter: complex, z_values: numpy.ndarray +) -> numpy.ndarray: + """ + >>> eval_quadratic_polynomial(0, 2) + 4 + >>> eval_quadratic_polynomial(-1, 1) + 0 + >>> round(eval_quadratic_polynomial(1.j, 0).imag) + 1 + >>> round(eval_quadratic_polynomial(1.j, 0).real) + 0 + """ + return z_values * z_values + c_parameter + + +def prepare_grid(window_size: float, nb_pixels: int) -> numpy.ndarray: + """ + Create a grid of complex values of size nb_pixels*nb_pixels with real and + imaginary parts ranging from -window_size to window_size (inclusive). + Returns a numpy array. + + >>> prepare_grid(1,3) + array([[-1.-1.j, -1.+0.j, -1.+1.j], + [ 0.-1.j, 0.+0.j, 0.+1.j], + [ 1.-1.j, 1.+0.j, 1.+1.j]]) + """ + x = numpy.linspace(-window_size, window_size, nb_pixels) + x = x.reshape((nb_pixels, 1)) + y = numpy.linspace(-window_size, window_size, nb_pixels) + y = y.reshape((1, nb_pixels)) + return x + 1.0j * y + + +def iterate_function( + eval_function: Callable[[Any, numpy.ndarray], numpy.ndarray], + function_params: Any, + nb_iterations: int, + z_0: numpy.ndarray, + infinity: float = None, +) -> numpy.ndarray: + """ + Iterate the function "eval_function" exactly nb_iterations times. + The first argument of the function is a parameter which is contained in + function_params. The variable z_0 is an array that contains the initial + values to iterate from. + This function returns the final iterates. + + >>> iterate_function(eval_quadratic_polynomial, 0, 3, numpy.array([0,1,2])).shape + (3,) + >>> numpy.round(iterate_function(eval_quadratic_polynomial, + ... 0, + ... 3, + ... numpy.array([0,1,2]))[0]) + 0j + >>> numpy.round(iterate_function(eval_quadratic_polynomial, + ... 0, + ... 3, + ... numpy.array([0,1,2]))[1]) + (1+0j) + >>> numpy.round(iterate_function(eval_quadratic_polynomial, + ... 0, + ... 3, + ... numpy.array([0,1,2]))[2]) + (256+0j) + """ + + z_n = z_0.astype("complex64") + for i in range(nb_iterations): + z_n = eval_function(function_params, z_n) + if infinity is not None: + numpy.nan_to_num(z_n, copy=False, nan=infinity) + z_n[abs(z_n) == numpy.inf] = infinity + return z_n + + +def show_results( + function_label: str, + function_params: Any, + escape_radius: float, + z_final: numpy.ndarray, +) -> None: + """ + Plots of whether the absolute value of z_final is greater than + the value of escape_radius. Adds the function_label and function_params to + the title. + + >>> show_results('80', 0, 1, numpy.array([[0,1,.5],[.4,2,1.1],[.2,1,1.3]])) + """ + + abs_z_final = (abs(z_final)).transpose() + abs_z_final[:, :] = abs_z_final[::-1, :] + pyplot.matshow(abs_z_final < escape_radius) + pyplot.title(f"Julia set of ${function_label}$, $c={function_params}$") + pyplot.show() + + +def ignore_overflow_warnings() -> None: + """ + Ignore some overflow and invalid value warnings. + + >>> ignore_overflow_warnings() + """ + warnings.filterwarnings( + "ignore", category=RuntimeWarning, message="overflow encountered in multiply" + ) + warnings.filterwarnings( + "ignore", + category=RuntimeWarning, + message="invalid value encountered in multiply", + ) + warnings.filterwarnings( + "ignore", category=RuntimeWarning, message="overflow encountered in absolute" + ) + warnings.filterwarnings( + "ignore", category=RuntimeWarning, message="overflow encountered in exp" + ) + + +if __name__ == "__main__": + + z_0 = prepare_grid(window_size, nb_pixels) + + ignore_overflow_warnings() # See file header for explanations + + nb_iterations = 24 + escape_radius = 2 * abs(c_cauliflower) + 1 + z_final = iterate_function( + eval_quadratic_polynomial, + c_cauliflower, + nb_iterations, + z_0, + infinity=1.1 * escape_radius, + ) + show_results("z^2+c", c_cauliflower, escape_radius, z_final) + + nb_iterations = 64 + escape_radius = 2 * abs(c_polynomial_1) + 1 + z_final = iterate_function( + eval_quadratic_polynomial, + c_polynomial_1, + nb_iterations, + z_0, + infinity=1.1 * escape_radius, + ) + show_results("z^2+c", c_polynomial_1, escape_radius, z_final) + + nb_iterations = 161 + escape_radius = 2 * abs(c_polynomial_2) + 1 + z_final = iterate_function( + eval_quadratic_polynomial, + c_polynomial_2, + nb_iterations, + z_0, + infinity=1.1 * escape_radius, + ) + show_results("z^2+c", c_polynomial_2, escape_radius, z_final) + + nb_iterations = 12 + escape_radius = 10000.0 + z_final = iterate_function( + eval_exponential, + c_exponential, + nb_iterations, + z_0 + 2, + infinity=1.0e10, + ) + show_results("e^z+c", c_exponential, escape_radius, z_final) From b9f18152b74e7a1b0b60c6c1781580e6228f4ba4 Mon Sep 17 00:00:00 2001 From: "Arghya Sarkar (ASRA)" <67339217+sarkarghya@users.noreply.github.com> Date: Wed, 29 Sep 2021 22:19:42 +0530 Subject: [PATCH 1563/2908] Create check_polygon.py (#4605) * Create check_polygon.py * Update check_polygon.py * Update maths/check_polygon.py * Update check_polygon.py * Update check_polygon.py Co-authored-by: John Law --- maths/check_polygon.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 maths/check_polygon.py diff --git a/maths/check_polygon.py b/maths/check_polygon.py new file mode 100644 index 000000000000..0e771197331f --- /dev/null +++ b/maths/check_polygon.py @@ -0,0 +1,31 @@ +from typing import List + + +def check_polygon(nums: List) -> bool: + """ + Takes list of possible side lengths and determines whether a + two-dimensional polygon with such side lengths can exist. + + Returns a boolean value for the < comparison + of the largest side length with sum of the rest. + Wiki: https://en.wikipedia.org/wiki/Triangle_inequality + + >>> check_polygon([6, 10, 5]) + True + >>> check_polygon([3, 7, 13, 2]) + False + >>> check_polygon([]) + Traceback (most recent call last): + ... + ValueError: List is invalid + """ + if not nums: + raise ValueError("List is invalid") + nums.sort() + return nums.pop() < sum(nums) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d1e70cfa3a56914f03a1be7d92447197b9a6a5dc Mon Sep 17 00:00:00 2001 From: ss1208 <87578327+ss1208@users.noreply.github.com> Date: Wed, 29 Sep 2021 23:34:35 +0530 Subject: [PATCH 1564/2908] docs: renovate README (#4620) Conjunctive adverbs should be followed by a comma. For more details, kindly refer: https://www.aje.com/arc/editing-tip-commas-conjunctive-adverbs/ Separate out the labels into two rows Co-authored-by: John Law Co-authored-by: Dhruv Manilawala --- README.md | 77 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 1e85ed0daa7c..0298d46020ac 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,55 @@ -# 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)  -[![Discord chat](https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA&style=flat-square)](https://discord.gg/c7MnfGFGa6)  -[![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  -[![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)  - - -### All algorithms implemented in Python (for education) - -These implementations are for learning purposes only. Therefore they may be less efficient than the implementations in the Python standard library. - -## Contribution Guidelines - -Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. - -## Community Channel - -We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. + + +Implementations are for learning purposes only. As they may be less efficient than the implementations in the Python standard library, use them at your discretion. + +## Getting Started + +Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. + +## Community Channels + +We're on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms)! Community channels are great for you to ask questions and get help. Please join us! ## List of Algorithms -See our [directory](DIRECTORY.md). +See our [directory](DIRECTORY.md) for easier navigation and better overview of the project. From 6341f351aab0ff510fcf1d9ce135be680763a971 Mon Sep 17 00:00:00 2001 From: DukicDev Date: Fri, 1 Oct 2021 23:48:47 +0200 Subject: [PATCH 1565/2908] Fix comments in backtracking/coloring.py (#4857) --- backtracking/coloring.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backtracking/coloring.py b/backtracking/coloring.py index 8bda4b5871df..9d539de8a3c4 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -1,7 +1,7 @@ """ 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 + consists of coloring a given graph with at most m colors + such that no adjacent vertices are assigned the same color Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring """ @@ -11,9 +11,9 @@ def valid_coloring( neighbours: list[int], colored_vertices: list[int], color: int ) -> bool: """ - For each neighbour check if coloring constraint is satisfied + For each neighbour check if the coloring constraint is satisfied If any of the neighbours fail the constraint return False - If all neighbours validate constraint return True + If all neighbours validate the constraint return True >>> neighbours = [0,1,0,1,0] >>> colored_vertices = [0, 2, 1, 2, 0] @@ -41,14 +41,14 @@ def util_color( Base Case: 1. Check if coloring is complete - 1.1 If complete return True (meaning that we successfully colored graph) + 1.1 If complete return True (meaning that we successfully colored the graph) Recursive Step: - 2. Itterates over each color: - Check if current coloring is valid: + 2. Iterates over each color: + Check if the 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.2. Do recursive call, check if this coloring leads to a solution + 2.4. if current coloring leads to a solution return 2.5. Uncolor given vertex >>> graph = [[0, 1, 0, 0, 0], From 31b34af9fa7e09b4832af36071be86df37959e0d Mon Sep 17 00:00:00 2001 From: DukicDev Date: Sat, 2 Oct 2021 15:37:28 +0200 Subject: [PATCH 1566/2908] Correct grammar in backtracking/n_queens_math.py (#4869) --- backtracking/n_queens_math.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py index c12aa6c3387d..2de784ded06b 100644 --- a/backtracking/n_queens_math.py +++ b/backtracking/n_queens_math.py @@ -1,7 +1,7 @@ r""" Problem: -The n queens problem is of placing N queens on a N * N chess board such that no queen +The n queens problem is: 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. @@ -31,7 +31,7 @@ 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 +At this point we have it 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: @@ -47,7 +47,7 @@ See:: https://www.enotes.com/homework-help/write-equation-line-that-hits-origin-45-degree-1474860 -Then we have this another formula: +Then we have this other formula: Slope intercept: @@ -59,7 +59,7 @@ y - mx = b -And like we already have the m values for the angles 45º and 135º, this formula would +And since we already have the m values for the angles 45º and 135º, this formula would look like this: 45º: y - (1)x = b @@ -71,7 +71,7 @@ y = row x = column -Applying this two formulas we can check if a queen in some position is being attacked +Applying these two formulas we can check if a queen in some position is being attacked for another one or vice versa. """ From c873fa0b1bab5dcfeffb386dddd6a755ef2aa0f9 Mon Sep 17 00:00:00 2001 From: DukicDev Date: Sat, 2 Oct 2021 15:51:53 +0200 Subject: [PATCH 1567/2908] Correct grammar of comment in backtracking/hamiltonian_cycle.py (#4868) --- backtracking/hamiltonian_cycle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index 19751b347320..500e993e5c8b 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -15,8 +15,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 succeed we return True, saying that it is possible to connect + this vertices, otherwise we return False Case 1:Use exact graph as in main function, with initialized values >>> graph = [[0, 1, 0, 1, 0], From d530d2bcf42391a8172c720e8d7c7d354a748abf Mon Sep 17 00:00:00 2001 From: Nolan Emirot Date: Sun, 3 Oct 2021 20:33:42 -0700 Subject: [PATCH 1568/2908] fix: comment in patience sort (#4972) --- sorts/patience_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/patience_sort.py b/sorts/patience_sort.py index 845db517420b..63c2c8ffe99c 100644 --- a/sorts/patience_sort.py +++ b/sorts/patience_sort.py @@ -29,7 +29,7 @@ def __eq__(self, other): def patience_sort(collection: list) -> list: - """A pure implementation of quick sort algorithm in Python + """A pure implementation of patience sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous comparable items inside From 90db98304e06a60a3c578e9f55fe139524a4c3f8 Mon Sep 17 00:00:00 2001 From: Sarvesh Kumar Dwivedi Date: Mon, 4 Oct 2021 09:37:58 +0530 Subject: [PATCH 1569/2908] Fix word typos in comments (#4928) * fixed: spelling nonegative -> non-negative * fixed: spelling transpostiion -> transposition * fixed: spelling topolical -> topological * fixed: spelling sufix -> suffix --- sorts/bead_sort.py | 2 +- sorts/odd_even_transposition_single_threaded.py | 2 +- sorts/topological_sort.py | 2 +- strings/prefix_function.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sorts/bead_sort.py b/sorts/bead_sort.py index 3767e842d8c2..26a3fabc4807 100644 --- a/sorts/bead_sort.py +++ b/sorts/bead_sort.py @@ -1,5 +1,5 @@ """ -Bead sort only works for sequences of nonegative integers. +Bead sort only works for sequences of non-negative integers. https://en.wikipedia.org/wiki/Bead_sort """ diff --git a/sorts/odd_even_transposition_single_threaded.py b/sorts/odd_even_transposition_single_threaded.py index fe06459e8dd1..f6cf7fba2a71 100644 --- a/sorts/odd_even_transposition_single_threaded.py +++ b/sorts/odd_even_transposition_single_threaded.py @@ -1,7 +1,7 @@ """ Source: https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort -This is a non-parallelized implementation of odd-even transpostiion sort. +This is a non-parallelized implementation of odd-even transposition sort. Normally the swaps in each set happen simultaneously, without that the algorithm is no better than bubble sort. diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py index e7a52f7c7714..59a0c8571b53 100644 --- a/sorts/topological_sort.py +++ b/sorts/topological_sort.py @@ -10,7 +10,7 @@ def topological_sort(start, visited, sort): - """Perform topolical sort on a directed acyclic graph.""" + """Perform topological sort on a directed acyclic graph.""" current = start # add current to visited visited.append(current) diff --git a/strings/prefix_function.py b/strings/prefix_function.py index 9e6dbbf5408f..6eca01635fe3 100644 --- a/strings/prefix_function.py +++ b/strings/prefix_function.py @@ -14,7 +14,7 @@ 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 + which represents the longest coincidence of prefix and suffix for given substring (input_str[0...i]) For the value of the first element the algorithm always returns 0 @@ -45,7 +45,7 @@ def prefix_function(input_string: str) -> list: def longest_prefix(input_str: str) -> int: """ Prefix-function use case - Finding longest prefix which is sufix as well + Finding longest prefix which is suffix as well >>> longest_prefix("aabcdaabc") 4 From a4d68d69f17f08f71ec81e5c5b681e1213ec8f7d Mon Sep 17 00:00:00 2001 From: Lewis Tian Date: Wed, 6 Oct 2021 22:06:49 +0800 Subject: [PATCH 1570/2908] bugfix: Add abs_max.py & abs_min.py empty list detection (#4844) * bugfix: Add abs_max.py & abs_min.py empty list detection * fix shebangs check --- maths/abs_max.py | 17 ++++++++++++++++- maths/abs_min.py | 19 +++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/maths/abs_max.py b/maths/abs_max.py index e5a8219657ac..4a4b4d9ebca3 100644 --- a/maths/abs_max.py +++ b/maths/abs_max.py @@ -7,7 +7,13 @@ def abs_max(x: list[int]) -> int: 11 >>> abs_max([3,-10,-2]) -10 + >>> abs_max([]) + Traceback (most recent call last): + ... + ValueError: abs_max() arg is an empty sequence """ + if len(x) == 0: + raise ValueError("abs_max() arg is an empty sequence") j = x[0] for i in x: if abs(i) > abs(j): @@ -15,13 +21,19 @@ def abs_max(x: list[int]) -> int: return j -def abs_max_sort(x): +def abs_max_sort(x: list[int]) -> int: """ >>> abs_max_sort([0,5,1,11]) 11 >>> abs_max_sort([3,-10,-2]) -10 + >>> abs_max_sort([]) + Traceback (most recent call last): + ... + ValueError: abs_max_sort() arg is an empty sequence """ + if len(x) == 0: + raise ValueError("abs_max_sort() arg is an empty sequence") return sorted(x, key=abs)[-1] @@ -32,4 +44,7 @@ def main(): if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) main() diff --git a/maths/abs_min.py b/maths/abs_min.py index eb84de37ce23..00dbcb025cfb 100644 --- a/maths/abs_min.py +++ b/maths/abs_min.py @@ -1,13 +1,21 @@ +from __future__ import annotations + from .abs import abs_val -def absMin(x): +def abs_min(x: list[int]) -> int: """ - >>> absMin([0,5,1,11]) + >>> abs_min([0,5,1,11]) 0 - >>> absMin([3,-10,-2]) + >>> abs_min([3,-10,-2]) -2 + >>> abs_min([]) + Traceback (most recent call last): + ... + ValueError: abs_min() arg is an empty sequence """ + if len(x) == 0: + raise ValueError("abs_min() arg is an empty sequence") j = x[0] for i in x: if abs_val(i) < abs_val(j): @@ -17,8 +25,11 @@ def absMin(x): def main(): a = [-3, -1, 2, -11] - print(absMin(a)) # = -1 + print(abs_min(a)) # = -1 if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) main() From 629369a34fdcaee70392d1fcd8cbcae5418c350b Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 6 Oct 2021 17:11:15 +0300 Subject: [PATCH 1571/2908] Improve Project Euler problem 203 solution 1 (#4807) --- project_euler/problem_203/sol1.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py index 030cf12f2a85..fe4d14b20c92 100644 --- a/project_euler/problem_203/sol1.py +++ b/project_euler/problem_203/sol1.py @@ -75,17 +75,15 @@ def get_primes_squared(max_number: int) -> list[int]: >>> get_primes_squared(100) [4, 9, 25, 49] """ - max_prime = round(math.sqrt(max_number)) - non_primes = set() + max_prime = math.isqrt(max_number) + non_primes = [False] * (max_prime + 1) primes = [] for num in range(2, max_prime + 1): - if num in non_primes: + if non_primes[num]: continue - counter = 2 - while num * counter <= max_prime: - non_primes.add(num * counter) - counter += 1 + for num_counter in range(num ** 2, max_prime + 1, num): + non_primes[num_counter] = True primes.append(num ** 2) return primes From d654806eae5dc6027911424ea828e566a64641fd Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 6 Oct 2021 17:11:50 +0300 Subject: [PATCH 1572/2908] Improve Project Euler problem 112 solution 1 (#4808) --- project_euler/problem_112/sol1.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/project_euler/problem_112/sol1.py b/project_euler/problem_112/sol1.py index d8cb334c9508..b3ea6b35654a 100644 --- a/project_euler/problem_112/sol1.py +++ b/project_euler/problem_112/sol1.py @@ -47,7 +47,9 @@ def check_bouncy(n: int) -> bool: """ 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) + str_n = str(n) + sorted_str_n = "".join(sorted(str_n)) + return sorted_str_n != str_n and sorted_str_n[::-1] != str_n def solution(percent: float = 99) -> int: From d324f91fe75cc859335ee1f7c9c6307d958d0558 Mon Sep 17 00:00:00 2001 From: Parth Satodiya Date: Thu, 7 Oct 2021 20:48:23 +0530 Subject: [PATCH 1573/2908] Fix mypy errors for data_structures->linked_list directory files (#4927) --- data_structures/linked_list/__init__.py | 11 +++++++---- .../linked_list/circular_linked_list.py | 14 +++++++------- data_structures/linked_list/has_loop.py | 6 +++--- .../linked_list/middle_element_of_linked_list.py | 8 ++++++-- data_structures/linked_list/skip_list.py | 6 +++--- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index a5f5537b1d96..8ae171d71035 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -6,7 +6,7 @@ - Last node: points to null """ -from typing import Any +from typing import Any, Optional class Node: @@ -17,7 +17,7 @@ def __init__(self, item: Any, next: Any) -> None: class LinkedList: def __init__(self) -> None: - self.head = None + self.head: Optional[Node] = None self.size = 0 def add(self, item: Any) -> None: @@ -25,7 +25,10 @@ def add(self, item: Any) -> None: self.size += 1 def remove(self) -> Any: - if self.is_empty(): + # Switched 'self.is_empty()' to 'self.head is None' + # because mypy was considering the possibility that 'self.head' + # can be None in below else part and giving error + if self.head is None: return None else: item = self.head.item @@ -50,7 +53,7 @@ def __str__(self) -> str: else: iterate = self.head item_str = "" - item_list = [] + item_list: list[str] = [] while iterate: item_list.append(str(iterate.item)) iterate = iterate.next diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index f67c1e8f2cf7..42794ba793a7 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -1,10 +1,10 @@ -from typing import Any +from typing import Any, Iterator, Optional class Node: def __init__(self, data: Any): - self.data = data - self.next = None + self.data: Any = data + self.next: Optional[Node] = None class CircularLinkedList: @@ -12,7 +12,7 @@ def __init__(self): self.head = None self.tail = None - def __iter__(self): + def __iter__(self) -> Iterator[Any]: node = self.head while self.head: yield node.data @@ -54,10 +54,10 @@ def insert_nth(self, index: int, data: Any) -> None: def delete_front(self): return self.delete_nth(0) - def delete_tail(self) -> None: + def delete_tail(self) -> Any: return self.delete_nth(len(self) - 1) - def delete_nth(self, index: int = 0): + def delete_nth(self, index: int = 0) -> Any: if not 0 <= index < len(self): raise IndexError("list index out of range.") delete_node = self.head @@ -76,7 +76,7 @@ def delete_nth(self, index: int = 0): self.tail = temp return delete_node.data - def is_empty(self): + def is_empty(self) -> bool: return len(self) == 0 diff --git a/data_structures/linked_list/has_loop.py b/data_structures/linked_list/has_loop.py index 405ece7e27c8..a155ab4c7c89 100644 --- a/data_structures/linked_list/has_loop.py +++ b/data_structures/linked_list/has_loop.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Optional class ContainsLoopError(Exception): @@ -7,8 +7,8 @@ class ContainsLoopError(Exception): class Node: def __init__(self, data: Any) -> None: - self.data = data - self.next_node = None + self.data: Any = data + self.next_node: Optional[Node] = None def __iter__(self): node = self 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 185c4ccbbb0a..296696897715 100644 --- a/data_structures/linked_list/middle_element_of_linked_list.py +++ b/data_structures/linked_list/middle_element_of_linked_list.py @@ -1,5 +1,8 @@ +from typing import Optional + + class Node: - def __init__(self, data: int) -> int: + def __init__(self, data: int) -> None: self.data = data self.next = None @@ -14,7 +17,7 @@ def push(self, new_data: int) -> int: self.head = new_node return self.head.data - def middle_element(self) -> int: + def middle_element(self) -> Optional[int]: """ >>> link = LinkedList() >>> link.middle_element() @@ -54,6 +57,7 @@ def middle_element(self) -> int: return slow_pointer.data else: print("No element found.") + return None if __name__ == "__main__": diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index ee0b4460730c..be30592ec77d 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -5,14 +5,14 @@ from __future__ import annotations from random import random -from typing import Generic, TypeVar +from typing import Generic, Optional, TypeVar, Union KT = TypeVar("KT") VT = TypeVar("VT") class Node(Generic[KT, VT]): - def __init__(self, key: KT, value: VT): + def __init__(self, key: Union[KT, str] = "root", value: Optional[VT] = None): self.key = key self.value = value self.forward: list[Node[KT, VT]] = [] @@ -49,7 +49,7 @@ def level(self) -> int: class SkipList(Generic[KT, VT]): def __init__(self, p: float = 0.5, max_level: int = 16): - self.head = Node("root", None) + self.head: Node[KT, VT] = Node[KT, VT]() self.level = 0 self.p = p self.max_level = max_level From 77b243e62b09cdf6201916af6762b03c54d8f77a Mon Sep 17 00:00:00 2001 From: Lewis Tian Date: Thu, 7 Oct 2021 23:20:32 +0800 Subject: [PATCH 1574/2908] bugfix: Add empty list detection for find_max/min (#4881) * bugfix: Add empty list detection for find_max/min * fix shebangs check --- maths/find_max.py | 20 ++++++++++++------- maths/find_max_recursion.py | 39 ++++++++++++++++++++++++++++++++++--- maths/find_min.py | 21 ++++++++++++++------ maths/find_min_recursion.py | 39 ++++++++++++++++++++++++++++++++++--- 4 files changed, 100 insertions(+), 19 deletions(-) diff --git a/maths/find_max.py b/maths/find_max.py index 4d92e37eb2e1..684fbe8161e8 100644 --- a/maths/find_max.py +++ b/maths/find_max.py @@ -1,7 +1,7 @@ -# NguyenU +from __future__ import annotations -def find_max(nums): +def find_max(nums: list[int | float]) -> int | float: """ >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): ... find_max(nums) == max(nums) @@ -9,7 +9,15 @@ def find_max(nums): True True True + >>> find_max([2, 4, 9, 7, 19, 94, 5]) + 94 + >>> find_max([]) + Traceback (most recent call last): + ... + ValueError: find_max() arg is an empty sequence """ + if len(nums) == 0: + raise ValueError("find_max() arg is an empty sequence") max_num = nums[0] for x in nums: if x > max_num: @@ -17,9 +25,7 @@ def find_max(nums): return max_num -def main(): - print(find_max([2, 4, 9, 7, 19, 94, 5])) # 94 - - if __name__ == "__main__": - main() + import doctest + + doctest.testmod(verbose=True) diff --git a/maths/find_max_recursion.py b/maths/find_max_recursion.py index 03fb81950dcb..629932e0818f 100644 --- a/maths/find_max_recursion.py +++ b/maths/find_max_recursion.py @@ -1,5 +1,8 @@ +from __future__ import annotations + + # Divide and Conquer algorithm -def find_max(nums, left, right): +def find_max(nums: list[int | float], left: int, right: int) -> int | float: """ find max value in list :param nums: contains elements @@ -7,10 +10,39 @@ def find_max(nums, left, right): :param right: index of last element :return: max in nums + >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): + ... find_max(nums, 0, len(nums) - 1) == max(nums) + True + True + True + True >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] >>> find_max(nums, 0, len(nums) - 1) == max(nums) True + >>> find_max([], 0, 0) + Traceback (most recent call last): + ... + ValueError: find_max() arg is an empty sequence + >>> find_max(nums, 0, len(nums)) == max(nums) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> find_max(nums, -len(nums), -1) == max(nums) + True + >>> find_max(nums, -len(nums) - 1, -1) == max(nums) + Traceback (most recent call last): + ... + IndexError: list index out of range """ + if len(nums) == 0: + raise ValueError("find_max() arg is an empty sequence") + if ( + left >= len(nums) + or left < -len(nums) + or right >= len(nums) + or right < -len(nums) + ): + raise IndexError("list index out of range") if left == right: return nums[left] mid = (left + right) >> 1 # the middle @@ -21,5 +53,6 @@ def find_max(nums, left, right): if __name__ == "__main__": - nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] - assert find_max(nums, 0, len(nums) - 1) == 10 + import doctest + + doctest.testmod(verbose=True) diff --git a/maths/find_min.py b/maths/find_min.py index 2af2e44ba353..228205ed7feb 100644 --- a/maths/find_min.py +++ b/maths/find_min.py @@ -1,4 +1,7 @@ -def find_min(nums): +from __future__ import annotations + + +def find_min(nums: list[int | float]) -> int | float: """ Find Minimum Number in a List :param nums: contains elements @@ -10,7 +13,15 @@ def find_min(nums): True True True + >>> find_min([0, 1, 2, 3, 4, 5, -3, 24, -56]) + -56 + >>> find_min([]) + Traceback (most recent call last): + ... + ValueError: find_min() arg is an empty sequence """ + if len(nums) == 0: + raise ValueError("find_min() arg is an empty sequence") min_num = nums[0] for num in nums: if min_num > num: @@ -18,9 +29,7 @@ def find_min(nums): return min_num -def main(): - assert find_min([0, 1, 2, 3, 4, 5, -3, 24, -56]) == -56 - - if __name__ == "__main__": - main() + import doctest + + doctest.testmod(verbose=True) diff --git a/maths/find_min_recursion.py b/maths/find_min_recursion.py index 4488967cc57a..4d11015efcd5 100644 --- a/maths/find_min_recursion.py +++ b/maths/find_min_recursion.py @@ -1,5 +1,8 @@ +from __future__ import annotations + + # Divide and Conquer algorithm -def find_min(nums, left, right): +def find_min(nums: list[int | float], left: int, right: int) -> int | float: """ find min value in list :param nums: contains elements @@ -7,10 +10,39 @@ def find_min(nums, left, right): :param right: index of last element :return: min in nums + >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): + ... find_min(nums, 0, len(nums) - 1) == min(nums) + True + True + True + True >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] >>> find_min(nums, 0, len(nums) - 1) == min(nums) True + >>> find_min([], 0, 0) + Traceback (most recent call last): + ... + ValueError: find_min() arg is an empty sequence + >>> find_min(nums, 0, len(nums)) == min(nums) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> find_min(nums, -len(nums), -1) == min(nums) + True + >>> find_min(nums, -len(nums) - 1, -1) == min(nums) + Traceback (most recent call last): + ... + IndexError: list index out of range """ + if len(nums) == 0: + raise ValueError("find_min() arg is an empty sequence") + if ( + left >= len(nums) + or left < -len(nums) + or right >= len(nums) + or right < -len(nums) + ): + raise IndexError("list index out of range") if left == right: return nums[left] mid = (left + right) >> 1 # the middle @@ -21,5 +53,6 @@ def find_min(nums, left, right): if __name__ == "__main__": - nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] - assert find_min(nums, 0, len(nums) - 1) == 1 + import doctest + + doctest.testmod(verbose=True) From 7578e0b920831c0ee3274cbfdaa6dc2b6a82cc97 Mon Sep 17 00:00:00 2001 From: Rohanrbharadwaj <89947037+Rohanrbharadwaj@users.noreply.github.com> Date: Sun, 10 Oct 2021 23:22:38 +0530 Subject: [PATCH 1575/2908] Used in-built method (#5183) * Used in-built method * Delete swap_case.py Co-authored-by: Christian Clauss --- strings/swap_case.py | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 strings/swap_case.py diff --git a/strings/swap_case.py b/strings/swap_case.py deleted file mode 100644 index 107fda4b52ec..000000000000 --- a/strings/swap_case.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -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 - -""" - - -def swap_case(sentence: str) -> str: - """ - 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() - elif char.islower(): - new_string += char.upper() - else: - new_string += char - - return new_string - - -if __name__ == "__main__": - print(swap_case(input("Please input sentence: "))) From 97562c19f8f1f079714b393b1b2afa895e930916 Mon Sep 17 00:00:00 2001 From: Rohanrbharadwaj <89947037+Rohanrbharadwaj@users.noreply.github.com> Date: Sun, 10 Oct 2021 23:30:04 +0530 Subject: [PATCH 1576/2908] Added doctest (#5182) --- strings/indian_phone_validator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/strings/indian_phone_validator.py b/strings/indian_phone_validator.py index d544e92661b1..7f3fda5db949 100644 --- a/strings/indian_phone_validator.py +++ b/strings/indian_phone_validator.py @@ -16,6 +16,8 @@ def indian_phone_validator(phone: str) -> bool: True >>> indian_phone_validator("+91-1234567899") False + >>> indian_phone_validator("+91-9876543218") + True """ pat = re.compile(r"^(\+91[\-\s]?)?[0]?(91)?[789]\d{9}$") match = re.search(pat, phone) From 729b4d875a07bd15bc6e5e8a3c79f16fa1e003e6 Mon Sep 17 00:00:00 2001 From: Jordan Rinder Date: Sun, 10 Oct 2021 14:02:44 -0400 Subject: [PATCH 1577/2908] Add Sylvester's sequence to maths (#5171) * Add Sylvester's sequence to maths * Update sylvester_sequence.py Co-authored-by: Christian Clauss --- maths/sylvester_sequence.py | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 maths/sylvester_sequence.py diff --git a/maths/sylvester_sequence.py b/maths/sylvester_sequence.py new file mode 100644 index 000000000000..0cd99affe046 --- /dev/null +++ b/maths/sylvester_sequence.py @@ -0,0 +1,43 @@ +""" + +Calculates the nth number in Sylvester's sequence + +Source: + https://en.wikipedia.org/wiki/Sylvester%27s_sequence + +""" + + +def sylvester(number: int) -> int: + """ + :param number: nth number to calculate in the sequence + :return: the nth number in Sylvester's sequence + + >>> sylvester(8) + 113423713055421844361000443 + + >>> sylvester(-1) + Traceback (most recent call last): + ... + ValueError: The input value of [n=-1] has to be > 0 + + >>> sylvester(8.0) + Traceback (most recent call last): + ... + AssertionError: The input value of [n=8.0] is not an integer + """ + assert isinstance(number, int), f"The input value of [n={number}] is not an integer" + + if number == 1: + return 2 + elif number < 1: + raise ValueError(f"The input value of [n={number}] has to be > 0") + else: + num = sylvester(number - 1) + lower = num - 1 + upper = num + return lower * upper + 1 + + +if __name__ == "__main__": + print(f"The 8th number in Sylvester's sequence: {sylvester(8)}") From fadb97609f7b84f83a47fcf8a253145562469b23 Mon Sep 17 00:00:00 2001 From: Sidhaant Thakker <59668364+SidhaantThakker@users.noreply.github.com> Date: Mon, 11 Oct 2021 21:59:52 +0530 Subject: [PATCH 1578/2908] Add carrier concentrations calculation algorithm (#4791) * added carrier concentrations algorithm * Add more references Added more references to the carrier concentrations file * Update electronics/carrier_concentration.py Co-authored-by: John Law * Update electronics/carrier_concentration.py Co-authored-by: John Law Co-authored-by: John Law --- electronics/carrier_concentration.py | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 electronics/carrier_concentration.py diff --git a/electronics/carrier_concentration.py b/electronics/carrier_concentration.py new file mode 100644 index 000000000000..87bcad8df398 --- /dev/null +++ b/electronics/carrier_concentration.py @@ -0,0 +1,75 @@ +# https://en.wikipedia.org/wiki/Charge_carrier_density +# https://www.pveducation.org/pvcdrom/pn-junctions/equilibrium-carrier-concentration +# http://www.ece.utep.edu/courses/ee3329/ee3329/Studyguide/ToC/Fundamentals/Carriers/concentrations.html + +from __future__ import annotations + + +def carrier_concentration( + electron_conc: float, + hole_conc: float, + intrinsic_conc: float, +) -> tuple: + """ + This function can calculate any one of the three - + 1. Electron Concentration + 2, Hole Concentration + 3. Intrinsic Concentration + given the other two. + Examples - + >>> carrier_concentration(electron_conc=25, hole_conc=100, intrinsic_conc=0) + ('intrinsic_conc', 50.0) + >>> carrier_concentration(electron_conc=0, hole_conc=1600, intrinsic_conc=200) + ('electron_conc', 25.0) + >>> carrier_concentration(electron_conc=1000, hole_conc=0, intrinsic_conc=1200) + ('hole_conc', 1440.0) + >>> carrier_concentration(electron_conc=1000, hole_conc=400, intrinsic_conc=1200) + Traceback (most recent call last): + File "", line 37, in + ValueError: You cannot supply more or less than 2 values + >>> carrier_concentration(electron_conc=-1000, hole_conc=0, intrinsic_conc=1200) + Traceback (most recent call last): + File "", line 40, in + ValueError: Electron concentration cannot be negative in a semiconductor + >>> carrier_concentration(electron_conc=0, hole_conc=-400, intrinsic_conc=1200) + Traceback (most recent call last): + File "", line 44, in + ValueError: Hole concentration cannot be negative in a semiconductor + >>> carrier_concentration(electron_conc=0, hole_conc=400, intrinsic_conc=-1200) + Traceback (most recent call last): + File "", line 48, in + ValueError: Intrinsic concentration cannot be negative in a semiconductor + """ + if (electron_conc, hole_conc, intrinsic_conc).count(0) != 1: + raise ValueError("You cannot supply more or less than 2 values") + elif electron_conc < 0: + raise ValueError("Electron concentration cannot be negative in a semiconductor") + elif hole_conc < 0: + raise ValueError("Hole concentration cannot be negative in a semiconductor") + elif intrinsic_conc < 0: + raise ValueError( + "Intrinsic concentration cannot be negative in a semiconductor" + ) + elif electron_conc == 0: + return ( + "electron_conc", + intrinsic_conc ** 2 / hole_conc, + ) + elif hole_conc == 0: + return ( + "hole_conc", + intrinsic_conc ** 2 / electron_conc, + ) + elif intrinsic_conc == 0: + return ( + "intrinsic_conc", + (electron_conc * hole_conc) ** 0.5, + ) + else: + return (-1, -1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From e311b02e704891b2f31a1e2c8fe2df77a032b09b Mon Sep 17 00:00:00 2001 From: Muhammad Hammad Sani <58339378+mhammadsaani@users.noreply.github.com> Date: Mon, 11 Oct 2021 21:33:06 +0500 Subject: [PATCH 1579/2908] Remove unnecessary branch (#4824) * Algorithm Optimized * Update divide_and_conquer/inversions.py Co-authored-by: John Law * Update divide_and_conquer/inversions.py Co-authored-by: John Law * Update divide_and_conquer/inversions.py Co-authored-by: John Law Co-authored-by: John Law --- divide_and_conquer/inversions.py | 36 +++++++++----------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py index 9bb656229321..b471456025be 100644 --- a/divide_and_conquer/inversions.py +++ b/divide_and_conquer/inversions.py @@ -2,31 +2,25 @@ 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 - +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]) @@ -49,20 +43,16 @@ def count_inversions_bf(arr): 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]) @@ -72,40 +62,34 @@ def count_inversions_recursive(arr): """ if len(arr) <= 1: return arr, 0 - else: - mid = len(arr) // 2 - P = arr[0:mid] - Q = arr[mid:] + 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) + 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 + 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]) From bcfca67faa120cc4cb42775af302d6d52d3a3f1e Mon Sep 17 00:00:00 2001 From: Joyce Date: Tue, 12 Oct 2021 00:33:44 +0800 Subject: [PATCH 1580/2908] [mypy] fix type annotations for all Project Euler problems (#4747) * [mypy] fix type annotations for problem003/sol1 and problem003/sol3 * [mypy] fix type annotations for project euler problem007/sol2 * [mypy] fix type annotations for project euler problem008/sol2 * [mypy] fix type annotations for project euler problem009/sol1 * [mypy] fix type annotations for project euler problem014/sol1 * [mypy] fix type annotations for project euler problem 025/sol2 * [mypy] fix type annotations for project euler problem026/sol1.py * [mypy] fix type annotations for project euler problem037/sol1 * [mypy] fix type annotations for project euler problem044/sol1 * [mypy] fix type annotations for project euler problem046/sol1 * [mypy] fix type annotations for project euler problem051/sol1 * [mypy] fix type annotations for project euler problem074/sol2 * [mypy] fix type annotations for project euler problem080/sol1 * [mypy] fix type annotations for project euler problem099/sol1 * [mypy] fix type annotations for project euler problem101/sol1 * [mypy] fix type annotations for project euler problem188/sol1 * [mypy] fix type annotations for project euler problem191/sol1 * [mypy] fix type annotations for project euler problem207/sol1 * [mypy] fix type annotations for project euler problem551/sol1 --- project_euler/problem_003/sol1.py | 4 ++-- project_euler/problem_003/sol3.py | 2 +- project_euler/problem_007/sol2.py | 2 +- project_euler/problem_008/sol2.py | 4 +++- project_euler/problem_009/sol1.py | 4 ++++ project_euler/problem_014/sol1.py | 2 +- project_euler/problem_025/sol2.py | 3 ++- project_euler/problem_026/sol1.py | 2 +- project_euler/problem_037/sol1.py | 2 +- project_euler/problem_044/sol1.py | 2 ++ project_euler/problem_046/sol1.py | 2 ++ project_euler/problem_051/sol1.py | 8 +++++--- project_euler/problem_074/sol2.py | 4 ++-- project_euler/problem_080/sol1.py | 5 +++-- project_euler/problem_099/sol1.py | 10 ++++++---- project_euler/problem_101/sol1.py | 2 +- project_euler/problem_188/sol1.py | 4 ++-- project_euler/problem_191/sol1.py | 2 +- project_euler/problem_207/sol1.py | 2 +- project_euler/problem_551/sol1.py | 3 ++- 20 files changed, 43 insertions(+), 26 deletions(-) diff --git a/project_euler/problem_003/sol1.py b/project_euler/problem_003/sol1.py index 3441dbf9e0b3..1f329984203a 100644 --- a/project_euler/problem_003/sol1.py +++ b/project_euler/problem_003/sol1.py @@ -92,8 +92,8 @@ def solution(n: int = 600851475143) -> int: return n for i in range(3, int(math.sqrt(n)) + 1, 2): if n % i == 0: - if isprime(n / i): - max_number = n / i + if isprime(n // i): + max_number = n // i break elif isprime(i): max_number = i diff --git a/project_euler/problem_003/sol3.py b/project_euler/problem_003/sol3.py index bc6f1d2f61ca..e13a0eb74ec1 100644 --- a/project_euler/problem_003/sol3.py +++ b/project_euler/problem_003/sol3.py @@ -57,7 +57,7 @@ def solution(n: int = 600851475143) -> int: i += 1 ans = i while n % i == 0: - n = n / i + n = n // i i += 1 return int(ans) diff --git a/project_euler/problem_007/sol2.py b/project_euler/problem_007/sol2.py index b395c631b766..20c2ddf21ab8 100644 --- a/project_euler/problem_007/sol2.py +++ b/project_euler/problem_007/sol2.py @@ -73,7 +73,7 @@ def solution(nth: int = 10001) -> int: raise TypeError("Parameter nth must be int or castable to int.") from None if nth <= 0: raise ValueError("Parameter nth must be greater than or equal to one.") - primes = [] + primes: list[int] = [] num = 2 while len(primes) < nth: if isprime(num): diff --git a/project_euler/problem_008/sol2.py b/project_euler/problem_008/sol2.py index 7f0540263278..889c3a3143c2 100644 --- a/project_euler/problem_008/sol2.py +++ b/project_euler/problem_008/sol2.py @@ -70,7 +70,9 @@ def solution(n: str = N) -> int: """ return max( - reduce(lambda x, y: int(x) * int(y), n[i : i + 13]) for i in range(len(n) - 12) + # mypy cannot properly interpret reduce + int(reduce(lambda x, y: str(int(x) * int(y)), n[i : i + 13])) + for i in range(len(n) - 12) ) diff --git a/project_euler/problem_009/sol1.py b/project_euler/problem_009/sol1.py index c50dfeecfd22..83c88acf1f8b 100644 --- a/project_euler/problem_009/sol1.py +++ b/project_euler/problem_009/sol1.py @@ -36,6 +36,8 @@ def solution() -> int: if (a ** 2) + (b ** 2) == (c ** 2): return a * b * c + return -1 + def solution_fast() -> int: """ @@ -55,6 +57,8 @@ def solution_fast() -> int: if a < b < c and (a ** 2) + (b ** 2) == (c ** 2): return a * b * c + return -1 + def benchmark() -> None: """ diff --git a/project_euler/problem_014/sol1.py b/project_euler/problem_014/sol1.py index 1745ec931e5a..43aa4e726af2 100644 --- a/project_euler/problem_014/sol1.py +++ b/project_euler/problem_014/sol1.py @@ -44,7 +44,7 @@ def solution(n: int = 1000000) -> int: while number > 1: if number % 2 == 0: - number /= 2 + number //= 2 counter += 1 else: number = (3 * number) + 1 diff --git a/project_euler/problem_025/sol2.py b/project_euler/problem_025/sol2.py index ed3b54bb351f..b041afd98c86 100644 --- a/project_euler/problem_025/sol2.py +++ b/project_euler/problem_025/sol2.py @@ -23,9 +23,10 @@ What is the index of the first term in the Fibonacci sequence to contain 1000 digits? """ +from typing import Generator -def fibonacci_generator() -> int: +def fibonacci_generator() -> Generator[int, None, None]: """ A generator that produces numbers in the Fibonacci sequence diff --git a/project_euler/problem_026/sol1.py b/project_euler/problem_026/sol1.py index 64e0bbfef472..75d48df7910c 100644 --- a/project_euler/problem_026/sol1.py +++ b/project_euler/problem_026/sol1.py @@ -39,7 +39,7 @@ def solution(numerator: int = 1, digit: int = 1000) -> int: longest_list_length = 0 for divide_by_number in range(numerator, digit + 1): - has_been_divided = [] + has_been_divided: list[int] = [] now_divide = numerator for division_cycle in range(1, digit + 1): if now_divide in has_been_divided: diff --git a/project_euler/problem_037/sol1.py b/project_euler/problem_037/sol1.py index 5423aac37c01..0411ad41ba2f 100644 --- a/project_euler/problem_037/sol1.py +++ b/project_euler/problem_037/sol1.py @@ -76,7 +76,7 @@ def compute_truncated_primes(count: int = 11) -> list[int]: >>> compute_truncated_primes(11) [23, 37, 53, 73, 313, 317, 373, 797, 3137, 3797, 739397] """ - list_truncated_primes = [] + list_truncated_primes: list[int] = [] num = 13 while len(list_truncated_primes) != count: if validate(num): diff --git a/project_euler/problem_044/sol1.py b/project_euler/problem_044/sol1.py index d3ae6476d45f..3b75b6a56a8e 100644 --- a/project_euler/problem_044/sol1.py +++ b/project_euler/problem_044/sol1.py @@ -42,6 +42,8 @@ def solution(limit: int = 5000) -> int: if is_pentagonal(a) and is_pentagonal(b): return b + return -1 + if __name__ == "__main__": print(f"{solution() = }") diff --git a/project_euler/problem_046/sol1.py b/project_euler/problem_046/sol1.py index 3fdf567551cc..550c4c7c4268 100644 --- a/project_euler/problem_046/sol1.py +++ b/project_euler/problem_046/sol1.py @@ -85,6 +85,8 @@ def compute_nums(n: int) -> list[int]: if len(list_nums) == n: return list_nums + return [] + def solution() -> int: """Return the solution to the problem""" diff --git a/project_euler/problem_051/sol1.py b/project_euler/problem_051/sol1.py index 5f607e3ffb42..eedb02379e62 100644 --- a/project_euler/problem_051/sol1.py +++ b/project_euler/problem_051/sol1.py @@ -63,12 +63,12 @@ def digit_replacements(number: int) -> list[list[int]]: >>> digit_replacements(3112) [[3002, 3112, 3222, 3332, 3442, 3552, 3662, 3772, 3882, 3992]] """ - number = str(number) + number_str = 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] + for duplicate in Counter(number_str) - Counter(set(number_str)): + family = [int(number_str.replace(duplicate, digit)) for digit in digits] replacements.append(family) return replacements @@ -106,6 +106,8 @@ def solution(family_length: int = 8) -> int: return min(primes_in_family) + return -1 + if __name__ == "__main__": print(solution()) diff --git a/project_euler/problem_074/sol2.py b/project_euler/problem_074/sol2.py index 689593277a81..55e67c6b98dd 100644 --- a/project_euler/problem_074/sol2.py +++ b/project_euler/problem_074/sol2.py @@ -20,8 +20,8 @@ counter increases. """ -factorial_cache = {} -factorial_sum_cache = {} +factorial_cache: dict[int, int] = {} +factorial_sum_cache: dict[int, int] = {} def factorial(a: int) -> int: diff --git a/project_euler/problem_080/sol1.py b/project_euler/problem_080/sol1.py index 517be3fc0ba8..916998bdd8ad 100644 --- a/project_euler/problem_080/sol1.py +++ b/project_euler/problem_080/sol1.py @@ -26,8 +26,8 @@ def solution() -> int: 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) + sqrt_number_str = str(sqrt_number)[2:101] + answer += sum(int(x) for x in sqrt_number_str) return answer @@ -35,3 +35,4 @@ def solution() -> int: import doctest doctest.testmod() + print(f"{solution() = }") diff --git a/project_euler/problem_099/sol1.py b/project_euler/problem_099/sol1.py index 88912e1f0f9e..bf5621c6583c 100644 --- a/project_euler/problem_099/sol1.py +++ b/project_euler/problem_099/sol1.py @@ -22,12 +22,14 @@ def solution(data_file: str = "base_exp.txt") -> int: >>> solution() 709 """ - largest = [0, 0] + largest: float = 0 + result = 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 x * log10(a) > largest: + largest = x * log10(a) + result = i + 1 + return result if __name__ == "__main__": diff --git a/project_euler/problem_101/sol1.py b/project_euler/problem_101/sol1.py index 553f8f442bb8..14013c435241 100644 --- a/project_euler/problem_101/sol1.py +++ b/project_euler/problem_101/sol1.py @@ -202,7 +202,7 @@ def solution(func: Callable[[int], int] = question_function, order: int = 10) -> ] ret: int = 0 - poly: int + poly: Callable[[int], int] x_val: int for poly in polynomials: diff --git a/project_euler/problem_188/sol1.py b/project_euler/problem_188/sol1.py index 6473c63620ed..c8cd9eb10aeb 100644 --- a/project_euler/problem_188/sol1.py +++ b/project_euler/problem_188/sol1.py @@ -19,7 +19,7 @@ """ -# small helper function for modular exponentiation +# small helper function for modular exponentiation (fast exponentiation algorithm) def _modexpt(base: int, exponent: int, modulo_value: int) -> int: """ Returns the modular exponentiation, that is the value @@ -36,7 +36,7 @@ def _modexpt(base: int, exponent: int, modulo_value: int) -> int: if exponent == 1: return base if exponent % 2 == 0: - x = _modexpt(base, exponent / 2, modulo_value) % modulo_value + 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 diff --git a/project_euler/problem_191/sol1.py b/project_euler/problem_191/sol1.py index 38325b363b89..6bff9d54eeca 100644 --- a/project_euler/problem_191/sol1.py +++ b/project_euler/problem_191/sol1.py @@ -26,7 +26,7 @@ """ -cache = {} +cache: dict[tuple[int, int, int], int] = {} def _calculate(days: int, absent: int, late: int) -> int: diff --git a/project_euler/problem_207/sol1.py b/project_euler/problem_207/sol1.py index fb901fde1624..99d1a91746d2 100644 --- a/project_euler/problem_207/sol1.py +++ b/project_euler/problem_207/sol1.py @@ -90,7 +90,7 @@ def solution(max_proportion: float = 1 / 12345) -> int: perfect_partitions += 1 if perfect_partitions > 0: if perfect_partitions / total_partitions < max_proportion: - return partition_candidate + return int(partition_candidate) integer += 1 diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index 71956691a56d..005d2e98514b 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -12,9 +12,10 @@ Find a(10^15) """ + ks = [k for k in range(2, 20 + 1)] base = [10 ** k for k in range(ks[-1] + 1)] -memo = {} +memo: dict[int, dict[int, list[list[int]]]] = {} def next_term(a_i, k, i, n): From abaa0d754b8dc24abacac2a4d7ecade2d3ddacb6 Mon Sep 17 00:00:00 2001 From: scfenton6 <91698851+scfenton6@users.noreply.github.com> Date: Mon, 11 Oct 2021 18:34:30 +0200 Subject: [PATCH 1581/2908] Add type annotations (#4814) --- data_structures/disjoint_set/disjoint_set.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/data_structures/disjoint_set/disjoint_set.py b/data_structures/disjoint_set/disjoint_set.py index bf5ab415d5e4..f8500bf2c3af 100644 --- a/data_structures/disjoint_set/disjoint_set.py +++ b/data_structures/disjoint_set/disjoint_set.py @@ -1,17 +1,19 @@ """ - disjoint set + Disjoint set. Reference: https://en.wikipedia.org/wiki/Disjoint-set_data_structure """ class Node: - def __init__(self, data): + def __init__(self, data: int) -> None: self.data = data + self.rank: int + self.parent: Node -def make_set(x): +def make_set(x: Node) -> None: """ - make x as a set. + Make x as a set. """ # rank is the distance from x to its' parent # root's rank is 0 @@ -19,9 +21,9 @@ def make_set(x): x.parent = x -def union_set(x, y): +def union_set(x: Node, y: Node) -> None: """ - union two sets. + Union of two sets. set with bigger rank should be parent, so that the disjoint set tree will be more flat. """ @@ -37,9 +39,9 @@ def union_set(x, y): y.rank += 1 -def find_set(x): +def find_set(x: Node) -> Node: """ - return the parent of x + Return the parent of x """ if x != x.parent: x.parent = find_set(x.parent) @@ -57,7 +59,7 @@ def find_python_set(node: Node) -> set: raise ValueError(f"{node.data} is not in {sets}") -def test_disjoint_set(): +def test_disjoint_set() -> None: """ >>> test_disjoint_set() """ From 9586a6a98ef4bad7894bfe31da4fab42f6b3d6cd Mon Sep 17 00:00:00 2001 From: poloso Date: Mon, 11 Oct 2021 11:44:38 -0500 Subject: [PATCH 1582/2908] Change comments for improved consistency (#5223) * Change comments for improved consistency https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md#L56 https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md#L80 https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md#L87 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- DIRECTORY.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 13d330a90dc5..e9cf0e6a18b7 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 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 +python3 -m pip install pre-commit # only required the first time 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: diff --git a/DIRECTORY.md b/DIRECTORY.md index 2e9942b5bf93..6c227fd2b5ad 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -274,6 +274,7 @@ * [Test Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/tests/test_send_file.py) ## Fractals + * [Julia Sets](https://github.com/TheAlgorithms/Python/blob/master/fractals/julia_sets.py) * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/fractals/koch_snowflake.py) * [Mandelbrot](https://github.com/TheAlgorithms/Python/blob/master/fractals/mandelbrot.py) * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/fractals/sierpinski_triangle.py) @@ -424,10 +425,12 @@ * [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) + * [Check Polygon](https://github.com/TheAlgorithms/Python/blob/master/maths/check_polygon.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) * [Decimal Isolate](https://github.com/TheAlgorithms/Python/blob/master/maths/decimal_isolate.py) + * [Double Factorial Iterative](https://github.com/TheAlgorithms/Python/blob/master/maths/double_factorial_iterative.py) * [Double Factorial Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/double_factorial_recursive.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) @@ -511,6 +514,7 @@ * [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) + * [Sylvester Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/sylvester_sequence.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) * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/triplet_sum.py) @@ -928,7 +932,6 @@ * [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) * [Upper](https://github.com/TheAlgorithms/Python/blob/master/strings/upper.py) * [Word Occurrence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurrence.py) * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/strings/word_patterns.py) From 1b0ac73da25915d4cb4d2754f7c12d1b81fc9f90 Mon Sep 17 00:00:00 2001 From: Aman kanojiya <50018596+AMANKANOJIYA@users.noreply.github.com> Date: Tue, 12 Oct 2021 15:21:27 +0530 Subject: [PATCH 1583/2908] Magnitude and Angle of Vector (#5225) * Magnitude and Angle Core function to find Magnitude and Angle of two Given Vector * Magnitude and Angle with Doctest added Doctest to the functions * Update linear_algebra/src/lib.py Co-authored-by: Christian Clauss * Update linear_algebra/src/lib.py Co-authored-by: Christian Clauss * Changes done and Magnitude and Angle Issues * black Co-authored-by: Christian Clauss --- linear_algebra/src/lib.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 74aeb9137666..6a18df5e15c3 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -148,6 +148,36 @@ def __mul__(self, other: float | Vector) -> float | Vector: else: # error case raise Exception("invalid operand!") + def magnitude(self) -> float: + """ + Magnitude of a Vector + + >>> Vector([2, 3, 4]).magnitude() + 5.385164807134504 + + """ + return sum([i ** 2 for i in self.__components]) ** (1 / 2) + + def angle(self, other: Vector, deg: bool = False) -> float: + """ + find angle between two Vector (self, Vector) + + >>> Vector([3, 4, -1]).angle(Vector([2, -1, 1])) + 1.4906464636572374 + >>> Vector([3, 4, -1]).angle(Vector([2, -1, 1]), deg = True) + 85.40775111366095 + >>> Vector([3, 4, -1]).angle(Vector([2, -1])) + Traceback (most recent call last): + ... + Exception: invalid operand! + """ + num = self * other + den = self.magnitude() * other.magnitude() + if deg: + return math.degrees(math.acos(num / den)) + else: + return math.acos(num / den) + def copy(self) -> Vector: """ copies this vector and returns it. From 943e03fc545f91482dae08d7a2f1335d9d1faf17 Mon Sep 17 00:00:00 2001 From: Raj-Pansuriya <72313592+Raj-Pansuriya@users.noreply.github.com> Date: Thu, 14 Oct 2021 16:21:13 +0530 Subject: [PATCH 1584/2908] Added Optimal Merge Pattern Algorithm (#5274) * Minor changes due to precommit * Update optimal_merge_pattern.py Co-authored-by: Christian Clauss --- greedy_methods/optimal_merge_pattern.py | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 greedy_methods/optimal_merge_pattern.py diff --git a/greedy_methods/optimal_merge_pattern.py b/greedy_methods/optimal_merge_pattern.py new file mode 100644 index 000000000000..911e1966f3b9 --- /dev/null +++ b/greedy_methods/optimal_merge_pattern.py @@ -0,0 +1,56 @@ +""" +This is a pure Python implementation of the greedy-merge-sort algorithm +reference: https://www.geeksforgeeks.org/optimal-file-merge-patterns/ + +For doctests run following command: +python3 -m doctest -v greedy_merge_sort.py + +Objective +Merge a set of sorted files of different length into a single sorted file. +We need to find an optimal solution, where the resultant file +will be generated in minimum time. + +Approach +If the number of sorted files are given, there are many ways +to merge them into a single sorted file. +This merge can be performed pair wise. +To merge a m-record file and a n-record file requires possibly m+n record moves +the optimal choice being, +merge the two smallest files together at each step (greedy approach). +""" + + +def optimal_merge_pattern(files: list) -> float: + """Function to merge all the files with optimum cost + + Args: + files [list]: A list of sizes of different files to be merged + + Returns: + optimal_merge_cost [int]: Optimal cost to merge all those files + + Examples: + >>> optimal_merge_pattern([2, 3, 4]) + 14 + >>> optimal_merge_pattern([5, 10, 20, 30, 30]) + 205 + >>> optimal_merge_pattern([8, 8, 8, 8, 8]) + 96 + """ + optimal_merge_cost = 0 + while len(files) > 1: + temp = 0 + # Consider two files with minimum cost to be merged + for i in range(2): + min_index = files.index(min(files)) + temp += files[min_index] + files.pop(min_index) + files.append(temp) + optimal_merge_cost += temp + return optimal_merge_cost + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9b4cb05ee5a89609590785b683f81f42a77dadf1 Mon Sep 17 00:00:00 2001 From: Aman kanojiya <50018596+AMANKANOJIYA@users.noreply.github.com> Date: Thu, 14 Oct 2021 16:23:03 +0530 Subject: [PATCH 1585/2908] Modified Euler's Method (#5258) * Magnitude and Angle Core function to find Magnitude and Angle of two Given Vector * Magnitude and Angle with Doctest added Doctest to the functions * Update linear_algebra/src/lib.py Co-authored-by: Christian Clauss * Update linear_algebra/src/lib.py Co-authored-by: Christian Clauss * Changes done and Magnitude and Angle Issues * black * Modified Euler's Method Adding Modified Euler's method, which was the further change to a Euler method and known for better accuracy to the given value * Modified Euler's Method (changed the typing of function) Modified function is used for better accuracy * Link added Added link to an explanation as per Contributions Guidelines * Resolving Pre-Commit error * Pre-Commit Error Resolved * Pre-Commit Error import statement Change * Removed Import Math * import math built issue * adding space pre-commit error * statement sorter for doc Co-authored-by: Christian Clauss --- maths/euler_modified.py | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 maths/euler_modified.py diff --git a/maths/euler_modified.py b/maths/euler_modified.py new file mode 100644 index 000000000000..bf0c07c17f48 --- /dev/null +++ b/maths/euler_modified.py @@ -0,0 +1,54 @@ +from typing import Callable + +import numpy as np + + +def euler_modified( + ode_func: Callable, y0: float, x0: float, step_size: float, x_end: float +) -> np.array: + """ + Calculate solution at each step to an ODE using Euler's Modified Method + The Euler is straightforward to implement, but can't give accurate solutions. + So, they Proposed some changes to improve the accuracy + + 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 f1(x, y): + ... return -2*x*(y**2) + >>> y = euler_modified(f1, 1.0, 0.0, 0.2, 1.0) + >>> y[-1] + 0.503338255442106 + >>> import math + >>> def f2(x, y): + ... return -2*y + (x**3)*math.exp(-2*x) + >>> y = euler_modified(f2, 1.0, 0.0, 0.1, 0.3) + >>> y[-1] + 0.5525976431951775 + """ + 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_get = y[k] + step_size * ode_func(x, y[k]) + y[k + 1] = y[k] + ( + (step_size / 2) * (ode_func(x, y[k]) + ode_func(x + step_size, y_get)) + ) + x += step_size + + return y + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d561de0bd93a4aef31765ae84b97dafdb606a7e6 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <89947037+Rohanrbharadwaj@users.noreply.github.com> Date: Thu, 14 Oct 2021 16:53:18 +0530 Subject: [PATCH 1586/2908] Add surface area of cone and cylinder and hemisphere (#5220) * Update area.py * Update area.py * Update area.py * Update area.py * Update area.py * Update area.py * Update area.py * Update area.py --- maths/area.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/maths/area.py b/maths/area.py index 8689f323cc9a..13c05af5f68e 100644 --- a/maths/area.py +++ b/maths/area.py @@ -42,6 +42,85 @@ def surface_area_sphere(radius: float) -> float: return 4 * pi * radius ** 2 +def surface_area_hemisphere(radius: float) -> float: + """ + Calculate the Surface Area of a Hemisphere. + Formula: 3 * pi * r^2 + + >>> surface_area_hemisphere(5) + 235.61944901923448 + >>> surface_area_hemisphere(1) + 9.42477796076938 + >>> surface_area_hemisphere(0) + 0.0 + >>> surface_area_hemisphere(1.1) + 11.40398133253095 + >>> surface_area_hemisphere(-1) + Traceback (most recent call last): + ... + ValueError: surface_area_hemisphere() only accepts non-negative values + """ + if radius < 0: + raise ValueError("surface_area_hemisphere() only accepts non-negative values") + return 3 * pi * radius ** 2 + + +def surface_area_cone(radius: float, height: float) -> float: + """ + Calculate the Surface Area of a Cone. + Wikipedia reference: https://en.wikipedia.org/wiki/Cone + Formula: pi * r * (r + (h ** 2 + r ** 2) ** 0.5) + + >>> surface_area_cone(10, 24) + 1130.9733552923256 + >>> surface_area_cone(6, 8) + 301.59289474462014 + >>> surface_area_cone(-1, -2) + Traceback (most recent call last): + ... + ValueError: surface_area_cone() only accepts non-negative values + >>> surface_area_cone(1, -2) + Traceback (most recent call last): + ... + ValueError: surface_area_cone() only accepts non-negative values + >>> surface_area_cone(-1, 2) + Traceback (most recent call last): + ... + ValueError: surface_area_cone() only accepts non-negative values + """ + if radius < 0 or height < 0: + raise ValueError("surface_area_cone() only accepts non-negative values") + return pi * radius * (radius + (height ** 2 + radius ** 2) ** 0.5) + + +def surface_area_cylinder(radius: float, height: float) -> float: + """ + Calculate the Surface Area of a Cylinder. + Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder + Formula: 2 * pi * r * (h + r) + + >>> surface_area_cylinder(7, 10) + 747.6990515543707 + >>> surface_area_cylinder(6, 8) + 527.7875658030853 + >>> surface_area_cylinder(-1, -2) + Traceback (most recent call last): + ... + ValueError: surface_area_cylinder() only accepts non-negative values + >>> surface_area_cylinder(1, -2) + Traceback (most recent call last): + ... + ValueError: surface_area_cylinder() only accepts non-negative values + >>> surface_area_cylinder(-1, 2) + Traceback (most recent call last): + ... + ValueError: surface_area_cylinder() only accepts non-negative values + """ + if radius < 0 or height < 0: + raise ValueError("surface_area_cylinder() only accepts non-negative values") + return 2 * pi * radius * (height + radius) + + def area_rectangle(length: float, width: float) -> float: """ Calculate the area of a rectangle. @@ -280,9 +359,12 @@ def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: 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"Rhombus: {area_rhombus(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) = }") + print(f"Hemisphere: {surface_area_hemisphere(20) = }") + print(f"Cone: {surface_area_cone(10, 20) = }") + print(f"Cylinder: {surface_area_cylinder(10, 20) = }") From bb37ebbe50b7c7140b6efc52b95f3c32f230ea0a Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <89947037+Rohanrbharadwaj@users.noreply.github.com> Date: Thu, 14 Oct 2021 19:31:38 +0530 Subject: [PATCH 1587/2908] Create baconian_cipher.py (#5251) * Create baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py * Update baconian_cipher.py --- ciphers/baconian_cipher.py | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 ciphers/baconian_cipher.py diff --git a/ciphers/baconian_cipher.py b/ciphers/baconian_cipher.py new file mode 100644 index 000000000000..027fbc50e89d --- /dev/null +++ b/ciphers/baconian_cipher.py @@ -0,0 +1,89 @@ +""" +Program to encode and decode Baconian or Bacon's Cipher +Wikipedia reference : https://en.wikipedia.org/wiki/Bacon%27s_cipher +""" + +encode_dict = { + "a": "AAAAA", + "b": "AAAAB", + "c": "AAABA", + "d": "AAABB", + "e": "AABAA", + "f": "AABAB", + "g": "AABBA", + "h": "AABBB", + "i": "ABAAA", + "j": "BBBAA", + "k": "ABAAB", + "l": "ABABA", + "m": "ABABB", + "n": "ABBAA", + "o": "ABBAB", + "p": "ABBBA", + "q": "ABBBB", + "r": "BAAAA", + "s": "BAAAB", + "t": "BAABA", + "u": "BAABB", + "v": "BBBAB", + "w": "BABAA", + "x": "BABAB", + "y": "BABBA", + "z": "BABBB", + " ": " ", +} + + +decode_dict = {value: key for key, value in encode_dict.items()} + + +def encode(word: str) -> str: + """ + Encodes to Baconian cipher + + >>> encode("hello") + 'AABBBAABAAABABAABABAABBAB' + >>> encode("hello world") + 'AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB' + >>> encode("hello world!") + Traceback (most recent call last): + ... + Exception: encode() accepts only letters of the alphabet and spaces + """ + encoded = "" + for letter in word.lower(): + if letter.isalpha() or letter == " ": + encoded += encode_dict[letter] + else: + raise Exception("encode() accepts only letters of the alphabet and spaces") + return encoded + + +def decode(coded: str) -> str: + """ + Decodes from Baconian cipher + + >>> decode("AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB") + 'hello world' + >>> decode("AABBBAABAAABABAABABAABBAB") + 'hello' + >>> decode("AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB!") + Traceback (most recent call last): + ... + Exception: decode() accepts only 'A', 'B' and spaces + """ + if set(coded) - {"A", "B", " "} != set(): + raise Exception("decode() accepts only 'A', 'B' and spaces") + decoded = "" + for word in coded.split(): + while len(word) != 0: + decoded += decode_dict[word[:5]] + word = word[5:] + decoded += " " + return decoded.strip() + + +if "__name__" == "__main__": + from doctest import testmod + + testmod() From 618f9ca885a6f4e0c2f7dfcf1768ef7b0f717ba6 Mon Sep 17 00:00:00 2001 From: Jordan Rinder Date: Thu, 14 Oct 2021 10:30:52 -0400 Subject: [PATCH 1588/2908] Add Proth number to maths (#5246) * Add Proth number to maths * Add test for 0 and more informative output * Fixing test failure issue - unused variable * Update proth_number.py Co-authored-by: Christian Clauss --- maths/proth_number.py | 77 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 maths/proth_number.py diff --git a/maths/proth_number.py b/maths/proth_number.py new file mode 100644 index 000000000000..065244ed7607 --- /dev/null +++ b/maths/proth_number.py @@ -0,0 +1,77 @@ +""" +Calculate the nth Proth number + +Source: + https://handwiki.org/wiki/Proth_number +""" + +import math + + +def proth(number: int) -> int: + """ + :param number: nth number to calculate in the sequence + :return: the nth number in Proth number + + Note: indexing starts at 1 i.e. proth(1) gives the first Proth number of 3 + + >>> proth(6) + 25 + + >>> proth(0) + Traceback (most recent call last): + ... + ValueError: Input value of [number=0] must be > 0 + + >>> proth(-1) + Traceback (most recent call last): + ... + ValueError: Input value of [number=-1] must be > 0 + + >>> proth(6.0) + Traceback (most recent call last): + ... + TypeError: Input value of [number=6.0] must be an integer + """ + + if not isinstance(number, int): + raise TypeError(f"Input value of [number={number}] must be an integer") + + if number < 1: + raise ValueError(f"Input value of [number={number}] must be > 0") + elif number == 1: + return 3 + elif number == 2: + return 5 + else: + block_index = number // 3 + """ + +1 for binary starting at 0 i.e. 2^0, 2^1, etc. + +1 to start the sequence at the 3rd Proth number + Hence, we have a +2 in the below statement + """ + block_index = math.log(block_index, 2) + 2 + block_index = int(block_index) + + proth_list = [3, 5] + proth_index = 2 + increment = 3 + for block in range(1, block_index): + for move in range(increment): + proth_list.append(2 ** (block + 1) + proth_list[proth_index - 1]) + proth_index += 1 + increment *= 2 + + return proth_list[number - 1] + + +if __name__ == "__main__": + for number in range(11): + value = 0 + try: + value = proth(number) + except ValueError: + print(f"ValueError: there is no {number}th Proth number") + continue + + print(f"The {number}th Proth number: {value}") From ca842b4add2a81c6d50e4fba2bf33ad07f54dbca Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 14 Oct 2021 18:19:47 +0200 Subject: [PATCH 1589/2908] It is OK to test ./scripts (#5290) * It is OK to test ./scripts * updating DIRECTORY.md * Update build.yml Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- DIRECTORY.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c2255275091..f710e1e0ed54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,6 @@ jobs: python -m pip install mypy pytest-cov -r requirements.txt - run: mypy --install-types --non-interactive . - name: Run tests - run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . + run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/DIRECTORY.md b/DIRECTORY.md index 6c227fd2b5ad..d92dccca5afb 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -264,6 +264,7 @@ * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) ## Electronics + * [Carrier Concentration](https://github.com/TheAlgorithms/Python/blob/master/electronics/carrier_concentration.py) * [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) @@ -346,6 +347,9 @@ * [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 Methods + * [Optimal Merge Pattern](https://github.com/TheAlgorithms/Python/blob/master/greedy_methods/optimal_merge_pattern.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) @@ -436,6 +440,7 @@ * [Euclidean Distance](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_distance.py) * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_gcd.py) * [Euler Method](https://github.com/TheAlgorithms/Python/blob/master/maths/euler_method.py) + * [Euler Modified](https://github.com/TheAlgorithms/Python/blob/master/maths/euler_modified.py) * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.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) From 545fec7a1446348d117f8c840cb4e334a67e25da Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <89947037+Rohanrbharadwaj@users.noreply.github.com> Date: Fri, 15 Oct 2021 16:03:39 +0530 Subject: [PATCH 1590/2908] Fix documentation (#5311) --- maths/euler_modified.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/euler_modified.py b/maths/euler_modified.py index bf0c07c17f48..7c76a0ee0b86 100644 --- a/maths/euler_modified.py +++ b/maths/euler_modified.py @@ -8,8 +8,8 @@ def euler_modified( ) -> np.array: """ Calculate solution at each step to an ODE using Euler's Modified Method - The Euler is straightforward to implement, but can't give accurate solutions. - So, they Proposed some changes to improve the accuracy + The Euler Method is straightforward to implement, but can't give accurate solutions. + So, some changes were proposed to improve accuracy. https://en.wikipedia.org/wiki/Euler_method From 908cb4f1e72e16726ef5ca8365b36d473fcf2e00 Mon Sep 17 00:00:00 2001 From: Manuel Di Lullo <39048927+manueldilullo@users.noreply.github.com> Date: Fri, 15 Oct 2021 15:04:38 +0200 Subject: [PATCH 1591/2908] Greedy min vertex cover hacktoberfest (#5241) * added complete graph generator function * added doctest, type hints, wikipedia explanation * added return type hint for function complete_graph * added descriptive name for the parameter: n * random graph generator with doctest and type hints * added Greedy min vertex algorithm * pre-commit hook(s) made changes * Delete complete_graph_generator.py * Delete random_graph_generator.py * fixed doctest * updated commit following highligths * fixed following pre-commit highlights * modified variables names --- graphs/greedy_min_vertex_cover.py | 65 +++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 graphs/greedy_min_vertex_cover.py diff --git a/graphs/greedy_min_vertex_cover.py b/graphs/greedy_min_vertex_cover.py new file mode 100644 index 000000000000..056c5b89bedf --- /dev/null +++ b/graphs/greedy_min_vertex_cover.py @@ -0,0 +1,65 @@ +""" +* Author: Manuel Di Lullo (https://github.com/manueldilullo) +* Description: Approximization algorithm for minimum vertex cover problem. + Greedy Approach. Uses graphs represented with an adjacency list + +URL: https://mathworld.wolfram.com/MinimumVertexCover.html +URL: https://cs.stackexchange.com/questions/129017/greedy-algorithm-for-vertex-cover +""" + +import heapq + + +def greedy_min_vertex_cover(graph: dict) -> set: + """ + Greedy APX Algorithm for min Vertex Cover + @input: graph (graph stored in an adjacency list where each vertex + is represented with an integer) + @example: + >>> graph = {0: [1, 3], 1: [0, 3], 2: [0, 3, 4], 3: [0, 1, 2], 4: [2, 3]} + >>> greedy_min_vertex_cover(graph) + {0, 1, 2, 4} + """ + # queue used to store nodes and their rank + queue = [] + + # for each node and his adjacency list add them and the rank of the node to queue + # using heapq module the queue will be filled like a Priority Queue + # heapq works with a min priority queue, so I used -1*len(v) to build it + for key, value in graph.items(): + # O(log(n)) + heapq.heappush(queue, [-1 * len(value), (key, value)]) + + # chosen_vertices = set of chosen vertices + chosen_vertices = set() + + # while queue isn't empty and there are still edges + # (queue[0][0] is the rank of the node with max rank) + while queue and queue[0][0] != 0: + # extract vertex with max rank from queue and add it to chosen_vertices + argmax = heapq.heappop(queue)[1][0] + chosen_vertices.add(argmax) + + # Remove all arcs adjacent to argmax + for elem in queue: + # if v haven't adjacent node, skip + if elem[0] == 0: + continue + # if argmax is reachable from elem + # remove argmax from elem's adjacent list and update his rank + if argmax in elem[1][1]: + index = elem[1][1].index(argmax) + del elem[1][1][index] + elem[0] += 1 + # re-order the queue + heapq.heapify(queue) + return chosen_vertices + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # graph = {0: [1, 3], 1: [0, 3], 2: [0, 3, 4], 3: [0, 1, 2], 4: [2, 3]} + # print(f"Minimum vertex cover:\n{greedy_min_vertex_cover(graph)}") From 1d457be29d0ac55ebafe049a9a13f9b9c09f1380 Mon Sep 17 00:00:00 2001 From: Manuel Di Lullo <39048927+manueldilullo@users.noreply.github.com> Date: Fri, 15 Oct 2021 17:03:57 +0200 Subject: [PATCH 1592/2908] Matching min vertex cover (#5326) * matching algorithm for min vertex cover problem * fixed hint on row 37 * changed variable names * provided doctest for get_edges function * Removed dict.keys() iteration * Update matching_min_vertex_cover.py Co-authored-by: Christian Clauss --- graphs/matching_min_vertex_cover.py | 62 +++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 graphs/matching_min_vertex_cover.py diff --git a/graphs/matching_min_vertex_cover.py b/graphs/matching_min_vertex_cover.py new file mode 100644 index 000000000000..5ac944ec1a07 --- /dev/null +++ b/graphs/matching_min_vertex_cover.py @@ -0,0 +1,62 @@ +""" +* Author: Manuel Di Lullo (https://github.com/manueldilullo) +* Description: Approximization algorithm for minimum vertex cover problem. + Matching Approach. Uses graphs represented with an adjacency list + +URL: https://mathworld.wolfram.com/MinimumVertexCover.html +URL: https://www.princeton.edu/~aaa/Public/Teaching/ORF523/ORF523_Lec6.pdf +""" + + +def matching_min_vertex_cover(graph: dict) -> set: + """ + APX Algorithm for min Vertex Cover using Matching Approach + @input: graph (graph stored in an adjacency list where each vertex + is represented as an integer) + @example: + >>> graph = {0: [1, 3], 1: [0, 3], 2: [0, 3, 4], 3: [0, 1, 2], 4: [2, 3]} + >>> matching_min_vertex_cover(graph) + {0, 1, 2, 4} + """ + # chosen_vertices = set of chosen vertices + chosen_vertices = set() + # edges = list of graph's edges + edges = get_edges(graph) + + # While there are still elements in edges list, take an arbitrary edge + # (from_node, to_node) and add his extremity to chosen_vertices and then + # remove all arcs adjacent to the from_node and to_node + while edges: + from_node, to_node = edges.pop() + chosen_vertices.add(from_node) + chosen_vertices.add(to_node) + for edge in edges.copy(): + if from_node in edge or to_node in edge: + edges.discard(edge) + return chosen_vertices + + +def get_edges(graph: dict) -> set: + """ + Return a set of couples that represents all of the edges. + @input: graph (graph stored in an adjacency list where each vertex is + represented as an integer) + @example: + >>> graph = {0: [1, 3], 1: [0, 3], 2: [0, 3], 3: [0, 1, 2]} + >>> get_edges(graph) + {(0, 1), (3, 1), (0, 3), (2, 0), (3, 0), (2, 3), (1, 0), (3, 2), (1, 3)} + """ + edges = set() + for from_node, to_nodes in graph.items(): + for to_node in to_nodes: + edges.add((from_node, to_node)) + return edges + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # graph = {0: [1, 3], 1: [0, 3], 2: [0, 3, 4], 3: [0, 1, 2], 4: [2, 3]} + # print(f"Matching vertex cover:\n{matching_min_vertex_cover(graph)}") From 4cf1aaeb967790b88c85d5c773538754ffd89a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Murilo=20Gon=C3=A7alves?= <38800183+murilo-goncalves@users.noreply.github.com> Date: Fri, 15 Oct 2021 18:57:41 -0300 Subject: [PATCH 1593/2908] Updated mypy.ini, removed ok folders that were excluded (#5331) --- mypy.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index 9eec22e22717..ba552f878e30 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True ; FIXME: #4052 fix mypy errors in the exclude directories and remove them below -exclude = (data_structures|dynamic_programming|graphs|maths|matrix|other|project_euler|searches|strings*)/$ +exclude = (data_structures|graphs|maths|matrix|other|searches)/$ From 152261765a93c2a12eb4af0abdd297652766d47d Mon Sep 17 00:00:00 2001 From: Appledora Date: Sat, 16 Oct 2021 06:02:44 +0600 Subject: [PATCH 1594/2908] Show images from google query (#4853) * Added new script to open the google image tab with a search query. * Added new script to open the google image tab with a search query. * Added new script to open the google image tab with a search query with doctests. * Fixed doctest error, removed print() from method, changed return type * Update web_programming/show_image_tab_from_google_query.py using iterators instead of lists Co-authored-by: Christian Clauss * Update web_programming/show_image_tab_from_google_query.py Improve readability by removing one-time used variable Co-authored-by: Christian Clauss * Update web_programming/show_image_tab_from_google_query.py Decreasing complication through standard practices. Co-authored-by: Christian Clauss * Update web_programming/show_image_tab_from_google_query.py Exception Handling Co-authored-by: Christian Clauss * changed complete method to download images from google search query * Update download_images_from_google_query.py * Delete show_image_tab_from_google_query.py Co-authored-by: Christian Clauss --- .../download_images_from_google_query.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 web_programming/download_images_from_google_query.py diff --git a/web_programming/download_images_from_google_query.py b/web_programming/download_images_from_google_query.py new file mode 100644 index 000000000000..c26262788c4c --- /dev/null +++ b/web_programming/download_images_from_google_query.py @@ -0,0 +1,99 @@ +import json +import os +import re +import sys +import urllib.request + +import requests +from bs4 import BeautifulSoup + +headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + " (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" +} + + +def download_images_from_google_query(query: str = "dhaka", max_images: int = 5) -> int: + """Searches google using the provided query term and downloads the images in a folder. + + Args: + query : The image search term to be provided by the user. Defaults to + "dhaka". + image_numbers : [description]. Defaults to 5. + + Returns: + The number of images successfully downloaded. + + >>> download_images_from_google_query() + 5 + >>> download_images_from_google_query("potato") + 5 + """ + max_images = min(max_images, 50) # Prevent abuse! + params = { + "q": query, + "tbm": "isch", + "hl": "en", + "ijn": "0", + } + + html = requests.get("https://www.google.com/search", params=params, headers=headers) + soup = BeautifulSoup(html.text, "html.parser") + matched_images_data = "".join( + re.findall(r"AF_initDataCallback\(([^<]+)\);", str(soup.select("script"))) + ) + + matched_images_data_fix = json.dumps(matched_images_data) + matched_images_data_json = json.loads(matched_images_data_fix) + + matched_google_image_data = re.findall( + r"\[\"GRID_STATE0\",null,\[\[1,\[0,\".*?\",(.*),\"All\",", + matched_images_data_json, + ) + if not matched_google_image_data: + return 0 + + removed_matched_google_images_thumbnails = re.sub( + r"\[\"(https\:\/\/encrypted-tbn0\.gstatic\.com\/images\?.*?)\",\d+,\d+\]", + "", + str(matched_google_image_data), + ) + + matched_google_full_resolution_images = re.findall( + r"(?:'|,),\[\"(https:|http.*?)\",\d+,\d+\]", + removed_matched_google_images_thumbnails, + ) + for index, fixed_full_res_image in enumerate(matched_google_full_resolution_images): + if index >= max_images: + return index + original_size_img_not_fixed = bytes(fixed_full_res_image, "ascii").decode( + "unicode-escape" + ) + original_size_img = bytes(original_size_img_not_fixed, "ascii").decode( + "unicode-escape" + ) + opener = urllib.request.build_opener() + opener.addheaders = [ + ( + "User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + " (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582", + ) + ] + urllib.request.install_opener(opener) + path_name = f"query_{query.replace(' ', '_')}" + if not os.path.exists(path_name): + os.makedirs(path_name) + urllib.request.urlretrieve( + original_size_img, f"{path_name}/original_size_img_{index}.jpg" + ) + return index + + +if __name__ == "__main__": + try: + image_count = download_images_from_google_query(sys.argv[1]) + print(f"{image_count} images were downloaded to disk.") + except IndexError: + print("Please provide a search term.") + raise From 37385883aaa140a505488d23fa37be4049768f2b Mon Sep 17 00:00:00 2001 From: Appledora Date: Sat, 16 Oct 2021 07:32:33 +0600 Subject: [PATCH 1595/2908] Improved readability of web_programming/get_imdbtop.py and added documentations with doctests (#4855) * improved readability of the existing method by reformatting, adding documentations with doctests. * improved readability of the existing method by reformatting, adding documentations with doctests. * fixed typo in test * added doctest to parse dictionary method * added doctest to parse dictionary method * Changed return type, removed print() from method and implemented doctests as suggested * Fixed doctest error, removed print() from method, created new script as suggested * Update get_imdbtop.py * Fix typo discovered by codespell * return () Co-authored-by: Christian Clauss --- web_programming/get_imdbtop.py | 57 +++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/web_programming/get_imdbtop.py b/web_programming/get_imdbtop.py index 669e7f89824b..5f7105f83239 100644 --- a/web_programming/get_imdbtop.py +++ b/web_programming/get_imdbtop.py @@ -1,20 +1,53 @@ +import bs4 import requests -from bs4 import BeautifulSoup -def imdb_top(imdb_top_n): +def get_movie_data_from_soup(soup: bs4.element.ResultSet) -> dict[str, str]: + return { + "name": soup.h3.a.text, + "genre": soup.find("span", class_="genre").text.strip(), + "rating": soup.strong.text, + "page_link": f"https://www.imdb.com{soup.a.get(/'href')}", + } + + +def get_imdb_top_movies(num_movies: int = 5) -> tuple: + """Get the top num_movies most highly rated movies from IMDB and + return a tuple of dicts describing each movie's name, genre, rating, and URL. + + Args: + num_movies: The number of movies to get. Defaults to 5. + + Returns: + A list of tuples containing information about the top n movies. + + >>> len(get_imdb_top_movies(5)) + 5 + >>> len(get_imdb_top_movies(-3)) + 0 + >>> len(get_imdb_top_movies(4.99999)) + 4 + """ + num_movies = int(float(num_movies)) + if num_movies < 1: + return () base_url = ( - f"https://www.imdb.com/search/title?title_type=" - f"feature&sort=num_votes,desc&count={imdb_top_n}" + "https://www.imdb.com/search/title?title_type=" + f"feature&sort=num_votes,desc&count={num_movies}" + ) + source = bs4.BeautifulSoup(requests.get(base_url).content, "html.parser") + return tuple( + get_movie_data_from_soup(movie) + for movie in source.find_all("div", class_="lister-item mode-advanced") ) - 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? ")) + import json + + num_movies = int(input("How many movies would you like to see? ")) + print( + ", ".join( + json.dumps(movie, indent=4) for movie in get_imdb_top_movies(num_movies) + ) + ) From 433b804f7d1a19403cb0856232f0d23f09d4b4a0 Mon Sep 17 00:00:00 2001 From: Saurabh Suresh Powar <66636289+Spnetic-5@users.noreply.github.com> Date: Sat, 16 Oct 2021 20:02:40 +0530 Subject: [PATCH 1596/2908] Added morphological operations, fixes: #5197 (#5199) * Added morphological operations, fixes: #5197 * Added dilation tests and type hints * Added erosion tests and type hints * fixes: TheAlgorithms#5197 * fixes: TheAlgorithms#5197 * Update erosion_operation.py * made suggested changes in dilation * made suggested changes in erosion * made suggested changes in dilation * removed extra spaces in the tests * removed extra spaces in the tests --- .../dilation_operation.py | 74 +++++++++++++++++++ .../erosion_operation.py | 74 +++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 digital_image_processing/morphological_operations/dilation_operation.py create mode 100644 digital_image_processing/morphological_operations/erosion_operation.py diff --git a/digital_image_processing/morphological_operations/dilation_operation.py b/digital_image_processing/morphological_operations/dilation_operation.py new file mode 100644 index 000000000000..274880b0a50a --- /dev/null +++ b/digital_image_processing/morphological_operations/dilation_operation.py @@ -0,0 +1,74 @@ +import numpy as np +from PIL import Image + + +def rgb2gray(rgb: np.array) -> np.array: + """ + Return gray image from rgb image + >>> rgb2gray(np.array([[[127, 255, 0]]])) + array([[187.6453]]) + >>> rgb2gray(np.array([[[0, 0, 0]]])) + array([[0.]]) + >>> rgb2gray(np.array([[[2, 4, 1]]])) + array([[3.0598]]) + >>> rgb2gray(np.array([[[26, 255, 14], [5, 147, 20], [1, 200, 0]]])) + array([[159.0524, 90.0635, 117.6989]]) + """ + r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2] + return 0.2989 * r + 0.5870 * g + 0.1140 * b + + +def gray2binary(gray: np.array) -> np.array: + """ + Return binary image from gray image + >>> gray2binary(np.array([[127, 255, 0]])) + array([[False, True, False]]) + >>> gray2binary(np.array([[0]])) + array([[False]]) + >>> gray2binary(np.array([[26.2409, 4.9315, 1.4729]])) + array([[False, False, False]]) + >>> gray2binary(np.array([[26, 255, 14], [5, 147, 20], [1, 200, 0]])) + array([[False, True, False], + [False, True, False], + [False, True, False]]) + """ + return (127 < gray) & (gray <= 255) + + +def dilation(image: np.array, kernel: np.array) -> np.array: + """ + Return dilated image + >>> dilation(np.array([[True, False, True]]), np.array([[0, 1, 0]])) + array([[False, False, False]]) + >>> dilation(np.array([[False, False, True]]), np.array([[1, 0, 1]])) + array([[False, False, False]]) + """ + output = np.zeros_like(image) + image_padded = np.zeros( + (image.shape[0] + kernel.shape[0] - 1, image.shape[1] + kernel.shape[1] - 1) + ) + + # Copy image to padded image + image_padded[kernel.shape[0] - 2 : -1 :, kernel.shape[1] - 2 : -1 :] = image + + # Iterate over image & apply kernel + for x in range(image.shape[1]): + for y in range(image.shape[0]): + summation = ( + kernel * image_padded[y : y + kernel.shape[0], x : x + kernel.shape[1]] + ).sum() + output[y, x] = int(summation > 0) + return output + + +# kernel to be applied +structuring_element = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]) + + +if __name__ == "__main__": + # read original image + image = np.array(Image.open(r"..\image_data\lena.jpg")) + output = dilation(gray2binary(rgb2gray(image)), structuring_element) + # Save the output image + pil_img = Image.fromarray(output).convert("RGB") + pil_img.save("result_dilation.png") diff --git a/digital_image_processing/morphological_operations/erosion_operation.py b/digital_image_processing/morphological_operations/erosion_operation.py new file mode 100644 index 000000000000..4b0a5eee8c03 --- /dev/null +++ b/digital_image_processing/morphological_operations/erosion_operation.py @@ -0,0 +1,74 @@ +import numpy as np +from PIL import Image + + +def rgb2gray(rgb: np.array) -> np.array: + """ + Return gray image from rgb image + >>> rgb2gray(np.array([[[127, 255, 0]]])) + array([[187.6453]]) + >>> rgb2gray(np.array([[[0, 0, 0]]])) + array([[0.]]) + >>> rgb2gray(np.array([[[2, 4, 1]]])) + array([[3.0598]]) + >>> rgb2gray(np.array([[[26, 255, 14], [5, 147, 20], [1, 200, 0]]])) + array([[159.0524, 90.0635, 117.6989]]) + """ + r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2] + return 0.2989 * r + 0.5870 * g + 0.1140 * b + + +def gray2binary(gray: np.array) -> np.array: + """ + Return binary image from gray image + >>> gray2binary(np.array([[127, 255, 0]])) + array([[False, True, False]]) + >>> gray2binary(np.array([[0]])) + array([[False]]) + >>> gray2binary(np.array([[26.2409, 4.9315, 1.4729]])) + array([[False, False, False]]) + >>> gray2binary(np.array([[26, 255, 14], [5, 147, 20], [1, 200, 0]])) + array([[False, True, False], + [False, True, False], + [False, True, False]]) + """ + return (127 < gray) & (gray <= 255) + + +def erosion(image: np.array, kernel: np.array) -> np.array: + """ + Return eroded image + >>> erosion(np.array([[True, True, False]]), np.array([[0, 1, 0]])) + array([[False, False, False]]) + >>> erosion(np.array([[True, False, False]]), np.array([[1, 1, 0]])) + array([[False, False, False]]) + """ + output = np.zeros_like(image) + image_padded = np.zeros( + (image.shape[0] + kernel.shape[0] - 1, image.shape[1] + kernel.shape[1] - 1) + ) + + # Copy image to padded image + image_padded[kernel.shape[0] - 2 : -1 :, kernel.shape[1] - 2 : -1 :] = image + + # Iterate over image & apply kernel + for x in range(image.shape[1]): + for y in range(image.shape[0]): + summation = ( + kernel * image_padded[y : y + kernel.shape[0], x : x + kernel.shape[1]] + ).sum() + output[y, x] = int(summation == 5) + return output + + +# kernel to be applied +structuring_element = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]) + +if __name__ == "__main__": + # read original image + image = np.array(Image.open(r"..\image_data\lena.jpg")) + # Apply erosion operation to a binary image + output = erosion(gray2binary(rgb2gray(image)), structuring_element) + # Save the output image + pil_img = Image.fromarray(output).convert("RGB") + pil_img.save("result_erosion.png") From 8dc7cdbc57e0a51c7ef0d6a5974d990c5f27cf4a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 16 Oct 2021 18:57:38 +0200 Subject: [PATCH 1597/2908] Add tests to morse_code.py (#5337) * Add tests to morse_code.py @dhruvmanila @poyea Your reviews, please. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 7 +- ciphers/morse_code.py | 58 +++++++++++++++++ ciphers/morse_code_implementation.py | 97 ---------------------------- 3 files changed, 64 insertions(+), 98 deletions(-) create mode 100644 ciphers/morse_code.py delete mode 100644 ciphers/morse_code_implementation.py diff --git a/DIRECTORY.md b/DIRECTORY.md index d92dccca5afb..219877a3aee8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -53,6 +53,7 @@ * [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) + * [Baconian Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/baconian_cipher.py) * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) * [Base64 Encoding](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_encoding.py) @@ -70,7 +71,7 @@ * [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) + * [Morse Code](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code.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) @@ -328,10 +329,12 @@ * [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) + * [Greedy Min Vertex Cover](https://github.com/TheAlgorithms/Python/blob/master/graphs/greedy_min_vertex_cover.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) * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/graphs/markov_chain.py) + * [Matching Min Vertex Cover](https://github.com/TheAlgorithms/Python/blob/master/graphs/matching_min_vertex_cover.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) @@ -497,6 +500,7 @@ * [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) * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/maths/primelib.py) + * [Proth Number](https://github.com/TheAlgorithms/Python/blob/master/maths/proth_number.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) @@ -951,6 +955,7 @@ * [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) + * [Download Images From Google Query](https://github.com/TheAlgorithms/Python/blob/master/web_programming/download_images_from_google_query.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/ciphers/morse_code.py b/ciphers/morse_code.py new file mode 100644 index 000000000000..0370c26fe4a6 --- /dev/null +++ b/ciphers/morse_code.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +""" +Python program to translate to and from Morse code. + +https://en.wikipedia.org/wiki/Morse_code +""" + +# fmt: off +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": "-----", "&": ".-...", "@": ".--.-.", + ":": "---...", ",": "--..--", ".": ".-.-.-", "'": ".----.", '"': ".-..-.", + "?": "..--..", "/": "-..-.", "=": "-...-", "+": ".-.-.", "-": "-....-", + "(": "-.--.", ")": "-.--.-", "!": "-.-.--", " ": "/" +} # Exclamation mark is not in ITU-R recommendation +# fmt: on +REVERSE_DICT = {value: key for key, value in MORSE_CODE_DICT.items()} + + +def encrypt(message: str) -> str: + """ + >>> encrypt("Sos!") + '... --- ... -.-.--' + >>> encrypt("SOS!") == encrypt("sos!") + True + """ + return " ".join(MORSE_CODE_DICT[char] for char in message.upper()) + + +def decrypt(message: str) -> str: + """ + >>> decrypt('... --- ... -.-.--') + 'SOS!' + """ + return "".join(REVERSE_DICT[char] for char in message.split()) + + +def main() -> None: + """ + >>> s = "".join(MORSE_CODE_DICT) + >>> decrypt(encrypt(s)) == s + True + """ + message = "Morse code here!" + print(message) + message = encrypt(message) + print(message) + message = decrypt(message) + print(message) + + +if __name__ == "__main__": + main() diff --git a/ciphers/morse_code_implementation.py b/ciphers/morse_code_implementation.py deleted file mode 100644 index eec4183fa56e..000000000000 --- a/ciphers/morse_code_implementation.py +++ /dev/null @@ -1,97 +0,0 @@ -# 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": "-----", - "&": ".-...", - "@": ".--.-.", - ":": "---...", - ",": "--..--", - ".": ".-.-.-", - "'": ".----.", - '"': ".-..-.", - "?": "..--..", - "/": "-..-.", - "=": "-...-", - "+": ".-.-.", - "-": "-....-", - "(": "-.--.", - ")": "-.--.-", - # Exclamation mark is not in ITU-R recommendation - "!": "-.-.--", -} - - -def encrypt(message: str) -> str: - cipher = "" - for letter in message: - if letter != " ": - cipher += MORSE_CODE_DICT[letter] + " " - else: - cipher += "/ " - - # Remove trailing space added on line 64 - return cipher[:-1] - - -def decrypt(message: str) -> str: - decipher = "" - letters = message.split(" ") - for letter in letters: - if letter != "/": - decipher += list(MORSE_CODE_DICT.keys())[ - list(MORSE_CODE_DICT.values()).index(letter) - ] - else: - decipher += " " - - return decipher - - -def main() -> None: - message = "Morse code here" - result = encrypt(message.upper()) - print(result) - - message = result - result = decrypt(message) - print(result) - - -if __name__ == "__main__": - main() From 7ef2e4d1d06a0d7497bb7cbca0cad32fc1d9c6cd Mon Sep 17 00:00:00 2001 From: Srishtik Bhandarkar <53395406+srishtik2310@users.noreply.github.com> Date: Sat, 16 Oct 2021 22:38:41 +0530 Subject: [PATCH 1598/2908] Add Project Euler Problem 092 (#5091) * adde solution to problem 092 * added solution to problem 092 * fixed the pre-comit shebang issue --- project_euler/problem_092/__init__.py | 0 project_euler/problem_092/sol1.py | 93 +++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 project_euler/problem_092/__init__.py create mode 100644 project_euler/problem_092/sol1.py diff --git a/project_euler/problem_092/__init__.py b/project_euler/problem_092/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_092/sol1.py b/project_euler/problem_092/sol1.py new file mode 100644 index 000000000000..a02629a7bc7d --- /dev/null +++ b/project_euler/problem_092/sol1.py @@ -0,0 +1,93 @@ +""" +Project Euler Problem 092: https://projecteuler.net/problem=92 +Square digit chains +A number chain is created by continuously adding the square of the digits in +a number to form a new number until it has been seen before. +For example, +44 → 32 → 13 → 10 → 1 → 1 +85 → 89 → 145 → 42 → 20 → 4 → 16 → 37 → 58 → 89 +Therefore any chain that arrives at 1 or 89 will become stuck in an endless loop. +What is most amazing is that EVERY starting number will eventually arrive at 1 or 89. +How many starting numbers below ten million will arrive at 89? +""" + + +def next_number(number: int) -> int: + """ + Returns the next number of the chain by adding the square of each digit + to form a neww number. + For example if number = 12, next_number() will return 1^2 + 2^2 = 5. + Therefore 5 is the next number of the chain. + >>> next_number(44) + 32 + >>> next_number(10) + 1 + >>> next_number(32) + 13 + """ + num = 0 + for i in range(len(str(number))): + num += int(str(number)[i]) ** 2 + + return num + + +def chain(number: int) -> bool: + """ + Generates the chain of numbers until the nest number generated is 1 0r 89. + for example, if starting number is 44, then the function generates the + following chain of numbers. + chain: 44 → 32 → 13 → 10 → 1 → 1 + once the next number generated is 1 or 89, the function + Returns True if the next number generated by next_number() if 1. + Returns False if the next number generated by next_number() is 89. + >>> chain(10) + True + >>> chain(58) + False + >>> chain(1) + True + """ + while number != 1 and number != 89: + number = next_number(number) + + if number == 1: + return True + + elif number == 89: + return False + + +def solution(number: int = 10000000) -> int: + """ + The function returns the total numbers that end up in 89 after the chain generation. + The function accepts a range number and the function checks all the values + under value number. + if the chain generation leads to the end number as 1 or 89. If the chain() + returns True, then total is incremented, implying that the number we + started with ended up with 1 else total2 is incremented, implying that + the number we started with ended up in 89 after chain generation. + But the function returns total2 as the requirement of question is + to find out how many ended up in 89. + + >>> solution(100) + 80 + >>> solution(10000000) + 8581146 + """ + total = 0 + total2 = 0 + for i in range(1, number): + val = chain(i) + + if val is True: + total += 1 + + elif val is False: + total2 += 1 + + return total2 + + +if __name__ == "__main__": + print(f"{solution() = }") From 4bf2eedd3c234eddd04fbea314005ca06d32e923 Mon Sep 17 00:00:00 2001 From: John Law Date: Sun, 17 Oct 2021 14:07:45 +0800 Subject: [PATCH 1599/2908] [mypy] fix mypy error in Project Euler Problem 092 solution 1 (#5357) * fix mypy error * updating DIRECTORY.md * simplify code * run black * fix doc consistency * Fix doc * fix doc Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 +++ project_euler/problem_092/sol1.py | 53 +++++++++++-------------------- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 219877a3aee8..c197dd88032e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -211,6 +211,9 @@ * 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) + * Morphological Operations + * [Dilation Operation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/morphological_operations/dilation_operation.py) + * [Erosion Operation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/morphological_operations/erosion_operation.py) * Resize * [Resize](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/resize/resize.py) * Rotation @@ -778,6 +781,8 @@ * [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 092 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_092/sol1.py) * Problem 097 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_097/sol1.py) * Problem 099 diff --git a/project_euler/problem_092/sol1.py b/project_euler/problem_092/sol1.py index a02629a7bc7d..dcda3a48679e 100644 --- a/project_euler/problem_092/sol1.py +++ b/project_euler/problem_092/sol1.py @@ -17,7 +17,7 @@ def next_number(number: int) -> int: Returns the next number of the chain by adding the square of each digit to form a neww number. For example if number = 12, next_number() will return 1^2 + 2^2 = 5. - Therefore 5 is the next number of the chain. + Therefore, 5 is the next number of the chain. >>> next_number(44) 32 >>> next_number(10) @@ -25,22 +25,22 @@ def next_number(number: int) -> int: >>> next_number(32) 13 """ - num = 0 - for i in range(len(str(number))): - num += int(str(number)[i]) ** 2 + sum_of_digits_squared = 0 + while number: + sum_of_digits_squared += (number % 10) ** 2 + number //= 10 - return num + return sum_of_digits_squared def chain(number: int) -> bool: """ - Generates the chain of numbers until the nest number generated is 1 0r 89. - for example, if starting number is 44, then the function generates the - following chain of numbers. - chain: 44 → 32 → 13 → 10 → 1 → 1 - once the next number generated is 1 or 89, the function - Returns True if the next number generated by next_number() if 1. - Returns False if the next number generated by next_number() is 89. + The function generates the chain of numbers until the next number is 1 or 89. + For example, if starting number is 44, then the function generates the + following chain of numbers: + 44 → 32 → 13 → 10 → 1 → 1. + Once the next number generated is 1 or 89, the function returns whether + or not the the next number generated by next_number() is 1. >>> chain(10) True >>> chain(58) @@ -51,43 +51,26 @@ def chain(number: int) -> bool: while number != 1 and number != 89: number = next_number(number) - if number == 1: - return True - - elif number == 89: - return False + return number == 1 def solution(number: int = 10000000) -> int: """ - The function returns the total numbers that end up in 89 after the chain generation. + The function returns the number of integers that end up being 89 in each chain. The function accepts a range number and the function checks all the values under value number. - if the chain generation leads to the end number as 1 or 89. If the chain() - returns True, then total is incremented, implying that the number we - started with ended up with 1 else total2 is incremented, implying that - the number we started with ended up in 89 after chain generation. - But the function returns total2 as the requirement of question is - to find out how many ended up in 89. >>> solution(100) 80 >>> solution(10000000) 8581146 """ - total = 0 - total2 = 0 - for i in range(1, number): - val = chain(i) - - if val is True: - total += 1 + return sum(1 for i in range(1, number) if not chain(i)) - elif val is False: - total2 += 1 - return total2 +if __name__ == "__main__": + import doctest + doctest.testmod() -if __name__ == "__main__": print(f"{solution() = }") From 08d4d226d797c94254f53be0c71d3304ea093bd4 Mon Sep 17 00:00:00 2001 From: Meysam Date: Sun, 17 Oct 2021 19:26:12 +0300 Subject: [PATCH 1600/2908] [mypy] Fix type annotations for graphs/boruvka (#4867) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: type annotations for pypi 🏷️ Fixes #4052 * updating DIRECTORY.md * 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 --- graphs/boruvka.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graphs/boruvka.py b/graphs/boruvka.py index 3fa5c6fd2a26..eea0b0009941 100644 --- a/graphs/boruvka.py +++ b/graphs/boruvka.py @@ -24,6 +24,7 @@ Details: https://en.wikipedia.org/wiki/Bor%C5%AFvka%27s_algorithm """ +from __future__ import annotations class Graph: @@ -39,8 +40,8 @@ def __init__(self, num_of_nodes: int) -> None: """ self.m_num_of_nodes = num_of_nodes - self.m_edges = [] - self.m_component = {} + self.m_edges: list[list[int]] = [] + self.m_component: dict[int, int] = {} def add_edge(self, u_node: int, v_node: int, weight: int) -> None: """Adds an edge in the format [first, second, edge weight] to graph.""" @@ -83,7 +84,7 @@ def boruvka(self) -> None: component_size = [] mst_weight = 0 - minimum_weight_edge = [-1] * self.m_num_of_nodes + minimum_weight_edge: list[int] = [-1] * self.m_num_of_nodes # A list of components (initialized to all of the nodes) for node in range(self.m_num_of_nodes): From 1e64bf4600e933e820701a769a453ee379c8ea2c Mon Sep 17 00:00:00 2001 From: Atharva Deshpande Date: Mon, 18 Oct 2021 10:16:23 +0530 Subject: [PATCH 1601/2908] Re-organize math/series (#5044) * added harmonic mean * Update maths/series/harmonic_mean.py Updated the write-up of reference given in the code. Co-authored-by: John Law * changes in arithmetic and geometric mean code * mean and series added in a single file Co-authored-by: John Law --- .../{arithmetic_mean.py => arithmetic.py} | 25 +++-- .../{geometric_mean.py => geometric.py} | 30 +++--- maths/series/harmonic.py | 92 +++++++++++++++++++ 3 files changed, 129 insertions(+), 18 deletions(-) rename maths/series/{arithmetic_mean.py => arithmetic.py} (68%) rename maths/series/{geometric_mean.py => geometric.py} (75%) create mode 100644 maths/series/harmonic.py diff --git a/maths/series/arithmetic_mean.py b/maths/series/arithmetic.py similarity index 68% rename from maths/series/arithmetic_mean.py rename to maths/series/arithmetic.py index b5d64b63ac3f..dc28c5c7bc5f 100644 --- a/maths/series/arithmetic_mean.py +++ b/maths/series/arithmetic.py @@ -1,20 +1,35 @@ """ -ARITHMETIC MEAN : https://en.wikipedia.org/wiki/Arithmetic_mean +Arithmetic mean +Reference: https://en.wikipedia.org/wiki/Arithmetic_mean +Arithmetic series +Reference: https://en.wikipedia.org/wiki/Arithmetic_series +(The URL above will redirect you to arithmetic progression) """ def is_arithmetic_series(series: list) -> bool: """ checking whether the input series is arithmetic series or not - >>> is_arithmetic_series([2, 4, 6]) True >>> is_arithmetic_series([3, 6, 12, 24]) False >>> is_arithmetic_series([1, 2, 3]) True + >>> is_arithmetic_series(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 6] + >>> is_arithmetic_series([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 6]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") if len(series) == 1: return True common_diff = series[1] - series[0] @@ -37,9 +52,7 @@ def arithmetic_mean(series: list) -> float: ... ValueError: Input series is not valid, valid series - [2, 4, 6] >>> arithmetic_mean([4, 8, 1]) - Traceback (most recent call last): - ... - ValueError: Input list is not an arithmetic series + 4.333333333333333 >>> arithmetic_mean([1, 2, 3]) 2.0 >>> arithmetic_mean([]) @@ -52,8 +65,6 @@ def arithmetic_mean(series: list) -> float: raise ValueError("Input series is not valid, valid series - [2, 4, 6]") if len(series) == 0: raise ValueError("Input list must be a non empty list") - if not is_arithmetic_series(series): - raise ValueError("Input list is not an arithmetic series") answer = 0 for val in series: answer += val diff --git a/maths/series/geometric_mean.py b/maths/series/geometric.py similarity index 75% rename from maths/series/geometric_mean.py rename to maths/series/geometric.py index 50ae54ad6574..7b6239b1585d 100644 --- a/maths/series/geometric_mean.py +++ b/maths/series/geometric.py @@ -1,12 +1,15 @@ """ -GEOMETRIC MEAN : https://en.wikipedia.org/wiki/Geometric_mean +Geometric Mean +Reference : https://en.wikipedia.org/wiki/Geometric_mean + +Geometric series +Reference: https://en.wikipedia.org/wiki/Geometric_series """ def is_geometric_series(series: list) -> bool: """ checking whether the input series is geometric series or not - >>> is_geometric_series([2, 4, 8]) True >>> is_geometric_series([3, 6, 12, 24]) @@ -15,8 +18,19 @@ def is_geometric_series(series: list) -> bool: False >>> is_geometric_series([0, 0, 3]) False - + >>> is_geometric_series([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + >>> is_geometric_series(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 8] """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 8]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") if len(series) == 1: return True try: @@ -44,13 +58,9 @@ def geometric_mean(series: list) -> float: ... ValueError: Input series is not valid, valid series - [2, 4, 8] >>> geometric_mean([1, 2, 3]) - Traceback (most recent call last): - ... - ValueError: Input list is not a geometric series + 1.8171205928321397 >>> geometric_mean([0, 2, 3]) - Traceback (most recent call last): - ... - ValueError: Input list is not a geometric series + 0.0 >>> geometric_mean([]) Traceback (most recent call last): ... @@ -61,8 +71,6 @@ def geometric_mean(series: list) -> float: raise ValueError("Input series is not valid, valid series - [2, 4, 8]") if len(series) == 0: raise ValueError("Input list must be a non empty list") - if not is_geometric_series(series): - raise ValueError("Input list is not a geometric series") answer = 1 for value in series: answer *= value diff --git a/maths/series/harmonic.py b/maths/series/harmonic.py new file mode 100644 index 000000000000..50f29c93dd5f --- /dev/null +++ b/maths/series/harmonic.py @@ -0,0 +1,92 @@ +""" +Harmonic mean +Reference: https://en.wikipedia.org/wiki/Harmonic_mean + +Harmonic series +Reference: https://en.wikipedia.org/wiki/Harmonic_series(mathematics) +""" + + +def is_harmonic_series(series: list) -> bool: + """ + checking whether the input series is arithmetic series or not + >>> is_harmonic_series([ 1, 2/3, 1/2, 2/5, 1/3]) + True + >>> is_harmonic_series([ 1, 2/3, 2/5, 1/3]) + False + >>> is_harmonic_series([1, 2, 3]) + False + >>> is_harmonic_series([1/2, 1/3, 1/4]) + True + >>> is_harmonic_series([2/5, 2/10, 2/15, 2/20, 2/25]) + True + >>> is_harmonic_series(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [1, 2/3, 2] + >>> is_harmonic_series([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + >>> is_harmonic_series([0]) + Traceback (most recent call last): + ... + ValueError: Input series cannot have 0 as an element + >>> is_harmonic_series([1,2,0,6]) + Traceback (most recent call last): + ... + ValueError: Input series cannot have 0 as an element + """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [1, 2/3, 2]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") + if len(series) == 1 and series[0] != 0: + return True + rec_series = [] + series_len = len(series) + for i in range(0, series_len): + if series[i] == 0: + raise ValueError("Input series cannot have 0 as an element") + rec_series.append(1 / series[i]) + common_diff = rec_series[1] - rec_series[0] + for index in range(2, series_len): + if rec_series[index] - rec_series[index - 1] != common_diff: + return False + return True + + +def harmonic_mean(series: list) -> float: + """ + return the harmonic mean of series + + >>> harmonic_mean([1, 4, 4]) + 2.0 + >>> harmonic_mean([3, 6, 9, 12]) + 5.759999999999999 + >>> harmonic_mean(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 6] + >>> harmonic_mean([1, 2, 3]) + 1.6363636363636365 + >>> harmonic_mean([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + + """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 6]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") + answer = 0 + for val in series: + answer += 1 / val + return len(series) / answer + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0935ab0cb2c3634b553406eefabb5f97011f2e84 Mon Sep 17 00:00:00 2001 From: Jaydeep Das Date: Mon, 18 Oct 2021 12:46:42 +0530 Subject: [PATCH 1602/2908] Added giphy.py to fetch gifs on a given topic (#5378) * Added giphy.py to fetch gifs on a given topic * Modified code [*]Added doctest [*]Formatted with black * Minor change * Minor refactoring to avoid name clash * Made necessary changes as per review * Update web_programming/giphy.py Co-authored-by: Christian Clauss * Apply suggestions from code review * Final cleanup * Placate psf/black Co-authored-by: Christian Clauss --- web_programming/giphy.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 web_programming/giphy.py diff --git a/web_programming/giphy.py b/web_programming/giphy.py new file mode 100644 index 000000000000..dc8c6be08caa --- /dev/null +++ b/web_programming/giphy.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +import requests + +giphy_api_key = "YOUR API KEY" +# Can be fetched from https://developers.giphy.com/dashboard/ + + +def get_gifs(query: str, api_key: str = giphy_api_key) -> list: + """ + Get a list of URLs of GIFs based on a given query.. + """ + formatted_query = "+".join(query.split()) + url = f"http://api.giphy.com/v1/gifs/search?q={formatted_query}&api_key={api_key}" + gifs = requests.get(url).json()["data"] + return [gif["url"] for gif in gifs] + + +if __name__ == "__main__": + print("\n".join(get_gifs("space ship"))) From fa88559cab4aa2e935df97b8e2710b34402fc10f Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <89947037+Rohanrbharadwaj@users.noreply.github.com> Date: Mon, 18 Oct 2021 19:05:35 +0530 Subject: [PATCH 1603/2908] Create join.py (#5363) * Create join.py Because we have a split.py * Update join.py * Update join.py * Update join.py * Update join.py * Update join.py * Update strings/join.py Co-authored-by: John Law * Update join.py * Update join.py * Update join.py * Update join.py Co-authored-by: John Law --- strings/join.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 strings/join.py diff --git a/strings/join.py b/strings/join.py new file mode 100644 index 000000000000..0cb88b76065d --- /dev/null +++ b/strings/join.py @@ -0,0 +1,32 @@ +""" +Program to join a list of strings with a given separator +""" + + +def join(separator: str, separated: list) -> str: + """ + >>> join("", ["a", "b", "c", "d"]) + 'abcd' + >>> join("#", ["a", "b", "c", "d"]) + 'a#b#c#d' + >>> join("#", "a") + 'a' + >>> join(" ", ["You", "are", "amazing!"]) + 'You are amazing!' + >>> join("#", ["a", "b", "c", 1]) + Traceback (most recent call last): + ... + Exception: join() accepts only strings to be joined + """ + joined = "" + for word_or_phrase in separated: + if not isinstance(word_or_phrase, str): + raise Exception("join() accepts only strings to be joined") + joined += word_or_phrase + separator + return joined.strip(separator) + + +if "__name__" == "__main__": + from doctest import testmod + + testmod() From 4af521504227c4cada677538163033779cd4df07 Mon Sep 17 00:00:00 2001 From: iradonov <86876427+iradonov@users.noreply.github.com> Date: Mon, 18 Oct 2021 19:46:47 +0300 Subject: [PATCH 1604/2908] added Schur complement to linear algebra (#4793) * added schur complement and tests to linear algebra * updated according to checklist * updated variable names and typing * added two testcases for input validation * fixed import order Co-authored-by: Ivan Radonov --- linear_algebra/src/schur_complement.py | 94 ++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 linear_algebra/src/schur_complement.py diff --git a/linear_algebra/src/schur_complement.py b/linear_algebra/src/schur_complement.py new file mode 100644 index 000000000000..f3cb736d9084 --- /dev/null +++ b/linear_algebra/src/schur_complement.py @@ -0,0 +1,94 @@ +import unittest + +import numpy as np + + +def schur_complement( + mat_a: np.ndarray, + mat_b: np.ndarray, + mat_c: np.ndarray, + pseudo_inv: np.ndarray = None, +) -> np.ndarray: + """ + Schur complement of a symmetric matrix X given as a 2x2 block matrix + consisting of matrices A, B and C. + Matrix A must be quadratic and non-singular. + In case A is singular, a pseudo-inverse may be provided using + the pseudo_inv argument. + + Link to Wiki: https://en.wikipedia.org/wiki/Schur_complement + See also Convex Optimization – Boyd and Vandenberghe, A.5.5 + >>> import numpy as np + >>> a = np.array([[1, 2], [2, 1]]) + >>> b = np.array([[0, 3], [3, 0]]) + >>> c = np.array([[2, 1], [6, 3]]) + >>> schur_complement(a, b, c) + array([[ 5., -5.], + [ 0., 6.]]) + """ + shape_a = np.shape(mat_a) + shape_b = np.shape(mat_b) + shape_c = np.shape(mat_c) + + if shape_a[0] != shape_b[0]: + raise ValueError( + f"Expected the same number of rows for A and B. \ + Instead found A of size {shape_a} and B of size {shape_b}" + ) + + if shape_b[1] != shape_c[1]: + raise ValueError( + f"Expected the same number of columns for B and C. \ + Instead found B of size {shape_b} and C of size {shape_c}" + ) + + a_inv = pseudo_inv + if a_inv is None: + try: + a_inv = np.linalg.inv(mat_a) + except np.linalg.LinAlgError: + raise ValueError( + "Input matrix A is not invertible. Cannot compute Schur complement." + ) + + return mat_c - mat_b.T @ a_inv @ mat_b + + +class TestSchurComplement(unittest.TestCase): + def test_schur_complement(self) -> None: + a = np.array([[1, 2, 1], [2, 1, 2], [3, 2, 4]]) + b = np.array([[0, 3], [3, 0], [2, 3]]) + c = np.array([[2, 1], [6, 3]]) + + s = schur_complement(a, b, c) + + input_matrix = np.block([[a, b], [b.T, c]]) + + det_x = np.linalg.det(input_matrix) + det_a = np.linalg.det(a) + det_s = np.linalg.det(s) + + self.assertAlmostEqual(det_x, det_a * det_s) + + def test_improper_a_b_dimensions(self) -> None: + a = np.array([[1, 2, 1], [2, 1, 2], [3, 2, 4]]) + b = np.array([[0, 3], [3, 0], [2, 3]]) + c = np.array([[2, 1], [6, 3]]) + + with self.assertRaises(ValueError): + schur_complement(a, b, c) + + def test_improper_b_c_dimensions(self) -> None: + a = np.array([[1, 2, 1], [2, 1, 2], [3, 2, 4]]) + b = np.array([[0, 3], [3, 0], [2, 3]]) + c = np.array([[2, 1, 3], [6, 3, 5]]) + + with self.assertRaises(ValueError): + schur_complement(a, b, c) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + unittest.main() From d28463c75df4dbaee795c99c05a4021d4cc5e386 Mon Sep 17 00:00:00 2001 From: Jainendra Mandavi Date: Mon, 18 Oct 2021 22:23:10 +0530 Subject: [PATCH 1605/2908] Create count_1s_brian_kernighan_method (#5385) Ref - http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan --- .../count_1s_brian_kernighan_method.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 bit_manipulation/count_1s_brian_kernighan_method.py diff --git a/bit_manipulation/count_1s_brian_kernighan_method.py b/bit_manipulation/count_1s_brian_kernighan_method.py new file mode 100644 index 000000000000..d217af90b3d9 --- /dev/null +++ b/bit_manipulation/count_1s_brian_kernighan_method.py @@ -0,0 +1,43 @@ +def get_1s_count(number: int) -> int: + """ + Count the number of set bits in a 32 bit integer using Brian Kernighan's way. + Ref - http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan + >>> get_1s_count(25) + 3 + >>> get_1s_count(37) + 3 + >>> get_1s_count(21) + 3 + >>> get_1s_count(58) + 4 + >>> get_1s_count(0) + 0 + >>> get_1s_count(256) + 1 + >>> get_1s_count(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must be positive + >>> get_1s_count(0.8) + Traceback (most recent call last): + ... + TypeError: Input value must be an 'int' type + """ + if number < 0: + raise ValueError("the value of input must be positive") + elif isinstance(number, float): + raise TypeError("Input value must be an 'int' type") + count = 0 + while number: + # This way we arrive at next set bit (next 1) instead of looping + # through each bit and checking for 1s hence the + # loop won't run 32 times it will only run the number of `1` times + number &= number - 1 + count += 1 + return count + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 66c96aa0378eca8bc2c39ca6ae2204096df4c728 Mon Sep 17 00:00:00 2001 From: Sabari Ganesh <64348740+SabariGanesh-K@users.noreply.github.com> Date: Tue, 19 Oct 2021 10:56:03 +0530 Subject: [PATCH 1606/2908] Added length unit conversions (#5373) * Added length unit conversions Conversion of length units were added with respective tests being implemented and passed. Available Units:- Metre,Kilometre,Feet,Inch,Centimeter,Yard,Foot,Mile,Millimeter * Formatted File File was formatted to go as per repo rules * Reformatted file * Reformatted code once again * Added more test Added test to evaluate whether the code handles wrong arguements passed * Update length_conversions.py * Update length_conversions.py * Update length_conversions.py * Update length_conversions.py * Update length_conversions.py * Update length_conversions.py * Update length_conversions.py * Fixed Minor errors in test One of the test was failing and it was fixed Co-authored-by: Christian Clauss --- conversions/length_conversions.py | 108 ++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 conversions/length_conversions.py diff --git a/conversions/length_conversions.py b/conversions/length_conversions.py new file mode 100644 index 000000000000..811a9a916b70 --- /dev/null +++ b/conversions/length_conversions.py @@ -0,0 +1,108 @@ +""" +Conversion of length units. +Available Units:- Metre,Kilometre,Feet,Inch,Centimeter,Yard,Foot,Mile,Millimeter + +USAGE : +-> Import this file into their respective project. +-> Use the function length_conversion() for conversion of length units. +-> Parameters : + -> value : The number of from units you want to convert + -> from_type : From which type you want to convert + -> to_type : To which type you want to convert + +REFERENCES : +-> Wikipedia reference: https://en.wikipedia.org/wiki/Meter +-> Wikipedia reference: https://en.wikipedia.org/wiki/Kilometer +-> Wikipedia reference: https://en.wikipedia.org/wiki/Feet +-> Wikipedia reference: https://en.wikipedia.org/wiki/Inch +-> Wikipedia reference: https://en.wikipedia.org/wiki/Centimeter +-> Wikipedia reference: https://en.wikipedia.org/wiki/Yard +-> Wikipedia reference: https://en.wikipedia.org/wiki/Foot +-> Wikipedia reference: https://en.wikipedia.org/wiki/Mile +-> Wikipedia reference: https://en.wikipedia.org/wiki/Millimeter +""" + +from collections import namedtuple + +from_to = namedtuple("from_to", "from_ to") + +METRIC_CONVERSION = { + "meter": from_to(1, 1), + "kilometer": from_to(1000, 0.001), + "feet": from_to(0.3048, 3.28084), + "inch": from_to(0.0254, 39.3701), + "centimeter": from_to(0.01, 100), + "yard": from_to(0.9144, 1.09361), + "foot": from_to(0.3048, 3.28084), + "mile": from_to(1609.34, 0.000621371), + "millimeter": from_to(0.001, 1000), +} + + +def length_conversion(value: float, from_type: str, to_type: str) -> float: + """ + Conversion between length units. + + >>> length_conversion(4, "meter", "feet") + 13.12336 + >>> length_conversion(1, "meter", "kilometer") + 0.001 + >>> length_conversion(1, "kilometer", "inch") + 39370.1 + >>> length_conversion(3, "kilometer", "mile") + 1.8641130000000001 + >>> length_conversion(2, "feet", "meter") + 0.6096 + >>> length_conversion(4, "feet", "yard") + 1.333329312 + >>> length_conversion(1, "inch", "meter") + 0.0254 + >>> length_conversion(2, "inch", "mile") + 3.15656468e-05 + >>> length_conversion(2, "centimeter", "millimeter") + 20.0 + >>> length_conversion(2, "centimeter", "yard") + 0.0218722 + >>> length_conversion(4, "yard", "meter") + 3.6576 + >>> length_conversion(4, "yard", "kilometer") + 0.0036576 + >>> length_conversion(3, "foot", "meter") + 0.9144000000000001 + >>> length_conversion(3, "foot", "inch") + 36.00001944 + >>> length_conversion(4, "mile", "kilometer") + 6.43736 + >>> length_conversion(2, "mile", "inch") + 126719.753468 + >>> length_conversion(3, "millimeter", "centimeter") + 0.3 + >>> length_conversion(3, "millimeter", "inch") + 0.1181103 + >>> length_conversion(4, "wrongUnit", "inch") + Traceback (most recent call last): + File "/usr/lib/python3.8/doctest.py", line 1336, in __run + exec(compile(example.source, filename, "single", + File "", line 1, in + length_conversion(4, "wrongUnit", "inch") + File "", line 85, in length_conversion + ValueError: Invalid 'from_type' value: 'wrongUnit' Supported values are: + meter, kilometer, feet, inch, centimeter, yard, foot, mile, millimeter + """ + if from_type not in METRIC_CONVERSION: + raise ValueError( + f"Invalid 'from_type' value: {from_type!r} Supported values are:\n" + + ", ".join(METRIC_CONVERSION) + ) + if to_type not in METRIC_CONVERSION: + raise ValueError( + f"Invalid 'to_type' value: {to_type!r}. Supported values are:\n" + + ", ".join(METRIC_CONVERSION) + ) + return value * METRIC_CONVERSION[from_type].from_ * METRIC_CONVERSION[to_type].to + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From aa0ace4df7dfff3a70685466e96c51dd264e5083 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 19 Oct 2021 08:05:20 +0200 Subject: [PATCH 1607/2908] Remove exception detail from doctest (#5430) * Remove exception detail from doctest These details are configuration dependant so should be removed according to https://docs.python.org/3/library/doctest.html * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 10 ++++++++-- conversions/length_conversions.py | 6 +----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index c197dd88032e..cf187a0db8a2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -32,6 +32,7 @@ * [Binary Shifts](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_shifts.py) * [Binary Twos Complement](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_twos_complement.py) * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) + * [Count 1S Brian Kernighan Method](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_1s_brian_kernighan_method.py) * [Count Number Of One Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_number_of_one_bits.py) * [Reverse Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py) @@ -112,6 +113,7 @@ * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) * [Hex To Bin](https://github.com/TheAlgorithms/Python/blob/master/conversions/hex_to_bin.py) * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/hexadecimal_to_decimal.py) + * [Length Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/length_conversions.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) @@ -381,6 +383,7 @@ * [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) + * [Schur Complement](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/schur_complement.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) @@ -513,9 +516,10 @@ * [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 - * [Arithmetic Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/arithmetic_mean.py) - * [Geometric Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_mean.py) + * [Arithmetic](https://github.com/TheAlgorithms/Python/blob/master/maths/series/arithmetic.py) + * [Geometric](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric.py) * [Geometric Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_series.py) + * [Harmonic](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic.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) @@ -933,6 +937,7 @@ * [Indian Phone Validator](https://github.com/TheAlgorithms/Python/blob/master/strings/indian_phone_validator.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) + * [Join](https://github.com/TheAlgorithms/Python/blob/master/strings/join.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) @@ -967,6 +972,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) + * [Giphy](https://github.com/TheAlgorithms/Python/blob/master/web_programming/giphy.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) diff --git a/conversions/length_conversions.py b/conversions/length_conversions.py index 811a9a916b70..8d0d3a424260 100644 --- a/conversions/length_conversions.py +++ b/conversions/length_conversions.py @@ -81,11 +81,7 @@ def length_conversion(value: float, from_type: str, to_type: str) -> float: 0.1181103 >>> length_conversion(4, "wrongUnit", "inch") Traceback (most recent call last): - File "/usr/lib/python3.8/doctest.py", line 1336, in __run - exec(compile(example.source, filename, "single", - File "", line 1, in - length_conversion(4, "wrongUnit", "inch") - File "", line 85, in length_conversion + ... ValueError: Invalid 'from_type' value: 'wrongUnit' Supported values are: meter, kilometer, feet, inch, centimeter, yard, foot, mile, millimeter """ From 4880931c2451d00a6b9b830f84b23af847b276a0 Mon Sep 17 00:00:00 2001 From: Vinicius Cordeiro <78505368+cordeirossauro@users.noreply.github.com> Date: Tue, 19 Oct 2021 03:37:51 -0300 Subject: [PATCH 1608/2908] Add Polybius cipher (#5409) * Add polybius cipher * Fix polybius.py build issues and add test --- ciphers/polybius.py | 96 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 ciphers/polybius.py diff --git a/ciphers/polybius.py b/ciphers/polybius.py new file mode 100644 index 000000000000..9e1dc4cbb5a8 --- /dev/null +++ b/ciphers/polybius.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +""" +A Polybius Square is a table that allows someone to translate letters into numbers. + +https://www.braingle.com/brainteasers/codes/polybius.php +""" + +import numpy as np + + +class PolybiusCipher: + def __init__(self) -> None: + SQUARE = [ + ["a", "b", "c", "d", "e"], + ["f", "g", "h", "i", "k"], + ["l", "m", "n", "o", "p"], + ["q", "r", "s", "t", "u"], + ["v", "w", "x", "y", "z"], + ] + self.SQUARE = np.array(SQUARE) + + def letter_to_numbers(self, letter: str) -> np.ndarray: + """ + Return the pair of numbers that represents the given letter in the + polybius square + >>> np.array_equal(PolybiusCipher().letter_to_numbers('a'), [1,1]) + True + + >>> np.array_equal(PolybiusCipher().letter_to_numbers('u'), [4,5]) + True + """ + index1, index2 = np.where(self.SQUARE == letter) + indexes = np.concatenate([index1 + 1, index2 + 1]) + return indexes + + def numbers_to_letter(self, index1: int, index2: int) -> str: + """ + Return the letter corresponding to the position [index1, index2] in + the polybius square + + >>> PolybiusCipher().numbers_to_letter(4, 5) == "u" + True + + >>> PolybiusCipher().numbers_to_letter(1, 1) == "a" + True + """ + letter = self.SQUARE[index1 - 1, index2 - 1] + return letter + + def encode(self, message: str) -> str: + """ + Return the encoded version of message according to the polybius cipher + + >>> PolybiusCipher().encode("test message") == "44154344 32154343112215" + True + + >>> PolybiusCipher().encode("Test Message") == "44154344 32154343112215" + True + """ + message = message.lower() + message = message.replace("j", "i") + + encoded_message = "" + for letter_index in range(len(message)): + if message[letter_index] != " ": + numbers = self.letter_to_numbers(message[letter_index]) + encoded_message = encoded_message + str(numbers[0]) + str(numbers[1]) + elif message[letter_index] == " ": + encoded_message = encoded_message + " " + + return encoded_message + + def decode(self, message: str) -> str: + """ + Return the decoded version of message according to the polybius cipher + + >>> PolybiusCipher().decode("44154344 32154343112215") == "test message" + True + + >>> PolybiusCipher().decode("4415434432154343112215") == "testmessage" + True + """ + message = message.replace(" ", " ") + decoded_message = "" + for numbers_index in range(int(len(message) / 2)): + if message[numbers_index * 2] != " ": + index1 = message[numbers_index * 2] + index2 = message[numbers_index * 2 + 1] + + letter = self.numbers_to_letter(int(index1), int(index2)) + decoded_message = decoded_message + letter + elif message[numbers_index * 2] == " ": + decoded_message = decoded_message + " " + + return decoded_message From f7804334f15681510d4f3a008bafe742cb65d97f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 19 Oct 2021 11:11:49 +0200 Subject: [PATCH 1609/2908] length_conversion.py: Deal with uppercase and abbreviations (#5433) * length_conversion.py: Deal with uppercase and abbreviations * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- ...th_conversions.py => length_conversion.py} | 60 ++++++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) rename conversions/{length_conversions.py => length_conversion.py} (60%) diff --git a/DIRECTORY.md b/DIRECTORY.md index cf187a0db8a2..e3b40530a6b7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -113,7 +113,7 @@ * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) * [Hex To Bin](https://github.com/TheAlgorithms/Python/blob/master/conversions/hex_to_bin.py) * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/hexadecimal_to_decimal.py) - * [Length Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/length_conversions.py) + * [Length Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/length_conversion.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) diff --git a/conversions/length_conversions.py b/conversions/length_conversion.py similarity index 60% rename from conversions/length_conversions.py rename to conversions/length_conversion.py index 8d0d3a424260..790d9c116845 100644 --- a/conversions/length_conversions.py +++ b/conversions/length_conversion.py @@ -26,16 +26,28 @@ from_to = namedtuple("from_to", "from_ to") +TYPE_CONVERSION = { + "millimeter": "mm", + "centimeter": "cm", + "meter": "m", + "kilometer": "km", + "inch": "in", + "inche": "in", # Trailing 's' has been stripped off + "feet": "ft", + "foot": "ft", + "yard": "yd", + "mile": "mi", +} + METRIC_CONVERSION = { - "meter": from_to(1, 1), - "kilometer": from_to(1000, 0.001), - "feet": from_to(0.3048, 3.28084), - "inch": from_to(0.0254, 39.3701), - "centimeter": from_to(0.01, 100), - "yard": from_to(0.9144, 1.09361), - "foot": from_to(0.3048, 3.28084), - "mile": from_to(1609.34, 0.000621371), - "millimeter": from_to(0.001, 1000), + "mm": from_to(0.001, 1000), + "cm": from_to(0.01, 100), + "m": from_to(1, 1), + "km": from_to(1000, 0.001), + "in": from_to(0.0254, 39.3701), + "ft": from_to(0.3048, 3.28084), + "yd": from_to(0.9144, 1.09361), + "mi": from_to(1609.34, 0.000621371), } @@ -43,7 +55,9 @@ def length_conversion(value: float, from_type: str, to_type: str) -> float: """ Conversion between length units. - >>> length_conversion(4, "meter", "feet") + >>> length_conversion(4, "METER", "FEET") + 13.12336 + >>> length_conversion(4, "M", "FT") 13.12336 >>> length_conversion(1, "meter", "kilometer") 0.001 @@ -73,29 +87,33 @@ def length_conversion(value: float, from_type: str, to_type: str) -> float: 36.00001944 >>> length_conversion(4, "mile", "kilometer") 6.43736 - >>> length_conversion(2, "mile", "inch") + >>> length_conversion(2, "miles", "InChEs") 126719.753468 >>> length_conversion(3, "millimeter", "centimeter") 0.3 - >>> length_conversion(3, "millimeter", "inch") + >>> length_conversion(3, "mm", "in") 0.1181103 >>> length_conversion(4, "wrongUnit", "inch") Traceback (most recent call last): ... - ValueError: Invalid 'from_type' value: 'wrongUnit' Supported values are: - meter, kilometer, feet, inch, centimeter, yard, foot, mile, millimeter + ValueError: Invalid 'from_type' value: 'wrongUnit'. + Conversion abbreviations are: mm, cm, m, km, in, ft, yd, mi """ - if from_type not in METRIC_CONVERSION: + new_from = from_type.lower().rstrip("s") + new_from = TYPE_CONVERSION.get(new_from, new_from) + new_to = to_type.lower().rstrip("s") + new_to = TYPE_CONVERSION.get(new_to, new_to) + if new_from not in METRIC_CONVERSION: raise ValueError( - f"Invalid 'from_type' value: {from_type!r} Supported values are:\n" - + ", ".join(METRIC_CONVERSION) + f"Invalid 'from_type' value: {from_type!r}.\n" + f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" ) - if to_type not in METRIC_CONVERSION: + if new_to not in METRIC_CONVERSION: raise ValueError( - f"Invalid 'to_type' value: {to_type!r}. Supported values are:\n" - + ", ".join(METRIC_CONVERSION) + f"Invalid 'to_type' value: {to_type!r}.\n" + f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" ) - return value * METRIC_CONVERSION[from_type].from_ * METRIC_CONVERSION[to_type].to + return value * METRIC_CONVERSION[new_from].from_ * METRIC_CONVERSION[new_to].to if __name__ == "__main__": From 21cf3cc2603de8598b717ef13a530f5fa12b9c47 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <89947037+Rohanrbharadwaj@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:06:01 +0530 Subject: [PATCH 1610/2908] Typo (#5443) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9cf0e6a18b7..4723c6c39a7d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,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.md) once your pull request is merged -- You submitted work fulfils or mostly fulfils our styles and standards +- Your 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 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. From d32d0158a3b56a4a4ad7bcfdd66e5835f2d594c7 Mon Sep 17 00:00:00 2001 From: Alvin Philips Date: Wed, 20 Oct 2021 01:09:15 +0530 Subject: [PATCH 1611/2908] Fixed typo (#5439) Changed it's (it is) to its --- data_structures/binary_tree/binary_tree_mirror.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/binary_tree_mirror.py b/data_structures/binary_tree/binary_tree_mirror.py index dc7f657b37c7..cdd56e35d765 100644 --- a/data_structures/binary_tree/binary_tree_mirror.py +++ b/data_structures/binary_tree/binary_tree_mirror.py @@ -1,6 +1,6 @@ """ Problem Description: -Given a binary tree, return it's mirror. +Given a binary tree, return its mirror. """ From c886a66d34b1bf8796d261c94f983a43453828d7 Mon Sep 17 00:00:00 2001 From: Snimerjot Singh Date: Wed, 20 Oct 2021 11:05:41 +0530 Subject: [PATCH 1612/2908] Added check_strong_password.py (#4950) * Added check_strong_password.py * Corrected Comment * Updated * Updated check_strong_password.py * Ran Pre-Commit --- other/check_strong_password.py | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 other/check_strong_password.py diff --git a/other/check_strong_password.py b/other/check_strong_password.py new file mode 100644 index 000000000000..95bb327addf4 --- /dev/null +++ b/other/check_strong_password.py @@ -0,0 +1,47 @@ +# This Will Check Whether A Given Password Is Strong Or Not +# It Follows The Rule that Length Of Password Should Be At Least 8 Characters +# And At Least 1 Lower, 1 Upper, 1 Number And 1 Special Character + +from string import ascii_lowercase, ascii_uppercase, digits, punctuation + + +def strong_password_detector(password: str, min_length: int = 8) -> str: + """ + >>> strong_password_detector('Hwea7$2!') + 'This is a strong Password' + + >>> strong_password_detector('Sh0r1') + 'Your Password must be at least 8 characters long' + + >>> strong_password_detector('Hello123') + 'Password should contain UPPERCASE, lowercase, numbers, special characters' + + >>> strong_password_detector('Hello1238udfhiaf038fajdvjjf!jaiuFhkqi1') + 'This is a strong Password' + + >>> strong_password_detector(0) + 'Your Password must be at least 8 characters long' + """ + + if len(str(password)) < 8: + return "Your Password must be at least 8 characters long" + + upper = any(char in ascii_uppercase for char in password) + lower = any(char in ascii_lowercase for char in password) + num = any(char in digits for char in password) + spec_char = any(char in punctuation for char in password) + + if upper and lower and num and spec_char: + return "This is a strong Password" + + else: + return ( + "Password should contain UPPERCASE, lowercase, " + "numbers, special characters" + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2e2e1b656cf12789c0cc93ad4d2a4771be86cbab Mon Sep 17 00:00:00 2001 From: Immiel Date: Wed, 20 Oct 2021 15:08:39 +0700 Subject: [PATCH 1613/2908] singly_linked_list: Added additional documentation, type hints and test cases (#4988) This is a followup to https://github.com/TheAlgorithms/Python/pull/4973#issuecomment-933117382 As per given suggestion, I've added type hints to certain methods that don't have them. I have also added documentation and example doctests as a usage example for (most of) those that don't have them. I have also added another test case following the previous test case's format. I noticed that the existing test case from previous pull request might be redundant with the ones I've made, so I decided to create a specific situation where the linked list would have to keep different kinds of data types for each node, in `test_singly_linked_list_2` test function. Some minor changes in strings has been done to keep things consistent with other parts of the document. If it is undesirable, please let me know. --- .../linked_list/singly_linked_list.py | 295 +++++++++++++++++- 1 file changed, 280 insertions(+), 15 deletions(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index e45a210a1785..4a5dc8263f79 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -1,17 +1,53 @@ +from typing import Any + + class Node: - def __init__(self, data): + def __init__(self, data: Any): + """ + Create and initialize Node class instance. + >>> Node(20) + Node(20) + >>> Node("Hello, world!") + Node(Hello, world!) + >>> Node(None) + Node(None) + >>> Node(True) + Node(True) + """ self.data = data self.next = None - def __repr__(self): + def __repr__(self) -> str: + """ + Get the string representation of this node. + >>> Node(10).__repr__() + 'Node(10)' + """ return f"Node({self.data})" class LinkedList: def __init__(self): + """ + Create and initialize LinkedList class instance. + >>> linked_list = LinkedList() + """ self.head = None - def __iter__(self): + def __iter__(self) -> Any: + """ + This function is intended for iterators to access + and iterate through data inside linked list. + >>> linked_list = LinkedList() + >>> linked_list.insert_tail("tail") + >>> linked_list.insert_tail("tail_1") + >>> linked_list.insert_tail("tail_2") + >>> for node in linked_list: # __iter__ used here. + ... node + 'tail' + 'tail_1' + 'tail_2' + """ node = self.head while node: yield node.data @@ -23,7 +59,7 @@ def __len__(self) -> int: >>> linked_list = LinkedList() >>> len(linked_list) 0 - >>> linked_list.insert_tail("head") + >>> linked_list.insert_tail("tail") >>> len(linked_list) 1 >>> linked_list.insert_head("head") @@ -38,13 +74,18 @@ def __len__(self) -> int: """ return len(tuple(iter(self))) - def __repr__(self): + def __repr__(self) -> str: """ String representation/visualization of a Linked Lists + >>> linked_list = LinkedList() + >>> linked_list.insert_tail(1) + >>> linked_list.insert_tail(3) + >>> linked_list.__repr__() + '1->3' """ return "->".join([str(item) for item in self]) - def __getitem__(self, index): + def __getitem__(self, index: int) -> Any: """ Indexing Support. Used to get a node at particular position >>> linked_list = LinkedList() @@ -68,7 +109,7 @@ def __getitem__(self, index): return node # Used to change the data of a particular node - def __setitem__(self, index, data): + def __setitem__(self, index: int, data: Any) -> None: """ >>> linked_list = LinkedList() >>> for i in range(0, 10): @@ -95,13 +136,54 @@ def __setitem__(self, index, data): current = current.next current.data = data - def insert_tail(self, data) -> None: + def insert_tail(self, data: Any) -> None: + """ + Insert data to the end of linked list. + >>> linked_list = LinkedList() + >>> linked_list.insert_tail("tail") + >>> linked_list + tail + >>> linked_list.insert_tail("tail_2") + >>> linked_list + tail->tail_2 + >>> linked_list.insert_tail("tail_3") + >>> linked_list + tail->tail_2->tail_3 + """ self.insert_nth(len(self), data) - def insert_head(self, data) -> None: + def insert_head(self, data: Any) -> None: + """ + Insert data to the beginning of linked list. + >>> linked_list = LinkedList() + >>> linked_list.insert_head("head") + >>> linked_list + head + >>> linked_list.insert_head("head_2") + >>> linked_list + head_2->head + >>> linked_list.insert_head("head_3") + >>> linked_list + head_3->head_2->head + """ self.insert_nth(0, data) - def insert_nth(self, index: int, data) -> None: + def insert_nth(self, index: int, data: Any) -> None: + """ + Insert data at given index. + >>> linked_list = LinkedList() + >>> linked_list.insert_tail("first") + >>> linked_list.insert_tail("second") + >>> linked_list.insert_tail("third") + >>> linked_list + first->second->third + >>> linked_list.insert_nth(1, "fourth") + >>> linked_list + first->fourth->second->third + >>> linked_list.insert_nth(3, "fifth") + >>> linked_list + first->fourth->second->fifth->third + """ if not 0 <= index <= len(self): raise IndexError("list index out of range") new_node = Node(data) @@ -118,17 +200,96 @@ def insert_nth(self, index: int, data) -> None: temp.next = new_node def print_list(self) -> None: # print every node data + """ + This method prints every node data. + >>> linked_list = LinkedList() + >>> linked_list.insert_tail("first") + >>> linked_list.insert_tail("second") + >>> linked_list.insert_tail("third") + >>> linked_list + first->second->third + """ print(self) - def delete_head(self): + def delete_head(self) -> Any: + """ + Delete the first node and return the + node's data. + >>> linked_list = LinkedList() + >>> linked_list.insert_tail("first") + >>> linked_list.insert_tail("second") + >>> linked_list.insert_tail("third") + >>> linked_list + first->second->third + >>> linked_list.delete_head() + 'first' + >>> linked_list + second->third + >>> linked_list.delete_head() + 'second' + >>> linked_list + third + >>> linked_list.delete_head() + 'third' + >>> linked_list.delete_head() + Traceback (most recent call last): + ... + IndexError: List index out of range. + """ return self.delete_nth(0) - def delete_tail(self): # delete from tail + def delete_tail(self) -> Any: # delete from tail + """ + Delete the tail end node and return the + node's data. + >>> linked_list = LinkedList() + >>> linked_list.insert_tail("first") + >>> linked_list.insert_tail("second") + >>> linked_list.insert_tail("third") + >>> linked_list + first->second->third + >>> linked_list.delete_tail() + 'third' + >>> linked_list + first->second + >>> linked_list.delete_tail() + 'second' + >>> linked_list + first + >>> linked_list.delete_tail() + 'first' + >>> linked_list.delete_tail() + Traceback (most recent call last): + ... + IndexError: List index out of range. + """ return self.delete_nth(len(self) - 1) - def delete_nth(self, index: int = 0): + def delete_nth(self, index: int = 0) -> Any: + """ + Delete node at given index and return the + node's data. + >>> linked_list = LinkedList() + >>> linked_list.insert_tail("first") + >>> linked_list.insert_tail("second") + >>> linked_list.insert_tail("third") + >>> linked_list + first->second->third + >>> linked_list.delete_nth(1) # delete middle + 'second' + >>> linked_list + first->third + >>> linked_list.delete_nth(5) # this raises error + Traceback (most recent call last): + ... + IndexError: List index out of range. + >>> linked_list.delete_nth(-1) # this also raises error + Traceback (most recent call last): + ... + IndexError: List index out of range. + """ if not 0 <= index <= len(self) - 1: # test if index is valid - raise IndexError("list index out of range") + raise IndexError("List index out of range.") delete_node = self.head # default first node if index == 0: self.head = self.head.next @@ -141,9 +302,30 @@ def delete_nth(self, index: int = 0): return delete_node.data def is_empty(self) -> bool: + """ + Check if linked list is empty. + >>> linked_list = LinkedList() + >>> linked_list.is_empty() + True + >>> linked_list.insert_head("first") + >>> linked_list.is_empty() + False + """ return self.head is None - def reverse(self): + def reverse(self) -> None: + """ + This reverses the linked list order. + >>> linked_list = LinkedList() + >>> linked_list.insert_tail("first") + >>> linked_list.insert_tail("second") + >>> linked_list.insert_tail("third") + >>> linked_list + first->second->third + >>> linked_list.reverse() + >>> linked_list + third->second->first + """ prev = None current = self.head @@ -201,6 +383,89 @@ def test_singly_linked_list() -> None: linked_list[i] = -i assert all(linked_list[i] == -i for i in range(0, 9)) is True + linked_list.reverse() + assert str(linked_list) == "->".join(str(i) for i in range(-8, 1)) + + +def test_singly_linked_list_2() -> None: + """ + This section of the test used varying data types for input. + >>> test_singly_linked_list_2() + """ + input = [ + -9, + 100, + Node(77345112), + "dlrow olleH", + 7, + 5555, + 0, + -192.55555, + "Hello, world!", + 77.9, + Node(10), + None, + None, + 12.20, + ] + linked_list = LinkedList() + [linked_list.insert_tail(i) for i in input] + + # Check if it's empty or not + assert linked_list.is_empty() is False + assert ( + str(linked_list) == "-9->100->Node(77345112)->dlrow olleH->7->5555->0->" + "-192.55555->Hello, world!->77.9->Node(10)->None->None->12.2" + ) + + # Delete the head + result = linked_list.delete_head() + assert result == -9 + assert ( + str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->" + "Hello, world!->77.9->Node(10)->None->None->12.2" + ) + + # Delete the tail + result = linked_list.delete_tail() + assert result == 12.2 + assert ( + str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->" + "Hello, world!->77.9->Node(10)->None->None" + ) + + # Delete a node in specific location in linked list + result = linked_list.delete_nth(10) + assert result is None + assert ( + str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->" + "Hello, world!->77.9->Node(10)->None" + ) + + # Add a Node instance to its head + linked_list.insert_head(Node("Hello again, world!")) + assert ( + str(linked_list) + == "Node(Hello again, world!)->100->Node(77345112)->dlrow olleH->" + "7->5555->0->-192.55555->Hello, world!->77.9->Node(10)->None" + ) + + # Add None to its tail + linked_list.insert_tail(None) + assert ( + str(linked_list) + == "Node(Hello again, world!)->100->Node(77345112)->dlrow olleH->" + "7->5555->0->-192.55555->Hello, world!->77.9->Node(10)->None->None" + ) + + # Reverse the linked list + linked_list.reverse() + assert ( + str(linked_list) + == "None->None->Node(10)->77.9->Hello, world!->-192.55555->0->5555->" + "7->dlrow olleH->Node(77345112)->100->Node(Hello again, world!)" + ) + def main(): from doctest import testmod From 83cf5786cddd694a2af25827f8861b7dbcbf706c Mon Sep 17 00:00:00 2001 From: P U N I T H <55887644+punithbajaj@users.noreply.github.com> Date: Wed, 20 Oct 2021 14:00:58 +0530 Subject: [PATCH 1614/2908] Add wildcard pattern matching using dynamic programming (#5334) * Added regular expression implimentation using dp * replaced input() with example values * Apply suggestions from code review Co-authored-by: Christian Clauss * changed returning value to bool and added test cases * added doctest Co-authored-by: John Law * added test cases * Apply suggestions from code review Co-authored-by: John Law * shifted to strings * Changed filename * Update function name to match_pattern Co-authored-by: John Law * Update function name to match_pattern Co-authored-by: John Law Co-authored-by: Christian Clauss Co-authored-by: John Law --- strings/wildcard_pattern_matching.py | 112 +++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 strings/wildcard_pattern_matching.py diff --git a/strings/wildcard_pattern_matching.py b/strings/wildcard_pattern_matching.py new file mode 100644 index 000000000000..83c8d834cca9 --- /dev/null +++ b/strings/wildcard_pattern_matching.py @@ -0,0 +1,112 @@ +""" +Implementation of regular expression matching with support for '.' and '*'. +'.' Matches any single character. +'*' Matches zero or more of the preceding element. +The matching should cover the entire input string (not partial). + +""" + + +def match_pattern(input_string: str, pattern: str) -> bool: + """ + uses bottom-up dynamic programming solution for matching the input + string with a given pattern. + + Runtime: O(len(input_string)*len(pattern)) + + Arguments + -------- + input_string: str, any string which should be compared with the pattern + pattern: str, the string that represents a pattern and may contain + '.' for single character matches and '*' for zero or more of preceding character + matches + + Note + ---- + the pattern cannot start with a '*', + because there should be at least one character before * + + Returns + ------- + A Boolean denoting whether the given string follows the pattern + + Examples + ------- + >>> match_pattern("aab", "c*a*b") + True + >>> match_pattern("dabc", "*abc") + False + >>> match_pattern("aaa", "aa") + False + >>> match_pattern("aaa", "a.a") + True + >>> match_pattern("aaab", "aa*") + False + >>> match_pattern("aaab", ".*") + True + >>> match_pattern("a", "bbbb") + False + >>> match_pattern("", "bbbb") + False + >>> match_pattern("a", "") + False + >>> match_pattern("", "") + True + """ + + len_string = len(input_string) + 1 + len_pattern = len(pattern) + 1 + + # dp is a 2d matrix where dp[i][j] denotes whether prefix string of + # length i of input_string matches with prefix string of length j of + # given pattern. + # "dp" stands for dynamic programming. + dp = [[0 for i in range(len_pattern)] for j in range(len_string)] + + # since string of zero length match pattern of zero length + dp[0][0] = 1 + + # since pattern of zero length will never match with string of non-zero length + for i in range(1, len_string): + dp[i][0] = 0 + + # since string of zero length will match with pattern where there + # is at least one * alternatively + for j in range(1, len_pattern): + dp[0][j] = dp[0][j - 2] if pattern[j - 1] == "*" else 0 + + # now using bottom-up approach to find for all remaining lengths + for i in range(1, len_string): + for j in range(1, len_pattern): + if input_string[i - 1] == pattern[j - 1] or pattern[j - 1] == ".": + dp[i][j] = dp[i - 1][j - 1] + + elif pattern[j - 1] == "*": + if dp[i][j - 2] == 1: + dp[i][j] = 1 + elif pattern[j - 2] in (input_string[i - 1], "."): + dp[i][j] = dp[i - 1][j] + else: + dp[i][j] = 0 + else: + dp[i][j] = 0 + + return bool(dp[-1][-1]) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + # inputing the strings + # input_string = input("input a string :") + # pattern = input("input a pattern :") + + input_string = "aab" + pattern = "c*a*b" + + # using function to check whether given string matches the given pattern + if match_pattern(input_string, pattern): + print(f"{input_string} matches the given pattern {pattern}") + else: + print(f"{input_string} does not match with the given pattern {pattern}") From 50485f7c8e33b0a3bf6e603cdae3505d40b1d97a Mon Sep 17 00:00:00 2001 From: Manan Rathi <76519771+Manan-Rathi@users.noreply.github.com> Date: Wed, 20 Oct 2021 14:12:32 +0530 Subject: [PATCH 1615/2908] Fix typos in Sorts and Bit_manipulation (#4949) * Fix several typos * Update bit_manipulation/README.md Co-authored-by: John Law * Update double_sort.py Co-authored-by: John Law --- bit_manipulation/README.md | 13 ++++++------- bit_manipulation/binary_and_operator.py | 4 ++-- bit_manipulation/binary_or_operator.py | 4 ++-- bit_manipulation/binary_xor_operator.py | 4 ++-- sorts/bead_sort.py | 6 +++--- sorts/double_sort.py | 6 +++--- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/bit_manipulation/README.md b/bit_manipulation/README.md index 2ef1661524f2..e5f82a270e28 100644 --- a/bit_manipulation/README.md +++ b/bit_manipulation/README.md @@ -1,7 +1,6 @@ -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 +* 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 diff --git a/bit_manipulation/binary_and_operator.py b/bit_manipulation/binary_and_operator.py index 191ff8eb44a4..36f6c668d9b3 100644 --- a/bit_manipulation/binary_and_operator.py +++ b/bit_manipulation/binary_and_operator.py @@ -22,7 +22,7 @@ def binary_and(a: int, b: int) -> str: >>> binary_and(0, -1) Traceback (most recent call last): ... - ValueError: the value of both input must be positive + ValueError: the value of both inputs must be positive >>> binary_and(0, 1.1) Traceback (most recent call last): ... @@ -33,7 +33,7 @@ def binary_and(a: int, b: int) -> str: 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") + raise ValueError("the value of both inputs must be positive") a_binary = str(bin(a))[2:] # remove the leading "0b" b_binary = str(bin(b))[2:] # remove the leading "0b" diff --git a/bit_manipulation/binary_or_operator.py b/bit_manipulation/binary_or_operator.py index dabf5bcb09fd..95f61f1da64e 100644 --- a/bit_manipulation/binary_or_operator.py +++ b/bit_manipulation/binary_or_operator.py @@ -21,7 +21,7 @@ def binary_or(a: int, b: int) -> str: >>> binary_or(0, -1) Traceback (most recent call last): ... - ValueError: the value of both input must be positive + ValueError: the value of both inputs must be positive >>> binary_or(0, 1.1) Traceback (most recent call last): ... @@ -32,7 +32,7 @@ def binary_or(a: int, b: int) -> str: 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") + raise ValueError("the value of both inputs 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)) diff --git a/bit_manipulation/binary_xor_operator.py b/bit_manipulation/binary_xor_operator.py index 6f8962192ad8..6206c70a99f6 100644 --- a/bit_manipulation/binary_xor_operator.py +++ b/bit_manipulation/binary_xor_operator.py @@ -22,7 +22,7 @@ def binary_xor(a: int, b: int) -> str: >>> binary_xor(0, -1) Traceback (most recent call last): ... - ValueError: the value of both input must be positive + ValueError: the value of both inputs must be positive >>> binary_xor(0, 1.1) Traceback (most recent call last): ... @@ -33,7 +33,7 @@ def binary_xor(a: int, b: int) -> str: 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") + raise ValueError("the value of both inputs must be positive") a_binary = str(bin(a))[2:] # remove the leading "0b" b_binary = str(bin(b))[2:] # remove the leading "0b" diff --git a/sorts/bead_sort.py b/sorts/bead_sort.py index 26a3fabc4807..d22367c52fa9 100644 --- a/sorts/bead_sort.py +++ b/sorts/bead_sort.py @@ -21,15 +21,15 @@ def bead_sort(sequence: list) -> list: >>> bead_sort([1, .9, 0.0, 0, -1, -.9]) Traceback (most recent call last): ... - TypeError: Sequence must be list of nonnegative integers + TypeError: Sequence must be list of non-negative integers >>> bead_sort("Hello world") Traceback (most recent call last): ... - TypeError: Sequence must be list of nonnegative integers + TypeError: Sequence must be list of non-negative integers """ if any(not isinstance(x, int) or x < 0 for x in sequence): - raise TypeError("Sequence must be list of nonnegative integers") + raise TypeError("Sequence must be list of non-negative integers") for _ in range(len(sequence)): for i, (rod_upper, rod_lower) in enumerate(zip(sequence, sequence[1:])): if rod_upper > rod_lower: diff --git a/sorts/double_sort.py b/sorts/double_sort.py index 04e18682017c..4e08e27b3c21 100644 --- a/sorts/double_sort.py +++ b/sorts/double_sort.py @@ -1,7 +1,7 @@ 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" + """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, it's called "Double sort" :param collection: mutable ordered sequence of elements :return: the same collection in ascending order Examples: From 672a0c8816fcebd8e426d64c0053cef4c42d7ec6 Mon Sep 17 00:00:00 2001 From: Hithru De Alwis Date: Wed, 20 Oct 2021 19:04:31 +0530 Subject: [PATCH 1616/2908] Fixed Typo (#5477) Change how many "=" sign to how many "=" signs --- ciphers/base64_encoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ciphers/base64_encoding.py b/ciphers/base64_encoding.py index 634afcb89873..38a952acc307 100644 --- a/ciphers/base64_encoding.py +++ b/ciphers/base64_encoding.py @@ -7,7 +7,7 @@ def base64_encode(data: bytes) -> bytes: 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. + how many "=" signs 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: From 2e955aea460d6ac173d5bfeab0c71db0658e2bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Garc=C3=ADa=20Roqu=C3=A9s?= <62822419+grbenjamin@users.noreply.github.com> Date: Thu, 21 Oct 2021 00:38:04 -0300 Subject: [PATCH 1617/2908] Replace double_ended_queue.py (#5429) * Add deque_from_scratch.py * added deque_from_scratch.py * add extend, extendleft and make comparison * updated operations list * fix doctest on Deque.__iter__ * pre-commit fix * time complexity comments, change type hints * pre-commit fix * added more doctests --- data_structures/queue/double_ended_queue.py | 495 ++++++++++++++++++-- 1 file changed, 454 insertions(+), 41 deletions(-) diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index dd003b7c98ac..36106d8bc0d9 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -1,57 +1,470 @@ -# Python code to demonstrate working of -# extend(), extendleft(), rotate(), reverse() +""" +Implementation of double ended queue. +""" +from dataclasses import dataclass +from typing import Any, Iterable -# importing "collections" for deque operations -import collections -# initializing deque -de = collections.deque([1, 2, 3]) +class Deque: + """ + Deque data structure. -# using extend() to add numbers to right end -# adds 4,5,6 to right end -de.extend([4, 5, 6]) + Operations + ---------- + append(val: Any) -> None -# printing modified deque -print("The deque after extending deque at end is : ") -print(de) + appendleft(val: Any) -> None -# using extendleft() to add numbers to left end -# adds 7,8,9 to right end -de.extendleft([7, 8, 9]) + extend(iter: Iterable) -> None -# printing modified deque -print("The deque after extending deque at beginning is : ") -print(de) + extendleft(iter: Iterable) -> None -# using rotate() to rotate the deque -# rotates by 3 to left -de.rotate(-3) + pop() -> Any -# printing modified deque -print("The deque after rotating deque is : ") -print(de) + popleft() -> Any -# using reverse() to reverse the deque -de.reverse() -# printing modified deque -print("The deque after reversing deque is : ") -print(de) + Observers + --------- + is_empty() -> bool -# get right-end value and eliminate -startValue = de.pop() -print("The deque after popping value at end is : ") -print(de) + Attributes + ---------- + _front: _Node + front of the deque a.k.a. the first element -# get left-end value and eliminate -endValue = de.popleft() + _back: _Node + back of the element a.k.a. the last element -print("The deque after popping value at start is : ") -print(de) + _len: int + the number of nodes + """ -# eliminate element searched by value -de.remove(5) + __slots__ = ["_front", "_back", "_len"] -print("The deque after eliminating element searched by value : ") -print(de) + @dataclass + class _Node: + """ + Representation of a node. + Contains a value and a pointer to the next node as well as to the previous one. + """ + + val: Any = None + next: "Deque._Node" = None + prev: "Deque._Node" = None + + class _Iterator: + """ + Helper class for iteration. Will be used to implement iteration. + + Attributes + ---------- + _cur: _Node + the current node of the iteration. + """ + + __slots__ = ["_cur"] + + def __init__(self, cur: "Deque._Node") -> None: + self._cur = cur + + def __iter__(self) -> "Deque._Iterator": + """ + >>> our_deque = Deque([1, 2, 3]) + >>> iterator = iter(our_deque) + """ + return self + + def __next__(self) -> Any: + """ + >>> our_deque = Deque([1, 2, 3]) + >>> iterator = iter(our_deque) + >>> next(iterator) + 1 + >>> next(iterator) + 2 + >>> next(iterator) + 3 + """ + if self._cur is None: + # finished iterating + raise StopIteration + val = self._cur.val + self._cur = self._cur.next + + return val + + def __init__(self, iterable: Iterable = None) -> None: + self._front = self._back = None + self._len = 0 + + if iterable is not None: + # append every value to the deque + for val in iterable: + self.append(val) + + def append(self, val: Any) -> None: + """ + Adds val to the end of the deque. + Time complexity: O(1) + + >>> our_deque_1 = Deque([1, 2, 3]) + >>> our_deque_1.append(4) + >>> our_deque_1 + [1, 2, 3, 4] + >>> our_deque_2 = Deque('ab') + >>> our_deque_2.append('c') + >>> our_deque_2 + ['a', 'b', 'c'] + + >>> from collections import deque + >>> deque_collections_1 = deque([1, 2, 3]) + >>> deque_collections_1.append(4) + >>> deque_collections_1 + deque([1, 2, 3, 4]) + >>> deque_collections_2 = deque('ab') + >>> deque_collections_2.append('c') + >>> deque_collections_2 + deque(['a', 'b', 'c']) + + >>> list(our_deque_1) == list(deque_collections_1) + True + >>> list(our_deque_2) == list(deque_collections_2) + True + """ + node = self._Node(val, None, None) + if self.is_empty(): + # front = back + self._front = self._back = node + self._len = 1 + else: + # connect nodes + self._back.next = node + node.prev = self._back + self._back = node # assign new back to the new node + + self._len += 1 + + # make sure there were no errors + assert not self.is_empty(), "Error on appending value." + + def appendleft(self, val: Any) -> None: + """ + Adds val to the beginning of the deque. + Time complexity: O(1) + + >>> our_deque_1 = Deque([2, 3]) + >>> our_deque_1.appendleft(1) + >>> our_deque_1 + [1, 2, 3] + >>> our_deque_2 = Deque('bc') + >>> our_deque_2.appendleft('a') + >>> our_deque_2 + ['a', 'b', 'c'] + + >>> from collections import deque + >>> deque_collections_1 = deque([2, 3]) + >>> deque_collections_1.appendleft(1) + >>> deque_collections_1 + deque([1, 2, 3]) + >>> deque_collections_2 = deque('bc') + >>> deque_collections_2.appendleft('a') + >>> deque_collections_2 + deque(['a', 'b', 'c']) + + >>> list(our_deque_1) == list(deque_collections_1) + True + >>> list(our_deque_2) == list(deque_collections_2) + True + """ + node = self._Node(val, None, None) + if self.is_empty(): + # front = back + self._front = self._back = node + self._len = 1 + else: + # connect nodes + node.next = self._front + self._front.prev = node + self._front = node # assign new front to the new node + + self._len += 1 + + # make sure there were no errors + assert not self.is_empty(), "Error on appending value." + + def extend(self, iter: Iterable) -> None: + """ + Appends every value of iter to the end of the deque. + Time complexity: O(n) + + >>> our_deque_1 = Deque([1, 2, 3]) + >>> our_deque_1.extend([4, 5]) + >>> our_deque_1 + [1, 2, 3, 4, 5] + >>> our_deque_2 = Deque('ab') + >>> our_deque_2.extend('cd') + >>> our_deque_2 + ['a', 'b', 'c', 'd'] + + >>> from collections import deque + >>> deque_collections_1 = deque([1, 2, 3]) + >>> deque_collections_1.extend([4, 5]) + >>> deque_collections_1 + deque([1, 2, 3, 4, 5]) + >>> deque_collections_2 = deque('ab') + >>> deque_collections_2.extend('cd') + >>> deque_collections_2 + deque(['a', 'b', 'c', 'd']) + + >>> list(our_deque_1) == list(deque_collections_1) + True + >>> list(our_deque_2) == list(deque_collections_2) + True + """ + for val in iter: + self.append(val) + + def extendleft(self, iter: Iterable) -> None: + """ + Appends every value of iter to the beginning of the deque. + Time complexity: O(n) + + >>> our_deque_1 = Deque([1, 2, 3]) + >>> our_deque_1.extendleft([0, -1]) + >>> our_deque_1 + [-1, 0, 1, 2, 3] + >>> our_deque_2 = Deque('cd') + >>> our_deque_2.extendleft('ba') + >>> our_deque_2 + ['a', 'b', 'c', 'd'] + + >>> from collections import deque + >>> deque_collections_1 = deque([1, 2, 3]) + >>> deque_collections_1.extendleft([0, -1]) + >>> deque_collections_1 + deque([-1, 0, 1, 2, 3]) + >>> deque_collections_2 = deque('cd') + >>> deque_collections_2.extendleft('ba') + >>> deque_collections_2 + deque(['a', 'b', 'c', 'd']) + + >>> list(our_deque_1) == list(deque_collections_1) + True + >>> list(our_deque_2) == list(deque_collections_2) + True + """ + for val in iter: + self.appendleft(val) + + def pop(self) -> Any: + """ + Removes the last element of the deque and returns it. + Time complexity: O(1) + + @returns topop.val: the value of the node to pop. + + >>> our_deque = Deque([1, 2, 3, 15182]) + >>> our_popped = our_deque.pop() + >>> our_popped + 15182 + >>> our_deque + [1, 2, 3] + + >>> from collections import deque + >>> deque_collections = deque([1, 2, 3, 15182]) + >>> collections_popped = deque_collections.pop() + >>> collections_popped + 15182 + >>> deque_collections + deque([1, 2, 3]) + + >>> list(our_deque) == list(deque_collections) + True + >>> our_popped == collections_popped + True + """ + # make sure the deque has elements to pop + assert not self.is_empty(), "Deque is empty." + + topop = self._back + self._back = self._back.prev # set new back + self._back.next = ( + None # drop the last node - python will deallocate memory automatically + ) + + self._len -= 1 + + return topop.val + + def popleft(self) -> Any: + """ + Removes the first element of the deque and returns it. + Time complexity: O(1) + + @returns topop.val: the value of the node to pop. + + >>> our_deque = Deque([15182, 1, 2, 3]) + >>> our_popped = our_deque.popleft() + >>> our_popped + 15182 + >>> our_deque + [1, 2, 3] + + >>> from collections import deque + >>> deque_collections = deque([15182, 1, 2, 3]) + >>> collections_popped = deque_collections.popleft() + >>> collections_popped + 15182 + >>> deque_collections + deque([1, 2, 3]) + + >>> list(our_deque) == list(deque_collections) + True + >>> our_popped == collections_popped + True + """ + # make sure the deque has elements to pop + assert not self.is_empty(), "Deque is empty." + + topop = self._front + self._front = self._front.next # set new front and drop the first node + self._front.prev = None + + self._len -= 1 + + return topop.val + + def is_empty(self) -> bool: + """ + Checks if the deque is empty. + Time complexity: O(1) + + >>> our_deque = Deque([1, 2, 3]) + >>> our_deque.is_empty() + False + >>> our_empty_deque = Deque() + >>> our_empty_deque.is_empty() + True + + >>> from collections import deque + >>> empty_deque_collections = deque() + >>> list(our_empty_deque) == list(empty_deque_collections) + True + """ + return self._front is None + + def __len__(self) -> int: + """ + Implements len() function. Returns the length of the deque. + Time complexity: O(1) + + >>> our_deque = Deque([1, 2, 3]) + >>> len(our_deque) + 3 + >>> our_empty_deque = Deque() + >>> len(our_empty_deque) + 0 + + >>> from collections import deque + >>> deque_collections = deque([1, 2, 3]) + >>> len(deque_collections) + 3 + >>> empty_deque_collections = deque() + >>> len(empty_deque_collections) + 0 + >>> len(our_empty_deque) == len(empty_deque_collections) + True + """ + return self._len + + def __eq__(self, other: "Deque") -> bool: + """ + Implements "==" operator. Returns if *self* is equal to *other*. + Time complexity: O(n) + + >>> our_deque_1 = Deque([1, 2, 3]) + >>> our_deque_2 = Deque([1, 2, 3]) + >>> our_deque_1 == our_deque_2 + True + >>> our_deque_3 = Deque([1, 2]) + >>> our_deque_1 == our_deque_3 + False + + >>> from collections import deque + >>> deque_collections_1 = deque([1, 2, 3]) + >>> deque_collections_2 = deque([1, 2, 3]) + >>> deque_collections_1 == deque_collections_2 + True + >>> deque_collections_3 = deque([1, 2]) + >>> deque_collections_1 == deque_collections_3 + False + + >>> (our_deque_1 == our_deque_2) == (deque_collections_1 == deque_collections_2) + True + >>> (our_deque_1 == our_deque_3) == (deque_collections_1 == deque_collections_3) + True + """ + me = self._front + oth = other._front + + # if the length of the deques are not the same, they are not equal + if len(self) != len(other): + return False + + while me is not None and oth is not None: + # compare every value + if me.val != oth.val: + return False + me = me.next + oth = oth.next + + return True + + def __iter__(self) -> "_Iterator": + """ + Implements iteration. + Time complexity: O(1) + + >>> our_deque = Deque([1, 2, 3]) + >>> for v in our_deque: + ... print(v) + 1 + 2 + 3 + + >>> from collections import deque + >>> deque_collections = deque([1, 2, 3]) + >>> for v in deque_collections: + ... print(v) + 1 + 2 + 3 + """ + return Deque._Iterator(self._front) + + def __repr__(self) -> str: + """ + Implements representation of the deque. + Represents it as a list, with its values between '[' and ']'. + Time complexity: O(n) + + >>> our_deque = Deque([1, 2, 3]) + >>> our_deque + [1, 2, 3] + """ + values_list = [] + aux = self._front + while aux is not None: + # append the values in a list to display + values_list.append(aux.val) + aux = aux.next + + return "[" + ", ".join(repr(val) for val in values_list) + "]" + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c0acfd46cbd6b29847d9e0e226431ab6004b8e9b Mon Sep 17 00:00:00 2001 From: John Law Date: Thu, 21 Oct 2021 15:06:32 +0800 Subject: [PATCH 1618/2908] Fix factorial issues (#5496) * updating DIRECTORY.md * pass integer to `math.factorial` in `project_euler/problem_015` * remove duplicated factorial function * updating DIRECTORY.md * Update maths/factorial_iterative.py Co-authored-by: Christian Clauss * Update factorial_iterative.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 4 +++- maths/factorial_iterative.py | 23 ++++++++++++++++----- maths/factorial_python.py | 34 ------------------------------- project_euler/problem_015/sol1.py | 2 +- 4 files changed, 22 insertions(+), 41 deletions(-) delete mode 100644 maths/factorial_python.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e3b40530a6b7..10149eac5aac 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -75,6 +75,7 @@ * [Morse Code](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code.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) + * [Polybius](https://github.com/TheAlgorithms/Python/blob/master/ciphers/polybius.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) @@ -453,7 +454,6 @@ * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.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) * [Fermat Little Theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) @@ -565,6 +565,7 @@ ## Other * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) + * [Check Strong Password](https://github.com/TheAlgorithms/Python/blob/master/other/check_strong_password.py) * [Date To Weekday](https://github.com/TheAlgorithms/Python/blob/master/other/date_to_weekday.py) * [Davisb Putnamb Logemannb Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davisb_putnamb_logemannb_loveland.py) * [Dijkstra Bankers Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/dijkstra_bankers_algorithm.py) @@ -952,6 +953,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) + * [Wildcard Pattern Matching](https://github.com/TheAlgorithms/Python/blob/master/strings/wildcard_pattern_matching.py) * [Word Occurrence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurrence.py) * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/strings/word_patterns.py) * [Z Function](https://github.com/TheAlgorithms/Python/blob/master/strings/z_function.py) diff --git a/maths/factorial_iterative.py b/maths/factorial_iterative.py index 64314790c11c..c6cf7de57ab2 100644 --- a/maths/factorial_iterative.py +++ b/maths/factorial_iterative.py @@ -1,8 +1,11 @@ -# factorial of a positive integer -- https://en.wikipedia.org/wiki/Factorial +"""Factorial of a positive integer -- https://en.wikipedia.org/wiki/Factorial +""" -def factorial(n: int) -> int: +def factorial(number: int) -> int: """ + Calculate the factorial of specified number (n!). + >>> import math >>> all(factorial(i) == math.factorial(i) for i in range(20)) True @@ -14,17 +17,27 @@ def factorial(n: int) -> int: Traceback (most recent call last): ... ValueError: factorial() not defined for negative values + >>> factorial(1) + 1 + >>> factorial(6) + 720 + >>> factorial(0) + 1 """ - if n != int(n): + if number != int(number): raise ValueError("factorial() only accepts integral values") - if n < 0: + if number < 0: raise ValueError("factorial() not defined for negative values") value = 1 - for i in range(1, n + 1): + for i in range(1, number + 1): value *= i return value if __name__ == "__main__": + import doctest + + doctest.testmod() + n = int(input("Enter a positive integer: ").strip() or 0) print(f"factorial{n} is {factorial(n)}") diff --git a/maths/factorial_python.py b/maths/factorial_python.py deleted file mode 100644 index 46688261af56..000000000000 --- a/maths/factorial_python.py +++ /dev/null @@ -1,34 +0,0 @@ -def factorial(input_number: int) -> int: - """ - 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("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/project_euler/problem_015/sol1.py b/project_euler/problem_015/sol1.py index da079d26120a..fb2020d6179f 100644 --- a/project_euler/problem_015/sol1.py +++ b/project_euler/problem_015/sol1.py @@ -26,7 +26,7 @@ def solution(n: int = 20) -> int: """ n = 2 * n # middle entry of odd rows starting at row 3 is the solution for n = 1, # 2, 3,... - k = n / 2 + k = n // 2 return int(factorial(n) / (factorial(k) * factorial(n - k))) From fdf095f69f428b161e939b18971812285608495a Mon Sep 17 00:00:00 2001 From: poloso Date: Thu, 21 Oct 2021 08:13:42 -0500 Subject: [PATCH 1619/2908] [mypy] check polygon and corrections (#5419) * Update annotations to Python 3.10 #4052 * Add floats doctest * Copy list to avoid changing input unpredictably * Refactor code to make it readable * updating DIRECTORY.md * Improve raised ValueErrors and add doctest * Split doctest in multiples lines * Change ValueError to Monogons and Digons are not poly * Correct doctest refering number of sides Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- maths/check_polygon.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/maths/check_polygon.py b/maths/check_polygon.py index 0e771197331f..1e8dce7183ad 100644 --- a/maths/check_polygon.py +++ b/maths/check_polygon.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def check_polygon(nums: List) -> bool: +def check_polygon(nums: list[float]) -> bool: """ Takes list of possible side lengths and determines whether a two-dimensional polygon with such side lengths can exist. @@ -14,15 +14,28 @@ def check_polygon(nums: List) -> bool: True >>> check_polygon([3, 7, 13, 2]) False + >>> check_polygon([1, 4.3, 5.2, 12.2]) + False + >>> nums = [3, 7, 13, 2] + >>> _ = check_polygon(nums) # Run function, do not show answer in output + >>> nums # Check numbers are not reordered + [3, 7, 13, 2] >>> check_polygon([]) Traceback (most recent call last): ... - ValueError: List is invalid + ValueError: Monogons and Digons are not polygons in the Euclidean space + >>> check_polygon([-2, 5, 6]) + Traceback (most recent call last): + ... + ValueError: All values must be greater than 0 """ - if not nums: - raise ValueError("List is invalid") - nums.sort() - return nums.pop() < sum(nums) + if len(nums) < 2: + raise ValueError("Monogons and Digons are not polygons in the Euclidean space") + if any(i <= 0 for i in nums): + raise ValueError("All values must be greater than 0") + copy_nums = nums.copy() + copy_nums.sort() + return copy_nums[-1] < sum(copy_nums[:-1]) if __name__ == "__main__": From 9153db2d275086bb951b696b3a4628c76a14ac90 Mon Sep 17 00:00:00 2001 From: Sherman Hui <11592023+shermanhui@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:39:18 -0700 Subject: [PATCH 1620/2908] [mypy] fix: fix mypy error in singly_linked_list.py (#5517) The list comprehension shortcut was implicitly expecting a return value causing a mypy error since `insert_tail` doesn't return a value --- data_structures/linked_list/singly_linked_list.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 4a5dc8263f79..a4156b650776 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -409,7 +409,9 @@ def test_singly_linked_list_2() -> None: 12.20, ] linked_list = LinkedList() - [linked_list.insert_tail(i) for i in input] + + for i in input: + linked_list.insert_tail(i) # Check if it's empty or not assert linked_list.is_empty() is False From b373c991f69e20d7e1dc92d1613e60a5605bf1a8 Mon Sep 17 00:00:00 2001 From: Sherman Hui <11592023+shermanhui@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:40:17 -0700 Subject: [PATCH 1621/2908] [mypy] fix: fix mypy error in trie.py(#5516) --- data_structures/trie/trie.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/trie/trie.py b/data_structures/trie/trie.py index 6582be24fd0c..766294c23ac8 100644 --- a/data_structures/trie/trie.py +++ b/data_structures/trie/trie.py @@ -11,7 +11,7 @@ def __init__(self): self.nodes = dict() # Mapping from char to TrieNode self.is_leaf = False - def insert_many(self, words: [str]): + def insert_many(self, words: list[str]): """ Inserts a list of words into the Trie :param words: list of string words From 57a7e5738b8224f58941019964da67ece679eab9 Mon Sep 17 00:00:00 2001 From: Jenny Vo <40080855+ovynnej@users.noreply.github.com> Date: Fri, 22 Oct 2021 04:52:39 +0100 Subject: [PATCH 1622/2908] Add implementation of Coulomb's Law (#4897) --- electronics/coulombs_law.py | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 electronics/coulombs_law.py diff --git a/electronics/coulombs_law.py b/electronics/coulombs_law.py new file mode 100644 index 000000000000..e4c8391c9f9a --- /dev/null +++ b/electronics/coulombs_law.py @@ -0,0 +1,86 @@ +# https://en.wikipedia.org/wiki/Coulomb%27s_law + +from __future__ import annotations + +COULOMBS_CONSTANT = 8.988e9 # units = N * m^s * C^-2 + + +def couloumbs_law( + force: float, charge1: float, charge2: float, distance: float +) -> dict[str, float]: + + """ + Apply Coulomb's Law on any three given values. These can be force, charge1, + charge2, or distance, and then in a Python dict return name/value pair of + the zero value. + + Coulomb's Law states that the magnitude of the electrostatic force of + attraction or repulsion between two point charges is directly proportional + to the product of the magnitudes of charges and inversely proportional to + the square of the distance between them. + + Reference + ---------- + Coulomb (1785) "Premier mémoire sur l’électricité et le magnétisme," + Histoire de l’Académie Royale des Sciences, pp. 569–577. + + Parameters + ---------- + force : float with units in Newtons + + charge1 : float with units in Coulombs + + charge2 : float with units in Coulombs + + distance : float with units in meters + + Returns + ------- + result : dict name/value pair of the zero value + + >>> couloumbs_law(force=0, charge1=3, charge2=5, distance=2000) + {'force': 33705.0} + + >>> couloumbs_law(force=10, charge1=3, charge2=5, distance=0) + {'distance': 116112.01488218177} + + >>> couloumbs_law(force=10, charge1=0, charge2=5, distance=2000) + {'charge1': 0.0008900756564307966} + + >>> couloumbs_law(force=0, charge1=0, charge2=5, distance=2000) + Traceback (most recent call last): + ... + ValueError: One and only one argument must be 0 + + >>> couloumbs_law(force=0, charge1=3, charge2=5, distance=-2000) + Traceback (most recent call last): + ... + ValueError: Distance cannot be negative + + """ + + charge_product = abs(charge1 * charge2) + + if (force, charge1, charge2, distance).count(0) != 1: + raise ValueError("One and only one argument must be 0") + if distance < 0: + raise ValueError("Distance cannot be negative") + if force == 0: + force = COULOMBS_CONSTANT * charge_product / (distance ** 2) + return {"force": force} + elif charge1 == 0: + charge1 = abs(force) * (distance ** 2) / (COULOMBS_CONSTANT * charge2) + return {"charge1": charge1} + elif charge2 == 0: + charge2 = abs(force) * (distance ** 2) / (COULOMBS_CONSTANT * charge1) + return {"charge2": charge2} + elif distance == 0: + distance = (COULOMBS_CONSTANT * charge_product / abs(force)) ** 0.5 + return {"distance": distance} + raise ValueError("Exactly one argument must be 0") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 83a63d9c22cb0003c4e279cf5e22a2f07b83d652 Mon Sep 17 00:00:00 2001 From: Erwin Junge Date: Fri, 22 Oct 2021 06:14:45 +0200 Subject: [PATCH 1623/2908] [mypy] Add missing type annotation in conways_game_of_life.py (#5490) --- cellular_automata/conways_game_of_life.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellular_automata/conways_game_of_life.py b/cellular_automata/conways_game_of_life.py index 079fb4d04499..84f4d5be40da 100644 --- a/cellular_automata/conways_game_of_life.py +++ b/cellular_automata/conways_game_of_life.py @@ -70,7 +70,7 @@ def new_generation(cells: list[list[int]]) -> list[list[int]]: return next_generation -def generate_images(cells: list[list[int]], frames) -> list[Image.Image]: +def generate_images(cells: list[list[int]], frames: int) -> list[Image.Image]: """ Generates a list of images of subsequent Game of Life states. """ From 08254eb2e4da3fba23d019f39f7f22a05532cd0e Mon Sep 17 00:00:00 2001 From: Erwin Junge Date: Fri, 22 Oct 2021 11:45:19 +0200 Subject: [PATCH 1624/2908] [mypy] Fix type annotations for boolean_algebra/quine_mc_cluskey.py (#5489) * Add missing type annotation * Fix conversion bug This failed when called with the documented example of `1.5` and was correctly pointed out by `mypy --strict` --- boolean_algebra/quine_mc_cluskey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 9cc99b1eeabb..0342e5c67753 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -146,10 +146,10 @@ def prime_implicant_chart( return chart -def main(): +def main() -> None: no_of_variable = int(input("Enter the no. of variables\n")) minterms = [ - int(x) + float(x) for x in input( "Enter the decimal representation of Minterms 'Spaces Separated'\n" ).split() From d924a8051bfe0fe8de164e5074eb4f5f8fa6afb3 Mon Sep 17 00:00:00 2001 From: Erwin Junge Date: Fri, 22 Oct 2021 11:45:30 +0200 Subject: [PATCH 1625/2908] [mypy] Add missing type annotation (#5491) --- cellular_automata/game_of_life.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cellular_automata/game_of_life.py b/cellular_automata/game_of_life.py index 09863993dc3a..c5324da73dbf 100644 --- a/cellular_automata/game_of_life.py +++ b/cellular_automata/game_of_life.py @@ -40,18 +40,18 @@ random.shuffle(choice) -def create_canvas(size): +def create_canvas(size: int) -> list[list[bool]]: canvas = [[False for i in range(size)] for j in range(size)] return canvas -def seed(canvas): +def seed(canvas: list[list[bool]]) -> None: for i, row in enumerate(canvas): for j, _ in enumerate(row): canvas[i][j] = bool(random.getrandbits(1)) -def run(canvas): +def run(canvas: list[list[bool]]) -> list[list[bool]]: """This function runs the rules of game through all points, and changes their status accordingly.(in the same canvas) @Args: @@ -62,21 +62,22 @@ def run(canvas): -- None """ - canvas = np.array(canvas) - next_gen_canvas = np.array(create_canvas(canvas.shape[0])) - for r, row in enumerate(canvas): + current_canvas = np.array(canvas) + next_gen_canvas = np.array(create_canvas(current_canvas.shape[0])) + for r, row in enumerate(current_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] + pt, current_canvas[r - 1 : r + 2, c - 1 : c + 2] ) - canvas = next_gen_canvas + current_canvas = next_gen_canvas del next_gen_canvas # cleaning memory as we move on. - return canvas.tolist() + return_canvas: list[list[bool]] = current_canvas.tolist() + return return_canvas -def __judge_point(pt, neighbours): +def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool: dead = 0 alive = 0 # finding dead or alive neighbours count. From 061614880d0c4e1b3483665b1b82cc124932ff51 Mon Sep 17 00:00:00 2001 From: poloso Date: Fri, 22 Oct 2021 05:07:28 -0500 Subject: [PATCH 1626/2908] [mypy] fix type annotations for graphs/a_star.py #4052 (#5224) * [mypy] fix type annotations for graphs/a_star.py #4052 * updating DIRECTORY.md * Add from __future__ import anotations * rename delta by DIRECTIONS Co-authored-by: John Law * Rename delta by DIRECTIONS in all code * Enclose script in __main__ code block * Refactor DIRECTIONS with comments for readibility * Delete heuristic example comment * Do not print, return all values * Fix multilines * fix black * Update a_star.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- graphs/a_star.py | 92 ++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/graphs/a_star.py b/graphs/a_star.py index d3657cb19540..e0f24734a4cb 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -1,37 +1,21 @@ -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]]""" - -init = [0, 0] -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])): - 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 - +from __future__ import annotations -# the actions we can take -delta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # go up # go left # go down # go right +DIRECTIONS = [ + [-1, 0], # left + [0, -1], # down + [1, 0], # right + [0, 1], # up +] # function to search the path -def search(grid, init, goal, cost, heuristic): +def search( + grid: list[list[int]], + init: list[int], + goal: list[int], + cost: int, + heuristic: list[list[int]], +) -> tuple[list[list[int]], list[list[int]]]: closed = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) @@ -52,7 +36,7 @@ def search(grid, init, goal, cost, heuristic): while not found and not resign: if len(cell) == 0: - return "FAIL" + raise ValueError("Algorithm is unable to find solution") else: # to choose the least costliest action so as to move closer to the goal cell.sort() cell.reverse() @@ -64,9 +48,9 @@ def search(grid, init, goal, cost, heuristic): if x == goal[0] and y == goal[1]: found = True else: - for i in range(len(delta)): # to try out different valid actions - x2 = x + delta[i][0] - y2 = y + delta[i][1] + for i in range(len(DIRECTIONS)): # to try out different valid actions + x2 = x + DIRECTIONS[i][0] + y2 = y + DIRECTIONS[i][1] 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 @@ -79,8 +63,8 @@ def search(grid, init, goal, cost, heuristic): y = goal[1] 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] + x2 = x - DIRECTIONS[action[x][y]][0] + y2 = y - DIRECTIONS[action[x][y]][1] x = x2 y = y2 invpath.append([x, y]) @@ -88,13 +72,37 @@ def search(grid, init, goal, cost, heuristic): path = [] for i in range(len(invpath)): path.append(invpath[len(invpath) - 1 - i]) + return path, action + + +if __name__ == "__main__": + 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], + ] + + init = [0, 0] + # all coordinates are given in format [y,x] + goal = [len(grid) - 1, len(grid[0]) - 1] + 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])): + heuristic[i][j] = abs(i - goal[0]) + abs(j - goal[1]) + if grid[i][j] == 1: + # added extra penalty in the heuristic map + heuristic[i][j] = 99 + + path, action = 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]) + for i in range(len(path)): + print(path[i]) From d82cf5292fbd0ffe1764a1da5c96a39b244618eb Mon Sep 17 00:00:00 2001 From: QuartzAl <55610038+QuartzAl@users.noreply.github.com> Date: Fri, 22 Oct 2021 18:14:35 +0700 Subject: [PATCH 1627/2908] split into usable functions and added docstrings for base32 cipher (#5466) * split into usable functions and added docstrings * Simplified code Co-authored-by: Christian Clauss * Simplified code Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- ciphers/base32.py | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/ciphers/base32.py b/ciphers/base32.py index da289a7210e8..fee53ccaf0c4 100644 --- a/ciphers/base32.py +++ b/ciphers/base32.py @@ -1,13 +1,42 @@ import base64 -def main() -> None: - 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 +def base32_encode(string: str) -> bytes: + """ + Encodes a given string to base32, returning a bytes-like object + >>> base32_encode("Hello World!") + b'JBSWY3DPEBLW64TMMQQQ====' + >>> base32_encode("123456") + b'GEZDGNBVGY======' + >>> base32_encode("some long complex string") + b'ONXW2ZJANRXW4ZZAMNXW24DMMV4CA43UOJUW4ZY=' + """ + + # encoded the input (we need a bytes like object) + # then, b32encoded the bytes-like object + return base64.b32encode(string.encode("utf-8")) + + +def base32_decode(encoded_bytes: bytes) -> str: + """ + Decodes a given bytes-like object to a string, returning a string + >>> base32_decode(b'JBSWY3DPEBLW64TMMQQQ====') + 'Hello World!' + >>> base32_decode(b'GEZDGNBVGY======') + '123456' + >>> base32_decode(b'ONXW2ZJANRXW4ZZAMNXW24DMMV4CA43UOJUW4ZY=') + 'some long complex string' + """ + + # decode the bytes from base32 + # then, decode the bytes-like object to return as a string + return base64.b32decode(encoded_bytes).decode("utf-8") if __name__ == "__main__": - main() + test = "Hello World!" + encoded = base32_encode(test) + print(encoded) + + decoded = base32_decode(encoded) + print(decoded) From 629848e3721d9354d25fad6cb4729e6afdbbf799 Mon Sep 17 00:00:00 2001 From: Sherman Hui <11592023+shermanhui@users.noreply.github.com> Date: Fri, 22 Oct 2021 07:07:05 -0700 Subject: [PATCH 1628/2908] [mypy] Fix type annotations in `data_structures/binary_tree` (#5518) * fix: fix mypy errors Update binary_search_tree `arr` argument to be typed as a list within `find_kth_smallest` function Update return type of `merge_two_binary_trees` as both inputs can be None which means that a None type value can be returned from this function * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + data_structures/binary_tree/binary_search_tree.py | 2 +- data_structures/binary_tree/merge_two_binary_trees.py | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 10149eac5aac..950d8e2c0c4b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -272,6 +272,7 @@ ## Electronics * [Carrier Concentration](https://github.com/TheAlgorithms/Python/blob/master/electronics/carrier_concentration.py) + * [Coulombs Law](https://github.com/TheAlgorithms/Python/blob/master/electronics/coulombs_law.py) * [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) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index a1ed1d0ac2a5..ce490fd98524 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -151,7 +151,7 @@ def inorder(self, arr: list, node: Node): def find_kth_smallest(self, k: int, node: Node) -> int: """Return the kth smallest element in a binary search tree""" - arr = [] + arr: list = [] self.inorder(arr, node) # append all values to list using inorder traversal return arr[k - 1] diff --git a/data_structures/binary_tree/merge_two_binary_trees.py b/data_structures/binary_tree/merge_two_binary_trees.py index d169e0e75b82..7487268940d3 100644 --- a/data_structures/binary_tree/merge_two_binary_trees.py +++ b/data_structures/binary_tree/merge_two_binary_trees.py @@ -7,6 +7,8 @@ """ from __future__ import annotations +from typing import Optional + class Node: """ @@ -19,7 +21,7 @@ def __init__(self, value: int = 0) -> None: self.right: Node | None = None -def merge_two_binary_trees(tree1: Node | None, tree2: Node | None) -> Node: +def merge_two_binary_trees(tree1: Node | None, tree2: Node | None) -> Optional[Node]: """ Returns root node of the merged tree. From 0ca12799974aa2d0756aec608d38fea04f1239d7 Mon Sep 17 00:00:00 2001 From: Toki345 <91814435+Toki345@users.noreply.github.com> Date: Fri, 22 Oct 2021 22:07:57 +0800 Subject: [PATCH 1629/2908] Fixed grammar on Anagram Description (#5512) Made the description more formal, also fixed a few grammatical issues. --- strings/check_anagrams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index 3083000cbb5d..62a4441a0c00 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -5,7 +5,7 @@ def check_anagrams(first_str: str, second_str: str) -> bool: """ - Two strings are anagrams if they are made of the same letters + Two strings are anagrams if they are made up of the same letters but are arranged differently (ignoring the case). >>> check_anagrams('Silent', 'Listen') True From 2ddd81df211332b580887a929367a1d529c56dbe Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <89947037+Rohanrbharadwaj@users.noreply.github.com> Date: Fri, 22 Oct 2021 22:44:08 +0530 Subject: [PATCH 1630/2908] Remove wrongly placed double qoutes (#5530) * Update baconian_cipher.py * Update join.py * Updated type hint --- ciphers/baconian_cipher.py | 2 +- strings/join.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ciphers/baconian_cipher.py b/ciphers/baconian_cipher.py index 027fbc50e89d..f146ba91b78f 100644 --- a/ciphers/baconian_cipher.py +++ b/ciphers/baconian_cipher.py @@ -83,7 +83,7 @@ def decode(coded: str) -> str: return decoded.strip() -if "__name__" == "__main__": +if __name__ == "__main__": from doctest import testmod testmod() diff --git a/strings/join.py b/strings/join.py index 0cb88b76065d..c17ddd144597 100644 --- a/strings/join.py +++ b/strings/join.py @@ -3,7 +3,7 @@ """ -def join(separator: str, separated: list) -> str: +def join(separator: str, separated: list[str]) -> str: """ >>> join("", ["a", "b", "c", "d"]) 'abcd' @@ -26,7 +26,7 @@ def join(separator: str, separated: list) -> str: return joined.strip(separator) -if "__name__" == "__main__": +if __name__ == "__main__": from doctest import testmod testmod() From 07141e4bcce7770300f4cf7c3042bdfc5e3c9934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Murilo=20Gon=C3=A7alves?= <38800183+murilo-goncalves@users.noreply.github.com> Date: Fri, 22 Oct 2021 14:14:27 -0300 Subject: [PATCH 1631/2908] Add doctests to prime_check function (#5503) * added doctests to prime_check function * fix doctests function name --- maths/prime_check.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/maths/prime_check.py b/maths/prime_check.py index e2bcb7b8f151..92d31cfeee80 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -5,9 +5,28 @@ def prime_check(number: int) -> bool: - """Checks to see if a number is a prime. + """Checks to see if a number is a prime in O(sqrt(n)). A number is prime if it has exactly two factors: 1 and itself. + + >>> prime_check(0) + False + >>> prime_check(1) + False + >>> prime_check(2) + True + >>> prime_check(3) + True + >>> prime_check(27) + False + >>> prime_check(87) + False + >>> prime_check(563) + True + >>> prime_check(2999) + True + >>> prime_check(67483) + False """ if 1 < number < 4: From 11ec2fd3fb472a8bcac738f372e6e0f731326d3b Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Fri, 22 Oct 2021 10:21:41 -0700 Subject: [PATCH 1632/2908] [mypy] Fix type annotations for trie.py (#5022) * Fix type annotations for trie.py * Add strict type annotations to trie.py Annotate return type for all functions and type for "nodes" * updating DIRECTORY.md * Format trie.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- data_structures/trie/trie.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/data_structures/trie/trie.py b/data_structures/trie/trie.py index 766294c23ac8..162d08d1d678 100644 --- a/data_structures/trie/trie.py +++ b/data_structures/trie/trie.py @@ -7,11 +7,11 @@ class TrieNode: - def __init__(self): - self.nodes = dict() # Mapping from char to TrieNode + def __init__(self) -> None: + self.nodes: dict[str, TrieNode] = dict() # Mapping from char to TrieNode self.is_leaf = False - def insert_many(self, words: list[str]): + def insert_many(self, words: list[str]) -> None: """ Inserts a list of words into the Trie :param words: list of string words @@ -20,7 +20,7 @@ def insert_many(self, words: list[str]): for word in words: self.insert(word) - def insert(self, word: str): + def insert(self, word: str) -> None: """ Inserts a word into the Trie :param word: word to be inserted @@ -46,14 +46,14 @@ def find(self, word: str) -> bool: curr = curr.nodes[char] return curr.is_leaf - def delete(self, word: str): + def delete(self, word: str) -> None: """ Deletes a word in a Trie :param word: word to delete :return: None """ - def _delete(curr: TrieNode, word: str, index: int): + def _delete(curr: TrieNode, word: str, index: int) -> bool: if index == len(word): # If word does not exist if not curr.is_leaf: @@ -75,7 +75,7 @@ def _delete(curr: TrieNode, word: str, index: int): _delete(self, word, 0) -def print_words(node: TrieNode, word: str): +def print_words(node: TrieNode, word: str) -> None: """ Prints all the words in a Trie :param node: root node of Trie @@ -89,7 +89,7 @@ def print_words(node: TrieNode, word: str): print_words(value, word + key) -def test_trie(): +def test_trie() -> bool: words = "banana bananas bandana band apple all beast".split() root = TrieNode() root.insert_many(words) @@ -112,11 +112,11 @@ 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_trie() -def main(): +def main() -> None: """ >>> pytests() """ From 20e09c3ec2021edf7e6cfd85299544c651012919 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Sat, 23 Oct 2021 06:56:58 -0300 Subject: [PATCH 1633/2908] [mypy] Add type annotations for linked queue in data structures (#5533) * [mypy] Add/fix type annotations for linked queue in data_structures * add return type annotation to __iter__ * Add more readable syntax --- data_structures/queue/linked_queue.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/data_structures/queue/linked_queue.py b/data_structures/queue/linked_queue.py index 8526ad311ed0..21970e7df965 100644 --- a/data_structures/queue/linked_queue.py +++ b/data_structures/queue/linked_queue.py @@ -1,11 +1,13 @@ """ A Queue using a linked list like structure """ -from typing import Any +from __future__ import annotations + +from typing import Any, Iterator class Node: def __init__(self, data: Any) -> None: - self.data = data - self.next = None + self.data: Any = data + self.next: Node | None = None def __str__(self) -> str: return f"{self.data}" @@ -39,9 +41,10 @@ class LinkedQueue: """ def __init__(self) -> None: - self.front = self.rear = None + self.front: Node | None = None + self.rear: Node | None = None - def __iter__(self): + def __iter__(self) -> Iterator[Any]: node = self.front while node: yield node.data @@ -87,7 +90,7 @@ def is_empty(self) -> bool: """ return len(self) == 0 - def put(self, item) -> None: + def put(self, item: Any) -> None: """ >>> queue = LinkedQueue() >>> queue.get() From c50f0c56aa23e8ab9ab019bc61f80465da135ef8 Mon Sep 17 00:00:00 2001 From: Atishaye Jain <64211411+atishaye@users.noreply.github.com> Date: Sat, 23 Oct 2021 15:59:42 +0530 Subject: [PATCH 1634/2908] add check_cycle.py (#5475) * add check_cycle.py * Update graphs/check_cycle.py Co-authored-by: John Law * Update check_cycle.py * Apply suggestions from code review Co-authored-by: John Law Co-authored-by: Christian Clauss --- graphs/check_cycle.py | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 graphs/check_cycle.py diff --git a/graphs/check_cycle.py b/graphs/check_cycle.py new file mode 100644 index 000000000000..71d42b4689b7 --- /dev/null +++ b/graphs/check_cycle.py @@ -0,0 +1,55 @@ +""" +Program to check if a cycle is present in a given graph +""" + + +def check_cycle(graph: dict) -> bool: + """ + Returns True if graph is cyclic else False + + >>> check_cycle(graph={0:[], 1:[0, 3], 2:[0, 4], 3:[5], 4:[5], 5:[]}) + False + >>> check_cycle(graph={0:[1, 2], 1:[2], 2:[0, 3], 3:[3]}) + True + """ + # Keep track of visited nodes + visited = set() + # To detect a back edge, keep track of vertices currently in the recursion stack + rec_stk = set() + for node in graph: + if node not in visited: + if depth_first_search(graph, node, visited, rec_stk): + return True + return False + + +def depth_first_search(graph: dict, vertex: int, visited: set, rec_stk: set) -> bool: + """ + Recur for all neighbours. + If any neighbour is visited and in rec_stk then graph is cyclic. + + >>> graph = {0:[], 1:[0, 3], 2:[0, 4], 3:[5], 4:[5], 5:[]} + >>> vertex, visited, rec_stk = 0, set(), set() + >>> depth_first_search(graph, vertex, visited, rec_stk) + False + """ + # Mark current node as visited and add to recursion stack + visited.add(vertex) + rec_stk.add(vertex) + + for node in graph[vertex]: + if node not in visited: + if depth_first_search(graph, node, visited, rec_stk): + return True + elif node in rec_stk: + return True + + # The node needs to be removed from recursion stack before function ends + rec_stk.remove(vertex) + return False + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 27f2465135dc00abb6ec569b050780e9e62d02f3 Mon Sep 17 00:00:00 2001 From: Jaydeep Das Date: Sat, 23 Oct 2021 16:26:26 +0530 Subject: [PATCH 1635/2908] Added new file: nasa_data.py (#5543) * Added new file: nasa_data.py * Modified as per review * Minor change * print(get_archive_data("apollo 2011")["collection"]["items"][0]["data"][0]["description"]) * Update nasa_data.py Co-authored-by: Christian Clauss --- web_programming/nasa_data.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 web_programming/nasa_data.py diff --git a/web_programming/nasa_data.py b/web_programming/nasa_data.py new file mode 100644 index 000000000000..9b15c38a7f05 --- /dev/null +++ b/web_programming/nasa_data.py @@ -0,0 +1,27 @@ +import requests + + +def get_apod_data(api_key: str) -> dict: + """ + Get the APOD(Astronomical Picture of the day) data + Get the API Key from : https://api.nasa.gov/ + """ + url = "https://api.nasa.gov/planetary/apod/" + return requests.get(url, params={"api_key": api_key}).json() + + +def get_archive_data(query: str) -> dict: + """ + Get the data of a particular query from NASA archives + """ + endpoint = "https://images-api.nasa.gov/search" + return requests.get(endpoint, params={"q": query}).json() + + +if __name__ == "__main__": + print(get_apod_data("YOUR API KEY")) + print( + get_archive_data("apollo 2011")["collection"]["items"][0]["data"][0][ + "description" + ] + ) From b64fd56776f03342559034df27948269d49c3375 Mon Sep 17 00:00:00 2001 From: Jaydeep Das Date: Sat, 23 Oct 2021 18:08:25 +0530 Subject: [PATCH 1636/2908] Added feature to `web_programming/nasa_data.py` : Can download the APOD image to a specified location on disk. (#5551) * Added a feature to download images. * Minor changes * Update nasa_data.py * : Co-authored-by: Christian Clauss --- web_programming/nasa_data.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/web_programming/nasa_data.py b/web_programming/nasa_data.py index 9b15c38a7f05..c0a2c4fdd1a7 100644 --- a/web_programming/nasa_data.py +++ b/web_programming/nasa_data.py @@ -1,27 +1,38 @@ +import shutil + import requests -def get_apod_data(api_key: str) -> dict: +def get_apod_data(api_key: str, download: bool = False, path: str = ".") -> dict: """ Get the APOD(Astronomical Picture of the day) data - Get the API Key from : https://api.nasa.gov/ + Get your API Key from: https://api.nasa.gov/ """ - url = "https://api.nasa.gov/planetary/apod/" + url = "https://api.nasa.gov/planetary/apod" return requests.get(url, params={"api_key": api_key}).json() +def save_apod(api_key: str, path: str = ".") -> dict: + apod_data = get_apod_data(api_key) + img_url = apod_data["url"] + img_name = img_url.split("/")[-1] + response = requests.get(img_url, stream=True) + + with open(f"{path}/{img_name}", "wb+") as img_file: + shutil.copyfileobj(response.raw, img_file) + del response + return apod_data + + def get_archive_data(query: str) -> dict: """ Get the data of a particular query from NASA archives """ - endpoint = "https://images-api.nasa.gov/search" - return requests.get(endpoint, params={"q": query}).json() + url = "https://images-api.nasa.gov/search" + return requests.get(url, params={"q": query}).json() if __name__ == "__main__": - print(get_apod_data("YOUR API KEY")) - print( - get_archive_data("apollo 2011")["collection"]["items"][0]["data"][0][ - "description" - ] - ) + print(save_apod("YOUR API KEY")) + apollo_2011_items = get_archive_data("apollo 2011")["collection"]["items"] + print(apollo_2011_items[0]["data"][0]["description"]) From b72a66b713bb998354df7bfd165c179a756e3b91 Mon Sep 17 00:00:00 2001 From: Mitheel <81575947+mitheelgajare@users.noreply.github.com> Date: Sat, 23 Oct 2021 18:24:41 +0530 Subject: [PATCH 1637/2908] Fixed grammatical errors in CONTRIBUTING.md (#5555) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4723c6c39a7d..f5c123674f4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Befo ### 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: +We are very happy that you consider implementing algorithms and data structures 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: - You did your work - no plagiarism allowed - Any plagiarized work will not be merged. @@ -25,7 +25,7 @@ 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. +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? From 80a885c97563ffc3d93a0678bd7219945be1a166 Mon Sep 17 00:00:00 2001 From: Limbad Yash <56826569+limbad-YK@users.noreply.github.com> Date: Sat, 23 Oct 2021 18:48:09 +0530 Subject: [PATCH 1638/2908] Update pop function (#5544) * Updated Pop function Added underflow condition * Update Pop Function Added condition to check underflow of stack * Update stack.py * if not self.stack: raise StackUnderflowError * Add doctests * StackUnderflowError * ..., not .... * Update stack.py Co-authored-by: Christian Clauss --- data_structures/stacks/stack.py | 38 ++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index c62412150626..4bc032f72561 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -5,6 +5,10 @@ class StackOverflowError(BaseException): pass +class StackUnderflowError(BaseException): + pass + + 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 @@ -31,11 +35,29 @@ def push(self, data): self.stack.append(data) def pop(self): - """Pop an element off of the top of the stack.""" + """ + Pop an element off of the top of the stack. + + >>> Stack().pop() + Traceback (most recent call last): + ... + data_structures.stacks.stack.StackUnderflowError + """ + if not self.stack: + raise StackUnderflowError return self.stack.pop() def peek(self): - """Peek at the top-most element of the stack.""" + """ + Peek at the top-most element of the stack. + + >>> Stack().pop() + Traceback (most recent call last): + ... + data_structures.stacks.stack.StackUnderflowError + """ + if not self.stack: + raise StackUnderflowError return self.stack[-1] def is_empty(self) -> bool: @@ -67,22 +89,22 @@ def test_stack() -> None: try: _ = stack.pop() assert False # This should not happen - except IndexError: + except StackUnderflowError: assert True # This should happen try: _ = stack.peek() assert False # This should not happen - except IndexError: + except StackUnderflowError: assert True # This should happen for i in range(10): assert stack.size() == i stack.push(i) - assert bool(stack) is True - assert stack.is_empty() is False - assert stack.is_full() is True + assert bool(stack) + assert not stack.is_empty() + assert stack.is_full() assert str(stack) == str(list(range(10))) assert stack.pop() == 9 assert stack.peek() == 8 @@ -96,7 +118,7 @@ def test_stack() -> None: except StackOverflowError: assert True # This should happen - assert stack.is_empty() is False + assert not stack.is_empty() assert stack.size() == 10 assert 5 in stack From 218d8921dbb264c9636bef5bd10e23acafd032eb Mon Sep 17 00:00:00 2001 From: Studiex <80968515+Studiex@users.noreply.github.com> Date: Sat, 23 Oct 2021 17:20:52 +0400 Subject: [PATCH 1639/2908] Implementation of SHA-256 using Python (#5532) * Add files via upload * Update sha256.py * Update sha256.py * Update sha256.py * Update sha256.py * Update sha256.py * Update sha256.py * Update sha256.py * Update sha256.py * @staticmethod def preprocessing(data: bytes) -> bytes: Co-authored-by: Christian Clauss --- hashes/sha256.py | 248 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 hashes/sha256.py diff --git a/hashes/sha256.py b/hashes/sha256.py new file mode 100644 index 000000000000..9d4f250fe353 --- /dev/null +++ b/hashes/sha256.py @@ -0,0 +1,248 @@ +# Author: M. Yathurshan +# Black Formatter: True + +""" +Implementation of SHA256 Hash function in a Python class and provides utilities +to find hash of string or hash of text from a file. + +Usage: python sha256.py --string "Hello World!!" + python sha256.py --file "hello_world.txt" + When run without any arguments, + it prints the hash of the string "Hello World!! Welcome to Cryptography" + +References: +https://qvault.io/cryptography/how-sha-2-works-step-by-step-sha-256/ +https://en.wikipedia.org/wiki/SHA-2 +""" + +import argparse +import struct +import unittest + + +class SHA256: + """ + Class to contain the entire pipeline for SHA1 Hashing Algorithm + + >>> SHA256(b'Python').hash + '18885f27b5af9012df19e496460f9294d5ab76128824c6f993787004f6d9a7db' + + >>> SHA256(b'hello world').hash + 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9' + """ + + def __init__(self, data: bytes) -> None: + self.data = data + + # Initialize hash values + self.hashes = [ + 0x6A09E667, + 0xBB67AE85, + 0x3C6EF372, + 0xA54FF53A, + 0x510E527F, + 0x9B05688C, + 0x1F83D9AB, + 0x5BE0CD19, + ] + + # Initialize round constants + self.round_constants = [ + 0x428A2F98, + 0x71374491, + 0xB5C0FBCF, + 0xE9B5DBA5, + 0x3956C25B, + 0x59F111F1, + 0x923F82A4, + 0xAB1C5ED5, + 0xD807AA98, + 0x12835B01, + 0x243185BE, + 0x550C7DC3, + 0x72BE5D74, + 0x80DEB1FE, + 0x9BDC06A7, + 0xC19BF174, + 0xE49B69C1, + 0xEFBE4786, + 0x0FC19DC6, + 0x240CA1CC, + 0x2DE92C6F, + 0x4A7484AA, + 0x5CB0A9DC, + 0x76F988DA, + 0x983E5152, + 0xA831C66D, + 0xB00327C8, + 0xBF597FC7, + 0xC6E00BF3, + 0xD5A79147, + 0x06CA6351, + 0x14292967, + 0x27B70A85, + 0x2E1B2138, + 0x4D2C6DFC, + 0x53380D13, + 0x650A7354, + 0x766A0ABB, + 0x81C2C92E, + 0x92722C85, + 0xA2BFE8A1, + 0xA81A664B, + 0xC24B8B70, + 0xC76C51A3, + 0xD192E819, + 0xD6990624, + 0xF40E3585, + 0x106AA070, + 0x19A4C116, + 0x1E376C08, + 0x2748774C, + 0x34B0BCB5, + 0x391C0CB3, + 0x4ED8AA4A, + 0x5B9CCA4F, + 0x682E6FF3, + 0x748F82EE, + 0x78A5636F, + 0x84C87814, + 0x8CC70208, + 0x90BEFFFA, + 0xA4506CEB, + 0xBEF9A3F7, + 0xC67178F2, + ] + + self.preprocessed_data = self.preprocessing(self.data) + self.final_hash() + + @staticmethod + def preprocessing(data: bytes) -> bytes: + padding = b"\x80" + (b"\x00" * (63 - (len(data) + 8) % 64)) + big_endian_integer = struct.pack(">Q", (len(data) * 8)) + return data + padding + big_endian_integer + + def final_hash(self) -> None: + # Convert into blocks of 64 bytes + self.blocks = [ + self.preprocessed_data[x : x + 64] + for x in range(0, len(self.preprocessed_data), 64) + ] + + for block in self.blocks: + # Convert the given block into a list of 4 byte integers + words = list(struct.unpack(">16L", block)) + # add 48 0-ed integers + words += [0] * 48 + + a, b, c, d, e, f, g, h = self.hashes + + for index in range(0, 64): + if index > 15: + # modify the zero-ed indexes at the end of the array + s0 = ( + self.ror(words[index - 15], 7) + ^ self.ror(words[index - 15], 18) + ^ (words[index - 15] >> 3) + ) + s1 = ( + self.ror(words[index - 2], 17) + ^ self.ror(words[index - 2], 19) + ^ (words[index - 2] >> 10) + ) + + words[index] = ( + words[index - 16] + s0 + words[index - 7] + s1 + ) % 0x100000000 + + # Compression + S1 = self.ror(e, 6) ^ self.ror(e, 11) ^ self.ror(e, 25) + ch = (e & f) ^ ((~e & (0xFFFFFFFF)) & g) + temp1 = ( + h + S1 + ch + self.round_constants[index] + words[index] + ) % 0x100000000 + S0 = self.ror(a, 2) ^ self.ror(a, 13) ^ self.ror(a, 22) + maj = (a & b) ^ (a & c) ^ (b & c) + temp2 = (S0 + maj) % 0x100000000 + + h, g, f, e, d, c, b, a = ( + g, + f, + e, + ((d + temp1) % 0x100000000), + c, + b, + a, + ((temp1 + temp2) % 0x100000000), + ) + + mutated_hash_values = [a, b, c, d, e, f, g, h] + + # Modify final values + self.hashes = [ + ((element + mutated_hash_values[index]) % 0x100000000) + for index, element in enumerate(self.hashes) + ] + + self.hash = "".join([hex(value)[2:].zfill(8) for value in self.hashes]) + + def ror(self, value: int, rotations: int) -> int: + """ + Right rotate a given unsigned number by a certain amount of rotations + """ + return 0xFFFFFFFF & (value << (32 - rotations)) | (value >> rotations) + + +class SHA256HashTest(unittest.TestCase): + """ + Test class for the SHA256 class. Inherits the TestCase class from unittest + """ + + def test_match_hashes(self) -> None: + import hashlib + + msg = bytes("Test String", "utf-8") + self.assertEqual(SHA256(msg).hash, hashlib.sha256(msg).hexdigest()) + + +def main() -> None: + """ + Provides option 'string' or 'file' to take input + and prints the calculated SHA-256 hash + """ + + # unittest.main() + + import doctest + + doctest.testmod() + + parser = argparse.ArgumentParser() + parser.add_argument( + "-s", + "--string", + dest="input_string", + default="Hello World!! Welcome to Cryptography", + help="Hash the string", + ) + parser.add_argument( + "-f", "--file", dest="input_file", help="Hash contents of a file" + ) + + args = parser.parse_args() + + input_string = args.input_string + + # hash input should be a bytestring + if args.input_file: + with open(args.input_file, "rb") as f: + hash_input = f.read() + else: + hash_input = bytes(input_string, "utf-8") + + print(SHA256(hash_input).hash) + + +if __name__ == "__main__": + main() From bd9464e4ac6ccccd4699bf52bddefa2bfb1dafea Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 23 Oct 2021 18:15:30 +0200 Subject: [PATCH 1640/2908] mandelbrot.py: Commenting out long running tests (#5558) * mandelbrot.py: Commenting out long running tests * updating DIRECTORY.md * Comment out 9 sec doctests * Update bidirectional_breadth_first_search.py * Comment out slow tests * Comment out slow (9.15 sec) pytests... * # Comment out slow (4.20s call) doctests * Comment out slow (3.45s) doctests * Update miller_rabin.py * Update miller_rabin.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 +++ fractals/mandelbrot.py | 6 ++++-- graphs/bidirectional_breadth_first_search.py | 15 +++++++++------ maths/miller_rabin.py | 3 ++- .../download_images_from_google_query.py | 5 +++-- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 950d8e2c0c4b..66d5f8040951 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -317,6 +317,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) + * [Check Cycle](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_cycle.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) @@ -370,6 +371,7 @@ * [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) + * [Sha256](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha256.py) ## Knapsack * [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/greedy_knapsack.py) @@ -979,6 +981,7 @@ * [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) + * [Nasa Data](https://github.com/TheAlgorithms/Python/blob/master/web_programming/nasa_data.py) * [Random Anime Character](https://github.com/TheAlgorithms/Python/blob/master/web_programming/random_anime_character.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) diff --git a/fractals/mandelbrot.py b/fractals/mandelbrot.py index de795bb3fc6f..5d61b72e172f 100644 --- a/fractals/mandelbrot.py +++ b/fractals/mandelbrot.py @@ -101,9 +101,11 @@ def get_image( of the Mandelbrot set is viewed. The main area of the Mandelbrot set is roughly between "-1.5 < x < 0.5" and "-1 < y < 1" in the figure-coordinates. - >>> get_image().load()[0,0] + Commenting out tests that slow down pytest... + # 13.35s call fractals/mandelbrot.py::mandelbrot.get_image + # >>> get_image().load()[0,0] (255, 0, 0) - >>> get_image(use_distance_color_coding = False).load()[0,0] + # >>> get_image(use_distance_color_coding = False).load()[0,0] (255, 255, 255) """ img = Image.new("RGB", (image_width, image_height)) diff --git a/graphs/bidirectional_breadth_first_search.py b/graphs/bidirectional_breadth_first_search.py index 27e4f0b16bbf..511b080a9add 100644 --- a/graphs/bidirectional_breadth_first_search.py +++ b/graphs/bidirectional_breadth_first_search.py @@ -34,16 +34,19 @@ def __init__( 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]) + # Comment out slow pytests... + # 9.15s call graphs/bidirectional_breadth_first_search.py:: \ + # graphs.bidirectional_breadth_first_search.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)] + # >>> [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]) + # >>> (bfs.start.pos_y + delta[2][0], bfs.start.pos_x + delta[2][1]) (1, 0) - >>> bfs.retrace_path(bfs.start) + # >>> bfs.retrace_path(bfs.start) [(0, 0)] - >>> bfs.search() # doctest: +NORMALIZE_WHITESPACE + # >>> 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)] """ diff --git a/maths/miller_rabin.py b/maths/miller_rabin.py index fe992027190b..2b0944508b4b 100644 --- a/maths/miller_rabin.py +++ b/maths/miller_rabin.py @@ -9,7 +9,8 @@ def is_prime(n, prec=1000): """ >>> from .prime_check import prime_check - >>> all(is_prime(i) == prime_check(i) for i in range(1000)) + >>> # all(is_prime(i) == prime_check(i) for i in range(1000)) # 3.45s + >>> all(is_prime(i) == prime_check(i) for i in range(256)) True """ if n < 2: diff --git a/web_programming/download_images_from_google_query.py b/web_programming/download_images_from_google_query.py index c26262788c4c..b11a7f883085 100644 --- a/web_programming/download_images_from_google_query.py +++ b/web_programming/download_images_from_google_query.py @@ -24,9 +24,10 @@ def download_images_from_google_query(query: str = "dhaka", max_images: int = 5) Returns: The number of images successfully downloaded. - >>> download_images_from_google_query() + # Comment out slow (4.20s call) doctests + # >>> download_images_from_google_query() 5 - >>> download_images_from_google_query("potato") + # >>> download_images_from_google_query("potato") 5 """ max_images = min(max_images, 50) # Prevent abuse! From 00a67010e8c28cdaa6142d0c4a386282bbf29421 Mon Sep 17 00:00:00 2001 From: Martmists Date: Sat, 23 Oct 2021 23:19:25 +0200 Subject: [PATCH 1641/2908] Simple audio filters (#5230) * Add IIR Filter and Butterworth design functions Signed-off-by: Martmists * naming conventions and missing type hints Signed-off-by: Martmists * Link wikipedia in IIRFilter Signed-off-by: Martmists * Add doctests and None return types Signed-off-by: Martmists * More doctests Signed-off-by: Martmists * Requested changes Signed-off-by: Martmists * run pre-commit Signed-off-by: Martmists * Make mypy stop complaining about ints vs floats Signed-off-by: Martmists * Use slower listcomp to make it more readable Signed-off-by: Martmists * Make doctests happy Signed-off-by: Martmists * Remove scipy Signed-off-by: Martmists * Test coefficients from bw filters Signed-off-by: Martmists * Protocol test Co-authored-by: Christian Clauss * Make requested change Signed-off-by: Martmists * Types Signed-off-by: Martmists * Apply suggestions from code review * Apply suggestions from code review * Update butterworth_filter.py Co-authored-by: Christian Clauss --- audio_filters/__init__.py | 0 audio_filters/butterworth_filter.py | 217 ++++++++++++++++++++++++++++ audio_filters/iir_filter.py | 92 ++++++++++++ audio_filters/show_response.py | 94 ++++++++++++ 4 files changed, 403 insertions(+) create mode 100644 audio_filters/__init__.py create mode 100644 audio_filters/butterworth_filter.py create mode 100644 audio_filters/iir_filter.py create mode 100644 audio_filters/show_response.py diff --git a/audio_filters/__init__.py b/audio_filters/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/audio_filters/butterworth_filter.py b/audio_filters/butterworth_filter.py new file mode 100644 index 000000000000..409cfeb1d95c --- /dev/null +++ b/audio_filters/butterworth_filter.py @@ -0,0 +1,217 @@ +from math import cos, sin, sqrt, tau + +from audio_filters.iir_filter import IIRFilter + +""" +Create 2nd-order IIR filters with Butterworth design. + +Code based on https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html +Alternatively you can use scipy.signal.butter, which should yield the same results. +""" + + +def make_lowpass( + frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) +) -> IIRFilter: + """ + Creates a low-pass filter + + >>> filter = make_lowpass(1000, 48000) + >>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE + [1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.004277569313094809, + 0.008555138626189618, 0.004277569313094809] + """ + w0 = tau * frequency / samplerate + _sin = sin(w0) + _cos = cos(w0) + alpha = _sin / (2 * q_factor) + + b0 = (1 - _cos) / 2 + b1 = 1 - _cos + + a0 = 1 + alpha + a1 = -2 * _cos + a2 = 1 - alpha + + filt = IIRFilter(2) + filt.set_coefficients([a0, a1, a2], [b0, b1, b0]) + return filt + + +def make_highpass( + frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) +) -> IIRFilter: + """ + Creates a high-pass filter + + >>> filter = make_highpass(1000, 48000) + >>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE + [1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.9957224306869052, + -1.9914448613738105, 0.9957224306869052] + """ + w0 = tau * frequency / samplerate + _sin = sin(w0) + _cos = cos(w0) + alpha = _sin / (2 * q_factor) + + b0 = (1 + _cos) / 2 + b1 = -1 - _cos + + a0 = 1 + alpha + a1 = -2 * _cos + a2 = 1 - alpha + + filt = IIRFilter(2) + filt.set_coefficients([a0, a1, a2], [b0, b1, b0]) + return filt + + +def make_bandpass( + frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) +) -> IIRFilter: + """ + Creates a band-pass filter + + >>> filter = make_bandpass(1000, 48000) + >>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE + [1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.06526309611002579, + 0, -0.06526309611002579] + """ + w0 = tau * frequency / samplerate + _sin = sin(w0) + _cos = cos(w0) + alpha = _sin / (2 * q_factor) + + b0 = _sin / 2 + b1 = 0 + b2 = -b0 + + a0 = 1 + alpha + a1 = -2 * _cos + a2 = 1 - alpha + + filt = IIRFilter(2) + filt.set_coefficients([a0, a1, a2], [b0, b1, b2]) + return filt + + +def make_allpass( + frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) +) -> IIRFilter: + """ + Creates an all-pass filter + + >>> filter = make_allpass(1000, 48000) + >>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE + [1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.9077040443587427, + -1.9828897227476208, 1.0922959556412573] + """ + w0 = tau * frequency / samplerate + _sin = sin(w0) + _cos = cos(w0) + alpha = _sin / (2 * q_factor) + + b0 = 1 - alpha + b1 = -2 * _cos + b2 = 1 + alpha + + filt = IIRFilter(2) + filt.set_coefficients([b2, b1, b0], [b0, b1, b2]) + return filt + + +def make_peak( + frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2) +) -> IIRFilter: + """ + Creates a peak filter + + >>> filter = make_peak(1000, 48000, 6) + >>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE + [1.0653405327119334, -1.9828897227476208, 0.9346594672880666, 1.1303715025601122, + -1.9828897227476208, 0.8696284974398878] + """ + w0 = tau * frequency / samplerate + _sin = sin(w0) + _cos = cos(w0) + alpha = _sin / (2 * q_factor) + big_a = 10 ** (gain_db / 40) + + b0 = 1 + alpha * big_a + b1 = -2 * _cos + b2 = 1 - alpha * big_a + a0 = 1 + alpha / big_a + a1 = -2 * _cos + a2 = 1 - alpha / big_a + + filt = IIRFilter(2) + filt.set_coefficients([a0, a1, a2], [b0, b1, b2]) + return filt + + +def make_lowshelf( + frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2) +) -> IIRFilter: + """ + Creates a low-shelf filter + + >>> filter = make_lowshelf(1000, 48000, 6) + >>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE + [3.0409336710888786, -5.608870992220748, 2.602157875636628, 3.139954022810743, + -5.591841778072785, 2.5201667380627257] + """ + w0 = tau * frequency / samplerate + _sin = sin(w0) + _cos = cos(w0) + alpha = _sin / (2 * q_factor) + big_a = 10 ** (gain_db / 40) + pmc = (big_a + 1) - (big_a - 1) * _cos + ppmc = (big_a + 1) + (big_a - 1) * _cos + mpc = (big_a - 1) - (big_a + 1) * _cos + pmpc = (big_a - 1) + (big_a + 1) * _cos + aa2 = 2 * sqrt(big_a) * alpha + + b0 = big_a * (pmc + aa2) + b1 = 2 * big_a * mpc + b2 = big_a * (pmc - aa2) + a0 = ppmc + aa2 + a1 = -2 * pmpc + a2 = ppmc - aa2 + + filt = IIRFilter(2) + filt.set_coefficients([a0, a1, a2], [b0, b1, b2]) + return filt + + +def make_highshelf( + frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2) +) -> IIRFilter: + """ + Creates a high-shelf filter + + >>> filter = make_highshelf(1000, 48000, 6) + >>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE + [2.2229172136088806, -3.9587208137297303, 1.7841414181566304, 4.295432981120543, + -7.922740859457287, 3.6756456963725253] + """ + w0 = tau * frequency / samplerate + _sin = sin(w0) + _cos = cos(w0) + alpha = _sin / (2 * q_factor) + big_a = 10 ** (gain_db / 40) + pmc = (big_a + 1) - (big_a - 1) * _cos + ppmc = (big_a + 1) + (big_a - 1) * _cos + mpc = (big_a - 1) - (big_a + 1) * _cos + pmpc = (big_a - 1) + (big_a + 1) * _cos + aa2 = 2 * sqrt(big_a) * alpha + + b0 = big_a * (ppmc + aa2) + b1 = -2 * big_a * pmpc + b2 = big_a * (ppmc - aa2) + a0 = pmc + aa2 + a1 = 2 * mpc + a2 = pmc - aa2 + + filt = IIRFilter(2) + filt.set_coefficients([a0, a1, a2], [b0, b1, b2]) + return filt diff --git a/audio_filters/iir_filter.py b/audio_filters/iir_filter.py new file mode 100644 index 000000000000..aae320365012 --- /dev/null +++ b/audio_filters/iir_filter.py @@ -0,0 +1,92 @@ +from __future__ import annotations + + +class IIRFilter: + r""" + N-Order IIR filter + Assumes working with float samples normalized on [-1, 1] + + --- + + Implementation details: + Based on the 2nd-order function from + https://en.wikipedia.org/wiki/Digital_biquad_filter, + this generalized N-order function was made. + + Using the following transfer function + H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}} + we can rewrite this to + y[n]={\frac{1}{a_{0}}}\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right) + """ + + def __init__(self, order: int) -> None: + self.order = order + + # a_{0} ... a_{k} + self.a_coeffs = [1.0] + [0.0] * order + # b_{0} ... b_{k} + self.b_coeffs = [1.0] + [0.0] * order + + # x[n-1] ... x[n-k] + self.input_history = [0.0] * self.order + # y[n-1] ... y[n-k] + self.output_history = [0.0] * self.order + + def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None: + """ + Set the coefficients for the IIR filter. These should both be of size order + 1. + a_0 may be left out, and it will use 1.0 as default value. + + This method works well with scipy's filter design functions + >>> # Make a 2nd-order 1000Hz butterworth lowpass filter + >>> import scipy.signal + >>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000, + ... btype='lowpass', + ... fs=48000) + >>> filt = IIRFilter(2) + >>> filt.set_coefficients(a_coeffs, b_coeffs) + """ + if len(a_coeffs) < self.order: + a_coeffs = [1.0] + a_coeffs + + if len(a_coeffs) != self.order + 1: + raise ValueError( + f"Expected a_coeffs to have {self.order + 1} elements for {self.order}" + f"-order filter, got {len(a_coeffs)}" + ) + + if len(b_coeffs) != self.order + 1: + raise ValueError( + f"Expected b_coeffs to have {self.order + 1} elements for {self.order}" + f"-order filter, got {len(a_coeffs)}" + ) + + self.a_coeffs = a_coeffs + self.b_coeffs = b_coeffs + + def process(self, sample: float) -> float: + """ + Calculate y[n] + + >>> filt = IIRFilter(2) + >>> filt.process(0) + 0.0 + """ + result = 0.0 + + # Start at index 1 and do index 0 at the end. + for i in range(1, self.order + 1): + result += ( + self.b_coeffs[i] * self.input_history[i - 1] + - self.a_coeffs[i] * self.output_history[i - 1] + ) + + result = (result + self.b_coeffs[0] * sample) / self.a_coeffs[0] + + self.input_history[1:] = self.input_history[:-1] + self.output_history[1:] = self.output_history[:-1] + + self.input_history[0] = sample + self.output_history[0] = result + + return result diff --git a/audio_filters/show_response.py b/audio_filters/show_response.py new file mode 100644 index 000000000000..6e2731a58419 --- /dev/null +++ b/audio_filters/show_response.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from math import pi +from typing import Protocol + +import matplotlib.pyplot as plt +import numpy as np + + +class FilterType(Protocol): + def process(self, sample: float) -> float: + """ + Calculate y[n] + + >>> issubclass(FilterType, Protocol) + True + """ + return 0.0 + + +def get_bounds( + fft_results: np.ndarray, samplerate: int +) -> tuple[int | float, int | float]: + """ + Get bounds for printing fft results + + >>> import numpy + >>> array = numpy.linspace(-20.0, 20.0, 1000) + >>> get_bounds(array, 1000) + (-20, 20) + """ + lowest = min([-20, np.min(fft_results[1 : samplerate // 2 - 1])]) + highest = max([20, np.max(fft_results[1 : samplerate // 2 - 1])]) + return lowest, highest + + +def show_frequency_response(filter: FilterType, samplerate: int) -> None: + """ + Show frequency response of a filter + + >>> from audio_filters.iir_filter import IIRFilter + >>> filt = IIRFilter(4) + >>> show_frequency_response(filt, 48000) + """ + + size = 512 + inputs = [1] + [0] * (size - 1) + outputs = [filter.process(item) for item in inputs] + + filler = [0] * (samplerate - size) # zero-padding + outputs += filler + fft_out = np.abs(np.fft.fft(outputs)) + fft_db = 20 * np.log10(fft_out) + + # Frequencies on log scale from 24 to nyquist frequency + plt.xlim(24, samplerate / 2 - 1) + plt.xlabel("Frequency (Hz)") + plt.xscale("log") + + # Display within reasonable bounds + bounds = get_bounds(fft_db, samplerate) + plt.ylim(max([-80, bounds[0]]), min([80, bounds[1]])) + plt.ylabel("Gain (dB)") + + plt.plot(fft_db) + plt.show() + + +def show_phase_response(filter: FilterType, samplerate: int) -> None: + """ + Show phase response of a filter + + >>> from audio_filters.iir_filter import IIRFilter + >>> filt = IIRFilter(4) + >>> show_phase_response(filt, 48000) + """ + + size = 512 + inputs = [1] + [0] * (size - 1) + outputs = [filter.process(item) for item in inputs] + + filler = [0] * (samplerate - size) # zero-padding + outputs += filler + fft_out = np.angle(np.fft.fft(outputs)) + + # Frequencies on log scale from 24 to nyquist frequency + plt.xlim(24, samplerate / 2 - 1) + plt.xlabel("Frequency (Hz)") + plt.xscale("log") + + plt.ylim(-2 * pi, 2 * pi) + plt.ylabel("Phase shift (Radians)") + plt.plot(np.unwrap(fft_out, -2 * pi)) + plt.show() From aaaa175b66f2b335023433a3c40635388b98c489 Mon Sep 17 00:00:00 2001 From: Erwin Junge Date: Sat, 23 Oct 2021 23:26:21 +0200 Subject: [PATCH 1642/2908] [mypy] annotate `computer_vision` (#5571) --- computer_vision/harris_corner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/computer_vision/harris_corner.py b/computer_vision/harris_corner.py index fb7f560f7873..02deb54084ef 100644 --- a/computer_vision/harris_corner.py +++ b/computer_vision/harris_corner.py @@ -21,11 +21,11 @@ def __init__(self, k: float, window_size: int): else: raise ValueError("invalid k value") - def __str__(self): + def __str__(self) -> str: return f"Harris Corner detection with k : {self.k}" - def detect(self, img_path: str): + def detect(self, img_path: str) -> tuple[cv2.Mat, list[list[int]]]: """ Returns the image with corners identified @@ -35,7 +35,7 @@ def detect(self, img_path: str): img = cv2.imread(img_path, 0) h, w = img.shape - corner_list = [] + corner_list: list[list[int]] = [] color_img = img.copy() color_img = cv2.cvtColor(color_img, cv2.COLOR_GRAY2RGB) dy, dx = np.gradient(img) From 5772d0734b72c307c0e08c76121b3eca589f50d4 Mon Sep 17 00:00:00 2001 From: Marcel Kuhmann Date: Sun, 24 Oct 2021 18:44:15 +0200 Subject: [PATCH 1643/2908] fix dead link (#5572) --- project_euler/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_euler/README.md b/project_euler/README.md index 1cc6f8150e38..c4c0a854472f 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -28,7 +28,7 @@ Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Befo * 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/scripts/validate_solutions.py). Keeping in mind the above example of [Problem 1](https://projecteuler.net/problem=1): ```python def solution(limit: int = 1000): From fc5d29e214c5624edec43300311b24c9d3962ac2 Mon Sep 17 00:00:00 2001 From: Vinicius Cordeiro Date: Sun, 24 Oct 2021 17:33:53 -0300 Subject: [PATCH 1644/2908] Add Bifid cipher (#5493) * Add Bifid cipher * Add missing type hint * Fix variable names --- ciphers/bifid.py | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 ciphers/bifid.py diff --git a/ciphers/bifid.py b/ciphers/bifid.py new file mode 100644 index 000000000000..c1b071155917 --- /dev/null +++ b/ciphers/bifid.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 + +""" +The Bifid Cipher uses a Polybius Square to encipher a message in a way that +makes it fairly difficult to decipher without knowing the secret. + +https://www.braingle.com/brainteasers/codes/bifid.php +""" + +import numpy as np + + +class BifidCipher: + def __init__(self) -> None: + SQUARE = [ + ["a", "b", "c", "d", "e"], + ["f", "g", "h", "i", "k"], + ["l", "m", "n", "o", "p"], + ["q", "r", "s", "t", "u"], + ["v", "w", "x", "y", "z"], + ] + self.SQUARE = np.array(SQUARE) + + def letter_to_numbers(self, letter: str) -> np.ndarray: + """ + Return the pair of numbers that represents the given letter in the + polybius square + + >>> np.array_equal(BifidCipher().letter_to_numbers('a'), [1,1]) + True + + >>> np.array_equal(BifidCipher().letter_to_numbers('u'), [4,5]) + True + """ + index1, index2 = np.where(self.SQUARE == letter) + indexes = np.concatenate([index1 + 1, index2 + 1]) + return indexes + + def numbers_to_letter(self, index1: int, index2: int) -> str: + """ + Return the letter corresponding to the position [index1, index2] in + the polybius square + + >>> BifidCipher().numbers_to_letter(4, 5) == "u" + True + + >>> BifidCipher().numbers_to_letter(1, 1) == "a" + True + """ + letter = self.SQUARE[index1 - 1, index2 - 1] + return letter + + def encode(self, message: str) -> str: + """ + Return the encoded version of message according to the polybius cipher + + >>> BifidCipher().encode('testmessage') == 'qtltbdxrxlk' + True + + >>> BifidCipher().encode('Test Message') == 'qtltbdxrxlk' + True + + >>> BifidCipher().encode('test j') == BifidCipher().encode('test i') + True + """ + message = message.lower() + message = message.replace(" ", "") + message = message.replace("j", "i") + + first_step = np.empty((2, len(message))) + for letter_index in range(len(message)): + numbers = self.letter_to_numbers(message[letter_index]) + + first_step[0, letter_index] = numbers[0] + first_step[1, letter_index] = numbers[1] + + second_step = first_step.reshape(2 * len(message)) + encoded_message = "" + for numbers_index in range(len(message)): + index1 = int(second_step[numbers_index * 2]) + index2 = int(second_step[(numbers_index * 2) + 1]) + letter = self.numbers_to_letter(index1, index2) + encoded_message = encoded_message + letter + + return encoded_message + + def decode(self, message: str) -> str: + """ + Return the decoded version of message according to the polybius cipher + + >>> BifidCipher().decode('qtltbdxrxlk') == 'testmessage' + True + """ + message = message.lower() + message.replace(" ", "") + first_step = np.empty(2 * len(message)) + for letter_index in range(len(message)): + numbers = self.letter_to_numbers(message[letter_index]) + first_step[letter_index * 2] = numbers[0] + first_step[letter_index * 2 + 1] = numbers[1] + + second_step = first_step.reshape((2, len(message))) + decoded_message = "" + for numbers_index in range(len(message)): + index1 = int(second_step[0, numbers_index]) + index2 = int(second_step[1, numbers_index]) + letter = self.numbers_to_letter(index1, index2) + decoded_message = decoded_message + letter + + return decoded_message From 568c107e6802dbcb8e6b9b82a31c9884645ac6b4 Mon Sep 17 00:00:00 2001 From: Francisco Perez <92104963+franciscoperez2021@users.noreply.github.com> Date: Mon, 25 Oct 2021 08:58:24 +0100 Subject: [PATCH 1645/2908] add bin_to_hexadecimal (#5156) --- DIRECTORY.md | 1 + conversions/binary_to_hexadecimal.py | 65 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 conversions/binary_to_hexadecimal.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 66d5f8040951..13a360ab67f3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -107,6 +107,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) + * [Binary To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_hexadecimal.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) diff --git a/conversions/binary_to_hexadecimal.py b/conversions/binary_to_hexadecimal.py new file mode 100644 index 000000000000..f94a12390607 --- /dev/null +++ b/conversions/binary_to_hexadecimal.py @@ -0,0 +1,65 @@ +def bin_to_hexadecimal(binary_str: str) -> str: + """ + Converting a binary string into hexadecimal using Grouping Method + + >>> bin_to_hexadecimal('101011111') + '0x15f' + >>> bin_to_hexadecimal(' 1010 ') + '0x0a' + >>> bin_to_hexadecimal('-11101') + '-0x1d' + >>> bin_to_hexadecimal('a') + Traceback (most recent call last): + ... + ValueError: Non-binary value was passed to the function + >>> bin_to_hexadecimal('') + Traceback (most recent call last): + ... + ValueError: Empty string was passed to the function + """ + BITS_TO_HEX = { + "0000": "0", + "0001": "1", + "0010": "2", + "0011": "3", + "0100": "4", + "0101": "5", + "0110": "6", + "0111": "7", + "1000": "8", + "1001": "9", + "1010": "a", + "1011": "b", + "1100": "c", + "1101": "d", + "1110": "e", + "1111": "f", + } + + # Sanitising parameter + binary_str = str(binary_str).strip() + + # Exceptions + if not binary_str: + raise ValueError("Empty string was passed to the function") + is_negative = binary_str[0] == "-" + binary_str = binary_str[1:] if is_negative else binary_str + if not all(char in "01" for char in binary_str): + raise ValueError("Non-binary value was passed to the function") + + binary_str = ( + "0" * (4 * (divmod(len(binary_str), 4)[0] + 1) - len(binary_str)) + binary_str + ) + + hexadecimal = [] + for x in range(0, len(binary_str), 4): + hexadecimal.append(BITS_TO_HEX[binary_str[x : x + 4]]) + hexadecimal_str = "0x" + "".join(hexadecimal) + + return "-" + hexadecimal_str if is_negative else hexadecimal_str + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From ba710054844fde4ccca666464c4bd08207e64a0d Mon Sep 17 00:00:00 2001 From: Manuel Di Lullo <39048927+manueldilullo@users.noreply.github.com> Date: Mon, 25 Oct 2021 09:59:52 +0200 Subject: [PATCH 1646/2908] Add random graph generator (#5240) * added complete graph generator function * added doctest, type hints, wikipedia explanation * added return type hint for function complete_graph * added descriptive name for the parameter: n * random graph generator with doctest and type hints * validated using pre-commit * Delete complete_graph_generator.py * fixed doctest * updated following reviews * simplified the code following reviews * fixed doctest and solved consistency issues * consistency fixes --- graphs/random_graph_generator.py | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 graphs/random_graph_generator.py diff --git a/graphs/random_graph_generator.py b/graphs/random_graph_generator.py new file mode 100644 index 000000000000..d7d5de8a37c0 --- /dev/null +++ b/graphs/random_graph_generator.py @@ -0,0 +1,67 @@ +""" +* Author: Manuel Di Lullo (https://github.com/manueldilullo) +* Description: Random graphs generator. + Uses graphs represented with an adjacency list. + +URL: https://en.wikipedia.org/wiki/Random_graph +""" + +import random + + +def random_graph( + vertices_number: int, probability: float, directed: bool = False +) -> dict: + """ + Generate a random graph + @input: vertices_number (number of vertices), + probability (probability that a generic edge (u,v) exists), + directed (if True: graph will be a directed graph, + otherwise it will be an undirected graph) + @examples: + >>> random.seed(1) + >>> random_graph(4, 0.5) + {0: [1], 1: [0, 2, 3], 2: [1, 3], 3: [1, 2]} + >>> random.seed(1) + >>> random_graph(4, 0.5, True) + {0: [1], 1: [2, 3], 2: [3], 3: []} + """ + graph = {i: [] for i in range(vertices_number)} + + # if probability is greater or equal than 1, then generate a complete graph + if probability >= 1: + return complete_graph(vertices_number) + # if probability is lower or equal than 0, then return a graph without edges + if probability <= 0: + return graph + + # for each couple of nodes, add an edge from u to v + # if the number randomly generated is greater than probability probability + for i in range(vertices_number): + for j in range(i + 1, vertices_number): + if random.random() < probability: + graph[i].append(j) + if not directed: + # if the graph is undirected, add an edge in from j to i, either + graph[j].append(i) + return graph + + +def complete_graph(vertices_number: int) -> dict: + """ + Generate a complete graph with vertices_number vertices. + @input: vertices_number (number of vertices), + directed (False if the graph is undirected, True otherwise) + @example: + >>> print(complete_graph(3)) + {0: [1, 2], 1: [0, 2], 2: [0, 1]} + """ + return { + i: [j for j in range(vertices_number) if i != j] for i in range(vertices_number) + } + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5f7bb3e9f7b458d039e1027b48b3d24d9a577f96 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 25 Oct 2021 11:03:22 +0300 Subject: [PATCH 1647/2908] Improve Project Euler problem 034 solution 1 (#5165) --- project_euler/problem_034/sol1.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/project_euler/problem_034/sol1.py b/project_euler/problem_034/sol1.py index 11c84ab96ac6..8d8432dbbb7a 100644 --- a/project_euler/problem_034/sol1.py +++ b/project_euler/problem_034/sol1.py @@ -8,6 +8,8 @@ from math import factorial +DIGIT_FACTORIAL = {str(d): factorial(d) for d in range(10)} + def sum_of_digit_factorial(n: int) -> int: """ @@ -17,7 +19,7 @@ def sum_of_digit_factorial(n: int) -> int: >>> sum_of_digit_factorial(0) 1 """ - return sum(factorial(int(char)) for char in str(n)) + return sum(DIGIT_FACTORIAL[d] for d in str(n)) def solution() -> int: From b55da046029e681c1811f5f1cb24c35117d462ce Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 25 Oct 2021 11:07:10 +0300 Subject: [PATCH 1648/2908] Improve Project Euler problem 058 solution 1 (#4782) * Fix typo * Improve solution * Retest * Replace n with number --- project_euler/problem_058/sol1.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_058/sol1.py b/project_euler/problem_058/sol1.py index d3b15157fbbd..ed407edf7158 100644 --- a/project_euler/problem_058/sol1.py +++ b/project_euler/problem_058/sol1.py @@ -33,11 +33,12 @@ count of current primes. """ +from math import isqrt -def isprime(d: int) -> int: +def isprime(number: int) -> int: """ - returns whether the given digit is prime or not + returns whether the given number is prime or not >>> isprime(1) 0 >>> isprime(17) @@ -45,14 +46,15 @@ def isprime(d: int) -> int: >>> isprime(10000) 0 """ - if d == 1: + if number == 1: return 0 - i = 2 - while i * i <= d: - if d % i == 0: + if number % 2 == 0 and number > 2: + return 0 + + for i in range(3, isqrt(number) + 1, 2): + if number % i == 0: return 0 - i = i + 1 return 1 From 74e442e979b1ffdbafa97765193ec04058893fca Mon Sep 17 00:00:00 2001 From: Mohammad Firmansyah <76118762+dimasdh842@users.noreply.github.com> Date: Mon, 25 Oct 2021 17:18:41 +0000 Subject: [PATCH 1649/2908] add an algorithm to spin some words (#5597) * add an algorithm to spin some words * Update index.py * Adding type hint of spin_words function * Update and rename python_codewars_disemvowel/index.py to strings/reverse_long_words.py Co-authored-by: Christian Clauss --- strings/reverse_long_words.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 strings/reverse_long_words.py diff --git a/strings/reverse_long_words.py b/strings/reverse_long_words.py new file mode 100644 index 000000000000..39ef11513f40 --- /dev/null +++ b/strings/reverse_long_words.py @@ -0,0 +1,21 @@ +def reverse_long_words(sentence: str) -> str: + """ + Reverse all words that are longer than 4 characters in a sentence. + + >>> reverse_long_words("Hey wollef sroirraw") + 'Hey fellow warriors' + >>> reverse_long_words("nohtyP is nohtyP") + 'Python is Python' + >>> reverse_long_words("1 12 123 1234 54321 654321") + '1 12 123 1234 12345 123456' + """ + return " ".join( + "".join(word[::-1]) if len(word) > 4 else word for word in sentence.split() + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(reverse_long_words("Hey wollef sroirraw")) From 12e81ea6a2e74691173895418f2129bb4d2752c2 Mon Sep 17 00:00:00 2001 From: "@im_8055" <38890773+Bhargavishnu@users.noreply.github.com> Date: Tue, 26 Oct 2021 10:51:07 +0530 Subject: [PATCH 1650/2908] Add credit card string validator (#5583) * Add credit card validator *  * Add return type hint * Add test cases for validator function * Add test cases * Feature: Rename file * Update strings/cc_validator.py Co-authored-by: Christian Clauss * Update strings/cc_validator.py Co-authored-by: Christian Clauss * Update strings/cc_validator.py Co-authored-by: Christian Clauss * Review: Fix redundant checks * Review: Refactor * Fix: Update test cases * Refactor * Update credit_card_validator.py Co-authored-by: Christian Clauss --- strings/credit_card_validator.py | 105 +++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 strings/credit_card_validator.py diff --git a/strings/credit_card_validator.py b/strings/credit_card_validator.py new file mode 100644 index 000000000000..3b5a1aae6dc9 --- /dev/null +++ b/strings/credit_card_validator.py @@ -0,0 +1,105 @@ +""" +Functions for testing the validity of credit card numbers. + +https://en.wikipedia.org/wiki/Luhn_algorithm +""" + + +def validate_initial_digits(credit_card_number: str) -> bool: + """ + Function to validate initial digits of a given credit card number. + >>> valid = "4111111111111111 41111111111111 34 35 37 412345 523456 634567" + >>> all(validate_initial_digits(cc) for cc in valid.split()) + True + >>> invalid = "32323 36111111111111" + >>> all(validate_initial_digits(cc) is False for cc in invalid.split()) + True + """ + if len(credit_card_number) < 2: + return False + return credit_card_number[0] in "456" or credit_card_number[1] in "457" + + +def luhn_validation(credit_card_number: str) -> bool: + """ + Function to luhn algorithm validation for a given credit card number. + >>> luhn_validation('4111111111111111') + True + >>> luhn_validation('36111111111111') + True + >>> luhn_validation('41111111111111') + False + """ + cc_number = credit_card_number + total = 0 + half_len = len(cc_number) - 2 + for i in range(half_len, -1, -2): + # double the value of every second digit + digit = int(cc_number[i]) + digit *= 2 + # If doubling of a number results in a two digit number + # i.e greater than 9(e.g., 6 × 2 = 12), + # then add the digits of the product (e.g., 12: 1 + 2 = 3, 15: 1 + 5 = 6), + # to get a single digit number. + if digit > 9: + digit %= 10 + digit += 1 + cc_number = cc_number[:i] + str(digit) + cc_number[i + 1 :] + total += digit + + # Sum up the remaining digits + for i in range(len(cc_number) - 1, -1, -2): + total += int(cc_number[i]) + + return total % 10 == 0 + + +def validate_credit_card_number(credit_card_number: str) -> bool: + """ + Function to validate the given credit card number. + >>> validate_credit_card_number('4111111111111111') + 4111111111111111 is a valid credit card number. + True + >>> validate_credit_card_number('helloworld$') + helloworld$ is an invalid credit card number because it has nonnumerical characters. + False + >>> validate_credit_card_number('32323') + 32323 is an invalid credit card number because of its length. + False + >>> validate_credit_card_number('32323323233232332323') + 32323323233232332323 is an invalid credit card number because of its length. + False + >>> validate_credit_card_number('36111111111111') + 36111111111111 is an invalid credit card number because of its first two digits. + False + >>> validate_credit_card_number('41111111111111') + 41111111111111 is an invalid credit card number because it fails the Lhun check. + False + """ + error_message = f"{credit_card_number} is an invalid credit card number because" + if not credit_card_number.isdigit(): + print(f"{error_message} it has nonnumerical characters.") + return False + + if not 13 <= len(credit_card_number) <= 16: + print(f"{error_message} of its length.") + return False + + if not validate_initial_digits(credit_card_number): + print(f"{error_message} of its first two digits.") + return False + + if not luhn_validation(credit_card_number): + print(f"{error_message} it fails the Lhun check.") + return False + + print(f"{credit_card_number} is a valid credit card number.") + return True + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + validate_credit_card_number("4111111111111111") + validate_credit_card_number("32323") From 716beb32ed231217c05782be9323ef43f6c1a0ce Mon Sep 17 00:00:00 2001 From: Leoriem-code <73761711+Leoriem-code@users.noreply.github.com> Date: Tue, 26 Oct 2021 09:21:44 +0200 Subject: [PATCH 1651/2908] Improved prime_numbers.py (#5592) * Improved prime_numbers.py * update prime_numbers.py * Increase the timeit number to 1_000_000 Co-authored-by: Christian Clauss --- maths/prime_numbers.py | 55 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index 38bebddeee41..183fbd39349e 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -58,6 +58,38 @@ def primes(max: int) -> Generator[int, None, None]: yield i +def fast_primes(max: int) -> Generator[int, None, None]: + """ + Return a list of all primes numbers up to max. + >>> list(fast_primes(0)) + [] + >>> list(fast_primes(-1)) + [] + >>> list(fast_primes(-10)) + [] + >>> list(fast_primes(25)) + [2, 3, 5, 7, 11, 13, 17, 19, 23] + >>> list(fast_primes(11)) + [2, 3, 5, 7, 11] + >>> list(fast_primes(33)) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31] + >>> list(fast_primes(10000))[-1] + 9973 + """ + numbers: Generator = (i for i in range(1, (max + 1), 2)) + # It's useless to test even numbers as they will not be prime + if max > 2: + yield 2 # Because 2 will not be tested, it's necessary to yield it now + for i in (n for n in numbers if n > 1): + bound = int(math.sqrt(i)) + 1 + for j in range(3, bound, 2): + # As we removed the even numbers, we don't need them now + if (i % j) == 0: + break + else: + yield i + + if __name__ == "__main__": number = int(input("Calculate primes up to:\n>> ").strip()) for ret in primes(number): @@ -66,5 +98,24 @@ 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")) + print( + timeit( + "slow_primes(1_000_000_000_000)", + setup="from __main__ import slow_primes", + number=1_000_000, + ) + ) + print( + timeit( + "primes(1_000_000_000_000)", + setup="from __main__ import primes", + number=1_000_000, + ) + ) + print( + timeit( + "fast_primes(1_000_000_000_000)", + setup="from __main__ import fast_primes", + number=1_000_000, + ) + ) From 8e857e8692bf8e07112dedb7042bdab1ecb1d20e Mon Sep 17 00:00:00 2001 From: Leoriem-code <73761711+Leoriem-code@users.noreply.github.com> Date: Tue, 26 Oct 2021 09:57:49 +0200 Subject: [PATCH 1652/2908] add implementation of Nagel and Schrekenberg algo (#5584) * add implementation of Nagel and Schrekenberg algo * Update cellular_automata/nasch.py Co-authored-by: Christian Clauss * Update nasch.py * Update and rename nasch.py to nagel_schrekenberg.py * Update cellular_automata/nagel_schrekenberg.py Co-authored-by: Christian Clauss * Update nagel_schrekenberg.py * Update nagel_schrekenberg.py * Update nagel_schrekenberg.py * update nagel_schrekenberg.py * Update nagel_schrekenberg.py Co-authored-by: Christian Clauss --- cellular_automata/nagel_schrekenberg.py | 140 ++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 cellular_automata/nagel_schrekenberg.py diff --git a/cellular_automata/nagel_schrekenberg.py b/cellular_automata/nagel_schrekenberg.py new file mode 100644 index 000000000000..be44761ecf82 --- /dev/null +++ b/cellular_automata/nagel_schrekenberg.py @@ -0,0 +1,140 @@ +""" +Simulate the evolution of a highway with only one road that is a loop. +The highway is divided in cells, each cell can have at most one car in it. +The highway is a loop so when a car comes to one end, it will come out on the other. +Each car is represented by its speed (from 0 to 5). + +Some information about speed: + -1 means that the cell on the highway is empty + 0 to 5 are the speed of the cars with 0 being the lowest and 5 the highest + +highway: list[int] Where every position and speed of every car will be stored +probability The probability that a driver will slow down +initial_speed The speed of the cars a the start +frequency How many cells there are between two cars at the start +max_speed The maximum speed a car can go to +number_of_cells How many cell are there in the highway +number_of_update How many times will the position be updated + +More information here: https://en.wikipedia.org/wiki/Nagel%E2%80%93Schreckenberg_model + +Examples for doctest: +>>> simulate(construct_highway(6, 3, 0), 2, 0, 2) +[[0, -1, -1, 0, -1, -1], [-1, 1, -1, -1, 1, -1], [-1, -1, 1, -1, -1, 1]] +>>> simulate(construct_highway(5, 2, -2), 3, 0, 2) +[[0, -1, 0, -1, 0], [0, -1, 0, -1, -1], [0, -1, -1, 1, -1], [-1, 1, -1, 0, -1]] +""" +from random import randint, random + + +def construct_highway( + number_of_cells: int, + frequency: int, + initial_speed: int, + random_frequency: bool = False, + random_speed: bool = False, + max_speed: int = 5, +) -> list: + """ + Build the highway following the parameters given + >>> construct_highway(10, 2, 6) + [[6, -1, 6, -1, 6, -1, 6, -1, 6, -1]] + >>> construct_highway(10, 10, 2) + [[2, -1, -1, -1, -1, -1, -1, -1, -1, -1]] + """ + + highway = [[-1] * number_of_cells] # Create a highway without any car + i = 0 + if initial_speed < 0: + initial_speed = 0 + while i < number_of_cells: + highway[0][i] = ( + randint(0, max_speed) if random_speed else initial_speed + ) # Place the cars + i += ( + randint(1, max_speed * 2) if random_frequency else frequency + ) # Arbitrary number, may need tuning + return highway + + +def get_distance(highway_now: list, car_index: int) -> int: + """ + Get the distance between a car (at index car_index) and the next car + >>> get_distance([6, -1, 6, -1, 6], 2) + 1 + >>> get_distance([2, -1, -1, -1, 3, 1, 0, 1, 3, 2], 0) + 3 + >>> get_distance([-1, -1, -1, -1, 2, -1, -1, -1, 3], -1) + 4 + """ + + distance = 0 + cells = highway_now[car_index + 1 :] + for cell in range(len(cells)): # May need a better name for this + if cells[cell] != -1: # If the cell is not empty then + return distance # we have the distance we wanted + distance += 1 + # Here if the car is near the end of the highway + return distance + get_distance(highway_now, -1) + + +def update(highway_now: list, probability: float, max_speed: int) -> list: + """ + Update the speed of the cars + >>> update([-1, -1, -1, -1, -1, 2, -1, -1, -1, -1, 3], 0.0, 5) + [-1, -1, -1, -1, -1, 3, -1, -1, -1, -1, 4] + >>> update([-1, -1, 2, -1, -1, -1, -1, 3], 0.0, 5) + [-1, -1, 3, -1, -1, -1, -1, 1] + """ + + number_of_cells = len(highway_now) + # Beforce calculations, the highway is empty + next_highway = [-1] * number_of_cells + + for car_index in range(number_of_cells): + if highway_now[car_index] != -1: + # Add 1 to the current speed of the car and cap the speed + next_highway[car_index] = min(highway_now[car_index] + 1, max_speed) + # Number of empty cell before the next car + dn = get_distance(highway_now, car_index) - 1 + # We can't have the car causing an accident + next_highway[car_index] = min(next_highway[car_index], dn) + if random() < probability: + # Randomly, a driver will slow down + next_highway[car_index] = max(next_highway[car_index] - 1, 0) + return next_highway + + +def simulate( + highway: list, number_of_update: int, probability: float, max_speed: int +) -> list: + """ + The main function, it will simulate the evolution of the highway + >>> simulate([[-1, 2, -1, -1, -1, 3]], 2, 0.0, 3) + [[-1, 2, -1, -1, -1, 3], [-1, -1, -1, 2, -1, 0], [1, -1, -1, 0, -1, -1]] + >>> simulate([[-1, 2, -1, 3]], 4, 0.0, 3) + [[-1, 2, -1, 3], [-1, 0, -1, 0], [-1, 0, -1, 0], [-1, 0, -1, 0], [-1, 0, -1, 0]] + """ + + number_of_cells = len(highway[0]) + + for i in range(number_of_update): + next_speeds_calculated = update(highway[i], probability, max_speed) + real_next_speeds = [-1] * number_of_cells + + for car_index in range(number_of_cells): + speed = next_speeds_calculated[car_index] + if speed != -1: + # Change the position based on the speed (with % to create the loop) + index = (car_index + speed) % number_of_cells + # Commit the change of position + real_next_speeds[index] = speed + highway.append(real_next_speeds) + + return highway + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f93c7d4d80bcad6b4679d09ecefa7cc3874aae54 Mon Sep 17 00:00:00 2001 From: Prakhar Gurunani Date: Tue, 26 Oct 2021 13:35:13 +0530 Subject: [PATCH 1653/2908] Get user tweets (#5593) * updating DIRECTORY.md * Create get_user_tweets.py * updating DIRECTORY.md * Reformat code with black * Add argument type * Add return type * Add tweepy * Fix isort issues * Fix flake8 issues * WIP: doctest * Doctest setup and format with pre-commit * Remove doctests * Update web_programming/get_user_tweets.py Co-authored-by: Christian Clauss * Update get_user_tweets.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 10 ++++- requirements.txt | 1 + web_programming/get_user_tweets.py | 60 ++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 web_programming/get_user_tweets.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 13a360ab67f3..e2b2442fd1b1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -10,6 +10,11 @@ * [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) +## Audio Filters + * [Butterworth Filter](https://github.com/TheAlgorithms/Python/blob/master/audio_filters/butterworth_filter.py) + * [Iir Filter](https://github.com/TheAlgorithms/Python/blob/master/audio_filters/iir_filter.py) + * [Show Response](https://github.com/TheAlgorithms/Python/blob/master/audio_filters/show_response.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) @@ -60,6 +65,7 @@ * [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) + * [Bifid](https://github.com/TheAlgorithms/Python/blob/master/ciphers/bifid.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) @@ -106,8 +112,8 @@ ## 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) * [Binary To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_hexadecimal.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 Binary Recursion](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary_recursion.py) @@ -352,6 +358,7 @@ * [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) + * [Random Graph Generator](https://github.com/TheAlgorithms/Python/blob/master/graphs/random_graph_generator.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) @@ -978,6 +985,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) + * [Get User Tweets](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_user_tweets.py) * [Giphy](https://github.com/TheAlgorithms/Python/blob/master/web_programming/giphy.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) diff --git a/requirements.txt b/requirements.txt index 4867de26f8f1..7c2672ae25d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,6 @@ sklearn statsmodels sympy tensorflow +tweepy types-requests xgboost diff --git a/web_programming/get_user_tweets.py b/web_programming/get_user_tweets.py new file mode 100644 index 000000000000..0f70201dc311 --- /dev/null +++ b/web_programming/get_user_tweets.py @@ -0,0 +1,60 @@ +import csv + +import tweepy + +# Twitter API credentials +consumer_key = "" +consumer_secret = "" +access_key = "" +access_secret = "" + + +def get_all_tweets(screen_name: str) -> None: + + # authorize twitter, initialize tweepy + auth = tweepy.OAuthHandler(consumer_key, consumer_secret) + auth.set_access_token(access_key, access_secret) + api = tweepy.API(auth) + + # initialize a list to hold all the tweepy Tweets + alltweets = [] + + # make initial request for most recent tweets (200 is the maximum allowed count) + new_tweets = api.user_timeline(screen_name=screen_name, count=200) + + # save most recent tweets + alltweets.extend(new_tweets) + + # save the id of the oldest tweet less one + oldest = alltweets[-1].id - 1 + + # keep grabbing tweets until there are no tweets left to grab + while len(new_tweets) > 0: + print(f"getting tweets before {oldest}") + + # all subsiquent requests use the max_id param to prevent duplicates + new_tweets = api.user_timeline( + screen_name=screen_name, count=200, max_id=oldest + ) + + # save most recent tweets + alltweets.extend(new_tweets) + + # update the id of the oldest tweet less one + oldest = alltweets[-1].id - 1 + + print(f"...{len(alltweets)} tweets downloaded so far") + + # transform the tweepy tweets into a 2D array that will populate the csv + outtweets = [[tweet.id_str, tweet.created_at, tweet.text] for tweet in alltweets] + + # write the csv + with open(f"new_{screen_name}_tweets.csv", "w") as f: + writer = csv.writer(f) + writer.writerow(["id", "created_at", "text"]) + writer.writerows(outtweets) + + +if __name__ == "__main__": + # pass in the username of the account you want to download + get_all_tweets("FirePing32") From 2606f1bbe58dfc370ce1f35d3d9c51396e791fa6 Mon Sep 17 00:00:00 2001 From: Andrew Grangaard Date: Tue, 26 Oct 2021 02:50:36 -0700 Subject: [PATCH 1654/2908] [mypy-fix] Type fixes for graham_scan (#5589) * [mypy] Fixes type annotations in other/graham_scan #4052 + Prefer tuple to list for point x,y pairs * NOP: fixes typo in comment --- other/graham_scan.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/other/graham_scan.py b/other/graham_scan.py index 67c5cd8ab9d8..91bb6812fefc 100644 --- a/other/graham_scan.py +++ b/other/graham_scan.py @@ -14,7 +14,7 @@ from sys import maxsize -def graham_scan(points: list[list[int, int]]) -> list[list[int, int]]: +def graham_scan(points: list[tuple[int, int]]) -> list[tuple[int, int]]: """Pure implementation of graham scan algorithm in Python :param points: The unique points on coordinates. @@ -57,7 +57,7 @@ def graham_scan(points: list[list[int, int]]) -> list[list[int, int]]: # remove the lowest and the most left point from points for preparing for sort points.pop(minidx) - def angle_comparer(point: list[int, int], minx: int, miny: int) -> float: + def angle_comparer(point: tuple[int, int], minx: int, miny: int) -> float: """Return the angle toward to point from (minx, miny) :param point: The target point @@ -66,13 +66,13 @@ def angle_comparer(point: list[int, int], minx: int, miny: int) -> float: :return: the angle Examples: - >>> angle_comparer([1,1], 0, 0) + >>> angle_comparer((1,1), 0, 0) 45.0 - >>> angle_comparer([100,1], 10, 10) + >>> angle_comparer((100,1), 10, 10) -5.710593137499642 - >>> angle_comparer([5,5], 2, 3) + >>> angle_comparer((5,5), 2, 3) 33.690067525979785 """ # sort the points accorgind to the angle from the lowest and the most left point @@ -83,7 +83,7 @@ def angle_comparer(point: list[int, int], minx: int, miny: int) -> float: sorted_points = sorted(points, key=lambda point: angle_comparer(point, minx, miny)) # This insert actually costs complexity, - # and you should insteadly add (minx, miny) into stack later. + # and you should instead add (minx, miny) into stack later. # I'm using insert just for easy understanding. sorted_points.insert(0, (minx, miny)) @@ -95,7 +95,7 @@ class Direction(Enum): right = 3 def check_direction( - starting: list[int, int], via: list[int, int], target: list[int, int] + starting: tuple[int, int], via: tuple[int, int], target: tuple[int, int] ) -> Direction: """Return the direction toward to the line from via to target from starting @@ -105,13 +105,13 @@ def check_direction( :return: the Direction Examples: - >>> check_direction([1,1], [2,2], [3,3]) + >>> check_direction((1,1), (2,2), (3,3)) Direction.straight - >>> check_direction([60,1], [-50,199], [30,2]) + >>> check_direction((60,1), (-50,199), (30,2)) Direction.left - >>> check_direction([0,0], [5,5], [10,0]) + >>> check_direction((0,0), (5,5), (10,0)) Direction.right """ x0, y0 = starting @@ -132,12 +132,12 @@ def check_direction( # If they are same, it means they are on a same line of convex hull. if target_angle > via_angle: return Direction.left - if target_angle == via_angle: + elif target_angle == via_angle: return Direction.straight - if target_angle < via_angle: + else: return Direction.right - stack = deque() + stack: deque[tuple[int, int]] = deque() stack.append(sorted_points[0]) stack.append(sorted_points[1]) stack.append(sorted_points[2]) From de07245c170f2007cef415fa4114be870078988e Mon Sep 17 00:00:00 2001 From: Andrew Grangaard Date: Tue, 26 Oct 2021 03:10:37 -0700 Subject: [PATCH 1655/2908] [mypy] Adds type annotations in other/activity_selection #4052 (#5590) --- other/activity_selection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/activity_selection.py b/other/activity_selection.py index c03956cce5d2..d809bf90a3f3 100644 --- a/other/activity_selection.py +++ b/other/activity_selection.py @@ -10,7 +10,7 @@ # finish[] --> An array that contains finish time of all activities -def printMaxActivities(start, finish): +def printMaxActivities(start: list[int], finish: list[int]) -> None: """ >>> start = [1, 3, 0, 5, 8, 5] >>> finish = [2, 4, 6, 7, 9, 9] From e49d8e3af427353c18fe5f1afb0927e3e8d6461c Mon Sep 17 00:00:00 2001 From: Erwin Junge Date: Tue, 26 Oct 2021 12:29:27 +0200 Subject: [PATCH 1656/2908] [mypy] annotate `compression` (#5570) --- compression/burrows_wheeler.py | 12 +++++- compression/huffman.py | 48 ++++++++++++----------- compression/lempel_ziv.py | 4 +- compression/peak_signal_to_noise_ratio.py | 4 +- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 7d705af7428e..4ad99a642e49 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -12,6 +12,13 @@ """ from __future__ import annotations +from typing import TypedDict + + +class BWTTransformDict(TypedDict): + bwt_string: str + idx_original_string: int + def all_rotations(s: str) -> list[str]: """ @@ -43,7 +50,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) -> BWTTransformDict: """ :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 @@ -75,10 +82,11 @@ def bwt_transform(s: str) -> dict: 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 { + response: BWTTransformDict = { "bwt_string": "".join([word[-1] for word in rotations]), "idx_original_string": rotations.index(s), } + return response def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: diff --git a/compression/huffman.py b/compression/huffman.py index 8f37a53ce2b7..d5d78b753c3f 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -1,29 +1,31 @@ +from __future__ import annotations + import sys class Letter: - def __init__(self, letter, freq): - self.letter = letter - self.freq = freq - self.bitstring = {} + def __init__(self, letter: str, freq: int): + self.letter: str = letter + self.freq: int = freq + self.bitstring: dict[str, str] = {} - def __repr__(self): + def __repr__(self) -> str: return f"{self.letter}:{self.freq}" class TreeNode: - def __init__(self, freq, left, right): - self.freq = freq - self.left = left - self.right = right + def __init__(self, freq: int, left: Letter | TreeNode, right: Letter | TreeNode): + self.freq: int = freq + self.left: Letter | TreeNode = left + self.right: Letter | TreeNode = right -def parse_file(file_path): +def parse_file(file_path: str) -> list[Letter]: """ Read the file and build a dict of all letters and their frequencies, then convert the dict into a list of Letters. """ - chars = {} + chars: dict[str, int] = {} with open(file_path) as f: while True: c = f.read(1) @@ -33,22 +35,23 @@ def parse_file(file_path): return sorted((Letter(c, f) for c, f in chars.items()), key=lambda l: l.freq) -def build_tree(letters): +def build_tree(letters: list[Letter]) -> Letter | TreeNode: """ 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) + response: list[Letter | TreeNode] = letters # type: ignore + while len(response) > 1: + left = response.pop(0) + right = response.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] + response.append(node) + response.sort(key=lambda l: l.freq) + return response[0] -def traverse_tree(root, bitstring): +def traverse_tree(root: Letter | TreeNode, bitstring: str) -> list[Letter]: """ Recursively traverse the Huffman Tree to set each Letter's bitstring dictionary, and return the list of Letters @@ -56,13 +59,14 @@ def traverse_tree(root, bitstring): if type(root) is Letter: root.bitstring[root.letter] = bitstring return [root] + treenode: TreeNode = root # type: ignore letters = [] - letters += traverse_tree(root.left, bitstring + "0") - letters += traverse_tree(root.right, bitstring + "1") + letters += traverse_tree(treenode.left, bitstring + "0") + letters += traverse_tree(treenode.right, bitstring + "1") return letters -def huffman(file_path): +def huffman(file_path: str) -> None: """ Parse the file, build the tree, then run through the file again, using the letters dictionary to find and print out the diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py index 6743dc42d56e..ea6f33944a91 100644 --- a/compression/lempel_ziv.py +++ b/compression/lempel_ziv.py @@ -26,7 +26,7 @@ def read_file_binary(file_path: str) -> str: def add_key_to_lexicon( - lexicon: dict, curr_string: str, index: int, last_match_id: str + lexicon: dict[str, str], curr_string: str, index: int, last_match_id: str ) -> None: """ Adds new strings (curr_string + "0", curr_string + "1") to the lexicon @@ -110,7 +110,7 @@ def write_file_binary(file_path: str, to_write: str) -> None: sys.exit() -def compress(source_path, destination_path: str) -> None: +def compress(source_path: str, destination_path: str) -> None: """ Reads source file, compresses it and writes the compressed result in destination file diff --git a/compression/peak_signal_to_noise_ratio.py b/compression/peak_signal_to_noise_ratio.py index 6c6c4c38a12a..dded2a712c7e 100644 --- a/compression/peak_signal_to_noise_ratio.py +++ b/compression/peak_signal_to_noise_ratio.py @@ -12,7 +12,7 @@ import numpy as np -def psnr(original, contrast): +def psnr(original: float, contrast: float) -> float: mse = np.mean((original - contrast) ** 2) if mse == 0: return 100 @@ -21,7 +21,7 @@ def psnr(original, contrast): return PSNR -def main(): +def main() -> None: 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")) From 700398ec063687b661e9be865f5c32d3c822fca1 Mon Sep 17 00:00:00 2001 From: Erwin Junge Date: Tue, 26 Oct 2021 12:35:21 +0200 Subject: [PATCH 1657/2908] [mypy] annotate `ciphers` (#5569) * [mypy] annotate `ciphers` * Update ciphers/polybius.py * Update polybius.py Co-authored-by: Christian Clauss --- ciphers/decrypt_caesar_with_chi_squared.py | 11 +++++++---- ciphers/polybius.py | 3 +-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 89477914a030..beac851b6c2a 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -221,10 +221,13 @@ def decrypt_caesar_with_chi_squared( # Get the most likely cipher by finding the cipher with the smallest chi squared # statistic - most_likely_cipher: int = min( # type: ignore - chi_squared_statistic_values, # type: ignore - key=chi_squared_statistic_values.get, # type: ignore - ) # type: ignore + def chi_squared_statistic_values_sorting_key(key: int) -> tuple[float, str]: + return chi_squared_statistic_values[key] + + most_likely_cipher: int = min( + chi_squared_statistic_values, + key=chi_squared_statistic_values_sorting_key, + ) # Get all the data from the most likely cipher (key, decoded message) ( diff --git a/ciphers/polybius.py b/ciphers/polybius.py index 9e1dc4cbb5a8..2a45f02a3773 100644 --- a/ciphers/polybius.py +++ b/ciphers/polybius.py @@ -45,8 +45,7 @@ def numbers_to_letter(self, index1: int, index2: int) -> str: >>> PolybiusCipher().numbers_to_letter(1, 1) == "a" True """ - letter = self.SQUARE[index1 - 1, index2 - 1] - return letter + return self.SQUARE[index1 - 1, index2 - 1] def encode(self, message: str) -> str: """ From 366a0f18397427b61979ea27f7497718962834d8 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 26 Oct 2021 14:32:34 +0200 Subject: [PATCH 1658/2908] Fix validate_initial_digits of credit_card_validator.py (#5600) * Fix validate_initial_digits of credit_card_validator.py @Bhargavishnu I think that I broke the logic of validate_initial_digits which should require that credit_card_number[0] is 3 before checking that credit_card_number[1] is 4, 5, or 7. Please verify the new changes and the new test cases to make sure that this is correct. Thanks! * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 ++ strings/credit_card_validator.py | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index e2b2442fd1b1..f765b29b60da 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -944,6 +944,7 @@ * [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) + * [Credit Card Validator](https://github.com/TheAlgorithms/Python/blob/master/strings/credit_card_validator.py) * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/strings/detecting_english_programmatically.py) * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/strings/frequency_finder.py) * [Indian Phone Validator](https://github.com/TheAlgorithms/Python/blob/master/strings/indian_phone_validator.py) @@ -961,6 +962,7 @@ * [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 Long Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_long_words.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) diff --git a/strings/credit_card_validator.py b/strings/credit_card_validator.py index 3b5a1aae6dc9..3a08c4117a6b 100644 --- a/strings/credit_card_validator.py +++ b/strings/credit_card_validator.py @@ -11,13 +11,11 @@ def validate_initial_digits(credit_card_number: str) -> bool: >>> valid = "4111111111111111 41111111111111 34 35 37 412345 523456 634567" >>> all(validate_initial_digits(cc) for cc in valid.split()) True - >>> invalid = "32323 36111111111111" + >>> invalid = "14 25 76 32323 36111111111111" >>> all(validate_initial_digits(cc) is False for cc in invalid.split()) True """ - if len(credit_card_number) < 2: - return False - return credit_card_number[0] in "456" or credit_card_number[1] in "457" + return credit_card_number.startswith(("34", "35", "37", "4", "5", "6")) def luhn_validation(credit_card_number: str) -> bool: From 827b8f04a4f8090f0a8b42f7685a6b510996e5a3 Mon Sep 17 00:00:00 2001 From: Prakhar Gurunani Date: Tue, 26 Oct 2021 18:43:23 +0530 Subject: [PATCH 1659/2908] Get top 10 HN posts (#5604) * updating DIRECTORY.md * updating DIRECTORY.md * Create get_top_hn_posts.py * updating DIRECTORY.md * Add return type and desc * Add texttable * Update web_programming/get_top_hn_posts.py Co-authored-by: Christian Clauss * Update web_programming/get_top_hn_posts.py Co-authored-by: Christian Clauss * Get top 10 posts * Update get_top_hn_posts.py * Don't use texttable * Setup doctest * Fix pre-commit issues * Remove print statement * Add hackernews_top_stories_as_markdown() Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 2 ++ requirements.txt | 1 + web_programming/get_top_hn_posts.py | 26 ++++++++++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 web_programming/get_top_hn_posts.py diff --git a/DIRECTORY.md b/DIRECTORY.md index f765b29b60da..67f2113ea87d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -53,6 +53,7 @@ ## Cellular Automata * [Conways Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/conways_game_of_life.py) * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/game_of_life.py) + * [Nagel Schrekenberg](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/nagel_schrekenberg.py) * [One Dimensional](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/one_dimensional.py) ## Ciphers @@ -987,6 +988,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) + * [Get Top Hn Posts](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_top_hn_posts.py) * [Get User Tweets](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_user_tweets.py) * [Giphy](https://github.com/TheAlgorithms/Python/blob/master/web_programming/giphy.py) * [Instagram Crawler](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_crawler.py) diff --git a/requirements.txt b/requirements.txt index 7c2672ae25d3..c28238a0774f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ sklearn statsmodels sympy tensorflow +texttable tweepy types-requests xgboost diff --git a/web_programming/get_top_hn_posts.py b/web_programming/get_top_hn_posts.py new file mode 100644 index 000000000000..fbb7c051a88e --- /dev/null +++ b/web_programming/get_top_hn_posts.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +import requests + + +def get_hackernews_story(story_id: str) -> dict: + url = f"https://hacker-news.firebaseio.com/v0/item/%7Bstory_id%7D.json?print=pretty" + return requests.get(url).json() + + +def hackernews_top_stories(max_stories: int = 10) -> list[dict]: + """ + Get the top max_stories posts from HackerNews - https://news.ycombinator.com/ + """ + url = "https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty" + story_ids = requests.get(url).json()[:max_stories] + return [get_hackernews_story(story_id) for story_id in story_ids] + + +def hackernews_top_stories_as_markdown(max_stories: int = 10) -> str: + stories = hackernews_top_stories(max_stories) + return "\n".join("* [{title}]({url})".format(**story) for story in stories) + + +if __name__ == "__main__": + print(hackernews_top_stories_as_markdown()) From 6fcefc04535357776124806f0fac40c005b35d1a Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Tue, 26 Oct 2021 18:53:38 +0530 Subject: [PATCH 1660/2908] Add decode function to base16.py (#5575) * Add decode function * Update base16.py * Update base16.py * Update base16.py * Made the line shorter * Made another line shorter --- ciphers/base16.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ciphers/base16.py b/ciphers/base16.py index f27ea4628e54..1ef60868dc3f 100644 --- a/ciphers/base16.py +++ b/ciphers/base16.py @@ -4,6 +4,7 @@ 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!') @@ -11,9 +12,23 @@ def encode_to_b16(inp: str) -> bytes: >>> 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 - return b16encoded + # encode the input into a bytes-like object and then encode b16encode that + return base64.b16encode(inp.encode("utf-8")) + + +def decode_from_b16(b16encoded: bytes) -> str: + """ + Decodes from base-16 to a utf-8 string. + + >>> decode_from_b16(b'48656C6C6F20576F726C6421') + 'Hello World!' + >>> decode_from_b16(b'48454C4C4F20574F524C4421') + 'HELLO WORLD!' + >>> decode_from_b16(b'') + '' + """ + # b16decode the input into bytes and decode that into a human readable string + return base64.b16decode(b16encoded).decode("utf-8") if __name__ == "__main__": From 23f43afee5f7ee3a2bc60ad2997644c54b23bba7 Mon Sep 17 00:00:00 2001 From: Sabari Ganesh <64348740+SabariGanesh-K@users.noreply.github.com> Date: Tue, 26 Oct 2021 21:27:59 +0530 Subject: [PATCH 1661/2908] Added volume conversions (#5607) * Added volume conversions This is a file which has relevant function which helps in conversion between volume units. Available Units:- Cubic metre,Litre,KiloLitre,Gallon,Cubic yard,Cubic foot,cup The file is also written in a way that , adding a new unit can easily be done by modifying tuple available in the source code * Formatted file The file was formatted to follow the syntax formatting rules of the repo * Formatted file further --- conversions/volume_conversions.py | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 conversions/volume_conversions.py diff --git a/conversions/volume_conversions.py b/conversions/volume_conversions.py new file mode 100644 index 000000000000..de2290196fc2 --- /dev/null +++ b/conversions/volume_conversions.py @@ -0,0 +1,79 @@ +""" +Conversion of volume units. +Available Units:- Cubic metre,Litre,KiloLitre,Gallon,Cubic yard,Cubic foot,cup +USAGE : +-> Import this file into their respective project. +-> Use the function length_conversion() for conversion of volume units. +-> Parameters : + -> value : The number of from units you want to convert + -> from_type : From which type you want to convert + -> to_type : To which type you want to convert +REFERENCES : +-> Wikipedia reference: https://en.wikipedia.org/wiki/Cubic_metre +-> Wikipedia reference: https://en.wikipedia.org/wiki/Litre +-> Wikipedia reference: https://en.wiktionary.org/wiki/kilolitre +-> Wikipedia reference: https://en.wikipedia.org/wiki/Gallon +-> Wikipedia reference: https://en.wikipedia.org/wiki/Cubic_yard +-> Wikipedia reference: https://en.wikipedia.org/wiki/Cubic_foot +-> Wikipedia reference: https://en.wikipedia.org/wiki/Cup_(unit) +""" + +from collections import namedtuple + +from_to = namedtuple("from_to", "from_ to") + +METRIC_CONVERSION = { + "cubicmeter": from_to(1, 1), + "litre": from_to(0.001, 1000), + "kilolitre": from_to(1, 1), + "gallon": from_to(0.00454, 264.172), + "cubicyard": from_to(0.76455, 1.30795), + "cubicfoot": from_to(0.028, 35.3147), + "cup": from_to(0.000236588, 4226.75), +} + + +def volume_conversion(value: float, from_type: str, to_type: str) -> float: + """ + Conversion between volume units. + >>> volume_conversion(4, "cubicmeter", "litre") + 4000 + >>> volume_conversion(1, "litre", "gallon") + 0.264172 + >>> volume_conversion(1, "kilolitre", "cubicmeter") + 1 + >>> volume_conversion(3, "gallon", "cubicyard") + 0.017814279 + >>> volume_conversion(2, "cubicyard", "litre") + 1529.1 + >>> volume_conversion(4, "cubicfoot", "cup") + 473.396 + >>> volume_conversion(1, "cup", "kilolitre") + 0.000236588 + >>> volume_conversion(4, "wrongUnit", "litre") + Traceback (most recent call last): + File "/usr/lib/python3.8/doctest.py", line 1336, in __run + exec(compile(example.source, filename, "single", + File "", line 1, in + volume_conversion(4, "wrongUnit", "litre") + File "", line 62, in volume_conversion + ValueError: Invalid 'from_type' value: 'wrongUnit' Supported values are: + cubicmeter, litre, kilolitre, gallon, cubicyard, cubicfoot, cup + """ + if from_type not in METRIC_CONVERSION: + raise ValueError( + f"Invalid 'from_type' value: {from_type!r} Supported values are:\n" + + ", ".join(METRIC_CONVERSION) + ) + if to_type not in METRIC_CONVERSION: + raise ValueError( + f"Invalid 'to_type' value: {to_type!r}. Supported values are:\n" + + ", ".join(METRIC_CONVERSION) + ) + return value * METRIC_CONVERSION[from_type].from_ * METRIC_CONVERSION[to_type].to + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c41bb49fc05339b6c9fbfe4c025946602b4bb919 Mon Sep 17 00:00:00 2001 From: Tarcisio Bruni Rangel Date: Tue, 26 Oct 2021 13:19:00 -0300 Subject: [PATCH 1662/2908] Financials (#5585) * feat: creates math calculations for financials * refactor: make pull request items requirements * refactor: provides type hint for parameters * refactor: applies code review suggestions * refactor: adds more examples tests * refactor: throws ValueError instead of Exception * refactor: fix formatting * refactor: fix formatting * Update interest.py * Update and rename financials/ABOUT.md to financial/ABOUT.md * Rename financials/__init__.py to financial/__init__.py * Rename financials/interest.py to financial/interest.py * https://www.investopedia.com * Update __init__.py * pre-commit: Disable end-of-file-fixer * Revert change to pre-commit * Update __init__.py * __init__.py Co-authored-by: Christian Clauss Co-authored-by: John Law --- financial/ABOUT.md | 4 +++ financial/__init__.py | 0 financial/interest.py | 83 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 financial/ABOUT.md create mode 100644 financial/__init__.py create mode 100644 financial/interest.py diff --git a/financial/ABOUT.md b/financial/ABOUT.md new file mode 100644 index 000000000000..f6b0647f8201 --- /dev/null +++ b/financial/ABOUT.md @@ -0,0 +1,4 @@ +### Interest + +* Compound Interest: "Compound interest is calculated by multiplying the initial principal amount by one plus the annual interest rate raised to the number of compound periods minus one." [Compound Interest](https://www.investopedia.com/) +* Simple Interest: "Simple interest paid or received over a certain period is a fixed percentage of the principal amount that was borrowed or lent. " [Simple Interest](https://www.investopedia.com/) diff --git a/financial/__init__.py b/financial/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/financial/interest.py b/financial/interest.py new file mode 100644 index 000000000000..394da2bc9511 --- /dev/null +++ b/financial/interest.py @@ -0,0 +1,83 @@ +# https://www.investopedia.com + +from __future__ import annotations + + +def simple_interest( + principle: float, daily_interest_rate: float, days_between_payments: int +) -> float: + """ + >>> simple_interest(18000.0, 0.06, 3) + 3240.0 + >>> simple_interest(0.5, 0.06, 3) + 0.09 + >>> simple_interest(18000.0, 0.01, 10) + 1800.0 + >>> simple_interest(18000.0, 0.0, 3) + 0.0 + >>> simple_interest(5500.0, 0.01, 100) + 5500.0 + >>> simple_interest(10000.0, -0.06, 3) + Traceback (most recent call last): + ... + ValueError: daily_interest_rate must be >= 0 + >>> simple_interest(-10000.0, 0.06, 3) + Traceback (most recent call last): + ... + ValueError: principle must be > 0 + >>> simple_interest(5500.0, 0.01, -5) + Traceback (most recent call last): + ... + ValueError: days_between_payments must be > 0 + """ + if days_between_payments <= 0: + raise ValueError("days_between_payments must be > 0") + if daily_interest_rate < 0: + raise ValueError("daily_interest_rate must be >= 0") + if principle <= 0: + raise ValueError("principle must be > 0") + return principle * daily_interest_rate * days_between_payments + + +def compound_interest( + principle: float, + nominal_annual_interest_rate_percentage: float, + number_of_compounding_periods: int, +) -> float: + """ + >>> compound_interest(10000.0, 0.05, 3) + 1576.2500000000014 + >>> compound_interest(10000.0, 0.05, 1) + 500.00000000000045 + >>> compound_interest(0.5, 0.05, 3) + 0.07881250000000006 + >>> compound_interest(10000.0, 0.06, -4) + Traceback (most recent call last): + ... + ValueError: number_of_compounding_periods must be > 0 + >>> compound_interest(10000.0, -3.5, 3.0) + Traceback (most recent call last): + ... + ValueError: nominal_annual_interest_rate_percentage must be >= 0 + >>> compound_interest(-5500.0, 0.01, 5) + Traceback (most recent call last): + ... + ValueError: principle must be > 0 + """ + if number_of_compounding_periods <= 0: + raise ValueError("number_of_compounding_periods must be > 0") + if nominal_annual_interest_rate_percentage < 0: + raise ValueError("nominal_annual_interest_rate_percentage must be >= 0") + if principle <= 0: + raise ValueError("principle must be > 0") + + return principle * ( + (1 + nominal_annual_interest_rate_percentage) ** number_of_compounding_periods + - 1 + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From cb4dc197238e670d150eaebd8f387670f7a54377 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 26 Oct 2021 18:41:32 +0200 Subject: [PATCH 1663/2908] Financial: principle -> principal (#5614) * Financial: principle -> principal The originally invested amount of money: `principal` -- https://www.grammarly.com/blog/principle-principal/ * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 ++++ financial/interest.py | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 67f2113ea87d..6fbd5e2cc1c5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -129,6 +129,7 @@ * [Rgb Hsv Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/rgb_hsv_conversion.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) + * [Volume Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/volume_conversions.py) * [Weight Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/weight_conversion.py) ## Data Structures @@ -290,6 +291,9 @@ * Tests * [Test Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/tests/test_send_file.py) +## Financial + * [Interest](https://github.com/TheAlgorithms/Python/blob/master/financial/interest.py) + ## Fractals * [Julia Sets](https://github.com/TheAlgorithms/Python/blob/master/fractals/julia_sets.py) * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/fractals/koch_snowflake.py) diff --git a/financial/interest.py b/financial/interest.py index 394da2bc9511..c69c730457d9 100644 --- a/financial/interest.py +++ b/financial/interest.py @@ -4,7 +4,7 @@ def simple_interest( - principle: float, daily_interest_rate: float, days_between_payments: int + principal: float, daily_interest_rate: float, days_between_payments: int ) -> float: """ >>> simple_interest(18000.0, 0.06, 3) @@ -24,7 +24,7 @@ def simple_interest( >>> simple_interest(-10000.0, 0.06, 3) Traceback (most recent call last): ... - ValueError: principle must be > 0 + ValueError: principal must be > 0 >>> simple_interest(5500.0, 0.01, -5) Traceback (most recent call last): ... @@ -34,13 +34,13 @@ def simple_interest( raise ValueError("days_between_payments must be > 0") if daily_interest_rate < 0: raise ValueError("daily_interest_rate must be >= 0") - if principle <= 0: - raise ValueError("principle must be > 0") - return principle * daily_interest_rate * days_between_payments + if principal <= 0: + raise ValueError("principal must be > 0") + return principal * daily_interest_rate * days_between_payments def compound_interest( - principle: float, + principal: float, nominal_annual_interest_rate_percentage: float, number_of_compounding_periods: int, ) -> float: @@ -62,16 +62,16 @@ def compound_interest( >>> compound_interest(-5500.0, 0.01, 5) Traceback (most recent call last): ... - ValueError: principle must be > 0 + ValueError: principal must be > 0 """ if number_of_compounding_periods <= 0: raise ValueError("number_of_compounding_periods must be > 0") if nominal_annual_interest_rate_percentage < 0: raise ValueError("nominal_annual_interest_rate_percentage must be >= 0") - if principle <= 0: - raise ValueError("principle must be > 0") + if principal <= 0: + raise ValueError("principal must be > 0") - return principle * ( + return principal * ( (1 + nominal_annual_interest_rate_percentage) ** number_of_compounding_periods - 1 ) From 31061aacf2482e7a8521f577b68930716dab21eb Mon Sep 17 00:00:00 2001 From: Connor Bottum Date: Tue, 26 Oct 2021 12:43:46 -0400 Subject: [PATCH 1664/2908] fix: use `+=` in sorts/recursive_mergesort_array.py (#5019) --- sorts/recursive_mergesort_array.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sorts/recursive_mergesort_array.py b/sorts/recursive_mergesort_array.py index f714d02380cf..e02c02405565 100644 --- a/sorts/recursive_mergesort_array.py +++ b/sorts/recursive_mergesort_array.py @@ -38,23 +38,23 @@ def merge(arr: list[int]) -> list[int]: ): # Runs until the lowers size of the left and right are sorted. if left_array[left_index] < right_array[right_index]: arr[index] = left_array[left_index] - left_index = left_index + 1 + left_index += 1 else: arr[index] = right_array[right_index] - right_index = right_index + 1 - index = index + 1 + right_index += 1 + index += 1 while ( left_index < left_size ): # Adds the left over elements in the left half of the array arr[index] = left_array[left_index] - left_index = left_index + 1 - index = index + 1 + left_index += 1 + index += 1 while ( right_index < right_size ): # Adds the left over elements in the right half of the array arr[index] = right_array[right_index] - right_index = right_index + 1 - index = index + 1 + right_index += 1 + index += 1 return arr From b4036b70f1f51c29d0c94a591e8738ac81d7397a Mon Sep 17 00:00:00 2001 From: Srishtik Bhandarkar <53395406+srishtik2310@users.noreply.github.com> Date: Tue, 26 Oct 2021 22:40:15 +0530 Subject: [PATCH 1665/2908] Add solution for probelm_686 of project_euler (#5480) * Added solution for probelm_686 of project_euler * Changed documentation and formatting. * Added ref link to optimization logic * Update project_euler/problem_686/sol1.py Co-authored-by: John Law Co-authored-by: John Law --- project_euler/problem_686/__init__.py | 0 project_euler/problem_686/sol1.py | 160 ++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 project_euler/problem_686/__init__.py create mode 100644 project_euler/problem_686/sol1.py diff --git a/project_euler/problem_686/__init__.py b/project_euler/problem_686/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_686/sol1.py b/project_euler/problem_686/sol1.py new file mode 100644 index 000000000000..3b6bdb655170 --- /dev/null +++ b/project_euler/problem_686/sol1.py @@ -0,0 +1,160 @@ +""" +Project Euler Problem 686: https://projecteuler.net/problem=686 + +2^7 = 128 is the first power of two whose leading digits are "12". +The next power of two whose leading digits are "12" is 2^80. + +Define p(L,n) to be the nth-smallest value of j such that +the base 10 representation of 2^j begins with the digits of L. + +So p(12, 1) = 7 and p(12, 2) = 80. + +You are given that p(123, 45) = 12710. + +Find p(123, 678910). +""" + +import math + + +def log_difference(number: int) -> float: + """ + This function returns the decimal value of a number multiplied with log(2) + Since the problem is on powers of two, finding the powers of two with + large exponents is time consuming. Hence we use log to reduce compute time. + + We can find out that the first power of 2 with starting digits 123 is 90. + Computing 2^90 is time consuming. + Hence we find log(2^90) = 90*log(2) = 27.092699609758302 + But we require only the decimal part to determine whether the power starts with 123. + SO we just return the decimal part of the log product. + Therefore we return 0.092699609758302 + + >>> log_difference(90) + 0.092699609758302 + >>> log_difference(379) + 0.090368356648852 + + """ + + log_number = math.log(2, 10) * number + difference = round((log_number - int(log_number)), 15) + + return difference + + +def solution(number: int = 678910) -> int: + """ + This function calculates the power of two which is nth (n = number) + smallest value of power of 2 + such that the starting digits of the 2^power is 123. + + For example the powers of 2 for which starting digits is 123 are: + 90, 379, 575, 864, 1060, 1545, 1741, 2030, 2226, 2515 and so on. + 90 is the first power of 2 whose starting digits are 123, + 379 is second power of 2 whose starting digits are 123, + and so on. + + So if number = 10, then solution returns 2515 as we observe from above series. + + Wwe will define a lowerbound and upperbound. + lowerbound = log(1.23), upperbound = log(1.24) + because we need to find the powers that yield 123 as starting digits. + + log(1.23) = 0.08990511143939792, log(1,24) = 0.09342168516223506. + We use 1.23 and not 12.3 or 123, because log(1.23) yields only decimal value + which is less than 1. + log(12.3) will be same decimal vale but 1 added to it + which is log(12.3) = 1.093421685162235. + We observe that decimal value remains same no matter 1.23 or 12.3 + Since we use the function log_difference(), + which returns the value that is only decimal part, using 1.23 is logical. + + If we see, 90*log(2) = 27.092699609758302, + decimal part = 0.092699609758302, which is inside the range of lowerbound + and upperbound. + + If we compute the difference between all the powers which lead to 123 + starting digits is as follows: + + 379 - 90 = 289 + 575 - 379 = 196 + 864 - 575 = 289 + 1060 - 864 = 196 + + We see a pattern here. The difference is either 196 or 289 = 196 + 93. + + Hence to optimize the algorithm we will increment by 196 or 93 depending upon the + log_difference() value. + + Lets take for example 90. + Since 90 is the first power leading to staring digits as 123, + we will increment iterator by 196. + Because the difference between any two powers leading to 123 + as staring digits is greater than or equal to 196. + After incrementing by 196 we get 286. + + log_difference(286) = 0.09457875989861 which is greater than upperbound. + The next power is 379, and we need to add 93 to get there. + The iterator will now become 379, + which is the next power leading to 123 as starting digits. + + Lets take 1060. We increment by 196, we get 1256. + log_difference(1256) = 0.09367455396034, + Which is greater than upperbound hence we increment by 93. Now iterator is 1349. + log_difference(1349) = 0.08946415071057 which is less than lowerbound. + The next power is 1545 and we need to add 196 to get 1545. + + Conditions are as follows: + + 1) If we find a power, whose log_difference() is in the range of + lower and upperbound, we will increment by 196. + which implies that the power is a number which will lead to 123 as starting digits. + 2) If we find a power, whose log_difference() is greater than or equal upperbound, + we will increment by 93. + 3) if log_difference() < lowerbound, we increment by 196. + + Reference to the above logic: + https://math.stackexchange.com/questions/4093970/powers-of-2-starting-with-123-does-a-pattern-exist + + >>> solution(1000) + 284168 + + >>> solution(56000) + 15924915 + + >>> solution(678910) + 193060223 + + """ + + power_iterator = 90 + position = 0 + + lower_limit = math.log(1.23, 10) + upper_limit = math.log(1.24, 10) + previous_power = 0 + + while position < number: + difference = log_difference(power_iterator) + + if difference >= upper_limit: + power_iterator += 93 + + elif difference < lower_limit: + power_iterator += 196 + + else: + previous_power = power_iterator + power_iterator += 196 + position += 1 + + return previous_power + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print(f"{solution() = }") From 582f57f41fb9d36ae8fe4d49c98775877b9013b7 Mon Sep 17 00:00:00 2001 From: Sabari Ganesh <64348740+SabariGanesh-K@users.noreply.github.com> Date: Tue, 26 Oct 2021 23:25:41 +0530 Subject: [PATCH 1666/2908] Added physical pressure units (#5613) * Added physical pressure units This uses tuple pair which stores units required to be converted to respective other units as mentioned. Available Units:- Pascal,Bar,Kilopascal,Megapascal,psi(pound per square inch),inHg(in mercury column),torr,atm * Formatted file File was formatted as per repo rules * Reformatted file :) * Added more reference * More reference added --- conversions/pressure_conversions.py | 85 +++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 conversions/pressure_conversions.py diff --git a/conversions/pressure_conversions.py b/conversions/pressure_conversions.py new file mode 100644 index 000000000000..2018080b9327 --- /dev/null +++ b/conversions/pressure_conversions.py @@ -0,0 +1,85 @@ +""" +Conversion of pressure units. +Available Units:- Pascal,Bar,Kilopascal,Megapascal,psi(pound per square inch), +inHg(in mercury column),torr,atm +USAGE : +-> Import this file into their respective project. +-> Use the function pressure_conversion() for conversion of pressure units. +-> Parameters : + -> value : The number of from units you want to convert + -> from_type : From which type you want to convert + -> to_type : To which type you want to convert +REFERENCES : +-> Wikipedia reference: https://en.wikipedia.org/wiki/Pascal_(unit) +-> Wikipedia reference: https://en.wikipedia.org/wiki/Pound_per_square_inch +-> Wikipedia reference: https://en.wikipedia.org/wiki/Inch_of_mercury +-> Wikipedia reference: https://en.wikipedia.org/wiki/Torr +-> https://en.wikipedia.org/wiki/Standard_atmosphere_(unit) +-> https://msestudent.com/what-are-the-units-of-pressure/ +-> https://www.unitconverters.net/pressure-converter.html +""" + +from collections import namedtuple + +from_to = namedtuple("from_to", "from_ to") + +PRESSURE_CONVERSION = { + "atm": from_to(1, 1), + "pascal": from_to(0.0000098, 101325), + "bar": from_to(0.986923, 1.01325), + "kilopascal": from_to(0.00986923, 101.325), + "megapascal": from_to(9.86923, 0.101325), + "psi": from_to(0.068046, 14.6959), + "inHg": from_to(0.0334211, 29.9213), + "torr": from_to(0.00131579, 760), +} + + +def pressure_conversion(value: float, from_type: str, to_type: str) -> float: + """ + Conversion between pressure units. + >>> pressure_conversion(4, "atm", "pascal") + 405300 + >>> pressure_conversion(1, "pascal", "psi") + 0.00014401981999999998 + >>> pressure_conversion(1, "bar", "atm") + 0.986923 + >>> pressure_conversion(3, "kilopascal", "bar") + 0.029999991892499998 + >>> pressure_conversion(2, "megapascal", "psi") + 290.074434314 + >>> pressure_conversion(4, "psi", "torr") + 206.85984 + >>> pressure_conversion(1, "inHg", "atm") + 0.0334211 + >>> pressure_conversion(1, "torr", "psi") + 0.019336718261000002 + >>> pressure_conversion(4, "wrongUnit", "atm") + Traceback (most recent call last): + File "/usr/lib/python3.8/doctest.py", line 1336, in __run + exec(compile(example.source, filename, "single", + File "", line 1, in + pressure_conversion(4, "wrongUnit", "atm") + File "", line 67, in pressure_conversion + ValueError: Invalid 'from_type' value: 'wrongUnit' Supported values are: + atm, pascal, bar, kilopascal, megapascal, psi, inHg, torr + """ + if from_type not in PRESSURE_CONVERSION: + raise ValueError( + f"Invalid 'from_type' value: {from_type!r} Supported values are:\n" + + ", ".join(PRESSURE_CONVERSION) + ) + if to_type not in PRESSURE_CONVERSION: + raise ValueError( + f"Invalid 'to_type' value: {to_type!r}. Supported values are:\n" + + ", ".join(PRESSURE_CONVERSION) + ) + return ( + value * PRESSURE_CONVERSION[from_type].from_ * PRESSURE_CONVERSION[to_type].to + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c0ed031b3fcf47736f98dfd89e2588dbffceadde Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Tue, 26 Oct 2021 11:33:08 -0700 Subject: [PATCH 1667/2908] Fix type annotations for stack.py (#5566) --- data_structures/stacks/balanced_parentheses.py | 2 +- .../stacks/dijkstras_two_stack_algorithm.py | 4 ++-- .../stacks/infix_to_postfix_conversion.py | 2 +- data_structures/stacks/stack.py | 18 +++++++++++------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 674f7ea436ed..3c036c220e5c 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -14,7 +14,7 @@ def balanced_parentheses(parentheses: str) -> bool: >>> balanced_parentheses("") True """ - stack = Stack() + stack: Stack[str] = Stack() bracket_pairs = {"(": ")", "[": "]", "{": "}"} for bracket in parentheses: if bracket in bracket_pairs: diff --git a/data_structures/stacks/dijkstras_two_stack_algorithm.py b/data_structures/stacks/dijkstras_two_stack_algorithm.py index 8b4668f9f839..ba2ca92c7b5c 100644 --- a/data_structures/stacks/dijkstras_two_stack_algorithm.py +++ b/data_structures/stacks/dijkstras_two_stack_algorithm.py @@ -51,8 +51,8 @@ def dijkstras_two_stack_algorithm(equation: str) -> int: """ operators = {"*": op.mul, "/": op.truediv, "+": op.add, "-": op.sub} - operand_stack = Stack() - operator_stack = Stack() + operand_stack: Stack[int] = Stack() + operator_stack: Stack[str] = Stack() for i in equation: if i.isdigit(): diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index dedba8479ac8..b812d108e290 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -38,7 +38,7 @@ def infix_to_postfix(expression_str: str) -> str: """ if not balanced_parentheses(expression_str): raise ValueError("Mismatched parentheses") - stack = Stack() + stack: Stack[str] = Stack() postfix = [] for char in expression_str: if char.isalpha() or char.isdigit(): diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 4bc032f72561..d1c73df43067 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -1,5 +1,9 @@ from __future__ import annotations +from typing import Generic, TypeVar + +T = TypeVar("T") + class StackOverflowError(BaseException): pass @@ -9,7 +13,7 @@ class StackUnderflowError(BaseException): pass -class Stack: +class Stack(Generic[T]): """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 @@ -19,7 +23,7 @@ class Stack: """ def __init__(self, limit: int = 10): - self.stack: list[int] = [] + self.stack: list[T] = [] self.limit = limit def __bool__(self) -> bool: @@ -28,13 +32,13 @@ def __bool__(self) -> bool: def __str__(self) -> str: return str(self.stack) - def push(self, data): + def push(self, data: T) -> None: """Push an element to the top of the stack.""" if len(self.stack) >= self.limit: raise StackOverflowError self.stack.append(data) - def pop(self): + def pop(self) -> T: """ Pop an element off of the top of the stack. @@ -47,7 +51,7 @@ def pop(self): raise StackUnderflowError return self.stack.pop() - def peek(self): + def peek(self) -> T: """ Peek at the top-most element of the stack. @@ -71,7 +75,7 @@ def size(self) -> int: """Return the size of the stack.""" return len(self.stack) - def __contains__(self, item) -> bool: + def __contains__(self, item: T) -> bool: """Check if item is in stack""" return item in self.stack @@ -80,7 +84,7 @@ def test_stack() -> None: """ >>> test_stack() """ - stack = Stack(10) + stack: Stack[int] = Stack(10) assert bool(stack) is False assert stack.is_empty() is True assert stack.is_full() is False From 9a03919052276bfe83f90a7ba2fe258d20a104e9 Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Tue, 26 Oct 2021 12:12:46 -0700 Subject: [PATCH 1668/2908] [mypy] Fix type annotations for stack_using_dll.py (#5577) * Fix mypy annotations for stack_using_dll.py * Replace Optional with inline union type --- data_structures/stacks/stack_using_dll.py | 40 ++++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/data_structures/stacks/stack_using_dll.py b/data_structures/stacks/stack_using_dll.py index 75e0cd20640d..a129665f209f 100644 --- a/data_structures/stacks/stack_using_dll.py +++ b/data_structures/stacks/stack_using_dll.py @@ -1,15 +1,21 @@ # A complete working Python program to demonstrate all # stack operations using a doubly linked list +from __future__ import annotations -class Node: - def __init__(self, data): +from typing import Generic, TypeVar + +T = TypeVar("T") + + +class Node(Generic[T]): + def __init__(self, data: T): self.data = data # Assign data - self.next = None # Initialize next as null - self.prev = None # Initialize prev as null + self.next: Node[T] | None = None # Initialize next as null + self.prev: Node[T] | None = None # Initialize prev as null -class Stack: +class Stack(Generic[T]): """ >>> stack = Stack() >>> stack.is_empty() @@ -35,10 +41,10 @@ class Stack: 2->1->0-> """ - def __init__(self): - self.head = None + def __init__(self) -> None: + self.head: Node[T] | None = None - def push(self, data): + def push(self, data: T) -> None: """add a Node to the stack""" if self.head is None: self.head = Node(data) @@ -49,21 +55,23 @@ def push(self, data): new_node.prev = None self.head = new_node - def pop(self): + def pop(self) -> T | None: """pop the top element off the stack""" if self.head is None: return None else: + assert self.head is not None temp = self.head.data self.head = self.head.next - self.head.prev = None + if self.head is not None: + self.head.prev = None return temp - def top(self): + def top(self) -> T | None: """return the top element of the stack""" - return self.head.data + return self.head.data if self.head is not None else None - def __len__(self): + def __len__(self) -> int: temp = self.head count = 0 while temp is not None: @@ -71,10 +79,10 @@ def __len__(self): temp = temp.next return count - def is_empty(self): + def is_empty(self) -> bool: return self.head is None - def print_stack(self): + def print_stack(self) -> None: print("stack elements are:") temp = self.head while temp is not None: @@ -86,7 +94,7 @@ def print_stack(self): if __name__ == "__main__": # Start with the empty stack - stack = Stack() + stack: Stack[int] = Stack() # Insert 4 at the beginning. So stack becomes 4->None print("Stack operations using Doubly LinkedList") From 4eb5c12727d7590d17a5cbefe34c6a255f69e670 Mon Sep 17 00:00:00 2001 From: Matteo Messmer <40521259+matteomessmer@users.noreply.github.com> Date: Wed, 27 Oct 2021 00:28:26 +0200 Subject: [PATCH 1669/2908] Sphere intersection and spherical cap volumes (#5579) * sphere intersection + spherical cap volume formulas * reformatted * Update volume.py Co-authored-by: Christian Clauss --- maths/volume.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/maths/volume.py b/maths/volume.py index 51b2b9fc0334..fd24aa9eef54 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -20,6 +20,56 @@ def vol_cube(side_length: int | float) -> float: return pow(side_length, 3) +def vol_spherical_cap(height: float, radius: float) -> float: + """ + Calculate the Volume of the spherical cap. + :return 1/3 pi * height ^ 2 * (3 * radius - height) + + >>> vol_spherical_cap(1, 2) + 5.235987755982988 + """ + return 1 / 3 * pi * pow(height, 2) * (3 * radius - height) + + +def vol_spheres_intersect( + radius_1: float, radius_2: float, centers_distance: float +) -> float: + """ + Calculate the volume of the intersection of two spheres. + + The intersection is composed by two spherical caps and therefore its volume is the + sum of the volumes of the spherical caps. First it calculates the heights (h1, h2) + of the the spherical caps, then the two volumes and it returns the sum. + The height formulas are + h1 = (radius_1 - radius_2 + centers_distance) + * (radius_1 + radius_2 - centers_distance) + / (2 * centers_distance) + h2 = (radius_2 - radius_1 + centers_distance) + * (radius_2 + radius_1 - centers_distance) + / (2 * centers_distance) + if centers_distance is 0 then it returns the volume of the smallers sphere + :return vol_spherical_cap(h1, radius_2) + vol_spherical_cap(h2, radius_1) + + >>> vol_spheres_intersect(2, 2, 1) + 21.205750411731103 + """ + if centers_distance == 0: + return vol_sphere(min(radius_1, radius_2)) + + h1 = ( + (radius_1 - radius_2 + centers_distance) + * (radius_1 + radius_2 - centers_distance) + / (2 * centers_distance) + ) + h2 = ( + (radius_2 - radius_1 + centers_distance) + * (radius_2 + radius_1 - centers_distance) + / (2 * centers_distance) + ) + + return vol_spherical_cap(h1, radius_2) + vol_spherical_cap(h2, radius_1) + + def vol_cuboid(width: float, height: float, length: float) -> float: """ Calculate the Volume of a Cuboid. @@ -127,6 +177,8 @@ def main(): 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 + print("Spherical cap: " + str(vol_spherical_cap(1, 2))) # ~= 5.24 + print("Spheres intersetion: " + str(vol_spheres_intersect(2, 2, 1))) # ~= 21.21 if __name__ == "__main__": From 8285913e81fb8f46b90d0e19da233862964c07dc Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 27 Oct 2021 00:45:33 -0300 Subject: [PATCH 1670/2908] [mypy] Fix and add type annotations (#5618) --- data_structures/queue/double_ended_queue.py | 72 ++++++--------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 36106d8bc0d9..a4658d99759c 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -1,6 +1,8 @@ """ Implementation of double ended queue. """ +from __future__ import annotations + from dataclasses import dataclass from typing import Any, Iterable @@ -8,35 +10,23 @@ class Deque: """ Deque data structure. - Operations ---------- append(val: Any) -> None - appendleft(val: Any) -> None - extend(iter: Iterable) -> None - extendleft(iter: Iterable) -> None - pop() -> Any - popleft() -> Any - - Observers --------- is_empty() -> bool - - Attributes ---------- _front: _Node front of the deque a.k.a. the first element - _back: _Node back of the element a.k.a. the last element - _len: int the number of nodes """ @@ -51,13 +41,12 @@ class _Node: """ val: Any = None - next: "Deque._Node" = None - prev: "Deque._Node" = None + next: Deque._Node | None = None + prev: Deque._Node | None = None class _Iterator: """ Helper class for iteration. Will be used to implement iteration. - Attributes ---------- _cur: _Node @@ -66,10 +55,10 @@ class _Iterator: __slots__ = ["_cur"] - def __init__(self, cur: "Deque._Node") -> None: + def __init__(self, cur: Deque._Node | None) -> None: self._cur = cur - def __iter__(self) -> "Deque._Iterator": + def __iter__(self) -> Deque._Iterator: """ >>> our_deque = Deque([1, 2, 3]) >>> iterator = iter(our_deque) @@ -95,9 +84,10 @@ def __next__(self) -> Any: return val - def __init__(self, iterable: Iterable = None) -> None: - self._front = self._back = None - self._len = 0 + def __init__(self, iterable: Iterable[Any] | None = None) -> None: + self._front: Any = None + self._back: Any = None + self._len: int = 0 if iterable is not None: # append every value to the deque @@ -108,7 +98,6 @@ def append(self, val: Any) -> None: """ Adds val to the end of the deque. Time complexity: O(1) - >>> our_deque_1 = Deque([1, 2, 3]) >>> our_deque_1.append(4) >>> our_deque_1 @@ -117,7 +106,6 @@ def append(self, val: Any) -> None: >>> our_deque_2.append('c') >>> our_deque_2 ['a', 'b', 'c'] - >>> from collections import deque >>> deque_collections_1 = deque([1, 2, 3]) >>> deque_collections_1.append(4) @@ -127,7 +115,6 @@ def append(self, val: Any) -> None: >>> deque_collections_2.append('c') >>> deque_collections_2 deque(['a', 'b', 'c']) - >>> list(our_deque_1) == list(deque_collections_1) True >>> list(our_deque_2) == list(deque_collections_2) @@ -153,7 +140,6 @@ def appendleft(self, val: Any) -> None: """ Adds val to the beginning of the deque. Time complexity: O(1) - >>> our_deque_1 = Deque([2, 3]) >>> our_deque_1.appendleft(1) >>> our_deque_1 @@ -162,7 +148,6 @@ def appendleft(self, val: Any) -> None: >>> our_deque_2.appendleft('a') >>> our_deque_2 ['a', 'b', 'c'] - >>> from collections import deque >>> deque_collections_1 = deque([2, 3]) >>> deque_collections_1.appendleft(1) @@ -172,7 +157,6 @@ def appendleft(self, val: Any) -> None: >>> deque_collections_2.appendleft('a') >>> deque_collections_2 deque(['a', 'b', 'c']) - >>> list(our_deque_1) == list(deque_collections_1) True >>> list(our_deque_2) == list(deque_collections_2) @@ -194,11 +178,10 @@ def appendleft(self, val: Any) -> None: # make sure there were no errors assert not self.is_empty(), "Error on appending value." - def extend(self, iter: Iterable) -> None: + def extend(self, iter: Iterable[Any]) -> None: """ Appends every value of iter to the end of the deque. Time complexity: O(n) - >>> our_deque_1 = Deque([1, 2, 3]) >>> our_deque_1.extend([4, 5]) >>> our_deque_1 @@ -207,7 +190,6 @@ def extend(self, iter: Iterable) -> None: >>> our_deque_2.extend('cd') >>> our_deque_2 ['a', 'b', 'c', 'd'] - >>> from collections import deque >>> deque_collections_1 = deque([1, 2, 3]) >>> deque_collections_1.extend([4, 5]) @@ -217,7 +199,6 @@ def extend(self, iter: Iterable) -> None: >>> deque_collections_2.extend('cd') >>> deque_collections_2 deque(['a', 'b', 'c', 'd']) - >>> list(our_deque_1) == list(deque_collections_1) True >>> list(our_deque_2) == list(deque_collections_2) @@ -226,11 +207,10 @@ def extend(self, iter: Iterable) -> None: for val in iter: self.append(val) - def extendleft(self, iter: Iterable) -> None: + def extendleft(self, iter: Iterable[Any]) -> None: """ Appends every value of iter to the beginning of the deque. Time complexity: O(n) - >>> our_deque_1 = Deque([1, 2, 3]) >>> our_deque_1.extendleft([0, -1]) >>> our_deque_1 @@ -239,7 +219,6 @@ def extendleft(self, iter: Iterable) -> None: >>> our_deque_2.extendleft('ba') >>> our_deque_2 ['a', 'b', 'c', 'd'] - >>> from collections import deque >>> deque_collections_1 = deque([1, 2, 3]) >>> deque_collections_1.extendleft([0, -1]) @@ -249,7 +228,6 @@ def extendleft(self, iter: Iterable) -> None: >>> deque_collections_2.extendleft('ba') >>> deque_collections_2 deque(['a', 'b', 'c', 'd']) - >>> list(our_deque_1) == list(deque_collections_1) True >>> list(our_deque_2) == list(deque_collections_2) @@ -262,16 +240,13 @@ def pop(self) -> Any: """ Removes the last element of the deque and returns it. Time complexity: O(1) - @returns topop.val: the value of the node to pop. - >>> our_deque = Deque([1, 2, 3, 15182]) >>> our_popped = our_deque.pop() >>> our_popped 15182 >>> our_deque [1, 2, 3] - >>> from collections import deque >>> deque_collections = deque([1, 2, 3, 15182]) >>> collections_popped = deque_collections.pop() @@ -279,7 +254,6 @@ def pop(self) -> Any: 15182 >>> deque_collections deque([1, 2, 3]) - >>> list(our_deque) == list(deque_collections) True >>> our_popped == collections_popped @@ -302,16 +276,13 @@ def popleft(self) -> Any: """ Removes the first element of the deque and returns it. Time complexity: O(1) - @returns topop.val: the value of the node to pop. - >>> our_deque = Deque([15182, 1, 2, 3]) >>> our_popped = our_deque.popleft() >>> our_popped 15182 >>> our_deque [1, 2, 3] - >>> from collections import deque >>> deque_collections = deque([15182, 1, 2, 3]) >>> collections_popped = deque_collections.popleft() @@ -319,7 +290,6 @@ def popleft(self) -> Any: 15182 >>> deque_collections deque([1, 2, 3]) - >>> list(our_deque) == list(deque_collections) True >>> our_popped == collections_popped @@ -340,14 +310,12 @@ def is_empty(self) -> bool: """ Checks if the deque is empty. Time complexity: O(1) - >>> our_deque = Deque([1, 2, 3]) >>> our_deque.is_empty() False >>> our_empty_deque = Deque() >>> our_empty_deque.is_empty() True - >>> from collections import deque >>> empty_deque_collections = deque() >>> list(our_empty_deque) == list(empty_deque_collections) @@ -359,14 +327,12 @@ def __len__(self) -> int: """ Implements len() function. Returns the length of the deque. Time complexity: O(1) - >>> our_deque = Deque([1, 2, 3]) >>> len(our_deque) 3 >>> our_empty_deque = Deque() >>> len(our_empty_deque) 0 - >>> from collections import deque >>> deque_collections = deque([1, 2, 3]) >>> len(deque_collections) @@ -379,11 +345,10 @@ def __len__(self) -> int: """ return self._len - def __eq__(self, other: "Deque") -> bool: + def __eq__(self, other: object) -> bool: """ Implements "==" operator. Returns if *self* is equal to *other*. Time complexity: O(n) - >>> our_deque_1 = Deque([1, 2, 3]) >>> our_deque_2 = Deque([1, 2, 3]) >>> our_deque_1 == our_deque_2 @@ -391,7 +356,6 @@ def __eq__(self, other: "Deque") -> bool: >>> our_deque_3 = Deque([1, 2]) >>> our_deque_1 == our_deque_3 False - >>> from collections import deque >>> deque_collections_1 = deque([1, 2, 3]) >>> deque_collections_2 = deque([1, 2, 3]) @@ -400,12 +364,15 @@ def __eq__(self, other: "Deque") -> bool: >>> deque_collections_3 = deque([1, 2]) >>> deque_collections_1 == deque_collections_3 False - >>> (our_deque_1 == our_deque_2) == (deque_collections_1 == deque_collections_2) True >>> (our_deque_1 == our_deque_3) == (deque_collections_1 == deque_collections_3) True """ + + if not isinstance(other, Deque): + return NotImplemented + me = self._front oth = other._front @@ -422,18 +389,16 @@ def __eq__(self, other: "Deque") -> bool: return True - def __iter__(self) -> "_Iterator": + def __iter__(self) -> Deque._Iterator: """ Implements iteration. Time complexity: O(1) - >>> our_deque = Deque([1, 2, 3]) >>> for v in our_deque: ... print(v) 1 2 3 - >>> from collections import deque >>> deque_collections = deque([1, 2, 3]) >>> for v in deque_collections: @@ -449,7 +414,6 @@ def __repr__(self) -> str: Implements representation of the deque. Represents it as a list, with its values between '[' and ']'. Time complexity: O(n) - >>> our_deque = Deque([1, 2, 3]) >>> our_deque [1, 2, 3] From fe5c711ce68cb1d410d13d8c8a02ee7bfd49b1d3 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 27 Oct 2021 03:48:43 +0000 Subject: [PATCH 1671/2908] Rewrite parts of Vector and Matrix (#5362) * Rewrite parts of Vector and Matrix methods * Refactor determinant method and add unit tests Refactor determinant method to create separate minor and cofactor methods. Add respective unit tests for new methods. Rename methods using snake case to follow Python naming conventions. * Reorganize Vector and Matrix methods * Update linear_algebra/README.md Co-authored-by: John Law * Fix punctuation and wording * Apply suggestions from code review Co-authored-by: John Law Co-authored-by: John Law --- knapsack/README.md | 2 +- linear_algebra/README.md | 44 +-- linear_algebra/src/lib.py | 378 ++++++++++++---------- linear_algebra/src/test_linear_algebra.py | 96 ++++-- 4 files changed, 295 insertions(+), 225 deletions(-) diff --git a/knapsack/README.md b/knapsack/README.md index 6041c1e48eb8..f31e5f591412 100644 --- a/knapsack/README.md +++ b/knapsack/README.md @@ -17,7 +17,7 @@ The knapsack problem has been studied for more than a century, with early works ## 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)`. +For instance, try `help(Vector)`, `help(unit_basis_vector)`, and `help(CLASSNAME.METHODNAME)`. --- diff --git a/linear_algebra/README.md b/linear_algebra/README.md index dc6085090d02..35b50b5e0f0a 100644 --- a/linear_algebra/README.md +++ b/linear_algebra/README.md @@ -10,56 +10,56 @@ This module contains classes and functions for doing linear algebra. - - This class represents a vector of arbitrary size and related operations. - **Overview about the methods:** + **Overview of the methods:** - - constructor(components : list) : init the vector - - set(components : list) : changes the vector components. + - constructor(components) : init the vector + - set(components) : changes the vector components. - \_\_str\_\_() : toString method - - component(i : int): gets the i-th component (start by 0) + - component(i): gets the i-th component (0-indexed) - \_\_len\_\_() : gets the size / length of the vector (number of components) - - euclidLength() : returns the eulidean length of the vector. + - euclidean_length() : 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. + - copy() : copies this vector and returns it + - change_component(pos,value) : changes the specified component -- function zeroVector(dimension) +- function zero_vector(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) +- function unit_basis_vector(dimension, pos) + - returns a unit basis vector with a one at index 'pos' (0-indexed) +- 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'. +- function random_vector(N, a, b) + - returns a random vector of size N, with random integer components between 'a' and 'b' inclusive ### class Matrix - - This class represents a matrix of arbitrary size and operations on it. - **Overview about the methods:** + **Overview of 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. + - change_component(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 + - determinant() : returns the determinant of the matrix if it is square - operator + : implements the matrix-addition. - - operator - _ implements the matrix-subtraction + - operator - : implements the matrix-subtraction -- function squareZeroMatrix(N) +- function square_zero_matrix(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' +- function random_matrix(W, H, a, b) + - returns a random matrix WxH with integer components between 'a' and 'b' inclusive --- ## 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)`. +For instance, try `help(Vector)`, `help(unit_basis_vector)`, and `help(CLASSNAME.METHODNAME)`. --- diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 6a18df5e15c3..dad0a8c0a6a2 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -10,13 +10,13 @@ Overview: - class Vector -- function zeroVector(dimension) -- function unitBasisVector(dimension,pos) -- function axpy(scalar,vector1,vector2) -- function randomVector(N,a,b) +- function zero_vector(dimension) +- function unit_basis_vector(dimension, pos) +- function axpy(scalar, vector1, vector2) +- function random_vector(N, a, b) - class Matrix -- function squareZeroMatrix(N) -- function randomMatrix(W,H,a,b) +- function square_zero_matrix(N) +- function random_matrix(W, H, a, b) """ from __future__ import annotations @@ -30,20 +30,23 @@ 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 + Overview of the methods: + + __init__(components: Collection[float] | None): init the vector + __len__(): gets the size of the vector (number of components) + __str__(): returns a string representation + __add__(other: Vector): vector addition + __sub__(other: Vector): vector subtraction + __mul__(other: float): scalar multiplication + __mul__(other: Vector): dot product + set(components: Collection[float]): changes the vector components + copy(): copies this vector and returns it + component(i): gets the i-th component (0-indexed) + change_component(pos: int, value: float): changes specified component + euclidean_length(): returns the euclidean length of the vector + magnitude(): returns the magnitude of the vector + angle(other: Vector, deg: bool): returns the angle between two vectors + TODO: compare-operator """ def __init__(self, components: Collection[float] | None = None) -> None: @@ -55,47 +58,17 @@ def __init__(self, components: Collection[float] | None = None) -> None: components = [] self.__components = list(components) - def set(self, components: Collection[float]) -> None: - """ - input: new components - changes the components of the vector. - replace the components with newer one. - """ - if len(components) > 0: - self.__components = list(components) - else: - raise Exception("please give any vector") - - def __str__(self) -> str: - """ - returns a string representation of the vector - """ - return "(" + ",".join(map(str, self.__components)) + ")" - - def component(self, i: int) -> float: - """ - 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] - else: - raise Exception("index out of range") - def __len__(self) -> int: """ returns the size of the vector """ return len(self.__components) - def euclidLength(self) -> float: + def __str__(self) -> str: """ - returns the euclidean length of the vector + returns a string representation of the vector """ - summe: float = 0 - for c in self.__components: - summe += c ** 2 - return math.sqrt(summe) + return "(" + ",".join(map(str, self.__components)) + ")" def __add__(self, other: Vector) -> Vector: """ @@ -139,15 +112,57 @@ def __mul__(self, other: float | Vector) -> float | Vector: if isinstance(other, float) or isinstance(other, int): ans = [c * other for c in self.__components] return Vector(ans) - elif isinstance(other, Vector) and (len(self) == len(other)): + elif isinstance(other, Vector) and len(self) == len(other): size = len(self) - summe: float = 0 - for i in range(size): - summe += self.__components[i] * other.component(i) - return summe + prods = [self.__components[i] * other.component(i) for i in range(size)] + return sum(prods) else: # error case raise Exception("invalid operand!") + def set(self, components: Collection[float]) -> None: + """ + input: new components + changes the components of the vector. + replaces the components with newer one. + """ + if len(components) > 0: + self.__components = list(components) + else: + raise Exception("please give any vector") + + def copy(self) -> Vector: + """ + copies this vector and returns it. + """ + return Vector(self.__components) + + def component(self, i: int) -> float: + """ + input: index (0-indexed) + 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] + else: + raise Exception("index out of range") + + def change_component(self, pos: int, value: float) -> None: + """ + input: an index (pos) and a value + changes the specified component (pos) with the + 'value' + """ + # precondition + assert -len(self.__components) <= pos < len(self.__components) + self.__components[pos] = value + + def euclidean_length(self) -> float: + """ + returns the euclidean length of the vector + """ + squares = [c ** 2 for c in self.__components] + return math.sqrt(sum(squares)) + def magnitude(self) -> float: """ Magnitude of a Vector @@ -156,7 +171,8 @@ def magnitude(self) -> float: 5.385164807134504 """ - return sum([i ** 2 for i in self.__components]) ** (1 / 2) + squares = [c ** 2 for c in self.__components] + return math.sqrt(sum(squares)) def angle(self, other: Vector, deg: bool = False) -> float: """ @@ -178,24 +194,8 @@ def angle(self, other: Vector, deg: bool = False) -> float: else: return math.acos(num / den) - def copy(self) -> Vector: - """ - copies this vector and returns it. - """ - return Vector(self.__components) - - def changeComponent(self, pos: int, value: float) -> None: - """ - input: an index (pos) and a value - changes the specified component (pos) with the - 'value' - """ - # precondition - assert -len(self.__components) <= pos < len(self.__components) - self.__components[pos] = value - -def zeroVector(dimension: int) -> Vector: +def zero_vector(dimension: int) -> Vector: """ returns a zero-vector of size 'dimension' """ @@ -204,7 +204,7 @@ def zeroVector(dimension: int) -> Vector: return Vector([0] * dimension) -def unitBasisVector(dimension: int, pos: int) -> Vector: +def unit_basis_vector(dimension: int, pos: int) -> Vector: """ returns a unit basis vector with a One at index 'pos' (indexing at 0) @@ -225,13 +225,13 @@ def axpy(scalar: float, x: Vector, y: Vector) -> Vector: # precondition assert ( isinstance(x, Vector) - and (isinstance(y, Vector)) + and isinstance(y, Vector) and (isinstance(scalar, int) or isinstance(scalar, float)) ) return x * scalar + y -def randomVector(N: int, a: int, b: int) -> Vector: +def random_vector(n: int, a: int, b: int) -> Vector: """ input: size (N) of the vector. random range (a,b) @@ -239,26 +239,30 @@ def randomVector(N: int, a: int, b: int) -> Vector: random integer components between 'a' and 'b'. """ random.seed(None) - ans = [random.randint(a, b) for _ in range(N)] + ans = [random.randint(a, b) for _ in range(n)] return Vector(ans) class Matrix: """ class: Matrix - This class represents a arbitrary 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 - operator + : implements the matrix-addition. - operator - _ implements the matrix-subtraction + This class represents an arbitrary matrix. + + Overview of the methods: + + __init__(): + __str__(): returns a string representation + __add__(other: Matrix): matrix addition + __sub__(other: Matrix): matrix subtraction + __mul__(other: float): scalar multiplication + __mul__(other: Vector): vector multiplication + height() : returns height + width() : returns width + component(x: int, y: int): returns specified component + change_component(x: int, y: int, value: float): changes specified component + minor(x: int, y: int): returns minor along (x, y) + cofactor(x: int, y: int): returns cofactor along (x, y) + determinant() : returns determinant """ def __init__(self, matrix: list[list[float]], w: int, h: int) -> None: @@ -285,62 +289,37 @@ def __str__(self) -> str: ans += str(self.__matrix[i][j]) + "|\n" return ans - def changeComponent(self, x: int, y: int, value: float) -> None: - """ - changes the x-y component of this matrix - """ - if 0 <= x < self.__height and 0 <= y < self.__width: - self.__matrix[x][y] = value - else: - raise Exception("changeComponent: indices out of bounds") - - def component(self, x: int, y: int) -> float: + def __add__(self, other: Matrix) -> Matrix: """ - returns the specified (x,y) component + implements the matrix-addition. """ - if 0 <= x < self.__height and 0 <= y < self.__width: - return self.__matrix[x][y] + if self.__width == other.width() and self.__height == other.height(): + matrix = [] + for i in range(self.__height): + row = [ + self.__matrix[i][j] + other.component(i, j) + for j in range(self.__width) + ] + matrix.append(row) + return Matrix(matrix, self.__width, self.__height) else: - raise Exception("changeComponent: indices out of bounds") - - def width(self) -> int: - """ - getter for the width - """ - return self.__width + raise Exception("matrix must have the same dimension!") - def height(self) -> int: + def __sub__(self, other: Matrix) -> Matrix: """ - getter for the height + implements the matrix-subtraction. """ - 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 + if self.__width == other.width() and self.__height == other.height(): + matrix = [] + for i in range(self.__height): + row = [ + self.__matrix[i][j] - other.component(i, j) + for j in range(self.__width) + ] + matrix.append(row) + return Matrix(matrix, self.__width, self.__height) else: - raise Exception("matrix is not square") + raise Exception("matrices must have the same dimension!") @overload def __mul__(self, other: float) -> Matrix: @@ -355,20 +334,20 @@ def __mul__(self, other: float | Vector) -> Vector | Matrix: implements the matrix-vector multiplication. implements the matrix-scalar multiplication """ - if isinstance(other, Vector): # vector-matrix + if isinstance(other, Vector): # matrix-vector if len(other) == self.__width: - ans = zeroVector(self.__height) + ans = zero_vector(self.__height) for i in range(self.__height): - summe: float = 0 - for j in range(self.__width): - summe += other.component(j) * self.__matrix[i][j] - ans.changeComponent(i, summe) - summe = 0 + prods = [ + self.__matrix[i][j] * other.component(j) + for j in range(self.__width) + ] + ans.change_component(i, sum(prods)) return ans else: raise Exception( "vector must have the same size as the " - + "number of columns of the matrix!" + "number of columns of the matrix!" ) elif isinstance(other, int) or isinstance(other, float): # matrix-scalar matrix = [ @@ -377,52 +356,95 @@ def __mul__(self, other: float | Vector) -> Vector | Matrix: ] return Matrix(matrix, self.__width, self.__height) - def __add__(self, other: Matrix) -> Matrix: + def height(self) -> int: """ - implements the matrix-addition. + getter for the 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)) - matrix.append(row) - return Matrix(matrix, self.__width, self.__height) + return self.__height + + def width(self) -> int: + """ + getter for the width + """ + return self.__width + + def component(self, x: int, y: int) -> float: + """ + returns the specified (x,y) component + """ + if 0 <= x < self.__height and 0 <= y < self.__width: + return self.__matrix[x][y] else: - raise Exception("matrix must have the same dimension!") + raise Exception("change_component: indices out of bounds") - def __sub__(self, other: Matrix) -> Matrix: + def change_component(self, x: int, y: int, value: float) -> None: """ - implements the matrix-subtraction. + changes the x-y component of this matrix """ - 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)) - matrix.append(row) - return Matrix(matrix, self.__width, self.__height) + if 0 <= x < self.__height and 0 <= y < self.__width: + self.__matrix[x][y] = value else: - raise Exception("matrix must have the same dimension!") + raise Exception("change_component: indices out of bounds") + + def minor(self, x: int, y: int) -> float: + """ + returns the minor along (x, y) + """ + if self.__height != self.__width: + raise Exception("Matrix is not square") + minor = self.__matrix[:x] + self.__matrix[x + 1 :] + for i in range(len(minor)): + minor[i] = minor[i][:y] + minor[i][y + 1 :] + return Matrix(minor, self.__width - 1, self.__height - 1).determinant() + + def cofactor(self, x: int, y: int) -> float: + """ + returns the cofactor (signed minor) along (x, y) + """ + if self.__height != self.__width: + raise Exception("Matrix is not square") + if 0 <= x < self.__height and 0 <= y < self.__width: + return (-1) ** (x + y) * self.minor(x, y) + else: + raise Exception("Indices out of bounds") + + def determinant(self) -> float: + """ + returns the determinant of an nxn matrix using Laplace expansion + """ + if self.__height != self.__width: + raise Exception("Matrix is not square") + if self.__height < 1: + raise Exception("Matrix has no element") + elif self.__height == 1: + return self.__matrix[0][0] + elif self.__height == 2: + return ( + self.__matrix[0][0] * self.__matrix[1][1] + - self.__matrix[0][1] * self.__matrix[1][0] + ) + else: + cofactor_prods = [ + self.__matrix[0][y] * self.cofactor(0, y) for y in range(self.__width) + ] + return sum(cofactor_prods) -def squareZeroMatrix(N: int) -> Matrix: +def square_zero_matrix(n: int) -> Matrix: """ returns a square zero-matrix of dimension NxN """ - ans: list[list[float]] = [[0] * N for _ in range(N)] - return Matrix(ans, N, N) + ans: list[list[float]] = [[0] * n for _ in range(n)] + return Matrix(ans, n, n) -def randomMatrix(W: int, H: int, a: int, b: int) -> Matrix: +def random_matrix(width: int, height: int, a: int, b: int) -> Matrix: """ returns a random matrix WxH with integer components between 'a' and 'b' """ random.seed(None) matrix: list[list[float]] = [ - [random.randint(a, b) for _ in range(W)] for _ in range(H) + [random.randint(a, b) for _ in range(width)] for _ in range(height) ] - return Matrix(matrix, W, H) + return Matrix(matrix, width, height) diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 0954a2d932b7..de7041a17038 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -8,13 +8,20 @@ """ import unittest -from .lib import Matrix, Vector, axpy, squareZeroMatrix, unitBasisVector, zeroVector +from .lib import ( + Matrix, + Vector, + axpy, + square_zero_matrix, + unit_basis_vector, + zero_vector, +) class Test(unittest.TestCase): def test_component(self) -> None: """ - test for method component + test for method component() """ x = Vector([1, 2, 3]) self.assertEqual(x.component(0), 1) @@ -23,24 +30,24 @@ def test_component(self) -> None: def test_str(self) -> None: """ - test for toString() method + test for method toString() """ x = Vector([0, 0, 0, 0, 0, 1]) self.assertEqual(str(x), "(0,0,0,0,0,1)") def test_size(self) -> None: """ - test for size()-method + test for method size() """ x = Vector([1, 2, 3, 4]) self.assertEqual(len(x), 4) def test_euclidLength(self) -> None: """ - test for the eulidean length + test for method euclidean_length() """ x = Vector([1, 2]) - self.assertAlmostEqual(x.euclidLength(), 2.236, 3) + self.assertAlmostEqual(x.euclidean_length(), 2.236, 3) def test_add(self) -> None: """ @@ -67,26 +74,26 @@ def test_mul(self) -> None: test for * operator """ x = Vector([1, 2, 3]) - a = Vector([2, -1, 4]) # for test of dot-product + 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) -> None: """ - test for the global function zeroVector(...) + test for global function zero_vector() """ - self.assertTrue(str(zeroVector(10)).count("0") == 10) + self.assertTrue(str(zero_vector(10)).count("0") == 10) def test_unitBasisVector(self) -> None: """ - test for the global function unitBasisVector(...) + test for global function unit_basis_vector() """ - self.assertEqual(str(unitBasisVector(3, 1)), "(0,1,0)") + self.assertEqual(str(unit_basis_vector(3, 1)), "(0,1,0)") def test_axpy(self) -> None: """ - test for the global function axpy(...) (operation) + test for global function axpy() (operation) """ x = Vector([1, 2, 3]) y = Vector([1, 0, 1]) @@ -94,7 +101,7 @@ def test_axpy(self) -> None: def test_copy(self) -> None: """ - test for the copy()-method + test for method copy() """ x = Vector([1, 0, 0, 0, 0, 0]) y = x.copy() @@ -102,53 +109,94 @@ def test_copy(self) -> None: def test_changeComponent(self) -> None: """ - test for the changeComponent(...)-method + test for method change_component() """ x = Vector([1, 0, 0]) - x.changeComponent(0, 0) - x.changeComponent(1, 1) + x.change_component(0, 0) + x.change_component(1, 1) self.assertEqual(str(x), "(0,1,0)") def test_str_matrix(self) -> None: + """ + test for Matrix method str() + """ 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) -> None: + def test_minor(self) -> None: + """ + test for Matrix method minor() + """ + A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + minors = [[-3, -14, -10], [-5, -10, -5], [-2, -1, 0]] + for x in range(A.height()): + for y in range(A.width()): + self.assertEqual(minors[x][y], A.minor(x, y)) + + def test_cofactor(self) -> None: """ - test for determinate() + test for Matrix method cofactor() """ - 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()) + A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + cofactors = [[-3, 14, -10], [5, -10, 5], [-2, 1, 0]] + for x in range(A.height()): + for y in range(A.width()): + self.assertEqual(cofactors[x][y], A.cofactor(x, y)) + + def test_determinant(self) -> None: + """ + test for Matrix method determinant() + """ + A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + self.assertEqual(-5, A.determinant()) def test__mul__matrix(self) -> None: + """ + test for Matrix * operator + """ 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) -> None: + def test_change_component_matrix(self) -> None: + """ + test for Matrix method change_component() + """ A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) - A.changeComponent(0, 2, 5) + A.change_component(0, 2, 5) self.assertEqual("|1,2,5|\n|2,4,5|\n|6,7,8|\n", str(A)) def test_component_matrix(self) -> None: + """ + test for Matrix method component() + """ 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) -> None: + """ + test for Matrix + operator + """ 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) -> None: + """ + test for Matrix - operator + """ 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) -> None: + """ + test for global function square_zero_matrix() + """ 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)), + "|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(square_zero_matrix(5)), ) From ce9a139b56735ee05fc21679b6a8e35940c7ca77 Mon Sep 17 00:00:00 2001 From: harshitkap00r <76745800+harshitkap00r@users.noreply.github.com> Date: Wed, 27 Oct 2021 09:55:48 +0530 Subject: [PATCH 1672/2908] Update binary_search.py (#4856) Take less time to calculate --- searches/binary_search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 0966cd8de857..88fee47157c6 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -51,7 +51,7 @@ def bisect_left( hi = len(sorted_collection) while lo < hi: - mid = (lo + hi) // 2 + mid = lo + (hi - lo) // 2 if sorted_collection[mid] < item: lo = mid + 1 else: @@ -96,7 +96,7 @@ def bisect_right( hi = len(sorted_collection) while lo < hi: - mid = (lo + hi) // 2 + mid = lo + (hi - lo) // 2 if sorted_collection[mid] <= item: lo = mid + 1 else: From 329feb492843129a285e8ec11b9d0c07c28579ba Mon Sep 17 00:00:00 2001 From: Prakhar Gurunani Date: Wed, 27 Oct 2021 14:49:04 +0530 Subject: [PATCH 1673/2908] Add Project Euler Problem 078 solution 01 (#5565) * Create sol1.py * updating DIRECTORY.md * Create __init__.py * Add docstring * Reformat with black * Fix flake8 issues * Add EOL * Fix formatting issues * Add docstring * Add func return type * Change return type * Remove test print statement * Reformat code * Fix return types * Break loop * Update doctest sol * Update project_euler/problem_078/sol1.py Co-authored-by: John Law * Added doctest and changed return type * Add int() * Fix flake8 issues * Use argument instead of fixed constant * Update sol1.py * fix sol1.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 2 + project_euler/problem_078/__init__.py | 0 project_euler/problem_078/sol1.py | 55 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 project_euler/problem_078/__init__.py create mode 100644 project_euler/problem_078/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 6fbd5e2cc1c5..c94fb78d6275 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -788,6 +788,8 @@ * [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 078 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_078/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_078/__init__.py b/project_euler/problem_078/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_078/sol1.py b/project_euler/problem_078/sol1.py new file mode 100644 index 000000000000..f92cf0f4020c --- /dev/null +++ b/project_euler/problem_078/sol1.py @@ -0,0 +1,55 @@ +""" +Problem 78 +Url: https://projecteuler.net/problem=78 +Statement: +Let p(n) represent the number of different ways in which n coins +can be separated into piles. For example, five coins can be separated +into piles in exactly seven different ways, so p(5)=7. + + OOOOO + OOOO O + OOO OO + OOO O O + OO OO O + OO O O O + O O O O O +Find the least value of n for which p(n) is divisible by one million. +""" + +import itertools + + +def solution(number: int = 1000000) -> int: + """ + >>> solution() + 55374 + """ + partitions = [1] + + for i in itertools.count(len(partitions)): + item = 0 + for j in itertools.count(1): + sign = -1 if j % 2 == 0 else +1 + index = (j * j * 3 - j) // 2 + if index > i: + break + item += partitions[i - index] * sign + index += j + if index > i: + break + item += partitions[i - index] * sign + item %= number + + if item == 0: + return i + partitions.append(item) + + return 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print(f"{solution() = }") From 615c428903602bf40d2e15dec44be914409fe804 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Wed, 27 Oct 2021 22:00:03 +0530 Subject: [PATCH 1674/2908] Add doctest for exception (#5629) * Add doctest for exception * Spelling correction --- maths/area.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/maths/area.py b/maths/area.py index 13c05af5f68e..7b39312cfaf0 100644 --- a/maths/area.py +++ b/maths/area.py @@ -203,6 +203,18 @@ def area_triangle_three_sides(side1: float, side2: float, side3: float) -> float Traceback (most recent call last): ... ValueError: area_triangle_three_sides() only accepts non-negative values + >>> area_triangle_three_sides(2, 4, 7) + Traceback (most recent call last): + ... + ValueError: Given three sides do not form a triangle + >>> area_triangle_three_sides(2, 7, 4) + Traceback (most recent call last): + ... + ValueError: Given three sides do not form a triangle + >>> area_triangle_three_sides(7, 2, 4) + Traceback (most recent call last): + ... + ValueError: Given three sides do not form a triangle """ if side1 < 0 or side2 < 0 or side3 < 0: raise ValueError("area_triangle_three_sides() only accepts non-negative values") From 6b6762bde9e242c21bae147c0e0d56bd072ece96 Mon Sep 17 00:00:00 2001 From: "@im_8055" <38890773+Bhargavishnu@users.noreply.github.com> Date: Wed, 27 Oct 2021 22:48:21 +0530 Subject: [PATCH 1675/2908] Fix pull request template (#5633) The existing template uses * to apply bold font weight. As we already have the ### to markdown the text as heading, its redundant to have the *s. --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 103ecf7c288a..4d2265968612 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -### **Describe your change:** +### Describe your change: @@ -6,7 +6,7 @@ * [ ] Fix a bug or typo in an existing algorithm? * [ ] Documentation change? -### **Checklist:** +### 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. From bf6db32ec2fb04b6477722f0809c5efef0cad813 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Thu, 28 Oct 2021 11:05:31 -0300 Subject: [PATCH 1676/2908] [mypy] Fix type annotations for binary tree traversals in data structures (#5556) * [mypy] Fix type annotations for binary tree traversals in data structures * Change variable name and update level_order_1 to use a deque Using a deque instead of a list here, because since we are removing from the beginning of the list, the deque will be more efficient. * remove duplicate function * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: John Law * fix function name at line 137 * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: John Law * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: John Law * Remove type alias and use the new syntax * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: John Law * Remove prints inside functions and return lists Co-authored-by: John Law --- .../binary_tree/binary_tree_traversals.py | 158 ++++++++++-------- 1 file changed, 92 insertions(+), 66 deletions(-) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index de9e9d60d272..9a62393914da 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -1,7 +1,9 @@ # https://en.wikipedia.org/wiki/Tree_traversal from __future__ import annotations +from collections import deque from dataclasses import dataclass +from typing import Any, Sequence @dataclass @@ -11,11 +13,11 @@ class Node: right: Node | None = None -def make_tree() -> Node: +def make_tree() -> Node | None: return Node(1, Node(2, Node(4), Node(5)), Node(3)) -def preorder(root: Node): +def preorder(root: Node | None) -> list[int]: """ Pre-order traversal visits root node, left subtree, right subtree. >>> preorder(make_tree()) @@ -24,7 +26,7 @@ def preorder(root: Node): return [root.data] + preorder(root.left) + preorder(root.right) if root else [] -def postorder(root: Node): +def postorder(root: Node | None) -> list[int]: """ Post-order traversal visits left subtree, right subtree, root node. >>> postorder(make_tree()) @@ -33,7 +35,7 @@ def postorder(root: Node): return postorder(root.left) + postorder(root.right) + [root.data] if root else [] -def inorder(root: Node): +def inorder(root: Node | None) -> list[int]: """ In-order traversal visits left subtree, root node, right subtree. >>> inorder(make_tree()) @@ -42,7 +44,7 @@ def inorder(root: Node): return inorder(root.left) + [root.data] + inorder(root.right) if root else [] -def height(root: Node): +def height(root: Node | None) -> int: """ Recursive function for calculating the height of the binary tree. >>> height(None) @@ -53,80 +55,99 @@ def height(root: Node): return (max(height(root.left), height(root.right)) + 1) if root else 0 -def level_order_1(root: Node): +def level_order(root: Node | None) -> Sequence[Node | None]: """ - Print whole binary tree in Level Order Traverse. + Returns a list of nodes value from a 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 + output: list[Any] = [] + if root is None: + return output -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) + process_queue = deque([root]) + + while process_queue: + node = process_queue.popleft() + output.append(node.data) + if node.left: + process_queue.append(node.left) + if node.right: + process_queue.append(node.right) + return output -def print_left_to_right(root: Node, level: int): + +def get_nodes_from_left_to_right( + root: Node | None, level: int +) -> Sequence[Node | None]: """ - Print elements on particular level from left to right direction of the binary tree. + Returns a list of nodes value from a particular level: + 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) + output: list[Any] = [] + + def populate_output(root: Node | None, level: int) -> None: + if not root: + return + if level == 1: + output.append(root.data) + elif level > 1: + populate_output(root.left, level - 1) + populate_output(root.right, level - 1) -def print_right_to_left(root: Node, level: int): + populate_output(root, level) + return output + + +def get_nodes_from_right_to_left( + root: Node | None, level: int +) -> Sequence[Node | None]: """ - Print elements on particular level from right to left direction of the binary tree. + Returns a list of nodes value from a particular level: + 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) + output: list[Any] = [] + + def populate_output(root: Node | None, level: int) -> None: + if root is None: + return + if level == 1: + output.append(root.data) + elif level > 1: + populate_output(root.right, level - 1) + populate_output(root.left, level - 1) + populate_output(root, level) + return output -def zigzag(root: Node): + +def zigzag(root: Node | None) -> Sequence[Node | None] | list[Any]: """ - ZigZag traverse: Print node left to right and right to left, alternatively. + ZigZag traverse: + Returns a list of nodes value from left to right and right to left, alternatively. """ + if root is None: + return [] + + output: list[Sequence[Node | None]] = [] + flag = 0 height_tree = height(root) + for h in range(1, height_tree + 1): - if flag == 0: - print_left_to_right(root, h) + if not flag: + output.append(get_nodes_from_left_to_right(root, h)) flag = 1 else: - print_right_to_left(root, h) + output.append(get_nodes_from_right_to_left(root, h)) flag = 0 + return output + -def main(): # Main function for testing. +def main() -> None: # Main function for testing. """ Create binary tree. """ @@ -134,18 +155,23 @@ def main(): # Main function for testing. """ 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() + + print(f"In-order Traversal: {inorder(root)}") + print(f"Pre-order Traversal: {preorder(root)}") + print(f"Post-order Traversal: {postorder(root)}", "\n") + + print(f"Height of Tree: {height(root)}", "\n") + + print("Complete Level Order Traversal: ") + print(level_order(root), "\n") + + print("Level-wise order Traversal: ") + + for level in range(1, height(root) + 1): + print(f"Level {level}:", get_nodes_from_left_to_right(root, level=level)) + + print("\nZigZag order Traversal: ") + print(zigzag(root)) if __name__ == "__main__": From 70368a757e8d37b9f3dd96af4ca535275cb39580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20A=2E=20Rodr=C3=ADguez?= Date: Thu, 28 Oct 2021 11:43:24 -0300 Subject: [PATCH 1677/2908] Implement Circular Queue using linked lists. Fixes TheAlgorithms#5361 (#5587) * CircularQueueLinkedList: empty list, trivial implementation TheAlgorithms#5361 * CircularQueueLinkedList: single element list TheAlgorithms#5361 * CircularQueueLinkedList: refactor, no que empty attribute TheAlgorithms#5361 * CircularQueueLinkedList: refactor TheAlgorithms#5361 * CircularQueueLinkedList: changed internal data structure to use double linked list TheAlgorithms#5361 * CircularQueueLinkedList: enqueue test cases added TheAlgorithms#5361 * CircularQueueLinkedList: track full queue TheAlgorithms#5361 * CircularQueueLinkedList: adding functions description TheAlgorithms#5361 * CircularQueueLinkedList: type hints TheAlgorithms#5361 * CircularQueueLinkedList: algorithm explanation TheAlgorithms#5361 * CircularQueueLinkedList: missing type hints TheAlgorithms#5361 * CircularQueueLinkedList: more missing type hints TheAlgorithms#5361 * Update data_structures/queue/circular_queue_linked_list.py Co-authored-by: John Law --- .../queue/circular_queue_linked_list.py | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 data_structures/queue/circular_queue_linked_list.py diff --git a/data_structures/queue/circular_queue_linked_list.py b/data_structures/queue/circular_queue_linked_list.py new file mode 100644 index 000000000000..1878403bd2ef --- /dev/null +++ b/data_structures/queue/circular_queue_linked_list.py @@ -0,0 +1,150 @@ +# Implementation of Circular Queue using linked lists +# https://en.wikipedia.org/wiki/Circular_buffer + +from typing import Any + + +class CircularQueueLinkedList: + """ + Circular FIFO list with the given capacity (default queue length : 6) + + >>> cq = CircularQueueLinkedList(2) + >>> cq.enqueue('a') + >>> cq.enqueue('b') + >>> cq.enqueue('c') + Traceback (most recent call last): + ... + Exception: Full Queue + """ + + def __init__(self, initial_capacity: int = 6) -> None: + self.front = None + self.rear = None + self.create_linked_list(initial_capacity) + + def create_linked_list(self, initial_capacity: int) -> None: + current_node = Node() + self.front = current_node + self.rear = current_node + previous_node = current_node + for i in range(1, initial_capacity): + current_node = Node() + previous_node.next = current_node + current_node.prev = previous_node + previous_node = current_node + previous_node.next = self.front + self.front.prev = previous_node + + def is_empty(self) -> bool: + """ + Checks where the queue is empty or not + >>> cq = CircularQueueLinkedList() + >>> cq.is_empty() + True + >>> cq.enqueue('a') + >>> cq.is_empty() + False + >>> cq.dequeue() + 'a' + >>> cq.is_empty() + True + """ + return self.front == self.rear and self.front.data is None + + def first(self) -> Any: + """ + Returns the first element of the queue + >>> cq = CircularQueueLinkedList() + >>> cq.first() + Traceback (most recent call last): + ... + Exception: Empty Queue + >>> cq.enqueue('a') + >>> cq.first() + 'a' + >>> cq.dequeue() + 'a' + >>> cq.first() + Traceback (most recent call last): + ... + Exception: Empty Queue + >>> cq.enqueue('b') + >>> cq.enqueue('c') + >>> cq.first() + 'b' + """ + self.check_can_perform_operation() + return self.front.data + + def enqueue(self, data: Any) -> None: + """ + Saves data at the end of the queue + + >>> cq = CircularQueueLinkedList() + >>> cq.enqueue('a') + >>> cq.enqueue('b') + >>> cq.dequeue() + 'a' + >>> cq.dequeue() + 'b' + >>> cq.dequeue() + Traceback (most recent call last): + ... + Exception: Empty Queue + """ + self.check_is_full() + if self.is_empty(): + self.rear.data = data + else: + self.rear = self.rear.next + self.rear.data = data + + def dequeue(self) -> Any: + """ + Removes and retrieves the first element of the queue + + >>> cq = CircularQueueLinkedList() + >>> cq.dequeue() + Traceback (most recent call last): + ... + Exception: Empty Queue + >>> cq.enqueue('a') + >>> cq.dequeue() + 'a' + >>> cq.dequeue() + Traceback (most recent call last): + ... + Exception: Empty Queue + """ + self.check_can_perform_operation() + if self.front == self.rear: + data = self.front.data + self.front.data = None + return data + + old_front = self.front + self.front = old_front.next + data = old_front.data + old_front.data = None + return data + + def check_can_perform_operation(self) -> None: + if self.is_empty(): + raise Exception("Empty Queue") + + def check_is_full(self) -> None: + if self.rear.next == self.front: + raise Exception("Full Queue") + + +class Node: + def __init__(self) -> None: + self.data = None + self.next = None + self.prev = None + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 477cc3fe597fd931c742700284016b937c778fe1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 28 Oct 2021 16:45:59 +0200 Subject: [PATCH 1678/2908] Add pyupgrade to pre-commit (#5638) * Add pyupgrade to pre-commit * Remove unused imports * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 6 ++++++ DIRECTORY.md | 3 +++ data_structures/binary_tree/merge_two_binary_trees.py | 4 +--- data_structures/linked_list/skip_list.py | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b666e88aa162..e60003051365 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,12 @@ repos: - id: isort args: - --profile=black + - repo: https://github.com/asottile/pyupgrade + rev: v2.29.0 + hooks: + - id: pyupgrade + args: + - --py39-plus - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.1 hooks: diff --git a/DIRECTORY.md b/DIRECTORY.md index c94fb78d6275..2acfff69dff4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -126,6 +126,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) + * [Pressure Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/pressure_conversions.py) * [Rgb Hsv Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/rgb_hsv_conversion.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) @@ -860,6 +861,8 @@ * [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) + * Problem 686 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_686/sol1.py) ## Quantum * [Deutsch Jozsa](https://github.com/TheAlgorithms/Python/blob/master/quantum/deutsch_jozsa.py) diff --git a/data_structures/binary_tree/merge_two_binary_trees.py b/data_structures/binary_tree/merge_two_binary_trees.py index 7487268940d3..3380f8c5fb31 100644 --- a/data_structures/binary_tree/merge_two_binary_trees.py +++ b/data_structures/binary_tree/merge_two_binary_trees.py @@ -7,8 +7,6 @@ """ from __future__ import annotations -from typing import Optional - class Node: """ @@ -21,7 +19,7 @@ def __init__(self, value: int = 0) -> None: self.right: Node | None = None -def merge_two_binary_trees(tree1: Node | None, tree2: Node | None) -> Optional[Node]: +def merge_two_binary_trees(tree1: Node | None, tree2: Node | None) -> Node | None: """ Returns root node of the merged tree. diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index be30592ec77d..176049120aab 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -5,14 +5,14 @@ from __future__ import annotations from random import random -from typing import Generic, Optional, TypeVar, Union +from typing import Generic, TypeVar KT = TypeVar("KT") VT = TypeVar("VT") class Node(Generic[KT, VT]): - def __init__(self, key: Union[KT, str] = "root", value: Optional[VT] = None): + def __init__(self, key: KT | str = "root", value: VT | None = None): self.key = key self.value = value self.forward: list[Node[KT, VT]] = [] From 11a15cc5842bb44a81bc8ee56af8f25d92a74287 Mon Sep 17 00:00:00 2001 From: Naveen Namani Date: Thu, 28 Oct 2021 22:57:14 +0530 Subject: [PATCH 1679/2908] Add solution for Project Euler problem 67 (#5519) * New solution for Euler problem 67 A faster and memory efficient solution based on the template of sol1.py. Modified the solution to be more memory efficient while reading and generating the array and during the solution finding. No conditions and straightforward logic. * added return type hint * Update project_euler/problem_067/sol2.py Preferring comprehensions over map Co-authored-by: Christian Clauss * Update sol2.py Self explanatory variable names * Updated sol2 to problem 067 in directory * Update project_euler/problem_067/sol2.py Co-authored-by: Christian Clauss * Update project_euler/problem_067/sol2.py Co-authored-by: Christian Clauss * Fixed extra line Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + project_euler/problem_067/sol2.py | 39 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 project_euler/problem_067/sol2.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 2acfff69dff4..a8986f195901 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -771,6 +771,7 @@ * [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) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol2.py) * Problem 069 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_069/sol1.py) * Problem 070 diff --git a/project_euler/problem_067/sol2.py b/project_euler/problem_067/sol2.py new file mode 100644 index 000000000000..2e88a57170a8 --- /dev/null +++ b/project_euler/problem_067/sol2.py @@ -0,0 +1,39 @@ +""" +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() -> int: + """ + 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_path = os.path.join(script_dir, "triangle.txt") + + with open(triangle_path) as in_file: + triangle = [[int(i) for i in line.split()] for line in in_file] + + while len(triangle) != 1: + last_row = triangle.pop() + curr_row = triangle[-1] + for j in range(len(last_row) - 1): + curr_row[j] += max(last_row[j], last_row[j + 1]) + return triangle[0][0] + + +if __name__ == "__main__": + print(solution()) From 61e1dd27b0db00302cec75fca5365d08e81ab707 Mon Sep 17 00:00:00 2001 From: poloso Date: Thu, 28 Oct 2021 15:31:32 -0500 Subject: [PATCH 1680/2908] [mypy] Fix type annotation in euler_method.py (#5649) * [mypy] Fix type annotation in euler_method.py In line with issue #4052. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + maths/euler_method.py | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index a8986f195901..434cddbfd32c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -186,6 +186,7 @@ * [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) + * [Circular Queue Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/circular_queue_linked_list.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) diff --git a/maths/euler_method.py b/maths/euler_method.py index 7c780198602b..155ef28d1f49 100644 --- a/maths/euler_method.py +++ b/maths/euler_method.py @@ -1,18 +1,25 @@ +from typing import Callable + import numpy as np -def explicit_euler(ode_func, y0, x0, step_size, x_end): - """ - Calculate numeric solution at each step to an ODE using Euler's Method +def explicit_euler( + ode_func: Callable, y0: float, x0: float, step_size: float, x_end: float +) -> np.ndarray: + """Calculate numeric solution at each step to an ODE using Euler's Method + + For reference to Euler's method refer to https://en.wikipedia.org/wiki/Euler_method. - https://en.wikipedia.org/wiki/Euler_method + Args: + ode_func (Callable): The ordinary differential equation + as a function of x and y. + y0 (float): The initial value for y. + x0 (float): The initial value for x. + step_size (float): The increment value for x. + x_end (float): The final value of x to be calculated. - 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 + Returns: + np.ndarray: Solution of y for every step in x. >>> # the exact solution is math.exp(x) >>> def f(x, y): From 0590d736fa61833c8f8591f7aa3bbea88b8274f9 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Thu, 28 Oct 2021 17:53:02 -0300 Subject: [PATCH 1681/2908] [mypy] Fix type annotations in `wavelet_tree.py` (#5641) * [mypy] Fix type annotations for wavelet_tree.py * fix a typo --- data_structures/binary_tree/wavelet_tree.py | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/data_structures/binary_tree/wavelet_tree.py b/data_structures/binary_tree/wavelet_tree.py index 173a88ab7316..8d7145189018 100644 --- a/data_structures/binary_tree/wavelet_tree.py +++ b/data_structures/binary_tree/wavelet_tree.py @@ -31,7 +31,7 @@ def __repr__(self) -> str: return f"min_value: {self.minn}, max_value: {self.maxx}" -def build_tree(arr: list[int]) -> Node: +def build_tree(arr: list[int]) -> Node | None: """ Builds the tree for arr and returns the root of the constructed tree @@ -51,7 +51,10 @@ def build_tree(arr: list[int]) -> Node: then recursively build trees for left_arr and right_arr """ pivot = (root.minn + root.maxx) // 2 - left_arr, right_arr = [], [] + + left_arr: list[int] = [] + right_arr: list[int] = [] + for index, num in enumerate(arr): if num <= pivot: left_arr.append(num) @@ -63,7 +66,7 @@ def build_tree(arr: list[int]) -> Node: return root -def rank_till_index(node: Node, num: int, index: int) -> int: +def rank_till_index(node: Node | None, num: int, index: int) -> int: """ Returns the number of occurrences of num in interval [0, index] in the list @@ -79,7 +82,7 @@ def rank_till_index(node: Node, num: int, index: int) -> int: >>> rank_till_index(root, 0, 9) 1 """ - if index < 0: + if index < 0 or node is None: return 0 # Leaf node cases if node.minn == node.maxx: @@ -93,7 +96,7 @@ def rank_till_index(node: Node, num: int, index: int) -> int: return rank_till_index(node.right, num, index - node.map_left[index]) -def rank(node: Node, num: int, start: int, end: int) -> int: +def rank(node: Node | None, num: int, start: int, end: int) -> int: """ Returns the number of occurrences of num in interval [start, end] in the list @@ -114,7 +117,7 @@ def rank(node: Node, num: int, start: int, end: int) -> int: return rank_till_end - rank_before_start -def quantile(node: Node, index: int, start: int, end: int) -> int: +def quantile(node: Node | None, index: int, start: int, end: int) -> int: """ Returns the index'th smallest element in interval [start, end] in the list index is 0-indexed @@ -129,7 +132,7 @@ def quantile(node: Node, index: int, start: int, end: int) -> int: >>> quantile(root, 4, 2, 5) -1 """ - if index > (end - start) or start > end: + if index > (end - start) or start > end or node is None: return -1 # Leaf node case if node.minn == node.maxx: @@ -155,10 +158,10 @@ def quantile(node: Node, index: int, start: int, end: int) -> int: def range_counting( - node: Node, start: int, end: int, start_num: int, end_num: int + node: Node | None, start: int, end: int, start_num: int, end_num: int ) -> int: """ - Returns the number of elememts in range [start_num, end_num] + Returns the number of elements in range [start_num, end_num] in interval [start, end] in the list >>> root = build_tree(test_array) @@ -175,6 +178,7 @@ def range_counting( """ if ( start > end + or node is None or start_num > end_num or node.minn > end_num or node.maxx < start_num From 5c8a6c824723e248047ed6eddfad1a4305de7696 Mon Sep 17 00:00:00 2001 From: Marcus T Date: Thu, 28 Oct 2021 19:53:39 -0400 Subject: [PATCH 1682/2908] Add Pollard's Rho algorithm for integer factorization (#5598) --- maths/pollard_rho.py | 148 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 maths/pollard_rho.py diff --git a/maths/pollard_rho.py b/maths/pollard_rho.py new file mode 100644 index 000000000000..df020c63f2f9 --- /dev/null +++ b/maths/pollard_rho.py @@ -0,0 +1,148 @@ +from math import gcd +from typing import Union + + +def pollard_rho( + num: int, + seed: int = 2, + step: int = 1, + attempts: int = 3, +) -> Union[int, None]: + """ + Use Pollard's Rho algorithm to return a nontrivial factor of ``num``. + The returned factor may be composite and require further factorization. + If the algorithm will return None if it fails to find a factor within + the specified number of attempts or within the specified number of steps. + If ``num`` is prime, this algorithm is guaranteed to return None. + https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm + + >>> pollard_rho(18446744073709551617) + 274177 + >>> pollard_rho(97546105601219326301) + 9876543191 + >>> pollard_rho(100) + 2 + >>> pollard_rho(17) + >>> pollard_rho(17**3) + 17 + >>> pollard_rho(17**3, attempts=1) + >>> pollard_rho(3*5*7) + 21 + >>> pollard_rho(1) + Traceback (most recent call last): + ... + ValueError: The input value cannot be less than 2 + """ + # A value less than 2 can cause an infinite loop in the algorithm. + if num < 2: + raise ValueError("The input value cannot be less than 2") + + # Because of the relationship between ``f(f(x))`` and ``f(x)``, this + # algorithm struggles to find factors that are divisible by two. + # As a workaround, we specifically check for two and even inputs. + # See: https://math.stackexchange.com/a/2856214/165820 + if num > 2 and num % 2 == 0: + return 2 + + # Pollard's Rho algorithm requires a function that returns pseudorandom + # values between 0 <= X < ``num``. It doesn't need to be random in the + # sense that the output value is cryptographically secure or difficult + # to calculate, it only needs to be random in the sense that all output + # values should be equally likely to appear. + # For this reason, Pollard suggested using ``f(x) = (x**2 - 1) % num`` + # However, the success of Pollard's algorithm isn't guaranteed and is + # determined in part by the initial seed and the chosen random function. + # To make retries easier, we will instead use ``f(x) = (x**2 + C) % num`` + # where ``C`` is a value that we can modify between each attempt. + def rand_fn(value: int, step: int, modulus: int) -> int: + """ + Returns a pseudorandom value modulo ``modulus`` based on the + input ``value`` and attempt-specific ``step`` size. + + >>> rand_fn(0, 0, 0) + Traceback (most recent call last): + ... + ZeroDivisionError: integer division or modulo by zero + >>> rand_fn(1, 2, 3) + 0 + >>> rand_fn(0, 10, 7) + 3 + >>> rand_fn(1234, 1, 17) + 16 + """ + return (pow(value, 2) + step) % modulus + + for attempt in range(attempts): + # These track the position within the cycle detection logic. + tortoise = seed + hare = seed + + while True: + # At each iteration, the tortoise moves one step and the hare moves two. + tortoise = rand_fn(tortoise, step, num) + hare = rand_fn(hare, step, num) + hare = rand_fn(hare, step, num) + + # At some point both the tortoise and the hare will enter a cycle whose + # length ``p`` is a divisor of ``num``. Once in that cycle, at some point + # the tortoise and hare will end up on the same value modulo ``p``. + # We can detect when this happens because the position difference between + # the tortoise and the hare will share a common divisor with ``num``. + divisor = gcd(hare - tortoise, num) + + if divisor == 1: + # No common divisor yet, just keep searching. + continue + else: + # We found a common divisor! + if divisor == num: + # Unfortunately, the divisor is ``num`` itself and is useless. + break + else: + # The divisor is a nontrivial factor of ``num``! + return divisor + + # If we made it here, then this attempt failed. + # We need to pick a new starting seed for the tortoise and hare + # in addition to a new step value for the random function. + # To keep this example implementation deterministic, the + # new values will be generated based on currently available + # values instead of using something like ``random.randint``. + + # We can use the hare's position as the new seed. + # This is actually what Richard Brent's the "optimized" variant does. + seed = hare + + # The new step value for the random function can just be incremented. + # At first the results will be similar to what the old function would + # have produced, but the value will quickly diverge after a bit. + step += 1 + + # We haven't found a divisor within the requested number of attempts. + # We were unlucky or ``num`` itself is actually prime. + return None + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument( + "num", + type=int, + help="The value to find a divisor of", + ) + parser.add_argument( + "--attempts", + type=int, + default=3, + help="The number of attempts before giving up", + ) + args = parser.parse_args() + + divisor = pollard_rho(args.num, attempts=args.attempts) + if divisor is None: + print(f"{args.num} is probably prime") + else: + quotient = args.num // divisor + print(f"{args.num} = {divisor} * {quotient}") From 0fc24e86296c613f5aa24015518a9f187a2cdbb6 Mon Sep 17 00:00:00 2001 From: Andrew Grangaard Date: Thu, 28 Oct 2021 22:21:16 -0700 Subject: [PATCH 1683/2908] [mypy] Annotates other/scoring_algorithm (#5621) * scoring_algorithm: Moves doctest into function docstring so it will be run * [mypy] annotates other/scoring_algorithm * [mypy] renames temp var to unique value to work around mypy issue in other/scoring_algorithm reusing loop variables with the same name and different types gives this very confusing mypy error response. pyright correctly infers the types without issue. ``` scoring_algorithm.py:58: error: Incompatible types in assignment (expression has type "float", variable has type "List[float]") scoring_algorithm.py:60: error: Unsupported operand types for - ("List[float]" and "float") scoring_algorithm.py:65: error: Incompatible types in assignment (expression has type "float", variable has type "List[float]") scoring_algorithm.py:67: error: Unsupported operand types for - ("List[float]" and "float") Found 4 errors in 1 file (checked 1 source file) ``` * scoring_algorithm: uses enumeration instead of manual indexing on loop var * scoring_algorithm: sometimes we look before we leap. * clean-up: runs `black` to fix formatting --- other/scoring_algorithm.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py index 77e614e2622c..cc1744012671 100644 --- a/other/scoring_algorithm.py +++ b/other/scoring_algorithm.py @@ -20,39 +20,38 @@ 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: +def procentual_proximity( + source_data: list[list[float]], weights: list[int] +) -> list[list[float]]: """ 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 + + >>> 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]] """ # 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: list[list[float]] = [] + for data in source_data: + for i, el in enumerate(data): + if len(data_lists) < i + 1: data_lists.append([]) - data_lists[i].append(float(item[i])) + data_lists[i].append(float(el)) - score_lists = [] + score_lists: list[list[float]] = [] # calculating each score for dlist, weight in zip(data_lists, weights): mind = min(dlist) maxd = max(dlist) - score = [] + score: list[float] = [] # for weight 0 score is 1 - actual score if weight == 0: for item in dlist: @@ -75,7 +74,7 @@ def procentual_proximity(source_data: list, weights: list) -> list: score_lists.append(score) # initialize final scores - final_scores = [0 for i in range(len(score_lists[0]))] + final_scores: list[float] = [0 for i in range(len(score_lists[0]))] # generate final scores for i, slist in enumerate(score_lists): From a281151a2c0140dda718186cf82329168e65cf96 Mon Sep 17 00:00:00 2001 From: Andrew Grangaard Date: Fri, 29 Oct 2021 00:22:57 -0700 Subject: [PATCH 1684/2908] Delete other/date_to_weekday.py as a how-to-use, not an algorithm (#5591) * [mypy] Fixes typing errors in other/date_to_weekday * [mypy] uses future annotation style for other/date_to_weekly * date_to_weekday: new implementation replaces buggy original * date_to_weekday: add examples from multiple of 100 years * clean-up: runs `black` to fix formatting * Delete date_to_weekday.py Co-authored-by: Christian Clauss --- other/date_to_weekday.py | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 other/date_to_weekday.py diff --git a/other/date_to_weekday.py b/other/date_to_weekday.py deleted file mode 100644 index 9dc68666e3b4..000000000000 --- a/other/date_to_weekday.py +++ /dev/null @@ -1,27 +0,0 @@ -from calendar import day_name -from datetime import datetime - - -def date_to_weekday(inp_date: str) -> str: - """ - It returns the day name of the given date string. - :param inp_date: - :return: String - >>> date_to_weekday("7/8/2035") - 'Tuesday' - >>> date_to_weekday("7/8/2021") - 'Saturday' - >>> date_to_weekday("1/1/2021") - 'Friday' - """ - day, month, year = (int(x) for x in inp_date.split("/")) - if year % 100 == 0: - year = "00" - new_base_date: str = f"{day}/{month}/{year%100} 0:0:0" - date_time_obj: datetime.date = datetime.strptime(new_base_date, "%d/%m/%y %H:%M:%S") - out_put_day: int = date_time_obj.weekday() - return day_name[out_put_day] - - -if __name__ == "__main__": - print(date_to_weekday("1/1/2021"), end=" ") From 3a4cc7e31084e15cf2cce24038957c686d41a1b3 Mon Sep 17 00:00:00 2001 From: Shriyans Gandhi <41372639+shri30yans@users.noreply.github.com> Date: Fri, 29 Oct 2021 13:09:32 +0530 Subject: [PATCH 1685/2908] Hexagonal number sequence (#5640) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Hexagonal number sequence A hexagonal number sequence is a sequence of figurate numbers where the nth hexagonal number hₙ is the number of distinct dots in a pattern of dots consisting of the outlines of regular hexagons with sides up to n dots, when the hexagons are overlaid so that they share one vertex. This program returns the hexagonal number sequence of n length. * Update hexagonalnumbers.py * Update hexagonalnumbers.py * Update hexagonalnumbers.py * Update hexagonalnumbers.py * Update and rename hexagonalnumbers.py to hexagonal_numbers.py * Length must be a positive integer Co-authored-by: Christian Clauss --- maths/series/hexagonal_numbers.py | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 maths/series/hexagonal_numbers.py diff --git a/maths/series/hexagonal_numbers.py b/maths/series/hexagonal_numbers.py new file mode 100644 index 000000000000..582b1989b7c6 --- /dev/null +++ b/maths/series/hexagonal_numbers.py @@ -0,0 +1,42 @@ +""" +A hexagonal number sequence is a sequence of figurate numbers +where the nth hexagonal number hₙ is the number of distinct dots +in a pattern of dots consisting of the outlines of regular +hexagons with sides up to n dots, when the hexagons are overlaid +so that they share one vertex. + + Calculates the hexagonal numbers sequence with a formula + hₙ = n(2n-1) + where: + hₙ --> is nth element of the sequence + n --> is the number of element in the sequence + reference-->"Hexagonal number" Wikipedia + +""" + + +def hexagonal_numbers(length: int) -> list[int]: + """ + :param len: max number of elements + :type len: int + :return: Hexagonal numbers as a list + + Tests: + >>> hexagonal_numbers(10) + [0, 1, 6, 15, 28, 45, 66, 91, 120, 153] + >>> hexagonal_numbers(5) + [0, 1, 6, 15, 28] + >>> hexagonal_numbers(0) + Traceback (most recent call last): + ... + ValueError: Length must be a positive integer. + """ + + if length <= 0 or not isinstance(length, int): + raise ValueError("Length must be a positive integer.") + return [n * (2 * n - 1) for n in range(length)] + + +if __name__ == "__main__": + print(hexagonal_numbers(length=5)) + print(hexagonal_numbers(length=10)) From e6cf13cc03475b3a5e7e3d3bf4723c37c3063dde Mon Sep 17 00:00:00 2001 From: Casper Rysgaard Date: Sat, 30 Oct 2021 13:06:25 +0200 Subject: [PATCH 1686/2908] Update queue implementation (#5388) * Update queue implementation Popping the first element of a list takes O(n) time. Using a cyclic queue takes O(1) time. * Add queue changes from extra files * Update indentation * Add empty line between imports * Fix lines * Apply suggestions from code review Co-authored-by: John Law Co-authored-by: John Law --- graphs/breadth_first_search.py | 12 +++++++----- graphs/breadth_first_search_2.py | 11 +++++++---- graphs/check_bipartite_graph_bfs.py | 13 ++++++++----- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index 7c626429e5c0..9264f57b41b2 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -3,6 +3,8 @@ """ Author: OMKAR PATHAK """ from __future__ import annotations +from queue import Queue + class Graph: def __init__(self) -> None: @@ -51,19 +53,19 @@ def bfs(self, start_vertex: int) -> set[int]: visited = set() # create a first in first out queue to store all the vertices for BFS - queue = [] + queue = Queue() # mark the source node as visited and enqueue it visited.add(start_vertex) - queue.append(start_vertex) + queue.put(start_vertex) - while queue: - vertex = queue.pop(0) + while not queue.empty(): + vertex = queue.get() # 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) + queue.put(adjacent_vertex) visited.add(adjacent_vertex) return visited diff --git a/graphs/breadth_first_search_2.py b/graphs/breadth_first_search_2.py index a90e963a4043..4c8b69faf656 100644 --- a/graphs/breadth_first_search_2.py +++ b/graphs/breadth_first_search_2.py @@ -14,6 +14,8 @@ """ from __future__ import annotations +from queue import Queue + G = { "A": ["B", "C"], "B": ["A", "D", "E"], @@ -30,13 +32,14 @@ def breadth_first_search(graph: dict, start: str) -> set[str]: 'ABCDEF' """ explored = {start} - queue = [start] - while queue: - v = queue.pop(0) # queue.popleft() + queue = Queue() + queue.put(start) + while not queue.empty(): + v = queue.get() for w in graph[v]: if w not in explored: explored.add(w) - queue.append(w) + queue.put(w) return explored diff --git a/graphs/check_bipartite_graph_bfs.py b/graphs/check_bipartite_graph_bfs.py index 00b771649b5d..b5203b4c5c7d 100644 --- a/graphs/check_bipartite_graph_bfs.py +++ b/graphs/check_bipartite_graph_bfs.py @@ -6,14 +6,17 @@ # 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. +from queue import Queue + + def checkBipartite(graph): - queue = [] + queue = Queue() visited = [False] * len(graph) color = [-1] * len(graph) def bfs(): - while queue: - u = queue.pop(0) + while not queue.empty(): + u = queue.get() visited[u] = True for neighbour in graph[u]: @@ -23,7 +26,7 @@ def bfs(): if color[neighbour] == -1: color[neighbour] = 1 - color[u] - queue.append(neighbour) + queue.put(neighbour) elif color[neighbour] == color[u]: return False @@ -32,7 +35,7 @@ def bfs(): for i in range(len(graph)): if not visited[i]: - queue.append(i) + queue.put(i) color[i] = 0 if bfs() is False: return False From e7565f8bfc276e0d58d609f6c39f39b80b92a4a2 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 30 Oct 2021 22:36:12 +0300 Subject: [PATCH 1687/2908] Improve Project Euler problem 070 solution 1 (#5166) * Change has_same_digits doctest * Improve has_same_digits function --- project_euler/problem_070/sol1.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/project_euler/problem_070/sol1.py b/project_euler/problem_070/sol1.py index e106800d5716..d42b017cc476 100644 --- a/project_euler/problem_070/sol1.py +++ b/project_euler/problem_070/sol1.py @@ -60,34 +60,16 @@ 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) + >>> has_same_digits(123, 23) 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 + return sorted(str(num1)) == sorted(str(num2)) def solution(max: int = 10000000) -> int: From 678535b5c83302ee25b8a135b977c7e48b8f8668 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Sat, 30 Oct 2021 16:43:48 -0300 Subject: [PATCH 1688/2908] [mypy] Fix type annotations in non_recursive_segment_tree (#5652) --- .../binary_tree/non_recursive_segment_tree.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index c914079e0a8d..b04a6e5cacb7 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -37,12 +37,12 @@ """ from __future__ import annotations -from typing import Callable, TypeVar +from typing import Any, Callable, Generic, TypeVar T = TypeVar("T") -class SegmentTree: +class SegmentTree(Generic[T]): def __init__(self, arr: list[T], fnc: Callable[[T, T], T]) -> None: """ Segment Tree constructor, it works just with commutative combiner. @@ -55,8 +55,10 @@ def __init__(self, arr: list[T], fnc: Callable[[T, T], T]) -> None: ... 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 + any_type: Any | T = None + + self.N: int = len(arr) + self.st: list[T] = [any_type for _ in range(self.N)] + arr self.fn = fnc self.build() @@ -83,7 +85,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: # noqa: E741 + def query(self, l: int, r: int) -> T | None: # noqa: E741 """ Get range query value in log(N) time :param l: left element index @@ -101,7 +103,8 @@ def query(self, l: int, r: int) -> T: # noqa: E741 7 """ l, r = l + self.N, r + self.N # noqa: E741 - res = None + + res: T | None = None while l <= r: # noqa: E741 if l % 2 == 1: res = self.st[l] if res is None else self.fn(res, self.st[l]) @@ -135,7 +138,7 @@ def query(self, l: int, r: int) -> T: # noqa: E741 max_segment_tree = SegmentTree(test_array, max) sum_segment_tree = SegmentTree(test_array, lambda a, b: a + b) - def test_all_segments(): + def test_all_segments() -> None: """ Test all possible segments """ From 359e0e795e7e0efa4212a3c94fb482e128bc63eb Mon Sep 17 00:00:00 2001 From: Mitheel <81575947+mitheelgajare@users.noreply.github.com> Date: Sun, 31 Oct 2021 01:18:50 +0530 Subject: [PATCH 1689/2908] Fixed grammatical errors in CONTRIBUTING.md (#5635) --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f5c123674f4a..4df60ed3f296 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,7 +67,7 @@ pre-commit run --all-files --show-diff-on-failure We want your work to be readable by others; therefore, we encourage you to note the following: - Please write in Python 3.9+. 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. +- Please focus hard on the 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. @@ -102,7 +102,7 @@ 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 a good example: + We encourage you to put docstrings inside your functions but please pay attention to the indentation of docstrings. The following is a good example: ```python def sum_ab(a, b): @@ -160,7 +160,7 @@ We want your work to be readable by others; therefore, we encourage you to note - [__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. +- 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. @@ -172,7 +172,7 @@ We want your work to be readable by others; therefore, we encourage you to note - 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. +- All submissions will be tested with [__mypy__](http://www.mypy-lang.org) so we encourage you 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.__ From 21c99d2ae294d08893bd0160cd9aacc2ad3ca556 Mon Sep 17 00:00:00 2001 From: Navpreet Singh Devpuri Date: Sun, 31 Oct 2021 01:34:46 +0530 Subject: [PATCH 1690/2908] added is_contains_unique_chars() (#5701) * added is_contains_unique_chars() * added is_contains_unique_chars() * added stackoverflow reference --- strings/is_contains_unique_chars.py | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 strings/is_contains_unique_chars.py diff --git a/strings/is_contains_unique_chars.py b/strings/is_contains_unique_chars.py new file mode 100644 index 000000000000..fdf7a02ff43f --- /dev/null +++ b/strings/is_contains_unique_chars.py @@ -0,0 +1,31 @@ +def is_contains_unique_chars(input_str: str) -> bool: + """ + Check if all characters in the string is unique or not. + >>> is_contains_unique_chars("I_love.py") + True + >>> is_contains_unique_chars("I don't love Python") + False + + Time complexity: O(n) + Space compexity: O(1) 19320 bytes as we are having 144697 characters in unicode + """ + + # Each bit will represent each unicode character + # For example 65th bit representing 'A' + # https://stackoverflow.com/a/12811293 + bitmap = 0 + for ch in input_str: + ch_unicode = ord(ch) + ch_bit_index_on = pow(2, ch_unicode) + + # If we already turned on bit for current character's unicode + if bitmap >> ch_unicode & 1 == 1: + return False + bitmap |= ch_bit_index_on + return True + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 99983c91ca26e8cf592699dd320e0d58140cfe41 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Sun, 31 Oct 2021 05:38:24 -0300 Subject: [PATCH 1691/2908] [mypy] Add/fix type annotations in `data_structures/heap/skew_heap.py` (#5634) * Add abstract base class Comparable * [mypy] Fix type annotations (strict mode) * Fix a typo * Remove Comparable class and set bound to bool --- data_structures/heap/skew_heap.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index b59441389a91..16ddc5545e36 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -2,9 +2,9 @@ from __future__ import annotations -from typing import Generic, Iterable, Iterator, TypeVar +from typing import Any, Generic, Iterable, Iterator, TypeVar -T = TypeVar("T") +T = TypeVar("T", bound=bool) class SkewNode(Generic[T]): @@ -51,7 +51,7 @@ class SkewHeap(Generic[T]): 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 + Visualization: https://www.cs.usfca.edu/~galles/visualization/SkewHeap.html >>> list(SkewHeap([2, 3, 1, 5, 1, 7])) [1, 1, 2, 3, 5, 7] @@ -70,14 +70,16 @@ class SkewHeap(Generic[T]): """ def __init__(self, data: Iterable[T] | None = ()) -> None: + """ >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] """ self._root: SkewNode[T] | None = None - for item in data: - self.insert(item) + if data: + for item in data: + self.insert(item) def __bool__(self) -> bool: """ @@ -103,7 +105,7 @@ def __iter__(self) -> Iterator[T]: >>> list(sh) [1, 3, 3, 7] """ - result = [] + result: list[Any] = [] while self: result.append(self.pop()) @@ -127,7 +129,7 @@ def insert(self, value: T) -> None: """ self._root = SkewNode.merge(self._root, SkewNode(value)) - def pop(self) -> T: + def pop(self) -> T | None: """ Pop the smallest value from the heap and return it. @@ -146,7 +148,9 @@ def pop(self) -> T: IndexError: Can't get top element for the empty heap. """ result = self.top() - self._root = SkewNode.merge(self._root.left, self._root.right) + self._root = ( + SkewNode.merge(self._root.left, self._root.right) if self._root else None + ) return result @@ -172,7 +176,7 @@ def top(self) -> T: raise IndexError("Can't get top element for the empty heap.") return self._root.value - def clear(self): + def clear(self) -> None: """ Clear the heap. From a94c6214ff33def88d9363de935263e3145fc96a Mon Sep 17 00:00:00 2001 From: "@im_8055" <38890773+Bhargavishnu@users.noreply.github.com> Date: Sun, 31 Oct 2021 16:06:03 +0530 Subject: [PATCH 1692/2908] Fix spellings (#5710) --- strings/credit_card_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/strings/credit_card_validator.py b/strings/credit_card_validator.py index 3a08c4117a6b..78bf45740a63 100644 --- a/strings/credit_card_validator.py +++ b/strings/credit_card_validator.py @@ -71,7 +71,7 @@ def validate_credit_card_number(credit_card_number: str) -> bool: 36111111111111 is an invalid credit card number because of its first two digits. False >>> validate_credit_card_number('41111111111111') - 41111111111111 is an invalid credit card number because it fails the Lhun check. + 41111111111111 is an invalid credit card number because it fails the Luhn check. False """ error_message = f"{credit_card_number} is an invalid credit card number because" @@ -88,7 +88,7 @@ def validate_credit_card_number(credit_card_number: str) -> bool: return False if not luhn_validation(credit_card_number): - print(f"{error_message} it fails the Lhun check.") + print(f"{error_message} it fails the Luhn check.") return False print(f"{credit_card_number} is a valid credit card number.") From 965b1ff7dfac4806d61e39dfbbdfb6c5c165c0a7 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 31 Oct 2021 13:36:53 +0300 Subject: [PATCH 1693/2908] Improve Project Euler problem 078 solution 1 (#5708) * Add solution doctests * Improve solution function --- project_euler/problem_078/sol1.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/project_euler/problem_078/sol1.py b/project_euler/problem_078/sol1.py index f92cf0f4020c..7e5938c4c466 100644 --- a/project_euler/problem_078/sol1.py +++ b/project_euler/problem_078/sol1.py @@ -21,6 +21,12 @@ def solution(number: int = 1000000) -> int: """ + >>> solution(1) + 1 + + >>> solution(9) + 14 + >>> solution() 55374 """ @@ -34,6 +40,7 @@ def solution(number: int = 1000000) -> int: if index > i: break item += partitions[i - index] * sign + item %= number index += j if index > i: break From 568425dfd14f687aaa11c6405e4acf2556ce74f2 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 31 Oct 2021 13:37:46 +0300 Subject: [PATCH 1694/2908] Improve solution (#5705) --- project_euler/problem_072/sol1.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/project_euler/problem_072/sol1.py b/project_euler/problem_072/sol1.py index 846396ab0f9c..a2a0eeeb31c5 100644 --- a/project_euler/problem_072/sol1.py +++ b/project_euler/problem_072/sol1.py @@ -18,7 +18,7 @@ function, phi(n). So, the answer is simply the sum of phi(n) for 2 <= n <= 1,000,000 Sum of phi(d), for all d|n = n. This result can be used to find phi(n) using a sieve. -Time: 3.5 sec +Time: 1 sec """ @@ -36,8 +36,9 @@ def solution(limit: int = 1_000_000) -> int: 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] + if phi[i] == i - 1: + for j in range(2 * i, limit + 1, i): + phi[j] -= phi[j] // i return sum(phi[2 : limit + 1]) From f92eac982dee9d4ea97e36cfda0f1fa19213b9f4 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 31 Oct 2021 13:38:28 +0300 Subject: [PATCH 1695/2908] Improve Project Euler problem 092 solution 1 (#5703) * Fix typos * Improve solution --- project_euler/problem_092/sol1.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_092/sol1.py b/project_euler/problem_092/sol1.py index dcda3a48679e..437a85badc57 100644 --- a/project_euler/problem_092/sol1.py +++ b/project_euler/problem_092/sol1.py @@ -12,11 +12,14 @@ """ +DIGITS_SQUARED = [digit ** 2 for digit in range(10)] + + def next_number(number: int) -> int: """ Returns the next number of the chain by adding the square of each digit - to form a neww number. - For example if number = 12, next_number() will return 1^2 + 2^2 = 5. + to form a new number. + For example, if number = 12, next_number() will return 1^2 + 2^2 = 5. Therefore, 5 is the next number of the chain. >>> next_number(44) 32 @@ -27,12 +30,15 @@ def next_number(number: int) -> int: """ sum_of_digits_squared = 0 while number: - sum_of_digits_squared += (number % 10) ** 2 + sum_of_digits_squared += DIGITS_SQUARED[number % 10] number //= 10 return sum_of_digits_squared +CHAINS = {1: True, 58: False} + + def chain(number: int) -> bool: """ The function generates the chain of numbers until the next number is 1 or 89. @@ -40,7 +46,7 @@ def chain(number: int) -> bool: following chain of numbers: 44 → 32 → 13 → 10 → 1 → 1. Once the next number generated is 1 or 89, the function returns whether - or not the the next number generated by next_number() is 1. + or not the next number generated by next_number() is 1. >>> chain(10) True >>> chain(58) @@ -48,10 +54,13 @@ def chain(number: int) -> bool: >>> chain(1) True """ - while number != 1 and number != 89: - number = next_number(number) + if number in CHAINS: + return CHAINS[number] + + number_chain = chain(next_number(number)) + CHAINS[number] = number_chain - return number == 1 + return number_chain def solution(number: int = 10000000) -> int: From 13fdf21c9c74d9a1d0cc573bb35711b790dc8010 Mon Sep 17 00:00:00 2001 From: SURYAPRATAP SINGH SURYAVANSHI <67123991+suryapratapsinghsuryavanshi@users.noreply.github.com> Date: Sun, 31 Oct 2021 16:10:32 +0530 Subject: [PATCH 1696/2908] Added alternative_list_arrange method (#4631) --- other/alternative_list_arrange.py | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 other/alternative_list_arrange.py diff --git a/other/alternative_list_arrange.py b/other/alternative_list_arrange.py new file mode 100644 index 000000000000..84c5dd4293ad --- /dev/null +++ b/other/alternative_list_arrange.py @@ -0,0 +1,34 @@ +def alternative_list_arrange(first_input_list: list, second_input_list: list) -> list: + """ + The method arranges two lists as one list in alternative forms of the list elements. + :param first_input_list: + :param second_input_list: + :return: List + >>> alternative_list_arrange([1, 2, 3, 4, 5], ["A", "B", "C"]) + [1, 'A', 2, 'B', 3, 'C', 4, 5] + >>> alternative_list_arrange(["A", "B", "C"], [1, 2, 3, 4, 5]) + ['A', 1, 'B', 2, 'C', 3, 4, 5] + >>> alternative_list_arrange(["X", "Y", "Z"], [9, 8, 7, 6]) + ['X', 9, 'Y', 8, 'Z', 7, 6] + >>> alternative_list_arrange([1, 2, 3, 4, 5], []) + [1, 2, 3, 4, 5] + """ + first_input_list_length: int = len(first_input_list) + second_input_list_length: int = len(second_input_list) + abs_length: int = ( + first_input_list_length + if first_input_list_length > second_input_list_length + else second_input_list_length + ) + output_result_list: list = [] + for char_count in range(abs_length): + if char_count < first_input_list_length: + output_result_list.append(first_input_list[char_count]) + if char_count < second_input_list_length: + output_result_list.append(second_input_list[char_count]) + + return output_result_list + + +if __name__ == "__main__": + print(alternative_list_arrange(["A", "B", "C"], [1, 2, 3, 4, 5]), end=" ") From 9ac94c09ebc4d25caf3527350ff61d3ba93431e2 Mon Sep 17 00:00:00 2001 From: Morteza Date: Sun, 31 Oct 2021 03:41:39 -0700 Subject: [PATCH 1697/2908] Improve checking anagrams in O(n) with dictionary (#4806) --- strings/check_anagrams.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index 62a4441a0c00..938bf4c2abee 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -1,6 +1,7 @@ """ wiki: https://en.wikipedia.org/wiki/Anagram """ +from collections import defaultdict def check_anagrams(first_str: str, second_str: str) -> bool: @@ -16,10 +17,30 @@ def check_anagrams(first_str: str, second_str: str) -> bool: >>> check_anagrams('There', 'Their') False """ - return ( - "".join(sorted(first_str.lower())).strip() - == "".join(sorted(second_str.lower())).strip() - ) + first_str = first_str.lower().strip() + second_str = second_str.lower().strip() + + # Remove whitespace + first_str = first_str.replace(" ", "") + second_str = second_str.replace(" ", "") + + # Strings of different lengths are not anagrams + if len(first_str) != len(second_str): + return False + + # Default values for count should be 0 + count = defaultdict(int) + + # For each character in input strings, + # increment count in the corresponding + for i in range(len(first_str)): + count[first_str[i]] += 1 + count[second_str[i]] -= 1 + + for _count in count.values(): + if _count != 0: + return False + return True if __name__ == "__main__": From f4fd147d0306e5fe3dfdda8ef01ec9068c1c247a Mon Sep 17 00:00:00 2001 From: happiestbee <87628038+happiestbee@users.noreply.github.com> Date: Sun, 31 Oct 2021 06:46:31 -0400 Subject: [PATCH 1698/2908] Make decrypt_caesar_with_chi_squared work with upper case letters (#5379) * Fixes: #5323 * Fixes: #5323 --- ciphers/decrypt_caesar_with_chi_squared.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index beac851b6c2a..6c36860207cd 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -6,7 +6,7 @@ def decrypt_caesar_with_chi_squared( ciphertext: str, cipher_alphabet: list[str] | None = None, frequencies_dict: dict[str, float] | None = None, - case_sensetive: bool = False, + case_sensitive: bool = False, ) -> tuple[int, float, str]: """ Basic Usage @@ -20,7 +20,7 @@ def decrypt_caesar_with_chi_squared( * 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 + * case_sensitive (bool): a boolean value: True if the case matters during decryption, False if it doesn't Returns: @@ -117,6 +117,9 @@ def decrypt_caesar_with_chi_squared( >>> decrypt_caesar_with_chi_squared('crybd cdbsxq') (10, 233.35343938980898, 'short string') + >>> decrypt_caesar_with_chi_squared('Crybd Cdbsxq', case_sensitive=True) + (10, 233.35343938980898, 'Short String') + >>> decrypt_caesar_with_chi_squared(12) Traceback (most recent call last): AttributeError: 'int' object has no attribute 'lower' @@ -158,7 +161,7 @@ def decrypt_caesar_with_chi_squared( # Custom frequencies dictionary frequencies = frequencies_dict - if not case_sensetive: + if not case_sensitive: ciphertext = ciphertext.lower() # Chi squared statistic values @@ -172,10 +175,14 @@ def decrypt_caesar_with_chi_squared( for letter in ciphertext: try: # Try to index the letter in the alphabet - new_key = (alphabet_letters.index(letter) - shift) % len( + new_key = (alphabet_letters.index(letter.lower()) - shift) % len( alphabet_letters ) - decrypted_with_shift += alphabet_letters[new_key] + decrypted_with_shift += ( + alphabet_letters[new_key].upper() + if case_sensitive and letter.isupper() + else alphabet_letters[new_key] + ) except ValueError: # Append the character if it isn't in the alphabet decrypted_with_shift += letter @@ -184,10 +191,11 @@ def decrypt_caesar_with_chi_squared( # Loop through each letter in the decoded message with the shift for letter in decrypted_with_shift: - if case_sensetive: + if case_sensitive: + letter = letter.lower() if letter in frequencies: # Get the amount of times the letter occurs in the message - occurrences = decrypted_with_shift.count(letter) + occurrences = decrypted_with_shift.lower().count(letter) # Get the excepcted amount of times the letter should appear based # on letter frequencies From 0f015fa034646fcacd812b429e47c684b44e5bd3 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 31 Oct 2021 11:48:10 +0100 Subject: [PATCH 1699/2908] Added solution for euler problem 493 (#5573) * Added solution for problem 493 * fixed typo * return result as string --- project_euler/problem_493/__init__.py | 0 project_euler/problem_493/sol1.py | 53 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 project_euler/problem_493/__init__.py create mode 100644 project_euler/problem_493/sol1.py diff --git a/project_euler/problem_493/__init__.py b/project_euler/problem_493/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_493/sol1.py b/project_euler/problem_493/sol1.py new file mode 100644 index 000000000000..c9879a528230 --- /dev/null +++ b/project_euler/problem_493/sol1.py @@ -0,0 +1,53 @@ +""" +Project Euler Problem 493: https://projecteuler.net/problem=493 + +70 coloured balls are placed in an urn, 10 for each of the seven rainbow colours. +What is the expected number of distinct colours in 20 randomly picked balls? +Give your answer with nine digits after the decimal point (a.bcdefghij). + +----- + +This combinatorial problem can be solved by decomposing the problem into the +following steps: +1. Calculate the total number of possible picking cominations +[combinations := binom_coeff(70, 20)] +2. Calculate the number of combinations with one colour missing +[missing := binom_coeff(60, 20)] +3. Calculate the probability of one colour missing +[missing_prob := missing / combinations] +4. Calculate the probability of no colour missing +[no_missing_prob := 1 - missing_prob] +5. Calculate the expected number of distinct colours +[expected = 7 * no_missing_prob] + +References: +- https://en.wikipedia.org/wiki/Binomial_coefficient +""" + +import math + +BALLS_PER_COLOUR = 10 +NUM_COLOURS = 7 +NUM_BALLS = BALLS_PER_COLOUR * NUM_COLOURS + + +def solution(num_picks: int = 20) -> str: + """ + Calculates the expected number of distinct colours + + >>> solution(10) + '5.669644129' + + >>> solution(30) + '6.985042712' + """ + total = math.comb(NUM_BALLS, num_picks) + missing_colour = math.comb(NUM_BALLS - BALLS_PER_COLOUR, num_picks) + + result = NUM_COLOURS * (1 - missing_colour / total) + + return f"{result:.9f}" + + +if __name__ == "__main__": + print(solution(20)) From 7488c5070e3f5a29a08f584644666494c420f834 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Sun, 31 Oct 2021 07:49:34 -0300 Subject: [PATCH 1700/2908] Fix type annotations in randomized_heap.py (#5704) --- data_structures/heap/randomized_heap.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/data_structures/heap/randomized_heap.py b/data_structures/heap/randomized_heap.py index f584f5cb3342..bab4ec1b34c6 100644 --- a/data_structures/heap/randomized_heap.py +++ b/data_structures/heap/randomized_heap.py @@ -3,9 +3,9 @@ from __future__ import annotations import random -from typing import Generic, Iterable, TypeVar +from typing import Any, Generic, Iterable, TypeVar -T = TypeVar("T") +T = TypeVar("T", bound=bool) class RandomizedHeapNode(Generic[T]): @@ -76,8 +76,10 @@ def __init__(self, data: Iterable[T] | None = ()) -> None: [1, 3, 3, 7] """ self._root: RandomizedHeapNode[T] | None = None - for item in data: - self.insert(item) + + if data: + for item in data: + self.insert(item) def insert(self, value: T) -> None: """ @@ -93,7 +95,7 @@ def insert(self, value: T) -> None: """ self._root = RandomizedHeapNode.merge(self._root, RandomizedHeapNode(value)) - def pop(self) -> T: + def pop(self) -> T | None: """ Pop the smallest value from the heap and return it. @@ -111,7 +113,12 @@ def pop(self) -> T: ... IndexError: Can't get top element for the empty heap. """ + result = self.top() + + if self._root is None: + return None + self._root = RandomizedHeapNode.merge(self._root.left, self._root.right) return result @@ -138,7 +145,7 @@ def top(self) -> T: raise IndexError("Can't get top element for the empty heap.") return self._root.value - def clear(self): + def clear(self) -> None: """ Clear the heap. @@ -151,7 +158,7 @@ def clear(self): """ self._root = None - def to_sorted_list(self) -> list[T]: + def to_sorted_list(self) -> list[Any]: """ Returns sorted list containing all the values in the heap. From 508589e3fc3fa93312b131c30c77ecd61460a430 Mon Sep 17 00:00:00 2001 From: Venkatesh Tantravahi <64308188+venkateshtantravahi@users.noreply.github.com> Date: Sun, 31 Oct 2021 16:57:50 +0530 Subject: [PATCH 1701/2908] Local Weighted Learning (#5615) * Local Weighted Learning Added * Delete LWL directory * Local Weighted Learning Added * local weighted learning added * Delete LWL directory * Delete local_weighted_learning.py * rephrased code added * local weight learning updated * local weight learning updated * Updated dir * updated codespell * import modification * Doctests added * doctests updated * lcl updated * doctests updated * doctest values updated --- .../local_weighted_learning/__init__.py | 0 .../local_weighted_learning.md | 66 +++++++++ .../local_weighted_learning.py | 135 ++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 machine_learning/local_weighted_learning/__init__.py create mode 100644 machine_learning/local_weighted_learning/local_weighted_learning.md create mode 100644 machine_learning/local_weighted_learning/local_weighted_learning.py diff --git a/machine_learning/local_weighted_learning/__init__.py b/machine_learning/local_weighted_learning/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.md b/machine_learning/local_weighted_learning/local_weighted_learning.md new file mode 100644 index 000000000000..5c7895e75104 --- /dev/null +++ b/machine_learning/local_weighted_learning/local_weighted_learning.md @@ -0,0 +1,66 @@ +# Locally Weighted Linear Regression +It is a non-parametric ML algorithm that does not learn on a fixed set of parameters such as **linear regression**. \ +So, here comes a question of what is *linear regression*? \ +**Linear regression** is a supervised learning algorithm used for computing linear relationships between input (X) and output (Y). \ + +### Terminology Involved + +number_of_features(i) = Number of features involved. \ +number_of_training_examples(m) = Number of training examples. \ +output_sequence(y) = Output Sequence. \ +$\theta$ $^T$ x = predicted point. \ +J($\theta$) = COst function of point. + +The steps involved in ordinary linear regression are: + +Training phase: Compute \theta to minimize the cost. \ +J($\theta$) = $\sum_{i=1}^m$ (($\theta$)$^T$ $x^i$ - $y^i$)$^2$ + +Predict output: for given query point x, \ + return: ($\theta$)$^T$ x + +Linear Regression + +This training phase is possible when data points are linear, but there again comes a question can we predict non-linear relationship between x and y ? as shown below + +Non-linear Data +
    +
    +So, here comes the role of non-parametric algorithm which doesn't compute predictions based on fixed set of params. Rather parameters $\theta$ are computed individually for each query point/data point x. +
    +
    +While Computing $\theta$ , a higher "preferance" is given to points in the vicinity of x than points farther from x. + +Cost Function J($\theta$) = $\sum_{i=1}^m$ $w^i$ (($\theta$)$^T$ $x^i$ - $y^i$)$^2$ + +$w^i$ is non-negative weight associated to training point $x^i$. \ +$w^i$ is large fr $x^i$'s lying closer to query point $x_i$. \ +$w^i$ is small for $x^i$'s lying farther to query point $x_i$. + +A Typical weight can be computed using \ + +$w^i$ = $\exp$(-$\frac{(x^i-x)(x^i-x)^T}{2\tau^2}$) + +Where $\tau$ is the bandwidth parameter that controls $w^i$ distance from x. + +Let's look at a example : + +Suppose, we had a query point x=5.0 and training points $x^1$=4.9 and $x^2$=5.0 than we can calculate weights as : + +$w^i$ = $\exp$(-$\frac{(x^i-x)(x^i-x)^T}{2\tau^2}$) with $\tau$=0.5 + +$w^1$ = $\exp$(-$\frac{(4.9-5)^2}{2(0.5)^2}$) = 0.9802 + +$w^2$ = $\exp$(-$\frac{(3-5)^2}{2(0.5)^2}$) = 0.000335 + +So, J($\theta$) = 0.9802*($\theta$ $^T$ $x^1$ - $y^1$) + 0.000335*($\theta$ $^T$ $x^2$ - $y^2$) + +So, here by we can conclude that the weight fall exponentially as the distance between x & $x^i$ increases and So, does the contribution of error in prediction for $x^i$ to the cost. + +Steps involved in LWL are : \ +Compute \theta to minimize the cost. +J($\theta$) = $\sum_{i=1}^m$ $w^i$ (($\theta$)$^T$ $x^i$ - $y^i$)$^2$ \ +Predict Output: for given query point x, \ +return : $\theta$ $^T$ x + +LWL diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py new file mode 100644 index 000000000000..af8694bf8f82 --- /dev/null +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -0,0 +1,135 @@ +# Required imports to run this file +import matplotlib.pyplot as plt +import numpy as np + + +# weighted matrix +def weighted_matrix(point: np.mat, training_data_x: np.mat, bandwidth: float) -> np.mat: + """ + Calculate the weight for every point in the + data set. It takes training_point , query_point, and tau + Here Tau is not a fixed value it can be varied depends on output. + tau --> bandwidth + xmat -->Training data + point --> the x where we want to make predictions + >>> weighted_matrix(np.array([1., 1.]),np.mat([[16.99, 10.34], [21.01,23.68], + ... [24.59,25.69]]), 0.6) + matrix([[1.43807972e-207, 0.00000000e+000, 0.00000000e+000], + [0.00000000e+000, 0.00000000e+000, 0.00000000e+000], + [0.00000000e+000, 0.00000000e+000, 0.00000000e+000]]) + """ + # m is the number of training samples + m, n = np.shape(training_data_x) + # Initializing weights as identity matrix + weights = np.mat(np.eye(m)) + # calculating weights for all training examples [x(i)'s] + for j in range(m): + diff = point - training_data_x[j] + weights[j, j] = np.exp(diff * diff.T / (-2.0 * bandwidth ** 2)) + return weights + + +def local_weight( + point: np.mat, training_data_x: np.mat, training_data_y: np.mat, bandwidth: float +) -> np.mat: + """ + Calculate the local weights using the weight_matrix function on training data. + Return the weighted matrix. + >>> local_weight(np.array([1., 1.]),np.mat([[16.99, 10.34], [21.01,23.68], + ... [24.59,25.69]]),np.mat([[1.01, 1.66, 3.5]]), 0.6) + matrix([[0.00873174], + [0.08272556]]) + """ + weight = weighted_matrix(point, training_data_x, bandwidth) + W = (training_data_x.T * (weight * training_data_x)).I * ( + training_data_x.T * weight * training_data_y.T + ) + + return W + + +def local_weight_regression( + training_data_x: np.mat, training_data_y: np.mat, bandwidth: float +) -> np.mat: + """ + Calculate predictions for each data point on axis. + >>> local_weight_regression(np.mat([[16.99, 10.34], [21.01,23.68], + ... [24.59,25.69]]),np.mat([[1.01, 1.66, 3.5]]), 0.6) + array([1.07173261, 1.65970737, 3.50160179]) + """ + m, n = np.shape(training_data_x) + ypred = np.zeros(m) + + for i, item in enumerate(training_data_x): + ypred[i] = item * local_weight( + item, training_data_x, training_data_y, bandwidth + ) + + return ypred + + +def load_data(dataset_name: str, cola_name: str, colb_name: str) -> np.mat: + """ + Function used for loading data from the seaborn splitting into x and y points + >>> pass # this function has no doctest + """ + import seaborn as sns + + data = sns.load_dataset(dataset_name) + col_a = np.array(data[cola_name]) # total_bill + col_b = np.array(data[colb_name]) # tip + + mcol_a = np.mat(col_a) + mcol_b = np.mat(col_b) + + m = np.shape(mcol_b)[1] + one = np.ones((1, m), dtype=int) + + # horizontal stacking + training_data_x = np.hstack((one.T, mcol_a.T)) + + return training_data_x, mcol_b, col_a, col_b + + +def get_preds(training_data_x: np.mat, mcol_b: np.mat, tau: float) -> np.ndarray: + """ + Get predictions with minimum error for each training data + >>> get_preds(np.mat([[16.99, 10.34], [21.01,23.68], + ... [24.59,25.69]]),np.mat([[1.01, 1.66, 3.5]]), 0.6) + array([1.07173261, 1.65970737, 3.50160179]) + """ + ypred = local_weight_regression(training_data_x, mcol_b, tau) + return ypred + + +def plot_preds( + training_data_x: np.mat, + predictions: np.ndarray, + col_x: np.ndarray, + col_y: np.ndarray, + cola_name: str, + colb_name: str, +) -> plt.plot: + """ + This function used to plot predictions and display the graph + >>> pass #this function has no doctest + """ + xsort = training_data_x.copy() + xsort.sort(axis=0) + plt.scatter(col_x, col_y, color="blue") + plt.plot( + xsort[:, 1], + predictions[training_data_x[:, 1].argsort(0)], + color="yellow", + linewidth=5, + ) + plt.title("Local Weighted Regression") + plt.xlabel(cola_name) + plt.ylabel(colb_name) + plt.show() + + +if __name__ == "__main__": + training_data_x, mcol_b, col_a, col_b = load_data("tips", "total_bill", "tip") + predictions = get_preds(training_data_x, mcol_b, 0.5) + plot_preds(training_data_x, predictions, col_a, col_b, "total_bill", "tip") From a64c9f1e7cc9616c54296ca3983123e15ec486f1 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 31 Oct 2021 14:16:02 +0000 Subject: [PATCH 1702/2908] Deduplicate euclidean_length method in Vector (#5658) * Rewrite parts of Vector and Matrix methods * Refactor determinant method and add unit tests Refactor determinant method to create separate minor and cofactor methods. Add respective unit tests for new methods. Rename methods using snake case to follow Python naming conventions. * Reorganize Vector and Matrix methods * Update linear_algebra/README.md Co-authored-by: John Law * Fix punctuation and wording * Apply suggestions from code review Co-authored-by: John Law * Deduplicate euclidean length method for Vector * Add more unit tests for Euclidean length method * Fix bug in unit test for euclidean_length * Remove old comments for magnitude method Co-authored-by: John Law --- linear_algebra/src/lib.py | 33 +++++++++++------------ linear_algebra/src/test_linear_algebra.py | 8 +++++- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index dad0a8c0a6a2..85dc4b71c4a4 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -44,7 +44,6 @@ class Vector: component(i): gets the i-th component (0-indexed) change_component(pos: int, value: float): changes specified component euclidean_length(): returns the euclidean length of the vector - magnitude(): returns the magnitude of the vector angle(other: Vector, deg: bool): returns the angle between two vectors TODO: compare-operator """ @@ -159,18 +158,20 @@ def change_component(self, pos: int, value: float) -> None: def euclidean_length(self) -> float: """ returns the euclidean length of the vector - """ - squares = [c ** 2 for c in self.__components] - return math.sqrt(sum(squares)) - - def magnitude(self) -> float: - """ - Magnitude of a Vector - >>> Vector([2, 3, 4]).magnitude() + >>> Vector([2, 3, 4]).euclidean_length() 5.385164807134504 - + >>> Vector([1]).euclidean_length() + 1.0 + >>> Vector([0, -1, -2, -3, 4, 5, 6]).euclidean_length() + 9.539392014169456 + >>> Vector([]).euclidean_length() + Traceback (most recent call last): + ... + Exception: Vector is empty """ + if len(self.__components) == 0: + raise Exception("Vector is empty") squares = [c ** 2 for c in self.__components] return math.sqrt(sum(squares)) @@ -188,7 +189,7 @@ def angle(self, other: Vector, deg: bool = False) -> float: Exception: invalid operand! """ num = self * other - den = self.magnitude() * other.magnitude() + den = self.euclidean_length() * other.euclidean_length() if deg: return math.degrees(math.acos(num / den)) else: @@ -267,8 +268,7 @@ class Matrix: def __init__(self, matrix: list[list[float]], w: int, h: int) -> None: """ - simple constructor for initializing - the matrix with components. + simple constructor for initializing the matrix with components. """ self.__matrix = matrix self.__width = w @@ -276,8 +276,7 @@ def __init__(self, matrix: list[list[float]], w: int, h: int) -> None: def __str__(self) -> str: """ - returns a string representation of this - matrix. + returns a string representation of this matrix. """ ans = "" for i in range(self.__height): @@ -291,7 +290,7 @@ def __str__(self) -> str: def __add__(self, other: Matrix) -> Matrix: """ - implements the matrix-addition. + implements matrix addition. """ if self.__width == other.width() and self.__height == other.height(): matrix = [] @@ -307,7 +306,7 @@ def __add__(self, other: Matrix) -> Matrix: def __sub__(self, other: Matrix) -> Matrix: """ - implements the matrix-subtraction. + implements matrix subtraction. """ if self.__width == other.width() and self.__height == other.height(): matrix = [] diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index de7041a17038..724ceef2599a 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -42,12 +42,18 @@ def test_size(self) -> None: x = Vector([1, 2, 3, 4]) self.assertEqual(len(x), 4) - def test_euclidLength(self) -> None: + def test_euclidean_length(self) -> None: """ test for method euclidean_length() """ x = Vector([1, 2]) + y = Vector([1, 2, 3, 4, 5]) + z = Vector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + w = Vector([1, -1, 1, -1, 2, -3, 4, -5]) self.assertAlmostEqual(x.euclidean_length(), 2.236, 3) + self.assertAlmostEqual(y.euclidean_length(), 7.416, 3) + self.assertEqual(z.euclidean_length(), 0) + self.assertAlmostEqual(w.euclidean_length(), 7.616, 3) def test_add(self) -> None: """ From 868c2fa0a8e1f51ba32e7622990a9259a8740604 Mon Sep 17 00:00:00 2001 From: Maarten Date: Sun, 31 Oct 2021 15:19:44 +0100 Subject: [PATCH 1703/2908] Rewrite fibonacci.py (#5665) (#5677) * Removed doctest call * Removed 0 and 1 append to `fib_array` * Moved fibonacci sequence logic into `calculate` * Refactored `get` to generate missing numbers * Renamed `fib_array` to `sequence` * Renamed `number` to `index` * Refactored `get` to only return sequence to `index` * Moved main block into function * Added documentation to `get` * Added missing type hints * Fixed doctest error in `get` docstring * Moved calculate logic into get * Reformatted with black * Fixed wrong generation range --- dynamic_programming/fibonacci.py | 89 ++++++++++++++------------------ 1 file changed, 38 insertions(+), 51 deletions(-) diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index cab1358ddea1..4abc60d4f3cc 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -5,61 +5,48 @@ class Fibonacci: - def __init__(self, N=None): - self.fib_array = [] - if N: - N = int(N) - self.fib_array.append(0) - self.fib_array.append(1) - for i in range(2, N + 1): - 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 __init__(self) -> None: + self.sequence = [0, 1] - def get(self, sequence_no=None): + def get(self, index: int) -> list: """ - >>> 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] - [] + Get the Fibonacci number of `index`. If the number does not exist, + calculate all missing numbers leading up to the number of `index`. + + >>> Fibonacci().get(10) + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] + >>> Fibonacci().get(5) + [0, 1, 1, 2, 3] """ - if sequence_no is not None: - if sequence_no < len(self.fib_array): - return print(self.fib_array[: sequence_no + 1]) - else: - print("Out of bound.") - else: - print("Please specify a value") + difference = index - (len(self.sequence) - 2) + if difference >= 1: + for _ in range(difference): + self.sequence.append(self.sequence[-1] + self.sequence[-2]) + return self.sequence[:index] -if __name__ == "__main__": - print("\n********* Fibonacci Series Using Dynamic Programming ************\n") - print("\n Enter the upper limit for the fibonacci sequence: ", end="") - try: - 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" - ) - while True: - try: - i = int(input("Enter value: ").strip()) - if i < 0: - print("\n********* Good Bye!! ************\n") - break - fib.get(i) - except NameError: - print("\nInvalid input, please try again.") - except NameError: - print("\n********* Invalid input, good bye!! ************\n") +def main(): + print( + "Fibonacci Series Using Dynamic Programming\n", + "Enter the index of the Fibonacci number you want to calculate ", + "in the prompt below. (To exit enter exit or Ctrl-C)\n", + sep="", + ) + fibonacci = Fibonacci() + + while True: + prompt: str = input(">> ") + if prompt in {"exit", "quit"}: + break - import doctest + try: + index: int = int(prompt) + except ValueError: + print("Enter a number or 'exit'") + continue - doctest.testmod() + print(fibonacci.get(index)) + + +if __name__ == "__main__": + main() From 94f38dd88c9f644d270a52f9cdd76c2e64e90c7c Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Sun, 31 Oct 2021 09:03:03 -0700 Subject: [PATCH 1704/2908] [mypy] Fix type annotations for linked_stack.py (#5576) * Fix type annotations for linked_stack.py * Replace Optional with inline union type * Rename linked_stack to stack_with_singly_linked_list * Rename stack_using_dll to stack_with_doubly_linked_list * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 +-- ...ll.py => stack_with_doubly_linked_list.py} | 0 ...ck.py => stack_with_singly_linked_list.py} | 29 ++++++++++--------- 3 files changed, 18 insertions(+), 15 deletions(-) rename data_structures/stacks/{stack_using_dll.py => stack_with_doubly_linked_list.py} (100%) rename data_structures/stacks/{linked_stack.py => stack_with_singly_linked_list.py} (83%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 434cddbfd32c..140dc632c931 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -198,12 +198,12 @@ * [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) * [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) + * [Stack With Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack_with_doubly_linked_list.py) + * [Stack With Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack_with_singly_linked_list.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) diff --git a/data_structures/stacks/stack_using_dll.py b/data_structures/stacks/stack_with_doubly_linked_list.py similarity index 100% rename from data_structures/stacks/stack_using_dll.py rename to data_structures/stacks/stack_with_doubly_linked_list.py diff --git a/data_structures/stacks/linked_stack.py b/data_structures/stacks/stack_with_singly_linked_list.py similarity index 83% rename from data_structures/stacks/linked_stack.py rename to data_structures/stacks/stack_with_singly_linked_list.py index 85b59a940e39..903ae39db4b5 100644 --- a/data_structures/stacks/linked_stack.py +++ b/data_structures/stacks/stack_with_singly_linked_list.py @@ -1,19 +1,22 @@ """ A Stack using a linked list like structure """ from __future__ import annotations -from typing import Any +from collections.abc import Iterator +from typing import Generic, TypeVar +T = TypeVar("T") -class Node: - def __init__(self, data): + +class Node(Generic[T]): + def __init__(self, data: T): self.data = data - self.next = None + self.next: Node[T] | None = None - def __str__(self): + def __str__(self) -> str: return f"{self.data}" -class LinkedStack: +class LinkedStack(Generic[T]): """ Linked List Stack implementing push (to top), pop (from top) and is_empty @@ -44,15 +47,15 @@ class LinkedStack: """ def __init__(self) -> None: - self.top: Node | None = None + self.top: Node[T] | None = None - def __iter__(self): + def __iter__(self) -> Iterator[T]: node = self.top while node: yield node.data node = node.next - def __str__(self): + def __str__(self) -> str: """ >>> stack = LinkedStack() >>> stack.push("c") @@ -63,7 +66,7 @@ def __str__(self): """ return "->".join([str(item) for item in self]) - def __len__(self): + def __len__(self) -> int: """ >>> stack = LinkedStack() >>> len(stack) == 0 @@ -87,7 +90,7 @@ def is_empty(self) -> bool: """ return self.top is None - def push(self, item: Any) -> None: + def push(self, item: T) -> None: """ >>> stack = LinkedStack() >>> stack.push("Python") @@ -101,7 +104,7 @@ def push(self, item: Any) -> None: node.next = self.top self.top = node - def pop(self) -> Any: + def pop(self) -> T: """ >>> stack = LinkedStack() >>> stack.pop() @@ -125,7 +128,7 @@ def pop(self) -> Any: self.top = self.top.next return pop_node.data - def peek(self) -> Any: + def peek(self) -> T: """ >>> stack = LinkedStack() >>> stack.push("Java") From 99cf2cc1c5a83585625a26b6f267ac8f25affdc7 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Mon, 1 Nov 2021 03:26:33 +0530 Subject: [PATCH 1705/2908] Fix build issues due to count (#5725) * Fix build issues due to count * Update check_anagrams.py --- strings/check_anagrams.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index 938bf4c2abee..f652e2294db2 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -2,6 +2,7 @@ wiki: https://en.wikipedia.org/wiki/Anagram """ from collections import defaultdict +from typing import DefaultDict def check_anagrams(first_str: str, second_str: str) -> bool: @@ -29,7 +30,7 @@ def check_anagrams(first_str: str, second_str: str) -> bool: return False # Default values for count should be 0 - count = defaultdict(int) + count: DefaultDict[str, int] = defaultdict(int) # For each character in input strings, # increment count in the corresponding From 06ab650e0823d7b58d1fc22a6f7cc0cca818f973 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 1 Nov 2021 06:25:40 +0000 Subject: [PATCH 1706/2908] Merge maths/fibonacci.py and maths/fibonacci_sequence_recursion.py (#5738) * Rewrite parts of Vector and Matrix methods * Refactor determinant method and add unit tests Refactor determinant method to create separate minor and cofactor methods. Add respective unit tests for new methods. Rename methods using snake case to follow Python naming conventions. * Reorganize Vector and Matrix methods * Update linear_algebra/README.md Co-authored-by: John Law * Fix punctuation and wording * Apply suggestions from code review Co-authored-by: John Law * Deduplicate euclidean length method for Vector * Add more unit tests for Euclidean length method * Fix bug in unit test for euclidean_length * Remove old comments for magnitude method * Rewrite maths/fibonacci.py * Rewrite timer and add unit tests * Fix typos in fib_binet unit tests * Fix typos in fib_binet unit tests * Clean main method * Merge fibonacci.py and fibonacci_sequence_recursion.py * Fix fib_binet unit test Co-authored-by: John Law --- maths/fibonacci.py | 260 +++++++++++++------------- maths/fibonacci_sequence_recursion.py | 22 --- 2 files changed, 130 insertions(+), 152 deletions(-) delete mode 100644 maths/fibonacci_sequence_recursion.py diff --git a/maths/fibonacci.py b/maths/fibonacci.py index e6519035401e..b009ea9df38a 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -1,130 +1,130 @@ -# 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 functools -import math -import time -from decimal import Decimal, getcontext - -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) +# fibonacci.py +""" +Calculates the Fibonacci sequence using iteration, recursion, and a simplified +form of Binet's formula + +NOTE 1: the iterative and recursive functions are more accurate than the Binet's +formula function because the iterative function doesn't use floats + +NOTE 2: the Binet's formula function is much more limited in the size of inputs +that it can handle due to the size limitations of Python floats +""" + +from math import sqrt +from time import time + + +def time_func(func, *args, **kwargs): + """ + Times the execution of a function with parameters + """ + start = time() + output = func(*args, **kwargs) + end = time() + if int(end - start) > 0: + print(f"{func.__name__} runtime: {(end - start):0.4f} s") + else: + print(f"{func.__name__} runtime: {(end - start) * 1000:0.4f} ms") + return output + + +def fib_iterative(n: int) -> list[int]: + """ + Calculates the first n (0-indexed) Fibonacci numbers using iteration + >>> fib_iterative(0) + [0] + >>> fib_iterative(1) + [0, 1] + >>> fib_iterative(5) + [0, 1, 1, 2, 3, 5] + >>> fib_iterative(10) + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + >>> fib_iterative(-1) + Traceback (most recent call last): + ... + Exception: n is negative + """ + if n < 0: + raise Exception("n is negative") + if n == 0: + return [0] + fib = [0, 1] + for _ in range(n - 1): + fib.append(fib[-1] + fib[-2]) + return fib + + +def fib_recursive(n: int) -> list[int]: + """ + Calculates the first n (0-indexed) Fibonacci numbers using recursion + >>> fib_iterative(0) + [0] + >>> fib_iterative(1) + [0, 1] + >>> fib_iterative(5) + [0, 1, 1, 2, 3, 5] + >>> fib_iterative(10) + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + >>> fib_iterative(-1) + Traceback (most recent call last): + ... + Exception: n is negative + """ + + def fib_recursive_term(i: int) -> int: + """ + Calculates the i-th (0-indexed) Fibonacci number using recursion + """ + if i < 0: + raise Exception("n is negative") + if i < 2: + return i + return fib_recursive_term(i - 1) + fib_recursive_term(i - 2) + + if n < 0: + raise Exception("n is negative") + return [fib_recursive_term(i) for i in range(n + 1)] + + +def fib_binet(n: int) -> list[int]: + """ + Calculates the first n (0-indexed) Fibonacci numbers using a simplified form + of Binet's formula: + https://en.m.wikipedia.org/wiki/Fibonacci_number#Computation_by_rounding + + NOTE 1: this function diverges from fib_iterative at around n = 71, likely + due to compounding floating-point arithmetic errors + + NOTE 2: this function overflows on n >= 1475 because of the size limitations + of Python floats + >>> fib_binet(0) + [0] + >>> fib_binet(1) + [0, 1] + >>> fib_binet(5) + [0, 1, 1, 2, 3, 5] + >>> fib_binet(10) + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + >>> fib_binet(-1) + Traceback (most recent call last): + ... + Exception: n is negative + >>> fib_binet(1475) + Traceback (most recent call last): + ... + Exception: n is too large + """ + if n < 0: + raise Exception("n is negative") + if n >= 1475: + raise Exception("n is too large") + sqrt_5 = sqrt(5) + phi = (1 + sqrt_5) / 2 + return [round(phi ** i / sqrt_5) for i in range(n + 1)] + + +if __name__ == "__main__": + num = 20 + time_func(fib_iterative, num) + time_func(fib_recursive, num) + time_func(fib_binet, num) diff --git a/maths/fibonacci_sequence_recursion.py b/maths/fibonacci_sequence_recursion.py deleted file mode 100644 index 794b9fc0bd3a..000000000000 --- a/maths/fibonacci_sequence_recursion.py +++ /dev/null @@ -1,22 +0,0 @@ -# Fibonacci Sequence Using Recursion - - -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] - """ - return n if n <= 1 else recur_fibo(n - 1) + recur_fibo(n - 2) - - -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:") - print([recur_fibo(n) for n in range(limit)]) - else: - print("Please enter a positive integer: ") - - -if __name__ == "__main__": - main() From 71ba3a1ad940bb42f773ac483da9d40b234ecb7f Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 1 Nov 2021 09:27:19 +0300 Subject: [PATCH 1707/2908] Improve Project Euler problem 012 solution 1 (#5731) * Improve solution * Uncomment code that has been commented due to slow execution affecting Travis * Retest --- project_euler/problem_012/sol1.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/project_euler/problem_012/sol1.py b/project_euler/problem_012/sol1.py index 7e080c4e45a1..861d026ece5b 100644 --- a/project_euler/problem_012/sol1.py +++ b/project_euler/problem_012/sol1.py @@ -21,17 +21,20 @@ What is the value of the first triangle number to have over five hundred divisors? """ -from math import sqrt def count_divisors(n): - nDivisors = 0 - for i in range(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 + nDivisors = 1 + i = 2 + while i * i <= n: + multiplicity = 0 + while n % i == 0: + n //= i + multiplicity += 1 + nDivisors *= multiplicity + 1 + i += 1 + if n > 1: + nDivisors *= 2 return nDivisors @@ -39,9 +42,8 @@ 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 + >>> solution() + 76576500 """ tNum = 1 i = 1 From 68ca61ecb75be579f13c6783c2071cc3b063d21b Mon Sep 17 00:00:00 2001 From: Kelly Costa Date: Mon, 1 Nov 2021 10:36:18 -0300 Subject: [PATCH 1708/2908] Add search book via ISBN using openlibrary.org API (#5736) * Add search book via ISBN using openlibrary.org API * FIX: parameters type hints and isbn sizes * Add doctests * Update search_books_by_isbn.py Co-authored-by: Christian Clauss --- web_programming/search_books_by_isbn.py | 76 +++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 web_programming/search_books_by_isbn.py diff --git a/web_programming/search_books_by_isbn.py b/web_programming/search_books_by_isbn.py new file mode 100644 index 000000000000..fcb8b0428b88 --- /dev/null +++ b/web_programming/search_books_by_isbn.py @@ -0,0 +1,76 @@ +""" +Get book and author data from https://openlibrary.org + +ISBN: https://en.wikipedia.org/wiki/International_Standard_Book_Number +""" +from json import JSONDecodeError # Workaround for requests.exceptions.JSONDecodeError + +import requests + + +def get_openlibrary_data(olid: str = "isbn/0140328726") -> dict: + """ + Given an 'isbn/0140328726', return book data from Open Library as a Python dict. + Given an '/authors/OL34184A', return authors data as a Python dict. + This code must work for olids with or without a leading slash ('/'). + + # Comment out doctests if they take too long or have results that may change + # >>> get_openlibrary_data(olid='isbn/0140328726') # doctest: +ELLIPSIS + {'publishers': ['Puffin'], 'number_of_pages': 96, 'isbn_10': ['0140328726'], ... + # >>> get_openlibrary_data(olid='/authors/OL7353617A') # doctest: +ELLIPSIS + {'name': 'Adrian Brisku', 'created': {'type': '/type/datetime', ... + >>> pass # Placate https://github.com/apps/algorithms-keeper + """ + new_olid = olid.strip().strip("/") # Remove leading/trailing whitespace & slashes + if new_olid.count("/") != 1: + raise ValueError(f"{olid} is not a valid Open Library olid") + return requests.get(f"https://openlibrary.org/%7Bnew_olid%7D.json").json() + + +def summerize_book(ol_book_data: dict) -> dict: + """ + Given Open Library book data, return a summary as a Python dict. + + >>> pass # Placate TheAlgorithms @ + """ + desired_keys = { + "title": "Title", + "publish_date": "Publish date", + "authors": "Authors", + "number_of_pages": "Number of pages:", + "first_sentence": "First sentence", + "isbn_10": "ISBN (10)", + "isbn_13": "ISBN (13)", + } + data = {better_key: ol_book_data[key] for key, better_key in desired_keys.items()} + data["Authors"] = [ + get_openlibrary_data(author["key"])["name"] for author in data["Authors"] + ] + data["First sentence"] = data["First sentence"]["value"] + for key, value in data.items(): + if isinstance(value, list): + data[key] = ", ".join(value) + return data + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + while True: + isbn = input("\nEnter the ISBN code to search (or 'quit' to stop): ").strip() + if isbn.lower() in ("", "q", "quit", "exit", "stop"): + break + + if len(isbn) not in (10, 13) or not isbn.isdigit(): + print(f"Sorry, {isbn} is not a valid ISBN. Please, input a valid ISBN.") + continue + + print(f"\nSearching Open Library for ISBN: {isbn}...\n") + + try: + book_summary = summerize_book(get_openlibrary_data(f"isbn/{isbn}")) + print("\n".join(f"{key}: {value}" for key, value in book_summary.items())) + except JSONDecodeError: # Workaround for requests.exceptions.RequestException: + print(f"Sorry, there are no results for ISBN: {isbn}.") From 84cca2119c5f493823cc65a3796fe37e4a9c643d Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 1 Nov 2021 17:06:35 +0000 Subject: [PATCH 1709/2908] Rewrite maths/fibonacci.py (#5734) * Rewrite parts of Vector and Matrix methods * Refactor determinant method and add unit tests Refactor determinant method to create separate minor and cofactor methods. Add respective unit tests for new methods. Rename methods using snake case to follow Python naming conventions. * Reorganize Vector and Matrix methods * Update linear_algebra/README.md Co-authored-by: John Law * Fix punctuation and wording * Apply suggestions from code review Co-authored-by: John Law * Deduplicate euclidean length method for Vector * Add more unit tests for Euclidean length method * Fix bug in unit test for euclidean_length * Remove old comments for magnitude method * Rewrite maths/fibonacci.py * Rewrite timer and add unit tests * Fix typos in fib_binet unit tests * Fix typos in fib_binet unit tests * Clean main method Co-authored-by: John Law --- maths/fibonacci.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/fibonacci.py b/maths/fibonacci.py index b009ea9df38a..9b193b74a827 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -95,8 +95,8 @@ def fib_binet(n: int) -> list[int]: NOTE 1: this function diverges from fib_iterative at around n = 71, likely due to compounding floating-point arithmetic errors - NOTE 2: this function overflows on n >= 1475 because of the size limitations - of Python floats + NOTE 2: this function doesn't accept n >= 1475 because it overflows + thereafter due to the size limitations of Python floats >>> fib_binet(0) [0] >>> fib_binet(1) From 74f496712628e5bc77ae9e11572d4207066214ba Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 1 Nov 2021 18:07:47 +0100 Subject: [PATCH 1710/2908] Fix comment (#5742) * Fix comment * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 11 +++++++++-- web_programming/search_books_by_isbn.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 140dc632c931..e70c0aab64a7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -420,6 +420,8 @@ * [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) + * Local Weighted Learning + * [Local Weighted Learning](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/local_weighted_learning/local_weighted_learning.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) @@ -476,7 +478,6 @@ * [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) @@ -517,6 +518,7 @@ * [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) + * [Pollard Rho](https://github.com/TheAlgorithms/Python/blob/master/maths/pollard_rho.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) @@ -539,6 +541,7 @@ * [Geometric Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_series.py) * [Harmonic](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic.py) * [Harmonic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic_series.py) + * [Hexagonal Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/series/hexagonal_numbers.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) @@ -583,8 +586,8 @@ ## Other * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) + * [Alternative List Arrange](https://github.com/TheAlgorithms/Python/blob/master/other/alternative_list_arrange.py) * [Check Strong Password](https://github.com/TheAlgorithms/Python/blob/master/other/check_strong_password.py) - * [Date To Weekday](https://github.com/TheAlgorithms/Python/blob/master/other/date_to_weekday.py) * [Davisb Putnamb Logemannb Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davisb_putnamb_logemannb_loveland.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) @@ -861,6 +864,8 @@ * [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 493 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_493/sol1.py) * Problem 551 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) * Problem 686 @@ -960,6 +965,7 @@ * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/strings/detecting_english_programmatically.py) * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/strings/frequency_finder.py) * [Indian Phone Validator](https://github.com/TheAlgorithms/Python/blob/master/strings/indian_phone_validator.py) + * [Is Contains Unique Chars](https://github.com/TheAlgorithms/Python/blob/master/strings/is_contains_unique_chars.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) * [Join](https://github.com/TheAlgorithms/Python/blob/master/strings/join.py) @@ -1008,6 +1014,7 @@ * [Nasa Data](https://github.com/TheAlgorithms/Python/blob/master/web_programming/nasa_data.py) * [Random Anime Character](https://github.com/TheAlgorithms/Python/blob/master/web_programming/random_anime_character.py) * [Recaptcha Verification](https://github.com/TheAlgorithms/Python/blob/master/web_programming/recaptcha_verification.py) + * [Search Books By Isbn](https://github.com/TheAlgorithms/Python/blob/master/web_programming/search_books_by_isbn.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/web_programming/search_books_by_isbn.py b/web_programming/search_books_by_isbn.py index fcb8b0428b88..a55110f3f5fc 100644 --- a/web_programming/search_books_by_isbn.py +++ b/web_programming/search_books_by_isbn.py @@ -31,7 +31,7 @@ def summerize_book(ol_book_data: dict) -> dict: """ Given Open Library book data, return a summary as a Python dict. - >>> pass # Placate TheAlgorithms @ + >>> pass # Placate https://github.com/apps/algorithms-keeper """ desired_keys = { "title": "Title", From dc6e77338c9cac607e58c06b178a232643f3e87d Mon Sep 17 00:00:00 2001 From: Brian Evans <53117772+mrbrianevans@users.noreply.github.com> Date: Mon, 1 Nov 2021 23:09:40 +0000 Subject: [PATCH 1711/2908] Add stone unit of measuring weight (#5730) * Add stone unit of measuring weight And some tests in the docs using an external calculator. Not yet tested if they pass. * Fix rounding descrepencies in doctests to pass tests --- conversions/weight_conversion.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/conversions/weight_conversion.py b/conversions/weight_conversion.py index c344416be5f5..18c4037317da 100644 --- a/conversions/weight_conversion.py +++ b/conversions/weight_conversion.py @@ -3,7 +3,7 @@ __author__ = "Anubhav Solanki" __license__ = "MIT" -__version__ = "1.0.0" +__version__ = "1.1.0" __maintainer__ = "Anubhav Solanki" __email__ = "anubhavsolanki0@gmail.com" @@ -27,6 +27,7 @@ -> 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) +-> Wikipedia reference: https://en.wikipedia.org/wiki/Stone_(unit) """ KILOGRAM_CHART: dict[str, float] = { @@ -37,6 +38,7 @@ "long-ton": 0.0009842073, "short-ton": 0.0011023122, "pound": 2.2046244202, + "stone": 0.1574731728, "ounce": 35.273990723, "carrat": 5000, "atomic-mass-unit": 6.022136652e26, @@ -50,6 +52,7 @@ "long-ton": 1016.04608, "short-ton": 907.184, "pound": 0.453592, + "stone": 6.35029, "ounce": 0.0283495, "carrat": 0.0002, "atomic-mass-unit": 1.660540199e-27, @@ -67,6 +70,7 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float: "long-ton" : 0.0009842073, "short-ton" : 0.0011023122, "pound" : 2.2046244202, + "stone": 0.1574731728, "ounce" : 35.273990723, "carrat" : 5000, "atomic-mass-unit" : 6.022136652E+26 @@ -85,6 +89,8 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float: 0.0011023122 >>> weight_conversion("kilogram","pound",4) 8.8184976808 + >>> weight_conversion("kilogram","stone",5) + 0.7873658640000001 >>> weight_conversion("kilogram","ounce",4) 141.095962892 >>> weight_conversion("kilogram","carrat",3) @@ -105,6 +111,8 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float: 3.3069366000000003e-06 >>> weight_conversion("gram","pound",3) 0.0066138732606 + >>> weight_conversion("gram","stone",4) + 0.0006298926912000001 >>> weight_conversion("gram","ounce",1) 0.035273990723 >>> weight_conversion("gram","carrat",2) @@ -211,6 +219,24 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float: 2267.96 >>> weight_conversion("pound","atomic-mass-unit",4) 1.0926372033015936e+27 + >>> weight_conversion("stone","kilogram",5) + 31.751450000000002 + >>> weight_conversion("stone","gram",2) + 12700.58 + >>> weight_conversion("stone","milligram",3) + 19050870.0 + >>> weight_conversion("stone","metric-ton",3) + 0.01905087 + >>> weight_conversion("stone","long-ton",3) + 0.018750005325351003 + >>> weight_conversion("stone","short-ton",3) + 0.021000006421614002 + >>> weight_conversion("stone","pound",2) + 28.00000881870372 + >>> weight_conversion("stone","ounce",1) + 224.00007054835967 + >>> weight_conversion("stone","carrat",2) + 63502.9 >>> weight_conversion("ounce","kilogram",3) 0.0850485 >>> weight_conversion("ounce","gram",3) From 5910c3aa78e6fa53ef425ab9efa43348cc421e6c Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Tue, 2 Nov 2021 14:50:55 +0530 Subject: [PATCH 1712/2908] Typo (#5750) --- web_programming/search_books_by_isbn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_programming/search_books_by_isbn.py b/web_programming/search_books_by_isbn.py index a55110f3f5fc..22a31dcb1db4 100644 --- a/web_programming/search_books_by_isbn.py +++ b/web_programming/search_books_by_isbn.py @@ -27,7 +27,7 @@ def get_openlibrary_data(olid: str = "isbn/0140328726") -> dict: return requests.get(f"https://openlibrary.org/%7Bnew_olid%7D.json").json() -def summerize_book(ol_book_data: dict) -> dict: +def summarize_book(ol_book_data: dict) -> dict: """ Given Open Library book data, return a summary as a Python dict. @@ -70,7 +70,7 @@ def summerize_book(ol_book_data: dict) -> dict: print(f"\nSearching Open Library for ISBN: {isbn}...\n") try: - book_summary = summerize_book(get_openlibrary_data(f"isbn/{isbn}")) + book_summary = summarize_book(get_openlibrary_data(f"isbn/{isbn}")) print("\n".join(f"{key}: {value}" for key, value in book_summary.items())) except JSONDecodeError: # Workaround for requests.exceptions.RequestException: print(f"Sorry, there are no results for ISBN: {isbn}.") From 424c2008473b00a8d3f31a9ad043526d95c31e69 Mon Sep 17 00:00:00 2001 From: Mozartus <32893711+Mozartuss@users.noreply.github.com> Date: Tue, 2 Nov 2021 11:06:39 +0100 Subject: [PATCH 1713/2908] Add gabor filter (#5289) * add gabor_filter.py * Update gabor_filter.py * update gabor_filter.py * add doctest * change import order * Update digital_image_processing/filters/gabor_filter.py Co-authored-by: John Law * Update gabor_filter.py * fix gabor filter calculation Co-authored-by: John Law --- .../filters/gabor_filter.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 digital_image_processing/filters/gabor_filter.py diff --git a/digital_image_processing/filters/gabor_filter.py b/digital_image_processing/filters/gabor_filter.py new file mode 100644 index 000000000000..90aa049c24a0 --- /dev/null +++ b/digital_image_processing/filters/gabor_filter.py @@ -0,0 +1,85 @@ +# Implementation of the Gaborfilter +# https://en.wikipedia.org/wiki/Gabor_filter +import numpy as np +from cv2 import COLOR_BGR2GRAY, CV_8UC3, cvtColor, filter2D, imread, imshow, waitKey + + +def gabor_filter_kernel( + ksize: int, sigma: int, theta: int, lambd: int, gamma: int, psi: int +) -> np.ndarray: + """ + :param ksize: The kernelsize of the convolutional filter (ksize x ksize) + :param sigma: standard deviation of the gaussian bell curve + :param theta: The orientation of the normal to the parallel stripes + of Gabor function. + :param lambd: Wavelength of the sinusoidal component. + :param gamma: The spatial aspect ratio and specifies the ellipticity + of the support of Gabor function. + :param psi: The phase offset of the sinusoidal function. + + >>> gabor_filter_kernel(3, 8, 0, 10, 0, 0).tolist() + [[0.8027212023735046, 1.0, 0.8027212023735046], [0.8027212023735046, 1.0, \ +0.8027212023735046], [0.8027212023735046, 1.0, 0.8027212023735046]] + + """ + + # prepare kernel + # the kernel size have to be odd + if (ksize % 2) == 0: + ksize = ksize + 1 + gabor = np.zeros((ksize, ksize), dtype=np.float32) + + # each value + for y in range(ksize): + for x in range(ksize): + # distance from center + px = x - ksize // 2 + py = y - ksize // 2 + + # degree to radiant + _theta = theta / 180 * np.pi + cos_theta = np.cos(_theta) + sin_theta = np.sin(_theta) + + # get kernel x + _x = cos_theta * px + sin_theta * py + + # get kernel y + _y = -sin_theta * px + cos_theta * py + + # fill kernel + gabor[y, x] = np.exp( + -(_x ** 2 + gamma ** 2 * _y ** 2) / (2 * sigma ** 2) + ) * np.cos(2 * np.pi * _x / lambd + psi) + + return gabor + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + # read original image + img = imread("../image_data/lena.jpg") + # turn image in gray scale value + gray = cvtColor(img, COLOR_BGR2GRAY) + + # Apply multiple Kernel to detect edges + out = np.zeros(gray.shape[:2]) + for theta in [0, 30, 60, 90, 120, 150]: + """ + ksize = 10 + sigma = 8 + lambd = 10 + gamma = 0 + psi = 0 + """ + kernel_10 = gabor_filter_kernel(10, 8, theta, 10, 0, 0) + out += filter2D(gray, CV_8UC3, kernel_10) + out = out / out.max() * 255 + out = out.astype(np.uint8) + + imshow("Original", gray) + imshow("Gabor filter with 20x20 mask and 6 directions", out) + + waitKey(0) From 3c8fec1316aade09134255dd101060b4ec036241 Mon Sep 17 00:00:00 2001 From: Matthew Wisdom Date: Tue, 2 Nov 2021 03:07:36 -0700 Subject: [PATCH 1714/2908] Add Neville's algorithm for polynomial interpolation (#5447) * Added nevilles algorithm for polynomial interpolation * Added type hinting for neville_interpolate function arguments. * Added more descriptive names * Update nevilles_method.py * Fixed some linting issues * Fixed type hinting error * Fixed nevilles_method.py * Add ellipsis for doctest spanning multiple lines * Update nevilles_method.py Co-authored-by: John Law --- maths/nevilles_method.py | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 maths/nevilles_method.py diff --git a/maths/nevilles_method.py b/maths/nevilles_method.py new file mode 100644 index 000000000000..5583e4269b32 --- /dev/null +++ b/maths/nevilles_method.py @@ -0,0 +1,56 @@ +""" + Python program to show how to interpolate and evaluate a polynomial + using Neville's method. + Neville’s method evaluates a polynomial that passes through a + given set of x and y points for a particular x value (x0) using the + Newton polynomial form. + Reference: + https://rpubs.com/aaronsc32/nevilles-method-polynomial-interpolation +""" + + +def neville_interpolate(x_points: list, y_points: list, x0: int) -> list: + """ + Interpolate and evaluate a polynomial using Neville's method. + Arguments: + x_points, y_points: Iterables of x and corresponding y points through + which the polynomial passes. + x0: The value of x to evaluate the polynomial for. + Return Value: A list of the approximated value and the Neville iterations + table respectively. + >>> import pprint + >>> neville_interpolate((1,2,3,4,6), (6,7,8,9,11), 5)[0] + 10.0 + >>> pprint.pprint(neville_interpolate((1,2,3,4,6), (6,7,8,9,11), 99)[1]) + [[0, 6, 0, 0, 0], + [0, 7, 0, 0, 0], + [0, 8, 104.0, 0, 0], + [0, 9, 104.0, 104.0, 0], + [0, 11, 104.0, 104.0, 104.0]] + >>> neville_interpolate((1,2,3,4,6), (6,7,8,9,11), 99)[0] + 104.0 + >>> neville_interpolate((1,2,3,4,6), (6,7,8,9,11), '') + Traceback (most recent call last): + File "", line 1, in + ... + TypeError: unsupported operand type(s) for -: 'str' and 'int' + """ + n = len(x_points) + q = [[0] * n for i in range(n)] + for i in range(n): + q[i][1] = y_points[i] + + for i in range(2, n): + for j in range(i, n): + q[j][i] = ( + (x0 - x_points[j - i + 1]) * q[j][i - 1] + - (x0 - x_points[j]) * q[j - 1][i - 1] + ) / (x_points[j] - x_points[j - i + 1]) + + return [q[n - 1][n - 1], q] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 24731b078c0e30d0f9bdd4ed96749f33574860c6 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Tue, 2 Nov 2021 07:09:46 -0300 Subject: [PATCH 1715/2908] [mypy] fix type annotations in `data_structures/queue/circular_queue_linked_list.py` (#5749) * [mypy] fix type annotations in circular_queue_linked_list * Remove 10 blank lines Co-authored-by: Christian Clauss --- .../queue/circular_queue_linked_list.py | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/data_structures/queue/circular_queue_linked_list.py b/data_structures/queue/circular_queue_linked_list.py index 1878403bd2ef..e8c2b8bffc06 100644 --- a/data_structures/queue/circular_queue_linked_list.py +++ b/data_structures/queue/circular_queue_linked_list.py @@ -1,6 +1,8 @@ # Implementation of Circular Queue using linked lists # https://en.wikipedia.org/wiki/Circular_buffer +from __future__ import annotations + from typing import Any @@ -18,8 +20,8 @@ class CircularQueueLinkedList: """ def __init__(self, initial_capacity: int = 6) -> None: - self.front = None - self.rear = None + self.front: Node | None = None + self.rear: Node | None = None self.create_linked_list(initial_capacity) def create_linked_list(self, initial_capacity: int) -> None: @@ -27,7 +29,7 @@ def create_linked_list(self, initial_capacity: int) -> None: self.front = current_node self.rear = current_node previous_node = current_node - for i in range(1, initial_capacity): + for _ in range(1, initial_capacity): current_node = Node() previous_node.next = current_node current_node.prev = previous_node @@ -49,9 +51,14 @@ def is_empty(self) -> bool: >>> cq.is_empty() True """ - return self.front == self.rear and self.front.data is None - def first(self) -> Any: + return ( + self.front == self.rear + and self.front is not None + and self.front.data is None + ) + + def first(self) -> Any | None: """ Returns the first element of the queue >>> cq = CircularQueueLinkedList() @@ -74,7 +81,7 @@ def first(self) -> Any: 'b' """ self.check_can_perform_operation() - return self.front.data + return self.front.data if self.front else None def enqueue(self, data: Any) -> None: """ @@ -92,11 +99,13 @@ def enqueue(self, data: Any) -> None: ... Exception: Empty Queue """ + if self.rear is None: + return + self.check_is_full() - if self.is_empty(): - self.rear.data = data - else: + if not self.is_empty(): self.rear = self.rear.next + if self.rear: self.rear.data = data def dequeue(self) -> Any: @@ -117,6 +126,8 @@ def dequeue(self) -> Any: Exception: Empty Queue """ self.check_can_perform_operation() + if self.rear is None or self.front is None: + return if self.front == self.rear: data = self.front.data self.front.data = None @@ -133,15 +144,15 @@ def check_can_perform_operation(self) -> None: raise Exception("Empty Queue") def check_is_full(self) -> None: - if self.rear.next == self.front: + if self.rear and self.rear.next == self.front: raise Exception("Full Queue") class Node: def __init__(self) -> None: - self.data = None - self.next = None - self.prev = None + self.data: Any | None = None + self.next: Node | None = None + self.prev: Node | None = None if __name__ == "__main__": From bdd135d40343daf047a00abc9a7360db192793d9 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Tue, 2 Nov 2021 15:40:25 +0530 Subject: [PATCH 1716/2908] Split base85.py into functions, Add doctests (#5746) * Update base16.py * Rename base64_encoding.py to base64.py * Split into functions, Add doctests * Update base16.py --- ciphers/base16.py | 16 +++++------ ciphers/{base64_encoding.py => base64.py} | 0 ciphers/base85.py | 34 ++++++++++++++++++----- 3 files changed, 35 insertions(+), 15 deletions(-) rename ciphers/{base64_encoding.py => base64.py} (100%) diff --git a/ciphers/base16.py b/ciphers/base16.py index 1ef60868dc3f..a149a6d8c5bf 100644 --- a/ciphers/base16.py +++ b/ciphers/base16.py @@ -1,30 +1,30 @@ import base64 -def encode_to_b16(inp: str) -> bytes: +def base16_encode(inp: str) -> bytes: """ Encodes a given utf-8 string into base-16. - >>> encode_to_b16('Hello World!') + >>> base16_encode('Hello World!') b'48656C6C6F20576F726C6421' - >>> encode_to_b16('HELLO WORLD!') + >>> base16_encode('HELLO WORLD!') b'48454C4C4F20574F524C4421' - >>> encode_to_b16('') + >>> base16_encode('') b'' """ # encode the input into a bytes-like object and then encode b16encode that return base64.b16encode(inp.encode("utf-8")) -def decode_from_b16(b16encoded: bytes) -> str: +def base16_decode(b16encoded: bytes) -> str: """ Decodes from base-16 to a utf-8 string. - >>> decode_from_b16(b'48656C6C6F20576F726C6421') + >>> base16_decode(b'48656C6C6F20576F726C6421') 'Hello World!' - >>> decode_from_b16(b'48454C4C4F20574F524C4421') + >>> base16_decode(b'48454C4C4F20574F524C4421') 'HELLO WORLD!' - >>> decode_from_b16(b'') + >>> base16_decode(b'') '' """ # b16decode the input into bytes and decode that into a human readable string diff --git a/ciphers/base64_encoding.py b/ciphers/base64.py similarity index 100% rename from ciphers/base64_encoding.py rename to ciphers/base64.py diff --git a/ciphers/base85.py b/ciphers/base85.py index 9740299b9771..afd1aff79d11 100644 --- a/ciphers/base85.py +++ b/ciphers/base85.py @@ -1,13 +1,33 @@ import base64 -def main() -> None: - 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 +def base85_encode(string: str) -> bytes: + """ + >>> base85_encode("") + b'' + >>> base85_encode("12345") + b'0etOA2#' + >>> base85_encode("base 85") + b'@UX=h+?24' + """ + # encoded the input to a bytes-like object and then a85encode that + return base64.a85encode(string.encode("utf-8")) + + +def base85_decode(a85encoded: bytes) -> str: + """ + >>> base85_decode(b"") + '' + >>> base85_decode(b"0etOA2#") + '12345' + >>> base85_decode(b"@UX=h+?24") + 'base 85' + """ + # a85decode the input into bytes and decode that into a human readable string + return base64.a85decode(a85encoded).decode("utf-8") if __name__ == "__main__": - main() + import doctest + + doctest.testmod() From 0124b73484aeb6a35b036842f382f7445fcec258 Mon Sep 17 00:00:00 2001 From: krishchopra02 <77331421+krishchopra02@users.noreply.github.com> Date: Tue, 2 Nov 2021 15:43:49 +0530 Subject: [PATCH 1717/2908] Add a gray_code_sequence.py file to the bit_manipulation folder (#5038) * Added a gray_code_sequence.py file to the bit_manipulation folder * Added a descriptive name for variable n changing it to bit count * Update gray_code_sequence.py Co-authored-by: krishchopra02 Co-authored-by: John Law --- bit_manipulation/gray_code_sequence.py | 94 ++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 bit_manipulation/gray_code_sequence.py diff --git a/bit_manipulation/gray_code_sequence.py b/bit_manipulation/gray_code_sequence.py new file mode 100644 index 000000000000..636578d89754 --- /dev/null +++ b/bit_manipulation/gray_code_sequence.py @@ -0,0 +1,94 @@ +def gray_code(bit_count: int) -> list: + """ + Takes in an integer n and returns a n-bit + gray code sequence + An n-bit gray code sequence is a sequence of 2^n + integers where: + + a) Every integer is between [0,2^n -1] inclusive + b) The sequence begins with 0 + c) An integer appears at most one times in the sequence + d)The binary representation of every pair of integers differ + by exactly one bit + e) The binary representation of first and last bit also + differ by exactly one bit + + >>> gray_code(2) + [0, 1, 3, 2] + + >>> gray_code(1) + [0, 1] + + >>> gray_code(3) + [0, 1, 3, 2, 6, 7, 5, 4] + + >>> gray_code(-1) + Traceback (most recent call last): + ... + ValueError: The given input must be positive + + >>> gray_code(10.6) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for <<: 'int' and 'float' + """ + + # bit count represents no. of bits in the gray code + if bit_count < 0: + raise ValueError("The given input must be positive") + + # get the generated string sequence + sequence = gray_code_sequence_string(bit_count) + # + # convert them to integers + for i in range(len(sequence)): + sequence[i] = int(sequence[i], 2) + + return sequence + + +def gray_code_sequence_string(bit_count: int) -> list: + """ + Will output the n-bit grey sequence as a + string of bits + + >>> gray_code_sequence_string(2) + ['00', '01', '11', '10'] + + >>> gray_code_sequence_string(1) + ['0', '1'] + """ + + # The approach is a recursive one + # Base case achieved when either n = 0 or n=1 + if bit_count == 0: + return ["0"] + + if bit_count == 1: + return ["0", "1"] + + seq_len = 1 << bit_count # defines the length of the sequence + # 1<< n is equivalent to 2^n + + # recursive answer will generate answer for n-1 bits + smaller_sequence = gray_code_sequence_string(bit_count - 1) + + sequence = [] + + # append 0 to first half of the smaller sequence generated + for i in range(seq_len // 2): + generated_no = "0" + smaller_sequence[i] + sequence.append(generated_no) + + # append 1 to second half ... start from the end of the list + for i in reversed(range(seq_len // 2)): + generated_no = "1" + smaller_sequence[i] + sequence.append(generated_no) + + return sequence + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From dd19d8120df2eeba8f3173f952187be4e7656144 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 2 Nov 2021 16:07:07 +0300 Subject: [PATCH 1718/2908] Uncomment code that has been commented due to slow execution affecting Travis (#5745) --- project_euler/problem_009/sol3.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/project_euler/problem_009/sol3.py b/project_euler/problem_009/sol3.py index 03aed4b70761..d299f821d4f6 100644 --- a/project_euler/problem_009/sol3.py +++ b/project_euler/problem_009/sol3.py @@ -24,9 +24,8 @@ def solution() -> int: 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 + >>> solution() + 31875000 """ return [ From 60ad32920d92a6095b28aa6952a759b40e5759c7 Mon Sep 17 00:00:00 2001 From: Leoriem-code <73761711+Leoriem-code@users.noreply.github.com> Date: Tue, 2 Nov 2021 22:17:57 +0100 Subject: [PATCH 1719/2908] fixed typo for codespell (#5753) --- project_euler/problem_045/sol1.py | 2 +- web_programming/get_user_tweets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project_euler/problem_045/sol1.py b/project_euler/problem_045/sol1.py index cb30a4d97339..cdf5c14cf362 100644 --- a/project_euler/problem_045/sol1.py +++ b/project_euler/problem_045/sol1.py @@ -43,7 +43,7 @@ def is_pentagonal(n: int) -> bool: def solution(start: int = 144) -> int: """ - Returns the next number which is traingular, pentagonal and hexagonal. + Returns the next number which is triangular, pentagonal and hexagonal. >>> solution(144) 1533776805 """ diff --git a/web_programming/get_user_tweets.py b/web_programming/get_user_tweets.py index 0f70201dc311..28cf85541dc4 100644 --- a/web_programming/get_user_tweets.py +++ b/web_programming/get_user_tweets.py @@ -32,7 +32,7 @@ def get_all_tweets(screen_name: str) -> None: while len(new_tweets) > 0: print(f"getting tweets before {oldest}") - # all subsiquent requests use the max_id param to prevent duplicates + # all subsequent requests use the max_id param to prevent duplicates new_tweets = api.user_timeline( screen_name=screen_name, count=200, max_id=oldest ) From 37bc6bdebf159d395b559dd7094934a337d59c8a Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 3 Nov 2021 00:28:09 +0300 Subject: [PATCH 1720/2908] Replace Travis CI mentions with GitHub actions (#5751) --- CONTRIBUTING.md | 4 ++-- project_euler/README.md | 4 ++-- project_euler/problem_012/sol2.py | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4df60ed3f296..c9525aa4080e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ __Improving comments__ and __writing proper tests__ are also highly welcome. 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. +Your contribution will be tested by our [automated testing on GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions 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 GitHub Actions 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. @@ -170,7 +170,7 @@ We want your work to be readable by others; therefore, we encourage you to note - 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. +- Do not update the README.md or DIRECTORY.md file which will be periodically autogenerated by our GitHub Actions 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 you to add [__Python type hints__](https://docs.python.org/3/library/typing.html) where it makes sense to do so. diff --git a/project_euler/README.md b/project_euler/README.md index c4c0a854472f..e3dc035eee5e 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -5,7 +5,7 @@ Problems are taken from https://projecteuler.net/, the Project Euler. [Problems 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/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. +The solutions will be checked by our [automated testing on GitHub Actions](https://github.com/TheAlgorithms/Python/actions) 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 GitHub Actions logs (under `slowest 10 durations`) and open a pull request to improve those solutions. ## Solution Guidelines @@ -28,7 +28,7 @@ Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Befo * 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/scripts/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 GitHub Actions build using this [script](https://github.com/TheAlgorithms/Python/blob/master/scripts/validate_solutions.py). Keeping in mind the above example of [Problem 1](https://projecteuler.net/problem=1): ```python def solution(limit: int = 1000): diff --git a/project_euler/problem_012/sol2.py b/project_euler/problem_012/sol2.py index 7578caa98938..1cc79fc4cd75 100644 --- a/project_euler/problem_012/sol2.py +++ b/project_euler/problem_012/sol2.py @@ -36,9 +36,8 @@ 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 + >>> solution() + 76576500 """ return next(i for i in triangle_number_generator() if count_divisors(i) > 500) From 85ee27687aee9723153bd2f32ed902fac7297757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C9=AA=C9=B4=E1=B4=80=CA=8F=E1=B4=80=E1=B4=8B=20P?= =?UTF-8?q?=E1=B4=80=C9=B4=E1=B4=85=E1=B4=87=CA=8F?= <87496159+Harpia-Vieillot@users.noreply.github.com> Date: Wed, 3 Nov 2021 16:02:17 +0530 Subject: [PATCH 1721/2908] Add Hexagonal Numbers in directory (#5696) Yesterday hexagonal_numbers.py was created. Added that file in this list(maths/series/hexagonal_numbers.py) From 0ea5c734e13e40fcefdea336bc5f735536f133a0 Mon Sep 17 00:00:00 2001 From: Souvik Ghosh <42302494+SouvikGhosh05@users.noreply.github.com> Date: Thu, 4 Nov 2021 01:54:50 +0530 Subject: [PATCH 1722/2908] sock_merchant.py: Matching socks by color (#5761) * Python file for finding number of pairs * updating DIRECTORY.md * fixed iterative_pair.py * further fixed with type casting * fixed naming conventions * further fixed with naming convention * documented done * build issue fixed * updating DIRECTORY.md * Revert "documented done" This reverts commit 3be15ca374f3ea3f01f725912dba59b939b058b5. * Update canny.py * Update test_digital_image_processing.py * Update sobel_filter.py * requirements.txt fixed * keras<2.7.0 * Update sock_merchant.py * doctest with black fixed * Update sock_merchant.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 6 +++++- maths/sock_merchant.py | 20 ++++++++++++++++++++ requirements.txt | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 maths/sock_merchant.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e70c0aab64a7..fd164c92e11c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -39,6 +39,7 @@ * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) * [Count 1S Brian Kernighan Method](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_1s_brian_kernighan_method.py) * [Count Number Of One Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_number_of_one_bits.py) + * [Gray Code Sequence](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/gray_code_sequence.py) * [Reverse Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py) @@ -63,7 +64,7 @@ * [Baconian Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/baconian_cipher.py) * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) - * [Base64 Encoding](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_encoding.py) + * [Base64](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64.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) * [Bifid](https://github.com/TheAlgorithms/Python/blob/master/ciphers/bifid.py) @@ -219,6 +220,7 @@ * 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) + * [Gabor Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gabor_filter.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) @@ -511,6 +513,7 @@ * [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) + * [Nevilles Method](https://github.com/TheAlgorithms/Python/blob/master/maths/nevilles_method.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) @@ -546,6 +549,7 @@ * [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) + * [Sock Merchant](https://github.com/TheAlgorithms/Python/blob/master/maths/sock_merchant.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) diff --git a/maths/sock_merchant.py b/maths/sock_merchant.py new file mode 100644 index 000000000000..304efec9ba5e --- /dev/null +++ b/maths/sock_merchant.py @@ -0,0 +1,20 @@ +from collections import Counter + + +def sock_merchant(colors: list[int]) -> int: + """ + >>> sock_merchant([10, 20, 20, 10, 10, 30, 50, 10, 20]) + 3 + >>> sock_merchant([1, 1, 3, 3]) + 2 + """ + return sum(socks_by_color // 2 for socks_by_color in Counter(colors).values()) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + colors = [int(x) for x in input("Enter socks by color :").rstrip().split()] + print(f"sock_merchant({colors}) = {sock_merchant(colors)}") diff --git a/requirements.txt b/requirements.txt index c28238a0774f..ef4e18043905 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ beautifulsoup4 fake_useragent -keras +keras<2.7.0 lxml matplotlib numpy From 765be4581e66f1e8e92f24606c243a8cd45e4d3c Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 3 Nov 2021 23:32:10 +0300 Subject: [PATCH 1723/2908] Improve Project Euler problem 012 solution 2 (#5760) * Improve solution * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- project_euler/problem_012/sol2.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/project_euler/problem_012/sol2.py b/project_euler/problem_012/sol2.py index 1cc79fc4cd75..380a9b74bb97 100644 --- a/project_euler/problem_012/sol2.py +++ b/project_euler/problem_012/sol2.py @@ -29,7 +29,18 @@ def triangle_number_generator(): 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) + divisors_count = 1 + i = 2 + while i * i <= n: + multiplicity = 0 + while n % i == 0: + n //= i + multiplicity += 1 + divisors_count *= multiplicity + 1 + i += 1 + if n > 1: + divisors_count *= 2 + return divisors_count def solution(): From 7954a3ae166db66ae6a43043c76417dda688a8e5 Mon Sep 17 00:00:00 2001 From: Andrew Grangaard Date: Wed, 3 Nov 2021 13:32:49 -0700 Subject: [PATCH 1724/2908] [mypy] Fixes typing errors in other/dpll (#5759) + As per usage examples, clause literals are a list of strings. + Note: symbols extracted from literals are expected to be exactly two characters. + self.literal boolean values are initialized to None, so must be optional + model values should be Booleans, but aren't guaranteed to be non-None in the code. + uses newer '... | None' annotation for Optional values + clauses are passed to the Formula initializer as both lists and sets, they are stored as lists. Returned clauses will always be lists. + use explicit tuple annotation from __future__ rather than using (..., ...) in return signatures + mapping returned by dpll_algorithm is optional per the documentation. --- other/davisb_putnamb_logemannb_loveland.py | 35 +++++++++++----------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/other/davisb_putnamb_logemannb_loveland.py b/other/davisb_putnamb_logemannb_loveland.py index 00068930b89e..031f0dbed404 100644 --- a/other/davisb_putnamb_logemannb_loveland.py +++ b/other/davisb_putnamb_logemannb_loveland.py @@ -11,6 +11,7 @@ from __future__ import annotations import random +from typing import Iterable class Clause: @@ -27,12 +28,12 @@ class Clause: True """ - def __init__(self, literals: list[int]) -> None: + def __init__(self, literals: list[str]) -> None: """ Represent the literals and an assignment in a clause." """ # Assign all literals to None initially - self.literals = {literal: None for literal in literals} + self.literals: dict[str, bool | None] = {literal: None for literal in literals} def __str__(self) -> str: """ @@ -52,7 +53,7 @@ def __len__(self) -> int: """ return len(self.literals) - def assign(self, model: dict[str, bool]) -> None: + def assign(self, model: dict[str, bool | None]) -> None: """ Assign values to literals of the clause as given by model. """ @@ -68,7 +69,7 @@ def assign(self, model: dict[str, bool]) -> None: value = not value self.literals[literal] = value - def evaluate(self, model: dict[str, bool]) -> bool: + def evaluate(self, model: dict[str, bool | None]) -> bool | None: """ Evaluates the clause with the assignments in model. This has the following steps: @@ -97,7 +98,7 @@ class Formula: {{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: + def __init__(self, clauses: Iterable[Clause]) -> None: """ Represent the number of clauses and the clauses themselves. """ @@ -139,14 +140,14 @@ def generate_formula() -> Formula: """ Randomly generate a formula. """ - clauses = set() + clauses: set[Clause] = set() no_of_clauses = random.randint(1, 10) while len(clauses) < no_of_clauses: clauses.add(generate_clause()) - return Formula(set(clauses)) + return Formula(clauses) -def generate_parameters(formula: Formula) -> (list[Clause], list[str]): +def generate_parameters(formula: Formula) -> tuple[list[Clause], list[str]]: """ Return the clauses and symbols from a formula. A symbol is the uncomplemented form of a literal. @@ -173,8 +174,8 @@ def generate_parameters(formula: Formula) -> (list[Clause], list[str]): def find_pure_symbols( - clauses: list[Clause], symbols: list[str], model: dict[str, bool] -) -> (list[str], dict[str, bool]): + clauses: list[Clause], symbols: list[str], model: dict[str, bool | None] +) -> tuple[list[str], dict[str, bool | None]]: """ Return pure symbols and their values to satisfy clause. Pure symbols are symbols in a formula that exist only @@ -198,11 +199,11 @@ def find_pure_symbols( {'A1': True, 'A2': False, 'A3': True, 'A5': False} """ pure_symbols = [] - assignment = dict() + assignment: dict[str, bool | None] = dict() literals = [] for clause in clauses: - if clause.evaluate(model) is True: + if clause.evaluate(model): continue for literal in clause.literals: literals.append(literal) @@ -225,8 +226,8 @@ def find_pure_symbols( def find_unit_clauses( - clauses: list[Clause], model: dict[str, bool] -) -> (list[str], dict[str, bool]): + clauses: list[Clause], model: dict[str, bool | None] +) -> tuple[list[str], dict[str, bool | None]]: """ Returns the unit symbols and their values to satisfy clause. Unit symbols are symbols in a formula that are: @@ -263,7 +264,7 @@ def find_unit_clauses( Ncount += 1 if Fcount == len(clause) - 1 and Ncount == 1: unit_symbols.append(sym) - assignment = dict() + assignment: dict[str, bool | None] = dict() for i in unit_symbols: symbol = i[:2] assignment[symbol] = len(i) == 2 @@ -273,8 +274,8 @@ def find_unit_clauses( def dpll_algorithm( - clauses: list[Clause], symbols: list[str], model: dict[str, bool] -) -> (bool, dict[str, bool]): + clauses: list[Clause], symbols: list[str], model: dict[str, bool | None] +) -> tuple[bool | None, dict[str, bool | None] | None]: """ Returns the model if the formula is satisfiable, else None This has the following steps: From 331fe6d3bc075ac2d91af766cf2bdf7df09f1281 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 3 Nov 2021 17:34:08 -0300 Subject: [PATCH 1725/2908] [mypy] Fix type annotations in `data_structures/binary_tree/lowest_common_ancestor.py` (#5757) * Fix type annotations in lowest_common_ancestor.py * Refactor line 53 in lowest_common_ancestor.py --- .../binary_tree/lowest_common_ancestor.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 2f1e893fcf99..651037703b95 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -3,7 +3,7 @@ from __future__ import annotations -import queue +from queue import Queue def swap(a: int, b: int) -> tuple[int, int]: @@ -37,7 +37,7 @@ 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]]: +) -> int: # u must be deeper in the tree than v if level[u] < level[v]: u, v = swap(u, v) @@ -50,7 +50,7 @@ def lowest_common_ancestor( return u # moving both nodes upwards till lca in found for i in range(18, -1, -1): - if parent[i][u] != 0 and parent[i][u] != parent[i][v]: + if parent[i][u] not in [0, parent[i][v]]: u, v = parent[i][u], parent[i][v] # returning longest common ancestor of u,v return parent[0][u] @@ -61,8 +61,8 @@ def breadth_first_search( level: list[int], parent: list[list[int]], max_node: int, - graph: dict[int, int], - root=1, + graph: dict[int, list[int]], + root: int = 1, ) -> tuple[list[int], list[list[int]]]: """ sets every nodes direct parent @@ -70,7 +70,7 @@ def breadth_first_search( calculates depth of each node from root node """ level[root] = 0 - q = queue.Queue(maxsize=max_node) + q: Queue[int] = Queue(maxsize=max_node) q.put(root) while q.qsize() != 0: u = q.get() @@ -88,7 +88,7 @@ def main() -> None: parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] # initializing with -1 which means every node is unvisited level = [-1 for _ in range(max_node + 10)] - graph = { + graph: dict[int, list[int]] = { 1: [2, 3, 4], 2: [5], 3: [6, 7], From 9655ec2a05f1ebbafb3c0ac5acf3d6278b498030 Mon Sep 17 00:00:00 2001 From: Divyesh Vishwakarma Date: Thu, 4 Nov 2021 16:18:57 +0530 Subject: [PATCH 1726/2908] Added newtons_second_law_of_motion.py (#5474) --- physics/newtons_second_law_of_motion.py | 81 +++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 physics/newtons_second_law_of_motion.py diff --git a/physics/newtons_second_law_of_motion.py b/physics/newtons_second_law_of_motion.py new file mode 100644 index 000000000000..cb53f8f6571f --- /dev/null +++ b/physics/newtons_second_law_of_motion.py @@ -0,0 +1,81 @@ +""" +Description : +Newton's second law of motion pertains to the behavior of objects for which +all existing forces are not balanced. +The second law states that the acceleration of an object is dependent upon two variables +- the net force acting upon the object and the mass of the object. +The acceleration of an object depends directly +upon the net force acting upon the object, +and inversely upon the mass of the object. +As the force acting upon an object is increased, +the acceleration of the object is increased. +As the mass of an object is increased, the acceleration of the object is decreased. +Source: https://www.physicsclassroom.com/class/newtlaws/Lesson-3/Newton-s-Second-Law +Formulation: Fnet = m • a +Diagrammatic Explanation: + Forces are unbalanced + | + | + | + V + There is acceleration + /\ + / \ + / \ + / \ + / \ + / \ + / \ + __________________ ____ ________________ + |The acceleration | |The acceleration | + |depends directly | |depends inversely | + |on the net Force | |upon the object's | + |_________________| |mass_______________| +Units: +1 Newton = 1 kg X meters / (seconds^2) +How to use? +Inputs: + ___________________________________________________ + |Name | Units | Type | + |-------------|-------------------------|-----------| + |mass | (in kgs) | float | + |-------------|-------------------------|-----------| + |acceleration | (in meters/(seconds^2)) | float | + |_____________|_________________________|___________| + +Output: + ___________________________________________________ + |Name | Units | Type | + |-------------|-------------------------|-----------| + |force | (in Newtons) | float | + |_____________|_________________________|___________| + +""" + + +def newtons_second_law_of_motion(mass: float, acceleration: float) -> float: + """ + >>> newtons_second_law_of_motion(10, 10) + 100 + >>> newtons_second_law_of_motion(2.0, 1) + 2.0 + """ + force = float() + try: + force = mass * acceleration + except Exception: + return -0.0 + return force + + +if __name__ == "__main__": + import doctest + + # run doctest + doctest.testmod() + + # demo + mass = 12.5 + acceleration = 10 + force = newtons_second_law_of_motion(mass, acceleration) + print("The force is ", force, "N") From 47dd31f4a1aaa371f3b822e178fd273c68f45962 Mon Sep 17 00:00:00 2001 From: Leoriem-code <73761711+Leoriem-code@users.noreply.github.com> Date: Thu, 4 Nov 2021 11:49:36 +0100 Subject: [PATCH 1727/2908] Add README files 1/7 (#5754) * Added 5 README files * corrected arithmetic_analysis README * Update audio_filters/README.md Co-authored-by: John Law * Update backtracking/README.md Co-authored-by: John Law * Update bit_manipulation/README.md Co-authored-by: John Law Co-authored-by: John Law --- arithmetic_analysis/README.md | 7 +++++++ audio_filters/README.md | 9 +++++++++ backtracking/README.md | 8 ++++++++ bit_manipulation/README.md | 17 +++++++++++------ boolean_algebra/README.md | 7 +++++++ 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 arithmetic_analysis/README.md create mode 100644 audio_filters/README.md create mode 100644 backtracking/README.md create mode 100644 boolean_algebra/README.md diff --git a/arithmetic_analysis/README.md b/arithmetic_analysis/README.md new file mode 100644 index 000000000000..45cf321eb6ad --- /dev/null +++ b/arithmetic_analysis/README.md @@ -0,0 +1,7 @@ +# Arithmetic analysis + +Arithmetic analysis is a branch of mathematics that deals with solving linear equations. + +* +* +* diff --git a/audio_filters/README.md b/audio_filters/README.md new file mode 100644 index 000000000000..4419bd8bdbf9 --- /dev/null +++ b/audio_filters/README.md @@ -0,0 +1,9 @@ +# Audio Filter + +Audio filters work on the frequency of an audio signal to attenuate unwanted frequency and amplify wanted ones. +They are used within anything related to sound, whether it is radio communication or a hi-fi system. + +* +* +* +* diff --git a/backtracking/README.md b/backtracking/README.md new file mode 100644 index 000000000000..d4975dfb5ad7 --- /dev/null +++ b/backtracking/README.md @@ -0,0 +1,8 @@ +# Backtracking + +Backtracking is a way to speed up the search process by removing candidates when they can't be the solution of a problem. + +* +* +* +* diff --git a/bit_manipulation/README.md b/bit_manipulation/README.md index e5f82a270e28..3f5e028beb8e 100644 --- a/bit_manipulation/README.md +++ b/bit_manipulation/README.md @@ -1,6 +1,11 @@ -* 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 +# Bit manipulation + +Bit manipulation is the act of manipulating bits to detect errors (hamming code), encrypts and decrypts messages (more on that in the 'ciphers' folder) or just do anything at the lowest level of your computer. + +* +* +* +* +* +* +* diff --git a/boolean_algebra/README.md b/boolean_algebra/README.md new file mode 100644 index 000000000000..45969c855f9c --- /dev/null +++ b/boolean_algebra/README.md @@ -0,0 +1,7 @@ +# Boolean Algebra + +Boolean algebra is used to do arithmetic with bits of values True (1) or False (0). +There are three basic operations: 'and', 'or' and 'not'. + +* +* From 3815a97575a2ab3209fd82ac4077942020c2d9bd Mon Sep 17 00:00:00 2001 From: Sailesh Shrestha <34860977+werewolf-65@users.noreply.github.com> Date: Thu, 4 Nov 2021 21:03:38 +0545 Subject: [PATCH 1728/2908] Add all_construct dynamic programming implementation (#5626) * Add all_construct dynamic programming implementation * all_construct: remove the main function * all_construct: Add type hints * all_construct: changed map to list comprehension,fix mutable default arguments * all_construct: fixed type hints * all_construct: cleaner code for initializing word_bank argument * all_construct: added an import for annotations * all_construct: added None in the argument with word_bank * all_construct: fixed a type hint * all_construct: Fixed some more type hints --- dynamic_programming/all_construct.py | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 dynamic_programming/all_construct.py diff --git a/dynamic_programming/all_construct.py b/dynamic_programming/all_construct.py new file mode 100644 index 000000000000..5ffed2caa182 --- /dev/null +++ b/dynamic_programming/all_construct.py @@ -0,0 +1,58 @@ +""" +Program to list all the ways a target string can be +constructed from the given list of substrings +""" +from __future__ import annotations + + +def all_construct(target: str, word_bank: list[str] | None = None) -> list[list[str]]: + """ + returns the list containing all the possible + combinations a string(target) can be constructed from + the given list of substrings(word_bank) + >>> all_construct("hello", ["he", "l", "o"]) + [['he', 'l', 'l', 'o']] + >>> all_construct("purple",["purp","p","ur","le","purpl"]) + [['purp', 'le'], ['p', 'ur', 'p', 'le']] + """ + + word_bank = word_bank or [] + # create a table + table_size: int = len(target) + 1 + + table: list[list[list[str]]] = [] + for i in range(table_size): + table.append([]) + # seed value + table[0] = [[]] # because empty string has empty combination + + # iterate through the indices + for i in range(table_size): + # condition + if table[i] != []: + for word in word_bank: + # slice condition + if target[i : i + len(word)] == word: + new_combinations: list[list[str]] = [ + [word] + way for way in table[i] + ] + # adds the word to every combination the current position holds + # now,push that combination to the table[i+len(word)] + table[i + len(word)] += new_combinations + + # combinations are in reverse order so reverse for better output + for combination in table[len(target)]: + combination.reverse() + + return table[len(target)] + + +if __name__ == "__main__": + print(all_construct("jwajalapa", ["jwa", "j", "w", "a", "la", "lapa"])) + print(all_construct("rajamati", ["s", "raj", "amat", "raja", "ma", "i", "t"])) + print( + all_construct( + "hexagonosaurus", + ["h", "ex", "hex", "ag", "ago", "ru", "auru", "rus", "go", "no", "o", "s"], + ) + ) From b6eb448e63a7eb8b145a600c368419e77872f134 Mon Sep 17 00:00:00 2001 From: Jaydeep Das Date: Thu, 4 Nov 2021 21:06:22 +0530 Subject: [PATCH 1729/2908] Added reddit.py to get data from reddit (#5698) * Rewritten reddit.py * Removed logging module import * Fixed minor bug which was causing extreme rate limiting * Update reddit.py * Update reddit.py * Update reddit.py Co-authored-by: Christian Clauss --- web_programming/reddit.py | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 web_programming/reddit.py diff --git a/web_programming/reddit.py b/web_programming/reddit.py new file mode 100644 index 000000000000..672109f1399d --- /dev/null +++ b/web_programming/reddit.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import requests + +valid_terms = set( + """approved_at_utc approved_by author_flair_background_color +author_flair_css_class author_flair_richtext author_flair_template_id author_fullname +author_premium can_mod_post category clicked content_categories created_utc downs +edited gilded gildings hidden hide_score is_created_from_ads_ui is_meta +is_original_content is_reddit_media_domain is_video link_flair_css_class +link_flair_richtext link_flair_text link_flair_text_color media_embed mod_reason_title +name permalink pwls quarantine saved score secure_media secure_media_embed selftext +subreddit subreddit_name_prefixed subreddit_type thumbnail title top_awarded_type +total_awards_received ups upvote_ratio url user_reports""".split() +) + + +def get_subreddit_data( + subreddit: str, limit: int = 1, age: str = "new", wanted_data: list | None = None +) -> dict: + """ + subreddit : Subreddit to query + limit : Number of posts to fetch + age : ["new", "top", "hot"] + wanted_data : Get only the required data in the list + + >>> pass + """ + wanted_data = wanted_data or [] + if invalid_search_terms := ", ".join(sorted(set(wanted_data) - valid_terms)): + raise ValueError(f"Invalid search term: {invalid_search_terms}") + response = requests.get( + f"https://reddit.com/r/%7Bsubreddit%7D/%7Bage%7D.json?limit={limit}", + headers={"User-agent": "A random string"}, + ) + if response.status_code == 429: + raise requests.HTTPError + + data = response.json() + if not wanted_data: + return {id_: data["data"]["children"][id_] for id_ in range(limit)} + + data_dict = {} + for id_ in range(limit): + data_dict[id_] = { + item: data["data"]["children"][id_]["data"][item] for item in wanted_data + } + return data_dict + + +if __name__ == "__main__": + # If you get Error 429, that means you are rate limited.Try after some time + print(get_subreddit_data("learnpython", wanted_data=["title", "url", "selftext"])) From e835e9685617198095ca44f5d9fda7dc02a3ec83 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 4 Nov 2021 18:37:47 +0300 Subject: [PATCH 1730/2908] Improve Project Euler problem 014 solution 1 (#5747) * Improve solution * Uncomment code that has been commented due to slow execution affecting Travis * Fix --- project_euler/problem_014/sol1.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/project_euler/problem_014/sol1.py b/project_euler/problem_014/sol1.py index 43aa4e726af2..0333495908de 100644 --- a/project_euler/problem_014/sol1.py +++ b/project_euler/problem_014/sol1.py @@ -25,9 +25,8 @@ def solution(n: int = 1000000) -> int: 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) - # 837799 + >>> solution(1000000) + 837799 >>> solution(200) 171 >>> solution(5000) @@ -35,14 +34,18 @@ def solution(n: int = 1000000) -> int: >>> solution(15000) 13255 """ - largest_number = 0 - pre_counter = 0 + largest_number = 1 + pre_counter = 1 + counters = {1: 1} - for input1 in range(n): - counter = 1 + for input1 in range(2, n): + counter = 0 number = input1 - while number > 1: + while True: + if number in counters: + counter += counters[number] + break if number % 2 == 0: number //= 2 counter += 1 @@ -50,6 +53,9 @@ def solution(n: int = 1000000) -> int: number = (3 * number) + 1 counter += 1 + if input1 not in counters: + counters[input1] = counter + if counter > pre_counter: largest_number = input1 pre_counter = counter From 7a605766fe7fe79a00ba1f30447877be4b77a6f2 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Thu, 4 Nov 2021 12:38:43 -0300 Subject: [PATCH 1731/2908] [mypy] Fix type annotations in `data_structures/binary_tree/red_black_tree.py` (#5739) * [mypy] Fix type annotations in red_black_tree.py * Remove blank lines * Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 121 ++++++++++-------- 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index e27757f20062..35517f307fe1 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -51,6 +51,8 @@ def rotate_left(self) -> RedBlackTree: """ parent = self.parent right = self.right + if right is None: + return self self.right = right.left if self.right: self.right.parent = self @@ -69,6 +71,8 @@ def rotate_right(self) -> RedBlackTree: returns the new root to this subtree. Performing one rotation can be done in O(1). """ + if self.left is None: + return self parent = self.parent left = self.left self.left = left.right @@ -123,23 +127,30 @@ def _insert_repair(self) -> None: if color(uncle) == 0: if self.is_left() and self.parent.is_right(): self.parent.rotate_right() - self.right._insert_repair() + if self.right: + self.right._insert_repair() elif self.is_right() and self.parent.is_left(): self.parent.rotate_left() - self.left._insert_repair() + if self.left: + self.left._insert_repair() elif self.is_left(): - self.grandparent.rotate_right() - self.parent.color = 0 - self.parent.right.color = 1 + if self.grandparent: + self.grandparent.rotate_right() + self.parent.color = 0 + if self.parent.right: + self.parent.right.color = 1 else: - self.grandparent.rotate_left() - self.parent.color = 0 - self.parent.left.color = 1 + if self.grandparent: + self.grandparent.rotate_left() + self.parent.color = 0 + if self.parent.left: + self.parent.left.color = 1 else: self.parent.color = 0 - uncle.color = 0 - self.grandparent.color = 1 - self.grandparent._insert_repair() + if uncle and self.grandparent: + uncle.color = 0 + self.grandparent.color = 1 + self.grandparent._insert_repair() def remove(self, label: int) -> RedBlackTree: """Remove label from this tree.""" @@ -149,8 +160,9 @@ def remove(self, label: int) -> RedBlackTree: # 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) + if value is not None: + self.label = value + self.left.remove(value) else: # This node has at most one non-None child, so we don't # need to replace @@ -160,10 +172,11 @@ def remove(self, label: int) -> RedBlackTree: # 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 + if self.parent: + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None else: # The node is black if child is None: @@ -188,7 +201,7 @@ def remove(self, label: int) -> RedBlackTree: self.left.parent = self if self.right: self.right.parent = self - elif self.label > label: + elif self.label is not None and self.label > label: if self.left: self.left.remove(label) else: @@ -198,6 +211,13 @@ def remove(self, label: int) -> RedBlackTree: def _remove_repair(self) -> None: """Repair the coloring of the tree that may have been messed up.""" + if ( + self.parent is None + or self.sibling is None + or self.parent.sibling is None + or self.grandparent is None + ): + return if color(self.sibling) == 1: self.sibling.color = 0 self.parent.color = 1 @@ -231,7 +251,8 @@ def _remove_repair(self) -> None: ): self.sibling.rotate_right() self.sibling.color = 0 - self.sibling.right.color = 1 + if self.sibling.right: + self.sibling.right.color = 1 if ( self.is_right() and color(self.sibling) == 0 @@ -240,7 +261,8 @@ def _remove_repair(self) -> None: ): self.sibling.rotate_left() self.sibling.color = 0 - self.sibling.left.color = 1 + if self.sibling.left: + self.sibling.left.color = 1 if ( self.is_left() and color(self.sibling) == 0 @@ -275,21 +297,17 @@ def check_color_properties(self) -> bool: """ # 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") @@ -297,7 +315,7 @@ def check_color_properties(self) -> bool: # All properties were met return True - def check_coloring(self) -> None: + def check_coloring(self) -> bool: """A helper function to recursively check Property 4 of a Red-Black Tree. See check_color_properties for more info. """ @@ -310,12 +328,12 @@ def check_coloring(self) -> None: return False return True - def black_height(self) -> int: + def black_height(self) -> int | None: """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 self is None or self.left is None or self.right is None: # If we're already at a leaf, there is no path return 1 left = RedBlackTree.black_height(self.left) @@ -332,21 +350,21 @@ def black_height(self) -> int: # Here are functions which are general to all binary search trees - def __contains__(self, label) -> bool: + def __contains__(self, label: int) -> 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: int) -> RedBlackTree: + def search(self, label: int) -> RedBlackTree | None: """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: + elif self.label is not None and label > self.label: if self.right is None: return None else: @@ -357,12 +375,12 @@ def search(self, label: int) -> RedBlackTree: else: return self.left.search(label) - def floor(self, label: int) -> int: + def floor(self, label: int) -> int | None: """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: + elif self.label is not None and self.label > label: if self.left: return self.left.floor(label) else: @@ -374,13 +392,13 @@ def floor(self, label: int) -> int: return attempt return self.label - def ceil(self, label: int) -> int: + def ceil(self, label: int) -> int | None: """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: + elif self.label is not None and self.label < label: if self.right: return self.right.ceil(label) else: @@ -392,7 +410,7 @@ def ceil(self, label: int) -> int: return attempt return self.label - def get_max(self) -> int: + def get_max(self) -> int | None: """Returns the largest element in this tree. This method is guaranteed to run in O(log(n)) time. """ @@ -402,7 +420,7 @@ def get_max(self) -> int: else: return self.label - def get_min(self) -> int: + def get_min(self) -> int | None: """Returns the smallest element in this tree. This method is guaranteed to run in O(log(n)) time. """ @@ -413,7 +431,7 @@ def get_min(self) -> int: return self.label @property - def grandparent(self) -> RedBlackTree: + def grandparent(self) -> RedBlackTree | None: """Get the current node's grandparent, or None if it doesn't exist.""" if self.parent is None: return None @@ -421,7 +439,7 @@ def grandparent(self) -> RedBlackTree: return self.parent.parent @property - def sibling(self) -> RedBlackTree: + def sibling(self) -> RedBlackTree | None: """Get the current node's sibling, or None if it doesn't exist.""" if self.parent is None: return None @@ -432,11 +450,15 @@ def sibling(self) -> RedBlackTree: 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 + if self.parent is None: + return False + return self.parent.left is self.parent.left is 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 + if self.parent is None: + return False + return self.parent.right is self def __bool__(self) -> bool: return True @@ -452,21 +474,21 @@ def __len__(self) -> int: ln += len(self.right) return ln - def preorder_traverse(self) -> Iterator[int]: + def preorder_traverse(self) -> Iterator[int | None]: 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) -> Iterator[int]: + def inorder_traverse(self) -> Iterator[int | None]: 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) -> Iterator[int]: + def postorder_traverse(self) -> Iterator[int | None]: if self.left: yield from self.left.postorder_traverse() if self.right: @@ -488,15 +510,17 @@ def __repr__(self) -> str: indent=1, ) - def __eq__(self, other) -> bool: + def __eq__(self, other: object) -> bool: """Test if two trees are equal.""" + if not isinstance(other, RedBlackTree): + return NotImplemented if self.label == other.label: return self.left == other.left and self.right == other.right else: return False -def color(node) -> int: +def color(node: RedBlackTree | None) -> int: """Returns the color of a node, allowing for None leaves.""" if node is None: return 0 @@ -699,19 +723,12 @@ def main() -> None: >>> 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() From 729aaf64275c61b8bc864ef9138eed078dea9cb2 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 4 Nov 2021 19:01:21 +0300 Subject: [PATCH 1732/2908] Improve Project Euler problem 014 solution 2 (#5744) * Improve solution * Uncomment code that has been commented due to slow execution affecting Travis * Fix * scikit-fuzzy is causing broken builds * fuzz = None * Update fuzzy_operations.py Co-authored-by: Christian Clauss --- fuzzy_logic/fuzzy_operations.py | 9 +++++++-- project_euler/problem_014/sol2.py | 22 ++++++++++++---------- requirements.txt | 2 +- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py index 0f573f158663..fbaca9421327 100644 --- a/fuzzy_logic/fuzzy_operations.py +++ b/fuzzy_logic/fuzzy_operations.py @@ -1,4 +1,5 @@ -"""README, Author - Jigyasa Gandhi(mailto:jigsgandhi97@gmail.com) +""" +README, Author - Jigyasa Gandhi(mailto:jigsgandhi97@gmail.com) Requirements: - scikit-fuzzy - numpy @@ -7,7 +8,11 @@ - 3.5 """ import numpy as np -import skfuzzy as fuzz + +try: + import skfuzzy as fuzz +except ImportError: + fuzz = None if __name__ == "__main__": # Create universe of discourse in Python using linspace () diff --git a/project_euler/problem_014/sol2.py b/project_euler/problem_014/sol2.py index 0a58f8d9a05a..d2a1d9f0e468 100644 --- a/project_euler/problem_014/sol2.py +++ b/project_euler/problem_014/sol2.py @@ -27,25 +27,27 @@ """ from __future__ import annotations +COLLATZ_SEQUENCE_LENGTHS = {1: 1} + def collatz_sequence_length(n: int) -> int: """Returns the Collatz sequence length for n.""" - sequence_length = 1 - while n != 1: - if n % 2 == 0: - n //= 2 - else: - n = 3 * n + 1 - sequence_length += 1 + if n in COLLATZ_SEQUENCE_LENGTHS: + return COLLATZ_SEQUENCE_LENGTHS[n] + if n % 2 == 0: + next_n = n // 2 + else: + next_n = 3 * n + 1 + sequence_length = collatz_sequence_length(next_n) + 1 + COLLATZ_SEQUENCE_LENGTHS[n] = sequence_length return sequence_length 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) - # 837799 + >>> solution(1000000) + 837799 >>> solution(200) 171 >>> solution(5000) diff --git a/requirements.txt b/requirements.txt index ef4e18043905..e01d87cffabe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pandas pillow qiskit requests -scikit-fuzzy +# scikit-fuzzy # Causing broken builds sklearn statsmodels sympy From 7390777f9aa4d60fcd87dc02b7628e61f92edc12 Mon Sep 17 00:00:00 2001 From: Snimerjot Singh Date: Thu, 4 Nov 2021 21:38:18 +0530 Subject: [PATCH 1733/2908] Added 2 shaped in volume.py (#5560) --- maths/volume.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/maths/volume.py b/maths/volume.py index fd24aa9eef54..b11995bab917 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -153,6 +153,21 @@ def vol_sphere(radius: float) -> float: return 4 / 3 * pi * pow(radius, 3) +def vol_hemisphere(radius: float): + """Calculate the volume of a hemisphere + Wikipedia reference: https://en.wikipedia.org/wiki/Hemisphere + Other references: https://www.cuemath.com/geometry/hemisphere + :return 2/3 * pi * radius^3 + + >>> vol_hemisphere(1) + 2.0943951023931953 + + >>> vol_hemisphere(7) + 718.3775201208659 + """ + return 2 / 3 * pi * pow(radius, 3) + + def vol_circular_cylinder(radius: float, height: float) -> float: """Calculate the Volume of a Circular Cylinder. Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder @@ -166,6 +181,26 @@ def vol_circular_cylinder(radius: float, height: float) -> float: return pi * pow(radius, 2) * height +def vol_conical_frustum(height: float, radius_1: float, radius_2: float): + """Calculate the Volume of a Conical Frustum. + Wikipedia reference: https://en.wikipedia.org/wiki/Frustum + :return 1/3 * pi * height * (radius_1^2 + radius_top^2 + radius_1 * radius_2) + + >>> vol_conical_frustum(45, 7, 28) + 48490.482608158454 + + >>> vol_conical_frustum(1, 1, 2) + 7.330382858376184 + """ + return ( + 1 + / 3 + * pi + * height + * (pow(radius_1, 2) + pow(radius_2, 2) + radius_1 * radius_2) + ) + + def main(): """Print the Results of Various Volume Calculations.""" print("Volumes:") @@ -176,7 +211,9 @@ def main(): 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("Hemisphere: " + str(vol_hemisphere(2))) # ~= 16.75 print("Circular Cylinder: " + str(vol_circular_cylinder(2, 2))) # ~= 25.1 + print("Conical Frustum: " + str(vol_conical_frustum(2, 2, 4))) # ~= 58.6 print("Spherical cap: " + str(vol_spherical_cap(1, 2))) # ~= 5.24 print("Spheres intersetion: " + str(vol_spheres_intersect(2, 2, 1))) # ~= 21.21 From dbddac74d3dbc0dc7c3d710ac3b42839685160a6 Mon Sep 17 00:00:00 2001 From: Boris Galochkin Date: Thu, 4 Nov 2021 19:51:31 +0300 Subject: [PATCH 1734/2908] Fix `graphs/finding_bridges.py` algorithm + doctests (#5765) * Fix finding_bridges algorithms + tests * update type hints * Better, more obvious condition fix * fix prev commit + more tests * Short explanation + url * Update finding_bridges.py Co-authored-by: Christian Clauss --- graphs/finding_bridges.py | 98 +++++++++++++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 20 deletions(-) diff --git a/graphs/finding_bridges.py b/graphs/finding_bridges.py index 6555dd7bc29e..a877a97489be 100644 --- a/graphs/finding_bridges.py +++ b/graphs/finding_bridges.py @@ -1,5 +1,77 @@ -# Finding Bridges in Undirected Graph -def computeBridges(graph): +""" +An edge is a bridge if, after removing it count of connected components in graph will +be increased by one. Bridges represent vulnerabilities in a connected network and are +useful for designing reliable networks. For example, in a wired computer network, an +articulation point indicates the critical computers and a bridge indicates the critical +wires or connections. + +For more details, refer this article: +https://www.geeksforgeeks.org/bridge-in-a-graph/ +""" + + +def __get_demo_graph(index): + return [ + { + 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], + }, + { + 0: [6], + 1: [9], + 2: [4, 5], + 3: [4], + 4: [2, 3], + 5: [2], + 6: [0, 7], + 7: [6], + 8: [], + 9: [1], + }, + { + 0: [4], + 1: [6], + 2: [], + 3: [5, 6, 7], + 4: [0, 6], + 5: [3, 8, 9], + 6: [1, 3, 4, 7], + 7: [3, 6, 8, 9], + 8: [5, 7], + 9: [5, 7], + }, + { + 0: [1, 3], + 1: [0, 2, 4], + 2: [1, 3, 4], + 3: [0, 2, 4], + 4: [1, 2, 3], + }, + ][index] + + +def compute_bridges(graph: dict[int, list[int]]) -> list[tuple[int, int]]: + """ + Return the list of undirected graph bridges [(a1, b1), ..., (ak, bk)]; ai <= bi + >>> compute_bridges(__get_demo_graph(0)) + [(3, 4), (2, 3), (2, 5)] + >>> compute_bridges(__get_demo_graph(1)) + [(6, 7), (0, 6), (1, 9), (3, 4), (2, 4), (2, 5)] + >>> compute_bridges(__get_demo_graph(2)) + [(1, 6), (4, 6), (0, 4)] + >>> compute_bridges(__get_demo_graph(3)) + [] + >>> compute_bridges({}) + [] + """ + id = 0 n = len(graph) # No of vertices in graph low = [0] * n @@ -15,28 +87,14 @@ def dfs(at, parent, bridges, id): elif not visited[to]: dfs(to, at, bridges, id) low[at] = min(low[at], low[to]) - if at < low[to]: - bridges.append([at, to]) + if id <= low[to]: + bridges.append((at, to) if at < to else (to, at)) else: # This edge is a back edge and cannot be a bridge - low[at] = min(low[at], to) + low[at] = min(low[at], low[to]) bridges = [] for i in range(n): if not visited[i]: dfs(i, -1, bridges, id) - print(bridges) - - -graph = { - 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(graph) + return bridges From 6b2b476f8633504e4c032dc948ab7dda04cd3d3f Mon Sep 17 00:00:00 2001 From: Leoriem-code <73761711+Leoriem-code@users.noreply.github.com> Date: Fri, 5 Nov 2021 06:06:37 +0100 Subject: [PATCH 1735/2908] fix typo on line 126 (#5768) --- scheduling/shortest_job_first.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index 17409108a34e..9372e9dbc3f4 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -123,7 +123,7 @@ def calculate_average_times( 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)) + print("Enter the arrival time and burst 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) From 48960268a2629fe0549ce7a67619852be91baa5f Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Sat, 6 Nov 2021 01:13:52 +0530 Subject: [PATCH 1736/2908] Improve Project Euler Problem 10 Sol-1 (#5773) * Improve Project Euler Problem 10 Sol-1 * Name correction * psf/black formatting * More formatting --- project_euler/problem_010/sol1.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/project_euler/problem_010/sol1.py b/project_euler/problem_010/sol1.py index bd49b3523c97..e060761eecab 100644 --- a/project_euler/problem_010/sol1.py +++ b/project_euler/problem_010/sol1.py @@ -28,11 +28,11 @@ def is_prime(n: int) -> bool: True """ - for i in range(2, int(sqrt(n)) + 1): - if n % i == 0: - return False - - return True + if 1 < n < 4: + return True + elif n < 2 or not n % 2: + return False + return not any(not n % i for i in range(3, int(sqrt(n) + 1), 2)) def solution(n: int = 2000000) -> int: @@ -49,16 +49,7 @@ def solution(n: int = 2000000) -> int: 10 """ - 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 + return sum(num for num in range(3, n, 2) if is_prime(num)) + 2 if n > 2 else 0 if __name__ == "__main__": From 1a43c92c77790632e7958df9c1048ed77aa36f68 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Fri, 5 Nov 2021 22:44:24 +0300 Subject: [PATCH 1737/2908] Improve Project Euler problem 043 solution 1 (#5772) * updating DIRECTORY.md * Fix typo * Improve solution Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 +++ project_euler/problem_043/sol1.py | 21 ++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index fd164c92e11c..32ca8cd3b256 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -253,6 +253,7 @@ ## Dynamic Programming * [Abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) + * [All Construct](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/all_construct.py) * [Bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) * [Catalan Numbers](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/catalan_numbers.py) * [Climbing Stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) @@ -612,6 +613,7 @@ ## Physics * [N Body Simulation](https://github.com/TheAlgorithms/Python/blob/master/physics/n_body_simulation.py) + * [Newtons Second Law Of Motion](https://github.com/TheAlgorithms/Python/blob/master/physics/newtons_second_law_of_motion.py) ## Project Euler * Problem 001 @@ -1018,6 +1020,7 @@ * [Nasa Data](https://github.com/TheAlgorithms/Python/blob/master/web_programming/nasa_data.py) * [Random Anime Character](https://github.com/TheAlgorithms/Python/blob/master/web_programming/random_anime_character.py) * [Recaptcha Verification](https://github.com/TheAlgorithms/Python/blob/master/web_programming/recaptcha_verification.py) + * [Reddit](https://github.com/TheAlgorithms/Python/blob/master/web_programming/reddit.py) * [Search Books By Isbn](https://github.com/TheAlgorithms/Python/blob/master/web_programming/search_books_by_isbn.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/project_euler/problem_043/sol1.py b/project_euler/problem_043/sol1.py index 1febe4a4d37f..c533f40da9c9 100644 --- a/project_euler/problem_043/sol1.py +++ b/project_euler/problem_043/sol1.py @@ -33,9 +33,18 @@ def is_substring_divisible(num: tuple) -> bool: >>> is_substring_divisible((1, 4, 0, 6, 3, 5, 7, 2, 8, 9)) True """ - tests = [2, 3, 5, 7, 11, 13, 17] + if num[3] % 2 != 0: + return False + + if (num[2] + num[3] + num[4]) % 3 != 0: + return False + + if num[5] % 5 != 0: + return False + + tests = [7, 11, 13, 17] for i, test in enumerate(tests): - if (num[i + 1] * 100 + num[i + 2] * 10 + num[i + 3]) % test != 0: + if (num[i + 4] * 100 + num[i + 5] * 10 + num[i + 6]) % test != 0: return False return True @@ -43,17 +52,15 @@ def is_substring_divisible(num: tuple) -> bool: def solution(n: int = 10) -> int: """ Returns the sum of all pandigital numbers which pass the - divisiility tests. + divisibility tests. >>> solution(10) 16695334890 """ - list_nums = [ + return sum( int("".join(map(str, num))) for num in permutations(range(n)) if is_substring_divisible(num) - ] - - return sum(list_nums) + ) if __name__ == "__main__": From e7381b513b526e2f3ca134022389832778bdf080 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Fri, 5 Nov 2021 16:45:37 -0300 Subject: [PATCH 1738/2908] [mypy] Fix type annotations in `data_structures/stacks/next_greater_element.py` (#5763) * Fix type annotations in next_greater_element.py * Refactor next_greater_element.py --- .../stacks/next_greater_element.py | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/data_structures/stacks/next_greater_element.py b/data_structures/stacks/next_greater_element.py index d8c7ed17317b..5bab7c609b67 100644 --- a/data_structures/stacks/next_greater_element.py +++ b/data_structures/stacks/next_greater_element.py @@ -1,8 +1,10 @@ +from __future__ import annotations + 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: list) -> list: +def next_greatest_element_slow(arr: list[float]) -> list[float]: """ 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 @@ -10,10 +12,13 @@ def next_greatest_element_slow(arr: list) -> list: >>> next_greatest_element_slow(arr) == expect True """ + result = [] - for i in range(0, len(arr), 1): - next = -1 - for j in range(i + 1, len(arr), 1): + arr_size = len(arr) + + for i in range(arr_size): + next: float = -1 + for j in range(i + 1, arr_size): if arr[i] < arr[j]: next = arr[j] break @@ -21,7 +26,7 @@ def next_greatest_element_slow(arr: list) -> list: return result -def next_greatest_element_fast(arr: list) -> list: +def next_greatest_element_fast(arr: list[float]) -> list[float]: """ Like next_greatest_element_slow() but changes the loops to use enumerate() instead of range(len()) for the outer loop and @@ -31,7 +36,7 @@ def next_greatest_element_fast(arr: list) -> list: """ result = [] for i, outer in enumerate(arr): - next = -1 + next: float = -1 for inner in arr[i + 1 :]: if outer < inner: next = inner @@ -40,7 +45,7 @@ def next_greatest_element_fast(arr: list) -> list: return result -def next_greatest_element(arr: list) -> list: +def next_greatest_element(arr: list[float]) -> list[float]: """ 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 @@ -53,21 +58,19 @@ def next_greatest_element(arr: list) -> list: >>> next_greatest_element(arr) == expect True """ - stack = [] - result = [-1] * len(arr) + arr_size = len(arr) + stack: list[float] = [] + result: list[float] = [-1] * arr_size - for index in reversed(range(len(arr))): - if len(stack): + for index in reversed(range(arr_size)): + if stack: while stack[-1] <= arr[index]: stack.pop() - if len(stack) == 0: + if not stack: break - - if len(stack) != 0: + if stack: result[index] = stack[-1] - stack.append(arr[index]) - return result From 8ac86f2ce559e323064b09a54267d35cf3c51ec6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 6 Nov 2021 13:58:15 +0100 Subject: [PATCH 1739/2908] mypy: Exclude only 20 files that are still failing (#5608) * DRAFT: Run a mypy reality check Let's see what is required to finish #4052 * mypy --ignore-missing-imports --install-types --non-interactive * Check our progress... * Update build.yml * Update build.yml * Update build.yml * Update build.yml * mypy --exclude 20 files * --exclude with no `=` * Update build.yml * 558 character regex!!! * With quotes * mypy.ini: mega exclude * Update mypy.ini * Update build.yml * Update mypy.ini * Update build.yml * Update mypy.ini * .py --> .p* * Escape the dots!: `.` --> `\.` * Remove the comment * Leading slash * Update mypy.ini Co-authored-by: Dylan Buchi Co-authored-by: Dylan Buchi --- .github/workflows/build.yml | 2 +- mypy.ini | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f710e1e0ed54..e5f8d6b39a7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: run: | python -m pip install --upgrade pip setuptools six wheel python -m pip install mypy pytest-cov -r requirements.txt - - run: mypy --install-types --non-interactive . + - run: mypy . # See `mypy.ini` for configuration settings. - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/mypy.ini b/mypy.ini index ba552f878e30..1a2282c44846 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,6 @@ [mypy] ignore_missing_imports = True +install_types = True +non_interactive = True +exclude = (data_structures/stacks/next_greater_element.py|graphs/boruvka.py|graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/finding_bridges.py|graphs/greedy_min_vertex_cover.py|graphs/random_graph_generator.py|maths/average_mode.py|maths/gamma_recursive.py|maths/proth_number.py|maths/series/geometric_series.py|maths/series/p_series.py|matrix_operation.py|other/fischer_yates_shuffle.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) -; FIXME: #4052 fix mypy errors in the exclude directories and remove them below -exclude = (data_structures|graphs|maths|matrix|other|searches)/$ From accee50cde961af501bd0c6f424cd07dc5d63269 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Sun, 7 Nov 2021 15:44:42 +0530 Subject: [PATCH 1740/2908] [mypy] Fix `other/fischer_yates_shuffle.py` (#5789) * [mypy] Fix `other/fischer_yates_shuffle.py` * Update mypy.ini --- mypy.ini | 2 +- other/fischer_yates_shuffle.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mypy.ini b/mypy.ini index 1a2282c44846..123ffae851a5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,5 +2,5 @@ ignore_missing_imports = True install_types = True non_interactive = True -exclude = (data_structures/stacks/next_greater_element.py|graphs/boruvka.py|graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/finding_bridges.py|graphs/greedy_min_vertex_cover.py|graphs/random_graph_generator.py|maths/average_mode.py|maths/gamma_recursive.py|maths/proth_number.py|maths/series/geometric_series.py|maths/series/p_series.py|matrix_operation.py|other/fischer_yates_shuffle.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) +exclude = (data_structures/stacks/next_greater_element.py|graphs/boruvka.py|graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/finding_bridges.py|graphs/greedy_min_vertex_cover.py|graphs/random_graph_generator.py|maths/average_mode.py|maths/gamma_recursive.py|maths/proth_number.py|maths/series/geometric_series.py|maths/series/p_series.py|matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index 035fcb482380..fa2f4dce9db0 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -6,14 +6,15 @@ wikipedia/Fischer-Yates-Shuffle. """ import random +from typing import Any -def fisher_yates_shuffle(data: list) -> list: - for _ 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 fisher_yates_shuffle(data: list) -> list[Any]: + for _ in range(len(data)): + a = random.randint(0, len(data) - 1) + b = random.randint(0, len(data) - 1) + data[a], data[b] = data[b], data[a] + return data if __name__ == "__main__": From db5aa1d18890439e4108fa416679dbab5859f30c Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Sun, 7 Nov 2021 20:10:23 +0530 Subject: [PATCH 1741/2908] Add equated_monthly_installments.py in Financials (#5775) * Add equated_monthly_installments.py in Financials * Formatting * More formatting, Descriptive names * Errors with name change * Formatting * Formatting, Naming Error * dedent * Update DIRECTORY.md --- DIRECTORY.md | 1 + financial/equated_monthly_installments.py | 61 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 financial/equated_monthly_installments.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 32ca8cd3b256..f515277f403e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -298,6 +298,7 @@ ## Financial * [Interest](https://github.com/TheAlgorithms/Python/blob/master/financial/interest.py) + * [EMI Calculation](https://github.com/TheAlgorithms/Python/blob/master/financial/equated_monthly_installments.py) ## Fractals * [Julia Sets](https://github.com/TheAlgorithms/Python/blob/master/fractals/julia_sets.py) diff --git a/financial/equated_monthly_installments.py b/financial/equated_monthly_installments.py new file mode 100644 index 000000000000..3af9224930b5 --- /dev/null +++ b/financial/equated_monthly_installments.py @@ -0,0 +1,61 @@ +""" +Program to calculate the amortization amount per month, given +- Principal borrowed +- Rate of interest per annum +- Years to repay the loan + +Wikipedia Reference: https://en.wikipedia.org/wiki/Equated_monthly_installment +""" + + +def equated_monthly_installments( + principal: float, rate_per_annum: float, years_to_repay: int +) -> float: + """ + Formula for amortization amount per month: + A = p * r * (1 + r)^n / ((1 + r)^n - 1) + where p is the principal, r is the rate of interest per month + and n is the number of payments + + >>> equated_monthly_installments(25000, 0.12, 3) + 830.3577453212793 + >>> equated_monthly_installments(25000, 0.12, 10) + 358.67737100646826 + >>> equated_monthly_installments(0, 0.12, 3) + Traceback (most recent call last): + ... + Exception: Principal borrowed must be > 0 + >>> equated_monthly_installments(25000, -1, 3) + Traceback (most recent call last): + ... + Exception: Rate of interest must be >= 0 + >>> equated_monthly_installments(25000, 0.12, 0) + Traceback (most recent call last): + ... + Exception: Years to repay must be an integer > 0 + """ + if principal <= 0: + raise Exception("Principal borrowed must be > 0") + if rate_per_annum < 0: + raise Exception("Rate of interest must be >= 0") + if years_to_repay <= 0 or not isinstance(years_to_repay, int): + raise Exception("Years to repay must be an integer > 0") + + # Yearly rate is divided by 12 to get monthly rate + rate_per_month = rate_per_annum / 12 + + # Years to repay is multiplied by 12 to get number of payments as payment is monthly + number_of_payments = years_to_repay * 12 + + return ( + principal + * rate_per_month + * (1 + rate_per_month) ** number_of_payments + / ((1 + rate_per_month) ** number_of_payments - 1) + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a98465230f21e6ece76332eeca1558613788c387 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Sun, 7 Nov 2021 20:43:58 +0530 Subject: [PATCH 1742/2908] [mypy] Fix type annotations for maths directory (#5782) * [mypy] Fix annotations in `maths/series/p_series.py` * Update p_series.py * Update p_series.py * Remove from excluded in mypy.ini * Type annotation for series * Annotate maths/proth_number.py (properly) * Remove from excluded in mypy.ini * Annotate average_mode.py * Update average_mode.py * Update average_mode.py * Update average_mode.py * Update average_mode.py * Remove from excluded in mypy.ini * Fix annotations in gamma_recursive.py * Remove from excluded in mypy.ini * Annotations for geometric_series.py * Update geometric_series.py * Update mypy.ini * Update average_mode.py * Update average_mode.py * Update average_mode.py * Update mypy.ini * Update mypy.ini * Update mypy.ini * Update average_mode.py * Update proth_number.py * Update average_mode.py * Update gamma_recursive.py * Update proth_number.py * Update mypy.ini * Update geometric_series.py * Update average_mode.py * Update proth_number.py * Update geometric_series.py * Update geometric_series.py * Update geometric_series.py * Update p_series.py * Update geometric_series.py * Update p_series.py * Update p_series.py * Update geometric_series.py * Update p_series.py * Update p_series.py * Remove data_structures/stacks/next_greater_element.py| Co-authored-by: Christian Clauss --- maths/average_mode.py | 31 ++++++++++------------ maths/gamma_recursive.py | 3 +-- maths/proth_number.py | 14 ++++------ maths/series/geometric_series.py | 44 ++++++++++++++++++++------------ maths/series/p_series.py | 32 +++++++++++++---------- mypy.ini | 3 +-- 6 files changed, 66 insertions(+), 61 deletions(-) diff --git a/maths/average_mode.py b/maths/average_mode.py index 83db820072bf..40f88f41f8ca 100644 --- a/maths/average_mode.py +++ b/maths/average_mode.py @@ -1,34 +1,29 @@ -def mode(input_list: list) -> list: # Defining function "mode." +from typing import Any + + +def mode(input_list: list) -> list[Any]: """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) + >>> mode([2, 3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 2, 2, 2]) [2] - >>> input_list = [3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 4, 2, 2, 2] - >>> mode(input_list) + >>> mode([3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 4, 2, 2, 2]) [2] - >>> input_list = [3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 4, 4, 2, 2, 4, 2] - >>> mode(input_list) + >>> mode([3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 4, 4, 2, 2, 4, 2]) [2, 4] - >>> input_list = ["x", "y", "y", "z"] - >>> mode(input_list) + >>> mode(["x", "y", "y", "z"]) ['y'] - >>> input_list = ["x", "x" , "y", "y", "z"] - >>> mode(input_list) + >>> mode(["x", "x" , "y", "y", "z"]) ['x', 'y'] """ - result = list() # Empty list to store the counts of elements in input_list - for x in input_list: - result.append(input_list.count(x)) - if not result: + if not input_list: return [] - y = max(result) # Gets the maximum value in the result list. + result = [input_list.count(value) for value in input_list] + y = max(result) # Gets the maximum count in the input list. # Gets values of modes - result = {input_list[i] for i, value in enumerate(result) if value == y} - return sorted(result) + return sorted({input_list[i] for i, value in enumerate(result) if value == y}) if __name__ == "__main__": diff --git a/maths/gamma_recursive.py b/maths/gamma_recursive.py index 683d7adb1aa8..3d6b8c5e8138 100644 --- a/maths/gamma_recursive.py +++ b/maths/gamma_recursive.py @@ -2,7 +2,6 @@ Gamma function is a very useful tool in math and physics. It helps calculating complex integral in a convenient way. for more info: https://en.wikipedia.org/wiki/Gamma_function - Python's Standard Library math.gamma() function overflows around gamma(171.624). """ from math import pi, sqrt @@ -71,7 +70,7 @@ def test_gamma() -> None: from doctest import testmod testmod() - num = 1 + num = 1.0 while num: num = float(input("Gamma of: ")) print(f"gamma({num}) = {gamma(num)}") diff --git a/maths/proth_number.py b/maths/proth_number.py index 065244ed7607..e175031435b0 100644 --- a/maths/proth_number.py +++ b/maths/proth_number.py @@ -1,6 +1,5 @@ """ Calculate the nth Proth number - Source: https://handwiki.org/wiki/Proth_number """ @@ -12,22 +11,17 @@ def proth(number: int) -> int: """ :param number: nth number to calculate in the sequence :return: the nth number in Proth number - Note: indexing starts at 1 i.e. proth(1) gives the first Proth number of 3 - >>> proth(6) 25 - >>> proth(0) Traceback (most recent call last): ... ValueError: Input value of [number=0] must be > 0 - >>> proth(-1) Traceback (most recent call last): ... ValueError: Input value of [number=-1] must be > 0 - >>> proth(6.0) Traceback (most recent call last): ... @@ -44,14 +38,12 @@ def proth(number: int) -> int: elif number == 2: return 5 else: - block_index = number // 3 """ +1 for binary starting at 0 i.e. 2^0, 2^1, etc. +1 to start the sequence at the 3rd Proth number Hence, we have a +2 in the below statement """ - block_index = math.log(block_index, 2) + 2 - block_index = int(block_index) + block_index = int(math.log(number // 3, 2)) + 2 proth_list = [3, 5] proth_index = 2 @@ -66,6 +58,10 @@ def proth(number: int) -> int: if __name__ == "__main__": + import doctest + + doctest.testmod() + for number in range(11): value = 0 try: diff --git a/maths/series/geometric_series.py b/maths/series/geometric_series.py index d12382e6d8c4..a875ab89a0c5 100644 --- a/maths/series/geometric_series.py +++ b/maths/series/geometric_series.py @@ -1,7 +1,6 @@ """ 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 @@ -11,8 +10,17 @@ """ -def geometric_series(nth_term: int, start_term_a: int, common_ratio_r: int) -> list: - """Pure Python implementation of Geometric Series algorithm +from __future__ import annotations + + +def geometric_series( + nth_term: float | int, + start_term_a: float | int, + common_ratio_r: float | int, +) -> list[float | int]: + """ + 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 @@ -20,15 +28,15 @@ def geometric_series(nth_term: int, start_term_a: int, common_ratio_r: int) -> l 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'] + [2, 4.0, 8.0, 16.0] >>> geometric_series(4.0, 2.0, 2.0) - [2.0, '4.0', '8.0', '16.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'] + [2.1, 4.41, 9.261000000000001, 19.448100000000004] >>> geometric_series(4, 2, -2) - [2, '-4.0', '8.0', '-16.0'] + [2, -4.0, 8.0, -16.0] >>> geometric_series(4, -2, 2) - [-2, '-4.0', '-8.0', '-16.0'] + [-2, -4.0, -8.0, -16.0] >>> geometric_series(-4, 2, 2) [] >>> geometric_series(0, 100, 500) @@ -38,9 +46,9 @@ def geometric_series(nth_term: int, start_term_a: int, common_ratio_r: int) -> l >>> geometric_series(0, 0, 0) [] """ - if "" in (nth_term, start_term_a, common_ratio_r): - return "" - series = [] + if not all((nth_term, start_term_a, common_ratio_r)): + return [] + series: list[float | int] = [] power = 1 multiple = common_ratio_r for _ in range(int(nth_term)): @@ -48,16 +56,20 @@ def geometric_series(nth_term: int, start_term_a: int, common_ratio_r: int) -> l series.append(start_term_a) else: power += 1 - series.append(str(float(start_term_a) * float(multiple))) + series.append(float(start_term_a * 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" + import doctest + + doctest.testmod() + + nth_term = float(input("Enter the last number (n term) of the Geometric Series")) + start_term_a = float(input("Enter the starting term (a) of the Geometric Series")) + common_ratio_r = float( + 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/p_series.py b/maths/series/p_series.py index 04019aed5a85..34fa3f2399af 100644 --- a/maths/series/p_series.py +++ b/maths/series/p_series.py @@ -1,48 +1,52 @@ """ 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 +from __future__ import annotations - :return: The P-Series starting from 1 to last (nth) term +def p_series(nth_term: int | float | str, power: int | float | str) -> list[str]: + """ + 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'] + ['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'] + ['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] + ['1'] """ if nth_term == "": - return nth_term + return [""] nth_term = int(nth_term) power = int(power) - series = [] + series: list[str] = [] for temp in range(int(nth_term)): - series.append(f"1/{pow(temp + 1, int(power))}" if series else 1) + 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") + import doctest + + doctest.testmod() + + nth_term = int(input("Enter the last number (nth term) of the P-Series")) + power = int(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)) diff --git a/mypy.ini b/mypy.ini index 123ffae851a5..df69fa841cef 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,5 +2,4 @@ ignore_missing_imports = True install_types = True non_interactive = True -exclude = (data_structures/stacks/next_greater_element.py|graphs/boruvka.py|graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/finding_bridges.py|graphs/greedy_min_vertex_cover.py|graphs/random_graph_generator.py|maths/average_mode.py|maths/gamma_recursive.py|maths/proth_number.py|maths/series/geometric_series.py|maths/series/p_series.py|matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) - +exclude = (graphs/boruvka.py|graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/finding_bridges.py|graphs/greedy_min_vertex_cover.py|graphs/random_graph_generator.py|matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) From 2f6a7ae1fa44514f52f9a97f83d7bbb2b18e53f2 Mon Sep 17 00:00:00 2001 From: Khoi Vo Date: Mon, 8 Nov 2021 12:35:40 +0700 Subject: [PATCH 1743/2908] ADD the algorithms of image augmentation (#5792) * ADD the algorithms of image augmentation * ADD the algorithms of image augmentation * ADD the algorithms of image augmentation * ADD the algorithms of image augmentation * ADD the algorithms of image augmentation * ADD the algorithms of image augmentation * UPDATE format code * UPDATE format and recode structure * UPDATE format import library * UPDATE code structure * Fix all checks have failded * FIX variable format * FIX variable format * FIX variable format * FIX code structure * FIX code structure * FIX code structure * FIX code structure --- computer_vision/flip_augmentation.py | 131 +++++++++++++++++ computer_vision/mosaic_augmentation.py | 189 +++++++++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 computer_vision/flip_augmentation.py create mode 100644 computer_vision/mosaic_augmentation.py diff --git a/computer_vision/flip_augmentation.py b/computer_vision/flip_augmentation.py new file mode 100644 index 000000000000..1272357fd03e --- /dev/null +++ b/computer_vision/flip_augmentation.py @@ -0,0 +1,131 @@ +import glob +import os +import random +from string import ascii_lowercase, digits + +import cv2 + +""" +Flip image and bounding box for computer vision task +https://paperswithcode.com/method/randomhorizontalflip +""" + +# Params +LABEL_DIR = "" +IMAGE_DIR = "" +OUTPUT_DIR = "" +FLIP_TYPE = 1 # (0 is vertical, 1 is horizontal) + + +def main() -> None: + """ + Get images list and annotations list from input dir. + Update new images and annotations. + Save images and annotations in output dir. + >>> pass # A doctest is not possible for this function. + """ + img_paths, annos = get_dataset(LABEL_DIR, IMAGE_DIR) + print("Processing...") + new_images, new_annos, paths = update_image_and_anno(img_paths, annos, FLIP_TYPE) + + for index, image in enumerate(new_images): + # Get random string code: '7b7ad245cdff75241935e4dd860f3bad' + letter_code = random_chars(32) + file_name = paths[index].split(os.sep)[-1].rsplit(".", 1)[0] + file_root = f"{OUTPUT_DIR}/{file_name}_FLIP_{letter_code}" + cv2.imwrite(f"/{file_root}.jpg", image, [cv2.IMWRITE_JPEG_QUALITY, 85]) + print(f"Success {index+1}/{len(new_images)} with {file_name}") + annos_list = [] + for anno in new_annos[index]: + obj = f"{anno[0]} {anno[1]} {anno[2]} {anno[3]} {anno[4]}" + annos_list.append(obj) + with open(f"/{file_root}.txt", "w") as outfile: + outfile.write("\n".join(line for line in annos_list)) + + +def get_dataset(label_dir: str, img_dir: str) -> tuple[list, list]: + """ + - label_dir : Path to label include annotation of images + - img_dir : Path to folder contain images + Return : List of images path and labels + >>> pass # A doctest is not possible for this function. + """ + img_paths = [] + labels = [] + for label_file in glob.glob(os.path.join(label_dir, "*.txt")): + label_name = label_file.split(os.sep)[-1].rsplit(".", 1)[0] + with open(label_file) as in_file: + obj_lists = in_file.readlines() + img_path = os.path.join(img_dir, f"{label_name}.jpg") + + boxes = [] + for obj_list in obj_lists: + obj = obj_list.rstrip("\n").split(" ") + boxes.append( + [ + int(obj[0]), + float(obj[1]), + float(obj[2]), + float(obj[3]), + float(obj[4]), + ] + ) + if not boxes: + continue + img_paths.append(img_path) + labels.append(boxes) + return img_paths, labels + + +def update_image_and_anno( + img_list: list, anno_list: list, flip_type: int = 1 +) -> tuple[list, list, list]: + """ + - img_list : list of all images + - anno_list : list of all annotations of specific image + - flip_type : 0 is vertical, 1 is horizontal + Return: + - new_imgs_list : image after resize + - new_annos_lists : list of new annotation after scale + - path_list : list the name of image file + >>> pass # A doctest is not possible for this function. + """ + new_annos_lists = [] + path_list = [] + new_imgs_list = [] + for idx in range(len(img_list)): + new_annos = [] + path = img_list[idx] + path_list.append(path) + img_annos = anno_list[idx] + img = cv2.imread(path) + if flip_type == 1: + new_img = cv2.flip(img, flip_type) + for bbox in img_annos: + x_center_new = 1 - bbox[1] + new_annos.append([bbox[0], x_center_new, bbox[2], bbox[3], bbox[4]]) + elif flip_type == 0: + new_img = cv2.flip(img, flip_type) + for bbox in img_annos: + y_center_new = 1 - bbox[2] + new_annos.append([bbox[0], bbox[1], y_center_new, bbox[3], bbox[4]]) + new_annos_lists.append(new_annos) + new_imgs_list.append(new_img) + return new_imgs_list, new_annos_lists, path_list + + +def random_chars(number_char: int = 32) -> str: + """ + Automatic generate random 32 characters. + Get random string code: '7b7ad245cdff75241935e4dd860f3bad' + >>> len(random_chars(32)) + 32 + """ + assert number_char > 1, "The number of character should greater than 1" + letter_code = ascii_lowercase + digits + return "".join(random.choice(letter_code) for _ in range(number_char)) + + +if __name__ == "__main__": + main() + print("DONE ✅") diff --git a/computer_vision/mosaic_augmentation.py b/computer_vision/mosaic_augmentation.py new file mode 100644 index 000000000000..4fd81957ce2a --- /dev/null +++ b/computer_vision/mosaic_augmentation.py @@ -0,0 +1,189 @@ +"""Source: https://github.com/jason9075/opencv-mosaic-data-aug""" + +import glob +import os +import random +from string import ascii_lowercase, digits + +import cv2 +import numpy as np + +# Parrameters +OUTPUT_SIZE = (720, 1280) # Height, Width +SCALE_RANGE = (0.4, 0.6) # if height or width lower than this scale, drop it. +FILTER_TINY_SCALE = 1 / 100 +LABEL_DIR = "" +IMG_DIR = "" +OUTPUT_DIR = "" +NUMBER_IMAGES = 250 + + +def main() -> None: + """ + Get images list and annotations list from input dir. + Update new images and annotations. + Save images and annotations in output dir. + >>> pass # A doctest is not possible for this function. + """ + img_paths, annos = get_dataset(LABEL_DIR, IMG_DIR) + for index in range(NUMBER_IMAGES): + idxs = random.sample(range(len(annos)), 4) + new_image, new_annos, path = update_image_and_anno( + img_paths, + annos, + idxs, + OUTPUT_SIZE, + SCALE_RANGE, + filter_scale=FILTER_TINY_SCALE, + ) + + # Get random string code: '7b7ad245cdff75241935e4dd860f3bad' + letter_code = random_chars(32) + file_name = path.split(os.sep)[-1].rsplit(".", 1)[0] + file_root = f"{OUTPUT_DIR}/{file_name}_MOSAIC_{letter_code}" + cv2.imwrite(f"{file_root}.jpg", new_image, [cv2.IMWRITE_JPEG_QUALITY, 85]) + print(f"Succeeded {index+1}/{NUMBER_IMAGES} with {file_name}") + annos_list = [] + for anno in new_annos: + width = anno[3] - anno[1] + height = anno[4] - anno[2] + x_center = anno[1] + width / 2 + y_center = anno[2] + height / 2 + obj = f"{anno[0]} {x_center} {y_center} {width} {height}" + annos_list.append(obj) + with open(f"{file_root}.txt", "w") as outfile: + outfile.write("\n".join(line for line in annos_list)) + + +def get_dataset(label_dir: str, img_dir: str) -> tuple[list, list]: + """ + - label_dir : Path to label include annotation of images + - img_dir : Path to folder contain images + Return : List of images path and labels + >>> pass # A doctest is not possible for this function. + """ + img_paths = [] + labels = [] + for label_file in glob.glob(os.path.join(label_dir, "*.txt")): + label_name = label_file.split(os.sep)[-1].rsplit(".", 1)[0] + with open(label_file) as in_file: + obj_lists = in_file.readlines() + img_path = os.path.join(img_dir, f"{label_name}.jpg") + + boxes = [] + for obj_list in obj_lists: + obj = obj_list.rstrip("\n").split(" ") + xmin = float(obj[1]) - float(obj[3]) / 2 + ymin = float(obj[2]) - float(obj[4]) / 2 + xmax = float(obj[1]) + float(obj[3]) / 2 + ymax = float(obj[2]) + float(obj[4]) / 2 + + boxes.append([int(obj[0]), xmin, ymin, xmax, ymax]) + if not boxes: + continue + img_paths.append(img_path) + labels.append(boxes) + return img_paths, labels + + +def update_image_and_anno( + all_img_list: list, + all_annos: list, + idxs: list[int], + output_size: tuple[int, int], + scale_range: tuple[float, float], + filter_scale: float = 0.0, +) -> tuple[list, list, str]: + """ + - all_img_list : list of all images + - all_annos : list of all annotations of specific image + - idxs : index of image in list + - output_size : size of output image (Height, Width) + - scale_range : range of scale image + - filter_scale : the condition of downscale image and bounding box + Return: + - output_img : image after resize + - new_anno : list of new annotation after scale + - path[0] : get the name of image file + >>> pass # A doctest is not possible for this function. + """ + output_img = np.zeros([output_size[0], output_size[1], 3], dtype=np.uint8) + scale_x = scale_range[0] + random.random() * (scale_range[1] - scale_range[0]) + scale_y = scale_range[0] + random.random() * (scale_range[1] - scale_range[0]) + divid_point_x = int(scale_x * output_size[1]) + divid_point_y = int(scale_y * output_size[0]) + + new_anno = [] + path_list = [] + for i, index in enumerate(idxs): + path = all_img_list[index] + path_list.append(path) + img_annos = all_annos[index] + img = cv2.imread(path) + if i == 0: # top-left + img = cv2.resize(img, (divid_point_x, divid_point_y)) + output_img[:divid_point_y, :divid_point_x, :] = img + for bbox in img_annos: + xmin = bbox[1] * scale_x + ymin = bbox[2] * scale_y + xmax = bbox[3] * scale_x + ymax = bbox[4] * scale_y + new_anno.append([bbox[0], xmin, ymin, xmax, ymax]) + elif i == 1: # top-right + img = cv2.resize(img, (output_size[1] - divid_point_x, divid_point_y)) + output_img[:divid_point_y, divid_point_x : output_size[1], :] = img + for bbox in img_annos: + xmin = scale_x + bbox[1] * (1 - scale_x) + ymin = bbox[2] * scale_y + xmax = scale_x + bbox[3] * (1 - scale_x) + ymax = bbox[4] * scale_y + new_anno.append([bbox[0], xmin, ymin, xmax, ymax]) + elif i == 2: # bottom-left + img = cv2.resize(img, (divid_point_x, output_size[0] - divid_point_y)) + output_img[divid_point_y : output_size[0], :divid_point_x, :] = img + for bbox in img_annos: + xmin = bbox[1] * scale_x + ymin = scale_y + bbox[2] * (1 - scale_y) + xmax = bbox[3] * scale_x + ymax = scale_y + bbox[4] * (1 - scale_y) + new_anno.append([bbox[0], xmin, ymin, xmax, ymax]) + else: # bottom-right + img = cv2.resize( + img, (output_size[1] - divid_point_x, output_size[0] - divid_point_y) + ) + output_img[ + divid_point_y : output_size[0], divid_point_x : output_size[1], : + ] = img + for bbox in img_annos: + xmin = scale_x + bbox[1] * (1 - scale_x) + ymin = scale_y + bbox[2] * (1 - scale_y) + xmax = scale_x + bbox[3] * (1 - scale_x) + ymax = scale_y + bbox[4] * (1 - scale_y) + new_anno.append([bbox[0], xmin, ymin, xmax, ymax]) + + # Remove bounding box small than scale of filter + if 0 < filter_scale: + new_anno = [ + anno + for anno in new_anno + if filter_scale < (anno[3] - anno[1]) and filter_scale < (anno[4] - anno[2]) + ] + + return output_img, new_anno, path_list[0] + + +def random_chars(number_char: int) -> str: + """ + Automatic generate random 32 characters. + Get random string code: '7b7ad245cdff75241935e4dd860f3bad' + >>> len(random_chars(32)) + 32 + """ + assert number_char > 1, "The number of character should greater than 1" + letter_code = ascii_lowercase + digits + return "".join(random.choice(letter_code) for _ in range(number_char)) + + +if __name__ == "__main__": + main() + print("DONE ✅") From ac4bdfd66dbbd4c7c92c73d894469aa4a5c3e5ab Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Mon, 8 Nov 2021 10:47:09 -0300 Subject: [PATCH 1744/2908] [mypy] Fix type annotations in `graphs/boruvka.py` (#5794) * Fix type annotations in boruvka.py * Remove graphs/boruvka.py| * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 +++- graphs/boruvka.py | 8 +++++--- mypy.ini | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f515277f403e..228d95472a60 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -109,8 +109,10 @@ ## Computer Vision * [Cnn Classification](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/cnn_classification.py) + * [Flip Augmentation](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/flip_augmentation.py) * [Harris Corner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harris_corner.py) * [Mean Threshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mean_threshold.py) + * [Mosaic Augmentation](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mosaic_augmentation.py) ## Conversions * [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py) @@ -297,8 +299,8 @@ * [Test Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/tests/test_send_file.py) ## Financial + * [Equated Monthly Installments](https://github.com/TheAlgorithms/Python/blob/master/financial/equated_monthly_installments.py) * [Interest](https://github.com/TheAlgorithms/Python/blob/master/financial/interest.py) - * [EMI Calculation](https://github.com/TheAlgorithms/Python/blob/master/financial/equated_monthly_installments.py) ## Fractals * [Julia Sets](https://github.com/TheAlgorithms/Python/blob/master/fractals/julia_sets.py) diff --git a/graphs/boruvka.py b/graphs/boruvka.py index eea0b0009941..2715a3085948 100644 --- a/graphs/boruvka.py +++ b/graphs/boruvka.py @@ -26,6 +26,8 @@ """ from __future__ import annotations +from typing import Any + class Graph: def __init__(self, num_of_nodes: int) -> None: @@ -62,7 +64,7 @@ def set_component(self, u_node: int) -> None: for k in self.m_component: self.m_component[k] = self.find_component(k) - def union(self, component_size: list, u_node: int, v_node: int) -> None: + def union(self, component_size: list[int], u_node: int, v_node: int) -> None: """Union finds the roots of components for two nodes, compares the components in terms of size, and attaches the smaller one to the larger one to form single component""" @@ -84,7 +86,7 @@ def boruvka(self) -> None: component_size = [] mst_weight = 0 - minimum_weight_edge: list[int] = [-1] * self.m_num_of_nodes + minimum_weight_edge: list[Any] = [-1] * self.m_num_of_nodes # A list of components (initialized to all of the nodes) for node in range(self.m_num_of_nodes): @@ -119,7 +121,7 @@ def boruvka(self) -> None: minimum_weight_edge[component] = [u, v, w] for edge in minimum_weight_edge: - if edge != -1: + if isinstance(edge, list): u, v, w = edge u_component = self.m_component[u] diff --git a/mypy.ini b/mypy.ini index df69fa841cef..16ca60c4dbcc 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True install_types = True non_interactive = True -exclude = (graphs/boruvka.py|graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/finding_bridges.py|graphs/greedy_min_vertex_cover.py|graphs/random_graph_generator.py|matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) +exclude = (graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/finding_bridges.py|graphs/greedy_min_vertex_cover.py|graphs/random_graph_generator.py|matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) From a8aeabdf1891397a4a55988f33ac435ae0313c55 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Mon, 8 Nov 2021 22:48:33 +0530 Subject: [PATCH 1745/2908] [mypy] Type annotations for `graphs/finding_bridges.py` and `graphs/random_graph_generator.py` (#5795) * [mypy] Annotate `graphs/finding_bridges.py` * Remove from excluded in `mypy.ini` * Add doctest.testmod() * psf/black formatting * Annotations for `graphs/random_graph_generator.py` * Remove from excluded in `mypy.ini` * Resolve merge conflict * Resolve merge conflict * Update mypy.ini * Update mypy.ini * Remove from excluded --- graphs/finding_bridges.py | 8 +++++++- graphs/random_graph_generator.py | 2 +- mypy.ini | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/graphs/finding_bridges.py b/graphs/finding_bridges.py index a877a97489be..3813c4ebbd2a 100644 --- a/graphs/finding_bridges.py +++ b/graphs/finding_bridges.py @@ -93,8 +93,14 @@ def dfs(at, parent, bridges, id): # This edge is a back edge and cannot be a bridge low[at] = min(low[at], low[to]) - bridges = [] + bridges: list[tuple[int, int]] = [] for i in range(n): if not visited[i]: dfs(i, -1, bridges, id) return bridges + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/graphs/random_graph_generator.py b/graphs/random_graph_generator.py index d7d5de8a37c0..15ccee5b399c 100644 --- a/graphs/random_graph_generator.py +++ b/graphs/random_graph_generator.py @@ -26,7 +26,7 @@ def random_graph( >>> random_graph(4, 0.5, True) {0: [1], 1: [2, 3], 2: [3], 3: []} """ - graph = {i: [] for i in range(vertices_number)} + graph: dict = {i: [] for i in range(vertices_number)} # if probability is greater or equal than 1, then generate a complete graph if probability >= 1: diff --git a/mypy.ini b/mypy.ini index 16ca60c4dbcc..429c6804daf5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True install_types = True non_interactive = True -exclude = (graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/finding_bridges.py|graphs/greedy_min_vertex_cover.py|graphs/random_graph_generator.py|matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) +exclude = (graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/greedy_min_vertex_cover.py|matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) From 4c9949f636248a547b9ff832ad18df372df57ed5 Mon Sep 17 00:00:00 2001 From: Leoriem-code <73761711+Leoriem-code@users.noreply.github.com> Date: Mon, 8 Nov 2021 18:58:15 +0100 Subject: [PATCH 1746/2908] edited strings/anagram.py (#5770) * rewrote anagrams.py, added doctests * corrected mistakes * add anagrams.txt * Update anagrams.py * Update strings/anagrams.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- strings/anagrams.py | 55 +- strings/anagrams.txt | 33957 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 33989 insertions(+), 23 deletions(-) create mode 100644 strings/anagrams.txt diff --git a/strings/anagrams.py b/strings/anagrams.py index 1a7c675d6719..b671d3f3d531 100644 --- a/strings/anagrams.py +++ b/strings/anagrams.py @@ -1,35 +1,44 @@ +from __future__ import annotations + import collections -import os import pprint -import time +from pathlib import Path + + +def signature(word: str) -> str: + """Return a word sorted + >>> signature("test") + 'estt' + >>> signature("this is a test") + ' aehiisssttt' + >>> signature("finaltest") + 'aefilnstt' + """ + return "".join(sorted(word)) -start_time = time.time() -print("creating word list...") -path = os.path.split(os.path.realpath(__file__)) -with open(path[0] + "/words.txt") as f: - word_list = sorted(list({word.strip().lower() for word in f})) +def anagram(my_word: str) -> list[str]: + """Return every anagram of the given word + >>> anagram('test') + ['sett', 'stet', 'test'] + >>> anagram('this is a test') + [] + >>> anagram('final') + ['final'] + """ + return word_bysig[signature(my_word)] -def signature(word): - return "".join(sorted(word)) +data: str = Path(__file__).parent.joinpath("words.txt").read_text(encoding="utf-8") +word_list = sorted({word.strip().lower() for word in data.splitlines()}) word_bysig = collections.defaultdict(list) for word in word_list: word_bysig[signature(word)].append(word) +if __name__ == "__main__": + all_anagrams = {word: anagram(word) for word in word_list if len(anagram(word)) > 1} -def anagram(my_word): - return word_bysig[signature(my_word)] - - -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 ]")) + with open("anagrams.txt", "w") as file: + file.write("all_anagrams = \n ") + file.write(pprint.pformat(all_anagrams)) diff --git a/strings/anagrams.txt b/strings/anagrams.txt new file mode 100644 index 000000000000..52a0fcf3e9a7 --- /dev/null +++ b/strings/anagrams.txt @@ -0,0 +1,33957 @@ +all_anagrams = + {'aal': ['aal', 'ala'], + 'aam': ['aam', 'ama'], + 'aaronic': ['aaronic', 'nicarao', 'ocarina'], + 'aaronite': ['aaronite', 'aeration'], + 'aaru': ['aaru', 'aura'], + 'ab': ['ab', 'ba'], + 'aba': ['aba', 'baa'], + 'abac': ['abac', 'caba'], + 'abactor': ['abactor', 'acrobat'], + 'abaft': ['abaft', 'bafta'], + 'abalone': ['abalone', 'balonea'], + 'abandoner': ['abandoner', 'reabandon'], + 'abanic': ['abanic', 'bianca'], + 'abaris': ['abaris', 'arabis'], + 'abas': ['abas', 'saba'], + 'abaser': ['abaser', 'abrase'], + 'abate': ['abate', 'ateba', 'batea', 'beata'], + 'abater': ['abater', 'artabe', 'eartab', 'trabea'], + 'abb': ['abb', 'bab'], + 'abba': ['abba', 'baba'], + 'abbey': ['abbey', 'bebay'], + 'abby': ['abby', 'baby'], + 'abdat': ['abdat', 'batad'], + 'abdiel': ['abdiel', 'baldie'], + 'abdominovaginal': ['abdominovaginal', 'vaginoabdominal'], + 'abdominovesical': ['abdominovesical', 'vesicoabdominal'], + 'abe': ['abe', 'bae', 'bea'], + 'abed': ['abed', 'bade', 'bead'], + 'abel': ['abel', 'able', 'albe', 'bale', 'beal', 'bela', 'blae'], + 'abele': ['abele', 'albee'], + 'abelian': ['abelian', 'nebalia'], + 'abenteric': ['abenteric', 'bicrenate'], + 'aberia': ['aberia', 'baeria', 'baiera'], + 'abet': ['abet', 'bate', 'beat', 'beta'], + 'abetment': ['abetment', 'batement'], + 'abettor': ['abettor', 'taboret'], + 'abhorrent': ['abhorrent', 'earthborn'], + 'abhorrer': ['abhorrer', 'harborer'], + 'abider': ['abider', 'bardie'], + 'abies': ['abies', 'beisa'], + 'abilla': ['abilla', 'labial'], + 'abilo': ['abilo', 'aboil'], + 'abir': ['abir', 'bari', 'rabi'], + 'abiston': ['abiston', 'bastion'], + 'abiuret': ['abiuret', 'aubrite', 'biurate', 'rubiate'], + 'abkar': ['abkar', 'arkab'], + 'abkhas': ['abkhas', 'kasbah'], + 'ablactate': ['ablactate', 'cabaletta'], + 'ablare': ['ablare', 'arable', 'arbela'], + 'ablastemic': ['ablastemic', 'masticable'], + 'ablation': ['ablation', 'obtainal'], + 'ablaut': ['ablaut', 'tabula'], + 'able': ['abel', 'able', 'albe', 'bale', 'beal', 'bela', 'blae'], + 'ableness': ['ableness', 'blaeness', 'sensable'], + 'ablepsia': ['ablepsia', 'epibasal'], + 'abler': ['abler', 'baler', 'belar', 'blare', 'blear'], + 'ablest': ['ablest', 'stable', 'tables'], + 'abloom': ['abloom', 'mabolo'], + 'ablow': ['ablow', 'balow', 'bowla'], + 'ablude': ['ablude', 'belaud'], + 'abluent': ['abluent', 'tunable'], + 'ablution': ['ablution', 'abutilon'], + 'ably': ['ably', 'blay', 'yalb'], + 'abmho': ['abmho', 'abohm'], + 'abner': ['abner', 'arneb', 'reban'], + 'abnet': ['abnet', 'beant'], + 'abo': ['abo', 'boa'], + 'aboard': ['aboard', 'aborad', 'abroad'], + 'abode': ['abode', 'adobe'], + 'abohm': ['abmho', 'abohm'], + 'aboil': ['abilo', 'aboil'], + 'abolisher': ['abolisher', 'reabolish'], + 'abongo': ['abongo', 'gaboon'], + 'aborad': ['aboard', 'aborad', 'abroad'], + 'aboral': ['aboral', 'arbalo'], + 'abord': ['abord', 'bardo', 'board', 'broad', 'dobra', 'dorab'], + 'abort': ['abort', 'tabor'], + 'aborticide': ['aborticide', 'bacterioid'], + 'abortient': ['abortient', 'torbanite'], + 'abortin': ['abortin', 'taborin'], + 'abortion': ['abortion', 'robotian'], + 'abortive': ['abortive', 'bravoite'], + 'abouts': ['abouts', 'basuto'], + 'abram': ['abram', 'ambar'], + 'abramis': ['abramis', 'arabism'], + 'abrasax': ['abrasax', 'abraxas'], + 'abrase': ['abaser', 'abrase'], + 'abrasion': ['abrasion', 'sorabian'], + 'abrastol': ['abrastol', 'albatros'], + 'abraxas': ['abrasax', 'abraxas'], + 'abreact': ['abreact', 'bractea', 'cabaret'], + 'abret': ['abret', 'bater', 'berat'], + 'abridge': ['abridge', 'brigade'], + 'abrim': ['abrim', 'birma'], + 'abrin': ['abrin', 'bairn', 'brain', 'brian', 'rabin'], + 'abristle': ['abristle', 'libertas'], + 'abroad': ['aboard', 'aborad', 'abroad'], + 'abrotine': ['abrotine', 'baritone', 'obtainer', 'reobtain'], + 'abrus': ['abrus', 'bursa', 'subra'], + 'absalom': ['absalom', 'balsamo'], + 'abscise': ['abscise', 'scabies'], + 'absent': ['absent', 'basten'], + 'absenter': ['absenter', 'reabsent'], + 'absi': ['absi', 'bais', 'bias', 'isba'], + 'absit': ['absit', 'batis'], + 'absmho': ['absmho', 'absohm'], + 'absohm': ['absmho', 'absohm'], + 'absorber': ['absorber', 'reabsorb'], + 'absorpt': ['absorpt', 'barpost'], + 'abthain': ['abthain', 'habitan'], + 'abulic': ['abulic', 'baculi'], + 'abut': ['abut', 'tabu', 'tuba'], + 'abuta': ['abuta', 'bauta'], + 'abutilon': ['ablution', 'abutilon'], + 'aby': ['aby', 'bay'], + 'abysmal': ['abysmal', 'balsamy'], + 'academite': ['academite', 'acetamide'], + 'acadie': ['acadie', 'acedia', 'adicea'], + 'acaleph': ['acaleph', 'acephal'], + 'acalepha': ['acalepha', 'acephala'], + 'acalephae': ['acalephae', 'apalachee'], + 'acalephan': ['acalephan', 'acephalan'], + 'acalyptrate': ['acalyptrate', 'calyptratae'], + 'acamar': ['acamar', 'camara', 'maraca'], + 'acanth': ['acanth', 'anchat', 'tanach'], + 'acanthia': ['acanthia', 'achatina'], + 'acanthial': ['acanthial', 'calathian'], + 'acanthin': ['acanthin', 'chinanta'], + 'acara': ['acara', 'araca'], + 'acardia': ['acardia', 'acarida', 'arcadia'], + 'acarian': ['acarian', 'acarina', 'acrania'], + 'acarid': ['acarid', 'cardia', 'carida'], + 'acarida': ['acardia', 'acarida', 'arcadia'], + 'acarina': ['acarian', 'acarina', 'acrania'], + 'acarine': ['acarine', 'acraein', 'arecain'], + 'acastus': ['acastus', 'astacus'], + 'acatholic': ['acatholic', 'chaotical'], + 'acaudate': ['acaudate', 'ecaudata'], + 'acca': ['acca', 'caca'], + 'accelerator': ['accelerator', 'retrocaecal'], + 'acception': ['acception', 'peccation'], + 'accessioner': ['accessioner', 'reaccession'], + 'accipitres': ['accipitres', 'preascitic'], + 'accite': ['accite', 'acetic'], + 'acclinate': ['acclinate', 'analectic'], + 'accoil': ['accoil', 'calico'], + 'accomplisher': ['accomplisher', 'reaccomplish'], + 'accompt': ['accompt', 'compact'], + 'accorder': ['accorder', 'reaccord'], + 'accoy': ['accoy', 'ccoya'], + 'accretion': ['accretion', 'anorectic', 'neoarctic'], + 'accrual': ['accrual', 'carucal'], + 'accurate': ['accurate', 'carucate'], + 'accurse': ['accurse', 'accuser'], + 'accusable': ['accusable', 'subcaecal'], + 'accused': ['accused', 'succade'], + 'accuser': ['accurse', 'accuser'], + 'acedia': ['acadie', 'acedia', 'adicea'], + 'acedy': ['acedy', 'decay'], + 'acentric': ['acentric', 'encratic', 'nearctic'], + 'acentrous': ['acentrous', 'courtesan', 'nectarous'], + 'acephal': ['acaleph', 'acephal'], + 'acephala': ['acalepha', 'acephala'], + 'acephalan': ['acalephan', 'acephalan'], + 'acephali': ['acephali', 'phacelia'], + 'acephalina': ['acephalina', 'phalaecian'], + 'acer': ['acer', 'acre', 'care', 'crea', 'race'], + 'aceraceae': ['aceraceae', 'arecaceae'], + 'aceraceous': ['aceraceous', 'arecaceous'], + 'acerb': ['acerb', 'brace', 'caber'], + 'acerbic': ['acerbic', 'breccia'], + 'acerdol': ['acerdol', 'coraled'], + 'acerin': ['acerin', 'cearin'], + 'acerous': ['acerous', 'carouse', 'euscaro'], + 'acervate': ['acervate', 'revacate'], + 'acervation': ['acervation', 'vacationer'], + 'acervuline': ['acervuline', 'avirulence'], + 'acetamide': ['academite', 'acetamide'], + 'acetamido': ['acetamido', 'coadamite'], + 'acetanilid': ['acetanilid', 'laciniated', 'teniacidal'], + 'acetanion': ['acetanion', 'antoecian'], + 'acetation': ['acetation', 'itaconate'], + 'acetic': ['accite', 'acetic'], + 'acetin': ['acetin', 'actine', 'enatic'], + 'acetmethylanilide': ['acetmethylanilide', 'methylacetanilide'], + 'acetoin': ['acetoin', 'aconite', 'anoetic', 'antoeci', 'cetonia'], + 'acetol': ['acetol', 'colate', 'locate'], + 'acetone': ['acetone', 'oceanet'], + 'acetonuria': ['acetonuria', 'aeronautic'], + 'acetopyrin': ['acetopyrin', 'capernoity'], + 'acetous': ['acetous', 'outcase'], + 'acetum': ['acetum', 'tecuma'], + 'aceturic': ['aceturic', 'cruciate'], + 'ach': ['ach', 'cha'], + 'achar': ['achar', 'chara'], + 'achate': ['achate', 'chaeta'], + 'achatina': ['acanthia', 'achatina'], + 'ache': ['ache', 'each', 'haec'], + 'acheirus': ['acheirus', 'eucharis'], + 'achen': ['achen', 'chane', 'chena', 'hance'], + 'acher': ['acher', 'arche', 'chare', 'chera', 'rache', 'reach'], + 'acherontic': ['acherontic', 'anchoretic'], + 'acherontical': ['acherontical', 'anchoretical'], + 'achete': ['achete', 'hecate', 'teache', 'thecae'], + 'acheulean': ['acheulean', 'euchlaena'], + 'achill': ['achill', 'cahill', 'chilla'], + 'achillea': ['achillea', 'heliacal'], + 'acholia': ['acholia', 'alochia'], + 'achondrite': ['achondrite', 'ditrochean', 'ordanchite'], + 'achor': ['achor', 'chora', 'corah', 'orach', 'roach'], + 'achras': ['achras', 'charas'], + 'achromat': ['achromat', 'trachoma'], + 'achromatin': ['achromatin', 'chariotman', 'machinator'], + 'achromatinic': ['achromatinic', 'chromatician'], + 'achtel': ['achtel', 'chalet', 'thecal', 'thecla'], + 'achy': ['achy', 'chay'], + 'aciculated': ['aciculated', 'claudicate'], + 'acid': ['acid', 'cadi', 'caid'], + 'acidanthera': ['acidanthera', 'cantharidae'], + 'acider': ['acider', 'ericad'], + 'acidimeter': ['acidimeter', 'mediatrice'], + 'acidity': ['acidity', 'adicity'], + 'acidly': ['acidly', 'acidyl'], + 'acidometry': ['acidometry', 'medicatory', 'radiectomy'], + 'acidophilous': ['acidophilous', 'aphidicolous'], + 'acidyl': ['acidly', 'acidyl'], + 'acier': ['acier', 'aeric', 'ceria', 'erica'], + 'acieral': ['acieral', 'aerical'], + 'aciform': ['aciform', 'formica'], + 'acilius': ['acilius', 'iliacus'], + 'acinar': ['acinar', + 'arnica', + 'canari', + 'carian', + 'carina', + 'crania', + 'narica'], + 'acinic': ['acinic', 'incaic'], + 'aciniform': ['aciniform', 'formicina'], + 'acipenserid': ['acipenserid', 'presidencia'], + 'acis': ['acis', 'asci', 'saic'], + 'acker': ['acker', 'caker', 'crake', 'creak'], + 'ackey': ['ackey', 'cakey'], + 'acle': ['acle', 'alec', 'lace'], + 'acleistous': ['acleistous', 'ossiculate'], + 'aclemon': ['aclemon', 'cloamen'], + 'aclinal': ['aclinal', 'ancilla'], + 'aclys': ['aclys', 'scaly'], + 'acme': ['acme', 'came', 'mace'], + 'acmite': ['acmite', 'micate'], + 'acne': ['acne', 'cane', 'nace'], + 'acnemia': ['acnemia', 'anaemic'], + 'acnida': ['acnida', 'anacid', 'dacian'], + 'acnodal': ['acnodal', 'canadol', 'locanda'], + 'acnode': ['acnode', 'deacon'], + 'acoin': ['acoin', 'oncia'], + 'acoma': ['acoma', 'macao'], + 'acone': ['acone', 'canoe', 'ocean'], + 'aconital': ['aconital', 'actional', 'anatolic'], + 'aconite': ['acetoin', 'aconite', 'anoetic', 'antoeci', 'cetonia'], + 'aconitic': ['aconitic', 'cationic', 'itaconic'], + 'aconitin': ['aconitin', 'inaction', 'nicotian'], + 'aconitum': ['aconitum', 'acontium'], + 'acontias': ['acontias', 'tacsonia'], + 'acontium': ['aconitum', 'acontium'], + 'acontius': ['acontius', 'anticous'], + 'acopon': ['acopon', 'poonac'], + 'acor': ['acor', 'caro', 'cora', 'orca'], + 'acorn': ['acorn', 'acron', 'racon'], + 'acorus': ['acorus', 'soucar'], + 'acosmist': ['acosmist', 'massicot', 'somatics'], + 'acquest': ['acquest', 'casquet'], + 'acrab': ['acrab', 'braca'], + 'acraein': ['acarine', 'acraein', 'arecain'], + 'acrania': ['acarian', 'acarina', 'acrania'], + 'acraniate': ['acraniate', 'carinatae'], + 'acratia': ['acratia', 'cataria'], + 'acre': ['acer', 'acre', 'care', 'crea', 'race'], + 'acream': ['acream', 'camera', 'mareca'], + 'acred': ['acred', 'cader', 'cadre', 'cedar'], + 'acrid': ['acrid', 'caird', 'carid', 'darci', 'daric', 'dirca'], + 'acridan': ['acridan', 'craniad'], + 'acridian': ['acridian', 'cnidaria'], + 'acrididae': ['acrididae', 'cardiidae', 'cidaridae'], + 'acridly': ['acridly', 'acridyl'], + 'acridonium': ['acridonium', 'dicoumarin'], + 'acridyl': ['acridly', 'acridyl'], + 'acrimonious': ['acrimonious', 'isocoumarin'], + 'acrisius': ['acrisius', 'sicarius'], + 'acrita': ['acrita', 'arctia'], + 'acritan': ['acritan', 'arctian'], + 'acrite': ['acrite', 'arcite', 'tercia', 'triace', 'tricae'], + 'acroa': ['acroa', 'caroa'], + 'acrobat': ['abactor', 'acrobat'], + 'acrocera': ['acrocera', 'caracore'], + 'acroclinium': ['acroclinium', 'alcicornium'], + 'acrodus': ['acrodus', 'crusado'], + 'acrogen': ['acrogen', 'cornage'], + 'acrolein': ['acrolein', + 'arecolin', + 'caroline', + 'colinear', + 'cornelia', + 'creolian', + 'lonicera'], + 'acrolith': ['acrolith', 'trochila'], + 'acron': ['acorn', 'acron', 'racon'], + 'acronical': ['acronical', 'alcoranic'], + 'acronym': ['acronym', 'romancy'], + 'acropetal': ['acropetal', 'cleopatra'], + 'acrose': ['acrose', 'coarse'], + 'acrostic': ['acrostic', 'sarcotic', 'socratic'], + 'acrostical': ['acrostical', 'socratical'], + 'acrostically': ['acrostically', 'socratically'], + 'acrosticism': ['acrosticism', 'socraticism'], + 'acrotic': ['acrotic', 'carotic'], + 'acrotism': ['acrotism', 'rotacism'], + 'acrotrophic': ['acrotrophic', 'prothoracic'], + 'acryl': ['acryl', 'caryl', 'clary'], + 'act': ['act', 'cat'], + 'actaeonidae': ['actaeonidae', 'donatiaceae'], + 'actian': ['actian', 'natica', 'tanica'], + 'actifier': ['actifier', 'artifice'], + 'actin': ['actin', 'antic'], + 'actinal': ['actinal', 'alantic', 'alicant', 'antical'], + 'actine': ['acetin', 'actine', 'enatic'], + 'actiniform': ['actiniform', 'naticiform'], + 'actinine': ['actinine', 'naticine'], + 'actinism': ['actinism', 'manistic'], + 'actinogram': ['actinogram', 'morganatic'], + 'actinoid': ['actinoid', 'diatonic', 'naticoid'], + 'actinon': ['actinon', 'cantion', 'contain'], + 'actinopteran': ['actinopteran', 'precantation'], + 'actinopteri': ['actinopteri', 'crepitation', 'precitation'], + 'actinost': ['actinost', 'oscitant'], + 'actinula': ['actinula', 'nautical'], + 'action': ['action', 'atonic', 'cation'], + 'actional': ['aconital', 'actional', 'anatolic'], + 'actioner': ['actioner', 'anerotic', 'ceration', 'creation', 'reaction'], + 'activable': ['activable', 'biclavate'], + 'activate': ['activate', 'cavitate'], + 'activation': ['activation', 'cavitation'], + 'activin': ['activin', 'civitan'], + 'actomyosin': ['actomyosin', 'inocystoma'], + 'acton': ['acton', 'canto', 'octan'], + 'actor': ['actor', 'corta', 'croat', 'rocta', 'taroc', 'troca'], + 'actorship': ['actorship', 'strophaic'], + 'acts': ['acts', 'cast', 'scat'], + 'actuator': ['actuator', 'autocrat'], + 'acture': ['acture', 'cauter', 'curate'], + 'acuan': ['acuan', 'aucan'], + 'acubens': ['acubens', 'benacus'], + 'acumen': ['acumen', 'cueman'], + 'acuminose': ['acuminose', 'mniaceous'], + 'acutenaculum': ['acutenaculum', 'unaccumulate'], + 'acuteness': ['acuteness', 'encaustes'], + 'acutorsion': ['acutorsion', 'octonarius'], + 'acyl': ['acyl', 'clay', 'lacy'], + 'acylation': ['acylation', 'claytonia'], + 'acylogen': ['acylogen', 'cynogale'], + 'ad': ['ad', 'da'], + 'adad': ['adad', 'adda', 'dada'], + 'adage': ['adage', 'agade'], + 'adam': ['adam', 'dama'], + 'adamic': ['adamic', 'cadmia'], + 'adamine': ['adamine', 'manidae'], + 'adamite': ['adamite', 'amidate'], + 'adamsite': ['adamsite', 'diastema'], + 'adance': ['adance', 'ecanda'], + 'adapter': ['adapter', 'predata', 'readapt'], + 'adaption': ['adaption', 'adoptian'], + 'adaptionism': ['adaptionism', 'adoptianism'], + 'adar': ['adar', 'arad', 'raad', 'rada'], + 'adarme': ['adarme', 'adream'], + 'adat': ['adat', 'data'], + 'adawn': ['adawn', 'wadna'], + 'adays': ['adays', 'dasya'], + 'add': ['add', 'dad'], + 'adda': ['adad', 'adda', 'dada'], + 'addendum': ['addendum', 'unmadded'], + 'adder': ['adder', 'dread', 'readd'], + 'addicent': ['addicent', 'dedicant'], + 'addlings': ['addlings', 'saddling'], + 'addresser': ['addresser', 'readdress'], + 'addu': ['addu', 'dadu', 'daud', 'duad'], + 'addy': ['addy', 'dyad'], + 'ade': ['ade', 'dae'], + 'adeem': ['adeem', 'ameed', 'edema'], + 'adela': ['adela', 'dalea'], + 'adeline': ['adeline', 'daniele', 'delaine'], + 'adeling': ['adeling', 'dealing', 'leading'], + 'adelops': ['adelops', 'deposal'], + 'ademonist': ['ademonist', 'demoniast', 'staminode'], + 'ademption': ['ademption', 'tampioned'], + 'adendric': ['adendric', 'riddance'], + 'adenectopic': ['adenectopic', 'pentadecoic'], + 'adenia': ['adenia', 'idaean'], + 'adenochondroma': ['adenochondroma', 'chondroadenoma'], + 'adenocystoma': ['adenocystoma', 'cystoadenoma'], + 'adenofibroma': ['adenofibroma', 'fibroadenoma'], + 'adenolipoma': ['adenolipoma', 'palaemonoid'], + 'adenosarcoma': ['adenosarcoma', 'sarcoadenoma'], + 'adenylic': ['adenylic', 'lycaenid'], + 'adeptness': ['adeptness', 'pedantess'], + 'adequation': ['adequation', 'deaquation'], + 'adermia': ['adermia', 'madeira'], + 'adermin': ['adermin', 'amerind', 'dimeran'], + 'adet': ['adet', 'date', 'tade', 'tead', 'teda'], + 'adevism': ['adevism', 'vedaism'], + 'adhere': ['adhere', 'header', 'hedera', 'rehead'], + 'adherent': ['adherent', 'headrent', 'neatherd', 'threaden'], + 'adiaphon': ['adiaphon', 'aphodian'], + 'adib': ['adib', 'ibad'], + 'adicea': ['acadie', 'acedia', 'adicea'], + 'adicity': ['acidity', 'adicity'], + 'adiel': ['adiel', 'delia', 'ideal'], + 'adieux': ['adieux', 'exaudi'], + 'adighe': ['adighe', 'hidage'], + 'adin': ['adin', 'andi', 'dain', 'dani', 'dian', 'naid'], + 'adinole': ['adinole', 'idoneal'], + 'adion': ['adion', 'danio', 'doina', 'donia'], + 'adipocele': ['adipocele', 'cepolidae', 'ploceidae'], + 'adipocere': ['adipocere', 'percoidea'], + 'adipyl': ['adipyl', 'plaidy'], + 'adit': ['adit', 'dita'], + 'adital': ['adital', 'altaid'], + 'aditus': ['aditus', 'studia'], + 'adjuster': ['adjuster', 'readjust'], + 'adlai': ['adlai', 'alida'], + 'adlay': ['adlay', 'dayal'], + 'adlet': ['adlet', 'dealt', 'delta', 'lated', 'taled'], + 'adlumine': ['adlumine', 'unmailed'], + 'adman': ['adman', 'daman', 'namda'], + 'admi': ['admi', 'amid', 'madi', 'maid'], + 'adminicle': ['adminicle', 'medicinal'], + 'admire': ['admire', 'armied', 'damier', 'dimera', 'merida'], + 'admired': ['admired', 'diaderm'], + 'admirer': ['admirer', 'madrier', 'married'], + 'admissive': ['admissive', 'misadvise'], + 'admit': ['admit', 'atmid'], + 'admittee': ['admittee', 'meditate'], + 'admonisher': ['admonisher', 'rhamnoside'], + 'admonition': ['admonition', 'domination'], + 'admonitive': ['admonitive', 'dominative'], + 'admonitor': ['admonitor', 'dominator'], + 'adnascence': ['adnascence', 'ascendance'], + 'adnascent': ['adnascent', 'ascendant'], + 'adnate': ['adnate', 'entada'], + 'ado': ['ado', 'dao', 'oda'], + 'adobe': ['abode', 'adobe'], + 'adolph': ['adolph', 'pholad'], + 'adonai': ['adonai', 'adonia'], + 'adonia': ['adonai', 'adonia'], + 'adonic': ['adonic', 'anodic'], + 'adonin': ['adonin', 'nanoid', 'nonaid'], + 'adoniram': ['adoniram', 'radioman'], + 'adonize': ['adonize', 'anodize'], + 'adopter': ['adopter', 'protead', 'readopt'], + 'adoptian': ['adaption', 'adoptian'], + 'adoptianism': ['adaptionism', 'adoptianism'], + 'adoptional': ['adoptional', 'aplodontia'], + 'adorability': ['adorability', 'roadability'], + 'adorable': ['adorable', 'roadable'], + 'adorant': ['adorant', 'ondatra'], + 'adore': ['adore', 'oared', 'oread'], + 'adorer': ['adorer', 'roader'], + 'adorn': ['adorn', 'donar', 'drona', 'radon'], + 'adorner': ['adorner', 'readorn'], + 'adpao': ['adpao', 'apoda'], + 'adpromission': ['adpromission', 'proadmission'], + 'adream': ['adarme', 'adream'], + 'adrenin': ['adrenin', 'nardine'], + 'adrenine': ['adrenine', 'adrienne'], + 'adrenolytic': ['adrenolytic', 'declinatory'], + 'adrenotropic': ['adrenotropic', 'incorporated'], + 'adrian': ['adrian', 'andira', 'andria', 'radian', 'randia'], + 'adrienne': ['adrenine', 'adrienne'], + 'adrip': ['adrip', 'rapid'], + 'adroitly': ['adroitly', 'dilatory', 'idolatry'], + 'adrop': ['adrop', 'pardo'], + 'adry': ['adry', 'dray', 'yard'], + 'adscendent': ['adscendent', 'descendant'], + 'adsmith': ['adsmith', 'mahdist'], + 'adular': ['adular', 'aludra', 'radula'], + 'adulation': ['adulation', 'laudation'], + 'adulator': ['adulator', 'laudator'], + 'adulatory': ['adulatory', 'laudatory'], + 'adult': ['adult', 'dulat'], + 'adulterine': ['adulterine', 'laurentide'], + 'adultness': ['adultness', 'dauntless'], + 'adustion': ['adustion', 'sudation'], + 'advene': ['advene', 'evadne'], + 'adventism': ['adventism', 'vedantism'], + 'adventist': ['adventist', 'vedantist'], + 'adventure': ['adventure', 'unaverted'], + 'advice': ['advice', 'vedaic'], + 'ady': ['ady', 'day', 'yad'], + 'adz': ['adz', 'zad'], + 'adze': ['adze', 'daze'], + 'adzer': ['adzer', 'zerda'], + 'ae': ['ae', 'ea'], + 'aecidioform': ['aecidioform', 'formicoidea'], + 'aedilian': ['aedilian', 'laniidae'], + 'aedilic': ['aedilic', 'elaidic'], + 'aedility': ['aedility', 'ideality'], + 'aegipan': ['aegipan', 'apinage'], + 'aegirine': ['aegirine', 'erigenia'], + 'aegirite': ['aegirite', 'ariegite'], + 'aegle': ['aegle', 'eagle', 'galee'], + 'aenean': ['aenean', 'enaena'], + 'aeolharmonica': ['aeolharmonica', 'chloroanaemia'], + 'aeolian': ['aeolian', 'aeolina', 'aeonial'], + 'aeolic': ['aeolic', 'coelia'], + 'aeolina': ['aeolian', 'aeolina', 'aeonial'], + 'aeolis': ['aeolis', 'laiose'], + 'aeolist': ['aeolist', 'isolate'], + 'aeolistic': ['aeolistic', 'socialite'], + 'aeon': ['aeon', 'eoan'], + 'aeonial': ['aeolian', 'aeolina', 'aeonial'], + 'aeonist': ['aeonist', 'asiento', 'satieno'], + 'aer': ['aer', 'are', 'ear', 'era', 'rea'], + 'aerage': ['aerage', 'graeae'], + 'aerarian': ['aerarian', 'arenaria'], + 'aeration': ['aaronite', 'aeration'], + 'aerial': ['aerial', 'aralie'], + 'aeric': ['acier', 'aeric', 'ceria', 'erica'], + 'aerical': ['acieral', 'aerical'], + 'aeried': ['aeried', 'dearie'], + 'aerogenic': ['aerogenic', 'recoinage'], + 'aerographer': ['aerographer', 'areographer'], + 'aerographic': ['aerographic', 'areographic'], + 'aerographical': ['aerographical', 'areographical'], + 'aerography': ['aerography', 'areography'], + 'aerologic': ['aerologic', 'areologic'], + 'aerological': ['aerological', 'areological'], + 'aerologist': ['aerologist', 'areologist'], + 'aerology': ['aerology', 'areology'], + 'aeromantic': ['aeromantic', 'cameration', 'maceration', 'racemation'], + 'aerometer': ['aerometer', 'areometer'], + 'aerometric': ['aerometric', 'areometric'], + 'aerometry': ['aerometry', 'areometry'], + 'aeronautic': ['acetonuria', 'aeronautic'], + 'aeronautism': ['aeronautism', 'measuration'], + 'aerope': ['aerope', 'operae'], + 'aerophilic': ['aerophilic', 'epichorial'], + 'aerosol': ['aerosol', 'roseola'], + 'aerostatics': ['aerostatics', 'aortectasis'], + 'aery': ['aery', 'eyra', 'yare', 'year'], + 'aes': ['aes', 'ase', 'sea'], + 'aesthetic': ['aesthetic', 'chaetites'], + 'aethalioid': ['aethalioid', 'haliotidae'], + 'aetian': ['aetian', 'antiae', 'taenia'], + 'aetobatus': ['aetobatus', 'eastabout'], + 'afebrile': ['afebrile', 'balefire', 'fireable'], + 'afenil': ['afenil', 'finale'], + 'affair': ['affair', 'raffia'], + 'affecter': ['affecter', 'reaffect'], + 'affeer': ['affeer', 'raffee'], + 'affiance': ['affiance', 'caffeina'], + 'affirmer': ['affirmer', 'reaffirm'], + 'afflicter': ['afflicter', 'reafflict'], + 'affy': ['affy', 'yaff'], + 'afghan': ['afghan', 'hafgan'], + 'afield': ['afield', 'defial'], + 'afire': ['afire', 'feria'], + 'aflare': ['aflare', 'rafael'], + 'aflat': ['aflat', 'fatal'], + 'afresh': ['afresh', 'fasher', 'ferash'], + 'afret': ['afret', 'after'], + 'afric': ['afric', 'firca'], + 'afshar': ['afshar', 'ashraf'], + 'aft': ['aft', 'fat'], + 'after': ['afret', 'after'], + 'afteract': ['afteract', 'artefact', 'farcetta', 'farctate'], + 'afterage': ['afterage', 'fregatae'], + 'afterblow': ['afterblow', 'batfowler'], + 'aftercome': ['aftercome', 'forcemeat'], + 'aftercrop': ['aftercrop', 'prefactor'], + 'aftergo': ['aftergo', 'fagoter'], + 'afterguns': ['afterguns', 'transfuge'], + 'aftermath': ['aftermath', 'hamfatter'], + 'afterstate': ['afterstate', 'aftertaste'], + 'aftertaste': ['afterstate', 'aftertaste'], + 'afunctional': ['afunctional', 'unfactional'], + 'agade': ['adage', 'agade'], + 'agal': ['agal', 'agla', 'alga', 'gala'], + 'agalite': ['agalite', 'tailage', 'taliage'], + 'agalma': ['agalma', 'malaga'], + 'agama': ['agama', 'amaga'], + 'agamid': ['agamid', 'madiga'], + 'agapeti': ['agapeti', 'agpaite'], + 'agapornis': ['agapornis', 'sporangia'], + 'agar': ['agar', 'agra', 'gara', 'raga'], + 'agaricin': ['agaricin', 'garcinia'], + 'agau': ['agau', 'agua'], + 'aged': ['aged', 'egad', 'gade'], + 'ageless': ['ageless', 'eagless'], + 'agen': ['agen', 'gaen', 'gane', 'gean', 'gena'], + 'agenesic': ['agenesic', 'genesiac'], + 'agenesis': ['agenesis', 'assignee'], + 'agential': ['agential', 'alginate'], + 'agentive': ['agentive', 'negative'], + 'ager': ['ager', 'agre', 'gare', 'gear', 'rage'], + 'agger': ['agger', 'gager', 'regga'], + 'aggeration': ['aggeration', 'agregation'], + 'aggry': ['aggry', 'raggy'], + 'agialid': ['agialid', 'galidia'], + 'agib': ['agib', 'biga', 'gabi'], + 'agiel': ['agiel', 'agile', 'galei'], + 'agile': ['agiel', 'agile', 'galei'], + 'agileness': ['agileness', 'signalese'], + 'agistment': ['agistment', 'magnetist'], + 'agistor': ['agistor', 'agrotis', 'orgiast'], + 'agla': ['agal', 'agla', 'alga', 'gala'], + 'aglaos': ['aglaos', 'salago'], + 'aglare': ['aglare', 'alegar', 'galera', 'laager'], + 'aglet': ['aglet', 'galet'], + 'agley': ['agley', 'galey'], + 'agmatine': ['agmatine', 'agminate'], + 'agminate': ['agmatine', 'agminate'], + 'agminated': ['agminated', 'diamagnet'], + 'agnail': ['agnail', 'linaga'], + 'agname': ['agname', 'manage'], + 'agnel': ['agnel', 'angel', 'angle', 'galen', 'genal', 'glean', 'lagen'], + 'agnes': ['agnes', 'gesan'], + 'agnize': ['agnize', 'ganzie'], + 'agnosis': ['agnosis', 'ganosis'], + 'agnostic': ['agnostic', 'coasting'], + 'agnus': ['agnus', 'angus', 'sugan'], + 'ago': ['ago', 'goa'], + 'agon': ['agon', 'ango', 'gaon', 'goan', 'gona'], + 'agonal': ['agonal', 'angola'], + 'agone': ['agone', 'genoa'], + 'agoniadin': ['agoniadin', 'anangioid', 'ganoidian'], + 'agonic': ['agonic', 'angico', 'gaonic', 'goniac'], + 'agonista': ['agonista', 'santiago'], + 'agonizer': ['agonizer', 'orangize', 'organize'], + 'agpaite': ['agapeti', 'agpaite'], + 'agra': ['agar', 'agra', 'gara', 'raga'], + 'agral': ['agral', 'argal'], + 'agrania': ['agrania', 'angaria', 'niagara'], + 'agre': ['ager', 'agre', 'gare', 'gear', 'rage'], + 'agree': ['agree', 'eager', 'eagre'], + 'agreed': ['agreed', 'geared'], + 'agregation': ['aggeration', 'agregation'], + 'agrege': ['agrege', 'raggee'], + 'agrestian': ['agrestian', 'gerastian', 'stangeria'], + 'agrestic': ['agrestic', 'ergastic'], + 'agria': ['agria', 'igara'], + 'agricolist': ['agricolist', 'algoristic'], + 'agrilus': ['agrilus', 'gularis'], + 'agrin': ['agrin', 'grain'], + 'agrito': ['agrito', 'ortiga'], + 'agroan': ['agroan', 'angora', 'anogra', 'arango', 'argoan', 'onagra'], + 'agrom': ['agrom', 'morga'], + 'agrotis': ['agistor', 'agrotis', 'orgiast'], + 'aground': ['aground', 'durango'], + 'agrufe': ['agrufe', 'gaufer', 'gaufre'], + 'agrypnia': ['agrypnia', 'paginary'], + 'agsam': ['agsam', 'magas'], + 'agua': ['agau', 'agua'], + 'ague': ['ague', 'auge'], + 'agush': ['agush', 'saugh'], + 'agust': ['agust', 'tsuga'], + 'agy': ['agy', 'gay'], + 'ah': ['ah', 'ha'], + 'ahem': ['ahem', 'haem', 'hame'], + 'ahet': ['ahet', 'haet', 'hate', 'heat', 'thea'], + 'ahey': ['ahey', 'eyah', 'yeah'], + 'ahind': ['ahind', 'dinah'], + 'ahint': ['ahint', 'hiant', 'tahin'], + 'ahir': ['ahir', 'hair'], + 'ahmed': ['ahmed', 'hemad'], + 'ahmet': ['ahmet', 'thema'], + 'aho': ['aho', 'hao'], + 'ahom': ['ahom', 'moha'], + 'ahong': ['ahong', 'hogan'], + 'ahorse': ['ahorse', 'ashore', 'hoarse', 'shorea'], + 'ahoy': ['ahoy', 'hoya'], + 'ahriman': ['ahriman', 'miranha'], + 'ahsan': ['ahsan', 'hansa', 'hasan'], + 'aht': ['aht', 'hat', 'tha'], + 'ahtena': ['ahtena', 'aneath', 'athena'], + 'ahu': ['ahu', 'auh', 'hau'], + 'ahum': ['ahum', 'huma'], + 'ahunt': ['ahunt', 'haunt', 'thuan', 'unhat'], + 'aid': ['aid', 'ida'], + 'aidance': ['aidance', 'canidae'], + 'aide': ['aide', 'idea'], + 'aidenn': ['aidenn', 'andine', 'dannie', 'indane'], + 'aider': ['aider', 'deair', 'irade', 'redia'], + 'aides': ['aides', 'aside', 'sadie'], + 'aiel': ['aiel', 'aile', 'elia'], + 'aiglet': ['aiglet', 'ligate', 'taigle', 'tailge'], + 'ail': ['ail', 'ila', 'lai'], + 'ailantine': ['ailantine', 'antialien'], + 'ailanto': ['ailanto', 'alation', 'laotian', 'notalia'], + 'aile': ['aiel', 'aile', 'elia'], + 'aileen': ['aileen', 'elaine'], + 'aileron': ['aileron', 'alienor'], + 'ailing': ['ailing', 'angili', 'nilgai'], + 'ailment': ['ailment', 'aliment'], + 'aim': ['aim', 'ami', 'ima'], + 'aimer': ['aimer', 'maire', 'marie', 'ramie'], + 'aimless': ['aimless', 'melissa', 'seismal'], + 'ainaleh': ['ainaleh', 'halenia'], + 'aint': ['aint', 'anti', 'tain', 'tina'], + 'aion': ['aion', 'naio'], + 'air': ['air', 'ira', 'ria'], + 'aira': ['aira', 'aria', 'raia'], + 'airan': ['airan', 'arain', 'arian'], + 'airdrome': ['airdrome', 'armoried'], + 'aire': ['aire', 'eria'], + 'airer': ['airer', 'arrie'], + 'airlike': ['airlike', 'kiliare'], + 'airman': ['airman', 'amarin', 'marian', 'marina', 'mirana'], + 'airplane': ['airplane', 'perianal'], + 'airplanist': ['airplanist', 'triplasian'], + 'airt': ['airt', 'rita', 'tari', 'tiar'], + 'airy': ['airy', 'yair'], + 'aisle': ['aisle', 'elias'], + 'aisled': ['aisled', 'deasil', 'ladies', 'sailed'], + 'aisling': ['aisling', 'sailing'], + 'aissor': ['aissor', 'rissoa'], + 'ait': ['ait', 'ati', 'ita', 'tai'], + 'aitch': ['aitch', 'chait', 'chati', 'chita', 'taich', 'tchai'], + 'aition': ['aition', 'itonia'], + 'aizle': ['aizle', 'eliza'], + 'ajar': ['ajar', 'jara', 'raja'], + 'ajhar': ['ajhar', 'rajah'], + 'ajuga': ['ajuga', 'jagua'], + 'ak': ['ak', 'ka'], + 'akal': ['akal', 'kala'], + 'akali': ['akali', 'alaki'], + 'akan': ['akan', 'kana'], + 'ake': ['ake', 'kea'], + 'akebi': ['akebi', 'bakie'], + 'akha': ['akha', 'kaha'], + 'akim': ['akim', 'maki'], + 'akin': ['akin', 'kina', 'naik'], + 'akka': ['akka', 'kaka'], + 'aknee': ['aknee', 'ankee', 'keena'], + 'ako': ['ako', 'koa', 'oak', 'oka'], + 'akoasma': ['akoasma', 'amakosa'], + 'aku': ['aku', 'auk', 'kua'], + 'al': ['al', 'la'], + 'ala': ['aal', 'ala'], + 'alacritous': ['alacritous', 'lactarious', 'lactosuria'], + 'alain': ['alain', 'alani', 'liana'], + 'alaki': ['akali', 'alaki'], + 'alalite': ['alalite', 'tillaea'], + 'alamo': ['alamo', 'aloma'], + 'alan': ['alan', 'anal', 'lana'], + 'alangin': ['alangin', 'anginal', 'anglian', 'nagnail'], + 'alangine': ['alangine', 'angelina', 'galenian'], + 'alani': ['alain', 'alani', 'liana'], + 'alanine': ['alanine', 'linnaea'], + 'alans': ['alans', 'lanas', 'nasal'], + 'alantic': ['actinal', 'alantic', 'alicant', 'antical'], + 'alantolic': ['alantolic', 'allantoic'], + 'alanyl': ['alanyl', 'anally'], + 'alares': ['alares', 'arales'], + 'alaria': ['alaria', 'aralia'], + 'alaric': ['alaric', 'racial'], + 'alarm': ['alarm', 'malar', 'maral', 'marla', 'ramal'], + 'alarmable': ['alarmable', 'ambarella'], + 'alarmedly': ['alarmedly', 'medallary'], + 'alarming': ['alarming', 'marginal'], + 'alarmingly': ['alarmingly', 'marginally'], + 'alarmist': ['alarmist', 'alastrim'], + 'alas': ['alas', 'lasa'], + 'alastair': ['alastair', 'salariat'], + 'alaster': ['alaster', 'tarsale'], + 'alastrim': ['alarmist', 'alastrim'], + 'alatern': ['alatern', 'lateran'], + 'alaternus': ['alaternus', 'saturnale'], + 'alation': ['ailanto', 'alation', 'laotian', 'notalia'], + 'alb': ['alb', 'bal', 'lab'], + 'alba': ['alba', 'baal', 'bala'], + 'alban': ['alban', 'balan', 'banal', 'laban', 'nabal', 'nabla'], + 'albanite': ['albanite', 'balanite', 'nabalite'], + 'albardine': ['albardine', 'drainable'], + 'albarium': ['albarium', 'brumalia'], + 'albata': ['albata', 'atabal', 'balata'], + 'albatros': ['abrastol', 'albatros'], + 'albe': ['abel', 'able', 'albe', 'bale', 'beal', 'bela', 'blae'], + 'albedo': ['albedo', 'doable'], + 'albee': ['abele', 'albee'], + 'albeit': ['albeit', + 'albite', + 'baltei', + 'belait', + 'betail', + 'bletia', + 'libate'], + 'albert': ['albert', 'balter', 'labret', 'tabler'], + 'alberta': ['alberta', 'latebra', 'ratable'], + 'albertina': ['albertina', 'trainable'], + 'alberto': ['alberto', 'bloater', 'latrobe'], + 'albetad': ['albetad', 'datable'], + 'albi': ['albi', 'bail', 'bali'], + 'albian': ['albian', 'bilaan'], + 'albin': ['albin', 'binal', 'blain'], + 'albino': ['albino', 'albion', 'alboin', 'oliban'], + 'albion': ['albino', 'albion', 'alboin', 'oliban'], + 'albite': ['albeit', + 'albite', + 'baltei', + 'belait', + 'betail', + 'bletia', + 'libate'], + 'alboin': ['albino', 'albion', 'alboin', 'oliban'], + 'alboranite': ['alboranite', 'rationable'], + 'albronze': ['albronze', 'blazoner'], + 'albuca': ['albuca', 'bacula'], + 'albuminate': ['albuminate', 'antelabium'], + 'alburnum': ['alburnum', 'laburnum'], + 'alcaic': ['alcaic', 'cicala'], + 'alcaide': ['alcaide', 'alcidae'], + 'alcamine': ['alcamine', 'analcime', 'calamine', 'camelina'], + 'alcantarines': ['alcantarines', 'lancasterian'], + 'alcedo': ['alcedo', 'dacelo'], + 'alces': ['alces', 'casel', 'scale'], + 'alchemic': ['alchemic', 'chemical'], + 'alchemistic': ['alchemistic', 'hemiclastic'], + 'alchera': ['alchera', 'archeal'], + 'alchitran': ['alchitran', 'clathrina'], + 'alcicornium': ['acroclinium', 'alcicornium'], + 'alcidae': ['alcaide', 'alcidae'], + 'alcidine': ['alcidine', 'danielic', 'lecaniid'], + 'alcine': ['alcine', 'ancile'], + 'alco': ['alco', 'coal', 'cola', 'loca'], + 'alcoate': ['alcoate', 'coelata'], + 'alcogel': ['alcogel', 'collage'], + 'alcor': ['alcor', 'calor', 'carlo', 'carol', 'claro', 'coral'], + 'alcoran': ['alcoran', 'ancoral', 'carolan'], + 'alcoranic': ['acronical', 'alcoranic'], + 'alcove': ['alcove', 'coeval', 'volcae'], + 'alcyon': ['alcyon', 'cyanol'], + 'alcyone': ['alcyone', 'cyanole'], + 'aldamine': ['aldamine', 'lamnidae'], + 'aldeament': ['aldeament', 'mandelate'], + 'alder': ['alder', 'daler', 'lader'], + 'aldermanry': ['aldermanry', 'marylander'], + 'aldern': ['aldern', + 'darnel', + 'enlard', + 'lander', + 'lenard', + 'randle', + 'reland'], + 'aldime': ['aldime', 'mailed', 'medial'], + 'aldine': ['aldine', 'daniel', 'delian', 'denial', 'enalid', 'leadin'], + 'aldus': ['aldus', 'sauld'], + 'ale': ['ale', 'lea'], + 'alec': ['acle', 'alec', 'lace'], + 'aleconner': ['aleconner', 'noncereal'], + 'alecost': ['alecost', 'lactose', 'scotale', 'talcose'], + 'alectoris': ['alectoris', 'sarcolite', 'sclerotia', 'sectorial'], + 'alectrion': ['alectrion', 'clarionet', 'crotaline', 'locarnite'], + 'alectryon': ['alectryon', 'tolerancy'], + 'alecup': ['alecup', 'clupea'], + 'alef': ['alef', 'feal', 'flea', 'leaf'], + 'aleft': ['aleft', 'alfet', 'fetal', 'fleta'], + 'alegar': ['aglare', 'alegar', 'galera', 'laager'], + 'alem': ['alem', 'alme', 'lame', 'leam', 'male', 'meal', 'mela'], + 'alemanni': ['alemanni', 'melanian'], + 'alemite': ['alemite', 'elamite'], + 'alen': ['alen', 'lane', 'lean', 'lena', 'nael', 'neal'], + 'aleph': ['aleph', 'pheal'], + 'alepot': ['alepot', 'pelota'], + 'alerce': ['alerce', 'cereal', 'relace'], + 'alerse': ['alerse', 'leaser', 'reales', 'resale', 'reseal', 'sealer'], + 'alert': ['alert', 'alter', 'artel', 'later', 'ratel', 'taler', 'telar'], + 'alertly': ['alertly', 'elytral'], + 'alestake': ['alestake', 'eastlake'], + 'aletap': ['aletap', 'palate', 'platea'], + 'aletris': ['aletris', 'alister', 'listera', 'realist', 'saltier'], + 'aleuronic': ['aleuronic', 'urceolina'], + 'aleut': ['aleut', 'atule'], + 'aleutic': ['aleutic', 'auletic', 'caulite', 'lutecia'], + 'alevin': ['alevin', 'alvine', 'valine', 'veinal', 'venial', 'vineal'], + 'alex': ['alex', 'axel', 'axle'], + 'alexandrian': ['alexandrian', 'alexandrina'], + 'alexandrina': ['alexandrian', 'alexandrina'], + 'alexin': ['alexin', 'xenial'], + 'aleyard': ['aleyard', 'already'], + 'alfenide': ['alfenide', 'enfilade'], + 'alfet': ['aleft', 'alfet', 'fetal', 'fleta'], + 'alfred': ['alfred', 'fardel'], + 'alfur': ['alfur', 'fural'], + 'alga': ['agal', 'agla', 'alga', 'gala'], + 'algae': ['algae', 'galea'], + 'algal': ['algal', 'galla'], + 'algebar': ['algebar', 'algebra'], + 'algebra': ['algebar', 'algebra'], + 'algedi': ['algedi', 'galeid'], + 'algedo': ['algedo', 'geodal'], + 'algedonic': ['algedonic', 'genocidal'], + 'algenib': ['algenib', 'bealing', 'belgian', 'bengali'], + 'algerian': ['algerian', 'geranial', 'regalian'], + 'algernon': ['algernon', 'nonglare'], + 'algesia': ['algesia', 'sailage'], + 'algesis': ['algesis', 'glassie'], + 'algieba': ['algieba', 'bailage'], + 'algin': ['algin', 'align', 'langi', 'liang', 'linga'], + 'alginate': ['agential', 'alginate'], + 'algine': ['algine', 'genial', 'linage'], + 'algist': ['algist', 'gaslit'], + 'algometer': ['algometer', 'glomerate'], + 'algometric': ['algometric', 'melotragic'], + 'algomian': ['algomian', 'magnolia'], + 'algor': ['algor', 'argol', 'goral', 'largo'], + 'algoristic': ['agricolist', 'algoristic'], + 'algorithm': ['algorithm', 'logarithm'], + 'algorithmic': ['algorithmic', 'logarithmic'], + 'algraphic': ['algraphic', 'graphical'], + 'algum': ['algum', 'almug', 'glaum', 'gluma', 'mulga'], + 'alibility': ['alibility', 'liability'], + 'alible': ['alible', 'belial', 'labile', 'liable'], + 'alicant': ['actinal', 'alantic', 'alicant', 'antical'], + 'alice': ['alice', 'celia', 'ileac'], + 'alichel': ['alichel', 'challie', 'helical'], + 'alida': ['adlai', 'alida'], + 'alien': ['alien', 'aline', 'anile', 'elain', 'elian', 'laine', 'linea'], + 'alienation': ['alienation', 'alineation'], + 'alienator': ['alienator', 'rationale'], + 'alienism': ['alienism', 'milesian'], + 'alienor': ['aileron', 'alienor'], + 'alif': ['alif', 'fail'], + 'aligerous': ['aligerous', 'glaireous'], + 'align': ['algin', 'align', 'langi', 'liang', 'linga'], + 'aligner': ['aligner', 'engrail', 'realign', 'reginal'], + 'alignment': ['alignment', 'lamenting'], + 'alike': ['alike', 'lakie'], + 'alikeness': ['alikeness', 'leakiness'], + 'alima': ['alima', 'lamia'], + 'aliment': ['ailment', 'aliment'], + 'alimenter': ['alimenter', 'marteline'], + 'alimentic': ['alimentic', 'antilemic', 'melanitic', 'metanilic'], + 'alimonied': ['alimonied', 'maleinoid'], + 'alin': ['alin', 'anil', 'lain', 'lina', 'nail'], + 'aline': ['alien', 'aline', 'anile', 'elain', 'elian', 'laine', 'linea'], + 'alineation': ['alienation', 'alineation'], + 'aliped': ['aliped', 'elapid'], + 'aliptes': ['aliptes', 'pastile', 'talipes'], + 'aliptic': ['aliptic', 'aplitic'], + 'aliseptal': ['aliseptal', 'pallasite'], + 'alish': ['alish', 'hilsa'], + 'alisier': ['alisier', 'israeli'], + 'aliso': ['aliso', 'alois'], + 'alison': ['alison', 'anolis'], + 'alisp': ['alisp', 'lapsi'], + 'alist': ['alist', 'litas', 'slait', 'talis'], + 'alister': ['aletris', 'alister', 'listera', 'realist', 'saltier'], + 'alit': ['alit', 'tail', 'tali'], + 'alite': ['alite', 'laeti'], + 'aliunde': ['aliunde', 'unideal'], + 'aliveness': ['aliveness', 'vealiness'], + 'alix': ['alix', 'axil'], + 'alk': ['alk', 'lak'], + 'alkalizer': ['alkalizer', 'lazarlike'], + 'alkamin': ['alkamin', 'malakin'], + 'alkene': ['alkene', 'lekane'], + 'alkes': ['alkes', 'sakel', 'slake'], + 'alkine': ['alkine', 'ilkane', 'inlake', 'inleak'], + 'alky': ['alky', 'laky'], + 'alkylic': ['alkylic', 'lilacky'], + 'allagite': ['allagite', 'alligate', 'talliage'], + 'allah': ['allah', 'halal'], + 'allantoic': ['alantolic', 'allantoic'], + 'allay': ['allay', 'yalla'], + 'allayer': ['allayer', 'yallaer'], + 'allbone': ['allbone', 'bellona'], + 'alle': ['alle', 'ella', 'leal'], + 'allecret': ['allecret', 'cellaret'], + 'allegate': ['allegate', 'ellagate'], + 'allegorist': ['allegorist', 'legislator'], + 'allergen': ['allergen', 'generall'], + 'allergia': ['allergia', 'galleria'], + 'allergin': ['allergin', 'gralline'], + 'allergy': ['allergy', 'gallery', 'largely', 'regally'], + 'alliable': ['alliable', 'labiella'], + 'alliably': ['alliably', 'labially'], + 'alliance': ['alliance', 'canaille'], + 'alliancer': ['alliancer', 'ralliance'], + 'allie': ['allie', 'leila', 'lelia'], + 'allies': ['allies', 'aselli'], + 'alligate': ['allagite', 'alligate', 'talliage'], + 'allium': ['allium', 'alulim', 'muilla'], + 'allocation': ['allocation', 'locational'], + 'allocute': ['allocute', 'loculate'], + 'allocution': ['allocution', 'loculation'], + 'allogenic': ['allogenic', 'collegian'], + 'allonym': ['allonym', 'malonyl'], + 'allopathy': ['allopathy', 'lalopathy'], + 'allopatric': ['allopatric', 'patrilocal'], + 'allot': ['allot', 'atoll'], + 'allothogenic': ['allothogenic', 'ethnological'], + 'allover': ['allover', 'overall'], + 'allower': ['allower', 'reallow'], + 'alloy': ['alloy', 'loyal'], + 'allude': ['allude', 'aludel'], + 'allure': ['allure', 'laurel'], + 'alma': ['alma', 'amla', 'lama', 'mala'], + 'almach': ['almach', 'chamal'], + 'almaciga': ['almaciga', 'macaglia'], + 'almain': ['almain', 'animal', 'lamina', 'manila'], + 'alman': ['alman', 'lamna', 'manal'], + 'almandite': ['almandite', 'laminated'], + 'alme': ['alem', 'alme', 'lame', 'leam', 'male', 'meal', 'mela'], + 'almerian': ['almerian', 'manerial'], + 'almoign': ['almoign', 'loaming'], + 'almon': ['almon', 'monal'], + 'almond': ['almond', 'dolman'], + 'almoner': ['almoner', 'moneral', 'nemoral'], + 'almonry': ['almonry', 'romanly'], + 'alms': ['alms', 'salm', 'slam'], + 'almuce': ['almuce', 'caelum', 'macule'], + 'almude': ['almude', 'maudle'], + 'almug': ['algum', 'almug', 'glaum', 'gluma', 'mulga'], + 'almuten': ['almuten', 'emulant'], + 'aln': ['aln', 'lan'], + 'alnage': ['alnage', 'angela', 'galena', 'lagena'], + 'alnico': ['alnico', 'cliona', 'oilcan'], + 'alnilam': ['alnilam', 'manilla'], + 'alnoite': ['alnoite', 'elation', 'toenail'], + 'alnuin': ['alnuin', 'unnail'], + 'alo': ['alo', 'lao', 'loa'], + 'alochia': ['acholia', 'alochia'], + 'alod': ['alod', 'dola', 'load', 'odal'], + 'aloe': ['aloe', 'olea'], + 'aloetic': ['aloetic', 'coalite'], + 'aloft': ['aloft', 'float', 'flota'], + 'alogian': ['alogian', 'logania'], + 'alogical': ['alogical', 'colalgia'], + 'aloid': ['aloid', 'dolia', 'idola'], + 'aloin': ['aloin', 'anoil', 'anoli'], + 'alois': ['aliso', 'alois'], + 'aloma': ['alamo', 'aloma'], + 'alone': ['alone', 'anole', 'olena'], + 'along': ['along', 'gonal', 'lango', 'longa', 'nogal'], + 'alonso': ['alonso', 'alsoon', 'saloon'], + 'alonzo': ['alonzo', 'zoonal'], + 'alop': ['alop', 'opal'], + 'alopecist': ['alopecist', 'altiscope', 'epicostal', 'scapolite'], + 'alosa': ['alosa', 'loasa', 'oasal'], + 'alose': ['alose', 'osela', 'solea'], + 'alow': ['alow', 'awol', 'lowa'], + 'aloxite': ['aloxite', 'oxalite'], + 'alp': ['alp', 'lap', 'pal'], + 'alpeen': ['alpeen', 'lenape', 'pelean'], + 'alpen': ['alpen', 'nepal', 'panel', 'penal', 'plane'], + 'alpestral': ['alpestral', 'palestral'], + 'alpestrian': ['alpestrian', 'palestrian', 'psalterian'], + 'alpestrine': ['alpestrine', 'episternal', 'interlapse', 'presential'], + 'alphenic': ['alphenic', 'cephalin'], + 'alphonse': ['alphonse', 'phenosal'], + 'alphos': ['alphos', 'pholas'], + 'alphosis': ['alphosis', 'haplosis'], + 'alpid': ['alpid', 'plaid'], + 'alpieu': ['alpieu', 'paulie'], + 'alpine': ['alpine', 'nepali', 'penial', 'pineal'], + 'alpinist': ['alpinist', 'antislip'], + 'alpist': ['alpist', 'pastil', 'spital'], + 'alraun': ['alraun', 'alruna', 'ranula'], + 'already': ['aleyard', 'already'], + 'alrighty': ['alrighty', 'arightly'], + 'alruna': ['alraun', 'alruna', 'ranula'], + 'alsine': ['alsine', 'neslia', 'saline', 'selina', 'silane'], + 'also': ['also', 'sola'], + 'alsoon': ['alonso', 'alsoon', 'saloon'], + 'alstonidine': ['alstonidine', 'nonidealist'], + 'alstonine': ['alstonine', 'tensional'], + 'alt': ['alt', 'lat', 'tal'], + 'altaian': ['altaian', 'latania', 'natalia'], + 'altaic': ['altaic', 'altica'], + 'altaid': ['adital', 'altaid'], + 'altair': ['altair', 'atrail', 'atrial', 'lariat', 'latria', 'talari'], + 'altamira': ['altamira', 'matralia'], + 'altar': ['altar', 'artal', 'ratal', 'talar'], + 'altared': ['altared', 'laterad'], + 'altarist': ['altarist', 'striatal'], + 'alter': ['alert', 'alter', 'artel', 'later', 'ratel', 'taler', 'telar'], + 'alterability': ['alterability', 'bilaterality', 'relatability'], + 'alterable': ['alterable', 'relatable'], + 'alterant': ['alterant', 'tarletan'], + 'alterer': ['alterer', 'realter', 'relater'], + 'altern': ['altern', 'antler', 'learnt', 'rental', 'ternal'], + 'alterne': ['alterne', 'enteral', 'eternal', 'teleran', 'teneral'], + 'althea': ['althea', 'elatha'], + 'altho': ['altho', 'lhota', 'loath'], + 'althorn': ['althorn', 'anthrol', 'thronal'], + 'altica': ['altaic', 'altica'], + 'altin': ['altin', 'latin'], + 'altiscope': ['alopecist', 'altiscope', 'epicostal', 'scapolite'], + 'altitude': ['altitude', 'latitude'], + 'altitudinal': ['altitudinal', 'latitudinal'], + 'altitudinarian': ['altitudinarian', 'latitudinarian'], + 'alto': ['alto', 'lota'], + 'altrices': ['altrices', 'selictar'], + 'altruism': ['altruism', 'muralist', 'traulism', 'ultraism'], + 'altruist': ['altruist', 'ultraist'], + 'altruistic': ['altruistic', 'truistical', 'ultraistic'], + 'aludel': ['allude', 'aludel'], + 'aludra': ['adular', 'aludra', 'radula'], + 'alulet': ['alulet', 'luteal'], + 'alulim': ['allium', 'alulim', 'muilla'], + 'alum': ['alum', 'maul'], + 'aluminate': ['aluminate', 'alumniate'], + 'aluminide': ['aluminide', 'unimedial'], + 'aluminosilicate': ['aluminosilicate', 'silicoaluminate'], + 'alumna': ['alumna', 'manual'], + 'alumni': ['alumni', 'unmail'], + 'alumniate': ['aluminate', 'alumniate'], + 'alur': ['alur', 'laur', 'lura', 'raul', 'ural'], + 'alure': ['alure', 'ureal'], + 'alurgite': ['alurgite', 'ligature'], + 'aluta': ['aluta', 'taula'], + 'alvan': ['alvan', 'naval'], + 'alvar': ['alvar', 'arval', 'larva'], + 'alveus': ['alveus', 'avulse'], + 'alvin': ['alvin', 'anvil', 'nival', 'vinal'], + 'alvine': ['alevin', 'alvine', 'valine', 'veinal', 'venial', 'vineal'], + 'aly': ['aly', 'lay'], + 'alypin': ['alypin', 'pialyn'], + 'alytes': ['alytes', 'astely', 'lysate', 'stealy'], + 'am': ['am', 'ma'], + 'ama': ['aam', 'ama'], + 'amacrine': ['amacrine', 'american', 'camerina', 'cinerama'], + 'amadi': ['amadi', 'damia', 'madia', 'maida'], + 'amaethon': ['amaethon', 'thomaean'], + 'amaga': ['agama', 'amaga'], + 'amah': ['amah', 'maha'], + 'amain': ['amain', 'amani', 'amnia', 'anima', 'mania'], + 'amakosa': ['akoasma', 'amakosa'], + 'amalgam': ['amalgam', 'malagma'], + 'amang': ['amang', 'ganam', 'manga'], + 'amani': ['amain', 'amani', 'amnia', 'anima', 'mania'], + 'amanist': ['amanist', 'stamina'], + 'amanitin': ['amanitin', 'maintain'], + 'amanitine': ['amanitine', 'inanimate'], + 'amanori': ['amanori', 'moarian'], + 'amapa': ['amapa', 'apama'], + 'amar': ['amar', 'amra', 'mara', 'rama'], + 'amarin': ['airman', 'amarin', 'marian', 'marina', 'mirana'], + 'amaroid': ['amaroid', 'diorama'], + 'amarth': ['amarth', 'martha'], + 'amass': ['amass', 'assam', 'massa', 'samas'], + 'amasser': ['amasser', 'reamass'], + 'amati': ['amati', 'amita', 'matai'], + 'amatorian': ['amatorian', 'inamorata'], + 'amaurosis': ['amaurosis', 'mosasauri'], + 'amazonite': ['amazonite', 'anatomize'], + 'amba': ['amba', 'maba'], + 'ambar': ['abram', 'ambar'], + 'ambarella': ['alarmable', 'ambarella'], + 'ambash': ['ambash', 'shamba'], + 'ambay': ['ambay', 'mbaya'], + 'ambeer': ['ambeer', 'beamer'], + 'amber': ['amber', 'bearm', 'bemar', 'bream', 'embar'], + 'ambier': ['ambier', 'bremia', 'embira'], + 'ambit': ['ambit', 'imbat'], + 'ambivert': ['ambivert', 'verbatim'], + 'amble': ['amble', 'belam', 'blame', 'mabel'], + 'ambler': ['ambler', 'blamer', 'lamber', 'marble', 'ramble'], + 'ambling': ['ambling', 'blaming'], + 'amblingly': ['amblingly', 'blamingly'], + 'ambo': ['ambo', 'boma'], + 'ambos': ['ambos', 'sambo'], + 'ambrein': ['ambrein', 'mirbane'], + 'ambrette': ['ambrette', 'tambreet'], + 'ambrose': ['ambrose', 'mesobar'], + 'ambrosia': ['ambrosia', 'saboraim'], + 'ambrosin': ['ambrosin', 'barosmin', 'sabromin'], + 'ambry': ['ambry', 'barmy'], + 'ambury': ['ambury', 'aumbry'], + 'ambush': ['ambush', 'shambu'], + 'amchoor': ['amchoor', 'ochroma'], + 'ame': ['ame', 'mae'], + 'ameed': ['adeem', 'ameed', 'edema'], + 'ameen': ['ameen', 'amene', 'enema'], + 'amelification': ['amelification', 'maleficiation'], + 'ameliorant': ['ameliorant', 'lomentaria'], + 'amellus': ['amellus', 'malleus'], + 'amelu': ['amelu', 'leuma', 'ulema'], + 'amelus': ['amelus', 'samuel'], + 'amen': ['amen', 'enam', 'mane', 'mean', 'name', 'nema'], + 'amenability': ['amenability', 'nameability'], + 'amenable': ['amenable', 'nameable'], + 'amend': ['amend', 'mande', 'maned'], + 'amende': ['amende', 'demean', 'meaned', 'nadeem'], + 'amender': ['amender', 'meander', 'reamend', 'reedman'], + 'amends': ['amends', 'desman'], + 'amene': ['ameen', 'amene', 'enema'], + 'amenia': ['amenia', 'anemia'], + 'amenism': ['amenism', 'immanes', 'misname'], + 'amenite': ['amenite', 'etamine', 'matinee'], + 'amenorrheal': ['amenorrheal', 'melanorrhea'], + 'ament': ['ament', 'meant', 'teman'], + 'amental': ['amental', 'leatman'], + 'amentia': ['amentia', 'aminate', 'anamite', 'animate'], + 'amerce': ['amerce', 'raceme'], + 'amercer': ['amercer', 'creamer'], + 'american': ['amacrine', 'american', 'camerina', 'cinerama'], + 'amerind': ['adermin', 'amerind', 'dimeran'], + 'amerism': ['amerism', 'asimmer', 'sammier'], + 'ameristic': ['ameristic', 'armistice', 'artemisic'], + 'amesite': ['amesite', 'mesitae', 'semitae'], + 'ametria': ['ametria', 'artemia', 'meratia', 'ramaite'], + 'ametrope': ['ametrope', 'metapore'], + 'amex': ['amex', 'exam', 'xema'], + 'amgarn': ['amgarn', 'mangar', 'marang', 'ragman'], + 'amhar': ['amhar', 'mahar', 'mahra'], + 'amherstite': ['amherstite', 'hemistater'], + 'amhran': ['amhran', 'harman', 'mahran'], + 'ami': ['aim', 'ami', 'ima'], + 'amia': ['amia', 'maia'], + 'amic': ['amic', 'mica'], + 'amical': ['amical', 'camail', 'lamaic'], + 'amiced': ['amiced', 'decima'], + 'amicicide': ['amicicide', 'cimicidae'], + 'amicron': ['amicron', 'marconi', 'minorca', 'romanic'], + 'amid': ['admi', 'amid', 'madi', 'maid'], + 'amidate': ['adamite', 'amidate'], + 'amide': ['amide', 'damie', 'media'], + 'amidide': ['amidide', 'diamide', 'mididae'], + 'amidin': ['amidin', 'damnii'], + 'amidine': ['amidine', 'diamine'], + 'amidism': ['amidism', 'maidism'], + 'amidist': ['amidist', 'dimatis'], + 'amidon': ['amidon', 'daimon', 'domain'], + 'amidophenol': ['amidophenol', 'monodelphia'], + 'amidst': ['amidst', 'datism'], + 'amigo': ['amigo', 'imago'], + 'amiidae': ['amiidae', 'maiidae'], + 'amil': ['amil', 'amli', 'lima', 'mail', 'mali', 'mila'], + 'amiles': ['amiles', 'asmile', 'mesail', 'mesial', 'samiel'], + 'amimia': ['amimia', 'miamia'], + 'amimide': ['amimide', 'mimidae'], + 'amin': ['amin', 'main', 'mani', 'mian', 'mina', 'naim'], + 'aminate': ['amentia', 'aminate', 'anamite', 'animate'], + 'amination': ['amination', 'animation'], + 'amine': ['amine', 'anime', 'maine', 'manei'], + 'amini': ['amini', 'animi'], + 'aminize': ['aminize', 'animize', 'azimine'], + 'amino': ['amino', 'inoma', 'naomi', 'omani', 'omina'], + 'aminoplast': ['aminoplast', 'plasmation'], + 'amintor': ['amintor', 'tormina'], + 'amir': ['amir', 'irma', 'mari', 'mira', 'rami', 'rima'], + 'amiranha': ['amiranha', 'maharani'], + 'amiray': ['amiray', 'myaria'], + 'amissness': ['amissness', 'massiness'], + 'amita': ['amati', 'amita', 'matai'], + 'amitosis': ['amitosis', 'omasitis'], + 'amixia': ['amixia', 'ixiama'], + 'amla': ['alma', 'amla', 'lama', 'mala'], + 'amli': ['amil', 'amli', 'lima', 'mail', 'mali', 'mila'], + 'amlong': ['amlong', 'logman'], + 'amma': ['amma', 'maam'], + 'ammelin': ['ammelin', 'limeman'], + 'ammeline': ['ammeline', 'melamine'], + 'ammeter': ['ammeter', 'metamer'], + 'ammi': ['ammi', 'imam', 'maim', 'mima'], + 'ammine': ['ammine', 'immane'], + 'ammo': ['ammo', 'mamo'], + 'ammonic': ['ammonic', 'mocmain'], + 'ammonolytic': ['ammonolytic', 'commonality'], + 'ammunition': ['ammunition', 'antimonium'], + 'amnestic': ['amnestic', 'semantic'], + 'amnia': ['amain', 'amani', 'amnia', 'anima', 'mania'], + 'amniac': ['amniac', 'caiman', 'maniac'], + 'amnic': ['amnic', 'manic'], + 'amnion': ['amnion', 'minoan', 'nomina'], + 'amnionata': ['amnionata', 'anamniota'], + 'amnionate': ['amnionate', 'anamniote', 'emanation'], + 'amniota': ['amniota', 'itonama'], + 'amniote': ['amniote', 'anomite'], + 'amniotic': ['amniotic', 'mication'], + 'amniotome': ['amniotome', 'momotinae'], + 'amok': ['amok', 'mako'], + 'amole': ['amole', 'maleo'], + 'amomis': ['amomis', 'mimosa'], + 'among': ['among', 'mango'], + 'amor': ['amor', 'maro', 'mora', 'omar', 'roam'], + 'amores': ['amores', 'ramose', 'sorema'], + 'amoret': ['amoret', 'morate'], + 'amorist': ['amorist', 'aortism', 'miastor'], + 'amoritic': ['amoritic', 'microtia'], + 'amorpha': ['amorpha', 'amphora'], + 'amorphic': ['amorphic', 'amphoric'], + 'amorphous': ['amorphous', 'amphorous'], + 'amort': ['amort', 'morat', 'torma'], + 'amortize': ['amortize', 'atomizer'], + 'amos': ['amos', 'soam', 'soma'], + 'amotion': ['amotion', 'otomian'], + 'amount': ['amount', 'moutan', 'outman'], + 'amoy': ['amoy', 'mayo'], + 'ampelis': ['ampelis', 'lepisma'], + 'ampelite': ['ampelite', 'pimelate'], + 'ampelitic': ['ampelitic', 'implicate'], + 'amper': ['amper', 'remap'], + 'amperemeter': ['amperemeter', 'permeameter'], + 'amperian': ['amperian', 'paramine', 'pearmain'], + 'amphicyon': ['amphicyon', 'hypomanic'], + 'amphigenous': ['amphigenous', 'musophagine'], + 'amphiphloic': ['amphiphloic', 'amphophilic'], + 'amphipodous': ['amphipodous', 'hippodamous'], + 'amphitropous': ['amphitropous', 'pastophorium'], + 'amphophilic': ['amphiphloic', 'amphophilic'], + 'amphora': ['amorpha', 'amphora'], + 'amphore': ['amphore', 'morphea'], + 'amphorette': ['amphorette', 'haptometer'], + 'amphoric': ['amorphic', 'amphoric'], + 'amphorous': ['amorphous', 'amphorous'], + 'amphoteric': ['amphoteric', 'metaphoric'], + 'ample': ['ample', 'maple'], + 'ampliate': ['ampliate', 'palamite'], + 'amplification': ['amplification', 'palmification'], + 'amply': ['amply', 'palmy'], + 'ampul': ['ampul', 'pluma'], + 'ampulla': ['ampulla', 'palmula'], + 'amra': ['amar', 'amra', 'mara', 'rama'], + 'amsath': ['amsath', 'asthma'], + 'amsel': ['amsel', 'melas', 'mesal', 'samel'], + 'amsonia': ['amsonia', 'anosmia'], + 'amt': ['amt', 'mat', 'tam'], + 'amulet': ['amulet', 'muleta'], + 'amunam': ['amunam', 'manuma'], + 'amuse': ['amuse', 'mesua'], + 'amused': ['amused', 'masdeu', 'medusa'], + 'amusee': ['amusee', 'saeume'], + 'amuser': ['amuser', 'mauser'], + 'amusgo': ['amusgo', 'sugamo'], + 'amvis': ['amvis', 'mavis'], + 'amy': ['amy', 'may', 'mya', 'yam'], + 'amyelic': ['amyelic', 'mycelia'], + 'amyl': ['amyl', 'lyam', 'myal'], + 'amylan': ['amylan', 'lamany', 'layman'], + 'amylenol': ['amylenol', 'myelonal'], + 'amylidene': ['amylidene', 'mydaleine'], + 'amylin': ['amylin', 'mainly'], + 'amylo': ['amylo', 'loamy'], + 'amylon': ['amylon', 'onymal'], + 'amyotrophy': ['amyotrophy', 'myoatrophy'], + 'amyrol': ['amyrol', 'molary'], + 'an': ['an', 'na'], + 'ana': ['ana', 'naa'], + 'anacara': ['anacara', 'aracana'], + 'anacard': ['anacard', 'caranda'], + 'anaces': ['anaces', 'scaean'], + 'anachorism': ['anachorism', 'chorasmian', 'maraschino'], + 'anacid': ['acnida', 'anacid', 'dacian'], + 'anacreontic': ['anacreontic', 'canceration'], + 'anacusis': ['anacusis', 'ascanius'], + 'anadem': ['anadem', 'maenad'], + 'anadenia': ['anadenia', 'danainae'], + 'anadrom': ['anadrom', 'madrona', 'mandora', 'monarda', 'roadman'], + 'anaemic': ['acnemia', 'anaemic'], + 'anaeretic': ['anaeretic', 'ecarinate'], + 'anal': ['alan', 'anal', 'lana'], + 'analcime': ['alcamine', 'analcime', 'calamine', 'camelina'], + 'analcite': ['analcite', 'anticlea', 'laitance'], + 'analcitite': ['analcitite', 'catalinite'], + 'analectic': ['acclinate', 'analectic'], + 'analeptical': ['analeptical', 'placentalia'], + 'anally': ['alanyl', 'anally'], + 'analogic': ['analogic', 'calinago'], + 'analogist': ['analogist', 'nostalgia'], + 'anam': ['anam', 'mana', 'naam', 'nama'], + 'anamesite': ['anamesite', 'seamanite'], + 'anamirta': ['anamirta', 'araminta'], + 'anamite': ['amentia', 'aminate', 'anamite', 'animate'], + 'anamniota': ['amnionata', 'anamniota'], + 'anamniote': ['amnionate', 'anamniote', 'emanation'], + 'anan': ['anan', 'anna', 'nana'], + 'ananda': ['ananda', 'danaan'], + 'anandria': ['anandria', 'andriana'], + 'anangioid': ['agoniadin', 'anangioid', 'ganoidian'], + 'ananism': ['ananism', 'samnani'], + 'ananite': ['ananite', 'anatine', 'taenian'], + 'anaphoric': ['anaphoric', 'pharaonic'], + 'anaphorical': ['anaphorical', 'pharaonical'], + 'anapnea': ['anapnea', 'napaean'], + 'anapsid': ['anapsid', 'sapinda'], + 'anapsida': ['anapsida', 'anaspida'], + 'anaptotic': ['anaptotic', 'captation'], + 'anarchic': ['anarchic', 'characin'], + 'anarchism': ['anarchism', 'arachnism'], + 'anarchist': ['anarchist', 'archsaint', 'cantharis'], + 'anarcotin': ['anarcotin', 'cantorian', 'carnation', 'narcotina'], + 'anaretic': ['anaretic', 'arcanite', 'carinate', 'craniate'], + 'anarthropod': ['anarthropod', 'arthropodan'], + 'anas': ['anas', 'ansa', 'saan'], + 'anasa': ['anasa', 'asana'], + 'anaspida': ['anapsida', 'anaspida'], + 'anastaltic': ['anastaltic', 'catalanist'], + 'anat': ['anat', 'anta', 'tana'], + 'anatidae': ['anatidae', 'taeniada'], + 'anatine': ['ananite', 'anatine', 'taenian'], + 'anatocism': ['anatocism', 'anosmatic'], + 'anatole': ['anatole', 'notaeal'], + 'anatolic': ['aconital', 'actional', 'anatolic'], + 'anatomicopathologic': ['anatomicopathologic', 'pathologicoanatomic'], + 'anatomicopathological': ['anatomicopathological', 'pathologicoanatomical'], + 'anatomicophysiologic': ['anatomicophysiologic', 'physiologicoanatomic'], + 'anatomism': ['anatomism', 'nomismata'], + 'anatomize': ['amazonite', 'anatomize'], + 'anatum': ['anatum', 'mantua', 'tamanu'], + 'anay': ['anay', 'yana'], + 'anba': ['anba', 'bana'], + 'ancestor': ['ancestor', 'entosarc'], + 'ancestral': ['ancestral', 'lancaster'], + 'anchat': ['acanth', 'anchat', 'tanach'], + 'anchietin': ['anchietin', 'cathinine'], + 'anchistea': ['anchistea', 'hanseatic'], + 'anchor': ['anchor', 'archon', 'charon', 'rancho'], + 'anchored': ['anchored', 'rondache'], + 'anchorer': ['anchorer', 'ranchero', 'reanchor'], + 'anchoretic': ['acherontic', 'anchoretic'], + 'anchoretical': ['acherontical', 'anchoretical'], + 'anchoretism': ['anchoretism', 'trichomanes'], + 'anchorite': ['anchorite', 'antechoir', 'heatronic', 'hectorian'], + 'anchoritism': ['anchoritism', 'chiromantis', 'chrismation', 'harmonistic'], + 'ancile': ['alcine', 'ancile'], + 'ancilla': ['aclinal', 'ancilla'], + 'ancillary': ['ancillary', 'carlylian', 'cranially'], + 'ancon': ['ancon', 'canon'], + 'anconitis': ['anconitis', 'antiscion', 'onanistic'], + 'ancony': ['ancony', 'canyon'], + 'ancoral': ['alcoran', 'ancoral', 'carolan'], + 'ancylus': ['ancylus', 'unscaly'], + 'ancyrene': ['ancyrene', 'cerynean'], + 'and': ['and', 'dan'], + 'anda': ['anda', 'dana'], + 'andante': ['andante', 'dantean'], + 'ande': ['ande', 'dane', 'dean', 'edna'], + 'andesitic': ['andesitic', 'dianetics'], + 'andhra': ['andhra', 'dharna'], + 'andi': ['adin', 'andi', 'dain', 'dani', 'dian', 'naid'], + 'andian': ['andian', 'danian', 'nidana'], + 'andine': ['aidenn', 'andine', 'dannie', 'indane'], + 'andira': ['adrian', 'andira', 'andria', 'radian', 'randia'], + 'andorite': ['andorite', 'nadorite', 'ordinate', 'rodentia'], + 'andre': ['andre', 'arend', 'daren', 'redan'], + 'andrew': ['andrew', 'redawn', 'wander', 'warden'], + 'andria': ['adrian', 'andira', 'andria', 'radian', 'randia'], + 'andriana': ['anandria', 'andriana'], + 'andrias': ['andrias', 'sardian', 'sarinda'], + 'andric': ['andric', 'cardin', 'rancid'], + 'andries': ['andries', 'isander', 'sardine'], + 'androgynus': ['androgynus', 'gynandrous'], + 'androl': ['androl', 'arnold', 'lardon', 'roland', 'ronald'], + 'andronicus': ['andronicus', 'unsardonic'], + 'androtomy': ['androtomy', 'dynamotor'], + 'anear': ['anear', 'arean', 'arena'], + 'aneath': ['ahtena', 'aneath', 'athena'], + 'anecdota': ['anecdota', 'coadnate'], + 'anele': ['anele', 'elean'], + 'anematosis': ['anematosis', 'menostasia'], + 'anemia': ['amenia', 'anemia'], + 'anemic': ['anemic', 'cinema', 'iceman'], + 'anemograph': ['anemograph', 'phanerogam'], + 'anemographic': ['anemographic', 'phanerogamic'], + 'anemography': ['anemography', 'phanerogamy'], + 'anemone': ['anemone', 'monaene'], + 'anent': ['anent', 'annet', 'nenta'], + 'anepia': ['anepia', 'apinae'], + 'aneretic': ['aneretic', 'centiare', 'creatine', 'increate', 'iterance'], + 'anergic': ['anergic', 'garnice', 'garniec', 'geranic', 'grecian'], + 'anergy': ['anergy', 'rangey'], + 'anerly': ['anerly', 'nearly'], + 'aneroid': ['aneroid', 'arenoid'], + 'anerotic': ['actioner', 'anerotic', 'ceration', 'creation', 'reaction'], + 'anes': ['anes', 'sane', 'sean'], + 'anesis': ['anesis', 'anseis', 'sanies', 'sansei', 'sasine'], + 'aneuric': ['aneuric', 'rinceau'], + 'aneurin': ['aneurin', 'uranine'], + 'aneurism': ['aneurism', 'arsenium', 'sumerian'], + 'anew': ['anew', 'wane', 'wean'], + 'angami': ['angami', 'magani', 'magian'], + 'angara': ['angara', 'aranga', 'nagara'], + 'angaria': ['agrania', 'angaria', 'niagara'], + 'angel': ['agnel', 'angel', 'angle', 'galen', 'genal', 'glean', 'lagen'], + 'angela': ['alnage', 'angela', 'galena', 'lagena'], + 'angeldom': ['angeldom', 'lodgeman'], + 'angelet': ['angelet', 'elegant'], + 'angelic': ['angelic', 'galenic'], + 'angelical': ['angelical', 'englacial', 'galenical'], + 'angelically': ['angelically', 'englacially'], + 'angelin': ['angelin', 'leaning'], + 'angelina': ['alangine', 'angelina', 'galenian'], + 'angelique': ['angelique', 'equiangle'], + 'angelo': ['angelo', 'engaol'], + 'angelot': ['angelot', 'tangelo'], + 'anger': ['anger', 'areng', 'grane', 'range'], + 'angerly': ['angerly', 'geranyl'], + 'angers': ['angers', 'sanger', 'serang'], + 'angico': ['agonic', 'angico', 'gaonic', 'goniac'], + 'angie': ['angie', 'gaine'], + 'angild': ['angild', 'lading'], + 'angili': ['ailing', 'angili', 'nilgai'], + 'angina': ['angina', 'inanga'], + 'anginal': ['alangin', 'anginal', 'anglian', 'nagnail'], + 'angiocholitis': ['angiocholitis', 'cholangioitis'], + 'angiochondroma': ['angiochondroma', 'chondroangioma'], + 'angiofibroma': ['angiofibroma', 'fibroangioma'], + 'angioid': ['angioid', 'gonidia'], + 'angiokeratoma': ['angiokeratoma', 'keratoangioma'], + 'angiometer': ['angiometer', 'ergotamine', 'geometrina'], + 'angka': ['angka', 'kanga'], + 'anglaise': ['anglaise', 'gelasian'], + 'angle': ['agnel', 'angel', 'angle', 'galen', 'genal', 'glean', 'lagen'], + 'angled': ['angled', 'dangle', 'englad', 'lagend'], + 'angler': ['angler', 'arleng', 'garnel', 'largen', 'rangle', 'regnal'], + 'angles': ['angles', 'gansel'], + 'angleworm': ['angleworm', 'lawmonger'], + 'anglian': ['alangin', 'anginal', 'anglian', 'nagnail'], + 'anglic': ['anglic', 'lacing'], + 'anglish': ['anglish', 'ashling'], + 'anglist': ['anglist', 'lasting', 'salting', 'slating', 'staling'], + 'angloid': ['angloid', 'loading'], + 'ango': ['agon', 'ango', 'gaon', 'goan', 'gona'], + 'angola': ['agonal', 'angola'], + 'angolar': ['angolar', 'organal'], + 'angor': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'angora': ['agroan', 'angora', 'anogra', 'arango', 'argoan', 'onagra'], + 'angriness': ['angriness', 'ranginess'], + 'angrite': ['angrite', 'granite', 'ingrate', 'tangier', 'tearing', 'tigrean'], + 'angry': ['angry', 'rangy'], + 'angst': ['angst', 'stang', 'tangs'], + 'angster': ['angster', 'garnets', 'nagster', 'strange'], + 'anguid': ['anguid', 'gaduin'], + 'anguine': ['anguine', 'guanine', 'guinean'], + 'angula': ['angula', 'nagual'], + 'angular': ['angular', 'granula'], + 'angulate': ['angulate', 'gaetulan'], + 'anguria': ['anguria', 'gaurian', 'guarani'], + 'angus': ['agnus', 'angus', 'sugan'], + 'anharmonic': ['anharmonic', 'monarchian'], + 'anhematosis': ['anhematosis', 'somasthenia'], + 'anhidrotic': ['anhidrotic', 'trachinoid'], + 'anhistous': ['anhistous', 'isanthous'], + 'anhydridize': ['anhydridize', 'hydrazidine'], + 'anhydrize': ['anhydrize', 'hydrazine'], + 'ani': ['ani', 'ian'], + 'anice': ['anice', 'eniac'], + 'aniconic': ['aniconic', 'ciconian'], + 'aniconism': ['aniconism', 'insomniac'], + 'anicular': ['anicular', 'caulinar'], + 'anicut': ['anicut', 'nautic', 'ticuna', 'tunica'], + 'anidian': ['anidian', 'indiana'], + 'aniente': ['aniente', 'itenean'], + 'anight': ['anight', 'athing'], + 'anil': ['alin', 'anil', 'lain', 'lina', 'nail'], + 'anile': ['alien', 'aline', 'anile', 'elain', 'elian', 'laine', 'linea'], + 'anilic': ['anilic', 'clinia'], + 'anilid': ['anilid', 'dialin', 'dianil', 'inlaid'], + 'anilide': ['anilide', 'elaidin'], + 'anilidic': ['anilidic', 'indicial'], + 'anima': ['amain', 'amani', 'amnia', 'anima', 'mania'], + 'animable': ['animable', 'maniable'], + 'animal': ['almain', 'animal', 'lamina', 'manila'], + 'animalic': ['animalic', 'limacina'], + 'animate': ['amentia', 'aminate', 'anamite', 'animate'], + 'animated': ['animated', 'mandaite', 'mantidae'], + 'animater': ['animater', 'marinate'], + 'animating': ['animating', 'imaginant'], + 'animation': ['amination', 'animation'], + 'animator': ['animator', 'tamanoir'], + 'anime': ['amine', 'anime', 'maine', 'manei'], + 'animi': ['amini', 'animi'], + 'animist': ['animist', 'santimi'], + 'animize': ['aminize', 'animize', 'azimine'], + 'animus': ['animus', 'anisum', 'anusim', 'manius'], + 'anionic': ['anionic', 'iconian'], + 'anis': ['anis', 'nais', 'nasi', 'nias', 'sain', 'sina'], + 'anisal': ['anisal', 'nasial', 'salian', 'salina'], + 'anisate': ['anisate', 'entasia'], + 'anise': ['anise', 'insea', 'siena', 'sinae'], + 'anisette': ['anisette', 'atestine', 'settaine'], + 'anisic': ['anisic', 'sicani', 'sinaic'], + 'anisilic': ['anisilic', 'sicilian'], + 'anisodont': ['anisodont', 'sondation'], + 'anisometric': ['anisometric', + 'creationism', + 'miscreation', + 'ramisection', + 'reactionism'], + 'anisopod': ['anisopod', 'isopodan'], + 'anisoptera': ['anisoptera', 'asperation', 'separation'], + 'anisum': ['animus', 'anisum', 'anusim', 'manius'], + 'anisuria': ['anisuria', 'isaurian'], + 'anisyl': ['anisyl', 'snaily'], + 'anita': ['anita', 'niata', 'tania'], + 'anither': ['anither', 'inearth', 'naither'], + 'ankee': ['aknee', 'ankee', 'keena'], + 'anker': ['anker', 'karen', 'naker'], + 'ankh': ['ankh', 'hank', 'khan'], + 'anklet': ['anklet', 'lanket', 'tankle'], + 'ankoli': ['ankoli', 'kaolin'], + 'ankus': ['ankus', 'kusan'], + 'ankyroid': ['ankyroid', 'dikaryon'], + 'anlace': ['anlace', 'calean'], + 'ann': ['ann', 'nan'], + 'anna': ['anan', 'anna', 'nana'], + 'annale': ['annale', 'anneal'], + 'annaline': ['annaline', 'linnaean'], + 'annalist': ['annalist', 'santalin'], + 'annalize': ['annalize', 'zelanian'], + 'annam': ['annam', 'manna'], + 'annamite': ['annamite', 'manatine'], + 'annard': ['annard', 'randan'], + 'annat': ['annat', 'tanan'], + 'annates': ['annates', 'tannase'], + 'anne': ['anne', 'nane'], + 'anneal': ['annale', 'anneal'], + 'annealer': ['annealer', 'lernaean', 'reanneal'], + 'annelid': ['annelid', 'lindane'], + 'annelism': ['annelism', 'linesman'], + 'anneloid': ['anneloid', 'nonideal'], + 'annet': ['anent', 'annet', 'nenta'], + 'annexer': ['annexer', 'reannex'], + 'annie': ['annie', 'inane'], + 'annite': ['annite', 'innate', 'tinean'], + 'annotine': ['annotine', 'tenonian'], + 'annoy': ['annoy', 'nonya'], + 'annoyancer': ['annoyancer', 'rayonnance'], + 'annoyer': ['annoyer', 'reannoy'], + 'annualist': ['annualist', 'sultanian'], + 'annulation': ['annulation', 'unnational'], + 'annulet': ['annulet', 'nauntle'], + 'annulment': ['annulment', 'tunnelman'], + 'annuloid': ['annuloid', 'uninodal'], + 'anodic': ['adonic', 'anodic'], + 'anodize': ['adonize', 'anodize'], + 'anodynic': ['anodynic', 'cydonian'], + 'anoestrous': ['anoestrous', 'treasonous'], + 'anoestrum': ['anoestrum', 'neuromast'], + 'anoetic': ['acetoin', 'aconite', 'anoetic', 'antoeci', 'cetonia'], + 'anogenic': ['anogenic', 'canoeing'], + 'anogra': ['agroan', 'angora', 'anogra', 'arango', 'argoan', 'onagra'], + 'anoil': ['aloin', 'anoil', 'anoli'], + 'anoint': ['anoint', 'nation'], + 'anointer': ['anointer', 'inornate', 'nonirate', 'reanoint'], + 'anole': ['alone', 'anole', 'olena'], + 'anoli': ['aloin', 'anoil', 'anoli'], + 'anolis': ['alison', 'anolis'], + 'anomaliped': ['anomaliped', 'palaemonid'], + 'anomalism': ['anomalism', 'malmaison'], + 'anomalist': ['anomalist', 'atonalism'], + 'anomiidae': ['anomiidae', 'maioidean'], + 'anomite': ['amniote', 'anomite'], + 'anomodont': ['anomodont', 'monodonta'], + 'anomural': ['anomural', 'monaural'], + 'anon': ['anon', 'nona', 'onan'], + 'anophyte': ['anophyte', 'typhoean'], + 'anopia': ['anopia', 'aponia', 'poiana'], + 'anorak': ['anorak', 'korana'], + 'anorchism': ['anorchism', 'harmonics'], + 'anorectic': ['accretion', 'anorectic', 'neoarctic'], + 'anorthic': ['anorthic', 'anthroic', 'tanchoir'], + 'anorthitite': ['anorthitite', 'trithionate'], + 'anorthose': ['anorthose', 'hoarstone'], + 'anosia': ['anosia', 'asonia'], + 'anosmatic': ['anatocism', 'anosmatic'], + 'anosmia': ['amsonia', 'anosmia'], + 'anosmic': ['anosmic', 'masonic'], + 'anoterite': ['anoterite', 'orientate'], + 'another': ['another', 'athenor', 'rheotan'], + 'anotia': ['anotia', 'atonia'], + 'anoxia': ['anoxia', 'axonia'], + 'anoxic': ['anoxic', 'oxanic'], + 'ansa': ['anas', 'ansa', 'saan'], + 'ansar': ['ansar', 'saran', 'sarna'], + 'ansation': ['ansation', 'sonatina'], + 'anseis': ['anesis', 'anseis', 'sanies', 'sansei', 'sasine'], + 'ansel': ['ansel', 'slane'], + 'anselm': ['anselm', 'mensal'], + 'anser': ['anser', 'nares', 'rasen', 'snare'], + 'anserine': ['anserine', 'reinsane'], + 'anserous': ['anserous', 'arsenous'], + 'ansu': ['ansu', 'anus'], + 'answerer': ['answerer', 'reanswer'], + 'ant': ['ant', 'nat', 'tan'], + 'anta': ['anat', 'anta', 'tana'], + 'antacrid': ['antacrid', 'cardiant', 'radicant', 'tridacna'], + 'antagonism': ['antagonism', 'montagnais'], + 'antagonist': ['antagonist', 'stagnation'], + 'antal': ['antal', 'natal'], + 'antanemic': ['antanemic', 'cinnamate'], + 'antar': ['antar', 'antra'], + 'antarchistical': ['antarchistical', 'charlatanistic'], + 'ante': ['ante', 'aten', 'etna', 'nate', 'neat', 'taen', 'tane', 'tean'], + 'anteact': ['anteact', 'cantate'], + 'anteal': ['anteal', 'lanate', 'teanal'], + 'antechoir': ['anchorite', 'antechoir', 'heatronic', 'hectorian'], + 'antecornu': ['antecornu', 'connature'], + 'antedate': ['antedate', 'edentata'], + 'antelabium': ['albuminate', 'antelabium'], + 'antelopian': ['antelopian', 'neapolitan', 'panelation'], + 'antelucan': ['antelucan', 'cannulate'], + 'antelude': ['antelude', 'unelated'], + 'anteluminary': ['anteluminary', 'unalimentary'], + 'antemedial': ['antemedial', 'delaminate'], + 'antenatal': ['antenatal', 'atlantean', 'tantalean'], + 'anteriad': ['anteriad', 'atridean', 'dentaria'], + 'anteroinferior': ['anteroinferior', 'inferoanterior'], + 'anterosuperior': ['anterosuperior', 'superoanterior'], + 'antes': ['antes', 'nates', 'stane', 'stean'], + 'anthela': ['anthela', 'ethanal'], + 'anthem': ['anthem', 'hetman', 'mentha'], + 'anthemwise': ['anthemwise', 'whitmanese'], + 'anther': ['anther', 'nather', 'tharen', 'thenar'], + 'anthericum': ['anthericum', 'narthecium'], + 'anthicidae': ['anthicidae', 'tachinidae'], + 'anthinae': ['anthinae', 'athenian'], + 'anthogenous': ['anthogenous', 'neognathous'], + 'anthonomus': ['anthonomus', 'monanthous'], + 'anthophile': ['anthophile', 'lithophane'], + 'anthracia': ['anthracia', 'antiarcha', 'catharina'], + 'anthroic': ['anorthic', 'anthroic', 'tanchoir'], + 'anthrol': ['althorn', 'anthrol', 'thronal'], + 'anthropic': ['anthropic', 'rhapontic'], + 'anti': ['aint', 'anti', 'tain', 'tina'], + 'antiabrin': ['antiabrin', 'britannia'], + 'antiae': ['aetian', 'antiae', 'taenia'], + 'antiager': ['antiager', 'trainage'], + 'antialbumid': ['antialbumid', 'balantidium'], + 'antialien': ['ailantine', 'antialien'], + 'antiarcha': ['anthracia', 'antiarcha', 'catharina'], + 'antiaris': ['antiaris', 'intarsia'], + 'antibenzaldoxime': ['antibenzaldoxime', 'benzantialdoxime'], + 'antiblue': ['antiblue', 'nubilate'], + 'antic': ['actin', 'antic'], + 'antical': ['actinal', 'alantic', 'alicant', 'antical'], + 'anticaste': ['anticaste', 'ataentsic'], + 'anticlea': ['analcite', 'anticlea', 'laitance'], + 'anticlinorium': ['anticlinorium', 'inclinatorium'], + 'anticly': ['anticly', 'cantily'], + 'anticness': ['anticness', 'cantiness', 'incessant'], + 'anticor': ['anticor', 'carotin', 'cortina', 'ontaric'], + 'anticouncil': ['anticouncil', 'inculcation'], + 'anticourt': ['anticourt', 'curtation', 'ructation'], + 'anticous': ['acontius', 'anticous'], + 'anticreative': ['anticreative', 'antireactive'], + 'anticreep': ['anticreep', 'apenteric', 'increpate'], + 'antidote': ['antidote', 'tetanoid'], + 'antidotical': ['antidotical', 'dictational'], + 'antietam': ['antietam', 'manettia'], + 'antiextreme': ['antiextreme', 'exterminate'], + 'antifouler': ['antifouler', 'fluorinate', 'uniflorate'], + 'antifriction': ['antifriction', 'nitrifaction'], + 'antifungin': ['antifungin', 'unfainting'], + 'antigen': ['antigen', 'gentian'], + 'antigenic': ['antigenic', 'gentianic'], + 'antiglare': ['antiglare', 'raglanite'], + 'antigod': ['antigod', 'doating'], + 'antigone': ['antigone', 'negation'], + 'antiheroic': ['antiheroic', 'theorician'], + 'antilemic': ['alimentic', 'antilemic', 'melanitic', 'metanilic'], + 'antilia': ['antilia', 'italian'], + 'antilipase': ['antilipase', 'sapiential'], + 'antilope': ['antilope', 'antipole'], + 'antimallein': ['antimallein', 'inalimental'], + 'antimasque': ['antimasque', 'squamatine'], + 'antimeric': ['antimeric', 'carminite', 'criminate', 'metrician'], + 'antimerina': ['antimerina', 'maintainer', 'remaintain'], + 'antimeter': ['antimeter', 'attermine', 'interteam', 'terminate', 'tetramine'], + 'antimodel': ['antimodel', 'maldonite', 'monilated'], + 'antimodern': ['antimodern', 'ordainment'], + 'antimonial': ['antimonial', 'lamination'], + 'antimonic': ['antimonic', 'antinomic'], + 'antimonium': ['ammunition', 'antimonium'], + 'antimony': ['antimony', 'antinomy'], + 'antimoral': ['antimoral', 'tailorman'], + 'antimosquito': ['antimosquito', 'misquotation'], + 'antinegro': ['antinegro', 'argentino', 'argention'], + 'antinepotic': ['antinepotic', 'pectination'], + 'antineutral': ['antineutral', 'triannulate'], + 'antinial': ['antinial', 'latinian'], + 'antinome': ['antinome', 'nominate'], + 'antinomian': ['antinomian', 'innominata'], + 'antinomic': ['antimonic', 'antinomic'], + 'antinomy': ['antimony', 'antinomy'], + 'antinormal': ['antinormal', 'nonmarital', 'nonmartial'], + 'antiphonetic': ['antiphonetic', 'pentathionic'], + 'antiphonic': ['antiphonic', 'napthionic'], + 'antiphony': ['antiphony', 'typhonian'], + 'antiphrasis': ['antiphrasis', 'artisanship'], + 'antipodic': ['antipodic', 'diapnotic'], + 'antipole': ['antilope', 'antipole'], + 'antipolo': ['antipolo', 'antipool', 'optional'], + 'antipool': ['antipolo', 'antipool', 'optional'], + 'antipope': ['antipope', 'appointe'], + 'antiprotease': ['antiprotease', 'entoparasite'], + 'antipsoric': ['antipsoric', 'ascription', 'crispation'], + 'antiptosis': ['antiptosis', 'panostitis'], + 'antiputrid': ['antiputrid', 'tripudiant'], + 'antipyretic': ['antipyretic', 'pertinacity'], + 'antique': ['antique', 'quinate'], + 'antiquer': ['antiquer', 'quartine'], + 'antirabies': ['antirabies', 'bestiarian'], + 'antiracer': ['antiracer', 'tarriance'], + 'antireactive': ['anticreative', 'antireactive'], + 'antired': ['antired', 'detrain', 'randite', 'trained'], + 'antireducer': ['antireducer', 'reincrudate', 'untraceried'], + 'antiroyalist': ['antiroyalist', 'stationarily'], + 'antirumor': ['antirumor', 'ruminator'], + 'antirun': ['antirun', 'untrain', 'urinant'], + 'antirust': ['antirust', 'naturist'], + 'antiscion': ['anconitis', 'antiscion', 'onanistic'], + 'antisepsin': ['antisepsin', 'paintiness'], + 'antisepsis': ['antisepsis', 'inspissate'], + 'antiseptic': ['antiseptic', 'psittacine'], + 'antiserum': ['antiserum', 'misaunter'], + 'antisi': ['antisi', 'isatin'], + 'antislip': ['alpinist', 'antislip'], + 'antisoporific': ['antisoporific', 'prosification', 'sporification'], + 'antispace': ['antispace', 'panaceist'], + 'antistes': ['antistes', 'titaness'], + 'antisun': ['antisun', 'unsaint', 'unsatin', 'unstain'], + 'antitheism': ['antitheism', 'themistian'], + 'antitonic': ['antitonic', 'nictation'], + 'antitorpedo': ['antitorpedo', 'deportation'], + 'antitrade': ['antitrade', 'attainder'], + 'antitrope': ['antitrope', 'patronite', 'tritanope'], + 'antitropic': ['antitropic', 'tritanopic'], + 'antitropical': ['antitropical', 'practitional'], + 'antivice': ['antivice', 'inactive', 'vineatic'], + 'antler': ['altern', 'antler', 'learnt', 'rental', 'ternal'], + 'antlia': ['antlia', 'latian', 'nalita'], + 'antliate': ['antliate', 'latinate'], + 'antlid': ['antlid', 'tindal'], + 'antling': ['antling', 'tanling'], + 'antoeci': ['acetoin', 'aconite', 'anoetic', 'antoeci', 'cetonia'], + 'antoecian': ['acetanion', 'antoecian'], + 'anton': ['anton', 'notan', 'tonna'], + 'antproof': ['antproof', 'tanproof'], + 'antra': ['antar', 'antra'], + 'antral': ['antral', 'tarnal'], + 'antre': ['antre', 'arent', 'retan', 'terna'], + 'antrocele': ['antrocele', 'coeternal', 'tolerance'], + 'antronasal': ['antronasal', 'nasoantral'], + 'antroscope': ['antroscope', 'contrapose'], + 'antroscopy': ['antroscopy', 'syncopator'], + 'antrotome': ['antrotome', 'nototrema'], + 'antrustion': ['antrustion', 'nasturtion'], + 'antu': ['antu', 'aunt', 'naut', 'taun', 'tuan', 'tuna'], + 'anubis': ['anubis', 'unbias'], + 'anura': ['anura', 'ruana'], + 'anuresis': ['anuresis', 'senarius'], + 'anuretic': ['anuretic', 'centauri', 'centuria', 'teucrian'], + 'anuria': ['anuria', 'urania'], + 'anuric': ['anuric', 'cinura', 'uranic'], + 'anurous': ['anurous', 'uranous'], + 'anury': ['anury', 'unary', 'unray'], + 'anus': ['ansu', 'anus'], + 'anusim': ['animus', 'anisum', 'anusim', 'manius'], + 'anvil': ['alvin', 'anvil', 'nival', 'vinal'], + 'any': ['any', 'nay', 'yan'], + 'aonach': ['aonach', 'choana'], + 'aoristic': ['aoristic', 'iscariot'], + 'aortal': ['aortal', 'rotala'], + 'aortectasis': ['aerostatics', 'aortectasis'], + 'aortism': ['amorist', 'aortism', 'miastor'], + 'aosmic': ['aosmic', 'mosaic'], + 'apachite': ['apachite', 'hepatica'], + 'apalachee': ['acalephae', 'apalachee'], + 'apama': ['amapa', 'apama'], + 'apanthropy': ['apanthropy', 'panatrophy'], + 'apar': ['apar', 'paar', 'para'], + 'apart': ['apart', 'trapa'], + 'apatetic': ['apatetic', 'capitate'], + 'ape': ['ape', 'pea'], + 'apedom': ['apedom', 'pomade'], + 'apeiron': ['apeiron', 'peorian'], + 'apelet': ['apelet', 'ptelea'], + 'apelike': ['apelike', 'pealike'], + 'apeling': ['apeling', 'leaping'], + 'apenteric': ['anticreep', 'apenteric', 'increpate'], + 'apeptic': ['apeptic', 'catpipe'], + 'aper': ['aper', 'pare', 'pear', 'rape', 'reap'], + 'aperch': ['aperch', 'eparch', 'percha', 'preach'], + 'aperitive': ['aperitive', 'petiveria'], + 'apert': ['apert', 'pater', 'peart', 'prate', 'taper', 'terap'], + 'apertly': ['apertly', 'peartly', 'platery', 'pteryla', 'taperly'], + 'apertness': ['apertness', 'peartness', 'taperness'], + 'apertured': ['apertured', 'departure'], + 'apery': ['apery', 'payer', 'repay'], + 'aphanes': ['aphanes', 'saphena'], + 'aphasia': ['aphasia', 'asaphia'], + 'aphasic': ['aphasic', 'asaphic'], + 'aphemic': ['aphemic', 'impeach'], + 'aphetic': ['aphetic', 'caphite', 'hepatic'], + 'aphetism': ['aphetism', 'mateship', 'shipmate', 'spithame'], + 'aphetize': ['aphetize', 'hepatize'], + 'aphides': ['aphides', 'diphase'], + 'aphidicolous': ['acidophilous', 'aphidicolous'], + 'aphis': ['aphis', 'apish', 'hispa', 'saiph', 'spahi'], + 'aphodian': ['adiaphon', 'aphodian'], + 'aphonic': ['aphonic', 'phocian'], + 'aphorismical': ['aphorismical', 'parochialism'], + 'aphotic': ['aphotic', 'picotah'], + 'aphra': ['aphra', 'harpa', 'parah'], + 'aphrodistic': ['aphrodistic', 'diastrophic'], + 'aphrodite': ['aphrodite', 'atrophied', 'diaporthe'], + 'apian': ['apian', 'apina'], + 'apiator': ['apiator', 'atropia', 'parotia'], + 'apical': ['apical', 'palaic'], + 'apicular': ['apicular', 'piacular'], + 'apina': ['apian', 'apina'], + 'apinae': ['anepia', 'apinae'], + 'apinage': ['aegipan', 'apinage'], + 'apinch': ['apinch', 'chapin', 'phanic'], + 'aping': ['aping', 'ngapi', 'pangi'], + 'apiole': ['apiole', 'leipoa'], + 'apiolin': ['apiolin', 'pinolia'], + 'apionol': ['apionol', 'polonia'], + 'apiose': ['apiose', 'apoise'], + 'apis': ['apis', 'pais', 'pasi', 'saip'], + 'apish': ['aphis', 'apish', 'hispa', 'saiph', 'spahi'], + 'apishly': ['apishly', 'layship'], + 'apism': ['apism', 'sampi'], + 'apitpat': ['apitpat', 'pitapat'], + 'apivorous': ['apivorous', 'oviparous'], + 'aplenty': ['aplenty', 'penalty'], + 'aplite': ['aplite', 'pilate'], + 'aplitic': ['aliptic', 'aplitic'], + 'aplodontia': ['adoptional', 'aplodontia'], + 'aplome': ['aplome', 'malope'], + 'apnea': ['apnea', 'paean'], + 'apneal': ['apneal', 'panela'], + 'apochromat': ['apochromat', 'archoptoma'], + 'apocrita': ['apocrita', 'aproctia'], + 'apocryph': ['apocryph', 'hypocarp'], + 'apod': ['apod', 'dopa'], + 'apoda': ['adpao', 'apoda'], + 'apogon': ['apogon', 'poonga'], + 'apoise': ['apiose', 'apoise'], + 'apolaustic': ['apolaustic', 'autopsical'], + 'apolistan': ['apolistan', 'lapsation'], + 'apollo': ['apollo', 'palolo'], + 'aponia': ['anopia', 'aponia', 'poiana'], + 'aponic': ['aponic', 'ponica'], + 'aporetic': ['aporetic', 'capriote', 'operatic'], + 'aporetical': ['aporetical', 'operatical'], + 'aporia': ['aporia', 'piaroa'], + 'aport': ['aport', 'parto', 'porta'], + 'aportoise': ['aportoise', 'esotropia'], + 'aposporous': ['aposporous', 'aprosopous'], + 'apostil': ['apostil', 'topsail'], + 'apostle': ['apostle', 'aseptol'], + 'apostrophus': ['apostrophus', 'pastophorus'], + 'apothesine': ['apothesine', 'isoheptane'], + 'apout': ['apout', 'taupo'], + 'appall': ['appall', 'palpal'], + 'apparent': ['apparent', 'trappean'], + 'appealer': ['appealer', 'reappeal'], + 'appealing': ['appealing', 'lagniappe', 'panplegia'], + 'appearer': ['appearer', 'rapparee', 'reappear'], + 'append': ['append', 'napped'], + 'applauder': ['applauder', 'reapplaud'], + 'applicator': ['applicator', 'procapital'], + 'applier': ['applier', 'aripple'], + 'appointe': ['antipope', 'appointe'], + 'appointer': ['appointer', 'reappoint'], + 'appointor': ['appointor', 'apportion'], + 'apportion': ['appointor', 'apportion'], + 'apportioner': ['apportioner', 'reapportion'], + 'appraisable': ['appraisable', 'parablepsia'], + 'apprehender': ['apprehender', 'reapprehend'], + 'approacher': ['approacher', 'reapproach'], + 'apricot': ['apricot', 'atropic', 'parotic', 'patrico'], + 'april': ['april', 'pilar', 'ripal'], + 'aprilis': ['aprilis', 'liparis'], + 'aproctia': ['apocrita', 'aproctia'], + 'apronless': ['apronless', 'responsal'], + 'aprosopous': ['aposporous', 'aprosopous'], + 'apse': ['apse', 'pesa', 'spae'], + 'apsidiole': ['apsidiole', 'episodial'], + 'apt': ['apt', 'pat', 'tap'], + 'aptal': ['aptal', 'palta', 'talpa'], + 'aptera': ['aptera', 'parate', 'patera'], + 'apterial': ['apterial', 'parietal'], + 'apteroid': ['apteroid', 'proteida'], + 'aptian': ['aptian', 'patina', 'taipan'], + 'aptly': ['aptly', 'patly', 'platy', 'typal'], + 'aptness': ['aptness', 'patness'], + 'aptote': ['aptote', 'optate', 'potate', 'teapot'], + 'apulian': ['apulian', 'paulian', 'paulina'], + 'apulse': ['apulse', 'upseal'], + 'apus': ['apus', 'supa', 'upas'], + 'aquabelle': ['aquabelle', 'equalable'], + 'aqueoigneous': ['aqueoigneous', 'igneoaqueous'], + 'aquicolous': ['aquicolous', 'loquacious'], + 'ar': ['ar', 'ra'], + 'arab': ['arab', 'arba', 'baar', 'bara'], + 'arabic': ['arabic', 'cairba'], + 'arabinic': ['arabinic', 'cabirian', 'carabini', 'cibarian'], + 'arabis': ['abaris', 'arabis'], + 'arabism': ['abramis', 'arabism'], + 'arabist': ['arabist', 'bartsia'], + 'arabit': ['arabit', 'tabira'], + 'arable': ['ablare', 'arable', 'arbela'], + 'araca': ['acara', 'araca'], + 'aracana': ['anacara', 'aracana'], + 'aracanga': ['aracanga', 'caragana'], + 'arachic': ['arachic', 'archaic'], + 'arachidonic': ['arachidonic', 'characinoid'], + 'arachis': ['arachis', 'asiarch', 'saharic'], + 'arachne': ['arachne', 'archean'], + 'arachnism': ['anarchism', 'arachnism'], + 'arachnitis': ['arachnitis', 'christiana'], + 'arad': ['adar', 'arad', 'raad', 'rada'], + 'arain': ['airan', 'arain', 'arian'], + 'arales': ['alares', 'arales'], + 'aralia': ['alaria', 'aralia'], + 'aralie': ['aerial', 'aralie'], + 'aramaic': ['aramaic', 'cariama'], + 'aramina': ['aramina', 'mariana'], + 'araminta': ['anamirta', 'araminta'], + 'aramus': ['aramus', 'asarum'], + 'araneid': ['araneid', 'ariadne', 'ranidae'], + 'aranein': ['aranein', 'raninae'], + 'aranga': ['angara', 'aranga', 'nagara'], + 'arango': ['agroan', 'angora', 'anogra', 'arango', 'argoan', 'onagra'], + 'arati': ['arati', 'atria', 'riata', 'tarai', 'tiara'], + 'aration': ['aration', 'otarian'], + 'arauan': ['arauan', 'arauna'], + 'arauna': ['arauan', 'arauna'], + 'arba': ['arab', 'arba', 'baar', 'bara'], + 'arbacin': ['arbacin', 'carabin', 'cariban'], + 'arbalester': ['arbalester', 'arbalestre', 'arrestable'], + 'arbalestre': ['arbalester', 'arbalestre', 'arrestable'], + 'arbalister': ['arbalister', 'breastrail'], + 'arbalo': ['aboral', 'arbalo'], + 'arbela': ['ablare', 'arable', 'arbela'], + 'arbiter': ['arbiter', 'rarebit'], + 'arbored': ['arbored', 'boarder', 'reboard'], + 'arboret': ['arboret', 'roberta', 'taborer'], + 'arboretum': ['arboretum', 'tambourer'], + 'arborist': ['arborist', 'ribroast'], + 'arbuscle': ['arbuscle', 'buscarle'], + 'arbutin': ['arbutin', 'tribuna'], + 'arc': ['arc', 'car'], + 'arca': ['arca', 'cara'], + 'arcadia': ['acardia', 'acarida', 'arcadia'], + 'arcadic': ['arcadic', 'cardiac'], + 'arcane': ['arcane', 'carane'], + 'arcanite': ['anaretic', 'arcanite', 'carinate', 'craniate'], + 'arcate': ['arcate', 'cerata'], + 'arch': ['arch', 'char', 'rach'], + 'archae': ['archae', 'areach'], + 'archaic': ['arachic', 'archaic'], + 'archaism': ['archaism', 'charisma'], + 'archapostle': ['archapostle', 'thecasporal'], + 'archcount': ['archcount', 'crouchant'], + 'arche': ['acher', 'arche', 'chare', 'chera', 'rache', 'reach'], + 'archeal': ['alchera', 'archeal'], + 'archean': ['arachne', 'archean'], + 'archer': ['archer', 'charer', 'rechar'], + 'arches': ['arches', 'chaser', 'eschar', 'recash', 'search'], + 'archidome': ['archidome', 'chromidae'], + 'archil': ['archil', 'chiral'], + 'arching': ['arching', 'chagrin'], + 'architis': ['architis', 'rachitis'], + 'archocele': ['archocele', 'cochleare'], + 'archon': ['anchor', 'archon', 'charon', 'rancho'], + 'archontia': ['archontia', 'tocharian'], + 'archoptoma': ['apochromat', 'archoptoma'], + 'archpoet': ['archpoet', 'protheca'], + 'archprelate': ['archprelate', 'pretracheal'], + 'archsaint': ['anarchist', 'archsaint', 'cantharis'], + 'archsee': ['archsee', 'rechase'], + 'archsin': ['archsin', 'incrash'], + 'archy': ['archy', 'chary'], + 'arcidae': ['arcidae', 'caridea'], + 'arcing': ['arcing', 'racing'], + 'arcite': ['acrite', 'arcite', 'tercia', 'triace', 'tricae'], + 'arcked': ['arcked', 'dacker'], + 'arcking': ['arcking', 'carking', 'racking'], + 'arcos': ['arcos', 'crosa', 'oscar', 'sacro'], + 'arctia': ['acrita', 'arctia'], + 'arctian': ['acritan', 'arctian'], + 'arcticize': ['arcticize', 'cicatrize'], + 'arctiid': ['arctiid', 'triacid', 'triadic'], + 'arctoid': ['arctoid', 'carotid', 'dartoic'], + 'arctoidean': ['arctoidean', 'carotidean', 'cordaitean', 'dinocerata'], + 'arctomys': ['arctomys', 'costmary', 'mascotry'], + 'arctos': ['arctos', 'castor', 'costar', 'scrota'], + 'arcual': ['arcual', 'arcula'], + 'arcuale': ['arcuale', 'caurale'], + 'arcubalist': ['arcubalist', 'ultrabasic'], + 'arcula': ['arcual', 'arcula'], + 'arculite': ['arculite', 'cutleria', 'lucretia', 'reticula', 'treculia'], + 'ardea': ['ardea', 'aread'], + 'ardeb': ['ardeb', 'beard', 'bread', 'debar'], + 'ardelia': ['ardelia', 'laridae', 'radiale'], + 'ardella': ['ardella', 'dareall'], + 'ardency': ['ardency', 'dancery'], + 'ardish': ['ardish', 'radish'], + 'ardoise': ['ardoise', 'aroides', 'soredia'], + 'ardu': ['ardu', 'daur', 'dura'], + 'are': ['aer', 'are', 'ear', 'era', 'rea'], + 'areach': ['archae', 'areach'], + 'aread': ['ardea', 'aread'], + 'areal': ['areal', 'reaal'], + 'arean': ['anear', 'arean', 'arena'], + 'arecaceae': ['aceraceae', 'arecaceae'], + 'arecaceous': ['aceraceous', 'arecaceous'], + 'arecain': ['acarine', 'acraein', 'arecain'], + 'arecolin': ['acrolein', + 'arecolin', + 'caroline', + 'colinear', + 'cornelia', + 'creolian', + 'lonicera'], + 'arecoline': ['arecoline', 'arenicole'], + 'arecuna': ['arecuna', 'aucaner'], + 'ared': ['ared', 'daer', 'dare', 'dear', 'read'], + 'areel': ['areel', 'earle'], + 'arena': ['anear', 'arean', 'arena'], + 'arenaria': ['aerarian', 'arenaria'], + 'arend': ['andre', 'arend', 'daren', 'redan'], + 'areng': ['anger', 'areng', 'grane', 'range'], + 'arenga': ['arenga', 'argean'], + 'arenicole': ['arecoline', 'arenicole'], + 'arenicolite': ['arenicolite', 'ricinoleate'], + 'arenig': ['arenig', 'earing', 'gainer', 'reagin', 'regain'], + 'arenoid': ['aneroid', 'arenoid'], + 'arenose': ['arenose', 'serenoa'], + 'arent': ['antre', 'arent', 'retan', 'terna'], + 'areographer': ['aerographer', 'areographer'], + 'areographic': ['aerographic', 'areographic'], + 'areographical': ['aerographical', 'areographical'], + 'areography': ['aerography', 'areography'], + 'areologic': ['aerologic', 'areologic'], + 'areological': ['aerological', 'areological'], + 'areologist': ['aerologist', 'areologist'], + 'areology': ['aerology', 'areology'], + 'areometer': ['aerometer', 'areometer'], + 'areometric': ['aerometric', 'areometric'], + 'areometry': ['aerometry', 'areometry'], + 'arete': ['arete', 'eater', 'teaer'], + 'argal': ['agral', 'argal'], + 'argali': ['argali', 'garial'], + 'argans': ['argans', 'sangar'], + 'argante': ['argante', 'granate', 'tanager'], + 'argas': ['argas', 'sagra'], + 'argean': ['arenga', 'argean'], + 'argeers': ['argeers', 'greaser', 'serrage'], + 'argel': ['argel', 'ergal', 'garle', 'glare', 'lager', 'large', 'regal'], + 'argenol': ['argenol', 'longear'], + 'argent': ['argent', 'garnet', 'garten', 'tanger'], + 'argentamid': ['argentamid', 'marginated'], + 'argenter': ['argenter', 'garneter'], + 'argenteum': ['argenteum', 'augmenter'], + 'argentic': ['argentic', 'citrange'], + 'argentide': ['argentide', 'denigrate', 'dinergate'], + 'argentiferous': ['argentiferous', 'garnetiferous'], + 'argentina': ['argentina', 'tanagrine'], + 'argentine': ['argentine', 'tangerine'], + 'argentino': ['antinegro', 'argentino', 'argention'], + 'argention': ['antinegro', 'argentino', 'argention'], + 'argentite': ['argentite', 'integrate'], + 'argentol': ['argentol', 'gerontal'], + 'argenton': ['argenton', 'negatron'], + 'argentous': ['argentous', 'neotragus'], + 'argentum': ['argentum', 'argument'], + 'arghan': ['arghan', 'hangar'], + 'argil': ['argil', 'glair', 'grail'], + 'arginine': ['arginine', 'nigerian'], + 'argive': ['argive', 'rivage'], + 'argo': ['argo', 'garo', 'gora'], + 'argoan': ['agroan', 'angora', 'anogra', 'arango', 'argoan', 'onagra'], + 'argol': ['algor', 'argol', 'goral', 'largo'], + 'argolet': ['argolet', 'gloater', 'legator'], + 'argolian': ['argolian', 'gloriana'], + 'argolic': ['argolic', 'cograil'], + 'argolid': ['argolid', 'goliard'], + 'argon': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'argot': ['argot', 'gator', 'gotra', 'groat'], + 'argue': ['argue', 'auger'], + 'argulus': ['argulus', 'lagurus'], + 'argument': ['argentum', 'argument'], + 'argus': ['argus', 'sugar'], + 'arguslike': ['arguslike', 'sugarlike'], + 'argute': ['argute', 'guetar', 'rugate', 'tuareg'], + 'argyle': ['argyle', 'gleary'], + 'arhar': ['arhar', 'arrah'], + 'arhat': ['arhat', 'artha', 'athar'], + 'aria': ['aira', 'aria', 'raia'], + 'ariadne': ['araneid', 'ariadne', 'ranidae'], + 'arian': ['airan', 'arain', 'arian'], + 'arianrhod': ['arianrhod', 'hordarian'], + 'aribine': ['aribine', 'bairnie', 'iberian'], + 'arician': ['arician', 'icarian'], + 'arid': ['arid', 'dari', 'raid'], + 'aridian': ['aridian', 'diarian'], + 'aridly': ['aridly', 'lyraid'], + 'ariegite': ['aegirite', 'ariegite'], + 'aries': ['aries', 'arise', 'raise', 'serai'], + 'arietid': ['arietid', 'iridate'], + 'arietta': ['arietta', 'ratitae'], + 'aright': ['aright', 'graith'], + 'arightly': ['alrighty', 'arightly'], + 'ariidae': ['ariidae', 'raiidae'], + 'aril': ['aril', 'lair', 'lari', 'liar', 'lira', 'rail', 'rial'], + 'ariled': ['ariled', 'derail', 'dialer'], + 'arillate': ['arillate', 'tiarella'], + 'arion': ['arion', 'noria'], + 'ariot': ['ariot', 'ratio'], + 'aripple': ['applier', 'aripple'], + 'arise': ['aries', 'arise', 'raise', 'serai'], + 'arisen': ['arisen', 'arsine', 'resina', 'serian'], + 'arist': ['arist', + 'astir', + 'sitar', + 'stair', + 'stria', + 'tarsi', + 'tisar', + 'trias'], + 'arista': ['arista', 'tarsia'], + 'aristeas': ['aristeas', 'asterias'], + 'aristol': ['aristol', 'oralist', 'ortalis', 'striola'], + 'aristulate': ['aristulate', 'australite'], + 'arite': ['arite', 'artie', 'irate', 'retia', 'tarie'], + 'arithmic': ['arithmic', 'mithraic', 'mithriac'], + 'arius': ['arius', 'asuri'], + 'arizona': ['arizona', 'azorian', 'zonaria'], + 'ark': ['ark', 'kra'], + 'arkab': ['abkar', 'arkab'], + 'arkite': ['arkite', 'karite'], + 'arkose': ['arkose', 'resoak', 'soaker'], + 'arlene': ['arlene', 'leaner'], + 'arleng': ['angler', 'arleng', 'garnel', 'largen', 'rangle', 'regnal'], + 'arles': ['arles', 'arsle', 'laser', 'seral', 'slare'], + 'arline': ['arline', 'larine', 'linear', 'nailer', 'renail'], + 'arm': ['arm', 'mar', 'ram'], + 'armada': ['armada', 'damara', 'ramada'], + 'armangite': ['armangite', 'marginate'], + 'armata': ['armata', 'matara', 'tamara'], + 'armed': ['armed', 'derma', 'dream', 'ramed'], + 'armenian': ['armenian', 'marianne'], + 'armenic': ['armenic', 'carmine', 'ceriman', 'crimean', 'mercian'], + 'armer': ['armer', 'rearm'], + 'armeria': ['armeria', 'mararie'], + 'armet': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'armful': ['armful', 'fulmar'], + 'armgaunt': ['armgaunt', 'granatum'], + 'armied': ['admire', 'armied', 'damier', 'dimera', 'merida'], + 'armiferous': ['armiferous', 'ramiferous'], + 'armigerous': ['armigerous', 'ramigerous'], + 'armil': ['armil', 'marli', 'rimal'], + 'armilla': ['armilla', 'marilla'], + 'armillated': ['armillated', 'malladrite', 'mallardite'], + 'arming': ['arming', 'ingram', 'margin'], + 'armistice': ['ameristic', 'armistice', 'artemisic'], + 'armlet': ['armlet', 'malter', 'martel'], + 'armonica': ['armonica', 'macaroni', 'marocain'], + 'armoried': ['airdrome', 'armoried'], + 'armpit': ['armpit', 'impart'], + 'armplate': ['armplate', 'malapert'], + 'arms': ['arms', 'mars'], + 'armscye': ['armscye', 'screamy'], + 'army': ['army', 'mary', 'myra', 'yarm'], + 'arn': ['arn', 'nar', 'ran'], + 'arna': ['arna', 'rana'], + 'arnaut': ['arnaut', 'arunta'], + 'arne': ['arne', 'earn', 'rane'], + 'arneb': ['abner', 'arneb', 'reban'], + 'arni': ['arni', 'iran', 'nair', 'rain', 'rani'], + 'arnica': ['acinar', + 'arnica', + 'canari', + 'carian', + 'carina', + 'crania', + 'narica'], + 'arnold': ['androl', 'arnold', 'lardon', 'roland', 'ronald'], + 'arnotta': ['arnotta', 'natator'], + 'arnotto': ['arnotto', 'notator'], + 'arnut': ['arnut', 'tuarn', 'untar'], + 'aro': ['aro', 'oar', 'ora'], + 'aroast': ['aroast', 'ostara'], + 'arock': ['arock', 'croak'], + 'aroid': ['aroid', 'doria', 'radio'], + 'aroides': ['ardoise', 'aroides', 'soredia'], + 'aroint': ['aroint', 'ration'], + 'aromatic': ['aromatic', 'macrotia'], + 'aroon': ['aroon', 'oraon'], + 'arose': ['arose', 'oreas'], + 'around': ['around', 'arundo'], + 'arpen': ['arpen', 'paren'], + 'arpent': ['arpent', + 'enrapt', + 'entrap', + 'panter', + 'parent', + 'pretan', + 'trepan'], + 'arrah': ['arhar', 'arrah'], + 'arras': ['arras', 'sarra'], + 'arrau': ['arrau', 'aurar'], + 'arrayer': ['arrayer', 'rearray'], + 'arrect': ['arrect', 'carter', 'crater', 'recart', 'tracer'], + 'arrector': ['arrector', 'carroter'], + 'arrent': ['arrent', 'errant', 'ranter', 'ternar'], + 'arrest': ['arrest', 'astrer', 'raster', 'starer'], + 'arrestable': ['arbalester', 'arbalestre', 'arrestable'], + 'arrester': ['arrester', 'rearrest'], + 'arresting': ['arresting', 'astringer'], + 'arretine': ['arretine', 'eretrian', 'eritrean', 'retainer'], + 'arride': ['arride', 'raider'], + 'arrie': ['airer', 'arrie'], + 'arriet': ['arriet', 'tarrie'], + 'arrish': ['arrish', 'harris', 'rarish', 'sirrah'], + 'arrive': ['arrive', 'varier'], + 'arrogance': ['arrogance', 'coarrange'], + 'arrogant': ['arrogant', 'tarragon'], + 'arrogative': ['arrogative', 'variegator'], + 'arrowy': ['arrowy', 'yarrow'], + 'arry': ['arry', 'yarr'], + 'arsacid': ['arsacid', 'ascarid'], + 'arse': ['arse', 'rase', 'sare', 'sear', 'sera'], + 'arsedine': ['arsedine', 'arsenide', 'sedanier', 'siderean'], + 'arsenal': ['arsenal', 'ranales'], + 'arsenate': ['arsenate', 'serenata'], + 'arsenation': ['arsenation', 'senatorian', 'sonneratia'], + 'arseniate': ['arseniate', 'saernaite'], + 'arsenic': ['arsenic', 'cerasin', 'sarcine'], + 'arsenide': ['arsedine', 'arsenide', 'sedanier', 'siderean'], + 'arsenite': ['arsenite', 'resinate', 'teresian', 'teresina'], + 'arsenium': ['aneurism', 'arsenium', 'sumerian'], + 'arseniuret': ['arseniuret', 'uniserrate'], + 'arseno': ['arseno', 'reason'], + 'arsenopyrite': ['arsenopyrite', 'pyroarsenite'], + 'arsenous': ['anserous', 'arsenous'], + 'arses': ['arses', 'rasse'], + 'arshine': ['arshine', 'nearish', 'rhesian', 'sherani'], + 'arsine': ['arisen', 'arsine', 'resina', 'serian'], + 'arsino': ['arsino', 'rasion', 'sonrai'], + 'arsis': ['arsis', 'sarsi'], + 'arsle': ['arles', 'arsle', 'laser', 'seral', 'slare'], + 'arson': ['arson', 'saron', 'sonar'], + 'arsonic': ['arsonic', 'saronic'], + 'arsonite': ['arsonite', 'asterion', 'oestrian', 'rosinate', 'serotina'], + 'art': ['art', 'rat', 'tar', 'tra'], + 'artaba': ['artaba', 'batara'], + 'artabe': ['abater', 'artabe', 'eartab', 'trabea'], + 'artal': ['altar', 'artal', 'ratal', 'talar'], + 'artamus': ['artamus', 'sumatra'], + 'artarine': ['artarine', 'errantia'], + 'artefact': ['afteract', 'artefact', 'farcetta', 'farctate'], + 'artel': ['alert', 'alter', 'artel', 'later', 'ratel', 'taler', 'telar'], + 'artemas': ['artemas', 'astream'], + 'artemia': ['ametria', 'artemia', 'meratia', 'ramaite'], + 'artemis': ['artemis', 'maestri', 'misrate'], + 'artemisic': ['ameristic', 'armistice', 'artemisic'], + 'arterial': ['arterial', 'triareal'], + 'arterin': ['arterin', 'retrain', 'terrain', 'trainer'], + 'arterious': ['arterious', 'autoriser'], + 'artesian': ['artesian', 'asterina', 'asternia', 'erastian', 'seatrain'], + 'artgum': ['artgum', 'targum'], + 'artha': ['arhat', 'artha', 'athar'], + 'arthel': ['arthel', 'halter', 'lather', 'thaler'], + 'arthemis': ['arthemis', 'marshite', 'meharist'], + 'arthrochondritis': ['arthrochondritis', 'chondroarthritis'], + 'arthromere': ['arthromere', 'metrorrhea'], + 'arthropodan': ['anarthropod', 'arthropodan'], + 'arthrosteitis': ['arthrosteitis', 'ostearthritis'], + 'article': ['article', 'recital'], + 'articled': ['articled', 'lacertid'], + 'artie': ['arite', 'artie', 'irate', 'retia', 'tarie'], + 'artifice': ['actifier', 'artifice'], + 'artisan': ['artisan', 'astrain', 'sartain', 'tsarina'], + 'artisanship': ['antiphrasis', 'artisanship'], + 'artist': ['artist', 'strait', 'strati'], + 'artiste': ['artiste', 'striate'], + 'artlet': ['artlet', 'latter', 'rattle', 'tartle', 'tatler'], + 'artlike': ['artlike', 'ratlike', 'tarlike'], + 'arty': ['arty', 'atry', 'tray'], + 'aru': ['aru', 'rua', 'ura'], + 'aruac': ['aruac', 'carua'], + 'arui': ['arui', 'uria'], + 'arum': ['arum', 'maru', 'mura'], + 'arundo': ['around', 'arundo'], + 'arunta': ['arnaut', 'arunta'], + 'arusa': ['arusa', 'saura', 'usara'], + 'arusha': ['arusha', 'aushar'], + 'arustle': ['arustle', 'estrual', 'saluter', 'saulter'], + 'arval': ['alvar', 'arval', 'larva'], + 'arvel': ['arvel', 'larve', 'laver', 'ravel', 'velar'], + 'arx': ['arx', 'rax'], + 'ary': ['ary', 'ray', 'yar'], + 'arya': ['arya', 'raya'], + 'aryan': ['aryan', 'nayar', 'rayan'], + 'aryl': ['aryl', 'lyra', 'ryal', 'yarl'], + 'as': ['as', 'sa'], + 'asa': ['asa', 'saa'], + 'asak': ['asak', 'kasa', 'saka'], + 'asana': ['anasa', 'asana'], + 'asaph': ['asaph', 'pasha'], + 'asaphia': ['aphasia', 'asaphia'], + 'asaphic': ['aphasic', 'asaphic'], + 'asaprol': ['asaprol', 'parasol'], + 'asarh': ['asarh', 'raash', 'sarah'], + 'asarite': ['asarite', 'asteria', 'atresia', 'setaria'], + 'asarum': ['aramus', 'asarum'], + 'asbest': ['asbest', 'basset'], + 'ascanius': ['anacusis', 'ascanius'], + 'ascare': ['ascare', 'caesar', 'resaca'], + 'ascarid': ['arsacid', 'ascarid'], + 'ascaris': ['ascaris', 'carissa'], + 'ascendance': ['adnascence', 'ascendance'], + 'ascendant': ['adnascent', 'ascendant'], + 'ascender': ['ascender', 'reascend'], + 'ascent': ['ascent', 'secant', 'stance'], + 'ascertain': ['ascertain', 'cartesian', 'cartisane', 'sectarian'], + 'ascertainer': ['ascertainer', 'reascertain', 'secretarian'], + 'ascetic': ['ascetic', 'castice', 'siccate'], + 'ascham': ['ascham', 'chasma'], + 'asci': ['acis', 'asci', 'saic'], + 'ascian': ['ascian', 'sacian', 'scania', 'sicana'], + 'ascidia': ['ascidia', 'diascia'], + 'ascii': ['ascii', 'isiac'], + 'ascites': ['ascites', 'ectasis'], + 'ascitic': ['ascitic', 'sciatic'], + 'ascitical': ['ascitical', 'sciatical'], + 'asclent': ['asclent', 'scantle'], + 'asclepian': ['asclepian', 'spalacine'], + 'ascolichen': ['ascolichen', 'chalcosine'], + 'ascon': ['ascon', 'canso', 'oscan'], + 'ascot': ['ascot', 'coast', 'costa', 'tacso', 'tasco'], + 'ascription': ['antipsoric', 'ascription', 'crispation'], + 'ascry': ['ascry', 'scary', 'scray'], + 'ascula': ['ascula', 'calusa', 'casual', 'casula', 'causal'], + 'asdic': ['asdic', 'sadic'], + 'ase': ['aes', 'ase', 'sea'], + 'asearch': ['asearch', 'eschara'], + 'aselli': ['allies', 'aselli'], + 'asem': ['asem', 'mesa', 'same', 'seam'], + 'asemia': ['asemia', 'saeima'], + 'aseptic': ['aseptic', 'spicate'], + 'aseptol': ['apostle', 'aseptol'], + 'ash': ['ash', 'sah', 'sha'], + 'ashanti': ['ashanti', 'sanhita', 'shaitan', 'thasian'], + 'ashen': ['ashen', 'hanse', 'shane', 'shean'], + 'asher': ['asher', 'share', 'shear'], + 'ashet': ['ashet', 'haste', 'sheat'], + 'ashimmer': ['ashimmer', 'haremism'], + 'ashir': ['ashir', 'shari'], + 'ashling': ['anglish', 'ashling'], + 'ashman': ['ashman', 'shaman'], + 'ashore': ['ahorse', 'ashore', 'hoarse', 'shorea'], + 'ashraf': ['afshar', 'ashraf'], + 'ashur': ['ashur', 'surah'], + 'ashy': ['ashy', 'shay'], + 'asian': ['asian', 'naias', 'sanai'], + 'asiarch': ['arachis', 'asiarch', 'saharic'], + 'aside': ['aides', 'aside', 'sadie'], + 'asideu': ['asideu', 'suidae'], + 'asiento': ['aeonist', 'asiento', 'satieno'], + 'asilid': ['asilid', 'sialid'], + 'asilidae': ['asilidae', 'sialidae'], + 'asilus': ['asilus', 'lasius'], + 'asimen': ['asimen', 'inseam', 'mesian'], + 'asimmer': ['amerism', 'asimmer', 'sammier'], + 'asiphonate': ['asiphonate', 'asthenopia'], + 'ask': ['ask', 'sak'], + 'asker': ['asker', 'reask', 'saker', 'sekar'], + 'askew': ['askew', 'wakes'], + 'askip': ['askip', 'spaik'], + 'askr': ['askr', 'kras', 'sark'], + 'aslant': ['aslant', 'lansat', 'natals', 'santal'], + 'asleep': ['asleep', 'elapse', 'please'], + 'aslope': ['aslope', 'poales'], + 'asmalte': ['asmalte', 'maltase'], + 'asmile': ['amiles', 'asmile', 'mesail', 'mesial', 'samiel'], + 'asnort': ['asnort', 'satron'], + 'asoak': ['asoak', 'asoka'], + 'asok': ['asok', 'soak', 'soka'], + 'asoka': ['asoak', 'asoka'], + 'asonia': ['anosia', 'asonia'], + 'asop': ['asop', 'sapo', 'soap'], + 'asor': ['asor', 'rosa', 'soar', 'sora'], + 'asp': ['asp', 'sap', 'spa'], + 'aspartic': ['aspartic', 'satrapic'], + 'aspection': ['aspection', 'stenopaic'], + 'aspectual': ['aspectual', 'capsulate'], + 'aspen': ['aspen', 'panse', 'snape', 'sneap', 'spane', 'spean'], + 'asper': ['asper', 'parse', 'prase', 'spaer', 'spare', 'spear'], + 'asperate': ['asperate', 'separate'], + 'asperation': ['anisoptera', 'asperation', 'separation'], + 'asperge': ['asperge', 'presage'], + 'asperger': ['asperger', 'presager'], + 'aspergil': ['aspergil', 'splairge'], + 'asperite': ['asperite', 'parietes'], + 'aspermia': ['aspermia', 'sapremia'], + 'aspermic': ['aspermic', 'sapremic'], + 'asperser': ['asperser', 'repasser'], + 'asperulous': ['asperulous', 'pleasurous'], + 'asphalt': ['asphalt', 'spathal', 'taplash'], + 'aspic': ['aspic', 'spica'], + 'aspidinol': ['aspidinol', 'diplasion'], + 'aspirant': ['aspirant', 'partisan', 'spartina'], + 'aspirata': ['aspirata', 'parasita'], + 'aspirate': ['aspirate', 'parasite'], + 'aspire': ['aspire', 'paries', 'praise', 'sirpea', 'spirea'], + 'aspirer': ['aspirer', 'praiser', 'serpari'], + 'aspiring': ['aspiring', 'praising', 'singarip'], + 'aspiringly': ['aspiringly', 'praisingly'], + 'aspish': ['aspish', 'phasis'], + 'asporous': ['asporous', 'saporous'], + 'asport': ['asport', 'pastor', 'sproat'], + 'aspread': ['aspread', 'saperda'], + 'aspring': ['aspring', 'rasping', 'sparing'], + 'asquirm': ['asquirm', 'marquis'], + 'assagai': ['assagai', 'gaiassa'], + 'assailer': ['assailer', 'reassail'], + 'assam': ['amass', 'assam', 'massa', 'samas'], + 'assaulter': ['assaulter', 'reassault', 'saleratus'], + 'assayer': ['assayer', 'reassay'], + 'assemble': ['assemble', 'beamless'], + 'assent': ['assent', 'snaste'], + 'assenter': ['assenter', 'reassent', 'sarsenet'], + 'assentor': ['assentor', 'essorant', 'starnose'], + 'assert': ['assert', 'tasser'], + 'asserter': ['asserter', 'reassert'], + 'assertible': ['assertible', 'resistable'], + 'assertional': ['assertional', 'sensatorial'], + 'assertor': ['assertor', 'assorter', 'oratress', 'reassort'], + 'asset': ['asset', 'tasse'], + 'assets': ['assets', 'stases'], + 'assidean': ['assidean', 'nassidae'], + 'assiento': ['assiento', 'ossetian'], + 'assignee': ['agenesis', 'assignee'], + 'assigner': ['assigner', 'reassign'], + 'assist': ['assist', 'stasis'], + 'assister': ['assister', 'reassist'], + 'associationism': ['associationism', 'misassociation'], + 'assoilment': ['assoilment', 'salmonsite'], + 'assorter': ['assertor', 'assorter', 'oratress', 'reassort'], + 'assuage': ['assuage', 'sausage'], + 'assume': ['assume', 'seamus'], + 'assumer': ['assumer', 'erasmus', 'masseur'], + 'ast': ['ast', 'sat'], + 'astacus': ['acastus', 'astacus'], + 'astare': ['astare', 'satrae'], + 'astart': ['astart', 'strata'], + 'astartian': ['astartian', 'astrantia'], + 'astatine': ['astatine', 'sanitate'], + 'asteep': ['asteep', 'peseta'], + 'asteer': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'astelic': ['astelic', 'elastic', 'latices'], + 'astely': ['alytes', 'astely', 'lysate', 'stealy'], + 'aster': ['aster', 'serta', 'stare', 'strae', 'tarse', 'teras'], + 'asteria': ['asarite', 'asteria', 'atresia', 'setaria'], + 'asterias': ['aristeas', 'asterias'], + 'asterikos': ['asterikos', 'keratosis'], + 'asterin': ['asterin', 'eranist', 'restain', 'stainer', 'starnie', 'stearin'], + 'asterina': ['artesian', 'asterina', 'asternia', 'erastian', 'seatrain'], + 'asterion': ['arsonite', 'asterion', 'oestrian', 'rosinate', 'serotina'], + 'astern': ['astern', 'enstar', 'stenar', 'sterna'], + 'asternia': ['artesian', 'asterina', 'asternia', 'erastian', 'seatrain'], + 'asteroid': ['asteroid', 'troiades'], + 'asterope': ['asterope', 'protease'], + 'asthenopia': ['asiphonate', 'asthenopia'], + 'asthma': ['amsath', 'asthma'], + 'asthmogenic': ['asthmogenic', 'mesognathic'], + 'asthore': ['asthore', 'earshot'], + 'astian': ['astian', 'tasian'], + 'astigmism': ['astigmism', 'sigmatism'], + 'astilbe': ['astilbe', 'bestial', 'blastie', 'stabile'], + 'astint': ['astint', 'tanist'], + 'astir': ['arist', + 'astir', + 'sitar', + 'stair', + 'stria', + 'tarsi', + 'tisar', + 'trias'], + 'astomous': ['astomous', 'somatous'], + 'astonied': ['astonied', 'sedation'], + 'astonisher': ['astonisher', 'reastonish', 'treasonish'], + 'astor': ['astor', 'roast'], + 'astragali': ['astragali', 'tarsalgia'], + 'astrain': ['artisan', 'astrain', 'sartain', 'tsarina'], + 'astral': ['astral', 'tarsal'], + 'astrantia': ['astartian', 'astrantia'], + 'astream': ['artemas', 'astream'], + 'astrer': ['arrest', 'astrer', 'raster', 'starer'], + 'astrict': ['astrict', 'cartist', 'stratic'], + 'astride': ['astride', 'diaster', 'disrate', 'restiad', 'staired'], + 'astrier': ['astrier', 'tarsier'], + 'astringe': ['astringe', 'ganister', 'gantries'], + 'astringent': ['astringent', 'transigent'], + 'astringer': ['arresting', 'astringer'], + 'astrodome': ['astrodome', 'roomstead'], + 'astrofel': ['astrofel', 'forestal'], + 'astroite': ['astroite', 'ostraite', 'storiate'], + 'astrolabe': ['astrolabe', 'roastable'], + 'astrut': ['astrut', 'rattus', 'stuart'], + 'astur': ['astur', 'surat', 'sutra'], + 'asturian': ['asturian', 'austrian', 'saturnia'], + 'astute': ['astute', 'statue'], + 'astylar': ['astylar', 'saltary'], + 'asunder': ['asunder', 'drusean'], + 'asuri': ['arius', 'asuri'], + 'aswail': ['aswail', 'sawali'], + 'asweat': ['asweat', 'awaste'], + 'aswim': ['aswim', 'swami'], + 'aswing': ['aswing', 'sawing'], + 'asyla': ['asyla', 'salay', 'sayal'], + 'asyllabic': ['asyllabic', 'basically'], + 'asyndetic': ['asyndetic', 'cystidean', 'syndicate'], + 'asynergia': ['asynergia', 'gainsayer'], + 'at': ['at', 'ta'], + 'ata': ['ata', 'taa'], + 'atabal': ['albata', 'atabal', 'balata'], + 'atabrine': ['atabrine', 'rabatine'], + 'atacaman': ['atacaman', 'tamanaca'], + 'ataentsic': ['anticaste', 'ataentsic'], + 'atalan': ['atalan', 'tanala'], + 'atap': ['atap', 'pata', 'tapa'], + 'atazir': ['atazir', 'ziarat'], + 'atchison': ['atchison', 'chitosan'], + 'ate': ['ate', 'eat', 'eta', 'tae', 'tea'], + 'ateba': ['abate', 'ateba', 'batea', 'beata'], + 'atebrin': ['atebrin', 'rabinet'], + 'atechnic': ['atechnic', 'catechin', 'technica'], + 'atechny': ['atechny', 'chantey'], + 'ateeter': ['ateeter', 'treatee'], + 'atef': ['atef', 'fate', 'feat'], + 'ateles': ['ateles', 'saltee', 'sealet', 'stelae', 'teasel'], + 'atelets': ['atelets', 'tsatlee'], + 'atelier': ['atelier', 'tiralee'], + 'aten': ['ante', 'aten', 'etna', 'nate', 'neat', 'taen', 'tane', 'tean'], + 'atenism': ['atenism', 'inmeats', 'insteam', 'samnite'], + 'atenist': ['atenist', 'instate', 'satient', 'steatin'], + 'ates': ['ates', 'east', 'eats', 'sate', 'seat', 'seta'], + 'atestine': ['anisette', 'atestine', 'settaine'], + 'athar': ['arhat', 'artha', 'athar'], + 'atheism': ['atheism', 'hamites'], + 'athena': ['ahtena', 'aneath', 'athena'], + 'athenian': ['anthinae', 'athenian'], + 'athenor': ['another', 'athenor', 'rheotan'], + 'athens': ['athens', 'hasten', 'snathe', 'sneath'], + 'atherine': ['atherine', 'herniate'], + 'atheris': ['atheris', 'sheriat'], + 'athermic': ['athermic', 'marchite', 'rhematic'], + 'athing': ['anight', 'athing'], + 'athirst': ['athirst', 'rattish', 'tartish'], + 'athletic': ['athletic', 'thetical'], + 'athletics': ['athletics', 'statelich'], + 'athort': ['athort', 'throat'], + 'athrive': ['athrive', 'hervati'], + 'ati': ['ait', 'ati', 'ita', 'tai'], + 'atik': ['atik', 'ikat'], + 'atimon': ['atimon', 'manito', 'montia'], + 'atingle': ['atingle', 'gelatin', 'genital', 'langite', 'telinga'], + 'atip': ['atip', 'pita'], + 'atis': ['atis', 'sita', 'tsia'], + 'atlantean': ['antenatal', 'atlantean', 'tantalean'], + 'atlantic': ['atlantic', 'tantalic'], + 'atlantid': ['atlantid', 'dilatant'], + 'atlantite': ['atlantite', 'tantalite'], + 'atlas': ['atlas', 'salat', 'salta'], + 'atle': ['atle', 'laet', 'late', 'leat', 'tael', 'tale', 'teal'], + 'atlee': ['atlee', 'elate'], + 'atloidean': ['atloidean', 'dealation'], + 'atma': ['atma', 'tama'], + 'atman': ['atman', 'manta'], + 'atmid': ['admit', 'atmid'], + 'atmo': ['atmo', 'atom', 'moat', 'toma'], + 'atmogenic': ['atmogenic', 'geomantic'], + 'atmos': ['atmos', 'stoma', 'tomas'], + 'atmosphere': ['atmosphere', 'shapometer'], + 'atmostea': ['atmostea', 'steatoma'], + 'atnah': ['atnah', 'tanha', 'thana'], + 'atocia': ['atocia', 'coaita'], + 'atokal': ['atokal', 'lakota'], + 'atoll': ['allot', 'atoll'], + 'atom': ['atmo', 'atom', 'moat', 'toma'], + 'atomic': ['atomic', 'matico'], + 'atomics': ['atomics', 'catoism', 'cosmati', 'osmatic', 'somatic'], + 'atomize': ['atomize', 'miaotze'], + 'atomizer': ['amortize', 'atomizer'], + 'atonal': ['atonal', 'latona'], + 'atonalism': ['anomalist', 'atonalism'], + 'atone': ['atone', 'oaten'], + 'atoner': ['atoner', 'norate', 'ornate'], + 'atonia': ['anotia', 'atonia'], + 'atonic': ['action', 'atonic', 'cation'], + 'atony': ['atony', 'ayont'], + 'atop': ['atop', 'pato'], + 'atopic': ['atopic', 'capito', 'copita'], + 'atorai': ['atorai', 'otaria'], + 'atrail': ['altair', 'atrail', 'atrial', 'lariat', 'latria', 'talari'], + 'atrepsy': ['atrepsy', 'yapster'], + 'atresia': ['asarite', 'asteria', 'atresia', 'setaria'], + 'atresic': ['atresic', 'stearic'], + 'atresy': ['atresy', 'estray', 'reasty', 'stayer'], + 'atretic': ['atretic', 'citrate'], + 'atria': ['arati', 'atria', 'riata', 'tarai', 'tiara'], + 'atrial': ['altair', 'atrail', 'atrial', 'lariat', 'latria', 'talari'], + 'atridean': ['anteriad', 'atridean', 'dentaria'], + 'atrip': ['atrip', 'tapir'], + 'atrocity': ['atrocity', 'citatory'], + 'atrophied': ['aphrodite', 'atrophied', 'diaporthe'], + 'atropia': ['apiator', 'atropia', 'parotia'], + 'atropic': ['apricot', 'atropic', 'parotic', 'patrico'], + 'atroscine': ['atroscine', 'certosina', 'ostracine', 'tinoceras', 'tricosane'], + 'atry': ['arty', 'atry', 'tray'], + 'attacco': ['attacco', 'toccata'], + 'attach': ['attach', 'chatta'], + 'attache': ['attache', 'thecata'], + 'attacher': ['attacher', 'reattach'], + 'attacker': ['attacker', 'reattack'], + 'attain': ['attain', 'tatian'], + 'attainder': ['antitrade', 'attainder'], + 'attainer': ['attainer', 'reattain', 'tertiana'], + 'attar': ['attar', 'tatar'], + 'attempter': ['attempter', 'reattempt'], + 'attender': ['attender', 'nattered', 'reattend'], + 'attention': ['attention', 'tentation'], + 'attentive': ['attentive', 'tentative'], + 'attentively': ['attentively', 'tentatively'], + 'attentiveness': ['attentiveness', 'tentativeness'], + 'atter': ['atter', 'tater', 'teart', 'tetra', 'treat'], + 'attermine': ['antimeter', 'attermine', 'interteam', 'terminate', 'tetramine'], + 'attern': ['attern', 'natter', 'ratten', 'tarten'], + 'attery': ['attery', 'treaty', 'yatter'], + 'attester': ['attester', 'reattest'], + 'attic': ['attic', 'catti', 'tacit'], + 'attical': ['attical', 'cattail'], + 'attinge': ['attinge', 'tintage'], + 'attire': ['attire', 'ratite', 'tertia'], + 'attired': ['attired', 'tradite'], + 'attorn': ['attorn', 'ratton', 'rottan'], + 'attracter': ['attracter', 'reattract'], + 'attractor': ['attractor', 'tractator'], + 'attrite': ['attrite', 'titrate'], + 'attrition': ['attrition', 'titration'], + 'attune': ['attune', 'nutate', 'tauten'], + 'atule': ['aleut', 'atule'], + 'atumble': ['atumble', 'mutable'], + 'atwin': ['atwin', 'twain', 'witan'], + 'atypic': ['atypic', 'typica'], + 'aube': ['aube', 'beau'], + 'aubrite': ['abiuret', 'aubrite', 'biurate', 'rubiate'], + 'aucan': ['acuan', 'aucan'], + 'aucaner': ['arecuna', 'aucaner'], + 'auchlet': ['auchlet', 'cutheal', 'taluche'], + 'auction': ['auction', 'caution'], + 'auctionary': ['auctionary', 'cautionary'], + 'audiencier': ['audiencier', 'enicuridae'], + 'auge': ['ague', 'auge'], + 'augen': ['augen', 'genua'], + 'augend': ['augend', 'engaud', 'unaged'], + 'auger': ['argue', 'auger'], + 'augerer': ['augerer', 'reargue'], + 'augh': ['augh', 'guha'], + 'augmenter': ['argenteum', 'augmenter'], + 'augustan': ['augustan', 'guatusan'], + 'auh': ['ahu', 'auh', 'hau'], + 'auk': ['aku', 'auk', 'kua'], + 'auld': ['auld', 'dual', 'laud', 'udal'], + 'aulete': ['aulete', 'eluate'], + 'auletic': ['aleutic', 'auletic', 'caulite', 'lutecia'], + 'auletris': ['auletris', 'lisuarte'], + 'aulic': ['aulic', 'lucia'], + 'aulostoma': ['aulostoma', 'autosomal'], + 'aulu': ['aulu', 'ulua'], + 'aum': ['aum', 'mau'], + 'aumbry': ['ambury', 'aumbry'], + 'aumil': ['aumil', 'miaul'], + 'aumrie': ['aumrie', 'uremia'], + 'auncel': ['auncel', 'cuneal', 'lacune', 'launce', 'unlace'], + 'aunt': ['antu', 'aunt', 'naut', 'taun', 'tuan', 'tuna'], + 'auntie': ['auntie', 'uniate'], + 'auntish': ['auntish', 'inhaust'], + 'auntly': ['auntly', 'lutany'], + 'auntsary': ['auntsary', 'unastray'], + 'aura': ['aaru', 'aura'], + 'aural': ['aural', 'laura'], + 'aurar': ['arrau', 'aurar'], + 'auresca': ['auresca', 'caesura'], + 'aureus': ['aureus', 'uraeus'], + 'auricle': ['auricle', 'ciruela'], + 'auricled': ['auricled', 'radicule'], + 'auride': ['auride', 'rideau'], + 'aurin': ['aurin', 'urian'], + 'aurir': ['aurir', 'urari'], + 'auriscalp': ['auriscalp', 'spiracula'], + 'auscult': ['auscult', 'scutula'], + 'aushar': ['arusha', 'aushar'], + 'auster': ['auster', 'reatus'], + 'austere': ['austere', 'euaster'], + 'australian': ['australian', 'saturnalia'], + 'australic': ['australic', 'lactarius'], + 'australite': ['aristulate', 'australite'], + 'austrian': ['asturian', 'austrian', 'saturnia'], + 'aute': ['aute', 'etua'], + 'autecism': ['autecism', 'musicate'], + 'authotype': ['authotype', 'autophyte'], + 'autoclave': ['autoclave', 'vacuolate'], + 'autocrat': ['actuator', 'autocrat'], + 'autoheterosis': ['autoheterosis', 'heteroousiast'], + 'autometric': ['autometric', 'tautomeric'], + 'autometry': ['autometry', 'tautomery'], + 'autophyte': ['authotype', 'autophyte'], + 'autoplast': ['autoplast', 'postulata'], + 'autopsic': ['autopsic', 'captious'], + 'autopsical': ['apolaustic', 'autopsical'], + 'autoradiograph': ['autoradiograph', 'radioautograph'], + 'autoradiographic': ['autoradiographic', 'radioautographic'], + 'autoradiography': ['autoradiography', 'radioautography'], + 'autoriser': ['arterious', 'autoriser'], + 'autosomal': ['aulostoma', 'autosomal'], + 'auxetic': ['auxetic', 'eutaxic'], + 'aval': ['aval', 'lava'], + 'avanti': ['avanti', 'vinata'], + 'avar': ['avar', 'vara'], + 'ave': ['ave', 'eva'], + 'avenge': ['avenge', 'geneva', 'vangee'], + 'avenger': ['avenger', 'engrave'], + 'avenin': ['avenin', 'vienna'], + 'aventine': ['aventine', 'venetian'], + 'aventurine': ['aventurine', 'uninervate'], + 'aver': ['aver', 'rave', 'vare', 'vera'], + 'avera': ['avera', 'erava'], + 'averil': ['averil', 'elvira'], + 'averin': ['averin', 'ravine'], + 'avert': ['avert', 'tarve', 'taver', 'trave'], + 'avertible': ['avertible', 'veritable'], + 'avertin': ['avertin', 'vitrean'], + 'aves': ['aves', 'save', 'vase'], + 'aviatic': ['aviatic', 'viatica'], + 'aviator': ['aviator', 'tovaria'], + 'avicular': ['avicular', 'varicula'], + 'avid': ['avid', 'diva'], + 'avidous': ['avidous', 'vaudois'], + 'avignonese': ['avignonese', 'ingaevones'], + 'avine': ['avine', 'naive', 'vinea'], + 'avirulence': ['acervuline', 'avirulence'], + 'avis': ['avis', 'siva', 'visa'], + 'avitic': ['avitic', 'viatic'], + 'avo': ['avo', 'ova'], + 'avocet': ['avocet', 'octave', 'vocate'], + 'avodire': ['avodire', 'avoider', 'reavoid'], + 'avoider': ['avodire', 'avoider', 'reavoid'], + 'avolation': ['avolation', 'ovational'], + 'avolitional': ['avolitional', 'violational'], + 'avoucher': ['avoucher', 'reavouch'], + 'avower': ['avower', 'reavow'], + 'avshar': ['avshar', 'varsha'], + 'avulse': ['alveus', 'avulse'], + 'aw': ['aw', 'wa'], + 'awag': ['awag', 'waag'], + 'awaiter': ['awaiter', 'reawait'], + 'awakener': ['awakener', 'reawaken'], + 'awarder': ['awarder', 'reaward'], + 'awash': ['awash', 'sawah'], + 'awaste': ['asweat', 'awaste'], + 'awat': ['awat', 'tawa'], + 'awd': ['awd', 'daw', 'wad'], + 'awe': ['awe', 'wae', 'wea'], + 'aweather': ['aweather', 'wheatear'], + 'aweek': ['aweek', 'keawe'], + 'awesome': ['awesome', 'waesome'], + 'awest': ['awest', 'sweat', 'tawse', 'waste'], + 'awfu': ['awfu', 'wauf'], + 'awful': ['awful', 'fulwa'], + 'awhet': ['awhet', 'wheat'], + 'awin': ['awin', 'wain'], + 'awing': ['awing', 'wigan'], + 'awl': ['awl', 'law'], + 'awn': ['awn', 'naw', 'wan'], + 'awned': ['awned', 'dewan', 'waned'], + 'awner': ['awner', 'newar'], + 'awning': ['awning', 'waning'], + 'awny': ['awny', 'wany', 'yawn'], + 'awol': ['alow', 'awol', 'lowa'], + 'awork': ['awork', 'korwa'], + 'awreck': ['awreck', 'wacker'], + 'awrong': ['awrong', 'growan'], + 'awry': ['awry', 'wary'], + 'axel': ['alex', 'axel', 'axle'], + 'axes': ['axes', 'saxe', 'seax'], + 'axil': ['alix', 'axil'], + 'axile': ['axile', 'lexia'], + 'axine': ['axine', 'xenia'], + 'axle': ['alex', 'axel', 'axle'], + 'axon': ['axon', 'noxa', 'oxan'], + 'axonal': ['axonal', 'oxalan'], + 'axonia': ['anoxia', 'axonia'], + 'ay': ['ay', 'ya'], + 'ayah': ['ayah', 'haya'], + 'aye': ['aye', 'yea'], + 'ayont': ['atony', 'ayont'], + 'azimine': ['aminize', 'animize', 'azimine'], + 'azo': ['azo', 'zoa'], + 'azole': ['azole', 'zoeal'], + 'azon': ['azon', 'onza', 'ozan'], + 'azorian': ['arizona', 'azorian', 'zonaria'], + 'azorite': ['azorite', 'zoarite'], + 'azoxine': ['azoxine', 'oxazine'], + 'azteca': ['azteca', 'zacate'], + 'azurine': ['azurine', 'urazine'], + 'ba': ['ab', 'ba'], + 'baa': ['aba', 'baa'], + 'baal': ['alba', 'baal', 'bala'], + 'baalath': ['baalath', 'bathala'], + 'baalite': ['baalite', 'bialate', 'labiate'], + 'baalshem': ['baalshem', 'shamable'], + 'baar': ['arab', 'arba', 'baar', 'bara'], + 'bab': ['abb', 'bab'], + 'baba': ['abba', 'baba'], + 'babbler': ['babbler', 'blabber', 'brabble'], + 'babery': ['babery', 'yabber'], + 'babhan': ['babhan', 'habnab'], + 'babishly': ['babishly', 'shabbily'], + 'babishness': ['babishness', 'shabbiness'], + 'babite': ['babite', 'bebait'], + 'babu': ['babu', 'buba'], + 'babul': ['babul', 'bubal'], + 'baby': ['abby', 'baby'], + 'babylonish': ['babylonish', 'nabobishly'], + 'bac': ['bac', 'cab'], + 'bacao': ['bacao', 'caoba'], + 'bach': ['bach', 'chab'], + 'bache': ['bache', 'beach'], + 'bachel': ['bachel', 'bleach'], + 'bachelor': ['bachelor', 'crabhole'], + 'bacillar': ['bacillar', 'cabrilla'], + 'bacis': ['bacis', 'basic'], + 'backblow': ['backblow', 'blowback'], + 'backen': ['backen', 'neback'], + 'backer': ['backer', 'reback'], + 'backfall': ['backfall', 'fallback'], + 'backfire': ['backfire', 'fireback'], + 'backlog': ['backlog', 'gablock'], + 'backrun': ['backrun', 'runback'], + 'backsaw': ['backsaw', 'sawback'], + 'backset': ['backset', 'setback'], + 'backstop': ['backstop', 'stopback'], + 'backswing': ['backswing', 'swingback'], + 'backward': ['backward', 'drawback'], + 'backway': ['backway', 'wayback'], + 'bacon': ['bacon', 'banco'], + 'bacterial': ['bacterial', 'calibrate'], + 'bacteriform': ['bacteriform', 'bracteiform'], + 'bacterin': ['bacterin', 'centibar'], + 'bacterioid': ['aborticide', 'bacterioid'], + 'bacterium': ['bacterium', 'cumbraite'], + 'bactrian': ['bactrian', 'cantabri'], + 'bacula': ['albuca', 'bacula'], + 'baculi': ['abulic', 'baculi'], + 'baculite': ['baculite', 'cubitale'], + 'baculites': ['baculites', 'bisulcate'], + 'baculoid': ['baculoid', 'cuboidal'], + 'bad': ['bad', 'dab'], + 'badaga': ['badaga', 'dagaba', 'gadaba'], + 'badan': ['badan', 'banda'], + 'bade': ['abed', 'bade', 'bead'], + 'badge': ['badge', 'begad'], + 'badian': ['badian', 'indaba'], + 'badigeon': ['badigeon', 'gabioned'], + 'badly': ['badly', 'baldy', 'blady'], + 'badon': ['badon', 'bando'], + 'bae': ['abe', 'bae', 'bea'], + 'baeria': ['aberia', 'baeria', 'baiera'], + 'baetulus': ['baetulus', 'subulate'], + 'baetyl': ['baetyl', 'baylet', 'bleaty'], + 'baetylic': ['baetylic', 'biacetyl'], + 'bafta': ['abaft', 'bafta'], + 'bag': ['bag', 'gab'], + 'bagani': ['bagani', 'bangia', 'ibanag'], + 'bagel': ['bagel', 'belga', 'gable', 'gleba'], + 'bagger': ['bagger', 'beggar'], + 'bagnio': ['bagnio', 'gabion', 'gobian'], + 'bago': ['bago', 'boga'], + 'bagre': ['bagre', 'barge', 'begar', 'rebag'], + 'bahar': ['bahar', 'bhara'], + 'bahoe': ['bahoe', 'bohea', 'obeah'], + 'baht': ['baht', 'bath', 'bhat'], + 'baiera': ['aberia', 'baeria', 'baiera'], + 'baignet': ['baignet', 'beating'], + 'bail': ['albi', 'bail', 'bali'], + 'bailage': ['algieba', 'bailage'], + 'bailer': ['bailer', 'barile'], + 'baillone': ['baillone', 'bonellia'], + 'bailment': ['bailment', 'libament'], + 'bailor': ['bailor', 'bioral'], + 'bailsman': ['bailsman', 'balanism', 'nabalism'], + 'bain': ['bain', 'bani', 'iban'], + 'baioc': ['baioc', 'cabio', 'cobia'], + 'bairam': ['bairam', 'bramia'], + 'bairn': ['abrin', 'bairn', 'brain', 'brian', 'rabin'], + 'bairnie': ['aribine', 'bairnie', 'iberian'], + 'bairnish': ['bairnish', 'bisharin'], + 'bais': ['absi', 'bais', 'bias', 'isba'], + 'baister': ['baister', 'tribase'], + 'baiter': ['baiter', 'barite', 'rebait', 'terbia'], + 'baith': ['baith', 'habit'], + 'bajocian': ['bajocian', 'jacobian'], + 'bakal': ['bakal', 'balak'], + 'bakatan': ['bakatan', 'batakan'], + 'bake': ['bake', 'beak'], + 'baker': ['baker', 'brake', 'break'], + 'bakerless': ['bakerless', 'brakeless', 'breakless'], + 'bakery': ['bakery', 'barkey'], + 'bakie': ['akebi', 'bakie'], + 'baku': ['baku', 'kuba'], + 'bal': ['alb', 'bal', 'lab'], + 'bala': ['alba', 'baal', 'bala'], + 'baladine': ['baladine', 'balaenid'], + 'balaenid': ['baladine', 'balaenid'], + 'balagan': ['balagan', 'bangala'], + 'balai': ['balai', 'labia'], + 'balak': ['bakal', 'balak'], + 'balan': ['alban', 'balan', 'banal', 'laban', 'nabal', 'nabla'], + 'balancer': ['balancer', 'barnacle'], + 'balangay': ['balangay', 'bangalay'], + 'balanic': ['balanic', 'caliban'], + 'balanid': ['balanid', 'banilad'], + 'balanism': ['bailsman', 'balanism', 'nabalism'], + 'balanite': ['albanite', 'balanite', 'nabalite'], + 'balanites': ['balanites', 'basaltine', 'stainable'], + 'balantidium': ['antialbumid', 'balantidium'], + 'balanus': ['balanus', 'nabalus', 'subanal'], + 'balas': ['balas', 'balsa', 'basal', 'sabal'], + 'balata': ['albata', 'atabal', 'balata'], + 'balatron': ['balatron', 'laborant'], + 'balaustine': ['balaustine', 'unsatiable'], + 'balaustre': ['balaustre', 'saturable'], + 'bald': ['bald', 'blad'], + 'balden': ['balden', 'bandle'], + 'balder': ['balder', 'bardel', 'bedlar', 'bedral', 'belard', 'blader'], + 'baldie': ['abdiel', 'baldie'], + 'baldish': ['baldish', 'bladish'], + 'baldmoney': ['baldmoney', 'molybdena'], + 'baldness': ['baldness', 'bandless'], + 'baldy': ['badly', 'baldy', 'blady'], + 'bale': ['abel', 'able', 'albe', 'bale', 'beal', 'bela', 'blae'], + 'balearic': ['balearic', 'cebalrai'], + 'baleen': ['baleen', 'enable'], + 'balefire': ['afebrile', 'balefire', 'fireable'], + 'baleise': ['baleise', 'besaiel'], + 'baler': ['abler', 'baler', 'belar', 'blare', 'blear'], + 'balete': ['balete', 'belate'], + 'bali': ['albi', 'bail', 'bali'], + 'baline': ['baline', 'blaine'], + 'balinger': ['balinger', 'ringable'], + 'balker': ['balker', 'barkle'], + 'ballaster': ['ballaster', 'reballast'], + 'ballate': ['ballate', 'tabella'], + 'balli': ['balli', 'billa'], + 'balloter': ['balloter', 'reballot'], + 'ballplayer': ['ballplayer', 'preallably'], + 'ballroom': ['ballroom', 'moorball'], + 'ballweed': ['ballweed', 'weldable'], + 'balm': ['balm', 'lamb'], + 'balminess': ['balminess', 'lambiness'], + 'balmlike': ['balmlike', 'lamblike'], + 'balmy': ['balmy', 'lamby'], + 'balolo': ['balolo', 'lobola'], + 'balonea': ['abalone', 'balonea'], + 'balor': ['balor', 'bolar', 'boral', 'labor', 'lobar'], + 'balow': ['ablow', 'balow', 'bowla'], + 'balsa': ['balas', 'balsa', 'basal', 'sabal'], + 'balsam': ['balsam', 'sambal'], + 'balsamic': ['balsamic', 'cabalism'], + 'balsamo': ['absalom', 'balsamo'], + 'balsamy': ['abysmal', 'balsamy'], + 'balt': ['balt', 'blat'], + 'baltei': ['albeit', + 'albite', + 'baltei', + 'belait', + 'betail', + 'bletia', + 'libate'], + 'balter': ['albert', 'balter', 'labret', 'tabler'], + 'balteus': ['balteus', 'sublate'], + 'baltis': ['baltis', 'bisalt'], + 'balu': ['balu', 'baul', 'bual', 'luba'], + 'balunda': ['balunda', 'bulanda'], + 'baluster': ['baluster', 'rustable'], + 'balut': ['balut', 'tubal'], + 'bam': ['bam', 'mab'], + 'ban': ['ban', 'nab'], + 'bana': ['anba', 'bana'], + 'banak': ['banak', 'nabak'], + 'banal': ['alban', 'balan', 'banal', 'laban', 'nabal', 'nabla'], + 'banat': ['banat', 'batan'], + 'banca': ['banca', 'caban'], + 'bancal': ['bancal', 'blanca'], + 'banco': ['bacon', 'banco'], + 'banda': ['badan', 'banda'], + 'bandage': ['bandage', 'dagbane'], + 'bandar': ['bandar', 'raband'], + 'bandarlog': ['bandarlog', 'langobard'], + 'bande': ['bande', 'benda'], + 'bander': ['bander', 'brenda'], + 'banderma': ['banderma', 'breadman'], + 'banderole': ['banderole', 'bandoleer'], + 'bandhook': ['bandhook', 'handbook'], + 'bandle': ['balden', 'bandle'], + 'bandless': ['baldness', 'bandless'], + 'bando': ['badon', 'bando'], + 'bandoleer': ['banderole', 'bandoleer'], + 'bandor': ['bandor', 'bondar', 'roband'], + 'bandore': ['bandore', 'broaden'], + 'bane': ['bane', 'bean', 'bena'], + 'bangala': ['balagan', 'bangala'], + 'bangalay': ['balangay', 'bangalay'], + 'bangash': ['bangash', 'nashgab'], + 'banger': ['banger', 'engarb', 'graben'], + 'banghy': ['banghy', 'hangby'], + 'bangia': ['bagani', 'bangia', 'ibanag'], + 'bangle': ['bangle', 'bengal'], + 'bani': ['bain', 'bani', 'iban'], + 'banilad': ['balanid', 'banilad'], + 'banisher': ['banisher', 'rebanish'], + 'baniva': ['baniva', 'bavian'], + 'baniya': ['baniya', 'banyai'], + 'banjoist': ['banjoist', 'bostanji'], + 'bank': ['bank', 'knab', 'nabk'], + 'banker': ['banker', 'barken'], + 'banshee': ['banshee', 'benshea'], + 'bantam': ['bantam', 'batman'], + 'banteng': ['banteng', 'bentang'], + 'banyai': ['baniya', 'banyai'], + 'banzai': ['banzai', 'zabian'], + 'bar': ['bar', 'bra', 'rab'], + 'bara': ['arab', 'arba', 'baar', 'bara'], + 'barabra': ['barabra', 'barbara'], + 'barad': ['barad', 'draba'], + 'barb': ['barb', 'brab'], + 'barbara': ['barabra', 'barbara'], + 'barbe': ['barbe', 'bebar', 'breba', 'rebab'], + 'barbed': ['barbed', 'dabber'], + 'barbel': ['barbel', 'labber', 'rabble'], + 'barbet': ['barbet', 'rabbet', 'tabber'], + 'barbette': ['barbette', 'bebatter'], + 'barbion': ['barbion', 'rabboni'], + 'barbitone': ['barbitone', 'barbotine'], + 'barbone': ['barbone', 'bebaron'], + 'barbotine': ['barbitone', 'barbotine'], + 'barcella': ['barcella', 'caballer'], + 'barcoo': ['barcoo', 'baroco'], + 'bard': ['bard', 'brad', 'drab'], + 'bardel': ['balder', 'bardel', 'bedlar', 'bedral', 'belard', 'blader'], + 'bardie': ['abider', 'bardie'], + 'bardily': ['bardily', 'rabidly', 'ridably'], + 'bardiness': ['bardiness', 'rabidness'], + 'barding': ['barding', 'brigand'], + 'bardo': ['abord', 'bardo', 'board', 'broad', 'dobra', 'dorab'], + 'bardy': ['bardy', 'darby'], + 'bare': ['bare', 'bear', 'brae'], + 'barefaced': ['barefaced', 'facebread'], + 'barefoot': ['barefoot', 'bearfoot'], + 'barehanded': ['barehanded', 'bradenhead', 'headbander'], + 'barehead': ['barehead', 'braehead'], + 'barely': ['barely', 'barley', 'bleary'], + 'barer': ['barer', 'rebar'], + 'baretta': ['baretta', 'rabatte', 'tabaret'], + 'bargainer': ['bargainer', 'rebargain'], + 'barge': ['bagre', 'barge', 'begar', 'rebag'], + 'bargeer': ['bargeer', 'gerbera'], + 'bargeese': ['bargeese', 'begrease'], + 'bari': ['abir', 'bari', 'rabi'], + 'baric': ['baric', 'carib', 'rabic'], + 'barid': ['barid', 'bidar', 'braid', 'rabid'], + 'barie': ['barie', 'beira', 'erbia', 'rebia'], + 'barile': ['bailer', 'barile'], + 'baris': ['baris', 'sabir'], + 'barish': ['barish', 'shibar'], + 'barit': ['barit', 'ribat'], + 'barite': ['baiter', 'barite', 'rebait', 'terbia'], + 'baritone': ['abrotine', 'baritone', 'obtainer', 'reobtain'], + 'barken': ['banker', 'barken'], + 'barker': ['barker', 'braker'], + 'barkey': ['bakery', 'barkey'], + 'barkle': ['balker', 'barkle'], + 'barky': ['barky', 'braky'], + 'barley': ['barely', 'barley', 'bleary'], + 'barling': ['barling', 'bringal'], + 'barm': ['barm', 'bram'], + 'barmbrack': ['barmbrack', 'brambrack'], + 'barmote': ['barmote', 'bromate'], + 'barmy': ['ambry', 'barmy'], + 'barn': ['barn', 'bran'], + 'barnabite': ['barnabite', 'rabbanite', 'rabbinate'], + 'barnacle': ['balancer', 'barnacle'], + 'barney': ['barney', 'nearby'], + 'barny': ['barny', 'bryan'], + 'baroco': ['barcoo', 'baroco'], + 'barolo': ['barolo', 'robalo'], + 'baron': ['baron', 'boran'], + 'baronet': ['baronet', 'reboant'], + 'barong': ['barong', 'brogan'], + 'barosmin': ['ambrosin', 'barosmin', 'sabromin'], + 'barothermograph': ['barothermograph', 'thermobarograph'], + 'barotse': ['barotse', 'boaster', 'reboast', 'sorbate'], + 'barpost': ['absorpt', 'barpost'], + 'barracan': ['barracan', 'barranca'], + 'barranca': ['barracan', 'barranca'], + 'barrelet': ['barrelet', 'terebral'], + 'barret': ['barret', 'barter'], + 'barrette': ['barrette', 'batterer'], + 'barrio': ['barrio', 'brairo'], + 'barsac': ['barsac', 'scarab'], + 'barse': ['barse', 'besra', 'saber', 'serab'], + 'bart': ['bart', 'brat'], + 'barter': ['barret', 'barter'], + 'barton': ['barton', 'brotan'], + 'bartsia': ['arabist', 'bartsia'], + 'barundi': ['barundi', 'unbraid'], + 'barvel': ['barvel', 'blaver', 'verbal'], + 'barwise': ['barwise', 'swarbie'], + 'barye': ['barye', 'beray', 'yerba'], + 'baryta': ['baryta', 'taryba'], + 'barytine': ['barytine', 'bryanite'], + 'baryton': ['baryton', 'brotany'], + 'bas': ['bas', 'sab'], + 'basal': ['balas', 'balsa', 'basal', 'sabal'], + 'basally': ['basally', 'salably'], + 'basaltic': ['basaltic', 'cabalist'], + 'basaltine': ['balanites', 'basaltine', 'stainable'], + 'base': ['base', 'besa', 'sabe', 'seba'], + 'basella': ['basella', 'sabella', 'salable'], + 'bash': ['bash', 'shab'], + 'basial': ['basial', 'blasia'], + 'basic': ['bacis', 'basic'], + 'basically': ['asyllabic', 'basically'], + 'basidium': ['basidium', 'diiambus'], + 'basil': ['basil', 'labis'], + 'basileus': ['basileus', 'issuable', 'suasible'], + 'basilweed': ['basilweed', 'bladewise'], + 'basinasal': ['basinasal', 'bassalian'], + 'basinet': ['basinet', 'besaint', 'bestain'], + 'basion': ['basion', 'bonsai', 'sabino'], + 'basiparachromatin': ['basiparachromatin', 'marsipobranchiata'], + 'basket': ['basket', 'betask'], + 'basketwork': ['basketwork', 'workbasket'], + 'basos': ['basos', 'basso'], + 'bassalian': ['basinasal', 'bassalian'], + 'bassanite': ['bassanite', 'sebastian'], + 'basset': ['asbest', 'basset'], + 'basso': ['basos', 'basso'], + 'bast': ['bast', 'bats', 'stab'], + 'basta': ['basta', 'staab'], + 'baste': ['baste', 'beast', 'tabes'], + 'basten': ['absent', 'basten'], + 'baster': ['baster', 'bestar', 'breast'], + 'bastille': ['bastille', 'listable'], + 'bastion': ['abiston', 'bastion'], + 'bastionet': ['bastionet', 'obstinate'], + 'bastite': ['bastite', 'batiste', 'bistate'], + 'basto': ['basto', 'boast', 'sabot'], + 'basuto': ['abouts', 'basuto'], + 'bat': ['bat', 'tab'], + 'batad': ['abdat', 'batad'], + 'batakan': ['bakatan', 'batakan'], + 'bataleur': ['bataleur', 'tabulare'], + 'batan': ['banat', 'batan'], + 'batara': ['artaba', 'batara'], + 'batcher': ['batcher', 'berchta', 'brachet'], + 'bate': ['abet', 'bate', 'beat', 'beta'], + 'batea': ['abate', 'ateba', 'batea', 'beata'], + 'batel': ['batel', 'blate', 'bleat', 'table'], + 'batement': ['abetment', 'batement'], + 'bater': ['abret', 'bater', 'berat'], + 'batfowler': ['afterblow', 'batfowler'], + 'bath': ['baht', 'bath', 'bhat'], + 'bathala': ['baalath', 'bathala'], + 'bathe': ['bathe', 'beath'], + 'bather': ['bather', 'bertha', 'breath'], + 'bathonian': ['bathonian', 'nabothian'], + 'batik': ['batik', 'kitab'], + 'batino': ['batino', 'oatbin', 'obtain'], + 'batis': ['absit', 'batis'], + 'batiste': ['bastite', 'batiste', 'bistate'], + 'batling': ['batling', 'tabling'], + 'batman': ['bantam', 'batman'], + 'batophobia': ['batophobia', 'tabophobia'], + 'batrachia': ['batrachia', 'brachiata'], + 'batrachian': ['batrachian', 'branchiata'], + 'bats': ['bast', 'bats', 'stab'], + 'battel': ['battel', 'battle', 'tablet'], + 'batteler': ['batteler', 'berattle'], + 'battening': ['battening', 'bitangent'], + 'batter': ['batter', 'bertat', 'tabret', 'tarbet'], + 'batterer': ['barrette', 'batterer'], + 'battle': ['battel', 'battle', 'tablet'], + 'battler': ['battler', 'blatter', 'brattle'], + 'battue': ['battue', 'tubate'], + 'batule': ['batule', 'betula', 'tabule'], + 'batyphone': ['batyphone', 'hypnobate'], + 'batzen': ['batzen', 'bezant', 'tanzeb'], + 'baud': ['baud', 'buda', 'daub'], + 'baul': ['balu', 'baul', 'bual', 'luba'], + 'baun': ['baun', 'buna', 'nabu', 'nuba'], + 'bauta': ['abuta', 'bauta'], + 'bavian': ['baniva', 'bavian'], + 'baw': ['baw', 'wab'], + 'bawl': ['bawl', 'blaw'], + 'bawler': ['bawler', 'brelaw', 'rebawl', 'warble'], + 'bay': ['aby', 'bay'], + 'baya': ['baya', 'yaba'], + 'bayed': ['bayed', 'beady', 'beday'], + 'baylet': ['baetyl', 'baylet', 'bleaty'], + 'bayonet': ['bayonet', 'betoyan'], + 'baze': ['baze', 'ezba'], + 'bea': ['abe', 'bae', 'bea'], + 'beach': ['bache', 'beach'], + 'bead': ['abed', 'bade', 'bead'], + 'beaded': ['beaded', 'bedead'], + 'beader': ['beader', 'bedare'], + 'beadleism': ['beadleism', 'demisable'], + 'beadlet': ['beadlet', 'belated'], + 'beady': ['bayed', 'beady', 'beday'], + 'beagle': ['beagle', 'belage', 'belgae'], + 'beak': ['bake', 'beak'], + 'beaker': ['beaker', 'berake', 'rebake'], + 'beal': ['abel', 'able', 'albe', 'bale', 'beal', 'bela', 'blae'], + 'bealing': ['algenib', 'bealing', 'belgian', 'bengali'], + 'beam': ['beam', 'bema'], + 'beamer': ['ambeer', 'beamer'], + 'beamless': ['assemble', 'beamless'], + 'beamster': ['beamster', 'bemaster', 'bestream'], + 'beamwork': ['beamwork', 'bowmaker'], + 'beamy': ['beamy', 'embay', 'maybe'], + 'bean': ['bane', 'bean', 'bena'], + 'beanfield': ['beanfield', 'definable'], + 'beant': ['abnet', 'beant'], + 'bear': ['bare', 'bear', 'brae'], + 'bearance': ['bearance', 'carabeen'], + 'beard': ['ardeb', 'beard', 'bread', 'debar'], + 'beardless': ['beardless', 'breadless'], + 'beardlessness': ['beardlessness', 'breadlessness'], + 'bearer': ['bearer', 'rebear'], + 'bearess': ['bearess', 'bessera'], + 'bearfoot': ['barefoot', 'bearfoot'], + 'bearing': ['bearing', 'begrain', 'brainge', 'rigbane'], + 'bearlet': ['bearlet', 'bleater', 'elberta', 'retable'], + 'bearm': ['amber', 'bearm', 'bemar', 'bream', 'embar'], + 'beast': ['baste', 'beast', 'tabes'], + 'beastlily': ['beastlily', 'bestially'], + 'beat': ['abet', 'bate', 'beat', 'beta'], + 'beata': ['abate', 'ateba', 'batea', 'beata'], + 'beater': ['beater', 'berate', 'betear', 'rebate', 'rebeat'], + 'beath': ['bathe', 'beath'], + 'beating': ['baignet', 'beating'], + 'beau': ['aube', 'beau'], + 'bebait': ['babite', 'bebait'], + 'bebar': ['barbe', 'bebar', 'breba', 'rebab'], + 'bebaron': ['barbone', 'bebaron'], + 'bebaste': ['bebaste', 'bebeast'], + 'bebatter': ['barbette', 'bebatter'], + 'bebay': ['abbey', 'bebay'], + 'bebeast': ['bebaste', 'bebeast'], + 'bebog': ['bebog', 'begob', 'gobbe'], + 'becard': ['becard', 'braced'], + 'becater': ['becater', 'betrace'], + 'because': ['because', 'besauce'], + 'becharm': ['becharm', 'brecham', 'chamber'], + 'becher': ['becher', 'breech'], + 'bechern': ['bechern', 'bencher'], + 'bechirp': ['bechirp', 'brephic'], + 'becker': ['becker', 'rebeck'], + 'beclad': ['beclad', 'cabled'], + 'beclart': ['beclart', 'crablet'], + 'becloud': ['becloud', 'obclude'], + 'becram': ['becram', 'camber', 'crambe'], + 'becrimson': ['becrimson', 'scombrine'], + 'becry': ['becry', 'bryce'], + 'bed': ['bed', 'deb'], + 'bedamn': ['bedamn', 'bedman'], + 'bedare': ['beader', 'bedare'], + 'bedark': ['bedark', 'debark'], + 'beday': ['bayed', 'beady', 'beday'], + 'bedead': ['beaded', 'bedead'], + 'bedel': ['bedel', 'bleed'], + 'beden': ['beden', 'deben', 'deneb'], + 'bedim': ['bedim', 'imbed'], + 'bedip': ['bedip', 'biped'], + 'bedismal': ['bedismal', 'semibald'], + 'bedlam': ['bedlam', 'beldam', 'blamed'], + 'bedlar': ['balder', 'bardel', 'bedlar', 'bedral', 'belard', 'blader'], + 'bedless': ['bedless', 'blessed'], + 'bedman': ['bedamn', 'bedman'], + 'bedoctor': ['bedoctor', 'codebtor'], + 'bedog': ['bedog', 'bodge'], + 'bedrail': ['bedrail', 'bridale', 'ridable'], + 'bedral': ['balder', 'bardel', 'bedlar', 'bedral', 'belard', 'blader'], + 'bedrid': ['bedrid', 'bidder'], + 'bedrip': ['bedrip', 'prebid'], + 'bedrock': ['bedrock', 'brocked'], + 'bedroom': ['bedroom', 'boerdom', 'boredom'], + 'bedrown': ['bedrown', 'browden'], + 'bedrug': ['bedrug', 'budger'], + 'bedsick': ['bedsick', 'sickbed'], + 'beduck': ['beduck', 'bucked'], + 'bedur': ['bedur', 'rebud', 'redub'], + 'bedusk': ['bedusk', 'busked'], + 'bedust': ['bedust', 'bestud', 'busted'], + 'beearn': ['beearn', 'berean'], + 'beeman': ['beeman', 'bemean', 'bename'], + 'been': ['been', 'bene', 'eben'], + 'beer': ['beer', 'bere', 'bree'], + 'beest': ['beest', 'beset'], + 'beeswing': ['beeswing', 'beswinge'], + 'befathered': ['befathered', 'featherbed'], + 'befile': ['befile', 'belief'], + 'befinger': ['befinger', 'befringe'], + 'beflea': ['beflea', 'beleaf'], + 'beflour': ['beflour', 'fourble'], + 'beflum': ['beflum', 'fumble'], + 'befret': ['befret', 'bereft'], + 'befringe': ['befinger', 'befringe'], + 'begad': ['badge', 'begad'], + 'begall': ['begall', 'glebal'], + 'begar': ['bagre', 'barge', 'begar', 'rebag'], + 'begash': ['begash', 'beshag'], + 'begat': ['begat', 'betag'], + 'begettal': ['begettal', 'gettable'], + 'beggar': ['bagger', 'beggar'], + 'beggarer': ['beggarer', 'rebeggar'], + 'begin': ['begin', 'being', 'binge'], + 'begird': ['begird', 'bridge'], + 'beglic': ['beglic', 'belgic'], + 'bego': ['bego', 'egbo'], + 'begob': ['bebog', 'begob', 'gobbe'], + 'begone': ['begone', 'engobe'], + 'begrain': ['bearing', 'begrain', 'brainge', 'rigbane'], + 'begrease': ['bargeese', 'begrease'], + 'behaviorism': ['behaviorism', 'misbehavior'], + 'behears': ['behears', 'beshear'], + 'behint': ['behint', 'henbit'], + 'beholder': ['beholder', 'rebehold'], + 'behorn': ['behorn', 'brehon'], + 'beid': ['beid', 'bide', 'debi', 'dieb'], + 'being': ['begin', 'being', 'binge'], + 'beira': ['barie', 'beira', 'erbia', 'rebia'], + 'beisa': ['abies', 'beisa'], + 'bel': ['bel', 'elb'], + 'bela': ['abel', 'able', 'albe', 'bale', 'beal', 'bela', 'blae'], + 'belabor': ['belabor', 'borable'], + 'belaced': ['belaced', 'debacle'], + 'belage': ['beagle', 'belage', 'belgae'], + 'belait': ['albeit', + 'albite', + 'baltei', + 'belait', + 'betail', + 'bletia', + 'libate'], + 'belam': ['amble', 'belam', 'blame', 'mabel'], + 'belar': ['abler', 'baler', 'belar', 'blare', 'blear'], + 'belard': ['balder', 'bardel', 'bedlar', 'bedral', 'belard', 'blader'], + 'belate': ['balete', 'belate'], + 'belated': ['beadlet', 'belated'], + 'belaud': ['ablude', 'belaud'], + 'beldam': ['bedlam', 'beldam', 'blamed'], + 'beleaf': ['beflea', 'beleaf'], + 'beleap': ['beleap', 'bepale'], + 'belga': ['bagel', 'belga', 'gable', 'gleba'], + 'belgae': ['beagle', 'belage', 'belgae'], + 'belgian': ['algenib', 'bealing', 'belgian', 'bengali'], + 'belgic': ['beglic', 'belgic'], + 'belial': ['alible', 'belial', 'labile', 'liable'], + 'belief': ['befile', 'belief'], + 'belili': ['belili', 'billie'], + 'belite': ['belite', 'beltie', 'bietle'], + 'belitter': ['belitter', 'tribelet'], + 'belive': ['belive', 'beveil'], + 'bella': ['bella', 'label'], + 'bellied': ['bellied', 'delible'], + 'bellona': ['allbone', 'bellona'], + 'bellonian': ['bellonian', 'nonliable'], + 'bellote': ['bellote', 'lobelet'], + 'bellower': ['bellower', 'rebellow'], + 'belltail': ['belltail', 'bletilla', 'tillable'], + 'bellyer': ['bellyer', 'rebelly'], + 'bellypinch': ['bellypinch', 'pinchbelly'], + 'beloid': ['beloid', 'boiled', 'bolide'], + 'belonger': ['belonger', 'rebelong'], + 'belonid': ['belonid', 'boldine'], + 'belord': ['belord', 'bordel', 'rebold'], + 'below': ['below', 'bowel', 'elbow'], + 'belt': ['belt', 'blet'], + 'beltane': ['beltane', 'tenable'], + 'belter': ['belter', 'elbert', 'treble'], + 'beltie': ['belite', 'beltie', 'bietle'], + 'beltine': ['beltine', 'tenible'], + 'beltir': ['beltir', 'riblet'], + 'beltman': ['beltman', 'lambent'], + 'belve': ['belve', 'bevel'], + 'bema': ['beam', 'bema'], + 'bemail': ['bemail', 'lambie'], + 'beman': ['beman', 'nambe'], + 'bemar': ['amber', 'bearm', 'bemar', 'bream', 'embar'], + 'bemaster': ['beamster', 'bemaster', 'bestream'], + 'bemaul': ['bemaul', 'blumea'], + 'bemeal': ['bemeal', 'meable'], + 'bemean': ['beeman', 'bemean', 'bename'], + 'bemire': ['bemire', 'bireme'], + 'bemitred': ['bemitred', 'timbered'], + 'bemoil': ['bemoil', 'mobile'], + 'bemole': ['bemole', 'embole'], + 'bemusk': ['bemusk', 'embusk'], + 'ben': ['ben', 'neb'], + 'bena': ['bane', 'bean', 'bena'], + 'benacus': ['acubens', 'benacus'], + 'bename': ['beeman', 'bemean', 'bename'], + 'benami': ['benami', 'bimane'], + 'bencher': ['bechern', 'bencher'], + 'benchwork': ['benchwork', 'workbench'], + 'benda': ['bande', 'benda'], + 'bender': ['bender', 'berend', 'rebend'], + 'bene': ['been', 'bene', 'eben'], + 'benedight': ['benedight', 'benighted'], + 'benefiter': ['benefiter', 'rebenefit'], + 'bengal': ['bangle', 'bengal'], + 'bengali': ['algenib', 'bealing', 'belgian', 'bengali'], + 'beni': ['beni', 'bien', 'bine', 'inbe'], + 'benighted': ['benedight', 'benighted'], + 'beno': ['beno', 'bone', 'ebon'], + 'benote': ['benote', 'betone'], + 'benshea': ['banshee', 'benshea'], + 'benshee': ['benshee', 'shebeen'], + 'bentang': ['banteng', 'bentang'], + 'benton': ['benton', 'bonnet'], + 'benu': ['benu', 'unbe'], + 'benward': ['benward', 'brawned'], + 'benzantialdoxime': ['antibenzaldoxime', 'benzantialdoxime'], + 'benzein': ['benzein', 'benzine'], + 'benzine': ['benzein', 'benzine'], + 'benzo': ['benzo', 'bonze'], + 'benzofluorene': ['benzofluorene', 'fluorobenzene'], + 'benzonitrol': ['benzonitrol', 'nitrobenzol'], + 'bepale': ['beleap', 'bepale'], + 'bepart': ['bepart', 'berapt', 'betrap'], + 'bepaste': ['bepaste', 'bespate'], + 'bepester': ['bepester', 'prebeset'], + 'beplaster': ['beplaster', 'prestable'], + 'ber': ['ber', 'reb'], + 'berake': ['beaker', 'berake', 'rebake'], + 'berapt': ['bepart', 'berapt', 'betrap'], + 'berat': ['abret', 'bater', 'berat'], + 'berate': ['beater', 'berate', 'betear', 'rebate', 'rebeat'], + 'berattle': ['batteler', 'berattle'], + 'beraunite': ['beraunite', 'unebriate'], + 'beray': ['barye', 'beray', 'yerba'], + 'berberi': ['berberi', 'rebribe'], + 'berchta': ['batcher', 'berchta', 'brachet'], + 'bere': ['beer', 'bere', 'bree'], + 'berean': ['beearn', 'berean'], + 'bereft': ['befret', 'bereft'], + 'berend': ['bender', 'berend', 'rebend'], + 'berg': ['berg', 'gerb'], + 'bergama': ['bergama', 'megabar'], + 'bergamo': ['bergamo', 'embargo'], + 'beri': ['beri', 'bier', 'brei', 'ribe'], + 'beringed': ['beringed', 'breeding'], + 'berinse': ['berinse', 'besiren'], + 'berley': ['berley', 'bleery'], + 'berlinite': ['berlinite', 'libertine'], + 'bermudite': ['bermudite', 'demibrute'], + 'bernard': ['bernard', 'brander', 'rebrand'], + 'bernese': ['bernese', 'besneer'], + 'beroe': ['beroe', 'boree'], + 'beroida': ['beroida', 'boreiad'], + 'beroll': ['beroll', 'boller'], + 'berossos': ['berossos', 'obsessor'], + 'beround': ['beround', 'bounder', 'rebound', 'unbored', 'unorbed', 'unrobed'], + 'berri': ['berri', 'brier'], + 'berried': ['berried', 'briered'], + 'berrybush': ['berrybush', 'shrubbery'], + 'bersil': ['bersil', 'birsle'], + 'bert': ['bert', 'bret'], + 'bertat': ['batter', 'bertat', 'tabret', 'tarbet'], + 'berth': ['berth', 'breth'], + 'bertha': ['bather', 'bertha', 'breath'], + 'berther': ['berther', 'herbert'], + 'berthing': ['berthing', 'brighten'], + 'bertie': ['bertie', 'betire', 'rebite'], + 'bertolonia': ['bertolonia', 'borolanite'], + 'berust': ['berust', 'buster', 'stuber'], + 'bervie': ['bervie', 'brieve'], + 'beryllia': ['beryllia', 'reliably'], + 'besa': ['base', 'besa', 'sabe', 'seba'], + 'besaiel': ['baleise', 'besaiel'], + 'besaint': ['basinet', 'besaint', 'bestain'], + 'besauce': ['because', 'besauce'], + 'bescour': ['bescour', 'buceros', 'obscure'], + 'beset': ['beest', 'beset'], + 'beshadow': ['beshadow', 'bodewash'], + 'beshag': ['begash', 'beshag'], + 'beshear': ['behears', 'beshear'], + 'beshod': ['beshod', 'debosh'], + 'besiren': ['berinse', 'besiren'], + 'besit': ['besit', 'betis'], + 'beslaver': ['beslaver', 'servable', 'versable'], + 'beslime': ['beslime', 'besmile'], + 'beslings': ['beslings', 'blessing', 'glibness'], + 'beslow': ['beslow', 'bowels'], + 'besmile': ['beslime', 'besmile'], + 'besneer': ['bernese', 'besneer'], + 'besoot': ['besoot', 'bootes'], + 'besot': ['besot', 'betso'], + 'besoul': ['besoul', 'blouse', 'obelus'], + 'besour': ['besour', 'boreus', 'bourse', 'bouser'], + 'bespate': ['bepaste', 'bespate'], + 'besra': ['barse', 'besra', 'saber', 'serab'], + 'bessera': ['bearess', 'bessera'], + 'bestain': ['basinet', 'besaint', 'bestain'], + 'bestar': ['baster', 'bestar', 'breast'], + 'besteer': ['besteer', 'rebeset'], + 'bestial': ['astilbe', 'bestial', 'blastie', 'stabile'], + 'bestially': ['beastlily', 'bestially'], + 'bestiarian': ['antirabies', 'bestiarian'], + 'bestiary': ['bestiary', 'sybarite'], + 'bestir': ['bestir', 'bister'], + 'bestorm': ['bestorm', 'mobster'], + 'bestowal': ['bestowal', 'stowable'], + 'bestower': ['bestower', 'rebestow'], + 'bestraw': ['bestraw', 'wabster'], + 'bestream': ['beamster', 'bemaster', 'bestream'], + 'bestrew': ['bestrew', 'webster'], + 'bestride': ['bestride', 'bistered'], + 'bestud': ['bedust', 'bestud', 'busted'], + 'beswinge': ['beeswing', 'beswinge'], + 'beta': ['abet', 'bate', 'beat', 'beta'], + 'betag': ['begat', 'betag'], + 'betail': ['albeit', + 'albite', + 'baltei', + 'belait', + 'betail', + 'bletia', + 'libate'], + 'betailor': ['betailor', 'laborite', 'orbitale'], + 'betask': ['basket', 'betask'], + 'betear': ['beater', 'berate', 'betear', 'rebate', 'rebeat'], + 'beth': ['beth', 'theb'], + 'betire': ['bertie', 'betire', 'rebite'], + 'betis': ['besit', 'betis'], + 'betone': ['benote', 'betone'], + 'betoss': ['betoss', 'bosset'], + 'betoya': ['betoya', 'teaboy'], + 'betoyan': ['bayonet', 'betoyan'], + 'betrace': ['becater', 'betrace'], + 'betrail': ['betrail', 'librate', 'triable', 'trilabe'], + 'betrap': ['bepart', 'berapt', 'betrap'], + 'betrayal': ['betrayal', 'tearably'], + 'betrayer': ['betrayer', 'eatberry', 'rebetray', 'teaberry'], + 'betread': ['betread', 'debater'], + 'betrim': ['betrim', 'timber', 'timbre'], + 'betso': ['besot', 'betso'], + 'betta': ['betta', 'tabet'], + 'bettina': ['bettina', 'tabinet', 'tibetan'], + 'betula': ['batule', 'betula', 'tabule'], + 'betulin': ['betulin', 'bluntie'], + 'beturbaned': ['beturbaned', 'unrabbeted'], + 'beveil': ['belive', 'beveil'], + 'bevel': ['belve', 'bevel'], + 'bever': ['bever', 'breve'], + 'bewailer': ['bewailer', 'rebewail'], + 'bework': ['bework', 'bowker'], + 'bey': ['bey', 'bye'], + 'beydom': ['beydom', 'embody'], + 'bezant': ['batzen', 'bezant', 'tanzeb'], + 'bezzo': ['bezzo', 'bozze'], + 'bhakti': ['bhakti', 'khatib'], + 'bhandari': ['bhandari', 'hairband'], + 'bhar': ['bhar', 'harb'], + 'bhara': ['bahar', 'bhara'], + 'bhat': ['baht', 'bath', 'bhat'], + 'bhima': ['bhima', 'biham'], + 'bhotia': ['bhotia', 'tobiah'], + 'bhutani': ['bhutani', 'unhabit'], + 'biacetyl': ['baetylic', 'biacetyl'], + 'bialate': ['baalite', 'bialate', 'labiate'], + 'bialveolar': ['bialveolar', 'labiovelar'], + 'bianca': ['abanic', 'bianca'], + 'bianco': ['bianco', 'bonaci'], + 'biangular': ['biangular', 'bulgarian'], + 'bias': ['absi', 'bais', 'bias', 'isba'], + 'biatomic': ['biatomic', 'moabitic'], + 'bible': ['bible', 'blibe'], + 'bicarpellary': ['bicarpellary', 'prebacillary'], + 'bickern': ['bickern', 'bricken'], + 'biclavate': ['activable', 'biclavate'], + 'bicorn': ['bicorn', 'bicron'], + 'bicornate': ['bicornate', 'carbonite', 'reboantic'], + 'bicrenate': ['abenteric', 'bicrenate'], + 'bicron': ['bicorn', 'bicron'], + 'bicrural': ['bicrural', 'rubrical'], + 'bid': ['bid', 'dib'], + 'bidar': ['barid', 'bidar', 'braid', 'rabid'], + 'bidder': ['bedrid', 'bidder'], + 'bide': ['beid', 'bide', 'debi', 'dieb'], + 'bident': ['bident', 'indebt'], + 'bidented': ['bidented', 'indebted'], + 'bider': ['bider', 'bredi', 'bride', 'rebid'], + 'bidet': ['bidet', 'debit'], + 'biduous': ['biduous', 'dubious'], + 'bien': ['beni', 'bien', 'bine', 'inbe'], + 'bier': ['beri', 'bier', 'brei', 'ribe'], + 'bietle': ['belite', 'beltie', 'bietle'], + 'bifer': ['bifer', 'brief', 'fiber'], + 'big': ['big', 'gib'], + 'biga': ['agib', 'biga', 'gabi'], + 'bigamous': ['bigamous', 'subimago'], + 'bigener': ['bigener', 'rebegin'], + 'bigential': ['bigential', 'tangibile'], + 'biggin': ['biggin', 'gibing'], + 'bigoted': ['bigoted', 'dogbite'], + 'biham': ['bhima', 'biham'], + 'bihari': ['bihari', 'habiri'], + 'bike': ['bike', 'kibe'], + 'bikram': ['bikram', 'imbark'], + 'bilaan': ['albian', 'bilaan'], + 'bilaterality': ['alterability', 'bilaterality', 'relatability'], + 'bilati': ['bilati', 'tibial'], + 'bilby': ['bilby', 'libby'], + 'bildar': ['bildar', 'bridal', 'ribald'], + 'bilge': ['bilge', 'gibel'], + 'biliate': ['biliate', 'tibiale'], + 'bilinear': ['bilinear', 'liberian'], + 'billa': ['balli', 'billa'], + 'billboard': ['billboard', 'broadbill'], + 'biller': ['biller', 'rebill'], + 'billeter': ['billeter', 'rebillet'], + 'billie': ['belili', 'billie'], + 'bilo': ['bilo', 'boil'], + 'bilobated': ['bilobated', 'bobtailed'], + 'biltong': ['biltong', 'bolting'], + 'bim': ['bim', 'mib'], + 'bimane': ['benami', 'bimane'], + 'bimodality': ['bimodality', 'myliobatid'], + 'bimotors': ['bimotors', 'robotism'], + 'bin': ['bin', 'nib'], + 'binal': ['albin', 'binal', 'blain'], + 'binary': ['binary', 'brainy'], + 'binder': ['binder', 'inbred', 'rebind'], + 'bindwood': ['bindwood', 'woodbind'], + 'bine': ['beni', 'bien', 'bine', 'inbe'], + 'binge': ['begin', 'being', 'binge'], + 'bino': ['bino', 'bion', 'boni'], + 'binocular': ['binocular', 'caliburno', 'colubrina'], + 'binomial': ['binomial', 'mobilian'], + 'binuclear': ['binuclear', 'incurable'], + 'biod': ['biod', 'boid'], + 'bion': ['bino', 'bion', 'boni'], + 'biopsychological': ['biopsychological', 'psychobiological'], + 'biopsychology': ['biopsychology', 'psychobiology'], + 'bioral': ['bailor', 'bioral'], + 'biorgan': ['biorgan', 'grobian'], + 'bios': ['bios', 'bois'], + 'biosociological': ['biosociological', 'sociobiological'], + 'biota': ['biota', 'ibota'], + 'biotics': ['biotics', 'cobitis'], + 'bipartile': ['bipartile', 'pretibial'], + 'biped': ['bedip', 'biped'], + 'bipedal': ['bipedal', 'piebald'], + 'bipersonal': ['bipersonal', 'prisonable'], + 'bipolar': ['bipolar', 'parboil'], + 'biracial': ['biracial', 'cibarial'], + 'birchen': ['birchen', 'brichen'], + 'bird': ['bird', 'drib'], + 'birdeen': ['birdeen', 'inbreed'], + 'birdlet': ['birdlet', 'driblet'], + 'birdling': ['birdling', 'bridling', 'lingbird'], + 'birdman': ['birdman', 'manbird'], + 'birdseed': ['birdseed', 'seedbird'], + 'birdstone': ['birdstone', 'stonebird'], + 'bireme': ['bemire', 'bireme'], + 'biretta': ['biretta', 'brattie', 'ratbite'], + 'birle': ['birle', 'liber'], + 'birma': ['abrim', 'birma'], + 'birn': ['birn', 'brin'], + 'birny': ['birny', 'briny'], + 'biron': ['biron', 'inorb', 'robin'], + 'birse': ['birse', 'ribes'], + 'birsle': ['bersil', 'birsle'], + 'birth': ['birth', 'brith'], + 'bis': ['bis', 'sib'], + 'bisalt': ['baltis', 'bisalt'], + 'bisaltae': ['bisaltae', 'satiable'], + 'bisharin': ['bairnish', 'bisharin'], + 'bistate': ['bastite', 'batiste', 'bistate'], + 'bister': ['bestir', 'bister'], + 'bistered': ['bestride', 'bistered'], + 'bisti': ['bisti', 'bitis'], + 'bisulcate': ['baculites', 'bisulcate'], + 'bit': ['bit', 'tib'], + 'bitangent': ['battening', 'bitangent'], + 'bitemporal': ['bitemporal', 'importable'], + 'biter': ['biter', 'tribe'], + 'bitis': ['bisti', 'bitis'], + 'bito': ['bito', 'obit'], + 'bitonality': ['bitonality', 'notability'], + 'bittern': ['bittern', 'britten'], + 'bitumed': ['bitumed', 'budtime'], + 'biurate': ['abiuret', 'aubrite', 'biurate', 'rubiate'], + 'biwa': ['biwa', 'wabi'], + 'bizarre': ['bizarre', 'brazier'], + 'bizet': ['bizet', 'zibet'], + 'blabber': ['babbler', 'blabber', 'brabble'], + 'blackacre': ['blackacre', 'crackable'], + 'blad': ['bald', 'blad'], + 'blader': ['balder', 'bardel', 'bedlar', 'bedral', 'belard', 'blader'], + 'bladewise': ['basilweed', 'bladewise'], + 'bladish': ['baldish', 'bladish'], + 'blady': ['badly', 'baldy', 'blady'], + 'blae': ['abel', 'able', 'albe', 'bale', 'beal', 'bela', 'blae'], + 'blaeberry': ['blaeberry', 'bleaberry'], + 'blaeness': ['ableness', 'blaeness', 'sensable'], + 'blain': ['albin', 'binal', 'blain'], + 'blaine': ['baline', 'blaine'], + 'blair': ['blair', 'brail', 'libra'], + 'blake': ['blake', 'bleak', 'kabel'], + 'blame': ['amble', 'belam', 'blame', 'mabel'], + 'blamed': ['bedlam', 'beldam', 'blamed'], + 'blamer': ['ambler', 'blamer', 'lamber', 'marble', 'ramble'], + 'blaming': ['ambling', 'blaming'], + 'blamingly': ['amblingly', 'blamingly'], + 'blanca': ['bancal', 'blanca'], + 'blare': ['abler', 'baler', 'belar', 'blare', 'blear'], + 'blarina': ['blarina', 'branial'], + 'blarney': ['blarney', 'renably'], + 'blas': ['blas', 'slab'], + 'blase': ['blase', 'sable'], + 'blasia': ['basial', 'blasia'], + 'blastema': ['blastema', 'lambaste'], + 'blastemic': ['blastemic', 'cembalist'], + 'blaster': ['blaster', 'reblast', 'stabler'], + 'blastie': ['astilbe', 'bestial', 'blastie', 'stabile'], + 'blasting': ['blasting', 'stabling'], + 'blastoderm': ['blastoderm', 'dermoblast'], + 'blastogenic': ['blastogenic', 'genoblastic'], + 'blastomeric': ['blastomeric', 'meroblastic'], + 'blastomycetic': ['blastomycetic', 'cytoblastemic'], + 'blastomycetous': ['blastomycetous', 'cytoblastemous'], + 'blasty': ['blasty', 'stably'], + 'blat': ['balt', 'blat'], + 'blate': ['batel', 'blate', 'bleat', 'table'], + 'blather': ['blather', 'halbert'], + 'blatter': ['battler', 'blatter', 'brattle'], + 'blaver': ['barvel', 'blaver', 'verbal'], + 'blaw': ['bawl', 'blaw'], + 'blay': ['ably', 'blay', 'yalb'], + 'blazoner': ['albronze', 'blazoner'], + 'bleaberry': ['blaeberry', 'bleaberry'], + 'bleach': ['bachel', 'bleach'], + 'bleacher': ['bleacher', 'rebleach'], + 'bleak': ['blake', 'bleak', 'kabel'], + 'bleaky': ['bleaky', 'kabyle'], + 'blear': ['abler', 'baler', 'belar', 'blare', 'blear'], + 'bleared': ['bleared', 'reblade'], + 'bleary': ['barely', 'barley', 'bleary'], + 'bleat': ['batel', 'blate', 'bleat', 'table'], + 'bleater': ['bearlet', 'bleater', 'elberta', 'retable'], + 'bleating': ['bleating', 'tangible'], + 'bleaty': ['baetyl', 'baylet', 'bleaty'], + 'bleed': ['bedel', 'bleed'], + 'bleery': ['berley', 'bleery'], + 'blender': ['blender', 'reblend'], + 'blendure': ['blendure', 'rebundle'], + 'blennoid': ['blennoid', 'blondine'], + 'blennoma': ['blennoma', 'nobleman'], + 'bleo': ['bleo', 'bole', 'lobe'], + 'blepharocera': ['blepharocera', 'reproachable'], + 'blessed': ['bedless', 'blessed'], + 'blesser': ['blesser', 'rebless'], + 'blessing': ['beslings', 'blessing', 'glibness'], + 'blet': ['belt', 'blet'], + 'bletia': ['albeit', + 'albite', + 'baltei', + 'belait', + 'betail', + 'bletia', + 'libate'], + 'bletilla': ['belltail', 'bletilla', 'tillable'], + 'blibe': ['bible', 'blibe'], + 'blighter': ['blighter', 'therblig'], + 'blimy': ['blimy', 'limby'], + 'blister': ['blister', 'bristle'], + 'blisterwort': ['blisterwort', 'bristlewort'], + 'blitter': ['blitter', 'brittle', 'triblet'], + 'blo': ['blo', 'lob'], + 'bloated': ['bloated', 'lobated'], + 'bloater': ['alberto', 'bloater', 'latrobe'], + 'bloating': ['bloating', 'obligant'], + 'blocker': ['blocker', 'brockle', 'reblock'], + 'blonde': ['blonde', 'bolden'], + 'blondine': ['blennoid', 'blondine'], + 'blood': ['blood', 'boldo'], + 'bloodleaf': ['bloodleaf', 'floodable'], + 'bloomer': ['bloomer', 'rebloom'], + 'bloomy': ['bloomy', 'lomboy'], + 'blore': ['blore', 'roble'], + 'blosmy': ['blosmy', 'symbol'], + 'blot': ['blot', 'bolt'], + 'blotless': ['blotless', 'boltless'], + 'blotter': ['blotter', 'bottler'], + 'blotting': ['blotting', 'bottling'], + 'blouse': ['besoul', 'blouse', 'obelus'], + 'blow': ['blow', 'bowl'], + 'blowback': ['backblow', 'blowback'], + 'blower': ['blower', 'bowler', 'reblow', 'worble'], + 'blowfly': ['blowfly', 'flyblow'], + 'blowing': ['blowing', 'bowling'], + 'blowout': ['blowout', 'outblow', 'outbowl'], + 'blowup': ['blowup', 'upblow'], + 'blowy': ['blowy', 'bowly'], + 'blub': ['blub', 'bulb'], + 'blubber': ['blubber', 'bubbler'], + 'blue': ['blue', 'lube'], + 'bluegill': ['bluegill', 'gullible'], + 'bluenose': ['bluenose', 'nebulose'], + 'bluer': ['bluer', 'brule', 'burel', 'ruble'], + 'blues': ['blues', 'bulse'], + 'bluffer': ['bluffer', 'rebluff'], + 'bluishness': ['bluishness', 'blushiness'], + 'bluism': ['bluism', 'limbus'], + 'blumea': ['bemaul', 'blumea'], + 'blunder': ['blunder', 'bundler'], + 'blunderer': ['blunderer', 'reblunder'], + 'blunge': ['blunge', 'bungle'], + 'blunger': ['blunger', 'bungler'], + 'bluntie': ['betulin', 'bluntie'], + 'blur': ['blur', 'burl'], + 'blushiness': ['bluishness', 'blushiness'], + 'bluster': ['bluster', 'brustle', 'bustler'], + 'boa': ['abo', 'boa'], + 'boar': ['boar', 'bora'], + 'board': ['abord', 'bardo', 'board', 'broad', 'dobra', 'dorab'], + 'boarder': ['arbored', 'boarder', 'reboard'], + 'boardly': ['boardly', 'broadly'], + 'boardy': ['boardy', 'boyard', 'byroad'], + 'boast': ['basto', 'boast', 'sabot'], + 'boaster': ['barotse', 'boaster', 'reboast', 'sorbate'], + 'boasting': ['boasting', 'bostangi'], + 'boat': ['boat', 'bota', 'toba'], + 'boater': ['boater', 'borate', 'rebato'], + 'boathouse': ['boathouse', 'houseboat'], + 'bobac': ['bobac', 'cabob'], + 'bobfly': ['bobfly', 'flobby'], + 'bobo': ['bobo', 'boob'], + 'bobtailed': ['bilobated', 'bobtailed'], + 'bocardo': ['bocardo', 'cordoba'], + 'boccale': ['boccale', 'cabocle'], + 'bocher': ['bocher', 'broche'], + 'bocking': ['bocking', 'kingcob'], + 'bod': ['bod', 'dob'], + 'bode': ['bode', 'dobe'], + 'boden': ['boden', 'boned'], + 'boder': ['boder', 'orbed'], + 'bodewash': ['beshadow', 'bodewash'], + 'bodge': ['bedog', 'bodge'], + 'bodhi': ['bodhi', 'dhobi'], + 'bodice': ['bodice', 'ceboid'], + 'bodier': ['bodier', 'boride', 'brodie'], + 'bodle': ['bodle', 'boled', 'lobed'], + 'bodo': ['bodo', 'bood', 'doob'], + 'body': ['body', 'boyd', 'doby'], + 'boer': ['boer', 'bore', 'robe'], + 'boerdom': ['bedroom', 'boerdom', 'boredom'], + 'boethian': ['boethian', 'nebaioth'], + 'bog': ['bog', 'gob'], + 'boga': ['bago', 'boga'], + 'bogan': ['bogan', 'goban'], + 'bogeyman': ['bogeyman', 'moneybag'], + 'boggler': ['boggler', 'broggle'], + 'boglander': ['boglander', 'longbeard'], + 'bogle': ['bogle', 'globe'], + 'boglet': ['boglet', 'goblet'], + 'bogo': ['bogo', 'gobo'], + 'bogue': ['bogue', 'bouge'], + 'bogum': ['bogum', 'gumbo'], + 'bogy': ['bogy', 'bygo', 'goby'], + 'bohea': ['bahoe', 'bohea', 'obeah'], + 'boho': ['boho', 'hobo'], + 'bohor': ['bohor', 'rohob'], + 'boid': ['biod', 'boid'], + 'boil': ['bilo', 'boil'], + 'boiled': ['beloid', 'boiled', 'bolide'], + 'boiler': ['boiler', 'reboil'], + 'boilover': ['boilover', 'overboil'], + 'bois': ['bios', 'bois'], + 'bojo': ['bojo', 'jobo'], + 'bolar': ['balor', 'bolar', 'boral', 'labor', 'lobar'], + 'bolden': ['blonde', 'bolden'], + 'bolderian': ['bolderian', 'ordinable'], + 'boldine': ['belonid', 'boldine'], + 'boldness': ['boldness', 'bondless'], + 'boldo': ['blood', 'boldo'], + 'bole': ['bleo', 'bole', 'lobe'], + 'boled': ['bodle', 'boled', 'lobed'], + 'bolelia': ['bolelia', 'lobelia', 'obelial'], + 'bolide': ['beloid', 'boiled', 'bolide'], + 'boller': ['beroll', 'boller'], + 'bolo': ['bolo', 'bool', 'lobo', 'obol'], + 'bolster': ['bolster', 'lobster'], + 'bolt': ['blot', 'bolt'], + 'boltage': ['boltage', 'globate'], + 'bolter': ['bolter', 'orblet', 'reblot', 'rebolt'], + 'bolthead': ['bolthead', 'theobald'], + 'bolting': ['biltong', 'bolting'], + 'boltless': ['blotless', 'boltless'], + 'boltonia': ['boltonia', 'lobation', 'oblation'], + 'bom': ['bom', 'mob'], + 'boma': ['ambo', 'boma'], + 'bombable': ['bombable', 'mobbable'], + 'bombacaceae': ['bombacaceae', 'cabombaceae'], + 'bomber': ['bomber', 'mobber'], + 'bon': ['bon', 'nob'], + 'bonaci': ['bianco', 'bonaci'], + 'bonair': ['bonair', 'borani'], + 'bondage': ['bondage', 'dogbane'], + 'bondar': ['bandor', 'bondar', 'roband'], + 'bondless': ['boldness', 'bondless'], + 'bone': ['beno', 'bone', 'ebon'], + 'boned': ['boden', 'boned'], + 'bonefish': ['bonefish', 'fishbone'], + 'boneless': ['boneless', 'noblesse'], + 'bonellia': ['baillone', 'bonellia'], + 'boner': ['boner', 'borne'], + 'boney': ['boney', 'ebony'], + 'boni': ['bino', 'bion', 'boni'], + 'bonitary': ['bonitary', 'trainboy'], + 'bonk': ['bonk', 'knob'], + 'bonnet': ['benton', 'bonnet'], + 'bonsai': ['basion', 'bonsai', 'sabino'], + 'bonus': ['bonus', 'bosun'], + 'bony': ['bony', 'byon'], + 'bonze': ['benzo', 'bonze'], + 'bonzer': ['bonzer', 'bronze'], + 'boob': ['bobo', 'boob'], + 'bood': ['bodo', 'bood', 'doob'], + 'booger': ['booger', 'goober'], + 'bookcase': ['bookcase', 'casebook'], + 'booker': ['booker', 'brooke', 'rebook'], + 'bookland': ['bookland', 'landbook'], + 'bookshop': ['bookshop', 'shopbook'], + 'bookward': ['bookward', 'woodbark'], + 'bookwork': ['bookwork', 'workbook'], + 'bool': ['bolo', 'bool', 'lobo', 'obol'], + 'booly': ['booly', 'looby'], + 'boomingly': ['boomingly', 'myoglobin'], + 'boopis': ['boopis', 'obispo'], + 'boor': ['boor', 'boro', 'broo'], + 'boort': ['boort', 'robot'], + 'boost': ['boost', 'boots'], + 'bootes': ['besoot', 'bootes'], + 'boother': ['boother', 'theorbo'], + 'boots': ['boost', 'boots'], + 'bop': ['bop', 'pob'], + 'bor': ['bor', 'orb', 'rob'], + 'bora': ['boar', 'bora'], + 'borable': ['belabor', 'borable'], + 'boracic': ['boracic', 'braccio'], + 'boral': ['balor', 'bolar', 'boral', 'labor', 'lobar'], + 'boran': ['baron', 'boran'], + 'borani': ['bonair', 'borani'], + 'borate': ['boater', 'borate', 'rebato'], + 'bord': ['bord', 'brod'], + 'bordel': ['belord', 'bordel', 'rebold'], + 'bordello': ['bordello', 'doorbell'], + 'border': ['border', 'roberd'], + 'borderer': ['borderer', 'broderer'], + 'bordure': ['bordure', 'bourder'], + 'bore': ['boer', 'bore', 'robe'], + 'boredom': ['bedroom', 'boerdom', 'boredom'], + 'boree': ['beroe', 'boree'], + 'boreen': ['boreen', 'enrobe', 'neebor', 'rebone'], + 'boreiad': ['beroida', 'boreiad'], + 'boreism': ['boreism', 'semiorb'], + 'borer': ['borer', 'rerob', 'rober'], + 'boreus': ['besour', 'boreus', 'bourse', 'bouser'], + 'borg': ['borg', 'brog', 'gorb'], + 'boric': ['boric', 'cribo', 'orbic'], + 'boride': ['bodier', 'boride', 'brodie'], + 'boring': ['boring', 'robing'], + 'boringly': ['boringly', 'goblinry'], + 'borlase': ['borlase', 'labrose', 'rosabel'], + 'borne': ['boner', 'borne'], + 'borneo': ['borneo', 'oberon'], + 'bornite': ['bornite', 'robinet'], + 'boro': ['boor', 'boro', 'broo'], + 'borocaine': ['borocaine', 'coenobiar'], + 'borofluohydric': ['borofluohydric', 'hydrofluoboric'], + 'borolanite': ['bertolonia', 'borolanite'], + 'boron': ['boron', 'broon'], + 'boronic': ['boronic', 'cobiron'], + 'borrower': ['borrower', 'reborrow'], + 'borscht': ['borscht', 'bortsch'], + 'bort': ['bort', 'brot'], + 'bortsch': ['borscht', 'bortsch'], + 'bos': ['bos', 'sob'], + 'bosc': ['bosc', 'scob'], + 'boser': ['boser', 'brose', 'sober'], + 'bosn': ['bosn', 'nobs', 'snob'], + 'bosselation': ['bosselation', 'eosinoblast'], + 'bosset': ['betoss', 'bosset'], + 'bostangi': ['boasting', 'bostangi'], + 'bostanji': ['banjoist', 'bostanji'], + 'bosun': ['bonus', 'bosun'], + 'bota': ['boat', 'bota', 'toba'], + 'botanical': ['botanical', 'catabolin'], + 'botanophilist': ['botanophilist', 'philobotanist'], + 'bote': ['bote', 'tobe'], + 'botein': ['botein', 'tobine'], + 'both': ['both', 'thob'], + 'bottler': ['blotter', 'bottler'], + 'bottling': ['blotting', 'bottling'], + 'bouge': ['bogue', 'bouge'], + 'bouget': ['bouget', 'outbeg'], + 'bouk': ['bouk', 'kobu'], + 'boulder': ['boulder', 'doubler'], + 'bouldering': ['bouldering', 'redoubling'], + 'boulter': ['boulter', 'trouble'], + 'bounden': ['bounden', 'unboned'], + 'bounder': ['beround', 'bounder', 'rebound', 'unbored', 'unorbed', 'unrobed'], + 'bounding': ['bounding', 'unboding'], + 'bourder': ['bordure', 'bourder'], + 'bourn': ['bourn', 'bruno'], + 'bourse': ['besour', 'boreus', 'bourse', 'bouser'], + 'bouser': ['besour', 'boreus', 'bourse', 'bouser'], + 'bousy': ['bousy', 'byous'], + 'bow': ['bow', 'wob'], + 'bowel': ['below', 'bowel', 'elbow'], + 'boweled': ['boweled', 'elbowed'], + 'bowels': ['beslow', 'bowels'], + 'bowery': ['bowery', 'bowyer', 'owerby'], + 'bowie': ['bowie', 'woibe'], + 'bowker': ['bework', 'bowker'], + 'bowl': ['blow', 'bowl'], + 'bowla': ['ablow', 'balow', 'bowla'], + 'bowler': ['blower', 'bowler', 'reblow', 'worble'], + 'bowling': ['blowing', 'bowling'], + 'bowly': ['blowy', 'bowly'], + 'bowmaker': ['beamwork', 'bowmaker'], + 'bowyer': ['bowery', 'bowyer', 'owerby'], + 'boxer': ['boxer', 'rebox'], + 'boxwork': ['boxwork', 'workbox'], + 'boyang': ['boyang', 'yagnob'], + 'boyard': ['boardy', 'boyard', 'byroad'], + 'boyd': ['body', 'boyd', 'doby'], + 'boyship': ['boyship', 'shipboy'], + 'bozo': ['bozo', 'zobo'], + 'bozze': ['bezzo', 'bozze'], + 'bra': ['bar', 'bra', 'rab'], + 'brab': ['barb', 'brab'], + 'brabble': ['babbler', 'blabber', 'brabble'], + 'braca': ['acrab', 'braca'], + 'braccio': ['boracic', 'braccio'], + 'brace': ['acerb', 'brace', 'caber'], + 'braced': ['becard', 'braced'], + 'braceleted': ['braceleted', 'celebrated'], + 'bracer': ['bracer', 'craber'], + 'braces': ['braces', 'scrabe'], + 'brachet': ['batcher', 'berchta', 'brachet'], + 'brachiata': ['batrachia', 'brachiata'], + 'brachiofacial': ['brachiofacial', 'faciobrachial'], + 'brachiopode': ['brachiopode', 'cardiophobe'], + 'bracon': ['bracon', 'carbon', 'corban'], + 'bractea': ['abreact', 'bractea', 'cabaret'], + 'bracteal': ['bracteal', 'cartable'], + 'bracteiform': ['bacteriform', 'bracteiform'], + 'bracteose': ['bracteose', 'obsecrate'], + 'brad': ['bard', 'brad', 'drab'], + 'bradenhead': ['barehanded', 'bradenhead', 'headbander'], + 'brae': ['bare', 'bear', 'brae'], + 'braehead': ['barehead', 'braehead'], + 'brag': ['brag', 'garb', 'grab'], + 'bragi': ['bragi', 'girba'], + 'bragless': ['bragless', 'garbless'], + 'brahmi': ['brahmi', 'mihrab'], + 'brahui': ['brahui', 'habiru'], + 'braid': ['barid', 'bidar', 'braid', 'rabid'], + 'braider': ['braider', 'rebraid'], + 'brail': ['blair', 'brail', 'libra'], + 'braille': ['braille', 'liberal'], + 'brain': ['abrin', 'bairn', 'brain', 'brian', 'rabin'], + 'brainache': ['brainache', 'branchiae'], + 'brainge': ['bearing', 'begrain', 'brainge', 'rigbane'], + 'brainwater': ['brainwater', 'waterbrain'], + 'brainy': ['binary', 'brainy'], + 'braird': ['braird', 'briard'], + 'brairo': ['barrio', 'brairo'], + 'braise': ['braise', 'rabies', 'rebias'], + 'brake': ['baker', 'brake', 'break'], + 'brakeage': ['brakeage', 'breakage'], + 'brakeless': ['bakerless', 'brakeless', 'breakless'], + 'braker': ['barker', 'braker'], + 'braky': ['barky', 'braky'], + 'bram': ['barm', 'bram'], + 'brambrack': ['barmbrack', 'brambrack'], + 'bramia': ['bairam', 'bramia'], + 'bran': ['barn', 'bran'], + 'brancher': ['brancher', 'rebranch'], + 'branchiae': ['brainache', 'branchiae'], + 'branchiata': ['batrachian', 'branchiata'], + 'branchiopoda': ['branchiopoda', 'podobranchia'], + 'brander': ['bernard', 'brander', 'rebrand'], + 'brandi': ['brandi', 'riband'], + 'brandisher': ['brandisher', 'rebrandish'], + 'branial': ['blarina', 'branial'], + 'brankie': ['brankie', 'inbreak'], + 'brash': ['brash', 'shrab'], + 'brasiletto': ['brasiletto', 'strobilate'], + 'brassie': ['brassie', 'rebasis'], + 'brat': ['bart', 'brat'], + 'brattie': ['biretta', 'brattie', 'ratbite'], + 'brattle': ['battler', 'blatter', 'brattle'], + 'braunite': ['braunite', 'urbanite', 'urbinate'], + 'brave': ['brave', 'breva'], + 'bravoite': ['abortive', 'bravoite'], + 'brawler': ['brawler', 'warbler'], + 'brawling': ['brawling', 'warbling'], + 'brawlingly': ['brawlingly', 'warblingly'], + 'brawly': ['brawly', 'byrlaw', 'warbly'], + 'brawned': ['benward', 'brawned'], + 'bray': ['bray', 'yarb'], + 'braza': ['braza', 'zabra'], + 'braze': ['braze', 'zebra'], + 'brazier': ['bizarre', 'brazier'], + 'bread': ['ardeb', 'beard', 'bread', 'debar'], + 'breadless': ['beardless', 'breadless'], + 'breadlessness': ['beardlessness', 'breadlessness'], + 'breadman': ['banderma', 'breadman'], + 'breadnut': ['breadnut', 'turbaned'], + 'breaghe': ['breaghe', 'herbage'], + 'break': ['baker', 'brake', 'break'], + 'breakage': ['brakeage', 'breakage'], + 'breakless': ['bakerless', 'brakeless', 'breakless'], + 'breakout': ['breakout', 'outbreak'], + 'breakover': ['breakover', 'overbreak'], + 'breakstone': ['breakstone', 'stonebreak'], + 'breakup': ['breakup', 'upbreak'], + 'breakwind': ['breakwind', 'windbreak'], + 'bream': ['amber', 'bearm', 'bemar', 'bream', 'embar'], + 'breast': ['baster', 'bestar', 'breast'], + 'breasting': ['breasting', 'brigantes'], + 'breastpin': ['breastpin', 'stepbairn'], + 'breastrail': ['arbalister', 'breastrail'], + 'breastweed': ['breastweed', 'sweetbread'], + 'breath': ['bather', 'bertha', 'breath'], + 'breathe': ['breathe', 'rebathe'], + 'breba': ['barbe', 'bebar', 'breba', 'rebab'], + 'breccia': ['acerbic', 'breccia'], + 'brecham': ['becharm', 'brecham', 'chamber'], + 'brede': ['brede', 'breed', 'rebed'], + 'bredi': ['bider', 'bredi', 'bride', 'rebid'], + 'bree': ['beer', 'bere', 'bree'], + 'breech': ['becher', 'breech'], + 'breed': ['brede', 'breed', 'rebed'], + 'breeder': ['breeder', 'rebreed'], + 'breeding': ['beringed', 'breeding'], + 'brehon': ['behorn', 'brehon'], + 'brei': ['beri', 'bier', 'brei', 'ribe'], + 'brelaw': ['bawler', 'brelaw', 'rebawl', 'warble'], + 'breme': ['breme', 'ember'], + 'bremia': ['ambier', 'bremia', 'embira'], + 'brenda': ['bander', 'brenda'], + 'brephic': ['bechirp', 'brephic'], + 'bret': ['bert', 'bret'], + 'breth': ['berth', 'breth'], + 'breva': ['brave', 'breva'], + 'breve': ['bever', 'breve'], + 'brewer': ['brewer', 'rebrew'], + 'brey': ['brey', 'byre', 'yerb'], + 'brian': ['abrin', 'bairn', 'brain', 'brian', 'rabin'], + 'briard': ['braird', 'briard'], + 'briber': ['briber', 'ribber'], + 'brichen': ['birchen', 'brichen'], + 'brickel': ['brickel', 'brickle'], + 'bricken': ['bickern', 'bricken'], + 'brickle': ['brickel', 'brickle'], + 'bricole': ['bricole', 'corbeil', 'orbicle'], + 'bridal': ['bildar', 'bridal', 'ribald'], + 'bridale': ['bedrail', 'bridale', 'ridable'], + 'bridally': ['bridally', 'ribaldly'], + 'bride': ['bider', 'bredi', 'bride', 'rebid'], + 'bridelace': ['bridelace', 'calibered'], + 'bridge': ['begird', 'bridge'], + 'bridgeward': ['bridgeward', 'drawbridge'], + 'bridling': ['birdling', 'bridling', 'lingbird'], + 'brief': ['bifer', 'brief', 'fiber'], + 'briefless': ['briefless', 'fiberless', 'fibreless'], + 'brier': ['berri', 'brier'], + 'briered': ['berried', 'briered'], + 'brieve': ['bervie', 'brieve'], + 'brigade': ['abridge', 'brigade'], + 'brigand': ['barding', 'brigand'], + 'brigantes': ['breasting', 'brigantes'], + 'brighten': ['berthing', 'brighten'], + 'brin': ['birn', 'brin'], + 'brine': ['brine', 'enrib'], + 'bringal': ['barling', 'bringal'], + 'bringer': ['bringer', 'rebring'], + 'briny': ['birny', 'briny'], + 'bristle': ['blister', 'bristle'], + 'bristlewort': ['blisterwort', 'bristlewort'], + 'brisure': ['brisure', 'bruiser'], + 'britannia': ['antiabrin', 'britannia'], + 'brith': ['birth', 'brith'], + 'brither': ['brither', 'rebirth'], + 'britten': ['bittern', 'britten'], + 'brittle': ['blitter', 'brittle', 'triblet'], + 'broacher': ['broacher', 'rebroach'], + 'broad': ['abord', 'bardo', 'board', 'broad', 'dobra', 'dorab'], + 'broadbill': ['billboard', 'broadbill'], + 'broadcaster': ['broadcaster', 'rebroadcast'], + 'broaden': ['bandore', 'broaden'], + 'broadhead': ['broadhead', 'headboard'], + 'broadly': ['boardly', 'broadly'], + 'broadside': ['broadside', 'sideboard'], + 'broadspread': ['broadspread', 'spreadboard'], + 'broadtail': ['broadtail', 'tailboard'], + 'brochan': ['brochan', 'charbon'], + 'broche': ['bocher', 'broche'], + 'brocho': ['brocho', 'brooch'], + 'brocked': ['bedrock', 'brocked'], + 'brockle': ['blocker', 'brockle', 'reblock'], + 'brod': ['bord', 'brod'], + 'broderer': ['borderer', 'broderer'], + 'brodie': ['bodier', 'boride', 'brodie'], + 'brog': ['borg', 'brog', 'gorb'], + 'brogan': ['barong', 'brogan'], + 'broggle': ['boggler', 'broggle'], + 'brolga': ['brolga', 'gorbal'], + 'broma': ['broma', 'rambo'], + 'bromate': ['barmote', 'bromate'], + 'brome': ['brome', 'omber'], + 'brominate': ['brominate', 'tribonema'], + 'bromohydrate': ['bromohydrate', 'hydrobromate'], + 'bronze': ['bonzer', 'bronze'], + 'broo': ['boor', 'boro', 'broo'], + 'brooch': ['brocho', 'brooch'], + 'brooke': ['booker', 'brooke', 'rebook'], + 'broon': ['boron', 'broon'], + 'brose': ['boser', 'brose', 'sober'], + 'brot': ['bort', 'brot'], + 'brotan': ['barton', 'brotan'], + 'brotany': ['baryton', 'brotany'], + 'broth': ['broth', 'throb'], + 'brothelry': ['brothelry', 'brotherly'], + 'brotherly': ['brothelry', 'brotherly'], + 'browden': ['bedrown', 'browden'], + 'browner': ['browner', 'rebrown'], + 'browntail': ['browntail', 'wrainbolt'], + 'bruce': ['bruce', 'cebur', 'cuber'], + 'brucina': ['brucina', 'rubican'], + 'bruckle': ['bruckle', 'buckler'], + 'brugh': ['brugh', 'burgh'], + 'bruin': ['bruin', 'burin', 'inrub'], + 'bruiser': ['brisure', 'bruiser'], + 'bruke': ['bruke', 'burke'], + 'brule': ['bluer', 'brule', 'burel', 'ruble'], + 'brulee': ['brulee', 'burele', 'reblue'], + 'brumal': ['brumal', 'labrum', 'lumbar', 'umbral'], + 'brumalia': ['albarium', 'brumalia'], + 'brume': ['brume', 'umber'], + 'brumous': ['brumous', 'umbrous'], + 'brunellia': ['brunellia', 'unliberal'], + 'brunet': ['brunet', 'bunter', 'burnet'], + 'bruno': ['bourn', 'bruno'], + 'brunt': ['brunt', 'burnt'], + 'brush': ['brush', 'shrub'], + 'brushed': ['brushed', 'subherd'], + 'brusher': ['brusher', 'rebrush'], + 'brushland': ['brushland', 'shrubland'], + 'brushless': ['brushless', 'shrubless'], + 'brushlet': ['brushlet', 'shrublet'], + 'brushlike': ['brushlike', 'shrublike'], + 'brushwood': ['brushwood', 'shrubwood'], + 'brustle': ['bluster', 'brustle', 'bustler'], + 'brut': ['brut', 'burt', 'trub', 'turb'], + 'bruta': ['bruta', 'tubar'], + 'brute': ['brute', 'buret', 'rebut', 'tuber'], + 'brutely': ['brutely', 'butlery'], + 'bryan': ['barny', 'bryan'], + 'bryanite': ['barytine', 'bryanite'], + 'bryce': ['becry', 'bryce'], + 'bual': ['balu', 'baul', 'bual', 'luba'], + 'buba': ['babu', 'buba'], + 'bubal': ['babul', 'bubal'], + 'bubbler': ['blubber', 'bubbler'], + 'buccocervical': ['buccocervical', 'cervicobuccal'], + 'bucconasal': ['bucconasal', 'nasobuccal'], + 'buceros': ['bescour', 'buceros', 'obscure'], + 'buckbush': ['buckbush', 'bushbuck'], + 'bucked': ['beduck', 'bucked'], + 'buckler': ['bruckle', 'buckler'], + 'bucksaw': ['bucksaw', 'sawbuck'], + 'bucrane': ['bucrane', 'unbrace'], + 'bud': ['bud', 'dub'], + 'buda': ['baud', 'buda', 'daub'], + 'budder': ['budder', 'redbud'], + 'budger': ['bedrug', 'budger'], + 'budgeter': ['budgeter', 'rebudget'], + 'budtime': ['bitumed', 'budtime'], + 'buffer': ['buffer', 'rebuff'], + 'buffeter': ['buffeter', 'rebuffet'], + 'bugan': ['bugan', 'bunga', 'unbag'], + 'bughouse': ['bughouse', 'housebug'], + 'bugi': ['bugi', 'guib'], + 'bugle': ['bugle', 'bulge'], + 'bugler': ['bugler', 'bulger', 'burgle'], + 'bugre': ['bugre', 'gebur'], + 'builder': ['builder', 'rebuild'], + 'buildup': ['buildup', 'upbuild'], + 'buirdly': ['buirdly', 'ludibry'], + 'bulanda': ['balunda', 'bulanda'], + 'bulb': ['blub', 'bulb'], + 'bulgarian': ['biangular', 'bulgarian'], + 'bulge': ['bugle', 'bulge'], + 'bulger': ['bugler', 'bulger', 'burgle'], + 'bulimic': ['bulimic', 'umbilic'], + 'bulimiform': ['bulimiform', 'umbiliform'], + 'bulker': ['bulker', 'rebulk'], + 'bulla': ['bulla', 'lulab'], + 'bullace': ['bullace', 'cueball'], + 'bulletin': ['bulletin', 'unbillet'], + 'bullfeast': ['bullfeast', 'stableful'], + 'bulse': ['blues', 'bulse'], + 'bulter': ['bulter', 'burlet', 'butler'], + 'bummler': ['bummler', 'mumbler'], + 'bun': ['bun', 'nub'], + 'buna': ['baun', 'buna', 'nabu', 'nuba'], + 'buncal': ['buncal', 'lucban'], + 'buncher': ['buncher', 'rebunch'], + 'bunder': ['bunder', 'burden', 'burned', 'unbred'], + 'bundle': ['bundle', 'unbled'], + 'bundler': ['blunder', 'bundler'], + 'bundu': ['bundu', 'unbud', 'undub'], + 'bunga': ['bugan', 'bunga', 'unbag'], + 'bungle': ['blunge', 'bungle'], + 'bungler': ['blunger', 'bungler'], + 'bungo': ['bungo', 'unbog'], + 'bunk': ['bunk', 'knub'], + 'bunter': ['brunet', 'bunter', 'burnet'], + 'bunty': ['bunty', 'butyn'], + 'bunya': ['bunya', 'unbay'], + 'bur': ['bur', 'rub'], + 'buran': ['buran', 'unbar', 'urban'], + 'burble': ['burble', 'lubber', 'rubble'], + 'burbler': ['burbler', 'rubbler'], + 'burbly': ['burbly', 'rubbly'], + 'burd': ['burd', 'drub'], + 'burdalone': ['burdalone', 'unlabored'], + 'burden': ['bunder', 'burden', 'burned', 'unbred'], + 'burdener': ['burdener', 'reburden'], + 'burdie': ['burdie', 'buried', 'rubied'], + 'bure': ['bure', 'reub', 'rube'], + 'burel': ['bluer', 'brule', 'burel', 'ruble'], + 'burele': ['brulee', 'burele', 'reblue'], + 'buret': ['brute', 'buret', 'rebut', 'tuber'], + 'burfish': ['burfish', 'furbish'], + 'burg': ['burg', 'grub'], + 'burgh': ['brugh', 'burgh'], + 'burgle': ['bugler', 'bulger', 'burgle'], + 'burian': ['burian', 'urbian'], + 'buried': ['burdie', 'buried', 'rubied'], + 'burin': ['bruin', 'burin', 'inrub'], + 'burke': ['bruke', 'burke'], + 'burl': ['blur', 'burl'], + 'burler': ['burler', 'burrel'], + 'burlet': ['bulter', 'burlet', 'butler'], + 'burletta': ['burletta', 'rebuttal'], + 'burmite': ['burmite', 'imbrute', 'terbium'], + 'burned': ['bunder', 'burden', 'burned', 'unbred'], + 'burner': ['burner', 'reburn'], + 'burnet': ['brunet', 'bunter', 'burnet'], + 'burnfire': ['burnfire', 'fireburn'], + 'burnie': ['burnie', 'rubine'], + 'burnisher': ['burnisher', 'reburnish'], + 'burnout': ['burnout', 'outburn'], + 'burnover': ['burnover', 'overburn'], + 'burnsides': ['burnsides', 'sideburns'], + 'burnt': ['brunt', 'burnt'], + 'burny': ['burny', 'runby'], + 'buro': ['buro', 'roub'], + 'burrel': ['burler', 'burrel'], + 'burro': ['burro', 'robur', 'rubor'], + 'bursa': ['abrus', 'bursa', 'subra'], + 'bursal': ['bursal', 'labrus'], + 'bursate': ['bursate', 'surbate'], + 'burse': ['burse', 'rebus', 'suber'], + 'burst': ['burst', 'strub'], + 'burster': ['burster', 'reburst'], + 'burt': ['brut', 'burt', 'trub', 'turb'], + 'burut': ['burut', 'trubu'], + 'bury': ['bury', 'ruby'], + 'bus': ['bus', 'sub'], + 'buscarle': ['arbuscle', 'buscarle'], + 'bushbuck': ['buckbush', 'bushbuck'], + 'busher': ['busher', 'rebush'], + 'bushwood': ['bushwood', 'woodbush'], + 'busied': ['busied', 'subdie'], + 'busked': ['bedusk', 'busked'], + 'busman': ['busman', 'subman'], + 'bust': ['bust', 'stub'], + 'busted': ['bedust', 'bestud', 'busted'], + 'buster': ['berust', 'buster', 'stuber'], + 'bustic': ['bustic', 'cubist'], + 'bustle': ['bustle', 'sublet', 'subtle'], + 'bustler': ['bluster', 'brustle', 'bustler'], + 'but': ['but', 'tub'], + 'bute': ['bute', 'tebu', 'tube'], + 'butea': ['butea', 'taube', 'tubae'], + 'butein': ['butein', 'butine', 'intube'], + 'butic': ['butic', 'cubit'], + 'butine': ['butein', 'butine', 'intube'], + 'butler': ['bulter', 'burlet', 'butler'], + 'butleress': ['butleress', 'tuberless'], + 'butlery': ['brutely', 'butlery'], + 'buttle': ['buttle', 'tublet'], + 'buttoner': ['buttoner', 'rebutton'], + 'butyn': ['bunty', 'butyn'], + 'buyer': ['buyer', 'rebuy'], + 'bye': ['bey', 'bye'], + 'byeman': ['byeman', 'byname'], + 'byerite': ['byerite', 'ebriety'], + 'bygo': ['bogy', 'bygo', 'goby'], + 'byname': ['byeman', 'byname'], + 'byon': ['bony', 'byon'], + 'byous': ['bousy', 'byous'], + 'byre': ['brey', 'byre', 'yerb'], + 'byrlaw': ['brawly', 'byrlaw', 'warbly'], + 'byroad': ['boardy', 'boyard', 'byroad'], + 'cab': ['bac', 'cab'], + 'caba': ['abac', 'caba'], + 'cabaan': ['cabaan', 'cabana', 'canaba'], + 'cabala': ['cabala', 'calaba'], + 'cabaletta': ['ablactate', 'cabaletta'], + 'cabalism': ['balsamic', 'cabalism'], + 'cabalist': ['basaltic', 'cabalist'], + 'caballer': ['barcella', 'caballer'], + 'caban': ['banca', 'caban'], + 'cabana': ['cabaan', 'cabana', 'canaba'], + 'cabaret': ['abreact', 'bractea', 'cabaret'], + 'cabbler': ['cabbler', 'clabber'], + 'caber': ['acerb', 'brace', 'caber'], + 'cabio': ['baioc', 'cabio', 'cobia'], + 'cabiri': ['cabiri', 'caribi'], + 'cabirian': ['arabinic', 'cabirian', 'carabini', 'cibarian'], + 'cable': ['cable', 'caleb'], + 'cabled': ['beclad', 'cabled'], + 'cabob': ['bobac', 'cabob'], + 'cabocle': ['boccale', 'cabocle'], + 'cabombaceae': ['bombacaceae', 'cabombaceae'], + 'cabrilla': ['bacillar', 'cabrilla'], + 'caca': ['acca', 'caca'], + 'cachet': ['cachet', 'chacte'], + 'cachou': ['cachou', 'caucho'], + 'cackler': ['cackler', 'clacker', 'crackle'], + 'cacodaemonic': ['cacodaemonic', 'cacodemoniac'], + 'cacodemoniac': ['cacodaemonic', 'cacodemoniac'], + 'cacomistle': ['cacomistle', 'cosmetical'], + 'cacoxenite': ['cacoxenite', 'excecation'], + 'cactaceae': ['cactaceae', 'taccaceae'], + 'cactaceous': ['cactaceous', 'taccaceous'], + 'cacti': ['cacti', 'ticca'], + 'cactoid': ['cactoid', 'octadic'], + 'caddice': ['caddice', 'decadic'], + 'caddie': ['caddie', 'eddaic'], + 'cade': ['cade', 'dace', 'ecad'], + 'cadent': ['cadent', 'canted', 'decant'], + 'cadential': ['cadential', 'dancalite'], + 'cader': ['acred', 'cader', 'cadre', 'cedar'], + 'cadet': ['cadet', 'ectad'], + 'cadge': ['cadge', 'caged'], + 'cadger': ['cadger', 'cradge'], + 'cadi': ['acid', 'cadi', 'caid'], + 'cadinene': ['cadinene', 'decennia', 'enneadic'], + 'cadmia': ['adamic', 'cadmia'], + 'cados': ['cados', 'scoad'], + 'cadre': ['acred', 'cader', 'cadre', 'cedar'], + 'cadua': ['cadua', 'cauda'], + 'caduac': ['caduac', 'caduca'], + 'caduca': ['caduac', 'caduca'], + 'cadus': ['cadus', 'dacus'], + 'caeciliae': ['caeciliae', 'ilicaceae'], + 'caedmonian': ['caedmonian', 'macedonian'], + 'caedmonic': ['caedmonic', 'macedonic'], + 'caelum': ['almuce', 'caelum', 'macule'], + 'caelus': ['caelus', 'caules', 'clause'], + 'caesar': ['ascare', 'caesar', 'resaca'], + 'caesarist': ['caesarist', 'staircase'], + 'caesura': ['auresca', 'caesura'], + 'caffeina': ['affiance', 'caffeina'], + 'caged': ['cadge', 'caged'], + 'cageling': ['cageling', 'glaceing'], + 'cager': ['cager', 'garce', 'grace'], + 'cahill': ['achill', 'cahill', 'chilla'], + 'cahita': ['cahita', 'ithaca'], + 'cahnite': ['cahnite', 'cathine'], + 'caid': ['acid', 'cadi', 'caid'], + 'caiman': ['amniac', 'caiman', 'maniac'], + 'caimito': ['caimito', 'comitia'], + 'cain': ['cain', 'inca'], + 'cainism': ['cainism', 'misniac'], + 'cairba': ['arabic', 'cairba'], + 'caird': ['acrid', 'caird', 'carid', 'darci', 'daric', 'dirca'], + 'cairene': ['cairene', 'cinerea'], + 'cairn': ['cairn', 'crain', 'naric'], + 'cairned': ['cairned', 'candier'], + 'cairny': ['cairny', 'riancy'], + 'cairo': ['cairo', 'oaric'], + 'caisson': ['caisson', 'cassino'], + 'cajoler': ['cajoler', 'jecoral'], + 'caker': ['acker', 'caker', 'crake', 'creak'], + 'cakey': ['ackey', 'cakey'], + 'cal': ['cal', 'lac'], + 'calaba': ['cabala', 'calaba'], + 'calamine': ['alcamine', 'analcime', 'calamine', 'camelina'], + 'calamint': ['calamint', 'claimant'], + 'calamitean': ['calamitean', 'catamenial'], + 'calander': ['calander', 'calendar'], + 'calandrinae': ['calandrinae', 'calendarian'], + 'calas': ['calas', 'casal', 'scala'], + 'calash': ['calash', 'lachsa'], + 'calathian': ['acanthial', 'calathian'], + 'calaverite': ['calaverite', 'lacerative'], + 'calcareocorneous': ['calcareocorneous', 'corneocalcareous'], + 'calcareosiliceous': ['calcareosiliceous', 'siliceocalcareous'], + 'calciner': ['calciner', 'larcenic'], + 'calculary': ['calculary', 'calycular'], + 'calculative': ['calculative', 'claviculate'], + 'calden': ['calden', 'candle', 'lanced'], + 'calean': ['anlace', 'calean'], + 'caleb': ['cable', 'caleb'], + 'caledonia': ['caledonia', 'laodicean'], + 'caledonite': ['caledonite', 'celadonite'], + 'calendar': ['calander', 'calendar'], + 'calendarial': ['calendarial', 'dalecarlian'], + 'calendarian': ['calandrinae', 'calendarian'], + 'calender': ['calender', 'encradle'], + 'calenture': ['calenture', 'crenulate'], + 'calepin': ['calepin', 'capelin', 'panicle', 'pelican', 'pinacle'], + 'calfkill': ['calfkill', 'killcalf'], + 'caliban': ['balanic', 'caliban'], + 'caliber': ['caliber', 'calibre'], + 'calibered': ['bridelace', 'calibered'], + 'calibrate': ['bacterial', 'calibrate'], + 'calibre': ['caliber', 'calibre'], + 'caliburno': ['binocular', 'caliburno', 'colubrina'], + 'calico': ['accoil', 'calico'], + 'calidity': ['calidity', 'dialytic'], + 'caliga': ['caliga', 'cigala'], + 'calinago': ['analogic', 'calinago'], + 'calinut': ['calinut', 'lunatic'], + 'caliper': ['caliper', 'picarel', 'replica'], + 'calipers': ['calipers', 'spiracle'], + 'caliphate': ['caliphate', 'hepatical'], + 'calite': ['calite', 'laetic', 'tecali'], + 'caliver': ['caliver', 'caviler', 'claiver', 'clavier', 'valeric', 'velaric'], + 'calk': ['calk', 'lack'], + 'calker': ['calker', 'lacker', 'rackle', 'recalk', 'reckla'], + 'callboy': ['callboy', 'collyba'], + 'caller': ['caller', 'cellar', 'recall'], + 'calli': ['calli', 'lilac'], + 'calligraphy': ['calligraphy', 'graphically'], + 'calliopsis': ['calliopsis', 'lipoclasis'], + 'callisection': ['callisection', 'clinoclasite'], + 'callitype': ['callitype', 'plicately'], + 'callo': ['callo', 'colla', 'local'], + 'callosal': ['callosal', 'scallola'], + 'callose': ['callose', 'oscella'], + 'callosity': ['callosity', 'stoically'], + 'callosum': ['callosum', 'mollusca'], + 'calluna': ['calluna', 'lacunal'], + 'callus': ['callus', 'sulcal'], + 'calm': ['calm', 'clam'], + 'calmant': ['calmant', 'clamant'], + 'calmative': ['calmative', 'clamative'], + 'calmer': ['calmer', 'carmel', 'clamer', 'marcel', 'mercal'], + 'calmierer': ['calmierer', 'reclaimer'], + 'calomba': ['calomba', 'cambalo'], + 'calonectria': ['calonectria', 'ectocranial'], + 'calor': ['alcor', 'calor', 'carlo', 'carol', 'claro', 'coral'], + 'calorie': ['calorie', 'cariole'], + 'calorist': ['calorist', 'coralist'], + 'calorite': ['calorite', 'erotical', 'loricate'], + 'calorize': ['calorize', 'coalizer'], + 'calotermes': ['calotermes', 'mesorectal', 'metacresol'], + 'calotermitid': ['calotermitid', 'dilatometric'], + 'calp': ['calp', 'clap'], + 'caltha': ['caltha', 'chalta'], + 'caltrop': ['caltrop', 'proctal'], + 'calusa': ['ascula', 'calusa', 'casual', 'casula', 'causal'], + 'calvaria': ['calvaria', 'clavaria'], + 'calvary': ['calvary', 'cavalry'], + 'calve': ['calve', 'cavel', 'clave'], + 'calver': ['calver', 'carvel', 'claver'], + 'calves': ['calves', 'scavel'], + 'calycular': ['calculary', 'calycular'], + 'calyptratae': ['acalyptrate', 'calyptratae'], + 'cam': ['cam', 'mac'], + 'camaca': ['camaca', 'macaca'], + 'camail': ['amical', 'camail', 'lamaic'], + 'caman': ['caman', 'macan'], + 'camara': ['acamar', 'camara', 'maraca'], + 'cambalo': ['calomba', 'cambalo'], + 'camber': ['becram', 'camber', 'crambe'], + 'cambrel': ['cambrel', 'clamber', 'cramble'], + 'came': ['acme', 'came', 'mace'], + 'cameist': ['cameist', 'etacism', 'sematic'], + 'camel': ['camel', 'clame', 'cleam', 'macle'], + 'camelid': ['camelid', 'decimal', 'declaim', 'medical'], + 'camelina': ['alcamine', 'analcime', 'calamine', 'camelina'], + 'camelish': ['camelish', 'schalmei'], + 'camellus': ['camellus', 'sacellum'], + 'cameloid': ['cameloid', 'comedial', 'melodica'], + 'cameograph': ['cameograph', 'macrophage'], + 'camera': ['acream', 'camera', 'mareca'], + 'cameral': ['cameral', 'caramel', 'carmela', 'ceramal', 'reclama'], + 'camerate': ['camerate', 'macerate', 'racemate'], + 'camerated': ['camerated', 'demarcate'], + 'cameration': ['aeromantic', 'cameration', 'maceration', 'racemation'], + 'camerina': ['amacrine', 'american', 'camerina', 'cinerama'], + 'camerist': ['camerist', 'ceramist', 'matrices'], + 'camion': ['camion', 'conima', 'manioc', 'monica'], + 'camisado': ['camisado', 'caodaism'], + 'camise': ['camise', 'macies'], + 'campaign': ['campaign', 'pangamic'], + 'campaigner': ['campaigner', 'recampaign'], + 'camphire': ['camphire', 'hemicarp'], + 'campine': ['campine', 'pemican'], + 'campoo': ['campoo', 'capomo'], + 'camptonite': ['camptonite', 'pentatomic'], + 'camus': ['camus', 'musca', 'scaum', 'sumac'], + 'camused': ['camused', 'muscade'], + 'canaba': ['cabaan', 'cabana', 'canaba'], + 'canadol': ['acnodal', 'canadol', 'locanda'], + 'canaille': ['alliance', 'canaille'], + 'canape': ['canape', 'panace'], + 'canari': ['acinar', + 'arnica', + 'canari', + 'carian', + 'carina', + 'crania', + 'narica'], + 'canarin': ['canarin', 'cranian'], + 'canariote': ['canariote', 'ceratonia'], + 'canary': ['canary', 'cynara'], + 'canaut': ['canaut', 'tucana'], + 'canceler': ['canceler', 'clarence', 'recancel'], + 'cancer': ['cancer', 'crance'], + 'cancerate': ['cancerate', 'reactance'], + 'canceration': ['anacreontic', 'canceration'], + 'cancri': ['cancri', 'carnic', 'cranic'], + 'cancroid': ['cancroid', 'draconic'], + 'candela': ['candela', 'decanal'], + 'candier': ['cairned', 'candier'], + 'candiru': ['candiru', 'iracund'], + 'candle': ['calden', 'candle', 'lanced'], + 'candor': ['candor', 'cardon', 'conrad'], + 'candroy': ['candroy', 'dacryon'], + 'cane': ['acne', 'cane', 'nace'], + 'canel': ['canel', 'clean', 'lance', 'lenca'], + 'canelo': ['canelo', 'colane'], + 'canephor': ['canephor', 'chaperno', 'chaperon'], + 'canephore': ['canephore', 'chaperone'], + 'canephroi': ['canephroi', 'parochine'], + 'caner': ['caner', 'crane', 'crena', 'nacre', 'rance'], + 'canful': ['canful', 'flucan'], + 'cangle': ['cangle', 'glance'], + 'cangler': ['cangler', 'glancer', 'reclang'], + 'cangue': ['cangue', 'uncage'], + 'canicola': ['canicola', 'laconica'], + 'canid': ['canid', 'cnida', 'danic'], + 'canidae': ['aidance', 'canidae'], + 'canine': ['canine', 'encina', 'neanic'], + 'canis': ['canis', 'scian'], + 'canister': ['canister', 'cestrian', 'cisterna', 'irascent'], + 'canker': ['canker', 'neckar'], + 'cankerworm': ['cankerworm', 'crownmaker'], + 'cannel': ['cannel', 'lencan'], + 'cannot': ['cannot', 'canton', 'conant', 'nonact'], + 'cannulate': ['antelucan', 'cannulate'], + 'canny': ['canny', 'nancy'], + 'canoe': ['acone', 'canoe', 'ocean'], + 'canoeing': ['anogenic', 'canoeing'], + 'canoeist': ['canoeist', 'cotesian'], + 'canon': ['ancon', 'canon'], + 'canonist': ['canonist', 'sanction', 'sonantic'], + 'canoodler': ['canoodler', 'coronaled'], + 'canroy': ['canroy', 'crayon', 'cyrano', 'nyroca'], + 'canso': ['ascon', 'canso', 'oscan'], + 'cantabri': ['bactrian', 'cantabri'], + 'cantala': ['cantala', 'catalan', 'lantaca'], + 'cantalite': ['cantalite', 'lactinate', 'tetanical'], + 'cantara': ['cantara', 'nacarat'], + 'cantaro': ['cantaro', 'croatan'], + 'cantate': ['anteact', 'cantate'], + 'canted': ['cadent', 'canted', 'decant'], + 'canteen': ['canteen', 'centena'], + 'canter': ['canter', + 'creant', + 'cretan', + 'nectar', + 'recant', + 'tanrec', + 'trance'], + 'canterer': ['canterer', 'recanter', 'recreant', 'terrance'], + 'cantharidae': ['acidanthera', 'cantharidae'], + 'cantharis': ['anarchist', 'archsaint', 'cantharis'], + 'canthus': ['canthus', 'staunch'], + 'cantico': ['cantico', 'catonic', 'taconic'], + 'cantilena': ['cantilena', 'lancinate'], + 'cantilever': ['cantilever', 'trivalence'], + 'cantily': ['anticly', 'cantily'], + 'cantina': ['cantina', 'tannaic'], + 'cantiness': ['anticness', 'cantiness', 'incessant'], + 'cantion': ['actinon', 'cantion', 'contain'], + 'cantle': ['cantle', 'cental', 'lancet', 'tancel'], + 'canto': ['acton', 'canto', 'octan'], + 'canton': ['cannot', 'canton', 'conant', 'nonact'], + 'cantonal': ['cantonal', 'connatal'], + 'cantor': ['cantor', 'carton', 'contra'], + 'cantorian': ['anarcotin', 'cantorian', 'carnation', 'narcotina'], + 'cantoris': ['cantoris', 'castorin', 'corsaint'], + 'cantred': ['cantred', 'centrad', 'tranced'], + 'cantus': ['cantus', 'tuscan', 'uncast'], + 'canun': ['canun', 'cunan'], + 'cany': ['cany', 'cyan'], + 'canyon': ['ancony', 'canyon'], + 'caoba': ['bacao', 'caoba'], + 'caodaism': ['camisado', 'caodaism'], + 'cap': ['cap', 'pac'], + 'capable': ['capable', 'pacable'], + 'caparison': ['caparison', 'paranosic'], + 'cape': ['cape', 'cepa', 'pace'], + 'caped': ['caped', 'decap', 'paced'], + 'capel': ['capel', 'place'], + 'capelin': ['calepin', 'capelin', 'panicle', 'pelican', 'pinacle'], + 'capeline': ['capeline', 'pelecani'], + 'caper': ['caper', 'crape', 'pacer', 'perca', 'recap'], + 'capernaite': ['capernaite', 'paraenetic'], + 'capernoited': ['capernoited', 'deprecation'], + 'capernoity': ['acetopyrin', 'capernoity'], + 'capes': ['capes', 'scape', 'space'], + 'caph': ['caph', 'chap'], + 'caphite': ['aphetic', 'caphite', 'hepatic'], + 'caphtor': ['caphtor', 'toparch'], + 'capias': ['capias', 'pisaca'], + 'capillament': ['capillament', 'implacental'], + 'capillarity': ['capillarity', 'piratically'], + 'capital': ['capital', 'palatic'], + 'capitan': ['capitan', 'captain'], + 'capitate': ['apatetic', 'capitate'], + 'capitellar': ['capitellar', 'prelatical'], + 'capito': ['atopic', 'capito', 'copita'], + 'capitol': ['capitol', 'coalpit', 'optical', 'topical'], + 'capomo': ['campoo', 'capomo'], + 'capon': ['capon', 'ponca'], + 'caponier': ['caponier', 'coprinae', 'procaine'], + 'capot': ['capot', 'coapt'], + 'capote': ['capote', 'toecap'], + 'capreol': ['capreol', 'polacre'], + 'capri': ['capri', 'picra', 'rapic'], + 'caprid': ['caprid', 'carpid', 'picard'], + 'capriote': ['aporetic', 'capriote', 'operatic'], + 'capsian': ['capsian', 'caspian', 'nascapi', 'panisca'], + 'capstone': ['capstone', 'opencast'], + 'capsula': ['capsula', 'pascual', 'scapula'], + 'capsular': ['capsular', 'scapular'], + 'capsulate': ['aspectual', 'capsulate'], + 'capsulated': ['capsulated', 'scapulated'], + 'capsule': ['capsule', 'specula', 'upscale'], + 'capsulectomy': ['capsulectomy', 'scapulectomy'], + 'capsuler': ['capsuler', 'specular'], + 'captain': ['capitan', 'captain'], + 'captation': ['anaptotic', 'captation'], + 'caption': ['caption', 'paction'], + 'captious': ['autopsic', 'captious'], + 'captor': ['captor', 'copart'], + 'capture': ['capture', 'uptrace'], + 'car': ['arc', 'car'], + 'cara': ['arca', 'cara'], + 'carabeen': ['bearance', 'carabeen'], + 'carabin': ['arbacin', 'carabin', 'cariban'], + 'carabini': ['arabinic', 'cabirian', 'carabini', 'cibarian'], + 'caracoli': ['caracoli', 'coracial'], + 'caracore': ['acrocera', 'caracore'], + 'caragana': ['aracanga', 'caragana'], + 'caramel': ['cameral', 'caramel', 'carmela', 'ceramal', 'reclama'], + 'caranda': ['anacard', 'caranda'], + 'carandas': ['carandas', 'sandarac'], + 'carane': ['arcane', 'carane'], + 'carangid': ['carangid', 'cardigan'], + 'carapine': ['carapine', 'carpaine'], + 'caravel': ['caravel', 'lavacre'], + 'carbamide': ['carbamide', 'crambidae'], + 'carbamine': ['carbamine', 'crambinae'], + 'carbamino': ['carbamino', 'macrobian'], + 'carbeen': ['carbeen', 'carbene'], + 'carbene': ['carbeen', 'carbene'], + 'carbo': ['carbo', 'carob', 'coarb', 'cobra'], + 'carbohydride': ['carbohydride', 'hydrocarbide'], + 'carbon': ['bracon', 'carbon', 'corban'], + 'carbonite': ['bicornate', 'carbonite', 'reboantic'], + 'carcel': ['carcel', 'cercal'], + 'carcinoma': ['carcinoma', 'macaronic'], + 'carcinosarcoma': ['carcinosarcoma', 'sarcocarcinoma'], + 'carcoon': ['carcoon', 'raccoon'], + 'cardel': ['cardel', 'cradle'], + 'cardia': ['acarid', 'cardia', 'carida'], + 'cardiac': ['arcadic', 'cardiac'], + 'cardial': ['cardial', 'radical'], + 'cardiant': ['antacrid', 'cardiant', 'radicant', 'tridacna'], + 'cardigan': ['carangid', 'cardigan'], + 'cardiidae': ['acrididae', 'cardiidae', 'cidaridae'], + 'cardin': ['andric', 'cardin', 'rancid'], + 'cardinal': ['cardinal', 'clarinda'], + 'cardioid': ['cardioid', 'caridoid'], + 'cardiophobe': ['brachiopode', 'cardiophobe'], + 'cardo': ['cardo', 'draco'], + 'cardon': ['candor', 'cardon', 'conrad'], + 'cardoon': ['cardoon', 'coronad'], + 'care': ['acer', 'acre', 'care', 'crea', 'race'], + 'careen': ['careen', 'carene', 'enrace'], + 'carene': ['careen', 'carene', 'enrace'], + 'carer': ['carer', 'crare', 'racer'], + 'carest': ['carest', 'caster', 'recast'], + 'caret': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'caretta': ['caretta', 'teacart', 'tearcat'], + 'carful': ['carful', 'furcal'], + 'carhop': ['carhop', 'paroch'], + 'cariama': ['aramaic', 'cariama'], + 'carian': ['acinar', + 'arnica', + 'canari', + 'carian', + 'carina', + 'crania', + 'narica'], + 'carib': ['baric', 'carib', 'rabic'], + 'cariban': ['arbacin', 'carabin', 'cariban'], + 'caribi': ['cabiri', 'caribi'], + 'carid': ['acrid', 'caird', 'carid', 'darci', 'daric', 'dirca'], + 'carida': ['acarid', 'cardia', 'carida'], + 'caridea': ['arcidae', 'caridea'], + 'caridean': ['caridean', 'dircaean', 'radiance'], + 'caridoid': ['cardioid', 'caridoid'], + 'carina': ['acinar', + 'arnica', + 'canari', + 'carian', + 'carina', + 'crania', + 'narica'], + 'carinal': ['carinal', 'carlina', 'clarain', 'cranial'], + 'carinatae': ['acraniate', 'carinatae'], + 'carinate': ['anaretic', 'arcanite', 'carinate', 'craniate'], + 'carinated': ['carinated', 'eradicant'], + 'cariole': ['calorie', 'cariole'], + 'carious': ['carious', 'curiosa'], + 'carisa': ['carisa', 'sciara'], + 'carissa': ['ascaris', 'carissa'], + 'cark': ['cark', 'rack'], + 'carking': ['arcking', 'carking', 'racking'], + 'carkingly': ['carkingly', 'rackingly'], + 'carless': ['carless', 'classer', 'reclass'], + 'carlet': ['carlet', 'cartel', 'claret', 'rectal', 'talcer'], + 'carlie': ['carlie', 'claire', 'eclair', 'erical'], + 'carlin': ['carlin', 'clarin', 'crinal'], + 'carlina': ['carinal', 'carlina', 'clarain', 'cranial'], + 'carlist': ['carlist', 'clarist'], + 'carlo': ['alcor', 'calor', 'carlo', 'carol', 'claro', 'coral'], + 'carlot': ['carlot', 'crotal'], + 'carlylian': ['ancillary', 'carlylian', 'cranially'], + 'carman': ['carman', 'marcan'], + 'carmel': ['calmer', 'carmel', 'clamer', 'marcel', 'mercal'], + 'carmela': ['cameral', 'caramel', 'carmela', 'ceramal', 'reclama'], + 'carmele': ['carmele', 'cleamer'], + 'carmelite': ['carmelite', 'melicerta'], + 'carmeloite': ['carmeloite', 'ectromelia', 'meteorical'], + 'carmine': ['armenic', 'carmine', 'ceriman', 'crimean', 'mercian'], + 'carminette': ['carminette', 'remittance'], + 'carminite': ['antimeric', 'carminite', 'criminate', 'metrician'], + 'carmot': ['carmot', 'comart'], + 'carnage': ['carnage', 'cranage', 'garance'], + 'carnalite': ['carnalite', 'claretian', 'lacertian', 'nectarial'], + 'carnate': ['carnate', 'cateran'], + 'carnation': ['anarcotin', 'cantorian', 'carnation', 'narcotina'], + 'carnationed': ['carnationed', 'dinoceratan'], + 'carnelian': ['carnelian', 'encranial'], + 'carneol': ['carneol', 'corneal'], + 'carneous': ['carneous', 'nacreous'], + 'carney': ['carney', 'craney'], + 'carnic': ['cancri', 'carnic', 'cranic'], + 'carniolan': ['carniolan', 'nonracial'], + 'carnose': ['carnose', 'coarsen', 'narcose'], + 'carnosity': ['carnosity', 'crayonist'], + 'carnotite': ['carnotite', 'cortinate'], + 'carnous': ['carnous', 'nacrous', 'narcous'], + 'caro': ['acor', 'caro', 'cora', 'orca'], + 'caroa': ['acroa', 'caroa'], + 'carob': ['carbo', 'carob', 'coarb', 'cobra'], + 'caroche': ['caroche', 'coacher', 'recoach'], + 'caroid': ['caroid', 'cordia'], + 'carol': ['alcor', 'calor', 'carlo', 'carol', 'claro', 'coral'], + 'carolan': ['alcoran', 'ancoral', 'carolan'], + 'carole': ['carole', 'coaler', 'coelar', 'oracle', 'recoal'], + 'carolean': ['carolean', 'lecanora'], + 'caroler': ['caroler', 'correal'], + 'caroli': ['caroli', 'corial', 'lorica'], + 'carolin': ['carolin', 'clarion', 'colarin', 'locrian'], + 'carolina': ['carolina', 'conarial'], + 'caroline': ['acrolein', + 'arecolin', + 'caroline', + 'colinear', + 'cornelia', + 'creolian', + 'lonicera'], + 'carolingian': ['carolingian', 'inorganical'], + 'carolus': ['carolus', 'oscular'], + 'carom': ['carom', 'coram', 'macro', 'marco'], + 'carone': ['carone', 'cornea'], + 'caroon': ['caroon', 'corona', 'racoon'], + 'carotenoid': ['carotenoid', 'coronadite', 'decoration'], + 'carotic': ['acrotic', 'carotic'], + 'carotid': ['arctoid', 'carotid', 'dartoic'], + 'carotidean': ['arctoidean', 'carotidean', 'cordaitean', 'dinocerata'], + 'carotin': ['anticor', 'carotin', 'cortina', 'ontaric'], + 'carouse': ['acerous', 'carouse', 'euscaro'], + 'carp': ['carp', 'crap'], + 'carpaine': ['carapine', 'carpaine'], + 'carpel': ['carpel', 'parcel', 'placer'], + 'carpellary': ['carpellary', 'parcellary'], + 'carpellate': ['carpellate', 'parcellate', 'prelacteal'], + 'carpent': ['carpent', 'precant'], + 'carpet': ['carpet', 'peract', 'preact'], + 'carpholite': ['carpholite', 'proethical'], + 'carpid': ['caprid', 'carpid', 'picard'], + 'carpiodes': ['carpiodes', 'scorpidae'], + 'carpocerite': ['carpocerite', 'reciprocate'], + 'carpogonial': ['carpogonial', 'coprolagnia'], + 'carpolite': ['carpolite', 'petricola'], + 'carpolith': ['carpolith', 'politarch', 'trophical'], + 'carposperm': ['carposperm', 'spermocarp'], + 'carrot': ['carrot', 'trocar'], + 'carroter': ['arrector', 'carroter'], + 'carse': ['carse', 'caser', 'ceras', 'scare', 'scrae'], + 'carsmith': ['carsmith', 'chartism'], + 'cartable': ['bracteal', 'cartable'], + 'carte': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'cartel': ['carlet', 'cartel', 'claret', 'rectal', 'talcer'], + 'cartelize': ['cartelize', 'zelatrice'], + 'carter': ['arrect', 'carter', 'crater', 'recart', 'tracer'], + 'cartesian': ['ascertain', 'cartesian', 'cartisane', 'sectarian'], + 'cartesianism': ['cartesianism', 'sectarianism'], + 'cartier': ['cartier', 'cirrate', 'erratic'], + 'cartilage': ['cartilage', 'rectalgia'], + 'cartisane': ['ascertain', 'cartesian', 'cartisane', 'sectarian'], + 'cartist': ['astrict', 'cartist', 'stratic'], + 'carton': ['cantor', 'carton', 'contra'], + 'cartoon': ['cartoon', 'coranto'], + 'cartoonist': ['cartoonist', 'scortation'], + 'carty': ['carty', 'tracy'], + 'carua': ['aruac', 'carua'], + 'carucal': ['accrual', 'carucal'], + 'carucate': ['accurate', 'carucate'], + 'carum': ['carum', 'cumar'], + 'carve': ['carve', 'crave', 'varec'], + 'carvel': ['calver', 'carvel', 'claver'], + 'carven': ['carven', 'cavern', 'craven'], + 'carver': ['carver', 'craver'], + 'carving': ['carving', 'craving'], + 'cary': ['cary', 'racy'], + 'caryl': ['acryl', 'caryl', 'clary'], + 'casabe': ['casabe', 'sabeca'], + 'casal': ['calas', 'casal', 'scala'], + 'cascade': ['cascade', 'saccade'], + 'case': ['case', 'esca'], + 'casebook': ['bookcase', 'casebook'], + 'caseful': ['caseful', 'fucales'], + 'casein': ['casein', 'incase'], + 'casel': ['alces', 'casel', 'scale'], + 'caser': ['carse', 'caser', 'ceras', 'scare', 'scrae'], + 'casern': ['casern', 'rescan'], + 'cashable': ['cashable', 'chasable'], + 'cashel': ['cashel', 'laches', 'sealch'], + 'cask': ['cask', 'sack'], + 'casket': ['casket', 'tesack'], + 'casking': ['casking', 'sacking'], + 'casklike': ['casklike', 'sacklike'], + 'casper': ['casper', 'escarp', 'parsec', 'scrape', 'secpar', 'spacer'], + 'caspian': ['capsian', 'caspian', 'nascapi', 'panisca'], + 'casque': ['casque', 'sacque'], + 'casquet': ['acquest', 'casquet'], + 'casse': ['casse', 'scase'], + 'cassian': ['cassian', 'cassina'], + 'cassina': ['cassian', 'cassina'], + 'cassino': ['caisson', 'cassino'], + 'cassock': ['cassock', 'cossack'], + 'cast': ['acts', 'cast', 'scat'], + 'castalia': ['castalia', 'sacalait'], + 'castalian': ['castalian', 'satanical'], + 'caste': ['caste', 'sceat'], + 'castelet': ['castelet', 'telecast'], + 'caster': ['carest', 'caster', 'recast'], + 'castice': ['ascetic', 'castice', 'siccate'], + 'castle': ['castle', 'sclate'], + 'castoff': ['castoff', 'offcast'], + 'castor': ['arctos', 'castor', 'costar', 'scrota'], + 'castores': ['castores', 'coassert'], + 'castoreum': ['castoreum', 'outscream'], + 'castoridae': ['castoridae', 'cestodaria'], + 'castorin': ['cantoris', 'castorin', 'corsaint'], + 'castra': ['castra', 'tarasc'], + 'casual': ['ascula', 'calusa', 'casual', 'casula', 'causal'], + 'casuality': ['casuality', 'causality'], + 'casually': ['casually', 'causally'], + 'casula': ['ascula', 'calusa', 'casual', 'casula', 'causal'], + 'cat': ['act', 'cat'], + 'catabolin': ['botanical', 'catabolin'], + 'catalan': ['cantala', 'catalan', 'lantaca'], + 'catalanist': ['anastaltic', 'catalanist'], + 'catalase': ['catalase', 'salaceta'], + 'catalinite': ['analcitite', 'catalinite'], + 'catalogue': ['catalogue', 'coagulate'], + 'catalyte': ['catalyte', 'cattleya'], + 'catamenial': ['calamitean', 'catamenial'], + 'catapultier': ['catapultier', 'particulate'], + 'cataria': ['acratia', 'cataria'], + 'catcher': ['catcher', 'recatch'], + 'catchup': ['catchup', 'upcatch'], + 'cate': ['cate', 'teca'], + 'catechin': ['atechnic', 'catechin', 'technica'], + 'catechism': ['catechism', 'schematic'], + 'catechol': ['catechol', 'coachlet'], + 'categoric': ['categoric', 'geocratic'], + 'catella': ['catella', 'lacteal'], + 'catenated': ['catenated', 'decantate'], + 'cater': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'cateran': ['carnate', 'cateran'], + 'caterer': ['caterer', 'recrate', 'retrace', 'terrace'], + 'cateress': ['cateress', 'cerastes'], + 'catfish': ['catfish', 'factish'], + 'cathari': ['cathari', 'chirata', 'cithara'], + 'catharina': ['anthracia', 'antiarcha', 'catharina'], + 'cathartae': ['cathartae', 'tracheata'], + 'cathepsin': ['cathepsin', 'stephanic'], + 'catherine': ['catherine', 'heritance'], + 'catheter': ['catheter', 'charette'], + 'cathine': ['cahnite', 'cathine'], + 'cathinine': ['anchietin', 'cathinine'], + 'cathion': ['cathion', 'chatino'], + 'cathograph': ['cathograph', 'tachograph'], + 'cathole': ['cathole', 'cholate'], + 'cathro': ['cathro', 'orchat'], + 'cathryn': ['cathryn', 'chantry'], + 'cathy': ['cathy', 'cyath', 'yacht'], + 'cation': ['action', 'atonic', 'cation'], + 'cationic': ['aconitic', 'cationic', 'itaconic'], + 'catkin': ['catkin', 'natick'], + 'catlin': ['catlin', 'tincal'], + 'catlinite': ['catlinite', 'intactile'], + 'catmalison': ['catmalison', 'monastical'], + 'catoism': ['atomics', 'catoism', 'cosmati', 'osmatic', 'somatic'], + 'catonian': ['catonian', 'taconian'], + 'catonic': ['cantico', 'catonic', 'taconic'], + 'catonism': ['catonism', 'monastic'], + 'catoptric': ['catoptric', 'protactic'], + 'catpipe': ['apeptic', 'catpipe'], + 'catstone': ['catstone', 'constate'], + 'catsup': ['catsup', 'upcast'], + 'cattail': ['attical', 'cattail'], + 'catti': ['attic', 'catti', 'tacit'], + 'cattily': ['cattily', 'tacitly'], + 'cattiness': ['cattiness', 'tacitness'], + 'cattle': ['cattle', 'tectal'], + 'cattleya': ['catalyte', 'cattleya'], + 'catvine': ['catvine', 'venatic'], + 'caucho': ['cachou', 'caucho'], + 'cauda': ['cadua', 'cauda'], + 'caudle': ['caudle', 'cedula', 'claude'], + 'caudodorsal': ['caudodorsal', 'dorsocaudal'], + 'caudofemoral': ['caudofemoral', 'femorocaudal'], + 'caudolateral': ['caudolateral', 'laterocaudal'], + 'caul': ['caul', 'ucal'], + 'cauld': ['cauld', 'ducal'], + 'caules': ['caelus', 'caules', 'clause'], + 'cauliform': ['cauliform', 'formulaic', 'fumarolic'], + 'caulinar': ['anicular', 'caulinar'], + 'caulis': ['caulis', 'clusia', 'sicula'], + 'caulite': ['aleutic', 'auletic', 'caulite', 'lutecia'], + 'caulome': ['caulome', 'leucoma'], + 'caulomic': ['caulomic', 'coumalic'], + 'caulote': ['caulote', 'colutea', 'oculate'], + 'caunch': ['caunch', 'cuchan'], + 'caurale': ['arcuale', 'caurale'], + 'causal': ['ascula', 'calusa', 'casual', 'casula', 'causal'], + 'causality': ['casuality', 'causality'], + 'causally': ['casually', 'causally'], + 'cause': ['cause', 'sauce'], + 'causeless': ['causeless', 'sauceless'], + 'causer': ['causer', 'saucer'], + 'causey': ['causey', 'cayuse'], + 'cautelous': ['cautelous', 'lutaceous'], + 'cauter': ['acture', 'cauter', 'curate'], + 'caution': ['auction', 'caution'], + 'cautionary': ['auctionary', 'cautionary'], + 'cautioner': ['cautioner', 'cointreau'], + 'caval': ['caval', 'clava'], + 'cavalry': ['calvary', 'cavalry'], + 'cavate': ['cavate', 'caveat', 'vacate'], + 'caveat': ['cavate', 'caveat', 'vacate'], + 'cavel': ['calve', 'cavel', 'clave'], + 'cavern': ['carven', 'cavern', 'craven'], + 'cavil': ['cavil', 'lavic'], + 'caviler': ['caliver', 'caviler', 'claiver', 'clavier', 'valeric', 'velaric'], + 'cavillation': ['cavillation', 'vacillation'], + 'cavitate': ['activate', 'cavitate'], + 'cavitation': ['activation', 'cavitation'], + 'cavitied': ['cavitied', 'vaticide'], + 'caw': ['caw', 'wac'], + 'cawk': ['cawk', 'wack'], + 'cawky': ['cawky', 'wacky'], + 'cayapa': ['cayapa', 'pacaya'], + 'cayuse': ['causey', 'cayuse'], + 'ccoya': ['accoy', 'ccoya'], + 'ceanothus': ['ceanothus', 'oecanthus'], + 'cearin': ['acerin', 'cearin'], + 'cebalrai': ['balearic', 'cebalrai'], + 'ceboid': ['bodice', 'ceboid'], + 'cebur': ['bruce', 'cebur', 'cuber'], + 'cecily': ['cecily', 'cicely'], + 'cedar': ['acred', 'cader', 'cadre', 'cedar'], + 'cedarn': ['cedarn', 'dancer', 'nacred'], + 'cedent': ['cedent', 'decent'], + 'ceder': ['ceder', 'cedre', 'cered', 'creed'], + 'cedrat': ['cedrat', 'decart', 'redact'], + 'cedrate': ['cedrate', 'cerated'], + 'cedre': ['ceder', 'cedre', 'cered', 'creed'], + 'cedrela': ['cedrela', 'creedal', 'declare'], + 'cedrin': ['cedrin', 'cinder', 'crined'], + 'cedriret': ['cedriret', 'directer', 'recredit', 'redirect'], + 'cedrol': ['cedrol', 'colder', 'cordel'], + 'cedron': ['cedron', 'conred'], + 'cedrus': ['cedrus', 'cursed'], + 'cedry': ['cedry', 'decry'], + 'cedula': ['caudle', 'cedula', 'claude'], + 'ceilinged': ['ceilinged', 'diligence'], + 'celadonite': ['caledonite', 'celadonite'], + 'celandine': ['celandine', 'decennial'], + 'celarent': ['celarent', 'centrale', 'enclaret'], + 'celature': ['celature', 'ulcerate'], + 'celebrate': ['celebrate', 'erectable'], + 'celebrated': ['braceleted', 'celebrated'], + 'celemin': ['celemin', 'melenic'], + 'celia': ['alice', 'celia', 'ileac'], + 'cellar': ['caller', 'cellar', 'recall'], + 'cellaret': ['allecret', 'cellaret'], + 'celloid': ['celloid', 'codille', 'collide', 'collied'], + 'celloidin': ['celloidin', 'collidine', 'decillion'], + 'celsian': ['celsian', 'escalin', 'sanicle', 'secalin'], + 'celtiberi': ['celtiberi', 'terebilic'], + 'celtis': ['celtis', 'clites'], + 'cembalist': ['blastemic', 'cembalist'], + 'cementer': ['cementer', 'cerement', 'recement'], + 'cendre': ['cendre', 'decern'], + 'cenosity': ['cenosity', 'cytosine'], + 'cense': ['cense', 'scene', 'sence'], + 'censer': ['censer', 'scerne', 'screen', 'secern'], + 'censerless': ['censerless', 'screenless'], + 'censorial': ['censorial', 'sarcoline'], + 'censual': ['censual', 'unscale'], + 'censureless': ['censureless', 'recluseness'], + 'cental': ['cantle', 'cental', 'lancet', 'tancel'], + 'centare': ['centare', 'crenate'], + 'centaur': ['centaur', 'untrace'], + 'centauri': ['anuretic', 'centauri', 'centuria', 'teucrian'], + 'centaury': ['centaury', 'cyanuret'], + 'centena': ['canteen', 'centena'], + 'centenar': ['centenar', 'entrance'], + 'centenier': ['centenier', 'renitence'], + 'center': ['center', 'recent', 'tenrec'], + 'centered': ['centered', 'decenter', 'decentre', 'recedent'], + 'centerer': ['centerer', 'recenter', 'recentre', 'terrence'], + 'centermost': ['centermost', 'escortment'], + 'centesimal': ['centesimal', 'lemniscate'], + 'centiar': ['centiar', 'certain', 'citrean', 'nacrite', 'nectria'], + 'centiare': ['aneretic', 'centiare', 'creatine', 'increate', 'iterance'], + 'centibar': ['bacterin', 'centibar'], + 'centimeter': ['centimeter', 'recitement', 'remittence'], + 'centimo': ['centimo', 'entomic', 'tecomin'], + 'centimolar': ['centimolar', 'melicraton'], + 'centinormal': ['centinormal', 'conterminal', 'nonmetrical'], + 'cento': ['cento', 'conte', 'tecon'], + 'centrad': ['cantred', 'centrad', 'tranced'], + 'centrale': ['celarent', 'centrale', 'enclaret'], + 'centranth': ['centranth', 'trenchant'], + 'centraxonia': ['centraxonia', 'excarnation'], + 'centriole': ['centriole', 'electrion', 'relection'], + 'centrodorsal': ['centrodorsal', 'dorsocentral'], + 'centroid': ['centroid', 'doctrine'], + 'centrolineal': ['centrolineal', 'crenellation'], + 'centunculus': ['centunculus', 'unsucculent'], + 'centuria': ['anuretic', 'centauri', 'centuria', 'teucrian'], + 'centurial': ['centurial', 'lucretian', 'ultranice'], + 'centuried': ['centuried', 'unrecited'], + 'centurion': ['centurion', 'continuer', 'cornutine'], + 'cepa': ['cape', 'cepa', 'pace'], + 'cephalin': ['alphenic', 'cephalin'], + 'cephalina': ['cephalina', 'epilachna'], + 'cephaloid': ['cephaloid', 'pholcidae'], + 'cephalomeningitis': ['cephalomeningitis', 'meningocephalitis'], + 'cephalometric': ['cephalometric', 'petrochemical'], + 'cephalopodous': ['cephalopodous', 'podocephalous'], + 'cephas': ['cephas', 'pesach'], + 'cepolidae': ['adipocele', 'cepolidae', 'ploceidae'], + 'ceps': ['ceps', 'spec'], + 'ceptor': ['ceptor', 'copter'], + 'ceral': ['ceral', 'clare', 'clear', 'lacer'], + 'ceramal': ['cameral', 'caramel', 'carmela', 'ceramal', 'reclama'], + 'ceramic': ['ceramic', 'racemic'], + 'ceramist': ['camerist', 'ceramist', 'matrices'], + 'ceras': ['carse', 'caser', 'ceras', 'scare', 'scrae'], + 'cerasein': ['cerasein', 'increase'], + 'cerasin': ['arsenic', 'cerasin', 'sarcine'], + 'cerastes': ['cateress', 'cerastes'], + 'cerata': ['arcate', 'cerata'], + 'cerate': ['cerate', 'create', 'ecarte'], + 'cerated': ['cedrate', 'cerated'], + 'ceratiid': ['ceratiid', 'raticide'], + 'ceration': ['actioner', 'anerotic', 'ceration', 'creation', 'reaction'], + 'ceratium': ['ceratium', 'muricate'], + 'ceratonia': ['canariote', 'ceratonia'], + 'ceratosa': ['ceratosa', 'ostracea'], + 'ceratothecal': ['ceratothecal', 'chloracetate'], + 'cerberic': ['cerberic', 'cerebric'], + 'cercal': ['carcel', 'cercal'], + 'cerci': ['cerci', 'ceric', 'cicer', 'circe'], + 'cercus': ['cercus', 'cruces'], + 'cerdonian': ['cerdonian', 'ordinance'], + 'cere': ['cere', 'cree'], + 'cereal': ['alerce', 'cereal', 'relace'], + 'cerealin': ['cerealin', 'cinereal', 'reliance'], + 'cerebra': ['cerebra', 'rebrace'], + 'cerebric': ['cerberic', 'cerebric'], + 'cerebroma': ['cerebroma', 'embraceor'], + 'cerebromeningitis': ['cerebromeningitis', 'meningocerebritis'], + 'cerebrum': ['cerebrum', 'cumberer'], + 'cered': ['ceder', 'cedre', 'cered', 'creed'], + 'cerement': ['cementer', 'cerement', 'recement'], + 'ceremonial': ['ceremonial', 'neomiracle'], + 'ceresin': ['ceresin', 'sincere'], + 'cereus': ['cereus', 'ceruse', 'recuse', 'rescue', 'secure'], + 'cerevis': ['cerevis', 'scrieve', 'service'], + 'ceria': ['acier', 'aeric', 'ceria', 'erica'], + 'ceric': ['cerci', 'ceric', 'cicer', 'circe'], + 'ceride': ['ceride', 'deicer'], + 'cerillo': ['cerillo', 'colleri', 'collier'], + 'ceriman': ['armenic', 'carmine', 'ceriman', 'crimean', 'mercian'], + 'cerin': ['cerin', 'crine'], + 'cerion': ['cerion', 'coiner', 'neroic', 'orcein', 'recoin'], + 'ceriops': ['ceriops', 'persico'], + 'cerite': ['cerite', 'certie', 'recite', 'tierce'], + 'cerium': ['cerium', 'uremic'], + 'cernuous': ['cernuous', 'coenurus'], + 'cero': ['cero', 'core'], + 'ceroma': ['ceroma', 'corema'], + 'ceroplast': ['ceroplast', 'precostal'], + 'ceroplastic': ['ceroplastic', 'cleistocarp', 'coreplastic'], + 'ceroplasty': ['ceroplasty', 'coreplasty'], + 'cerotic': ['cerotic', 'orectic'], + 'cerotin': ['cerotin', 'cointer', 'cotrine', 'cretion', 'noticer', 'rection'], + 'cerous': ['cerous', 'course', 'crouse', 'source'], + 'certain': ['centiar', 'certain', 'citrean', 'nacrite', 'nectria'], + 'certhia': ['certhia', 'rhaetic', 'theriac'], + 'certie': ['cerite', 'certie', 'recite', 'tierce'], + 'certifiable': ['certifiable', 'rectifiable'], + 'certification': ['certification', 'cretification', 'rectification'], + 'certificative': ['certificative', 'rectificative'], + 'certificator': ['certificator', 'rectificator'], + 'certificatory': ['certificatory', 'rectificatory'], + 'certified': ['certified', 'rectified'], + 'certifier': ['certifier', 'rectifier'], + 'certify': ['certify', 'cretify', 'rectify'], + 'certis': ['certis', 'steric'], + 'certitude': ['certitude', 'rectitude'], + 'certosina': ['atroscine', 'certosina', 'ostracine', 'tinoceras', 'tricosane'], + 'certosino': ['certosino', 'cortisone', 'socotrine'], + 'cerulean': ['cerulean', 'laurence'], + 'ceruminal': ['ceruminal', 'melanuric', 'numerical'], + 'ceruse': ['cereus', 'ceruse', 'recuse', 'rescue', 'secure'], + 'cervicobuccal': ['buccocervical', 'cervicobuccal'], + 'cervicodorsal': ['cervicodorsal', 'dorsocervical'], + 'cervicodynia': ['cervicodynia', 'corycavidine'], + 'cervicofacial': ['cervicofacial', 'faciocervical'], + 'cervicolabial': ['cervicolabial', 'labiocervical'], + 'cervicovesical': ['cervicovesical', 'vesicocervical'], + 'cervoid': ['cervoid', 'divorce'], + 'cervuline': ['cervuline', 'virulence'], + 'ceryl': ['ceryl', 'clyer'], + 'cerynean': ['ancyrene', 'cerynean'], + 'cesare': ['cesare', 'crease', 'recase', 'searce'], + 'cesarolite': ['cesarolite', 'esoterical'], + 'cesium': ['cesium', 'miscue'], + 'cesser': ['cesser', 'recess'], + 'cession': ['cession', 'oscines'], + 'cessor': ['cessor', 'crosse', 'scorse'], + 'cest': ['cest', 'sect'], + 'cestodaria': ['castoridae', 'cestodaria'], + 'cestrian': ['canister', 'cestrian', 'cisterna', 'irascent'], + 'cetane': ['cetane', 'tenace'], + 'cetene': ['cetene', 'ectene'], + 'ceti': ['ceti', 'cite', 'tice'], + 'cetid': ['cetid', 'edict'], + 'cetomorphic': ['cetomorphic', 'chemotropic', 'ectomorphic'], + 'cetonia': ['acetoin', 'aconite', 'anoetic', 'antoeci', 'cetonia'], + 'cetonian': ['cetonian', 'enaction'], + 'cetorhinus': ['cetorhinus', 'urosthenic'], + 'cetus': ['cetus', 'scute'], + 'cevenol': ['cevenol', 'clovene'], + 'cevine': ['cevine', 'evince', 'venice'], + 'cha': ['ach', 'cha'], + 'chab': ['bach', 'chab'], + 'chabouk': ['chabouk', 'chakobu'], + 'chaco': ['chaco', 'choca', 'coach'], + 'chacte': ['cachet', 'chacte'], + 'chaenolobus': ['chaenolobus', 'unchoosable'], + 'chaeta': ['achate', 'chaeta'], + 'chaetites': ['aesthetic', 'chaetites'], + 'chaetognath': ['chaetognath', 'gnathotheca'], + 'chaetopod': ['chaetopod', 'podotheca'], + 'chafer': ['chafer', 'frache'], + 'chagan': ['chagan', 'changa'], + 'chagrin': ['arching', 'chagrin'], + 'chai': ['chai', 'chia'], + 'chain': ['chain', 'chian', 'china'], + 'chained': ['chained', 'echidna'], + 'chainer': ['chainer', 'enchair', 'rechain'], + 'chainlet': ['chainlet', 'ethnical'], + 'chainman': ['chainman', 'chinaman'], + 'chair': ['chair', 'chria'], + 'chairer': ['chairer', 'charier'], + 'chait': ['aitch', 'chait', 'chati', 'chita', 'taich', 'tchai'], + 'chakar': ['chakar', 'chakra', 'charka'], + 'chakari': ['chakari', 'chikara', 'kachari'], + 'chakobu': ['chabouk', 'chakobu'], + 'chakra': ['chakar', 'chakra', 'charka'], + 'chalcon': ['chalcon', 'clochan', 'conchal'], + 'chalcosine': ['ascolichen', 'chalcosine'], + 'chaldron': ['chaldron', 'chlordan', 'chondral'], + 'chalet': ['achtel', 'chalet', 'thecal', 'thecla'], + 'chalker': ['chalker', 'hackler'], + 'chalky': ['chalky', 'hackly'], + 'challie': ['alichel', 'challie', 'helical'], + 'chalmer': ['chalmer', 'charmel'], + 'chalon': ['chalon', 'lochan'], + 'chalone': ['chalone', 'cholane'], + 'chalta': ['caltha', 'chalta'], + 'chamal': ['almach', 'chamal'], + 'chamar': ['chamar', 'machar'], + 'chamber': ['becharm', 'brecham', 'chamber'], + 'chamberer': ['chamberer', 'rechamber'], + 'chamian': ['chamian', 'mahican'], + 'chamisal': ['chamisal', 'chiasmal'], + 'chamiso': ['chamiso', 'chamois'], + 'chamite': ['chamite', 'hematic'], + 'chamois': ['chamiso', 'chamois'], + 'champa': ['champa', 'mapach'], + 'champain': ['champain', 'chinampa'], + 'chancer': ['chancer', 'chancre'], + 'chanchito': ['chanchito', 'nachitoch'], + 'chanco': ['chanco', 'concha'], + 'chancre': ['chancer', 'chancre'], + 'chandu': ['chandu', 'daunch'], + 'chane': ['achen', 'chane', 'chena', 'hance'], + 'chang': ['chang', 'ganch'], + 'changa': ['chagan', 'changa'], + 'changer': ['changer', 'genarch'], + 'chanidae': ['chanidae', 'hacienda'], + 'channeler': ['channeler', 'encharnel'], + 'chanst': ['chanst', 'snatch', 'stanch'], + 'chant': ['chant', 'natch'], + 'chanter': ['chanter', 'rechant'], + 'chantey': ['atechny', 'chantey'], + 'chantry': ['cathryn', 'chantry'], + 'chaos': ['chaos', 'oshac'], + 'chaotical': ['acatholic', 'chaotical'], + 'chap': ['caph', 'chap'], + 'chaparro': ['chaparro', 'parachor'], + 'chape': ['chape', 'cheap', 'peach'], + 'chaped': ['chaped', 'phecda'], + 'chapel': ['chapel', 'lepcha', 'pleach'], + 'chapelet': ['chapelet', 'peachlet'], + 'chapelmaster': ['chapelmaster', 'spermathecal'], + 'chaperno': ['canephor', 'chaperno', 'chaperon'], + 'chaperon': ['canephor', 'chaperno', 'chaperon'], + 'chaperone': ['canephore', 'chaperone'], + 'chaperonless': ['chaperonless', 'proseneschal'], + 'chapin': ['apinch', 'chapin', 'phanic'], + 'chapiter': ['chapiter', 'phreatic'], + 'chaps': ['chaps', 'pasch'], + 'chapt': ['chapt', 'pacht', 'patch'], + 'chapter': ['chapter', 'patcher', 'repatch'], + 'char': ['arch', 'char', 'rach'], + 'chara': ['achar', 'chara'], + 'charac': ['charac', 'charca'], + 'characin': ['anarchic', 'characin'], + 'characinoid': ['arachidonic', 'characinoid'], + 'charadrii': ['charadrii', 'richardia'], + 'charas': ['achras', 'charas'], + 'charbon': ['brochan', 'charbon'], + 'charca': ['charac', 'charca'], + 'chare': ['acher', 'arche', 'chare', 'chera', 'rache', 'reach'], + 'charer': ['archer', 'charer', 'rechar'], + 'charette': ['catheter', 'charette'], + 'charge': ['charge', 'creagh'], + 'charier': ['chairer', 'charier'], + 'chariot': ['chariot', 'haricot'], + 'charioted': ['charioted', 'trochidae'], + 'chariotman': ['achromatin', 'chariotman', 'machinator'], + 'charism': ['charism', 'chrisma'], + 'charisma': ['archaism', 'charisma'], + 'chark': ['chark', 'karch'], + 'charka': ['chakar', 'chakra', 'charka'], + 'charlatanistic': ['antarchistical', 'charlatanistic'], + 'charleen': ['charleen', 'charlene'], + 'charlene': ['charleen', 'charlene'], + 'charles': ['charles', 'clasher'], + 'charm': ['charm', 'march'], + 'charmel': ['chalmer', 'charmel'], + 'charmer': ['charmer', 'marcher', 'remarch'], + 'charnel': ['charnel', 'larchen'], + 'charon': ['anchor', 'archon', 'charon', 'rancho'], + 'charpoy': ['charpoy', 'corypha'], + 'chart': ['chart', 'ratch'], + 'charter': ['charter', 'ratcher'], + 'charterer': ['charterer', 'recharter'], + 'charting': ['charting', 'ratching'], + 'chartism': ['carsmith', 'chartism'], + 'charuk': ['charuk', 'chukar'], + 'chary': ['archy', 'chary'], + 'chasable': ['cashable', 'chasable'], + 'chaser': ['arches', 'chaser', 'eschar', 'recash', 'search'], + 'chasma': ['ascham', 'chasma'], + 'chaste': ['chaste', 'sachet', 'scathe', 'scheat'], + 'chasten': ['chasten', 'sanetch'], + 'chastener': ['chastener', 'rechasten'], + 'chastity': ['chastity', 'yachtist'], + 'chasuble': ['chasuble', 'subchela'], + 'chat': ['chat', 'tach'], + 'chatelainry': ['chatelainry', 'trachylinae'], + 'chati': ['aitch', 'chait', 'chati', 'chita', 'taich', 'tchai'], + 'chatino': ['cathion', 'chatino'], + 'chatsome': ['chatsome', 'moschate'], + 'chatta': ['attach', 'chatta'], + 'chattation': ['chattation', 'thanatotic'], + 'chattel': ['chattel', 'latchet'], + 'chatter': ['chatter', 'ratchet'], + 'chattery': ['chattery', 'ratchety', 'trachyte'], + 'chatti': ['chatti', 'hattic'], + 'chatty': ['chatty', 'tatchy'], + 'chatwood': ['chatwood', 'woodchat'], + 'chaute': ['chaute', 'chueta'], + 'chawan': ['chawan', 'chwana', 'wachna'], + 'chawer': ['chawer', 'rechaw'], + 'chawk': ['chawk', 'whack'], + 'chay': ['achy', 'chay'], + 'cheap': ['chape', 'cheap', 'peach'], + 'cheapen': ['cheapen', 'peachen'], + 'cheapery': ['cheapery', 'peachery'], + 'cheapside': ['cheapside', 'sphecidae'], + 'cheat': ['cheat', 'tache', 'teach', 'theca'], + 'cheatable': ['cheatable', 'teachable'], + 'cheatableness': ['cheatableness', 'teachableness'], + 'cheater': ['cheater', 'hectare', 'recheat', 'reteach', 'teacher'], + 'cheatery': ['cheatery', 'cytherea', 'teachery'], + 'cheating': ['cheating', 'teaching'], + 'cheatingly': ['cheatingly', 'teachingly'], + 'cheatrie': ['cheatrie', 'hetaeric'], + 'checker': ['checker', 'recheck'], + 'chee': ['chee', 'eche'], + 'cheek': ['cheek', 'cheke', 'keech'], + 'cheerer': ['cheerer', 'recheer'], + 'cheerly': ['cheerly', 'lechery'], + 'cheery': ['cheery', 'reechy'], + 'cheet': ['cheet', 'hecte'], + 'cheir': ['cheir', 'rheic'], + 'cheiropodist': ['cheiropodist', 'coeditorship'], + 'cheka': ['cheka', 'keach'], + 'cheke': ['cheek', 'cheke', 'keech'], + 'chela': ['chela', 'lache', 'leach'], + 'chelide': ['chelide', 'heliced'], + 'chelidon': ['chelidon', 'chelonid', 'delichon'], + 'chelidonate': ['chelidonate', 'endothecial'], + 'chelodina': ['chelodina', 'hedonical'], + 'chelone': ['chelone', 'echelon'], + 'chelonid': ['chelidon', 'chelonid', 'delichon'], + 'cheloniid': ['cheloniid', 'lichenoid'], + 'chemiatrist': ['chemiatrist', 'chrismatite', 'theatricism'], + 'chemical': ['alchemic', 'chemical'], + 'chemicomechanical': ['chemicomechanical', 'mechanicochemical'], + 'chemicophysical': ['chemicophysical', 'physicochemical'], + 'chemicovital': ['chemicovital', 'vitochemical'], + 'chemiloon': ['chemiloon', 'homocline'], + 'chemotaxy': ['chemotaxy', 'myxotheca'], + 'chemotropic': ['cetomorphic', 'chemotropic', 'ectomorphic'], + 'chena': ['achen', 'chane', 'chena', 'hance'], + 'chenica': ['chenica', 'chicane'], + 'chenille': ['chenille', 'hellenic'], + 'chenopod': ['chenopod', 'ponchoed'], + 'chera': ['acher', 'arche', 'chare', 'chera', 'rache', 'reach'], + 'chermes': ['chermes', 'schemer'], + 'chert': ['chert', 'retch'], + 'cherte': ['cherte', 'etcher'], + 'chervil': ['chervil', 'chilver'], + 'cheson': ['cheson', 'chosen', 'schone'], + 'chest': ['chest', 'stech'], + 'chestily': ['chestily', 'lecythis'], + 'chesty': ['chesty', 'scythe'], + 'chet': ['chet', 'etch', 'tche', 'tech'], + 'chettik': ['chettik', 'thicket'], + 'chetty': ['chetty', 'tetchy'], + 'chewer': ['chewer', 'rechew'], + 'chewink': ['chewink', 'whicken'], + 'chi': ['chi', 'hic', 'ich'], + 'chia': ['chai', 'chia'], + 'chiam': ['chiam', 'machi', 'micah'], + 'chian': ['chain', 'chian', 'china'], + 'chiasmal': ['chamisal', 'chiasmal'], + 'chiastolite': ['chiastolite', 'heliostatic'], + 'chicane': ['chenica', 'chicane'], + 'chicle': ['chicle', 'cliche'], + 'chid': ['chid', 'dich'], + 'chider': ['chider', 'herdic'], + 'chidra': ['chidra', 'diarch'], + 'chief': ['chief', 'fiche'], + 'chield': ['chield', 'childe'], + 'chien': ['chien', 'chine', 'niche'], + 'chikara': ['chakari', 'chikara', 'kachari'], + 'chil': ['chil', 'lich'], + 'childe': ['chield', 'childe'], + 'chilean': ['chilean', 'echinal', 'nichael'], + 'chili': ['chili', 'lichi'], + 'chiliasm': ['chiliasm', 'hilasmic', 'machilis'], + 'chilla': ['achill', 'cahill', 'chilla'], + 'chiloma': ['chiloma', 'malicho'], + 'chilopod': ['chilopod', 'pholcoid'], + 'chilopoda': ['chilopoda', 'haplodoci'], + 'chilostome': ['chilostome', 'schooltime'], + 'chilver': ['chervil', 'chilver'], + 'chimane': ['chimane', 'machine'], + 'chime': ['chime', 'hemic', 'miche'], + 'chimer': ['chimer', 'mechir', 'micher'], + 'chimera': ['chimera', 'hermaic'], + 'chimney': ['chimney', 'hymenic'], + 'chimu': ['chimu', 'humic'], + 'chin': ['chin', 'inch'], + 'china': ['chain', 'chian', 'china'], + 'chinaman': ['chainman', 'chinaman'], + 'chinampa': ['champain', 'chinampa'], + 'chinanta': ['acanthin', 'chinanta'], + 'chinar': ['chinar', 'inarch'], + 'chine': ['chien', 'chine', 'niche'], + 'chined': ['chined', 'inched'], + 'chink': ['chink', 'kinch'], + 'chinkle': ['chinkle', 'kelchin'], + 'chinks': ['chinks', 'skinch'], + 'chinoa': ['chinoa', 'noahic'], + 'chinotti': ['chinotti', 'tithonic'], + 'chint': ['chint', 'nitch'], + 'chiolite': ['chiolite', 'eolithic'], + 'chionididae': ['chionididae', 'onchidiidae'], + 'chiral': ['archil', 'chiral'], + 'chirapsia': ['chirapsia', 'pharisaic'], + 'chirata': ['cathari', 'chirata', 'cithara'], + 'chiro': ['chiro', 'choir', 'ichor'], + 'chiromancist': ['chiromancist', 'monarchistic'], + 'chiromant': ['chiromant', 'chromatin'], + 'chiromantic': ['chiromantic', 'chromatinic'], + 'chiromantis': ['anchoritism', 'chiromantis', 'chrismation', 'harmonistic'], + 'chirometer': ['chirometer', 'rheometric'], + 'chiroplasty': ['chiroplasty', 'polyarchist'], + 'chiropter': ['chiropter', 'peritroch'], + 'chirosophist': ['chirosophist', 'opisthorchis'], + 'chirotes': ['chirotes', 'theorics'], + 'chirotype': ['chirotype', 'hypocrite'], + 'chirp': ['chirp', 'prich'], + 'chirper': ['chirper', 'prerich'], + 'chiseler': ['chiseler', 'rechisel'], + 'chit': ['chit', 'itch', 'tchi'], + 'chita': ['aitch', 'chait', 'chati', 'chita', 'taich', 'tchai'], + 'chital': ['chital', 'claith'], + 'chitinoid': ['chitinoid', 'dithionic'], + 'chitosan': ['atchison', 'chitosan'], + 'chitose': ['chitose', 'echoist'], + 'chloe': ['chloe', 'choel'], + 'chloracetate': ['ceratothecal', 'chloracetate'], + 'chloranthy': ['chloranthy', 'rhynchotal'], + 'chlorate': ['chlorate', 'trochlea'], + 'chlordan': ['chaldron', 'chlordan', 'chondral'], + 'chlore': ['chlore', 'choler', 'orchel'], + 'chloremia': ['chloremia', 'homerical'], + 'chlorinate': ['chlorinate', 'ectorhinal', 'tornachile'], + 'chlorite': ['chlorite', 'clothier'], + 'chloritic': ['chloritic', 'trochilic'], + 'chloroamine': ['chloroamine', 'melanochroi'], + 'chloroanaemia': ['aeolharmonica', 'chloroanaemia'], + 'chloroanemia': ['chloroanemia', 'choleromania'], + 'chloroiodide': ['chloroiodide', 'iodochloride'], + 'chloroplatinic': ['chloroplatinic', 'platinochloric'], + 'cho': ['cho', 'och'], + 'choana': ['aonach', 'choana'], + 'choca': ['chaco', 'choca', 'coach'], + 'choco': ['choco', 'hocco'], + 'choel': ['chloe', 'choel'], + 'choenix': ['choenix', 'hexonic'], + 'choes': ['choes', 'chose'], + 'choiak': ['choiak', 'kochia'], + 'choice': ['choice', 'echoic'], + 'choil': ['choil', 'choli', 'olchi'], + 'choir': ['chiro', 'choir', 'ichor'], + 'choirman': ['choirman', 'harmonic', 'omniarch'], + 'choker': ['choker', 'hocker'], + 'choky': ['choky', 'hocky'], + 'chol': ['chol', 'loch'], + 'chola': ['chola', 'loach', 'olcha'], + 'cholane': ['chalone', 'cholane'], + 'cholangioitis': ['angiocholitis', 'cholangioitis'], + 'cholanic': ['cholanic', 'colchian'], + 'cholate': ['cathole', 'cholate'], + 'cholecystoduodenostomy': ['cholecystoduodenostomy', 'duodenocholecystostomy'], + 'choler': ['chlore', 'choler', 'orchel'], + 'cholera': ['cholera', 'choreal'], + 'cholerine': ['cholerine', 'rhinocele'], + 'choleromania': ['chloroanemia', 'choleromania'], + 'cholesteremia': ['cholesteremia', 'heteroecismal'], + 'choli': ['choil', 'choli', 'olchi'], + 'choline': ['choline', 'helicon'], + 'cholo': ['cholo', 'cohol'], + 'chondral': ['chaldron', 'chlordan', 'chondral'], + 'chondrite': ['chondrite', 'threnodic'], + 'chondroadenoma': ['adenochondroma', 'chondroadenoma'], + 'chondroangioma': ['angiochondroma', 'chondroangioma'], + 'chondroarthritis': ['arthrochondritis', 'chondroarthritis'], + 'chondrocostal': ['chondrocostal', 'costochondral'], + 'chondrofibroma': ['chondrofibroma', 'fibrochondroma'], + 'chondrolipoma': ['chondrolipoma', 'lipochondroma'], + 'chondromyxoma': ['chondromyxoma', 'myxochondroma'], + 'chondromyxosarcoma': ['chondromyxosarcoma', 'myxochondrosarcoma'], + 'choop': ['choop', 'pooch'], + 'chopa': ['chopa', 'phoca', 'poach'], + 'chopin': ['chopin', 'phonic'], + 'chopine': ['chopine', 'phocine'], + 'chora': ['achor', 'chora', 'corah', 'orach', 'roach'], + 'choral': ['choral', 'lorcha'], + 'chorasmian': ['anachorism', 'chorasmian', 'maraschino'], + 'chordal': ['chordal', 'dorlach'], + 'chorditis': ['chorditis', 'orchidist'], + 'chordotonal': ['chordotonal', 'notochordal'], + 'chore': ['chore', 'ocher'], + 'chorea': ['chorea', 'ochrea', 'rochea'], + 'choreal': ['cholera', 'choreal'], + 'choree': ['choree', 'cohere', 'echoer'], + 'choreoid': ['choreoid', 'ochidore'], + 'choreus': ['choreus', 'chouser', 'rhoecus'], + 'choric': ['choric', 'orchic'], + 'choriocele': ['choriocele', 'orchiocele'], + 'chorioidoretinitis': ['chorioidoretinitis', 'retinochorioiditis'], + 'chorism': ['chorism', 'chrisom'], + 'chorist': ['chorist', 'ostrich'], + 'choristate': ['choristate', 'rheostatic'], + 'chorization': ['chorization', 'rhizoctonia', 'zonotrichia'], + 'choroid': ['choroid', 'ochroid'], + 'chort': ['chort', 'rotch', 'torch'], + 'chorten': ['chorten', 'notcher'], + 'chorti': ['chorti', 'orthic', 'thoric', 'trochi'], + 'chose': ['choes', 'chose'], + 'chosen': ['cheson', 'chosen', 'schone'], + 'chou': ['chou', 'ouch'], + 'chough': ['chough', 'hughoc'], + 'choup': ['choup', 'pouch'], + 'chous': ['chous', 'hocus'], + 'chouser': ['choreus', 'chouser', 'rhoecus'], + 'chowder': ['chowder', 'cowherd'], + 'chria': ['chair', 'chria'], + 'chrism': ['chrism', 'smirch'], + 'chrisma': ['charism', 'chrisma'], + 'chrismation': ['anchoritism', 'chiromantis', 'chrismation', 'harmonistic'], + 'chrismatite': ['chemiatrist', 'chrismatite', 'theatricism'], + 'chrisom': ['chorism', 'chrisom'], + 'christ': ['christ', 'strich'], + 'christen': ['christen', 'snitcher'], + 'christener': ['christener', 'rechristen'], + 'christian': ['christian', 'christina'], + 'christiana': ['arachnitis', 'christiana'], + 'christina': ['christian', 'christina'], + 'christophe': ['christophe', 'hectorship'], + 'chromatician': ['achromatinic', 'chromatician'], + 'chromatid': ['chromatid', 'dichromat'], + 'chromatin': ['chiromant', 'chromatin'], + 'chromatinic': ['chiromantic', 'chromatinic'], + 'chromatocyte': ['chromatocyte', 'thoracectomy'], + 'chromatoid': ['chromatoid', 'tichodroma'], + 'chromatone': ['chromatone', 'enomotarch'], + 'chromid': ['chromid', 'richdom'], + 'chromidae': ['archidome', 'chromidae'], + 'chromite': ['chromite', 'trichome'], + 'chromocyte': ['chromocyte', 'cytochrome'], + 'chromolithography': ['chromolithography', 'lithochromography'], + 'chromophotography': ['chromophotography', 'photochromography'], + 'chromophotolithograph': ['chromophotolithograph', 'photochromolithograph'], + 'chromopsia': ['chromopsia', 'isocamphor'], + 'chromotype': ['chromotype', 'cormophyte', 'ectomorphy'], + 'chromotypic': ['chromotypic', 'cormophytic', 'mycotrophic'], + 'chronophotograph': ['chronophotograph', 'photochronograph'], + 'chronophotographic': ['chronophotographic', 'photochronographic'], + 'chronophotography': ['chronophotography', 'photochronography'], + 'chrysography': ['chrysography', 'psychorrhagy'], + 'chrysolite': ['chrysolite', 'chrysotile'], + 'chrysopid': ['chrysopid', 'dysphoric'], + 'chrysotile': ['chrysolite', 'chrysotile'], + 'chucker': ['chucker', 'rechuck'], + 'chueta': ['chaute', 'chueta'], + 'chukar': ['charuk', 'chukar'], + 'chulan': ['chulan', 'launch', 'nuchal'], + 'chum': ['chum', 'much'], + 'chumpish': ['chumpish', 'chumship'], + 'chumship': ['chumpish', 'chumship'], + 'chunari': ['chunari', 'unchair'], + 'chunnia': ['chunnia', 'unchain'], + 'chuprassie': ['chuprassie', 'haruspices'], + 'churl': ['churl', 'lurch'], + 'churn': ['churn', 'runch'], + 'chut': ['chut', 'tchu', 'utch'], + 'chwana': ['chawan', 'chwana', 'wachna'], + 'chyak': ['chyak', 'hacky'], + 'chylous': ['chylous', 'slouchy'], + 'cibarial': ['biracial', 'cibarial'], + 'cibarian': ['arabinic', 'cabirian', 'carabini', 'cibarian'], + 'cibolan': ['cibolan', 'coalbin'], + 'cicala': ['alcaic', 'cicala'], + 'cicatrize': ['arcticize', 'cicatrize'], + 'cicely': ['cecily', 'cicely'], + 'cicer': ['cerci', 'ceric', 'cicer', 'circe'], + 'cicerone': ['cicerone', 'croceine'], + 'cicindela': ['cicindela', 'cinclidae', 'icelandic'], + 'ciclatoun': ['ciclatoun', 'noctiluca'], + 'ciconian': ['aniconic', 'ciconian'], + 'ciconine': ['ciconine', 'conicine'], + 'cidaridae': ['acrididae', 'cardiidae', 'cidaridae'], + 'cidaris': ['cidaris', 'sciarid'], + 'cider': ['cider', 'cried', 'deric', 'dicer'], + 'cigala': ['caliga', 'cigala'], + 'cigar': ['cigar', 'craig'], + 'cilia': ['cilia', 'iliac'], + 'ciliation': ['ciliation', 'coinitial'], + 'cilice': ['cilice', 'icicle'], + 'cimbia': ['cimbia', 'iambic'], + 'cimicidae': ['amicicide', 'cimicidae'], + 'cinchonine': ['cinchonine', 'conchinine'], + 'cinclidae': ['cicindela', 'cinclidae', 'icelandic'], + 'cinder': ['cedrin', 'cinder', 'crined'], + 'cinderous': ['cinderous', 'decursion'], + 'cindie': ['cindie', 'incide'], + 'cine': ['cine', 'nice'], + 'cinel': ['cinel', 'cline'], + 'cinema': ['anemic', 'cinema', 'iceman'], + 'cinematographer': ['cinematographer', 'megachiropteran'], + 'cinene': ['cinene', 'nicene'], + 'cineole': ['cineole', 'coeline'], + 'cinerama': ['amacrine', 'american', 'camerina', 'cinerama'], + 'cineration': ['cineration', 'inceration'], + 'cinerea': ['cairene', 'cinerea'], + 'cinereal': ['cerealin', 'cinereal', 'reliance'], + 'cingulum': ['cingulum', 'glucinum'], + 'cinnamate': ['antanemic', 'cinnamate'], + 'cinnamol': ['cinnamol', 'nonclaim'], + 'cinnamon': ['cinnamon', 'mannonic'], + 'cinnamoned': ['cinnamoned', 'demicannon'], + 'cinque': ['cinque', 'quince'], + 'cinter': ['cinter', 'cretin', 'crinet'], + 'cinura': ['anuric', 'cinura', 'uranic'], + 'cion': ['cion', 'coin', 'icon'], + 'cipher': ['cipher', 'rechip'], + 'cipo': ['cipo', 'pico'], + 'circe': ['cerci', 'ceric', 'cicer', 'circe'], + 'circle': ['circle', 'cleric'], + 'cirrate': ['cartier', 'cirrate', 'erratic'], + 'cirrated': ['cirrated', 'craterid'], + 'cirrhotic': ['cirrhotic', 'trichroic'], + 'cirrose': ['cirrose', 'crosier'], + 'cirsectomy': ['cirsectomy', 'citromyces'], + 'cirsoid': ['cirsoid', 'soricid'], + 'ciruela': ['auricle', 'ciruela'], + 'cise': ['cise', 'sice'], + 'cisplatine': ['cisplatine', 'plasticine'], + 'cispontine': ['cispontine', 'inspection'], + 'cissoidal': ['cissoidal', 'dissocial'], + 'cistern': ['cistern', 'increst'], + 'cisterna': ['canister', 'cestrian', 'cisterna', 'irascent'], + 'cisternal': ['cisternal', 'larcenist'], + 'cistvaen': ['cistvaen', 'vesicant'], + 'cit': ['cit', 'tic'], + 'citadel': ['citadel', 'deltaic', 'dialect', 'edictal', 'lactide'], + 'citatory': ['atrocity', 'citatory'], + 'cite': ['ceti', 'cite', 'tice'], + 'citer': ['citer', 'recti', 'ticer', 'trice'], + 'cithara': ['cathari', 'chirata', 'cithara'], + 'citharist': ['citharist', 'trachitis'], + 'citharoedic': ['citharoedic', 'diachoretic'], + 'cither': ['cither', 'thrice'], + 'citied': ['citied', 'dietic'], + 'citizen': ['citizen', 'zincite'], + 'citral': ['citral', 'rictal'], + 'citramide': ['citramide', 'diametric', 'matricide'], + 'citrange': ['argentic', 'citrange'], + 'citrate': ['atretic', 'citrate'], + 'citrated': ['citrated', 'tetracid', 'tetradic'], + 'citrean': ['centiar', 'certain', 'citrean', 'nacrite', 'nectria'], + 'citrene': ['citrene', 'enteric', 'enticer', 'tercine'], + 'citreous': ['citreous', 'urticose'], + 'citric': ['citric', 'critic'], + 'citrin': ['citrin', 'nitric'], + 'citrination': ['citrination', 'intrication'], + 'citrine': ['citrine', 'crinite', 'inciter', 'neritic'], + 'citromyces': ['cirsectomy', 'citromyces'], + 'citron': ['citron', 'cortin', 'crotin'], + 'citronade': ['citronade', 'endaortic', 'redaction'], + 'citronella': ['citronella', 'interlocal'], + 'citrus': ['citrus', 'curtis', 'rictus', 'rustic'], + 'cive': ['cive', 'vice'], + 'civet': ['civet', 'evict'], + 'civetone': ['civetone', 'evection'], + 'civitan': ['activin', 'civitan'], + 'cixo': ['cixo', 'coix'], + 'clabber': ['cabbler', 'clabber'], + 'clacker': ['cackler', 'clacker', 'crackle'], + 'cladine': ['cladine', 'decalin', 'iceland'], + 'cladonia': ['cladonia', 'condalia', 'diaconal'], + 'cladophyll': ['cladophyll', 'phylloclad'], + 'claim': ['claim', 'clima', 'malic'], + 'claimant': ['calamint', 'claimant'], + 'claimer': ['claimer', 'miracle', 'reclaim'], + 'clairce': ['clairce', 'clarice'], + 'claire': ['carlie', 'claire', 'eclair', 'erical'], + 'claith': ['chital', 'claith'], + 'claiver': ['caliver', 'caviler', 'claiver', 'clavier', 'valeric', 'velaric'], + 'clam': ['calm', 'clam'], + 'clamant': ['calmant', 'clamant'], + 'clamative': ['calmative', 'clamative'], + 'clamatores': ['clamatores', 'scleromata'], + 'clamber': ['cambrel', 'clamber', 'cramble'], + 'clame': ['camel', 'clame', 'cleam', 'macle'], + 'clamer': ['calmer', 'carmel', 'clamer', 'marcel', 'mercal'], + 'clamor': ['clamor', 'colmar'], + 'clamorist': ['clamorist', 'crotalism'], + 'clangingly': ['clangingly', 'glancingly'], + 'clap': ['calp', 'clap'], + 'clapper': ['clapper', 'crapple'], + 'claquer': ['claquer', 'lacquer'], + 'clarain': ['carinal', 'carlina', 'clarain', 'cranial'], + 'clare': ['ceral', 'clare', 'clear', 'lacer'], + 'clarence': ['canceler', 'clarence', 'recancel'], + 'claret': ['carlet', 'cartel', 'claret', 'rectal', 'talcer'], + 'claretian': ['carnalite', 'claretian', 'lacertian', 'nectarial'], + 'clarice': ['clairce', 'clarice'], + 'clarin': ['carlin', 'clarin', 'crinal'], + 'clarinda': ['cardinal', 'clarinda'], + 'clarion': ['carolin', 'clarion', 'colarin', 'locrian'], + 'clarionet': ['alectrion', 'clarionet', 'crotaline', 'locarnite'], + 'clarist': ['carlist', 'clarist'], + 'claro': ['alcor', 'calor', 'carlo', 'carol', 'claro', 'coral'], + 'clary': ['acryl', 'caryl', 'clary'], + 'clasher': ['charles', 'clasher'], + 'clasp': ['clasp', 'scalp'], + 'clasper': ['clasper', 'reclasp', 'scalper'], + 'clasping': ['clasping', 'scalping'], + 'classed': ['classed', 'declass'], + 'classer': ['carless', 'classer', 'reclass'], + 'classism': ['classism', 'misclass'], + 'classwork': ['classwork', 'crosswalk'], + 'clat': ['clat', 'talc'], + 'clathrina': ['alchitran', 'clathrina'], + 'clathrose': ['clathrose', 'searcloth'], + 'clatterer': ['clatterer', 'craterlet'], + 'claude': ['caudle', 'cedula', 'claude'], + 'claudian': ['claudian', 'dulciana'], + 'claudicate': ['aciculated', 'claudicate'], + 'clause': ['caelus', 'caules', 'clause'], + 'claustral': ['claustral', 'lacustral'], + 'clava': ['caval', 'clava'], + 'clavacin': ['clavacin', 'vaccinal'], + 'clavaria': ['calvaria', 'clavaria'], + 'clave': ['calve', 'cavel', 'clave'], + 'claver': ['calver', 'carvel', 'claver'], + 'claviculate': ['calculative', 'claviculate'], + 'clavier': ['caliver', 'caviler', 'claiver', 'clavier', 'valeric', 'velaric'], + 'clavis': ['clavis', 'slavic'], + 'clay': ['acyl', 'clay', 'lacy'], + 'clayer': ['clayer', 'lacery'], + 'claytonia': ['acylation', 'claytonia'], + 'clead': ['clead', 'decal', 'laced'], + 'cleam': ['camel', 'clame', 'cleam', 'macle'], + 'cleamer': ['carmele', 'cleamer'], + 'clean': ['canel', 'clean', 'lance', 'lenca'], + 'cleaner': ['cleaner', 'reclean'], + 'cleanly': ['cleanly', 'lancely'], + 'cleanout': ['cleanout', 'outlance'], + 'cleanse': ['cleanse', 'scalene'], + 'cleanup': ['cleanup', 'unplace'], + 'clear': ['ceral', 'clare', 'clear', 'lacer'], + 'clearable': ['clearable', 'lacerable'], + 'clearer': ['clearer', 'reclear'], + 'cleat': ['cleat', 'eclat', 'ectal', 'lacet', 'tecla'], + 'clefted': ['clefted', 'deflect'], + 'cleistocarp': ['ceroplastic', 'cleistocarp', 'coreplastic'], + 'cleistogeny': ['cleistogeny', 'lysogenetic'], + 'cleoid': ['cleoid', 'coiled', 'docile'], + 'cleopatra': ['acropetal', 'cleopatra'], + 'cleric': ['circle', 'cleric'], + 'clericature': ['clericature', 'recirculate'], + 'clerkess': ['clerkess', 'reckless'], + 'clerking': ['clerking', 'reckling'], + 'clerus': ['clerus', 'cruels'], + 'clethra': ['clethra', 'latcher', 'ratchel', 'relatch', 'talcher', 'trachle'], + 'cleveite': ['cleveite', 'elective'], + 'cliche': ['chicle', 'cliche'], + 'clicker': ['clicker', 'crickle'], + 'clidastes': ['clidastes', 'discastle'], + 'clientage': ['clientage', 'genetical'], + 'cliented': ['cliented', 'denticle'], + 'cliftonia': ['cliftonia', 'fictional'], + 'clima': ['claim', 'clima', 'malic'], + 'climber': ['climber', 'reclimb'], + 'clime': ['clime', 'melic'], + 'cline': ['cinel', 'cline'], + 'clinger': ['clinger', 'cringle'], + 'clinia': ['anilic', 'clinia'], + 'clinicopathological': ['clinicopathological', 'pathologicoclinical'], + 'clinium': ['clinium', 'ulminic'], + 'clinker': ['clinker', 'crinkle'], + 'clinoclase': ['clinoclase', 'oscillance'], + 'clinoclasite': ['callisection', 'clinoclasite'], + 'clinodome': ['clinodome', 'melodicon', 'monocleid'], + 'clinology': ['clinology', 'coolingly'], + 'clinometer': ['clinometer', 'recoilment'], + 'clinospore': ['clinospore', 'necropolis'], + 'clio': ['clio', 'coil', 'coli', 'loci'], + 'cliona': ['alnico', 'cliona', 'oilcan'], + 'clione': ['clione', 'coelin', 'encoil', 'enolic'], + 'clipeus': ['clipeus', 'spicule'], + 'clipper': ['clipper', 'cripple'], + 'clipse': ['clipse', 'splice'], + 'clipsome': ['clipsome', 'polemics'], + 'clite': ['clite', 'telic'], + 'clites': ['celtis', 'clites'], + 'clitia': ['clitia', 'italic'], + 'clition': ['clition', 'nilotic'], + 'clitoria': ['clitoria', 'loricati'], + 'clitoridean': ['clitoridean', 'directional'], + 'clitoris': ['clitoris', 'coistril'], + 'clive': ['clive', 'velic'], + 'cloacinal': ['cloacinal', 'cocillana'], + 'cloam': ['cloam', 'comal'], + 'cloamen': ['aclemon', 'cloamen'], + 'clobber': ['clobber', 'cobbler'], + 'clochan': ['chalcon', 'clochan', 'conchal'], + 'clocked': ['clocked', 'cockled'], + 'clocker': ['clocker', 'cockler'], + 'clod': ['clod', 'cold'], + 'clodder': ['clodder', 'coddler'], + 'cloggy': ['cloggy', 'coggly'], + 'cloister': ['cloister', 'coistrel'], + 'cloisteral': ['cloisteral', 'sclerotial'], + 'cloit': ['cloit', 'lotic'], + 'clonicotonic': ['clonicotonic', 'tonicoclonic'], + 'clonus': ['clonus', 'consul'], + 'clop': ['clop', 'colp'], + 'close': ['close', 'socle'], + 'closer': ['closer', 'cresol', 'escrol'], + 'closter': ['closter', 'costrel'], + 'closterium': ['closterium', 'sclerotium'], + 'clot': ['clot', 'colt'], + 'clothier': ['chlorite', 'clothier'], + 'clotho': ['clotho', 'coolth'], + 'clotter': ['clotter', 'crottle'], + 'cloture': ['cloture', 'clouter'], + 'cloud': ['cloud', 'could'], + 'clouter': ['cloture', 'clouter'], + 'clovene': ['cevenol', 'clovene'], + 'clow': ['clow', 'cowl'], + 'cloy': ['cloy', 'coly'], + 'clue': ['clue', 'luce'], + 'clumse': ['clumse', 'muscle'], + 'clumsily': ['clumsily', 'scyllium'], + 'clumsy': ['clumsy', 'muscly'], + 'clunist': ['clunist', 'linctus'], + 'clupea': ['alecup', 'clupea'], + 'clupeine': ['clupeine', 'pulicene'], + 'clusia': ['caulis', 'clusia', 'sicula'], + 'clutch': ['clutch', 'cultch'], + 'clutter': ['clutter', 'cuttler'], + 'clyde': ['clyde', 'decyl'], + 'clyer': ['ceryl', 'clyer'], + 'clymenia': ['clymenia', 'mycelian'], + 'clypeolate': ['clypeolate', 'ptyalocele'], + 'clysis': ['clysis', 'lyssic'], + 'clysmic': ['clysmic', 'cyclism'], + 'cnemial': ['cnemial', 'melanic'], + 'cnemis': ['cnemis', 'mnesic'], + 'cneorum': ['cneorum', 'corneum'], + 'cnicus': ['cnicus', 'succin'], + 'cnida': ['canid', 'cnida', 'danic'], + 'cnidaria': ['acridian', 'cnidaria'], + 'cnidian': ['cnidian', 'indican'], + 'cnidophore': ['cnidophore', 'princehood'], + 'coach': ['chaco', 'choca', 'coach'], + 'coacher': ['caroche', 'coacher', 'recoach'], + 'coachlet': ['catechol', 'coachlet'], + 'coactor': ['coactor', 'tarocco'], + 'coadamite': ['acetamido', 'coadamite'], + 'coadnate': ['anecdota', 'coadnate'], + 'coadunite': ['coadunite', 'education', 'noctuidae'], + 'coagent': ['coagent', 'cognate'], + 'coagulate': ['catalogue', 'coagulate'], + 'coaita': ['atocia', 'coaita'], + 'coal': ['alco', 'coal', 'cola', 'loca'], + 'coalbin': ['cibolan', 'coalbin'], + 'coaler': ['carole', 'coaler', 'coelar', 'oracle', 'recoal'], + 'coalite': ['aloetic', 'coalite'], + 'coalition': ['coalition', 'lociation'], + 'coalitionist': ['coalitionist', 'solicitation'], + 'coalizer': ['calorize', 'coalizer'], + 'coalpit': ['capitol', 'coalpit', 'optical', 'topical'], + 'coaltitude': ['coaltitude', 'colatitude'], + 'coaming': ['coaming', 'macigno'], + 'coan': ['coan', 'onca'], + 'coapt': ['capot', 'coapt'], + 'coarb': ['carbo', 'carob', 'coarb', 'cobra'], + 'coarrange': ['arrogance', 'coarrange'], + 'coarse': ['acrose', 'coarse'], + 'coarsen': ['carnose', 'coarsen', 'narcose'], + 'coassert': ['castores', 'coassert'], + 'coast': ['ascot', 'coast', 'costa', 'tacso', 'tasco'], + 'coastal': ['coastal', 'salacot'], + 'coaster': ['coaster', 'recoast'], + 'coasting': ['agnostic', 'coasting'], + 'coated': ['coated', 'decoat'], + 'coater': ['coater', 'recoat'], + 'coating': ['coating', 'cotinga'], + 'coatroom': ['coatroom', 'morocota'], + 'coax': ['coax', 'coxa'], + 'cobbler': ['clobber', 'cobbler'], + 'cobia': ['baioc', 'cabio', 'cobia'], + 'cobiron': ['boronic', 'cobiron'], + 'cobitis': ['biotics', 'cobitis'], + 'cobra': ['carbo', 'carob', 'coarb', 'cobra'], + 'cocaine': ['cocaine', 'oceanic'], + 'cocainist': ['cocainist', 'siccation'], + 'cocama': ['cocama', 'macaco'], + 'cocamine': ['cocamine', 'comacine'], + 'cochleare': ['archocele', 'cochleare'], + 'cochleitis': ['cochleitis', 'ochlesitic'], + 'cocillana': ['cloacinal', 'cocillana'], + 'cocker': ['cocker', 'recock'], + 'cockily': ['cockily', 'colicky'], + 'cockled': ['clocked', 'cockled'], + 'cockler': ['clocker', 'cockler'], + 'cockup': ['cockup', 'upcock'], + 'cocreditor': ['cocreditor', 'codirector'], + 'cocurrent': ['cocurrent', 'occurrent', 'uncorrect'], + 'cod': ['cod', 'doc'], + 'codamine': ['codamine', 'comedian', 'daemonic', 'demoniac'], + 'codder': ['codder', 'corded'], + 'coddler': ['clodder', 'coddler'], + 'code': ['code', 'coed'], + 'codebtor': ['bedoctor', 'codebtor'], + 'coder': ['coder', 'cored', 'credo'], + 'coderive': ['coderive', 'divorcee'], + 'codicil': ['codicil', 'dicolic'], + 'codille': ['celloid', 'codille', 'collide', 'collied'], + 'codirector': ['cocreditor', 'codirector'], + 'codium': ['codium', 'mucoid'], + 'coed': ['code', 'coed'], + 'coeditorship': ['cheiropodist', 'coeditorship'], + 'coelar': ['carole', 'coaler', 'coelar', 'oracle', 'recoal'], + 'coelata': ['alcoate', 'coelata'], + 'coelection': ['coelection', 'entocoelic'], + 'coelia': ['aeolic', 'coelia'], + 'coelin': ['clione', 'coelin', 'encoil', 'enolic'], + 'coeline': ['cineole', 'coeline'], + 'coelogyne': ['coelogyne', 'gonyocele'], + 'coenactor': ['coenactor', 'croconate'], + 'coenobiar': ['borocaine', 'coenobiar'], + 'coenurus': ['cernuous', 'coenurus'], + 'coestate': ['coestate', 'ecostate'], + 'coeternal': ['antrocele', 'coeternal', 'tolerance'], + 'coeval': ['alcove', 'coeval', 'volcae'], + 'cofaster': ['cofaster', 'forecast'], + 'coferment': ['coferment', 'forcement'], + 'cogeneric': ['cogeneric', 'concierge'], + 'coggly': ['cloggy', 'coggly'], + 'cognate': ['coagent', 'cognate'], + 'cognatical': ['cognatical', 'galactonic'], + 'cognation': ['cognation', 'contagion'], + 'cognition': ['cognition', 'incognito'], + 'cognominal': ['cognominal', 'gnomonical'], + 'cogon': ['cogon', 'congo'], + 'cograil': ['argolic', 'cograil'], + 'coheir': ['coheir', 'heroic'], + 'cohen': ['cohen', 'enoch'], + 'cohere': ['choree', 'cohere', 'echoer'], + 'cohol': ['cholo', 'cohol'], + 'cohune': ['cohune', 'hounce'], + 'coif': ['coif', 'fico', 'foci'], + 'coign': ['coign', 'incog'], + 'coil': ['clio', 'coil', 'coli', 'loci'], + 'coiled': ['cleoid', 'coiled', 'docile'], + 'coiler': ['coiler', 'recoil'], + 'coin': ['cion', 'coin', 'icon'], + 'coinclude': ['coinclude', 'undecolic'], + 'coiner': ['cerion', 'coiner', 'neroic', 'orcein', 'recoin'], + 'coinfer': ['coinfer', 'conifer'], + 'coinherence': ['coinherence', 'incoherence'], + 'coinherent': ['coinherent', 'incoherent'], + 'coinitial': ['ciliation', 'coinitial'], + 'coinmate': ['coinmate', 'maconite'], + 'coinspire': ['coinspire', 'precision'], + 'coinsure': ['coinsure', 'corineus', 'cusinero'], + 'cointer': ['cerotin', 'cointer', 'cotrine', 'cretion', 'noticer', 'rection'], + 'cointreau': ['cautioner', 'cointreau'], + 'coistrel': ['cloister', 'coistrel'], + 'coistril': ['clitoris', 'coistril'], + 'coix': ['cixo', 'coix'], + 'coker': ['coker', 'corke', 'korec'], + 'coky': ['coky', 'yock'], + 'cola': ['alco', 'coal', 'cola', 'loca'], + 'colalgia': ['alogical', 'colalgia'], + 'colan': ['colan', 'conal'], + 'colane': ['canelo', 'colane'], + 'colarin': ['carolin', 'clarion', 'colarin', 'locrian'], + 'colate': ['acetol', 'colate', 'locate'], + 'colation': ['colation', 'coontail', 'location'], + 'colatitude': ['coaltitude', 'colatitude'], + 'colchian': ['cholanic', 'colchian'], + 'colcine': ['colcine', 'concile', 'conicle'], + 'colcothar': ['colcothar', 'ochlocrat'], + 'cold': ['clod', 'cold'], + 'colder': ['cedrol', 'colder', 'cordel'], + 'colectomy': ['colectomy', 'cyclotome'], + 'colemanite': ['colemanite', 'melaconite'], + 'coleur': ['coleur', 'colure'], + 'coleus': ['coleus', 'oscule'], + 'coli': ['clio', 'coil', 'coli', 'loci'], + 'colias': ['colias', 'scolia', 'social'], + 'colicky': ['cockily', 'colicky'], + 'colima': ['colima', 'olamic'], + 'colin': ['colin', 'nicol'], + 'colinear': ['acrolein', + 'arecolin', + 'caroline', + 'colinear', + 'cornelia', + 'creolian', + 'lonicera'], + 'colitis': ['colitis', 'solicit'], + 'colk': ['colk', 'lock'], + 'colla': ['callo', 'colla', 'local'], + 'collage': ['alcogel', 'collage'], + 'collare': ['collare', 'corella', 'ocellar'], + 'collaret': ['collaret', 'corallet'], + 'collarino': ['collarino', 'coronilla'], + 'collatee': ['collatee', 'ocellate'], + 'collationer': ['collationer', 'recollation'], + 'collectioner': ['collectioner', 'recollection'], + 'collegial': ['collegial', 'gallicole'], + 'collegian': ['allogenic', 'collegian'], + 'colleri': ['cerillo', 'colleri', 'collier'], + 'colleter': ['colleter', 'coteller', 'coterell', 'recollet'], + 'colletia': ['colletia', 'teocalli'], + 'collide': ['celloid', 'codille', 'collide', 'collied'], + 'collidine': ['celloidin', 'collidine', 'decillion'], + 'collie': ['collie', 'ocelli'], + 'collied': ['celloid', 'codille', 'collide', 'collied'], + 'collier': ['cerillo', 'colleri', 'collier'], + 'colligate': ['colligate', 'cotillage'], + 'colline': ['colline', 'lioncel'], + 'collinear': ['collinear', 'coralline'], + 'collinsia': ['collinsia', 'isoclinal'], + 'collotypy': ['collotypy', 'polycotyl'], + 'collusive': ['collusive', 'colluvies'], + 'colluvies': ['collusive', 'colluvies'], + 'collyba': ['callboy', 'collyba'], + 'colmar': ['clamor', 'colmar'], + 'colobus': ['colobus', 'subcool'], + 'coloenteritis': ['coloenteritis', 'enterocolitis'], + 'colombian': ['colombian', 'colombina'], + 'colombina': ['colombian', 'colombina'], + 'colonalgia': ['colonalgia', 'naological'], + 'colonate': ['colonate', 'ecotonal'], + 'colonialist': ['colonialist', 'oscillation'], + 'coloproctitis': ['coloproctitis', 'proctocolitis'], + 'color': ['color', 'corol', 'crool'], + 'colored': ['colored', 'croodle', 'decolor'], + 'colorer': ['colorer', 'recolor'], + 'colorin': ['colorin', 'orcinol'], + 'colorman': ['colorman', 'conormal'], + 'colp': ['clop', 'colp'], + 'colpeurynter': ['colpeurynter', 'counterreply'], + 'colpitis': ['colpitis', 'politics', 'psilotic'], + 'colporrhagia': ['colporrhagia', 'orographical'], + 'colt': ['clot', 'colt'], + 'colter': ['colter', 'lector', 'torcel'], + 'coltskin': ['coltskin', 'linstock'], + 'colubrina': ['binocular', 'caliburno', 'colubrina'], + 'columbo': ['columbo', 'coulomb'], + 'columbotitanate': ['columbotitanate', 'titanocolumbate'], + 'columnated': ['columnated', 'documental'], + 'columned': ['columned', 'uncledom'], + 'colunar': ['colunar', 'cornual', 'courlan'], + 'colure': ['coleur', 'colure'], + 'colutea': ['caulote', 'colutea', 'oculate'], + 'coly': ['cloy', 'coly'], + 'coma': ['coma', 'maco'], + 'comacine': ['cocamine', 'comacine'], + 'comal': ['cloam', 'comal'], + 'coman': ['coman', 'macon', 'manoc'], + 'comart': ['carmot', 'comart'], + 'comate': ['comate', 'metoac', 'tecoma'], + 'combat': ['combat', 'tombac'], + 'comber': ['comber', 'recomb'], + 'combinedly': ['combinedly', 'molybdenic'], + 'comedial': ['cameloid', 'comedial', 'melodica'], + 'comedian': ['codamine', 'comedian', 'daemonic', 'demoniac'], + 'comediant': ['comediant', 'metaconid'], + 'comedist': ['comedist', 'demotics', 'docetism', 'domestic'], + 'comedown': ['comedown', 'downcome'], + 'comeliness': ['comeliness', 'incomeless'], + 'comeling': ['comeling', 'comingle'], + 'comenic': ['comenic', 'encomic', 'meconic'], + 'comer': ['comer', 'crome'], + 'comforter': ['comforter', 'recomfort'], + 'comid': ['comid', 'domic'], + 'coming': ['coming', 'gnomic'], + 'comingle': ['comeling', 'comingle'], + 'comintern': ['comintern', 'nonmetric'], + 'comitia': ['caimito', 'comitia'], + 'comitragedy': ['comitragedy', 'tragicomedy'], + 'comity': ['comity', 'myotic'], + 'commander': ['commander', 'recommand'], + 'commation': ['commation', 'monatomic'], + 'commelina': ['commelina', 'melomanic'], + 'commender': ['commender', 'recommend'], + 'commentarial': ['commentarial', 'manometrical'], + 'commination': ['commination', 'monamniotic'], + 'commissioner': ['commissioner', 'recommission'], + 'commonality': ['ammonolytic', 'commonality'], + 'commorient': ['commorient', 'metronomic', 'monometric'], + 'compact': ['accompt', 'compact'], + 'compacter': ['compacter', 'recompact'], + 'company': ['company', 'copyman'], + 'compare': ['compare', 'compear'], + 'comparition': ['comparition', 'proamniotic'], + 'compasser': ['compasser', 'recompass'], + 'compear': ['compare', 'compear'], + 'compenetrate': ['compenetrate', 'contemperate'], + 'competitioner': ['competitioner', 'recompetition'], + 'compile': ['compile', 'polemic'], + 'compiler': ['compiler', 'complier'], + 'complainer': ['complainer', 'procnemial', 'recomplain'], + 'complaint': ['complaint', 'compliant'], + 'complanate': ['complanate', 'placentoma'], + 'compliant': ['complaint', 'compliant'], + 'complier': ['compiler', 'complier'], + 'compounder': ['compounder', 'recompound'], + 'comprehender': ['comprehender', 'recomprehend'], + 'compressed': ['compressed', 'decompress'], + 'comprise': ['comprise', 'perosmic'], + 'compromission': ['compromission', 'procommission'], + 'conal': ['colan', 'conal'], + 'conamed': ['conamed', 'macedon'], + 'conant': ['cannot', 'canton', 'conant', 'nonact'], + 'conarial': ['carolina', 'conarial'], + 'conarium': ['conarium', 'coumarin'], + 'conative': ['conative', 'invocate'], + 'concealer': ['concealer', 'reconceal'], + 'concent': ['concent', 'connect'], + 'concenter': ['concenter', 'reconnect'], + 'concentive': ['concentive', 'connective'], + 'concertize': ['concertize', 'concretize'], + 'concessioner': ['concessioner', 'reconcession'], + 'concha': ['chanco', 'concha'], + 'conchal': ['chalcon', 'clochan', 'conchal'], + 'conchinine': ['cinchonine', 'conchinine'], + 'concierge': ['cogeneric', 'concierge'], + 'concile': ['colcine', 'concile', 'conicle'], + 'concocter': ['concocter', 'reconcoct'], + 'concreter': ['concreter', 'reconcert'], + 'concretize': ['concertize', 'concretize'], + 'concretor': ['concretor', 'conrector'], + 'condalia': ['cladonia', 'condalia', 'diaconal'], + 'condemner': ['condemner', 'recondemn'], + 'condite': ['condite', 'ctenoid'], + 'conditioner': ['conditioner', 'recondition'], + 'condor': ['condor', 'cordon'], + 'conduit': ['conduit', 'duction', 'noctuid'], + 'condylomatous': ['condylomatous', 'monodactylous'], + 'cone': ['cone', 'once'], + 'conepate': ['conepate', 'tepecano'], + 'coner': ['coner', 'crone', 'recon'], + 'cones': ['cones', 'scone'], + 'confesser': ['confesser', 'reconfess'], + 'configurationism': ['configurationism', 'misconfiguration'], + 'confirmer': ['confirmer', 'reconfirm'], + 'confirmor': ['confirmor', 'corniform'], + 'conflate': ['conflate', 'falconet'], + 'conformer': ['conformer', 'reconform'], + 'confounder': ['confounder', 'reconfound'], + 'confrere': ['confrere', 'enforcer', 'reconfer'], + 'confronter': ['confronter', 'reconfront'], + 'congealer': ['congealer', 'recongeal'], + 'congeneric': ['congeneric', 'necrogenic'], + 'congenerous': ['congenerous', 'necrogenous'], + 'congenial': ['congenial', 'goclenian'], + 'congo': ['cogon', 'congo'], + 'congreet': ['congreet', 'coregent'], + 'congreve': ['congreve', 'converge'], + 'conical': ['conical', 'laconic'], + 'conicine': ['ciconine', 'conicine'], + 'conicle': ['colcine', 'concile', 'conicle'], + 'conicoid': ['conicoid', 'conoidic'], + 'conidium': ['conidium', 'mucinoid', 'oncidium'], + 'conifer': ['coinfer', 'conifer'], + 'conima': ['camion', 'conima', 'manioc', 'monica'], + 'conin': ['conin', 'nonic', 'oncin'], + 'conine': ['conine', 'connie', 'ennoic'], + 'conjoiner': ['conjoiner', 'reconjoin'], + 'conk': ['conk', 'nock'], + 'conker': ['conker', 'reckon'], + 'conkers': ['conkers', 'snocker'], + 'connarite': ['connarite', 'container', 'cotarnine', 'crenation', 'narcotine'], + 'connatal': ['cantonal', 'connatal'], + 'connation': ['connation', 'nonaction'], + 'connature': ['antecornu', 'connature'], + 'connect': ['concent', 'connect'], + 'connectival': ['connectival', 'conventical'], + 'connective': ['concentive', 'connective'], + 'connie': ['conine', 'connie', 'ennoic'], + 'connoissance': ['connoissance', 'nonaccession'], + 'conoidal': ['conoidal', 'dolciano'], + 'conoidic': ['conicoid', 'conoidic'], + 'conor': ['conor', 'croon', 'ronco'], + 'conormal': ['colorman', 'conormal'], + 'conoy': ['conoy', 'coony'], + 'conrad': ['candor', 'cardon', 'conrad'], + 'conrector': ['concretor', 'conrector'], + 'conred': ['cedron', 'conred'], + 'conringia': ['conringia', 'inorganic'], + 'consenter': ['consenter', 'nonsecret', 'reconsent'], + 'conservable': ['conservable', 'conversable'], + 'conservancy': ['conservancy', 'conversancy'], + 'conservant': ['conservant', 'conversant'], + 'conservation': ['conservation', 'conversation'], + 'conservational': ['conservational', 'conversational'], + 'conservationist': ['conservationist', 'conversationist'], + 'conservative': ['conservative', 'conversative'], + 'conserve': ['conserve', 'converse'], + 'conserver': ['conserver', 'converser'], + 'considerate': ['considerate', 'desecration'], + 'considerative': ['considerative', 'devisceration'], + 'considered': ['considered', 'deconsider'], + 'considerer': ['considerer', 'reconsider'], + 'consigner': ['consigner', 'reconsign'], + 'conspiracy': ['conspiracy', 'snipocracy'], + 'conspire': ['conspire', 'incorpse'], + 'constate': ['catstone', 'constate'], + 'constitutionalism': ['constitutionalism', 'misconstitutional'], + 'constitutioner': ['constitutioner', 'reconstitution'], + 'constrain': ['constrain', 'transonic'], + 'constructer': ['constructer', 'reconstruct'], + 'constructionism': ['constructionism', 'misconstruction'], + 'consul': ['clonus', 'consul'], + 'consulage': ['consulage', 'glucosane'], + 'consulary': ['consulary', 'cynosural'], + 'consulter': ['consulter', 'reconsult'], + 'consume': ['consume', 'muscone'], + 'consumer': ['consumer', 'mucrones'], + 'consute': ['consute', 'contuse'], + 'contagion': ['cognation', 'contagion'], + 'contain': ['actinon', 'cantion', 'contain'], + 'container': ['connarite', 'container', 'cotarnine', 'crenation', 'narcotine'], + 'conte': ['cento', 'conte', 'tecon'], + 'contemperate': ['compenetrate', 'contemperate'], + 'contender': ['contender', 'recontend'], + 'conter': ['conter', 'cornet', 'cronet', 'roncet'], + 'conterminal': ['centinormal', 'conterminal', 'nonmetrical'], + 'contester': ['contester', 'recontest'], + 'continual': ['continual', 'inoculant', 'unctional'], + 'continued': ['continued', 'unnoticed'], + 'continuer': ['centurion', 'continuer', 'cornutine'], + 'contise': ['contise', 'noetics', 'section'], + 'contline': ['contline', 'nonlicet'], + 'contortae': ['contortae', 'crotonate'], + 'contour': ['contour', 'cornuto', 'countor', 'crouton'], + 'contra': ['cantor', 'carton', 'contra'], + 'contracter': ['contracter', 'correctant', 'recontract'], + 'contrapose': ['antroscope', 'contrapose'], + 'contravene': ['contravene', 'covenanter'], + 'contrite': ['contrite', 'tetronic'], + 'contrive': ['contrive', 'invector'], + 'conturbation': ['conturbation', 'obtruncation'], + 'contuse': ['consute', 'contuse'], + 'conure': ['conure', 'rounce', 'uncore'], + 'conventical': ['connectival', 'conventical'], + 'conventioner': ['conventioner', 'reconvention'], + 'converge': ['congreve', 'converge'], + 'conversable': ['conservable', 'conversable'], + 'conversancy': ['conservancy', 'conversancy'], + 'conversant': ['conservant', 'conversant'], + 'conversation': ['conservation', 'conversation'], + 'conversational': ['conservational', 'conversational'], + 'conversationist': ['conservationist', 'conversationist'], + 'conversative': ['conservative', 'conversative'], + 'converse': ['conserve', 'converse'], + 'converser': ['conserver', 'converser'], + 'converter': ['converter', 'reconvert'], + 'convertise': ['convertise', 'ventricose'], + 'conveyer': ['conveyer', 'reconvey'], + 'conycatcher': ['conycatcher', 'technocracy'], + 'conyrine': ['conyrine', 'corynine'], + 'cooker': ['cooker', 'recook'], + 'cool': ['cool', 'loco'], + 'coolant': ['coolant', 'octonal'], + 'cooler': ['cooler', 'recool'], + 'coolingly': ['clinology', 'coolingly'], + 'coolth': ['clotho', 'coolth'], + 'coolweed': ['coolweed', 'locoweed'], + 'cooly': ['cooly', 'coyol'], + 'coonroot': ['coonroot', 'octoroon'], + 'coontail': ['colation', 'coontail', 'location'], + 'coony': ['conoy', 'coony'], + 'coop': ['coop', 'poco'], + 'coos': ['coos', 'soco'], + 'coost': ['coost', 'scoot'], + 'coot': ['coot', 'coto', 'toco'], + 'copa': ['copa', 'paco'], + 'copable': ['copable', 'placebo'], + 'copalite': ['copalite', 'poetical'], + 'coparent': ['coparent', 'portance'], + 'copart': ['captor', 'copart'], + 'copartner': ['copartner', 'procreant'], + 'copatain': ['copatain', 'pacation'], + 'copehan': ['copehan', 'panoche', 'phocean'], + 'copen': ['copen', 'ponce'], + 'coperta': ['coperta', 'pectora', 'porcate'], + 'copied': ['copied', 'epodic'], + 'copis': ['copis', 'pisco'], + 'copist': ['copist', 'coptis', 'optics', 'postic'], + 'copita': ['atopic', 'capito', 'copita'], + 'coplanar': ['coplanar', 'procanal'], + 'copleased': ['copleased', 'escaloped'], + 'copperer': ['copperer', 'recopper'], + 'coppery': ['coppery', 'precopy'], + 'copr': ['copr', 'corp', 'crop'], + 'coprinae': ['caponier', 'coprinae', 'procaine'], + 'coprinus': ['coprinus', 'poncirus'], + 'coprolagnia': ['carpogonial', 'coprolagnia'], + 'coprophagist': ['coprophagist', 'topographics'], + 'coprose': ['coprose', 'scooper'], + 'copse': ['copse', 'pecos', 'scope'], + 'copter': ['ceptor', 'copter'], + 'coptis': ['copist', 'coptis', 'optics', 'postic'], + 'copula': ['copula', 'cupola'], + 'copular': ['copular', 'croupal', 'cupolar', 'porcula'], + 'copulate': ['copulate', 'outplace'], + 'copulation': ['copulation', 'poculation'], + 'copus': ['copus', 'scoup'], + 'copyman': ['company', 'copyman'], + 'copyrighter': ['copyrighter', 'recopyright'], + 'coque': ['coque', 'ocque'], + 'coquitlam': ['coquitlam', 'quamoclit'], + 'cor': ['cor', 'cro', 'orc', 'roc'], + 'cora': ['acor', 'caro', 'cora', 'orca'], + 'coraciae': ['coraciae', 'icacorea'], + 'coracial': ['caracoli', 'coracial'], + 'coracias': ['coracias', 'rascacio'], + 'coradicate': ['coradicate', 'ectocardia'], + 'corah': ['achor', 'chora', 'corah', 'orach', 'roach'], + 'coraise': ['coraise', 'scoriae'], + 'coral': ['alcor', 'calor', 'carlo', 'carol', 'claro', 'coral'], + 'coraled': ['acerdol', 'coraled'], + 'coralist': ['calorist', 'coralist'], + 'corallet': ['collaret', 'corallet'], + 'corallian': ['corallian', 'corallina'], + 'corallina': ['corallian', 'corallina'], + 'coralline': ['collinear', 'coralline'], + 'corallite': ['corallite', 'lectorial'], + 'coram': ['carom', 'coram', 'macro', 'marco'], + 'coranto': ['cartoon', 'coranto'], + 'corban': ['bracon', 'carbon', 'corban'], + 'corbeil': ['bricole', 'corbeil', 'orbicle'], + 'cordaitean': ['arctoidean', 'carotidean', 'cordaitean', 'dinocerata'], + 'cordate': ['cordate', 'decator', 'redcoat'], + 'corded': ['codder', 'corded'], + 'cordel': ['cedrol', 'colder', 'cordel'], + 'corder': ['corder', 'record'], + 'cordia': ['caroid', 'cordia'], + 'cordial': ['cordial', 'dorical'], + 'cordicole': ['cordicole', 'crocodile'], + 'cordierite': ['cordierite', 'directoire'], + 'cordoba': ['bocardo', 'cordoba'], + 'cordon': ['condor', 'cordon'], + 'core': ['cero', 'core'], + 'cored': ['coder', 'cored', 'credo'], + 'coregent': ['congreet', 'coregent'], + 'coreless': ['coreless', 'sclerose'], + 'corella': ['collare', 'corella', 'ocellar'], + 'corema': ['ceroma', 'corema'], + 'coreplastic': ['ceroplastic', 'cleistocarp', 'coreplastic'], + 'coreplasty': ['ceroplasty', 'coreplasty'], + 'corer': ['corer', 'crore'], + 'coresidual': ['coresidual', 'radiculose'], + 'coresign': ['coresign', 'cosigner'], + 'corge': ['corge', 'gorce'], + 'corgi': ['corgi', 'goric', 'orgic'], + 'corial': ['caroli', 'corial', 'lorica'], + 'coriamyrtin': ['coriamyrtin', 'criminatory'], + 'corin': ['corin', 'noric', 'orcin'], + 'corindon': ['corindon', 'nodicorn'], + 'corineus': ['coinsure', 'corineus', 'cusinero'], + 'corinna': ['corinna', 'cronian'], + 'corinne': ['corinne', 'cornein', 'neronic'], + 'cork': ['cork', 'rock'], + 'corke': ['coker', 'corke', 'korec'], + 'corked': ['corked', 'docker', 'redock'], + 'corker': ['corker', 'recork', 'rocker'], + 'corkiness': ['corkiness', 'rockiness'], + 'corking': ['corking', 'rocking'], + 'corkish': ['corkish', 'rockish'], + 'corkwood': ['corkwood', 'rockwood', 'woodrock'], + 'corky': ['corky', 'rocky'], + 'corm': ['corm', 'crom'], + 'cormophyte': ['chromotype', 'cormophyte', 'ectomorphy'], + 'cormophytic': ['chromotypic', 'cormophytic', 'mycotrophic'], + 'cornage': ['acrogen', 'cornage'], + 'cornea': ['carone', 'cornea'], + 'corneal': ['carneol', 'corneal'], + 'cornein': ['corinne', 'cornein', 'neronic'], + 'cornelia': ['acrolein', + 'arecolin', + 'caroline', + 'colinear', + 'cornelia', + 'creolian', + 'lonicera'], + 'cornelius': ['cornelius', 'inclosure', 'reclusion'], + 'corneocalcareous': ['calcareocorneous', 'corneocalcareous'], + 'cornet': ['conter', 'cornet', 'cronet', 'roncet'], + 'corneum': ['cneorum', 'corneum'], + 'cornic': ['cornic', 'crocin'], + 'cornice': ['cornice', 'crocein'], + 'corniform': ['confirmor', 'corniform'], + 'cornin': ['cornin', 'rincon'], + 'cornish': ['cornish', 'cronish', 'sorchin'], + 'cornual': ['colunar', 'cornual', 'courlan'], + 'cornuate': ['cornuate', 'courante', 'cuneator', 'outrance'], + 'cornuated': ['cornuated', 'undercoat'], + 'cornucopiate': ['cornucopiate', 'reoccupation'], + 'cornulites': ['cornulites', 'uncloister'], + 'cornute': ['cornute', 'counter', 'recount', 'trounce'], + 'cornutine': ['centurion', 'continuer', 'cornutine'], + 'cornuto': ['contour', 'cornuto', 'countor', 'crouton'], + 'corny': ['corny', 'crony'], + 'corol': ['color', 'corol', 'crool'], + 'corollated': ['corollated', 'decollator'], + 'corona': ['caroon', 'corona', 'racoon'], + 'coronad': ['cardoon', 'coronad'], + 'coronadite': ['carotenoid', 'coronadite', 'decoration'], + 'coronal': ['coronal', 'locarno'], + 'coronaled': ['canoodler', 'coronaled'], + 'coronate': ['coronate', 'octonare', 'otocrane'], + 'coronated': ['coronated', 'creodonta'], + 'coroner': ['coroner', 'crooner', 'recroon'], + 'coronilla': ['collarino', 'coronilla'], + 'corp': ['copr', 'corp', 'crop'], + 'corporealist': ['corporealist', 'prosectorial'], + 'corradiate': ['corradiate', 'cortaderia', 'eradicator'], + 'correal': ['caroler', 'correal'], + 'correctant': ['contracter', 'correctant', 'recontract'], + 'correctioner': ['correctioner', 'recorrection'], + 'corrente': ['corrente', 'terceron'], + 'correption': ['correption', 'porrection'], + 'corrodentia': ['corrodentia', 'recordation'], + 'corrupter': ['corrupter', 'recorrupt'], + 'corsage': ['corsage', 'socager'], + 'corsaint': ['cantoris', 'castorin', 'corsaint'], + 'corse': ['corse', 'score'], + 'corselet': ['corselet', 'sclerote', 'selector'], + 'corset': ['corset', 'cortes', 'coster', 'escort', 'scoter', 'sector'], + 'corta': ['actor', 'corta', 'croat', 'rocta', 'taroc', 'troca'], + 'cortaderia': ['corradiate', 'cortaderia', 'eradicator'], + 'cortes': ['corset', 'cortes', 'coster', 'escort', 'scoter', 'sector'], + 'cortical': ['cortical', 'crotalic'], + 'cortices': ['cortices', 'cresotic'], + 'corticose': ['corticose', 'creosotic'], + 'cortin': ['citron', 'cortin', 'crotin'], + 'cortina': ['anticor', 'carotin', 'cortina', 'ontaric'], + 'cortinate': ['carnotite', 'cortinate'], + 'cortisone': ['certosino', 'cortisone', 'socotrine'], + 'corton': ['corton', 'croton'], + 'corvinae': ['corvinae', 'veronica'], + 'cory': ['cory', 'croy'], + 'corycavidine': ['cervicodynia', 'corycavidine'], + 'corydon': ['corydon', 'croydon'], + 'corynine': ['conyrine', 'corynine'], + 'corypha': ['charpoy', 'corypha'], + 'coryphene': ['coryphene', 'hypercone'], + 'cos': ['cos', 'osc', 'soc'], + 'cosalite': ['cosalite', 'societal'], + 'coset': ['coset', 'estoc', 'scote'], + 'cosh': ['cosh', 'scho'], + 'cosharer': ['cosharer', 'horsecar'], + 'cosigner': ['coresign', 'cosigner'], + 'cosine': ['cosine', 'oscine'], + 'cosmati': ['atomics', 'catoism', 'cosmati', 'osmatic', 'somatic'], + 'cosmetical': ['cacomistle', 'cosmetical'], + 'cosmetician': ['cosmetician', 'encomiastic'], + 'cosmist': ['cosmist', 'scotism'], + 'cossack': ['cassock', 'cossack'], + 'cosse': ['cosse', 'secos'], + 'cost': ['cost', 'scot'], + 'costa': ['ascot', 'coast', 'costa', 'tacso', 'tasco'], + 'costar': ['arctos', 'castor', 'costar', 'scrota'], + 'costean': ['costean', 'tsoneca'], + 'coster': ['corset', 'cortes', 'coster', 'escort', 'scoter', 'sector'], + 'costing': ['costing', 'gnostic'], + 'costispinal': ['costispinal', 'pansciolist'], + 'costmary': ['arctomys', 'costmary', 'mascotry'], + 'costochondral': ['chondrocostal', 'costochondral'], + 'costosternal': ['costosternal', 'sternocostal'], + 'costovertebral': ['costovertebral', 'vertebrocostal'], + 'costrel': ['closter', 'costrel'], + 'costula': ['costula', 'locusta', 'talcous'], + 'costumer': ['costumer', 'customer'], + 'cosurety': ['cosurety', 'courtesy'], + 'cosustain': ['cosustain', 'scusation'], + 'cotarius': ['cotarius', 'octarius', 'suctoria'], + 'cotarnine': ['connarite', 'container', 'cotarnine', 'crenation', 'narcotine'], + 'cote': ['cote', 'teco'], + 'coteline': ['coteline', 'election'], + 'coteller': ['colleter', 'coteller', 'coterell', 'recollet'], + 'coterell': ['colleter', 'coteller', 'coterell', 'recollet'], + 'cotesian': ['canoeist', 'cotesian'], + 'coth': ['coth', 'ocht'], + 'cotidal': ['cotidal', 'lactoid', 'talcoid'], + 'cotillage': ['colligate', 'cotillage'], + 'cotillion': ['cotillion', 'octillion'], + 'cotinga': ['coating', 'cotinga'], + 'cotinus': ['cotinus', 'suction', 'unstoic'], + 'cotise': ['cotise', 'oecist'], + 'coto': ['coot', 'coto', 'toco'], + 'cotranspire': ['cotranspire', 'pornerastic'], + 'cotrine': ['cerotin', 'cointer', 'cotrine', 'cretion', 'noticer', 'rection'], + 'cotripper': ['cotripper', 'periproct'], + 'cotte': ['cotte', 'octet'], + 'cotylosaur': ['cotylosaur', 'osculatory'], + 'cotype': ['cotype', 'ectopy'], + 'coude': ['coude', 'douce'], + 'could': ['cloud', 'could'], + 'coulisse': ['coulisse', 'leucosis', 'ossicule'], + 'coulomb': ['columbo', 'coulomb'], + 'coumalic': ['caulomic', 'coumalic'], + 'coumarin': ['conarium', 'coumarin'], + 'counsel': ['counsel', 'unclose'], + 'counter': ['cornute', 'counter', 'recount', 'trounce'], + 'counteracter': ['counteracter', 'countercarte'], + 'countercarte': ['counteracter', 'countercarte'], + 'countercharm': ['countercharm', 'countermarch'], + 'counterguard': ['counterguard', 'uncorrugated'], + 'counteridea': ['counteridea', 'decurionate'], + 'countermarch': ['countercharm', 'countermarch'], + 'counterpaled': ['counterpaled', 'counterplead', 'unpercolated'], + 'counterpaly': ['counterpaly', 'counterplay'], + 'counterplay': ['counterpaly', 'counterplay'], + 'counterplead': ['counterpaled', 'counterplead', 'unpercolated'], + 'counterreply': ['colpeurynter', 'counterreply'], + 'countersale': ['countersale', 'counterseal'], + 'countersea': ['countersea', 'nectareous'], + 'counterseal': ['countersale', 'counterseal'], + 'countershade': ['countershade', 'decantherous'], + 'counterstand': ['counterstand', 'uncontrasted'], + 'countertail': ['countertail', 'reluctation'], + 'countertrades': ['countertrades', 'unstercorated'], + 'countervail': ['countervail', 'involucrate'], + 'countervair': ['countervair', 'overcurtain', 'recurvation'], + 'countor': ['contour', 'cornuto', 'countor', 'crouton'], + 'coupe': ['coupe', 'pouce'], + 'couper': ['couper', 'croupe', 'poucer', 'recoup'], + 'couplement': ['couplement', 'uncomplete'], + 'couplet': ['couplet', 'octuple'], + 'coupon': ['coupon', 'uncoop'], + 'couponed': ['couponed', 'uncooped'], + 'courante': ['cornuate', 'courante', 'cuneator', 'outrance'], + 'courbaril': ['courbaril', 'orbicular'], + 'courlan': ['colunar', 'cornual', 'courlan'], + 'cours': ['cours', 'scour'], + 'course': ['cerous', 'course', 'crouse', 'source'], + 'coursed': ['coursed', 'scoured'], + 'courser': ['courser', 'scourer'], + 'coursing': ['coursing', 'scouring'], + 'court': ['court', 'crout', 'turco'], + 'courtesan': ['acentrous', 'courtesan', 'nectarous'], + 'courtesy': ['cosurety', 'courtesy'], + 'courtier': ['courtier', 'outcrier'], + 'courtiership': ['courtiership', 'peritrichous'], + 'courtin': ['courtin', 'ruction'], + 'courtman': ['courtman', 'turcoman'], + 'couter': ['couter', 'croute'], + 'couth': ['couth', 'thuoc', 'touch'], + 'couthily': ['couthily', 'touchily'], + 'couthiness': ['couthiness', 'touchiness'], + 'couthless': ['couthless', 'touchless'], + 'coutil': ['coutil', 'toluic'], + 'covenanter': ['contravene', 'covenanter'], + 'coverer': ['coverer', 'recover'], + 'coversine': ['coversine', 'vernicose'], + 'covert': ['covert', 'vector'], + 'covisit': ['covisit', 'ovistic'], + 'cowardy': ['cowardy', 'cowyard'], + 'cowherd': ['chowder', 'cowherd'], + 'cowl': ['clow', 'cowl'], + 'cowyard': ['cowardy', 'cowyard'], + 'coxa': ['coax', 'coxa'], + 'coxite': ['coxite', 'exotic'], + 'coyness': ['coyness', 'sycones'], + 'coyol': ['cooly', 'coyol'], + 'coyote': ['coyote', 'oocyte'], + 'craber': ['bracer', 'craber'], + 'crabhole': ['bachelor', 'crabhole'], + 'crablet': ['beclart', 'crablet'], + 'crackable': ['blackacre', 'crackable'], + 'crackle': ['cackler', 'clacker', 'crackle'], + 'crackmans': ['crackmans', 'cracksman'], + 'cracksman': ['crackmans', 'cracksman'], + 'cradge': ['cadger', 'cradge'], + 'cradle': ['cardel', 'cradle'], + 'cradlemate': ['cradlemate', 'malcreated'], + 'craig': ['cigar', 'craig'], + 'crain': ['cairn', 'crain', 'naric'], + 'crake': ['acker', 'caker', 'crake', 'creak'], + 'cram': ['cram', 'marc'], + 'cramasie': ['cramasie', 'mesaraic'], + 'crambe': ['becram', 'camber', 'crambe'], + 'crambidae': ['carbamide', 'crambidae'], + 'crambinae': ['carbamine', 'crambinae'], + 'cramble': ['cambrel', 'clamber', 'cramble'], + 'cramper': ['cramper', 'recramp'], + 'crampon': ['crampon', 'cropman'], + 'cranage': ['carnage', 'cranage', 'garance'], + 'crance': ['cancer', 'crance'], + 'crane': ['caner', 'crane', 'crena', 'nacre', 'rance'], + 'craner': ['craner', 'rancer'], + 'craney': ['carney', 'craney'], + 'crania': ['acinar', + 'arnica', + 'canari', + 'carian', + 'carina', + 'crania', + 'narica'], + 'craniad': ['acridan', 'craniad'], + 'cranial': ['carinal', 'carlina', 'clarain', 'cranial'], + 'cranially': ['ancillary', 'carlylian', 'cranially'], + 'cranian': ['canarin', 'cranian'], + 'craniate': ['anaretic', 'arcanite', 'carinate', 'craniate'], + 'cranic': ['cancri', 'carnic', 'cranic'], + 'craniectomy': ['craniectomy', 'cyanometric'], + 'craniognomy': ['craniognomy', 'organonymic'], + 'craniota': ['craniota', 'croatian', 'narcotia', 'raincoat'], + 'cranker': ['cranker', 'recrank'], + 'crap': ['carp', 'crap'], + 'crape': ['caper', 'crape', 'pacer', 'perca', 'recap'], + 'crappie': ['crappie', 'epicarp'], + 'crapple': ['clapper', 'crapple'], + 'crappo': ['crappo', 'croppa'], + 'craps': ['craps', 'scarp', 'scrap'], + 'crapulous': ['crapulous', 'opuscular'], + 'crare': ['carer', 'crare', 'racer'], + 'crate': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'crateful': ['crateful', 'fulcrate'], + 'crater': ['arrect', 'carter', 'crater', 'recart', 'tracer'], + 'craterid': ['cirrated', 'craterid'], + 'crateriform': ['crateriform', 'terraciform'], + 'crateris': ['crateris', 'serratic'], + 'craterlet': ['clatterer', 'craterlet'], + 'craterous': ['craterous', 'recusator'], + 'cratinean': ['cratinean', 'incarnate', 'nectarian'], + 'cratometric': ['cratometric', 'metrocratic'], + 'crave': ['carve', 'crave', 'varec'], + 'craven': ['carven', 'cavern', 'craven'], + 'craver': ['carver', 'craver'], + 'craving': ['carving', 'craving'], + 'crayon': ['canroy', 'crayon', 'cyrano', 'nyroca'], + 'crayonist': ['carnosity', 'crayonist'], + 'crea': ['acer', 'acre', 'care', 'crea', 'race'], + 'creagh': ['charge', 'creagh'], + 'creak': ['acker', 'caker', 'crake', 'creak'], + 'cream': ['cream', 'macer'], + 'creamer': ['amercer', 'creamer'], + 'creant': ['canter', + 'creant', + 'cretan', + 'nectar', + 'recant', + 'tanrec', + 'trance'], + 'crease': ['cesare', 'crease', 'recase', 'searce'], + 'creaser': ['creaser', 'searcer'], + 'creasing': ['creasing', 'scirenga'], + 'creat': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'creatable': ['creatable', 'traceable'], + 'create': ['cerate', 'create', 'ecarte'], + 'creatine': ['aneretic', 'centiare', 'creatine', 'increate', 'iterance'], + 'creatinine': ['creatinine', 'incinerate'], + 'creation': ['actioner', 'anerotic', 'ceration', 'creation', 'reaction'], + 'creational': ['creational', 'crotalinae', 'laceration', 'reactional'], + 'creationary': ['creationary', 'reactionary'], + 'creationism': ['anisometric', + 'creationism', + 'miscreation', + 'ramisection', + 'reactionism'], + 'creationist': ['creationist', 'reactionist'], + 'creative': ['creative', 'reactive'], + 'creatively': ['creatively', 'reactively'], + 'creativeness': ['creativeness', 'reactiveness'], + 'creativity': ['creativity', 'reactivity'], + 'creator': ['creator', 'reactor'], + 'crebrous': ['crebrous', 'obscurer'], + 'credential': ['credential', 'interlaced', 'reclinated'], + 'credit': ['credit', 'direct'], + 'creditable': ['creditable', 'directable'], + 'creditive': ['creditive', 'directive'], + 'creditor': ['creditor', 'director'], + 'creditorship': ['creditorship', 'directorship'], + 'creditress': ['creditress', 'directress'], + 'creditrix': ['creditrix', 'directrix'], + 'crednerite': ['crednerite', 'interceder'], + 'credo': ['coder', 'cored', 'credo'], + 'cree': ['cere', 'cree'], + 'creed': ['ceder', 'cedre', 'cered', 'creed'], + 'creedal': ['cedrela', 'creedal', 'declare'], + 'creedalism': ['creedalism', 'misdeclare'], + 'creedist': ['creedist', 'desertic', 'discreet', 'discrete'], + 'creep': ['creep', 'crepe'], + 'creepered': ['creepered', 'predecree'], + 'creepie': ['creepie', 'repiece'], + 'cremation': ['cremation', 'manticore'], + 'cremator': ['cremator', 'mercator'], + 'crematorial': ['crematorial', 'mercatorial'], + 'cremor': ['cremor', 'cromer'], + 'crena': ['caner', 'crane', 'crena', 'nacre', 'rance'], + 'crenate': ['centare', 'crenate'], + 'crenated': ['crenated', 'decanter', 'nectared'], + 'crenation': ['connarite', 'container', 'cotarnine', 'crenation', 'narcotine'], + 'crenelate': ['crenelate', 'lanceteer'], + 'crenelation': ['crenelation', 'intolerance'], + 'crenele': ['crenele', 'encreel'], + 'crenellation': ['centrolineal', 'crenellation'], + 'crenitic': ['crenitic', 'cretinic'], + 'crenology': ['crenology', 'necrology'], + 'crenula': ['crenula', 'lucarne', 'nuclear', 'unclear'], + 'crenulate': ['calenture', 'crenulate'], + 'creodonta': ['coronated', 'creodonta'], + 'creolian': ['acrolein', + 'arecolin', + 'caroline', + 'colinear', + 'cornelia', + 'creolian', + 'lonicera'], + 'creolin': ['creolin', 'licorne', 'locrine'], + 'creosotic': ['corticose', 'creosotic'], + 'crepe': ['creep', 'crepe'], + 'crepidula': ['crepidula', 'pedicular'], + 'crepine': ['crepine', 'increep'], + 'crepiness': ['crepiness', 'princesse'], + 'crepis': ['crepis', 'cripes', 'persic', 'precis', 'spicer'], + 'crepitant': ['crepitant', 'pittancer'], + 'crepitation': ['actinopteri', 'crepitation', 'precitation'], + 'crepitous': ['crepitous', 'euproctis', 'uroseptic'], + 'crepitus': ['crepitus', 'piecrust'], + 'crepon': ['crepon', 'procne'], + 'crepy': ['crepy', 'cypre', 'percy'], + 'cresol': ['closer', 'cresol', 'escrol'], + 'cresolin': ['cresolin', 'licensor'], + 'cresotic': ['cortices', 'cresotic'], + 'cresson': ['cresson', 'crosnes'], + 'crestline': ['crestline', 'stenciler'], + 'crestmoreite': ['crestmoreite', 'stereometric'], + 'creta': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'cretan': ['canter', + 'creant', + 'cretan', + 'nectar', + 'recant', + 'tanrec', + 'trance'], + 'crete': ['crete', 'erect'], + 'cretification': ['certification', 'cretification', 'rectification'], + 'cretify': ['certify', 'cretify', 'rectify'], + 'cretin': ['cinter', 'cretin', 'crinet'], + 'cretinic': ['crenitic', 'cretinic'], + 'cretinoid': ['cretinoid', 'direction'], + 'cretion': ['cerotin', 'cointer', 'cotrine', 'cretion', 'noticer', 'rection'], + 'cretism': ['cretism', 'metrics'], + 'crewer': ['crewer', 'recrew'], + 'cribo': ['boric', 'cribo', 'orbic'], + 'crickle': ['clicker', 'crickle'], + 'cricothyroid': ['cricothyroid', 'thyrocricoid'], + 'cried': ['cider', 'cried', 'deric', 'dicer'], + 'crier': ['crier', 'ricer'], + 'criey': ['criey', 'ricey'], + 'crile': ['crile', 'elric', 'relic'], + 'crimean': ['armenic', 'carmine', 'ceriman', 'crimean', 'mercian'], + 'crimeful': ['crimeful', 'merciful'], + 'crimeless': ['crimeless', 'merciless'], + 'crimelessness': ['crimelessness', 'mercilessness'], + 'criminalese': ['criminalese', 'misreliance'], + 'criminate': ['antimeric', 'carminite', 'criminate', 'metrician'], + 'criminatory': ['coriamyrtin', 'criminatory'], + 'crimpage': ['crimpage', 'pergamic'], + 'crinal': ['carlin', 'clarin', 'crinal'], + 'crinanite': ['crinanite', 'natricine'], + 'crinated': ['crinated', 'dicentra'], + 'crine': ['cerin', 'crine'], + 'crined': ['cedrin', 'cinder', 'crined'], + 'crinet': ['cinter', 'cretin', 'crinet'], + 'cringle': ['clinger', 'cringle'], + 'crinite': ['citrine', 'crinite', 'inciter', 'neritic'], + 'crinkle': ['clinker', 'crinkle'], + 'cripes': ['crepis', 'cripes', 'persic', 'precis', 'spicer'], + 'cripple': ['clipper', 'cripple'], + 'crisp': ['crisp', 'scrip'], + 'crispation': ['antipsoric', 'ascription', 'crispation'], + 'crisped': ['crisped', 'discerp'], + 'crispy': ['crispy', 'cypris'], + 'crista': ['crista', 'racist'], + 'cristopher': ['cristopher', 'rectorship'], + 'criteria': ['criteria', 'triceria'], + 'criterion': ['criterion', 'tricerion'], + 'criterium': ['criterium', 'tricerium'], + 'crith': ['crith', 'richt'], + 'critic': ['citric', 'critic'], + 'cro': ['cor', 'cro', 'orc', 'roc'], + 'croak': ['arock', 'croak'], + 'croat': ['actor', 'corta', 'croat', 'rocta', 'taroc', 'troca'], + 'croatan': ['cantaro', 'croatan'], + 'croatian': ['craniota', 'croatian', 'narcotia', 'raincoat'], + 'crocein': ['cornice', 'crocein'], + 'croceine': ['cicerone', 'croceine'], + 'crocetin': ['crocetin', 'necrotic'], + 'crocidolite': ['crocidolite', 'crocodilite'], + 'crocin': ['cornic', 'crocin'], + 'crocodile': ['cordicole', 'crocodile'], + 'crocodilite': ['crocidolite', 'crocodilite'], + 'croconate': ['coenactor', 'croconate'], + 'crocus': ['crocus', 'succor'], + 'crom': ['corm', 'crom'], + 'crome': ['comer', 'crome'], + 'cromer': ['cremor', 'cromer'], + 'crone': ['coner', 'crone', 'recon'], + 'cronet': ['conter', 'cornet', 'cronet', 'roncet'], + 'cronian': ['corinna', 'cronian'], + 'cronish': ['cornish', 'cronish', 'sorchin'], + 'crony': ['corny', 'crony'], + 'croodle': ['colored', 'croodle', 'decolor'], + 'crool': ['color', 'corol', 'crool'], + 'croon': ['conor', 'croon', 'ronco'], + 'crooner': ['coroner', 'crooner', 'recroon'], + 'crop': ['copr', 'corp', 'crop'], + 'cropman': ['crampon', 'cropman'], + 'croppa': ['crappo', 'croppa'], + 'crore': ['corer', 'crore'], + 'crosa': ['arcos', 'crosa', 'oscar', 'sacro'], + 'crosier': ['cirrose', 'crosier'], + 'crosnes': ['cresson', 'crosnes'], + 'crosse': ['cessor', 'crosse', 'scorse'], + 'crosser': ['crosser', 'recross'], + 'crossite': ['crossite', 'crosstie'], + 'crossover': ['crossover', 'overcross'], + 'crosstie': ['crossite', 'crosstie'], + 'crosstied': ['crosstied', 'dissector'], + 'crosstree': ['crosstree', 'rectoress'], + 'crosswalk': ['classwork', 'crosswalk'], + 'crotal': ['carlot', 'crotal'], + 'crotalic': ['cortical', 'crotalic'], + 'crotalinae': ['creational', 'crotalinae', 'laceration', 'reactional'], + 'crotaline': ['alectrion', 'clarionet', 'crotaline', 'locarnite'], + 'crotalism': ['clamorist', 'crotalism'], + 'crotalo': ['crotalo', 'locator'], + 'crotaloid': ['crotaloid', 'doctorial'], + 'crotin': ['citron', 'cortin', 'crotin'], + 'croton': ['corton', 'croton'], + 'crotonate': ['contortae', 'crotonate'], + 'crottle': ['clotter', 'crottle'], + 'crouchant': ['archcount', 'crouchant'], + 'croupal': ['copular', 'croupal', 'cupolar', 'porcula'], + 'croupe': ['couper', 'croupe', 'poucer', 'recoup'], + 'croupily': ['croupily', 'polyuric'], + 'croupiness': ['croupiness', 'percussion', 'supersonic'], + 'crouse': ['cerous', 'course', 'crouse', 'source'], + 'crout': ['court', 'crout', 'turco'], + 'croute': ['couter', 'croute'], + 'crouton': ['contour', 'cornuto', 'countor', 'crouton'], + 'crowder': ['crowder', 'recrowd'], + 'crowned': ['crowned', 'decrown'], + 'crowner': ['crowner', 'recrown'], + 'crownmaker': ['cankerworm', 'crownmaker'], + 'croy': ['cory', 'croy'], + 'croydon': ['corydon', 'croydon'], + 'cruces': ['cercus', 'cruces'], + 'cruciate': ['aceturic', 'cruciate'], + 'crudwort': ['crudwort', 'curdwort'], + 'cruel': ['cruel', 'lucre', 'ulcer'], + 'cruels': ['clerus', 'cruels'], + 'cruelty': ['cruelty', 'cutlery'], + 'cruet': ['cruet', 'eruct', 'recut', 'truce'], + 'cruise': ['cruise', 'crusie'], + 'cruisken': ['cruisken', 'unsicker'], + 'crunode': ['crunode', 'uncored'], + 'crureus': ['crureus', 'surcrue'], + 'crurogenital': ['crurogenital', 'genitocrural'], + 'cruroinguinal': ['cruroinguinal', 'inguinocrural'], + 'crus': ['crus', 'scur'], + 'crusado': ['acrodus', 'crusado'], + 'crusca': ['crusca', 'curcas'], + 'cruse': ['cruse', 'curse', 'sucre'], + 'crusher': ['crusher', 'recrush'], + 'crusie': ['cruise', 'crusie'], + 'crust': ['crust', 'curst'], + 'crustate': ['crustate', 'scrutate'], + 'crustation': ['crustation', 'scrutation'], + 'crustily': ['crustily', 'rusticly'], + 'crustiness': ['crustiness', 'rusticness'], + 'crusty': ['crusty', 'curtsy'], + 'cruth': ['cruth', 'rutch'], + 'cryosel': ['cryosel', 'scroyle'], + 'cryptodire': ['cryptodire', 'predictory'], + 'cryptomeria': ['cryptomeria', 'imprecatory'], + 'cryptostomate': ['cryptostomate', 'prostatectomy'], + 'ctenidial': ['ctenidial', 'identical'], + 'ctenoid': ['condite', 'ctenoid'], + 'ctenolium': ['ctenolium', 'monticule'], + 'ctenophore': ['ctenophore', 'nectophore'], + 'ctetology': ['ctetology', 'tectology'], + 'cuailnge': ['cuailnge', 'glaucine'], + 'cuarteron': ['cuarteron', 'raconteur'], + 'cubanite': ['cubanite', 'incubate'], + 'cuber': ['bruce', 'cebur', 'cuber'], + 'cubist': ['bustic', 'cubist'], + 'cubit': ['butic', 'cubit'], + 'cubitale': ['baculite', 'cubitale'], + 'cuboidal': ['baculoid', 'cuboidal'], + 'cuchan': ['caunch', 'cuchan'], + 'cueball': ['bullace', 'cueball'], + 'cueman': ['acumen', 'cueman'], + 'cuir': ['cuir', 'uric'], + 'culebra': ['culebra', 'curable'], + 'culet': ['culet', 'lucet'], + 'culinary': ['culinary', 'uranylic'], + 'culmy': ['culmy', 'cumyl'], + 'culpose': ['culpose', 'ploceus', 'upclose'], + 'cultch': ['clutch', 'cultch'], + 'cultivar': ['cultivar', 'curvital'], + 'culturine': ['culturine', 'inculture'], + 'cumaean': ['cumaean', 'encauma'], + 'cumar': ['carum', 'cumar'], + 'cumber': ['cumber', 'cumbre'], + 'cumberer': ['cerebrum', 'cumberer'], + 'cumbraite': ['bacterium', 'cumbraite'], + 'cumbre': ['cumber', 'cumbre'], + 'cumic': ['cumic', 'mucic'], + 'cumin': ['cumin', 'mucin'], + 'cumol': ['cumol', 'locum'], + 'cumulite': ['cumulite', 'lutecium'], + 'cumyl': ['culmy', 'cumyl'], + 'cuna': ['cuna', 'unca'], + 'cunan': ['canun', 'cunan'], + 'cuneal': ['auncel', 'cuneal', 'lacune', 'launce', 'unlace'], + 'cuneator': ['cornuate', 'courante', 'cuneator', 'outrance'], + 'cunila': ['cunila', 'lucian', 'lucina', 'uncial'], + 'cuon': ['cuon', 'unco'], + 'cuorin': ['cuorin', 'uronic'], + 'cupid': ['cupid', 'pudic'], + 'cupidity': ['cupidity', 'pudicity'], + 'cupidone': ['cupidone', 'uncopied'], + 'cupola': ['copula', 'cupola'], + 'cupolar': ['copular', 'croupal', 'cupolar', 'porcula'], + 'cupreous': ['cupreous', 'upcourse'], + 'cuprite': ['cuprite', 'picture'], + 'curable': ['culebra', 'curable'], + 'curate': ['acture', 'cauter', 'curate'], + 'curateship': ['curateship', 'pasticheur'], + 'curation': ['curation', 'nocturia'], + 'curatory': ['curatory', 'outcarry'], + 'curcas': ['crusca', 'curcas'], + 'curdle': ['curdle', 'curled'], + 'curdwort': ['crudwort', 'curdwort'], + 'cure': ['cure', 'ecru', 'eruc'], + 'curer': ['curer', 'recur'], + 'curial': ['curial', 'lauric', 'uracil', 'uralic'], + 'curialist': ['curialist', 'rusticial'], + 'curie': ['curie', 'ureic'], + 'curin': ['curin', 'incur', 'runic'], + 'curine': ['curine', 'erucin', 'neuric'], + 'curiosa': ['carious', 'curiosa'], + 'curite': ['curite', 'teucri', 'uretic'], + 'curled': ['curdle', 'curled'], + 'curler': ['curler', 'recurl'], + 'cursa': ['cursa', 'scaur'], + 'cursal': ['cursal', 'sulcar'], + 'curse': ['cruse', 'curse', 'sucre'], + 'cursed': ['cedrus', 'cursed'], + 'curst': ['crust', 'curst'], + 'cursus': ['cursus', 'ruscus'], + 'curtail': ['curtail', 'trucial'], + 'curtailer': ['curtailer', 'recruital', 'reticular'], + 'curtain': ['curtain', 'turacin', 'turcian'], + 'curtation': ['anticourt', 'curtation', 'ructation'], + 'curtilage': ['curtilage', 'cutigeral', 'graticule'], + 'curtis': ['citrus', 'curtis', 'rictus', 'rustic'], + 'curtise': ['curtise', 'icterus'], + 'curtsy': ['crusty', 'curtsy'], + 'curvital': ['cultivar', 'curvital'], + 'cush': ['cush', 'such'], + 'cushionless': ['cushionless', 'slouchiness'], + 'cusinero': ['coinsure', 'corineus', 'cusinero'], + 'cusk': ['cusk', 'suck'], + 'cusp': ['cusp', 'scup'], + 'cuspal': ['cuspal', 'placus'], + 'custom': ['custom', 'muscot'], + 'customer': ['costumer', 'customer'], + 'cutheal': ['auchlet', 'cutheal', 'taluche'], + 'cutigeral': ['curtilage', 'cutigeral', 'graticule'], + 'cutin': ['cutin', 'incut', 'tunic'], + 'cutis': ['cutis', 'ictus'], + 'cutler': ['cutler', 'reluct'], + 'cutleress': ['cutleress', 'lecturess', 'truceless'], + 'cutleria': ['arculite', 'cutleria', 'lucretia', 'reticula', 'treculia'], + 'cutlery': ['cruelty', 'cutlery'], + 'cutlet': ['cutlet', 'cuttle'], + 'cutoff': ['cutoff', 'offcut'], + 'cutout': ['cutout', 'outcut'], + 'cutover': ['cutover', 'overcut'], + 'cuttle': ['cutlet', 'cuttle'], + 'cuttler': ['clutter', 'cuttler'], + 'cutup': ['cutup', 'upcut'], + 'cuya': ['cuya', 'yuca'], + 'cyamus': ['cyamus', 'muysca'], + 'cyan': ['cany', 'cyan'], + 'cyanidine': ['cyanidine', 'dicyanine'], + 'cyanol': ['alcyon', 'cyanol'], + 'cyanole': ['alcyone', 'cyanole'], + 'cyanometric': ['craniectomy', 'cyanometric'], + 'cyanophycin': ['cyanophycin', 'phycocyanin'], + 'cyanuret': ['centaury', 'cyanuret'], + 'cyath': ['cathy', 'cyath', 'yacht'], + 'cyclamine': ['cyclamine', 'macilency'], + 'cyclian': ['cyclian', 'cynical'], + 'cyclide': ['cyclide', 'decylic', 'dicycle'], + 'cyclism': ['clysmic', 'cyclism'], + 'cyclotome': ['colectomy', 'cyclotome'], + 'cydonian': ['anodynic', 'cydonian'], + 'cylindrite': ['cylindrite', 'indirectly'], + 'cylix': ['cylix', 'xylic'], + 'cymation': ['cymation', 'myatonic', 'onymatic'], + 'cymoid': ['cymoid', 'mycoid'], + 'cymometer': ['cymometer', 'mecometry'], + 'cymose': ['cymose', 'mycose'], + 'cymule': ['cymule', 'lyceum'], + 'cynara': ['canary', 'cynara'], + 'cynaroid': ['cynaroid', 'dicaryon'], + 'cynical': ['cyclian', 'cynical'], + 'cynogale': ['acylogen', 'cynogale'], + 'cynophilic': ['cynophilic', 'philocynic'], + 'cynosural': ['consulary', 'cynosural'], + 'cyphonism': ['cyphonism', 'symphonic'], + 'cypre': ['crepy', 'cypre', 'percy'], + 'cypria': ['cypria', 'picary', 'piracy'], + 'cyprian': ['cyprian', 'cyprina'], + 'cyprina': ['cyprian', 'cyprina'], + 'cyprine': ['cyprine', 'pyrenic'], + 'cypris': ['crispy', 'cypris'], + 'cyrano': ['canroy', 'crayon', 'cyrano', 'nyroca'], + 'cyril': ['cyril', 'lyric'], + 'cyrilla': ['cyrilla', 'lyrical'], + 'cyrtopia': ['cyrtopia', 'poticary'], + 'cyst': ['cyst', 'scyt'], + 'cystidean': ['asyndetic', 'cystidean', 'syndicate'], + 'cystitis': ['cystitis', 'scytitis'], + 'cystoadenoma': ['adenocystoma', 'cystoadenoma'], + 'cystofibroma': ['cystofibroma', 'fibrocystoma'], + 'cystolith': ['cystolith', 'lithocyst'], + 'cystomyxoma': ['cystomyxoma', 'myxocystoma'], + 'cystonephrosis': ['cystonephrosis', 'nephrocystosis'], + 'cystopyelitis': ['cystopyelitis', 'pyelocystitis'], + 'cystotome': ['cystotome', 'cytostome', 'ostectomy'], + 'cystourethritis': ['cystourethritis', 'urethrocystitis'], + 'cytase': ['cytase', 'stacey'], + 'cytherea': ['cheatery', 'cytherea', 'teachery'], + 'cytherean': ['cytherean', 'enchytrae'], + 'cytisine': ['cytisine', 'syenitic'], + 'cytoblastemic': ['blastomycetic', 'cytoblastemic'], + 'cytoblastemous': ['blastomycetous', 'cytoblastemous'], + 'cytochrome': ['chromocyte', 'cytochrome'], + 'cytoid': ['cytoid', 'docity'], + 'cytomere': ['cytomere', 'merocyte'], + 'cytophil': ['cytophil', 'phycitol'], + 'cytosine': ['cenosity', 'cytosine'], + 'cytosome': ['cytosome', 'otomyces'], + 'cytost': ['cytost', 'scotty'], + 'cytostome': ['cystotome', 'cytostome', 'ostectomy'], + 'czarian': ['czarian', 'czarina'], + 'czarina': ['czarian', 'czarina'], + 'da': ['ad', 'da'], + 'dab': ['bad', 'dab'], + 'dabber': ['barbed', 'dabber'], + 'dabbler': ['dabbler', 'drabble'], + 'dabitis': ['dabitis', 'dibatis'], + 'dablet': ['dablet', 'tabled'], + 'dace': ['cade', 'dace', 'ecad'], + 'dacelo': ['alcedo', 'dacelo'], + 'dacian': ['acnida', 'anacid', 'dacian'], + 'dacker': ['arcked', 'dacker'], + 'dacryolith': ['dacryolith', 'hydrotical'], + 'dacryon': ['candroy', 'dacryon'], + 'dactylonomy': ['dactylonomy', 'monodactyly'], + 'dactylopteridae': ['dactylopteridae', 'pterodactylidae'], + 'dactylopterus': ['dactylopterus', 'pterodactylus'], + 'dacus': ['cadus', 'dacus'], + 'dad': ['add', 'dad'], + 'dada': ['adad', 'adda', 'dada'], + 'dadap': ['dadap', 'padda'], + 'dade': ['dade', 'dead', 'edda'], + 'dadu': ['addu', 'dadu', 'daud', 'duad'], + 'dae': ['ade', 'dae'], + 'daemon': ['daemon', 'damone', 'modena'], + 'daemonic': ['codamine', 'comedian', 'daemonic', 'demoniac'], + 'daer': ['ared', 'daer', 'dare', 'dear', 'read'], + 'dag': ['dag', 'gad'], + 'dagaba': ['badaga', 'dagaba', 'gadaba'], + 'dagame': ['dagame', 'damage'], + 'dagbane': ['bandage', 'dagbane'], + 'dagestan': ['dagestan', 'standage'], + 'dagger': ['dagger', 'gadger', 'ragged'], + 'daggers': ['daggers', 'seggard'], + 'daggle': ['daggle', 'lagged'], + 'dago': ['dago', 'goad'], + 'dagomba': ['dagomba', 'gambado'], + 'dags': ['dags', 'sgad'], + 'dah': ['dah', 'dha', 'had'], + 'daidle': ['daidle', 'laddie'], + 'daikon': ['daikon', 'nodiak'], + 'dail': ['dail', 'dali', 'dial', 'laid', 'lida'], + 'daily': ['daily', 'lydia'], + 'daimen': ['daimen', 'damine', 'maiden', 'median', 'medina'], + 'daimio': ['daimio', 'maioid'], + 'daimon': ['amidon', 'daimon', 'domain'], + 'dain': ['adin', 'andi', 'dain', 'dani', 'dian', 'naid'], + 'dairi': ['dairi', 'darii', 'radii'], + 'dairy': ['dairy', 'diary', 'yaird'], + 'dais': ['dais', 'dasi', 'disa', 'said', 'sida'], + 'daisy': ['daisy', 'sayid'], + 'daker': ['daker', 'drake', 'kedar', 'radek'], + 'dal': ['dal', 'lad'], + 'dale': ['dale', 'deal', 'lade', 'lead', 'leda'], + 'dalea': ['adela', 'dalea'], + 'dalecarlian': ['calendarial', 'dalecarlian'], + 'daleman': ['daleman', 'lademan', 'leadman'], + 'daler': ['alder', 'daler', 'lader'], + 'dalesman': ['dalesman', 'leadsman'], + 'dali': ['dail', 'dali', 'dial', 'laid', 'lida'], + 'dalle': ['dalle', 'della', 'ladle'], + 'dallying': ['dallying', 'ladyling'], + 'dalt': ['dalt', 'tald'], + 'dalteen': ['dalteen', 'dentale', 'edental'], + 'dam': ['dam', 'mad'], + 'dama': ['adam', 'dama'], + 'damage': ['dagame', 'damage'], + 'daman': ['adman', 'daman', 'namda'], + 'damara': ['armada', 'damara', 'ramada'], + 'dame': ['dame', 'made', 'mead'], + 'damewort': ['damewort', 'wardmote'], + 'damia': ['amadi', 'damia', 'madia', 'maida'], + 'damie': ['amide', 'damie', 'media'], + 'damier': ['admire', 'armied', 'damier', 'dimera', 'merida'], + 'damine': ['daimen', 'damine', 'maiden', 'median', 'medina'], + 'dammer': ['dammer', 'dramme'], + 'dammish': ['dammish', 'mahdism'], + 'damn': ['damn', 'mand'], + 'damnation': ['damnation', 'mandation'], + 'damnatory': ['damnatory', 'mandatory'], + 'damned': ['damned', 'demand', 'madden'], + 'damner': ['damner', 'manred', 'randem', 'remand'], + 'damnii': ['amidin', 'damnii'], + 'damnous': ['damnous', 'osmunda'], + 'damon': ['damon', 'monad', 'nomad'], + 'damone': ['daemon', 'damone', 'modena'], + 'damonico': ['damonico', 'monoacid'], + 'dampen': ['dampen', 'madnep'], + 'damper': ['damper', 'ramped'], + 'dampish': ['dampish', 'madship', 'phasmid'], + 'dan': ['and', 'dan'], + 'dana': ['anda', 'dana'], + 'danaan': ['ananda', 'danaan'], + 'danai': ['danai', 'diana', 'naiad'], + 'danainae': ['anadenia', 'danainae'], + 'danakil': ['danakil', 'dankali', 'kaldani', 'ladakin'], + 'danalite': ['danalite', 'detainal'], + 'dancalite': ['cadential', 'dancalite'], + 'dance': ['dance', 'decan'], + 'dancer': ['cedarn', 'dancer', 'nacred'], + 'dancery': ['ardency', 'dancery'], + 'dander': ['dander', 'darned', 'nadder'], + 'dandle': ['dandle', 'landed'], + 'dandler': ['dandler', 'dendral'], + 'dane': ['ande', 'dane', 'dean', 'edna'], + 'danewort': ['danewort', 'teardown'], + 'danger': ['danger', 'gander', 'garden', 'ranged'], + 'dangerful': ['dangerful', 'gardenful'], + 'dangerless': ['dangerless', 'gardenless'], + 'dangle': ['angled', 'dangle', 'englad', 'lagend'], + 'dangler': ['dangler', 'gnarled'], + 'danglin': ['danglin', 'landing'], + 'dani': ['adin', 'andi', 'dain', 'dani', 'dian', 'naid'], + 'danian': ['andian', 'danian', 'nidana'], + 'danic': ['canid', 'cnida', 'danic'], + 'daniel': ['aldine', 'daniel', 'delian', 'denial', 'enalid', 'leadin'], + 'daniele': ['adeline', 'daniele', 'delaine'], + 'danielic': ['alcidine', 'danielic', 'lecaniid'], + 'danio': ['adion', 'danio', 'doina', 'donia'], + 'danish': ['danish', 'sandhi'], + 'danism': ['danism', 'disman'], + 'danite': ['danite', 'detain'], + 'dankali': ['danakil', 'dankali', 'kaldani', 'ladakin'], + 'danli': ['danli', 'ladin', 'linda', 'nidal'], + 'dannie': ['aidenn', 'andine', 'dannie', 'indane'], + 'danseuse': ['danseuse', 'sudanese'], + 'dantean': ['andante', 'dantean'], + 'dantist': ['dantist', 'distant'], + 'danuri': ['danuri', 'diurna', 'dunair', 'durain', 'durani', 'durian'], + 'dao': ['ado', 'dao', 'oda'], + 'daoine': ['daoine', 'oneida'], + 'dap': ['dap', 'pad'], + 'daphnis': ['daphnis', 'dishpan'], + 'dapicho': ['dapicho', 'phacoid'], + 'dapple': ['dapple', 'lapped', 'palped'], + 'dar': ['dar', 'rad'], + 'daraf': ['daraf', 'farad'], + 'darby': ['bardy', 'darby'], + 'darci': ['acrid', 'caird', 'carid', 'darci', 'daric', 'dirca'], + 'dare': ['ared', 'daer', 'dare', 'dear', 'read'], + 'dareall': ['ardella', 'dareall'], + 'daren': ['andre', 'arend', 'daren', 'redan'], + 'darer': ['darer', 'drear'], + 'darg': ['darg', 'drag', 'grad'], + 'darger': ['darger', 'gerard', 'grader', 'redrag', 'regard'], + 'dargo': ['dargo', 'dogra', 'drago'], + 'dargsman': ['dargsman', 'dragsman'], + 'dari': ['arid', 'dari', 'raid'], + 'daric': ['acrid', 'caird', 'carid', 'darci', 'daric', 'dirca'], + 'darien': ['darien', 'draine'], + 'darii': ['dairi', 'darii', 'radii'], + 'darin': ['darin', 'dinar', 'drain', 'indra', 'nadir', 'ranid'], + 'daring': ['daring', 'dingar', 'gradin'], + 'darius': ['darius', 'radius'], + 'darken': ['darken', 'kanred', 'ranked'], + 'darkener': ['darkener', 'redarken'], + 'darn': ['darn', 'nard', 'rand'], + 'darned': ['dander', 'darned', 'nadder'], + 'darnel': ['aldern', + 'darnel', + 'enlard', + 'lander', + 'lenard', + 'randle', + 'reland'], + 'darner': ['darner', 'darren', 'errand', 'rander', 'redarn'], + 'darning': ['darning', 'randing'], + 'darrein': ['darrein', 'drainer'], + 'darren': ['darner', 'darren', 'errand', 'rander', 'redarn'], + 'darshana': ['darshana', 'shardana'], + 'darst': ['darst', 'darts', 'strad'], + 'dart': ['dart', 'drat'], + 'darter': ['darter', + 'dartre', + 'redart', + 'retard', + 'retrad', + 'tarred', + 'trader'], + 'darting': ['darting', 'trading'], + 'dartle': ['dartle', 'tardle'], + 'dartoic': ['arctoid', 'carotid', 'dartoic'], + 'dartre': ['darter', + 'dartre', + 'redart', + 'retard', + 'retrad', + 'tarred', + 'trader'], + 'dartrose': ['dartrose', 'roadster'], + 'darts': ['darst', 'darts', 'strad'], + 'daryl': ['daryl', 'lardy', 'lyard'], + 'das': ['das', 'sad'], + 'dash': ['dash', 'sadh', 'shad'], + 'dashed': ['dashed', 'shaded'], + 'dasheen': ['dasheen', 'enshade'], + 'dasher': ['dasher', 'shader', 'sheard'], + 'dashing': ['dashing', 'shading'], + 'dashnak': ['dashnak', 'shadkan'], + 'dashy': ['dashy', 'shady'], + 'dasi': ['dais', 'dasi', 'disa', 'said', 'sida'], + 'dasnt': ['dasnt', 'stand'], + 'dasturi': ['dasturi', 'rudista'], + 'dasya': ['adays', 'dasya'], + 'dasyurine': ['dasyurine', 'dysneuria'], + 'data': ['adat', 'data'], + 'datable': ['albetad', 'datable'], + 'dataria': ['dataria', 'radiata'], + 'date': ['adet', 'date', 'tade', 'tead', 'teda'], + 'dateless': ['dateless', 'detassel'], + 'dater': ['dater', 'derat', 'detar', 'drate', 'rated', 'trade', 'tread'], + 'datil': ['datil', 'dital', 'tidal', 'tilda'], + 'datism': ['amidst', 'datism'], + 'daub': ['baud', 'buda', 'daub'], + 'dauber': ['dauber', 'redaub'], + 'daubster': ['daubster', 'subtread'], + 'daud': ['addu', 'dadu', 'daud', 'duad'], + 'daunch': ['chandu', 'daunch'], + 'daunter': ['daunter', 'unarted', 'unrated', 'untread'], + 'dauntless': ['adultness', 'dauntless'], + 'daur': ['ardu', 'daur', 'dura'], + 'dave': ['dave', 'deva', 'vade', 'veda'], + 'daven': ['daven', 'vaned'], + 'davy': ['davy', 'vady'], + 'daw': ['awd', 'daw', 'wad'], + 'dawdler': ['dawdler', 'waddler'], + 'dawdling': ['dawdling', 'waddling'], + 'dawdlingly': ['dawdlingly', 'waddlingly'], + 'dawdy': ['dawdy', 'waddy'], + 'dawn': ['dawn', 'wand'], + 'dawnlike': ['dawnlike', 'wandlike'], + 'dawny': ['dawny', 'wandy'], + 'day': ['ady', 'day', 'yad'], + 'dayal': ['adlay', 'dayal'], + 'dayfly': ['dayfly', 'ladyfy'], + 'days': ['days', 'dyas'], + 'daysman': ['daysman', 'mandyas'], + 'daytime': ['daytime', 'maytide'], + 'daywork': ['daywork', 'workday'], + 'daze': ['adze', 'daze'], + 'de': ['de', 'ed'], + 'deacon': ['acnode', 'deacon'], + 'deaconship': ['deaconship', 'endophasic'], + 'dead': ['dade', 'dead', 'edda'], + 'deadborn': ['deadborn', 'endboard'], + 'deadener': ['deadener', 'endeared'], + 'deadlock': ['deadlock', 'deckload'], + 'deaf': ['deaf', 'fade'], + 'deair': ['aider', 'deair', 'irade', 'redia'], + 'deal': ['dale', 'deal', 'lade', 'lead', 'leda'], + 'dealable': ['dealable', 'leadable'], + 'dealation': ['atloidean', 'dealation'], + 'dealer': ['dealer', 'leader', 'redeal', 'relade', 'relead'], + 'dealership': ['dealership', 'leadership'], + 'dealing': ['adeling', 'dealing', 'leading'], + 'dealt': ['adlet', 'dealt', 'delta', 'lated', 'taled'], + 'deaminase': ['deaminase', 'mesadenia'], + 'dean': ['ande', 'dane', 'dean', 'edna'], + 'deaner': ['deaner', 'endear'], + 'deaness': ['deaness', 'edessan'], + 'deaquation': ['adequation', 'deaquation'], + 'dear': ['ared', 'daer', 'dare', 'dear', 'read'], + 'dearie': ['aeried', 'dearie'], + 'dearth': ['dearth', 'hatred', 'rathed', 'thread'], + 'deary': ['deary', 'deray', 'rayed', 'ready', 'yeard'], + 'deash': ['deash', 'hades', 'sadhe', 'shade'], + 'deasil': ['aisled', 'deasil', 'ladies', 'sailed'], + 'deave': ['deave', 'eaved', 'evade'], + 'deb': ['bed', 'deb'], + 'debacle': ['belaced', 'debacle'], + 'debar': ['ardeb', 'beard', 'bread', 'debar'], + 'debark': ['bedark', 'debark'], + 'debaser': ['debaser', 'sabered'], + 'debater': ['betread', 'debater'], + 'deben': ['beden', 'deben', 'deneb'], + 'debi': ['beid', 'bide', 'debi', 'dieb'], + 'debile': ['debile', 'edible'], + 'debit': ['bidet', 'debit'], + 'debosh': ['beshod', 'debosh'], + 'debrief': ['debrief', 'defiber', 'fibered'], + 'debutant': ['debutant', 'unbatted'], + 'debutante': ['debutante', 'unabetted'], + 'decachord': ['decachord', 'dodecarch'], + 'decadic': ['caddice', 'decadic'], + 'decal': ['clead', 'decal', 'laced'], + 'decalin': ['cladine', 'decalin', 'iceland'], + 'decaliter': ['decaliter', 'decalitre'], + 'decalitre': ['decaliter', 'decalitre'], + 'decameter': ['decameter', 'decametre'], + 'decametre': ['decameter', 'decametre'], + 'decan': ['dance', 'decan'], + 'decanal': ['candela', 'decanal'], + 'decani': ['decani', 'decian'], + 'decant': ['cadent', 'canted', 'decant'], + 'decantate': ['catenated', 'decantate'], + 'decanter': ['crenated', 'decanter', 'nectared'], + 'decantherous': ['countershade', 'decantherous'], + 'decap': ['caped', 'decap', 'paced'], + 'decart': ['cedrat', 'decart', 'redact'], + 'decastere': ['decastere', 'desecrate'], + 'decator': ['cordate', 'decator', 'redcoat'], + 'decay': ['acedy', 'decay'], + 'deceiver': ['deceiver', 'received'], + 'decennia': ['cadinene', 'decennia', 'enneadic'], + 'decennial': ['celandine', 'decennial'], + 'decent': ['cedent', 'decent'], + 'decenter': ['centered', 'decenter', 'decentre', 'recedent'], + 'decentre': ['centered', 'decenter', 'decentre', 'recedent'], + 'decern': ['cendre', 'decern'], + 'decian': ['decani', 'decian'], + 'deciatine': ['deciatine', 'diacetine', 'taenicide', 'teniacide'], + 'decider': ['decider', 'decried'], + 'decillion': ['celloidin', 'collidine', 'decillion'], + 'decima': ['amiced', 'decima'], + 'decimal': ['camelid', 'decimal', 'declaim', 'medical'], + 'decimally': ['decimally', 'medically'], + 'decimate': ['decimate', 'medicate'], + 'decimation': ['decimation', 'medication'], + 'decimator': ['decimator', 'medicator', 'mordicate'], + 'decimestrial': ['decimestrial', 'sedimetrical'], + 'decimosexto': ['decimosexto', 'sextodecimo'], + 'deckel': ['deckel', 'deckle'], + 'decker': ['decker', 'redeck'], + 'deckle': ['deckel', 'deckle'], + 'deckload': ['deadlock', 'deckload'], + 'declaim': ['camelid', 'decimal', 'declaim', 'medical'], + 'declaimer': ['declaimer', 'demiracle'], + 'declaration': ['declaration', 'redactional'], + 'declare': ['cedrela', 'creedal', 'declare'], + 'declass': ['classed', 'declass'], + 'declinate': ['declinate', 'encitadel'], + 'declinatory': ['adrenolytic', 'declinatory'], + 'decoat': ['coated', 'decoat'], + 'decollate': ['decollate', 'ocellated'], + 'decollator': ['corollated', 'decollator'], + 'decolor': ['colored', 'croodle', 'decolor'], + 'decompress': ['compressed', 'decompress'], + 'deconsider': ['considered', 'deconsider'], + 'decorate': ['decorate', 'ocreated'], + 'decoration': ['carotenoid', 'coronadite', 'decoration'], + 'decorist': ['decorist', 'sectroid'], + 'decream': ['decream', 'racemed'], + 'decree': ['decree', 'recede'], + 'decreer': ['decreer', 'receder'], + 'decreet': ['decreet', 'decrete'], + 'decrepit': ['decrepit', 'depicter', 'precited'], + 'decrete': ['decreet', 'decrete'], + 'decretist': ['decretist', 'trisected'], + 'decrial': ['decrial', 'radicel', 'radicle'], + 'decried': ['decider', 'decried'], + 'decrown': ['crowned', 'decrown'], + 'decry': ['cedry', 'decry'], + 'decurionate': ['counteridea', 'decurionate'], + 'decurrency': ['decurrency', 'recrudency'], + 'decursion': ['cinderous', 'decursion'], + 'decus': ['decus', 'duces'], + 'decyl': ['clyde', 'decyl'], + 'decylic': ['cyclide', 'decylic', 'dicycle'], + 'dedan': ['dedan', 'denda'], + 'dedicant': ['addicent', 'dedicant'], + 'dedo': ['dedo', 'dode', 'eddo'], + 'deduce': ['deduce', 'deuced'], + 'deduct': ['deduct', 'ducted'], + 'deem': ['deem', 'deme', 'mede', 'meed'], + 'deemer': ['deemer', 'meered', 'redeem', 'remede'], + 'deep': ['deep', 'peed'], + 'deer': ['deer', 'dere', 'dree', 'rede', 'reed'], + 'deerhair': ['deerhair', 'dehairer'], + 'deerhorn': ['deerhorn', 'dehorner'], + 'deerwood': ['deerwood', 'doorweed'], + 'defat': ['defat', 'fated'], + 'defaulter': ['defaulter', 'redefault'], + 'defeater': ['defeater', 'federate', 'redefeat'], + 'defensor': ['defensor', 'foresend'], + 'defer': ['defer', 'freed'], + 'defial': ['afield', 'defial'], + 'defiber': ['debrief', 'defiber', 'fibered'], + 'defile': ['defile', 'fidele'], + 'defiled': ['defiled', 'fielded'], + 'defiler': ['defiler', 'fielder'], + 'definable': ['beanfield', 'definable'], + 'define': ['define', 'infeed'], + 'definer': ['definer', 'refined'], + 'deflect': ['clefted', 'deflect'], + 'deflesh': ['deflesh', 'fleshed'], + 'deflex': ['deflex', 'flexed'], + 'deflower': ['deflower', 'flowered'], + 'defluent': ['defluent', 'unfelted'], + 'defog': ['defog', 'fodge'], + 'deforciant': ['deforciant', 'fornicated'], + 'deforest': ['deforest', 'forested'], + 'deform': ['deform', 'formed'], + 'deformer': ['deformer', 'reformed'], + 'defray': ['defray', 'frayed'], + 'defrost': ['defrost', 'frosted'], + 'deg': ['deg', 'ged'], + 'degarnish': ['degarnish', 'garnished'], + 'degasser': ['degasser', 'dressage'], + 'degelation': ['degelation', 'delegation'], + 'degrain': ['degrain', 'deraign', 'deringa', 'gradine', 'grained', 'reading'], + 'degu': ['degu', 'gude'], + 'dehair': ['dehair', 'haired'], + 'dehairer': ['deerhair', 'dehairer'], + 'dehorn': ['dehorn', 'horned'], + 'dehorner': ['deerhorn', 'dehorner'], + 'dehors': ['dehors', 'rhodes', 'shoder', 'shored'], + 'dehortation': ['dehortation', 'theriodonta'], + 'dehusk': ['dehusk', 'husked'], + 'deicer': ['ceride', 'deicer'], + 'deictical': ['deictical', 'dialectic'], + 'deification': ['deification', 'edification'], + 'deificatory': ['deificatory', 'edificatory'], + 'deifier': ['deifier', 'edifier'], + 'deify': ['deify', 'edify'], + 'deign': ['deign', 'dinge', 'nidge'], + 'deino': ['deino', 'dione', 'edoni'], + 'deinocephalia': ['deinocephalia', 'palaeechinoid'], + 'deinos': ['deinos', 'donsie', 'inodes', 'onside'], + 'deipara': ['deipara', 'paridae'], + 'deirdre': ['deirdre', 'derider', 'derride', 'ridered'], + 'deism': ['deism', 'disme'], + 'deist': ['deist', 'steid'], + 'deistic': ['deistic', 'dietics'], + 'deistically': ['deistically', 'dialystelic'], + 'deity': ['deity', 'tydie'], + 'deityship': ['deityship', 'diphysite'], + 'del': ['del', 'eld', 'led'], + 'delaine': ['adeline', 'daniele', 'delaine'], + 'delaminate': ['antemedial', 'delaminate'], + 'delapse': ['delapse', 'sepaled'], + 'delate': ['delate', 'elated'], + 'delater': ['delater', 'related', 'treadle'], + 'delator': ['delator', 'leotard'], + 'delawn': ['delawn', 'lawned', 'wandle'], + 'delay': ['delay', 'leady'], + 'delayer': ['delayer', 'layered', 'redelay'], + 'delayful': ['delayful', 'feudally'], + 'dele': ['dele', 'lede', 'leed'], + 'delead': ['delead', 'leaded'], + 'delegation': ['degelation', 'delegation'], + 'delegatory': ['delegatory', 'derogately'], + 'delete': ['delete', 'teedle'], + 'delf': ['delf', 'fled'], + 'delhi': ['delhi', 'hield'], + 'delia': ['adiel', 'delia', 'ideal'], + 'delian': ['aldine', 'daniel', 'delian', 'denial', 'enalid', 'leadin'], + 'delible': ['bellied', 'delible'], + 'delicateness': ['delicateness', 'delicatessen'], + 'delicatessen': ['delicateness', 'delicatessen'], + 'delichon': ['chelidon', 'chelonid', 'delichon'], + 'delict': ['delict', 'deltic'], + 'deligation': ['deligation', 'gadolinite', 'gelatinoid'], + 'delignate': ['delignate', 'gelatined'], + 'delimit': ['delimit', 'limited'], + 'delimitation': ['delimitation', 'mniotiltidae'], + 'delineator': ['delineator', 'rondeletia'], + 'delint': ['delint', 'dentil'], + 'delirament': ['delirament', 'derailment'], + 'deliriant': ['deliriant', 'draintile', 'interlaid'], + 'deliver': ['deliver', 'deviler', 'livered'], + 'deliverer': ['deliverer', 'redeliver'], + 'della': ['dalle', 'della', 'ladle'], + 'deloul': ['deloul', 'duello'], + 'delphinius': ['delphinius', 'sulphinide'], + 'delta': ['adlet', 'dealt', 'delta', 'lated', 'taled'], + 'deltaic': ['citadel', 'deltaic', 'dialect', 'edictal', 'lactide'], + 'deltic': ['delict', 'deltic'], + 'deluding': ['deluding', 'ungilded'], + 'delusion': ['delusion', 'unsoiled'], + 'delusionist': ['delusionist', 'indissolute'], + 'deluster': ['deluster', 'ulstered'], + 'demal': ['demal', 'medal'], + 'demand': ['damned', 'demand', 'madden'], + 'demander': ['demander', 'redemand'], + 'demanding': ['demanding', 'maddening'], + 'demandingly': ['demandingly', 'maddeningly'], + 'demantoid': ['demantoid', 'dominated'], + 'demarcate': ['camerated', 'demarcate'], + 'demarcation': ['demarcation', 'democratian'], + 'demark': ['demark', 'marked'], + 'demast': ['demast', 'masted'], + 'deme': ['deem', 'deme', 'mede', 'meed'], + 'demean': ['amende', 'demean', 'meaned', 'nadeem'], + 'demeanor': ['demeanor', 'enamored'], + 'dementia': ['dementia', 'mendaite'], + 'demerit': ['demerit', 'dimeter', 'merited', 'mitered'], + 'demerol': ['demerol', 'modeler', 'remodel'], + 'demetrian': ['demetrian', 'dermatine', 'meandrite', 'minareted'], + 'demi': ['demi', 'diem', 'dime', 'mide'], + 'demibrute': ['bermudite', 'demibrute'], + 'demicannon': ['cinnamoned', 'demicannon'], + 'demicanon': ['demicanon', 'dominance'], + 'demidog': ['demidog', 'demigod'], + 'demigod': ['demidog', 'demigod'], + 'demiluster': ['demiluster', 'demilustre'], + 'demilustre': ['demiluster', 'demilustre'], + 'demiparallel': ['demiparallel', 'imparalleled'], + 'demipronation': ['demipronation', 'preadmonition', 'predomination'], + 'demiracle': ['declaimer', 'demiracle'], + 'demiram': ['demiram', 'mermaid'], + 'demirep': ['demirep', 'epiderm', 'impeder', 'remiped'], + 'demirobe': ['demirobe', 'embodier'], + 'demisable': ['beadleism', 'demisable'], + 'demise': ['demise', 'diseme'], + 'demit': ['demit', 'timed'], + 'demiturned': ['demiturned', 'undertimed'], + 'demob': ['demob', 'mobed'], + 'democratian': ['demarcation', 'democratian'], + 'demolisher': ['demolisher', 'redemolish'], + 'demoniac': ['codamine', 'comedian', 'daemonic', 'demoniac'], + 'demoniacism': ['demoniacism', 'seminomadic'], + 'demonial': ['demonial', 'melanoid'], + 'demoniast': ['ademonist', 'demoniast', 'staminode'], + 'demonish': ['demonish', 'hedonism'], + 'demonism': ['demonism', 'medimnos', 'misnomed'], + 'demotics': ['comedist', 'demotics', 'docetism', 'domestic'], + 'demotion': ['demotion', 'entomoid', 'moontide'], + 'demount': ['demount', 'mounted'], + 'demurrer': ['demurrer', 'murderer'], + 'demurring': ['demurring', 'murdering'], + 'demurringly': ['demurringly', 'murderingly'], + 'demy': ['demy', 'emyd'], + 'den': ['den', 'end', 'ned'], + 'denarius': ['denarius', 'desaurin', 'unraised'], + 'denaro': ['denaro', 'orenda'], + 'denary': ['denary', 'yander'], + 'denat': ['denat', 'entad'], + 'denature': ['denature', 'undereat'], + 'denda': ['dedan', 'denda'], + 'dendral': ['dandler', 'dendral'], + 'dendrite': ['dendrite', 'tindered'], + 'dendrites': ['dendrites', 'distender', 'redistend'], + 'dene': ['dene', 'eden', 'need'], + 'deneb': ['beden', 'deben', 'deneb'], + 'dengue': ['dengue', 'unedge'], + 'denial': ['aldine', 'daniel', 'delian', 'denial', 'enalid', 'leadin'], + 'denier': ['denier', 'nereid'], + 'denierer': ['denierer', 'reindeer'], + 'denigrate': ['argentide', 'denigrate', 'dinergate'], + 'denim': ['denim', 'mendi'], + 'denis': ['denis', 'snide'], + 'denominate': ['denominate', 'emendation'], + 'denotable': ['denotable', 'detonable'], + 'denotation': ['denotation', 'detonation'], + 'denotative': ['denotative', 'detonative'], + 'denotive': ['denotive', 'devonite'], + 'denouncer': ['denouncer', 'unencored'], + 'dense': ['dense', 'needs'], + 'denshare': ['denshare', 'seerhand'], + 'denshire': ['denshire', 'drisheen'], + 'density': ['density', 'destiny'], + 'dent': ['dent', 'tend'], + 'dental': ['dental', 'tandle'], + 'dentale': ['dalteen', 'dentale', 'edental'], + 'dentalism': ['dentalism', 'dismantle'], + 'dentaria': ['anteriad', 'atridean', 'dentaria'], + 'dentatoserrate': ['dentatoserrate', 'serratodentate'], + 'dentatosinuate': ['dentatosinuate', 'sinuatodentate'], + 'denter': ['denter', 'rented', 'tender'], + 'dentex': ['dentex', 'extend'], + 'denticle': ['cliented', 'denticle'], + 'denticular': ['denticular', 'unarticled'], + 'dentil': ['delint', 'dentil'], + 'dentilingual': ['dentilingual', 'indulgential', 'linguidental'], + 'dentin': ['dentin', 'indent', 'intend', 'tinned'], + 'dentinal': ['dentinal', 'teinland', 'tendinal'], + 'dentine': ['dentine', 'nineted'], + 'dentinitis': ['dentinitis', 'tendinitis'], + 'dentinoma': ['dentinoma', 'nominated'], + 'dentist': ['dentist', 'distent', 'stinted'], + 'dentolabial': ['dentolabial', 'labiodental'], + 'dentolingual': ['dentolingual', 'linguodental'], + 'denture': ['denture', 'untreed'], + 'denudative': ['denudative', 'undeviated'], + 'denude': ['denude', 'dudeen'], + 'denumeral': ['denumeral', 'undermeal', 'unrealmed'], + 'denunciator': ['denunciator', 'underaction'], + 'deny': ['deny', 'dyne'], + 'deoppilant': ['deoppilant', 'pentaploid'], + 'deota': ['deota', 'todea'], + 'depa': ['depa', 'peda'], + 'depaint': ['depaint', 'inadept', 'painted', 'patined'], + 'depart': ['depart', 'parted', 'petard'], + 'departition': ['departition', 'partitioned', 'trepidation'], + 'departure': ['apertured', 'departure'], + 'depas': ['depas', 'sepad', 'spade'], + 'depencil': ['depencil', 'penciled', 'pendicle'], + 'depender': ['depender', 'redepend'], + 'depetticoat': ['depetticoat', 'petticoated'], + 'depicter': ['decrepit', 'depicter', 'precited'], + 'depiction': ['depiction', 'pectinoid'], + 'depilate': ['depilate', 'leptidae', 'pileated'], + 'depletion': ['depletion', 'diplotene'], + 'deploration': ['deploration', 'periodontal'], + 'deploy': ['deploy', 'podley'], + 'depoh': ['depoh', 'ephod', 'hoped'], + 'depolish': ['depolish', 'polished'], + 'deport': ['deport', 'ported', 'redtop'], + 'deportation': ['antitorpedo', 'deportation'], + 'deposal': ['adelops', 'deposal'], + 'deposer': ['deposer', 'reposed'], + 'deposit': ['deposit', 'topside'], + 'deposition': ['deposition', 'positioned'], + 'depositional': ['depositional', 'despoliation'], + 'depositure': ['depositure', 'pterideous'], + 'deprave': ['deprave', 'pervade'], + 'depraver': ['depraver', 'pervader'], + 'depravingly': ['depravingly', 'pervadingly'], + 'deprecable': ['deprecable', 'precedable'], + 'deprecation': ['capernoited', 'deprecation'], + 'depreciation': ['depreciation', 'predeication'], + 'depressant': ['depressant', 'partedness'], + 'deprint': ['deprint', 'printed'], + 'deprival': ['deprival', 'prevalid'], + 'deprivate': ['deprivate', 'predative'], + 'deprive': ['deprive', 'previde'], + 'depriver': ['depriver', 'predrive'], + 'depurant': ['depurant', 'unparted'], + 'depuration': ['depuration', 'portunidae'], + 'deraign': ['degrain', 'deraign', 'deringa', 'gradine', 'grained', 'reading'], + 'derail': ['ariled', 'derail', 'dialer'], + 'derailment': ['delirament', 'derailment'], + 'derange': ['derange', 'enraged', 'gardeen', 'gerenda', 'grandee', 'grenade'], + 'deranged': ['deranged', 'gardened'], + 'deranger': ['deranger', 'gardener'], + 'derat': ['dater', 'derat', 'detar', 'drate', 'rated', 'trade', 'tread'], + 'derate': ['derate', 'redate'], + 'derater': ['derater', 'retrade', 'retread', 'treader'], + 'deray': ['deary', 'deray', 'rayed', 'ready', 'yeard'], + 'dere': ['deer', 'dere', 'dree', 'rede', 'reed'], + 'deregister': ['deregister', 'registered'], + 'derelict': ['derelict', 'relicted'], + 'deric': ['cider', 'cried', 'deric', 'dicer'], + 'derider': ['deirdre', 'derider', 'derride', 'ridered'], + 'deringa': ['degrain', 'deraign', 'deringa', 'gradine', 'grained', 'reading'], + 'derision': ['derision', 'ironside', 'resinoid', 'sirenoid'], + 'derivation': ['derivation', 'ordinative'], + 'derivational': ['derivational', 'revalidation'], + 'derive': ['derive', 'redive'], + 'deriver': ['deriver', 'redrive', 'rivered'], + 'derma': ['armed', 'derma', 'dream', 'ramed'], + 'dermad': ['dermad', 'madder'], + 'dermal': ['dermal', 'marled', 'medlar'], + 'dermatic': ['dermatic', 'timecard'], + 'dermatine': ['demetrian', 'dermatine', 'meandrite', 'minareted'], + 'dermatoneurosis': ['dermatoneurosis', 'neurodermatosis'], + 'dermatophone': ['dermatophone', 'herpetomonad'], + 'dermoblast': ['blastoderm', 'dermoblast'], + 'dermol': ['dermol', 'molder', 'remold'], + 'dermosclerite': ['dermosclerite', 'sclerodermite'], + 'dern': ['dern', 'rend'], + 'derogately': ['delegatory', 'derogately'], + 'derogation': ['derogation', 'trogonidae'], + 'derout': ['derout', 'detour', 'douter'], + 'derride': ['deirdre', 'derider', 'derride', 'ridered'], + 'derries': ['derries', 'desirer', 'resider', 'serried'], + 'derringer': ['derringer', 'regrinder'], + 'derry': ['derry', 'redry', 'ryder'], + 'derust': ['derust', 'duster'], + 'desalt': ['desalt', 'salted'], + 'desand': ['desand', 'sadden', 'sanded'], + 'desaurin': ['denarius', 'desaurin', 'unraised'], + 'descendant': ['adscendent', 'descendant'], + 'descender': ['descender', 'redescend'], + 'descent': ['descent', 'scented'], + 'description': ['description', 'discerption'], + 'desecrate': ['decastere', 'desecrate'], + 'desecration': ['considerate', 'desecration'], + 'deseed': ['deseed', 'seeded'], + 'desertic': ['creedist', 'desertic', 'discreet', 'discrete'], + 'desertion': ['desertion', 'detersion'], + 'deserver': ['deserver', 'reserved', 'reversed'], + 'desex': ['desex', 'sexed'], + 'deshabille': ['deshabille', 'shieldable'], + 'desi': ['desi', 'ides', 'seid', 'side'], + 'desiccation': ['desiccation', 'discoactine'], + 'desight': ['desight', 'sighted'], + 'design': ['design', 'singed'], + 'designer': ['designer', 'redesign', 'resigned'], + 'desilver': ['desilver', 'silvered'], + 'desirable': ['desirable', 'redisable'], + 'desire': ['desire', 'reside'], + 'desirer': ['derries', 'desirer', 'resider', 'serried'], + 'desirous': ['desirous', 'siderous'], + 'desition': ['desition', 'sedition'], + 'desma': ['desma', 'mesad'], + 'desman': ['amends', 'desman'], + 'desmopathy': ['desmopathy', 'phymatodes'], + 'desorption': ['desorption', 'priodontes'], + 'despair': ['despair', 'pardesi'], + 'despairing': ['despairing', 'spinigrade'], + 'desperation': ['desperation', 'esperantido'], + 'despise': ['despise', 'pedesis'], + 'despiser': ['despiser', 'disperse'], + 'despoil': ['despoil', 'soliped', 'spoiled'], + 'despoiler': ['despoiler', 'leprosied'], + 'despoliation': ['depositional', 'despoliation'], + 'despot': ['despot', 'posted'], + 'despotat': ['despotat', 'postdate'], + 'dessert': ['dessert', 'tressed'], + 'destain': ['destain', 'instead', 'sainted', 'satined'], + 'destine': ['destine', 'edestin'], + 'destinism': ['destinism', 'timidness'], + 'destiny': ['density', 'destiny'], + 'desugar': ['desugar', 'sugared'], + 'detail': ['detail', 'dietal', 'dilate', 'edital', 'tailed'], + 'detailer': ['detailer', 'elaterid'], + 'detain': ['danite', 'detain'], + 'detainal': ['danalite', 'detainal'], + 'detar': ['dater', 'derat', 'detar', 'drate', 'rated', 'trade', 'tread'], + 'detassel': ['dateless', 'detassel'], + 'detax': ['detax', 'taxed'], + 'detecter': ['detecter', 'redetect'], + 'detent': ['detent', 'netted', 'tented'], + 'deter': ['deter', 'treed'], + 'determinant': ['determinant', 'detrainment'], + 'detersion': ['desertion', 'detersion'], + 'detest': ['detest', 'tested'], + 'dethrone': ['dethrone', 'threnode'], + 'detin': ['detin', 'teind', 'tined'], + 'detinet': ['detinet', 'dinette'], + 'detonable': ['denotable', 'detonable'], + 'detonation': ['denotation', 'detonation'], + 'detonative': ['denotative', 'detonative'], + 'detonator': ['detonator', 'tetraodon'], + 'detour': ['derout', 'detour', 'douter'], + 'detracter': ['detracter', 'retracted'], + 'detraction': ['detraction', 'doctrinate', 'tetarconid'], + 'detrain': ['antired', 'detrain', 'randite', 'trained'], + 'detrainment': ['determinant', 'detrainment'], + 'detrusion': ['detrusion', 'tinderous', 'unstoried'], + 'detrusive': ['detrusive', 'divesture', 'servitude'], + 'deuce': ['deuce', 'educe'], + 'deuced': ['deduce', 'deuced'], + 'deul': ['deul', 'duel', 'leud'], + 'deva': ['dave', 'deva', 'vade', 'veda'], + 'devance': ['devance', 'vendace'], + 'develin': ['develin', 'endevil'], + 'developer': ['developer', 'redevelop'], + 'devil': ['devil', 'divel', 'lived'], + 'deviler': ['deliver', 'deviler', 'livered'], + 'devisceration': ['considerative', 'devisceration'], + 'deviser': ['deviser', 'diverse', 'revised'], + 'devitrify': ['devitrify', 'fervidity'], + 'devoid': ['devoid', 'voided'], + 'devoir': ['devoir', 'voider'], + 'devonite': ['denotive', 'devonite'], + 'devourer': ['devourer', 'overdure', 'overrude'], + 'devow': ['devow', 'vowed'], + 'dew': ['dew', 'wed'], + 'dewan': ['awned', 'dewan', 'waned'], + 'dewater': ['dewater', 'tarweed', 'watered'], + 'dewer': ['dewer', 'ewder', 'rewed'], + 'dewey': ['dewey', 'weedy'], + 'dewily': ['dewily', 'widely', 'wieldy'], + 'dewiness': ['dewiness', 'wideness'], + 'dewool': ['dewool', 'elwood', 'wooled'], + 'deworm': ['deworm', 'wormed'], + 'dewy': ['dewy', 'wyde'], + 'dextraural': ['dextraural', 'extradural'], + 'dextrosinistral': ['dextrosinistral', 'sinistrodextral'], + 'dey': ['dey', 'dye', 'yed'], + 'deyhouse': ['deyhouse', 'dyehouse'], + 'deyship': ['deyship', 'diphyes'], + 'dezinc': ['dezinc', 'zendic'], + 'dha': ['dah', 'dha', 'had'], + 'dhamnoo': ['dhamnoo', 'hoodman', 'manhood'], + 'dhan': ['dhan', 'hand'], + 'dharna': ['andhra', 'dharna'], + 'dheri': ['dheri', 'hider', 'hired'], + 'dhobi': ['bodhi', 'dhobi'], + 'dhoon': ['dhoon', 'hondo'], + 'dhu': ['dhu', 'hud'], + 'di': ['di', 'id'], + 'diabolist': ['diabolist', 'idioblast'], + 'diacetin': ['diacetin', 'indicate'], + 'diacetine': ['deciatine', 'diacetine', 'taenicide', 'teniacide'], + 'diacetyl': ['diacetyl', 'lyctidae'], + 'diachoretic': ['citharoedic', 'diachoretic'], + 'diaclase': ['diaclase', 'sidalcea'], + 'diaconal': ['cladonia', 'condalia', 'diaconal'], + 'diact': ['diact', 'dicta'], + 'diadem': ['diadem', 'mediad'], + 'diaderm': ['admired', 'diaderm'], + 'diaeretic': ['diaeretic', 'icteridae'], + 'diagenetic': ['diagenetic', 'digenetica'], + 'diageotropism': ['diageotropism', 'geodiatropism'], + 'diagonal': ['diagonal', 'ganoidal', 'gonadial'], + 'dial': ['dail', 'dali', 'dial', 'laid', 'lida'], + 'dialect': ['citadel', 'deltaic', 'dialect', 'edictal', 'lactide'], + 'dialectic': ['deictical', 'dialectic'], + 'dialector': ['dialector', 'lacertoid'], + 'dialer': ['ariled', 'derail', 'dialer'], + 'dialin': ['anilid', 'dialin', 'dianil', 'inlaid'], + 'dialing': ['dialing', 'gliadin'], + 'dialister': ['dialister', 'trailside'], + 'diallelon': ['diallelon', 'llandeilo'], + 'dialogism': ['dialogism', 'sigmoidal'], + 'dialystelic': ['deistically', 'dialystelic'], + 'dialytic': ['calidity', 'dialytic'], + 'diamagnet': ['agminated', 'diamagnet'], + 'diamantine': ['diamantine', 'inanimated'], + 'diameter': ['diameter', 'diatreme'], + 'diametric': ['citramide', 'diametric', 'matricide'], + 'diamide': ['amidide', 'diamide', 'mididae'], + 'diamine': ['amidine', 'diamine'], + 'diamorphine': ['diamorphine', 'phronimidae'], + 'dian': ['adin', 'andi', 'dain', 'dani', 'dian', 'naid'], + 'diana': ['danai', 'diana', 'naiad'], + 'diander': ['diander', 'drained'], + 'diane': ['diane', 'idean'], + 'dianetics': ['andesitic', 'dianetics'], + 'dianil': ['anilid', 'dialin', 'dianil', 'inlaid'], + 'diapensia': ['diapensia', 'diaspinae'], + 'diaper': ['diaper', 'paired'], + 'diaphote': ['diaphote', 'hepatoid'], + 'diaphtherin': ['diaphtherin', 'diphtherian'], + 'diapnoic': ['diapnoic', 'pinacoid'], + 'diapnotic': ['antipodic', 'diapnotic'], + 'diaporthe': ['aphrodite', 'atrophied', 'diaporthe'], + 'diarch': ['chidra', 'diarch'], + 'diarchial': ['diarchial', 'rachidial'], + 'diarchy': ['diarchy', 'hyracid'], + 'diarian': ['aridian', 'diarian'], + 'diary': ['dairy', 'diary', 'yaird'], + 'diascia': ['ascidia', 'diascia'], + 'diascope': ['diascope', 'psocidae', 'scopidae'], + 'diaspinae': ['diapensia', 'diaspinae'], + 'diastem': ['diastem', 'misdate'], + 'diastema': ['adamsite', 'diastema'], + 'diaster': ['astride', 'diaster', 'disrate', 'restiad', 'staired'], + 'diastole': ['diastole', 'isolated', 'sodalite', 'solidate'], + 'diastrophic': ['aphrodistic', 'diastrophic'], + 'diastrophy': ['diastrophy', 'dystrophia'], + 'diatomales': ['diatomales', 'mastoidale', 'mastoideal'], + 'diatomean': ['diatomean', 'mantoidea'], + 'diatomin': ['diatomin', 'domitian'], + 'diatonic': ['actinoid', 'diatonic', 'naticoid'], + 'diatreme': ['diameter', 'diatreme'], + 'diatropism': ['diatropism', 'prismatoid'], + 'dib': ['bid', 'dib'], + 'dibatis': ['dabitis', 'dibatis'], + 'dibber': ['dibber', 'ribbed'], + 'dibbler': ['dibbler', 'dribble'], + 'dibrom': ['dibrom', 'morbid'], + 'dicaryon': ['cynaroid', 'dicaryon'], + 'dicast': ['dicast', 'stadic'], + 'dice': ['dice', 'iced'], + 'dicentra': ['crinated', 'dicentra'], + 'dicer': ['cider', 'cried', 'deric', 'dicer'], + 'diceras': ['diceras', 'radices', 'sidecar'], + 'dich': ['chid', 'dich'], + 'dichroite': ['dichroite', 'erichtoid', 'theriodic'], + 'dichromat': ['chromatid', 'dichromat'], + 'dichter': ['dichter', 'ditcher'], + 'dicolic': ['codicil', 'dicolic'], + 'dicolon': ['dicolon', 'dolcino'], + 'dicoumarin': ['acridonium', 'dicoumarin'], + 'dicta': ['diact', 'dicta'], + 'dictaphone': ['dictaphone', 'endopathic'], + 'dictational': ['antidotical', 'dictational'], + 'dictionary': ['dictionary', 'indicatory'], + 'dicyanine': ['cyanidine', 'dicyanine'], + 'dicycle': ['cyclide', 'decylic', 'dicycle'], + 'dicyema': ['dicyema', 'mediacy'], + 'diddle': ['diddle', 'lidded'], + 'diddler': ['diddler', 'driddle'], + 'didym': ['didym', 'middy'], + 'die': ['die', 'ide'], + 'dieb': ['beid', 'bide', 'debi', 'dieb'], + 'diego': ['diego', 'dogie', 'geoid'], + 'dielytra': ['dielytra', 'tileyard'], + 'diem': ['demi', 'diem', 'dime', 'mide'], + 'dier': ['dier', 'dire', 'reid', 'ride'], + 'diesel': ['diesel', 'sedile', 'seidel'], + 'diet': ['diet', 'dite', 'edit', 'tide', 'tied'], + 'dietal': ['detail', 'dietal', 'dilate', 'edital', 'tailed'], + 'dieter': ['dieter', 'tiered'], + 'dietic': ['citied', 'dietic'], + 'dietics': ['deistic', 'dietics'], + 'dig': ['dig', 'gid'], + 'digenetica': ['diagenetic', 'digenetica'], + 'digeny': ['digeny', 'dyeing'], + 'digester': ['digester', 'redigest'], + 'digitalein': ['digitalein', 'diligentia'], + 'digitation': ['digitation', 'goniatitid'], + 'digitonin': ['digitonin', 'indigotin'], + 'digredient': ['digredient', 'reddingite'], + 'dihalo': ['dihalo', 'haloid'], + 'diiambus': ['basidium', 'diiambus'], + 'dika': ['dika', 'kaid'], + 'dikaryon': ['ankyroid', 'dikaryon'], + 'dike': ['dike', 'keid'], + 'dilacerate': ['dilacerate', 'lacertidae'], + 'dilatant': ['atlantid', 'dilatant'], + 'dilate': ['detail', 'dietal', 'dilate', 'edital', 'tailed'], + 'dilater': ['dilater', 'lardite', 'redtail'], + 'dilatometric': ['calotermitid', 'dilatometric'], + 'dilator': ['dilator', 'ortalid'], + 'dilatory': ['adroitly', 'dilatory', 'idolatry'], + 'diligence': ['ceilinged', 'diligence'], + 'diligentia': ['digitalein', 'diligentia'], + 'dillue': ['dillue', 'illude'], + 'dilluer': ['dilluer', 'illuder'], + 'dilo': ['dilo', 'diol', 'doli', 'idol', 'olid'], + 'diluent': ['diluent', 'untiled'], + 'dilute': ['dilute', 'dultie'], + 'diluted': ['diluted', 'luddite'], + 'dilutent': ['dilutent', 'untilted', 'untitled'], + 'diluvian': ['diluvian', 'induvial'], + 'dim': ['dim', 'mid'], + 'dimatis': ['amidist', 'dimatis'], + 'dimble': ['dimble', 'limbed'], + 'dime': ['demi', 'diem', 'dime', 'mide'], + 'dimer': ['dimer', 'mider'], + 'dimera': ['admire', 'armied', 'damier', 'dimera', 'merida'], + 'dimeran': ['adermin', 'amerind', 'dimeran'], + 'dimerous': ['dimerous', 'soredium'], + 'dimeter': ['demerit', 'dimeter', 'merited', 'mitered'], + 'dimetria': ['dimetria', 'mitridae', 'tiremaid', 'triamide'], + 'diminisher': ['diminisher', 'rediminish'], + 'dimit': ['dimit', 'timid'], + 'dimmer': ['dimmer', 'immerd', 'rimmed'], + 'dimna': ['dimna', 'manid'], + 'dimyarian': ['dimyarian', 'myrianida'], + 'din': ['din', 'ind', 'nid'], + 'dinah': ['ahind', 'dinah'], + 'dinar': ['darin', 'dinar', 'drain', 'indra', 'nadir', 'ranid'], + 'dinder': ['dinder', 'ridden', 'rinded'], + 'dindle': ['dindle', 'niddle'], + 'dine': ['dine', 'enid', 'inde', 'nide'], + 'diner': ['diner', 'riden', 'rinde'], + 'dinergate': ['argentide', 'denigrate', 'dinergate'], + 'dinero': ['dinero', 'dorine'], + 'dinette': ['detinet', 'dinette'], + 'dineuric': ['dineuric', 'eurindic'], + 'dingar': ['daring', 'dingar', 'gradin'], + 'dinge': ['deign', 'dinge', 'nidge'], + 'dingle': ['dingle', 'elding', 'engild', 'gilden'], + 'dingo': ['dingo', 'doing', 'gondi', 'gonid'], + 'dingwall': ['dingwall', 'windgall'], + 'dingy': ['dingy', 'dying'], + 'dinheiro': ['dinheiro', 'hernioid'], + 'dinic': ['dinic', 'indic'], + 'dining': ['dining', 'indign', 'niding'], + 'dink': ['dink', 'kind'], + 'dinkey': ['dinkey', 'kidney'], + 'dinocerata': ['arctoidean', 'carotidean', 'cordaitean', 'dinocerata'], + 'dinoceratan': ['carnationed', 'dinoceratan'], + 'dinomic': ['dinomic', 'dominic'], + 'dint': ['dint', 'tind'], + 'dinus': ['dinus', 'indus', 'nidus'], + 'dioeciopolygamous': ['dioeciopolygamous', 'polygamodioecious'], + 'diogenite': ['diogenite', 'gideonite'], + 'diol': ['dilo', 'diol', 'doli', 'idol', 'olid'], + 'dion': ['dion', 'nodi', 'odin'], + 'dione': ['deino', 'dione', 'edoni'], + 'diopter': ['diopter', 'peridot', 'proetid', 'protide', 'pteroid'], + 'dioptra': ['dioptra', 'parotid'], + 'dioptral': ['dioptral', 'tripodal'], + 'dioptric': ['dioptric', 'tripodic'], + 'dioptrical': ['dioptrical', 'tripodical'], + 'dioptry': ['dioptry', 'tripody'], + 'diorama': ['amaroid', 'diorama'], + 'dioramic': ['dioramic', 'dromicia'], + 'dioscorein': ['dioscorein', 'dioscorine'], + 'dioscorine': ['dioscorein', 'dioscorine'], + 'dioscuri': ['dioscuri', 'sciuroid'], + 'diose': ['diose', 'idose', 'oside'], + 'diosmin': ['diosmin', 'odinism'], + 'diosmotic': ['diosmotic', 'sodomitic'], + 'diparentum': ['diparentum', 'unimparted'], + 'dipetto': ['dipetto', 'diptote'], + 'diphase': ['aphides', 'diphase'], + 'diphaser': ['diphaser', 'parished', 'raphides', 'sephardi'], + 'diphosphate': ['diphosphate', 'phosphatide'], + 'diphtherian': ['diaphtherin', 'diphtherian'], + 'diphyes': ['deyship', 'diphyes'], + 'diphysite': ['deityship', 'diphysite'], + 'dipicrate': ['dipicrate', 'patricide', 'pediatric'], + 'diplanar': ['diplanar', 'prandial'], + 'diplasion': ['aspidinol', 'diplasion'], + 'dipleura': ['dipleura', 'epidural'], + 'dipleural': ['dipleural', 'preludial'], + 'diplocephalus': ['diplocephalus', 'pseudophallic'], + 'diploe': ['diploe', 'dipole'], + 'diploetic': ['diploetic', 'lepidotic'], + 'diplotene': ['depletion', 'diplotene'], + 'dipnoan': ['dipnoan', 'nonpaid', 'pandion'], + 'dipolar': ['dipolar', 'polarid'], + 'dipole': ['diploe', 'dipole'], + 'dipsaceous': ['dipsaceous', 'spadiceous'], + 'dipter': ['dipter', 'trepid'], + 'dipteraceous': ['dipteraceous', 'epiceratodus'], + 'dipteral': ['dipteral', 'tripedal'], + 'dipterological': ['dipterological', 'pteridological'], + 'dipterologist': ['dipterologist', 'pteridologist'], + 'dipterology': ['dipterology', 'pteridology'], + 'dipteros': ['dipteros', 'portside'], + 'diptote': ['dipetto', 'diptote'], + 'dirca': ['acrid', 'caird', 'carid', 'darci', 'daric', 'dirca'], + 'dircaean': ['caridean', 'dircaean', 'radiance'], + 'dire': ['dier', 'dire', 'reid', 'ride'], + 'direct': ['credit', 'direct'], + 'directable': ['creditable', 'directable'], + 'directer': ['cedriret', 'directer', 'recredit', 'redirect'], + 'direction': ['cretinoid', 'direction'], + 'directional': ['clitoridean', 'directional'], + 'directive': ['creditive', 'directive'], + 'directly': ['directly', 'tridecyl'], + 'directoire': ['cordierite', 'directoire'], + 'director': ['creditor', 'director'], + 'directorship': ['creditorship', 'directorship'], + 'directress': ['creditress', 'directress'], + 'directrix': ['creditrix', 'directrix'], + 'direly': ['direly', 'idyler'], + 'direption': ['direption', 'perdition', 'tropidine'], + 'dirge': ['dirge', 'gride', 'redig', 'ridge'], + 'dirgelike': ['dirgelike', 'ridgelike'], + 'dirgeman': ['dirgeman', 'margined', 'midrange'], + 'dirgler': ['dirgler', 'girdler'], + 'dirten': ['dirten', 'rident', 'tinder'], + 'dis': ['dis', 'sid'], + 'disa': ['dais', 'dasi', 'disa', 'said', 'sida'], + 'disadventure': ['disadventure', 'unadvertised'], + 'disappearer': ['disappearer', 'redisappear'], + 'disarmed': ['disarmed', 'misdread'], + 'disastimeter': ['disastimeter', 'semistriated'], + 'disattire': ['disattire', 'distraite'], + 'disbud': ['disbud', 'disdub'], + 'disburse': ['disburse', 'subsider'], + 'discastle': ['clidastes', 'discastle'], + 'discern': ['discern', 'rescind'], + 'discerner': ['discerner', 'rescinder'], + 'discernment': ['discernment', 'rescindment'], + 'discerp': ['crisped', 'discerp'], + 'discerption': ['description', 'discerption'], + 'disclike': ['disclike', 'sicklied'], + 'discoactine': ['desiccation', 'discoactine'], + 'discoid': ['discoid', 'disodic'], + 'discontinuer': ['discontinuer', 'undiscretion'], + 'discounter': ['discounter', 'rediscount'], + 'discoverer': ['discoverer', 'rediscover'], + 'discreate': ['discreate', 'sericated'], + 'discreet': ['creedist', 'desertic', 'discreet', 'discrete'], + 'discreetly': ['discreetly', 'discretely'], + 'discreetness': ['discreetness', 'discreteness'], + 'discrepate': ['discrepate', 'pederastic'], + 'discrete': ['creedist', 'desertic', 'discreet', 'discrete'], + 'discretely': ['discreetly', 'discretely'], + 'discreteness': ['discreetness', 'discreteness'], + 'discretion': ['discretion', 'soricident'], + 'discriminator': ['discriminator', 'doctrinairism'], + 'disculpate': ['disculpate', 'spiculated'], + 'discusser': ['discusser', 'rediscuss'], + 'discutable': ['discutable', 'subdeltaic', 'subdialect'], + 'disdub': ['disbud', 'disdub'], + 'disease': ['disease', 'seaside'], + 'diseme': ['demise', 'diseme'], + 'disenact': ['disenact', 'distance'], + 'disendow': ['disendow', 'downside'], + 'disentwine': ['disentwine', 'indentwise'], + 'disharmony': ['disharmony', 'hydramnios'], + 'dishearten': ['dishearten', 'intershade'], + 'dished': ['dished', 'eddish'], + 'disherent': ['disherent', 'hinderest', 'tenderish'], + 'dishling': ['dishling', 'hidlings'], + 'dishonor': ['dishonor', 'ironshod'], + 'dishorn': ['dishorn', 'dronish'], + 'dishpan': ['daphnis', 'dishpan'], + 'disilicate': ['disilicate', 'idealistic'], + 'disimprove': ['disimprove', 'misprovide'], + 'disk': ['disk', 'kids', 'skid'], + 'dislocate': ['dislocate', 'lactoside'], + 'disman': ['danism', 'disman'], + 'dismantle': ['dentalism', 'dismantle'], + 'disme': ['deism', 'disme'], + 'dismemberer': ['dismemberer', 'disremember'], + 'disnature': ['disnature', 'sturnidae', 'truandise'], + 'disnest': ['disnest', 'dissent'], + 'disodic': ['discoid', 'disodic'], + 'disparage': ['disparage', 'grapsidae'], + 'disparation': ['disparation', 'tridiapason'], + 'dispatcher': ['dispatcher', 'redispatch'], + 'dispensable': ['dispensable', 'piebaldness'], + 'dispense': ['dispense', 'piedness'], + 'disperse': ['despiser', 'disperse'], + 'dispetal': ['dispetal', 'pedalist'], + 'dispireme': ['dispireme', 'epidermis'], + 'displayer': ['displayer', 'redisplay'], + 'displeaser': ['displeaser', 'pearlsides'], + 'disponee': ['disponee', 'openside'], + 'disporum': ['disporum', 'misproud'], + 'disprepare': ['disprepare', 'predespair'], + 'disrate': ['astride', 'diaster', 'disrate', 'restiad', 'staired'], + 'disremember': ['dismemberer', 'disremember'], + 'disrepute': ['disrepute', 'redispute'], + 'disrespect': ['disrespect', 'disscepter'], + 'disrupt': ['disrupt', 'prudist'], + 'disscepter': ['disrespect', 'disscepter'], + 'disseat': ['disseat', 'sestiad'], + 'dissector': ['crosstied', 'dissector'], + 'dissent': ['disnest', 'dissent'], + 'dissenter': ['dissenter', 'tiredness'], + 'dissertate': ['dissertate', 'statesider'], + 'disserve': ['disserve', 'dissever'], + 'dissever': ['disserve', 'dissever'], + 'dissocial': ['cissoidal', 'dissocial'], + 'dissolve': ['dissolve', 'voidless'], + 'dissoul': ['dissoul', 'dulosis', 'solidus'], + 'distale': ['distale', 'salited'], + 'distance': ['disenact', 'distance'], + 'distant': ['dantist', 'distant'], + 'distater': ['distater', 'striated'], + 'distender': ['dendrites', 'distender', 'redistend'], + 'distent': ['dentist', 'distent', 'stinted'], + 'distich': ['distich', 'stichid'], + 'distillage': ['distillage', 'sigillated'], + 'distiller': ['distiller', 'redistill'], + 'distinguisher': ['distinguisher', 'redistinguish'], + 'distoma': ['distoma', 'mastoid'], + 'distome': ['distome', 'modiste'], + 'distrainer': ['distrainer', 'redistrain'], + 'distrait': ['distrait', 'triadist'], + 'distraite': ['disattire', 'distraite'], + 'disturber': ['disturber', 'redisturb'], + 'disulphone': ['disulphone', 'unpolished'], + 'disuniform': ['disuniform', 'indusiform'], + 'dit': ['dit', 'tid'], + 'dita': ['adit', 'dita'], + 'dital': ['datil', 'dital', 'tidal', 'tilda'], + 'ditcher': ['dichter', 'ditcher'], + 'dite': ['diet', 'dite', 'edit', 'tide', 'tied'], + 'diter': ['diter', 'tired', 'tried'], + 'dithionic': ['chitinoid', 'dithionic'], + 'ditone': ['ditone', 'intoed'], + 'ditrochean': ['achondrite', 'ditrochean', 'ordanchite'], + 'diuranate': ['diuranate', 'untiaraed'], + 'diurna': ['danuri', 'diurna', 'dunair', 'durain', 'durani', 'durian'], + 'diurnation': ['diurnation', 'induration'], + 'diurne': ['diurne', 'inured', 'ruined', 'unride'], + 'diva': ['avid', 'diva'], + 'divan': ['divan', 'viand'], + 'divata': ['divata', 'dvaita'], + 'divel': ['devil', 'divel', 'lived'], + 'diver': ['diver', 'drive'], + 'diverge': ['diverge', 'grieved'], + 'diverse': ['deviser', 'diverse', 'revised'], + 'diverter': ['diverter', 'redivert', 'verditer'], + 'divest': ['divest', 'vedist'], + 'divesture': ['detrusive', 'divesture', 'servitude'], + 'divisionism': ['divisionism', 'misdivision'], + 'divorce': ['cervoid', 'divorce'], + 'divorcee': ['coderive', 'divorcee'], + 'do': ['do', 'od'], + 'doable': ['albedo', 'doable'], + 'doarium': ['doarium', 'uramido'], + 'doat': ['doat', 'toad', 'toda'], + 'doater': ['doater', 'toader'], + 'doating': ['antigod', 'doating'], + 'doatish': ['doatish', 'toadish'], + 'dob': ['bod', 'dob'], + 'dobe': ['bode', 'dobe'], + 'dobra': ['abord', 'bardo', 'board', 'broad', 'dobra', 'dorab'], + 'dobrao': ['dobrao', 'doorba'], + 'doby': ['body', 'boyd', 'doby'], + 'doc': ['cod', 'doc'], + 'docetism': ['comedist', 'demotics', 'docetism', 'domestic'], + 'docile': ['cleoid', 'coiled', 'docile'], + 'docity': ['cytoid', 'docity'], + 'docker': ['corked', 'docker', 'redock'], + 'doctorial': ['crotaloid', 'doctorial'], + 'doctorship': ['doctorship', 'trophodisc'], + 'doctrinairism': ['discriminator', 'doctrinairism'], + 'doctrinate': ['detraction', 'doctrinate', 'tetarconid'], + 'doctrine': ['centroid', 'doctrine'], + 'documental': ['columnated', 'documental'], + 'dod': ['dod', 'odd'], + 'dode': ['dedo', 'dode', 'eddo'], + 'dodecarch': ['decachord', 'dodecarch'], + 'dodlet': ['dodlet', 'toddle'], + 'dodman': ['dodman', 'oddman'], + 'doe': ['doe', 'edo', 'ode'], + 'doeg': ['doeg', 'doge', 'gode'], + 'doer': ['doer', 'redo', 'rode', 'roed'], + 'does': ['does', 'dose'], + 'doesnt': ['doesnt', 'stoned'], + 'dog': ['dog', 'god'], + 'dogate': ['dogate', 'dotage', 'togaed'], + 'dogbane': ['bondage', 'dogbane'], + 'dogbite': ['bigoted', 'dogbite'], + 'doge': ['doeg', 'doge', 'gode'], + 'dogger': ['dogger', 'gorged'], + 'doghead': ['doghead', 'godhead'], + 'doghood': ['doghood', 'godhood'], + 'dogie': ['diego', 'dogie', 'geoid'], + 'dogless': ['dogless', 'glossed', 'godless'], + 'doglike': ['doglike', 'godlike'], + 'dogly': ['dogly', 'godly', 'goldy'], + 'dogra': ['dargo', 'dogra', 'drago'], + 'dogship': ['dogship', 'godship'], + 'dogstone': ['dogstone', 'stegodon'], + 'dogwatch': ['dogwatch', 'watchdog'], + 'doina': ['adion', 'danio', 'doina', 'donia'], + 'doing': ['dingo', 'doing', 'gondi', 'gonid'], + 'doko': ['doko', 'dook'], + 'dol': ['dol', 'lod', 'old'], + 'dola': ['alod', 'dola', 'load', 'odal'], + 'dolcian': ['dolcian', 'nodical'], + 'dolciano': ['conoidal', 'dolciano'], + 'dolcino': ['dicolon', 'dolcino'], + 'dole': ['dole', 'elod', 'lode', 'odel'], + 'dolesman': ['dolesman', 'lodesman'], + 'doless': ['doless', 'dossel'], + 'doli': ['dilo', 'diol', 'doli', 'idol', 'olid'], + 'dolia': ['aloid', 'dolia', 'idola'], + 'dolina': ['dolina', 'ladino'], + 'doline': ['doline', 'indole', 'leonid', 'loined', 'olenid'], + 'dolium': ['dolium', 'idolum'], + 'dolly': ['dolly', 'lloyd'], + 'dolman': ['almond', 'dolman'], + 'dolor': ['dolor', 'drool'], + 'dolose': ['dolose', 'oodles', 'soodle'], + 'dolphin': ['dolphin', 'pinhold'], + 'dolt': ['dolt', 'told'], + 'dom': ['dom', 'mod'], + 'domain': ['amidon', 'daimon', 'domain'], + 'domainal': ['domainal', 'domanial'], + 'domal': ['domal', 'modal'], + 'domanial': ['domainal', 'domanial'], + 'dome': ['dome', 'mode', 'moed'], + 'domer': ['domer', 'drome'], + 'domestic': ['comedist', 'demotics', 'docetism', 'domestic'], + 'domic': ['comid', 'domic'], + 'domical': ['domical', 'lacmoid'], + 'dominance': ['demicanon', 'dominance'], + 'dominate': ['dominate', 'nematoid'], + 'dominated': ['demantoid', 'dominated'], + 'domination': ['admonition', 'domination'], + 'dominative': ['admonitive', 'dominative'], + 'dominator': ['admonitor', 'dominator'], + 'domine': ['domine', 'domnei', 'emodin', 'medino'], + 'dominial': ['dominial', 'imolinda', 'limoniad'], + 'dominic': ['dinomic', 'dominic'], + 'domino': ['domino', 'monoid'], + 'domitian': ['diatomin', 'domitian'], + 'domnei': ['domine', 'domnei', 'emodin', 'medino'], + 'don': ['don', 'nod'], + 'donal': ['donal', 'nodal'], + 'donar': ['adorn', 'donar', 'drona', 'radon'], + 'donated': ['donated', 'nodated'], + 'donatiaceae': ['actaeonidae', 'donatiaceae'], + 'donatism': ['donatism', 'saintdom'], + 'donator': ['donator', 'odorant', 'tornado'], + 'done': ['done', 'node'], + 'donet': ['donet', 'noted', 'toned'], + 'dong': ['dong', 'gond'], + 'donga': ['donga', 'gonad'], + 'dongola': ['dongola', 'gondola'], + 'dongon': ['dongon', 'nongod'], + 'donia': ['adion', 'danio', 'doina', 'donia'], + 'donna': ['donna', 'nonda'], + 'donnert': ['donnert', 'tendron'], + 'donnie': ['donnie', 'indone', 'ondine'], + 'donor': ['donor', 'rondo'], + 'donorship': ['donorship', 'rhodopsin'], + 'donsie': ['deinos', 'donsie', 'inodes', 'onside'], + 'donum': ['donum', 'mound'], + 'doob': ['bodo', 'bood', 'doob'], + 'dook': ['doko', 'dook'], + 'dool': ['dool', 'lood'], + 'dooli': ['dooli', 'iodol'], + 'doom': ['doom', 'mood'], + 'doomer': ['doomer', 'mooder', 'redoom', 'roomed'], + 'dooms': ['dooms', 'sodom'], + 'door': ['door', 'odor', 'oord', 'rood'], + 'doorba': ['dobrao', 'doorba'], + 'doorbell': ['bordello', 'doorbell'], + 'doored': ['doored', 'odored'], + 'doorframe': ['doorframe', 'reformado'], + 'doorless': ['doorless', 'odorless'], + 'doorplate': ['doorplate', 'leptodora'], + 'doorpost': ['doorpost', 'doorstop'], + 'doorstone': ['doorstone', 'roodstone'], + 'doorstop': ['doorpost', 'doorstop'], + 'doorweed': ['deerwood', 'doorweed'], + 'dop': ['dop', 'pod'], + 'dopa': ['apod', 'dopa'], + 'doper': ['doper', 'pedro', 'pored'], + 'dopplerite': ['dopplerite', 'lepidopter'], + 'dor': ['dor', 'rod'], + 'dora': ['dora', 'orad', 'road'], + 'dorab': ['abord', 'bardo', 'board', 'broad', 'dobra', 'dorab'], + 'doree': ['doree', 'erode'], + 'dori': ['dori', 'roid'], + 'doria': ['aroid', 'doria', 'radio'], + 'dorian': ['dorian', 'inroad', 'ordain'], + 'dorical': ['cordial', 'dorical'], + 'dorine': ['dinero', 'dorine'], + 'dorlach': ['chordal', 'dorlach'], + 'dormancy': ['dormancy', 'mordancy'], + 'dormant': ['dormant', 'mordant'], + 'dormer': ['dormer', 'remord'], + 'dormie': ['dormie', 'moider'], + 'dorn': ['dorn', 'rond'], + 'dornic': ['dornic', 'nordic'], + 'dorothea': ['dorothea', 'theodora'], + 'dorp': ['dorp', 'drop', 'prod'], + 'dorsel': ['dorsel', 'seldor', 'solder'], + 'dorsoapical': ['dorsoapical', 'prosodiacal'], + 'dorsocaudal': ['caudodorsal', 'dorsocaudal'], + 'dorsocentral': ['centrodorsal', 'dorsocentral'], + 'dorsocervical': ['cervicodorsal', 'dorsocervical'], + 'dorsolateral': ['dorsolateral', 'laterodorsal'], + 'dorsomedial': ['dorsomedial', 'mediodorsal'], + 'dorsosacral': ['dorsosacral', 'sacrodorsal'], + 'dorsoventrad': ['dorsoventrad', 'ventrodorsad'], + 'dorsoventral': ['dorsoventral', 'ventrodorsal'], + 'dorsoventrally': ['dorsoventrally', 'ventrodorsally'], + 'dos': ['dos', 'ods', 'sod'], + 'dosa': ['dosa', 'sado', 'soda'], + 'dosage': ['dosage', 'seadog'], + 'dose': ['does', 'dose'], + 'doser': ['doser', 'rosed'], + 'dosimetric': ['dosimetric', 'mediocrist'], + 'dossel': ['doless', 'dossel'], + 'dosser': ['dosser', 'sordes'], + 'dot': ['dot', 'tod'], + 'dotage': ['dogate', 'dotage', 'togaed'], + 'dote': ['dote', 'tode', 'toed'], + 'doter': ['doter', 'tored', 'trode'], + 'doty': ['doty', 'tody'], + 'doubler': ['boulder', 'doubler'], + 'doubter': ['doubter', 'obtrude', 'outbred', 'redoubt'], + 'douc': ['douc', 'duco'], + 'douce': ['coude', 'douce'], + 'doum': ['doum', 'moud', 'odum'], + 'doup': ['doup', 'updo'], + 'dour': ['dour', 'duro', 'ordu', 'roud'], + 'dourine': ['dourine', 'neuroid'], + 'dourly': ['dourly', 'lourdy'], + 'douser': ['douser', 'soured'], + 'douter': ['derout', 'detour', 'douter'], + 'dover': ['dover', 'drove', 'vedro'], + 'dow': ['dow', 'owd', 'wod'], + 'dowager': ['dowager', 'wordage'], + 'dower': ['dower', 'rowed'], + 'dowl': ['dowl', 'wold'], + 'dowlas': ['dowlas', 'oswald'], + 'downbear': ['downbear', 'rawboned'], + 'downcome': ['comedown', 'downcome'], + 'downer': ['downer', 'wonder', 'worden'], + 'downingia': ['downingia', 'godwinian'], + 'downset': ['downset', 'setdown'], + 'downside': ['disendow', 'downside'], + 'downtake': ['downtake', 'takedown'], + 'downthrow': ['downthrow', 'throwdown'], + 'downturn': ['downturn', 'turndown'], + 'downward': ['downward', 'drawdown'], + 'dowry': ['dowry', 'rowdy', 'wordy'], + 'dowser': ['dowser', 'drowse'], + 'doxa': ['doxa', 'odax'], + 'doyle': ['doyle', 'yodel'], + 'dozen': ['dozen', 'zoned'], + 'drab': ['bard', 'brad', 'drab'], + 'draba': ['barad', 'draba'], + 'drabble': ['dabbler', 'drabble'], + 'draco': ['cardo', 'draco'], + 'draconic': ['cancroid', 'draconic'], + 'draconis': ['draconis', 'sardonic'], + 'dracontian': ['dracontian', 'octandrian'], + 'drafter': ['drafter', 'redraft'], + 'drag': ['darg', 'drag', 'grad'], + 'draggle': ['draggle', 'raggled'], + 'dragline': ['dragline', 'reginald', 'ringlead'], + 'dragman': ['dragman', 'grandam', 'grandma'], + 'drago': ['dargo', 'dogra', 'drago'], + 'dragoman': ['dragoman', 'garamond', 'ondagram'], + 'dragonize': ['dragonize', 'organized'], + 'dragoon': ['dragoon', 'gadroon'], + 'dragoonage': ['dragoonage', 'gadroonage'], + 'dragsman': ['dargsman', 'dragsman'], + 'drail': ['drail', 'laird', 'larid', 'liard'], + 'drain': ['darin', 'dinar', 'drain', 'indra', 'nadir', 'ranid'], + 'drainable': ['albardine', 'drainable'], + 'drainage': ['drainage', 'gardenia'], + 'draine': ['darien', 'draine'], + 'drained': ['diander', 'drained'], + 'drainer': ['darrein', 'drainer'], + 'drainman': ['drainman', 'mandarin'], + 'draintile': ['deliriant', 'draintile', 'interlaid'], + 'drake': ['daker', 'drake', 'kedar', 'radek'], + 'dramme': ['dammer', 'dramme'], + 'drang': ['drang', 'grand'], + 'drape': ['drape', 'padre'], + 'drat': ['dart', 'drat'], + 'drate': ['dater', 'derat', 'detar', 'drate', 'rated', 'trade', 'tread'], + 'draw': ['draw', 'ward'], + 'drawable': ['drawable', 'wardable'], + 'drawback': ['backward', 'drawback'], + 'drawbore': ['drawbore', 'wardrobe'], + 'drawbridge': ['bridgeward', 'drawbridge'], + 'drawdown': ['downward', 'drawdown'], + 'drawee': ['drawee', 'rewade'], + 'drawer': ['drawer', 'redraw', 'reward', 'warder'], + 'drawers': ['drawers', 'resward'], + 'drawfile': ['drawfile', 'lifeward'], + 'drawgate': ['drawgate', 'gateward'], + 'drawhead': ['drawhead', 'headward'], + 'drawhorse': ['drawhorse', 'shoreward'], + 'drawing': ['drawing', 'ginward', 'warding'], + 'drawoff': ['drawoff', 'offward'], + 'drawout': ['drawout', 'outdraw', 'outward'], + 'drawsheet': ['drawsheet', 'watershed'], + 'drawstop': ['drawstop', 'postward'], + 'dray': ['adry', 'dray', 'yard'], + 'drayage': ['drayage', 'yardage'], + 'drayman': ['drayman', 'yardman'], + 'dread': ['adder', 'dread', 'readd'], + 'dreadly': ['dreadly', 'laddery'], + 'dream': ['armed', 'derma', 'dream', 'ramed'], + 'dreamage': ['dreamage', 'redamage'], + 'dreamer': ['dreamer', 'redream'], + 'dreamhole': ['dreamhole', 'heloderma'], + 'dreamish': ['dreamish', 'semihard'], + 'dreamland': ['dreamland', 'raddleman'], + 'drear': ['darer', 'drear'], + 'dreary': ['dreary', 'yarder'], + 'dredge': ['dredge', 'gedder'], + 'dree': ['deer', 'dere', 'dree', 'rede', 'reed'], + 'dreiling': ['dreiling', 'gridelin'], + 'dressage': ['degasser', 'dressage'], + 'dresser': ['dresser', 'redress'], + 'drib': ['bird', 'drib'], + 'dribble': ['dibbler', 'dribble'], + 'driblet': ['birdlet', 'driblet'], + 'driddle': ['diddler', 'driddle'], + 'drier': ['drier', 'rider'], + 'driest': ['driest', 'stride'], + 'driller': ['driller', 'redrill'], + 'drillman': ['drillman', 'mandrill'], + 'dringle': ['dringle', 'grindle'], + 'drisheen': ['denshire', 'drisheen'], + 'drive': ['diver', 'drive'], + 'driven': ['driven', 'nervid', 'verdin'], + 'drivescrew': ['drivescrew', 'screwdrive'], + 'drogue': ['drogue', 'gourde'], + 'drolly': ['drolly', 'lordly'], + 'drome': ['domer', 'drome'], + 'dromicia': ['dioramic', 'dromicia'], + 'drona': ['adorn', 'donar', 'drona', 'radon'], + 'drone': ['drone', 'ronde'], + 'drongo': ['drongo', 'gordon'], + 'dronish': ['dishorn', 'dronish'], + 'drool': ['dolor', 'drool'], + 'drop': ['dorp', 'drop', 'prod'], + 'dropsy': ['dropsy', 'dryops'], + 'drossel': ['drossel', 'rodless'], + 'drove': ['dover', 'drove', 'vedro'], + 'drow': ['drow', 'word'], + 'drowse': ['dowser', 'drowse'], + 'drub': ['burd', 'drub'], + 'drugger': ['drugger', 'grudger'], + 'druggery': ['druggery', 'grudgery'], + 'drungar': ['drungar', 'gurnard'], + 'drupe': ['drupe', 'duper', 'perdu', 'prude', 'pured'], + 'drusean': ['asunder', 'drusean'], + 'dryops': ['dropsy', 'dryops'], + 'duad': ['addu', 'dadu', 'daud', 'duad'], + 'dual': ['auld', 'dual', 'laud', 'udal'], + 'duali': ['duali', 'dulia'], + 'dualin': ['dualin', 'ludian', 'unlaid'], + 'dualism': ['dualism', 'laudism'], + 'dualist': ['dualist', 'laudist'], + 'dub': ['bud', 'dub'], + 'dubber': ['dubber', 'rubbed'], + 'dubious': ['biduous', 'dubious'], + 'dubitate': ['dubitate', 'tabitude'], + 'ducal': ['cauld', 'ducal'], + 'duces': ['decus', 'duces'], + 'duckstone': ['duckstone', 'unstocked'], + 'duco': ['douc', 'duco'], + 'ducted': ['deduct', 'ducted'], + 'duction': ['conduit', 'duction', 'noctuid'], + 'duculinae': ['duculinae', 'nuculidae'], + 'dudeen': ['denude', 'dudeen'], + 'dudler': ['dudler', 'ruddle'], + 'duel': ['deul', 'duel', 'leud'], + 'dueler': ['dueler', 'eluder'], + 'dueling': ['dueling', 'indulge'], + 'duello': ['deloul', 'duello'], + 'duenna': ['duenna', 'undean'], + 'duer': ['duer', 'dure', 'rude', 'urde'], + 'duffer': ['duffer', 'ruffed'], + 'dufter': ['dufter', 'turfed'], + 'dug': ['dug', 'gud'], + 'duim': ['duim', 'muid'], + 'dukery': ['dukery', 'duyker'], + 'dulat': ['adult', 'dulat'], + 'dulcian': ['dulcian', 'incudal', 'lucanid', 'lucinda'], + 'dulciana': ['claudian', 'dulciana'], + 'duler': ['duler', 'urled'], + 'dulia': ['duali', 'dulia'], + 'dullify': ['dullify', 'fluidly'], + 'dulosis': ['dissoul', 'dulosis', 'solidus'], + 'dulseman': ['dulseman', 'unalmsed'], + 'dultie': ['dilute', 'dultie'], + 'dum': ['dum', 'mud'], + 'duma': ['duma', 'maud'], + 'dumaist': ['dumaist', 'stadium'], + 'dumontite': ['dumontite', 'unomitted'], + 'dumple': ['dumple', 'plumed'], + 'dunair': ['danuri', 'diurna', 'dunair', 'durain', 'durani', 'durian'], + 'dunal': ['dunal', 'laund', 'lunda', 'ulnad'], + 'dunderpate': ['dunderpate', 'undeparted'], + 'dune': ['dune', 'nude', 'unde'], + 'dungaree': ['dungaree', 'guardeen', 'unagreed', 'underage', 'ungeared'], + 'dungeon': ['dungeon', 'negundo'], + 'dunger': ['dunger', 'gerund', 'greund', 'nudger'], + 'dungol': ['dungol', 'ungold'], + 'dungy': ['dungy', 'gundy'], + 'dunite': ['dunite', 'united', 'untied'], + 'dunlap': ['dunlap', 'upland'], + 'dunne': ['dunne', 'unden'], + 'dunner': ['dunner', 'undern'], + 'dunpickle': ['dunpickle', 'unpickled'], + 'dunstable': ['dunstable', 'unblasted', 'unstabled'], + 'dunt': ['dunt', 'tund'], + 'duny': ['duny', 'undy'], + 'duo': ['duo', 'udo'], + 'duodenal': ['duodenal', 'unloaded'], + 'duodenocholecystostomy': ['cholecystoduodenostomy', 'duodenocholecystostomy'], + 'duodenojejunal': ['duodenojejunal', 'jejunoduodenal'], + 'duodenopancreatectomy': ['duodenopancreatectomy', 'pancreatoduodenectomy'], + 'dup': ['dup', 'pud'], + 'duper': ['drupe', 'duper', 'perdu', 'prude', 'pured'], + 'dupion': ['dupion', 'unipod'], + 'dupla': ['dupla', 'plaud'], + 'duplone': ['duplone', 'unpoled'], + 'dura': ['ardu', 'daur', 'dura'], + 'durain': ['danuri', 'diurna', 'dunair', 'durain', 'durani', 'durian'], + 'duramen': ['duramen', 'maunder', 'unarmed'], + 'durance': ['durance', 'redunca', 'unraced'], + 'durango': ['aground', 'durango'], + 'durani': ['danuri', 'diurna', 'dunair', 'durain', 'durani', 'durian'], + 'durant': ['durant', 'tundra'], + 'durban': ['durban', 'undrab'], + 'durdenite': ['durdenite', 'undertide'], + 'dure': ['duer', 'dure', 'rude', 'urde'], + 'durene': ['durene', 'endure'], + 'durenol': ['durenol', 'lounder', 'roundel'], + 'durgan': ['durgan', 'undrag'], + 'durian': ['danuri', 'diurna', 'dunair', 'durain', 'durani', 'durian'], + 'during': ['during', 'ungird'], + 'durity': ['durity', 'rudity'], + 'durmast': ['durmast', 'mustard'], + 'duro': ['dour', 'duro', 'ordu', 'roud'], + 'dusken': ['dusken', 'sundek'], + 'dust': ['dust', 'stud'], + 'duster': ['derust', 'duster'], + 'dustin': ['dustin', 'nudist'], + 'dustpan': ['dustpan', 'upstand'], + 'dusty': ['dusty', 'study'], + 'duyker': ['dukery', 'duyker'], + 'dvaita': ['divata', 'dvaita'], + 'dwale': ['dwale', 'waled', 'weald'], + 'dwine': ['dwine', 'edwin', 'wendi', 'widen', 'wined'], + 'dyad': ['addy', 'dyad'], + 'dyas': ['days', 'dyas'], + 'dye': ['dey', 'dye', 'yed'], + 'dyehouse': ['deyhouse', 'dyehouse'], + 'dyeing': ['digeny', 'dyeing'], + 'dyer': ['dyer', 'yerd'], + 'dying': ['dingy', 'dying'], + 'dynamo': ['dynamo', 'monday'], + 'dynamoelectric': ['dynamoelectric', 'electrodynamic'], + 'dynamoelectrical': ['dynamoelectrical', 'electrodynamical'], + 'dynamotor': ['androtomy', 'dynamotor'], + 'dyne': ['deny', 'dyne'], + 'dyophone': ['dyophone', 'honeypod'], + 'dysluite': ['dysluite', 'sedulity'], + 'dysneuria': ['dasyurine', 'dysneuria'], + 'dysphoric': ['chrysopid', 'dysphoric'], + 'dysphrenia': ['dysphrenia', 'sphyraenid', 'sphyrnidae'], + 'dystome': ['dystome', 'modesty'], + 'dystrophia': ['diastrophy', 'dystrophia'], + 'ea': ['ae', 'ea'], + 'each': ['ache', 'each', 'haec'], + 'eager': ['agree', 'eager', 'eagre'], + 'eagle': ['aegle', 'eagle', 'galee'], + 'eagless': ['ageless', 'eagless'], + 'eaglet': ['eaglet', 'legate', 'teagle', 'telega'], + 'eagre': ['agree', 'eager', 'eagre'], + 'ean': ['ean', 'nae', 'nea'], + 'ear': ['aer', 'are', 'ear', 'era', 'rea'], + 'eared': ['eared', 'erade'], + 'earful': ['earful', 'farleu', 'ferula'], + 'earing': ['arenig', 'earing', 'gainer', 'reagin', 'regain'], + 'earl': ['earl', 'eral', 'lear', 'real'], + 'earlap': ['earlap', 'parale'], + 'earle': ['areel', 'earle'], + 'earlet': ['earlet', 'elater', 'relate'], + 'earliness': ['earliness', 'naileress'], + 'earlship': ['earlship', 'pearlish'], + 'early': ['early', 'layer', 'relay'], + 'earn': ['arne', 'earn', 'rane'], + 'earner': ['earner', 'ranere'], + 'earnest': ['earnest', 'eastern', 'nearest'], + 'earnestly': ['earnestly', 'easternly'], + 'earnful': ['earnful', 'funeral'], + 'earning': ['earning', 'engrain'], + 'earplug': ['earplug', 'graupel', 'plaguer'], + 'earring': ['earring', 'grainer'], + 'earringed': ['earringed', 'grenadier'], + 'earshot': ['asthore', 'earshot'], + 'eartab': ['abater', 'artabe', 'eartab', 'trabea'], + 'earth': ['earth', 'hater', 'heart', 'herat', 'rathe'], + 'earthborn': ['abhorrent', 'earthborn'], + 'earthed': ['earthed', 'hearted'], + 'earthen': ['earthen', 'enheart', 'hearten', 'naether', 'teheran', 'traheen'], + 'earthian': ['earthian', 'rhaetian'], + 'earthiness': ['earthiness', 'heartiness'], + 'earthless': ['earthless', 'heartless'], + 'earthling': ['earthling', 'heartling'], + 'earthly': ['earthly', 'heartly', 'lathery', 'rathely'], + 'earthnut': ['earthnut', 'heartnut'], + 'earthpea': ['earthpea', 'heartpea'], + 'earthquake': ['earthquake', 'heartquake'], + 'earthward': ['earthward', 'heartward'], + 'earthy': ['earthy', 'hearty', 'yearth'], + 'earwig': ['earwig', 'grewia'], + 'earwitness': ['earwitness', 'wateriness'], + 'easel': ['easel', 'lease'], + 'easement': ['easement', 'estamene'], + 'easer': ['easer', 'erase'], + 'easily': ['easily', 'elysia'], + 'easing': ['easing', 'sangei'], + 'east': ['ates', 'east', 'eats', 'sate', 'seat', 'seta'], + 'eastabout': ['aetobatus', 'eastabout'], + 'eastbound': ['eastbound', 'unboasted'], + 'easter': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'easterling': ['easterling', 'generalist'], + 'eastern': ['earnest', 'eastern', 'nearest'], + 'easternly': ['earnestly', 'easternly'], + 'easting': ['easting', + 'gainset', + 'genista', + 'ingesta', + 'seating', + 'signate', + 'teasing'], + 'eastlake': ['alestake', 'eastlake'], + 'eastre': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'easy': ['easy', 'eyas'], + 'eat': ['ate', 'eat', 'eta', 'tae', 'tea'], + 'eatberry': ['betrayer', 'eatberry', 'rebetray', 'teaberry'], + 'eaten': ['eaten', 'enate'], + 'eater': ['arete', 'eater', 'teaer'], + 'eating': ['eating', 'ingate', 'tangie'], + 'eats': ['ates', 'east', 'eats', 'sate', 'seat', 'seta'], + 'eave': ['eave', 'evea'], + 'eaved': ['deave', 'eaved', 'evade'], + 'eaver': ['eaver', 'reave'], + 'eaves': ['eaves', 'evase', 'seave'], + 'eben': ['been', 'bene', 'eben'], + 'ebenales': ['ebenales', 'lebanese'], + 'ebon': ['beno', 'bone', 'ebon'], + 'ebony': ['boney', 'ebony'], + 'ebriety': ['byerite', 'ebriety'], + 'eburna': ['eburna', 'unbare', 'unbear', 'urbane'], + 'eburnated': ['eburnated', 'underbeat', 'unrebated'], + 'eburnian': ['eburnian', 'inurbane'], + 'ecad': ['cade', 'dace', 'ecad'], + 'ecanda': ['adance', 'ecanda'], + 'ecardinal': ['ecardinal', 'lardacein'], + 'ecarinate': ['anaeretic', 'ecarinate'], + 'ecarte': ['cerate', 'create', 'ecarte'], + 'ecaudata': ['acaudate', 'ecaudata'], + 'ecclesiasticism': ['ecclesiasticism', 'misecclesiastic'], + 'eche': ['chee', 'eche'], + 'echelon': ['chelone', 'echelon'], + 'echeveria': ['echeveria', 'reachieve'], + 'echidna': ['chained', 'echidna'], + 'echinal': ['chilean', 'echinal', 'nichael'], + 'echinate': ['echinate', 'hecatine'], + 'echinital': ['echinital', 'inethical'], + 'echis': ['echis', 'shice'], + 'echoer': ['choree', 'cohere', 'echoer'], + 'echoic': ['choice', 'echoic'], + 'echoist': ['chitose', 'echoist'], + 'eciton': ['eciton', 'noetic', 'notice', 'octine'], + 'eckehart': ['eckehart', 'hacktree'], + 'eclair': ['carlie', 'claire', 'eclair', 'erical'], + 'eclat': ['cleat', 'eclat', 'ectal', 'lacet', 'tecla'], + 'eclipsable': ['eclipsable', 'spliceable'], + 'eclipser': ['eclipser', 'pericles', 'resplice'], + 'economics': ['economics', 'neocosmic'], + 'economism': ['economism', 'monoecism', 'monosemic'], + 'economist': ['economist', 'mesotonic'], + 'ecorticate': ['ecorticate', 'octaeteric'], + 'ecostate': ['coestate', 'ecostate'], + 'ecotonal': ['colonate', 'ecotonal'], + 'ecotype': ['ecotype', 'ocypete'], + 'ecrasite': ['ecrasite', 'sericate'], + 'ecru': ['cure', 'ecru', 'eruc'], + 'ectad': ['cadet', 'ectad'], + 'ectal': ['cleat', 'eclat', 'ectal', 'lacet', 'tecla'], + 'ectasis': ['ascites', 'ectasis'], + 'ectene': ['cetene', 'ectene'], + 'ectental': ['ectental', 'tentacle'], + 'ectiris': ['ectiris', 'eristic'], + 'ectocardia': ['coradicate', 'ectocardia'], + 'ectocranial': ['calonectria', 'ectocranial'], + 'ectoglia': ['ectoglia', 'geotical', 'goetical'], + 'ectomorph': ['ectomorph', 'topchrome'], + 'ectomorphic': ['cetomorphic', 'chemotropic', 'ectomorphic'], + 'ectomorphy': ['chromotype', 'cormophyte', 'ectomorphy'], + 'ectopia': ['ectopia', 'opacite'], + 'ectopy': ['cotype', 'ectopy'], + 'ectorhinal': ['chlorinate', 'ectorhinal', 'tornachile'], + 'ectosarc': ['ectosarc', 'reaccost'], + 'ectrogenic': ['ectrogenic', 'egocentric', 'geocentric'], + 'ectromelia': ['carmeloite', 'ectromelia', 'meteorical'], + 'ectropion': ['ectropion', 'neotropic'], + 'ed': ['de', 'ed'], + 'edda': ['dade', 'dead', 'edda'], + 'eddaic': ['caddie', 'eddaic'], + 'eddish': ['dished', 'eddish'], + 'eddo': ['dedo', 'dode', 'eddo'], + 'edema': ['adeem', 'ameed', 'edema'], + 'eden': ['dene', 'eden', 'need'], + 'edental': ['dalteen', 'dentale', 'edental'], + 'edentata': ['antedate', 'edentata'], + 'edessan': ['deaness', 'edessan'], + 'edestan': ['edestan', 'standee'], + 'edestin': ['destine', 'edestin'], + 'edgar': ['edgar', 'grade'], + 'edger': ['edger', 'greed'], + 'edgerman': ['edgerman', 'gendarme'], + 'edgrew': ['edgrew', 'wedger'], + 'edible': ['debile', 'edible'], + 'edict': ['cetid', 'edict'], + 'edictal': ['citadel', 'deltaic', 'dialect', 'edictal', 'lactide'], + 'edification': ['deification', 'edification'], + 'edificatory': ['deificatory', 'edificatory'], + 'edifier': ['deifier', 'edifier'], + 'edify': ['deify', 'edify'], + 'edit': ['diet', 'dite', 'edit', 'tide', 'tied'], + 'edital': ['detail', 'dietal', 'dilate', 'edital', 'tailed'], + 'edith': ['edith', 'ethid'], + 'edition': ['edition', 'odinite', 'otidine', 'tineoid'], + 'editor': ['editor', 'triode'], + 'editorial': ['editorial', 'radiolite'], + 'edmund': ['edmund', 'mudden'], + 'edna': ['ande', 'dane', 'dean', 'edna'], + 'edo': ['doe', 'edo', 'ode'], + 'edoni': ['deino', 'dione', 'edoni'], + 'education': ['coadunite', 'education', 'noctuidae'], + 'educe': ['deuce', 'educe'], + 'edward': ['edward', 'wadder', 'warded'], + 'edwin': ['dwine', 'edwin', 'wendi', 'widen', 'wined'], + 'eel': ['eel', 'lee'], + 'eelgrass': ['eelgrass', 'gearless', 'rageless'], + 'eelpot': ['eelpot', 'opelet'], + 'eelspear': ['eelspear', 'prelease'], + 'eely': ['eely', 'yeel'], + 'eer': ['eer', 'ere', 'ree'], + 'efik': ['efik', 'fike'], + 'eft': ['eft', 'fet'], + 'egad': ['aged', 'egad', 'gade'], + 'egba': ['egba', 'gabe'], + 'egbo': ['bego', 'egbo'], + 'egeran': ['egeran', 'enrage', 'ergane', 'genear', 'genera'], + 'egest': ['egest', 'geest', 'geste'], + 'egger': ['egger', 'grege'], + 'egghot': ['egghot', 'hogget'], + 'eggler': ['eggler', 'legger'], + 'eggy': ['eggy', 'yegg'], + 'eglantine': ['eglantine', 'inelegant', 'legantine'], + 'eglatere': ['eglatere', 'regelate', 'relegate'], + 'egma': ['egma', 'game', 'mage'], + 'ego': ['ego', 'geo'], + 'egocentric': ['ectrogenic', 'egocentric', 'geocentric'], + 'egoist': ['egoist', 'stogie'], + 'egol': ['egol', 'goel', 'loge', 'ogle', 'oleg'], + 'egotheism': ['egotheism', 'eightsome'], + 'egret': ['egret', 'greet', 'reget'], + 'eh': ['eh', 'he'], + 'ehretia': ['ehretia', 'etheria'], + 'eident': ['eident', 'endite'], + 'eidograph': ['eidograph', 'ideograph'], + 'eidology': ['eidology', 'ideology'], + 'eighth': ['eighth', 'height'], + 'eightsome': ['egotheism', 'eightsome'], + 'eigne': ['eigne', 'genie'], + 'eileen': ['eileen', 'lienee'], + 'ekaha': ['ekaha', 'hakea'], + 'eke': ['eke', 'kee'], + 'eker': ['eker', 'reek'], + 'ekoi': ['ekoi', 'okie'], + 'ekron': ['ekron', 'krone'], + 'ektene': ['ektene', 'ketene'], + 'elabrate': ['elabrate', 'tearable'], + 'elaidic': ['aedilic', 'elaidic'], + 'elaidin': ['anilide', 'elaidin'], + 'elain': ['alien', 'aline', 'anile', 'elain', 'elian', 'laine', 'linea'], + 'elaine': ['aileen', 'elaine'], + 'elamite': ['alemite', 'elamite'], + 'elance': ['elance', 'enlace'], + 'eland': ['eland', 'laden', 'lenad'], + 'elanet': ['elanet', 'lanete', 'lateen'], + 'elanus': ['elanus', 'unseal'], + 'elaphomyces': ['elaphomyces', 'mesocephaly'], + 'elaphurus': ['elaphurus', 'sulphurea'], + 'elapid': ['aliped', 'elapid'], + 'elapoid': ['elapoid', 'oedipal'], + 'elaps': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'elapse': ['asleep', 'elapse', 'please'], + 'elastic': ['astelic', 'elastic', 'latices'], + 'elasticin': ['elasticin', 'inelastic', 'sciential'], + 'elastin': ['elastin', 'salient', 'saltine', 'slainte'], + 'elastomer': ['elastomer', 'salometer'], + 'elate': ['atlee', 'elate'], + 'elated': ['delate', 'elated'], + 'elater': ['earlet', 'elater', 'relate'], + 'elaterid': ['detailer', 'elaterid'], + 'elaterin': ['elaterin', 'entailer', 'treenail'], + 'elatha': ['althea', 'elatha'], + 'elatine': ['elatine', 'lineate'], + 'elation': ['alnoite', 'elation', 'toenail'], + 'elator': ['elator', 'lorate'], + 'elb': ['bel', 'elb'], + 'elbert': ['belter', 'elbert', 'treble'], + 'elberta': ['bearlet', 'bleater', 'elberta', 'retable'], + 'elbow': ['below', 'bowel', 'elbow'], + 'elbowed': ['boweled', 'elbowed'], + 'eld': ['del', 'eld', 'led'], + 'eldin': ['eldin', 'lined'], + 'elding': ['dingle', 'elding', 'engild', 'gilden'], + 'elean': ['anele', 'elean'], + 'election': ['coteline', 'election'], + 'elective': ['cleveite', 'elective'], + 'elector': ['elector', 'electro'], + 'electoral': ['electoral', 'recollate'], + 'electra': ['electra', 'treacle'], + 'electragy': ['electragy', 'glycerate'], + 'electret': ['electret', 'tercelet'], + 'electric': ['electric', 'lectrice'], + 'electrion': ['centriole', 'electrion', 'relection'], + 'electro': ['elector', 'electro'], + 'electrodynamic': ['dynamoelectric', 'electrodynamic'], + 'electrodynamical': ['dynamoelectrical', 'electrodynamical'], + 'electromagnetic': ['electromagnetic', 'magnetoelectric'], + 'electromagnetical': ['electromagnetical', 'magnetoelectrical'], + 'electrothermic': ['electrothermic', 'thermoelectric'], + 'electrothermometer': ['electrothermometer', 'thermoelectrometer'], + 'elegant': ['angelet', 'elegant'], + 'elegiambus': ['elegiambus', 'iambelegus'], + 'elegiast': ['elegiast', 'selagite'], + 'elemi': ['elemi', 'meile'], + 'elemin': ['elemin', 'meline'], + 'elephantic': ['elephantic', 'plancheite'], + 'elettaria': ['elettaria', 'retaliate'], + 'eleut': ['eleut', 'elute'], + 'elevator': ['elevator', 'overlate'], + 'elfin': ['elfin', 'nifle'], + 'elfishness': ['elfishness', 'fleshiness'], + 'elfkin': ['elfkin', 'finkel'], + 'elfwort': ['elfwort', 'felwort'], + 'eli': ['eli', 'lei', 'lie'], + 'elia': ['aiel', 'aile', 'elia'], + 'elian': ['alien', 'aline', 'anile', 'elain', 'elian', 'laine', 'linea'], + 'elias': ['aisle', 'elias'], + 'elicitor': ['elicitor', 'trioleic'], + 'eliminand': ['eliminand', 'mindelian'], + 'elinor': ['elinor', 'lienor', 'lorien', 'noiler'], + 'elinvar': ['elinvar', 'ravelin', 'reanvil', 'valerin'], + 'elisha': ['elisha', 'hailse', 'sheila'], + 'elisor': ['elisor', 'resoil'], + 'elissa': ['elissa', 'lassie'], + 'elite': ['elite', 'telei'], + 'eliza': ['aizle', 'eliza'], + 'elk': ['elk', 'lek'], + 'ella': ['alle', 'ella', 'leal'], + 'ellagate': ['allegate', 'ellagate'], + 'ellenyard': ['ellenyard', 'learnedly'], + 'ellick': ['ellick', 'illeck'], + 'elliot': ['elliot', 'oillet'], + 'elm': ['elm', 'mel'], + 'elmer': ['elmer', 'merel', 'merle'], + 'elmy': ['elmy', 'yelm'], + 'eloah': ['eloah', 'haole'], + 'elod': ['dole', 'elod', 'lode', 'odel'], + 'eloge': ['eloge', 'golee'], + 'elohimic': ['elohimic', 'hemiolic'], + 'elohist': ['elohist', 'hostile'], + 'eloign': ['eloign', 'gileno', 'legion'], + 'eloigner': ['eloigner', 'legioner'], + 'eloignment': ['eloignment', 'omnilegent'], + 'elon': ['elon', 'enol', 'leno', 'leon', 'lone', 'noel'], + 'elonite': ['elonite', 'leonite'], + 'elops': ['elops', 'slope', 'spole'], + 'elric': ['crile', 'elric', 'relic'], + 'els': ['els', 'les'], + 'elsa': ['elsa', 'sale', 'seal', 'slae'], + 'else': ['else', 'lees', 'seel', 'sele', 'slee'], + 'elsin': ['elsin', 'lenis', 'niels', 'silen', 'sline'], + 'elt': ['elt', 'let'], + 'eluate': ['aulete', 'eluate'], + 'eluder': ['dueler', 'eluder'], + 'elusion': ['elusion', 'luiseno'], + 'elusory': ['elusory', 'yoursel'], + 'elute': ['eleut', 'elute'], + 'elution': ['elution', 'outline'], + 'elutor': ['elutor', 'louter', 'outler'], + 'elvan': ['elvan', 'navel', 'venal'], + 'elvanite': ['elvanite', 'lavenite'], + 'elver': ['elver', 'lever', 'revel'], + 'elvet': ['elvet', 'velte'], + 'elvira': ['averil', 'elvira'], + 'elvis': ['elvis', 'levis', 'slive'], + 'elwood': ['dewool', 'elwood', 'wooled'], + 'elymi': ['elymi', 'emily', 'limey'], + 'elysia': ['easily', 'elysia'], + 'elytral': ['alertly', 'elytral'], + 'elytrin': ['elytrin', 'inertly', 'trinely'], + 'elytroposis': ['elytroposis', 'proteolysis'], + 'elytrous': ['elytrous', 'urostyle'], + 'em': ['em', 'me'], + 'emanate': ['emanate', 'manatee'], + 'emanation': ['amnionate', 'anamniote', 'emanation'], + 'emanatist': ['emanatist', 'staminate', 'tasmanite'], + 'embalmer': ['embalmer', 'emmarble'], + 'embar': ['amber', 'bearm', 'bemar', 'bream', 'embar'], + 'embargo': ['bergamo', 'embargo'], + 'embark': ['embark', 'markeb'], + 'embay': ['beamy', 'embay', 'maybe'], + 'ember': ['breme', 'ember'], + 'embind': ['embind', 'nimbed'], + 'embira': ['ambier', 'bremia', 'embira'], + 'embodier': ['demirobe', 'embodier'], + 'embody': ['beydom', 'embody'], + 'embole': ['bemole', 'embole'], + 'embraceor': ['cerebroma', 'embraceor'], + 'embrail': ['embrail', 'mirabel'], + 'embryoid': ['embryoid', 'reimbody'], + 'embus': ['embus', 'sebum'], + 'embusk': ['bemusk', 'embusk'], + 'emcee': ['emcee', 'meece'], + 'emeership': ['emeership', 'ephemeris'], + 'emend': ['emend', 'mende'], + 'emendation': ['denominate', 'emendation'], + 'emendator': ['emendator', 'ondameter'], + 'emerita': ['emerita', 'emirate'], + 'emerse': ['emerse', 'seemer'], + 'emersion': ['emersion', 'meriones'], + 'emersonian': ['emersonian', 'mansioneer'], + 'emesa': ['emesa', 'mease'], + 'emigrate': ['emigrate', 'remigate'], + 'emigration': ['emigration', 'remigation'], + 'emil': ['emil', 'lime', 'mile'], + 'emilia': ['emilia', 'mailie'], + 'emily': ['elymi', 'emily', 'limey'], + 'emim': ['emim', 'mime'], + 'emir': ['emir', 'imer', 'mire', 'reim', 'remi', 'riem', 'rime'], + 'emirate': ['emerita', 'emirate'], + 'emirship': ['emirship', 'imperish'], + 'emissary': ['emissary', 'missayer'], + 'emit': ['emit', 'item', 'mite', 'time'], + 'emitter': ['emitter', 'termite'], + 'emm': ['emm', 'mem'], + 'emmarble': ['embalmer', 'emmarble'], + 'emodin': ['domine', 'domnei', 'emodin', 'medino'], + 'emotion': ['emotion', 'moonite'], + 'empanel': ['empanel', 'emplane', 'peelman'], + 'empathic': ['empathic', 'emphatic'], + 'empathically': ['empathically', 'emphatically'], + 'emphasis': ['emphasis', 'misshape'], + 'emphatic': ['empathic', 'emphatic'], + 'emphatically': ['empathically', 'emphatically'], + 'empire': ['empire', 'epimer'], + 'empiricist': ['empiricist', 'empiristic'], + 'empiristic': ['empiricist', 'empiristic'], + 'emplane': ['empanel', 'emplane', 'peelman'], + 'employer': ['employer', 'polymere'], + 'emporia': ['emporia', 'meropia'], + 'emporial': ['emporial', 'proemial'], + 'emporium': ['emporium', 'pomerium', 'proemium'], + 'emprise': ['emprise', 'imprese', 'premise', 'spireme'], + 'empt': ['empt', 'temp'], + 'emptier': ['emptier', 'impetre'], + 'emption': ['emption', 'pimento'], + 'emptional': ['emptional', 'palmitone'], + 'emptor': ['emptor', 'trompe'], + 'empyesis': ['empyesis', 'pyemesis'], + 'emu': ['emu', 'ume'], + 'emulant': ['almuten', 'emulant'], + 'emulation': ['emulation', 'laumonite'], + 'emulsion': ['emulsion', 'solenium'], + 'emundation': ['emundation', 'mountained'], + 'emyd': ['demy', 'emyd'], + 'en': ['en', 'ne'], + 'enable': ['baleen', 'enable'], + 'enabler': ['enabler', 'renable'], + 'enaction': ['cetonian', 'enaction'], + 'enactor': ['enactor', 'necator', 'orcanet'], + 'enactory': ['enactory', 'octenary'], + 'enaena': ['aenean', 'enaena'], + 'enalid': ['aldine', 'daniel', 'delian', 'denial', 'enalid', 'leadin'], + 'enaliornis': ['enaliornis', 'rosaniline'], + 'enaluron': ['enaluron', 'neuronal'], + 'enam': ['amen', 'enam', 'mane', 'mean', 'name', 'nema'], + 'enamel': ['enamel', 'melena'], + 'enameling': ['enameling', 'malengine', 'meningeal'], + 'enamor': ['enamor', 'monera', 'oreman', 'romane'], + 'enamored': ['demeanor', 'enamored'], + 'enanthem': ['enanthem', 'menthane'], + 'enantiomer': ['enantiomer', 'renominate'], + 'enapt': ['enapt', 'paten', 'penta', 'tapen'], + 'enarch': ['enarch', 'ranche'], + 'enarm': ['enarm', 'namer', 'reman'], + 'enarme': ['enarme', 'meaner', 'rename'], + 'enarthrosis': ['enarthrosis', 'nearthrosis'], + 'enate': ['eaten', 'enate'], + 'enatic': ['acetin', 'actine', 'enatic'], + 'enation': ['enation', 'etonian'], + 'enbrave': ['enbrave', 'verbena'], + 'encapsule': ['encapsule', 'pelecanus'], + 'encase': ['encase', 'seance', 'seneca'], + 'encash': ['encash', 'sanche'], + 'encauma': ['cumaean', 'encauma'], + 'encaustes': ['acuteness', 'encaustes'], + 'encaustic': ['encaustic', 'succinate'], + 'encephalomeningitis': ['encephalomeningitis', 'meningoencephalitis'], + 'encephalomeningocele': ['encephalomeningocele', 'meningoencephalocele'], + 'encephalomyelitis': ['encephalomyelitis', 'myeloencephalitis'], + 'enchair': ['chainer', 'enchair', 'rechain'], + 'encharge': ['encharge', 'rechange'], + 'encharnel': ['channeler', 'encharnel'], + 'enchytrae': ['cytherean', 'enchytrae'], + 'encina': ['canine', 'encina', 'neanic'], + 'encinillo': ['encinillo', 'linolenic'], + 'encist': ['encist', 'incest', 'insect', 'scient'], + 'encitadel': ['declinate', 'encitadel'], + 'enclaret': ['celarent', 'centrale', 'enclaret'], + 'enclasp': ['enclasp', 'spancel'], + 'enclave': ['enclave', 'levance', 'valence'], + 'enclosure': ['enclosure', 'recounsel'], + 'encoignure': ['encoignure', 'neurogenic'], + 'encoil': ['clione', 'coelin', 'encoil', 'enolic'], + 'encomiastic': ['cosmetician', 'encomiastic'], + 'encomic': ['comenic', 'encomic', 'meconic'], + 'encomium': ['encomium', 'meconium'], + 'encoronal': ['encoronal', 'olecranon'], + 'encoronate': ['encoronate', 'entocornea'], + 'encradle': ['calender', 'encradle'], + 'encranial': ['carnelian', 'encranial'], + 'encratic': ['acentric', 'encratic', 'nearctic'], + 'encratism': ['encratism', 'miscreant'], + 'encraty': ['encraty', 'nectary'], + 'encreel': ['crenele', 'encreel'], + 'encrinital': ['encrinital', 'tricennial'], + 'encrisp': ['encrisp', 'pincers'], + 'encrust': ['encrust', 'uncrest'], + 'encurl': ['encurl', 'lucern'], + 'encurtain': ['encurtain', 'runcinate', 'uncertain'], + 'encyrtidae': ['encyrtidae', 'nycteridae'], + 'end': ['den', 'end', 'ned'], + 'endaortic': ['citronade', 'endaortic', 'redaction'], + 'endboard': ['deadborn', 'endboard'], + 'endear': ['deaner', 'endear'], + 'endeared': ['deadener', 'endeared'], + 'endearing': ['endearing', 'engrained', 'grenadine'], + 'endearingly': ['endearingly', 'engrainedly'], + 'endemial': ['endemial', 'madeline'], + 'endere': ['endere', 'needer', 'reeden'], + 'enderonic': ['enderonic', 'endocrine'], + 'endevil': ['develin', 'endevil'], + 'endew': ['endew', 'wende'], + 'ending': ['ending', 'ginned'], + 'endite': ['eident', 'endite'], + 'endive': ['endive', 'envied', 'veined'], + 'endoarteritis': ['endoarteritis', 'sideronatrite'], + 'endocline': ['endocline', 'indolence'], + 'endocrine': ['enderonic', 'endocrine'], + 'endome': ['endome', 'omened'], + 'endopathic': ['dictaphone', 'endopathic'], + 'endophasic': ['deaconship', 'endophasic'], + 'endoral': ['endoral', 'ladrone', 'leonard'], + 'endosarc': ['endosarc', 'secondar'], + 'endosome': ['endosome', 'moonseed'], + 'endosporium': ['endosporium', 'imponderous'], + 'endosteal': ['endosteal', 'leadstone'], + 'endothecial': ['chelidonate', 'endothecial'], + 'endothelia': ['endothelia', 'ethanediol', 'ethenoidal'], + 'endow': ['endow', 'nowed'], + 'endura': ['endura', 'neurad', 'undear', 'unread'], + 'endurably': ['endurably', 'undryable'], + 'endure': ['durene', 'endure'], + 'endurer': ['endurer', 'underer'], + 'enduring': ['enduring', 'unringed'], + 'enduringly': ['enduringly', 'underlying'], + 'endwise': ['endwise', 'sinewed'], + 'enema': ['ameen', 'amene', 'enema'], + 'enemy': ['enemy', 'yemen'], + 'energesis': ['energesis', 'regenesis'], + 'energeticist': ['energeticist', 'energetistic'], + 'energetistic': ['energeticist', 'energetistic'], + 'energic': ['energic', 'generic'], + 'energical': ['energical', 'generical'], + 'energid': ['energid', 'reeding'], + 'energist': ['energist', 'steering'], + 'energy': ['energy', 'greeny', 'gyrene'], + 'enervate': ['enervate', 'venerate'], + 'enervation': ['enervation', 'veneration'], + 'enervative': ['enervative', 'venerative'], + 'enervator': ['enervator', 'renovater', 'venerator'], + 'enfilade': ['alfenide', 'enfilade'], + 'enfile': ['enfile', 'enlief', 'enlife', 'feline'], + 'enflesh': ['enflesh', 'fleshen'], + 'enfoil': ['enfoil', 'olefin'], + 'enfold': ['enfold', 'folden', 'fondle'], + 'enforcer': ['confrere', 'enforcer', 'reconfer'], + 'enframe': ['enframe', 'freeman'], + 'engaol': ['angelo', 'engaol'], + 'engarb': ['banger', 'engarb', 'graben'], + 'engaud': ['augend', 'engaud', 'unaged'], + 'engild': ['dingle', 'elding', 'engild', 'gilden'], + 'engird': ['engird', 'ringed'], + 'engirdle': ['engirdle', 'reedling'], + 'engirt': ['engirt', 'tinger'], + 'englacial': ['angelical', 'englacial', 'galenical'], + 'englacially': ['angelically', 'englacially'], + 'englad': ['angled', 'dangle', 'englad', 'lagend'], + 'englander': ['englander', 'greenland'], + 'english': ['english', 'shingle'], + 'englisher': ['englisher', 'reshingle'], + 'englut': ['englut', 'gluten', 'ungelt'], + 'engobe': ['begone', 'engobe'], + 'engold': ['engold', 'golden'], + 'engrail': ['aligner', 'engrail', 'realign', 'reginal'], + 'engrailed': ['engrailed', 'geraldine'], + 'engrailment': ['engrailment', 'realignment'], + 'engrain': ['earning', 'engrain'], + 'engrained': ['endearing', 'engrained', 'grenadine'], + 'engrainedly': ['endearingly', 'engrainedly'], + 'engram': ['engram', 'german', 'manger'], + 'engraphic': ['engraphic', 'preaching'], + 'engrave': ['avenger', 'engrave'], + 'engross': ['engross', 'grossen'], + 'enhat': ['enhat', 'ethan', 'nathe', 'neath', 'thane'], + 'enheart': ['earthen', 'enheart', 'hearten', 'naether', 'teheran', 'traheen'], + 'enherit': ['enherit', 'etherin', 'neither', 'therein'], + 'enhydra': ['enhydra', 'henyard'], + 'eniac': ['anice', 'eniac'], + 'enicuridae': ['audiencier', 'enicuridae'], + 'enid': ['dine', 'enid', 'inde', 'nide'], + 'enif': ['enif', 'fine', 'neif', 'nife'], + 'enisle': ['enisle', 'ensile', 'senile', 'silene'], + 'enlace': ['elance', 'enlace'], + 'enlard': ['aldern', + 'darnel', + 'enlard', + 'lander', + 'lenard', + 'randle', + 'reland'], + 'enlarge': ['enlarge', 'general', 'gleaner'], + 'enleaf': ['enleaf', 'leafen'], + 'enlief': ['enfile', 'enlief', 'enlife', 'feline'], + 'enlife': ['enfile', 'enlief', 'enlife', 'feline'], + 'enlight': ['enlight', 'lighten'], + 'enlist': ['enlist', 'listen', 'silent', 'tinsel'], + 'enlisted': ['enlisted', 'lintseed'], + 'enlister': ['enlister', 'esterlin', 'listener', 'relisten'], + 'enmass': ['enmass', 'maness', 'messan'], + 'enneadic': ['cadinene', 'decennia', 'enneadic'], + 'ennobler': ['ennobler', 'nonrebel'], + 'ennoic': ['conine', 'connie', 'ennoic'], + 'ennomic': ['ennomic', 'meconin'], + 'enoch': ['cohen', 'enoch'], + 'enocyte': ['enocyte', 'neocyte'], + 'enodal': ['enodal', 'loaden'], + 'enoil': ['enoil', 'ileon', 'olein'], + 'enol': ['elon', 'enol', 'leno', 'leon', 'lone', 'noel'], + 'enolic': ['clione', 'coelin', 'encoil', 'enolic'], + 'enomania': ['enomania', 'maeonian'], + 'enomotarch': ['chromatone', 'enomotarch'], + 'enorganic': ['enorganic', 'ignorance'], + 'enorm': ['enorm', 'moner', 'morne'], + 'enormous': ['enormous', 'unmorose'], + 'enos': ['enos', 'nose'], + 'enostosis': ['enostosis', 'sootiness'], + 'enow': ['enow', 'owen', 'wone'], + 'enphytotic': ['enphytotic', 'entophytic'], + 'enrace': ['careen', 'carene', 'enrace'], + 'enrage': ['egeran', 'enrage', 'ergane', 'genear', 'genera'], + 'enraged': ['derange', 'enraged', 'gardeen', 'gerenda', 'grandee', 'grenade'], + 'enragedly': ['enragedly', 'legendary'], + 'enrapt': ['arpent', + 'enrapt', + 'entrap', + 'panter', + 'parent', + 'pretan', + 'trepan'], + 'enravish': ['enravish', 'ravenish', 'vanisher'], + 'enray': ['enray', 'yearn'], + 'enrib': ['brine', 'enrib'], + 'enrich': ['enrich', 'nicher', 'richen'], + 'enring': ['enring', 'ginner'], + 'enrive': ['enrive', 'envier', 'veiner', 'verine'], + 'enrobe': ['boreen', 'enrobe', 'neebor', 'rebone'], + 'enrol': ['enrol', 'loren'], + 'enrolled': ['enrolled', 'rondelle'], + 'enrough': ['enrough', 'roughen'], + 'enruin': ['enruin', 'neurin', 'unrein'], + 'enrut': ['enrut', 'tuner', 'urent'], + 'ens': ['ens', 'sen'], + 'ensaint': ['ensaint', 'stanine'], + 'ensate': ['ensate', 'enseat', 'santee', 'sateen', 'senate'], + 'ense': ['ense', 'esne', 'nese', 'seen', 'snee'], + 'enseam': ['enseam', 'semnae'], + 'enseat': ['ensate', 'enseat', 'santee', 'sateen', 'senate'], + 'ensepulcher': ['ensepulcher', 'ensepulchre'], + 'ensepulchre': ['ensepulcher', 'ensepulchre'], + 'enshade': ['dasheen', 'enshade'], + 'enshroud': ['enshroud', 'unshored'], + 'ensigncy': ['ensigncy', 'syngenic'], + 'ensilage': ['ensilage', 'genesial', 'signalee'], + 'ensile': ['enisle', 'ensile', 'senile', 'silene'], + 'ensilver': ['ensilver', 'sniveler'], + 'ensmall': ['ensmall', 'smallen'], + 'ensoul': ['ensoul', 'olenus', 'unsole'], + 'enspirit': ['enspirit', 'pristine'], + 'enstar': ['astern', 'enstar', 'stenar', 'sterna'], + 'enstatite': ['enstatite', 'intestate', 'satinette'], + 'enstool': ['enstool', 'olonets'], + 'enstore': ['enstore', 'estrone', 'storeen', 'tornese'], + 'ensue': ['ensue', 'seenu', 'unsee'], + 'ensuer': ['ensuer', 'ensure'], + 'ensure': ['ensuer', 'ensure'], + 'entablature': ['entablature', 'untreatable'], + 'entach': ['entach', 'netcha'], + 'entad': ['denat', 'entad'], + 'entada': ['adnate', 'entada'], + 'entail': ['entail', 'tineal'], + 'entailer': ['elaterin', 'entailer', 'treenail'], + 'ental': ['ental', 'laten', 'leant'], + 'entasia': ['anisate', 'entasia'], + 'entasis': ['entasis', 'sestian', 'sestina'], + 'entelam': ['entelam', 'leetman'], + 'enter': ['enter', 'neter', 'renet', 'terne', 'treen'], + 'enteral': ['alterne', 'enteral', 'eternal', 'teleran', 'teneral'], + 'enterer': ['enterer', 'terrene'], + 'enteria': ['enteria', 'trainee', 'triaene'], + 'enteric': ['citrene', 'enteric', 'enticer', 'tercine'], + 'enterocolitis': ['coloenteritis', 'enterocolitis'], + 'enterogastritis': ['enterogastritis', 'gastroenteritis'], + 'enteroid': ['enteroid', 'orendite'], + 'enteron': ['enteron', 'tenoner'], + 'enteropexy': ['enteropexy', 'oxyterpene'], + 'entertain': ['entertain', 'tarentine', 'terentian'], + 'entheal': ['entheal', 'lethean'], + 'enthraldom': ['enthraldom', 'motherland'], + 'enthuse': ['enthuse', 'unsheet'], + 'entia': ['entia', 'teian', 'tenai', 'tinea'], + 'enticer': ['citrene', 'enteric', 'enticer', 'tercine'], + 'entincture': ['entincture', 'unreticent'], + 'entire': ['entire', 'triene'], + 'entirely': ['entirely', 'lientery'], + 'entirety': ['entirety', 'eternity'], + 'entity': ['entity', 'tinety'], + 'entocoelic': ['coelection', 'entocoelic'], + 'entocornea': ['encoronate', 'entocornea'], + 'entohyal': ['entohyal', 'ethanoyl'], + 'entoil': ['entoil', 'lionet'], + 'entomeric': ['entomeric', 'intercome', 'morencite'], + 'entomic': ['centimo', 'entomic', 'tecomin'], + 'entomical': ['entomical', 'melanotic'], + 'entomion': ['entomion', 'noontime'], + 'entomoid': ['demotion', 'entomoid', 'moontide'], + 'entomophily': ['entomophily', 'monophylite'], + 'entomotomy': ['entomotomy', 'omentotomy'], + 'entoparasite': ['antiprotease', 'entoparasite'], + 'entophyte': ['entophyte', 'tenophyte'], + 'entophytic': ['enphytotic', 'entophytic'], + 'entopic': ['entopic', 'nepotic', 'pentoic'], + 'entoplastic': ['entoplastic', 'spinotectal', 'tectospinal', 'tenoplastic'], + 'entoretina': ['entoretina', 'tetraonine'], + 'entosarc': ['ancestor', 'entosarc'], + 'entotic': ['entotic', 'tonetic'], + 'entozoa': ['entozoa', 'ozonate'], + 'entozoic': ['entozoic', 'enzootic'], + 'entrail': ['entrail', + 'latiner', + 'latrine', + 'ratline', + 'reliant', + 'retinal', + 'trenail'], + 'entrain': ['entrain', 'teriann'], + 'entrance': ['centenar', 'entrance'], + 'entrap': ['arpent', + 'enrapt', + 'entrap', + 'panter', + 'parent', + 'pretan', + 'trepan'], + 'entreat': ['entreat', 'ratteen', 'tarente', 'ternate', 'tetrane'], + 'entreating': ['entreating', 'interagent'], + 'entree': ['entree', 'rentee', 'retene'], + 'entrepas': ['entrepas', 'septenar'], + 'entropion': ['entropion', 'pontonier', 'prenotion'], + 'entropium': ['entropium', 'importune'], + 'entrust': ['entrust', 'stunter', 'trusten'], + 'enumeration': ['enumeration', 'mountaineer'], + 'enunciation': ['enunciation', 'incuneation'], + 'enunciator': ['enunciator', 'uncreation'], + 'enure': ['enure', 'reune'], + 'envelope': ['envelope', 'ovenpeel'], + 'enverdure': ['enverdure', 'unrevered'], + 'envied': ['endive', 'envied', 'veined'], + 'envier': ['enrive', 'envier', 'veiner', 'verine'], + 'envious': ['envious', 'niveous', 'veinous'], + 'envoy': ['envoy', 'nevoy', 'yoven'], + 'enwood': ['enwood', 'wooden'], + 'enwound': ['enwound', 'unowned'], + 'enwrap': ['enwrap', 'pawner', 'repawn'], + 'enwrite': ['enwrite', 'retwine'], + 'enzootic': ['entozoic', 'enzootic'], + 'eoan': ['aeon', 'eoan'], + 'eogaean': ['eogaean', 'neogaea'], + 'eolithic': ['chiolite', 'eolithic'], + 'eon': ['eon', 'neo', 'one'], + 'eonism': ['eonism', 'mesion', 'oneism', 'simeon'], + 'eophyton': ['eophyton', 'honeypot'], + 'eosaurus': ['eosaurus', 'rousseau'], + 'eosin': ['eosin', 'noise'], + 'eosinoblast': ['bosselation', 'eosinoblast'], + 'epacrid': ['epacrid', 'peracid', 'preacid'], + 'epacris': ['epacris', 'scrapie', 'serapic'], + 'epactal': ['epactal', 'placate'], + 'eparch': ['aperch', 'eparch', 'percha', 'preach'], + 'eparchial': ['eparchial', 'raphaelic'], + 'eparchy': ['eparchy', 'preachy'], + 'epha': ['epha', 'heap'], + 'epharmonic': ['epharmonic', 'pinachrome'], + 'ephemeris': ['emeership', 'ephemeris'], + 'ephod': ['depoh', 'ephod', 'hoped'], + 'ephor': ['ephor', 'hoper'], + 'ephorus': ['ephorus', 'orpheus', 'upshore'], + 'epibasal': ['ablepsia', 'epibasal'], + 'epibole': ['epibole', 'epilobe'], + 'epic': ['epic', 'pice'], + 'epical': ['epical', 'piacle', 'plaice'], + 'epicarp': ['crappie', 'epicarp'], + 'epicentral': ['epicentral', 'parentelic'], + 'epiceratodus': ['dipteraceous', 'epiceratodus'], + 'epichorial': ['aerophilic', 'epichorial'], + 'epicly': ['epicly', 'pyelic'], + 'epicostal': ['alopecist', 'altiscope', 'epicostal', 'scapolite'], + 'epicotyl': ['epicotyl', 'lipocyte'], + 'epicranial': ['epicranial', 'periacinal'], + 'epiderm': ['demirep', 'epiderm', 'impeder', 'remiped'], + 'epiderma': ['epiderma', 'premedia'], + 'epidermal': ['epidermal', 'impleader', 'premedial'], + 'epidermis': ['dispireme', 'epidermis'], + 'epididymovasostomy': ['epididymovasostomy', 'vasoepididymostomy'], + 'epidural': ['dipleura', 'epidural'], + 'epigram': ['epigram', 'primage'], + 'epilabrum': ['epilabrum', 'impuberal'], + 'epilachna': ['cephalina', 'epilachna'], + 'epilate': ['epilate', 'epitela', 'pileate'], + 'epilation': ['epilation', 'polianite'], + 'epilatory': ['epilatory', 'petiolary'], + 'epilobe': ['epibole', 'epilobe'], + 'epimer': ['empire', 'epimer'], + 'epiotic': ['epiotic', 'poietic'], + 'epipactis': ['epipactis', 'epipastic'], + 'epipastic': ['epipactis', 'epipastic'], + 'epiplasm': ['epiplasm', 'palmipes'], + 'epiploic': ['epiploic', 'epipolic'], + 'epipolic': ['epiploic', 'epipolic'], + 'epirotic': ['epirotic', 'periotic'], + 'episclera': ['episclera', 'periclase'], + 'episematic': ['episematic', 'septicemia'], + 'episodal': ['episodal', 'lapidose', 'sepaloid'], + 'episodial': ['apsidiole', 'episodial'], + 'epistatic': ['epistatic', 'pistacite'], + 'episternal': ['alpestrine', 'episternal', 'interlapse', 'presential'], + 'episternum': ['episternum', 'uprisement'], + 'epistlar': ['epistlar', 'pilaster', 'plaister', 'priestal'], + 'epistle': ['epistle', 'septile'], + 'epistler': ['epistler', 'spirelet'], + 'epistoler': ['epistoler', 'peristole', 'perseitol', 'pistoleer'], + 'epistoma': ['epistoma', 'metopias'], + 'epistome': ['epistome', 'epsomite'], + 'epistroma': ['epistroma', 'peristoma'], + 'epitela': ['epilate', 'epitela', 'pileate'], + 'epithecal': ['epithecal', 'petechial', 'phacelite'], + 'epithecate': ['epithecate', 'petechiate'], + 'epithet': ['epithet', 'heptite'], + 'epithyme': ['epithyme', 'hemitype'], + 'epitomizer': ['epitomizer', 'peritomize'], + 'epizoal': ['epizoal', 'lopezia', 'opalize'], + 'epoch': ['epoch', 'poche'], + 'epodic': ['copied', 'epodic'], + 'epornitic': ['epornitic', 'proteinic'], + 'epos': ['epos', 'peso', 'pose', 'sope'], + 'epsilon': ['epsilon', 'sinople'], + 'epsomite': ['epistome', 'epsomite'], + 'epulis': ['epulis', 'pileus'], + 'epulo': ['epulo', 'loupe'], + 'epuloid': ['epuloid', 'euploid'], + 'epulosis': ['epulosis', 'pelusios'], + 'epulotic': ['epulotic', 'poultice'], + 'epural': ['epural', 'perula', 'pleura'], + 'epuration': ['epuration', 'eupatorin'], + 'equal': ['equal', 'quale', 'queal'], + 'equalable': ['aquabelle', 'equalable'], + 'equiangle': ['angelique', 'equiangle'], + 'equinity': ['equinity', 'inequity'], + 'equip': ['equip', 'pique'], + 'equitable': ['equitable', 'quietable'], + 'equitist': ['equitist', 'quietist'], + 'equus': ['equus', 'usque'], + 'er': ['er', 're'], + 'era': ['aer', 'are', 'ear', 'era', 'rea'], + 'erade': ['eared', 'erade'], + 'eradicant': ['carinated', 'eradicant'], + 'eradicator': ['corradiate', 'cortaderia', 'eradicator'], + 'eral': ['earl', 'eral', 'lear', 'real'], + 'eranist': ['asterin', 'eranist', 'restain', 'stainer', 'starnie', 'stearin'], + 'erase': ['easer', 'erase'], + 'erased': ['erased', 'reseda', 'seared'], + 'eraser': ['eraser', 'searer'], + 'erasmian': ['erasmian', 'raiseman'], + 'erasmus': ['assumer', 'erasmus', 'masseur'], + 'erastian': ['artesian', 'asterina', 'asternia', 'erastian', 'seatrain'], + 'erastus': ['erastus', 'ressaut'], + 'erava': ['avera', 'erava'], + 'erbia': ['barie', 'beira', 'erbia', 'rebia'], + 'erbium': ['erbium', 'imbrue'], + 'erd': ['erd', 'red'], + 'ere': ['eer', 'ere', 'ree'], + 'erect': ['crete', 'erect'], + 'erectable': ['celebrate', 'erectable'], + 'erecting': ['erecting', 'gentrice'], + 'erection': ['erection', 'neoteric', 'nocerite', 'renotice'], + 'eremic': ['eremic', 'merice'], + 'eremital': ['eremital', 'materiel'], + 'erept': ['erept', 'peter', 'petre'], + 'ereptic': ['ereptic', 'precite', 'receipt'], + 'ereption': ['ereption', 'tropeine'], + 'erethic': ['erethic', 'etheric', 'heretic', 'heteric', 'teicher'], + 'erethism': ['erethism', 'etherism', 'heterism'], + 'erethismic': ['erethismic', 'hetericism'], + 'erethistic': ['erethistic', 'hetericist'], + 'eretrian': ['arretine', 'eretrian', 'eritrean', 'retainer'], + 'erg': ['erg', 'ger', 'reg'], + 'ergal': ['argel', 'ergal', 'garle', 'glare', 'lager', 'large', 'regal'], + 'ergamine': ['ergamine', 'merginae'], + 'ergane': ['egeran', 'enrage', 'ergane', 'genear', 'genera'], + 'ergastic': ['agrestic', 'ergastic'], + 'ergates': ['ergates', 'gearset', 'geaster'], + 'ergoism': ['ergoism', 'ogreism'], + 'ergomaniac': ['ergomaniac', 'grecomania'], + 'ergon': ['ergon', 'genro', 'goner', 'negro'], + 'ergot': ['ergot', 'rotge'], + 'ergotamine': ['angiometer', 'ergotamine', 'geometrina'], + 'ergotin': ['ergotin', 'genitor', 'negrito', 'ogtiern', 'trigone'], + 'ergusia': ['ergusia', 'gerusia', 'sarigue'], + 'eria': ['aire', 'eria'], + 'erian': ['erian', 'irena', 'reina'], + 'eric': ['eric', 'rice'], + 'erica': ['acier', 'aeric', 'ceria', 'erica'], + 'ericad': ['acider', 'ericad'], + 'erical': ['carlie', 'claire', 'eclair', 'erical'], + 'erichtoid': ['dichroite', 'erichtoid', 'theriodic'], + 'erigenia': ['aegirine', 'erigenia'], + 'erigeron': ['erigeron', 'reignore'], + 'erik': ['erik', 'kier', 'reki'], + 'erineum': ['erineum', 'unireme'], + 'erinose': ['erinose', 'roseine'], + 'eristalis': ['eristalis', 'serialist'], + 'eristic': ['ectiris', 'eristic'], + 'eristical': ['eristical', 'realistic'], + 'erithacus': ['erithacus', 'eucharist'], + 'eritrean': ['arretine', 'eretrian', 'eritrean', 'retainer'], + 'erma': ['erma', 'mare', 'rame', 'ream'], + 'ermani': ['ermani', 'marine', 'remain'], + 'ermines': ['ermines', 'inermes'], + 'erne': ['erne', 'neer', 'reen'], + 'ernest': ['ernest', 'nester', 'resent', 'streen'], + 'ernie': ['ernie', 'ierne', 'irene'], + 'ernst': ['ernst', 'stern'], + 'erode': ['doree', 'erode'], + 'eros': ['eros', 'rose', 'sero', 'sore'], + 'erose': ['erose', 'soree'], + 'erotesis': ['erotesis', 'isostere'], + 'erotic': ['erotic', 'tercio'], + 'erotical': ['calorite', 'erotical', 'loricate'], + 'eroticism': ['eroticism', 'isometric', 'meroistic', 'trioecism'], + 'erotism': ['erotism', 'mortise', 'trisome'], + 'erotogenic': ['erotogenic', 'geocronite', 'orogenetic'], + 'errabund': ['errabund', 'unbarred'], + 'errand': ['darner', 'darren', 'errand', 'rander', 'redarn'], + 'errant': ['arrent', 'errant', 'ranter', 'ternar'], + 'errantia': ['artarine', 'errantia'], + 'erratic': ['cartier', 'cirrate', 'erratic'], + 'erratum': ['erratum', 'maturer'], + 'erring': ['erring', 'rering', 'ringer'], + 'errite': ['errite', 'reiter', 'retier', 'retire', 'tierer'], + 'ers': ['ers', 'ser'], + 'ersar': ['ersar', 'raser', 'serra'], + 'erse': ['erse', 'rees', 'seer', 'sere'], + 'erthen': ['erthen', 'henter', 'nether', 'threne'], + 'eruc': ['cure', 'ecru', 'eruc'], + 'eruciform': ['eruciform', 'urceiform'], + 'erucin': ['curine', 'erucin', 'neuric'], + 'erucivorous': ['erucivorous', 'overcurious'], + 'eruct': ['cruet', 'eruct', 'recut', 'truce'], + 'eruction': ['eruction', 'neurotic'], + 'erugate': ['erugate', 'guetare'], + 'erumpent': ['erumpent', 'untemper'], + 'eruption': ['eruption', 'unitrope'], + 'erwin': ['erwin', 'rewin', 'winer'], + 'eryngium': ['eryngium', 'gynerium'], + 'eryon': ['eryon', 'onery'], + 'eryops': ['eryops', 'osprey'], + 'erythea': ['erythea', 'hetaery', 'yeather'], + 'erythrin': ['erythrin', 'tyrrheni'], + 'erythrophage': ['erythrophage', 'heterography'], + 'erythrophyllin': ['erythrophyllin', 'phylloerythrin'], + 'erythropia': ['erythropia', 'pyrotheria'], + 'es': ['es', 'se'], + 'esca': ['case', 'esca'], + 'escalan': ['escalan', 'scalena'], + 'escalin': ['celsian', 'escalin', 'sanicle', 'secalin'], + 'escaloped': ['copleased', 'escaloped'], + 'escapement': ['escapement', 'espacement'], + 'escaper': ['escaper', 'respace'], + 'escarp': ['casper', 'escarp', 'parsec', 'scrape', 'secpar', 'spacer'], + 'eschar': ['arches', 'chaser', 'eschar', 'recash', 'search'], + 'eschara': ['asearch', 'eschara'], + 'escheator': ['escheator', 'tocharese'], + 'escobilla': ['escobilla', 'obeliscal'], + 'escolar': ['escolar', 'solacer'], + 'escort': ['corset', 'cortes', 'coster', 'escort', 'scoter', 'sector'], + 'escortment': ['centermost', 'escortment'], + 'escrol': ['closer', 'cresol', 'escrol'], + 'escropulo': ['escropulo', 'supercool'], + 'esculent': ['esculent', 'unselect'], + 'esculin': ['esculin', 'incluse'], + 'esere': ['esere', 'reese', 'resee'], + 'esexual': ['esexual', 'sexuale'], + 'eshin': ['eshin', 'shine'], + 'esiphonal': ['esiphonal', 'phaseolin'], + 'esker': ['esker', 'keres', 'reesk', 'seker', 'skeer', 'skere'], + 'eskualdun': ['eskualdun', 'euskaldun'], + 'eskuara': ['eskuara', 'euskara'], + 'esne': ['ense', 'esne', 'nese', 'seen', 'snee'], + 'esophagogastrostomy': ['esophagogastrostomy', 'gastroesophagostomy'], + 'esopus': ['esopus', 'spouse'], + 'esoterical': ['cesarolite', 'esoterical'], + 'esoterist': ['esoterist', 'trisetose'], + 'esotrope': ['esotrope', 'proteose'], + 'esotropia': ['aportoise', 'esotropia'], + 'espacement': ['escapement', 'espacement'], + 'espadon': ['espadon', 'spadone'], + 'esparto': ['esparto', 'petrosa', 'seaport'], + 'esperantic': ['esperantic', 'interspace'], + 'esperantido': ['desperation', 'esperantido'], + 'esperantism': ['esperantism', 'strepsinema'], + 'esperanto': ['esperanto', 'personate'], + 'espial': ['espial', 'lipase', 'pelias'], + 'espier': ['espier', 'peiser'], + 'espinal': ['espinal', 'pinales', 'spaniel'], + 'espino': ['espino', 'sepion'], + 'espringal': ['espringal', 'presignal', 'relapsing'], + 'esquire': ['esquire', 'risquee'], + 'essence': ['essence', 'senesce'], + 'essenism': ['essenism', 'messines'], + 'essie': ['essie', 'seise'], + 'essling': ['essling', 'singles'], + 'essoin': ['essoin', 'ossein'], + 'essonite': ['essonite', 'ossetine'], + 'essorant': ['assentor', 'essorant', 'starnose'], + 'estamene': ['easement', 'estamene'], + 'esteem': ['esteem', 'mestee'], + 'estella': ['estella', 'sellate'], + 'ester': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'esterlin': ['enlister', 'esterlin', 'listener', 'relisten'], + 'esterling': ['esterling', 'steerling'], + 'estevin': ['estevin', 'tensive'], + 'esth': ['esth', 'hest', 'seth'], + 'esther': ['esther', 'hester', 'theres'], + 'estivage': ['estivage', 'vegasite'], + 'estoc': ['coset', 'estoc', 'scote'], + 'estonian': ['estonian', 'nasonite'], + 'estop': ['estop', 'stoep', 'stope'], + 'estradiol': ['estradiol', 'idolaster'], + 'estrange': ['estrange', 'segreant', 'sergeant', 'sternage'], + 'estray': ['atresy', 'estray', 'reasty', 'stayer'], + 'estre': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'estreat': ['estreat', 'restate', 'retaste'], + 'estrepe': ['estrepe', 'resteep', 'steeper'], + 'estriate': ['estriate', 'treatise'], + 'estrin': ['estrin', 'insert', 'sinter', 'sterin', 'triens'], + 'estriol': ['estriol', 'torsile'], + 'estrogen': ['estrogen', 'gerontes'], + 'estrone': ['enstore', 'estrone', 'storeen', 'tornese'], + 'estrous': ['estrous', 'oestrus', 'sestuor', 'tussore'], + 'estrual': ['arustle', 'estrual', 'saluter', 'saulter'], + 'estufa': ['estufa', 'fusate'], + 'eta': ['ate', 'eat', 'eta', 'tae', 'tea'], + 'etacism': ['cameist', 'etacism', 'sematic'], + 'etacist': ['etacist', 'statice'], + 'etalon': ['etalon', 'tolane'], + 'etamin': ['etamin', 'inmate', 'taimen', 'tamein'], + 'etamine': ['amenite', 'etamine', 'matinee'], + 'etch': ['chet', 'etch', 'tche', 'tech'], + 'etcher': ['cherte', 'etcher'], + 'eternal': ['alterne', 'enteral', 'eternal', 'teleran', 'teneral'], + 'eternalism': ['eternalism', 'streamline'], + 'eternity': ['entirety', 'eternity'], + 'etesian': ['etesian', 'senaite'], + 'ethal': ['ethal', 'lathe', 'leath'], + 'ethan': ['enhat', 'ethan', 'nathe', 'neath', 'thane'], + 'ethanal': ['anthela', 'ethanal'], + 'ethane': ['ethane', 'taheen'], + 'ethanediol': ['endothelia', 'ethanediol', 'ethenoidal'], + 'ethanim': ['ethanim', 'hematin'], + 'ethanoyl': ['entohyal', 'ethanoyl'], + 'ethel': ['ethel', 'lethe'], + 'ethenoidal': ['endothelia', 'ethanediol', 'ethenoidal'], + 'ether': ['ether', 'rethe', 'theer', 'there', 'three'], + 'etheria': ['ehretia', 'etheria'], + 'etheric': ['erethic', 'etheric', 'heretic', 'heteric', 'teicher'], + 'etherin': ['enherit', 'etherin', 'neither', 'therein'], + 'etherion': ['etherion', 'hereinto', 'heronite'], + 'etherism': ['erethism', 'etherism', 'heterism'], + 'etherization': ['etherization', 'heterization'], + 'etherize': ['etherize', 'heterize'], + 'ethicism': ['ethicism', 'shemitic'], + 'ethicist': ['ethicist', 'thecitis', 'theistic'], + 'ethics': ['ethics', 'sethic'], + 'ethid': ['edith', 'ethid'], + 'ethine': ['ethine', 'theine'], + 'ethiop': ['ethiop', 'ophite', 'peitho'], + 'ethmoidal': ['ethmoidal', 'oldhamite'], + 'ethmosphenoid': ['ethmosphenoid', 'sphenoethmoid'], + 'ethmosphenoidal': ['ethmosphenoidal', 'sphenoethmoidal'], + 'ethnal': ['ethnal', 'hantle', 'lathen', 'thenal'], + 'ethnical': ['chainlet', 'ethnical'], + 'ethnological': ['allothogenic', 'ethnological'], + 'ethnos': ['ethnos', 'honest'], + 'ethography': ['ethography', 'hyetograph'], + 'ethologic': ['ethologic', 'theologic'], + 'ethological': ['ethological', 'lethologica', 'theological'], + 'ethology': ['ethology', 'theology'], + 'ethos': ['ethos', 'shote', 'those'], + 'ethylic': ['ethylic', 'techily'], + 'ethylin': ['ethylin', 'thienyl'], + 'etna': ['ante', 'aten', 'etna', 'nate', 'neat', 'taen', 'tane', 'tean'], + 'etnean': ['etnean', 'neaten'], + 'etonian': ['enation', 'etonian'], + 'etruscan': ['etruscan', 'recusant'], + 'etta': ['etta', 'tate', 'teat'], + 'ettarre': ['ettarre', 'retreat', 'treater'], + 'ettle': ['ettle', 'tetel'], + 'etua': ['aute', 'etua'], + 'euaster': ['austere', 'euaster'], + 'eucalypteol': ['eucalypteol', 'eucalyptole'], + 'eucalyptole': ['eucalypteol', 'eucalyptole'], + 'eucatropine': ['eucatropine', 'neurectopia'], + 'eucharis': ['acheirus', 'eucharis'], + 'eucharist': ['erithacus', 'eucharist'], + 'euchlaena': ['acheulean', 'euchlaena'], + 'eulogism': ['eulogism', 'uglisome'], + 'eumolpus': ['eumolpus', 'plumeous'], + 'eunomia': ['eunomia', 'moineau'], + 'eunomy': ['eunomy', 'euonym'], + 'euonym': ['eunomy', 'euonym'], + 'eupatorin': ['epuration', 'eupatorin'], + 'euplastic': ['euplastic', 'spiculate'], + 'euploid': ['epuloid', 'euploid'], + 'euproctis': ['crepitous', 'euproctis', 'uroseptic'], + 'eurindic': ['dineuric', 'eurindic'], + 'eurus': ['eurus', 'usure'], + 'euscaro': ['acerous', 'carouse', 'euscaro'], + 'euskaldun': ['eskualdun', 'euskaldun'], + 'euskara': ['eskuara', 'euskara'], + 'eusol': ['eusol', 'louse'], + 'eutannin': ['eutannin', 'uninnate'], + 'eutaxic': ['auxetic', 'eutaxic'], + 'eutheria': ['eutheria', 'hauerite'], + 'eutropic': ['eutropic', 'outprice'], + 'eva': ['ave', 'eva'], + 'evade': ['deave', 'eaved', 'evade'], + 'evader': ['evader', 'verdea'], + 'evadne': ['advene', 'evadne'], + 'evan': ['evan', 'nave', 'vane'], + 'evanish': ['evanish', 'inshave'], + 'evase': ['eaves', 'evase', 'seave'], + 'eve': ['eve', 'vee'], + 'evea': ['eave', 'evea'], + 'evection': ['civetone', 'evection'], + 'evejar': ['evejar', 'rajeev'], + 'evelyn': ['evelyn', 'evenly'], + 'even': ['even', 'neve', 'veen'], + 'evener': ['evener', 'veneer'], + 'evenly': ['evelyn', 'evenly'], + 'evens': ['evens', 'seven'], + 'eveque': ['eveque', 'queeve'], + 'ever': ['ever', 'reve', 'veer'], + 'evert': ['evert', 'revet'], + 'everwhich': ['everwhich', 'whichever'], + 'everwho': ['everwho', 'however', 'whoever'], + 'every': ['every', 'veery'], + 'evestar': ['evestar', 'versate'], + 'evict': ['civet', 'evict'], + 'evil': ['evil', 'levi', 'live', 'veil', 'vile', 'vlei'], + 'evildoer': ['evildoer', 'overidle'], + 'evilhearted': ['evilhearted', 'vilehearted'], + 'evilly': ['evilly', 'lively', 'vilely'], + 'evilness': ['evilness', 'liveness', 'veinless', 'vileness', 'vineless'], + 'evince': ['cevine', 'evince', 'venice'], + 'evisite': ['evisite', 'visitee'], + 'evitation': ['evitation', 'novitiate'], + 'evocator': ['evocator', 'overcoat'], + 'evodia': ['evodia', 'ovidae'], + 'evoker': ['evoker', 'revoke'], + 'evolver': ['evolver', 'revolve'], + 'ewder': ['dewer', 'ewder', 'rewed'], + 'ewe': ['ewe', 'wee'], + 'ewer': ['ewer', 'were'], + 'exacter': ['exacter', 'excreta'], + 'exalt': ['exalt', 'latex'], + 'exam': ['amex', 'exam', 'xema'], + 'examinate': ['examinate', 'exanimate', 'metaxenia'], + 'examination': ['examination', 'exanimation'], + 'exanimate': ['examinate', 'exanimate', 'metaxenia'], + 'exanimation': ['examination', 'exanimation'], + 'exasperation': ['exasperation', 'xenoparasite'], + 'exaudi': ['adieux', 'exaudi'], + 'excarnation': ['centraxonia', 'excarnation'], + 'excecation': ['cacoxenite', 'excecation'], + 'except': ['except', 'expect'], + 'exceptant': ['exceptant', 'expectant'], + 'exceptive': ['exceptive', 'expective'], + 'excitation': ['excitation', 'intoxicate'], + 'excitor': ['excitor', 'xerotic'], + 'excreta': ['exacter', 'excreta'], + 'excurse': ['excurse', 'excuser'], + 'excuser': ['excurse', 'excuser'], + 'exert': ['exert', 'exter'], + 'exhilarate': ['exhilarate', 'heteraxial'], + 'exist': ['exist', 'sixte'], + 'exocarp': ['exocarp', 'praecox'], + 'exon': ['exon', 'oxen'], + 'exordia': ['exordia', 'exradio'], + 'exotic': ['coxite', 'exotic'], + 'expatiater': ['expatiater', 'expatriate'], + 'expatriate': ['expatiater', 'expatriate'], + 'expect': ['except', 'expect'], + 'expectant': ['exceptant', 'expectant'], + 'expective': ['exceptive', 'expective'], + 'expirator': ['expirator', 'operatrix'], + 'expiree': ['expiree', 'peixere'], + 'explicator': ['explicator', 'extropical'], + 'expressionism': ['expressionism', 'misexpression'], + 'exradio': ['exordia', 'exradio'], + 'extend': ['dentex', 'extend'], + 'exter': ['exert', 'exter'], + 'exterminate': ['antiextreme', 'exterminate'], + 'extirpationist': ['extirpationist', 'sextipartition'], + 'extra': ['extra', 'retax', 'taxer'], + 'extradural': ['dextraural', 'extradural'], + 'extropical': ['explicator', 'extropical'], + 'exultancy': ['exultancy', 'unexactly'], + 'ey': ['ey', 'ye'], + 'eyah': ['ahey', 'eyah', 'yeah'], + 'eyas': ['easy', 'eyas'], + 'eye': ['eye', 'yee'], + 'eyed': ['eyed', 'yede'], + 'eyen': ['eyen', 'eyne'], + 'eyer': ['eyer', 'eyre', 'yere'], + 'eyn': ['eyn', 'nye', 'yen'], + 'eyne': ['eyen', 'eyne'], + 'eyot': ['eyot', 'yote'], + 'eyra': ['aery', 'eyra', 'yare', 'year'], + 'eyre': ['eyer', 'eyre', 'yere'], + 'ezba': ['baze', 'ezba'], + 'ezra': ['ezra', 'raze'], + 'facebread': ['barefaced', 'facebread'], + 'facer': ['facer', 'farce'], + 'faciend': ['faciend', 'fancied'], + 'facile': ['facile', 'filace'], + 'faciobrachial': ['brachiofacial', 'faciobrachial'], + 'faciocervical': ['cervicofacial', 'faciocervical'], + 'factable': ['factable', 'labefact'], + 'factional': ['factional', 'falcation'], + 'factish': ['catfish', 'factish'], + 'facture': ['facture', 'furcate'], + 'facula': ['facula', 'faucal'], + 'fade': ['deaf', 'fade'], + 'fader': ['fader', 'farde'], + 'faery': ['faery', 'freya'], + 'fagoter': ['aftergo', 'fagoter'], + 'faience': ['faience', 'fiancee'], + 'fail': ['alif', 'fail'], + 'fain': ['fain', 'naif'], + 'fainly': ['fainly', 'naifly'], + 'faint': ['faint', 'fanti'], + 'fair': ['fair', 'fiar', 'raif'], + 'fake': ['fake', 'feak'], + 'faker': ['faker', 'freak'], + 'fakery': ['fakery', 'freaky'], + 'fakir': ['fakir', 'fraik', 'kafir', 'rafik'], + 'falcation': ['factional', 'falcation'], + 'falco': ['falco', 'focal'], + 'falconet': ['conflate', 'falconet'], + 'fallback': ['backfall', 'fallback'], + 'faller': ['faller', 'refall'], + 'fallfish': ['fallfish', 'fishfall'], + 'fallible': ['fallible', 'fillable'], + 'falling': ['falling', 'fingall'], + 'falser': ['falser', 'flaser'], + 'faltboat': ['faltboat', 'flatboat'], + 'falutin': ['falutin', 'flutina'], + 'falx': ['falx', 'flax'], + 'fameless': ['fameless', 'selfsame'], + 'famelessness': ['famelessness', 'selfsameness'], + 'famine': ['famine', 'infame'], + 'fancied': ['faciend', 'fancied'], + 'fangle': ['fangle', 'flange'], + 'fannia': ['fannia', 'fianna'], + 'fanti': ['faint', 'fanti'], + 'far': ['far', 'fra'], + 'farad': ['daraf', 'farad'], + 'farce': ['facer', 'farce'], + 'farcetta': ['afteract', 'artefact', 'farcetta', 'farctate'], + 'farctate': ['afteract', 'artefact', 'farcetta', 'farctate'], + 'farde': ['fader', 'farde'], + 'fardel': ['alfred', 'fardel'], + 'fare': ['fare', 'fear', 'frae', 'rafe'], + 'farfel': ['farfel', 'raffle'], + 'faring': ['faring', 'frangi'], + 'farl': ['farl', 'ralf'], + 'farleu': ['earful', 'farleu', 'ferula'], + 'farm': ['farm', 'fram'], + 'farmable': ['farmable', 'framable'], + 'farmer': ['farmer', 'framer'], + 'farming': ['farming', 'framing'], + 'farnesol': ['farnesol', 'forensal'], + 'faro': ['faro', 'fora'], + 'farolito': ['farolito', 'footrail'], + 'farse': ['farse', 'frase'], + 'farset': ['farset', 'faster', 'strafe'], + 'farsi': ['farsi', 'sarif'], + 'fascio': ['fascio', 'fiasco'], + 'fasher': ['afresh', 'fasher', 'ferash'], + 'fashioner': ['fashioner', 'refashion'], + 'fast': ['fast', 'saft'], + 'fasten': ['fasten', 'nefast', 'stefan'], + 'fastener': ['fastener', 'fenestra', 'refasten'], + 'faster': ['farset', 'faster', 'strafe'], + 'fasthold': ['fasthold', 'holdfast'], + 'fastland': ['fastland', 'landfast'], + 'fat': ['aft', 'fat'], + 'fatal': ['aflat', 'fatal'], + 'fate': ['atef', 'fate', 'feat'], + 'fated': ['defat', 'fated'], + 'father': ['father', 'freath', 'hafter'], + 'faucal': ['facula', 'faucal'], + 'faucet': ['faucet', 'fucate'], + 'faulter': ['faulter', 'refutal', 'tearful'], + 'faultfind': ['faultfind', 'findfault'], + 'faunish': ['faunish', 'nusfiah'], + 'faunist': ['faunist', 'fustian', 'infaust'], + 'favorer': ['favorer', 'overfar', 'refavor'], + 'fayles': ['fayles', 'safely'], + 'feague': ['feague', 'feuage'], + 'feak': ['fake', 'feak'], + 'feal': ['alef', 'feal', 'flea', 'leaf'], + 'fealty': ['fealty', 'featly'], + 'fear': ['fare', 'fear', 'frae', 'rafe'], + 'feastful': ['feastful', 'sufflate'], + 'feat': ['atef', 'fate', 'feat'], + 'featherbed': ['befathered', 'featherbed'], + 'featherer': ['featherer', 'hereafter'], + 'featly': ['fealty', 'featly'], + 'feckly': ['feckly', 'flecky'], + 'fecundate': ['fecundate', 'unfaceted'], + 'fecundator': ['fecundator', 'unfactored'], + 'federate': ['defeater', 'federate', 'redefeat'], + 'feeder': ['feeder', 'refeed'], + 'feeding': ['feeding', 'feigned'], + 'feel': ['feel', 'flee'], + 'feeler': ['feeler', 'refeel', 'reflee'], + 'feer': ['feer', 'free', 'reef'], + 'feering': ['feering', 'feigner', 'freeing', 'reefing', 'refeign'], + 'feetless': ['feetless', 'feteless'], + 'fei': ['fei', 'fie', 'ife'], + 'feif': ['feif', 'fife'], + 'feigned': ['feeding', 'feigned'], + 'feigner': ['feering', 'feigner', 'freeing', 'reefing', 'refeign'], + 'feil': ['feil', 'file', 'leif', 'lief', 'life'], + 'feint': ['feint', 'fient'], + 'feis': ['feis', 'fise', 'sife'], + 'feist': ['feist', 'stife'], + 'felapton': ['felapton', 'pantofle'], + 'felid': ['felid', 'field'], + 'feline': ['enfile', 'enlief', 'enlife', 'feline'], + 'felinity': ['felinity', 'finitely'], + 'fels': ['fels', 'self'], + 'felt': ['felt', 'flet', 'left'], + 'felter': ['felter', 'telfer', 'trefle'], + 'felting': ['felting', 'neftgil'], + 'feltness': ['feltness', 'leftness'], + 'felwort': ['elfwort', 'felwort'], + 'feminal': ['feminal', 'inflame'], + 'femora': ['femora', 'foamer'], + 'femorocaudal': ['caudofemoral', 'femorocaudal'], + 'femorotibial': ['femorotibial', 'tibiofemoral'], + 'femur': ['femur', 'fumer'], + 'fen': ['fen', 'nef'], + 'fender': ['fender', 'ferned'], + 'fenestra': ['fastener', 'fenestra', 'refasten'], + 'feodary': ['feodary', 'foreday'], + 'feral': ['feral', 'flare'], + 'ferash': ['afresh', 'fasher', 'ferash'], + 'feria': ['afire', 'feria'], + 'ferine': ['ferine', 'refine'], + 'ferison': ['ferison', 'foresin'], + 'ferity': ['ferity', 'freity'], + 'ferk': ['ferk', 'kerf'], + 'ferling': ['ferling', 'flinger', 'refling'], + 'ferly': ['ferly', 'flyer', 'refly'], + 'fermail': ['fermail', 'fermila'], + 'fermenter': ['fermenter', 'referment'], + 'fermila': ['fermail', 'fermila'], + 'ferned': ['fender', 'ferned'], + 'ferri': ['ferri', 'firer', 'freir', 'frier'], + 'ferrihydrocyanic': ['ferrihydrocyanic', 'hydroferricyanic'], + 'ferrohydrocyanic': ['ferrohydrocyanic', 'hydroferrocyanic'], + 'ferry': ['ferry', 'freyr', 'fryer'], + 'fertil': ['fertil', 'filter', 'lifter', 'relift', 'trifle'], + 'ferula': ['earful', 'farleu', 'ferula'], + 'ferule': ['ferule', 'fueler', 'refuel'], + 'ferulic': ['ferulic', 'lucifer'], + 'fervidity': ['devitrify', 'fervidity'], + 'festination': ['festination', 'infestation', 'sinfonietta'], + 'fet': ['eft', 'fet'], + 'fetal': ['aleft', 'alfet', 'fetal', 'fleta'], + 'fetcher': ['fetcher', 'refetch'], + 'feteless': ['feetless', 'feteless'], + 'fetial': ['fetial', 'filate', 'lafite', 'leafit'], + 'fetish': ['fetish', 'fishet'], + 'fetor': ['fetor', 'forte', 'ofter'], + 'fetter': ['fetter', 'frette'], + 'feuage': ['feague', 'feuage'], + 'feudalism': ['feudalism', 'sulfamide'], + 'feudally': ['delayful', 'feudally'], + 'feulamort': ['feulamort', 'formulate'], + 'fi': ['fi', 'if'], + 'fiance': ['fiance', 'inface'], + 'fiancee': ['faience', 'fiancee'], + 'fianna': ['fannia', 'fianna'], + 'fiar': ['fair', 'fiar', 'raif'], + 'fiard': ['fiard', 'fraid'], + 'fiasco': ['fascio', 'fiasco'], + 'fiber': ['bifer', 'brief', 'fiber'], + 'fibered': ['debrief', 'defiber', 'fibered'], + 'fiberless': ['briefless', 'fiberless', 'fibreless'], + 'fiberware': ['fiberware', 'fibreware'], + 'fibreless': ['briefless', 'fiberless', 'fibreless'], + 'fibreware': ['fiberware', 'fibreware'], + 'fibroadenoma': ['adenofibroma', 'fibroadenoma'], + 'fibroangioma': ['angiofibroma', 'fibroangioma'], + 'fibrochondroma': ['chondrofibroma', 'fibrochondroma'], + 'fibrocystoma': ['cystofibroma', 'fibrocystoma'], + 'fibrolipoma': ['fibrolipoma', 'lipofibroma'], + 'fibromucous': ['fibromucous', 'mucofibrous'], + 'fibromyoma': ['fibromyoma', 'myofibroma'], + 'fibromyxoma': ['fibromyxoma', 'myxofibroma'], + 'fibromyxosarcoma': ['fibromyxosarcoma', 'myxofibrosarcoma'], + 'fibroneuroma': ['fibroneuroma', 'neurofibroma'], + 'fibroserous': ['fibroserous', 'serofibrous'], + 'fiche': ['chief', 'fiche'], + 'fickleness': ['fickleness', 'fleckiness'], + 'fickly': ['fickly', 'flicky'], + 'fico': ['coif', 'fico', 'foci'], + 'fictional': ['cliftonia', 'fictional'], + 'ficula': ['ficula', 'fulica'], + 'fiddler': ['fiddler', 'flidder'], + 'fidele': ['defile', 'fidele'], + 'fidget': ['fidget', 'gifted'], + 'fidicula': ['fidicula', 'fiducial'], + 'fiducial': ['fidicula', 'fiducial'], + 'fie': ['fei', 'fie', 'ife'], + 'fiedlerite': ['fiedlerite', 'friedelite'], + 'field': ['felid', 'field'], + 'fielded': ['defiled', 'fielded'], + 'fielder': ['defiler', 'fielder'], + 'fieldman': ['fieldman', 'inflamed'], + 'fiendish': ['fiendish', 'finished'], + 'fient': ['feint', 'fient'], + 'fiery': ['fiery', 'reify'], + 'fife': ['feif', 'fife'], + 'fifteener': ['fifteener', 'teneriffe'], + 'fifty': ['fifty', 'tiffy'], + 'fig': ['fig', 'gif'], + 'fighter': ['fighter', 'freight', 'refight'], + 'figurate': ['figurate', 'fruitage'], + 'fike': ['efik', 'fike'], + 'filace': ['facile', 'filace'], + 'filago': ['filago', 'gifola'], + 'filao': ['filao', 'folia'], + 'filar': ['filar', 'flair', 'frail'], + 'filate': ['fetial', 'filate', 'lafite', 'leafit'], + 'file': ['feil', 'file', 'leif', 'lief', 'life'], + 'filelike': ['filelike', 'lifelike'], + 'filer': ['filer', 'flier', 'lifer', 'rifle'], + 'filet': ['filet', 'flite'], + 'fillable': ['fallible', 'fillable'], + 'filler': ['filler', 'refill'], + 'filo': ['filo', 'foil', 'lifo'], + 'filter': ['fertil', 'filter', 'lifter', 'relift', 'trifle'], + 'filterer': ['filterer', 'refilter'], + 'filthless': ['filthless', 'shelflist'], + 'filtrable': ['filtrable', 'flirtable'], + 'filtration': ['filtration', 'flirtation'], + 'finale': ['afenil', 'finale'], + 'finder': ['finder', 'friend', 'redfin', 'refind'], + 'findfault': ['faultfind', 'findfault'], + 'fine': ['enif', 'fine', 'neif', 'nife'], + 'finely': ['finely', 'lenify'], + 'finer': ['finer', 'infer'], + 'finesser': ['finesser', 'rifeness'], + 'fingall': ['falling', 'fingall'], + 'finger': ['finger', 'fringe'], + 'fingerer': ['fingerer', 'refinger'], + 'fingerflower': ['fingerflower', 'fringeflower'], + 'fingerless': ['fingerless', 'fringeless'], + 'fingerlet': ['fingerlet', 'fringelet'], + 'fingu': ['fingu', 'fungi'], + 'finical': ['finical', 'lanific'], + 'finished': ['fiendish', 'finished'], + 'finisher': ['finisher', 'refinish'], + 'finitely': ['felinity', 'finitely'], + 'finkel': ['elfkin', 'finkel'], + 'finlet': ['finlet', 'infelt'], + 'finner': ['finner', 'infern'], + 'firca': ['afric', 'firca'], + 'fire': ['fire', 'reif', 'rife'], + 'fireable': ['afebrile', 'balefire', 'fireable'], + 'firearm': ['firearm', 'marfire'], + 'fireback': ['backfire', 'fireback'], + 'fireburn': ['burnfire', 'fireburn'], + 'fired': ['fired', 'fried'], + 'fireplug': ['fireplug', 'gripeful'], + 'firer': ['ferri', 'firer', 'freir', 'frier'], + 'fireshaft': ['fireshaft', 'tasheriff'], + 'firestone': ['firestone', 'forestine'], + 'firetop': ['firetop', 'potifer'], + 'firm': ['firm', 'frim'], + 'first': ['first', 'frist'], + 'firth': ['firth', 'frith'], + 'fise': ['feis', 'fise', 'sife'], + 'fishbone': ['bonefish', 'fishbone'], + 'fisheater': ['fisheater', 'sherifate'], + 'fisher': ['fisher', 'sherif'], + 'fishery': ['fishery', 'sherify'], + 'fishet': ['fetish', 'fishet'], + 'fishfall': ['fallfish', 'fishfall'], + 'fishlet': ['fishlet', 'leftish'], + 'fishpond': ['fishpond', 'pondfish'], + 'fishpool': ['fishpool', 'foolship'], + 'fishwood': ['fishwood', 'woodfish'], + 'fissury': ['fissury', 'russify'], + 'fist': ['fist', 'sift'], + 'fisted': ['fisted', 'sifted'], + 'fister': ['fister', 'resift', 'sifter', 'strife'], + 'fisting': ['fisting', 'sifting'], + 'fitout': ['fitout', 'outfit'], + 'fitter': ['fitter', 'tifter'], + 'fixer': ['fixer', 'refix'], + 'flageolet': ['flageolet', 'folletage'], + 'flair': ['filar', 'flair', 'frail'], + 'flamant': ['flamant', 'flatman'], + 'flame': ['flame', 'fleam'], + 'flamed': ['flamed', 'malfed'], + 'flandowser': ['flandowser', 'sandflower'], + 'flange': ['fangle', 'flange'], + 'flare': ['feral', 'flare'], + 'flaser': ['falser', 'flaser'], + 'flasher': ['flasher', 'reflash'], + 'flatboat': ['faltboat', 'flatboat'], + 'flatman': ['flamant', 'flatman'], + 'flatwise': ['flatwise', 'saltwife'], + 'flaunt': ['flaunt', 'unflat'], + 'flax': ['falx', 'flax'], + 'flea': ['alef', 'feal', 'flea', 'leaf'], + 'fleam': ['flame', 'fleam'], + 'fleay': ['fleay', 'leafy'], + 'fleche': ['fleche', 'fleech'], + 'flecker': ['flecker', 'freckle'], + 'fleckiness': ['fickleness', 'fleckiness'], + 'flecky': ['feckly', 'flecky'], + 'fled': ['delf', 'fled'], + 'flee': ['feel', 'flee'], + 'fleech': ['fleche', 'fleech'], + 'fleer': ['fleer', 'refel'], + 'flemish': ['flemish', 'himself'], + 'flenser': ['flenser', 'fresnel'], + 'flesh': ['flesh', 'shelf'], + 'fleshed': ['deflesh', 'fleshed'], + 'fleshen': ['enflesh', 'fleshen'], + 'flesher': ['flesher', 'herself'], + 'fleshful': ['fleshful', 'shelfful'], + 'fleshiness': ['elfishness', 'fleshiness'], + 'fleshy': ['fleshy', 'shelfy'], + 'flet': ['felt', 'flet', 'left'], + 'fleta': ['aleft', 'alfet', 'fetal', 'fleta'], + 'fleuret': ['fleuret', 'treeful'], + 'flew': ['flew', 'welf'], + 'flexed': ['deflex', 'flexed'], + 'flexured': ['flexured', 'refluxed'], + 'flicky': ['fickly', 'flicky'], + 'flidder': ['fiddler', 'flidder'], + 'flier': ['filer', 'flier', 'lifer', 'rifle'], + 'fligger': ['fligger', 'friggle'], + 'flinger': ['ferling', 'flinger', 'refling'], + 'flingy': ['flingy', 'flying'], + 'flirtable': ['filtrable', 'flirtable'], + 'flirtation': ['filtration', 'flirtation'], + 'flirter': ['flirter', 'trifler'], + 'flirting': ['flirting', 'trifling'], + 'flirtingly': ['flirtingly', 'triflingly'], + 'flit': ['flit', 'lift'], + 'flite': ['filet', 'flite'], + 'fliting': ['fliting', 'lifting'], + 'flitter': ['flitter', 'triflet'], + 'flo': ['flo', 'lof'], + 'float': ['aloft', 'float', 'flota'], + 'floater': ['floater', 'florate', 'refloat'], + 'flobby': ['bobfly', 'flobby'], + 'flodge': ['flodge', 'fodgel'], + 'floe': ['floe', 'fole'], + 'flog': ['flog', 'golf'], + 'flogger': ['flogger', 'frogleg'], + 'floodable': ['bloodleaf', 'floodable'], + 'flooder': ['flooder', 'reflood'], + 'floodwater': ['floodwater', 'toadflower', 'waterflood'], + 'floorer': ['floorer', 'refloor'], + 'florate': ['floater', 'florate', 'refloat'], + 'florentine': ['florentine', 'nonfertile'], + 'floret': ['floret', 'forlet', 'lofter', 'torfel'], + 'floria': ['floria', 'foliar'], + 'floriate': ['floriate', 'foralite'], + 'florican': ['florican', 'fornical'], + 'floridan': ['floridan', 'florinda'], + 'florinda': ['floridan', 'florinda'], + 'flot': ['flot', 'loft'], + 'flota': ['aloft', 'float', 'flota'], + 'flounder': ['flounder', 'reunfold', 'unfolder'], + 'flour': ['flour', 'fluor'], + 'flourisher': ['flourisher', 'reflourish'], + 'flouting': ['flouting', 'outfling'], + 'flow': ['flow', 'fowl', 'wolf'], + 'flower': ['flower', 'fowler', 'reflow', 'wolfer'], + 'flowered': ['deflower', 'flowered'], + 'flowerer': ['flowerer', 'reflower'], + 'flowery': ['flowery', 'fowlery'], + 'flowing': ['flowing', 'fowling'], + 'floyd': ['floyd', 'foldy'], + 'fluavil': ['fluavil', 'fluvial', 'vialful'], + 'flucan': ['canful', 'flucan'], + 'fluctuant': ['fluctuant', 'untactful'], + 'flue': ['flue', 'fuel'], + 'fluent': ['fluent', 'netful', 'unfelt', 'unleft'], + 'fluidly': ['dullify', 'fluidly'], + 'flukewort': ['flukewort', 'flutework'], + 'fluor': ['flour', 'fluor'], + 'fluorate': ['fluorate', 'outflare'], + 'fluorinate': ['antifouler', 'fluorinate', 'uniflorate'], + 'fluorine': ['fluorine', 'neurofil'], + 'fluorobenzene': ['benzofluorene', 'fluorobenzene'], + 'flusher': ['flusher', 'reflush'], + 'flushing': ['flushing', 'lungfish'], + 'fluster': ['fluster', 'restful'], + 'flustra': ['flustra', 'starful'], + 'flutework': ['flukewort', 'flutework'], + 'flutina': ['falutin', 'flutina'], + 'fluvial': ['fluavil', 'fluvial', 'vialful'], + 'fluxer': ['fluxer', 'reflux'], + 'flyblow': ['blowfly', 'flyblow'], + 'flyer': ['ferly', 'flyer', 'refly'], + 'flying': ['flingy', 'flying'], + 'fo': ['fo', 'of'], + 'foal': ['foal', 'loaf', 'olaf'], + 'foamer': ['femora', 'foamer'], + 'focal': ['falco', 'focal'], + 'foci': ['coif', 'fico', 'foci'], + 'focuser': ['focuser', 'refocus'], + 'fodge': ['defog', 'fodge'], + 'fodgel': ['flodge', 'fodgel'], + 'fogeater': ['fogeater', 'foregate'], + 'fogo': ['fogo', 'goof'], + 'foil': ['filo', 'foil', 'lifo'], + 'foister': ['foister', 'forties'], + 'folden': ['enfold', 'folden', 'fondle'], + 'folder': ['folder', 'refold'], + 'foldy': ['floyd', 'foldy'], + 'fole': ['floe', 'fole'], + 'folia': ['filao', 'folia'], + 'foliar': ['floria', 'foliar'], + 'foliature': ['foliature', 'toluifera'], + 'folletage': ['flageolet', 'folletage'], + 'fomenter': ['fomenter', 'refoment'], + 'fondle': ['enfold', 'folden', 'fondle'], + 'fondu': ['fondu', 'found'], + 'foo': ['foo', 'ofo'], + 'fool': ['fool', 'loof', 'olof'], + 'foolship': ['fishpool', 'foolship'], + 'footer': ['footer', 'refoot'], + 'foothot': ['foothot', 'hotfoot'], + 'footler': ['footler', 'rooflet'], + 'footpad': ['footpad', 'padfoot'], + 'footrail': ['farolito', 'footrail'], + 'foots': ['foots', 'sfoot', 'stoof'], + 'footsore': ['footsore', 'sorefoot'], + 'foppish': ['foppish', 'fopship'], + 'fopship': ['foppish', 'fopship'], + 'for': ['for', 'fro', 'orf'], + 'fora': ['faro', 'fora'], + 'foralite': ['floriate', 'foralite'], + 'foramen': ['foramen', 'foreman'], + 'forcemeat': ['aftercome', 'forcemeat'], + 'forcement': ['coferment', 'forcement'], + 'fore': ['fore', 'froe', 'ofer'], + 'forecast': ['cofaster', 'forecast'], + 'forecaster': ['forecaster', 'reforecast'], + 'forecover': ['forecover', 'overforce'], + 'foreday': ['feodary', 'foreday'], + 'forefit': ['forefit', 'forfeit'], + 'foregate': ['fogeater', 'foregate'], + 'foregirth': ['foregirth', 'foreright'], + 'forego': ['forego', 'goofer'], + 'forel': ['forel', 'rolfe'], + 'forelive': ['forelive', 'overfile'], + 'foreman': ['foramen', 'foreman'], + 'foremean': ['foremean', 'forename'], + 'forename': ['foremean', 'forename'], + 'forensal': ['farnesol', 'forensal'], + 'forensic': ['forensic', 'forinsec'], + 'forepart': ['forepart', 'prefator'], + 'foreright': ['foregirth', 'foreright'], + 'foresend': ['defensor', 'foresend'], + 'foresign': ['foresign', 'foresing'], + 'foresin': ['ferison', 'foresin'], + 'foresing': ['foresign', 'foresing'], + 'forest': ['forest', 'forset', 'foster'], + 'forestage': ['forestage', 'fosterage'], + 'forestal': ['astrofel', 'forestal'], + 'forestate': ['forestate', 'foretaste'], + 'forested': ['deforest', 'forested'], + 'forestem': ['forestem', 'fretsome'], + 'forester': ['forester', 'fosterer', 'reforest'], + 'forestine': ['firestone', 'forestine'], + 'foretaste': ['forestate', 'foretaste'], + 'foreutter': ['foreutter', 'outferret'], + 'forfeit': ['forefit', 'forfeit'], + 'forfeiter': ['forfeiter', 'reforfeit'], + 'forgeman': ['forgeman', 'formagen'], + 'forinsec': ['forensic', 'forinsec'], + 'forint': ['forint', 'fortin'], + 'forlet': ['floret', 'forlet', 'lofter', 'torfel'], + 'form': ['form', 'from'], + 'formagen': ['forgeman', 'formagen'], + 'formalin': ['formalin', 'informal', 'laniform'], + 'formally': ['formally', 'formylal'], + 'formed': ['deform', 'formed'], + 'former': ['former', 'reform'], + 'formica': ['aciform', 'formica'], + 'formicina': ['aciniform', 'formicina'], + 'formicoidea': ['aecidioform', 'formicoidea'], + 'formin': ['formin', 'inform'], + 'forminate': ['forminate', 'fremontia', 'taeniform'], + 'formulae': ['formulae', 'fumarole'], + 'formulaic': ['cauliform', 'formulaic', 'fumarolic'], + 'formulate': ['feulamort', 'formulate'], + 'formulator': ['formulator', 'torulaform'], + 'formylal': ['formally', 'formylal'], + 'fornical': ['florican', 'fornical'], + 'fornicated': ['deforciant', 'fornicated'], + 'forpit': ['forpit', 'profit'], + 'forritsome': ['forritsome', 'ostreiform'], + 'forrue': ['forrue', 'fourer', 'fourre', 'furore'], + 'forset': ['forest', 'forset', 'foster'], + 'forst': ['forst', 'frost'], + 'fort': ['fort', 'frot'], + 'forte': ['fetor', 'forte', 'ofter'], + 'forth': ['forth', 'froth'], + 'forthcome': ['forthcome', 'homecroft'], + 'forthy': ['forthy', 'frothy'], + 'forties': ['foister', 'forties'], + 'fortin': ['forint', 'fortin'], + 'forward': ['forward', 'froward'], + 'forwarder': ['forwarder', 'reforward'], + 'forwardly': ['forwardly', 'frowardly'], + 'forwardness': ['forwardness', 'frowardness'], + 'foster': ['forest', 'forset', 'foster'], + 'fosterage': ['forestage', 'fosterage'], + 'fosterer': ['forester', 'fosterer', 'reforest'], + 'fot': ['fot', 'oft'], + 'fou': ['fou', 'ouf'], + 'fouler': ['fouler', 'furole'], + 'found': ['fondu', 'found'], + 'foundationer': ['foundationer', 'refoundation'], + 'founder': ['founder', 'refound'], + 'foundling': ['foundling', 'unfolding'], + 'fourble': ['beflour', 'fourble'], + 'fourer': ['forrue', 'fourer', 'fourre', 'furore'], + 'fourre': ['forrue', 'fourer', 'fourre', 'furore'], + 'fowl': ['flow', 'fowl', 'wolf'], + 'fowler': ['flower', 'fowler', 'reflow', 'wolfer'], + 'fowlery': ['flowery', 'fowlery'], + 'fowling': ['flowing', 'fowling'], + 'fra': ['far', 'fra'], + 'frache': ['chafer', 'frache'], + 'frae': ['fare', 'fear', 'frae', 'rafe'], + 'fraghan': ['fraghan', 'harfang'], + 'fraid': ['fiard', 'fraid'], + 'fraik': ['fakir', 'fraik', 'kafir', 'rafik'], + 'frail': ['filar', 'flair', 'frail'], + 'fraiser': ['fraiser', 'frasier'], + 'fram': ['farm', 'fram'], + 'framable': ['farmable', 'framable'], + 'frame': ['frame', 'fream'], + 'framer': ['farmer', 'framer'], + 'framing': ['farming', 'framing'], + 'frangi': ['faring', 'frangi'], + 'frantic': ['frantic', 'infarct', 'infract'], + 'frase': ['farse', 'frase'], + 'frasier': ['fraiser', 'frasier'], + 'frat': ['frat', 'raft'], + 'fratcheous': ['fratcheous', 'housecraft'], + 'frater': ['frater', 'rafter'], + 'frayed': ['defray', 'frayed'], + 'freak': ['faker', 'freak'], + 'freaky': ['fakery', 'freaky'], + 'fream': ['frame', 'fream'], + 'freath': ['father', 'freath', 'hafter'], + 'freckle': ['flecker', 'freckle'], + 'free': ['feer', 'free', 'reef'], + 'freed': ['defer', 'freed'], + 'freeing': ['feering', 'feigner', 'freeing', 'reefing', 'refeign'], + 'freeman': ['enframe', 'freeman'], + 'freer': ['freer', 'refer'], + 'fregata': ['fregata', 'raftage'], + 'fregatae': ['afterage', 'fregatae'], + 'freight': ['fighter', 'freight', 'refight'], + 'freir': ['ferri', 'firer', 'freir', 'frier'], + 'freit': ['freit', 'refit'], + 'freity': ['ferity', 'freity'], + 'fremontia': ['forminate', 'fremontia', 'taeniform'], + 'frenetic': ['frenetic', 'infecter', 'reinfect'], + 'freshener': ['freshener', 'refreshen'], + 'fresnel': ['flenser', 'fresnel'], + 'fret': ['fret', 'reft', 'tref'], + 'fretful': ['fretful', 'truffle'], + 'fretsome': ['forestem', 'fretsome'], + 'frette': ['fetter', 'frette'], + 'freya': ['faery', 'freya'], + 'freyr': ['ferry', 'freyr', 'fryer'], + 'fried': ['fired', 'fried'], + 'friedelite': ['fiedlerite', 'friedelite'], + 'friend': ['finder', 'friend', 'redfin', 'refind'], + 'frier': ['ferri', 'firer', 'freir', 'frier'], + 'friesic': ['friesic', 'serific'], + 'friggle': ['fligger', 'friggle'], + 'frightener': ['frightener', 'refrighten'], + 'frigolabile': ['frigolabile', 'glorifiable'], + 'frike': ['frike', 'kefir'], + 'frim': ['firm', 'frim'], + 'fringe': ['finger', 'fringe'], + 'fringeflower': ['fingerflower', 'fringeflower'], + 'fringeless': ['fingerless', 'fringeless'], + 'fringelet': ['fingerlet', 'fringelet'], + 'frist': ['first', 'frist'], + 'frit': ['frit', 'rift'], + 'frith': ['firth', 'frith'], + 'friulian': ['friulian', 'unifilar'], + 'fro': ['for', 'fro', 'orf'], + 'froe': ['fore', 'froe', 'ofer'], + 'frogleg': ['flogger', 'frogleg'], + 'from': ['form', 'from'], + 'fronter': ['fronter', 'refront'], + 'frontonasal': ['frontonasal', 'nasofrontal'], + 'frontooccipital': ['frontooccipital', 'occipitofrontal'], + 'frontoorbital': ['frontoorbital', 'orbitofrontal'], + 'frontoparietal': ['frontoparietal', 'parietofrontal'], + 'frontotemporal': ['frontotemporal', 'temporofrontal'], + 'frontpiece': ['frontpiece', 'perfection'], + 'frost': ['forst', 'frost'], + 'frosted': ['defrost', 'frosted'], + 'frot': ['fort', 'frot'], + 'froth': ['forth', 'froth'], + 'frothy': ['forthy', 'frothy'], + 'froward': ['forward', 'froward'], + 'frowardly': ['forwardly', 'frowardly'], + 'frowardness': ['forwardness', 'frowardness'], + 'fruitage': ['figurate', 'fruitage'], + 'fruitless': ['fruitless', 'resistful'], + 'frush': ['frush', 'shurf'], + 'frustule': ['frustule', 'sulfuret'], + 'fruticulose': ['fruticulose', 'luctiferous'], + 'fryer': ['ferry', 'freyr', 'fryer'], + 'fucales': ['caseful', 'fucales'], + 'fucate': ['faucet', 'fucate'], + 'fuel': ['flue', 'fuel'], + 'fueler': ['ferule', 'fueler', 'refuel'], + 'fuerte': ['fuerte', 'refute'], + 'fuirena': ['fuirena', 'unafire'], + 'fulcrate': ['crateful', 'fulcrate'], + 'fulica': ['ficula', 'fulica'], + 'fulmar': ['armful', 'fulmar'], + 'fulminatory': ['fulminatory', 'unformality'], + 'fulminous': ['fulminous', 'sulfonium'], + 'fulwa': ['awful', 'fulwa'], + 'fumarole': ['formulae', 'fumarole'], + 'fumarolic': ['cauliform', 'formulaic', 'fumarolic'], + 'fumble': ['beflum', 'fumble'], + 'fumer': ['femur', 'fumer'], + 'fundable': ['fundable', 'unfabled'], + 'funder': ['funder', 'refund'], + 'funebrial': ['funebrial', 'unfriable'], + 'funeral': ['earnful', 'funeral'], + 'fungal': ['fungal', 'unflag'], + 'fungi': ['fingu', 'fungi'], + 'funori': ['funori', 'furoin'], + 'fur': ['fur', 'urf'], + 'fural': ['alfur', 'fural'], + 'furan': ['furan', 'unfar'], + 'furbish': ['burfish', 'furbish'], + 'furbisher': ['furbisher', 'refurbish'], + 'furcal': ['carful', 'furcal'], + 'furcate': ['facture', 'furcate'], + 'furler': ['furler', 'refurl'], + 'furnish': ['furnish', 'runfish'], + 'furnisher': ['furnisher', 'refurnish'], + 'furoin': ['funori', 'furoin'], + 'furole': ['fouler', 'furole'], + 'furore': ['forrue', 'fourer', 'fourre', 'furore'], + 'furstone': ['furstone', 'unforest'], + 'fusate': ['estufa', 'fusate'], + 'fusteric': ['fusteric', 'scutifer'], + 'fustian': ['faunist', 'fustian', 'infaust'], + 'gab': ['bag', 'gab'], + 'gabbler': ['gabbler', 'grabble'], + 'gabe': ['egba', 'gabe'], + 'gabelle': ['gabelle', 'gelable'], + 'gabelled': ['gabelled', 'geldable'], + 'gabi': ['agib', 'biga', 'gabi'], + 'gabion': ['bagnio', 'gabion', 'gobian'], + 'gabioned': ['badigeon', 'gabioned'], + 'gable': ['bagel', 'belga', 'gable', 'gleba'], + 'gablock': ['backlog', 'gablock'], + 'gaboon': ['abongo', 'gaboon'], + 'gad': ['dag', 'gad'], + 'gadaba': ['badaga', 'dagaba', 'gadaba'], + 'gadder': ['gadder', 'graded'], + 'gaddi': ['gaddi', 'gadid'], + 'gade': ['aged', 'egad', 'gade'], + 'gadger': ['dagger', 'gadger', 'ragged'], + 'gadget': ['gadget', 'tagged'], + 'gadid': ['gaddi', 'gadid'], + 'gadinine': ['gadinine', 'indigena'], + 'gadolinite': ['deligation', 'gadolinite', 'gelatinoid'], + 'gadroon': ['dragoon', 'gadroon'], + 'gadroonage': ['dragoonage', 'gadroonage'], + 'gaduin': ['anguid', 'gaduin'], + 'gael': ['gael', 'gale', 'geal'], + 'gaen': ['agen', 'gaen', 'gane', 'gean', 'gena'], + 'gaet': ['gaet', 'gate', 'geat', 'geta'], + 'gaetulan': ['angulate', 'gaetulan'], + 'gager': ['agger', 'gager', 'regga'], + 'gahnite': ['gahnite', 'heating'], + 'gahrwali': ['gahrwali', 'garhwali'], + 'gaiassa': ['assagai', 'gaiassa'], + 'gail': ['gail', 'gali', 'gila', 'glia'], + 'gain': ['gain', 'inga', 'naig', 'ngai'], + 'gaincall': ['gaincall', 'gallican'], + 'gaine': ['angie', 'gaine'], + 'gainer': ['arenig', 'earing', 'gainer', 'reagin', 'regain'], + 'gainless': ['gainless', 'glassine'], + 'gainly': ['gainly', 'laying'], + 'gainsayer': ['asynergia', 'gainsayer'], + 'gainset': ['easting', + 'gainset', + 'genista', + 'ingesta', + 'seating', + 'signate', + 'teasing'], + 'gainstrive': ['gainstrive', 'vinegarist'], + 'gainturn': ['gainturn', 'naturing'], + 'gaiter': ['gaiter', 'tairge', 'triage'], + 'gaize': ['gaize', 'ziega'], + 'gaj': ['gaj', 'jag'], + 'gal': ['gal', 'lag'], + 'gala': ['agal', 'agla', 'alga', 'gala'], + 'galactonic': ['cognatical', 'galactonic'], + 'galatae': ['galatae', 'galatea'], + 'galatea': ['galatae', 'galatea'], + 'gale': ['gael', 'gale', 'geal'], + 'galea': ['algae', 'galea'], + 'galee': ['aegle', 'eagle', 'galee'], + 'galei': ['agiel', 'agile', 'galei'], + 'galeid': ['algedi', 'galeid'], + 'galen': ['agnel', 'angel', 'angle', 'galen', 'genal', 'glean', 'lagen'], + 'galena': ['alnage', 'angela', 'galena', 'lagena'], + 'galenian': ['alangine', 'angelina', 'galenian'], + 'galenic': ['angelic', 'galenic'], + 'galenical': ['angelical', 'englacial', 'galenical'], + 'galenist': ['galenist', 'genitals', 'stealing'], + 'galenite': ['galenite', 'legatine'], + 'galeoid': ['galeoid', 'geoidal'], + 'galera': ['aglare', 'alegar', 'galera', 'laager'], + 'galet': ['aglet', 'galet'], + 'galewort': ['galewort', 'waterlog'], + 'galey': ['agley', 'galey'], + 'galga': ['galga', 'glaga'], + 'gali': ['gail', 'gali', 'gila', 'glia'], + 'galidia': ['agialid', 'galidia'], + 'galik': ['galik', 'glaik'], + 'galilean': ['galilean', 'gallinae'], + 'galiot': ['galiot', 'latigo'], + 'galla': ['algal', 'galla'], + 'gallate': ['gallate', 'tallage'], + 'gallein': ['gallein', 'galline', 'nigella'], + 'galleria': ['allergia', 'galleria'], + 'gallery': ['allergy', 'gallery', 'largely', 'regally'], + 'galli': ['galli', 'glial'], + 'gallican': ['gaincall', 'gallican'], + 'gallicole': ['collegial', 'gallicole'], + 'gallinae': ['galilean', 'gallinae'], + 'galline': ['gallein', 'galline', 'nigella'], + 'gallnut': ['gallnut', 'nutgall'], + 'galloper': ['galloper', 'regallop'], + 'gallotannate': ['gallotannate', 'tannogallate'], + 'gallotannic': ['gallotannic', 'tannogallic'], + 'gallstone': ['gallstone', 'stonegall'], + 'gallybagger': ['gallybagger', 'gallybeggar'], + 'gallybeggar': ['gallybagger', 'gallybeggar'], + 'galore': ['galore', 'gaoler'], + 'galtonia': ['galtonia', 'notalgia'], + 'galvanopsychic': ['galvanopsychic', 'psychogalvanic'], + 'galvanothermometer': ['galvanothermometer', 'thermogalvanometer'], + 'gam': ['gam', 'mag'], + 'gamaliel': ['gamaliel', 'melalgia'], + 'gamashes': ['gamashes', 'smashage'], + 'gamasid': ['gamasid', 'magadis'], + 'gambado': ['dagomba', 'gambado'], + 'gambier': ['gambier', 'imbarge'], + 'gambler': ['gambler', 'gambrel'], + 'gambrel': ['gambler', 'gambrel'], + 'game': ['egma', 'game', 'mage'], + 'gamely': ['gamely', 'gleamy', 'mygale'], + 'gamene': ['gamene', 'manege', 'menage'], + 'gamete': ['gamete', 'metage'], + 'gametogenic': ['gametogenic', 'gamogenetic', 'geomagnetic'], + 'gamic': ['gamic', 'magic'], + 'gamin': ['gamin', 'mangi'], + 'gaming': ['gaming', 'gigman'], + 'gamma': ['gamma', 'magma'], + 'gammer': ['gammer', 'gramme'], + 'gamogenetic': ['gametogenic', 'gamogenetic', 'geomagnetic'], + 'gamori': ['gamori', 'gomari', 'gromia'], + 'gan': ['gan', 'nag'], + 'ganam': ['amang', 'ganam', 'manga'], + 'ganch': ['chang', 'ganch'], + 'gander': ['danger', 'gander', 'garden', 'ranged'], + 'gandul': ['gandul', 'unglad'], + 'gane': ['agen', 'gaen', 'gane', 'gean', 'gena'], + 'gangan': ['gangan', 'nagnag'], + 'ganger': ['ganger', 'grange', 'nagger'], + 'ganging': ['ganging', 'nagging'], + 'gangism': ['gangism', 'gigsman'], + 'ganglioneuron': ['ganglioneuron', 'neuroganglion'], + 'gangly': ['gangly', 'naggly'], + 'ganguela': ['ganguela', 'language'], + 'gangway': ['gangway', 'waygang'], + 'ganister': ['astringe', 'ganister', 'gantries'], + 'ganoidal': ['diagonal', 'ganoidal', 'gonadial'], + 'ganoidean': ['ganoidean', 'indogaean'], + 'ganoidian': ['agoniadin', 'anangioid', 'ganoidian'], + 'ganosis': ['agnosis', 'ganosis'], + 'gansel': ['angles', 'gansel'], + 'gant': ['gant', 'gnat', 'tang'], + 'ganta': ['ganta', 'tanga'], + 'ganton': ['ganton', 'tongan'], + 'gantries': ['astringe', 'ganister', 'gantries'], + 'gantry': ['gantry', 'gyrant'], + 'ganymede': ['ganymede', 'megadyne'], + 'ganzie': ['agnize', 'ganzie'], + 'gaol': ['gaol', 'goal', 'gola', 'olga'], + 'gaoler': ['galore', 'gaoler'], + 'gaon': ['agon', 'ango', 'gaon', 'goan', 'gona'], + 'gaonic': ['agonic', 'angico', 'gaonic', 'goniac'], + 'gapa': ['gapa', 'paga'], + 'gape': ['gape', 'page', 'peag', 'pega'], + 'gaper': ['gaper', 'grape', 'pager', 'parge'], + 'gar': ['gar', 'gra', 'rag'], + 'gara': ['agar', 'agra', 'gara', 'raga'], + 'garamond': ['dragoman', 'garamond', 'ondagram'], + 'garance': ['carnage', 'cranage', 'garance'], + 'garb': ['brag', 'garb', 'grab'], + 'garbel': ['garbel', 'garble'], + 'garble': ['garbel', 'garble'], + 'garbless': ['bragless', 'garbless'], + 'garce': ['cager', 'garce', 'grace'], + 'garcinia': ['agaricin', 'garcinia'], + 'gardeen': ['derange', 'enraged', 'gardeen', 'gerenda', 'grandee', 'grenade'], + 'garden': ['danger', 'gander', 'garden', 'ranged'], + 'gardened': ['deranged', 'gardened'], + 'gardener': ['deranger', 'gardener'], + 'gardenful': ['dangerful', 'gardenful'], + 'gardenia': ['drainage', 'gardenia'], + 'gardenin': ['gardenin', 'grenadin'], + 'gardenless': ['dangerless', 'gardenless'], + 'gare': ['ager', 'agre', 'gare', 'gear', 'rage'], + 'gareh': ['gareh', 'gerah'], + 'garetta': ['garetta', 'rattage', 'regatta'], + 'garewaite': ['garewaite', 'waiterage'], + 'garfish': ['garfish', 'ragfish'], + 'garget': ['garget', 'tagger'], + 'gargety': ['gargety', 'raggety'], + 'gargle': ['gargle', 'gregal', 'lagger', 'raggle'], + 'garhwali': ['gahrwali', 'garhwali'], + 'garial': ['argali', 'garial'], + 'garle': ['argel', 'ergal', 'garle', 'glare', 'lager', 'large', 'regal'], + 'garment': ['garment', 'margent'], + 'garmenture': ['garmenture', 'reargument'], + 'garn': ['garn', 'gnar', 'rang'], + 'garnel': ['angler', 'arleng', 'garnel', 'largen', 'rangle', 'regnal'], + 'garner': ['garner', 'ranger'], + 'garnet': ['argent', 'garnet', 'garten', 'tanger'], + 'garneter': ['argenter', 'garneter'], + 'garnetiferous': ['argentiferous', 'garnetiferous'], + 'garnets': ['angster', 'garnets', 'nagster', 'strange'], + 'garnett': ['garnett', 'gnatter', 'gratten', 'tergant'], + 'garnice': ['anergic', 'garnice', 'garniec', 'geranic', 'grecian'], + 'garniec': ['anergic', 'garnice', 'garniec', 'geranic', 'grecian'], + 'garnish': ['garnish', 'rashing'], + 'garnished': ['degarnish', 'garnished'], + 'garnisher': ['garnisher', 'regarnish'], + 'garo': ['argo', 'garo', 'gora'], + 'garran': ['garran', 'ragnar'], + 'garret': ['garret', 'garter', 'grater', 'targer'], + 'garreted': ['garreted', 'gartered'], + 'garroter': ['garroter', 'regrator'], + 'garten': ['argent', 'garnet', 'garten', 'tanger'], + 'garter': ['garret', 'garter', 'grater', 'targer'], + 'gartered': ['garreted', 'gartered'], + 'gartering': ['gartering', 'regrating'], + 'garum': ['garum', 'murga'], + 'gary': ['gary', 'gray'], + 'gas': ['gas', 'sag'], + 'gasan': ['gasan', 'sanga'], + 'gash': ['gash', 'shag'], + 'gasless': ['gasless', 'glasses', 'sagless'], + 'gaslit': ['algist', 'gaslit'], + 'gasoliner': ['gasoliner', 'seignoral'], + 'gasper': ['gasper', 'sparge'], + 'gast': ['gast', 'stag'], + 'gaster': ['gaster', 'stager'], + 'gastrin': ['gastrin', 'staring'], + 'gastroenteritis': ['enterogastritis', 'gastroenteritis'], + 'gastroesophagostomy': ['esophagogastrostomy', 'gastroesophagostomy'], + 'gastrohepatic': ['gastrohepatic', 'hepatogastric'], + 'gastronomic': ['gastronomic', 'monogastric'], + 'gastropathic': ['gastropathic', 'graphostatic'], + 'gastrophrenic': ['gastrophrenic', 'nephrogastric', 'phrenogastric'], + 'gastrular': ['gastrular', 'stragular'], + 'gat': ['gat', 'tag'], + 'gate': ['gaet', 'gate', 'geat', 'geta'], + 'gateman': ['gateman', 'magenta', 'magnate', 'magneta'], + 'gater': ['gater', 'grate', 'great', 'greta', 'retag', 'targe'], + 'gateward': ['drawgate', 'gateward'], + 'gateway': ['gateway', 'getaway', 'waygate'], + 'gatherer': ['gatherer', 'regather'], + 'gator': ['argot', 'gator', 'gotra', 'groat'], + 'gatter': ['gatter', 'target'], + 'gaucho': ['gaucho', 'guacho'], + 'gaufer': ['agrufe', 'gaufer', 'gaufre'], + 'gauffer': ['gauffer', 'gauffre'], + 'gauffre': ['gauffer', 'gauffre'], + 'gaufre': ['agrufe', 'gaufer', 'gaufre'], + 'gaul': ['gaul', 'gula'], + 'gaulin': ['gaulin', 'lingua'], + 'gaulter': ['gaulter', 'tegular'], + 'gaum': ['gaum', 'muga'], + 'gaun': ['gaun', 'guan', 'guna', 'uang'], + 'gaunt': ['gaunt', 'tunga'], + 'gaur': ['gaur', 'guar', 'ruga'], + 'gaura': ['gaura', 'guara'], + 'gaurian': ['anguria', 'gaurian', 'guarani'], + 'gave': ['gave', 'vage', 'vega'], + 'gavyuti': ['gavyuti', 'vaguity'], + 'gaw': ['gaw', 'wag'], + 'gawn': ['gawn', 'gnaw', 'wang'], + 'gay': ['agy', 'gay'], + 'gaz': ['gaz', 'zag'], + 'gazel': ['gazel', 'glaze'], + 'gazer': ['gazer', 'graze'], + 'gazon': ['gazon', 'zogan'], + 'gazy': ['gazy', 'zyga'], + 'geal': ['gael', 'gale', 'geal'], + 'gean': ['agen', 'gaen', 'gane', 'gean', 'gena'], + 'gear': ['ager', 'agre', 'gare', 'gear', 'rage'], + 'geared': ['agreed', 'geared'], + 'gearless': ['eelgrass', 'gearless', 'rageless'], + 'gearman': ['gearman', 'manager'], + 'gearset': ['ergates', 'gearset', 'geaster'], + 'geaster': ['ergates', 'gearset', 'geaster'], + 'geat': ['gaet', 'gate', 'geat', 'geta'], + 'gebur': ['bugre', 'gebur'], + 'ged': ['deg', 'ged'], + 'gedder': ['dredge', 'gedder'], + 'geest': ['egest', 'geest', 'geste'], + 'gegger': ['gegger', 'gregge'], + 'geheimrat': ['geheimrat', 'hermitage'], + 'gein': ['gein', 'gien'], + 'geira': ['geira', 'regia'], + 'geison': ['geison', 'isogen'], + 'geissospermine': ['geissospermine', 'spermiogenesis'], + 'gel': ['gel', 'leg'], + 'gelable': ['gabelle', 'gelable'], + 'gelasian': ['anglaise', 'gelasian'], + 'gelastic': ['gelastic', 'gestical'], + 'gelatin': ['atingle', 'gelatin', 'genital', 'langite', 'telinga'], + 'gelatinate': ['gelatinate', 'nagatelite'], + 'gelatined': ['delignate', 'gelatined'], + 'gelatinizer': ['gelatinizer', 'integralize'], + 'gelatinoid': ['deligation', 'gadolinite', 'gelatinoid'], + 'gelation': ['gelation', 'lagonite', 'legation'], + 'gelatose': ['gelatose', 'segolate'], + 'geldable': ['gabelled', 'geldable'], + 'gelder': ['gelder', 'ledger', 'redleg'], + 'gelding': ['gelding', 'ledging'], + 'gelid': ['gelid', 'glide'], + 'gelidness': ['gelidness', 'glideness'], + 'gelosin': ['gelosin', 'lignose'], + 'gem': ['gem', 'meg'], + 'gemara': ['gemara', 'ramage'], + 'gemaric': ['gemaric', 'grimace', 'megaric'], + 'gemarist': ['gemarist', 'magister', 'sterigma'], + 'gematria': ['gematria', 'maritage'], + 'gemul': ['gemul', 'glume'], + 'gena': ['agen', 'gaen', 'gane', 'gean', 'gena'], + 'genal': ['agnel', 'angel', 'angle', 'galen', 'genal', 'glean', 'lagen'], + 'genarch': ['changer', 'genarch'], + 'gendarme': ['edgerman', 'gendarme'], + 'genear': ['egeran', 'enrage', 'ergane', 'genear', 'genera'], + 'geneat': ['geneat', 'negate', 'tegean'], + 'genera': ['egeran', 'enrage', 'ergane', 'genear', 'genera'], + 'generable': ['generable', 'greenable'], + 'general': ['enlarge', 'general', 'gleaner'], + 'generalist': ['easterling', 'generalist'], + 'generall': ['allergen', 'generall'], + 'generation': ['generation', 'renegation'], + 'generic': ['energic', 'generic'], + 'generical': ['energical', 'generical'], + 'genesiac': ['agenesic', 'genesiac'], + 'genesial': ['ensilage', 'genesial', 'signalee'], + 'genetical': ['clientage', 'genetical'], + 'genetta': ['genetta', 'tentage'], + 'geneura': ['geneura', 'uneager'], + 'geneva': ['avenge', 'geneva', 'vangee'], + 'genial': ['algine', 'genial', 'linage'], + 'genicular': ['genicular', 'neuralgic'], + 'genie': ['eigne', 'genie'], + 'genion': ['genion', 'inogen'], + 'genipa': ['genipa', 'piegan'], + 'genista': ['easting', + 'gainset', + 'genista', + 'ingesta', + 'seating', + 'signate', + 'teasing'], + 'genistein': ['genistein', 'gentisein'], + 'genital': ['atingle', 'gelatin', 'genital', 'langite', 'telinga'], + 'genitals': ['galenist', 'genitals', 'stealing'], + 'genitival': ['genitival', 'vigilante'], + 'genitocrural': ['crurogenital', 'genitocrural'], + 'genitor': ['ergotin', 'genitor', 'negrito', 'ogtiern', 'trigone'], + 'genitorial': ['genitorial', 'religation'], + 'genitory': ['genitory', 'ortygine'], + 'genitourinary': ['genitourinary', 'urinogenitary'], + 'geniture': ['geniture', 'guerinet'], + 'genizero': ['genizero', 'negroize'], + 'genoa': ['agone', 'genoa'], + 'genoblastic': ['blastogenic', 'genoblastic'], + 'genocidal': ['algedonic', 'genocidal'], + 'genom': ['genom', 'gnome'], + 'genotypical': ['genotypical', 'ptyalogenic'], + 'genre': ['genre', 'green', 'neger', 'reneg'], + 'genro': ['ergon', 'genro', 'goner', 'negro'], + 'gent': ['gent', 'teng'], + 'gentes': ['gentes', 'gesten'], + 'genthite': ['genthite', 'teething'], + 'gentian': ['antigen', 'gentian'], + 'gentianic': ['antigenic', 'gentianic'], + 'gentisein': ['genistein', 'gentisein'], + 'gentle': ['gentle', 'telegn'], + 'gentrice': ['erecting', 'gentrice'], + 'genua': ['augen', 'genua'], + 'genual': ['genual', 'leguan'], + 'genuine': ['genuine', 'ingenue'], + 'genus': ['genus', 'negus'], + 'geo': ['ego', 'geo'], + 'geocentric': ['ectrogenic', 'egocentric', 'geocentric'], + 'geocratic': ['categoric', 'geocratic'], + 'geocronite': ['erotogenic', 'geocronite', 'orogenetic'], + 'geodal': ['algedo', 'geodal'], + 'geode': ['geode', 'ogeed'], + 'geodiatropism': ['diageotropism', 'geodiatropism'], + 'geoduck': ['geoduck', 'goeduck'], + 'geohydrology': ['geohydrology', 'hydrogeology'], + 'geoid': ['diego', 'dogie', 'geoid'], + 'geoidal': ['galeoid', 'geoidal'], + 'geoisotherm': ['geoisotherm', 'isogeotherm'], + 'geomagnetic': ['gametogenic', 'gamogenetic', 'geomagnetic'], + 'geomant': ['geomant', 'magneto', 'megaton', 'montage'], + 'geomantic': ['atmogenic', 'geomantic'], + 'geometrical': ['geometrical', 'glaciometer'], + 'geometrina': ['angiometer', 'ergotamine', 'geometrina'], + 'geon': ['geon', 'gone'], + 'geonim': ['geonim', 'imogen'], + 'georama': ['georama', 'roamage'], + 'geotectonic': ['geotectonic', 'tocogenetic'], + 'geotic': ['geotic', 'goetic'], + 'geotical': ['ectoglia', 'geotical', 'goetical'], + 'geotonic': ['geotonic', 'otogenic'], + 'geoty': ['geoty', 'goety'], + 'ger': ['erg', 'ger', 'reg'], + 'gerah': ['gareh', 'gerah'], + 'geraldine': ['engrailed', 'geraldine'], + 'geranial': ['algerian', 'geranial', 'regalian'], + 'geranic': ['anergic', 'garnice', 'garniec', 'geranic', 'grecian'], + 'geraniol': ['geraniol', 'regional'], + 'geranomorph': ['geranomorph', 'monographer', 'nomographer'], + 'geranyl': ['angerly', 'geranyl'], + 'gerard': ['darger', 'gerard', 'grader', 'redrag', 'regard'], + 'gerastian': ['agrestian', 'gerastian', 'stangeria'], + 'geraty': ['geraty', 'gyrate'], + 'gerb': ['berg', 'gerb'], + 'gerbe': ['gerbe', 'grebe', 'rebeg'], + 'gerbera': ['bargeer', 'gerbera'], + 'gerenda': ['derange', 'enraged', 'gardeen', 'gerenda', 'grandee', 'grenade'], + 'gerendum': ['gerendum', 'unmerged'], + 'gerent': ['gerent', 'regent'], + 'gerenuk': ['gerenuk', 'greenuk'], + 'gerim': ['gerim', 'grime'], + 'gerip': ['gerip', 'gripe'], + 'german': ['engram', 'german', 'manger'], + 'germania': ['germania', 'megarian'], + 'germanics': ['germanics', 'screaming'], + 'germanification': ['germanification', 'remagnification'], + 'germanify': ['germanify', 'remagnify'], + 'germanious': ['germanious', 'gramineous', 'marigenous'], + 'germanist': ['germanist', 'streaming'], + 'germanite': ['germanite', 'germinate', 'gramenite', 'mangerite'], + 'germanly': ['germanly', 'germanyl'], + 'germanyl': ['germanly', 'germanyl'], + 'germinal': ['germinal', 'maligner', 'malinger'], + 'germinant': ['germinant', 'minargent'], + 'germinate': ['germanite', 'germinate', 'gramenite', 'mangerite'], + 'germon': ['germon', 'monger', 'morgen'], + 'geronomite': ['geronomite', 'goniometer'], + 'geront': ['geront', 'tonger'], + 'gerontal': ['argentol', 'gerontal'], + 'gerontes': ['estrogen', 'gerontes'], + 'gerontic': ['gerontic', 'negrotic'], + 'gerontism': ['gerontism', 'monergist'], + 'gerres': ['gerres', 'serger'], + 'gersum': ['gersum', 'mergus'], + 'gerund': ['dunger', 'gerund', 'greund', 'nudger'], + 'gerundive': ['gerundive', 'ungrieved'], + 'gerusia': ['ergusia', 'gerusia', 'sarigue'], + 'gervas': ['gervas', 'graves'], + 'gervase': ['gervase', 'greaves', 'servage'], + 'ges': ['ges', 'seg'], + 'gesan': ['agnes', 'gesan'], + 'gesith': ['gesith', 'steigh'], + 'gesning': ['gesning', 'ginseng'], + 'gest': ['gest', 'steg'], + 'gestapo': ['gestapo', 'postage'], + 'gestate': ['gestate', 'tagetes'], + 'geste': ['egest', 'geest', 'geste'], + 'gesten': ['gentes', 'gesten'], + 'gestical': ['gelastic', 'gestical'], + 'gesticular': ['gesticular', 'scutigeral'], + 'gesture': ['gesture', 'guester'], + 'get': ['get', 'teg'], + 'geta': ['gaet', 'gate', 'geat', 'geta'], + 'getaway': ['gateway', 'getaway', 'waygate'], + 'gettable': ['begettal', 'gettable'], + 'getup': ['getup', 'upget'], + 'geyerite': ['geyerite', 'tigereye'], + 'ghaist': ['ghaist', 'tagish'], + 'ghent': ['ghent', 'thegn'], + 'ghosty': ['ghosty', 'hogsty'], + 'ghoul': ['ghoul', 'lough'], + 'giansar': ['giansar', 'sarangi'], + 'giant': ['giant', 'tangi', 'tiang'], + 'gib': ['big', 'gib'], + 'gibbon': ['gibbon', 'gobbin'], + 'gibel': ['bilge', 'gibel'], + 'gibing': ['biggin', 'gibing'], + 'gid': ['dig', 'gid'], + 'gideonite': ['diogenite', 'gideonite'], + 'gien': ['gein', 'gien'], + 'gienah': ['gienah', 'hangie'], + 'gif': ['fig', 'gif'], + 'gifola': ['filago', 'gifola'], + 'gifted': ['fidget', 'gifted'], + 'gigman': ['gaming', 'gigman'], + 'gigsman': ['gangism', 'gigsman'], + 'gila': ['gail', 'gali', 'gila', 'glia'], + 'gilaki': ['gilaki', 'giliak'], + 'gilbertese': ['gilbertese', 'selbergite'], + 'gilden': ['dingle', 'elding', 'engild', 'gilden'], + 'gilder': ['gilder', 'girdle', 'glider', 'regild', 'ridgel'], + 'gilding': ['gilding', 'gliding'], + 'gileno': ['eloign', 'gileno', 'legion'], + 'giles': ['giles', 'gilse'], + 'giliak': ['gilaki', 'giliak'], + 'giller': ['giller', 'grille', 'regill'], + 'gilo': ['gilo', 'goli'], + 'gilpy': ['gilpy', 'pigly'], + 'gilse': ['giles', 'gilse'], + 'gim': ['gim', 'mig'], + 'gimel': ['gimel', 'glime'], + 'gimmer': ['gimmer', 'grimme', 'megrim'], + 'gimper': ['gimper', 'impreg'], + 'gin': ['gin', 'ing', 'nig'], + 'ginger': ['ginger', 'nigger'], + 'gingery': ['gingery', 'niggery'], + 'ginglymodi': ['ginglymodi', 'ginglymoid'], + 'ginglymoid': ['ginglymodi', 'ginglymoid'], + 'gink': ['gink', 'king'], + 'ginned': ['ending', 'ginned'], + 'ginner': ['enring', 'ginner'], + 'ginney': ['ginney', 'nignye'], + 'ginseng': ['gesning', 'ginseng'], + 'ginward': ['drawing', 'ginward', 'warding'], + 'gio': ['gio', 'goi'], + 'giornata': ['giornata', 'gratiano'], + 'giornatate': ['giornatate', 'tetragonia'], + 'gip': ['gip', 'pig'], + 'gipper': ['gipper', 'grippe'], + 'girandole': ['girandole', 'negroidal'], + 'girasole': ['girasole', 'seraglio'], + 'girba': ['bragi', 'girba'], + 'gird': ['gird', 'grid'], + 'girder': ['girder', 'ridger'], + 'girding': ['girding', 'ridging'], + 'girdingly': ['girdingly', 'ridgingly'], + 'girdle': ['gilder', 'girdle', 'glider', 'regild', 'ridgel'], + 'girdler': ['dirgler', 'girdler'], + 'girdling': ['girdling', 'ridgling'], + 'girling': ['girling', 'rigling'], + 'girn': ['girn', 'grin', 'ring'], + 'girny': ['girny', 'ringy'], + 'girondin': ['girondin', 'nonrigid'], + 'girsle': ['girsle', 'gisler', 'glires', 'grilse'], + 'girt': ['girt', 'grit', 'trig'], + 'girth': ['girth', 'grith', 'right'], + 'gish': ['gish', 'sigh'], + 'gisla': ['gisla', 'ligas', 'sigla'], + 'gisler': ['girsle', 'gisler', 'glires', 'grilse'], + 'git': ['git', 'tig'], + 'gitalin': ['gitalin', 'tailing'], + 'gith': ['gith', 'thig'], + 'gitksan': ['gitksan', 'skating', 'takings'], + 'gittern': ['gittern', 'gritten', 'retting'], + 'giustina': ['giustina', 'ignatius'], + 'giver': ['giver', 'vergi'], + 'glaceing': ['cageling', 'glaceing'], + 'glacier': ['glacier', 'gracile'], + 'glaciometer': ['geometrical', 'glaciometer'], + 'gladdener': ['gladdener', 'glandered', 'regladden'], + 'glaga': ['galga', 'glaga'], + 'glaik': ['galik', 'glaik'], + 'glaiket': ['glaiket', 'taglike'], + 'glair': ['argil', 'glair', 'grail'], + 'glaireous': ['aligerous', 'glaireous'], + 'glaister': ['glaister', 'regalist'], + 'glaive': ['glaive', 'vagile'], + 'glance': ['cangle', 'glance'], + 'glancer': ['cangler', 'glancer', 'reclang'], + 'glancingly': ['clangingly', 'glancingly'], + 'glandered': ['gladdener', 'glandered', 'regladden'], + 'glans': ['glans', 'slang'], + 'glare': ['argel', 'ergal', 'garle', 'glare', 'lager', 'large', 'regal'], + 'glariness': ['glariness', 'grainless'], + 'glary': ['glary', 'gyral'], + 'glasser': ['glasser', 'largess'], + 'glasses': ['gasless', 'glasses', 'sagless'], + 'glassie': ['algesis', 'glassie'], + 'glassine': ['gainless', 'glassine'], + 'glaucin': ['glaucin', 'glucina'], + 'glaucine': ['cuailnge', 'glaucine'], + 'glaum': ['algum', 'almug', 'glaum', 'gluma', 'mulga'], + 'glaur': ['glaur', 'gular'], + 'glaury': ['glaury', 'raguly'], + 'glaver': ['glaver', 'gravel'], + 'glaze': ['gazel', 'glaze'], + 'glazy': ['glazy', 'zygal'], + 'gleamy': ['gamely', 'gleamy', 'mygale'], + 'glean': ['agnel', 'angel', 'angle', 'galen', 'genal', 'glean', 'lagen'], + 'gleaner': ['enlarge', 'general', 'gleaner'], + 'gleary': ['argyle', 'gleary'], + 'gleba': ['bagel', 'belga', 'gable', 'gleba'], + 'glebal': ['begall', 'glebal'], + 'glede': ['glede', 'gleed', 'ledge'], + 'gledy': ['gledy', 'ledgy'], + 'gleed': ['glede', 'gleed', 'ledge'], + 'gleeman': ['gleeman', 'melange'], + 'glia': ['gail', 'gali', 'gila', 'glia'], + 'gliadin': ['dialing', 'gliadin'], + 'glial': ['galli', 'glial'], + 'glibness': ['beslings', 'blessing', 'glibness'], + 'glidder': ['glidder', 'griddle'], + 'glide': ['gelid', 'glide'], + 'glideness': ['gelidness', 'glideness'], + 'glider': ['gilder', 'girdle', 'glider', 'regild', 'ridgel'], + 'gliding': ['gilding', 'gliding'], + 'glime': ['gimel', 'glime'], + 'glink': ['glink', 'kling'], + 'glires': ['girsle', 'gisler', 'glires', 'grilse'], + 'glisten': ['glisten', 'singlet'], + 'glister': ['glister', 'gristle'], + 'glitnir': ['glitnir', 'ritling'], + 'glitter': ['glitter', 'grittle'], + 'gloater': ['argolet', 'gloater', 'legator'], + 'gloating': ['gloating', 'goatling'], + 'globate': ['boltage', 'globate'], + 'globe': ['bogle', 'globe'], + 'globin': ['globin', 'goblin', 'lobing'], + 'gloea': ['gloea', 'legoa'], + 'glome': ['glome', 'golem', 'molge'], + 'glomerate': ['algometer', 'glomerate'], + 'glore': ['glore', 'ogler'], + 'gloria': ['gloria', 'larigo', 'logria'], + 'gloriana': ['argolian', 'gloriana'], + 'gloriette': ['gloriette', 'rigolette'], + 'glorifiable': ['frigolabile', 'glorifiable'], + 'glossed': ['dogless', 'glossed', 'godless'], + 'glosser': ['glosser', 'regloss'], + 'glossitic': ['glossitic', 'logistics'], + 'glossohyal': ['glossohyal', 'hyoglossal'], + 'glossolabial': ['glossolabial', 'labioglossal'], + 'glossolabiolaryngeal': ['glossolabiolaryngeal', 'labioglossolaryngeal'], + 'glossolabiopharyngeal': ['glossolabiopharyngeal', 'labioglossopharyngeal'], + 'glottid': ['glottid', 'goldtit'], + 'glover': ['glover', 'grovel'], + 'gloveress': ['gloveress', 'groveless'], + 'glow': ['glow', 'gowl'], + 'glower': ['glower', 'reglow'], + 'gloy': ['gloy', 'logy'], + 'glucemia': ['glucemia', 'mucilage'], + 'glucina': ['glaucin', 'glucina'], + 'glucine': ['glucine', 'lucigen'], + 'glucinum': ['cingulum', 'glucinum'], + 'glucosane': ['consulage', 'glucosane'], + 'glue': ['glue', 'gule', 'luge'], + 'gluer': ['gluer', 'gruel', 'luger'], + 'gluma': ['algum', 'almug', 'glaum', 'gluma', 'mulga'], + 'glume': ['gemul', 'glume'], + 'glumose': ['glumose', 'lugsome'], + 'gluten': ['englut', 'gluten', 'ungelt'], + 'glutin': ['glutin', 'luting', 'ungilt'], + 'glutter': ['glutter', 'guttler'], + 'glycerate': ['electragy', 'glycerate'], + 'glycerinize': ['glycerinize', 'glycerizine'], + 'glycerizine': ['glycerinize', 'glycerizine'], + 'glycerophosphate': ['glycerophosphate', 'phosphoglycerate'], + 'glycocin': ['glycocin', 'glyconic'], + 'glyconic': ['glycocin', 'glyconic'], + 'glycosine': ['glycosine', 'lysogenic'], + 'glycosuria': ['glycosuria', 'graciously'], + 'gnaeus': ['gnaeus', 'unsage'], + 'gnaphalium': ['gnaphalium', 'phalangium'], + 'gnar': ['garn', 'gnar', 'rang'], + 'gnarled': ['dangler', 'gnarled'], + 'gnash': ['gnash', 'shang'], + 'gnat': ['gant', 'gnat', 'tang'], + 'gnatho': ['gnatho', 'thonga'], + 'gnathotheca': ['chaetognath', 'gnathotheca'], + 'gnatling': ['gnatling', 'tangling'], + 'gnatter': ['garnett', 'gnatter', 'gratten', 'tergant'], + 'gnaw': ['gawn', 'gnaw', 'wang'], + 'gnetum': ['gnetum', 'nutmeg'], + 'gnome': ['genom', 'gnome'], + 'gnomic': ['coming', 'gnomic'], + 'gnomist': ['gnomist', 'mosting'], + 'gnomonic': ['gnomonic', 'oncoming'], + 'gnomonical': ['cognominal', 'gnomonical'], + 'gnostic': ['costing', 'gnostic'], + 'gnostical': ['gnostical', 'nostalgic'], + 'gnu': ['gnu', 'gun'], + 'go': ['go', 'og'], + 'goa': ['ago', 'goa'], + 'goad': ['dago', 'goad'], + 'goal': ['gaol', 'goal', 'gola', 'olga'], + 'goan': ['agon', 'ango', 'gaon', 'goan', 'gona'], + 'goat': ['goat', 'toag', 'toga'], + 'goatee': ['goatee', 'goetae'], + 'goatlike': ['goatlike', 'togalike'], + 'goatling': ['gloating', 'goatling'], + 'goatly': ['goatly', 'otalgy'], + 'gob': ['bog', 'gob'], + 'goban': ['bogan', 'goban'], + 'gobbe': ['bebog', 'begob', 'gobbe'], + 'gobbin': ['gibbon', 'gobbin'], + 'gobelin': ['gobelin', 'gobline', 'ignoble', 'inglobe'], + 'gobian': ['bagnio', 'gabion', 'gobian'], + 'goblet': ['boglet', 'goblet'], + 'goblin': ['globin', 'goblin', 'lobing'], + 'gobline': ['gobelin', 'gobline', 'ignoble', 'inglobe'], + 'goblinry': ['boringly', 'goblinry'], + 'gobo': ['bogo', 'gobo'], + 'goby': ['bogy', 'bygo', 'goby'], + 'goclenian': ['congenial', 'goclenian'], + 'god': ['dog', 'god'], + 'goddam': ['goddam', 'mogdad'], + 'gode': ['doeg', 'doge', 'gode'], + 'godhead': ['doghead', 'godhead'], + 'godhood': ['doghood', 'godhood'], + 'godless': ['dogless', 'glossed', 'godless'], + 'godlike': ['doglike', 'godlike'], + 'godling': ['godling', 'lodging'], + 'godly': ['dogly', 'godly', 'goldy'], + 'godship': ['dogship', 'godship'], + 'godwinian': ['downingia', 'godwinian'], + 'goeduck': ['geoduck', 'goeduck'], + 'goel': ['egol', 'goel', 'loge', 'ogle', 'oleg'], + 'goer': ['goer', 'gore', 'ogre'], + 'goes': ['goes', 'sego'], + 'goetae': ['goatee', 'goetae'], + 'goetic': ['geotic', 'goetic'], + 'goetical': ['ectoglia', 'geotical', 'goetical'], + 'goety': ['geoty', 'goety'], + 'goglet': ['goglet', 'toggel', 'toggle'], + 'goi': ['gio', 'goi'], + 'goidel': ['goidel', 'goldie'], + 'goitral': ['goitral', 'larigot', 'ligator'], + 'gol': ['gol', 'log'], + 'gola': ['gaol', 'goal', 'gola', 'olga'], + 'golden': ['engold', 'golden'], + 'goldenmouth': ['goldenmouth', 'longmouthed'], + 'golder': ['golder', 'lodger'], + 'goldie': ['goidel', 'goldie'], + 'goldtit': ['glottid', 'goldtit'], + 'goldy': ['dogly', 'godly', 'goldy'], + 'golee': ['eloge', 'golee'], + 'golem': ['glome', 'golem', 'molge'], + 'golf': ['flog', 'golf'], + 'golfer': ['golfer', 'reflog'], + 'goli': ['gilo', 'goli'], + 'goliard': ['argolid', 'goliard'], + 'golo': ['golo', 'gool'], + 'goma': ['goma', 'ogam'], + 'gomari': ['gamori', 'gomari', 'gromia'], + 'gomart': ['gomart', 'margot'], + 'gomphrena': ['gomphrena', 'nephogram'], + 'gon': ['gon', 'nog'], + 'gona': ['agon', 'ango', 'gaon', 'goan', 'gona'], + 'gonad': ['donga', 'gonad'], + 'gonadial': ['diagonal', 'ganoidal', 'gonadial'], + 'gonal': ['along', 'gonal', 'lango', 'longa', 'nogal'], + 'gond': ['dong', 'gond'], + 'gondi': ['dingo', 'doing', 'gondi', 'gonid'], + 'gondola': ['dongola', 'gondola'], + 'gondolier': ['gondolier', 'negroloid'], + 'gone': ['geon', 'gone'], + 'goner': ['ergon', 'genro', 'goner', 'negro'], + 'gonesome': ['gonesome', 'osmogene'], + 'gongoresque': ['gongoresque', 'gorgonesque'], + 'gonia': ['gonia', 'ngaio', 'nogai'], + 'goniac': ['agonic', 'angico', 'gaonic', 'goniac'], + 'goniale': ['goniale', 'noilage'], + 'goniaster': ['goniaster', 'orangeist'], + 'goniatitid': ['digitation', 'goniatitid'], + 'gonid': ['dingo', 'doing', 'gondi', 'gonid'], + 'gonidia': ['angioid', 'gonidia'], + 'gonidiferous': ['gonidiferous', 'indigoferous'], + 'goniometer': ['geronomite', 'goniometer'], + 'gonomery': ['gonomery', 'merogony'], + 'gonosome': ['gonosome', 'mongoose'], + 'gonyocele': ['coelogyne', 'gonyocele'], + 'gonys': ['gonys', 'songy'], + 'goober': ['booger', 'goober'], + 'goodyear': ['goodyear', 'goodyera'], + 'goodyera': ['goodyear', 'goodyera'], + 'goof': ['fogo', 'goof'], + 'goofer': ['forego', 'goofer'], + 'gool': ['golo', 'gool'], + 'gools': ['gools', 'logos'], + 'goop': ['goop', 'pogo'], + 'gor': ['gor', 'rog'], + 'gora': ['argo', 'garo', 'gora'], + 'goral': ['algor', 'argol', 'goral', 'largo'], + 'goran': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'gorb': ['borg', 'brog', 'gorb'], + 'gorbal': ['brolga', 'gorbal'], + 'gorce': ['corge', 'gorce'], + 'gordian': ['gordian', 'idorgan', 'roading'], + 'gordon': ['drongo', 'gordon'], + 'gordonia': ['gordonia', 'organoid', 'rigadoon'], + 'gore': ['goer', 'gore', 'ogre'], + 'gorer': ['gorer', 'roger'], + 'gorge': ['gorge', 'grego'], + 'gorged': ['dogger', 'gorged'], + 'gorger': ['gorger', 'gregor'], + 'gorgerin': ['gorgerin', 'ringgoer'], + 'gorgonesque': ['gongoresque', 'gorgonesque'], + 'goric': ['corgi', 'goric', 'orgic'], + 'goring': ['goring', 'gringo'], + 'gorse': ['gorse', 'soger'], + 'gortonian': ['gortonian', 'organotin'], + 'gory': ['gory', 'gyro', 'orgy'], + 'gos': ['gos', 'sog'], + 'gosain': ['gosain', 'isagon', 'sagoin'], + 'gosh': ['gosh', 'shog'], + 'gospel': ['gospel', 'spogel'], + 'gossipry': ['gossipry', 'gryposis'], + 'got': ['got', 'tog'], + 'gotra': ['argot', 'gator', 'gotra', 'groat'], + 'goup': ['goup', 'ogpu', 'upgo'], + 'gourde': ['drogue', 'gourde'], + 'gout': ['gout', 'toug'], + 'goutish': ['goutish', 'outsigh'], + 'gowan': ['gowan', 'wagon', 'wonga'], + 'gowdnie': ['gowdnie', 'widgeon'], + 'gowl': ['glow', 'gowl'], + 'gown': ['gown', 'wong'], + 'goyin': ['goyin', 'yogin'], + 'gra': ['gar', 'gra', 'rag'], + 'grab': ['brag', 'garb', 'grab'], + 'grabble': ['gabbler', 'grabble'], + 'graben': ['banger', 'engarb', 'graben'], + 'grace': ['cager', 'garce', 'grace'], + 'gracile': ['glacier', 'gracile'], + 'graciously': ['glycosuria', 'graciously'], + 'grad': ['darg', 'drag', 'grad'], + 'gradation': ['gradation', 'indagator', 'tanagroid'], + 'grade': ['edgar', 'grade'], + 'graded': ['gadder', 'graded'], + 'grader': ['darger', 'gerard', 'grader', 'redrag', 'regard'], + 'gradient': ['gradient', 'treading'], + 'gradienter': ['gradienter', 'intergrade'], + 'gradientia': ['gradientia', 'grantiidae'], + 'gradin': ['daring', 'dingar', 'gradin'], + 'gradine': ['degrain', 'deraign', 'deringa', 'gradine', 'grained', 'reading'], + 'grading': ['grading', 'niggard'], + 'graeae': ['aerage', 'graeae'], + 'graeme': ['graeme', 'meager', 'meagre'], + 'grafter': ['grafter', 'regraft'], + 'graian': ['graian', 'nagari'], + 'grail': ['argil', 'glair', 'grail'], + 'grailer': ['grailer', 'reglair'], + 'grain': ['agrin', 'grain'], + 'grained': ['degrain', 'deraign', 'deringa', 'gradine', 'grained', 'reading'], + 'grainer': ['earring', 'grainer'], + 'grainless': ['glariness', 'grainless'], + 'graith': ['aright', 'graith'], + 'grallina': ['grallina', 'granilla'], + 'gralline': ['allergin', 'gralline'], + 'grame': ['grame', 'marge', 'regma'], + 'gramenite': ['germanite', 'germinate', 'gramenite', 'mangerite'], + 'gramineous': ['germanious', 'gramineous', 'marigenous'], + 'graminiform': ['graminiform', 'marginiform'], + 'graminous': ['graminous', 'ignoramus'], + 'gramme': ['gammer', 'gramme'], + 'gramophonic': ['gramophonic', 'monographic', 'nomographic', 'phonogramic'], + 'gramophonical': ['gramophonical', 'monographical', 'nomographical'], + 'gramophonically': ['gramophonically', + 'monographically', + 'nomographically', + 'phonogramically'], + 'gramophonist': ['gramophonist', 'monographist'], + 'granadine': ['granadine', 'grenadian'], + 'granate': ['argante', 'granate', 'tanager'], + 'granatum': ['armgaunt', 'granatum'], + 'grand': ['drang', 'grand'], + 'grandam': ['dragman', 'grandam', 'grandma'], + 'grandee': ['derange', 'enraged', 'gardeen', 'gerenda', 'grandee', 'grenade'], + 'grandeeism': ['grandeeism', 'renegadism'], + 'grandeur': ['grandeur', 'unregard'], + 'grandeval': ['grandeval', 'landgrave'], + 'grandiose': ['grandiose', 'sargonide'], + 'grandma': ['dragman', 'grandam', 'grandma'], + 'grandparental': ['grandparental', 'grandpaternal'], + 'grandpaternal': ['grandparental', 'grandpaternal'], + 'grane': ['anger', 'areng', 'grane', 'range'], + 'grange': ['ganger', 'grange', 'nagger'], + 'grangousier': ['grangousier', 'gregarinous'], + 'granilla': ['grallina', 'granilla'], + 'granite': ['angrite', 'granite', 'ingrate', 'tangier', 'tearing', 'tigrean'], + 'granivore': ['granivore', 'overgrain'], + 'grano': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'granophyre': ['granophyre', 'renography'], + 'grantee': ['grantee', 'greaten', 'reagent', 'rentage'], + 'granter': ['granter', 'regrant'], + 'granth': ['granth', 'thrang'], + 'grantiidae': ['gradientia', 'grantiidae'], + 'granula': ['angular', 'granula'], + 'granule': ['granule', 'unlarge', 'unregal'], + 'granulite': ['granulite', 'traguline'], + 'grape': ['gaper', 'grape', 'pager', 'parge'], + 'graperoot': ['graperoot', 'prorogate'], + 'graphical': ['algraphic', 'graphical'], + 'graphically': ['calligraphy', 'graphically'], + 'graphologic': ['graphologic', 'logographic'], + 'graphological': ['graphological', 'logographical'], + 'graphology': ['graphology', 'logography'], + 'graphometer': ['graphometer', 'meteorgraph'], + 'graphophonic': ['graphophonic', 'phonographic'], + 'graphostatic': ['gastropathic', 'graphostatic'], + 'graphotypic': ['graphotypic', 'pictography', 'typographic'], + 'grapsidae': ['disparage', 'grapsidae'], + 'grasp': ['grasp', 'sprag'], + 'grasper': ['grasper', 'regrasp', 'sparger'], + 'grasser': ['grasser', 'regrass'], + 'grasshopper': ['grasshopper', 'hoppergrass'], + 'grassman': ['grassman', 'mangrass'], + 'grat': ['grat', 'trag'], + 'grate': ['gater', 'grate', 'great', 'greta', 'retag', 'targe'], + 'grateman': ['grateman', 'mangrate', 'mentagra', 'targeman'], + 'grater': ['garret', 'garter', 'grater', 'targer'], + 'gratiano': ['giornata', 'gratiano'], + 'graticule': ['curtilage', 'cutigeral', 'graticule'], + 'gratiolin': ['gratiolin', 'largition', 'tailoring'], + 'gratis': ['gratis', 'striga'], + 'gratten': ['garnett', 'gnatter', 'gratten', 'tergant'], + 'graupel': ['earplug', 'graupel', 'plaguer'], + 'gravamen': ['gravamen', 'graveman'], + 'gravel': ['glaver', 'gravel'], + 'graveman': ['gravamen', 'graveman'], + 'graves': ['gervas', 'graves'], + 'gravure': ['gravure', 'verruga'], + 'gray': ['gary', 'gray'], + 'grayling': ['grayling', 'ragingly'], + 'graze': ['gazer', 'graze'], + 'greaser': ['argeers', 'greaser', 'serrage'], + 'great': ['gater', 'grate', 'great', 'greta', 'retag', 'targe'], + 'greaten': ['grantee', 'greaten', 'reagent', 'rentage'], + 'greater': ['greater', 'regrate', 'terrage'], + 'greaves': ['gervase', 'greaves', 'servage'], + 'grebe': ['gerbe', 'grebe', 'rebeg'], + 'grecian': ['anergic', 'garnice', 'garniec', 'geranic', 'grecian'], + 'grecomania': ['ergomaniac', 'grecomania'], + 'greed': ['edger', 'greed'], + 'green': ['genre', 'green', 'neger', 'reneg'], + 'greenable': ['generable', 'greenable'], + 'greener': ['greener', 'regreen', 'reneger'], + 'greenish': ['greenish', 'sheering'], + 'greenland': ['englander', 'greenland'], + 'greenuk': ['gerenuk', 'greenuk'], + 'greeny': ['energy', 'greeny', 'gyrene'], + 'greet': ['egret', 'greet', 'reget'], + 'greeter': ['greeter', 'regreet'], + 'gregal': ['gargle', 'gregal', 'lagger', 'raggle'], + 'gregarian': ['gregarian', 'gregarina'], + 'gregarina': ['gregarian', 'gregarina'], + 'gregarinous': ['grangousier', 'gregarinous'], + 'grege': ['egger', 'grege'], + 'gregge': ['gegger', 'gregge'], + 'grego': ['gorge', 'grego'], + 'gregor': ['gorger', 'gregor'], + 'greige': ['greige', 'reggie'], + 'grein': ['grein', 'inger', 'nigre', 'regin', 'reign', 'ringe'], + 'gremial': ['gremial', 'lamiger'], + 'gremlin': ['gremlin', 'mingler'], + 'grenade': ['derange', 'enraged', 'gardeen', 'gerenda', 'grandee', 'grenade'], + 'grenadian': ['granadine', 'grenadian'], + 'grenadier': ['earringed', 'grenadier'], + 'grenadin': ['gardenin', 'grenadin'], + 'grenadine': ['endearing', 'engrained', 'grenadine'], + 'greta': ['gater', 'grate', 'great', 'greta', 'retag', 'targe'], + 'gretel': ['gretel', 'reglet'], + 'greund': ['dunger', 'gerund', 'greund', 'nudger'], + 'grewia': ['earwig', 'grewia'], + 'grey': ['grey', 'gyre'], + 'grid': ['gird', 'grid'], + 'griddle': ['glidder', 'griddle'], + 'gride': ['dirge', 'gride', 'redig', 'ridge'], + 'gridelin': ['dreiling', 'gridelin'], + 'grieve': ['grieve', 'regive'], + 'grieved': ['diverge', 'grieved'], + 'grille': ['giller', 'grille', 'regill'], + 'grilse': ['girsle', 'gisler', 'glires', 'grilse'], + 'grimace': ['gemaric', 'grimace', 'megaric'], + 'grime': ['gerim', 'grime'], + 'grimme': ['gimmer', 'grimme', 'megrim'], + 'grin': ['girn', 'grin', 'ring'], + 'grinder': ['grinder', 'regrind'], + 'grindle': ['dringle', 'grindle'], + 'gringo': ['goring', 'gringo'], + 'grip': ['grip', 'prig'], + 'gripe': ['gerip', 'gripe'], + 'gripeful': ['fireplug', 'gripeful'], + 'griper': ['griper', 'regrip'], + 'gripman': ['gripman', 'prigman', 'ramping'], + 'grippe': ['gipper', 'grippe'], + 'grisounite': ['grisounite', 'grisoutine', 'integrious'], + 'grisoutine': ['grisounite', 'grisoutine', 'integrious'], + 'grist': ['grist', 'grits', 'strig'], + 'gristle': ['glister', 'gristle'], + 'grit': ['girt', 'grit', 'trig'], + 'grith': ['girth', 'grith', 'right'], + 'grits': ['grist', 'grits', 'strig'], + 'gritten': ['gittern', 'gritten', 'retting'], + 'grittle': ['glitter', 'grittle'], + 'grivna': ['grivna', 'raving'], + 'grizzel': ['grizzel', 'grizzle'], + 'grizzle': ['grizzel', 'grizzle'], + 'groan': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'groaner': ['groaner', 'oranger', 'organer'], + 'groaning': ['groaning', 'organing'], + 'groat': ['argot', 'gator', 'gotra', 'groat'], + 'grobian': ['biorgan', 'grobian'], + 'groined': ['groined', 'negroid'], + 'gromia': ['gamori', 'gomari', 'gromia'], + 'groove': ['groove', 'overgo'], + 'grope': ['grope', 'porge'], + 'groper': ['groper', 'porger'], + 'groset': ['groset', 'storge'], + 'grossen': ['engross', 'grossen'], + 'grot': ['grot', 'trog'], + 'grotian': ['grotian', 'trigona'], + 'grotto': ['grotto', 'torgot'], + 'grounded': ['grounded', 'underdog', 'undergod'], + 'grouper': ['grouper', 'regroup'], + 'grouse': ['grouse', 'rugose'], + 'grousy': ['grousy', 'gyrous'], + 'grovel': ['glover', 'grovel'], + 'groveless': ['gloveress', 'groveless'], + 'growan': ['awrong', 'growan'], + 'grower': ['grower', 'regrow'], + 'grown': ['grown', 'wrong'], + 'grub': ['burg', 'grub'], + 'grudge': ['grudge', 'rugged'], + 'grudger': ['drugger', 'grudger'], + 'grudgery': ['druggery', 'grudgery'], + 'grue': ['grue', 'urge'], + 'gruel': ['gluer', 'gruel', 'luger'], + 'gruelly': ['gruelly', 'gullery'], + 'grues': ['grues', 'surge'], + 'grun': ['grun', 'rung'], + 'grush': ['grush', 'shrug'], + 'grusinian': ['grusinian', 'unarising'], + 'grutten': ['grutten', 'turgent'], + 'gryposis': ['gossipry', 'gryposis'], + 'guacho': ['gaucho', 'guacho'], + 'guan': ['gaun', 'guan', 'guna', 'uang'], + 'guanamine': ['guanamine', 'guineaman'], + 'guanine': ['anguine', 'guanine', 'guinean'], + 'guar': ['gaur', 'guar', 'ruga'], + 'guara': ['gaura', 'guara'], + 'guarani': ['anguria', 'gaurian', 'guarani'], + 'guarantorship': ['guarantorship', 'uranographist'], + 'guardeen': ['dungaree', 'guardeen', 'unagreed', 'underage', 'ungeared'], + 'guarder': ['guarder', 'reguard'], + 'guatusan': ['augustan', 'guatusan'], + 'gud': ['dug', 'gud'], + 'gude': ['degu', 'gude'], + 'guenon': ['guenon', 'ungone'], + 'guepard': ['guepard', 'upgrade'], + 'guerdon': ['guerdon', 'undergo', 'ungored'], + 'guerdoner': ['guerdoner', 'reundergo', 'undergoer', 'undergore'], + 'guerinet': ['geniture', 'guerinet'], + 'guester': ['gesture', 'guester'], + 'guetar': ['argute', 'guetar', 'rugate', 'tuareg'], + 'guetare': ['erugate', 'guetare'], + 'guha': ['augh', 'guha'], + 'guiana': ['guiana', 'iguana'], + 'guib': ['bugi', 'guib'], + 'guineaman': ['guanamine', 'guineaman'], + 'guinean': ['anguine', 'guanine', 'guinean'], + 'guiser': ['guiser', 'sergiu'], + 'gul': ['gul', 'lug'], + 'gula': ['gaul', 'gula'], + 'gulae': ['gulae', 'legua'], + 'gular': ['glaur', 'gular'], + 'gularis': ['agrilus', 'gularis'], + 'gulden': ['gulden', 'lunged'], + 'gule': ['glue', 'gule', 'luge'], + 'gules': ['gules', 'gusle'], + 'gullery': ['gruelly', 'gullery'], + 'gullible': ['bluegill', 'gullible'], + 'gulonic': ['gulonic', 'unlogic'], + 'gulp': ['gulp', 'plug'], + 'gulpin': ['gulpin', 'puling'], + 'gum': ['gum', 'mug'], + 'gumbo': ['bogum', 'gumbo'], + 'gumshoe': ['gumshoe', 'hugsome'], + 'gumweed': ['gumweed', 'mugweed'], + 'gun': ['gnu', 'gun'], + 'guna': ['gaun', 'guan', 'guna', 'uang'], + 'gunate': ['gunate', 'tangue'], + 'gundi': ['gundi', 'undig'], + 'gundy': ['dungy', 'gundy'], + 'gunk': ['gunk', 'kung'], + 'gunl': ['gunl', 'lung'], + 'gunnership': ['gunnership', 'unsphering'], + 'gunreach': ['gunreach', 'uncharge'], + 'gunsel': ['gunsel', 'selung', 'slunge'], + 'gunshot': ['gunshot', 'shotgun', 'uhtsong'], + 'gunster': ['gunster', 'surgent'], + 'gunter': ['gunter', 'gurnet', 'urgent'], + 'gup': ['gup', 'pug'], + 'gur': ['gur', 'rug'], + 'gurgeon': ['gurgeon', 'ungorge'], + 'gurgle': ['gurgle', 'lugger', 'ruggle'], + 'gurian': ['gurian', 'ugrian'], + 'guric': ['guric', 'ugric'], + 'gurl': ['gurl', 'lurg'], + 'gurnard': ['drungar', 'gurnard'], + 'gurnet': ['gunter', 'gurnet', 'urgent'], + 'gurt': ['gurt', 'trug'], + 'gush': ['gush', 'shug', 'sugh'], + 'gusher': ['gusher', 'regush'], + 'gusle': ['gules', 'gusle'], + 'gust': ['gust', 'stug'], + 'gut': ['gut', 'tug'], + 'gutless': ['gutless', 'tugless'], + 'gutlike': ['gutlike', 'tuglike'], + 'gutnish': ['gutnish', 'husting', 'unsight'], + 'guttler': ['glutter', 'guttler'], + 'guttular': ['guttular', 'guttural'], + 'guttural': ['guttular', 'guttural'], + 'gweed': ['gweed', 'wedge'], + 'gymnasic': ['gymnasic', 'syngamic'], + 'gymnastic': ['gymnastic', 'nystagmic'], + 'gynandrous': ['androgynus', 'gynandrous'], + 'gynerium': ['eryngium', 'gynerium'], + 'gynospore': ['gynospore', 'sporogeny'], + 'gypsine': ['gypsine', 'pigsney'], + 'gyral': ['glary', 'gyral'], + 'gyrant': ['gantry', 'gyrant'], + 'gyrate': ['geraty', 'gyrate'], + 'gyration': ['gyration', 'organity', 'ortygian'], + 'gyre': ['grey', 'gyre'], + 'gyrene': ['energy', 'greeny', 'gyrene'], + 'gyro': ['gory', 'gyro', 'orgy'], + 'gyroma': ['gyroma', 'morgay'], + 'gyromitra': ['gyromitra', 'migratory'], + 'gyrophora': ['gyrophora', 'orography'], + 'gyrous': ['grousy', 'gyrous'], + 'gyrus': ['gyrus', 'surgy'], + 'ha': ['ah', 'ha'], + 'haberdine': ['haberdine', 'hebridean'], + 'habile': ['habile', 'halebi'], + 'habiri': ['bihari', 'habiri'], + 'habiru': ['brahui', 'habiru'], + 'habit': ['baith', 'habit'], + 'habitan': ['abthain', 'habitan'], + 'habitat': ['habitat', 'tabitha'], + 'habited': ['habited', 'thebaid'], + 'habitus': ['habitus', 'ushabti'], + 'habnab': ['babhan', 'habnab'], + 'hacienda': ['chanidae', 'hacienda'], + 'hackin': ['hackin', 'kachin'], + 'hackle': ['hackle', 'lekach'], + 'hackler': ['chalker', 'hackler'], + 'hackly': ['chalky', 'hackly'], + 'hacktree': ['eckehart', 'hacktree'], + 'hackwood': ['hackwood', 'woodhack'], + 'hacky': ['chyak', 'hacky'], + 'had': ['dah', 'dha', 'had'], + 'hadden': ['hadden', 'handed'], + 'hade': ['hade', 'head'], + 'hades': ['deash', 'hades', 'sadhe', 'shade'], + 'hadji': ['hadji', 'jihad'], + 'haec': ['ache', 'each', 'haec'], + 'haem': ['ahem', 'haem', 'hame'], + 'haet': ['ahet', 'haet', 'hate', 'heat', 'thea'], + 'hafgan': ['afghan', 'hafgan'], + 'hafter': ['father', 'freath', 'hafter'], + 'hageen': ['hageen', 'hangee'], + 'hailse': ['elisha', 'hailse', 'sheila'], + 'hainan': ['hainan', 'nahani'], + 'hair': ['ahir', 'hair'], + 'hairband': ['bhandari', 'hairband'], + 'haired': ['dehair', 'haired'], + 'hairen': ['hairen', 'hernia'], + 'hairlet': ['hairlet', 'therial'], + 'hairstone': ['hairstone', 'hortensia'], + 'hairup': ['hairup', 'rupiah'], + 'hak': ['hak', 'kha'], + 'hakam': ['hakam', 'makah'], + 'hakea': ['ekaha', 'hakea'], + 'hakim': ['hakim', 'khami'], + 'haku': ['haku', 'kahu'], + 'halal': ['allah', 'halal'], + 'halbert': ['blather', 'halbert'], + 'hale': ['hale', 'heal', 'leah'], + 'halebi': ['habile', 'halebi'], + 'halenia': ['ainaleh', 'halenia'], + 'halesome': ['halesome', 'healsome'], + 'halicore': ['halicore', 'heroical'], + 'haliotidae': ['aethalioid', 'haliotidae'], + 'hallan': ['hallan', 'nallah'], + 'hallower': ['hallower', 'rehallow'], + 'halma': ['halma', 'hamal'], + 'halogeton': ['halogeton', 'theogonal'], + 'haloid': ['dihalo', 'haloid'], + 'halophile': ['halophile', 'philohela'], + 'halophytism': ['halophytism', 'hylopathism'], + 'hals': ['hals', 'lash'], + 'halse': ['halse', 'leash', 'selah', 'shale', 'sheal', 'shela'], + 'halsen': ['halsen', 'hansel', 'lanseh'], + 'halt': ['halt', 'lath'], + 'halter': ['arthel', 'halter', 'lather', 'thaler'], + 'halterbreak': ['halterbreak', 'leatherbark'], + 'halting': ['halting', 'lathing', 'thingal'], + 'halve': ['halve', 'havel'], + 'halver': ['halver', 'lavehr'], + 'ham': ['ham', 'mah'], + 'hamal': ['halma', 'hamal'], + 'hame': ['ahem', 'haem', 'hame'], + 'hameil': ['hameil', 'hiemal'], + 'hamel': ['hamel', 'hemal'], + 'hamfatter': ['aftermath', 'hamfatter'], + 'hami': ['hami', 'hima', 'mahi'], + 'hamital': ['hamital', 'thalami'], + 'hamites': ['atheism', 'hamites'], + 'hamlet': ['hamlet', 'malthe'], + 'hammerer': ['hammerer', 'rehammer'], + 'hamsa': ['hamsa', 'masha', 'shama'], + 'hamulites': ['hamulites', 'shulamite'], + 'hamus': ['hamus', 'musha'], + 'hanaster': ['hanaster', 'sheratan'], + 'hance': ['achen', 'chane', 'chena', 'hance'], + 'hand': ['dhan', 'hand'], + 'handbook': ['bandhook', 'handbook'], + 'handed': ['hadden', 'handed'], + 'hander': ['hander', 'harden'], + 'handicapper': ['handicapper', 'prehandicap'], + 'handscrape': ['handscrape', 'scaphander'], + 'handstone': ['handstone', 'stonehand'], + 'handwork': ['handwork', 'workhand'], + 'hangar': ['arghan', 'hangar'], + 'hangby': ['banghy', 'hangby'], + 'hangee': ['hageen', 'hangee'], + 'hanger': ['hanger', 'rehang'], + 'hangie': ['gienah', 'hangie'], + 'hangnail': ['hangnail', 'langhian'], + 'hangout': ['hangout', 'tohunga'], + 'hank': ['ankh', 'hank', 'khan'], + 'hano': ['hano', 'noah'], + 'hans': ['hans', 'nash', 'shan'], + 'hansa': ['ahsan', 'hansa', 'hasan'], + 'hanse': ['ashen', 'hanse', 'shane', 'shean'], + 'hanseatic': ['anchistea', 'hanseatic'], + 'hansel': ['halsen', 'hansel', 'lanseh'], + 'hant': ['hant', 'tanh', 'than'], + 'hantle': ['ethnal', 'hantle', 'lathen', 'thenal'], + 'hao': ['aho', 'hao'], + 'haole': ['eloah', 'haole'], + 'haoma': ['haoma', 'omaha'], + 'haori': ['haori', 'iroha'], + 'hap': ['hap', 'pah'], + 'hapalotis': ['hapalotis', 'sapotilha'], + 'hapi': ['hapi', 'pahi'], + 'haplodoci': ['chilopoda', 'haplodoci'], + 'haplont': ['haplont', 'naphtol'], + 'haplosis': ['alphosis', 'haplosis'], + 'haply': ['haply', 'phyla'], + 'happiest': ['happiest', 'peatship'], + 'haptene': ['haptene', 'heptane', 'phenate'], + 'haptenic': ['haptenic', 'pantheic', 'pithecan'], + 'haptere': ['haptere', 'preheat'], + 'haptic': ['haptic', 'pathic'], + 'haptics': ['haptics', 'spathic'], + 'haptometer': ['amphorette', 'haptometer'], + 'haptophoric': ['haptophoric', 'pathophoric'], + 'haptophorous': ['haptophorous', 'pathophorous'], + 'haptotropic': ['haptotropic', 'protopathic'], + 'hapu': ['hapu', 'hupa'], + 'harass': ['harass', 'hassar'], + 'harb': ['bhar', 'harb'], + 'harborer': ['abhorrer', 'harborer'], + 'harden': ['hander', 'harden'], + 'hardener': ['hardener', 'reharden'], + 'hardenite': ['hardenite', 'herniated'], + 'hardtail': ['hardtail', 'thaliard'], + 'hardy': ['hardy', 'hydra'], + 'hare': ['hare', 'hear', 'rhea'], + 'harebrain': ['harebrain', 'herbarian'], + 'harem': ['harem', 'herma', 'rhema'], + 'haremism': ['ashimmer', 'haremism'], + 'harfang': ['fraghan', 'harfang'], + 'haricot': ['chariot', 'haricot'], + 'hark': ['hark', 'khar', 'rakh'], + 'harka': ['harka', 'kahar'], + 'harlot': ['harlot', 'orthal', 'thoral'], + 'harmala': ['harmala', 'marhala'], + 'harman': ['amhran', 'harman', 'mahran'], + 'harmer': ['harmer', 'reharm'], + 'harmine': ['harmine', 'hireman'], + 'harmonic': ['choirman', 'harmonic', 'omniarch'], + 'harmonical': ['harmonical', 'monarchial'], + 'harmonics': ['anorchism', 'harmonics'], + 'harmonistic': ['anchoritism', 'chiromantis', 'chrismation', 'harmonistic'], + 'harnesser': ['harnesser', 'reharness'], + 'harold': ['harold', 'holard'], + 'harpa': ['aphra', 'harpa', 'parah'], + 'harpings': ['harpings', 'phrasing'], + 'harpist': ['harpist', 'traship'], + 'harpless': ['harpless', 'splasher'], + 'harris': ['arrish', 'harris', 'rarish', 'sirrah'], + 'harrower': ['harrower', 'reharrow'], + 'hart': ['hart', 'rath', 'tahr', 'thar', 'trah'], + 'hartin': ['hartin', 'thrain'], + 'hartite': ['hartite', 'rathite'], + 'haruspices': ['chuprassie', 'haruspices'], + 'harvester': ['harvester', 'reharvest'], + 'hasan': ['ahsan', 'hansa', 'hasan'], + 'hash': ['hash', 'sahh', 'shah'], + 'hasher': ['hasher', 'rehash'], + 'hasidic': ['hasidic', 'sahidic'], + 'hasidim': ['hasidim', 'maidish'], + 'hasky': ['hasky', 'shaky'], + 'haslet': ['haslet', 'lesath', 'shelta'], + 'hasp': ['hasp', 'pash', 'psha', 'shap'], + 'hassar': ['harass', 'hassar'], + 'hassel': ['hassel', 'hassle'], + 'hassle': ['hassel', 'hassle'], + 'haste': ['ashet', 'haste', 'sheat'], + 'hasten': ['athens', 'hasten', 'snathe', 'sneath'], + 'haster': ['haster', 'hearst', 'hearts'], + 'hastilude': ['hastilude', 'lustihead'], + 'hastler': ['hastler', 'slather'], + 'hasty': ['hasty', 'yasht'], + 'hat': ['aht', 'hat', 'tha'], + 'hatchery': ['hatchery', 'thearchy'], + 'hate': ['ahet', 'haet', 'hate', 'heat', 'thea'], + 'hateable': ['hateable', 'heatable'], + 'hateful': ['hateful', 'heatful'], + 'hateless': ['hateless', 'heatless'], + 'hater': ['earth', 'hater', 'heart', 'herat', 'rathe'], + 'hati': ['hati', 'thai'], + 'hatred': ['dearth', 'hatred', 'rathed', 'thread'], + 'hatress': ['hatress', 'shaster'], + 'hatt': ['hatt', 'tath', 'that'], + 'hattemist': ['hattemist', 'thematist'], + 'hatter': ['hatter', 'threat'], + 'hattery': ['hattery', 'theatry'], + 'hattic': ['chatti', 'hattic'], + 'hattock': ['hattock', 'totchka'], + 'hau': ['ahu', 'auh', 'hau'], + 'hauerite': ['eutheria', 'hauerite'], + 'haul': ['haul', 'hula'], + 'hauler': ['hauler', 'rehaul'], + 'haunt': ['ahunt', 'haunt', 'thuan', 'unhat'], + 'haunter': ['haunter', 'nauther', 'unearth', 'unheart', 'urethan'], + 'hauntingly': ['hauntingly', 'unhatingly'], + 'haurient': ['haurient', 'huterian'], + 'havel': ['halve', 'havel'], + 'havers': ['havers', 'shaver', 'shrave'], + 'haw': ['haw', 'hwa', 'wah', 'wha'], + 'hawer': ['hawer', 'whare'], + 'hawm': ['hawm', 'wham'], + 'hawse': ['hawse', 'shewa', 'whase'], + 'hawser': ['hawser', 'rewash', 'washer'], + 'hay': ['hay', 'yah'], + 'haya': ['ayah', 'haya'], + 'hayz': ['hayz', 'hazy'], + 'hazarder': ['hazarder', 'rehazard'], + 'hazel': ['hazel', 'hazle'], + 'hazle': ['hazel', 'hazle'], + 'hazy': ['hayz', 'hazy'], + 'he': ['eh', 'he'], + 'head': ['hade', 'head'], + 'headbander': ['barehanded', 'bradenhead', 'headbander'], + 'headboard': ['broadhead', 'headboard'], + 'header': ['adhere', 'header', 'hedera', 'rehead'], + 'headily': ['headily', 'hylidae'], + 'headlight': ['headlight', 'lighthead'], + 'headlong': ['headlong', 'longhead'], + 'headman': ['headman', 'manhead'], + 'headmaster': ['headmaster', 'headstream', 'streamhead'], + 'headnote': ['headnote', 'notehead'], + 'headrail': ['headrail', 'railhead'], + 'headrent': ['adherent', 'headrent', 'neatherd', 'threaden'], + 'headring': ['headring', 'ringhead'], + 'headset': ['headset', 'sethead'], + 'headskin': ['headskin', 'nakedish', 'sinkhead'], + 'headspring': ['headspring', 'springhead'], + 'headstone': ['headstone', 'stonehead'], + 'headstream': ['headmaster', 'headstream', 'streamhead'], + 'headstrong': ['headstrong', 'stronghead'], + 'headward': ['drawhead', 'headward'], + 'headwater': ['headwater', 'waterhead'], + 'heal': ['hale', 'heal', 'leah'], + 'healer': ['healer', 'rehale', 'reheal'], + 'healsome': ['halesome', 'healsome'], + 'heap': ['epha', 'heap'], + 'heaper': ['heaper', 'reheap'], + 'heaps': ['heaps', 'pesah', 'phase', 'shape'], + 'hear': ['hare', 'hear', 'rhea'], + 'hearer': ['hearer', 'rehear'], + 'hearken': ['hearken', 'kenareh'], + 'hearst': ['haster', 'hearst', 'hearts'], + 'heart': ['earth', 'hater', 'heart', 'herat', 'rathe'], + 'heartdeep': ['heartdeep', 'preheated'], + 'hearted': ['earthed', 'hearted'], + 'heartedness': ['heartedness', 'neatherdess'], + 'hearten': ['earthen', 'enheart', 'hearten', 'naether', 'teheran', 'traheen'], + 'heartener': ['heartener', 'rehearten'], + 'heartiness': ['earthiness', 'heartiness'], + 'hearting': ['hearting', 'ingather'], + 'heartless': ['earthless', 'heartless'], + 'heartling': ['earthling', 'heartling'], + 'heartly': ['earthly', 'heartly', 'lathery', 'rathely'], + 'heartnut': ['earthnut', 'heartnut'], + 'heartpea': ['earthpea', 'heartpea'], + 'heartquake': ['earthquake', 'heartquake'], + 'hearts': ['haster', 'hearst', 'hearts'], + 'heartsome': ['heartsome', 'samothere'], + 'heartward': ['earthward', 'heartward'], + 'heartweed': ['heartweed', 'weathered'], + 'hearty': ['earthy', 'hearty', 'yearth'], + 'heat': ['ahet', 'haet', 'hate', 'heat', 'thea'], + 'heatable': ['hateable', 'heatable'], + 'heater': ['heater', 'hereat', 'reheat'], + 'heatful': ['hateful', 'heatful'], + 'heath': ['heath', 'theah'], + 'heating': ['gahnite', 'heating'], + 'heatless': ['hateless', 'heatless'], + 'heatronic': ['anchorite', 'antechoir', 'heatronic', 'hectorian'], + 'heave': ['heave', 'hevea'], + 'hebraizer': ['hebraizer', 'herbarize'], + 'hebridean': ['haberdine', 'hebridean'], + 'hecate': ['achete', 'hecate', 'teache', 'thecae'], + 'hecatine': ['echinate', 'hecatine'], + 'heckle': ['heckle', 'kechel'], + 'hectare': ['cheater', 'hectare', 'recheat', 'reteach', 'teacher'], + 'hecte': ['cheet', 'hecte'], + 'hector': ['hector', 'rochet', 'tocher', 'troche'], + 'hectorian': ['anchorite', 'antechoir', 'heatronic', 'hectorian'], + 'hectorship': ['christophe', 'hectorship'], + 'hedera': ['adhere', 'header', 'hedera', 'rehead'], + 'hedonical': ['chelodina', 'hedonical'], + 'hedonism': ['demonish', 'hedonism'], + 'heehaw': ['heehaw', 'wahehe'], + 'heel': ['heel', 'hele'], + 'heeler': ['heeler', 'reheel'], + 'heelpost': ['heelpost', 'pesthole'], + 'heer': ['heer', 'here'], + 'hegari': ['hegari', 'hegira'], + 'hegemonic': ['hegemonic', 'hemogenic'], + 'hegira': ['hegari', 'hegira'], + 'hei': ['hei', 'hie'], + 'height': ['eighth', 'height'], + 'heightener': ['heightener', 'reheighten'], + 'heintzite': ['heintzite', 'hintzeite'], + 'heinz': ['heinz', 'hienz'], + 'heir': ['heir', 'hire'], + 'heirdom': ['heirdom', 'homerid'], + 'heirless': ['heirless', 'hireless'], + 'hejazi': ['hejazi', 'jeziah'], + 'helcosis': ['helcosis', 'ochlesis'], + 'helcotic': ['helcotic', 'lochetic', 'ochletic'], + 'hele': ['heel', 'hele'], + 'heliacal': ['achillea', 'heliacal'], + 'heliast': ['heliast', 'thesial'], + 'helical': ['alichel', 'challie', 'helical'], + 'heliced': ['chelide', 'heliced'], + 'helicon': ['choline', 'helicon'], + 'heling': ['heling', 'hingle'], + 'heliophotography': ['heliophotography', 'photoheliography'], + 'helios': ['helios', 'isohel'], + 'heliostatic': ['chiastolite', 'heliostatic'], + 'heliotactic': ['heliotactic', 'thiolacetic'], + 'helium': ['helium', 'humlie'], + 'hellcat': ['hellcat', 'tellach'], + 'helleborein': ['helleborein', 'helleborine'], + 'helleborine': ['helleborein', 'helleborine'], + 'hellenic': ['chenille', 'hellenic'], + 'helleri': ['helleri', 'hellier'], + 'hellicat': ['hellicat', 'lecithal'], + 'hellier': ['helleri', 'hellier'], + 'helm': ['helm', 'heml'], + 'heloderma': ['dreamhole', 'heloderma'], + 'helot': ['helot', 'hotel', 'thole'], + 'helotize': ['helotize', 'hotelize'], + 'helpmeet': ['helpmeet', 'meethelp'], + 'hemad': ['ahmed', 'hemad'], + 'hemal': ['hamel', 'hemal'], + 'hemapod': ['hemapod', 'mophead'], + 'hematic': ['chamite', 'hematic'], + 'hematin': ['ethanim', 'hematin'], + 'hematinic': ['hematinic', 'minchiate'], + 'hematolin': ['hematolin', 'maholtine'], + 'hematonic': ['hematonic', 'methanoic'], + 'hematosin': ['hematosin', 'thomasine'], + 'hematoxic': ['hematoxic', 'hexatomic'], + 'hematuric': ['hematuric', 'rheumatic'], + 'hemiasci': ['hemiasci', 'ischemia'], + 'hemiatrophy': ['hemiatrophy', 'hypothermia'], + 'hemic': ['chime', 'hemic', 'miche'], + 'hemicarp': ['camphire', 'hemicarp'], + 'hemicatalepsy': ['hemicatalepsy', 'mesaticephaly'], + 'hemiclastic': ['alchemistic', 'hemiclastic'], + 'hemicrany': ['hemicrany', 'machinery'], + 'hemiholohedral': ['hemiholohedral', 'holohemihedral'], + 'hemiolic': ['elohimic', 'hemiolic'], + 'hemiparesis': ['hemiparesis', 'phariseeism'], + 'hemistater': ['amherstite', 'hemistater'], + 'hemiterata': ['hemiterata', 'metatheria'], + 'hemitype': ['epithyme', 'hemitype'], + 'heml': ['helm', 'heml'], + 'hemogenic': ['hegemonic', 'hemogenic'], + 'hemol': ['hemol', 'mohel'], + 'hemologist': ['hemologist', 'theologism'], + 'hemopneumothorax': ['hemopneumothorax', 'pneumohemothorax'], + 'henbit': ['behint', 'henbit'], + 'hent': ['hent', 'neth', 'then'], + 'henter': ['erthen', 'henter', 'nether', 'threne'], + 'henyard': ['enhydra', 'henyard'], + 'hepar': ['hepar', 'phare', 'raphe'], + 'heparin': ['heparin', 'nephria'], + 'hepatic': ['aphetic', 'caphite', 'hepatic'], + 'hepatica': ['apachite', 'hepatica'], + 'hepatical': ['caliphate', 'hepatical'], + 'hepatize': ['aphetize', 'hepatize'], + 'hepatocolic': ['hepatocolic', 'otocephalic'], + 'hepatogastric': ['gastrohepatic', 'hepatogastric'], + 'hepatoid': ['diaphote', 'hepatoid'], + 'hepatomegalia': ['hepatomegalia', 'megalohepatia'], + 'hepatonephric': ['hepatonephric', 'phrenohepatic'], + 'hepatostomy': ['hepatostomy', 'somatophyte'], + 'hepialid': ['hepialid', 'phialide'], + 'heptace': ['heptace', 'tepache'], + 'heptad': ['heptad', 'pathed'], + 'heptagon': ['heptagon', 'pathogen'], + 'heptameron': ['heptameron', 'promethean'], + 'heptane': ['haptene', 'heptane', 'phenate'], + 'heptaploidy': ['heptaploidy', 'typhlopidae'], + 'hepteris': ['hepteris', 'treeship'], + 'heptine': ['heptine', 'nephite'], + 'heptite': ['epithet', 'heptite'], + 'heptorite': ['heptorite', 'tephroite'], + 'heptylic': ['heptylic', 'phyletic'], + 'her': ['her', 'reh', 'rhe'], + 'heraclid': ['heraclid', 'heraldic'], + 'heraldic': ['heraclid', 'heraldic'], + 'heraldist': ['heraldist', 'tehsildar'], + 'herat': ['earth', 'hater', 'heart', 'herat', 'rathe'], + 'herbage': ['breaghe', 'herbage'], + 'herbarian': ['harebrain', 'herbarian'], + 'herbarism': ['herbarism', 'shambrier'], + 'herbarize': ['hebraizer', 'herbarize'], + 'herbert': ['berther', 'herbert'], + 'herbous': ['herbous', 'subhero'], + 'herdic': ['chider', 'herdic'], + 'here': ['heer', 'here'], + 'hereafter': ['featherer', 'hereafter'], + 'hereat': ['heater', 'hereat', 'reheat'], + 'herein': ['herein', 'inhere'], + 'hereinto': ['etherion', 'hereinto', 'heronite'], + 'herem': ['herem', 'rheme'], + 'heretic': ['erethic', 'etheric', 'heretic', 'heteric', 'teicher'], + 'heretically': ['heretically', 'heterically'], + 'heretication': ['heretication', 'theoretician'], + 'hereto': ['hereto', 'hetero'], + 'heritance': ['catherine', 'heritance'], + 'herl': ['herl', 'hler', 'lehr'], + 'herma': ['harem', 'herma', 'rhema'], + 'hermaic': ['chimera', 'hermaic'], + 'hermitage': ['geheimrat', 'hermitage'], + 'hermo': ['hermo', 'homer', 'horme'], + 'herne': ['herne', 'rheen'], + 'hernia': ['hairen', 'hernia'], + 'hernial': ['hernial', 'inhaler'], + 'herniate': ['atherine', 'herniate'], + 'herniated': ['hardenite', 'herniated'], + 'hernioid': ['dinheiro', 'hernioid'], + 'hero': ['hero', 'hoer'], + 'herodian': ['herodian', 'ironhead'], + 'heroic': ['coheir', 'heroic'], + 'heroical': ['halicore', 'heroical'], + 'heroin': ['heroin', 'hieron', 'hornie'], + 'heroism': ['heroism', 'moreish'], + 'heronite': ['etherion', 'hereinto', 'heronite'], + 'herophile': ['herophile', 'rheophile'], + 'herpes': ['herpes', 'hesper', 'sphere'], + 'herpetism': ['herpetism', 'metership', 'metreship', 'temperish'], + 'herpetological': ['herpetological', 'pretheological'], + 'herpetomonad': ['dermatophone', 'herpetomonad'], + 'hers': ['hers', 'resh', 'sher'], + 'herse': ['herse', 'sereh', 'sheer', 'shree'], + 'hersed': ['hersed', 'sheder'], + 'herself': ['flesher', 'herself'], + 'hersir': ['hersir', 'sherri'], + 'herulian': ['herulian', 'inhauler'], + 'hervati': ['athrive', 'hervati'], + 'hesitater': ['hesitater', 'hetaerist'], + 'hesper': ['herpes', 'hesper', 'sphere'], + 'hespera': ['hespera', 'rephase', 'reshape'], + 'hesperia': ['hesperia', 'pharisee'], + 'hesperian': ['hesperian', 'phrenesia', 'seraphine'], + 'hesperid': ['hesperid', 'perished'], + 'hesperinon': ['hesperinon', 'prehension'], + 'hesperis': ['hesperis', 'seership'], + 'hest': ['esth', 'hest', 'seth'], + 'hester': ['esther', 'hester', 'theres'], + 'het': ['het', 'the'], + 'hetaeric': ['cheatrie', 'hetaeric'], + 'hetaerist': ['hesitater', 'hetaerist'], + 'hetaery': ['erythea', 'hetaery', 'yeather'], + 'heteratomic': ['heteratomic', 'theorematic'], + 'heteraxial': ['exhilarate', 'heteraxial'], + 'heteric': ['erethic', 'etheric', 'heretic', 'heteric', 'teicher'], + 'heterically': ['heretically', 'heterically'], + 'hetericism': ['erethismic', 'hetericism'], + 'hetericist': ['erethistic', 'hetericist'], + 'heterism': ['erethism', 'etherism', 'heterism'], + 'heterization': ['etherization', 'heterization'], + 'heterize': ['etherize', 'heterize'], + 'hetero': ['hereto', 'hetero'], + 'heterocarpus': ['heterocarpus', 'urethrascope'], + 'heteroclite': ['heteroclite', 'heterotelic'], + 'heterodromy': ['heterodromy', 'hydrometeor'], + 'heteroecismal': ['cholesteremia', 'heteroecismal'], + 'heterography': ['erythrophage', 'heterography'], + 'heterogynous': ['heterogynous', 'thyreogenous'], + 'heterology': ['heterology', 'thereology'], + 'heteromeri': ['heteromeri', 'moerithere'], + 'heteroousiast': ['autoheterosis', 'heteroousiast'], + 'heteropathy': ['heteropathy', 'theotherapy'], + 'heteropodal': ['heteropodal', 'prelatehood'], + 'heterotelic': ['heteroclite', 'heterotelic'], + 'heterotic': ['heterotic', 'theoretic'], + 'hetman': ['anthem', 'hetman', 'mentha'], + 'hetmanate': ['hetmanate', 'methanate'], + 'hetter': ['hetter', 'tether'], + 'hevea': ['heave', 'hevea'], + 'hevi': ['hevi', 'hive'], + 'hewel': ['hewel', 'wheel'], + 'hewer': ['hewer', 'wheer', 'where'], + 'hewn': ['hewn', 'when'], + 'hewt': ['hewt', 'thew', 'whet'], + 'hexacid': ['hexacid', 'hexadic'], + 'hexadic': ['hexacid', 'hexadic'], + 'hexakisoctahedron': ['hexakisoctahedron', 'octakishexahedron'], + 'hexakistetrahedron': ['hexakistetrahedron', 'tetrakishexahedron'], + 'hexatetrahedron': ['hexatetrahedron', 'tetrahexahedron'], + 'hexatomic': ['hematoxic', 'hexatomic'], + 'hexonic': ['choenix', 'hexonic'], + 'hiant': ['ahint', 'hiant', 'tahin'], + 'hiatal': ['hiatal', 'thalia'], + 'hibernate': ['hibernate', 'inbreathe'], + 'hic': ['chi', 'hic', 'ich'], + 'hickwall': ['hickwall', 'wallhick'], + 'hidage': ['adighe', 'hidage'], + 'hider': ['dheri', 'hider', 'hired'], + 'hidling': ['hidling', 'hilding'], + 'hidlings': ['dishling', 'hidlings'], + 'hidrotic': ['hidrotic', 'trichoid'], + 'hie': ['hei', 'hie'], + 'hield': ['delhi', 'hield'], + 'hiemal': ['hameil', 'hiemal'], + 'hienz': ['heinz', 'hienz'], + 'hieron': ['heroin', 'hieron', 'hornie'], + 'hieros': ['hieros', 'hosier'], + 'hight': ['hight', 'thigh'], + 'higuero': ['higuero', 'roughie'], + 'hilasmic': ['chiliasm', 'hilasmic', 'machilis'], + 'hilding': ['hidling', 'hilding'], + 'hillside': ['hillside', 'sidehill'], + 'hilsa': ['alish', 'hilsa'], + 'hilt': ['hilt', 'lith'], + 'hima': ['hami', 'hima', 'mahi'], + 'himself': ['flemish', 'himself'], + 'hinderest': ['disherent', 'hinderest', 'tenderish'], + 'hindu': ['hindu', 'hundi', 'unhid'], + 'hing': ['hing', 'nigh'], + 'hinge': ['hinge', 'neigh'], + 'hingle': ['heling', 'hingle'], + 'hint': ['hint', 'thin'], + 'hinter': ['hinter', 'nither', 'theirn'], + 'hintproof': ['hintproof', 'hoofprint'], + 'hintzeite': ['heintzite', 'hintzeite'], + 'hip': ['hip', 'phi'], + 'hipbone': ['hipbone', 'hopbine'], + 'hippodamous': ['amphipodous', 'hippodamous'], + 'hippolyte': ['hippolyte', 'typophile'], + 'hippus': ['hippus', 'uppish'], + 'hiram': ['hiram', 'ihram', 'mahri'], + 'hircine': ['hircine', 'rheinic'], + 'hire': ['heir', 'hire'], + 'hired': ['dheri', 'hider', 'hired'], + 'hireless': ['heirless', 'hireless'], + 'hireman': ['harmine', 'hireman'], + 'hiren': ['hiren', 'rhein', 'rhine'], + 'hirmos': ['hirmos', 'romish'], + 'hirse': ['hirse', 'shier', 'shire'], + 'hirsel': ['hirsel', 'hirsle', 'relish'], + 'hirsle': ['hirsel', 'hirsle', 'relish'], + 'his': ['his', 'hsi', 'shi'], + 'hish': ['hish', 'shih'], + 'hisn': ['hisn', 'shin', 'sinh'], + 'hispa': ['aphis', 'apish', 'hispa', 'saiph', 'spahi'], + 'hispanist': ['hispanist', 'saintship'], + 'hiss': ['hiss', 'sish'], + 'hist': ['hist', 'sith', 'this', 'tshi'], + 'histamine': ['histamine', 'semihiant'], + 'histie': ['histie', 'shiite'], + 'histioid': ['histioid', 'idiotish'], + 'histon': ['histon', 'shinto', 'tonish'], + 'histonal': ['histonal', 'toshnail'], + 'historic': ['historic', 'orchitis'], + 'historics': ['historics', 'trichosis'], + 'history': ['history', 'toryish'], + 'hittable': ['hittable', 'tithable'], + 'hitter': ['hitter', 'tither'], + 'hive': ['hevi', 'hive'], + 'hives': ['hives', 'shive'], + 'hler': ['herl', 'hler', 'lehr'], + 'ho': ['ho', 'oh'], + 'hoar': ['hoar', 'hora'], + 'hoard': ['hoard', 'rhoda'], + 'hoarse': ['ahorse', 'ashore', 'hoarse', 'shorea'], + 'hoarstone': ['anorthose', 'hoarstone'], + 'hoast': ['hoast', 'hosta', 'shoat'], + 'hobbism': ['hobbism', 'mobbish'], + 'hobo': ['boho', 'hobo'], + 'hocco': ['choco', 'hocco'], + 'hock': ['hock', 'koch'], + 'hocker': ['choker', 'hocker'], + 'hocky': ['choky', 'hocky'], + 'hocus': ['chous', 'hocus'], + 'hodiernal': ['hodiernal', 'rhodaline'], + 'hoer': ['hero', 'hoer'], + 'hogan': ['ahong', 'hogan'], + 'hogget': ['egghot', 'hogget'], + 'hogmanay': ['hogmanay', 'mahogany'], + 'hognut': ['hognut', 'nought'], + 'hogsty': ['ghosty', 'hogsty'], + 'hoister': ['hoister', 'rehoist'], + 'hoit': ['hoit', 'hoti', 'thio'], + 'holard': ['harold', 'holard'], + 'holconoti': ['holconoti', 'holotonic'], + 'holcus': ['holcus', 'lochus', 'slouch'], + 'holdfast': ['fasthold', 'holdfast'], + 'holdout': ['holdout', 'outhold'], + 'holdup': ['holdup', 'uphold'], + 'holeman': ['holeman', 'manhole'], + 'holey': ['holey', 'hoyle'], + 'holiday': ['holiday', 'hyaloid', 'hyoidal'], + 'hollandite': ['hollandite', 'hollantide'], + 'hollantide': ['hollandite', 'hollantide'], + 'hollower': ['hollower', 'rehollow'], + 'holmia': ['holmia', 'maholi'], + 'holocentrid': ['holocentrid', 'lechriodont'], + 'holohemihedral': ['hemiholohedral', 'holohemihedral'], + 'holosteric': ['holosteric', 'thiocresol'], + 'holotonic': ['holconoti', 'holotonic'], + 'holster': ['holster', 'hostler'], + 'homage': ['homage', 'ohmage'], + 'homarine': ['homarine', 'homerian'], + 'homecroft': ['forthcome', 'homecroft'], + 'homeogenous': ['homeogenous', 'homogeneous'], + 'homeotypic': ['homeotypic', 'mythopoeic'], + 'homeotypical': ['homeotypical', 'polymetochia'], + 'homer': ['hermo', 'homer', 'horme'], + 'homerian': ['homarine', 'homerian'], + 'homeric': ['homeric', 'moriche'], + 'homerical': ['chloremia', 'homerical'], + 'homerid': ['heirdom', 'homerid'], + 'homerist': ['homerist', 'isotherm', 'otherism', 'theorism'], + 'homiletics': ['homiletics', 'mesolithic'], + 'homo': ['homo', 'moho'], + 'homocline': ['chemiloon', 'homocline'], + 'homogeneous': ['homeogenous', 'homogeneous'], + 'homopolic': ['homopolic', 'lophocomi'], + 'homopteran': ['homopteran', 'trophonema'], + 'homrai': ['homrai', 'mahori', 'mohair'], + 'hondo': ['dhoon', 'hondo'], + 'honest': ['ethnos', 'honest'], + 'honeypod': ['dyophone', 'honeypod'], + 'honeypot': ['eophyton', 'honeypot'], + 'honorer': ['honorer', 'rehonor'], + 'hontous': ['hontous', 'nothous'], + 'hoodman': ['dhamnoo', 'hoodman', 'manhood'], + 'hoofprint': ['hintproof', 'hoofprint'], + 'hooker': ['hooker', 'rehook'], + 'hookweed': ['hookweed', 'weedhook'], + 'hoop': ['hoop', 'phoo', 'pooh'], + 'hooper': ['hooper', 'rehoop'], + 'hoot': ['hoot', 'thoo', 'toho'], + 'hop': ['hop', 'pho', 'poh'], + 'hopbine': ['hipbone', 'hopbine'], + 'hopcalite': ['hopcalite', 'phacolite'], + 'hope': ['hope', 'peho'], + 'hoped': ['depoh', 'ephod', 'hoped'], + 'hoper': ['ephor', 'hoper'], + 'hoplite': ['hoplite', 'pithole'], + 'hoppergrass': ['grasshopper', 'hoppergrass'], + 'hoppers': ['hoppers', 'shopper'], + 'hora': ['hoar', 'hora'], + 'horal': ['horal', 'lohar'], + 'hordarian': ['arianrhod', 'hordarian'], + 'horizontal': ['horizontal', 'notorhizal'], + 'horme': ['hermo', 'homer', 'horme'], + 'horned': ['dehorn', 'horned'], + 'hornet': ['hornet', 'nother', 'theron', 'throne'], + 'hornie': ['heroin', 'hieron', 'hornie'], + 'hornpipe': ['hornpipe', 'porphine'], + 'horopteric': ['horopteric', 'rheotropic', 'trichopore'], + 'horrent': ['horrent', 'norther'], + 'horse': ['horse', 'shoer', 'shore'], + 'horsecar': ['cosharer', 'horsecar'], + 'horseless': ['horseless', 'shoreless'], + 'horseman': ['horseman', 'rhamnose', 'shoreman'], + 'horser': ['horser', 'shorer'], + 'horsetail': ['horsetail', 'isotheral'], + 'horseweed': ['horseweed', 'shoreweed'], + 'horsewhip': ['horsewhip', 'whoreship'], + 'horsewood': ['horsewood', 'woodhorse'], + 'horsing': ['horsing', 'shoring'], + 'horst': ['horst', 'short'], + 'hortensia': ['hairstone', 'hortensia'], + 'hortite': ['hortite', 'orthite', 'thorite'], + 'hose': ['hose', 'shoe'], + 'hosed': ['hosed', 'shode'], + 'hosel': ['hosel', 'sheol', 'shole'], + 'hoseless': ['hoseless', 'shoeless'], + 'hoseman': ['hoseman', 'shoeman'], + 'hosier': ['hieros', 'hosier'], + 'hospitaler': ['hospitaler', 'trophesial'], + 'host': ['host', 'shot', 'thos', 'tosh'], + 'hosta': ['hoast', 'hosta', 'shoat'], + 'hostager': ['hostager', 'shortage'], + 'hoster': ['hoster', 'tosher'], + 'hostile': ['elohist', 'hostile'], + 'hosting': ['hosting', 'onsight'], + 'hostler': ['holster', 'hostler'], + 'hostless': ['hostless', 'shotless'], + 'hostly': ['hostly', 'toshly'], + 'hot': ['hot', 'tho'], + 'hotel': ['helot', 'hotel', 'thole'], + 'hotelize': ['helotize', 'hotelize'], + 'hotfoot': ['foothot', 'hotfoot'], + 'hoti': ['hoit', 'hoti', 'thio'], + 'hotter': ['hotter', 'tother'], + 'hounce': ['cohune', 'hounce'], + 'houseboat': ['boathouse', 'houseboat'], + 'housebug': ['bughouse', 'housebug'], + 'housecraft': ['fratcheous', 'housecraft'], + 'housetop': ['housetop', 'pothouse'], + 'housewarm': ['housewarm', 'warmhouse'], + 'housewear': ['housewear', 'warehouse'], + 'housework': ['housework', 'workhouse'], + 'hovering': ['hovering', 'overnigh'], + 'how': ['how', 'who'], + 'howel': ['howel', 'whole'], + 'however': ['everwho', 'however', 'whoever'], + 'howlet': ['howlet', 'thowel'], + 'howso': ['howso', 'woosh'], + 'howsomever': ['howsomever', 'whomsoever', 'whosomever'], + 'hoya': ['ahoy', 'hoya'], + 'hoyle': ['holey', 'hoyle'], + 'hsi': ['his', 'hsi', 'shi'], + 'huari': ['huari', 'uriah'], + 'hubert': ['hubert', 'turbeh'], + 'hud': ['dhu', 'hud'], + 'hudsonite': ['hudsonite', 'unhoisted'], + 'huer': ['huer', 'hure'], + 'hug': ['hug', 'ugh'], + 'hughes': ['hughes', 'sheugh'], + 'hughoc': ['chough', 'hughoc'], + 'hugo': ['hugo', 'ough'], + 'hugsome': ['gumshoe', 'hugsome'], + 'huk': ['huk', 'khu'], + 'hula': ['haul', 'hula'], + 'hulsean': ['hulsean', 'unleash'], + 'hulster': ['hulster', 'hustler', 'sluther'], + 'huma': ['ahum', 'huma'], + 'human': ['human', 'nahum'], + 'humane': ['humane', 'humean'], + 'humanics': ['humanics', 'inasmuch'], + 'humean': ['humane', 'humean'], + 'humeroradial': ['humeroradial', 'radiohumeral'], + 'humic': ['chimu', 'humic'], + 'humidor': ['humidor', 'rhodium'], + 'humlie': ['helium', 'humlie'], + 'humor': ['humor', 'mohur'], + 'humoralistic': ['humoralistic', 'humoristical'], + 'humoristical': ['humoralistic', 'humoristical'], + 'hump': ['hump', 'umph'], + 'hundi': ['hindu', 'hundi', 'unhid'], + 'hunger': ['hunger', 'rehung'], + 'hunterian': ['hunterian', 'ruthenian'], + 'hup': ['hup', 'phu'], + 'hupa': ['hapu', 'hupa'], + 'hurdis': ['hurdis', 'rudish'], + 'hurdle': ['hurdle', 'hurled'], + 'hure': ['huer', 'hure'], + 'hurled': ['hurdle', 'hurled'], + 'huron': ['huron', 'rohun'], + 'hurst': ['hurst', 'trush'], + 'hurt': ['hurt', 'ruth'], + 'hurter': ['hurter', 'ruther'], + 'hurtful': ['hurtful', 'ruthful'], + 'hurtfully': ['hurtfully', 'ruthfully'], + 'hurtfulness': ['hurtfulness', 'ruthfulness'], + 'hurting': ['hurting', 'ungirth', 'unright'], + 'hurtingest': ['hurtingest', 'shuttering'], + 'hurtle': ['hurtle', 'luther'], + 'hurtless': ['hurtless', 'ruthless'], + 'hurtlessly': ['hurtlessly', 'ruthlessly'], + 'hurtlessness': ['hurtlessness', 'ruthlessness'], + 'husbander': ['husbander', 'shabunder'], + 'husked': ['dehusk', 'husked'], + 'huso': ['huso', 'shou'], + 'huspil': ['huspil', 'pulish'], + 'husting': ['gutnish', 'husting', 'unsight'], + 'hustle': ['hustle', 'sleuth'], + 'hustler': ['hulster', 'hustler', 'sluther'], + 'huterian': ['haurient', 'huterian'], + 'hwa': ['haw', 'hwa', 'wah', 'wha'], + 'hyaloid': ['holiday', 'hyaloid', 'hyoidal'], + 'hydra': ['hardy', 'hydra'], + 'hydramnios': ['disharmony', 'hydramnios'], + 'hydrate': ['hydrate', 'thready'], + 'hydrazidine': ['anhydridize', 'hydrazidine'], + 'hydrazine': ['anhydrize', 'hydrazine'], + 'hydriodate': ['hydriodate', 'iodhydrate'], + 'hydriodic': ['hydriodic', 'iodhydric'], + 'hydriote': ['hydriote', 'thyreoid'], + 'hydrobromate': ['bromohydrate', 'hydrobromate'], + 'hydrocarbide': ['carbohydride', 'hydrocarbide'], + 'hydrocharis': ['hydrocharis', 'hydrorachis'], + 'hydroferricyanic': ['ferrihydrocyanic', 'hydroferricyanic'], + 'hydroferrocyanic': ['ferrohydrocyanic', 'hydroferrocyanic'], + 'hydrofluoboric': ['borofluohydric', 'hydrofluoboric'], + 'hydrogeology': ['geohydrology', 'hydrogeology'], + 'hydroiodic': ['hydroiodic', 'iodohydric'], + 'hydrometeor': ['heterodromy', 'hydrometeor'], + 'hydromotor': ['hydromotor', 'orthodromy'], + 'hydronephrosis': ['hydronephrosis', 'nephrohydrosis'], + 'hydropneumopericardium': ['hydropneumopericardium', 'pneumohydropericardium'], + 'hydropneumothorax': ['hydropneumothorax', 'pneumohydrothorax'], + 'hydrorachis': ['hydrocharis', 'hydrorachis'], + 'hydrosulphate': ['hydrosulphate', 'sulphohydrate'], + 'hydrotical': ['dacryolith', 'hydrotical'], + 'hydrous': ['hydrous', 'shroudy'], + 'hyetograph': ['ethography', 'hyetograph'], + 'hylidae': ['headily', 'hylidae'], + 'hylist': ['hylist', 'slithy'], + 'hyllus': ['hyllus', 'lushly'], + 'hylopathism': ['halophytism', 'hylopathism'], + 'hymenic': ['chimney', 'hymenic'], + 'hymettic': ['hymettic', 'thymetic'], + 'hymnologist': ['hymnologist', 'smoothingly'], + 'hyoglossal': ['glossohyal', 'hyoglossal'], + 'hyoidal': ['holiday', 'hyaloid', 'hyoidal'], + 'hyothyreoid': ['hyothyreoid', 'thyreohyoid'], + 'hyothyroid': ['hyothyroid', 'thyrohyoid'], + 'hypaethron': ['hypaethron', 'hypothenar'], + 'hypercone': ['coryphene', 'hypercone'], + 'hypergamous': ['hypergamous', 'museography'], + 'hypertoxic': ['hypertoxic', 'xerophytic'], + 'hypnobate': ['batyphone', 'hypnobate'], + 'hypnoetic': ['hypnoetic', 'neophytic'], + 'hypnotic': ['hypnotic', 'phytonic', 'pythonic', 'typhonic'], + 'hypnotism': ['hypnotism', 'pythonism'], + 'hypnotist': ['hypnotist', 'pythonist'], + 'hypnotize': ['hypnotize', 'pythonize'], + 'hypnotoid': ['hypnotoid', 'pythonoid'], + 'hypobole': ['hypobole', 'lyophobe'], + 'hypocarp': ['apocryph', 'hypocarp'], + 'hypocrite': ['chirotype', 'hypocrite'], + 'hypodorian': ['hypodorian', 'radiophony'], + 'hypoglottis': ['hypoglottis', 'phytologist'], + 'hypomanic': ['amphicyon', 'hypomanic'], + 'hypopteron': ['hypopteron', 'phonotyper'], + 'hyporadius': ['hyporadius', 'suprahyoid'], + 'hyposcleral': ['hyposcleral', 'phylloceras'], + 'hyposmia': ['hyposmia', 'phymosia'], + 'hypostomatic': ['hypostomatic', 'somatophytic'], + 'hypothec': ['hypothec', 'photechy'], + 'hypothenar': ['hypaethron', 'hypothenar'], + 'hypothermia': ['hemiatrophy', 'hypothermia'], + 'hypsiloid': ['hypsiloid', 'syphiloid'], + 'hyracid': ['diarchy', 'hyracid'], + 'hyssop': ['hyssop', 'phossy', 'sposhy'], + 'hysteresial': ['hysteresial', 'hysteriales'], + 'hysteria': ['hysteria', 'sheriyat'], + 'hysteriales': ['hysteresial', 'hysteriales'], + 'hysterolaparotomy': ['hysterolaparotomy', 'laparohysterotomy'], + 'hysteromyomectomy': ['hysteromyomectomy', 'myomohysterectomy'], + 'hysteropathy': ['hysteropathy', 'hysterophyta'], + 'hysterophyta': ['hysteropathy', 'hysterophyta'], + 'iamb': ['iamb', 'mabi'], + 'iambelegus': ['elegiambus', 'iambelegus'], + 'iambic': ['cimbia', 'iambic'], + 'ian': ['ani', 'ian'], + 'ianus': ['ianus', 'suina'], + 'iatraliptics': ['iatraliptics', 'partialistic'], + 'iatric': ['iatric', 'tricia'], + 'ibad': ['adib', 'ibad'], + 'iban': ['bain', 'bani', 'iban'], + 'ibanag': ['bagani', 'bangia', 'ibanag'], + 'iberian': ['aribine', 'bairnie', 'iberian'], + 'ibo': ['ibo', 'obi'], + 'ibota': ['biota', 'ibota'], + 'icacorea': ['coraciae', 'icacorea'], + 'icarian': ['arician', 'icarian'], + 'icecap': ['icecap', 'ipecac'], + 'iced': ['dice', 'iced'], + 'iceland': ['cladine', 'decalin', 'iceland'], + 'icelandic': ['cicindela', 'cinclidae', 'icelandic'], + 'iceman': ['anemic', 'cinema', 'iceman'], + 'ich': ['chi', 'hic', 'ich'], + 'ichnolite': ['ichnolite', 'neolithic'], + 'ichor': ['chiro', 'choir', 'ichor'], + 'icicle': ['cilice', 'icicle'], + 'icon': ['cion', 'coin', 'icon'], + 'iconian': ['anionic', 'iconian'], + 'iconism': ['iconism', 'imsonic', 'miscoin'], + 'iconolater': ['iconolater', 'relocation'], + 'iconomania': ['iconomania', 'oniomaniac'], + 'iconometrical': ['iconometrical', 'intracoelomic'], + 'icteridae': ['diaeretic', 'icteridae'], + 'icterine': ['icterine', 'reincite'], + 'icterus': ['curtise', 'icterus'], + 'ictonyx': ['ictonyx', 'oxyntic'], + 'ictus': ['cutis', 'ictus'], + 'id': ['di', 'id'], + 'ida': ['aid', 'ida'], + 'idaean': ['adenia', 'idaean'], + 'ide': ['die', 'ide'], + 'idea': ['aide', 'idea'], + 'ideal': ['adiel', 'delia', 'ideal'], + 'idealism': ['idealism', 'lamiides'], + 'idealistic': ['disilicate', 'idealistic'], + 'ideality': ['aedility', 'ideality'], + 'idealness': ['idealness', 'leadiness'], + 'idean': ['diane', 'idean'], + 'ideation': ['ideation', 'iodinate', 'taenioid'], + 'identical': ['ctenidial', 'identical'], + 'ideograph': ['eidograph', 'ideograph'], + 'ideology': ['eidology', 'ideology'], + 'ideoplasty': ['ideoplasty', 'stylopidae'], + 'ides': ['desi', 'ides', 'seid', 'side'], + 'idiasm': ['idiasm', 'simiad'], + 'idioblast': ['diabolist', 'idioblast'], + 'idiomology': ['idiomology', 'oligomyoid'], + 'idioretinal': ['idioretinal', 'litorinidae'], + 'idiotish': ['histioid', 'idiotish'], + 'idle': ['idle', 'lide', 'lied'], + 'idleman': ['idleman', 'melinda'], + 'idleset': ['idleset', 'isleted'], + 'idlety': ['idlety', 'lydite', 'tidely', 'tidley'], + 'idly': ['idly', 'idyl'], + 'idocrase': ['idocrase', 'radicose'], + 'idoism': ['idoism', 'iodism'], + 'idol': ['dilo', 'diol', 'doli', 'idol', 'olid'], + 'idola': ['aloid', 'dolia', 'idola'], + 'idolaster': ['estradiol', 'idolaster'], + 'idolatry': ['adroitly', 'dilatory', 'idolatry'], + 'idolum': ['dolium', 'idolum'], + 'idoneal': ['adinole', 'idoneal'], + 'idorgan': ['gordian', 'idorgan', 'roading'], + 'idose': ['diose', 'idose', 'oside'], + 'idotea': ['idotea', 'iodate', 'otidae'], + 'idryl': ['idryl', 'lyrid'], + 'idyl': ['idly', 'idyl'], + 'idyler': ['direly', 'idyler'], + 'ierne': ['ernie', 'ierne', 'irene'], + 'if': ['fi', 'if'], + 'ife': ['fei', 'fie', 'ife'], + 'igara': ['agria', 'igara'], + 'igdyr': ['igdyr', 'ridgy'], + 'igloo': ['igloo', 'logoi'], + 'ignatius': ['giustina', 'ignatius'], + 'igneoaqueous': ['aqueoigneous', 'igneoaqueous'], + 'ignicolist': ['ignicolist', 'soliciting'], + 'igniter': ['igniter', 'ringite', 'tigrine'], + 'ignitor': ['ignitor', 'rioting'], + 'ignoble': ['gobelin', 'gobline', 'ignoble', 'inglobe'], + 'ignoramus': ['graminous', 'ignoramus'], + 'ignorance': ['enorganic', 'ignorance'], + 'ignorant': ['ignorant', 'tongrian'], + 'ignore': ['ignore', 'region'], + 'ignorement': ['ignorement', 'omnigerent'], + 'iguana': ['guiana', 'iguana'], + 'ihlat': ['ihlat', 'tahil'], + 'ihram': ['hiram', 'ihram', 'mahri'], + 'ijma': ['ijma', 'jami'], + 'ikat': ['atik', 'ikat'], + 'ikona': ['ikona', 'konia'], + 'ikra': ['ikra', 'kari', 'raki'], + 'ila': ['ail', 'ila', 'lai'], + 'ileac': ['alice', 'celia', 'ileac'], + 'ileon': ['enoil', 'ileon', 'olein'], + 'iliac': ['cilia', 'iliac'], + 'iliacus': ['acilius', 'iliacus'], + 'ilian': ['ilian', 'inial'], + 'ilicaceae': ['caeciliae', 'ilicaceae'], + 'ilioischiac': ['ilioischiac', 'ischioiliac'], + 'iliosacral': ['iliosacral', 'oscillaria'], + 'ilk': ['ilk', 'kil'], + 'ilka': ['ilka', 'kail', 'kali'], + 'ilkane': ['alkine', 'ilkane', 'inlake', 'inleak'], + 'illative': ['illative', 'veiltail'], + 'illaudatory': ['illaudatory', 'laudatorily'], + 'illeck': ['ellick', 'illeck'], + 'illinois': ['illinois', 'illision'], + 'illision': ['illinois', 'illision'], + 'illium': ['illium', 'lilium'], + 'illoricated': ['illoricated', 'lacertiloid'], + 'illth': ['illth', 'thill'], + 'illude': ['dillue', 'illude'], + 'illuder': ['dilluer', 'illuder'], + 'illy': ['illy', 'lily', 'yill'], + 'ilmenite': ['ilmenite', 'melinite', 'menilite'], + 'ilongot': ['ilongot', 'tooling'], + 'ilot': ['ilot', 'toil'], + 'ilya': ['ilya', 'yali'], + 'ima': ['aim', 'ami', 'ima'], + 'imager': ['imager', 'maigre', 'margie', 'mirage'], + 'imaginant': ['animating', 'imaginant'], + 'imaginer': ['imaginer', 'migraine'], + 'imagist': ['imagist', 'stigmai'], + 'imago': ['amigo', 'imago'], + 'imam': ['ammi', 'imam', 'maim', 'mima'], + 'imaret': ['imaret', 'metria', 'mirate', 'rimate'], + 'imbarge': ['gambier', 'imbarge'], + 'imbark': ['bikram', 'imbark'], + 'imbat': ['ambit', 'imbat'], + 'imbed': ['bedim', 'imbed'], + 'imbrue': ['erbium', 'imbrue'], + 'imbrute': ['burmite', 'imbrute', 'terbium'], + 'imer': ['emir', 'imer', 'mire', 'reim', 'remi', 'riem', 'rime'], + 'imerina': ['imerina', 'inermia'], + 'imitancy': ['imitancy', 'intimacy', 'minacity'], + 'immane': ['ammine', 'immane'], + 'immanes': ['amenism', 'immanes', 'misname'], + 'immaterials': ['immaterials', 'materialism'], + 'immerd': ['dimmer', 'immerd', 'rimmed'], + 'immersible': ['immersible', 'semilimber'], + 'immersion': ['immersion', 'semiminor'], + 'immi': ['immi', 'mimi'], + 'imogen': ['geonim', 'imogen'], + 'imolinda': ['dominial', 'imolinda', 'limoniad'], + 'imp': ['imp', 'pim'], + 'impaction': ['impaction', 'ptomainic'], + 'impages': ['impages', 'mispage'], + 'impaint': ['impaint', 'timpani'], + 'impair': ['impair', 'pamiri'], + 'impala': ['impala', 'malapi'], + 'impaler': ['impaler', 'impearl', 'lempira', 'premial'], + 'impalsy': ['impalsy', 'misplay'], + 'impane': ['impane', 'pieman'], + 'impanel': ['impanel', 'maniple'], + 'impar': ['impar', 'pamir', 'prima'], + 'imparalleled': ['demiparallel', 'imparalleled'], + 'imparl': ['imparl', 'primal'], + 'impart': ['armpit', 'impart'], + 'imparter': ['imparter', 'reimpart'], + 'impartial': ['impartial', 'primatial'], + 'impaste': ['impaste', 'pastime'], + 'impasture': ['impasture', 'septarium'], + 'impeach': ['aphemic', 'impeach'], + 'impearl': ['impaler', 'impearl', 'lempira', 'premial'], + 'impeder': ['demirep', 'epiderm', 'impeder', 'remiped'], + 'impedient': ['impedient', 'mendipite'], + 'impenetrable': ['impenetrable', 'intemperable'], + 'impenetrably': ['impenetrably', 'intemperably'], + 'impenetrate': ['impenetrate', 'intemperate'], + 'imperant': ['imperant', 'pairment', 'partimen', 'premiant', 'tripeman'], + 'imperate': ['imperate', 'premiate'], + 'imperish': ['emirship', 'imperish'], + 'imperscriptible': ['imperscriptible', 'imprescriptible'], + 'impersonate': ['impersonate', 'proseminate'], + 'impersonation': ['impersonation', 'prosemination', 'semipronation'], + 'impeticos': ['impeticos', 'poeticism'], + 'impetre': ['emptier', 'impetre'], + 'impetus': ['impetus', 'upsmite'], + 'imphee': ['imphee', 'phemie'], + 'implacental': ['capillament', 'implacental'], + 'implanter': ['implanter', 'reimplant'], + 'implate': ['implate', 'palmite'], + 'impleader': ['epidermal', 'impleader', 'premedial'], + 'implicate': ['ampelitic', 'implicate'], + 'impling': ['impling', 'limping'], + 'imply': ['imply', 'limpy', 'pilmy'], + 'impollute': ['impollute', 'multipole'], + 'imponderous': ['endosporium', 'imponderous'], + 'imponent': ['imponent', 'pimenton'], + 'importable': ['bitemporal', 'importable'], + 'importancy': ['importancy', 'patronymic', 'pyromantic'], + 'importer': ['importer', 'promerit', 'reimport'], + 'importunance': ['importunance', 'unimportance'], + 'importunate': ['importunate', 'permutation'], + 'importune': ['entropium', 'importune'], + 'imposal': ['imposal', 'spiloma'], + 'imposer': ['imposer', 'promise', 'semipro'], + 'imposter': ['imposter', 'tripsome'], + 'imposure': ['imposure', 'premious'], + 'imprecatory': ['cryptomeria', 'imprecatory'], + 'impreg': ['gimper', 'impreg'], + 'imprescriptible': ['imperscriptible', 'imprescriptible'], + 'imprese': ['emprise', 'imprese', 'premise', 'spireme'], + 'impress': ['impress', 'persism', 'premiss'], + 'impresser': ['impresser', 'reimpress'], + 'impressibility': ['impressibility', 'permissibility'], + 'impressible': ['impressible', 'permissible'], + 'impressibleness': ['impressibleness', 'permissibleness'], + 'impressibly': ['impressibly', 'permissibly'], + 'impression': ['impression', 'permission'], + 'impressionism': ['impressionism', 'misimpression'], + 'impressive': ['impressive', 'permissive'], + 'impressively': ['impressively', 'permissively'], + 'impressiveness': ['impressiveness', 'permissiveness'], + 'impressure': ['impressure', 'presurmise'], + 'imprinter': ['imprinter', 'reimprint'], + 'imprisoner': ['imprisoner', 'reimprison'], + 'improcreant': ['improcreant', 'preromantic'], + 'impship': ['impship', 'pimpish'], + 'impuberal': ['epilabrum', 'impuberal'], + 'impugnable': ['impugnable', 'plumbagine'], + 'impure': ['impure', 'umpire'], + 'impuritan': ['impuritan', 'partinium'], + 'imputer': ['imputer', 'trumpie'], + 'imsonic': ['iconism', 'imsonic', 'miscoin'], + 'in': ['in', 'ni'], + 'inaction': ['aconitin', 'inaction', 'nicotian'], + 'inactivate': ['inactivate', 'vaticinate'], + 'inactivation': ['inactivation', 'vaticination'], + 'inactive': ['antivice', 'inactive', 'vineatic'], + 'inadept': ['depaint', 'inadept', 'painted', 'patined'], + 'inaja': ['inaja', 'jaina'], + 'inalimental': ['antimallein', 'inalimental'], + 'inamorata': ['amatorian', 'inamorata'], + 'inane': ['annie', 'inane'], + 'inanga': ['angina', 'inanga'], + 'inanimate': ['amanitine', 'inanimate'], + 'inanimated': ['diamantine', 'inanimated'], + 'inapt': ['inapt', 'paint', 'pinta'], + 'inaptly': ['inaptly', 'planity', 'ptyalin'], + 'inarch': ['chinar', 'inarch'], + 'inarm': ['inarm', 'minar'], + 'inasmuch': ['humanics', 'inasmuch'], + 'inaurate': ['inaurate', 'ituraean'], + 'inbe': ['beni', 'bien', 'bine', 'inbe'], + 'inbreak': ['brankie', 'inbreak'], + 'inbreathe': ['hibernate', 'inbreathe'], + 'inbred': ['binder', 'inbred', 'rebind'], + 'inbreed': ['birdeen', 'inbreed'], + 'inca': ['cain', 'inca'], + 'incaic': ['acinic', 'incaic'], + 'incarnate': ['cratinean', 'incarnate', 'nectarian'], + 'incase': ['casein', 'incase'], + 'incast': ['incast', 'nastic'], + 'incensation': ['incensation', 'inscenation'], + 'incept': ['incept', 'pectin'], + 'inceptor': ['inceptor', 'pretonic'], + 'inceration': ['cineration', 'inceration'], + 'incessant': ['anticness', 'cantiness', 'incessant'], + 'incest': ['encist', 'incest', 'insect', 'scient'], + 'inch': ['chin', 'inch'], + 'inched': ['chined', 'inched'], + 'inchoate': ['inchoate', 'noachite'], + 'incide': ['cindie', 'incide'], + 'incinerate': ['creatinine', 'incinerate'], + 'incisal': ['incisal', 'salicin'], + 'incision': ['incision', 'inosinic'], + 'incisure': ['incisure', 'sciurine'], + 'inciter': ['citrine', 'crinite', 'inciter', 'neritic'], + 'inclinatorium': ['anticlinorium', 'inclinatorium'], + 'inclosure': ['cornelius', 'inclosure', 'reclusion'], + 'include': ['include', 'nuclide'], + 'incluse': ['esculin', 'incluse'], + 'incog': ['coign', 'incog'], + 'incognito': ['cognition', 'incognito'], + 'incoherence': ['coinherence', 'incoherence'], + 'incoherent': ['coinherent', 'incoherent'], + 'incomeless': ['comeliness', 'incomeless'], + 'incomer': ['incomer', 'moneric'], + 'incomputable': ['incomputable', 'uncompatible'], + 'incondite': ['incondite', 'nicotined'], + 'inconglomerate': ['inconglomerate', 'nongeometrical'], + 'inconsistent': ['inconsistent', 'nonscientist'], + 'inconsonant': ['inconsonant', 'nonsanction'], + 'incontrovertibility': ['incontrovertibility', 'introconvertibility'], + 'incontrovertible': ['incontrovertible', 'introconvertible'], + 'incorporate': ['incorporate', 'procreation'], + 'incorporated': ['adrenotropic', 'incorporated'], + 'incorpse': ['conspire', 'incorpse'], + 'incrash': ['archsin', 'incrash'], + 'increase': ['cerasein', 'increase'], + 'increate': ['aneretic', 'centiare', 'creatine', 'increate', 'iterance'], + 'incredited': ['incredited', 'indirected'], + 'increep': ['crepine', 'increep'], + 'increpate': ['anticreep', 'apenteric', 'increpate'], + 'increst': ['cistern', 'increst'], + 'incruental': ['incruental', 'unicentral'], + 'incrustant': ['incrustant', 'scrutinant'], + 'incrustate': ['incrustate', 'scaturient', 'scrutinate'], + 'incubate': ['cubanite', 'incubate'], + 'incudal': ['dulcian', 'incudal', 'lucanid', 'lucinda'], + 'incudomalleal': ['incudomalleal', 'malleoincudal'], + 'inculcation': ['anticouncil', 'inculcation'], + 'inculture': ['culturine', 'inculture'], + 'incuneation': ['enunciation', 'incuneation'], + 'incur': ['curin', 'incur', 'runic'], + 'incurable': ['binuclear', 'incurable'], + 'incus': ['incus', 'usnic'], + 'incut': ['cutin', 'incut', 'tunic'], + 'ind': ['din', 'ind', 'nid'], + 'indaba': ['badian', 'indaba'], + 'indagator': ['gradation', 'indagator', 'tanagroid'], + 'indan': ['indan', 'nandi'], + 'indane': ['aidenn', 'andine', 'dannie', 'indane'], + 'inde': ['dine', 'enid', 'inde', 'nide'], + 'indebt': ['bident', 'indebt'], + 'indebted': ['bidented', 'indebted'], + 'indefinitude': ['indefinitude', 'unidentified'], + 'indent': ['dentin', 'indent', 'intend', 'tinned'], + 'indented': ['indented', 'intended'], + 'indentedly': ['indentedly', 'intendedly'], + 'indenter': ['indenter', 'intender', 'reintend'], + 'indentment': ['indentment', 'intendment'], + 'indentured': ['indentured', 'underntide'], + 'indentwise': ['disentwine', 'indentwise'], + 'indeprivable': ['indeprivable', 'predivinable'], + 'indesert': ['indesert', 'inserted', 'resident'], + 'indiana': ['anidian', 'indiana'], + 'indic': ['dinic', 'indic'], + 'indican': ['cnidian', 'indican'], + 'indicate': ['diacetin', 'indicate'], + 'indicatory': ['dictionary', 'indicatory'], + 'indicial': ['anilidic', 'indicial'], + 'indicter': ['indicter', 'indirect', 'reindict'], + 'indies': ['indies', 'inside'], + 'indigena': ['gadinine', 'indigena'], + 'indigitate': ['indigitate', 'tingitidae'], + 'indign': ['dining', 'indign', 'niding'], + 'indigoferous': ['gonidiferous', 'indigoferous'], + 'indigotin': ['digitonin', 'indigotin'], + 'indirect': ['indicter', 'indirect', 'reindict'], + 'indirected': ['incredited', 'indirected'], + 'indirectly': ['cylindrite', 'indirectly'], + 'indiscreet': ['indiscreet', 'indiscrete', 'iridescent'], + 'indiscreetly': ['indiscreetly', 'indiscretely', 'iridescently'], + 'indiscrete': ['indiscreet', 'indiscrete', 'iridescent'], + 'indiscretely': ['indiscreetly', 'indiscretely', 'iridescently'], + 'indissolute': ['delusionist', 'indissolute'], + 'indite': ['indite', 'tineid'], + 'inditer': ['inditer', 'nitride'], + 'indogaean': ['ganoidean', 'indogaean'], + 'indole': ['doline', 'indole', 'leonid', 'loined', 'olenid'], + 'indolence': ['endocline', 'indolence'], + 'indoles': ['indoles', 'sondeli'], + 'indologist': ['indologist', 'nidologist'], + 'indology': ['indology', 'nidology'], + 'indone': ['donnie', 'indone', 'ondine'], + 'indoors': ['indoors', 'sordino'], + 'indorse': ['indorse', 'ordines', 'siredon', 'sordine'], + 'indra': ['darin', 'dinar', 'drain', 'indra', 'nadir', 'ranid'], + 'indrawn': ['indrawn', 'winnard'], + 'induce': ['induce', 'uniced'], + 'inducer': ['inducer', 'uncried'], + 'indulge': ['dueling', 'indulge'], + 'indulgential': ['dentilingual', 'indulgential', 'linguidental'], + 'indulger': ['indulger', 'ungirdle'], + 'indument': ['indument', 'unminted'], + 'indurable': ['indurable', 'unbrailed', 'unridable'], + 'indurate': ['indurate', 'turdinae'], + 'induration': ['diurnation', 'induration'], + 'indus': ['dinus', 'indus', 'nidus'], + 'indusiform': ['disuniform', 'indusiform'], + 'induviae': ['induviae', 'viduinae'], + 'induvial': ['diluvian', 'induvial'], + 'inearth': ['anither', 'inearth', 'naither'], + 'inelastic': ['elasticin', 'inelastic', 'sciential'], + 'inelegant': ['eglantine', 'inelegant', 'legantine'], + 'ineludible': ['ineludible', 'unelidible'], + 'inept': ['inept', 'pinte'], + 'inequity': ['equinity', 'inequity'], + 'inerm': ['inerm', 'miner'], + 'inermes': ['ermines', 'inermes'], + 'inermia': ['imerina', 'inermia'], + 'inermous': ['inermous', 'monsieur'], + 'inert': ['inert', 'inter', 'niter', 'retin', 'trine'], + 'inertance': ['inertance', 'nectarine'], + 'inertial': ['inertial', 'linarite'], + 'inertly': ['elytrin', 'inertly', 'trinely'], + 'inethical': ['echinital', 'inethical'], + 'ineunt': ['ineunt', 'untine'], + 'inez': ['inez', 'zein'], + 'inface': ['fiance', 'inface'], + 'infame': ['famine', 'infame'], + 'infamy': ['infamy', 'manify'], + 'infarct': ['frantic', 'infarct', 'infract'], + 'infarction': ['infarction', 'infraction'], + 'infaust': ['faunist', 'fustian', 'infaust'], + 'infecter': ['frenetic', 'infecter', 'reinfect'], + 'infeed': ['define', 'infeed'], + 'infelt': ['finlet', 'infelt'], + 'infer': ['finer', 'infer'], + 'inferable': ['inferable', 'refinable'], + 'infern': ['finner', 'infern'], + 'inferoanterior': ['anteroinferior', 'inferoanterior'], + 'inferoposterior': ['inferoposterior', 'posteroinferior'], + 'infestation': ['festination', 'infestation', 'sinfonietta'], + 'infester': ['infester', 'reinfest'], + 'infidel': ['infidel', 'infield'], + 'infield': ['infidel', 'infield'], + 'inflame': ['feminal', 'inflame'], + 'inflamed': ['fieldman', 'inflamed'], + 'inflamer': ['inflamer', 'rifleman'], + 'inflatus': ['inflatus', 'stainful'], + 'inflicter': ['inflicter', 'reinflict'], + 'inform': ['formin', 'inform'], + 'informal': ['formalin', 'informal', 'laniform'], + 'informer': ['informer', 'reinform', 'reniform'], + 'infra': ['infra', 'irfan'], + 'infract': ['frantic', 'infarct', 'infract'], + 'infraction': ['infarction', 'infraction'], + 'infringe': ['infringe', 'refining'], + 'ing': ['gin', 'ing', 'nig'], + 'inga': ['gain', 'inga', 'naig', 'ngai'], + 'ingaevones': ['avignonese', 'ingaevones'], + 'ingate': ['eating', 'ingate', 'tangie'], + 'ingather': ['hearting', 'ingather'], + 'ingenue': ['genuine', 'ingenue'], + 'ingenuous': ['ingenuous', 'unigenous'], + 'inger': ['grein', 'inger', 'nigre', 'regin', 'reign', 'ringe'], + 'ingest': ['ingest', 'signet', 'stinge'], + 'ingesta': ['easting', + 'gainset', + 'genista', + 'ingesta', + 'seating', + 'signate', + 'teasing'], + 'ingle': ['ingle', 'ligne', 'linge', 'nigel'], + 'inglobe': ['gobelin', 'gobline', 'ignoble', 'inglobe'], + 'ingomar': ['ingomar', 'moringa', 'roaming'], + 'ingram': ['arming', 'ingram', 'margin'], + 'ingrate': ['angrite', 'granite', 'ingrate', 'tangier', 'tearing', 'tigrean'], + 'ingrow': ['ingrow', 'rowing'], + 'ingrowth': ['ingrowth', 'throwing'], + 'inguinal': ['inguinal', 'unailing'], + 'inguinocrural': ['cruroinguinal', 'inguinocrural'], + 'inhabiter': ['inhabiter', 'reinhabit'], + 'inhaler': ['hernial', 'inhaler'], + 'inhauler': ['herulian', 'inhauler'], + 'inhaust': ['auntish', 'inhaust'], + 'inhere': ['herein', 'inhere'], + 'inhumer': ['inhumer', 'rhenium'], + 'inial': ['ilian', 'inial'], + 'ink': ['ink', 'kin'], + 'inkle': ['inkle', 'liken'], + 'inkless': ['inkless', 'kinless'], + 'inkling': ['inkling', 'linking'], + 'inknot': ['inknot', 'tonkin'], + 'inkra': ['inkra', 'krina', 'nakir', 'rinka'], + 'inks': ['inks', 'sink', 'skin'], + 'inlaid': ['anilid', 'dialin', 'dianil', 'inlaid'], + 'inlake': ['alkine', 'ilkane', 'inlake', 'inleak'], + 'inlaut': ['inlaut', 'unital'], + 'inlaw': ['inlaw', 'liwan'], + 'inlay': ['inlay', 'naily'], + 'inlayer': ['inlayer', 'nailery'], + 'inleak': ['alkine', 'ilkane', 'inlake', 'inleak'], + 'inlet': ['inlet', 'linet'], + 'inlook': ['inlook', 'koilon'], + 'inly': ['inly', 'liny'], + 'inmate': ['etamin', 'inmate', 'taimen', 'tamein'], + 'inmeats': ['atenism', 'inmeats', 'insteam', 'samnite'], + 'inmost': ['inmost', 'monist', 'omnist'], + 'innate': ['annite', 'innate', 'tinean'], + 'innative': ['innative', 'invinate'], + 'innatural': ['innatural', 'triannual'], + 'inner': ['inner', 'renin'], + 'innerve': ['innerve', 'nervine', 'vernine'], + 'innest': ['innest', 'sennit', 'sinnet', 'tennis'], + 'innet': ['innet', 'tinne'], + 'innominata': ['antinomian', 'innominata'], + 'innovate': ['innovate', 'venation'], + 'innovationist': ['innovationist', 'nonvisitation'], + 'ino': ['ino', 'ion'], + 'inobtainable': ['inobtainable', 'nonbilabiate'], + 'inocarpus': ['inocarpus', 'unprosaic'], + 'inoculant': ['continual', 'inoculant', 'unctional'], + 'inocystoma': ['actomyosin', 'inocystoma'], + 'inodes': ['deinos', 'donsie', 'inodes', 'onside'], + 'inogen': ['genion', 'inogen'], + 'inoma': ['amino', 'inoma', 'naomi', 'omani', 'omina'], + 'inomyxoma': ['inomyxoma', 'myxoinoma'], + 'inone': ['inone', 'oenin'], + 'inoperculata': ['inoperculata', 'precautional'], + 'inorb': ['biron', 'inorb', 'robin'], + 'inorganic': ['conringia', 'inorganic'], + 'inorganical': ['carolingian', 'inorganical'], + 'inornate': ['anointer', 'inornate', 'nonirate', 'reanoint'], + 'inosic': ['inosic', 'sinico'], + 'inosinic': ['incision', 'inosinic'], + 'inosite': ['inosite', 'sionite'], + 'inphase': ['inphase', 'phineas'], + 'inpush': ['inpush', 'punish', 'unship'], + 'input': ['input', 'punti'], + 'inquiet': ['inquiet', 'quinite'], + 'inreality': ['inreality', 'linearity'], + 'inro': ['inro', 'iron', 'noir', 'nori'], + 'inroad': ['dorian', 'inroad', 'ordain'], + 'inroader': ['inroader', 'ordainer', 'reordain'], + 'inrub': ['bruin', 'burin', 'inrub'], + 'inrun': ['inrun', 'inurn'], + 'insane': ['insane', 'sienna'], + 'insatiably': ['insatiably', 'sanability'], + 'inscenation': ['incensation', 'inscenation'], + 'inscient': ['inscient', 'nicenist'], + 'insculp': ['insculp', 'sculpin'], + 'insea': ['anise', 'insea', 'siena', 'sinae'], + 'inseam': ['asimen', 'inseam', 'mesian'], + 'insect': ['encist', 'incest', 'insect', 'scient'], + 'insectan': ['insectan', 'instance'], + 'insectile': ['insectile', 'selenitic'], + 'insectivora': ['insectivora', 'visceration'], + 'insecure': ['insecure', 'sinecure'], + 'insee': ['insee', 'seine'], + 'inseer': ['inseer', 'nereis', 'seiner', 'serine', 'sirene'], + 'insert': ['estrin', 'insert', 'sinter', 'sterin', 'triens'], + 'inserted': ['indesert', 'inserted', 'resident'], + 'inserter': ['inserter', 'reinsert'], + 'insessor': ['insessor', 'rosiness'], + 'inset': ['inset', 'neist', 'snite', 'stein', 'stine', 'tsine'], + 'insetter': ['insetter', 'interest', 'interset', 'sternite'], + 'inshave': ['evanish', 'inshave'], + 'inshoot': ['inshoot', 'insooth'], + 'inside': ['indies', 'inside'], + 'insider': ['insider', 'siderin'], + 'insistent': ['insistent', 'tintiness'], + 'insister': ['insister', 'reinsist', 'sinister', 'sisterin'], + 'insole': ['insole', 'leonis', 'lesion', 'selion'], + 'insomnia': ['insomnia', 'simonian'], + 'insomniac': ['aniconism', 'insomniac'], + 'insooth': ['inshoot', 'insooth'], + 'insorb': ['insorb', 'sorbin'], + 'insoul': ['insoul', 'linous', 'nilous', 'unsoil'], + 'inspection': ['cispontine', 'inspection'], + 'inspiriter': ['inspiriter', 'reinspirit'], + 'inspissate': ['antisepsis', 'inspissate'], + 'inspreith': ['inspreith', 'nephritis', 'phrenitis'], + 'installer': ['installer', 'reinstall'], + 'instance': ['insectan', 'instance'], + 'instanter': ['instanter', 'transient'], + 'instar': ['instar', 'santir', 'strain'], + 'instate': ['atenist', 'instate', 'satient', 'steatin'], + 'instead': ['destain', 'instead', 'sainted', 'satined'], + 'insteam': ['atenism', 'inmeats', 'insteam', 'samnite'], + 'instep': ['instep', 'spinet'], + 'instiller': ['instiller', 'reinstill'], + 'instructer': ['instructer', 'intercrust', 'reinstruct'], + 'instructional': ['instructional', 'nonaltruistic'], + 'insula': ['insula', 'lanius', 'lusian'], + 'insulant': ['insulant', 'sultanin'], + 'insulse': ['insulse', 'silenus'], + 'insult': ['insult', 'sunlit', 'unlist', 'unslit'], + 'insulter': ['insulter', 'lustrine', 'reinsult'], + 'insunk': ['insunk', 'unskin'], + 'insurable': ['insurable', 'sublinear'], + 'insurance': ['insurance', 'nuisancer'], + 'insurant': ['insurant', 'unstrain'], + 'insure': ['insure', 'rusine', 'ursine'], + 'insurge': ['insurge', 'resuing'], + 'insurgent': ['insurgent', 'unresting'], + 'intactile': ['catlinite', 'intactile'], + 'intaglio': ['intaglio', 'ligation'], + 'intake': ['intake', 'kentia'], + 'intaker': ['intaker', 'katrine', 'keratin'], + 'intarsia': ['antiaris', 'intarsia'], + 'intarsiate': ['intarsiate', 'nestiatria'], + 'integral': ['integral', 'teraglin', 'triangle'], + 'integralize': ['gelatinizer', 'integralize'], + 'integrate': ['argentite', 'integrate'], + 'integrative': ['integrative', 'vertiginate', 'vinaigrette'], + 'integrious': ['grisounite', 'grisoutine', 'integrious'], + 'intemperable': ['impenetrable', 'intemperable'], + 'intemperably': ['impenetrably', 'intemperably'], + 'intemperate': ['impenetrate', 'intemperate'], + 'intemporal': ['intemporal', 'trampoline'], + 'intend': ['dentin', 'indent', 'intend', 'tinned'], + 'intended': ['indented', 'intended'], + 'intendedly': ['indentedly', 'intendedly'], + 'intender': ['indenter', 'intender', 'reintend'], + 'intendment': ['indentment', 'intendment'], + 'intense': ['intense', 'sennite'], + 'intent': ['intent', 'tinnet'], + 'intently': ['intently', 'nitently'], + 'inter': ['inert', 'inter', 'niter', 'retin', 'trine'], + 'interactional': ['interactional', 'intercalation'], + 'interagent': ['entreating', 'interagent'], + 'interally': ['interally', 'reliantly'], + 'interastral': ['interastral', 'intertarsal'], + 'intercalation': ['interactional', 'intercalation'], + 'intercale': ['intercale', 'interlace', 'lacertine', 'reclinate'], + 'intercede': ['intercede', 'tridecene'], + 'interceder': ['crednerite', 'interceder'], + 'intercession': ['intercession', 'recensionist'], + 'intercome': ['entomeric', 'intercome', 'morencite'], + 'interconal': ['interconal', 'nonrecital'], + 'intercrust': ['instructer', 'intercrust', 'reinstruct'], + 'interdome': ['interdome', 'mordenite', 'nemertoid'], + 'intereat': ['intereat', 'tinetare'], + 'interest': ['insetter', 'interest', 'interset', 'sternite'], + 'interester': ['interester', 'reinterest'], + 'interfering': ['interfering', 'interfinger'], + 'interfinger': ['interfering', 'interfinger'], + 'intergrade': ['gradienter', 'intergrade'], + 'interim': ['interim', 'termini'], + 'interimistic': ['interimistic', 'trimesitinic'], + 'interlace': ['intercale', 'interlace', 'lacertine', 'reclinate'], + 'interlaced': ['credential', 'interlaced', 'reclinated'], + 'interlaid': ['deliriant', 'draintile', 'interlaid'], + 'interlap': ['interlap', 'repliant', 'triplane'], + 'interlapse': ['alpestrine', 'episternal', 'interlapse', 'presential'], + 'interlay': ['interlay', 'lyterian'], + 'interleaf': ['interleaf', 'reinflate'], + 'interleaver': ['interleaver', 'reverential'], + 'interlocal': ['citronella', 'interlocal'], + 'interlope': ['interlope', 'interpole', 'repletion', 'terpineol'], + 'interlot': ['interlot', 'trotline'], + 'intermat': ['intermat', 'martinet', 'tetramin'], + 'intermatch': ['intermatch', 'thermantic'], + 'intermine': ['intermine', 'nemertini', 'terminine'], + 'intermorainic': ['intermorainic', 'recrimination'], + 'intermutual': ['intermutual', 'ultraminute'], + 'intern': ['intern', 'tinner'], + 'internality': ['internality', 'itinerantly'], + 'internecive': ['internecive', 'reincentive'], + 'internee': ['internee', 'retinene'], + 'interoceptor': ['interoceptor', 'reprotection'], + 'interpause': ['interpause', 'resupinate'], + 'interpave': ['interpave', 'prenative'], + 'interpeal': ['interpeal', 'interplea'], + 'interpellate': ['interpellate', 'pantellerite'], + 'interpellation': ['interpellation', 'interpollinate'], + 'interphone': ['interphone', 'pinnothere'], + 'interplay': ['interplay', 'painterly'], + 'interplea': ['interpeal', 'interplea'], + 'interplead': ['interplead', 'peridental'], + 'interpolar': ['interpolar', 'reniportal'], + 'interpolate': ['interpolate', 'triantelope'], + 'interpole': ['interlope', 'interpole', 'repletion', 'terpineol'], + 'interpollinate': ['interpellation', 'interpollinate'], + 'interpone': ['interpone', 'peritenon', 'pinnotere', 'preintone'], + 'interposal': ['interposal', 'psalterion'], + 'interposure': ['interposure', 'neuropteris'], + 'interpreter': ['interpreter', 'reinterpret'], + 'interproduce': ['interproduce', 'prereduction'], + 'interroom': ['interroom', 'remontoir'], + 'interrupter': ['interrupter', 'reinterrupt'], + 'intersale': ['intersale', 'larsenite'], + 'intersectional': ['intersectional', 'intraselection'], + 'interset': ['insetter', 'interest', 'interset', 'sternite'], + 'intershade': ['dishearten', 'intershade'], + 'intersituate': ['intersituate', 'tenuistriate'], + 'intersocial': ['intersocial', 'orleanistic', 'sclerotinia'], + 'interspace': ['esperantic', 'interspace'], + 'interspecific': ['interspecific', 'prescientific'], + 'interspiration': ['interspiration', 'repristination'], + 'intersporal': ['intersporal', 'tripersonal'], + 'interstation': ['interstation', 'strontianite'], + 'intertalk': ['intertalk', 'latterkin'], + 'intertarsal': ['interastral', 'intertarsal'], + 'interteam': ['antimeter', 'attermine', 'interteam', 'terminate', 'tetramine'], + 'intertie': ['intertie', 'retinite'], + 'intertone': ['intertone', 'retention'], + 'intervascular': ['intervascular', 'vernacularist'], + 'intervention': ['intervention', 'introvenient'], + 'interverbal': ['interverbal', 'invertebral'], + 'interviewer': ['interviewer', 'reinterview'], + 'interwed': ['interwed', 'wintered'], + 'interwish': ['interwish', 'winterish'], + 'interwork': ['interwork', 'tinworker'], + 'interwove': ['interwove', 'overtwine'], + 'intestate': ['enstatite', 'intestate', 'satinette'], + 'intestinovesical': ['intestinovesical', 'vesicointestinal'], + 'inthrong': ['inthrong', 'northing'], + 'intima': ['intima', 'timani'], + 'intimacy': ['imitancy', 'intimacy', 'minacity'], + 'intimater': ['intimater', 'traintime'], + 'into': ['into', 'nito', 'oint', 'tino'], + 'intoed': ['ditone', 'intoed'], + 'intolerance': ['crenelation', 'intolerance'], + 'intolerating': ['intolerating', 'nitrogelatin'], + 'intonate': ['intonate', 'totanine'], + 'intonator': ['intonator', 'tortonian'], + 'intone': ['intone', 'tenino'], + 'intonement': ['intonement', 'omnitenent'], + 'intoner': ['intoner', 'ternion'], + 'intort': ['intort', 'tornit', 'triton'], + 'intoxicate': ['excitation', 'intoxicate'], + 'intracoelomic': ['iconometrical', 'intracoelomic'], + 'intracosmic': ['intracosmic', 'narcoticism'], + 'intracostal': ['intracostal', 'stratonical'], + 'intractile': ['intractile', 'triclinate'], + 'intrada': ['intrada', 'radiant'], + 'intraselection': ['intersectional', 'intraselection'], + 'intraseptal': ['intraseptal', 'paternalist', 'prenatalist'], + 'intraspinal': ['intraspinal', 'pinnitarsal'], + 'intreat': ['intreat', 'iterant', 'nitrate', 'tertian'], + 'intrencher': ['intrencher', 'reintrench'], + 'intricate': ['intricate', 'triactine'], + 'intrication': ['citrination', 'intrication'], + 'intrigue': ['intrigue', 'tigurine'], + 'introconvertibility': ['incontrovertibility', 'introconvertibility'], + 'introconvertible': ['incontrovertible', 'introconvertible'], + 'introduce': ['introduce', 'reduction'], + 'introit': ['introit', 'nitriot'], + 'introitus': ['introitus', 'routinist'], + 'introvenient': ['intervention', 'introvenient'], + 'intrude': ['intrude', 'turdine', 'untired', 'untried'], + 'intruse': ['intruse', 'sturine'], + 'intrust': ['intrust', 'sturtin'], + 'intube': ['butein', 'butine', 'intube'], + 'intue': ['intue', 'unite', 'untie'], + 'inula': ['inula', 'luian', 'uinal'], + 'inurbane': ['eburnian', 'inurbane'], + 'inure': ['inure', 'urine'], + 'inured': ['diurne', 'inured', 'ruined', 'unride'], + 'inurn': ['inrun', 'inurn'], + 'inustion': ['inustion', 'unionist'], + 'invader': ['invader', 'ravined', 'viander'], + 'invaluable': ['invaluable', 'unvailable'], + 'invar': ['invar', 'ravin', 'vanir'], + 'invector': ['contrive', 'invector'], + 'inveigler': ['inveigler', 'relieving'], + 'inventer': ['inventer', 'reinvent', 'ventrine', 'vintener'], + 'inventress': ['inventress', 'vintneress'], + 'inverness': ['inverness', 'nerviness'], + 'inversatile': ['inversatile', 'serviential'], + 'inverse': ['inverse', 'versine'], + 'invert': ['invert', 'virent'], + 'invertase': ['invertase', 'servetian'], + 'invertebral': ['interverbal', 'invertebral'], + 'inverter': ['inverter', 'reinvert', 'trinerve'], + 'investigation': ['investigation', 'tenovaginitis'], + 'invinate': ['innative', 'invinate'], + 'inviter': ['inviter', 'vitrine'], + 'invocate': ['conative', 'invocate'], + 'invoker': ['invoker', 'overink'], + 'involucrate': ['countervail', 'involucrate'], + 'involucre': ['involucre', 'volucrine'], + 'inwards': ['inwards', 'sinward'], + 'inwith': ['inwith', 'within'], + 'iodate': ['idotea', 'iodate', 'otidae'], + 'iodhydrate': ['hydriodate', 'iodhydrate'], + 'iodhydric': ['hydriodic', 'iodhydric'], + 'iodinate': ['ideation', 'iodinate', 'taenioid'], + 'iodinium': ['iodinium', 'ionidium'], + 'iodism': ['idoism', 'iodism'], + 'iodite': ['iodite', 'teioid'], + 'iodo': ['iodo', 'ooid'], + 'iodocasein': ['iodocasein', 'oniscoidea'], + 'iodochloride': ['chloroiodide', 'iodochloride'], + 'iodohydric': ['hydroiodic', 'iodohydric'], + 'iodol': ['dooli', 'iodol'], + 'iodothyrin': ['iodothyrin', 'thyroiodin'], + 'iodous': ['iodous', 'odious'], + 'ion': ['ino', 'ion'], + 'ionidium': ['iodinium', 'ionidium'], + 'ionizer': ['ionizer', 'ironize'], + 'iota': ['iota', 'tiao'], + 'iotacist': ['iotacist', 'taoistic'], + 'ipecac': ['icecap', 'ipecac'], + 'ipil': ['ipil', 'pili'], + 'ipseand': ['ipseand', 'panside', 'pansied'], + 'ira': ['air', 'ira', 'ria'], + 'iracund': ['candiru', 'iracund'], + 'irade': ['aider', 'deair', 'irade', 'redia'], + 'iran': ['arni', 'iran', 'nair', 'rain', 'rani'], + 'irani': ['irani', 'irian'], + 'iranism': ['iranism', 'sirmian'], + 'iranist': ['iranist', 'istrian'], + 'irascent': ['canister', 'cestrian', 'cisterna', 'irascent'], + 'irate': ['arite', 'artie', 'irate', 'retia', 'tarie'], + 'irately': ['irately', 'reality'], + 'ire': ['ire', 'rie'], + 'irena': ['erian', 'irena', 'reina'], + 'irene': ['ernie', 'ierne', 'irene'], + 'irenic': ['irenic', 'ricine'], + 'irenics': ['irenics', 'resinic', 'sericin', 'sirenic'], + 'irenicum': ['irenicum', 'muricine'], + 'iresine': ['iresine', 'iserine'], + 'irfan': ['infra', 'irfan'], + 'irgun': ['irgun', 'ruing', 'unrig'], + 'irian': ['irani', 'irian'], + 'iridal': ['iridal', 'lariid'], + 'iridate': ['arietid', 'iridate'], + 'iridectomy': ['iridectomy', 'mediocrity'], + 'irides': ['irides', 'irised'], + 'iridescent': ['indiscreet', 'indiscrete', 'iridescent'], + 'iridescently': ['indiscreetly', 'indiscretely', 'iridescently'], + 'iridosmium': ['iridosmium', 'osmiridium'], + 'irised': ['irides', 'irised'], + 'irish': ['irish', 'rishi', 'sirih'], + 'irk': ['irk', 'rik'], + 'irma': ['amir', 'irma', 'mari', 'mira', 'rami', 'rima'], + 'iroha': ['haori', 'iroha'], + 'irok': ['irok', 'kori'], + 'iron': ['inro', 'iron', 'noir', 'nori'], + 'ironclad': ['ironclad', 'rolandic'], + 'irone': ['irone', 'norie'], + 'ironhead': ['herodian', 'ironhead'], + 'ironice': ['ironice', 'oneiric'], + 'ironize': ['ionizer', 'ironize'], + 'ironshod': ['dishonor', 'ironshod'], + 'ironside': ['derision', 'ironside', 'resinoid', 'sirenoid'], + 'irradiant': ['irradiant', 'triandria'], + 'irrationable': ['irrationable', 'orbitelarian'], + 'irredenta': ['irredenta', 'retainder'], + 'irrelate': ['irrelate', 'retailer'], + 'irrepentance': ['irrepentance', 'pretercanine'], + 'irving': ['irving', 'riving', 'virgin'], + 'irvingiana': ['irvingiana', 'viraginian'], + 'is': ['is', 'si'], + 'isabel': ['isabel', 'lesbia'], + 'isabella': ['isabella', 'sailable'], + 'isagogical': ['isagogical', 'sialagogic'], + 'isagon': ['gosain', 'isagon', 'sagoin'], + 'isander': ['andries', 'isander', 'sardine'], + 'isanthous': ['anhistous', 'isanthous'], + 'isatate': ['isatate', 'satiate', 'taetsia'], + 'isatic': ['isatic', 'saitic'], + 'isatin': ['antisi', 'isatin'], + 'isatinic': ['isatinic', 'sinaitic'], + 'isaurian': ['anisuria', 'isaurian'], + 'isawa': ['isawa', 'waasi'], + 'isba': ['absi', 'bais', 'bias', 'isba'], + 'iscariot': ['aoristic', 'iscariot'], + 'ischemia': ['hemiasci', 'ischemia'], + 'ischioiliac': ['ilioischiac', 'ischioiliac'], + 'ischiorectal': ['ischiorectal', 'sciotherical'], + 'iserine': ['iresine', 'iserine'], + 'iseum': ['iseum', 'musie'], + 'isiac': ['ascii', 'isiac'], + 'isidore': ['isidore', 'osiride'], + 'isis': ['isis', 'sisi'], + 'islam': ['islam', 'ismal', 'simal'], + 'islamic': ['islamic', 'laicism', 'silicam'], + 'islamitic': ['islamitic', 'italicism'], + 'islandy': ['islandy', 'lindsay'], + 'islay': ['islay', 'saily'], + 'isle': ['isle', 'lise', 'sile'], + 'islet': ['islet', 'istle', 'slite', 'stile'], + 'isleta': ['isleta', 'litsea', 'salite', 'stelai'], + 'isleted': ['idleset', 'isleted'], + 'ism': ['ism', 'sim'], + 'ismal': ['islam', 'ismal', 'simal'], + 'ismatic': ['ismatic', 'itacism'], + 'ismatical': ['ismatical', 'lamaistic'], + 'isocamphor': ['chromopsia', 'isocamphor'], + 'isoclinal': ['collinsia', 'isoclinal'], + 'isocline': ['isocline', 'silicone'], + 'isocoumarin': ['acrimonious', 'isocoumarin'], + 'isodulcite': ['isodulcite', 'solicitude'], + 'isogen': ['geison', 'isogen'], + 'isogeotherm': ['geoisotherm', 'isogeotherm'], + 'isogon': ['isogon', 'songoi'], + 'isogram': ['isogram', 'orgiasm'], + 'isohel': ['helios', 'isohel'], + 'isoheptane': ['apothesine', 'isoheptane'], + 'isolate': ['aeolist', 'isolate'], + 'isolated': ['diastole', 'isolated', 'sodalite', 'solidate'], + 'isolative': ['isolative', 'soliative'], + 'isolde': ['isolde', 'soiled'], + 'isomer': ['isomer', 'rimose'], + 'isometric': ['eroticism', 'isometric', 'meroistic', 'trioecism'], + 'isomorph': ['isomorph', 'moorship'], + 'isonitrile': ['isonitrile', 'resilition'], + 'isonym': ['isonym', 'myosin', 'simony'], + 'isophthalyl': ['isophthalyl', 'lithophysal'], + 'isopodan': ['anisopod', 'isopodan'], + 'isoptera': ['isoptera', 'septoria'], + 'isosaccharic': ['isosaccharic', 'sacroischiac'], + 'isostere': ['erotesis', 'isostere'], + 'isotac': ['isotac', 'scotia'], + 'isotheral': ['horsetail', 'isotheral'], + 'isotherm': ['homerist', 'isotherm', 'otherism', 'theorism'], + 'isotria': ['isotria', 'oaritis'], + 'isotron': ['isotron', 'torsion'], + 'isotrope': ['isotrope', 'portoise'], + 'isotropism': ['isotropism', 'promitosis'], + 'isotropy': ['isotropy', 'porosity'], + 'israel': ['israel', 'relais', 'resail', 'sailer', 'serail', 'serial'], + 'israeli': ['alisier', 'israeli'], + 'israelite': ['israelite', 'resiliate'], + 'issuable': ['basileus', 'issuable', 'suasible'], + 'issuant': ['issuant', 'sustain'], + 'issue': ['issue', 'susie'], + 'issuer': ['issuer', 'uresis'], + 'ist': ['ist', 'its', 'sit'], + 'isthmi': ['isthmi', 'timish'], + 'isthmian': ['isthmian', 'smithian'], + 'isthmoid': ['isthmoid', 'thomisid'], + 'istle': ['islet', 'istle', 'slite', 'stile'], + 'istrian': ['iranist', 'istrian'], + 'isuret': ['isuret', 'resuit'], + 'it': ['it', 'ti'], + 'ita': ['ait', 'ati', 'ita', 'tai'], + 'itacism': ['ismatic', 'itacism'], + 'itaconate': ['acetation', 'itaconate'], + 'itaconic': ['aconitic', 'cationic', 'itaconic'], + 'itali': ['itali', 'tilia'], + 'italian': ['antilia', 'italian'], + 'italic': ['clitia', 'italic'], + 'italicism': ['islamitic', 'italicism'], + 'italite': ['italite', 'letitia', 'tilaite'], + 'italon': ['italon', 'lation', 'talion'], + 'itaves': ['itaves', 'stevia'], + 'itch': ['chit', 'itch', 'tchi'], + 'item': ['emit', 'item', 'mite', 'time'], + 'iten': ['iten', 'neti', 'tien', 'tine'], + 'itenean': ['aniente', 'itenean'], + 'iter': ['iter', 'reit', 'rite', 'teri', 'tier', 'tire'], + 'iterable': ['iterable', 'liberate'], + 'iterance': ['aneretic', 'centiare', 'creatine', 'increate', 'iterance'], + 'iterant': ['intreat', 'iterant', 'nitrate', 'tertian'], + 'ithaca': ['cahita', 'ithaca'], + 'ithacan': ['ithacan', 'tachina'], + 'ither': ['ither', 'their'], + 'itinerant': ['itinerant', 'nitratine'], + 'itinerantly': ['internality', 'itinerantly'], + 'itmo': ['itmo', 'moit', 'omit', 'timo'], + 'ito': ['ito', 'toi'], + 'itoism': ['itoism', 'omitis'], + 'itoist': ['itoist', 'otitis'], + 'itoland': ['itoland', 'talonid', 'tindalo'], + 'itonama': ['amniota', 'itonama'], + 'itonia': ['aition', 'itonia'], + 'its': ['ist', 'its', 'sit'], + 'itself': ['itself', 'stifle'], + 'ituraean': ['inaurate', 'ituraean'], + 'itza': ['itza', 'tiza', 'zati'], + 'iva': ['iva', 'vai', 'via'], + 'ivan': ['ivan', 'vain', 'vina'], + 'ivorist': ['ivorist', 'visitor'], + 'iwaiwa': ['iwaiwa', 'waiwai'], + 'ixiama': ['amixia', 'ixiama'], + 'ixodic': ['ixodic', 'oxidic'], + 'iyo': ['iyo', 'yoi'], + 'izar': ['izar', 'zira'], + 'jacami': ['jacami', 'jicama'], + 'jacobian': ['bajocian', 'jacobian'], + 'jag': ['gaj', 'jag'], + 'jagir': ['jagir', 'jirga'], + 'jagua': ['ajuga', 'jagua'], + 'jail': ['jail', 'lija'], + 'jailer': ['jailer', 'rejail'], + 'jaime': ['jaime', 'jamie'], + 'jain': ['jain', 'jina'], + 'jaina': ['inaja', 'jaina'], + 'jalouse': ['jalouse', 'jealous'], + 'jama': ['jama', 'maja'], + 'jamesian': ['jamesian', 'jamesina'], + 'jamesina': ['jamesian', 'jamesina'], + 'jami': ['ijma', 'jami'], + 'jamie': ['jaime', 'jamie'], + 'jane': ['jane', 'jean'], + 'janos': ['janos', 'jason', 'jonas', 'sonja'], + 'jantu': ['jantu', 'jaunt', 'junta'], + 'januslike': ['januslike', 'seljukian'], + 'japonism': ['japonism', 'pajonism'], + 'jar': ['jar', 'raj'], + 'jara': ['ajar', 'jara', 'raja'], + 'jarmo': ['jarmo', 'major'], + 'jarnut': ['jarnut', 'jurant'], + 'jason': ['janos', 'jason', 'jonas', 'sonja'], + 'jat': ['jat', 'taj'], + 'jatki': ['jatki', 'tajik'], + 'jato': ['jato', 'jota'], + 'jaun': ['jaun', 'juan'], + 'jaunt': ['jantu', 'jaunt', 'junta'], + 'jaup': ['jaup', 'puja'], + 'jealous': ['jalouse', 'jealous'], + 'jean': ['jane', 'jean'], + 'jebusitical': ['jebusitical', 'justiciable'], + 'jecoral': ['cajoler', 'jecoral'], + 'jeffery': ['jeffery', 'jeffrey'], + 'jeffrey': ['jeffery', 'jeffrey'], + 'jejunoduodenal': ['duodenojejunal', 'jejunoduodenal'], + 'jenine': ['jenine', 'jennie'], + 'jennie': ['jenine', 'jennie'], + 'jerker': ['jerker', 'rejerk'], + 'jerkin': ['jerkin', 'jinker'], + 'jeziah': ['hejazi', 'jeziah'], + 'jicama': ['jacami', 'jicama'], + 'jihad': ['hadji', 'jihad'], + 'jina': ['jain', 'jina'], + 'jingoist': ['jingoist', 'joisting'], + 'jinker': ['jerkin', 'jinker'], + 'jirga': ['jagir', 'jirga'], + 'jobo': ['bojo', 'jobo'], + 'johan': ['johan', 'jonah'], + 'join': ['join', 'joni'], + 'joinant': ['joinant', 'jotnian'], + 'joiner': ['joiner', 'rejoin'], + 'jointless': ['jointless', 'joltiness'], + 'joisting': ['jingoist', 'joisting'], + 'jolter': ['jolter', 'rejolt'], + 'joltiness': ['jointless', 'joltiness'], + 'jonah': ['johan', 'jonah'], + 'jonas': ['janos', 'jason', 'jonas', 'sonja'], + 'joni': ['join', 'joni'], + 'joom': ['joom', 'mojo'], + 'joshi': ['joshi', 'shoji'], + 'jota': ['jato', 'jota'], + 'jotnian': ['joinant', 'jotnian'], + 'journeyer': ['journeyer', 'rejourney'], + 'joust': ['joust', 'justo'], + 'juan': ['jaun', 'juan'], + 'judaic': ['judaic', 'judica'], + 'judica': ['judaic', 'judica'], + 'jujitsu': ['jujitsu', 'jujuist'], + 'jujuist': ['jujitsu', 'jujuist'], + 'junta': ['jantu', 'jaunt', 'junta'], + 'jurant': ['jarnut', 'jurant'], + 'justiciable': ['jebusitical', 'justiciable'], + 'justo': ['joust', 'justo'], + 'jute': ['jute', 'teju'], + 'ka': ['ak', 'ka'], + 'kabel': ['blake', 'bleak', 'kabel'], + 'kaberu': ['kaberu', 'kubera'], + 'kabuli': ['kabuli', 'kiluba'], + 'kabyle': ['bleaky', 'kabyle'], + 'kachari': ['chakari', 'chikara', 'kachari'], + 'kachin': ['hackin', 'kachin'], + 'kafir': ['fakir', 'fraik', 'kafir', 'rafik'], + 'kaha': ['akha', 'kaha'], + 'kahar': ['harka', 'kahar'], + 'kahu': ['haku', 'kahu'], + 'kaid': ['dika', 'kaid'], + 'kaik': ['kaik', 'kaki'], + 'kail': ['ilka', 'kail', 'kali'], + 'kainga': ['kainga', 'kanagi'], + 'kaiwi': ['kaiwi', 'kiwai'], + 'kaka': ['akka', 'kaka'], + 'kaki': ['kaik', 'kaki'], + 'kala': ['akal', 'kala'], + 'kalamian': ['kalamian', 'malikana'], + 'kaldani': ['danakil', 'dankali', 'kaldani', 'ladakin'], + 'kale': ['kale', 'lake', 'leak'], + 'kali': ['ilka', 'kail', 'kali'], + 'kalo': ['kalo', 'kola', 'loka'], + 'kamansi': ['kamansi', 'kamasin'], + 'kamares': ['kamares', 'seamark'], + 'kamasin': ['kamansi', 'kamasin'], + 'kame': ['kame', 'make', 'meak'], + 'kamel': ['kamel', 'kemal'], + 'kamiya': ['kamiya', 'yakima'], + 'kan': ['kan', 'nak'], + 'kana': ['akan', 'kana'], + 'kanagi': ['kainga', 'kanagi'], + 'kanap': ['kanap', 'panak'], + 'kanat': ['kanat', 'tanak', 'tanka'], + 'kande': ['kande', 'knead', 'naked'], + 'kang': ['kang', 'knag'], + 'kanga': ['angka', 'kanga'], + 'kangani': ['kangani', 'kiangan'], + 'kangli': ['kangli', 'laking'], + 'kanred': ['darken', 'kanred', 'ranked'], + 'kans': ['kans', 'sank'], + 'kaolin': ['ankoli', 'kaolin'], + 'karch': ['chark', 'karch'], + 'karel': ['karel', 'laker'], + 'karen': ['anker', 'karen', 'naker'], + 'kari': ['ikra', 'kari', 'raki'], + 'karite': ['arkite', 'karite'], + 'karl': ['karl', 'kral', 'lark'], + 'karling': ['karling', 'larking'], + 'karma': ['karma', 'krama', 'marka'], + 'karo': ['karo', 'kora', 'okra', 'roka'], + 'karree': ['karree', 'rerake'], + 'karst': ['karst', 'skart', 'stark'], + 'karstenite': ['karstenite', 'kersantite'], + 'kartel': ['kartel', 'retalk', 'talker'], + 'kasa': ['asak', 'kasa', 'saka'], + 'kasbah': ['abkhas', 'kasbah'], + 'kasha': ['kasha', 'khasa', 'sakha', 'shaka'], + 'kashan': ['kashan', 'sankha'], + 'kasher': ['kasher', 'shaker'], + 'kashi': ['kashi', 'khasi'], + 'kasm': ['kasm', 'mask'], + 'katar': ['katar', 'takar'], + 'kate': ['kate', 'keta', 'take', 'teak'], + 'kath': ['kath', 'khat'], + 'katharsis': ['katharsis', 'shastraik'], + 'katie': ['katie', 'keita'], + 'katik': ['katik', 'tikka'], + 'katrine': ['intaker', 'katrine', 'keratin'], + 'katy': ['katy', 'kyat', 'taky'], + 'kavass': ['kavass', 'vakass'], + 'kavi': ['kavi', 'kiva'], + 'kay': ['kay', 'yak'], + 'kayak': ['kayak', 'yakka'], + 'kayan': ['kayan', 'yakan'], + 'kayo': ['kayo', 'oaky'], + 'kea': ['ake', 'kea'], + 'keach': ['cheka', 'keach'], + 'keawe': ['aweek', 'keawe'], + 'kechel': ['heckle', 'kechel'], + 'kedar': ['daker', 'drake', 'kedar', 'radek'], + 'kee': ['eke', 'kee'], + 'keech': ['cheek', 'cheke', 'keech'], + 'keel': ['keel', 'kele', 'leek'], + 'keen': ['keen', 'knee'], + 'keena': ['aknee', 'ankee', 'keena'], + 'keep': ['keep', 'peek'], + 'keepership': ['keepership', 'shipkeeper'], + 'kees': ['kees', 'seek', 'skee'], + 'keest': ['keest', 'skeet', 'skete', 'steek'], + 'kefir': ['frike', 'kefir'], + 'keid': ['dike', 'keid'], + 'keita': ['katie', 'keita'], + 'keith': ['keith', 'kithe'], + 'keitloa': ['keitloa', 'oatlike'], + 'kelchin': ['chinkle', 'kelchin'], + 'kele': ['keel', 'kele', 'leek'], + 'kelima': ['kelima', 'mikael'], + 'kelpie': ['kelpie', 'pelike'], + 'kelty': ['kelty', 'ketyl'], + 'kemal': ['kamel', 'kemal'], + 'kemalist': ['kemalist', 'mastlike'], + 'kenareh': ['hearken', 'kenareh'], + 'kennel': ['kennel', 'nelken'], + 'kenotic': ['kenotic', 'ketonic'], + 'kent': ['kent', 'knet'], + 'kentia': ['intake', 'kentia'], + 'kenton': ['kenton', 'nekton'], + 'kepi': ['kepi', 'kipe', 'pike'], + 'keralite': ['keralite', 'tearlike'], + 'kerasin': ['kerasin', 'sarkine'], + 'kerat': ['kerat', 'taker'], + 'keratin': ['intaker', 'katrine', 'keratin'], + 'keratoangioma': ['angiokeratoma', 'keratoangioma'], + 'keratosis': ['asterikos', 'keratosis'], + 'keres': ['esker', 'keres', 'reesk', 'seker', 'skeer', 'skere'], + 'keresan': ['keresan', 'sneaker'], + 'kerewa': ['kerewa', 'rewake'], + 'kerf': ['ferk', 'kerf'], + 'kern': ['kern', 'renk'], + 'kersantite': ['karstenite', 'kersantite'], + 'kersey': ['kersey', 'skeery'], + 'kestrel': ['kestrel', 'skelter'], + 'keta': ['kate', 'keta', 'take', 'teak'], + 'ketene': ['ektene', 'ketene'], + 'keto': ['keto', 'oket', 'toke'], + 'ketol': ['ketol', 'loket'], + 'ketonic': ['kenotic', 'ketonic'], + 'ketu': ['ketu', 'teuk', 'tuke'], + 'ketupa': ['ketupa', 'uptake'], + 'ketyl': ['kelty', 'ketyl'], + 'keup': ['keup', 'puke'], + 'keuper': ['keuper', 'peruke'], + 'kevan': ['kevan', 'knave'], + 'kha': ['hak', 'kha'], + 'khami': ['hakim', 'khami'], + 'khan': ['ankh', 'hank', 'khan'], + 'khar': ['hark', 'khar', 'rakh'], + 'khasa': ['kasha', 'khasa', 'sakha', 'shaka'], + 'khasi': ['kashi', 'khasi'], + 'khat': ['kath', 'khat'], + 'khatib': ['bhakti', 'khatib'], + 'khila': ['khila', 'kilah'], + 'khu': ['huk', 'khu'], + 'khula': ['khula', 'kulah'], + 'kiangan': ['kangani', 'kiangan'], + 'kibe': ['bike', 'kibe'], + 'kicker': ['kicker', 'rekick'], + 'kickout': ['kickout', 'outkick'], + 'kidney': ['dinkey', 'kidney'], + 'kids': ['disk', 'kids', 'skid'], + 'kiel': ['kiel', 'like'], + 'kier': ['erik', 'kier', 'reki'], + 'kiku': ['kiku', 'kuki'], + 'kikumon': ['kikumon', 'kokumin'], + 'kil': ['ilk', 'kil'], + 'kilah': ['khila', 'kilah'], + 'kiliare': ['airlike', 'kiliare'], + 'killcalf': ['calfkill', 'killcalf'], + 'killer': ['killer', 'rekill'], + 'kiln': ['kiln', 'link'], + 'kilnman': ['kilnman', 'linkman'], + 'kilo': ['kilo', 'koil', 'koli'], + 'kilp': ['kilp', 'klip'], + 'kilter': ['kilter', 'kirtle'], + 'kilting': ['kilting', 'kitling'], + 'kiluba': ['kabuli', 'kiluba'], + 'kimberlite': ['kimberlite', 'timberlike'], + 'kimnel': ['kimnel', 'milken'], + 'kin': ['ink', 'kin'], + 'kina': ['akin', 'kina', 'naik'], + 'kinase': ['kinase', 'sekani'], + 'kinch': ['chink', 'kinch'], + 'kind': ['dink', 'kind'], + 'kindle': ['kindle', 'linked'], + 'kinetomer': ['kinetomer', 'konimeter'], + 'king': ['gink', 'king'], + 'kingcob': ['bocking', 'kingcob'], + 'kingpin': ['kingpin', 'pinking'], + 'kingrow': ['kingrow', 'working'], + 'kinless': ['inkless', 'kinless'], + 'kinship': ['kinship', 'pinkish'], + 'kioko': ['kioko', 'kokio'], + 'kip': ['kip', 'pik'], + 'kipe': ['kepi', 'kipe', 'pike'], + 'kirk': ['kirk', 'rikk'], + 'kirktown': ['kirktown', 'knitwork'], + 'kirn': ['kirn', 'rink'], + 'kirsten': ['kirsten', 'kristen', 'stinker'], + 'kirsty': ['kirsty', 'skirty'], + 'kirtle': ['kilter', 'kirtle'], + 'kirve': ['kirve', 'kiver'], + 'kish': ['kish', 'shik', 'sikh'], + 'kishen': ['kishen', 'neskhi'], + 'kisra': ['kisra', 'sikar', 'skair'], + 'kissar': ['kissar', 'krasis'], + 'kisser': ['kisser', 'rekiss'], + 'kist': ['kist', 'skit'], + 'kistful': ['kistful', 'lutfisk'], + 'kitab': ['batik', 'kitab'], + 'kitan': ['kitan', 'takin'], + 'kitar': ['kitar', 'krait', 'rakit', 'traik'], + 'kitchen': ['kitchen', 'thicken'], + 'kitchener': ['kitchener', 'rethicken', 'thickener'], + 'kithe': ['keith', 'kithe'], + 'kitling': ['kilting', 'kitling'], + 'kitlope': ['kitlope', 'potlike', 'toplike'], + 'kittel': ['kittel', 'kittle'], + 'kittle': ['kittel', 'kittle'], + 'kittles': ['kittles', 'skittle'], + 'kiva': ['kavi', 'kiva'], + 'kiver': ['kirve', 'kiver'], + 'kiwai': ['kaiwi', 'kiwai'], + 'klan': ['klan', 'lank'], + 'klanism': ['klanism', 'silkman'], + 'klaus': ['klaus', 'lukas', 'sulka'], + 'kleistian': ['kleistian', 'saintlike', 'satinlike'], + 'klendusic': ['klendusic', 'unsickled'], + 'kling': ['glink', 'kling'], + 'klip': ['kilp', 'klip'], + 'klop': ['klop', 'polk'], + 'knab': ['bank', 'knab', 'nabk'], + 'knag': ['kang', 'knag'], + 'knap': ['knap', 'pank'], + 'knape': ['knape', 'pekan'], + 'knar': ['knar', 'kran', 'nark', 'rank'], + 'knave': ['kevan', 'knave'], + 'knawel': ['knawel', 'wankle'], + 'knead': ['kande', 'knead', 'naked'], + 'knee': ['keen', 'knee'], + 'knet': ['kent', 'knet'], + 'knit': ['knit', 'tink'], + 'knitter': ['knitter', 'trinket'], + 'knitwork': ['kirktown', 'knitwork'], + 'knob': ['bonk', 'knob'], + 'knot': ['knot', 'tonk'], + 'knottiness': ['knottiness', 'stinkstone'], + 'knower': ['knower', 'reknow', 'wroken'], + 'knub': ['bunk', 'knub'], + 'knurly': ['knurly', 'runkly'], + 'knut': ['knut', 'tunk'], + 'knute': ['knute', 'unket'], + 'ko': ['ko', 'ok'], + 'koa': ['ako', 'koa', 'oak', 'oka'], + 'koali': ['koali', 'koila'], + 'kobu': ['bouk', 'kobu'], + 'koch': ['hock', 'koch'], + 'kochia': ['choiak', 'kochia'], + 'koel': ['koel', 'loke'], + 'koi': ['koi', 'oki'], + 'koil': ['kilo', 'koil', 'koli'], + 'koila': ['koali', 'koila'], + 'koilon': ['inlook', 'koilon'], + 'kokan': ['kokan', 'konak'], + 'kokio': ['kioko', 'kokio'], + 'kokumin': ['kikumon', 'kokumin'], + 'kola': ['kalo', 'kola', 'loka'], + 'koli': ['kilo', 'koil', 'koli'], + 'kolo': ['kolo', 'look'], + 'kome': ['kome', 'moke'], + 'komi': ['komi', 'moki'], + 'kona': ['kona', 'nako'], + 'konak': ['kokan', 'konak'], + 'kongo': ['kongo', 'ngoko'], + 'kongoni': ['kongoni', 'nooking'], + 'konia': ['ikona', 'konia'], + 'konimeter': ['kinetomer', 'konimeter'], + 'kor': ['kor', 'rok'], + 'kora': ['karo', 'kora', 'okra', 'roka'], + 'korait': ['korait', 'troika'], + 'koran': ['koran', 'krona'], + 'korana': ['anorak', 'korana'], + 'kore': ['kore', 'roke'], + 'korec': ['coker', 'corke', 'korec'], + 'korero': ['korero', 'rooker'], + 'kori': ['irok', 'kori'], + 'korimako': ['korimako', 'koromika'], + 'koromika': ['korimako', 'koromika'], + 'korwa': ['awork', 'korwa'], + 'kory': ['kory', 'roky', 'york'], + 'kos': ['kos', 'sok'], + 'koso': ['koso', 'skoo', 'sook'], + 'kotar': ['kotar', 'tarok'], + 'koto': ['koto', 'toko', 'took'], + 'kra': ['ark', 'kra'], + 'krait': ['kitar', 'krait', 'rakit', 'traik'], + 'kraken': ['kraken', 'nekkar'], + 'kral': ['karl', 'kral', 'lark'], + 'krama': ['karma', 'krama', 'marka'], + 'kran': ['knar', 'kran', 'nark', 'rank'], + 'kras': ['askr', 'kras', 'sark'], + 'krasis': ['kissar', 'krasis'], + 'kraut': ['kraut', 'tukra'], + 'kreis': ['kreis', 'skier'], + 'kreistle': ['kreistle', 'triskele'], + 'krepi': ['krepi', 'piker'], + 'krina': ['inkra', 'krina', 'nakir', 'rinka'], + 'kris': ['kris', 'risk'], + 'krishna': ['krishna', 'rankish'], + 'kristen': ['kirsten', 'kristen', 'stinker'], + 'krona': ['koran', 'krona'], + 'krone': ['ekron', 'krone'], + 'kroo': ['kroo', 'rook'], + 'krosa': ['krosa', 'oskar'], + 'kua': ['aku', 'auk', 'kua'], + 'kuar': ['kuar', 'raku', 'rauk'], + 'kuba': ['baku', 'kuba'], + 'kubera': ['kaberu', 'kubera'], + 'kuki': ['kiku', 'kuki'], + 'kulah': ['khula', 'kulah'], + 'kulimit': ['kulimit', 'tilikum'], + 'kulm': ['kulm', 'mulk'], + 'kuman': ['kuman', 'naumk'], + 'kumhar': ['kumhar', 'kumrah'], + 'kumrah': ['kumhar', 'kumrah'], + 'kunai': ['kunai', 'nikau'], + 'kuneste': ['kuneste', 'netsuke'], + 'kung': ['gunk', 'kung'], + 'kurmi': ['kurmi', 'mukri'], + 'kurt': ['kurt', 'turk'], + 'kurus': ['kurus', 'ursuk'], + 'kusa': ['kusa', 'skua'], + 'kusam': ['kusam', 'sumak'], + 'kusan': ['ankus', 'kusan'], + 'kusha': ['kusha', 'shaku', 'ushak'], + 'kutchin': ['kutchin', 'unthick'], + 'kutenai': ['kutenai', 'unakite'], + 'kyar': ['kyar', 'yark'], + 'kyat': ['katy', 'kyat', 'taky'], + 'kyle': ['kyle', 'yelk'], + 'kylo': ['kylo', 'yolk'], + 'kyte': ['kyte', 'tyke'], + 'la': ['al', 'la'], + 'laager': ['aglare', 'alegar', 'galera', 'laager'], + 'laang': ['laang', 'lagan', 'lagna'], + 'lab': ['alb', 'bal', 'lab'], + 'laban': ['alban', 'balan', 'banal', 'laban', 'nabal', 'nabla'], + 'labber': ['barbel', 'labber', 'rabble'], + 'labefact': ['factable', 'labefact'], + 'label': ['bella', 'label'], + 'labeler': ['labeler', 'relabel'], + 'labia': ['balai', 'labia'], + 'labial': ['abilla', 'labial'], + 'labially': ['alliably', 'labially'], + 'labiate': ['baalite', 'bialate', 'labiate'], + 'labiella': ['alliable', 'labiella'], + 'labile': ['alible', 'belial', 'labile', 'liable'], + 'labiocervical': ['cervicolabial', 'labiocervical'], + 'labiodental': ['dentolabial', 'labiodental'], + 'labioglossal': ['glossolabial', 'labioglossal'], + 'labioglossolaryngeal': ['glossolabiolaryngeal', 'labioglossolaryngeal'], + 'labioglossopharyngeal': ['glossolabiopharyngeal', 'labioglossopharyngeal'], + 'labiomental': ['labiomental', 'mentolabial'], + 'labionasal': ['labionasal', 'nasolabial'], + 'labiovelar': ['bialveolar', 'labiovelar'], + 'labis': ['basil', 'labis'], + 'labor': ['balor', 'bolar', 'boral', 'labor', 'lobar'], + 'laborant': ['balatron', 'laborant'], + 'laborism': ['laborism', 'mislabor'], + 'laborist': ['laborist', 'strobila'], + 'laborite': ['betailor', 'laborite', 'orbitale'], + 'labrador': ['labrador', 'larboard'], + 'labret': ['albert', 'balter', 'labret', 'tabler'], + 'labridae': ['labridae', 'radiable'], + 'labrose': ['borlase', 'labrose', 'rosabel'], + 'labrum': ['brumal', 'labrum', 'lumbar', 'umbral'], + 'labrus': ['bursal', 'labrus'], + 'laburnum': ['alburnum', 'laburnum'], + 'lac': ['cal', 'lac'], + 'lace': ['acle', 'alec', 'lace'], + 'laced': ['clead', 'decal', 'laced'], + 'laceman': ['laceman', 'manacle'], + 'lacepod': ['lacepod', 'pedocal', 'placode'], + 'lacer': ['ceral', 'clare', 'clear', 'lacer'], + 'lacerable': ['clearable', 'lacerable'], + 'lacerate': ['lacerate', 'lacertae'], + 'laceration': ['creational', 'crotalinae', 'laceration', 'reactional'], + 'lacerative': ['calaverite', 'lacerative'], + 'lacertae': ['lacerate', 'lacertae'], + 'lacertian': ['carnalite', 'claretian', 'lacertian', 'nectarial'], + 'lacertid': ['articled', 'lacertid'], + 'lacertidae': ['dilacerate', 'lacertidae'], + 'lacertiloid': ['illoricated', 'lacertiloid'], + 'lacertine': ['intercale', 'interlace', 'lacertine', 'reclinate'], + 'lacertoid': ['dialector', 'lacertoid'], + 'lacery': ['clayer', 'lacery'], + 'lacet': ['cleat', 'eclat', 'ectal', 'lacet', 'tecla'], + 'lache': ['chela', 'lache', 'leach'], + 'laches': ['cashel', 'laches', 'sealch'], + 'lachrymonasal': ['lachrymonasal', 'nasolachrymal'], + 'lachsa': ['calash', 'lachsa'], + 'laciness': ['laciness', 'sensical'], + 'lacing': ['anglic', 'lacing'], + 'lacinia': ['lacinia', 'licania'], + 'laciniated': ['acetanilid', 'laciniated', 'teniacidal'], + 'lacis': ['lacis', 'salic'], + 'lack': ['calk', 'lack'], + 'lacker': ['calker', 'lacker', 'rackle', 'recalk', 'reckla'], + 'lacmoid': ['domical', 'lacmoid'], + 'laconic': ['conical', 'laconic'], + 'laconica': ['canicola', 'laconica'], + 'laconizer': ['laconizer', 'locarnize'], + 'lacquer': ['claquer', 'lacquer'], + 'lacquerer': ['lacquerer', 'relacquer'], + 'lactarene': ['lactarene', 'nectareal'], + 'lactarious': ['alacritous', 'lactarious', 'lactosuria'], + 'lactarium': ['lactarium', 'matricula'], + 'lactarius': ['australic', 'lactarius'], + 'lacteal': ['catella', 'lacteal'], + 'lacteous': ['lacteous', 'osculate'], + 'lactide': ['citadel', 'deltaic', 'dialect', 'edictal', 'lactide'], + 'lactinate': ['cantalite', 'lactinate', 'tetanical'], + 'lacto': ['lacto', 'tlaco'], + 'lactoid': ['cotidal', 'lactoid', 'talcoid'], + 'lactoprotein': ['lactoprotein', 'protectional'], + 'lactose': ['alecost', 'lactose', 'scotale', 'talcose'], + 'lactoside': ['dislocate', 'lactoside'], + 'lactosuria': ['alacritous', 'lactarious', 'lactosuria'], + 'lacunal': ['calluna', 'lacunal'], + 'lacune': ['auncel', 'cuneal', 'lacune', 'launce', 'unlace'], + 'lacustral': ['claustral', 'lacustral'], + 'lacwork': ['lacwork', 'warlock'], + 'lacy': ['acyl', 'clay', 'lacy'], + 'lad': ['dal', 'lad'], + 'ladakin': ['danakil', 'dankali', 'kaldani', 'ladakin'], + 'ladanum': ['ladanum', 'udalman'], + 'ladder': ['ladder', 'raddle'], + 'laddery': ['dreadly', 'laddery'], + 'laddie': ['daidle', 'laddie'], + 'lade': ['dale', 'deal', 'lade', 'lead', 'leda'], + 'lademan': ['daleman', 'lademan', 'leadman'], + 'laden': ['eland', 'laden', 'lenad'], + 'lader': ['alder', 'daler', 'lader'], + 'ladies': ['aisled', 'deasil', 'ladies', 'sailed'], + 'ladin': ['danli', 'ladin', 'linda', 'nidal'], + 'lading': ['angild', 'lading'], + 'ladino': ['dolina', 'ladino'], + 'ladle': ['dalle', 'della', 'ladle'], + 'ladrone': ['endoral', 'ladrone', 'leonard'], + 'ladyfy': ['dayfly', 'ladyfy'], + 'ladyish': ['ladyish', 'shadily'], + 'ladyling': ['dallying', 'ladyling'], + 'laet': ['atle', 'laet', 'late', 'leat', 'tael', 'tale', 'teal'], + 'laeti': ['alite', 'laeti'], + 'laetic': ['calite', 'laetic', 'tecali'], + 'lafite': ['fetial', 'filate', 'lafite', 'leafit'], + 'lag': ['gal', 'lag'], + 'lagan': ['laang', 'lagan', 'lagna'], + 'lagen': ['agnel', 'angel', 'angle', 'galen', 'genal', 'glean', 'lagen'], + 'lagena': ['alnage', 'angela', 'galena', 'lagena'], + 'lagend': ['angled', 'dangle', 'englad', 'lagend'], + 'lager': ['argel', 'ergal', 'garle', 'glare', 'lager', 'large', 'regal'], + 'lagetto': ['lagetto', 'tagetol'], + 'lagged': ['daggle', 'lagged'], + 'laggen': ['laggen', 'naggle'], + 'lagger': ['gargle', 'gregal', 'lagger', 'raggle'], + 'lagna': ['laang', 'lagan', 'lagna'], + 'lagniappe': ['appealing', 'lagniappe', 'panplegia'], + 'lagonite': ['gelation', 'lagonite', 'legation'], + 'lagunero': ['lagunero', 'organule', 'uroglena'], + 'lagurus': ['argulus', 'lagurus'], + 'lai': ['ail', 'ila', 'lai'], + 'laicism': ['islamic', 'laicism', 'silicam'], + 'laid': ['dail', 'dali', 'dial', 'laid', 'lida'], + 'lain': ['alin', 'anil', 'lain', 'lina', 'nail'], + 'laine': ['alien', 'aline', 'anile', 'elain', 'elian', 'laine', 'linea'], + 'laiose': ['aeolis', 'laiose'], + 'lair': ['aril', 'lair', 'lari', 'liar', 'lira', 'rail', 'rial'], + 'lairage': ['lairage', 'railage', 'regalia'], + 'laird': ['drail', 'laird', 'larid', 'liard'], + 'lairless': ['lairless', 'railless'], + 'lairman': ['lairman', 'laminar', 'malarin', 'railman'], + 'lairstone': ['lairstone', 'orleanist', 'serotinal'], + 'lairy': ['lairy', 'riyal'], + 'laitance': ['analcite', 'anticlea', 'laitance'], + 'laity': ['laity', 'taily'], + 'lak': ['alk', 'lak'], + 'lake': ['kale', 'lake', 'leak'], + 'lakeless': ['lakeless', 'leakless'], + 'laker': ['karel', 'laker'], + 'lakie': ['alike', 'lakie'], + 'laking': ['kangli', 'laking'], + 'lakish': ['lakish', 'shakil'], + 'lakota': ['atokal', 'lakota'], + 'laky': ['alky', 'laky'], + 'lalo': ['lalo', 'lola', 'olla'], + 'lalopathy': ['allopathy', 'lalopathy'], + 'lam': ['lam', 'mal'], + 'lama': ['alma', 'amla', 'lama', 'mala'], + 'lamaic': ['amical', 'camail', 'lamaic'], + 'lamaism': ['lamaism', 'miasmal'], + 'lamaist': ['lamaist', 'lamista'], + 'lamaistic': ['ismatical', 'lamaistic'], + 'lamanite': ['lamanite', 'laminate'], + 'lamany': ['amylan', 'lamany', 'layman'], + 'lamb': ['balm', 'lamb'], + 'lambaste': ['blastema', 'lambaste'], + 'lambent': ['beltman', 'lambent'], + 'lamber': ['ambler', 'blamer', 'lamber', 'marble', 'ramble'], + 'lambie': ['bemail', 'lambie'], + 'lambiness': ['balminess', 'lambiness'], + 'lamblike': ['balmlike', 'lamblike'], + 'lamby': ['balmy', 'lamby'], + 'lame': ['alem', 'alme', 'lame', 'leam', 'male', 'meal', 'mela'], + 'lamella': ['lamella', 'malella', 'malleal'], + 'lamellose': ['lamellose', 'semolella'], + 'lamely': ['lamely', 'mellay'], + 'lameness': ['lameness', 'maleness', 'maneless', 'nameless'], + 'lament': ['lament', 'manlet', 'mantel', 'mantle', 'mental'], + 'lamenter': ['lamenter', 'relament', 'remantle'], + 'lamenting': ['alignment', 'lamenting'], + 'lameter': ['lameter', 'metaler', 'remetal'], + 'lamia': ['alima', 'lamia'], + 'lamiger': ['gremial', 'lamiger'], + 'lamiides': ['idealism', 'lamiides'], + 'lamin': ['lamin', 'liman', 'milan'], + 'lamina': ['almain', 'animal', 'lamina', 'manila'], + 'laminae': ['laminae', 'melania'], + 'laminar': ['lairman', 'laminar', 'malarin', 'railman'], + 'laminarin': ['laminarin', 'linamarin'], + 'laminarite': ['laminarite', 'terminalia'], + 'laminate': ['lamanite', 'laminate'], + 'laminated': ['almandite', 'laminated'], + 'lamination': ['antimonial', 'lamination'], + 'laminboard': ['laminboard', 'lombardian'], + 'laminectomy': ['laminectomy', 'metonymical'], + 'laminose': ['laminose', 'lemonias', 'semolina'], + 'lamish': ['lamish', 'shimal'], + 'lamista': ['lamaist', 'lamista'], + 'lamiter': ['lamiter', 'marlite'], + 'lammer': ['lammer', 'rammel'], + 'lammy': ['lammy', 'malmy'], + 'lamna': ['alman', 'lamna', 'manal'], + 'lamnid': ['lamnid', 'mandil'], + 'lamnidae': ['aldamine', 'lamnidae'], + 'lamp': ['lamp', 'palm'], + 'lampad': ['lampad', 'palmad'], + 'lampas': ['lampas', 'plasma'], + 'lamper': ['lamper', 'palmer', 'relamp'], + 'lampers': ['lampers', 'sampler'], + 'lampful': ['lampful', 'palmful'], + 'lampist': ['lampist', 'palmist'], + 'lampistry': ['lampistry', 'palmistry'], + 'lampoon': ['lampoon', 'pomonal'], + 'lamprey': ['lamprey', 'palmery'], + 'lampyridae': ['lampyridae', 'pyramidale'], + 'lamus': ['lamus', 'malus', 'musal', 'slaum'], + 'lamut': ['lamut', 'tamul'], + 'lan': ['aln', 'lan'], + 'lana': ['alan', 'anal', 'lana'], + 'lanas': ['alans', 'lanas', 'nasal'], + 'lanate': ['anteal', 'lanate', 'teanal'], + 'lancaster': ['ancestral', 'lancaster'], + 'lancasterian': ['alcantarines', 'lancasterian'], + 'lance': ['canel', 'clean', 'lance', 'lenca'], + 'lanced': ['calden', 'candle', 'lanced'], + 'lancely': ['cleanly', 'lancely'], + 'lanceolar': ['lanceolar', 'olecranal'], + 'lancer': ['lancer', 'rancel'], + 'lances': ['lances', 'senlac'], + 'lancet': ['cantle', 'cental', 'lancet', 'tancel'], + 'lanceteer': ['crenelate', 'lanceteer'], + 'lancinate': ['cantilena', 'lancinate'], + 'landbook': ['bookland', 'landbook'], + 'landed': ['dandle', 'landed'], + 'lander': ['aldern', + 'darnel', + 'enlard', + 'lander', + 'lenard', + 'randle', + 'reland'], + 'landfast': ['fastland', 'landfast'], + 'landgrave': ['grandeval', 'landgrave'], + 'landimere': ['landimere', 'madrilene'], + 'landing': ['danglin', 'landing'], + 'landlubber': ['landlubber', 'lubberland'], + 'landreeve': ['landreeve', 'reeveland'], + 'landstorm': ['landstorm', 'transmold'], + 'landwash': ['landwash', 'washland'], + 'lane': ['alen', 'lane', 'lean', 'lena', 'nael', 'neal'], + 'lanete': ['elanet', 'lanete', 'lateen'], + 'laney': ['laney', 'layne'], + 'langhian': ['hangnail', 'langhian'], + 'langi': ['algin', 'align', 'langi', 'liang', 'linga'], + 'langite': ['atingle', 'gelatin', 'genital', 'langite', 'telinga'], + 'lango': ['along', 'gonal', 'lango', 'longa', 'nogal'], + 'langobard': ['bandarlog', 'langobard'], + 'language': ['ganguela', 'language'], + 'laniate': ['laniate', 'natalie', 'taenial'], + 'lanific': ['finical', 'lanific'], + 'laniform': ['formalin', 'informal', 'laniform'], + 'laniidae': ['aedilian', 'laniidae'], + 'lanista': ['lanista', 'santali'], + 'lanius': ['insula', 'lanius', 'lusian'], + 'lank': ['klan', 'lank'], + 'lanket': ['anklet', 'lanket', 'tankle'], + 'lanner': ['lanner', 'rannel'], + 'lansat': ['aslant', 'lansat', 'natals', 'santal'], + 'lanseh': ['halsen', 'hansel', 'lanseh'], + 'lantaca': ['cantala', 'catalan', 'lantaca'], + 'lanum': ['lanum', 'manul'], + 'lao': ['alo', 'lao', 'loa'], + 'laodicean': ['caledonia', 'laodicean'], + 'laotian': ['ailanto', 'alation', 'laotian', 'notalia'], + 'lap': ['alp', 'lap', 'pal'], + 'laparohysterotomy': ['hysterolaparotomy', 'laparohysterotomy'], + 'laparosplenotomy': ['laparosplenotomy', 'splenolaparotomy'], + 'lapidarist': ['lapidarist', 'triapsidal'], + 'lapidate': ['lapidate', 'talpidae'], + 'lapideon': ['lapideon', 'palinode', 'pedalion'], + 'lapidose': ['episodal', 'lapidose', 'sepaloid'], + 'lapith': ['lapith', 'tilpah'], + 'lapon': ['lapon', 'nopal'], + 'lapp': ['lapp', 'palp', 'plap'], + 'lappa': ['lappa', 'papal'], + 'lapped': ['dapple', 'lapped', 'palped'], + 'lapper': ['lapper', 'rappel'], + 'lappish': ['lappish', 'shiplap'], + 'lapsation': ['apolistan', 'lapsation'], + 'lapse': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'lapsi': ['alisp', 'lapsi'], + 'lapsing': ['lapsing', 'sapling'], + 'lapstone': ['lapstone', 'pleonast'], + 'larboard': ['labrador', 'larboard'], + 'larcenic': ['calciner', 'larcenic'], + 'larcenist': ['cisternal', 'larcenist'], + 'larcenous': ['larcenous', 'senocular'], + 'larchen': ['charnel', 'larchen'], + 'lardacein': ['ecardinal', 'lardacein'], + 'lardite': ['dilater', 'lardite', 'redtail'], + 'lardon': ['androl', 'arnold', 'lardon', 'roland', 'ronald'], + 'lardy': ['daryl', 'lardy', 'lyard'], + 'large': ['argel', 'ergal', 'garle', 'glare', 'lager', 'large', 'regal'], + 'largely': ['allergy', 'gallery', 'largely', 'regally'], + 'largen': ['angler', 'arleng', 'garnel', 'largen', 'rangle', 'regnal'], + 'largeness': ['largeness', 'rangeless', 'regalness'], + 'largess': ['glasser', 'largess'], + 'largition': ['gratiolin', 'largition', 'tailoring'], + 'largo': ['algor', 'argol', 'goral', 'largo'], + 'lari': ['aril', 'lair', 'lari', 'liar', 'lira', 'rail', 'rial'], + 'lariat': ['altair', 'atrail', 'atrial', 'lariat', 'latria', 'talari'], + 'larid': ['drail', 'laird', 'larid', 'liard'], + 'laridae': ['ardelia', 'laridae', 'radiale'], + 'larigo': ['gloria', 'larigo', 'logria'], + 'larigot': ['goitral', 'larigot', 'ligator'], + 'lariid': ['iridal', 'lariid'], + 'larine': ['arline', 'larine', 'linear', 'nailer', 'renail'], + 'lark': ['karl', 'kral', 'lark'], + 'larking': ['karling', 'larking'], + 'larsenite': ['intersale', 'larsenite'], + 'larus': ['larus', 'sural', 'ursal'], + 'larva': ['alvar', 'arval', 'larva'], + 'larval': ['larval', 'vallar'], + 'larvate': ['larvate', 'lavaret', 'travale'], + 'larve': ['arvel', 'larve', 'laver', 'ravel', 'velar'], + 'larvicide': ['larvicide', 'veridical'], + 'laryngopharyngeal': ['laryngopharyngeal', 'pharyngolaryngeal'], + 'laryngopharyngitis': ['laryngopharyngitis', 'pharyngolaryngitis'], + 'laryngotome': ['laryngotome', 'maternology'], + 'laryngotracheotomy': ['laryngotracheotomy', 'tracheolaryngotomy'], + 'las': ['las', 'sal', 'sla'], + 'lasa': ['alas', 'lasa'], + 'lascar': ['lascar', 'rascal', 'sacral', 'scalar'], + 'laser': ['arles', 'arsle', 'laser', 'seral', 'slare'], + 'lash': ['hals', 'lash'], + 'lasi': ['lasi', 'lias', 'lisa', 'sail', 'sial'], + 'lasius': ['asilus', 'lasius'], + 'lask': ['lask', 'skal'], + 'lasket': ['lasket', 'sklate'], + 'laspring': ['laspring', 'sparling', 'springal'], + 'lasque': ['lasque', 'squeal'], + 'lasset': ['lasset', 'tassel'], + 'lassie': ['elissa', 'lassie'], + 'lasso': ['lasso', 'ossal'], + 'lassoer': ['lassoer', 'oarless', 'rosales'], + 'last': ['last', 'salt', 'slat'], + 'laster': ['laster', + 'lastre', + 'rastle', + 'relast', + 'resalt', + 'salter', + 'slater', + 'stelar'], + 'lasting': ['anglist', 'lasting', 'salting', 'slating', 'staling'], + 'lastly': ['lastly', 'saltly'], + 'lastness': ['lastness', 'saltness'], + 'lastre': ['laster', + 'lastre', + 'rastle', + 'relast', + 'resalt', + 'salter', + 'slater', + 'stelar'], + 'lasty': ['lasty', 'salty', 'slaty'], + 'lat': ['alt', 'lat', 'tal'], + 'lata': ['lata', 'taal', 'tala'], + 'latania': ['altaian', 'latania', 'natalia'], + 'latcher': ['clethra', 'latcher', 'ratchel', 'relatch', 'talcher', 'trachle'], + 'latchet': ['chattel', 'latchet'], + 'late': ['atle', 'laet', 'late', 'leat', 'tael', 'tale', 'teal'], + 'latebra': ['alberta', 'latebra', 'ratable'], + 'lated': ['adlet', 'dealt', 'delta', 'lated', 'taled'], + 'lateen': ['elanet', 'lanete', 'lateen'], + 'lately': ['lately', 'lealty'], + 'laten': ['ental', 'laten', 'leant'], + 'latent': ['latent', 'latten', 'nattle', 'talent', 'tantle'], + 'latentness': ['latentness', 'tenantless'], + 'later': ['alert', 'alter', 'artel', 'later', 'ratel', 'taler', 'telar'], + 'latera': ['latera', 'relata'], + 'laterad': ['altared', 'laterad'], + 'lateralis': ['lateralis', 'stellaria'], + 'lateran': ['alatern', 'lateran'], + 'laterite': ['laterite', 'literate', 'teretial'], + 'laterocaudal': ['caudolateral', 'laterocaudal'], + 'laterodorsal': ['dorsolateral', 'laterodorsal'], + 'lateroventral': ['lateroventral', 'ventrolateral'], + 'latest': ['latest', 'sattle', 'taslet'], + 'latex': ['exalt', 'latex'], + 'lath': ['halt', 'lath'], + 'lathe': ['ethal', 'lathe', 'leath'], + 'latheman': ['latheman', 'methanal'], + 'lathen': ['ethnal', 'hantle', 'lathen', 'thenal'], + 'lather': ['arthel', 'halter', 'lather', 'thaler'], + 'lathery': ['earthly', 'heartly', 'lathery', 'rathely'], + 'lathing': ['halting', 'lathing', 'thingal'], + 'latian': ['antlia', 'latian', 'nalita'], + 'latibulize': ['latibulize', 'utilizable'], + 'latices': ['astelic', 'elastic', 'latices'], + 'laticlave': ['laticlave', 'vacillate'], + 'latigo': ['galiot', 'latigo'], + 'latimeria': ['latimeria', 'marialite'], + 'latin': ['altin', 'latin'], + 'latinate': ['antliate', 'latinate'], + 'latiner': ['entrail', + 'latiner', + 'latrine', + 'ratline', + 'reliant', + 'retinal', + 'trenail'], + 'latinesque': ['latinesque', 'sequential'], + 'latinian': ['antinial', 'latinian'], + 'latinizer': ['latinizer', 'trinalize'], + 'latinus': ['latinus', 'tulisan', 'unalist'], + 'lation': ['italon', 'lation', 'talion'], + 'latirostres': ['latirostres', 'setirostral'], + 'latirus': ['latirus', 'trisula'], + 'latish': ['latish', 'tahsil'], + 'latite': ['latite', 'tailet', 'tailte', 'talite'], + 'latitude': ['altitude', 'latitude'], + 'latitudinal': ['altitudinal', 'latitudinal'], + 'latitudinarian': ['altitudinarian', 'latitudinarian'], + 'latomy': ['latomy', 'tyloma'], + 'latona': ['atonal', 'latona'], + 'latonian': ['latonian', 'nataloin', 'national'], + 'latria': ['altair', 'atrail', 'atrial', 'lariat', 'latria', 'talari'], + 'latrine': ['entrail', + 'latiner', + 'latrine', + 'ratline', + 'reliant', + 'retinal', + 'trenail'], + 'latris': ['latris', 'strial'], + 'latro': ['latro', 'rotal', 'toral'], + 'latrobe': ['alberto', 'bloater', 'latrobe'], + 'latrobite': ['latrobite', 'trilobate'], + 'latrocinium': ['latrocinium', 'tourmalinic'], + 'latron': ['latron', 'lontar', 'tornal'], + 'latten': ['latent', 'latten', 'nattle', 'talent', 'tantle'], + 'latter': ['artlet', 'latter', 'rattle', 'tartle', 'tatler'], + 'latterkin': ['intertalk', 'latterkin'], + 'lattice': ['lattice', 'tactile'], + 'latticinio': ['latticinio', 'licitation'], + 'latuka': ['latuka', 'taluka'], + 'latus': ['latus', 'sault', 'talus'], + 'latvian': ['latvian', 'valiant'], + 'laubanite': ['laubanite', 'unlabiate'], + 'laud': ['auld', 'dual', 'laud', 'udal'], + 'laudation': ['adulation', 'laudation'], + 'laudator': ['adulator', 'laudator'], + 'laudatorily': ['illaudatory', 'laudatorily'], + 'laudatory': ['adulatory', 'laudatory'], + 'lauder': ['lauder', 'udaler'], + 'laudism': ['dualism', 'laudism'], + 'laudist': ['dualist', 'laudist'], + 'laumonite': ['emulation', 'laumonite'], + 'laun': ['laun', 'luna', 'ulna', 'unal'], + 'launce': ['auncel', 'cuneal', 'lacune', 'launce', 'unlace'], + 'launch': ['chulan', 'launch', 'nuchal'], + 'launcher': ['launcher', 'relaunch'], + 'laund': ['dunal', 'laund', 'lunda', 'ulnad'], + 'launder': ['launder', 'rundale'], + 'laur': ['alur', 'laur', 'lura', 'raul', 'ural'], + 'laura': ['aural', 'laura'], + 'laurel': ['allure', 'laurel'], + 'laureled': ['laureled', 'reallude'], + 'laurence': ['cerulean', 'laurence'], + 'laurent': ['laurent', 'neutral', 'unalert'], + 'laurentide': ['adulterine', 'laurentide'], + 'lauric': ['curial', 'lauric', 'uracil', 'uralic'], + 'laurin': ['laurin', 'urinal'], + 'laurite': ['laurite', 'uralite'], + 'laurus': ['laurus', 'ursula'], + 'lava': ['aval', 'lava'], + 'lavacre': ['caravel', 'lavacre'], + 'lavaret': ['larvate', 'lavaret', 'travale'], + 'lave': ['lave', 'vale', 'veal', 'vela'], + 'laveer': ['laveer', 'leaver', 'reveal', 'vealer'], + 'lavehr': ['halver', 'lavehr'], + 'lavenite': ['elvanite', 'lavenite'], + 'laver': ['arvel', 'larve', 'laver', 'ravel', 'velar'], + 'laverania': ['laverania', 'valeriana'], + 'lavic': ['cavil', 'lavic'], + 'lavinia': ['lavinia', 'vinalia'], + 'lavish': ['lavish', 'vishal'], + 'lavisher': ['lavisher', 'shrieval'], + 'lavolta': ['lavolta', 'vallota'], + 'law': ['awl', 'law'], + 'lawing': ['lawing', 'waling'], + 'lawk': ['lawk', 'walk'], + 'lawmonger': ['angleworm', 'lawmonger'], + 'lawned': ['delawn', 'lawned', 'wandle'], + 'lawner': ['lawner', 'warnel'], + 'lawny': ['lawny', 'wanly'], + 'lawrie': ['lawrie', 'wailer'], + 'lawter': ['lawter', 'walter'], + 'lawyer': ['lawyer', 'yawler'], + 'laxism': ['laxism', 'smilax'], + 'lay': ['aly', 'lay'], + 'layer': ['early', 'layer', 'relay'], + 'layered': ['delayer', 'layered', 'redelay'], + 'layery': ['layery', 'yearly'], + 'laying': ['gainly', 'laying'], + 'layman': ['amylan', 'lamany', 'layman'], + 'layne': ['laney', 'layne'], + 'layout': ['layout', 'lutayo', 'outlay'], + 'layover': ['layover', 'overlay'], + 'layship': ['apishly', 'layship'], + 'lazarlike': ['alkalizer', 'lazarlike'], + 'laze': ['laze', 'zeal'], + 'lea': ['ale', 'lea'], + 'leach': ['chela', 'lache', 'leach'], + 'leachman': ['leachman', 'mechanal'], + 'lead': ['dale', 'deal', 'lade', 'lead', 'leda'], + 'leadable': ['dealable', 'leadable'], + 'leaded': ['delead', 'leaded'], + 'leader': ['dealer', 'leader', 'redeal', 'relade', 'relead'], + 'leadership': ['dealership', 'leadership'], + 'leadin': ['aldine', 'daniel', 'delian', 'denial', 'enalid', 'leadin'], + 'leadiness': ['idealness', 'leadiness'], + 'leading': ['adeling', 'dealing', 'leading'], + 'leadman': ['daleman', 'lademan', 'leadman'], + 'leads': ['leads', 'slade'], + 'leadsman': ['dalesman', 'leadsman'], + 'leadstone': ['endosteal', 'leadstone'], + 'leady': ['delay', 'leady'], + 'leaf': ['alef', 'feal', 'flea', 'leaf'], + 'leafen': ['enleaf', 'leafen'], + 'leafit': ['fetial', 'filate', 'lafite', 'leafit'], + 'leafy': ['fleay', 'leafy'], + 'leah': ['hale', 'heal', 'leah'], + 'leak': ['kale', 'lake', 'leak'], + 'leakiness': ['alikeness', 'leakiness'], + 'leakless': ['lakeless', 'leakless'], + 'leal': ['alle', 'ella', 'leal'], + 'lealty': ['lately', 'lealty'], + 'leam': ['alem', 'alme', 'lame', 'leam', 'male', 'meal', 'mela'], + 'leamer': ['leamer', 'mealer'], + 'lean': ['alen', 'lane', 'lean', 'lena', 'nael', 'neal'], + 'leander': ['leander', 'learned', 'reladen'], + 'leaner': ['arlene', 'leaner'], + 'leaning': ['angelin', 'leaning'], + 'leant': ['ental', 'laten', 'leant'], + 'leap': ['leap', 'lepa', 'pale', 'peal', 'plea'], + 'leaper': ['leaper', 'releap', 'repale', 'repeal'], + 'leaping': ['apeling', 'leaping'], + 'leapt': ['leapt', + 'palet', + 'patel', + 'pelta', + 'petal', + 'plate', + 'pleat', + 'tepal'], + 'lear': ['earl', 'eral', 'lear', 'real'], + 'learn': ['learn', 'renal'], + 'learned': ['leander', 'learned', 'reladen'], + 'learnedly': ['ellenyard', 'learnedly'], + 'learner': ['learner', 'relearn'], + 'learnt': ['altern', 'antler', 'learnt', 'rental', 'ternal'], + 'leasable': ['leasable', 'sealable'], + 'lease': ['easel', 'lease'], + 'leaser': ['alerse', 'leaser', 'reales', 'resale', 'reseal', 'sealer'], + 'leash': ['halse', 'leash', 'selah', 'shale', 'sheal', 'shela'], + 'leasing': ['leasing', 'sealing'], + 'least': ['least', 'setal', 'slate', 'stale', 'steal', 'stela', 'tales'], + 'leat': ['atle', 'laet', 'late', 'leat', 'tael', 'tale', 'teal'], + 'leath': ['ethal', 'lathe', 'leath'], + 'leather': ['leather', 'tarheel'], + 'leatherbark': ['halterbreak', 'leatherbark'], + 'leatherer': ['leatherer', 'releather', 'tarheeler'], + 'leatman': ['amental', 'leatman'], + 'leaver': ['laveer', 'leaver', 'reveal', 'vealer'], + 'leaves': ['leaves', 'sleave'], + 'leaving': ['leaving', 'vangeli'], + 'leavy': ['leavy', 'vealy'], + 'leban': ['leban', 'nable'], + 'lebanese': ['ebenales', 'lebanese'], + 'lebensraum': ['lebensraum', 'mensurable'], + 'lecaniid': ['alcidine', 'danielic', 'lecaniid'], + 'lecanora': ['carolean', 'lecanora'], + 'lecanoroid': ['lecanoroid', 'olecranoid'], + 'lechery': ['cheerly', 'lechery'], + 'lechriodont': ['holocentrid', 'lechriodont'], + 'lecithal': ['hellicat', 'lecithal'], + 'lecontite': ['lecontite', 'nicolette'], + 'lector': ['colter', 'lector', 'torcel'], + 'lectorial': ['corallite', 'lectorial'], + 'lectorship': ['lectorship', 'leptorchis'], + 'lectrice': ['electric', 'lectrice'], + 'lecturess': ['cutleress', 'lecturess', 'truceless'], + 'lecyth': ['lecyth', 'letchy'], + 'lecythis': ['chestily', 'lecythis'], + 'led': ['del', 'eld', 'led'], + 'leda': ['dale', 'deal', 'lade', 'lead', 'leda'], + 'lede': ['dele', 'lede', 'leed'], + 'leden': ['leden', 'neeld'], + 'ledge': ['glede', 'gleed', 'ledge'], + 'ledger': ['gelder', 'ledger', 'redleg'], + 'ledging': ['gelding', 'ledging'], + 'ledgy': ['gledy', 'ledgy'], + 'lee': ['eel', 'lee'], + 'leed': ['dele', 'lede', 'leed'], + 'leek': ['keel', 'kele', 'leek'], + 'leep': ['leep', 'peel', 'pele'], + 'leepit': ['leepit', 'pelite', 'pielet'], + 'leer': ['leer', 'reel'], + 'leeringly': ['leeringly', 'reelingly'], + 'leerness': ['leerness', 'lessener'], + 'lees': ['else', 'lees', 'seel', 'sele', 'slee'], + 'leet': ['leet', 'lete', 'teel', 'tele'], + 'leetman': ['entelam', 'leetman'], + 'leewan': ['leewan', 'weanel'], + 'left': ['felt', 'flet', 'left'], + 'leftish': ['fishlet', 'leftish'], + 'leftness': ['feltness', 'leftness'], + 'leg': ['gel', 'leg'], + 'legalist': ['legalist', 'stillage'], + 'legantine': ['eglantine', 'inelegant', 'legantine'], + 'legate': ['eaglet', 'legate', 'teagle', 'telega'], + 'legatine': ['galenite', 'legatine'], + 'legation': ['gelation', 'lagonite', 'legation'], + 'legative': ['legative', 'levigate'], + 'legator': ['argolet', 'gloater', 'legator'], + 'legendary': ['enragedly', 'legendary'], + 'leger': ['leger', 'regle'], + 'legger': ['eggler', 'legger'], + 'legion': ['eloign', 'gileno', 'legion'], + 'legioner': ['eloigner', 'legioner'], + 'legionry': ['legionry', 'yeorling'], + 'legislator': ['allegorist', 'legislator'], + 'legman': ['legman', 'mangel', 'mangle'], + 'legoa': ['gloea', 'legoa'], + 'legua': ['gulae', 'legua'], + 'leguan': ['genual', 'leguan'], + 'lehr': ['herl', 'hler', 'lehr'], + 'lei': ['eli', 'lei', 'lie'], + 'leif': ['feil', 'file', 'leif', 'lief', 'life'], + 'leila': ['allie', 'leila', 'lelia'], + 'leipoa': ['apiole', 'leipoa'], + 'leisten': ['leisten', 'setline', 'tensile'], + 'leister': ['leister', 'sterile'], + 'leith': ['leith', 'lithe'], + 'leitneria': ['leitneria', 'lienteria'], + 'lek': ['elk', 'lek'], + 'lekach': ['hackle', 'lekach'], + 'lekane': ['alkene', 'lekane'], + 'lelia': ['allie', 'leila', 'lelia'], + 'leman': ['leman', 'lemna'], + 'lemma': ['lemma', 'melam'], + 'lemna': ['leman', 'lemna'], + 'lemnad': ['lemnad', 'menald'], + 'lemnian': ['lemnian', 'lineman', 'melanin'], + 'lemniscate': ['centesimal', 'lemniscate'], + 'lemon': ['lemon', 'melon', 'monel'], + 'lemonias': ['laminose', 'lemonias', 'semolina'], + 'lemonlike': ['lemonlike', 'melonlike'], + 'lemony': ['lemony', 'myelon'], + 'lemosi': ['lemosi', 'limose', 'moiles'], + 'lempira': ['impaler', 'impearl', 'lempira', 'premial'], + 'lemuria': ['lemuria', 'miauler'], + 'lemurian': ['lemurian', 'malurine', 'rumelian'], + 'lemurinae': ['lemurinae', 'neurilema'], + 'lemurine': ['lemurine', 'meruline', 'relumine'], + 'lena': ['alen', 'lane', 'lean', 'lena', 'nael', 'neal'], + 'lenad': ['eland', 'laden', 'lenad'], + 'lenape': ['alpeen', 'lenape', 'pelean'], + 'lenard': ['aldern', + 'darnel', + 'enlard', + 'lander', + 'lenard', + 'randle', + 'reland'], + 'lenca': ['canel', 'clean', 'lance', 'lenca'], + 'lencan': ['cannel', 'lencan'], + 'lendee': ['lendee', 'needle'], + 'lender': ['lender', 'relend'], + 'lendu': ['lendu', 'unled'], + 'lengthy': ['lengthy', 'thegnly'], + 'lenient': ['lenient', 'tenline'], + 'lenify': ['finely', 'lenify'], + 'lenis': ['elsin', 'lenis', 'niels', 'silen', 'sline'], + 'lenity': ['lenity', 'yetlin'], + 'lenny': ['lenny', 'lynne'], + 'leno': ['elon', 'enol', 'leno', 'leon', 'lone', 'noel'], + 'lenora': ['lenora', 'loaner', 'orlean', 'reloan'], + 'lenticel': ['lenticel', 'lenticle'], + 'lenticle': ['lenticel', 'lenticle'], + 'lentil': ['lentil', 'lintel'], + 'lentisc': ['lentisc', 'scintle', 'stencil'], + 'lentisco': ['lentisco', 'telsonic'], + 'lentiscus': ['lentiscus', 'tunicless'], + 'lento': ['lento', 'olent'], + 'lentous': ['lentous', 'sultone'], + 'lenvoy': ['lenvoy', 'ovenly'], + 'leo': ['leo', 'ole'], + 'leon': ['elon', 'enol', 'leno', 'leon', 'lone', 'noel'], + 'leonard': ['endoral', 'ladrone', 'leonard'], + 'leonhardite': ['leonhardite', 'lionhearted'], + 'leonid': ['doline', 'indole', 'leonid', 'loined', 'olenid'], + 'leonines': ['leonines', 'selenion'], + 'leonis': ['insole', 'leonis', 'lesion', 'selion'], + 'leonist': ['leonist', 'onliest'], + 'leonite': ['elonite', 'leonite'], + 'leonotis': ['leonotis', 'oilstone'], + 'leoparde': ['leoparde', 'reapdole'], + 'leopardite': ['leopardite', 'protelidae'], + 'leotard': ['delator', 'leotard'], + 'lepa': ['leap', 'lepa', 'pale', 'peal', 'plea'], + 'lepanto': ['lepanto', 'nepotal', 'petalon', 'polenta'], + 'lepas': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'lepcha': ['chapel', 'lepcha', 'pleach'], + 'leper': ['leper', 'perle', 'repel'], + 'leperdom': ['leperdom', 'premodel'], + 'lepidopter': ['dopplerite', 'lepidopter'], + 'lepidosauria': ['lepidosauria', 'pliosauridae'], + 'lepidote': ['lepidote', 'petioled'], + 'lepidotic': ['diploetic', 'lepidotic'], + 'lepisma': ['ampelis', 'lepisma'], + 'leporid': ['leporid', 'leproid'], + 'leporis': ['leporis', 'spoiler'], + 'lepra': ['lepra', 'paler', 'parel', 'parle', 'pearl', 'perla', 'relap'], + 'leproid': ['leporid', 'leproid'], + 'leproma': ['leproma', 'palermo', 'pleroma', 'polearm'], + 'leprosied': ['despoiler', 'leprosied'], + 'leprosis': ['leprosis', 'plerosis'], + 'leprous': ['leprous', 'pelorus', 'sporule'], + 'leptandra': ['leptandra', 'peltandra'], + 'leptidae': ['depilate', 'leptidae', 'pileated'], + 'leptiform': ['leptiform', 'peltiform'], + 'leptodora': ['doorplate', 'leptodora'], + 'leptome': ['leptome', 'poemlet'], + 'lepton': ['lepton', 'pentol'], + 'leptonema': ['leptonema', 'ptolemean'], + 'leptorchis': ['lectorship', 'leptorchis'], + 'lepus': ['lepus', 'pulse'], + 'ler': ['ler', 'rel'], + 'lernaean': ['annealer', 'lernaean', 'reanneal'], + 'lerot': ['lerot', 'orlet', 'relot'], + 'lerwa': ['lerwa', 'waler'], + 'les': ['els', 'les'], + 'lesath': ['haslet', 'lesath', 'shelta'], + 'lesbia': ['isabel', 'lesbia'], + 'lesche': ['lesche', 'sleech'], + 'lesion': ['insole', 'leonis', 'lesion', 'selion'], + 'lesional': ['lesional', 'solenial'], + 'leslie': ['leslie', 'sellie'], + 'lessener': ['leerness', 'lessener'], + 'lest': ['lest', 'selt'], + 'lester': ['lester', 'selter', 'streel'], + 'let': ['elt', 'let'], + 'letchy': ['lecyth', 'letchy'], + 'lete': ['leet', 'lete', 'teel', 'tele'], + 'lethargus': ['lethargus', 'slaughter'], + 'lethe': ['ethel', 'lethe'], + 'lethean': ['entheal', 'lethean'], + 'lethologica': ['ethological', 'lethologica', 'theological'], + 'letitia': ['italite', 'letitia', 'tilaite'], + 'leto': ['leto', 'lote', 'tole'], + 'letoff': ['letoff', 'offlet'], + 'lett': ['lett', 'telt'], + 'letten': ['letten', 'nettle'], + 'letterer': ['letterer', 'reletter'], + 'lettish': ['lettish', 'thistle'], + 'lettrin': ['lettrin', 'trintle'], + 'leu': ['leu', 'lue', 'ule'], + 'leucadian': ['leucadian', 'lucanidae'], + 'leucocism': ['leucocism', 'muscicole'], + 'leucoma': ['caulome', 'leucoma'], + 'leucosis': ['coulisse', 'leucosis', 'ossicule'], + 'leud': ['deul', 'duel', 'leud'], + 'leuk': ['leuk', 'luke'], + 'leuma': ['amelu', 'leuma', 'ulema'], + 'leung': ['leung', 'lunge'], + 'levance': ['enclave', 'levance', 'valence'], + 'levant': ['levant', 'valent'], + 'levanter': ['levanter', 'relevant', 'revelant'], + 'levantine': ['levantine', 'valentine'], + 'leveler': ['leveler', 'relevel'], + 'lever': ['elver', 'lever', 'revel'], + 'leverer': ['leverer', 'reveler'], + 'levi': ['evil', 'levi', 'live', 'veil', 'vile', 'vlei'], + 'levier': ['levier', 'relive', 'reveil', 'revile', 'veiler'], + 'levigate': ['legative', 'levigate'], + 'levin': ['levin', 'liven'], + 'levining': ['levining', 'nievling'], + 'levir': ['levir', 'liver', 'livre', 'rivel'], + 'levirate': ['levirate', 'relative'], + 'levis': ['elvis', 'levis', 'slive'], + 'levitation': ['levitation', 'tonalitive', 'velitation'], + 'levo': ['levo', 'love', 'velo', 'vole'], + 'levyist': ['levyist', 'sylvite'], + 'lewd': ['lewd', 'weld'], + 'lewis': ['lewis', 'swile'], + 'lexia': ['axile', 'lexia'], + 'ley': ['ley', 'lye'], + 'lhota': ['altho', 'lhota', 'loath'], + 'liability': ['alibility', 'liability'], + 'liable': ['alible', 'belial', 'labile', 'liable'], + 'liana': ['alain', 'alani', 'liana'], + 'liang': ['algin', 'align', 'langi', 'liang', 'linga'], + 'liar': ['aril', 'lair', 'lari', 'liar', 'lira', 'rail', 'rial'], + 'liard': ['drail', 'laird', 'larid', 'liard'], + 'lias': ['lasi', 'lias', 'lisa', 'sail', 'sial'], + 'liatris': ['liatris', 'trilisa'], + 'libament': ['bailment', 'libament'], + 'libate': ['albeit', + 'albite', + 'baltei', + 'belait', + 'betail', + 'bletia', + 'libate'], + 'libationer': ['libationer', 'liberation'], + 'libber': ['libber', 'ribble'], + 'libby': ['bilby', 'libby'], + 'libellary': ['libellary', 'liberally'], + 'liber': ['birle', 'liber'], + 'liberal': ['braille', 'liberal'], + 'liberally': ['libellary', 'liberally'], + 'liberate': ['iterable', 'liberate'], + 'liberation': ['libationer', 'liberation'], + 'liberator': ['liberator', 'orbitelar'], + 'liberian': ['bilinear', 'liberian'], + 'libertas': ['abristle', 'libertas'], + 'libertine': ['berlinite', 'libertine'], + 'libra': ['blair', 'brail', 'libra'], + 'librate': ['betrail', 'librate', 'triable', 'trilabe'], + 'licania': ['lacinia', 'licania'], + 'license': ['license', 'selenic', 'silence'], + 'licensed': ['licensed', 'silenced'], + 'licenser': ['licenser', 'silencer'], + 'licensor': ['cresolin', 'licensor'], + 'lich': ['chil', 'lich'], + 'lichanos': ['lichanos', 'nicholas'], + 'lichenoid': ['cheloniid', 'lichenoid'], + 'lichi': ['chili', 'lichi'], + 'licitation': ['latticinio', 'licitation'], + 'licker': ['licker', 'relick', 'rickle'], + 'lickspit': ['lickspit', 'lipstick'], + 'licorne': ['creolin', 'licorne', 'locrine'], + 'lida': ['dail', 'dali', 'dial', 'laid', 'lida'], + 'lidded': ['diddle', 'lidded'], + 'lidder': ['lidder', 'riddel', 'riddle'], + 'lide': ['idle', 'lide', 'lied'], + 'lie': ['eli', 'lei', 'lie'], + 'lied': ['idle', 'lide', 'lied'], + 'lief': ['feil', 'file', 'leif', 'lief', 'life'], + 'lien': ['lien', 'line', 'neil', 'nile'], + 'lienal': ['lienal', 'lineal'], + 'lienee': ['eileen', 'lienee'], + 'lienor': ['elinor', 'lienor', 'lorien', 'noiler'], + 'lienteria': ['leitneria', 'lienteria'], + 'lientery': ['entirely', 'lientery'], + 'lier': ['lier', 'lire', 'rile'], + 'lierne': ['lierne', 'reline'], + 'lierre': ['lierre', 'relier'], + 'liesh': ['liesh', 'shiel'], + 'lievaart': ['lievaart', 'varietal'], + 'life': ['feil', 'file', 'leif', 'lief', 'life'], + 'lifelike': ['filelike', 'lifelike'], + 'lifer': ['filer', 'flier', 'lifer', 'rifle'], + 'lifeward': ['drawfile', 'lifeward'], + 'lifo': ['filo', 'foil', 'lifo'], + 'lift': ['flit', 'lift'], + 'lifter': ['fertil', 'filter', 'lifter', 'relift', 'trifle'], + 'lifting': ['fliting', 'lifting'], + 'ligament': ['ligament', 'metaling', 'tegminal'], + 'ligas': ['gisla', 'ligas', 'sigla'], + 'ligate': ['aiglet', 'ligate', 'taigle', 'tailge'], + 'ligation': ['intaglio', 'ligation'], + 'ligator': ['goitral', 'larigot', 'ligator'], + 'ligature': ['alurgite', 'ligature'], + 'lighten': ['enlight', 'lighten'], + 'lightener': ['lightener', 'relighten', 'threeling'], + 'lighter': ['lighter', 'relight', 'rightle'], + 'lighthead': ['headlight', 'lighthead'], + 'lightness': ['lightness', 'nightless', 'thingless'], + 'ligne': ['ingle', 'ligne', 'linge', 'nigel'], + 'lignin': ['lignin', 'lining'], + 'lignitic': ['lignitic', 'tiglinic'], + 'lignose': ['gelosin', 'lignose'], + 'ligroine': ['ligroine', 'religion'], + 'ligure': ['ligure', 'reguli'], + 'lija': ['jail', 'lija'], + 'like': ['kiel', 'like'], + 'liken': ['inkle', 'liken'], + 'likewise': ['likewise', 'wiselike'], + 'lilac': ['calli', 'lilac'], + 'lilacky': ['alkylic', 'lilacky'], + 'lilium': ['illium', 'lilium'], + 'lilt': ['lilt', 'till'], + 'lily': ['illy', 'lily', 'yill'], + 'lim': ['lim', 'mil'], + 'lima': ['amil', 'amli', 'lima', 'mail', 'mali', 'mila'], + 'limacina': ['animalic', 'limacina'], + 'limacon': ['limacon', 'malonic'], + 'liman': ['lamin', 'liman', 'milan'], + 'limation': ['limation', 'miltonia'], + 'limbat': ['limbat', 'timbal'], + 'limbate': ['limbate', 'timable', 'timbale'], + 'limbed': ['dimble', 'limbed'], + 'limbus': ['bluism', 'limbus'], + 'limby': ['blimy', 'limby'], + 'lime': ['emil', 'lime', 'mile'], + 'limean': ['limean', 'maline', 'melian', 'menial'], + 'limeman': ['ammelin', 'limeman'], + 'limer': ['limer', 'meril', 'miler'], + 'limes': ['limes', 'miles', 'slime', 'smile'], + 'limestone': ['limestone', 'melonites', 'milestone'], + 'limey': ['elymi', 'emily', 'limey'], + 'liminess': ['liminess', 'senilism'], + 'limitary': ['limitary', 'military'], + 'limitate': ['limitate', 'militate'], + 'limitation': ['limitation', 'militation'], + 'limited': ['delimit', 'limited'], + 'limiter': ['limiter', 'relimit'], + 'limitless': ['limitless', 'semistill'], + 'limner': ['limner', 'merlin', 'milner'], + 'limnetic': ['limnetic', 'milicent'], + 'limoniad': ['dominial', 'imolinda', 'limoniad'], + 'limosa': ['limosa', 'somali'], + 'limose': ['lemosi', 'limose', 'moiles'], + 'limp': ['limp', 'pilm', 'plim'], + 'limper': ['limper', 'prelim', 'rimple'], + 'limping': ['impling', 'limping'], + 'limpsy': ['limpsy', 'simply'], + 'limpy': ['imply', 'limpy', 'pilmy'], + 'limsy': ['limsy', 'slimy', 'smily'], + 'lin': ['lin', 'nil'], + 'lina': ['alin', 'anil', 'lain', 'lina', 'nail'], + 'linaga': ['agnail', 'linaga'], + 'linage': ['algine', 'genial', 'linage'], + 'linamarin': ['laminarin', 'linamarin'], + 'linarite': ['inertial', 'linarite'], + 'linchet': ['linchet', 'tinchel'], + 'linctus': ['clunist', 'linctus'], + 'linda': ['danli', 'ladin', 'linda', 'nidal'], + 'lindane': ['annelid', 'lindane'], + 'linder': ['linder', 'rindle'], + 'lindoite': ['lindoite', 'tolidine'], + 'lindsay': ['islandy', 'lindsay'], + 'line': ['lien', 'line', 'neil', 'nile'], + 'linea': ['alien', 'aline', 'anile', 'elain', 'elian', 'laine', 'linea'], + 'lineal': ['lienal', 'lineal'], + 'lineament': ['lineament', 'manteline'], + 'linear': ['arline', 'larine', 'linear', 'nailer', 'renail'], + 'linearity': ['inreality', 'linearity'], + 'lineate': ['elatine', 'lineate'], + 'lineature': ['lineature', 'rutelinae'], + 'linecut': ['linecut', 'tunicle'], + 'lined': ['eldin', 'lined'], + 'lineman': ['lemnian', 'lineman', 'melanin'], + 'linen': ['linen', 'linne'], + 'linesman': ['annelism', 'linesman'], + 'linet': ['inlet', 'linet'], + 'linga': ['algin', 'align', 'langi', 'liang', 'linga'], + 'lingbird': ['birdling', 'bridling', 'lingbird'], + 'linge': ['ingle', 'ligne', 'linge', 'nigel'], + 'linger': ['linger', 'ringle'], + 'lingo': ['lingo', 'login'], + 'lingtow': ['lingtow', 'twoling'], + 'lingua': ['gaulin', 'lingua'], + 'lingual': ['lingual', 'lingula'], + 'linguidental': ['dentilingual', 'indulgential', 'linguidental'], + 'lingula': ['lingual', 'lingula'], + 'linguodental': ['dentolingual', 'linguodental'], + 'lingy': ['lingy', 'lying'], + 'linha': ['linha', 'nihal'], + 'lining': ['lignin', 'lining'], + 'link': ['kiln', 'link'], + 'linked': ['kindle', 'linked'], + 'linker': ['linker', 'relink'], + 'linking': ['inkling', 'linking'], + 'linkman': ['kilnman', 'linkman'], + 'links': ['links', 'slink'], + 'linnaea': ['alanine', 'linnaea'], + 'linnaean': ['annaline', 'linnaean'], + 'linne': ['linen', 'linne'], + 'linnet': ['linnet', 'linten'], + 'lino': ['lino', 'lion', 'loin', 'noil'], + 'linolenic': ['encinillo', 'linolenic'], + 'linometer': ['linometer', 'nilometer'], + 'linopteris': ['linopteris', 'prosilient'], + 'linous': ['insoul', 'linous', 'nilous', 'unsoil'], + 'linsey': ['linsey', 'lysine'], + 'linstock': ['coltskin', 'linstock'], + 'lintel': ['lentil', 'lintel'], + 'linten': ['linnet', 'linten'], + 'lintseed': ['enlisted', 'lintseed'], + 'linum': ['linum', 'ulmin'], + 'linus': ['linus', 'sunil'], + 'liny': ['inly', 'liny'], + 'lion': ['lino', 'lion', 'loin', 'noil'], + 'lioncel': ['colline', 'lioncel'], + 'lionel': ['lionel', 'niello'], + 'lionet': ['entoil', 'lionet'], + 'lionhearted': ['leonhardite', 'lionhearted'], + 'lipa': ['lipa', 'pail', 'pali', 'pial'], + 'lipan': ['lipan', 'pinal', 'plain'], + 'liparis': ['aprilis', 'liparis'], + 'liparite': ['liparite', 'reptilia'], + 'liparous': ['liparous', 'pliosaur'], + 'lipase': ['espial', 'lipase', 'pelias'], + 'lipin': ['lipin', 'pilin'], + 'liplet': ['liplet', 'pillet'], + 'lipochondroma': ['chondrolipoma', 'lipochondroma'], + 'lipoclasis': ['calliopsis', 'lipoclasis'], + 'lipocyte': ['epicotyl', 'lipocyte'], + 'lipofibroma': ['fibrolipoma', 'lipofibroma'], + 'lipolytic': ['lipolytic', 'politicly'], + 'lipoma': ['lipoma', 'pimola', 'ploima'], + 'lipomyoma': ['lipomyoma', 'myolipoma'], + 'lipomyxoma': ['lipomyxoma', 'myxolipoma'], + 'liposis': ['liposis', 'pilosis'], + 'lipotype': ['lipotype', 'polypite'], + 'lippen': ['lippen', 'nipple'], + 'lipper': ['lipper', 'ripple'], + 'lippia': ['lippia', 'pilpai'], + 'lipsanotheca': ['lipsanotheca', 'sphacelation'], + 'lipstick': ['lickspit', 'lipstick'], + 'liquate': ['liquate', 'tequila'], + 'liquidate': ['liquidate', 'qualitied'], + 'lira': ['aril', 'lair', 'lari', 'liar', 'lira', 'rail', 'rial'], + 'lirate': ['lirate', 'retail', 'retial', 'tailer'], + 'liration': ['liration', 'litorina'], + 'lire': ['lier', 'lire', 'rile'], + 'lis': ['lis', 'sil'], + 'lisa': ['lasi', 'lias', 'lisa', 'sail', 'sial'], + 'lise': ['isle', 'lise', 'sile'], + 'lisere': ['lisere', 'resile'], + 'lisk': ['lisk', 'silk', 'skil'], + 'lisle': ['lisle', 'selli'], + 'lisp': ['lisp', 'slip'], + 'lisper': ['lisper', 'pliers', 'sirple', 'spiler'], + 'list': ['list', 'silt', 'slit'], + 'listable': ['bastille', 'listable'], + 'listen': ['enlist', 'listen', 'silent', 'tinsel'], + 'listener': ['enlister', 'esterlin', 'listener', 'relisten'], + 'lister': ['lister', 'relist'], + 'listera': ['aletris', 'alister', 'listera', 'realist', 'saltier'], + 'listerian': ['listerian', 'trisilane'], + 'listerine': ['listerine', 'resilient'], + 'listerize': ['listerize', 'sterilize'], + 'listing': ['listing', 'silting'], + 'listless': ['listless', 'slitless'], + 'lisuarte': ['auletris', 'lisuarte'], + 'lit': ['lit', 'til'], + 'litas': ['alist', 'litas', 'slait', 'talis'], + 'litchi': ['litchi', 'lithic'], + 'lite': ['lite', 'teil', 'teli', 'tile'], + 'liter': ['liter', 'tiler'], + 'literal': ['literal', 'tallier'], + 'literary': ['literary', 'trailery'], + 'literate': ['laterite', 'literate', 'teretial'], + 'literose': ['literose', 'roselite', 'tirolese'], + 'lith': ['hilt', 'lith'], + 'litharge': ['litharge', 'thirlage'], + 'lithe': ['leith', 'lithe'], + 'lithectomy': ['lithectomy', 'methylotic'], + 'lithic': ['litchi', 'lithic'], + 'litho': ['litho', 'thiol', 'tholi'], + 'lithochromography': ['chromolithography', 'lithochromography'], + 'lithocyst': ['cystolith', 'lithocyst'], + 'lithonephria': ['lithonephria', 'philotherian'], + 'lithonephrotomy': ['lithonephrotomy', 'nephrolithotomy'], + 'lithophane': ['anthophile', 'lithophane'], + 'lithophone': ['lithophone', 'thiophenol'], + 'lithophotography': ['lithophotography', 'photolithography'], + 'lithophysal': ['isophthalyl', 'lithophysal'], + 'lithopone': ['lithopone', 'phonolite'], + 'lithous': ['lithous', 'loutish'], + 'litigate': ['litigate', 'tagilite'], + 'litmus': ['litmus', 'tilmus'], + 'litorina': ['liration', 'litorina'], + 'litorinidae': ['idioretinal', 'litorinidae'], + 'litra': ['litra', 'trail', 'trial'], + 'litsea': ['isleta', 'litsea', 'salite', 'stelai'], + 'litster': ['litster', 'slitter', 'stilter', 'testril'], + 'litten': ['litten', 'tinlet'], + 'litter': ['litter', 'tilter', 'titler'], + 'littery': ['littery', 'tritely'], + 'littoral': ['littoral', 'tortilla'], + 'lituiform': ['lituiform', 'trifolium'], + 'litus': ['litus', 'sluit', 'tulsi'], + 'live': ['evil', 'levi', 'live', 'veil', 'vile', 'vlei'], + 'lived': ['devil', 'divel', 'lived'], + 'livedo': ['livedo', 'olived'], + 'liveliness': ['liveliness', 'villeiness'], + 'livelong': ['livelong', 'loveling'], + 'lively': ['evilly', 'lively', 'vilely'], + 'liven': ['levin', 'liven'], + 'liveness': ['evilness', 'liveness', 'veinless', 'vileness', 'vineless'], + 'liver': ['levir', 'liver', 'livre', 'rivel'], + 'livered': ['deliver', 'deviler', 'livered'], + 'livery': ['livery', 'verily'], + 'livier': ['livier', 'virile'], + 'livonian': ['livonian', 'violanin'], + 'livre': ['levir', 'liver', 'livre', 'rivel'], + 'liwan': ['inlaw', 'liwan'], + 'llandeilo': ['diallelon', 'llandeilo'], + 'llew': ['llew', 'well'], + 'lloyd': ['dolly', 'lloyd'], + 'loa': ['alo', 'lao', 'loa'], + 'loach': ['chola', 'loach', 'olcha'], + 'load': ['alod', 'dola', 'load', 'odal'], + 'loaden': ['enodal', 'loaden'], + 'loader': ['loader', 'ordeal', 'reload'], + 'loading': ['angloid', 'loading'], + 'loaf': ['foal', 'loaf', 'olaf'], + 'loam': ['loam', 'loma', 'malo', 'mola', 'olam'], + 'loaminess': ['loaminess', 'melanosis'], + 'loaming': ['almoign', 'loaming'], + 'loamy': ['amylo', 'loamy'], + 'loaner': ['lenora', 'loaner', 'orlean', 'reloan'], + 'loasa': ['alosa', 'loasa', 'oasal'], + 'loath': ['altho', 'lhota', 'loath'], + 'loather': ['loather', 'rathole'], + 'loathly': ['loathly', 'tallyho'], + 'lob': ['blo', 'lob'], + 'lobar': ['balor', 'bolar', 'boral', 'labor', 'lobar'], + 'lobate': ['lobate', 'oblate'], + 'lobated': ['bloated', 'lobated'], + 'lobately': ['lobately', 'oblately'], + 'lobation': ['boltonia', 'lobation', 'oblation'], + 'lobe': ['bleo', 'bole', 'lobe'], + 'lobed': ['bodle', 'boled', 'lobed'], + 'lobelet': ['bellote', 'lobelet'], + 'lobelia': ['bolelia', 'lobelia', 'obelial'], + 'lobing': ['globin', 'goblin', 'lobing'], + 'lobo': ['bolo', 'bool', 'lobo', 'obol'], + 'lobola': ['balolo', 'lobola'], + 'lobscourse': ['lobscourse', 'lobscouser'], + 'lobscouser': ['lobscourse', 'lobscouser'], + 'lobster': ['bolster', 'lobster'], + 'loca': ['alco', 'coal', 'cola', 'loca'], + 'local': ['callo', 'colla', 'local'], + 'locanda': ['acnodal', 'canadol', 'locanda'], + 'locarnite': ['alectrion', 'clarionet', 'crotaline', 'locarnite'], + 'locarnize': ['laconizer', 'locarnize'], + 'locarno': ['coronal', 'locarno'], + 'locate': ['acetol', 'colate', 'locate'], + 'location': ['colation', 'coontail', 'location'], + 'locational': ['allocation', 'locational'], + 'locator': ['crotalo', 'locator'], + 'loch': ['chol', 'loch'], + 'lochan': ['chalon', 'lochan'], + 'lochetic': ['helcotic', 'lochetic', 'ochletic'], + 'lochus': ['holcus', 'lochus', 'slouch'], + 'loci': ['clio', 'coil', 'coli', 'loci'], + 'lociation': ['coalition', 'lociation'], + 'lock': ['colk', 'lock'], + 'locker': ['locker', 'relock'], + 'lockpin': ['lockpin', 'pinlock'], + 'lockram': ['lockram', 'marlock'], + 'lockspit': ['lockspit', 'lopstick'], + 'lockup': ['lockup', 'uplock'], + 'loco': ['cool', 'loco'], + 'locoweed': ['coolweed', 'locoweed'], + 'locrian': ['carolin', 'clarion', 'colarin', 'locrian'], + 'locrine': ['creolin', 'licorne', 'locrine'], + 'loculate': ['allocute', 'loculate'], + 'loculation': ['allocution', 'loculation'], + 'locum': ['cumol', 'locum'], + 'locusta': ['costula', 'locusta', 'talcous'], + 'lod': ['dol', 'lod', 'old'], + 'lode': ['dole', 'elod', 'lode', 'odel'], + 'lodesman': ['dolesman', 'lodesman'], + 'lodgeman': ['angeldom', 'lodgeman'], + 'lodger': ['golder', 'lodger'], + 'lodging': ['godling', 'lodging'], + 'loess': ['loess', 'soles'], + 'loessic': ['loessic', 'ossicle'], + 'lof': ['flo', 'lof'], + 'loft': ['flot', 'loft'], + 'lofter': ['floret', 'forlet', 'lofter', 'torfel'], + 'lofty': ['lofty', 'oftly'], + 'log': ['gol', 'log'], + 'logania': ['alogian', 'logania'], + 'logarithm': ['algorithm', 'logarithm'], + 'logarithmic': ['algorithmic', 'logarithmic'], + 'loge': ['egol', 'goel', 'loge', 'ogle', 'oleg'], + 'logger': ['logger', 'roggle'], + 'logicalist': ['logicalist', 'logistical'], + 'logicist': ['logicist', 'logistic'], + 'login': ['lingo', 'login'], + 'logistic': ['logicist', 'logistic'], + 'logistical': ['logicalist', 'logistical'], + 'logistics': ['glossitic', 'logistics'], + 'logman': ['amlong', 'logman'], + 'logographic': ['graphologic', 'logographic'], + 'logographical': ['graphological', 'logographical'], + 'logography': ['graphology', 'logography'], + 'logoi': ['igloo', 'logoi'], + 'logometrical': ['logometrical', 'metrological'], + 'logos': ['gools', 'logos'], + 'logotypy': ['logotypy', 'typology'], + 'logria': ['gloria', 'larigo', 'logria'], + 'logy': ['gloy', 'logy'], + 'lohar': ['horal', 'lohar'], + 'loin': ['lino', 'lion', 'loin', 'noil'], + 'loined': ['doline', 'indole', 'leonid', 'loined', 'olenid'], + 'loir': ['loir', 'lori', 'roil'], + 'lois': ['lois', 'silo', 'siol', 'soil', 'soli'], + 'loiter': ['loiter', 'toiler', 'triole'], + 'loka': ['kalo', 'kola', 'loka'], + 'lokao': ['lokao', 'oolak'], + 'loke': ['koel', 'loke'], + 'loket': ['ketol', 'loket'], + 'lola': ['lalo', 'lola', 'olla'], + 'loma': ['loam', 'loma', 'malo', 'mola', 'olam'], + 'lomatine': ['lomatine', 'tolamine'], + 'lombardian': ['laminboard', 'lombardian'], + 'lomboy': ['bloomy', 'lomboy'], + 'loment': ['loment', 'melton', 'molten'], + 'lomentaria': ['ameliorant', 'lomentaria'], + 'lomita': ['lomita', 'tomial'], + 'lone': ['elon', 'enol', 'leno', 'leon', 'lone', 'noel'], + 'longa': ['along', 'gonal', 'lango', 'longa', 'nogal'], + 'longanimous': ['longanimous', 'longimanous'], + 'longbeard': ['boglander', 'longbeard'], + 'longear': ['argenol', 'longear'], + 'longhead': ['headlong', 'longhead'], + 'longimanous': ['longanimous', 'longimanous'], + 'longimetry': ['longimetry', 'mongrelity'], + 'longmouthed': ['goldenmouth', 'longmouthed'], + 'longue': ['longue', 'lounge'], + 'lonicera': ['acrolein', + 'arecolin', + 'caroline', + 'colinear', + 'cornelia', + 'creolian', + 'lonicera'], + 'lontar': ['latron', 'lontar', 'tornal'], + 'looby': ['booly', 'looby'], + 'lood': ['dool', 'lood'], + 'loof': ['fool', 'loof', 'olof'], + 'look': ['kolo', 'look'], + 'looker': ['looker', 'relook'], + 'lookout': ['lookout', 'outlook'], + 'loom': ['loom', 'mool'], + 'loon': ['loon', 'nolo'], + 'loop': ['loop', 'polo', 'pool'], + 'looper': ['looper', 'pooler'], + 'loopist': ['loopist', 'poloist', 'topsoil'], + 'loopy': ['loopy', 'pooly'], + 'loosing': ['loosing', 'sinolog'], + 'loot': ['loot', 'tool'], + 'looter': ['looter', 'retool', 'rootle', 'tooler'], + 'lootie': ['lootie', 'oolite'], + 'lop': ['lop', 'pol'], + 'lope': ['lope', 'olpe', 'pole'], + 'loper': ['loper', 'poler'], + 'lopezia': ['epizoal', 'lopezia', 'opalize'], + 'lophine': ['lophine', 'pinhole'], + 'lophocomi': ['homopolic', 'lophocomi'], + 'loppet': ['loppet', 'topple'], + 'loppy': ['loppy', 'polyp'], + 'lopstick': ['lockspit', 'lopstick'], + 'loquacious': ['aquicolous', 'loquacious'], + 'lora': ['lora', 'oral'], + 'lorandite': ['lorandite', 'rodential'], + 'lorate': ['elator', 'lorate'], + 'lorcha': ['choral', 'lorcha'], + 'lordly': ['drolly', 'lordly'], + 'lore': ['lore', 'orle', 'role'], + 'lored': ['lored', 'older'], + 'loren': ['enrol', 'loren'], + 'lori': ['loir', 'lori', 'roil'], + 'lorica': ['caroli', 'corial', 'lorica'], + 'loricate': ['calorite', 'erotical', 'loricate'], + 'loricati': ['clitoria', 'loricati'], + 'lorien': ['elinor', 'lienor', 'lorien', 'noiler'], + 'loro': ['loro', 'olor', 'orlo', 'rool'], + 'lose': ['lose', 'sloe', 'sole'], + 'loser': ['loser', 'orsel', 'rosel', 'soler'], + 'lost': ['lost', 'lots', 'slot'], + 'lot': ['lot', 'tol'], + 'lota': ['alto', 'lota'], + 'lotase': ['lotase', 'osteal', 'solate', 'stolae', 'talose'], + 'lote': ['leto', 'lote', 'tole'], + 'lotic': ['cloit', 'lotic'], + 'lotrite': ['lotrite', 'tortile', 'triolet'], + 'lots': ['lost', 'lots', 'slot'], + 'lotta': ['lotta', 'total'], + 'lotter': ['lotter', 'rottle', 'tolter'], + 'lottie': ['lottie', 'toilet', 'tolite'], + 'lou': ['lou', 'luo'], + 'loud': ['loud', 'ludo'], + 'louden': ['louden', 'nodule'], + 'lough': ['ghoul', 'lough'], + 'lounder': ['durenol', 'lounder', 'roundel'], + 'lounge': ['longue', 'lounge'], + 'loupe': ['epulo', 'loupe'], + 'lourdy': ['dourly', 'lourdy'], + 'louse': ['eusol', 'louse'], + 'lousy': ['lousy', 'souly'], + 'lout': ['lout', 'tolu'], + 'louter': ['elutor', 'louter', 'outler'], + 'loutish': ['lithous', 'loutish'], + 'louty': ['louty', 'outly'], + 'louvar': ['louvar', 'ovular'], + 'louver': ['louver', 'louvre'], + 'louvre': ['louver', 'louvre'], + 'lovable': ['lovable', 'volable'], + 'lovage': ['lovage', 'volage'], + 'love': ['levo', 'love', 'velo', 'vole'], + 'loveling': ['livelong', 'loveling'], + 'lovely': ['lovely', 'volley'], + 'lovering': ['lovering', 'overling'], + 'low': ['low', 'lwo', 'owl'], + 'lowa': ['alow', 'awol', 'lowa'], + 'lowder': ['lowder', 'weldor', 'wordle'], + 'lower': ['lower', 'owler', 'rowel'], + 'lowerer': ['lowerer', 'relower'], + 'lowery': ['lowery', 'owlery', 'rowley', 'yowler'], + 'lowish': ['lowish', 'owlish'], + 'lowishly': ['lowishly', 'owlishly', 'sillyhow'], + 'lowishness': ['lowishness', 'owlishness'], + 'lowy': ['lowy', 'owly', 'yowl'], + 'loyal': ['alloy', 'loyal'], + 'loyalism': ['loyalism', 'lysiloma'], + 'loyd': ['loyd', 'odyl'], + 'luba': ['balu', 'baul', 'bual', 'luba'], + 'lubber': ['burble', 'lubber', 'rubble'], + 'lubberland': ['landlubber', 'lubberland'], + 'lube': ['blue', 'lube'], + 'lucan': ['lucan', 'nucal'], + 'lucania': ['lucania', 'luciana'], + 'lucanid': ['dulcian', 'incudal', 'lucanid', 'lucinda'], + 'lucanidae': ['leucadian', 'lucanidae'], + 'lucarne': ['crenula', 'lucarne', 'nuclear', 'unclear'], + 'lucban': ['buncal', 'lucban'], + 'luce': ['clue', 'luce'], + 'luceres': ['luceres', 'recluse'], + 'lucern': ['encurl', 'lucern'], + 'lucernal': ['lucernal', 'nucellar', 'uncellar'], + 'lucet': ['culet', 'lucet'], + 'lucia': ['aulic', 'lucia'], + 'lucian': ['cunila', 'lucian', 'lucina', 'uncial'], + 'luciana': ['lucania', 'luciana'], + 'lucifer': ['ferulic', 'lucifer'], + 'lucigen': ['glucine', 'lucigen'], + 'lucina': ['cunila', 'lucian', 'lucina', 'uncial'], + 'lucinda': ['dulcian', 'incudal', 'lucanid', 'lucinda'], + 'lucinoid': ['lucinoid', 'oculinid'], + 'lucite': ['lucite', 'luetic', 'uletic'], + 'lucrative': ['lucrative', 'revictual', 'victualer'], + 'lucre': ['cruel', 'lucre', 'ulcer'], + 'lucretia': ['arculite', 'cutleria', 'lucretia', 'reticula', 'treculia'], + 'lucretian': ['centurial', 'lucretian', 'ultranice'], + 'luctiferous': ['fruticulose', 'luctiferous'], + 'lucubrate': ['lucubrate', 'tubercula'], + 'ludden': ['ludden', 'nuddle'], + 'luddite': ['diluted', 'luddite'], + 'ludian': ['dualin', 'ludian', 'unlaid'], + 'ludibry': ['buirdly', 'ludibry'], + 'ludicroserious': ['ludicroserious', 'serioludicrous'], + 'ludo': ['loud', 'ludo'], + 'lue': ['leu', 'lue', 'ule'], + 'lues': ['lues', 'slue'], + 'luetic': ['lucite', 'luetic', 'uletic'], + 'lug': ['gul', 'lug'], + 'luge': ['glue', 'gule', 'luge'], + 'luger': ['gluer', 'gruel', 'luger'], + 'lugger': ['gurgle', 'lugger', 'ruggle'], + 'lugnas': ['lugnas', 'salung'], + 'lugsome': ['glumose', 'lugsome'], + 'luian': ['inula', 'luian', 'uinal'], + 'luiseno': ['elusion', 'luiseno'], + 'luite': ['luite', 'utile'], + 'lukas': ['klaus', 'lukas', 'sulka'], + 'luke': ['leuk', 'luke'], + 'lula': ['lula', 'ulla'], + 'lulab': ['bulla', 'lulab'], + 'lumbar': ['brumal', 'labrum', 'lumbar', 'umbral'], + 'lumber': ['lumber', 'rumble', 'umbrel'], + 'lumbosacral': ['lumbosacral', 'sacrolumbal'], + 'lumine': ['lumine', 'unlime'], + 'lump': ['lump', 'plum'], + 'lumper': ['lumper', 'plumer', 'replum', 'rumple'], + 'lumpet': ['lumpet', 'plumet'], + 'lumpiness': ['lumpiness', 'pluminess'], + 'lumpy': ['lumpy', 'plumy'], + 'luna': ['laun', 'luna', 'ulna', 'unal'], + 'lunacy': ['lunacy', 'unclay'], + 'lunar': ['lunar', 'ulnar', 'urnal'], + 'lunare': ['lunare', 'neural', 'ulnare', 'unreal'], + 'lunaria': ['lunaria', 'ulnaria', 'uralian'], + 'lunary': ['lunary', 'uranyl'], + 'lunatic': ['calinut', 'lunatic'], + 'lunation': ['lunation', 'ultonian'], + 'lunda': ['dunal', 'laund', 'lunda', 'ulnad'], + 'lung': ['gunl', 'lung'], + 'lunge': ['leung', 'lunge'], + 'lunged': ['gulden', 'lunged'], + 'lungfish': ['flushing', 'lungfish'], + 'lungsick': ['lungsick', 'suckling'], + 'lunisolar': ['lunisolar', 'solilunar'], + 'luo': ['lou', 'luo'], + 'lupe': ['lupe', 'pelu', 'peul', 'pule'], + 'luperci': ['luperci', 'pleuric'], + 'lupicide': ['lupicide', 'pediculi', 'pulicide'], + 'lupinaster': ['lupinaster', 'palustrine'], + 'lupine': ['lupine', 'unpile', 'upline'], + 'lupinus': ['lupinus', 'pinulus'], + 'lupis': ['lupis', 'pilus'], + 'lupous': ['lupous', 'opulus'], + 'lura': ['alur', 'laur', 'lura', 'raul', 'ural'], + 'lurch': ['churl', 'lurch'], + 'lure': ['lure', 'rule'], + 'lurer': ['lurer', 'ruler'], + 'lurg': ['gurl', 'lurg'], + 'luringly': ['luringly', 'rulingly'], + 'luscinia': ['luscinia', 'siculian'], + 'lush': ['lush', 'shlu', 'shul'], + 'lusher': ['lusher', 'shuler'], + 'lushly': ['hyllus', 'lushly'], + 'lushness': ['lushness', 'shunless'], + 'lusian': ['insula', 'lanius', 'lusian'], + 'lusk': ['lusk', 'sulk'], + 'lusky': ['lusky', 'sulky'], + 'lusory': ['lusory', 'sourly'], + 'lust': ['lust', 'slut'], + 'luster': ['luster', 'result', 'rustle', 'sutler', 'ulster'], + 'lusterless': ['lusterless', 'lustreless', 'resultless'], + 'lustihead': ['hastilude', 'lustihead'], + 'lustreless': ['lusterless', 'lustreless', 'resultless'], + 'lustrine': ['insulter', 'lustrine', 'reinsult'], + 'lustring': ['lustring', 'rustling'], + 'lusty': ['lusty', 'tylus'], + 'lutaceous': ['cautelous', 'lutaceous'], + 'lutany': ['auntly', 'lutany'], + 'lutayo': ['layout', 'lutayo', 'outlay'], + 'lute': ['lute', 'tule'], + 'luteal': ['alulet', 'luteal'], + 'lutecia': ['aleutic', 'auletic', 'caulite', 'lutecia'], + 'lutecium': ['cumulite', 'lutecium'], + 'lutein': ['lutein', 'untile'], + 'lutfisk': ['kistful', 'lutfisk'], + 'luther': ['hurtle', 'luther'], + 'lutheran': ['lutheran', 'unhalter'], + 'lutianoid': ['lutianoid', 'nautiloid'], + 'lutianus': ['lutianus', 'nautilus', 'ustulina'], + 'luting': ['glutin', 'luting', 'ungilt'], + 'lutose': ['lutose', 'solute', 'tousle'], + 'lutra': ['lutra', 'ultra'], + 'lutrinae': ['lutrinae', 'retinula', 'rutelian', 'tenurial'], + 'luxe': ['luxe', 'ulex'], + 'lwo': ['low', 'lwo', 'owl'], + 'lyam': ['amyl', 'lyam', 'myal'], + 'lyard': ['daryl', 'lardy', 'lyard'], + 'lyas': ['lyas', 'slay'], + 'lycaenid': ['adenylic', 'lycaenid'], + 'lyceum': ['cymule', 'lyceum'], + 'lycopodium': ['lycopodium', 'polycodium'], + 'lyctidae': ['diacetyl', 'lyctidae'], + 'lyddite': ['lyddite', 'tiddley'], + 'lydia': ['daily', 'lydia'], + 'lydite': ['idlety', 'lydite', 'tidely', 'tidley'], + 'lye': ['ley', 'lye'], + 'lying': ['lingy', 'lying'], + 'lymnaeid': ['lymnaeid', 'maidenly', 'medianly'], + 'lymphadenia': ['lymphadenia', 'nymphalidae'], + 'lymphectasia': ['lymphectasia', 'metaphysical'], + 'lymphopenia': ['lymphopenia', 'polyphemian'], + 'lynne': ['lenny', 'lynne'], + 'lyon': ['lyon', 'only'], + 'lyophobe': ['hypobole', 'lyophobe'], + 'lyra': ['aryl', 'lyra', 'ryal', 'yarl'], + 'lyraid': ['aridly', 'lyraid'], + 'lyrate': ['lyrate', 'raylet', 'realty', 'telary'], + 'lyre': ['lyre', 'rely'], + 'lyric': ['cyril', 'lyric'], + 'lyrical': ['cyrilla', 'lyrical'], + 'lyrid': ['idryl', 'lyrid'], + 'lys': ['lys', 'sly'], + 'lysander': ['lysander', 'synedral'], + 'lysate': ['alytes', 'astely', 'lysate', 'stealy'], + 'lyse': ['lyse', 'sley'], + 'lysiloma': ['loyalism', 'lysiloma'], + 'lysine': ['linsey', 'lysine'], + 'lysogenetic': ['cleistogeny', 'lysogenetic'], + 'lysogenic': ['glycosine', 'lysogenic'], + 'lyssic': ['clysis', 'lyssic'], + 'lyterian': ['interlay', 'lyterian'], + 'lyxose': ['lyxose', 'xylose'], + 'ma': ['am', 'ma'], + 'maam': ['amma', 'maam'], + 'mab': ['bam', 'mab'], + 'maba': ['amba', 'maba'], + 'mabel': ['amble', 'belam', 'blame', 'mabel'], + 'mabi': ['iamb', 'mabi'], + 'mabolo': ['abloom', 'mabolo'], + 'mac': ['cam', 'mac'], + 'macaca': ['camaca', 'macaca'], + 'macaco': ['cocama', 'macaco'], + 'macaglia': ['almaciga', 'macaglia'], + 'macan': ['caman', 'macan'], + 'macanese': ['macanese', 'maecenas'], + 'macao': ['acoma', 'macao'], + 'macarism': ['macarism', 'marasmic'], + 'macaroni': ['armonica', 'macaroni', 'marocain'], + 'macaronic': ['carcinoma', 'macaronic'], + 'mace': ['acme', 'came', 'mace'], + 'macedon': ['conamed', 'macedon'], + 'macedonian': ['caedmonian', 'macedonian'], + 'macedonic': ['caedmonic', 'macedonic'], + 'macer': ['cream', 'macer'], + 'macerate': ['camerate', 'macerate', 'racemate'], + 'maceration': ['aeromantic', 'cameration', 'maceration', 'racemation'], + 'machar': ['chamar', 'machar'], + 'machi': ['chiam', 'machi', 'micah'], + 'machilis': ['chiliasm', 'hilasmic', 'machilis'], + 'machinator': ['achromatin', 'chariotman', 'machinator'], + 'machine': ['chimane', 'machine'], + 'machinery': ['hemicrany', 'machinery'], + 'macies': ['camise', 'macies'], + 'macigno': ['coaming', 'macigno'], + 'macilency': ['cyclamine', 'macilency'], + 'macle': ['camel', 'clame', 'cleam', 'macle'], + 'maclura': ['maclura', 'macular'], + 'maco': ['coma', 'maco'], + 'macon': ['coman', 'macon', 'manoc'], + 'maconite': ['coinmate', 'maconite'], + 'macro': ['carom', 'coram', 'macro', 'marco'], + 'macrobian': ['carbamino', 'macrobian'], + 'macromazia': ['macromazia', 'macrozamia'], + 'macrophage': ['cameograph', 'macrophage'], + 'macrophotograph': ['macrophotograph', 'photomacrograph'], + 'macrotia': ['aromatic', 'macrotia'], + 'macrotin': ['macrotin', 'romantic'], + 'macrourid': ['macrourid', 'macruroid'], + 'macrourus': ['macrourus', 'macrurous'], + 'macrozamia': ['macromazia', 'macrozamia'], + 'macruroid': ['macrourid', 'macruroid'], + 'macrurous': ['macrourus', 'macrurous'], + 'mactra': ['mactra', 'tarmac'], + 'macular': ['maclura', 'macular'], + 'macule': ['almuce', 'caelum', 'macule'], + 'maculose': ['maculose', 'somacule'], + 'mad': ['dam', 'mad'], + 'madden': ['damned', 'demand', 'madden'], + 'maddening': ['demanding', 'maddening'], + 'maddeningly': ['demandingly', 'maddeningly'], + 'madder': ['dermad', 'madder'], + 'made': ['dame', 'made', 'mead'], + 'madeira': ['adermia', 'madeira'], + 'madeiran': ['madeiran', 'marinade'], + 'madeline': ['endemial', 'madeline'], + 'madi': ['admi', 'amid', 'madi', 'maid'], + 'madia': ['amadi', 'damia', 'madia', 'maida'], + 'madiga': ['agamid', 'madiga'], + 'madman': ['madman', 'nammad'], + 'madnep': ['dampen', 'madnep'], + 'madrid': ['madrid', 'riddam'], + 'madrier': ['admirer', 'madrier', 'married'], + 'madrilene': ['landimere', 'madrilene'], + 'madrona': ['anadrom', 'madrona', 'mandora', 'monarda', 'roadman'], + 'madship': ['dampish', 'madship', 'phasmid'], + 'madurese': ['madurese', 'measured'], + 'mae': ['ame', 'mae'], + 'maecenas': ['macanese', 'maecenas'], + 'maenad': ['anadem', 'maenad'], + 'maenadism': ['maenadism', 'mandaeism'], + 'maeonian': ['enomania', 'maeonian'], + 'maestri': ['artemis', 'maestri', 'misrate'], + 'maestro': ['maestro', 'tarsome'], + 'mag': ['gam', 'mag'], + 'magadis': ['gamasid', 'magadis'], + 'magani': ['angami', 'magani', 'magian'], + 'magas': ['agsam', 'magas'], + 'mage': ['egma', 'game', 'mage'], + 'magenta': ['gateman', 'magenta', 'magnate', 'magneta'], + 'magian': ['angami', 'magani', 'magian'], + 'magic': ['gamic', 'magic'], + 'magister': ['gemarist', 'magister', 'sterigma'], + 'magistrate': ['magistrate', 'sterigmata'], + 'magma': ['gamma', 'magma'], + 'magnate': ['gateman', 'magenta', 'magnate', 'magneta'], + 'magnes': ['magnes', 'semang'], + 'magneta': ['gateman', 'magenta', 'magnate', 'magneta'], + 'magnetist': ['agistment', 'magnetist'], + 'magneto': ['geomant', 'magneto', 'megaton', 'montage'], + 'magnetod': ['magnetod', 'megadont'], + 'magnetoelectric': ['electromagnetic', 'magnetoelectric'], + 'magnetoelectrical': ['electromagnetical', 'magnetoelectrical'], + 'magnolia': ['algomian', 'magnolia'], + 'magnus': ['magnus', 'musang'], + 'magpie': ['magpie', 'piemag'], + 'magyar': ['magyar', 'margay'], + 'mah': ['ham', 'mah'], + 'maha': ['amah', 'maha'], + 'mahar': ['amhar', 'mahar', 'mahra'], + 'maharani': ['amiranha', 'maharani'], + 'mahdism': ['dammish', 'mahdism'], + 'mahdist': ['adsmith', 'mahdist'], + 'mahi': ['hami', 'hima', 'mahi'], + 'mahican': ['chamian', 'mahican'], + 'mahogany': ['hogmanay', 'mahogany'], + 'maholi': ['holmia', 'maholi'], + 'maholtine': ['hematolin', 'maholtine'], + 'mahori': ['homrai', 'mahori', 'mohair'], + 'mahra': ['amhar', 'mahar', 'mahra'], + 'mahran': ['amhran', 'harman', 'mahran'], + 'mahri': ['hiram', 'ihram', 'mahri'], + 'maia': ['amia', 'maia'], + 'maid': ['admi', 'amid', 'madi', 'maid'], + 'maida': ['amadi', 'damia', 'madia', 'maida'], + 'maiden': ['daimen', 'damine', 'maiden', 'median', 'medina'], + 'maidenism': ['maidenism', 'medianism'], + 'maidenly': ['lymnaeid', 'maidenly', 'medianly'], + 'maidish': ['hasidim', 'maidish'], + 'maidism': ['amidism', 'maidism'], + 'maigre': ['imager', 'maigre', 'margie', 'mirage'], + 'maiidae': ['amiidae', 'maiidae'], + 'mail': ['amil', 'amli', 'lima', 'mail', 'mali', 'mila'], + 'mailed': ['aldime', 'mailed', 'medial'], + 'mailer': ['mailer', 'remail'], + 'mailie': ['emilia', 'mailie'], + 'maim': ['ammi', 'imam', 'maim', 'mima'], + 'main': ['amin', 'main', 'mani', 'mian', 'mina', 'naim'], + 'maine': ['amine', 'anime', 'maine', 'manei'], + 'mainly': ['amylin', 'mainly'], + 'mainour': ['mainour', 'uramino'], + 'mainpast': ['mainpast', 'mantispa', 'panamist', 'stampian'], + 'mainprise': ['mainprise', 'presimian'], + 'mains': ['mains', 'manis'], + 'maint': ['maint', 'matin'], + 'maintain': ['amanitin', 'maintain'], + 'maintainer': ['antimerina', 'maintainer', 'remaintain'], + 'maintop': ['maintop', 'ptomain', 'tampion', 'timpano'], + 'maioid': ['daimio', 'maioid'], + 'maioidean': ['anomiidae', 'maioidean'], + 'maire': ['aimer', 'maire', 'marie', 'ramie'], + 'maja': ['jama', 'maja'], + 'majoon': ['majoon', 'moonja'], + 'major': ['jarmo', 'major'], + 'makah': ['hakam', 'makah'], + 'makassar': ['makassar', 'samskara'], + 'make': ['kame', 'make', 'meak'], + 'maker': ['maker', 'marek', 'merak'], + 'maki': ['akim', 'maki'], + 'mako': ['amok', 'mako'], + 'mal': ['lam', 'mal'], + 'mala': ['alma', 'amla', 'lama', 'mala'], + 'malacologist': ['malacologist', 'mastological'], + 'malaga': ['agalma', 'malaga'], + 'malagma': ['amalgam', 'malagma'], + 'malakin': ['alkamin', 'malakin'], + 'malanga': ['malanga', 'nagmaal'], + 'malapert': ['armplate', 'malapert'], + 'malapi': ['impala', 'malapi'], + 'malar': ['alarm', 'malar', 'maral', 'marla', 'ramal'], + 'malarin': ['lairman', 'laminar', 'malarin', 'railman'], + 'malate': ['malate', 'meatal', 'tamale'], + 'malcreated': ['cradlemate', 'malcreated'], + 'maldonite': ['antimodel', 'maldonite', 'monilated'], + 'male': ['alem', 'alme', 'lame', 'leam', 'male', 'meal', 'mela'], + 'maleficiation': ['amelification', 'maleficiation'], + 'maleic': ['maleic', 'malice', 'melica'], + 'maleinoid': ['alimonied', 'maleinoid'], + 'malella': ['lamella', 'malella', 'malleal'], + 'maleness': ['lameness', 'maleness', 'maneless', 'nameless'], + 'malengine': ['enameling', 'malengine', 'meningeal'], + 'maleo': ['amole', 'maleo'], + 'malfed': ['flamed', 'malfed'], + 'malhonest': ['malhonest', 'mashelton'], + 'mali': ['amil', 'amli', 'lima', 'mail', 'mali', 'mila'], + 'malic': ['claim', 'clima', 'malic'], + 'malice': ['maleic', 'malice', 'melica'], + 'malicho': ['chiloma', 'malicho'], + 'maligner': ['germinal', 'maligner', 'malinger'], + 'malikana': ['kalamian', 'malikana'], + 'maline': ['limean', 'maline', 'melian', 'menial'], + 'malines': ['malines', 'salmine', 'selamin', 'seminal'], + 'malinger': ['germinal', 'maligner', 'malinger'], + 'malison': ['malison', 'manolis', 'osmanli', 'somnial'], + 'malladrite': ['armillated', 'malladrite', 'mallardite'], + 'mallardite': ['armillated', 'malladrite', 'mallardite'], + 'malleal': ['lamella', 'malella', 'malleal'], + 'mallein': ['mallein', 'manille'], + 'malleoincudal': ['incudomalleal', 'malleoincudal'], + 'malleus': ['amellus', 'malleus'], + 'malmaison': ['anomalism', 'malmaison'], + 'malmy': ['lammy', 'malmy'], + 'malo': ['loam', 'loma', 'malo', 'mola', 'olam'], + 'malonic': ['limacon', 'malonic'], + 'malonyl': ['allonym', 'malonyl'], + 'malope': ['aplome', 'malope'], + 'malpoise': ['malpoise', 'semiopal'], + 'malposed': ['malposed', 'plasmode'], + 'maltase': ['asmalte', 'maltase'], + 'malter': ['armlet', 'malter', 'martel'], + 'maltese': ['maltese', 'seamlet'], + 'malthe': ['hamlet', 'malthe'], + 'malurinae': ['malurinae', 'melanuria'], + 'malurine': ['lemurian', 'malurine', 'rumelian'], + 'malurus': ['malurus', 'ramulus'], + 'malus': ['lamus', 'malus', 'musal', 'slaum'], + 'mamers': ['mamers', 'sammer'], + 'mamo': ['ammo', 'mamo'], + 'man': ['man', 'nam'], + 'mana': ['anam', 'mana', 'naam', 'nama'], + 'manacle': ['laceman', 'manacle'], + 'manacus': ['manacus', 'samucan'], + 'manage': ['agname', 'manage'], + 'manager': ['gearman', 'manager'], + 'manal': ['alman', 'lamna', 'manal'], + 'manas': ['manas', 'saman'], + 'manatee': ['emanate', 'manatee'], + 'manatine': ['annamite', 'manatine'], + 'manbird': ['birdman', 'manbird'], + 'manchester': ['manchester', 'searchment'], + 'mand': ['damn', 'mand'], + 'mandaeism': ['maenadism', 'mandaeism'], + 'mandaite': ['animated', 'mandaite', 'mantidae'], + 'mandarin': ['drainman', 'mandarin'], + 'mandation': ['damnation', 'mandation'], + 'mandatory': ['damnatory', 'mandatory'], + 'mande': ['amend', 'mande', 'maned'], + 'mandelate': ['aldeament', 'mandelate'], + 'mandil': ['lamnid', 'mandil'], + 'mandola': ['mandola', 'odalman'], + 'mandora': ['anadrom', 'madrona', 'mandora', 'monarda', 'roadman'], + 'mandra': ['mandra', 'radman'], + 'mandrill': ['drillman', 'mandrill'], + 'mandyas': ['daysman', 'mandyas'], + 'mane': ['amen', 'enam', 'mane', 'mean', 'name', 'nema'], + 'maned': ['amend', 'mande', 'maned'], + 'manege': ['gamene', 'manege', 'menage'], + 'manei': ['amine', 'anime', 'maine', 'manei'], + 'maneless': ['lameness', 'maleness', 'maneless', 'nameless'], + 'manent': ['manent', 'netman'], + 'manerial': ['almerian', 'manerial'], + 'manes': ['manes', 'manse', 'mensa', 'samen', 'senam'], + 'maness': ['enmass', 'maness', 'messan'], + 'manettia': ['antietam', 'manettia'], + 'maney': ['maney', 'yamen'], + 'manga': ['amang', 'ganam', 'manga'], + 'mangar': ['amgarn', 'mangar', 'marang', 'ragman'], + 'mangel': ['legman', 'mangel', 'mangle'], + 'mangelin': ['mangelin', 'nameling'], + 'manger': ['engram', 'german', 'manger'], + 'mangerite': ['germanite', 'germinate', 'gramenite', 'mangerite'], + 'mangi': ['gamin', 'mangi'], + 'mangle': ['legman', 'mangel', 'mangle'], + 'mango': ['among', 'mango'], + 'mangrass': ['grassman', 'mangrass'], + 'mangrate': ['grateman', 'mangrate', 'mentagra', 'targeman'], + 'mangue': ['mangue', 'maunge'], + 'manhead': ['headman', 'manhead'], + 'manhole': ['holeman', 'manhole'], + 'manhood': ['dhamnoo', 'hoodman', 'manhood'], + 'mani': ['amin', 'main', 'mani', 'mian', 'mina', 'naim'], + 'mania': ['amain', 'amani', 'amnia', 'anima', 'mania'], + 'maniable': ['animable', 'maniable'], + 'maniac': ['amniac', 'caiman', 'maniac'], + 'manic': ['amnic', 'manic'], + 'manid': ['dimna', 'manid'], + 'manidae': ['adamine', 'manidae'], + 'manify': ['infamy', 'manify'], + 'manila': ['almain', 'animal', 'lamina', 'manila'], + 'manilla': ['alnilam', 'manilla'], + 'manille': ['mallein', 'manille'], + 'manioc': ['camion', 'conima', 'manioc', 'monica'], + 'maniple': ['impanel', 'maniple'], + 'manipuri': ['manipuri', 'unimpair'], + 'manis': ['mains', 'manis'], + 'manist': ['manist', 'mantis', 'matins', 'stamin'], + 'manistic': ['actinism', 'manistic'], + 'manito': ['atimon', 'manito', 'montia'], + 'maniu': ['maniu', 'munia', 'unami'], + 'manius': ['animus', 'anisum', 'anusim', 'manius'], + 'maniva': ['maniva', 'vimana'], + 'manlet': ['lament', 'manlet', 'mantel', 'mantle', 'mental'], + 'manna': ['annam', 'manna'], + 'mannite': ['mannite', 'tineman'], + 'mannonic': ['cinnamon', 'mannonic'], + 'mano': ['mano', 'moan', 'mona', 'noam', 'noma', 'oman'], + 'manoc': ['coman', 'macon', 'manoc'], + 'manolis': ['malison', 'manolis', 'osmanli', 'somnial'], + 'manometrical': ['commentarial', 'manometrical'], + 'manometry': ['manometry', 'momentary'], + 'manor': ['manor', 'moran', 'norma', 'ramon', 'roman'], + 'manorial': ['manorial', 'morainal'], + 'manorship': ['manorship', 'orphanism'], + 'manoscope': ['manoscope', 'moonscape'], + 'manred': ['damner', 'manred', 'randem', 'remand'], + 'manrent': ['manrent', 'remnant'], + 'manrope': ['manrope', 'ropeman'], + 'manse': ['manes', 'manse', 'mensa', 'samen', 'senam'], + 'manship': ['manship', 'shipman'], + 'mansion': ['mansion', 'onanism'], + 'mansioneer': ['emersonian', 'mansioneer'], + 'manslaughter': ['manslaughter', 'slaughterman'], + 'manso': ['manso', 'mason', 'monas'], + 'manta': ['atman', 'manta'], + 'mantel': ['lament', 'manlet', 'mantel', 'mantle', 'mental'], + 'manteline': ['lineament', 'manteline'], + 'manter': ['manter', 'marten', 'rament'], + 'mantes': ['mantes', 'stamen'], + 'manticore': ['cremation', 'manticore'], + 'mantidae': ['animated', 'mandaite', 'mantidae'], + 'mantis': ['manist', 'mantis', 'matins', 'stamin'], + 'mantispa': ['mainpast', 'mantispa', 'panamist', 'stampian'], + 'mantissa': ['mantissa', 'satanism'], + 'mantle': ['lament', 'manlet', 'mantel', 'mantle', 'mental'], + 'manto': ['manto', 'toman'], + 'mantodea': ['mantodea', 'nematoda'], + 'mantoidea': ['diatomean', 'mantoidea'], + 'mantra': ['mantra', 'tarman'], + 'mantrap': ['mantrap', 'rampant'], + 'mantua': ['anatum', 'mantua', 'tamanu'], + 'manual': ['alumna', 'manual'], + 'manualism': ['manualism', 'musalmani'], + 'manualiter': ['manualiter', 'unmaterial'], + 'manuel': ['manuel', 'unlame'], + 'manul': ['lanum', 'manul'], + 'manuma': ['amunam', 'manuma'], + 'manure': ['manure', 'menura'], + 'manward': ['manward', 'wardman'], + 'manwards': ['manwards', 'wardsman'], + 'manway': ['manway', 'wayman'], + 'manwise': ['manwise', 'wiseman'], + 'many': ['many', 'myna'], + 'mao': ['mao', 'oam'], + 'maori': ['maori', 'mario', 'moira'], + 'map': ['map', 'pam'], + 'mapach': ['champa', 'mapach'], + 'maple': ['ample', 'maple'], + 'mapper': ['mapper', 'pamper', 'pampre'], + 'mar': ['arm', 'mar', 'ram'], + 'mara': ['amar', 'amra', 'mara', 'rama'], + 'marabout': ['marabout', 'marabuto', 'tamboura'], + 'marabuto': ['marabout', 'marabuto', 'tamboura'], + 'maraca': ['acamar', 'camara', 'maraca'], + 'maral': ['alarm', 'malar', 'maral', 'marla', 'ramal'], + 'marang': ['amgarn', 'mangar', 'marang', 'ragman'], + 'mararie': ['armeria', 'mararie'], + 'marasca': ['marasca', 'mascara'], + 'maraschino': ['anachorism', 'chorasmian', 'maraschino'], + 'marasmic': ['macarism', 'marasmic'], + 'marbelize': ['marbelize', 'marbleize'], + 'marble': ['ambler', 'blamer', 'lamber', 'marble', 'ramble'], + 'marbleize': ['marbelize', 'marbleize'], + 'marbler': ['marbler', 'rambler'], + 'marbling': ['marbling', 'rambling'], + 'marc': ['cram', 'marc'], + 'marcan': ['carman', 'marcan'], + 'marcel': ['calmer', 'carmel', 'clamer', 'marcel', 'mercal'], + 'marcescent': ['marcescent', 'scarcement'], + 'march': ['charm', 'march'], + 'marcher': ['charmer', 'marcher', 'remarch'], + 'marchite': ['athermic', 'marchite', 'rhematic'], + 'marchpane': ['marchpane', 'preachman'], + 'marci': ['marci', 'mirac'], + 'marcionist': ['marcionist', 'romanistic'], + 'marcionite': ['marcionite', 'microtinae', 'remication'], + 'marco': ['carom', 'coram', 'macro', 'marco'], + 'marconi': ['amicron', 'marconi', 'minorca', 'romanic'], + 'mare': ['erma', 'mare', 'rame', 'ream'], + 'mareca': ['acream', 'camera', 'mareca'], + 'marek': ['maker', 'marek', 'merak'], + 'marengo': ['marengo', 'megaron'], + 'mareotid': ['mareotid', 'mediator'], + 'marfik': ['marfik', 'mirfak'], + 'marfire': ['firearm', 'marfire'], + 'margay': ['magyar', 'margay'], + 'marge': ['grame', 'marge', 'regma'], + 'margeline': ['margeline', 'regimenal'], + 'margent': ['garment', 'margent'], + 'margie': ['imager', 'maigre', 'margie', 'mirage'], + 'margin': ['arming', 'ingram', 'margin'], + 'marginal': ['alarming', 'marginal'], + 'marginally': ['alarmingly', 'marginally'], + 'marginate': ['armangite', 'marginate'], + 'marginated': ['argentamid', 'marginated'], + 'margined': ['dirgeman', 'margined', 'midrange'], + 'marginiform': ['graminiform', 'marginiform'], + 'margot': ['gomart', 'margot'], + 'marhala': ['harmala', 'marhala'], + 'mari': ['amir', 'irma', 'mari', 'mira', 'rami', 'rima'], + 'marialite': ['latimeria', 'marialite'], + 'marian': ['airman', 'amarin', 'marian', 'marina', 'mirana'], + 'mariana': ['aramina', 'mariana'], + 'marianne': ['armenian', 'marianne'], + 'marie': ['aimer', 'maire', 'marie', 'ramie'], + 'marigenous': ['germanious', 'gramineous', 'marigenous'], + 'marilla': ['armilla', 'marilla'], + 'marina': ['airman', 'amarin', 'marian', 'marina', 'mirana'], + 'marinade': ['madeiran', 'marinade'], + 'marinate': ['animater', 'marinate'], + 'marine': ['ermani', 'marine', 'remain'], + 'marinist': ['marinist', 'mistrain'], + 'mario': ['maori', 'mario', 'moira'], + 'marion': ['marion', 'romain'], + 'mariou': ['mariou', 'oarium'], + 'maris': ['maris', 'marsi', 'samir', 'simar'], + 'marish': ['marish', 'shamir'], + 'marishness': ['marishness', 'marshiness'], + 'marist': ['marist', 'matris', 'ramist'], + 'maritage': ['gematria', 'maritage'], + 'marital': ['marital', 'martial'], + 'maritality': ['maritality', 'martiality'], + 'maritally': ['maritally', 'martially'], + 'marka': ['karma', 'krama', 'marka'], + 'markeb': ['embark', 'markeb'], + 'marked': ['demark', 'marked'], + 'marker': ['marker', 'remark'], + 'marketable': ['marketable', 'tablemaker'], + 'marketeer': ['marketeer', 'treemaker'], + 'marketer': ['marketer', 'remarket'], + 'marko': ['marko', 'marok'], + 'marla': ['alarm', 'malar', 'maral', 'marla', 'ramal'], + 'marled': ['dermal', 'marled', 'medlar'], + 'marli': ['armil', 'marli', 'rimal'], + 'marline': ['marline', 'mineral', 'ramline'], + 'marlite': ['lamiter', 'marlite'], + 'marlock': ['lockram', 'marlock'], + 'maro': ['amor', 'maro', 'mora', 'omar', 'roam'], + 'marocain': ['armonica', 'macaroni', 'marocain'], + 'marok': ['marko', 'marok'], + 'maronian': ['maronian', 'romanian'], + 'maronist': ['maronist', 'romanist'], + 'maronite': ['maronite', 'martinoe', 'minorate', 'morenita', 'romanite'], + 'marquesan': ['marquesan', 'squareman'], + 'marquis': ['asquirm', 'marquis'], + 'marree': ['marree', 'reamer'], + 'married': ['admirer', 'madrier', 'married'], + 'marrot': ['marrot', 'mortar'], + 'marrowed': ['marrowed', 'romeward'], + 'marryer': ['marryer', 'remarry'], + 'mars': ['arms', 'mars'], + 'marsh': ['marsh', 'shram'], + 'marshaler': ['marshaler', 'remarshal'], + 'marshiness': ['marishness', 'marshiness'], + 'marshite': ['arthemis', 'marshite', 'meharist'], + 'marsi': ['maris', 'marsi', 'samir', 'simar'], + 'marsipobranchiata': ['basiparachromatin', 'marsipobranchiata'], + 'mart': ['mart', 'tram'], + 'martel': ['armlet', 'malter', 'martel'], + 'marteline': ['alimenter', 'marteline'], + 'marten': ['manter', 'marten', 'rament'], + 'martes': ['martes', 'master', 'remast', 'stream'], + 'martha': ['amarth', 'martha'], + 'martial': ['marital', 'martial'], + 'martiality': ['maritality', 'martiality'], + 'martially': ['maritally', 'martially'], + 'martian': ['martian', 'tamarin'], + 'martinet': ['intermat', 'martinet', 'tetramin'], + 'martinico': ['martinico', 'mortician'], + 'martinoe': ['maronite', 'martinoe', 'minorate', 'morenita', 'romanite'], + 'martite': ['martite', 'mitrate'], + 'martius': ['martius', 'matsuri', 'maurist'], + 'martu': ['martu', 'murat', 'turma'], + 'marty': ['marty', 'tryma'], + 'maru': ['arum', 'maru', 'mura'], + 'mary': ['army', 'mary', 'myra', 'yarm'], + 'marylander': ['aldermanry', 'marylander'], + 'marysole': ['marysole', 'ramosely'], + 'mas': ['mas', 'sam', 'sma'], + 'mascara': ['marasca', 'mascara'], + 'mascotry': ['arctomys', 'costmary', 'mascotry'], + 'masculine': ['masculine', 'semuncial', 'simulance'], + 'masculist': ['masculist', 'simulcast'], + 'masdeu': ['amused', 'masdeu', 'medusa'], + 'mash': ['mash', 'samh', 'sham'], + 'masha': ['hamsa', 'masha', 'shama'], + 'mashal': ['mashal', 'shamal'], + 'mashelton': ['malhonest', 'mashelton'], + 'masher': ['masher', 'ramesh', 'shamer'], + 'mashy': ['mashy', 'shyam'], + 'mask': ['kasm', 'mask'], + 'masker': ['masker', 'remask'], + 'mason': ['manso', 'mason', 'monas'], + 'masoner': ['masoner', 'romanes'], + 'masonic': ['anosmic', 'masonic'], + 'masonite': ['masonite', 'misatone'], + 'maspiter': ['maspiter', 'pastimer', 'primates'], + 'masque': ['masque', 'squame', 'squeam'], + 'massa': ['amass', 'assam', 'massa', 'samas'], + 'masse': ['masse', 'sesma'], + 'masser': ['masser', 'remass'], + 'masseter': ['masseter', 'seamster'], + 'masseur': ['assumer', 'erasmus', 'masseur'], + 'massicot': ['acosmist', 'massicot', 'somatics'], + 'massiness': ['amissness', 'massiness'], + 'masskanne': ['masskanne', 'sneaksman'], + 'mast': ['mast', 'mats', 'stam'], + 'masted': ['demast', 'masted'], + 'master': ['martes', 'master', 'remast', 'stream'], + 'masterate': ['masterate', 'metatarse'], + 'masterer': ['masterer', 'restream', 'streamer'], + 'masterful': ['masterful', 'streamful'], + 'masterless': ['masterless', 'streamless'], + 'masterlike': ['masterlike', 'streamlike'], + 'masterling': ['masterling', 'streamling'], + 'masterly': ['masterly', 'myrtales'], + 'mastership': ['mastership', 'shipmaster'], + 'masterwork': ['masterwork', 'workmaster'], + 'masterwort': ['masterwort', 'streamwort'], + 'mastery': ['mastery', 'streamy'], + 'mastic': ['mastic', 'misact'], + 'masticable': ['ablastemic', 'masticable'], + 'mastiche': ['mastiche', 'misteach'], + 'mastlike': ['kemalist', 'mastlike'], + 'mastoid': ['distoma', 'mastoid'], + 'mastoidale': ['diatomales', 'mastoidale', 'mastoideal'], + 'mastoideal': ['diatomales', 'mastoidale', 'mastoideal'], + 'mastological': ['malacologist', 'mastological'], + 'mastomenia': ['mastomenia', 'seminomata'], + 'mastotomy': ['mastotomy', 'stomatomy'], + 'masu': ['masu', 'musa', 'saum'], + 'mat': ['amt', 'mat', 'tam'], + 'matacan': ['matacan', 'tamanac'], + 'matai': ['amati', 'amita', 'matai'], + 'matar': ['matar', 'matra', 'trama'], + 'matara': ['armata', 'matara', 'tamara'], + 'matcher': ['matcher', 'rematch'], + 'mate': ['mate', 'meat', 'meta', 'tame', 'team', 'tema'], + 'mateless': ['mateless', 'meatless', 'tameless', 'teamless'], + 'matelessness': ['matelessness', 'tamelessness'], + 'mately': ['mately', 'tamely'], + 'mater': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'materialism': ['immaterials', 'materialism'], + 'materiel': ['eremital', 'materiel'], + 'maternal': ['maternal', 'ramental'], + 'maternology': ['laryngotome', 'maternology'], + 'mateship': ['aphetism', 'mateship', 'shipmate', 'spithame'], + 'matey': ['matey', 'meaty'], + 'mathesis': ['mathesis', 'thamesis'], + 'mathetic': ['mathetic', 'thematic'], + 'matico': ['atomic', 'matico'], + 'matin': ['maint', 'matin'], + 'matinee': ['amenite', 'etamine', 'matinee'], + 'matins': ['manist', 'mantis', 'matins', 'stamin'], + 'matra': ['matar', 'matra', 'trama'], + 'matral': ['matral', 'tramal'], + 'matralia': ['altamira', 'matralia'], + 'matrices': ['camerist', 'ceramist', 'matrices'], + 'matricide': ['citramide', 'diametric', 'matricide'], + 'matricula': ['lactarium', 'matricula'], + 'matricular': ['matricular', 'trimacular'], + 'matris': ['marist', 'matris', 'ramist'], + 'matrocliny': ['matrocliny', 'romanticly'], + 'matronism': ['matronism', 'romantism'], + 'mats': ['mast', 'mats', 'stam'], + 'matsu': ['matsu', 'tamus', 'tsuma'], + 'matsuri': ['martius', 'matsuri', 'maurist'], + 'matter': ['matter', 'mettar'], + 'mattoir': ['mattoir', 'tritoma'], + 'maturable': ['maturable', 'metabular'], + 'maturation': ['maturation', 'natatorium'], + 'maturer': ['erratum', 'maturer'], + 'mau': ['aum', 'mau'], + 'maud': ['duma', 'maud'], + 'maudle': ['almude', 'maudle'], + 'mauger': ['mauger', 'murage'], + 'maul': ['alum', 'maul'], + 'mauler': ['mauler', 'merula', 'ramule'], + 'maun': ['maun', 'numa'], + 'maund': ['maund', 'munda', 'numda', 'undam', 'unmad'], + 'maunder': ['duramen', 'maunder', 'unarmed'], + 'maunderer': ['maunderer', 'underream'], + 'maunge': ['mangue', 'maunge'], + 'maureen': ['maureen', 'menurae'], + 'maurice': ['maurice', 'uraemic'], + 'maurist': ['martius', 'matsuri', 'maurist'], + 'mauser': ['amuser', 'mauser'], + 'mavis': ['amvis', 'mavis'], + 'maw': ['maw', 'mwa'], + 'mawp': ['mawp', 'wamp'], + 'may': ['amy', 'may', 'mya', 'yam'], + 'maybe': ['beamy', 'embay', 'maybe'], + 'mayer': ['mayer', 'reamy'], + 'maylike': ['maylike', 'yamilke'], + 'mayo': ['amoy', 'mayo'], + 'mayor': ['mayor', 'moray'], + 'maypoling': ['maypoling', 'pygmalion'], + 'maysin': ['maysin', 'minyas', 'mysian'], + 'maytide': ['daytime', 'maytide'], + 'mazer': ['mazer', 'zerma'], + 'mazur': ['mazur', 'murza'], + 'mbaya': ['ambay', 'mbaya'], + 'me': ['em', 'me'], + 'meable': ['bemeal', 'meable'], + 'mead': ['dame', 'made', 'mead'], + 'meader': ['meader', 'remade'], + 'meager': ['graeme', 'meager', 'meagre'], + 'meagre': ['graeme', 'meager', 'meagre'], + 'meak': ['kame', 'make', 'meak'], + 'meal': ['alem', 'alme', 'lame', 'leam', 'male', 'meal', 'mela'], + 'mealer': ['leamer', 'mealer'], + 'mealiness': ['mealiness', 'messaline'], + 'mealy': ['mealy', 'yamel'], + 'mean': ['amen', 'enam', 'mane', 'mean', 'name', 'nema'], + 'meander': ['amender', 'meander', 'reamend', 'reedman'], + 'meandrite': ['demetrian', 'dermatine', 'meandrite', 'minareted'], + 'meandrous': ['meandrous', 'roundseam'], + 'meaned': ['amende', 'demean', 'meaned', 'nadeem'], + 'meaner': ['enarme', 'meaner', 'rename'], + 'meanly': ['meanly', 'namely'], + 'meant': ['ament', 'meant', 'teman'], + 'mease': ['emesa', 'mease'], + 'measly': ['measly', 'samely'], + 'measuration': ['aeronautism', 'measuration'], + 'measure': ['measure', 'reamuse'], + 'measured': ['madurese', 'measured'], + 'meat': ['mate', 'meat', 'meta', 'tame', 'team', 'tema'], + 'meatal': ['malate', 'meatal', 'tamale'], + 'meatless': ['mateless', 'meatless', 'tameless', 'teamless'], + 'meatman': ['meatman', 'teamman'], + 'meatus': ['meatus', 'mutase'], + 'meaty': ['matey', 'meaty'], + 'mechanal': ['leachman', 'mechanal'], + 'mechanicochemical': ['chemicomechanical', 'mechanicochemical'], + 'mechanics': ['mechanics', 'mischance'], + 'mechir': ['chimer', 'mechir', 'micher'], + 'mecometry': ['cymometer', 'mecometry'], + 'meconic': ['comenic', 'encomic', 'meconic'], + 'meconin': ['ennomic', 'meconin'], + 'meconioid': ['meconioid', 'monoeidic'], + 'meconium': ['encomium', 'meconium'], + 'mecopteron': ['mecopteron', 'protocneme'], + 'medal': ['demal', 'medal'], + 'medallary': ['alarmedly', 'medallary'], + 'mede': ['deem', 'deme', 'mede', 'meed'], + 'media': ['amide', 'damie', 'media'], + 'mediacy': ['dicyema', 'mediacy'], + 'mediad': ['diadem', 'mediad'], + 'medial': ['aldime', 'mailed', 'medial'], + 'median': ['daimen', 'damine', 'maiden', 'median', 'medina'], + 'medianism': ['maidenism', 'medianism'], + 'medianly': ['lymnaeid', 'maidenly', 'medianly'], + 'mediator': ['mareotid', 'mediator'], + 'mediatress': ['mediatress', 'streamside'], + 'mediatrice': ['acidimeter', 'mediatrice'], + 'medical': ['camelid', 'decimal', 'declaim', 'medical'], + 'medically': ['decimally', 'medically'], + 'medicate': ['decimate', 'medicate'], + 'medication': ['decimation', 'medication'], + 'medicator': ['decimator', 'medicator', 'mordicate'], + 'medicatory': ['acidometry', 'medicatory', 'radiectomy'], + 'medicinal': ['adminicle', 'medicinal'], + 'medicophysical': ['medicophysical', 'physicomedical'], + 'medimnos': ['demonism', 'medimnos', 'misnomed'], + 'medina': ['daimen', 'damine', 'maiden', 'median', 'medina'], + 'medino': ['domine', 'domnei', 'emodin', 'medino'], + 'mediocrist': ['dosimetric', 'mediocrist'], + 'mediocrity': ['iridectomy', 'mediocrity'], + 'mediodorsal': ['dorsomedial', 'mediodorsal'], + 'medioventral': ['medioventral', 'ventromedial'], + 'meditate': ['admittee', 'meditate'], + 'meditator': ['meditator', 'trematoid'], + 'medlar': ['dermal', 'marled', 'medlar'], + 'medusa': ['amused', 'masdeu', 'medusa'], + 'medusan': ['medusan', 'sudamen'], + 'meece': ['emcee', 'meece'], + 'meed': ['deem', 'deme', 'mede', 'meed'], + 'meeks': ['meeks', 'smeek'], + 'meered': ['deemer', 'meered', 'redeem', 'remede'], + 'meet': ['meet', 'mete', 'teem'], + 'meeter': ['meeter', 'remeet', 'teemer'], + 'meethelp': ['helpmeet', 'meethelp'], + 'meeting': ['meeting', 'teeming', 'tegmine'], + 'meg': ['gem', 'meg'], + 'megabar': ['bergama', 'megabar'], + 'megachiropteran': ['cinematographer', 'megachiropteran'], + 'megadont': ['magnetod', 'megadont'], + 'megadyne': ['ganymede', 'megadyne'], + 'megaera': ['megaera', 'reamage'], + 'megalodon': ['megalodon', 'moonglade'], + 'megalohepatia': ['hepatomegalia', 'megalohepatia'], + 'megalophonous': ['megalophonous', 'omphalogenous'], + 'megalosplenia': ['megalosplenia', 'splenomegalia'], + 'megapod': ['megapod', 'pagedom'], + 'megapodius': ['megapodius', 'pseudimago'], + 'megarian': ['germania', 'megarian'], + 'megaric': ['gemaric', 'grimace', 'megaric'], + 'megaron': ['marengo', 'megaron'], + 'megaton': ['geomant', 'magneto', 'megaton', 'montage'], + 'megmho': ['megmho', 'megohm'], + 'megohm': ['megmho', 'megohm'], + 'megrim': ['gimmer', 'grimme', 'megrim'], + 'mehari': ['mehari', 'meriah'], + 'meharist': ['arthemis', 'marshite', 'meharist'], + 'meile': ['elemi', 'meile'], + 'mein': ['mein', 'mien', 'mine'], + 'meio': ['meio', 'oime'], + 'mel': ['elm', 'mel'], + 'mela': ['alem', 'alme', 'lame', 'leam', 'male', 'meal', 'mela'], + 'melaconite': ['colemanite', 'melaconite'], + 'melalgia': ['gamaliel', 'melalgia'], + 'melam': ['lemma', 'melam'], + 'melamine': ['ammeline', 'melamine'], + 'melange': ['gleeman', 'melange'], + 'melania': ['laminae', 'melania'], + 'melanian': ['alemanni', 'melanian'], + 'melanic': ['cnemial', 'melanic'], + 'melanilin': ['melanilin', 'millennia'], + 'melanin': ['lemnian', 'lineman', 'melanin'], + 'melanism': ['melanism', 'slimeman'], + 'melanite': ['melanite', 'meletian', 'metaline', 'nemalite'], + 'melanitic': ['alimentic', 'antilemic', 'melanitic', 'metanilic'], + 'melanochroi': ['chloroamine', 'melanochroi'], + 'melanogen': ['melanogen', 'melongena'], + 'melanoid': ['demonial', 'melanoid'], + 'melanorrhea': ['amenorrheal', 'melanorrhea'], + 'melanosis': ['loaminess', 'melanosis'], + 'melanotic': ['entomical', 'melanotic'], + 'melanuria': ['malurinae', 'melanuria'], + 'melanuric': ['ceruminal', 'melanuric', 'numerical'], + 'melas': ['amsel', 'melas', 'mesal', 'samel'], + 'melastoma': ['melastoma', 'metasomal'], + 'meldrop': ['meldrop', 'premold'], + 'melena': ['enamel', 'melena'], + 'melenic': ['celemin', 'melenic'], + 'meletian': ['melanite', 'meletian', 'metaline', 'nemalite'], + 'meletski': ['meletski', 'stemlike'], + 'melian': ['limean', 'maline', 'melian', 'menial'], + 'meliatin': ['meliatin', 'timaline'], + 'melic': ['clime', 'melic'], + 'melica': ['maleic', 'malice', 'melica'], + 'melicerta': ['carmelite', 'melicerta'], + 'melicraton': ['centimolar', 'melicraton'], + 'melinda': ['idleman', 'melinda'], + 'meline': ['elemin', 'meline'], + 'melinite': ['ilmenite', 'melinite', 'menilite'], + 'meliorant': ['meliorant', 'mentorial'], + 'melissa': ['aimless', 'melissa', 'seismal'], + 'melitose': ['melitose', 'mesolite'], + 'mellay': ['lamely', 'mellay'], + 'mellit': ['mellit', 'millet'], + 'melodia': ['melodia', 'molidae'], + 'melodica': ['cameloid', 'comedial', 'melodica'], + 'melodicon': ['clinodome', 'melodicon', 'monocleid'], + 'melodist': ['melodist', 'modelist'], + 'melomanic': ['commelina', 'melomanic'], + 'melon': ['lemon', 'melon', 'monel'], + 'melongena': ['melanogen', 'melongena'], + 'melonist': ['melonist', 'telonism'], + 'melonites': ['limestone', 'melonites', 'milestone'], + 'melonlike': ['lemonlike', 'melonlike'], + 'meloplasty': ['meloplasty', 'myeloplast'], + 'melosa': ['melosa', 'salome', 'semola'], + 'melotragic': ['algometric', 'melotragic'], + 'melotrope': ['melotrope', 'metropole'], + 'melter': ['melter', 'remelt'], + 'melters': ['melters', 'resmelt', 'smelter'], + 'melton': ['loment', 'melton', 'molten'], + 'melungeon': ['melungeon', 'nonlegume'], + 'mem': ['emm', 'mem'], + 'memnon': ['memnon', 'mennom'], + 'memo': ['memo', 'mome'], + 'memorandist': ['memorandist', 'moderantism', 'semidormant'], + 'menage': ['gamene', 'manege', 'menage'], + 'menald': ['lemnad', 'menald'], + 'menaspis': ['menaspis', 'semispan'], + 'mendaite': ['dementia', 'mendaite'], + 'mende': ['emend', 'mende'], + 'mender': ['mender', 'remend'], + 'mendi': ['denim', 'mendi'], + 'mendipite': ['impedient', 'mendipite'], + 'menial': ['limean', 'maline', 'melian', 'menial'], + 'menic': ['menic', 'mince'], + 'menilite': ['ilmenite', 'melinite', 'menilite'], + 'meningeal': ['enameling', 'malengine', 'meningeal'], + 'meningocephalitis': ['cephalomeningitis', 'meningocephalitis'], + 'meningocerebritis': ['cerebromeningitis', 'meningocerebritis'], + 'meningoencephalitis': ['encephalomeningitis', 'meningoencephalitis'], + 'meningoencephalocele': ['encephalomeningocele', 'meningoencephalocele'], + 'meningomyelitis': ['meningomyelitis', 'myelomeningitis'], + 'meningomyelocele': ['meningomyelocele', 'myelomeningocele'], + 'mennom': ['memnon', 'mennom'], + 'menostasia': ['anematosis', 'menostasia'], + 'mensa': ['manes', 'manse', 'mensa', 'samen', 'senam'], + 'mensal': ['anselm', 'mensal'], + 'mense': ['mense', 'mesne', 'semen'], + 'menstrual': ['menstrual', 'ulsterman'], + 'mensurable': ['lebensraum', 'mensurable'], + 'mentagra': ['grateman', 'mangrate', 'mentagra', 'targeman'], + 'mental': ['lament', 'manlet', 'mantel', 'mantle', 'mental'], + 'mentalis': ['mentalis', 'smaltine', 'stileman'], + 'mentalize': ['mentalize', 'mentzelia'], + 'mentation': ['mentation', 'montanite'], + 'mentha': ['anthem', 'hetman', 'mentha'], + 'menthane': ['enanthem', 'menthane'], + 'mentigerous': ['mentigerous', 'tergeminous'], + 'mentolabial': ['labiomental', 'mentolabial'], + 'mentor': ['mentor', 'merton', 'termon', 'tormen'], + 'mentorial': ['meliorant', 'mentorial'], + 'mentzelia': ['mentalize', 'mentzelia'], + 'menura': ['manure', 'menura'], + 'menurae': ['maureen', 'menurae'], + 'menyie': ['menyie', 'yemeni'], + 'meo': ['meo', 'moe'], + 'mephisto': ['mephisto', 'pithsome'], + 'merak': ['maker', 'marek', 'merak'], + 'merat': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'meratia': ['ametria', 'artemia', 'meratia', 'ramaite'], + 'mercal': ['calmer', 'carmel', 'clamer', 'marcel', 'mercal'], + 'mercator': ['cremator', 'mercator'], + 'mercatorial': ['crematorial', 'mercatorial'], + 'mercian': ['armenic', 'carmine', 'ceriman', 'crimean', 'mercian'], + 'merciful': ['crimeful', 'merciful'], + 'merciless': ['crimeless', 'merciless'], + 'mercilessness': ['crimelessness', 'mercilessness'], + 'mere': ['mere', 'reem'], + 'merel': ['elmer', 'merel', 'merle'], + 'merely': ['merely', 'yelmer'], + 'merginae': ['ergamine', 'merginae'], + 'mergus': ['gersum', 'mergus'], + 'meriah': ['mehari', 'meriah'], + 'merice': ['eremic', 'merice'], + 'merida': ['admire', 'armied', 'damier', 'dimera', 'merida'], + 'meril': ['limer', 'meril', 'miler'], + 'meriones': ['emersion', 'meriones'], + 'merism': ['merism', 'mermis', 'simmer'], + 'merist': ['merist', 'mister', 'smiter'], + 'meristem': ['meristem', 'mimester'], + 'meristic': ['meristic', 'trimesic', 'trisemic'], + 'merit': ['merit', 'miter', 'mitre', 'remit', 'timer'], + 'merited': ['demerit', 'dimeter', 'merited', 'mitered'], + 'meriter': ['meriter', 'miterer', 'trireme'], + 'merle': ['elmer', 'merel', 'merle'], + 'merlin': ['limner', 'merlin', 'milner'], + 'mermaid': ['demiram', 'mermaid'], + 'mermis': ['merism', 'mermis', 'simmer'], + 'mero': ['mero', 'more', 'omer', 'rome'], + 'meroblastic': ['blastomeric', 'meroblastic'], + 'merocyte': ['cytomere', 'merocyte'], + 'merogony': ['gonomery', 'merogony'], + 'meroistic': ['eroticism', 'isometric', 'meroistic', 'trioecism'], + 'merop': ['merop', 'moper', 'proem', 'remop'], + 'meropia': ['emporia', 'meropia'], + 'meros': ['meros', 'mores', 'morse', 'sermo', 'smore'], + 'merosthenic': ['merosthenic', 'microsthene'], + 'merostome': ['merostome', 'osmometer'], + 'merrow': ['merrow', 'wormer'], + 'merse': ['merse', 'smeer'], + 'merton': ['mentor', 'merton', 'termon', 'tormen'], + 'merula': ['mauler', 'merula', 'ramule'], + 'meruline': ['lemurine', 'meruline', 'relumine'], + 'mesa': ['asem', 'mesa', 'same', 'seam'], + 'mesad': ['desma', 'mesad'], + 'mesadenia': ['deaminase', 'mesadenia'], + 'mesail': ['amiles', 'asmile', 'mesail', 'mesial', 'samiel'], + 'mesal': ['amsel', 'melas', 'mesal', 'samel'], + 'mesalike': ['mesalike', 'seamlike'], + 'mesaraic': ['cramasie', 'mesaraic'], + 'mesaticephaly': ['hemicatalepsy', 'mesaticephaly'], + 'mese': ['mese', 'seem', 'seme', 'smee'], + 'meshech': ['meshech', 'shechem'], + 'mesial': ['amiles', 'asmile', 'mesail', 'mesial', 'samiel'], + 'mesian': ['asimen', 'inseam', 'mesian'], + 'mesic': ['mesic', 'semic'], + 'mesion': ['eonism', 'mesion', 'oneism', 'simeon'], + 'mesitae': ['amesite', 'mesitae', 'semitae'], + 'mesne': ['mense', 'mesne', 'semen'], + 'meso': ['meso', 'mose', 'some'], + 'mesobar': ['ambrose', 'mesobar'], + 'mesocephaly': ['elaphomyces', 'mesocephaly'], + 'mesognathic': ['asthmogenic', 'mesognathic'], + 'mesohepar': ['mesohepar', 'semaphore'], + 'mesolite': ['melitose', 'mesolite'], + 'mesolithic': ['homiletics', 'mesolithic'], + 'mesological': ['mesological', 'semological'], + 'mesology': ['mesology', 'semology'], + 'mesomeric': ['mesomeric', 'microseme', 'semicrome'], + 'mesonotum': ['mesonotum', 'momentous'], + 'mesorectal': ['calotermes', 'mesorectal', 'metacresol'], + 'mesotonic': ['economist', 'mesotonic'], + 'mesoventral': ['mesoventral', 'ventromesal'], + 'mespil': ['mespil', 'simple'], + 'mesropian': ['mesropian', 'promnesia', 'spironema'], + 'messalian': ['messalian', 'seminasal'], + 'messaline': ['mealiness', 'messaline'], + 'messan': ['enmass', 'maness', 'messan'], + 'messelite': ['messelite', 'semisteel', 'teleseism'], + 'messines': ['essenism', 'messines'], + 'messor': ['messor', 'mosser', 'somers'], + 'mestee': ['esteem', 'mestee'], + 'mester': ['mester', 'restem', 'temser', 'termes'], + 'mesua': ['amuse', 'mesua'], + 'meta': ['mate', 'meat', 'meta', 'tame', 'team', 'tema'], + 'metabular': ['maturable', 'metabular'], + 'metaconid': ['comediant', 'metaconid'], + 'metacresol': ['calotermes', 'mesorectal', 'metacresol'], + 'metage': ['gamete', 'metage'], + 'metaler': ['lameter', 'metaler', 'remetal'], + 'metaline': ['melanite', 'meletian', 'metaline', 'nemalite'], + 'metaling': ['ligament', 'metaling', 'tegminal'], + 'metalist': ['metalist', 'smaltite'], + 'metallism': ['metallism', 'smalltime'], + 'metamer': ['ammeter', 'metamer'], + 'metanilic': ['alimentic', 'antilemic', 'melanitic', 'metanilic'], + 'metaphor': ['metaphor', 'trophema'], + 'metaphoric': ['amphoteric', 'metaphoric'], + 'metaphorical': ['metaphorical', 'pharmacolite'], + 'metaphysical': ['lymphectasia', 'metaphysical'], + 'metaplastic': ['metaplastic', 'palmatisect'], + 'metapore': ['ametrope', 'metapore'], + 'metasomal': ['melastoma', 'metasomal'], + 'metatarse': ['masterate', 'metatarse'], + 'metatheria': ['hemiterata', 'metatheria'], + 'metatrophic': ['metatrophic', 'metropathic'], + 'metaxenia': ['examinate', 'exanimate', 'metaxenia'], + 'mete': ['meet', 'mete', 'teem'], + 'meteor': ['meteor', 'remote'], + 'meteorgraph': ['graphometer', 'meteorgraph'], + 'meteorical': ['carmeloite', 'ectromelia', 'meteorical'], + 'meteoristic': ['meteoristic', 'meteoritics'], + 'meteoritics': ['meteoristic', 'meteoritics'], + 'meteoroid': ['meteoroid', 'odiometer'], + 'meter': ['meter', 'retem'], + 'meterless': ['meterless', 'metreless'], + 'metership': ['herpetism', 'metership', 'metreship', 'temperish'], + 'methanal': ['latheman', 'methanal'], + 'methanate': ['hetmanate', 'methanate'], + 'methanoic': ['hematonic', 'methanoic'], + 'mether': ['mether', 'themer'], + 'method': ['method', 'mothed'], + 'methylacetanilide': ['acetmethylanilide', 'methylacetanilide'], + 'methylic': ['methylic', 'thymelic'], + 'methylotic': ['lithectomy', 'methylotic'], + 'metier': ['metier', 'retime', 'tremie'], + 'metin': ['metin', 'temin', 'timne'], + 'metis': ['metis', 'smite', 'stime', 'times'], + 'metoac': ['comate', 'metoac', 'tecoma'], + 'metol': ['metol', 'motel'], + 'metonymical': ['laminectomy', 'metonymical'], + 'metope': ['metope', 'poemet'], + 'metopias': ['epistoma', 'metopias'], + 'metosteon': ['metosteon', 'tomentose'], + 'metra': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'metrectasia': ['metrectasia', 'remasticate'], + 'metreless': ['meterless', 'metreless'], + 'metreship': ['herpetism', 'metership', 'metreship', 'temperish'], + 'metria': ['imaret', 'metria', 'mirate', 'rimate'], + 'metrician': ['antimeric', 'carminite', 'criminate', 'metrician'], + 'metrics': ['cretism', 'metrics'], + 'metrocratic': ['cratometric', 'metrocratic'], + 'metrological': ['logometrical', 'metrological'], + 'metronome': ['metronome', 'monometer', 'monotreme'], + 'metronomic': ['commorient', 'metronomic', 'monometric'], + 'metronomical': ['metronomical', 'monometrical'], + 'metropathic': ['metatrophic', 'metropathic'], + 'metrophlebitis': ['metrophlebitis', 'phlebometritis'], + 'metropole': ['melotrope', 'metropole'], + 'metroptosia': ['metroptosia', 'prostomiate'], + 'metrorrhea': ['arthromere', 'metrorrhea'], + 'metrostyle': ['metrostyle', 'stylometer'], + 'mettar': ['matter', 'mettar'], + 'metusia': ['metusia', 'suimate', 'timaeus'], + 'mew': ['mew', 'wem'], + 'meward': ['meward', 'warmed'], + 'mho': ['mho', 'ohm'], + 'mhometer': ['mhometer', 'ohmmeter'], + 'miamia': ['amimia', 'miamia'], + 'mian': ['amin', 'main', 'mani', 'mian', 'mina', 'naim'], + 'miaotse': ['miaotse', 'ostemia'], + 'miaotze': ['atomize', 'miaotze'], + 'mias': ['mias', 'saim', 'siam', 'sima'], + 'miasmal': ['lamaism', 'miasmal'], + 'miastor': ['amorist', 'aortism', 'miastor'], + 'miaul': ['aumil', 'miaul'], + 'miauler': ['lemuria', 'miauler'], + 'mib': ['bim', 'mib'], + 'mica': ['amic', 'mica'], + 'micah': ['chiam', 'machi', 'micah'], + 'micate': ['acmite', 'micate'], + 'mication': ['amniotic', 'mication'], + 'micellar': ['micellar', 'millrace'], + 'michael': ['michael', 'micheal'], + 'miche': ['chime', 'hemic', 'miche'], + 'micheal': ['michael', 'micheal'], + 'micher': ['chimer', 'mechir', 'micher'], + 'micht': ['micht', 'mitch'], + 'micranthropos': ['micranthropos', 'promonarchist'], + 'micro': ['micro', 'moric', 'romic'], + 'microcephal': ['microcephal', 'prochemical'], + 'microcephaly': ['microcephaly', 'pyrochemical'], + 'microcinema': ['microcinema', 'microcnemia'], + 'microcnemia': ['microcinema', 'microcnemia'], + 'microcrith': ['microcrith', 'trichromic'], + 'micropetalous': ['micropetalous', 'somatopleuric'], + 'microphagy': ['microphagy', 'myographic'], + 'microphone': ['microphone', 'neomorphic'], + 'microphot': ['microphot', 'morphotic'], + 'microphotograph': ['microphotograph', 'photomicrograph'], + 'microphotographic': ['microphotographic', 'photomicrographic'], + 'microphotography': ['microphotography', 'photomicrography'], + 'microphotoscope': ['microphotoscope', 'photomicroscope'], + 'micropterous': ['micropterous', 'prosectorium'], + 'micropyle': ['micropyle', 'polymeric'], + 'microradiometer': ['microradiometer', 'radiomicrometer'], + 'microseme': ['mesomeric', 'microseme', 'semicrome'], + 'microspectroscope': ['microspectroscope', 'spectromicroscope'], + 'microstat': ['microstat', 'stromatic'], + 'microsthene': ['merosthenic', 'microsthene'], + 'microstome': ['microstome', 'osmometric'], + 'microtia': ['amoritic', 'microtia'], + 'microtinae': ['marcionite', 'microtinae', 'remication'], + 'mid': ['dim', 'mid'], + 'midden': ['midden', 'minded'], + 'middler': ['middler', 'mildred'], + 'middy': ['didym', 'middy'], + 'mide': ['demi', 'diem', 'dime', 'mide'], + 'mider': ['dimer', 'mider'], + 'mididae': ['amidide', 'diamide', 'mididae'], + 'midrange': ['dirgeman', 'margined', 'midrange'], + 'midstory': ['midstory', 'modistry'], + 'miek': ['miek', 'mike'], + 'mien': ['mein', 'mien', 'mine'], + 'mig': ['gim', 'mig'], + 'migraine': ['imaginer', 'migraine'], + 'migrate': ['migrate', 'ragtime'], + 'migratory': ['gyromitra', 'migratory'], + 'mihrab': ['brahmi', 'mihrab'], + 'mikael': ['kelima', 'mikael'], + 'mike': ['miek', 'mike'], + 'mil': ['lim', 'mil'], + 'mila': ['amil', 'amli', 'lima', 'mail', 'mali', 'mila'], + 'milan': ['lamin', 'liman', 'milan'], + 'milden': ['milden', 'mindel'], + 'mildness': ['mildness', 'mindless'], + 'mildred': ['middler', 'mildred'], + 'mile': ['emil', 'lime', 'mile'], + 'milepost': ['milepost', 'polemist'], + 'miler': ['limer', 'meril', 'miler'], + 'miles': ['limes', 'miles', 'slime', 'smile'], + 'milesian': ['alienism', 'milesian'], + 'milestone': ['limestone', 'melonites', 'milestone'], + 'milicent': ['limnetic', 'milicent'], + 'military': ['limitary', 'military'], + 'militate': ['limitate', 'militate'], + 'militation': ['limitation', 'militation'], + 'milken': ['kimnel', 'milken'], + 'millennia': ['melanilin', 'millennia'], + 'miller': ['miller', 'remill'], + 'millet': ['mellit', 'millet'], + 'milliare': ['milliare', 'ramillie'], + 'millrace': ['micellar', 'millrace'], + 'milner': ['limner', 'merlin', 'milner'], + 'milo': ['milo', 'moil'], + 'milsie': ['milsie', 'simile'], + 'miltonia': ['limation', 'miltonia'], + 'mima': ['ammi', 'imam', 'maim', 'mima'], + 'mime': ['emim', 'mime'], + 'mimester': ['meristem', 'mimester'], + 'mimi': ['immi', 'mimi'], + 'mimidae': ['amimide', 'mimidae'], + 'mimosa': ['amomis', 'mimosa'], + 'min': ['min', 'nim'], + 'mina': ['amin', 'main', 'mani', 'mian', 'mina', 'naim'], + 'minacity': ['imitancy', 'intimacy', 'minacity'], + 'minar': ['inarm', 'minar'], + 'minaret': ['minaret', 'raiment', 'tireman'], + 'minareted': ['demetrian', 'dermatine', 'meandrite', 'minareted'], + 'minargent': ['germinant', 'minargent'], + 'minatory': ['minatory', 'romanity'], + 'mince': ['menic', 'mince'], + 'minchiate': ['hematinic', 'minchiate'], + 'mincopie': ['mincopie', 'poimenic'], + 'minded': ['midden', 'minded'], + 'mindel': ['milden', 'mindel'], + 'mindelian': ['eliminand', 'mindelian'], + 'minder': ['minder', 'remind'], + 'mindless': ['mildness', 'mindless'], + 'mine': ['mein', 'mien', 'mine'], + 'miner': ['inerm', 'miner'], + 'mineral': ['marline', 'mineral', 'ramline'], + 'minerva': ['minerva', 'vermian'], + 'minerval': ['minerval', 'verminal'], + 'mingler': ['gremlin', 'mingler'], + 'miniator': ['miniator', 'triamino'], + 'minish': ['minish', 'nimshi'], + 'minister': ['minister', 'misinter'], + 'ministry': ['ministry', 'myristin'], + 'minkish': ['minkish', 'nimkish'], + 'minnetaree': ['minnetaree', 'nemertinea'], + 'minoan': ['amnion', 'minoan', 'nomina'], + 'minometer': ['minometer', 'omnimeter'], + 'minor': ['minor', 'morin'], + 'minorate': ['maronite', 'martinoe', 'minorate', 'morenita', 'romanite'], + 'minorca': ['amicron', 'marconi', 'minorca', 'romanic'], + 'minos': ['minos', 'osmin', 'simon'], + 'minot': ['minot', 'timon', 'tomin'], + 'mintage': ['mintage', 'teaming', 'tegmina'], + 'minter': ['minter', 'remint', 'termin'], + 'minuend': ['minuend', 'unmined'], + 'minuet': ['minuet', 'minute'], + 'minute': ['minuet', 'minute'], + 'minutely': ['minutely', 'untimely'], + 'minuter': ['minuter', 'unmiter'], + 'minyas': ['maysin', 'minyas', 'mysian'], + 'mir': ['mir', 'rim'], + 'mira': ['amir', 'irma', 'mari', 'mira', 'rami', 'rima'], + 'mirabel': ['embrail', 'mirabel'], + 'mirac': ['marci', 'mirac'], + 'miracle': ['claimer', 'miracle', 'reclaim'], + 'mirage': ['imager', 'maigre', 'margie', 'mirage'], + 'mirana': ['airman', 'amarin', 'marian', 'marina', 'mirana'], + 'miranha': ['ahriman', 'miranha'], + 'mirate': ['imaret', 'metria', 'mirate', 'rimate'], + 'mirbane': ['ambrein', 'mirbane'], + 'mire': ['emir', 'imer', 'mire', 'reim', 'remi', 'riem', 'rime'], + 'mirfak': ['marfik', 'mirfak'], + 'mirounga': ['mirounga', 'moringua', 'origanum'], + 'miry': ['miry', 'rimy', 'yirm'], + 'mirza': ['mirza', 'mizar'], + 'misact': ['mastic', 'misact'], + 'misadvise': ['admissive', 'misadvise'], + 'misagent': ['misagent', 'steaming'], + 'misaim': ['misaim', 'misima'], + 'misandry': ['misandry', 'myrsinad'], + 'misassociation': ['associationism', 'misassociation'], + 'misatone': ['masonite', 'misatone'], + 'misattend': ['misattend', 'tandemist'], + 'misaunter': ['antiserum', 'misaunter'], + 'misbehavior': ['behaviorism', 'misbehavior'], + 'mischance': ['mechanics', 'mischance'], + 'misclass': ['classism', 'misclass'], + 'miscoin': ['iconism', 'imsonic', 'miscoin'], + 'misconfiguration': ['configurationism', 'misconfiguration'], + 'misconstitutional': ['constitutionalism', 'misconstitutional'], + 'misconstruction': ['constructionism', 'misconstruction'], + 'miscreant': ['encratism', 'miscreant'], + 'miscreation': ['anisometric', + 'creationism', + 'miscreation', + 'ramisection', + 'reactionism'], + 'miscue': ['cesium', 'miscue'], + 'misdate': ['diastem', 'misdate'], + 'misdaub': ['misdaub', 'submaid'], + 'misdeal': ['misdeal', 'mislead'], + 'misdealer': ['misdealer', 'misleader', 'misleared'], + 'misdeclare': ['creedalism', 'misdeclare'], + 'misdiet': ['misdiet', 'misedit', 'mistide'], + 'misdivision': ['divisionism', 'misdivision'], + 'misdread': ['disarmed', 'misdread'], + 'mise': ['mise', 'semi', 'sime'], + 'misease': ['misease', 'siamese'], + 'misecclesiastic': ['ecclesiasticism', 'misecclesiastic'], + 'misedit': ['misdiet', 'misedit', 'mistide'], + 'misexpression': ['expressionism', 'misexpression'], + 'mishmash': ['mishmash', 'shammish'], + 'misima': ['misaim', 'misima'], + 'misimpression': ['impressionism', 'misimpression'], + 'misinter': ['minister', 'misinter'], + 'mislabel': ['mislabel', 'semiball'], + 'mislabor': ['laborism', 'mislabor'], + 'mislead': ['misdeal', 'mislead'], + 'misleader': ['misdealer', 'misleader', 'misleared'], + 'mislear': ['mislear', 'realism'], + 'misleared': ['misdealer', 'misleader', 'misleared'], + 'misname': ['amenism', 'immanes', 'misname'], + 'misniac': ['cainism', 'misniac'], + 'misnomed': ['demonism', 'medimnos', 'misnomed'], + 'misoneism': ['misoneism', 'simeonism'], + 'mispage': ['impages', 'mispage'], + 'misperception': ['misperception', 'perceptionism'], + 'misperform': ['misperform', 'preformism'], + 'misphrase': ['misphrase', 'seraphism'], + 'misplay': ['impalsy', 'misplay'], + 'misplead': ['misplead', 'pedalism'], + 'misprisal': ['misprisal', 'spiralism'], + 'misproud': ['disporum', 'misproud'], + 'misprovide': ['disimprove', 'misprovide'], + 'misput': ['misput', 'sumpit'], + 'misquotation': ['antimosquito', 'misquotation'], + 'misrate': ['artemis', 'maestri', 'misrate'], + 'misread': ['misread', 'sidearm'], + 'misreform': ['misreform', 'reformism'], + 'misrelate': ['misrelate', 'salimeter'], + 'misrelation': ['misrelation', 'orientalism', 'relationism'], + 'misreliance': ['criminalese', 'misreliance'], + 'misreporter': ['misreporter', 'reporterism'], + 'misrepresentation': ['misrepresentation', 'representationism'], + 'misrepresenter': ['misrepresenter', 'remisrepresent'], + 'misrepute': ['misrepute', 'septerium'], + 'misrhyme': ['misrhyme', 'shimmery'], + 'misrule': ['misrule', 'simuler'], + 'missal': ['missal', 'salmis'], + 'missayer': ['emissary', 'missayer'], + 'misset': ['misset', 'tmesis'], + 'misshape': ['emphasis', 'misshape'], + 'missioner': ['missioner', 'remission'], + 'misspell': ['misspell', 'psellism'], + 'missuggestion': ['missuggestion', 'suggestionism'], + 'missy': ['missy', 'mysis'], + 'mist': ['mist', 'smit', 'stim'], + 'misteach': ['mastiche', 'misteach'], + 'mister': ['merist', 'mister', 'smiter'], + 'mistide': ['misdiet', 'misedit', 'mistide'], + 'mistle': ['mistle', 'smilet'], + 'mistone': ['mistone', 'moisten'], + 'mistradition': ['mistradition', 'traditionism'], + 'mistrain': ['marinist', 'mistrain'], + 'mistreat': ['mistreat', 'teratism'], + 'mistrial': ['mistrial', 'trialism'], + 'mistutor': ['mistutor', 'tutorism'], + 'misty': ['misty', 'stimy'], + 'misunderstander': ['misunderstander', 'remisunderstand'], + 'misura': ['misura', 'ramusi'], + 'misuser': ['misuser', 'surmise'], + 'mitannish': ['mitannish', 'sminthian'], + 'mitch': ['micht', 'mitch'], + 'mite': ['emit', 'item', 'mite', 'time'], + 'mitella': ['mitella', 'tellima'], + 'miteproof': ['miteproof', 'timeproof'], + 'miter': ['merit', 'miter', 'mitre', 'remit', 'timer'], + 'mitered': ['demerit', 'dimeter', 'merited', 'mitered'], + 'miterer': ['meriter', 'miterer', 'trireme'], + 'mithraic': ['arithmic', 'mithraic', 'mithriac'], + 'mithraicist': ['mithraicist', 'mithraistic'], + 'mithraistic': ['mithraicist', 'mithraistic'], + 'mithriac': ['arithmic', 'mithraic', 'mithriac'], + 'mitra': ['mitra', 'tarmi', 'timar', 'tirma'], + 'mitral': ['mitral', 'ramtil'], + 'mitrate': ['martite', 'mitrate'], + 'mitre': ['merit', 'miter', 'mitre', 'remit', 'timer'], + 'mitrer': ['mitrer', 'retrim', 'trimer'], + 'mitridae': ['dimetria', 'mitridae', 'tiremaid', 'triamide'], + 'mixer': ['mixer', 'remix'], + 'mizar': ['mirza', 'mizar'], + 'mnesic': ['cnemis', 'mnesic'], + 'mniaceous': ['acuminose', 'mniaceous'], + 'mniotiltidae': ['delimitation', 'mniotiltidae'], + 'mnium': ['mnium', 'nummi'], + 'mo': ['mo', 'om'], + 'moabitic': ['biatomic', 'moabitic'], + 'moan': ['mano', 'moan', 'mona', 'noam', 'noma', 'oman'], + 'moarian': ['amanori', 'moarian'], + 'moat': ['atmo', 'atom', 'moat', 'toma'], + 'mob': ['bom', 'mob'], + 'mobbable': ['bombable', 'mobbable'], + 'mobber': ['bomber', 'mobber'], + 'mobbish': ['hobbism', 'mobbish'], + 'mobed': ['demob', 'mobed'], + 'mobile': ['bemoil', 'mobile'], + 'mobilian': ['binomial', 'mobilian'], + 'mobocrat': ['mobocrat', 'motorcab'], + 'mobship': ['mobship', 'phobism'], + 'mobster': ['bestorm', 'mobster'], + 'mocker': ['mocker', 'remock'], + 'mocmain': ['ammonic', 'mocmain'], + 'mod': ['dom', 'mod'], + 'modal': ['domal', 'modal'], + 'mode': ['dome', 'mode', 'moed'], + 'modeler': ['demerol', 'modeler', 'remodel'], + 'modelist': ['melodist', 'modelist'], + 'modena': ['daemon', 'damone', 'modena'], + 'modenese': ['modenese', 'needsome'], + 'moderant': ['moderant', 'normated'], + 'moderantism': ['memorandist', 'moderantism', 'semidormant'], + 'modern': ['modern', 'morned'], + 'modernistic': ['modernistic', 'monstricide'], + 'modestly': ['modestly', 'styledom'], + 'modesty': ['dystome', 'modesty'], + 'modiste': ['distome', 'modiste'], + 'modistry': ['midstory', 'modistry'], + 'modius': ['modius', 'sodium'], + 'moe': ['meo', 'moe'], + 'moed': ['dome', 'mode', 'moed'], + 'moerithere': ['heteromeri', 'moerithere'], + 'mogdad': ['goddam', 'mogdad'], + 'moha': ['ahom', 'moha'], + 'mohair': ['homrai', 'mahori', 'mohair'], + 'mohel': ['hemol', 'mohel'], + 'mohican': ['mohican', 'monachi'], + 'moho': ['homo', 'moho'], + 'mohur': ['humor', 'mohur'], + 'moider': ['dormie', 'moider'], + 'moieter': ['moieter', 'romeite'], + 'moiety': ['moiety', 'moyite'], + 'moil': ['milo', 'moil'], + 'moiles': ['lemosi', 'limose', 'moiles'], + 'moineau': ['eunomia', 'moineau'], + 'moira': ['maori', 'mario', 'moira'], + 'moisten': ['mistone', 'moisten'], + 'moistener': ['moistener', 'neoterism'], + 'moisture': ['moisture', 'semitour'], + 'moit': ['itmo', 'moit', 'omit', 'timo'], + 'mojo': ['joom', 'mojo'], + 'moke': ['kome', 'moke'], + 'moki': ['komi', 'moki'], + 'mola': ['loam', 'loma', 'malo', 'mola', 'olam'], + 'molar': ['molar', 'moral', 'romal'], + 'molarity': ['molarity', 'morality'], + 'molary': ['amyrol', 'molary'], + 'molder': ['dermol', 'molder', 'remold'], + 'moler': ['moler', 'morel'], + 'molge': ['glome', 'golem', 'molge'], + 'molidae': ['melodia', 'molidae'], + 'molinia': ['molinia', 'monilia'], + 'mollusca': ['callosum', 'mollusca'], + 'moloid': ['moloid', 'oildom'], + 'molten': ['loment', 'melton', 'molten'], + 'molybdena': ['baldmoney', 'molybdena'], + 'molybdenic': ['combinedly', 'molybdenic'], + 'mome': ['memo', 'mome'], + 'moment': ['moment', 'montem'], + 'momentary': ['manometry', 'momentary'], + 'momentous': ['mesonotum', 'momentous'], + 'momotinae': ['amniotome', 'momotinae'], + 'mona': ['mano', 'moan', 'mona', 'noam', 'noma', 'oman'], + 'monachi': ['mohican', 'monachi'], + 'monactin': ['monactin', 'montanic'], + 'monad': ['damon', 'monad', 'nomad'], + 'monadic': ['monadic', 'nomadic'], + 'monadical': ['monadical', 'nomadical'], + 'monadically': ['monadically', 'nomadically'], + 'monadina': ['monadina', 'nomadian'], + 'monadism': ['monadism', 'nomadism'], + 'monaene': ['anemone', 'monaene'], + 'monal': ['almon', 'monal'], + 'monamniotic': ['commination', 'monamniotic'], + 'monanthous': ['anthonomus', 'monanthous'], + 'monarch': ['monarch', 'nomarch', 'onmarch'], + 'monarchial': ['harmonical', 'monarchial'], + 'monarchian': ['anharmonic', 'monarchian'], + 'monarchistic': ['chiromancist', 'monarchistic'], + 'monarchy': ['monarchy', 'nomarchy'], + 'monarda': ['anadrom', 'madrona', 'mandora', 'monarda', 'roadman'], + 'monas': ['manso', 'mason', 'monas'], + 'monasa': ['monasa', 'samoan'], + 'monase': ['monase', 'nosema'], + 'monaster': ['monaster', 'monstera', 'nearmost', 'storeman'], + 'monastery': ['monastery', 'oysterman'], + 'monastic': ['catonism', 'monastic'], + 'monastical': ['catmalison', 'monastical'], + 'monatomic': ['commation', 'monatomic'], + 'monaural': ['anomural', 'monaural'], + 'monday': ['dynamo', 'monday'], + 'mone': ['mone', 'nome', 'omen'], + 'monel': ['lemon', 'melon', 'monel'], + 'moner': ['enorm', 'moner', 'morne'], + 'monera': ['enamor', 'monera', 'oreman', 'romane'], + 'moneral': ['almoner', 'moneral', 'nemoral'], + 'monergist': ['gerontism', 'monergist'], + 'moneric': ['incomer', 'moneric'], + 'monesia': ['monesia', 'osamine', 'osmanie'], + 'monetary': ['monetary', 'myronate', 'naometry'], + 'money': ['money', 'moyen'], + 'moneybag': ['bogeyman', 'moneybag'], + 'moneyless': ['moneyless', 'moyenless'], + 'monger': ['germon', 'monger', 'morgen'], + 'mongler': ['mongler', 'mongrel'], + 'mongoose': ['gonosome', 'mongoose'], + 'mongrel': ['mongler', 'mongrel'], + 'mongrelity': ['longimetry', 'mongrelity'], + 'monial': ['monial', 'nomial', 'oilman'], + 'monias': ['monias', 'osamin', 'osmina'], + 'monica': ['camion', 'conima', 'manioc', 'monica'], + 'monilated': ['antimodel', 'maldonite', 'monilated'], + 'monilia': ['molinia', 'monilia'], + 'monism': ['monism', 'nomism', 'simmon'], + 'monist': ['inmost', 'monist', 'omnist'], + 'monistic': ['monistic', 'nicotism', 'nomistic'], + 'monitory': ['monitory', 'moronity'], + 'monitress': ['monitress', 'sermonist'], + 'mono': ['mono', 'moon'], + 'monoacid': ['damonico', 'monoacid'], + 'monoazo': ['monoazo', 'monozoa'], + 'monocleid': ['clinodome', 'melodicon', 'monocleid'], + 'monoclinous': ['monoclinous', 'monoclonius'], + 'monoclonius': ['monoclinous', 'monoclonius'], + 'monocracy': ['monocracy', 'nomocracy'], + 'monocystidae': ['monocystidae', 'monocystidea'], + 'monocystidea': ['monocystidae', 'monocystidea'], + 'monodactylous': ['condylomatous', 'monodactylous'], + 'monodactyly': ['dactylonomy', 'monodactyly'], + 'monodelphia': ['amidophenol', 'monodelphia'], + 'monodonta': ['anomodont', 'monodonta'], + 'monodram': ['monodram', 'romandom'], + 'monoecian': ['monoecian', 'neocomian'], + 'monoecism': ['economism', 'monoecism', 'monosemic'], + 'monoeidic': ['meconioid', 'monoeidic'], + 'monogastric': ['gastronomic', 'monogastric'], + 'monogenist': ['monogenist', 'nomogenist'], + 'monogenous': ['monogenous', 'nomogenous'], + 'monogeny': ['monogeny', 'nomogeny'], + 'monogram': ['monogram', 'nomogram'], + 'monograph': ['monograph', 'nomograph', 'phonogram'], + 'monographer': ['geranomorph', 'monographer', 'nomographer'], + 'monographic': ['gramophonic', 'monographic', 'nomographic', 'phonogramic'], + 'monographical': ['gramophonical', 'monographical', 'nomographical'], + 'monographically': ['gramophonically', + 'monographically', + 'nomographically', + 'phonogramically'], + 'monographist': ['gramophonist', 'monographist'], + 'monography': ['monography', 'nomography'], + 'monoid': ['domino', 'monoid'], + 'monological': ['monological', 'nomological'], + 'monologist': ['monologist', 'nomologist', 'ontologism'], + 'monology': ['monology', 'nomology'], + 'monometer': ['metronome', 'monometer', 'monotreme'], + 'monometric': ['commorient', 'metronomic', 'monometric'], + 'monometrical': ['metronomical', 'monometrical'], + 'monomorphic': ['monomorphic', 'morphonomic'], + 'monont': ['monont', 'monton'], + 'monopathy': ['monopathy', 'pathonomy'], + 'monopersulphuric': ['monopersulphuric', 'permonosulphuric'], + 'monophote': ['monophote', 'motophone'], + 'monophylite': ['entomophily', 'monophylite'], + 'monophyllous': ['monophyllous', 'nomophyllous'], + 'monoplanist': ['monoplanist', 'postnominal'], + 'monopsychism': ['monopsychism', 'psychomonism'], + 'monopteral': ['monopteral', 'protonemal'], + 'monosemic': ['economism', 'monoecism', 'monosemic'], + 'monosodium': ['monosodium', 'omnimodous', 'onosmodium'], + 'monotheism': ['monotheism', 'nomotheism'], + 'monotheist': ['monotheist', 'thomsonite'], + 'monothetic': ['monothetic', 'nomothetic'], + 'monotreme': ['metronome', 'monometer', 'monotreme'], + 'monotypal': ['monotypal', 'toponymal'], + 'monotypic': ['monotypic', 'toponymic'], + 'monotypical': ['monotypical', 'toponymical'], + 'monozoa': ['monoazo', 'monozoa'], + 'monozoic': ['monozoic', 'zoonomic'], + 'monroeism': ['monroeism', 'semimoron'], + 'monsieur': ['inermous', 'monsieur'], + 'monstera': ['monaster', 'monstera', 'nearmost', 'storeman'], + 'monstricide': ['modernistic', 'monstricide'], + 'montage': ['geomant', 'magneto', 'megaton', 'montage'], + 'montagnais': ['antagonism', 'montagnais'], + 'montanic': ['monactin', 'montanic'], + 'montanite': ['mentation', 'montanite'], + 'montem': ['moment', 'montem'], + 'montes': ['montes', 'ostmen'], + 'montia': ['atimon', 'manito', 'montia'], + 'monticule': ['ctenolium', 'monticule'], + 'monton': ['monont', 'monton'], + 'montu': ['montu', 'mount', 'notum'], + 'monture': ['monture', 'mounter', 'remount'], + 'monumentary': ['monumentary', 'unmomentary'], + 'mood': ['doom', 'mood'], + 'mooder': ['doomer', 'mooder', 'redoom', 'roomed'], + 'mool': ['loom', 'mool'], + 'mools': ['mools', 'sloom'], + 'moon': ['mono', 'moon'], + 'moonglade': ['megalodon', 'moonglade'], + 'moonite': ['emotion', 'moonite'], + 'moonja': ['majoon', 'moonja'], + 'moonscape': ['manoscope', 'moonscape'], + 'moonseed': ['endosome', 'moonseed'], + 'moontide': ['demotion', 'entomoid', 'moontide'], + 'moop': ['moop', 'pomo'], + 'moor': ['moor', 'moro', 'room'], + 'moorage': ['moorage', 'roomage'], + 'moorball': ['ballroom', 'moorball'], + 'moore': ['moore', 'romeo'], + 'moorn': ['moorn', 'moron'], + 'moorship': ['isomorph', 'moorship'], + 'moorup': ['moorup', 'uproom'], + 'moorwort': ['moorwort', 'rootworm', 'tomorrow', 'wormroot'], + 'moory': ['moory', 'roomy'], + 'moost': ['moost', 'smoot'], + 'moot': ['moot', 'toom'], + 'mooth': ['mooth', 'thoom'], + 'mootstead': ['mootstead', 'stomatode'], + 'mop': ['mop', 'pom'], + 'mopane': ['mopane', 'pomane'], + 'mope': ['mope', 'poem', 'pome'], + 'moper': ['merop', 'moper', 'proem', 'remop'], + 'mophead': ['hemapod', 'mophead'], + 'mopish': ['mopish', 'ophism'], + 'mopla': ['mopla', 'palmo'], + 'mopsy': ['mopsy', 'myops'], + 'mora': ['amor', 'maro', 'mora', 'omar', 'roam'], + 'morainal': ['manorial', 'morainal'], + 'moraine': ['moraine', 'romaine'], + 'moral': ['molar', 'moral', 'romal'], + 'morality': ['molarity', 'morality'], + 'morals': ['morals', 'morsal'], + 'moran': ['manor', 'moran', 'norma', 'ramon', 'roman'], + 'morat': ['amort', 'morat', 'torma'], + 'morate': ['amoret', 'morate'], + 'moray': ['mayor', 'moray'], + 'morbid': ['dibrom', 'morbid'], + 'mordancy': ['dormancy', 'mordancy'], + 'mordant': ['dormant', 'mordant'], + 'mordenite': ['interdome', 'mordenite', 'nemertoid'], + 'mordicate': ['decimator', 'medicator', 'mordicate'], + 'more': ['mero', 'more', 'omer', 'rome'], + 'moreish': ['heroism', 'moreish'], + 'morel': ['moler', 'morel'], + 'morencite': ['entomeric', 'intercome', 'morencite'], + 'morenita': ['maronite', 'martinoe', 'minorate', 'morenita', 'romanite'], + 'moreote': ['moreote', 'oometer'], + 'mores': ['meros', 'mores', 'morse', 'sermo', 'smore'], + 'morga': ['agrom', 'morga'], + 'morganatic': ['actinogram', 'morganatic'], + 'morgay': ['gyroma', 'morgay'], + 'morgen': ['germon', 'monger', 'morgen'], + 'moribund': ['moribund', 'unmorbid'], + 'moric': ['micro', 'moric', 'romic'], + 'moriche': ['homeric', 'moriche'], + 'morin': ['minor', 'morin'], + 'moringa': ['ingomar', 'moringa', 'roaming'], + 'moringua': ['mirounga', 'moringua', 'origanum'], + 'morn': ['morn', 'norm'], + 'morne': ['enorm', 'moner', 'morne'], + 'morned': ['modern', 'morned'], + 'mornless': ['mornless', 'normless'], + 'moro': ['moor', 'moro', 'room'], + 'morocota': ['coatroom', 'morocota'], + 'moron': ['moorn', 'moron'], + 'moronic': ['moronic', 'omicron'], + 'moronity': ['monitory', 'moronity'], + 'morphea': ['amphore', 'morphea'], + 'morphonomic': ['monomorphic', 'morphonomic'], + 'morphotic': ['microphot', 'morphotic'], + 'morphotropic': ['morphotropic', 'protomorphic'], + 'morrisean': ['morrisean', 'rosmarine'], + 'morsal': ['morals', 'morsal'], + 'morse': ['meros', 'mores', 'morse', 'sermo', 'smore'], + 'mortacious': ['mortacious', 'urosomatic'], + 'mortar': ['marrot', 'mortar'], + 'mortician': ['martinico', 'mortician'], + 'mortise': ['erotism', 'mortise', 'trisome'], + 'morton': ['morton', 'tomorn'], + 'mortuarian': ['mortuarian', 'muratorian'], + 'mortuary': ['mortuary', 'outmarry'], + 'mortuous': ['mortuous', 'tumorous'], + 'morus': ['morus', 'mosur'], + 'mosaic': ['aosmic', 'mosaic'], + 'mosandrite': ['mosandrite', 'tarsonemid'], + 'mosasauri': ['amaurosis', 'mosasauri'], + 'moschate': ['chatsome', 'moschate'], + 'mose': ['meso', 'mose', 'some'], + 'mosker': ['mosker', 'smoker'], + 'mosser': ['messor', 'mosser', 'somers'], + 'moste': ['moste', 'smote'], + 'mosting': ['gnomist', 'mosting'], + 'mosul': ['mosul', 'mouls', 'solum'], + 'mosur': ['morus', 'mosur'], + 'mot': ['mot', 'tom'], + 'mote': ['mote', 'tome'], + 'motel': ['metol', 'motel'], + 'motet': ['motet', 'motte', 'totem'], + 'mothed': ['method', 'mothed'], + 'mother': ['mother', 'thermo'], + 'motherland': ['enthraldom', 'motherland'], + 'motherward': ['motherward', 'threadworm'], + 'motograph': ['motograph', 'photogram'], + 'motographic': ['motographic', 'tomographic'], + 'motophone': ['monophote', 'motophone'], + 'motorcab': ['mobocrat', 'motorcab'], + 'motte': ['motet', 'motte', 'totem'], + 'moud': ['doum', 'moud', 'odum'], + 'moudy': ['moudy', 'yomud'], + 'moul': ['moul', 'ulmo'], + 'mouls': ['mosul', 'mouls', 'solum'], + 'mound': ['donum', 'mound'], + 'mount': ['montu', 'mount', 'notum'], + 'mountained': ['emundation', 'mountained'], + 'mountaineer': ['enumeration', 'mountaineer'], + 'mounted': ['demount', 'mounted'], + 'mounter': ['monture', 'mounter', 'remount'], + 'mousery': ['mousery', 'seymour'], + 'mousoni': ['mousoni', 'ominous'], + 'mousse': ['mousse', 'smouse'], + 'moutan': ['amount', 'moutan', 'outman'], + 'mouther': ['mouther', 'theorum'], + 'mover': ['mover', 'vomer'], + 'moy': ['moy', 'yom'], + 'moyen': ['money', 'moyen'], + 'moyenless': ['moneyless', 'moyenless'], + 'moyite': ['moiety', 'moyite'], + 'mru': ['mru', 'rum'], + 'mu': ['mu', 'um'], + 'muang': ['muang', 'munga'], + 'much': ['chum', 'much'], + 'mucic': ['cumic', 'mucic'], + 'mucilage': ['glucemia', 'mucilage'], + 'mucin': ['cumin', 'mucin'], + 'mucinoid': ['conidium', 'mucinoid', 'oncidium'], + 'mucofibrous': ['fibromucous', 'mucofibrous'], + 'mucoid': ['codium', 'mucoid'], + 'muconic': ['muconic', 'uncomic'], + 'mucor': ['mucor', 'mucro'], + 'mucoserous': ['mucoserous', 'seromucous'], + 'mucro': ['mucor', 'mucro'], + 'mucrones': ['consumer', 'mucrones'], + 'mud': ['dum', 'mud'], + 'mudar': ['mudar', 'mudra'], + 'mudden': ['edmund', 'mudden'], + 'mudir': ['mudir', 'murid'], + 'mudra': ['mudar', 'mudra'], + 'mudstone': ['mudstone', 'unmodest'], + 'mug': ['gum', 'mug'], + 'muga': ['gaum', 'muga'], + 'muggles': ['muggles', 'smuggle'], + 'mugweed': ['gumweed', 'mugweed'], + 'muid': ['duim', 'muid'], + 'muilla': ['allium', 'alulim', 'muilla'], + 'muir': ['muir', 'rimu'], + 'muishond': ['muishond', 'unmodish'], + 'muist': ['muist', 'tuism'], + 'mukri': ['kurmi', 'mukri'], + 'muleta': ['amulet', 'muleta'], + 'mulga': ['algum', 'almug', 'glaum', 'gluma', 'mulga'], + 'mulier': ['mulier', 'muriel'], + 'mulita': ['mulita', 'ultima'], + 'mulk': ['kulm', 'mulk'], + 'multani': ['multani', 'talinum'], + 'multinervose': ['multinervose', 'volunteerism'], + 'multipole': ['impollute', 'multipole'], + 'mumbler': ['bummler', 'mumbler'], + 'munda': ['maund', 'munda', 'numda', 'undam', 'unmad'], + 'mundane': ['mundane', 'unamend', 'unmaned', 'unnamed'], + 'munga': ['muang', 'munga'], + 'mungo': ['mungo', 'muong'], + 'munia': ['maniu', 'munia', 'unami'], + 'munity': ['munity', 'mutiny'], + 'muong': ['mungo', 'muong'], + 'mura': ['arum', 'maru', 'mura'], + 'murage': ['mauger', 'murage'], + 'mural': ['mural', 'rumal'], + 'muralist': ['altruism', 'muralist', 'traulism', 'ultraism'], + 'muran': ['muran', 'ruman', 'unarm', 'unram', 'urman'], + 'murat': ['martu', 'murat', 'turma'], + 'muratorian': ['mortuarian', 'muratorian'], + 'murderer': ['demurrer', 'murderer'], + 'murdering': ['demurring', 'murdering'], + 'murderingly': ['demurringly', 'murderingly'], + 'murex': ['murex', 'rumex'], + 'murga': ['garum', 'murga'], + 'muricate': ['ceratium', 'muricate'], + 'muricine': ['irenicum', 'muricine'], + 'murid': ['mudir', 'murid'], + 'muriel': ['mulier', 'muriel'], + 'murine': ['murine', 'nerium'], + 'murly': ['murly', 'rumly'], + 'murmurer': ['murmurer', 'remurmur'], + 'murrain': ['murrain', 'murrina'], + 'murrina': ['murrain', 'murrina'], + 'murut': ['murut', 'utrum'], + 'murza': ['mazur', 'murza'], + 'mus': ['mus', 'sum'], + 'musa': ['masu', 'musa', 'saum'], + 'musal': ['lamus', 'malus', 'musal', 'slaum'], + 'musalmani': ['manualism', 'musalmani'], + 'musang': ['magnus', 'musang'], + 'musar': ['musar', 'ramus', 'rusma', 'surma'], + 'musca': ['camus', 'musca', 'scaum', 'sumac'], + 'muscade': ['camused', 'muscade'], + 'muscarine': ['muscarine', 'sucramine'], + 'musci': ['musci', 'music'], + 'muscicole': ['leucocism', 'muscicole'], + 'muscinae': ['muscinae', 'semuncia'], + 'muscle': ['clumse', 'muscle'], + 'muscly': ['clumsy', 'muscly'], + 'muscone': ['consume', 'muscone'], + 'muscot': ['custom', 'muscot'], + 'mused': ['mused', 'sedum'], + 'museography': ['hypergamous', 'museography'], + 'muser': ['muser', 'remus', 'serum'], + 'musha': ['hamus', 'musha'], + 'music': ['musci', 'music'], + 'musicate': ['autecism', 'musicate'], + 'musico': ['musico', 'suomic'], + 'musie': ['iseum', 'musie'], + 'musing': ['musing', 'signum'], + 'muslined': ['muslined', 'unmisled', 'unsmiled'], + 'musophagine': ['amphigenous', 'musophagine'], + 'mussaenda': ['mussaenda', 'unamassed'], + 'must': ['must', 'smut', 'stum'], + 'mustang': ['mustang', 'stagnum'], + 'mustard': ['durmast', 'mustard'], + 'muster': ['muster', 'sertum', 'stumer'], + 'musterer': ['musterer', 'remuster'], + 'mustily': ['mustily', 'mytilus'], + 'muta': ['muta', 'taum'], + 'mutable': ['atumble', 'mutable'], + 'mutant': ['mutant', 'tantum', 'tutman'], + 'mutase': ['meatus', 'mutase'], + 'mute': ['mute', 'tume'], + 'muteness': ['muteness', 'tenesmus'], + 'mutescence': ['mutescence', 'tumescence'], + 'mutilate': ['mutilate', 'ultimate'], + 'mutilation': ['mutilation', 'ultimation'], + 'mutiny': ['munity', 'mutiny'], + 'mutism': ['mutism', 'summit'], + 'mutual': ['mutual', 'umlaut'], + 'mutulary': ['mutulary', 'tumulary'], + 'muysca': ['cyamus', 'muysca'], + 'mwa': ['maw', 'mwa'], + 'my': ['my', 'ym'], + 'mya': ['amy', 'may', 'mya', 'yam'], + 'myal': ['amyl', 'lyam', 'myal'], + 'myaria': ['amiray', 'myaria'], + 'myatonic': ['cymation', 'myatonic', 'onymatic'], + 'mycelia': ['amyelic', 'mycelia'], + 'mycelian': ['clymenia', 'mycelian'], + 'mycoid': ['cymoid', 'mycoid'], + 'mycophagist': ['mycophagist', 'phagocytism'], + 'mycose': ['cymose', 'mycose'], + 'mycosterol': ['mycosterol', 'sclerotomy'], + 'mycotrophic': ['chromotypic', 'cormophytic', 'mycotrophic'], + 'mycterism': ['mycterism', 'symmetric'], + 'myctodera': ['myctodera', 'radectomy'], + 'mydaleine': ['amylidene', 'mydaleine'], + 'myeloencephalitis': ['encephalomyelitis', 'myeloencephalitis'], + 'myelomeningitis': ['meningomyelitis', 'myelomeningitis'], + 'myelomeningocele': ['meningomyelocele', 'myelomeningocele'], + 'myelon': ['lemony', 'myelon'], + 'myelonal': ['amylenol', 'myelonal'], + 'myeloneuritis': ['myeloneuritis', 'neuromyelitis'], + 'myeloplast': ['meloplasty', 'myeloplast'], + 'mygale': ['gamely', 'gleamy', 'mygale'], + 'myitis': ['myitis', 'simity'], + 'myliobatid': ['bimodality', 'myliobatid'], + 'mymar': ['mymar', 'rammy'], + 'myna': ['many', 'myna'], + 'myoatrophy': ['amyotrophy', 'myoatrophy'], + 'myocolpitis': ['myocolpitis', 'polysomitic'], + 'myofibroma': ['fibromyoma', 'myofibroma'], + 'myoglobin': ['boomingly', 'myoglobin'], + 'myographic': ['microphagy', 'myographic'], + 'myographist': ['myographist', 'pythagorism'], + 'myolipoma': ['lipomyoma', 'myolipoma'], + 'myomohysterectomy': ['hysteromyomectomy', 'myomohysterectomy'], + 'myope': ['myope', 'pomey'], + 'myoplastic': ['myoplastic', 'polymastic'], + 'myoplasty': ['myoplasty', 'polymasty'], + 'myopolar': ['myopolar', 'playroom'], + 'myops': ['mopsy', 'myops'], + 'myosin': ['isonym', 'myosin', 'simony'], + 'myosote': ['myosote', 'toysome'], + 'myotenotomy': ['myotenotomy', 'tenomyotomy'], + 'myotic': ['comity', 'myotic'], + 'myra': ['army', 'mary', 'myra', 'yarm'], + 'myrcia': ['myrcia', 'myrica'], + 'myrialiter': ['myrialiter', 'myrialitre'], + 'myrialitre': ['myrialiter', 'myrialitre'], + 'myriameter': ['myriameter', 'myriametre'], + 'myriametre': ['myriameter', 'myriametre'], + 'myrianida': ['dimyarian', 'myrianida'], + 'myrica': ['myrcia', 'myrica'], + 'myristate': ['myristate', 'tasimetry'], + 'myristin': ['ministry', 'myristin'], + 'myristone': ['myristone', 'smyrniote'], + 'myronate': ['monetary', 'myronate', 'naometry'], + 'myrsinad': ['misandry', 'myrsinad'], + 'myrtales': ['masterly', 'myrtales'], + 'myrtle': ['myrtle', 'termly'], + 'mysell': ['mysell', 'smelly'], + 'mysian': ['maysin', 'minyas', 'mysian'], + 'mysis': ['missy', 'mysis'], + 'mysterial': ['mysterial', 'salimetry'], + 'mystes': ['mystes', 'system'], + 'mythographer': ['mythographer', 'thermography'], + 'mythogreen': ['mythogreen', 'thermogeny'], + 'mythologer': ['mythologer', 'thermology'], + 'mythopoeic': ['homeotypic', 'mythopoeic'], + 'mythus': ['mythus', 'thymus'], + 'mytilid': ['mytilid', 'timidly'], + 'mytilus': ['mustily', 'mytilus'], + 'myxochondroma': ['chondromyxoma', 'myxochondroma'], + 'myxochondrosarcoma': ['chondromyxosarcoma', 'myxochondrosarcoma'], + 'myxocystoma': ['cystomyxoma', 'myxocystoma'], + 'myxofibroma': ['fibromyxoma', 'myxofibroma'], + 'myxofibrosarcoma': ['fibromyxosarcoma', 'myxofibrosarcoma'], + 'myxoinoma': ['inomyxoma', 'myxoinoma'], + 'myxolipoma': ['lipomyxoma', 'myxolipoma'], + 'myxotheca': ['chemotaxy', 'myxotheca'], + 'na': ['an', 'na'], + 'naa': ['ana', 'naa'], + 'naam': ['anam', 'mana', 'naam', 'nama'], + 'nab': ['ban', 'nab'], + 'nabak': ['banak', 'nabak'], + 'nabal': ['alban', 'balan', 'banal', 'laban', 'nabal', 'nabla'], + 'nabalism': ['bailsman', 'balanism', 'nabalism'], + 'nabalite': ['albanite', 'balanite', 'nabalite'], + 'nabalus': ['balanus', 'nabalus', 'subanal'], + 'nabk': ['bank', 'knab', 'nabk'], + 'nabla': ['alban', 'balan', 'banal', 'laban', 'nabal', 'nabla'], + 'nable': ['leban', 'nable'], + 'nabobishly': ['babylonish', 'nabobishly'], + 'nabothian': ['bathonian', 'nabothian'], + 'nabs': ['nabs', 'snab'], + 'nabu': ['baun', 'buna', 'nabu', 'nuba'], + 'nacarat': ['cantara', 'nacarat'], + 'nace': ['acne', 'cane', 'nace'], + 'nachitoch': ['chanchito', 'nachitoch'], + 'nacre': ['caner', 'crane', 'crena', 'nacre', 'rance'], + 'nacred': ['cedarn', 'dancer', 'nacred'], + 'nacreous': ['carneous', 'nacreous'], + 'nacrite': ['centiar', 'certain', 'citrean', 'nacrite', 'nectria'], + 'nacrous': ['carnous', 'nacrous', 'narcous'], + 'nadder': ['dander', 'darned', 'nadder'], + 'nadeem': ['amende', 'demean', 'meaned', 'nadeem'], + 'nadir': ['darin', 'dinar', 'drain', 'indra', 'nadir', 'ranid'], + 'nadorite': ['andorite', 'nadorite', 'ordinate', 'rodentia'], + 'nae': ['ean', 'nae', 'nea'], + 'nael': ['alen', 'lane', 'lean', 'lena', 'nael', 'neal'], + 'naether': ['earthen', 'enheart', 'hearten', 'naether', 'teheran', 'traheen'], + 'nag': ['gan', 'nag'], + 'nagara': ['angara', 'aranga', 'nagara'], + 'nagari': ['graian', 'nagari'], + 'nagatelite': ['gelatinate', 'nagatelite'], + 'nagger': ['ganger', 'grange', 'nagger'], + 'nagging': ['ganging', 'nagging'], + 'naggle': ['laggen', 'naggle'], + 'naggly': ['gangly', 'naggly'], + 'nagmaal': ['malanga', 'nagmaal'], + 'nagnag': ['gangan', 'nagnag'], + 'nagnail': ['alangin', 'anginal', 'anglian', 'nagnail'], + 'nagor': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'nagster': ['angster', 'garnets', 'nagster', 'strange'], + 'nagual': ['angula', 'nagual'], + 'nahani': ['hainan', 'nahani'], + 'nahor': ['nahor', 'norah', 'rohan'], + 'nahum': ['human', 'nahum'], + 'naiad': ['danai', 'diana', 'naiad'], + 'naiant': ['naiant', 'tainan'], + 'naias': ['asian', 'naias', 'sanai'], + 'naid': ['adin', 'andi', 'dain', 'dani', 'dian', 'naid'], + 'naif': ['fain', 'naif'], + 'naifly': ['fainly', 'naifly'], + 'naig': ['gain', 'inga', 'naig', 'ngai'], + 'naik': ['akin', 'kina', 'naik'], + 'nail': ['alin', 'anil', 'lain', 'lina', 'nail'], + 'nailer': ['arline', 'larine', 'linear', 'nailer', 'renail'], + 'naileress': ['earliness', 'naileress'], + 'nailery': ['inlayer', 'nailery'], + 'nailless': ['nailless', 'sensilla'], + 'nailrod': ['nailrod', 'ordinal', 'rinaldo', 'rodinal'], + 'nailshop': ['nailshop', 'siphonal'], + 'naily': ['inlay', 'naily'], + 'naim': ['amin', 'main', 'mani', 'mian', 'mina', 'naim'], + 'nain': ['nain', 'nina'], + 'naio': ['aion', 'naio'], + 'nair': ['arni', 'iran', 'nair', 'rain', 'rani'], + 'nairy': ['nairy', 'rainy'], + 'nais': ['anis', 'nais', 'nasi', 'nias', 'sain', 'sina'], + 'naish': ['naish', 'shina'], + 'naither': ['anither', 'inearth', 'naither'], + 'naive': ['avine', 'naive', 'vinea'], + 'naivete': ['naivete', 'nieveta'], + 'nak': ['kan', 'nak'], + 'naked': ['kande', 'knead', 'naked'], + 'nakedish': ['headskin', 'nakedish', 'sinkhead'], + 'naker': ['anker', 'karen', 'naker'], + 'nakir': ['inkra', 'krina', 'nakir', 'rinka'], + 'nako': ['kona', 'nako'], + 'nalita': ['antlia', 'latian', 'nalita'], + 'nallah': ['hallan', 'nallah'], + 'nam': ['man', 'nam'], + 'nama': ['anam', 'mana', 'naam', 'nama'], + 'namaz': ['namaz', 'zaman'], + 'nambe': ['beman', 'nambe'], + 'namda': ['adman', 'daman', 'namda'], + 'name': ['amen', 'enam', 'mane', 'mean', 'name', 'nema'], + 'nameability': ['amenability', 'nameability'], + 'nameable': ['amenable', 'nameable'], + 'nameless': ['lameness', 'maleness', 'maneless', 'nameless'], + 'nameling': ['mangelin', 'nameling'], + 'namely': ['meanly', 'namely'], + 'namer': ['enarm', 'namer', 'reman'], + 'nammad': ['madman', 'nammad'], + 'nan': ['ann', 'nan'], + 'nana': ['anan', 'anna', 'nana'], + 'nanaimo': ['nanaimo', 'omniana'], + 'nancy': ['canny', 'nancy'], + 'nandi': ['indan', 'nandi'], + 'nane': ['anne', 'nane'], + 'nanes': ['nanes', 'senna'], + 'nanoid': ['adonin', 'nanoid', 'nonaid'], + 'nanosomia': ['nanosomia', 'nosomania'], + 'nanpie': ['nanpie', 'pennia', 'pinnae'], + 'naological': ['colonalgia', 'naological'], + 'naometry': ['monetary', 'myronate', 'naometry'], + 'naomi': ['amino', 'inoma', 'naomi', 'omani', 'omina'], + 'naoto': ['naoto', 'toona'], + 'nap': ['nap', 'pan'], + 'napaean': ['anapnea', 'napaean'], + 'nape': ['nape', 'neap', 'nepa', 'pane', 'pean'], + 'napead': ['napead', 'panade'], + 'napery': ['napery', 'pyrena'], + 'naphthalize': ['naphthalize', 'phthalazine'], + 'naphtol': ['haplont', 'naphtol'], + 'napkin': ['napkin', 'pankin'], + 'napped': ['append', 'napped'], + 'napper': ['napper', 'papern'], + 'napron': ['napron', 'nonpar'], + 'napthionic': ['antiphonic', 'napthionic'], + 'napu': ['napu', 'puan', 'puna'], + 'nar': ['arn', 'nar', 'ran'], + 'narcaciontes': ['narcaciontes', 'transoceanic'], + 'narcose': ['carnose', 'coarsen', 'narcose'], + 'narcotia': ['craniota', 'croatian', 'narcotia', 'raincoat'], + 'narcoticism': ['intracosmic', 'narcoticism'], + 'narcotina': ['anarcotin', 'cantorian', 'carnation', 'narcotina'], + 'narcotine': ['connarite', 'container', 'cotarnine', 'crenation', 'narcotine'], + 'narcotism': ['narcotism', 'romancist'], + 'narcotist': ['narcotist', 'stratonic'], + 'narcotize': ['narcotize', 'zirconate'], + 'narcous': ['carnous', 'nacrous', 'narcous'], + 'nard': ['darn', 'nard', 'rand'], + 'nardine': ['adrenin', 'nardine'], + 'nardus': ['nardus', 'sundar', 'sundra'], + 'nares': ['anser', 'nares', 'rasen', 'snare'], + 'nargil': ['nargil', 'raglin'], + 'naric': ['cairn', 'crain', 'naric'], + 'narica': ['acinar', + 'arnica', + 'canari', + 'carian', + 'carina', + 'crania', + 'narica'], + 'nariform': ['nariform', 'raniform'], + 'narine': ['narine', 'ranine'], + 'nark': ['knar', 'kran', 'nark', 'rank'], + 'narration': ['narration', 'tornarian'], + 'narthecium': ['anthericum', 'narthecium'], + 'nary': ['nary', 'yarn'], + 'nasab': ['nasab', 'saban'], + 'nasal': ['alans', 'lanas', 'nasal'], + 'nasalism': ['nasalism', 'sailsman'], + 'nasard': ['nasard', 'sandra'], + 'nascapi': ['capsian', 'caspian', 'nascapi', 'panisca'], + 'nash': ['hans', 'nash', 'shan'], + 'nashgab': ['bangash', 'nashgab'], + 'nasi': ['anis', 'nais', 'nasi', 'nias', 'sain', 'sina'], + 'nasial': ['anisal', 'nasial', 'salian', 'salina'], + 'nasitis': ['nasitis', 'sistani'], + 'nasoantral': ['antronasal', 'nasoantral'], + 'nasobuccal': ['bucconasal', 'nasobuccal'], + 'nasofrontal': ['frontonasal', 'nasofrontal'], + 'nasolabial': ['labionasal', 'nasolabial'], + 'nasolachrymal': ['lachrymonasal', 'nasolachrymal'], + 'nasonite': ['estonian', 'nasonite'], + 'nasoorbital': ['nasoorbital', 'orbitonasal'], + 'nasopalatal': ['nasopalatal', 'palatonasal'], + 'nasoseptal': ['nasoseptal', 'septonasal'], + 'nassa': ['nassa', 'sasan'], + 'nassidae': ['assidean', 'nassidae'], + 'nast': ['nast', 'sant', 'stan'], + 'nastic': ['incast', 'nastic'], + 'nastily': ['nastily', 'saintly', 'staynil'], + 'nasturtion': ['antrustion', 'nasturtion'], + 'nasty': ['nasty', 'styan', 'tansy'], + 'nasua': ['nasua', 'sauna'], + 'nasus': ['nasus', 'susan'], + 'nasute': ['nasute', 'nauset', 'unseat'], + 'nat': ['ant', 'nat', 'tan'], + 'nataka': ['nataka', 'tanaka'], + 'natal': ['antal', 'natal'], + 'natalia': ['altaian', 'latania', 'natalia'], + 'natalie': ['laniate', 'natalie', 'taenial'], + 'nataloin': ['latonian', 'nataloin', 'national'], + 'natals': ['aslant', 'lansat', 'natals', 'santal'], + 'natator': ['arnotta', 'natator'], + 'natatorium': ['maturation', 'natatorium'], + 'natch': ['chant', 'natch'], + 'nate': ['ante', 'aten', 'etna', 'nate', 'neat', 'taen', 'tane', 'tean'], + 'nates': ['antes', 'nates', 'stane', 'stean'], + 'nathan': ['nathan', 'thanan'], + 'nathe': ['enhat', 'ethan', 'nathe', 'neath', 'thane'], + 'nather': ['anther', 'nather', 'tharen', 'thenar'], + 'natica': ['actian', 'natica', 'tanica'], + 'naticiform': ['actiniform', 'naticiform'], + 'naticine': ['actinine', 'naticine'], + 'natick': ['catkin', 'natick'], + 'naticoid': ['actinoid', 'diatonic', 'naticoid'], + 'nation': ['anoint', 'nation'], + 'national': ['latonian', 'nataloin', 'national'], + 'native': ['native', 'navite'], + 'natively': ['natively', 'venality'], + 'nativist': ['nativist', 'visitant'], + 'natr': ['natr', 'rant', 'tarn', 'tran'], + 'natricinae': ['natricinae', 'nectarinia'], + 'natricine': ['crinanite', 'natricine'], + 'natrolite': ['natrolite', 'tentorial'], + 'natter': ['attern', 'natter', 'ratten', 'tarten'], + 'nattered': ['attender', 'nattered', 'reattend'], + 'nattily': ['nattily', 'titanyl'], + 'nattle': ['latent', 'latten', 'nattle', 'talent', 'tantle'], + 'naturalistic': ['naturalistic', 'unartistical'], + 'naturing': ['gainturn', 'naturing'], + 'naturism': ['naturism', 'sturmian', 'turanism'], + 'naturist': ['antirust', 'naturist'], + 'naturistic': ['naturistic', 'unartistic'], + 'naturistically': ['naturistically', 'unartistically'], + 'nauger': ['nauger', 'raunge', 'ungear'], + 'naumk': ['kuman', 'naumk'], + 'naunt': ['naunt', 'tunna'], + 'nauntle': ['annulet', 'nauntle'], + 'nauplius': ['nauplius', 'paulinus'], + 'nauset': ['nasute', 'nauset', 'unseat'], + 'naut': ['antu', 'aunt', 'naut', 'taun', 'tuan', 'tuna'], + 'nauther': ['haunter', 'nauther', 'unearth', 'unheart', 'urethan'], + 'nautic': ['anicut', 'nautic', 'ticuna', 'tunica'], + 'nautical': ['actinula', 'nautical'], + 'nautiloid': ['lutianoid', 'nautiloid'], + 'nautilus': ['lutianus', 'nautilus', 'ustulina'], + 'naval': ['alvan', 'naval'], + 'navalist': ['navalist', 'salivant'], + 'navar': ['navar', 'varan', 'varna'], + 'nave': ['evan', 'nave', 'vane'], + 'navel': ['elvan', 'navel', 'venal'], + 'naviculare': ['naviculare', 'uncavalier'], + 'navigant': ['navigant', 'vaginant'], + 'navigate': ['navigate', 'vaginate'], + 'navite': ['native', 'navite'], + 'naw': ['awn', 'naw', 'wan'], + 'nawt': ['nawt', 'tawn', 'want'], + 'nay': ['any', 'nay', 'yan'], + 'nayar': ['aryan', 'nayar', 'rayan'], + 'nazarite': ['nazarite', 'nazirate', 'triazane'], + 'nazi': ['nazi', 'zain'], + 'nazim': ['nazim', 'nizam'], + 'nazirate': ['nazarite', 'nazirate', 'triazane'], + 'nazirite': ['nazirite', 'triazine'], + 'ne': ['en', 'ne'], + 'nea': ['ean', 'nae', 'nea'], + 'neal': ['alen', 'lane', 'lean', 'lena', 'nael', 'neal'], + 'neanic': ['canine', 'encina', 'neanic'], + 'neap': ['nape', 'neap', 'nepa', 'pane', 'pean'], + 'neapolitan': ['antelopian', 'neapolitan', 'panelation'], + 'nearby': ['barney', 'nearby'], + 'nearctic': ['acentric', 'encratic', 'nearctic'], + 'nearest': ['earnest', 'eastern', 'nearest'], + 'nearish': ['arshine', 'nearish', 'rhesian', 'sherani'], + 'nearly': ['anerly', 'nearly'], + 'nearmost': ['monaster', 'monstera', 'nearmost', 'storeman'], + 'nearthrosis': ['enarthrosis', 'nearthrosis'], + 'neat': ['ante', 'aten', 'etna', 'nate', 'neat', 'taen', 'tane', 'tean'], + 'neaten': ['etnean', 'neaten'], + 'neath': ['enhat', 'ethan', 'nathe', 'neath', 'thane'], + 'neatherd': ['adherent', 'headrent', 'neatherd', 'threaden'], + 'neatherdess': ['heartedness', 'neatherdess'], + 'neb': ['ben', 'neb'], + 'neback': ['backen', 'neback'], + 'nebaioth': ['boethian', 'nebaioth'], + 'nebalia': ['abelian', 'nebalia'], + 'nebelist': ['nebelist', 'stilbene', 'tensible'], + 'nebula': ['nebula', 'unable', 'unbale'], + 'nebulose': ['bluenose', 'nebulose'], + 'necator': ['enactor', 'necator', 'orcanet'], + 'necessarian': ['necessarian', 'renaissance'], + 'neckar': ['canker', 'neckar'], + 'necrogenic': ['congeneric', 'necrogenic'], + 'necrogenous': ['congenerous', 'necrogenous'], + 'necrology': ['crenology', 'necrology'], + 'necropoles': ['necropoles', 'preconsole'], + 'necropolis': ['clinospore', 'necropolis'], + 'necrotic': ['crocetin', 'necrotic'], + 'necrotomic': ['necrotomic', 'oncometric'], + 'necrotomy': ['necrotomy', 'normocyte', 'oncometry'], + 'nectar': ['canter', + 'creant', + 'cretan', + 'nectar', + 'recant', + 'tanrec', + 'trance'], + 'nectareal': ['lactarene', 'nectareal'], + 'nectared': ['crenated', 'decanter', 'nectared'], + 'nectareous': ['countersea', 'nectareous'], + 'nectarial': ['carnalite', 'claretian', 'lacertian', 'nectarial'], + 'nectarian': ['cratinean', 'incarnate', 'nectarian'], + 'nectaried': ['nectaried', 'tridecane'], + 'nectarine': ['inertance', 'nectarine'], + 'nectarinia': ['natricinae', 'nectarinia'], + 'nectarious': ['nectarious', 'recusation'], + 'nectarlike': ['nectarlike', 'trancelike'], + 'nectarous': ['acentrous', 'courtesan', 'nectarous'], + 'nectary': ['encraty', 'nectary'], + 'nectophore': ['ctenophore', 'nectophore'], + 'nectria': ['centiar', 'certain', 'citrean', 'nacrite', 'nectria'], + 'ned': ['den', 'end', 'ned'], + 'nedder': ['nedder', 'redden'], + 'neebor': ['boreen', 'enrobe', 'neebor', 'rebone'], + 'need': ['dene', 'eden', 'need'], + 'needer': ['endere', 'needer', 'reeden'], + 'needfire': ['needfire', 'redefine'], + 'needily': ['needily', 'yielden'], + 'needle': ['lendee', 'needle'], + 'needless': ['needless', 'seldseen'], + 'needs': ['dense', 'needs'], + 'needsome': ['modenese', 'needsome'], + 'neeger': ['neeger', 'reenge', 'renege'], + 'neeld': ['leden', 'neeld'], + 'neep': ['neep', 'peen'], + 'neepour': ['neepour', 'neurope'], + 'neer': ['erne', 'neer', 'reen'], + 'neet': ['neet', 'nete', 'teen'], + 'neetup': ['neetup', 'petune'], + 'nef': ['fen', 'nef'], + 'nefast': ['fasten', 'nefast', 'stefan'], + 'neftgil': ['felting', 'neftgil'], + 'negate': ['geneat', 'negate', 'tegean'], + 'negation': ['antigone', 'negation'], + 'negative': ['agentive', 'negative'], + 'negativism': ['negativism', 'timesaving'], + 'negator': ['negator', 'tronage'], + 'negatron': ['argenton', 'negatron'], + 'neger': ['genre', 'green', 'neger', 'reneg'], + 'neglecter': ['neglecter', 'reneglect'], + 'negritian': ['negritian', 'retaining'], + 'negrito': ['ergotin', 'genitor', 'negrito', 'ogtiern', 'trigone'], + 'negritoid': ['negritoid', 'rodingite'], + 'negro': ['ergon', 'genro', 'goner', 'negro'], + 'negroid': ['groined', 'negroid'], + 'negroidal': ['girandole', 'negroidal'], + 'negroize': ['genizero', 'negroize'], + 'negroloid': ['gondolier', 'negroloid'], + 'negrotic': ['gerontic', 'negrotic'], + 'negundo': ['dungeon', 'negundo'], + 'negus': ['genus', 'negus'], + 'neif': ['enif', 'fine', 'neif', 'nife'], + 'neigh': ['hinge', 'neigh'], + 'neil': ['lien', 'line', 'neil', 'nile'], + 'neiper': ['neiper', 'perine', 'pirene', 'repine'], + 'neist': ['inset', 'neist', 'snite', 'stein', 'stine', 'tsine'], + 'neither': ['enherit', 'etherin', 'neither', 'therein'], + 'nekkar': ['kraken', 'nekkar'], + 'nekton': ['kenton', 'nekton'], + 'nelken': ['kennel', 'nelken'], + 'nelsonite': ['nelsonite', 'solentine'], + 'nelumbian': ['nelumbian', 'unminable'], + 'nema': ['amen', 'enam', 'mane', 'mean', 'name', 'nema'], + 'nemalite': ['melanite', 'meletian', 'metaline', 'nemalite'], + 'nematoda': ['mantodea', 'nematoda'], + 'nematoid': ['dominate', 'nematoid'], + 'nematophyton': ['nematophyton', 'tenontophyma'], + 'nemertinea': ['minnetaree', 'nemertinea'], + 'nemertini': ['intermine', 'nemertini', 'terminine'], + 'nemertoid': ['interdome', 'mordenite', 'nemertoid'], + 'nemoral': ['almoner', 'moneral', 'nemoral'], + 'nenta': ['anent', 'annet', 'nenta'], + 'neo': ['eon', 'neo', 'one'], + 'neoarctic': ['accretion', 'anorectic', 'neoarctic'], + 'neocomian': ['monoecian', 'neocomian'], + 'neocosmic': ['economics', 'neocosmic'], + 'neocyte': ['enocyte', 'neocyte'], + 'neogaea': ['eogaean', 'neogaea'], + 'neogenesis': ['neogenesis', 'noegenesis'], + 'neogenetic': ['neogenetic', 'noegenetic'], + 'neognathous': ['anthogenous', 'neognathous'], + 'neolatry': ['neolatry', 'ornately', 'tyrolean'], + 'neolithic': ['ichnolite', 'neolithic'], + 'neomiracle': ['ceremonial', 'neomiracle'], + 'neomorphic': ['microphone', 'neomorphic'], + 'neon': ['neon', 'none'], + 'neophilism': ['neophilism', 'philoneism'], + 'neophytic': ['hypnoetic', 'neophytic'], + 'neoplasm': ['neoplasm', 'pleonasm', 'polesman', 'splenoma'], + 'neoplastic': ['neoplastic', 'pleonastic'], + 'neorama': ['neorama', 'romaean'], + 'neornithes': ['neornithes', 'rhinestone'], + 'neossin': ['neossin', 'sension'], + 'neoteric': ['erection', 'neoteric', 'nocerite', 'renotice'], + 'neoterism': ['moistener', 'neoterism'], + 'neotragus': ['argentous', 'neotragus'], + 'neotropic': ['ectropion', 'neotropic'], + 'neotropical': ['neotropical', 'percolation'], + 'neoza': ['neoza', 'ozena'], + 'nep': ['nep', 'pen'], + 'nepa': ['nape', 'neap', 'nepa', 'pane', 'pean'], + 'nepal': ['alpen', 'nepal', 'panel', 'penal', 'plane'], + 'nepali': ['alpine', 'nepali', 'penial', 'pineal'], + 'neper': ['neper', 'preen', 'repen'], + 'nepheloscope': ['nepheloscope', 'phonelescope'], + 'nephite': ['heptine', 'nephite'], + 'nephogram': ['gomphrena', 'nephogram'], + 'nephological': ['nephological', 'phenological'], + 'nephologist': ['nephologist', 'phenologist'], + 'nephology': ['nephology', 'phenology'], + 'nephria': ['heparin', 'nephria'], + 'nephric': ['nephric', 'phrenic', 'pincher'], + 'nephrite': ['nephrite', 'prehnite', 'trephine'], + 'nephritic': ['nephritic', 'phrenitic', 'prehnitic'], + 'nephritis': ['inspreith', 'nephritis', 'phrenitis'], + 'nephrocardiac': ['nephrocardiac', 'phrenocardiac'], + 'nephrocolic': ['nephrocolic', 'phrenocolic'], + 'nephrocystosis': ['cystonephrosis', 'nephrocystosis'], + 'nephrogastric': ['gastrophrenic', 'nephrogastric', 'phrenogastric'], + 'nephrohydrosis': ['hydronephrosis', 'nephrohydrosis'], + 'nephrolithotomy': ['lithonephrotomy', 'nephrolithotomy'], + 'nephrologist': ['nephrologist', 'phrenologist'], + 'nephrology': ['nephrology', 'phrenology'], + 'nephropathic': ['nephropathic', 'phrenopathic'], + 'nephropathy': ['nephropathy', 'phrenopathy'], + 'nephropsidae': ['nephropsidae', 'praesphenoid'], + 'nephroptosia': ['nephroptosia', 'prosiphonate'], + 'nephropyelitis': ['nephropyelitis', 'pyelonephritis'], + 'nephropyosis': ['nephropyosis', 'pyonephrosis'], + 'nephrosis': ['nephrosis', 'phronesis'], + 'nephrostoma': ['nephrostoma', 'strophomena'], + 'nephrotome': ['nephrotome', 'phonometer'], + 'nephrotomy': ['nephrotomy', 'phonometry'], + 'nepman': ['nepman', 'penman'], + 'nepotal': ['lepanto', 'nepotal', 'petalon', 'polenta'], + 'nepote': ['nepote', 'pontee', 'poteen'], + 'nepotic': ['entopic', 'nepotic', 'pentoic'], + 'nereid': ['denier', 'nereid'], + 'nereis': ['inseer', 'nereis', 'seiner', 'serine', 'sirene'], + 'neri': ['neri', 'rein', 'rine'], + 'nerita': ['nerita', 'ratine', 'retain', 'retina', 'tanier'], + 'neritic': ['citrine', 'crinite', 'inciter', 'neritic'], + 'neritina': ['neritina', 'retinian'], + 'neritoid': ['neritoid', 'retinoid'], + 'nerium': ['murine', 'nerium'], + 'neroic': ['cerion', 'coiner', 'neroic', 'orcein', 'recoin'], + 'neronic': ['corinne', 'cornein', 'neronic'], + 'nerval': ['nerval', 'vernal'], + 'nervate': ['nervate', 'veteran'], + 'nervation': ['nervation', 'vernation'], + 'nerve': ['nerve', 'never'], + 'nervid': ['driven', 'nervid', 'verdin'], + 'nervine': ['innerve', 'nervine', 'vernine'], + 'nerviness': ['inverness', 'nerviness'], + 'nervish': ['nervish', 'shriven'], + 'nervulose': ['nervulose', 'unresolve', 'vulnerose'], + 'nese': ['ense', 'esne', 'nese', 'seen', 'snee'], + 'nesh': ['nesh', 'shen'], + 'nesiot': ['nesiot', 'ostein'], + 'neskhi': ['kishen', 'neskhi'], + 'neslia': ['alsine', 'neslia', 'saline', 'selina', 'silane'], + 'nest': ['nest', 'sent', 'sten'], + 'nester': ['ernest', 'nester', 'resent', 'streen'], + 'nestiatria': ['intarsiate', 'nestiatria'], + 'nestlike': ['nestlike', 'skeletin'], + 'nestor': ['nestor', 'sterno', 'stoner', 'strone', 'tensor'], + 'net': ['net', 'ten'], + 'netcha': ['entach', 'netcha'], + 'nete': ['neet', 'nete', 'teen'], + 'neter': ['enter', 'neter', 'renet', 'terne', 'treen'], + 'netful': ['fluent', 'netful', 'unfelt', 'unleft'], + 'neth': ['hent', 'neth', 'then'], + 'nether': ['erthen', 'henter', 'nether', 'threne'], + 'neti': ['iten', 'neti', 'tien', 'tine'], + 'netman': ['manent', 'netman'], + 'netsuke': ['kuneste', 'netsuke'], + 'nettable': ['nettable', 'tentable'], + 'nettapus': ['nettapus', 'stepaunt'], + 'netted': ['detent', 'netted', 'tented'], + 'netter': ['netter', 'retent', 'tenter'], + 'nettion': ['nettion', 'tention', 'tontine'], + 'nettle': ['letten', 'nettle'], + 'nettler': ['nettler', 'ternlet'], + 'netty': ['netty', 'tenty'], + 'neurad': ['endura', 'neurad', 'undear', 'unread'], + 'neural': ['lunare', 'neural', 'ulnare', 'unreal'], + 'neuralgic': ['genicular', 'neuralgic'], + 'neuralist': ['neuralist', 'ulsterian', 'unrealist'], + 'neurectopia': ['eucatropine', 'neurectopia'], + 'neuric': ['curine', 'erucin', 'neuric'], + 'neurilema': ['lemurinae', 'neurilema'], + 'neurin': ['enruin', 'neurin', 'unrein'], + 'neurism': ['neurism', 'semiurn'], + 'neurite': ['neurite', 'retinue', 'reunite', 'uterine'], + 'neuroblast': ['neuroblast', 'unsortable'], + 'neurodermatosis': ['dermatoneurosis', 'neurodermatosis'], + 'neurofibroma': ['fibroneuroma', 'neurofibroma'], + 'neurofil': ['fluorine', 'neurofil'], + 'neuroganglion': ['ganglioneuron', 'neuroganglion'], + 'neurogenic': ['encoignure', 'neurogenic'], + 'neuroid': ['dourine', 'neuroid'], + 'neurolysis': ['neurolysis', 'resinously'], + 'neuromast': ['anoestrum', 'neuromast'], + 'neuromyelitis': ['myeloneuritis', 'neuromyelitis'], + 'neuronal': ['enaluron', 'neuronal'], + 'neurope': ['neepour', 'neurope'], + 'neuropsychological': ['neuropsychological', 'psychoneurological'], + 'neuropsychosis': ['neuropsychosis', 'psychoneurosis'], + 'neuropteris': ['interposure', 'neuropteris'], + 'neurosis': ['neurosis', 'resinous'], + 'neurotic': ['eruction', 'neurotic'], + 'neurotripsy': ['neurotripsy', 'tripyrenous'], + 'neustrian': ['neustrian', 'saturnine', 'sturninae'], + 'neuter': ['neuter', 'retune', 'runtee', 'tenure', 'tureen'], + 'neuterly': ['neuterly', 'rutylene'], + 'neutral': ['laurent', 'neutral', 'unalert'], + 'neutralism': ['neutralism', 'trimensual'], + 'neutrally': ['neutrally', 'unalertly'], + 'neutralness': ['neutralness', 'unalertness'], + 'nevada': ['nevada', 'vedana', 'venada'], + 'neve': ['even', 'neve', 'veen'], + 'never': ['nerve', 'never'], + 'nevo': ['nevo', 'oven'], + 'nevoy': ['envoy', 'nevoy', 'yoven'], + 'nevus': ['nevus', 'venus'], + 'new': ['new', 'wen'], + 'newar': ['awner', 'newar'], + 'newari': ['newari', 'wainer'], + 'news': ['news', 'sewn', 'snew'], + 'newt': ['newt', 'went'], + 'nexus': ['nexus', 'unsex'], + 'ngai': ['gain', 'inga', 'naig', 'ngai'], + 'ngaio': ['gonia', 'ngaio', 'nogai'], + 'ngapi': ['aping', 'ngapi', 'pangi'], + 'ngoko': ['kongo', 'ngoko'], + 'ni': ['in', 'ni'], + 'niagara': ['agrania', 'angaria', 'niagara'], + 'nias': ['anis', 'nais', 'nasi', 'nias', 'sain', 'sina'], + 'niata': ['anita', 'niata', 'tania'], + 'nib': ['bin', 'nib'], + 'nibs': ['nibs', 'snib'], + 'nibsome': ['nibsome', 'nimbose'], + 'nicarao': ['aaronic', 'nicarao', 'ocarina'], + 'niccolous': ['niccolous', 'occlusion'], + 'nice': ['cine', 'nice'], + 'nicene': ['cinene', 'nicene'], + 'nicenist': ['inscient', 'nicenist'], + 'nicesome': ['nicesome', 'semicone'], + 'nichael': ['chilean', 'echinal', 'nichael'], + 'niche': ['chien', 'chine', 'niche'], + 'nicher': ['enrich', 'nicher', 'richen'], + 'nicholas': ['lichanos', 'nicholas'], + 'nickel': ['nickel', 'nickle'], + 'nickle': ['nickel', 'nickle'], + 'nicol': ['colin', 'nicol'], + 'nicolas': ['nicolas', 'scaloni'], + 'nicolette': ['lecontite', 'nicolette'], + 'nicotian': ['aconitin', 'inaction', 'nicotian'], + 'nicotianin': ['nicotianin', 'nicotinian'], + 'nicotined': ['incondite', 'nicotined'], + 'nicotinian': ['nicotianin', 'nicotinian'], + 'nicotism': ['monistic', 'nicotism', 'nomistic'], + 'nicotize': ['nicotize', 'tonicize'], + 'nictate': ['nictate', 'tetanic'], + 'nictation': ['antitonic', 'nictation'], + 'nid': ['din', 'ind', 'nid'], + 'nidal': ['danli', 'ladin', 'linda', 'nidal'], + 'nidana': ['andian', 'danian', 'nidana'], + 'nidation': ['nidation', 'notidani'], + 'niddle': ['dindle', 'niddle'], + 'nide': ['dine', 'enid', 'inde', 'nide'], + 'nidge': ['deign', 'dinge', 'nidge'], + 'nidget': ['nidget', 'tinged'], + 'niding': ['dining', 'indign', 'niding'], + 'nidologist': ['indologist', 'nidologist'], + 'nidology': ['indology', 'nidology'], + 'nidularia': ['nidularia', 'uniradial'], + 'nidulate': ['nidulate', 'untailed'], + 'nidus': ['dinus', 'indus', 'nidus'], + 'niello': ['lionel', 'niello'], + 'niels': ['elsin', 'lenis', 'niels', 'silen', 'sline'], + 'nieve': ['nieve', 'venie'], + 'nieveta': ['naivete', 'nieveta'], + 'nievling': ['levining', 'nievling'], + 'nife': ['enif', 'fine', 'neif', 'nife'], + 'nifle': ['elfin', 'nifle'], + 'nig': ['gin', 'ing', 'nig'], + 'nigel': ['ingle', 'ligne', 'linge', 'nigel'], + 'nigella': ['gallein', 'galline', 'nigella'], + 'nigerian': ['arginine', 'nigerian'], + 'niggard': ['grading', 'niggard'], + 'nigger': ['ginger', 'nigger'], + 'niggery': ['gingery', 'niggery'], + 'nigh': ['hing', 'nigh'], + 'night': ['night', 'thing'], + 'nightless': ['lightness', 'nightless', 'thingless'], + 'nightlike': ['nightlike', 'thinglike'], + 'nightly': ['nightly', 'thingly'], + 'nightman': ['nightman', 'thingman'], + 'nignye': ['ginney', 'nignye'], + 'nigori': ['nigori', 'origin'], + 'nigre': ['grein', 'inger', 'nigre', 'regin', 'reign', 'ringe'], + 'nigrous': ['nigrous', 'rousing', 'souring'], + 'nihal': ['linha', 'nihal'], + 'nikau': ['kunai', 'nikau'], + 'nil': ['lin', 'nil'], + 'nile': ['lien', 'line', 'neil', 'nile'], + 'nilgai': ['ailing', 'angili', 'nilgai'], + 'nilometer': ['linometer', 'nilometer'], + 'niloscope': ['niloscope', 'scopoline'], + 'nilotic': ['clition', 'nilotic'], + 'nilous': ['insoul', 'linous', 'nilous', 'unsoil'], + 'nim': ['min', 'nim'], + 'nimbed': ['embind', 'nimbed'], + 'nimbose': ['nibsome', 'nimbose'], + 'nimkish': ['minkish', 'nimkish'], + 'nimshi': ['minish', 'nimshi'], + 'nina': ['nain', 'nina'], + 'ninescore': ['ninescore', 'recension'], + 'nineted': ['dentine', 'nineted'], + 'ninevite': ['ninevite', 'nivenite'], + 'ningpo': ['ningpo', 'pignon'], + 'nintu': ['nintu', 'ninut', 'untin'], + 'ninut': ['nintu', 'ninut', 'untin'], + 'niota': ['niota', 'taino'], + 'nip': ['nip', 'pin'], + 'nipa': ['nipa', 'pain', 'pani', 'pian', 'pina'], + 'nippers': ['nippers', 'snipper'], + 'nipple': ['lippen', 'nipple'], + 'nipter': ['nipter', 'terpin'], + 'nisaean': ['nisaean', 'sinaean'], + 'nisqualli': ['nisqualli', 'squillian'], + 'nisus': ['nisus', 'sinus'], + 'nit': ['nit', 'tin'], + 'nitch': ['chint', 'nitch'], + 'nitella': ['nitella', 'tellina'], + 'nitently': ['intently', 'nitently'], + 'niter': ['inert', 'inter', 'niter', 'retin', 'trine'], + 'nitered': ['nitered', 'redient', 'teinder'], + 'nither': ['hinter', 'nither', 'theirn'], + 'nito': ['into', 'nito', 'oint', 'tino'], + 'niton': ['niton', 'noint'], + 'nitrate': ['intreat', 'iterant', 'nitrate', 'tertian'], + 'nitratine': ['itinerant', 'nitratine'], + 'nitric': ['citrin', 'nitric'], + 'nitride': ['inditer', 'nitride'], + 'nitrifaction': ['antifriction', 'nitrifaction'], + 'nitriot': ['introit', 'nitriot'], + 'nitrobenzol': ['benzonitrol', 'nitrobenzol'], + 'nitrogelatin': ['intolerating', 'nitrogelatin'], + 'nitrosate': ['nitrosate', 'stationer'], + 'nitrous': ['nitrous', 'trusion'], + 'nitter': ['nitter', 'tinter'], + 'nitty': ['nitty', 'tinty'], + 'niue': ['niue', 'unie'], + 'nival': ['alvin', 'anvil', 'nival', 'vinal'], + 'nivenite': ['ninevite', 'nivenite'], + 'niveous': ['envious', 'niveous', 'veinous'], + 'nivosity': ['nivosity', 'vinosity'], + 'nizam': ['nazim', 'nizam'], + 'no': ['no', 'on'], + 'noa': ['noa', 'ona'], + 'noachite': ['inchoate', 'noachite'], + 'noah': ['hano', 'noah'], + 'noahic': ['chinoa', 'noahic'], + 'noam': ['mano', 'moan', 'mona', 'noam', 'noma', 'oman'], + 'nob': ['bon', 'nob'], + 'nobleman': ['blennoma', 'nobleman'], + 'noblesse': ['boneless', 'noblesse'], + 'nobs': ['bosn', 'nobs', 'snob'], + 'nocardia': ['nocardia', 'orcadian'], + 'nocent': ['nocent', 'nocten'], + 'nocerite': ['erection', 'neoteric', 'nocerite', 'renotice'], + 'nock': ['conk', 'nock'], + 'nocten': ['nocent', 'nocten'], + 'noctiluca': ['ciclatoun', 'noctiluca'], + 'noctuid': ['conduit', 'duction', 'noctuid'], + 'noctuidae': ['coadunite', 'education', 'noctuidae'], + 'nocturia': ['curation', 'nocturia'], + 'nod': ['don', 'nod'], + 'nodal': ['donal', 'nodal'], + 'nodated': ['donated', 'nodated'], + 'node': ['done', 'node'], + 'nodi': ['dion', 'nodi', 'odin'], + 'nodiak': ['daikon', 'nodiak'], + 'nodical': ['dolcian', 'nodical'], + 'nodicorn': ['corindon', 'nodicorn'], + 'nodule': ['louden', 'nodule'], + 'nodus': ['nodus', 'ounds', 'sound'], + 'noegenesis': ['neogenesis', 'noegenesis'], + 'noegenetic': ['neogenetic', 'noegenetic'], + 'noel': ['elon', 'enol', 'leno', 'leon', 'lone', 'noel'], + 'noetic': ['eciton', 'noetic', 'notice', 'octine'], + 'noetics': ['contise', 'noetics', 'section'], + 'nog': ['gon', 'nog'], + 'nogai': ['gonia', 'ngaio', 'nogai'], + 'nogal': ['along', 'gonal', 'lango', 'longa', 'nogal'], + 'noil': ['lino', 'lion', 'loin', 'noil'], + 'noilage': ['goniale', 'noilage'], + 'noiler': ['elinor', 'lienor', 'lorien', 'noiler'], + 'noint': ['niton', 'noint'], + 'noir': ['inro', 'iron', 'noir', 'nori'], + 'noise': ['eosin', 'noise'], + 'noiseless': ['noiseless', 'selenosis'], + 'noisette': ['noisette', 'teosinte'], + 'nolo': ['loon', 'nolo'], + 'noma': ['mano', 'moan', 'mona', 'noam', 'noma', 'oman'], + 'nomad': ['damon', 'monad', 'nomad'], + 'nomadian': ['monadina', 'nomadian'], + 'nomadic': ['monadic', 'nomadic'], + 'nomadical': ['monadical', 'nomadical'], + 'nomadically': ['monadically', 'nomadically'], + 'nomadism': ['monadism', 'nomadism'], + 'nomarch': ['monarch', 'nomarch', 'onmarch'], + 'nomarchy': ['monarchy', 'nomarchy'], + 'nome': ['mone', 'nome', 'omen'], + 'nomeus': ['nomeus', 'unsome'], + 'nomial': ['monial', 'nomial', 'oilman'], + 'nomina': ['amnion', 'minoan', 'nomina'], + 'nominate': ['antinome', 'nominate'], + 'nominated': ['dentinoma', 'nominated'], + 'nominature': ['nominature', 'numeration'], + 'nomism': ['monism', 'nomism', 'simmon'], + 'nomismata': ['anatomism', 'nomismata'], + 'nomistic': ['monistic', 'nicotism', 'nomistic'], + 'nomocracy': ['monocracy', 'nomocracy'], + 'nomogenist': ['monogenist', 'nomogenist'], + 'nomogenous': ['monogenous', 'nomogenous'], + 'nomogeny': ['monogeny', 'nomogeny'], + 'nomogram': ['monogram', 'nomogram'], + 'nomograph': ['monograph', 'nomograph', 'phonogram'], + 'nomographer': ['geranomorph', 'monographer', 'nomographer'], + 'nomographic': ['gramophonic', 'monographic', 'nomographic', 'phonogramic'], + 'nomographical': ['gramophonical', 'monographical', 'nomographical'], + 'nomographically': ['gramophonically', + 'monographically', + 'nomographically', + 'phonogramically'], + 'nomography': ['monography', 'nomography'], + 'nomological': ['monological', 'nomological'], + 'nomologist': ['monologist', 'nomologist', 'ontologism'], + 'nomology': ['monology', 'nomology'], + 'nomophyllous': ['monophyllous', 'nomophyllous'], + 'nomotheism': ['monotheism', 'nomotheism'], + 'nomothetic': ['monothetic', 'nomothetic'], + 'nona': ['anon', 'nona', 'onan'], + 'nonaccession': ['connoissance', 'nonaccession'], + 'nonact': ['cannot', 'canton', 'conant', 'nonact'], + 'nonaction': ['connation', 'nonaction'], + 'nonagent': ['nonagent', 'tannogen'], + 'nonaid': ['adonin', 'nanoid', 'nonaid'], + 'nonaltruistic': ['instructional', 'nonaltruistic'], + 'nonanimal': ['nonanimal', 'nonmanila'], + 'nonbilabiate': ['inobtainable', 'nonbilabiate'], + 'noncaste': ['noncaste', 'tsonecan'], + 'noncereal': ['aleconner', 'noncereal'], + 'noncertified': ['noncertified', 'nonrectified'], + 'nonclaim': ['cinnamol', 'nonclaim'], + 'noncreation': ['noncreation', 'nonreaction'], + 'noncreative': ['noncreative', 'nonreactive'], + 'noncurantist': ['noncurantist', 'unconstraint'], + 'nonda': ['donna', 'nonda'], + 'nondesecration': ['nondesecration', 'recondensation'], + 'none': ['neon', 'none'], + 'nonempirical': ['nonempirical', 'prenominical'], + 'nonerudite': ['nonerudite', 'unoriented'], + 'nonesuch': ['nonesuch', 'unchosen'], + 'nonet': ['nonet', 'tenon'], + 'nonfertile': ['florentine', 'nonfertile'], + 'nongeometrical': ['inconglomerate', 'nongeometrical'], + 'nonglare': ['algernon', 'nonglare'], + 'nongod': ['dongon', 'nongod'], + 'nonhepatic': ['nonhepatic', 'pantheonic'], + 'nonic': ['conin', 'nonic', 'oncin'], + 'nonideal': ['anneloid', 'nonideal'], + 'nonidealist': ['alstonidine', 'nonidealist'], + 'nonirate': ['anointer', 'inornate', 'nonirate', 'reanoint'], + 'nonius': ['nonius', 'unison'], + 'nonlegato': ['nonlegato', 'ontogenal'], + 'nonlegume': ['melungeon', 'nonlegume'], + 'nonliable': ['bellonian', 'nonliable'], + 'nonlicet': ['contline', 'nonlicet'], + 'nonly': ['nonly', 'nonyl', 'nylon'], + 'nonmanila': ['nonanimal', 'nonmanila'], + 'nonmarital': ['antinormal', 'nonmarital', 'nonmartial'], + 'nonmartial': ['antinormal', 'nonmarital', 'nonmartial'], + 'nonmatter': ['nonmatter', 'remontant'], + 'nonmetric': ['comintern', 'nonmetric'], + 'nonmetrical': ['centinormal', 'conterminal', 'nonmetrical'], + 'nonmolar': ['nonmolar', 'nonmoral'], + 'nonmoral': ['nonmolar', 'nonmoral'], + 'nonnat': ['nonnat', 'nontan'], + 'nonoriental': ['nonoriental', 'nonrelation'], + 'nonpaid': ['dipnoan', 'nonpaid', 'pandion'], + 'nonpar': ['napron', 'nonpar'], + 'nonparental': ['nonparental', 'nonpaternal'], + 'nonpaternal': ['nonparental', 'nonpaternal'], + 'nonpearlitic': ['nonpearlitic', 'pratincoline'], + 'nonpenal': ['nonpenal', 'nonplane'], + 'nonplane': ['nonpenal', 'nonplane'], + 'nonracial': ['carniolan', 'nonracial'], + 'nonrated': ['nonrated', 'nontrade'], + 'nonreaction': ['noncreation', 'nonreaction'], + 'nonreactive': ['noncreative', 'nonreactive'], + 'nonrebel': ['ennobler', 'nonrebel'], + 'nonrecital': ['interconal', 'nonrecital'], + 'nonrectified': ['noncertified', 'nonrectified'], + 'nonrelation': ['nonoriental', 'nonrelation'], + 'nonreserve': ['nonreserve', 'nonreverse'], + 'nonreverse': ['nonreserve', 'nonreverse'], + 'nonrigid': ['girondin', 'nonrigid'], + 'nonsanction': ['inconsonant', 'nonsanction'], + 'nonscientist': ['inconsistent', 'nonscientist'], + 'nonsecret': ['consenter', 'nonsecret', 'reconsent'], + 'nontan': ['nonnat', 'nontan'], + 'nontrade': ['nonrated', 'nontrade'], + 'nonunited': ['nonunited', 'unintoned'], + 'nonuse': ['nonuse', 'unnose'], + 'nonvaginal': ['nonvaginal', 'novanglian'], + 'nonvisitation': ['innovationist', 'nonvisitation'], + 'nonya': ['annoy', 'nonya'], + 'nonyl': ['nonly', 'nonyl', 'nylon'], + 'nooking': ['kongoni', 'nooking'], + 'noontide': ['noontide', 'notioned'], + 'noontime': ['entomion', 'noontime'], + 'noop': ['noop', 'poon'], + 'noose': ['noose', 'osone'], + 'nooser': ['nooser', 'seroon', 'sooner'], + 'nopal': ['lapon', 'nopal'], + 'nope': ['nope', 'open', 'peon', 'pone'], + 'nor': ['nor', 'ron'], + 'nora': ['nora', 'orna', 'roan'], + 'norah': ['nahor', 'norah', 'rohan'], + 'norate': ['atoner', 'norate', 'ornate'], + 'noration': ['noration', 'ornation', 'orotinan'], + 'nordic': ['dornic', 'nordic'], + 'nordicity': ['nordicity', 'tyrocidin'], + 'noreast': ['noreast', 'rosetan', 'seatron', 'senator', 'treason'], + 'nori': ['inro', 'iron', 'noir', 'nori'], + 'noria': ['arion', 'noria'], + 'noric': ['corin', 'noric', 'orcin'], + 'norie': ['irone', 'norie'], + 'norite': ['norite', 'orient'], + 'norm': ['morn', 'norm'], + 'norma': ['manor', 'moran', 'norma', 'ramon', 'roman'], + 'normality': ['normality', 'trionymal'], + 'normated': ['moderant', 'normated'], + 'normless': ['mornless', 'normless'], + 'normocyte': ['necrotomy', 'normocyte', 'oncometry'], + 'norse': ['norse', 'noser', 'seron', 'snore'], + 'norsk': ['norsk', 'snork'], + 'north': ['north', 'thorn'], + 'norther': ['horrent', 'norther'], + 'northing': ['inthrong', 'northing'], + 'nosairi': ['nosairi', 'osirian'], + 'nose': ['enos', 'nose'], + 'nosean': ['nosean', 'oannes'], + 'noseless': ['noseless', 'soleness'], + 'noselite': ['noselite', 'solenite'], + 'nosema': ['monase', 'nosema'], + 'noser': ['norse', 'noser', 'seron', 'snore'], + 'nosesmart': ['nosesmart', 'storesman'], + 'nosism': ['nosism', 'simson'], + 'nosomania': ['nanosomia', 'nosomania'], + 'nostalgia': ['analogist', 'nostalgia'], + 'nostalgic': ['gnostical', 'nostalgic'], + 'nostic': ['nostic', 'sintoc', 'tocsin'], + 'nostoc': ['nostoc', 'oncost'], + 'nosu': ['nosu', 'nous', 'onus'], + 'not': ['not', 'ton'], + 'notability': ['bitonality', 'notability'], + 'notaeal': ['anatole', 'notaeal'], + 'notaeum': ['notaeum', 'outname'], + 'notal': ['notal', 'ontal', 'talon', 'tolan', 'tonal'], + 'notalgia': ['galtonia', 'notalgia'], + 'notalia': ['ailanto', 'alation', 'laotian', 'notalia'], + 'notan': ['anton', 'notan', 'tonna'], + 'notarial': ['notarial', 'rational', 'rotalian'], + 'notarially': ['notarially', 'rationally'], + 'notariate': ['notariate', 'rationate'], + 'notation': ['notation', 'tonation'], + 'notator': ['arnotto', 'notator'], + 'notcher': ['chorten', 'notcher'], + 'note': ['note', 'tone'], + 'noted': ['donet', 'noted', 'toned'], + 'notehead': ['headnote', 'notehead'], + 'noteless': ['noteless', 'toneless'], + 'notelessly': ['notelessly', 'tonelessly'], + 'notelessness': ['notelessness', 'tonelessness'], + 'noter': ['noter', 'tenor', 'toner', 'trone'], + 'nother': ['hornet', 'nother', 'theron', 'throne'], + 'nothous': ['hontous', 'nothous'], + 'notice': ['eciton', 'noetic', 'notice', 'octine'], + 'noticer': ['cerotin', 'cointer', 'cotrine', 'cretion', 'noticer', 'rection'], + 'notidani': ['nidation', 'notidani'], + 'notify': ['notify', 'tonify'], + 'notioned': ['noontide', 'notioned'], + 'notochordal': ['chordotonal', 'notochordal'], + 'notopterus': ['notopterus', 'portentous'], + 'notorhizal': ['horizontal', 'notorhizal'], + 'nototrema': ['antrotome', 'nototrema'], + 'notour': ['notour', 'unroot'], + 'notropis': ['notropis', 'positron', 'sorption'], + 'notum': ['montu', 'mount', 'notum'], + 'notus': ['notus', 'snout', 'stoun', 'tonus'], + 'nought': ['hognut', 'nought'], + 'noup': ['noup', 'puno', 'upon'], + 'nourisher': ['nourisher', 'renourish'], + 'nous': ['nosu', 'nous', 'onus'], + 'novalia': ['novalia', 'valonia'], + 'novanglian': ['nonvaginal', 'novanglian'], + 'novem': ['novem', 'venom'], + 'novitiate': ['evitation', 'novitiate'], + 'now': ['now', 'own', 'won'], + 'nowanights': ['nowanights', 'washington'], + 'nowed': ['endow', 'nowed'], + 'nowhere': ['nowhere', 'whereon'], + 'nowise': ['nowise', 'snowie'], + 'nowness': ['nowness', 'ownness'], + 'nowt': ['nowt', 'town', 'wont'], + 'noxa': ['axon', 'noxa', 'oxan'], + 'noy': ['noy', 'yon'], + 'nozi': ['nozi', 'zion'], + 'nu': ['nu', 'un'], + 'nub': ['bun', 'nub'], + 'nuba': ['baun', 'buna', 'nabu', 'nuba'], + 'nubian': ['nubian', 'unbain'], + 'nubilate': ['antiblue', 'nubilate'], + 'nubile': ['nubile', 'unible'], + 'nucal': ['lucan', 'nucal'], + 'nucellar': ['lucernal', 'nucellar', 'uncellar'], + 'nuchal': ['chulan', 'launch', 'nuchal'], + 'nuciferous': ['nuciferous', 'unciferous'], + 'nuciform': ['nuciform', 'unciform'], + 'nuclear': ['crenula', 'lucarne', 'nuclear', 'unclear'], + 'nucleator': ['nucleator', 'recountal'], + 'nucleoid': ['nucleoid', 'uncoiled'], + 'nuclide': ['include', 'nuclide'], + 'nuculid': ['nuculid', 'unlucid'], + 'nuculidae': ['duculinae', 'nuculidae'], + 'nudate': ['nudate', 'undate'], + 'nuddle': ['ludden', 'nuddle'], + 'nude': ['dune', 'nude', 'unde'], + 'nudeness': ['nudeness', 'unsensed'], + 'nudger': ['dunger', 'gerund', 'greund', 'nudger'], + 'nudist': ['dustin', 'nudist'], + 'nudity': ['nudity', 'untidy'], + 'nuisancer': ['insurance', 'nuisancer'], + 'numa': ['maun', 'numa'], + 'numberer': ['numberer', 'renumber'], + 'numda': ['maund', 'munda', 'numda', 'undam', 'unmad'], + 'numeration': ['nominature', 'numeration'], + 'numerical': ['ceruminal', 'melanuric', 'numerical'], + 'numerist': ['numerist', 'terminus'], + 'numida': ['numida', 'unmaid'], + 'numidae': ['numidae', 'unaimed'], + 'nummi': ['mnium', 'nummi'], + 'nunciate': ['nunciate', 'uncinate'], + 'nuncio': ['nuncio', 'uncoin'], + 'nuncioship': ['nuncioship', 'pincushion'], + 'nunki': ['nunki', 'unkin'], + 'nunlet': ['nunlet', 'tunnel', 'unlent'], + 'nunlike': ['nunlike', 'unliken'], + 'nunnated': ['nunnated', 'untanned'], + 'nunni': ['nunni', 'uninn'], + 'nuptial': ['nuptial', 'unplait'], + 'nurse': ['nurse', 'resun'], + 'nusfiah': ['faunish', 'nusfiah'], + 'nut': ['nut', 'tun'], + 'nutarian': ['nutarian', 'turanian'], + 'nutate': ['attune', 'nutate', 'tauten'], + 'nutgall': ['gallnut', 'nutgall'], + 'nuthatch': ['nuthatch', 'unthatch'], + 'nutlike': ['nutlike', 'tunlike'], + 'nutmeg': ['gnetum', 'nutmeg'], + 'nutramin': ['nutramin', 'ruminant'], + 'nutrice': ['nutrice', 'teucrin'], + 'nycteridae': ['encyrtidae', 'nycteridae'], + 'nycterine': ['nycterine', 'renitency'], + 'nycteris': ['nycteris', 'stycerin'], + 'nycturia': ['nycturia', 'tunicary'], + 'nye': ['eyn', 'nye', 'yen'], + 'nylast': ['nylast', 'stanly'], + 'nylon': ['nonly', 'nonyl', 'nylon'], + 'nymphalidae': ['lymphadenia', 'nymphalidae'], + 'nyroca': ['canroy', 'crayon', 'cyrano', 'nyroca'], + 'nystagmic': ['gymnastic', 'nystagmic'], + 'oak': ['ako', 'koa', 'oak', 'oka'], + 'oaky': ['kayo', 'oaky'], + 'oam': ['mao', 'oam'], + 'oannes': ['nosean', 'oannes'], + 'oar': ['aro', 'oar', 'ora'], + 'oared': ['adore', 'oared', 'oread'], + 'oaric': ['cairo', 'oaric'], + 'oaritis': ['isotria', 'oaritis'], + 'oarium': ['mariou', 'oarium'], + 'oarless': ['lassoer', 'oarless', 'rosales'], + 'oarman': ['oarman', 'ramona'], + 'oasal': ['alosa', 'loasa', 'oasal'], + 'oasis': ['oasis', 'sosia'], + 'oast': ['oast', 'stoa', 'taos'], + 'oat': ['oat', 'tao', 'toa'], + 'oatbin': ['batino', 'oatbin', 'obtain'], + 'oaten': ['atone', 'oaten'], + 'oatlike': ['keitloa', 'oatlike'], + 'obclude': ['becloud', 'obclude'], + 'obeah': ['bahoe', 'bohea', 'obeah'], + 'obeisant': ['obeisant', 'sabotine'], + 'obelial': ['bolelia', 'lobelia', 'obelial'], + 'obeliscal': ['escobilla', 'obeliscal'], + 'obelus': ['besoul', 'blouse', 'obelus'], + 'oberon': ['borneo', 'oberon'], + 'obi': ['ibo', 'obi'], + 'obispo': ['boopis', 'obispo'], + 'obit': ['bito', 'obit'], + 'objectative': ['objectative', 'objectivate'], + 'objectivate': ['objectative', 'objectivate'], + 'oblate': ['lobate', 'oblate'], + 'oblately': ['lobately', 'oblately'], + 'oblation': ['boltonia', 'lobation', 'oblation'], + 'obligant': ['bloating', 'obligant'], + 'obliviality': ['obliviality', 'violability'], + 'obol': ['bolo', 'bool', 'lobo', 'obol'], + 'obscurant': ['obscurant', 'subcantor'], + 'obscurantic': ['obscurantic', 'subnarcotic'], + 'obscurantist': ['obscurantist', 'substraction'], + 'obscure': ['bescour', 'buceros', 'obscure'], + 'obscurer': ['crebrous', 'obscurer'], + 'obsecrate': ['bracteose', 'obsecrate'], + 'observe': ['observe', 'obverse', 'verbose'], + 'obsessor': ['berossos', 'obsessor'], + 'obstinate': ['bastionet', 'obstinate'], + 'obtain': ['batino', 'oatbin', 'obtain'], + 'obtainal': ['ablation', 'obtainal'], + 'obtainer': ['abrotine', 'baritone', 'obtainer', 'reobtain'], + 'obtrude': ['doubter', 'obtrude', 'outbred', 'redoubt'], + 'obtruncation': ['conturbation', 'obtruncation'], + 'obturate': ['obturate', 'tabouret'], + 'obverse': ['observe', 'obverse', 'verbose'], + 'obversely': ['obversely', 'verbosely'], + 'ocarina': ['aaronic', 'nicarao', 'ocarina'], + 'occasioner': ['occasioner', 'reoccasion'], + 'occipitofrontal': ['frontooccipital', 'occipitofrontal'], + 'occipitotemporal': ['occipitotemporal', 'temporooccipital'], + 'occlusion': ['niccolous', 'occlusion'], + 'occurrent': ['cocurrent', 'occurrent', 'uncorrect'], + 'ocean': ['acone', 'canoe', 'ocean'], + 'oceanet': ['acetone', 'oceanet'], + 'oceanic': ['cocaine', 'oceanic'], + 'ocellar': ['collare', 'corella', 'ocellar'], + 'ocellate': ['collatee', 'ocellate'], + 'ocellated': ['decollate', 'ocellated'], + 'ocelli': ['collie', 'ocelli'], + 'och': ['cho', 'och'], + 'ocher': ['chore', 'ocher'], + 'ocherous': ['ocherous', 'ochreous'], + 'ochidore': ['choreoid', 'ochidore'], + 'ochlesis': ['helcosis', 'ochlesis'], + 'ochlesitic': ['cochleitis', 'ochlesitic'], + 'ochletic': ['helcotic', 'lochetic', 'ochletic'], + 'ochlocrat': ['colcothar', 'ochlocrat'], + 'ochrea': ['chorea', 'ochrea', 'rochea'], + 'ochreous': ['ocherous', 'ochreous'], + 'ochroid': ['choroid', 'ochroid'], + 'ochroma': ['amchoor', 'ochroma'], + 'ocht': ['coth', 'ocht'], + 'ocque': ['coque', 'ocque'], + 'ocreated': ['decorate', 'ocreated'], + 'octadic': ['cactoid', 'octadic'], + 'octaeteric': ['ecorticate', 'octaeteric'], + 'octakishexahedron': ['hexakisoctahedron', 'octakishexahedron'], + 'octan': ['acton', 'canto', 'octan'], + 'octandrian': ['dracontian', 'octandrian'], + 'octarius': ['cotarius', 'octarius', 'suctoria'], + 'octastrophic': ['octastrophic', 'postthoracic'], + 'octave': ['avocet', 'octave', 'vocate'], + 'octavian': ['octavian', 'octavina', 'vacation'], + 'octavina': ['octavian', 'octavina', 'vacation'], + 'octenary': ['enactory', 'octenary'], + 'octet': ['cotte', 'octet'], + 'octillion': ['cotillion', 'octillion'], + 'octine': ['eciton', 'noetic', 'notice', 'octine'], + 'octometer': ['octometer', 'rectotome', 'tocometer'], + 'octonal': ['coolant', 'octonal'], + 'octonare': ['coronate', 'octonare', 'otocrane'], + 'octonarius': ['acutorsion', 'octonarius'], + 'octoroon': ['coonroot', 'octoroon'], + 'octuple': ['couplet', 'octuple'], + 'ocularist': ['ocularist', 'suctorial'], + 'oculate': ['caulote', 'colutea', 'oculate'], + 'oculinid': ['lucinoid', 'oculinid'], + 'ocypete': ['ecotype', 'ocypete'], + 'od': ['do', 'od'], + 'oda': ['ado', 'dao', 'oda'], + 'odal': ['alod', 'dola', 'load', 'odal'], + 'odalman': ['mandola', 'odalman'], + 'odax': ['doxa', 'odax'], + 'odd': ['dod', 'odd'], + 'oddman': ['dodman', 'oddman'], + 'ode': ['doe', 'edo', 'ode'], + 'odel': ['dole', 'elod', 'lode', 'odel'], + 'odin': ['dion', 'nodi', 'odin'], + 'odinism': ['diosmin', 'odinism'], + 'odinite': ['edition', 'odinite', 'otidine', 'tineoid'], + 'odiometer': ['meteoroid', 'odiometer'], + 'odious': ['iodous', 'odious'], + 'odor': ['door', 'odor', 'oord', 'rood'], + 'odorant': ['donator', 'odorant', 'tornado'], + 'odored': ['doored', 'odored'], + 'odorless': ['doorless', 'odorless'], + 'ods': ['dos', 'ods', 'sod'], + 'odum': ['doum', 'moud', 'odum'], + 'odyl': ['loyd', 'odyl'], + 'odylist': ['odylist', 'styloid'], + 'oecanthus': ['ceanothus', 'oecanthus'], + 'oecist': ['cotise', 'oecist'], + 'oedipal': ['elapoid', 'oedipal'], + 'oenin': ['inone', 'oenin'], + 'oenocarpus': ['oenocarpus', 'uranoscope'], + 'oer': ['oer', 'ore', 'roe'], + 'oes': ['oes', 'ose', 'soe'], + 'oestrian': ['arsonite', 'asterion', 'oestrian', 'rosinate', 'serotina'], + 'oestrid': ['oestrid', 'steroid', 'storied'], + 'oestridae': ['oestridae', 'ostreidae', 'sorediate'], + 'oestrin': ['oestrin', 'tersion'], + 'oestriol': ['oestriol', 'rosolite'], + 'oestroid': ['oestroid', 'ordosite', 'ostreoid'], + 'oestrual': ['oestrual', 'rosulate'], + 'oestrum': ['oestrum', 'rosetum'], + 'oestrus': ['estrous', 'oestrus', 'sestuor', 'tussore'], + 'of': ['fo', 'of'], + 'ofer': ['fore', 'froe', 'ofer'], + 'offcast': ['castoff', 'offcast'], + 'offcut': ['cutoff', 'offcut'], + 'offender': ['offender', 'reoffend'], + 'offerer': ['offerer', 'reoffer'], + 'offlet': ['letoff', 'offlet'], + 'offset': ['offset', 'setoff'], + 'offuscate': ['offuscate', 'suffocate'], + 'offuscation': ['offuscation', 'suffocation'], + 'offward': ['drawoff', 'offward'], + 'ofo': ['foo', 'ofo'], + 'oft': ['fot', 'oft'], + 'oftens': ['oftens', 'soften'], + 'ofter': ['fetor', 'forte', 'ofter'], + 'oftly': ['lofty', 'oftly'], + 'og': ['go', 'og'], + 'ogam': ['goma', 'ogam'], + 'ogeed': ['geode', 'ogeed'], + 'ogle': ['egol', 'goel', 'loge', 'ogle', 'oleg'], + 'ogler': ['glore', 'ogler'], + 'ogpu': ['goup', 'ogpu', 'upgo'], + 'ogre': ['goer', 'gore', 'ogre'], + 'ogreism': ['ergoism', 'ogreism'], + 'ogtiern': ['ergotin', 'genitor', 'negrito', 'ogtiern', 'trigone'], + 'oh': ['ho', 'oh'], + 'ohm': ['mho', 'ohm'], + 'ohmage': ['homage', 'ohmage'], + 'ohmmeter': ['mhometer', 'ohmmeter'], + 'oilcan': ['alnico', 'cliona', 'oilcan'], + 'oilcup': ['oilcup', 'upcoil'], + 'oildom': ['moloid', 'oildom'], + 'oiler': ['oiler', 'oriel', 'reoil'], + 'oillet': ['elliot', 'oillet'], + 'oilman': ['monial', 'nomial', 'oilman'], + 'oilstone': ['leonotis', 'oilstone'], + 'oime': ['meio', 'oime'], + 'oinomania': ['oinomania', 'oniomania'], + 'oint': ['into', 'nito', 'oint', 'tino'], + 'oireachtas': ['oireachtas', 'theocrasia'], + 'ok': ['ko', 'ok'], + 'oka': ['ako', 'koa', 'oak', 'oka'], + 'oket': ['keto', 'oket', 'toke'], + 'oki': ['koi', 'oki'], + 'okie': ['ekoi', 'okie'], + 'okra': ['karo', 'kora', 'okra', 'roka'], + 'olaf': ['foal', 'loaf', 'olaf'], + 'olam': ['loam', 'loma', 'malo', 'mola', 'olam'], + 'olamic': ['colima', 'olamic'], + 'olcha': ['chola', 'loach', 'olcha'], + 'olchi': ['choil', 'choli', 'olchi'], + 'old': ['dol', 'lod', 'old'], + 'older': ['lored', 'older'], + 'oldhamite': ['ethmoidal', 'oldhamite'], + 'ole': ['leo', 'ole'], + 'olea': ['aloe', 'olea'], + 'olecranal': ['lanceolar', 'olecranal'], + 'olecranoid': ['lecanoroid', 'olecranoid'], + 'olecranon': ['encoronal', 'olecranon'], + 'olefin': ['enfoil', 'olefin'], + 'oleg': ['egol', 'goel', 'loge', 'ogle', 'oleg'], + 'olein': ['enoil', 'ileon', 'olein'], + 'olena': ['alone', 'anole', 'olena'], + 'olenid': ['doline', 'indole', 'leonid', 'loined', 'olenid'], + 'olent': ['lento', 'olent'], + 'olenus': ['ensoul', 'olenus', 'unsole'], + 'oleosity': ['oleosity', 'otiosely'], + 'olga': ['gaol', 'goal', 'gola', 'olga'], + 'oliban': ['albino', 'albion', 'alboin', 'oliban'], + 'olibanum': ['olibanum', 'umbonial'], + 'olid': ['dilo', 'diol', 'doli', 'idol', 'olid'], + 'oligoclase': ['oligoclase', 'sociolegal'], + 'oligomyoid': ['idiomology', 'oligomyoid'], + 'oligonephria': ['oligonephria', 'oligophrenia'], + 'oligonephric': ['oligonephric', 'oligophrenic'], + 'oligophrenia': ['oligonephria', 'oligophrenia'], + 'oligophrenic': ['oligonephric', 'oligophrenic'], + 'oliprance': ['oliprance', 'porcelain'], + 'oliva': ['oliva', 'viola'], + 'olivaceous': ['olivaceous', 'violaceous'], + 'olive': ['olive', 'ovile', 'voile'], + 'olived': ['livedo', 'olived'], + 'oliver': ['oliver', 'violer', 'virole'], + 'olivescent': ['olivescent', 'violescent'], + 'olivet': ['olivet', 'violet'], + 'olivetan': ['olivetan', 'velation'], + 'olivette': ['olivette', 'violette'], + 'olivine': ['olivine', 'violine'], + 'olla': ['lalo', 'lola', 'olla'], + 'olof': ['fool', 'loof', 'olof'], + 'olonets': ['enstool', 'olonets'], + 'olor': ['loro', 'olor', 'orlo', 'rool'], + 'olpe': ['lope', 'olpe', 'pole'], + 'olson': ['olson', 'solon'], + 'olympian': ['olympian', 'polymnia'], + 'om': ['mo', 'om'], + 'omaha': ['haoma', 'omaha'], + 'oman': ['mano', 'moan', 'mona', 'noam', 'noma', 'oman'], + 'omani': ['amino', 'inoma', 'naomi', 'omani', 'omina'], + 'omar': ['amor', 'maro', 'mora', 'omar', 'roam'], + 'omasitis': ['amitosis', 'omasitis'], + 'omber': ['brome', 'omber'], + 'omelet': ['omelet', 'telome'], + 'omen': ['mone', 'nome', 'omen'], + 'omened': ['endome', 'omened'], + 'omental': ['omental', 'telamon'], + 'omentotomy': ['entomotomy', 'omentotomy'], + 'omer': ['mero', 'more', 'omer', 'rome'], + 'omicron': ['moronic', 'omicron'], + 'omina': ['amino', 'inoma', 'naomi', 'omani', 'omina'], + 'ominous': ['mousoni', 'ominous'], + 'omit': ['itmo', 'moit', 'omit', 'timo'], + 'omitis': ['itoism', 'omitis'], + 'omniana': ['nanaimo', 'omniana'], + 'omniarch': ['choirman', 'harmonic', 'omniarch'], + 'omnigerent': ['ignorement', 'omnigerent'], + 'omnilegent': ['eloignment', 'omnilegent'], + 'omnimeter': ['minometer', 'omnimeter'], + 'omnimodous': ['monosodium', 'omnimodous', 'onosmodium'], + 'omnist': ['inmost', 'monist', 'omnist'], + 'omnitenent': ['intonement', 'omnitenent'], + 'omphalogenous': ['megalophonous', 'omphalogenous'], + 'on': ['no', 'on'], + 'ona': ['noa', 'ona'], + 'onager': ['onager', 'orange'], + 'onagra': ['agroan', 'angora', 'anogra', 'arango', 'argoan', 'onagra'], + 'onan': ['anon', 'nona', 'onan'], + 'onanism': ['mansion', 'onanism'], + 'onanistic': ['anconitis', 'antiscion', 'onanistic'], + 'onca': ['coan', 'onca'], + 'once': ['cone', 'once'], + 'oncetta': ['oncetta', 'tectona'], + 'onchidiidae': ['chionididae', 'onchidiidae'], + 'oncia': ['acoin', 'oncia'], + 'oncidium': ['conidium', 'mucinoid', 'oncidium'], + 'oncin': ['conin', 'nonic', 'oncin'], + 'oncometric': ['necrotomic', 'oncometric'], + 'oncometry': ['necrotomy', 'normocyte', 'oncometry'], + 'oncoming': ['gnomonic', 'oncoming'], + 'oncosimeter': ['oncosimeter', 'semicoronet'], + 'oncost': ['nostoc', 'oncost'], + 'ondagram': ['dragoman', 'garamond', 'ondagram'], + 'ondameter': ['emendator', 'ondameter'], + 'ondatra': ['adorant', 'ondatra'], + 'ondine': ['donnie', 'indone', 'ondine'], + 'ondy': ['ondy', 'yond'], + 'one': ['eon', 'neo', 'one'], + 'oneida': ['daoine', 'oneida'], + 'oneiric': ['ironice', 'oneiric'], + 'oneism': ['eonism', 'mesion', 'oneism', 'simeon'], + 'oneness': ['oneness', 'senones'], + 'oner': ['oner', 'rone'], + 'onery': ['eryon', 'onery'], + 'oniomania': ['oinomania', 'oniomania'], + 'oniomaniac': ['iconomania', 'oniomaniac'], + 'oniscidae': ['oniscidae', 'oscinidae', 'sciaenoid'], + 'onisciform': ['onisciform', 'somnorific'], + 'oniscoidea': ['iodocasein', 'oniscoidea'], + 'onkos': ['onkos', 'snook'], + 'onlepy': ['onlepy', 'openly'], + 'onliest': ['leonist', 'onliest'], + 'only': ['lyon', 'only'], + 'onmarch': ['monarch', 'nomarch', 'onmarch'], + 'onosmodium': ['monosodium', 'omnimodous', 'onosmodium'], + 'ons': ['ons', 'son'], + 'onset': ['onset', 'seton', 'steno', 'stone'], + 'onshore': ['onshore', 'sorehon'], + 'onside': ['deinos', 'donsie', 'inodes', 'onside'], + 'onsight': ['hosting', 'onsight'], + 'ontal': ['notal', 'ontal', 'talon', 'tolan', 'tonal'], + 'ontaric': ['anticor', 'carotin', 'cortina', 'ontaric'], + 'onto': ['onto', 'oont', 'toon'], + 'ontogenal': ['nonlegato', 'ontogenal'], + 'ontological': ['ontological', 'tonological'], + 'ontologism': ['monologist', 'nomologist', 'ontologism'], + 'ontology': ['ontology', 'tonology'], + 'onus': ['nosu', 'nous', 'onus'], + 'onymal': ['amylon', 'onymal'], + 'onymatic': ['cymation', 'myatonic', 'onymatic'], + 'onza': ['azon', 'onza', 'ozan'], + 'oocyte': ['coyote', 'oocyte'], + 'oodles': ['dolose', 'oodles', 'soodle'], + 'ooid': ['iodo', 'ooid'], + 'oolak': ['lokao', 'oolak'], + 'oolite': ['lootie', 'oolite'], + 'oometer': ['moreote', 'oometer'], + 'oons': ['oons', 'soon'], + 'oont': ['onto', 'oont', 'toon'], + 'oopak': ['oopak', 'pooka'], + 'oord': ['door', 'odor', 'oord', 'rood'], + 'opacate': ['opacate', 'peacoat'], + 'opacite': ['ectopia', 'opacite'], + 'opah': ['opah', 'paho', 'poha'], + 'opal': ['alop', 'opal'], + 'opalina': ['opalina', 'pianola'], + 'opalinine': ['opalinine', 'pleionian'], + 'opalize': ['epizoal', 'lopezia', 'opalize'], + 'opata': ['opata', 'patao', 'tapoa'], + 'opdalite': ['opdalite', 'petaloid'], + 'ope': ['ope', 'poe'], + 'opelet': ['eelpot', 'opelet'], + 'open': ['nope', 'open', 'peon', 'pone'], + 'opencast': ['capstone', 'opencast'], + 'opener': ['opener', 'reopen', 'repone'], + 'openly': ['onlepy', 'openly'], + 'openside': ['disponee', 'openside'], + 'operable': ['operable', 'ropeable'], + 'operae': ['aerope', 'operae'], + 'operant': ['operant', 'pronate', 'protean'], + 'operatic': ['aporetic', 'capriote', 'operatic'], + 'operatical': ['aporetical', 'operatical'], + 'operating': ['operating', 'pignorate'], + 'operatrix': ['expirator', 'operatrix'], + 'opercular': ['opercular', 'preocular'], + 'ophidion': ['ophidion', 'ophionid'], + 'ophionid': ['ophidion', 'ophionid'], + 'ophism': ['mopish', 'ophism'], + 'ophite': ['ethiop', 'ophite', 'peitho'], + 'opinant': ['opinant', 'pintano'], + 'opinator': ['opinator', 'tropaion'], + 'opiner': ['opiner', 'orpine', 'ponier'], + 'opiniaster': ['opiniaster', 'opiniastre'], + 'opiniastre': ['opiniaster', 'opiniastre'], + 'opiniatrety': ['opiniatrety', 'petitionary'], + 'opisometer': ['opisometer', 'opsiometer'], + 'opisthenar': ['opisthenar', 'spheration'], + 'opisthorchis': ['chirosophist', 'opisthorchis'], + 'oppian': ['oppian', 'papion', 'popian'], + 'opposer': ['opposer', 'propose'], + 'oppugn': ['oppugn', 'popgun'], + 'opsiometer': ['opisometer', 'opsiometer'], + 'opsonic': ['opsonic', 'pocosin'], + 'opsy': ['opsy', 'posy'], + 'opt': ['opt', 'pot', 'top'], + 'optable': ['optable', 'potable'], + 'optableness': ['optableness', 'potableness'], + 'optate': ['aptote', 'optate', 'potate', 'teapot'], + 'optation': ['optation', 'potation'], + 'optative': ['optative', 'potative'], + 'optic': ['optic', 'picot', 'topic'], + 'optical': ['capitol', 'coalpit', 'optical', 'topical'], + 'optically': ['optically', 'topically'], + 'optics': ['copist', 'coptis', 'optics', 'postic'], + 'optimal': ['optimal', 'palmito'], + 'option': ['option', 'potion'], + 'optional': ['antipolo', 'antipool', 'optional'], + 'optography': ['optography', 'topography'], + 'optological': ['optological', 'topological'], + 'optologist': ['optologist', 'topologist'], + 'optology': ['optology', 'topology'], + 'optometer': ['optometer', 'potometer'], + 'optophone': ['optophone', 'topophone'], + 'optotype': ['optotype', 'topotype'], + 'opulaster': ['opulaster', 'sportulae', 'sporulate'], + 'opulus': ['lupous', 'opulus'], + 'opuntia': ['opuntia', 'utopian'], + 'opus': ['opus', 'soup'], + 'opuscular': ['crapulous', 'opuscular'], + 'or': ['or', 'ro'], + 'ora': ['aro', 'oar', 'ora'], + 'orach': ['achor', 'chora', 'corah', 'orach', 'roach'], + 'oracle': ['carole', 'coaler', 'coelar', 'oracle', 'recoal'], + 'orad': ['dora', 'orad', 'road'], + 'oral': ['lora', 'oral'], + 'oralist': ['aristol', 'oralist', 'ortalis', 'striola'], + 'orality': ['orality', 'tailory'], + 'orang': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'orange': ['onager', 'orange'], + 'orangeist': ['goniaster', 'orangeist'], + 'oranger': ['groaner', 'oranger', 'organer'], + 'orangism': ['orangism', 'organism', 'sinogram'], + 'orangist': ['orangist', 'organist', 'roasting', 'signator'], + 'orangize': ['agonizer', 'orangize', 'organize'], + 'orant': ['orant', 'rotan', 'toran', 'trona'], + 'oraon': ['aroon', 'oraon'], + 'oratress': ['assertor', 'assorter', 'oratress', 'reassort'], + 'orb': ['bor', 'orb', 'rob'], + 'orbed': ['boder', 'orbed'], + 'orbic': ['boric', 'cribo', 'orbic'], + 'orbicle': ['bricole', 'corbeil', 'orbicle'], + 'orbicular': ['courbaril', 'orbicular'], + 'orbitale': ['betailor', 'laborite', 'orbitale'], + 'orbitelar': ['liberator', 'orbitelar'], + 'orbitelarian': ['irrationable', 'orbitelarian'], + 'orbitofrontal': ['frontoorbital', 'orbitofrontal'], + 'orbitonasal': ['nasoorbital', 'orbitonasal'], + 'orblet': ['bolter', 'orblet', 'reblot', 'rebolt'], + 'orbulina': ['orbulina', 'unilobar'], + 'orc': ['cor', 'cro', 'orc', 'roc'], + 'orca': ['acor', 'caro', 'cora', 'orca'], + 'orcadian': ['nocardia', 'orcadian'], + 'orcanet': ['enactor', 'necator', 'orcanet'], + 'orcein': ['cerion', 'coiner', 'neroic', 'orcein', 'recoin'], + 'orchat': ['cathro', 'orchat'], + 'orchel': ['chlore', 'choler', 'orchel'], + 'orchester': ['orchester', 'orchestre'], + 'orchestre': ['orchester', 'orchestre'], + 'orchic': ['choric', 'orchic'], + 'orchid': ['orchid', 'rhodic'], + 'orchidist': ['chorditis', 'orchidist'], + 'orchiocele': ['choriocele', 'orchiocele'], + 'orchitis': ['historic', 'orchitis'], + 'orcin': ['corin', 'noric', 'orcin'], + 'orcinol': ['colorin', 'orcinol'], + 'ordain': ['dorian', 'inroad', 'ordain'], + 'ordainer': ['inroader', 'ordainer', 'reordain'], + 'ordainment': ['antimodern', 'ordainment'], + 'ordanchite': ['achondrite', 'ditrochean', 'ordanchite'], + 'ordeal': ['loader', 'ordeal', 'reload'], + 'orderer': ['orderer', 'reorder'], + 'ordinable': ['bolderian', 'ordinable'], + 'ordinal': ['nailrod', 'ordinal', 'rinaldo', 'rodinal'], + 'ordinance': ['cerdonian', 'ordinance'], + 'ordinate': ['andorite', 'nadorite', 'ordinate', 'rodentia'], + 'ordinative': ['derivation', 'ordinative'], + 'ordinator': ['ordinator', 'radiotron'], + 'ordines': ['indorse', 'ordines', 'siredon', 'sordine'], + 'ordosite': ['oestroid', 'ordosite', 'ostreoid'], + 'ordu': ['dour', 'duro', 'ordu', 'roud'], + 'ore': ['oer', 'ore', 'roe'], + 'oread': ['adore', 'oared', 'oread'], + 'oreas': ['arose', 'oreas'], + 'orectic': ['cerotic', 'orectic'], + 'oreman': ['enamor', 'monera', 'oreman', 'romane'], + 'orenda': ['denaro', 'orenda'], + 'orendite': ['enteroid', 'orendite'], + 'orestean': ['orestean', 'resonate', 'stearone'], + 'orf': ['for', 'fro', 'orf'], + 'organ': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'organal': ['angolar', 'organal'], + 'organer': ['groaner', 'oranger', 'organer'], + 'organicism': ['organicism', 'organismic'], + 'organicist': ['organicist', 'organistic'], + 'organing': ['groaning', 'organing'], + 'organism': ['orangism', 'organism', 'sinogram'], + 'organismic': ['organicism', 'organismic'], + 'organist': ['orangist', 'organist', 'roasting', 'signator'], + 'organistic': ['organicist', 'organistic'], + 'organity': ['gyration', 'organity', 'ortygian'], + 'organize': ['agonizer', 'orangize', 'organize'], + 'organized': ['dragonize', 'organized'], + 'organoid': ['gordonia', 'organoid', 'rigadoon'], + 'organonymic': ['craniognomy', 'organonymic'], + 'organotin': ['gortonian', 'organotin'], + 'organule': ['lagunero', 'organule', 'uroglena'], + 'orgiasm': ['isogram', 'orgiasm'], + 'orgiast': ['agistor', 'agrotis', 'orgiast'], + 'orgic': ['corgi', 'goric', 'orgic'], + 'orgue': ['orgue', 'rogue', 'rouge'], + 'orgy': ['gory', 'gyro', 'orgy'], + 'oriel': ['oiler', 'oriel', 'reoil'], + 'orient': ['norite', 'orient'], + 'oriental': ['oriental', 'relation', 'tirolean'], + 'orientalism': ['misrelation', 'orientalism', 'relationism'], + 'orientalist': ['orientalist', 'relationist'], + 'orientate': ['anoterite', 'orientate'], + 'origanum': ['mirounga', 'moringua', 'origanum'], + 'origin': ['nigori', 'origin'], + 'orle': ['lore', 'orle', 'role'], + 'orlean': ['lenora', 'loaner', 'orlean', 'reloan'], + 'orleanist': ['lairstone', 'orleanist', 'serotinal'], + 'orleanistic': ['intersocial', 'orleanistic', 'sclerotinia'], + 'orlet': ['lerot', 'orlet', 'relot'], + 'orlo': ['loro', 'olor', 'orlo', 'rool'], + 'orna': ['nora', 'orna', 'roan'], + 'ornamenter': ['ornamenter', 'reornament'], + 'ornate': ['atoner', 'norate', 'ornate'], + 'ornately': ['neolatry', 'ornately', 'tyrolean'], + 'ornation': ['noration', 'ornation', 'orotinan'], + 'ornis': ['ornis', 'rosin'], + 'orniscopic': ['orniscopic', 'scorpionic'], + 'ornithomantic': ['ornithomantic', 'orthantimonic'], + 'ornithoptera': ['ornithoptera', 'prototherian'], + 'orogenetic': ['erotogenic', 'geocronite', 'orogenetic'], + 'orographical': ['colporrhagia', 'orographical'], + 'orography': ['gyrophora', 'orography'], + 'orotinan': ['noration', 'ornation', 'orotinan'], + 'orotund': ['orotund', 'rotundo'], + 'orphanism': ['manorship', 'orphanism'], + 'orpheon': ['orpheon', 'phorone'], + 'orpheus': ['ephorus', 'orpheus', 'upshore'], + 'orphical': ['orphical', 'rhopalic'], + 'orphism': ['orphism', 'rompish'], + 'orphize': ['orphize', 'phiroze'], + 'orpine': ['opiner', 'orpine', 'ponier'], + 'orsel': ['loser', 'orsel', 'rosel', 'soler'], + 'orselle': ['orselle', 'roselle'], + 'ort': ['ort', 'rot', 'tor'], + 'ortalid': ['dilator', 'ortalid'], + 'ortalis': ['aristol', 'oralist', 'ortalis', 'striola'], + 'ortet': ['ortet', 'otter', 'toter'], + 'orthal': ['harlot', 'orthal', 'thoral'], + 'orthantimonic': ['ornithomantic', 'orthantimonic'], + 'orthian': ['orthian', 'thorina'], + 'orthic': ['chorti', 'orthic', 'thoric', 'trochi'], + 'orthite': ['hortite', 'orthite', 'thorite'], + 'ortho': ['ortho', 'thoro'], + 'orthodromy': ['hydromotor', 'orthodromy'], + 'orthogamy': ['orthogamy', 'othygroma'], + 'orthogonial': ['orthogonial', 'orthologian'], + 'orthologian': ['orthogonial', 'orthologian'], + 'orthose': ['orthose', 'reshoot', 'shooter', 'soother'], + 'ortiga': ['agrito', 'ortiga'], + 'ortstein': ['ortstein', 'tenorist'], + 'ortygian': ['gyration', 'organity', 'ortygian'], + 'ortygine': ['genitory', 'ortygine'], + 'ory': ['ory', 'roy', 'yor'], + 'oryx': ['oryx', 'roxy'], + 'os': ['os', 'so'], + 'osamin': ['monias', 'osamin', 'osmina'], + 'osamine': ['monesia', 'osamine', 'osmanie'], + 'osc': ['cos', 'osc', 'soc'], + 'oscan': ['ascon', 'canso', 'oscan'], + 'oscar': ['arcos', 'crosa', 'oscar', 'sacro'], + 'oscella': ['callose', 'oscella'], + 'oscheal': ['oscheal', 'scholae'], + 'oscillance': ['clinoclase', 'oscillance'], + 'oscillaria': ['iliosacral', 'oscillaria'], + 'oscillation': ['colonialist', 'oscillation'], + 'oscin': ['oscin', 'scion', 'sonic'], + 'oscine': ['cosine', 'oscine'], + 'oscines': ['cession', 'oscines'], + 'oscinian': ['oscinian', 'socinian'], + 'oscinidae': ['oniscidae', 'oscinidae', 'sciaenoid'], + 'oscitant': ['actinost', 'oscitant'], + 'oscular': ['carolus', 'oscular'], + 'osculate': ['lacteous', 'osculate'], + 'osculatory': ['cotylosaur', 'osculatory'], + 'oscule': ['coleus', 'oscule'], + 'ose': ['oes', 'ose', 'soe'], + 'osela': ['alose', 'osela', 'solea'], + 'oshac': ['chaos', 'oshac'], + 'oside': ['diose', 'idose', 'oside'], + 'osier': ['osier', 'serio'], + 'osirian': ['nosairi', 'osirian'], + 'osiride': ['isidore', 'osiride'], + 'oskar': ['krosa', 'oskar'], + 'osmanie': ['monesia', 'osamine', 'osmanie'], + 'osmanli': ['malison', 'manolis', 'osmanli', 'somnial'], + 'osmatic': ['atomics', 'catoism', 'cosmati', 'osmatic', 'somatic'], + 'osmatism': ['osmatism', 'somatism'], + 'osmerus': ['osmerus', 'smouser'], + 'osmin': ['minos', 'osmin', 'simon'], + 'osmina': ['monias', 'osamin', 'osmina'], + 'osmiridium': ['iridosmium', 'osmiridium'], + 'osmogene': ['gonesome', 'osmogene'], + 'osmometer': ['merostome', 'osmometer'], + 'osmometric': ['microstome', 'osmometric'], + 'osmophore': ['osmophore', 'sophomore'], + 'osmotactic': ['osmotactic', 'scotomatic'], + 'osmunda': ['damnous', 'osmunda'], + 'osone': ['noose', 'osone'], + 'osprey': ['eryops', 'osprey'], + 'ossal': ['lasso', 'ossal'], + 'ossein': ['essoin', 'ossein'], + 'osselet': ['osselet', 'sestole', 'toeless'], + 'ossetian': ['assiento', 'ossetian'], + 'ossetine': ['essonite', 'ossetine'], + 'ossicle': ['loessic', 'ossicle'], + 'ossiculate': ['acleistous', 'ossiculate'], + 'ossicule': ['coulisse', 'leucosis', 'ossicule'], + 'ossuary': ['ossuary', 'suasory'], + 'ostara': ['aroast', 'ostara'], + 'osteal': ['lotase', 'osteal', 'solate', 'stolae', 'talose'], + 'ostearthritis': ['arthrosteitis', 'ostearthritis'], + 'ostectomy': ['cystotome', 'cytostome', 'ostectomy'], + 'ostein': ['nesiot', 'ostein'], + 'ostemia': ['miaotse', 'ostemia'], + 'ostent': ['ostent', 'teston'], + 'ostentation': ['ostentation', 'tionontates'], + 'ostentous': ['ostentous', 'sostenuto'], + 'osteometric': ['osteometric', 'stereotomic'], + 'osteometrical': ['osteometrical', 'stereotomical'], + 'osteometry': ['osteometry', 'stereotomy'], + 'ostic': ['ostic', 'sciot', 'stoic'], + 'ostmen': ['montes', 'ostmen'], + 'ostracea': ['ceratosa', 'ostracea'], + 'ostracean': ['ostracean', 'socratean'], + 'ostracine': ['atroscine', 'certosina', 'ostracine', 'tinoceras', 'tricosane'], + 'ostracism': ['ostracism', 'socratism'], + 'ostracize': ['ostracize', 'socratize'], + 'ostracon': ['ostracon', 'socotran'], + 'ostraite': ['astroite', 'ostraite', 'storiate'], + 'ostreidae': ['oestridae', 'ostreidae', 'sorediate'], + 'ostreiform': ['forritsome', 'ostreiform'], + 'ostreoid': ['oestroid', 'ordosite', 'ostreoid'], + 'ostrich': ['chorist', 'ostrich'], + 'oswald': ['dowlas', 'oswald'], + 'otalgy': ['goatly', 'otalgy'], + 'otaria': ['atorai', 'otaria'], + 'otarian': ['aration', 'otarian'], + 'otarine': ['otarine', 'torenia'], + 'other': ['other', 'thore', 'throe', 'toher'], + 'otherism': ['homerist', 'isotherm', 'otherism', 'theorism'], + 'otherist': ['otherist', 'theorist'], + 'othygroma': ['orthogamy', 'othygroma'], + 'otiant': ['otiant', 'titano'], + 'otidae': ['idotea', 'iodate', 'otidae'], + 'otidine': ['edition', 'odinite', 'otidine', 'tineoid'], + 'otiosely': ['oleosity', 'otiosely'], + 'otitis': ['itoist', 'otitis'], + 'oto': ['oto', 'too'], + 'otocephalic': ['hepatocolic', 'otocephalic'], + 'otocrane': ['coronate', 'octonare', 'otocrane'], + 'otogenic': ['geotonic', 'otogenic'], + 'otomian': ['amotion', 'otomian'], + 'otomyces': ['cytosome', 'otomyces'], + 'ottar': ['ottar', 'tarot', 'torta', 'troat'], + 'otter': ['ortet', 'otter', 'toter'], + 'otto': ['otto', 'toot', 'toto'], + 'otus': ['otus', 'oust', 'suto'], + 'otyak': ['otyak', 'tokay'], + 'ouch': ['chou', 'ouch'], + 'ouf': ['fou', 'ouf'], + 'ough': ['hugo', 'ough'], + 'ought': ['ought', 'tough'], + 'oughtness': ['oughtness', 'toughness'], + 'ounds': ['nodus', 'ounds', 'sound'], + 'our': ['our', 'uro'], + 'ours': ['ours', 'sour'], + 'oust': ['otus', 'oust', 'suto'], + 'ouster': ['ouster', 'souter', 'touser', 'trouse'], + 'out': ['out', 'tou'], + 'outarde': ['outarde', 'outdare', 'outread'], + 'outban': ['outban', 'unboat'], + 'outbar': ['outbar', 'rubato', 'tabour'], + 'outbeg': ['bouget', 'outbeg'], + 'outblow': ['blowout', 'outblow', 'outbowl'], + 'outblunder': ['outblunder', 'untroubled'], + 'outbowl': ['blowout', 'outblow', 'outbowl'], + 'outbreak': ['breakout', 'outbreak'], + 'outbred': ['doubter', 'obtrude', 'outbred', 'redoubt'], + 'outburn': ['burnout', 'outburn'], + 'outburst': ['outburst', 'subtutor'], + 'outbustle': ['outbustle', 'outsubtle'], + 'outcarol': ['outcarol', 'taurocol'], + 'outcarry': ['curatory', 'outcarry'], + 'outcase': ['acetous', 'outcase'], + 'outcharm': ['outcharm', 'outmarch'], + 'outcrier': ['courtier', 'outcrier'], + 'outcut': ['cutout', 'outcut'], + 'outdance': ['outdance', 'uncoated'], + 'outdare': ['outarde', 'outdare', 'outread'], + 'outdraw': ['drawout', 'outdraw', 'outward'], + 'outer': ['outer', 'outre', 'route'], + 'outerness': ['outerness', 'outreness'], + 'outferret': ['foreutter', 'outferret'], + 'outfit': ['fitout', 'outfit'], + 'outflare': ['fluorate', 'outflare'], + 'outfling': ['flouting', 'outfling'], + 'outfly': ['outfly', 'toyful'], + 'outgoer': ['outgoer', 'rougeot'], + 'outgrin': ['outgrin', 'outring', 'routing', 'touring'], + 'outhire': ['outhire', 'routhie'], + 'outhold': ['holdout', 'outhold'], + 'outkick': ['kickout', 'outkick'], + 'outlance': ['cleanout', 'outlance'], + 'outlay': ['layout', 'lutayo', 'outlay'], + 'outleap': ['outleap', 'outpeal'], + 'outler': ['elutor', 'louter', 'outler'], + 'outlet': ['outlet', 'tutelo'], + 'outline': ['elution', 'outline'], + 'outlinear': ['outlinear', 'uranolite'], + 'outlined': ['outlined', 'untoiled'], + 'outlook': ['lookout', 'outlook'], + 'outly': ['louty', 'outly'], + 'outman': ['amount', 'moutan', 'outman'], + 'outmarch': ['outcharm', 'outmarch'], + 'outmarry': ['mortuary', 'outmarry'], + 'outmaster': ['outmaster', 'outstream'], + 'outname': ['notaeum', 'outname'], + 'outpaint': ['outpaint', 'putation'], + 'outpass': ['outpass', 'passout'], + 'outpay': ['outpay', 'tapuyo'], + 'outpeal': ['outleap', 'outpeal'], + 'outpitch': ['outpitch', 'pitchout'], + 'outplace': ['copulate', 'outplace'], + 'outprice': ['eutropic', 'outprice'], + 'outpromise': ['outpromise', 'peritomous'], + 'outrance': ['cornuate', 'courante', 'cuneator', 'outrance'], + 'outrate': ['outrate', 'outtear', 'torteau'], + 'outre': ['outer', 'outre', 'route'], + 'outread': ['outarde', 'outdare', 'outread'], + 'outremer': ['outremer', 'urometer'], + 'outreness': ['outerness', 'outreness'], + 'outring': ['outgrin', 'outring', 'routing', 'touring'], + 'outrun': ['outrun', 'runout'], + 'outsaint': ['outsaint', 'titanous'], + 'outscream': ['castoreum', 'outscream'], + 'outsell': ['outsell', 'sellout'], + 'outset': ['outset', 'setout'], + 'outshake': ['outshake', 'shakeout'], + 'outshape': ['outshape', 'taphouse'], + 'outshine': ['outshine', 'tinhouse'], + 'outshut': ['outshut', 'shutout'], + 'outside': ['outside', 'tedious'], + 'outsideness': ['outsideness', 'tediousness'], + 'outsigh': ['goutish', 'outsigh'], + 'outsin': ['outsin', 'ustion'], + 'outslide': ['outslide', 'solitude'], + 'outsnore': ['outsnore', 'urosteon'], + 'outsoler': ['outsoler', 'torulose'], + 'outspend': ['outspend', 'unposted'], + 'outspit': ['outspit', 'utopist'], + 'outspring': ['outspring', 'sprouting'], + 'outspurn': ['outspurn', 'portunus'], + 'outstair': ['outstair', 'ratitous'], + 'outstand': ['outstand', 'standout'], + 'outstate': ['outstate', 'outtaste'], + 'outstream': ['outmaster', 'outstream'], + 'outstreet': ['outstreet', 'tetterous'], + 'outsubtle': ['outbustle', 'outsubtle'], + 'outtaste': ['outstate', 'outtaste'], + 'outtear': ['outrate', 'outtear', 'torteau'], + 'outthrough': ['outthrough', 'throughout'], + 'outthrow': ['outthrow', 'outworth', 'throwout'], + 'outtrail': ['outtrail', 'tutorial'], + 'outturn': ['outturn', 'turnout'], + 'outturned': ['outturned', 'untutored'], + 'outwalk': ['outwalk', 'walkout'], + 'outward': ['drawout', 'outdraw', 'outward'], + 'outwash': ['outwash', 'washout'], + 'outwatch': ['outwatch', 'watchout'], + 'outwith': ['outwith', 'without'], + 'outwork': ['outwork', 'workout'], + 'outworth': ['outthrow', 'outworth', 'throwout'], + 'ova': ['avo', 'ova'], + 'ovaloid': ['ovaloid', 'ovoidal'], + 'ovarial': ['ovarial', 'variola'], + 'ovariotubal': ['ovariotubal', 'tuboovarial'], + 'ovational': ['avolation', 'ovational'], + 'oven': ['nevo', 'oven'], + 'ovenly': ['lenvoy', 'ovenly'], + 'ovenpeel': ['envelope', 'ovenpeel'], + 'over': ['over', 'rove'], + 'overaction': ['overaction', 'revocation'], + 'overactive': ['overactive', 'revocative'], + 'overall': ['allover', 'overall'], + 'overblame': ['overblame', 'removable'], + 'overblow': ['overblow', 'overbowl'], + 'overboil': ['boilover', 'overboil'], + 'overbowl': ['overblow', 'overbowl'], + 'overbreak': ['breakover', 'overbreak'], + 'overburden': ['overburden', 'overburned'], + 'overburn': ['burnover', 'overburn'], + 'overburned': ['overburden', 'overburned'], + 'overcall': ['overcall', 'vocaller'], + 'overcare': ['overcare', 'overrace'], + 'overcirculate': ['overcirculate', 'uterocervical'], + 'overcoat': ['evocator', 'overcoat'], + 'overcross': ['crossover', 'overcross'], + 'overcup': ['overcup', 'upcover'], + 'overcurious': ['erucivorous', 'overcurious'], + 'overcurtain': ['countervair', 'overcurtain', 'recurvation'], + 'overcut': ['cutover', 'overcut'], + 'overdamn': ['overdamn', 'ravendom'], + 'overdare': ['overdare', 'overdear', 'overread'], + 'overdeal': ['overdeal', 'overlade', 'overlead'], + 'overdear': ['overdare', 'overdear', 'overread'], + 'overdraw': ['overdraw', 'overward'], + 'overdrawer': ['overdrawer', 'overreward'], + 'overdrip': ['overdrip', 'provider'], + 'overdure': ['devourer', 'overdure', 'overrude'], + 'overdust': ['overdust', 'overstud'], + 'overedit': ['overedit', 'overtide'], + 'overfar': ['favorer', 'overfar', 'refavor'], + 'overfile': ['forelive', 'overfile'], + 'overfilm': ['overfilm', 'veliform'], + 'overflower': ['overflower', 'reoverflow'], + 'overforce': ['forecover', 'overforce'], + 'overgaiter': ['overgaiter', 'revigorate'], + 'overglint': ['overglint', 'revolting'], + 'overgo': ['groove', 'overgo'], + 'overgrain': ['granivore', 'overgrain'], + 'overhate': ['overhate', 'overheat'], + 'overheat': ['overhate', 'overheat'], + 'overheld': ['overheld', 'verdelho'], + 'overidle': ['evildoer', 'overidle'], + 'overink': ['invoker', 'overink'], + 'overinsist': ['overinsist', 'versionist'], + 'overkeen': ['overkeen', 'overknee'], + 'overknee': ['overkeen', 'overknee'], + 'overlade': ['overdeal', 'overlade', 'overlead'], + 'overlast': ['overlast', 'oversalt'], + 'overlate': ['elevator', 'overlate'], + 'overlay': ['layover', 'overlay'], + 'overlead': ['overdeal', 'overlade', 'overlead'], + 'overlean': ['overlean', 'valerone'], + 'overleg': ['overleg', 'reglove'], + 'overlie': ['overlie', 'relievo'], + 'overling': ['lovering', 'overling'], + 'overlisten': ['overlisten', 'oversilent'], + 'overlive': ['overlive', 'overveil'], + 'overly': ['overly', 'volery'], + 'overmantel': ['overmantel', 'overmantle'], + 'overmantle': ['overmantel', 'overmantle'], + 'overmaster': ['overmaster', 'overstream'], + 'overmean': ['overmean', 'overname'], + 'overmerit': ['overmerit', 'overtimer'], + 'overname': ['overmean', 'overname'], + 'overneat': ['overneat', 'renovate'], + 'overnew': ['overnew', 'rewoven'], + 'overnigh': ['hovering', 'overnigh'], + 'overpaint': ['overpaint', 'pronative'], + 'overpass': ['overpass', 'passover'], + 'overpet': ['overpet', 'preveto', 'prevote'], + 'overpick': ['overpick', 'pickover'], + 'overplain': ['overplain', 'parvoline'], + 'overply': ['overply', 'plovery'], + 'overpointed': ['overpointed', 'predevotion'], + 'overpot': ['overpot', 'overtop'], + 'overrace': ['overcare', 'overrace'], + 'overrate': ['overrate', 'overtare'], + 'overread': ['overdare', 'overdear', 'overread'], + 'overreward': ['overdrawer', 'overreward'], + 'overrude': ['devourer', 'overdure', 'overrude'], + 'overrun': ['overrun', 'runover'], + 'oversad': ['oversad', 'savored'], + 'oversale': ['oversale', 'overseal'], + 'oversalt': ['overlast', 'oversalt'], + 'oversauciness': ['oversauciness', 'veraciousness'], + 'overseal': ['oversale', 'overseal'], + 'overseen': ['overseen', 'veronese'], + 'overset': ['overset', 'setover'], + 'oversilent': ['overlisten', 'oversilent'], + 'overslip': ['overslip', 'slipover'], + 'overspread': ['overspread', 'spreadover'], + 'overstain': ['overstain', 'servation', 'versation'], + 'overstir': ['overstir', 'servitor'], + 'overstrain': ['overstrain', 'traversion'], + 'overstream': ['overmaster', 'overstream'], + 'overstrew': ['overstrew', 'overwrest'], + 'overstud': ['overdust', 'overstud'], + 'overt': ['overt', 'rovet', 'torve', 'trove', 'voter'], + 'overtare': ['overrate', 'overtare'], + 'overthrow': ['overthrow', 'overwroth'], + 'overthwart': ['overthwart', 'thwartover'], + 'overtide': ['overedit', 'overtide'], + 'overtime': ['overtime', 'remotive'], + 'overtimer': ['overmerit', 'overtimer'], + 'overtip': ['overtip', 'pivoter'], + 'overtop': ['overpot', 'overtop'], + 'overtrade': ['overtrade', 'overtread'], + 'overtread': ['overtrade', 'overtread'], + 'overtrue': ['overtrue', 'overture', 'trouvere'], + 'overture': ['overtrue', 'overture', 'trouvere'], + 'overturn': ['overturn', 'turnover'], + 'overtwine': ['interwove', 'overtwine'], + 'overveil': ['overlive', 'overveil'], + 'overwalk': ['overwalk', 'walkover'], + 'overward': ['overdraw', 'overward'], + 'overwrest': ['overstrew', 'overwrest'], + 'overwroth': ['overthrow', 'overwroth'], + 'ovest': ['ovest', 'stove'], + 'ovidae': ['evodia', 'ovidae'], + 'ovidian': ['ovidian', 'vidonia'], + 'ovile': ['olive', 'ovile', 'voile'], + 'ovillus': ['ovillus', 'villous'], + 'oviparous': ['apivorous', 'oviparous'], + 'ovist': ['ovist', 'visto'], + 'ovistic': ['covisit', 'ovistic'], + 'ovoidal': ['ovaloid', 'ovoidal'], + 'ovular': ['louvar', 'ovular'], + 'ow': ['ow', 'wo'], + 'owd': ['dow', 'owd', 'wod'], + 'owe': ['owe', 'woe'], + 'owen': ['enow', 'owen', 'wone'], + 'owenism': ['owenism', 'winsome'], + 'ower': ['ower', 'wore'], + 'owerby': ['bowery', 'bowyer', 'owerby'], + 'owl': ['low', 'lwo', 'owl'], + 'owler': ['lower', 'owler', 'rowel'], + 'owlery': ['lowery', 'owlery', 'rowley', 'yowler'], + 'owlet': ['owlet', 'towel'], + 'owlish': ['lowish', 'owlish'], + 'owlishly': ['lowishly', 'owlishly', 'sillyhow'], + 'owlishness': ['lowishness', 'owlishness'], + 'owly': ['lowy', 'owly', 'yowl'], + 'own': ['now', 'own', 'won'], + 'owner': ['owner', 'reown', 'rowen'], + 'ownership': ['ownership', 'shipowner'], + 'ownness': ['nowness', 'ownness'], + 'owser': ['owser', 'resow', 'serow', 'sower', 'swore', 'worse'], + 'oxalan': ['axonal', 'oxalan'], + 'oxalite': ['aloxite', 'oxalite'], + 'oxan': ['axon', 'noxa', 'oxan'], + 'oxanic': ['anoxic', 'oxanic'], + 'oxazine': ['azoxine', 'oxazine'], + 'oxen': ['exon', 'oxen'], + 'oxidic': ['ixodic', 'oxidic'], + 'oximate': ['oximate', 'toxemia'], + 'oxy': ['oxy', 'yox'], + 'oxyntic': ['ictonyx', 'oxyntic'], + 'oxyphenol': ['oxyphenol', 'xylophone'], + 'oxyterpene': ['enteropexy', 'oxyterpene'], + 'oyer': ['oyer', 'roey', 'yore'], + 'oyster': ['oyster', 'rosety'], + 'oysterish': ['oysterish', 'thyreosis'], + 'oysterman': ['monastery', 'oysterman'], + 'ozan': ['azon', 'onza', 'ozan'], + 'ozena': ['neoza', 'ozena'], + 'ozonate': ['entozoa', 'ozonate'], + 'ozonic': ['ozonic', 'zoonic'], + 'ozotype': ['ozotype', 'zootype'], + 'paal': ['paal', 'pala'], + 'paar': ['apar', 'paar', 'para'], + 'pablo': ['pablo', 'polab'], + 'pac': ['cap', 'pac'], + 'pacable': ['capable', 'pacable'], + 'pacation': ['copatain', 'pacation'], + 'pacaya': ['cayapa', 'pacaya'], + 'pace': ['cape', 'cepa', 'pace'], + 'paced': ['caped', 'decap', 'paced'], + 'pacer': ['caper', 'crape', 'pacer', 'perca', 'recap'], + 'pachnolite': ['pachnolite', 'phonetical'], + 'pachometer': ['pachometer', 'phacometer'], + 'pacht': ['chapt', 'pacht', 'patch'], + 'pachylosis': ['pachylosis', 'phacolysis'], + 'pacificist': ['pacificist', 'pacifistic'], + 'pacifistic': ['pacificist', 'pacifistic'], + 'packer': ['packer', 'repack'], + 'paco': ['copa', 'paco'], + 'pacolet': ['pacolet', 'polecat'], + 'paction': ['caption', 'paction'], + 'pactional': ['pactional', 'pactolian', 'placation'], + 'pactionally': ['pactionally', 'polyactinal'], + 'pactolian': ['pactional', 'pactolian', 'placation'], + 'pad': ['dap', 'pad'], + 'padda': ['dadap', 'padda'], + 'padder': ['padder', 'parded'], + 'padfoot': ['footpad', 'padfoot'], + 'padle': ['padle', 'paled', 'pedal', 'plead'], + 'padre': ['drape', 'padre'], + 'padtree': ['padtree', 'predate', 'tapered'], + 'paean': ['apnea', 'paean'], + 'paeanism': ['paeanism', 'spanemia'], + 'paegel': ['paegel', 'paegle', 'pelage'], + 'paegle': ['paegel', 'paegle', 'pelage'], + 'paga': ['gapa', 'paga'], + 'page': ['gape', 'page', 'peag', 'pega'], + 'pagedom': ['megapod', 'pagedom'], + 'pager': ['gaper', 'grape', 'pager', 'parge'], + 'pageship': ['pageship', 'shippage'], + 'paginary': ['agrypnia', 'paginary'], + 'paguridae': ['paguridae', 'paguridea'], + 'paguridea': ['paguridae', 'paguridea'], + 'pagurine': ['pagurine', 'perugian'], + 'pah': ['hap', 'pah'], + 'pahari': ['pahari', 'pariah', 'raphia'], + 'pahi': ['hapi', 'pahi'], + 'paho': ['opah', 'paho', 'poha'], + 'paigle': ['paigle', 'pilage'], + 'paik': ['paik', 'pika'], + 'pail': ['lipa', 'pail', 'pali', 'pial'], + 'paillasse': ['paillasse', 'palliasse'], + 'pain': ['nipa', 'pain', 'pani', 'pian', 'pina'], + 'painless': ['painless', 'spinales'], + 'paint': ['inapt', 'paint', 'pinta'], + 'painted': ['depaint', 'inadept', 'painted', 'patined'], + 'painter': ['painter', 'pertain', 'pterian', 'repaint'], + 'painterly': ['interplay', 'painterly'], + 'paintiness': ['antisepsin', 'paintiness'], + 'paip': ['paip', 'pipa'], + 'pair': ['pair', 'pari', 'pria', 'ripa'], + 'paired': ['diaper', 'paired'], + 'pairer': ['pairer', 'rapier', 'repair'], + 'pairment': ['imperant', 'pairment', 'partimen', 'premiant', 'tripeman'], + 'pais': ['apis', 'pais', 'pasi', 'saip'], + 'pajonism': ['japonism', 'pajonism'], + 'pal': ['alp', 'lap', 'pal'], + 'pala': ['paal', 'pala'], + 'palaeechinoid': ['deinocephalia', 'palaeechinoid'], + 'palaemonid': ['anomaliped', 'palaemonid'], + 'palaemonoid': ['adenolipoma', 'palaemonoid'], + 'palaeornis': ['palaeornis', 'personalia'], + 'palaestrics': ['palaestrics', 'paracelsist'], + 'palaic': ['apical', 'palaic'], + 'palaite': ['palaite', 'petalia', 'pileata'], + 'palame': ['palame', 'palmae', 'pamela'], + 'palamite': ['ampliate', 'palamite'], + 'palas': ['palas', 'salpa'], + 'palate': ['aletap', 'palate', 'platea'], + 'palatial': ['palatial', 'palliata'], + 'palatic': ['capital', 'palatic'], + 'palation': ['palation', 'talapoin'], + 'palatonasal': ['nasopalatal', 'palatonasal'], + 'palau': ['palau', 'paula'], + 'palay': ['palay', 'playa'], + 'pale': ['leap', 'lepa', 'pale', 'peal', 'plea'], + 'paled': ['padle', 'paled', 'pedal', 'plead'], + 'paleness': ['paleness', 'paneless'], + 'paleolithy': ['paleolithy', 'polyhalite', 'polythelia'], + 'paler': ['lepra', 'paler', 'parel', 'parle', 'pearl', 'perla', 'relap'], + 'palermitan': ['palermitan', 'parliament'], + 'palermo': ['leproma', 'palermo', 'pleroma', 'polearm'], + 'pales': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'palestral': ['alpestral', 'palestral'], + 'palestrian': ['alpestrian', 'palestrian', 'psalterian'], + 'palet': ['leapt', + 'palet', + 'patel', + 'pelta', + 'petal', + 'plate', + 'pleat', + 'tepal'], + 'palette': ['palette', 'peltate'], + 'pali': ['lipa', 'pail', 'pali', 'pial'], + 'palification': ['palification', 'pontificalia'], + 'palinode': ['lapideon', 'palinode', 'pedalion'], + 'palinodist': ['palinodist', 'plastinoid'], + 'palisade': ['palisade', 'salpidae'], + 'palish': ['palish', 'silpha'], + 'pallasite': ['aliseptal', 'pallasite'], + 'pallette': ['pallette', 'platelet'], + 'palliasse': ['paillasse', 'palliasse'], + 'palliata': ['palatial', 'palliata'], + 'pallone': ['pallone', 'pleonal'], + 'palluites': ['palluites', 'pulsatile'], + 'palm': ['lamp', 'palm'], + 'palmad': ['lampad', 'palmad'], + 'palmae': ['palame', 'palmae', 'pamela'], + 'palmary': ['palmary', 'palmyra'], + 'palmatilobed': ['palmatilobed', 'palmilobated'], + 'palmatisect': ['metaplastic', 'palmatisect'], + 'palmer': ['lamper', 'palmer', 'relamp'], + 'palmery': ['lamprey', 'palmery'], + 'palmette': ['palmette', 'template'], + 'palmful': ['lampful', 'palmful'], + 'palmification': ['amplification', 'palmification'], + 'palmilobated': ['palmatilobed', 'palmilobated'], + 'palmipes': ['epiplasm', 'palmipes'], + 'palmist': ['lampist', 'palmist'], + 'palmister': ['palmister', 'prelatism'], + 'palmistry': ['lampistry', 'palmistry'], + 'palmite': ['implate', 'palmite'], + 'palmito': ['optimal', 'palmito'], + 'palmitone': ['emptional', 'palmitone'], + 'palmo': ['mopla', 'palmo'], + 'palmula': ['ampulla', 'palmula'], + 'palmy': ['amply', 'palmy'], + 'palmyra': ['palmary', 'palmyra'], + 'palolo': ['apollo', 'palolo'], + 'palp': ['lapp', 'palp', 'plap'], + 'palpal': ['appall', 'palpal'], + 'palpatory': ['palpatory', 'papolatry'], + 'palped': ['dapple', 'lapped', 'palped'], + 'palpi': ['palpi', 'pipal'], + 'palster': ['palster', 'persalt', 'plaster', 'psalter', 'spartle', 'stapler'], + 'palsy': ['palsy', 'splay'], + 'palt': ['palt', 'plat'], + 'palta': ['aptal', 'palta', 'talpa'], + 'palter': ['palter', 'plater'], + 'palterer': ['palterer', 'platerer'], + 'paltry': ['paltry', 'partly', 'raptly'], + 'paludian': ['paludian', 'paludina'], + 'paludic': ['paludic', 'pudical'], + 'paludina': ['paludian', 'paludina'], + 'palus': ['palus', 'pasul'], + 'palustral': ['palustral', 'plaustral'], + 'palustrine': ['lupinaster', 'palustrine'], + 'paly': ['paly', 'play', 'pyal', 'pyla'], + 'pam': ['map', 'pam'], + 'pamela': ['palame', 'palmae', 'pamela'], + 'pamir': ['impar', 'pamir', 'prima'], + 'pamiri': ['impair', 'pamiri'], + 'pamper': ['mapper', 'pamper', 'pampre'], + 'pampre': ['mapper', 'pamper', 'pampre'], + 'pan': ['nap', 'pan'], + 'panace': ['canape', 'panace'], + 'panaceist': ['antispace', 'panaceist'], + 'panade': ['napead', 'panade'], + 'panak': ['kanap', 'panak'], + 'panamist': ['mainpast', 'mantispa', 'panamist', 'stampian'], + 'panary': ['panary', 'panyar'], + 'panatela': ['panatela', 'plataean'], + 'panatrophy': ['apanthropy', 'panatrophy'], + 'pancreatoduodenectomy': ['duodenopancreatectomy', 'pancreatoduodenectomy'], + 'pandean': ['pandean', 'pannade'], + 'pandemia': ['pandemia', 'pedimana'], + 'pander': ['pander', 'repand'], + 'panderly': ['panderly', 'repandly'], + 'pandermite': ['pandermite', 'pentamerid'], + 'panderous': ['panderous', 'repandous'], + 'pandion': ['dipnoan', 'nonpaid', 'pandion'], + 'pandour': ['pandour', 'poduran'], + 'pane': ['nape', 'neap', 'nepa', 'pane', 'pean'], + 'paned': ['paned', 'penda'], + 'panel': ['alpen', 'nepal', 'panel', 'penal', 'plane'], + 'panela': ['apneal', 'panela'], + 'panelation': ['antelopian', 'neapolitan', 'panelation'], + 'paneler': ['paneler', 'repanel', 'replane'], + 'paneless': ['paleness', 'paneless'], + 'panelist': ['panelist', 'pantelis', 'penalist', 'plastein'], + 'pangamic': ['campaign', 'pangamic'], + 'pangane': ['pangane', 'pannage'], + 'pangen': ['pangen', 'penang'], + 'pangene': ['pangene', 'pennage'], + 'pangi': ['aping', 'ngapi', 'pangi'], + 'pani': ['nipa', 'pain', 'pani', 'pian', 'pina'], + 'panicle': ['calepin', 'capelin', 'panicle', 'pelican', 'pinacle'], + 'paniculitis': ['paniculitis', 'paulinistic'], + 'panisca': ['capsian', 'caspian', 'nascapi', 'panisca'], + 'panisic': ['panisic', 'piscian', 'piscina', 'sinapic'], + 'pank': ['knap', 'pank'], + 'pankin': ['napkin', 'pankin'], + 'panman': ['panman', 'pannam'], + 'panmug': ['panmug', 'pugman'], + 'pannade': ['pandean', 'pannade'], + 'pannage': ['pangane', 'pannage'], + 'pannam': ['panman', 'pannam'], + 'panne': ['panne', 'penna'], + 'pannicle': ['pannicle', 'pinnacle'], + 'pannus': ['pannus', 'sannup', 'unsnap', 'unspan'], + 'panoche': ['copehan', 'panoche', 'phocean'], + 'panoistic': ['panoistic', 'piscation'], + 'panornithic': ['panornithic', 'rhaponticin'], + 'panostitis': ['antiptosis', 'panostitis'], + 'panplegia': ['appealing', 'lagniappe', 'panplegia'], + 'pansciolist': ['costispinal', 'pansciolist'], + 'panse': ['aspen', 'panse', 'snape', 'sneap', 'spane', 'spean'], + 'panside': ['ipseand', 'panside', 'pansied'], + 'pansied': ['ipseand', 'panside', 'pansied'], + 'pansy': ['pansy', 'snapy'], + 'pantaleon': ['pantaleon', 'pantalone'], + 'pantalone': ['pantaleon', 'pantalone'], + 'pantarchy': ['pantarchy', 'pyracanth'], + 'pantelis': ['panelist', 'pantelis', 'penalist', 'plastein'], + 'pantellerite': ['interpellate', 'pantellerite'], + 'panter': ['arpent', + 'enrapt', + 'entrap', + 'panter', + 'parent', + 'pretan', + 'trepan'], + 'pantheic': ['haptenic', 'pantheic', 'pithecan'], + 'pantheonic': ['nonhepatic', 'pantheonic'], + 'pantie': ['pantie', 'patine'], + 'panties': ['panties', 'sapient', 'spinate'], + 'pantile': ['pantile', 'pentail', 'platine', 'talpine'], + 'pantle': ['pantle', 'planet', 'platen'], + 'pantler': ['pantler', 'planter', 'replant'], + 'pantofle': ['felapton', 'pantofle'], + 'pantry': ['pantry', 'trypan'], + 'panyar': ['panary', 'panyar'], + 'papabot': ['papabot', 'papboat'], + 'papal': ['lappa', 'papal'], + 'papalistic': ['papalistic', 'papistical'], + 'papboat': ['papabot', 'papboat'], + 'paper': ['paper', 'rappe'], + 'papered': ['papered', 'pradeep'], + 'paperer': ['paperer', 'perpera', 'prepare', 'repaper'], + 'papern': ['napper', 'papern'], + 'papery': ['papery', 'prepay', 'yapper'], + 'papillote': ['papillote', 'popliteal'], + 'papion': ['oppian', 'papion', 'popian'], + 'papisher': ['papisher', 'sapphire'], + 'papistical': ['papalistic', 'papistical'], + 'papless': ['papless', 'sapples'], + 'papolatry': ['palpatory', 'papolatry'], + 'papule': ['papule', 'upleap'], + 'par': ['par', 'rap'], + 'para': ['apar', 'paar', 'para'], + 'parablepsia': ['appraisable', 'parablepsia'], + 'paracelsist': ['palaestrics', 'paracelsist'], + 'parachor': ['chaparro', 'parachor'], + 'paracolitis': ['paracolitis', 'piscatorial'], + 'paradisaic': ['paradisaic', 'paradisiac'], + 'paradisaically': ['paradisaically', 'paradisiacally'], + 'paradise': ['paradise', 'sparidae'], + 'paradisiac': ['paradisaic', 'paradisiac'], + 'paradisiacally': ['paradisaically', 'paradisiacally'], + 'parado': ['parado', 'pardao'], + 'paraenetic': ['capernaite', 'paraenetic'], + 'paragrapher': ['paragrapher', 'reparagraph'], + 'parah': ['aphra', 'harpa', 'parah'], + 'parale': ['earlap', 'parale'], + 'param': ['param', 'parma', 'praam'], + 'paramine': ['amperian', 'paramine', 'pearmain'], + 'paranephric': ['paranephric', 'paraphrenic'], + 'paranephritis': ['paranephritis', 'paraphrenitis'], + 'paranosic': ['caparison', 'paranosic'], + 'paraphrenic': ['paranephric', 'paraphrenic'], + 'paraphrenitis': ['paranephritis', 'paraphrenitis'], + 'parasita': ['aspirata', 'parasita'], + 'parasite': ['aspirate', 'parasite'], + 'parasol': ['asaprol', 'parasol'], + 'parasuchian': ['parasuchian', 'unpharasaic'], + 'parasyntheton': ['parasyntheton', 'thysanopteran'], + 'parate': ['aptera', 'parate', 'patera'], + 'parathion': ['parathion', 'phanariot'], + 'parazoan': ['parazoan', 'zaparoan'], + 'parboil': ['bipolar', 'parboil'], + 'parcel': ['carpel', 'parcel', 'placer'], + 'parcellary': ['carpellary', 'parcellary'], + 'parcellate': ['carpellate', 'parcellate', 'prelacteal'], + 'parchesi': ['parchesi', 'seraphic'], + 'pard': ['pard', 'prad'], + 'pardao': ['parado', 'pardao'], + 'parded': ['padder', 'parded'], + 'pardesi': ['despair', 'pardesi'], + 'pardo': ['adrop', 'pardo'], + 'pardoner': ['pardoner', 'preadorn'], + 'pare': ['aper', 'pare', 'pear', 'rape', 'reap'], + 'parel': ['lepra', 'paler', 'parel', 'parle', 'pearl', 'perla', 'relap'], + 'paren': ['arpen', 'paren'], + 'parent': ['arpent', + 'enrapt', + 'entrap', + 'panter', + 'parent', + 'pretan', + 'trepan'], + 'parental': ['parental', 'paternal', 'prenatal'], + 'parentalia': ['parentalia', 'planetaria'], + 'parentalism': ['parentalism', 'paternalism'], + 'parentality': ['parentality', 'paternality'], + 'parentally': ['parentally', 'paternally', 'prenatally'], + 'parentelic': ['epicentral', 'parentelic'], + 'parenticide': ['parenticide', 'preindicate'], + 'parer': ['parer', 'raper'], + 'paresis': ['paresis', 'serapis'], + 'paretic': ['paretic', 'patrice', 'picrate'], + 'parge': ['gaper', 'grape', 'pager', 'parge'], + 'pari': ['pair', 'pari', 'pria', 'ripa'], + 'pariah': ['pahari', 'pariah', 'raphia'], + 'paridae': ['deipara', 'paridae'], + 'paries': ['aspire', 'paries', 'praise', 'sirpea', 'spirea'], + 'parietal': ['apterial', 'parietal'], + 'parietes': ['asperite', 'parietes'], + 'parietofrontal': ['frontoparietal', 'parietofrontal'], + 'parietosquamosal': ['parietosquamosal', 'squamosoparietal'], + 'parietotemporal': ['parietotemporal', 'temporoparietal'], + 'parietovisceral': ['parietovisceral', 'visceroparietal'], + 'parine': ['parine', 'rapine'], + 'paring': ['paring', 'raping'], + 'paris': ['paris', 'parsi', 'sarip'], + 'parish': ['parish', 'raphis', 'rhapis'], + 'parished': ['diphaser', 'parished', 'raphides', 'sephardi'], + 'parison': ['parison', 'soprani'], + 'parity': ['parity', 'piraty'], + 'parkee': ['parkee', 'peaker'], + 'parker': ['parker', 'repark'], + 'parlatory': ['parlatory', 'portrayal'], + 'parle': ['lepra', 'paler', 'parel', 'parle', 'pearl', 'perla', 'relap'], + 'parley': ['parley', 'pearly', 'player', 'replay'], + 'parliament': ['palermitan', 'parliament'], + 'parly': ['parly', 'pylar', 'pyral'], + 'parma': ['param', 'parma', 'praam'], + 'parmesan': ['parmesan', 'spearman'], + 'parnel': ['parnel', 'planer', 'replan'], + 'paroch': ['carhop', 'paroch'], + 'parochialism': ['aphorismical', 'parochialism'], + 'parochine': ['canephroi', 'parochine'], + 'parodic': ['parodic', 'picador'], + 'paroecism': ['paroecism', 'premosaic'], + 'parol': ['parol', 'polar', 'poral', 'proal'], + 'parosela': ['parosela', 'psoralea'], + 'parosteal': ['parosteal', 'pastorale'], + 'parostotic': ['parostotic', 'postaortic'], + 'parotia': ['apiator', 'atropia', 'parotia'], + 'parotic': ['apricot', 'atropic', 'parotic', 'patrico'], + 'parotid': ['dioptra', 'parotid'], + 'parotitic': ['parotitic', 'patriotic'], + 'parotitis': ['parotitis', 'topiarist'], + 'parous': ['parous', 'upsoar'], + 'parovarium': ['parovarium', 'vaporarium'], + 'parrot': ['parrot', 'raptor'], + 'parroty': ['parroty', 'portray', 'tropary'], + 'parsable': ['parsable', 'prebasal', 'sparable'], + 'parse': ['asper', 'parse', 'prase', 'spaer', 'spare', 'spear'], + 'parsec': ['casper', 'escarp', 'parsec', 'scrape', 'secpar', 'spacer'], + 'parsee': ['parsee', 'persae', 'persea', 'serape'], + 'parser': ['parser', 'rasper', 'sparer'], + 'parsi': ['paris', 'parsi', 'sarip'], + 'parsley': ['parsley', 'pyrales', 'sparely', 'splayer'], + 'parsoned': ['parsoned', 'spadrone'], + 'parsonese': ['parsonese', 'preseason'], + 'parsonic': ['parsonic', 'scoparin'], + 'part': ['part', 'prat', 'rapt', 'tarp', 'trap'], + 'partan': ['partan', 'tarpan'], + 'parted': ['depart', 'parted', 'petard'], + 'partedness': ['depressant', 'partedness'], + 'parter': ['parter', 'prater'], + 'parthian': ['parthian', 'taphrina'], + 'partial': ['partial', 'patrial'], + 'partialistic': ['iatraliptics', 'partialistic'], + 'particle': ['particle', 'plicater', 'prelatic'], + 'particulate': ['catapultier', 'particulate'], + 'partigen': ['partigen', 'tapering'], + 'partile': ['partile', 'plaiter', 'replait'], + 'partimen': ['imperant', 'pairment', 'partimen', 'premiant', 'tripeman'], + 'partinium': ['impuritan', 'partinium'], + 'partisan': ['aspirant', 'partisan', 'spartina'], + 'partite': ['partite', 'tearpit'], + 'partitioned': ['departition', 'partitioned', 'trepidation'], + 'partitioner': ['partitioner', 'repartition'], + 'partlet': ['partlet', 'platter', 'prattle'], + 'partly': ['paltry', 'partly', 'raptly'], + 'parto': ['aport', 'parto', 'porta'], + 'parture': ['parture', 'rapture'], + 'party': ['party', 'trypa'], + 'parulis': ['parulis', 'spirula', 'uprisal'], + 'parure': ['parure', 'uprear'], + 'parvoline': ['overplain', 'parvoline'], + 'pasan': ['pasan', 'sapan'], + 'pasch': ['chaps', 'pasch'], + 'pascha': ['pascha', 'scapha'], + 'paschite': ['paschite', 'pastiche', 'pistache', 'scaphite'], + 'pascual': ['capsula', 'pascual', 'scapula'], + 'pash': ['hasp', 'pash', 'psha', 'shap'], + 'pasha': ['asaph', 'pasha'], + 'pashm': ['pashm', 'phasm'], + 'pashto': ['pashto', 'pathos', 'potash'], + 'pasi': ['apis', 'pais', 'pasi', 'saip'], + 'passer': ['passer', 'repass', 'sparse'], + 'passional': ['passional', 'sponsalia'], + 'passo': ['passo', 'psoas'], + 'passout': ['outpass', 'passout'], + 'passover': ['overpass', 'passover'], + 'past': ['past', 'spat', 'stap', 'taps'], + 'paste': ['paste', 'septa', 'spate'], + 'pastel': ['pastel', 'septal', 'staple'], + 'paster': ['paster', 'repast', 'trapes'], + 'pasterer': ['pasterer', 'strepera'], + 'pasteur': ['pasteur', 'pasture', 'upstare'], + 'pastiche': ['paschite', 'pastiche', 'pistache', 'scaphite'], + 'pasticheur': ['curateship', 'pasticheur'], + 'pastil': ['alpist', 'pastil', 'spital'], + 'pastile': ['aliptes', 'pastile', 'talipes'], + 'pastime': ['impaste', 'pastime'], + 'pastimer': ['maspiter', 'pastimer', 'primates'], + 'pastophorium': ['amphitropous', 'pastophorium'], + 'pastophorus': ['apostrophus', 'pastophorus'], + 'pastor': ['asport', 'pastor', 'sproat'], + 'pastoral': ['pastoral', 'proatlas'], + 'pastorale': ['parosteal', 'pastorale'], + 'pastose': ['pastose', 'petasos'], + 'pastural': ['pastural', 'spatular'], + 'pasture': ['pasteur', 'pasture', 'upstare'], + 'pasty': ['pasty', 'patsy'], + 'pasul': ['palus', 'pasul'], + 'pat': ['apt', 'pat', 'tap'], + 'pata': ['atap', 'pata', 'tapa'], + 'patao': ['opata', 'patao', 'tapoa'], + 'patarin': ['patarin', 'tarapin'], + 'patarine': ['patarine', 'tarpeian'], + 'patas': ['patas', 'tapas'], + 'patch': ['chapt', 'pacht', 'patch'], + 'patcher': ['chapter', 'patcher', 'repatch'], + 'patchery': ['patchery', 'petchary'], + 'pate': ['pate', 'peat', 'tape', 'teap'], + 'patel': ['leapt', + 'palet', + 'patel', + 'pelta', + 'petal', + 'plate', + 'pleat', + 'tepal'], + 'paten': ['enapt', 'paten', 'penta', 'tapen'], + 'patener': ['patener', 'pearten', 'petrean', 'terpane'], + 'patent': ['patent', 'patten', 'tapnet'], + 'pater': ['apert', 'pater', 'peart', 'prate', 'taper', 'terap'], + 'patera': ['aptera', 'parate', 'patera'], + 'paternal': ['parental', 'paternal', 'prenatal'], + 'paternalism': ['parentalism', 'paternalism'], + 'paternalist': ['intraseptal', 'paternalist', 'prenatalist'], + 'paternality': ['parentality', 'paternality'], + 'paternally': ['parentally', 'paternally', 'prenatally'], + 'paternoster': ['paternoster', 'prosternate', 'transportee'], + 'patesi': ['patesi', 'pietas'], + 'pathed': ['heptad', 'pathed'], + 'pathic': ['haptic', 'pathic'], + 'pathlet': ['pathlet', 'telpath'], + 'pathogen': ['heptagon', 'pathogen'], + 'pathologicoanatomic': ['anatomicopathologic', 'pathologicoanatomic'], + 'pathologicoanatomical': ['anatomicopathological', 'pathologicoanatomical'], + 'pathologicoclinical': ['clinicopathological', 'pathologicoclinical'], + 'pathonomy': ['monopathy', 'pathonomy'], + 'pathophoric': ['haptophoric', 'pathophoric'], + 'pathophorous': ['haptophorous', 'pathophorous'], + 'pathos': ['pashto', 'pathos', 'potash'], + 'pathy': ['pathy', 'typha'], + 'patiently': ['patiently', 'platynite'], + 'patina': ['aptian', 'patina', 'taipan'], + 'patine': ['pantie', 'patine'], + 'patined': ['depaint', 'inadept', 'painted', 'patined'], + 'patio': ['patio', 'taipo', 'topia'], + 'patly': ['aptly', 'patly', 'platy', 'typal'], + 'patness': ['aptness', 'patness'], + 'pato': ['atop', 'pato'], + 'patola': ['patola', 'tapalo'], + 'patrial': ['partial', 'patrial'], + 'patriarch': ['patriarch', 'phratriac'], + 'patrice': ['paretic', 'patrice', 'picrate'], + 'patricide': ['dipicrate', 'patricide', 'pediatric'], + 'patrico': ['apricot', 'atropic', 'parotic', 'patrico'], + 'patrilocal': ['allopatric', 'patrilocal'], + 'patriotic': ['parotitic', 'patriotic'], + 'patroclinous': ['patroclinous', 'pratincolous'], + 'patrol': ['patrol', 'portal', 'tropal'], + 'patron': ['patron', 'tarpon'], + 'patroness': ['patroness', 'transpose'], + 'patronite': ['antitrope', 'patronite', 'tritanope'], + 'patronymic': ['importancy', 'patronymic', 'pyromantic'], + 'patsy': ['pasty', 'patsy'], + 'patte': ['patte', 'tapet'], + 'pattee': ['pattee', 'tapete'], + 'patten': ['patent', 'patten', 'tapnet'], + 'pattener': ['pattener', 'repatent'], + 'patterer': ['patterer', 'pretreat'], + 'pattern': ['pattern', 'reptant'], + 'patterner': ['patterner', 'repattern'], + 'patu': ['patu', 'paut', 'tapu'], + 'patulent': ['patulent', 'petulant'], + 'pau': ['pau', 'pua'], + 'paul': ['paul', 'upla'], + 'paula': ['palau', 'paula'], + 'paulian': ['apulian', 'paulian', 'paulina'], + 'paulie': ['alpieu', 'paulie'], + 'paulin': ['paulin', 'pulian'], + 'paulina': ['apulian', 'paulian', 'paulina'], + 'paulinistic': ['paniculitis', 'paulinistic'], + 'paulinus': ['nauplius', 'paulinus'], + 'paulist': ['paulist', 'stipula'], + 'paup': ['paup', 'pupa'], + 'paut': ['patu', 'paut', 'tapu'], + 'paver': ['paver', 'verpa'], + 'pavid': ['pavid', 'vapid'], + 'pavidity': ['pavidity', 'vapidity'], + 'pavier': ['pavier', 'vipera'], + 'pavisor': ['pavisor', 'proavis'], + 'paw': ['paw', 'wap'], + 'pawner': ['enwrap', 'pawner', 'repawn'], + 'pay': ['pay', 'pya', 'yap'], + 'payer': ['apery', 'payer', 'repay'], + 'payroll': ['payroll', 'polarly'], + 'pea': ['ape', 'pea'], + 'peach': ['chape', 'cheap', 'peach'], + 'peachen': ['cheapen', 'peachen'], + 'peachery': ['cheapery', 'peachery'], + 'peachlet': ['chapelet', 'peachlet'], + 'peacoat': ['opacate', 'peacoat'], + 'peag': ['gape', 'page', 'peag', 'pega'], + 'peaker': ['parkee', 'peaker'], + 'peal': ['leap', 'lepa', 'pale', 'peal', 'plea'], + 'pealike': ['apelike', 'pealike'], + 'pean': ['nape', 'neap', 'nepa', 'pane', 'pean'], + 'pear': ['aper', 'pare', 'pear', 'rape', 'reap'], + 'pearl': ['lepra', 'paler', 'parel', 'parle', 'pearl', 'perla', 'relap'], + 'pearled': ['pearled', 'pedaler', 'pleader', 'replead'], + 'pearlet': ['pearlet', 'pleater', 'prelate', 'ptereal', 'replate', 'repleat'], + 'pearlin': ['pearlin', 'plainer', 'praline'], + 'pearlish': ['earlship', 'pearlish'], + 'pearlsides': ['displeaser', 'pearlsides'], + 'pearly': ['parley', 'pearly', 'player', 'replay'], + 'pearmain': ['amperian', 'paramine', 'pearmain'], + 'peart': ['apert', 'pater', 'peart', 'prate', 'taper', 'terap'], + 'pearten': ['patener', 'pearten', 'petrean', 'terpane'], + 'peartly': ['apertly', 'peartly', 'platery', 'pteryla', 'taperly'], + 'peartness': ['apertness', 'peartness', 'taperness'], + 'peasantry': ['peasantry', 'synaptera'], + 'peat': ['pate', 'peat', 'tape', 'teap'], + 'peatman': ['peatman', 'tapeman'], + 'peatship': ['happiest', 'peatship'], + 'peccation': ['acception', 'peccation'], + 'peckerwood': ['peckerwood', 'woodpecker'], + 'pecos': ['copse', 'pecos', 'scope'], + 'pectin': ['incept', 'pectin'], + 'pectinate': ['pectinate', 'pencatite'], + 'pectination': ['antinepotic', 'pectination'], + 'pectinatopinnate': ['pectinatopinnate', 'pinnatopectinate'], + 'pectinoid': ['depiction', 'pectinoid'], + 'pectora': ['coperta', 'pectora', 'porcate'], + 'pecunious': ['pecunious', 'puniceous'], + 'peda': ['depa', 'peda'], + 'pedal': ['padle', 'paled', 'pedal', 'plead'], + 'pedaler': ['pearled', 'pedaler', 'pleader', 'replead'], + 'pedalier': ['pedalier', 'perlidae'], + 'pedalion': ['lapideon', 'palinode', 'pedalion'], + 'pedalism': ['misplead', 'pedalism'], + 'pedalist': ['dispetal', 'pedalist'], + 'pedaliter': ['pedaliter', 'predetail'], + 'pedant': ['pedant', 'pentad'], + 'pedantess': ['adeptness', 'pedantess'], + 'pedantic': ['pedantic', 'pentacid'], + 'pedary': ['pedary', 'preday'], + 'pederastic': ['discrepate', 'pederastic'], + 'pedes': ['pedes', 'speed'], + 'pedesis': ['despise', 'pedesis'], + 'pedestrial': ['pedestrial', 'pilastered'], + 'pediatric': ['dipicrate', 'patricide', 'pediatric'], + 'pedicel': ['pedicel', 'pedicle'], + 'pedicle': ['pedicel', 'pedicle'], + 'pedicular': ['crepidula', 'pedicular'], + 'pediculi': ['lupicide', 'pediculi', 'pulicide'], + 'pedimana': ['pandemia', 'pedimana'], + 'pedocal': ['lacepod', 'pedocal', 'placode'], + 'pedometrician': ['pedometrician', 'premedication'], + 'pedrail': ['pedrail', 'predial'], + 'pedro': ['doper', 'pedro', 'pored'], + 'peed': ['deep', 'peed'], + 'peek': ['keep', 'peek'], + 'peel': ['leep', 'peel', 'pele'], + 'peelman': ['empanel', 'emplane', 'peelman'], + 'peen': ['neep', 'peen'], + 'peerly': ['peerly', 'yelper'], + 'pega': ['gape', 'page', 'peag', 'pega'], + 'peho': ['hope', 'peho'], + 'peiser': ['espier', 'peiser'], + 'peitho': ['ethiop', 'ophite', 'peitho'], + 'peixere': ['expiree', 'peixere'], + 'pekan': ['knape', 'pekan'], + 'pelage': ['paegel', 'paegle', 'pelage'], + 'pelasgoi': ['pelasgoi', 'spoilage'], + 'pele': ['leep', 'peel', 'pele'], + 'pelean': ['alpeen', 'lenape', 'pelean'], + 'pelecani': ['capeline', 'pelecani'], + 'pelecanus': ['encapsule', 'pelecanus'], + 'pelias': ['espial', 'lipase', 'pelias'], + 'pelican': ['calepin', 'capelin', 'panicle', 'pelican', 'pinacle'], + 'pelick': ['pelick', 'pickle'], + 'pelides': ['pelides', 'seedlip'], + 'pelidnota': ['pelidnota', 'planetoid'], + 'pelike': ['kelpie', 'pelike'], + 'pelisse': ['pelisse', 'pieless'], + 'pelite': ['leepit', 'pelite', 'pielet'], + 'pellation': ['pellation', 'pollinate'], + 'pellotine': ['pellotine', 'pollenite'], + 'pelmet': ['pelmet', 'temple'], + 'pelon': ['pelon', 'pleon'], + 'pelops': ['pelops', 'peplos'], + 'pelorian': ['pelorian', 'peronial', 'proalien'], + 'peloric': ['peloric', 'precoil'], + 'pelorus': ['leprous', 'pelorus', 'sporule'], + 'pelota': ['alepot', 'pelota'], + 'pelta': ['leapt', + 'palet', + 'patel', + 'pelta', + 'petal', + 'plate', + 'pleat', + 'tepal'], + 'peltandra': ['leptandra', 'peltandra'], + 'peltast': ['peltast', 'spattle'], + 'peltate': ['palette', 'peltate'], + 'peltation': ['peltation', 'potential'], + 'pelter': ['pelter', 'petrel'], + 'peltiform': ['leptiform', 'peltiform'], + 'pelting': ['pelting', 'petling'], + 'peltry': ['peltry', 'pertly'], + 'pelu': ['lupe', 'pelu', 'peul', 'pule'], + 'pelusios': ['epulosis', 'pelusios'], + 'pelycography': ['pelycography', 'pyrgocephaly'], + 'pemican': ['campine', 'pemican'], + 'pen': ['nep', 'pen'], + 'penal': ['alpen', 'nepal', 'panel', 'penal', 'plane'], + 'penalist': ['panelist', 'pantelis', 'penalist', 'plastein'], + 'penalty': ['aplenty', 'penalty'], + 'penang': ['pangen', 'penang'], + 'penates': ['penates', 'septane'], + 'pencatite': ['pectinate', 'pencatite'], + 'penciled': ['depencil', 'penciled', 'pendicle'], + 'pencilry': ['pencilry', 'princely'], + 'penda': ['paned', 'penda'], + 'pendicle': ['depencil', 'penciled', 'pendicle'], + 'pendulant': ['pendulant', 'unplanted'], + 'pendular': ['pendular', 'underlap', 'uplander'], + 'pendulate': ['pendulate', 'unpleated'], + 'pendulation': ['pendulation', 'pennatuloid'], + 'pendulum': ['pendulum', 'unlumped', 'unplumed'], + 'penetrable': ['penetrable', 'repentable'], + 'penetrance': ['penetrance', 'repentance'], + 'penetrant': ['penetrant', 'repentant'], + 'penial': ['alpine', 'nepali', 'penial', 'pineal'], + 'penis': ['penis', 'snipe', 'spine'], + 'penitencer': ['penitencer', 'pertinence'], + 'penman': ['nepman', 'penman'], + 'penna': ['panne', 'penna'], + 'pennage': ['pangene', 'pennage'], + 'pennate': ['pennate', 'pentane'], + 'pennatulid': ['pennatulid', 'pinnulated'], + 'pennatuloid': ['pendulation', 'pennatuloid'], + 'pennia': ['nanpie', 'pennia', 'pinnae'], + 'pennisetum': ['pennisetum', 'septennium'], + 'pensioner': ['pensioner', 'repension'], + 'pensive': ['pensive', 'vespine'], + 'penster': ['penster', 'present', 'serpent', 'strepen'], + 'penta': ['enapt', 'paten', 'penta', 'tapen'], + 'pentace': ['pentace', 'tepanec'], + 'pentacid': ['pedantic', 'pentacid'], + 'pentad': ['pedant', 'pentad'], + 'pentadecoic': ['adenectopic', 'pentadecoic'], + 'pentail': ['pantile', 'pentail', 'platine', 'talpine'], + 'pentamerid': ['pandermite', 'pentamerid'], + 'pentameroid': ['pentameroid', 'predominate'], + 'pentane': ['pennate', 'pentane'], + 'pentaploid': ['deoppilant', 'pentaploid'], + 'pentathionic': ['antiphonetic', 'pentathionic'], + 'pentatomic': ['camptonite', 'pentatomic'], + 'pentitol': ['pentitol', 'pointlet'], + 'pentoic': ['entopic', 'nepotic', 'pentoic'], + 'pentol': ['lepton', 'pentol'], + 'pentose': ['pentose', 'posteen'], + 'pentyl': ['pentyl', 'plenty'], + 'penult': ['penult', 'punlet', 'puntel'], + 'peon': ['nope', 'open', 'peon', 'pone'], + 'peony': ['peony', 'poney'], + 'peopler': ['peopler', 'popeler'], + 'peorian': ['apeiron', 'peorian'], + 'peplos': ['pelops', 'peplos'], + 'peplum': ['peplum', 'pumple'], + 'peplus': ['peplus', 'supple'], + 'pepo': ['pepo', 'pope'], + 'per': ['per', 'rep'], + 'peracid': ['epacrid', 'peracid', 'preacid'], + 'peract': ['carpet', 'peract', 'preact'], + 'peracute': ['peracute', 'preacute'], + 'peradventure': ['peradventure', 'preadventure'], + 'perakim': ['perakim', 'permiak', 'rampike'], + 'peramble': ['peramble', 'preamble'], + 'perambulate': ['perambulate', 'preambulate'], + 'perambulation': ['perambulation', 'preambulation'], + 'perambulatory': ['perambulatory', 'preambulatory'], + 'perates': ['perates', 'repaste', 'sperate'], + 'perbend': ['perbend', 'prebend'], + 'perborate': ['perborate', 'prorebate', 'reprobate'], + 'perca': ['caper', 'crape', 'pacer', 'perca', 'recap'], + 'percale': ['percale', 'replace'], + 'percaline': ['percaline', 'periclean'], + 'percent': ['percent', 'precent'], + 'percept': ['percept', 'precept'], + 'perception': ['perception', 'preception'], + 'perceptionism': ['misperception', 'perceptionism'], + 'perceptive': ['perceptive', 'preceptive'], + 'perceptively': ['perceptively', 'preceptively'], + 'perceptual': ['perceptual', 'preceptual'], + 'perceptually': ['perceptually', 'preceptually'], + 'percha': ['aperch', 'eparch', 'percha', 'preach'], + 'perchloric': ['perchloric', 'prechloric'], + 'percid': ['percid', 'priced'], + 'perclose': ['perclose', 'preclose'], + 'percoidea': ['adipocere', 'percoidea'], + 'percolate': ['percolate', 'prelocate'], + 'percolation': ['neotropical', 'percolation'], + 'percompound': ['percompound', 'precompound'], + 'percontation': ['percontation', 'pernoctation'], + 'perculsion': ['perculsion', 'preclusion'], + 'perculsive': ['perculsive', 'preclusive'], + 'percurrent': ['percurrent', 'precurrent'], + 'percursory': ['percursory', 'precursory'], + 'percussion': ['croupiness', 'percussion', 'supersonic'], + 'percussioner': ['percussioner', 'repercussion'], + 'percussor': ['percussor', 'procuress'], + 'percy': ['crepy', 'cypre', 'percy'], + 'perdicine': ['perdicine', 'recipiend'], + 'perdition': ['direption', 'perdition', 'tropidine'], + 'perdu': ['drupe', 'duper', 'perdu', 'prude', 'pured'], + 'peregrina': ['peregrina', 'pregainer'], + 'pereion': ['pereion', 'pioneer'], + 'perendure': ['perendure', 'underpeer'], + 'peres': ['peres', 'perse', 'speer', 'spree'], + 'perfect': ['perfect', 'prefect'], + 'perfected': ['perfected', 'predefect'], + 'perfection': ['frontpiece', 'perfection'], + 'perfectly': ['perfectly', 'prefectly'], + 'perfervid': ['perfervid', 'prefervid'], + 'perfoliation': ['perfoliation', 'prefoliation'], + 'perforative': ['perforative', 'prefavorite'], + 'perform': ['perform', 'preform'], + 'performant': ['performant', 'preformant'], + 'performative': ['performative', 'preformative'], + 'performer': ['performer', 'prereform', 'reperform'], + 'pergamic': ['crimpage', 'pergamic'], + 'perhaps': ['perhaps', 'prehaps'], + 'perhazard': ['perhazard', 'prehazard'], + 'peri': ['peri', 'pier', 'ripe'], + 'periacinal': ['epicranial', 'periacinal'], + 'perianal': ['airplane', 'perianal'], + 'perianth': ['perianth', 'triphane'], + 'periapt': ['periapt', 'rappite'], + 'periaster': ['periaster', 'sparterie'], + 'pericardiacophrenic': ['pericardiacophrenic', 'phrenicopericardiac'], + 'pericardiopleural': ['pericardiopleural', 'pleuropericardial'], + 'perichete': ['perichete', 'perithece'], + 'periclase': ['episclera', 'periclase'], + 'periclean': ['percaline', 'periclean'], + 'pericles': ['eclipser', 'pericles', 'resplice'], + 'pericopal': ['pericopal', 'periploca'], + 'periculant': ['periculant', 'unprelatic'], + 'peridental': ['interplead', 'peridental'], + 'peridiastolic': ['peridiastolic', 'periodicalist', 'proidealistic'], + 'peridot': ['diopter', 'peridot', 'proetid', 'protide', 'pteroid'], + 'perigone': ['perigone', 'pigeoner'], + 'peril': ['peril', 'piler', 'plier'], + 'perilous': ['perilous', 'uropsile'], + 'perimeter': ['perimeter', 'peritreme'], + 'perine': ['neiper', 'perine', 'pirene', 'repine'], + 'perineovaginal': ['perineovaginal', 'vaginoperineal'], + 'periodate': ['periodate', 'proetidae', 'proteidae'], + 'periodicalist': ['peridiastolic', 'periodicalist', 'proidealistic'], + 'periodontal': ['deploration', 'periodontal'], + 'periost': ['periost', 'porites', 'reposit', 'riposte'], + 'periosteal': ['periosteal', 'praseolite'], + 'periotic': ['epirotic', 'periotic'], + 'peripatetic': ['peripatetic', 'precipitate'], + 'peripatidae': ['peripatidae', 'peripatidea'], + 'peripatidea': ['peripatidae', 'peripatidea'], + 'periplaneta': ['periplaneta', 'prepalatine'], + 'periploca': ['pericopal', 'periploca'], + 'periplus': ['periplus', 'supplier'], + 'periportal': ['periportal', 'peritropal'], + 'periproct': ['cotripper', 'periproct'], + 'peripterous': ['peripterous', 'prepositure'], + 'perique': ['perique', 'repique'], + 'perirectal': ['perirectal', 'prerecital'], + 'periscian': ['periscian', 'precisian'], + 'periscopal': ['periscopal', 'sapropelic'], + 'perish': ['perish', 'reship'], + 'perished': ['hesperid', 'perished'], + 'perishment': ['perishment', 'reshipment'], + 'perisomal': ['perisomal', 'semipolar'], + 'perisome': ['perisome', 'promisee', 'reimpose'], + 'perispome': ['perispome', 'preimpose'], + 'peristole': ['epistoler', 'peristole', 'perseitol', 'pistoleer'], + 'peristoma': ['epistroma', 'peristoma'], + 'peristomal': ['peristomal', 'prestomial'], + 'peristylos': ['peristylos', 'pterylosis'], + 'perit': ['perit', 'retip', 'tripe'], + 'perite': ['perite', 'petrie', 'pieter'], + 'peritenon': ['interpone', 'peritenon', 'pinnotere', 'preintone'], + 'perithece': ['perichete', 'perithece'], + 'peritomize': ['epitomizer', 'peritomize'], + 'peritomous': ['outpromise', 'peritomous'], + 'peritreme': ['perimeter', 'peritreme'], + 'peritrichous': ['courtiership', 'peritrichous'], + 'peritroch': ['chiropter', 'peritroch'], + 'peritropal': ['periportal', 'peritropal'], + 'peritropous': ['peritropous', 'proprietous'], + 'perkin': ['perkin', 'pinker'], + 'perknite': ['perknite', 'peterkin'], + 'perla': ['lepra', 'paler', 'parel', 'parle', 'pearl', 'perla', 'relap'], + 'perle': ['leper', 'perle', 'repel'], + 'perlection': ['perlection', 'prelection'], + 'perlidae': ['pedalier', 'perlidae'], + 'perlingual': ['perlingual', 'prelingual'], + 'perlite': ['perlite', 'reptile'], + 'perlitic': ['perlitic', 'triplice'], + 'permeameter': ['amperemeter', 'permeameter'], + 'permeance': ['permeance', 'premenace'], + 'permeant': ['permeant', 'peterman'], + 'permeation': ['permeation', 'preominate'], + 'permiak': ['perakim', 'permiak', 'rampike'], + 'permissibility': ['impressibility', 'permissibility'], + 'permissible': ['impressible', 'permissible'], + 'permissibleness': ['impressibleness', 'permissibleness'], + 'permissibly': ['impressibly', 'permissibly'], + 'permission': ['impression', 'permission'], + 'permissive': ['impressive', 'permissive'], + 'permissively': ['impressively', 'permissively'], + 'permissiveness': ['impressiveness', 'permissiveness'], + 'permitter': ['permitter', 'pretermit'], + 'permixture': ['permixture', 'premixture'], + 'permonosulphuric': ['monopersulphuric', 'permonosulphuric'], + 'permutation': ['importunate', 'permutation'], + 'pernasal': ['pernasal', 'prenasal'], + 'pernis': ['pernis', 'respin', 'sniper'], + 'pernoctation': ['percontation', 'pernoctation'], + 'pernor': ['pernor', 'perron'], + 'pernyi': ['pernyi', 'pinery'], + 'peronial': ['pelorian', 'peronial', 'proalien'], + 'peropus': ['peropus', 'purpose'], + 'peroral': ['peroral', 'preoral'], + 'perorally': ['perorally', 'preorally'], + 'perorate': ['perorate', 'retepora'], + 'perosmate': ['perosmate', 'sematrope'], + 'perosmic': ['comprise', 'perosmic'], + 'perotic': ['perotic', 'proteic', 'tropeic'], + 'perpera': ['paperer', 'perpera', 'prepare', 'repaper'], + 'perpetualist': ['perpetualist', 'pluriseptate'], + 'perplexer': ['perplexer', 'reperplex'], + 'perron': ['pernor', 'perron'], + 'perry': ['perry', 'pryer'], + 'persae': ['parsee', 'persae', 'persea', 'serape'], + 'persalt': ['palster', 'persalt', 'plaster', 'psalter', 'spartle', 'stapler'], + 'perscribe': ['perscribe', 'prescribe'], + 'perse': ['peres', 'perse', 'speer', 'spree'], + 'persea': ['parsee', 'persae', 'persea', 'serape'], + 'perseid': ['perseid', 'preside'], + 'perseitol': ['epistoler', 'peristole', 'perseitol', 'pistoleer'], + 'perseity': ['perseity', 'speerity'], + 'persian': ['persian', 'prasine', 'saprine'], + 'persic': ['crepis', 'cripes', 'persic', 'precis', 'spicer'], + 'persico': ['ceriops', 'persico'], + 'persism': ['impress', 'persism', 'premiss'], + 'persist': ['persist', 'spriest'], + 'persistent': ['persistent', 'presentist', 'prettiness'], + 'personalia': ['palaeornis', 'personalia'], + 'personalistic': ['personalistic', 'pictorialness'], + 'personate': ['esperanto', 'personate'], + 'personed': ['personed', 'responde'], + 'pert': ['pert', 'petr', 'terp'], + 'pertain': ['painter', 'pertain', 'pterian', 'repaint'], + 'perten': ['perten', 'repent'], + 'perthite': ['perthite', 'tephrite'], + 'perthitic': ['perthitic', 'tephritic'], + 'pertinacity': ['antipyretic', 'pertinacity'], + 'pertinence': ['penitencer', 'pertinence'], + 'pertly': ['peltry', 'pertly'], + 'perturbational': ['perturbational', 'protuberantial'], + 'pertussal': ['pertussal', 'supersalt'], + 'perty': ['perty', 'typer'], + 'peru': ['peru', 'prue', 'pure'], + 'perugian': ['pagurine', 'perugian'], + 'peruke': ['keuper', 'peruke'], + 'perula': ['epural', 'perula', 'pleura'], + 'perun': ['perun', 'prune'], + 'perusable': ['perusable', 'superable'], + 'perusal': ['perusal', 'serpula'], + 'peruse': ['peruse', 'respue'], + 'pervade': ['deprave', 'pervade'], + 'pervader': ['depraver', 'pervader'], + 'pervadingly': ['depravingly', 'pervadingly'], + 'perverse': ['perverse', 'preserve'], + 'perversion': ['perversion', 'preversion'], + 'pervious': ['pervious', 'previous', 'viperous'], + 'perviously': ['perviously', 'previously', 'viperously'], + 'perviousness': ['perviousness', 'previousness', 'viperousness'], + 'pesa': ['apse', 'pesa', 'spae'], + 'pesach': ['cephas', 'pesach'], + 'pesah': ['heaps', 'pesah', 'phase', 'shape'], + 'peseta': ['asteep', 'peseta'], + 'peso': ['epos', 'peso', 'pose', 'sope'], + 'pess': ['pess', 'seps'], + 'pessoner': ['pessoner', 'response'], + 'pest': ['pest', 'sept', 'spet', 'step'], + 'peste': ['peste', 'steep'], + 'pester': ['pester', 'preset', 'restep', 'streep'], + 'pesthole': ['heelpost', 'pesthole'], + 'pesticidal': ['pesticidal', 'septicidal'], + 'pestiferous': ['pestiferous', 'septiferous'], + 'pestle': ['pestle', 'spleet'], + 'petal': ['leapt', + 'palet', + 'patel', + 'pelta', + 'petal', + 'plate', + 'pleat', + 'tepal'], + 'petalia': ['palaite', 'petalia', 'pileata'], + 'petaline': ['petaline', 'tapeline'], + 'petalism': ['petalism', 'septimal'], + 'petalless': ['petalless', 'plateless', 'pleatless'], + 'petallike': ['petallike', 'platelike'], + 'petaloid': ['opdalite', 'petaloid'], + 'petalon': ['lepanto', 'nepotal', 'petalon', 'polenta'], + 'petard': ['depart', 'parted', 'petard'], + 'petary': ['petary', 'pratey'], + 'petasos': ['pastose', 'petasos'], + 'petchary': ['patchery', 'petchary'], + 'petechial': ['epithecal', 'petechial', 'phacelite'], + 'petechiate': ['epithecate', 'petechiate'], + 'peteman': ['peteman', 'tempean'], + 'peter': ['erept', 'peter', 'petre'], + 'peterkin': ['perknite', 'peterkin'], + 'peterman': ['permeant', 'peterman'], + 'petiolary': ['epilatory', 'petiolary'], + 'petiole': ['petiole', 'pilotee'], + 'petioled': ['lepidote', 'petioled'], + 'petitionary': ['opiniatrety', 'petitionary'], + 'petitioner': ['petitioner', 'repetition'], + 'petiveria': ['aperitive', 'petiveria'], + 'petling': ['pelting', 'petling'], + 'peto': ['peto', 'poet', 'pote', 'tope'], + 'petr': ['pert', 'petr', 'terp'], + 'petre': ['erept', 'peter', 'petre'], + 'petrea': ['petrea', 'repeat', 'retape'], + 'petrean': ['patener', 'pearten', 'petrean', 'terpane'], + 'petrel': ['pelter', 'petrel'], + 'petricola': ['carpolite', 'petricola'], + 'petrie': ['perite', 'petrie', 'pieter'], + 'petrine': ['petrine', 'terpine'], + 'petrochemical': ['cephalometric', 'petrochemical'], + 'petrogale': ['petrogale', 'petrolage', 'prolegate'], + 'petrographer': ['petrographer', 'pterographer'], + 'petrographic': ['petrographic', 'pterographic'], + 'petrographical': ['petrographical', 'pterographical'], + 'petrographically': ['petrographically', 'pterylographical'], + 'petrography': ['petrography', 'pterography', 'typographer'], + 'petrol': ['petrol', 'replot'], + 'petrolage': ['petrogale', 'petrolage', 'prolegate'], + 'petrolean': ['petrolean', 'rantepole'], + 'petrolic': ['petrolic', 'plerotic'], + 'petrologically': ['petrologically', 'pterylological'], + 'petrosa': ['esparto', 'petrosa', 'seaport'], + 'petrosal': ['petrosal', 'polestar'], + 'petrosquamosal': ['petrosquamosal', 'squamopetrosal'], + 'petrous': ['petrous', 'posture', 'proetus', 'proteus', 'septuor', 'spouter'], + 'petticoated': ['depetticoat', 'petticoated'], + 'petulant': ['patulent', 'petulant'], + 'petune': ['neetup', 'petune'], + 'peul': ['lupe', 'pelu', 'peul', 'pule'], + 'pewy': ['pewy', 'wype'], + 'peyote': ['peyote', 'poteye'], + 'peyotl': ['peyotl', 'poetly'], + 'phacelia': ['acephali', 'phacelia'], + 'phacelite': ['epithecal', 'petechial', 'phacelite'], + 'phacoid': ['dapicho', 'phacoid'], + 'phacolite': ['hopcalite', 'phacolite'], + 'phacolysis': ['pachylosis', 'phacolysis'], + 'phacometer': ['pachometer', 'phacometer'], + 'phaethonic': ['phaethonic', 'theophanic'], + 'phaeton': ['phaeton', 'phonate'], + 'phagocytism': ['mycophagist', 'phagocytism'], + 'phalaecian': ['acephalina', 'phalaecian'], + 'phalangium': ['gnaphalium', 'phalangium'], + 'phalera': ['phalera', 'raphael'], + 'phanariot': ['parathion', 'phanariot'], + 'phanerogam': ['anemograph', 'phanerogam'], + 'phanerogamic': ['anemographic', 'phanerogamic'], + 'phanerogamy': ['anemography', 'phanerogamy'], + 'phanic': ['apinch', 'chapin', 'phanic'], + 'phano': ['phano', 'pohna'], + 'pharaonic': ['anaphoric', 'pharaonic'], + 'pharaonical': ['anaphorical', 'pharaonical'], + 'phare': ['hepar', 'phare', 'raphe'], + 'pharian': ['pharian', 'piranha'], + 'pharisaic': ['chirapsia', 'pharisaic'], + 'pharisean': ['pharisean', 'seraphina'], + 'pharisee': ['hesperia', 'pharisee'], + 'phariseeism': ['hemiparesis', 'phariseeism'], + 'pharmacolite': ['metaphorical', 'pharmacolite'], + 'pharyngolaryngeal': ['laryngopharyngeal', 'pharyngolaryngeal'], + 'pharyngolaryngitis': ['laryngopharyngitis', 'pharyngolaryngitis'], + 'pharyngorhinitis': ['pharyngorhinitis', 'rhinopharyngitis'], + 'phase': ['heaps', 'pesah', 'phase', 'shape'], + 'phaseless': ['phaseless', 'shapeless'], + 'phaseolin': ['esiphonal', 'phaseolin'], + 'phasis': ['aspish', 'phasis'], + 'phasm': ['pashm', 'phasm'], + 'phasmid': ['dampish', 'madship', 'phasmid'], + 'phasmoid': ['phasmoid', 'shopmaid'], + 'pheal': ['aleph', 'pheal'], + 'pheasant': ['pheasant', 'stephana'], + 'phecda': ['chaped', 'phecda'], + 'phemie': ['imphee', 'phemie'], + 'phenacite': ['phenacite', 'phenicate'], + 'phenate': ['haptene', 'heptane', 'phenate'], + 'phenetole': ['phenetole', 'telephone'], + 'phenic': ['phenic', 'pinche'], + 'phenicate': ['phenacite', 'phenicate'], + 'phenolic': ['phenolic', 'pinochle'], + 'phenological': ['nephological', 'phenological'], + 'phenologist': ['nephologist', 'phenologist'], + 'phenology': ['nephology', 'phenology'], + 'phenosal': ['alphonse', 'phenosal'], + 'pheon': ['pheon', 'phone'], + 'phi': ['hip', 'phi'], + 'phialide': ['hepialid', 'phialide'], + 'philobotanist': ['botanophilist', 'philobotanist'], + 'philocynic': ['cynophilic', 'philocynic'], + 'philohela': ['halophile', 'philohela'], + 'philoneism': ['neophilism', 'philoneism'], + 'philopoet': ['philopoet', 'photopile'], + 'philotheist': ['philotheist', 'theophilist'], + 'philotherian': ['lithonephria', 'philotherian'], + 'philozoic': ['philozoic', 'zoophilic'], + 'philozoist': ['philozoist', 'zoophilist'], + 'philter': ['philter', 'thripel'], + 'phineas': ['inphase', 'phineas'], + 'phiroze': ['orphize', 'phiroze'], + 'phit': ['phit', 'pith'], + 'phlebometritis': ['metrophlebitis', 'phlebometritis'], + 'phleum': ['phleum', 'uphelm'], + 'phloretic': ['phloretic', 'plethoric'], + 'pho': ['hop', 'pho', 'poh'], + 'phobism': ['mobship', 'phobism'], + 'phoca': ['chopa', 'phoca', 'poach'], + 'phocaean': ['phocaean', 'phocaena'], + 'phocaena': ['phocaean', 'phocaena'], + 'phocaenine': ['phocaenine', 'phoenicean'], + 'phocean': ['copehan', 'panoche', 'phocean'], + 'phocian': ['aphonic', 'phocian'], + 'phocine': ['chopine', 'phocine'], + 'phoenicean': ['phocaenine', 'phoenicean'], + 'pholad': ['adolph', 'pholad'], + 'pholas': ['alphos', 'pholas'], + 'pholcidae': ['cephaloid', 'pholcidae'], + 'pholcoid': ['chilopod', 'pholcoid'], + 'phonate': ['phaeton', 'phonate'], + 'phone': ['pheon', 'phone'], + 'phonelescope': ['nepheloscope', 'phonelescope'], + 'phonetical': ['pachnolite', 'phonetical'], + 'phonetics': ['phonetics', 'sphenotic'], + 'phoniatry': ['phoniatry', 'thiopyran'], + 'phonic': ['chopin', 'phonic'], + 'phonogram': ['monograph', 'nomograph', 'phonogram'], + 'phonogramic': ['gramophonic', 'monographic', 'nomographic', 'phonogramic'], + 'phonogramically': ['gramophonically', + 'monographically', + 'nomographically', + 'phonogramically'], + 'phonographic': ['graphophonic', 'phonographic'], + 'phonolite': ['lithopone', 'phonolite'], + 'phonometer': ['nephrotome', 'phonometer'], + 'phonometry': ['nephrotomy', 'phonometry'], + 'phonophote': ['phonophote', 'photophone'], + 'phonotyper': ['hypopteron', 'phonotyper'], + 'phoo': ['hoop', 'phoo', 'pooh'], + 'phorone': ['orpheon', 'phorone'], + 'phoronidea': ['phoronidea', 'radiophone'], + 'phos': ['phos', 'posh', 'shop', 'soph'], + 'phosphatide': ['diphosphate', 'phosphatide'], + 'phosphoglycerate': ['glycerophosphate', 'phosphoglycerate'], + 'phossy': ['hyssop', 'phossy', 'sposhy'], + 'phot': ['phot', 'toph'], + 'photechy': ['hypothec', 'photechy'], + 'photochromography': ['chromophotography', 'photochromography'], + 'photochromolithograph': ['chromophotolithograph', 'photochromolithograph'], + 'photochronograph': ['chronophotograph', 'photochronograph'], + 'photochronographic': ['chronophotographic', 'photochronographic'], + 'photochronography': ['chronophotography', 'photochronography'], + 'photogram': ['motograph', 'photogram'], + 'photographer': ['photographer', 'rephotograph'], + 'photoheliography': ['heliophotography', 'photoheliography'], + 'photolithography': ['lithophotography', 'photolithography'], + 'photomacrograph': ['macrophotograph', 'photomacrograph'], + 'photometer': ['photometer', 'prototheme'], + 'photomicrograph': ['microphotograph', 'photomicrograph'], + 'photomicrographic': ['microphotographic', 'photomicrographic'], + 'photomicrography': ['microphotography', 'photomicrography'], + 'photomicroscope': ['microphotoscope', 'photomicroscope'], + 'photophone': ['phonophote', 'photophone'], + 'photopile': ['philopoet', 'photopile'], + 'photostereograph': ['photostereograph', 'stereophotograph'], + 'phototelegraph': ['phototelegraph', 'telephotograph'], + 'phototelegraphic': ['phototelegraphic', 'telephotographic'], + 'phototelegraphy': ['phototelegraphy', 'telephotography'], + 'phototypography': ['phototypography', 'phytotopography'], + 'phrase': ['phrase', 'seraph', 'shaper', 'sherpa'], + 'phraser': ['phraser', 'sharper'], + 'phrasing': ['harpings', 'phrasing'], + 'phrasy': ['phrasy', 'sharpy'], + 'phratriac': ['patriarch', 'phratriac'], + 'phreatic': ['chapiter', 'phreatic'], + 'phrenesia': ['hesperian', 'phrenesia', 'seraphine'], + 'phrenic': ['nephric', 'phrenic', 'pincher'], + 'phrenicopericardiac': ['pericardiacophrenic', 'phrenicopericardiac'], + 'phrenics': ['phrenics', 'pinscher'], + 'phrenitic': ['nephritic', 'phrenitic', 'prehnitic'], + 'phrenitis': ['inspreith', 'nephritis', 'phrenitis'], + 'phrenocardiac': ['nephrocardiac', 'phrenocardiac'], + 'phrenocolic': ['nephrocolic', 'phrenocolic'], + 'phrenocostal': ['phrenocostal', 'plastochrone'], + 'phrenogastric': ['gastrophrenic', 'nephrogastric', 'phrenogastric'], + 'phrenohepatic': ['hepatonephric', 'phrenohepatic'], + 'phrenologist': ['nephrologist', 'phrenologist'], + 'phrenology': ['nephrology', 'phrenology'], + 'phrenopathic': ['nephropathic', 'phrenopathic'], + 'phrenopathy': ['nephropathy', 'phrenopathy'], + 'phrenosplenic': ['phrenosplenic', 'splenonephric', 'splenophrenic'], + 'phronesis': ['nephrosis', 'phronesis'], + 'phronimidae': ['diamorphine', 'phronimidae'], + 'phthalazine': ['naphthalize', 'phthalazine'], + 'phu': ['hup', 'phu'], + 'phycitol': ['cytophil', 'phycitol'], + 'phycocyanin': ['cyanophycin', 'phycocyanin'], + 'phyla': ['haply', 'phyla'], + 'phyletic': ['heptylic', 'phyletic'], + 'phylloceras': ['hyposcleral', 'phylloceras'], + 'phylloclad': ['cladophyll', 'phylloclad'], + 'phyllodial': ['phyllodial', 'phylloidal'], + 'phylloerythrin': ['erythrophyllin', 'phylloerythrin'], + 'phylloidal': ['phyllodial', 'phylloidal'], + 'phyllopodous': ['phyllopodous', 'podophyllous'], + 'phyma': ['phyma', 'yamph'], + 'phymatodes': ['desmopathy', 'phymatodes'], + 'phymosia': ['hyposmia', 'phymosia'], + 'physa': ['physa', 'shapy'], + 'physalite': ['physalite', 'styphelia'], + 'physic': ['physic', 'scyphi'], + 'physicochemical': ['chemicophysical', 'physicochemical'], + 'physicomedical': ['medicophysical', 'physicomedical'], + 'physiocrat': ['physiocrat', 'psychotria'], + 'physiologicoanatomic': ['anatomicophysiologic', 'physiologicoanatomic'], + 'physiopsychological': ['physiopsychological', 'psychophysiological'], + 'physiopsychology': ['physiopsychology', 'psychophysiology'], + 'phytic': ['phytic', 'pitchy', 'pythic', 'typhic'], + 'phytogenesis': ['phytogenesis', 'pythogenesis'], + 'phytogenetic': ['phytogenetic', 'pythogenetic'], + 'phytogenic': ['phytogenic', 'pythogenic', 'typhogenic'], + 'phytogenous': ['phytogenous', 'pythogenous'], + 'phytoid': ['phytoid', 'typhoid'], + 'phytologist': ['hypoglottis', 'phytologist'], + 'phytometer': ['phytometer', 'thermotype'], + 'phytometric': ['phytometric', 'thermotypic'], + 'phytometry': ['phytometry', 'thermotypy'], + 'phytomonas': ['phytomonas', 'somnopathy'], + 'phyton': ['phyton', 'python'], + 'phytonic': ['hypnotic', 'phytonic', 'pythonic', 'typhonic'], + 'phytosis': ['phytosis', 'typhosis'], + 'phytotopography': ['phototypography', 'phytotopography'], + 'phytozoa': ['phytozoa', 'zoopathy', 'zoophyta'], + 'piacle': ['epical', 'piacle', 'plaice'], + 'piacular': ['apicular', 'piacular'], + 'pial': ['lipa', 'pail', 'pali', 'pial'], + 'pialyn': ['alypin', 'pialyn'], + 'pian': ['nipa', 'pain', 'pani', 'pian', 'pina'], + 'pianiste': ['pianiste', 'pisanite'], + 'piannet': ['piannet', 'pinnate'], + 'pianola': ['opalina', 'pianola'], + 'piaroa': ['aporia', 'piaroa'], + 'piast': ['piast', 'stipa', 'tapis'], + 'piaster': ['piaster', 'piastre', 'raspite', 'spirate', 'traipse'], + 'piastre': ['piaster', 'piastre', 'raspite', 'spirate', 'traipse'], + 'picador': ['parodic', 'picador'], + 'picae': ['picae', 'picea'], + 'pical': ['pical', 'plica'], + 'picard': ['caprid', 'carpid', 'picard'], + 'picarel': ['caliper', 'picarel', 'replica'], + 'picary': ['cypria', 'picary', 'piracy'], + 'pice': ['epic', 'pice'], + 'picea': ['picae', 'picea'], + 'picene': ['picene', 'piecen'], + 'picker': ['picker', 'repick'], + 'pickle': ['pelick', 'pickle'], + 'pickler': ['pickler', 'prickle'], + 'pickover': ['overpick', 'pickover'], + 'picktooth': ['picktooth', 'toothpick'], + 'pico': ['cipo', 'pico'], + 'picot': ['optic', 'picot', 'topic'], + 'picotah': ['aphotic', 'picotah'], + 'picra': ['capri', 'picra', 'rapic'], + 'picrate': ['paretic', 'patrice', 'picrate'], + 'pictography': ['graphotypic', 'pictography', 'typographic'], + 'pictorialness': ['personalistic', 'pictorialness'], + 'picture': ['cuprite', 'picture'], + 'picudilla': ['picudilla', 'pulicidal'], + 'pidan': ['pidan', 'pinda'], + 'piebald': ['bipedal', 'piebald'], + 'piebaldness': ['dispensable', 'piebaldness'], + 'piecen': ['picene', 'piecen'], + 'piecer': ['piecer', 'pierce', 'recipe'], + 'piecework': ['piecework', 'workpiece'], + 'piecrust': ['crepitus', 'piecrust'], + 'piedness': ['dispense', 'piedness'], + 'piegan': ['genipa', 'piegan'], + 'pieless': ['pelisse', 'pieless'], + 'pielet': ['leepit', 'pelite', 'pielet'], + 'piemag': ['magpie', 'piemag'], + 'pieman': ['impane', 'pieman'], + 'pien': ['pien', 'pine'], + 'piend': ['piend', 'pined'], + 'pier': ['peri', 'pier', 'ripe'], + 'pierce': ['piecer', 'pierce', 'recipe'], + 'piercent': ['piercent', 'prentice'], + 'piercer': ['piercer', 'reprice'], + 'pierlike': ['pierlike', 'ripelike'], + 'piet': ['piet', 'tipe'], + 'pietas': ['patesi', 'pietas'], + 'pieter': ['perite', 'petrie', 'pieter'], + 'pig': ['gip', 'pig'], + 'pigeoner': ['perigone', 'pigeoner'], + 'pigeontail': ['pigeontail', 'plagionite'], + 'pigly': ['gilpy', 'pigly'], + 'pignon': ['ningpo', 'pignon'], + 'pignorate': ['operating', 'pignorate'], + 'pigskin': ['pigskin', 'spiking'], + 'pigsney': ['gypsine', 'pigsney'], + 'pik': ['kip', 'pik'], + 'pika': ['paik', 'pika'], + 'pike': ['kepi', 'kipe', 'pike'], + 'pikel': ['pikel', 'pikle'], + 'piker': ['krepi', 'piker'], + 'pikle': ['pikel', 'pikle'], + 'pilage': ['paigle', 'pilage'], + 'pilar': ['april', 'pilar', 'ripal'], + 'pilaster': ['epistlar', 'pilaster', 'plaister', 'priestal'], + 'pilastered': ['pedestrial', 'pilastered'], + 'pilastric': ['pilastric', 'triplasic'], + 'pilate': ['aplite', 'pilate'], + 'pileata': ['palaite', 'petalia', 'pileata'], + 'pileate': ['epilate', 'epitela', 'pileate'], + 'pileated': ['depilate', 'leptidae', 'pileated'], + 'piled': ['piled', 'plied'], + 'piler': ['peril', 'piler', 'plier'], + 'piles': ['piles', 'plies', 'slipe', 'spiel', 'spile'], + 'pileus': ['epulis', 'pileus'], + 'pili': ['ipil', 'pili'], + 'pilin': ['lipin', 'pilin'], + 'pillarist': ['pillarist', 'pistillar'], + 'pillet': ['liplet', 'pillet'], + 'pilm': ['limp', 'pilm', 'plim'], + 'pilmy': ['imply', 'limpy', 'pilmy'], + 'pilosis': ['liposis', 'pilosis'], + 'pilotee': ['petiole', 'pilotee'], + 'pilpai': ['lippia', 'pilpai'], + 'pilus': ['lupis', 'pilus'], + 'pim': ['imp', 'pim'], + 'pimelate': ['ampelite', 'pimelate'], + 'pimento': ['emption', 'pimento'], + 'pimenton': ['imponent', 'pimenton'], + 'pimola': ['lipoma', 'pimola', 'ploima'], + 'pimpish': ['impship', 'pimpish'], + 'pimplous': ['pimplous', 'pompilus', 'populism'], + 'pin': ['nip', 'pin'], + 'pina': ['nipa', 'pain', 'pani', 'pian', 'pina'], + 'pinaces': ['pinaces', 'pincase'], + 'pinachrome': ['epharmonic', 'pinachrome'], + 'pinacle': ['calepin', 'capelin', 'panicle', 'pelican', 'pinacle'], + 'pinacoid': ['diapnoic', 'pinacoid'], + 'pinal': ['lipan', 'pinal', 'plain'], + 'pinales': ['espinal', 'pinales', 'spaniel'], + 'pinaster': ['pinaster', 'pristane'], + 'pincase': ['pinaces', 'pincase'], + 'pincer': ['pincer', 'prince'], + 'pincerlike': ['pincerlike', 'princelike'], + 'pincers': ['encrisp', 'pincers'], + 'pinchbelly': ['bellypinch', 'pinchbelly'], + 'pinche': ['phenic', 'pinche'], + 'pincher': ['nephric', 'phrenic', 'pincher'], + 'pincushion': ['nuncioship', 'pincushion'], + 'pinda': ['pidan', 'pinda'], + 'pindari': ['pindari', 'pridian'], + 'pine': ['pien', 'pine'], + 'pineal': ['alpine', 'nepali', 'penial', 'pineal'], + 'pined': ['piend', 'pined'], + 'piner': ['piner', 'prine', 'repin', 'ripen'], + 'pinery': ['pernyi', 'pinery'], + 'pingler': ['pingler', 'pringle'], + 'pinhold': ['dolphin', 'pinhold'], + 'pinhole': ['lophine', 'pinhole'], + 'pinite': ['pinite', 'tiepin'], + 'pinker': ['perkin', 'pinker'], + 'pinking': ['kingpin', 'pinking'], + 'pinkish': ['kinship', 'pinkish'], + 'pinlock': ['lockpin', 'pinlock'], + 'pinnacle': ['pannicle', 'pinnacle'], + 'pinnae': ['nanpie', 'pennia', 'pinnae'], + 'pinnate': ['piannet', 'pinnate'], + 'pinnatopectinate': ['pectinatopinnate', 'pinnatopectinate'], + 'pinnet': ['pinnet', 'tenpin'], + 'pinnitarsal': ['intraspinal', 'pinnitarsal'], + 'pinnotere': ['interpone', 'peritenon', 'pinnotere', 'preintone'], + 'pinnothere': ['interphone', 'pinnothere'], + 'pinnula': ['pinnula', 'unplain'], + 'pinnulated': ['pennatulid', 'pinnulated'], + 'pinochle': ['phenolic', 'pinochle'], + 'pinole': ['pinole', 'pleion'], + 'pinolia': ['apiolin', 'pinolia'], + 'pinscher': ['phrenics', 'pinscher'], + 'pinta': ['inapt', 'paint', 'pinta'], + 'pintail': ['pintail', 'tailpin'], + 'pintano': ['opinant', 'pintano'], + 'pinte': ['inept', 'pinte'], + 'pinto': ['pinto', 'point'], + 'pintura': ['pintura', 'puritan', 'uptrain'], + 'pinulus': ['lupinus', 'pinulus'], + 'piny': ['piny', 'pyin'], + 'pinyl': ['pinyl', 'pliny'], + 'pioneer': ['pereion', 'pioneer'], + 'pioted': ['pioted', 'podite'], + 'pipa': ['paip', 'pipa'], + 'pipal': ['palpi', 'pipal'], + 'piperno': ['piperno', 'propine'], + 'pique': ['equip', 'pique'], + 'pir': ['pir', 'rip'], + 'piracy': ['cypria', 'picary', 'piracy'], + 'piranha': ['pharian', 'piranha'], + 'piratess': ['piratess', 'serapist', 'tarsipes'], + 'piratically': ['capillarity', 'piratically'], + 'piraty': ['parity', 'piraty'], + 'pirene': ['neiper', 'perine', 'pirene', 'repine'], + 'pirssonite': ['pirssonite', 'trispinose'], + 'pisaca': ['capias', 'pisaca'], + 'pisan': ['pisan', 'sapin', 'spina'], + 'pisanite': ['pianiste', 'pisanite'], + 'piscation': ['panoistic', 'piscation'], + 'piscatorial': ['paracolitis', 'piscatorial'], + 'piscian': ['panisic', 'piscian', 'piscina', 'sinapic'], + 'pisciferous': ['pisciferous', 'spiciferous'], + 'pisciform': ['pisciform', 'spiciform'], + 'piscina': ['panisic', 'piscian', 'piscina', 'sinapic'], + 'pisco': ['copis', 'pisco'], + 'pise': ['pise', 'sipe'], + 'pish': ['pish', 'ship'], + 'pisk': ['pisk', 'skip'], + 'pisky': ['pisky', 'spiky'], + 'pismire': ['pismire', 'primsie'], + 'pisonia': ['pisonia', 'sinopia'], + 'pist': ['pist', 'spit'], + 'pistache': ['paschite', 'pastiche', 'pistache', 'scaphite'], + 'pistacite': ['epistatic', 'pistacite'], + 'pistareen': ['pistareen', 'sparteine'], + 'pistillar': ['pillarist', 'pistillar'], + 'pistillary': ['pistillary', 'spiritally'], + 'pistle': ['pistle', 'stipel'], + 'pistol': ['pistol', 'postil', 'spoilt'], + 'pistoleer': ['epistoler', 'peristole', 'perseitol', 'pistoleer'], + 'pit': ['pit', 'tip'], + 'pita': ['atip', 'pita'], + 'pitapat': ['apitpat', 'pitapat'], + 'pitarah': ['pitarah', 'taphria'], + 'pitcher': ['pitcher', 'repitch'], + 'pitchout': ['outpitch', 'pitchout'], + 'pitchy': ['phytic', 'pitchy', 'pythic', 'typhic'], + 'pith': ['phit', 'pith'], + 'pithecan': ['haptenic', 'pantheic', 'pithecan'], + 'pithole': ['hoplite', 'pithole'], + 'pithsome': ['mephisto', 'pithsome'], + 'pitless': ['pitless', 'tipless'], + 'pitman': ['pitman', 'tampin', 'tipman'], + 'pittancer': ['crepitant', 'pittancer'], + 'pittoid': ['pittoid', 'poditti'], + 'pityroid': ['pityroid', 'pyritoid'], + 'pivoter': ['overtip', 'pivoter'], + 'placate': ['epactal', 'placate'], + 'placation': ['pactional', 'pactolian', 'placation'], + 'place': ['capel', 'place'], + 'placebo': ['copable', 'placebo'], + 'placentalia': ['analeptical', 'placentalia'], + 'placentoma': ['complanate', 'placentoma'], + 'placer': ['carpel', 'parcel', 'placer'], + 'placode': ['lacepod', 'pedocal', 'placode'], + 'placoid': ['placoid', 'podalic', 'podical'], + 'placus': ['cuspal', 'placus'], + 'plagionite': ['pigeontail', 'plagionite'], + 'plague': ['plague', 'upgale'], + 'plaguer': ['earplug', 'graupel', 'plaguer'], + 'plaice': ['epical', 'piacle', 'plaice'], + 'plaid': ['alpid', 'plaid'], + 'plaidy': ['adipyl', 'plaidy'], + 'plain': ['lipan', 'pinal', 'plain'], + 'plainer': ['pearlin', 'plainer', 'praline'], + 'plaint': ['plaint', 'pliant'], + 'plaister': ['epistlar', 'pilaster', 'plaister', 'priestal'], + 'plaited': ['plaited', 'taliped'], + 'plaiter': ['partile', 'plaiter', 'replait'], + 'planate': ['planate', 'planeta', 'plantae', 'platane'], + 'planation': ['planation', 'platonian'], + 'plancheite': ['elephantic', 'plancheite'], + 'plane': ['alpen', 'nepal', 'panel', 'penal', 'plane'], + 'planer': ['parnel', 'planer', 'replan'], + 'planera': ['planera', 'preanal'], + 'planet': ['pantle', 'planet', 'platen'], + 'planeta': ['planate', 'planeta', 'plantae', 'platane'], + 'planetabler': ['planetabler', 'replantable'], + 'planetaria': ['parentalia', 'planetaria'], + 'planetoid': ['pelidnota', 'planetoid'], + 'planity': ['inaptly', 'planity', 'ptyalin'], + 'planker': ['planker', 'prankle'], + 'planta': ['planta', 'platan'], + 'plantae': ['planate', 'planeta', 'plantae', 'platane'], + 'planter': ['pantler', 'planter', 'replant'], + 'plap': ['lapp', 'palp', 'plap'], + 'plasher': ['plasher', 'spheral'], + 'plasm': ['plasm', 'psalm', 'slamp'], + 'plasma': ['lampas', 'plasma'], + 'plasmation': ['aminoplast', 'plasmation'], + 'plasmic': ['plasmic', 'psalmic'], + 'plasmode': ['malposed', 'plasmode'], + 'plasmodial': ['plasmodial', 'psalmodial'], + 'plasmodic': ['plasmodic', 'psalmodic'], + 'plasson': ['plasson', 'sponsal'], + 'plastein': ['panelist', 'pantelis', 'penalist', 'plastein'], + 'plaster': ['palster', 'persalt', 'plaster', 'psalter', 'spartle', 'stapler'], + 'plasterer': ['plasterer', 'replaster'], + 'plastery': ['plastery', 'psaltery'], + 'plasticine': ['cisplatine', 'plasticine'], + 'plastidome': ['plastidome', 'postmedial'], + 'plastinoid': ['palinodist', 'plastinoid'], + 'plastochrone': ['phrenocostal', 'plastochrone'], + 'plat': ['palt', 'plat'], + 'plataean': ['panatela', 'plataean'], + 'platan': ['planta', 'platan'], + 'platane': ['planate', 'planeta', 'plantae', 'platane'], + 'plate': ['leapt', + 'palet', + 'patel', + 'pelta', + 'petal', + 'plate', + 'pleat', + 'tepal'], + 'platea': ['aletap', 'palate', 'platea'], + 'plateless': ['petalless', 'plateless', 'pleatless'], + 'platelet': ['pallette', 'platelet'], + 'platelike': ['petallike', 'platelike'], + 'platen': ['pantle', 'planet', 'platen'], + 'plater': ['palter', 'plater'], + 'platerer': ['palterer', 'platerer'], + 'platery': ['apertly', 'peartly', 'platery', 'pteryla', 'taperly'], + 'platine': ['pantile', 'pentail', 'platine', 'talpine'], + 'platinochloric': ['chloroplatinic', 'platinochloric'], + 'platinous': ['platinous', 'pulsation'], + 'platode': ['platode', 'tadpole'], + 'platoid': ['platoid', 'talpoid'], + 'platonian': ['planation', 'platonian'], + 'platter': ['partlet', 'platter', 'prattle'], + 'platy': ['aptly', 'patly', 'platy', 'typal'], + 'platynite': ['patiently', 'platynite'], + 'platysternal': ['platysternal', 'transeptally'], + 'plaud': ['dupla', 'plaud'], + 'plaustral': ['palustral', 'plaustral'], + 'play': ['paly', 'play', 'pyal', 'pyla'], + 'playa': ['palay', 'playa'], + 'player': ['parley', 'pearly', 'player', 'replay'], + 'playgoer': ['playgoer', 'pylagore'], + 'playroom': ['myopolar', 'playroom'], + 'plea': ['leap', 'lepa', 'pale', 'peal', 'plea'], + 'pleach': ['chapel', 'lepcha', 'pleach'], + 'plead': ['padle', 'paled', 'pedal', 'plead'], + 'pleader': ['pearled', 'pedaler', 'pleader', 'replead'], + 'please': ['asleep', 'elapse', 'please'], + 'pleaser': ['pleaser', 'preseal', 'relapse'], + 'pleasure': ['pleasure', 'serpulae'], + 'pleasurer': ['pleasurer', 'reperusal'], + 'pleasurous': ['asperulous', 'pleasurous'], + 'pleat': ['leapt', + 'palet', + 'patel', + 'pelta', + 'petal', + 'plate', + 'pleat', + 'tepal'], + 'pleater': ['pearlet', 'pleater', 'prelate', 'ptereal', 'replate', 'repleat'], + 'pleatless': ['petalless', 'plateless', 'pleatless'], + 'plectre': ['plectre', 'prelect'], + 'pleion': ['pinole', 'pleion'], + 'pleionian': ['opalinine', 'pleionian'], + 'plenilunar': ['plenilunar', 'plurennial'], + 'plenty': ['pentyl', 'plenty'], + 'pleomorph': ['pleomorph', 'prophloem'], + 'pleon': ['pelon', 'pleon'], + 'pleonal': ['pallone', 'pleonal'], + 'pleonasm': ['neoplasm', 'pleonasm', 'polesman', 'splenoma'], + 'pleonast': ['lapstone', 'pleonast'], + 'pleonastic': ['neoplastic', 'pleonastic'], + 'pleroma': ['leproma', 'palermo', 'pleroma', 'polearm'], + 'plerosis': ['leprosis', 'plerosis'], + 'plerotic': ['petrolic', 'plerotic'], + 'plessor': ['plessor', 'preloss'], + 'plethora': ['plethora', 'traphole'], + 'plethoric': ['phloretic', 'plethoric'], + 'pleura': ['epural', 'perula', 'pleura'], + 'pleuric': ['luperci', 'pleuric'], + 'pleuropericardial': ['pericardiopleural', 'pleuropericardial'], + 'pleurotoma': ['pleurotoma', 'tropaeolum'], + 'pleurovisceral': ['pleurovisceral', 'visceropleural'], + 'pliancy': ['pliancy', 'pycnial'], + 'pliant': ['plaint', 'pliant'], + 'plica': ['pical', 'plica'], + 'plicately': ['callitype', 'plicately'], + 'plicater': ['particle', 'plicater', 'prelatic'], + 'plicator': ['plicator', 'tropical'], + 'plied': ['piled', 'plied'], + 'plier': ['peril', 'piler', 'plier'], + 'pliers': ['lisper', 'pliers', 'sirple', 'spiler'], + 'plies': ['piles', 'plies', 'slipe', 'spiel', 'spile'], + 'plighter': ['plighter', 'replight'], + 'plim': ['limp', 'pilm', 'plim'], + 'pliny': ['pinyl', 'pliny'], + 'pliosaur': ['liparous', 'pliosaur'], + 'pliosauridae': ['lepidosauria', 'pliosauridae'], + 'ploceidae': ['adipocele', 'cepolidae', 'ploceidae'], + 'ploceus': ['culpose', 'ploceus', 'upclose'], + 'plodder': ['plodder', 'proddle'], + 'ploima': ['lipoma', 'pimola', 'ploima'], + 'ploration': ['ploration', 'portional', 'prolation'], + 'plot': ['plot', 'polt'], + 'plotful': ['plotful', 'topfull'], + 'plotted': ['plotted', 'pottled'], + 'plotter': ['plotter', 'portlet'], + 'plousiocracy': ['plousiocracy', 'procaciously'], + 'plout': ['plout', 'pluto', 'poult'], + 'plouter': ['plouter', 'poulter'], + 'plovery': ['overply', 'plovery'], + 'plower': ['plower', 'replow'], + 'ploy': ['ploy', 'poly'], + 'plucker': ['plucker', 'puckrel'], + 'plug': ['gulp', 'plug'], + 'plum': ['lump', 'plum'], + 'pluma': ['ampul', 'pluma'], + 'plumbagine': ['impugnable', 'plumbagine'], + 'plumbic': ['plumbic', 'upclimb'], + 'plumed': ['dumple', 'plumed'], + 'plumeous': ['eumolpus', 'plumeous'], + 'plumer': ['lumper', 'plumer', 'replum', 'rumple'], + 'plumet': ['lumpet', 'plumet'], + 'pluminess': ['lumpiness', 'pluminess'], + 'plumy': ['lumpy', 'plumy'], + 'plunderer': ['plunderer', 'replunder'], + 'plunge': ['plunge', 'pungle'], + 'plup': ['plup', 'pulp'], + 'plurennial': ['plenilunar', 'plurennial'], + 'pluriseptate': ['perpetualist', 'pluriseptate'], + 'plutean': ['plutean', 'unpetal', 'unpleat'], + 'pluteus': ['pluteus', 'pustule'], + 'pluto': ['plout', 'pluto', 'poult'], + 'pluvine': ['pluvine', 'vulpine'], + 'plyer': ['plyer', 'reply'], + 'pneumatophony': ['pneumatophony', 'pneumonopathy'], + 'pneumohemothorax': ['hemopneumothorax', 'pneumohemothorax'], + 'pneumohydropericardium': ['hydropneumopericardium', 'pneumohydropericardium'], + 'pneumohydrothorax': ['hydropneumothorax', 'pneumohydrothorax'], + 'pneumonopathy': ['pneumatophony', 'pneumonopathy'], + 'pneumopyothorax': ['pneumopyothorax', 'pyopneumothorax'], + 'poach': ['chopa', 'phoca', 'poach'], + 'poachy': ['poachy', 'pochay'], + 'poales': ['aslope', 'poales'], + 'pob': ['bop', 'pob'], + 'pochay': ['poachy', 'pochay'], + 'poche': ['epoch', 'poche'], + 'pocketer': ['pocketer', 'repocket'], + 'poco': ['coop', 'poco'], + 'pocosin': ['opsonic', 'pocosin'], + 'poculation': ['copulation', 'poculation'], + 'pod': ['dop', 'pod'], + 'podalic': ['placoid', 'podalic', 'podical'], + 'podarthritis': ['podarthritis', 'traditorship'], + 'podial': ['podial', 'poliad'], + 'podical': ['placoid', 'podalic', 'podical'], + 'podiceps': ['podiceps', 'scopiped'], + 'podite': ['pioted', 'podite'], + 'poditti': ['pittoid', 'poditti'], + 'podler': ['podler', 'polder', 'replod'], + 'podley': ['deploy', 'podley'], + 'podobranchia': ['branchiopoda', 'podobranchia'], + 'podocephalous': ['cephalopodous', 'podocephalous'], + 'podophyllous': ['phyllopodous', 'podophyllous'], + 'podoscaph': ['podoscaph', 'scaphopod'], + 'podostomata': ['podostomata', 'stomatopoda'], + 'podostomatous': ['podostomatous', 'stomatopodous'], + 'podotheca': ['chaetopod', 'podotheca'], + 'podura': ['podura', 'uproad'], + 'poduran': ['pandour', 'poduran'], + 'poe': ['ope', 'poe'], + 'poem': ['mope', 'poem', 'pome'], + 'poemet': ['metope', 'poemet'], + 'poemlet': ['leptome', 'poemlet'], + 'poesy': ['poesy', 'posey', 'sepoy'], + 'poet': ['peto', 'poet', 'pote', 'tope'], + 'poetastrical': ['poetastrical', 'spectatorial'], + 'poetical': ['copalite', 'poetical'], + 'poeticism': ['impeticos', 'poeticism'], + 'poetics': ['poetics', 'septoic'], + 'poetly': ['peyotl', 'poetly'], + 'poetryless': ['poetryless', 'presystole'], + 'pogo': ['goop', 'pogo'], + 'poh': ['hop', 'pho', 'poh'], + 'poha': ['opah', 'paho', 'poha'], + 'pohna': ['phano', 'pohna'], + 'poiana': ['anopia', 'aponia', 'poiana'], + 'poietic': ['epiotic', 'poietic'], + 'poimenic': ['mincopie', 'poimenic'], + 'poinder': ['poinder', 'ponerid'], + 'point': ['pinto', 'point'], + 'pointel': ['pointel', 'pontile', 'topline'], + 'pointer': ['pointer', 'protein', 'pterion', 'repoint', 'tropine'], + 'pointlet': ['pentitol', 'pointlet'], + 'pointrel': ['pointrel', 'terpinol'], + 'poisonless': ['poisonless', 'solenopsis'], + 'poker': ['poker', 'proke'], + 'pol': ['lop', 'pol'], + 'polab': ['pablo', 'polab'], + 'polacre': ['capreol', 'polacre'], + 'polander': ['polander', 'ponderal', 'prenodal'], + 'polar': ['parol', 'polar', 'poral', 'proal'], + 'polarid': ['dipolar', 'polarid'], + 'polarimetry': ['polarimetry', 'premorality', 'temporarily'], + 'polaristic': ['polaristic', 'poristical', 'saprolitic'], + 'polarly': ['payroll', 'polarly'], + 'polder': ['podler', 'polder', 'replod'], + 'pole': ['lope', 'olpe', 'pole'], + 'polearm': ['leproma', 'palermo', 'pleroma', 'polearm'], + 'polecat': ['pacolet', 'polecat'], + 'polemic': ['compile', 'polemic'], + 'polemics': ['clipsome', 'polemics'], + 'polemist': ['milepost', 'polemist'], + 'polenta': ['lepanto', 'nepotal', 'petalon', 'polenta'], + 'poler': ['loper', 'poler'], + 'polesman': ['neoplasm', 'pleonasm', 'polesman', 'splenoma'], + 'polestar': ['petrosal', 'polestar'], + 'poliad': ['podial', 'poliad'], + 'polianite': ['epilation', 'polianite'], + 'policyholder': ['policyholder', 'polychloride'], + 'polio': ['polio', 'pooli'], + 'polis': ['polis', 'spoil'], + 'polished': ['depolish', 'polished'], + 'polisher': ['polisher', 'repolish'], + 'polistes': ['polistes', 'telopsis'], + 'politarch': ['carpolith', 'politarch', 'trophical'], + 'politicly': ['lipolytic', 'politicly'], + 'politics': ['colpitis', 'politics', 'psilotic'], + 'polk': ['klop', 'polk'], + 'pollenite': ['pellotine', 'pollenite'], + 'poller': ['poller', 'repoll'], + 'pollinate': ['pellation', 'pollinate'], + 'polo': ['loop', 'polo', 'pool'], + 'poloist': ['loopist', 'poloist', 'topsoil'], + 'polonia': ['apionol', 'polonia'], + 'polos': ['polos', 'sloop', 'spool'], + 'polt': ['plot', 'polt'], + 'poly': ['ploy', 'poly'], + 'polyacid': ['polyacid', 'polyadic'], + 'polyactinal': ['pactionally', 'polyactinal'], + 'polyadic': ['polyacid', 'polyadic'], + 'polyarchist': ['chiroplasty', 'polyarchist'], + 'polychloride': ['policyholder', 'polychloride'], + 'polychroism': ['polychroism', 'polyorchism'], + 'polycitral': ['polycitral', 'tropically'], + 'polycodium': ['lycopodium', 'polycodium'], + 'polycotyl': ['collotypy', 'polycotyl'], + 'polyeidic': ['polyeidic', 'polyideic'], + 'polyeidism': ['polyeidism', 'polyideism'], + 'polyester': ['polyester', 'proselyte'], + 'polygamodioecious': ['dioeciopolygamous', 'polygamodioecious'], + 'polyhalite': ['paleolithy', 'polyhalite', 'polythelia'], + 'polyideic': ['polyeidic', 'polyideic'], + 'polyideism': ['polyeidism', 'polyideism'], + 'polymastic': ['myoplastic', 'polymastic'], + 'polymasty': ['myoplasty', 'polymasty'], + 'polymere': ['employer', 'polymere'], + 'polymeric': ['micropyle', 'polymeric'], + 'polymetochia': ['homeotypical', 'polymetochia'], + 'polymnia': ['olympian', 'polymnia'], + 'polymyodi': ['polymyodi', 'polymyoid'], + 'polymyoid': ['polymyodi', 'polymyoid'], + 'polyorchism': ['polychroism', 'polyorchism'], + 'polyp': ['loppy', 'polyp'], + 'polyphemian': ['lymphopenia', 'polyphemian'], + 'polyphonist': ['polyphonist', 'psilophyton'], + 'polypite': ['lipotype', 'polypite'], + 'polyprene': ['polyprene', 'propylene'], + 'polypterus': ['polypterus', 'suppletory'], + 'polysomitic': ['myocolpitis', 'polysomitic'], + 'polyspore': ['polyspore', 'prosopyle'], + 'polythelia': ['paleolithy', 'polyhalite', 'polythelia'], + 'polythene': ['polythene', 'telephony'], + 'polyuric': ['croupily', 'polyuric'], + 'pom': ['mop', 'pom'], + 'pomade': ['apedom', 'pomade'], + 'pomane': ['mopane', 'pomane'], + 'pome': ['mope', 'poem', 'pome'], + 'pomeranian': ['pomeranian', 'praenomina'], + 'pomerium': ['emporium', 'pomerium', 'proemium'], + 'pomey': ['myope', 'pomey'], + 'pomo': ['moop', 'pomo'], + 'pomonal': ['lampoon', 'pomonal'], + 'pompilus': ['pimplous', 'pompilus', 'populism'], + 'pomster': ['pomster', 'stomper'], + 'ponca': ['capon', 'ponca'], + 'ponce': ['copen', 'ponce'], + 'ponchoed': ['chenopod', 'ponchoed'], + 'poncirus': ['coprinus', 'poncirus'], + 'ponderal': ['polander', 'ponderal', 'prenodal'], + 'ponderate': ['ponderate', 'predonate'], + 'ponderation': ['ponderation', 'predonation'], + 'ponderer': ['ponderer', 'reponder'], + 'pondfish': ['fishpond', 'pondfish'], + 'pone': ['nope', 'open', 'peon', 'pone'], + 'ponerid': ['poinder', 'ponerid'], + 'poneroid': ['poneroid', 'porodine'], + 'poney': ['peony', 'poney'], + 'ponica': ['aponic', 'ponica'], + 'ponier': ['opiner', 'orpine', 'ponier'], + 'pontederia': ['pontederia', 'proteidean'], + 'pontee': ['nepote', 'pontee', 'poteen'], + 'pontes': ['pontes', 'posnet'], + 'ponticular': ['ponticular', 'untropical'], + 'pontificalia': ['palification', 'pontificalia'], + 'pontile': ['pointel', 'pontile', 'topline'], + 'pontonier': ['entropion', 'pontonier', 'prenotion'], + 'pontus': ['pontus', 'unspot', 'unstop'], + 'pooch': ['choop', 'pooch'], + 'pooh': ['hoop', 'phoo', 'pooh'], + 'pooka': ['oopak', 'pooka'], + 'pool': ['loop', 'polo', 'pool'], + 'pooler': ['looper', 'pooler'], + 'pooli': ['polio', 'pooli'], + 'pooly': ['loopy', 'pooly'], + 'poon': ['noop', 'poon'], + 'poonac': ['acopon', 'poonac'], + 'poonga': ['apogon', 'poonga'], + 'poor': ['poor', 'proo'], + 'poot': ['poot', 'toop', 'topo'], + 'pope': ['pepo', 'pope'], + 'popeler': ['peopler', 'popeler'], + 'popery': ['popery', 'pyrope'], + 'popgun': ['oppugn', 'popgun'], + 'popian': ['oppian', 'papion', 'popian'], + 'popish': ['popish', 'shippo'], + 'popliteal': ['papillote', 'popliteal'], + 'poppel': ['poppel', 'popple'], + 'popple': ['poppel', 'popple'], + 'populism': ['pimplous', 'pompilus', 'populism'], + 'populus': ['populus', 'pulpous'], + 'poral': ['parol', 'polar', 'poral', 'proal'], + 'porcate': ['coperta', 'pectora', 'porcate'], + 'porcelain': ['oliprance', 'porcelain'], + 'porcelanite': ['porcelanite', 'praelection'], + 'porcula': ['copular', 'croupal', 'cupolar', 'porcula'], + 'pore': ['pore', 'rope'], + 'pored': ['doper', 'pedro', 'pored'], + 'porelike': ['porelike', 'ropelike'], + 'porer': ['porer', 'prore', 'roper'], + 'porge': ['grope', 'porge'], + 'porger': ['groper', 'porger'], + 'poriness': ['poriness', 'pression', 'ropiness'], + 'poring': ['poring', 'roping'], + 'poristical': ['polaristic', 'poristical', 'saprolitic'], + 'porites': ['periost', 'porites', 'reposit', 'riposte'], + 'poritidae': ['poritidae', 'triopidae'], + 'porker': ['porker', 'proker'], + 'pornerastic': ['cotranspire', 'pornerastic'], + 'porodine': ['poneroid', 'porodine'], + 'poros': ['poros', 'proso', 'sopor', 'spoor'], + 'porosity': ['isotropy', 'porosity'], + 'porotic': ['porotic', 'portico'], + 'porpentine': ['porpentine', 'prepontine'], + 'porphine': ['hornpipe', 'porphine'], + 'porphyrous': ['porphyrous', 'pyrophorus'], + 'porrection': ['correption', 'porrection'], + 'porret': ['porret', 'porter', 'report', 'troper'], + 'porta': ['aport', 'parto', 'porta'], + 'portail': ['portail', 'toprail'], + 'portal': ['patrol', 'portal', 'tropal'], + 'portance': ['coparent', 'portance'], + 'ported': ['deport', 'ported', 'redtop'], + 'portend': ['portend', 'protend'], + 'porteno': ['porteno', 'protone'], + 'portension': ['portension', 'protension'], + 'portent': ['portent', 'torpent'], + 'portentous': ['notopterus', 'portentous'], + 'porter': ['porret', 'porter', 'report', 'troper'], + 'porterage': ['porterage', 'reportage'], + 'portership': ['portership', 'pretorship'], + 'portfire': ['portfire', 'profiter'], + 'portia': ['portia', 'tapiro'], + 'portico': ['porotic', 'portico'], + 'portify': ['portify', 'torpify'], + 'portional': ['ploration', 'portional', 'prolation'], + 'portioner': ['portioner', 'reportion'], + 'portlet': ['plotter', 'portlet'], + 'portly': ['portly', 'protyl', 'tropyl'], + 'porto': ['porto', 'proto', 'troop'], + 'portoise': ['isotrope', 'portoise'], + 'portolan': ['portolan', 'pronotal'], + 'portor': ['portor', 'torpor'], + 'portray': ['parroty', 'portray', 'tropary'], + 'portrayal': ['parlatory', 'portrayal'], + 'portside': ['dipteros', 'portside'], + 'portsider': ['portsider', 'postrider'], + 'portunidae': ['depuration', 'portunidae'], + 'portunus': ['outspurn', 'portunus'], + 'pory': ['pory', 'pyro', 'ropy'], + 'posca': ['posca', 'scopa'], + 'pose': ['epos', 'peso', 'pose', 'sope'], + 'poser': ['poser', 'prose', 'ropes', 'spore'], + 'poseur': ['poseur', 'pouser', 'souper', 'uprose'], + 'posey': ['poesy', 'posey', 'sepoy'], + 'posh': ['phos', 'posh', 'shop', 'soph'], + 'posingly': ['posingly', 'spongily'], + 'position': ['position', 'sopition'], + 'positional': ['positional', 'spoilation', 'spoliation'], + 'positioned': ['deposition', 'positioned'], + 'positioner': ['positioner', 'reposition'], + 'positron': ['notropis', 'positron', 'sorption'], + 'positum': ['positum', 'utopism'], + 'posnet': ['pontes', 'posnet'], + 'posse': ['posse', 'speos'], + 'possessioner': ['possessioner', 'repossession'], + 'post': ['post', 'spot', 'stop', 'tops'], + 'postage': ['gestapo', 'postage'], + 'postaortic': ['parostotic', 'postaortic'], + 'postdate': ['despotat', 'postdate'], + 'posted': ['despot', 'posted'], + 'posteen': ['pentose', 'posteen'], + 'poster': ['poster', 'presto', 'repost', 'respot', 'stoper'], + 'posterial': ['posterial', 'saprolite'], + 'posterior': ['posterior', 'repositor'], + 'posterish': ['posterish', 'prothesis', 'sophister', 'storeship', 'tephrosis'], + 'posteroinferior': ['inferoposterior', 'posteroinferior'], + 'posterosuperior': ['posterosuperior', 'superoposterior'], + 'postgenial': ['postgenial', 'spangolite'], + 'posthaste': ['posthaste', 'tosephtas'], + 'postic': ['copist', 'coptis', 'optics', 'postic'], + 'postical': ['postical', 'slipcoat'], + 'postil': ['pistol', 'postil', 'spoilt'], + 'posting': ['posting', 'stoping'], + 'postischial': ['postischial', 'sophistical'], + 'postless': ['postless', 'spotless', 'stopless'], + 'postlike': ['postlike', 'spotlike'], + 'postman': ['postman', 'topsman'], + 'postmedial': ['plastidome', 'postmedial'], + 'postnaris': ['postnaris', 'sopranist'], + 'postnominal': ['monoplanist', 'postnominal'], + 'postrider': ['portsider', 'postrider'], + 'postsacral': ['postsacral', 'sarcoplast'], + 'postsign': ['postsign', 'signpost'], + 'postthoracic': ['octastrophic', 'postthoracic'], + 'postulata': ['autoplast', 'postulata'], + 'postural': ['postural', 'pulsator', 'sportula'], + 'posture': ['petrous', 'posture', 'proetus', 'proteus', 'septuor', 'spouter'], + 'posturer': ['posturer', 'resprout', 'sprouter'], + 'postuterine': ['postuterine', 'pretentious'], + 'postwar': ['postwar', 'sapwort'], + 'postward': ['drawstop', 'postward'], + 'postwoman': ['postwoman', 'womanpost'], + 'posy': ['opsy', 'posy'], + 'pot': ['opt', 'pot', 'top'], + 'potable': ['optable', 'potable'], + 'potableness': ['optableness', 'potableness'], + 'potash': ['pashto', 'pathos', 'potash'], + 'potass': ['potass', 'topass'], + 'potate': ['aptote', 'optate', 'potate', 'teapot'], + 'potation': ['optation', 'potation'], + 'potative': ['optative', 'potative'], + 'potator': ['potator', 'taproot'], + 'pote': ['peto', 'poet', 'pote', 'tope'], + 'poteen': ['nepote', 'pontee', 'poteen'], + 'potential': ['peltation', 'potential'], + 'poter': ['poter', 'prote', 'repot', 'tepor', 'toper', 'trope'], + 'poteye': ['peyote', 'poteye'], + 'pothouse': ['housetop', 'pothouse'], + 'poticary': ['cyrtopia', 'poticary'], + 'potifer': ['firetop', 'potifer'], + 'potion': ['option', 'potion'], + 'potlatch': ['potlatch', 'tolpatch'], + 'potlike': ['kitlope', 'potlike', 'toplike'], + 'potmaker': ['potmaker', 'topmaker'], + 'potmaking': ['potmaking', 'topmaking'], + 'potman': ['potman', 'tampon', 'topman'], + 'potometer': ['optometer', 'potometer'], + 'potstick': ['potstick', 'tipstock'], + 'potstone': ['potstone', 'topstone'], + 'pottled': ['plotted', 'pottled'], + 'pouce': ['coupe', 'pouce'], + 'poucer': ['couper', 'croupe', 'poucer', 'recoup'], + 'pouch': ['choup', 'pouch'], + 'poulpe': ['poulpe', 'pupelo'], + 'poult': ['plout', 'pluto', 'poult'], + 'poulter': ['plouter', 'poulter'], + 'poultice': ['epulotic', 'poultice'], + 'pounce': ['pounce', 'uncope'], + 'pounder': ['pounder', 'repound', 'unroped'], + 'pour': ['pour', 'roup'], + 'pourer': ['pourer', 'repour', 'rouper'], + 'pouser': ['poseur', 'pouser', 'souper', 'uprose'], + 'pout': ['pout', 'toup'], + 'pouter': ['pouter', 'roupet', 'troupe'], + 'pow': ['pow', 'wop'], + 'powder': ['powder', 'prowed'], + 'powderer': ['powderer', 'repowder'], + 'powerful': ['powerful', 'upflower'], + 'praam': ['param', 'parma', 'praam'], + 'practitional': ['antitropical', 'practitional'], + 'prad': ['pard', 'prad'], + 'pradeep': ['papered', 'pradeep'], + 'praecox': ['exocarp', 'praecox'], + 'praelabrum': ['praelabrum', 'preambular'], + 'praelection': ['porcelanite', 'praelection'], + 'praelector': ['praelector', 'receptoral'], + 'praenomina': ['pomeranian', 'praenomina'], + 'praepostor': ['praepostor', 'pterospora'], + 'praesphenoid': ['nephropsidae', 'praesphenoid'], + 'praetor': ['praetor', 'prorate'], + 'praetorian': ['praetorian', 'reparation'], + 'praise': ['aspire', 'paries', 'praise', 'sirpea', 'spirea'], + 'praiser': ['aspirer', 'praiser', 'serpari'], + 'praising': ['aspiring', 'praising', 'singarip'], + 'praisingly': ['aspiringly', 'praisingly'], + 'praline': ['pearlin', 'plainer', 'praline'], + 'pram': ['pram', 'ramp'], + 'prandial': ['diplanar', 'prandial'], + 'prankle': ['planker', 'prankle'], + 'prase': ['asper', 'parse', 'prase', 'spaer', 'spare', 'spear'], + 'praseolite': ['periosteal', 'praseolite'], + 'prasine': ['persian', 'prasine', 'saprine'], + 'prasinous': ['prasinous', 'psaronius'], + 'prasoid': ['prasoid', 'sparoid'], + 'prasophagous': ['prasophagous', 'saprophagous'], + 'prat': ['part', 'prat', 'rapt', 'tarp', 'trap'], + 'prate': ['apert', 'pater', 'peart', 'prate', 'taper', 'terap'], + 'prater': ['parter', 'prater'], + 'pratey': ['petary', 'pratey'], + 'pratfall': ['pratfall', 'trapfall'], + 'pratincoline': ['nonpearlitic', 'pratincoline'], + 'pratincolous': ['patroclinous', 'pratincolous'], + 'prattle': ['partlet', 'platter', 'prattle'], + 'prau': ['prau', 'rupa'], + 'prawner': ['prawner', 'prewarn'], + 'prayer': ['prayer', 'repray'], + 'preach': ['aperch', 'eparch', 'percha', 'preach'], + 'preacher': ['preacher', 'repreach'], + 'preaching': ['engraphic', 'preaching'], + 'preachman': ['marchpane', 'preachman'], + 'preachy': ['eparchy', 'preachy'], + 'preacid': ['epacrid', 'peracid', 'preacid'], + 'preact': ['carpet', 'peract', 'preact'], + 'preaction': ['preaction', 'precation', 'recaption'], + 'preactive': ['preactive', 'precative'], + 'preactively': ['preactively', 'precatively'], + 'preacute': ['peracute', 'preacute'], + 'preadmonition': ['demipronation', 'preadmonition', 'predomination'], + 'preadorn': ['pardoner', 'preadorn'], + 'preadventure': ['peradventure', 'preadventure'], + 'preallably': ['ballplayer', 'preallably'], + 'preallow': ['preallow', 'walloper'], + 'preamble': ['peramble', 'preamble'], + 'preambular': ['praelabrum', 'preambular'], + 'preambulate': ['perambulate', 'preambulate'], + 'preambulation': ['perambulation', 'preambulation'], + 'preambulatory': ['perambulatory', 'preambulatory'], + 'preanal': ['planera', 'preanal'], + 'prearm': ['prearm', 'ramper'], + 'preascitic': ['accipitres', 'preascitic'], + 'preauditory': ['preauditory', 'repudiatory'], + 'prebacillary': ['bicarpellary', 'prebacillary'], + 'prebasal': ['parsable', 'prebasal', 'sparable'], + 'prebend': ['perbend', 'prebend'], + 'prebeset': ['bepester', 'prebeset'], + 'prebid': ['bedrip', 'prebid'], + 'precant': ['carpent', 'precant'], + 'precantation': ['actinopteran', 'precantation'], + 'precast': ['precast', 'spectra'], + 'precation': ['preaction', 'precation', 'recaption'], + 'precative': ['preactive', 'precative'], + 'precatively': ['preactively', 'precatively'], + 'precaution': ['precaution', 'unoperatic'], + 'precautional': ['inoperculata', 'precautional'], + 'precedable': ['deprecable', 'precedable'], + 'preceder': ['preceder', 'precreed'], + 'precent': ['percent', 'precent'], + 'precept': ['percept', 'precept'], + 'preception': ['perception', 'preception'], + 'preceptive': ['perceptive', 'preceptive'], + 'preceptively': ['perceptively', 'preceptively'], + 'preceptual': ['perceptual', 'preceptual'], + 'preceptually': ['perceptually', 'preceptually'], + 'prechloric': ['perchloric', 'prechloric'], + 'prechoose': ['prechoose', 'rheoscope'], + 'precipitate': ['peripatetic', 'precipitate'], + 'precipitator': ['precipitator', 'prepatriotic'], + 'precis': ['crepis', 'cripes', 'persic', 'precis', 'spicer'], + 'precise': ['precise', 'scripee'], + 'precisian': ['periscian', 'precisian'], + 'precision': ['coinspire', 'precision'], + 'precitation': ['actinopteri', 'crepitation', 'precitation'], + 'precite': ['ereptic', 'precite', 'receipt'], + 'precited': ['decrepit', 'depicter', 'precited'], + 'preclose': ['perclose', 'preclose'], + 'preclusion': ['perculsion', 'preclusion'], + 'preclusive': ['perculsive', 'preclusive'], + 'precoil': ['peloric', 'precoil'], + 'precompound': ['percompound', 'precompound'], + 'precondense': ['precondense', 'respondence'], + 'preconnubial': ['preconnubial', 'pronunciable'], + 'preconsole': ['necropoles', 'preconsole'], + 'preconsultor': ['preconsultor', 'supercontrol'], + 'precontest': ['precontest', 'torpescent'], + 'precopy': ['coppery', 'precopy'], + 'precostal': ['ceroplast', 'precostal'], + 'precredit': ['precredit', 'predirect', 'repredict'], + 'precreditor': ['precreditor', 'predirector'], + 'precreed': ['preceder', 'precreed'], + 'precurrent': ['percurrent', 'precurrent'], + 'precursory': ['percursory', 'precursory'], + 'precyst': ['precyst', 'sceptry', 'spectry'], + 'predata': ['adapter', 'predata', 'readapt'], + 'predate': ['padtree', 'predate', 'tapered'], + 'predatism': ['predatism', 'spermatid'], + 'predative': ['deprivate', 'predative'], + 'predator': ['predator', 'protrade', 'teardrop'], + 'preday': ['pedary', 'preday'], + 'predealer': ['predealer', 'repleader'], + 'predecree': ['creepered', 'predecree'], + 'predefect': ['perfected', 'predefect'], + 'predeication': ['depreciation', 'predeication'], + 'prederive': ['prederive', 'redeprive'], + 'predespair': ['disprepare', 'predespair'], + 'predestine': ['predestine', 'presidente'], + 'predetail': ['pedaliter', 'predetail'], + 'predevotion': ['overpointed', 'predevotion'], + 'predial': ['pedrail', 'predial'], + 'prediastolic': ['prediastolic', 'psiloceratid'], + 'predication': ['predication', 'procidentia'], + 'predictory': ['cryptodire', 'predictory'], + 'predirect': ['precredit', 'predirect', 'repredict'], + 'predirector': ['precreditor', 'predirector'], + 'prediscretion': ['prediscretion', 'redescription'], + 'predislike': ['predislike', 'spiderlike'], + 'predivinable': ['indeprivable', 'predivinable'], + 'predominate': ['pentameroid', 'predominate'], + 'predomination': ['demipronation', 'preadmonition', 'predomination'], + 'predonate': ['ponderate', 'predonate'], + 'predonation': ['ponderation', 'predonation'], + 'predoubter': ['predoubter', 'preobtrude'], + 'predrive': ['depriver', 'predrive'], + 'preen': ['neper', 'preen', 'repen'], + 'prefactor': ['aftercrop', 'prefactor'], + 'prefator': ['forepart', 'prefator'], + 'prefavorite': ['perforative', 'prefavorite'], + 'prefect': ['perfect', 'prefect'], + 'prefectly': ['perfectly', 'prefectly'], + 'prefervid': ['perfervid', 'prefervid'], + 'prefiction': ['prefiction', 'proficient'], + 'prefoliation': ['perfoliation', 'prefoliation'], + 'preform': ['perform', 'preform'], + 'preformant': ['performant', 'preformant'], + 'preformative': ['performative', 'preformative'], + 'preformism': ['misperform', 'preformism'], + 'pregainer': ['peregrina', 'pregainer'], + 'prehandicap': ['handicapper', 'prehandicap'], + 'prehaps': ['perhaps', 'prehaps'], + 'prehazard': ['perhazard', 'prehazard'], + 'preheal': ['preheal', 'rephael'], + 'preheat': ['haptere', 'preheat'], + 'preheated': ['heartdeep', 'preheated'], + 'prehension': ['hesperinon', 'prehension'], + 'prehnite': ['nephrite', 'prehnite', 'trephine'], + 'prehnitic': ['nephritic', 'phrenitic', 'prehnitic'], + 'prehuman': ['prehuman', 'unhamper'], + 'preimpose': ['perispome', 'preimpose'], + 'preindicate': ['parenticide', 'preindicate'], + 'preinduce': ['preinduce', 'unpierced'], + 'preintone': ['interpone', 'peritenon', 'pinnotere', 'preintone'], + 'prelabrum': ['prelabrum', 'prelumbar'], + 'prelacteal': ['carpellate', 'parcellate', 'prelacteal'], + 'prelate': ['pearlet', 'pleater', 'prelate', 'ptereal', 'replate', 'repleat'], + 'prelatehood': ['heteropodal', 'prelatehood'], + 'prelatic': ['particle', 'plicater', 'prelatic'], + 'prelatical': ['capitellar', 'prelatical'], + 'prelation': ['prelation', 'rantipole'], + 'prelatism': ['palmister', 'prelatism'], + 'prelease': ['eelspear', 'prelease'], + 'prelect': ['plectre', 'prelect'], + 'prelection': ['perlection', 'prelection'], + 'prelim': ['limper', 'prelim', 'rimple'], + 'prelingual': ['perlingual', 'prelingual'], + 'prelocate': ['percolate', 'prelocate'], + 'preloss': ['plessor', 'preloss'], + 'preludial': ['dipleural', 'preludial'], + 'prelumbar': ['prelabrum', 'prelumbar'], + 'prelusion': ['prelusion', 'repulsion'], + 'prelusive': ['prelusive', 'repulsive'], + 'prelusively': ['prelusively', 'repulsively'], + 'prelusory': ['prelusory', 'repulsory'], + 'premate': ['premate', 'tempera'], + 'premedia': ['epiderma', 'premedia'], + 'premedial': ['epidermal', 'impleader', 'premedial'], + 'premedication': ['pedometrician', 'premedication'], + 'premenace': ['permeance', 'premenace'], + 'premerit': ['premerit', 'preremit', 'repermit'], + 'premial': ['impaler', 'impearl', 'lempira', 'premial'], + 'premiant': ['imperant', 'pairment', 'partimen', 'premiant', 'tripeman'], + 'premiate': ['imperate', 'premiate'], + 'premier': ['premier', 'reprime'], + 'premious': ['imposure', 'premious'], + 'premise': ['emprise', 'imprese', 'premise', 'spireme'], + 'premiss': ['impress', 'persism', 'premiss'], + 'premixture': ['permixture', 'premixture'], + 'premodel': ['leperdom', 'premodel'], + 'premolar': ['premolar', 'premoral'], + 'premold': ['meldrop', 'premold'], + 'premonetary': ['premonetary', 'pyranometer'], + 'premoral': ['premolar', 'premoral'], + 'premorality': ['polarimetry', 'premorality', 'temporarily'], + 'premosaic': ['paroecism', 'premosaic'], + 'premover': ['premover', 'prevomer'], + 'premusical': ['premusical', 'superclaim'], + 'prenasal': ['pernasal', 'prenasal'], + 'prenatal': ['parental', 'paternal', 'prenatal'], + 'prenatalist': ['intraseptal', 'paternalist', 'prenatalist'], + 'prenatally': ['parentally', 'paternally', 'prenatally'], + 'prenative': ['interpave', 'prenative'], + 'prender': ['prender', 'prendre'], + 'prendre': ['prender', 'prendre'], + 'prenodal': ['polander', 'ponderal', 'prenodal'], + 'prenominical': ['nonempirical', 'prenominical'], + 'prenotice': ['prenotice', 'reception'], + 'prenotion': ['entropion', 'pontonier', 'prenotion'], + 'prentice': ['piercent', 'prentice'], + 'preobtrude': ['predoubter', 'preobtrude'], + 'preocular': ['opercular', 'preocular'], + 'preominate': ['permeation', 'preominate'], + 'preopen': ['preopen', 'propene'], + 'preopinion': ['preopinion', 'prionopine'], + 'preoption': ['preoption', 'protopine'], + 'preoral': ['peroral', 'preoral'], + 'preorally': ['perorally', 'preorally'], + 'prep': ['prep', 'repp'], + 'prepalatine': ['periplaneta', 'prepalatine'], + 'prepare': ['paperer', 'perpera', 'prepare', 'repaper'], + 'prepatriotic': ['precipitator', 'prepatriotic'], + 'prepay': ['papery', 'prepay', 'yapper'], + 'preplot': ['preplot', 'toppler'], + 'prepollent': ['prepollent', 'propellent'], + 'prepontine': ['porpentine', 'prepontine'], + 'prepositure': ['peripterous', 'prepositure'], + 'prepuce': ['prepuce', 'upcreep'], + 'prerational': ['prerational', 'proletarian'], + 'prerealization': ['prerealization', 'proletarianize'], + 'prereceive': ['prereceive', 'reperceive'], + 'prerecital': ['perirectal', 'prerecital'], + 'prereduction': ['interproduce', 'prereduction'], + 'prerefer': ['prerefer', 'reprefer'], + 'prereform': ['performer', 'prereform', 'reperform'], + 'preremit': ['premerit', 'preremit', 'repermit'], + 'prerental': ['prerental', 'replanter'], + 'prerich': ['chirper', 'prerich'], + 'preromantic': ['improcreant', 'preromantic'], + 'presage': ['asperge', 'presage'], + 'presager': ['asperger', 'presager'], + 'presay': ['presay', 'speary'], + 'prescapular': ['prescapular', 'supercarpal'], + 'prescient': ['prescient', 'reinspect'], + 'prescientific': ['interspecific', 'prescientific'], + 'prescribe': ['perscribe', 'prescribe'], + 'prescutal': ['prescutal', 'scalpture'], + 'preseal': ['pleaser', 'preseal', 'relapse'], + 'preseason': ['parsonese', 'preseason'], + 'presell': ['presell', 'respell', 'speller'], + 'present': ['penster', 'present', 'serpent', 'strepen'], + 'presenter': ['presenter', 'represent'], + 'presential': ['alpestrine', 'episternal', 'interlapse', 'presential'], + 'presentist': ['persistent', 'presentist', 'prettiness'], + 'presentive': ['presentive', 'pretensive', 'vespertine'], + 'presentively': ['presentively', 'pretensively'], + 'presentiveness': ['presentiveness', 'pretensiveness'], + 'presently': ['presently', 'serpently'], + 'preserve': ['perverse', 'preserve'], + 'preset': ['pester', 'preset', 'restep', 'streep'], + 'preshare': ['preshare', 'rephrase'], + 'preship': ['preship', 'shipper'], + 'preshortage': ['preshortage', 'stereograph'], + 'preside': ['perseid', 'preside'], + 'presidencia': ['acipenserid', 'presidencia'], + 'president': ['president', 'serpentid'], + 'presidente': ['predestine', 'presidente'], + 'presider': ['presider', 'serriped'], + 'presign': ['presign', 'springe'], + 'presignal': ['espringal', 'presignal', 'relapsing'], + 'presimian': ['mainprise', 'presimian'], + 'presley': ['presley', 'sleepry'], + 'presser': ['presser', 'repress'], + 'pression': ['poriness', 'pression', 'ropiness'], + 'pressive': ['pressive', 'viperess'], + 'prest': ['prest', 'spret'], + 'prestable': ['beplaster', 'prestable'], + 'prestant': ['prestant', 'transept'], + 'prestate': ['prestate', 'pretaste'], + 'presto': ['poster', 'presto', 'repost', 'respot', 'stoper'], + 'prestock': ['prestock', 'sprocket'], + 'prestomial': ['peristomal', 'prestomial'], + 'prestrain': ['prestrain', 'transpire'], + 'presume': ['presume', 'supreme'], + 'presurmise': ['impressure', 'presurmise'], + 'presustain': ['presustain', 'puritaness', 'supersaint'], + 'presystole': ['poetryless', 'presystole'], + 'pretan': ['arpent', + 'enrapt', + 'entrap', + 'panter', + 'parent', + 'pretan', + 'trepan'], + 'pretaste': ['prestate', 'pretaste'], + 'pretensive': ['presentive', 'pretensive', 'vespertine'], + 'pretensively': ['presentively', 'pretensively'], + 'pretensiveness': ['presentiveness', 'pretensiveness'], + 'pretentious': ['postuterine', 'pretentious'], + 'pretercanine': ['irrepentance', 'pretercanine'], + 'preterient': ['preterient', 'triterpene'], + 'pretermit': ['permitter', 'pretermit'], + 'pretheological': ['herpetological', 'pretheological'], + 'pretibial': ['bipartile', 'pretibial'], + 'pretonic': ['inceptor', 'pretonic'], + 'pretorship': ['portership', 'pretorship'], + 'pretrace': ['pretrace', 'recarpet'], + 'pretracheal': ['archprelate', 'pretracheal'], + 'pretrain': ['pretrain', 'terrapin'], + 'pretransmission': ['pretransmission', 'transimpression'], + 'pretreat': ['patterer', 'pretreat'], + 'prettiness': ['persistent', 'presentist', 'prettiness'], + 'prevailer': ['prevailer', 'reprieval'], + 'prevalid': ['deprival', 'prevalid'], + 'prevention': ['prevention', 'provenient'], + 'preversion': ['perversion', 'preversion'], + 'preveto': ['overpet', 'preveto', 'prevote'], + 'previde': ['deprive', 'previde'], + 'previous': ['pervious', 'previous', 'viperous'], + 'previously': ['perviously', 'previously', 'viperously'], + 'previousness': ['perviousness', 'previousness', 'viperousness'], + 'prevoid': ['prevoid', 'provide'], + 'prevomer': ['premover', 'prevomer'], + 'prevote': ['overpet', 'preveto', 'prevote'], + 'prewar': ['prewar', 'rewrap', 'warper'], + 'prewarn': ['prawner', 'prewarn'], + 'prewhip': ['prewhip', 'whipper'], + 'prewrap': ['prewrap', 'wrapper'], + 'prexy': ['prexy', 'pyrex'], + 'prey': ['prey', 'pyre', 'rype'], + 'pria': ['pair', 'pari', 'pria', 'ripa'], + 'price': ['price', 'repic'], + 'priced': ['percid', 'priced'], + 'prich': ['chirp', 'prich'], + 'prickfoot': ['prickfoot', 'tickproof'], + 'prickle': ['pickler', 'prickle'], + 'pride': ['pride', 'pried', 'redip'], + 'pridian': ['pindari', 'pridian'], + 'pried': ['pride', 'pried', 'redip'], + 'prier': ['prier', 'riper'], + 'priest': ['priest', 'pteris', 'sprite', 'stripe'], + 'priestal': ['epistlar', 'pilaster', 'plaister', 'priestal'], + 'priesthood': ['priesthood', 'spritehood'], + 'priestless': ['priestless', 'stripeless'], + 'prig': ['grip', 'prig'], + 'prigman': ['gripman', 'prigman', 'ramping'], + 'prima': ['impar', 'pamir', 'prima'], + 'primage': ['epigram', 'primage'], + 'primal': ['imparl', 'primal'], + 'primates': ['maspiter', 'pastimer', 'primates'], + 'primatial': ['impartial', 'primatial'], + 'primely': ['primely', 'reimply'], + 'primeness': ['primeness', 'spenerism'], + 'primost': ['primost', 'tropism'], + 'primrose': ['primrose', 'promiser'], + 'primsie': ['pismire', 'primsie'], + 'primus': ['primus', 'purism'], + 'prince': ['pincer', 'prince'], + 'princehood': ['cnidophore', 'princehood'], + 'princeite': ['princeite', 'recipient'], + 'princelike': ['pincerlike', 'princelike'], + 'princely': ['pencilry', 'princely'], + 'princesse': ['crepiness', 'princesse'], + 'prine': ['piner', 'prine', 'repin', 'ripen'], + 'pringle': ['pingler', 'pringle'], + 'printed': ['deprint', 'printed'], + 'printer': ['printer', 'reprint'], + 'priodontes': ['desorption', 'priodontes'], + 'prionopine': ['preopinion', 'prionopine'], + 'prisage': ['prisage', 'spairge'], + 'prisal': ['prisal', 'spiral'], + 'prismatoid': ['diatropism', 'prismatoid'], + 'prisometer': ['prisometer', 'spirometer'], + 'prisonable': ['bipersonal', 'prisonable'], + 'pristane': ['pinaster', 'pristane'], + 'pristine': ['enspirit', 'pristine'], + 'pristis': ['pristis', 'tripsis'], + 'prius': ['prius', 'sirup'], + 'proadmission': ['adpromission', 'proadmission'], + 'proal': ['parol', 'polar', 'poral', 'proal'], + 'proalien': ['pelorian', 'peronial', 'proalien'], + 'proamniotic': ['comparition', 'proamniotic'], + 'proathletic': ['proathletic', 'prothetical'], + 'proatlas': ['pastoral', 'proatlas'], + 'proavis': ['pavisor', 'proavis'], + 'probationer': ['probationer', 'reprobation'], + 'probe': ['probe', 'rebop'], + 'procaciously': ['plousiocracy', 'procaciously'], + 'procaine': ['caponier', 'coprinae', 'procaine'], + 'procanal': ['coplanar', 'procanal'], + 'procapital': ['applicator', 'procapital'], + 'procedure': ['procedure', 'reproduce'], + 'proceeder': ['proceeder', 'reproceed'], + 'procellas': ['procellas', 'scalloper'], + 'procerite': ['procerite', 'receiptor'], + 'procession': ['procession', 'scorpiones'], + 'prochemical': ['microcephal', 'prochemical'], + 'procidentia': ['predication', 'procidentia'], + 'proclaimer': ['proclaimer', 'reproclaim'], + 'procne': ['crepon', 'procne'], + 'procnemial': ['complainer', 'procnemial', 'recomplain'], + 'procommission': ['compromission', 'procommission'], + 'procreant': ['copartner', 'procreant'], + 'procreate': ['procreate', 'pterocera'], + 'procreation': ['incorporate', 'procreation'], + 'proctal': ['caltrop', 'proctal'], + 'proctitis': ['proctitis', 'protistic', 'tropistic'], + 'proctocolitis': ['coloproctitis', 'proctocolitis'], + 'procuress': ['percussor', 'procuress'], + 'prod': ['dorp', 'drop', 'prod'], + 'proddle': ['plodder', 'proddle'], + 'proem': ['merop', 'moper', 'proem', 'remop'], + 'proemial': ['emporial', 'proemial'], + 'proemium': ['emporium', 'pomerium', 'proemium'], + 'proethical': ['carpholite', 'proethical'], + 'proetid': ['diopter', 'peridot', 'proetid', 'protide', 'pteroid'], + 'proetidae': ['periodate', 'proetidae', 'proteidae'], + 'proetus': ['petrous', 'posture', 'proetus', 'proteus', 'septuor', 'spouter'], + 'proficient': ['prefiction', 'proficient'], + 'profit': ['forpit', 'profit'], + 'profiter': ['portfire', 'profiter'], + 'progeny': ['progeny', 'pyrogen'], + 'prohibiter': ['prohibiter', 'reprohibit'], + 'proidealistic': ['peridiastolic', 'periodicalist', 'proidealistic'], + 'proke': ['poker', 'proke'], + 'proker': ['porker', 'proker'], + 'prolacrosse': ['prolacrosse', 'sclerospora'], + 'prolapse': ['prolapse', 'sapropel'], + 'prolation': ['ploration', 'portional', 'prolation'], + 'prolegate': ['petrogale', 'petrolage', 'prolegate'], + 'proletarian': ['prerational', 'proletarian'], + 'proletarianize': ['prerealization', 'proletarianize'], + 'proletariat': ['proletariat', 'reptatorial'], + 'proletary': ['proletary', 'pyrolater'], + 'prolicense': ['prolicense', 'proselenic'], + 'prolongate': ['prolongate', 'protogenal'], + 'promerit': ['importer', 'promerit', 'reimport'], + 'promethean': ['heptameron', 'promethean'], + 'promise': ['imposer', 'promise', 'semipro'], + 'promisee': ['perisome', 'promisee', 'reimpose'], + 'promiser': ['primrose', 'promiser'], + 'promitosis': ['isotropism', 'promitosis'], + 'promnesia': ['mesropian', 'promnesia', 'spironema'], + 'promonarchist': ['micranthropos', 'promonarchist'], + 'promote': ['promote', 'protome'], + 'pronaos': ['pronaos', 'soprano'], + 'pronate': ['operant', 'pronate', 'protean'], + 'pronative': ['overpaint', 'pronative'], + 'proneur': ['proneur', 'purrone'], + 'pronotal': ['portolan', 'pronotal'], + 'pronto': ['pronto', 'proton'], + 'pronunciable': ['preconnubial', 'pronunciable'], + 'proo': ['poor', 'proo'], + 'proofer': ['proofer', 'reproof'], + 'prop': ['prop', 'ropp'], + 'propellent': ['prepollent', 'propellent'], + 'propene': ['preopen', 'propene'], + 'prophloem': ['pleomorph', 'prophloem'], + 'propine': ['piperno', 'propine'], + 'propinoic': ['propinoic', 'propionic'], + 'propionic': ['propinoic', 'propionic'], + 'propitial': ['propitial', 'triplopia'], + 'proportioner': ['proportioner', 'reproportion'], + 'propose': ['opposer', 'propose'], + 'propraetorial': ['propraetorial', 'protoperlaria'], + 'proprietous': ['peritropous', 'proprietous'], + 'propylene': ['polyprene', 'propylene'], + 'propyne': ['propyne', 'pyropen'], + 'prorate': ['praetor', 'prorate'], + 'proration': ['proration', 'troparion'], + 'prore': ['porer', 'prore', 'roper'], + 'prorebate': ['perborate', 'prorebate', 'reprobate'], + 'proreduction': ['proreduction', 'reproduction'], + 'prorevision': ['prorevision', 'provisioner', 'reprovision'], + 'prorogate': ['graperoot', 'prorogate'], + 'prosaist': ['prosaist', 'protasis'], + 'prosateur': ['prosateur', 'pterosaur'], + 'prose': ['poser', 'prose', 'ropes', 'spore'], + 'prosecretin': ['prosecretin', 'reinspector'], + 'prosectorial': ['corporealist', 'prosectorial'], + 'prosectorium': ['micropterous', 'prosectorium'], + 'proselenic': ['prolicense', 'proselenic'], + 'proselyte': ['polyester', 'proselyte'], + 'proseminate': ['impersonate', 'proseminate'], + 'prosemination': ['impersonation', 'prosemination', 'semipronation'], + 'proseneschal': ['chaperonless', 'proseneschal'], + 'prosification': ['antisoporific', 'prosification', 'sporification'], + 'prosilient': ['linopteris', 'prosilient'], + 'prosiphonate': ['nephroptosia', 'prosiphonate'], + 'proso': ['poros', 'proso', 'sopor', 'spoor'], + 'prosodiacal': ['dorsoapical', 'prosodiacal'], + 'prosopyle': ['polyspore', 'prosopyle'], + 'prossy': ['prossy', 'spyros'], + 'prostatectomy': ['cryptostomate', 'prostatectomy'], + 'prosternate': ['paternoster', 'prosternate', 'transportee'], + 'prostomiate': ['metroptosia', 'prostomiate'], + 'protactic': ['catoptric', 'protactic'], + 'protasis': ['prosaist', 'protasis'], + 'prote': ['poter', 'prote', 'repot', 'tepor', 'toper', 'trope'], + 'protead': ['adopter', 'protead', 'readopt'], + 'protean': ['operant', 'pronate', 'protean'], + 'protease': ['asterope', 'protease'], + 'protectional': ['lactoprotein', 'protectional'], + 'proteic': ['perotic', 'proteic', 'tropeic'], + 'proteida': ['apteroid', 'proteida'], + 'proteidae': ['periodate', 'proetidae', 'proteidae'], + 'proteidean': ['pontederia', 'proteidean'], + 'protein': ['pointer', 'protein', 'pterion', 'repoint', 'tropine'], + 'proteinic': ['epornitic', 'proteinic'], + 'proteles': ['proteles', 'serpolet'], + 'protelidae': ['leopardite', 'protelidae'], + 'protend': ['portend', 'protend'], + 'protension': ['portension', 'protension'], + 'proteolysis': ['elytroposis', 'proteolysis'], + 'proteose': ['esotrope', 'proteose'], + 'protest': ['protest', 'spotter'], + 'protester': ['protester', 'reprotest'], + 'proteus': ['petrous', 'posture', 'proetus', 'proteus', 'septuor', 'spouter'], + 'protheca': ['archpoet', 'protheca'], + 'prothesis': ['posterish', 'prothesis', 'sophister', 'storeship', 'tephrosis'], + 'prothetical': ['proathletic', 'prothetical'], + 'prothoracic': ['acrotrophic', 'prothoracic'], + 'protide': ['diopter', 'peridot', 'proetid', 'protide', 'pteroid'], + 'protist': ['protist', 'tropist'], + 'protistic': ['proctitis', 'protistic', 'tropistic'], + 'proto': ['porto', 'proto', 'troop'], + 'protocneme': ['mecopteron', 'protocneme'], + 'protogenal': ['prolongate', 'protogenal'], + 'protoma': ['protoma', 'taproom'], + 'protomagnesium': ['protomagnesium', 'spermatogonium'], + 'protome': ['promote', 'protome'], + 'protomorphic': ['morphotropic', 'protomorphic'], + 'proton': ['pronto', 'proton'], + 'protone': ['porteno', 'protone'], + 'protonemal': ['monopteral', 'protonemal'], + 'protoparent': ['protoparent', 'protopteran'], + 'protopathic': ['haptotropic', 'protopathic'], + 'protopathy': ['protopathy', 'protophyta'], + 'protoperlaria': ['propraetorial', 'protoperlaria'], + 'protophyta': ['protopathy', 'protophyta'], + 'protophyte': ['protophyte', 'tropophyte'], + 'protophytic': ['protophytic', 'tropophytic'], + 'protopine': ['preoption', 'protopine'], + 'protopteran': ['protoparent', 'protopteran'], + 'protore': ['protore', 'trooper'], + 'protosulphide': ['protosulphide', 'sulphoproteid'], + 'prototheme': ['photometer', 'prototheme'], + 'prototherian': ['ornithoptera', 'prototherian'], + 'prototrophic': ['prototrophic', 'trophotropic'], + 'protrade': ['predator', 'protrade', 'teardrop'], + 'protreaty': ['protreaty', 'reptatory'], + 'protuberantial': ['perturbational', 'protuberantial'], + 'protyl': ['portly', 'protyl', 'tropyl'], + 'provenient': ['prevention', 'provenient'], + 'provide': ['prevoid', 'provide'], + 'provider': ['overdrip', 'provider'], + 'provisioner': ['prorevision', 'provisioner', 'reprovision'], + 'prowed': ['powder', 'prowed'], + 'prude': ['drupe', 'duper', 'perdu', 'prude', 'pured'], + 'prudent': ['prudent', 'prunted', 'uptrend'], + 'prudential': ['prudential', 'putredinal'], + 'prudist': ['disrupt', 'prudist'], + 'prudy': ['prudy', 'purdy', 'updry'], + 'prue': ['peru', 'prue', 'pure'], + 'prune': ['perun', 'prune'], + 'prunted': ['prudent', 'prunted', 'uptrend'], + 'prussic': ['prussic', 'scirpus'], + 'prut': ['prut', 'turp'], + 'pry': ['pry', 'pyr'], + 'pryer': ['perry', 'pryer'], + 'pryse': ['pryse', 'spyer'], + 'psalm': ['plasm', 'psalm', 'slamp'], + 'psalmic': ['plasmic', 'psalmic'], + 'psalmister': ['psalmister', 'spermalist'], + 'psalmodial': ['plasmodial', 'psalmodial'], + 'psalmodic': ['plasmodic', 'psalmodic'], + 'psaloid': ['psaloid', 'salpoid'], + 'psalter': ['palster', 'persalt', 'plaster', 'psalter', 'spartle', 'stapler'], + 'psalterian': ['alpestrian', 'palestrian', 'psalterian'], + 'psalterion': ['interposal', 'psalterion'], + 'psaltery': ['plastery', 'psaltery'], + 'psaltress': ['psaltress', 'strapless'], + 'psaronius': ['prasinous', 'psaronius'], + 'psedera': ['psedera', 'respade'], + 'psellism': ['misspell', 'psellism'], + 'pseudelytron': ['pseudelytron', 'unproselyted'], + 'pseudimago': ['megapodius', 'pseudimago'], + 'pseudophallic': ['diplocephalus', 'pseudophallic'], + 'psha': ['hasp', 'pash', 'psha', 'shap'], + 'psi': ['psi', 'sip'], + 'psiloceratid': ['prediastolic', 'psiloceratid'], + 'psilophyton': ['polyphonist', 'psilophyton'], + 'psilotic': ['colpitis', 'politics', 'psilotic'], + 'psittacine': ['antiseptic', 'psittacine'], + 'psoadic': ['psoadic', 'scapoid', 'sciapod'], + 'psoas': ['passo', 'psoas'], + 'psocidae': ['diascope', 'psocidae', 'scopidae'], + 'psocine': ['psocine', 'scopine'], + 'psora': ['psora', 'sapor', 'sarpo'], + 'psoralea': ['parosela', 'psoralea'], + 'psoroid': ['psoroid', 'sporoid'], + 'psorous': ['psorous', 'soursop', 'sporous'], + 'psychobiological': ['biopsychological', 'psychobiological'], + 'psychobiology': ['biopsychology', 'psychobiology'], + 'psychogalvanic': ['galvanopsychic', 'psychogalvanic'], + 'psychomancy': ['psychomancy', 'scyphomancy'], + 'psychomonism': ['monopsychism', 'psychomonism'], + 'psychoneurological': ['neuropsychological', 'psychoneurological'], + 'psychoneurosis': ['neuropsychosis', 'psychoneurosis'], + 'psychophysiological': ['physiopsychological', 'psychophysiological'], + 'psychophysiology': ['physiopsychology', 'psychophysiology'], + 'psychorrhagy': ['chrysography', 'psychorrhagy'], + 'psychosomatic': ['psychosomatic', 'somatopsychic'], + 'psychotechnology': ['psychotechnology', 'technopsychology'], + 'psychotheism': ['psychotheism', 'theopsychism'], + 'psychotria': ['physiocrat', 'psychotria'], + 'ptelea': ['apelet', 'ptelea'], + 'ptereal': ['pearlet', 'pleater', 'prelate', 'ptereal', 'replate', 'repleat'], + 'pterian': ['painter', 'pertain', 'pterian', 'repaint'], + 'pterideous': ['depositure', 'pterideous'], + 'pteridological': ['dipterological', 'pteridological'], + 'pteridologist': ['dipterologist', 'pteridologist'], + 'pteridology': ['dipterology', 'pteridology'], + 'pterion': ['pointer', 'protein', 'pterion', 'repoint', 'tropine'], + 'pteris': ['priest', 'pteris', 'sprite', 'stripe'], + 'pterocera': ['procreate', 'pterocera'], + 'pterodactylidae': ['dactylopteridae', 'pterodactylidae'], + 'pterodactylus': ['dactylopterus', 'pterodactylus'], + 'pterographer': ['petrographer', 'pterographer'], + 'pterographic': ['petrographic', 'pterographic'], + 'pterographical': ['petrographical', 'pterographical'], + 'pterography': ['petrography', 'pterography', 'typographer'], + 'pteroid': ['diopter', 'peridot', 'proetid', 'protide', 'pteroid'], + 'pteroma': ['pteroma', 'tempora'], + 'pteropus': ['pteropus', 'stoppeur'], + 'pterosaur': ['prosateur', 'pterosaur'], + 'pterospora': ['praepostor', 'pterospora'], + 'pteryla': ['apertly', 'peartly', 'platery', 'pteryla', 'taperly'], + 'pterylographical': ['petrographically', 'pterylographical'], + 'pterylological': ['petrologically', 'pterylological'], + 'pterylosis': ['peristylos', 'pterylosis'], + 'ptilota': ['ptilota', 'talipot', 'toptail'], + 'ptinus': ['ptinus', 'unspit'], + 'ptolemean': ['leptonema', 'ptolemean'], + 'ptomain': ['maintop', 'ptomain', 'tampion', 'timpano'], + 'ptomainic': ['impaction', 'ptomainic'], + 'ptyalin': ['inaptly', 'planity', 'ptyalin'], + 'ptyalocele': ['clypeolate', 'ptyalocele'], + 'ptyalogenic': ['genotypical', 'ptyalogenic'], + 'pu': ['pu', 'up'], + 'pua': ['pau', 'pua'], + 'puan': ['napu', 'puan', 'puna'], + 'publisher': ['publisher', 'republish'], + 'puckball': ['puckball', 'pullback'], + 'puckrel': ['plucker', 'puckrel'], + 'pud': ['dup', 'pud'], + 'pudendum': ['pudendum', 'undumped'], + 'pudent': ['pudent', 'uptend'], + 'pudic': ['cupid', 'pudic'], + 'pudical': ['paludic', 'pudical'], + 'pudicity': ['cupidity', 'pudicity'], + 'puerer': ['puerer', 'purree'], + 'puffer': ['puffer', 'repuff'], + 'pug': ['gup', 'pug'], + 'pugman': ['panmug', 'pugman'], + 'puisne': ['puisne', 'supine'], + 'puist': ['puist', 'upsit'], + 'puja': ['jaup', 'puja'], + 'puke': ['keup', 'puke'], + 'pule': ['lupe', 'pelu', 'peul', 'pule'], + 'pulian': ['paulin', 'pulian'], + 'pulicene': ['clupeine', 'pulicene'], + 'pulicidal': ['picudilla', 'pulicidal'], + 'pulicide': ['lupicide', 'pediculi', 'pulicide'], + 'puling': ['gulpin', 'puling'], + 'pulish': ['huspil', 'pulish'], + 'pullback': ['puckball', 'pullback'], + 'pulp': ['plup', 'pulp'], + 'pulper': ['pulper', 'purple'], + 'pulpiter': ['pulpiter', 'repulpit'], + 'pulpous': ['populus', 'pulpous'], + 'pulpstone': ['pulpstone', 'unstopple'], + 'pulsant': ['pulsant', 'upslant'], + 'pulsate': ['pulsate', 'spatule', 'upsteal'], + 'pulsatile': ['palluites', 'pulsatile'], + 'pulsation': ['platinous', 'pulsation'], + 'pulsator': ['postural', 'pulsator', 'sportula'], + 'pulse': ['lepus', 'pulse'], + 'pulsion': ['pulsion', 'unspoil', 'upsilon'], + 'pulvic': ['pulvic', 'vulpic'], + 'pumper': ['pumper', 'repump'], + 'pumple': ['peplum', 'pumple'], + 'puna': ['napu', 'puan', 'puna'], + 'puncher': ['puncher', 'unperch'], + 'punctilio': ['punctilio', 'unpolitic'], + 'puncturer': ['puncturer', 'upcurrent'], + 'punger': ['punger', 'repugn'], + 'pungle': ['plunge', 'pungle'], + 'puniceous': ['pecunious', 'puniceous'], + 'punish': ['inpush', 'punish', 'unship'], + 'punisher': ['punisher', 'repunish'], + 'punishment': ['punishment', 'unshipment'], + 'punlet': ['penult', 'punlet', 'puntel'], + 'punnet': ['punnet', 'unpent'], + 'puno': ['noup', 'puno', 'upon'], + 'punta': ['punta', 'unapt', 'untap'], + 'puntal': ['puntal', 'unplat'], + 'puntel': ['penult', 'punlet', 'puntel'], + 'punti': ['input', 'punti'], + 'punto': ['punto', 'unpot', 'untop'], + 'pupa': ['paup', 'pupa'], + 'pupelo': ['poulpe', 'pupelo'], + 'purana': ['purana', 'uparna'], + 'purdy': ['prudy', 'purdy', 'updry'], + 'pure': ['peru', 'prue', 'pure'], + 'pured': ['drupe', 'duper', 'perdu', 'prude', 'pured'], + 'puree': ['puree', 'rupee'], + 'purer': ['purer', 'purre'], + 'purine': ['purine', 'unripe', 'uprein'], + 'purism': ['primus', 'purism'], + 'purist': ['purist', 'spruit', 'uprist', 'upstir'], + 'puritan': ['pintura', 'puritan', 'uptrain'], + 'puritaness': ['presustain', 'puritaness', 'supersaint'], + 'purler': ['purler', 'purrel'], + 'purple': ['pulper', 'purple'], + 'purpose': ['peropus', 'purpose'], + 'purpuroxanthin': ['purpuroxanthin', 'xanthopurpurin'], + 'purre': ['purer', 'purre'], + 'purree': ['puerer', 'purree'], + 'purrel': ['purler', 'purrel'], + 'purrone': ['proneur', 'purrone'], + 'purse': ['purse', 'resup', 'sprue', 'super'], + 'purser': ['purser', 'spruer'], + 'purslane': ['purslane', 'serpulan', 'supernal'], + 'purslet': ['purslet', 'spurlet', 'spurtle'], + 'pursuer': ['pursuer', 'usurper'], + 'pursy': ['pursy', 'pyrus', 'syrup'], + 'pus': ['pus', 'sup'], + 'pushtu': ['pushtu', 'upshut'], + 'pustule': ['pluteus', 'pustule'], + 'pustulose': ['pustulose', 'stupulose'], + 'put': ['put', 'tup'], + 'putanism': ['putanism', 'sumpitan'], + 'putation': ['outpaint', 'putation'], + 'putredinal': ['prudential', 'putredinal'], + 'putrid': ['putrid', 'turpid'], + 'putridly': ['putridly', 'turpidly'], + 'pya': ['pay', 'pya', 'yap'], + 'pyal': ['paly', 'play', 'pyal', 'pyla'], + 'pycnial': ['pliancy', 'pycnial'], + 'pyelic': ['epicly', 'pyelic'], + 'pyelitis': ['pyelitis', 'sipylite'], + 'pyelocystitis': ['cystopyelitis', 'pyelocystitis'], + 'pyelonephritis': ['nephropyelitis', 'pyelonephritis'], + 'pyeloureterogram': ['pyeloureterogram', 'ureteropyelogram'], + 'pyemesis': ['empyesis', 'pyemesis'], + 'pygmalion': ['maypoling', 'pygmalion'], + 'pyin': ['piny', 'pyin'], + 'pyla': ['paly', 'play', 'pyal', 'pyla'], + 'pylades': ['pylades', 'splayed'], + 'pylagore': ['playgoer', 'pylagore'], + 'pylar': ['parly', 'pylar', 'pyral'], + 'pyonephrosis': ['nephropyosis', 'pyonephrosis'], + 'pyopneumothorax': ['pneumopyothorax', 'pyopneumothorax'], + 'pyosepticemia': ['pyosepticemia', 'septicopyemia'], + 'pyosepticemic': ['pyosepticemic', 'septicopyemic'], + 'pyr': ['pry', 'pyr'], + 'pyracanth': ['pantarchy', 'pyracanth'], + 'pyral': ['parly', 'pylar', 'pyral'], + 'pyrales': ['parsley', 'pyrales', 'sparely', 'splayer'], + 'pyralid': ['pyralid', 'rapidly'], + 'pyramidale': ['lampyridae', 'pyramidale'], + 'pyranometer': ['premonetary', 'pyranometer'], + 'pyre': ['prey', 'pyre', 'rype'], + 'pyrena': ['napery', 'pyrena'], + 'pyrenic': ['cyprine', 'pyrenic'], + 'pyrenoid': ['pyrenoid', 'pyridone', 'pyrodine'], + 'pyretogenic': ['pyretogenic', 'pyrogenetic'], + 'pyrex': ['prexy', 'pyrex'], + 'pyrgocephaly': ['pelycography', 'pyrgocephaly'], + 'pyridone': ['pyrenoid', 'pyridone', 'pyrodine'], + 'pyrites': ['pyrites', 'sperity'], + 'pyritoid': ['pityroid', 'pyritoid'], + 'pyro': ['pory', 'pyro', 'ropy'], + 'pyroarsenite': ['arsenopyrite', 'pyroarsenite'], + 'pyrochemical': ['microcephaly', 'pyrochemical'], + 'pyrocomenic': ['pyrocomenic', 'pyromeconic'], + 'pyrodine': ['pyrenoid', 'pyridone', 'pyrodine'], + 'pyrogen': ['progeny', 'pyrogen'], + 'pyrogenetic': ['pyretogenic', 'pyrogenetic'], + 'pyrolater': ['proletary', 'pyrolater'], + 'pyromantic': ['importancy', 'patronymic', 'pyromantic'], + 'pyromeconic': ['pyrocomenic', 'pyromeconic'], + 'pyrope': ['popery', 'pyrope'], + 'pyropen': ['propyne', 'pyropen'], + 'pyrophorus': ['porphyrous', 'pyrophorus'], + 'pyrotheria': ['erythropia', 'pyrotheria'], + 'pyruline': ['pyruline', 'unripely'], + 'pyrus': ['pursy', 'pyrus', 'syrup'], + 'pyruvil': ['pyruvil', 'pyvuril'], + 'pythagorism': ['myographist', 'pythagorism'], + 'pythia': ['pythia', 'typhia'], + 'pythic': ['phytic', 'pitchy', 'pythic', 'typhic'], + 'pythogenesis': ['phytogenesis', 'pythogenesis'], + 'pythogenetic': ['phytogenetic', 'pythogenetic'], + 'pythogenic': ['phytogenic', 'pythogenic', 'typhogenic'], + 'pythogenous': ['phytogenous', 'pythogenous'], + 'python': ['phyton', 'python'], + 'pythonic': ['hypnotic', 'phytonic', 'pythonic', 'typhonic'], + 'pythonism': ['hypnotism', 'pythonism'], + 'pythonist': ['hypnotist', 'pythonist'], + 'pythonize': ['hypnotize', 'pythonize'], + 'pythonoid': ['hypnotoid', 'pythonoid'], + 'pyvuril': ['pyruvil', 'pyvuril'], + 'quadrual': ['quadrual', 'quadrula'], + 'quadrula': ['quadrual', 'quadrula'], + 'quail': ['quail', 'quila'], + 'quake': ['quake', 'queak'], + 'quale': ['equal', 'quale', 'queal'], + 'qualitied': ['liquidate', 'qualitied'], + 'quamoclit': ['coquitlam', 'quamoclit'], + 'quannet': ['quannet', 'tanquen'], + 'quartile': ['quartile', 'requital', 'triequal'], + 'quartine': ['antiquer', 'quartine'], + 'quata': ['quata', 'taqua'], + 'quatrin': ['quatrin', 'tarquin'], + 'queak': ['quake', 'queak'], + 'queal': ['equal', 'quale', 'queal'], + 'queeve': ['eveque', 'queeve'], + 'quencher': ['quencher', 'requench'], + 'querier': ['querier', 'require'], + 'queriman': ['queriman', 'ramequin'], + 'querist': ['querist', 'squiret'], + 'quernal': ['quernal', 'ranquel'], + 'quester': ['quester', 'request'], + 'questioner': ['questioner', 'requestion'], + 'questor': ['questor', 'torques'], + 'quiet': ['quiet', 'quite'], + 'quietable': ['equitable', 'quietable'], + 'quieter': ['quieter', 'requite'], + 'quietist': ['equitist', 'quietist'], + 'quietsome': ['quietsome', 'semiquote'], + 'quiina': ['quiina', 'quinia'], + 'quila': ['quail', 'quila'], + 'quiles': ['quiles', 'quisle'], + 'quinate': ['antique', 'quinate'], + 'quince': ['cinque', 'quince'], + 'quinia': ['quiina', 'quinia'], + 'quinite': ['inquiet', 'quinite'], + 'quinnat': ['quinnat', 'quintan'], + 'quinse': ['quinse', 'sequin'], + 'quintan': ['quinnat', 'quintan'], + 'quintato': ['quintato', 'totaquin'], + 'quinze': ['quinze', 'zequin'], + 'quirt': ['quirt', 'qurti'], + 'quisle': ['quiles', 'quisle'], + 'quite': ['quiet', 'quite'], + 'quits': ['quits', 'squit'], + 'quote': ['quote', 'toque'], + 'quoter': ['quoter', 'roquet', 'torque'], + 'qurti': ['quirt', 'qurti'], + 'ra': ['ar', 'ra'], + 'raad': ['adar', 'arad', 'raad', 'rada'], + 'raash': ['asarh', 'raash', 'sarah'], + 'rab': ['bar', 'bra', 'rab'], + 'raband': ['bandar', 'raband'], + 'rabatine': ['atabrine', 'rabatine'], + 'rabatte': ['baretta', 'rabatte', 'tabaret'], + 'rabbanite': ['barnabite', 'rabbanite', 'rabbinate'], + 'rabbet': ['barbet', 'rabbet', 'tabber'], + 'rabbinate': ['barnabite', 'rabbanite', 'rabbinate'], + 'rabble': ['barbel', 'labber', 'rabble'], + 'rabboni': ['barbion', 'rabboni'], + 'rabi': ['abir', 'bari', 'rabi'], + 'rabic': ['baric', 'carib', 'rabic'], + 'rabid': ['barid', 'bidar', 'braid', 'rabid'], + 'rabidly': ['bardily', 'rabidly', 'ridably'], + 'rabidness': ['bardiness', 'rabidness'], + 'rabies': ['braise', 'rabies', 'rebias'], + 'rabin': ['abrin', 'bairn', 'brain', 'brian', 'rabin'], + 'rabinet': ['atebrin', 'rabinet'], + 'raccoon': ['carcoon', 'raccoon'], + 'race': ['acer', 'acre', 'care', 'crea', 'race'], + 'racemate': ['camerate', 'macerate', 'racemate'], + 'racemation': ['aeromantic', 'cameration', 'maceration', 'racemation'], + 'raceme': ['amerce', 'raceme'], + 'racemed': ['decream', 'racemed'], + 'racemic': ['ceramic', 'racemic'], + 'racer': ['carer', 'crare', 'racer'], + 'rach': ['arch', 'char', 'rach'], + 'rache': ['acher', 'arche', 'chare', 'chera', 'rache', 'reach'], + 'rachel': ['rachel', 'rechal'], + 'rachianectes': ['rachianectes', 'rhacianectes'], + 'rachidial': ['diarchial', 'rachidial'], + 'rachitis': ['architis', 'rachitis'], + 'racial': ['alaric', 'racial'], + 'racialist': ['racialist', 'satirical'], + 'racing': ['arcing', 'racing'], + 'racist': ['crista', 'racist'], + 'rack': ['cark', 'rack'], + 'racker': ['racker', 'rerack'], + 'racket': ['racket', 'retack', 'tacker'], + 'racking': ['arcking', 'carking', 'racking'], + 'rackingly': ['carkingly', 'rackingly'], + 'rackle': ['calker', 'lacker', 'rackle', 'recalk', 'reckla'], + 'racon': ['acorn', 'acron', 'racon'], + 'raconteur': ['cuarteron', 'raconteur'], + 'racoon': ['caroon', 'corona', 'racoon'], + 'racy': ['cary', 'racy'], + 'rad': ['dar', 'rad'], + 'rada': ['adar', 'arad', 'raad', 'rada'], + 'raddle': ['ladder', 'raddle'], + 'raddleman': ['dreamland', 'raddleman'], + 'radectomy': ['myctodera', 'radectomy'], + 'radek': ['daker', 'drake', 'kedar', 'radek'], + 'radiable': ['labridae', 'radiable'], + 'radiale': ['ardelia', 'laridae', 'radiale'], + 'radian': ['adrian', 'andira', 'andria', 'radian', 'randia'], + 'radiance': ['caridean', 'dircaean', 'radiance'], + 'radiant': ['intrada', 'radiant'], + 'radiata': ['dataria', 'radiata'], + 'radical': ['cardial', 'radical'], + 'radicant': ['antacrid', 'cardiant', 'radicant', 'tridacna'], + 'radicel': ['decrial', 'radicel', 'radicle'], + 'radices': ['diceras', 'radices', 'sidecar'], + 'radicle': ['decrial', 'radicel', 'radicle'], + 'radicose': ['idocrase', 'radicose'], + 'radicule': ['auricled', 'radicule'], + 'radiculose': ['coresidual', 'radiculose'], + 'radiectomy': ['acidometry', 'medicatory', 'radiectomy'], + 'radii': ['dairi', 'darii', 'radii'], + 'radio': ['aroid', 'doria', 'radio'], + 'radioautograph': ['autoradiograph', 'radioautograph'], + 'radioautographic': ['autoradiographic', 'radioautographic'], + 'radioautography': ['autoradiography', 'radioautography'], + 'radiohumeral': ['humeroradial', 'radiohumeral'], + 'radiolite': ['editorial', 'radiolite'], + 'radiolucent': ['radiolucent', 'reductional'], + 'radioman': ['adoniram', 'radioman'], + 'radiomicrometer': ['microradiometer', 'radiomicrometer'], + 'radiophone': ['phoronidea', 'radiophone'], + 'radiophony': ['hypodorian', 'radiophony'], + 'radiotelephone': ['radiotelephone', 'teleradiophone'], + 'radiotron': ['ordinator', 'radiotron'], + 'radish': ['ardish', 'radish'], + 'radius': ['darius', 'radius'], + 'radman': ['mandra', 'radman'], + 'radon': ['adorn', 'donar', 'drona', 'radon'], + 'radula': ['adular', 'aludra', 'radula'], + 'rafael': ['aflare', 'rafael'], + 'rafe': ['fare', 'fear', 'frae', 'rafe'], + 'raffee': ['affeer', 'raffee'], + 'raffia': ['affair', 'raffia'], + 'raffle': ['farfel', 'raffle'], + 'rafik': ['fakir', 'fraik', 'kafir', 'rafik'], + 'raft': ['frat', 'raft'], + 'raftage': ['fregata', 'raftage'], + 'rafter': ['frater', 'rafter'], + 'rag': ['gar', 'gra', 'rag'], + 'raga': ['agar', 'agra', 'gara', 'raga'], + 'rage': ['ager', 'agre', 'gare', 'gear', 'rage'], + 'rageless': ['eelgrass', 'gearless', 'rageless'], + 'ragfish': ['garfish', 'ragfish'], + 'ragged': ['dagger', 'gadger', 'ragged'], + 'raggee': ['agrege', 'raggee'], + 'raggety': ['gargety', 'raggety'], + 'raggle': ['gargle', 'gregal', 'lagger', 'raggle'], + 'raggled': ['draggle', 'raggled'], + 'raggy': ['aggry', 'raggy'], + 'ragingly': ['grayling', 'ragingly'], + 'raglanite': ['antiglare', 'raglanite'], + 'raglet': ['raglet', 'tergal'], + 'raglin': ['nargil', 'raglin'], + 'ragman': ['amgarn', 'mangar', 'marang', 'ragman'], + 'ragnar': ['garran', 'ragnar'], + 'ragshag': ['ragshag', 'shagrag'], + 'ragtag': ['ragtag', 'tagrag'], + 'ragtime': ['migrate', 'ragtime'], + 'ragule': ['ragule', 'regula'], + 'raguly': ['glaury', 'raguly'], + 'raia': ['aira', 'aria', 'raia'], + 'raid': ['arid', 'dari', 'raid'], + 'raider': ['arride', 'raider'], + 'raif': ['fair', 'fiar', 'raif'], + 'raiidae': ['ariidae', 'raiidae'], + 'rail': ['aril', 'lair', 'lari', 'liar', 'lira', 'rail', 'rial'], + 'railage': ['lairage', 'railage', 'regalia'], + 'railer': ['railer', 'rerail'], + 'railhead': ['headrail', 'railhead'], + 'railless': ['lairless', 'railless'], + 'railman': ['lairman', 'laminar', 'malarin', 'railman'], + 'raiment': ['minaret', 'raiment', 'tireman'], + 'rain': ['arni', 'iran', 'nair', 'rain', 'rani'], + 'raincoat': ['craniota', 'croatian', 'narcotia', 'raincoat'], + 'rainful': ['rainful', 'unfrail'], + 'rainspout': ['rainspout', 'supinator'], + 'rainy': ['nairy', 'rainy'], + 'rais': ['rais', 'sair', 'sari'], + 'raise': ['aries', 'arise', 'raise', 'serai'], + 'raiseman': ['erasmian', 'raiseman'], + 'raiser': ['raiser', 'sierra'], + 'raisin': ['raisin', 'sirian'], + 'raj': ['jar', 'raj'], + 'raja': ['ajar', 'jara', 'raja'], + 'rajah': ['ajhar', 'rajah'], + 'rajeev': ['evejar', 'rajeev'], + 'rake': ['rake', 'reak'], + 'rakesteel': ['rakesteel', 'rakestele'], + 'rakestele': ['rakesteel', 'rakestele'], + 'rakh': ['hark', 'khar', 'rakh'], + 'raki': ['ikra', 'kari', 'raki'], + 'rakish': ['rakish', 'riksha', 'shikar', 'shikra', 'sikhra'], + 'rakit': ['kitar', 'krait', 'rakit', 'traik'], + 'raku': ['kuar', 'raku', 'rauk'], + 'ralf': ['farl', 'ralf'], + 'ralliance': ['alliancer', 'ralliance'], + 'ralline': ['ralline', 'renilla'], + 'ram': ['arm', 'mar', 'ram'], + 'rama': ['amar', 'amra', 'mara', 'rama'], + 'ramada': ['armada', 'damara', 'ramada'], + 'ramage': ['gemara', 'ramage'], + 'ramaite': ['ametria', 'artemia', 'meratia', 'ramaite'], + 'ramal': ['alarm', 'malar', 'maral', 'marla', 'ramal'], + 'ramanas': ['ramanas', 'sramana'], + 'ramate': ['ramate', 'retama'], + 'ramble': ['ambler', 'blamer', 'lamber', 'marble', 'ramble'], + 'rambler': ['marbler', 'rambler'], + 'rambling': ['marbling', 'rambling'], + 'rambo': ['broma', 'rambo'], + 'rambutan': ['rambutan', 'tamburan'], + 'rame': ['erma', 'mare', 'rame', 'ream'], + 'ramed': ['armed', 'derma', 'dream', 'ramed'], + 'rament': ['manter', 'marten', 'rament'], + 'ramental': ['maternal', 'ramental'], + 'ramequin': ['queriman', 'ramequin'], + 'ramesh': ['masher', 'ramesh', 'shamer'], + 'ramet': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'rami': ['amir', 'irma', 'mari', 'mira', 'rami', 'rima'], + 'ramie': ['aimer', 'maire', 'marie', 'ramie'], + 'ramiferous': ['armiferous', 'ramiferous'], + 'ramigerous': ['armigerous', 'ramigerous'], + 'ramillie': ['milliare', 'ramillie'], + 'ramisection': ['anisometric', + 'creationism', + 'miscreation', + 'ramisection', + 'reactionism'], + 'ramist': ['marist', 'matris', 'ramist'], + 'ramline': ['marline', 'mineral', 'ramline'], + 'rammel': ['lammer', 'rammel'], + 'rammy': ['mymar', 'rammy'], + 'ramon': ['manor', 'moran', 'norma', 'ramon', 'roman'], + 'ramona': ['oarman', 'ramona'], + 'ramose': ['amores', 'ramose', 'sorema'], + 'ramosely': ['marysole', 'ramosely'], + 'ramp': ['pram', 'ramp'], + 'rampant': ['mantrap', 'rampant'], + 'ramped': ['damper', 'ramped'], + 'ramper': ['prearm', 'ramper'], + 'rampike': ['perakim', 'permiak', 'rampike'], + 'ramping': ['gripman', 'prigman', 'ramping'], + 'ramsey': ['ramsey', 'smeary'], + 'ramson': ['ramson', 'ransom'], + 'ramtil': ['mitral', 'ramtil'], + 'ramule': ['mauler', 'merula', 'ramule'], + 'ramulus': ['malurus', 'ramulus'], + 'ramus': ['musar', 'ramus', 'rusma', 'surma'], + 'ramusi': ['misura', 'ramusi'], + 'ran': ['arn', 'nar', 'ran'], + 'rana': ['arna', 'rana'], + 'ranales': ['arsenal', 'ranales'], + 'rance': ['caner', 'crane', 'crena', 'nacre', 'rance'], + 'rancel': ['lancer', 'rancel'], + 'rancer': ['craner', 'rancer'], + 'ranche': ['enarch', 'ranche'], + 'ranchero': ['anchorer', 'ranchero', 'reanchor'], + 'rancho': ['anchor', 'archon', 'charon', 'rancho'], + 'rancid': ['andric', 'cardin', 'rancid'], + 'rand': ['darn', 'nard', 'rand'], + 'randan': ['annard', 'randan'], + 'randem': ['damner', 'manred', 'randem', 'remand'], + 'rander': ['darner', 'darren', 'errand', 'rander', 'redarn'], + 'randia': ['adrian', 'andira', 'andria', 'radian', 'randia'], + 'randing': ['darning', 'randing'], + 'randite': ['antired', 'detrain', 'randite', 'trained'], + 'randle': ['aldern', + 'darnel', + 'enlard', + 'lander', + 'lenard', + 'randle', + 'reland'], + 'random': ['random', 'rodman'], + 'rane': ['arne', 'earn', 'rane'], + 'ranere': ['earner', 'ranere'], + 'rang': ['garn', 'gnar', 'rang'], + 'range': ['anger', 'areng', 'grane', 'range'], + 'ranged': ['danger', 'gander', 'garden', 'ranged'], + 'rangeless': ['largeness', 'rangeless', 'regalness'], + 'ranger': ['garner', 'ranger'], + 'rangey': ['anergy', 'rangey'], + 'ranginess': ['angriness', 'ranginess'], + 'rangle': ['angler', 'arleng', 'garnel', 'largen', 'rangle', 'regnal'], + 'rangy': ['angry', 'rangy'], + 'rani': ['arni', 'iran', 'nair', 'rain', 'rani'], + 'ranid': ['darin', 'dinar', 'drain', 'indra', 'nadir', 'ranid'], + 'ranidae': ['araneid', 'ariadne', 'ranidae'], + 'raniform': ['nariform', 'raniform'], + 'raninae': ['aranein', 'raninae'], + 'ranine': ['narine', 'ranine'], + 'rank': ['knar', 'kran', 'nark', 'rank'], + 'ranked': ['darken', 'kanred', 'ranked'], + 'ranker': ['ranker', 'rerank'], + 'rankish': ['krishna', 'rankish'], + 'rannel': ['lanner', 'rannel'], + 'ranquel': ['quernal', 'ranquel'], + 'ransom': ['ramson', 'ransom'], + 'rant': ['natr', 'rant', 'tarn', 'tran'], + 'rantepole': ['petrolean', 'rantepole'], + 'ranter': ['arrent', 'errant', 'ranter', 'ternar'], + 'rantipole': ['prelation', 'rantipole'], + 'ranula': ['alraun', 'alruna', 'ranula'], + 'rap': ['par', 'rap'], + 'rape': ['aper', 'pare', 'pear', 'rape', 'reap'], + 'rapeful': ['rapeful', 'upflare'], + 'raper': ['parer', 'raper'], + 'raphael': ['phalera', 'raphael'], + 'raphaelic': ['eparchial', 'raphaelic'], + 'raphe': ['hepar', 'phare', 'raphe'], + 'raphia': ['pahari', 'pariah', 'raphia'], + 'raphides': ['diphaser', 'parished', 'raphides', 'sephardi'], + 'raphis': ['parish', 'raphis', 'rhapis'], + 'rapic': ['capri', 'picra', 'rapic'], + 'rapid': ['adrip', 'rapid'], + 'rapidly': ['pyralid', 'rapidly'], + 'rapier': ['pairer', 'rapier', 'repair'], + 'rapine': ['parine', 'rapine'], + 'raping': ['paring', 'raping'], + 'rapparee': ['appearer', 'rapparee', 'reappear'], + 'rappe': ['paper', 'rappe'], + 'rappel': ['lapper', 'rappel'], + 'rappite': ['periapt', 'rappite'], + 'rapt': ['part', 'prat', 'rapt', 'tarp', 'trap'], + 'raptly': ['paltry', 'partly', 'raptly'], + 'raptor': ['parrot', 'raptor'], + 'rapture': ['parture', 'rapture'], + 'rare': ['rare', 'rear'], + 'rarebit': ['arbiter', 'rarebit'], + 'rareripe': ['rareripe', 'repairer'], + 'rarish': ['arrish', 'harris', 'rarish', 'sirrah'], + 'ras': ['ras', 'sar'], + 'rasa': ['rasa', 'sara'], + 'rascacio': ['coracias', 'rascacio'], + 'rascal': ['lascar', 'rascal', 'sacral', 'scalar'], + 'rase': ['arse', 'rase', 'sare', 'sear', 'sera'], + 'rasen': ['anser', 'nares', 'rasen', 'snare'], + 'raser': ['ersar', 'raser', 'serra'], + 'rasher': ['rasher', 'sharer'], + 'rashing': ['garnish', 'rashing'], + 'rashti': ['rashti', 'tarish'], + 'rasion': ['arsino', 'rasion', 'sonrai'], + 'rasp': ['rasp', 'spar'], + 'rasped': ['rasped', 'spader', 'spread'], + 'rasper': ['parser', 'rasper', 'sparer'], + 'rasping': ['aspring', 'rasping', 'sparing'], + 'raspingly': ['raspingly', 'sparingly'], + 'raspingness': ['raspingness', 'sparingness'], + 'raspite': ['piaster', 'piastre', 'raspite', 'spirate', 'traipse'], + 'raspy': ['raspy', 'spary', 'spray'], + 'rasse': ['arses', 'rasse'], + 'raster': ['arrest', 'astrer', 'raster', 'starer'], + 'rastik': ['rastik', 'sarkit', 'straik'], + 'rastle': ['laster', + 'lastre', + 'rastle', + 'relast', + 'resalt', + 'salter', + 'slater', + 'stelar'], + 'rastus': ['rastus', 'tarsus'], + 'rat': ['art', 'rat', 'tar', 'tra'], + 'rata': ['rata', 'taar', 'tara'], + 'ratable': ['alberta', 'latebra', 'ratable'], + 'ratal': ['altar', 'artal', 'ratal', 'talar'], + 'ratanhia': ['ratanhia', 'rhatania'], + 'ratbite': ['biretta', 'brattie', 'ratbite'], + 'ratch': ['chart', 'ratch'], + 'ratchel': ['clethra', 'latcher', 'ratchel', 'relatch', 'talcher', 'trachle'], + 'ratcher': ['charter', 'ratcher'], + 'ratchet': ['chatter', 'ratchet'], + 'ratchety': ['chattery', 'ratchety', 'trachyte'], + 'ratching': ['charting', 'ratching'], + 'rate': ['rate', 'tare', 'tear', 'tera'], + 'rated': ['dater', 'derat', 'detar', 'drate', 'rated', 'trade', 'tread'], + 'ratel': ['alert', 'alter', 'artel', 'later', 'ratel', 'taler', 'telar'], + 'rateless': ['rateless', 'tasseler', 'tearless', 'tesseral'], + 'ratfish': ['ratfish', 'tashrif'], + 'rath': ['hart', 'rath', 'tahr', 'thar', 'trah'], + 'rathe': ['earth', 'hater', 'heart', 'herat', 'rathe'], + 'rathed': ['dearth', 'hatred', 'rathed', 'thread'], + 'rathely': ['earthly', 'heartly', 'lathery', 'rathely'], + 'ratherest': ['ratherest', 'shatterer'], + 'rathest': ['rathest', 'shatter'], + 'rathite': ['hartite', 'rathite'], + 'rathole': ['loather', 'rathole'], + 'raticidal': ['raticidal', 'triadical'], + 'raticide': ['ceratiid', 'raticide'], + 'ratine': ['nerita', 'ratine', 'retain', 'retina', 'tanier'], + 'rating': ['rating', 'tringa'], + 'ratio': ['ariot', 'ratio'], + 'ration': ['aroint', 'ration'], + 'rationable': ['alboranite', 'rationable'], + 'rational': ['notarial', 'rational', 'rotalian'], + 'rationale': ['alienator', 'rationale'], + 'rationalize': ['rationalize', 'realization'], + 'rationally': ['notarially', 'rationally'], + 'rationate': ['notariate', 'rationate'], + 'ratitae': ['arietta', 'ratitae'], + 'ratite': ['attire', 'ratite', 'tertia'], + 'ratitous': ['outstair', 'ratitous'], + 'ratlike': ['artlike', 'ratlike', 'tarlike'], + 'ratline': ['entrail', + 'latiner', + 'latrine', + 'ratline', + 'reliant', + 'retinal', + 'trenail'], + 'rattage': ['garetta', 'rattage', 'regatta'], + 'rattan': ['rattan', 'tantra', 'tartan'], + 'ratteen': ['entreat', 'ratteen', 'tarente', 'ternate', 'tetrane'], + 'ratten': ['attern', 'natter', 'ratten', 'tarten'], + 'ratti': ['ratti', 'titar', 'trait'], + 'rattish': ['athirst', 'rattish', 'tartish'], + 'rattle': ['artlet', 'latter', 'rattle', 'tartle', 'tatler'], + 'rattles': ['rattles', 'slatter', 'starlet', 'startle'], + 'rattlesome': ['rattlesome', 'saltometer'], + 'rattly': ['rattly', 'tartly'], + 'ratton': ['attorn', 'ratton', 'rottan'], + 'rattus': ['astrut', 'rattus', 'stuart'], + 'ratwood': ['ratwood', 'tarwood'], + 'raught': ['raught', 'tughra'], + 'rauk': ['kuar', 'raku', 'rauk'], + 'raul': ['alur', 'laur', 'lura', 'raul', 'ural'], + 'rauli': ['rauli', 'urali', 'urial'], + 'raun': ['raun', 'uran', 'urna'], + 'raunge': ['nauger', 'raunge', 'ungear'], + 'rave': ['aver', 'rave', 'vare', 'vera'], + 'ravel': ['arvel', 'larve', 'laver', 'ravel', 'velar'], + 'ravelin': ['elinvar', 'ravelin', 'reanvil', 'valerin'], + 'ravelly': ['ravelly', 'valeryl'], + 'ravendom': ['overdamn', 'ravendom'], + 'ravenelia': ['ravenelia', 'veneralia'], + 'ravenish': ['enravish', 'ravenish', 'vanisher'], + 'ravi': ['ravi', 'riva', 'vair', 'vari', 'vira'], + 'ravigote': ['ravigote', 'rogative'], + 'ravin': ['invar', 'ravin', 'vanir'], + 'ravine': ['averin', 'ravine'], + 'ravined': ['invader', 'ravined', 'viander'], + 'raving': ['grivna', 'raving'], + 'ravissant': ['ravissant', 'srivatsan'], + 'raw': ['raw', 'war'], + 'rawboned': ['downbear', 'rawboned'], + 'rawish': ['rawish', 'wairsh', 'warish'], + 'rax': ['arx', 'rax'], + 'ray': ['ary', 'ray', 'yar'], + 'raya': ['arya', 'raya'], + 'rayan': ['aryan', 'nayar', 'rayan'], + 'rayed': ['deary', 'deray', 'rayed', 'ready', 'yeard'], + 'raylet': ['lyrate', 'raylet', 'realty', 'telary'], + 'rayonnance': ['annoyancer', 'rayonnance'], + 'raze': ['ezra', 'raze'], + 're': ['er', 're'], + 'rea': ['aer', 'are', 'ear', 'era', 'rea'], + 'reaal': ['areal', 'reaal'], + 'reabandon': ['abandoner', 'reabandon'], + 'reabolish': ['abolisher', 'reabolish'], + 'reabsent': ['absenter', 'reabsent'], + 'reabsorb': ['absorber', 'reabsorb'], + 'reaccession': ['accessioner', 'reaccession'], + 'reaccomplish': ['accomplisher', 'reaccomplish'], + 'reaccord': ['accorder', 'reaccord'], + 'reaccost': ['ectosarc', 'reaccost'], + 'reach': ['acher', 'arche', 'chare', 'chera', 'rache', 'reach'], + 'reachieve': ['echeveria', 'reachieve'], + 'react': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'reactance': ['cancerate', 'reactance'], + 'reaction': ['actioner', 'anerotic', 'ceration', 'creation', 'reaction'], + 'reactional': ['creational', 'crotalinae', 'laceration', 'reactional'], + 'reactionary': ['creationary', 'reactionary'], + 'reactionism': ['anisometric', + 'creationism', + 'miscreation', + 'ramisection', + 'reactionism'], + 'reactionist': ['creationist', 'reactionist'], + 'reactive': ['creative', 'reactive'], + 'reactively': ['creatively', 'reactively'], + 'reactiveness': ['creativeness', 'reactiveness'], + 'reactivity': ['creativity', 'reactivity'], + 'reactor': ['creator', 'reactor'], + 'read': ['ared', 'daer', 'dare', 'dear', 'read'], + 'readapt': ['adapter', 'predata', 'readapt'], + 'readd': ['adder', 'dread', 'readd'], + 'readdress': ['addresser', 'readdress'], + 'reader': ['reader', 'redare', 'reread'], + 'reading': ['degrain', 'deraign', 'deringa', 'gradine', 'grained', 'reading'], + 'readjust': ['adjuster', 'readjust'], + 'readopt': ['adopter', 'protead', 'readopt'], + 'readorn': ['adorner', 'readorn'], + 'ready': ['deary', 'deray', 'rayed', 'ready', 'yeard'], + 'reaffect': ['affecter', 'reaffect'], + 'reaffirm': ['affirmer', 'reaffirm'], + 'reafflict': ['afflicter', 'reafflict'], + 'reagent': ['grantee', 'greaten', 'reagent', 'rentage'], + 'reagin': ['arenig', 'earing', 'gainer', 'reagin', 'regain'], + 'reak': ['rake', 'reak'], + 'real': ['earl', 'eral', 'lear', 'real'], + 'reales': ['alerse', 'leaser', 'reales', 'resale', 'reseal', 'sealer'], + 'realest': ['realest', 'reslate', 'resteal', 'stealer', 'teasler'], + 'realign': ['aligner', 'engrail', 'realign', 'reginal'], + 'realignment': ['engrailment', 'realignment'], + 'realism': ['mislear', 'realism'], + 'realist': ['aletris', 'alister', 'listera', 'realist', 'saltier'], + 'realistic': ['eristical', 'realistic'], + 'reality': ['irately', 'reality'], + 'realive': ['realive', 'valerie'], + 'realization': ['rationalize', 'realization'], + 'reallot': ['reallot', 'rotella', 'tallero'], + 'reallow': ['allower', 'reallow'], + 'reallude': ['laureled', 'reallude'], + 'realmlet': ['realmlet', 'tremella'], + 'realter': ['alterer', 'realter', 'relater'], + 'realtor': ['realtor', 'relator'], + 'realty': ['lyrate', 'raylet', 'realty', 'telary'], + 'ream': ['erma', 'mare', 'rame', 'ream'], + 'reamage': ['megaera', 'reamage'], + 'reamass': ['amasser', 'reamass'], + 'reamend': ['amender', 'meander', 'reamend', 'reedman'], + 'reamer': ['marree', 'reamer'], + 'reamuse': ['measure', 'reamuse'], + 'reamy': ['mayer', 'reamy'], + 'reanchor': ['anchorer', 'ranchero', 'reanchor'], + 'reanneal': ['annealer', 'lernaean', 'reanneal'], + 'reannex': ['annexer', 'reannex'], + 'reannoy': ['annoyer', 'reannoy'], + 'reanoint': ['anointer', 'inornate', 'nonirate', 'reanoint'], + 'reanswer': ['answerer', 'reanswer'], + 'reanvil': ['elinvar', 'ravelin', 'reanvil', 'valerin'], + 'reap': ['aper', 'pare', 'pear', 'rape', 'reap'], + 'reapdole': ['leoparde', 'reapdole'], + 'reappeal': ['appealer', 'reappeal'], + 'reappear': ['appearer', 'rapparee', 'reappear'], + 'reapplaud': ['applauder', 'reapplaud'], + 'reappoint': ['appointer', 'reappoint'], + 'reapportion': ['apportioner', 'reapportion'], + 'reapprehend': ['apprehender', 'reapprehend'], + 'reapproach': ['approacher', 'reapproach'], + 'rear': ['rare', 'rear'], + 'reargue': ['augerer', 'reargue'], + 'reargument': ['garmenture', 'reargument'], + 'rearise': ['rearise', 'reraise'], + 'rearm': ['armer', 'rearm'], + 'rearray': ['arrayer', 'rearray'], + 'rearrest': ['arrester', 'rearrest'], + 'reascend': ['ascender', 'reascend'], + 'reascent': ['reascent', 'sarcenet'], + 'reascertain': ['ascertainer', 'reascertain', 'secretarian'], + 'reask': ['asker', 'reask', 'saker', 'sekar'], + 'reason': ['arseno', 'reason'], + 'reassail': ['assailer', 'reassail'], + 'reassault': ['assaulter', 'reassault', 'saleratus'], + 'reassay': ['assayer', 'reassay'], + 'reassent': ['assenter', 'reassent', 'sarsenet'], + 'reassert': ['asserter', 'reassert'], + 'reassign': ['assigner', 'reassign'], + 'reassist': ['assister', 'reassist'], + 'reassort': ['assertor', 'assorter', 'oratress', 'reassort'], + 'reastonish': ['astonisher', 'reastonish', 'treasonish'], + 'reasty': ['atresy', 'estray', 'reasty', 'stayer'], + 'reasy': ['reasy', 'resay', 'sayer', 'seary'], + 'reattach': ['attacher', 'reattach'], + 'reattack': ['attacker', 'reattack'], + 'reattain': ['attainer', 'reattain', 'tertiana'], + 'reattempt': ['attempter', 'reattempt'], + 'reattend': ['attender', 'nattered', 'reattend'], + 'reattest': ['attester', 'reattest'], + 'reattract': ['attracter', 'reattract'], + 'reattraction': ['reattraction', 'retractation'], + 'reatus': ['auster', 'reatus'], + 'reavail': ['reavail', 'valeria'], + 'reave': ['eaver', 'reave'], + 'reavoid': ['avodire', 'avoider', 'reavoid'], + 'reavouch': ['avoucher', 'reavouch'], + 'reavow': ['avower', 'reavow'], + 'reawait': ['awaiter', 'reawait'], + 'reawaken': ['awakener', 'reawaken'], + 'reaward': ['awarder', 'reaward'], + 'reb': ['ber', 'reb'], + 'rebab': ['barbe', 'bebar', 'breba', 'rebab'], + 'reback': ['backer', 'reback'], + 'rebag': ['bagre', 'barge', 'begar', 'rebag'], + 'rebait': ['baiter', 'barite', 'rebait', 'terbia'], + 'rebake': ['beaker', 'berake', 'rebake'], + 'reballast': ['ballaster', 'reballast'], + 'reballot': ['balloter', 'reballot'], + 'reban': ['abner', 'arneb', 'reban'], + 'rebanish': ['banisher', 'rebanish'], + 'rebar': ['barer', 'rebar'], + 'rebargain': ['bargainer', 'rebargain'], + 'rebasis': ['brassie', 'rebasis'], + 'rebate': ['beater', 'berate', 'betear', 'rebate', 'rebeat'], + 'rebater': ['rebater', 'terebra'], + 'rebathe': ['breathe', 'rebathe'], + 'rebato': ['boater', 'borate', 'rebato'], + 'rebawl': ['bawler', 'brelaw', 'rebawl', 'warble'], + 'rebear': ['bearer', 'rebear'], + 'rebeat': ['beater', 'berate', 'betear', 'rebate', 'rebeat'], + 'rebeck': ['becker', 'rebeck'], + 'rebed': ['brede', 'breed', 'rebed'], + 'rebeg': ['gerbe', 'grebe', 'rebeg'], + 'rebeggar': ['beggarer', 'rebeggar'], + 'rebegin': ['bigener', 'rebegin'], + 'rebehold': ['beholder', 'rebehold'], + 'rebellow': ['bellower', 'rebellow'], + 'rebelly': ['bellyer', 'rebelly'], + 'rebelong': ['belonger', 'rebelong'], + 'rebend': ['bender', 'berend', 'rebend'], + 'rebenefit': ['benefiter', 'rebenefit'], + 'rebeset': ['besteer', 'rebeset'], + 'rebestow': ['bestower', 'rebestow'], + 'rebetray': ['betrayer', 'eatberry', 'rebetray', 'teaberry'], + 'rebewail': ['bewailer', 'rebewail'], + 'rebia': ['barie', 'beira', 'erbia', 'rebia'], + 'rebias': ['braise', 'rabies', 'rebias'], + 'rebid': ['bider', 'bredi', 'bride', 'rebid'], + 'rebill': ['biller', 'rebill'], + 'rebillet': ['billeter', 'rebillet'], + 'rebind': ['binder', 'inbred', 'rebind'], + 'rebirth': ['brither', 'rebirth'], + 'rebite': ['bertie', 'betire', 'rebite'], + 'reblade': ['bleared', 'reblade'], + 'reblast': ['blaster', 'reblast', 'stabler'], + 'rebleach': ['bleacher', 'rebleach'], + 'reblend': ['blender', 'reblend'], + 'rebless': ['blesser', 'rebless'], + 'reblock': ['blocker', 'brockle', 'reblock'], + 'rebloom': ['bloomer', 'rebloom'], + 'reblot': ['bolter', 'orblet', 'reblot', 'rebolt'], + 'reblow': ['blower', 'bowler', 'reblow', 'worble'], + 'reblue': ['brulee', 'burele', 'reblue'], + 'rebluff': ['bluffer', 'rebluff'], + 'reblunder': ['blunderer', 'reblunder'], + 'reboant': ['baronet', 'reboant'], + 'reboantic': ['bicornate', 'carbonite', 'reboantic'], + 'reboard': ['arbored', 'boarder', 'reboard'], + 'reboast': ['barotse', 'boaster', 'reboast', 'sorbate'], + 'reboil': ['boiler', 'reboil'], + 'rebold': ['belord', 'bordel', 'rebold'], + 'rebolt': ['bolter', 'orblet', 'reblot', 'rebolt'], + 'rebone': ['boreen', 'enrobe', 'neebor', 'rebone'], + 'rebook': ['booker', 'brooke', 'rebook'], + 'rebop': ['probe', 'rebop'], + 'rebore': ['rebore', 'rerobe'], + 'reborrow': ['borrower', 'reborrow'], + 'rebound': ['beround', 'bounder', 'rebound', 'unbored', 'unorbed', 'unrobed'], + 'rebounder': ['rebounder', 'underrobe'], + 'rebox': ['boxer', 'rebox'], + 'rebrace': ['cerebra', 'rebrace'], + 'rebraid': ['braider', 'rebraid'], + 'rebranch': ['brancher', 'rebranch'], + 'rebrand': ['bernard', 'brander', 'rebrand'], + 'rebrandish': ['brandisher', 'rebrandish'], + 'rebreed': ['breeder', 'rebreed'], + 'rebrew': ['brewer', 'rebrew'], + 'rebribe': ['berberi', 'rebribe'], + 'rebring': ['bringer', 'rebring'], + 'rebroach': ['broacher', 'rebroach'], + 'rebroadcast': ['broadcaster', 'rebroadcast'], + 'rebrown': ['browner', 'rebrown'], + 'rebrush': ['brusher', 'rebrush'], + 'rebud': ['bedur', 'rebud', 'redub'], + 'rebudget': ['budgeter', 'rebudget'], + 'rebuff': ['buffer', 'rebuff'], + 'rebuffet': ['buffeter', 'rebuffet'], + 'rebuild': ['builder', 'rebuild'], + 'rebulk': ['bulker', 'rebulk'], + 'rebunch': ['buncher', 'rebunch'], + 'rebundle': ['blendure', 'rebundle'], + 'reburden': ['burdener', 'reburden'], + 'reburn': ['burner', 'reburn'], + 'reburnish': ['burnisher', 'reburnish'], + 'reburst': ['burster', 'reburst'], + 'rebus': ['burse', 'rebus', 'suber'], + 'rebush': ['busher', 'rebush'], + 'rebut': ['brute', 'buret', 'rebut', 'tuber'], + 'rebute': ['rebute', 'retube'], + 'rebuttal': ['burletta', 'rebuttal'], + 'rebutton': ['buttoner', 'rebutton'], + 'rebuy': ['buyer', 'rebuy'], + 'recalk': ['calker', 'lacker', 'rackle', 'recalk', 'reckla'], + 'recall': ['caller', 'cellar', 'recall'], + 'recampaign': ['campaigner', 'recampaign'], + 'recancel': ['canceler', 'clarence', 'recancel'], + 'recant': ['canter', + 'creant', + 'cretan', + 'nectar', + 'recant', + 'tanrec', + 'trance'], + 'recantation': ['recantation', 'triacontane'], + 'recanter': ['canterer', 'recanter', 'recreant', 'terrance'], + 'recap': ['caper', 'crape', 'pacer', 'perca', 'recap'], + 'recaption': ['preaction', 'precation', 'recaption'], + 'recarpet': ['pretrace', 'recarpet'], + 'recart': ['arrect', 'carter', 'crater', 'recart', 'tracer'], + 'recase': ['cesare', 'crease', 'recase', 'searce'], + 'recash': ['arches', 'chaser', 'eschar', 'recash', 'search'], + 'recast': ['carest', 'caster', 'recast'], + 'recatch': ['catcher', 'recatch'], + 'recede': ['decree', 'recede'], + 'recedent': ['centered', 'decenter', 'decentre', 'recedent'], + 'receder': ['decreer', 'receder'], + 'receipt': ['ereptic', 'precite', 'receipt'], + 'receiptor': ['procerite', 'receiptor'], + 'receivables': ['receivables', 'serviceable'], + 'received': ['deceiver', 'received'], + 'recement': ['cementer', 'cerement', 'recement'], + 'recension': ['ninescore', 'recension'], + 'recensionist': ['intercession', 'recensionist'], + 'recent': ['center', 'recent', 'tenrec'], + 'recenter': ['centerer', 'recenter', 'recentre', 'terrence'], + 'recentre': ['centerer', 'recenter', 'recentre', 'terrence'], + 'reception': ['prenotice', 'reception'], + 'receptoral': ['praelector', 'receptoral'], + 'recess': ['cesser', 'recess'], + 'rechain': ['chainer', 'enchair', 'rechain'], + 'rechal': ['rachel', 'rechal'], + 'rechamber': ['chamberer', 'rechamber'], + 'rechange': ['encharge', 'rechange'], + 'rechant': ['chanter', 'rechant'], + 'rechar': ['archer', 'charer', 'rechar'], + 'recharter': ['charterer', 'recharter'], + 'rechase': ['archsee', 'rechase'], + 'rechaser': ['rechaser', 'research', 'searcher'], + 'rechasten': ['chastener', 'rechasten'], + 'rechaw': ['chawer', 'rechaw'], + 'recheat': ['cheater', 'hectare', 'recheat', 'reteach', 'teacher'], + 'recheck': ['checker', 'recheck'], + 'recheer': ['cheerer', 'recheer'], + 'rechew': ['chewer', 'rechew'], + 'rechip': ['cipher', 'rechip'], + 'rechisel': ['chiseler', 'rechisel'], + 'rechristen': ['christener', 'rechristen'], + 'rechuck': ['chucker', 'rechuck'], + 'recidivous': ['recidivous', 'veridicous'], + 'recipe': ['piecer', 'pierce', 'recipe'], + 'recipiend': ['perdicine', 'recipiend'], + 'recipient': ['princeite', 'recipient'], + 'reciprocate': ['carpocerite', 'reciprocate'], + 'recirculate': ['clericature', 'recirculate'], + 'recision': ['recision', 'soricine'], + 'recital': ['article', 'recital'], + 'recitativo': ['recitativo', 'victoriate'], + 'recite': ['cerite', 'certie', 'recite', 'tierce'], + 'recitement': ['centimeter', 'recitement', 'remittence'], + 'reckla': ['calker', 'lacker', 'rackle', 'recalk', 'reckla'], + 'reckless': ['clerkess', 'reckless'], + 'reckling': ['clerking', 'reckling'], + 'reckon': ['conker', 'reckon'], + 'reclaim': ['claimer', 'miracle', 'reclaim'], + 'reclaimer': ['calmierer', 'reclaimer'], + 'reclama': ['cameral', 'caramel', 'carmela', 'ceramal', 'reclama'], + 'reclang': ['cangler', 'glancer', 'reclang'], + 'reclasp': ['clasper', 'reclasp', 'scalper'], + 'reclass': ['carless', 'classer', 'reclass'], + 'reclean': ['cleaner', 'reclean'], + 'reclear': ['clearer', 'reclear'], + 'reclimb': ['climber', 'reclimb'], + 'reclinate': ['intercale', 'interlace', 'lacertine', 'reclinate'], + 'reclinated': ['credential', 'interlaced', 'reclinated'], + 'recluse': ['luceres', 'recluse'], + 'recluseness': ['censureless', 'recluseness'], + 'reclusion': ['cornelius', 'inclosure', 'reclusion'], + 'reclusive': ['reclusive', 'versicule'], + 'recoach': ['caroche', 'coacher', 'recoach'], + 'recoal': ['carole', 'coaler', 'coelar', 'oracle', 'recoal'], + 'recoast': ['coaster', 'recoast'], + 'recoat': ['coater', 'recoat'], + 'recock': ['cocker', 'recock'], + 'recoil': ['coiler', 'recoil'], + 'recoilment': ['clinometer', 'recoilment'], + 'recoin': ['cerion', 'coiner', 'neroic', 'orcein', 'recoin'], + 'recoinage': ['aerogenic', 'recoinage'], + 'recollate': ['electoral', 'recollate'], + 'recollation': ['collationer', 'recollation'], + 'recollection': ['collectioner', 'recollection'], + 'recollet': ['colleter', 'coteller', 'coterell', 'recollet'], + 'recolor': ['colorer', 'recolor'], + 'recomb': ['comber', 'recomb'], + 'recomfort': ['comforter', 'recomfort'], + 'recommand': ['commander', 'recommand'], + 'recommend': ['commender', 'recommend'], + 'recommission': ['commissioner', 'recommission'], + 'recompact': ['compacter', 'recompact'], + 'recompass': ['compasser', 'recompass'], + 'recompetition': ['competitioner', 'recompetition'], + 'recomplain': ['complainer', 'procnemial', 'recomplain'], + 'recompound': ['compounder', 'recompound'], + 'recomprehend': ['comprehender', 'recomprehend'], + 'recon': ['coner', 'crone', 'recon'], + 'reconceal': ['concealer', 'reconceal'], + 'reconcert': ['concreter', 'reconcert'], + 'reconcession': ['concessioner', 'reconcession'], + 'reconcoct': ['concocter', 'reconcoct'], + 'recondemn': ['condemner', 'recondemn'], + 'recondensation': ['nondesecration', 'recondensation'], + 'recondition': ['conditioner', 'recondition'], + 'reconfer': ['confrere', 'enforcer', 'reconfer'], + 'reconfess': ['confesser', 'reconfess'], + 'reconfirm': ['confirmer', 'reconfirm'], + 'reconform': ['conformer', 'reconform'], + 'reconfound': ['confounder', 'reconfound'], + 'reconfront': ['confronter', 'reconfront'], + 'recongeal': ['congealer', 'recongeal'], + 'reconjoin': ['conjoiner', 'reconjoin'], + 'reconnect': ['concenter', 'reconnect'], + 'reconnoiter': ['reconnoiter', 'reconnoitre'], + 'reconnoitre': ['reconnoiter', 'reconnoitre'], + 'reconsent': ['consenter', 'nonsecret', 'reconsent'], + 'reconsider': ['considerer', 'reconsider'], + 'reconsign': ['consigner', 'reconsign'], + 'reconstitution': ['constitutioner', 'reconstitution'], + 'reconstruct': ['constructer', 'reconstruct'], + 'reconsult': ['consulter', 'reconsult'], + 'recontend': ['contender', 'recontend'], + 'recontest': ['contester', 'recontest'], + 'recontinue': ['recontinue', 'unctioneer'], + 'recontract': ['contracter', 'correctant', 'recontract'], + 'reconvention': ['conventioner', 'reconvention'], + 'reconvert': ['converter', 'reconvert'], + 'reconvey': ['conveyer', 'reconvey'], + 'recook': ['cooker', 'recook'], + 'recool': ['cooler', 'recool'], + 'recopper': ['copperer', 'recopper'], + 'recopyright': ['copyrighter', 'recopyright'], + 'record': ['corder', 'record'], + 'recordation': ['corrodentia', 'recordation'], + 'recork': ['corker', 'recork', 'rocker'], + 'recorrection': ['correctioner', 'recorrection'], + 'recorrupt': ['corrupter', 'recorrupt'], + 'recounsel': ['enclosure', 'recounsel'], + 'recount': ['cornute', 'counter', 'recount', 'trounce'], + 'recountal': ['nucleator', 'recountal'], + 'recoup': ['couper', 'croupe', 'poucer', 'recoup'], + 'recourse': ['recourse', 'resource'], + 'recover': ['coverer', 'recover'], + 'recramp': ['cramper', 'recramp'], + 'recrank': ['cranker', 'recrank'], + 'recrate': ['caterer', 'recrate', 'retrace', 'terrace'], + 'recreant': ['canterer', 'recanter', 'recreant', 'terrance'], + 'recredit': ['cedriret', 'directer', 'recredit', 'redirect'], + 'recrew': ['crewer', 'recrew'], + 'recrimination': ['intermorainic', 'recrimination'], + 'recroon': ['coroner', 'crooner', 'recroon'], + 'recross': ['crosser', 'recross'], + 'recrowd': ['crowder', 'recrowd'], + 'recrown': ['crowner', 'recrown'], + 'recrudency': ['decurrency', 'recrudency'], + 'recruital': ['curtailer', 'recruital', 'reticular'], + 'recrush': ['crusher', 'recrush'], + 'recta': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'rectal': ['carlet', 'cartel', 'claret', 'rectal', 'talcer'], + 'rectalgia': ['cartilage', 'rectalgia'], + 'recti': ['citer', 'recti', 'ticer', 'trice'], + 'rectifiable': ['certifiable', 'rectifiable'], + 'rectification': ['certification', 'cretification', 'rectification'], + 'rectificative': ['certificative', 'rectificative'], + 'rectificator': ['certificator', 'rectificator'], + 'rectificatory': ['certificatory', 'rectificatory'], + 'rectified': ['certified', 'rectified'], + 'rectifier': ['certifier', 'rectifier'], + 'rectify': ['certify', 'cretify', 'rectify'], + 'rection': ['cerotin', 'cointer', 'cotrine', 'cretion', 'noticer', 'rection'], + 'rectitude': ['certitude', 'rectitude'], + 'rectoress': ['crosstree', 'rectoress'], + 'rectorship': ['cristopher', 'rectorship'], + 'rectotome': ['octometer', 'rectotome', 'tocometer'], + 'rectovesical': ['rectovesical', 'vesicorectal'], + 'recur': ['curer', 'recur'], + 'recurl': ['curler', 'recurl'], + 'recurse': ['recurse', 'rescuer', 'securer'], + 'recurtain': ['recurtain', 'unerratic'], + 'recurvation': ['countervair', 'overcurtain', 'recurvation'], + 'recurvous': ['recurvous', 'verrucous'], + 'recusance': ['recusance', 'securance'], + 'recusant': ['etruscan', 'recusant'], + 'recusation': ['nectarious', 'recusation'], + 'recusator': ['craterous', 'recusator'], + 'recuse': ['cereus', 'ceruse', 'recuse', 'rescue', 'secure'], + 'recut': ['cruet', 'eruct', 'recut', 'truce'], + 'red': ['erd', 'red'], + 'redact': ['cedrat', 'decart', 'redact'], + 'redaction': ['citronade', 'endaortic', 'redaction'], + 'redactional': ['declaration', 'redactional'], + 'redamage': ['dreamage', 'redamage'], + 'redan': ['andre', 'arend', 'daren', 'redan'], + 'redare': ['reader', 'redare', 'reread'], + 'redarken': ['darkener', 'redarken'], + 'redarn': ['darner', 'darren', 'errand', 'rander', 'redarn'], + 'redart': ['darter', + 'dartre', + 'redart', + 'retard', + 'retrad', + 'tarred', + 'trader'], + 'redate': ['derate', 'redate'], + 'redaub': ['dauber', 'redaub'], + 'redawn': ['andrew', 'redawn', 'wander', 'warden'], + 'redbait': ['redbait', 'tribade'], + 'redbud': ['budder', 'redbud'], + 'redcoat': ['cordate', 'decator', 'redcoat'], + 'redden': ['nedder', 'redden'], + 'reddingite': ['digredient', 'reddingite'], + 'rede': ['deer', 'dere', 'dree', 'rede', 'reed'], + 'redeal': ['dealer', 'leader', 'redeal', 'relade', 'relead'], + 'redeck': ['decker', 'redeck'], + 'redeed': ['redeed', 'reeded'], + 'redeem': ['deemer', 'meered', 'redeem', 'remede'], + 'redefault': ['defaulter', 'redefault'], + 'redefeat': ['defeater', 'federate', 'redefeat'], + 'redefine': ['needfire', 'redefine'], + 'redeflect': ['redeflect', 'reflected'], + 'redelay': ['delayer', 'layered', 'redelay'], + 'redeliver': ['deliverer', 'redeliver'], + 'redemand': ['demander', 'redemand'], + 'redemolish': ['demolisher', 'redemolish'], + 'redeny': ['redeny', 'yender'], + 'redepend': ['depender', 'redepend'], + 'redeprive': ['prederive', 'redeprive'], + 'rederivation': ['rederivation', 'veratroidine'], + 'redescend': ['descender', 'redescend'], + 'redescription': ['prediscretion', 'redescription'], + 'redesign': ['designer', 'redesign', 'resigned'], + 'redesman': ['redesman', 'seamrend'], + 'redetect': ['detecter', 'redetect'], + 'redevelop': ['developer', 'redevelop'], + 'redfin': ['finder', 'friend', 'redfin', 'refind'], + 'redhoop': ['redhoop', 'rhodope'], + 'redia': ['aider', 'deair', 'irade', 'redia'], + 'redient': ['nitered', 'redient', 'teinder'], + 'redig': ['dirge', 'gride', 'redig', 'ridge'], + 'redigest': ['digester', 'redigest'], + 'rediminish': ['diminisher', 'rediminish'], + 'redintegrator': ['redintegrator', 'retrogradient'], + 'redip': ['pride', 'pried', 'redip'], + 'redirect': ['cedriret', 'directer', 'recredit', 'redirect'], + 'redisable': ['desirable', 'redisable'], + 'redisappear': ['disappearer', 'redisappear'], + 'rediscount': ['discounter', 'rediscount'], + 'rediscover': ['discoverer', 'rediscover'], + 'rediscuss': ['discusser', 'rediscuss'], + 'redispatch': ['dispatcher', 'redispatch'], + 'redisplay': ['displayer', 'redisplay'], + 'redispute': ['disrepute', 'redispute'], + 'redistend': ['dendrites', 'distender', 'redistend'], + 'redistill': ['distiller', 'redistill'], + 'redistinguish': ['distinguisher', 'redistinguish'], + 'redistrain': ['distrainer', 'redistrain'], + 'redisturb': ['disturber', 'redisturb'], + 'redive': ['derive', 'redive'], + 'redivert': ['diverter', 'redivert', 'verditer'], + 'redleg': ['gelder', 'ledger', 'redleg'], + 'redlegs': ['redlegs', 'sledger'], + 'redo': ['doer', 'redo', 'rode', 'roed'], + 'redock': ['corked', 'docker', 'redock'], + 'redolent': ['redolent', 'rondelet'], + 'redoom': ['doomer', 'mooder', 'redoom', 'roomed'], + 'redoubling': ['bouldering', 'redoubling'], + 'redoubt': ['doubter', 'obtrude', 'outbred', 'redoubt'], + 'redound': ['redound', 'rounded', 'underdo'], + 'redowa': ['redowa', 'woader'], + 'redraft': ['drafter', 'redraft'], + 'redrag': ['darger', 'gerard', 'grader', 'redrag', 'regard'], + 'redraw': ['drawer', 'redraw', 'reward', 'warder'], + 'redrawer': ['redrawer', 'rewarder', 'warderer'], + 'redream': ['dreamer', 'redream'], + 'redress': ['dresser', 'redress'], + 'redrill': ['driller', 'redrill'], + 'redrive': ['deriver', 'redrive', 'rivered'], + 'redry': ['derry', 'redry', 'ryder'], + 'redtail': ['dilater', 'lardite', 'redtail'], + 'redtop': ['deport', 'ported', 'redtop'], + 'redub': ['bedur', 'rebud', 'redub'], + 'reductant': ['reductant', 'traducent', 'truncated'], + 'reduction': ['introduce', 'reduction'], + 'reductional': ['radiolucent', 'reductional'], + 'redue': ['redue', 'urdee'], + 'redunca': ['durance', 'redunca', 'unraced'], + 'redwithe': ['redwithe', 'withered'], + 'redye': ['redye', 'reedy'], + 'ree': ['eer', 'ere', 'ree'], + 'reechy': ['cheery', 'reechy'], + 'reed': ['deer', 'dere', 'dree', 'rede', 'reed'], + 'reeded': ['redeed', 'reeded'], + 'reeden': ['endere', 'needer', 'reeden'], + 'reedily': ['reedily', 'reyield', 'yielder'], + 'reeding': ['energid', 'reeding'], + 'reedling': ['engirdle', 'reedling'], + 'reedman': ['amender', 'meander', 'reamend', 'reedman'], + 'reedwork': ['reedwork', 'reworked'], + 'reedy': ['redye', 'reedy'], + 'reef': ['feer', 'free', 'reef'], + 'reefing': ['feering', 'feigner', 'freeing', 'reefing', 'refeign'], + 'reek': ['eker', 'reek'], + 'reel': ['leer', 'reel'], + 'reeler': ['reeler', 'rereel'], + 'reelingly': ['leeringly', 'reelingly'], + 'reem': ['mere', 'reem'], + 'reeming': ['reeming', 'regimen'], + 'reen': ['erne', 'neer', 'reen'], + 'reenge': ['neeger', 'reenge', 'renege'], + 'rees': ['erse', 'rees', 'seer', 'sere'], + 'reese': ['esere', 'reese', 'resee'], + 'reesk': ['esker', 'keres', 'reesk', 'seker', 'skeer', 'skere'], + 'reest': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'reester': ['reester', 'steerer'], + 'reestle': ['reestle', 'resteel', 'steeler'], + 'reesty': ['reesty', 'yester'], + 'reet': ['reet', 'teer', 'tree'], + 'reetam': ['reetam', 'retame', 'teamer'], + 'reeveland': ['landreeve', 'reeveland'], + 'refall': ['faller', 'refall'], + 'refashion': ['fashioner', 'refashion'], + 'refasten': ['fastener', 'fenestra', 'refasten'], + 'refavor': ['favorer', 'overfar', 'refavor'], + 'refeed': ['feeder', 'refeed'], + 'refeel': ['feeler', 'refeel', 'reflee'], + 'refeign': ['feering', 'feigner', 'freeing', 'reefing', 'refeign'], + 'refel': ['fleer', 'refel'], + 'refer': ['freer', 'refer'], + 'referment': ['fermenter', 'referment'], + 'refetch': ['fetcher', 'refetch'], + 'refight': ['fighter', 'freight', 'refight'], + 'refill': ['filler', 'refill'], + 'refilter': ['filterer', 'refilter'], + 'refinable': ['inferable', 'refinable'], + 'refind': ['finder', 'friend', 'redfin', 'refind'], + 'refine': ['ferine', 'refine'], + 'refined': ['definer', 'refined'], + 'refiner': ['refiner', 'reinfer'], + 'refinger': ['fingerer', 'refinger'], + 'refining': ['infringe', 'refining'], + 'refinish': ['finisher', 'refinish'], + 'refit': ['freit', 'refit'], + 'refix': ['fixer', 'refix'], + 'reflash': ['flasher', 'reflash'], + 'reflected': ['redeflect', 'reflected'], + 'reflee': ['feeler', 'refeel', 'reflee'], + 'refling': ['ferling', 'flinger', 'refling'], + 'refloat': ['floater', 'florate', 'refloat'], + 'reflog': ['golfer', 'reflog'], + 'reflood': ['flooder', 'reflood'], + 'refloor': ['floorer', 'refloor'], + 'reflourish': ['flourisher', 'reflourish'], + 'reflow': ['flower', 'fowler', 'reflow', 'wolfer'], + 'reflower': ['flowerer', 'reflower'], + 'reflush': ['flusher', 'reflush'], + 'reflux': ['fluxer', 'reflux'], + 'refluxed': ['flexured', 'refluxed'], + 'refly': ['ferly', 'flyer', 'refly'], + 'refocus': ['focuser', 'refocus'], + 'refold': ['folder', 'refold'], + 'refoment': ['fomenter', 'refoment'], + 'refoot': ['footer', 'refoot'], + 'reforecast': ['forecaster', 'reforecast'], + 'reforest': ['forester', 'fosterer', 'reforest'], + 'reforfeit': ['forfeiter', 'reforfeit'], + 'reform': ['former', 'reform'], + 'reformado': ['doorframe', 'reformado'], + 'reformed': ['deformer', 'reformed'], + 'reformism': ['misreform', 'reformism'], + 'reformist': ['reformist', 'restiform'], + 'reforward': ['forwarder', 'reforward'], + 'refound': ['founder', 'refound'], + 'refoundation': ['foundationer', 'refoundation'], + 'refreshen': ['freshener', 'refreshen'], + 'refrighten': ['frightener', 'refrighten'], + 'refront': ['fronter', 'refront'], + 'reft': ['fret', 'reft', 'tref'], + 'refuel': ['ferule', 'fueler', 'refuel'], + 'refund': ['funder', 'refund'], + 'refurbish': ['furbisher', 'refurbish'], + 'refurl': ['furler', 'refurl'], + 'refurnish': ['furnisher', 'refurnish'], + 'refusingly': ['refusingly', 'syringeful'], + 'refutal': ['faulter', 'refutal', 'tearful'], + 'refute': ['fuerte', 'refute'], + 'reg': ['erg', 'ger', 'reg'], + 'regain': ['arenig', 'earing', 'gainer', 'reagin', 'regain'], + 'regal': ['argel', 'ergal', 'garle', 'glare', 'lager', 'large', 'regal'], + 'regalia': ['lairage', 'railage', 'regalia'], + 'regalian': ['algerian', 'geranial', 'regalian'], + 'regalist': ['glaister', 'regalist'], + 'regallop': ['galloper', 'regallop'], + 'regally': ['allergy', 'gallery', 'largely', 'regally'], + 'regalness': ['largeness', 'rangeless', 'regalness'], + 'regard': ['darger', 'gerard', 'grader', 'redrag', 'regard'], + 'regarnish': ['garnisher', 'regarnish'], + 'regather': ['gatherer', 'regather'], + 'regatta': ['garetta', 'rattage', 'regatta'], + 'regelate': ['eglatere', 'regelate', 'relegate'], + 'regelation': ['regelation', 'relegation'], + 'regenesis': ['energesis', 'regenesis'], + 'regent': ['gerent', 'regent'], + 'reges': ['reges', 'serge'], + 'reget': ['egret', 'greet', 'reget'], + 'regga': ['agger', 'gager', 'regga'], + 'reggie': ['greige', 'reggie'], + 'regia': ['geira', 'regia'], + 'regild': ['gilder', 'girdle', 'glider', 'regild', 'ridgel'], + 'regill': ['giller', 'grille', 'regill'], + 'regimen': ['reeming', 'regimen'], + 'regimenal': ['margeline', 'regimenal'], + 'regin': ['grein', 'inger', 'nigre', 'regin', 'reign', 'ringe'], + 'reginal': ['aligner', 'engrail', 'realign', 'reginal'], + 'reginald': ['dragline', 'reginald', 'ringlead'], + 'region': ['ignore', 'region'], + 'regional': ['geraniol', 'regional'], + 'registered': ['deregister', 'registered'], + 'registerer': ['registerer', 'reregister'], + 'regive': ['grieve', 'regive'], + 'regladden': ['gladdener', 'glandered', 'regladden'], + 'reglair': ['grailer', 'reglair'], + 'regle': ['leger', 'regle'], + 'reglet': ['gretel', 'reglet'], + 'regloss': ['glosser', 'regloss'], + 'reglove': ['overleg', 'reglove'], + 'reglow': ['glower', 'reglow'], + 'regma': ['grame', 'marge', 'regma'], + 'regnal': ['angler', 'arleng', 'garnel', 'largen', 'rangle', 'regnal'], + 'regraft': ['grafter', 'regraft'], + 'regrant': ['granter', 'regrant'], + 'regrasp': ['grasper', 'regrasp', 'sparger'], + 'regrass': ['grasser', 'regrass'], + 'regrate': ['greater', 'regrate', 'terrage'], + 'regrating': ['gartering', 'regrating'], + 'regrator': ['garroter', 'regrator'], + 'regreen': ['greener', 'regreen', 'reneger'], + 'regreet': ['greeter', 'regreet'], + 'regrind': ['grinder', 'regrind'], + 'regrinder': ['derringer', 'regrinder'], + 'regrip': ['griper', 'regrip'], + 'regroup': ['grouper', 'regroup'], + 'regrow': ['grower', 'regrow'], + 'reguard': ['guarder', 'reguard'], + 'regula': ['ragule', 'regula'], + 'regulation': ['regulation', 'urogenital'], + 'reguli': ['ligure', 'reguli'], + 'regur': ['regur', 'urger'], + 'regush': ['gusher', 'regush'], + 'reh': ['her', 'reh', 'rhe'], + 'rehale': ['healer', 'rehale', 'reheal'], + 'rehallow': ['hallower', 'rehallow'], + 'rehammer': ['hammerer', 'rehammer'], + 'rehang': ['hanger', 'rehang'], + 'reharden': ['hardener', 'reharden'], + 'reharm': ['harmer', 'reharm'], + 'reharness': ['harnesser', 'reharness'], + 'reharrow': ['harrower', 'reharrow'], + 'reharvest': ['harvester', 'reharvest'], + 'rehash': ['hasher', 'rehash'], + 'rehaul': ['hauler', 'rehaul'], + 'rehazard': ['hazarder', 'rehazard'], + 'rehead': ['adhere', 'header', 'hedera', 'rehead'], + 'reheal': ['healer', 'rehale', 'reheal'], + 'reheap': ['heaper', 'reheap'], + 'rehear': ['hearer', 'rehear'], + 'rehearser': ['rehearser', 'reshearer'], + 'rehearten': ['heartener', 'rehearten'], + 'reheat': ['heater', 'hereat', 'reheat'], + 'reheel': ['heeler', 'reheel'], + 'reheighten': ['heightener', 'reheighten'], + 'rehoist': ['hoister', 'rehoist'], + 'rehollow': ['hollower', 'rehollow'], + 'rehonor': ['honorer', 'rehonor'], + 'rehook': ['hooker', 'rehook'], + 'rehoop': ['hooper', 'rehoop'], + 'rehung': ['hunger', 'rehung'], + 'reid': ['dier', 'dire', 'reid', 'ride'], + 'reif': ['fire', 'reif', 'rife'], + 'reify': ['fiery', 'reify'], + 'reign': ['grein', 'inger', 'nigre', 'regin', 'reign', 'ringe'], + 'reignore': ['erigeron', 'reignore'], + 'reillustrate': ['reillustrate', 'ultrasterile'], + 'reim': ['emir', 'imer', 'mire', 'reim', 'remi', 'riem', 'rime'], + 'reimbody': ['embryoid', 'reimbody'], + 'reimpart': ['imparter', 'reimpart'], + 'reimplant': ['implanter', 'reimplant'], + 'reimply': ['primely', 'reimply'], + 'reimport': ['importer', 'promerit', 'reimport'], + 'reimpose': ['perisome', 'promisee', 'reimpose'], + 'reimpress': ['impresser', 'reimpress'], + 'reimpression': ['reimpression', 'repermission'], + 'reimprint': ['imprinter', 'reimprint'], + 'reimprison': ['imprisoner', 'reimprison'], + 'rein': ['neri', 'rein', 'rine'], + 'reina': ['erian', 'irena', 'reina'], + 'reincentive': ['internecive', 'reincentive'], + 'reincite': ['icterine', 'reincite'], + 'reincrudate': ['antireducer', 'reincrudate', 'untraceried'], + 'reindeer': ['denierer', 'reindeer'], + 'reindict': ['indicter', 'indirect', 'reindict'], + 'reindue': ['reindue', 'uredine'], + 'reinfect': ['frenetic', 'infecter', 'reinfect'], + 'reinfer': ['refiner', 'reinfer'], + 'reinfest': ['infester', 'reinfest'], + 'reinflate': ['interleaf', 'reinflate'], + 'reinflict': ['inflicter', 'reinflict'], + 'reinform': ['informer', 'reinform', 'reniform'], + 'reinhabit': ['inhabiter', 'reinhabit'], + 'reins': ['reins', 'resin', 'rinse', 'risen', 'serin', 'siren'], + 'reinsane': ['anserine', 'reinsane'], + 'reinsert': ['inserter', 'reinsert'], + 'reinsist': ['insister', 'reinsist', 'sinister', 'sisterin'], + 'reinspect': ['prescient', 'reinspect'], + 'reinspector': ['prosecretin', 'reinspector'], + 'reinspirit': ['inspiriter', 'reinspirit'], + 'reinstall': ['installer', 'reinstall'], + 'reinstation': ['reinstation', 'santorinite'], + 'reinstill': ['instiller', 'reinstill'], + 'reinstruct': ['instructer', 'intercrust', 'reinstruct'], + 'reinsult': ['insulter', 'lustrine', 'reinsult'], + 'reintend': ['indenter', 'intender', 'reintend'], + 'reinter': ['reinter', 'terrine'], + 'reinterest': ['interester', 'reinterest'], + 'reinterpret': ['interpreter', 'reinterpret'], + 'reinterrupt': ['interrupter', 'reinterrupt'], + 'reinterview': ['interviewer', 'reinterview'], + 'reintrench': ['intrencher', 'reintrench'], + 'reintrude': ['reintrude', 'unretired'], + 'reinvent': ['inventer', 'reinvent', 'ventrine', 'vintener'], + 'reinvert': ['inverter', 'reinvert', 'trinerve'], + 'reinvest': ['reinvest', 'servient'], + 'reis': ['reis', 'rise', 'seri', 'sier', 'sire'], + 'reit': ['iter', 'reit', 'rite', 'teri', 'tier', 'tire'], + 'reiter': ['errite', 'reiter', 'retier', 'retire', 'tierer'], + 'reiterable': ['reiterable', 'reliberate'], + 'rejail': ['jailer', 'rejail'], + 'rejerk': ['jerker', 'rejerk'], + 'rejoin': ['joiner', 'rejoin'], + 'rejolt': ['jolter', 'rejolt'], + 'rejourney': ['journeyer', 'rejourney'], + 'reki': ['erik', 'kier', 'reki'], + 'rekick': ['kicker', 'rekick'], + 'rekill': ['killer', 'rekill'], + 'rekiss': ['kisser', 'rekiss'], + 'reknit': ['reknit', 'tinker'], + 'reknow': ['knower', 'reknow', 'wroken'], + 'rel': ['ler', 'rel'], + 'relabel': ['labeler', 'relabel'], + 'relace': ['alerce', 'cereal', 'relace'], + 'relacquer': ['lacquerer', 'relacquer'], + 'relade': ['dealer', 'leader', 'redeal', 'relade', 'relead'], + 'reladen': ['leander', 'learned', 'reladen'], + 'relais': ['israel', 'relais', 'resail', 'sailer', 'serail', 'serial'], + 'relament': ['lamenter', 'relament', 'remantle'], + 'relamp': ['lamper', 'palmer', 'relamp'], + 'reland': ['aldern', + 'darnel', + 'enlard', + 'lander', + 'lenard', + 'randle', + 'reland'], + 'relap': ['lepra', 'paler', 'parel', 'parle', 'pearl', 'perla', 'relap'], + 'relapse': ['pleaser', 'preseal', 'relapse'], + 'relapsing': ['espringal', 'presignal', 'relapsing'], + 'relast': ['laster', + 'lastre', + 'rastle', + 'relast', + 'resalt', + 'salter', + 'slater', + 'stelar'], + 'relata': ['latera', 'relata'], + 'relatability': ['alterability', 'bilaterality', 'relatability'], + 'relatable': ['alterable', 'relatable'], + 'relatch': ['clethra', 'latcher', 'ratchel', 'relatch', 'talcher', 'trachle'], + 'relate': ['earlet', 'elater', 'relate'], + 'related': ['delater', 'related', 'treadle'], + 'relater': ['alterer', 'realter', 'relater'], + 'relation': ['oriental', 'relation', 'tirolean'], + 'relationism': ['misrelation', 'orientalism', 'relationism'], + 'relationist': ['orientalist', 'relationist'], + 'relative': ['levirate', 'relative'], + 'relativization': ['relativization', 'revitalization'], + 'relativize': ['relativize', 'revitalize'], + 'relator': ['realtor', 'relator'], + 'relaunch': ['launcher', 'relaunch'], + 'relay': ['early', 'layer', 'relay'], + 'relead': ['dealer', 'leader', 'redeal', 'relade', 'relead'], + 'releap': ['leaper', 'releap', 'repale', 'repeal'], + 'relearn': ['learner', 'relearn'], + 'releather': ['leatherer', 'releather', 'tarheeler'], + 'relection': ['centriole', 'electrion', 'relection'], + 'relegate': ['eglatere', 'regelate', 'relegate'], + 'relegation': ['regelation', 'relegation'], + 'relend': ['lender', 'relend'], + 'reletter': ['letterer', 'reletter'], + 'relevant': ['levanter', 'relevant', 'revelant'], + 'relevation': ['relevation', 'revelation'], + 'relevator': ['relevator', 'revelator', 'veratrole'], + 'relevel': ['leveler', 'relevel'], + 'reliably': ['beryllia', 'reliably'], + 'reliance': ['cerealin', 'cinereal', 'reliance'], + 'reliant': ['entrail', + 'latiner', + 'latrine', + 'ratline', + 'reliant', + 'retinal', + 'trenail'], + 'reliantly': ['interally', 'reliantly'], + 'reliberate': ['reiterable', 'reliberate'], + 'relic': ['crile', 'elric', 'relic'], + 'relick': ['licker', 'relick', 'rickle'], + 'relicted': ['derelict', 'relicted'], + 'relier': ['lierre', 'relier'], + 'relieving': ['inveigler', 'relieving'], + 'relievo': ['overlie', 'relievo'], + 'relift': ['fertil', 'filter', 'lifter', 'relift', 'trifle'], + 'religation': ['genitorial', 'religation'], + 'relight': ['lighter', 'relight', 'rightle'], + 'relighten': ['lightener', 'relighten', 'threeling'], + 'religion': ['ligroine', 'religion'], + 'relimit': ['limiter', 'relimit'], + 'reline': ['lierne', 'reline'], + 'relink': ['linker', 'relink'], + 'relish': ['hirsel', 'hirsle', 'relish'], + 'relishy': ['relishy', 'shirley'], + 'relist': ['lister', 'relist'], + 'relisten': ['enlister', 'esterlin', 'listener', 'relisten'], + 'relive': ['levier', 'relive', 'reveil', 'revile', 'veiler'], + 'reload': ['loader', 'ordeal', 'reload'], + 'reloan': ['lenora', 'loaner', 'orlean', 'reloan'], + 'relocation': ['iconolater', 'relocation'], + 'relock': ['locker', 'relock'], + 'relook': ['looker', 'relook'], + 'relose': ['relose', 'resole'], + 'relost': ['relost', 'reslot', 'rostel', 'sterol', 'torsel'], + 'relot': ['lerot', 'orlet', 'relot'], + 'relower': ['lowerer', 'relower'], + 'reluct': ['cutler', 'reluct'], + 'reluctation': ['countertail', 'reluctation'], + 'relumine': ['lemurine', 'meruline', 'relumine'], + 'rely': ['lyre', 'rely'], + 'remade': ['meader', 'remade'], + 'remagnification': ['germanification', 'remagnification'], + 'remagnify': ['germanify', 'remagnify'], + 'remail': ['mailer', 'remail'], + 'remain': ['ermani', 'marine', 'remain'], + 'remains': ['remains', 'seminar'], + 'remaintain': ['antimerina', 'maintainer', 'remaintain'], + 'reman': ['enarm', 'namer', 'reman'], + 'remand': ['damner', 'manred', 'randem', 'remand'], + 'remanet': ['remanet', 'remeant', 'treeman'], + 'remantle': ['lamenter', 'relament', 'remantle'], + 'remap': ['amper', 'remap'], + 'remarch': ['charmer', 'marcher', 'remarch'], + 'remark': ['marker', 'remark'], + 'remarket': ['marketer', 'remarket'], + 'remarry': ['marryer', 'remarry'], + 'remarshal': ['marshaler', 'remarshal'], + 'remask': ['masker', 'remask'], + 'remass': ['masser', 'remass'], + 'remast': ['martes', 'master', 'remast', 'stream'], + 'remasticate': ['metrectasia', 'remasticate'], + 'rematch': ['matcher', 'rematch'], + 'remeant': ['remanet', 'remeant', 'treeman'], + 'remede': ['deemer', 'meered', 'redeem', 'remede'], + 'remeet': ['meeter', 'remeet', 'teemer'], + 'remelt': ['melter', 'remelt'], + 'remend': ['mender', 'remend'], + 'remetal': ['lameter', 'metaler', 'remetal'], + 'remi': ['emir', 'imer', 'mire', 'reim', 'remi', 'riem', 'rime'], + 'remication': ['marcionite', 'microtinae', 'remication'], + 'remigate': ['emigrate', 'remigate'], + 'remigation': ['emigration', 'remigation'], + 'remill': ['miller', 'remill'], + 'remind': ['minder', 'remind'], + 'remint': ['minter', 'remint', 'termin'], + 'remiped': ['demirep', 'epiderm', 'impeder', 'remiped'], + 'remisrepresent': ['misrepresenter', 'remisrepresent'], + 'remission': ['missioner', 'remission'], + 'remisunderstand': ['misunderstander', 'remisunderstand'], + 'remit': ['merit', 'miter', 'mitre', 'remit', 'timer'], + 'remittal': ['remittal', 'termital'], + 'remittance': ['carminette', 'remittance'], + 'remittence': ['centimeter', 'recitement', 'remittence'], + 'remitter': ['remitter', 'trimeter'], + 'remix': ['mixer', 'remix'], + 'remnant': ['manrent', 'remnant'], + 'remock': ['mocker', 'remock'], + 'remodel': ['demerol', 'modeler', 'remodel'], + 'remold': ['dermol', 'molder', 'remold'], + 'remontant': ['nonmatter', 'remontant'], + 'remontoir': ['interroom', 'remontoir'], + 'remop': ['merop', 'moper', 'proem', 'remop'], + 'remora': ['remora', 'roamer'], + 'remord': ['dormer', 'remord'], + 'remote': ['meteor', 'remote'], + 'remotive': ['overtime', 'remotive'], + 'remould': ['remould', 'ruledom'], + 'remount': ['monture', 'mounter', 'remount'], + 'removable': ['overblame', 'removable'], + 'remunerate': ['remunerate', 'renumerate'], + 'remuneration': ['remuneration', 'renumeration'], + 'remurmur': ['murmurer', 'remurmur'], + 'remus': ['muser', 'remus', 'serum'], + 'remuster': ['musterer', 'remuster'], + 'renable': ['enabler', 'renable'], + 'renably': ['blarney', 'renably'], + 'renail': ['arline', 'larine', 'linear', 'nailer', 'renail'], + 'renaissance': ['necessarian', 'renaissance'], + 'renal': ['learn', 'renal'], + 'rename': ['enarme', 'meaner', 'rename'], + 'renavigate': ['renavigate', 'vegetarian'], + 'rend': ['dern', 'rend'], + 'rendition': ['rendition', 'trinodine'], + 'reneg': ['genre', 'green', 'neger', 'reneg'], + 'renegadism': ['grandeeism', 'renegadism'], + 'renegation': ['generation', 'renegation'], + 'renege': ['neeger', 'reenge', 'renege'], + 'reneger': ['greener', 'regreen', 'reneger'], + 'reneglect': ['neglecter', 'reneglect'], + 'renerve': ['renerve', 'venerer'], + 'renes': ['renes', 'sneer'], + 'renet': ['enter', 'neter', 'renet', 'terne', 'treen'], + 'reniform': ['informer', 'reinform', 'reniform'], + 'renilla': ['ralline', 'renilla'], + 'renin': ['inner', 'renin'], + 'reniportal': ['interpolar', 'reniportal'], + 'renish': ['renish', 'shiner', 'shrine'], + 'renitence': ['centenier', 'renitence'], + 'renitency': ['nycterine', 'renitency'], + 'renitent': ['renitent', 'trentine'], + 'renk': ['kern', 'renk'], + 'rennet': ['rennet', 'tenner'], + 'renography': ['granophyre', 'renography'], + 'renominate': ['enantiomer', 'renominate'], + 'renotation': ['renotation', 'retonation'], + 'renotice': ['erection', 'neoteric', 'nocerite', 'renotice'], + 'renourish': ['nourisher', 'renourish'], + 'renovate': ['overneat', 'renovate'], + 'renovater': ['enervator', 'renovater', 'venerator'], + 'renown': ['renown', 'wonner'], + 'rent': ['rent', 'tern'], + 'rentage': ['grantee', 'greaten', 'reagent', 'rentage'], + 'rental': ['altern', 'antler', 'learnt', 'rental', 'ternal'], + 'rentaler': ['rentaler', 'rerental'], + 'rented': ['denter', 'rented', 'tender'], + 'rentee': ['entree', 'rentee', 'retene'], + 'renter': ['renter', 'rerent'], + 'renu': ['renu', 'ruen', 'rune'], + 'renumber': ['numberer', 'renumber'], + 'renumerate': ['remunerate', 'renumerate'], + 'renumeration': ['remuneration', 'renumeration'], + 'reobtain': ['abrotine', 'baritone', 'obtainer', 'reobtain'], + 'reoccasion': ['occasioner', 'reoccasion'], + 'reoccupation': ['cornucopiate', 'reoccupation'], + 'reoffend': ['offender', 'reoffend'], + 'reoffer': ['offerer', 'reoffer'], + 'reoil': ['oiler', 'oriel', 'reoil'], + 'reopen': ['opener', 'reopen', 'repone'], + 'reordain': ['inroader', 'ordainer', 'reordain'], + 'reorder': ['orderer', 'reorder'], + 'reordinate': ['reordinate', 'treronidae'], + 'reornament': ['ornamenter', 'reornament'], + 'reoverflow': ['overflower', 'reoverflow'], + 'reown': ['owner', 'reown', 'rowen'], + 'rep': ['per', 'rep'], + 'repack': ['packer', 'repack'], + 'repaint': ['painter', 'pertain', 'pterian', 'repaint'], + 'repair': ['pairer', 'rapier', 'repair'], + 'repairer': ['rareripe', 'repairer'], + 'repale': ['leaper', 'releap', 'repale', 'repeal'], + 'repand': ['pander', 'repand'], + 'repandly': ['panderly', 'repandly'], + 'repandous': ['panderous', 'repandous'], + 'repanel': ['paneler', 'repanel', 'replane'], + 'repaper': ['paperer', 'perpera', 'prepare', 'repaper'], + 'reparagraph': ['paragrapher', 'reparagraph'], + 'reparation': ['praetorian', 'reparation'], + 'repark': ['parker', 'repark'], + 'repartee': ['repartee', 'repeater'], + 'repartition': ['partitioner', 'repartition'], + 'repass': ['passer', 'repass', 'sparse'], + 'repasser': ['asperser', 'repasser'], + 'repast': ['paster', 'repast', 'trapes'], + 'repaste': ['perates', 'repaste', 'sperate'], + 'repasture': ['repasture', 'supertare'], + 'repatch': ['chapter', 'patcher', 'repatch'], + 'repatent': ['pattener', 'repatent'], + 'repattern': ['patterner', 'repattern'], + 'repawn': ['enwrap', 'pawner', 'repawn'], + 'repay': ['apery', 'payer', 'repay'], + 'repeal': ['leaper', 'releap', 'repale', 'repeal'], + 'repeat': ['petrea', 'repeat', 'retape'], + 'repeater': ['repartee', 'repeater'], + 'repel': ['leper', 'perle', 'repel'], + 'repen': ['neper', 'preen', 'repen'], + 'repension': ['pensioner', 'repension'], + 'repent': ['perten', 'repent'], + 'repentable': ['penetrable', 'repentable'], + 'repentance': ['penetrance', 'repentance'], + 'repentant': ['penetrant', 'repentant'], + 'reperceive': ['prereceive', 'reperceive'], + 'repercussion': ['percussioner', 'repercussion'], + 'repercussive': ['repercussive', 'superservice'], + 'reperform': ['performer', 'prereform', 'reperform'], + 'repermission': ['reimpression', 'repermission'], + 'repermit': ['premerit', 'preremit', 'repermit'], + 'reperplex': ['perplexer', 'reperplex'], + 'reperusal': ['pleasurer', 'reperusal'], + 'repetition': ['petitioner', 'repetition'], + 'rephael': ['preheal', 'rephael'], + 'rephase': ['hespera', 'rephase', 'reshape'], + 'rephotograph': ['photographer', 'rephotograph'], + 'rephrase': ['preshare', 'rephrase'], + 'repic': ['price', 'repic'], + 'repick': ['picker', 'repick'], + 'repiece': ['creepie', 'repiece'], + 'repin': ['piner', 'prine', 'repin', 'ripen'], + 'repine': ['neiper', 'perine', 'pirene', 'repine'], + 'repiner': ['repiner', 'ripener'], + 'repiningly': ['repiningly', 'ripeningly'], + 'repique': ['perique', 'repique'], + 'repitch': ['pitcher', 'repitch'], + 'replace': ['percale', 'replace'], + 'replait': ['partile', 'plaiter', 'replait'], + 'replan': ['parnel', 'planer', 'replan'], + 'replane': ['paneler', 'repanel', 'replane'], + 'replant': ['pantler', 'planter', 'replant'], + 'replantable': ['planetabler', 'replantable'], + 'replanter': ['prerental', 'replanter'], + 'replaster': ['plasterer', 'replaster'], + 'replate': ['pearlet', 'pleater', 'prelate', 'ptereal', 'replate', 'repleat'], + 'replay': ['parley', 'pearly', 'player', 'replay'], + 'replead': ['pearled', 'pedaler', 'pleader', 'replead'], + 'repleader': ['predealer', 'repleader'], + 'repleat': ['pearlet', 'pleater', 'prelate', 'ptereal', 'replate', 'repleat'], + 'repleteness': ['repleteness', 'terpeneless'], + 'repletion': ['interlope', 'interpole', 'repletion', 'terpineol'], + 'repliant': ['interlap', 'repliant', 'triplane'], + 'replica': ['caliper', 'picarel', 'replica'], + 'replight': ['plighter', 'replight'], + 'replod': ['podler', 'polder', 'replod'], + 'replot': ['petrol', 'replot'], + 'replow': ['plower', 'replow'], + 'replum': ['lumper', 'plumer', 'replum', 'rumple'], + 'replunder': ['plunderer', 'replunder'], + 'reply': ['plyer', 'reply'], + 'repocket': ['pocketer', 'repocket'], + 'repoint': ['pointer', 'protein', 'pterion', 'repoint', 'tropine'], + 'repolish': ['polisher', 'repolish'], + 'repoll': ['poller', 'repoll'], + 'reponder': ['ponderer', 'reponder'], + 'repone': ['opener', 'reopen', 'repone'], + 'report': ['porret', 'porter', 'report', 'troper'], + 'reportage': ['porterage', 'reportage'], + 'reporterism': ['misreporter', 'reporterism'], + 'reportion': ['portioner', 'reportion'], + 'reposed': ['deposer', 'reposed'], + 'reposit': ['periost', 'porites', 'reposit', 'riposte'], + 'reposition': ['positioner', 'reposition'], + 'repositor': ['posterior', 'repositor'], + 'repossession': ['possessioner', 'repossession'], + 'repost': ['poster', 'presto', 'repost', 'respot', 'stoper'], + 'repot': ['poter', 'prote', 'repot', 'tepor', 'toper', 'trope'], + 'repound': ['pounder', 'repound', 'unroped'], + 'repour': ['pourer', 'repour', 'rouper'], + 'repowder': ['powderer', 'repowder'], + 'repp': ['prep', 'repp'], + 'repray': ['prayer', 'repray'], + 'repreach': ['preacher', 'repreach'], + 'repredict': ['precredit', 'predirect', 'repredict'], + 'reprefer': ['prerefer', 'reprefer'], + 'represent': ['presenter', 'represent'], + 'representationism': ['misrepresentation', 'representationism'], + 'repress': ['presser', 'repress'], + 'repressive': ['repressive', 'respersive'], + 'reprice': ['piercer', 'reprice'], + 'reprieval': ['prevailer', 'reprieval'], + 'reprime': ['premier', 'reprime'], + 'reprint': ['printer', 'reprint'], + 'reprise': ['reprise', 'respire'], + 'repristination': ['interspiration', 'repristination'], + 'reproachable': ['blepharocera', 'reproachable'], + 'reprobate': ['perborate', 'prorebate', 'reprobate'], + 'reprobation': ['probationer', 'reprobation'], + 'reproceed': ['proceeder', 'reproceed'], + 'reproclaim': ['proclaimer', 'reproclaim'], + 'reproduce': ['procedure', 'reproduce'], + 'reproduction': ['proreduction', 'reproduction'], + 'reprohibit': ['prohibiter', 'reprohibit'], + 'reproof': ['proofer', 'reproof'], + 'reproportion': ['proportioner', 'reproportion'], + 'reprotection': ['interoceptor', 'reprotection'], + 'reprotest': ['protester', 'reprotest'], + 'reprovision': ['prorevision', 'provisioner', 'reprovision'], + 'reps': ['reps', 'resp'], + 'reptant': ['pattern', 'reptant'], + 'reptatorial': ['proletariat', 'reptatorial'], + 'reptatory': ['protreaty', 'reptatory'], + 'reptile': ['perlite', 'reptile'], + 'reptilia': ['liparite', 'reptilia'], + 'republish': ['publisher', 'republish'], + 'repudiatory': ['preauditory', 'repudiatory'], + 'repuff': ['puffer', 'repuff'], + 'repugn': ['punger', 'repugn'], + 'repulpit': ['pulpiter', 'repulpit'], + 'repulsion': ['prelusion', 'repulsion'], + 'repulsive': ['prelusive', 'repulsive'], + 'repulsively': ['prelusively', 'repulsively'], + 'repulsory': ['prelusory', 'repulsory'], + 'repump': ['pumper', 'repump'], + 'repunish': ['punisher', 'repunish'], + 'reputative': ['reputative', 'vituperate'], + 'repute': ['repute', 'uptree'], + 'requench': ['quencher', 'requench'], + 'request': ['quester', 'request'], + 'requestion': ['questioner', 'requestion'], + 'require': ['querier', 'require'], + 'requital': ['quartile', 'requital', 'triequal'], + 'requite': ['quieter', 'requite'], + 'rerack': ['racker', 'rerack'], + 'rerail': ['railer', 'rerail'], + 'reraise': ['rearise', 'reraise'], + 'rerake': ['karree', 'rerake'], + 'rerank': ['ranker', 'rerank'], + 'rerate': ['rerate', 'retare', 'tearer'], + 'reread': ['reader', 'redare', 'reread'], + 'rereel': ['reeler', 'rereel'], + 'reregister': ['registerer', 'reregister'], + 'rerent': ['renter', 'rerent'], + 'rerental': ['rentaler', 'rerental'], + 'rering': ['erring', 'rering', 'ringer'], + 'rerise': ['rerise', 'sirree'], + 'rerivet': ['rerivet', 'riveter'], + 'rerob': ['borer', 'rerob', 'rober'], + 'rerobe': ['rebore', 'rerobe'], + 'reroll': ['reroll', 'roller'], + 'reroof': ['reroof', 'roofer'], + 'reroot': ['reroot', 'rooter', 'torero'], + 'rerow': ['rerow', 'rower'], + 'rerun': ['rerun', 'runer'], + 'resaca': ['ascare', 'caesar', 'resaca'], + 'resack': ['resack', 'sacker', 'screak'], + 'resail': ['israel', 'relais', 'resail', 'sailer', 'serail', 'serial'], + 'resale': ['alerse', 'leaser', 'reales', 'resale', 'reseal', 'sealer'], + 'resalt': ['laster', + 'lastre', + 'rastle', + 'relast', + 'resalt', + 'salter', + 'slater', + 'stelar'], + 'resanction': ['resanction', 'sanctioner'], + 'resaw': ['resaw', 'sawer', 'seraw', 'sware', 'swear', 'warse'], + 'resawer': ['resawer', 'reswear', 'swearer'], + 'resay': ['reasy', 'resay', 'sayer', 'seary'], + 'rescan': ['casern', 'rescan'], + 'rescind': ['discern', 'rescind'], + 'rescinder': ['discerner', 'rescinder'], + 'rescindment': ['discernment', 'rescindment'], + 'rescratch': ['rescratch', 'scratcher'], + 'rescuable': ['rescuable', 'securable'], + 'rescue': ['cereus', 'ceruse', 'recuse', 'rescue', 'secure'], + 'rescuer': ['recurse', 'rescuer', 'securer'], + 'reseal': ['alerse', 'leaser', 'reales', 'resale', 'reseal', 'sealer'], + 'reseam': ['reseam', 'seamer'], + 'research': ['rechaser', 'research', 'searcher'], + 'reseat': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'resect': ['resect', 'screet', 'secret'], + 'resection': ['resection', 'secretion'], + 'resectional': ['resectional', 'secretional'], + 'reseda': ['erased', 'reseda', 'seared'], + 'resee': ['esere', 'reese', 'resee'], + 'reseed': ['reseed', 'seeder'], + 'reseek': ['reseek', 'seeker'], + 'resell': ['resell', 'seller'], + 'resend': ['resend', 'sender'], + 'resene': ['resene', 'serene'], + 'resent': ['ernest', 'nester', 'resent', 'streen'], + 'reservable': ['reservable', 'reversable'], + 'reserval': ['reserval', 'reversal', 'slaverer'], + 'reserve': ['reserve', 'resever', 'reverse', 'severer'], + 'reserved': ['deserver', 'reserved', 'reversed'], + 'reservedly': ['reservedly', 'reversedly'], + 'reserveful': ['reserveful', 'reverseful'], + 'reserveless': ['reserveless', 'reverseless'], + 'reserver': ['reserver', 'reverser'], + 'reservist': ['reservist', 'reversist'], + 'reset': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'resever': ['reserve', 'resever', 'reverse', 'severer'], + 'resew': ['resew', 'sewer', 'sweer'], + 'resex': ['resex', 'xeres'], + 'resh': ['hers', 'resh', 'sher'], + 'reshape': ['hespera', 'rephase', 'reshape'], + 'reshare': ['reshare', 'reshear', 'shearer'], + 'resharpen': ['resharpen', 'sharpener'], + 'reshear': ['reshare', 'reshear', 'shearer'], + 'reshearer': ['rehearser', 'reshearer'], + 'reshift': ['reshift', 'shifter'], + 'reshingle': ['englisher', 'reshingle'], + 'reship': ['perish', 'reship'], + 'reshipment': ['perishment', 'reshipment'], + 'reshoot': ['orthose', 'reshoot', 'shooter', 'soother'], + 'reshoulder': ['reshoulder', 'shoulderer'], + 'reshower': ['reshower', 'showerer'], + 'reshun': ['reshun', 'rushen'], + 'reshunt': ['reshunt', 'shunter'], + 'reshut': ['reshut', 'suther', 'thurse', 'tusher'], + 'reside': ['desire', 'reside'], + 'resident': ['indesert', 'inserted', 'resident'], + 'resider': ['derries', 'desirer', 'resider', 'serried'], + 'residua': ['residua', 'ursidae'], + 'resift': ['fister', 'resift', 'sifter', 'strife'], + 'resigh': ['resigh', 'sigher'], + 'resign': ['resign', 'resing', 'signer', 'singer'], + 'resignal': ['resignal', 'seringal', 'signaler'], + 'resigned': ['designer', 'redesign', 'resigned'], + 'resile': ['lisere', 'resile'], + 'resiliate': ['israelite', 'resiliate'], + 'resilient': ['listerine', 'resilient'], + 'resilition': ['isonitrile', 'resilition'], + 'resilver': ['resilver', 'silverer', 'sliverer'], + 'resin': ['reins', 'resin', 'rinse', 'risen', 'serin', 'siren'], + 'resina': ['arisen', 'arsine', 'resina', 'serian'], + 'resinate': ['arsenite', 'resinate', 'teresian', 'teresina'], + 'resing': ['resign', 'resing', 'signer', 'singer'], + 'resinic': ['irenics', 'resinic', 'sericin', 'sirenic'], + 'resinize': ['resinize', 'sirenize'], + 'resink': ['resink', 'reskin', 'sinker'], + 'resinlike': ['resinlike', 'sirenlike'], + 'resinoid': ['derision', 'ironside', 'resinoid', 'sirenoid'], + 'resinol': ['resinol', 'serolin'], + 'resinous': ['neurosis', 'resinous'], + 'resinously': ['neurolysis', 'resinously'], + 'resiny': ['resiny', 'sireny'], + 'resist': ['resist', 'restis', 'sister'], + 'resistable': ['assertible', 'resistable'], + 'resistance': ['resistance', 'senatrices'], + 'resistful': ['fruitless', 'resistful'], + 'resisting': ['resisting', 'sistering'], + 'resistless': ['resistless', 'sisterless'], + 'resize': ['resize', 'seizer'], + 'resketch': ['resketch', 'sketcher'], + 'reskin': ['resink', 'reskin', 'sinker'], + 'reslash': ['reslash', 'slasher'], + 'reslate': ['realest', 'reslate', 'resteal', 'stealer', 'teasler'], + 'reslay': ['reslay', 'slayer'], + 'reslot': ['relost', 'reslot', 'rostel', 'sterol', 'torsel'], + 'resmell': ['resmell', 'smeller'], + 'resmelt': ['melters', 'resmelt', 'smelter'], + 'resmooth': ['resmooth', 'romeshot', 'smoother'], + 'resnap': ['resnap', 'respan', 'snaper'], + 'resnatch': ['resnatch', 'snatcher', 'stancher'], + 'resoak': ['arkose', 'resoak', 'soaker'], + 'resoap': ['resoap', 'soaper'], + 'resoften': ['resoften', 'softener'], + 'resoil': ['elisor', 'resoil'], + 'resojourn': ['resojourn', 'sojourner'], + 'resolder': ['resolder', 'solderer'], + 'resole': ['relose', 'resole'], + 'resolicit': ['resolicit', 'soliciter'], + 'resolution': ['resolution', 'solutioner'], + 'resonate': ['orestean', 'resonate', 'stearone'], + 'resort': ['resort', 'roster', 'sorter', 'storer'], + 'resorter': ['resorter', 'restorer', 'retrorse'], + 'resound': ['resound', 'sounder', 'unrosed'], + 'resource': ['recourse', 'resource'], + 'resow': ['owser', 'resow', 'serow', 'sower', 'swore', 'worse'], + 'resp': ['reps', 'resp'], + 'respace': ['escaper', 'respace'], + 'respade': ['psedera', 'respade'], + 'respan': ['resnap', 'respan', 'snaper'], + 'respeak': ['respeak', 'speaker'], + 'respect': ['respect', 'scepter', 'specter'], + 'respectless': ['respectless', 'scepterless'], + 'respell': ['presell', 'respell', 'speller'], + 'respersive': ['repressive', 'respersive'], + 'respin': ['pernis', 'respin', 'sniper'], + 'respiration': ['respiration', 'retinispora'], + 'respire': ['reprise', 'respire'], + 'respirit': ['respirit', 'spiriter'], + 'respite': ['respite', 'septier'], + 'resplend': ['resplend', 'splender'], + 'resplice': ['eclipser', 'pericles', 'resplice'], + 'responde': ['personed', 'responde'], + 'respondence': ['precondense', 'respondence'], + 'responsal': ['apronless', 'responsal'], + 'response': ['pessoner', 'response'], + 'respot': ['poster', 'presto', 'repost', 'respot', 'stoper'], + 'respray': ['respray', 'sprayer'], + 'respread': ['respread', 'spreader'], + 'respring': ['respring', 'springer'], + 'resprout': ['posturer', 'resprout', 'sprouter'], + 'respue': ['peruse', 'respue'], + 'resqueak': ['resqueak', 'squeaker'], + 'ressaut': ['erastus', 'ressaut'], + 'rest': ['rest', 'sert', 'stre'], + 'restack': ['restack', 'stacker'], + 'restaff': ['restaff', 'staffer'], + 'restain': ['asterin', 'eranist', 'restain', 'stainer', 'starnie', 'stearin'], + 'restake': ['restake', 'sakeret'], + 'restamp': ['restamp', 'stamper'], + 'restart': ['restart', 'starter'], + 'restate': ['estreat', 'restate', 'retaste'], + 'resteal': ['realest', 'reslate', 'resteal', 'stealer', 'teasler'], + 'resteel': ['reestle', 'resteel', 'steeler'], + 'resteep': ['estrepe', 'resteep', 'steeper'], + 'restem': ['mester', 'restem', 'temser', 'termes'], + 'restep': ['pester', 'preset', 'restep', 'streep'], + 'restful': ['fluster', 'restful'], + 'restiad': ['astride', 'diaster', 'disrate', 'restiad', 'staired'], + 'restiffen': ['restiffen', 'stiffener'], + 'restiform': ['reformist', 'restiform'], + 'resting': ['resting', 'stinger'], + 'restio': ['restio', 'sorite', 'sortie', 'triose'], + 'restis': ['resist', 'restis', 'sister'], + 'restitch': ['restitch', 'stitcher'], + 'restive': ['restive', 'servite'], + 'restock': ['restock', 'stocker'], + 'restorer': ['resorter', 'restorer', 'retrorse'], + 'restow': ['restow', 'stower', 'towser', 'worset'], + 'restowal': ['restowal', 'sealwort'], + 'restraighten': ['restraighten', 'straightener'], + 'restrain': ['restrain', 'strainer', 'transire'], + 'restraint': ['restraint', 'retransit', 'trainster', 'transiter'], + 'restream': ['masterer', 'restream', 'streamer'], + 'restrengthen': ['restrengthen', 'strengthener'], + 'restress': ['restress', 'stresser'], + 'restretch': ['restretch', 'stretcher'], + 'restring': ['restring', 'ringster', 'stringer'], + 'restrip': ['restrip', 'striper'], + 'restrive': ['restrive', 'reverist'], + 'restuff': ['restuff', 'stuffer'], + 'resty': ['resty', 'strey'], + 'restyle': ['restyle', 'tersely'], + 'resucceed': ['resucceed', 'succeeder'], + 'resuck': ['resuck', 'sucker'], + 'resue': ['resue', 'reuse'], + 'resuffer': ['resuffer', 'sufferer'], + 'resuggest': ['resuggest', 'suggester'], + 'resuing': ['insurge', 'resuing'], + 'resuit': ['isuret', 'resuit'], + 'result': ['luster', 'result', 'rustle', 'sutler', 'ulster'], + 'resulting': ['resulting', 'ulstering'], + 'resultless': ['lusterless', 'lustreless', 'resultless'], + 'resummon': ['resummon', 'summoner'], + 'resun': ['nurse', 'resun'], + 'resup': ['purse', 'resup', 'sprue', 'super'], + 'resuperheat': ['resuperheat', 'superheater'], + 'resupinate': ['interpause', 'resupinate'], + 'resupination': ['resupination', 'uranospinite'], + 'resupport': ['resupport', 'supporter'], + 'resuppose': ['resuppose', 'superpose'], + 'resupposition': ['resupposition', 'superposition'], + 'resuppress': ['resuppress', 'suppresser'], + 'resurrender': ['resurrender', 'surrenderer'], + 'resurround': ['resurround', 'surrounder'], + 'resuspect': ['resuspect', 'suspecter'], + 'resuspend': ['resuspend', 'suspender', 'unpressed'], + 'reswallow': ['reswallow', 'swallower'], + 'resward': ['drawers', 'resward'], + 'reswarm': ['reswarm', 'swarmer'], + 'reswear': ['resawer', 'reswear', 'swearer'], + 'resweat': ['resweat', 'sweater'], + 'resweep': ['resweep', 'sweeper'], + 'reswell': ['reswell', 'sweller'], + 'reswill': ['reswill', 'swiller'], + 'retable': ['bearlet', 'bleater', 'elberta', 'retable'], + 'retack': ['racket', 'retack', 'tacker'], + 'retag': ['gater', 'grate', 'great', 'greta', 'retag', 'targe'], + 'retail': ['lirate', 'retail', 'retial', 'tailer'], + 'retailer': ['irrelate', 'retailer'], + 'retain': ['nerita', 'ratine', 'retain', 'retina', 'tanier'], + 'retainal': ['retainal', 'telarian'], + 'retainder': ['irredenta', 'retainder'], + 'retainer': ['arretine', 'eretrian', 'eritrean', 'retainer'], + 'retaining': ['negritian', 'retaining'], + 'retaliate': ['elettaria', 'retaliate'], + 'retalk': ['kartel', 'retalk', 'talker'], + 'retama': ['ramate', 'retama'], + 'retame': ['reetam', 'retame', 'teamer'], + 'retan': ['antre', 'arent', 'retan', 'terna'], + 'retape': ['petrea', 'repeat', 'retape'], + 'retard': ['darter', + 'dartre', + 'redart', + 'retard', + 'retrad', + 'tarred', + 'trader'], + 'retardent': ['retardent', 'tetrander'], + 'retare': ['rerate', 'retare', 'tearer'], + 'retaste': ['estreat', 'restate', 'retaste'], + 'retax': ['extra', 'retax', 'taxer'], + 'retaxation': ['retaxation', 'tetraxonia'], + 'retch': ['chert', 'retch'], + 'reteach': ['cheater', 'hectare', 'recheat', 'reteach', 'teacher'], + 'retelegraph': ['retelegraph', 'telegrapher'], + 'retell': ['retell', 'teller'], + 'retem': ['meter', 'retem'], + 'retemper': ['retemper', 'temperer'], + 'retempt': ['retempt', 'tempter'], + 'retenant': ['retenant', 'tenanter'], + 'retender': ['retender', 'tenderer'], + 'retene': ['entree', 'rentee', 'retene'], + 'retent': ['netter', 'retent', 'tenter'], + 'retention': ['intertone', 'retention'], + 'retepora': ['perorate', 'retepora'], + 'retest': ['retest', 'setter', 'street', 'tester'], + 'rethank': ['rethank', 'thanker'], + 'rethatch': ['rethatch', 'thatcher'], + 'rethaw': ['rethaw', 'thawer', 'wreath'], + 'rethe': ['ether', 'rethe', 'theer', 'there', 'three'], + 'retheness': ['retheness', 'thereness', 'threeness'], + 'rethicken': ['kitchener', 'rethicken', 'thickener'], + 'rethink': ['rethink', 'thinker'], + 'rethrash': ['rethrash', 'thrasher'], + 'rethread': ['rethread', 'threader'], + 'rethreaten': ['rethreaten', 'threatener'], + 'rethresh': ['rethresh', 'thresher'], + 'rethrill': ['rethrill', 'thriller'], + 'rethrow': ['rethrow', 'thrower'], + 'rethrust': ['rethrust', 'thruster'], + 'rethunder': ['rethunder', 'thunderer'], + 'retia': ['arite', 'artie', 'irate', 'retia', 'tarie'], + 'retial': ['lirate', 'retail', 'retial', 'tailer'], + 'reticent': ['reticent', 'tencteri'], + 'reticket': ['reticket', 'ticketer'], + 'reticula': ['arculite', 'cutleria', 'lucretia', 'reticula', 'treculia'], + 'reticular': ['curtailer', 'recruital', 'reticular'], + 'retier': ['errite', 'reiter', 'retier', 'retire', 'tierer'], + 'retighten': ['retighten', 'tightener'], + 'retill': ['retill', 'rillet', 'tiller'], + 'retimber': ['retimber', 'timberer'], + 'retime': ['metier', 'retime', 'tremie'], + 'retin': ['inert', 'inter', 'niter', 'retin', 'trine'], + 'retina': ['nerita', 'ratine', 'retain', 'retina', 'tanier'], + 'retinal': ['entrail', + 'latiner', + 'latrine', + 'ratline', + 'reliant', + 'retinal', + 'trenail'], + 'retinalite': ['retinalite', 'trilineate'], + 'retinene': ['internee', 'retinene'], + 'retinian': ['neritina', 'retinian'], + 'retinispora': ['respiration', 'retinispora'], + 'retinite': ['intertie', 'retinite'], + 'retinker': ['retinker', 'tinkerer'], + 'retinochorioiditis': ['chorioidoretinitis', 'retinochorioiditis'], + 'retinoid': ['neritoid', 'retinoid'], + 'retinue': ['neurite', 'retinue', 'reunite', 'uterine'], + 'retinula': ['lutrinae', 'retinula', 'rutelian', 'tenurial'], + 'retinular': ['retinular', 'trineural'], + 'retip': ['perit', 'retip', 'tripe'], + 'retiral': ['retiral', 'retrial', 'trailer'], + 'retire': ['errite', 'reiter', 'retier', 'retire', 'tierer'], + 'retirer': ['retirer', 'terrier'], + 'retistene': ['retistene', 'serinette'], + 'retoast': ['retoast', 'rosetta', 'stoater', 'toaster'], + 'retold': ['retold', 'rodlet'], + 'retomb': ['retomb', 'trombe'], + 'retonation': ['renotation', 'retonation'], + 'retool': ['looter', 'retool', 'rootle', 'tooler'], + 'retooth': ['retooth', 'toother'], + 'retort': ['retort', 'retrot', 'rotter'], + 'retoss': ['retoss', 'tosser'], + 'retouch': ['retouch', 'toucher'], + 'retour': ['retour', 'router', 'tourer'], + 'retrace': ['caterer', 'recrate', 'retrace', 'terrace'], + 'retrack': ['retrack', 'tracker'], + 'retractation': ['reattraction', 'retractation'], + 'retracted': ['detracter', 'retracted'], + 'retraction': ['retraction', 'triaconter'], + 'retrad': ['darter', + 'dartre', + 'redart', + 'retard', + 'retrad', + 'tarred', + 'trader'], + 'retrade': ['derater', 'retrade', 'retread', 'treader'], + 'retradition': ['retradition', 'traditioner'], + 'retrain': ['arterin', 'retrain', 'terrain', 'trainer'], + 'retral': ['retral', 'terral'], + 'retramp': ['retramp', 'tramper'], + 'retransform': ['retransform', 'transformer'], + 'retransit': ['restraint', 'retransit', 'trainster', 'transiter'], + 'retransplant': ['retransplant', 'transplanter'], + 'retransport': ['retransport', 'transporter'], + 'retravel': ['retravel', 'revertal', 'traveler'], + 'retread': ['derater', 'retrade', 'retread', 'treader'], + 'retreat': ['ettarre', 'retreat', 'treater'], + 'retree': ['retree', 'teerer'], + 'retrench': ['retrench', 'trencher'], + 'retrial': ['retiral', 'retrial', 'trailer'], + 'retrim': ['mitrer', 'retrim', 'trimer'], + 'retrocaecal': ['accelerator', 'retrocaecal'], + 'retrogradient': ['redintegrator', 'retrogradient'], + 'retrorse': ['resorter', 'restorer', 'retrorse'], + 'retrot': ['retort', 'retrot', 'rotter'], + 'retrue': ['retrue', 'ureter'], + 'retrust': ['retrust', 'truster'], + 'retry': ['retry', 'terry'], + 'retter': ['retter', 'terret'], + 'retting': ['gittern', 'gritten', 'retting'], + 'retube': ['rebute', 'retube'], + 'retuck': ['retuck', 'tucker'], + 'retune': ['neuter', 'retune', 'runtee', 'tenure', 'tureen'], + 'returf': ['returf', 'rufter'], + 'return': ['return', 'turner'], + 'retuse': ['retuse', 'tereus'], + 'retwine': ['enwrite', 'retwine'], + 'retwist': ['retwist', 'twister'], + 'retzian': ['retzian', 'terzina'], + 'reub': ['bure', 'reub', 'rube'], + 'reundergo': ['guerdoner', 'reundergo', 'undergoer', 'undergore'], + 'reune': ['enure', 'reune'], + 'reunfold': ['flounder', 'reunfold', 'unfolder'], + 'reunify': ['reunify', 'unfiery'], + 'reunionist': ['reunionist', 'sturionine'], + 'reunite': ['neurite', 'retinue', 'reunite', 'uterine'], + 'reunpack': ['reunpack', 'unpacker'], + 'reuphold': ['reuphold', 'upholder'], + 'reupholster': ['reupholster', 'upholsterer'], + 'reuplift': ['reuplift', 'uplifter'], + 'reuse': ['resue', 'reuse'], + 'reutter': ['reutter', 'utterer'], + 'revacate': ['acervate', 'revacate'], + 'revalidation': ['derivational', 'revalidation'], + 'revamp': ['revamp', 'vamper'], + 'revarnish': ['revarnish', 'varnisher'], + 'reve': ['ever', 'reve', 'veer'], + 'reveal': ['laveer', 'leaver', 'reveal', 'vealer'], + 'reveil': ['levier', 'relive', 'reveil', 'revile', 'veiler'], + 'revel': ['elver', 'lever', 'revel'], + 'revelant': ['levanter', 'relevant', 'revelant'], + 'revelation': ['relevation', 'revelation'], + 'revelator': ['relevator', 'revelator', 'veratrole'], + 'reveler': ['leverer', 'reveler'], + 'revenant': ['revenant', 'venerant'], + 'revend': ['revend', 'vender'], + 'revender': ['revender', 'reverend'], + 'reveneer': ['reveneer', 'veneerer'], + 'revent': ['revent', 'venter'], + 'revenue': ['revenue', 'unreeve'], + 'rever': ['rever', 'verre'], + 'reverend': ['revender', 'reverend'], + 'reverential': ['interleaver', 'reverential'], + 'reverist': ['restrive', 'reverist'], + 'revers': ['revers', 'server', 'verser'], + 'reversable': ['reservable', 'reversable'], + 'reversal': ['reserval', 'reversal', 'slaverer'], + 'reverse': ['reserve', 'resever', 'reverse', 'severer'], + 'reversed': ['deserver', 'reserved', 'reversed'], + 'reversedly': ['reservedly', 'reversedly'], + 'reverseful': ['reserveful', 'reverseful'], + 'reverseless': ['reserveless', 'reverseless'], + 'reverser': ['reserver', 'reverser'], + 'reversewise': ['reversewise', 'revieweress'], + 'reversi': ['reversi', 'reviser'], + 'reversion': ['reversion', 'versioner'], + 'reversist': ['reservist', 'reversist'], + 'revertal': ['retravel', 'revertal', 'traveler'], + 'revest': ['revest', 'servet', 'sterve', 'verset', 'vester'], + 'revet': ['evert', 'revet'], + 'revete': ['revete', 'tervee'], + 'revictual': ['lucrative', 'revictual', 'victualer'], + 'review': ['review', 'viewer'], + 'revieweress': ['reversewise', 'revieweress'], + 'revigorate': ['overgaiter', 'revigorate'], + 'revile': ['levier', 'relive', 'reveil', 'revile', 'veiler'], + 'reviling': ['reviling', 'vierling'], + 'revisal': ['revisal', 'virales'], + 'revise': ['revise', 'siever'], + 'revised': ['deviser', 'diverse', 'revised'], + 'reviser': ['reversi', 'reviser'], + 'revision': ['revision', 'visioner'], + 'revisit': ['revisit', 'visiter'], + 'revisitant': ['revisitant', 'transitive'], + 'revitalization': ['relativization', 'revitalization'], + 'revitalize': ['relativize', 'revitalize'], + 'revocation': ['overaction', 'revocation'], + 'revocative': ['overactive', 'revocative'], + 'revoke': ['evoker', 'revoke'], + 'revolting': ['overglint', 'revolting'], + 'revolute': ['revolute', 'truelove'], + 'revolve': ['evolver', 'revolve'], + 'revomit': ['revomit', 'vomiter'], + 'revote': ['revote', 'vetoer'], + 'revuist': ['revuist', 'stuiver'], + 'rewade': ['drawee', 'rewade'], + 'rewager': ['rewager', 'wagerer'], + 'rewake': ['kerewa', 'rewake'], + 'rewaken': ['rewaken', 'wakener'], + 'rewall': ['rewall', 'waller'], + 'rewallow': ['rewallow', 'wallower'], + 'reward': ['drawer', 'redraw', 'reward', 'warder'], + 'rewarder': ['redrawer', 'rewarder', 'warderer'], + 'rewarm': ['rewarm', 'warmer'], + 'rewarn': ['rewarn', 'warner', 'warren'], + 'rewash': ['hawser', 'rewash', 'washer'], + 'rewater': ['rewater', 'waterer'], + 'rewave': ['rewave', 'weaver'], + 'rewax': ['rewax', 'waxer'], + 'reweaken': ['reweaken', 'weakener'], + 'rewear': ['rewear', 'warree', 'wearer'], + 'rewed': ['dewer', 'ewder', 'rewed'], + 'reweigh': ['reweigh', 'weigher'], + 'reweld': ['reweld', 'welder'], + 'rewet': ['rewet', 'tewer', 'twere'], + 'rewhirl': ['rewhirl', 'whirler'], + 'rewhisper': ['rewhisper', 'whisperer'], + 'rewhiten': ['rewhiten', 'whitener'], + 'rewiden': ['rewiden', 'widener'], + 'rewin': ['erwin', 'rewin', 'winer'], + 'rewind': ['rewind', 'winder'], + 'rewish': ['rewish', 'wisher'], + 'rewithdraw': ['rewithdraw', 'withdrawer'], + 'reword': ['reword', 'worder'], + 'rework': ['rework', 'worker'], + 'reworked': ['reedwork', 'reworked'], + 'rewound': ['rewound', 'unrowed', 'wounder'], + 'rewoven': ['overnew', 'rewoven'], + 'rewrap': ['prewar', 'rewrap', 'warper'], + 'reyield': ['reedily', 'reyield', 'yielder'], + 'rhacianectes': ['rachianectes', 'rhacianectes'], + 'rhaetian': ['earthian', 'rhaetian'], + 'rhaetic': ['certhia', 'rhaetic', 'theriac'], + 'rhamnose': ['horseman', 'rhamnose', 'shoreman'], + 'rhamnoside': ['admonisher', 'rhamnoside'], + 'rhapis': ['parish', 'raphis', 'rhapis'], + 'rhapontic': ['anthropic', 'rhapontic'], + 'rhaponticin': ['panornithic', 'rhaponticin'], + 'rhason': ['rhason', 'sharon', 'shoran'], + 'rhatania': ['ratanhia', 'rhatania'], + 'rhe': ['her', 'reh', 'rhe'], + 'rhea': ['hare', 'hear', 'rhea'], + 'rheen': ['herne', 'rheen'], + 'rheic': ['cheir', 'rheic'], + 'rhein': ['hiren', 'rhein', 'rhine'], + 'rheinic': ['hircine', 'rheinic'], + 'rhema': ['harem', 'herma', 'rhema'], + 'rhematic': ['athermic', 'marchite', 'rhematic'], + 'rheme': ['herem', 'rheme'], + 'rhemist': ['rhemist', 'smither'], + 'rhenium': ['inhumer', 'rhenium'], + 'rheometric': ['chirometer', 'rheometric'], + 'rheophile': ['herophile', 'rheophile'], + 'rheoscope': ['prechoose', 'rheoscope'], + 'rheostatic': ['choristate', 'rheostatic'], + 'rheotactic': ['rheotactic', 'theocratic'], + 'rheotan': ['another', 'athenor', 'rheotan'], + 'rheotropic': ['horopteric', 'rheotropic', 'trichopore'], + 'rhesian': ['arshine', 'nearish', 'rhesian', 'sherani'], + 'rhesus': ['rhesus', 'suresh'], + 'rhetor': ['rhetor', 'rother'], + 'rhetoricals': ['rhetoricals', 'trochlearis'], + 'rhetorize': ['rhetorize', 'theorizer'], + 'rheumatic': ['hematuric', 'rheumatic'], + 'rhine': ['hiren', 'rhein', 'rhine'], + 'rhinestone': ['neornithes', 'rhinestone'], + 'rhineura': ['rhineura', 'unhairer'], + 'rhinocele': ['cholerine', 'rhinocele'], + 'rhinopharyngitis': ['pharyngorhinitis', 'rhinopharyngitis'], + 'rhipidate': ['rhipidate', 'thripidae'], + 'rhizoctonia': ['chorization', 'rhizoctonia', 'zonotrichia'], + 'rhoda': ['hoard', 'rhoda'], + 'rhodaline': ['hodiernal', 'rhodaline'], + 'rhodanthe': ['rhodanthe', 'thornhead'], + 'rhodeose': ['rhodeose', 'seerhood'], + 'rhodes': ['dehors', 'rhodes', 'shoder', 'shored'], + 'rhodic': ['orchid', 'rhodic'], + 'rhodite': ['rhodite', 'theroid'], + 'rhodium': ['humidor', 'rhodium'], + 'rhodope': ['redhoop', 'rhodope'], + 'rhodopsin': ['donorship', 'rhodopsin'], + 'rhoecus': ['choreus', 'chouser', 'rhoecus'], + 'rhopalic': ['orphical', 'rhopalic'], + 'rhus': ['rhus', 'rush'], + 'rhynchotal': ['chloranthy', 'rhynchotal'], + 'rhyton': ['rhyton', 'thorny'], + 'ria': ['air', 'ira', 'ria'], + 'rial': ['aril', 'lair', 'lari', 'liar', 'lira', 'rail', 'rial'], + 'riancy': ['cairny', 'riancy'], + 'riant': ['riant', 'tairn', 'tarin', 'train'], + 'riata': ['arati', 'atria', 'riata', 'tarai', 'tiara'], + 'ribald': ['bildar', 'bridal', 'ribald'], + 'ribaldly': ['bridally', 'ribaldly'], + 'riband': ['brandi', 'riband'], + 'ribat': ['barit', 'ribat'], + 'ribbed': ['dibber', 'ribbed'], + 'ribber': ['briber', 'ribber'], + 'ribble': ['libber', 'ribble'], + 'ribbon': ['ribbon', 'robbin'], + 'ribe': ['beri', 'bier', 'brei', 'ribe'], + 'ribes': ['birse', 'ribes'], + 'riblet': ['beltir', 'riblet'], + 'ribroast': ['arborist', 'ribroast'], + 'ribspare': ['ribspare', 'sparerib'], + 'rice': ['eric', 'rice'], + 'ricer': ['crier', 'ricer'], + 'ricey': ['criey', 'ricey'], + 'richardia': ['charadrii', 'richardia'], + 'richdom': ['chromid', 'richdom'], + 'richen': ['enrich', 'nicher', 'richen'], + 'riches': ['riches', 'shicer'], + 'richt': ['crith', 'richt'], + 'ricine': ['irenic', 'ricine'], + 'ricinoleate': ['arenicolite', 'ricinoleate'], + 'rickets': ['rickets', 'sticker'], + 'rickle': ['licker', 'relick', 'rickle'], + 'rictal': ['citral', 'rictal'], + 'rictus': ['citrus', 'curtis', 'rictus', 'rustic'], + 'ridable': ['bedrail', 'bridale', 'ridable'], + 'ridably': ['bardily', 'rabidly', 'ridably'], + 'riddam': ['madrid', 'riddam'], + 'riddance': ['adendric', 'riddance'], + 'riddel': ['lidder', 'riddel', 'riddle'], + 'ridden': ['dinder', 'ridden', 'rinded'], + 'riddle': ['lidder', 'riddel', 'riddle'], + 'ride': ['dier', 'dire', 'reid', 'ride'], + 'rideau': ['auride', 'rideau'], + 'riden': ['diner', 'riden', 'rinde'], + 'rident': ['dirten', 'rident', 'tinder'], + 'rider': ['drier', 'rider'], + 'ridered': ['deirdre', 'derider', 'derride', 'ridered'], + 'ridge': ['dirge', 'gride', 'redig', 'ridge'], + 'ridgel': ['gilder', 'girdle', 'glider', 'regild', 'ridgel'], + 'ridgelike': ['dirgelike', 'ridgelike'], + 'ridger': ['girder', 'ridger'], + 'ridging': ['girding', 'ridging'], + 'ridgingly': ['girdingly', 'ridgingly'], + 'ridgling': ['girdling', 'ridgling'], + 'ridgy': ['igdyr', 'ridgy'], + 'rie': ['ire', 'rie'], + 'riem': ['emir', 'imer', 'mire', 'reim', 'remi', 'riem', 'rime'], + 'rife': ['fire', 'reif', 'rife'], + 'rifeness': ['finesser', 'rifeness'], + 'rifle': ['filer', 'flier', 'lifer', 'rifle'], + 'rifleman': ['inflamer', 'rifleman'], + 'rift': ['frit', 'rift'], + 'rigadoon': ['gordonia', 'organoid', 'rigadoon'], + 'rigation': ['rigation', 'trigonia'], + 'rigbane': ['bearing', 'begrain', 'brainge', 'rigbane'], + 'right': ['girth', 'grith', 'right'], + 'rightle': ['lighter', 'relight', 'rightle'], + 'rigling': ['girling', 'rigling'], + 'rigolette': ['gloriette', 'rigolette'], + 'rik': ['irk', 'rik'], + 'rikisha': ['rikisha', 'shikari'], + 'rikk': ['kirk', 'rikk'], + 'riksha': ['rakish', 'riksha', 'shikar', 'shikra', 'sikhra'], + 'rile': ['lier', 'lire', 'rile'], + 'rillet': ['retill', 'rillet', 'tiller'], + 'rillett': ['rillett', 'trillet'], + 'rillock': ['rillock', 'rollick'], + 'rim': ['mir', 'rim'], + 'rima': ['amir', 'irma', 'mari', 'mira', 'rami', 'rima'], + 'rimal': ['armil', 'marli', 'rimal'], + 'rimate': ['imaret', 'metria', 'mirate', 'rimate'], + 'rime': ['emir', 'imer', 'mire', 'reim', 'remi', 'riem', 'rime'], + 'rimmed': ['dimmer', 'immerd', 'rimmed'], + 'rimose': ['isomer', 'rimose'], + 'rimple': ['limper', 'prelim', 'rimple'], + 'rimu': ['muir', 'rimu'], + 'rimula': ['rimula', 'uramil'], + 'rimy': ['miry', 'rimy', 'yirm'], + 'rinaldo': ['nailrod', 'ordinal', 'rinaldo', 'rodinal'], + 'rinceau': ['aneuric', 'rinceau'], + 'rincon': ['cornin', 'rincon'], + 'rinde': ['diner', 'riden', 'rinde'], + 'rinded': ['dinder', 'ridden', 'rinded'], + 'rindle': ['linder', 'rindle'], + 'rine': ['neri', 'rein', 'rine'], + 'ring': ['girn', 'grin', 'ring'], + 'ringable': ['balinger', 'ringable'], + 'ringe': ['grein', 'inger', 'nigre', 'regin', 'reign', 'ringe'], + 'ringed': ['engird', 'ringed'], + 'ringer': ['erring', 'rering', 'ringer'], + 'ringgoer': ['gorgerin', 'ringgoer'], + 'ringhead': ['headring', 'ringhead'], + 'ringite': ['igniter', 'ringite', 'tigrine'], + 'ringle': ['linger', 'ringle'], + 'ringlead': ['dragline', 'reginald', 'ringlead'], + 'ringlet': ['ringlet', 'tingler', 'tringle'], + 'ringster': ['restring', 'ringster', 'stringer'], + 'ringtail': ['ringtail', 'trailing'], + 'ringy': ['girny', 'ringy'], + 'rink': ['kirn', 'rink'], + 'rinka': ['inkra', 'krina', 'nakir', 'rinka'], + 'rinse': ['reins', 'resin', 'rinse', 'risen', 'serin', 'siren'], + 'rio': ['rio', 'roi'], + 'riot': ['riot', 'roit', 'trio'], + 'rioting': ['ignitor', 'rioting'], + 'rip': ['pir', 'rip'], + 'ripa': ['pair', 'pari', 'pria', 'ripa'], + 'ripal': ['april', 'pilar', 'ripal'], + 'ripe': ['peri', 'pier', 'ripe'], + 'ripelike': ['pierlike', 'ripelike'], + 'ripen': ['piner', 'prine', 'repin', 'ripen'], + 'ripener': ['repiner', 'ripener'], + 'ripeningly': ['repiningly', 'ripeningly'], + 'riper': ['prier', 'riper'], + 'ripgut': ['ripgut', 'upgirt'], + 'ripost': ['ripost', 'triops', 'tripos'], + 'riposte': ['periost', 'porites', 'reposit', 'riposte'], + 'rippet': ['rippet', 'tipper'], + 'ripple': ['lipper', 'ripple'], + 'ripplet': ['ripplet', 'tippler', 'tripple'], + 'ripup': ['ripup', 'uprip'], + 'rise': ['reis', 'rise', 'seri', 'sier', 'sire'], + 'risen': ['reins', 'resin', 'rinse', 'risen', 'serin', 'siren'], + 'rishi': ['irish', 'rishi', 'sirih'], + 'risk': ['kris', 'risk'], + 'risky': ['risky', 'sirky'], + 'risper': ['risper', 'sprier'], + 'risque': ['risque', 'squire'], + 'risquee': ['esquire', 'risquee'], + 'rissel': ['rissel', 'rissle'], + 'rissle': ['rissel', 'rissle'], + 'rissoa': ['aissor', 'rissoa'], + 'rist': ['rist', 'stir'], + 'rit': ['rit', 'tri'], + 'rita': ['airt', 'rita', 'tari', 'tiar'], + 'rite': ['iter', 'reit', 'rite', 'teri', 'tier', 'tire'], + 'riteless': ['riteless', 'tireless'], + 'ritelessness': ['ritelessness', 'tirelessness'], + 'ritling': ['glitnir', 'ritling'], + 'ritualize': ['ritualize', 'uralitize'], + 'riva': ['ravi', 'riva', 'vair', 'vari', 'vira'], + 'rivage': ['argive', 'rivage'], + 'rival': ['rival', 'viral'], + 'rive': ['rive', 'veri', 'vier', 'vire'], + 'rivel': ['levir', 'liver', 'livre', 'rivel'], + 'riven': ['riven', 'viner'], + 'rivered': ['deriver', 'redrive', 'rivered'], + 'rivet': ['rivet', 'tirve', 'tiver'], + 'riveter': ['rerivet', 'riveter'], + 'rivetless': ['rivetless', 'silvester'], + 'riving': ['irving', 'riving', 'virgin'], + 'rivingly': ['rivingly', 'virginly'], + 'rivose': ['rivose', 'virose'], + 'riyal': ['lairy', 'riyal'], + 'ro': ['or', 'ro'], + 'roach': ['achor', 'chora', 'corah', 'orach', 'roach'], + 'road': ['dora', 'orad', 'road'], + 'roadability': ['adorability', 'roadability'], + 'roadable': ['adorable', 'roadable'], + 'roader': ['adorer', 'roader'], + 'roading': ['gordian', 'idorgan', 'roading'], + 'roadite': ['roadite', 'toadier'], + 'roadman': ['anadrom', 'madrona', 'mandora', 'monarda', 'roadman'], + 'roadster': ['dartrose', 'roadster'], + 'roam': ['amor', 'maro', 'mora', 'omar', 'roam'], + 'roamage': ['georama', 'roamage'], + 'roamer': ['remora', 'roamer'], + 'roaming': ['ingomar', 'moringa', 'roaming'], + 'roan': ['nora', 'orna', 'roan'], + 'roast': ['astor', 'roast'], + 'roastable': ['astrolabe', 'roastable'], + 'roasting': ['orangist', 'organist', 'roasting', 'signator'], + 'rob': ['bor', 'orb', 'rob'], + 'robalo': ['barolo', 'robalo'], + 'roband': ['bandor', 'bondar', 'roband'], + 'robbin': ['ribbon', 'robbin'], + 'robe': ['boer', 'bore', 'robe'], + 'rober': ['borer', 'rerob', 'rober'], + 'roberd': ['border', 'roberd'], + 'roberta': ['arboret', 'roberta', 'taborer'], + 'robin': ['biron', 'inorb', 'robin'], + 'robinet': ['bornite', 'robinet'], + 'robing': ['boring', 'robing'], + 'roble': ['blore', 'roble'], + 'robot': ['boort', 'robot'], + 'robotian': ['abortion', 'robotian'], + 'robotism': ['bimotors', 'robotism'], + 'robur': ['burro', 'robur', 'rubor'], + 'roc': ['cor', 'cro', 'orc', 'roc'], + 'rochea': ['chorea', 'ochrea', 'rochea'], + 'rochet': ['hector', 'rochet', 'tocher', 'troche'], + 'rock': ['cork', 'rock'], + 'rocker': ['corker', 'recork', 'rocker'], + 'rocketer': ['rocketer', 'rocktree'], + 'rockiness': ['corkiness', 'rockiness'], + 'rocking': ['corking', 'rocking'], + 'rockish': ['corkish', 'rockish'], + 'rocktree': ['rocketer', 'rocktree'], + 'rockwood': ['corkwood', 'rockwood', 'woodrock'], + 'rocky': ['corky', 'rocky'], + 'rocta': ['actor', 'corta', 'croat', 'rocta', 'taroc', 'troca'], + 'rod': ['dor', 'rod'], + 'rode': ['doer', 'redo', 'rode', 'roed'], + 'rodentia': ['andorite', 'nadorite', 'ordinate', 'rodentia'], + 'rodential': ['lorandite', 'rodential'], + 'rodinal': ['nailrod', 'ordinal', 'rinaldo', 'rodinal'], + 'rodingite': ['negritoid', 'rodingite'], + 'rodless': ['drossel', 'rodless'], + 'rodlet': ['retold', 'rodlet'], + 'rodman': ['random', 'rodman'], + 'rodney': ['rodney', 'yonder'], + 'roe': ['oer', 'ore', 'roe'], + 'roed': ['doer', 'redo', 'rode', 'roed'], + 'roey': ['oyer', 'roey', 'yore'], + 'rog': ['gor', 'rog'], + 'rogan': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'rogative': ['ravigote', 'rogative'], + 'roger': ['gorer', 'roger'], + 'roggle': ['logger', 'roggle'], + 'rogue': ['orgue', 'rogue', 'rouge'], + 'rohan': ['nahor', 'norah', 'rohan'], + 'rohob': ['bohor', 'rohob'], + 'rohun': ['huron', 'rohun'], + 'roi': ['rio', 'roi'], + 'roid': ['dori', 'roid'], + 'roil': ['loir', 'lori', 'roil'], + 'roister': ['roister', 'storier'], + 'roit': ['riot', 'roit', 'trio'], + 'rok': ['kor', 'rok'], + 'roka': ['karo', 'kora', 'okra', 'roka'], + 'roke': ['kore', 'roke'], + 'rokey': ['rokey', 'yoker'], + 'roky': ['kory', 'roky', 'york'], + 'roland': ['androl', 'arnold', 'lardon', 'roland', 'ronald'], + 'rolandic': ['ironclad', 'rolandic'], + 'role': ['lore', 'orle', 'role'], + 'rolfe': ['forel', 'rolfe'], + 'roller': ['reroll', 'roller'], + 'rollick': ['rillock', 'rollick'], + 'romaean': ['neorama', 'romaean'], + 'romain': ['marion', 'romain'], + 'romaine': ['moraine', 'romaine'], + 'romal': ['molar', 'moral', 'romal'], + 'roman': ['manor', 'moran', 'norma', 'ramon', 'roman'], + 'romancist': ['narcotism', 'romancist'], + 'romancy': ['acronym', 'romancy'], + 'romandom': ['monodram', 'romandom'], + 'romane': ['enamor', 'monera', 'oreman', 'romane'], + 'romanes': ['masoner', 'romanes'], + 'romanian': ['maronian', 'romanian'], + 'romanic': ['amicron', 'marconi', 'minorca', 'romanic'], + 'romanist': ['maronist', 'romanist'], + 'romanistic': ['marcionist', 'romanistic'], + 'romanite': ['maronite', 'martinoe', 'minorate', 'morenita', 'romanite'], + 'romanity': ['minatory', 'romanity'], + 'romanly': ['almonry', 'romanly'], + 'romantic': ['macrotin', 'romantic'], + 'romanticly': ['matrocliny', 'romanticly'], + 'romantism': ['matronism', 'romantism'], + 'rome': ['mero', 'more', 'omer', 'rome'], + 'romeite': ['moieter', 'romeite'], + 'romeo': ['moore', 'romeo'], + 'romero': ['romero', 'roomer'], + 'romeshot': ['resmooth', 'romeshot', 'smoother'], + 'romeward': ['marrowed', 'romeward'], + 'romic': ['micro', 'moric', 'romic'], + 'romish': ['hirmos', 'romish'], + 'rompish': ['orphism', 'rompish'], + 'ron': ['nor', 'ron'], + 'ronald': ['androl', 'arnold', 'lardon', 'roland', 'ronald'], + 'roncet': ['conter', 'cornet', 'cronet', 'roncet'], + 'ronco': ['conor', 'croon', 'ronco'], + 'rond': ['dorn', 'rond'], + 'rondache': ['anchored', 'rondache'], + 'ronde': ['drone', 'ronde'], + 'rondeau': ['rondeau', 'unoared'], + 'rondel': ['rondel', 'rondle'], + 'rondelet': ['redolent', 'rondelet'], + 'rondeletia': ['delineator', 'rondeletia'], + 'rondelle': ['enrolled', 'rondelle'], + 'rondle': ['rondel', 'rondle'], + 'rondo': ['donor', 'rondo'], + 'rondure': ['rondure', 'rounder', 'unorder'], + 'rone': ['oner', 'rone'], + 'ronga': ['angor', + 'argon', + 'goran', + 'grano', + 'groan', + 'nagor', + 'orang', + 'organ', + 'rogan', + 'ronga'], + 'rood': ['door', 'odor', 'oord', 'rood'], + 'roodstone': ['doorstone', 'roodstone'], + 'roofer': ['reroof', 'roofer'], + 'rooflet': ['footler', 'rooflet'], + 'rook': ['kroo', 'rook'], + 'rooker': ['korero', 'rooker'], + 'rool': ['loro', 'olor', 'orlo', 'rool'], + 'room': ['moor', 'moro', 'room'], + 'roomage': ['moorage', 'roomage'], + 'roomed': ['doomer', 'mooder', 'redoom', 'roomed'], + 'roomer': ['romero', 'roomer'], + 'roomlet': ['roomlet', 'tremolo'], + 'roomstead': ['astrodome', 'roomstead'], + 'roomward': ['roomward', 'wardroom'], + 'roomy': ['moory', 'roomy'], + 'roost': ['roost', 'torso'], + 'root': ['root', 'roto', 'toro'], + 'rooter': ['reroot', 'rooter', 'torero'], + 'rootle': ['looter', 'retool', 'rootle', 'tooler'], + 'rootlet': ['rootlet', 'tootler'], + 'rootworm': ['moorwort', 'rootworm', 'tomorrow', 'wormroot'], + 'rope': ['pore', 'rope'], + 'ropeable': ['operable', 'ropeable'], + 'ropelike': ['porelike', 'ropelike'], + 'ropeman': ['manrope', 'ropeman'], + 'roper': ['porer', 'prore', 'roper'], + 'ropes': ['poser', 'prose', 'ropes', 'spore'], + 'ropiness': ['poriness', 'pression', 'ropiness'], + 'roping': ['poring', 'roping'], + 'ropp': ['prop', 'ropp'], + 'ropy': ['pory', 'pyro', 'ropy'], + 'roquet': ['quoter', 'roquet', 'torque'], + 'rosa': ['asor', 'rosa', 'soar', 'sora'], + 'rosabel': ['borlase', 'labrose', 'rosabel'], + 'rosal': ['rosal', 'solar', 'soral'], + 'rosales': ['lassoer', 'oarless', 'rosales'], + 'rosalie': ['rosalie', 'seriola'], + 'rosaniline': ['enaliornis', 'rosaniline'], + 'rosated': ['rosated', 'torsade'], + 'rose': ['eros', 'rose', 'sero', 'sore'], + 'roseal': ['roseal', 'solera'], + 'rosed': ['doser', 'rosed'], + 'rosehead': ['rosehead', 'sorehead'], + 'roseine': ['erinose', 'roseine'], + 'rosel': ['loser', 'orsel', 'rosel', 'soler'], + 'roselite': ['literose', 'roselite', 'tirolese'], + 'roselle': ['orselle', 'roselle'], + 'roseola': ['aerosol', 'roseola'], + 'roset': ['roset', 'rotse', 'soter', 'stero', 'store', 'torse'], + 'rosetan': ['noreast', 'rosetan', 'seatron', 'senator', 'treason'], + 'rosetime': ['rosetime', 'timorese', 'tiresome'], + 'rosetta': ['retoast', 'rosetta', 'stoater', 'toaster'], + 'rosette': ['rosette', 'tetrose'], + 'rosetum': ['oestrum', 'rosetum'], + 'rosety': ['oyster', 'rosety'], + 'rosin': ['ornis', 'rosin'], + 'rosinate': ['arsonite', 'asterion', 'oestrian', 'rosinate', 'serotina'], + 'rosine': ['rosine', 'senior', 'soneri'], + 'rosiness': ['insessor', 'rosiness'], + 'rosmarine': ['morrisean', 'rosmarine'], + 'rosolite': ['oestriol', 'rosolite'], + 'rosorial': ['rosorial', 'sororial'], + 'rossite': ['rossite', 'sorites'], + 'rostel': ['relost', 'reslot', 'rostel', 'sterol', 'torsel'], + 'roster': ['resort', 'roster', 'sorter', 'storer'], + 'rostra': ['rostra', 'sartor'], + 'rostrate': ['rostrate', 'trostera'], + 'rosulate': ['oestrual', 'rosulate'], + 'rosy': ['rosy', 'sory'], + 'rot': ['ort', 'rot', 'tor'], + 'rota': ['rota', 'taro', 'tora'], + 'rotacism': ['acrotism', 'rotacism'], + 'rotal': ['latro', 'rotal', 'toral'], + 'rotala': ['aortal', 'rotala'], + 'rotalian': ['notarial', 'rational', 'rotalian'], + 'rotan': ['orant', 'rotan', 'toran', 'trona'], + 'rotanev': ['rotanev', 'venator'], + 'rotarian': ['rotarian', 'tornaria'], + 'rotate': ['rotate', 'tetrao'], + 'rotch': ['chort', 'rotch', 'torch'], + 'rote': ['rote', 'tore'], + 'rotella': ['reallot', 'rotella', 'tallero'], + 'rotge': ['ergot', 'rotge'], + 'rother': ['rhetor', 'rother'], + 'roto': ['root', 'roto', 'toro'], + 'rotse': ['roset', 'rotse', 'soter', 'stero', 'store', 'torse'], + 'rottan': ['attorn', 'ratton', 'rottan'], + 'rotten': ['rotten', 'terton'], + 'rotter': ['retort', 'retrot', 'rotter'], + 'rottle': ['lotter', 'rottle', 'tolter'], + 'rotula': ['rotula', 'torula'], + 'rotulian': ['rotulian', 'uranotil'], + 'rotuliform': ['rotuliform', 'toruliform'], + 'rotulus': ['rotulus', 'torulus'], + 'rotund': ['rotund', 'untrod'], + 'rotunda': ['rotunda', 'tandour'], + 'rotundate': ['rotundate', 'unrotated'], + 'rotundifoliate': ['rotundifoliate', 'titanofluoride'], + 'rotundo': ['orotund', 'rotundo'], + 'roub': ['buro', 'roub'], + 'roud': ['dour', 'duro', 'ordu', 'roud'], + 'rouge': ['orgue', 'rogue', 'rouge'], + 'rougeot': ['outgoer', 'rougeot'], + 'roughen': ['enrough', 'roughen'], + 'roughie': ['higuero', 'roughie'], + 'rouky': ['rouky', 'yurok'], + 'roulade': ['roulade', 'urodela'], + 'rounce': ['conure', 'rounce', 'uncore'], + 'rounded': ['redound', 'rounded', 'underdo'], + 'roundel': ['durenol', 'lounder', 'roundel'], + 'rounder': ['rondure', 'rounder', 'unorder'], + 'roundhead': ['roundhead', 'unhoarded'], + 'roundseam': ['meandrous', 'roundseam'], + 'roundup': ['roundup', 'unproud'], + 'roup': ['pour', 'roup'], + 'rouper': ['pourer', 'repour', 'rouper'], + 'roupet': ['pouter', 'roupet', 'troupe'], + 'rousedness': ['rousedness', 'souredness'], + 'rouser': ['rouser', 'sourer'], + 'rousing': ['nigrous', 'rousing', 'souring'], + 'rousseau': ['eosaurus', 'rousseau'], + 'roust': ['roust', 'rusot', 'stour', 'sutor', 'torus'], + 'rouster': ['rouster', 'trouser'], + 'rousting': ['rousting', 'stouring'], + 'rout': ['rout', 'toru', 'tour'], + 'route': ['outer', 'outre', 'route'], + 'router': ['retour', 'router', 'tourer'], + 'routh': ['routh', 'throu'], + 'routhie': ['outhire', 'routhie'], + 'routine': ['routine', 'tueiron'], + 'routing': ['outgrin', 'outring', 'routing', 'touring'], + 'routinist': ['introitus', 'routinist'], + 'rove': ['over', 'rove'], + 'rovet': ['overt', 'rovet', 'torve', 'trove', 'voter'], + 'row': ['row', 'wro'], + 'rowdily': ['rowdily', 'wordily'], + 'rowdiness': ['rowdiness', 'wordiness'], + 'rowdy': ['dowry', 'rowdy', 'wordy'], + 'rowed': ['dower', 'rowed'], + 'rowel': ['lower', 'owler', 'rowel'], + 'rowelhead': ['rowelhead', 'wheelroad'], + 'rowen': ['owner', 'reown', 'rowen'], + 'rower': ['rerow', 'rower'], + 'rowet': ['rowet', 'tower', 'wrote'], + 'rowing': ['ingrow', 'rowing'], + 'rowlet': ['rowlet', 'trowel', 'wolter'], + 'rowley': ['lowery', 'owlery', 'rowley', 'yowler'], + 'roxy': ['oryx', 'roxy'], + 'roy': ['ory', 'roy', 'yor'], + 'royalist': ['royalist', 'solitary'], + 'royet': ['royet', 'toyer'], + 'royt': ['royt', 'ryot', 'tory', 'troy', 'tyro'], + 'rua': ['aru', 'rua', 'ura'], + 'ruana': ['anura', 'ruana'], + 'rub': ['bur', 'rub'], + 'rubasse': ['rubasse', 'surbase'], + 'rubato': ['outbar', 'rubato', 'tabour'], + 'rubbed': ['dubber', 'rubbed'], + 'rubble': ['burble', 'lubber', 'rubble'], + 'rubbler': ['burbler', 'rubbler'], + 'rubbly': ['burbly', 'rubbly'], + 'rube': ['bure', 'reub', 'rube'], + 'rubella': ['rubella', 'rulable'], + 'rubescent': ['rubescent', 'subcenter'], + 'rubiate': ['abiuret', 'aubrite', 'biurate', 'rubiate'], + 'rubiator': ['rubiator', 'torrubia'], + 'rubican': ['brucina', 'rubican'], + 'rubied': ['burdie', 'buried', 'rubied'], + 'rubification': ['rubification', 'urbification'], + 'rubify': ['rubify', 'urbify'], + 'rubine': ['burnie', 'rubine'], + 'ruble': ['bluer', 'brule', 'burel', 'ruble'], + 'rubor': ['burro', 'robur', 'rubor'], + 'rubrical': ['bicrural', 'rubrical'], + 'ruby': ['bury', 'ruby'], + 'ructation': ['anticourt', 'curtation', 'ructation'], + 'ruction': ['courtin', 'ruction'], + 'rud': ['rud', 'urd'], + 'rudas': ['rudas', 'sudra'], + 'ruddle': ['dudler', 'ruddle'], + 'rude': ['duer', 'dure', 'rude', 'urde'], + 'rudish': ['hurdis', 'rudish'], + 'rudista': ['dasturi', 'rudista'], + 'rudity': ['durity', 'rudity'], + 'rue': ['rue', 'ure'], + 'ruen': ['renu', 'ruen', 'rune'], + 'ruffed': ['duffer', 'ruffed'], + 'rufter': ['returf', 'rufter'], + 'rug': ['gur', 'rug'], + 'ruga': ['gaur', 'guar', 'ruga'], + 'rugate': ['argute', 'guetar', 'rugate', 'tuareg'], + 'rugged': ['grudge', 'rugged'], + 'ruggle': ['gurgle', 'lugger', 'ruggle'], + 'rugose': ['grouse', 'rugose'], + 'ruinate': ['ruinate', 'taurine', 'uranite', 'urinate'], + 'ruination': ['ruination', 'urination'], + 'ruinator': ['ruinator', 'urinator'], + 'ruined': ['diurne', 'inured', 'ruined', 'unride'], + 'ruing': ['irgun', 'ruing', 'unrig'], + 'ruinous': ['ruinous', 'urinous'], + 'ruinousness': ['ruinousness', 'urinousness'], + 'rulable': ['rubella', 'rulable'], + 'rule': ['lure', 'rule'], + 'ruledom': ['remould', 'ruledom'], + 'ruler': ['lurer', 'ruler'], + 'ruling': ['ruling', 'urling'], + 'rulingly': ['luringly', 'rulingly'], + 'rum': ['mru', 'rum'], + 'rumal': ['mural', 'rumal'], + 'ruman': ['muran', 'ruman', 'unarm', 'unram', 'urman'], + 'rumble': ['lumber', 'rumble', 'umbrel'], + 'rumelian': ['lemurian', 'malurine', 'rumelian'], + 'rumex': ['murex', 'rumex'], + 'ruminant': ['nutramin', 'ruminant'], + 'ruminator': ['antirumor', 'ruminator'], + 'rumly': ['murly', 'rumly'], + 'rumple': ['lumper', 'plumer', 'replum', 'rumple'], + 'run': ['run', 'urn'], + 'runback': ['backrun', 'runback'], + 'runby': ['burny', 'runby'], + 'runch': ['churn', 'runch'], + 'runcinate': ['encurtain', 'runcinate', 'uncertain'], + 'rundale': ['launder', 'rundale'], + 'rundi': ['rundi', 'unrid'], + 'rundlet': ['rundlet', 'trundle'], + 'rune': ['renu', 'ruen', 'rune'], + 'runed': ['runed', 'under', 'unred'], + 'runer': ['rerun', 'runer'], + 'runfish': ['furnish', 'runfish'], + 'rung': ['grun', 'rung'], + 'runic': ['curin', 'incur', 'runic'], + 'runically': ['runically', 'unlyrical'], + 'runite': ['runite', 'triune', 'uniter', 'untire'], + 'runkly': ['knurly', 'runkly'], + 'runlet': ['runlet', 'turnel'], + 'runnet': ['runnet', 'tunner', 'unrent'], + 'runout': ['outrun', 'runout'], + 'runover': ['overrun', 'runover'], + 'runt': ['runt', 'trun', 'turn'], + 'runted': ['runted', 'tunder', 'turned'], + 'runtee': ['neuter', 'retune', 'runtee', 'tenure', 'tureen'], + 'runway': ['runway', 'unwary'], + 'rupa': ['prau', 'rupa'], + 'rupee': ['puree', 'rupee'], + 'rupestrian': ['rupestrian', 'supertrain'], + 'rupiah': ['hairup', 'rupiah'], + 'rural': ['rural', 'urlar'], + 'rus': ['rus', 'sur', 'urs'], + 'rusa': ['rusa', 'saur', 'sura', 'ursa', 'usar'], + 'ruscus': ['cursus', 'ruscus'], + 'ruse': ['ruse', 'suer', 'sure', 'user'], + 'rush': ['rhus', 'rush'], + 'rushen': ['reshun', 'rushen'], + 'rusine': ['insure', 'rusine', 'ursine'], + 'rusma': ['musar', 'ramus', 'rusma', 'surma'], + 'rusot': ['roust', 'rusot', 'stour', 'sutor', 'torus'], + 'russelia': ['russelia', 'siruelas'], + 'russet': ['russet', 'tusser'], + 'russify': ['fissury', 'russify'], + 'russine': ['russine', 'serinus', 'sunrise'], + 'rustable': ['baluster', 'rustable'], + 'rustic': ['citrus', 'curtis', 'rictus', 'rustic'], + 'rusticial': ['curialist', 'rusticial'], + 'rusticly': ['crustily', 'rusticly'], + 'rusticness': ['crustiness', 'rusticness'], + 'rustle': ['luster', 'result', 'rustle', 'sutler', 'ulster'], + 'rustling': ['lustring', 'rustling'], + 'rustly': ['rustly', 'sultry'], + 'rut': ['rut', 'tur'], + 'ruta': ['ruta', 'taur'], + 'rutch': ['cruth', 'rutch'], + 'rutelian': ['lutrinae', 'retinula', 'rutelian', 'tenurial'], + 'rutelinae': ['lineature', 'rutelinae'], + 'ruth': ['hurt', 'ruth'], + 'ruthenian': ['hunterian', 'ruthenian'], + 'ruther': ['hurter', 'ruther'], + 'ruthful': ['hurtful', 'ruthful'], + 'ruthfully': ['hurtfully', 'ruthfully'], + 'ruthfulness': ['hurtfulness', 'ruthfulness'], + 'ruthless': ['hurtless', 'ruthless'], + 'ruthlessly': ['hurtlessly', 'ruthlessly'], + 'ruthlessness': ['hurtlessness', 'ruthlessness'], + 'rutilant': ['rutilant', 'turntail'], + 'rutinose': ['rutinose', 'tursenoi'], + 'rutter': ['rutter', 'turret'], + 'rutyl': ['rutyl', 'truly'], + 'rutylene': ['neuterly', 'rutylene'], + 'ryal': ['aryl', 'lyra', 'ryal', 'yarl'], + 'ryder': ['derry', 'redry', 'ryder'], + 'rye': ['rye', 'yer'], + 'ryen': ['ryen', 'yern'], + 'ryot': ['royt', 'ryot', 'tory', 'troy', 'tyro'], + 'rype': ['prey', 'pyre', 'rype'], + 'rytina': ['rytina', 'trainy', 'tyrian'], + 'sa': ['as', 'sa'], + 'saa': ['asa', 'saa'], + 'saan': ['anas', 'ansa', 'saan'], + 'sab': ['bas', 'sab'], + 'saba': ['abas', 'saba'], + 'sabal': ['balas', 'balsa', 'basal', 'sabal'], + 'saban': ['nasab', 'saban'], + 'sabanut': ['sabanut', 'sabutan', 'tabanus'], + 'sabe': ['base', 'besa', 'sabe', 'seba'], + 'sabeca': ['casabe', 'sabeca'], + 'sabella': ['basella', 'sabella', 'salable'], + 'sabelli': ['sabelli', 'sebilla'], + 'sabellid': ['sabellid', 'slidable'], + 'saber': ['barse', 'besra', 'saber', 'serab'], + 'sabered': ['debaser', 'sabered'], + 'sabian': ['sabian', 'sabina'], + 'sabina': ['sabian', 'sabina'], + 'sabino': ['basion', 'bonsai', 'sabino'], + 'sabir': ['baris', 'sabir'], + 'sable': ['blase', 'sable'], + 'saboraim': ['ambrosia', 'saboraim'], + 'sabot': ['basto', 'boast', 'sabot'], + 'sabotine': ['obeisant', 'sabotine'], + 'sabromin': ['ambrosin', 'barosmin', 'sabromin'], + 'sabulite': ['sabulite', 'suitable'], + 'sabutan': ['sabanut', 'sabutan', 'tabanus'], + 'sacalait': ['castalia', 'sacalait'], + 'saccade': ['cascade', 'saccade'], + 'saccomyian': ['saccomyian', 'saccomyina'], + 'saccomyina': ['saccomyian', 'saccomyina'], + 'sacculoutricular': ['sacculoutricular', 'utriculosaccular'], + 'sacellum': ['camellus', 'sacellum'], + 'sachem': ['sachem', 'schema'], + 'sachet': ['chaste', 'sachet', 'scathe', 'scheat'], + 'sacian': ['ascian', 'sacian', 'scania', 'sicana'], + 'sack': ['cask', 'sack'], + 'sackbut': ['sackbut', 'subtack'], + 'sacken': ['sacken', 'skance'], + 'sacker': ['resack', 'sacker', 'screak'], + 'sacking': ['casking', 'sacking'], + 'sacklike': ['casklike', 'sacklike'], + 'sacque': ['casque', 'sacque'], + 'sacral': ['lascar', 'rascal', 'sacral', 'scalar'], + 'sacrification': ['sacrification', 'scarification'], + 'sacrificator': ['sacrificator', 'scarificator'], + 'sacripant': ['sacripant', 'spartanic'], + 'sacro': ['arcos', 'crosa', 'oscar', 'sacro'], + 'sacrodorsal': ['dorsosacral', 'sacrodorsal'], + 'sacroischiac': ['isosaccharic', 'sacroischiac'], + 'sacrolumbal': ['lumbosacral', 'sacrolumbal'], + 'sacrovertebral': ['sacrovertebral', 'vertebrosacral'], + 'sad': ['das', 'sad'], + 'sadden': ['desand', 'sadden', 'sanded'], + 'saddling': ['addlings', 'saddling'], + 'sadh': ['dash', 'sadh', 'shad'], + 'sadhe': ['deash', 'hades', 'sadhe', 'shade'], + 'sadic': ['asdic', 'sadic'], + 'sadie': ['aides', 'aside', 'sadie'], + 'sadiron': ['sadiron', 'sardoin'], + 'sado': ['dosa', 'sado', 'soda'], + 'sadr': ['sadr', 'sard'], + 'saeima': ['asemia', 'saeima'], + 'saernaite': ['arseniate', 'saernaite'], + 'saeter': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'saeume': ['amusee', 'saeume'], + 'safar': ['safar', 'saraf'], + 'safely': ['fayles', 'safely'], + 'saft': ['fast', 'saft'], + 'sag': ['gas', 'sag'], + 'sagai': ['sagai', 'saiga'], + 'sagene': ['sagene', 'senega'], + 'sagger': ['sagger', 'seggar'], + 'sagless': ['gasless', 'glasses', 'sagless'], + 'sago': ['sago', 'soga'], + 'sagoin': ['gosain', 'isagon', 'sagoin'], + 'sagra': ['argas', 'sagra'], + 'sah': ['ash', 'sah', 'sha'], + 'saharic': ['arachis', 'asiarch', 'saharic'], + 'sahh': ['hash', 'sahh', 'shah'], + 'sahidic': ['hasidic', 'sahidic'], + 'sahme': ['sahme', 'shame'], + 'saho': ['saho', 'shoa'], + 'sai': ['sai', 'sia'], + 'saic': ['acis', 'asci', 'saic'], + 'said': ['dais', 'dasi', 'disa', 'said', 'sida'], + 'saidi': ['saidi', 'saiid'], + 'saiga': ['sagai', 'saiga'], + 'saiid': ['saidi', 'saiid'], + 'sail': ['lasi', 'lias', 'lisa', 'sail', 'sial'], + 'sailable': ['isabella', 'sailable'], + 'sailage': ['algesia', 'sailage'], + 'sailed': ['aisled', 'deasil', 'ladies', 'sailed'], + 'sailer': ['israel', 'relais', 'resail', 'sailer', 'serail', 'serial'], + 'sailing': ['aisling', 'sailing'], + 'sailoring': ['sailoring', 'signorial'], + 'sailsman': ['nasalism', 'sailsman'], + 'saily': ['islay', 'saily'], + 'saim': ['mias', 'saim', 'siam', 'sima'], + 'sain': ['anis', 'nais', 'nasi', 'nias', 'sain', 'sina'], + 'sainfoin': ['sainfoin', 'sinfonia'], + 'saint': ['saint', 'satin', 'stain'], + 'saintdom': ['donatism', 'saintdom'], + 'sainted': ['destain', 'instead', 'sainted', 'satined'], + 'saintless': ['saintless', 'saltiness', 'slatiness', 'stainless'], + 'saintlike': ['kleistian', 'saintlike', 'satinlike'], + 'saintly': ['nastily', 'saintly', 'staynil'], + 'saintship': ['hispanist', 'saintship'], + 'saip': ['apis', 'pais', 'pasi', 'saip'], + 'saiph': ['aphis', 'apish', 'hispa', 'saiph', 'spahi'], + 'sair': ['rais', 'sair', 'sari'], + 'saite': ['saite', 'taise'], + 'saithe': ['saithe', 'tashie', 'teaish'], + 'saitic': ['isatic', 'saitic'], + 'saivism': ['saivism', 'sivaism'], + 'sak': ['ask', 'sak'], + 'saka': ['asak', 'kasa', 'saka'], + 'sake': ['sake', 'seak'], + 'sakeen': ['sakeen', 'sekane'], + 'sakel': ['alkes', 'sakel', 'slake'], + 'saker': ['asker', 'reask', 'saker', 'sekar'], + 'sakeret': ['restake', 'sakeret'], + 'sakha': ['kasha', 'khasa', 'sakha', 'shaka'], + 'saki': ['saki', 'siak', 'sika'], + 'sal': ['las', 'sal', 'sla'], + 'salable': ['basella', 'sabella', 'salable'], + 'salably': ['basally', 'salably'], + 'salaceta': ['catalase', 'salaceta'], + 'salacot': ['coastal', 'salacot'], + 'salading': ['salading', 'salangid'], + 'salago': ['aglaos', 'salago'], + 'salamandarin': ['salamandarin', 'salamandrian', 'salamandrina'], + 'salamandrian': ['salamandarin', 'salamandrian', 'salamandrina'], + 'salamandrina': ['salamandarin', 'salamandrian', 'salamandrina'], + 'salangid': ['salading', 'salangid'], + 'salariat': ['alastair', 'salariat'], + 'salat': ['atlas', 'salat', 'salta'], + 'salay': ['asyla', 'salay', 'sayal'], + 'sale': ['elsa', 'sale', 'seal', 'slae'], + 'salele': ['salele', 'sallee'], + 'salep': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'saleratus': ['assaulter', 'reassault', 'saleratus'], + 'salian': ['anisal', 'nasial', 'salian', 'salina'], + 'salic': ['lacis', 'salic'], + 'salicin': ['incisal', 'salicin'], + 'salicylide': ['salicylide', 'scylliidae'], + 'salience': ['salience', 'secaline'], + 'salient': ['elastin', 'salient', 'saltine', 'slainte'], + 'salimeter': ['misrelate', 'salimeter'], + 'salimetry': ['mysterial', 'salimetry'], + 'salina': ['anisal', 'nasial', 'salian', 'salina'], + 'saline': ['alsine', 'neslia', 'saline', 'selina', 'silane'], + 'salinoterreous': ['salinoterreous', 'soliterraneous'], + 'salite': ['isleta', 'litsea', 'salite', 'stelai'], + 'salited': ['distale', 'salited'], + 'saliva': ['saliva', 'salvia'], + 'salivan': ['salivan', 'slavian'], + 'salivant': ['navalist', 'salivant'], + 'salivate': ['salivate', 'vestalia'], + 'salle': ['salle', 'sella'], + 'sallee': ['salele', 'sallee'], + 'sallet': ['sallet', 'stella', 'talles'], + 'sallow': ['sallow', 'swallo'], + 'salm': ['alms', 'salm', 'slam'], + 'salma': ['salma', 'samal'], + 'salmine': ['malines', 'salmine', 'selamin', 'seminal'], + 'salmis': ['missal', 'salmis'], + 'salmo': ['salmo', 'somal'], + 'salmonsite': ['assoilment', 'salmonsite'], + 'salome': ['melosa', 'salome', 'semola'], + 'salometer': ['elastomer', 'salometer'], + 'salon': ['salon', 'sloan', 'solan'], + 'saloon': ['alonso', 'alsoon', 'saloon'], + 'salp': ['salp', 'slap'], + 'salpa': ['palas', 'salpa'], + 'salpidae': ['palisade', 'salpidae'], + 'salpoid': ['psaloid', 'salpoid'], + 'salt': ['last', 'salt', 'slat'], + 'salta': ['atlas', 'salat', 'salta'], + 'saltary': ['astylar', 'saltary'], + 'saltation': ['saltation', 'stational'], + 'salted': ['desalt', 'salted'], + 'saltee': ['ateles', 'saltee', 'sealet', 'stelae', 'teasel'], + 'salter': ['laster', + 'lastre', + 'rastle', + 'relast', + 'resalt', + 'salter', + 'slater', + 'stelar'], + 'saltern': ['saltern', 'starnel', 'sternal'], + 'saltery': ['saltery', 'stearyl'], + 'saltier': ['aletris', 'alister', 'listera', 'realist', 'saltier'], + 'saltine': ['elastin', 'salient', 'saltine', 'slainte'], + 'saltiness': ['saintless', 'saltiness', 'slatiness', 'stainless'], + 'salting': ['anglist', 'lasting', 'salting', 'slating', 'staling'], + 'saltish': ['saltish', 'slatish'], + 'saltly': ['lastly', 'saltly'], + 'saltness': ['lastness', 'saltness'], + 'saltometer': ['rattlesome', 'saltometer'], + 'saltus': ['saltus', 'tussal'], + 'saltwife': ['flatwise', 'saltwife'], + 'salty': ['lasty', 'salty', 'slaty'], + 'salung': ['lugnas', 'salung'], + 'salute': ['salute', 'setula'], + 'saluter': ['arustle', 'estrual', 'saluter', 'saulter'], + 'salva': ['salva', 'valsa', 'vasal'], + 'salve': ['salve', 'selva', 'slave', 'valse'], + 'salver': ['salver', 'serval', 'slaver', 'versal'], + 'salvia': ['saliva', 'salvia'], + 'salvy': ['salvy', 'sylva'], + 'sam': ['mas', 'sam', 'sma'], + 'samal': ['salma', 'samal'], + 'saman': ['manas', 'saman'], + 'samani': ['samani', 'samian'], + 'samaritan': ['samaritan', 'sarmatian'], + 'samas': ['amass', 'assam', 'massa', 'samas'], + 'sambal': ['balsam', 'sambal'], + 'sambo': ['ambos', 'sambo'], + 'same': ['asem', 'mesa', 'same', 'seam'], + 'samel': ['amsel', 'melas', 'mesal', 'samel'], + 'samely': ['measly', 'samely'], + 'samen': ['manes', 'manse', 'mensa', 'samen', 'senam'], + 'samh': ['mash', 'samh', 'sham'], + 'samian': ['samani', 'samian'], + 'samiel': ['amiles', 'asmile', 'mesail', 'mesial', 'samiel'], + 'samir': ['maris', 'marsi', 'samir', 'simar'], + 'samisen': ['samisen', 'samsien'], + 'samish': ['samish', 'sisham'], + 'samite': ['samite', 'semita', 'tamise', 'teaism'], + 'sammer': ['mamers', 'sammer'], + 'sammier': ['amerism', 'asimmer', 'sammier'], + 'samnani': ['ananism', 'samnani'], + 'samnite': ['atenism', 'inmeats', 'insteam', 'samnite'], + 'samoan': ['monasa', 'samoan'], + 'samothere': ['heartsome', 'samothere'], + 'samoyed': ['samoyed', 'someday'], + 'samphire': ['samphire', 'seraphim'], + 'sampi': ['apism', 'sampi'], + 'sampler': ['lampers', 'sampler'], + 'samsien': ['samisen', 'samsien'], + 'samskara': ['makassar', 'samskara'], + 'samucan': ['manacus', 'samucan'], + 'samuel': ['amelus', 'samuel'], + 'sanability': ['insatiably', 'sanability'], + 'sanai': ['asian', 'naias', 'sanai'], + 'sanand': ['sanand', 'sandan'], + 'sanche': ['encash', 'sanche'], + 'sanct': ['sanct', 'scant'], + 'sanction': ['canonist', 'sanction', 'sonantic'], + 'sanctioner': ['resanction', 'sanctioner'], + 'sanctity': ['sanctity', 'scantity'], + 'sandak': ['sandak', 'skanda'], + 'sandan': ['sanand', 'sandan'], + 'sandarac': ['carandas', 'sandarac'], + 'sandawe': ['sandawe', 'weasand'], + 'sanded': ['desand', 'sadden', 'sanded'], + 'sanderling': ['sanderling', 'slandering'], + 'sandflower': ['flandowser', 'sandflower'], + 'sandhi': ['danish', 'sandhi'], + 'sandra': ['nasard', 'sandra'], + 'sandworm': ['sandworm', 'swordman', 'wordsman'], + 'sane': ['anes', 'sane', 'sean'], + 'sanetch': ['chasten', 'sanetch'], + 'sang': ['sang', 'snag'], + 'sanga': ['gasan', 'sanga'], + 'sangar': ['argans', 'sangar'], + 'sangei': ['easing', 'sangei'], + 'sanger': ['angers', 'sanger', 'serang'], + 'sangrel': ['sangrel', 'snagrel'], + 'sanhita': ['ashanti', 'sanhita', 'shaitan', 'thasian'], + 'sanicle': ['celsian', 'escalin', 'sanicle', 'secalin'], + 'sanies': ['anesis', 'anseis', 'sanies', 'sansei', 'sasine'], + 'sanious': ['sanious', 'suasion'], + 'sanitate': ['astatine', 'sanitate'], + 'sanitize': ['sanitize', 'satinize'], + 'sanity': ['sanity', 'satiny'], + 'sank': ['kans', 'sank'], + 'sankha': ['kashan', 'sankha'], + 'sannup': ['pannus', 'sannup', 'unsnap', 'unspan'], + 'sanpoil': ['sanpoil', 'spaniol'], + 'sansei': ['anesis', 'anseis', 'sanies', 'sansei', 'sasine'], + 'sansi': ['sansi', 'sasin'], + 'sant': ['nast', 'sant', 'stan'], + 'santa': ['santa', 'satan'], + 'santal': ['aslant', 'lansat', 'natals', 'santal'], + 'santali': ['lanista', 'santali'], + 'santalin': ['annalist', 'santalin'], + 'santee': ['ensate', 'enseat', 'santee', 'sateen', 'senate'], + 'santiago': ['agonista', 'santiago'], + 'santimi': ['animist', 'santimi'], + 'santir': ['instar', 'santir', 'strain'], + 'santon': ['santon', 'sonant', 'stanno'], + 'santorinite': ['reinstation', 'santorinite'], + 'sap': ['asp', 'sap', 'spa'], + 'sapan': ['pasan', 'sapan'], + 'sapek': ['sapek', 'speak'], + 'saperda': ['aspread', 'saperda'], + 'saphena': ['aphanes', 'saphena'], + 'sapid': ['sapid', 'spaid'], + 'sapient': ['panties', 'sapient', 'spinate'], + 'sapiential': ['antilipase', 'sapiential'], + 'sapin': ['pisan', 'sapin', 'spina'], + 'sapinda': ['anapsid', 'sapinda'], + 'saple': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'sapling': ['lapsing', 'sapling'], + 'sapo': ['asop', 'sapo', 'soap'], + 'sapor': ['psora', 'sapor', 'sarpo'], + 'saporous': ['asporous', 'saporous'], + 'sapota': ['sapota', 'taposa'], + 'sapotilha': ['hapalotis', 'sapotilha'], + 'sapphire': ['papisher', 'sapphire'], + 'sapples': ['papless', 'sapples'], + 'sapremia': ['aspermia', 'sapremia'], + 'sapremic': ['aspermic', 'sapremic'], + 'saprine': ['persian', 'prasine', 'saprine'], + 'saprolite': ['posterial', 'saprolite'], + 'saprolitic': ['polaristic', 'poristical', 'saprolitic'], + 'sapropel': ['prolapse', 'sapropel'], + 'sapropelic': ['periscopal', 'sapropelic'], + 'saprophagous': ['prasophagous', 'saprophagous'], + 'sapwort': ['postwar', 'sapwort'], + 'sar': ['ras', 'sar'], + 'sara': ['rasa', 'sara'], + 'saraad': ['saraad', 'sarada'], + 'sarada': ['saraad', 'sarada'], + 'saraf': ['safar', 'saraf'], + 'sarah': ['asarh', 'raash', 'sarah'], + 'saran': ['ansar', 'saran', 'sarna'], + 'sarangi': ['giansar', 'sarangi'], + 'sarcenet': ['reascent', 'sarcenet'], + 'sarcine': ['arsenic', 'cerasin', 'sarcine'], + 'sarcitis': ['sarcitis', 'triassic'], + 'sarcle': ['sarcle', 'scaler', 'sclera'], + 'sarcoadenoma': ['adenosarcoma', 'sarcoadenoma'], + 'sarcocarcinoma': ['carcinosarcoma', 'sarcocarcinoma'], + 'sarcoid': ['sarcoid', 'scaroid'], + 'sarcoline': ['censorial', 'sarcoline'], + 'sarcolite': ['alectoris', 'sarcolite', 'sclerotia', 'sectorial'], + 'sarcoplast': ['postsacral', 'sarcoplast'], + 'sarcotic': ['acrostic', 'sarcotic', 'socratic'], + 'sard': ['sadr', 'sard'], + 'sardian': ['andrias', 'sardian', 'sarinda'], + 'sardine': ['andries', 'isander', 'sardine'], + 'sardoin': ['sadiron', 'sardoin'], + 'sardonic': ['draconis', 'sardonic'], + 'sare': ['arse', 'rase', 'sare', 'sear', 'sera'], + 'sargonide': ['grandiose', 'sargonide'], + 'sari': ['rais', 'sair', 'sari'], + 'sarif': ['farsi', 'sarif'], + 'sarigue': ['ergusia', 'gerusia', 'sarigue'], + 'sarinda': ['andrias', 'sardian', 'sarinda'], + 'sarip': ['paris', 'parsi', 'sarip'], + 'sark': ['askr', 'kras', 'sark'], + 'sarkine': ['kerasin', 'sarkine'], + 'sarkit': ['rastik', 'sarkit', 'straik'], + 'sarmatian': ['samaritan', 'sarmatian'], + 'sarment': ['sarment', 'smarten'], + 'sarmentous': ['sarmentous', 'tarsonemus'], + 'sarna': ['ansar', 'saran', 'sarna'], + 'sarod': ['sarod', 'sorda'], + 'saron': ['arson', 'saron', 'sonar'], + 'saronic': ['arsonic', 'saronic'], + 'sarpo': ['psora', 'sapor', 'sarpo'], + 'sarra': ['arras', 'sarra'], + 'sarsenet': ['assenter', 'reassent', 'sarsenet'], + 'sarsi': ['arsis', 'sarsi'], + 'sart': ['sart', 'star', 'stra', 'tars', 'tsar'], + 'sartain': ['artisan', 'astrain', 'sartain', 'tsarina'], + 'sartish': ['sartish', 'shastri'], + 'sartor': ['rostra', 'sartor'], + 'sasan': ['nassa', 'sasan'], + 'sasin': ['sansi', 'sasin'], + 'sasine': ['anesis', 'anseis', 'sanies', 'sansei', 'sasine'], + 'sat': ['ast', 'sat'], + 'satan': ['santa', 'satan'], + 'satanical': ['castalian', 'satanical'], + 'satanism': ['mantissa', 'satanism'], + 'sate': ['ates', 'east', 'eats', 'sate', 'seat', 'seta'], + 'sateen': ['ensate', 'enseat', 'santee', 'sateen', 'senate'], + 'sateless': ['sateless', 'seatless'], + 'satelles': ['satelles', 'tessella'], + 'satellite': ['satellite', 'telestial'], + 'satiable': ['bisaltae', 'satiable'], + 'satiate': ['isatate', 'satiate', 'taetsia'], + 'satieno': ['aeonist', 'asiento', 'satieno'], + 'satient': ['atenist', 'instate', 'satient', 'steatin'], + 'satin': ['saint', 'satin', 'stain'], + 'satine': ['satine', 'tisane'], + 'satined': ['destain', 'instead', 'sainted', 'satined'], + 'satinette': ['enstatite', 'intestate', 'satinette'], + 'satinite': ['satinite', 'sittinae'], + 'satinize': ['sanitize', 'satinize'], + 'satinlike': ['kleistian', 'saintlike', 'satinlike'], + 'satiny': ['sanity', 'satiny'], + 'satire': ['satire', 'striae'], + 'satirical': ['racialist', 'satirical'], + 'satirist': ['satirist', 'tarsitis'], + 'satrae': ['astare', 'satrae'], + 'satrapic': ['aspartic', 'satrapic'], + 'satron': ['asnort', 'satron'], + 'sattle': ['latest', 'sattle', 'taslet'], + 'sattva': ['sattva', 'tavast'], + 'saturable': ['balaustre', 'saturable'], + 'saturation': ['saturation', 'titanosaur'], + 'saturator': ['saturator', 'tartarous'], + 'saturn': ['saturn', 'unstar'], + 'saturnale': ['alaternus', 'saturnale'], + 'saturnalia': ['australian', 'saturnalia'], + 'saturnia': ['asturian', 'austrian', 'saturnia'], + 'saturnine': ['neustrian', 'saturnine', 'sturninae'], + 'saturnism': ['saturnism', 'surmisant'], + 'satyr': ['satyr', 'stary', 'stray', 'trasy'], + 'satyrlike': ['satyrlike', 'streakily'], + 'sauce': ['cause', 'sauce'], + 'sauceless': ['causeless', 'sauceless'], + 'sauceline': ['sauceline', 'seleucian'], + 'saucer': ['causer', 'saucer'], + 'sauger': ['sauger', 'usager'], + 'saugh': ['agush', 'saugh'], + 'saul': ['saul', 'sula'], + 'sauld': ['aldus', 'sauld'], + 'sault': ['latus', 'sault', 'talus'], + 'saulter': ['arustle', 'estrual', 'saluter', 'saulter'], + 'saum': ['masu', 'musa', 'saum'], + 'sauna': ['nasua', 'sauna'], + 'saur': ['rusa', 'saur', 'sura', 'ursa', 'usar'], + 'saura': ['arusa', 'saura', 'usara'], + 'saurian': ['saurian', 'suriana'], + 'saury': ['saury', 'surya'], + 'sausage': ['assuage', 'sausage'], + 'saut': ['saut', 'tasu', 'utas'], + 'sauve': ['sauve', 'suave'], + 'save': ['aves', 'save', 'vase'], + 'savin': ['savin', 'sivan'], + 'saviour': ['saviour', 'various'], + 'savor': ['savor', 'sorva'], + 'savored': ['oversad', 'savored'], + 'saw': ['saw', 'swa', 'was'], + 'sawah': ['awash', 'sawah'], + 'sawali': ['aswail', 'sawali'], + 'sawback': ['backsaw', 'sawback'], + 'sawbuck': ['bucksaw', 'sawbuck'], + 'sawer': ['resaw', 'sawer', 'seraw', 'sware', 'swear', 'warse'], + 'sawing': ['aswing', 'sawing'], + 'sawish': ['sawish', 'siwash'], + 'sawn': ['sawn', 'snaw', 'swan'], + 'sawt': ['sawt', 'staw', 'swat', 'taws', 'twas', 'wast'], + 'sawyer': ['sawyer', 'swayer'], + 'saxe': ['axes', 'saxe', 'seax'], + 'saxten': ['saxten', 'sextan'], + 'say': ['say', 'yas'], + 'sayal': ['asyla', 'salay', 'sayal'], + 'sayer': ['reasy', 'resay', 'sayer', 'seary'], + 'sayid': ['daisy', 'sayid'], + 'scabbler': ['scabbler', 'scrabble'], + 'scabies': ['abscise', 'scabies'], + 'scaddle': ['scaddle', 'scalded'], + 'scaean': ['anaces', 'scaean'], + 'scala': ['calas', 'casal', 'scala'], + 'scalar': ['lascar', 'rascal', 'sacral', 'scalar'], + 'scalded': ['scaddle', 'scalded'], + 'scale': ['alces', 'casel', 'scale'], + 'scalena': ['escalan', 'scalena'], + 'scalene': ['cleanse', 'scalene'], + 'scaler': ['sarcle', 'scaler', 'sclera'], + 'scallola': ['callosal', 'scallola'], + 'scalloper': ['procellas', 'scalloper'], + 'scaloni': ['nicolas', 'scaloni'], + 'scalp': ['clasp', 'scalp'], + 'scalper': ['clasper', 'reclasp', 'scalper'], + 'scalping': ['clasping', 'scalping'], + 'scalpture': ['prescutal', 'scalpture'], + 'scaly': ['aclys', 'scaly'], + 'scambler': ['scambler', 'scramble'], + 'scampish': ['scampish', 'scaphism'], + 'scania': ['ascian', 'sacian', 'scania', 'sicana'], + 'scant': ['sanct', 'scant'], + 'scantity': ['sanctity', 'scantity'], + 'scantle': ['asclent', 'scantle'], + 'scape': ['capes', 'scape', 'space'], + 'scapeless': ['scapeless', 'spaceless'], + 'scapha': ['pascha', 'scapha'], + 'scaphander': ['handscrape', 'scaphander'], + 'scaphism': ['scampish', 'scaphism'], + 'scaphite': ['paschite', 'pastiche', 'pistache', 'scaphite'], + 'scaphopod': ['podoscaph', 'scaphopod'], + 'scapoid': ['psoadic', 'scapoid', 'sciapod'], + 'scapolite': ['alopecist', 'altiscope', 'epicostal', 'scapolite'], + 'scappler': ['scappler', 'scrapple'], + 'scapula': ['capsula', 'pascual', 'scapula'], + 'scapular': ['capsular', 'scapular'], + 'scapulated': ['capsulated', 'scapulated'], + 'scapulectomy': ['capsulectomy', 'scapulectomy'], + 'scarab': ['barsac', 'scarab'], + 'scarcement': ['marcescent', 'scarcement'], + 'scare': ['carse', 'caser', 'ceras', 'scare', 'scrae'], + 'scarification': ['sacrification', 'scarification'], + 'scarificator': ['sacrificator', 'scarificator'], + 'scarily': ['scarily', 'scraily'], + 'scarlet': ['scarlet', 'sclater'], + 'scarn': ['scarn', 'scran'], + 'scaroid': ['sarcoid', 'scaroid'], + 'scarp': ['craps', 'scarp', 'scrap'], + 'scarping': ['scarping', 'scraping'], + 'scart': ['scart', 'scrat'], + 'scarth': ['scarth', 'scrath', 'starch'], + 'scary': ['ascry', 'scary', 'scray'], + 'scase': ['casse', 'scase'], + 'scat': ['acts', 'cast', 'scat'], + 'scathe': ['chaste', 'sachet', 'scathe', 'scheat'], + 'scatterer': ['scatterer', 'streetcar'], + 'scaturient': ['incrustate', 'scaturient', 'scrutinate'], + 'scaum': ['camus', 'musca', 'scaum', 'sumac'], + 'scaur': ['cursa', 'scaur'], + 'scaut': ['scaut', 'scuta'], + 'scavel': ['calves', 'scavel'], + 'scawl': ['scawl', 'sclaw'], + 'sceat': ['caste', 'sceat'], + 'scene': ['cense', 'scene', 'sence'], + 'scenery': ['scenery', 'screeny'], + 'scented': ['descent', 'scented'], + 'scepter': ['respect', 'scepter', 'specter'], + 'sceptered': ['sceptered', 'spectered'], + 'scepterless': ['respectless', 'scepterless'], + 'sceptral': ['sceptral', 'scraplet', 'spectral'], + 'sceptry': ['precyst', 'sceptry', 'spectry'], + 'scerne': ['censer', 'scerne', 'screen', 'secern'], + 'schalmei': ['camelish', 'schalmei'], + 'scheat': ['chaste', 'sachet', 'scathe', 'scheat'], + 'schema': ['sachem', 'schema'], + 'schematic': ['catechism', 'schematic'], + 'scheme': ['scheme', 'smeech'], + 'schemer': ['chermes', 'schemer'], + 'scho': ['cosh', 'scho'], + 'scholae': ['oscheal', 'scholae'], + 'schone': ['cheson', 'chosen', 'schone'], + 'schooltime': ['chilostome', 'schooltime'], + 'schout': ['schout', 'scouth'], + 'schute': ['schute', 'tusche'], + 'sciaenoid': ['oniscidae', 'oscinidae', 'sciaenoid'], + 'scian': ['canis', 'scian'], + 'sciapod': ['psoadic', 'scapoid', 'sciapod'], + 'sciara': ['carisa', 'sciara'], + 'sciarid': ['cidaris', 'sciarid'], + 'sciatic': ['ascitic', 'sciatic'], + 'sciatical': ['ascitical', 'sciatical'], + 'scient': ['encist', 'incest', 'insect', 'scient'], + 'sciential': ['elasticin', 'inelastic', 'sciential'], + 'scillitan': ['scillitan', 'scintilla'], + 'scintilla': ['scillitan', 'scintilla'], + 'scintle': ['lentisc', 'scintle', 'stencil'], + 'scion': ['oscin', 'scion', 'sonic'], + 'sciot': ['ostic', 'sciot', 'stoic'], + 'sciotherical': ['ischiorectal', 'sciotherical'], + 'scious': ['scious', 'socius'], + 'scirenga': ['creasing', 'scirenga'], + 'scirpus': ['prussic', 'scirpus'], + 'scissortail': ['scissortail', 'solaristics'], + 'sciurine': ['incisure', 'sciurine'], + 'sciuroid': ['dioscuri', 'sciuroid'], + 'sclate': ['castle', 'sclate'], + 'sclater': ['scarlet', 'sclater'], + 'sclaw': ['scawl', 'sclaw'], + 'sclera': ['sarcle', 'scaler', 'sclera'], + 'sclere': ['sclere', 'screel'], + 'scleria': ['scleria', 'sercial'], + 'sclerite': ['sclerite', 'silcrete'], + 'sclerodermite': ['dermosclerite', 'sclerodermite'], + 'scleromata': ['clamatores', 'scleromata'], + 'sclerose': ['coreless', 'sclerose'], + 'sclerospora': ['prolacrosse', 'sclerospora'], + 'sclerote': ['corselet', 'sclerote', 'selector'], + 'sclerotia': ['alectoris', 'sarcolite', 'sclerotia', 'sectorial'], + 'sclerotial': ['cloisteral', 'sclerotial'], + 'sclerotinia': ['intersocial', 'orleanistic', 'sclerotinia'], + 'sclerotium': ['closterium', 'sclerotium'], + 'sclerotomy': ['mycosterol', 'sclerotomy'], + 'scoad': ['cados', 'scoad'], + 'scob': ['bosc', 'scob'], + 'scobicular': ['scobicular', 'scrobicula'], + 'scolia': ['colias', 'scolia', 'social'], + 'scolion': ['scolion', 'solonic'], + 'scombrine': ['becrimson', 'scombrine'], + 'scone': ['cones', 'scone'], + 'scooper': ['coprose', 'scooper'], + 'scoot': ['coost', 'scoot'], + 'scopa': ['posca', 'scopa'], + 'scoparin': ['parsonic', 'scoparin'], + 'scope': ['copse', 'pecos', 'scope'], + 'scopidae': ['diascope', 'psocidae', 'scopidae'], + 'scopine': ['psocine', 'scopine'], + 'scopiped': ['podiceps', 'scopiped'], + 'scopoline': ['niloscope', 'scopoline'], + 'scorbutical': ['scorbutical', 'subcortical'], + 'scorbutically': ['scorbutically', 'subcortically'], + 'score': ['corse', 'score'], + 'scorer': ['scorer', 'sorcer'], + 'scoriae': ['coraise', 'scoriae'], + 'scorpidae': ['carpiodes', 'scorpidae'], + 'scorpiones': ['procession', 'scorpiones'], + 'scorpionic': ['orniscopic', 'scorpionic'], + 'scorse': ['cessor', 'crosse', 'scorse'], + 'scortation': ['cartoonist', 'scortation'], + 'scot': ['cost', 'scot'], + 'scotale': ['alecost', 'lactose', 'scotale', 'talcose'], + 'scote': ['coset', 'estoc', 'scote'], + 'scoter': ['corset', 'cortes', 'coster', 'escort', 'scoter', 'sector'], + 'scotia': ['isotac', 'scotia'], + 'scotism': ['cosmist', 'scotism'], + 'scotomatic': ['osmotactic', 'scotomatic'], + 'scotty': ['cytost', 'scotty'], + 'scoup': ['copus', 'scoup'], + 'scour': ['cours', 'scour'], + 'scoured': ['coursed', 'scoured'], + 'scourer': ['courser', 'scourer'], + 'scourge': ['scourge', 'scrouge'], + 'scourger': ['scourger', 'scrouger'], + 'scouring': ['coursing', 'scouring'], + 'scouth': ['schout', 'scouth'], + 'scrabble': ['scabbler', 'scrabble'], + 'scrabe': ['braces', 'scrabe'], + 'scrae': ['carse', 'caser', 'ceras', 'scare', 'scrae'], + 'scraily': ['scarily', 'scraily'], + 'scramble': ['scambler', 'scramble'], + 'scran': ['scarn', 'scran'], + 'scrap': ['craps', 'scarp', 'scrap'], + 'scrape': ['casper', 'escarp', 'parsec', 'scrape', 'secpar', 'spacer'], + 'scrapie': ['epacris', 'scrapie', 'serapic'], + 'scraping': ['scarping', 'scraping'], + 'scraplet': ['sceptral', 'scraplet', 'spectral'], + 'scrapple': ['scappler', 'scrapple'], + 'scrat': ['scart', 'scrat'], + 'scratcher': ['rescratch', 'scratcher'], + 'scrath': ['scarth', 'scrath', 'starch'], + 'scray': ['ascry', 'scary', 'scray'], + 'screak': ['resack', 'sacker', 'screak'], + 'screaming': ['germanics', 'screaming'], + 'screamy': ['armscye', 'screamy'], + 'scree': ['scree', 'secre'], + 'screel': ['sclere', 'screel'], + 'screen': ['censer', 'scerne', 'screen', 'secern'], + 'screenless': ['censerless', 'screenless'], + 'screeny': ['scenery', 'screeny'], + 'screet': ['resect', 'screet', 'secret'], + 'screwdrive': ['drivescrew', 'screwdrive'], + 'scrieve': ['cerevis', 'scrieve', 'service'], + 'scrike': ['scrike', 'sicker'], + 'scrip': ['crisp', 'scrip'], + 'scripee': ['precise', 'scripee'], + 'scripula': ['scripula', 'spicular'], + 'scrobicula': ['scobicular', 'scrobicula'], + 'scrota': ['arctos', 'castor', 'costar', 'scrota'], + 'scrouge': ['scourge', 'scrouge'], + 'scrouger': ['scourger', 'scrouger'], + 'scrout': ['scrout', 'scruto'], + 'scroyle': ['cryosel', 'scroyle'], + 'scruf': ['scruf', 'scurf'], + 'scruffle': ['scruffle', 'scuffler'], + 'scruple': ['scruple', 'sculper'], + 'scrutate': ['crustate', 'scrutate'], + 'scrutation': ['crustation', 'scrutation'], + 'scrutinant': ['incrustant', 'scrutinant'], + 'scrutinate': ['incrustate', 'scaturient', 'scrutinate'], + 'scruto': ['scrout', 'scruto'], + 'scudi': ['scudi', 'sudic'], + 'scuffler': ['scruffle', 'scuffler'], + 'sculper': ['scruple', 'sculper'], + 'sculpin': ['insculp', 'sculpin'], + 'scup': ['cusp', 'scup'], + 'scur': ['crus', 'scur'], + 'scurf': ['scruf', 'scurf'], + 'scusation': ['cosustain', 'scusation'], + 'scuta': ['scaut', 'scuta'], + 'scutal': ['scutal', 'suclat'], + 'scute': ['cetus', 'scute'], + 'scutifer': ['fusteric', 'scutifer'], + 'scutigeral': ['gesticular', 'scutigeral'], + 'scutula': ['auscult', 'scutula'], + 'scye': ['scye', 'syce'], + 'scylliidae': ['salicylide', 'scylliidae'], + 'scyllium': ['clumsily', 'scyllium'], + 'scyphi': ['physic', 'scyphi'], + 'scyphomancy': ['psychomancy', 'scyphomancy'], + 'scyt': ['cyst', 'scyt'], + 'scythe': ['chesty', 'scythe'], + 'scytitis': ['cystitis', 'scytitis'], + 'se': ['es', 'se'], + 'sea': ['aes', 'ase', 'sea'], + 'seadog': ['dosage', 'seadog'], + 'seagirt': ['seagirt', 'strigae'], + 'seah': ['seah', 'shea'], + 'seak': ['sake', 'seak'], + 'seal': ['elsa', 'sale', 'seal', 'slae'], + 'sealable': ['leasable', 'sealable'], + 'sealch': ['cashel', 'laches', 'sealch'], + 'sealer': ['alerse', 'leaser', 'reales', 'resale', 'reseal', 'sealer'], + 'sealet': ['ateles', 'saltee', 'sealet', 'stelae', 'teasel'], + 'sealing': ['leasing', 'sealing'], + 'sealwort': ['restowal', 'sealwort'], + 'seam': ['asem', 'mesa', 'same', 'seam'], + 'seamanite': ['anamesite', 'seamanite'], + 'seamark': ['kamares', 'seamark'], + 'seamer': ['reseam', 'seamer'], + 'seamlet': ['maltese', 'seamlet'], + 'seamlike': ['mesalike', 'seamlike'], + 'seamrend': ['redesman', 'seamrend'], + 'seamster': ['masseter', 'seamster'], + 'seamus': ['assume', 'seamus'], + 'sean': ['anes', 'sane', 'sean'], + 'seance': ['encase', 'seance', 'seneca'], + 'seaplane': ['seaplane', 'spelaean'], + 'seaport': ['esparto', 'petrosa', 'seaport'], + 'sear': ['arse', 'rase', 'sare', 'sear', 'sera'], + 'searce': ['cesare', 'crease', 'recase', 'searce'], + 'searcer': ['creaser', 'searcer'], + 'search': ['arches', 'chaser', 'eschar', 'recash', 'search'], + 'searcher': ['rechaser', 'research', 'searcher'], + 'searchment': ['manchester', 'searchment'], + 'searcloth': ['clathrose', 'searcloth'], + 'seared': ['erased', 'reseda', 'seared'], + 'searer': ['eraser', 'searer'], + 'searing': ['searing', 'seringa'], + 'seary': ['reasy', 'resay', 'sayer', 'seary'], + 'seaside': ['disease', 'seaside'], + 'seat': ['ates', 'east', 'eats', 'sate', 'seat', 'seta'], + 'seated': ['seated', 'sedate'], + 'seater': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'seating': ['easting', + 'gainset', + 'genista', + 'ingesta', + 'seating', + 'signate', + 'teasing'], + 'seatless': ['sateless', 'seatless'], + 'seatrain': ['artesian', 'asterina', 'asternia', 'erastian', 'seatrain'], + 'seatron': ['noreast', 'rosetan', 'seatron', 'senator', 'treason'], + 'seave': ['eaves', 'evase', 'seave'], + 'seax': ['axes', 'saxe', 'seax'], + 'seba': ['base', 'besa', 'sabe', 'seba'], + 'sebastian': ['bassanite', 'sebastian'], + 'sebilla': ['sabelli', 'sebilla'], + 'sebum': ['embus', 'sebum'], + 'secalin': ['celsian', 'escalin', 'sanicle', 'secalin'], + 'secaline': ['salience', 'secaline'], + 'secant': ['ascent', 'secant', 'stance'], + 'secern': ['censer', 'scerne', 'screen', 'secern'], + 'secernent': ['secernent', 'sentencer'], + 'secondar': ['endosarc', 'secondar'], + 'secos': ['cosse', 'secos'], + 'secpar': ['casper', 'escarp', 'parsec', 'scrape', 'secpar', 'spacer'], + 'secre': ['scree', 'secre'], + 'secret': ['resect', 'screet', 'secret'], + 'secretarian': ['ascertainer', 'reascertain', 'secretarian'], + 'secretion': ['resection', 'secretion'], + 'secretional': ['resectional', 'secretional'], + 'sect': ['cest', 'sect'], + 'sectarian': ['ascertain', 'cartesian', 'cartisane', 'sectarian'], + 'sectarianism': ['cartesianism', 'sectarianism'], + 'section': ['contise', 'noetics', 'section'], + 'sectism': ['sectism', 'smectis'], + 'sector': ['corset', 'cortes', 'coster', 'escort', 'scoter', 'sector'], + 'sectorial': ['alectoris', 'sarcolite', 'sclerotia', 'sectorial'], + 'sectroid': ['decorist', 'sectroid'], + 'securable': ['rescuable', 'securable'], + 'securance': ['recusance', 'securance'], + 'secure': ['cereus', 'ceruse', 'recuse', 'rescue', 'secure'], + 'securer': ['recurse', 'rescuer', 'securer'], + 'sedan': ['sedan', 'snead'], + 'sedanier': ['arsedine', 'arsenide', 'sedanier', 'siderean'], + 'sedat': ['sedat', 'stade', 'stead'], + 'sedate': ['seated', 'sedate'], + 'sedation': ['astonied', 'sedation'], + 'sederunt': ['sederunt', 'underset', 'undesert', 'unrested'], + 'sedile': ['diesel', 'sedile', 'seidel'], + 'sedimetric': ['sedimetric', 'semidirect'], + 'sedimetrical': ['decimestrial', 'sedimetrical'], + 'sedition': ['desition', 'sedition'], + 'sedulity': ['dysluite', 'sedulity'], + 'sedum': ['mused', 'sedum'], + 'seedbird': ['birdseed', 'seedbird'], + 'seeded': ['deseed', 'seeded'], + 'seeder': ['reseed', 'seeder'], + 'seedlip': ['pelides', 'seedlip'], + 'seeing': ['seeing', 'signee'], + 'seek': ['kees', 'seek', 'skee'], + 'seeker': ['reseek', 'seeker'], + 'seel': ['else', 'lees', 'seel', 'sele', 'slee'], + 'seem': ['mese', 'seem', 'seme', 'smee'], + 'seemer': ['emerse', 'seemer'], + 'seen': ['ense', 'esne', 'nese', 'seen', 'snee'], + 'seenu': ['ensue', 'seenu', 'unsee'], + 'seer': ['erse', 'rees', 'seer', 'sere'], + 'seerband': ['seerband', 'serabend'], + 'seerhand': ['denshare', 'seerhand'], + 'seerhood': ['rhodeose', 'seerhood'], + 'seership': ['hesperis', 'seership'], + 'seething': ['seething', 'sheeting'], + 'seg': ['ges', 'seg'], + 'seggar': ['sagger', 'seggar'], + 'seggard': ['daggers', 'seggard'], + 'sego': ['goes', 'sego'], + 'segolate': ['gelatose', 'segolate'], + 'segreant': ['estrange', 'segreant', 'sergeant', 'sternage'], + 'seid': ['desi', 'ides', 'seid', 'side'], + 'seidel': ['diesel', 'sedile', 'seidel'], + 'seignioral': ['seignioral', 'seignorial'], + 'seignoral': ['gasoliner', 'seignoral'], + 'seignorial': ['seignioral', 'seignorial'], + 'seine': ['insee', 'seine'], + 'seiner': ['inseer', 'nereis', 'seiner', 'serine', 'sirene'], + 'seise': ['essie', 'seise'], + 'seism': ['seism', 'semis'], + 'seismal': ['aimless', 'melissa', 'seismal'], + 'seismotic': ['seismotic', 'societism'], + 'seit': ['seit', 'site'], + 'seizable': ['seizable', 'sizeable'], + 'seizer': ['resize', 'seizer'], + 'sekane': ['sakeen', 'sekane'], + 'sekani': ['kinase', 'sekani'], + 'sekar': ['asker', 'reask', 'saker', 'sekar'], + 'seker': ['esker', 'keres', 'reesk', 'seker', 'skeer', 'skere'], + 'selagite': ['elegiast', 'selagite'], + 'selah': ['halse', 'leash', 'selah', 'shale', 'sheal', 'shela'], + 'selamin': ['malines', 'salmine', 'selamin', 'seminal'], + 'selbergite': ['gilbertese', 'selbergite'], + 'seldor': ['dorsel', 'seldor', 'solder'], + 'seldseen': ['needless', 'seldseen'], + 'sele': ['else', 'lees', 'seel', 'sele', 'slee'], + 'selector': ['corselet', 'sclerote', 'selector'], + 'selenic': ['license', 'selenic', 'silence'], + 'selenion': ['leonines', 'selenion'], + 'selenitic': ['insectile', 'selenitic'], + 'selenium': ['selenium', 'semilune', 'seminule'], + 'selenosis': ['noiseless', 'selenosis'], + 'seleucian': ['sauceline', 'seleucian'], + 'self': ['fels', 'self'], + 'selfsame': ['fameless', 'selfsame'], + 'selfsameness': ['famelessness', 'selfsameness'], + 'selictar': ['altrices', 'selictar'], + 'selina': ['alsine', 'neslia', 'saline', 'selina', 'silane'], + 'selion': ['insole', 'leonis', 'lesion', 'selion'], + 'seljukian': ['januslike', 'seljukian'], + 'sella': ['salle', 'sella'], + 'sellably': ['sellably', 'syllable'], + 'sellate': ['estella', 'sellate'], + 'seller': ['resell', 'seller'], + 'selli': ['lisle', 'selli'], + 'sellie': ['leslie', 'sellie'], + 'sellout': ['outsell', 'sellout'], + 'selt': ['lest', 'selt'], + 'selter': ['lester', 'selter', 'streel'], + 'selung': ['gunsel', 'selung', 'slunge'], + 'selva': ['salve', 'selva', 'slave', 'valse'], + 'semang': ['magnes', 'semang'], + 'semantic': ['amnestic', 'semantic'], + 'semaphore': ['mesohepar', 'semaphore'], + 'sematic': ['cameist', 'etacism', 'sematic'], + 'sematrope': ['perosmate', 'sematrope'], + 'seme': ['mese', 'seem', 'seme', 'smee'], + 'semen': ['mense', 'mesne', 'semen'], + 'semeostoma': ['semeostoma', 'semostomae'], + 'semi': ['mise', 'semi', 'sime'], + 'semiarch': ['semiarch', 'smachrie'], + 'semibald': ['bedismal', 'semibald'], + 'semiball': ['mislabel', 'semiball'], + 'semic': ['mesic', 'semic'], + 'semicircle': ['semicircle', 'semicleric'], + 'semicleric': ['semicircle', 'semicleric'], + 'semicone': ['nicesome', 'semicone'], + 'semicoronet': ['oncosimeter', 'semicoronet'], + 'semicrome': ['mesomeric', 'microseme', 'semicrome'], + 'semidirect': ['sedimetric', 'semidirect'], + 'semidormant': ['memorandist', 'moderantism', 'semidormant'], + 'semihard': ['dreamish', 'semihard'], + 'semihiant': ['histamine', 'semihiant'], + 'semilimber': ['immersible', 'semilimber'], + 'semilunar': ['semilunar', 'unrealism'], + 'semilune': ['selenium', 'semilune', 'seminule'], + 'semiminor': ['immersion', 'semiminor'], + 'semimoron': ['monroeism', 'semimoron'], + 'seminal': ['malines', 'salmine', 'selamin', 'seminal'], + 'seminar': ['remains', 'seminar'], + 'seminasal': ['messalian', 'seminasal'], + 'seminomadic': ['demoniacism', 'seminomadic'], + 'seminomata': ['mastomenia', 'seminomata'], + 'seminule': ['selenium', 'semilune', 'seminule'], + 'semiopal': ['malpoise', 'semiopal'], + 'semiorb': ['boreism', 'semiorb'], + 'semiovaloid': ['semiovaloid', 'semiovoidal'], + 'semiovoidal': ['semiovaloid', 'semiovoidal'], + 'semipolar': ['perisomal', 'semipolar'], + 'semipro': ['imposer', 'promise', 'semipro'], + 'semipronation': ['impersonation', 'prosemination', 'semipronation'], + 'semiquote': ['quietsome', 'semiquote'], + 'semirotund': ['semirotund', 'unmortised'], + 'semis': ['seism', 'semis'], + 'semispan': ['menaspis', 'semispan'], + 'semisteel': ['messelite', 'semisteel', 'teleseism'], + 'semistill': ['limitless', 'semistill'], + 'semistriated': ['disastimeter', 'semistriated'], + 'semita': ['samite', 'semita', 'tamise', 'teaism'], + 'semitae': ['amesite', 'mesitae', 'semitae'], + 'semitour': ['moisture', 'semitour'], + 'semiurban': ['semiurban', 'submarine'], + 'semiurn': ['neurism', 'semiurn'], + 'semivector': ['semivector', 'viscometer'], + 'semnae': ['enseam', 'semnae'], + 'semola': ['melosa', 'salome', 'semola'], + 'semolella': ['lamellose', 'semolella'], + 'semolina': ['laminose', 'lemonias', 'semolina'], + 'semological': ['mesological', 'semological'], + 'semology': ['mesology', 'semology'], + 'semostomae': ['semeostoma', 'semostomae'], + 'sempiternous': ['sempiternous', 'supermoisten'], + 'semuncia': ['muscinae', 'semuncia'], + 'semuncial': ['masculine', 'semuncial', 'simulance'], + 'sen': ['ens', 'sen'], + 'senaite': ['etesian', 'senaite'], + 'senam': ['manes', 'manse', 'mensa', 'samen', 'senam'], + 'senarius': ['anuresis', 'senarius'], + 'senate': ['ensate', 'enseat', 'santee', 'sateen', 'senate'], + 'senator': ['noreast', 'rosetan', 'seatron', 'senator', 'treason'], + 'senatorian': ['arsenation', 'senatorian', 'sonneratia'], + 'senatrices': ['resistance', 'senatrices'], + 'sence': ['cense', 'scene', 'sence'], + 'senci': ['senci', 'since'], + 'send': ['send', 'sned'], + 'sender': ['resend', 'sender'], + 'seneca': ['encase', 'seance', 'seneca'], + 'senega': ['sagene', 'senega'], + 'senesce': ['essence', 'senesce'], + 'senile': ['enisle', 'ensile', 'senile', 'silene'], + 'senilism': ['liminess', 'senilism'], + 'senior': ['rosine', 'senior', 'soneri'], + 'senlac': ['lances', 'senlac'], + 'senna': ['nanes', 'senna'], + 'sennit': ['innest', 'sennit', 'sinnet', 'tennis'], + 'sennite': ['intense', 'sennite'], + 'senocular': ['larcenous', 'senocular'], + 'senones': ['oneness', 'senones'], + 'sensable': ['ableness', 'blaeness', 'sensable'], + 'sensatorial': ['assertional', 'sensatorial'], + 'sensical': ['laciness', 'sensical'], + 'sensilia': ['sensilia', 'silesian'], + 'sensilla': ['nailless', 'sensilla'], + 'sension': ['neossin', 'sension'], + 'sensuism': ['sensuism', 'senusism'], + 'sent': ['nest', 'sent', 'sten'], + 'sentencer': ['secernent', 'sentencer'], + 'sentition': ['sentition', 'tenonitis'], + 'senusism': ['sensuism', 'senusism'], + 'sepad': ['depas', 'sepad', 'spade'], + 'sepal': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'sepaled': ['delapse', 'sepaled'], + 'sepaloid': ['episodal', 'lapidose', 'sepaloid'], + 'separable': ['separable', 'spareable'], + 'separate': ['asperate', 'separate'], + 'separation': ['anisoptera', 'asperation', 'separation'], + 'sephardi': ['diphaser', 'parished', 'raphides', 'sephardi'], + 'sephen': ['sephen', 'sphene'], + 'sepian': ['sepian', 'spinae'], + 'sepic': ['sepic', 'spice'], + 'sepion': ['espino', 'sepion'], + 'sepoy': ['poesy', 'posey', 'sepoy'], + 'seps': ['pess', 'seps'], + 'sepsis': ['sepsis', 'speiss'], + 'sept': ['pest', 'sept', 'spet', 'step'], + 'septa': ['paste', 'septa', 'spate'], + 'septal': ['pastel', 'septal', 'staple'], + 'septane': ['penates', 'septane'], + 'septarium': ['impasture', 'septarium'], + 'septenar': ['entrepas', 'septenar'], + 'septennium': ['pennisetum', 'septennium'], + 'septentrio': ['septentrio', 'tripestone'], + 'septerium': ['misrepute', 'septerium'], + 'septi': ['septi', 'spite', 'stipe'], + 'septicemia': ['episematic', 'septicemia'], + 'septicidal': ['pesticidal', 'septicidal'], + 'septicopyemia': ['pyosepticemia', 'septicopyemia'], + 'septicopyemic': ['pyosepticemic', 'septicopyemic'], + 'septier': ['respite', 'septier'], + 'septiferous': ['pestiferous', 'septiferous'], + 'septile': ['epistle', 'septile'], + 'septimal': ['petalism', 'septimal'], + 'septocosta': ['septocosta', 'statoscope'], + 'septoic': ['poetics', 'septoic'], + 'septonasal': ['nasoseptal', 'septonasal'], + 'septoria': ['isoptera', 'septoria'], + 'septum': ['septum', 'upstem'], + 'septuor': ['petrous', 'posture', 'proetus', 'proteus', 'septuor', 'spouter'], + 'sequential': ['latinesque', 'sequential'], + 'sequin': ['quinse', 'sequin'], + 'ser': ['ers', 'ser'], + 'sera': ['arse', 'rase', 'sare', 'sear', 'sera'], + 'serab': ['barse', 'besra', 'saber', 'serab'], + 'serabend': ['seerband', 'serabend'], + 'seraglio': ['girasole', 'seraglio'], + 'serai': ['aries', 'arise', 'raise', 'serai'], + 'serail': ['israel', 'relais', 'resail', 'sailer', 'serail', 'serial'], + 'seral': ['arles', 'arsle', 'laser', 'seral', 'slare'], + 'serang': ['angers', 'sanger', 'serang'], + 'serape': ['parsee', 'persae', 'persea', 'serape'], + 'seraph': ['phrase', 'seraph', 'shaper', 'sherpa'], + 'seraphic': ['parchesi', 'seraphic'], + 'seraphim': ['samphire', 'seraphim'], + 'seraphina': ['pharisean', 'seraphina'], + 'seraphine': ['hesperian', 'phrenesia', 'seraphine'], + 'seraphism': ['misphrase', 'seraphism'], + 'serapic': ['epacris', 'scrapie', 'serapic'], + 'serapis': ['paresis', 'serapis'], + 'serapist': ['piratess', 'serapist', 'tarsipes'], + 'serau': ['serau', 'urase'], + 'seraw': ['resaw', 'sawer', 'seraw', 'sware', 'swear', 'warse'], + 'sercial': ['scleria', 'sercial'], + 'sere': ['erse', 'rees', 'seer', 'sere'], + 'serean': ['serean', 'serena'], + 'sereh': ['herse', 'sereh', 'sheer', 'shree'], + 'serena': ['serean', 'serena'], + 'serenata': ['arsenate', 'serenata'], + 'serene': ['resene', 'serene'], + 'serenoa': ['arenose', 'serenoa'], + 'serge': ['reges', 'serge'], + 'sergeant': ['estrange', 'segreant', 'sergeant', 'sternage'], + 'sergei': ['sergei', 'sieger'], + 'serger': ['gerres', 'serger'], + 'serging': ['serging', 'snigger'], + 'sergiu': ['guiser', 'sergiu'], + 'seri': ['reis', 'rise', 'seri', 'sier', 'sire'], + 'serial': ['israel', 'relais', 'resail', 'sailer', 'serail', 'serial'], + 'serialist': ['eristalis', 'serialist'], + 'serian': ['arisen', 'arsine', 'resina', 'serian'], + 'sericate': ['ecrasite', 'sericate'], + 'sericated': ['discreate', 'sericated'], + 'sericin': ['irenics', 'resinic', 'sericin', 'sirenic'], + 'serific': ['friesic', 'serific'], + 'serin': ['reins', 'resin', 'rinse', 'risen', 'serin', 'siren'], + 'serine': ['inseer', 'nereis', 'seiner', 'serine', 'sirene'], + 'serinette': ['retistene', 'serinette'], + 'seringa': ['searing', 'seringa'], + 'seringal': ['resignal', 'seringal', 'signaler'], + 'serinus': ['russine', 'serinus', 'sunrise'], + 'serio': ['osier', 'serio'], + 'seriola': ['rosalie', 'seriola'], + 'serioludicrous': ['ludicroserious', 'serioludicrous'], + 'sermo': ['meros', 'mores', 'morse', 'sermo', 'smore'], + 'sermonist': ['monitress', 'sermonist'], + 'sero': ['eros', 'rose', 'sero', 'sore'], + 'serofibrous': ['fibroserous', 'serofibrous'], + 'serolin': ['resinol', 'serolin'], + 'seromucous': ['mucoserous', 'seromucous'], + 'seron': ['norse', 'noser', 'seron', 'snore'], + 'seroon': ['nooser', 'seroon', 'sooner'], + 'seroot': ['seroot', 'sooter', 'torose'], + 'serotina': ['arsonite', 'asterion', 'oestrian', 'rosinate', 'serotina'], + 'serotinal': ['lairstone', 'orleanist', 'serotinal'], + 'serotine': ['serotine', 'torinese'], + 'serous': ['serous', 'souser'], + 'serow': ['owser', 'resow', 'serow', 'sower', 'swore', 'worse'], + 'serpari': ['aspirer', 'praiser', 'serpari'], + 'serpent': ['penster', 'present', 'serpent', 'strepen'], + 'serpentian': ['serpentian', 'serpentina'], + 'serpentid': ['president', 'serpentid'], + 'serpentina': ['serpentian', 'serpentina'], + 'serpentinous': ['serpentinous', 'supertension'], + 'serpently': ['presently', 'serpently'], + 'serpiginous': ['serpiginous', 'spinigerous'], + 'serpolet': ['proteles', 'serpolet'], + 'serpula': ['perusal', 'serpula'], + 'serpulae': ['pleasure', 'serpulae'], + 'serpulan': ['purslane', 'serpulan', 'supernal'], + 'serpulidae': ['serpulidae', 'superideal'], + 'serpuline': ['serpuline', 'superline'], + 'serra': ['ersar', 'raser', 'serra'], + 'serrage': ['argeers', 'greaser', 'serrage'], + 'serran': ['serran', 'snarer'], + 'serrano': ['serrano', 'sornare'], + 'serratic': ['crateris', 'serratic'], + 'serratodentate': ['dentatoserrate', 'serratodentate'], + 'serrature': ['serrature', 'treasurer'], + 'serried': ['derries', 'desirer', 'resider', 'serried'], + 'serriped': ['presider', 'serriped'], + 'sert': ['rest', 'sert', 'stre'], + 'serta': ['aster', 'serta', 'stare', 'strae', 'tarse', 'teras'], + 'sertum': ['muster', 'sertum', 'stumer'], + 'serum': ['muser', 'remus', 'serum'], + 'serut': ['serut', 'strue', 'turse', 'uster'], + 'servable': ['beslaver', 'servable', 'versable'], + 'servage': ['gervase', 'greaves', 'servage'], + 'serval': ['salver', 'serval', 'slaver', 'versal'], + 'servant': ['servant', 'versant'], + 'servation': ['overstain', 'servation', 'versation'], + 'serve': ['serve', 'sever', 'verse'], + 'server': ['revers', 'server', 'verser'], + 'servet': ['revest', 'servet', 'sterve', 'verset', 'vester'], + 'servetian': ['invertase', 'servetian'], + 'servian': ['servian', 'vansire'], + 'service': ['cerevis', 'scrieve', 'service'], + 'serviceable': ['receivables', 'serviceable'], + 'servient': ['reinvest', 'servient'], + 'serviential': ['inversatile', 'serviential'], + 'servilize': ['servilize', 'silverize'], + 'servite': ['restive', 'servite'], + 'servitor': ['overstir', 'servitor'], + 'servitude': ['detrusive', 'divesture', 'servitude'], + 'servo': ['servo', 'verso'], + 'sesma': ['masse', 'sesma'], + 'sestertium': ['sestertium', 'trusteeism'], + 'sestet': ['sestet', 'testes', 'tsetse'], + 'sestiad': ['disseat', 'sestiad'], + 'sestian': ['entasis', 'sestian', 'sestina'], + 'sestina': ['entasis', 'sestian', 'sestina'], + 'sestole': ['osselet', 'sestole', 'toeless'], + 'sestuor': ['estrous', 'oestrus', 'sestuor', 'tussore'], + 'sesuto': ['sesuto', 'setous'], + 'seta': ['ates', 'east', 'eats', 'sate', 'seat', 'seta'], + 'setae': ['setae', 'tease'], + 'setal': ['least', 'setal', 'slate', 'stale', 'steal', 'stela', 'tales'], + 'setaria': ['asarite', 'asteria', 'atresia', 'setaria'], + 'setback': ['backset', 'setback'], + 'setdown': ['downset', 'setdown'], + 'seth': ['esth', 'hest', 'seth'], + 'sethead': ['headset', 'sethead'], + 'sethian': ['sethian', 'sthenia'], + 'sethic': ['ethics', 'sethic'], + 'setibo': ['setibo', 'sobeit'], + 'setirostral': ['latirostres', 'setirostral'], + 'setline': ['leisten', 'setline', 'tensile'], + 'setoff': ['offset', 'setoff'], + 'seton': ['onset', 'seton', 'steno', 'stone'], + 'setous': ['sesuto', 'setous'], + 'setout': ['outset', 'setout'], + 'setover': ['overset', 'setover'], + 'sett': ['sett', 'stet', 'test'], + 'settable': ['settable', 'testable'], + 'settaine': ['anisette', 'atestine', 'settaine'], + 'settee': ['settee', 'testee'], + 'setter': ['retest', 'setter', 'street', 'tester'], + 'setting': ['setting', 'testing'], + 'settler': ['settler', 'sterlet', 'trestle'], + 'settlor': ['settlor', 'slotter'], + 'setula': ['salute', 'setula'], + 'setup': ['setup', 'stupe', 'upset'], + 'setwall': ['setwall', 'swallet'], + 'seven': ['evens', 'seven'], + 'sevener': ['sevener', 'veneres'], + 'sever': ['serve', 'sever', 'verse'], + 'severer': ['reserve', 'resever', 'reverse', 'severer'], + 'sew': ['sew', 'wes'], + 'sewed': ['sewed', 'swede'], + 'sewer': ['resew', 'sewer', 'sweer'], + 'sewered': ['sewered', 'sweered'], + 'sewing': ['sewing', 'swinge'], + 'sewn': ['news', 'sewn', 'snew'], + 'sewround': ['sewround', 'undersow'], + 'sexed': ['desex', 'sexed'], + 'sextan': ['saxten', 'sextan'], + 'sextipartition': ['extirpationist', 'sextipartition'], + 'sextodecimo': ['decimosexto', 'sextodecimo'], + 'sextry': ['sextry', 'xyster'], + 'sexuale': ['esexual', 'sexuale'], + 'sey': ['sey', 'sye', 'yes'], + 'seymour': ['mousery', 'seymour'], + 'sfoot': ['foots', 'sfoot', 'stoof'], + 'sgad': ['dags', 'sgad'], + 'sha': ['ash', 'sah', 'sha'], + 'shab': ['bash', 'shab'], + 'shabbily': ['babishly', 'shabbily'], + 'shabbiness': ['babishness', 'shabbiness'], + 'shabunder': ['husbander', 'shabunder'], + 'shad': ['dash', 'sadh', 'shad'], + 'shade': ['deash', 'hades', 'sadhe', 'shade'], + 'shaded': ['dashed', 'shaded'], + 'shader': ['dasher', 'shader', 'sheard'], + 'shadily': ['ladyish', 'shadily'], + 'shading': ['dashing', 'shading'], + 'shadkan': ['dashnak', 'shadkan'], + 'shady': ['dashy', 'shady'], + 'shafting': ['shafting', 'tangfish'], + 'shag': ['gash', 'shag'], + 'shagrag': ['ragshag', 'shagrag'], + 'shah': ['hash', 'sahh', 'shah'], + 'shahi': ['shahi', 'shiah'], + 'shaitan': ['ashanti', 'sanhita', 'shaitan', 'thasian'], + 'shaivism': ['shaivism', 'shivaism'], + 'shaka': ['kasha', 'khasa', 'sakha', 'shaka'], + 'shakeout': ['outshake', 'shakeout'], + 'shaker': ['kasher', 'shaker'], + 'shakil': ['lakish', 'shakil'], + 'shaku': ['kusha', 'shaku', 'ushak'], + 'shaky': ['hasky', 'shaky'], + 'shale': ['halse', 'leash', 'selah', 'shale', 'sheal', 'shela'], + 'shalt': ['shalt', 'slath'], + 'sham': ['mash', 'samh', 'sham'], + 'shama': ['hamsa', 'masha', 'shama'], + 'shamable': ['baalshem', 'shamable'], + 'shamal': ['mashal', 'shamal'], + 'shaman': ['ashman', 'shaman'], + 'shamba': ['ambash', 'shamba'], + 'shambrier': ['herbarism', 'shambrier'], + 'shambu': ['ambush', 'shambu'], + 'shame': ['sahme', 'shame'], + 'shamer': ['masher', 'ramesh', 'shamer'], + 'shamir': ['marish', 'shamir'], + 'shammish': ['mishmash', 'shammish'], + 'shan': ['hans', 'nash', 'shan'], + 'shane': ['ashen', 'hanse', 'shane', 'shean'], + 'shang': ['gnash', 'shang'], + 'shant': ['shant', 'snath'], + 'shap': ['hasp', 'pash', 'psha', 'shap'], + 'shape': ['heaps', 'pesah', 'phase', 'shape'], + 'shapeless': ['phaseless', 'shapeless'], + 'shaper': ['phrase', 'seraph', 'shaper', 'sherpa'], + 'shapometer': ['atmosphere', 'shapometer'], + 'shapy': ['physa', 'shapy'], + 'shardana': ['darshana', 'shardana'], + 'share': ['asher', 'share', 'shear'], + 'shareman': ['shareman', 'shearman'], + 'sharer': ['rasher', 'sharer'], + 'sharesman': ['sharesman', 'shearsman'], + 'shargar': ['shargar', 'sharrag'], + 'shari': ['ashir', 'shari'], + 'sharon': ['rhason', 'sharon', 'shoran'], + 'sharp': ['sharp', 'shrap'], + 'sharpener': ['resharpen', 'sharpener'], + 'sharper': ['phraser', 'sharper'], + 'sharpy': ['phrasy', 'sharpy'], + 'sharrag': ['shargar', 'sharrag'], + 'shasta': ['shasta', 'tassah'], + 'shaster': ['hatress', 'shaster'], + 'shastraik': ['katharsis', 'shastraik'], + 'shastri': ['sartish', 'shastri'], + 'shat': ['shat', 'tash'], + 'shatter': ['rathest', 'shatter'], + 'shatterer': ['ratherest', 'shatterer'], + 'shattering': ['shattering', 'straighten'], + 'shauri': ['shauri', 'surahi'], + 'shave': ['shave', 'sheva'], + 'shavee': ['shavee', 'sheave'], + 'shaver': ['havers', 'shaver', 'shrave'], + 'shavery': ['shavery', 'shravey'], + 'shaw': ['shaw', 'wash'], + 'shawano': ['shawano', 'washoan'], + 'shawl': ['shawl', 'walsh'], + 'shawy': ['shawy', 'washy'], + 'shay': ['ashy', 'shay'], + 'shea': ['seah', 'shea'], + 'sheal': ['halse', 'leash', 'selah', 'shale', 'sheal', 'shela'], + 'shean': ['ashen', 'hanse', 'shane', 'shean'], + 'shear': ['asher', 'share', 'shear'], + 'shearbill': ['shearbill', 'shillaber'], + 'sheard': ['dasher', 'shader', 'sheard'], + 'shearer': ['reshare', 'reshear', 'shearer'], + 'shearman': ['shareman', 'shearman'], + 'shearsman': ['sharesman', 'shearsman'], + 'sheat': ['ashet', 'haste', 'sheat'], + 'sheave': ['shavee', 'sheave'], + 'shebeen': ['benshee', 'shebeen'], + 'shechem': ['meshech', 'shechem'], + 'sheder': ['hersed', 'sheder'], + 'sheely': ['sheely', 'sheyle'], + 'sheer': ['herse', 'sereh', 'sheer', 'shree'], + 'sheering': ['greenish', 'sheering'], + 'sheet': ['sheet', 'these'], + 'sheeter': ['sheeter', 'therese'], + 'sheeting': ['seething', 'sheeting'], + 'sheila': ['elisha', 'hailse', 'sheila'], + 'shela': ['halse', 'leash', 'selah', 'shale', 'sheal', 'shela'], + 'shelf': ['flesh', 'shelf'], + 'shelfful': ['fleshful', 'shelfful'], + 'shelflist': ['filthless', 'shelflist'], + 'shelfy': ['fleshy', 'shelfy'], + 'shelta': ['haslet', 'lesath', 'shelta'], + 'shelty': ['shelty', 'thysel'], + 'shelve': ['shelve', 'shevel'], + 'shemitic': ['ethicism', 'shemitic'], + 'shen': ['nesh', 'shen'], + 'sheol': ['hosel', 'sheol', 'shole'], + 'sher': ['hers', 'resh', 'sher'], + 'sherani': ['arshine', 'nearish', 'rhesian', 'sherani'], + 'sheratan': ['hanaster', 'sheratan'], + 'sheriat': ['atheris', 'sheriat'], + 'sherif': ['fisher', 'sherif'], + 'sherifate': ['fisheater', 'sherifate'], + 'sherify': ['fishery', 'sherify'], + 'sheriyat': ['hysteria', 'sheriyat'], + 'sherpa': ['phrase', 'seraph', 'shaper', 'sherpa'], + 'sherri': ['hersir', 'sherri'], + 'sheugh': ['hughes', 'sheugh'], + 'sheva': ['shave', 'sheva'], + 'shevel': ['shelve', 'shevel'], + 'shevri': ['shevri', 'shiver', 'shrive'], + 'shewa': ['hawse', 'shewa', 'whase'], + 'sheyle': ['sheely', 'sheyle'], + 'shi': ['his', 'hsi', 'shi'], + 'shiah': ['shahi', 'shiah'], + 'shibar': ['barish', 'shibar'], + 'shice': ['echis', 'shice'], + 'shicer': ['riches', 'shicer'], + 'shide': ['shide', 'shied', 'sidhe'], + 'shied': ['shide', 'shied', 'sidhe'], + 'shiel': ['liesh', 'shiel'], + 'shieldable': ['deshabille', 'shieldable'], + 'shier': ['hirse', 'shier', 'shire'], + 'shiest': ['shiest', 'thesis'], + 'shifter': ['reshift', 'shifter'], + 'shih': ['hish', 'shih'], + 'shiite': ['histie', 'shiite'], + 'shik': ['kish', 'shik', 'sikh'], + 'shikar': ['rakish', 'riksha', 'shikar', 'shikra', 'sikhra'], + 'shikara': ['shikara', 'sikhara'], + 'shikari': ['rikisha', 'shikari'], + 'shikra': ['rakish', 'riksha', 'shikar', 'shikra', 'sikhra'], + 'shillaber': ['shearbill', 'shillaber'], + 'shimal': ['lamish', 'shimal'], + 'shimmery': ['misrhyme', 'shimmery'], + 'shin': ['hisn', 'shin', 'sinh'], + 'shina': ['naish', 'shina'], + 'shine': ['eshin', 'shine'], + 'shiner': ['renish', 'shiner', 'shrine'], + 'shingle': ['english', 'shingle'], + 'shinto': ['histon', 'shinto', 'tonish'], + 'shinty': ['shinty', 'snithy'], + 'ship': ['pish', 'ship'], + 'shipboy': ['boyship', 'shipboy'], + 'shipkeeper': ['keepership', 'shipkeeper'], + 'shiplap': ['lappish', 'shiplap'], + 'shipman': ['manship', 'shipman'], + 'shipmaster': ['mastership', 'shipmaster'], + 'shipmate': ['aphetism', 'mateship', 'shipmate', 'spithame'], + 'shipowner': ['ownership', 'shipowner'], + 'shippage': ['pageship', 'shippage'], + 'shipper': ['preship', 'shipper'], + 'shippo': ['popish', 'shippo'], + 'shipward': ['shipward', 'wardship'], + 'shipwork': ['shipwork', 'workship'], + 'shipworm': ['shipworm', 'wormship'], + 'shire': ['hirse', 'shier', 'shire'], + 'shirker': ['shirker', 'skirreh'], + 'shirley': ['relishy', 'shirley'], + 'shirty': ['shirty', 'thyris'], + 'shirvan': ['shirvan', 'varnish'], + 'shita': ['shita', 'thais'], + 'shivaism': ['shaivism', 'shivaism'], + 'shive': ['hives', 'shive'], + 'shiver': ['shevri', 'shiver', 'shrive'], + 'shlu': ['lush', 'shlu', 'shul'], + 'sho': ['sho', 'soh'], + 'shoa': ['saho', 'shoa'], + 'shoal': ['shoal', 'shola'], + 'shoat': ['hoast', 'hosta', 'shoat'], + 'shockable': ['shockable', 'shoeblack'], + 'shode': ['hosed', 'shode'], + 'shoder': ['dehors', 'rhodes', 'shoder', 'shored'], + 'shoe': ['hose', 'shoe'], + 'shoeblack': ['shockable', 'shoeblack'], + 'shoebrush': ['shoebrush', 'shorebush'], + 'shoeless': ['hoseless', 'shoeless'], + 'shoeman': ['hoseman', 'shoeman'], + 'shoer': ['horse', 'shoer', 'shore'], + 'shog': ['gosh', 'shog'], + 'shoji': ['joshi', 'shoji'], + 'shola': ['shoal', 'shola'], + 'shole': ['hosel', 'sheol', 'shole'], + 'shoo': ['shoo', 'soho'], + 'shoot': ['shoot', 'sooth', 'sotho', 'toosh'], + 'shooter': ['orthose', 'reshoot', 'shooter', 'soother'], + 'shooting': ['shooting', 'soothing'], + 'shop': ['phos', 'posh', 'shop', 'soph'], + 'shopbook': ['bookshop', 'shopbook'], + 'shopmaid': ['phasmoid', 'shopmaid'], + 'shopper': ['hoppers', 'shopper'], + 'shopwork': ['shopwork', 'workshop'], + 'shoran': ['rhason', 'sharon', 'shoran'], + 'shore': ['horse', 'shoer', 'shore'], + 'shorea': ['ahorse', 'ashore', 'hoarse', 'shorea'], + 'shorebush': ['shoebrush', 'shorebush'], + 'shored': ['dehors', 'rhodes', 'shoder', 'shored'], + 'shoreless': ['horseless', 'shoreless'], + 'shoreman': ['horseman', 'rhamnose', 'shoreman'], + 'shorer': ['horser', 'shorer'], + 'shoreward': ['drawhorse', 'shoreward'], + 'shoreweed': ['horseweed', 'shoreweed'], + 'shoring': ['horsing', 'shoring'], + 'short': ['horst', 'short'], + 'shortage': ['hostager', 'shortage'], + 'shorten': ['shorten', 'threnos'], + 'shot': ['host', 'shot', 'thos', 'tosh'], + 'shote': ['ethos', 'shote', 'those'], + 'shotgun': ['gunshot', 'shotgun', 'uhtsong'], + 'shotless': ['hostless', 'shotless'], + 'shotstar': ['shotstar', 'starshot'], + 'shou': ['huso', 'shou'], + 'shoulderer': ['reshoulder', 'shoulderer'], + 'shout': ['shout', 'south'], + 'shouter': ['shouter', 'souther'], + 'shouting': ['shouting', 'southing'], + 'shover': ['shover', 'shrove'], + 'showerer': ['reshower', 'showerer'], + 'shrab': ['brash', 'shrab'], + 'shram': ['marsh', 'shram'], + 'shrap': ['sharp', 'shrap'], + 'shrave': ['havers', 'shaver', 'shrave'], + 'shravey': ['shavery', 'shravey'], + 'shree': ['herse', 'sereh', 'sheer', 'shree'], + 'shrewly': ['shrewly', 'welshry'], + 'shriek': ['shriek', 'shrike'], + 'shrieval': ['lavisher', 'shrieval'], + 'shrike': ['shriek', 'shrike'], + 'shrine': ['renish', 'shiner', 'shrine'], + 'shrite': ['shrite', 'theirs'], + 'shrive': ['shevri', 'shiver', 'shrive'], + 'shriven': ['nervish', 'shriven'], + 'shroudy': ['hydrous', 'shroudy'], + 'shrove': ['shover', 'shrove'], + 'shrub': ['brush', 'shrub'], + 'shrubbery': ['berrybush', 'shrubbery'], + 'shrubland': ['brushland', 'shrubland'], + 'shrubless': ['brushless', 'shrubless'], + 'shrublet': ['brushlet', 'shrublet'], + 'shrublike': ['brushlike', 'shrublike'], + 'shrubwood': ['brushwood', 'shrubwood'], + 'shrug': ['grush', 'shrug'], + 'shu': ['shu', 'ush'], + 'shuba': ['shuba', 'subah'], + 'shug': ['gush', 'shug', 'sugh'], + 'shul': ['lush', 'shlu', 'shul'], + 'shulamite': ['hamulites', 'shulamite'], + 'shuler': ['lusher', 'shuler'], + 'shunless': ['lushness', 'shunless'], + 'shunter': ['reshunt', 'shunter'], + 'shure': ['shure', 'usher'], + 'shurf': ['frush', 'shurf'], + 'shut': ['shut', 'thus', 'tush'], + 'shutness': ['shutness', 'thusness'], + 'shutout': ['outshut', 'shutout'], + 'shuttering': ['hurtingest', 'shuttering'], + 'shyam': ['mashy', 'shyam'], + 'si': ['is', 'si'], + 'sia': ['sai', 'sia'], + 'siak': ['saki', 'siak', 'sika'], + 'sial': ['lasi', 'lias', 'lisa', 'sail', 'sial'], + 'sialagogic': ['isagogical', 'sialagogic'], + 'sialic': ['sialic', 'silica'], + 'sialid': ['asilid', 'sialid'], + 'sialidae': ['asilidae', 'sialidae'], + 'siam': ['mias', 'saim', 'siam', 'sima'], + 'siamese': ['misease', 'siamese'], + 'sib': ['bis', 'sib'], + 'sibyl': ['sibyl', 'sybil'], + 'sibylla': ['sibylla', 'syllabi'], + 'sicana': ['ascian', 'sacian', 'scania', 'sicana'], + 'sicani': ['anisic', 'sicani', 'sinaic'], + 'sicarius': ['acrisius', 'sicarius'], + 'siccate': ['ascetic', 'castice', 'siccate'], + 'siccation': ['cocainist', 'siccation'], + 'sice': ['cise', 'sice'], + 'sicel': ['sicel', 'slice'], + 'sicilian': ['anisilic', 'sicilian'], + 'sickbed': ['bedsick', 'sickbed'], + 'sicker': ['scrike', 'sicker'], + 'sickerly': ['sickerly', 'slickery'], + 'sickle': ['sickle', 'skelic'], + 'sickler': ['sickler', 'slicker'], + 'sicklied': ['disclike', 'sicklied'], + 'sickling': ['sickling', 'slicking'], + 'sicula': ['caulis', 'clusia', 'sicula'], + 'siculian': ['luscinia', 'siculian'], + 'sid': ['dis', 'sid'], + 'sida': ['dais', 'dasi', 'disa', 'said', 'sida'], + 'sidalcea': ['diaclase', 'sidalcea'], + 'side': ['desi', 'ides', 'seid', 'side'], + 'sidearm': ['misread', 'sidearm'], + 'sideboard': ['broadside', 'sideboard'], + 'sideburns': ['burnsides', 'sideburns'], + 'sidecar': ['diceras', 'radices', 'sidecar'], + 'sidehill': ['hillside', 'sidehill'], + 'siderean': ['arsedine', 'arsenide', 'sedanier', 'siderean'], + 'siderin': ['insider', 'siderin'], + 'sideronatrite': ['endoarteritis', 'sideronatrite'], + 'siderous': ['desirous', 'siderous'], + 'sidership': ['sidership', 'spiderish'], + 'sidesway': ['sidesway', 'sideways'], + 'sidetrack': ['sidetrack', 'trackside'], + 'sidewalk': ['sidewalk', 'walkside'], + 'sideway': ['sideway', 'wayside'], + 'sideways': ['sidesway', 'sideways'], + 'sidhe': ['shide', 'shied', 'sidhe'], + 'sidle': ['sidle', 'slide'], + 'sidler': ['sidler', 'slider'], + 'sidling': ['sidling', 'sliding'], + 'sidlingly': ['sidlingly', 'slidingly'], + 'sieger': ['sergei', 'sieger'], + 'siena': ['anise', 'insea', 'siena', 'sinae'], + 'sienna': ['insane', 'sienna'], + 'sier': ['reis', 'rise', 'seri', 'sier', 'sire'], + 'sierra': ['raiser', 'sierra'], + 'siesta': ['siesta', 'tassie'], + 'siever': ['revise', 'siever'], + 'sife': ['feis', 'fise', 'sife'], + 'sift': ['fist', 'sift'], + 'sifted': ['fisted', 'sifted'], + 'sifter': ['fister', 'resift', 'sifter', 'strife'], + 'sifting': ['fisting', 'sifting'], + 'sigh': ['gish', 'sigh'], + 'sigher': ['resigh', 'sigher'], + 'sighted': ['desight', 'sighted'], + 'sightlily': ['sightlily', 'slightily'], + 'sightliness': ['sightliness', 'slightiness'], + 'sightly': ['sightly', 'slighty'], + 'sigillated': ['distillage', 'sigillated'], + 'sigla': ['gisla', 'ligas', 'sigla'], + 'sigmatism': ['astigmism', 'sigmatism'], + 'sigmoidal': ['dialogism', 'sigmoidal'], + 'sign': ['sign', 'sing', 'snig'], + 'signable': ['signable', 'singable'], + 'signalee': ['ensilage', 'genesial', 'signalee'], + 'signaler': ['resignal', 'seringal', 'signaler'], + 'signalese': ['agileness', 'signalese'], + 'signally': ['signally', 'singally', 'slangily'], + 'signary': ['signary', 'syringa'], + 'signate': ['easting', + 'gainset', + 'genista', + 'ingesta', + 'seating', + 'signate', + 'teasing'], + 'signator': ['orangist', 'organist', 'roasting', 'signator'], + 'signee': ['seeing', 'signee'], + 'signer': ['resign', 'resing', 'signer', 'singer'], + 'signet': ['ingest', 'signet', 'stinge'], + 'signorial': ['sailoring', 'signorial'], + 'signpost': ['postsign', 'signpost'], + 'signum': ['musing', 'signum'], + 'sika': ['saki', 'siak', 'sika'], + 'sikar': ['kisra', 'sikar', 'skair'], + 'siket': ['siket', 'skite'], + 'sikh': ['kish', 'shik', 'sikh'], + 'sikhara': ['shikara', 'sikhara'], + 'sikhra': ['rakish', 'riksha', 'shikar', 'shikra', 'sikhra'], + 'sil': ['lis', 'sil'], + 'silane': ['alsine', 'neslia', 'saline', 'selina', 'silane'], + 'silas': ['silas', 'sisal'], + 'silcrete': ['sclerite', 'silcrete'], + 'sile': ['isle', 'lise', 'sile'], + 'silen': ['elsin', 'lenis', 'niels', 'silen', 'sline'], + 'silence': ['license', 'selenic', 'silence'], + 'silenced': ['licensed', 'silenced'], + 'silencer': ['licenser', 'silencer'], + 'silene': ['enisle', 'ensile', 'senile', 'silene'], + 'silent': ['enlist', 'listen', 'silent', 'tinsel'], + 'silently': ['silently', 'tinselly'], + 'silenus': ['insulse', 'silenus'], + 'silesian': ['sensilia', 'silesian'], + 'silica': ['sialic', 'silica'], + 'silicam': ['islamic', 'laicism', 'silicam'], + 'silicane': ['silicane', 'silicean'], + 'silicean': ['silicane', 'silicean'], + 'siliceocalcareous': ['calcareosiliceous', 'siliceocalcareous'], + 'silicoaluminate': ['aluminosilicate', 'silicoaluminate'], + 'silicone': ['isocline', 'silicone'], + 'silicotitanate': ['silicotitanate', 'titanosilicate'], + 'silicotungstate': ['silicotungstate', 'tungstosilicate'], + 'silicotungstic': ['silicotungstic', 'tungstosilicic'], + 'silk': ['lisk', 'silk', 'skil'], + 'silkaline': ['silkaline', 'snaillike'], + 'silkman': ['klanism', 'silkman'], + 'silkness': ['silkness', 'sinkless', 'skinless'], + 'silly': ['silly', 'silyl'], + 'sillyhow': ['lowishly', 'owlishly', 'sillyhow'], + 'silo': ['lois', 'silo', 'siol', 'soil', 'soli'], + 'silpha': ['palish', 'silpha'], + 'silt': ['list', 'silt', 'slit'], + 'silting': ['listing', 'silting'], + 'siltlike': ['siltlike', 'slitlike'], + 'silva': ['silva', 'slavi'], + 'silver': ['silver', 'sliver'], + 'silvered': ['desilver', 'silvered'], + 'silverer': ['resilver', 'silverer', 'sliverer'], + 'silverize': ['servilize', 'silverize'], + 'silverlike': ['silverlike', 'sliverlike'], + 'silverwood': ['silverwood', 'woodsilver'], + 'silvery': ['silvery', 'slivery'], + 'silvester': ['rivetless', 'silvester'], + 'silyl': ['silly', 'silyl'], + 'sim': ['ism', 'sim'], + 'sima': ['mias', 'saim', 'siam', 'sima'], + 'simal': ['islam', 'ismal', 'simal'], + 'simar': ['maris', 'marsi', 'samir', 'simar'], + 'sime': ['mise', 'semi', 'sime'], + 'simeon': ['eonism', 'mesion', 'oneism', 'simeon'], + 'simeonism': ['misoneism', 'simeonism'], + 'simiad': ['idiasm', 'simiad'], + 'simile': ['milsie', 'simile'], + 'simity': ['myitis', 'simity'], + 'simling': ['simling', 'smiling'], + 'simmer': ['merism', 'mermis', 'simmer'], + 'simmon': ['monism', 'nomism', 'simmon'], + 'simon': ['minos', 'osmin', 'simon'], + 'simonian': ['insomnia', 'simonian'], + 'simonist': ['simonist', 'sintoism'], + 'simony': ['isonym', 'myosin', 'simony'], + 'simple': ['mespil', 'simple'], + 'simpleton': ['simpleton', 'spoilment'], + 'simplicist': ['simplicist', 'simplistic'], + 'simplistic': ['simplicist', 'simplistic'], + 'simply': ['limpsy', 'simply'], + 'simson': ['nosism', 'simson'], + 'simulance': ['masculine', 'semuncial', 'simulance'], + 'simulcast': ['masculist', 'simulcast'], + 'simuler': ['misrule', 'simuler'], + 'sina': ['anis', 'nais', 'nasi', 'nias', 'sain', 'sina'], + 'sinae': ['anise', 'insea', 'siena', 'sinae'], + 'sinaean': ['nisaean', 'sinaean'], + 'sinaic': ['anisic', 'sicani', 'sinaic'], + 'sinaitic': ['isatinic', 'sinaitic'], + 'sinal': ['sinal', 'slain', 'snail'], + 'sinapic': ['panisic', 'piscian', 'piscina', 'sinapic'], + 'since': ['senci', 'since'], + 'sincere': ['ceresin', 'sincere'], + 'sinecure': ['insecure', 'sinecure'], + 'sinew': ['sinew', 'swine', 'wisen'], + 'sinewed': ['endwise', 'sinewed'], + 'sinewy': ['sinewy', 'swiney'], + 'sinfonia': ['sainfoin', 'sinfonia'], + 'sinfonietta': ['festination', 'infestation', 'sinfonietta'], + 'sing': ['sign', 'sing', 'snig'], + 'singable': ['signable', 'singable'], + 'singally': ['signally', 'singally', 'slangily'], + 'singarip': ['aspiring', 'praising', 'singarip'], + 'singed': ['design', 'singed'], + 'singer': ['resign', 'resing', 'signer', 'singer'], + 'single': ['single', 'slinge'], + 'singler': ['singler', 'slinger'], + 'singles': ['essling', 'singles'], + 'singlet': ['glisten', 'singlet'], + 'sinh': ['hisn', 'shin', 'sinh'], + 'sinico': ['inosic', 'sinico'], + 'sinister': ['insister', 'reinsist', 'sinister', 'sisterin'], + 'sinistrodextral': ['dextrosinistral', 'sinistrodextral'], + 'sink': ['inks', 'sink', 'skin'], + 'sinker': ['resink', 'reskin', 'sinker'], + 'sinkhead': ['headskin', 'nakedish', 'sinkhead'], + 'sinkless': ['silkness', 'sinkless', 'skinless'], + 'sinklike': ['sinklike', 'skinlike'], + 'sinnet': ['innest', 'sennit', 'sinnet', 'tennis'], + 'sinoatrial': ['sinoatrial', 'solitarian'], + 'sinogram': ['orangism', 'organism', 'sinogram'], + 'sinolog': ['loosing', 'sinolog'], + 'sinopia': ['pisonia', 'sinopia'], + 'sinople': ['epsilon', 'sinople'], + 'sinter': ['estrin', 'insert', 'sinter', 'sterin', 'triens'], + 'sinto': ['sinto', 'stion'], + 'sintoc': ['nostic', 'sintoc', 'tocsin'], + 'sintoism': ['simonist', 'sintoism'], + 'sintu': ['sintu', 'suint'], + 'sinuatodentate': ['dentatosinuate', 'sinuatodentate'], + 'sinuose': ['sinuose', 'suiones'], + 'sinus': ['nisus', 'sinus'], + 'sinward': ['inwards', 'sinward'], + 'siol': ['lois', 'silo', 'siol', 'soil', 'soli'], + 'sionite': ['inosite', 'sionite'], + 'sip': ['psi', 'sip'], + 'sipe': ['pise', 'sipe'], + 'siper': ['siper', 'spier', 'spire'], + 'siphonal': ['nailshop', 'siphonal'], + 'siphuncle': ['siphuncle', 'uncleship'], + 'sipling': ['sipling', 'spiling'], + 'sipylite': ['pyelitis', 'sipylite'], + 'sir': ['sir', 'sri'], + 'sire': ['reis', 'rise', 'seri', 'sier', 'sire'], + 'siredon': ['indorse', 'ordines', 'siredon', 'sordine'], + 'siren': ['reins', 'resin', 'rinse', 'risen', 'serin', 'siren'], + 'sirene': ['inseer', 'nereis', 'seiner', 'serine', 'sirene'], + 'sirenic': ['irenics', 'resinic', 'sericin', 'sirenic'], + 'sirenize': ['resinize', 'sirenize'], + 'sirenlike': ['resinlike', 'sirenlike'], + 'sirenoid': ['derision', 'ironside', 'resinoid', 'sirenoid'], + 'sireny': ['resiny', 'sireny'], + 'sirian': ['raisin', 'sirian'], + 'sirih': ['irish', 'rishi', 'sirih'], + 'sirky': ['risky', 'sirky'], + 'sirmian': ['iranism', 'sirmian'], + 'sirpea': ['aspire', 'paries', 'praise', 'sirpea', 'spirea'], + 'sirple': ['lisper', 'pliers', 'sirple', 'spiler'], + 'sirrah': ['arrish', 'harris', 'rarish', 'sirrah'], + 'sirree': ['rerise', 'sirree'], + 'siruelas': ['russelia', 'siruelas'], + 'sirup': ['prius', 'sirup'], + 'siruper': ['siruper', 'upriser'], + 'siryan': ['siryan', 'syrian'], + 'sis': ['sis', 'ssi'], + 'sisal': ['silas', 'sisal'], + 'sish': ['hiss', 'sish'], + 'sisham': ['samish', 'sisham'], + 'sisi': ['isis', 'sisi'], + 'sisseton': ['sisseton', 'stenosis'], + 'sistani': ['nasitis', 'sistani'], + 'sister': ['resist', 'restis', 'sister'], + 'sisterin': ['insister', 'reinsist', 'sinister', 'sisterin'], + 'sistering': ['resisting', 'sistering'], + 'sisterless': ['resistless', 'sisterless'], + 'sistrum': ['sistrum', 'trismus'], + 'sit': ['ist', 'its', 'sit'], + 'sita': ['atis', 'sita', 'tsia'], + 'sitao': ['sitao', 'staio'], + 'sitar': ['arist', + 'astir', + 'sitar', + 'stair', + 'stria', + 'tarsi', + 'tisar', + 'trias'], + 'sitch': ['sitch', 'stchi', 'stich'], + 'site': ['seit', 'site'], + 'sith': ['hist', 'sith', 'this', 'tshi'], + 'sithens': ['sithens', 'thissen'], + 'sitient': ['sitient', 'sittine'], + 'sitology': ['sitology', 'tsiology'], + 'sittinae': ['satinite', 'sittinae'], + 'sittine': ['sitient', 'sittine'], + 'situal': ['situal', 'situla', 'tulasi'], + 'situate': ['situate', 'usitate'], + 'situla': ['situal', 'situla', 'tulasi'], + 'situs': ['situs', 'suist'], + 'siva': ['avis', 'siva', 'visa'], + 'sivaism': ['saivism', 'sivaism'], + 'sivan': ['savin', 'sivan'], + 'siwan': ['siwan', 'swain'], + 'siwash': ['sawish', 'siwash'], + 'sixte': ['exist', 'sixte'], + 'sixty': ['sixty', 'xysti'], + 'sizeable': ['seizable', 'sizeable'], + 'sizeman': ['sizeman', 'zamenis'], + 'skair': ['kisra', 'sikar', 'skair'], + 'skal': ['lask', 'skal'], + 'skance': ['sacken', 'skance'], + 'skanda': ['sandak', 'skanda'], + 'skart': ['karst', 'skart', 'stark'], + 'skat': ['skat', 'task'], + 'skate': ['skate', 'stake', 'steak'], + 'skater': ['skater', 'staker', 'strake', 'streak', 'tasker'], + 'skating': ['gitksan', 'skating', 'takings'], + 'skean': ['skean', 'snake', 'sneak'], + 'skee': ['kees', 'seek', 'skee'], + 'skeel': ['skeel', 'sleek'], + 'skeeling': ['skeeling', 'sleeking'], + 'skeely': ['skeely', 'sleeky'], + 'skeen': ['skeen', 'skene'], + 'skeer': ['esker', 'keres', 'reesk', 'seker', 'skeer', 'skere'], + 'skeery': ['kersey', 'skeery'], + 'skeet': ['keest', 'skeet', 'skete', 'steek'], + 'skeeter': ['skeeter', 'teskere'], + 'skeletin': ['nestlike', 'skeletin'], + 'skelic': ['sickle', 'skelic'], + 'skelp': ['skelp', 'spelk'], + 'skelter': ['kestrel', 'skelter'], + 'skene': ['skeen', 'skene'], + 'skeo': ['skeo', 'soke'], + 'skeptic': ['skeptic', 'spicket'], + 'skere': ['esker', 'keres', 'reesk', 'seker', 'skeer', 'skere'], + 'sketcher': ['resketch', 'sketcher'], + 'skete': ['keest', 'skeet', 'skete', 'steek'], + 'skey': ['skey', 'skye'], + 'skid': ['disk', 'kids', 'skid'], + 'skier': ['kreis', 'skier'], + 'skil': ['lisk', 'silk', 'skil'], + 'skin': ['inks', 'sink', 'skin'], + 'skinch': ['chinks', 'skinch'], + 'skinless': ['silkness', 'sinkless', 'skinless'], + 'skinlike': ['sinklike', 'skinlike'], + 'skip': ['pisk', 'skip'], + 'skippel': ['skippel', 'skipple'], + 'skipple': ['skippel', 'skipple'], + 'skirmish': ['skirmish', 'smirkish'], + 'skirreh': ['shirker', 'skirreh'], + 'skirret': ['skirret', 'skirter', 'striker'], + 'skirt': ['skirt', 'stirk'], + 'skirter': ['skirret', 'skirter', 'striker'], + 'skirting': ['skirting', 'striking'], + 'skirtingly': ['skirtingly', 'strikingly'], + 'skirty': ['kirsty', 'skirty'], + 'skit': ['kist', 'skit'], + 'skite': ['siket', 'skite'], + 'skiter': ['skiter', 'strike'], + 'skittle': ['kittles', 'skittle'], + 'sklate': ['lasket', 'sklate'], + 'sklater': ['sklater', 'stalker'], + 'sklinter': ['sklinter', 'strinkle'], + 'skoal': ['skoal', 'sloka'], + 'skoo': ['koso', 'skoo', 'sook'], + 'skua': ['kusa', 'skua'], + 'skun': ['skun', 'sunk'], + 'skye': ['skey', 'skye'], + 'sla': ['las', 'sal', 'sla'], + 'slab': ['blas', 'slab'], + 'slacken': ['slacken', 'snackle'], + 'slade': ['leads', 'slade'], + 'slae': ['elsa', 'sale', 'seal', 'slae'], + 'slain': ['sinal', 'slain', 'snail'], + 'slainte': ['elastin', 'salient', 'saltine', 'slainte'], + 'slait': ['alist', 'litas', 'slait', 'talis'], + 'slake': ['alkes', 'sakel', 'slake'], + 'slam': ['alms', 'salm', 'slam'], + 'slamp': ['plasm', 'psalm', 'slamp'], + 'slandering': ['sanderling', 'slandering'], + 'slane': ['ansel', 'slane'], + 'slang': ['glans', 'slang'], + 'slangily': ['signally', 'singally', 'slangily'], + 'slangish': ['slangish', 'slashing'], + 'slangishly': ['slangishly', 'slashingly'], + 'slangster': ['slangster', 'strangles'], + 'slap': ['salp', 'slap'], + 'slape': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'slare': ['arles', 'arsle', 'laser', 'seral', 'slare'], + 'slasher': ['reslash', 'slasher'], + 'slashing': ['slangish', 'slashing'], + 'slashingly': ['slangishly', 'slashingly'], + 'slat': ['last', 'salt', 'slat'], + 'slate': ['least', 'setal', 'slate', 'stale', 'steal', 'stela', 'tales'], + 'slater': ['laster', + 'lastre', + 'rastle', + 'relast', + 'resalt', + 'salter', + 'slater', + 'stelar'], + 'slath': ['shalt', 'slath'], + 'slather': ['hastler', 'slather'], + 'slatiness': ['saintless', 'saltiness', 'slatiness', 'stainless'], + 'slating': ['anglist', 'lasting', 'salting', 'slating', 'staling'], + 'slatish': ['saltish', 'slatish'], + 'slatter': ['rattles', 'slatter', 'starlet', 'startle'], + 'slaty': ['lasty', 'salty', 'slaty'], + 'slaughter': ['lethargus', 'slaughter'], + 'slaughterman': ['manslaughter', 'slaughterman'], + 'slaum': ['lamus', 'malus', 'musal', 'slaum'], + 'slave': ['salve', 'selva', 'slave', 'valse'], + 'slaver': ['salver', 'serval', 'slaver', 'versal'], + 'slaverer': ['reserval', 'reversal', 'slaverer'], + 'slavey': ['slavey', 'sylvae'], + 'slavi': ['silva', 'slavi'], + 'slavian': ['salivan', 'slavian'], + 'slavic': ['clavis', 'slavic'], + 'slavonic': ['slavonic', 'volscian'], + 'slay': ['lyas', 'slay'], + 'slayer': ['reslay', 'slayer'], + 'sleave': ['leaves', 'sleave'], + 'sledger': ['redlegs', 'sledger'], + 'slee': ['else', 'lees', 'seel', 'sele', 'slee'], + 'sleech': ['lesche', 'sleech'], + 'sleek': ['skeel', 'sleek'], + 'sleeking': ['skeeling', 'sleeking'], + 'sleeky': ['skeely', 'sleeky'], + 'sleep': ['sleep', 'speel'], + 'sleepless': ['sleepless', 'speelless'], + 'sleepry': ['presley', 'sleepry'], + 'sleet': ['sleet', 'slete', 'steel', 'stele'], + 'sleetiness': ['sleetiness', 'steeliness'], + 'sleeting': ['sleeting', 'steeling'], + 'sleetproof': ['sleetproof', 'steelproof'], + 'sleety': ['sleety', 'steely'], + 'slept': ['slept', 'spelt', 'splet'], + 'slete': ['sleet', 'slete', 'steel', 'stele'], + 'sleuth': ['hustle', 'sleuth'], + 'slew': ['slew', 'wels'], + 'slewing': ['slewing', 'swingle'], + 'sley': ['lyse', 'sley'], + 'slice': ['sicel', 'slice'], + 'slicht': ['slicht', 'slitch'], + 'slicken': ['slicken', 'snickle'], + 'slicker': ['sickler', 'slicker'], + 'slickery': ['sickerly', 'slickery'], + 'slicking': ['sickling', 'slicking'], + 'slidable': ['sabellid', 'slidable'], + 'slidden': ['slidden', 'sniddle'], + 'slide': ['sidle', 'slide'], + 'slider': ['sidler', 'slider'], + 'sliding': ['sidling', 'sliding'], + 'slidingly': ['sidlingly', 'slidingly'], + 'slifter': ['slifter', 'stifler'], + 'slightily': ['sightlily', 'slightily'], + 'slightiness': ['sightliness', 'slightiness'], + 'slighty': ['sightly', 'slighty'], + 'slime': ['limes', 'miles', 'slime', 'smile'], + 'slimeman': ['melanism', 'slimeman'], + 'slimer': ['slimer', 'smiler'], + 'slimy': ['limsy', 'slimy', 'smily'], + 'sline': ['elsin', 'lenis', 'niels', 'silen', 'sline'], + 'slinge': ['single', 'slinge'], + 'slinger': ['singler', 'slinger'], + 'slink': ['links', 'slink'], + 'slip': ['lisp', 'slip'], + 'slipcoat': ['postical', 'slipcoat'], + 'slipe': ['piles', 'plies', 'slipe', 'spiel', 'spile'], + 'slipover': ['overslip', 'slipover'], + 'slipway': ['slipway', 'waspily'], + 'slit': ['list', 'silt', 'slit'], + 'slitch': ['slicht', 'slitch'], + 'slite': ['islet', 'istle', 'slite', 'stile'], + 'slithy': ['hylist', 'slithy'], + 'slitless': ['listless', 'slitless'], + 'slitlike': ['siltlike', 'slitlike'], + 'slitted': ['slitted', 'stilted'], + 'slitter': ['litster', 'slitter', 'stilter', 'testril'], + 'slitty': ['slitty', 'stilty'], + 'slive': ['elvis', 'levis', 'slive'], + 'sliver': ['silver', 'sliver'], + 'sliverer': ['resilver', 'silverer', 'sliverer'], + 'sliverlike': ['silverlike', 'sliverlike'], + 'slivery': ['silvery', 'slivery'], + 'sloan': ['salon', 'sloan', 'solan'], + 'slod': ['slod', 'sold'], + 'sloe': ['lose', 'sloe', 'sole'], + 'sloka': ['skoal', 'sloka'], + 'slone': ['slone', 'solen'], + 'sloo': ['sloo', 'solo', 'sool'], + 'sloom': ['mools', 'sloom'], + 'sloop': ['polos', 'sloop', 'spool'], + 'slope': ['elops', 'slope', 'spole'], + 'sloper': ['sloper', 'splore'], + 'slot': ['lost', 'lots', 'slot'], + 'slote': ['slote', 'stole'], + 'sloted': ['sloted', 'stoled'], + 'slotter': ['settlor', 'slotter'], + 'slouch': ['holcus', 'lochus', 'slouch'], + 'slouchiness': ['cushionless', 'slouchiness'], + 'slouchy': ['chylous', 'slouchy'], + 'slovenian': ['slovenian', 'venosinal'], + 'slow': ['slow', 'sowl'], + 'slud': ['slud', 'suld'], + 'slue': ['lues', 'slue'], + 'sluit': ['litus', 'sluit', 'tulsi'], + 'slunge': ['gunsel', 'selung', 'slunge'], + 'slurp': ['slurp', 'spurl'], + 'slut': ['lust', 'slut'], + 'sluther': ['hulster', 'hustler', 'sluther'], + 'slutter': ['slutter', 'trustle'], + 'sly': ['lys', 'sly'], + 'sma': ['mas', 'sam', 'sma'], + 'smachrie': ['semiarch', 'smachrie'], + 'smallen': ['ensmall', 'smallen'], + 'smalltime': ['metallism', 'smalltime'], + 'smaltine': ['mentalis', 'smaltine', 'stileman'], + 'smaltite': ['metalist', 'smaltite'], + 'smart': ['smart', 'stram'], + 'smarten': ['sarment', 'smarten'], + 'smashage': ['gamashes', 'smashage'], + 'smeary': ['ramsey', 'smeary'], + 'smectis': ['sectism', 'smectis'], + 'smee': ['mese', 'seem', 'seme', 'smee'], + 'smeech': ['scheme', 'smeech'], + 'smeek': ['meeks', 'smeek'], + 'smeer': ['merse', 'smeer'], + 'smeeth': ['smeeth', 'smethe'], + 'smeller': ['resmell', 'smeller'], + 'smelly': ['mysell', 'smelly'], + 'smelter': ['melters', 'resmelt', 'smelter'], + 'smethe': ['smeeth', 'smethe'], + 'smilax': ['laxism', 'smilax'], + 'smile': ['limes', 'miles', 'slime', 'smile'], + 'smiler': ['slimer', 'smiler'], + 'smilet': ['mistle', 'smilet'], + 'smiling': ['simling', 'smiling'], + 'smily': ['limsy', 'slimy', 'smily'], + 'sminthian': ['mitannish', 'sminthian'], + 'smirch': ['chrism', 'smirch'], + 'smirkish': ['skirmish', 'smirkish'], + 'smit': ['mist', 'smit', 'stim'], + 'smite': ['metis', 'smite', 'stime', 'times'], + 'smiter': ['merist', 'mister', 'smiter'], + 'smither': ['rhemist', 'smither'], + 'smithian': ['isthmian', 'smithian'], + 'smoker': ['mosker', 'smoker'], + 'smoot': ['moost', 'smoot'], + 'smoother': ['resmooth', 'romeshot', 'smoother'], + 'smoothingly': ['hymnologist', 'smoothingly'], + 'smore': ['meros', 'mores', 'morse', 'sermo', 'smore'], + 'smote': ['moste', 'smote'], + 'smother': ['smother', 'thermos'], + 'smouse': ['mousse', 'smouse'], + 'smouser': ['osmerus', 'smouser'], + 'smuggle': ['muggles', 'smuggle'], + 'smut': ['must', 'smut', 'stum'], + 'smyrniot': ['smyrniot', 'tyronism'], + 'smyrniote': ['myristone', 'smyrniote'], + 'snab': ['nabs', 'snab'], + 'snackle': ['slacken', 'snackle'], + 'snag': ['sang', 'snag'], + 'snagrel': ['sangrel', 'snagrel'], + 'snail': ['sinal', 'slain', 'snail'], + 'snaillike': ['silkaline', 'snaillike'], + 'snaily': ['anisyl', 'snaily'], + 'snaith': ['snaith', 'tahsin'], + 'snake': ['skean', 'snake', 'sneak'], + 'snap': ['snap', 'span'], + 'snape': ['aspen', 'panse', 'snape', 'sneap', 'spane', 'spean'], + 'snaper': ['resnap', 'respan', 'snaper'], + 'snapless': ['snapless', 'spanless'], + 'snapy': ['pansy', 'snapy'], + 'snare': ['anser', 'nares', 'rasen', 'snare'], + 'snarer': ['serran', 'snarer'], + 'snaste': ['assent', 'snaste'], + 'snatch': ['chanst', 'snatch', 'stanch'], + 'snatchable': ['snatchable', 'stanchable'], + 'snatcher': ['resnatch', 'snatcher', 'stancher'], + 'snath': ['shant', 'snath'], + 'snathe': ['athens', 'hasten', 'snathe', 'sneath'], + 'snaw': ['sawn', 'snaw', 'swan'], + 'snead': ['sedan', 'snead'], + 'sneak': ['skean', 'snake', 'sneak'], + 'sneaker': ['keresan', 'sneaker'], + 'sneaksman': ['masskanne', 'sneaksman'], + 'sneap': ['aspen', 'panse', 'snape', 'sneap', 'spane', 'spean'], + 'sneath': ['athens', 'hasten', 'snathe', 'sneath'], + 'sneathe': ['sneathe', 'thesean'], + 'sned': ['send', 'sned'], + 'snee': ['ense', 'esne', 'nese', 'seen', 'snee'], + 'sneer': ['renes', 'sneer'], + 'snew': ['news', 'sewn', 'snew'], + 'snib': ['nibs', 'snib'], + 'snickle': ['slicken', 'snickle'], + 'sniddle': ['slidden', 'sniddle'], + 'snide': ['denis', 'snide'], + 'snig': ['sign', 'sing', 'snig'], + 'snigger': ['serging', 'snigger'], + 'snip': ['snip', 'spin'], + 'snipe': ['penis', 'snipe', 'spine'], + 'snipebill': ['snipebill', 'spinebill'], + 'snipelike': ['snipelike', 'spinelike'], + 'sniper': ['pernis', 'respin', 'sniper'], + 'snipocracy': ['conspiracy', 'snipocracy'], + 'snipper': ['nippers', 'snipper'], + 'snippet': ['snippet', 'stippen'], + 'snipy': ['snipy', 'spiny'], + 'snitcher': ['christen', 'snitcher'], + 'snite': ['inset', 'neist', 'snite', 'stein', 'stine', 'tsine'], + 'snithy': ['shinty', 'snithy'], + 'sniveler': ['ensilver', 'sniveler'], + 'snively': ['snively', 'sylvine'], + 'snob': ['bosn', 'nobs', 'snob'], + 'snocker': ['conkers', 'snocker'], + 'snod': ['snod', 'sond'], + 'snoek': ['snoek', 'snoke', 'soken'], + 'snog': ['snog', 'song'], + 'snoke': ['snoek', 'snoke', 'soken'], + 'snook': ['onkos', 'snook'], + 'snoop': ['snoop', 'spoon'], + 'snooper': ['snooper', 'spooner'], + 'snoopy': ['snoopy', 'spoony'], + 'snoot': ['snoot', 'stoon'], + 'snore': ['norse', 'noser', 'seron', 'snore'], + 'snorer': ['snorer', 'sorner'], + 'snoring': ['snoring', 'sorning'], + 'snork': ['norsk', 'snork'], + 'snotter': ['snotter', 'stentor', 'torsten'], + 'snout': ['notus', 'snout', 'stoun', 'tonus'], + 'snouter': ['snouter', 'tonsure', 'unstore'], + 'snow': ['snow', 'sown'], + 'snowie': ['nowise', 'snowie'], + 'snowish': ['snowish', 'whisson'], + 'snowy': ['snowy', 'wyson'], + 'snug': ['snug', 'sung'], + 'snup': ['snup', 'spun'], + 'snurp': ['snurp', 'spurn'], + 'snurt': ['snurt', 'turns'], + 'so': ['os', 'so'], + 'soak': ['asok', 'soak', 'soka'], + 'soaker': ['arkose', 'resoak', 'soaker'], + 'soally': ['soally', 'sollya'], + 'soam': ['amos', 'soam', 'soma'], + 'soap': ['asop', 'sapo', 'soap'], + 'soaper': ['resoap', 'soaper'], + 'soar': ['asor', 'rosa', 'soar', 'sora'], + 'sob': ['bos', 'sob'], + 'sobeit': ['setibo', 'sobeit'], + 'sober': ['boser', 'brose', 'sober'], + 'sobralite': ['sobralite', 'strobilae'], + 'soc': ['cos', 'osc', 'soc'], + 'socager': ['corsage', 'socager'], + 'social': ['colias', 'scolia', 'social'], + 'socialite': ['aeolistic', 'socialite'], + 'societal': ['cosalite', 'societal'], + 'societism': ['seismotic', 'societism'], + 'socinian': ['oscinian', 'socinian'], + 'sociobiological': ['biosociological', 'sociobiological'], + 'sociolegal': ['oligoclase', 'sociolegal'], + 'socius': ['scious', 'socius'], + 'socle': ['close', 'socle'], + 'soco': ['coos', 'soco'], + 'socotran': ['ostracon', 'socotran'], + 'socotrine': ['certosino', 'cortisone', 'socotrine'], + 'socratean': ['ostracean', 'socratean'], + 'socratic': ['acrostic', 'sarcotic', 'socratic'], + 'socratical': ['acrostical', 'socratical'], + 'socratically': ['acrostically', 'socratically'], + 'socraticism': ['acrosticism', 'socraticism'], + 'socratism': ['ostracism', 'socratism'], + 'socratize': ['ostracize', 'socratize'], + 'sod': ['dos', 'ods', 'sod'], + 'soda': ['dosa', 'sado', 'soda'], + 'sodalite': ['diastole', 'isolated', 'sodalite', 'solidate'], + 'sodium': ['modius', 'sodium'], + 'sodom': ['dooms', 'sodom'], + 'sodomitic': ['diosmotic', 'sodomitic'], + 'soe': ['oes', 'ose', 'soe'], + 'soft': ['soft', 'stof'], + 'soften': ['oftens', 'soften'], + 'softener': ['resoften', 'softener'], + 'sog': ['gos', 'sog'], + 'soga': ['sago', 'soga'], + 'soger': ['gorse', 'soger'], + 'soh': ['sho', 'soh'], + 'soho': ['shoo', 'soho'], + 'soil': ['lois', 'silo', 'siol', 'soil', 'soli'], + 'soiled': ['isolde', 'soiled'], + 'sojourner': ['resojourn', 'sojourner'], + 'sok': ['kos', 'sok'], + 'soka': ['asok', 'soak', 'soka'], + 'soke': ['skeo', 'soke'], + 'soken': ['snoek', 'snoke', 'soken'], + 'sola': ['also', 'sola'], + 'solacer': ['escolar', 'solacer'], + 'solan': ['salon', 'sloan', 'solan'], + 'solar': ['rosal', 'solar', 'soral'], + 'solaristics': ['scissortail', 'solaristics'], + 'solate': ['lotase', 'osteal', 'solate', 'stolae', 'talose'], + 'sold': ['slod', 'sold'], + 'solder': ['dorsel', 'seldor', 'solder'], + 'solderer': ['resolder', 'solderer'], + 'soldi': ['soldi', 'solid'], + 'soldo': ['soldo', 'solod'], + 'sole': ['lose', 'sloe', 'sole'], + 'solea': ['alose', 'osela', 'solea'], + 'solecist': ['solecist', 'solstice'], + 'solen': ['slone', 'solen'], + 'soleness': ['noseless', 'soleness'], + 'solenial': ['lesional', 'solenial'], + 'solenite': ['noselite', 'solenite'], + 'solenium': ['emulsion', 'solenium'], + 'solenopsis': ['poisonless', 'solenopsis'], + 'solent': ['solent', 'stolen', 'telson'], + 'solentine': ['nelsonite', 'solentine'], + 'soler': ['loser', 'orsel', 'rosel', 'soler'], + 'solera': ['roseal', 'solera'], + 'soles': ['loess', 'soles'], + 'soli': ['lois', 'silo', 'siol', 'soil', 'soli'], + 'soliative': ['isolative', 'soliative'], + 'solicit': ['colitis', 'solicit'], + 'solicitation': ['coalitionist', 'solicitation'], + 'soliciter': ['resolicit', 'soliciter'], + 'soliciting': ['ignicolist', 'soliciting'], + 'solicitude': ['isodulcite', 'solicitude'], + 'solid': ['soldi', 'solid'], + 'solidate': ['diastole', 'isolated', 'sodalite', 'solidate'], + 'solidus': ['dissoul', 'dulosis', 'solidus'], + 'solilunar': ['lunisolar', 'solilunar'], + 'soliped': ['despoil', 'soliped', 'spoiled'], + 'solitarian': ['sinoatrial', 'solitarian'], + 'solitary': ['royalist', 'solitary'], + 'soliterraneous': ['salinoterreous', 'soliterraneous'], + 'solitude': ['outslide', 'solitude'], + 'sollya': ['soally', 'sollya'], + 'solmizate': ['solmizate', 'zealotism'], + 'solo': ['sloo', 'solo', 'sool'], + 'solod': ['soldo', 'solod'], + 'solon': ['olson', 'solon'], + 'solonic': ['scolion', 'solonic'], + 'soloth': ['soloth', 'tholos'], + 'solotink': ['solotink', 'solotnik'], + 'solotnik': ['solotink', 'solotnik'], + 'solstice': ['solecist', 'solstice'], + 'solum': ['mosul', 'mouls', 'solum'], + 'solute': ['lutose', 'solute', 'tousle'], + 'solutioner': ['resolution', 'solutioner'], + 'soma': ['amos', 'soam', 'soma'], + 'somacule': ['maculose', 'somacule'], + 'somal': ['salmo', 'somal'], + 'somali': ['limosa', 'somali'], + 'somasthenia': ['anhematosis', 'somasthenia'], + 'somatic': ['atomics', 'catoism', 'cosmati', 'osmatic', 'somatic'], + 'somatics': ['acosmist', 'massicot', 'somatics'], + 'somatism': ['osmatism', 'somatism'], + 'somatophyte': ['hepatostomy', 'somatophyte'], + 'somatophytic': ['hypostomatic', 'somatophytic'], + 'somatopleuric': ['micropetalous', 'somatopleuric'], + 'somatopsychic': ['psychosomatic', 'somatopsychic'], + 'somatosplanchnic': ['somatosplanchnic', 'splanchnosomatic'], + 'somatous': ['astomous', 'somatous'], + 'somber': ['somber', 'sombre'], + 'sombre': ['somber', 'sombre'], + 'some': ['meso', 'mose', 'some'], + 'someday': ['samoyed', 'someday'], + 'somers': ['messor', 'mosser', 'somers'], + 'somnambule': ['somnambule', 'summonable'], + 'somnial': ['malison', 'manolis', 'osmanli', 'somnial'], + 'somnopathy': ['phytomonas', 'somnopathy'], + 'somnorific': ['onisciform', 'somnorific'], + 'son': ['ons', 'son'], + 'sonant': ['santon', 'sonant', 'stanno'], + 'sonantic': ['canonist', 'sanction', 'sonantic'], + 'sonar': ['arson', 'saron', 'sonar'], + 'sonatina': ['ansation', 'sonatina'], + 'sond': ['snod', 'sond'], + 'sondation': ['anisodont', 'sondation'], + 'sondeli': ['indoles', 'sondeli'], + 'soneri': ['rosine', 'senior', 'soneri'], + 'song': ['snog', 'song'], + 'songoi': ['isogon', 'songoi'], + 'songy': ['gonys', 'songy'], + 'sonic': ['oscin', 'scion', 'sonic'], + 'sonja': ['janos', 'jason', 'jonas', 'sonja'], + 'sonneratia': ['arsenation', 'senatorian', 'sonneratia'], + 'sonnet': ['sonnet', 'stonen', 'tenson'], + 'sonnetwise': ['sonnetwise', 'swinestone'], + 'sonrai': ['arsino', 'rasion', 'sonrai'], + 'sontag': ['sontag', 'tongas'], + 'soodle': ['dolose', 'oodles', 'soodle'], + 'sook': ['koso', 'skoo', 'sook'], + 'sool': ['sloo', 'solo', 'sool'], + 'soon': ['oons', 'soon'], + 'sooner': ['nooser', 'seroon', 'sooner'], + 'sooter': ['seroot', 'sooter', 'torose'], + 'sooth': ['shoot', 'sooth', 'sotho', 'toosh'], + 'soother': ['orthose', 'reshoot', 'shooter', 'soother'], + 'soothing': ['shooting', 'soothing'], + 'sootiness': ['enostosis', 'sootiness'], + 'sooty': ['sooty', 'soyot'], + 'sope': ['epos', 'peso', 'pose', 'sope'], + 'soph': ['phos', 'posh', 'shop', 'soph'], + 'sophister': ['posterish', 'prothesis', 'sophister', 'storeship', 'tephrosis'], + 'sophistical': ['postischial', 'sophistical'], + 'sophomore': ['osmophore', 'sophomore'], + 'sopition': ['position', 'sopition'], + 'sopor': ['poros', 'proso', 'sopor', 'spoor'], + 'soprani': ['parison', 'soprani'], + 'sopranist': ['postnaris', 'sopranist'], + 'soprano': ['pronaos', 'soprano'], + 'sora': ['asor', 'rosa', 'soar', 'sora'], + 'sorabian': ['abrasion', 'sorabian'], + 'soral': ['rosal', 'solar', 'soral'], + 'sorbate': ['barotse', 'boaster', 'reboast', 'sorbate'], + 'sorbin': ['insorb', 'sorbin'], + 'sorcer': ['scorer', 'sorcer'], + 'sorchin': ['cornish', 'cronish', 'sorchin'], + 'sorda': ['sarod', 'sorda'], + 'sordes': ['dosser', 'sordes'], + 'sordine': ['indorse', 'ordines', 'siredon', 'sordine'], + 'sordino': ['indoors', 'sordino'], + 'sore': ['eros', 'rose', 'sero', 'sore'], + 'soredia': ['ardoise', 'aroides', 'soredia'], + 'sorediate': ['oestridae', 'ostreidae', 'sorediate'], + 'soredium': ['dimerous', 'soredium'], + 'soree': ['erose', 'soree'], + 'sorefoot': ['footsore', 'sorefoot'], + 'sorehead': ['rosehead', 'sorehead'], + 'sorehon': ['onshore', 'sorehon'], + 'sorema': ['amores', 'ramose', 'sorema'], + 'soricid': ['cirsoid', 'soricid'], + 'soricident': ['discretion', 'soricident'], + 'soricine': ['recision', 'soricine'], + 'sorite': ['restio', 'sorite', 'sortie', 'triose'], + 'sorites': ['rossite', 'sorites'], + 'sornare': ['serrano', 'sornare'], + 'sorner': ['snorer', 'sorner'], + 'sorning': ['snoring', 'sorning'], + 'sororial': ['rosorial', 'sororial'], + 'sorption': ['notropis', 'positron', 'sorption'], + 'sortable': ['sortable', 'storable'], + 'sorted': ['sorted', 'strode'], + 'sorter': ['resort', 'roster', 'sorter', 'storer'], + 'sortie': ['restio', 'sorite', 'sortie', 'triose'], + 'sortilegus': ['sortilegus', 'strigulose'], + 'sortiment': ['sortiment', 'trimstone'], + 'sortly': ['sortly', 'styrol'], + 'sorty': ['sorty', 'story', 'stroy'], + 'sorva': ['savor', 'sorva'], + 'sory': ['rosy', 'sory'], + 'sosia': ['oasis', 'sosia'], + 'sostenuto': ['ostentous', 'sostenuto'], + 'soter': ['roset', 'rotse', 'soter', 'stero', 'store', 'torse'], + 'soterial': ['soterial', 'striolae'], + 'sotho': ['shoot', 'sooth', 'sotho', 'toosh'], + 'sotie': ['sotie', 'toise'], + 'sotnia': ['sotnia', 'tinosa'], + 'sotol': ['sotol', 'stool'], + 'sots': ['sots', 'toss'], + 'sotter': ['sotter', 'testor'], + 'soucar': ['acorus', 'soucar'], + 'souchet': ['souchet', 'techous', 'tousche'], + 'souly': ['lousy', 'souly'], + 'soum': ['soum', 'sumo'], + 'soumansite': ['soumansite', 'stamineous'], + 'sound': ['nodus', 'ounds', 'sound'], + 'sounder': ['resound', 'sounder', 'unrosed'], + 'soup': ['opus', 'soup'], + 'souper': ['poseur', 'pouser', 'souper', 'uprose'], + 'sour': ['ours', 'sour'], + 'source': ['cerous', 'course', 'crouse', 'source'], + 'soured': ['douser', 'soured'], + 'souredness': ['rousedness', 'souredness'], + 'souren': ['souren', 'unsore', 'ursone'], + 'sourer': ['rouser', 'sourer'], + 'souring': ['nigrous', 'rousing', 'souring'], + 'sourly': ['lusory', 'sourly'], + 'soursop': ['psorous', 'soursop', 'sporous'], + 'soury': ['soury', 'yours'], + 'souser': ['serous', 'souser'], + 'souter': ['ouster', 'souter', 'touser', 'trouse'], + 'souterrain': ['souterrain', 'ternarious', 'trouserian'], + 'south': ['shout', 'south'], + 'souther': ['shouter', 'souther'], + 'southerland': ['southerland', 'southlander'], + 'southing': ['shouting', 'southing'], + 'southlander': ['southerland', 'southlander'], + 'soviet': ['soviet', 'sovite'], + 'sovite': ['soviet', 'sovite'], + 'sowdones': ['sowdones', 'woodness'], + 'sowel': ['sowel', 'sowle'], + 'sower': ['owser', 'resow', 'serow', 'sower', 'swore', 'worse'], + 'sowl': ['slow', 'sowl'], + 'sowle': ['sowel', 'sowle'], + 'sown': ['snow', 'sown'], + 'sowt': ['sowt', 'stow', 'swot', 'wots'], + 'soyot': ['sooty', 'soyot'], + 'spa': ['asp', 'sap', 'spa'], + 'space': ['capes', 'scape', 'space'], + 'spaceless': ['scapeless', 'spaceless'], + 'spacer': ['casper', 'escarp', 'parsec', 'scrape', 'secpar', 'spacer'], + 'spade': ['depas', 'sepad', 'spade'], + 'spader': ['rasped', 'spader', 'spread'], + 'spadiceous': ['dipsaceous', 'spadiceous'], + 'spadone': ['espadon', 'spadone'], + 'spadonic': ['spadonic', 'spondaic', 'spondiac'], + 'spadrone': ['parsoned', 'spadrone'], + 'spae': ['apse', 'pesa', 'spae'], + 'spaer': ['asper', 'parse', 'prase', 'spaer', 'spare', 'spear'], + 'spahi': ['aphis', 'apish', 'hispa', 'saiph', 'spahi'], + 'spaid': ['sapid', 'spaid'], + 'spaik': ['askip', 'spaik'], + 'spairge': ['prisage', 'spairge'], + 'spalacine': ['asclepian', 'spalacine'], + 'spale': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'spalt': ['spalt', 'splat'], + 'span': ['snap', 'span'], + 'spancel': ['enclasp', 'spancel'], + 'spane': ['aspen', 'panse', 'snape', 'sneap', 'spane', 'spean'], + 'spanemia': ['paeanism', 'spanemia'], + 'spangler': ['spangler', 'sprangle'], + 'spangolite': ['postgenial', 'spangolite'], + 'spaniel': ['espinal', 'pinales', 'spaniel'], + 'spaniol': ['sanpoil', 'spaniol'], + 'spanless': ['snapless', 'spanless'], + 'spar': ['rasp', 'spar'], + 'sparable': ['parsable', 'prebasal', 'sparable'], + 'spare': ['asper', 'parse', 'prase', 'spaer', 'spare', 'spear'], + 'spareable': ['separable', 'spareable'], + 'sparely': ['parsley', 'pyrales', 'sparely', 'splayer'], + 'sparer': ['parser', 'rasper', 'sparer'], + 'sparerib': ['ribspare', 'sparerib'], + 'sparge': ['gasper', 'sparge'], + 'sparger': ['grasper', 'regrasp', 'sparger'], + 'sparidae': ['paradise', 'sparidae'], + 'sparing': ['aspring', 'rasping', 'sparing'], + 'sparingly': ['raspingly', 'sparingly'], + 'sparingness': ['raspingness', 'sparingness'], + 'sparling': ['laspring', 'sparling', 'springal'], + 'sparoid': ['prasoid', 'sparoid'], + 'sparse': ['passer', 'repass', 'sparse'], + 'spart': ['spart', 'sprat', 'strap', 'traps'], + 'spartanic': ['sacripant', 'spartanic'], + 'sparteine': ['pistareen', 'sparteine'], + 'sparterie': ['periaster', 'sparterie'], + 'spartina': ['aspirant', 'partisan', 'spartina'], + 'spartle': ['palster', 'persalt', 'plaster', 'psalter', 'spartle', 'stapler'], + 'spary': ['raspy', 'spary', 'spray'], + 'spat': ['past', 'spat', 'stap', 'taps'], + 'spate': ['paste', 'septa', 'spate'], + 'spathal': ['asphalt', 'spathal', 'taplash'], + 'spathe': ['spathe', 'thapes'], + 'spathic': ['haptics', 'spathic'], + 'spatling': ['spatling', 'stapling'], + 'spatter': ['spatter', 'tapster'], + 'spattering': ['spattering', 'tapestring'], + 'spattle': ['peltast', 'spattle'], + 'spatular': ['pastural', 'spatular'], + 'spatule': ['pulsate', 'spatule', 'upsteal'], + 'spave': ['spave', 'vespa'], + 'speak': ['sapek', 'speak'], + 'speaker': ['respeak', 'speaker'], + 'speal': ['elaps', + 'lapse', + 'lepas', + 'pales', + 'salep', + 'saple', + 'sepal', + 'slape', + 'spale', + 'speal'], + 'spean': ['aspen', 'panse', 'snape', 'sneap', 'spane', 'spean'], + 'spear': ['asper', 'parse', 'prase', 'spaer', 'spare', 'spear'], + 'spearman': ['parmesan', 'spearman'], + 'spearmint': ['spearmint', 'spermatin'], + 'speary': ['presay', 'speary'], + 'spec': ['ceps', 'spec'], + 'spectatorial': ['poetastrical', 'spectatorial'], + 'specter': ['respect', 'scepter', 'specter'], + 'spectered': ['sceptered', 'spectered'], + 'spectra': ['precast', 'spectra'], + 'spectral': ['sceptral', 'scraplet', 'spectral'], + 'spectromicroscope': ['microspectroscope', 'spectromicroscope'], + 'spectrotelescope': ['spectrotelescope', 'telespectroscope'], + 'spectrous': ['spectrous', 'susceptor', 'suspector'], + 'spectry': ['precyst', 'sceptry', 'spectry'], + 'specula': ['capsule', 'specula', 'upscale'], + 'specular': ['capsuler', 'specular'], + 'speed': ['pedes', 'speed'], + 'speel': ['sleep', 'speel'], + 'speelless': ['sleepless', 'speelless'], + 'speer': ['peres', 'perse', 'speer', 'spree'], + 'speerity': ['perseity', 'speerity'], + 'speiss': ['sepsis', 'speiss'], + 'spelaean': ['seaplane', 'spelaean'], + 'spelk': ['skelp', 'spelk'], + 'speller': ['presell', 'respell', 'speller'], + 'spelt': ['slept', 'spelt', 'splet'], + 'spenerism': ['primeness', 'spenerism'], + 'speos': ['posse', 'speos'], + 'sperate': ['perates', 'repaste', 'sperate'], + 'sperity': ['pyrites', 'sperity'], + 'sperling': ['sperling', 'springle'], + 'spermalist': ['psalmister', 'spermalist'], + 'spermathecal': ['chapelmaster', 'spermathecal'], + 'spermatid': ['predatism', 'spermatid'], + 'spermatin': ['spearmint', 'spermatin'], + 'spermatogonium': ['protomagnesium', 'spermatogonium'], + 'spermatozoic': ['spermatozoic', 'zoospermatic'], + 'spermiogenesis': ['geissospermine', 'spermiogenesis'], + 'spermocarp': ['carposperm', 'spermocarp'], + 'spet': ['pest', 'sept', 'spet', 'step'], + 'spew': ['spew', 'swep'], + 'sphacelation': ['lipsanotheca', 'sphacelation'], + 'sphecidae': ['cheapside', 'sphecidae'], + 'sphene': ['sephen', 'sphene'], + 'sphenoethmoid': ['ethmosphenoid', 'sphenoethmoid'], + 'sphenoethmoidal': ['ethmosphenoidal', 'sphenoethmoidal'], + 'sphenotic': ['phonetics', 'sphenotic'], + 'spheral': ['plasher', 'spheral'], + 'spheration': ['opisthenar', 'spheration'], + 'sphere': ['herpes', 'hesper', 'sphere'], + 'sphery': ['sphery', 'sypher'], + 'sphyraenid': ['dysphrenia', 'sphyraenid', 'sphyrnidae'], + 'sphyrnidae': ['dysphrenia', 'sphyraenid', 'sphyrnidae'], + 'spica': ['aspic', 'spica'], + 'spicate': ['aseptic', 'spicate'], + 'spice': ['sepic', 'spice'], + 'spicer': ['crepis', 'cripes', 'persic', 'precis', 'spicer'], + 'spiciferous': ['pisciferous', 'spiciferous'], + 'spiciform': ['pisciform', 'spiciform'], + 'spicket': ['skeptic', 'spicket'], + 'spicular': ['scripula', 'spicular'], + 'spiculate': ['euplastic', 'spiculate'], + 'spiculated': ['disculpate', 'spiculated'], + 'spicule': ['clipeus', 'spicule'], + 'spider': ['spider', 'spired', 'spried'], + 'spiderish': ['sidership', 'spiderish'], + 'spiderlike': ['predislike', 'spiderlike'], + 'spiel': ['piles', 'plies', 'slipe', 'spiel', 'spile'], + 'spier': ['siper', 'spier', 'spire'], + 'spikelet': ['spikelet', 'steplike'], + 'spiking': ['pigskin', 'spiking'], + 'spiky': ['pisky', 'spiky'], + 'spile': ['piles', 'plies', 'slipe', 'spiel', 'spile'], + 'spiler': ['lisper', 'pliers', 'sirple', 'spiler'], + 'spiling': ['sipling', 'spiling'], + 'spiloma': ['imposal', 'spiloma'], + 'spilt': ['spilt', 'split'], + 'spin': ['snip', 'spin'], + 'spina': ['pisan', 'sapin', 'spina'], + 'spinae': ['sepian', 'spinae'], + 'spinales': ['painless', 'spinales'], + 'spinate': ['panties', 'sapient', 'spinate'], + 'spindled': ['spindled', 'splendid'], + 'spindler': ['spindler', 'splinder'], + 'spine': ['penis', 'snipe', 'spine'], + 'spinebill': ['snipebill', 'spinebill'], + 'spinel': ['spinel', 'spline'], + 'spinelike': ['snipelike', 'spinelike'], + 'spinet': ['instep', 'spinet'], + 'spinigerous': ['serpiginous', 'spinigerous'], + 'spinigrade': ['despairing', 'spinigrade'], + 'spinoid': ['spinoid', 'spionid'], + 'spinoneural': ['spinoneural', 'unipersonal'], + 'spinotectal': ['entoplastic', 'spinotectal', 'tectospinal', 'tenoplastic'], + 'spiny': ['snipy', 'spiny'], + 'spionid': ['spinoid', 'spionid'], + 'spiracle': ['calipers', 'spiracle'], + 'spiracula': ['auriscalp', 'spiracula'], + 'spiral': ['prisal', 'spiral'], + 'spiralism': ['misprisal', 'spiralism'], + 'spiraloid': ['spiraloid', 'sporidial'], + 'spiran': ['spiran', 'sprain'], + 'spirant': ['spirant', 'spraint'], + 'spirate': ['piaster', 'piastre', 'raspite', 'spirate', 'traipse'], + 'spire': ['siper', 'spier', 'spire'], + 'spirea': ['aspire', 'paries', 'praise', 'sirpea', 'spirea'], + 'spired': ['spider', 'spired', 'spried'], + 'spirelet': ['epistler', 'spirelet'], + 'spireme': ['emprise', 'imprese', 'premise', 'spireme'], + 'spiritally': ['pistillary', 'spiritally'], + 'spiriter': ['respirit', 'spiriter'], + 'spirometer': ['prisometer', 'spirometer'], + 'spironema': ['mesropian', 'promnesia', 'spironema'], + 'spirt': ['spirt', 'sprit', 'stirp', 'strip'], + 'spirula': ['parulis', 'spirula', 'uprisal'], + 'spit': ['pist', 'spit'], + 'spital': ['alpist', 'pastil', 'spital'], + 'spite': ['septi', 'spite', 'stipe'], + 'spithame': ['aphetism', 'mateship', 'shipmate', 'spithame'], + 'spitter': ['spitter', 'tipster'], + 'splairge': ['aspergil', 'splairge'], + 'splanchnosomatic': ['somatosplanchnic', 'splanchnosomatic'], + 'splasher': ['harpless', 'splasher'], + 'splat': ['spalt', 'splat'], + 'splay': ['palsy', 'splay'], + 'splayed': ['pylades', 'splayed'], + 'splayer': ['parsley', 'pyrales', 'sparely', 'splayer'], + 'spleet': ['pestle', 'spleet'], + 'splender': ['resplend', 'splender'], + 'splendid': ['spindled', 'splendid'], + 'splenium': ['splenium', 'unsimple'], + 'splenolaparotomy': ['laparosplenotomy', 'splenolaparotomy'], + 'splenoma': ['neoplasm', 'pleonasm', 'polesman', 'splenoma'], + 'splenomegalia': ['megalosplenia', 'splenomegalia'], + 'splenonephric': ['phrenosplenic', 'splenonephric', 'splenophrenic'], + 'splenophrenic': ['phrenosplenic', 'splenonephric', 'splenophrenic'], + 'splet': ['slept', 'spelt', 'splet'], + 'splice': ['clipse', 'splice'], + 'spliceable': ['eclipsable', 'spliceable'], + 'splinder': ['spindler', 'splinder'], + 'spline': ['spinel', 'spline'], + 'split': ['spilt', 'split'], + 'splitter': ['splitter', 'striplet'], + 'splore': ['sloper', 'splore'], + 'spogel': ['gospel', 'spogel'], + 'spoil': ['polis', 'spoil'], + 'spoilage': ['pelasgoi', 'spoilage'], + 'spoilation': ['positional', 'spoilation', 'spoliation'], + 'spoiled': ['despoil', 'soliped', 'spoiled'], + 'spoiler': ['leporis', 'spoiler'], + 'spoilment': ['simpleton', 'spoilment'], + 'spoilt': ['pistol', 'postil', 'spoilt'], + 'spole': ['elops', 'slope', 'spole'], + 'spoliation': ['positional', 'spoilation', 'spoliation'], + 'spondaic': ['spadonic', 'spondaic', 'spondiac'], + 'spondiac': ['spadonic', 'spondaic', 'spondiac'], + 'spongily': ['posingly', 'spongily'], + 'sponsal': ['plasson', 'sponsal'], + 'sponsalia': ['passional', 'sponsalia'], + 'spool': ['polos', 'sloop', 'spool'], + 'spoon': ['snoop', 'spoon'], + 'spooner': ['snooper', 'spooner'], + 'spoony': ['snoopy', 'spoony'], + 'spoonyism': ['spoonyism', 'symposion'], + 'spoor': ['poros', 'proso', 'sopor', 'spoor'], + 'spoot': ['spoot', 'stoop'], + 'sporangia': ['agapornis', 'sporangia'], + 'spore': ['poser', 'prose', 'ropes', 'spore'], + 'sporidial': ['spiraloid', 'sporidial'], + 'sporification': ['antisoporific', 'prosification', 'sporification'], + 'sporogeny': ['gynospore', 'sporogeny'], + 'sporoid': ['psoroid', 'sporoid'], + 'sporotrichum': ['sporotrichum', 'trichosporum'], + 'sporous': ['psorous', 'soursop', 'sporous'], + 'sporozoic': ['sporozoic', 'zoosporic'], + 'sport': ['sport', 'strop'], + 'sporter': ['sporter', 'strepor'], + 'sportula': ['postural', 'pulsator', 'sportula'], + 'sportulae': ['opulaster', 'sportulae', 'sporulate'], + 'sporulate': ['opulaster', 'sportulae', 'sporulate'], + 'sporule': ['leprous', 'pelorus', 'sporule'], + 'sposhy': ['hyssop', 'phossy', 'sposhy'], + 'spot': ['post', 'spot', 'stop', 'tops'], + 'spotless': ['postless', 'spotless', 'stopless'], + 'spotlessness': ['spotlessness', 'stoplessness'], + 'spotlike': ['postlike', 'spotlike'], + 'spottedly': ['spottedly', 'spotteldy'], + 'spotteldy': ['spottedly', 'spotteldy'], + 'spotter': ['protest', 'spotter'], + 'spouse': ['esopus', 'spouse'], + 'spout': ['spout', 'stoup'], + 'spouter': ['petrous', 'posture', 'proetus', 'proteus', 'septuor', 'spouter'], + 'sprag': ['grasp', 'sprag'], + 'sprain': ['spiran', 'sprain'], + 'spraint': ['spirant', 'spraint'], + 'sprangle': ['spangler', 'sprangle'], + 'sprat': ['spart', 'sprat', 'strap', 'traps'], + 'spray': ['raspy', 'spary', 'spray'], + 'sprayer': ['respray', 'sprayer'], + 'spread': ['rasped', 'spader', 'spread'], + 'spreadboard': ['broadspread', 'spreadboard'], + 'spreader': ['respread', 'spreader'], + 'spreadover': ['overspread', 'spreadover'], + 'spree': ['peres', 'perse', 'speer', 'spree'], + 'spret': ['prest', 'spret'], + 'spried': ['spider', 'spired', 'spried'], + 'sprier': ['risper', 'sprier'], + 'spriest': ['persist', 'spriest'], + 'springal': ['laspring', 'sparling', 'springal'], + 'springe': ['presign', 'springe'], + 'springer': ['respring', 'springer'], + 'springhead': ['headspring', 'springhead'], + 'springhouse': ['springhouse', 'surgeonship'], + 'springle': ['sperling', 'springle'], + 'sprit': ['spirt', 'sprit', 'stirp', 'strip'], + 'sprite': ['priest', 'pteris', 'sprite', 'stripe'], + 'spritehood': ['priesthood', 'spritehood'], + 'sproat': ['asport', 'pastor', 'sproat'], + 'sprocket': ['prestock', 'sprocket'], + 'sprout': ['sprout', 'stroup', 'stupor'], + 'sprouter': ['posturer', 'resprout', 'sprouter'], + 'sprouting': ['outspring', 'sprouting'], + 'sprue': ['purse', 'resup', 'sprue', 'super'], + 'spruer': ['purser', 'spruer'], + 'spruit': ['purist', 'spruit', 'uprist', 'upstir'], + 'spun': ['snup', 'spun'], + 'spunkie': ['spunkie', 'unspike'], + 'spuriae': ['spuriae', 'uparise', 'upraise'], + 'spurl': ['slurp', 'spurl'], + 'spurlet': ['purslet', 'spurlet', 'spurtle'], + 'spurn': ['snurp', 'spurn'], + 'spurt': ['spurt', 'turps'], + 'spurtive': ['spurtive', 'upstrive'], + 'spurtle': ['purslet', 'spurlet', 'spurtle'], + 'sputa': ['sputa', 'staup', 'stupa'], + 'sputumary': ['sputumary', 'sumptuary'], + 'sputumous': ['sputumous', 'sumptuous'], + 'spyer': ['pryse', 'spyer'], + 'spyros': ['prossy', 'spyros'], + 'squail': ['squail', 'squali'], + 'squali': ['squail', 'squali'], + 'squamatine': ['antimasque', 'squamatine'], + 'squame': ['masque', 'squame', 'squeam'], + 'squameous': ['squameous', 'squeamous'], + 'squamopetrosal': ['petrosquamosal', 'squamopetrosal'], + 'squamosoparietal': ['parietosquamosal', 'squamosoparietal'], + 'squareman': ['marquesan', 'squareman'], + 'squeaker': ['resqueak', 'squeaker'], + 'squeal': ['lasque', 'squeal'], + 'squeam': ['masque', 'squame', 'squeam'], + 'squeamous': ['squameous', 'squeamous'], + 'squillian': ['nisqualli', 'squillian'], + 'squire': ['risque', 'squire'], + 'squiret': ['querist', 'squiret'], + 'squit': ['quits', 'squit'], + 'sramana': ['ramanas', 'sramana'], + 'sri': ['sir', 'sri'], + 'srivatsan': ['ravissant', 'srivatsan'], + 'ssi': ['sis', 'ssi'], + 'ssu': ['ssu', 'sus'], + 'staab': ['basta', 'staab'], + 'stab': ['bast', 'bats', 'stab'], + 'stabile': ['astilbe', 'bestial', 'blastie', 'stabile'], + 'stable': ['ablest', 'stable', 'tables'], + 'stableful': ['bullfeast', 'stableful'], + 'stabler': ['blaster', 'reblast', 'stabler'], + 'stabling': ['blasting', 'stabling'], + 'stably': ['blasty', 'stably'], + 'staccato': ['staccato', 'stoccata'], + 'stacey': ['cytase', 'stacey'], + 'stacher': ['stacher', 'thraces'], + 'stacker': ['restack', 'stacker'], + 'stackman': ['stackman', 'tacksman'], + 'stacy': ['stacy', 'styca'], + 'stade': ['sedat', 'stade', 'stead'], + 'stadic': ['dicast', 'stadic'], + 'stadium': ['dumaist', 'stadium'], + 'staffer': ['restaff', 'staffer'], + 'stag': ['gast', 'stag'], + 'stager': ['gaster', 'stager'], + 'stagily': ['stagily', 'stygial'], + 'stagnation': ['antagonist', 'stagnation'], + 'stagnum': ['mustang', 'stagnum'], + 'stain': ['saint', 'satin', 'stain'], + 'stainable': ['balanites', 'basaltine', 'stainable'], + 'stainer': ['asterin', 'eranist', 'restain', 'stainer', 'starnie', 'stearin'], + 'stainful': ['inflatus', 'stainful'], + 'stainless': ['saintless', 'saltiness', 'slatiness', 'stainless'], + 'staio': ['sitao', 'staio'], + 'stair': ['arist', + 'astir', + 'sitar', + 'stair', + 'stria', + 'tarsi', + 'tisar', + 'trias'], + 'staircase': ['caesarist', 'staircase'], + 'staired': ['astride', 'diaster', 'disrate', 'restiad', 'staired'], + 'staithman': ['staithman', 'thanatism'], + 'staiver': ['staiver', 'taivers'], + 'stake': ['skate', 'stake', 'steak'], + 'staker': ['skater', 'staker', 'strake', 'streak', 'tasker'], + 'stalagmitic': ['stalagmitic', 'stigmatical'], + 'stale': ['least', 'setal', 'slate', 'stale', 'steal', 'stela', 'tales'], + 'staling': ['anglist', 'lasting', 'salting', 'slating', 'staling'], + 'stalker': ['sklater', 'stalker'], + 'staller': ['staller', 'stellar'], + 'stam': ['mast', 'mats', 'stam'], + 'stamen': ['mantes', 'stamen'], + 'stamin': ['manist', 'mantis', 'matins', 'stamin'], + 'stamina': ['amanist', 'stamina'], + 'staminal': ['staminal', 'tailsman', 'talisman'], + 'staminate': ['emanatist', 'staminate', 'tasmanite'], + 'stamineous': ['soumansite', 'stamineous'], + 'staminode': ['ademonist', 'demoniast', 'staminode'], + 'stammer': ['stammer', 'stremma'], + 'stampede': ['stampede', 'stepdame'], + 'stamper': ['restamp', 'stamper'], + 'stampian': ['mainpast', 'mantispa', 'panamist', 'stampian'], + 'stan': ['nast', 'sant', 'stan'], + 'stance': ['ascent', 'secant', 'stance'], + 'stanch': ['chanst', 'snatch', 'stanch'], + 'stanchable': ['snatchable', 'stanchable'], + 'stancher': ['resnatch', 'snatcher', 'stancher'], + 'stand': ['dasnt', 'stand'], + 'standage': ['dagestan', 'standage'], + 'standee': ['edestan', 'standee'], + 'stander': ['stander', 'sternad'], + 'standout': ['outstand', 'standout'], + 'standstill': ['standstill', 'stillstand'], + 'stane': ['antes', 'nates', 'stane', 'stean'], + 'stang': ['angst', 'stang', 'tangs'], + 'stangeria': ['agrestian', 'gerastian', 'stangeria'], + 'stanine': ['ensaint', 'stanine'], + 'stanly': ['nylast', 'stanly'], + 'stanno': ['santon', 'sonant', 'stanno'], + 'stap': ['past', 'spat', 'stap', 'taps'], + 'staple': ['pastel', 'septal', 'staple'], + 'stapler': ['palster', 'persalt', 'plaster', 'psalter', 'spartle', 'stapler'], + 'stapling': ['spatling', 'stapling'], + 'star': ['sart', 'star', 'stra', 'tars', 'tsar'], + 'starch': ['scarth', 'scrath', 'starch'], + 'stardom': ['stardom', 'tsardom'], + 'stare': ['aster', 'serta', 'stare', 'strae', 'tarse', 'teras'], + 'staree': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'starer': ['arrest', 'astrer', 'raster', 'starer'], + 'starful': ['flustra', 'starful'], + 'staring': ['gastrin', 'staring'], + 'staringly': ['staringly', 'strayling'], + 'stark': ['karst', 'skart', 'stark'], + 'starky': ['starky', 'straky'], + 'starlet': ['rattles', 'slatter', 'starlet', 'startle'], + 'starlit': ['starlit', 'trisalt'], + 'starlite': ['starlite', 'taistrel'], + 'starnel': ['saltern', 'starnel', 'sternal'], + 'starnie': ['asterin', 'eranist', 'restain', 'stainer', 'starnie', 'stearin'], + 'starnose': ['assentor', 'essorant', 'starnose'], + 'starship': ['starship', 'tsarship'], + 'starshot': ['shotstar', 'starshot'], + 'starter': ['restart', 'starter'], + 'startle': ['rattles', 'slatter', 'starlet', 'startle'], + 'starve': ['starve', 'staver', 'strave', 'tavers', 'versta'], + 'starwise': ['starwise', 'waitress'], + 'stary': ['satyr', 'stary', 'stray', 'trasy'], + 'stases': ['assets', 'stases'], + 'stasis': ['assist', 'stasis'], + 'statable': ['statable', 'tastable'], + 'state': ['state', 'taste', 'tates', 'testa'], + 'stated': ['stated', 'tasted'], + 'stateful': ['stateful', 'tasteful'], + 'statefully': ['statefully', 'tastefully'], + 'statefulness': ['statefulness', 'tastefulness'], + 'stateless': ['stateless', 'tasteless'], + 'statelich': ['athletics', 'statelich'], + 'stately': ['stately', 'stylate'], + 'statement': ['statement', 'testament'], + 'stater': ['stater', 'taster', 'testar'], + 'statesider': ['dissertate', 'statesider'], + 'static': ['static', 'sticta'], + 'statice': ['etacist', 'statice'], + 'stational': ['saltation', 'stational'], + 'stationarily': ['antiroyalist', 'stationarily'], + 'stationer': ['nitrosate', 'stationer'], + 'statoscope': ['septocosta', 'statoscope'], + 'statue': ['astute', 'statue'], + 'stature': ['stature', 'stauter'], + 'staumer': ['staumer', 'strumae'], + 'staun': ['staun', 'suant'], + 'staunch': ['canthus', 'staunch'], + 'staup': ['sputa', 'staup', 'stupa'], + 'staurion': ['staurion', 'sutorian'], + 'stauter': ['stature', 'stauter'], + 'stave': ['stave', 'vesta'], + 'staver': ['starve', 'staver', 'strave', 'tavers', 'versta'], + 'staw': ['sawt', 'staw', 'swat', 'taws', 'twas', 'wast'], + 'stawn': ['stawn', 'wasnt'], + 'stayable': ['stayable', 'teasably'], + 'stayed': ['stayed', 'steady'], + 'stayer': ['atresy', 'estray', 'reasty', 'stayer'], + 'staynil': ['nastily', 'saintly', 'staynil'], + 'stchi': ['sitch', 'stchi', 'stich'], + 'stead': ['sedat', 'stade', 'stead'], + 'steady': ['stayed', 'steady'], + 'steak': ['skate', 'stake', 'steak'], + 'steal': ['least', 'setal', 'slate', 'stale', 'steal', 'stela', 'tales'], + 'stealer': ['realest', 'reslate', 'resteal', 'stealer', 'teasler'], + 'stealing': ['galenist', 'genitals', 'stealing'], + 'stealy': ['alytes', 'astely', 'lysate', 'stealy'], + 'steam': ['steam', 'stema'], + 'steaming': ['misagent', 'steaming'], + 'stean': ['antes', 'nates', 'stane', 'stean'], + 'stearic': ['atresic', 'stearic'], + 'stearin': ['asterin', 'eranist', 'restain', 'stainer', 'starnie', 'stearin'], + 'stearone': ['orestean', 'resonate', 'stearone'], + 'stearyl': ['saltery', 'stearyl'], + 'steatin': ['atenist', 'instate', 'satient', 'steatin'], + 'steatoma': ['atmostea', 'steatoma'], + 'steatornis': ['steatornis', 'treasonist'], + 'stech': ['chest', 'stech'], + 'steek': ['keest', 'skeet', 'skete', 'steek'], + 'steel': ['sleet', 'slete', 'steel', 'stele'], + 'steeler': ['reestle', 'resteel', 'steeler'], + 'steeliness': ['sleetiness', 'steeliness'], + 'steeling': ['sleeting', 'steeling'], + 'steelproof': ['sleetproof', 'steelproof'], + 'steely': ['sleety', 'steely'], + 'steen': ['steen', 'teens', 'tense'], + 'steep': ['peste', 'steep'], + 'steeper': ['estrepe', 'resteep', 'steeper'], + 'steepy': ['steepy', 'typees'], + 'steer': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'steerer': ['reester', 'steerer'], + 'steering': ['energist', 'steering'], + 'steerling': ['esterling', 'steerling'], + 'steeve': ['steeve', 'vestee'], + 'stefan': ['fasten', 'nefast', 'stefan'], + 'steg': ['gest', 'steg'], + 'stegodon': ['dogstone', 'stegodon'], + 'steid': ['deist', 'steid'], + 'steigh': ['gesith', 'steigh'], + 'stein': ['inset', 'neist', 'snite', 'stein', 'stine', 'tsine'], + 'stela': ['least', 'setal', 'slate', 'stale', 'steal', 'stela', 'tales'], + 'stelae': ['ateles', 'saltee', 'sealet', 'stelae', 'teasel'], + 'stelai': ['isleta', 'litsea', 'salite', 'stelai'], + 'stelar': ['laster', + 'lastre', + 'rastle', + 'relast', + 'resalt', + 'salter', + 'slater', + 'stelar'], + 'stele': ['sleet', 'slete', 'steel', 'stele'], + 'stella': ['sallet', 'stella', 'talles'], + 'stellar': ['staller', 'stellar'], + 'stellaria': ['lateralis', 'stellaria'], + 'stema': ['steam', 'stema'], + 'stemlike': ['meletski', 'stemlike'], + 'sten': ['nest', 'sent', 'sten'], + 'stenar': ['astern', 'enstar', 'stenar', 'sterna'], + 'stencil': ['lentisc', 'scintle', 'stencil'], + 'stenciler': ['crestline', 'stenciler'], + 'stenion': ['stenion', 'tension'], + 'steno': ['onset', 'seton', 'steno', 'stone'], + 'stenopaic': ['aspection', 'stenopaic'], + 'stenosis': ['sisseton', 'stenosis'], + 'stenotic': ['stenotic', 'tonetics'], + 'stentor': ['snotter', 'stentor', 'torsten'], + 'step': ['pest', 'sept', 'spet', 'step'], + 'stepaunt': ['nettapus', 'stepaunt'], + 'stepbairn': ['breastpin', 'stepbairn'], + 'stepdame': ['stampede', 'stepdame'], + 'stephana': ['pheasant', 'stephana'], + 'stephanic': ['cathepsin', 'stephanic'], + 'steplike': ['spikelet', 'steplike'], + 'sterculia': ['sterculia', 'urticales'], + 'stere': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'stereograph': ['preshortage', 'stereograph'], + 'stereometric': ['crestmoreite', 'stereometric'], + 'stereophotograph': ['photostereograph', 'stereophotograph'], + 'stereotelescope': ['stereotelescope', 'telestereoscope'], + 'stereotomic': ['osteometric', 'stereotomic'], + 'stereotomical': ['osteometrical', 'stereotomical'], + 'stereotomy': ['osteometry', 'stereotomy'], + 'steric': ['certis', 'steric'], + 'sterigma': ['gemarist', 'magister', 'sterigma'], + 'sterigmata': ['magistrate', 'sterigmata'], + 'sterile': ['leister', 'sterile'], + 'sterilize': ['listerize', 'sterilize'], + 'sterin': ['estrin', 'insert', 'sinter', 'sterin', 'triens'], + 'sterlet': ['settler', 'sterlet', 'trestle'], + 'stern': ['ernst', 'stern'], + 'sterna': ['astern', 'enstar', 'stenar', 'sterna'], + 'sternad': ['stander', 'sternad'], + 'sternage': ['estrange', 'segreant', 'sergeant', 'sternage'], + 'sternal': ['saltern', 'starnel', 'sternal'], + 'sternalis': ['sternalis', 'trainless'], + 'sternite': ['insetter', 'interest', 'interset', 'sternite'], + 'sterno': ['nestor', 'sterno', 'stoner', 'strone', 'tensor'], + 'sternocostal': ['costosternal', 'sternocostal'], + 'sternovertebral': ['sternovertebral', 'vertebrosternal'], + 'stero': ['roset', 'rotse', 'soter', 'stero', 'store', 'torse'], + 'steroid': ['oestrid', 'steroid', 'storied'], + 'sterol': ['relost', 'reslot', 'rostel', 'sterol', 'torsel'], + 'stert': ['stert', 'stret', 'trest'], + 'sterve': ['revest', 'servet', 'sterve', 'verset', 'vester'], + 'stet': ['sett', 'stet', 'test'], + 'stevan': ['stevan', 'svante'], + 'stevel': ['stevel', 'svelte'], + 'stevia': ['itaves', 'stevia'], + 'stew': ['stew', 'west'], + 'stewart': ['stewart', 'swatter'], + 'stewed': ['stewed', 'wedset'], + 'stewy': ['stewy', 'westy'], + 'stey': ['stey', 'yest'], + 'sthenia': ['sethian', 'sthenia'], + 'stich': ['sitch', 'stchi', 'stich'], + 'stichid': ['distich', 'stichid'], + 'sticker': ['rickets', 'sticker'], + 'stickler': ['stickler', 'strickle'], + 'sticta': ['static', 'sticta'], + 'stife': ['feist', 'stife'], + 'stiffener': ['restiffen', 'stiffener'], + 'stifle': ['itself', 'stifle'], + 'stifler': ['slifter', 'stifler'], + 'stigmai': ['imagist', 'stigmai'], + 'stigmatical': ['stalagmitic', 'stigmatical'], + 'stilbene': ['nebelist', 'stilbene', 'tensible'], + 'stile': ['islet', 'istle', 'slite', 'stile'], + 'stileman': ['mentalis', 'smaltine', 'stileman'], + 'stillage': ['legalist', 'stillage'], + 'stiller': ['stiller', 'trellis'], + 'stillstand': ['standstill', 'stillstand'], + 'stilted': ['slitted', 'stilted'], + 'stilter': ['litster', 'slitter', 'stilter', 'testril'], + 'stilty': ['slitty', 'stilty'], + 'stim': ['mist', 'smit', 'stim'], + 'stime': ['metis', 'smite', 'stime', 'times'], + 'stimulancy': ['stimulancy', 'unmystical'], + 'stimy': ['misty', 'stimy'], + 'stine': ['inset', 'neist', 'snite', 'stein', 'stine', 'tsine'], + 'stinge': ['ingest', 'signet', 'stinge'], + 'stinger': ['resting', 'stinger'], + 'stinker': ['kirsten', 'kristen', 'stinker'], + 'stinkstone': ['knottiness', 'stinkstone'], + 'stinted': ['dentist', 'distent', 'stinted'], + 'stion': ['sinto', 'stion'], + 'stipa': ['piast', 'stipa', 'tapis'], + 'stipe': ['septi', 'spite', 'stipe'], + 'stipel': ['pistle', 'stipel'], + 'stippen': ['snippet', 'stippen'], + 'stipula': ['paulist', 'stipula'], + 'stir': ['rist', 'stir'], + 'stirk': ['skirt', 'stirk'], + 'stirp': ['spirt', 'sprit', 'stirp', 'strip'], + 'stitcher': ['restitch', 'stitcher'], + 'stiver': ['stiver', 'strive', 'verist'], + 'stoa': ['oast', 'stoa', 'taos'], + 'stoach': ['stoach', 'stocah'], + 'stoat': ['stoat', 'toast'], + 'stoater': ['retoast', 'rosetta', 'stoater', 'toaster'], + 'stocah': ['stoach', 'stocah'], + 'stoccata': ['staccato', 'stoccata'], + 'stocker': ['restock', 'stocker'], + 'stoep': ['estop', 'stoep', 'stope'], + 'stof': ['soft', 'stof'], + 'stog': ['stog', 'togs'], + 'stogie': ['egoist', 'stogie'], + 'stoic': ['ostic', 'sciot', 'stoic'], + 'stoically': ['callosity', 'stoically'], + 'stoker': ['stoker', 'stroke'], + 'stolae': ['lotase', 'osteal', 'solate', 'stolae', 'talose'], + 'stole': ['slote', 'stole'], + 'stoled': ['sloted', 'stoled'], + 'stolen': ['solent', 'stolen', 'telson'], + 'stoma': ['atmos', 'stoma', 'tomas'], + 'stomatode': ['mootstead', 'stomatode'], + 'stomatomy': ['mastotomy', 'stomatomy'], + 'stomatopoda': ['podostomata', 'stomatopoda'], + 'stomatopodous': ['podostomatous', 'stomatopodous'], + 'stomper': ['pomster', 'stomper'], + 'stone': ['onset', 'seton', 'steno', 'stone'], + 'stonebird': ['birdstone', 'stonebird'], + 'stonebreak': ['breakstone', 'stonebreak'], + 'stoned': ['doesnt', 'stoned'], + 'stonegall': ['gallstone', 'stonegall'], + 'stonehand': ['handstone', 'stonehand'], + 'stonehead': ['headstone', 'stonehead'], + 'stonen': ['sonnet', 'stonen', 'tenson'], + 'stoner': ['nestor', 'sterno', 'stoner', 'strone', 'tensor'], + 'stonewood': ['stonewood', 'woodstone'], + 'stong': ['stong', 'tongs'], + 'stonker': ['stonker', 'storken'], + 'stoof': ['foots', 'sfoot', 'stoof'], + 'stool': ['sotol', 'stool'], + 'stoon': ['snoot', 'stoon'], + 'stoop': ['spoot', 'stoop'], + 'stop': ['post', 'spot', 'stop', 'tops'], + 'stopback': ['backstop', 'stopback'], + 'stope': ['estop', 'stoep', 'stope'], + 'stoper': ['poster', 'presto', 'repost', 'respot', 'stoper'], + 'stoping': ['posting', 'stoping'], + 'stopless': ['postless', 'spotless', 'stopless'], + 'stoplessness': ['spotlessness', 'stoplessness'], + 'stoppeur': ['pteropus', 'stoppeur'], + 'storable': ['sortable', 'storable'], + 'storage': ['storage', 'tagsore'], + 'store': ['roset', 'rotse', 'soter', 'stero', 'store', 'torse'], + 'storeen': ['enstore', 'estrone', 'storeen', 'tornese'], + 'storeman': ['monaster', 'monstera', 'nearmost', 'storeman'], + 'storer': ['resort', 'roster', 'sorter', 'storer'], + 'storeship': ['posterish', 'prothesis', 'sophister', 'storeship', 'tephrosis'], + 'storesman': ['nosesmart', 'storesman'], + 'storge': ['groset', 'storge'], + 'storiate': ['astroite', 'ostraite', 'storiate'], + 'storied': ['oestrid', 'steroid', 'storied'], + 'storier': ['roister', 'storier'], + 'stork': ['stork', 'torsk'], + 'storken': ['stonker', 'storken'], + 'storm': ['storm', 'strom'], + 'stormwind': ['stormwind', 'windstorm'], + 'story': ['sorty', 'story', 'stroy'], + 'stot': ['stot', 'tost'], + 'stotter': ['stotter', 'stretto'], + 'stoun': ['notus', 'snout', 'stoun', 'tonus'], + 'stoup': ['spout', 'stoup'], + 'stour': ['roust', 'rusot', 'stour', 'sutor', 'torus'], + 'stouring': ['rousting', 'stouring'], + 'stoutly': ['stoutly', 'tylotus'], + 'stove': ['ovest', 'stove'], + 'stover': ['stover', 'strove'], + 'stow': ['sowt', 'stow', 'swot', 'wots'], + 'stowable': ['bestowal', 'stowable'], + 'stower': ['restow', 'stower', 'towser', 'worset'], + 'stra': ['sart', 'star', 'stra', 'tars', 'tsar'], + 'strad': ['darst', 'darts', 'strad'], + 'stradine': ['stradine', 'strained', 'tarnside'], + 'strae': ['aster', 'serta', 'stare', 'strae', 'tarse', 'teras'], + 'strafe': ['farset', 'faster', 'strafe'], + 'stragular': ['gastrular', 'stragular'], + 'straighten': ['shattering', 'straighten'], + 'straightener': ['restraighten', 'straightener'], + 'straightup': ['straightup', 'upstraight'], + 'straik': ['rastik', 'sarkit', 'straik'], + 'strain': ['instar', 'santir', 'strain'], + 'strained': ['stradine', 'strained', 'tarnside'], + 'strainer': ['restrain', 'strainer', 'transire'], + 'strainerman': ['strainerman', 'transmarine'], + 'straint': ['straint', 'transit', 'tristan'], + 'strait': ['artist', 'strait', 'strati'], + 'strake': ['skater', 'staker', 'strake', 'streak', 'tasker'], + 'straky': ['starky', 'straky'], + 'stram': ['smart', 'stram'], + 'strange': ['angster', 'garnets', 'nagster', 'strange'], + 'strangles': ['slangster', 'strangles'], + 'strap': ['spart', 'sprat', 'strap', 'traps'], + 'strapless': ['psaltress', 'strapless'], + 'strata': ['astart', 'strata'], + 'strategi': ['strategi', 'strigate'], + 'strath': ['strath', 'thrast'], + 'strati': ['artist', 'strait', 'strati'], + 'stratic': ['astrict', 'cartist', 'stratic'], + 'stratonic': ['narcotist', 'stratonic'], + 'stratonical': ['intracostal', 'stratonical'], + 'strave': ['starve', 'staver', 'strave', 'tavers', 'versta'], + 'straw': ['straw', 'swart', 'warst'], + 'strawy': ['strawy', 'swarty'], + 'stray': ['satyr', 'stary', 'stray', 'trasy'], + 'strayling': ['staringly', 'strayling'], + 'stre': ['rest', 'sert', 'stre'], + 'streak': ['skater', 'staker', 'strake', 'streak', 'tasker'], + 'streakily': ['satyrlike', 'streakily'], + 'stream': ['martes', 'master', 'remast', 'stream'], + 'streamer': ['masterer', 'restream', 'streamer'], + 'streamful': ['masterful', 'streamful'], + 'streamhead': ['headmaster', 'headstream', 'streamhead'], + 'streaming': ['germanist', 'streaming'], + 'streamless': ['masterless', 'streamless'], + 'streamlike': ['masterlike', 'streamlike'], + 'streamline': ['eternalism', 'streamline'], + 'streamling': ['masterling', 'streamling'], + 'streamside': ['mediatress', 'streamside'], + 'streamwort': ['masterwort', 'streamwort'], + 'streamy': ['mastery', 'streamy'], + 'stree': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'streek': ['streek', 'streke'], + 'streel': ['lester', 'selter', 'streel'], + 'streen': ['ernest', 'nester', 'resent', 'streen'], + 'streep': ['pester', 'preset', 'restep', 'streep'], + 'street': ['retest', 'setter', 'street', 'tester'], + 'streetcar': ['scatterer', 'streetcar'], + 'streke': ['streek', 'streke'], + 'strelitz': ['strelitz', 'streltzi'], + 'streltzi': ['strelitz', 'streltzi'], + 'stremma': ['stammer', 'stremma'], + 'strengthener': ['restrengthen', 'strengthener'], + 'strepen': ['penster', 'present', 'serpent', 'strepen'], + 'strepera': ['pasterer', 'strepera'], + 'strepor': ['sporter', 'strepor'], + 'strepsinema': ['esperantism', 'strepsinema'], + 'streptothricosis': ['streptothricosis', 'streptotrichosis'], + 'streptotrichosis': ['streptothricosis', 'streptotrichosis'], + 'stresser': ['restress', 'stresser'], + 'stret': ['stert', 'stret', 'trest'], + 'stretcher': ['restretch', 'stretcher'], + 'stretcherman': ['stretcherman', 'trenchmaster'], + 'stretto': ['stotter', 'stretto'], + 'strew': ['strew', 'trews', 'wrest'], + 'strewer': ['strewer', 'wrester'], + 'strey': ['resty', 'strey'], + 'streyne': ['streyne', 'styrene', 'yestern'], + 'stria': ['arist', + 'astir', + 'sitar', + 'stair', + 'stria', + 'tarsi', + 'tisar', + 'trias'], + 'striae': ['satire', 'striae'], + 'strial': ['latris', 'strial'], + 'striatal': ['altarist', 'striatal'], + 'striate': ['artiste', 'striate'], + 'striated': ['distater', 'striated'], + 'strich': ['christ', 'strich'], + 'strickle': ['stickler', 'strickle'], + 'stride': ['driest', 'stride'], + 'strife': ['fister', 'resift', 'sifter', 'strife'], + 'strig': ['grist', 'grits', 'strig'], + 'striga': ['gratis', 'striga'], + 'strigae': ['seagirt', 'strigae'], + 'strigate': ['strategi', 'strigate'], + 'striges': ['striges', 'tigress'], + 'strigulose': ['sortilegus', 'strigulose'], + 'strike': ['skiter', 'strike'], + 'striker': ['skirret', 'skirter', 'striker'], + 'striking': ['skirting', 'striking'], + 'strikingly': ['skirtingly', 'strikingly'], + 'stringer': ['restring', 'ringster', 'stringer'], + 'strinkle': ['sklinter', 'strinkle'], + 'striola': ['aristol', 'oralist', 'ortalis', 'striola'], + 'striolae': ['soterial', 'striolae'], + 'strip': ['spirt', 'sprit', 'stirp', 'strip'], + 'stripe': ['priest', 'pteris', 'sprite', 'stripe'], + 'stripeless': ['priestless', 'stripeless'], + 'striper': ['restrip', 'striper'], + 'striplet': ['splitter', 'striplet'], + 'strippit': ['strippit', 'trippist'], + 'strit': ['strit', 'trist'], + 'strive': ['stiver', 'strive', 'verist'], + 'stroam': ['stroam', 'stroma'], + 'strobila': ['laborist', 'strobila'], + 'strobilae': ['sobralite', 'strobilae'], + 'strobilate': ['brasiletto', 'strobilate'], + 'strode': ['sorted', 'strode'], + 'stroke': ['stoker', 'stroke'], + 'strom': ['storm', 'strom'], + 'stroma': ['stroam', 'stroma'], + 'stromatic': ['microstat', 'stromatic'], + 'strone': ['nestor', 'sterno', 'stoner', 'strone', 'tensor'], + 'stronghead': ['headstrong', 'stronghead'], + 'strontian': ['strontian', 'trisonant'], + 'strontianite': ['interstation', 'strontianite'], + 'strop': ['sport', 'strop'], + 'strophaic': ['actorship', 'strophaic'], + 'strophiolate': ['strophiolate', 'theatropolis'], + 'strophomena': ['nephrostoma', 'strophomena'], + 'strounge': ['strounge', 'sturgeon'], + 'stroup': ['sprout', 'stroup', 'stupor'], + 'strove': ['stover', 'strove'], + 'strow': ['strow', 'worst'], + 'stroy': ['sorty', 'story', 'stroy'], + 'strub': ['burst', 'strub'], + 'struck': ['struck', 'trucks'], + 'strue': ['serut', 'strue', 'turse', 'uster'], + 'strumae': ['staumer', 'strumae'], + 'strut': ['strut', 'sturt', 'trust'], + 'struth': ['struth', 'thrust'], + 'struthian': ['struthian', 'unathirst'], + 'stu': ['stu', 'ust'], + 'stuart': ['astrut', 'rattus', 'stuart'], + 'stub': ['bust', 'stub'], + 'stuber': ['berust', 'buster', 'stuber'], + 'stud': ['dust', 'stud'], + 'studdie': ['studdie', 'studied'], + 'student': ['student', 'stunted'], + 'studia': ['aditus', 'studia'], + 'studied': ['studdie', 'studied'], + 'study': ['dusty', 'study'], + 'stue': ['stue', 'suet'], + 'stuffer': ['restuff', 'stuffer'], + 'stug': ['gust', 'stug'], + 'stuiver': ['revuist', 'stuiver'], + 'stum': ['must', 'smut', 'stum'], + 'stumer': ['muster', 'sertum', 'stumer'], + 'stumper': ['stumper', 'sumpter'], + 'stun': ['stun', 'sunt', 'tsun'], + 'stunner': ['stunner', 'unstern'], + 'stunted': ['student', 'stunted'], + 'stunter': ['entrust', 'stunter', 'trusten'], + 'stupa': ['sputa', 'staup', 'stupa'], + 'stupe': ['setup', 'stupe', 'upset'], + 'stupor': ['sprout', 'stroup', 'stupor'], + 'stuprate': ['stuprate', 'upstater'], + 'stupulose': ['pustulose', 'stupulose'], + 'sturdiness': ['sturdiness', 'undistress'], + 'sturgeon': ['strounge', 'sturgeon'], + 'sturine': ['intruse', 'sturine'], + 'sturionine': ['reunionist', 'sturionine'], + 'sturmian': ['naturism', 'sturmian', 'turanism'], + 'sturnidae': ['disnature', 'sturnidae', 'truandise'], + 'sturninae': ['neustrian', 'saturnine', 'sturninae'], + 'sturnus': ['sturnus', 'untruss'], + 'sturt': ['strut', 'sturt', 'trust'], + 'sturtin': ['intrust', 'sturtin'], + 'stut': ['stut', 'tuts'], + 'stutter': ['stutter', 'tutster'], + 'styan': ['nasty', 'styan', 'tansy'], + 'styca': ['stacy', 'styca'], + 'stycerin': ['nycteris', 'stycerin'], + 'stygial': ['stagily', 'stygial'], + 'stylate': ['stately', 'stylate'], + 'styledom': ['modestly', 'styledom'], + 'stylite': ['stylite', 'testily'], + 'styloid': ['odylist', 'styloid'], + 'stylometer': ['metrostyle', 'stylometer'], + 'stylopidae': ['ideoplasty', 'stylopidae'], + 'styphelia': ['physalite', 'styphelia'], + 'styrene': ['streyne', 'styrene', 'yestern'], + 'styrol': ['sortly', 'styrol'], + 'stythe': ['stythe', 'tethys'], + 'styx': ['styx', 'xyst'], + 'suability': ['suability', 'usability'], + 'suable': ['suable', 'usable'], + 'sualocin': ['sualocin', 'unsocial'], + 'suant': ['staun', 'suant'], + 'suasible': ['basileus', 'issuable', 'suasible'], + 'suasion': ['sanious', 'suasion'], + 'suasory': ['ossuary', 'suasory'], + 'suave': ['sauve', 'suave'], + 'sub': ['bus', 'sub'], + 'subah': ['shuba', 'subah'], + 'subalpine': ['subalpine', 'unspiable'], + 'subanal': ['balanus', 'nabalus', 'subanal'], + 'subcaecal': ['accusable', 'subcaecal'], + 'subcantor': ['obscurant', 'subcantor'], + 'subcapsular': ['subcapsular', 'subscapular'], + 'subcenter': ['rubescent', 'subcenter'], + 'subchela': ['chasuble', 'subchela'], + 'subcool': ['colobus', 'subcool'], + 'subcortical': ['scorbutical', 'subcortical'], + 'subcortically': ['scorbutically', 'subcortically'], + 'subdealer': ['subdealer', 'subleader'], + 'subdean': ['subdean', 'unbased'], + 'subdeltaic': ['discutable', 'subdeltaic', 'subdialect'], + 'subdialect': ['discutable', 'subdeltaic', 'subdialect'], + 'subdie': ['busied', 'subdie'], + 'suber': ['burse', 'rebus', 'suber'], + 'subessential': ['subessential', 'suitableness'], + 'subherd': ['brushed', 'subherd'], + 'subhero': ['herbous', 'subhero'], + 'subhuman': ['subhuman', 'unambush'], + 'subimago': ['bigamous', 'subimago'], + 'sublanate': ['sublanate', 'unsatable'], + 'sublate': ['balteus', 'sublate'], + 'sublative': ['sublative', 'vestibula'], + 'subleader': ['subdealer', 'subleader'], + 'sublet': ['bustle', 'sublet', 'subtle'], + 'sublinear': ['insurable', 'sublinear'], + 'sublumbar': ['sublumbar', 'subumbral'], + 'submaid': ['misdaub', 'submaid'], + 'subman': ['busman', 'subman'], + 'submarine': ['semiurban', 'submarine'], + 'subnarcotic': ['obscurantic', 'subnarcotic'], + 'subnitrate': ['subnitrate', 'subtertian'], + 'subnote': ['subnote', 'subtone', 'unbesot'], + 'suboval': ['suboval', 'subvola'], + 'subpeltate': ['subpeltate', 'upsettable'], + 'subplat': ['subplat', 'upblast'], + 'subra': ['abrus', 'bursa', 'subra'], + 'subscapular': ['subcapsular', 'subscapular'], + 'subserve': ['subserve', 'subverse'], + 'subsider': ['disburse', 'subsider'], + 'substernal': ['substernal', 'turbanless'], + 'substraction': ['obscurantist', 'substraction'], + 'subtack': ['sackbut', 'subtack'], + 'subterraneal': ['subterraneal', 'unarrestable'], + 'subtertian': ['subnitrate', 'subtertian'], + 'subtle': ['bustle', 'sublet', 'subtle'], + 'subtone': ['subnote', 'subtone', 'unbesot'], + 'subtread': ['daubster', 'subtread'], + 'subtutor': ['outburst', 'subtutor'], + 'subulate': ['baetulus', 'subulate'], + 'subumbral': ['sublumbar', 'subumbral'], + 'subverse': ['subserve', 'subverse'], + 'subvola': ['suboval', 'subvola'], + 'succade': ['accused', 'succade'], + 'succeeder': ['resucceed', 'succeeder'], + 'succin': ['cnicus', 'succin'], + 'succinate': ['encaustic', 'succinate'], + 'succor': ['crocus', 'succor'], + 'such': ['cush', 'such'], + 'suck': ['cusk', 'suck'], + 'sucker': ['resuck', 'sucker'], + 'suckling': ['lungsick', 'suckling'], + 'suclat': ['scutal', 'suclat'], + 'sucramine': ['muscarine', 'sucramine'], + 'sucre': ['cruse', 'curse', 'sucre'], + 'suction': ['cotinus', 'suction', 'unstoic'], + 'suctional': ['suctional', 'sulcation', 'unstoical'], + 'suctoria': ['cotarius', 'octarius', 'suctoria'], + 'suctorial': ['ocularist', 'suctorial'], + 'sud': ['sud', 'uds'], + 'sudamen': ['medusan', 'sudamen'], + 'sudan': ['sudan', 'unsad'], + 'sudanese': ['danseuse', 'sudanese'], + 'sudani': ['sudani', 'unsaid'], + 'sudation': ['adustion', 'sudation'], + 'sudic': ['scudi', 'sudic'], + 'sudra': ['rudas', 'sudra'], + 'sue': ['sue', 'use'], + 'suer': ['ruse', 'suer', 'sure', 'user'], + 'suet': ['stue', 'suet'], + 'sufferer': ['resuffer', 'sufferer'], + 'sufflate': ['feastful', 'sufflate'], + 'suffocate': ['offuscate', 'suffocate'], + 'suffocation': ['offuscation', 'suffocation'], + 'sugamo': ['amusgo', 'sugamo'], + 'sugan': ['agnus', 'angus', 'sugan'], + 'sugar': ['argus', 'sugar'], + 'sugared': ['desugar', 'sugared'], + 'sugarlike': ['arguslike', 'sugarlike'], + 'suggester': ['resuggest', 'suggester'], + 'suggestionism': ['missuggestion', 'suggestionism'], + 'sugh': ['gush', 'shug', 'sugh'], + 'suidae': ['asideu', 'suidae'], + 'suimate': ['metusia', 'suimate', 'timaeus'], + 'suina': ['ianus', 'suina'], + 'suint': ['sintu', 'suint'], + 'suiones': ['sinuose', 'suiones'], + 'suist': ['situs', 'suist'], + 'suitable': ['sabulite', 'suitable'], + 'suitableness': ['subessential', 'suitableness'], + 'suitor': ['suitor', 'tursio'], + 'sula': ['saul', 'sula'], + 'sulcal': ['callus', 'sulcal'], + 'sulcar': ['cursal', 'sulcar'], + 'sulcation': ['suctional', 'sulcation', 'unstoical'], + 'suld': ['slud', 'suld'], + 'sulfamide': ['feudalism', 'sulfamide'], + 'sulfonium': ['fulminous', 'sulfonium'], + 'sulfuret': ['frustule', 'sulfuret'], + 'sulk': ['lusk', 'sulk'], + 'sulka': ['klaus', 'lukas', 'sulka'], + 'sulky': ['lusky', 'sulky'], + 'sulphinide': ['delphinius', 'sulphinide'], + 'sulphohydrate': ['hydrosulphate', 'sulphohydrate'], + 'sulphoproteid': ['protosulphide', 'sulphoproteid'], + 'sulphurea': ['elaphurus', 'sulphurea'], + 'sultan': ['sultan', 'unsalt'], + 'sultane': ['sultane', 'unslate'], + 'sultanian': ['annualist', 'sultanian'], + 'sultanin': ['insulant', 'sultanin'], + 'sultone': ['lentous', 'sultone'], + 'sultry': ['rustly', 'sultry'], + 'sum': ['mus', 'sum'], + 'sumac': ['camus', 'musca', 'scaum', 'sumac'], + 'sumak': ['kusam', 'sumak'], + 'sumatra': ['artamus', 'sumatra'], + 'sumerian': ['aneurism', 'arsenium', 'sumerian'], + 'sumitro': ['sumitro', 'tourism'], + 'summit': ['mutism', 'summit'], + 'summonable': ['somnambule', 'summonable'], + 'summoner': ['resummon', 'summoner'], + 'sumo': ['soum', 'sumo'], + 'sumpit': ['misput', 'sumpit'], + 'sumpitan': ['putanism', 'sumpitan'], + 'sumpter': ['stumper', 'sumpter'], + 'sumptuary': ['sputumary', 'sumptuary'], + 'sumptuous': ['sputumous', 'sumptuous'], + 'sunbeamed': ['sunbeamed', 'unembased'], + 'sundar': ['nardus', 'sundar', 'sundra'], + 'sundek': ['dusken', 'sundek'], + 'sundra': ['nardus', 'sundar', 'sundra'], + 'sung': ['snug', 'sung'], + 'sunil': ['linus', 'sunil'], + 'sunk': ['skun', 'sunk'], + 'sunlighted': ['sunlighted', 'unslighted'], + 'sunlit': ['insult', 'sunlit', 'unlist', 'unslit'], + 'sunni': ['sunni', 'unsin'], + 'sunray': ['sunray', 'surnay', 'synura'], + 'sunrise': ['russine', 'serinus', 'sunrise'], + 'sunshade': ['sunshade', 'unsashed'], + 'sunt': ['stun', 'sunt', 'tsun'], + 'sunup': ['sunup', 'upsun'], + 'sunweed': ['sunweed', 'unsewed'], + 'suomic': ['musico', 'suomic'], + 'sup': ['pus', 'sup'], + 'supa': ['apus', 'supa', 'upas'], + 'super': ['purse', 'resup', 'sprue', 'super'], + 'superable': ['perusable', 'superable'], + 'supercarpal': ['prescapular', 'supercarpal'], + 'superclaim': ['premusical', 'superclaim'], + 'supercontrol': ['preconsultor', 'supercontrol'], + 'supercool': ['escropulo', 'supercool'], + 'superheater': ['resuperheat', 'superheater'], + 'superideal': ['serpulidae', 'superideal'], + 'superintender': ['superintender', 'unenterprised'], + 'superline': ['serpuline', 'superline'], + 'supermoisten': ['sempiternous', 'supermoisten'], + 'supernacular': ['supernacular', 'supranuclear'], + 'supernal': ['purslane', 'serpulan', 'supernal'], + 'superoanterior': ['anterosuperior', 'superoanterior'], + 'superoposterior': ['posterosuperior', 'superoposterior'], + 'superpose': ['resuppose', 'superpose'], + 'superposition': ['resupposition', 'superposition'], + 'supersaint': ['presustain', 'puritaness', 'supersaint'], + 'supersalt': ['pertussal', 'supersalt'], + 'superservice': ['repercussive', 'superservice'], + 'supersonic': ['croupiness', 'percussion', 'supersonic'], + 'supertare': ['repasture', 'supertare'], + 'supertension': ['serpentinous', 'supertension'], + 'supertotal': ['supertotal', 'tetraplous'], + 'supertrain': ['rupestrian', 'supertrain'], + 'supinator': ['rainspout', 'supinator'], + 'supine': ['puisne', 'supine'], + 'supper': ['supper', 'uppers'], + 'supple': ['peplus', 'supple'], + 'suppletory': ['polypterus', 'suppletory'], + 'supplier': ['periplus', 'supplier'], + 'supporter': ['resupport', 'supporter'], + 'suppresser': ['resuppress', 'suppresser'], + 'suprahyoid': ['hyporadius', 'suprahyoid'], + 'supranuclear': ['supernacular', 'supranuclear'], + 'supreme': ['presume', 'supreme'], + 'sur': ['rus', 'sur', 'urs'], + 'sura': ['rusa', 'saur', 'sura', 'ursa', 'usar'], + 'surah': ['ashur', 'surah'], + 'surahi': ['shauri', 'surahi'], + 'sural': ['larus', 'sural', 'ursal'], + 'surat': ['astur', 'surat', 'sutra'], + 'surbase': ['rubasse', 'surbase'], + 'surbate': ['bursate', 'surbate'], + 'surcrue': ['crureus', 'surcrue'], + 'sure': ['ruse', 'suer', 'sure', 'user'], + 'suresh': ['rhesus', 'suresh'], + 'surette': ['surette', 'trustee'], + 'surge': ['grues', 'surge'], + 'surgent': ['gunster', 'surgent'], + 'surgeonship': ['springhouse', 'surgeonship'], + 'surgy': ['gyrus', 'surgy'], + 'suriana': ['saurian', 'suriana'], + 'surinam': ['surinam', 'uranism'], + 'surma': ['musar', 'ramus', 'rusma', 'surma'], + 'surmisant': ['saturnism', 'surmisant'], + 'surmise': ['misuser', 'surmise'], + 'surnap': ['surnap', 'unspar'], + 'surnay': ['sunray', 'surnay', 'synura'], + 'surprisement': ['surprisement', 'trumperiness'], + 'surrenderer': ['resurrender', 'surrenderer'], + 'surrogate': ['surrogate', 'urogaster'], + 'surrounder': ['resurround', 'surrounder'], + 'surya': ['saury', 'surya'], + 'sus': ['ssu', 'sus'], + 'susan': ['nasus', 'susan'], + 'suscept': ['suscept', 'suspect'], + 'susceptible': ['susceptible', 'suspectible'], + 'susceptor': ['spectrous', 'susceptor', 'suspector'], + 'susie': ['issue', 'susie'], + 'suspect': ['suscept', 'suspect'], + 'suspecter': ['resuspect', 'suspecter'], + 'suspectible': ['susceptible', 'suspectible'], + 'suspector': ['spectrous', 'susceptor', 'suspector'], + 'suspender': ['resuspend', 'suspender', 'unpressed'], + 'sustain': ['issuant', 'sustain'], + 'suther': ['reshut', 'suther', 'thurse', 'tusher'], + 'sutler': ['luster', 'result', 'rustle', 'sutler', 'ulster'], + 'suto': ['otus', 'oust', 'suto'], + 'sutor': ['roust', 'rusot', 'stour', 'sutor', 'torus'], + 'sutorian': ['staurion', 'sutorian'], + 'sutorious': ['sutorious', 'ustorious'], + 'sutra': ['astur', 'surat', 'sutra'], + 'suttin': ['suttin', 'tunist'], + 'suture': ['suture', 'uterus'], + 'svante': ['stevan', 'svante'], + 'svelte': ['stevel', 'svelte'], + 'swa': ['saw', 'swa', 'was'], + 'swage': ['swage', 'wages'], + 'swain': ['siwan', 'swain'], + 'swale': ['swale', 'sweal', 'wasel'], + 'swaler': ['swaler', 'warsel', 'warsle'], + 'swallet': ['setwall', 'swallet'], + 'swallo': ['sallow', 'swallo'], + 'swallower': ['reswallow', 'swallower'], + 'swami': ['aswim', 'swami'], + 'swan': ['sawn', 'snaw', 'swan'], + 'swap': ['swap', 'wasp'], + 'swarbie': ['barwise', 'swarbie'], + 'sware': ['resaw', 'sawer', 'seraw', 'sware', 'swear', 'warse'], + 'swarmer': ['reswarm', 'swarmer'], + 'swart': ['straw', 'swart', 'warst'], + 'swarty': ['strawy', 'swarty'], + 'swarve': ['swarve', 'swaver'], + 'swat': ['sawt', 'staw', 'swat', 'taws', 'twas', 'wast'], + 'swath': ['swath', 'whats'], + 'swathe': ['swathe', 'sweath'], + 'swati': ['swati', 'waist'], + 'swatter': ['stewart', 'swatter'], + 'swaver': ['swarve', 'swaver'], + 'sway': ['sway', 'ways', 'yaws'], + 'swayer': ['sawyer', 'swayer'], + 'sweal': ['swale', 'sweal', 'wasel'], + 'swear': ['resaw', 'sawer', 'seraw', 'sware', 'swear', 'warse'], + 'swearer': ['resawer', 'reswear', 'swearer'], + 'sweat': ['awest', 'sweat', 'tawse', 'waste'], + 'sweater': ['resweat', 'sweater'], + 'sweatful': ['sweatful', 'wasteful'], + 'sweath': ['swathe', 'sweath'], + 'sweatily': ['sweatily', 'tileways'], + 'sweatless': ['sweatless', 'wasteless'], + 'sweatproof': ['sweatproof', 'wasteproof'], + 'swede': ['sewed', 'swede'], + 'sweep': ['sweep', 'weeps'], + 'sweeper': ['resweep', 'sweeper'], + 'sweer': ['resew', 'sewer', 'sweer'], + 'sweered': ['sewered', 'sweered'], + 'sweet': ['sweet', 'weste'], + 'sweetbread': ['breastweed', 'sweetbread'], + 'sweller': ['reswell', 'sweller'], + 'swelter': ['swelter', 'wrestle'], + 'swep': ['spew', 'swep'], + 'swertia': ['swertia', 'waister'], + 'swile': ['lewis', 'swile'], + 'swiller': ['reswill', 'swiller'], + 'swindle': ['swindle', 'windles'], + 'swine': ['sinew', 'swine', 'wisen'], + 'swinestone': ['sonnetwise', 'swinestone'], + 'swiney': ['sinewy', 'swiney'], + 'swingback': ['backswing', 'swingback'], + 'swinge': ['sewing', 'swinge'], + 'swingle': ['slewing', 'swingle'], + 'swingtree': ['swingtree', 'westering'], + 'swipy': ['swipy', 'wispy'], + 'swire': ['swire', 'wiser'], + 'swith': ['swith', 'whist', 'whits', 'wisht'], + 'swithe': ['swithe', 'whites'], + 'swither': ['swither', 'whister', 'withers'], + 'swoon': ['swoon', 'woons'], + 'swordman': ['sandworm', 'swordman', 'wordsman'], + 'swordmanship': ['swordmanship', 'wordsmanship'], + 'swore': ['owser', 'resow', 'serow', 'sower', 'swore', 'worse'], + 'swot': ['sowt', 'stow', 'swot', 'wots'], + 'sybarite': ['bestiary', 'sybarite'], + 'sybil': ['sibyl', 'sybil'], + 'syce': ['scye', 'syce'], + 'sycones': ['coyness', 'sycones'], + 'syconoid': ['syconoid', 'syodicon'], + 'sye': ['sey', 'sye', 'yes'], + 'syenitic': ['cytisine', 'syenitic'], + 'syllabi': ['sibylla', 'syllabi'], + 'syllable': ['sellably', 'syllable'], + 'sylva': ['salvy', 'sylva'], + 'sylvae': ['slavey', 'sylvae'], + 'sylvine': ['snively', 'sylvine'], + 'sylvite': ['levyist', 'sylvite'], + 'symbol': ['blosmy', 'symbol'], + 'symmetric': ['mycterism', 'symmetric'], + 'sympathy': ['sympathy', 'symphyta'], + 'symphonic': ['cyphonism', 'symphonic'], + 'symphyta': ['sympathy', 'symphyta'], + 'symposion': ['spoonyism', 'symposion'], + 'synapse': ['synapse', 'yapness'], + 'synaptera': ['peasantry', 'synaptera'], + 'syncopator': ['antroscopy', 'syncopator'], + 'syndicate': ['asyndetic', 'cystidean', 'syndicate'], + 'synedral': ['lysander', 'synedral'], + 'syngamic': ['gymnasic', 'syngamic'], + 'syngenic': ['ensigncy', 'syngenic'], + 'synura': ['sunray', 'surnay', 'synura'], + 'syodicon': ['syconoid', 'syodicon'], + 'sypher': ['sphery', 'sypher'], + 'syphiloid': ['hypsiloid', 'syphiloid'], + 'syrian': ['siryan', 'syrian'], + 'syringa': ['signary', 'syringa'], + 'syringeful': ['refusingly', 'syringeful'], + 'syrup': ['pursy', 'pyrus', 'syrup'], + 'system': ['mystes', 'system'], + 'systole': ['systole', 'toyless'], + 'ta': ['at', 'ta'], + 'taa': ['ata', 'taa'], + 'taal': ['lata', 'taal', 'tala'], + 'taar': ['rata', 'taar', 'tara'], + 'tab': ['bat', 'tab'], + 'tabanus': ['sabanut', 'sabutan', 'tabanus'], + 'tabaret': ['baretta', 'rabatte', 'tabaret'], + 'tabber': ['barbet', 'rabbet', 'tabber'], + 'tabella': ['ballate', 'tabella'], + 'tabes': ['baste', 'beast', 'tabes'], + 'tabet': ['betta', 'tabet'], + 'tabinet': ['bettina', 'tabinet', 'tibetan'], + 'tabira': ['arabit', 'tabira'], + 'tabitha': ['habitat', 'tabitha'], + 'tabitude': ['dubitate', 'tabitude'], + 'table': ['batel', 'blate', 'bleat', 'table'], + 'tabled': ['dablet', 'tabled'], + 'tablemaker': ['marketable', 'tablemaker'], + 'tabler': ['albert', 'balter', 'labret', 'tabler'], + 'tables': ['ablest', 'stable', 'tables'], + 'tablet': ['battel', 'battle', 'tablet'], + 'tabletary': ['tabletary', 'treatably'], + 'tabling': ['batling', 'tabling'], + 'tabophobia': ['batophobia', 'tabophobia'], + 'tabor': ['abort', 'tabor'], + 'taborer': ['arboret', 'roberta', 'taborer'], + 'taboret': ['abettor', 'taboret'], + 'taborin': ['abortin', 'taborin'], + 'tabour': ['outbar', 'rubato', 'tabour'], + 'tabouret': ['obturate', 'tabouret'], + 'tabret': ['batter', 'bertat', 'tabret', 'tarbet'], + 'tabu': ['abut', 'tabu', 'tuba'], + 'tabula': ['ablaut', 'tabula'], + 'tabulare': ['bataleur', 'tabulare'], + 'tabule': ['batule', 'betula', 'tabule'], + 'taccaceae': ['cactaceae', 'taccaceae'], + 'taccaceous': ['cactaceous', 'taccaceous'], + 'tach': ['chat', 'tach'], + 'tache': ['cheat', 'tache', 'teach', 'theca'], + 'tacheless': ['tacheless', 'teachless'], + 'tachina': ['ithacan', 'tachina'], + 'tachinidae': ['anthicidae', 'tachinidae'], + 'tachograph': ['cathograph', 'tachograph'], + 'tacit': ['attic', 'catti', 'tacit'], + 'tacitly': ['cattily', 'tacitly'], + 'tacitness': ['cattiness', 'tacitness'], + 'taciturn': ['taciturn', 'urticant'], + 'tacker': ['racket', 'retack', 'tacker'], + 'tacksman': ['stackman', 'tacksman'], + 'taconian': ['catonian', 'taconian'], + 'taconic': ['cantico', 'catonic', 'taconic'], + 'tacso': ['ascot', 'coast', 'costa', 'tacso', 'tasco'], + 'tacsonia': ['acontias', 'tacsonia'], + 'tactile': ['lattice', 'tactile'], + 'tade': ['adet', 'date', 'tade', 'tead', 'teda'], + 'tadpole': ['platode', 'tadpole'], + 'tae': ['ate', 'eat', 'eta', 'tae', 'tea'], + 'tael': ['atle', 'laet', 'late', 'leat', 'tael', 'tale', 'teal'], + 'taen': ['ante', 'aten', 'etna', 'nate', 'neat', 'taen', 'tane', 'tean'], + 'taenia': ['aetian', 'antiae', 'taenia'], + 'taeniada': ['anatidae', 'taeniada'], + 'taenial': ['laniate', 'natalie', 'taenial'], + 'taenian': ['ananite', 'anatine', 'taenian'], + 'taenicide': ['deciatine', 'diacetine', 'taenicide', 'teniacide'], + 'taeniform': ['forminate', 'fremontia', 'taeniform'], + 'taenioid': ['ideation', 'iodinate', 'taenioid'], + 'taetsia': ['isatate', 'satiate', 'taetsia'], + 'tag': ['gat', 'tag'], + 'tagetes': ['gestate', 'tagetes'], + 'tagetol': ['lagetto', 'tagetol'], + 'tagged': ['gadget', 'tagged'], + 'tagger': ['garget', 'tagger'], + 'tagilite': ['litigate', 'tagilite'], + 'tagish': ['ghaist', 'tagish'], + 'taglike': ['glaiket', 'taglike'], + 'tagrag': ['ragtag', 'tagrag'], + 'tagsore': ['storage', 'tagsore'], + 'taheen': ['ethane', 'taheen'], + 'tahil': ['ihlat', 'tahil'], + 'tahin': ['ahint', 'hiant', 'tahin'], + 'tahr': ['hart', 'rath', 'tahr', 'thar', 'trah'], + 'tahsil': ['latish', 'tahsil'], + 'tahsin': ['snaith', 'tahsin'], + 'tai': ['ait', 'ati', 'ita', 'tai'], + 'taich': ['aitch', 'chait', 'chati', 'chita', 'taich', 'tchai'], + 'taigle': ['aiglet', 'ligate', 'taigle', 'tailge'], + 'tail': ['alit', 'tail', 'tali'], + 'tailage': ['agalite', 'tailage', 'taliage'], + 'tailboard': ['broadtail', 'tailboard'], + 'tailed': ['detail', 'dietal', 'dilate', 'edital', 'tailed'], + 'tailer': ['lirate', 'retail', 'retial', 'tailer'], + 'tailet': ['latite', 'tailet', 'tailte', 'talite'], + 'tailge': ['aiglet', 'ligate', 'taigle', 'tailge'], + 'tailing': ['gitalin', 'tailing'], + 'taille': ['taille', 'telial'], + 'tailoring': ['gratiolin', 'largition', 'tailoring'], + 'tailorman': ['antimoral', 'tailorman'], + 'tailory': ['orality', 'tailory'], + 'tailpin': ['pintail', 'tailpin'], + 'tailsman': ['staminal', 'tailsman', 'talisman'], + 'tailte': ['latite', 'tailet', 'tailte', 'talite'], + 'taily': ['laity', 'taily'], + 'taimen': ['etamin', 'inmate', 'taimen', 'tamein'], + 'tain': ['aint', 'anti', 'tain', 'tina'], + 'tainan': ['naiant', 'tainan'], + 'taino': ['niota', 'taino'], + 'taint': ['taint', 'tanti', 'tinta', 'titan'], + 'tainture': ['tainture', 'unattire'], + 'taipan': ['aptian', 'patina', 'taipan'], + 'taipo': ['patio', 'taipo', 'topia'], + 'tairge': ['gaiter', 'tairge', 'triage'], + 'tairn': ['riant', 'tairn', 'tarin', 'train'], + 'taise': ['saite', 'taise'], + 'taistrel': ['starlite', 'taistrel'], + 'taistril': ['taistril', 'trialist'], + 'taivers': ['staiver', 'taivers'], + 'taj': ['jat', 'taj'], + 'tajik': ['jatki', 'tajik'], + 'takar': ['katar', 'takar'], + 'take': ['kate', 'keta', 'take', 'teak'], + 'takedown': ['downtake', 'takedown'], + 'taker': ['kerat', 'taker'], + 'takin': ['kitan', 'takin'], + 'takings': ['gitksan', 'skating', 'takings'], + 'taky': ['katy', 'kyat', 'taky'], + 'tal': ['alt', 'lat', 'tal'], + 'tala': ['lata', 'taal', 'tala'], + 'talapoin': ['palation', 'talapoin'], + 'talar': ['altar', 'artal', 'ratal', 'talar'], + 'talari': ['altair', 'atrail', 'atrial', 'lariat', 'latria', 'talari'], + 'talc': ['clat', 'talc'], + 'talcer': ['carlet', 'cartel', 'claret', 'rectal', 'talcer'], + 'talcher': ['clethra', 'latcher', 'ratchel', 'relatch', 'talcher', 'trachle'], + 'talcoid': ['cotidal', 'lactoid', 'talcoid'], + 'talcose': ['alecost', 'lactose', 'scotale', 'talcose'], + 'talcous': ['costula', 'locusta', 'talcous'], + 'tald': ['dalt', 'tald'], + 'tale': ['atle', 'laet', 'late', 'leat', 'tael', 'tale', 'teal'], + 'taled': ['adlet', 'dealt', 'delta', 'lated', 'taled'], + 'talent': ['latent', 'latten', 'nattle', 'talent', 'tantle'], + 'taler': ['alert', 'alter', 'artel', 'later', 'ratel', 'taler', 'telar'], + 'tales': ['least', 'setal', 'slate', 'stale', 'steal', 'stela', 'tales'], + 'tali': ['alit', 'tail', 'tali'], + 'taliage': ['agalite', 'tailage', 'taliage'], + 'taligrade': ['taligrade', 'tragedial'], + 'talinum': ['multani', 'talinum'], + 'talion': ['italon', 'lation', 'talion'], + 'taliped': ['plaited', 'taliped'], + 'talipedic': ['talipedic', 'talpicide'], + 'talipes': ['aliptes', 'pastile', 'talipes'], + 'talipot': ['ptilota', 'talipot', 'toptail'], + 'talis': ['alist', 'litas', 'slait', 'talis'], + 'talisman': ['staminal', 'tailsman', 'talisman'], + 'talite': ['latite', 'tailet', 'tailte', 'talite'], + 'talker': ['kartel', 'retalk', 'talker'], + 'tallage': ['gallate', 'tallage'], + 'tallero': ['reallot', 'rotella', 'tallero'], + 'talles': ['sallet', 'stella', 'talles'], + 'talliage': ['allagite', 'alligate', 'talliage'], + 'tallier': ['literal', 'tallier'], + 'tallyho': ['loathly', 'tallyho'], + 'talon': ['notal', 'ontal', 'talon', 'tolan', 'tonal'], + 'taloned': ['taloned', 'toledan'], + 'talonid': ['itoland', 'talonid', 'tindalo'], + 'talose': ['lotase', 'osteal', 'solate', 'stolae', 'talose'], + 'talpa': ['aptal', 'palta', 'talpa'], + 'talpicide': ['talipedic', 'talpicide'], + 'talpidae': ['lapidate', 'talpidae'], + 'talpine': ['pantile', 'pentail', 'platine', 'talpine'], + 'talpoid': ['platoid', 'talpoid'], + 'taluche': ['auchlet', 'cutheal', 'taluche'], + 'taluka': ['latuka', 'taluka'], + 'talus': ['latus', 'sault', 'talus'], + 'tam': ['amt', 'mat', 'tam'], + 'tama': ['atma', 'tama'], + 'tamale': ['malate', 'meatal', 'tamale'], + 'tamanac': ['matacan', 'tamanac'], + 'tamanaca': ['atacaman', 'tamanaca'], + 'tamanoir': ['animator', 'tamanoir'], + 'tamanu': ['anatum', 'mantua', 'tamanu'], + 'tamara': ['armata', 'matara', 'tamara'], + 'tamarao': ['tamarao', 'tamaroa'], + 'tamarin': ['martian', 'tamarin'], + 'tamaroa': ['tamarao', 'tamaroa'], + 'tambor': ['tambor', 'tromba'], + 'tamboura': ['marabout', 'marabuto', 'tamboura'], + 'tambourer': ['arboretum', 'tambourer'], + 'tambreet': ['ambrette', 'tambreet'], + 'tamburan': ['rambutan', 'tamburan'], + 'tame': ['mate', 'meat', 'meta', 'tame', 'team', 'tema'], + 'tamein': ['etamin', 'inmate', 'taimen', 'tamein'], + 'tameless': ['mateless', 'meatless', 'tameless', 'teamless'], + 'tamelessness': ['matelessness', 'tamelessness'], + 'tamely': ['mately', 'tamely'], + 'tamer': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'tamise': ['samite', 'semita', 'tamise', 'teaism'], + 'tampin': ['pitman', 'tampin', 'tipman'], + 'tampion': ['maintop', 'ptomain', 'tampion', 'timpano'], + 'tampioned': ['ademption', 'tampioned'], + 'tampon': ['potman', 'tampon', 'topman'], + 'tamul': ['lamut', 'tamul'], + 'tamus': ['matsu', 'tamus', 'tsuma'], + 'tan': ['ant', 'nat', 'tan'], + 'tana': ['anat', 'anta', 'tana'], + 'tanach': ['acanth', 'anchat', 'tanach'], + 'tanager': ['argante', 'granate', 'tanager'], + 'tanagridae': ['tanagridae', 'tangaridae'], + 'tanagrine': ['argentina', 'tanagrine'], + 'tanagroid': ['gradation', 'indagator', 'tanagroid'], + 'tanak': ['kanat', 'tanak', 'tanka'], + 'tanaka': ['nataka', 'tanaka'], + 'tanala': ['atalan', 'tanala'], + 'tanan': ['annat', 'tanan'], + 'tanbur': ['tanbur', 'turban'], + 'tancel': ['cantle', 'cental', 'lancet', 'tancel'], + 'tanchoir': ['anorthic', 'anthroic', 'tanchoir'], + 'tandemist': ['misattend', 'tandemist'], + 'tandle': ['dental', 'tandle'], + 'tandour': ['rotunda', 'tandour'], + 'tane': ['ante', 'aten', 'etna', 'nate', 'neat', 'taen', 'tane', 'tean'], + 'tang': ['gant', 'gnat', 'tang'], + 'tanga': ['ganta', 'tanga'], + 'tangaridae': ['tanagridae', 'tangaridae'], + 'tangelo': ['angelot', 'tangelo'], + 'tanger': ['argent', 'garnet', 'garten', 'tanger'], + 'tangerine': ['argentine', 'tangerine'], + 'tangfish': ['shafting', 'tangfish'], + 'tangi': ['giant', 'tangi', 'tiang'], + 'tangibile': ['bigential', 'tangibile'], + 'tangible': ['bleating', 'tangible'], + 'tangie': ['eating', 'ingate', 'tangie'], + 'tangier': ['angrite', 'granite', 'ingrate', 'tangier', 'tearing', 'tigrean'], + 'tangle': ['tangle', 'telang'], + 'tangling': ['gnatling', 'tangling'], + 'tango': ['tango', 'tonga'], + 'tangs': ['angst', 'stang', 'tangs'], + 'tangue': ['gunate', 'tangue'], + 'tangum': ['tangum', 'tugman'], + 'tangun': ['tangun', 'tungan'], + 'tanh': ['hant', 'tanh', 'than'], + 'tanha': ['atnah', 'tanha', 'thana'], + 'tania': ['anita', 'niata', 'tania'], + 'tanica': ['actian', 'natica', 'tanica'], + 'tanier': ['nerita', 'ratine', 'retain', 'retina', 'tanier'], + 'tanist': ['astint', 'tanist'], + 'tanitic': ['tanitic', 'titanic'], + 'tanka': ['kanat', 'tanak', 'tanka'], + 'tankle': ['anklet', 'lanket', 'tankle'], + 'tanling': ['antling', 'tanling'], + 'tannaic': ['cantina', 'tannaic'], + 'tannase': ['annates', 'tannase'], + 'tannogallate': ['gallotannate', 'tannogallate'], + 'tannogallic': ['gallotannic', 'tannogallic'], + 'tannogen': ['nonagent', 'tannogen'], + 'tanproof': ['antproof', 'tanproof'], + 'tanquen': ['quannet', 'tanquen'], + 'tanrec': ['canter', + 'creant', + 'cretan', + 'nectar', + 'recant', + 'tanrec', + 'trance'], + 'tansy': ['nasty', 'styan', 'tansy'], + 'tantalean': ['antenatal', 'atlantean', 'tantalean'], + 'tantalic': ['atlantic', 'tantalic'], + 'tantalite': ['atlantite', 'tantalite'], + 'tantara': ['tantara', 'tartana'], + 'tantarara': ['tantarara', 'tarantara'], + 'tanti': ['taint', 'tanti', 'tinta', 'titan'], + 'tantle': ['latent', 'latten', 'nattle', 'talent', 'tantle'], + 'tantra': ['rattan', 'tantra', 'tartan'], + 'tantrism': ['tantrism', 'transmit'], + 'tantum': ['mutant', 'tantum', 'tutman'], + 'tanzeb': ['batzen', 'bezant', 'tanzeb'], + 'tao': ['oat', 'tao', 'toa'], + 'taoistic': ['iotacist', 'taoistic'], + 'taos': ['oast', 'stoa', 'taos'], + 'tap': ['apt', 'pat', 'tap'], + 'tapa': ['atap', 'pata', 'tapa'], + 'tapalo': ['patola', 'tapalo'], + 'tapas': ['patas', 'tapas'], + 'tape': ['pate', 'peat', 'tape', 'teap'], + 'tapeline': ['petaline', 'tapeline'], + 'tapeman': ['peatman', 'tapeman'], + 'tapen': ['enapt', 'paten', 'penta', 'tapen'], + 'taper': ['apert', 'pater', 'peart', 'prate', 'taper', 'terap'], + 'tapered': ['padtree', 'predate', 'tapered'], + 'tapering': ['partigen', 'tapering'], + 'taperly': ['apertly', 'peartly', 'platery', 'pteryla', 'taperly'], + 'taperness': ['apertness', 'peartness', 'taperness'], + 'tapestring': ['spattering', 'tapestring'], + 'tapestry': ['tapestry', 'tryptase'], + 'tapet': ['patte', 'tapet'], + 'tapete': ['pattee', 'tapete'], + 'taphouse': ['outshape', 'taphouse'], + 'taphria': ['pitarah', 'taphria'], + 'taphrina': ['parthian', 'taphrina'], + 'tapir': ['atrip', 'tapir'], + 'tapiro': ['portia', 'tapiro'], + 'tapirus': ['tapirus', 'upstair'], + 'tapis': ['piast', 'stipa', 'tapis'], + 'taplash': ['asphalt', 'spathal', 'taplash'], + 'tapmost': ['tapmost', 'topmast'], + 'tapnet': ['patent', 'patten', 'tapnet'], + 'tapoa': ['opata', 'patao', 'tapoa'], + 'taposa': ['sapota', 'taposa'], + 'taproom': ['protoma', 'taproom'], + 'taproot': ['potator', 'taproot'], + 'taps': ['past', 'spat', 'stap', 'taps'], + 'tapster': ['spatter', 'tapster'], + 'tapu': ['patu', 'paut', 'tapu'], + 'tapuyo': ['outpay', 'tapuyo'], + 'taqua': ['quata', 'taqua'], + 'tar': ['art', 'rat', 'tar', 'tra'], + 'tara': ['rata', 'taar', 'tara'], + 'taraf': ['taraf', 'tarfa'], + 'tarai': ['arati', 'atria', 'riata', 'tarai', 'tiara'], + 'taranchi': ['taranchi', 'thracian'], + 'tarantara': ['tantarara', 'tarantara'], + 'tarapin': ['patarin', 'tarapin'], + 'tarasc': ['castra', 'tarasc'], + 'tarbet': ['batter', 'bertat', 'tabret', 'tarbet'], + 'tardle': ['dartle', 'tardle'], + 'tardy': ['tardy', 'trady'], + 'tare': ['rate', 'tare', 'tear', 'tera'], + 'tarente': ['entreat', 'ratteen', 'tarente', 'ternate', 'tetrane'], + 'tarentine': ['entertain', 'tarentine', 'terentian'], + 'tarfa': ['taraf', 'tarfa'], + 'targe': ['gater', 'grate', 'great', 'greta', 'retag', 'targe'], + 'targeman': ['grateman', 'mangrate', 'mentagra', 'targeman'], + 'targer': ['garret', 'garter', 'grater', 'targer'], + 'target': ['gatter', 'target'], + 'targetman': ['targetman', 'termagant'], + 'targum': ['artgum', 'targum'], + 'tarheel': ['leather', 'tarheel'], + 'tarheeler': ['leatherer', 'releather', 'tarheeler'], + 'tari': ['airt', 'rita', 'tari', 'tiar'], + 'tarie': ['arite', 'artie', 'irate', 'retia', 'tarie'], + 'tarin': ['riant', 'tairn', 'tarin', 'train'], + 'tarish': ['rashti', 'tarish'], + 'tarletan': ['alterant', 'tarletan'], + 'tarlike': ['artlike', 'ratlike', 'tarlike'], + 'tarmac': ['mactra', 'tarmac'], + 'tarman': ['mantra', 'tarman'], + 'tarmi': ['mitra', 'tarmi', 'timar', 'tirma'], + 'tarn': ['natr', 'rant', 'tarn', 'tran'], + 'tarnal': ['antral', 'tarnal'], + 'tarnish': ['tarnish', 'trishna'], + 'tarnside': ['stradine', 'strained', 'tarnside'], + 'taro': ['rota', 'taro', 'tora'], + 'taroc': ['actor', 'corta', 'croat', 'rocta', 'taroc', 'troca'], + 'tarocco': ['coactor', 'tarocco'], + 'tarok': ['kotar', 'tarok'], + 'tarot': ['ottar', 'tarot', 'torta', 'troat'], + 'tarp': ['part', 'prat', 'rapt', 'tarp', 'trap'], + 'tarpan': ['partan', 'tarpan'], + 'tarpaulin': ['tarpaulin', 'unpartial'], + 'tarpeian': ['patarine', 'tarpeian'], + 'tarpon': ['patron', 'tarpon'], + 'tarquin': ['quatrin', 'tarquin'], + 'tarragon': ['arrogant', 'tarragon'], + 'tarred': ['darter', + 'dartre', + 'redart', + 'retard', + 'retrad', + 'tarred', + 'trader'], + 'tarrer': ['tarrer', 'terrar'], + 'tarriance': ['antiracer', 'tarriance'], + 'tarrie': ['arriet', 'tarrie'], + 'tars': ['sart', 'star', 'stra', 'tars', 'tsar'], + 'tarsal': ['astral', 'tarsal'], + 'tarsale': ['alaster', 'tarsale'], + 'tarsalgia': ['astragali', 'tarsalgia'], + 'tarse': ['aster', 'serta', 'stare', 'strae', 'tarse', 'teras'], + 'tarsi': ['arist', + 'astir', + 'sitar', + 'stair', + 'stria', + 'tarsi', + 'tisar', + 'trias'], + 'tarsia': ['arista', 'tarsia'], + 'tarsier': ['astrier', 'tarsier'], + 'tarsipes': ['piratess', 'serapist', 'tarsipes'], + 'tarsitis': ['satirist', 'tarsitis'], + 'tarsome': ['maestro', 'tarsome'], + 'tarsonemid': ['mosandrite', 'tarsonemid'], + 'tarsonemus': ['sarmentous', 'tarsonemus'], + 'tarsus': ['rastus', 'tarsus'], + 'tartan': ['rattan', 'tantra', 'tartan'], + 'tartana': ['tantara', 'tartana'], + 'tartaret': ['tartaret', 'tartrate'], + 'tartarin': ['tartarin', 'triratna'], + 'tartarous': ['saturator', 'tartarous'], + 'tarten': ['attern', 'natter', 'ratten', 'tarten'], + 'tartish': ['athirst', 'rattish', 'tartish'], + 'tartle': ['artlet', 'latter', 'rattle', 'tartle', 'tatler'], + 'tartlet': ['tartlet', 'tattler'], + 'tartly': ['rattly', 'tartly'], + 'tartrate': ['tartaret', 'tartrate'], + 'taruma': ['taruma', 'trauma'], + 'tarve': ['avert', 'tarve', 'taver', 'trave'], + 'tarweed': ['dewater', 'tarweed', 'watered'], + 'tarwood': ['ratwood', 'tarwood'], + 'taryba': ['baryta', 'taryba'], + 'tasco': ['ascot', 'coast', 'costa', 'tacso', 'tasco'], + 'tash': ['shat', 'tash'], + 'tasheriff': ['fireshaft', 'tasheriff'], + 'tashie': ['saithe', 'tashie', 'teaish'], + 'tashrif': ['ratfish', 'tashrif'], + 'tasian': ['astian', 'tasian'], + 'tasimetry': ['myristate', 'tasimetry'], + 'task': ['skat', 'task'], + 'tasker': ['skater', 'staker', 'strake', 'streak', 'tasker'], + 'taslet': ['latest', 'sattle', 'taslet'], + 'tasmanite': ['emanatist', 'staminate', 'tasmanite'], + 'tassah': ['shasta', 'tassah'], + 'tasse': ['asset', 'tasse'], + 'tassel': ['lasset', 'tassel'], + 'tasseler': ['rateless', 'tasseler', 'tearless', 'tesseral'], + 'tasser': ['assert', 'tasser'], + 'tassie': ['siesta', 'tassie'], + 'tastable': ['statable', 'tastable'], + 'taste': ['state', 'taste', 'tates', 'testa'], + 'tasted': ['stated', 'tasted'], + 'tasteful': ['stateful', 'tasteful'], + 'tastefully': ['statefully', 'tastefully'], + 'tastefulness': ['statefulness', 'tastefulness'], + 'tasteless': ['stateless', 'tasteless'], + 'taster': ['stater', 'taster', 'testar'], + 'tasu': ['saut', 'tasu', 'utas'], + 'tatar': ['attar', 'tatar'], + 'tatarize': ['tatarize', 'zaratite'], + 'tatchy': ['chatty', 'tatchy'], + 'tate': ['etta', 'tate', 'teat'], + 'tater': ['atter', 'tater', 'teart', 'tetra', 'treat'], + 'tates': ['state', 'taste', 'tates', 'testa'], + 'tath': ['hatt', 'tath', 'that'], + 'tatian': ['attain', 'tatian'], + 'tatler': ['artlet', 'latter', 'rattle', 'tartle', 'tatler'], + 'tatterly': ['tatterly', 'tattlery'], + 'tattler': ['tartlet', 'tattler'], + 'tattlery': ['tatterly', 'tattlery'], + 'tatu': ['tatu', 'taut'], + 'tau': ['tau', 'tua', 'uta'], + 'taube': ['butea', 'taube', 'tubae'], + 'taula': ['aluta', 'taula'], + 'taum': ['muta', 'taum'], + 'taun': ['antu', 'aunt', 'naut', 'taun', 'tuan', 'tuna'], + 'taungthu': ['taungthu', 'untaught'], + 'taupe': ['taupe', 'upeat'], + 'taupo': ['apout', 'taupo'], + 'taur': ['ruta', 'taur'], + 'taurean': ['taurean', 'uranate'], + 'tauric': ['tauric', 'uratic', 'urtica'], + 'taurine': ['ruinate', 'taurine', 'uranite', 'urinate'], + 'taurocol': ['outcarol', 'taurocol'], + 'taut': ['tatu', 'taut'], + 'tauten': ['attune', 'nutate', 'tauten'], + 'tautomeric': ['autometric', 'tautomeric'], + 'tautomery': ['autometry', 'tautomery'], + 'tav': ['tav', 'vat'], + 'tavast': ['sattva', 'tavast'], + 'tave': ['tave', 'veta'], + 'taver': ['avert', 'tarve', 'taver', 'trave'], + 'tavers': ['starve', 'staver', 'strave', 'tavers', 'versta'], + 'tavert': ['tavert', 'vatter'], + 'tavola': ['tavola', 'volata'], + 'taw': ['taw', 'twa', 'wat'], + 'tawa': ['awat', 'tawa'], + 'tawer': ['tawer', 'water', 'wreat'], + 'tawery': ['tawery', 'watery'], + 'tawite': ['tawite', 'tawtie', 'twaite'], + 'tawn': ['nawt', 'tawn', 'want'], + 'tawny': ['tawny', 'wanty'], + 'taws': ['sawt', 'staw', 'swat', 'taws', 'twas', 'wast'], + 'tawse': ['awest', 'sweat', 'tawse', 'waste'], + 'tawtie': ['tawite', 'tawtie', 'twaite'], + 'taxed': ['detax', 'taxed'], + 'taxer': ['extra', 'retax', 'taxer'], + 'tay': ['tay', 'yat'], + 'tayer': ['tayer', 'teary'], + 'tchai': ['aitch', 'chait', 'chati', 'chita', 'taich', 'tchai'], + 'tche': ['chet', 'etch', 'tche', 'tech'], + 'tchi': ['chit', 'itch', 'tchi'], + 'tchu': ['chut', 'tchu', 'utch'], + 'tchwi': ['tchwi', 'wicht', 'witch'], + 'tea': ['ate', 'eat', 'eta', 'tae', 'tea'], + 'teaberry': ['betrayer', 'eatberry', 'rebetray', 'teaberry'], + 'teaboy': ['betoya', 'teaboy'], + 'teacart': ['caretta', 'teacart', 'tearcat'], + 'teach': ['cheat', 'tache', 'teach', 'theca'], + 'teachable': ['cheatable', 'teachable'], + 'teachableness': ['cheatableness', 'teachableness'], + 'teache': ['achete', 'hecate', 'teache', 'thecae'], + 'teacher': ['cheater', 'hectare', 'recheat', 'reteach', 'teacher'], + 'teachery': ['cheatery', 'cytherea', 'teachery'], + 'teaching': ['cheating', 'teaching'], + 'teachingly': ['cheatingly', 'teachingly'], + 'teachless': ['tacheless', 'teachless'], + 'tead': ['adet', 'date', 'tade', 'tead', 'teda'], + 'teaer': ['arete', 'eater', 'teaer'], + 'teagle': ['eaglet', 'legate', 'teagle', 'telega'], + 'teaish': ['saithe', 'tashie', 'teaish'], + 'teaism': ['samite', 'semita', 'tamise', 'teaism'], + 'teak': ['kate', 'keta', 'take', 'teak'], + 'teal': ['atle', 'laet', 'late', 'leat', 'tael', 'tale', 'teal'], + 'team': ['mate', 'meat', 'meta', 'tame', 'team', 'tema'], + 'teamer': ['reetam', 'retame', 'teamer'], + 'teaming': ['mintage', 'teaming', 'tegmina'], + 'teamless': ['mateless', 'meatless', 'tameless', 'teamless'], + 'teamman': ['meatman', 'teamman'], + 'teamster': ['teamster', 'trametes'], + 'tean': ['ante', 'aten', 'etna', 'nate', 'neat', 'taen', 'tane', 'tean'], + 'teanal': ['anteal', 'lanate', 'teanal'], + 'teap': ['pate', 'peat', 'tape', 'teap'], + 'teapot': ['aptote', 'optate', 'potate', 'teapot'], + 'tear': ['rate', 'tare', 'tear', 'tera'], + 'tearable': ['elabrate', 'tearable'], + 'tearably': ['betrayal', 'tearably'], + 'tearcat': ['caretta', 'teacart', 'tearcat'], + 'teardown': ['danewort', 'teardown'], + 'teardrop': ['predator', 'protrade', 'teardrop'], + 'tearer': ['rerate', 'retare', 'tearer'], + 'tearful': ['faulter', 'refutal', 'tearful'], + 'tearing': ['angrite', 'granite', 'ingrate', 'tangier', 'tearing', 'tigrean'], + 'tearless': ['rateless', 'tasseler', 'tearless', 'tesseral'], + 'tearlike': ['keralite', 'tearlike'], + 'tearpit': ['partite', 'tearpit'], + 'teart': ['atter', 'tater', 'teart', 'tetra', 'treat'], + 'teary': ['tayer', 'teary'], + 'teasably': ['stayable', 'teasably'], + 'tease': ['setae', 'tease'], + 'teasel': ['ateles', 'saltee', 'sealet', 'stelae', 'teasel'], + 'teaser': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'teasing': ['easting', + 'gainset', + 'genista', + 'ingesta', + 'seating', + 'signate', + 'teasing'], + 'teasler': ['realest', 'reslate', 'resteal', 'stealer', 'teasler'], + 'teasy': ['teasy', 'yeast'], + 'teat': ['etta', 'tate', 'teat'], + 'teather': ['teather', 'theater', 'thereat'], + 'tebu': ['bute', 'tebu', 'tube'], + 'teca': ['cate', 'teca'], + 'tecali': ['calite', 'laetic', 'tecali'], + 'tech': ['chet', 'etch', 'tche', 'tech'], + 'techily': ['ethylic', 'techily'], + 'technica': ['atechnic', 'catechin', 'technica'], + 'technocracy': ['conycatcher', 'technocracy'], + 'technopsychology': ['psychotechnology', 'technopsychology'], + 'techous': ['souchet', 'techous', 'tousche'], + 'techy': ['techy', 'tyche'], + 'tecla': ['cleat', 'eclat', 'ectal', 'lacet', 'tecla'], + 'teco': ['cote', 'teco'], + 'tecoma': ['comate', 'metoac', 'tecoma'], + 'tecomin': ['centimo', 'entomic', 'tecomin'], + 'tecon': ['cento', 'conte', 'tecon'], + 'tectal': ['cattle', 'tectal'], + 'tectology': ['ctetology', 'tectology'], + 'tectona': ['oncetta', 'tectona'], + 'tectospinal': ['entoplastic', 'spinotectal', 'tectospinal', 'tenoplastic'], + 'tecuma': ['acetum', 'tecuma'], + 'tecuna': ['tecuna', 'uncate'], + 'teda': ['adet', 'date', 'tade', 'tead', 'teda'], + 'tedious': ['outside', 'tedious'], + 'tediousness': ['outsideness', 'tediousness'], + 'teedle': ['delete', 'teedle'], + 'teel': ['leet', 'lete', 'teel', 'tele'], + 'teem': ['meet', 'mete', 'teem'], + 'teemer': ['meeter', 'remeet', 'teemer'], + 'teeming': ['meeting', 'teeming', 'tegmine'], + 'teems': ['teems', 'temse'], + 'teen': ['neet', 'nete', 'teen'], + 'teens': ['steen', 'teens', 'tense'], + 'teer': ['reet', 'teer', 'tree'], + 'teerer': ['retree', 'teerer'], + 'teest': ['teest', 'teste'], + 'teet': ['teet', 'tete'], + 'teeter': ['teeter', 'terete'], + 'teeth': ['teeth', 'theet'], + 'teething': ['genthite', 'teething'], + 'teg': ['get', 'teg'], + 'tegean': ['geneat', 'negate', 'tegean'], + 'tegmina': ['mintage', 'teaming', 'tegmina'], + 'tegminal': ['ligament', 'metaling', 'tegminal'], + 'tegmine': ['meeting', 'teeming', 'tegmine'], + 'tegular': ['gaulter', 'tegular'], + 'teheran': ['earthen', 'enheart', 'hearten', 'naether', 'teheran', 'traheen'], + 'tehsildar': ['heraldist', 'tehsildar'], + 'teian': ['entia', 'teian', 'tenai', 'tinea'], + 'teicher': ['erethic', 'etheric', 'heretic', 'heteric', 'teicher'], + 'teil': ['lite', 'teil', 'teli', 'tile'], + 'teind': ['detin', 'teind', 'tined'], + 'teinder': ['nitered', 'redient', 'teinder'], + 'teinland': ['dentinal', 'teinland', 'tendinal'], + 'teioid': ['iodite', 'teioid'], + 'teju': ['jute', 'teju'], + 'telamon': ['omental', 'telamon'], + 'telang': ['tangle', 'telang'], + 'telar': ['alert', 'alter', 'artel', 'later', 'ratel', 'taler', 'telar'], + 'telarian': ['retainal', 'telarian'], + 'telary': ['lyrate', 'raylet', 'realty', 'telary'], + 'tele': ['leet', 'lete', 'teel', 'tele'], + 'telecast': ['castelet', 'telecast'], + 'telega': ['eaglet', 'legate', 'teagle', 'telega'], + 'telegn': ['gentle', 'telegn'], + 'telegrapher': ['retelegraph', 'telegrapher'], + 'telei': ['elite', 'telei'], + 'telephone': ['phenetole', 'telephone'], + 'telephony': ['polythene', 'telephony'], + 'telephotograph': ['phototelegraph', 'telephotograph'], + 'telephotographic': ['phototelegraphic', 'telephotographic'], + 'telephotography': ['phototelegraphy', 'telephotography'], + 'teleradiophone': ['radiotelephone', 'teleradiophone'], + 'teleran': ['alterne', 'enteral', 'eternal', 'teleran', 'teneral'], + 'teleseism': ['messelite', 'semisteel', 'teleseism'], + 'telespectroscope': ['spectrotelescope', 'telespectroscope'], + 'telestereoscope': ['stereotelescope', 'telestereoscope'], + 'telestial': ['satellite', 'telestial'], + 'telestic': ['telestic', 'testicle'], + 'telfer': ['felter', 'telfer', 'trefle'], + 'teli': ['lite', 'teil', 'teli', 'tile'], + 'telial': ['taille', 'telial'], + 'telic': ['clite', 'telic'], + 'telinga': ['atingle', 'gelatin', 'genital', 'langite', 'telinga'], + 'tellach': ['hellcat', 'tellach'], + 'teller': ['retell', 'teller'], + 'tellima': ['mitella', 'tellima'], + 'tellina': ['nitella', 'tellina'], + 'tellurian': ['tellurian', 'unliteral'], + 'telome': ['omelet', 'telome'], + 'telonism': ['melonist', 'telonism'], + 'telopsis': ['polistes', 'telopsis'], + 'telpath': ['pathlet', 'telpath'], + 'telson': ['solent', 'stolen', 'telson'], + 'telsonic': ['lentisco', 'telsonic'], + 'telt': ['lett', 'telt'], + 'tema': ['mate', 'meat', 'meta', 'tame', 'team', 'tema'], + 'teman': ['ament', 'meant', 'teman'], + 'temin': ['metin', 'temin', 'timne'], + 'temp': ['empt', 'temp'], + 'tempean': ['peteman', 'tempean'], + 'temper': ['temper', 'tempre'], + 'tempera': ['premate', 'tempera'], + 'temperer': ['retemper', 'temperer'], + 'temperish': ['herpetism', 'metership', 'metreship', 'temperish'], + 'templar': ['templar', 'trample'], + 'template': ['palmette', 'template'], + 'temple': ['pelmet', 'temple'], + 'tempora': ['pteroma', 'tempora'], + 'temporarily': ['polarimetry', 'premorality', 'temporarily'], + 'temporofrontal': ['frontotemporal', 'temporofrontal'], + 'temporooccipital': ['occipitotemporal', 'temporooccipital'], + 'temporoparietal': ['parietotemporal', 'temporoparietal'], + 'tempre': ['temper', 'tempre'], + 'tempter': ['retempt', 'tempter'], + 'temse': ['teems', 'temse'], + 'temser': ['mester', 'restem', 'temser', 'termes'], + 'temulent': ['temulent', 'unmettle'], + 'ten': ['net', 'ten'], + 'tenable': ['beltane', 'tenable'], + 'tenace': ['cetane', 'tenace'], + 'tenai': ['entia', 'teian', 'tenai', 'tinea'], + 'tenanter': ['retenant', 'tenanter'], + 'tenantless': ['latentness', 'tenantless'], + 'tencteri': ['reticent', 'tencteri'], + 'tend': ['dent', 'tend'], + 'tender': ['denter', 'rented', 'tender'], + 'tenderer': ['retender', 'tenderer'], + 'tenderish': ['disherent', 'hinderest', 'tenderish'], + 'tendinal': ['dentinal', 'teinland', 'tendinal'], + 'tendinitis': ['dentinitis', 'tendinitis'], + 'tendour': ['tendour', 'unroted'], + 'tendril': ['tendril', 'trindle'], + 'tendron': ['donnert', 'tendron'], + 'teneral': ['alterne', 'enteral', 'eternal', 'teleran', 'teneral'], + 'teneriffe': ['fifteener', 'teneriffe'], + 'tenesmus': ['muteness', 'tenesmus'], + 'teng': ['gent', 'teng'], + 'tengu': ['tengu', 'unget'], + 'teniacidal': ['acetanilid', 'laciniated', 'teniacidal'], + 'teniacide': ['deciatine', 'diacetine', 'taenicide', 'teniacide'], + 'tenible': ['beltine', 'tenible'], + 'tenino': ['intone', 'tenino'], + 'tenline': ['lenient', 'tenline'], + 'tenner': ['rennet', 'tenner'], + 'tennis': ['innest', 'sennit', 'sinnet', 'tennis'], + 'tenomyotomy': ['myotenotomy', 'tenomyotomy'], + 'tenon': ['nonet', 'tenon'], + 'tenoner': ['enteron', 'tenoner'], + 'tenonian': ['annotine', 'tenonian'], + 'tenonitis': ['sentition', 'tenonitis'], + 'tenontophyma': ['nematophyton', 'tenontophyma'], + 'tenophyte': ['entophyte', 'tenophyte'], + 'tenoplastic': ['entoplastic', 'spinotectal', 'tectospinal', 'tenoplastic'], + 'tenor': ['noter', 'tenor', 'toner', 'trone'], + 'tenorist': ['ortstein', 'tenorist'], + 'tenovaginitis': ['investigation', 'tenovaginitis'], + 'tenpin': ['pinnet', 'tenpin'], + 'tenrec': ['center', 'recent', 'tenrec'], + 'tense': ['steen', 'teens', 'tense'], + 'tensible': ['nebelist', 'stilbene', 'tensible'], + 'tensile': ['leisten', 'setline', 'tensile'], + 'tension': ['stenion', 'tension'], + 'tensional': ['alstonine', 'tensional'], + 'tensive': ['estevin', 'tensive'], + 'tenson': ['sonnet', 'stonen', 'tenson'], + 'tensor': ['nestor', 'sterno', 'stoner', 'strone', 'tensor'], + 'tentable': ['nettable', 'tentable'], + 'tentacle': ['ectental', 'tentacle'], + 'tentage': ['genetta', 'tentage'], + 'tentation': ['attention', 'tentation'], + 'tentative': ['attentive', 'tentative'], + 'tentatively': ['attentively', 'tentatively'], + 'tentativeness': ['attentiveness', 'tentativeness'], + 'tented': ['detent', 'netted', 'tented'], + 'tenter': ['netter', 'retent', 'tenter'], + 'tention': ['nettion', 'tention', 'tontine'], + 'tentorial': ['natrolite', 'tentorial'], + 'tenty': ['netty', 'tenty'], + 'tenuiroster': ['tenuiroster', 'urosternite'], + 'tenuistriate': ['intersituate', 'tenuistriate'], + 'tenure': ['neuter', 'retune', 'runtee', 'tenure', 'tureen'], + 'tenurial': ['lutrinae', 'retinula', 'rutelian', 'tenurial'], + 'teocalli': ['colletia', 'teocalli'], + 'teosinte': ['noisette', 'teosinte'], + 'tepache': ['heptace', 'tepache'], + 'tepal': ['leapt', + 'palet', + 'patel', + 'pelta', + 'petal', + 'plate', + 'pleat', + 'tepal'], + 'tepanec': ['pentace', 'tepanec'], + 'tepecano': ['conepate', 'tepecano'], + 'tephrite': ['perthite', 'tephrite'], + 'tephritic': ['perthitic', 'tephritic'], + 'tephroite': ['heptorite', 'tephroite'], + 'tephrosis': ['posterish', 'prothesis', 'sophister', 'storeship', 'tephrosis'], + 'tepor': ['poter', 'prote', 'repot', 'tepor', 'toper', 'trope'], + 'tequila': ['liquate', 'tequila'], + 'tera': ['rate', 'tare', 'tear', 'tera'], + 'teraglin': ['integral', 'teraglin', 'triangle'], + 'terap': ['apert', 'pater', 'peart', 'prate', 'taper', 'terap'], + 'teras': ['aster', 'serta', 'stare', 'strae', 'tarse', 'teras'], + 'teratism': ['mistreat', 'teratism'], + 'terbia': ['baiter', 'barite', 'rebait', 'terbia'], + 'terbium': ['burmite', 'imbrute', 'terbium'], + 'tercelet': ['electret', 'tercelet'], + 'terceron': ['corrente', 'terceron'], + 'tercia': ['acrite', 'arcite', 'tercia', 'triace', 'tricae'], + 'tercine': ['citrene', 'enteric', 'enticer', 'tercine'], + 'tercio': ['erotic', 'tercio'], + 'terebilic': ['celtiberi', 'terebilic'], + 'terebinthian': ['terebinthian', 'terebinthina'], + 'terebinthina': ['terebinthian', 'terebinthina'], + 'terebra': ['rebater', 'terebra'], + 'terebral': ['barrelet', 'terebral'], + 'terentian': ['entertain', 'tarentine', 'terentian'], + 'teresa': ['asteer', + 'easter', + 'eastre', + 'reseat', + 'saeter', + 'seater', + 'staree', + 'teaser', + 'teresa'], + 'teresian': ['arsenite', 'resinate', 'teresian', 'teresina'], + 'teresina': ['arsenite', 'resinate', 'teresian', 'teresina'], + 'terete': ['teeter', 'terete'], + 'teretial': ['laterite', 'literate', 'teretial'], + 'tereus': ['retuse', 'tereus'], + 'tergal': ['raglet', 'tergal'], + 'tergant': ['garnett', 'gnatter', 'gratten', 'tergant'], + 'tergeminous': ['mentigerous', 'tergeminous'], + 'teri': ['iter', 'reit', 'rite', 'teri', 'tier', 'tire'], + 'teriann': ['entrain', 'teriann'], + 'terma': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'termagant': ['targetman', 'termagant'], + 'termes': ['mester', 'restem', 'temser', 'termes'], + 'termin': ['minter', 'remint', 'termin'], + 'terminal': ['terminal', 'tramline'], + 'terminalia': ['laminarite', 'terminalia'], + 'terminate': ['antimeter', 'attermine', 'interteam', 'terminate', 'tetramine'], + 'termini': ['interim', 'termini'], + 'terminine': ['intermine', 'nemertini', 'terminine'], + 'terminus': ['numerist', 'terminus'], + 'termital': ['remittal', 'termital'], + 'termite': ['emitter', 'termite'], + 'termly': ['myrtle', 'termly'], + 'termon': ['mentor', 'merton', 'termon', 'tormen'], + 'termor': ['termor', 'tremor'], + 'tern': ['rent', 'tern'], + 'terna': ['antre', 'arent', 'retan', 'terna'], + 'ternal': ['altern', 'antler', 'learnt', 'rental', 'ternal'], + 'ternar': ['arrent', 'errant', 'ranter', 'ternar'], + 'ternarious': ['souterrain', 'ternarious', 'trouserian'], + 'ternate': ['entreat', 'ratteen', 'tarente', 'ternate', 'tetrane'], + 'terne': ['enter', 'neter', 'renet', 'terne', 'treen'], + 'ternion': ['intoner', 'ternion'], + 'ternlet': ['nettler', 'ternlet'], + 'terp': ['pert', 'petr', 'terp'], + 'terpane': ['patener', 'pearten', 'petrean', 'terpane'], + 'terpeneless': ['repleteness', 'terpeneless'], + 'terpin': ['nipter', 'terpin'], + 'terpine': ['petrine', 'terpine'], + 'terpineol': ['interlope', 'interpole', 'repletion', 'terpineol'], + 'terpinol': ['pointrel', 'terpinol'], + 'terrace': ['caterer', 'recrate', 'retrace', 'terrace'], + 'terraciform': ['crateriform', 'terraciform'], + 'terrage': ['greater', 'regrate', 'terrage'], + 'terrain': ['arterin', 'retrain', 'terrain', 'trainer'], + 'terral': ['retral', 'terral'], + 'terrance': ['canterer', 'recanter', 'recreant', 'terrance'], + 'terrapin': ['pretrain', 'terrapin'], + 'terrar': ['tarrer', 'terrar'], + 'terrence': ['centerer', 'recenter', 'recentre', 'terrence'], + 'terrene': ['enterer', 'terrene'], + 'terret': ['retter', 'terret'], + 'terri': ['terri', 'tirer', 'trier'], + 'terrier': ['retirer', 'terrier'], + 'terrine': ['reinter', 'terrine'], + 'terron': ['terron', 'treron', 'troner'], + 'terry': ['retry', 'terry'], + 'terse': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'tersely': ['restyle', 'tersely'], + 'tersion': ['oestrin', 'tersion'], + 'tertia': ['attire', 'ratite', 'tertia'], + 'tertian': ['intreat', 'iterant', 'nitrate', 'tertian'], + 'tertiana': ['attainer', 'reattain', 'tertiana'], + 'terton': ['rotten', 'terton'], + 'tervee': ['revete', 'tervee'], + 'terzina': ['retzian', 'terzina'], + 'terzo': ['terzo', 'tozer'], + 'tesack': ['casket', 'tesack'], + 'teskere': ['skeeter', 'teskere'], + 'tessella': ['satelles', 'tessella'], + 'tesseral': ['rateless', 'tasseler', 'tearless', 'tesseral'], + 'test': ['sett', 'stet', 'test'], + 'testa': ['state', 'taste', 'tates', 'testa'], + 'testable': ['settable', 'testable'], + 'testament': ['statement', 'testament'], + 'testar': ['stater', 'taster', 'testar'], + 'teste': ['teest', 'teste'], + 'tested': ['detest', 'tested'], + 'testee': ['settee', 'testee'], + 'tester': ['retest', 'setter', 'street', 'tester'], + 'testes': ['sestet', 'testes', 'tsetse'], + 'testicle': ['telestic', 'testicle'], + 'testicular': ['testicular', 'trisulcate'], + 'testily': ['stylite', 'testily'], + 'testing': ['setting', 'testing'], + 'teston': ['ostent', 'teston'], + 'testor': ['sotter', 'testor'], + 'testril': ['litster', 'slitter', 'stilter', 'testril'], + 'testy': ['testy', 'tyste'], + 'tetanic': ['nictate', 'tetanic'], + 'tetanical': ['cantalite', 'lactinate', 'tetanical'], + 'tetanoid': ['antidote', 'tetanoid'], + 'tetanus': ['tetanus', 'unstate', 'untaste'], + 'tetarconid': ['detraction', 'doctrinate', 'tetarconid'], + 'tetard': ['tetard', 'tetrad'], + 'tetchy': ['chetty', 'tetchy'], + 'tete': ['teet', 'tete'], + 'tetel': ['ettle', 'tetel'], + 'tether': ['hetter', 'tether'], + 'tethys': ['stythe', 'tethys'], + 'tetra': ['atter', 'tater', 'teart', 'tetra', 'treat'], + 'tetracid': ['citrated', 'tetracid', 'tetradic'], + 'tetrad': ['tetard', 'tetrad'], + 'tetradic': ['citrated', 'tetracid', 'tetradic'], + 'tetragonia': ['giornatate', 'tetragonia'], + 'tetrahexahedron': ['hexatetrahedron', 'tetrahexahedron'], + 'tetrakishexahedron': ['hexakistetrahedron', 'tetrakishexahedron'], + 'tetralin': ['tetralin', 'triental'], + 'tetramin': ['intermat', 'martinet', 'tetramin'], + 'tetramine': ['antimeter', 'attermine', 'interteam', 'terminate', 'tetramine'], + 'tetrander': ['retardent', 'tetrander'], + 'tetrandrous': ['tetrandrous', 'unrostrated'], + 'tetrane': ['entreat', 'ratteen', 'tarente', 'ternate', 'tetrane'], + 'tetrao': ['rotate', 'tetrao'], + 'tetraodon': ['detonator', 'tetraodon'], + 'tetraonine': ['entoretina', 'tetraonine'], + 'tetraplous': ['supertotal', 'tetraplous'], + 'tetrasporic': ['tetrasporic', 'triceratops'], + 'tetraxonia': ['retaxation', 'tetraxonia'], + 'tetrical': ['tetrical', 'tractile'], + 'tetricous': ['tetricous', 'toreutics'], + 'tetronic': ['contrite', 'tetronic'], + 'tetrose': ['rosette', 'tetrose'], + 'tetterous': ['outstreet', 'tetterous'], + 'teucri': ['curite', 'teucri', 'uretic'], + 'teucrian': ['anuretic', 'centauri', 'centuria', 'teucrian'], + 'teucrin': ['nutrice', 'teucrin'], + 'teuk': ['ketu', 'teuk', 'tuke'], + 'tew': ['tew', 'wet'], + 'tewa': ['tewa', 'twae', 'weta'], + 'tewel': ['tewel', 'tweel'], + 'tewer': ['rewet', 'tewer', 'twere'], + 'tewit': ['tewit', 'twite'], + 'tewly': ['tewly', 'wetly'], + 'tha': ['aht', 'hat', 'tha'], + 'thai': ['hati', 'thai'], + 'thais': ['shita', 'thais'], + 'thalami': ['hamital', 'thalami'], + 'thaler': ['arthel', 'halter', 'lather', 'thaler'], + 'thalia': ['hiatal', 'thalia'], + 'thaliard': ['hardtail', 'thaliard'], + 'thamesis': ['mathesis', 'thamesis'], + 'than': ['hant', 'tanh', 'than'], + 'thana': ['atnah', 'tanha', 'thana'], + 'thanan': ['nathan', 'thanan'], + 'thanatism': ['staithman', 'thanatism'], + 'thanatotic': ['chattation', 'thanatotic'], + 'thane': ['enhat', 'ethan', 'nathe', 'neath', 'thane'], + 'thanker': ['rethank', 'thanker'], + 'thapes': ['spathe', 'thapes'], + 'thar': ['hart', 'rath', 'tahr', 'thar', 'trah'], + 'tharen': ['anther', 'nather', 'tharen', 'thenar'], + 'tharm': ['tharm', 'thram'], + 'thasian': ['ashanti', 'sanhita', 'shaitan', 'thasian'], + 'that': ['hatt', 'tath', 'that'], + 'thatcher': ['rethatch', 'thatcher'], + 'thaw': ['thaw', 'wath', 'what'], + 'thawer': ['rethaw', 'thawer', 'wreath'], + 'the': ['het', 'the'], + 'thea': ['ahet', 'haet', 'hate', 'heat', 'thea'], + 'theah': ['heath', 'theah'], + 'thearchy': ['hatchery', 'thearchy'], + 'theat': ['theat', 'theta'], + 'theater': ['teather', 'theater', 'thereat'], + 'theatricism': ['chemiatrist', 'chrismatite', 'theatricism'], + 'theatropolis': ['strophiolate', 'theatropolis'], + 'theatry': ['hattery', 'theatry'], + 'theb': ['beth', 'theb'], + 'thebaid': ['habited', 'thebaid'], + 'theca': ['cheat', 'tache', 'teach', 'theca'], + 'thecae': ['achete', 'hecate', 'teache', 'thecae'], + 'thecal': ['achtel', 'chalet', 'thecal', 'thecla'], + 'thecasporal': ['archapostle', 'thecasporal'], + 'thecata': ['attache', 'thecata'], + 'thecitis': ['ethicist', 'thecitis', 'theistic'], + 'thecla': ['achtel', 'chalet', 'thecal', 'thecla'], + 'theer': ['ether', 'rethe', 'theer', 'there', 'three'], + 'theet': ['teeth', 'theet'], + 'thegn': ['ghent', 'thegn'], + 'thegnly': ['lengthy', 'thegnly'], + 'theine': ['ethine', 'theine'], + 'their': ['ither', 'their'], + 'theirn': ['hinter', 'nither', 'theirn'], + 'theirs': ['shrite', 'theirs'], + 'theism': ['theism', 'themis'], + 'theist': ['theist', 'thetis'], + 'theistic': ['ethicist', 'thecitis', 'theistic'], + 'thema': ['ahmet', 'thema'], + 'thematic': ['mathetic', 'thematic'], + 'thematist': ['hattemist', 'thematist'], + 'themer': ['mether', 'themer'], + 'themis': ['theism', 'themis'], + 'themistian': ['antitheism', 'themistian'], + 'then': ['hent', 'neth', 'then'], + 'thenal': ['ethnal', 'hantle', 'lathen', 'thenal'], + 'thenar': ['anther', 'nather', 'tharen', 'thenar'], + 'theobald': ['bolthead', 'theobald'], + 'theocrasia': ['oireachtas', 'theocrasia'], + 'theocrat': ['theocrat', 'trochate'], + 'theocratic': ['rheotactic', 'theocratic'], + 'theodora': ['dorothea', 'theodora'], + 'theodore': ['theodore', 'treehood'], + 'theogonal': ['halogeton', 'theogonal'], + 'theologic': ['ethologic', 'theologic'], + 'theological': ['ethological', 'lethologica', 'theological'], + 'theologism': ['hemologist', 'theologism'], + 'theology': ['ethology', 'theology'], + 'theophanic': ['phaethonic', 'theophanic'], + 'theophilist': ['philotheist', 'theophilist'], + 'theopsychism': ['psychotheism', 'theopsychism'], + 'theorbo': ['boother', 'theorbo'], + 'theorematic': ['heteratomic', 'theorematic'], + 'theoretic': ['heterotic', 'theoretic'], + 'theoretician': ['heretication', 'theoretician'], + 'theorician': ['antiheroic', 'theorician'], + 'theorics': ['chirotes', 'theorics'], + 'theorism': ['homerist', 'isotherm', 'otherism', 'theorism'], + 'theorist': ['otherist', 'theorist'], + 'theorizer': ['rhetorize', 'theorizer'], + 'theorum': ['mouther', 'theorum'], + 'theotherapy': ['heteropathy', 'theotherapy'], + 'therblig': ['blighter', 'therblig'], + 'there': ['ether', 'rethe', 'theer', 'there', 'three'], + 'thereas': ['thereas', 'theresa'], + 'thereat': ['teather', 'theater', 'thereat'], + 'therein': ['enherit', 'etherin', 'neither', 'therein'], + 'thereness': ['retheness', 'thereness', 'threeness'], + 'thereology': ['heterology', 'thereology'], + 'theres': ['esther', 'hester', 'theres'], + 'theresa': ['thereas', 'theresa'], + 'therese': ['sheeter', 'therese'], + 'therewithal': ['therewithal', 'whitleather'], + 'theriac': ['certhia', 'rhaetic', 'theriac'], + 'therial': ['hairlet', 'therial'], + 'theriodic': ['dichroite', 'erichtoid', 'theriodic'], + 'theriodonta': ['dehortation', 'theriodonta'], + 'thermantic': ['intermatch', 'thermantic'], + 'thermo': ['mother', 'thermo'], + 'thermobarograph': ['barothermograph', 'thermobarograph'], + 'thermoelectric': ['electrothermic', 'thermoelectric'], + 'thermoelectrometer': ['electrothermometer', 'thermoelectrometer'], + 'thermogalvanometer': ['galvanothermometer', 'thermogalvanometer'], + 'thermogeny': ['mythogreen', 'thermogeny'], + 'thermography': ['mythographer', 'thermography'], + 'thermology': ['mythologer', 'thermology'], + 'thermos': ['smother', 'thermos'], + 'thermotype': ['phytometer', 'thermotype'], + 'thermotypic': ['phytometric', 'thermotypic'], + 'thermotypy': ['phytometry', 'thermotypy'], + 'theroid': ['rhodite', 'theroid'], + 'theron': ['hornet', 'nother', 'theron', 'throne'], + 'thersitical': ['thersitical', 'trachelitis'], + 'these': ['sheet', 'these'], + 'thesean': ['sneathe', 'thesean'], + 'thesial': ['heliast', 'thesial'], + 'thesis': ['shiest', 'thesis'], + 'theta': ['theat', 'theta'], + 'thetical': ['athletic', 'thetical'], + 'thetis': ['theist', 'thetis'], + 'thew': ['hewt', 'thew', 'whet'], + 'they': ['they', 'yeth'], + 'theyre': ['theyre', 'yether'], + 'thicken': ['kitchen', 'thicken'], + 'thickener': ['kitchener', 'rethicken', 'thickener'], + 'thicket': ['chettik', 'thicket'], + 'thienyl': ['ethylin', 'thienyl'], + 'thig': ['gith', 'thig'], + 'thigh': ['hight', 'thigh'], + 'thill': ['illth', 'thill'], + 'thin': ['hint', 'thin'], + 'thing': ['night', 'thing'], + 'thingal': ['halting', 'lathing', 'thingal'], + 'thingless': ['lightness', 'nightless', 'thingless'], + 'thinglet': ['thinglet', 'thlinget'], + 'thinglike': ['nightlike', 'thinglike'], + 'thingly': ['nightly', 'thingly'], + 'thingman': ['nightman', 'thingman'], + 'thinker': ['rethink', 'thinker'], + 'thio': ['hoit', 'hoti', 'thio'], + 'thiocresol': ['holosteric', 'thiocresol'], + 'thiol': ['litho', 'thiol', 'tholi'], + 'thiolacetic': ['heliotactic', 'thiolacetic'], + 'thiophenol': ['lithophone', 'thiophenol'], + 'thiopyran': ['phoniatry', 'thiopyran'], + 'thirlage': ['litharge', 'thirlage'], + 'this': ['hist', 'sith', 'this', 'tshi'], + 'thissen': ['sithens', 'thissen'], + 'thistle': ['lettish', 'thistle'], + 'thlinget': ['thinglet', 'thlinget'], + 'tho': ['hot', 'tho'], + 'thob': ['both', 'thob'], + 'thole': ['helot', 'hotel', 'thole'], + 'tholi': ['litho', 'thiol', 'tholi'], + 'tholos': ['soloth', 'tholos'], + 'thomaean': ['amaethon', 'thomaean'], + 'thomasine': ['hematosin', 'thomasine'], + 'thomisid': ['isthmoid', 'thomisid'], + 'thomsonite': ['monotheist', 'thomsonite'], + 'thonder': ['thonder', 'thorned'], + 'thonga': ['gnatho', 'thonga'], + 'thoo': ['hoot', 'thoo', 'toho'], + 'thoom': ['mooth', 'thoom'], + 'thoracectomy': ['chromatocyte', 'thoracectomy'], + 'thoracic': ['thoracic', 'tocharic', 'trochaic'], + 'thoral': ['harlot', 'orthal', 'thoral'], + 'thore': ['other', 'thore', 'throe', 'toher'], + 'thoric': ['chorti', 'orthic', 'thoric', 'trochi'], + 'thorina': ['orthian', 'thorina'], + 'thorite': ['hortite', 'orthite', 'thorite'], + 'thorn': ['north', 'thorn'], + 'thorned': ['thonder', 'thorned'], + 'thornhead': ['rhodanthe', 'thornhead'], + 'thorny': ['rhyton', 'thorny'], + 'thoro': ['ortho', 'thoro'], + 'thort': ['thort', 'troth'], + 'thos': ['host', 'shot', 'thos', 'tosh'], + 'those': ['ethos', 'shote', 'those'], + 'thowel': ['howlet', 'thowel'], + 'thraces': ['stacher', 'thraces'], + 'thracian': ['taranchi', 'thracian'], + 'thraep': ['thraep', 'threap'], + 'thrain': ['hartin', 'thrain'], + 'thram': ['tharm', 'thram'], + 'thrang': ['granth', 'thrang'], + 'thrasher': ['rethrash', 'thrasher'], + 'thrast': ['strath', 'thrast'], + 'thraw': ['thraw', 'warth', 'whart', 'wrath'], + 'thread': ['dearth', 'hatred', 'rathed', 'thread'], + 'threaden': ['adherent', 'headrent', 'neatherd', 'threaden'], + 'threader': ['rethread', 'threader'], + 'threadworm': ['motherward', 'threadworm'], + 'thready': ['hydrate', 'thready'], + 'threap': ['thraep', 'threap'], + 'threat': ['hatter', 'threat'], + 'threatener': ['rethreaten', 'threatener'], + 'three': ['ether', 'rethe', 'theer', 'there', 'three'], + 'threeling': ['lightener', 'relighten', 'threeling'], + 'threeness': ['retheness', 'thereness', 'threeness'], + 'threne': ['erthen', 'henter', 'nether', 'threne'], + 'threnode': ['dethrone', 'threnode'], + 'threnodic': ['chondrite', 'threnodic'], + 'threnos': ['shorten', 'threnos'], + 'thresher': ['rethresh', 'thresher'], + 'thrice': ['cither', 'thrice'], + 'thriller': ['rethrill', 'thriller'], + 'thripel': ['philter', 'thripel'], + 'thripidae': ['rhipidate', 'thripidae'], + 'throat': ['athort', 'throat'], + 'throb': ['broth', 'throb'], + 'throe': ['other', 'thore', 'throe', 'toher'], + 'thronal': ['althorn', 'anthrol', 'thronal'], + 'throne': ['hornet', 'nother', 'theron', 'throne'], + 'throu': ['routh', 'throu'], + 'throughout': ['outthrough', 'throughout'], + 'throw': ['throw', 'whort', 'worth', 'wroth'], + 'throwdown': ['downthrow', 'throwdown'], + 'thrower': ['rethrow', 'thrower'], + 'throwing': ['ingrowth', 'throwing'], + 'throwout': ['outthrow', 'outworth', 'throwout'], + 'thrum': ['thrum', 'thurm'], + 'thrust': ['struth', 'thrust'], + 'thruster': ['rethrust', 'thruster'], + 'thuan': ['ahunt', 'haunt', 'thuan', 'unhat'], + 'thulr': ['thulr', 'thurl'], + 'thunderbearing': ['thunderbearing', 'underbreathing'], + 'thunderer': ['rethunder', 'thunderer'], + 'thundering': ['thundering', 'underthing'], + 'thuoc': ['couth', 'thuoc', 'touch'], + 'thurl': ['thulr', 'thurl'], + 'thurm': ['thrum', 'thurm'], + 'thurse': ['reshut', 'suther', 'thurse', 'tusher'], + 'thurt': ['thurt', 'truth'], + 'thus': ['shut', 'thus', 'tush'], + 'thusness': ['shutness', 'thusness'], + 'thwacker': ['thwacker', 'whatreck'], + 'thwartover': ['overthwart', 'thwartover'], + 'thymelic': ['methylic', 'thymelic'], + 'thymetic': ['hymettic', 'thymetic'], + 'thymus': ['mythus', 'thymus'], + 'thyreogenous': ['heterogynous', 'thyreogenous'], + 'thyreohyoid': ['hyothyreoid', 'thyreohyoid'], + 'thyreoid': ['hydriote', 'thyreoid'], + 'thyreoidal': ['thyreoidal', 'thyroideal'], + 'thyreosis': ['oysterish', 'thyreosis'], + 'thyris': ['shirty', 'thyris'], + 'thyrocricoid': ['cricothyroid', 'thyrocricoid'], + 'thyrogenic': ['thyrogenic', 'trichogyne'], + 'thyrohyoid': ['hyothyroid', 'thyrohyoid'], + 'thyroideal': ['thyreoidal', 'thyroideal'], + 'thyroiodin': ['iodothyrin', 'thyroiodin'], + 'thyroprivic': ['thyroprivic', 'vitrophyric'], + 'thysanopteran': ['parasyntheton', 'thysanopteran'], + 'thysel': ['shelty', 'thysel'], + 'ti': ['it', 'ti'], + 'tiang': ['giant', 'tangi', 'tiang'], + 'tiao': ['iota', 'tiao'], + 'tiar': ['airt', 'rita', 'tari', 'tiar'], + 'tiara': ['arati', 'atria', 'riata', 'tarai', 'tiara'], + 'tiarella': ['arillate', 'tiarella'], + 'tib': ['bit', 'tib'], + 'tibetan': ['bettina', 'tabinet', 'tibetan'], + 'tibial': ['bilati', 'tibial'], + 'tibiale': ['biliate', 'tibiale'], + 'tibiofemoral': ['femorotibial', 'tibiofemoral'], + 'tic': ['cit', 'tic'], + 'ticca': ['cacti', 'ticca'], + 'tice': ['ceti', 'cite', 'tice'], + 'ticer': ['citer', 'recti', 'ticer', 'trice'], + 'tichodroma': ['chromatoid', 'tichodroma'], + 'ticketer': ['reticket', 'ticketer'], + 'tickler': ['tickler', 'trickle'], + 'tickproof': ['prickfoot', 'tickproof'], + 'ticuna': ['anicut', 'nautic', 'ticuna', 'tunica'], + 'ticunan': ['ticunan', 'tunican'], + 'tid': ['dit', 'tid'], + 'tidal': ['datil', 'dital', 'tidal', 'tilda'], + 'tiddley': ['lyddite', 'tiddley'], + 'tide': ['diet', 'dite', 'edit', 'tide', 'tied'], + 'tidely': ['idlety', 'lydite', 'tidely', 'tidley'], + 'tiding': ['tiding', 'tingid'], + 'tidley': ['idlety', 'lydite', 'tidely', 'tidley'], + 'tied': ['diet', 'dite', 'edit', 'tide', 'tied'], + 'tien': ['iten', 'neti', 'tien', 'tine'], + 'tiepin': ['pinite', 'tiepin'], + 'tier': ['iter', 'reit', 'rite', 'teri', 'tier', 'tire'], + 'tierce': ['cerite', 'certie', 'recite', 'tierce'], + 'tiered': ['dieter', 'tiered'], + 'tierer': ['errite', 'reiter', 'retier', 'retire', 'tierer'], + 'tiffy': ['fifty', 'tiffy'], + 'tifter': ['fitter', 'tifter'], + 'tig': ['git', 'tig'], + 'tigella': ['tigella', 'tillage'], + 'tiger': ['tiger', 'tigre'], + 'tigereye': ['geyerite', 'tigereye'], + 'tightener': ['retighten', 'tightener'], + 'tiglinic': ['lignitic', 'tiglinic'], + 'tigre': ['tiger', 'tigre'], + 'tigrean': ['angrite', 'granite', 'ingrate', 'tangier', 'tearing', 'tigrean'], + 'tigress': ['striges', 'tigress'], + 'tigrine': ['igniter', 'ringite', 'tigrine'], + 'tigurine': ['intrigue', 'tigurine'], + 'tikka': ['katik', 'tikka'], + 'tikur': ['tikur', 'turki'], + 'til': ['lit', 'til'], + 'tilaite': ['italite', 'letitia', 'tilaite'], + 'tilda': ['datil', 'dital', 'tidal', 'tilda'], + 'tilde': ['tilde', 'tiled'], + 'tile': ['lite', 'teil', 'teli', 'tile'], + 'tiled': ['tilde', 'tiled'], + 'tiler': ['liter', 'tiler'], + 'tilery': ['tilery', 'tilyer'], + 'tileways': ['sweatily', 'tileways'], + 'tileyard': ['dielytra', 'tileyard'], + 'tilia': ['itali', 'tilia'], + 'tilikum': ['kulimit', 'tilikum'], + 'till': ['lilt', 'till'], + 'tillable': ['belltail', 'bletilla', 'tillable'], + 'tillaea': ['alalite', 'tillaea'], + 'tillage': ['tigella', 'tillage'], + 'tiller': ['retill', 'rillet', 'tiller'], + 'tilmus': ['litmus', 'tilmus'], + 'tilpah': ['lapith', 'tilpah'], + 'tilter': ['litter', 'tilter', 'titler'], + 'tilting': ['tilting', 'titling', 'tlingit'], + 'tiltup': ['tiltup', 'uptilt'], + 'tilyer': ['tilery', 'tilyer'], + 'timable': ['limbate', 'timable', 'timbale'], + 'timaeus': ['metusia', 'suimate', 'timaeus'], + 'timaline': ['meliatin', 'timaline'], + 'timani': ['intima', 'timani'], + 'timar': ['mitra', 'tarmi', 'timar', 'tirma'], + 'timbal': ['limbat', 'timbal'], + 'timbale': ['limbate', 'timable', 'timbale'], + 'timber': ['betrim', 'timber', 'timbre'], + 'timbered': ['bemitred', 'timbered'], + 'timberer': ['retimber', 'timberer'], + 'timberlike': ['kimberlite', 'timberlike'], + 'timbre': ['betrim', 'timber', 'timbre'], + 'time': ['emit', 'item', 'mite', 'time'], + 'timecard': ['dermatic', 'timecard'], + 'timed': ['demit', 'timed'], + 'timeproof': ['miteproof', 'timeproof'], + 'timer': ['merit', 'miter', 'mitre', 'remit', 'timer'], + 'times': ['metis', 'smite', 'stime', 'times'], + 'timesaving': ['negativism', 'timesaving'], + 'timework': ['timework', 'worktime'], + 'timid': ['dimit', 'timid'], + 'timidly': ['mytilid', 'timidly'], + 'timidness': ['destinism', 'timidness'], + 'timish': ['isthmi', 'timish'], + 'timne': ['metin', 'temin', 'timne'], + 'timo': ['itmo', 'moit', 'omit', 'timo'], + 'timon': ['minot', 'timon', 'tomin'], + 'timorese': ['rosetime', 'timorese', 'tiresome'], + 'timpani': ['impaint', 'timpani'], + 'timpano': ['maintop', 'ptomain', 'tampion', 'timpano'], + 'tin': ['nit', 'tin'], + 'tina': ['aint', 'anti', 'tain', 'tina'], + 'tincal': ['catlin', 'tincal'], + 'tinchel': ['linchet', 'tinchel'], + 'tinctorial': ['tinctorial', 'trinoctial'], + 'tind': ['dint', 'tind'], + 'tindal': ['antlid', 'tindal'], + 'tindalo': ['itoland', 'talonid', 'tindalo'], + 'tinder': ['dirten', 'rident', 'tinder'], + 'tindered': ['dendrite', 'tindered'], + 'tinderous': ['detrusion', 'tinderous', 'unstoried'], + 'tine': ['iten', 'neti', 'tien', 'tine'], + 'tinea': ['entia', 'teian', 'tenai', 'tinea'], + 'tineal': ['entail', 'tineal'], + 'tinean': ['annite', 'innate', 'tinean'], + 'tined': ['detin', 'teind', 'tined'], + 'tineid': ['indite', 'tineid'], + 'tineman': ['mannite', 'tineman'], + 'tineoid': ['edition', 'odinite', 'otidine', 'tineoid'], + 'tinetare': ['intereat', 'tinetare'], + 'tinety': ['entity', 'tinety'], + 'tinged': ['nidget', 'tinged'], + 'tinger': ['engirt', 'tinger'], + 'tingid': ['tiding', 'tingid'], + 'tingitidae': ['indigitate', 'tingitidae'], + 'tingler': ['ringlet', 'tingler', 'tringle'], + 'tinhouse': ['outshine', 'tinhouse'], + 'tink': ['knit', 'tink'], + 'tinker': ['reknit', 'tinker'], + 'tinkerer': ['retinker', 'tinkerer'], + 'tinkler': ['tinkler', 'trinkle'], + 'tinlet': ['litten', 'tinlet'], + 'tinne': ['innet', 'tinne'], + 'tinned': ['dentin', 'indent', 'intend', 'tinned'], + 'tinner': ['intern', 'tinner'], + 'tinnet': ['intent', 'tinnet'], + 'tino': ['into', 'nito', 'oint', 'tino'], + 'tinoceras': ['atroscine', 'certosina', 'ostracine', 'tinoceras', 'tricosane'], + 'tinosa': ['sotnia', 'tinosa'], + 'tinsel': ['enlist', 'listen', 'silent', 'tinsel'], + 'tinselly': ['silently', 'tinselly'], + 'tinta': ['taint', 'tanti', 'tinta', 'titan'], + 'tintage': ['attinge', 'tintage'], + 'tinter': ['nitter', 'tinter'], + 'tintie': ['tintie', 'titien'], + 'tintiness': ['insistent', 'tintiness'], + 'tinty': ['nitty', 'tinty'], + 'tinworker': ['interwork', 'tinworker'], + 'tionontates': ['ostentation', 'tionontates'], + 'tip': ['pit', 'tip'], + 'tipe': ['piet', 'tipe'], + 'tipful': ['tipful', 'uplift'], + 'tipless': ['pitless', 'tipless'], + 'tipman': ['pitman', 'tampin', 'tipman'], + 'tipper': ['rippet', 'tipper'], + 'tippler': ['ripplet', 'tippler', 'tripple'], + 'tipster': ['spitter', 'tipster'], + 'tipstock': ['potstick', 'tipstock'], + 'tipula': ['tipula', 'tulipa'], + 'tiralee': ['atelier', 'tiralee'], + 'tire': ['iter', 'reit', 'rite', 'teri', 'tier', 'tire'], + 'tired': ['diter', 'tired', 'tried'], + 'tiredly': ['tiredly', 'triedly'], + 'tiredness': ['dissenter', 'tiredness'], + 'tireless': ['riteless', 'tireless'], + 'tirelessness': ['ritelessness', 'tirelessness'], + 'tiremaid': ['dimetria', 'mitridae', 'tiremaid', 'triamide'], + 'tireman': ['minaret', 'raiment', 'tireman'], + 'tirer': ['terri', 'tirer', 'trier'], + 'tiresmith': ['tiresmith', 'tritheism'], + 'tiresome': ['rosetime', 'timorese', 'tiresome'], + 'tirma': ['mitra', 'tarmi', 'timar', 'tirma'], + 'tirolean': ['oriental', 'relation', 'tirolean'], + 'tirolese': ['literose', 'roselite', 'tirolese'], + 'tirve': ['rivet', 'tirve', 'tiver'], + 'tisane': ['satine', 'tisane'], + 'tisar': ['arist', + 'astir', + 'sitar', + 'stair', + 'stria', + 'tarsi', + 'tisar', + 'trias'], + 'titan': ['taint', 'tanti', 'tinta', 'titan'], + 'titaness': ['antistes', 'titaness'], + 'titanic': ['tanitic', 'titanic'], + 'titano': ['otiant', 'titano'], + 'titanocolumbate': ['columbotitanate', 'titanocolumbate'], + 'titanofluoride': ['rotundifoliate', 'titanofluoride'], + 'titanosaur': ['saturation', 'titanosaur'], + 'titanosilicate': ['silicotitanate', 'titanosilicate'], + 'titanous': ['outsaint', 'titanous'], + 'titanyl': ['nattily', 'titanyl'], + 'titar': ['ratti', 'titar', 'trait'], + 'titer': ['titer', 'titre', 'trite'], + 'tithable': ['hittable', 'tithable'], + 'tither': ['hitter', 'tither'], + 'tithonic': ['chinotti', 'tithonic'], + 'titien': ['tintie', 'titien'], + 'titleboard': ['titleboard', 'trilobated'], + 'titler': ['litter', 'tilter', 'titler'], + 'titling': ['tilting', 'titling', 'tlingit'], + 'titrate': ['attrite', 'titrate'], + 'titration': ['attrition', 'titration'], + 'titre': ['titer', 'titre', 'trite'], + 'tiver': ['rivet', 'tirve', 'tiver'], + 'tiza': ['itza', 'tiza', 'zati'], + 'tlaco': ['lacto', 'tlaco'], + 'tlingit': ['tilting', 'titling', 'tlingit'], + 'tmesis': ['misset', 'tmesis'], + 'toa': ['oat', 'tao', 'toa'], + 'toad': ['doat', 'toad', 'toda'], + 'toader': ['doater', 'toader'], + 'toadflower': ['floodwater', 'toadflower', 'waterflood'], + 'toadier': ['roadite', 'toadier'], + 'toadish': ['doatish', 'toadish'], + 'toady': ['toady', 'today'], + 'toadyish': ['toadyish', 'todayish'], + 'toag': ['goat', 'toag', 'toga'], + 'toast': ['stoat', 'toast'], + 'toaster': ['retoast', 'rosetta', 'stoater', 'toaster'], + 'toba': ['boat', 'bota', 'toba'], + 'tobe': ['bote', 'tobe'], + 'tobiah': ['bhotia', 'tobiah'], + 'tobine': ['botein', 'tobine'], + 'toccata': ['attacco', 'toccata'], + 'tocharese': ['escheator', 'tocharese'], + 'tocharian': ['archontia', 'tocharian'], + 'tocharic': ['thoracic', 'tocharic', 'trochaic'], + 'tocher': ['hector', 'rochet', 'tocher', 'troche'], + 'toco': ['coot', 'coto', 'toco'], + 'tocogenetic': ['geotectonic', 'tocogenetic'], + 'tocometer': ['octometer', 'rectotome', 'tocometer'], + 'tocsin': ['nostic', 'sintoc', 'tocsin'], + 'tod': ['dot', 'tod'], + 'toda': ['doat', 'toad', 'toda'], + 'today': ['toady', 'today'], + 'todayish': ['toadyish', 'todayish'], + 'toddle': ['dodlet', 'toddle'], + 'tode': ['dote', 'tode', 'toed'], + 'todea': ['deota', 'todea'], + 'tody': ['doty', 'tody'], + 'toecap': ['capote', 'toecap'], + 'toed': ['dote', 'tode', 'toed'], + 'toeless': ['osselet', 'sestole', 'toeless'], + 'toenail': ['alnoite', 'elation', 'toenail'], + 'tog': ['got', 'tog'], + 'toga': ['goat', 'toag', 'toga'], + 'togaed': ['dogate', 'dotage', 'togaed'], + 'togalike': ['goatlike', 'togalike'], + 'toggel': ['goglet', 'toggel', 'toggle'], + 'toggle': ['goglet', 'toggel', 'toggle'], + 'togs': ['stog', 'togs'], + 'toher': ['other', 'thore', 'throe', 'toher'], + 'toho': ['hoot', 'thoo', 'toho'], + 'tohunga': ['hangout', 'tohunga'], + 'toi': ['ito', 'toi'], + 'toil': ['ilot', 'toil'], + 'toiler': ['loiter', 'toiler', 'triole'], + 'toilet': ['lottie', 'toilet', 'tolite'], + 'toiletry': ['toiletry', 'tyrolite'], + 'toise': ['sotie', 'toise'], + 'tokay': ['otyak', 'tokay'], + 'toke': ['keto', 'oket', 'toke'], + 'toko': ['koto', 'toko', 'took'], + 'tol': ['lot', 'tol'], + 'tolamine': ['lomatine', 'tolamine'], + 'tolan': ['notal', 'ontal', 'talon', 'tolan', 'tonal'], + 'tolane': ['etalon', 'tolane'], + 'told': ['dolt', 'told'], + 'tole': ['leto', 'lote', 'tole'], + 'toledan': ['taloned', 'toledan'], + 'toledo': ['toledo', 'toodle'], + 'tolerance': ['antrocele', 'coeternal', 'tolerance'], + 'tolerancy': ['alectryon', 'tolerancy'], + 'tolidine': ['lindoite', 'tolidine'], + 'tolite': ['lottie', 'toilet', 'tolite'], + 'tollery': ['tollery', 'trolley'], + 'tolly': ['tolly', 'tolyl'], + 'tolpatch': ['potlatch', 'tolpatch'], + 'tolsey': ['tolsey', 'tylose'], + 'tolter': ['lotter', 'rottle', 'tolter'], + 'tolu': ['lout', 'tolu'], + 'toluic': ['coutil', 'toluic'], + 'toluifera': ['foliature', 'toluifera'], + 'tolyl': ['tolly', 'tolyl'], + 'tom': ['mot', 'tom'], + 'toma': ['atmo', 'atom', 'moat', 'toma'], + 'toman': ['manto', 'toman'], + 'tomas': ['atmos', 'stoma', 'tomas'], + 'tombac': ['combat', 'tombac'], + 'tome': ['mote', 'tome'], + 'tomentose': ['metosteon', 'tomentose'], + 'tomial': ['lomita', 'tomial'], + 'tomin': ['minot', 'timon', 'tomin'], + 'tomographic': ['motographic', 'tomographic'], + 'tomorn': ['morton', 'tomorn'], + 'tomorrow': ['moorwort', 'rootworm', 'tomorrow', 'wormroot'], + 'ton': ['not', 'ton'], + 'tonal': ['notal', 'ontal', 'talon', 'tolan', 'tonal'], + 'tonalitive': ['levitation', 'tonalitive', 'velitation'], + 'tonation': ['notation', 'tonation'], + 'tone': ['note', 'tone'], + 'toned': ['donet', 'noted', 'toned'], + 'toneless': ['noteless', 'toneless'], + 'tonelessly': ['notelessly', 'tonelessly'], + 'tonelessness': ['notelessness', 'tonelessness'], + 'toner': ['noter', 'tenor', 'toner', 'trone'], + 'tonetic': ['entotic', 'tonetic'], + 'tonetics': ['stenotic', 'tonetics'], + 'tonga': ['tango', 'tonga'], + 'tongan': ['ganton', 'tongan'], + 'tongas': ['sontag', 'tongas'], + 'tonger': ['geront', 'tonger'], + 'tongrian': ['ignorant', 'tongrian'], + 'tongs': ['stong', 'tongs'], + 'tonicize': ['nicotize', 'tonicize'], + 'tonicoclonic': ['clonicotonic', 'tonicoclonic'], + 'tonify': ['notify', 'tonify'], + 'tonish': ['histon', 'shinto', 'tonish'], + 'tonk': ['knot', 'tonk'], + 'tonkin': ['inknot', 'tonkin'], + 'tonna': ['anton', 'notan', 'tonna'], + 'tonological': ['ontological', 'tonological'], + 'tonology': ['ontology', 'tonology'], + 'tonsorial': ['tonsorial', 'torsional'], + 'tonsure': ['snouter', 'tonsure', 'unstore'], + 'tonsured': ['tonsured', 'unsorted', 'unstored'], + 'tontine': ['nettion', 'tention', 'tontine'], + 'tonus': ['notus', 'snout', 'stoun', 'tonus'], + 'tony': ['tony', 'yont'], + 'too': ['oto', 'too'], + 'toodle': ['toledo', 'toodle'], + 'took': ['koto', 'toko', 'took'], + 'tool': ['loot', 'tool'], + 'tooler': ['looter', 'retool', 'rootle', 'tooler'], + 'tooling': ['ilongot', 'tooling'], + 'toom': ['moot', 'toom'], + 'toon': ['onto', 'oont', 'toon'], + 'toona': ['naoto', 'toona'], + 'toop': ['poot', 'toop', 'topo'], + 'toosh': ['shoot', 'sooth', 'sotho', 'toosh'], + 'toot': ['otto', 'toot', 'toto'], + 'toother': ['retooth', 'toother'], + 'toothpick': ['picktooth', 'toothpick'], + 'tootler': ['rootlet', 'tootler'], + 'top': ['opt', 'pot', 'top'], + 'toparch': ['caphtor', 'toparch'], + 'topass': ['potass', 'topass'], + 'topchrome': ['ectomorph', 'topchrome'], + 'tope': ['peto', 'poet', 'pote', 'tope'], + 'toper': ['poter', 'prote', 'repot', 'tepor', 'toper', 'trope'], + 'topfull': ['plotful', 'topfull'], + 'toph': ['phot', 'toph'], + 'tophus': ['tophus', 'upshot'], + 'topia': ['patio', 'taipo', 'topia'], + 'topiarist': ['parotitis', 'topiarist'], + 'topic': ['optic', 'picot', 'topic'], + 'topical': ['capitol', 'coalpit', 'optical', 'topical'], + 'topically': ['optically', 'topically'], + 'toplike': ['kitlope', 'potlike', 'toplike'], + 'topline': ['pointel', 'pontile', 'topline'], + 'topmaker': ['potmaker', 'topmaker'], + 'topmaking': ['potmaking', 'topmaking'], + 'topman': ['potman', 'tampon', 'topman'], + 'topmast': ['tapmost', 'topmast'], + 'topo': ['poot', 'toop', 'topo'], + 'topographics': ['coprophagist', 'topographics'], + 'topography': ['optography', 'topography'], + 'topological': ['optological', 'topological'], + 'topologist': ['optologist', 'topologist'], + 'topology': ['optology', 'topology'], + 'toponymal': ['monotypal', 'toponymal'], + 'toponymic': ['monotypic', 'toponymic'], + 'toponymical': ['monotypical', 'toponymical'], + 'topophone': ['optophone', 'topophone'], + 'topotype': ['optotype', 'topotype'], + 'topple': ['loppet', 'topple'], + 'toppler': ['preplot', 'toppler'], + 'toprail': ['portail', 'toprail'], + 'tops': ['post', 'spot', 'stop', 'tops'], + 'topsail': ['apostil', 'topsail'], + 'topside': ['deposit', 'topside'], + 'topsman': ['postman', 'topsman'], + 'topsoil': ['loopist', 'poloist', 'topsoil'], + 'topstone': ['potstone', 'topstone'], + 'toptail': ['ptilota', 'talipot', 'toptail'], + 'toque': ['quote', 'toque'], + 'tor': ['ort', 'rot', 'tor'], + 'tora': ['rota', 'taro', 'tora'], + 'toral': ['latro', 'rotal', 'toral'], + 'toran': ['orant', 'rotan', 'toran', 'trona'], + 'torbanite': ['abortient', 'torbanite'], + 'torcel': ['colter', 'lector', 'torcel'], + 'torch': ['chort', 'rotch', 'torch'], + 'tore': ['rote', 'tore'], + 'tored': ['doter', 'tored', 'trode'], + 'torenia': ['otarine', 'torenia'], + 'torero': ['reroot', 'rooter', 'torero'], + 'toreutics': ['tetricous', 'toreutics'], + 'torfel': ['floret', 'forlet', 'lofter', 'torfel'], + 'torgot': ['grotto', 'torgot'], + 'toric': ['toric', 'troic'], + 'torinese': ['serotine', 'torinese'], + 'torma': ['amort', 'morat', 'torma'], + 'tormen': ['mentor', 'merton', 'termon', 'tormen'], + 'tormina': ['amintor', 'tormina'], + 'torn': ['torn', 'tron'], + 'tornachile': ['chlorinate', 'ectorhinal', 'tornachile'], + 'tornado': ['donator', 'odorant', 'tornado'], + 'tornal': ['latron', 'lontar', 'tornal'], + 'tornaria': ['rotarian', 'tornaria'], + 'tornarian': ['narration', 'tornarian'], + 'tornese': ['enstore', 'estrone', 'storeen', 'tornese'], + 'torney': ['torney', 'tyrone'], + 'tornit': ['intort', 'tornit', 'triton'], + 'tornus': ['tornus', 'unsort'], + 'toro': ['root', 'roto', 'toro'], + 'torose': ['seroot', 'sooter', 'torose'], + 'torpent': ['portent', 'torpent'], + 'torpescent': ['precontest', 'torpescent'], + 'torpid': ['torpid', 'tripod'], + 'torpify': ['portify', 'torpify'], + 'torpor': ['portor', 'torpor'], + 'torque': ['quoter', 'roquet', 'torque'], + 'torques': ['questor', 'torques'], + 'torrubia': ['rubiator', 'torrubia'], + 'torsade': ['rosated', 'torsade'], + 'torse': ['roset', 'rotse', 'soter', 'stero', 'store', 'torse'], + 'torsel': ['relost', 'reslot', 'rostel', 'sterol', 'torsel'], + 'torsile': ['estriol', 'torsile'], + 'torsion': ['isotron', 'torsion'], + 'torsional': ['tonsorial', 'torsional'], + 'torsk': ['stork', 'torsk'], + 'torso': ['roost', 'torso'], + 'torsten': ['snotter', 'stentor', 'torsten'], + 'tort': ['tort', 'trot'], + 'torta': ['ottar', 'tarot', 'torta', 'troat'], + 'torteau': ['outrate', 'outtear', 'torteau'], + 'torticone': ['torticone', 'tritocone'], + 'tortile': ['lotrite', 'tortile', 'triolet'], + 'tortilla': ['littoral', 'tortilla'], + 'tortonian': ['intonator', 'tortonian'], + 'tortrices': ['tortrices', 'trisector'], + 'torture': ['torture', 'trouter', 'tutorer'], + 'toru': ['rout', 'toru', 'tour'], + 'torula': ['rotula', 'torula'], + 'torulaform': ['formulator', 'torulaform'], + 'toruliform': ['rotuliform', 'toruliform'], + 'torulose': ['outsoler', 'torulose'], + 'torulus': ['rotulus', 'torulus'], + 'torus': ['roust', 'rusot', 'stour', 'sutor', 'torus'], + 'torve': ['overt', 'rovet', 'torve', 'trove', 'voter'], + 'tory': ['royt', 'ryot', 'tory', 'troy', 'tyro'], + 'toryish': ['history', 'toryish'], + 'toryism': ['toryism', 'trisomy'], + 'tosephtas': ['posthaste', 'tosephtas'], + 'tosh': ['host', 'shot', 'thos', 'tosh'], + 'tosher': ['hoster', 'tosher'], + 'toshly': ['hostly', 'toshly'], + 'toshnail': ['histonal', 'toshnail'], + 'toss': ['sots', 'toss'], + 'tosser': ['retoss', 'tosser'], + 'tossily': ['tossily', 'tylosis'], + 'tossup': ['tossup', 'uptoss'], + 'tost': ['stot', 'tost'], + 'total': ['lotta', 'total'], + 'totanine': ['intonate', 'totanine'], + 'totaquin': ['quintato', 'totaquin'], + 'totchka': ['hattock', 'totchka'], + 'totem': ['motet', 'motte', 'totem'], + 'toter': ['ortet', 'otter', 'toter'], + 'tother': ['hotter', 'tother'], + 'toto': ['otto', 'toot', 'toto'], + 'toty': ['toty', 'tyto'], + 'tou': ['out', 'tou'], + 'toucan': ['toucan', 'tucano', 'uncoat'], + 'touch': ['couth', 'thuoc', 'touch'], + 'toucher': ['retouch', 'toucher'], + 'touchily': ['couthily', 'touchily'], + 'touchiness': ['couthiness', 'touchiness'], + 'touching': ['touching', 'ungothic'], + 'touchless': ['couthless', 'touchless'], + 'toug': ['gout', 'toug'], + 'tough': ['ought', 'tough'], + 'toughness': ['oughtness', 'toughness'], + 'toup': ['pout', 'toup'], + 'tour': ['rout', 'toru', 'tour'], + 'tourer': ['retour', 'router', 'tourer'], + 'touring': ['outgrin', 'outring', 'routing', 'touring'], + 'tourism': ['sumitro', 'tourism'], + 'touristy': ['touristy', 'yttrious'], + 'tourmalinic': ['latrocinium', 'tourmalinic'], + 'tournamental': ['tournamental', 'ultramontane'], + 'tourte': ['tourte', 'touter'], + 'tousche': ['souchet', 'techous', 'tousche'], + 'touser': ['ouster', 'souter', 'touser', 'trouse'], + 'tousle': ['lutose', 'solute', 'tousle'], + 'touter': ['tourte', 'touter'], + 'tovaria': ['aviator', 'tovaria'], + 'tow': ['tow', 'two', 'wot'], + 'towel': ['owlet', 'towel'], + 'tower': ['rowet', 'tower', 'wrote'], + 'town': ['nowt', 'town', 'wont'], + 'towned': ['towned', 'wonted'], + 'towser': ['restow', 'stower', 'towser', 'worset'], + 'towy': ['towy', 'yowt'], + 'toxemia': ['oximate', 'toxemia'], + 'toy': ['toy', 'yot'], + 'toyer': ['royet', 'toyer'], + 'toyful': ['outfly', 'toyful'], + 'toyless': ['systole', 'toyless'], + 'toysome': ['myosote', 'toysome'], + 'tozer': ['terzo', 'tozer'], + 'tra': ['art', 'rat', 'tar', 'tra'], + 'trabea': ['abater', 'artabe', 'eartab', 'trabea'], + 'trace': ['caret', + 'carte', + 'cater', + 'crate', + 'creat', + 'creta', + 'react', + 'recta', + 'trace'], + 'traceable': ['creatable', 'traceable'], + 'tracer': ['arrect', 'carter', 'crater', 'recart', 'tracer'], + 'tracheata': ['cathartae', 'tracheata'], + 'trachelitis': ['thersitical', 'trachelitis'], + 'tracheolaryngotomy': ['laryngotracheotomy', 'tracheolaryngotomy'], + 'trachinoid': ['anhidrotic', 'trachinoid'], + 'trachitis': ['citharist', 'trachitis'], + 'trachle': ['clethra', 'latcher', 'ratchel', 'relatch', 'talcher', 'trachle'], + 'trachoma': ['achromat', 'trachoma'], + 'trachylinae': ['chatelainry', 'trachylinae'], + 'trachyte': ['chattery', 'ratchety', 'trachyte'], + 'tracker': ['retrack', 'tracker'], + 'trackside': ['sidetrack', 'trackside'], + 'tractator': ['attractor', 'tractator'], + 'tractile': ['tetrical', 'tractile'], + 'tracy': ['carty', 'tracy'], + 'trade': ['dater', 'derat', 'detar', 'drate', 'rated', 'trade', 'tread'], + 'trader': ['darter', + 'dartre', + 'redart', + 'retard', + 'retrad', + 'tarred', + 'trader'], + 'trading': ['darting', 'trading'], + 'tradite': ['attired', 'tradite'], + 'traditioner': ['retradition', 'traditioner'], + 'traditionism': ['mistradition', 'traditionism'], + 'traditorship': ['podarthritis', 'traditorship'], + 'traducent': ['reductant', 'traducent', 'truncated'], + 'trady': ['tardy', 'trady'], + 'trag': ['grat', 'trag'], + 'tragedial': ['taligrade', 'tragedial'], + 'tragicomedy': ['comitragedy', 'tragicomedy'], + 'tragulina': ['tragulina', 'triangula'], + 'traguline': ['granulite', 'traguline'], + 'trah': ['hart', 'rath', 'tahr', 'thar', 'trah'], + 'traheen': ['earthen', 'enheart', 'hearten', 'naether', 'teheran', 'traheen'], + 'traik': ['kitar', 'krait', 'rakit', 'traik'], + 'trail': ['litra', 'trail', 'trial'], + 'trailer': ['retiral', 'retrial', 'trailer'], + 'trailery': ['literary', 'trailery'], + 'trailing': ['ringtail', 'trailing'], + 'trailside': ['dialister', 'trailside'], + 'train': ['riant', 'tairn', 'tarin', 'train'], + 'trainable': ['albertina', 'trainable'], + 'trainage': ['antiager', 'trainage'], + 'trainboy': ['bonitary', 'trainboy'], + 'trained': ['antired', 'detrain', 'randite', 'trained'], + 'trainee': ['enteria', 'trainee', 'triaene'], + 'trainer': ['arterin', 'retrain', 'terrain', 'trainer'], + 'trainless': ['sternalis', 'trainless'], + 'trainster': ['restraint', 'retransit', 'trainster', 'transiter'], + 'traintime': ['intimater', 'traintime'], + 'trainy': ['rytina', 'trainy', 'tyrian'], + 'traipse': ['piaster', 'piastre', 'raspite', 'spirate', 'traipse'], + 'trait': ['ratti', 'titar', 'trait'], + 'tram': ['mart', 'tram'], + 'trama': ['matar', 'matra', 'trama'], + 'tramal': ['matral', 'tramal'], + 'trame': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'trametes': ['teamster', 'trametes'], + 'tramline': ['terminal', 'tramline'], + 'tramper': ['retramp', 'tramper'], + 'trample': ['templar', 'trample'], + 'trampoline': ['intemporal', 'trampoline'], + 'tran': ['natr', 'rant', 'tarn', 'tran'], + 'trance': ['canter', + 'creant', + 'cretan', + 'nectar', + 'recant', + 'tanrec', + 'trance'], + 'tranced': ['cantred', 'centrad', 'tranced'], + 'trancelike': ['nectarlike', 'trancelike'], + 'trankum': ['trankum', 'turkman'], + 'transamination': ['transamination', 'transanimation'], + 'transanimation': ['transamination', 'transanimation'], + 'transept': ['prestant', 'transept'], + 'transeptally': ['platysternal', 'transeptally'], + 'transformer': ['retransform', 'transformer'], + 'transfuge': ['afterguns', 'transfuge'], + 'transient': ['instanter', 'transient'], + 'transigent': ['astringent', 'transigent'], + 'transimpression': ['pretransmission', 'transimpression'], + 'transire': ['restrain', 'strainer', 'transire'], + 'transit': ['straint', 'transit', 'tristan'], + 'transiter': ['restraint', 'retransit', 'trainster', 'transiter'], + 'transitive': ['revisitant', 'transitive'], + 'transmarine': ['strainerman', 'transmarine'], + 'transmit': ['tantrism', 'transmit'], + 'transmold': ['landstorm', 'transmold'], + 'transoceanic': ['narcaciontes', 'transoceanic'], + 'transonic': ['constrain', 'transonic'], + 'transpire': ['prestrain', 'transpire'], + 'transplanter': ['retransplant', 'transplanter'], + 'transportee': ['paternoster', 'prosternate', 'transportee'], + 'transporter': ['retransport', 'transporter'], + 'transpose': ['patroness', 'transpose'], + 'transposer': ['transposer', 'transprose'], + 'transprose': ['transposer', 'transprose'], + 'trap': ['part', 'prat', 'rapt', 'tarp', 'trap'], + 'trapa': ['apart', 'trapa'], + 'trapes': ['paster', 'repast', 'trapes'], + 'trapfall': ['pratfall', 'trapfall'], + 'traphole': ['plethora', 'traphole'], + 'trappean': ['apparent', 'trappean'], + 'traps': ['spart', 'sprat', 'strap', 'traps'], + 'traship': ['harpist', 'traship'], + 'trasy': ['satyr', 'stary', 'stray', 'trasy'], + 'traulism': ['altruism', 'muralist', 'traulism', 'ultraism'], + 'trauma': ['taruma', 'trauma'], + 'travale': ['larvate', 'lavaret', 'travale'], + 'trave': ['avert', 'tarve', 'taver', 'trave'], + 'travel': ['travel', 'varlet'], + 'traveler': ['retravel', 'revertal', 'traveler'], + 'traversion': ['overstrain', 'traversion'], + 'travertine': ['travertine', 'trinervate'], + 'travoy': ['travoy', 'votary'], + 'tray': ['arty', 'atry', 'tray'], + 'treacle': ['electra', 'treacle'], + 'tread': ['dater', 'derat', 'detar', 'drate', 'rated', 'trade', 'tread'], + 'treader': ['derater', 'retrade', 'retread', 'treader'], + 'treading': ['gradient', 'treading'], + 'treadle': ['delater', 'related', 'treadle'], + 'treason': ['noreast', 'rosetan', 'seatron', 'senator', 'treason'], + 'treasonish': ['astonisher', 'reastonish', 'treasonish'], + 'treasonist': ['steatornis', 'treasonist'], + 'treasonous': ['anoestrous', 'treasonous'], + 'treasurer': ['serrature', 'treasurer'], + 'treat': ['atter', 'tater', 'teart', 'tetra', 'treat'], + 'treatably': ['tabletary', 'treatably'], + 'treatee': ['ateeter', 'treatee'], + 'treater': ['ettarre', 'retreat', 'treater'], + 'treatise': ['estriate', 'treatise'], + 'treaty': ['attery', 'treaty', 'yatter'], + 'treble': ['belter', 'elbert', 'treble'], + 'treculia': ['arculite', 'cutleria', 'lucretia', 'reticula', 'treculia'], + 'tree': ['reet', 'teer', 'tree'], + 'treed': ['deter', 'treed'], + 'treeful': ['fleuret', 'treeful'], + 'treehood': ['theodore', 'treehood'], + 'treemaker': ['marketeer', 'treemaker'], + 'treeman': ['remanet', 'remeant', 'treeman'], + 'treen': ['enter', 'neter', 'renet', 'terne', 'treen'], + 'treenail': ['elaterin', 'entailer', 'treenail'], + 'treeship': ['hepteris', 'treeship'], + 'tref': ['fret', 'reft', 'tref'], + 'trefle': ['felter', 'telfer', 'trefle'], + 'trellis': ['stiller', 'trellis'], + 'trema': ['armet', + 'mater', + 'merat', + 'metra', + 'ramet', + 'tamer', + 'terma', + 'trame', + 'trema'], + 'trematoid': ['meditator', 'trematoid'], + 'tremella': ['realmlet', 'tremella'], + 'tremie': ['metier', 'retime', 'tremie'], + 'tremolo': ['roomlet', 'tremolo'], + 'tremor': ['termor', 'tremor'], + 'trenail': ['entrail', + 'latiner', + 'latrine', + 'ratline', + 'reliant', + 'retinal', + 'trenail'], + 'trenchant': ['centranth', 'trenchant'], + 'trencher': ['retrench', 'trencher'], + 'trenchmaster': ['stretcherman', 'trenchmaster'], + 'trenchwise': ['trenchwise', 'winchester'], + 'trentine': ['renitent', 'trentine'], + 'trepan': ['arpent', + 'enrapt', + 'entrap', + 'panter', + 'parent', + 'pretan', + 'trepan'], + 'trephine': ['nephrite', 'prehnite', 'trephine'], + 'trepid': ['dipter', 'trepid'], + 'trepidation': ['departition', 'partitioned', 'trepidation'], + 'treron': ['terron', 'treron', 'troner'], + 'treronidae': ['reordinate', 'treronidae'], + 'tressed': ['dessert', 'tressed'], + 'tressful': ['tressful', 'turfless'], + 'tressour': ['tressour', 'trousers'], + 'trest': ['stert', 'stret', 'trest'], + 'trestle': ['settler', 'sterlet', 'trestle'], + 'trevor': ['trevor', 'trover'], + 'trews': ['strew', 'trews', 'wrest'], + 'trey': ['trey', 'tyre'], + 'tri': ['rit', 'tri'], + 'triable': ['betrail', 'librate', 'triable', 'trilabe'], + 'triace': ['acrite', 'arcite', 'tercia', 'triace', 'tricae'], + 'triacid': ['arctiid', 'triacid', 'triadic'], + 'triacontane': ['recantation', 'triacontane'], + 'triaconter': ['retraction', 'triaconter'], + 'triactine': ['intricate', 'triactine'], + 'triadic': ['arctiid', 'triacid', 'triadic'], + 'triadical': ['raticidal', 'triadical'], + 'triadist': ['distrait', 'triadist'], + 'triaene': ['enteria', 'trainee', 'triaene'], + 'triage': ['gaiter', 'tairge', 'triage'], + 'trial': ['litra', 'trail', 'trial'], + 'trialism': ['mistrial', 'trialism'], + 'trialist': ['taistril', 'trialist'], + 'triamide': ['dimetria', 'mitridae', 'tiremaid', 'triamide'], + 'triamino': ['miniator', 'triamino'], + 'triandria': ['irradiant', 'triandria'], + 'triangle': ['integral', 'teraglin', 'triangle'], + 'triangula': ['tragulina', 'triangula'], + 'triannual': ['innatural', 'triannual'], + 'triannulate': ['antineutral', 'triannulate'], + 'triantelope': ['interpolate', 'triantelope'], + 'triapsidal': ['lapidarist', 'triapsidal'], + 'triareal': ['arterial', 'triareal'], + 'trias': ['arist', + 'astir', + 'sitar', + 'stair', + 'stria', + 'tarsi', + 'tisar', + 'trias'], + 'triassic': ['sarcitis', 'triassic'], + 'triazane': ['nazarite', 'nazirate', 'triazane'], + 'triazine': ['nazirite', 'triazine'], + 'tribade': ['redbait', 'tribade'], + 'tribase': ['baister', 'tribase'], + 'tribe': ['biter', 'tribe'], + 'tribelet': ['belitter', 'tribelet'], + 'triblet': ['blitter', 'brittle', 'triblet'], + 'tribonema': ['brominate', 'tribonema'], + 'tribuna': ['arbutin', 'tribuna'], + 'tribunal': ['tribunal', 'turbinal', 'untribal'], + 'tribunate': ['tribunate', 'turbinate'], + 'tribune': ['tribune', 'tuberin', 'turbine'], + 'tricae': ['acrite', 'arcite', 'tercia', 'triace', 'tricae'], + 'trice': ['citer', 'recti', 'ticer', 'trice'], + 'tricennial': ['encrinital', 'tricennial'], + 'triceratops': ['tetrasporic', 'triceratops'], + 'triceria': ['criteria', 'triceria'], + 'tricerion': ['criterion', 'tricerion'], + 'tricerium': ['criterium', 'tricerium'], + 'trichinous': ['trichinous', 'unhistoric'], + 'trichogyne': ['thyrogenic', 'trichogyne'], + 'trichoid': ['hidrotic', 'trichoid'], + 'trichomanes': ['anchoretism', 'trichomanes'], + 'trichome': ['chromite', 'trichome'], + 'trichopore': ['horopteric', 'rheotropic', 'trichopore'], + 'trichosis': ['historics', 'trichosis'], + 'trichosporum': ['sporotrichum', 'trichosporum'], + 'trichroic': ['cirrhotic', 'trichroic'], + 'trichroism': ['trichroism', 'triorchism'], + 'trichromic': ['microcrith', 'trichromic'], + 'tricia': ['iatric', 'tricia'], + 'trickle': ['tickler', 'trickle'], + 'triclinate': ['intractile', 'triclinate'], + 'tricolumnar': ['tricolumnar', 'ultramicron'], + 'tricosane': ['atroscine', 'certosina', 'ostracine', 'tinoceras', 'tricosane'], + 'tridacna': ['antacrid', 'cardiant', 'radicant', 'tridacna'], + 'tridecane': ['nectaried', 'tridecane'], + 'tridecene': ['intercede', 'tridecene'], + 'tridecyl': ['directly', 'tridecyl'], + 'tridiapason': ['disparation', 'tridiapason'], + 'tried': ['diter', 'tired', 'tried'], + 'triedly': ['tiredly', 'triedly'], + 'triene': ['entire', 'triene'], + 'triens': ['estrin', 'insert', 'sinter', 'sterin', 'triens'], + 'triental': ['tetralin', 'triental'], + 'triequal': ['quartile', 'requital', 'triequal'], + 'trier': ['terri', 'tirer', 'trier'], + 'trifle': ['fertil', 'filter', 'lifter', 'relift', 'trifle'], + 'trifler': ['flirter', 'trifler'], + 'triflet': ['flitter', 'triflet'], + 'trifling': ['flirting', 'trifling'], + 'triflingly': ['flirtingly', 'triflingly'], + 'trifolium': ['lituiform', 'trifolium'], + 'trig': ['girt', 'grit', 'trig'], + 'trigona': ['grotian', 'trigona'], + 'trigone': ['ergotin', 'genitor', 'negrito', 'ogtiern', 'trigone'], + 'trigonia': ['rigation', 'trigonia'], + 'trigonid': ['trigonid', 'tringoid'], + 'trigyn': ['trigyn', 'trying'], + 'trilabe': ['betrail', 'librate', 'triable', 'trilabe'], + 'trilineate': ['retinalite', 'trilineate'], + 'trilisa': ['liatris', 'trilisa'], + 'trillet': ['rillett', 'trillet'], + 'trilobate': ['latrobite', 'trilobate'], + 'trilobated': ['titleboard', 'trilobated'], + 'trimacular': ['matricular', 'trimacular'], + 'trimensual': ['neutralism', 'trimensual'], + 'trimer': ['mitrer', 'retrim', 'trimer'], + 'trimesic': ['meristic', 'trimesic', 'trisemic'], + 'trimesitinic': ['interimistic', 'trimesitinic'], + 'trimesyl': ['trimesyl', 'tylerism'], + 'trimeter': ['remitter', 'trimeter'], + 'trimstone': ['sortiment', 'trimstone'], + 'trinalize': ['latinizer', 'trinalize'], + 'trindle': ['tendril', 'trindle'], + 'trine': ['inert', 'inter', 'niter', 'retin', 'trine'], + 'trinely': ['elytrin', 'inertly', 'trinely'], + 'trinervate': ['travertine', 'trinervate'], + 'trinerve': ['inverter', 'reinvert', 'trinerve'], + 'trineural': ['retinular', 'trineural'], + 'tringa': ['rating', 'tringa'], + 'tringle': ['ringlet', 'tingler', 'tringle'], + 'tringoid': ['trigonid', 'tringoid'], + 'trinket': ['knitter', 'trinket'], + 'trinkle': ['tinkler', 'trinkle'], + 'trinoctial': ['tinctorial', 'trinoctial'], + 'trinodine': ['rendition', 'trinodine'], + 'trintle': ['lettrin', 'trintle'], + 'trio': ['riot', 'roit', 'trio'], + 'triode': ['editor', 'triode'], + 'trioecism': ['eroticism', 'isometric', 'meroistic', 'trioecism'], + 'triole': ['loiter', 'toiler', 'triole'], + 'trioleic': ['elicitor', 'trioleic'], + 'triolet': ['lotrite', 'tortile', 'triolet'], + 'trionymal': ['normality', 'trionymal'], + 'triopidae': ['poritidae', 'triopidae'], + 'triops': ['ripost', 'triops', 'tripos'], + 'triorchism': ['trichroism', 'triorchism'], + 'triose': ['restio', 'sorite', 'sortie', 'triose'], + 'tripe': ['perit', 'retip', 'tripe'], + 'tripedal': ['dipteral', 'tripedal'], + 'tripel': ['tripel', 'triple'], + 'tripeman': ['imperant', 'pairment', 'partimen', 'premiant', 'tripeman'], + 'tripersonal': ['intersporal', 'tripersonal'], + 'tripestone': ['septentrio', 'tripestone'], + 'triphane': ['perianth', 'triphane'], + 'triplane': ['interlap', 'repliant', 'triplane'], + 'triplasian': ['airplanist', 'triplasian'], + 'triplasic': ['pilastric', 'triplasic'], + 'triple': ['tripel', 'triple'], + 'triplice': ['perlitic', 'triplice'], + 'triplopia': ['propitial', 'triplopia'], + 'tripod': ['torpid', 'tripod'], + 'tripodal': ['dioptral', 'tripodal'], + 'tripodic': ['dioptric', 'tripodic'], + 'tripodical': ['dioptrical', 'tripodical'], + 'tripody': ['dioptry', 'tripody'], + 'tripos': ['ripost', 'triops', 'tripos'], + 'trippist': ['strippit', 'trippist'], + 'tripple': ['ripplet', 'tippler', 'tripple'], + 'tripsis': ['pristis', 'tripsis'], + 'tripsome': ['imposter', 'tripsome'], + 'tripudiant': ['antiputrid', 'tripudiant'], + 'tripyrenous': ['neurotripsy', 'tripyrenous'], + 'triratna': ['tartarin', 'triratna'], + 'trireme': ['meriter', 'miterer', 'trireme'], + 'trisalt': ['starlit', 'trisalt'], + 'trisected': ['decretist', 'trisected'], + 'trisector': ['tortrices', 'trisector'], + 'trisemic': ['meristic', 'trimesic', 'trisemic'], + 'trisetose': ['esoterist', 'trisetose'], + 'trishna': ['tarnish', 'trishna'], + 'trisilane': ['listerian', 'trisilane'], + 'triskele': ['kreistle', 'triskele'], + 'trismus': ['sistrum', 'trismus'], + 'trisome': ['erotism', 'mortise', 'trisome'], + 'trisomy': ['toryism', 'trisomy'], + 'trisonant': ['strontian', 'trisonant'], + 'trispinose': ['pirssonite', 'trispinose'], + 'trist': ['strit', 'trist'], + 'tristan': ['straint', 'transit', 'tristan'], + 'trisula': ['latirus', 'trisula'], + 'trisulcate': ['testicular', 'trisulcate'], + 'tritanope': ['antitrope', 'patronite', 'tritanope'], + 'tritanopic': ['antitropic', 'tritanopic'], + 'trite': ['titer', 'titre', 'trite'], + 'tritely': ['littery', 'tritely'], + 'triterpene': ['preterient', 'triterpene'], + 'tritheism': ['tiresmith', 'tritheism'], + 'trithionate': ['anorthitite', 'trithionate'], + 'tritocone': ['torticone', 'tritocone'], + 'tritoma': ['mattoir', 'tritoma'], + 'triton': ['intort', 'tornit', 'triton'], + 'triune': ['runite', 'triune', 'uniter', 'untire'], + 'trivalence': ['cantilever', 'trivalence'], + 'trivial': ['trivial', 'vitrail'], + 'trivialist': ['trivialist', 'vitrailist'], + 'troat': ['ottar', 'tarot', 'torta', 'troat'], + 'troca': ['actor', 'corta', 'croat', 'rocta', 'taroc', 'troca'], + 'trocar': ['carrot', 'trocar'], + 'trochaic': ['thoracic', 'tocharic', 'trochaic'], + 'trochate': ['theocrat', 'trochate'], + 'troche': ['hector', 'rochet', 'tocher', 'troche'], + 'trochi': ['chorti', 'orthic', 'thoric', 'trochi'], + 'trochidae': ['charioted', 'trochidae'], + 'trochila': ['acrolith', 'trochila'], + 'trochilic': ['chloritic', 'trochilic'], + 'trochlea': ['chlorate', 'trochlea'], + 'trochlearis': ['rhetoricals', 'trochlearis'], + 'trode': ['doter', 'tored', 'trode'], + 'trog': ['grot', 'trog'], + 'trogonidae': ['derogation', 'trogonidae'], + 'troiades': ['asteroid', 'troiades'], + 'troic': ['toric', 'troic'], + 'troika': ['korait', 'troika'], + 'trolley': ['tollery', 'trolley'], + 'tromba': ['tambor', 'tromba'], + 'trombe': ['retomb', 'trombe'], + 'trompe': ['emptor', 'trompe'], + 'tron': ['torn', 'tron'], + 'trona': ['orant', 'rotan', 'toran', 'trona'], + 'tronage': ['negator', 'tronage'], + 'trone': ['noter', 'tenor', 'toner', 'trone'], + 'troner': ['terron', 'treron', 'troner'], + 'troop': ['porto', 'proto', 'troop'], + 'trooper': ['protore', 'trooper'], + 'tropaeolum': ['pleurotoma', 'tropaeolum'], + 'tropaion': ['opinator', 'tropaion'], + 'tropal': ['patrol', 'portal', 'tropal'], + 'troparion': ['proration', 'troparion'], + 'tropary': ['parroty', 'portray', 'tropary'], + 'trope': ['poter', 'prote', 'repot', 'tepor', 'toper', 'trope'], + 'tropeic': ['perotic', 'proteic', 'tropeic'], + 'tropeine': ['ereption', 'tropeine'], + 'troper': ['porret', 'porter', 'report', 'troper'], + 'trophema': ['metaphor', 'trophema'], + 'trophesial': ['hospitaler', 'trophesial'], + 'trophical': ['carpolith', 'politarch', 'trophical'], + 'trophodisc': ['doctorship', 'trophodisc'], + 'trophonema': ['homopteran', 'trophonema'], + 'trophotropic': ['prototrophic', 'trophotropic'], + 'tropical': ['plicator', 'tropical'], + 'tropically': ['polycitral', 'tropically'], + 'tropidine': ['direption', 'perdition', 'tropidine'], + 'tropine': ['pointer', 'protein', 'pterion', 'repoint', 'tropine'], + 'tropism': ['primost', 'tropism'], + 'tropist': ['protist', 'tropist'], + 'tropistic': ['proctitis', 'protistic', 'tropistic'], + 'tropophyte': ['protophyte', 'tropophyte'], + 'tropophytic': ['protophytic', 'tropophytic'], + 'tropyl': ['portly', 'protyl', 'tropyl'], + 'trostera': ['rostrate', 'trostera'], + 'trot': ['tort', 'trot'], + 'troth': ['thort', 'troth'], + 'trotline': ['interlot', 'trotline'], + 'trouble': ['boulter', 'trouble'], + 'troughy': ['troughy', 'yoghurt'], + 'trounce': ['cornute', 'counter', 'recount', 'trounce'], + 'troupe': ['pouter', 'roupet', 'troupe'], + 'trouse': ['ouster', 'souter', 'touser', 'trouse'], + 'trouser': ['rouster', 'trouser'], + 'trouserian': ['souterrain', 'ternarious', 'trouserian'], + 'trousers': ['tressour', 'trousers'], + 'trout': ['trout', 'tutor'], + 'trouter': ['torture', 'trouter', 'tutorer'], + 'troutless': ['troutless', 'tutorless'], + 'trouty': ['trouty', 'tryout', 'tutory'], + 'trouvere': ['overtrue', 'overture', 'trouvere'], + 'trove': ['overt', 'rovet', 'torve', 'trove', 'voter'], + 'trover': ['trevor', 'trover'], + 'trow': ['trow', 'wort'], + 'trowel': ['rowlet', 'trowel', 'wolter'], + 'troy': ['royt', 'ryot', 'tory', 'troy', 'tyro'], + 'truandise': ['disnature', 'sturnidae', 'truandise'], + 'truant': ['truant', 'turtan'], + 'trub': ['brut', 'burt', 'trub', 'turb'], + 'trubu': ['burut', 'trubu'], + 'truce': ['cruet', 'eruct', 'recut', 'truce'], + 'truceless': ['cutleress', 'lecturess', 'truceless'], + 'trucial': ['curtail', 'trucial'], + 'trucks': ['struck', 'trucks'], + 'truculent': ['truculent', 'unclutter'], + 'truelove': ['revolute', 'truelove'], + 'truffle': ['fretful', 'truffle'], + 'trug': ['gurt', 'trug'], + 'truistical': ['altruistic', 'truistical', 'ultraistic'], + 'truly': ['rutyl', 'truly'], + 'trumperiness': ['surprisement', 'trumperiness'], + 'trumpie': ['imputer', 'trumpie'], + 'trun': ['runt', 'trun', 'turn'], + 'truncated': ['reductant', 'traducent', 'truncated'], + 'trundle': ['rundlet', 'trundle'], + 'trush': ['hurst', 'trush'], + 'trusion': ['nitrous', 'trusion'], + 'trust': ['strut', 'sturt', 'trust'], + 'trustee': ['surette', 'trustee'], + 'trusteeism': ['sestertium', 'trusteeism'], + 'trusten': ['entrust', 'stunter', 'trusten'], + 'truster': ['retrust', 'truster'], + 'trustle': ['slutter', 'trustle'], + 'truth': ['thurt', 'truth'], + 'trying': ['trigyn', 'trying'], + 'tryma': ['marty', 'tryma'], + 'tryout': ['trouty', 'tryout', 'tutory'], + 'trypa': ['party', 'trypa'], + 'trypan': ['pantry', 'trypan'], + 'tryptase': ['tapestry', 'tryptase'], + 'tsar': ['sart', 'star', 'stra', 'tars', 'tsar'], + 'tsardom': ['stardom', 'tsardom'], + 'tsarina': ['artisan', 'astrain', 'sartain', 'tsarina'], + 'tsarship': ['starship', 'tsarship'], + 'tsatlee': ['atelets', 'tsatlee'], + 'tsere': ['ester', + 'estre', + 'reest', + 'reset', + 'steer', + 'stere', + 'stree', + 'terse', + 'tsere'], + 'tsetse': ['sestet', 'testes', 'tsetse'], + 'tshi': ['hist', 'sith', 'this', 'tshi'], + 'tsia': ['atis', 'sita', 'tsia'], + 'tsine': ['inset', 'neist', 'snite', 'stein', 'stine', 'tsine'], + 'tsiology': ['sitology', 'tsiology'], + 'tsoneca': ['costean', 'tsoneca'], + 'tsonecan': ['noncaste', 'tsonecan'], + 'tsuga': ['agust', 'tsuga'], + 'tsuma': ['matsu', 'tamus', 'tsuma'], + 'tsun': ['stun', 'sunt', 'tsun'], + 'tu': ['tu', 'ut'], + 'tua': ['tau', 'tua', 'uta'], + 'tuan': ['antu', 'aunt', 'naut', 'taun', 'tuan', 'tuna'], + 'tuareg': ['argute', 'guetar', 'rugate', 'tuareg'], + 'tuarn': ['arnut', 'tuarn', 'untar'], + 'tub': ['but', 'tub'], + 'tuba': ['abut', 'tabu', 'tuba'], + 'tubae': ['butea', 'taube', 'tubae'], + 'tubal': ['balut', 'tubal'], + 'tubar': ['bruta', 'tubar'], + 'tubate': ['battue', 'tubate'], + 'tube': ['bute', 'tebu', 'tube'], + 'tuber': ['brute', 'buret', 'rebut', 'tuber'], + 'tubercula': ['lucubrate', 'tubercula'], + 'tuberin': ['tribune', 'tuberin', 'turbine'], + 'tuberless': ['butleress', 'tuberless'], + 'tublet': ['buttle', 'tublet'], + 'tuboovarial': ['ovariotubal', 'tuboovarial'], + 'tucana': ['canaut', 'tucana'], + 'tucano': ['toucan', 'tucano', 'uncoat'], + 'tuchun': ['tuchun', 'uncuth'], + 'tucker': ['retuck', 'tucker'], + 'tue': ['tue', 'ute'], + 'tueiron': ['routine', 'tueiron'], + 'tug': ['gut', 'tug'], + 'tughra': ['raught', 'tughra'], + 'tugless': ['gutless', 'tugless'], + 'tuglike': ['gutlike', 'tuglike'], + 'tugman': ['tangum', 'tugman'], + 'tuism': ['muist', 'tuism'], + 'tuke': ['ketu', 'teuk', 'tuke'], + 'tukra': ['kraut', 'tukra'], + 'tulare': ['tulare', 'uretal'], + 'tulasi': ['situal', 'situla', 'tulasi'], + 'tulchan': ['tulchan', 'unlatch'], + 'tule': ['lute', 'tule'], + 'tulipa': ['tipula', 'tulipa'], + 'tulisan': ['latinus', 'tulisan', 'unalist'], + 'tulsi': ['litus', 'sluit', 'tulsi'], + 'tumbler': ['tumbler', 'tumbrel'], + 'tumbrel': ['tumbler', 'tumbrel'], + 'tume': ['mute', 'tume'], + 'tumescence': ['mutescence', 'tumescence'], + 'tumorous': ['mortuous', 'tumorous'], + 'tumulary': ['mutulary', 'tumulary'], + 'tun': ['nut', 'tun'], + 'tuna': ['antu', 'aunt', 'naut', 'taun', 'tuan', 'tuna'], + 'tunable': ['abluent', 'tunable'], + 'tunbellied': ['tunbellied', 'unbilleted'], + 'tunca': ['tunca', 'unact'], + 'tund': ['dunt', 'tund'], + 'tunder': ['runted', 'tunder', 'turned'], + 'tundra': ['durant', 'tundra'], + 'tuner': ['enrut', 'tuner', 'urent'], + 'tunga': ['gaunt', 'tunga'], + 'tungan': ['tangun', 'tungan'], + 'tungate': ['tungate', 'tutenag'], + 'tungo': ['tungo', 'ungot'], + 'tungstosilicate': ['silicotungstate', 'tungstosilicate'], + 'tungstosilicic': ['silicotungstic', 'tungstosilicic'], + 'tunic': ['cutin', 'incut', 'tunic'], + 'tunica': ['anicut', 'nautic', 'ticuna', 'tunica'], + 'tunican': ['ticunan', 'tunican'], + 'tunicary': ['nycturia', 'tunicary'], + 'tunicle': ['linecut', 'tunicle'], + 'tunicless': ['lentiscus', 'tunicless'], + 'tunist': ['suttin', 'tunist'], + 'tunk': ['knut', 'tunk'], + 'tunker': ['tunker', 'turken'], + 'tunlike': ['nutlike', 'tunlike'], + 'tunna': ['naunt', 'tunna'], + 'tunnel': ['nunlet', 'tunnel', 'unlent'], + 'tunnelman': ['annulment', 'tunnelman'], + 'tunner': ['runnet', 'tunner', 'unrent'], + 'tunnor': ['tunnor', 'untorn'], + 'tuno': ['tuno', 'unto'], + 'tup': ['put', 'tup'], + 'tur': ['rut', 'tur'], + 'turacin': ['curtain', 'turacin', 'turcian'], + 'turanian': ['nutarian', 'turanian'], + 'turanism': ['naturism', 'sturmian', 'turanism'], + 'turb': ['brut', 'burt', 'trub', 'turb'], + 'turban': ['tanbur', 'turban'], + 'turbaned': ['breadnut', 'turbaned'], + 'turbanless': ['substernal', 'turbanless'], + 'turbeh': ['hubert', 'turbeh'], + 'turbinal': ['tribunal', 'turbinal', 'untribal'], + 'turbinate': ['tribunate', 'turbinate'], + 'turbine': ['tribune', 'tuberin', 'turbine'], + 'turbined': ['turbined', 'underbit'], + 'turcian': ['curtain', 'turacin', 'turcian'], + 'turco': ['court', 'crout', 'turco'], + 'turcoman': ['courtman', 'turcoman'], + 'turdinae': ['indurate', 'turdinae'], + 'turdine': ['intrude', 'turdine', 'untired', 'untried'], + 'tureen': ['neuter', 'retune', 'runtee', 'tenure', 'tureen'], + 'turfed': ['dufter', 'turfed'], + 'turfen': ['turfen', 'unfret'], + 'turfless': ['tressful', 'turfless'], + 'turgent': ['grutten', 'turgent'], + 'turk': ['kurt', 'turk'], + 'turken': ['tunker', 'turken'], + 'turki': ['tikur', 'turki'], + 'turkman': ['trankum', 'turkman'], + 'turma': ['martu', 'murat', 'turma'], + 'turn': ['runt', 'trun', 'turn'], + 'turndown': ['downturn', 'turndown'], + 'turned': ['runted', 'tunder', 'turned'], + 'turnel': ['runlet', 'turnel'], + 'turner': ['return', 'turner'], + 'turnhall': ['turnhall', 'unthrall'], + 'turnout': ['outturn', 'turnout'], + 'turnover': ['overturn', 'turnover'], + 'turnpin': ['turnpin', 'unprint'], + 'turns': ['snurt', 'turns'], + 'turntail': ['rutilant', 'turntail'], + 'turnup': ['turnup', 'upturn'], + 'turp': ['prut', 'turp'], + 'turpid': ['putrid', 'turpid'], + 'turpidly': ['putridly', 'turpidly'], + 'turps': ['spurt', 'turps'], + 'turret': ['rutter', 'turret'], + 'turricula': ['turricula', 'utricular'], + 'turse': ['serut', 'strue', 'turse', 'uster'], + 'tursenoi': ['rutinose', 'tursenoi'], + 'tursio': ['suitor', 'tursio'], + 'turtan': ['truant', 'turtan'], + 'tuscan': ['cantus', 'tuscan', 'uncast'], + 'tusche': ['schute', 'tusche'], + 'tush': ['shut', 'thus', 'tush'], + 'tusher': ['reshut', 'suther', 'thurse', 'tusher'], + 'tussal': ['saltus', 'tussal'], + 'tusser': ['russet', 'tusser'], + 'tussore': ['estrous', 'oestrus', 'sestuor', 'tussore'], + 'tutelo': ['outlet', 'tutelo'], + 'tutenag': ['tungate', 'tutenag'], + 'tutman': ['mutant', 'tantum', 'tutman'], + 'tutor': ['trout', 'tutor'], + 'tutorer': ['torture', 'trouter', 'tutorer'], + 'tutorial': ['outtrail', 'tutorial'], + 'tutorism': ['mistutor', 'tutorism'], + 'tutorless': ['troutless', 'tutorless'], + 'tutory': ['trouty', 'tryout', 'tutory'], + 'tuts': ['stut', 'tuts'], + 'tutster': ['stutter', 'tutster'], + 'twa': ['taw', 'twa', 'wat'], + 'twae': ['tewa', 'twae', 'weta'], + 'twain': ['atwin', 'twain', 'witan'], + 'twaite': ['tawite', 'tawtie', 'twaite'], + 'twal': ['twal', 'walt'], + 'twas': ['sawt', 'staw', 'swat', 'taws', 'twas', 'wast'], + 'twat': ['twat', 'watt'], + 'twee': ['twee', 'weet'], + 'tweel': ['tewel', 'tweel'], + 'twere': ['rewet', 'tewer', 'twere'], + 'twi': ['twi', 'wit'], + 'twigsome': ['twigsome', 'wegotism'], + 'twin': ['twin', 'wint'], + 'twiner': ['twiner', 'winter'], + 'twingle': ['twingle', 'welting', 'winglet'], + 'twinkle': ['twinkle', 'winklet'], + 'twinkler': ['twinkler', 'wrinklet'], + 'twinter': ['twinter', 'written'], + 'twire': ['twire', 'write'], + 'twister': ['retwist', 'twister'], + 'twitchety': ['twitchety', 'witchetty'], + 'twite': ['tewit', 'twite'], + 'two': ['tow', 'two', 'wot'], + 'twoling': ['lingtow', 'twoling'], + 'tyche': ['techy', 'tyche'], + 'tydie': ['deity', 'tydie'], + 'tye': ['tye', 'yet'], + 'tyke': ['kyte', 'tyke'], + 'tylerism': ['trimesyl', 'tylerism'], + 'tyloma': ['latomy', 'tyloma'], + 'tylose': ['tolsey', 'tylose'], + 'tylosis': ['tossily', 'tylosis'], + 'tylotus': ['stoutly', 'tylotus'], + 'tylus': ['lusty', 'tylus'], + 'typal': ['aptly', 'patly', 'platy', 'typal'], + 'typees': ['steepy', 'typees'], + 'typer': ['perty', 'typer'], + 'typha': ['pathy', 'typha'], + 'typhia': ['pythia', 'typhia'], + 'typhic': ['phytic', 'pitchy', 'pythic', 'typhic'], + 'typhlopidae': ['heptaploidy', 'typhlopidae'], + 'typhoean': ['anophyte', 'typhoean'], + 'typhogenic': ['phytogenic', 'pythogenic', 'typhogenic'], + 'typhoid': ['phytoid', 'typhoid'], + 'typhonian': ['antiphony', 'typhonian'], + 'typhonic': ['hypnotic', 'phytonic', 'pythonic', 'typhonic'], + 'typhosis': ['phytosis', 'typhosis'], + 'typica': ['atypic', 'typica'], + 'typographer': ['petrography', 'pterography', 'typographer'], + 'typographic': ['graphotypic', 'pictography', 'typographic'], + 'typology': ['logotypy', 'typology'], + 'typophile': ['hippolyte', 'typophile'], + 'tyre': ['trey', 'tyre'], + 'tyrian': ['rytina', 'trainy', 'tyrian'], + 'tyro': ['royt', 'ryot', 'tory', 'troy', 'tyro'], + 'tyrocidin': ['nordicity', 'tyrocidin'], + 'tyrolean': ['neolatry', 'ornately', 'tyrolean'], + 'tyrolite': ['toiletry', 'tyrolite'], + 'tyrone': ['torney', 'tyrone'], + 'tyronism': ['smyrniot', 'tyronism'], + 'tyrosine': ['tyrosine', 'tyrsenoi'], + 'tyrrheni': ['erythrin', 'tyrrheni'], + 'tyrsenoi': ['tyrosine', 'tyrsenoi'], + 'tyste': ['testy', 'tyste'], + 'tyto': ['toty', 'tyto'], + 'uang': ['gaun', 'guan', 'guna', 'uang'], + 'ucal': ['caul', 'ucal'], + 'udal': ['auld', 'dual', 'laud', 'udal'], + 'udaler': ['lauder', 'udaler'], + 'udalman': ['ladanum', 'udalman'], + 'udo': ['duo', 'udo'], + 'uds': ['sud', 'uds'], + 'ugh': ['hug', 'ugh'], + 'uglisome': ['eulogism', 'uglisome'], + 'ugrian': ['gurian', 'ugrian'], + 'ugric': ['guric', 'ugric'], + 'uhtsong': ['gunshot', 'shotgun', 'uhtsong'], + 'uinal': ['inula', 'luian', 'uinal'], + 'uinta': ['uinta', 'uniat'], + 'ulcer': ['cruel', 'lucre', 'ulcer'], + 'ulcerate': ['celature', 'ulcerate'], + 'ulcerous': ['ulcerous', 'urceolus'], + 'ule': ['leu', 'lue', 'ule'], + 'ulema': ['amelu', 'leuma', 'ulema'], + 'uletic': ['lucite', 'luetic', 'uletic'], + 'ulex': ['luxe', 'ulex'], + 'ulla': ['lula', 'ulla'], + 'ulling': ['ulling', 'ungill'], + 'ulmin': ['linum', 'ulmin'], + 'ulminic': ['clinium', 'ulminic'], + 'ulmo': ['moul', 'ulmo'], + 'ulna': ['laun', 'luna', 'ulna', 'unal'], + 'ulnad': ['dunal', 'laund', 'lunda', 'ulnad'], + 'ulnar': ['lunar', 'ulnar', 'urnal'], + 'ulnare': ['lunare', 'neural', 'ulnare', 'unreal'], + 'ulnaria': ['lunaria', 'ulnaria', 'uralian'], + 'ulotrichi': ['ulotrichi', 'urolithic'], + 'ulster': ['luster', 'result', 'rustle', 'sutler', 'ulster'], + 'ulstered': ['deluster', 'ulstered'], + 'ulsterian': ['neuralist', 'ulsterian', 'unrealist'], + 'ulstering': ['resulting', 'ulstering'], + 'ulsterman': ['menstrual', 'ulsterman'], + 'ultima': ['mulita', 'ultima'], + 'ultimate': ['mutilate', 'ultimate'], + 'ultimation': ['mutilation', 'ultimation'], + 'ultonian': ['lunation', 'ultonian'], + 'ultra': ['lutra', 'ultra'], + 'ultrabasic': ['arcubalist', 'ultrabasic'], + 'ultraism': ['altruism', 'muralist', 'traulism', 'ultraism'], + 'ultraist': ['altruist', 'ultraist'], + 'ultraistic': ['altruistic', 'truistical', 'ultraistic'], + 'ultramicron': ['tricolumnar', 'ultramicron'], + 'ultraminute': ['intermutual', 'ultraminute'], + 'ultramontane': ['tournamental', 'ultramontane'], + 'ultranice': ['centurial', 'lucretian', 'ultranice'], + 'ultrasterile': ['reillustrate', 'ultrasterile'], + 'ulua': ['aulu', 'ulua'], + 'ulva': ['ulva', 'uval'], + 'um': ['mu', 'um'], + 'umbel': ['umbel', 'umble'], + 'umbellar': ['umbellar', 'umbrella'], + 'umber': ['brume', 'umber'], + 'umbilic': ['bulimic', 'umbilic'], + 'umbiliform': ['bulimiform', 'umbiliform'], + 'umble': ['umbel', 'umble'], + 'umbonial': ['olibanum', 'umbonial'], + 'umbral': ['brumal', 'labrum', 'lumbar', 'umbral'], + 'umbrel': ['lumber', 'rumble', 'umbrel'], + 'umbrella': ['umbellar', 'umbrella'], + 'umbrous': ['brumous', 'umbrous'], + 'ume': ['emu', 'ume'], + 'umlaut': ['mutual', 'umlaut'], + 'umph': ['hump', 'umph'], + 'umpire': ['impure', 'umpire'], + 'un': ['nu', 'un'], + 'unabetted': ['debutante', 'unabetted'], + 'unabhorred': ['unabhorred', 'unharbored'], + 'unable': ['nebula', 'unable', 'unbale'], + 'unaccumulate': ['acutenaculum', 'unaccumulate'], + 'unact': ['tunca', 'unact'], + 'unadherent': ['unadherent', 'underneath', 'underthane'], + 'unadmire': ['unadmire', 'underaim'], + 'unadmired': ['unadmired', 'undermaid'], + 'unadored': ['unadored', 'unroaded'], + 'unadvertised': ['disadventure', 'unadvertised'], + 'unafire': ['fuirena', 'unafire'], + 'unaged': ['augend', 'engaud', 'unaged'], + 'unagreed': ['dungaree', 'guardeen', 'unagreed', 'underage', 'ungeared'], + 'unailing': ['inguinal', 'unailing'], + 'unaimed': ['numidae', 'unaimed'], + 'unaisled': ['unaisled', 'unsailed'], + 'unakite': ['kutenai', 'unakite'], + 'unal': ['laun', 'luna', 'ulna', 'unal'], + 'unalarming': ['unalarming', 'unmarginal'], + 'unalert': ['laurent', 'neutral', 'unalert'], + 'unalertly': ['neutrally', 'unalertly'], + 'unalertness': ['neutralness', 'unalertness'], + 'unalimentary': ['anteluminary', 'unalimentary'], + 'unalist': ['latinus', 'tulisan', 'unalist'], + 'unallotted': ['unallotted', 'untotalled'], + 'unalmsed': ['dulseman', 'unalmsed'], + 'unaltered': ['unaltered', 'unrelated'], + 'unaltering': ['unaltering', 'unrelating'], + 'unamassed': ['mussaenda', 'unamassed'], + 'unambush': ['subhuman', 'unambush'], + 'unamenability': ['unamenability', 'unnameability'], + 'unamenable': ['unamenable', 'unnameable'], + 'unamenableness': ['unamenableness', 'unnameableness'], + 'unamenably': ['unamenably', 'unnameably'], + 'unamend': ['mundane', 'unamend', 'unmaned', 'unnamed'], + 'unami': ['maniu', 'munia', 'unami'], + 'unapt': ['punta', 'unapt', 'untap'], + 'unarising': ['grusinian', 'unarising'], + 'unarm': ['muran', 'ruman', 'unarm', 'unram', 'urman'], + 'unarmed': ['duramen', 'maunder', 'unarmed'], + 'unarray': ['unarray', 'yaruran'], + 'unarrestable': ['subterraneal', 'unarrestable'], + 'unarrested': ['unarrested', 'unserrated'], + 'unarted': ['daunter', 'unarted', 'unrated', 'untread'], + 'unarticled': ['denticular', 'unarticled'], + 'unartistic': ['naturistic', 'unartistic'], + 'unartistical': ['naturalistic', 'unartistical'], + 'unartistically': ['naturistically', 'unartistically'], + 'unary': ['anury', 'unary', 'unray'], + 'unastray': ['auntsary', 'unastray'], + 'unathirst': ['struthian', 'unathirst'], + 'unattire': ['tainture', 'unattire'], + 'unattuned': ['unattuned', 'untaunted'], + 'unaverted': ['adventure', 'unaverted'], + 'unavertible': ['unavertible', 'unveritable'], + 'unbag': ['bugan', 'bunga', 'unbag'], + 'unbain': ['nubian', 'unbain'], + 'unbale': ['nebula', 'unable', 'unbale'], + 'unbar': ['buran', 'unbar', 'urban'], + 'unbare': ['eburna', 'unbare', 'unbear', 'urbane'], + 'unbarred': ['errabund', 'unbarred'], + 'unbased': ['subdean', 'unbased'], + 'unbaste': ['unbaste', 'unbeast'], + 'unbatted': ['debutant', 'unbatted'], + 'unbay': ['bunya', 'unbay'], + 'unbe': ['benu', 'unbe'], + 'unbear': ['eburna', 'unbare', 'unbear', 'urbane'], + 'unbearded': ['unbearded', 'unbreaded'], + 'unbeast': ['unbaste', 'unbeast'], + 'unbeavered': ['unbeavered', 'unbereaved'], + 'unbelied': ['unbelied', 'unedible'], + 'unbereaved': ['unbeavered', 'unbereaved'], + 'unbesot': ['subnote', 'subtone', 'unbesot'], + 'unbias': ['anubis', 'unbias'], + 'unbillet': ['bulletin', 'unbillet'], + 'unbilleted': ['tunbellied', 'unbilleted'], + 'unblasted': ['dunstable', 'unblasted', 'unstabled'], + 'unbled': ['bundle', 'unbled'], + 'unboasted': ['eastbound', 'unboasted'], + 'unboat': ['outban', 'unboat'], + 'unboding': ['bounding', 'unboding'], + 'unbog': ['bungo', 'unbog'], + 'unboiled': ['unboiled', 'unilobed'], + 'unboned': ['bounden', 'unboned'], + 'unborder': ['unborder', 'underorb'], + 'unbored': ['beround', 'bounder', 'rebound', 'unbored', 'unorbed', 'unrobed'], + 'unboweled': ['unboweled', 'unelbowed'], + 'unbrace': ['bucrane', 'unbrace'], + 'unbraceleted': ['unbraceleted', 'uncelebrated'], + 'unbraid': ['barundi', 'unbraid'], + 'unbrailed': ['indurable', 'unbrailed', 'unridable'], + 'unbreaded': ['unbearded', 'unbreaded'], + 'unbred': ['bunder', 'burden', 'burned', 'unbred'], + 'unbribed': ['unbribed', 'unribbed'], + 'unbrief': ['unbrief', 'unfiber'], + 'unbriefed': ['unbriefed', 'unfibered'], + 'unbroiled': ['unbroiled', 'underboil'], + 'unbrushed': ['unbrushed', 'underbush'], + 'unbud': ['bundu', 'unbud', 'undub'], + 'unburden': ['unburden', 'unburned'], + 'unburned': ['unburden', 'unburned'], + 'unbuttered': ['unbuttered', 'unrebutted'], + 'unca': ['cuna', 'unca'], + 'uncage': ['cangue', 'uncage'], + 'uncambered': ['uncambered', 'unembraced'], + 'uncamerated': ['uncamerated', 'unmacerated'], + 'uncapable': ['uncapable', 'unpacable'], + 'uncaptious': ['uncaptious', 'usucaption'], + 'uncarted': ['uncarted', 'uncrated', 'underact', 'untraced'], + 'uncartooned': ['uncartooned', 'uncoronated'], + 'uncase': ['uncase', 'usance'], + 'uncask': ['uncask', 'unsack'], + 'uncasked': ['uncasked', 'unsacked'], + 'uncast': ['cantus', 'tuscan', 'uncast'], + 'uncatalogued': ['uncatalogued', 'uncoagulated'], + 'uncate': ['tecuna', 'uncate'], + 'uncaused': ['uncaused', 'unsauced'], + 'uncavalier': ['naviculare', 'uncavalier'], + 'uncelebrated': ['unbraceleted', 'uncelebrated'], + 'uncellar': ['lucernal', 'nucellar', 'uncellar'], + 'uncenter': ['uncenter', 'unrecent'], + 'uncertain': ['encurtain', 'runcinate', 'uncertain'], + 'uncertifiable': ['uncertifiable', 'unrectifiable'], + 'uncertified': ['uncertified', 'unrectified'], + 'unchain': ['chunnia', 'unchain'], + 'unchair': ['chunari', 'unchair'], + 'unchalked': ['unchalked', 'unhackled'], + 'uncharge': ['gunreach', 'uncharge'], + 'uncharm': ['uncharm', 'unmarch'], + 'uncharming': ['uncharming', 'unmarching'], + 'uncharred': ['uncharred', 'underarch'], + 'uncheat': ['uncheat', 'unteach'], + 'uncheating': ['uncheating', 'unteaching'], + 'unchoked': ['unchoked', 'unhocked'], + 'unchoosable': ['chaenolobus', 'unchoosable'], + 'unchosen': ['nonesuch', 'unchosen'], + 'uncial': ['cunila', 'lucian', 'lucina', 'uncial'], + 'unciferous': ['nuciferous', 'unciferous'], + 'unciform': ['nuciform', 'unciform'], + 'uncinate': ['nunciate', 'uncinate'], + 'unclaimed': ['unclaimed', 'undecimal', 'unmedical'], + 'unclay': ['lunacy', 'unclay'], + 'unclead': ['unclead', 'unlaced'], + 'unclear': ['crenula', 'lucarne', 'nuclear', 'unclear'], + 'uncleared': ['uncleared', 'undeclare'], + 'uncledom': ['columned', 'uncledom'], + 'uncleship': ['siphuncle', 'uncleship'], + 'uncloister': ['cornulites', 'uncloister'], + 'unclose': ['counsel', 'unclose'], + 'unclutter': ['truculent', 'unclutter'], + 'unco': ['cuon', 'unco'], + 'uncoagulated': ['uncatalogued', 'uncoagulated'], + 'uncoat': ['toucan', 'tucano', 'uncoat'], + 'uncoated': ['outdance', 'uncoated'], + 'uncoiled': ['nucleoid', 'uncoiled'], + 'uncoin': ['nuncio', 'uncoin'], + 'uncollapsed': ['uncollapsed', 'unscalloped'], + 'uncolored': ['uncolored', 'undercool'], + 'uncomic': ['muconic', 'uncomic'], + 'uncompatible': ['incomputable', 'uncompatible'], + 'uncomplaint': ['uncomplaint', 'uncompliant'], + 'uncomplete': ['couplement', 'uncomplete'], + 'uncompliant': ['uncomplaint', 'uncompliant'], + 'unconcerted': ['unconcerted', 'unconcreted'], + 'unconcreted': ['unconcerted', 'unconcreted'], + 'unconservable': ['unconservable', 'unconversable'], + 'unconstraint': ['noncurantist', 'unconstraint'], + 'uncontrasted': ['counterstand', 'uncontrasted'], + 'unconversable': ['unconservable', 'unconversable'], + 'uncoop': ['coupon', 'uncoop'], + 'uncooped': ['couponed', 'uncooped'], + 'uncope': ['pounce', 'uncope'], + 'uncopied': ['cupidone', 'uncopied'], + 'uncore': ['conure', 'rounce', 'uncore'], + 'uncored': ['crunode', 'uncored'], + 'uncorked': ['uncorked', 'unrocked'], + 'uncoronated': ['uncartooned', 'uncoronated'], + 'uncorrect': ['cocurrent', 'occurrent', 'uncorrect'], + 'uncorrugated': ['counterguard', 'uncorrugated'], + 'uncorseted': ['uncorseted', 'unescorted'], + 'uncostumed': ['uncostumed', 'uncustomed'], + 'uncoursed': ['uncoursed', 'unscoured'], + 'uncouth': ['uncouth', 'untouch'], + 'uncoverable': ['uncoverable', 'unrevocable'], + 'uncradled': ['uncradled', 'underclad'], + 'uncrated': ['uncarted', 'uncrated', 'underact', 'untraced'], + 'uncreased': ['uncreased', 'undercase'], + 'uncreatable': ['uncreatable', 'untraceable'], + 'uncreatableness': ['uncreatableness', 'untraceableness'], + 'uncreation': ['enunciator', 'uncreation'], + 'uncreative': ['uncreative', 'unreactive'], + 'uncredited': ['uncredited', 'undirected'], + 'uncrest': ['encrust', 'uncrest'], + 'uncrested': ['uncrested', 'undersect'], + 'uncried': ['inducer', 'uncried'], + 'uncrooked': ['uncrooked', 'undercook'], + 'uncrude': ['uncrude', 'uncured'], + 'unctional': ['continual', 'inoculant', 'unctional'], + 'unctioneer': ['recontinue', 'unctioneer'], + 'uncured': ['uncrude', 'uncured'], + 'uncustomed': ['uncostumed', 'uncustomed'], + 'uncuth': ['tuchun', 'uncuth'], + 'undam': ['maund', 'munda', 'numda', 'undam', 'unmad'], + 'undangered': ['undangered', 'underanged', 'ungardened'], + 'undarken': ['undarken', 'unranked'], + 'undashed': ['undashed', 'unshaded'], + 'undate': ['nudate', 'undate'], + 'unde': ['dune', 'nude', 'unde'], + 'undean': ['duenna', 'undean'], + 'undear': ['endura', 'neurad', 'undear', 'unread'], + 'undeceiver': ['undeceiver', 'unreceived'], + 'undecimal': ['unclaimed', 'undecimal', 'unmedical'], + 'undeclare': ['uncleared', 'undeclare'], + 'undecolic': ['coinclude', 'undecolic'], + 'undecorated': ['undecorated', 'undercoated'], + 'undefiled': ['undefiled', 'unfielded'], + 'undeified': ['undeified', 'unedified'], + 'undelible': ['undelible', 'unlibeled'], + 'undelight': ['undelight', 'unlighted'], + 'undelude': ['undelude', 'uneluded'], + 'undeluding': ['undeluding', 'unindulged'], + 'undemanded': ['undemanded', 'unmaddened'], + 'unden': ['dunne', 'unden'], + 'undeparted': ['dunderpate', 'undeparted'], + 'undepraved': ['undepraved', 'unpervaded'], + 'under': ['runed', 'under', 'unred'], + 'underact': ['uncarted', 'uncrated', 'underact', 'untraced'], + 'underacted': ['underacted', 'unredacted'], + 'underaction': ['denunciator', 'underaction'], + 'underage': ['dungaree', 'guardeen', 'unagreed', 'underage', 'ungeared'], + 'underaid': ['underaid', 'unraided'], + 'underaim': ['unadmire', 'underaim'], + 'underanged': ['undangered', 'underanged', 'ungardened'], + 'underarch': ['uncharred', 'underarch'], + 'underarm': ['underarm', 'unmarred'], + 'underbake': ['underbake', 'underbeak'], + 'underbeak': ['underbake', 'underbeak'], + 'underbeat': ['eburnated', 'underbeat', 'unrebated'], + 'underbit': ['turbined', 'underbit'], + 'underboil': ['unbroiled', 'underboil'], + 'underbreathing': ['thunderbearing', 'underbreathing'], + 'underbrush': ['underbrush', 'undershrub'], + 'underbush': ['unbrushed', 'underbush'], + 'undercase': ['uncreased', 'undercase'], + 'underchap': ['underchap', 'unparched'], + 'underclad': ['uncradled', 'underclad'], + 'undercoat': ['cornuated', 'undercoat'], + 'undercoated': ['undecorated', 'undercoated'], + 'undercook': ['uncrooked', 'undercook'], + 'undercool': ['uncolored', 'undercool'], + 'undercut': ['undercut', 'unreduct'], + 'underdead': ['underdead', 'undreaded'], + 'underdig': ['underdig', 'ungirded', 'unridged'], + 'underdive': ['underdive', 'underived'], + 'underdo': ['redound', 'rounded', 'underdo'], + 'underdoer': ['underdoer', 'unordered'], + 'underdog': ['grounded', 'underdog', 'undergod'], + 'underdown': ['underdown', 'undrowned'], + 'underdrag': ['underdrag', 'undergrad'], + 'underdraw': ['underdraw', 'underward'], + 'undereat': ['denature', 'undereat'], + 'underer': ['endurer', 'underer'], + 'underfiend': ['underfiend', 'unfriended'], + 'underfill': ['underfill', 'unfrilled'], + 'underfire': ['underfire', 'unferried'], + 'underflow': ['underflow', 'wonderful'], + 'underfur': ['underfur', 'unfurred'], + 'undergo': ['guerdon', 'undergo', 'ungored'], + 'undergod': ['grounded', 'underdog', 'undergod'], + 'undergoer': ['guerdoner', 'reundergo', 'undergoer', 'undergore'], + 'undergore': ['guerdoner', 'reundergo', 'undergoer', 'undergore'], + 'undergown': ['undergown', 'unwronged'], + 'undergrad': ['underdrag', 'undergrad'], + 'undergrade': ['undergrade', 'unregarded'], + 'underheat': ['underheat', 'unearthed'], + 'underhonest': ['underhonest', 'unshortened'], + 'underhorse': ['underhorse', 'undershore'], + 'underived': ['underdive', 'underived'], + 'underkind': ['underkind', 'unkindred'], + 'underlap': ['pendular', 'underlap', 'uplander'], + 'underleaf': ['underleaf', 'unfederal'], + 'underlease': ['underlease', 'unreleased'], + 'underlegate': ['underlegate', 'unrelegated'], + 'underlid': ['underlid', 'unriddle'], + 'underlive': ['underlive', 'unreviled'], + 'underlying': ['enduringly', 'underlying'], + 'undermade': ['undermade', 'undreamed'], + 'undermaid': ['unadmired', 'undermaid'], + 'undermaker': ['undermaker', 'unremarked'], + 'undermaster': ['undermaster', 'understream'], + 'undermeal': ['denumeral', 'undermeal', 'unrealmed'], + 'undermine': ['undermine', 'unermined'], + 'undermost': ['undermost', 'unstormed'], + 'undermotion': ['undermotion', 'unmonitored'], + 'undern': ['dunner', 'undern'], + 'underneath': ['unadherent', 'underneath', 'underthane'], + 'undernote': ['undernote', 'undertone'], + 'undernoted': ['undernoted', 'undertoned'], + 'underntide': ['indentured', 'underntide'], + 'underorb': ['unborder', 'underorb'], + 'underpay': ['underpay', 'unprayed'], + 'underpeer': ['perendure', 'underpeer'], + 'underpick': ['underpick', 'unpricked'], + 'underpier': ['underpier', 'underripe'], + 'underpile': ['underpile', 'unreplied'], + 'underpose': ['underpose', 'unreposed'], + 'underpuke': ['underpuke', 'unperuked'], + 'underream': ['maunderer', 'underream'], + 'underripe': ['underpier', 'underripe'], + 'underrobe': ['rebounder', 'underrobe'], + 'undersap': ['undersap', 'unparsed', 'unrasped', 'unspared', 'unspread'], + 'undersea': ['undersea', 'unerased', 'unseared'], + 'underseam': ['underseam', 'unsmeared'], + 'undersect': ['uncrested', 'undersect'], + 'underserve': ['underserve', + 'underverse', + 'undeserver', + 'unreserved', + 'unreversed'], + 'underset': ['sederunt', 'underset', 'undesert', 'unrested'], + 'undershapen': ['undershapen', 'unsharpened'], + 'undershore': ['underhorse', 'undershore'], + 'undershrub': ['underbrush', 'undershrub'], + 'underside': ['underside', 'undesired'], + 'undersoil': ['undersoil', 'unsoldier'], + 'undersow': ['sewround', 'undersow'], + 'underspar': ['underspar', 'unsparred'], + 'understain': ['understain', 'unstrained'], + 'understand': ['understand', 'unstranded'], + 'understream': ['undermaster', 'understream'], + 'underthane': ['unadherent', 'underneath', 'underthane'], + 'underthing': ['thundering', 'underthing'], + 'undertide': ['durdenite', 'undertide'], + 'undertime': ['undertime', 'unmerited'], + 'undertimed': ['demiturned', 'undertimed'], + 'undertitle': ['undertitle', 'unlittered'], + 'undertone': ['undernote', 'undertone'], + 'undertoned': ['undernoted', 'undertoned'], + 'undertow': ['undertow', 'untrowed'], + 'undertread': ['undertread', 'unretarded'], + 'undertutor': ['undertutor', 'untortured'], + 'underverse': ['underserve', + 'underverse', + 'undeserver', + 'unreserved', + 'unreversed'], + 'underwage': ['underwage', 'unwagered'], + 'underward': ['underdraw', 'underward'], + 'underwarp': ['underwarp', 'underwrap'], + 'underwave': ['underwave', 'unwavered'], + 'underwrap': ['underwarp', 'underwrap'], + 'undesert': ['sederunt', 'underset', 'undesert', 'unrested'], + 'undeserve': ['undeserve', 'unsevered'], + 'undeserver': ['underserve', + 'underverse', + 'undeserver', + 'unreserved', + 'unreversed'], + 'undesign': ['undesign', 'unsigned', 'unsinged'], + 'undesired': ['underside', 'undesired'], + 'undeviated': ['denudative', 'undeviated'], + 'undieted': ['undieted', 'unedited'], + 'undig': ['gundi', 'undig'], + 'undirect': ['undirect', 'untriced'], + 'undirected': ['uncredited', 'undirected'], + 'undiscerned': ['undiscerned', 'unrescinded'], + 'undiscretion': ['discontinuer', 'undiscretion'], + 'undistress': ['sturdiness', 'undistress'], + 'undiverse': ['undiverse', 'unrevised'], + 'undog': ['undog', 'ungod'], + 'undrab': ['durban', 'undrab'], + 'undrag': ['durgan', 'undrag'], + 'undrape': ['undrape', 'unpared', 'unraped'], + 'undreaded': ['underdead', 'undreaded'], + 'undreamed': ['undermade', 'undreamed'], + 'undrowned': ['underdown', 'undrowned'], + 'undrugged': ['undrugged', 'ungrudged'], + 'undryable': ['endurably', 'undryable'], + 'undub': ['bundu', 'unbud', 'undub'], + 'undumped': ['pudendum', 'undumped'], + 'undy': ['duny', 'undy'], + 'uneager': ['geneura', 'uneager'], + 'unearned': ['unearned', 'unneared'], + 'unearnest': ['unearnest', 'uneastern'], + 'unearth': ['haunter', 'nauther', 'unearth', 'unheart', 'urethan'], + 'unearthed': ['underheat', 'unearthed'], + 'unearthly': ['unearthly', 'urethylan'], + 'uneastern': ['unearnest', 'uneastern'], + 'uneath': ['uneath', 'unhate'], + 'unebriate': ['beraunite', 'unebriate'], + 'unedge': ['dengue', 'unedge'], + 'unedible': ['unbelied', 'unedible'], + 'unedified': ['undeified', 'unedified'], + 'unedited': ['undieted', 'unedited'], + 'unelapsed': ['unelapsed', 'unpleased'], + 'unelated': ['antelude', 'unelated'], + 'unelbowed': ['unboweled', 'unelbowed'], + 'unelidible': ['ineludible', 'unelidible'], + 'uneluded': ['undelude', 'uneluded'], + 'unembased': ['sunbeamed', 'unembased'], + 'unembraced': ['uncambered', 'unembraced'], + 'unenabled': ['unenabled', 'unendable'], + 'unencored': ['denouncer', 'unencored'], + 'unendable': ['unenabled', 'unendable'], + 'unending': ['unending', 'unginned'], + 'unenervated': ['unenervated', 'unvenerated'], + 'unenlisted': ['unenlisted', 'unlistened', 'untinseled'], + 'unenterprised': ['superintender', 'unenterprised'], + 'unenviable': ['unenviable', 'unveniable'], + 'unenvied': ['unenvied', 'unveined'], + 'unequitable': ['unequitable', 'unquietable'], + 'unerased': ['undersea', 'unerased', 'unseared'], + 'unermined': ['undermine', 'unermined'], + 'unerratic': ['recurtain', 'unerratic'], + 'unerupted': ['unerupted', 'unreputed'], + 'unescorted': ['uncorseted', 'unescorted'], + 'unevil': ['unevil', 'unlive', 'unveil'], + 'unexactly': ['exultancy', 'unexactly'], + 'unexceptable': ['unexceptable', 'unexpectable'], + 'unexcepted': ['unexcepted', 'unexpected'], + 'unexcepting': ['unexcepting', 'unexpecting'], + 'unexpectable': ['unexceptable', 'unexpectable'], + 'unexpected': ['unexcepted', 'unexpected'], + 'unexpecting': ['unexcepting', 'unexpecting'], + 'unfabled': ['fundable', 'unfabled'], + 'unfaceted': ['fecundate', 'unfaceted'], + 'unfactional': ['afunctional', 'unfactional'], + 'unfactored': ['fecundator', 'unfactored'], + 'unfainting': ['antifungin', 'unfainting'], + 'unfallible': ['unfallible', 'unfillable'], + 'unfar': ['furan', 'unfar'], + 'unfarmed': ['unfarmed', 'unframed'], + 'unfederal': ['underleaf', 'unfederal'], + 'unfeeding': ['unfeeding', 'unfeigned'], + 'unfeeling': ['unfeeling', 'unfleeing'], + 'unfeigned': ['unfeeding', 'unfeigned'], + 'unfelt': ['fluent', 'netful', 'unfelt', 'unleft'], + 'unfelted': ['defluent', 'unfelted'], + 'unferried': ['underfire', 'unferried'], + 'unfiber': ['unbrief', 'unfiber'], + 'unfibered': ['unbriefed', 'unfibered'], + 'unfielded': ['undefiled', 'unfielded'], + 'unfiend': ['unfiend', 'unfined'], + 'unfiery': ['reunify', 'unfiery'], + 'unfillable': ['unfallible', 'unfillable'], + 'unfined': ['unfiend', 'unfined'], + 'unfired': ['unfired', 'unfried'], + 'unflag': ['fungal', 'unflag'], + 'unflat': ['flaunt', 'unflat'], + 'unfleeing': ['unfeeling', 'unfleeing'], + 'unfloured': ['unfloured', 'unfoldure'], + 'unfolder': ['flounder', 'reunfold', 'unfolder'], + 'unfolding': ['foundling', 'unfolding'], + 'unfoldure': ['unfloured', 'unfoldure'], + 'unforest': ['furstone', 'unforest'], + 'unforested': ['unforested', 'unfostered'], + 'unformality': ['fulminatory', 'unformality'], + 'unforward': ['unforward', 'unfroward'], + 'unfostered': ['unforested', 'unfostered'], + 'unfrail': ['rainful', 'unfrail'], + 'unframed': ['unfarmed', 'unframed'], + 'unfret': ['turfen', 'unfret'], + 'unfriable': ['funebrial', 'unfriable'], + 'unfried': ['unfired', 'unfried'], + 'unfriended': ['underfiend', 'unfriended'], + 'unfriending': ['unfriending', 'uninfringed'], + 'unfrilled': ['underfill', 'unfrilled'], + 'unfroward': ['unforward', 'unfroward'], + 'unfurl': ['unfurl', 'urnful'], + 'unfurred': ['underfur', 'unfurred'], + 'ungaite': ['ungaite', 'unitage'], + 'unganged': ['unganged', 'unnagged'], + 'ungardened': ['undangered', 'underanged', 'ungardened'], + 'ungarnish': ['ungarnish', 'unsharing'], + 'ungear': ['nauger', 'raunge', 'ungear'], + 'ungeared': ['dungaree', 'guardeen', 'unagreed', 'underage', 'ungeared'], + 'ungelt': ['englut', 'gluten', 'ungelt'], + 'ungenerable': ['ungenerable', 'ungreenable'], + 'unget': ['tengu', 'unget'], + 'ungilded': ['deluding', 'ungilded'], + 'ungill': ['ulling', 'ungill'], + 'ungilt': ['glutin', 'luting', 'ungilt'], + 'unginned': ['unending', 'unginned'], + 'ungird': ['during', 'ungird'], + 'ungirded': ['underdig', 'ungirded', 'unridged'], + 'ungirdle': ['indulger', 'ungirdle'], + 'ungirt': ['ungirt', 'untrig'], + 'ungirth': ['hurting', 'ungirth', 'unright'], + 'ungirthed': ['ungirthed', 'unrighted'], + 'unglad': ['gandul', 'unglad'], + 'unglued': ['unglued', 'unguled'], + 'ungod': ['undog', 'ungod'], + 'ungold': ['dungol', 'ungold'], + 'ungone': ['guenon', 'ungone'], + 'ungored': ['guerdon', 'undergo', 'ungored'], + 'ungorge': ['gurgeon', 'ungorge'], + 'ungot': ['tungo', 'ungot'], + 'ungothic': ['touching', 'ungothic'], + 'ungraphic': ['ungraphic', 'uparching'], + 'ungreenable': ['ungenerable', 'ungreenable'], + 'ungrieved': ['gerundive', 'ungrieved'], + 'ungroined': ['ungroined', 'unignored'], + 'ungrudged': ['undrugged', 'ungrudged'], + 'ungual': ['ungual', 'ungula'], + 'ungueal': ['ungueal', 'ungulae'], + 'ungula': ['ungual', 'ungula'], + 'ungulae': ['ungueal', 'ungulae'], + 'unguled': ['unglued', 'unguled'], + 'ungulp': ['ungulp', 'unplug'], + 'unhabit': ['bhutani', 'unhabit'], + 'unhackled': ['unchalked', 'unhackled'], + 'unhairer': ['rhineura', 'unhairer'], + 'unhalsed': ['unhalsed', 'unlashed', 'unshaled'], + 'unhalted': ['unhalted', 'unlathed'], + 'unhalter': ['lutheran', 'unhalter'], + 'unhaltered': ['unhaltered', 'unlathered'], + 'unhamper': ['prehuman', 'unhamper'], + 'unharbored': ['unabhorred', 'unharbored'], + 'unhasped': ['unhasped', 'unphased', 'unshaped'], + 'unhat': ['ahunt', 'haunt', 'thuan', 'unhat'], + 'unhate': ['uneath', 'unhate'], + 'unhatingly': ['hauntingly', 'unhatingly'], + 'unhayed': ['unhayed', 'unheady'], + 'unheady': ['unhayed', 'unheady'], + 'unhearsed': ['unhearsed', 'unsheared'], + 'unheart': ['haunter', 'nauther', 'unearth', 'unheart', 'urethan'], + 'unhid': ['hindu', 'hundi', 'unhid'], + 'unhistoric': ['trichinous', 'unhistoric'], + 'unhittable': ['unhittable', 'untithable'], + 'unhoarded': ['roundhead', 'unhoarded'], + 'unhocked': ['unchoked', 'unhocked'], + 'unhoisted': ['hudsonite', 'unhoisted'], + 'unhorse': ['unhorse', 'unshore'], + 'unhose': ['unhose', 'unshoe'], + 'unhosed': ['unhosed', 'unshoed'], + 'unhurt': ['unhurt', 'unruth'], + 'uniat': ['uinta', 'uniat'], + 'uniate': ['auntie', 'uniate'], + 'unible': ['nubile', 'unible'], + 'uniced': ['induce', 'uniced'], + 'unicentral': ['incruental', 'unicentral'], + 'unideal': ['aliunde', 'unideal'], + 'unidentified': ['indefinitude', 'unidentified'], + 'unidly': ['unidly', 'yildun'], + 'unie': ['niue', 'unie'], + 'unifilar': ['friulian', 'unifilar'], + 'uniflorate': ['antifouler', 'fluorinate', 'uniflorate'], + 'unigenous': ['ingenuous', 'unigenous'], + 'unignored': ['ungroined', 'unignored'], + 'unilobar': ['orbulina', 'unilobar'], + 'unilobed': ['unboiled', 'unilobed'], + 'unimedial': ['aluminide', 'unimedial'], + 'unimpair': ['manipuri', 'unimpair'], + 'unimparted': ['diparentum', 'unimparted'], + 'unimportance': ['importunance', 'unimportance'], + 'unimpressible': ['unimpressible', 'unpermissible'], + 'unimpressive': ['unimpressive', 'unpermissive'], + 'unindented': ['unindented', 'unintended'], + 'unindulged': ['undeluding', 'unindulged'], + 'uninervate': ['aventurine', 'uninervate'], + 'uninfringed': ['unfriending', 'uninfringed'], + 'uningested': ['uningested', 'unsigneted'], + 'uninn': ['nunni', 'uninn'], + 'uninnate': ['eutannin', 'uninnate'], + 'uninodal': ['annuloid', 'uninodal'], + 'unintended': ['unindented', 'unintended'], + 'unintoned': ['nonunited', 'unintoned'], + 'uninured': ['uninured', 'unruined'], + 'unionist': ['inustion', 'unionist'], + 'unipersonal': ['spinoneural', 'unipersonal'], + 'unipod': ['dupion', 'unipod'], + 'uniradial': ['nidularia', 'uniradial'], + 'unireme': ['erineum', 'unireme'], + 'uniserrate': ['arseniuret', 'uniserrate'], + 'unison': ['nonius', 'unison'], + 'unitage': ['ungaite', 'unitage'], + 'unital': ['inlaut', 'unital'], + 'unite': ['intue', 'unite', 'untie'], + 'united': ['dunite', 'united', 'untied'], + 'uniter': ['runite', 'triune', 'uniter', 'untire'], + 'unitiveness': ['unitiveness', 'unsensitive'], + 'unitrope': ['eruption', 'unitrope'], + 'univied': ['univied', 'viduine'], + 'unket': ['knute', 'unket'], + 'unkilned': ['unkilned', 'unlinked'], + 'unkin': ['nunki', 'unkin'], + 'unkindred': ['underkind', 'unkindred'], + 'unlabiate': ['laubanite', 'unlabiate'], + 'unlabored': ['burdalone', 'unlabored'], + 'unlace': ['auncel', 'cuneal', 'lacune', 'launce', 'unlace'], + 'unlaced': ['unclead', 'unlaced'], + 'unlade': ['unlade', 'unlead'], + 'unlaid': ['dualin', 'ludian', 'unlaid'], + 'unlame': ['manuel', 'unlame'], + 'unlapped': ['unlapped', 'unpalped'], + 'unlarge': ['granule', 'unlarge', 'unregal'], + 'unlashed': ['unhalsed', 'unlashed', 'unshaled'], + 'unlasting': ['unlasting', 'unslating'], + 'unlatch': ['tulchan', 'unlatch'], + 'unlathed': ['unhalted', 'unlathed'], + 'unlathered': ['unhaltered', 'unlathered'], + 'unlay': ['unlay', 'yulan'], + 'unlead': ['unlade', 'unlead'], + 'unleasable': ['unleasable', 'unsealable'], + 'unleased': ['unleased', 'unsealed'], + 'unleash': ['hulsean', 'unleash'], + 'unled': ['lendu', 'unled'], + 'unleft': ['fluent', 'netful', 'unfelt', 'unleft'], + 'unlent': ['nunlet', 'tunnel', 'unlent'], + 'unlevied': ['unlevied', 'unveiled'], + 'unlibeled': ['undelible', 'unlibeled'], + 'unliberal': ['brunellia', 'unliberal'], + 'unlicensed': ['unlicensed', 'unsilenced'], + 'unlighted': ['undelight', 'unlighted'], + 'unliken': ['nunlike', 'unliken'], + 'unlime': ['lumine', 'unlime'], + 'unlinked': ['unkilned', 'unlinked'], + 'unlist': ['insult', 'sunlit', 'unlist', 'unslit'], + 'unlistened': ['unenlisted', 'unlistened', 'untinseled'], + 'unlit': ['unlit', 'until'], + 'unliteral': ['tellurian', 'unliteral'], + 'unlittered': ['undertitle', 'unlittered'], + 'unlive': ['unevil', 'unlive', 'unveil'], + 'unloaded': ['duodenal', 'unloaded'], + 'unloaden': ['unloaden', 'unloaned'], + 'unloader': ['unloader', 'urodelan'], + 'unloaned': ['unloaden', 'unloaned'], + 'unlodge': ['unlodge', 'unogled'], + 'unlogic': ['gulonic', 'unlogic'], + 'unlooped': ['unlooped', 'unpooled'], + 'unlooted': ['unlooted', 'untooled'], + 'unlost': ['unlost', 'unslot'], + 'unlowered': ['unlowered', 'unroweled'], + 'unlucid': ['nuculid', 'unlucid'], + 'unlumped': ['pendulum', 'unlumped', 'unplumed'], + 'unlured': ['unlured', 'unruled'], + 'unlyrical': ['runically', 'unlyrical'], + 'unmacerated': ['uncamerated', 'unmacerated'], + 'unmad': ['maund', 'munda', 'numda', 'undam', 'unmad'], + 'unmadded': ['addendum', 'unmadded'], + 'unmaddened': ['undemanded', 'unmaddened'], + 'unmaid': ['numida', 'unmaid'], + 'unmail': ['alumni', 'unmail'], + 'unmailed': ['adlumine', 'unmailed'], + 'unmaned': ['mundane', 'unamend', 'unmaned', 'unnamed'], + 'unmantle': ['unmantle', 'unmental'], + 'unmarch': ['uncharm', 'unmarch'], + 'unmarching': ['uncharming', 'unmarching'], + 'unmarginal': ['unalarming', 'unmarginal'], + 'unmarred': ['underarm', 'unmarred'], + 'unmashed': ['unmashed', 'unshamed'], + 'unmate': ['unmate', 'untame', 'unteam'], + 'unmated': ['unmated', 'untamed'], + 'unmaterial': ['manualiter', 'unmaterial'], + 'unmeated': ['unmeated', 'unteamed'], + 'unmedical': ['unclaimed', 'undecimal', 'unmedical'], + 'unmeet': ['unmeet', 'unteem'], + 'unmemoired': ['unmemoired', 'unmemoried'], + 'unmemoried': ['unmemoired', 'unmemoried'], + 'unmental': ['unmantle', 'unmental'], + 'unmerged': ['gerendum', 'unmerged'], + 'unmerited': ['undertime', 'unmerited'], + 'unmettle': ['temulent', 'unmettle'], + 'unminable': ['nelumbian', 'unminable'], + 'unmined': ['minuend', 'unmined'], + 'unminted': ['indument', 'unminted'], + 'unmisled': ['muslined', 'unmisled', 'unsmiled'], + 'unmiter': ['minuter', 'unmiter'], + 'unmodest': ['mudstone', 'unmodest'], + 'unmodish': ['muishond', 'unmodish'], + 'unmomentary': ['monumentary', 'unmomentary'], + 'unmonitored': ['undermotion', 'unmonitored'], + 'unmorbid': ['moribund', 'unmorbid'], + 'unmorose': ['enormous', 'unmorose'], + 'unmortised': ['semirotund', 'unmortised'], + 'unmotived': ['unmotived', 'unvomited'], + 'unmystical': ['stimulancy', 'unmystical'], + 'unnagged': ['unganged', 'unnagged'], + 'unnail': ['alnuin', 'unnail'], + 'unnameability': ['unamenability', 'unnameability'], + 'unnameable': ['unamenable', 'unnameable'], + 'unnameableness': ['unamenableness', 'unnameableness'], + 'unnameably': ['unamenably', 'unnameably'], + 'unnamed': ['mundane', 'unamend', 'unmaned', 'unnamed'], + 'unnational': ['annulation', 'unnational'], + 'unnative': ['unnative', 'venutian'], + 'unneared': ['unearned', 'unneared'], + 'unnest': ['unnest', 'unsent'], + 'unnetted': ['unnetted', 'untented'], + 'unnose': ['nonuse', 'unnose'], + 'unnoted': ['unnoted', 'untoned'], + 'unnoticed': ['continued', 'unnoticed'], + 'unoared': ['rondeau', 'unoared'], + 'unogled': ['unlodge', 'unogled'], + 'unomitted': ['dumontite', 'unomitted'], + 'unoperatic': ['precaution', 'unoperatic'], + 'unorbed': ['beround', 'bounder', 'rebound', 'unbored', 'unorbed', 'unrobed'], + 'unorder': ['rondure', 'rounder', 'unorder'], + 'unordered': ['underdoer', 'unordered'], + 'unoriented': ['nonerudite', 'unoriented'], + 'unown': ['unown', 'unwon'], + 'unowned': ['enwound', 'unowned'], + 'unpacable': ['uncapable', 'unpacable'], + 'unpacker': ['reunpack', 'unpacker'], + 'unpaired': ['unpaired', 'unrepaid'], + 'unpale': ['unpale', 'uplane'], + 'unpalped': ['unlapped', 'unpalped'], + 'unpanel': ['unpanel', 'unpenal'], + 'unparceled': ['unparceled', 'unreplaced'], + 'unparched': ['underchap', 'unparched'], + 'unpared': ['undrape', 'unpared', 'unraped'], + 'unparsed': ['undersap', 'unparsed', 'unrasped', 'unspared', 'unspread'], + 'unparted': ['depurant', 'unparted'], + 'unpartial': ['tarpaulin', 'unpartial'], + 'unpenal': ['unpanel', 'unpenal'], + 'unpenetrable': ['unpenetrable', 'unrepentable'], + 'unpent': ['punnet', 'unpent'], + 'unperch': ['puncher', 'unperch'], + 'unpercolated': ['counterpaled', 'counterplead', 'unpercolated'], + 'unpermissible': ['unimpressible', 'unpermissible'], + 'unpermissive': ['unimpressive', 'unpermissive'], + 'unperuked': ['underpuke', 'unperuked'], + 'unpervaded': ['undepraved', 'unpervaded'], + 'unpetal': ['plutean', 'unpetal', 'unpleat'], + 'unpharasaic': ['parasuchian', 'unpharasaic'], + 'unphased': ['unhasped', 'unphased', 'unshaped'], + 'unphrased': ['unphrased', 'unsharped'], + 'unpickled': ['dunpickle', 'unpickled'], + 'unpierced': ['preinduce', 'unpierced'], + 'unpile': ['lupine', 'unpile', 'upline'], + 'unpiled': ['unpiled', 'unplied'], + 'unplace': ['cleanup', 'unplace'], + 'unplain': ['pinnula', 'unplain'], + 'unplait': ['nuptial', 'unplait'], + 'unplanted': ['pendulant', 'unplanted'], + 'unplat': ['puntal', 'unplat'], + 'unpleased': ['unelapsed', 'unpleased'], + 'unpleat': ['plutean', 'unpetal', 'unpleat'], + 'unpleated': ['pendulate', 'unpleated'], + 'unplied': ['unpiled', 'unplied'], + 'unplug': ['ungulp', 'unplug'], + 'unplumed': ['pendulum', 'unlumped', 'unplumed'], + 'unpoled': ['duplone', 'unpoled'], + 'unpolished': ['disulphone', 'unpolished'], + 'unpolitic': ['punctilio', 'unpolitic'], + 'unpooled': ['unlooped', 'unpooled'], + 'unposted': ['outspend', 'unposted'], + 'unpot': ['punto', 'unpot', 'untop'], + 'unprayed': ['underpay', 'unprayed'], + 'unprelatic': ['periculant', 'unprelatic'], + 'unpressed': ['resuspend', 'suspender', 'unpressed'], + 'unpricked': ['underpick', 'unpricked'], + 'unprint': ['turnpin', 'unprint'], + 'unprosaic': ['inocarpus', 'unprosaic'], + 'unproselyted': ['pseudelytron', 'unproselyted'], + 'unproud': ['roundup', 'unproud'], + 'unpursued': ['unpursued', 'unusurped'], + 'unpursuing': ['unpursuing', 'unusurping'], + 'unquietable': ['unequitable', 'unquietable'], + 'unrabbeted': ['beturbaned', 'unrabbeted'], + 'unraced': ['durance', 'redunca', 'unraced'], + 'unraided': ['underaid', 'unraided'], + 'unraised': ['denarius', 'desaurin', 'unraised'], + 'unram': ['muran', 'ruman', 'unarm', 'unram', 'urman'], + 'unranked': ['undarken', 'unranked'], + 'unraped': ['undrape', 'unpared', 'unraped'], + 'unrasped': ['undersap', 'unparsed', 'unrasped', 'unspared', 'unspread'], + 'unrated': ['daunter', 'unarted', 'unrated', 'untread'], + 'unravel': ['unravel', 'venular'], + 'unray': ['anury', 'unary', 'unray'], + 'unrayed': ['unrayed', 'unready'], + 'unreactive': ['uncreative', 'unreactive'], + 'unread': ['endura', 'neurad', 'undear', 'unread'], + 'unready': ['unrayed', 'unready'], + 'unreal': ['lunare', 'neural', 'ulnare', 'unreal'], + 'unrealism': ['semilunar', 'unrealism'], + 'unrealist': ['neuralist', 'ulsterian', 'unrealist'], + 'unrealmed': ['denumeral', 'undermeal', 'unrealmed'], + 'unrebated': ['eburnated', 'underbeat', 'unrebated'], + 'unrebutted': ['unbuttered', 'unrebutted'], + 'unreceived': ['undeceiver', 'unreceived'], + 'unrecent': ['uncenter', 'unrecent'], + 'unrecited': ['centuried', 'unrecited'], + 'unrectifiable': ['uncertifiable', 'unrectifiable'], + 'unrectified': ['uncertified', 'unrectified'], + 'unred': ['runed', 'under', 'unred'], + 'unredacted': ['underacted', 'unredacted'], + 'unreduct': ['undercut', 'unreduct'], + 'unreeve': ['revenue', 'unreeve'], + 'unreeving': ['unreeving', 'unveering'], + 'unregal': ['granule', 'unlarge', 'unregal'], + 'unregard': ['grandeur', 'unregard'], + 'unregarded': ['undergrade', 'unregarded'], + 'unrein': ['enruin', 'neurin', 'unrein'], + 'unreinstated': ['unreinstated', 'unstraitened'], + 'unrelated': ['unaltered', 'unrelated'], + 'unrelating': ['unaltering', 'unrelating'], + 'unreleased': ['underlease', 'unreleased'], + 'unrelegated': ['underlegate', 'unrelegated'], + 'unremarked': ['undermaker', 'unremarked'], + 'unrent': ['runnet', 'tunner', 'unrent'], + 'unrented': ['unrented', 'untender'], + 'unrepaid': ['unpaired', 'unrepaid'], + 'unrepentable': ['unpenetrable', 'unrepentable'], + 'unrepined': ['unrepined', 'unripened'], + 'unrepining': ['unrepining', 'unripening'], + 'unreplaced': ['unparceled', 'unreplaced'], + 'unreplied': ['underpile', 'unreplied'], + 'unreposed': ['underpose', 'unreposed'], + 'unreputed': ['unerupted', 'unreputed'], + 'unrescinded': ['undiscerned', 'unrescinded'], + 'unrescued': ['unrescued', 'unsecured'], + 'unreserved': ['underserve', + 'underverse', + 'undeserver', + 'unreserved', + 'unreversed'], + 'unresisted': ['unresisted', 'unsistered'], + 'unresolve': ['nervulose', 'unresolve', 'vulnerose'], + 'unrespect': ['unrespect', 'unscepter', 'unsceptre'], + 'unrespected': ['unrespected', 'unsceptered'], + 'unrested': ['sederunt', 'underset', 'undesert', 'unrested'], + 'unresting': ['insurgent', 'unresting'], + 'unretarded': ['undertread', 'unretarded'], + 'unreticent': ['entincture', 'unreticent'], + 'unretired': ['reintrude', 'unretired'], + 'unrevered': ['enverdure', 'unrevered'], + 'unreversed': ['underserve', + 'underverse', + 'undeserver', + 'unreserved', + 'unreversed'], + 'unreviled': ['underlive', 'unreviled'], + 'unrevised': ['undiverse', 'unrevised'], + 'unrevocable': ['uncoverable', 'unrevocable'], + 'unribbed': ['unbribed', 'unribbed'], + 'unrich': ['unrich', 'urchin'], + 'unrid': ['rundi', 'unrid'], + 'unridable': ['indurable', 'unbrailed', 'unridable'], + 'unriddle': ['underlid', 'unriddle'], + 'unride': ['diurne', 'inured', 'ruined', 'unride'], + 'unridged': ['underdig', 'ungirded', 'unridged'], + 'unrig': ['irgun', 'ruing', 'unrig'], + 'unright': ['hurting', 'ungirth', 'unright'], + 'unrighted': ['ungirthed', 'unrighted'], + 'unring': ['unring', 'urning'], + 'unringed': ['enduring', 'unringed'], + 'unripe': ['purine', 'unripe', 'uprein'], + 'unripely': ['pyruline', 'unripely'], + 'unripened': ['unrepined', 'unripened'], + 'unripening': ['unrepining', 'unripening'], + 'unroaded': ['unadored', 'unroaded'], + 'unrobed': ['beround', 'bounder', 'rebound', 'unbored', 'unorbed', 'unrobed'], + 'unrocked': ['uncorked', 'unrocked'], + 'unroot': ['notour', 'unroot'], + 'unroped': ['pounder', 'repound', 'unroped'], + 'unrosed': ['resound', 'sounder', 'unrosed'], + 'unrostrated': ['tetrandrous', 'unrostrated'], + 'unrotated': ['rotundate', 'unrotated'], + 'unroted': ['tendour', 'unroted'], + 'unroused': ['unroused', 'unsoured'], + 'unrouted': ['unrouted', 'untoured'], + 'unrowed': ['rewound', 'unrowed', 'wounder'], + 'unroweled': ['unlowered', 'unroweled'], + 'unroyalist': ['unroyalist', 'unsolitary'], + 'unruined': ['uninured', 'unruined'], + 'unruled': ['unlured', 'unruled'], + 'unrun': ['unrun', 'unurn'], + 'unruth': ['unhurt', 'unruth'], + 'unsack': ['uncask', 'unsack'], + 'unsacked': ['uncasked', 'unsacked'], + 'unsacred': ['unsacred', 'unscared'], + 'unsad': ['sudan', 'unsad'], + 'unsadden': ['unsadden', 'unsanded'], + 'unsage': ['gnaeus', 'unsage'], + 'unsaid': ['sudani', 'unsaid'], + 'unsailed': ['unaisled', 'unsailed'], + 'unsaint': ['antisun', 'unsaint', 'unsatin', 'unstain'], + 'unsainted': ['unsainted', 'unstained'], + 'unsalt': ['sultan', 'unsalt'], + 'unsalted': ['unsalted', 'unslated', 'unstaled'], + 'unsanded': ['unsadden', 'unsanded'], + 'unsardonic': ['andronicus', 'unsardonic'], + 'unsashed': ['sunshade', 'unsashed'], + 'unsatable': ['sublanate', 'unsatable'], + 'unsatiable': ['balaustine', 'unsatiable'], + 'unsatin': ['antisun', 'unsaint', 'unsatin', 'unstain'], + 'unsauced': ['uncaused', 'unsauced'], + 'unscale': ['censual', 'unscale'], + 'unscalloped': ['uncollapsed', 'unscalloped'], + 'unscaly': ['ancylus', 'unscaly'], + 'unscared': ['unsacred', 'unscared'], + 'unscepter': ['unrespect', 'unscepter', 'unsceptre'], + 'unsceptered': ['unrespected', 'unsceptered'], + 'unsceptre': ['unrespect', 'unscepter', 'unsceptre'], + 'unscoured': ['uncoursed', 'unscoured'], + 'unseal': ['elanus', 'unseal'], + 'unsealable': ['unleasable', 'unsealable'], + 'unsealed': ['unleased', 'unsealed'], + 'unseared': ['undersea', 'unerased', 'unseared'], + 'unseat': ['nasute', 'nauset', 'unseat'], + 'unseated': ['unseated', 'unsedate', 'unteased'], + 'unsecured': ['unrescued', 'unsecured'], + 'unsedate': ['unseated', 'unsedate', 'unteased'], + 'unsee': ['ensue', 'seenu', 'unsee'], + 'unseethed': ['unseethed', 'unsheeted'], + 'unseizable': ['unseizable', 'unsizeable'], + 'unselect': ['esculent', 'unselect'], + 'unsensed': ['nudeness', 'unsensed'], + 'unsensitive': ['unitiveness', 'unsensitive'], + 'unsent': ['unnest', 'unsent'], + 'unsepulcher': ['unsepulcher', 'unsepulchre'], + 'unsepulchre': ['unsepulcher', 'unsepulchre'], + 'unserrated': ['unarrested', 'unserrated'], + 'unserved': ['unserved', 'unversed'], + 'unset': ['unset', 'usent'], + 'unsevered': ['undeserve', 'unsevered'], + 'unsewed': ['sunweed', 'unsewed'], + 'unsex': ['nexus', 'unsex'], + 'unshaded': ['undashed', 'unshaded'], + 'unshaled': ['unhalsed', 'unlashed', 'unshaled'], + 'unshamed': ['unmashed', 'unshamed'], + 'unshaped': ['unhasped', 'unphased', 'unshaped'], + 'unsharing': ['ungarnish', 'unsharing'], + 'unsharped': ['unphrased', 'unsharped'], + 'unsharpened': ['undershapen', 'unsharpened'], + 'unsheared': ['unhearsed', 'unsheared'], + 'unsheet': ['enthuse', 'unsheet'], + 'unsheeted': ['unseethed', 'unsheeted'], + 'unship': ['inpush', 'punish', 'unship'], + 'unshipment': ['punishment', 'unshipment'], + 'unshoe': ['unhose', 'unshoe'], + 'unshoed': ['unhosed', 'unshoed'], + 'unshore': ['unhorse', 'unshore'], + 'unshored': ['enshroud', 'unshored'], + 'unshortened': ['underhonest', 'unshortened'], + 'unsicker': ['cruisken', 'unsicker'], + 'unsickled': ['klendusic', 'unsickled'], + 'unsight': ['gutnish', 'husting', 'unsight'], + 'unsignable': ['unsignable', 'unsingable'], + 'unsigned': ['undesign', 'unsigned', 'unsinged'], + 'unsigneted': ['uningested', 'unsigneted'], + 'unsilenced': ['unlicensed', 'unsilenced'], + 'unsimple': ['splenium', 'unsimple'], + 'unsin': ['sunni', 'unsin'], + 'unsingable': ['unsignable', 'unsingable'], + 'unsinged': ['undesign', 'unsigned', 'unsinged'], + 'unsistered': ['unresisted', 'unsistered'], + 'unsizeable': ['unseizable', 'unsizeable'], + 'unskin': ['insunk', 'unskin'], + 'unslate': ['sultane', 'unslate'], + 'unslated': ['unsalted', 'unslated', 'unstaled'], + 'unslating': ['unlasting', 'unslating'], + 'unslept': ['unslept', 'unspelt'], + 'unslighted': ['sunlighted', 'unslighted'], + 'unslit': ['insult', 'sunlit', 'unlist', 'unslit'], + 'unslot': ['unlost', 'unslot'], + 'unsmeared': ['underseam', 'unsmeared'], + 'unsmiled': ['muslined', 'unmisled', 'unsmiled'], + 'unsnap': ['pannus', 'sannup', 'unsnap', 'unspan'], + 'unsnatch': ['unsnatch', 'unstanch'], + 'unsnow': ['unsnow', 'unsown'], + 'unsocial': ['sualocin', 'unsocial'], + 'unsoil': ['insoul', 'linous', 'nilous', 'unsoil'], + 'unsoiled': ['delusion', 'unsoiled'], + 'unsoldier': ['undersoil', 'unsoldier'], + 'unsole': ['ensoul', 'olenus', 'unsole'], + 'unsolitary': ['unroyalist', 'unsolitary'], + 'unsomber': ['unsomber', 'unsombre'], + 'unsombre': ['unsomber', 'unsombre'], + 'unsome': ['nomeus', 'unsome'], + 'unsore': ['souren', 'unsore', 'ursone'], + 'unsort': ['tornus', 'unsort'], + 'unsortable': ['neuroblast', 'unsortable'], + 'unsorted': ['tonsured', 'unsorted', 'unstored'], + 'unsoured': ['unroused', 'unsoured'], + 'unsown': ['unsnow', 'unsown'], + 'unspan': ['pannus', 'sannup', 'unsnap', 'unspan'], + 'unspar': ['surnap', 'unspar'], + 'unspared': ['undersap', 'unparsed', 'unrasped', 'unspared', 'unspread'], + 'unsparred': ['underspar', 'unsparred'], + 'unspecterlike': ['unspecterlike', 'unspectrelike'], + 'unspectrelike': ['unspecterlike', 'unspectrelike'], + 'unsped': ['unsped', 'upsend'], + 'unspelt': ['unslept', 'unspelt'], + 'unsphering': ['gunnership', 'unsphering'], + 'unspiable': ['subalpine', 'unspiable'], + 'unspike': ['spunkie', 'unspike'], + 'unspit': ['ptinus', 'unspit'], + 'unspoil': ['pulsion', 'unspoil', 'upsilon'], + 'unspot': ['pontus', 'unspot', 'unstop'], + 'unspread': ['undersap', 'unparsed', 'unrasped', 'unspared', 'unspread'], + 'unstabled': ['dunstable', 'unblasted', 'unstabled'], + 'unstain': ['antisun', 'unsaint', 'unsatin', 'unstain'], + 'unstained': ['unsainted', 'unstained'], + 'unstaled': ['unsalted', 'unslated', 'unstaled'], + 'unstanch': ['unsnatch', 'unstanch'], + 'unstar': ['saturn', 'unstar'], + 'unstatable': ['unstatable', 'untastable'], + 'unstate': ['tetanus', 'unstate', 'untaste'], + 'unstateable': ['unstateable', 'untasteable'], + 'unstated': ['unstated', 'untasted'], + 'unstating': ['unstating', 'untasting'], + 'unstayed': ['unstayed', 'unsteady'], + 'unsteady': ['unstayed', 'unsteady'], + 'unstercorated': ['countertrades', 'unstercorated'], + 'unstern': ['stunner', 'unstern'], + 'unstocked': ['duckstone', 'unstocked'], + 'unstoic': ['cotinus', 'suction', 'unstoic'], + 'unstoical': ['suctional', 'sulcation', 'unstoical'], + 'unstop': ['pontus', 'unspot', 'unstop'], + 'unstopple': ['pulpstone', 'unstopple'], + 'unstore': ['snouter', 'tonsure', 'unstore'], + 'unstored': ['tonsured', 'unsorted', 'unstored'], + 'unstoried': ['detrusion', 'tinderous', 'unstoried'], + 'unstormed': ['undermost', 'unstormed'], + 'unstrain': ['insurant', 'unstrain'], + 'unstrained': ['understain', 'unstrained'], + 'unstraitened': ['unreinstated', 'unstraitened'], + 'unstranded': ['understand', 'unstranded'], + 'unstrewed': ['unstrewed', 'unwrested'], + 'unsucculent': ['centunculus', 'unsucculent'], + 'unsued': ['unsued', 'unused'], + 'unsusceptible': ['unsusceptible', 'unsuspectible'], + 'unsusceptive': ['unsusceptive', 'unsuspective'], + 'unsuspectible': ['unsusceptible', 'unsuspectible'], + 'unsuspective': ['unsusceptive', 'unsuspective'], + 'untactful': ['fluctuant', 'untactful'], + 'untailed': ['nidulate', 'untailed'], + 'untame': ['unmate', 'untame', 'unteam'], + 'untamed': ['unmated', 'untamed'], + 'untanned': ['nunnated', 'untanned'], + 'untap': ['punta', 'unapt', 'untap'], + 'untar': ['arnut', 'tuarn', 'untar'], + 'untastable': ['unstatable', 'untastable'], + 'untaste': ['tetanus', 'unstate', 'untaste'], + 'untasteable': ['unstateable', 'untasteable'], + 'untasted': ['unstated', 'untasted'], + 'untasting': ['unstating', 'untasting'], + 'untaught': ['taungthu', 'untaught'], + 'untaunted': ['unattuned', 'untaunted'], + 'unteach': ['uncheat', 'unteach'], + 'unteaching': ['uncheating', 'unteaching'], + 'unteam': ['unmate', 'untame', 'unteam'], + 'unteamed': ['unmeated', 'unteamed'], + 'unteased': ['unseated', 'unsedate', 'unteased'], + 'unteem': ['unmeet', 'unteem'], + 'untemper': ['erumpent', 'untemper'], + 'untender': ['unrented', 'untender'], + 'untented': ['unnetted', 'untented'], + 'unthatch': ['nuthatch', 'unthatch'], + 'unthick': ['kutchin', 'unthick'], + 'unthrall': ['turnhall', 'unthrall'], + 'untiaraed': ['diuranate', 'untiaraed'], + 'untidy': ['nudity', 'untidy'], + 'untie': ['intue', 'unite', 'untie'], + 'untied': ['dunite', 'united', 'untied'], + 'until': ['unlit', 'until'], + 'untile': ['lutein', 'untile'], + 'untiled': ['diluent', 'untiled'], + 'untilted': ['dilutent', 'untilted', 'untitled'], + 'untimely': ['minutely', 'untimely'], + 'untin': ['nintu', 'ninut', 'untin'], + 'untine': ['ineunt', 'untine'], + 'untinseled': ['unenlisted', 'unlistened', 'untinseled'], + 'untirable': ['untirable', 'untriable'], + 'untire': ['runite', 'triune', 'uniter', 'untire'], + 'untired': ['intrude', 'turdine', 'untired', 'untried'], + 'untithable': ['unhittable', 'untithable'], + 'untitled': ['dilutent', 'untilted', 'untitled'], + 'unto': ['tuno', 'unto'], + 'untoiled': ['outlined', 'untoiled'], + 'untoned': ['unnoted', 'untoned'], + 'untooled': ['unlooted', 'untooled'], + 'untop': ['punto', 'unpot', 'untop'], + 'untorn': ['tunnor', 'untorn'], + 'untortured': ['undertutor', 'untortured'], + 'untotalled': ['unallotted', 'untotalled'], + 'untouch': ['uncouth', 'untouch'], + 'untoured': ['unrouted', 'untoured'], + 'untrace': ['centaur', 'untrace'], + 'untraceable': ['uncreatable', 'untraceable'], + 'untraceableness': ['uncreatableness', 'untraceableness'], + 'untraced': ['uncarted', 'uncrated', 'underact', 'untraced'], + 'untraceried': ['antireducer', 'reincrudate', 'untraceried'], + 'untradeable': ['untradeable', 'untreadable'], + 'untrain': ['antirun', 'untrain', 'urinant'], + 'untread': ['daunter', 'unarted', 'unrated', 'untread'], + 'untreadable': ['untradeable', 'untreadable'], + 'untreatable': ['entablature', 'untreatable'], + 'untreed': ['denture', 'untreed'], + 'untriable': ['untirable', 'untriable'], + 'untribal': ['tribunal', 'turbinal', 'untribal'], + 'untriced': ['undirect', 'untriced'], + 'untried': ['intrude', 'turdine', 'untired', 'untried'], + 'untrig': ['ungirt', 'untrig'], + 'untrod': ['rotund', 'untrod'], + 'untropical': ['ponticular', 'untropical'], + 'untroubled': ['outblunder', 'untroubled'], + 'untrowed': ['undertow', 'untrowed'], + 'untruss': ['sturnus', 'untruss'], + 'untutored': ['outturned', 'untutored'], + 'unurn': ['unrun', 'unurn'], + 'unused': ['unsued', 'unused'], + 'unusurped': ['unpursued', 'unusurped'], + 'unusurping': ['unpursuing', 'unusurping'], + 'unvailable': ['invaluable', 'unvailable'], + 'unveering': ['unreeving', 'unveering'], + 'unveil': ['unevil', 'unlive', 'unveil'], + 'unveiled': ['unlevied', 'unveiled'], + 'unveined': ['unenvied', 'unveined'], + 'unvenerated': ['unenervated', 'unvenerated'], + 'unveniable': ['unenviable', 'unveniable'], + 'unveritable': ['unavertible', 'unveritable'], + 'unversed': ['unserved', 'unversed'], + 'unvessel': ['unvessel', 'usselven'], + 'unvest': ['unvest', 'venust'], + 'unvomited': ['unmotived', 'unvomited'], + 'unwagered': ['underwage', 'unwagered'], + 'unwan': ['unwan', 'wunna'], + 'unware': ['unware', 'wauner'], + 'unwarp': ['unwarp', 'unwrap'], + 'unwary': ['runway', 'unwary'], + 'unwavered': ['underwave', 'unwavered'], + 'unwept': ['unwept', 'upwent'], + 'unwon': ['unown', 'unwon'], + 'unwrap': ['unwarp', 'unwrap'], + 'unwrested': ['unstrewed', 'unwrested'], + 'unwronged': ['undergown', 'unwronged'], + 'unyoung': ['unyoung', 'youngun'], + 'unze': ['unze', 'zenu'], + 'up': ['pu', 'up'], + 'uparching': ['ungraphic', 'uparching'], + 'uparise': ['spuriae', 'uparise', 'upraise'], + 'uparna': ['purana', 'uparna'], + 'upas': ['apus', 'supa', 'upas'], + 'upblast': ['subplat', 'upblast'], + 'upblow': ['blowup', 'upblow'], + 'upbreak': ['breakup', 'upbreak'], + 'upbuild': ['buildup', 'upbuild'], + 'upcast': ['catsup', 'upcast'], + 'upcatch': ['catchup', 'upcatch'], + 'upclimb': ['plumbic', 'upclimb'], + 'upclose': ['culpose', 'ploceus', 'upclose'], + 'upcock': ['cockup', 'upcock'], + 'upcoil': ['oilcup', 'upcoil'], + 'upcourse': ['cupreous', 'upcourse'], + 'upcover': ['overcup', 'upcover'], + 'upcreep': ['prepuce', 'upcreep'], + 'upcurrent': ['puncturer', 'upcurrent'], + 'upcut': ['cutup', 'upcut'], + 'updo': ['doup', 'updo'], + 'updraw': ['updraw', 'upward'], + 'updry': ['prudy', 'purdy', 'updry'], + 'upeat': ['taupe', 'upeat'], + 'upflare': ['rapeful', 'upflare'], + 'upflower': ['powerful', 'upflower'], + 'upgale': ['plague', 'upgale'], + 'upget': ['getup', 'upget'], + 'upgirt': ['ripgut', 'upgirt'], + 'upgo': ['goup', 'ogpu', 'upgo'], + 'upgrade': ['guepard', 'upgrade'], + 'uphelm': ['phleum', 'uphelm'], + 'uphold': ['holdup', 'uphold'], + 'upholder': ['reuphold', 'upholder'], + 'upholsterer': ['reupholster', 'upholsterer'], + 'upla': ['paul', 'upla'], + 'upland': ['dunlap', 'upland'], + 'uplander': ['pendular', 'underlap', 'uplander'], + 'uplane': ['unpale', 'uplane'], + 'upleap': ['papule', 'upleap'], + 'uplift': ['tipful', 'uplift'], + 'uplifter': ['reuplift', 'uplifter'], + 'upline': ['lupine', 'unpile', 'upline'], + 'uplock': ['lockup', 'uplock'], + 'upon': ['noup', 'puno', 'upon'], + 'uppers': ['supper', 'uppers'], + 'uppish': ['hippus', 'uppish'], + 'upraise': ['spuriae', 'uparise', 'upraise'], + 'uprear': ['parure', 'uprear'], + 'uprein': ['purine', 'unripe', 'uprein'], + 'uprip': ['ripup', 'uprip'], + 'uprisal': ['parulis', 'spirula', 'uprisal'], + 'uprisement': ['episternum', 'uprisement'], + 'upriser': ['siruper', 'upriser'], + 'uprist': ['purist', 'spruit', 'uprist', 'upstir'], + 'uproad': ['podura', 'uproad'], + 'uproom': ['moorup', 'uproom'], + 'uprose': ['poseur', 'pouser', 'souper', 'uprose'], + 'upscale': ['capsule', 'specula', 'upscale'], + 'upseal': ['apulse', 'upseal'], + 'upsend': ['unsped', 'upsend'], + 'upset': ['setup', 'stupe', 'upset'], + 'upsettable': ['subpeltate', 'upsettable'], + 'upsetter': ['upsetter', 'upstreet'], + 'upshore': ['ephorus', 'orpheus', 'upshore'], + 'upshot': ['tophus', 'upshot'], + 'upshut': ['pushtu', 'upshut'], + 'upsilon': ['pulsion', 'unspoil', 'upsilon'], + 'upsit': ['puist', 'upsit'], + 'upslant': ['pulsant', 'upslant'], + 'upsmite': ['impetus', 'upsmite'], + 'upsoar': ['parous', 'upsoar'], + 'upstair': ['tapirus', 'upstair'], + 'upstand': ['dustpan', 'upstand'], + 'upstare': ['pasteur', 'pasture', 'upstare'], + 'upstater': ['stuprate', 'upstater'], + 'upsteal': ['pulsate', 'spatule', 'upsteal'], + 'upstem': ['septum', 'upstem'], + 'upstir': ['purist', 'spruit', 'uprist', 'upstir'], + 'upstraight': ['straightup', 'upstraight'], + 'upstreet': ['upsetter', 'upstreet'], + 'upstrive': ['spurtive', 'upstrive'], + 'upsun': ['sunup', 'upsun'], + 'upsway': ['upsway', 'upways'], + 'uptake': ['ketupa', 'uptake'], + 'uptend': ['pudent', 'uptend'], + 'uptilt': ['tiltup', 'uptilt'], + 'uptoss': ['tossup', 'uptoss'], + 'uptrace': ['capture', 'uptrace'], + 'uptrain': ['pintura', 'puritan', 'uptrain'], + 'uptree': ['repute', 'uptree'], + 'uptrend': ['prudent', 'prunted', 'uptrend'], + 'upturn': ['turnup', 'upturn'], + 'upward': ['updraw', 'upward'], + 'upwarp': ['upwarp', 'upwrap'], + 'upways': ['upsway', 'upways'], + 'upwent': ['unwept', 'upwent'], + 'upwind': ['upwind', 'windup'], + 'upwrap': ['upwarp', 'upwrap'], + 'ura': ['aru', 'rua', 'ura'], + 'uracil': ['curial', 'lauric', 'uracil', 'uralic'], + 'uraemic': ['maurice', 'uraemic'], + 'uraeus': ['aureus', 'uraeus'], + 'ural': ['alur', 'laur', 'lura', 'raul', 'ural'], + 'urali': ['rauli', 'urali', 'urial'], + 'uralian': ['lunaria', 'ulnaria', 'uralian'], + 'uralic': ['curial', 'lauric', 'uracil', 'uralic'], + 'uralite': ['laurite', 'uralite'], + 'uralitize': ['ritualize', 'uralitize'], + 'uramido': ['doarium', 'uramido'], + 'uramil': ['rimula', 'uramil'], + 'uramino': ['mainour', 'uramino'], + 'uran': ['raun', 'uran', 'urna'], + 'uranate': ['taurean', 'uranate'], + 'urania': ['anuria', 'urania'], + 'uranic': ['anuric', 'cinura', 'uranic'], + 'uranine': ['aneurin', 'uranine'], + 'uranism': ['surinam', 'uranism'], + 'uranite': ['ruinate', 'taurine', 'uranite', 'urinate'], + 'uranographist': ['guarantorship', 'uranographist'], + 'uranolite': ['outlinear', 'uranolite'], + 'uranoscope': ['oenocarpus', 'uranoscope'], + 'uranospinite': ['resupination', 'uranospinite'], + 'uranotil': ['rotulian', 'uranotil'], + 'uranous': ['anurous', 'uranous'], + 'uranyl': ['lunary', 'uranyl'], + 'uranylic': ['culinary', 'uranylic'], + 'urari': ['aurir', 'urari'], + 'urase': ['serau', 'urase'], + 'uratic': ['tauric', 'uratic', 'urtica'], + 'urazine': ['azurine', 'urazine'], + 'urban': ['buran', 'unbar', 'urban'], + 'urbane': ['eburna', 'unbare', 'unbear', 'urbane'], + 'urbanite': ['braunite', 'urbanite', 'urbinate'], + 'urbian': ['burian', 'urbian'], + 'urbification': ['rubification', 'urbification'], + 'urbify': ['rubify', 'urbify'], + 'urbinate': ['braunite', 'urbanite', 'urbinate'], + 'urceiform': ['eruciform', 'urceiform'], + 'urceole': ['urceole', 'urocele'], + 'urceolina': ['aleuronic', 'urceolina'], + 'urceolus': ['ulcerous', 'urceolus'], + 'urchin': ['unrich', 'urchin'], + 'urd': ['rud', 'urd'], + 'urde': ['duer', 'dure', 'rude', 'urde'], + 'urdee': ['redue', 'urdee'], + 'ure': ['rue', 'ure'], + 'ureal': ['alure', 'ureal'], + 'uredine': ['reindue', 'uredine'], + 'ureic': ['curie', 'ureic'], + 'uremia': ['aumrie', 'uremia'], + 'uremic': ['cerium', 'uremic'], + 'urena': ['urena', 'urnae'], + 'urent': ['enrut', 'tuner', 'urent'], + 'uresis': ['issuer', 'uresis'], + 'uretal': ['tulare', 'uretal'], + 'ureter': ['retrue', 'ureter'], + 'ureteropyelogram': ['pyeloureterogram', 'ureteropyelogram'], + 'urethan': ['haunter', 'nauther', 'unearth', 'unheart', 'urethan'], + 'urethrascope': ['heterocarpus', 'urethrascope'], + 'urethrocystitis': ['cystourethritis', 'urethrocystitis'], + 'urethylan': ['unearthly', 'urethylan'], + 'uretic': ['curite', 'teucri', 'uretic'], + 'urf': ['fur', 'urf'], + 'urge': ['grue', 'urge'], + 'urgent': ['gunter', 'gurnet', 'urgent'], + 'urger': ['regur', 'urger'], + 'uria': ['arui', 'uria'], + 'uriah': ['huari', 'uriah'], + 'urial': ['rauli', 'urali', 'urial'], + 'urian': ['aurin', 'urian'], + 'uric': ['cuir', 'uric'], + 'urinal': ['laurin', 'urinal'], + 'urinant': ['antirun', 'untrain', 'urinant'], + 'urinate': ['ruinate', 'taurine', 'uranite', 'urinate'], + 'urination': ['ruination', 'urination'], + 'urinator': ['ruinator', 'urinator'], + 'urine': ['inure', 'urine'], + 'urinogenitary': ['genitourinary', 'urinogenitary'], + 'urinous': ['ruinous', 'urinous'], + 'urinousness': ['ruinousness', 'urinousness'], + 'urite': ['urite', 'uteri'], + 'urlar': ['rural', 'urlar'], + 'urled': ['duler', 'urled'], + 'urling': ['ruling', 'urling'], + 'urman': ['muran', 'ruman', 'unarm', 'unram', 'urman'], + 'urn': ['run', 'urn'], + 'urna': ['raun', 'uran', 'urna'], + 'urnae': ['urena', 'urnae'], + 'urnal': ['lunar', 'ulnar', 'urnal'], + 'urnful': ['unfurl', 'urnful'], + 'urning': ['unring', 'urning'], + 'uro': ['our', 'uro'], + 'urocele': ['urceole', 'urocele'], + 'urodela': ['roulade', 'urodela'], + 'urodelan': ['unloader', 'urodelan'], + 'urogaster': ['surrogate', 'urogaster'], + 'urogenital': ['regulation', 'urogenital'], + 'uroglena': ['lagunero', 'organule', 'uroglena'], + 'urolithic': ['ulotrichi', 'urolithic'], + 'urometer': ['outremer', 'urometer'], + 'uronic': ['cuorin', 'uronic'], + 'uropsile': ['perilous', 'uropsile'], + 'uroseptic': ['crepitous', 'euproctis', 'uroseptic'], + 'urosomatic': ['mortacious', 'urosomatic'], + 'urosteon': ['outsnore', 'urosteon'], + 'urosternite': ['tenuiroster', 'urosternite'], + 'urosthenic': ['cetorhinus', 'urosthenic'], + 'urostyle': ['elytrous', 'urostyle'], + 'urs': ['rus', 'sur', 'urs'], + 'ursa': ['rusa', 'saur', 'sura', 'ursa', 'usar'], + 'ursal': ['larus', 'sural', 'ursal'], + 'ursidae': ['residua', 'ursidae'], + 'ursine': ['insure', 'rusine', 'ursine'], + 'ursone': ['souren', 'unsore', 'ursone'], + 'ursuk': ['kurus', 'ursuk'], + 'ursula': ['laurus', 'ursula'], + 'urtica': ['tauric', 'uratic', 'urtica'], + 'urticales': ['sterculia', 'urticales'], + 'urticant': ['taciturn', 'urticant'], + 'urticose': ['citreous', 'urticose'], + 'usability': ['suability', 'usability'], + 'usable': ['suable', 'usable'], + 'usager': ['sauger', 'usager'], + 'usance': ['uncase', 'usance'], + 'usar': ['rusa', 'saur', 'sura', 'ursa', 'usar'], + 'usara': ['arusa', 'saura', 'usara'], + 'use': ['sue', 'use'], + 'usent': ['unset', 'usent'], + 'user': ['ruse', 'suer', 'sure', 'user'], + 'ush': ['shu', 'ush'], + 'ushabti': ['habitus', 'ushabti'], + 'ushak': ['kusha', 'shaku', 'ushak'], + 'usher': ['shure', 'usher'], + 'usitate': ['situate', 'usitate'], + 'usnic': ['incus', 'usnic'], + 'usque': ['equus', 'usque'], + 'usselven': ['unvessel', 'usselven'], + 'ust': ['stu', 'ust'], + 'uster': ['serut', 'strue', 'turse', 'uster'], + 'ustion': ['outsin', 'ustion'], + 'ustorious': ['sutorious', 'ustorious'], + 'ustulina': ['lutianus', 'nautilus', 'ustulina'], + 'usucaption': ['uncaptious', 'usucaption'], + 'usure': ['eurus', 'usure'], + 'usurper': ['pursuer', 'usurper'], + 'ut': ['tu', 'ut'], + 'uta': ['tau', 'tua', 'uta'], + 'utas': ['saut', 'tasu', 'utas'], + 'utch': ['chut', 'tchu', 'utch'], + 'ute': ['tue', 'ute'], + 'uteri': ['urite', 'uteri'], + 'uterine': ['neurite', 'retinue', 'reunite', 'uterine'], + 'uterocervical': ['overcirculate', 'uterocervical'], + 'uterus': ['suture', 'uterus'], + 'utile': ['luite', 'utile'], + 'utilizable': ['latibulize', 'utilizable'], + 'utopian': ['opuntia', 'utopian'], + 'utopism': ['positum', 'utopism'], + 'utopist': ['outspit', 'utopist'], + 'utricular': ['turricula', 'utricular'], + 'utriculosaccular': ['sacculoutricular', 'utriculosaccular'], + 'utrum': ['murut', 'utrum'], + 'utterer': ['reutter', 'utterer'], + 'uva': ['uva', 'vau'], + 'uval': ['ulva', 'uval'], + 'uveal': ['uveal', 'value'], + 'uviol': ['uviol', 'vouli'], + 'vacate': ['cavate', 'caveat', 'vacate'], + 'vacation': ['octavian', 'octavina', 'vacation'], + 'vacationer': ['acervation', 'vacationer'], + 'vaccinal': ['clavacin', 'vaccinal'], + 'vacillate': ['laticlave', 'vacillate'], + 'vacillation': ['cavillation', 'vacillation'], + 'vacuolate': ['autoclave', 'vacuolate'], + 'vade': ['dave', 'deva', 'vade', 'veda'], + 'vady': ['davy', 'vady'], + 'vage': ['gave', 'vage', 'vega'], + 'vagile': ['glaive', 'vagile'], + 'vaginant': ['navigant', 'vaginant'], + 'vaginate': ['navigate', 'vaginate'], + 'vaginoabdominal': ['abdominovaginal', 'vaginoabdominal'], + 'vaginoperineal': ['perineovaginal', 'vaginoperineal'], + 'vaginovesical': ['vaginovesical', 'vesicovaginal'], + 'vaguity': ['gavyuti', 'vaguity'], + 'vai': ['iva', 'vai', 'via'], + 'vail': ['vail', 'vali', 'vial', 'vila'], + 'vain': ['ivan', 'vain', 'vina'], + 'vair': ['ravi', 'riva', 'vair', 'vari', 'vira'], + 'vakass': ['kavass', 'vakass'], + 'vale': ['lave', 'vale', 'veal', 'vela'], + 'valence': ['enclave', 'levance', 'valence'], + 'valencia': ['valencia', 'valiance'], + 'valent': ['levant', 'valent'], + 'valentine': ['levantine', 'valentine'], + 'valeria': ['reavail', 'valeria'], + 'valeriana': ['laverania', 'valeriana'], + 'valeric': ['caliver', 'caviler', 'claiver', 'clavier', 'valeric', 'velaric'], + 'valerie': ['realive', 'valerie'], + 'valerin': ['elinvar', 'ravelin', 'reanvil', 'valerin'], + 'valerone': ['overlean', 'valerone'], + 'valeryl': ['ravelly', 'valeryl'], + 'valeur': ['valeur', 'valuer'], + 'vali': ['vail', 'vali', 'vial', 'vila'], + 'valiance': ['valencia', 'valiance'], + 'valiant': ['latvian', 'valiant'], + 'valine': ['alevin', 'alvine', 'valine', 'veinal', 'venial', 'vineal'], + 'vallar': ['larval', 'vallar'], + 'vallidom': ['vallidom', 'villadom'], + 'vallota': ['lavolta', 'vallota'], + 'valonia': ['novalia', 'valonia'], + 'valor': ['valor', 'volar'], + 'valsa': ['salva', 'valsa', 'vasal'], + 'valse': ['salve', 'selva', 'slave', 'valse'], + 'value': ['uveal', 'value'], + 'valuer': ['valeur', 'valuer'], + 'vamper': ['revamp', 'vamper'], + 'vane': ['evan', 'nave', 'vane'], + 'vaned': ['daven', 'vaned'], + 'vangee': ['avenge', 'geneva', 'vangee'], + 'vangeli': ['leaving', 'vangeli'], + 'vanir': ['invar', 'ravin', 'vanir'], + 'vanisher': ['enravish', 'ravenish', 'vanisher'], + 'vansire': ['servian', 'vansire'], + 'vapid': ['pavid', 'vapid'], + 'vapidity': ['pavidity', 'vapidity'], + 'vaporarium': ['parovarium', 'vaporarium'], + 'vara': ['avar', 'vara'], + 'varan': ['navar', 'varan', 'varna'], + 'vare': ['aver', 'rave', 'vare', 'vera'], + 'varec': ['carve', 'crave', 'varec'], + 'vari': ['ravi', 'riva', 'vair', 'vari', 'vira'], + 'variate': ['variate', 'vateria'], + 'varices': ['varices', 'viscera'], + 'varicula': ['avicular', 'varicula'], + 'variegator': ['arrogative', 'variegator'], + 'varier': ['arrive', 'varier'], + 'varietal': ['lievaart', 'varietal'], + 'variola': ['ovarial', 'variola'], + 'various': ['saviour', 'various'], + 'varlet': ['travel', 'varlet'], + 'varletry': ['varletry', 'veratryl'], + 'varna': ['navar', 'varan', 'varna'], + 'varnish': ['shirvan', 'varnish'], + 'varnisher': ['revarnish', 'varnisher'], + 'varsha': ['avshar', 'varsha'], + 'vasal': ['salva', 'valsa', 'vasal'], + 'vase': ['aves', 'save', 'vase'], + 'vasoepididymostomy': ['epididymovasostomy', 'vasoepididymostomy'], + 'vat': ['tav', 'vat'], + 'vateria': ['variate', 'vateria'], + 'vaticide': ['cavitied', 'vaticide'], + 'vaticinate': ['inactivate', 'vaticinate'], + 'vaticination': ['inactivation', 'vaticination'], + 'vatter': ['tavert', 'vatter'], + 'vau': ['uva', 'vau'], + 'vaudois': ['avidous', 'vaudois'], + 'veal': ['lave', 'vale', 'veal', 'vela'], + 'vealer': ['laveer', 'leaver', 'reveal', 'vealer'], + 'vealiness': ['aliveness', 'vealiness'], + 'vealy': ['leavy', 'vealy'], + 'vector': ['covert', 'vector'], + 'veda': ['dave', 'deva', 'vade', 'veda'], + 'vedaic': ['advice', 'vedaic'], + 'vedaism': ['adevism', 'vedaism'], + 'vedana': ['nevada', 'vedana', 'venada'], + 'vedanta': ['vedanta', 'vetanda'], + 'vedantism': ['adventism', 'vedantism'], + 'vedantist': ['adventist', 'vedantist'], + 'vedist': ['divest', 'vedist'], + 'vedro': ['dover', 'drove', 'vedro'], + 'vee': ['eve', 'vee'], + 'veen': ['even', 'neve', 'veen'], + 'veer': ['ever', 'reve', 'veer'], + 'veery': ['every', 'veery'], + 'vega': ['gave', 'vage', 'vega'], + 'vegasite': ['estivage', 'vegasite'], + 'vegetarian': ['renavigate', 'vegetarian'], + 'vei': ['vei', 'vie'], + 'veil': ['evil', 'levi', 'live', 'veil', 'vile', 'vlei'], + 'veiler': ['levier', 'relive', 'reveil', 'revile', 'veiler'], + 'veiltail': ['illative', 'veiltail'], + 'vein': ['vein', 'vine'], + 'veinal': ['alevin', 'alvine', 'valine', 'veinal', 'venial', 'vineal'], + 'veined': ['endive', 'envied', 'veined'], + 'veiner': ['enrive', 'envier', 'veiner', 'verine'], + 'veinless': ['evilness', 'liveness', 'veinless', 'vileness', 'vineless'], + 'veinlet': ['veinlet', 'vinelet'], + 'veinous': ['envious', 'niveous', 'veinous'], + 'veinstone': ['veinstone', 'vonsenite'], + 'veinwise': ['veinwise', 'vinewise'], + 'vela': ['lave', 'vale', 'veal', 'vela'], + 'velar': ['arvel', 'larve', 'laver', 'ravel', 'velar'], + 'velaric': ['caliver', 'caviler', 'claiver', 'clavier', 'valeric', 'velaric'], + 'velation': ['olivetan', 'velation'], + 'velic': ['clive', 'velic'], + 'veliform': ['overfilm', 'veliform'], + 'velitation': ['levitation', 'tonalitive', 'velitation'], + 'velo': ['levo', 'love', 'velo', 'vole'], + 'velte': ['elvet', 'velte'], + 'venada': ['nevada', 'vedana', 'venada'], + 'venal': ['elvan', 'navel', 'venal'], + 'venality': ['natively', 'venality'], + 'venatic': ['catvine', 'venatic'], + 'venation': ['innovate', 'venation'], + 'venator': ['rotanev', 'venator'], + 'venatorial': ['venatorial', 'venoatrial'], + 'vendace': ['devance', 'vendace'], + 'vender': ['revend', 'vender'], + 'veneer': ['evener', 'veneer'], + 'veneerer': ['reveneer', 'veneerer'], + 'veneralia': ['ravenelia', 'veneralia'], + 'venerant': ['revenant', 'venerant'], + 'venerate': ['enervate', 'venerate'], + 'veneration': ['enervation', 'veneration'], + 'venerative': ['enervative', 'venerative'], + 'venerator': ['enervator', 'renovater', 'venerator'], + 'venerer': ['renerve', 'venerer'], + 'veneres': ['sevener', 'veneres'], + 'veneti': ['veneti', 'venite'], + 'venetian': ['aventine', 'venetian'], + 'venial': ['alevin', 'alvine', 'valine', 'veinal', 'venial', 'vineal'], + 'venice': ['cevine', 'evince', 'venice'], + 'venie': ['nieve', 'venie'], + 'venite': ['veneti', 'venite'], + 'venoatrial': ['venatorial', 'venoatrial'], + 'venom': ['novem', 'venom'], + 'venosinal': ['slovenian', 'venosinal'], + 'venter': ['revent', 'venter'], + 'ventrad': ['ventrad', 'verdant'], + 'ventricose': ['convertise', 'ventricose'], + 'ventrine': ['inventer', 'reinvent', 'ventrine', 'vintener'], + 'ventrodorsad': ['dorsoventrad', 'ventrodorsad'], + 'ventrodorsal': ['dorsoventral', 'ventrodorsal'], + 'ventrodorsally': ['dorsoventrally', 'ventrodorsally'], + 'ventrolateral': ['lateroventral', 'ventrolateral'], + 'ventromedial': ['medioventral', 'ventromedial'], + 'ventromesal': ['mesoventral', 'ventromesal'], + 'venular': ['unravel', 'venular'], + 'venus': ['nevus', 'venus'], + 'venust': ['unvest', 'venust'], + 'venutian': ['unnative', 'venutian'], + 'vera': ['aver', 'rave', 'vare', 'vera'], + 'veraciousness': ['oversauciness', 'veraciousness'], + 'veratroidine': ['rederivation', 'veratroidine'], + 'veratrole': ['relevator', 'revelator', 'veratrole'], + 'veratryl': ['varletry', 'veratryl'], + 'verbal': ['barvel', 'blaver', 'verbal'], + 'verbality': ['verbality', 'veritably'], + 'verbatim': ['ambivert', 'verbatim'], + 'verbena': ['enbrave', 'verbena'], + 'verberate': ['verberate', 'vertebrae'], + 'verbose': ['observe', 'obverse', 'verbose'], + 'verbosely': ['obversely', 'verbosely'], + 'verdant': ['ventrad', 'verdant'], + 'verdea': ['evader', 'verdea'], + 'verdelho': ['overheld', 'verdelho'], + 'verdin': ['driven', 'nervid', 'verdin'], + 'verditer': ['diverter', 'redivert', 'verditer'], + 'vergi': ['giver', 'vergi'], + 'veri': ['rive', 'veri', 'vier', 'vire'], + 'veridical': ['larvicide', 'veridical'], + 'veridicous': ['recidivous', 'veridicous'], + 'verily': ['livery', 'verily'], + 'verine': ['enrive', 'envier', 'veiner', 'verine'], + 'verism': ['verism', 'vermis'], + 'verist': ['stiver', 'strive', 'verist'], + 'veritable': ['avertible', 'veritable'], + 'veritably': ['verbality', 'veritably'], + 'vermian': ['minerva', 'vermian'], + 'verminal': ['minerval', 'verminal'], + 'vermis': ['verism', 'vermis'], + 'vernacularist': ['intervascular', 'vernacularist'], + 'vernal': ['nerval', 'vernal'], + 'vernation': ['nervation', 'vernation'], + 'vernicose': ['coversine', 'vernicose'], + 'vernine': ['innerve', 'nervine', 'vernine'], + 'veronese': ['overseen', 'veronese'], + 'veronica': ['corvinae', 'veronica'], + 'verpa': ['paver', 'verpa'], + 'verre': ['rever', 'verre'], + 'verrucous': ['recurvous', 'verrucous'], + 'verruga': ['gravure', 'verruga'], + 'versable': ['beslaver', 'servable', 'versable'], + 'versal': ['salver', 'serval', 'slaver', 'versal'], + 'versant': ['servant', 'versant'], + 'versate': ['evestar', 'versate'], + 'versation': ['overstain', 'servation', 'versation'], + 'verse': ['serve', 'sever', 'verse'], + 'verser': ['revers', 'server', 'verser'], + 'verset': ['revest', 'servet', 'sterve', 'verset', 'vester'], + 'versicule': ['reclusive', 'versicule'], + 'versine': ['inverse', 'versine'], + 'versioner': ['reversion', 'versioner'], + 'versionist': ['overinsist', 'versionist'], + 'verso': ['servo', 'verso'], + 'versta': ['starve', 'staver', 'strave', 'tavers', 'versta'], + 'vertebrae': ['verberate', 'vertebrae'], + 'vertebrocostal': ['costovertebral', 'vertebrocostal'], + 'vertebrosacral': ['sacrovertebral', 'vertebrosacral'], + 'vertebrosternal': ['sternovertebral', 'vertebrosternal'], + 'vertiginate': ['integrative', 'vertiginate', 'vinaigrette'], + 'vesicant': ['cistvaen', 'vesicant'], + 'vesicoabdominal': ['abdominovesical', 'vesicoabdominal'], + 'vesicocervical': ['cervicovesical', 'vesicocervical'], + 'vesicointestinal': ['intestinovesical', 'vesicointestinal'], + 'vesicorectal': ['rectovesical', 'vesicorectal'], + 'vesicovaginal': ['vaginovesical', 'vesicovaginal'], + 'vespa': ['spave', 'vespa'], + 'vespertine': ['presentive', 'pretensive', 'vespertine'], + 'vespine': ['pensive', 'vespine'], + 'vesta': ['stave', 'vesta'], + 'vestalia': ['salivate', 'vestalia'], + 'vestee': ['steeve', 'vestee'], + 'vester': ['revest', 'servet', 'sterve', 'verset', 'vester'], + 'vestibula': ['sublative', 'vestibula'], + 'veta': ['tave', 'veta'], + 'vetanda': ['vedanta', 'vetanda'], + 'veteran': ['nervate', 'veteran'], + 'veto': ['veto', 'voet', 'vote'], + 'vetoer': ['revote', 'vetoer'], + 'via': ['iva', 'vai', 'via'], + 'vial': ['vail', 'vali', 'vial', 'vila'], + 'vialful': ['fluavil', 'fluvial', 'vialful'], + 'viand': ['divan', 'viand'], + 'viander': ['invader', 'ravined', 'viander'], + 'viatic': ['avitic', 'viatic'], + 'viatica': ['aviatic', 'viatica'], + 'vibrate': ['vibrate', 'vrbaite'], + 'vicar': ['vicar', 'vraic'], + 'vice': ['cive', 'vice'], + 'vicegeral': ['vicegeral', 'viceregal'], + 'viceregal': ['vicegeral', 'viceregal'], + 'victoriate': ['recitativo', 'victoriate'], + 'victrola': ['victrola', 'vortical'], + 'victualer': ['lucrative', 'revictual', 'victualer'], + 'vidonia': ['ovidian', 'vidonia'], + 'viduinae': ['induviae', 'viduinae'], + 'viduine': ['univied', 'viduine'], + 'vie': ['vei', 'vie'], + 'vienna': ['avenin', 'vienna'], + 'vier': ['rive', 'veri', 'vier', 'vire'], + 'vierling': ['reviling', 'vierling'], + 'view': ['view', 'wive'], + 'viewer': ['review', 'viewer'], + 'vigilante': ['genitival', 'vigilante'], + 'vigor': ['vigor', 'virgo'], + 'vila': ['vail', 'vali', 'vial', 'vila'], + 'vile': ['evil', 'levi', 'live', 'veil', 'vile', 'vlei'], + 'vilehearted': ['evilhearted', 'vilehearted'], + 'vilely': ['evilly', 'lively', 'vilely'], + 'vileness': ['evilness', 'liveness', 'veinless', 'vileness', 'vineless'], + 'villadom': ['vallidom', 'villadom'], + 'villeiness': ['liveliness', 'villeiness'], + 'villous': ['ovillus', 'villous'], + 'vimana': ['maniva', 'vimana'], + 'vina': ['ivan', 'vain', 'vina'], + 'vinaigrette': ['integrative', 'vertiginate', 'vinaigrette'], + 'vinaigrous': ['vinaigrous', 'viraginous'], + 'vinal': ['alvin', 'anvil', 'nival', 'vinal'], + 'vinalia': ['lavinia', 'vinalia'], + 'vinata': ['avanti', 'vinata'], + 'vinculate': ['vinculate', 'vulcanite'], + 'vine': ['vein', 'vine'], + 'vinea': ['avine', 'naive', 'vinea'], + 'vineal': ['alevin', 'alvine', 'valine', 'veinal', 'venial', 'vineal'], + 'vineatic': ['antivice', 'inactive', 'vineatic'], + 'vinegarist': ['gainstrive', 'vinegarist'], + 'vineless': ['evilness', 'liveness', 'veinless', 'vileness', 'vineless'], + 'vinelet': ['veinlet', 'vinelet'], + 'viner': ['riven', 'viner'], + 'vinewise': ['veinwise', 'vinewise'], + 'vinosity': ['nivosity', 'vinosity'], + 'vintener': ['inventer', 'reinvent', 'ventrine', 'vintener'], + 'vintneress': ['inventress', 'vintneress'], + 'viola': ['oliva', 'viola'], + 'violability': ['obliviality', 'violability'], + 'violaceous': ['olivaceous', 'violaceous'], + 'violanin': ['livonian', 'violanin'], + 'violational': ['avolitional', 'violational'], + 'violer': ['oliver', 'violer', 'virole'], + 'violescent': ['olivescent', 'violescent'], + 'violet': ['olivet', 'violet'], + 'violette': ['olivette', 'violette'], + 'violine': ['olivine', 'violine'], + 'vipera': ['pavier', 'vipera'], + 'viperess': ['pressive', 'viperess'], + 'viperian': ['viperian', 'viperina'], + 'viperina': ['viperian', 'viperina'], + 'viperous': ['pervious', 'previous', 'viperous'], + 'viperously': ['perviously', 'previously', 'viperously'], + 'viperousness': ['perviousness', 'previousness', 'viperousness'], + 'vira': ['ravi', 'riva', 'vair', 'vari', 'vira'], + 'viraginian': ['irvingiana', 'viraginian'], + 'viraginous': ['vinaigrous', 'viraginous'], + 'viral': ['rival', 'viral'], + 'virales': ['revisal', 'virales'], + 'vire': ['rive', 'veri', 'vier', 'vire'], + 'virent': ['invert', 'virent'], + 'virgate': ['virgate', 'vitrage'], + 'virgin': ['irving', 'riving', 'virgin'], + 'virginly': ['rivingly', 'virginly'], + 'virgo': ['vigor', 'virgo'], + 'virile': ['livier', 'virile'], + 'virole': ['oliver', 'violer', 'virole'], + 'virose': ['rivose', 'virose'], + 'virtual': ['virtual', 'vitular'], + 'virtuose': ['virtuose', 'vitreous'], + 'virulence': ['cervuline', 'virulence'], + 'visa': ['avis', 'siva', 'visa'], + 'viscera': ['varices', 'viscera'], + 'visceration': ['insectivora', 'visceration'], + 'visceroparietal': ['parietovisceral', 'visceroparietal'], + 'visceropleural': ['pleurovisceral', 'visceropleural'], + 'viscometer': ['semivector', 'viscometer'], + 'viscontal': ['viscontal', 'volcanist'], + 'vishal': ['lavish', 'vishal'], + 'visioner': ['revision', 'visioner'], + 'visit': ['visit', 'vitis'], + 'visitant': ['nativist', 'visitant'], + 'visitee': ['evisite', 'visitee'], + 'visiter': ['revisit', 'visiter'], + 'visitor': ['ivorist', 'visitor'], + 'vistal': ['vistal', 'vitals'], + 'visto': ['ovist', 'visto'], + 'vitals': ['vistal', 'vitals'], + 'vitis': ['visit', 'vitis'], + 'vitochemical': ['chemicovital', 'vitochemical'], + 'vitrage': ['virgate', 'vitrage'], + 'vitrail': ['trivial', 'vitrail'], + 'vitrailist': ['trivialist', 'vitrailist'], + 'vitrain': ['vitrain', 'vitrina'], + 'vitrean': ['avertin', 'vitrean'], + 'vitreous': ['virtuose', 'vitreous'], + 'vitrina': ['vitrain', 'vitrina'], + 'vitrine': ['inviter', 'vitrine'], + 'vitrophyric': ['thyroprivic', 'vitrophyric'], + 'vitular': ['virtual', 'vitular'], + 'vituperate': ['reputative', 'vituperate'], + 'vlei': ['evil', 'levi', 'live', 'veil', 'vile', 'vlei'], + 'vocaller': ['overcall', 'vocaller'], + 'vocate': ['avocet', 'octave', 'vocate'], + 'voet': ['veto', 'voet', 'vote'], + 'voeten': ['voeten', 'voteen'], + 'vogue': ['vogue', 'vouge'], + 'voided': ['devoid', 'voided'], + 'voider': ['devoir', 'voider'], + 'voidless': ['dissolve', 'voidless'], + 'voile': ['olive', 'ovile', 'voile'], + 'volable': ['lovable', 'volable'], + 'volage': ['lovage', 'volage'], + 'volar': ['valor', 'volar'], + 'volata': ['tavola', 'volata'], + 'volatic': ['volatic', 'voltaic'], + 'volcae': ['alcove', 'coeval', 'volcae'], + 'volcanist': ['viscontal', 'volcanist'], + 'vole': ['levo', 'love', 'velo', 'vole'], + 'volery': ['overly', 'volery'], + 'volitate': ['volitate', 'voltaite'], + 'volley': ['lovely', 'volley'], + 'volscian': ['slavonic', 'volscian'], + 'volta': ['volta', 'votal'], + 'voltaic': ['volatic', 'voltaic'], + 'voltaite': ['volitate', 'voltaite'], + 'volucrine': ['involucre', 'volucrine'], + 'volunteerism': ['multinervose', 'volunteerism'], + 'vomer': ['mover', 'vomer'], + 'vomiter': ['revomit', 'vomiter'], + 'vonsenite': ['veinstone', 'vonsenite'], + 'vortical': ['victrola', 'vortical'], + 'votal': ['volta', 'votal'], + 'votary': ['travoy', 'votary'], + 'vote': ['veto', 'voet', 'vote'], + 'voteen': ['voeten', 'voteen'], + 'voter': ['overt', 'rovet', 'torve', 'trove', 'voter'], + 'vouge': ['vogue', 'vouge'], + 'vouli': ['uviol', 'vouli'], + 'vowed': ['devow', 'vowed'], + 'vowel': ['vowel', 'wolve'], + 'vraic': ['vicar', 'vraic'], + 'vrbaite': ['vibrate', 'vrbaite'], + 'vulcanite': ['vinculate', 'vulcanite'], + 'vulnerose': ['nervulose', 'unresolve', 'vulnerose'], + 'vulpic': ['pulvic', 'vulpic'], + 'vulpine': ['pluvine', 'vulpine'], + 'wa': ['aw', 'wa'], + 'waag': ['awag', 'waag'], + 'waasi': ['isawa', 'waasi'], + 'wab': ['baw', 'wab'], + 'wabi': ['biwa', 'wabi'], + 'wabster': ['bestraw', 'wabster'], + 'wac': ['caw', 'wac'], + 'wachna': ['chawan', 'chwana', 'wachna'], + 'wack': ['cawk', 'wack'], + 'wacker': ['awreck', 'wacker'], + 'wacky': ['cawky', 'wacky'], + 'wad': ['awd', 'daw', 'wad'], + 'wadder': ['edward', 'wadder', 'warded'], + 'waddler': ['dawdler', 'waddler'], + 'waddling': ['dawdling', 'waddling'], + 'waddlingly': ['dawdlingly', 'waddlingly'], + 'waddy': ['dawdy', 'waddy'], + 'wadna': ['adawn', 'wadna'], + 'wadset': ['wadset', 'wasted'], + 'wae': ['awe', 'wae', 'wea'], + 'waeg': ['waeg', 'wage', 'wega'], + 'waer': ['waer', 'ware', 'wear'], + 'waesome': ['awesome', 'waesome'], + 'wag': ['gaw', 'wag'], + 'wage': ['waeg', 'wage', 'wega'], + 'wagerer': ['rewager', 'wagerer'], + 'wages': ['swage', 'wages'], + 'waggel': ['waggel', 'waggle'], + 'waggle': ['waggel', 'waggle'], + 'wagnerite': ['wagnerite', 'winterage'], + 'wagon': ['gowan', 'wagon', 'wonga'], + 'wah': ['haw', 'hwa', 'wah', 'wha'], + 'wahehe': ['heehaw', 'wahehe'], + 'wail': ['wail', 'wali'], + 'wailer': ['lawrie', 'wailer'], + 'wain': ['awin', 'wain'], + 'wainer': ['newari', 'wainer'], + 'wairsh': ['rawish', 'wairsh', 'warish'], + 'waist': ['swati', 'waist'], + 'waister': ['swertia', 'waister'], + 'waiterage': ['garewaite', 'waiterage'], + 'waitress': ['starwise', 'waitress'], + 'waiwai': ['iwaiwa', 'waiwai'], + 'wake': ['wake', 'weak', 'weka'], + 'wakener': ['rewaken', 'wakener'], + 'waker': ['waker', 'wreak'], + 'wakes': ['askew', 'wakes'], + 'wale': ['wale', 'weal'], + 'waled': ['dwale', 'waled', 'weald'], + 'waler': ['lerwa', 'waler'], + 'wali': ['wail', 'wali'], + 'waling': ['lawing', 'waling'], + 'walk': ['lawk', 'walk'], + 'walkout': ['outwalk', 'walkout'], + 'walkover': ['overwalk', 'walkover'], + 'walkside': ['sidewalk', 'walkside'], + 'waller': ['rewall', 'waller'], + 'wallet': ['wallet', 'wellat'], + 'wallhick': ['hickwall', 'wallhick'], + 'walloper': ['preallow', 'walloper'], + 'wallower': ['rewallow', 'wallower'], + 'walsh': ['shawl', 'walsh'], + 'walt': ['twal', 'walt'], + 'walter': ['lawter', 'walter'], + 'wame': ['wame', 'weam'], + 'wamp': ['mawp', 'wamp'], + 'wan': ['awn', 'naw', 'wan'], + 'wand': ['dawn', 'wand'], + 'wander': ['andrew', 'redawn', 'wander', 'warden'], + 'wandle': ['delawn', 'lawned', 'wandle'], + 'wandlike': ['dawnlike', 'wandlike'], + 'wandy': ['dawny', 'wandy'], + 'wane': ['anew', 'wane', 'wean'], + 'waned': ['awned', 'dewan', 'waned'], + 'wang': ['gawn', 'gnaw', 'wang'], + 'wanghee': ['wanghee', 'whangee'], + 'wangler': ['wangler', 'wrangle'], + 'waning': ['awning', 'waning'], + 'wankle': ['knawel', 'wankle'], + 'wanly': ['lawny', 'wanly'], + 'want': ['nawt', 'tawn', 'want'], + 'wanty': ['tawny', 'wanty'], + 'wany': ['awny', 'wany', 'yawn'], + 'wap': ['paw', 'wap'], + 'war': ['raw', 'war'], + 'warble': ['bawler', 'brelaw', 'rebawl', 'warble'], + 'warbler': ['brawler', 'warbler'], + 'warbling': ['brawling', 'warbling'], + 'warblingly': ['brawlingly', 'warblingly'], + 'warbly': ['brawly', 'byrlaw', 'warbly'], + 'ward': ['draw', 'ward'], + 'wardable': ['drawable', 'wardable'], + 'warded': ['edward', 'wadder', 'warded'], + 'warden': ['andrew', 'redawn', 'wander', 'warden'], + 'warder': ['drawer', 'redraw', 'reward', 'warder'], + 'warderer': ['redrawer', 'rewarder', 'warderer'], + 'warding': ['drawing', 'ginward', 'warding'], + 'wardman': ['manward', 'wardman'], + 'wardmote': ['damewort', 'wardmote'], + 'wardrobe': ['drawbore', 'wardrobe'], + 'wardroom': ['roomward', 'wardroom'], + 'wardship': ['shipward', 'wardship'], + 'wardsman': ['manwards', 'wardsman'], + 'ware': ['waer', 'ware', 'wear'], + 'warehouse': ['housewear', 'warehouse'], + 'warf': ['warf', 'wraf'], + 'warish': ['rawish', 'wairsh', 'warish'], + 'warlock': ['lacwork', 'warlock'], + 'warmed': ['meward', 'warmed'], + 'warmer': ['rewarm', 'warmer'], + 'warmhouse': ['housewarm', 'warmhouse'], + 'warmish': ['warmish', 'wishram'], + 'warn': ['warn', 'wran'], + 'warnel': ['lawner', 'warnel'], + 'warner': ['rewarn', 'warner', 'warren'], + 'warp': ['warp', 'wrap'], + 'warper': ['prewar', 'rewrap', 'warper'], + 'warree': ['rewear', 'warree', 'wearer'], + 'warren': ['rewarn', 'warner', 'warren'], + 'warri': ['warri', 'wirra'], + 'warse': ['resaw', 'sawer', 'seraw', 'sware', 'swear', 'warse'], + 'warsel': ['swaler', 'warsel', 'warsle'], + 'warsle': ['swaler', 'warsel', 'warsle'], + 'warst': ['straw', 'swart', 'warst'], + 'warth': ['thraw', 'warth', 'whart', 'wrath'], + 'warua': ['warua', 'waura'], + 'warve': ['warve', 'waver'], + 'wary': ['awry', 'wary'], + 'was': ['saw', 'swa', 'was'], + 'wasel': ['swale', 'sweal', 'wasel'], + 'wash': ['shaw', 'wash'], + 'washen': ['washen', 'whenas'], + 'washer': ['hawser', 'rewash', 'washer'], + 'washington': ['nowanights', 'washington'], + 'washland': ['landwash', 'washland'], + 'washoan': ['shawano', 'washoan'], + 'washout': ['outwash', 'washout'], + 'washy': ['shawy', 'washy'], + 'wasnt': ['stawn', 'wasnt'], + 'wasp': ['swap', 'wasp'], + 'waspily': ['slipway', 'waspily'], + 'wast': ['sawt', 'staw', 'swat', 'taws', 'twas', 'wast'], + 'waste': ['awest', 'sweat', 'tawse', 'waste'], + 'wasted': ['wadset', 'wasted'], + 'wasteful': ['sweatful', 'wasteful'], + 'wasteless': ['sweatless', 'wasteless'], + 'wasteproof': ['sweatproof', 'wasteproof'], + 'wastrel': ['wastrel', 'wrastle'], + 'wat': ['taw', 'twa', 'wat'], + 'watchdog': ['dogwatch', 'watchdog'], + 'watchout': ['outwatch', 'watchout'], + 'water': ['tawer', 'water', 'wreat'], + 'waterbrain': ['brainwater', 'waterbrain'], + 'watered': ['dewater', 'tarweed', 'watered'], + 'waterer': ['rewater', 'waterer'], + 'waterflood': ['floodwater', 'toadflower', 'waterflood'], + 'waterhead': ['headwater', 'waterhead'], + 'wateriness': ['earwitness', 'wateriness'], + 'waterlog': ['galewort', 'waterlog'], + 'watershed': ['drawsheet', 'watershed'], + 'watery': ['tawery', 'watery'], + 'wath': ['thaw', 'wath', 'what'], + 'watt': ['twat', 'watt'], + 'wauf': ['awfu', 'wauf'], + 'wauner': ['unware', 'wauner'], + 'waura': ['warua', 'waura'], + 'waver': ['warve', 'waver'], + 'waxer': ['rewax', 'waxer'], + 'way': ['way', 'yaw'], + 'wayback': ['backway', 'wayback'], + 'waygang': ['gangway', 'waygang'], + 'waygate': ['gateway', 'getaway', 'waygate'], + 'wayman': ['manway', 'wayman'], + 'ways': ['sway', 'ways', 'yaws'], + 'wayside': ['sideway', 'wayside'], + 'wea': ['awe', 'wae', 'wea'], + 'weak': ['wake', 'weak', 'weka'], + 'weakener': ['reweaken', 'weakener'], + 'weakliness': ['weakliness', 'weaselskin'], + 'weal': ['wale', 'weal'], + 'weald': ['dwale', 'waled', 'weald'], + 'weam': ['wame', 'weam'], + 'wean': ['anew', 'wane', 'wean'], + 'weanel': ['leewan', 'weanel'], + 'wear': ['waer', 'ware', 'wear'], + 'wearer': ['rewear', 'warree', 'wearer'], + 'weasand': ['sandawe', 'weasand'], + 'weaselskin': ['weakliness', 'weaselskin'], + 'weather': ['weather', 'whereat', 'wreathe'], + 'weathered': ['heartweed', 'weathered'], + 'weaver': ['rewave', 'weaver'], + 'webster': ['bestrew', 'webster'], + 'wed': ['dew', 'wed'], + 'wede': ['wede', 'weed'], + 'wedge': ['gweed', 'wedge'], + 'wedger': ['edgrew', 'wedger'], + 'wedset': ['stewed', 'wedset'], + 'wee': ['ewe', 'wee'], + 'weed': ['wede', 'weed'], + 'weedhook': ['hookweed', 'weedhook'], + 'weedy': ['dewey', 'weedy'], + 'ween': ['ween', 'wene'], + 'weeps': ['sweep', 'weeps'], + 'weet': ['twee', 'weet'], + 'wega': ['waeg', 'wage', 'wega'], + 'wegotism': ['twigsome', 'wegotism'], + 'weigher': ['reweigh', 'weigher'], + 'weir': ['weir', 'weri', 'wire'], + 'weirangle': ['weirangle', 'wierangle'], + 'weird': ['weird', 'wired', 'wride', 'wried'], + 'weka': ['wake', 'weak', 'weka'], + 'weld': ['lewd', 'weld'], + 'weldable': ['ballweed', 'weldable'], + 'welder': ['reweld', 'welder'], + 'weldor': ['lowder', 'weldor', 'wordle'], + 'welf': ['flew', 'welf'], + 'welkin': ['welkin', 'winkel', 'winkle'], + 'well': ['llew', 'well'], + 'wellat': ['wallet', 'wellat'], + 'wels': ['slew', 'wels'], + 'welshry': ['shrewly', 'welshry'], + 'welting': ['twingle', 'welting', 'winglet'], + 'wem': ['mew', 'wem'], + 'wen': ['new', 'wen'], + 'wende': ['endew', 'wende'], + 'wendi': ['dwine', 'edwin', 'wendi', 'widen', 'wined'], + 'wene': ['ween', 'wene'], + 'went': ['newt', 'went'], + 'were': ['ewer', 'were'], + 'weri': ['weir', 'weri', 'wire'], + 'werther': ['werther', 'wherret'], + 'wes': ['sew', 'wes'], + 'weskit': ['weskit', 'wisket'], + 'west': ['stew', 'west'], + 'weste': ['sweet', 'weste'], + 'westering': ['swingtree', 'westering'], + 'westy': ['stewy', 'westy'], + 'wet': ['tew', 'wet'], + 'weta': ['tewa', 'twae', 'weta'], + 'wetly': ['tewly', 'wetly'], + 'wey': ['wey', 'wye', 'yew'], + 'wha': ['haw', 'hwa', 'wah', 'wha'], + 'whack': ['chawk', 'whack'], + 'whale': ['whale', 'wheal'], + 'wham': ['hawm', 'wham'], + 'whame': ['whame', 'wheam'], + 'whangee': ['wanghee', 'whangee'], + 'whare': ['hawer', 'whare'], + 'whart': ['thraw', 'warth', 'whart', 'wrath'], + 'whase': ['hawse', 'shewa', 'whase'], + 'what': ['thaw', 'wath', 'what'], + 'whatreck': ['thwacker', 'whatreck'], + 'whats': ['swath', 'whats'], + 'wheal': ['whale', 'wheal'], + 'wheam': ['whame', 'wheam'], + 'wheat': ['awhet', 'wheat'], + 'wheatear': ['aweather', 'wheatear'], + 'wheedle': ['wheedle', 'wheeled'], + 'wheel': ['hewel', 'wheel'], + 'wheeled': ['wheedle', 'wheeled'], + 'wheelroad': ['rowelhead', 'wheelroad'], + 'wheer': ['hewer', 'wheer', 'where'], + 'whein': ['whein', 'whine'], + 'when': ['hewn', 'when'], + 'whenas': ['washen', 'whenas'], + 'whenso': ['whenso', 'whosen'], + 'where': ['hewer', 'wheer', 'where'], + 'whereat': ['weather', 'whereat', 'wreathe'], + 'whereon': ['nowhere', 'whereon'], + 'wherret': ['werther', 'wherret'], + 'wherrit': ['wherrit', 'whirret', 'writher'], + 'whet': ['hewt', 'thew', 'whet'], + 'whichever': ['everwhich', 'whichever'], + 'whicken': ['chewink', 'whicken'], + 'whilter': ['whilter', 'whirtle'], + 'whine': ['whein', 'whine'], + 'whipper': ['prewhip', 'whipper'], + 'whirler': ['rewhirl', 'whirler'], + 'whirret': ['wherrit', 'whirret', 'writher'], + 'whirtle': ['whilter', 'whirtle'], + 'whisperer': ['rewhisper', 'whisperer'], + 'whisson': ['snowish', 'whisson'], + 'whist': ['swith', 'whist', 'whits', 'wisht'], + 'whister': ['swither', 'whister', 'withers'], + 'whit': ['whit', 'with'], + 'white': ['white', 'withe'], + 'whiten': ['whiten', 'withen'], + 'whitener': ['rewhiten', 'whitener'], + 'whitepot': ['whitepot', 'whitetop'], + 'whites': ['swithe', 'whites'], + 'whitetop': ['whitepot', 'whitetop'], + 'whitewood': ['whitewood', 'withewood'], + 'whitleather': ['therewithal', 'whitleather'], + 'whitmanese': ['anthemwise', 'whitmanese'], + 'whits': ['swith', 'whist', 'whits', 'wisht'], + 'whity': ['whity', 'withy'], + 'who': ['how', 'who'], + 'whoever': ['everwho', 'however', 'whoever'], + 'whole': ['howel', 'whole'], + 'whomsoever': ['howsomever', 'whomsoever', 'whosomever'], + 'whoreship': ['horsewhip', 'whoreship'], + 'whort': ['throw', 'whort', 'worth', 'wroth'], + 'whosen': ['whenso', 'whosen'], + 'whosomever': ['howsomever', 'whomsoever', 'whosomever'], + 'wicht': ['tchwi', 'wicht', 'witch'], + 'widdle': ['widdle', 'wilded'], + 'widely': ['dewily', 'widely', 'wieldy'], + 'widen': ['dwine', 'edwin', 'wendi', 'widen', 'wined'], + 'widener': ['rewiden', 'widener'], + 'wideness': ['dewiness', 'wideness'], + 'widgeon': ['gowdnie', 'widgeon'], + 'wieldy': ['dewily', 'widely', 'wieldy'], + 'wierangle': ['weirangle', 'wierangle'], + 'wigan': ['awing', 'wigan'], + 'wiggler': ['wiggler', 'wriggle'], + 'wilded': ['widdle', 'wilded'], + 'wildness': ['wildness', 'windless'], + 'winchester': ['trenchwise', 'winchester'], + 'windbreak': ['breakwind', 'windbreak'], + 'winder': ['rewind', 'winder'], + 'windgall': ['dingwall', 'windgall'], + 'windles': ['swindle', 'windles'], + 'windless': ['wildness', 'windless'], + 'windstorm': ['stormwind', 'windstorm'], + 'windup': ['upwind', 'windup'], + 'wined': ['dwine', 'edwin', 'wendi', 'widen', 'wined'], + 'winer': ['erwin', 'rewin', 'winer'], + 'winglet': ['twingle', 'welting', 'winglet'], + 'winkel': ['welkin', 'winkel', 'winkle'], + 'winkle': ['welkin', 'winkel', 'winkle'], + 'winklet': ['twinkle', 'winklet'], + 'winnard': ['indrawn', 'winnard'], + 'winnel': ['winnel', 'winnle'], + 'winnle': ['winnel', 'winnle'], + 'winsome': ['owenism', 'winsome'], + 'wint': ['twin', 'wint'], + 'winter': ['twiner', 'winter'], + 'winterage': ['wagnerite', 'winterage'], + 'wintered': ['interwed', 'wintered'], + 'winterish': ['interwish', 'winterish'], + 'winze': ['winze', 'wizen'], + 'wips': ['wips', 'wisp'], + 'wire': ['weir', 'weri', 'wire'], + 'wired': ['weird', 'wired', 'wride', 'wried'], + 'wirer': ['wirer', 'wrier'], + 'wirra': ['warri', 'wirra'], + 'wiselike': ['likewise', 'wiselike'], + 'wiseman': ['manwise', 'wiseman'], + 'wisen': ['sinew', 'swine', 'wisen'], + 'wiser': ['swire', 'wiser'], + 'wisewoman': ['wisewoman', 'womanwise'], + 'wisher': ['rewish', 'wisher'], + 'wishmay': ['wishmay', 'yahwism'], + 'wishram': ['warmish', 'wishram'], + 'wisht': ['swith', 'whist', 'whits', 'wisht'], + 'wisket': ['weskit', 'wisket'], + 'wisp': ['wips', 'wisp'], + 'wispy': ['swipy', 'wispy'], + 'wit': ['twi', 'wit'], + 'witan': ['atwin', 'twain', 'witan'], + 'witch': ['tchwi', 'wicht', 'witch'], + 'witchetty': ['twitchety', 'witchetty'], + 'with': ['whit', 'with'], + 'withdrawer': ['rewithdraw', 'withdrawer'], + 'withe': ['white', 'withe'], + 'withen': ['whiten', 'withen'], + 'wither': ['wither', 'writhe'], + 'withered': ['redwithe', 'withered'], + 'withering': ['withering', 'wrightine'], + 'withers': ['swither', 'whister', 'withers'], + 'withewood': ['whitewood', 'withewood'], + 'within': ['inwith', 'within'], + 'without': ['outwith', 'without'], + 'withy': ['whity', 'withy'], + 'wive': ['view', 'wive'], + 'wiver': ['wiver', 'wrive'], + 'wizen': ['winze', 'wizen'], + 'wo': ['ow', 'wo'], + 'woader': ['redowa', 'woader'], + 'wob': ['bow', 'wob'], + 'wod': ['dow', 'owd', 'wod'], + 'woe': ['owe', 'woe'], + 'woibe': ['bowie', 'woibe'], + 'wold': ['dowl', 'wold'], + 'wolf': ['flow', 'fowl', 'wolf'], + 'wolfer': ['flower', 'fowler', 'reflow', 'wolfer'], + 'wolter': ['rowlet', 'trowel', 'wolter'], + 'wolve': ['vowel', 'wolve'], + 'womanpost': ['postwoman', 'womanpost'], + 'womanwise': ['wisewoman', 'womanwise'], + 'won': ['now', 'own', 'won'], + 'wonder': ['downer', 'wonder', 'worden'], + 'wonderful': ['underflow', 'wonderful'], + 'wone': ['enow', 'owen', 'wone'], + 'wong': ['gown', 'wong'], + 'wonga': ['gowan', 'wagon', 'wonga'], + 'wonner': ['renown', 'wonner'], + 'wont': ['nowt', 'town', 'wont'], + 'wonted': ['towned', 'wonted'], + 'woodbark': ['bookward', 'woodbark'], + 'woodbind': ['bindwood', 'woodbind'], + 'woodbush': ['bushwood', 'woodbush'], + 'woodchat': ['chatwood', 'woodchat'], + 'wooden': ['enwood', 'wooden'], + 'woodfish': ['fishwood', 'woodfish'], + 'woodhack': ['hackwood', 'woodhack'], + 'woodhorse': ['horsewood', 'woodhorse'], + 'woodness': ['sowdones', 'woodness'], + 'woodpecker': ['peckerwood', 'woodpecker'], + 'woodrock': ['corkwood', 'rockwood', 'woodrock'], + 'woodsilver': ['silverwood', 'woodsilver'], + 'woodstone': ['stonewood', 'woodstone'], + 'woodworm': ['woodworm', 'wormwood'], + 'wooled': ['dewool', 'elwood', 'wooled'], + 'woons': ['swoon', 'woons'], + 'woosh': ['howso', 'woosh'], + 'wop': ['pow', 'wop'], + 'worble': ['blower', 'bowler', 'reblow', 'worble'], + 'word': ['drow', 'word'], + 'wordage': ['dowager', 'wordage'], + 'worden': ['downer', 'wonder', 'worden'], + 'worder': ['reword', 'worder'], + 'wordily': ['rowdily', 'wordily'], + 'wordiness': ['rowdiness', 'wordiness'], + 'wordle': ['lowder', 'weldor', 'wordle'], + 'wordsman': ['sandworm', 'swordman', 'wordsman'], + 'wordsmanship': ['swordmanship', 'wordsmanship'], + 'wordy': ['dowry', 'rowdy', 'wordy'], + 'wore': ['ower', 'wore'], + 'workbasket': ['basketwork', 'workbasket'], + 'workbench': ['benchwork', 'workbench'], + 'workbook': ['bookwork', 'workbook'], + 'workbox': ['boxwork', 'workbox'], + 'workday': ['daywork', 'workday'], + 'worker': ['rework', 'worker'], + 'workhand': ['handwork', 'workhand'], + 'workhouse': ['housework', 'workhouse'], + 'working': ['kingrow', 'working'], + 'workmaster': ['masterwork', 'workmaster'], + 'workout': ['outwork', 'workout'], + 'workpiece': ['piecework', 'workpiece'], + 'workship': ['shipwork', 'workship'], + 'workshop': ['shopwork', 'workshop'], + 'worktime': ['timework', 'worktime'], + 'wormed': ['deworm', 'wormed'], + 'wormer': ['merrow', 'wormer'], + 'wormroot': ['moorwort', 'rootworm', 'tomorrow', 'wormroot'], + 'wormship': ['shipworm', 'wormship'], + 'wormwood': ['woodworm', 'wormwood'], + 'worse': ['owser', 'resow', 'serow', 'sower', 'swore', 'worse'], + 'worset': ['restow', 'stower', 'towser', 'worset'], + 'worst': ['strow', 'worst'], + 'wort': ['trow', 'wort'], + 'worth': ['throw', 'whort', 'worth', 'wroth'], + 'worthful': ['worthful', 'wrothful'], + 'worthily': ['worthily', 'wrothily'], + 'worthiness': ['worthiness', 'wrothiness'], + 'worthy': ['worthy', 'wrothy'], + 'wot': ['tow', 'two', 'wot'], + 'wots': ['sowt', 'stow', 'swot', 'wots'], + 'wounder': ['rewound', 'unrowed', 'wounder'], + 'woy': ['woy', 'yow'], + 'wraf': ['warf', 'wraf'], + 'wrainbolt': ['browntail', 'wrainbolt'], + 'wraitly': ['wraitly', 'wrytail'], + 'wran': ['warn', 'wran'], + 'wrangle': ['wangler', 'wrangle'], + 'wrap': ['warp', 'wrap'], + 'wrapper': ['prewrap', 'wrapper'], + 'wrastle': ['wastrel', 'wrastle'], + 'wrath': ['thraw', 'warth', 'whart', 'wrath'], + 'wreak': ['waker', 'wreak'], + 'wreat': ['tawer', 'water', 'wreat'], + 'wreath': ['rethaw', 'thawer', 'wreath'], + 'wreathe': ['weather', 'whereat', 'wreathe'], + 'wrest': ['strew', 'trews', 'wrest'], + 'wrester': ['strewer', 'wrester'], + 'wrestle': ['swelter', 'wrestle'], + 'wride': ['weird', 'wired', 'wride', 'wried'], + 'wried': ['weird', 'wired', 'wride', 'wried'], + 'wrier': ['wirer', 'wrier'], + 'wriggle': ['wiggler', 'wriggle'], + 'wrightine': ['withering', 'wrightine'], + 'wrinklet': ['twinkler', 'wrinklet'], + 'write': ['twire', 'write'], + 'writhe': ['wither', 'writhe'], + 'writher': ['wherrit', 'whirret', 'writher'], + 'written': ['twinter', 'written'], + 'wrive': ['wiver', 'wrive'], + 'wro': ['row', 'wro'], + 'wroken': ['knower', 'reknow', 'wroken'], + 'wrong': ['grown', 'wrong'], + 'wrote': ['rowet', 'tower', 'wrote'], + 'wroth': ['throw', 'whort', 'worth', 'wroth'], + 'wrothful': ['worthful', 'wrothful'], + 'wrothily': ['worthily', 'wrothily'], + 'wrothiness': ['worthiness', 'wrothiness'], + 'wrothy': ['worthy', 'wrothy'], + 'wrytail': ['wraitly', 'wrytail'], + 'wunna': ['unwan', 'wunna'], + 'wyde': ['dewy', 'wyde'], + 'wye': ['wey', 'wye', 'yew'], + 'wype': ['pewy', 'wype'], + 'wyson': ['snowy', 'wyson'], + 'xanthein': ['xanthein', 'xanthine'], + 'xanthine': ['xanthein', 'xanthine'], + 'xanthopurpurin': ['purpuroxanthin', 'xanthopurpurin'], + 'xema': ['amex', 'exam', 'xema'], + 'xenia': ['axine', 'xenia'], + 'xenial': ['alexin', 'xenial'], + 'xenoparasite': ['exasperation', 'xenoparasite'], + 'xeres': ['resex', 'xeres'], + 'xerophytic': ['hypertoxic', 'xerophytic'], + 'xerotic': ['excitor', 'xerotic'], + 'xylic': ['cylix', 'xylic'], + 'xylitone': ['xylitone', 'xylonite'], + 'xylonite': ['xylitone', 'xylonite'], + 'xylophone': ['oxyphenol', 'xylophone'], + 'xylose': ['lyxose', 'xylose'], + 'xyst': ['styx', 'xyst'], + 'xyster': ['sextry', 'xyster'], + 'xysti': ['sixty', 'xysti'], + 'ya': ['ay', 'ya'], + 'yaba': ['baya', 'yaba'], + 'yabber': ['babery', 'yabber'], + 'yacht': ['cathy', 'cyath', 'yacht'], + 'yachtist': ['chastity', 'yachtist'], + 'yad': ['ady', 'day', 'yad'], + 'yaff': ['affy', 'yaff'], + 'yagnob': ['boyang', 'yagnob'], + 'yah': ['hay', 'yah'], + 'yahwism': ['wishmay', 'yahwism'], + 'yair': ['airy', 'yair'], + 'yaird': ['dairy', 'diary', 'yaird'], + 'yak': ['kay', 'yak'], + 'yakan': ['kayan', 'yakan'], + 'yakima': ['kamiya', 'yakima'], + 'yakka': ['kayak', 'yakka'], + 'yalb': ['ably', 'blay', 'yalb'], + 'yali': ['ilya', 'yali'], + 'yalla': ['allay', 'yalla'], + 'yallaer': ['allayer', 'yallaer'], + 'yam': ['amy', 'may', 'mya', 'yam'], + 'yamel': ['mealy', 'yamel'], + 'yamen': ['maney', 'yamen'], + 'yamilke': ['maylike', 'yamilke'], + 'yamph': ['phyma', 'yamph'], + 'yan': ['any', 'nay', 'yan'], + 'yana': ['anay', 'yana'], + 'yander': ['denary', 'yander'], + 'yap': ['pay', 'pya', 'yap'], + 'yapness': ['synapse', 'yapness'], + 'yapper': ['papery', 'prepay', 'yapper'], + 'yapster': ['atrepsy', 'yapster'], + 'yar': ['ary', 'ray', 'yar'], + 'yarb': ['bray', 'yarb'], + 'yard': ['adry', 'dray', 'yard'], + 'yardage': ['drayage', 'yardage'], + 'yarder': ['dreary', 'yarder'], + 'yardman': ['drayman', 'yardman'], + 'yare': ['aery', 'eyra', 'yare', 'year'], + 'yark': ['kyar', 'yark'], + 'yarl': ['aryl', 'lyra', 'ryal', 'yarl'], + 'yarm': ['army', 'mary', 'myra', 'yarm'], + 'yarn': ['nary', 'yarn'], + 'yarr': ['arry', 'yarr'], + 'yarrow': ['arrowy', 'yarrow'], + 'yaruran': ['unarray', 'yaruran'], + 'yas': ['say', 'yas'], + 'yasht': ['hasty', 'yasht'], + 'yat': ['tay', 'yat'], + 'yate': ['yate', 'yeat', 'yeta'], + 'yatter': ['attery', 'treaty', 'yatter'], + 'yaw': ['way', 'yaw'], + 'yawler': ['lawyer', 'yawler'], + 'yawn': ['awny', 'wany', 'yawn'], + 'yaws': ['sway', 'ways', 'yaws'], + 'ye': ['ey', 'ye'], + 'yea': ['aye', 'yea'], + 'yeah': ['ahey', 'eyah', 'yeah'], + 'year': ['aery', 'eyra', 'yare', 'year'], + 'yeard': ['deary', 'deray', 'rayed', 'ready', 'yeard'], + 'yearly': ['layery', 'yearly'], + 'yearn': ['enray', 'yearn'], + 'yearth': ['earthy', 'hearty', 'yearth'], + 'yeast': ['teasy', 'yeast'], + 'yeat': ['yate', 'yeat', 'yeta'], + 'yeather': ['erythea', 'hetaery', 'yeather'], + 'yed': ['dey', 'dye', 'yed'], + 'yede': ['eyed', 'yede'], + 'yee': ['eye', 'yee'], + 'yeel': ['eely', 'yeel'], + 'yees': ['yees', 'yese'], + 'yegg': ['eggy', 'yegg'], + 'yelk': ['kyle', 'yelk'], + 'yelm': ['elmy', 'yelm'], + 'yelmer': ['merely', 'yelmer'], + 'yelper': ['peerly', 'yelper'], + 'yemen': ['enemy', 'yemen'], + 'yemeni': ['menyie', 'yemeni'], + 'yen': ['eyn', 'nye', 'yen'], + 'yender': ['redeny', 'yender'], + 'yeo': ['yeo', 'yoe'], + 'yeorling': ['legionry', 'yeorling'], + 'yer': ['rye', 'yer'], + 'yerb': ['brey', 'byre', 'yerb'], + 'yerba': ['barye', 'beray', 'yerba'], + 'yerd': ['dyer', 'yerd'], + 'yere': ['eyer', 'eyre', 'yere'], + 'yern': ['ryen', 'yern'], + 'yes': ['sey', 'sye', 'yes'], + 'yese': ['yees', 'yese'], + 'yest': ['stey', 'yest'], + 'yester': ['reesty', 'yester'], + 'yestern': ['streyne', 'styrene', 'yestern'], + 'yet': ['tye', 'yet'], + 'yeta': ['yate', 'yeat', 'yeta'], + 'yeth': ['they', 'yeth'], + 'yether': ['theyre', 'yether'], + 'yetlin': ['lenity', 'yetlin'], + 'yew': ['wey', 'wye', 'yew'], + 'yielden': ['needily', 'yielden'], + 'yielder': ['reedily', 'reyield', 'yielder'], + 'yildun': ['unidly', 'yildun'], + 'yill': ['illy', 'lily', 'yill'], + 'yirm': ['miry', 'rimy', 'yirm'], + 'ym': ['my', 'ym'], + 'yock': ['coky', 'yock'], + 'yodel': ['doyle', 'yodel'], + 'yoe': ['yeo', 'yoe'], + 'yoghurt': ['troughy', 'yoghurt'], + 'yogin': ['goyin', 'yogin'], + 'yoi': ['iyo', 'yoi'], + 'yoker': ['rokey', 'yoker'], + 'yolk': ['kylo', 'yolk'], + 'yom': ['moy', 'yom'], + 'yomud': ['moudy', 'yomud'], + 'yon': ['noy', 'yon'], + 'yond': ['ondy', 'yond'], + 'yonder': ['rodney', 'yonder'], + 'yont': ['tony', 'yont'], + 'yor': ['ory', 'roy', 'yor'], + 'yore': ['oyer', 'roey', 'yore'], + 'york': ['kory', 'roky', 'york'], + 'yot': ['toy', 'yot'], + 'yote': ['eyot', 'yote'], + 'youngun': ['unyoung', 'youngun'], + 'yours': ['soury', 'yours'], + 'yoursel': ['elusory', 'yoursel'], + 'yoven': ['envoy', 'nevoy', 'yoven'], + 'yow': ['woy', 'yow'], + 'yowl': ['lowy', 'owly', 'yowl'], + 'yowler': ['lowery', 'owlery', 'rowley', 'yowler'], + 'yowt': ['towy', 'yowt'], + 'yox': ['oxy', 'yox'], + 'yttrious': ['touristy', 'yttrious'], + 'yuca': ['cuya', 'yuca'], + 'yuckel': ['yuckel', 'yuckle'], + 'yuckle': ['yuckel', 'yuckle'], + 'yulan': ['unlay', 'yulan'], + 'yurok': ['rouky', 'yurok'], + 'zabian': ['banzai', 'zabian'], + 'zabra': ['braza', 'zabra'], + 'zacate': ['azteca', 'zacate'], + 'zad': ['adz', 'zad'], + 'zag': ['gaz', 'zag'], + 'zain': ['nazi', 'zain'], + 'zaman': ['namaz', 'zaman'], + 'zamenis': ['sizeman', 'zamenis'], + 'zaparoan': ['parazoan', 'zaparoan'], + 'zaratite': ['tatarize', 'zaratite'], + 'zati': ['itza', 'tiza', 'zati'], + 'zeal': ['laze', 'zeal'], + 'zealotism': ['solmizate', 'zealotism'], + 'zebra': ['braze', 'zebra'], + 'zein': ['inez', 'zein'], + 'zelanian': ['annalize', 'zelanian'], + 'zelatrice': ['cartelize', 'zelatrice'], + 'zemmi': ['zemmi', 'zimme'], + 'zendic': ['dezinc', 'zendic'], + 'zenick': ['zenick', 'zincke'], + 'zenu': ['unze', 'zenu'], + 'zequin': ['quinze', 'zequin'], + 'zerda': ['adzer', 'zerda'], + 'zerma': ['mazer', 'zerma'], + 'ziarat': ['atazir', 'ziarat'], + 'zibet': ['bizet', 'zibet'], + 'ziega': ['gaize', 'ziega'], + 'zimme': ['zemmi', 'zimme'], + 'zincite': ['citizen', 'zincite'], + 'zincke': ['zenick', 'zincke'], + 'zinco': ['zinco', 'zonic'], + 'zion': ['nozi', 'zion'], + 'zira': ['izar', 'zira'], + 'zirconate': ['narcotize', 'zirconate'], + 'zoa': ['azo', 'zoa'], + 'zoanthidae': ['zoanthidae', 'zoanthidea'], + 'zoanthidea': ['zoanthidae', 'zoanthidea'], + 'zoarite': ['azorite', 'zoarite'], + 'zobo': ['bozo', 'zobo'], + 'zoeal': ['azole', 'zoeal'], + 'zogan': ['gazon', 'zogan'], + 'zolotink': ['zolotink', 'zolotnik'], + 'zolotnik': ['zolotink', 'zolotnik'], + 'zonaria': ['arizona', 'azorian', 'zonaria'], + 'zoned': ['dozen', 'zoned'], + 'zonic': ['zinco', 'zonic'], + 'zonotrichia': ['chorization', 'rhizoctonia', 'zonotrichia'], + 'zoonal': ['alonzo', 'zoonal'], + 'zoonic': ['ozonic', 'zoonic'], + 'zoonomic': ['monozoic', 'zoonomic'], + 'zoopathy': ['phytozoa', 'zoopathy', 'zoophyta'], + 'zoophilic': ['philozoic', 'zoophilic'], + 'zoophilist': ['philozoist', 'zoophilist'], + 'zoophyta': ['phytozoa', 'zoopathy', 'zoophyta'], + 'zoospermatic': ['spermatozoic', 'zoospermatic'], + 'zoosporic': ['sporozoic', 'zoosporic'], + 'zootype': ['ozotype', 'zootype'], + 'zyga': ['gazy', 'zyga'], + 'zygal': ['glazy', 'zygal']} \ No newline at end of file From ed4c92d98af4b96605a0463bc94143b9c771a7cd Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Mon, 8 Nov 2021 23:48:30 +0530 Subject: [PATCH 1747/2908] [mypy] Type annotations for graphs directory (#5798) * Type annotations for `breadth_first_search.py` * Type annotations for `breadth_first_search_2.py` * Remove from excluded in mypy.ini * Add doctest.testmod() * Type annotations for `graphs/check_cycle.py` * Type annotations for `graphs/greedy_min_vertex_cover.py` * Remove from excluded in mypy.ini --- graphs/breadth_first_search.py | 2 +- graphs/breadth_first_search_2.py | 5 ++++- graphs/check_cycle.py | 6 ++---- graphs/greedy_min_vertex_cover.py | 9 ++++----- mypy.ini | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index 9264f57b41b2..171d3875f3c5 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -53,7 +53,7 @@ def bfs(self, start_vertex: int) -> set[int]: visited = set() # create a first in first out queue to store all the vertices for BFS - queue = Queue() + queue: Queue = Queue() # mark the source node as visited and enqueue it visited.add(start_vertex) diff --git a/graphs/breadth_first_search_2.py b/graphs/breadth_first_search_2.py index 4c8b69faf656..2f060a90d40d 100644 --- a/graphs/breadth_first_search_2.py +++ b/graphs/breadth_first_search_2.py @@ -32,7 +32,7 @@ def breadth_first_search(graph: dict, start: str) -> set[str]: 'ABCDEF' """ explored = {start} - queue = Queue() + queue: Queue = Queue() queue.put(start) while not queue.empty(): v = queue.get() @@ -44,4 +44,7 @@ def breadth_first_search(graph: dict, start: str) -> set[str]: if __name__ == "__main__": + import doctest + + doctest.testmod() print(breadth_first_search(G, "A")) diff --git a/graphs/check_cycle.py b/graphs/check_cycle.py index 71d42b4689b7..dcc864988ca5 100644 --- a/graphs/check_cycle.py +++ b/graphs/check_cycle.py @@ -6,16 +6,15 @@ def check_cycle(graph: dict) -> bool: """ Returns True if graph is cyclic else False - >>> check_cycle(graph={0:[], 1:[0, 3], 2:[0, 4], 3:[5], 4:[5], 5:[]}) False >>> check_cycle(graph={0:[1, 2], 1:[2], 2:[0, 3], 3:[3]}) True """ # Keep track of visited nodes - visited = set() + visited: set[int] = set() # To detect a back edge, keep track of vertices currently in the recursion stack - rec_stk = set() + rec_stk: set[int] = set() for node in graph: if node not in visited: if depth_first_search(graph, node, visited, rec_stk): @@ -27,7 +26,6 @@ def depth_first_search(graph: dict, vertex: int, visited: set, rec_stk: set) -> """ Recur for all neighbours. If any neighbour is visited and in rec_stk then graph is cyclic. - >>> graph = {0:[], 1:[0, 3], 2:[0, 4], 3:[5], 4:[5], 5:[]} >>> vertex, visited, rec_stk = 0, set(), set() >>> depth_first_search(graph, vertex, visited, rec_stk) diff --git a/graphs/greedy_min_vertex_cover.py b/graphs/greedy_min_vertex_cover.py index 056c5b89bedf..cdef69141bd6 100644 --- a/graphs/greedy_min_vertex_cover.py +++ b/graphs/greedy_min_vertex_cover.py @@ -2,7 +2,6 @@ * Author: Manuel Di Lullo (https://github.com/manueldilullo) * Description: Approximization algorithm for minimum vertex cover problem. Greedy Approach. Uses graphs represented with an adjacency list - URL: https://mathworld.wolfram.com/MinimumVertexCover.html URL: https://cs.stackexchange.com/questions/129017/greedy-algorithm-for-vertex-cover """ @@ -10,7 +9,7 @@ import heapq -def greedy_min_vertex_cover(graph: dict) -> set: +def greedy_min_vertex_cover(graph: dict) -> set[int]: """ Greedy APX Algorithm for min Vertex Cover @input: graph (graph stored in an adjacency list where each vertex @@ -21,7 +20,7 @@ def greedy_min_vertex_cover(graph: dict) -> set: {0, 1, 2, 4} """ # queue used to store nodes and their rank - queue = [] + queue: list[list] = [] # for each node and his adjacency list add them and the rank of the node to queue # using heapq module the queue will be filled like a Priority Queue @@ -61,5 +60,5 @@ def greedy_min_vertex_cover(graph: dict) -> set: doctest.testmod() - # graph = {0: [1, 3], 1: [0, 3], 2: [0, 3, 4], 3: [0, 1, 2], 4: [2, 3]} - # print(f"Minimum vertex cover:\n{greedy_min_vertex_cover(graph)}") + graph = {0: [1, 3], 1: [0, 3], 2: [0, 3, 4], 3: [0, 1, 2], 4: [2, 3]} + print(f"Minimum vertex cover:\n{greedy_min_vertex_cover(graph)}") diff --git a/mypy.ini b/mypy.ini index 429c6804daf5..ce7c262ab059 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True install_types = True non_interactive = True -exclude = (graphs/breadth_first_search.py|graphs/breadth_first_search_2.py|graphs/check_cycle.py|graphs/greedy_min_vertex_cover.py|matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) +exclude = (matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) From 0b8d6d70cea06eabc19b40a4e583efe62c2c0c2e Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 9 Nov 2021 17:25:29 +0300 Subject: [PATCH 1748/2908] Add Project Euler problem 205 solution 1 (#5781) * updating DIRECTORY.md * Add solution * updating DIRECTORY.md * Fix * Fix Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_205/__init__.py | 0 project_euler/problem_205/sol1.py | 75 +++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 project_euler/problem_205/__init__.py create mode 100644 project_euler/problem_205/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 228d95472a60..a2f229a9ed37 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -865,6 +865,8 @@ * [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 205 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_205/sol1.py) * Problem 206 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_206/sol1.py) * Problem 207 diff --git a/project_euler/problem_205/__init__.py b/project_euler/problem_205/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_205/sol1.py b/project_euler/problem_205/sol1.py new file mode 100644 index 000000000000..7249df48829b --- /dev/null +++ b/project_euler/problem_205/sol1.py @@ -0,0 +1,75 @@ +""" +Project Euler Problem 205: https://projecteuler.net/problem=205 + +Peter has nine four-sided (pyramidal) dice, each with faces numbered 1, 2, 3, 4. +Colin has six six-sided (cubic) dice, each with faces numbered 1, 2, 3, 4, 5, 6. + +Peter and Colin roll their dice and compare totals: the highest total wins. +The result is a draw if the totals are equal. + +What is the probability that Pyramidal Peter beats Cubic Colin? +Give your answer rounded to seven decimal places in the form 0.abcdefg +""" + +from itertools import product + + +def total_frequency_distribution(sides_number: int, dice_number: int) -> list[int]: + """ + Returns frequency distribution of total + + >>> total_frequency_distribution(sides_number=6, dice_number=1) + [0, 1, 1, 1, 1, 1, 1] + + >>> total_frequency_distribution(sides_number=4, dice_number=2) + [0, 0, 1, 2, 3, 4, 3, 2, 1] + """ + + max_face_number = sides_number + max_total = max_face_number * dice_number + totals_frequencies = [0] * (max_total + 1) + + min_face_number = 1 + faces_numbers = range(min_face_number, max_face_number + 1) + for dice_numbers in product(faces_numbers, repeat=dice_number): + total = sum(dice_numbers) + totals_frequencies[total] += 1 + + return totals_frequencies + + +def solution() -> float: + """ + Returns probability that Pyramidal Peter beats Cubic Colin + rounded to seven decimal places in the form 0.abcdefg + + >>> solution() + 0.5731441 + """ + + peter_totals_frequencies = total_frequency_distribution( + sides_number=4, dice_number=9 + ) + colin_totals_frequencies = total_frequency_distribution( + sides_number=6, dice_number=6 + ) + + peter_wins_count = 0 + min_peter_total = 9 + max_peter_total = 4 * 9 + min_colin_total = 6 + for peter_total in range(min_peter_total, max_peter_total + 1): + peter_wins_count += peter_totals_frequencies[peter_total] * sum( + colin_totals_frequencies[min_colin_total:peter_total] + ) + + total_games_number = (4 ** 9) * (6 ** 6) + peter_win_probability = peter_wins_count / total_games_number + + rounded_peter_win_probability = round(peter_win_probability, ndigits=7) + + return rounded_peter_win_probability + + +if __name__ == "__main__": + print(f"{solution() = }") From c3d1ff0ebd034eeb6105ef8bad6a3c962efa56f2 Mon Sep 17 00:00:00 2001 From: Nivas Manduva <53264470+eviltypha@users.noreply.github.com> Date: Tue, 9 Nov 2021 20:10:57 +0530 Subject: [PATCH 1749/2908] Add Jacobi Iteration Method (#5113) * Added Jacobi Iteration Method Added this method in arithmetic_analysis folder. This method is used to solve system of linear equations. * Added comments * Added reference link * Update jacobi_iteration_method.py * Changes for codespell test * Update jacobi_iteration_method.py * Update jacobi_iteration_method.py * Update arithmetic_analysis/jacobi_iteration_method.py Co-authored-by: Christian Clauss * updating DIRECTORY.md * Update arithmetic_analysis/jacobi_iteration_method.py Co-authored-by: Christian Clauss * Update arithmetic_analysis/jacobi_iteration_method.py Co-authored-by: Christian Clauss * Update arithmetic_analysis/jacobi_iteration_method.py Co-authored-by: Christian Clauss * Update arithmetic_analysis/jacobi_iteration_method.py Co-authored-by: Christian Clauss * Update arithmetic_analysis/jacobi_iteration_method.py Co-authored-by: Christian Clauss * Update arithmetic_analysis/jacobi_iteration_method.py Co-authored-by: Christian Clauss * Update jacobi_iteration_method.py * Update jacobi_iteration_method.py * Update jacobi_iteration_method.py * fix styles Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 1 + .../jacobi_iteration_method.py | 163 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 arithmetic_analysis/jacobi_iteration_method.py diff --git a/DIRECTORY.md b/DIRECTORY.md index a2f229a9ed37..883b81b2444d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -4,6 +4,7 @@ * [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) + * [Jacobi Iteration Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/jacobi_iteration_method.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) diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/arithmetic_analysis/jacobi_iteration_method.py new file mode 100644 index 000000000000..6674824255a1 --- /dev/null +++ b/arithmetic_analysis/jacobi_iteration_method.py @@ -0,0 +1,163 @@ +""" +Jacobi Iteration Method - https://en.wikipedia.org/wiki/Jacobi_method +""" +from __future__ import annotations + +import numpy as np + + +# Method to find solution of system of linear equations +def jacobi_iteration_method( + coefficient_matrix: np.ndarray, + constant_matrix: np.ndarray, + init_val: list, + iterations: int, +) -> list[float]: + """ + Jacobi Iteration Method: + An iterative algorithm to determine the solutions of strictly diagonally dominant + system of linear equations + + 4x1 + x2 + x3 = 2 + x1 + 5x2 + 2x3 = -6 + x1 + 2x2 + 4x3 = -4 + + x_init = [0.5, -0.5 , -0.5] + + Examples: + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) + >>> constant = np.array([[2], [-6], [-4]]) + >>> init_val = [0.5, -0.5, -0.5] + >>> iterations = 3 + >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + [0.909375, -1.14375, -0.7484375] + + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2]]) + >>> constant = np.array([[2], [-6], [-4]]) + >>> init_val = [0.5, -0.5, -0.5] + >>> iterations = 3 + >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + Traceback (most recent call last): + ... + ValueError: Coefficient matrix dimensions must be nxn but received 2x3 + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) + >>> constant = np.array([[2], [-6]]) + >>> init_val = [0.5, -0.5, -0.5] + >>> iterations = 3 + >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + Traceback (most recent call last): + ... + ValueError: Coefficient and constant matrices dimensions must be nxn and nx1 but + received 3x3 and 2x1 + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) + >>> constant = np.array([[2], [-6], [-4]]) + >>> init_val = [0.5, -0.5] + >>> iterations = 3 + >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + Traceback (most recent call last): + ... + ValueError: Number of initial values must be equal to number of rows in coefficient + matrix but received 2 and 3 + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) + >>> constant = np.array([[2], [-6], [-4]]) + >>> init_val = [0.5, -0.5, -0.5] + >>> iterations = 0 + >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + Traceback (most recent call last): + ... + ValueError: Iterations must be at least 1 + """ + + rows1, cols1 = coefficient_matrix.shape + rows2, cols2 = constant_matrix.shape + + if rows1 != cols1: + raise ValueError( + f"Coefficient matrix dimensions must be nxn but received {rows1}x{cols1}" + ) + + if cols2 != 1: + raise ValueError(f"Constant matrix must be nx1 but received {rows2}x{cols2}") + + if rows1 != rows2: + raise ValueError( + f"""Coefficient and constant matrices dimensions must be nxn and nx1 but + received {rows1}x{cols1} and {rows2}x{cols2}""" + ) + + if len(init_val) != rows1: + raise ValueError( + f"""Number of initial values must be equal to number of rows in coefficient + matrix but received {len(init_val)} and {rows1}""" + ) + + if iterations <= 0: + raise ValueError("Iterations must be at least 1") + + table = np.concatenate((coefficient_matrix, constant_matrix), axis=1) + + rows, cols = table.shape + + strictly_diagonally_dominant(table) + + # Iterates the whole matrix for given number of times + for i in range(iterations): + new_val = [] + for row in range(rows): + temp = 0 + for col in range(cols): + if col == row: + denom = table[row][col] + elif col == cols - 1: + val = table[row][col] + else: + temp += (-1) * table[row][col] * init_val[col] + temp = (temp + val) / denom + new_val.append(temp) + init_val = new_val + + return [float(i) for i in new_val] + + +# Checks if the given matrix is strictly diagonally dominant +def strictly_diagonally_dominant(table: np.ndarray) -> bool: + """ + >>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 4, -4]]) + >>> strictly_diagonally_dominant(table) + True + + >>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 3, -4]]) + >>> strictly_diagonally_dominant(table) + Traceback (most recent call last): + ... + ValueError: Coefficient matrix is not strictly diagonally dominant + """ + + rows, cols = table.shape + + is_diagonally_dominant = True + + for i in range(0, rows): + sum = 0 + for j in range(0, cols - 1): + if i == j: + continue + else: + sum += table[i][j] + + if table[i][i] <= sum: + raise ValueError("Coefficient matrix is not strictly diagonally dominant") + + return is_diagonally_dominant + + +# Test Cases +if __name__ == "__main__": + import doctest + + doctest.testmod() From 745f9e2bc37280368ae007d1a30ffc217e4a5b81 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Tue, 9 Nov 2021 21:18:30 +0530 Subject: [PATCH 1750/2908] [mypy] Type annotations for searches directory (#5799) * Update ternary_search.py * Update mypy.ini * Update simulated_annealing.py * Update ternary_search.py * formatting * formatting * Update matrix_operation.py * Update matrix_operation.py * Update matrix_operation.py --- mypy.ini | 2 +- searches/simulated_annealing.py | 3 ++- searches/ternary_search.py | 12 ++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/mypy.ini b/mypy.ini index ce7c262ab059..94fb125fb124 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True install_types = True non_interactive = True -exclude = (matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py|searches/simulated_annealing.py|searches/ternary_search.py) +exclude = (matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py) diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py index 2aa980be7748..ad29559f1b8d 100644 --- a/searches/simulated_annealing.py +++ b/searches/simulated_annealing.py @@ -1,6 +1,7 @@ # https://en.wikipedia.org/wiki/Simulated_annealing import math import random +from typing import Any from .hill_climbing import SearchProblem @@ -16,7 +17,7 @@ def simulated_annealing( start_temperate: float = 100, rate_of_decrease: float = 0.01, threshold_temp: float = 1, -) -> SearchProblem: +) -> Any: """ 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 diff --git a/searches/ternary_search.py b/searches/ternary_search.py index 01e437723473..9830cce36000 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -89,8 +89,8 @@ def ite_ternary_search(array: list[int], target: int) -> int: if right - left < precision: return lin_search(left, right, array, target) - one_third = (left + right) / 3 + 1 - two_third = 2 * (left + right) / 3 + 1 + one_third = (left + right) // 3 + 1 + two_third = 2 * (left + right) // 3 + 1 if array[one_third] == target: return one_third @@ -138,8 +138,8 @@ def rec_ternary_search(left: int, right: int, array: list[int], target: int) -> if left < right: if right - left < precision: return lin_search(left, right, array, target) - one_third = (left + right) / 3 + 1 - two_third = 2 * (left + right) / 3 + 1 + one_third = (left + right) // 3 + 1 + two_third = 2 * (left + right) // 3 + 1 if array[one_third] == target: return one_third @@ -157,6 +157,10 @@ def rec_ternary_search(left: int, right: int, array: list[int], target: int) -> if __name__ == "__main__": + import doctest + + doctest.testmod() + 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}." From d6a1623448f2494fbb21116b6bc699dccb0401b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Henrik=20=C3=85keson?= <80765479+chakeson@users.noreply.github.com> Date: Wed, 10 Nov 2021 11:22:27 +0100 Subject: [PATCH 1751/2908] Add solution for Project Euler problem 145 (#5173) * Added solution for Project Euler problem 145 * Updated spelling of comments Updated spelling inline with codespell * Removed trailing whitespaces in comments * Added default values. * nr -> number Co-authored-by: John Law * nr -> number * Update sol1.py * Update sol1.py Co-authored-by: John Law --- project_euler/problem_145/__init__.py | 0 project_euler/problem_145/sol1.py | 87 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 project_euler/problem_145/__init__.py create mode 100644 project_euler/problem_145/sol1.py diff --git a/project_euler/problem_145/__init__.py b/project_euler/problem_145/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py new file mode 100644 index 000000000000..5ba3af86a6a1 --- /dev/null +++ b/project_euler/problem_145/sol1.py @@ -0,0 +1,87 @@ +""" +Problem 145: https://projecteuler.net/problem=145 + +Name: How many reversible numbers are there below one-billion? + +Some positive integers n have the property that the +sum [ n + reverse(n) ] consists entirely of odd (decimal) digits. +For instance, 36 + 63 = 99 and 409 + 904 = 1313. +We will call such numbers reversible; so 36, 63, 409, and 904 are reversible. +Leading zeroes are not allowed in either n or reverse(n). + +There are 120 reversible numbers below one-thousand. + +How many reversible numbers are there below one-billion (10^9)? + + +Solution: + +Here a brute force solution is used to find and count the reversible numbers. + +""" +from __future__ import annotations + + +def check_if_odd(sum: int = 36) -> int: + """ + Check if the last digit in the sum is even or odd. If even return 0. + If odd then floor division by 10 is used to remove the last number. + Process continues until sum becomes 0 because no more numbers. + >>> check_if_odd(36) + 0 + >>> check_if_odd(33) + 1 + """ + while sum > 0: + if (sum % 10) % 2 == 0: + return 0 + sum = sum // 10 + return 1 + + +def find_reverse_number(number: int = 36) -> int: + """ + Reverses the given number. Does not work with number that end in zero. + >>> find_reverse_number(36) + 63 + >>> find_reverse_number(409) + 904 + """ + reverse = 0 + + while number > 0: + temp = number % 10 + reverse = reverse * 10 + temp + number = number // 10 + + return reverse + + +def solution(number: int = 1000000000) -> int: + """ + Loops over the range of numbers. + Checks if they have ending zeros which disqualifies them from being reversible. + If that condition is passed it generates the reversed number. + Then sum up n and reverse(n). + Then check if all the numbers in the sum are odd. If true add to the answer. + >>> solution(1000000000) + 608720 + >>> solution(1000000) + 18720 + >>> solution(1000000) + 18720 + >>> solution(1000) + 120 + """ + answer = 0 + for x in range(1, number): + if x % 10 != 0: + reversed_number = find_reverse_number(x) + sum = x + reversed_number + answer += check_if_odd(sum) + + return answer + + +if __name__ == "__main__": + print(f"{solution() = }") From e9882e41ba7a1d1fbdc12362a522a1b646497192 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Wed, 10 Nov 2021 20:52:52 +0530 Subject: [PATCH 1752/2908] [mypy] Fix `matrix_operation.py` (#5808) * Update matrix_operation.py * Update mypy.ini * Update DIRECTORY.md * formatting * Update matrix_operation.py * doctest for exception * A bit more... --- DIRECTORY.md | 2 ++ matrix/matrix_operation.py | 33 ++++++++++++++++++++++++--------- mypy.ini | 2 +- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 883b81b2444d..c46d81ab75bc 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -854,6 +854,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_135/sol1.py) * Problem 144 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_144/sol1.py) + * Problem 145 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_145/sol1.py) * Problem 173 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) * Problem 174 diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index dca01f9c3183..6d0cd4e655eb 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -13,11 +13,16 @@ def add(*matrix_s: list[list]) -> list[list]: [[3.2, 5.4], [7, 9]] >>> add([[1, 2], [4, 5]], [[3, 7], [3, 4]], [[3, 5], [5, 7]]) [[7, 14], [12, 16]] + >>> add([3], [4, 5]) + Traceback (most recent call last): + ... + TypeError: Expected a matrix, got int/list instead """ if all(_check_not_integer(m) for m in matrix_s): 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)] + raise TypeError("Expected a matrix, got int/list instead") def subtract(matrix_a: list[list], matrix_b: list[list]) -> list[list]: @@ -26,6 +31,10 @@ def subtract(matrix_a: list[list], matrix_b: list[list]) -> list[list]: [[-1, -1], [-1, -1]] >>> subtract([[1,2.5],[3,4]],[[2,3],[4,5.5]]) [[-1, -0.5], [-1, -1.5]] + >>> subtract([3], [4, 5]) + Traceback (most recent call last): + ... + TypeError: Expected a matrix, got int/list instead """ if ( _check_not_integer(matrix_a) @@ -33,9 +42,10 @@ def subtract(matrix_a: list[list], matrix_b: list[list]) -> list[list]: 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)] + raise TypeError("Expected a matrix, got int/list instead") -def scalar_multiply(matrix: list[list], n: int) -> list[list]: +def scalar_multiply(matrix: list[list], n: int | float) -> list[list]: """ >>> scalar_multiply([[1,2],[3,4]],5) [[5, 10], [15, 20]] @@ -79,18 +89,23 @@ 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] | map[list]: """ >>> transpose([[1,2],[3,4]]) # doctest: +ELLIPSIS >> transpose([[1,2],[3,4]], return_map=False) [[1, 3], [2, 4]] + >>> transpose([1, [2, 3]]) + Traceback (most recent call last): + ... + TypeError: Expected a matrix, got int/list instead """ if _check_not_integer(matrix): if return_map: return map(list, zip(*matrix)) else: return list(map(list, zip(*matrix))) + raise TypeError("Expected a matrix, got int/list instead") def minor(matrix: list[list], row: int, column: int) -> list[list]: @@ -118,7 +133,7 @@ def determinant(matrix: list[list]) -> int: ) -def inverse(matrix: list[list]) -> list[list]: +def inverse(matrix: list[list]) -> list[list] | None: """ >>> inverse([[1, 2], [3, 4]]) [[-2.0, 1.0], [1.5, -0.5]] @@ -138,21 +153,21 @@ def inverse(matrix: list[list]) -> list[list]: [x * (-1) ** (row + col) for col, x in enumerate(matrix_minor[row])] for row in range(len(matrix)) ] - adjugate = transpose(cofactors) + adjugate = list(transpose(cofactors)) return scalar_multiply(adjugate, 1 / det) 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") + return not isinstance(matrix, int) and not isinstance(matrix[0], int) -def _shape(matrix: list[list]) -> list: +def _shape(matrix: list[list]) -> tuple[int, int]: 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[tuple, tuple]: shape = _shape(matrix_a) + _shape(matrix_b) if shape[0] != shape[3] or shape[1] != shape[2]: raise ValueError( diff --git a/mypy.ini b/mypy.ini index 94fb125fb124..f00b3eeb6bac 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True install_types = True non_interactive = True -exclude = (matrix_operation.py|other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py) +exclude = (other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py) From 7e81551d7b54f121458bd8e6a67b7ca86156815c Mon Sep 17 00:00:00 2001 From: Joyce Date: Thu, 11 Nov 2021 03:55:23 +0800 Subject: [PATCH 1753/2908] [mypy] fix type annotations for other/least-recently-used.py (#5811) --- other/least_recently_used.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/other/least_recently_used.py b/other/least_recently_used.py index d0e27efc6dc8..9d6b6d7cb6a6 100644 --- a/other/least_recently_used.py +++ b/other/least_recently_used.py @@ -1,5 +1,4 @@ import sys -from abc import abstractmethod from collections import deque @@ -10,7 +9,6 @@ class LRUCache: 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. From f36ee034f1f5c65cc89ed1fadea29a28e744a297 Mon Sep 17 00:00:00 2001 From: Andrew Grangaard Date: Wed, 10 Nov 2021 14:21:16 -0800 Subject: [PATCH 1754/2908] [mypy] Annotate other/lru_cache and other/lfu_cache (#5755) * Adds repr and doctest of current behavior linkedlist in other/lru_cache * Blocks removal of head or tail of double linked list * clarifies add() logic for double linked list in other/lru_cache * expands doctests to compare cache and lru cache * [mypy] annotates vars for other/lru_cache * [mypy] Annotates lru_cache decorator for other/lru_cache * Higher order functions require a verbose Callable annotation * [mypy] Makes LRU_Cache generic over key and value types for other/lru_cache + no reason to force int -> int * [mypy] makes decorator a classmethod for access to class generic types * breaks two long lines in doctest for other/lru_cache * simplifies boundary test remove() for other/lru_cache * [mypy] Annotates, adds doctests, and makes Generic other/lfu_cache See also commits to other/lru_cache which guided these * [mypy] annotates cls var in other/lfu_cache * cleans up items from code review for lfu_cache and lru_cache * [mypy] runs mypy on lfu_cache and lru_cache --- mypy.ini | 2 +- other/lfu_cache.py | 232 ++++++++++++++++++++++++++++++++++---------- other/lru_cache.py | 235 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 366 insertions(+), 103 deletions(-) diff --git a/mypy.ini b/mypy.ini index f00b3eeb6bac..7dbc7c4ffc80 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,4 +2,4 @@ ignore_missing_imports = True install_types = True non_interactive = True -exclude = (other/least_recently_used.py|other/lfu_cache.py|other/lru_cache.py) +exclude = (other/least_recently_used.py) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 88167ac1f2cb..e955973c95b0 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -1,61 +1,165 @@ from __future__ import annotations -from typing import Callable +from typing import Callable, Generic, TypeVar +T = TypeVar("T") +U = TypeVar("U") -class DoubleLinkedListNode: + +class DoubleLinkedListNode(Generic[T, U]): """ Double Linked List Node built specifically for LFU Cache + + >>> node = DoubleLinkedListNode(1,1) + >>> node + Node: key: 1, val: 1, freq: 0, has next: False, has prev: False """ - def __init__(self, key: int, val: int): + def __init__(self, key: T | None, val: U | None): self.key = key self.val = val - self.freq = 0 - self.next = None - self.prev = None + self.freq: int = 0 + self.next: DoubleLinkedListNode[T, U] | None = None + self.prev: DoubleLinkedListNode[T, U] | None = None + def __repr__(self) -> str: + return "Node: key: {}, val: {}, freq: {}, has next: {}, has prev: {}".format( + self.key, self.val, self.freq, self.next is not None, self.prev is not None + ) -class DoubleLinkedList: + +class DoubleLinkedList(Generic[T, U]): """ Double Linked List built specifically for LFU Cache + + >>> dll: DoubleLinkedList = DoubleLinkedList() + >>> dll + DoubleLinkedList, + Node: key: None, val: None, freq: 0, has next: True, has prev: False, + Node: key: None, val: None, freq: 0, has next: False, has prev: True + + >>> first_node = DoubleLinkedListNode(1,10) + >>> first_node + Node: key: 1, val: 10, freq: 0, has next: False, has prev: False + + + >>> dll.add(first_node) + >>> dll + DoubleLinkedList, + Node: key: None, val: None, freq: 0, has next: True, has prev: False, + Node: key: 1, val: 10, freq: 1, has next: True, has prev: True, + Node: key: None, val: None, freq: 0, has next: False, has prev: True + + >>> # node is mutated + >>> first_node + Node: key: 1, val: 10, freq: 1, has next: True, has prev: True + + >>> second_node = DoubleLinkedListNode(2,20) + >>> second_node + Node: key: 2, val: 20, freq: 0, has next: False, has prev: False + + >>> dll.add(second_node) + >>> dll + DoubleLinkedList, + Node: key: None, val: None, freq: 0, has next: True, has prev: False, + Node: key: 1, val: 10, freq: 1, has next: True, has prev: True, + Node: key: 2, val: 20, freq: 1, has next: True, has prev: True, + Node: key: None, val: None, freq: 0, has next: False, has prev: True + + >>> removed_node = dll.remove(first_node) + >>> assert removed_node == first_node + >>> dll + DoubleLinkedList, + Node: key: None, val: None, freq: 0, has next: True, has prev: False, + Node: key: 2, val: 20, freq: 1, has next: True, has prev: True, + Node: key: None, val: None, freq: 0, has next: False, has prev: True + + + >>> # Attempt to remove node not on list + >>> removed_node = dll.remove(first_node) + >>> removed_node is None + True + + >>> # Attempt to remove head or rear + >>> dll.head + Node: key: None, val: None, freq: 0, has next: True, has prev: False + >>> dll.remove(dll.head) is None + True + + >>> # Attempt to remove head or rear + >>> dll.rear + Node: key: None, val: None, freq: 0, has next: False, has prev: True + >>> dll.remove(dll.rear) is None + True + + """ - def __init__(self): - self.head = DoubleLinkedListNode(None, None) - self.rear = DoubleLinkedListNode(None, None) + def __init__(self) -> None: + self.head: DoubleLinkedListNode[T, U] = DoubleLinkedListNode(None, None) + self.rear: DoubleLinkedListNode[T, U] = DoubleLinkedListNode(None, None) self.head.next, self.rear.prev = self.rear, self.head - def add(self, node: DoubleLinkedListNode) -> None: + def __repr__(self) -> str: + rep = ["DoubleLinkedList"] + node = self.head + while node.next is not None: + rep.append(str(node)) + node = node.next + rep.append(str(self.rear)) + return ",\n ".join(rep) + + def add(self, node: DoubleLinkedListNode[T, U]) -> None: """ - Adds the given node at the head of the list and shifting it to proper position + Adds the given node at the tail of the list and shifting it to proper position """ - temp = self.rear.prev + previous = self.rear.prev + + # All nodes other than self.head are guaranteed to have non-None previous + assert previous is not None - self.rear.prev, node.next = node, self.rear - temp.next, node.prev = node, temp + previous.next = node + node.prev = previous + self.rear.prev = node + node.next = self.rear 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 _position_node(self, node: DoubleLinkedListNode[T, U]) -> None: + """ + Moves node forward to maintain invariant of sort by freq value + """ + + while node.prev is not None and node.prev.freq > node.freq: + # swap node with previous node + previous_node = node.prev - def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: + node.prev = previous_node.prev + previous_node.next = node.prev + node.next = previous_node + previous_node.prev = node + + def remove( + self, node: DoubleLinkedListNode[T, U] + ) -> DoubleLinkedListNode[T, U] | None: """ Removes and returns the given node from the list + + Returns None if node.prev or node.next is None """ - temp_last, temp_next = node.prev, node.next - node.prev, node.next = None, None - temp_last.next, temp_next.prev = temp_next, temp_last + if node.prev is None or node.next is None: + return None + + node.prev.next = node.next + node.next.prev = node.prev + node.prev = None + node.next = None return node -class LFUCache: +class LFUCache(Generic[T, U]): """ LFU Cache to store a given capacity of data. Can be used as a stand-alone object or as a function decorator. @@ -66,9 +170,11 @@ class LFUCache: >>> cache.get(1) 1 >>> cache.set(3, 3) - >>> cache.get(2) # None is returned + >>> cache.get(2) is None + True >>> cache.set(4, 4) - >>> cache.get(1) # None is returned + >>> cache.get(1) is None + True >>> cache.get(3) 3 >>> cache.get(4) @@ -89,15 +195,15 @@ class LFUCache: """ # class variable to map the decorator functions to their respective instance - decorator_function_to_instance_map = {} + decorator_function_to_instance_map: dict[Callable[[T], U], LFUCache[T, U]] = {} def __init__(self, capacity: int): - self.list = DoubleLinkedList() + self.list: DoubleLinkedList[T, U] = DoubleLinkedList() self.capacity = capacity self.num_keys = 0 self.hits = 0 self.miss = 0 - self.cache = {} + self.cache: dict[T, DoubleLinkedListNode[T, U]] = {} def __repr__(self) -> str: """ @@ -110,40 +216,57 @@ def __repr__(self) -> str: f"capacity={self.capacity}, current_size={self.num_keys})" ) - def __contains__(self, key: int) -> bool: + def __contains__(self, key: T) -> 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) -> int | None: + def get(self, key: T) -> U | None: """ Returns the value for the input key and updates the Double Linked List. Returns - None if key is not present in cache + 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 + value_node: DoubleLinkedListNode[T, U] = self.cache[key] + node = self.list.remove(self.cache[key]) + assert node == value_node + + # node is guaranteed not None because it is in self.cache + assert node is not None + self.list.add(node) + return node.val self.miss += 1 return None - def set(self, key: int, value: int) -> None: + def set(self, key: T, value: U) -> 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] + # delete first node when over capacity + first_node = self.list.head.next + + # guaranteed to have a non-None first node when num_keys > 0 + # explain to type checker via assertions + assert first_node is not None + assert first_node.key is not None + assert self.list.remove(first_node) is not None + # first_node guaranteed to be in list + + del self.cache[first_node.key] self.num_keys -= 1 self.cache[key] = DoubleLinkedListNode(key, value) self.list.add(self.cache[key]) @@ -151,32 +274,35 @@ def set(self, key: int, value: int) -> None: else: node = self.list.remove(self.cache[key]) + assert node is not None # node guaranteed to be in list node.val = value self.list.add(node) - @staticmethod - def decorator(size: int = 128): + @classmethod + def decorator( + cls: type[LFUCache[T, U]], size: int = 128 + ) -> Callable[[Callable[[T], U]], Callable[..., U]]: """ Decorator version of LFU Cache + + Decorated function must be function of T -> U """ - 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) + def cache_decorator_inner(func: Callable[[T], U]) -> Callable[..., U]: + def cache_decorator_wrapper(*args: T) -> U: + if func not in cls.decorator_function_to_instance_map: + cls.decorator_function_to_instance_map[func] = LFUCache(size) - result = LFUCache.decorator_function_to_instance_map[func].get(args[0]) + result = cls.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 - ) + result = func(*args) + cls.decorator_function_to_instance_map[func].set(args[0], result) return result - def cache_info(): - return LFUCache.decorator_function_to_instance_map[func] + def cache_info() -> LFUCache[T, U]: + return cls.decorator_function_to_instance_map[func] - cache_decorator_wrapper.cache_info = cache_info + setattr(cache_decorator_wrapper, "cache_info", cache_info) return cache_decorator_wrapper diff --git a/other/lru_cache.py b/other/lru_cache.py index b74c0a45caf9..98051f89db4f 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -1,52 +1,147 @@ from __future__ import annotations -from typing import Callable +from typing import Callable, Generic, TypeVar +T = TypeVar("T") +U = TypeVar("U") -class DoubleLinkedListNode: + +class DoubleLinkedListNode(Generic[T, U]): """ Double Linked List Node built specifically for LRU Cache + + >>> DoubleLinkedListNode(1,1) + Node: key: 1, val: 1, has next: False, has prev: False """ - def __init__(self, key: int, val: int): + def __init__(self, key: T | None, val: U | None): self.key = key self.val = val - self.next = None - self.prev = None + self.next: DoubleLinkedListNode[T, U] | None = None + self.prev: DoubleLinkedListNode[T, U] | None = None + + def __repr__(self) -> str: + return "Node: key: {}, val: {}, has next: {}, has prev: {}".format( + self.key, self.val, self.next is not None, self.prev is not None + ) -class DoubleLinkedList: +class DoubleLinkedList(Generic[T, U]): """ Double Linked List built specifically for LRU Cache + + >>> dll: DoubleLinkedList = DoubleLinkedList() + >>> dll + DoubleLinkedList, + Node: key: None, val: None, has next: True, has prev: False, + Node: key: None, val: None, has next: False, has prev: True + + >>> first_node = DoubleLinkedListNode(1,10) + >>> first_node + Node: key: 1, val: 10, has next: False, has prev: False + + + >>> dll.add(first_node) + >>> dll + DoubleLinkedList, + Node: key: None, val: None, has next: True, has prev: False, + Node: key: 1, val: 10, has next: True, has prev: True, + Node: key: None, val: None, has next: False, has prev: True + + >>> # node is mutated + >>> first_node + Node: key: 1, val: 10, has next: True, has prev: True + + >>> second_node = DoubleLinkedListNode(2,20) + >>> second_node + Node: key: 2, val: 20, has next: False, has prev: False + + >>> dll.add(second_node) + >>> dll + DoubleLinkedList, + Node: key: None, val: None, has next: True, has prev: False, + Node: key: 1, val: 10, has next: True, has prev: True, + Node: key: 2, val: 20, has next: True, has prev: True, + Node: key: None, val: None, has next: False, has prev: True + + >>> removed_node = dll.remove(first_node) + >>> assert removed_node == first_node + >>> dll + DoubleLinkedList, + Node: key: None, val: None, has next: True, has prev: False, + Node: key: 2, val: 20, has next: True, has prev: True, + Node: key: None, val: None, has next: False, has prev: True + + + >>> # Attempt to remove node not on list + >>> removed_node = dll.remove(first_node) + >>> removed_node is None + True + + >>> # Attempt to remove head or rear + >>> dll.head + Node: key: None, val: None, has next: True, has prev: False + >>> dll.remove(dll.head) is None + True + + >>> # Attempt to remove head or rear + >>> dll.rear + Node: key: None, val: None, has next: False, has prev: True + >>> dll.remove(dll.rear) is None + True + + """ - def __init__(self): - self.head = DoubleLinkedListNode(None, None) - self.rear = DoubleLinkedListNode(None, None) + def __init__(self) -> None: + self.head: DoubleLinkedListNode[T, U] = DoubleLinkedListNode(None, None) + self.rear: DoubleLinkedListNode[T, U] = DoubleLinkedListNode(None, None) self.head.next, self.rear.prev = self.rear, self.head - def add(self, node: DoubleLinkedListNode) -> None: + def __repr__(self) -> str: + rep = ["DoubleLinkedList"] + node = self.head + while node.next is not None: + rep.append(str(node)) + node = node.next + rep.append(str(self.rear)) + return ",\n ".join(rep) + + def add(self, node: DoubleLinkedListNode[T, U]) -> 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 + previous = self.rear.prev + + # All nodes other than self.head are guaranteed to have non-None previous + assert previous is not None + + previous.next = node + node.prev = previous + self.rear.prev = node + node.next = self.rear - def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: + def remove( + self, node: DoubleLinkedListNode[T, U] + ) -> DoubleLinkedListNode[T, U] | None: """ Removes and returns the given node from the list + + Returns None if node.prev or node.next is None """ - temp_last, temp_next = node.prev, node.next - node.prev, node.next = None, None - temp_last.next, temp_next.prev = temp_next, temp_last + if node.prev is None or node.next is None: + return None + node.prev.next = node.next + node.next.prev = node.prev + node.prev = None + node.next = None return node -class LRUCache: +class LRUCache(Generic[T, U]): """ LRU Cache to store a given capacity of data. Can be used as a stand-alone object or as a function decorator. @@ -54,19 +149,41 @@ class LRUCache: >>> cache = LRUCache(2) >>> cache.set(1, 1) - >>> cache.set(2, 2) - >>> cache.get(1) 1 + >>> cache.list + DoubleLinkedList, + Node: key: None, val: None, has next: True, has prev: False, + Node: key: 2, val: 2, has next: True, has prev: True, + Node: key: 1, val: 1, has next: True, has prev: True, + Node: key: None, val: None, has next: False, has prev: True + + >>> cache.cache # doctest: +NORMALIZE_WHITESPACE + {1: Node: key: 1, val: 1, has next: True, has prev: True, \ + 2: Node: key: 2, val: 2, has next: True, has prev: True} + >>> cache.set(3, 3) - >>> cache.get(2) # None returned + >>> cache.list + DoubleLinkedList, + Node: key: None, val: None, has next: True, has prev: False, + Node: key: 1, val: 1, has next: True, has prev: True, + Node: key: 3, val: 3, has next: True, has prev: True, + Node: key: None, val: None, has next: False, has prev: True + + >>> cache.cache # doctest: +NORMALIZE_WHITESPACE + {1: Node: key: 1, val: 1, has next: True, has prev: True, \ + 3: Node: key: 3, val: 3, has next: True, has prev: True} + + >>> cache.get(2) is None + True >>> cache.set(4, 4) - >>> cache.get(1) # None returned + >>> cache.get(1) is None + True >>> cache.get(3) 3 @@ -91,15 +208,15 @@ class LRUCache: """ # class variable to map the decorator functions to their respective instance - decorator_function_to_instance_map = {} + decorator_function_to_instance_map: dict[Callable[[T], U], LRUCache[T, U]] = {} def __init__(self, capacity: int): - self.list = DoubleLinkedList() + self.list: DoubleLinkedList[T, U] = DoubleLinkedList() self.capacity = capacity self.num_keys = 0 self.hits = 0 self.miss = 0 - self.cache = {} + self.cache: dict[T, DoubleLinkedListNode[T, U]] = {} def __repr__(self) -> str: """ @@ -112,7 +229,7 @@ def __repr__(self) -> str: f"capacity={self.capacity}, current size={self.num_keys})" ) - def __contains__(self, key: int) -> bool: + def __contains__(self, key: T) -> bool: """ >>> cache = LRUCache(1) @@ -127,62 +244,82 @@ def __contains__(self, key: int) -> bool: return key in self.cache - def get(self, key: int) -> int | None: + def get(self, key: T) -> U | None: """ - Returns the value for the input key and updates the Double Linked List. Returns - None if key is not present in cache + Returns the value for the input key and updates the Double Linked List. + Returns None if key is not present in cache """ + # Note: pythonic interface would throw KeyError rather than return None if key in self.cache: self.hits += 1 - self.list.add(self.list.remove(self.cache[key])) - return self.cache[key].val + value_node: DoubleLinkedListNode[T, U] = self.cache[key] + node = self.list.remove(self.cache[key]) + assert node == value_node + + # node is guaranteed not None because it is in self.cache + assert node is not None + self.list.add(node) + return node.val self.miss += 1 return None - def set(self, key: int, value: int) -> None: + def set(self, key: T, value: U) -> 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] + # delete first node (oldest) when over capacity + first_node = self.list.head.next + + # guaranteed to have a non-None first node when num_keys > 0 + # explain to type checker via assertions + assert first_node is not None + assert first_node.key is not None + assert ( + self.list.remove(first_node) is not None + ) # node guaranteed to be in list assert node.key is not None + + del self.cache[first_node.key] self.num_keys -= 1 self.cache[key] = DoubleLinkedListNode(key, value) self.list.add(self.cache[key]) self.num_keys += 1 else: + # bump node to the end of the list, update value node = self.list.remove(self.cache[key]) + assert node is not None # node guaranteed to be in list node.val = value self.list.add(node) - @staticmethod - def decorator(size: int = 128): + @classmethod + def decorator( + cls, size: int = 128 + ) -> Callable[[Callable[[T], U]], Callable[..., U]]: """ Decorator version of LRU Cache + + Decorated function must be function of T -> U """ - 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) + def cache_decorator_inner(func: Callable[[T], U]) -> Callable[..., U]: + def cache_decorator_wrapper(*args: T) -> U: + if func not in cls.decorator_function_to_instance_map: + cls.decorator_function_to_instance_map[func] = LRUCache(size) - result = LRUCache.decorator_function_to_instance_map[func].get(args[0]) + result = cls.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 - ) + result = func(*args) + cls.decorator_function_to_instance_map[func].set(args[0], result) return result - def cache_info(): - return LRUCache.decorator_function_to_instance_map[func] + def cache_info() -> LRUCache[T, U]: + return cls.decorator_function_to_instance_map[func] - cache_decorator_wrapper.cache_info = cache_info + setattr(cache_decorator_wrapper, "cache_info", cache_info) return cache_decorator_wrapper From 6314195bb1e6001ddd0a11a59c41f1d0b9eeb722 Mon Sep 17 00:00:00 2001 From: Leoriem-code <73761711+Leoriem-code@users.noreply.github.com> Date: Thu, 11 Nov 2021 14:39:54 +0100 Subject: [PATCH 1755/2908] Add README files 2/8 (#5766) * add 5 README files * empty commit to (hopefully) get rid of the `test-are-failling` label * Update ciphers/README.md Co-authored-by: John Law * Update conversions/README.md Co-authored-by: John Law * Update cellular_automata/README.md Co-authored-by: John Law * Update computer_vision/README.md Co-authored-by: John Law * Update conversions/README.md Co-authored-by: John Law * Update compression/README.md Co-authored-by: John Law --- cellular_automata/README.md | 8 ++++++-- ciphers/README.md | 7 +++++++ compression/README.md | 10 ++++++++++ computer_vision/README.md | 10 +++++++--- conversions/README.md | 6 ++++++ 5 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 ciphers/README.md create mode 100644 compression/README.md create mode 100644 conversions/README.md diff --git a/cellular_automata/README.md b/cellular_automata/README.md index c3fa0516f5dd..c5681b33906c 100644 --- a/cellular_automata/README.md +++ b/cellular_automata/README.md @@ -1,4 +1,8 @@ # Cellular Automata -* https://en.wikipedia.org/wiki/Cellular_automaton -* https://mathworld.wolfram.com/ElementaryCellularAutomaton.html +Cellular automata are a way to simulate the behavior of "life", no matter if it is a robot or cell. +They usually follow simple rules but can lead to the creation of complex forms. +The most popular cellular automaton is Conway's [Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life). + +* +* diff --git a/ciphers/README.md b/ciphers/README.md new file mode 100644 index 000000000000..fa09874f38e5 --- /dev/null +++ b/ciphers/README.md @@ -0,0 +1,7 @@ +# Ciphers + +Ciphers are used to protect data from people that are not allowed to have it. They are everywhere on the internet to protect your connections. + +* +* +* diff --git a/compression/README.md b/compression/README.md new file mode 100644 index 000000000000..cf54ea986175 --- /dev/null +++ b/compression/README.md @@ -0,0 +1,10 @@ +# Compression + +Data compression is everywhere, you need it to store data without taking too much space. +Either the compression lose some data (then we talk about lossy compression, such as .jpg) or it does not (and then it is lossless compression, such as .png) + +Lossless compression is mainly used for archive purpose as it allow storing data without losing information about the file archived. On the other hand, lossy compression is used for transfer of file where quality isn't necessarily what is required (i.e: images on Twitter). + +* +* +* diff --git a/computer_vision/README.md b/computer_vision/README.md index 94ee493086cc..8d2f4a130d05 100644 --- a/computer_vision/README.md +++ b/computer_vision/README.md @@ -1,7 +1,11 @@ -### Computer Vision +# 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 does, and provide appropriate output. -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 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). + +* +* diff --git a/conversions/README.md b/conversions/README.md new file mode 100644 index 000000000000..ec3d931fd828 --- /dev/null +++ b/conversions/README.md @@ -0,0 +1,6 @@ +# Conversion + +Conversion programs convert a type of data, a number from a numerical base or unit into one of another type, base or unit, e.g. binary to decimal, integer to string or foot to meters. + +* +* From 9b9405fdcdc5ba9fc16a53f8ae6be081d4f5582a Mon Sep 17 00:00:00 2001 From: Bartechnika <48760796+Bartechnika@users.noreply.github.com> Date: Sat, 13 Nov 2021 09:32:44 +0000 Subject: [PATCH 1756/2908] Add new persistence algorithm (#4751) * Created new persistence algorithm * Update persistence.py * Added another persistence function --- maths/persistence.py | 82 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 maths/persistence.py diff --git a/maths/persistence.py b/maths/persistence.py new file mode 100644 index 000000000000..607641e67200 --- /dev/null +++ b/maths/persistence.py @@ -0,0 +1,82 @@ +def multiplicative_persistence(num: int) -> int: + """ + Return the persistence of a given number. + + https://en.wikipedia.org/wiki/Persistence_of_a_number + + >>> multiplicative_persistence(217) + 2 + >>> multiplicative_persistence(-1) + Traceback (most recent call last): + ... + ValueError: multiplicative_persistence() does not accept negative values + >>> multiplicative_persistence("long number") + Traceback (most recent call last): + ... + ValueError: multiplicative_persistence() only accepts integral values + """ + + if not isinstance(num, int): + raise ValueError("multiplicative_persistence() only accepts integral values") + if num < 0: + raise ValueError("multiplicative_persistence() does not accept negative values") + + steps = 0 + num_string = str(num) + + while len(num_string) != 1: + numbers = [int(i) for i in num_string] + + total = 1 + for i in range(0, len(numbers)): + total *= numbers[i] + + num_string = str(total) + + steps += 1 + return steps + + +def additive_persistence(num: int) -> int: + """ + Return the persistence of a given number. + + https://en.wikipedia.org/wiki/Persistence_of_a_number + + >>> additive_persistence(199) + 3 + >>> additive_persistence(-1) + Traceback (most recent call last): + ... + ValueError: additive_persistence() does not accept negative values + >>> additive_persistence("long number") + Traceback (most recent call last): + ... + ValueError: additive_persistence() only accepts integral values + """ + + if not isinstance(num, int): + raise ValueError("additive_persistence() only accepts integral values") + if num < 0: + raise ValueError("additive_persistence() does not accept negative values") + + steps = 0 + num_string = str(num) + + while len(num_string) != 1: + numbers = [int(i) for i in num_string] + + total = 0 + for i in range(0, len(numbers)): + total += numbers[i] + + num_string = str(total) + + steps += 1 + return steps + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 551c65766d1594ecd7e127ac288ed23b9e89b8c3 Mon Sep 17 00:00:00 2001 From: Andrew Grangaard Date: Tue, 16 Nov 2021 06:01:17 -0800 Subject: [PATCH 1757/2908] [Mypy] fix other/least_recently_used (#5814) * makes LRUCache constructor concrete * fixes bug in dq_removal in other/least_recently_used + deque.remove() operates by value not index * [mypy] Annotates other/least_recently_used over generic type + clean-up: rename key_reference to match type. * [mypy] updates example to demonstrate LRUCache with complex type * Adds doctest to other/least_recently_used * mypy.ini: Remove exclude = (other/least_recently_used.py) * Various mypy configs * Delete mypy.ini * Add mypy to .pre-commit-config.yaml * mypy --ignore-missing-imports --install-types --non-interactive . * mypy v0.910 * Pillow=8.3.7 * Pillow==8.3.7 * Pillow==8.3.2 * Update .pre-commit-config.yaml * Update requirements.txt * Update pre-commit.yml * --install-types # See mirrors-mypy README.md Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 2 +- .github/workflows/pre-commit.yml | 2 + .pre-commit-config.yaml | 23 +++++++---- mypy.ini | 5 --- other/least_recently_used.py | 70 +++++++++++++++++++++++--------- requirements.txt | 1 - 6 files changed, 68 insertions(+), 35 deletions(-) delete mode 100644 mypy.ini diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5f8d6b39a7b..df000cda5997 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: run: | python -m pip install --upgrade pip setuptools six wheel python -m pip install mypy pytest-cov -r requirements.txt - - run: mypy . # See `mypy.ini` for configuration settings. + - run: mypy --ignore-missing-imports --install-types --non-interactive . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 27a5a97c0b6c..19196098b1c1 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,6 +14,8 @@ jobs: ~/.cache/pip key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/setup-python@v2 + with: + python-version: 3.9 - uses: psf/black@21.4b0 - name: Install pre-commit run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e60003051365..0ebd6dfa0d7e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,22 +12,26 @@ repos: data_structures/heap/binomial_heap.py )$ - id: requirements-txt-fixer + - repo: https://github.com/psf/black rev: 21.4b0 hooks: - id: black + - repo: https://github.com/PyCQA/isort rev: 5.8.0 hooks: - id: isort args: - --profile=black + - repo: https://github.com/asottile/pyupgrade rev: v2.29.0 hooks: - id: pyupgrade args: - --py39-plus + - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.1 hooks: @@ -36,13 +40,16 @@ repos: - --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/pre-commit/mirrors-mypy + rev: v0.910 + hooks: + - id: mypy + args: + - --ignore-missing-imports + - --install-types # See mirrors-mypy README.md + - --non-interactive + - repo: https://github.com/codespell-project/codespell rev: v2.0.0 hooks: @@ -50,13 +57,13 @@ repos: args: - --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,tim - --skip="./.*,./strings/dictionary.txt,./strings/words.txt,./project_euler/problem_022/p022_names.txt" - - --quiet-level=2 exclude: | (?x)^( strings/dictionary.txt | strings/words.txt | project_euler/problem_022/p022_names.txt )$ + - repo: local hooks: - id: validate-filenames diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 7dbc7c4ffc80..000000000000 --- a/mypy.ini +++ /dev/null @@ -1,5 +0,0 @@ -[mypy] -ignore_missing_imports = True -install_types = True -non_interactive = True -exclude = (other/least_recently_used.py) diff --git a/other/least_recently_used.py b/other/least_recently_used.py index 9d6b6d7cb6a6..cb692bb1b1c0 100644 --- a/other/least_recently_used.py +++ b/other/least_recently_used.py @@ -1,20 +1,45 @@ +from __future__ import annotations + import sys from collections import deque +from typing import Generic, TypeVar + +T = TypeVar("T") + + +class LRUCache(Generic[T]): + """ + Page Replacement Algorithm, Least Recently Used (LRU) Caching. + + >>> lru_cache: LRUCache[str | int] = LRUCache(4) + >>> lru_cache.refer("A") + >>> lru_cache.refer(2) + >>> lru_cache.refer(3) + + >>> lru_cache + LRUCache(4) => [3, 2, 'A'] + >>> lru_cache.refer("A") + >>> lru_cache + LRUCache(4) => ['A', 3, 2] -class LRUCache: - """Page Replacement Algorithm, Least Recently Used (LRU) Caching.""" + >>> lru_cache.refer(4) + >>> lru_cache.refer(5) + >>> lru_cache + LRUCache(4) => [5, 4, 'A', 3] - dq_store = object() # Cache store of keys - key_reference_map = object() # References of the keys in cache + """ + + dq_store: deque[T] # Cache store of keys + key_reference: set[T] # References of the keys in cache _MAX_CAPACITY: int = 10 # Maximum capacity of cache - def __init__(self, n: int): + def __init__(self, n: int) -> None: """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() + self.key_reference = set() if not n: LRUCache._MAX_CAPACITY = sys.maxsize elif n < 0: @@ -22,41 +47,46 @@ def __init__(self, n: int): else: LRUCache._MAX_CAPACITY = n - def refer(self, x): + def refer(self, x: T) -> None: """ 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 x not in self.key_reference: if len(self.dq_store) == LRUCache._MAX_CAPACITY: last_element = self.dq_store.pop() - self.key_reference_map.remove(last_element) + self.key_reference.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.remove(x) self.dq_store.appendleft(x) - self.key_reference_map.add(x) + self.key_reference.add(x) - def display(self): + def display(self) -> None: """ Prints all the elements in the store. """ for k in self.dq_store: print(k) + def __repr__(self) -> str: + return f"LRUCache({self._MAX_CAPACITY}) => {list(self.dq_store)}" + if __name__ == "__main__": - lru_cache = LRUCache(4) - lru_cache.refer(1) + import doctest + + doctest.testmod() + + lru_cache: LRUCache[str | int] = LRUCache(4) + lru_cache.refer("A") lru_cache.refer(2) lru_cache.refer(3) - lru_cache.refer(1) + lru_cache.refer("A") lru_cache.refer(4) lru_cache.refer(5) lru_cache.display() + + print(lru_cache) + assert str(lru_cache) == "LRUCache(4) => [5, 4, 'A', 3]" diff --git a/requirements.txt b/requirements.txt index e01d87cffabe..9a26dcc21f36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,5 +16,4 @@ sympy tensorflow texttable tweepy -types-requests xgboost From d848bfbf3229f2a3240a298a583f6b80a9efc1fd Mon Sep 17 00:00:00 2001 From: Navaneeth Sharma <63489382+Navaneeth-Sharma@users.noreply.github.com> Date: Wed, 17 Nov 2021 04:28:47 +0530 Subject: [PATCH 1758/2908] Adding Pooling Algorithms (#5826) * adding pooling algorithms * pooling.py: Adding pooling algorithms to computer vision pull_number= * pooling.py: Adding pooling algorithms to computer vision * pooling_functions.py: Adding pooling algorithms to computer vision * pooling.py: Adding Pooling Algorithms * pooling_functions.py Add and Update * Update pooling_functions.py * Update computer_vision/pooling_functions.py Co-authored-by: Christian Clauss * Update computer_vision/pooling_functions.py Co-authored-by: Christian Clauss * Update computer_vision/pooling_functions.py Co-authored-by: Christian Clauss * Update computer_vision/pooling_functions.py Co-authored-by: Christian Clauss * Update pooling_functions.py * Formatting pooling.py Co-authored-by: Christian Clauss --- computer_vision/pooling_functions.py | 135 +++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 computer_vision/pooling_functions.py diff --git a/computer_vision/pooling_functions.py b/computer_vision/pooling_functions.py new file mode 100644 index 000000000000..09beabcba82d --- /dev/null +++ b/computer_vision/pooling_functions.py @@ -0,0 +1,135 @@ +# Source : https://computersciencewiki.org/index.php/Max-pooling_/_Pooling +# Importing the libraries +import numpy as np +from PIL import Image + + +# Maxpooling Function +def maxpooling(arr: np.ndarray, size: int, stride: int) -> np.ndarray: + """ + This function is used to perform maxpooling on the input array of 2D matrix(image) + Args: + arr: numpy array + size: size of pooling matrix + stride: the number of pixels shifts over the input matrix + Returns: + numpy array of maxpooled matrix + Sample Input Output: + >>> maxpooling([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], 2, 2) + array([[ 6., 8.], + [14., 16.]]) + >>> maxpooling([[147, 180, 122],[241, 76, 32],[126, 13, 157]], 2, 1) + array([[241., 180.], + [241., 157.]]) + """ + arr = np.array(arr) + if arr.shape[0] != arr.shape[1]: + raise ValueError("The input array is not a square matrix") + i = 0 + j = 0 + mat_i = 0 + mat_j = 0 + + # compute the shape of the output matrix + maxpool_shape = (arr.shape[0] - size) // stride + 1 + # initialize the output matrix with zeros of shape maxpool_shape + updated_arr = np.zeros((maxpool_shape, maxpool_shape)) + + while i < arr.shape[0]: + if i + size > arr.shape[0]: + # if the end of the matrix is reached, break + break + while j < arr.shape[1]: + # if the end of the matrix is reached, break + if j + size > arr.shape[1]: + break + # compute the maximum of the pooling matrix + updated_arr[mat_i][mat_j] = np.max(arr[i : i + size, j : j + size]) + # shift the pooling matrix by stride of column pixels + j += stride + mat_j += 1 + + # shift the pooling matrix by stride of row pixels + i += stride + mat_i += 1 + + # reset the column index to 0 + j = 0 + mat_j = 0 + + return updated_arr + + +# Averagepooling Function +def avgpooling(arr: np.ndarray, size: int, stride: int) -> np.ndarray: + """ + This function is used to perform avgpooling on the input array of 2D matrix(image) + Args: + arr: numpy array + size: size of pooling matrix + stride: the number of pixels shifts over the input matrix + Returns: + numpy array of avgpooled matrix + Sample Input Output: + >>> avgpooling([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], 2, 2) + array([[ 3., 5.], + [11., 13.]]) + >>> avgpooling([[147, 180, 122],[241, 76, 32],[126, 13, 157]], 2, 1) + array([[161., 102.], + [114., 69.]]) + """ + arr = np.array(arr) + if arr.shape[0] != arr.shape[1]: + raise ValueError("The input array is not a square matrix") + i = 0 + j = 0 + mat_i = 0 + mat_j = 0 + + # compute the shape of the output matrix + avgpool_shape = (arr.shape[0] - size) // stride + 1 + # initialize the output matrix with zeros of shape avgpool_shape + updated_arr = np.zeros((avgpool_shape, avgpool_shape)) + + while i < arr.shape[0]: + # if the end of the matrix is reached, break + if i + size > arr.shape[0]: + break + while j < arr.shape[1]: + # if the end of the matrix is reached, break + if j + size > arr.shape[1]: + break + # compute the average of the pooling matrix + updated_arr[mat_i][mat_j] = int(np.average(arr[i : i + size, j : j + size])) + # shift the pooling matrix by stride of column pixels + j += stride + mat_j += 1 + + # shift the pooling matrix by stride of row pixels + i += stride + mat_i += 1 + # reset the column index to 0 + j = 0 + mat_j = 0 + + return updated_arr + + +# Main Function +if __name__ == "__main__": + from doctest import testmod + + testmod(name="avgpooling", verbose=True) + + # Loading the image + image = Image.open("path_to_image") + + # Converting the image to numpy array and maxpooling, displaying the result + # Ensure that the image is a square matrix + + Image.fromarray(maxpooling(np.array(image), size=3, stride=2)).show() + + # Converting the image to numpy array and averagepooling, displaying the result + # Ensure that the image is a square matrix + + Image.fromarray(avgpooling(np.array(image), size=3, stride=2)).show() From 1ae5abfc3ca5dcf89b7e378735ceb9ef41769cbf Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 17 Nov 2021 04:43:02 +0100 Subject: [PATCH 1759/2908] Replace typing.optional with new annotations syntax (#5829) * Replace typing.optional with new annotations syntax * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + data_structures/linked_list/__init__.py | 5 +++-- data_structures/linked_list/circular_linked_list.py | 6 ++++-- data_structures/linked_list/has_loop.py | 6 ++++-- .../linked_list/middle_element_of_linked_list.py | 4 ++-- maths/pollard_rho.py | 5 +++-- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index c46d81ab75bc..16244e6ad08e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -525,6 +525,7 @@ * [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) + * [Persistence](https://github.com/TheAlgorithms/Python/blob/master/maths/persistence.py) * [Pi Monte Carlo Estimation](https://github.com/TheAlgorithms/Python/blob/master/maths/pi_monte_carlo_estimation.py) * [Pollard Rho](https://github.com/TheAlgorithms/Python/blob/master/maths/pollard_rho.py) * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index 8ae171d71035..6ba660231ae1 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -5,8 +5,9 @@ head node gives us access of the complete list - Last node: points to null """ +from __future__ import annotations -from typing import Any, Optional +from typing import Any class Node: @@ -17,7 +18,7 @@ def __init__(self, item: Any, next: Any) -> None: class LinkedList: def __init__(self) -> None: - self.head: Optional[Node] = None + self.head: Node | None = None self.size = 0 def add(self, item: Any) -> None: diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 42794ba793a7..121d934c6957 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -1,10 +1,12 @@ -from typing import Any, Iterator, Optional +from __future__ import annotations + +from typing import Any, Iterator class Node: def __init__(self, data: Any): self.data: Any = data - self.next: Optional[Node] = None + self.next: Node | None = None class CircularLinkedList: diff --git a/data_structures/linked_list/has_loop.py b/data_structures/linked_list/has_loop.py index a155ab4c7c89..bc06ffe150e8 100644 --- a/data_structures/linked_list/has_loop.py +++ b/data_structures/linked_list/has_loop.py @@ -1,4 +1,6 @@ -from typing import Any, Optional +from __future__ import annotations + +from typing import Any class ContainsLoopError(Exception): @@ -8,7 +10,7 @@ class ContainsLoopError(Exception): class Node: def __init__(self, data: Any) -> None: self.data: Any = data - self.next_node: Optional[Node] = None + self.next_node: Node | None = None def __iter__(self): node = self 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 296696897715..0c6250f3b731 100644 --- a/data_structures/linked_list/middle_element_of_linked_list.py +++ b/data_structures/linked_list/middle_element_of_linked_list.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations class Node: @@ -17,7 +17,7 @@ def push(self, new_data: int) -> int: self.head = new_node return self.head.data - def middle_element(self) -> Optional[int]: + def middle_element(self) -> int | None: """ >>> link = LinkedList() >>> link.middle_element() diff --git a/maths/pollard_rho.py b/maths/pollard_rho.py index df020c63f2f9..0fc80cd4280b 100644 --- a/maths/pollard_rho.py +++ b/maths/pollard_rho.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from math import gcd -from typing import Union def pollard_rho( @@ -7,7 +8,7 @@ def pollard_rho( seed: int = 2, step: int = 1, attempts: int = 3, -) -> Union[int, None]: +) -> int | None: """ Use Pollard's Rho algorithm to return a nontrivial factor of ``num``. The returned factor may be composite and require further factorization. From f4a16f607b996b030347fadf011b57320b5624b2 Mon Sep 17 00:00:00 2001 From: Goodness Ezeh <88127727+GoodnessEzeh@users.noreply.github.com> Date: Wed, 24 Nov 2021 23:23:44 +0900 Subject: [PATCH 1760/2908] Lowercase g --> Capital G (#5845) * Updated the comments in the code * Update .gitignore * Update .gitignore * Update .gitignore Co-authored-by: Christian Clauss --- other/password_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/other/password_generator.py b/other/password_generator.py index cf7250c814ff..c09afd7e6125 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -1,4 +1,4 @@ -"""Password generator allows you to generate a random password of length N.""" +"""Password Generator allows you to generate a random password of length N.""" from random import choice, shuffle from string import ascii_letters, digits, punctuation @@ -24,7 +24,7 @@ def password_generator(length=8): # ctbi= characters that must be in password # 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 + # Password Generator = full boot with random_number, random_letters, and # random_character FUNCTIONS # Put your code here... i = i - len(ctbi) From 65d3cfff2fb6f75999fff7918bccb463593f939d Mon Sep 17 00:00:00 2001 From: Jaydeep Das Date: Sun, 28 Nov 2021 23:50:18 +0530 Subject: [PATCH 1761/2908] Added memoization function in fibonacci (#5856) * Added memoization function in fibonacci * Minor changes --- maths/fibonacci.py | 48 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/maths/fibonacci.py b/maths/fibonacci.py index 9b193b74a827..ca4f4a2360a3 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -1,13 +1,19 @@ # fibonacci.py """ -Calculates the Fibonacci sequence using iteration, recursion, and a simplified -form of Binet's formula +Calculates the Fibonacci sequence using iteration, recursion, memoization, +and a simplified form of Binet's formula -NOTE 1: the iterative and recursive functions are more accurate than the Binet's -formula function because the iterative function doesn't use floats +NOTE 1: the iterative, recursive, memoization functions are more accurate than +the Binet's formula function because the Binet formula function uses floats NOTE 2: the Binet's formula function is much more limited in the size of inputs that it can handle due to the size limitations of Python floats + +RESULTS: (n = 20) +fib_iterative runtime: 0.0055 ms +fib_recursive runtime: 6.5627 ms +fib_memoization runtime: 0.0107 ms +fib_binet runtime: 0.0174 ms """ from math import sqrt @@ -86,6 +92,39 @@ def fib_recursive_term(i: int) -> int: return [fib_recursive_term(i) for i in range(n + 1)] +def fib_memoization(n: int) -> list[int]: + """ + Calculates the first n (0-indexed) Fibonacci numbers using memoization + >>> fib_memoization(0) + [0] + >>> fib_memoization(1) + [0, 1] + >>> fib_memoization(5) + [0, 1, 1, 2, 3, 5] + >>> fib_memoization(10) + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + >>> fib_iterative(-1) + Traceback (most recent call last): + ... + Exception: n is negative + """ + if n < 0: + raise Exception("n is negative") + # Cache must be outside recursuive function + # other it will reset every time it calls itself. + cache: dict[int, int] = {0: 0, 1: 1, 2: 1} # Prefilled cache + + def rec_fn_memoized(num: int) -> int: + if num in cache: + return cache[num] + + value = rec_fn_memoized(num - 1) + rec_fn_memoized(num - 2) + cache[num] = value + return value + + return [rec_fn_memoized(i) for i in range(n + 1)] + + def fib_binet(n: int) -> list[int]: """ Calculates the first n (0-indexed) Fibonacci numbers using a simplified form @@ -127,4 +166,5 @@ def fib_binet(n: int) -> list[int]: num = 20 time_func(fib_iterative, num) time_func(fib_recursive, num) + time_func(fib_memoization, num) time_func(fib_binet, num) From 6680e435a7983c3691f2bb9399e675cc5dc632db Mon Sep 17 00:00:00 2001 From: yellowsto <79023119+yellowsto@users.noreply.github.com> Date: Thu, 16 Dec 2021 10:27:15 +0100 Subject: [PATCH 1762/2908] Update merge_insertion_sort.py (#5833) * Update merge_insertion_sort.py Fixes #5774 merge_insertion_sort Co-Authored-By: AilisOsswald <44617437+AilisOsswald@users.noreply.github.com> * Update merge_insertion_sort.py Fixes #5774 merge_insertion_sort Co-Authored-By: AilisOsswald <44617437+AilisOsswald@users.noreply.github.com> * Update merge_insertion_sort.py Fixes #5774 added permutation range from 0 to 4 Co-Authored-By: AilisOsswald <44617437+AilisOsswald@users.noreply.github.com> * Use `all()` Co-authored-by: AilisOsswald <44617437+AilisOsswald@users.noreply.github.com> Co-authored-by: John Law --- sorts/merge_insertion_sort.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sorts/merge_insertion_sort.py b/sorts/merge_insertion_sort.py index fb71d84a3c14..ecaa535457f4 100644 --- a/sorts/merge_insertion_sort.py +++ b/sorts/merge_insertion_sort.py @@ -30,6 +30,12 @@ def merge_insertion_sort(collection: list[int]) -> list[int]: >>> merge_insertion_sort([-2, -5, -45]) [-45, -5, -2] + + Testing with all permutations on range(0,5): + >>> import itertools + >>> permutations = list(itertools.permutations([0, 1, 2, 3, 4])) + >>> all(merge_insertion_sort(p) == [0, 1, 2, 3, 4] for p in permutations) + True """ def binary_search_insertion(sorted_list, item): @@ -160,7 +166,7 @@ def merge(left, right): """ is_last_odd_item_inserted_before_this_index = False for i in range(len(sorted_list_2d) - 1): - if result[i] == collection[-i]: + if result[i] == collection[-1] and has_last_odd_item: 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, From 9af2eef9b3761bf51580dedfb6fa7136ca0c5c2c Mon Sep 17 00:00:00 2001 From: RenatoLopes771 <52989307+RenatoLopes771@users.noreply.github.com> Date: Thu, 16 Dec 2021 06:28:31 -0300 Subject: [PATCH 1763/2908] =?UTF-8?q?Improve=20Quine=E2=80=93McCluskey=20a?= =?UTF-8?q?lgorithm=20(#4935)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Syntax improvements (I hope) to boolean algebra * Reverted certain index variables to i * remove extra line on decimal_to_binary * Update quine_mc_cluskey.py Co-authored-by: John Law --- boolean_algebra/quine_mc_cluskey.py | 44 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 0342e5c67753..fb23c8c2e79c 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Sequence + def compare_string(string1: str, string2: str) -> str: """ @@ -9,17 +11,17 @@ def compare_string(string1: str, string2: str) -> str: >>> compare_string('0110','1101') 'X' """ - l1 = list(string1) - l2 = list(string2) + list1 = list(string1) + list2 = list(string2) count = 0 - for i in range(len(l1)): - if l1[i] != l2[i]: + for i in range(len(list1)): + if list1[i] != list2[i]: count += 1 - l1[i] = "_" + list1[i] = "_" if count > 1: return "X" else: - return "".join(l1) + return "".join(list1) def check(binary: list[str]) -> list[str]: @@ -28,7 +30,7 @@ def check(binary: list[str]) -> list[str]: ['0.00.01.5'] """ pi = [] - while 1: + while True: check1 = ["$"] * len(binary) temp = [] for i in range(len(binary)): @@ -46,19 +48,18 @@ def check(binary: list[str]) -> list[str]: binary = list(set(temp)) -def decimal_to_binary(no_of_variable: int, minterms: list[float]) -> list[str]: +def decimal_to_binary(no_of_variable: int, minterms: Sequence[float]) -> list[str]: """ >>> decimal_to_binary(3,[1.5]) ['0.00.01.5'] """ temp = [] - s = "" - for m in minterms: + for minterm in minterms: + string = "" for i in range(no_of_variable): - s = str(m % 2) + s - m //= 2 - temp.append(s) - s = "" + string = str(minterm % 2) + string + minterm //= 2 + temp.append(string) return temp @@ -70,16 +71,13 @@ def is_for_table(string1: str, string2: str, count: int) -> bool: >>> is_for_table('01_','001',1) False """ - l1 = list(string1) - l2 = list(string2) + list1 = list(string1) + list2 = list(string2) count_n = 0 - for i in range(len(l1)): - if l1[i] != l2[i]: + for i in range(len(list1)): + if list1[i] != list2[i]: count_n += 1 - if count_n == count: - return True - else: - return False + return count_n == count def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]: @@ -108,7 +106,7 @@ def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]: for k in range(len(chart)): chart[k][j] = 0 temp.append(prime_implicants[i]) - while 1: + while True: max_n = 0 rem = -1 count_n = 0 From 7423875cef469c57abdb2be2c2b1b39490141d89 Mon Sep 17 00:00:00 2001 From: Michael Currin <18750745+MichaelCurrin@users.noreply.github.com> Date: Wed, 26 Jan 2022 17:35:51 +0200 Subject: [PATCH 1764/2908] ci: add mkdir step for mypy (#5927) * ci: add mkdir step for mypy * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 4 +++- DIRECTORY.md | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index df000cda5997..4f270ea55d17 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,9 @@ jobs: run: | python -m pip install --upgrade pip setuptools six wheel python -m pip install mypy pytest-cov -r requirements.txt - - run: mypy --ignore-missing-imports --install-types --non-interactive . + - run: | + mkdir -p .mypy_cache + mypy --ignore-missing-imports --install-types --non-interactive . - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/DIRECTORY.md b/DIRECTORY.md index 16244e6ad08e..550920c0fc39 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -114,6 +114,7 @@ * [Harris Corner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harris_corner.py) * [Mean Threshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mean_threshold.py) * [Mosaic Augmentation](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mosaic_augmentation.py) + * [Pooling Functions](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/pooling_functions.py) ## Conversions * [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py) From c15a4d5af6ade25c967a226877ba567166707aac Mon Sep 17 00:00:00 2001 From: Michael Currin <18750745+MichaelCurrin@users.noreply.github.com> Date: Fri, 28 Jan 2022 08:52:42 +0200 Subject: [PATCH 1765/2908] Refactor currency_converter.py (#5917) * Update currency_converter.py * refactor: add types and remove reserved keyword "from" usage * feat: update text * Update web_programming/currency_converter.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> * Update web_programming/currency_converter.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> * fix: update currency_converter.py * updating DIRECTORY.md * Update currency_converter.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- web_programming/currency_converter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py index 447595b0b646..6fcc60e8feeb 100644 --- a/web_programming/currency_converter.py +++ b/web_programming/currency_converter.py @@ -10,9 +10,11 @@ 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.") +if not API_KEY and not TESTING: + raise KeyError( + "API key must be provided in the 'AMDOREN_API_KEY' environment variable." + ) # Currency and their description list_of_currencies = """ From 24d3cf82445e3f4a2e5287829395a3bf1353a8a3 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 30 Jan 2022 20:29:54 +0100 Subject: [PATCH 1766/2908] The black formatter is no longer beta (#5960) * The black formatter is no longer beta * pre-commit autoupdate * pre-commit autoupdate * Remove project_euler/problem_145 which is killing our CI tests * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 16 ++-- DIRECTORY.md | 2 - arithmetic_analysis/bisection.py | 4 +- arithmetic_analysis/in_static_equilibrium.py | 2 +- arithmetic_analysis/intersection.py | 2 +- arithmetic_analysis/newton_method.py | 6 +- arithmetic_analysis/newton_raphson.py | 2 +- ciphers/deterministic_miller_rabin.py | 2 +- ciphers/rabin_miller.py | 2 +- ciphers/rsa_cipher.py | 4 +- ciphers/rsa_factorization.py | 2 +- computer_vision/harris_corner.py | 8 +- .../filters/gabor_filter.py | 2 +- digital_image_processing/index_calculation.py | 6 +- electronics/carrier_concentration.py | 4 +- electronics/coulombs_law.py | 6 +- graphics/bezier_curve.py | 2 +- graphs/bidirectional_a_star.py | 2 +- hashes/chaos_machine.py | 4 +- hashes/hamming_code.py | 2 +- hashes/md5.py | 6 +- linear_algebra/src/lib.py | 2 +- machine_learning/k_means_clust.py | 2 +- .../local_weighted_learning.py | 2 +- .../sequential_minimum_optimization.py | 8 +- maths/area.py | 12 +-- maths/area_under_curve.py | 2 +- maths/armstrong_numbers.py | 4 +- maths/basic_maths.py | 4 +- maths/binomial_distribution.py | 2 +- maths/fibonacci.py | 2 +- maths/gaussian.py | 2 +- maths/karatsuba.py | 4 +- maths/monte_carlo.py | 2 +- maths/numerical_integration.py | 2 +- maths/perfect_square.py | 4 +- maths/pi_monte_carlo_estimation.py | 2 +- maths/polynomial_evaluation.py | 2 +- maths/radix2_fft.py | 2 +- maths/segmented_sieve.py | 2 +- maths/sum_of_geometric_progression.py | 2 +- physics/n_body_simulation.py | 6 +- project_euler/problem_006/sol1.py | 4 +- project_euler/problem_007/sol2.py | 2 +- project_euler/problem_009/sol1.py | 4 +- project_euler/problem_010/sol3.py | 2 +- project_euler/problem_016/sol1.py | 4 +- project_euler/problem_016/sol2.py | 2 +- project_euler/problem_023/sol1.py | 2 +- project_euler/problem_027/sol1.py | 2 +- project_euler/problem_028/sol1.py | 2 +- project_euler/problem_029/sol1.py | 2 +- project_euler/problem_048/sol1.py | 2 +- project_euler/problem_050/sol1.py | 2 +- project_euler/problem_051/sol1.py | 2 +- project_euler/problem_056/sol1.py | 2 +- project_euler/problem_062/sol1.py | 2 +- project_euler/problem_063/sol1.py | 2 +- project_euler/problem_064/sol1.py | 2 +- project_euler/problem_069/sol1.py | 2 +- project_euler/problem_077/sol1.py | 2 +- project_euler/problem_086/sol1.py | 2 +- project_euler/problem_092/sol1.py | 2 +- project_euler/problem_097/sol1.py | 2 +- project_euler/problem_101/sol1.py | 18 ++-- project_euler/problem_125/sol1.py | 6 +- project_euler/problem_144/sol1.py | 6 +- project_euler/problem_145/__init__.py | 0 project_euler/problem_145/sol1.py | 87 ------------------- project_euler/problem_173/sol1.py | 4 +- project_euler/problem_180/sol1.py | 2 +- project_euler/problem_188/sol1.py | 2 +- project_euler/problem_203/sol1.py | 4 +- project_euler/problem_205/sol1.py | 2 +- project_euler/problem_207/sol1.py | 2 +- project_euler/problem_234/sol1.py | 8 +- project_euler/problem_301/sol1.py | 4 +- project_euler/problem_551/sol1.py | 6 +- quantum/deutsch_jozsa.py | 2 +- searches/hill_climbing.py | 4 +- searches/simulated_annealing.py | 4 +- 81 files changed, 139 insertions(+), 228 deletions(-) delete mode 100644 project_euler/problem_145/__init__.py delete mode 100644 project_euler/problem_145/sol1.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ebd6dfa0d7e..33069a807cee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.1.0 hooks: - id: check-executables-have-shebangs - id: check-yaml @@ -14,26 +14,26 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: 21.4b0 + rev: 22.1.0 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.8.0 + rev: 5.10.1 hooks: - id: isort args: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v2.29.0 + rev: v2.31.0 hooks: - id: pyupgrade args: - --py39-plus - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.1 + rev: 3.9.2 hooks: - id: flake8 args: @@ -42,7 +42,7 @@ repos: - --max-line-length=88 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910 + rev: v0.931 hooks: - id: mypy args: @@ -51,11 +51,11 @@ repos: - --non-interactive - repo: https://github.com/codespell-project/codespell - rev: v2.0.0 + rev: v2.1.0 hooks: - id: codespell args: - - --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,tim + - --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,sur,tim - --skip="./.*,./strings/dictionary.txt,./strings/words.txt,./project_euler/problem_022/p022_names.txt" exclude: | (?x)^( diff --git a/DIRECTORY.md b/DIRECTORY.md index 550920c0fc39..b5ddb9fcb156 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -856,8 +856,6 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_135/sol1.py) * Problem 144 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_144/sol1.py) - * Problem 145 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_145/sol1.py) * Problem 173 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) * Problem 174 diff --git a/arithmetic_analysis/bisection.py b/arithmetic_analysis/bisection.py index 0ef691678702..1feb4a8cf626 100644 --- a/arithmetic_analysis/bisection.py +++ b/arithmetic_analysis/bisection.py @@ -32,7 +32,7 @@ def bisection(function: Callable[[float], float], a: float, b: float) -> float: raise ValueError("could not find root in given interval.") else: mid: float = start + (end - start) / 2.0 - while abs(start - mid) > 10 ** -7: # until precisely equals to 10^-7 + while abs(start - mid) > 10**-7: # until precisely equals to 10^-7 if function(mid) == 0: return mid elif function(mid) * function(start) < 0: @@ -44,7 +44,7 @@ def bisection(function: Callable[[float], float], a: float, b: float) -> float: def f(x: float) -> float: - return x ** 3 - 2 * x - 5 + return x**3 - 2 * x - 5 if __name__ == "__main__": diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index 6e8d1d043036..6fe84b45475c 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -23,7 +23,7 @@ def polar_force( def in_static_equilibrium( - forces: ndarray, location: ndarray, eps: float = 10 ** -1 + forces: ndarray, location: ndarray, eps: float = 10**-1 ) -> bool: """ Check if a system is in equilibrium. diff --git a/arithmetic_analysis/intersection.py b/arithmetic_analysis/intersection.py index 204dd5d8a935..9d4651144668 100644 --- a/arithmetic_analysis/intersection.py +++ b/arithmetic_analysis/intersection.py @@ -35,7 +35,7 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl 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: + if abs(x_n2 - x_n1) < 10**-5: return x_n2 x_n = x_n1 x_n1 = x_n2 diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index a9a94372671e..f0cf4eaa6e83 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -37,17 +37,17 @@ def newton( next_guess = prev_guess - function(prev_guess) / derivative(prev_guess) except ZeroDivisionError: raise ZeroDivisionError("Could not find root") from None - if abs(prev_guess - next_guess) < 10 ** -5: + if abs(prev_guess - next_guess) < 10**-5: return next_guess prev_guess = next_guess def f(x: float) -> float: - return (x ** 3) - (2 * x) - 5 + return (x**3) - (2 * x) - 5 def f1(x: float) -> float: - return 3 * (x ** 2) - 2 + return 3 * (x**2) - 2 if __name__ == "__main__": diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index 1a820538630f..86ff9d350dde 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -11,7 +11,7 @@ def newton_raphson( - func: str, a: float | Decimal, precision: float = 10 ** -10 + func: str, a: float | Decimal, precision: float = 10**-10 ) -> float: """Finds root from the point 'a' onwards by Newton-Raphson method >>> newton_raphson("sin(x)", 2) diff --git a/ciphers/deterministic_miller_rabin.py b/ciphers/deterministic_miller_rabin.py index d7fcb67e936c..2191caf630a7 100644 --- a/ciphers/deterministic_miller_rabin.py +++ b/ciphers/deterministic_miller_rabin.py @@ -73,7 +73,7 @@ def miller_rabin(n: int, allow_probable: bool = False) -> bool: for prime in plist: pr = False for r in range(s): - m = pow(prime, d * 2 ** r, n) + 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 diff --git a/ciphers/rabin_miller.py b/ciphers/rabin_miller.py index 65c162984ece..c42ad2f5928d 100644 --- a/ciphers/rabin_miller.py +++ b/ciphers/rabin_miller.py @@ -21,7 +21,7 @@ def rabinMiller(num: int) -> bool: return False else: i = i + 1 - v = (v ** 2) % num + v = (v**2) % num return True diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index b1e8a73f33c6..5bb9f9916de5 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -29,8 +29,8 @@ def get_text_from_blocks( block_message: list[str] = [] for i in range(block_size - 1, -1, -1): if len(message) + i < message_length: - ascii_number = block_int // (BYTE_SIZE ** i) - block_int = block_int % (BYTE_SIZE ** i) + ascii_number = block_int // (BYTE_SIZE**i) + block_int = block_int % (BYTE_SIZE**i) block_message.insert(0, chr(ascii_number)) message.extend(block_message) return "".join(message) diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py index 6df32b6cc887..de4df27770c7 100644 --- a/ciphers/rsa_factorization.py +++ b/ciphers/rsa_factorization.py @@ -40,7 +40,7 @@ def rsafactor(d: int, e: int, N: int) -> list[int]: while True: if t % 2 == 0: t = t // 2 - x = (g ** t) % N + x = (g**t) % N y = math.gcd(x - 1, N) if x > 1 and y > 1: p = y diff --git a/computer_vision/harris_corner.py b/computer_vision/harris_corner.py index 02deb54084ef..886ff52ea70b 100644 --- a/computer_vision/harris_corner.py +++ b/computer_vision/harris_corner.py @@ -39,8 +39,8 @@ def detect(self, img_path: str) -> tuple[cv2.Mat, list[list[int]]]: color_img = img.copy() color_img = cv2.cvtColor(color_img, cv2.COLOR_GRAY2RGB) dy, dx = np.gradient(img) - ixx = dx ** 2 - iyy = dy ** 2 + ixx = dx**2 + iyy = dy**2 ixy = dx * dy k = 0.04 offset = self.window_size // 2 @@ -56,9 +56,9 @@ def detect(self, img_path: str) -> tuple[cv2.Mat, list[list[int]]]: y - offset : y + offset + 1, x - offset : x + offset + 1 ].sum() - det = (wxx * wyy) - (wxy ** 2) + det = (wxx * wyy) - (wxy**2) trace = wxx + wyy - r = det - k * (trace ** 2) + r = det - k * (trace**2) # Can change the value if r > 0.5: corner_list.append([x, y, r]) diff --git a/digital_image_processing/filters/gabor_filter.py b/digital_image_processing/filters/gabor_filter.py index 90aa049c24a0..8f9212a35a79 100644 --- a/digital_image_processing/filters/gabor_filter.py +++ b/digital_image_processing/filters/gabor_filter.py @@ -49,7 +49,7 @@ def gabor_filter_kernel( # fill kernel gabor[y, x] = np.exp( - -(_x ** 2 + gamma ** 2 * _y ** 2) / (2 * sigma ** 2) + -(_x**2 + gamma**2 * _y**2) / (2 * sigma**2) ) * np.cos(2 * np.pi * _x / lambd + psi) return gabor diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index 4350b8603390..033334af8a2a 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -203,7 +203,7 @@ def CVI(self): https://www.indexdatabase.de/db/i-single.php?id=391 :return: index """ - return self.nir * (self.red / (self.green ** 2)) + return self.nir * (self.red / (self.green**2)) def GLI(self): """ @@ -295,7 +295,7 @@ def ATSAVI(self, X=0.08, a=1.22, b=0.03): """ return a * ( (self.nir - a * self.red - b) - / (a * self.nir + self.red - a * b + X * (1 + a ** 2)) + / (a * self.nir + self.red - a * b + X * (1 + a**2)) ) def BWDRVI(self): @@ -363,7 +363,7 @@ def GEMI(self): 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) / ( + 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) diff --git a/electronics/carrier_concentration.py b/electronics/carrier_concentration.py index 87bcad8df398..03482f1e336e 100644 --- a/electronics/carrier_concentration.py +++ b/electronics/carrier_concentration.py @@ -53,12 +53,12 @@ def carrier_concentration( elif electron_conc == 0: return ( "electron_conc", - intrinsic_conc ** 2 / hole_conc, + intrinsic_conc**2 / hole_conc, ) elif hole_conc == 0: return ( "hole_conc", - intrinsic_conc ** 2 / electron_conc, + intrinsic_conc**2 / electron_conc, ) elif intrinsic_conc == 0: return ( diff --git a/electronics/coulombs_law.py b/electronics/coulombs_law.py index e4c8391c9f9a..e41c0410cc9e 100644 --- a/electronics/coulombs_law.py +++ b/electronics/coulombs_law.py @@ -66,13 +66,13 @@ def couloumbs_law( if distance < 0: raise ValueError("Distance cannot be negative") if force == 0: - force = COULOMBS_CONSTANT * charge_product / (distance ** 2) + force = COULOMBS_CONSTANT * charge_product / (distance**2) return {"force": force} elif charge1 == 0: - charge1 = abs(force) * (distance ** 2) / (COULOMBS_CONSTANT * charge2) + charge1 = abs(force) * (distance**2) / (COULOMBS_CONSTANT * charge2) return {"charge1": charge1} elif charge2 == 0: - charge2 = abs(force) * (distance ** 2) / (COULOMBS_CONSTANT * charge1) + charge2 = abs(force) * (distance**2) / (COULOMBS_CONSTANT * charge1) return {"charge2": charge2} elif distance == 0: distance = (COULOMBS_CONSTANT * charge_product / abs(force)) ** 0.5 diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 2bb764fdc916..7c22329ad8b4 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -40,7 +40,7 @@ def basis_function(self, t: float) -> 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) + 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 diff --git a/graphs/bidirectional_a_star.py b/graphs/bidirectional_a_star.py index 071f1cd685b1..373d67142aa9 100644 --- a/graphs/bidirectional_a_star.py +++ b/graphs/bidirectional_a_star.py @@ -68,7 +68,7 @@ def calculate_heuristic(self) -> float: if HEURISTIC == 1: return abs(dx) + abs(dy) else: - return sqrt(dy ** 2 + dx ** 2) + return sqrt(dy**2 + dx**2) def __lt__(self, other: Node) -> bool: return self.f_cost < other.f_cost diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 7ef4fdb3ca51..7ad3e5540479 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -63,8 +63,8 @@ def xorshift(X, Y): 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)) + X = int(buffer_space[(key + 2) % m] * (10**10)) + Y = int(buffer_space[(key - 2) % m] * (10**10)) # Machine Time machine_time += 1 diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 4a32bae1a51c..ac20fe03b3fb 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -78,7 +78,7 @@ def emitterConverter(sizePar, data): >>> 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): + if sizePar + len(data) <= 2**sizePar - (len(data) - 1): print("ERROR - size of parity don't match with size of data") exit(0) diff --git a/hashes/md5.py b/hashes/md5.py index b08ab957340a..c56c073cc0c7 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -94,7 +94,7 @@ def not32(i): def sum32(a, b): - return (a + b) % 2 ** 32 + return (a + b) % 2**32 def leftrot32(i, s): @@ -114,7 +114,7 @@ def md5me(testString): 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 @@ -211,7 +211,7 @@ def md5me(testString): 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) diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 85dc4b71c4a4..2bfcea7f8c84 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -172,7 +172,7 @@ def euclidean_length(self) -> float: """ if len(self.__components) == 0: raise Exception("Vector is empty") - squares = [c ** 2 for c in self.__components] + squares = [c**2 for c in self.__components] return math.sqrt(sum(squares)) def angle(self, other: Vector, deg: bool = False) -> float: diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index c45be8a4c064..10c9374d8492 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -112,7 +112,7 @@ def compute_heterogeneity(data, k, centroids, cluster_assignment): distances = pairwise_distances( member_data_points, [centroids[i]], metric="euclidean" ) - squared_distances = distances ** 2 + squared_distances = distances**2 heterogeneity += np.sum(squared_distances) return heterogeneity diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index af8694bf8f82..db6868687661 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -25,7 +25,7 @@ def weighted_matrix(point: np.mat, training_data_x: np.mat, bandwidth: float) -> # calculating weights for all training examples [x(i)'s] for j in range(m): diff = point - training_data_x[j] - weights[j, j] = np.exp(diff * diff.T / (-2.0 * bandwidth ** 2)) + weights[j, j] = np.exp(diff * diff.T / (-2.0 * bandwidth**2)) return weights diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 98ce05c46cff..c217a370a975 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -345,15 +345,15 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): ol = ( l1 * f1 + L * f2 - + 1 / 2 * l1 ** 2 * K(i1, i1) - + 1 / 2 * L ** 2 * K(i2, i2) + + 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) + + 1 / 2 * h1**2 * K(i1, i1) + + 1 / 2 * H**2 * K(i2, i2) + s * H * h1 * K(i1, i2) ) """ diff --git a/maths/area.py b/maths/area.py index 7b39312cfaf0..b1b139cf4e22 100644 --- a/maths/area.py +++ b/maths/area.py @@ -19,7 +19,7 @@ def surface_area_cube(side_length: float) -> float: """ if side_length < 0: raise ValueError("surface_area_cube() only accepts non-negative values") - return 6 * side_length ** 2 + return 6 * side_length**2 def surface_area_sphere(radius: float) -> float: @@ -39,7 +39,7 @@ def surface_area_sphere(radius: float) -> float: """ if radius < 0: raise ValueError("surface_area_sphere() only accepts non-negative values") - return 4 * pi * radius ** 2 + return 4 * pi * radius**2 def surface_area_hemisphere(radius: float) -> float: @@ -62,7 +62,7 @@ def surface_area_hemisphere(radius: float) -> float: """ if radius < 0: raise ValueError("surface_area_hemisphere() only accepts non-negative values") - return 3 * pi * radius ** 2 + return 3 * pi * radius**2 def surface_area_cone(radius: float, height: float) -> float: @@ -90,7 +90,7 @@ def surface_area_cone(radius: float, height: float) -> float: """ if radius < 0 or height < 0: raise ValueError("surface_area_cone() only accepts non-negative values") - return pi * radius * (radius + (height ** 2 + radius ** 2) ** 0.5) + return pi * radius * (radius + (height**2 + radius**2) ** 0.5) def surface_area_cylinder(radius: float, height: float) -> float: @@ -158,7 +158,7 @@ def area_square(side_length: float) -> float: """ if side_length < 0: raise ValueError("area_square() only accepts non-negative values") - return side_length ** 2 + return side_length**2 def area_triangle(base: float, height: float) -> float: @@ -307,7 +307,7 @@ def area_circle(radius: float) -> float: """ if radius < 0: raise ValueError("area_circle() only accepts non-negative values") - return pi * radius ** 2 + return pi * radius**2 def area_ellipse(radius_x: float, radius_y: float) -> float: diff --git a/maths/area_under_curve.py b/maths/area_under_curve.py index ce0932426ef6..6fb3a7c98396 100644 --- a/maths/area_under_curve.py +++ b/maths/area_under_curve.py @@ -50,7 +50,7 @@ def trapezoidal_area( 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:") diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index 4e62737e1333..65aebe93722e 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -36,7 +36,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 @@ -63,7 +63,7 @@ def pluperfect_number(n: int) -> bool: digit_total += 1 for (cnt, i) in zip(digit_histogram, range(len(digit_histogram))): - sum += cnt * i ** digit_total + sum += cnt * i**digit_total return n == sum diff --git a/maths/basic_maths.py b/maths/basic_maths.py index 47d3d91b397d..58e797772a28 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -81,14 +81,14 @@ def sum_of_divisors(n: int) -> int: 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 while n % i == 0: temp += 1 n = int(n / i) if temp > 1: - s *= (i ** temp - 1) / (i - 1) + s *= (i**temp - 1) / (i - 1) return int(s) diff --git a/maths/binomial_distribution.py b/maths/binomial_distribution.py index a74a5a7ed994..5b56f2d59244 100644 --- a/maths/binomial_distribution.py +++ b/maths/binomial_distribution.py @@ -24,7 +24,7 @@ def binomial_distribution(successes: int, trials: int, prob: float) -> float: 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)) + 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) diff --git a/maths/fibonacci.py b/maths/fibonacci.py index ca4f4a2360a3..07bd6d2ece51 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -159,7 +159,7 @@ def fib_binet(n: int) -> list[int]: raise Exception("n is too large") sqrt_5 = sqrt(5) phi = (1 + sqrt_5) / 2 - return [round(phi ** i / sqrt_5) for i in range(n + 1)] + return [round(phi**i / sqrt_5) for i in range(n + 1)] if __name__ == "__main__": diff --git a/maths/gaussian.py b/maths/gaussian.py index a5dba50a927d..51ebc2e25849 100644 --- a/maths/gaussian.py +++ b/maths/gaussian.py @@ -52,7 +52,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/karatsuba.py b/maths/karatsuba.py index df29c77a5cf2..b772c0d77039 100644 --- a/maths/karatsuba.py +++ b/maths/karatsuba.py @@ -14,8 +14,8 @@ def karatsuba(a, b): 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)) diff --git a/maths/monte_carlo.py b/maths/monte_carlo.py index 28027cbe4178..efb6a01d57fd 100644 --- a/maths/monte_carlo.py +++ b/maths/monte_carlo.py @@ -20,7 +20,7 @@ def pi_estimator(iterations: int): """ # A local function to see if a dot lands in the circle. def is_in_circle(x: float, y: float) -> bool: - distance_from_centre = sqrt((x ** 2) + (y ** 2)) + 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 diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index 577c41a4440e..cf2efce12baf 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -56,7 +56,7 @@ def trapezoidal_area( 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:") diff --git a/maths/perfect_square.py b/maths/perfect_square.py index 4393dcfbc774..107e68528068 100644 --- a/maths/perfect_square.py +++ b/maths/perfect_square.py @@ -58,9 +58,9 @@ def perfect_square_binary_search(n: int) -> bool: right = n while left <= right: mid = (left + right) // 2 - if mid ** 2 == n: + if mid**2 == n: return True - elif mid ** 2 > n: + elif mid**2 > n: right = mid - 1 else: left = mid + 1 diff --git a/maths/pi_monte_carlo_estimation.py b/maths/pi_monte_carlo_estimation.py index 20b46dddc6e5..81be083787bd 100644 --- a/maths/pi_monte_carlo_estimation.py +++ b/maths/pi_monte_carlo_estimation.py @@ -11,7 +11,7 @@ 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 + return (self.x**2 + self.y**2) <= 1 @classmethod def random_unit_square(cls): diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index 68ff97ddd25d..4e4016e5133d 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -12,7 +12,7 @@ def evaluate_poly(poly: Sequence[float], x: float) -> float: >>> 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)) + return sum(c * (x**i) for i, c in enumerate(poly)) def horner(poly: Sequence[float], x: float) -> float: diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index 9fc9f843e685..0a431a115fb8 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -91,7 +91,7 @@ def __DFT(self, which): next_ncol = self.C_max_length // 2 while next_ncol > 0: new_dft = [[] for i in range(next_ncol)] - root = self.root ** next_ncol + root = self.root**next_ncol # First half of next step current_root = 1 diff --git a/maths/segmented_sieve.py b/maths/segmented_sieve.py index c1cc497ad33e..b15ec2480678 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/sum_of_geometric_progression.py b/maths/sum_of_geometric_progression.py index f29dd8005cff..9079f35af6d9 100644 --- a/maths/sum_of_geometric_progression.py +++ b/maths/sum_of_geometric_progression.py @@ -25,4 +25,4 @@ def sum_of_geometric_progression( 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) + return (first_term / (1 - common_ratio)) * (1 - common_ratio**num_of_terms) diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index 045a49f7ff00..01083b9a272e 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -159,16 +159,16 @@ def update_system(self, delta_time: float) -> None: # Calculation of the distance using Pythagoras's theorem # Extra factor due to the softening technique - distance = (dif_x ** 2 + dif_y ** 2 + self.softening_factor) ** ( + distance = (dif_x**2 + dif_y**2 + self.softening_factor) ** ( 1 / 2 ) # Newton's law of universal gravitation. force_x += ( - self.gravitation_constant * body2.mass * dif_x / distance ** 3 + self.gravitation_constant * body2.mass * dif_x / distance**3 ) force_y += ( - self.gravitation_constant * body2.mass * dif_y / distance ** 3 + self.gravitation_constant * body2.mass * dif_y / distance**3 ) # Update the body's velocity once all the force components have been added diff --git a/project_euler/problem_006/sol1.py b/project_euler/problem_006/sol1.py index 61dd7a321011..615991bb172c 100644 --- a/project_euler/problem_006/sol1.py +++ b/project_euler/problem_006/sol1.py @@ -35,9 +35,9 @@ def solution(n: int = 100) -> int: sum_of_squares = 0 sum_of_ints = 0 for i in range(1, n + 1): - sum_of_squares += i ** 2 + sum_of_squares += i**2 sum_of_ints += i - return sum_of_ints ** 2 - sum_of_squares + return sum_of_ints**2 - sum_of_squares if __name__ == "__main__": diff --git a/project_euler/problem_007/sol2.py b/project_euler/problem_007/sol2.py index 20c2ddf21ab8..dfcad8c148af 100644 --- a/project_euler/problem_007/sol2.py +++ b/project_euler/problem_007/sol2.py @@ -25,7 +25,7 @@ def isprime(number: int) -> bool: True """ - for i in range(2, int(number ** 0.5) + 1): + for i in range(2, int(number**0.5) + 1): if number % i == 0: return False return True diff --git a/project_euler/problem_009/sol1.py b/project_euler/problem_009/sol1.py index 83c88acf1f8b..1d908402b6b1 100644 --- a/project_euler/problem_009/sol1.py +++ b/project_euler/problem_009/sol1.py @@ -33,7 +33,7 @@ def solution() -> int: for b in range(a + 1, 400): for c in range(b + 1, 500): if (a + b + c) == 1000: - if (a ** 2) + (b ** 2) == (c ** 2): + if (a**2) + (b**2) == (c**2): return a * b * c return -1 @@ -54,7 +54,7 @@ def solution_fast() -> int: 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): + if a < b < c and (a**2) + (b**2) == (c**2): return a * b * c return -1 diff --git a/project_euler/problem_010/sol3.py b/project_euler/problem_010/sol3.py index f49d9393c7af..72e2894df293 100644 --- a/project_euler/problem_010/sol3.py +++ b/project_euler/problem_010/sol3.py @@ -46,7 +46,7 @@ def solution(n: int = 2000000) -> int: primality_list[0] = 1 primality_list[1] = 1 - for i in range(2, int(n ** 0.5) + 1): + for i in range(2, int(n**0.5) + 1): if primality_list[i] == 0: for j in range(i * i, n + 1, i): primality_list[j] = 1 diff --git a/project_euler/problem_016/sol1.py b/project_euler/problem_016/sol1.py index f6620aa9482f..93584d1d436f 100644 --- a/project_euler/problem_016/sol1.py +++ b/project_euler/problem_016/sol1.py @@ -18,7 +18,7 @@ def solution(power: int = 1000) -> int: >>> solution(15) 26 """ - num = 2 ** power + num = 2**power string_num = str(num) list_num = list(string_num) sum_of_num = 0 @@ -31,6 +31,6 @@ def solution(power: int = 1000) -> int: if __name__ == "__main__": power = int(input("Enter the power of 2: ").strip()) - print("2 ^ ", power, " = ", 2 ** power) + print("2 ^ ", power, " = ", 2**power) result = solution(power) print("Sum of the digits is: ", result) diff --git a/project_euler/problem_016/sol2.py b/project_euler/problem_016/sol2.py index 304d27d1e5d0..1408212e74c5 100644 --- a/project_euler/problem_016/sol2.py +++ b/project_euler/problem_016/sol2.py @@ -19,7 +19,7 @@ def solution(power: int = 1000) -> int: >>> solution(15) 26 """ - n = 2 ** power + n = 2**power r = 0 while n: r, n = r + n % 10, n // 10 diff --git a/project_euler/problem_023/sol1.py b/project_euler/problem_023/sol1.py index a72b6123e3ee..83b85f3f721c 100644 --- a/project_euler/problem_023/sol1.py +++ b/project_euler/problem_023/sol1.py @@ -30,7 +30,7 @@ def solution(limit=28123): """ sumDivs = [1] * (limit + 1) - for i in range(2, int(limit ** 0.5) + 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 diff --git a/project_euler/problem_027/sol1.py b/project_euler/problem_027/sol1.py index 6f28b925be08..928c0ec4feb7 100644 --- a/project_euler/problem_027/sol1.py +++ b/project_euler/problem_027/sol1.py @@ -61,7 +61,7 @@ def solution(a_limit: int = 1000, b_limit: int = 1000) -> int: if is_prime(b): count = 0 n = 0 - while is_prime((n ** 2) + (a * n) + b): + while is_prime((n**2) + (a * n) + b): count += 1 n += 1 if count > longest[0]: diff --git a/project_euler/problem_028/sol1.py b/project_euler/problem_028/sol1.py index cbc7de6bea9a..1ea5d4fcafd4 100644 --- a/project_euler/problem_028/sol1.py +++ b/project_euler/problem_028/sol1.py @@ -40,7 +40,7 @@ def solution(n: int = 1001) -> int: for i in range(1, int(ceil(n / 2.0))): odd = 2 * i + 1 even = 2 * i - total = total + 4 * odd ** 2 - 6 * even + total = total + 4 * odd**2 - 6 * even return total diff --git a/project_euler/problem_029/sol1.py b/project_euler/problem_029/sol1.py index 726bcaf6ebd8..d3ab90ac7d25 100644 --- a/project_euler/problem_029/sol1.py +++ b/project_euler/problem_029/sol1.py @@ -41,7 +41,7 @@ def solution(n: int = 100) -> int: for a in range(2, N): for b in range(2, N): - currentPow = a ** b # calculates the current power + currentPow = a**b # calculates the current power collectPowers.add(currentPow) # adds the result to the set return len(collectPowers) diff --git a/project_euler/problem_048/sol1.py b/project_euler/problem_048/sol1.py index 01ff702d9cd5..5a4538cf5d4e 100644 --- a/project_euler/problem_048/sol1.py +++ b/project_euler/problem_048/sol1.py @@ -17,7 +17,7 @@ def solution(): """ total = 0 for i in range(1, 1001): - total += i ** i + total += i**i return str(total)[-10:] diff --git a/project_euler/problem_050/sol1.py b/project_euler/problem_050/sol1.py index cfb1911df5de..fc6e6f2b9a5d 100644 --- a/project_euler/problem_050/sol1.py +++ b/project_euler/problem_050/sol1.py @@ -35,7 +35,7 @@ def prime_sieve(limit: int) -> list[int]: is_prime[1] = False is_prime[2] = True - for i in range(3, int(limit ** 0.5 + 1), 2): + for i in range(3, int(limit**0.5 + 1), 2): index = i * 2 while index < limit: is_prime[index] = False diff --git a/project_euler/problem_051/sol1.py b/project_euler/problem_051/sol1.py index eedb02379e62..921704bc4455 100644 --- a/project_euler/problem_051/sol1.py +++ b/project_euler/problem_051/sol1.py @@ -37,7 +37,7 @@ def prime_sieve(n: int) -> list[int]: is_prime[1] = False is_prime[2] = True - for i in range(3, int(n ** 0.5 + 1), 2): + for i in range(3, int(n**0.5 + 1), 2): index = i * 2 while index < n: is_prime[index] = False diff --git a/project_euler/problem_056/sol1.py b/project_euler/problem_056/sol1.py index f1ec03c497be..c772bec58692 100644 --- a/project_euler/problem_056/sol1.py +++ b/project_euler/problem_056/sol1.py @@ -30,7 +30,7 @@ def solution(a: int = 100, b: int = 100) -> 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)) + sum(int(x) for x in str(base**power)) for base in range(a) for power in range(b) ) diff --git a/project_euler/problem_062/sol1.py b/project_euler/problem_062/sol1.py index 83286c801301..0c9baf880497 100644 --- a/project_euler/problem_062/sol1.py +++ b/project_euler/problem_062/sol1.py @@ -55,7 +55,7 @@ def get_digits(num: int) -> str: >>> get_digits(123) '0166788' """ - return "".join(sorted(list(str(num ** 3)))) + return "".join(sorted(list(str(num**3)))) if __name__ == "__main__": diff --git a/project_euler/problem_063/sol1.py b/project_euler/problem_063/sol1.py index 29efddba4216..bea30a2e5670 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_064/sol1.py b/project_euler/problem_064/sol1.py index 69e3f6d97580..5df64a90ae55 100644 --- a/project_euler/problem_064/sol1.py +++ b/project_euler/problem_064/sol1.py @@ -38,7 +38,7 @@ def continuous_fraction_period(n: int) -> int: period = 0 while integer_part != 2 * ROOT: numerator = denominator * integer_part - numerator - denominator = (n - numerator ** 2) / denominator + denominator = (n - numerator**2) / denominator integer_part = int((ROOT + numerator) / denominator) period += 1 return period diff --git a/project_euler/problem_069/sol1.py b/project_euler/problem_069/sol1.py index d148dd79a777..5dfd61a89e94 100644 --- a/project_euler/problem_069/sol1.py +++ b/project_euler/problem_069/sol1.py @@ -24,7 +24,7 @@ """ -def solution(n: int = 10 ** 6) -> int: +def solution(n: int = 10**6) -> int: """ Returns solution to problem. Algorithm: diff --git a/project_euler/problem_077/sol1.py b/project_euler/problem_077/sol1.py index 214e258793f6..6098ea9e50a6 100644 --- a/project_euler/problem_077/sol1.py +++ b/project_euler/problem_077/sol1.py @@ -23,7 +23,7 @@ primes.add(2) prime: int -for prime in range(3, ceil(NUM_PRIMES ** 0.5), 2): +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))) diff --git a/project_euler/problem_086/sol1.py b/project_euler/problem_086/sol1.py index 0bf66e6b5a31..064af215c049 100644 --- a/project_euler/problem_086/sol1.py +++ b/project_euler/problem_086/sol1.py @@ -91,7 +91,7 @@ def solution(limit: int = 1000000) -> 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(): + 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) diff --git a/project_euler/problem_092/sol1.py b/project_euler/problem_092/sol1.py index 437a85badc57..d326fc33fcca 100644 --- a/project_euler/problem_092/sol1.py +++ b/project_euler/problem_092/sol1.py @@ -12,7 +12,7 @@ """ -DIGITS_SQUARED = [digit ** 2 for digit in range(10)] +DIGITS_SQUARED = [digit**2 for digit in range(10)] def next_number(number: int) -> int: diff --git a/project_euler/problem_097/sol1.py b/project_euler/problem_097/sol1.py index 2e848c09a940..da5e8120b7c5 100644 --- a/project_euler/problem_097/sol1.py +++ b/project_euler/problem_097/sol1.py @@ -34,7 +34,7 @@ def solution(n: int = 10) -> str: """ if not isinstance(n, int) or n < 0: raise ValueError("Invalid input") - MODULUS = 10 ** n + MODULUS = 10**n NUMBER = 28433 * (pow(2, 7830457, MODULUS)) + 1 return str(NUMBER % MODULUS) diff --git a/project_euler/problem_101/sol1.py b/project_euler/problem_101/sol1.py index 14013c435241..04678847508c 100644 --- a/project_euler/problem_101/sol1.py +++ b/project_euler/problem_101/sol1.py @@ -175,15 +175,15 @@ def question_function(variable: int) -> int: return ( 1 - variable - + variable ** 2 - - variable ** 3 - + variable ** 4 - - variable ** 5 - + variable ** 6 - - variable ** 7 - + variable ** 8 - - variable ** 9 - + variable ** 10 + + variable**2 + - variable**3 + + variable**4 + - variable**5 + + variable**6 + - variable**7 + + variable**8 + - variable**9 + + variable**10 ) diff --git a/project_euler/problem_125/sol1.py b/project_euler/problem_125/sol1.py index afc1f2890cef..7a8f908ed6a9 100644 --- a/project_euler/problem_125/sol1.py +++ b/project_euler/problem_125/sol1.py @@ -35,7 +35,7 @@ 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 + LIMIT = 10**8 answer = set() first_square = 1 sum_squares = 5 @@ -45,9 +45,9 @@ def solution() -> int: if is_palindrome(sum_squares): answer.add(sum_squares) last_square += 1 - sum_squares += last_square ** 2 + sum_squares += last_square**2 first_square += 1 - sum_squares = first_square ** 2 + (first_square + 1) ** 2 + sum_squares = first_square**2 + (first_square + 1) ** 2 return sum(answer) diff --git a/project_euler/problem_144/sol1.py b/project_euler/problem_144/sol1.py index 3f7a766be20f..b5f103b64ff5 100644 --- a/project_euler/problem_144/sol1.py +++ b/project_euler/problem_144/sol1.py @@ -58,15 +58,15 @@ def next_point( # y^2 + 4x^2 = 100 # y - b = m * (x - a) # ==> A x^2 + B x + C = 0 - quadratic_term = outgoing_gradient ** 2 + 4 + quadratic_term = outgoing_gradient**2 + 4 linear_term = 2 * outgoing_gradient * (point_y - outgoing_gradient * point_x) constant_term = (point_y - outgoing_gradient * point_x) ** 2 - 100 x_minus = ( - -linear_term - sqrt(linear_term ** 2 - 4 * quadratic_term * constant_term) + -linear_term - sqrt(linear_term**2 - 4 * quadratic_term * constant_term) ) / (2 * quadratic_term) x_plus = ( - -linear_term + sqrt(linear_term ** 2 - 4 * quadratic_term * constant_term) + -linear_term + sqrt(linear_term**2 - 4 * quadratic_term * constant_term) ) / (2 * quadratic_term) # two solutions, one of which is our input point diff --git a/project_euler/problem_145/__init__.py b/project_euler/problem_145/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py deleted file mode 100644 index 5ba3af86a6a1..000000000000 --- a/project_euler/problem_145/sol1.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Problem 145: https://projecteuler.net/problem=145 - -Name: How many reversible numbers are there below one-billion? - -Some positive integers n have the property that the -sum [ n + reverse(n) ] consists entirely of odd (decimal) digits. -For instance, 36 + 63 = 99 and 409 + 904 = 1313. -We will call such numbers reversible; so 36, 63, 409, and 904 are reversible. -Leading zeroes are not allowed in either n or reverse(n). - -There are 120 reversible numbers below one-thousand. - -How many reversible numbers are there below one-billion (10^9)? - - -Solution: - -Here a brute force solution is used to find and count the reversible numbers. - -""" -from __future__ import annotations - - -def check_if_odd(sum: int = 36) -> int: - """ - Check if the last digit in the sum is even or odd. If even return 0. - If odd then floor division by 10 is used to remove the last number. - Process continues until sum becomes 0 because no more numbers. - >>> check_if_odd(36) - 0 - >>> check_if_odd(33) - 1 - """ - while sum > 0: - if (sum % 10) % 2 == 0: - return 0 - sum = sum // 10 - return 1 - - -def find_reverse_number(number: int = 36) -> int: - """ - Reverses the given number. Does not work with number that end in zero. - >>> find_reverse_number(36) - 63 - >>> find_reverse_number(409) - 904 - """ - reverse = 0 - - while number > 0: - temp = number % 10 - reverse = reverse * 10 + temp - number = number // 10 - - return reverse - - -def solution(number: int = 1000000000) -> int: - """ - Loops over the range of numbers. - Checks if they have ending zeros which disqualifies them from being reversible. - If that condition is passed it generates the reversed number. - Then sum up n and reverse(n). - Then check if all the numbers in the sum are odd. If true add to the answer. - >>> solution(1000000000) - 608720 - >>> solution(1000000) - 18720 - >>> solution(1000000) - 18720 - >>> solution(1000) - 120 - """ - answer = 0 - for x in range(1, number): - if x % 10 != 0: - reversed_number = find_reverse_number(x) - sum = x + reversed_number - answer += check_if_odd(sum) - - return answer - - -if __name__ == "__main__": - print(f"{solution() = }") diff --git a/project_euler/problem_173/sol1.py b/project_euler/problem_173/sol1.py index d539b1437ef1..5416e25462cc 100644 --- a/project_euler/problem_173/sol1.py +++ b/project_euler/problem_173/sol1.py @@ -25,8 +25,8 @@ def solution(limit: int = 1000000) -> int: 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) + 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: diff --git a/project_euler/problem_180/sol1.py b/project_euler/problem_180/sol1.py index f7c097323c62..12e34dcaa76b 100644 --- a/project_euler/problem_180/sol1.py +++ b/project_euler/problem_180/sol1.py @@ -61,7 +61,7 @@ def is_sq(number: int) -> bool: >>> is_sq(1000000) True """ - sq: int = int(number ** 0.5) + sq: int = int(number**0.5) return number == sq * sq diff --git a/project_euler/problem_188/sol1.py b/project_euler/problem_188/sol1.py index c8cd9eb10aeb..dd4360adb32b 100644 --- a/project_euler/problem_188/sol1.py +++ b/project_euler/problem_188/sol1.py @@ -59,7 +59,7 @@ def solution(base: int = 1777, height: int = 1855, digits: int = 8) -> int: # exponentiation result = base for i in range(1, height): - result = _modexpt(base, result, 10 ** digits) + result = _modexpt(base, result, 10**digits) return result diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py index fe4d14b20c92..2ba3c96c9e00 100644 --- a/project_euler/problem_203/sol1.py +++ b/project_euler/problem_203/sol1.py @@ -82,10 +82,10 @@ def get_primes_squared(max_number: int) -> list[int]: if non_primes[num]: continue - for num_counter in range(num ** 2, max_prime + 1, num): + for num_counter in range(num**2, max_prime + 1, num): non_primes[num_counter] = True - primes.append(num ** 2) + primes.append(num**2) return primes diff --git a/project_euler/problem_205/sol1.py b/project_euler/problem_205/sol1.py index 7249df48829b..63b997b9f5a9 100644 --- a/project_euler/problem_205/sol1.py +++ b/project_euler/problem_205/sol1.py @@ -63,7 +63,7 @@ def solution() -> float: colin_totals_frequencies[min_colin_total:peter_total] ) - total_games_number = (4 ** 9) * (6 ** 6) + total_games_number = (4**9) * (6**6) peter_win_probability = peter_wins_count / total_games_number rounded_peter_win_probability = round(peter_win_probability, ndigits=7) diff --git a/project_euler/problem_207/sol1.py b/project_euler/problem_207/sol1.py index 99d1a91746d2..2b3591f51cfa 100644 --- a/project_euler/problem_207/sol1.py +++ b/project_euler/problem_207/sol1.py @@ -81,7 +81,7 @@ def solution(max_proportion: float = 1 / 12345) -> int: integer = 3 while True: - partition_candidate = (integer ** 2 - 1) / 4 + 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) diff --git a/project_euler/problem_234/sol1.py b/project_euler/problem_234/sol1.py index 7516b164db2d..7b20a2206ed2 100644 --- a/project_euler/problem_234/sol1.py +++ b/project_euler/problem_234/sol1.py @@ -35,7 +35,7 @@ def prime_sieve(n: int) -> list: is_prime[1] = False is_prime[2] = True - for i in range(3, int(n ** 0.5 + 1), 2): + for i in range(3, int(n**0.5 + 1), 2): index = i * 2 while index < n: is_prime[index] = False @@ -69,11 +69,11 @@ def solution(limit: int = 999_966_663_333) -> int: prime_index = 0 last_prime = primes[prime_index] - while (last_prime ** 2) <= limit: + while (last_prime**2) <= limit: next_prime = primes[prime_index + 1] - lower_bound = last_prime ** 2 - upper_bound = next_prime ** 2 + lower_bound = last_prime**2 + upper_bound = next_prime**2 # Get numbers divisible by lps(current) current = lower_bound + last_prime diff --git a/project_euler/problem_301/sol1.py b/project_euler/problem_301/sol1.py index b1d434c189b7..4b494033c92d 100644 --- a/project_euler/problem_301/sol1.py +++ b/project_euler/problem_301/sol1.py @@ -48,8 +48,8 @@ def solution(exponent: int = 30) -> int: # 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 + phi = (1 + 5**0.5) / 2 + fibonacci = (phi**fibonacci_index - (phi - 1) ** fibonacci_index) / 5**0.5 return int(fibonacci) diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index 005d2e98514b..c15445e4d7b0 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -14,7 +14,7 @@ ks = [k for k in range(2, 20 + 1)] -base = [10 ** k for k in range(ks[-1] + 1)] +base = [10**k for k in range(ks[-1] + 1)] memo: dict[int, dict[int, list[list[int]]]] = {} @@ -168,7 +168,7 @@ def add(digits, k, addend): digits.append(digit) -def solution(n: int = 10 ** 15) -> int: +def solution(n: int = 10**15) -> int: """ returns n-th term of sequence @@ -193,7 +193,7 @@ def solution(n: int = 10 ** 15) -> int: a_n = 0 for j in range(len(digits)): - a_n += digits[j] * 10 ** j + a_n += digits[j] * 10**j return a_n diff --git a/quantum/deutsch_jozsa.py b/quantum/deutsch_jozsa.py index 304eea196e03..d7e2d8335fb9 100755 --- a/quantum/deutsch_jozsa.py +++ b/quantum/deutsch_jozsa.py @@ -39,7 +39,7 @@ def dj_oracle(case: str, num_qubits: int) -> q.QuantumCircuit: 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) + 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 diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index bb24e781a6c1..83a3b8b74e27 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -166,7 +166,7 @@ def hill_climbing( doctest.testmod() def test_f1(x, y): - return (x ** 2) + (y ** 2) + 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) @@ -187,7 +187,7 @@ def test_f1(x, y): ) def test_f2(x, y): - return (3 * x ** 2) - (6 * 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) diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py index ad29559f1b8d..063d225d0b22 100644 --- a/searches/simulated_annealing.py +++ b/searches/simulated_annealing.py @@ -97,7 +97,7 @@ def simulated_annealing( if __name__ == "__main__": def test_f1(x, y): - return (x ** 2) + (y ** 2) + 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) @@ -120,7 +120,7 @@ def test_f1(x, y): ) def test_f2(x, y): - return (3 * x ** 2) - (6 * 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) From b2a77cc4fb922ab66f0978e45c108bdb4c30396d Mon Sep 17 00:00:00 2001 From: Saptarshi Sengupta <94242536+saptarshi1996@users.noreply.github.com> Date: Mon, 31 Jan 2022 06:11:46 +0530 Subject: [PATCH 1767/2908] Scraping prescription drug prices from Rx site using the prescription drug name and zipcode (#5967) * add wellrx scraping * write test fix docs * fix resolve issues * black format. fix returns * type check fix for union * black formatted * Change requests after code review * add precommit changes * flake errors --- web_programming/fetch_well_rx_price.py | 102 +++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 web_programming/fetch_well_rx_price.py diff --git a/web_programming/fetch_well_rx_price.py b/web_programming/fetch_well_rx_price.py new file mode 100644 index 000000000000..58dbe5993adb --- /dev/null +++ b/web_programming/fetch_well_rx_price.py @@ -0,0 +1,102 @@ +""" + +Scrape the price and pharmacy name for a prescription drug from rx site +after providing the drug name and zipcode. + +""" + +from typing import Union +from urllib.error import HTTPError + +from bs4 import BeautifulSoup +from requests import exceptions, get + +BASE_URL = "https://www.wellrx.com/prescriptions/%7B0%7D/%7B1%7D/?freshSearch=true" + + +def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> Union[list, None]: + """[summary] + + This function will take input of drug name and zipcode, + then request to the BASE_URL site. + Get the page data and scrape it to the generate the + list of lowest prices for the prescription drug. + + Args: + drug_name (str): [Drug name] + zip_code(str): [Zip code] + + Returns: + list: [List of pharmacy name and price] + + >>> fetch_pharmacy_and_price_list(None, None) + + >>> fetch_pharmacy_and_price_list(None, 30303) + + >>> fetch_pharmacy_and_price_list("eliquis", None) + + """ + + try: + + # Has user provided both inputs? + if not drug_name or not zip_code: + return None + + request_url = BASE_URL.format(drug_name, zip_code) + response = get(request_url) + + # Is the response ok? + response.raise_for_status() + + # Scrape the data using bs4 + soup = BeautifulSoup(response.text, "html.parser") + + # This list will store the name and price. + pharmacy_price_list = [] + + # Fetch all the grids that contains the items. + grid_list = soup.find_all("div", {"class": "grid-x pharmCard"}) + if grid_list and len(grid_list) > 0: + for grid in grid_list: + + # Get the pharmacy price. + pharmacy_name = grid.find("p", {"class": "list-title"}).text + + # Get price of the drug. + price = grid.find("span", {"p", "price price-large"}).text + + pharmacy_price_list.append( + { + "pharmacy_name": pharmacy_name, + "price": price, + } + ) + + return pharmacy_price_list + + except (HTTPError, exceptions.RequestException, ValueError): + return None + + +if __name__ == "__main__": + + # Enter a drug name and a zip code + drug_name = input("Enter drug name: ").strip() + zip_code = input("Enter zip code: ").strip() + + pharmacy_price_list: Union[list, None] = fetch_pharmacy_and_price_list( + drug_name, zip_code + ) + + if pharmacy_price_list: + + print(f"\nSearch results for {drug_name} at location {zip_code}:") + for pharmacy_price in pharmacy_price_list: + + name = pharmacy_price["pharmacy_name"] + price = pharmacy_price["price"] + + print(f"Pharmacy: {name} Price: {price}") + else: + print(f"No results found for {drug_name}") From d28ac6483a97deb5ac09a5261d851e97a25c2ee5 Mon Sep 17 00:00:00 2001 From: Saptarshi Sengupta <94242536+saptarshi1996@users.noreply.github.com> Date: Wed, 2 Feb 2022 03:49:17 +0530 Subject: [PATCH 1768/2908] Scrape anime and play episodes on browser without ads from terminal (#5975) * fetch anime * formatted code * fix format errors * fix bot reviews * pr review fixes * remove unussed exception * change var name * fix comments --- web_programming/fetch_anime_and_play.py | 188 ++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 web_programming/fetch_anime_and_play.py diff --git a/web_programming/fetch_anime_and_play.py b/web_programming/fetch_anime_and_play.py new file mode 100644 index 000000000000..e11948d0ae78 --- /dev/null +++ b/web_programming/fetch_anime_and_play.py @@ -0,0 +1,188 @@ +from xml.dom import NotFoundErr + +import requests +from bs4 import BeautifulSoup, NavigableString +from fake_useragent import UserAgent + +BASE_URL = "https://ww1.gogoanime2.org/" + + +def search_scraper(anime_name: str) -> list: + + """[summary] + + Take an url and + return list of anime after scraping the site. + + >>> type(search_scraper("demon_slayer")) + + + Args: + anime_name (str): [Name of anime] + + Raises: + e: [Raises exception on failure] + + Returns: + [list]: [List of animes] + """ + + # concat the name to form the search url. + search_url = f"{BASE_URL}/search/{anime_name}" + + response = requests.get( + search_url, headers={"UserAgent": UserAgent().chrome} + ) # request the url. + + # Is the response ok? + response.raise_for_status() + + # parse with soup. + soup = BeautifulSoup(response.text, "html.parser") + + # get list of anime + anime_ul = soup.find("ul", {"class": "items"}) + anime_li = anime_ul.children + + # for each anime, insert to list. the name and url. + anime_list = [] + for anime in anime_li: + if not isinstance(anime, NavigableString): + try: + anime_url, anime_title = ( + anime.find("a")["href"], + anime.find("a")["title"], + ) + anime_list.append( + { + "title": anime_title, + "url": anime_url, + } + ) + except (NotFoundErr, KeyError): + pass + + return anime_list + + +def search_anime_episode_list(episode_endpoint: str) -> list: + + """[summary] + + Take an url and + return list of episodes after scraping the site + for an url. + + >>> type(search_anime_episode_list("/anime/kimetsu-no-yaiba")) + + + Args: + episode_endpoint (str): [Endpoint of episode] + + Raises: + e: [description] + + Returns: + [list]: [List of episodes] + """ + + request_url = f"{BASE_URL}{episode_endpoint}" + + response = requests.get(url=request_url, headers={"UserAgent": UserAgent().chrome}) + response.raise_for_status() + + soup = BeautifulSoup(response.text, "html.parser") + + # With this id. get the episode list. + episode_page_ul = soup.find("ul", {"id": "episode_related"}) + episode_page_li = episode_page_ul.children + + episode_list = [] + for episode in episode_page_li: + try: + if not isinstance(episode, NavigableString): + episode_list.append( + { + "title": episode.find("div", {"class": "name"}).text.replace( + " ", "" + ), + "url": episode.find("a")["href"], + } + ) + except (KeyError, NotFoundErr): + pass + + return episode_list + + +def get_anime_episode(episode_endpoint: str) -> list: + + """[summary] + + Get click url and download url from episode url + + >>> type(get_anime_episode("/watch/kimetsu-no-yaiba/1")) + + + Args: + episode_endpoint (str): [Endpoint of episode] + + Raises: + e: [description] + + Returns: + [list]: [List of download and watch url] + """ + + episode_page_url = f"{BASE_URL}{episode_endpoint}" + + response = requests.get( + url=episode_page_url, headers={"User-Agent": UserAgent().chrome} + ) + response.raise_for_status() + + soup = BeautifulSoup(response.text, "html.parser") + + try: + episode_url = soup.find("iframe", {"id": "playerframe"})["src"] + download_url = episode_url.replace("/embed/", "/playlist/") + ".m3u8" + except (KeyError, NotFoundErr) as e: + raise e + + return [f"{BASE_URL}{episode_url}", f"{BASE_URL}{download_url}"] + + +if __name__ == "__main__": + + anime_name = input("Enter anime name: ").strip() + anime_list = search_scraper(anime_name) + print("\n") + + if len(anime_list) == 0: + print("No anime found with this name") + else: + + print(f"Found {len(anime_list)} results: ") + for (i, anime) in enumerate(anime_list): + anime_title = anime["title"] + print(f"{i+1}. {anime_title}") + + anime_choice = int(input("\nPlease choose from the following list: ").strip()) + chosen_anime = anime_list[anime_choice - 1] + print(f"You chose {chosen_anime['title']}. Searching for episodes...") + + episode_list = search_anime_episode_list(chosen_anime["url"]) + if len(episode_list) == 0: + print("No episode found for this anime") + else: + print(f"Found {len(episode_list)} results: ") + for (i, episode) in enumerate(episode_list): + print(f"{i+1}. {episode['title']}") + + episode_choice = int(input("\nChoose an episode by serial no: ").strip()) + chosen_episode = episode_list[episode_choice - 1] + print(f"You chose {chosen_episode['title']}. Searching...") + + episode_url, download_url = get_anime_episode(chosen_episode["url"]) + print(f"\nTo watch, ctrl+click on {episode_url}.") + print(f"To download, ctrl+click on {download_url}.") From 54f765bdd0331f4b9381de8c879218ace1313be9 Mon Sep 17 00:00:00 2001 From: Calvin McCarter Date: Wed, 2 Feb 2022 15:05:05 -0500 Subject: [PATCH 1769/2908] Extend power iteration to handle complex Hermitian input matrices (#5925) * works python3 -m unittest discover --start-directory src --pattern "power*.py" --t . -v * cleanup * revert switch to unittest * fix flake8 --- linear_algebra/src/power_iteration.py | 70 ++++++++++++++++++--------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/linear_algebra/src/power_iteration.py b/linear_algebra/src/power_iteration.py index 2cf22838e4a1..4c6525b6e4af 100644 --- a/linear_algebra/src/power_iteration.py +++ b/linear_algebra/src/power_iteration.py @@ -9,10 +9,10 @@ def power_iteration( ) -> tuple[float, np.ndarray]: """ Power Iteration. - Find the largest eignevalue and corresponding eigenvector + Find the largest eigenvalue 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_matrix must be either real or Hermitian. Input input_matrix: input matrix whose largest eigenvalue we will find. @@ -41,6 +41,12 @@ def power_iteration( assert np.shape(input_matrix)[0] == np.shape(input_matrix)[1] # Ensure proper dimensionality. assert np.shape(input_matrix)[0] == np.shape(vector)[0] + # Ensure inputs are either both complex or both real + assert np.iscomplexobj(input_matrix) == np.iscomplexobj(vector) + is_complex = np.iscomplexobj(input_matrix) + if is_complex: + # Ensure complex input_matrix is Hermitian + assert np.array_equal(input_matrix, input_matrix.conj().T) # Set convergence to False. Will define convergence when we exceed max_iterations # or when we have small changes from one iteration to next. @@ -57,7 +63,8 @@ def power_iteration( 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)) + vectorH = vector.conj().T if is_complex else vector.T + lamda = np.dot(vectorH, np.dot(input_matrix, vector)) # Check convergence. error = np.abs(lamda - lamda_previous) / lamda @@ -68,6 +75,9 @@ def power_iteration( lamda_previous = lamda + if is_complex: + lamda = np.real(lamda) + return lamda, vector @@ -75,26 +85,40 @@ 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 + real_input_matrix = np.array([[41, 4, 20], [4, 26, 30], [20, 30, 50]]) + real_vector = np.array([41, 4, 20]) + complex_input_matrix = real_input_matrix.astype(np.complex128) + imag_matrix = np.triu(1j * complex_input_matrix, 1) + complex_input_matrix += imag_matrix + complex_input_matrix += -1 * imag_matrix.T + complex_vector = np.array([41, 4, 20]).astype(np.complex128) + + for problem_type in ["real", "complex"]: + if problem_type == "real": + input_matrix = real_input_matrix + vector = real_vector + elif problem_type == "complex": + input_matrix = complex_input_matrix + vector = complex_vector + + # Our implementation. + eigen_value, eigen_vector = power_iteration(input_matrix, vector) + + # Numpy implementation. + + # Get eigenvalues and eigenvectors using built-in numpy + # eigh (eigh used for symmetric or hermetian matrices). + eigen_values, eigen_vectors = np.linalg.eigh(input_matrix) + # Last eigenvalue is the maximum one. + eigen_value_max = eigen_values[-1] + # Last column in this matrix is eigenvector corresponding to largest eigenvalue. + 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__": From f707f6d689ed40f51e5ceb8f0554e26e1e9fd507 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 13 Feb 2022 06:57:44 +0100 Subject: [PATCH 1770/2908] Upgrade to Python 3.10 (#5992) * Upgrade to Python 3.10 * Upgrade to Python 3.10 * mypy || true * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- .github/workflows/pre-commit.yml | 2 +- DIRECTORY.md | 2 ++ requirements.txt | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f270ea55d17..aabfacbfc327 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: "3.9" + python-version: "3.10" - uses: actions/cache@v2 with: path: ~/.cache/pip @@ -23,7 +23,7 @@ jobs: python -m pip install mypy pytest-cov -r requirements.txt - run: | mkdir -p .mypy_cache - mypy --ignore-missing-imports --install-types --non-interactive . + mypy --ignore-missing-imports --install-types --non-interactive . || true - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 19196098b1c1..de73c96adfb1 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -15,7 +15,7 @@ jobs: key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: "3.10" - uses: psf/black@21.4b0 - name: Install pre-commit run: | diff --git a/DIRECTORY.md b/DIRECTORY.md index b5ddb9fcb156..e95785b25d66 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1014,9 +1014,11 @@ * [Daily Horoscope](https://github.com/TheAlgorithms/Python/blob/master/web_programming/daily_horoscope.py) * [Download Images From Google Query](https://github.com/TheAlgorithms/Python/blob/master/web_programming/download_images_from_google_query.py) * [Emails From Url](https://github.com/TheAlgorithms/Python/blob/master/web_programming/emails_from_url.py) + * [Fetch Anime And Play](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_anime_and_play.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) + * [Fetch Well Rx Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_well_rx_price.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) * [Get Top Hn Posts](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_top_hn_posts.py) diff --git a/requirements.txt b/requirements.txt index 9a26dcc21f36..294494acf41a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ beautifulsoup4 fake_useragent -keras<2.7.0 +keras lxml matplotlib numpy From 885580b3a152d02ad72ff433c2aefb6d604ef3c8 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 13 Feb 2022 11:01:58 +0100 Subject: [PATCH 1771/2908] pyupgrade --py310-plus and run mypy in precommit, not build (#5996) * pyupgrade --py310-plus and run mypy in precommit, not build * pyupgrade --py310-plus web_programming/fetch_well_rx_price.py * pyupgrade --py310-plus web_programming/fetch_well_rx_price.py * Fix arithmetic_analysis/in_static_equilibrium.py * Fix arithmetic_analysis/in_static_equilibrium.py --- .github/workflows/build.yml | 5 +---- .pre-commit-config.yaml | 2 +- arithmetic_analysis/in_static_equilibrium.py | 4 ++-- web_programming/fetch_well_rx_price.py | 5 ++--- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aabfacbfc327..403ec44c888d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,10 +20,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools six wheel - python -m pip install mypy pytest-cov -r requirements.txt - - run: | - mkdir -p .mypy_cache - mypy --ignore-missing-imports --install-types --non-interactive . || true + python -m pip install pytest-cov -r requirements.txt - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33069a807cee..ab74d28e167a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: hooks: - id: pyupgrade args: - - --py39-plus + - --py310-plus - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.2 diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index 6fe84b45475c..2ac3e7213fda 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -13,9 +13,9 @@ def polar_force( Resolves force along rectangular components. (force, angle) => (force_x, force_y) >>> polar_force(10, 45) - [7.0710678118654755, 7.071067811865475] + [7.071067811865477, 7.0710678118654755] >>> polar_force(10, 3.14, radian_mode=True) - [-9.999987317275394, 0.01592652916486828] + [-9.999987317275396, 0.01592652916486828] """ if radian_mode: return [magnitude * cos(angle), magnitude * sin(angle)] diff --git a/web_programming/fetch_well_rx_price.py b/web_programming/fetch_well_rx_price.py index 58dbe5993adb..5174f39f9532 100644 --- a/web_programming/fetch_well_rx_price.py +++ b/web_programming/fetch_well_rx_price.py @@ -5,7 +5,6 @@ """ -from typing import Union from urllib.error import HTTPError from bs4 import BeautifulSoup @@ -14,7 +13,7 @@ BASE_URL = "https://www.wellrx.com/prescriptions/%7B0%7D/%7B1%7D/?freshSearch=true" -def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> Union[list, None]: +def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None: """[summary] This function will take input of drug name and zipcode, @@ -85,7 +84,7 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> Union[list, drug_name = input("Enter drug name: ").strip() zip_code = input("Enter zip code: ").strip() - pharmacy_price_list: Union[list, None] = fetch_pharmacy_and_price_list( + pharmacy_price_list: list | None = fetch_pharmacy_and_price_list( drug_name, zip_code ) From 637cf10555adf7bffdc6aeeef587c4145c8a27a7 Mon Sep 17 00:00:00 2001 From: zer0-x <65136727+zer0-x@users.noreply.github.com> Date: Sun, 13 Feb 2022 20:09:09 +0300 Subject: [PATCH 1772/2908] Add points are collinear in 3d algorithm to /maths (#5983) * Add points are collinear in 3d algorithm to /maths * Apply suggestions from code review in points_are_collinear_3d.py Thanks to cclauss. Co-authored-by: Christian Clauss * Rename some variables to be more self-documenting. * Update points_are_collinear_3d.py Co-authored-by: Christian Clauss --- maths/points_are_collinear_3d.py | 126 +++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 maths/points_are_collinear_3d.py diff --git a/maths/points_are_collinear_3d.py b/maths/points_are_collinear_3d.py new file mode 100644 index 000000000000..3bc0b3b9ebe5 --- /dev/null +++ b/maths/points_are_collinear_3d.py @@ -0,0 +1,126 @@ +""" +Check if three points are collinear in 3D. + +In short, the idea is that we are able to create a triangle using three points, +and the area of that triangle can determine if the three points are collinear or not. + + +First, we create two vectors with the same initial point from the three points, +then we will calculate the cross-product of them. + +The length of the cross vector is numerically equal to the area of a parallelogram. + +Finally, the area of the triangle is equal to half of the area of the parallelogram. + +Since we are only differentiating between zero and anything else, +we can get rid of the square root when calculating the length of the vector, +and also the division by two at the end. + +From a second perspective, if the two vectors are parallel and overlapping, +we can't get a nonzero perpendicular vector, +since there will be an infinite number of orthogonal vectors. + +To simplify the solution we will not calculate the length, +but we will decide directly from the vector whether it is equal to (0, 0, 0) or not. + + +Read More: + https://math.stackexchange.com/a/1951650 +""" + +Vector3d = tuple[float, float, float] +Point3d = tuple[float, float, float] + + +def create_vector(end_point1: Point3d, end_point2: Point3d) -> Vector3d: + """ + Pass two points to get the vector from them in the form (x, y, z). + + >>> create_vector((0, 0, 0), (1, 1, 1)) + (1, 1, 1) + >>> create_vector((45, 70, 24), (47, 32, 1)) + (2, -38, -23) + >>> create_vector((-14, -1, -8), (-7, 6, 4)) + (7, 7, 12) + """ + x = end_point2[0] - end_point1[0] + y = end_point2[1] - end_point1[1] + z = end_point2[2] - end_point1[2] + return (x, y, z) + + +def get_3d_vectors_cross(ab: Vector3d, ac: Vector3d) -> Vector3d: + """ + Get the cross of the two vectors AB and AC. + + I used determinant of 2x2 to get the determinant of the 3x3 matrix in the process. + + Read More: + https://en.wikipedia.org/wiki/Cross_product + https://en.wikipedia.org/wiki/Determinant + + >>> get_3d_vectors_cross((3, 4, 7), (4, 9, 2)) + (-55, 22, 11) + >>> get_3d_vectors_cross((1, 1, 1), (1, 1, 1)) + (0, 0, 0) + >>> get_3d_vectors_cross((-4, 3, 0), (3, -9, -12)) + (-36, -48, 27) + >>> get_3d_vectors_cross((17.67, 4.7, 6.78), (-9.5, 4.78, -19.33)) + (-123.2594, 277.15110000000004, 129.11260000000001) + """ + x = ab[1] * ac[2] - ab[2] * ac[1] # *i + y = (ab[0] * ac[2] - ab[2] * ac[0]) * -1 # *j + z = ab[0] * ac[1] - ab[1] * ac[0] # *k + return (x, y, z) + + +def is_zero_vector(vector: Vector3d, accuracy: int) -> bool: + """ + Check if vector is equal to (0, 0, 0) of not. + + Sine the algorithm is very accurate, we will never get a zero vector, + so we need to round the vector axis, + because we want a result that is either True or False. + In other applications, we can return a float that represents the collinearity ratio. + + >>> is_zero_vector((0, 0, 0), accuracy=10) + True + >>> is_zero_vector((15, 74, 32), accuracy=10) + False + >>> is_zero_vector((-15, -74, -32), accuracy=10) + False + """ + return tuple(round(x, accuracy) for x in vector) == (0, 0, 0) + + +def are_collinear(a: Point3d, b: Point3d, c: Point3d, accuracy: int = 10) -> bool: + """ + Check if three points are collinear or not. + + 1- Create tow vectors AB and AC. + 2- Get the cross vector of the tow vectors. + 3- Calcolate the length of the cross vector. + 4- If the length is zero then the points are collinear, else they are not. + + The use of the accuracy parameter is explained in is_zero_vector docstring. + + >>> are_collinear((4.802293498137402, 3.536233125455244, 0), + ... (-2.186788107953106, -9.24561398001649, 7.141509524846482), + ... (1.530169574640268, -2.447927606600034, 3.343487096469054)) + True + >>> are_collinear((-6, -2, 6), + ... (6.200213806439997, -4.930157614926678, -4.482371908289856), + ... (-4.085171149525941, -2.459889509029438, 4.354787180795383)) + True + >>> are_collinear((2.399001826862445, -2.452009976680793, 4.464656666157666), + ... (-3.682816335934376, 5.753788986533145, 9.490993909044244), + ... (1.962903518985307, 3.741415730125627, 7)) + False + >>> are_collinear((1.875375340689544, -7.268426006071538, 7.358196269835993), + ... (-3.546599383667157, -4.630005261513976, 3.208784032924246), + ... (-2.564606140206386, 3.937845170672183, 7)) + False + """ + ab = create_vector(a, b) + ac = create_vector(a, c) + return is_zero_vector(get_3d_vectors_cross(ab, ac), accuracy) From 7a9b3c7292cbd71fdc7723f449b9bbcbefbf9747 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sun, 13 Feb 2022 12:20:19 -0500 Subject: [PATCH 1773/2908] Added average absolute deviation (#5951) * Added average absolute deviation * Formats program with black * reruns updated pre commit * Update average_absolute_deviation.py Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + maths/average_absolute_deviation.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 maths/average_absolute_deviation.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e95785b25d66..eeea22e4768f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -454,6 +454,7 @@ * [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 Absolute Deviation](https://github.com/TheAlgorithms/Python/blob/master/maths/average_absolute_deviation.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) diff --git a/maths/average_absolute_deviation.py b/maths/average_absolute_deviation.py new file mode 100644 index 000000000000..193d94a2f265 --- /dev/null +++ b/maths/average_absolute_deviation.py @@ -0,0 +1,29 @@ +def average_absolute_deviation(nums: list[int]) -> float: + """ + Return the average absolute deviation of a list of numbers. + Wiki: https://en.wikipedia.org/wiki/Average_absolute_deviation + + >>> average_absolute_deviation([0]) + 0.0 + >>> average_absolute_deviation([4, 1, 3, 2]) + 1.0 + >>> average_absolute_deviation([2, 70, 6, 50, 20, 8, 4, 0]) + 20.0 + >>> average_absolute_deviation([-20, 0, 30, 15]) + 16.25 + >>> average_absolute_deviation([]) + Traceback (most recent call last): + ... + ValueError: List is empty + """ + if not nums: # Makes sure that the list is not empty + raise ValueError("List is empty") + + average = sum(nums) / len(nums) # Calculate the average + return sum(abs(x - average) for x in nums) / len(nums) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 37200a4b3b262dd9aa690c4b6514ead0944b3778 Mon Sep 17 00:00:00 2001 From: Anirudh Lakhotia Date: Wed, 16 Mar 2022 21:10:48 +0530 Subject: [PATCH 1774/2908] LICENSE: Year change (#5920) * :memo: Updated year Fixes: #5916 * Update LICENSE.md Co-authored-by: John Law --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index c3c2857cd312..2897d02e2a01 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2021 The Algorithms +Copyright (c) 2016-2022 TheAlgorithms and contributors 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 4064bf45f54c1359b686cd7f231b25ee5ae2d3cc Mon Sep 17 00:00:00 2001 From: Harshit Agarwal <43147421+9harshit@users.noreply.github.com> Date: Wed, 30 Mar 2022 00:10:56 -0300 Subject: [PATCH 1775/2908] fix(pre-commit): update `black` version (#6075) black==22.1.0 is breaking the hook. Updating it to 22.3.0 fixes all the issue Refer: https://github.com/python-poetry/poetry/issues/5375 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab74d28e167a..33da02fb72ad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black From 1f1daaf1c75adbe43126e53ef1eba718ecb67029 Mon Sep 17 00:00:00 2001 From: Harshit Agarwal <43147421+9harshit@users.noreply.github.com> Date: Mon, 4 Apr 2022 00:36:32 -0300 Subject: [PATCH 1776/2908] feat: add strings/ngram algorithm (#6074) * feat: added ngram algorithm * fix(test): use `math.isclose` to match floating point numbers approximately Co-authored-by: Christian Clauss Co-authored-by: Dhruv Manilawala --- arithmetic_analysis/in_static_equilibrium.py | 14 +++++++++--- strings/ngram.py | 23 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 strings/ngram.py diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index 2ac3e7213fda..ed0d1eb98cf3 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -12,8 +12,12 @@ def polar_force( """ Resolves force along rectangular components. (force, angle) => (force_x, force_y) - >>> polar_force(10, 45) - [7.071067811865477, 7.0710678118654755] + >>> import math + >>> force = polar_force(10, 45) + >>> math.isclose(force[0], 7.071067811865477) + True + >>> math.isclose(force[1], 7.0710678118654755) + True >>> polar_force(10, 3.14, radian_mode=True) [-9.999987317275396, 0.01592652916486828] """ @@ -50,7 +54,11 @@ 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/strings/ngram.py b/strings/ngram.py new file mode 100644 index 000000000000..0b13e34a4732 --- /dev/null +++ b/strings/ngram.py @@ -0,0 +1,23 @@ +""" +https://en.wikipedia.org/wiki/N-gram +""" + + +def create_ngram(sentence: str, ngram_size: int) -> list[str]: + """ + Create ngrams from a sentence + + >>> create_ngram("I am a sentence", 2) + ['I ', ' a', 'am', 'm ', ' a', 'a ', ' s', 'se', 'en', 'nt', 'te', 'en', 'nc', 'ce'] + >>> create_ngram("I am an NLPer", 2) + ['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er'] + >>> create_ngram("This is short", 50) + [] + """ + return [sentence[i : i + ngram_size] for i in range(len(sentence) - ngram_size + 1)] + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 1d3d18bcd28cd4eb0ffb7b1db213215f2f92c78a Mon Sep 17 00:00:00 2001 From: Aviv Faraj <73610201+avivfaraj@users.noreply.github.com> Date: Mon, 4 Apr 2022 16:44:29 +0300 Subject: [PATCH 1777/2908] horizontal motion code physics (#4710) * Add files via upload * Changed print to f-string Also printed out results in a math notation * Add files via upload * Fixes: #4710 provided return type * File exists in another pull request * imported radians from math * Updated file according to pre-commit test * Updated file * Updated gamma * Deleted duplicate file * removed pi * reversed tests * Fixed angle condition * Modified prints to f-string * Update horizontal_projectile_motion.py * Update horizontal_projectile_motion.py * Fixes #4710 added exceptions and tests * Added float tests * Fixed type annotations * Fixed last annotation * Fixed annotations * fixed format * Revert "fixed format" This reverts commit 5b0249ac0a0f9c36c3cfbab8423eb72925a73ffb. Undo changes @wq * Revert "Fixed annotations" This reverts commit c37bb9540834cb77e37822eb376a5896cda34778. * Revert "Fixed last annotation" This reverts commit e3678fdeadd23f1bfca27015ab524efa184f6c79. * Revert "Fixed type annotations" This reverts commit 3f2b238c34cd926b335d1f6f750e009f08e8f270. * Revert to 4e2fcaf6fb * Fixing errors found during pre-commit --- physics/horizontal_projectile_motion.py | 152 ++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 physics/horizontal_projectile_motion.py diff --git a/physics/horizontal_projectile_motion.py b/physics/horizontal_projectile_motion.py new file mode 100644 index 000000000000..0f27b0617105 --- /dev/null +++ b/physics/horizontal_projectile_motion.py @@ -0,0 +1,152 @@ +""" +Horizontal Projectile Motion problem in physics. +This algorithm solves a specific problem in which +the motion starts from the ground as can be seen below: + (v = 0) + ** + * * + * * + * * + * * + * * +GROUND GROUND +For more info: https://en.wikipedia.org/wiki/Projectile_motion +""" + +# Importing packages +from math import radians as angle_to_radians +from math import sin + +# Acceleration Constant on hearth (unit m/s^2) +g = 9.80665 + + +def check_args(init_velocity: float, angle: float) -> None: + """ + Check that the arguments are valid + """ + + # Ensure valid instance + if not isinstance(init_velocity, (int, float)): + raise TypeError("Invalid velocity. Should be a positive number.") + + if not isinstance(angle, (int, float)): + raise TypeError("Invalid angle. Range is 1-90 degrees.") + + # Ensure valid angle + if angle > 90 or angle < 1: + raise ValueError("Invalid angle. Range is 1-90 degrees.") + + # Ensure valid velocity + if init_velocity < 0: + raise ValueError("Invalid velocity. Should be a positive number.") + + +def horizontal_distance(init_velocity: float, angle: float) -> float: + """ + Returns the horizontal distance that the object cover + Formula: + v_0^2 * sin(2 * alpha) + --------------------- + g + v_0 - initial velocity + alpha - angle + >>> horizontal_distance(30, 45) + 91.77 + >>> horizontal_distance(100, 78) + 414.76 + >>> horizontal_distance(-1, 20) + Traceback (most recent call last): + ... + ValueError: Invalid velocity. Should be a positive number. + >>> horizontal_distance(30, -20) + Traceback (most recent call last): + ... + ValueError: Invalid angle. Range is 1-90 degrees. + """ + check_args(init_velocity, angle) + radians = angle_to_radians(2 * angle) + return round(init_velocity**2 * sin(radians) / g, 2) + + +def max_height(init_velocity: float, angle: float) -> float: + """ + Returns the maximum height that the object reach + Formula: + v_0^2 * sin^2(alpha) + -------------------- + 2g + v_0 - initial velocity + alpha - angle + >>> max_height(30, 45) + 22.94 + >>> max_height(100, 78) + 487.82 + >>> max_height("a", 20) + Traceback (most recent call last): + ... + TypeError: Invalid velocity. Should be a positive number. + >>> horizontal_distance(30, "b") + Traceback (most recent call last): + ... + TypeError: Invalid angle. Range is 1-90 degrees. + """ + check_args(init_velocity, angle) + radians = angle_to_radians(angle) + return round(init_velocity**2 * sin(radians) ** 2 / (2 * g), 2) + + +def total_time(init_velocity: float, angle: float) -> float: + """ + Returns total time of the motion + Formula: + 2 * v_0 * sin(alpha) + -------------------- + g + v_0 - initial velocity + alpha - angle + >>> total_time(30, 45) + 4.33 + >>> total_time(100, 78) + 19.95 + >>> total_time(-10, 40) + Traceback (most recent call last): + ... + ValueError: Invalid velocity. Should be a positive number. + >>> total_time(30, "b") + Traceback (most recent call last): + ... + TypeError: Invalid angle. Range is 1-90 degrees. + """ + check_args(init_velocity, angle) + radians = angle_to_radians(angle) + return round(2 * init_velocity * sin(radians) / g, 2) + + +def test_motion() -> None: + """ + >>> test_motion() + """ + v0, angle = 25, 20 + assert horizontal_distance(v0, angle) == 40.97 + assert max_height(v0, angle) == 3.73 + assert total_time(v0, angle) == 1.74 + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + + # Get input from user + init_vel = float(input("Initial Velocity: ").strip()) + + # Get input from user + angle = float(input("angle: ").strip()) + + # Print results + print() + print("Results: ") + print(f"Horizontal Distance: {str(horizontal_distance(init_vel, angle))} [m]") + print(f"Maximum Height: {str(max_height(init_vel, angle))} [m]") + print(f"Total Time: {str(total_time(init_vel, angle))} [s]") From 1400cb86ff7c656087963db41844e0ca503ae6d5 Mon Sep 17 00:00:00 2001 From: "Paulo S. G. Ferraz" Date: Fri, 8 Apr 2022 14:40:45 -0300 Subject: [PATCH 1778/2908] Remove duplicate is_prime related functions (#5892) * Fixes (#5434) * Update ciphers.rabin_miller.py maths.miller_rabin.py * Fixing ERROR maths/miller_rabin.py - ModuleNotFoundError and changing project_euler's isPrime to is_prime function names * Update sol1.py * fix: try to change to list * fix pre-commit * fix capital letters * Update miller_rabin.py * Update rabin_miller.py Co-authored-by: John Law --- ciphers/rabin_miller.py | 6 +++--- maths/miller_rabin.py | 10 +++++----- project_euler/problem_003/sol1.py | 22 +++++++++++----------- project_euler/problem_007/sol2.py | 10 +++++----- project_euler/problem_007/sol3.py | 10 +++++----- project_euler/problem_058/sol1.py | 17 ++++++++--------- 6 files changed, 37 insertions(+), 38 deletions(-) diff --git a/ciphers/rabin_miller.py b/ciphers/rabin_miller.py index c42ad2f5928d..a9b834bfb4be 100644 --- a/ciphers/rabin_miller.py +++ b/ciphers/rabin_miller.py @@ -25,7 +25,7 @@ def rabinMiller(num: int) -> bool: return True -def isPrime(num: int) -> bool: +def is_prime_low_num(num: int) -> bool: if num < 2: return False @@ -213,11 +213,11 @@ def isPrime(num: int) -> bool: def generateLargePrime(keysize: int = 1024) -> int: while True: num = random.randrange(2 ** (keysize - 1), 2 ** (keysize)) - if isPrime(num): + if is_prime_low_num(num): return num if __name__ == "__main__": num = generateLargePrime() print(("Prime number:", num)) - print(("isPrime:", isPrime(num))) + print(("is_prime_low_num:", is_prime_low_num(num))) diff --git a/maths/miller_rabin.py b/maths/miller_rabin.py index 2b0944508b4b..d35e5485888f 100644 --- a/maths/miller_rabin.py +++ b/maths/miller_rabin.py @@ -6,11 +6,11 @@ # 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): +def is_prime_big(n, prec=1000): """ - >>> from .prime_check import prime_check - >>> # all(is_prime(i) == prime_check(i) for i in range(1000)) # 3.45s - >>> all(is_prime(i) == prime_check(i) for i in range(256)) + >>> from maths.prime_check import prime_check + >>> # all(is_prime_big(i) == prime_check(i) for i in range(1000)) # 3.45s + >>> all(is_prime_big(i) == prime_check(i) for i in range(256)) True """ if n < 2: @@ -48,4 +48,4 @@ def is_prime(n, prec=1000): 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))) + print(", ".join(str(i) for i in range(n + 1) if is_prime_big(i))) diff --git a/project_euler/problem_003/sol1.py b/project_euler/problem_003/sol1.py index 1f329984203a..606a6945e4ad 100644 --- a/project_euler/problem_003/sol1.py +++ b/project_euler/problem_003/sol1.py @@ -13,23 +13,23 @@ import math -def isprime(num: int) -> bool: +def is_prime(num: int) -> bool: """ Returns boolean representing primality of given number num. - >>> isprime(2) + >>> is_prime(2) True - >>> isprime(3) + >>> is_prime(3) True - >>> isprime(27) + >>> is_prime(27) False - >>> isprime(2999) + >>> is_prime(2999) True - >>> isprime(0) + >>> is_prime(0) Traceback (most recent call last): ... ValueError: Parameter num must be greater than or equal to two. - >>> isprime(1) + >>> is_prime(1) Traceback (most recent call last): ... ValueError: Parameter num must be greater than or equal to two. @@ -84,18 +84,18 @@ def solution(n: int = 600851475143) -> int: if n <= 0: raise ValueError("Parameter n must be greater than or equal to one.") max_number = 0 - if isprime(n): + if is_prime(n): return n while n % 2 == 0: n //= 2 - if isprime(n): + if is_prime(n): return n for i in range(3, int(math.sqrt(n)) + 1, 2): if n % i == 0: - if isprime(n // i): + if is_prime(n // i): max_number = n // i break - elif isprime(i): + elif is_prime(i): max_number = i return max_number diff --git a/project_euler/problem_007/sol2.py b/project_euler/problem_007/sol2.py index dfcad8c148af..44d72e9493e8 100644 --- a/project_euler/problem_007/sol2.py +++ b/project_euler/problem_007/sol2.py @@ -13,15 +13,15 @@ """ -def isprime(number: int) -> bool: +def is_prime(number: int) -> bool: """ Determines whether the given number is prime or not - >>> isprime(2) + >>> is_prime(2) True - >>> isprime(15) + >>> is_prime(15) False - >>> isprime(29) + >>> is_prime(29) True """ @@ -76,7 +76,7 @@ def solution(nth: int = 10001) -> int: primes: list[int] = [] num = 2 while len(primes) < nth: - if isprime(num): + if is_prime(num): primes.append(num) num += 1 else: diff --git a/project_euler/problem_007/sol3.py b/project_euler/problem_007/sol3.py index 7911fa3e9d6f..daa719cefbda 100644 --- a/project_euler/problem_007/sol3.py +++ b/project_euler/problem_007/sol3.py @@ -15,15 +15,15 @@ import math -def prime_check(number: int) -> bool: +def is_prime(number: int) -> bool: """ Determines whether a given number is prime or not - >>> prime_check(2) + >>> is_prime(2) True - >>> prime_check(15) + >>> is_prime(15) False - >>> prime_check(29) + >>> is_prime(29) True """ @@ -39,7 +39,7 @@ def prime_generator(): num = 2 while True: - if prime_check(num): + if is_prime(num): yield num num += 1 diff --git a/project_euler/problem_058/sol1.py b/project_euler/problem_058/sol1.py index ed407edf7158..c59b0dd71af1 100644 --- a/project_euler/problem_058/sol1.py +++ b/project_euler/problem_058/sol1.py @@ -36,14 +36,14 @@ from math import isqrt -def isprime(number: int) -> int: +def is_prime(number: int) -> int: """ - returns whether the given number is prime or not - >>> isprime(1) + Returns whether the given number is prime or not + >>> is_prime(1) 0 - >>> isprime(17) + >>> is_prime(17) 1 - >>> isprime(10000) + >>> is_prime(10000) 0 """ if number == 1: @@ -60,7 +60,7 @@ def isprime(number: int) -> int: def solution(ratio: float = 0.1) -> int: """ - returns the side length of the square spiral of odd length greater + 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) @@ -76,9 +76,8 @@ def solution(ratio: float = 0.1) -> int: 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 + primes += is_prime(i) + j += 2 return j From 10d0e4ecbf741f9acded245b5f47f77b8d672596 Mon Sep 17 00:00:00 2001 From: varopxndx <42877919+varopxndx@users.noreply.github.com> Date: Thu, 28 Apr 2022 12:05:21 -0500 Subject: [PATCH 1779/2908] docs: Fix quicksort & binary tree traversal doc (#4878) * Fix quicksort doc * add binary tree traversals doc * Add link to the reference * Fix job * Change url * Update binary_tree_traversals.md * Update normal_distribution_quick_sort.md * Update normal_distribution_quick_sort.md Co-authored-by: John Law --- .../binary_tree/binary_tree_traversals.md | 111 ++++++++++++++++++ sorts/normal_distribution_quick_sort.md | 45 ++----- 2 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 data_structures/binary_tree/binary_tree_traversals.md diff --git a/data_structures/binary_tree/binary_tree_traversals.md b/data_structures/binary_tree/binary_tree_traversals.md new file mode 100644 index 000000000000..ebe727b6589d --- /dev/null +++ b/data_structures/binary_tree/binary_tree_traversals.md @@ -0,0 +1,111 @@ +# Binary Tree Traversal + +## Overview + +The combination of binary trees being data structures and traversal being an algorithm relates to classic problems, either directly or indirectly. + +> If you can grasp the traversal of binary trees, the traversal of other complicated trees will be easy for you. + +The following are some common ways to traverse trees. + +- Depth First Traversals (DFS): In-order, Pre-order, Post-order + +- Level Order Traversal or Breadth First or Traversal (BFS) + +There are applications for both DFS and BFS. + +Stack can be used to simplify the process of DFS traversal. Besides, since tree is a recursive data structure, recursion and stack are two key points for DFS. + +Graph for DFS: + +![binary-tree-traversal-dfs](https://tva1.sinaimg.cn/large/007S8ZIlly1ghluhzhynsg30dw0dw3yl.gif) + +The key point of BFS is how to determine whether the traversal of each level has been completed. The answer is to use a variable as a flag to represent the end of the traversal of current level. + +## Pre-order Traversal + +The traversal order of pre-order traversal is `root-left-right`. + +Algorithm Pre-order + +1. Visit the root node and push it into a stack. + +2. Pop a node from the stack, and push its right and left child node into the stack respectively. + +3. Repeat step 2. + +Conclusion: This problem involves the classic recursive data structure (i.e. a binary tree), and the algorithm above demonstrates how a simplified solution can be reached by using a stack. + +If you look at the bigger picture, you'll find that the process of traversal is as followed. `Visit the left subtrees respectively from top to bottom, and visit the right subtrees respectively from bottom to top`. If we are to implement it from this perspective, things will be somewhat different. For the `top to bottom` part we can simply use recursion, and for the `bottom to top` part we can turn to stack. + +## In-order Traversal + +The traversal order of in-order traversal is `left-root-right`. + +So the root node is not printed first. Things are getting a bit complicated here. + +Algorithm In-order + +1. Visit the root and push it into a stack. + +2. If there is a left child node, push it into the stack. Repeat this process until a leaf node reached. + + > At this point the root node and all the left nodes are in the stack. + +3. Start popping nodes from the stack. If a node has a right child node, push the child node into the stack. Repeat step 2. + +It's worth pointing out that the in-order traversal of a binary search tree (BST) is a sorted array, which is helpful for coming up simplified solutions for some problems. + +## Post-order Traversal + +The traversal order of post-order traversal is `left-right-root`. + +This one is a bit of a challenge. It deserves the `hard` tag of LeetCode. + +In this case, the root node is printed not as the first but the last one. A cunning way to do it is to: + +Record whether the current node has been visited. If 1) it's a leaf node or 2) both its left and right subtrees have been traversed, then it can be popped from the stack. + +As for `1) it's a leaf node`, you can easily tell whether a node is a leaf if both its left and right are `null`. + +As for `2) both its left and right subtrees have been traversed`, we only need a variable to record whether a node has been visited or not. In the worst case, we need to record the status for every single node and the space complexity is `O(n)`. But if you come to think about it, as we are using a stack and start printing the result from the leaf nodes, it makes sense that we only record the status for the current node popping from the stack, reducing the space complexity to `O(1)`. + +## Level Order Traversal + +The key point of level order traversal is how do we know whether the traversal of each level is done. The answer is that we use a variable as a flag representing the end of the traversal of the current level. + +![binary-tree-traversal-bfs](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlui1tpoug30dw0dw3yl.gif) + +Algorithm Level-order + +1. Visit the root node, put it in a FIFO queue, put in the queue a special flag (we are using `null` here). + +2. Dequeue a node. + +3. If the node equals `null`, it means that all nodes of the current level have been visited. If the queue is empty, we do nothing. Or else we put in another `null`. + +4. If the node is not `null`, meaning the traversal of current level has not finished yet, we enqueue its left subtree and right subtree respectively. + +## Bi-color marking + +We know that there is a tri-color marking in garbage collection algorithm, which works as described below. + +- The white color represents "not visited". + +- The gray color represents "not all child nodes visited". + +- The black color represents "all child nodes visited". + +Enlightened by tri-color marking, a bi-color marking method can be invented to solve all three traversal problems with one solution. + +The core idea is as follow. + +- Use a color to mark whether a node has been visited or not. Nodes yet to be visited are marked as white and visited nodes are marked as gray. + +- If we are visiting a white node, turn it into gray, and push its right child node, itself, and it's left child node into the stack respectively. + +- If we are visiting a gray node, print it. + +Implementation of pre-order and post-order traversal algorithms can be easily done by changing the order of pushing the child nodes into the stack. + +Reference: [LeetCode](https://github.com/azl397985856/leetcode/blob/master/thinkings/binary-tree-traversal.en.md) diff --git a/sorts/normal_distribution_quick_sort.md b/sorts/normal_distribution_quick_sort.md index 2a9f77b3ee95..c073f2cbc81c 100644 --- a/sorts/normal_distribution_quick_sort.md +++ b/sorts/normal_distribution_quick_sort.md @@ -1,15 +1,12 @@ # Normal Distribution QuickSort +QuickSort Algorithm where the pivot element is chosen randomly between first and last elements of the array, and the array elements are taken from Standard Normal Distribution. -Algorithm implementing QuickSort Algorithm where the pivot element is chosen randomly between first and last elements of the array and the array elements are taken from a Standard Normal Distribution. -This is different from the ordinary quicksort in the sense, that it applies more to real life problems , where elements usually follow a normal distribution. Also the pivot is randomized to make it a more generic one. +## Array elements +The array elements are taken from a Standard Normal Distribution, having mean = 0 and standard deviation = 1. -## Array Elements - -The array elements are taken from a Standard Normal Distribution , having mean = 0 and standard deviation 1. - -#### The code +### The code ```python @@ -27,7 +24,7 @@ The array elements are taken from a Standard Normal Distribution , having mean = ------ -#### The Distribution of the Array elements. +#### The distribution of the array elements ```python >>> mu, sigma = 0, 1 # mean and standard deviation @@ -35,41 +32,25 @@ The array elements are taken from a Standard Normal Distribution , having mean = >>> 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() - ``` +------ +![normal distribution large](https://upload.wikimedia.org/wikipedia/commons/thumb/2/25/The_Normal_Distribution.svg/1280px-The_Normal_Distribution.svg.png) ------ - - - - -![](https://www.mathsisfun.com/data/images/normal-distrubution-large.gif) - ---- - ---------------------- +------ --- +## Comparing the numbers of comparisons -## Plotting the function for Checking 'The Number of Comparisons' taking place between Normal Distribution QuickSort and Ordinary QuickSort +We can plot the function for Checking 'The Number of Comparisons' taking place between Normal Distribution QuickSort and Ordinary QuickSort: ```python ->>>import matplotlib.pyplot as plt +>>> import matplotlib.pyplot as plt - - # Normal Disrtibution QuickSort is red + # Normal Distribution 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 + # 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') >>> plt.show() - ``` - - ----- - - ------------------- From a7e4b2326a74067404339b1147c1ff40568ee4c0 Mon Sep 17 00:00:00 2001 From: Manuel Di Lullo <39048927+manueldilullo@users.noreply.github.com> Date: Sun, 1 May 2022 11:45:08 +0200 Subject: [PATCH 1780/2908] Add prefix conversions for strings (#5453) * First commit for add_prefix_conversion * Class names in CamelCase, str.format() to f-string * Fixed following pre-commit guidelines * solved issues with mypy and enum.Enum * Rename add_prefix_conversion.py to prefix_conversions_string.py Co-authored-by: John Law --- conversions/prefix_conversions_string.py | 121 +++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 conversions/prefix_conversions_string.py diff --git a/conversions/prefix_conversions_string.py b/conversions/prefix_conversions_string.py new file mode 100644 index 000000000000..3255eae6ff45 --- /dev/null +++ b/conversions/prefix_conversions_string.py @@ -0,0 +1,121 @@ +""" +* Author: Manuel Di Lullo (https://github.com/manueldilullo) +* Description: Convert a number to use the correct SI or Binary unit prefix. + +Inspired by prefix_conversion.py file in this repository by lance-pyles + +URL: https://en.wikipedia.org/wiki/Metric_prefix#List_of_SI_prefixes +URL: https://en.wikipedia.org/wiki/Binary_prefix +""" + +from __future__ import annotations + +from enum import Enum, unique +from typing import Type, TypeVar + +# Create a generic variable that can be 'Enum', or any subclass. +T = TypeVar("T", bound="Enum") + + +@unique +class BinaryUnit(Enum): + yotta = 80 + zetta = 70 + exa = 60 + peta = 50 + tera = 40 + giga = 30 + mega = 20 + kilo = 10 + + +@unique +class SIUnit(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 + + @classmethod + def get_positive(cls: Type[T]) -> dict: + """ + Returns a dictionary with only the elements of this enum + that has a positive value + >>> from itertools import islice + >>> positive = SIUnit.get_positive() + >>> inc = iter(positive.items()) + >>> dict(islice(inc, len(positive) // 2)) + {'yotta': 24, 'zetta': 21, 'exa': 18, 'peta': 15, 'tera': 12} + >>> dict(inc) + {'giga': 9, 'mega': 6, 'kilo': 3, 'hecto': 2, 'deca': 1} + """ + return {unit.name: unit.value for unit in cls if unit.value > 0} + + @classmethod + def get_negative(cls: Type[T]) -> dict: + """ + Returns a dictionary with only the elements of this enum + that has a negative value + @example + >>> from itertools import islice + >>> negative = SIUnit.get_negative() + >>> inc = iter(negative.items()) + >>> dict(islice(inc, len(negative) // 2)) + {'deci': -1, 'centi': -2, 'milli': -3, 'micro': -6, 'nano': -9} + >>> dict(inc) + {'pico': -12, 'femto': -15, 'atto': -18, 'zepto': -21, 'yocto': -24} + """ + return {unit.name: unit.value for unit in cls if unit.value < 0} + + +def add_si_prefix(value: float) -> str: + """ + Function that converts a number to his version with SI prefix + @input value (an integer) + @example: + >>> add_si_prefix(10000) + '10.0 kilo' + """ + prefixes = SIUnit.get_positive() if value > 0 else SIUnit.get_negative() + for name_prefix, value_prefix in prefixes.items(): + numerical_part = value / (10 ** value_prefix) + if numerical_part > 1: + return f"{str(numerical_part)} {name_prefix}" + return str(value) + + +def add_binary_prefix(value: float) -> str: + """ + Function that converts a number to his version with Binary prefix + @input value (an integer) + @example: + >>> add_binary_prefix(65536) + '64.0 kilo' + """ + for prefix in BinaryUnit: + numerical_part = value / (2 ** prefix.value) + if numerical_part > 1: + return f"{str(numerical_part)} {prefix.name}" + return str(value) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From e1ec661d4e368ceabd50e7ef3714c85dbe139c02 Mon Sep 17 00:00:00 2001 From: Shuangchi He <34329208+Yulv-git@users.noreply.github.com> Date: Sun, 1 May 2022 18:44:23 +0800 Subject: [PATCH 1781/2908] Fix some typos (#6113) * Fix some typos. * Update volume.py Co-authored-by: John Law --- ciphers/shuffled_shift_cipher.py | 2 +- data_structures/stacks/dijkstras_two_stack_algorithm.py | 2 +- divide_and_conquer/inversions.py | 2 +- maths/volume.py | 4 ++-- strings/manacher.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index 3b84f97f6769..714acd4b1afc 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -9,7 +9,7 @@ class ShuffledShiftCipher: 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 + The passcode is 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 diff --git a/data_structures/stacks/dijkstras_two_stack_algorithm.py b/data_structures/stacks/dijkstras_two_stack_algorithm.py index ba2ca92c7b5c..976c9a53c931 100644 --- a/data_structures/stacks/dijkstras_two_stack_algorithm.py +++ b/data_structures/stacks/dijkstras_two_stack_algorithm.py @@ -10,7 +10,7 @@ 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. + push it onto the operand stack. RULE 2: When an operator is encountered in the expression, push it onto the operator stack. diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py index b471456025be..e20d35daccbe 100644 --- a/divide_and_conquer/inversions.py +++ b/divide_and_conquer/inversions.py @@ -11,7 +11,7 @@ def count_inversions_bf(arr): """ - Counts the number of inversions using a a naive brute-force algorithm + Counts the number of inversions using a naive brute-force algorithm Parameters ---------- arr: arr: array-like, the list containing the items for which the number diff --git a/maths/volume.py b/maths/volume.py index b11995bab917..acaed65f4858 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -38,8 +38,8 @@ def vol_spheres_intersect( Calculate the volume of the intersection of two spheres. The intersection is composed by two spherical caps and therefore its volume is the - sum of the volumes of the spherical caps. First it calculates the heights (h1, h2) - of the the spherical caps, then the two volumes and it returns the sum. + sum of the volumes of the spherical caps. First, it calculates the heights (h1, h2) + of the spherical caps, then the two volumes and it returns the sum. The height formulas are h1 = (radius_1 - radius_2 + centers_distance) * (radius_1 + radius_2 - centers_distance) diff --git a/strings/manacher.py b/strings/manacher.py index e6ea71cde12f..c58c7c19ec44 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -88,7 +88,7 @@ def palindromic_string(input_string: str) -> str: 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 +previously we have calculated that a0==a2 (palindrome of center a1) a2==a4 (palindrome of center a3) a0==a6 (palindrome of center a3) From 7a394411b70bf7ca654e97f2d2663674ce1757c7 Mon Sep 17 00:00:00 2001 From: John Law Date: Sun, 1 May 2022 21:52:40 +0800 Subject: [PATCH 1782/2908] fix black at prefix string (#6122) * fix black at prefix string * Type -> type * Type unused --- conversions/prefix_conversions_string.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/conversions/prefix_conversions_string.py b/conversions/prefix_conversions_string.py index 3255eae6ff45..3851d7c8b993 100644 --- a/conversions/prefix_conversions_string.py +++ b/conversions/prefix_conversions_string.py @@ -11,7 +11,7 @@ from __future__ import annotations from enum import Enum, unique -from typing import Type, TypeVar +from typing import TypeVar # Create a generic variable that can be 'Enum', or any subclass. T = TypeVar("T", bound="Enum") @@ -53,7 +53,7 @@ class SIUnit(Enum): yocto = -24 @classmethod - def get_positive(cls: Type[T]) -> dict: + def get_positive(cls: type[T]) -> dict: """ Returns a dictionary with only the elements of this enum that has a positive value @@ -68,7 +68,7 @@ def get_positive(cls: Type[T]) -> dict: return {unit.name: unit.value for unit in cls if unit.value > 0} @classmethod - def get_negative(cls: Type[T]) -> dict: + def get_negative(cls: type[T]) -> dict: """ Returns a dictionary with only the elements of this enum that has a negative value @@ -94,7 +94,7 @@ def add_si_prefix(value: float) -> str: """ prefixes = SIUnit.get_positive() if value > 0 else SIUnit.get_negative() for name_prefix, value_prefix in prefixes.items(): - numerical_part = value / (10 ** value_prefix) + numerical_part = value / (10**value_prefix) if numerical_part > 1: return f"{str(numerical_part)} {name_prefix}" return str(value) @@ -109,7 +109,7 @@ def add_binary_prefix(value: float) -> str: '64.0 kilo' """ for prefix in BinaryUnit: - numerical_part = value / (2 ** prefix.value) + numerical_part = value / (2**prefix.value) if numerical_part > 1: return f"{str(numerical_part)} {prefix.name}" return str(value) From 4bd5494992a03a63aa0a1d55169a0171dee38468 Mon Sep 17 00:00:00 2001 From: Vineet Rao <28603906+VinWare@users.noreply.github.com> Date: Mon, 2 May 2022 19:28:12 +0530 Subject: [PATCH 1783/2908] Add solution to Problem 145 of Project Euler (#5464) * Solution to Problem 145 of Project Euler * Provided more descriptive filename * Update sol1.py Co-authored-by: John Law --- project_euler/problem_145/__init__.py | 0 project_euler/problem_145/sol1.py | 57 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 project_euler/problem_145/__init__.py create mode 100644 project_euler/problem_145/sol1.py diff --git a/project_euler/problem_145/__init__.py b/project_euler/problem_145/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py new file mode 100644 index 000000000000..82e2ea79bc25 --- /dev/null +++ b/project_euler/problem_145/sol1.py @@ -0,0 +1,57 @@ +""" +Project Euler problem 145: https://projecteuler.net/problem=145 +Author: Vineet Rao +Problem statement: + +Some positive integers n have the property that the sum [ n + reverse(n) ] +consists entirely of odd (decimal) digits. +For instance, 36 + 63 = 99 and 409 + 904 = 1313. +We will call such numbers reversible; so 36, 63, 409, and 904 are reversible. +Leading zeroes are not allowed in either n or reverse(n). + +There are 120 reversible numbers below one-thousand. + +How many reversible numbers are there below one-billion (10^9)? +""" + + +def odd_digits(num: int) -> bool: + """ + Check if the number passed as argument has only odd digits. + >>> odd_digits(123) + False + >>> odd_digits(135797531) + True + """ + num_str = str(num) + for i in ["0", "2", "4", "6", "8"]: + if i in num_str: + return False + return True + + +def solution(max_num: int = 1_000_000_000) -> int: + """ + To evaluate the solution, use solution() + >>> solution(1000) + 120 + >>> solution(1_000_000) + 18720 + >>> solution(10_000_000) + 68720 + """ + result = 0 + # All single digit numbers reverse to themselves, so their sums are even + # Therefore at least one digit in their sum is even + # Last digit cannot be 0, else it causes leading zeros in reverse + for num in range(11, max_num): + if num % 10 == 0: + continue + num_sum = num + int(str(num)[::-1]) + num_is_reversible = odd_digits(num_sum) + result += 1 if num_is_reversible else 0 + return result + + +if __name__ == "__main__": + print(f"{solution() = }") From 26f2df762248e947638ffdb61a9d7c9f5d5f0592 Mon Sep 17 00:00:00 2001 From: Kunwar Preet Singh <75082218+Enkryp@users.noreply.github.com> Date: Mon, 2 May 2022 19:42:18 +0530 Subject: [PATCH 1784/2908] Add sol for P104 Project Euler (#5257) * Hacktoberfest: added sol for P104 Project Euler * bot requests resolved * pre-commit * Update sol.py * Update sol.py * remove trailing zeroes * Update sol.py * Update sol.py * Update sol.py Co-authored-by: John Law --- project_euler/problem_104/__init__.py | 0 project_euler/problem_104/sol.py | 137 ++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 project_euler/problem_104/__init__.py create mode 100644 project_euler/problem_104/sol.py diff --git a/project_euler/problem_104/__init__.py b/project_euler/problem_104/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_104/sol.py b/project_euler/problem_104/sol.py new file mode 100644 index 000000000000..0818ac401c3a --- /dev/null +++ b/project_euler/problem_104/sol.py @@ -0,0 +1,137 @@ +""" +Project Euler Problem 104 : https://projecteuler.net/problem=104 + +The Fibonacci sequence is defined by the recurrence relation: + +Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. +It turns out that F541, which contains 113 digits, is the first Fibonacci number +for which the last nine digits are 1-9 pandigital (contain all the digits 1 to 9, +but not necessarily in order). And F2749, which contains 575 digits, is the first +Fibonacci number for which the first nine digits are 1-9 pandigital. + +Given that Fk is the first Fibonacci number for which the first nine digits AND +the last nine digits are 1-9 pandigital, find k. +""" + + +def check(number: int) -> bool: + """ + Takes a number and checks if it is pandigital both from start and end + + + >>> check(123456789987654321) + True + + >>> check(120000987654321) + False + + >>> check(1234567895765677987654321) + True + + """ + + check_last = [0] * 11 + check_front = [0] * 11 + + # mark last 9 numbers + for x in range(9): + check_last[int(number % 10)] = 1 + number = number // 10 + # flag + f = True + + # check last 9 numbers for pandigitality + + for x in range(9): + if not check_last[x + 1]: + f = False + if not f: + return f + + # mark first 9 numbers + number = int(str(number)[:9]) + + for x in range(9): + check_front[int(number % 10)] = 1 + number = number // 10 + + # check first 9 numbers for pandigitality + + for x in range(9): + if not check_front[x + 1]: + f = False + return f + + +def check1(number: int) -> bool: + """ + Takes a number and checks if it is pandigital from END + + >>> check1(123456789987654321) + True + + >>> check1(120000987654321) + True + + >>> check1(12345678957656779870004321) + False + + """ + + check_last = [0] * 11 + + # mark last 9 numbers + for x in range(9): + check_last[int(number % 10)] = 1 + number = number // 10 + # flag + f = True + + # check last 9 numbers for pandigitality + + for x in range(9): + if not check_last[x + 1]: + f = False + return f + + +def solution() -> int: + """ + Outputs the answer is the least Fibonacci number pandigital from both sides. + >>> solution() + 329468 + """ + + a = 1 + b = 1 + c = 2 + # temporary Fibonacci numbers + + a1 = 1 + b1 = 1 + c1 = 2 + # temporary Fibonacci numbers mod 1e9 + + # mod m=1e9, done for fast optimisation + tocheck = [0] * 1000000 + m = 1000000000 + + for x in range(1000000): + c1 = (a1 + b1) % m + a1 = b1 % m + b1 = c1 % m + if check1(b1): + tocheck[x + 3] = 1 + + for x in range(1000000): + c = a + b + a = b + b = c + # perform check only if in tocheck + if tocheck[x + 3] and check(b): + return x + 3 # first 2 already done + return -1 + + +if __name__ == "__main__": + print(f"{solution() = }") From 8226636ea321d8fddae55460cd3c8a25b537160e Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 May 2022 18:07:29 +0200 Subject: [PATCH 1785/2908] Add the Horn-Schunck algorithm (#5333) * Added implementation of the Horn-Schunck algorithm * Cleaner variable names * added doctests * Fix doctest * Update horn_schunck.py * Update horn_schunck.py * Update horn_schunck.py * Update horn_schunck.py Co-authored-by: John Law --- computer_vision/horn_schunck.py | 130 ++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 computer_vision/horn_schunck.py diff --git a/computer_vision/horn_schunck.py b/computer_vision/horn_schunck.py new file mode 100644 index 000000000000..1428487d051b --- /dev/null +++ b/computer_vision/horn_schunck.py @@ -0,0 +1,130 @@ +""" + The Horn-Schunck method estimates the optical flow for every single pixel of + a sequence of images. + It works by assuming brightness constancy between two consecutive frames + and smoothness in the optical flow. + + Useful resources: + Wikipedia: https://en.wikipedia.org/wiki/Horn%E2%80%93Schunck_method + Paper: http://image.diku.dk/imagecanon/material/HornSchunckOptical_Flow.pdf +""" + +import numpy as np +from scipy.ndimage.filters import convolve +from typing_extensions import SupportsIndex + + +def warp( + image: np.ndarray, horizontal_flow: np.ndarray, vertical_flow: np.ndarray +) -> np.ndarray: + """ + Warps the pixels of an image into a new image using the horizontal and vertical + flows. + Pixels that are warped from an invalid location are set to 0. + + Parameters: + image: Grayscale image + horizontal_flow: Horizontal flow + vertical_flow: Vertical flow + + Returns: Warped image + + >>> warp(np.array([[0, 1, 2], [0, 3, 0], [2, 2, 2]]), \ + np.array([[0, 1, -1], [-1, 0, 0], [1, 1, 1]]), \ + np.array([[0, 0, 0], [0, 1, 0], [0, 0, 1]])) + array([[0, 0, 0], + [3, 1, 0], + [0, 2, 3]]) + """ + flow = np.stack((horizontal_flow, vertical_flow), 2) + + # Create a grid of all pixel coordinates and subtract the flow to get the + # target pixels coordinates + grid = np.stack( + np.meshgrid(np.arange(0, image.shape[1]), np.arange(0, image.shape[0])), 2 + ) + grid = np.round(grid - flow).astype(np.int32) + + # Find the locations outside of the original image + invalid = (grid < 0) | (grid >= np.array([image.shape[1], image.shape[0]])) + grid[invalid] = 0 + + warped = image[grid[:, :, 1], grid[:, :, 0]] + + # Set pixels at invalid locations to 0 + warped[invalid[:, :, 0] | invalid[:, :, 1]] = 0 + + return warped + + +def horn_schunck( + image0: np.ndarray, + image1: np.ndarray, + num_iter: SupportsIndex, + alpha: float | None = None, +) -> tuple[np.ndarray, np.ndarray]: + """ + This function performs the Horn-Schunck algorithm and returns the estimated + optical flow. It is assumed that the input images are grayscale and + normalized to be in [0, 1]. + + Parameters: + image0: First image of the sequence + image1: Second image of the sequence + alpha: Regularization constant + num_iter: Number of iterations performed + + Returns: estimated horizontal & vertical flow + + >>> np.round(horn_schunck(np.array([[0, 0, 2], [0, 0, 2]]), \ + np.array([[0, 2, 0], [0, 2, 0]]), alpha=0.1, num_iter=110)).\ + astype(np.int32) + array([[[ 0, -1, -1], + [ 0, -1, -1]], + + [[ 0, 0, 0], + [ 0, 0, 0]]], dtype=int32) + """ + if alpha is None: + alpha = 0.1 + + # Initialize flow + horizontal_flow = np.zeros_like(image0) + vertical_flow = np.zeros_like(image0) + + # Prepare kernels for the calculation of the derivatives and the average velocity + kernel_x = np.array([[-1, 1], [-1, 1]]) * 0.25 + kernel_y = np.array([[-1, -1], [1, 1]]) * 0.25 + kernel_t = np.array([[1, 1], [1, 1]]) * 0.25 + kernel_laplacian = np.array( + [[1 / 12, 1 / 6, 1 / 12], [1 / 6, 0, 1 / 6], [1 / 12, 1 / 6, 1 / 12]] + ) + + # Iteratively refine the flow + for _ in range(num_iter): + warped_image = warp(image0, horizontal_flow, vertical_flow) + derivative_x = convolve(warped_image, kernel_x) + convolve(image1, kernel_x) + derivative_y = convolve(warped_image, kernel_y) + convolve(image1, kernel_y) + derivative_t = convolve(warped_image, kernel_t) + convolve(image1, -kernel_t) + + avg_horizontal_velocity = convolve(horizontal_flow, kernel_laplacian) + avg_vertical_velocity = convolve(vertical_flow, kernel_laplacian) + + # This updates the flow as proposed in the paper (Step 12) + update = ( + derivative_x * avg_horizontal_velocity + + derivative_y * avg_vertical_velocity + + derivative_t + ) + update = update / (alpha**2 + derivative_x**2 + derivative_y**2) + + horizontal_flow = avg_horizontal_velocity - derivative_x * update + vertical_flow = avg_vertical_velocity - derivative_y * update + + return horizontal_flow, vertical_flow + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 3bff196981312e41ba9dac91e1bd971b7120726c Mon Sep 17 00:00:00 2001 From: KerimovEmil Date: Wed, 11 May 2022 23:28:45 -0400 Subject: [PATCH 1786/2908] Fix some typos in solution 1 of euler 686 (#6112) While reading this code I noticed some typos in the doc strings and wanted to fix them. --- project_euler/problem_686/sol1.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/project_euler/problem_686/sol1.py b/project_euler/problem_686/sol1.py index 3b6bdb655170..99a317cd1451 100644 --- a/project_euler/problem_686/sol1.py +++ b/project_euler/problem_686/sol1.py @@ -27,7 +27,7 @@ def log_difference(number: int) -> float: Computing 2^90 is time consuming. Hence we find log(2^90) = 90*log(2) = 27.092699609758302 But we require only the decimal part to determine whether the power starts with 123. - SO we just return the decimal part of the log product. + So we just return the decimal part of the log product. Therefore we return 0.092699609758302 >>> log_difference(90) @@ -57,14 +57,14 @@ def solution(number: int = 678910) -> int: So if number = 10, then solution returns 2515 as we observe from above series. - Wwe will define a lowerbound and upperbound. + We will define a lowerbound and upperbound. lowerbound = log(1.23), upperbound = log(1.24) because we need to find the powers that yield 123 as starting digits. log(1.23) = 0.08990511143939792, log(1,24) = 0.09342168516223506. We use 1.23 and not 12.3 or 123, because log(1.23) yields only decimal value which is less than 1. - log(12.3) will be same decimal vale but 1 added to it + log(12.3) will be same decimal value but 1 added to it which is log(12.3) = 1.093421685162235. We observe that decimal value remains same no matter 1.23 or 12.3 Since we use the function log_difference(), @@ -87,7 +87,7 @@ def solution(number: int = 678910) -> int: Hence to optimize the algorithm we will increment by 196 or 93 depending upon the log_difference() value. - Lets take for example 90. + Let's take for example 90. Since 90 is the first power leading to staring digits as 123, we will increment iterator by 196. Because the difference between any two powers leading to 123 @@ -99,7 +99,7 @@ def solution(number: int = 678910) -> int: The iterator will now become 379, which is the next power leading to 123 as starting digits. - Lets take 1060. We increment by 196, we get 1256. + Let's take 1060. We increment by 196, we get 1256. log_difference(1256) = 0.09367455396034, Which is greater than upperbound hence we increment by 93. Now iterator is 1349. log_difference(1349) = 0.08946415071057 which is less than lowerbound. @@ -107,7 +107,7 @@ def solution(number: int = 678910) -> int: Conditions are as follows: - 1) If we find a power, whose log_difference() is in the range of + 1) If we find a power whose log_difference() is in the range of lower and upperbound, we will increment by 196. which implies that the power is a number which will lead to 123 as starting digits. 2) If we find a power, whose log_difference() is greater than or equal upperbound, From e23c18fb5cb34d51b69e2840c304ade597163085 Mon Sep 17 00:00:00 2001 From: Omkaar <79257339+Pysics@users.noreply.github.com> Date: Thu, 12 May 2022 09:00:00 +0530 Subject: [PATCH 1787/2908] Fix typos (#6127) --- maths/integration_by_simpson_approx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/integration_by_simpson_approx.py b/maths/integration_by_simpson_approx.py index da0e1cffde02..feb77440dd2f 100644 --- a/maths/integration_by_simpson_approx.py +++ b/maths/integration_by_simpson_approx.py @@ -40,7 +40,7 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo Args: function : the function which's integration is desired a : the lower limit of integration - b : upper limit of integraion + b : upper limit of integration precision : precision of the result,error required default is 4 Returns: @@ -106,7 +106,7 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo 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 + # just applying the formula of simpson for approximate integration written in # mentioned article in first comment of this file and above this function h = (b - a) / N_STEPS From 533eea5afa916fbe1d0db6db8da76c68b2928ca0 Mon Sep 17 00:00:00 2001 From: Leoriem-code <73761711+Leoriem-code@users.noreply.github.com> Date: Thu, 12 May 2022 05:35:56 +0200 Subject: [PATCH 1788/2908] fix mypy annotations for arithmetic_analysis (#6040) * fixed mypy annotations for arithmetic_analysis * shortened numpy references --- arithmetic_analysis/gaussian_elimination.py | 14 ++++++++++---- arithmetic_analysis/in_static_equilibrium.py | 9 +++++---- arithmetic_analysis/jacobi_iteration_method.py | 14 +++++++++----- arithmetic_analysis/lu_decomposition.py | 6 +++++- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/arithmetic_analysis/gaussian_elimination.py b/arithmetic_analysis/gaussian_elimination.py index 2dada4fbf9b1..89ed3b323d03 100644 --- a/arithmetic_analysis/gaussian_elimination.py +++ b/arithmetic_analysis/gaussian_elimination.py @@ -5,9 +5,13 @@ import numpy as np +from numpy import float64 +from numpy.typing import NDArray -def retroactive_resolution(coefficients: np.matrix, vector: np.ndarray) -> np.ndarray: +def retroactive_resolution( + coefficients: NDArray[float64], vector: NDArray[float64] +) -> NDArray[float64]: """ This function performs a retroactive linear system resolution for triangular matrix @@ -27,7 +31,7 @@ def retroactive_resolution(coefficients: np.matrix, vector: np.ndarray) -> np.nd rows, columns = np.shape(coefficients) - x = np.zeros((rows, 1), dtype=float) + x: NDArray[float64] = np.zeros((rows, 1), dtype=float) for row in reversed(range(rows)): sum = 0 for col in range(row + 1, columns): @@ -38,7 +42,9 @@ def retroactive_resolution(coefficients: np.matrix, vector: np.ndarray) -> np.nd return x -def gaussian_elimination(coefficients: np.matrix, vector: np.ndarray) -> np.ndarray: +def gaussian_elimination( + coefficients: NDArray[float64], vector: NDArray[float64] +) -> NDArray[float64]: """ This function performs Gaussian elimination method @@ -60,7 +66,7 @@ def gaussian_elimination(coefficients: np.matrix, vector: np.ndarray) -> np.ndar return np.array((), dtype=float) # augmented matrix - augmented_mat = np.concatenate((coefficients, vector), axis=1) + augmented_mat: NDArray[float64] = np.concatenate((coefficients, vector), axis=1) augmented_mat = augmented_mat.astype("float64") # scale the matrix leaving it triangular diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index ed0d1eb98cf3..d762a376f577 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -3,7 +3,8 @@ """ from __future__ import annotations -from numpy import array, cos, cross, ndarray, radians, sin +from numpy import array, cos, cross, float64, radians, sin +from numpy.typing import NDArray def polar_force( @@ -27,7 +28,7 @@ def polar_force( def in_static_equilibrium( - forces: ndarray, location: ndarray, eps: float = 10**-1 + forces: NDArray[float64], location: NDArray[float64], eps: float = 10**-1 ) -> bool: """ Check if a system is in equilibrium. @@ -46,7 +47,7 @@ def in_static_equilibrium( False """ # summation of moments is zero - moments: ndarray = cross(location, forces) + moments: NDArray[float64] = cross(location, forces) sum_moments: float = sum(moments) return abs(sum_moments) < eps @@ -61,7 +62,7 @@ def in_static_equilibrium( ] ) - location = array([[0, 0], [0, 0], [0, 0]]) + location: NDArray[float64] = array([[0, 0], [0, 0], [0, 0]]) assert in_static_equilibrium(forces, location) diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/arithmetic_analysis/jacobi_iteration_method.py index 6674824255a1..4336aaa91623 100644 --- a/arithmetic_analysis/jacobi_iteration_method.py +++ b/arithmetic_analysis/jacobi_iteration_method.py @@ -4,13 +4,15 @@ from __future__ import annotations import numpy as np +from numpy import float64 +from numpy.typing import NDArray # Method to find solution of system of linear equations def jacobi_iteration_method( - coefficient_matrix: np.ndarray, - constant_matrix: np.ndarray, - init_val: list, + coefficient_matrix: NDArray[float64], + constant_matrix: NDArray[float64], + init_val: list[int], iterations: int, ) -> list[float]: """ @@ -99,7 +101,9 @@ def jacobi_iteration_method( if iterations <= 0: raise ValueError("Iterations must be at least 1") - table = np.concatenate((coefficient_matrix, constant_matrix), axis=1) + table: NDArray[float64] = np.concatenate( + (coefficient_matrix, constant_matrix), axis=1 + ) rows, cols = table.shape @@ -125,7 +129,7 @@ def jacobi_iteration_method( # Checks if the given matrix is strictly diagonally dominant -def strictly_diagonally_dominant(table: np.ndarray) -> bool: +def strictly_diagonally_dominant(table: NDArray[float64]) -> bool: """ >>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 4, -4]]) >>> strictly_diagonally_dominant(table) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index b488b1bb3211..371f7b166b2e 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -6,9 +6,13 @@ from __future__ import annotations import numpy as np +import numpy.typing as NDArray +from numpy import float64 -def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray]: +def lower_upper_decomposition( + table: NDArray[float64], +) -> tuple[NDArray[float64], NDArray[float64]]: """Lower-Upper (LU) Decomposition Example: From 562cf31a9a9d448b761cdc30df03fb7b526966d9 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 12 May 2022 06:48:04 +0300 Subject: [PATCH 1789/2908] Improve Project Euler problem 074 solution 2 (#5803) * Fix statement * Improve solution * Fix * Add tests --- project_euler/problem_074/sol2.py | 197 +++++++++++++++++------------- 1 file changed, 109 insertions(+), 88 deletions(-) diff --git a/project_euler/problem_074/sol2.py b/project_euler/problem_074/sol2.py index 55e67c6b98dd..d76bb014d629 100644 --- a/project_euler/problem_074/sol2.py +++ b/project_euler/problem_074/sol2.py @@ -1,122 +1,143 @@ """ - Project Euler Problem 074: https://projecteuler.net/problem=74 +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. +The number 145 is well known for the property that the sum of the factorial of its +digits is equal to 145: - 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. +1! + 4! + 5! = 1 + 24 + 120 = 145 - The request is to find how many numbers less than one million - produce a chain with exactly 60 non repeating items. +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: - 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. -""" +169 → 363601 → 1454 → 169 +871 → 45361 → 871 +872 → 45362 → 872 -factorial_cache: dict[int, int] = {} -factorial_sum_cache: dict[int, int] = {} +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) -def factorial(a: int) -> int: - """Returns the factorial of the input a - >>> factorial(5) - 120 +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. - >>> factorial(6) - 720 +How many chains, with a starting number below one million, contain exactly sixty +non-repeating terms? - >>> factorial(0) - 1 - """ - - # The factorial function is not defined for negative numbers - if a < 0: - raise ValueError("Invalid negative input!", a) +Solution approach: +This solution simply consists in a loop that generates the chains of non repeating +items using the cached sizes of the previous chains. +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. +""" +from math import factorial - if a in factorial_cache: - return factorial_cache[a] +DIGIT_FACTORIAL: dict[str, int] = {str(digit): factorial(digit) for digit in range(10)} - # The case of 0! is handled separately - if a == 0: - factorial_cache[a] = 1 - else: - # use a temporary support variable to store the computation - temporary_number = a - temporary_computation = 1 - while temporary_number > 0: - temporary_computation *= temporary_number - temporary_number -= 1 +def digit_factorial_sum(number: int) -> int: + """ + Function to perform the sum of the factorial of all the digits in number - factorial_cache[a] = temporary_computation - return factorial_cache[a] + >>> digit_factorial_sum(69.0) + Traceback (most recent call last): + ... + TypeError: Parameter number must be int + >>> digit_factorial_sum(-1) + Traceback (most recent call last): + ... + ValueError: Parameter number must be greater than or equal to 0 -def factorial_sum(a: int) -> int: - """Function to perform the sum of the factorial - of all the digits in a + >>> digit_factorial_sum(0) + 1 - >>> factorial_sum(69) + >>> digit_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 - - """ 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)) - factorial_sum_cache[a] = fact_sum - return fact_sum + if not isinstance(number, int): + raise TypeError("Parameter number must be int") + + if number < 0: + raise ValueError("Parameter number must be greater than or equal to 0") + + # Converts number in string to iterate on its digits and adds its factorial. + return sum(DIGIT_FACTORIAL[digit] for digit in str(number)) 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(10, 1000) - 26 """ + Returns the number of numbers below number_limit that produce chains with exactly + chain_length non repeating elements. - # the counter for the chains with the exact desired length - chain_counter = 0 - - for i in range(1, number_limit + 1): + >>> solution(10.0, 1000) + Traceback (most recent call last): + ... + TypeError: Parameters chain_length and number_limit must be int - # The temporary list will contain the elements of the chain - chain_set = {i} - len_chain_set = 1 - last_chain_element = i + >>> solution(10, 1000.0) + Traceback (most recent call last): + ... + TypeError: Parameters chain_length and number_limit must be int - # The new element of the chain - new_chain_element = factorial_sum(last_chain_element) + >>> solution(0, 1000) + Traceback (most recent call last): + ... + ValueError: Parameters chain_length and number_limit must be greater than 0 - # Stop computing the chain when you find a repeating item - # or the length it greater then the desired one. + >>> solution(10, 0) + Traceback (most recent call last): + ... + ValueError: Parameters chain_length and number_limit must be greater than 0 - while new_chain_element not in chain_set and len_chain_set <= chain_length: - chain_set.add(new_chain_element) + >>> solution(10, 1000) + 26 + """ - len_chain_set += 1 - last_chain_element = new_chain_element - new_chain_element = factorial_sum(last_chain_element) + if not isinstance(chain_length, int) or not isinstance(number_limit, int): + raise TypeError("Parameters chain_length and number_limit must be int") - # 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 + if chain_length <= 0 or number_limit <= 0: + raise ValueError( + "Parameters chain_length and number_limit must be greater than 0" + ) - return chain_counter + # the counter for the chains with the exact desired length + chains_counter = 0 + # the cached sizes of the previous chains + chain_sets_lengths: dict[int, int] = {} + + for start_chain_element in range(1, number_limit): + + # The temporary set will contain the elements of the chain + chain_set = set() + chain_set_length = 0 + + # Stop computing the chain when you find a cached size, a repeating item or the + # length is greater then the desired one. + chain_element = start_chain_element + while ( + chain_element not in chain_sets_lengths + and chain_element not in chain_set + and chain_set_length <= chain_length + ): + chain_set.add(chain_element) + chain_set_length += 1 + chain_element = digit_factorial_sum(chain_element) + + if chain_element in chain_sets_lengths: + chain_set_length += chain_sets_lengths[chain_element] + + chain_sets_lengths[start_chain_element] = chain_set_length + + # If chain contains the exact amount of elements increase the counter + if chain_set_length == chain_length: + chains_counter += 1 + + return chains_counter if __name__ == "__main__": From bbb88bb5c261085ff23bce2b3c17266ebfa7b087 Mon Sep 17 00:00:00 2001 From: eee555 <50390200+eee555@users.noreply.github.com> Date: Fri, 13 May 2022 04:28:51 +0800 Subject: [PATCH 1790/2908] Fix bug in bucket_sort.py (#6005) --- sorts/bucket_sort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 58242a1cb1f8..7bcbe61a4526 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -54,8 +54,8 @@ def bucket_sort(my_list: list) -> list: bucket_count = int(max_value - min_value) + 1 buckets: list[list] = [[] for _ in range(bucket_count)] - for i in range(len(my_list)): - buckets[(int(my_list[i] - min_value) // bucket_count)].append(my_list[i]) + for i in my_list: + buckets[int(i - min_value)].append(i) return [v for bucket in buckets for v in sorted(bucket)] From e95ecfaf27c545391bdb7a2d1d8948943a40f828 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj Date: Fri, 13 May 2022 11:25:53 +0530 Subject: [PATCH 1791/2908] Add missing type annotations for `strings` directory (#5817) * Type annotations for `strings/autocomplete_using_trie.py` * Update autocomplete_using_trie.py * Update detecting_english_programmatically.py * Update detecting_english_programmatically.py * Update frequency_finder.py * Update frequency_finder.py * Update frequency_finder.py * Update word_occurrence.py * Update frequency_finder.py * Update z_function.py * Update z_function.py * Update frequency_finder.py --- strings/autocomplete_using_trie.py | 28 +++--- strings/detecting_english_programmatically.py | 53 ++++++----- strings/frequency_finder.py | 94 +++++++------------ strings/word_occurrence.py | 3 +- strings/z_function.py | 6 +- 5 files changed, 82 insertions(+), 102 deletions(-) diff --git a/strings/autocomplete_using_trie.py b/strings/autocomplete_using_trie.py index 8aa0dc223680..758260292a30 100644 --- a/strings/autocomplete_using_trie.py +++ b/strings/autocomplete_using_trie.py @@ -1,11 +1,13 @@ +from __future__ import annotations + END = "#" class Trie: - def __init__(self): - self._trie = {} + def __init__(self) -> None: + self._trie: dict = {} - def insert_word(self, text): + def insert_word(self, text: str) -> None: trie = self._trie for char in text: if char not in trie: @@ -13,7 +15,7 @@ def insert_word(self, text): trie = trie[char] trie[END] = True - def find_word(self, prefix): + def find_word(self, prefix: str) -> tuple | list: trie = self._trie for char in prefix: if char in trie: @@ -22,7 +24,7 @@ def find_word(self, prefix): return [] return self._elements(trie) - def _elements(self, d): + def _elements(self, d: dict) -> tuple: result = [] for c, v in d.items(): if c == END: @@ -39,26 +41,28 @@ def _elements(self, d): trie.insert_word(word) -def autocomplete_using_trie(s): +def autocomplete_using_trie(string: str) -> tuple: """ >>> trie = Trie() >>> for word in words: ... trie.insert_word(word) ... >>> matches = autocomplete_using_trie("de") - - "detergent " in matches + >>> "detergent " in matches True - "dog " in matches + >>> "dog " in matches False """ - suffixes = trie.find_word(s) - return tuple(s + w for w in suffixes) + suffixes = trie.find_word(string) + return tuple(string + word for word in suffixes) -def main(): +def main() -> None: print(autocomplete_using_trie("de")) if __name__ == "__main__": + import doctest + + doctest.testmod() main() diff --git a/strings/detecting_english_programmatically.py b/strings/detecting_english_programmatically.py index 44fb7191866b..aa18db21027a 100644 --- a/strings/detecting_english_programmatically.py +++ b/strings/detecting_english_programmatically.py @@ -4,55 +4,56 @@ LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + " \t\n" -def loadDictionary(): +def load_dictionary() -> dict[str, None]: path = os.path.split(os.path.realpath(__file__)) - englishWords = {} - with open(path[0] + "/dictionary.txt") as dictionaryFile: - for word in dictionaryFile.read().split("\n"): - englishWords[word] = None - return englishWords + english_words: dict[str, None] = {} + with open(path[0] + "/dictionary.txt") as dictionary_file: + for word in dictionary_file.read().split("\n"): + english_words[word] = None + return english_words -ENGLISH_WORDS = loadDictionary() +ENGLISH_WORDS = load_dictionary() -def getEnglishCount(message): +def get_english_count(message: str) -> float: message = message.upper() - message = removeNonLetters(message) - possibleWords = message.split() + message = remove_non_letters(message) + possible_words = message.split() - if possibleWords == []: + if possible_words == []: return 0.0 matches = 0 - for word in possibleWords: + for word in possible_words: if word in ENGLISH_WORDS: matches += 1 - return float(matches) / len(possibleWords) + return float(matches) / len(possible_words) -def removeNonLetters(message): - lettersOnly = [] +def remove_non_letters(message: str) -> str: + letters_only = [] for symbol in message: if symbol in LETTERS_AND_SPACE: - lettersOnly.append(symbol) - return "".join(lettersOnly) + letters_only.append(symbol) + return "".join(letters_only) -def isEnglish(message, wordPercentage=20, letterPercentage=85): +def is_english( + message: str, word_percentage: int = 20, letter_percentage: int = 85 +) -> bool: """ - >>> isEnglish('Hello World') + >>> is_english('Hello World') True - - >>> isEnglish('llold HorWd') + >>> is_english('llold HorWd') False """ - wordsMatch = getEnglishCount(message) * 100 >= wordPercentage - numLetters = len(removeNonLetters(message)) - messageLettersPercentage = (float(numLetters) / len(message)) * 100 - lettersMatch = messageLettersPercentage >= letterPercentage - return wordsMatch and lettersMatch + words_match = get_english_count(message) * 100 >= word_percentage + num_letters = len(remove_non_letters(message)) + message_letters_percentage = (float(num_letters) / len(message)) * 100 + letters_match = message_letters_percentage >= letter_percentage + return words_match and letters_match if __name__ == "__main__": diff --git a/strings/frequency_finder.py b/strings/frequency_finder.py index 48760a9deb09..7024be17b8ab 100644 --- a/strings/frequency_finder.py +++ b/strings/frequency_finder.py @@ -1,7 +1,9 @@ # Frequency Finder +import string + # frequency taken from http://en.wikipedia.org/wiki/Letter_frequency -englishLetterFreq = { +english_letter_freq = { "E": 12.70, "T": 9.06, "A": 8.17, @@ -33,85 +35,57 @@ 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, - } +def get_letter_count(message: str) -> dict[str, int]: + letter_count = {letter: 0 for letter in string.ascii_uppercase} for letter in message.upper(): if letter in LETTERS: - letterCount[letter] += 1 + letter_count[letter] += 1 - return letterCount + return letter_count -def getItemAtIndexZero(x): +def get_item_at_index_zero(x: tuple) -> str: return x[0] -def getFrequencyOrder(message): - letterToFreq = getLetterCount(message) - freqToLetter = {} +def get_frequency_order(message: str) -> str: + letter_to_freq = get_letter_count(message) + freq_to_letter: dict[int, list[str]] = { + freq: [] for letter, freq in letter_to_freq.items() + } for letter in LETTERS: - if letterToFreq[letter] not in freqToLetter: - freqToLetter[letterToFreq[letter]] = [letter] - else: - freqToLetter[letterToFreq[letter]].append(letter) + freq_to_letter[letter_to_freq[letter]].append(letter) + + freq_to_letter_str: dict[int, str] = {} - for freq in freqToLetter: - freqToLetter[freq].sort(key=ETAOIN.find, reverse=True) - freqToLetter[freq] = "".join(freqToLetter[freq]) + for freq in freq_to_letter: + freq_to_letter[freq].sort(key=ETAOIN.find, reverse=True) + freq_to_letter_str[freq] = "".join(freq_to_letter[freq]) - freqPairs = list(freqToLetter.items()) - freqPairs.sort(key=getItemAtIndexZero, reverse=True) + freq_pairs = list(freq_to_letter_str.items()) + freq_pairs.sort(key=get_item_at_index_zero, reverse=True) - freqOrder = [] - for freqPair in freqPairs: - freqOrder.append(freqPair[1]) + freq_order: list[str] = [freq_pair[1] for freq_pair in freq_pairs] - return "".join(freqOrder) + return "".join(freq_order) -def englishFreqMatchScore(message): +def english_freq_match_score(message: str) -> int: """ - >>> englishFreqMatchScore('Hello World') + >>> english_freq_match_score('Hello World') 1 """ - freqOrder = getFrequencyOrder(message) - matchScore = 0 - for commonLetter in ETAOIN[:6]: - if commonLetter in freqOrder[:6]: - matchScore += 1 + freq_order = get_frequency_order(message) + match_score = 0 + for common_letter in ETAOIN[:6]: + if common_letter in freq_order[:6]: + match_score += 1 - for uncommonLetter in ETAOIN[-6:]: - if uncommonLetter in freqOrder[-6:]: - matchScore += 1 + for uncommon_letter in ETAOIN[-6:]: + if uncommon_letter in freq_order[-6:]: + match_score += 1 - return matchScore + return match_score if __name__ == "__main__": diff --git a/strings/word_occurrence.py b/strings/word_occurrence.py index 4acfa41adf11..4e0b3ff34ccf 100644 --- a/strings/word_occurrence.py +++ b/strings/word_occurrence.py @@ -1,6 +1,7 @@ # Created by sarathkaul on 17/11/19 # Modified by Arkadip Bhattacharya(@darkmatter18) on 20/04/2020 from collections import defaultdict +from typing import DefaultDict def word_occurence(sentence: str) -> dict: @@ -14,7 +15,7 @@ def word_occurence(sentence: str) -> dict: >>> dict(word_occurence("Two spaces")) {'Two': 1, 'spaces': 1} """ - occurrence: dict = defaultdict(int) + occurrence: DefaultDict[str, int] = defaultdict(int) # Creating a dictionary containing count of each word for word in sentence.split(): occurrence[word] += 1 diff --git a/strings/z_function.py b/strings/z_function.py index d8d823a37efb..e77ba8dab5df 100644 --- a/strings/z_function.py +++ b/strings/z_function.py @@ -10,7 +10,7 @@ """ -def z_function(input_str: str) -> list: +def z_function(input_str: str) -> list[int]: """ For the given string this function computes value for each index, which represents the maximal length substring starting from the index @@ -27,7 +27,7 @@ def z_function(input_str: str) -> list: >>> z_function("zxxzxxz") [0, 0, 0, 4, 0, 0, 1] """ - z_result = [0] * len(input_str) + z_result = [0 for i in range(len(input_str))] # initialize interval's left pointer and right pointer left_pointer, right_pointer = 0, 0 @@ -49,7 +49,7 @@ def z_function(input_str: str) -> list: return z_result -def go_next(i, z_result, s): +def go_next(i: int, z_result: list[int], s: str) -> bool: """ Check if we have to move forward to the next characters or not """ From dbee5f072f68c57bce3443e5ed07fe496ba9d76d Mon Sep 17 00:00:00 2001 From: Omkaar <79257339+Pysics@users.noreply.github.com> Date: Fri, 13 May 2022 18:21:44 +0530 Subject: [PATCH 1792/2908] Improve code on f-strings and brevity (#6126) * Update strassen_matrix_multiplication.py * Update matrix_operation.py * Update enigma_machine2.py * Update enigma_machine.py * Update enigma_machine2.py * Update rod_cutting.py * Update external_sort.py * Update sol1.py * Update hill_cipher.py * Update prime_numbers.py * Update integration_by_simpson_approx.py --- ciphers/enigma_machine2.py | 6 +++--- ciphers/hill_cipher.py | 2 +- data_structures/hashing/number_theory/prime_numbers.py | 2 +- divide_and_conquer/strassen_matrix_multiplication.py | 2 +- dynamic_programming/rod_cutting.py | 2 +- hashes/enigma_machine.py | 2 +- maths/integration_by_simpson_approx.py | 10 +++------- matrix/matrix_operation.py | 2 +- project_euler/problem_067/sol1.py | 2 +- sorts/external_sort.py | 2 +- 10 files changed, 14 insertions(+), 18 deletions(-) diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 9252dd0edbf7..70f84752d55b 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -94,15 +94,15 @@ def _validator( 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}" + "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})" + "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})" + "Third rotor position is not within range of 1..26 (" f"{rotorpos3})" ) # Validates string and returns dict diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index bc8f5b41b624..d8e436e92c56 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -62,7 +62,7 @@ class HillCipher: # take x and return x % len(key_string) modulus = numpy.vectorize(lambda x: x % 36) - to_int = numpy.vectorize(lambda x: round(x)) + to_int = numpy.vectorize(round) def __init__(self, encrypt_key: numpy.ndarray) -> None: """ diff --git a/data_structures/hashing/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py index db4d40f475b2..bf614e7d48df 100644 --- a/data_structures/hashing/number_theory/prime_numbers.py +++ b/data_structures/hashing/number_theory/prime_numbers.py @@ -14,7 +14,7 @@ def check_prime(number): elif number == special_non_primes[-1]: return 3 - return all([number % i for i in range(2, number)]) + return all(number % i for i in range(2, number)) def next_prime(value, factor=1, **kwargs): diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index ca10e04abcbc..17efcfc7c8ee 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -114,7 +114,7 @@ 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. \n" + "Unable to multiply these matrices, please check the dimensions. \n" f"Matrix A:{matrix1} \nMatrix B:{matrix2}" ) dimension1 = matrix_dimensions(matrix1) diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index 442a39cb1616..79104d8f4044 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -181,7 +181,7 @@ def _enforce_args(n: int, prices: list): if n > len(prices): raise ValueError( - f"Each integral piece of rod must have a corresponding " + "Each integral piece of rod must have a corresponding " f"price. Got n = {n} but length of prices = {len(prices)}" ) diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py index d1cb6efc2e8d..b0d45718e286 100644 --- a/hashes/enigma_machine.py +++ b/hashes/enigma_machine.py @@ -55,5 +55,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!" + "this message again you should input same digits as token!" ) diff --git a/maths/integration_by_simpson_approx.py b/maths/integration_by_simpson_approx.py index feb77440dd2f..408041de93f1 100644 --- a/maths/integration_by_simpson_approx.py +++ b/maths/integration_by_simpson_approx.py @@ -92,16 +92,12 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo 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), ( + assert isinstance(a, (float, int)), f"a should be float or integer your input : {a}" + assert isinstance(function(a), (float, 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}" + assert isinstance(b, (float, 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}" diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 6d0cd4e655eb..8e5d0f583486 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -171,7 +171,7 @@ def _verify_matrix_sizes( 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 " + "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]) diff --git a/project_euler/problem_067/sol1.py b/project_euler/problem_067/sol1.py index ebfa865a9479..527d4dc592ac 100644 --- a/project_euler/problem_067/sol1.py +++ b/project_euler/problem_067/sol1.py @@ -29,7 +29,7 @@ def solution(): 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)) + a = list(map(lambda x: list(map(int, x)), a)) for i in range(1, len(a)): for j in range(len(a[i])): diff --git a/sorts/external_sort.py b/sorts/external_sort.py index 060e67adf827..7af7dc0a609d 100644 --- a/sorts/external_sort.py +++ b/sorts/external_sort.py @@ -41,7 +41,7 @@ def split(self, block_size, sort_key=None): i += 1 def cleanup(self): - map(lambda f: os.remove(f), self.block_filenames) + map(os.remove, self.block_filenames) class NWayMerge: From 80f1da235b0a467dc9b31aa8a56dd3a792a59d7c Mon Sep 17 00:00:00 2001 From: zer0-x <65136727+zer0-x@users.noreply.github.com> Date: Mon, 16 May 2022 14:28:30 +0300 Subject: [PATCH 1793/2908] Add sin function to maths (#5949) * Add sin function to /maths. * Fix typo in /maths/sin.py * Format sin.py to meet the new black rules. * Some improvements. * Fix a formating error. --- maths/sin.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 maths/sin.py diff --git a/maths/sin.py b/maths/sin.py new file mode 100644 index 000000000000..b06e6c9f1e5d --- /dev/null +++ b/maths/sin.py @@ -0,0 +1,64 @@ +""" +Calculate sin function. + +It's not a perfect function so I am rounding the result to 10 decimal places by default. + +Formula: sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ... +Where: x = angle in randians. + +Source: + https://www.homeschoolmath.net/teaching/sine_calculator.php + +""" + +from math import factorial, radians + + +def sin( + angle_in_degrees: float, accuracy: int = 18, rounded_values_count: int = 10 +) -> float: + """ + Implement sin function. + + >>> sin(0.0) + 0.0 + >>> sin(90.0) + 1.0 + >>> sin(180.0) + 0.0 + >>> sin(270.0) + -1.0 + >>> sin(0.68) + 0.0118679603 + >>> sin(1.97) + 0.0343762121 + >>> sin(64.0) + 0.8987940463 + >>> sin(9999.0) + -0.9876883406 + >>> sin(-689.0) + 0.5150380749 + >>> sin(89.7) + 0.9999862922 + """ + # Simplify the angle to be between 360 and -360 degrees. + angle_in_degrees = angle_in_degrees - ((angle_in_degrees // 360.0) * 360.0) + + # Converting from degrees to radians + angle_in_radians = radians(angle_in_degrees) + + result = angle_in_radians + a = 3 + b = -1 + + for _ in range(accuracy): + result += (b * (angle_in_radians**a)) / factorial(a) + + b = -b # One positive term and the next will be negative and so on... + a += 2 # Increased by 2 for every term. + + return round(result, rounded_values_count) + + +if __name__ == "__main__": + __import__("doctest").testmod() From ec54da34b96de0b09b0685881646df1f9f7df989 Mon Sep 17 00:00:00 2001 From: Aviv Faraj <73610201+avivfaraj@users.noreply.github.com> Date: Mon, 16 May 2022 10:26:19 -0400 Subject: [PATCH 1794/2908] Lorenz transformation - physics (#6097) * Add files via upload * Changed print to f-string Also printed out results in a math notation * Add files via upload * Fixes: #4710 provided return type * File exists in another pull request * imported radians from math * Updated file according to pre-commit test * Updated file * Updated gamma * Deleted duplicate file * removed pi * reversed tests * Fixed angle condition * Modified prints to f-string * Update horizontal_projectile_motion.py * Update horizontal_projectile_motion.py * Fixes #4710 added exceptions and tests * Added float tests * Fixed type annotations * Fixed last annotation * Fixed annotations * fixed format * Revert "fixed format" This reverts commit 5b0249ac0a0f9c36c3cfbab8423eb72925a73ffb. Undo changes @wq * Revert "Fixed annotations" This reverts commit c37bb9540834cb77e37822eb376a5896cda34778. * Revert "Fixed last annotation" This reverts commit e3678fdeadd23f1bfca27015ab524efa184f6c79. * Revert "Fixed type annotations" This reverts commit 3f2b238c34cd926b335d1f6f750e009f08e8f270. * Revert to 4e2fcaf6fb * Fixing errors found during pre-commit * Added gauss law * Implemented Lorenz tranformation with four vector * pre-commit fixes * flake8 fixes * More flake8 fixes * Added blank space for flake8 * Added reference * Trailing whitespace fix * Replaced argument u with velocity (descriptive name fix) * Added tests for functions + moved velocity check to beta function * Modified condition to 'not symbolic' in the transform function * trainling whitespace fix * Added type hint for 'smybolic' argument in transform function * Changed reference to avoid pre-commit fails because of spelling issue related to the URL * Added tests for gamma and transformation_matrix functions * Fixed transformation_matrix tests * Fixed tests on beta and gamma functions --- physics/__init__.py | 0 physics/lorenz_transformation_four_vector.py | 205 +++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 physics/__init__.py create mode 100644 physics/lorenz_transformation_four_vector.py diff --git a/physics/__init__.py b/physics/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/physics/lorenz_transformation_four_vector.py b/physics/lorenz_transformation_four_vector.py new file mode 100644 index 000000000000..6c0d5f9d1997 --- /dev/null +++ b/physics/lorenz_transformation_four_vector.py @@ -0,0 +1,205 @@ +""" +Lorenz transformation describes the transition from a reference frame P +to another reference frame P', each of which is moving in a direction with +respect to the other. The Lorenz transformation implemented in this code +is the relativistic version using a four vector described by Minkowsky Space: +x0 = ct, x1 = x, x2 = y, and x3 = z + +NOTE: Please note that x0 is c (speed of light) times t (time). + +So, the Lorenz transformation using a four vector is defined as: + +|ct'| | γ -γβ 0 0| |ct| +|x' | = |-γβ γ 0 0| *|x | +|y' | | 0 0 1 0| |y | +|z' | | 0 0 0 1| |z | + +Where: + 1 +γ = --------------- + ----------- + / v^2 | + /(1 - --- + -/ c^2 + + v +β = ----- + c + +Reference: https://en.wikipedia.org/wiki/Lorentz_transformation +""" +from __future__ import annotations + +from math import sqrt + +import numpy as np # type: ignore +from sympy import symbols # type: ignore + +# Coefficient +# Speed of light (m/s) +c = 299792458 + +# Symbols +ct, x, y, z = symbols("ct x y z") +ct_p, x_p, y_p, z_p = symbols("ct' x' y' z'") + + +# Vehicle's speed divided by speed of light (no units) +def beta(velocity: float) -> float: + """ + >>> beta(c) + 1.0 + + >>> beta(199792458) + 0.666435904801848 + + >>> beta(1e5) + 0.00033356409519815205 + + >>> beta(0.2) + Traceback (most recent call last): + ... + ValueError: Speed must be greater than 1! + """ + if velocity > c: + raise ValueError("Speed must not exceed Light Speed 299,792,458 [m/s]!") + + # Usually the speed u should be much higher than 1 (c order of magnitude) + elif velocity < 1: + raise ValueError("Speed must be greater than 1!") + return velocity / c + + +def gamma(velocity: float) -> float: + """ + >>> gamma(4) + 1.0000000000000002 + + >>> gamma(1e5) + 1.0000000556325075 + + >>> gamma(3e7) + 1.005044845777813 + + >>> gamma(2.8e8) + 2.7985595722318277 + + >>> gamma(299792451) + 4627.49902669495 + + >>> gamma(0.3) + Traceback (most recent call last): + ... + ValueError: Speed must be greater than 1! + + >>> gamma(2*c) + Traceback (most recent call last): + ... + ValueError: Speed must not exceed Light Speed 299,792,458 [m/s]! + """ + return 1 / (sqrt(1 - beta(velocity) ** 2)) + + +def transformation_matrix(velocity: float) -> np.array: + """ + >>> transformation_matrix(29979245) + array([[ 1.00503781, -0.10050378, 0. , 0. ], + [-0.10050378, 1.00503781, 0. , 0. ], + [ 0. , 0. , 1. , 0. ], + [ 0. , 0. , 0. , 1. ]]) + + >>> transformation_matrix(19979245.2) + array([[ 1.00222811, -0.06679208, 0. , 0. ], + [-0.06679208, 1.00222811, 0. , 0. ], + [ 0. , 0. , 1. , 0. ], + [ 0. , 0. , 0. , 1. ]]) + + >>> transformation_matrix(1) + array([[ 1.00000000e+00, -3.33564095e-09, 0.00000000e+00, + 0.00000000e+00], + [-3.33564095e-09, 1.00000000e+00, 0.00000000e+00, + 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00, + 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 1.00000000e+00]]) + + >>> transformation_matrix(0) + Traceback (most recent call last): + ... + ValueError: Speed must be greater than 1! + + >>> transformation_matrix(c * 1.5) + Traceback (most recent call last): + ... + ValueError: Speed must not exceed Light Speed 299,792,458 [m/s]! + """ + return np.array( + [ + [gamma(velocity), -gamma(velocity) * beta(velocity), 0, 0], + [-gamma(velocity) * beta(velocity), gamma(velocity), 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + ] + ) + + +def transform( + velocity: float, event: np.array = np.zeros(4), symbolic: bool = True +) -> np.array: + """ + >>> transform(29979245,np.array([1,2,3,4]), False) + array([ 3.01302757e+08, -3.01302729e+07, 3.00000000e+00, 4.00000000e+00]) + + >>> transform(29979245) + array([1.00503781498831*ct - 0.100503778816875*x, + -0.100503778816875*ct + 1.00503781498831*x, 1.0*y, 1.0*z], + dtype=object) + + >>> transform(19879210.2) + array([1.0022057787097*ct - 0.066456172618675*x, + -0.066456172618675*ct + 1.0022057787097*x, 1.0*y, 1.0*z], + dtype=object) + + >>> transform(299792459, np.array([1,1,1,1])) + Traceback (most recent call last): + ... + ValueError: Speed must not exceed Light Speed 299,792,458 [m/s]! + + >>> transform(-1, np.array([1,1,1,1])) + Traceback (most recent call last): + ... + ValueError: Speed must be greater than 1! + """ + # Ensure event is not a vector of zeros + if not symbolic: + + # x0 is ct (speed of ligt * time) + event[0] = event[0] * c + else: + + # Symbolic four vector + event = np.array([ct, x, y, z]) + + return transformation_matrix(velocity).dot(event) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Example of symbolic vector: + four_vector = transform(29979245) + print("Example of four vector: ") + print(f"ct' = {four_vector[0]}") + print(f"x' = {four_vector[1]}") + print(f"y' = {four_vector[2]}") + print(f"z' = {four_vector[3]}") + + # Substitute symbols with numerical values: + values = np.array([1, 1, 1, 1]) + sub_dict = {ct: c * values[0], x: values[1], y: values[2], z: values[3]} + numerical_vector = [four_vector[i].subs(sub_dict) for i in range(0, 4)] + + print(f"\n{numerical_vector}") From dceb30aad623c8c9dffd739f41e1f5f46eb44530 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Fri, 20 May 2022 13:03:54 +0900 Subject: [PATCH 1795/2908] Fix typo in word_occurrence.py (#6154) word_occurence -> word_occurrence --- strings/word_occurrence.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/strings/word_occurrence.py b/strings/word_occurrence.py index 4e0b3ff34ccf..8260620c38a4 100644 --- a/strings/word_occurrence.py +++ b/strings/word_occurrence.py @@ -4,15 +4,15 @@ from typing import DefaultDict -def word_occurence(sentence: str) -> dict: +def word_occurrence(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) + >>> occurence_dict = word_occurrence(SENTENCE) >>> all(occurence_dict[word] == count for word, count ... in Counter(SENTENCE.split()).items()) True - >>> dict(word_occurence("Two spaces")) + >>> dict(word_occurrence("Two spaces")) {'Two': 1, 'spaces': 1} """ occurrence: DefaultDict[str, int] = defaultdict(int) @@ -23,5 +23,5 @@ def word_occurence(sentence: str) -> dict: if __name__ == "__main__": - for word, count in word_occurence("INPUT STRING").items(): + for word, count in word_occurrence("INPUT STRING").items(): print(f"{word}: {count}") From 5bac76d7a505c43aad6d0a32cd39982f4b927eac Mon Sep 17 00:00:00 2001 From: dangbb <51513363+dangbb@users.noreply.github.com> Date: Sat, 21 May 2022 21:02:53 +0700 Subject: [PATCH 1796/2908] Fix `iter_merge_sort` bug (#6153) * Fixed bug where array length 2 can't be sorted * Add MCC and DU path test Add test to conversions/octal_to_decimal.py and sorts\iterative_merge_sort.py * "" * Update octal_to_decimal.py Co-authored-by: John Law --- conversions/octal_to_decimal.py | 36 +++++++++++++++++++++++++++++++++ sorts/iterative_merge_sort.py | 24 ++++++++++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/conversions/octal_to_decimal.py b/conversions/octal_to_decimal.py index 5a7373fef7e3..551311e2651e 100644 --- a/conversions/octal_to_decimal.py +++ b/conversions/octal_to_decimal.py @@ -2,12 +2,48 @@ def oct_to_decimal(oct_string: str) -> int: """ Convert a octal value to its decimal equivalent + >>> oct_to_decimal("") + Traceback (most recent call last): + ... + ValueError: Empty string was passed to the function + >>> oct_to_decimal("-") + Traceback (most recent call last): + ... + ValueError: Non-octal value was passed to the function + >>> oct_to_decimal("e") + Traceback (most recent call last): + ... + ValueError: Non-octal value was passed to the function + >>> oct_to_decimal("8") + Traceback (most recent call last): + ... + ValueError: Non-octal value was passed to the function + >>> oct_to_decimal("-e") + Traceback (most recent call last): + ... + ValueError: Non-octal value was passed to the function + >>> oct_to_decimal("-8") + Traceback (most recent call last): + ... + ValueError: Non-octal value was passed to the function + >>> oct_to_decimal("1") + 1 + >>> oct_to_decimal("-1") + -1 >>> oct_to_decimal("12") 10 >>> oct_to_decimal(" 12 ") 10 >>> oct_to_decimal("-45") -37 + >>> oct_to_decimal("-") + Traceback (most recent call last): + ... + ValueError: Non-octal value was passed to the function + >>> oct_to_decimal("0") + 0 + >>> oct_to_decimal("-4055") + -2093 >>> oct_to_decimal("2-0Fm") Traceback (most recent call last): ... diff --git a/sorts/iterative_merge_sort.py b/sorts/iterative_merge_sort.py index 5ee0badab9e6..327974fa61ae 100644 --- a/sorts/iterative_merge_sort.py +++ b/sorts/iterative_merge_sort.py @@ -32,6 +32,22 @@ def iter_merge_sort(input_list: list) -> list: >>> iter_merge_sort([5, 9, 8, 7, 1, 2, 7]) [1, 2, 5, 7, 7, 8, 9] + >>> iter_merge_sort([1]) + [1] + >>> iter_merge_sort([2, 1]) + [1, 2] + >>> iter_merge_sort([2, 1, 3]) + [1, 2, 3] + >>> iter_merge_sort([4, 3, 2, 1]) + [1, 2, 3, 4] + >>> iter_merge_sort([5, 4, 3, 2, 1]) + [1, 2, 3, 4, 5] + >>> iter_merge_sort(['c', 'b', 'a']) + ['a', 'b', 'c'] + >>> iter_merge_sort([0.3, 0.2, 0.1]) + [0.1, 0.2, 0.3] + >>> iter_merge_sort(['dep', 'dang', 'trai']) + ['dang', 'dep', 'trai'] >>> iter_merge_sort([6]) [6] >>> iter_merge_sort([]) @@ -51,7 +67,7 @@ def iter_merge_sort(input_list: list) -> list: # iteration for two-way merging p = 2 - while p < len(input_list): + 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 @@ -62,6 +78,7 @@ def iter_merge_sort(input_list: list) -> list: if p * 2 >= len(input_list): mid = i input_list = merge(input_list, 0, mid, len(input_list) - 1) + break p *= 2 return input_list @@ -69,5 +86,8 @@ def iter_merge_sort(input_list: list) -> 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(",")] + if user_input == "": + unsorted = [] + else: + unsorted = [int(item.strip()) for item in user_input.split(",")] print(iter_merge_sort(unsorted)) From a28ad3f759ba4b69e58e65db60eb26f0fd9756cc Mon Sep 17 00:00:00 2001 From: Luke Banicevic <60857954+banaboi@users.noreply.github.com> Date: Tue, 24 May 2022 11:18:50 +1000 Subject: [PATCH 1797/2908] Add Microsoft Excel Column Title to Column Number Conversion (#4849) * Added excel column title to number algorithm as part of conversions * Renamed file to better reflect algorithm function * Removed duplicate file * Update excel_title_to_column.py * Update excel_title_to_column.py Co-authored-by: John Law --- conversions/excel_title_to_column.py | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 conversions/excel_title_to_column.py diff --git a/conversions/excel_title_to_column.py b/conversions/excel_title_to_column.py new file mode 100644 index 000000000000..d77031ec26f2 --- /dev/null +++ b/conversions/excel_title_to_column.py @@ -0,0 +1,33 @@ +def excel_title_to_column(column_title: str) -> int: + """ + Given a string column_title that represents + the column title in an Excel sheet, return + its corresponding column number. + + >>> excel_title_to_column("A") + 1 + >>> excel_title_to_column("B") + 2 + >>> excel_title_to_column("AB") + 28 + >>> excel_title_to_column("Z") + 26 + """ + assert column_title.isupper() + answer = 0 + index = len(column_title) - 1 + power = 0 + + while index >= 0: + value = (ord(column_title[index]) - 64) * pow(26, power) + answer += value + power += 1 + index -= 1 + + return answer + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From de4d98081b9f9ba4bc6447ef20bb8fed329b343e Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 24 May 2022 04:20:47 +0300 Subject: [PATCH 1798/2908] Improve Project Euler problem 145 solution 1 (#6141) * updating DIRECTORY.md * Improve solution * updating DIRECTORY.md * Fix Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 9 +++++++++ project_euler/problem_145/sol1.py | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index eeea22e4768f..64a87dc660da 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -112,6 +112,7 @@ * [Cnn Classification](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/cnn_classification.py) * [Flip Augmentation](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/flip_augmentation.py) * [Harris Corner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harris_corner.py) + * [Horn Schunck](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/horn_schunck.py) * [Mean Threshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mean_threshold.py) * [Mosaic Augmentation](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mosaic_augmentation.py) * [Pooling Functions](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/pooling_functions.py) @@ -131,6 +132,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) + * [Prefix Conversions String](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions_string.py) * [Pressure Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/pressure_conversions.py) * [Rgb Hsv Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/rgb_hsv_conversion.py) * [Roman Numerals](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_numerals.py) @@ -529,6 +531,7 @@ * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) * [Persistence](https://github.com/TheAlgorithms/Python/blob/master/maths/persistence.py) * [Pi Monte Carlo Estimation](https://github.com/TheAlgorithms/Python/blob/master/maths/pi_monte_carlo_estimation.py) + * [Points Are Collinear 3D](https://github.com/TheAlgorithms/Python/blob/master/maths/points_are_collinear_3d.py) * [Pollard Rho](https://github.com/TheAlgorithms/Python/blob/master/maths/pollard_rho.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) @@ -619,6 +622,7 @@ * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) ## Physics + * [Horizontal Projectile Motion](https://github.com/TheAlgorithms/Python/blob/master/physics/horizontal_projectile_motion.py) * [N Body Simulation](https://github.com/TheAlgorithms/Python/blob/master/physics/n_body_simulation.py) * [Newtons Second Law Of Motion](https://github.com/TheAlgorithms/Python/blob/master/physics/newtons_second_law_of_motion.py) @@ -833,6 +837,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 104 + * [Sol](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_104/sol.py) * Problem 107 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_107/sol1.py) * Problem 109 @@ -857,6 +863,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_135/sol1.py) * Problem 144 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_144/sol1.py) + * Problem 145 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_145/sol1.py) * Problem 173 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) * Problem 174 @@ -990,6 +998,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) + * [Ngram](https://github.com/TheAlgorithms/Python/blob/master/strings/ngram.py) * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/palindrome.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) diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py index 82e2ea79bc25..09d8daff57be 100644 --- a/project_euler/problem_145/sol1.py +++ b/project_euler/problem_145/sol1.py @@ -23,10 +23,11 @@ def odd_digits(num: int) -> bool: >>> odd_digits(135797531) True """ - num_str = str(num) - for i in ["0", "2", "4", "6", "8"]: - if i in num_str: + while num > 0: + digit = num % 10 + if digit % 2 == 0: return False + num //= 10 return True From b8fdd81f286e2435d058750d35420cb5a89f470d Mon Sep 17 00:00:00 2001 From: Raine Legary <64663183+Rainethhh@users.noreply.github.com> Date: Tue, 24 May 2022 23:49:54 -0600 Subject: [PATCH 1799/2908] Add minmum path sum (#5882) * commit on 'shortest_path_sum' * minimum_path_sum updated * commit to 'minimum_path_sum' * added description to minimum_path_sum * bot requirements fixed for * Update minimum_path_sum.py * Update minimum_path_sum.py Co-authored-by: John Law --- graphs/minimum_path_sum.py | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 graphs/minimum_path_sum.py diff --git a/graphs/minimum_path_sum.py b/graphs/minimum_path_sum.py new file mode 100644 index 000000000000..df1e545df3d0 --- /dev/null +++ b/graphs/minimum_path_sum.py @@ -0,0 +1,63 @@ +def min_path_sum(grid: list) -> int: + """ + Find the path from top left to bottom right of array of numbers + with the lowest possible sum and return the sum along this path. + >>> min_path_sum([ + ... [1, 3, 1], + ... [1, 5, 1], + ... [4, 2, 1], + ... ]) + 7 + + >>> min_path_sum([ + ... [1, 0, 5, 6, 7], + ... [8, 9, 0, 4, 2], + ... [4, 4, 4, 5, 1], + ... [9, 6, 3, 1, 0], + ... [8, 4, 3, 2, 7], + ... ]) + 20 + + >>> min_path_sum(None) + Traceback (most recent call last): + ... + TypeError: The grid does not contain the appropriate information + + >>> min_path_sum([[]]) + Traceback (most recent call last): + ... + TypeError: The grid does not contain the appropriate information + """ + + if not grid or not grid[0]: + raise TypeError("The grid does not contain the appropriate information") + + for cell_n in range(1, len(grid[0])): + grid[0][cell_n] += grid[0][cell_n - 1] + row_above = grid[0] + + for row_n in range(1, len(grid)): + current_row = grid[row_n] + grid[row_n] = fill_row(current_row, row_above) + row_above = grid[row_n] + + return grid[-1][-1] + + +def fill_row(current_row: list, row_above: list) -> list: + """ + >>> fill_row([2, 2, 2], [1, 2, 3]) + [3, 4, 5] + """ + + current_row[0] += row_above[0] + for cell_n in range(1, len(current_row)): + current_row[cell_n] += min(current_row[cell_n - 1], row_above[cell_n]) + + return current_row + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 8004671b984a58a86eb010e3add16f7268ed56b8 Mon Sep 17 00:00:00 2001 From: kugiyasan <44143656+kugiyasan@users.noreply.github.com> Date: Thu, 26 May 2022 15:24:23 -0400 Subject: [PATCH 1800/2908] Add Project Euler 68 Solution (#5552) * updating DIRECTORY.md * Project Euler 68 Solution * updating DIRECTORY.md * Project Euler 68 Fixed doctests, now at 93% coverage * Update sol1.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: kugiyasan Co-authored-by: John Law --- DIRECTORY.md | 2 + project_euler/problem_068/__init__.py | 0 project_euler/problem_068/sol1.py | 133 ++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 project_euler/problem_068/__init__.py create mode 100644 project_euler/problem_068/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 64a87dc660da..f4a470c12148 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -793,6 +793,8 @@ * Problem 067 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol2.py) + * Problem 068 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_068/sol1.py) * Problem 069 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_069/sol1.py) * Problem 070 diff --git a/project_euler/problem_068/__init__.py b/project_euler/problem_068/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_068/sol1.py b/project_euler/problem_068/sol1.py new file mode 100644 index 000000000000..772be359f630 --- /dev/null +++ b/project_euler/problem_068/sol1.py @@ -0,0 +1,133 @@ +""" +Project Euler Problem 68: https://projecteuler.net/problem=68 + +Magic 5-gon ring + +Problem Statement: +Consider the following "magic" 3-gon ring, +filled with the numbers 1 to 6, and each line adding to nine. + + 4 + \ + 3 + / \ + 1 - 2 - 6 + / + 5 + +Working clockwise, and starting from the group of three +with the numerically lowest external node (4,3,2 in this example), +each solution can be described uniquely. +For example, the above solution can be described by the set: 4,3,2; 6,2,1; 5,1,3. + +It is possible to complete the ring with four different totals: 9, 10, 11, and 12. +There are eight solutions in total. +Total Solution Set +9 4,2,3; 5,3,1; 6,1,2 +9 4,3,2; 6,2,1; 5,1,3 +10 2,3,5; 4,5,1; 6,1,3 +10 2,5,3; 6,3,1; 4,1,5 +11 1,4,6; 3,6,2; 5,2,4 +11 1,6,4; 5,4,2; 3,2,6 +12 1,5,6; 2,6,4; 3,4,5 +12 1,6,5; 3,5,4; 2,4,6 + +By concatenating each group it is possible to form 9-digit strings; +the maximum string for a 3-gon ring is 432621513. + +Using the numbers 1 to 10, and depending on arrangements, +it is possible to form 16- and 17-digit strings. +What is the maximum 16-digit string for a "magic" 5-gon ring? +""" + +from itertools import permutations + + +def solution(gon_side: int = 5) -> int: + """ + Find the maximum number for a "magic" gon_side-gon ring + + The gon_side parameter should be in the range [3, 5], + other side numbers aren't tested + + >>> solution(3) + 432621513 + >>> solution(4) + 426561813732 + >>> solution() + 6531031914842725 + >>> solution(6) + Traceback (most recent call last): + ValueError: gon_side must be in the range [3, 5] + """ + if gon_side < 3 or gon_side > 5: + raise ValueError("gon_side must be in the range [3, 5]") + + # Since it's 16, we know 10 is on the outer ring + # Put the big numbers at the end so that they are never the first number + small_numbers = list(range(gon_side + 1, 0, -1)) + big_numbers = list(range(gon_side + 2, gon_side * 2 + 1)) + + for perm in permutations(small_numbers + big_numbers): + numbers = generate_gon_ring(gon_side, list(perm)) + if is_magic_gon(numbers): + return int("".join(str(n) for n in numbers)) + + raise ValueError(f"Magic {gon_side}-gon ring is impossible") + + +def generate_gon_ring(gon_side: int, perm: list[int]) -> list[int]: + """ + Generate a gon_side-gon ring from a permutation state + The permutation state is the ring, but every duplicate is removed + + >>> generate_gon_ring(3, [4, 2, 3, 5, 1, 6]) + [4, 2, 3, 5, 3, 1, 6, 1, 2] + >>> generate_gon_ring(5, [6, 5, 4, 3, 2, 1, 7, 8, 9, 10]) + [6, 5, 4, 3, 4, 2, 1, 2, 7, 8, 7, 9, 10, 9, 5] + """ + result = [0] * (gon_side * 3) + result[0:3] = perm[0:3] + perm.append(perm[1]) + + magic_number = 1 if gon_side < 5 else 2 + + for i in range(1, len(perm) // 3 + magic_number): + result[3 * i] = perm[2 * i + 1] + result[3 * i + 1] = result[3 * i - 1] + result[3 * i + 2] = perm[2 * i + 2] + + return result + + +def is_magic_gon(numbers: list[int]) -> bool: + """ + Check if the solution set is a magic n-gon ring + Check that the first number is the smallest number on the outer ring + Take a list, and check if the sum of each 3 numbers chunk is equal to the same total + + >>> is_magic_gon([4, 2, 3, 5, 3, 1, 6, 1, 2]) + True + >>> is_magic_gon([4, 3, 2, 6, 2, 1, 5, 1, 3]) + True + >>> is_magic_gon([2, 3, 5, 4, 5, 1, 6, 1, 3]) + True + >>> is_magic_gon([1, 2, 3, 4, 5, 6, 7, 8, 9]) + False + >>> is_magic_gon([1]) + Traceback (most recent call last): + ValueError: a gon ring should have a length that is a multiple of 3 + """ + if len(numbers) % 3 != 0: + raise ValueError("a gon ring should have a length that is a multiple of 3") + + if min(numbers[::3]) != numbers[0]: + return False + + total = sum(numbers[:3]) + + return all(sum(numbers[i : i + 3]) == total for i in range(3, len(numbers), 3)) + + +if __name__ == "__main__": + print(solution()) From a44afc9b7dd74f85f0c54ebdd8f0135b6bc38dc9 Mon Sep 17 00:00:00 2001 From: DongJoon Cha <81581204+dongjji@users.noreply.github.com> Date: Sun, 5 Jun 2022 01:41:52 +0900 Subject: [PATCH 1801/2908] Add Multi-Level-Feedback-Queue scheduling algorithm (#6165) * Add Multi-Level-Feedback-Queue scheduling algorithm * fix type hint annotation for pre-commit * Update scheduling/multi_level_feedback_queue.py Co-authored-by: John Law * Update scheduling/multi_level_feedback_queue.py Co-authored-by: John Law * Update scheduling/multi_level_feedback_queue.py Co-authored-by: John Law * Update scheduling/multi_level_feedback_queue.py * Update scheduling/multi_level_feedback_queue.py Co-authored-by: John Law Co-authored-by: John Law --- scheduling/multi_level_feedback_queue.py | 312 +++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 scheduling/multi_level_feedback_queue.py diff --git a/scheduling/multi_level_feedback_queue.py b/scheduling/multi_level_feedback_queue.py new file mode 100644 index 000000000000..95ca827e062d --- /dev/null +++ b/scheduling/multi_level_feedback_queue.py @@ -0,0 +1,312 @@ +from collections import deque + + +class Process: + def __init__(self, process_name: str, arrival_time: int, burst_time: int) -> None: + self.process_name = process_name # process name + self.arrival_time = arrival_time # arrival time of the process + # completion time of finished process or last interrupted time + self.stop_time = arrival_time + self.burst_time = burst_time # remaining burst time + self.waiting_time = 0 # total time of the process wait in ready queue + self.turnaround_time = 0 # time from arrival time to completion time + + +class MLFQ: + """ + MLFQ(Multi Level Feedback Queue) + https://en.wikipedia.org/wiki/Multilevel_feedback_queue + MLFQ has a lot of queues that have different priority + In this MLFQ, + The first Queue(0) to last second Queue(N-2) of MLFQ have Round Robin Algorithm + The last Queue(N-1) has First Come, First Served Algorithm + """ + + def __init__( + self, + number_of_queues: int, + time_slices: list[int], + queue: deque[Process], + current_time: int, + ) -> None: + # total number of mlfq's queues + self.number_of_queues = number_of_queues + # time slice of queues that round robin algorithm applied + self.time_slices = time_slices + # unfinished process is in this ready_queue + self.ready_queue = queue + # current time + self.current_time = current_time + # finished process is in this sequence queue + self.finish_queue: deque[Process] = deque() + + def calculate_sequence_of_finish_queue(self) -> list[str]: + """ + This method returns the sequence of finished processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2', 'P4', 'P1', 'P3'] + """ + sequence = [] + for i in range(len(self.finish_queue)): + sequence.append(self.finish_queue[i].process_name) + return sequence + + def calculate_waiting_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates waiting time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_waiting_time([P1, P2, P3, P4]) + [83, 17, 94, 101] + """ + waiting_times = [] + for i in range(len(queue)): + waiting_times.append(queue[i].waiting_time) + return waiting_times + + def calculate_turnaround_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates turnaround time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_turnaround_time([P1, P2, P3, P4]) + [136, 34, 162, 125] + """ + turnaround_times = [] + for i in range(len(queue)): + turnaround_times.append(queue[i].turnaround_time) + return turnaround_times + + def calculate_completion_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates completion time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_turnaround_time([P1, P2, P3, P4]) + [136, 34, 162, 125] + """ + completion_times = [] + for i in range(len(queue)): + completion_times.append(queue[i].stop_time) + return completion_times + + def calculate_remaining_burst_time_of_processes( + self, queue: deque[Process] + ) -> list[int]: + """ + This method calculate remaining burst time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue, ready_queue = mlfq.round_robin(deque([P1, P2, P3, P4]), 17) + >>> mlfq.calculate_remaining_burst_time_of_processes(mlfq.finish_queue) + [0] + >>> mlfq.calculate_remaining_burst_time_of_processes(ready_queue) + [36, 51, 7] + >>> finish_queue, ready_queue = mlfq.round_robin(ready_queue, 25) + >>> mlfq.calculate_remaining_burst_time_of_processes(mlfq.finish_queue) + [0, 0] + >>> mlfq.calculate_remaining_burst_time_of_processes(ready_queue) + [11, 26] + """ + return [q.burst_time for q in queue] + + def update_waiting_time(self, process: Process) -> int: + """ + This method updates waiting times of unfinished processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> mlfq.current_time = 10 + >>> P1.stop_time = 5 + >>> mlfq.update_waiting_time(P1) + 5 + """ + process.waiting_time += self.current_time - process.stop_time + return process.waiting_time + + def first_come_first_served(self, ready_queue: deque[Process]) -> deque[Process]: + """ + FCFS(First Come, First Served) + FCFS will be applied to MLFQ's last queue + A first came process will be finished at first + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.first_come_first_served(mlfq.ready_queue) + >>> mlfq.calculate_sequence_of_finish_queue() + ['P1', 'P2', 'P3', 'P4'] + """ + finished: deque[Process] = deque() # sequence deque of finished process + while len(ready_queue) != 0: + cp = ready_queue.popleft() # current process + + # if process's arrival time is later than current time, update current time + if self.current_time < cp.arrival_time: + self.current_time += cp.arrival_time + + # update waiting time of current process + self.update_waiting_time(cp) + # update current time + self.current_time += cp.burst_time + # finish the process and set the process's burst-time 0 + cp.burst_time = 0 + # set the process's turnaround time because it is finished + cp.turnaround_time = self.current_time - cp.arrival_time + # set the completion time + cp.stop_time = self.current_time + # add the process to queue that has finished queue + finished.append(cp) + + self.finish_queue.extend(finished) # add finished process to finish queue + # FCFS will finish all remaining processes + return finished + + def round_robin( + self, ready_queue: deque[Process], time_slice: int + ) -> tuple[deque[Process], deque[Process]]: + """ + RR(Round Robin) + RR will be applied to MLFQ's all queues except last queue + All processes can't use CPU for time more than time_slice + If the process consume CPU up to time_slice, it will go back to ready queue + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue, ready_queue = mlfq.round_robin(mlfq.ready_queue, 17) + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2'] + """ + finished: deque[Process] = deque() # sequence deque of terminated process + # just for 1 cycle and unfinished processes will go back to queue + for i in range(len(ready_queue)): + cp = ready_queue.popleft() # current process + + # if process's arrival time is later than current time, update current time + if self.current_time < cp.arrival_time: + self.current_time += cp.arrival_time + + # update waiting time of unfinished processes + self.update_waiting_time(cp) + # if the burst time of process is bigger than time-slice + if cp.burst_time > time_slice: + # use CPU for only time-slice + self.current_time += time_slice + # update remaining burst time + cp.burst_time -= time_slice + # update end point time + cp.stop_time = self.current_time + # locate the process behind the queue because it is not finished + ready_queue.append(cp) + else: + # use CPU for remaining burst time + self.current_time += cp.burst_time + # set burst time 0 because the process is finished + cp.burst_time = 0 + # set the finish time + cp.stop_time = self.current_time + # update the process' turnaround time because it is finished + cp.turnaround_time = self.current_time - cp.arrival_time + # add the process to queue that has finished queue + finished.append(cp) + + self.finish_queue.extend(finished) # add finished process to finish queue + # return finished processes queue and remaining processes queue + return finished, ready_queue + + def multi_level_feedback_queue(self) -> deque[Process]: + """ + MLFQ(Multi Level Feedback Queue) + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2', 'P4', 'P1', 'P3'] + """ + + # all queues except last one have round_robin algorithm + for i in range(self.number_of_queues - 1): + finished, self.ready_queue = self.round_robin( + self.ready_queue, self.time_slices[i] + ) + # the last queue has first_come_first_served algorithm + self.first_come_first_served(self.ready_queue) + + return self.finish_queue + + +if __name__ == "__main__": + import doctest + + P1 = Process("P1", 0, 53) + P2 = Process("P2", 0, 17) + P3 = Process("P3", 0, 68) + P4 = Process("P4", 0, 24) + number_of_queues = 3 + time_slices = [17, 25] + queue = deque([P1, P2, P3, P4]) + + if len(time_slices) != number_of_queues - 1: + exit() + + doctest.testmod(extraglobs={"queue": deque([P1, P2, P3, P4])}) + + P1 = Process("P1", 0, 53) + P2 = Process("P2", 0, 17) + P3 = Process("P3", 0, 68) + P4 = Process("P4", 0, 24) + number_of_queues = 3 + time_slices = [17, 25] + queue = deque([P1, P2, P3, P4]) + mlfq = MLFQ(number_of_queues, time_slices, queue, 0) + finish_queue = mlfq.multi_level_feedback_queue() + + # print total waiting times of processes(P1, P2, P3, P4) + print( + f"waiting time:\ + \t\t\t{MLFQ.calculate_waiting_time(mlfq, [P1, P2, P3, P4])}" + ) + # print completion times of processes(P1, P2, P3, P4) + print( + f"completion time:\ + \t\t{MLFQ.calculate_completion_time(mlfq, [P1, P2, P3, P4])}" + ) + # print total turnaround times of processes(P1, P2, P3, P4) + print( + f"turnaround time:\ + \t\t{MLFQ.calculate_turnaround_time(mlfq, [P1, P2, P3, P4])}" + ) + # print sequence of finished processes + print( + f"sequnece of finished processes:\ + {mlfq.calculate_sequence_of_finish_queue()}" + ) From c86aa72cfa0467bd9a5711d7b5a77ed8243e49f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=B9=88?= <76545238+Bynnn@users.noreply.github.com> Date: Tue, 7 Jun 2022 01:44:49 +0900 Subject: [PATCH 1802/2908] Create non_preemptive_shortest_job_first.py (#6169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create non_preemptive_shortest_job_first.py * 파일 위치 변경 * Delete non_preemptive_shortest_job_first.py * delete Korean comments * change comments, & to and, type annotation * type annotation * delete unnecessary comment --- .../non_preemptive_shortest_job_first.py | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 scheduling/non_preemptive_shortest_job_first.py diff --git a/scheduling/non_preemptive_shortest_job_first.py b/scheduling/non_preemptive_shortest_job_first.py new file mode 100644 index 000000000000..96e571230ec0 --- /dev/null +++ b/scheduling/non_preemptive_shortest_job_first.py @@ -0,0 +1,111 @@ +""" +Non-preemptive Shortest Job First +Shortest execution time process is chosen for the next execution. +https://www.guru99.com/shortest-job-first-sjf-scheduling.html +https://en.wikipedia.org/wiki/Shortest_job_next +""" + + +from __future__ import annotations + +from statistics import mean + + +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: The waiting time for each process. + >>> calculate_waitingtime([0,1,2], [10, 5, 8], 3) + [0, 9, 13] + >>> calculate_waitingtime([1,2,2,4], [4, 6, 3, 1], 4) + [0, 7, 4, 1] + >>> calculate_waitingtime([0,0,0], [12, 2, 10],3) + [12, 0, 2] + """ + + waiting_time = [0] * no_of_processes + remaining_time = [0] * no_of_processes + + # Initialize remaining_time to waiting_time. + + for i in range(no_of_processes): + remaining_time[i] = burst_time[i] + ready_process: list[int] = [] + + completed = 0 + total_time = 0 + + # When processes are not completed, + # A process whose arrival time has passed \ + # and has remaining execution time is put into the ready_process. + # The shortest process in the ready_process, target_process is executed. + + while completed != no_of_processes: + ready_process = [] + target_process = -1 + + for i in range(no_of_processes): + if (arrival_time[i] <= total_time) and (remaining_time[i] > 0): + ready_process.append(i) + + if len(ready_process) > 0: + target_process = ready_process[0] + for i in ready_process: + if remaining_time[i] < remaining_time[target_process]: + target_process = i + total_time += burst_time[target_process] + completed += 1 + remaining_time[target_process] = 0 + waiting_time[target_process] = ( + total_time - arrival_time[target_process] - burst_time[target_process] + ) + else: + total_time += 1 + + return waiting_time + + +def calculate_turnaroundtime( + burst_time: list[int], no_of_processes: int, waiting_time: list[int] +) -> list[int]: + """ + Calculate the turnaround time of each process. + + Return: The turnaround time for each process. + >>> calculate_turnaroundtime([0,1,2], 3, [0, 10, 15]) + [0, 11, 17] + >>> calculate_turnaroundtime([1,2,2,4], 4, [1, 8, 5, 4]) + [2, 10, 7, 8] + >>> calculate_turnaroundtime([0,0,0], 3, [12, 0, 2]) + [12, 0, 2] + """ + + 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 + + +if __name__ == "__main__": + print("[TEST CASE 01]") + + no_of_processes = 4 + burst_time = [2, 5, 3, 7] + arrival_time = [0, 0, 0, 0] + waiting_time = calculate_waitingtime(arrival_time, burst_time, no_of_processes) + turn_around_time = calculate_turnaroundtime( + burst_time, no_of_processes, waiting_time + ) + + # Printing the Result + print("PID\tBurst Time\tArrival Time\tWaiting Time\tTurnaround Time") + for i, process_ID in enumerate(list(range(1, 5))): + print( + f"{process_ID}\t{burst_time[i]}\t\t\t{arrival_time[i]}\t\t\t\t" + f"{waiting_time[i]}\t\t\t\t{turn_around_time[i]}" + ) + print(f"\nAverage waiting time = {mean(waiting_time):.5f}") + print(f"Average turnaround time = {mean(turn_around_time):.5f}") From 69cde43ca1e78980922adaf6b852008840d52e14 Mon Sep 17 00:00:00 2001 From: Vcrostin <52068696+Vcrostin@users.noreply.github.com> Date: Wed, 22 Jun 2022 07:01:05 +0300 Subject: [PATCH 1803/2908] make DIRECTORY.md paths relative Fixes (#6179) (#6190) --- DIRECTORY.md | 1658 +++++++++++++++++---------------- scripts/build_directory_md.py | 4 +- 2 files changed, 833 insertions(+), 829 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f4a470c12148..d30e275d067f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,1049 +1,1055 @@ ## 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) - * [Jacobi Iteration Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/jacobi_iteration_method.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) + * [Bisection](arithmetic_analysis/bisection.py) + * [Gaussian Elimination](arithmetic_analysis/gaussian_elimination.py) + * [In Static Equilibrium](arithmetic_analysis/in_static_equilibrium.py) + * [Intersection](arithmetic_analysis/intersection.py) + * [Jacobi Iteration Method](arithmetic_analysis/jacobi_iteration_method.py) + * [Lu Decomposition](arithmetic_analysis/lu_decomposition.py) + * [Newton Forward Interpolation](arithmetic_analysis/newton_forward_interpolation.py) + * [Newton Method](arithmetic_analysis/newton_method.py) + * [Newton Raphson](arithmetic_analysis/newton_raphson.py) + * [Secant Method](arithmetic_analysis/secant_method.py) ## Audio Filters - * [Butterworth Filter](https://github.com/TheAlgorithms/Python/blob/master/audio_filters/butterworth_filter.py) - * [Iir Filter](https://github.com/TheAlgorithms/Python/blob/master/audio_filters/iir_filter.py) - * [Show Response](https://github.com/TheAlgorithms/Python/blob/master/audio_filters/show_response.py) + * [Butterworth Filter](audio_filters/butterworth_filter.py) + * [Iir Filter](audio_filters/iir_filter.py) + * [Show Response](audio_filters/show_response.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) - * [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) - * [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) + * [All Combinations](backtracking/all_combinations.py) + * [All Permutations](backtracking/all_permutations.py) + * [All Subsequences](backtracking/all_subsequences.py) + * [Coloring](backtracking/coloring.py) + * [Hamiltonian Cycle](backtracking/hamiltonian_cycle.py) + * [Knight Tour](backtracking/knight_tour.py) + * [Minimax](backtracking/minimax.py) + * [N Queens](backtracking/n_queens.py) + * [N Queens Math](backtracking/n_queens_math.py) + * [Rat In Maze](backtracking/rat_in_maze.py) + * [Sudoku](backtracking/sudoku.py) + * [Sum Of Subsets](backtracking/sum_of_subsets.py) ## 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 Shifts](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_shifts.py) - * [Binary Twos Complement](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_twos_complement.py) - * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) - * [Count 1S Brian Kernighan Method](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_1s_brian_kernighan_method.py) - * [Count Number Of One Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_number_of_one_bits.py) - * [Gray Code Sequence](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/gray_code_sequence.py) - * [Reverse Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/reverse_bits.py) - * [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py) + * [Binary And Operator](bit_manipulation/binary_and_operator.py) + * [Binary Count Setbits](bit_manipulation/binary_count_setbits.py) + * [Binary Count Trailing Zeros](bit_manipulation/binary_count_trailing_zeros.py) + * [Binary Or Operator](bit_manipulation/binary_or_operator.py) + * [Binary Shifts](bit_manipulation/binary_shifts.py) + * [Binary Twos Complement](bit_manipulation/binary_twos_complement.py) + * [Binary Xor Operator](bit_manipulation/binary_xor_operator.py) + * [Count 1S Brian Kernighan Method](bit_manipulation/count_1s_brian_kernighan_method.py) + * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py) + * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py) + * [Reverse Bits](bit_manipulation/reverse_bits.py) + * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.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) + * [Chinese Remainder Theorem](blockchain/chinese_remainder_theorem.py) + * [Diophantine Equation](blockchain/diophantine_equation.py) + * [Modular Division](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](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) - * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/game_of_life.py) - * [Nagel Schrekenberg](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/nagel_schrekenberg.py) - * [One Dimensional](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/one_dimensional.py) + * [Conways Game Of Life](cellular_automata/conways_game_of_life.py) + * [Game Of Life](cellular_automata/game_of_life.py) + * [Nagel Schrekenberg](cellular_automata/nagel_schrekenberg.py) + * [One Dimensional](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) - * [Baconian Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/baconian_cipher.py) - * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) - * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) - * [Base64](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64.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) - * [Bifid](https://github.com/TheAlgorithms/Python/blob/master/ciphers/bifid.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) - * [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) - * [Diffie Hellman](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie_hellman.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) - * [Mono Alphabetic Ciphers](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mono_alphabetic_ciphers.py) - * [Morse Code](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code.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) - * [Polybius](https://github.com/TheAlgorithms/Python/blob/master/ciphers/polybius.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) - * [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) + * [A1Z26](ciphers/a1z26.py) + * [Affine Cipher](ciphers/affine_cipher.py) + * [Atbash](ciphers/atbash.py) + * [Baconian Cipher](ciphers/baconian_cipher.py) + * [Base16](ciphers/base16.py) + * [Base32](ciphers/base32.py) + * [Base64](ciphers/base64.py) + * [Base85](ciphers/base85.py) + * [Beaufort Cipher](ciphers/beaufort_cipher.py) + * [Bifid](ciphers/bifid.py) + * [Brute Force Caesar Cipher](ciphers/brute_force_caesar_cipher.py) + * [Caesar Cipher](ciphers/caesar_cipher.py) + * [Cryptomath Module](ciphers/cryptomath_module.py) + * [Decrypt Caesar With Chi Squared](ciphers/decrypt_caesar_with_chi_squared.py) + * [Deterministic Miller Rabin](ciphers/deterministic_miller_rabin.py) + * [Diffie](ciphers/diffie.py) + * [Diffie Hellman](ciphers/diffie_hellman.py) + * [Elgamal Key Generator](ciphers/elgamal_key_generator.py) + * [Enigma Machine2](ciphers/enigma_machine2.py) + * [Hill Cipher](ciphers/hill_cipher.py) + * [Mixed Keyword Cypher](ciphers/mixed_keyword_cypher.py) + * [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py) + * [Morse Code](ciphers/morse_code.py) + * [Onepad Cipher](ciphers/onepad_cipher.py) + * [Playfair Cipher](ciphers/playfair_cipher.py) + * [Polybius](ciphers/polybius.py) + * [Porta Cipher](ciphers/porta_cipher.py) + * [Rabin Miller](ciphers/rabin_miller.py) + * [Rail Fence Cipher](ciphers/rail_fence_cipher.py) + * [Rot13](ciphers/rot13.py) + * [Rsa Cipher](ciphers/rsa_cipher.py) + * [Rsa Factorization](ciphers/rsa_factorization.py) + * [Rsa Key Generator](ciphers/rsa_key_generator.py) + * [Shuffled Shift Cipher](ciphers/shuffled_shift_cipher.py) + * [Simple Keyword Cypher](ciphers/simple_keyword_cypher.py) + * [Simple Substitution Cipher](ciphers/simple_substitution_cipher.py) + * [Trafid Cipher](ciphers/trafid_cipher.py) + * [Transposition Cipher](ciphers/transposition_cipher.py) + * [Transposition Cipher Encrypt Decrypt File](ciphers/transposition_cipher_encrypt_decrypt_file.py) + * [Vigenere Cipher](ciphers/vigenere_cipher.py) + * [Xor Cipher](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) - * [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) + * [Burrows Wheeler](compression/burrows_wheeler.py) + * [Huffman](compression/huffman.py) + * [Lempel Ziv](compression/lempel_ziv.py) + * [Lempel Ziv Decompress](compression/lempel_ziv_decompress.py) + * [Peak Signal To Noise Ratio](compression/peak_signal_to_noise_ratio.py) ## Computer Vision - * [Cnn Classification](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/cnn_classification.py) - * [Flip Augmentation](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/flip_augmentation.py) - * [Harris Corner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harris_corner.py) - * [Horn Schunck](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/horn_schunck.py) - * [Mean Threshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mean_threshold.py) - * [Mosaic Augmentation](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/mosaic_augmentation.py) - * [Pooling Functions](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/pooling_functions.py) + * [Cnn Classification](computer_vision/cnn_classification.py) + * [Flip Augmentation](computer_vision/flip_augmentation.py) + * [Harris Corner](computer_vision/harris_corner.py) + * [Horn Schunck](computer_vision/horn_schunck.py) + * [Mean Threshold](computer_vision/mean_threshold.py) + * [Mosaic Augmentation](computer_vision/mosaic_augmentation.py) + * [Pooling Functions](computer_vision/pooling_functions.py) ## Conversions - * [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py) - * [Binary To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_hexadecimal.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 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) - * [Hex To Bin](https://github.com/TheAlgorithms/Python/blob/master/conversions/hex_to_bin.py) - * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/hexadecimal_to_decimal.py) - * [Length Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/length_conversion.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) - * [Prefix Conversions String](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions_string.py) - * [Pressure Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/pressure_conversions.py) - * [Rgb Hsv Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/rgb_hsv_conversion.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) - * [Volume Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/volume_conversions.py) - * [Weight Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/weight_conversion.py) + * [Binary To Decimal](conversions/binary_to_decimal.py) + * [Binary To Hexadecimal](conversions/binary_to_hexadecimal.py) + * [Binary To Octal](conversions/binary_to_octal.py) + * [Decimal To Any](conversions/decimal_to_any.py) + * [Decimal To Binary](conversions/decimal_to_binary.py) + * [Decimal To Binary Recursion](conversions/decimal_to_binary_recursion.py) + * [Decimal To Hexadecimal](conversions/decimal_to_hexadecimal.py) + * [Decimal To Octal](conversions/decimal_to_octal.py) + * [Excel Title To Column](conversions/excel_title_to_column.py) + * [Hex To Bin](conversions/hex_to_bin.py) + * [Hexadecimal To Decimal](conversions/hexadecimal_to_decimal.py) + * [Length Conversion](conversions/length_conversion.py) + * [Molecular Chemistry](conversions/molecular_chemistry.py) + * [Octal To Decimal](conversions/octal_to_decimal.py) + * [Prefix Conversions](conversions/prefix_conversions.py) + * [Prefix Conversions String](conversions/prefix_conversions_string.py) + * [Pressure Conversions](conversions/pressure_conversions.py) + * [Rgb Hsv Conversion](conversions/rgb_hsv_conversion.py) + * [Roman Numerals](conversions/roman_numerals.py) + * [Temperature Conversions](conversions/temperature_conversions.py) + * [Volume Conversions](conversions/volume_conversions.py) + * [Weight Conversion](conversions/weight_conversion.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) - * [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) - * [Merge Two Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/merge_two_binary_trees.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) - * [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) - * [Wavelet Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/wavelet_tree.py) + * [Avl Tree](data_structures/binary_tree/avl_tree.py) + * [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py) + * [Binary Search Tree](data_structures/binary_tree/binary_search_tree.py) + * [Binary Search Tree Recursive](data_structures/binary_tree/binary_search_tree_recursive.py) + * [Binary Tree Mirror](data_structures/binary_tree/binary_tree_mirror.py) + * [Binary Tree Traversals](data_structures/binary_tree/binary_tree_traversals.py) + * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) + * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) + * [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py) + * [Merge Two Binary Trees](data_structures/binary_tree/merge_two_binary_trees.py) + * [Non Recursive Segment Tree](data_structures/binary_tree/non_recursive_segment_tree.py) + * [Number Of Possible Binary Trees](data_structures/binary_tree/number_of_possible_binary_trees.py) + * [Red Black Tree](data_structures/binary_tree/red_black_tree.py) + * [Segment Tree](data_structures/binary_tree/segment_tree.py) + * [Segment Tree Other](data_structures/binary_tree/segment_tree_other.py) + * [Treap](data_structures/binary_tree/treap.py) + * [Wavelet Tree](data_structures/binary_tree/wavelet_tree.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) + * [Alternate Disjoint Set](data_structures/disjoint_set/alternate_disjoint_set.py) + * [Disjoint Set](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) + * [Double Hash](data_structures/hashing/double_hash.py) + * [Hash Table](data_structures/hashing/hash_table.py) + * [Hash Table With Linked List](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) + * [Prime Numbers](data_structures/hashing/number_theory/prime_numbers.py) + * [Quadratic Probing](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) - * [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) - * [Skew Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/skew_heap.py) + * [Binomial Heap](data_structures/heap/binomial_heap.py) + * [Heap](data_structures/heap/heap.py) + * [Heap Generic](data_structures/heap/heap_generic.py) + * [Max Heap](data_structures/heap/max_heap.py) + * [Min Heap](data_structures/heap/min_heap.py) + * [Randomized Heap](data_structures/heap/randomized_heap.py) + * [Skew Heap](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) - * [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) - * [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) - * [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) + * [Circular Linked List](data_structures/linked_list/circular_linked_list.py) + * [Deque Doubly](data_structures/linked_list/deque_doubly.py) + * [Doubly Linked List](data_structures/linked_list/doubly_linked_list.py) + * [Doubly Linked List Two](data_structures/linked_list/doubly_linked_list_two.py) + * [From Sequence](data_structures/linked_list/from_sequence.py) + * [Has Loop](data_structures/linked_list/has_loop.py) + * [Is Palindrome](data_structures/linked_list/is_palindrome.py) + * [Merge Two Lists](data_structures/linked_list/merge_two_lists.py) + * [Middle Element Of Linked List](data_structures/linked_list/middle_element_of_linked_list.py) + * [Print Reverse](data_structures/linked_list/print_reverse.py) + * [Singly Linked List](data_structures/linked_list/singly_linked_list.py) + * [Skip List](data_structures/linked_list/skip_list.py) + * [Swap Nodes](data_structures/linked_list/swap_nodes.py) * Queue - * [Circular Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/circular_queue.py) - * [Circular Queue Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/circular_queue_linked_list.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) + * [Circular Queue](data_structures/queue/circular_queue.py) + * [Circular Queue Linked List](data_structures/queue/circular_queue_linked_list.py) + * [Double Ended Queue](data_structures/queue/double_ended_queue.py) + * [Linked Queue](data_structures/queue/linked_queue.py) + * [Priority Queue Using List](data_structures/queue/priority_queue_using_list.py) + * [Queue On List](data_structures/queue/queue_on_list.py) + * [Queue On Pseudo Stack](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) - * [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) - * [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 With Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack_with_doubly_linked_list.py) - * [Stack With Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack_with_singly_linked_list.py) - * [Stock Span Problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) + * [Balanced Parentheses](data_structures/stacks/balanced_parentheses.py) + * [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py) + * [Evaluate Postfix Notations](data_structures/stacks/evaluate_postfix_notations.py) + * [Infix To Postfix Conversion](data_structures/stacks/infix_to_postfix_conversion.py) + * [Infix To Prefix Conversion](data_structures/stacks/infix_to_prefix_conversion.py) + * [Next Greater Element](data_structures/stacks/next_greater_element.py) + * [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py) + * [Prefix Evaluation](data_structures/stacks/prefix_evaluation.py) + * [Stack](data_structures/stacks/stack.py) + * [Stack With Doubly Linked List](data_structures/stacks/stack_with_doubly_linked_list.py) + * [Stack With Singly Linked List](data_structures/stacks/stack_with_singly_linked_list.py) + * [Stock Span Problem](data_structures/stacks/stock_span_problem.py) * Trie - * [Trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) + * [Trie](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) + * [Change Brightness](digital_image_processing/change_brightness.py) + * [Change Contrast](digital_image_processing/change_contrast.py) + * [Convert To Negative](digital_image_processing/convert_to_negative.py) * Dithering - * [Burkes](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/dithering/burkes.py) + * [Burkes](digital_image_processing/dithering/burkes.py) * Edge Detection - * [Canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) + * [Canny](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) - * [Gabor Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gabor_filter.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) + * [Bilateral Filter](digital_image_processing/filters/bilateral_filter.py) + * [Convolve](digital_image_processing/filters/convolve.py) + * [Gabor Filter](digital_image_processing/filters/gabor_filter.py) + * [Gaussian Filter](digital_image_processing/filters/gaussian_filter.py) + * [Median Filter](digital_image_processing/filters/median_filter.py) + * [Sobel Filter](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) + * [Histogram Stretch](digital_image_processing/histogram_equalization/histogram_stretch.py) + * [Index Calculation](digital_image_processing/index_calculation.py) * Morphological Operations - * [Dilation Operation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/morphological_operations/dilation_operation.py) - * [Erosion Operation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/morphological_operations/erosion_operation.py) + * [Dilation Operation](digital_image_processing/morphological_operations/dilation_operation.py) + * [Erosion Operation](digital_image_processing/morphological_operations/erosion_operation.py) * Resize - * [Resize](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/resize/resize.py) + * [Resize](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) - * [Test Digital Image Processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) + * [Rotation](digital_image_processing/rotation/rotation.py) + * [Sepia](digital_image_processing/sepia.py) + * [Test Digital Image Processing](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) - * [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 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) - * [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) + * [Closest Pair Of Points](divide_and_conquer/closest_pair_of_points.py) + * [Convex Hull](divide_and_conquer/convex_hull.py) + * [Heaps Algorithm](divide_and_conquer/heaps_algorithm.py) + * [Heaps Algorithm Iterative](divide_and_conquer/heaps_algorithm_iterative.py) + * [Inversions](divide_and_conquer/inversions.py) + * [Kth Order Statistic](divide_and_conquer/kth_order_statistic.py) + * [Max Difference Pair](divide_and_conquer/max_difference_pair.py) + * [Max Subarray Sum](divide_and_conquer/max_subarray_sum.py) + * [Mergesort](divide_and_conquer/mergesort.py) + * [Peak](divide_and_conquer/peak.py) + * [Power](divide_and_conquer/power.py) + * [Strassen Matrix Multiplication](divide_and_conquer/strassen_matrix_multiplication.py) ## Dynamic Programming - * [Abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) - * [All Construct](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/all_construct.py) - * [Bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) - * [Catalan Numbers](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/catalan_numbers.py) - * [Climbing Stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.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) - * [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 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 Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_coin_change.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) - * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) + * [Abbreviation](dynamic_programming/abbreviation.py) + * [All Construct](dynamic_programming/all_construct.py) + * [Bitmask](dynamic_programming/bitmask.py) + * [Catalan Numbers](dynamic_programming/catalan_numbers.py) + * [Climbing Stairs](dynamic_programming/climbing_stairs.py) + * [Edit Distance](dynamic_programming/edit_distance.py) + * [Factorial](dynamic_programming/factorial.py) + * [Fast Fibonacci](dynamic_programming/fast_fibonacci.py) + * [Fibonacci](dynamic_programming/fibonacci.py) + * [Floyd Warshall](dynamic_programming/floyd_warshall.py) + * [Fractional Knapsack](dynamic_programming/fractional_knapsack.py) + * [Fractional Knapsack 2](dynamic_programming/fractional_knapsack_2.py) + * [Integer Partition](dynamic_programming/integer_partition.py) + * [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py) + * [Knapsack](dynamic_programming/knapsack.py) + * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) + * [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py) + * [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py) + * [Longest Sub Array](dynamic_programming/longest_sub_array.py) + * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) + * [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py) + * [Max Sub Array](dynamic_programming/max_sub_array.py) + * [Max Sum Contiguous Subsequence](dynamic_programming/max_sum_contiguous_subsequence.py) + * [Minimum Coin Change](dynamic_programming/minimum_coin_change.py) + * [Minimum Cost Path](dynamic_programming/minimum_cost_path.py) + * [Minimum Partition](dynamic_programming/minimum_partition.py) + * [Minimum Steps To One](dynamic_programming/minimum_steps_to_one.py) + * [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py) + * [Rod Cutting](dynamic_programming/rod_cutting.py) + * [Subset Generation](dynamic_programming/subset_generation.py) + * [Sum Of Subset](dynamic_programming/sum_of_subset.py) ## Electronics - * [Carrier Concentration](https://github.com/TheAlgorithms/Python/blob/master/electronics/carrier_concentration.py) - * [Coulombs Law](https://github.com/TheAlgorithms/Python/blob/master/electronics/coulombs_law.py) - * [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) + * [Carrier Concentration](electronics/carrier_concentration.py) + * [Coulombs Law](electronics/coulombs_law.py) + * [Electric Power](electronics/electric_power.py) + * [Ohms Law](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) + * [Receive File](file_transfer/receive_file.py) + * [Send File](file_transfer/send_file.py) * Tests - * [Test Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/tests/test_send_file.py) + * [Test Send File](file_transfer/tests/test_send_file.py) ## Financial - * [Equated Monthly Installments](https://github.com/TheAlgorithms/Python/blob/master/financial/equated_monthly_installments.py) - * [Interest](https://github.com/TheAlgorithms/Python/blob/master/financial/interest.py) + * [Equated Monthly Installments](financial/equated_monthly_installments.py) + * [Interest](financial/interest.py) ## Fractals - * [Julia Sets](https://github.com/TheAlgorithms/Python/blob/master/fractals/julia_sets.py) - * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/fractals/koch_snowflake.py) - * [Mandelbrot](https://github.com/TheAlgorithms/Python/blob/master/fractals/mandelbrot.py) - * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/fractals/sierpinski_triangle.py) + * [Julia Sets](fractals/julia_sets.py) + * [Koch Snowflake](fractals/koch_snowflake.py) + * [Mandelbrot](fractals/mandelbrot.py) + * [Sierpinski Triangle](fractals/sierpinski_triangle.py) ## Fuzzy Logic - * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) + * [Fuzzy Operations](fuzzy_logic/fuzzy_operations.py) ## Genetic Algorithm - * [Basic String](https://github.com/TheAlgorithms/Python/blob/master/genetic_algorithm/basic_string.py) + * [Basic String](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) + * [Haversine Distance](geodesy/haversine_distance.py) + * [Lamberts Ellipsoidal Distance](geodesy/lamberts_ellipsoidal_distance.py) ## 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) + * [Bezier Curve](graphics/bezier_curve.py) + * [Vector3 For 2D Rendering](graphics/vector3_for_2d_rendering.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 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) - * [Boruvka](https://github.com/TheAlgorithms/Python/blob/master/graphs/boruvka.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) - * [Check Cycle](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_cycle.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) - * [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) - * [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) - * [Greedy Best First](https://github.com/TheAlgorithms/Python/blob/master/graphs/greedy_best_first.py) - * [Greedy Min Vertex Cover](https://github.com/TheAlgorithms/Python/blob/master/graphs/greedy_min_vertex_cover.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) - * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/graphs/markov_chain.py) - * [Matching Min Vertex Cover](https://github.com/TheAlgorithms/Python/blob/master/graphs/matching_min_vertex_cover.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) - * [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) - * [Random Graph Generator](https://github.com/TheAlgorithms/Python/blob/master/graphs/random_graph_generator.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) + * [A Star](graphs/a_star.py) + * [Articulation Points](graphs/articulation_points.py) + * [Basic Graphs](graphs/basic_graphs.py) + * [Bellman Ford](graphs/bellman_ford.py) + * [Bfs Shortest Path](graphs/bfs_shortest_path.py) + * [Bfs Zero One Shortest Path](graphs/bfs_zero_one_shortest_path.py) + * [Bidirectional A Star](graphs/bidirectional_a_star.py) + * [Bidirectional Breadth First Search](graphs/bidirectional_breadth_first_search.py) + * [Boruvka](graphs/boruvka.py) + * [Breadth First Search](graphs/breadth_first_search.py) + * [Breadth First Search 2](graphs/breadth_first_search_2.py) + * [Breadth First Search Shortest Path](graphs/breadth_first_search_shortest_path.py) + * [Check Bipartite Graph Bfs](graphs/check_bipartite_graph_bfs.py) + * [Check Bipartite Graph Dfs](graphs/check_bipartite_graph_dfs.py) + * [Check Cycle](graphs/check_cycle.py) + * [Connected Components](graphs/connected_components.py) + * [Depth First Search](graphs/depth_first_search.py) + * [Depth First Search 2](graphs/depth_first_search_2.py) + * [Dijkstra](graphs/dijkstra.py) + * [Dijkstra 2](graphs/dijkstra_2.py) + * [Dijkstra Algorithm](graphs/dijkstra_algorithm.py) + * [Dinic](graphs/dinic.py) + * [Directed And Undirected (Weighted) Graph](graphs/directed_and_undirected_(weighted)_graph.py) + * [Edmonds Karp Multiple Source And Sink](graphs/edmonds_karp_multiple_source_and_sink.py) + * [Eulerian Path And Circuit For Undirected Graph](graphs/eulerian_path_and_circuit_for_undirected_graph.py) + * [Even Tree](graphs/even_tree.py) + * [Finding Bridges](graphs/finding_bridges.py) + * [Frequent Pattern Graph Miner](graphs/frequent_pattern_graph_miner.py) + * [G Topological Sort](graphs/g_topological_sort.py) + * [Gale Shapley Bigraph](graphs/gale_shapley_bigraph.py) + * [Graph List](graphs/graph_list.py) + * [Graph Matrix](graphs/graph_matrix.py) + * [Graphs Floyd Warshall](graphs/graphs_floyd_warshall.py) + * [Greedy Best First](graphs/greedy_best_first.py) + * [Greedy Min Vertex Cover](graphs/greedy_min_vertex_cover.py) + * [Kahns Algorithm Long](graphs/kahns_algorithm_long.py) + * [Kahns Algorithm Topo](graphs/kahns_algorithm_topo.py) + * [Karger](graphs/karger.py) + * [Markov Chain](graphs/markov_chain.py) + * [Matching Min Vertex Cover](graphs/matching_min_vertex_cover.py) + * [Minimum Path Sum](graphs/minimum_path_sum.py) + * [Minimum Spanning Tree Boruvka](graphs/minimum_spanning_tree_boruvka.py) + * [Minimum Spanning Tree Kruskal](graphs/minimum_spanning_tree_kruskal.py) + * [Minimum Spanning Tree Kruskal2](graphs/minimum_spanning_tree_kruskal2.py) + * [Minimum Spanning Tree Prims](graphs/minimum_spanning_tree_prims.py) + * [Minimum Spanning Tree Prims2](graphs/minimum_spanning_tree_prims2.py) + * [Multi Heuristic Astar](graphs/multi_heuristic_astar.py) + * [Page Rank](graphs/page_rank.py) + * [Prim](graphs/prim.py) + * [Random Graph Generator](graphs/random_graph_generator.py) + * [Scc Kosaraju](graphs/scc_kosaraju.py) + * [Strongly Connected Components](graphs/strongly_connected_components.py) + * [Tarjans Scc](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) + * [Test Min Spanning Tree Kruskal](graphs/tests/test_min_spanning_tree_kruskal.py) + * [Test Min Spanning Tree Prim](graphs/tests/test_min_spanning_tree_prim.py) ## Greedy Methods - * [Optimal Merge Pattern](https://github.com/TheAlgorithms/Python/blob/master/greedy_methods/optimal_merge_pattern.py) + * [Optimal Merge Pattern](greedy_methods/optimal_merge_pattern.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) - * [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) - * [Luhn](https://github.com/TheAlgorithms/Python/blob/master/hashes/luhn.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) - * [Sha256](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha256.py) + * [Adler32](hashes/adler32.py) + * [Chaos Machine](hashes/chaos_machine.py) + * [Djb2](hashes/djb2.py) + * [Enigma Machine](hashes/enigma_machine.py) + * [Hamming Code](hashes/hamming_code.py) + * [Luhn](hashes/luhn.py) + * [Md5](hashes/md5.py) + * [Sdbm](hashes/sdbm.py) + * [Sha1](hashes/sha1.py) + * [Sha256](hashes/sha256.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) + * [Greedy Knapsack](knapsack/greedy_knapsack.py) + * [Knapsack](knapsack/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) + * [Test Greedy Knapsack](knapsack/tests/test_greedy_knapsack.py) + * [Test Knapsack](knapsack/tests/test_knapsack.py) ## 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) - * [Rayleigh Quotient](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/rayleigh_quotient.py) - * [Schur Complement](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/schur_complement.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) + * [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py) + * [Lib](linear_algebra/src/lib.py) + * [Polynom For Points](linear_algebra/src/polynom_for_points.py) + * [Power Iteration](linear_algebra/src/power_iteration.py) + * [Rayleigh Quotient](linear_algebra/src/rayleigh_quotient.py) + * [Schur Complement](linear_algebra/src/schur_complement.py) + * [Test Linear Algebra](linear_algebra/src/test_linear_algebra.py) + * [Transformations 2D](linear_algebra/src/transformations_2d.py) ## 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) + * [Astar](machine_learning/astar.py) + * [Data Transformations](machine_learning/data_transformations.py) + * [Decision Tree](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) - * [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) + * [Run](machine_learning/forecasting/run.py) + * [Gaussian Naive Bayes](machine_learning/gaussian_naive_bayes.py) + * [Gradient Boosting Regressor](machine_learning/gradient_boosting_regressor.py) + * [Gradient Descent](machine_learning/gradient_descent.py) + * [K Means Clust](machine_learning/k_means_clust.py) + * [K Nearest Neighbours](machine_learning/k_nearest_neighbours.py) + * [Knn Sklearn](machine_learning/knn_sklearn.py) + * [Linear Discriminant Analysis](machine_learning/linear_discriminant_analysis.py) + * [Linear Regression](machine_learning/linear_regression.py) * Local Weighted Learning - * [Local Weighted Learning](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/local_weighted_learning/local_weighted_learning.py) - * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) + * [Local Weighted Learning](machine_learning/local_weighted_learning/local_weighted_learning.py) + * [Logistic Regression](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) - * [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) + * [Lstm Prediction](machine_learning/lstm/lstm_prediction.py) + * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) + * [Polymonial Regression](machine_learning/polymonial_regression.py) + * [Random Forest Classifier](machine_learning/random_forest_classifier.py) + * [Random Forest Regressor](machine_learning/random_forest_regressor.py) + * [Scoring Functions](machine_learning/scoring_functions.py) + * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) + * [Similarity Search](machine_learning/similarity_search.py) + * [Support Vector Machines](machine_learning/support_vector_machines.py) + * [Word Frequency Functions](machine_learning/word_frequency_functions.py) ## Maths - * [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) - * [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 Absolute Deviation](https://github.com/TheAlgorithms/Python/blob/master/maths/average_absolute_deviation.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) - * [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) - * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation_2.py) - * [Binary Exponentiation 3](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation_3.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) - * [Check Polygon](https://github.com/TheAlgorithms/Python/blob/master/maths/check_polygon.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) - * [Decimal Isolate](https://github.com/TheAlgorithms/Python/blob/master/maths/decimal_isolate.py) - * [Double Factorial Iterative](https://github.com/TheAlgorithms/Python/blob/master/maths/double_factorial_iterative.py) - * [Double Factorial Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/double_factorial_recursive.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) - * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_gcd.py) - * [Euler Method](https://github.com/TheAlgorithms/Python/blob/master/maths/euler_method.py) - * [Euler Modified](https://github.com/TheAlgorithms/Python/blob/master/maths/euler_modified.py) - * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.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 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) - * [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) - * [Gamma Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/gamma_recursive.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) - * [Greedy Coin Change](https://github.com/TheAlgorithms/Python/blob/master/maths/greedy_coin_change.py) - * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) - * [Integration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/maths/integration_by_simpson_approx.py) - * [Is Ip V4 Address Valid](https://github.com/TheAlgorithms/Python/blob/master/maths/is_ip_v4_address_valid.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) - * [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) - * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_subarray_sum.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) - * [Max Sum Sliding Window](https://github.com/TheAlgorithms/Python/blob/master/maths/max_sum_sliding_window.py) - * [Median Of Two Arrays](https://github.com/TheAlgorithms/Python/blob/master/maths/median_of_two_arrays.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) - * [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) - * [Nevilles Method](https://github.com/TheAlgorithms/Python/blob/master/maths/nevilles_method.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 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) - * [Persistence](https://github.com/TheAlgorithms/Python/blob/master/maths/persistence.py) - * [Pi Monte Carlo Estimation](https://github.com/TheAlgorithms/Python/blob/master/maths/pi_monte_carlo_estimation.py) - * [Points Are Collinear 3D](https://github.com/TheAlgorithms/Python/blob/master/maths/points_are_collinear_3d.py) - * [Pollard Rho](https://github.com/TheAlgorithms/Python/blob/master/maths/pollard_rho.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) - * [Prime Sieve Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_sieve_eratosthenes.py) - * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/maths/primelib.py) - * [Proth Number](https://github.com/TheAlgorithms/Python/blob/master/maths/proth_number.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) - * [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) + * [3N Plus 1](maths/3n_plus_1.py) + * [Abs](maths/abs.py) + * [Abs Max](maths/abs_max.py) + * [Abs Min](maths/abs_min.py) + * [Add](maths/add.py) + * [Aliquot Sum](maths/aliquot_sum.py) + * [Allocation Number](maths/allocation_number.py) + * [Area](maths/area.py) + * [Area Under Curve](maths/area_under_curve.py) + * [Armstrong Numbers](maths/armstrong_numbers.py) + * [Average Absolute Deviation](maths/average_absolute_deviation.py) + * [Average Mean](maths/average_mean.py) + * [Average Median](maths/average_median.py) + * [Average Mode](maths/average_mode.py) + * [Bailey Borwein Plouffe](maths/bailey_borwein_plouffe.py) + * [Basic Maths](maths/basic_maths.py) + * [Binary Exp Mod](maths/binary_exp_mod.py) + * [Binary Exponentiation](maths/binary_exponentiation.py) + * [Binary Exponentiation 2](maths/binary_exponentiation_2.py) + * [Binary Exponentiation 3](maths/binary_exponentiation_3.py) + * [Binomial Coefficient](maths/binomial_coefficient.py) + * [Binomial Distribution](maths/binomial_distribution.py) + * [Bisection](maths/bisection.py) + * [Ceil](maths/ceil.py) + * [Check Polygon](maths/check_polygon.py) + * [Chudnovsky Algorithm](maths/chudnovsky_algorithm.py) + * [Collatz Sequence](maths/collatz_sequence.py) + * [Combinations](maths/combinations.py) + * [Decimal Isolate](maths/decimal_isolate.py) + * [Double Factorial Iterative](maths/double_factorial_iterative.py) + * [Double Factorial Recursive](maths/double_factorial_recursive.py) + * [Entropy](maths/entropy.py) + * [Euclidean Distance](maths/euclidean_distance.py) + * [Euclidean Gcd](maths/euclidean_gcd.py) + * [Euler Method](maths/euler_method.py) + * [Euler Modified](maths/euler_modified.py) + * [Eulers Totient](maths/eulers_totient.py) + * [Extended Euclidean Algorithm](maths/extended_euclidean_algorithm.py) + * [Factorial Iterative](maths/factorial_iterative.py) + * [Factorial Recursive](maths/factorial_recursive.py) + * [Factors](maths/factors.py) + * [Fermat Little Theorem](maths/fermat_little_theorem.py) + * [Fibonacci](maths/fibonacci.py) + * [Find Max](maths/find_max.py) + * [Find Max Recursion](maths/find_max_recursion.py) + * [Find Min](maths/find_min.py) + * [Find Min Recursion](maths/find_min_recursion.py) + * [Floor](maths/floor.py) + * [Gamma](maths/gamma.py) + * [Gamma Recursive](maths/gamma_recursive.py) + * [Gaussian](maths/gaussian.py) + * [Greatest Common Divisor](maths/greatest_common_divisor.py) + * [Greedy Coin Change](maths/greedy_coin_change.py) + * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) + * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) + * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) + * [Is Square Free](maths/is_square_free.py) + * [Jaccard Similarity](maths/jaccard_similarity.py) + * [Kadanes](maths/kadanes.py) + * [Karatsuba](maths/karatsuba.py) + * [Krishnamurthy Number](maths/krishnamurthy_number.py) + * [Kth Lexicographic Permutation](maths/kth_lexicographic_permutation.py) + * [Largest Of Very Large Numbers](maths/largest_of_very_large_numbers.py) + * [Largest Subarray Sum](maths/largest_subarray_sum.py) + * [Least Common Multiple](maths/least_common_multiple.py) + * [Line Length](maths/line_length.py) + * [Lucas Lehmer Primality Test](maths/lucas_lehmer_primality_test.py) + * [Lucas Series](maths/lucas_series.py) + * [Matrix Exponentiation](maths/matrix_exponentiation.py) + * [Max Sum Sliding Window](maths/max_sum_sliding_window.py) + * [Median Of Two Arrays](maths/median_of_two_arrays.py) + * [Miller Rabin](maths/miller_rabin.py) + * [Mobius Function](maths/mobius_function.py) + * [Modular Exponential](maths/modular_exponential.py) + * [Monte Carlo](maths/monte_carlo.py) + * [Monte Carlo Dice](maths/monte_carlo_dice.py) + * [Nevilles Method](maths/nevilles_method.py) + * [Newton Raphson](maths/newton_raphson.py) + * [Number Of Digits](maths/number_of_digits.py) + * [Numerical Integration](maths/numerical_integration.py) + * [Perfect Cube](maths/perfect_cube.py) + * [Perfect Number](maths/perfect_number.py) + * [Perfect Square](maths/perfect_square.py) + * [Persistence](maths/persistence.py) + * [Pi Monte Carlo Estimation](maths/pi_monte_carlo_estimation.py) + * [Points Are Collinear 3D](maths/points_are_collinear_3d.py) + * [Pollard Rho](maths/pollard_rho.py) + * [Polynomial Evaluation](maths/polynomial_evaluation.py) + * [Power Using Recursion](maths/power_using_recursion.py) + * [Prime Check](maths/prime_check.py) + * [Prime Factors](maths/prime_factors.py) + * [Prime Numbers](maths/prime_numbers.py) + * [Prime Sieve Eratosthenes](maths/prime_sieve_eratosthenes.py) + * [Primelib](maths/primelib.py) + * [Proth Number](maths/proth_number.py) + * [Pythagoras](maths/pythagoras.py) + * [Qr Decomposition](maths/qr_decomposition.py) + * [Quadratic Equations Complex Numbers](maths/quadratic_equations_complex_numbers.py) + * [Radians](maths/radians.py) + * [Radix2 Fft](maths/radix2_fft.py) + * [Relu](maths/relu.py) + * [Runge Kutta](maths/runge_kutta.py) + * [Segmented Sieve](maths/segmented_sieve.py) * Series - * [Arithmetic](https://github.com/TheAlgorithms/Python/blob/master/maths/series/arithmetic.py) - * [Geometric](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric.py) - * [Geometric Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_series.py) - * [Harmonic](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic.py) - * [Harmonic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic_series.py) - * [Hexagonal Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/series/hexagonal_numbers.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) - * [Sock Merchant](https://github.com/TheAlgorithms/Python/blob/master/maths/sock_merchant.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) - * [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) - * [Sylvester Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/sylvester_sequence.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) - * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/triplet_sum.py) - * [Two Pointer](https://github.com/TheAlgorithms/Python/blob/master/maths/two_pointer.py) - * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/two_sum.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) + * [Arithmetic](maths/series/arithmetic.py) + * [Geometric](maths/series/geometric.py) + * [Geometric Series](maths/series/geometric_series.py) + * [Harmonic](maths/series/harmonic.py) + * [Harmonic Series](maths/series/harmonic_series.py) + * [Hexagonal Numbers](maths/series/hexagonal_numbers.py) + * [P Series](maths/series/p_series.py) + * [Sieve Of Eratosthenes](maths/sieve_of_eratosthenes.py) + * [Sigmoid](maths/sigmoid.py) + * [Simpson Rule](maths/simpson_rule.py) + * [Sin](maths/sin.py) + * [Sock Merchant](maths/sock_merchant.py) + * [Softmax](maths/softmax.py) + * [Square Root](maths/square_root.py) + * [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py) + * [Sum Of Digits](maths/sum_of_digits.py) + * [Sum Of Geometric Progression](maths/sum_of_geometric_progression.py) + * [Sylvester Sequence](maths/sylvester_sequence.py) + * [Test Prime Check](maths/test_prime_check.py) + * [Trapezoidal Rule](maths/trapezoidal_rule.py) + * [Triplet Sum](maths/triplet_sum.py) + * [Two Pointer](maths/two_pointer.py) + * [Two Sum](maths/two_sum.py) + * [Ugly Numbers](maths/ugly_numbers.py) + * [Volume](maths/volume.py) + * [Zellers Congruence](maths/zellers_congruence.py) ## 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) - * [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) + * [Count Islands In Matrix](matrix/count_islands_in_matrix.py) + * [Inverse Of Matrix](matrix/inverse_of_matrix.py) + * [Matrix Class](matrix/matrix_class.py) + * [Matrix Operation](matrix/matrix_operation.py) + * [Nth Fibonacci Using Matrix Exponentiation](matrix/nth_fibonacci_using_matrix_exponentiation.py) + * [Rotate Matrix](matrix/rotate_matrix.py) + * [Searching In Sorted Matrix](matrix/searching_in_sorted_matrix.py) + * [Sherman Morrison](matrix/sherman_morrison.py) + * [Spiral Print](matrix/spiral_print.py) * Tests - * [Test Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) + * [Test Matrix Operation](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](networking_flow/ford_fulkerson.py) + * [Minimum Cut](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) + * [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py) + * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) + * [Convolution Neural Network](neural_network/convolution_neural_network.py) + * [Perceptron](neural_network/perceptron.py) ## Other - * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) - * [Alternative List Arrange](https://github.com/TheAlgorithms/Python/blob/master/other/alternative_list_arrange.py) - * [Check Strong Password](https://github.com/TheAlgorithms/Python/blob/master/other/check_strong_password.py) - * [Davisb Putnamb Logemannb Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davisb_putnamb_logemannb_loveland.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) - * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) - * [Gauss Easter](https://github.com/TheAlgorithms/Python/blob/master/other/gauss_easter.py) - * [Graham Scan](https://github.com/TheAlgorithms/Python/blob/master/other/graham_scan.py) - * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.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) - * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) - * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.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) - * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) + * [Activity Selection](other/activity_selection.py) + * [Alternative List Arrange](other/alternative_list_arrange.py) + * [Check Strong Password](other/check_strong_password.py) + * [Davisb Putnamb Logemannb Loveland](other/davisb_putnamb_logemannb_loveland.py) + * [Dijkstra Bankers Algorithm](other/dijkstra_bankers_algorithm.py) + * [Doomsday](other/doomsday.py) + * [Fischer Yates Shuffle](other/fischer_yates_shuffle.py) + * [Gauss Easter](other/gauss_easter.py) + * [Graham Scan](other/graham_scan.py) + * [Greedy](other/greedy.py) + * [Least Recently Used](other/least_recently_used.py) + * [Lfu Cache](other/lfu_cache.py) + * [Linear Congruential Generator](other/linear_congruential_generator.py) + * [Lru Cache](other/lru_cache.py) + * [Magicdiamondpattern](other/magicdiamondpattern.py) + * [Nested Brackets](other/nested_brackets.py) + * [Password Generator](other/password_generator.py) + * [Scoring Algorithm](other/scoring_algorithm.py) + * [Sdes](other/sdes.py) + * [Tower Of Hanoi](other/tower_of_hanoi.py) ## Physics - * [Horizontal Projectile Motion](https://github.com/TheAlgorithms/Python/blob/master/physics/horizontal_projectile_motion.py) - * [N Body Simulation](https://github.com/TheAlgorithms/Python/blob/master/physics/n_body_simulation.py) - * [Newtons Second Law Of Motion](https://github.com/TheAlgorithms/Python/blob/master/physics/newtons_second_law_of_motion.py) + * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) + * [Lorenz Transformation Four Vector](physics/lorenz_transformation_four_vector.py) + * [N Body Simulation](physics/n_body_simulation.py) + * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) ## Project Euler * 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) + * [Sol1](project_euler/problem_001/sol1.py) + * [Sol2](project_euler/problem_001/sol2.py) + * [Sol3](project_euler/problem_001/sol3.py) + * [Sol4](project_euler/problem_001/sol4.py) + * [Sol5](project_euler/problem_001/sol5.py) + * [Sol6](project_euler/problem_001/sol6.py) + * [Sol7](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) + * [Sol1](project_euler/problem_002/sol1.py) + * [Sol2](project_euler/problem_002/sol2.py) + * [Sol3](project_euler/problem_002/sol3.py) + * [Sol4](project_euler/problem_002/sol4.py) + * [Sol5](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) + * [Sol1](project_euler/problem_003/sol1.py) + * [Sol2](project_euler/problem_003/sol2.py) + * [Sol3](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) + * [Sol1](project_euler/problem_004/sol1.py) + * [Sol2](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) + * [Sol1](project_euler/problem_005/sol1.py) + * [Sol2](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) + * [Sol1](project_euler/problem_006/sol1.py) + * [Sol2](project_euler/problem_006/sol2.py) + * [Sol3](project_euler/problem_006/sol3.py) + * [Sol4](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) + * [Sol1](project_euler/problem_007/sol1.py) + * [Sol2](project_euler/problem_007/sol2.py) + * [Sol3](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) + * [Sol1](project_euler/problem_008/sol1.py) + * [Sol2](project_euler/problem_008/sol2.py) + * [Sol3](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) + * [Sol1](project_euler/problem_009/sol1.py) + * [Sol2](project_euler/problem_009/sol2.py) + * [Sol3](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) + * [Sol1](project_euler/problem_010/sol1.py) + * [Sol2](project_euler/problem_010/sol2.py) + * [Sol3](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) + * [Sol1](project_euler/problem_011/sol1.py) + * [Sol2](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) + * [Sol1](project_euler/problem_012/sol1.py) + * [Sol2](project_euler/problem_012/sol2.py) * Problem 013 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_013/sol1.py) + * [Sol1](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) + * [Sol1](project_euler/problem_014/sol1.py) + * [Sol2](project_euler/problem_014/sol2.py) * Problem 015 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_015/sol1.py) + * [Sol1](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) + * [Sol1](project_euler/problem_016/sol1.py) + * [Sol2](project_euler/problem_016/sol2.py) * Problem 017 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_017/sol1.py) + * [Sol1](project_euler/problem_017/sol1.py) * Problem 018 - * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_018/solution.py) + * [Solution](project_euler/problem_018/solution.py) * Problem 019 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_019/sol1.py) + * [Sol1](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) + * [Sol1](project_euler/problem_020/sol1.py) + * [Sol2](project_euler/problem_020/sol2.py) + * [Sol3](project_euler/problem_020/sol3.py) + * [Sol4](project_euler/problem_020/sol4.py) * Problem 021 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_021/sol1.py) + * [Sol1](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) + * [Sol1](project_euler/problem_022/sol1.py) + * [Sol2](project_euler/problem_022/sol2.py) * Problem 023 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_023/sol1.py) + * [Sol1](project_euler/problem_023/sol1.py) * Problem 024 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_024/sol1.py) + * [Sol1](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) + * [Sol1](project_euler/problem_025/sol1.py) + * [Sol2](project_euler/problem_025/sol2.py) + * [Sol3](project_euler/problem_025/sol3.py) * Problem 026 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_026/sol1.py) + * [Sol1](project_euler/problem_026/sol1.py) * Problem 027 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_027/sol1.py) + * [Sol1](project_euler/problem_027/sol1.py) * Problem 028 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_028/sol1.py) + * [Sol1](project_euler/problem_028/sol1.py) * Problem 029 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_029/sol1.py) + * [Sol1](project_euler/problem_029/sol1.py) * Problem 030 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_030/sol1.py) + * [Sol1](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) + * [Sol1](project_euler/problem_031/sol1.py) + * [Sol2](project_euler/problem_031/sol2.py) * Problem 032 - * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_032/sol32.py) + * [Sol32](project_euler/problem_032/sol32.py) * Problem 033 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_033/sol1.py) + * [Sol1](project_euler/problem_033/sol1.py) * Problem 034 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_034/sol1.py) + * [Sol1](project_euler/problem_034/sol1.py) * Problem 035 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_035/sol1.py) + * [Sol1](project_euler/problem_035/sol1.py) * Problem 036 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_036/sol1.py) + * [Sol1](project_euler/problem_036/sol1.py) * Problem 037 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_037/sol1.py) + * [Sol1](project_euler/problem_037/sol1.py) * Problem 038 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_038/sol1.py) + * [Sol1](project_euler/problem_038/sol1.py) * Problem 039 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_039/sol1.py) + * [Sol1](project_euler/problem_039/sol1.py) * Problem 040 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_040/sol1.py) + * [Sol1](project_euler/problem_040/sol1.py) * Problem 041 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_041/sol1.py) + * [Sol1](project_euler/problem_041/sol1.py) * Problem 042 - * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_042/solution42.py) + * [Solution42](project_euler/problem_042/solution42.py) * Problem 043 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_043/sol1.py) + * [Sol1](project_euler/problem_043/sol1.py) * Problem 044 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_044/sol1.py) + * [Sol1](project_euler/problem_044/sol1.py) * Problem 045 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_045/sol1.py) + * [Sol1](project_euler/problem_045/sol1.py) * Problem 046 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_046/sol1.py) + * [Sol1](project_euler/problem_046/sol1.py) * Problem 047 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_047/sol1.py) + * [Sol1](project_euler/problem_047/sol1.py) * Problem 048 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_048/sol1.py) + * [Sol1](project_euler/problem_048/sol1.py) * Problem 049 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_049/sol1.py) + * [Sol1](project_euler/problem_049/sol1.py) * Problem 050 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_050/sol1.py) + * [Sol1](project_euler/problem_050/sol1.py) * Problem 051 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_051/sol1.py) + * [Sol1](project_euler/problem_051/sol1.py) * Problem 052 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_052/sol1.py) + * [Sol1](project_euler/problem_052/sol1.py) * Problem 053 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_053/sol1.py) + * [Sol1](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) + * [Sol1](project_euler/problem_054/sol1.py) + * [Test Poker Hand](project_euler/problem_054/test_poker_hand.py) * Problem 055 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_055/sol1.py) + * [Sol1](project_euler/problem_055/sol1.py) * Problem 056 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_056/sol1.py) + * [Sol1](project_euler/problem_056/sol1.py) * Problem 057 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_057/sol1.py) + * [Sol1](project_euler/problem_057/sol1.py) * Problem 058 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_058/sol1.py) + * [Sol1](project_euler/problem_058/sol1.py) * Problem 059 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_059/sol1.py) + * [Sol1](project_euler/problem_059/sol1.py) * Problem 062 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_062/sol1.py) + * [Sol1](project_euler/problem_062/sol1.py) * Problem 063 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_063/sol1.py) + * [Sol1](project_euler/problem_063/sol1.py) * Problem 064 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_064/sol1.py) + * [Sol1](project_euler/problem_064/sol1.py) * Problem 065 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_065/sol1.py) + * [Sol1](project_euler/problem_065/sol1.py) * Problem 067 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol2.py) + * [Sol1](project_euler/problem_067/sol1.py) + * [Sol2](project_euler/problem_067/sol2.py) * Problem 068 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_068/sol1.py) + * [Sol1](project_euler/problem_068/sol1.py) * Problem 069 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_069/sol1.py) + * [Sol1](project_euler/problem_069/sol1.py) * Problem 070 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_070/sol1.py) + * [Sol1](project_euler/problem_070/sol1.py) * Problem 071 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_071/sol1.py) + * [Sol1](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) + * [Sol1](project_euler/problem_072/sol1.py) + * [Sol2](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) + * [Sol1](project_euler/problem_074/sol1.py) + * [Sol2](project_euler/problem_074/sol2.py) * Problem 075 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_075/sol1.py) + * [Sol1](project_euler/problem_075/sol1.py) * Problem 076 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_076/sol1.py) + * [Sol1](project_euler/problem_076/sol1.py) * Problem 077 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_077/sol1.py) + * [Sol1](project_euler/problem_077/sol1.py) * Problem 078 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_078/sol1.py) + * [Sol1](project_euler/problem_078/sol1.py) * Problem 080 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_080/sol1.py) + * [Sol1](project_euler/problem_080/sol1.py) * Problem 081 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_081/sol1.py) + * [Sol1](project_euler/problem_081/sol1.py) * Problem 085 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_085/sol1.py) + * [Sol1](project_euler/problem_085/sol1.py) * Problem 086 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_086/sol1.py) + * [Sol1](project_euler/problem_086/sol1.py) * Problem 087 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_087/sol1.py) + * [Sol1](project_euler/problem_087/sol1.py) * Problem 089 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_089/sol1.py) + * [Sol1](project_euler/problem_089/sol1.py) * Problem 091 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_091/sol1.py) + * [Sol1](project_euler/problem_091/sol1.py) * Problem 092 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_092/sol1.py) + * [Sol1](project_euler/problem_092/sol1.py) * Problem 097 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_097/sol1.py) + * [Sol1](project_euler/problem_097/sol1.py) * Problem 099 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_099/sol1.py) + * [Sol1](project_euler/problem_099/sol1.py) * Problem 101 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_101/sol1.py) + * [Sol1](project_euler/problem_101/sol1.py) * Problem 102 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_102/sol1.py) + * [Sol1](project_euler/problem_102/sol1.py) * Problem 104 - * [Sol](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_104/sol.py) + * [Sol](project_euler/problem_104/sol.py) * Problem 107 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_107/sol1.py) + * [Sol1](project_euler/problem_107/sol1.py) * Problem 109 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_109/sol1.py) + * [Sol1](project_euler/problem_109/sol1.py) * Problem 112 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) + * [Sol1](project_euler/problem_112/sol1.py) * Problem 113 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_113/sol1.py) + * [Sol1](project_euler/problem_113/sol1.py) * Problem 119 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_119/sol1.py) + * [Sol1](project_euler/problem_119/sol1.py) * Problem 120 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_120/sol1.py) + * [Sol1](project_euler/problem_120/sol1.py) * Problem 121 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_121/sol1.py) + * [Sol1](project_euler/problem_121/sol1.py) * Problem 123 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_123/sol1.py) + * [Sol1](project_euler/problem_123/sol1.py) * Problem 125 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) + * [Sol1](project_euler/problem_125/sol1.py) * Problem 129 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_129/sol1.py) + * [Sol1](project_euler/problem_129/sol1.py) * Problem 135 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_135/sol1.py) + * [Sol1](project_euler/problem_135/sol1.py) * Problem 144 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_144/sol1.py) + * [Sol1](project_euler/problem_144/sol1.py) * Problem 145 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_145/sol1.py) + * [Sol1](project_euler/problem_145/sol1.py) * Problem 173 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) + * [Sol1](project_euler/problem_173/sol1.py) * Problem 174 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_174/sol1.py) + * [Sol1](project_euler/problem_174/sol1.py) * Problem 180 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_180/sol1.py) + * [Sol1](project_euler/problem_180/sol1.py) * Problem 188 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_188/sol1.py) + * [Sol1](project_euler/problem_188/sol1.py) * Problem 191 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) + * [Sol1](project_euler/problem_191/sol1.py) * Problem 203 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_203/sol1.py) + * [Sol1](project_euler/problem_203/sol1.py) * Problem 205 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_205/sol1.py) + * [Sol1](project_euler/problem_205/sol1.py) * Problem 206 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_206/sol1.py) + * [Sol1](project_euler/problem_206/sol1.py) * Problem 207 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_207/sol1.py) + * [Sol1](project_euler/problem_207/sol1.py) * Problem 234 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) + * [Sol1](project_euler/problem_234/sol1.py) * Problem 301 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_301/sol1.py) + * [Sol1](project_euler/problem_301/sol1.py) * Problem 493 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_493/sol1.py) + * [Sol1](project_euler/problem_493/sol1.py) * Problem 551 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) + * [Sol1](project_euler/problem_551/sol1.py) * Problem 686 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_686/sol1.py) + * [Sol1](project_euler/problem_686/sol1.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) - * [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) + * [Deutsch Jozsa](quantum/deutsch_jozsa.py) + * [Half Adder](quantum/half_adder.py) + * [Not Gate](quantum/not_gate.py) + * [Quantum Entanglement](quantum/quantum_entanglement.py) + * [Ripple Adder Classic](quantum/ripple_adder_classic.py) + * [Single Qubit Measure](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) - * [Shortest Job First](https://github.com/TheAlgorithms/Python/blob/master/scheduling/shortest_job_first.py) + * [First Come First Served](scheduling/first_come_first_served.py) + * [Multi Level Feedback Queue](scheduling/multi_level_feedback_queue.py) + * [Non Preemptive Shortest Job First](scheduling/non_preemptive_shortest_job_first.py) + * [Round Robin](scheduling/round_robin.py) + * [Shortest Job First](scheduling/shortest_job_first.py) ## Searches - * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) - * [Binary Tree Traversal](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_tree_traversal.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) - * [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) - * [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) + * [Binary Search](searches/binary_search.py) + * [Binary Tree Traversal](searches/binary_tree_traversal.py) + * [Double Linear Search](searches/double_linear_search.py) + * [Double Linear Search Recursion](searches/double_linear_search_recursion.py) + * [Fibonacci Search](searches/fibonacci_search.py) + * [Hill Climbing](searches/hill_climbing.py) + * [Interpolation Search](searches/interpolation_search.py) + * [Jump Search](searches/jump_search.py) + * [Linear Search](searches/linear_search.py) + * [Quick Select](searches/quick_select.py) + * [Sentinel Linear Search](searches/sentinel_linear_search.py) + * [Simple Binary Search](searches/simple_binary_search.py) + * [Simulated Annealing](searches/simulated_annealing.py) + * [Tabu Search](searches/tabu_search.py) + * [Ternary Search](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) - * [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) - * [Dutch National Flag Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/dutch_national_flag_sort.py) - * [Exchange Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/exchange_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) - * [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) - * [Msd Radix Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/msd_radix_sort.py) - * [Natural Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/natural_sort.py) - * [Odd Even Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_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) - * [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) - * [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 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 Mergesort Array](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_mergesort_array.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) - * [Slowsort](https://github.com/TheAlgorithms/Python/blob/master/sorts/slowsort.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) - * [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) + * [Bead Sort](sorts/bead_sort.py) + * [Bitonic Sort](sorts/bitonic_sort.py) + * [Bogo Sort](sorts/bogo_sort.py) + * [Bubble Sort](sorts/bubble_sort.py) + * [Bucket Sort](sorts/bucket_sort.py) + * [Cocktail Shaker Sort](sorts/cocktail_shaker_sort.py) + * [Comb Sort](sorts/comb_sort.py) + * [Counting Sort](sorts/counting_sort.py) + * [Cycle Sort](sorts/cycle_sort.py) + * [Double Sort](sorts/double_sort.py) + * [Dutch National Flag Sort](sorts/dutch_national_flag_sort.py) + * [Exchange Sort](sorts/exchange_sort.py) + * [External Sort](sorts/external_sort.py) + * [Gnome Sort](sorts/gnome_sort.py) + * [Heap Sort](sorts/heap_sort.py) + * [Insertion Sort](sorts/insertion_sort.py) + * [Intro Sort](sorts/intro_sort.py) + * [Iterative Merge Sort](sorts/iterative_merge_sort.py) + * [Merge Insertion Sort](sorts/merge_insertion_sort.py) + * [Merge Sort](sorts/merge_sort.py) + * [Msd Radix Sort](sorts/msd_radix_sort.py) + * [Natural Sort](sorts/natural_sort.py) + * [Odd Even Sort](sorts/odd_even_sort.py) + * [Odd Even Transposition Parallel](sorts/odd_even_transposition_parallel.py) + * [Odd Even Transposition Single Threaded](sorts/odd_even_transposition_single_threaded.py) + * [Pancake Sort](sorts/pancake_sort.py) + * [Patience Sort](sorts/patience_sort.py) + * [Pigeon Sort](sorts/pigeon_sort.py) + * [Pigeonhole Sort](sorts/pigeonhole_sort.py) + * [Quick Sort](sorts/quick_sort.py) + * [Quick Sort 3 Partition](sorts/quick_sort_3_partition.py) + * [Radix Sort](sorts/radix_sort.py) + * [Random Normal Distribution Quicksort](sorts/random_normal_distribution_quicksort.py) + * [Random Pivot Quick Sort](sorts/random_pivot_quick_sort.py) + * [Recursive Bubble Sort](sorts/recursive_bubble_sort.py) + * [Recursive Insertion Sort](sorts/recursive_insertion_sort.py) + * [Recursive Mergesort Array](sorts/recursive_mergesort_array.py) + * [Recursive Quick Sort](sorts/recursive_quick_sort.py) + * [Selection Sort](sorts/selection_sort.py) + * [Shell Sort](sorts/shell_sort.py) + * [Slowsort](sorts/slowsort.py) + * [Stooge Sort](sorts/stooge_sort.py) + * [Strand Sort](sorts/strand_sort.py) + * [Tim Sort](sorts/tim_sort.py) + * [Topological Sort](sorts/topological_sort.py) + * [Tree Sort](sorts/tree_sort.py) + * [Unknown Sort](sorts/unknown_sort.py) + * [Wiggle Sort](sorts/wiggle_sort.py) ## Strings - * [Aho Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho_corasick.py) - * [Alternative String Arrange](https://github.com/TheAlgorithms/Python/blob/master/strings/alternative_string_arrange.py) - * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/anagrams.py) - * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/strings/autocomplete_using_trie.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) - * [Credit Card Validator](https://github.com/TheAlgorithms/Python/blob/master/strings/credit_card_validator.py) - * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/strings/detecting_english_programmatically.py) - * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/strings/frequency_finder.py) - * [Indian Phone Validator](https://github.com/TheAlgorithms/Python/blob/master/strings/indian_phone_validator.py) - * [Is Contains Unique Chars](https://github.com/TheAlgorithms/Python/blob/master/strings/is_contains_unique_chars.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) - * [Join](https://github.com/TheAlgorithms/Python/blob/master/strings/join.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) - * [Ngram](https://github.com/TheAlgorithms/Python/blob/master/strings/ngram.py) - * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/palindrome.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 Letters](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_letters.py) - * [Reverse Long Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_long_words.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) - * [Wildcard Pattern Matching](https://github.com/TheAlgorithms/Python/blob/master/strings/wildcard_pattern_matching.py) - * [Word Occurrence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurrence.py) - * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/strings/word_patterns.py) - * [Z Function](https://github.com/TheAlgorithms/Python/blob/master/strings/z_function.py) + * [Aho Corasick](strings/aho_corasick.py) + * [Alternative String Arrange](strings/alternative_string_arrange.py) + * [Anagrams](strings/anagrams.py) + * [Autocomplete Using Trie](strings/autocomplete_using_trie.py) + * [Boyer Moore Search](strings/boyer_moore_search.py) + * [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py) + * [Capitalize](strings/capitalize.py) + * [Check Anagrams](strings/check_anagrams.py) + * [Check Pangram](strings/check_pangram.py) + * [Credit Card Validator](strings/credit_card_validator.py) + * [Detecting English Programmatically](strings/detecting_english_programmatically.py) + * [Frequency Finder](strings/frequency_finder.py) + * [Indian Phone Validator](strings/indian_phone_validator.py) + * [Is Contains Unique Chars](strings/is_contains_unique_chars.py) + * [Is Palindrome](strings/is_palindrome.py) + * [Jaro Winkler](strings/jaro_winkler.py) + * [Join](strings/join.py) + * [Knuth Morris Pratt](strings/knuth_morris_pratt.py) + * [Levenshtein Distance](strings/levenshtein_distance.py) + * [Lower](strings/lower.py) + * [Manacher](strings/manacher.py) + * [Min Cost String Conversion](strings/min_cost_string_conversion.py) + * [Naive String Search](strings/naive_string_search.py) + * [Ngram](strings/ngram.py) + * [Palindrome](strings/palindrome.py) + * [Prefix Function](strings/prefix_function.py) + * [Rabin Karp](strings/rabin_karp.py) + * [Remove Duplicate](strings/remove_duplicate.py) + * [Reverse Letters](strings/reverse_letters.py) + * [Reverse Long Words](strings/reverse_long_words.py) + * [Reverse Words](strings/reverse_words.py) + * [Split](strings/split.py) + * [Upper](strings/upper.py) + * [Wildcard Pattern Matching](strings/wildcard_pattern_matching.py) + * [Word Occurrence](strings/word_occurrence.py) + * [Word Patterns](strings/word_patterns.py) + * [Z Function](strings/z_function.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) - * [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) - * [Download Images From Google Query](https://github.com/TheAlgorithms/Python/blob/master/web_programming/download_images_from_google_query.py) - * [Emails From Url](https://github.com/TheAlgorithms/Python/blob/master/web_programming/emails_from_url.py) - * [Fetch Anime And Play](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_anime_and_play.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) - * [Fetch Well Rx Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_well_rx_price.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) - * [Get Top Hn Posts](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_top_hn_posts.py) - * [Get User Tweets](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_user_tweets.py) - * [Giphy](https://github.com/TheAlgorithms/Python/blob/master/web_programming/giphy.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) - * [Nasa Data](https://github.com/TheAlgorithms/Python/blob/master/web_programming/nasa_data.py) - * [Random Anime Character](https://github.com/TheAlgorithms/Python/blob/master/web_programming/random_anime_character.py) - * [Recaptcha Verification](https://github.com/TheAlgorithms/Python/blob/master/web_programming/recaptcha_verification.py) - * [Reddit](https://github.com/TheAlgorithms/Python/blob/master/web_programming/reddit.py) - * [Search Books By Isbn](https://github.com/TheAlgorithms/Python/blob/master/web_programming/search_books_by_isbn.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) + * [Co2 Emission](web_programming/co2_emission.py) + * [Covid Stats Via Xpath](web_programming/covid_stats_via_xpath.py) + * [Crawl Google Results](web_programming/crawl_google_results.py) + * [Crawl Google Scholar Citation](web_programming/crawl_google_scholar_citation.py) + * [Currency Converter](web_programming/currency_converter.py) + * [Current Stock Price](web_programming/current_stock_price.py) + * [Current Weather](web_programming/current_weather.py) + * [Daily Horoscope](web_programming/daily_horoscope.py) + * [Download Images From Google Query](web_programming/download_images_from_google_query.py) + * [Emails From Url](web_programming/emails_from_url.py) + * [Fetch Anime And Play](web_programming/fetch_anime_and_play.py) + * [Fetch Bbc News](web_programming/fetch_bbc_news.py) + * [Fetch Github Info](web_programming/fetch_github_info.py) + * [Fetch Jobs](web_programming/fetch_jobs.py) + * [Fetch Well Rx Price](web_programming/fetch_well_rx_price.py) + * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) + * [Get Imdbtop](web_programming/get_imdbtop.py) + * [Get Top Hn Posts](web_programming/get_top_hn_posts.py) + * [Get User Tweets](web_programming/get_user_tweets.py) + * [Giphy](web_programming/giphy.py) + * [Instagram Crawler](web_programming/instagram_crawler.py) + * [Instagram Pic](web_programming/instagram_pic.py) + * [Instagram Video](web_programming/instagram_video.py) + * [Nasa Data](web_programming/nasa_data.py) + * [Random Anime Character](web_programming/random_anime_character.py) + * [Recaptcha Verification](web_programming/recaptcha_verification.py) + * [Reddit](web_programming/reddit.py) + * [Search Books By Isbn](web_programming/search_books_by_isbn.py) + * [Slack Message](web_programming/slack_message.py) + * [Test Fetch Github Info](web_programming/test_fetch_github_info.py) + * [World Covid19 Stats](web_programming/world_covid19_stats.py) diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index 7a4bc3a4b258..71577fe6d4ac 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -3,8 +3,6 @@ import os from typing import Iterator -URL_BASE = "https://github.com/TheAlgorithms/Python/blob/master" - def good_file_paths(top_dir: str = ".") -> Iterator[str]: for dir_path, dir_names, filenames in os.walk(top_dir): @@ -36,7 +34,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((filepath, filename)).replace(" ", "%20") filename = os.path.splitext(filename.replace("_", " ").title())[0] print(f"{md_prefix(indent)} [{filename}]({url})") From 42a80cdaf689b03b326164862318039bd43bbff1 Mon Sep 17 00:00:00 2001 From: Nivid Patel <66813410+nivid26@users.noreply.github.com> Date: Wed, 22 Jun 2022 00:04:18 -0400 Subject: [PATCH 1804/2908] Update basic_maths.py (#6017) --- maths/basic_maths.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maths/basic_maths.py b/maths/basic_maths.py index 58e797772a28..26c52c54983e 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -57,6 +57,8 @@ def number_of_divisors(n: int) -> int: temp += 1 n = int(n / i) div *= temp + if n > 1: + div *= 2 return div From a80e5aadf30817251989378e8d908ca18f733a2f Mon Sep 17 00:00:00 2001 From: yulmam <70622601+yulmam@users.noreply.github.com> Date: Wed, 22 Jun 2022 13:23:52 +0900 Subject: [PATCH 1805/2908] add highest_response_ratio_next.py (#6183) * add highest_response_ratio_next.py * Update highest_response_ratio_next.py * Update highest_response_ratio_next.py --- scheduling/highest_response_ratio_next.py | 118 ++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 scheduling/highest_response_ratio_next.py diff --git a/scheduling/highest_response_ratio_next.py b/scheduling/highest_response_ratio_next.py new file mode 100644 index 000000000000..a5c62ddbe952 --- /dev/null +++ b/scheduling/highest_response_ratio_next.py @@ -0,0 +1,118 @@ +""" +Highest response ratio next (HRRN) scheduling is a non-preemptive discipline. +It was developed as modification of shortest job next or shortest job first (SJN or SJF) +to mitigate the problem of process starvation. +https://en.wikipedia.org/wiki/Highest_response_ratio_next +""" +from statistics import mean + +import numpy as np + + +def calculate_turn_around_time( + process_name: list, arrival_time: list, burst_time: list, no_of_process: int +) -> list: + """ + Calculate the turn around time of each processes + + Return: The turn around time time for each process. + >>> calculate_turn_around_time(["A", "B", "C"], [3, 5, 8], [2, 4, 6], 3) + [2, 4, 7] + >>> calculate_turn_around_time(["A", "B", "C"], [0, 2, 4], [3, 5, 7], 3) + [3, 6, 11] + """ + + current_time = 0 + # Number of processes finished + finished_process_count = 0 + # Displays the finished process. + # If it is 0, the performance is completed if it is 1, before the performance. + finished_process = [0] * no_of_process + # List to include calculation results + turn_around_time = [0] * no_of_process + + # Sort by arrival time. + burst_time = [burst_time[i] for i in np.argsort(arrival_time)] + process_name = [process_name[i] for i in np.argsort(arrival_time)] + arrival_time.sort() + + while no_of_process > finished_process_count: + + """ + If the current time is less than the arrival time of + the process that arrives first among the processes that have not been performed, + change the current time. + """ + i = 0 + while finished_process[i] == 1: + i += 1 + if current_time < arrival_time[i]: + current_time = arrival_time[i] + + response_ratio = 0 + # Index showing the location of the process being performed + loc = 0 + # Saves the current response ratio. + temp = 0 + for i in range(0, no_of_process): + if finished_process[i] == 0 and arrival_time[i] <= current_time: + temp = (burst_time[i] + (current_time - arrival_time[i])) / burst_time[ + i + ] + if response_ratio < temp: + response_ratio = temp + loc = i + + # Calculate the turn around time + turn_around_time[loc] = current_time + burst_time[loc] - arrival_time[loc] + current_time += burst_time[loc] + # Indicates that the process has been performed. + finished_process[loc] = 1 + # Increase finished_process_count by 1 + finished_process_count += 1 + + return turn_around_time + + +def calculate_waiting_time( + process_name: list, turn_around_time: list, burst_time: list, no_of_process: int +) -> list: + """ + Calculate the waiting time of each processes. + + Return: The waiting time for each process. + >>> calculate_waiting_time(["A", "B", "C"], [2, 4, 7], [2, 4, 6], 3) + [0, 0, 1] + >>> calculate_waiting_time(["A", "B", "C"], [3, 6, 11], [3, 5, 7], 3) + [0, 1, 4] + """ + + waiting_time = [0] * no_of_process + for i in range(0, no_of_process): + waiting_time[i] = turn_around_time[i] - burst_time[i] + return waiting_time + + +if __name__ == "__main__": + + no_of_process = 5 + process_name = ["A", "B", "C", "D", "E"] + arrival_time = [1, 2, 3, 4, 5] + burst_time = [1, 2, 3, 4, 5] + + turn_around_time = calculate_turn_around_time( + process_name, arrival_time, burst_time, no_of_process + ) + waiting_time = calculate_waiting_time( + process_name, turn_around_time, burst_time, no_of_process + ) + + print("Process name \tArrival time \tBurst time \tTurn around time \tWaiting time") + for i in range(0, no_of_process): + print( + f"{process_name[i]}\t\t{arrival_time[i]}\t\t{burst_time[i]}\t\t" + f"{turn_around_time[i]}\t\t\t{waiting_time[i]}" + ) + + print(f"average waiting time : {mean(waiting_time):.5f}") + print(f"average turn around time : {mean(turn_around_time):.5f}") From 04bc8f01dd81b8f4ca68e470d046fcb571b4d3d0 Mon Sep 17 00:00:00 2001 From: Margaret <62753112+meg-1@users.noreply.github.com> Date: Thu, 23 Jun 2022 19:47:29 +0300 Subject: [PATCH 1806/2908] Wave (#6061) * Added censor function * Added censor code * Added comments to the code * modified censor function * added decrypt function * added cypher and decypher functions, deleted censor and decrypt functions * Deleted decrypt.py * Deleted censor.py * edited the crypt and decrypt files * Update cypher_txt.py * Remove the endline in cypher.py * Removed the print at the end of decypher.py * added 4 new algorithms * added tests to the four files * added type hints for the function variables * Deleted decode message * Deleted code message * Welford average algorithm * added average welford algorithm * is_narcissistic added * added a descriptive name * added max_sectors algorithm * added find_unique * added wave algorithm * deleting average_welford [ in the wrong pr ] * deleting is_narcissistic [ is in the wrong pr ] * deleting max_sectors [ is in the wrong pr ] * deleting find_unique [ is in the wrong pr ] * deleting censor [ is in the wrong pr ] * deleting decrypt [ is in the wrong pr ] * fixed wave.py fixed indentation and followed the bots reccomendations * fixed wave.py again * fixing wave.py for the third time. * fixing wave.py * merging strings/wave.py merging the suggestion Co-authored-by: John Law Co-authored-by: John Law --- strings/wave.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 strings/wave.py diff --git a/strings/wave.py b/strings/wave.py new file mode 100644 index 000000000000..69d534432420 --- /dev/null +++ b/strings/wave.py @@ -0,0 +1,20 @@ +def wave(txt: str) -> list: + """ + Returns a so called 'wave' of a given string + >>> wave('cat') + ['Cat', 'cAt', 'caT'] + >>> wave('one') + ['One', 'oNe', 'onE'] + >>> wave('book') + ['Book', 'bOok', 'boOk', 'booK'] + """ + + return [ + txt[:a] + txt[a].upper() + txt[a + 1 :] + for a in range(len(txt)) + if txt[a].isalpha() + ] + + +if __name__ == "__main__": + __import__("doctest").testmod() From 4a51244e0feee90c8a80d6516628c9acb69c40b3 Mon Sep 17 00:00:00 2001 From: Erik Parmann Date: Thu, 23 Jun 2022 19:00:55 +0200 Subject: [PATCH 1807/2908] Remove how-to example for support vector machine (#6201) --- machine_learning/support_vector_machines.py | 58 --------------------- 1 file changed, 58 deletions(-) delete mode 100644 machine_learning/support_vector_machines.py diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py deleted file mode 100644 index c5e5085d8748..000000000000 --- a/machine_learning/support_vector_machines.py +++ /dev/null @@ -1,58 +0,0 @@ -from sklearn import svm -from sklearn.datasets import load_iris -from sklearn.model_selection import train_test_split - - -# 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(tol=10e-2) - 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=1000, 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 - - -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__": - import doctest - - doctest.testmod() From e5d1ff2ea8c3820343c46fd08b089207f75ca03d Mon Sep 17 00:00:00 2001 From: lovetodevelop <103916828+lovetodevelop@users.noreply.github.com> Date: Sun, 3 Jul 2022 09:28:53 -0700 Subject: [PATCH 1808/2908] Fix tiny spelling error (#6219) --- ciphers/enigma_machine2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 70f84752d55b..9f9dbe6f7cd0 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -8,7 +8,7 @@ Module includes: - enigma function - showcase of function usage -- 9 randnomly generated rotors +- 9 randomly generated rotors - reflector (aka static rotor) - original alphabet From 89fc7bf0b024e4c9508db80f575efd5b5616f932 Mon Sep 17 00:00:00 2001 From: Sedat Aybars Nazlica Date: Wed, 6 Jul 2022 16:19:13 +0900 Subject: [PATCH 1809/2908] Add hamming distance (#6194) * Add hamming distance * Fix doctest * Refactor * Raise ValueError when string lengths are different --- strings/hamming_distance.py | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 strings/hamming_distance.py diff --git a/strings/hamming_distance.py b/strings/hamming_distance.py new file mode 100644 index 000000000000..b8feaef06190 --- /dev/null +++ b/strings/hamming_distance.py @@ -0,0 +1,39 @@ +def hamming_distance(string1: str, string2: str) -> int: + """Calculate the Hamming distance between two equal length strings + In information theory, the Hamming distance between two strings of equal + length is the number of positions at which the corresponding symbols are + different. https://en.wikipedia.org/wiki/Hamming_distance + + Args: + string1 (str): Sequence 1 + string2 (str): Sequence 2 + + Returns: + int: Hamming distance + + >>> hamming_distance("python", "python") + 0 + >>> hamming_distance("karolin", "kathrin") + 3 + >>> hamming_distance("00000", "11111") + 5 + >>> hamming_distance("karolin", "kath") + ValueError: String lengths must match! + """ + if len(string1) != len(string2): + raise ValueError("String lengths must match!") + + count = 0 + + for char1, char2 in zip(string1, string2): + if char1 != char2: + count += 1 + + return count + + +if __name__ == "__main__": + + import doctest + + doctest.testmod() From 9135a1f41192ebe1d835282a1465dc284359d95c Mon Sep 17 00:00:00 2001 From: John Law Date: Wed, 6 Jul 2022 16:00:05 +0800 Subject: [PATCH 1810/2908] Fix doctests and builds in various files (#6233) * Fix doctest in hamming distance * add line break * try to fix quantum_riper_adder * fix floating point build --- arithmetic_analysis/in_static_equilibrium.py | 7 +++++-- quantum/ripple_adder_classic.py | 4 ++-- strings/hamming_distance.py | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index d762a376f577..7aaecf174a5e 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -19,8 +19,11 @@ def polar_force( True >>> math.isclose(force[1], 7.0710678118654755) True - >>> polar_force(10, 3.14, radian_mode=True) - [-9.999987317275396, 0.01592652916486828] + >>> force = polar_force(10, 3.14, radian_mode=True) + >>> math.isclose(force[0], -9.999987317275396) + True + >>> math.isclose(force[1], 0.01592652916486828) + True """ if radian_mode: return [magnitude * cos(angle), magnitude * sin(angle)] diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py index 8539a62afd52..1d3724476068 100644 --- a/quantum/ripple_adder_classic.py +++ b/quantum/ripple_adder_classic.py @@ -3,7 +3,7 @@ # https://en.wikipedia.org/wiki/Controlled_NOT_gate from qiskit import Aer, QuantumCircuit, execute -from qiskit.providers import BaseBackend +from qiskit.providers import Backend def store_two_classics(val1: int, val2: int) -> tuple[QuantumCircuit, str, str]: @@ -62,7 +62,7 @@ def full_adder( def ripple_adder( val1: int, val2: int, - backend: BaseBackend = Aer.get_backend("qasm_simulator"), # noqa: B008 + backend: Backend = Aer.get_backend("qasm_simulator"), # noqa: B008 ) -> int: """ Quantum Equivalent of a Ripple Adder Circuit diff --git a/strings/hamming_distance.py b/strings/hamming_distance.py index b8feaef06190..5de27dc77f44 100644 --- a/strings/hamming_distance.py +++ b/strings/hamming_distance.py @@ -18,6 +18,8 @@ def hamming_distance(string1: str, string2: str) -> int: >>> hamming_distance("00000", "11111") 5 >>> hamming_distance("karolin", "kath") + Traceback (most recent call last): + ... ValueError: String lengths must match! """ if len(string1) != len(string2): From 0a0f4986e4fde05ebc2a24c9cc2cd6b8200b8df1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 7 Jul 2022 05:25:25 +0200 Subject: [PATCH 1811/2908] Upgrade GitHub Actions (#6236) * Upgrade GitHub Actions * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 8 ++++---- .github/workflows/directory_writer.yml | 6 ++++-- .github/workflows/pre-commit.yml | 10 +++++----- .github/workflows/project_euler.yml | 12 ++++++++---- .pre-commit-config.yaml | 8 ++++---- DIRECTORY.md | 4 +++- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 403ec44c888d..8481b962a256 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,11 +9,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: - python-version: "3.10" - - uses: actions/cache@v2 + python-version: 3.x + - uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index be8154a32696..331962cef11e 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -6,8 +6,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 # v1, NOT v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v1 # v1, NOT v2 or v3 + - uses: actions/setup-python@v4 + 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/pre-commit.yml b/.github/workflows/pre-commit.yml index de73c96adfb1..3b128bc540bf 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -6,17 +6,17 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 + - uses: actions/checkout@v3 + - uses: actions/cache@v3 with: path: | ~/.cache/pre-commit ~/.cache/pip key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: - python-version: "3.10" - - uses: psf/black@21.4b0 + python-version: 3.x + # - uses: psf/black@22.6.0 - name: Install pre-commit run: | python -m pip install --upgrade pip diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml index 995295fcaa9a..460938219c14 100644 --- a/.github/workflows/project_euler.yml +++ b/.github/workflows/project_euler.yml @@ -14,8 +14,10 @@ jobs: project-euler: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x - name: Install pytest and pytest-cov run: | python -m pip install --upgrade pip @@ -24,8 +26,10 @@ jobs: validate-solutions: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x - name: Install pytest and requests run: | python -m pip install --upgrade pip diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33da02fb72ad..90feb50ff2af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.3.0 hooks: - id: check-executables-have-shebangs - id: check-yaml @@ -14,7 +14,7 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.6.0 hooks: - id: black @@ -26,7 +26,7 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + rev: v2.34.0 hooks: - id: pyupgrade args: @@ -42,7 +42,7 @@ repos: - --max-line-length=88 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.931 + rev: v0.961 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index d30e275d067f..2e9c03cbcd9b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -444,7 +444,6 @@ * [Scoring Functions](machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) * [Similarity Search](machine_learning/similarity_search.py) - * [Support Vector Machines](machine_learning/support_vector_machines.py) * [Word Frequency Functions](machine_learning/word_frequency_functions.py) ## Maths @@ -910,6 +909,7 @@ ## Scheduling * [First Come First Served](scheduling/first_come_first_served.py) + * [Highest Response Ratio Next](scheduling/highest_response_ratio_next.py) * [Multi Level Feedback Queue](scheduling/multi_level_feedback_queue.py) * [Non Preemptive Shortest Job First](scheduling/non_preemptive_shortest_job_first.py) * [Round Robin](scheduling/round_robin.py) @@ -995,6 +995,7 @@ * [Credit Card Validator](strings/credit_card_validator.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py) * [Frequency Finder](strings/frequency_finder.py) + * [Hamming Distance](strings/hamming_distance.py) * [Indian Phone Validator](strings/indian_phone_validator.py) * [Is Contains Unique Chars](strings/is_contains_unique_chars.py) * [Is Palindrome](strings/is_palindrome.py) @@ -1016,6 +1017,7 @@ * [Reverse Words](strings/reverse_words.py) * [Split](strings/split.py) * [Upper](strings/upper.py) + * [Wave](strings/wave.py) * [Wildcard Pattern Matching](strings/wildcard_pattern_matching.py) * [Word Occurrence](strings/word_occurrence.py) * [Word Patterns](strings/word_patterns.py) From 2d5dd6f132a25165473471bf83765ec50c9f14d6 Mon Sep 17 00:00:00 2001 From: Vardhaman <83634399+cyai@users.noreply.github.com> Date: Thu, 7 Jul 2022 20:04:07 +0530 Subject: [PATCH 1812/2908] MAINT: Updated f-string method (#6230) * MAINT: Used f-string method Updated the code with f-string methods wherever required for a better and cleaner understanding of the code. * Updated files with f-string method * Update rsa_key_generator.py * Update rsa_key_generator.py * Update elgamal_key_generator.py * Update lru_cache.py I don't think this change is efficient but it might tackle the error as the error was due to using long character lines. * Update lru_cache.py * Update lru_cache.py Co-authored-by: cyai Co-authored-by: Christian Clauss --- ciphers/elgamal_key_generator.py | 12 +++++------- ciphers/rsa_cipher.py | 4 ++-- ciphers/rsa_key_generator.py | 12 +++++------- ciphers/transposition_cipher.py | 4 ++-- ciphers/transposition_cipher_encrypt_decrypt_file.py | 4 ++-- ciphers/vigenere_cipher.py | 2 +- data_structures/binary_tree/binary_search_tree.py | 2 +- hashes/chaos_machine.py | 2 +- machine_learning/gradient_boosting_regressor.py | 4 ++-- machine_learning/k_means_clust.py | 4 +--- machine_learning/linear_regression.py | 2 +- matrix/sherman_morrison.py | 2 +- neural_network/convolution_neural_network.py | 4 ++-- other/lru_cache.py | 5 +++-- other/scoring_algorithm.py | 2 +- scheduling/shortest_job_first.py | 2 +- searches/binary_tree_traversal.py | 4 ++-- strings/min_cost_string_conversion.py | 12 ++++++------ 18 files changed, 39 insertions(+), 44 deletions(-) diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index f557b0e0dc91..485b77595c7c 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -38,9 +38,7 @@ def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, i def make_key_files(name: str, keySize: int) -> None: - if os.path.exists("%s_pubkey.txt" % name) or os.path.exists( - "%s_privkey.txt" % name - ): + if os.path.exists(f"{name}_pubkey.txt") or os.path.exists(f"{name}_privkey.txt"): print("\nWARNING:") print( '"%s_pubkey.txt" or "%s_privkey.txt" already exists. \n' @@ -50,14 +48,14 @@ def make_key_files(name: str, keySize: int) -> None: sys.exit() publicKey, privateKey = generate_key(keySize) - print("\nWriting public key to file %s_pubkey.txt..." % name) - with open("%s_pubkey.txt" % name, "w") as fo: + print(f"\nWriting public key to file {name}_pubkey.txt...") + with open(f"{name}_pubkey.txt", "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: + print(f"Writing private key to file {name}_privkey.txt...") + with open(f"{name}_privkey.txt", "w") as fo: fo.write("%d,%d" % (privateKey[0], privateKey[1])) diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 5bb9f9916de5..c6bfaa0fb00c 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -129,7 +129,7 @@ def main() -> None: message = input("\nEnter message: ") pubkey_filename = "rsa_pubkey.txt" - print("Encrypting and writing to %s..." % (filename)) + print(f"Encrypting and writing to {filename}...") encryptedText = encrypt_and_write_to_file(filename, pubkey_filename, message) print("\nEncrypted text:") @@ -137,7 +137,7 @@ def main() -> None: elif mode == "decrypt": privkey_filename = "rsa_privkey.txt" - print("Reading from %s and decrypting..." % (filename)) + print(f"Reading from {filename} and decrypting...") decrypted_text = read_from_file_and_decrypt(filename, privkey_filename) print("writing decryption to rsa_decryption.txt...") with open("rsa_decryption.txt", "w") as dec: diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 584066d8970f..d983c14f1d7e 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -34,9 +34,7 @@ def generateKey(keySize: int) -> tuple[tuple[int, int], tuple[int, int]]: def makeKeyFiles(name: str, keySize: int) -> None: - if os.path.exists("%s_pubkey.txt" % (name)) or os.path.exists( - "%s_privkey.txt" % (name) - ): + if os.path.exists(f"{name}_pubkey.txt") or os.path.exists(f"{name}_privkey.txt"): print("\nWARNING:") print( '"%s_pubkey.txt" or "%s_privkey.txt" already exists. \n' @@ -46,12 +44,12 @@ def makeKeyFiles(name: str, keySize: int) -> None: sys.exit() publicKey, privateKey = generateKey(keySize) - print("\nWriting public key to file %s_pubkey.txt..." % name) - with open("%s_pubkey.txt" % name, "w") as out_file: + print(f"\nWriting public key to file {name}_pubkey.txt...") + with open(f"{name}_pubkey.txt", "w") as out_file: out_file.write(f"{keySize},{publicKey[0]},{publicKey[1]}") - print("Writing private key to file %s_privkey.txt..." % name) - with open("%s_privkey.txt" % name, "w") as out_file: + print(f"Writing private key to file {name}_privkey.txt...") + with open(f"{name}_privkey.txt", "w") as out_file: out_file.write(f"{keySize},{privateKey[0]},{privateKey[1]}") diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index 589bb8cb5cd5..ed9923a6ba46 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -10,7 +10,7 @@ def main() -> None: message = input("Enter message: ") - key = int(input("Enter key [2-%s]: " % (len(message) - 1))) + key = int(input(f"Enter key [2-{len(message) - 1}]: ")) mode = input("Encryption/Decryption [e/d]: ") if mode.lower().startswith("e"): @@ -19,7 +19,7 @@ def main() -> None: text = decryptMessage(key, message) # Append pipe symbol (vertical bar) to identify spaces at the end. - print("Output:\n%s" % (text + "|")) + print(f"Output:\n{text + '|'}") def encryptMessage(key: int, message: str) -> str: diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index b91c73c9f2ad..926a1b36ac44 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -12,10 +12,10 @@ def main() -> None: mode = input("Encrypt/Decrypt [e/d]: ") if not os.path.exists(inputFile): - print("File %s does not exist. Quitting..." % inputFile) + print(f"File {inputFile} does not exist. Quitting...") sys.exit() if os.path.exists(outputFile): - print("Overwrite %s? [y/n]" % outputFile) + print(f"Overwrite {outputFile}? [y/n]") response = input("> ") if not response.lower().startswith("y"): sys.exit() diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index d97a96949fb8..2e3987708d01 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -13,7 +13,7 @@ def main() -> None: mode = "decrypt" translated = decryptMessage(key, message) - print("\n%sed message:" % mode.title()) + print(f"\n{mode.title()}ed message:") print(translated) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index ce490fd98524..b9af23dc8b00 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({f"{self.value}": (self.left, self.right)}, indent=1) class BinarySearchTree: diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 7ad3e5540479..a6d476eb7320 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -96,7 +96,7 @@ def reset(): # Pulling Data (Output) while inp in ("e", "E"): - print("%s" % format(pull(), "#04x")) + print(f"{format(pull(), '#04x')}") print(buffer_space) print(params_space) inp = input("(e)exit? ").strip() diff --git a/machine_learning/gradient_boosting_regressor.py b/machine_learning/gradient_boosting_regressor.py index 0aa0e7a10ac5..c73e30680a67 100644 --- a/machine_learning/gradient_boosting_regressor.py +++ b/machine_learning/gradient_boosting_regressor.py @@ -47,9 +47,9 @@ def main(): y_pred = model.predict(X_test) # The mean squared error - print("Mean squared error: %.2f" % mean_squared_error(y_test, y_pred)) + print(f"Mean squared error: {mean_squared_error(y_test, y_pred):.2f}") # Explained variance score: 1 is perfect prediction - print("Test Variance score: %.2f" % r2_score(y_test, y_pred)) + print(f"Test Variance score: {r2_score(y_test, y_pred):.2f}") # So let's run the model against the test data fig, ax = plt.subplots() diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 10c9374d8492..60450b7f8493 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -164,9 +164,7 @@ def kmeans( num_changed = np.sum(prev_cluster_assignment != cluster_assignment) if verbose: print( - " {:5d} elements changed their cluster assignment.".format( - num_changed - ) + f" {num_changed:5d} elements changed their cluster assignment." ) # Record heterogeneity convergence metric diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index b0bbc7b904c3..85fdfb0005ac 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -99,7 +99,7 @@ def main(): len_result = theta.shape[1] print("Resultant Feature vector : ") for i in range(0, len_result): - print("%.5f" % (theta[0, i])) + print(f"{theta[0, i]:.5f}") if __name__ == "__main__": diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 3466b3d4a01f..63783c8b40fc 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -256,7 +256,7 @@ def test1(): v[0, 0], v[1, 0], v[2, 0] = 4, -2, 5 print(f"u is {u}") print(f"v is {v}") - print("uv^T is %s" % (u * v.transpose())) + print(f"uv^T is {u * v.transpose()}") # Sherman Morrison print(f"(a + uv^T)^(-1) is {ainv.ShermanMorrison(u, v)}") diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index d821488025ef..e3993efb4249 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -71,7 +71,7 @@ def save_model(self, save_path): with open(save_path, "wb") as f: pickle.dump(model_dic, f) - print("Model saved: %s" % save_path) + print(f"Model saved: {save_path}") @classmethod def ReadModel(cls, model_path): @@ -303,7 +303,7 @@ def draw_error(): plt.show() print("------------------Training Complished---------------------") - print((" - - Training epoch: ", rp, " - - Mse: %.6f" % mse)) + print((" - - Training epoch: ", rp, f" - - Mse: {mse:.6f}")) if draw_e: draw_error() return mse diff --git a/other/lru_cache.py b/other/lru_cache.py index 98051f89db4f..834ea52a95e1 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -21,8 +21,9 @@ def __init__(self, key: T | None, val: U | None): self.prev: DoubleLinkedListNode[T, U] | None = None def __repr__(self) -> str: - return "Node: key: {}, val: {}, has next: {}, has prev: {}".format( - self.key, self.val, self.next is not None, self.prev is not None + return ( + f"Node: key: {self.key}, val: {self.val}, " + f"has next: {bool(self.next)}, has prev: {bool(self.prev)}" ) diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py index cc1744012671..aecd19c55927 100644 --- a/other/scoring_algorithm.py +++ b/other/scoring_algorithm.py @@ -69,7 +69,7 @@ def procentual_proximity( # weight not 0 or 1 else: - raise ValueError("Invalid weight of %f provided" % (weight)) + raise ValueError(f"Invalid weight of {weight:f} provided") score_lists.append(score) diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index 9372e9dbc3f4..b3f81bfd10e7 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -111,7 +111,7 @@ def calculate_average_times( 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(f"Average waiting time = {total_waiting_time / no_of_processes:.5f}") print("Average turn around time =", total_turn_around_time / no_of_processes) diff --git a/searches/binary_tree_traversal.py b/searches/binary_tree_traversal.py index f919a2962354..033db83d789e 100644 --- a/searches/binary_tree_traversal.py +++ b/searches/binary_tree_traversal.py @@ -25,14 +25,14 @@ def build_tree(): q.put(tree_node) while not q.empty(): node_found = q.get() - msg = "Enter the left node of %s: " % node_found.data + msg = f"Enter the left node of {node_found.data}: " check = input(msg).strip().lower() or "n" if check == "n": return tree_node left_node = TreeNode(int(check)) node_found.left = left_node q.put(left_node) - msg = "Enter the right node of %s: " % node_found.data + msg = f"Enter the right node of {node_found.data}: " check = input(msg).strip().lower() or "n" if check == "n": return tree_node diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index 147bc6fc740a..089c2532f900 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -31,28 +31,28 @@ def compute_transform_tables( for i in range(1, len_source_seq + 1): costs[i][0] = i * delete_cost - ops[i][0] = "D%c" % source_seq[i - 1] + ops[i][0] = f"D{source_seq[i - 1]:c}" for i in range(1, len_destination_seq + 1): costs[0][i] = i * insert_cost - ops[0][i] = "I%c" % destination_seq[i - 1] + ops[0][i] = f"I{destination_seq[i - 1]:c}" 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] + ops[i][j] = f"C{source_seq[i - 1]:c}" else: costs[i][j] = costs[i - 1][j - 1] + replace_cost - ops[i][j] = "R%c" % source_seq[i - 1] + str(destination_seq[j - 1]) + ops[i][j] = f"R{source_seq[i - 1]:c}" + str(destination_seq[j - 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] + ops[i][j] = f"D{source_seq[i - 1]:c}" 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] + ops[i][j] = f"I{destination_seq[j - 1]:c}" return costs, ops From b75a7c77f89e55e1f2510b2eca9b4fd1a5d21ed8 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 11 Jul 2022 10:19:52 +0200 Subject: [PATCH 1813/2908] pre-commit autoupdate: pyupgrade v2.34.0 -> v2.37.0 (#6245) * pre-commit autoupdate: pyupgrade v2.34.0 -> v2.37.0 * pre-commit run --all-files --- .pre-commit-config.yaml | 2 +- arithmetic_analysis/bisection.py | 2 +- arithmetic_analysis/intersection.py | 2 +- arithmetic_analysis/newton_method.py | 2 +- boolean_algebra/quine_mc_cluskey.py | 2 +- ciphers/playfair_cipher.py | 2 +- computer_vision/horn_schunck.py | 3 ++- data_structures/binary_tree/binary_search_tree_recursive.py | 2 +- data_structures/binary_tree/binary_tree_traversals.py | 3 ++- data_structures/binary_tree/non_recursive_segment_tree.py | 3 ++- data_structures/binary_tree/red_black_tree.py | 2 +- data_structures/heap/heap.py | 2 +- data_structures/heap/randomized_heap.py | 3 ++- data_structures/heap/skew_heap.py | 3 ++- data_structures/linked_list/circular_linked_list.py | 3 ++- data_structures/queue/double_ended_queue.py | 3 ++- data_structures/queue/linked_queue.py | 3 ++- divide_and_conquer/convex_hull.py | 2 +- fractals/julia_sets.py | 3 ++- graphs/prim.py | 2 +- linear_algebra/src/lib.py | 3 ++- machine_learning/linear_discriminant_analysis.py | 3 ++- maths/area_under_curve.py | 2 +- maths/euclidean_distance.py | 3 ++- maths/euler_method.py | 2 +- maths/euler_modified.py | 2 +- maths/line_length.py | 2 +- maths/monte_carlo.py | 2 +- maths/numerical_integration.py | 2 +- maths/polynomial_evaluation.py | 2 +- maths/prime_numbers.py | 2 +- other/davisb_putnamb_logemannb_loveland.py | 2 +- other/lfu_cache.py | 3 ++- other/lru_cache.py | 3 ++- project_euler/problem_010/sol2.py | 2 +- project_euler/problem_025/sol2.py | 2 +- project_euler/problem_101/sol1.py | 3 ++- project_euler/problem_107/sol1.py | 2 +- project_euler/problem_123/sol1.py | 2 +- scripts/build_directory_md.py | 2 +- web_programming/fetch_jobs.py | 2 +- 41 files changed, 56 insertions(+), 41 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 90feb50ff2af..7ff7459978e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v2.37.0 hooks: - id: pyupgrade args: diff --git a/arithmetic_analysis/bisection.py b/arithmetic_analysis/bisection.py index 1feb4a8cf626..640913a7acc0 100644 --- a/arithmetic_analysis/bisection.py +++ b/arithmetic_analysis/bisection.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable def bisection(function: Callable[[float], float], a: float, b: float) -> float: diff --git a/arithmetic_analysis/intersection.py b/arithmetic_analysis/intersection.py index 9d4651144668..49213dd05988 100644 --- a/arithmetic_analysis/intersection.py +++ b/arithmetic_analysis/intersection.py @@ -1,5 +1,5 @@ import math -from typing import Callable +from collections.abc import Callable def intersection(function: Callable[[float], float], x0: float, x1: float) -> float: diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index f0cf4eaa6e83..c4018a0f260c 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -1,7 +1,7 @@ """Newton's Method.""" # Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method -from typing import Callable +from collections.abc import Callable RealFunc = Callable[[float], float] # type alias for a real -> real function diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index fb23c8c2e79c..9aa9b10c8429 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence def compare_string(string1: str, string2: str) -> str: diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 7c0ee5bd5ae1..89aedb7afdb8 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -1,6 +1,6 @@ import itertools import string -from typing import Generator, Iterable +from collections.abc import Generator, Iterable def chunker(seq: Iterable[str], size: int) -> Generator[tuple[str, ...], None, None]: diff --git a/computer_vision/horn_schunck.py b/computer_vision/horn_schunck.py index 1428487d051b..2a153d06ddae 100644 --- a/computer_vision/horn_schunck.py +++ b/computer_vision/horn_schunck.py @@ -9,9 +9,10 @@ Paper: http://image.diku.dk/imagecanon/material/HornSchunckOptical_Flow.pdf """ +from typing import SupportsIndex + import numpy as np from scipy.ndimage.filters import convolve -from typing_extensions import SupportsIndex def warp( diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index 4bdf4e33dcc3..0d0ac8fd1e22 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -10,7 +10,7 @@ from __future__ import annotations import unittest -from typing import Iterator +from collections.abc import Iterator class Node: diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 9a62393914da..378598bb096d 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -2,8 +2,9 @@ from __future__ import annotations from collections import deque +from collections.abc import Sequence from dataclasses import dataclass -from typing import Any, Sequence +from typing import Any @dataclass diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index b04a6e5cacb7..c29adefffd20 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -37,7 +37,8 @@ """ from __future__ import annotations -from typing import Any, Callable, Generic, TypeVar +from collections.abc import Callable +from typing import Any, Generic, TypeVar T = TypeVar("T") diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 35517f307fe1..a9dbd699c3c1 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -4,7 +4,7 @@ """ from __future__ import annotations -from typing import Iterator +from collections.abc import Iterator class RedBlackTree: diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 550439edd239..4c19747ec823 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Iterable +from collections.abc import Iterable class Heap: diff --git a/data_structures/heap/randomized_heap.py b/data_structures/heap/randomized_heap.py index bab4ec1b34c6..c0f9888f80c7 100644 --- a/data_structures/heap/randomized_heap.py +++ b/data_structures/heap/randomized_heap.py @@ -3,7 +3,8 @@ from __future__ import annotations import random -from typing import Any, Generic, Iterable, TypeVar +from collections.abc import Iterable +from typing import Any, Generic, TypeVar T = TypeVar("T", bound=bool) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 16ddc5545e36..490db061deac 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Generic, Iterable, Iterator, TypeVar +from collections.abc import Iterable, Iterator +from typing import Any, Generic, TypeVar T = TypeVar("T", bound=bool) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 121d934c6957..6fec0a12542f 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Any, Iterator +from collections.abc import Iterator +from typing import Any class Node: diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index a4658d99759c..1603e50bc7f2 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -3,8 +3,9 @@ """ from __future__ import annotations +from collections.abc import Iterable from dataclasses import dataclass -from typing import Any, Iterable +from typing import Any class Deque: diff --git a/data_structures/queue/linked_queue.py b/data_structures/queue/linked_queue.py index 21970e7df965..c6e9f53908dd 100644 --- a/data_structures/queue/linked_queue.py +++ b/data_structures/queue/linked_queue.py @@ -1,7 +1,8 @@ """ A Queue using a linked list like structure """ from __future__ import annotations -from typing import Any, Iterator +from collections.abc import Iterator +from typing import Any class Node: diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 63f8dbb20cc0..72da116398a9 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -14,7 +14,7 @@ """ from __future__ import annotations -from typing import Iterable +from collections.abc import Iterable class Point: diff --git a/fractals/julia_sets.py b/fractals/julia_sets.py index 0168a0153de1..f273943851fc 100644 --- a/fractals/julia_sets.py +++ b/fractals/julia_sets.py @@ -22,7 +22,8 @@ """ import warnings -from typing import Any, Callable +from collections.abc import Callable +from typing import Any import numpy from matplotlib import pyplot diff --git a/graphs/prim.py b/graphs/prim.py index 70329da7e8e2..55d0fbfa8e96 100644 --- a/graphs/prim.py +++ b/graphs/prim.py @@ -7,7 +7,7 @@ import heapq as hq import math -from typing import Iterator +from collections.abc import Iterator class Vertex: diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 2bfcea7f8c84..b9791c860a74 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -22,7 +22,8 @@ import math import random -from typing import Collection, overload +from collections.abc import Collection +from typing import overload class Vector: diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 18553a77ad1c..9ef42ed19bab 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -42,10 +42,11 @@ Author: @EverLookNeverSee """ +from collections.abc import Callable from math import log from os import name, system from random import gauss, seed -from typing import Callable, TypeVar +from typing import TypeVar # Make a training dataset drawn from a gaussian distribution diff --git a/maths/area_under_curve.py b/maths/area_under_curve.py index 6fb3a7c98396..d345398b4c2c 100644 --- a/maths/area_under_curve.py +++ b/maths/area_under_curve.py @@ -3,7 +3,7 @@ """ from __future__ import annotations -from typing import Callable +from collections.abc import Callable def trapezoidal_area( diff --git a/maths/euclidean_distance.py b/maths/euclidean_distance.py index a2078161374b..22012e92c9cf 100644 --- a/maths/euclidean_distance.py +++ b/maths/euclidean_distance.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Iterable, Union +from collections.abc import Iterable +from typing import Union import numpy as np diff --git a/maths/euler_method.py b/maths/euler_method.py index 155ef28d1f49..af7eecb2ff29 100644 --- a/maths/euler_method.py +++ b/maths/euler_method.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable import numpy as np diff --git a/maths/euler_modified.py b/maths/euler_modified.py index 7c76a0ee0b86..5659fa063fc4 100644 --- a/maths/euler_modified.py +++ b/maths/euler_modified.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable import numpy as np diff --git a/maths/line_length.py b/maths/line_length.py index c4d986279cda..ad12a816b93e 100644 --- a/maths/line_length.py +++ b/maths/line_length.py @@ -1,7 +1,7 @@ from __future__ import annotations import math -from typing import Callable +from collections.abc import Callable def line_length( diff --git a/maths/monte_carlo.py b/maths/monte_carlo.py index efb6a01d57fd..c13b8d0a4f6b 100644 --- a/maths/monte_carlo.py +++ b/maths/monte_carlo.py @@ -1,10 +1,10 @@ """ @author: MatteoRaso """ +from collections.abc import Callable from math import pi, sqrt from random import uniform from statistics import mean -from typing import Callable def pi_estimator(iterations: int): diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index cf2efce12baf..a2bfce5b911d 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -3,7 +3,7 @@ """ from __future__ import annotations -from typing import Callable +from collections.abc import Callable def trapezoidal_area( diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index 4e4016e5133d..8ee82467efa1 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -1,4 +1,4 @@ -from typing import Sequence +from collections.abc import Sequence def evaluate_poly(poly: Sequence[float], x: float) -> float: diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index 183fbd39349e..7be4d3d95b0e 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -1,5 +1,5 @@ import math -from typing import Generator +from collections.abc import Generator def slow_primes(max: int) -> Generator[int, None, None]: diff --git a/other/davisb_putnamb_logemannb_loveland.py b/other/davisb_putnamb_logemannb_loveland.py index 031f0dbed404..88aefabc8087 100644 --- a/other/davisb_putnamb_logemannb_loveland.py +++ b/other/davisb_putnamb_logemannb_loveland.py @@ -11,7 +11,7 @@ from __future__ import annotations import random -from typing import Iterable +from collections.abc import Iterable class Clause: diff --git a/other/lfu_cache.py b/other/lfu_cache.py index e955973c95b0..072d00ab58c8 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Callable, Generic, TypeVar +from collections.abc import Callable +from typing import Generic, TypeVar T = TypeVar("T") U = TypeVar("U") diff --git a/other/lru_cache.py b/other/lru_cache.py index 834ea52a95e1..b68ae0a8e296 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Callable, Generic, TypeVar +from collections.abc import Callable +from typing import Generic, TypeVar T = TypeVar("T") U = TypeVar("U") diff --git a/project_euler/problem_010/sol2.py b/project_euler/problem_010/sol2.py index 3a2f485dde50..a288bb85fd52 100644 --- a/project_euler/problem_010/sol2.py +++ b/project_euler/problem_010/sol2.py @@ -11,8 +11,8 @@ - https://en.wikipedia.org/wiki/Prime_number """ import math +from collections.abc import Iterator from itertools import takewhile -from typing import Iterator def is_prime(number: int) -> bool: diff --git a/project_euler/problem_025/sol2.py b/project_euler/problem_025/sol2.py index b041afd98c86..6f49e89fb465 100644 --- a/project_euler/problem_025/sol2.py +++ b/project_euler/problem_025/sol2.py @@ -23,7 +23,7 @@ What is the index of the first term in the Fibonacci sequence to contain 1000 digits? """ -from typing import Generator +from collections.abc import Generator def fibonacci_generator() -> Generator[int, None, None]: diff --git a/project_euler/problem_101/sol1.py b/project_euler/problem_101/sol1.py index 04678847508c..27438e086c4f 100644 --- a/project_euler/problem_101/sol1.py +++ b/project_euler/problem_101/sol1.py @@ -43,7 +43,8 @@ """ from __future__ import annotations -from typing import Callable, Union +from collections.abc import Callable +from typing import Union Matrix = list[list[Union[float, int]]] diff --git a/project_euler/problem_107/sol1.py b/project_euler/problem_107/sol1.py index 6a411a11473d..048cf033dc2e 100644 --- a/project_euler/problem_107/sol1.py +++ b/project_euler/problem_107/sol1.py @@ -30,7 +30,7 @@ from __future__ import annotations import os -from typing import Mapping +from collections.abc import Mapping EdgeT = tuple[int, int] diff --git a/project_euler/problem_123/sol1.py b/project_euler/problem_123/sol1.py index 91913222759b..f74cdd999401 100644 --- a/project_euler/problem_123/sol1.py +++ b/project_euler/problem_123/sol1.py @@ -39,7 +39,7 @@ """ from __future__ import annotations -from typing import Generator +from collections.abc import Generator def sieve() -> Generator[int, None, None]: diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index 71577fe6d4ac..7572ce342720 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import os -from typing import Iterator +from collections.abc import Iterator def good_file_paths(top_dir: str = ".") -> Iterator[str]: diff --git a/web_programming/fetch_jobs.py b/web_programming/fetch_jobs.py index bb2171e1f0ee..5af90a0bb239 100644 --- a/web_programming/fetch_jobs.py +++ b/web_programming/fetch_jobs.py @@ -3,7 +3,7 @@ """ from __future__ import annotations -from typing import Generator +from collections.abc import Generator import requests from bs4 import BeautifulSoup From ba129de7f32b6acd1efd8e942aca109bacd86646 Mon Sep 17 00:00:00 2001 From: Todor Peev <46652070+Bjiornulf@users.noreply.github.com> Date: Mon, 11 Jul 2022 12:42:07 +0200 Subject: [PATCH 1814/2908] Fixes: 6216 | Support vector machines (#6240) * initial commit * first implementation of hard margin * remove debugging print * many commits squashed because pre-commit was buggy * more kernels and improved kernel management * remove unnecessary code + fix names + formatting + doctests * rename to fit initial naming * better naming and documentation * better naming and documentation --- machine_learning/support_vector_machines.py | 205 ++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 machine_learning/support_vector_machines.py diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py new file mode 100644 index 000000000000..caec10175c50 --- /dev/null +++ b/machine_learning/support_vector_machines.py @@ -0,0 +1,205 @@ +import numpy as np +from numpy import ndarray +from scipy.optimize import Bounds, LinearConstraint, minimize + + +def norm_squared(vector: ndarray) -> float: + """ + Return the squared second norm of vector + norm_squared(v) = sum(x * x for x in v) + + Args: + vector (ndarray): input vector + + Returns: + float: squared second norm of vector + + >>> norm_squared([1, 2]) + 5 + >>> norm_squared(np.asarray([1, 2])) + 5 + >>> norm_squared([0, 0]) + 0 + """ + return np.dot(vector, vector) + + +class SVC: + """ + Support Vector Classifier + + Args: + kernel (str): kernel to use. Default: linear + Possible choices: + - linear + regularization: constraint for soft margin (data not linearly separable) + Default: unbound + + >>> SVC(kernel="asdf") + Traceback (most recent call last): + ... + ValueError: Unknown kernel: asdf + + >>> SVC(kernel="rbf") + Traceback (most recent call last): + ... + ValueError: rbf kernel requires gamma + + >>> SVC(kernel="rbf", gamma=-1) + Traceback (most recent call last): + ... + ValueError: gamma must be > 0 + """ + + def __init__( + self, + *, + regularization: float = np.inf, + kernel: str = "linear", + gamma: float = 0, + ) -> None: + self.regularization = regularization + self.gamma = gamma + if kernel == "linear": + self.kernel = self.__linear + elif kernel == "rbf": + if self.gamma == 0: + raise ValueError("rbf kernel requires gamma") + if not (isinstance(self.gamma, float) or isinstance(self.gamma, int)): + raise ValueError("gamma must be float or int") + if not self.gamma > 0: + raise ValueError("gamma must be > 0") + self.kernel = self.__rbf + # in the future, there could be a default value like in sklearn + # sklear: def_gamma = 1/(n_features * X.var()) (wiki) + # previously it was 1/(n_features) + else: + raise ValueError(f"Unknown kernel: {kernel}") + + # kernels + def __linear(self, vector1: ndarray, vector2: ndarray) -> float: + """Linear kernel (as if no kernel used at all)""" + return np.dot(vector1, vector2) + + def __rbf(self, vector1: ndarray, vector2: ndarray) -> float: + """ + RBF: Radial Basis Function Kernel + + Note: for more information see: + https://en.wikipedia.org/wiki/Radial_basis_function_kernel + + Args: + vector1 (ndarray): first vector + vector2 (ndarray): second vector) + + Returns: + float: exp(-(gamma * norm_squared(vector1 - vector2))) + """ + return np.exp(-(self.gamma * norm_squared(vector1 - vector2))) + + def fit(self, observations: list[ndarray], classes: ndarray) -> None: + """ + Fits the SVC with a set of observations. + + Args: + observations (list[ndarray]): list of observations + classes (ndarray): classification of each observation (in {1, -1}) + """ + + self.observations = observations + self.classes = classes + + # using Wolfe's Dual to calculate w. + # Primal problem: minimize 1/2*norm_squared(w) + # constraint: yn(w . xn + b) >= 1 + # + # With l a vector + # Dual problem: maximize sum_n(ln) - + # 1/2 * sum_n(sum_m(ln*lm*yn*ym*xn . xm)) + # constraint: self.C >= ln >= 0 + # and sum_n(ln*yn) = 0 + # Then we get w using w = sum_n(ln*yn*xn) + # At the end we can get b ~= mean(yn - w . xn) + # + # Since we use kernels, we only need l_star to calculate b + # and to classify observations + + (n,) = np.shape(classes) + + def to_minimize(candidate: ndarray) -> float: + """ + Opposite of the function to maximize + + Args: + candidate (ndarray): candidate array to test + + Return: + float: Wolfe's Dual result to minimize + """ + s = 0 + (n,) = np.shape(candidate) + for i in range(n): + for j in range(n): + s += ( + candidate[i] + * candidate[j] + * classes[i] + * classes[j] + * self.kernel(observations[i], observations[j]) + ) + return 1 / 2 * s - sum(candidate) + + ly_contraint = LinearConstraint(classes, 0, 0) + l_bounds = Bounds(0, self.regularization) + + l_star = minimize( + to_minimize, np.ones(n), bounds=l_bounds, constraints=[ly_contraint] + ).x + self.optimum = l_star + + # calculating mean offset of separation plane to points + s = 0 + for i in range(n): + for j in range(n): + s += classes[i] - classes[i] * self.optimum[i] * self.kernel( + observations[i], observations[j] + ) + self.offset = s / n + + def predict(self, observation: ndarray) -> int: + """ + Get the expected class of an observation + + Args: + observation (Vector): observation + + Returns: + int {1, -1}: expected class + + >>> xs = [ + ... np.asarray([0, 1]), np.asarray([0, 2]), + ... np.asarray([1, 1]), np.asarray([1, 2]) + ... ] + >>> y = np.asarray([1, 1, -1, -1]) + >>> s = SVC() + >>> s.fit(xs, y) + >>> s.predict(np.asarray([0, 1])) + 1 + >>> s.predict(np.asarray([1, 1])) + -1 + >>> s.predict(np.asarray([2, 2])) + -1 + """ + s = sum( + self.optimum[n] + * self.classes[n] + * self.kernel(self.observations[n], observation) + for n in range(len(self.classes)) + ) + return 1 if s + self.offset >= 0 else -1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From dad789d9034ea6fb183bddb1a34b6b89d379e422 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 11 Jul 2022 13:11:17 +0200 Subject: [PATCH 1815/2908] Get rid of the Union (#6246) * Get rid of the Union * updating DIRECTORY.md * Get rid of the Union * Remove the redundant pre-commit runs. Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/pre-commit.yml | 6 +++++- DIRECTORY.md | 1 + project_euler/problem_101/sol1.py | 3 +-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 3b128bc540bf..eb5e3d4ce1cd 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,6 +1,10 @@ name: pre-commit -on: [push, pull_request] +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] jobs: pre-commit: diff --git a/DIRECTORY.md b/DIRECTORY.md index 2e9c03cbcd9b..c8f03658c537 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -444,6 +444,7 @@ * [Scoring Functions](machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) * [Similarity Search](machine_learning/similarity_search.py) + * [Support Vector Machines](machine_learning/support_vector_machines.py) * [Word Frequency Functions](machine_learning/word_frequency_functions.py) ## Maths diff --git a/project_euler/problem_101/sol1.py b/project_euler/problem_101/sol1.py index 27438e086c4f..d5c503af796a 100644 --- a/project_euler/problem_101/sol1.py +++ b/project_euler/problem_101/sol1.py @@ -44,9 +44,8 @@ from __future__ import annotations from collections.abc import Callable -from typing import Union -Matrix = list[list[Union[float, int]]] +Matrix = list[list[float | int]] def solve(matrix: Matrix, vector: Matrix) -> Matrix: From f7c58e4c4b66750cbb3afd9ad29e9c246b2480ab Mon Sep 17 00:00:00 2001 From: Nikos Giachoudis Date: Mon, 11 Jul 2022 10:36:57 -0400 Subject: [PATCH 1816/2908] Unify primality checking (#6228) * renames prime functions and occurances in comments * changes implementation of primality testing to be uniform * adds static typing as per conventions * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 - data_structures/hashing/double_hash.py | 4 +- .../hashing/number_theory/prime_numbers.py | 50 ++++++++++++--- maths/prime_check.py | 61 ++++++++++--------- maths/primelib.py | 22 +++---- 5 files changed, 86 insertions(+), 52 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index c8f03658c537..2e9c03cbcd9b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -444,7 +444,6 @@ * [Scoring Functions](machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) * [Similarity Search](machine_learning/similarity_search.py) - * [Support Vector Machines](machine_learning/support_vector_machines.py) * [Word Frequency Functions](machine_learning/word_frequency_functions.py) ## Maths diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index 57b1ffff4770..bd1355fca65d 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 .number_theory.prime_numbers import is_prime, next_prime class DoubleHash(HashTable): @@ -15,7 +15,7 @@ def __hash_function_2(self, value, data): next_prime_gt = ( next_prime(value % self.size_table) - if not check_prime(value % self.size_table) + if not is_prime(value % self.size_table) else value % self.size_table ) # gt = bigger than return next_prime_gt - (data % next_prime_gt) diff --git a/data_structures/hashing/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py index bf614e7d48df..b88ab76ecc23 100644 --- a/data_structures/hashing/number_theory/prime_numbers.py +++ b/data_structures/hashing/number_theory/prime_numbers.py @@ -3,25 +3,55 @@ module to operations with prime numbers """ +import math -def check_prime(number): - """ - it's not the best solution + +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + + A number is prime if it has exactly two factors: 1 and itself. + + >>> is_prime(0) + False + >>> is_prime(1) + False + >>> is_prime(2) + True + >>> is_prime(3) + True + >>> is_prime(27) + False + >>> is_prime(87) + False + >>> is_prime(563) + True + >>> is_prime(2999) + True + >>> is_prime(67483) + False """ - 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)) + # precondition + assert isinstance(number, int) and ( + number >= 0 + ), "'number' must been an int and positive" + + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or not number % 2: + # Negatives, 0, 1 and all even numbers are not primes + return False + + odd_numbers = range(3, int(math.sqrt(number) + 1), 2) + return not any(not number % i for i in odd_numbers) def next_prime(value, factor=1, **kwargs): value = factor * value first_value_val = value - while not check_prime(value): + while not is_prime(value): value += 1 if not ("desc" in kwargs.keys() and kwargs["desc"] is True) else -1 if value == first_value_val: diff --git a/maths/prime_check.py b/maths/prime_check.py index 92d31cfeee80..315492054659 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -4,31 +4,36 @@ import unittest -def prime_check(number: int) -> bool: +def is_prime(number: int) -> bool: """Checks to see if a number is a prime in O(sqrt(n)). A number is prime if it has exactly two factors: 1 and itself. - >>> prime_check(0) + >>> is_prime(0) False - >>> prime_check(1) + >>> is_prime(1) False - >>> prime_check(2) + >>> is_prime(2) True - >>> prime_check(3) + >>> is_prime(3) True - >>> prime_check(27) + >>> is_prime(27) False - >>> prime_check(87) + >>> is_prime(87) False - >>> prime_check(563) + >>> is_prime(563) True - >>> prime_check(2999) + >>> is_prime(2999) True - >>> prime_check(67483) + >>> is_prime(67483) False """ + # precondition + assert isinstance(number, int) and ( + number >= 0 + ), "'number' must been an int and positive" + if 1 < number < 4: # 2 and 3 are primes return True @@ -42,35 +47,35 @@ def prime_check(number: int) -> bool: class Test(unittest.TestCase): def test_primes(self): - 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)) + self.assertTrue(is_prime(2)) + self.assertTrue(is_prime(3)) + self.assertTrue(is_prime(5)) + self.assertTrue(is_prime(7)) + self.assertTrue(is_prime(11)) + self.assertTrue(is_prime(13)) + self.assertTrue(is_prime(17)) + self.assertTrue(is_prime(19)) + self.assertTrue(is_prime(23)) + self.assertTrue(is_prime(29)) def test_not_primes(self): self.assertFalse( - prime_check(-19), + is_prime(-19), "Negative numbers are excluded by definition of prime numbers.", ) self.assertFalse( - prime_check(0), + is_prime(0), "Zero doesn't have any positive factors, primes must have exactly two.", ) self.assertFalse( - prime_check(1), + is_prime(1), "One only has 1 positive factor, primes must have exactly 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)) + self.assertFalse(is_prime(2 * 2)) + self.assertFalse(is_prime(2 * 3)) + self.assertFalse(is_prime(3 * 3)) + self.assertFalse(is_prime(3 * 5)) + self.assertFalse(is_prime(3 * 5 * 7)) if __name__ == "__main__": diff --git a/maths/primelib.py b/maths/primelib.py index 37883d9cf591..3da9c56f66d6 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -41,7 +41,7 @@ from math import sqrt -def isPrime(number): +def is_prime(number: int) -> bool: """ input: positive integer 'number' returns true if 'number' is prime otherwise false. @@ -129,7 +129,7 @@ def getPrimeNumbers(N): # if a number is prime then appends to list 'ans' for number in range(2, N + 1): - if isPrime(number): + if is_prime(number): ans.append(number) @@ -164,11 +164,11 @@ def primeFactorization(number): ans.append(number) # if 'number' not prime then builds the prime factorization of 'number' - elif not isPrime(number): + elif not is_prime(number): while quotient != 1: - if isPrime(factor) and (quotient % factor == 0): + if is_prime(factor) and (quotient % factor == 0): ans.append(factor) quotient /= factor else: @@ -317,8 +317,8 @@ def goldbach(number): isinstance(ans, list) and (len(ans) == 2) and (ans[0] + ans[1] == number) - and isPrime(ans[0]) - and isPrime(ans[1]) + and is_prime(ans[0]) + and is_prime(ans[1]) ), "'ans' must contains two primes. And sum of elements must been eq 'number'" return ans @@ -462,11 +462,11 @@ def getPrime(n): # if ans not prime then # runs to the next prime number. - while not isPrime(ans): + while not is_prime(ans): ans += 1 # precondition - assert isinstance(ans, int) and isPrime( + assert isinstance(ans, int) and is_prime( ans ), "'ans' must been a prime number and from type int" @@ -486,7 +486,7 @@ def getPrimesBetween(pNumber1, pNumber2): # precondition assert ( - isPrime(pNumber1) and isPrime(pNumber2) and (pNumber1 < pNumber2) + is_prime(pNumber1) and is_prime(pNumber2) and (pNumber1 < pNumber2) ), "The arguments must been prime numbers and 'pNumber1' < 'pNumber2'" number = pNumber1 + 1 # jump to the next number @@ -495,7 +495,7 @@ def getPrimesBetween(pNumber1, pNumber2): # if number is not prime then # fetch the next prime number. - while not isPrime(number): + while not is_prime(number): number += 1 while number < pNumber2: @@ -505,7 +505,7 @@ def getPrimesBetween(pNumber1, pNumber2): number += 1 # fetch the next prime number. - while not isPrime(number): + while not is_prime(number): number += 1 # precondition From dcc387631d201de42dbf34216088e7faba302a41 Mon Sep 17 00:00:00 2001 From: lakshmikanth ayyadevara <52835045+Lakshmikanth2001@users.noreply.github.com> Date: Mon, 11 Jul 2022 20:59:27 +0530 Subject: [PATCH 1817/2908] Improve `prime_check` in math modules (#6044) * improved prime_check * updating DIRECTORY.md * included suggested changes * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- maths/prime_check.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/maths/prime_check.py b/maths/prime_check.py index 315492054659..6af5a75c2dd8 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -37,12 +37,15 @@ def is_prime(number: int) -> bool: if 1 < number < 4: # 2 and 3 are primes return True - elif number < 2 or not number % 2: - # Negatives, 0, 1 and all even numbers are not primes + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes return False - odd_numbers = range(3, int(math.sqrt(number) + 1), 2) - return not any(not number % i for i in odd_numbers) + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False + return True class Test(unittest.TestCase): From 38dfcd28b5f2fb19bae130a942466d73933e072f Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 14 Jul 2022 12:54:24 +0530 Subject: [PATCH 1818/2908] fix: test failures (#6250) 1. Incorrect function was being imported from the module 2. Testing for exception was not done correctly --- maths/miller_rabin.py | 6 +++--- maths/prime_check.py | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/maths/miller_rabin.py b/maths/miller_rabin.py index d35e5485888f..b4dfed1290de 100644 --- a/maths/miller_rabin.py +++ b/maths/miller_rabin.py @@ -8,9 +8,9 @@ # if it's not a prime, the chance of it returning true is at most 1/4**prec def is_prime_big(n, prec=1000): """ - >>> from maths.prime_check import prime_check - >>> # all(is_prime_big(i) == prime_check(i) for i in range(1000)) # 3.45s - >>> all(is_prime_big(i) == prime_check(i) for i in range(256)) + >>> from maths.prime_check import is_prime + >>> # all(is_prime_big(i) == is_prime(i) for i in range(1000)) # 3.45s + >>> all(is_prime_big(i) == is_prime(i) for i in range(256)) True """ if n < 2: diff --git a/maths/prime_check.py b/maths/prime_check.py index 6af5a75c2dd8..80ab8bc5d2cd 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -62,10 +62,8 @@ def test_primes(self): self.assertTrue(is_prime(29)) def test_not_primes(self): - self.assertFalse( - is_prime(-19), - "Negative numbers are excluded by definition of prime numbers.", - ) + with self.assertRaises(AssertionError): + is_prime(-19) self.assertFalse( is_prime(0), "Zero doesn't have any positive factors, primes must have exactly two.", From e1e7922efac2b7fdfab7555baaf784edb345c222 Mon Sep 17 00:00:00 2001 From: KanakalathaVemuru <46847239+KanakalathaVemuru@users.noreply.github.com> Date: Sun, 17 Jul 2022 03:12:58 +0530 Subject: [PATCH 1819/2908] Add circle sort implementation (#5548) * Added circle sort implementation * Added modifications * Added modifications * Update circle_sort.py * Update circle_sort.py Co-authored-by: Christian Clauss Co-authored-by: John Law --- sorts/circle_sort.py | 87 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 sorts/circle_sort.py diff --git a/sorts/circle_sort.py b/sorts/circle_sort.py new file mode 100644 index 000000000000..da3c59059516 --- /dev/null +++ b/sorts/circle_sort.py @@ -0,0 +1,87 @@ +""" +This is a Python implementation of the circle sort algorithm + +For doctests run following command: +python3 -m doctest -v circle_sort.py + +For manual testing run: +python3 circle_sort.py +""" + + +def circle_sort(collection: list) -> list: + """A pure Python implementation of circle sort algorithm + + :param collection: a mutable collection of comparable items in any order + :return: the same collection in ascending order + + Examples: + >>> circle_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + >>> circle_sort([]) + [] + >>> circle_sort([-2, 5, 0, -45]) + [-45, -2, 0, 5] + >>> collections = ([], [0, 5, 3, 2, 2], [-2, 5, 0, -45]) + >>> all(sorted(collection) == circle_sort(collection) for collection in collections) + True + """ + + if len(collection) < 2: + return collection + + def circle_sort_util(collection: list, low: int, high: int) -> bool: + """ + >>> arr = [5,4,3,2,1] + >>> circle_sort_util(lst, 0, 2) + True + >>> arr + [3, 4, 5, 2, 1] + """ + + swapped = False + + if low == high: + return swapped + + left = low + right = high + + while left < right: + if collection[left] > collection[right]: + collection[left], collection[right] = ( + collection[right], + collection[left], + ) + swapped = True + + left += 1 + right -= 1 + + if left == right: + if collection[left] > collection[right + 1]: + collection[left], collection[right + 1] = ( + collection[right + 1], + collection[left], + ) + + swapped = True + + mid = low + int((high - low) / 2) + left_swap = circle_sort_util(collection, low, mid) + right_swap = circle_sort_util(collection, mid + 1, high) + + return swapped or left_swap or right_swap + + is_not_sorted = True + + while is_not_sorted is True: + is_not_sorted = circle_sort_util(collection, 0, len(collection) - 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(",")] + print(circle_sort(unsorted)) From b3d9281591df03768fe062cc0517ee0d4cc387f0 Mon Sep 17 00:00:00 2001 From: U <80122730+und1n3@users.noreply.github.com> Date: Sat, 16 Jul 2022 23:55:29 +0200 Subject: [PATCH 1820/2908] Add algorithm for creating Hamming numbers (#4992) * Added algorithm for creating Hamming numbers series in Python * Changed to f-string format. * Added modifications * Update and rename hamming.py to hamming_numbers.py * Update hamming_numbers.py * Update hamming_numbers.py * Rename maths/series/hamming_numbers.py to maths/hamming_numbers.py Co-authored-by: John Law --- maths/hamming_numbers.py | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 maths/hamming_numbers.py diff --git a/maths/hamming_numbers.py b/maths/hamming_numbers.py new file mode 100644 index 000000000000..4575119c8a95 --- /dev/null +++ b/maths/hamming_numbers.py @@ -0,0 +1,51 @@ +""" +A Hamming number is a positive integer of the form 2^i*3^j*5^k, for some +non-negative integers i, j, and k. They are often referred to as regular numbers. +More info at: https://en.wikipedia.org/wiki/Regular_number. +""" + + +def hamming(n_element: int) -> list: + """ + This function creates an ordered list of n length as requested, and afterwards + returns the last value of the list. It must be given a positive integer. + + :param n_element: The number of elements on the list + :return: The nth element of the list + + >>> hamming(5) + [1, 2, 3, 4, 5] + >>> hamming(10) + [1, 2, 3, 4, 5, 6, 8, 9, 10, 12] + >>> hamming(15) + [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24] + """ + n_element = int(n_element) + if n_element < 1: + my_error = ValueError("a should be a positive number") + raise my_error + + hamming_list = [1] + i, j, k = (0, 0, 0) + index = 1 + while index < n_element: + while hamming_list[i] * 2 <= hamming_list[-1]: + i += 1 + while hamming_list[j] * 3 <= hamming_list[-1]: + j += 1 + while hamming_list[k] * 5 <= hamming_list[-1]: + k += 1 + hamming_list.append( + min(hamming_list[i] * 2, hamming_list[j] * 3, hamming_list[k] * 5) + ) + index += 1 + return hamming_list + + +if __name__ == "__main__": + n = input("Enter the last number (nth term) of the Hamming Number Series: ") + print("Formula of Hamming Number Series => 2^i * 3^j * 5^k") + hamming_numbers = hamming(int(n)) + print("-----------------------------------------------------") + print(f"The list with nth numbers is: {hamming_numbers}") + print("-----------------------------------------------------") From c45fb3c2948449760667fdf085cfc0467376ade8 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 23 Jul 2022 04:53:46 +0300 Subject: [PATCH 1821/2908] perf: Project Euler problem 145 solution 1 (#6259) Improve solution (~30 times - from 900+ seconds to ~30 seconds) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 ++ project_euler/problem_145/sol1.py | 90 ++++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 2e9c03cbcd9b..1ee106252ce2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -444,6 +444,7 @@ * [Scoring Functions](machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) * [Similarity Search](machine_learning/similarity_search.py) + * [Support Vector Machines](machine_learning/support_vector_machines.py) * [Word Frequency Functions](machine_learning/word_frequency_functions.py) ## Maths @@ -500,6 +501,7 @@ * [Gaussian](maths/gaussian.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) * [Greedy Coin Change](maths/greedy_coin_change.py) + * [Hamming Numbers](maths/hamming_numbers.py) * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) @@ -938,6 +940,7 @@ * [Bogo Sort](sorts/bogo_sort.py) * [Bubble Sort](sorts/bubble_sort.py) * [Bucket Sort](sorts/bucket_sort.py) + * [Circle Sort](sorts/circle_sort.py) * [Cocktail Shaker Sort](sorts/cocktail_shaker_sort.py) * [Comb Sort](sorts/comb_sort.py) * [Counting Sort](sorts/counting_sort.py) diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py index 09d8daff57be..e9fc1a199161 100644 --- a/project_euler/problem_145/sol1.py +++ b/project_euler/problem_145/sol1.py @@ -1,6 +1,6 @@ """ Project Euler problem 145: https://projecteuler.net/problem=145 -Author: Vineet Rao +Author: Vineet Rao, Maxim Smolskiy Problem statement: Some positive integers n have the property that the sum [ n + reverse(n) ] @@ -13,44 +13,82 @@ How many reversible numbers are there below one-billion (10^9)? """ +EVEN_DIGITS = [0, 2, 4, 6, 8] +ODD_DIGITS = [1, 3, 5, 7, 9] -def odd_digits(num: int) -> bool: +def reversible_numbers( + remaining_length: int, remainder: int, digits: list[int], length: int +) -> int: """ - Check if the number passed as argument has only odd digits. - >>> odd_digits(123) - False - >>> odd_digits(135797531) - True + Count the number of reversible numbers of given length. + Iterate over possible digits considering parity of current sum remainder. + >>> reversible_numbers(1, 0, [0], 1) + 0 + >>> reversible_numbers(2, 0, [0] * 2, 2) + 20 + >>> reversible_numbers(3, 0, [0] * 3, 3) + 100 """ - while num > 0: - digit = num % 10 - if digit % 2 == 0: - return False - num //= 10 - return True + if remaining_length == 0: + if digits[0] == 0 or digits[-1] == 0: + return 0 + for i in range(length // 2 - 1, -1, -1): + remainder += digits[i] + digits[length - i - 1] -def solution(max_num: int = 1_000_000_000) -> int: + if remainder % 2 == 0: + return 0 + + remainder //= 10 + + return 1 + + if remaining_length == 1: + if remainder % 2 == 0: + return 0 + + result = 0 + for digit in range(10): + digits[length // 2] = digit + result += reversible_numbers( + 0, (remainder + 2 * digit) // 10, digits, length + ) + return result + + result = 0 + for digit1 in range(10): + digits[(length + remaining_length) // 2 - 1] = digit1 + + if (remainder + digit1) % 2 == 0: + other_parity_digits = ODD_DIGITS + else: + other_parity_digits = EVEN_DIGITS + + for digit2 in other_parity_digits: + digits[(length - remaining_length) // 2] = digit2 + result += reversible_numbers( + remaining_length - 2, + (remainder + digit1 + digit2) // 10, + digits, + length, + ) + return result + + +def solution(max_power: int = 9) -> int: """ To evaluate the solution, use solution() - >>> solution(1000) + >>> solution(3) 120 - >>> solution(1_000_000) + >>> solution(6) 18720 - >>> solution(10_000_000) + >>> solution(7) 68720 """ result = 0 - # All single digit numbers reverse to themselves, so their sums are even - # Therefore at least one digit in their sum is even - # Last digit cannot be 0, else it causes leading zeros in reverse - for num in range(11, max_num): - if num % 10 == 0: - continue - num_sum = num + int(str(num)[::-1]) - num_is_reversible = odd_digits(num_sum) - result += 1 if num_is_reversible else 0 + for length in range(1, max_power + 1): + result += reversible_numbers(length, 0, [0] * length, length) return result From d53fdc29e2b47213999f566d16acd60409de6dc2 Mon Sep 17 00:00:00 2001 From: lance-pyles <36748284+lance-pyles@users.noreply.github.com> Date: Fri, 22 Jul 2022 19:26:59 -0700 Subject: [PATCH 1822/2908] chore: update .gitignore (#6263) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 574cdf312836..baea84b8d1f1 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ venv.bak/ .idea .try .vscode/ +.vs/ From 7d9ebee75fd9036579c2ecb282cbf4910de12b58 Mon Sep 17 00:00:00 2001 From: keshav Sharma <72795959+ksharma20@users.noreply.github.com> Date: Sun, 24 Jul 2022 21:33:10 +0530 Subject: [PATCH 1823/2908] chore: rename gcd to greatest_common_divisor (#6265) As described in CONTRIBUTING.md > Expand acronyms because gcd() is hard to understand but greatest_common_divisor() is not. Co-authored-by: Dhruv Manilawala --- project_euler/problem_005/sol2.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/project_euler/problem_005/sol2.py b/project_euler/problem_005/sol2.py index c88044487d20..1b3e5e130f03 100644 --- a/project_euler/problem_005/sol2.py +++ b/project_euler/problem_005/sol2.py @@ -16,28 +16,28 @@ """ -def gcd(x: int, y: int) -> int: +def greatest_common_divisor(x: int, y: int) -> int: """ - Euclidean GCD algorithm (Greatest Common Divisor) + Euclidean Greatest Common Divisor algorithm - >>> gcd(0, 0) + >>> greatest_common_divisor(0, 0) 0 - >>> gcd(23, 42) + >>> greatest_common_divisor(23, 42) 1 - >>> gcd(15, 33) + >>> greatest_common_divisor(15, 33) 3 - >>> gcd(12345, 67890) + >>> greatest_common_divisor(12345, 67890) 15 """ - return x if y == 0 else gcd(y, x % y) + return x if y == 0 else greatest_common_divisor(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 + Using the property that lcm(a, b) * greatest_common_divisor(a, b) = a*b >>> lcm(3, 15) 15 @@ -49,7 +49,7 @@ def lcm(x: int, y: int) -> int: 192 """ - return (x * y) // gcd(x, y) + return (x * y) // greatest_common_divisor(x, y) def solution(n: int = 20) -> int: From 90959212e5b0f3cfbae95ea38100e6fee4d2475f Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 25 Jul 2022 19:41:12 +0300 Subject: [PATCH 1824/2908] perf: improve Project Euler problem 030 solution 1 (#6267) Improve solution (locally 3+ times - from 3+ seconds to ~1 second) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- project_euler/problem_030/sol1.py | 32 ++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/project_euler/problem_030/sol1.py b/project_euler/problem_030/sol1.py index c9f2d71965e3..2c6b4e4e85d5 100644 --- a/project_euler/problem_030/sol1.py +++ b/project_euler/problem_030/sol1.py @@ -1,4 +1,4 @@ -""" Problem Statement (Digit Fifth Power ): https://projecteuler.net/problem=30 +""" Problem Statement (Digit Fifth Powers): https://projecteuler.net/problem=30 Surprisingly there are only three numbers that can be written as the sum of fourth powers of their digits: @@ -13,26 +13,32 @@ 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) +9^5 = 59049 +59049 * 7 = 413343 (which is only 6 digit number) +So, numbers greater than 999999 are rejected +and also 59049 * 3 = 177147 (which exceeds the criteria of number being 3 digit) +So, number > 999 +and hence a number between 1000 and 1000000 """ -def digitsum(s: str) -> int: +DIGITS_FIFTH_POWER = {str(digit): digit**5 for digit in range(10)} + + +def digits_fifth_powers_sum(number: int) -> int: """ - >>> all(digitsum(str(i)) == (1 if i == 1 else 0) for i in range(100)) - True + >>> digits_fifth_powers_sum(1234) + 1300 """ - i = sum(pow(int(c), 5) for c in s) - return i if i == int(s) else 0 + return sum(DIGITS_FIFTH_POWER[digit] for digit in str(number)) def solution() -> int: - return sum(digitsum(str(i)) for i in range(1000, 1000000)) + return sum( + number + for number in range(1000, 1000000) + if number == digits_fifth_powers_sum(number) + ) if __name__ == "__main__": From 97f25d4b431ffe432a30853ed0bcc75ea5e8166f Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 26 Jul 2022 19:15:14 +0300 Subject: [PATCH 1825/2908] feat: add Project Euler problem 587 solution 1 (#6269) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_587/__init__.py | 0 project_euler/problem_587/sol1.py | 94 +++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 project_euler/problem_587/__init__.py create mode 100644 project_euler/problem_587/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 1ee106252ce2..843ff77bb67b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -898,6 +898,8 @@ * [Sol1](project_euler/problem_493/sol1.py) * Problem 551 * [Sol1](project_euler/problem_551/sol1.py) + * Problem 587 + * [Sol1](project_euler/problem_587/sol1.py) * Problem 686 * [Sol1](project_euler/problem_686/sol1.py) diff --git a/project_euler/problem_587/__init__.py b/project_euler/problem_587/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_587/sol1.py b/project_euler/problem_587/sol1.py new file mode 100644 index 000000000000..dde5c16103ac --- /dev/null +++ b/project_euler/problem_587/sol1.py @@ -0,0 +1,94 @@ +""" +Project Euler Problem 587: https://projecteuler.net/problem=587 + +A square is drawn around a circle as shown in the diagram below on the left. +We shall call the blue shaded region the L-section. +A line is drawn from the bottom left of the square to the top right +as shown in the diagram on the right. +We shall call the orange shaded region a concave triangle. + +It should be clear that the concave triangle occupies exactly half of the L-section. + +Two circles are placed next to each other horizontally, +a rectangle is drawn around both circles, and +a line is drawn from the bottom left to the top right as shown in the diagram below. + +This time the concave triangle occupies approximately 36.46% of the L-section. + +If n circles are placed next to each other horizontally, +a rectangle is drawn around the n circles, and +a line is drawn from the bottom left to the top right, +then it can be shown that the least value of n +for which the concave triangle occupies less than 10% of the L-section is n = 15. + +What is the least value of n +for which the concave triangle occupies less than 0.1% of the L-section? +""" + +from itertools import count +from math import asin, pi, sqrt + + +def circle_bottom_arc_integral(point: float) -> float: + """ + Returns integral of circle bottom arc y = 1 / 2 - sqrt(1 / 4 - (x - 1 / 2) ^ 2) + + >>> circle_bottom_arc_integral(0) + 0.39269908169872414 + + >>> circle_bottom_arc_integral(1 / 2) + 0.44634954084936207 + + >>> circle_bottom_arc_integral(1) + 0.5 + """ + + return ( + (1 - 2 * point) * sqrt(point - point**2) + 2 * point + asin(sqrt(1 - point)) + ) / 4 + + +def concave_triangle_area(circles_number: int) -> float: + """ + Returns area of concave triangle + + >>> concave_triangle_area(1) + 0.026825229575318944 + + >>> concave_triangle_area(2) + 0.01956236140083944 + """ + + intersection_y = (circles_number + 1 - sqrt(2 * circles_number)) / ( + 2 * (circles_number**2 + 1) + ) + intersection_x = circles_number * intersection_y + + triangle_area = intersection_x * intersection_y / 2 + concave_region_area = circle_bottom_arc_integral( + 1 / 2 + ) - circle_bottom_arc_integral(intersection_x) + + return triangle_area + concave_region_area + + +def solution(fraction: float = 1 / 1000) -> int: + """ + Returns least value of n + for which the concave triangle occupies less than fraction of the L-section + + >>> solution(1 / 10) + 15 + """ + + l_section_area = (1 - pi / 4) / 4 + + for n in count(1): + if concave_triangle_area(n) / l_section_area < fraction: + return n + + return -1 + + +if __name__ == "__main__": + print(f"{solution() = }") From defc205ef4459264b753429c6cbc23481347e8b7 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 6 Aug 2022 17:04:24 +0300 Subject: [PATCH 1826/2908] perf: improve Project Euler problem 203 solution 1 (#6279) Improve solution (locally 1500+ times - from 3+ seconds to ~2 milliseconds) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- project_euler/problem_203/sol1.py | 112 ++++++------------------------ 1 file changed, 21 insertions(+), 91 deletions(-) diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py index 2ba3c96c9e00..dc93683da535 100644 --- a/project_euler/problem_203/sol1.py +++ b/project_euler/problem_203/sol1.py @@ -29,8 +29,6 @@ """ from __future__ import annotations -import math - def get_pascal_triangle_unique_coefficients(depth: int) -> set[int]: """ @@ -61,76 +59,9 @@ def get_pascal_triangle_unique_coefficients(depth: int) -> set[int]: 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 = math.isqrt(max_number) - non_primes = [False] * (max_prime + 1) - primes = [] - for num in range(2, max_prime + 1): - if non_primes[num]: - continue - - for num_counter in range(num**2, max_prime + 1, num): - non_primes[num_counter] = True - - 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 +def get_squarefrees(unique_coefficients: set[int]) -> set[int]: """ - 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. + Calculates the squarefree numbers inside unique_coefficients. 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 @@ -140,27 +71,27 @@ def get_squarefree( 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]) + >>> get_squarefrees({1}) + {1} + >>> get_squarefrees({1, 2}) + {1, 2} + >>> get_squarefrees({1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21}) {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) + for number in unique_coefficients: + divisor = 2 + copy_number = number + while divisor**2 <= copy_number: + multiplicity = 0 + while copy_number % divisor == 0: + copy_number //= divisor + multiplicity += 1 + if multiplicity >= 2: + non_squarefrees.add(number) + break + divisor += 1 return unique_coefficients.difference(non_squarefrees) @@ -170,15 +101,14 @@ def solution(n: int = 51) -> int: Returns the sum of squarefrees for a given Pascal's Triangle of depth n. >>> solution(1) - 0 + 1 >>> 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) + squarefrees = get_squarefrees(unique_coefficients) return sum(squarefrees) From 9eac958725c8e5c62b225c77eaf83ec0ecb7e6f6 Mon Sep 17 00:00:00 2001 From: Horst JENS Date: Sat, 6 Aug 2022 17:47:56 +0200 Subject: [PATCH 1827/2908] typo corrected: heart -> Earth (#6275) --- physics/horizontal_projectile_motion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physics/horizontal_projectile_motion.py b/physics/horizontal_projectile_motion.py index 0f27b0617105..a747acd72072 100644 --- a/physics/horizontal_projectile_motion.py +++ b/physics/horizontal_projectile_motion.py @@ -17,7 +17,7 @@ from math import radians as angle_to_radians from math import sin -# Acceleration Constant on hearth (unit m/s^2) +# Acceleration Constant on Earth (unit m/s^2) g = 9.80665 From a69d880bb5e9113ccf09aeaa31a570330f856417 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 7 Aug 2022 05:07:35 +0300 Subject: [PATCH 1828/2908] feat: add Project Euler problem 114 solution 1 (#6300) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_114/__init__.py | 0 project_euler/problem_114/sol1.py | 58 +++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 project_euler/problem_114/__init__.py create mode 100644 project_euler/problem_114/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 843ff77bb67b..98b87f2fe279 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -854,6 +854,8 @@ * [Sol1](project_euler/problem_112/sol1.py) * Problem 113 * [Sol1](project_euler/problem_113/sol1.py) + * Problem 114 + * [Sol1](project_euler/problem_114/sol1.py) * Problem 119 * [Sol1](project_euler/problem_119/sol1.py) * Problem 120 diff --git a/project_euler/problem_114/__init__.py b/project_euler/problem_114/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_114/sol1.py b/project_euler/problem_114/sol1.py new file mode 100644 index 000000000000..14d8092b25dd --- /dev/null +++ b/project_euler/problem_114/sol1.py @@ -0,0 +1,58 @@ +""" +Project Euler Problem 114: https://projecteuler.net/problem=114 + +A row measuring seven units in length has red blocks with a minimum length +of three units placed on it, such that any two red blocks +(which are allowed to be different lengths) are separated by at least one grey square. +There are exactly seventeen ways of doing this. + + |g|g|g|g|g|g|g| |r,r,r|g|g|g|g| + + |g|r,r,r|g|g|g| |g|g|r,r,r|g|g| + + |g|g|g|r,r,r|g| |g|g|g|g|r,r,r| + + |r,r,r|g|r,r,r| |r,r,r,r|g|g|g| + + |g|r,r,r,r|g|g| |g|g|r,r,r,r|g| + + |g|g|g|r,r,r,r| |r,r,r,r,r|g|g| + + |g|r,r,r,r,r|g| |g|g|r,r,r,r,r| + + |r,r,r,r,r,r|g| |g|r,r,r,r,r,r| + + |r,r,r,r,r,r,r| + +How many ways can a row measuring fifty units in length be filled? + +NOTE: Although the example above does not lend itself to the possibility, +in general it is permitted to mix block sizes. For example, +on a row measuring eight units in length you could use red (3), grey (1), and red (4). +""" + + +def solution(length: int = 50) -> int: + """ + Returns the number of ways a row of the given length can be filled + + >>> solution(7) + 17 + """ + + ways_number = [1] * (length + 1) + + for row_length in range(3, length + 1): + for block_length in range(3, row_length + 1): + for block_start in range(row_length - block_length): + ways_number[row_length] += ways_number[ + row_length - block_start - block_length - 1 + ] + + ways_number[row_length] += 1 + + return ways_number[length] + + +if __name__ == "__main__": + print(f"{solution() = }") From 063a0eced918ffa58af99c9c8d2fab72ea519c59 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 7 Aug 2022 14:20:45 +0300 Subject: [PATCH 1829/2908] feat: add Project Euler problem 115 solution 1 (#6303) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_115/__init__.py | 0 project_euler/problem_115/sol1.py | 62 +++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 project_euler/problem_115/__init__.py create mode 100644 project_euler/problem_115/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 98b87f2fe279..b37bb35ec619 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -856,6 +856,8 @@ * [Sol1](project_euler/problem_113/sol1.py) * Problem 114 * [Sol1](project_euler/problem_114/sol1.py) + * Problem 115 + * [Sol1](project_euler/problem_115/sol1.py) * Problem 119 * [Sol1](project_euler/problem_119/sol1.py) * Problem 120 diff --git a/project_euler/problem_115/__init__.py b/project_euler/problem_115/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_115/sol1.py b/project_euler/problem_115/sol1.py new file mode 100644 index 000000000000..15a13516d54d --- /dev/null +++ b/project_euler/problem_115/sol1.py @@ -0,0 +1,62 @@ +""" +Project Euler Problem 115: https://projecteuler.net/problem=115 + +NOTE: This is a more difficult version of Problem 114 +(https://projecteuler.net/problem=114). + +A row measuring n units in length has red blocks +with a minimum length of m units placed on it, such that any two red blocks +(which are allowed to be different lengths) are separated by at least one black square. + +Let the fill-count function, F(m, n), +represent the number of ways that a row can be filled. + +For example, F(3, 29) = 673135 and F(3, 30) = 1089155. + +That is, for m = 3, it can be seen that n = 30 is the smallest value +for which the fill-count function first exceeds one million. + +In the same way, for m = 10, it can be verified that +F(10, 56) = 880711 and F(10, 57) = 1148904, so n = 57 is the least value +for which the fill-count function first exceeds one million. + +For m = 50, find the least value of n +for which the fill-count function first exceeds one million. +""" + +from itertools import count + + +def solution(min_block_length: int = 50) -> int: + """ + Returns for given minimum block length the least value of n + for which the fill-count function first exceeds one million + + >>> solution(3) + 30 + + >>> solution(10) + 57 + """ + + fill_count_functions = [1] * min_block_length + + for n in count(min_block_length): + fill_count_functions.append(1) + + for block_length in range(min_block_length, n + 1): + for block_start in range(n - block_length): + fill_count_functions[n] += fill_count_functions[ + n - block_start - block_length - 1 + ] + + fill_count_functions[n] += 1 + + if fill_count_functions[n] > 1_000_000: + break + + return n + + +if __name__ == "__main__": + print(f"{solution() = }") From f46ce47274e89ab52581b56fec0aefa0e844dfa7 Mon Sep 17 00:00:00 2001 From: AmirMohammad Hosseini Nasab <19665344+itsamirhn@users.noreply.github.com> Date: Fri, 12 Aug 2022 13:42:58 +0430 Subject: [PATCH 1830/2908] Add Max Fenwick Tree (#6298) * Add `MaxFenwickTree` * Reformat code style * Fix type hints * Fix type hints again * Complete docstring * Complete docstring * Fix typo in file name * Change MaxFenwickTree into 0-based indexing * Fix Bugs * Minor fix --- .../binary_tree/maximum_fenwick_tree.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 data_structures/binary_tree/maximum_fenwick_tree.py diff --git a/data_structures/binary_tree/maximum_fenwick_tree.py b/data_structures/binary_tree/maximum_fenwick_tree.py new file mode 100644 index 000000000000..e90bd634d51c --- /dev/null +++ b/data_structures/binary_tree/maximum_fenwick_tree.py @@ -0,0 +1,102 @@ +class MaxFenwickTree: + """ + Maximum Fenwick Tree + + More info: https://cp-algorithms.com/data_structures/fenwick.html + --------- + >>> ft = MaxFenwickTree(5) + >>> ft.query(0, 5) + 0 + >>> ft.update(4, 100) + >>> ft.query(0, 5) + 100 + >>> ft.update(4, 0) + >>> ft.update(2, 20) + >>> ft.query(0, 5) + 20 + >>> ft.update(4, 10) + >>> ft.query(2, 5) + 10 + >>> ft.query(1, 5) + 20 + >>> ft.update(2, 0) + >>> ft.query(0, 5) + 10 + >>> ft = MaxFenwickTree(10000) + >>> ft.update(255, 30) + >>> ft.query(0, 10000) + 30 + """ + + def __init__(self, size: int) -> None: + """ + Create empty Maximum Fenwick Tree with specified size + + Parameters: + size: size of Array + + Returns: + None + """ + self.size = size + self.arr = [0] * size + self.tree = [0] * size + + @staticmethod + def get_next(index: int) -> int: + """ + Get next index in O(1) + """ + return index + (index & -index) + + @staticmethod + def get_prev(index: int) -> int: + """ + Get previous index in O(1) + """ + return index - (index & -index) + + def update(self, index: int, value: int) -> None: + """ + Set index to value in O(lg^2 N) + + Parameters: + index: index to update + value: value to set + + Returns: + None + """ + self.arr[index] = value + while index < self.size: + self.tree[index] = max(value, self.query(self.get_prev(index), index)) + index = self.get_next(index) + + def query(self, left: int, right: int) -> int: + """ + Answer the query of maximum range [l, r) in O(lg^2 N) + + Parameters: + left: left index of query range (inclusive) + right: right index of query range (exclusive) + + Returns: + Maximum value of range [left, right) + """ + right -= 1 # Because of right is exclusive + result = 0 + while left < right: + current_left = self.get_prev(right) + if left < current_left: + result = max(result, self.tree[right]) + right = current_left + else: + result = max(result, self.arr[right]) + right -= 1 + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f31fa4ea7e26d8721be7e966ecf92fcbd87af65f Mon Sep 17 00:00:00 2001 From: AmirMohammad Hosseini Nasab <19665344+itsamirhn@users.noreply.github.com> Date: Tue, 16 Aug 2022 22:08:33 +0430 Subject: [PATCH 1831/2908] Fenwick Tree (#6319) * Enhance fenwick_tree.py * Change update to add in fenwick_tree.py * Some changes * Fix bug * Add O(N) initializer to FenwickTree * Add get method to Fenwick Tree * Change tree in Fenwick Tree * Add rank query to FenwickTree * Add get_array method to FenwickTree * Add some tests * Update data_structures/binary_tree/fenwick_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/fenwick_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/fenwick_tree.py Co-authored-by: Christian Clauss * change `List` to `list` Co-authored-by: Christian Clauss --- data_structures/binary_tree/fenwick_tree.py | 263 ++++++++++++++++++-- 1 file changed, 241 insertions(+), 22 deletions(-) diff --git a/data_structures/binary_tree/fenwick_tree.py b/data_structures/binary_tree/fenwick_tree.py index 54f0f07ac68d..96020d1427af 100644 --- a/data_structures/binary_tree/fenwick_tree.py +++ b/data_structures/binary_tree/fenwick_tree.py @@ -1,28 +1,247 @@ +from copy import deepcopy + + class FenwickTree: - def __init__(self, SIZE): # create fenwick tree with size SIZE - self.Size = SIZE - self.ft = [0 for i in range(0, SIZE)] + """ + Fenwick Tree + + More info: https://en.wikipedia.org/wiki/Fenwick_tree + """ + + def __init__(self, arr: list[int] = None, size: int = None) -> None: + """ + Constructor for the Fenwick tree + + Parameters: + arr (list): list of elements to initialize the tree with (optional) + size (int): size of the Fenwick tree (if arr is None) + """ + + if arr is None and size is not None: + self.size = size + self.tree = [0] * size + elif arr is not None: + self.init(arr) + else: + raise ValueError("Either arr or size must be specified") + + def init(self, arr: list[int]) -> None: + """ + Initialize the Fenwick tree with arr in O(N) + + Parameters: + arr (list): list of elements to initialize the tree with + + Returns: + None + + >>> a = [1, 2, 3, 4, 5] + >>> f1 = FenwickTree(a) + >>> f2 = FenwickTree(size=len(a)) + >>> for index, value in enumerate(a): + ... f2.add(index, value) + >>> f1.tree == f2.tree + True + """ + self.size = len(arr) + self.tree = deepcopy(arr) + for i in range(1, self.size): + j = self.next(i) + if j < self.size: + self.tree[j] += self.tree[i] + + def get_array(self) -> list[int]: + """ + Get the Normal Array of the Fenwick tree in O(N) + + Returns: + list: Normal Array of the Fenwick tree + + >>> a = [i for i in range(128)] + >>> f = FenwickTree(a) + >>> f.get_array() == a + True + """ + arr = self.tree[:] + for i in range(self.size - 1, 0, -1): + j = self.next(i) + if j < self.size: + arr[j] -= arr[i] + return arr + + @staticmethod + def next(index: int) -> int: + return index + (index & (-index)) + + @staticmethod + def prev(index: int) -> int: + return index - (index & (-index)) + + def add(self, index: int, value: int) -> None: + """ + Add a value to index in O(lg N) + + Parameters: + index (int): index to add value to + value (int): value to add to index + + Returns: + None + + >>> f = FenwickTree([1, 2, 3, 4, 5]) + >>> f.add(0, 1) + >>> f.add(1, 2) + >>> f.add(2, 3) + >>> f.add(3, 4) + >>> f.add(4, 5) + >>> f.get_array() + [2, 4, 6, 8, 10] + """ + if index == 0: + self.tree[0] += value + return + while index < self.size: + self.tree[index] += value + index = self.next(index) + + def update(self, index: int, value: int) -> None: + """ + Set the value of index in O(lg N) + + Parameters: + index (int): index to set value to + value (int): value to set in index - 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) + Returns: + None - def query(self, i): # query cumulative data from index 0 to i in O(lg N) - ret = 0 - while i > 0: - ret += self.ft[i] - i -= i & (-i) - return ret + >>> f = FenwickTree([5, 4, 3, 2, 1]) + >>> f.update(0, 1) + >>> f.update(1, 2) + >>> f.update(2, 3) + >>> f.update(3, 4) + >>> f.update(4, 5) + >>> f.get_array() + [1, 2, 3, 4, 5] + """ + self.add(index, value - self.get(index)) + + def prefix(self, right: int) -> int: + """ + Prefix sum of all elements in [0, right) in O(lg N) + + Parameters: + right (int): right bound of the query (exclusive) + + Returns: + int: sum of all elements in [0, right) + + >>> a = [i for i in range(128)] + >>> f = FenwickTree(a) + >>> res = True + >>> for i in range(len(a)): + ... res = res and f.prefix(i) == sum(a[:i]) + >>> res + True + """ + if right == 0: + return 0 + result = self.tree[0] + right -= 1 # make right inclusive + while right > 0: + result += self.tree[right] + right = self.prev(right) + return result + + def query(self, left: int, right: int) -> int: + """ + Query the sum of all elements in [left, right) in O(lg N) + + Parameters: + left (int): left bound of the query (inclusive) + right (int): right bound of the query (exclusive) + + Returns: + int: sum of all elements in [left, right) + + >>> a = [i for i in range(128)] + >>> f = FenwickTree(a) + >>> res = True + >>> for i in range(len(a)): + ... for j in range(i + 1, len(a)): + ... res = res and f.query(i, j) == sum(a[i:j]) + >>> res + True + """ + return self.prefix(right) - self.prefix(left) + + def get(self, index: int) -> int: + """ + Get value at index in O(lg N) + + Parameters: + index (int): index to get the value + + Returns: + int: Value of element at index + + >>> a = [i for i in range(128)] + >>> f = FenwickTree(a) + >>> res = True + >>> for i in range(len(a)): + ... res = res and f.get(i) == a[i] + >>> res + True + """ + return self.query(index, index + 1) + + def rank_query(self, value: int) -> int: + """ + Find the largest index with prefix(i) <= value in O(lg N) + NOTE: Requires that all values are non-negative! + + Parameters: + value (int): value to find the largest index of + + Returns: + -1: if value is smaller than all elements in prefix sum + int: largest index with prefix(i) <= value + + >>> f = FenwickTree([1, 2, 0, 3, 0, 5]) + >>> f.rank_query(0) + -1 + >>> f.rank_query(2) + 0 + >>> f.rank_query(1) + 0 + >>> f.rank_query(3) + 2 + >>> f.rank_query(5) + 2 + >>> f.rank_query(6) + 4 + >>> f.rank_query(11) + 5 + """ + value -= self.tree[0] + if value < 0: + return -1 + + j = 1 # Largest power of 2 <= size + while j * 2 < self.size: + j *= 2 + + i = 0 + + while j > 0: + if i + j < self.size and self.tree[i + j] <= value: + value -= self.tree[i + j] + i += j + j //= 2 + return i 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)) - f.update(2, -5) - print(f.query(1)) - print(f.query(3)) + import doctest + + doctest.testmod() From b1818af5171ecf149b5a602572a7361c5e624f0d Mon Sep 17 00:00:00 2001 From: zhexuanl <63616187+zhexuanl@users.noreply.github.com> Date: Wed, 24 Aug 2022 12:48:54 +0800 Subject: [PATCH 1832/2908] Add Digital Image Processing Algorithm: Local Binary Pattern (#6294) * add algorithm local binary pattern * fix failed test for local binary pattern * updating DIRECTORY.md * fix detected precommit-error * fix precommit error * final check * Add descriptive name for parameters x and y * Update digital_image_processing/filters/local_binary_pattern.py Co-authored-by: Christian Clauss * Update digital_image_processing/filters/local_binary_pattern.py Co-authored-by: Christian Clauss * Update digital_image_processing/filters/local_binary_pattern.py Co-authored-by: Christian Clauss * Update local_binary_pattern.py * undo changes made on get_neighbors_pixel() * files formatted by black * Update digital_image_processing/filters/local_binary_pattern.py ok thanks Co-authored-by: Christian Clauss * add test for get_neighbors_pixel() function * reviewed * fix get_neighbors_pixel * Update test_digital_image_processing.py * updating DIRECTORY.md * Create code_quality.yml * Create code_quality.yml * Delete code_quality.yml * Update code_quality.yml * Delete code_quality.yml Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 2 + .../filters/local_binary_pattern.py | 81 +++++++++++++++++++ .../test_digital_image_processing.py | 32 ++++++++ 3 files changed, 115 insertions(+) create mode 100644 digital_image_processing/filters/local_binary_pattern.py diff --git a/DIRECTORY.md b/DIRECTORY.md index b37bb35ec619..a7305395a67b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -152,6 +152,7 @@ * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py) + * [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py) * [Merge Two Binary Trees](data_structures/binary_tree/merge_two_binary_trees.py) * [Non Recursive Segment Tree](data_structures/binary_tree/non_recursive_segment_tree.py) * [Number Of Possible Binary Trees](data_structures/binary_tree/number_of_possible_binary_trees.py) @@ -229,6 +230,7 @@ * [Convolve](digital_image_processing/filters/convolve.py) * [Gabor Filter](digital_image_processing/filters/gabor_filter.py) * [Gaussian Filter](digital_image_processing/filters/gaussian_filter.py) + * [Local Binary Pattern](digital_image_processing/filters/local_binary_pattern.py) * [Median Filter](digital_image_processing/filters/median_filter.py) * [Sobel Filter](digital_image_processing/filters/sobel_filter.py) * Histogram Equalization diff --git a/digital_image_processing/filters/local_binary_pattern.py b/digital_image_processing/filters/local_binary_pattern.py new file mode 100644 index 000000000000..e73aa59bfa53 --- /dev/null +++ b/digital_image_processing/filters/local_binary_pattern.py @@ -0,0 +1,81 @@ +import cv2 +import numpy as np + + +def get_neighbors_pixel( + image: np.ndarray, x_coordinate: int, y_coordinate: int, center: int +) -> int: + """ + Comparing local neighborhood pixel value with threshold value of centre pixel. + Exception is required when neighborhood value of a center pixel value is null. + i.e. values present at boundaries. + + :param image: The image we're working with + :param x_coordinate: x-coordinate of the pixel + :param y_coordinate: The y coordinate of the pixel + :param center: center pixel value + :return: The value of the pixel is being returned. + """ + + try: + return int(image[x_coordinate][y_coordinate] >= center) + except (IndexError, TypeError): + return 0 + + +def local_binary_value(image: np.ndarray, x_coordinate: int, y_coordinate: int) -> int: + """ + It takes an image, an x and y coordinate, and returns the + decimal value of the local binary patternof the pixel + at that coordinate + + :param image: the image to be processed + :param x_coordinate: x coordinate of the pixel + :param y_coordinate: the y coordinate of the pixel + :return: The decimal value of the binary value of the pixels + around the center pixel. + """ + center = image[x_coordinate][y_coordinate] + powers = [1, 2, 4, 8, 16, 32, 64, 128] + + # skip get_neighbors_pixel if center is null + if center is None: + return 0 + + # Starting from the top right, assigning value to pixels clockwise + binary_values = [ + get_neighbors_pixel(image, x_coordinate - 1, y_coordinate + 1, center), + get_neighbors_pixel(image, x_coordinate, y_coordinate + 1, center), + get_neighbors_pixel(image, x_coordinate - 1, y_coordinate, center), + get_neighbors_pixel(image, x_coordinate + 1, y_coordinate + 1, center), + get_neighbors_pixel(image, x_coordinate + 1, y_coordinate, center), + get_neighbors_pixel(image, x_coordinate + 1, y_coordinate - 1, center), + get_neighbors_pixel(image, x_coordinate, y_coordinate - 1, center), + get_neighbors_pixel(image, x_coordinate - 1, y_coordinate - 1, center), + ] + + # Converting the binary value to decimal. + return sum( + binary_value * power for binary_value, power in zip(binary_values, powers) + ) + + +if __name__ == "main": + + # Reading the image and converting it to grayscale. + image = cv2.imread( + "digital_image_processing/image_data/lena.jpg", cv2.IMREAD_GRAYSCALE + ) + + # Create a numpy array as the same height and width of read image + lbp_image = np.zeros((image.shape[0], image.shape[1])) + + # Iterating through the image and calculating the + # local binary pattern value for each pixel. + for i in range(0, image.shape[0]): + for j in range(0, image.shape[1]): + lbp_image[i][j] = local_binary_value(image, i, j) + + cv2.imshow("local binary pattern", lbp_image) + cv2.waitKey(0) + cv2.destroyAllWindows() diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 40f2f7b83b6d..1f42fddf297a 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -1,6 +1,7 @@ """ PyTest's for Digital Image Processing """ +import numpy as np from cv2 import COLOR_BGR2GRAY, cvtColor, imread from numpy import array, uint8 from PIL import Image @@ -12,6 +13,7 @@ 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 local_binary_pattern as lbp 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 @@ -91,3 +93,33 @@ def test_nearest_neighbour( nn = rs.NearestNeighbour(imread(file_path, 1), 400, 200) nn.process() assert nn.output.any() + + +def test_local_binary_pattern(): + file_path: str = "digital_image_processing/image_data/lena.jpg" + + # Reading the image and converting it to grayscale. + image = imread(file_path, 0) + + # Test for get_neighbors_pixel function() return not None + x_coordinate = 0 + y_coordinate = 0 + center = image[x_coordinate][y_coordinate] + + neighbors_pixels = lbp.get_neighbors_pixel( + image, x_coordinate, y_coordinate, center + ) + + assert neighbors_pixels is not None + + # Test for local_binary_pattern function() + # Create a numpy array as the same height and width of read image + lbp_image = np.zeros((image.shape[0], image.shape[1])) + + # Iterating through the image and calculating the local binary pattern value + # for each pixel. + for i in range(0, image.shape[0]): + for j in range(0, image.shape[1]): + lbp_image[i][j] = lbp.local_binary_value(image, i, j) + + assert lbp_image.any() From cbf3c6140aafefbaef7186e0cb97d0758b1d38b2 Mon Sep 17 00:00:00 2001 From: Margaret <62753112+meg-1@users.noreply.github.com> Date: Mon, 5 Sep 2022 04:51:11 +0300 Subject: [PATCH 1833/2908] add the dna algorithm (#6323) * adding the dna algorithm * following bot recommendations following bot recommendations for the indentation * following bot recommendations following bot recommendations regarding indentation [ again ] * following bot recommendations following bot recommendations regarding indentation [ again. ] * following bot recommendations following bot recommendations. --- strings/dna.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 strings/dna.py diff --git a/strings/dna.py b/strings/dna.py new file mode 100644 index 000000000000..46e271d689db --- /dev/null +++ b/strings/dna.py @@ -0,0 +1,26 @@ +import re + + +def dna(dna: str) -> str: + + """ + https://en.wikipedia.org/wiki/DNA + Returns the second side of a DNA strand + + >>> dna("GCTA") + 'CGAT' + >>> dna("ATGC") + 'TACG' + >>> dna("CTGA") + 'GACT' + >>> dna("GFGG") + 'Invalid Strand' + """ + + r = len(re.findall("[ATCG]", dna)) != len(dna) + val = dna.translate(dna.maketrans("ATCG", "TAGC")) + return "Invalid Strand" if r else val + + +if __name__ == "__main__": + __import__("doctest").testmod() From 4e4fe95369c15e62364f7d6a6bfc9464c1143dc6 Mon Sep 17 00:00:00 2001 From: Nikhil Kala Date: Fri, 9 Sep 2022 11:09:31 -0600 Subject: [PATCH 1834/2908] chore: remove the PayPal badge (#6348) --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 0298d46020ac..c787979607ee 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@ Contributions Welcome - - Donate - Discord chat From 81e30fd33c91bc37bc3baf54c42d1b192ecf41a6 Mon Sep 17 00:00:00 2001 From: C21 <31063253+C21-github@users.noreply.github.com> Date: Wed, 14 Sep 2022 13:54:55 +0530 Subject: [PATCH 1835/2908] Fix Max Fenwick Tree (#6328) --- .../binary_tree/maximum_fenwick_tree.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/data_structures/binary_tree/maximum_fenwick_tree.py b/data_structures/binary_tree/maximum_fenwick_tree.py index e90bd634d51c..84967a70cc73 100644 --- a/data_structures/binary_tree/maximum_fenwick_tree.py +++ b/data_structures/binary_tree/maximum_fenwick_tree.py @@ -16,7 +16,7 @@ class MaxFenwickTree: 20 >>> ft.update(4, 10) >>> ft.query(2, 5) - 10 + 20 >>> ft.query(1, 5) 20 >>> ft.update(2, 0) @@ -26,6 +26,14 @@ class MaxFenwickTree: >>> ft.update(255, 30) >>> ft.query(0, 10000) 30 + >>> ft = MaxFenwickTree(6) + >>> ft.update(5, 1) + >>> ft.query(5, 6) + 1 + >>> ft = MaxFenwickTree(6) + >>> ft.update(0, 1000) + >>> ft.query(0, 1) + 1000 """ def __init__(self, size: int) -> None: @@ -47,14 +55,14 @@ def get_next(index: int) -> int: """ Get next index in O(1) """ - return index + (index & -index) + return index | (index + 1) @staticmethod def get_prev(index: int) -> int: """ Get previous index in O(1) """ - return index - (index & -index) + return (index & (index + 1)) - 1 def update(self, index: int, value: int) -> None: """ @@ -69,7 +77,11 @@ def update(self, index: int, value: int) -> None: """ self.arr[index] = value while index < self.size: - self.tree[index] = max(value, self.query(self.get_prev(index), index)) + current_left_border = self.get_prev(index) + 1 + if current_left_border == index: + self.tree[index] = value + else: + self.tree[index] = max(value, current_left_border, index) index = self.get_next(index) def query(self, left: int, right: int) -> int: @@ -85,9 +97,9 @@ def query(self, left: int, right: int) -> int: """ right -= 1 # Because of right is exclusive result = 0 - while left < right: + while left <= right: current_left = self.get_prev(right) - if left < current_left: + if left <= current_left: result = max(result, self.tree[right]) right = current_left else: From 2104fa7aebe8d76b2b2b2c47fe7e2ee615a05df6 Mon Sep 17 00:00:00 2001 From: Nikos Giachoudis Date: Wed, 14 Sep 2022 11:40:04 +0300 Subject: [PATCH 1836/2908] Unify `O(sqrt(N))` `is_prime` functions under `project_euler` (#6258) * fixes #5434 * fixes broken solution * removes assert * removes assert * Apply suggestions from code review Co-authored-by: John Law * Update project_euler/problem_003/sol1.py Co-authored-by: John Law --- project_euler/problem_003/sol1.py | 30 ++++++++--------- project_euler/problem_007/sol1.py | 32 ++++++++++++------- project_euler/problem_007/sol2.py | 29 +++++++++++++---- project_euler/problem_007/sol3.py | 29 +++++++++++++---- project_euler/problem_010/sol1.py | 27 ++++++++++++---- project_euler/problem_010/sol2.py | 23 +++++++++++--- project_euler/problem_027/sol1.py | 41 +++++++++++++++++------- project_euler/problem_037/sol1.py | 51 ++++++++++++++++++++--------- project_euler/problem_041/sol1.py | 40 +++++++++++++++++------ project_euler/problem_046/sol1.py | 49 +++++++++++++++++++--------- project_euler/problem_049/sol1.py | 38 +++++++++++++++------- project_euler/problem_058/sol1.py | 53 ++++++++++++++++++++----------- 12 files changed, 310 insertions(+), 132 deletions(-) diff --git a/project_euler/problem_003/sol1.py b/project_euler/problem_003/sol1.py index 606a6945e4ad..a7d01bb041ba 100644 --- a/project_euler/problem_003/sol1.py +++ b/project_euler/problem_003/sol1.py @@ -13,9 +13,11 @@ import math -def is_prime(num: int) -> bool: - """ - Returns boolean representing primality of given number num. +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + A number is prime if it has exactly two factors: 1 and itself. + Returns boolean representing primality of given number (i.e., if the + result is true, then the number is indeed prime else it is not). >>> is_prime(2) True @@ -26,23 +28,21 @@ def is_prime(num: int) -> bool: >>> is_prime(2999) True >>> is_prime(0) - Traceback (most recent call last): - ... - ValueError: Parameter num must be greater than or equal to two. + False >>> is_prime(1) - Traceback (most recent call last): - ... - ValueError: Parameter num must be greater than or equal to two. + False """ - if num <= 1: - raise ValueError("Parameter num must be greater than or equal to two.") - if num == 2: + if 1 < number < 4: + # 2 and 3 are primes return True - elif num % 2 == 0: + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes return False - for i in range(3, int(math.sqrt(num)) + 1, 2): - if num % i == 0: + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: return False return True diff --git a/project_euler/problem_007/sol1.py b/project_euler/problem_007/sol1.py index 78fbcb511611..f52ff931f9a8 100644 --- a/project_euler/problem_007/sol1.py +++ b/project_euler/problem_007/sol1.py @@ -15,29 +15,37 @@ from math import sqrt -def is_prime(num: int) -> bool: - """ - Determines whether the given number is prime or not +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + A number is prime if it has exactly two factors: 1 and itself. + Returns boolean representing primality of given number (i.e., if the + result is true, then the number is indeed prime else it is not). >>> is_prime(2) True - >>> is_prime(15) + >>> is_prime(3) + True + >>> is_prime(27) False - >>> is_prime(29) + >>> is_prime(2999) True >>> is_prime(0) False + >>> is_prime(1) + False """ - if num == 2: + if 1 < number < 4: + # 2 and 3 are primes return True - elif num % 2 == 0: + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes return False - else: - sq = int(sqrt(num)) + 1 - for i in range(3, sq, 2): - if num % i == 0: - return False + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False return True diff --git a/project_euler/problem_007/sol2.py b/project_euler/problem_007/sol2.py index 44d72e9493e8..75d351889ea8 100644 --- a/project_euler/problem_007/sol2.py +++ b/project_euler/problem_007/sol2.py @@ -11,22 +11,39 @@ References: - https://en.wikipedia.org/wiki/Prime_number """ +import math def is_prime(number: int) -> bool: - """ - Determines whether the given number is prime or not + """Checks to see if a number is a prime in O(sqrt(n)). + A number is prime if it has exactly two factors: 1 and itself. + Returns boolean representing primality of given number (i.e., if the + result is true, then the number is indeed prime else it is not). >>> is_prime(2) True - >>> is_prime(15) + >>> is_prime(3) + True + >>> is_prime(27) False - >>> is_prime(29) + >>> is_prime(2999) True + >>> is_prime(0) + False + >>> is_prime(1) + False """ - for i in range(2, int(number**0.5) + 1): - if number % i == 0: + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes + return False + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: return False return True diff --git a/project_euler/problem_007/sol3.py b/project_euler/problem_007/sol3.py index daa719cefbda..774260db99a0 100644 --- a/project_euler/problem_007/sol3.py +++ b/project_euler/problem_007/sol3.py @@ -16,20 +16,37 @@ def is_prime(number: int) -> bool: - """ - Determines whether a given number is prime or not + """Checks to see if a number is a prime in O(sqrt(n)). + A number is prime if it has exactly two factors: 1 and itself. + Returns boolean representing primality of given number (i.e., if the + result is true, then the number is indeed prime else it is not). >>> is_prime(2) True - >>> is_prime(15) + >>> is_prime(3) + True + >>> is_prime(27) False - >>> is_prime(29) + >>> is_prime(2999) True + >>> is_prime(0) + False + >>> is_prime(1) + False """ - if number % 2 == 0 and number > 2: + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes return False - return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False + return True def prime_generator(): diff --git a/project_euler/problem_010/sol1.py b/project_euler/problem_010/sol1.py index e060761eecab..31f2feda3728 100644 --- a/project_euler/problem_010/sol1.py +++ b/project_euler/problem_010/sol1.py @@ -11,12 +11,14 @@ - https://en.wikipedia.org/wiki/Prime_number """ -from math import sqrt +import math -def is_prime(n: int) -> bool: - """ - Returns boolean representing primality of given number num. +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + A number is prime if it has exactly two factors: 1 and itself. + Returns boolean representing primality of given number num (i.e., if the + result is true, then the number is indeed prime else it is not). >>> is_prime(2) True @@ -26,13 +28,24 @@ def is_prime(n: int) -> bool: False >>> is_prime(2999) True + >>> is_prime(0) + False + >>> is_prime(1) + False """ - if 1 < n < 4: + if 1 < number < 4: + # 2 and 3 are primes return True - elif n < 2 or not n % 2: + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes return False - return not any(not n % i for i in range(3, int(sqrt(n) + 1), 2)) + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False + return True def solution(n: int = 2000000) -> int: diff --git a/project_euler/problem_010/sol2.py b/project_euler/problem_010/sol2.py index a288bb85fd52..245cca1d1720 100644 --- a/project_euler/problem_010/sol2.py +++ b/project_euler/problem_010/sol2.py @@ -16,8 +16,10 @@ def is_prime(number: int) -> bool: - """ - Returns boolean representing primality of given number num. + """Checks to see if a number is a prime in O(sqrt(n)). + A number is prime if it has exactly two factors: 1 and itself. + Returns boolean representing primality of given number num (i.e., if the + result is true, then the number is indeed prime else it is not). >>> is_prime(2) True @@ -27,11 +29,24 @@ def is_prime(number: int) -> bool: False >>> is_prime(2999) True + >>> is_prime(0) + False + >>> is_prime(1) + False """ - if number % 2 == 0 and number > 2: + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes return False - return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False + return True def prime_generator() -> Iterator[int]: diff --git a/project_euler/problem_027/sol1.py b/project_euler/problem_027/sol1.py index 928c0ec4feb7..c93e2b4fa251 100644 --- a/project_euler/problem_027/sol1.py +++ b/project_euler/problem_027/sol1.py @@ -23,22 +23,39 @@ import math -def is_prime(k: int) -> bool: - """ - Determine if a number is prime - >>> is_prime(10) +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + A number is prime if it has exactly two factors: 1 and itself. + Returns boolean representing primality of given number num (i.e., if the + result is true, then the number is indeed prime else it is not). + + >>> is_prime(2) + True + >>> is_prime(3) + True + >>> is_prime(27) False - >>> is_prime(11) + >>> is_prime(2999) True + >>> is_prime(0) + False + >>> is_prime(1) + False + >>> is_prime(-10) + False """ - if k < 2 or k % 2 == 0: - return False - elif k == 2: + + if 1 < number < 4: + # 2 and 3 are primes return True - else: - for x in range(3, int(math.sqrt(k) + 1), 2): - if k % x == 0: - return False + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes + return False + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False return True diff --git a/project_euler/problem_037/sol1.py b/project_euler/problem_037/sol1.py index 0411ad41ba2f..ef7686cbcb96 100644 --- a/project_euler/problem_037/sol1.py +++ b/project_euler/problem_037/sol1.py @@ -1,4 +1,7 @@ """ +Truncatable primes +Problem 37: https://projecteuler.net/problem=37 + 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. @@ -11,28 +14,46 @@ from __future__ import annotations -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 +import math -def is_prime(n: int) -> bool: - """ - Returns True if n is prime, - False otherwise, for 1 <= n <= 1000000 - >>> is_prime(87) +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + + A number is prime if it has exactly two factors: 1 and itself. + + >>> is_prime(0) False >>> is_prime(1) False - >>> is_prime(25363) + >>> is_prime(2) + True + >>> is_prime(3) + True + >>> is_prime(27) + False + >>> is_prime(87) + False + >>> is_prime(563) + True + >>> is_prime(2999) + True + >>> is_prime(67483) False """ - return seive[n] + + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes + return False + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False + return True def list_truncated_nums(n: int) -> list[int]: diff --git a/project_euler/problem_041/sol1.py b/project_euler/problem_041/sol1.py index 80ef2125b82a..2ef0120684c3 100644 --- a/project_euler/problem_041/sol1.py +++ b/project_euler/problem_041/sol1.py @@ -12,25 +12,45 @@ """ from __future__ import annotations +import math from itertools import permutations -from math import sqrt -def is_prime(n: int) -> bool: - """ - Returns True if n is prime, - False otherwise. - >>> is_prime(67483) +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + + A number is prime if it has exactly two factors: 1 and itself. + + >>> is_prime(0) False - >>> is_prime(563) + >>> is_prime(1) + False + >>> is_prime(2) True + >>> is_prime(3) + True + >>> is_prime(27) + False >>> is_prime(87) False + >>> is_prime(563) + True + >>> is_prime(2999) + True + >>> is_prime(67483) + False """ - if n % 2 == 0: + + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes return False - for i in range(3, int(sqrt(n) + 1), 2): - if n % i == 0: + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: return False return True diff --git a/project_euler/problem_046/sol1.py b/project_euler/problem_046/sol1.py index 550c4c7c4268..07dd9bbf84c8 100644 --- a/project_euler/problem_046/sol1.py +++ b/project_euler/problem_046/sol1.py @@ -19,30 +19,49 @@ from __future__ import annotations -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 +import math -def is_prime(n: int) -> bool: - """ - Returns True if n is prime, - False otherwise, for 2 <= n <= 100000 +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + + A number is prime if it has exactly two factors: 1 and itself. + + >>> is_prime(0) + False + >>> is_prime(1) + False + >>> is_prime(2) + True + >>> is_prime(3) + True + >>> is_prime(27) + False >>> is_prime(87) False - >>> is_prime(23) + >>> is_prime(563) + True + >>> is_prime(2999) True - >>> is_prime(25363) + >>> is_prime(67483) False """ - return seive[n] + + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes + return False + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False + return True -odd_composites = [num for num in range(3, len(seive), 2) if not is_prime(num)] +odd_composites = [num for num in range(3, 100001, 2) if not is_prime(num)] def compute_nums(n: int) -> list[int]: diff --git a/project_euler/problem_049/sol1.py b/project_euler/problem_049/sol1.py index dd2ef71a38a8..5c7560cbddae 100644 --- a/project_euler/problem_049/sol1.py +++ b/project_euler/problem_049/sol1.py @@ -25,32 +25,46 @@ The bruteforce of this solution will be about 1 sec. """ +import math 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) + """Checks to see if a number is a prime in O(sqrt(n)). + + A number is prime if it has exactly two factors: 1 and itself. + + >>> is_prime(0) False >>> is_prime(1) False - >>> is_prime(-800) + >>> is_prime(2) + True + >>> is_prime(3) + True + >>> is_prime(27) + False + >>> is_prime(87) False - >>> is_prime(104729) + >>> is_prime(563) True + >>> is_prime(2999) + True + >>> is_prime(67483) + False """ - if number < 2: + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes return False - for i in range(2, floor(sqrt(number)) + 1): - if number % i == 0: + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: return False - return True diff --git a/project_euler/problem_058/sol1.py b/project_euler/problem_058/sol1.py index c59b0dd71af1..6a991c58b6b8 100644 --- a/project_euler/problem_058/sol1.py +++ b/project_euler/problem_058/sol1.py @@ -33,29 +33,46 @@ count of current primes. """ -from math import isqrt +import math -def is_prime(number: int) -> int: - """ - Returns whether the given number is prime or not +def is_prime(number: int) -> bool: + """Checks to see if a number is a prime in O(sqrt(n)). + + A number is prime if it has exactly two factors: 1 and itself. + + >>> is_prime(0) + False >>> is_prime(1) - 0 - >>> is_prime(17) - 1 - >>> is_prime(10000) - 0 + False + >>> is_prime(2) + True + >>> is_prime(3) + True + >>> is_prime(27) + False + >>> is_prime(87) + False + >>> is_prime(563) + True + >>> is_prime(2999) + True + >>> is_prime(67483) + False """ - if number == 1: - return 0 - - if number % 2 == 0 and number > 2: - return 0 - for i in range(3, isqrt(number) + 1, 2): - if number % i == 0: - return 0 - return 1 + if 1 < number < 4: + # 2 and 3 are primes + return True + elif number < 2 or number % 2 == 0 or number % 3 == 0: + # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes + return False + + # All primes number are in format of 6k +/- 1 + for i in range(5, int(math.sqrt(number) + 1), 6): + if number % i == 0 or number % (i + 2) == 0: + return False + return True def solution(ratio: float = 0.1) -> int: From 45d3eabeb5f22624245095abdc044422bfe5eeea Mon Sep 17 00:00:00 2001 From: Satish Mishra <36122092+ZicsX@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:13:08 +0530 Subject: [PATCH 1837/2908] Add Optimized Shell Sort (#6225) * Add Optimized Shell Sort * Added return type * reformatted * added shrink_shell.py * ran directory generator * Rename shrink_shell.py to shrink_shell_sort.py Co-authored-by: John Law --- DIRECTORY.md | 2 +- sorts/shrink_shell_sort.py | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 sorts/shrink_shell_sort.py diff --git a/DIRECTORY.md b/DIRECTORY.md index a7305395a67b..25eb0ef0e9ca 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -446,7 +446,6 @@ * [Scoring Functions](machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) * [Similarity Search](machine_learning/similarity_search.py) - * [Support Vector Machines](machine_learning/support_vector_machines.py) * [Word Frequency Functions](machine_learning/word_frequency_functions.py) ## Maths @@ -984,6 +983,7 @@ * [Recursive Quick Sort](sorts/recursive_quick_sort.py) * [Selection Sort](sorts/selection_sort.py) * [Shell Sort](sorts/shell_sort.py) + * [Shrink Shell](sorts/shrink_shell.py) * [Slowsort](sorts/slowsort.py) * [Stooge Sort](sorts/stooge_sort.py) * [Strand Sort](sorts/strand_sort.py) diff --git a/sorts/shrink_shell_sort.py b/sorts/shrink_shell_sort.py new file mode 100644 index 000000000000..69992bfb75bc --- /dev/null +++ b/sorts/shrink_shell_sort.py @@ -0,0 +1,66 @@ +""" +This function implements the shell sort algorithm +which is slightly faster than its pure implementation. + +This shell sort is implemented using a gap, which +shrinks by a certain factor each iteration. In this +implementation, the gap is initially set to the +length of the collection. The gap is then reduced by +a certain factor (1.3) each iteration. + +For each iteration, the algorithm compares elements +that are a certain number of positions apart +(determined by the gap). If the element at the higher +position is greater than the element at the lower +position, the two elements are swapped. The process +is repeated until the gap is equal to 1. + +The reason this is more efficient is that it reduces +the number of comparisons that need to be made. By +using a smaller gap, the list is sorted more quickly. +""" + + +def shell_sort(collection: list) -> list: + """Implementation of shell sort algorithm in Python + :param collection: Some mutable ordered collection with heterogeneous + comparable items inside + :return: the same collection ordered by ascending + + >>> shell_sort([3, 2, 1]) + [1, 2, 3] + >>> shell_sort([]) + [] + >>> shell_sort([1]) + [1] + """ + + # Choose an initial gap value + gap = len(collection) + + # Set the gap value to be decreased by a factor of 1.3 + # after each iteration + shrink = 1.3 + + # Continue sorting until the gap is 1 + while gap > 1: + + # Decrease the gap value + gap = int(gap / shrink) + + # Sort the elements using insertion sort + for i in range(gap, len(collection)): + temp = collection[i] + j = i + while j >= gap and collection[j - gap] > temp: + collection[j] = collection[j - gap] + j -= gap + collection[j] = temp + + return collection + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5754bd09ffd9a13d9e9bc3083419a22ab8ff7df6 Mon Sep 17 00:00:00 2001 From: Beksultan <96925396+Vazno@users.noreply.github.com> Date: Wed, 21 Sep 2022 21:37:38 +0600 Subject: [PATCH 1838/2908] fix typo (#6375) --- strings/is_contains_unique_chars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strings/is_contains_unique_chars.py b/strings/is_contains_unique_chars.py index fdf7a02ff43f..d6b698e769f8 100644 --- a/strings/is_contains_unique_chars.py +++ b/strings/is_contains_unique_chars.py @@ -7,7 +7,7 @@ def is_contains_unique_chars(input_str: str) -> bool: False Time complexity: O(n) - Space compexity: O(1) 19320 bytes as we are having 144697 characters in unicode + Space complexity: O(1) 19320 bytes as we are having 144697 characters in unicode """ # Each bit will represent each unicode character From 91c671ebabb3449fbbaefc2c4d959566eaed48dc Mon Sep 17 00:00:00 2001 From: Yannick Brenning <90418998+ybrenning@users.noreply.github.com> Date: Sat, 24 Sep 2022 18:46:03 +0200 Subject: [PATCH 1839/2908] Fix minor typo and add matrix dimension check (#6367) * Fix minor typo in comment * Add matrix dimension check * Add descriptive comment --- matrix/inverse_of_matrix.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/matrix/inverse_of_matrix.py b/matrix/inverse_of_matrix.py index 9deca6c3c08e..e414ee254c10 100644 --- a/matrix/inverse_of_matrix.py +++ b/matrix/inverse_of_matrix.py @@ -27,14 +27,21 @@ def inverse_of_matrix(matrix: list[list[float]]) -> list[list[float]]: [[0.25, -0.5], [-0.3, 1.0]] """ - D = Decimal # An abbreviation to be conciseness + D = Decimal # An abbreviation for conciseness + + # Check if the provided matrix has 2 rows and 2 columns, since this implementation only works for 2x2 matrices + if len(matrix) != 2 or len(matrix[0]) != 2 or len(matrix[1]) != 2: + raise ValueError("Please provide a matrix of size 2x2.") + # 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 a0b0f414ae134aa1772d33bb930e5a960f9979e8 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 24 Sep 2022 20:04:00 +0300 Subject: [PATCH 1840/2908] Add Project Euler problem 116 solution 1 (#6305) * Add solution * updating DIRECTORY.md * Fix pre-commit * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 6 ++- matrix/inverse_of_matrix.py | 3 +- project_euler/problem_116/__init__.py | 0 project_euler/problem_116/sol1.py | 64 +++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 project_euler/problem_116/__init__.py create mode 100644 project_euler/problem_116/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 25eb0ef0e9ca..1d9e6eff75c6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -446,6 +446,7 @@ * [Scoring Functions](machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) * [Similarity Search](machine_learning/similarity_search.py) + * [Support Vector Machines](machine_learning/support_vector_machines.py) * [Word Frequency Functions](machine_learning/word_frequency_functions.py) ## Maths @@ -859,6 +860,8 @@ * [Sol1](project_euler/problem_114/sol1.py) * Problem 115 * [Sol1](project_euler/problem_115/sol1.py) + * Problem 116 + * [Sol1](project_euler/problem_116/sol1.py) * Problem 119 * [Sol1](project_euler/problem_119/sol1.py) * Problem 120 @@ -983,7 +986,7 @@ * [Recursive Quick Sort](sorts/recursive_quick_sort.py) * [Selection Sort](sorts/selection_sort.py) * [Shell Sort](sorts/shell_sort.py) - * [Shrink Shell](sorts/shrink_shell.py) + * [Shrink Shell Sort](sorts/shrink_shell_sort.py) * [Slowsort](sorts/slowsort.py) * [Stooge Sort](sorts/stooge_sort.py) * [Strand Sort](sorts/strand_sort.py) @@ -1005,6 +1008,7 @@ * [Check Pangram](strings/check_pangram.py) * [Credit Card Validator](strings/credit_card_validator.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py) + * [Dna](strings/dna.py) * [Frequency Finder](strings/frequency_finder.py) * [Hamming Distance](strings/hamming_distance.py) * [Indian Phone Validator](strings/indian_phone_validator.py) diff --git a/matrix/inverse_of_matrix.py b/matrix/inverse_of_matrix.py index e414ee254c10..92780e656ea1 100644 --- a/matrix/inverse_of_matrix.py +++ b/matrix/inverse_of_matrix.py @@ -29,7 +29,8 @@ def inverse_of_matrix(matrix: list[list[float]]) -> list[list[float]]: D = Decimal # An abbreviation for conciseness - # Check if the provided matrix has 2 rows and 2 columns, since this implementation only works for 2x2 matrices + # Check if the provided matrix has 2 rows and 2 columns + # since this implementation only works for 2x2 matrices if len(matrix) != 2 or len(matrix[0]) != 2 or len(matrix[1]) != 2: raise ValueError("Please provide a matrix of size 2x2.") diff --git a/project_euler/problem_116/__init__.py b/project_euler/problem_116/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_116/sol1.py b/project_euler/problem_116/sol1.py new file mode 100644 index 000000000000..efa13ee3f25a --- /dev/null +++ b/project_euler/problem_116/sol1.py @@ -0,0 +1,64 @@ +""" +Project Euler Problem 116: https://projecteuler.net/problem=116 + +A row of five grey square tiles is to have a number of its tiles +replaced with coloured oblong tiles chosen +from red (length two), green (length three), or blue (length four). + +If red tiles are chosen there are exactly seven ways this can be done. + + |red,red|grey|grey|grey| |grey|red,red|grey|grey| + + |grey|grey|red,red|grey| |grey|grey|grey|red,red| + + |red,red|red,red|grey| |red,red|grey|red,red| + + |grey|red,red|red,red| + +If green tiles are chosen there are three ways. + + |green,green,green|grey|grey| |grey|green,green,green|grey| + + |grey|grey|green,green,green| + +And if blue tiles are chosen there are two ways. + + |blue,blue,blue,blue|grey| |grey|blue,blue,blue,blue| + +Assuming that colours cannot be mixed there are 7 + 3 + 2 = 12 ways +of replacing the grey tiles in a row measuring five units in length. + +How many different ways can the grey tiles in a row measuring fifty units in length +be replaced if colours cannot be mixed and at least one coloured tile must be used? + +NOTE: This is related to Problem 117 (https://projecteuler.net/problem=117). +""" + + +def solution(length: int = 50) -> int: + """ + Returns the number of different ways can the grey tiles in a row + of the given length be replaced if colours cannot be mixed + and at least one coloured tile must be used + + >>> solution(5) + 12 + """ + + different_colour_ways_number = [[0] * 3 for _ in range(length + 1)] + + for row_length in range(length + 1): + for tile_length in range(2, 5): + for tile_start in range(row_length - tile_length + 1): + different_colour_ways_number[row_length][tile_length - 2] += ( + different_colour_ways_number[row_length - tile_start - tile_length][ + tile_length - 2 + ] + + 1 + ) + + return sum(different_colour_ways_number[length]) + + +if __name__ == "__main__": + print(f"{solution() = }") From a12e6941a6c90d80d4aaee5c2c013e7da0288492 Mon Sep 17 00:00:00 2001 From: Debjit Bhowal <68442560+debjit-bw@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:47:15 +0530 Subject: [PATCH 1841/2908] Fix docstring (#6461) * fixed wrong algo name to radix sort * added wiki url * Added "source" in docstring * Update radix_sort.py Co-authored-by: Christian Clauss --- sorts/radix_sort.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index e433bc507a1e..c3ff04f3d5e5 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -1,11 +1,7 @@ """ -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 +This is a pure Python implementation of the radix sort algorithm + +Source: https://en.wikipedia.org/wiki/Radix_sort """ from __future__ import annotations From 346b0a8466732c9594eb62c4c60203d87106bf69 Mon Sep 17 00:00:00 2001 From: RAHUL S H Date: Sun, 2 Oct 2022 03:50:47 -0700 Subject: [PATCH 1842/2908] Added fetch_quotes.py (#6529) * Added fetch_quotes.py fetches quotes from zenquotes.io api * Update web_programming/fetch_quotes.py Co-authored-by: rohanr18 <114707091+rohanr18@users.noreply.github.com> Co-authored-by: rohanr18 <114707091+rohanr18@users.noreply.github.com> --- web_programming/fetch_quotes.py | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 web_programming/fetch_quotes.py diff --git a/web_programming/fetch_quotes.py b/web_programming/fetch_quotes.py new file mode 100644 index 000000000000..4a3b002e515f --- /dev/null +++ b/web_programming/fetch_quotes.py @@ -0,0 +1,34 @@ +""" +This file fetches quotes from the " ZenQuotes API ". +It does not require any API key as it uses free tier. + +For more details and premium features visit: + https://zenquotes.io/ +""" + +import pprint + +import requests + + +def quote_of_the_day() -> list: + API_ENDPOINT_URL = "https://zenquotes.io/api/today/" + return requests.get(API_ENDPOINT_URL).json() + + +def random_quotes() -> list: + API_ENDPOINT_URL = "https://zenquotes.io/api/random/" + return requests.get(API_ENDPOINT_URL).json() + + +if __name__ == "__main__": + """ + response object has all the info with the quote + To retrieve the actual quote access the response.json() object as below + response.json() is a list of json object + response.json()[0]['q'] = actual quote. + response.json()[0]['a'] = author name. + response.json()[0]['h'] = in html format. + """ + response = random_quotes() + pprint.pprint(response) From cabd8c63825fcd1b35fdd621ba443f31d0fb880d Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 2 Oct 2022 17:49:30 +0530 Subject: [PATCH 1843/2908] feat: basic issue forms (#6533) --- .github/ISSUE_TEMPLATE/bug_report.yaml | 54 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yaml | 5 ++ .github/ISSUE_TEMPLATE/feature_request.yaml | 26 ++++++++++ 3 files changed, 85 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yaml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 000000000000..6b3a5222f0eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,54 @@ +name: Bug report +description: Create a bug report to help us address errors in the repository +labels: [bug] +body: + - type: markdown + attributes: + value: | + Before requesting please search [existing issues](https://github.com/TheAlgorithms/Python/labels/bug). + Usage questions such as "How do I...?" belong on the + [Discord](https://discord.gg/c7MnfGFGa6) and will be closed. + + - type: input + attributes: + label: "Repository commit" + description: | + The commit hash for `TheAlgorithms/Python` repository. You can get this + by running the command `git rev-parse HEAD` locally. + placeholder: "a0b0f414ae134aa1772d33bb930e5a960f9979e8" + validations: + required: true + + - type: input + attributes: + label: "Python version (python --version)" + placeholder: "Python 3.10.7" + validations: + required: true + + - type: input + attributes: + label: "Dependencies version (pip freeze)" + description: | + This is the output of the command `pip freeze --all`. Note that the + actual output might be different as compared to the placeholder text. + placeholder: | + appnope==0.1.3 + asttokens==2.0.8 + backcall==0.2.0 + ... + validations: + required: true + + - type: textarea + attributes: + label: "Expected behavior" + description: "Describe the behavior you expect. May include images or videos." + validations: + required: true + + - type: textarea + attributes: + label: "Actual behavior" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 000000000000..62019bb08938 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discord community + url: https://discord.gg/c7MnfGFGa6 + about: Have any questions or need any help? Please contact us via Discord diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 000000000000..7d6e221e32bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,26 @@ +name: Feature request +description: Suggest features, propose improvements, discuss new ideas. +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Before requesting please search [existing issues](https://github.com/TheAlgorithms/Python/labels/enhancement). + Usage questions such as "How do I...?" belong on the + [Discord](https://discord.gg/c7MnfGFGa6) and will be closed. + + - type: textarea + attributes: + label: "Feature description" + description: | + This could be new algorithms, data structures or improving any existing + implementations. + validations: + required: true + + - type: checkboxes + attributes: + label: Would you like to work on this feature? + options: + - label: Yes, I want to work on this feature! + required: false From c9f1d09e1a82ec25ebd259e108b6b85046212a6e Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 2 Oct 2022 18:51:53 +0530 Subject: [PATCH 1844/2908] fix: GitHub requires `.yml` extension (#6542) * fix: GitHub requires `.yml` extension Ref: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser * fix: remove newlines from multiline string * fix: use textarea for dependencies list input --- .../ISSUE_TEMPLATE/{bug_report.yaml => bug_report.yml} | 8 ++++---- .github/ISSUE_TEMPLATE/{config.yaml => config.yml} | 0 .../{feature_request.yaml => feature_request.yml} | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename .github/ISSUE_TEMPLATE/{bug_report.yaml => bug_report.yml} (95%) rename .github/ISSUE_TEMPLATE/{config.yaml => config.yml} (100%) rename .github/ISSUE_TEMPLATE/{feature_request.yaml => feature_request.yml} (95%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yml similarity index 95% rename from .github/ISSUE_TEMPLATE/bug_report.yaml rename to .github/ISSUE_TEMPLATE/bug_report.yml index 6b3a5222f0eb..4ccdb52cad24 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -4,7 +4,7 @@ labels: [bug] body: - type: markdown attributes: - value: | + value: > Before requesting please search [existing issues](https://github.com/TheAlgorithms/Python/labels/bug). Usage questions such as "How do I...?" belong on the [Discord](https://discord.gg/c7MnfGFGa6) and will be closed. @@ -12,7 +12,7 @@ body: - type: input attributes: label: "Repository commit" - description: | + description: > The commit hash for `TheAlgorithms/Python` repository. You can get this by running the command `git rev-parse HEAD` locally. placeholder: "a0b0f414ae134aa1772d33bb930e5a960f9979e8" @@ -26,10 +26,10 @@ body: validations: required: true - - type: input + - type: textarea attributes: label: "Dependencies version (pip freeze)" - description: | + description: > This is the output of the command `pip freeze --all`. Note that the actual output might be different as compared to the placeholder text. placeholder: | diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yaml rename to .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yml similarity index 95% rename from .github/ISSUE_TEMPLATE/feature_request.yaml rename to .github/ISSUE_TEMPLATE/feature_request.yml index 7d6e221e32bd..bed3e8ab54ae 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -4,7 +4,7 @@ labels: [enhancement] body: - type: markdown attributes: - value: | + value: > Before requesting please search [existing issues](https://github.com/TheAlgorithms/Python/labels/enhancement). Usage questions such as "How do I...?" belong on the [Discord](https://discord.gg/c7MnfGFGa6) and will be closed. @@ -12,7 +12,7 @@ body: - type: textarea attributes: label: "Feature description" - description: | + description: > This could be new algorithms, data structures or improving any existing implementations. validations: From 072312bd0a4a2b48cc44e44a55c41ae408e8d9c1 Mon Sep 17 00:00:00 2001 From: Jay Gala <57001778+jaygala223@users.noreply.github.com> Date: Sun, 2 Oct 2022 20:19:49 +0530 Subject: [PATCH 1845/2908] Added code for Maximum Subarray Sum (#6536) * Added maximum subarray sum #6519 * fixes: #6519 function names changed as per naming conventions --- other/maximum_subarray.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 other/maximum_subarray.py diff --git a/other/maximum_subarray.py b/other/maximum_subarray.py new file mode 100644 index 000000000000..756e009444fe --- /dev/null +++ b/other/maximum_subarray.py @@ -0,0 +1,26 @@ +def max_subarray(nums: list[int]) -> int: + """ + Returns the subarray with maximum sum + >>> max_subarray([1,2,3,4,-2]) + 10 + >>> max_subarray([-2,1,-3,4,-1,2,1,-5,4]) + 6 + """ + + curr_max = ans = nums[0] + + for i in range(1, len(nums)): + if curr_max >= 0: + curr_max = curr_max + nums[i] + else: + curr_max = nums[i] + + ans = max(curr_max, ans) + + return ans + + +if __name__ == "__main__": + n = int(input("Enter number of elements : ").strip()) + array = list(map(int, input("\nEnter the numbers : ").strip().split()))[:n] + print(max_subarray(array)) From 50545d10c55859e3a3d792132ca6769f219bb130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerrit=20Gl=C3=A4sker?= Date: Sun, 2 Oct 2022 16:57:11 +0200 Subject: [PATCH 1846/2908] Run length encoding (#6492) * Removed unused commit * Added wikipedia url * Renamed parameter, changed decoding to use list comprehension --- compression/run_length_encoding.py | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 compression/run_length_encoding.py diff --git a/compression/run_length_encoding.py b/compression/run_length_encoding.py new file mode 100644 index 000000000000..691e19095dc6 --- /dev/null +++ b/compression/run_length_encoding.py @@ -0,0 +1,48 @@ +# https://en.wikipedia.org/wiki/Run-length_encoding + + +def run_length_encode(text: str) -> list: + """ + Performs Run Length Encoding + >>> run_length_encode("AAAABBBCCDAA") + [('A', 4), ('B', 3), ('C', 2), ('D', 1), ('A', 2)] + >>> run_length_encode("A") + [('A', 1)] + >>> run_length_encode("AA") + [('A', 2)] + >>> run_length_encode("AAADDDDDDFFFCCCAAVVVV") + [('A', 3), ('D', 6), ('F', 3), ('C', 3), ('A', 2), ('V', 4)] + """ + encoded = [] + count = 1 + + for i in range(len(text)): + if i + 1 < len(text) and text[i] == text[i + 1]: + count += 1 + else: + encoded.append((text[i], count)) + count = 1 + + return encoded + + +def run_length_decode(encoded: list) -> str: + """ + Performs Run Length Decoding + >>> run_length_decode([('A', 4), ('B', 3), ('C', 2), ('D', 1), ('A', 2)]) + 'AAAABBBCCDAA' + >>> run_length_decode([('A', 1)]) + 'A' + >>> run_length_decode([('A', 2)]) + 'AA' + >>> run_length_decode([('A', 3), ('D', 6), ('F', 3), ('C', 3), ('A', 2), ('V', 4)]) + 'AAADDDDDDFFFCCCAAVVVV' + """ + return "".join(char * length for char, length in encoded) + + +if __name__ == "__main__": + from doctest import testmod + + testmod(name="run_length_encode", verbose=True) + testmod(name="run_length_decode", verbose=True) From 8b8fba34594764cbd8d834337f03d6e03b108964 Mon Sep 17 00:00:00 2001 From: Daniel Pustotin Date: Sun, 2 Oct 2022 19:35:02 +0300 Subject: [PATCH 1847/2908] Improve code complexity for segmented sieve (#6372) --- maths/segmented_sieve.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/maths/segmented_sieve.py b/maths/segmented_sieve.py index b15ec2480678..0054b0595be5 100644 --- a/maths/segmented_sieve.py +++ b/maths/segmented_sieve.py @@ -15,15 +15,12 @@ def sieve(n): if temp[start] is True: in_prime.append(start) for i in range(start * start, end + 1, start): - if temp[i] is True: - temp[i] = False + temp[i] = False start += 1 prime += in_prime low = end + 1 - high = low + end - 1 - if high > n: - high = n + high = min(2 * end, n) while low <= n: temp = [True] * (high - low + 1) @@ -41,9 +38,7 @@ def sieve(n): prime.append(j + low) low = high + 1 - high = low + end - 1 - if high > n: - high = n + high = min(high + end, n) return prime From f42b2b8dff3fc9463d73072a2968594f4eda383b Mon Sep 17 00:00:00 2001 From: Saksham1970 <45041294+Saksham1970@users.noreply.github.com> Date: Sun, 2 Oct 2022 23:21:04 +0530 Subject: [PATCH 1848/2908] Newton raphson complex (#6545) * Newton raphson better implementation * flake8 test passed * Update arithmetic_analysis/newton_raphson_new.py Co-authored-by: Christian Clauss * added multiline suggestions Co-authored-by: Christian Clauss --- arithmetic_analysis/newton_raphson_new.py | 84 +++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 arithmetic_analysis/newton_raphson_new.py diff --git a/arithmetic_analysis/newton_raphson_new.py b/arithmetic_analysis/newton_raphson_new.py new file mode 100644 index 000000000000..19ea4ce21806 --- /dev/null +++ b/arithmetic_analysis/newton_raphson_new.py @@ -0,0 +1,84 @@ +# Implementing Newton Raphson method in Python +# Author: Saksham Gupta +# +# The Newton-Raphson method (also known as Newton's method) is a way to +# quickly find a good approximation for the root of a functreal-valued ion +# The method can also be extended to complex functions +# +# Newton's Method - https://en.wikipedia.org/wiki/Newton's_method + +from sympy import diff, lambdify, symbols +from sympy.functions import * # noqa: F401, F403 + + +def newton_raphson( + function: str, + starting_point: complex, + variable: str = "x", + precision: float = 10**-10, + multiplicity: int = 1, +) -> complex: + """Finds root from the 'starting_point' onwards by Newton-Raphson method + Refer to https://docs.sympy.org/latest/modules/functions/index.html + for usable mathematical functions + + >>> newton_raphson("sin(x)", 2) + 3.141592653589793 + >>> newton_raphson("x**4 -5", 0.4 + 5j) + (-7.52316384526264e-37+1.4953487812212207j) + >>> newton_raphson('log(y) - 1', 2, variable='y') + 2.7182818284590455 + >>> newton_raphson('exp(x) - 1', 10, precision=0.005) + 1.2186556186174883e-10 + >>> newton_raphson('cos(x)', 0) + Traceback (most recent call last): + ... + ZeroDivisionError: Could not find root + """ + + x = symbols(variable) + func = lambdify(x, function) + diff_function = lambdify(x, diff(function, x)) + + prev_guess = starting_point + + while True: + if diff_function(prev_guess) != 0: + next_guess = prev_guess - multiplicity * func(prev_guess) / diff_function( + prev_guess + ) + else: + raise ZeroDivisionError("Could not find root") from None + + # Precision is checked by comparing the difference of consecutive guesses + if abs(next_guess - prev_guess) < precision: + return next_guess + + prev_guess = next_guess + + +# 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 + # Find fourth Root of 5 + print(f"The root of x**4 - 5 = 0 is {newton_raphson('x**4 -5', 0.4 +5j)}") + + # Find value of e + print( + "The root of log(y) - 1 = 0 is ", + f"{newton_raphson('log(y) - 1', 2, variable='y')}", + ) + + # Exponential Roots + print( + "The root of exp(x) - 1 = 0 is", + f"{newton_raphson('exp(x) - 1', 10, precision=0.005)}", + ) + + # Find root of cos(x) + print(f"The root of cos(x) = 0 is {newton_raphson('cos(x)', 0)}") From 3d33b36e92c2edd1afd542b59d157ad4bccd4bf6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 2 Oct 2022 21:59:17 +0200 Subject: [PATCH 1849/2908] Fix pre-commit.ci: additional_dependencies: [types-requests] (#6559) * Fix pre-commit.ci: additional_dependencies: [types-requests==2.28.11] * updating DIRECTORY.md * Update .pre-commit-config.yaml * additional_dependencies: [types-requests] Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 1 + DIRECTORY.md | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7ff7459978e6..325063c3b8a5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,6 +49,7 @@ repos: - --ignore-missing-imports - --install-types # See mirrors-mypy README.md - --non-interactive + additional_dependencies: [types-requests] - repo: https://github.com/codespell-project/codespell rev: v2.1.0 diff --git a/DIRECTORY.md b/DIRECTORY.md index 1d9e6eff75c6..64e9d5333a2f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -9,6 +9,7 @@ * [Newton Forward Interpolation](arithmetic_analysis/newton_forward_interpolation.py) * [Newton Method](arithmetic_analysis/newton_method.py) * [Newton Raphson](arithmetic_analysis/newton_raphson.py) + * [Newton Raphson New](arithmetic_analysis/newton_raphson_new.py) * [Secant Method](arithmetic_analysis/secant_method.py) ## Audio Filters @@ -107,6 +108,7 @@ * [Lempel Ziv](compression/lempel_ziv.py) * [Lempel Ziv Decompress](compression/lempel_ziv_decompress.py) * [Peak Signal To Noise Ratio](compression/peak_signal_to_noise_ratio.py) + * [Run Length Encoding](compression/run_length_encoding.py) ## Computer Vision * [Cnn Classification](computer_vision/cnn_classification.py) @@ -621,6 +623,7 @@ * [Linear Congruential Generator](other/linear_congruential_generator.py) * [Lru Cache](other/lru_cache.py) * [Magicdiamondpattern](other/magicdiamondpattern.py) + * [Maximum Subarray](other/maximum_subarray.py) * [Nested Brackets](other/nested_brackets.py) * [Password Generator](other/password_generator.py) * [Scoring Algorithm](other/scoring_algorithm.py) @@ -1053,6 +1056,7 @@ * [Fetch Bbc News](web_programming/fetch_bbc_news.py) * [Fetch Github Info](web_programming/fetch_github_info.py) * [Fetch Jobs](web_programming/fetch_jobs.py) + * [Fetch Quotes](web_programming/fetch_quotes.py) * [Fetch Well Rx Price](web_programming/fetch_well_rx_price.py) * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](web_programming/get_imdbtop.py) From 707809b0006a76210bc3d0e4312ff5c73ff68300 Mon Sep 17 00:00:00 2001 From: AHTESHAM ZAIDI Date: Mon, 3 Oct 2022 03:25:24 +0530 Subject: [PATCH 1850/2908] Update astar.py (#6456) * Update astar.py Improved comments added punctuations. * Update astar.py * Update machine_learning/astar.py Co-authored-by: Caeden * Update astar.py Co-authored-by: Christian Clauss Co-authored-by: Caeden --- machine_learning/astar.py | 55 ++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/machine_learning/astar.py b/machine_learning/astar.py index ee3fcff0b7bf..7a60ed225a2d 100644 --- a/machine_learning/astar.py +++ b/machine_learning/astar.py @@ -1,41 +1,38 @@ """ -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 +The A* algorithm combines features of uniform-cost search and pure heuristic search to +efficiently compute optimal solutions. + +The 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. + +The A* algorithm introduces a heuristic into a regular graph-searching algorithm, +essentially planning ahead at each step so a more optimal decision is made. For this +reason, A* is known as an algorithm with brains. + +https://en.wikipedia.org/wiki/A*_search_algorithm """ import numpy as np 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 - 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 - which can be any function. for simplicity used line - distance + Class cell represents a cell in the world which have the properties: + position: represented by tuple of x and y coordinates initially set to (0,0). + parent: Contains the parent cell object visited before we arrived at this cell. + g, h, f: Parameters used when calling our heuristic function. """ 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 + Overrides equals method because otherwise cell assign will give + wrong results. """ def __eq__(self, cell): @@ -48,8 +45,8 @@ def showcell(self): class Gridworld: """ Gridworld class represents the external world here a grid M*M - matrix - world_size: create a numpy array with the given world_size default is 5 + matrix. + world_size: create a numpy array with the given world_size default is 5. """ def __init__(self, world_size=(5, 5)): @@ -90,10 +87,10 @@ def get_neigbours(self, cell): 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 + 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() @@ -137,14 +134,14 @@ def astar(world, start, goal): if __name__ == "__main__": world = Gridworld() - # stat position and Goal + # Start position and goal start = Cell() start.position = (0, 0) goal = Cell() goal.position = (4, 4) print(f"path from {start.position} to {goal.position}") s = astar(world, start, goal) - # Just for visual reasons + # Just for visual reasons. for i in s: world.w[i] = 1 print(world.w) From e9862adafce9eb682cabcf8ac502893e0272ae65 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 3 Oct 2022 03:27:14 +0200 Subject: [PATCH 1851/2908] chore: remove pre-commit GHA (#6565) [`pre-commit.ci` is working](https://results.pre-commit.ci/repo/github/63476337) so let's remove our redundant and less powerful GitHub Action. --- .github/workflows/pre-commit.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/pre-commit.yml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml deleted file mode 100644 index eb5e3d4ce1cd..000000000000 --- a/.github/workflows/pre-commit.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: pre-commit - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: | - ~/.cache/pre-commit - ~/.cache/pip - key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - uses: actions/setup-python@v4 - with: - python-version: 3.x - # - uses: psf/black@22.6.0 - - 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 From 756bb268eb22199534fc8d6478cf0e006f02b56b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 22:00:45 +0200 Subject: [PATCH 1852/2908] [pre-commit.ci] pre-commit autoupdate (#6629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 22.6.0 → 22.8.0](https://github.com/psf/black/compare/22.6.0...22.8.0) - [github.com/asottile/pyupgrade: v2.37.0 → v2.38.2](https://github.com/asottile/pyupgrade/compare/v2.37.0...v2.38.2) - https://gitlab.com/pycqa/flake8 → https://github.com/PyCQA/flake8 - [github.com/PyCQA/flake8: 3.9.2 → 5.0.4](https://github.com/PyCQA/flake8/compare/3.9.2...5.0.4) - [github.com/pre-commit/mirrors-mypy: v0.961 → v0.981](https://github.com/pre-commit/mirrors-mypy/compare/v0.961...v0.981) - [github.com/codespell-project/codespell: v2.1.0 → v2.2.1](https://github.com/codespell-project/codespell/compare/v2.1.0...v2.2.1) * Fix a long line * Update sol1.py * Update sol1.py * lambda_ * Update multi_level_feedback_queue.py * Update double_ended_queue.py * Update sequential_minimum_optimization.py * Update .pre-commit-config.yaml Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 14 +++++++------- data_structures/queue/double_ended_queue.py | 2 +- linear_algebra/src/power_iteration.py | 12 ++++++------ .../sequential_minimum_optimization.py | 2 +- project_euler/problem_045/sol1.py | 2 +- project_euler/problem_113/sol1.py | 2 +- scheduling/multi_level_feedback_queue.py | 2 +- .../download_images_from_google_query.py | 3 ++- 8 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 325063c3b8a5..a2fcf12c9bbd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: 22.6.0 + rev: 22.8.0 hooks: - id: black @@ -26,14 +26,14 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v2.37.0 + rev: v2.38.2 hooks: - id: pyupgrade args: - --py310-plus - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 hooks: - id: flake8 args: @@ -42,7 +42,7 @@ repos: - --max-line-length=88 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.961 + rev: v0.981 hooks: - id: mypy args: @@ -52,11 +52,11 @@ repos: additional_dependencies: [types-requests] - repo: https://github.com/codespell-project/codespell - rev: v2.1.0 + rev: v2.2.1 hooks: - id: codespell args: - - --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,sur,tim + - --ignore-words-list=ans,crate,damon,fo,followings,hist,iff,mater,secant,som,sur,tim,zar - --skip="./.*,./strings/dictionary.txt,./strings/words.txt,./project_euler/problem_022/p022_names.txt" exclude: | (?x)^( diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 1603e50bc7f2..f38874788df1 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -377,7 +377,7 @@ def __eq__(self, other: object) -> bool: me = self._front oth = other._front - # if the length of the deques are not the same, they are not equal + # if the length of the dequeues are not the same, they are not equal if len(self) != len(other): return False diff --git a/linear_algebra/src/power_iteration.py b/linear_algebra/src/power_iteration.py index 4c6525b6e4af..4b866331b8e3 100644 --- a/linear_algebra/src/power_iteration.py +++ b/linear_algebra/src/power_iteration.py @@ -52,7 +52,7 @@ def power_iteration( # or when we have small changes from one iteration to next. convergence = False - lamda_previous = 0 + lambda_previous = 0 iterations = 0 error = 1e12 @@ -64,21 +64,21 @@ def power_iteration( # Find rayleigh quotient # (faster than usual b/c we know vector is normalized already) vectorH = vector.conj().T if is_complex else vector.T - lamda = np.dot(vectorH, np.dot(input_matrix, vector)) + lambda_ = np.dot(vectorH, np.dot(input_matrix, vector)) # Check convergence. - error = np.abs(lamda - lamda_previous) / lamda + error = np.abs(lambda_ - lambda_previous) / lambda_ iterations += 1 if error <= error_tol or iterations >= max_iterations: convergence = True - lamda_previous = lamda + lambda_previous = lambda_ if is_complex: - lamda = np.real(lamda) + lambda_ = np.real(lambda_) - return lamda, vector + return lambda_, vector def test_power_iteration() -> None: diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index c217a370a975..cc7868d0fd8e 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -145,7 +145,7 @@ def fit(self): if self._is_unbound(i2): self._error[i2] = 0 - # Predict test samles + # Predict test samples def predict(self, test_samples, classify=True): if test_samples.shape[1] > self.samples.shape[1]: diff --git a/project_euler/problem_045/sol1.py b/project_euler/problem_045/sol1.py index cdf5c14cf362..d921b2802c2d 100644 --- a/project_euler/problem_045/sol1.py +++ b/project_euler/problem_045/sol1.py @@ -8,7 +8,7 @@ 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. +All triangle 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. """ diff --git a/project_euler/problem_113/sol1.py b/project_euler/problem_113/sol1.py index 951d9b49c104..2077c0fa62f3 100644 --- a/project_euler/problem_113/sol1.py +++ b/project_euler/problem_113/sol1.py @@ -62,7 +62,7 @@ def non_bouncy_upto(n: int) -> int: def solution(num_digits: int = 100) -> int: """ - Caclulate the number of non-bouncy numbers less than a googol. + Calculate the number of non-bouncy numbers less than a googol. >>> solution(6) 12951 >>> solution(10) diff --git a/scheduling/multi_level_feedback_queue.py b/scheduling/multi_level_feedback_queue.py index 95ca827e062d..b54cc8719039 100644 --- a/scheduling/multi_level_feedback_queue.py +++ b/scheduling/multi_level_feedback_queue.py @@ -307,6 +307,6 @@ def multi_level_feedback_queue(self) -> deque[Process]: ) # print sequence of finished processes print( - f"sequnece of finished processes:\ + f"sequence of finished processes:\ {mlfq.calculate_sequence_of_finish_queue()}" ) diff --git a/web_programming/download_images_from_google_query.py b/web_programming/download_images_from_google_query.py index b11a7f883085..9c0c21dc804e 100644 --- a/web_programming/download_images_from_google_query.py +++ b/web_programming/download_images_from_google_query.py @@ -14,7 +14,8 @@ def download_images_from_google_query(query: str = "dhaka", max_images: int = 5) -> int: - """Searches google using the provided query term and downloads the images in a folder. + """ + Searches google using the provided query term and downloads the images in a folder. Args: query : The image search term to be provided by the user. Defaults to From fa49e27d22d57db01994e94d2d5391b8d52c79ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Eertmans?= Date: Tue, 4 Oct 2022 12:25:23 +0200 Subject: [PATCH 1853/2908] fix: remove non-existing user from CODEOWNERS (#6648) Removes user @mateuszz0000 that does not exist, or seems to have been renamed to @L3str4nge --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 260b9704eda7..fdce879f80c4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -31,7 +31,7 @@ # /data_structures/ @cclauss # TODO: Uncomment this line after Hacktoberfest -/digital_image_processing/ @mateuszz0000 +# /digital_image_processing/ # /divide_and_conquer/ @@ -79,7 +79,7 @@ # /searches/ -/sorts/ @mateuszz0000 +# /sorts/ # /strings/ @cclauss # TODO: Uncomment this line after Hacktoberfest From a84fb58271b1d42da300ccad54ee8391a518a5bb Mon Sep 17 00:00:00 2001 From: Tarun Jain <66197713+lucifertrj@users.noreply.github.com> Date: Tue, 4 Oct 2022 22:10:53 +0530 Subject: [PATCH 1854/2908] Discord Server invite (#6663) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9525aa4080e..b5a07af100ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -176,7 +176,7 @@ We want your work to be readable by others; therefore, we encourage you to note - Most importantly, - __Be consistent in the use of these guidelines when submitting.__ - - __Join__ [Gitter](https://gitter.im/TheAlgorithms) __now!__ + - __Join__ us on [Discord](https://discord.com/invite/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms) __now!__ - Happy coding! Writer [@poyea](https://github.com/poyea), Jun 2019. From 46842e8c5b5fc78ced0f38206560deb2b8160a54 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <114707091+rohanr18@users.noreply.github.com> Date: Tue, 4 Oct 2022 23:35:56 +0530 Subject: [PATCH 1855/2908] Add missing type hints in `matrix` directory (#6612) * Update count_islands_in_matrix.py * Update matrix_class.py * Update matrix_operation.py * Update nth_fibonacci_using_matrix_exponentiation.py * Update searching_in_sorted_matrix.py * Update count_islands_in_matrix.py * Update matrix_class.py * Update matrix_operation.py * Update rotate_matrix.py * Update sherman_morrison.py * Update spiral_print.py * Update count_islands_in_matrix.py * formatting * formatting * Update matrix_class.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- matrix/count_islands_in_matrix.py | 7 +- matrix/matrix_class.py | 80 ++++++++++--------- matrix/matrix_operation.py | 34 ++++---- ...h_fibonacci_using_matrix_exponentiation.py | 10 +-- matrix/rotate_matrix.py | 16 ++-- matrix/searching_in_sorted_matrix.py | 4 +- matrix/sherman_morrison.py | 46 +++++------ matrix/spiral_print.py | 64 +++++++++------ 8 files changed, 142 insertions(+), 119 deletions(-) diff --git a/matrix/count_islands_in_matrix.py b/matrix/count_islands_in_matrix.py index ad9c67fb8c1b..00f9e14362b2 100644 --- a/matrix/count_islands_in_matrix.py +++ b/matrix/count_islands_in_matrix.py @@ -4,12 +4,12 @@ class matrix: # Public class to implement a graph - def __init__(self, row: int, col: int, graph: list): + def __init__(self, row: int, col: int, graph: list[list[bool]]) -> None: self.ROW = row self.COL = col self.graph = graph - def is_safe(self, i, j, visited) -> bool: + def is_safe(self, i: int, j: int, visited: list[list[bool]]) -> bool: return ( 0 <= i < self.ROW and 0 <= j < self.COL @@ -17,7 +17,8 @@ def is_safe(self, i, j, visited) -> bool: and self.graph[i][j] ) - def diffs(self, i, j, visited): # Checking all 8 elements surrounding nth element + def diffs(self, i: int, j: int, visited: list[list[bool]]) -> None: + # 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 diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 57a2fc45ffd1..305cad0a5a9c 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -1,5 +1,7 @@ # An OOP approach to representing and manipulating matrices +from __future__ import annotations + class Matrix: """ @@ -54,7 +56,9 @@ class Matrix: [6. -12. 6.] [-3. 6. -3.]] >>> print(matrix.inverse()) - None + Traceback (most recent call last): + ... + TypeError: Only matrices with a non-zero determinant have an inverse Determinant is an int, float, or Nonetype >>> matrix.determinant() @@ -101,10 +105,9 @@ class Matrix: [198. 243. 288. 304.] [306. 378. 450. 472.] [414. 513. 612. 640.]] - """ - def __init__(self, rows): + def __init__(self, rows: list[list[int]]): 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 " @@ -125,42 +128,43 @@ def __init__(self, rows): self.rows = [] # MATRIX INFORMATION - def columns(self): + def columns(self) -> list[list[int]]: return [[row[i] for row in self.rows] for i in range(len(self.rows[0]))] @property - def num_rows(self): + def num_rows(self) -> int: return len(self.rows) @property - def num_columns(self): + def num_columns(self) -> int: return len(self.rows[0]) @property - def order(self): + def order(self) -> tuple[int, int]: return (self.num_rows, self.num_columns) @property - def is_square(self): + def is_square(self) -> bool: return self.order[0] == self.order[1] - def identity(self): + def identity(self) -> Matrix: 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): + def determinant(self) -> int: if not self.is_square: - return None + return 0 if self.order == (0, 0): return 1 if self.order == (1, 1): - return self.rows[0][0] + return int(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] + return int( + (self.rows[0][0] * self.rows[1][1]) + - (self.rows[0][1] * self.rows[1][0]) ) else: return sum( @@ -168,10 +172,10 @@ def determinant(self): for column in range(self.num_columns) ) - def is_invertable(self): + def is_invertable(self) -> bool: return bool(self.determinant()) - def get_minor(self, row, column): + def get_minor(self, row: int, column: int) -> int: values = [ [ self.rows[other_row][other_column] @@ -183,12 +187,12 @@ def get_minor(self, row, column): ] return Matrix(values).determinant() - def get_cofactor(self, row, column): + def get_cofactor(self, row: int, column: int) -> int: if (row + column) % 2 == 0: return self.get_minor(row, column) return -1 * self.get_minor(row, column) - def minors(self): + def minors(self) -> Matrix: return Matrix( [ [self.get_minor(row, column) for column in range(self.num_columns)] @@ -196,7 +200,7 @@ def minors(self): ] ) - def cofactors(self): + def cofactors(self) -> Matrix: return Matrix( [ [ @@ -209,25 +213,27 @@ def cofactors(self): ] ) - def adjugate(self): + def adjugate(self) -> Matrix: 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): + def inverse(self) -> Matrix: determinant = self.determinant() - return None if not determinant else self.adjugate() * (1 / determinant) + if not determinant: + raise TypeError("Only matrices with a non-zero determinant have an inverse") + return self.adjugate() * (1 / determinant) - def __repr__(self): + def __repr__(self) -> str: return str(self.rows) - def __str__(self): + def __str__(self) -> str: if self.num_rows == 0: return "[]" if self.num_rows == 1: - return "[[" + ". ".join(self.rows[0]) + "]]" + return "[[" + ". ".join(str(self.rows[0])) + "]]" return ( "[" + "\n ".join( @@ -240,7 +246,7 @@ def __str__(self): ) # MATRIX MANIPULATION - def add_row(self, row, position=None): + def add_row(self, row: list[int], position: int | None = None) -> None: type_error = TypeError("Row must be a list containing all ints and/or floats") if not isinstance(row, list): raise type_error @@ -256,7 +262,7 @@ def add_row(self, row, position=None): else: self.rows = self.rows[0:position] + [row] + self.rows[position:] - def add_column(self, column, position=None): + def add_column(self, column: list[int], position: int | None = None) -> None: type_error = TypeError( "Column must be a list containing all ints and/or floats" ) @@ -278,18 +284,18 @@ def add_column(self, column, position=None): ] # MATRIX OPERATIONS - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, Matrix): raise TypeError("A Matrix can only be compared with another Matrix") return self.rows == other.rows - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not self == other - def __neg__(self): + def __neg__(self) -> Matrix: return self * -1 - def __add__(self, other): + def __add__(self, other: Matrix) -> Matrix: if self.order != other.order: raise ValueError("Addition requires matrices of the same order") return Matrix( @@ -299,7 +305,7 @@ def __add__(self, other): ] ) - def __sub__(self, other): + def __sub__(self, other: Matrix) -> Matrix: if self.order != other.order: raise ValueError("Subtraction requires matrices of the same order") return Matrix( @@ -309,9 +315,11 @@ def __sub__(self, other): ] ) - def __mul__(self, other): + def __mul__(self, other: Matrix | int | float) -> Matrix: if isinstance(other, (int, float)): - return Matrix([[element * other for element in row] for row in self.rows]) + return Matrix( + [[int(element * other) for element in row] for row in self.rows] + ) elif isinstance(other, Matrix): if self.num_columns != other.num_rows: raise ValueError( @@ -329,7 +337,7 @@ def __mul__(self, other): "A Matrix can only be multiplied by an int, float, or another matrix" ) - def __pow__(self, other): + def __pow__(self, other: int) -> Matrix: if not isinstance(other, int): raise TypeError("A Matrix can only be raised to the power of an int") if not self.is_square: @@ -348,7 +356,7 @@ def __pow__(self, other): return result @classmethod - def dot_product(cls, row, column): + def dot_product(cls, row: list[int], column: list[int]) -> int: return sum(row[i] * column[i] for i in range(len(row))) diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 8e5d0f583486..576094902af4 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -4,8 +4,10 @@ from __future__ import annotations +from typing import Any -def add(*matrix_s: list[list]) -> list[list]: + +def add(*matrix_s: list[list[int]]) -> list[list[int]]: """ >>> add([[1,2],[3,4]],[[2,3],[4,5]]) [[3, 5], [7, 9]] @@ -25,7 +27,7 @@ def add(*matrix_s: list[list]) -> list[list]: raise TypeError("Expected a matrix, got int/list instead") -def subtract(matrix_a: list[list], matrix_b: list[list]) -> list[list]: +def subtract(matrix_a: list[list[int]], matrix_b: list[list[int]]) -> list[list[int]]: """ >>> subtract([[1,2],[3,4]],[[2,3],[4,5]]) [[-1, -1], [-1, -1]] @@ -45,7 +47,7 @@ def subtract(matrix_a: list[list], matrix_b: list[list]) -> list[list]: raise TypeError("Expected a matrix, got int/list instead") -def scalar_multiply(matrix: list[list], n: int | float) -> list[list]: +def scalar_multiply(matrix: list[list[int]], n: int | float) -> list[list[float]]: """ >>> scalar_multiply([[1,2],[3,4]],5) [[5, 10], [15, 20]] @@ -55,7 +57,7 @@ def scalar_multiply(matrix: list[list], n: int | float) -> 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[int]], matrix_b: list[list[int]]) -> list[list[int]]: """ >>> multiply([[1,2],[3,4]],[[5,5],[7,5]]) [[19, 15], [43, 35]] @@ -77,7 +79,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[int]]: """ :param n: dimension for nxn matrix :type n: int @@ -89,7 +91,9 @@ 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] | map[list]: +def transpose( + matrix: list[list[int]], return_map: bool = True +) -> list[list[int]] | map[list[int]]: """ >>> transpose([[1,2],[3,4]]) # doctest: +ELLIPSIS list[list] | map[l raise TypeError("Expected a matrix, got int/list instead") -def minor(matrix: list[list], row: int, column: int) -> list[list]: +def minor(matrix: list[list[int]], row: int, column: int) -> list[list[int]]: """ >>> minor([[1, 2], [3, 4]], 1, 1) [[1]] @@ -117,7 +121,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]]) -> Any: """ >>> determinant([[1, 2], [3, 4]]) -2 @@ -133,7 +137,7 @@ def determinant(matrix: list[list]) -> int: ) -def inverse(matrix: list[list]) -> list[list] | None: +def inverse(matrix: list[list[int]]) -> list[list[float]] | None: """ >>> inverse([[1, 2], [3, 4]]) [[-2.0, 1.0], [1.5, -0.5]] @@ -157,27 +161,27 @@ def inverse(matrix: list[list]) -> list[list] | None: return scalar_multiply(adjugate, 1 / det) -def _check_not_integer(matrix: list[list]) -> bool: +def _check_not_integer(matrix: list[list[int]]) -> bool: return not isinstance(matrix, int) and not isinstance(matrix[0], int) -def _shape(matrix: list[list]) -> tuple[int, int]: +def _shape(matrix: list[list[int]]) -> tuple[int, int]: return len(matrix), len(matrix[0]) def _verify_matrix_sizes( - matrix_a: list[list], matrix_b: list[list] -) -> tuple[tuple, tuple]: + matrix_a: list[list[int]], matrix_b: list[list[int]] +) -> tuple[tuple[int, int], tuple[int, int]]: shape = _shape(matrix_a) + _shape(matrix_b) if shape[0] != shape[3] or shape[1] != shape[2]: raise ValueError( - "operands could not be broadcast together with shape " + 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(): +def main() -> None: 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]] diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 341a02e1a95d..7c964d884617 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -16,7 +16,7 @@ """ -def multiply(matrix_a, matrix_b): +def multiply(matrix_a: list[list[int]], matrix_b: list[list[int]]) -> list[list[int]]: matrix_c = [] n = len(matrix_a) for i in range(n): @@ -30,11 +30,11 @@ def multiply(matrix_a, matrix_b): return matrix_c -def identity(n): +def identity(n: int) -> list[list[int]]: return [[int(row == column) for column in range(n)] for row in range(n)] -def nth_fibonacci_matrix(n): +def nth_fibonacci_matrix(n: int) -> int: """ >>> nth_fibonacci_matrix(100) 354224848179261915075 @@ -54,7 +54,7 @@ def nth_fibonacci_matrix(n): return res_matrix[0][0] -def nth_fibonacci_bruteforce(n): +def nth_fibonacci_bruteforce(n: int) -> int: """ >>> nth_fibonacci_bruteforce(100) 354224848179261915075 @@ -70,7 +70,7 @@ def nth_fibonacci_bruteforce(n): return fib1 -def main(): +def main() -> None: 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( diff --git a/matrix/rotate_matrix.py b/matrix/rotate_matrix.py index f638597ae35d..c16cdb9a81bb 100644 --- a/matrix/rotate_matrix.py +++ b/matrix/rotate_matrix.py @@ -8,7 +8,7 @@ from __future__ import annotations -def make_matrix(row_size: int = 4) -> list[list]: +def make_matrix(row_size: int = 4) -> list[list[int]]: """ >>> make_matrix() [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] @@ -25,7 +25,7 @@ def make_matrix(row_size: int = 4) -> list[list]: return [[1 + x + y * row_size for x in range(row_size)] for y in range(row_size)] -def rotate_90(matrix: list[list]) -> list[list]: +def rotate_90(matrix: list[list[int]]) -> list[list[int]]: """ >>> rotate_90(make_matrix()) [[4, 8, 12, 16], [3, 7, 11, 15], [2, 6, 10, 14], [1, 5, 9, 13]] @@ -37,7 +37,7 @@ def rotate_90(matrix: list[list]) -> list[list]: # OR.. transpose(reverse_column(matrix)) -def rotate_180(matrix: list[list]) -> list[list]: +def rotate_180(matrix: list[list[int]]) -> list[list[int]]: """ >>> rotate_180(make_matrix()) [[16, 15, 14, 13], [12, 11, 10, 9], [8, 7, 6, 5], [4, 3, 2, 1]] @@ -49,7 +49,7 @@ def rotate_180(matrix: list[list]) -> list[list]: # OR.. reverse_column(reverse_row(matrix)) -def rotate_270(matrix: list[list]) -> list[list]: +def rotate_270(matrix: list[list[int]]) -> list[list[int]]: """ >>> rotate_270(make_matrix()) [[13, 9, 5, 1], [14, 10, 6, 2], [15, 11, 7, 3], [16, 12, 8, 4]] @@ -61,22 +61,22 @@ def rotate_270(matrix: list[list]) -> list[list]: # OR.. transpose(reverse_row(matrix)) -def transpose(matrix: list[list]) -> list[list]: +def transpose(matrix: list[list[int]]) -> list[list[int]]: matrix[:] = [list(x) for x in zip(*matrix)] return matrix -def reverse_row(matrix: list[list]) -> list[list]: +def reverse_row(matrix: list[list[int]]) -> list[list[int]]: matrix[:] = matrix[::-1] return matrix -def reverse_column(matrix: list[list]) -> list[list]: +def reverse_column(matrix: list[list[int]]) -> list[list[int]]: matrix[:] = [x[::-1] for x in matrix] return matrix -def print_matrix(matrix: list[list]) -> None: +def print_matrix(matrix: list[list[int]]) -> None: for i in matrix: print(*i) diff --git a/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py index ae81361499e5..ddca3b1ce781 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: list[list], m: int, n: int, key: int | float + mat: list[list[int]], m: int, n: int, key: int | float ) -> None: """ >>> search_in_a_sorted_matrix( @@ -30,7 +30,7 @@ def search_in_a_sorted_matrix( print(f"Key {key} not found") -def main(): +def main() -> None: mat = [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]] x = int(input("Enter the element to be searched:")) print(mat) diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 63783c8b40fc..a0c93f11574e 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -1,14 +1,18 @@ +from __future__ import annotations + +from typing import Any + + class Matrix: """ Matrix structure. """ - def __init__(self, row: int, column: int, default_value: float = 0): + def __init__(self, row: int, column: int, default_value: float = 0) -> None: """ Initialize matrix with given size and default value. - Example: >>> a = Matrix(2, 3, 1) >>> a @@ -20,7 +24,7 @@ def __init__(self, row: int, column: int, default_value: float = 0): self.row, self.column = row, column self.array = [[default_value for c in range(column)] for r in range(row)] - def __str__(self): + def __str__(self) -> str: """ Return string representation of this matrix. @@ -37,7 +41,7 @@ def __str__(self): string_format_identifier = "%%%ds" % (max_element_length,) # Make string and return - def single_line(row_vector): + def single_line(row_vector: list[float]) -> str: nonlocal string_format_identifier line = "[" line += ", ".join(string_format_identifier % (obj,) for obj in row_vector) @@ -47,14 +51,13 @@ def single_line(row_vector): s += "\n".join(single_line(row_vector) for row_vector in self.array) return s - def __repr__(self): + def __repr__(self) -> str: return str(self) - def validateIndices(self, loc: tuple): + def validateIndices(self, loc: tuple[int, int]) -> bool: """ Check if given indices are valid to pick element from matrix. - Example: >>> a = Matrix(2, 6, 0) >>> a.validateIndices((2, 7)) @@ -69,11 +72,10 @@ def validateIndices(self, loc: tuple): else: return True - def __getitem__(self, loc: tuple): + def __getitem__(self, loc: tuple[int, int]) -> Any: """ Return array[row][column] where loc = (row, column). - Example: >>> a = Matrix(3, 2, 7) >>> a[1, 0] @@ -82,11 +84,10 @@ def __getitem__(self, loc: tuple): assert self.validateIndices(loc) return self.array[loc[0]][loc[1]] - def __setitem__(self, loc: tuple, value: float): + def __setitem__(self, loc: tuple[int, int], value: float) -> None: """ Set array[row][column] = value where loc = (row, column). - Example: >>> a = Matrix(2, 3, 1) >>> a[1, 2] = 51 @@ -98,11 +99,10 @@ def __setitem__(self, loc: tuple, value: float): assert self.validateIndices(loc) self.array[loc[0]][loc[1]] = value - def __add__(self, another): + def __add__(self, another: Matrix) -> Matrix: """ Return self + another. - Example: >>> a = Matrix(2, 1, -4) >>> b = Matrix(2, 1, 3) @@ -123,11 +123,10 @@ def __add__(self, another): result[r, c] = self[r, c] + another[r, c] return result - def __neg__(self): + def __neg__(self) -> Matrix: """ Return -self. - Example: >>> a = Matrix(2, 2, 3) >>> a[0, 1] = a[1, 0] = -2 @@ -143,14 +142,13 @@ def __neg__(self): result[r, c] = -self[r, c] return result - def __sub__(self, another): + def __sub__(self, another: Matrix) -> Matrix: return self + (-another) - def __mul__(self, another): + def __mul__(self, another: int | float | Matrix) -> Matrix: """ Return self * another. - Example: >>> a = Matrix(2, 3, 1) >>> a[0,2] = a[1,2] = 3 @@ -177,11 +175,10 @@ def __mul__(self, another): else: raise TypeError(f"Unsupported type given for another ({type(another)})") - def transpose(self): + def transpose(self) -> Matrix: """ Return self^T. - Example: >>> a = Matrix(2, 3) >>> for r in range(2): @@ -201,7 +198,7 @@ def transpose(self): result[c, r] = self[r, c] return result - def ShermanMorrison(self, u, v): + def ShermanMorrison(self, u: Matrix, v: Matrix) -> Any: """ Apply Sherman-Morrison formula in O(n^2). @@ -211,7 +208,6 @@ def ShermanMorrison(self, u, v): 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 @@ -243,7 +239,7 @@ def ShermanMorrison(self, u, v): # Testing if __name__ == "__main__": - def test1(): + def test1() -> None: # a^(-1) ainv = Matrix(3, 3, 0) for i in range(3): @@ -256,11 +252,11 @@ def test1(): v[0, 0], v[1, 0], v[2, 0] = 4, -2, 5 print(f"u is {u}") print(f"v is {v}") - print(f"uv^T is {u * v.transpose()}") + print("uv^T is %s" % (u * v.transpose())) # Sherman Morrison print(f"(a + uv^T)^(-1) is {ainv.ShermanMorrison(u, v)}") - def test2(): + def test2() -> None: import doctest doctest.testmod() diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 6f699c1ab662..2441f05d15ef 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -1,19 +1,17 @@ """ This program print the matrix 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) number of column of all rows should be equal """ -from collections.abc import Iterable - -def check_matrix(matrix): +def check_matrix(matrix: list[list[int]]) -> bool: # must be - if matrix and isinstance(matrix, Iterable): - if isinstance(matrix[0], Iterable): + matrix = list(list(row) for row in matrix) + if matrix and isinstance(matrix, list): + if isinstance(matrix[0], list): prev_len = 0 for row in matrix: if prev_len == 0: @@ -29,32 +27,48 @@ def check_matrix(matrix): return result -def spiralPrint(a): +def spiral_print_clockwise(a: list[list[int]]) -> None: + """ + >>> spiral_print_clockwise([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) + 1 + 2 + 3 + 4 + 8 + 12 + 11 + 10 + 9 + 5 + 6 + 7 + """ if check_matrix(a) and len(a) > 0: - matRow = len(a) - if isinstance(a[0], Iterable): - matCol = len(a[0]) + a = list(list(row) for row in a) + mat_row = len(a) + if isinstance(a[0], list): + mat_col = len(a[0]) else: for dat in a: - print(dat), + print(dat) return # horizotal printing increasing - for i in range(0, matCol): - print(a[0][i]), + for i in range(0, mat_col): + print(a[0][i]) # vertical printing down - for i in range(1, matRow): - print(a[i][matCol - 1]), + for i in range(1, mat_row): + print(a[i][mat_col - 1]) # horizotal printing decreasing - if matRow > 1: - for i in range(matCol - 2, -1, -1): - print(a[matRow - 1][i]), + if mat_row > 1: + for i in range(mat_col - 2, -1, -1): + print(a[mat_row - 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) + for i in range(mat_row - 2, 0, -1): + print(a[i][0]) + remain_mat = [row[1 : mat_col - 1] for row in a[1 : mat_row - 1]] + if len(remain_mat) > 0: + spiral_print_clockwise(remain_mat) else: return else: @@ -64,5 +78,5 @@ def spiralPrint(a): # driver code if __name__ == "__main__": - a = ([1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]) - spiralPrint(a) + a = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] + spiral_print_clockwise(a) From 087a3a8d537ceb179d0a47eda66f47d103c4b1b9 Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <114707091+rohanr18@users.noreply.github.com> Date: Wed, 5 Oct 2022 01:22:49 +0530 Subject: [PATCH 1856/2908] lorenz -> lorentz (#6670) --- ...four_vector.py => lorentz_transformation_four_vector.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename physics/{lorenz_transformation_four_vector.py => lorentz_transformation_four_vector.py} (96%) diff --git a/physics/lorenz_transformation_four_vector.py b/physics/lorentz_transformation_four_vector.py similarity index 96% rename from physics/lorenz_transformation_four_vector.py rename to physics/lorentz_transformation_four_vector.py index 6c0d5f9d1997..bda852c25520 100644 --- a/physics/lorenz_transformation_four_vector.py +++ b/physics/lorentz_transformation_four_vector.py @@ -1,13 +1,13 @@ """ -Lorenz transformation describes the transition from a reference frame P +Lorentz transformation describes the transition from a reference frame P to another reference frame P', each of which is moving in a direction with -respect to the other. The Lorenz transformation implemented in this code +respect to the other. The Lorentz transformation implemented in this code is the relativistic version using a four vector described by Minkowsky Space: x0 = ct, x1 = x, x2 = y, and x3 = z NOTE: Please note that x0 is c (speed of light) times t (time). -So, the Lorenz transformation using a four vector is defined as: +So, the Lorentz transformation using a four vector is defined as: |ct'| | γ -γβ 0 0| |ct| |x' | = |-γβ γ 0 0| *|x | From 8cce0d463a0f65c22769fe4f0750acbeed2e0d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Eertmans?= Date: Wed, 5 Oct 2022 12:32:07 +0200 Subject: [PATCH 1857/2908] refactor: pivot is randomly chosen (#6643) As described in #6095, this reduces the chances to observe a O(n^2) complexity. Here, `collection.pop(pivot_index)` is avoided for performance reasons. Fixes: #6095 --- 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 b099c78861ba..70cd19d7afe0 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -9,6 +9,8 @@ """ from __future__ import annotations +from random import randrange + def quick_sort(collection: list) -> list: """A pure Python implementation of quick sort algorithm @@ -26,11 +28,17 @@ def quick_sort(collection: list) -> list: """ if len(collection) < 2: return collection - pivot = collection.pop() # Use the last element as the first pivot + pivot_index = randrange(len(collection)) # Use random element as pivot + pivot = collection[pivot_index] greater: list[int] = [] # All elements greater than pivot lesser: list[int] = [] # All elements less than or equal to pivot - for element in collection: + + for element in collection[:pivot_index]: (greater if element > pivot else lesser).append(element) + + for element in collection[pivot_index + 1 :]: + (greater if element > pivot else lesser).append(element) + return quick_sort(lesser) + [pivot] + quick_sort(greater) From 660d2bb66c8ca03e2225090b5c638ffb0fd14a60 Mon Sep 17 00:00:00 2001 From: Paul <56065602+ZeroDayOwl@users.noreply.github.com> Date: Thu, 6 Oct 2022 23:19:34 +0600 Subject: [PATCH 1858/2908] Add algorithm for Newton's Law of Gravitation (#6626) * Add algorithm for Newton's Law of Gravitation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update physics/newtons_law_of_gravitation.py Co-authored-by: Christian Clauss * One and only one argument must be 0 * Update newtons_law_of_gravitation.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- physics/newtons_law_of_gravitation.py | 100 ++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 physics/newtons_law_of_gravitation.py diff --git a/physics/newtons_law_of_gravitation.py b/physics/newtons_law_of_gravitation.py new file mode 100644 index 000000000000..0bb27bb2415d --- /dev/null +++ b/physics/newtons_law_of_gravitation.py @@ -0,0 +1,100 @@ +""" +Title : Finding the value of either Gravitational Force, one of the masses or distance +provided that the other three parameters are given. + +Description : Newton's Law of Universal Gravitation explains the presence of force of +attraction between bodies having a definite mass situated at a distance. It is usually +stated as that, every particle attracts every other particle in the universe with a +force that is directly proportional to the product of their masses and inversely +proportional to the square of the distance between their centers. The publication of the +theory has become known as the "first great unification", as it marked the unification +of the previously described phenomena of gravity on Earth with known astronomical +behaviors. + +The equation for the universal gravitation is as follows: +F = (G * mass_1 * mass_2) / (distance)^2 + +Source : +- https://en.wikipedia.org/wiki/Newton%27s_law_of_universal_gravitation +- Newton (1687) "Philosophiæ Naturalis Principia Mathematica" +""" + +from __future__ import annotations + +# Define the Gravitational Constant G and the function +GRAVITATIONAL_CONSTANT = 6.6743e-11 # unit of G : m^3 * kg^-1 * s^-2 + + +def gravitational_law( + force: float, mass_1: float, mass_2: float, distance: float +) -> dict[str, float]: + + """ + Input Parameters + ---------------- + force : magnitude in Newtons + + mass_1 : mass in Kilograms + + mass_2 : mass in Kilograms + + distance : distance in Meters + + Returns + ------- + result : dict name, value pair of the parameter having Zero as it's value + + Returns the value of one of the parameters specified as 0, provided the values of + other parameters are given. + >>> gravitational_law(force=0, mass_1=5, mass_2=10, distance=20) + {'force': 8.342875e-12} + + >>> gravitational_law(force=7367.382, mass_1=0, mass_2=74, distance=3048) + {'mass_1': 1.385816317292268e+19} + + >>> gravitational_law(force=36337.283, mass_1=0, mass_2=0, distance=35584) + Traceback (most recent call last): + ... + ValueError: One and only one argument must be 0 + + >>> gravitational_law(force=36337.283, mass_1=-674, mass_2=0, distance=35584) + Traceback (most recent call last): + ... + ValueError: Mass can not be negative + + >>> gravitational_law(force=-847938e12, mass_1=674, mass_2=0, distance=9374) + Traceback (most recent call last): + ... + ValueError: Gravitational force can not be negative + """ + + product_of_mass = mass_1 * mass_2 + + if (force, mass_1, mass_2, distance).count(0) != 1: + raise ValueError("One and only one argument must be 0") + if force < 0: + raise ValueError("Gravitational force can not be negative") + if distance < 0: + raise ValueError("Distance can not be negative") + if mass_1 < 0 or mass_2 < 0: + raise ValueError("Mass can not be negative") + if force == 0: + force = GRAVITATIONAL_CONSTANT * product_of_mass / (distance**2) + return {"force": force} + elif mass_1 == 0: + mass_1 = (force) * (distance**2) / (GRAVITATIONAL_CONSTANT * mass_2) + return {"mass_1": mass_1} + elif mass_2 == 0: + mass_2 = (force) * (distance**2) / (GRAVITATIONAL_CONSTANT * mass_1) + return {"mass_2": mass_2} + elif distance == 0: + distance = (GRAVITATIONAL_CONSTANT * product_of_mass / (force)) ** 0.5 + return {"distance": distance} + raise ValueError("One and only one argument must be 0") + + +# Run doctest +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5894554d41116af83152b1ea59fbf78303d87966 Mon Sep 17 00:00:00 2001 From: Jordan Rinder Date: Sat, 8 Oct 2022 18:28:17 -0400 Subject: [PATCH 1859/2908] Add Catalan number to maths (#6845) * Add Catalan number to maths * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 4 +++- maths/catalan_number.py | 51 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 maths/catalan_number.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 64e9d5333a2f..668da4761f74 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -475,6 +475,7 @@ * [Binomial Coefficient](maths/binomial_coefficient.py) * [Binomial Distribution](maths/binomial_distribution.py) * [Bisection](maths/bisection.py) + * [Catalan Number](maths/catalan_number.py) * [Ceil](maths/ceil.py) * [Check Polygon](maths/check_polygon.py) * [Chudnovsky Algorithm](maths/chudnovsky_algorithm.py) @@ -632,8 +633,9 @@ ## Physics * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) - * [Lorenz Transformation Four Vector](physics/lorenz_transformation_four_vector.py) + * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) * [N Body Simulation](physics/n_body_simulation.py) + * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) ## Project Euler diff --git a/maths/catalan_number.py b/maths/catalan_number.py new file mode 100644 index 000000000000..4a1280a45bf2 --- /dev/null +++ b/maths/catalan_number.py @@ -0,0 +1,51 @@ +""" + +Calculate the nth Catalan number + +Source: + https://en.wikipedia.org/wiki/Catalan_number + +""" + + +def catalan(number: int) -> int: + """ + :param number: nth catalan number to calculate + :return: the nth catalan number + Note: A catalan number is only defined for positive integers + + >>> catalan(5) + 14 + >>> catalan(0) + Traceback (most recent call last): + ... + ValueError: Input value of [number=0] must be > 0 + >>> catalan(-1) + Traceback (most recent call last): + ... + ValueError: Input value of [number=-1] must be > 0 + >>> catalan(5.0) + Traceback (most recent call last): + ... + TypeError: Input value of [number=5.0] must be an integer + """ + + if not isinstance(number, int): + raise TypeError(f"Input value of [number={number}] must be an integer") + + if number < 1: + raise ValueError(f"Input value of [number={number}] must be > 0") + + current_number = 1 + + for i in range(1, number): + current_number *= 4 * i - 2 + current_number //= i + 1 + + return current_number + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 51dba4d743cd2c8d407eea3e9cd4e7b2f69ee34d Mon Sep 17 00:00:00 2001 From: Lakshay Roopchandani <75477853+lakshayroop5@users.noreply.github.com> Date: Sun, 9 Oct 2022 18:23:44 +0530 Subject: [PATCH 1860/2908] Job sequencing with deadlines (#6854) * completed optimised code for job sequencing with deadline problem * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * completed optimised code for job sequencing with deadline problem * completed optimized code for job sequencing with deadline problem * completed optimised code for job sequencing with deadline problem * completed optimised code for job sequencing with deadline problem * completed optimised code for job sequencing with deadline problem * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * completed optimized code for the issue "Job Scheduling with deadlines" * completed optimized code for the issue "Job Scheduling with deadlines" * completed optimized code for the issue "Job Scheduling with deadlines" * Update greedy_methods/job_sequencing_with_deadline.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated reviews * Updated reviews * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename greedy_methods/job_sequencing_with_deadline.py to scheduling/job_sequencing_with_deadline.py Co-authored-by: lakshayroop5 <87693528+lavenroop5@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- scheduling/job_sequencing_with_deadline.py | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 scheduling/job_sequencing_with_deadline.py diff --git a/scheduling/job_sequencing_with_deadline.py b/scheduling/job_sequencing_with_deadline.py new file mode 100644 index 000000000000..7b23c0b3575f --- /dev/null +++ b/scheduling/job_sequencing_with_deadline.py @@ -0,0 +1,48 @@ +def job_sequencing_with_deadlines(num_jobs: int, jobs: list) -> list: + """ + Function to find the maximum profit by doing jobs in a given time frame + + Args: + num_jobs [int]: Number of jobs + jobs [list]: A list of tuples of (job_id, deadline, profit) + + Returns: + max_profit [int]: Maximum profit that can be earned by doing jobs + in a given time frame + + Examples: + >>> job_sequencing_with_deadlines(4, + ... [(1, 4, 20), (2, 1, 10), (3, 1, 40), (4, 1, 30)]) + [2, 60] + >>> job_sequencing_with_deadlines(5, + ... [(1, 2, 100), (2, 1, 19), (3, 2, 27), (4, 1, 25), (5, 1, 15)]) + [2, 127] + """ + + # Sort the jobs in descending order of profit + jobs = sorted(jobs, key=lambda value: value[2], reverse=True) + + # Create a list of size equal to the maximum deadline + # and initialize it with -1 + max_deadline = max(jobs, key=lambda value: value[1])[1] + time_slots = [-1] * max_deadline + + # Finding the maximum profit and the count of jobs + count = 0 + max_profit = 0 + for job in jobs: + # Find a free time slot for this job + # (Note that we start from the last possible slot) + for i in range(job[1] - 1, -1, -1): + if time_slots[i] == -1: + time_slots[i] = job[0] + count += 1 + max_profit += job[2] + break + return [count, max_profit] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0a3433eaed6c8369f6c45b3abf70ee33a3a74910 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 22:04:33 +0200 Subject: [PATCH 1861/2908] [pre-commit.ci] pre-commit autoupdate (#6940) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0) - [github.com/asottile/pyupgrade: v2.38.2 → v3.0.0](https://github.com/asottile/pyupgrade/compare/v2.38.2...v3.0.0) - [github.com/pre-commit/mirrors-mypy: v0.981 → v0.982](https://github.com/pre-commit/mirrors-mypy/compare/v0.981...v0.982) * updating DIRECTORY.md Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a2fcf12c9bbd..0abe647b017a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 22.10.0 hooks: - id: black @@ -26,7 +26,7 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v2.38.2 + rev: v3.0.0 hooks: - id: pyupgrade args: @@ -42,7 +42,7 @@ repos: - --max-line-length=88 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.981 + rev: v0.982 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 668da4761f74..9ef72c403f32 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -927,6 +927,7 @@ ## Scheduling * [First Come First Served](scheduling/first_come_first_served.py) * [Highest Response Ratio Next](scheduling/highest_response_ratio_next.py) + * [Job Sequencing With Deadline](scheduling/job_sequencing_with_deadline.py) * [Multi Level Feedback Queue](scheduling/multi_level_feedback_queue.py) * [Non Preemptive Shortest Job First](scheduling/non_preemptive_shortest_job_first.py) * [Round Robin](scheduling/round_robin.py) From f0d1a42deb146bebcdf7b1b2ec788c815ede452a Mon Sep 17 00:00:00 2001 From: Shubhajit Roy <81477286+shubhajitroy123@users.noreply.github.com> Date: Wed, 12 Oct 2022 12:52:23 +0530 Subject: [PATCH 1862/2908] Python program for Carmicheal Number (#6864) * Add files via upload Python program to determine whether a number is Carmichael Number or not. * Rename Carmichael Number.py to carmichael number.py * Rename carmichael number.py to carmichael_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update carmichael_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Create carmichael_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/carmichael_number.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/carmichael_number.py | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 maths/carmichael_number.py diff --git a/maths/carmichael_number.py b/maths/carmichael_number.py new file mode 100644 index 000000000000..09a4fedfb763 --- /dev/null +++ b/maths/carmichael_number.py @@ -0,0 +1,47 @@ +""" +== Carmichael Numbers == +A number n is said to be a Carmichael number if it +satisfies the following modular arithmetic condition: + + power(b, n-1) MOD n = 1, + for all b ranging from 1 to n such that b and + n are relatively prime, i.e, gcd(b, n) = 1 + +Examples of Carmichael Numbers: 561, 1105, ... +https://en.wikipedia.org/wiki/Carmichael_number +""" + + +def gcd(a: int, b: int) -> int: + if a < b: + return gcd(b, a) + if a % b == 0: + return b + return gcd(b, a % b) + + +def power(x: int, y: int, mod: int) -> int: + if y == 0: + return 1 + temp = power(x, y // 2, mod) % mod + temp = (temp * temp) % mod + if y % 2 == 1: + temp = (temp * x) % mod + return temp + + +def isCarmichaelNumber(n: int) -> bool: + b = 2 + while b < n: + if gcd(b, n) == 1 and power(b, n - 1, n) != 1: + return False + b += 1 + return True + + +if __name__ == "__main__": + number = int(input("Enter number: ").strip()) + if isCarmichaelNumber(number): + print(f"{number} is a Carmichael Number.") + else: + print(f"{number} is not a Carmichael Number.") From a04a6365dee01bebf382809a5638b6fd0d0a51e6 Mon Sep 17 00:00:00 2001 From: Martmists Date: Wed, 12 Oct 2022 15:19:00 +0200 Subject: [PATCH 1863/2908] Add Equal Loudness Filter (#7019) * Add Equal Loudness Filter Signed-off-by: Martmists * NoneType return on __init__ Signed-off-by: Martmists * Add data to JSON as requested by @CenTdemeern1 in a not very polite manner Signed-off-by: Martmists * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * 'modernize' Signed-off-by: Martmists * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update audio_filters/equal_loudness_filter.py Co-authored-by: Christian Clauss * Update equal_loudness_filter.py * Update equal_loudness_filter.py * Finally!! * Arrgghh Signed-off-by: Martmists Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- audio_filters/equal_loudness_filter.py | 61 +++++++++++++++++++++ audio_filters/loudness_curve.json | 76 ++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 138 insertions(+) create mode 100644 audio_filters/equal_loudness_filter.py create mode 100644 audio_filters/loudness_curve.json diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py new file mode 100644 index 000000000000..b9a3c50e1c33 --- /dev/null +++ b/audio_filters/equal_loudness_filter.py @@ -0,0 +1,61 @@ +from json import loads +from pathlib import Path + +import numpy as np +from yulewalker import yulewalk + +from audio_filters.butterworth_filter import make_highpass +from audio_filters.iir_filter import IIRFilter + +data = loads((Path(__file__).resolve().parent / "loudness_curve.json").read_text()) + + +class EqualLoudnessFilter: + r""" + An equal-loudness filter which compensates for the human ear's non-linear response + to sound. + This filter corrects this by cascading a yulewalk filter and a butterworth filter. + + Designed for use with samplerate of 44.1kHz and above. If you're using a lower + samplerate, use with caution. + + Code based on matlab implementation at https://bit.ly/3eqh2HU + (url shortened for flake8) + + Target curve: https://i.imgur.com/3g2VfaM.png + Yulewalk response: https://i.imgur.com/J9LnJ4C.png + Butterworth and overall response: https://i.imgur.com/3g2VfaM.png + + Images and original matlab implementation by David Robinson, 2001 + """ + + def __init__(self, samplerate: int = 44100) -> None: + self.yulewalk_filter = IIRFilter(10) + self.butterworth_filter = make_highpass(150, samplerate) + + # pad the data to nyquist + curve_freqs = np.array(data["frequencies"] + [max(20000.0, samplerate / 2)]) + curve_gains = np.array(data["gains"] + [140]) + + # Convert to angular frequency + freqs_normalized = curve_freqs / samplerate * 2 + # Invert the curve and normalize to 0dB + gains_normalized = np.power(10, (np.min(curve_gains) - curve_gains) / 20) + + # Scipy's `yulewalk` function is a stub, so we're using the + # `yulewalker` library instead. + # This function computes the coefficients using a least-squares + # fit to the specified curve. + ya, yb = yulewalk(10, freqs_normalized, gains_normalized) + self.yulewalk_filter.set_coefficients(ya, yb) + + def process(self, sample: float) -> float: + """ + Process a single sample through both filters + + >>> filt = EqualLoudnessFilter() + >>> filt.process(0.0) + 0.0 + """ + tmp = self.yulewalk_filter.process(sample) + return self.butterworth_filter.process(tmp) diff --git a/audio_filters/loudness_curve.json b/audio_filters/loudness_curve.json new file mode 100644 index 000000000000..fc066a0810fc --- /dev/null +++ b/audio_filters/loudness_curve.json @@ -0,0 +1,76 @@ +{ + "_comment": "The following is a representative average of the Equal Loudness Contours as measured by Robinson and Dadson, 1956", + "_doi": "10.1088/0508-3443/7/5/302", + "frequencies": [ + 0, + 20, + 30, + 40, + 50, + 60, + 70, + 80, + 90, + 100, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000, + 1500, + 2000, + 2500, + 3000, + 3700, + 4000, + 5000, + 6000, + 7000, + 8000, + 9000, + 10000, + 12000, + 15000, + 20000 + ], + "gains": [ + 120, + 113, + 103, + 97, + 93, + 91, + 89, + 87, + 86, + 85, + 78, + 76, + 76, + 76, + 76, + 77, + 78, + 79.5, + 80, + 79, + 77, + 74, + 71.5, + 70, + 70.5, + 74, + 79, + 84, + 86, + 86, + 85, + 95, + 110, + 125 + ] +} diff --git a/requirements.txt b/requirements.txt index 294494acf41a..0fbc1cc4b45c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ tensorflow texttable tweepy xgboost +yulewalker From d15bf7d492bc778682f80392bfd559074c4adbec Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Wed, 12 Oct 2022 22:05:31 +0530 Subject: [PATCH 1864/2908] Add typing to data_structures/heap/heap_generic.py (#7044) * Update heap_generic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update heap_generic.py * Update heap_generic.py * Update heap_generic.py * Update heap_generic.py * Update heap_generic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/heap/heap_generic.py | 35 +++++++++++++++------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/data_structures/heap/heap_generic.py b/data_structures/heap/heap_generic.py index 553cb94518c4..e7831cd45b43 100644 --- a/data_structures/heap/heap_generic.py +++ b/data_structures/heap/heap_generic.py @@ -1,35 +1,38 @@ +from collections.abc import Callable + + class Heap: """ A generic Heap class, can be used as min or max by passing the key function accordingly. """ - def __init__(self, key=None): + def __init__(self, key: Callable | None = None) -> None: # Stores actual heap items. - self.arr = list() + self.arr: list = list() # Stores indexes of each item for supporting updates and deletion. - self.pos_map = {} + self.pos_map: dict = {} # 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): + def _parent(self, i: int) -> int | None: """Returns parent index of given index if exists else None""" return int((i - 1) / 2) if i > 0 else None - def _left(self, i): + def _left(self, i: int) -> int | None: """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): + def _right(self, i: int) -> int | None: """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): + def _swap(self, i: int, j: int) -> None: """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]] = ( @@ -39,11 +42,11 @@ def _swap(self, i, j): # Then swap the items in the list. self.arr[i], self.arr[j] = self.arr[j], self.arr[i] - def _cmp(self, i, j): + def _cmp(self, i: int, j: int) -> bool: """Compares the two items using default comparison""" return self.arr[i][1] < self.arr[j][1] - def _get_valid_parent(self, i): + def _get_valid_parent(self, i: int) -> int: """ Returns index of valid parent as per desired ordering among given index and both it's children @@ -59,21 +62,21 @@ def _get_valid_parent(self, i): return valid_parent - def _heapify_up(self, index): + def _heapify_up(self, index: int) -> None: """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): + def _heapify_down(self, index: int) -> None: """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): + def update_item(self, item: int, item_value: int) -> None: """Updates given item value in heap if present""" if item not in self.pos_map: return @@ -84,7 +87,7 @@ def update_item(self, item, item_value): self._heapify_up(index) self._heapify_down(index) - def delete_item(self, item): + def delete_item(self, item: int) -> None: """Deletes given item from heap if present""" if item not in self.pos_map: return @@ -99,7 +102,7 @@ def delete_item(self, item): self._heapify_up(index) self._heapify_down(index) - def insert_item(self, item, item_value): + def insert_item(self, item: int, item_value: int) -> None: """Inserts given item with given value in heap""" arr_len = len(self.arr) if arr_len == self.size: @@ -110,11 +113,11 @@ def insert_item(self, item, item_value): self.size += 1 self._heapify_up(self.size - 1) - def get_top(self): + def get_top(self) -> tuple | None: """Returns top item tuple (Calculated value, item) from heap if present""" return self.arr[0] if self.size else None - def extract_top(self): + def extract_top(self) -> tuple | None: """ Return top item tuple (Calculated value, item) from heap and removes it as well if present From aeb933bff55734f33268848fb1fcb6a0395297cb Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Wed, 12 Oct 2022 22:07:00 +0530 Subject: [PATCH 1865/2908] Add typing to data_structures/hashing/hash_table.py (#7040) * Update hash_table.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update hash_table.py * Update hash_table.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/hashing/hash_table.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index f4422de53821..1cd71cc4baf3 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -7,13 +7,18 @@ class HashTable: Basic Hash Table example with open addressing and linear probing """ - def __init__(self, size_table, charge_factor=None, lim_charge=None): + def __init__( + self, + size_table: int, + charge_factor: int | None = None, + lim_charge: float | None = None, + ) -> None: self.size_table = size_table self.values = [None] * self.size_table self.lim_charge = 0.75 if lim_charge is None else lim_charge self.charge_factor = 1 if charge_factor is None else charge_factor - self.__aux_list = [] - self._keys = {} + self.__aux_list: list = [] + self._keys: dict = {} def keys(self): return self._keys From e272b9d6a494036aaa7f71c53d01017a34117bc9 Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Wed, 12 Oct 2022 22:14:08 +0530 Subject: [PATCH 1866/2908] Add typing to data_structures/queue/queue_on_pseudo_stack.py (#7037) * Add typing hacktoberfest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/queue/queue_on_pseudo_stack.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data_structures/queue/queue_on_pseudo_stack.py b/data_structures/queue/queue_on_pseudo_stack.py index 7fa2fb2566af..9a0c16f61eb4 100644 --- a/data_structures/queue/queue_on_pseudo_stack.py +++ b/data_structures/queue/queue_on_pseudo_stack.py @@ -1,4 +1,5 @@ """Queue represented by a pseudo stack (represented by a list with pop and append)""" +from typing import Any class Queue: @@ -14,7 +15,7 @@ def __str__(self): @param item item to enqueue""" - def put(self, item): + def put(self, item: Any) -> None: self.stack.append(item) self.length = self.length + 1 @@ -23,7 +24,7 @@ def put(self, item): @return dequeued item that was dequeued""" - def get(self): + def get(self) -> Any: self.rotate(1) dequeued = self.stack[self.length - 1] self.stack = self.stack[:-1] @@ -35,7 +36,7 @@ def get(self): @param rotation number of times to rotate queue""" - def rotate(self, rotation): + def rotate(self, rotation: int) -> None: for i in range(rotation): temp = self.stack[0] self.stack = self.stack[1:] @@ -45,7 +46,7 @@ def rotate(self, rotation): """Reports item at the front of self @return item at front of self.stack""" - def front(self): + def front(self) -> Any: front = self.get() self.put(front) self.rotate(self.length - 1) @@ -53,5 +54,5 @@ def front(self): """Returns the length of this.stack""" - def size(self): + def size(self) -> int: return self.length From f676055bc6e4f3540c97745ffc19bf62955c9077 Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Wed, 12 Oct 2022 22:19:49 +0530 Subject: [PATCH 1867/2908] Add typing to maths/segmented_sieve.py (#7054) --- maths/segmented_sieve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/segmented_sieve.py b/maths/segmented_sieve.py index 0054b0595be5..35ed9702b3be 100644 --- a/maths/segmented_sieve.py +++ b/maths/segmented_sieve.py @@ -3,7 +3,7 @@ import math -def sieve(n): +def sieve(n: int) -> list[int]: """Segmented Sieve.""" in_prime = [] start = 2 From 922887c38609650dc8eb8eaa9153605eabc45ecd Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <114707091+rohanr18@users.noreply.github.com> Date: Thu, 13 Oct 2022 00:04:01 +0530 Subject: [PATCH 1868/2908] Add volume of hollow circular cylinder, Exceptions (#6441) * Add volume of hollow circular cylinder, Exceptions * Update volume.py * floats, zeroes tests added * Update volume.py * f-strings --- maths/volume.py | 255 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 223 insertions(+), 32 deletions(-) diff --git a/maths/volume.py b/maths/volume.py index acaed65f4858..97c06d7e1c3a 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -1,6 +1,5 @@ """ Find Volumes of Various Shapes. - Wikipedia reference: https://en.wikipedia.org/wiki/Volume """ from __future__ import annotations @@ -11,12 +10,21 @@ def vol_cube(side_length: int | float) -> float: """ Calculate the Volume of a Cube. - >>> vol_cube(1) 1.0 >>> vol_cube(3) 27.0 + >>> vol_cube(0) + 0.0 + >>> vol_cube(1.6) + 4.096000000000001 + >>> vol_cube(-1) + Traceback (most recent call last): + ... + ValueError: vol_cube() only accepts non-negative values """ + if side_length < 0: + raise ValueError("vol_cube() only accepts non-negative values") return pow(side_length, 3) @@ -24,10 +32,23 @@ def vol_spherical_cap(height: float, radius: float) -> float: """ Calculate the Volume of the spherical cap. :return 1/3 pi * height ^ 2 * (3 * radius - height) - >>> vol_spherical_cap(1, 2) 5.235987755982988 + >>> vol_spherical_cap(1.6, 2.6) + 16.621119532592402 + >>> vol_spherical_cap(0, 0) + 0.0 + >>> vol_spherical_cap(-1, 2) + Traceback (most recent call last): + ... + ValueError: vol_spherical_cap() only accepts non-negative values + >>> vol_spherical_cap(1, -2) + Traceback (most recent call last): + ... + ValueError: vol_spherical_cap() only accepts non-negative values """ + if height < 0 or radius < 0: + raise ValueError("vol_spherical_cap() only accepts non-negative values") return 1 / 3 * pi * pow(height, 2) * (3 * radius - height) @@ -36,7 +57,6 @@ def vol_spheres_intersect( ) -> float: """ Calculate the volume of the intersection of two spheres. - The intersection is composed by two spherical caps and therefore its volume is the sum of the volumes of the spherical caps. First, it calculates the heights (h1, h2) of the spherical caps, then the two volumes and it returns the sum. @@ -49,10 +69,27 @@ def vol_spheres_intersect( / (2 * centers_distance) if centers_distance is 0 then it returns the volume of the smallers sphere :return vol_spherical_cap(h1, radius_2) + vol_spherical_cap(h2, radius_1) - >>> vol_spheres_intersect(2, 2, 1) 21.205750411731103 + >>> vol_spheres_intersect(2.6, 2.6, 1.6) + 40.71504079052372 + >>> vol_spheres_intersect(0, 0, 0) + 0.0 + >>> vol_spheres_intersect(-2, 2, 1) + Traceback (most recent call last): + ... + ValueError: vol_spheres_intersect() only accepts non-negative values + >>> vol_spheres_intersect(2, -2, 1) + Traceback (most recent call last): + ... + ValueError: vol_spheres_intersect() only accepts non-negative values + >>> vol_spheres_intersect(2, 2, -1) + Traceback (most recent call last): + ... + ValueError: vol_spheres_intersect() only accepts non-negative values """ + if radius_1 < 0 or radius_2 < 0 or centers_distance < 0: + raise ValueError("vol_spheres_intersect() only accepts non-negative values") if centers_distance == 0: return vol_sphere(min(radius_1, radius_2)) @@ -74,40 +111,81 @@ 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 + >>> vol_cuboid(1.6, 2.6, 3.6) + 14.976 + >>> vol_cuboid(0, 0, 0) + 0.0 + >>> vol_cuboid(-1, 2, 3) + Traceback (most recent call last): + ... + ValueError: vol_cuboid() only accepts non-negative values + >>> vol_cuboid(1, -2, 3) + Traceback (most recent call last): + ... + ValueError: vol_cuboid() only accepts non-negative values + >>> vol_cuboid(1, 2, -3) + Traceback (most recent call last): + ... + ValueError: vol_cuboid() only accepts non-negative values """ + if width < 0 or height < 0 or length < 0: + raise ValueError("vol_cuboid() only accepts non-negative values") return float(width * height * length) def vol_cone(area_of_base: float, height: float) -> float: """ Calculate the Volume of a Cone. - Wikipedia reference: https://en.wikipedia.org/wiki/Cone :return (1/3) * area_of_base * height - >>> vol_cone(10, 3) 10.0 >>> vol_cone(1, 1) 0.3333333333333333 + >>> vol_cone(1.6, 1.6) + 0.8533333333333335 + >>> vol_cone(0, 0) + 0.0 + >>> vol_cone(-1, 1) + Traceback (most recent call last): + ... + ValueError: vol_cone() only accepts non-negative values + >>> vol_cone(1, -1) + Traceback (most recent call last): + ... + ValueError: vol_cone() only accepts non-negative values """ + if height < 0 or area_of_base < 0: + raise ValueError("vol_cone() only accepts non-negative values") return area_of_base * height / 3.0 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 :return (1/3) * pi * radius^2 * height - >>> vol_right_circ_cone(2, 3) 12.566370614359172 + >>> vol_right_circ_cone(0, 0) + 0.0 + >>> vol_right_circ_cone(1.6, 1.6) + 4.289321169701265 + >>> vol_right_circ_cone(-1, 1) + Traceback (most recent call last): + ... + ValueError: vol_right_circ_cone() only accepts non-negative values + >>> vol_right_circ_cone(1, -1) + Traceback (most recent call last): + ... + ValueError: vol_right_circ_cone() only accepts non-negative values """ + if height < 0 or radius < 0: + raise ValueError("vol_right_circ_cone() only accepts non-negative values") return pi * pow(radius, 2) * height / 3.0 @@ -116,12 +194,25 @@ def vol_prism(area_of_base: float, height: float) -> float: Calculate the Volume of a Prism. Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry) :return V = Bh - >>> vol_prism(10, 2) 20.0 >>> vol_prism(11, 1) 11.0 + >>> vol_prism(1.6, 1.6) + 2.5600000000000005 + >>> vol_prism(0, 0) + 0.0 + >>> vol_prism(-1, 1) + Traceback (most recent call last): + ... + ValueError: vol_prism() only accepts non-negative values + >>> vol_prism(1, -1) + Traceback (most recent call last): + ... + ValueError: vol_prism() only accepts non-negative values """ + if height < 0 or area_of_base < 0: + raise ValueError("vol_prism() only accepts non-negative values") return float(area_of_base * height) @@ -130,12 +221,25 @@ def vol_pyramid(area_of_base: float, height: float) -> float: 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 + >>> vol_pyramid(1.6, 1.6) + 0.8533333333333335 + >>> vol_pyramid(0, 0) + 0.0 + >>> vol_pyramid(-1, 1) + Traceback (most recent call last): + ... + ValueError: vol_pyramid() only accepts non-negative values + >>> vol_pyramid(1, -1) + Traceback (most recent call last): + ... + ValueError: vol_pyramid() only accepts non-negative values """ + if height < 0 or area_of_base < 0: + raise ValueError("vol_pyramid() only accepts non-negative values") return area_of_base * height / 3.0 @@ -144,27 +248,44 @@ def vol_sphere(radius: float) -> float: Calculate the Volume of a Sphere. Wikipedia reference: https://en.wikipedia.org/wiki/Sphere :return (4/3) * pi * r^3 - >>> vol_sphere(5) 523.5987755982989 >>> vol_sphere(1) 4.1887902047863905 + >>> vol_sphere(1.6) + 17.15728467880506 + >>> vol_sphere(0) + 0.0 + >>> vol_sphere(-1) + Traceback (most recent call last): + ... + ValueError: vol_sphere() only accepts non-negative values """ + if radius < 0: + raise ValueError("vol_sphere() only accepts non-negative values") return 4 / 3 * pi * pow(radius, 3) -def vol_hemisphere(radius: float): +def vol_hemisphere(radius: float) -> float: """Calculate the volume of a hemisphere Wikipedia reference: https://en.wikipedia.org/wiki/Hemisphere Other references: https://www.cuemath.com/geometry/hemisphere :return 2/3 * pi * radius^3 - >>> vol_hemisphere(1) 2.0943951023931953 - >>> vol_hemisphere(7) 718.3775201208659 + >>> vol_hemisphere(1.6) + 8.57864233940253 + >>> vol_hemisphere(0) + 0.0 + >>> vol_hemisphere(-1) + Traceback (most recent call last): + ... + ValueError: vol_hemisphere() only accepts non-negative values """ + if radius < 0: + raise ValueError("vol_hemisphere() only accepts non-negative values") return 2 / 3 * pi * pow(radius, 3) @@ -172,26 +293,93 @@ def vol_circular_cylinder(radius: float, height: float) -> float: """Calculate the Volume of a Circular Cylinder. Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder :return pi * radius^2 * height - >>> vol_circular_cylinder(1, 1) 3.141592653589793 >>> vol_circular_cylinder(4, 3) 150.79644737231007 + >>> vol_circular_cylinder(1.6, 1.6) + 12.867963509103795 + >>> vol_circular_cylinder(0, 0) + 0.0 + >>> vol_circular_cylinder(-1, 1) + Traceback (most recent call last): + ... + ValueError: vol_circular_cylinder() only accepts non-negative values + >>> vol_circular_cylinder(1, -1) + Traceback (most recent call last): + ... + ValueError: vol_circular_cylinder() only accepts non-negative values """ + if height < 0 or radius < 0: + raise ValueError("vol_circular_cylinder() only accepts non-negative values") return pi * pow(radius, 2) * height -def vol_conical_frustum(height: float, radius_1: float, radius_2: float): +def vol_hollow_circular_cylinder( + inner_radius: float, outer_radius: float, height: float +) -> float: + """Calculate the Volume of a Hollow Circular Cylinder. + >>> vol_hollow_circular_cylinder(1, 2, 3) + 28.274333882308138 + >>> vol_hollow_circular_cylinder(1.6, 2.6, 3.6) + 47.50088092227767 + >>> vol_hollow_circular_cylinder(-1, 2, 3) + Traceback (most recent call last): + ... + ValueError: vol_hollow_circular_cylinder() only accepts non-negative values + >>> vol_hollow_circular_cylinder(1, -2, 3) + Traceback (most recent call last): + ... + ValueError: vol_hollow_circular_cylinder() only accepts non-negative values + >>> vol_hollow_circular_cylinder(1, 2, -3) + Traceback (most recent call last): + ... + ValueError: vol_hollow_circular_cylinder() only accepts non-negative values + >>> vol_hollow_circular_cylinder(2, 1, 3) + Traceback (most recent call last): + ... + ValueError: outer_radius must be greater than inner_radius + >>> vol_hollow_circular_cylinder(0, 0, 0) + Traceback (most recent call last): + ... + ValueError: outer_radius must be greater than inner_radius + """ + if inner_radius < 0 or outer_radius < 0 or height < 0: + raise ValueError( + "vol_hollow_circular_cylinder() only accepts non-negative values" + ) + if outer_radius <= inner_radius: + raise ValueError("outer_radius must be greater than inner_radius") + return pi * (pow(outer_radius, 2) - pow(inner_radius, 2)) * height + + +def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> float: """Calculate the Volume of a Conical Frustum. Wikipedia reference: https://en.wikipedia.org/wiki/Frustum :return 1/3 * pi * height * (radius_1^2 + radius_top^2 + radius_1 * radius_2) - >>> vol_conical_frustum(45, 7, 28) 48490.482608158454 - >>> vol_conical_frustum(1, 1, 2) 7.330382858376184 + >>> vol_conical_frustum(1.6, 2.6, 3.6) + 48.7240076620753 + >>> vol_conical_frustum(0, 0, 0) + 0.0 + >>> vol_conical_frustum(-2, 2, 1) + Traceback (most recent call last): + ... + ValueError: vol_conical_frustum() only accepts non-negative values + >>> vol_conical_frustum(2, -2, 1) + Traceback (most recent call last): + ... + ValueError: vol_conical_frustum() only accepts non-negative values + >>> vol_conical_frustum(2, 2, -1) + Traceback (most recent call last): + ... + ValueError: vol_conical_frustum() only accepts non-negative values """ + if radius_1 < 0 or radius_2 < 0 or height < 0: + raise ValueError("vol_conical_frustum() only accepts non-negative values") return ( 1 / 3 @@ -204,18 +392,21 @@ def vol_conical_frustum(height: float, radius_1: float, radius_2: float): 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("Hemisphere: " + str(vol_hemisphere(2))) # ~= 16.75 - print("Circular Cylinder: " + str(vol_circular_cylinder(2, 2))) # ~= 25.1 - print("Conical Frustum: " + str(vol_conical_frustum(2, 2, 4))) # ~= 58.6 - print("Spherical cap: " + str(vol_spherical_cap(1, 2))) # ~= 5.24 - print("Spheres intersetion: " + str(vol_spheres_intersect(2, 2, 1))) # ~= 21.21 + print(f"Cube: {vol_cube(2) = }") # = 8 + print(f"Cuboid: {vol_cuboid(2, 2, 2) = }") # = 8 + print(f"Cone: {vol_cone(2, 2) = }") # ~= 1.33 + print(f"Right Circular Cone: {vol_right_circ_cone(2, 2) = }") # ~= 8.38 + print(f"Prism: {vol_prism(2, 2) = }") # = 4 + print(f"Pyramid: {vol_pyramid(2, 2) = }") # ~= 1.33 + print(f"Sphere: {vol_sphere(2) = }") # ~= 33.5 + print(f"Hemisphere: {vol_hemisphere(2) = }") # ~= 16.75 + print(f"Circular Cylinder: {vol_circular_cylinder(2, 2) = }") # ~= 25.1 + print( + f"Hollow Circular Cylinder: {vol_hollow_circular_cylinder(1, 2, 3) = }" + ) # ~= 28.3 + print(f"Conical Frustum: {vol_conical_frustum(2, 2, 4) = }") # ~= 58.6 + print(f"Spherical cap: {vol_spherical_cap(1, 2) = }") # ~= 5.24 + print(f"Spheres intersetion: {vol_spheres_intersect(2, 2, 1) = }") # ~= 21.21 if __name__ == "__main__": From 2423760e1d28b4c6860ef63f83b1e6b4b83c1522 Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Thu, 13 Oct 2022 00:11:01 +0530 Subject: [PATCH 1869/2908] Add typing to maths/abs.py (#7060) --- maths/abs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/abs.py b/maths/abs.py index 68c99a1d51d8..dfea52dfbb97 100644 --- a/maths/abs.py +++ b/maths/abs.py @@ -1,7 +1,7 @@ """Absolute Value.""" -def abs_val(num): +def abs_val(num: float) -> float: """ Find the absolute value of a number. From 74494d433f8d050d37642f912f616451f40d65e6 Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Thu, 13 Oct 2022 00:11:52 +0530 Subject: [PATCH 1870/2908] Add typing to maths/ceil.py (#7057) --- maths/ceil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/ceil.py b/maths/ceil.py index 97578265c1a9..909e02b3f780 100644 --- a/maths/ceil.py +++ b/maths/ceil.py @@ -3,7 +3,7 @@ """ -def ceil(x) -> int: +def ceil(x: float) -> int: """ Return the ceiling of x as an Integral. From 32ff33648e0d1f93398db34fd271aa6606abc3a4 Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Thu, 13 Oct 2022 00:12:30 +0530 Subject: [PATCH 1871/2908] Add typing to maths/floor.py (#7056) --- maths/floor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/floor.py b/maths/floor.py index 482250f5e59e..8bbcb21aa6e4 100644 --- a/maths/floor.py +++ b/maths/floor.py @@ -3,7 +3,7 @@ """ -def floor(x) -> int: +def floor(x: float) -> int: """ Return the floor of x as an Integral. :param x: the number From 467ade28a04ed3e77b6c89542fd99f390139b5bd Mon Sep 17 00:00:00 2001 From: Rohan R Bharadwaj <114707091+rohanr18@users.noreply.github.com> Date: Thu, 13 Oct 2022 00:18:49 +0530 Subject: [PATCH 1872/2908] Add surface area of cuboid, conical frustum (#6442) * Add surface area of cuboid, conical frustum * add tests for floats, zeroes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/area.py | 131 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 15 deletions(-) diff --git a/maths/area.py b/maths/area.py index b1b139cf4e22..abbf7aa85da5 100644 --- a/maths/area.py +++ b/maths/area.py @@ -7,9 +7,12 @@ def surface_area_cube(side_length: float) -> float: """ Calculate the Surface Area of a Cube. - >>> surface_area_cube(1) 6 + >>> surface_area_cube(1.6) + 15.360000000000003 + >>> surface_area_cube(0) + 0 >>> surface_area_cube(3) 54 >>> surface_area_cube(-1) @@ -22,16 +25,46 @@ def surface_area_cube(side_length: float) -> float: return 6 * side_length**2 +def surface_area_cuboid(length: float, breadth: float, height: float) -> float: + """ + Calculate the Surface Area of a Cuboid. + >>> surface_area_cuboid(1, 2, 3) + 22 + >>> surface_area_cuboid(0, 0, 0) + 0 + >>> surface_area_cuboid(1.6, 2.6, 3.6) + 38.56 + >>> surface_area_cuboid(-1, 2, 3) + Traceback (most recent call last): + ... + ValueError: surface_area_cuboid() only accepts non-negative values + >>> surface_area_cuboid(1, -2, 3) + Traceback (most recent call last): + ... + ValueError: surface_area_cuboid() only accepts non-negative values + >>> surface_area_cuboid(1, 2, -3) + Traceback (most recent call last): + ... + ValueError: surface_area_cuboid() only accepts non-negative values + """ + if length < 0 or breadth < 0 or height < 0: + raise ValueError("surface_area_cuboid() only accepts non-negative values") + return 2 * ((length * breadth) + (breadth * height) + (length * height)) + + def surface_area_sphere(radius: float) -> float: """ Calculate the Surface Area of a Sphere. Wikipedia reference: https://en.wikipedia.org/wiki/Sphere Formula: 4 * pi * r^2 - >>> surface_area_sphere(5) 314.1592653589793 >>> surface_area_sphere(1) 12.566370614359172 + >>> surface_area_sphere(1.6) + 32.169908772759484 + >>> surface_area_sphere(0) + 0.0 >>> surface_area_sphere(-1) Traceback (most recent call last): ... @@ -46,7 +79,6 @@ def surface_area_hemisphere(radius: float) -> float: """ Calculate the Surface Area of a Hemisphere. Formula: 3 * pi * r^2 - >>> surface_area_hemisphere(5) 235.61944901923448 >>> surface_area_hemisphere(1) @@ -70,11 +102,14 @@ def surface_area_cone(radius: float, height: float) -> float: Calculate the Surface Area of a Cone. Wikipedia reference: https://en.wikipedia.org/wiki/Cone Formula: pi * r * (r + (h ** 2 + r ** 2) ** 0.5) - >>> surface_area_cone(10, 24) 1130.9733552923256 >>> surface_area_cone(6, 8) 301.59289474462014 + >>> surface_area_cone(1.6, 2.6) + 23.387862992395807 + >>> surface_area_cone(0, 0) + 0.0 >>> surface_area_cone(-1, -2) Traceback (most recent call last): ... @@ -93,14 +128,51 @@ def surface_area_cone(radius: float, height: float) -> float: return pi * radius * (radius + (height**2 + radius**2) ** 0.5) +def surface_area_conical_frustum( + radius_1: float, radius_2: float, height: float +) -> float: + """ + Calculate the Surface Area of a Conical Frustum. + >>> surface_area_conical_frustum(1, 2, 3) + 45.511728065337266 + >>> surface_area_conical_frustum(4, 5, 6) + 300.7913575056268 + >>> surface_area_conical_frustum(0, 0, 0) + 0.0 + >>> surface_area_conical_frustum(1.6, 2.6, 3.6) + 78.57907060751548 + >>> surface_area_conical_frustum(-1, 2, 3) + Traceback (most recent call last): + ... + ValueError: surface_area_conical_frustum() only accepts non-negative values + >>> surface_area_conical_frustum(1, -2, 3) + Traceback (most recent call last): + ... + ValueError: surface_area_conical_frustum() only accepts non-negative values + >>> surface_area_conical_frustum(1, 2, -3) + Traceback (most recent call last): + ... + ValueError: surface_area_conical_frustum() only accepts non-negative values + """ + if radius_1 < 0 or radius_2 < 0 or height < 0: + raise ValueError( + "surface_area_conical_frustum() only accepts non-negative values" + ) + slant_height = (height**2 + (radius_1 - radius_2) ** 2) ** 0.5 + return pi * ((slant_height * (radius_1 + radius_2)) + radius_1**2 + radius_2**2) + + def surface_area_cylinder(radius: float, height: float) -> float: """ Calculate the Surface Area of a Cylinder. Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder Formula: 2 * pi * r * (h + r) - >>> surface_area_cylinder(7, 10) 747.6990515543707 + >>> surface_area_cylinder(1.6, 2.6) + 42.22300526424682 + >>> surface_area_cylinder(0, 0) + 0.0 >>> surface_area_cylinder(6, 8) 527.7875658030853 >>> surface_area_cylinder(-1, -2) @@ -124,9 +196,12 @@ def surface_area_cylinder(radius: float, height: float) -> float: def area_rectangle(length: float, width: float) -> float: """ Calculate the area of a rectangle. - >>> area_rectangle(10, 20) 200 + >>> area_rectangle(1.6, 2.6) + 4.16 + >>> area_rectangle(0, 0) + 0 >>> area_rectangle(-1, -2) Traceback (most recent call last): ... @@ -148,9 +223,12 @@ def area_rectangle(length: float, width: float) -> float: def area_square(side_length: float) -> float: """ Calculate the area of a square. - >>> area_square(10) 100 + >>> area_square(0) + 0 + >>> area_square(1.6) + 2.5600000000000005 >>> area_square(-1) Traceback (most recent call last): ... @@ -164,9 +242,12 @@ def area_square(side_length: float) -> float: def area_triangle(base: float, height: float) -> float: """ Calculate the area of a triangle given the base and height. - >>> area_triangle(10, 10) 50.0 + >>> area_triangle(1.6, 2.6) + 2.08 + >>> area_triangle(0, 0) + 0.0 >>> area_triangle(-1, -2) Traceback (most recent call last): ... @@ -188,13 +269,15 @@ def area_triangle(base: float, height: float) -> float: 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(0, 0, 0) + 0.0 + >>> area_triangle_three_sides(1.6, 2.6, 3.6) + 1.8703742940919619 >>> area_triangle_three_sides(-1, -2, -1) Traceback (most recent call last): ... @@ -233,9 +316,12 @@ def area_triangle_three_sides(side1: float, side2: float, side3: float) -> float def area_parallelogram(base: float, height: float) -> float: """ Calculate the area of a parallelogram. - >>> area_parallelogram(10, 20) 200 + >>> area_parallelogram(1.6, 2.6) + 4.16 + >>> area_parallelogram(0, 0) + 0 >>> area_parallelogram(-1, -2) Traceback (most recent call last): ... @@ -257,9 +343,12 @@ def area_parallelogram(base: float, height: float) -> float: def area_trapezium(base1: float, base2: float, height: float) -> float: """ Calculate the area of a trapezium. - >>> area_trapezium(10, 20, 30) 450.0 + >>> area_trapezium(1.6, 2.6, 3.6) + 7.5600000000000005 + >>> area_trapezium(0, 0, 0) + 0.0 >>> area_trapezium(-1, -2, -3) Traceback (most recent call last): ... @@ -297,9 +386,12 @@ def area_trapezium(base1: float, base2: float, height: float) -> float: def area_circle(radius: float) -> float: """ Calculate the area of a circle. - >>> area_circle(20) 1256.6370614359173 + >>> area_circle(1.6) + 8.042477193189871 + >>> area_circle(0) + 0.0 >>> area_circle(-1) Traceback (most recent call last): ... @@ -313,11 +405,14 @@ def area_circle(radius: float) -> float: 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(0, 0) + 0.0 + >>> area_ellipse(1.6, 2.6) + 13.06902543893354 >>> area_ellipse(-10, 20) Traceback (most recent call last): ... @@ -339,9 +434,12 @@ 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. - >>> area_rhombus(10, 20) 100.0 + >>> area_rhombus(1.6, 2.6) + 2.08 + >>> area_rhombus(0, 0) + 0.0 >>> area_rhombus(-1, -2) Traceback (most recent call last): ... @@ -374,9 +472,12 @@ def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: print(f"Rhombus: {area_rhombus(10, 20) = }") print(f"Trapezium: {area_trapezium(10, 20, 30) = }") print(f"Circle: {area_circle(20) = }") + print(f"Ellipse: {area_ellipse(10, 20) = }") print("\nSurface Areas of various geometric shapes: \n") print(f"Cube: {surface_area_cube(20) = }") + print(f"Cuboid: {surface_area_cuboid(10, 20, 30) = }") print(f"Sphere: {surface_area_sphere(20) = }") print(f"Hemisphere: {surface_area_hemisphere(20) = }") print(f"Cone: {surface_area_cone(10, 20) = }") + print(f"Conical Frustum: {surface_area_conical_frustum(10, 20, 30) = }") print(f"Cylinder: {surface_area_cylinder(10, 20) = }") From c0c230255ffe79946bd959ecb559696353ac33f2 Mon Sep 17 00:00:00 2001 From: Eeman Majumder <54275491+Eeman1113@users.noreply.github.com> Date: Thu, 13 Oct 2022 01:13:52 +0530 Subject: [PATCH 1873/2908] added self organising maps algorithm in the machine learning section. (#6877) * added self organising maps algo * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update machine_learning/Self_Organising_Maps.py * Update and rename Self_Organising_Maps.py to self_organizing_map.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update self_organizing_map.py * Update self_organizing_map.py * Update self_organizing_map.py * Update self_organizing_map.py Co-authored-by: Eeman Majumder Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- machine_learning/self_organizing_map.py | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 machine_learning/self_organizing_map.py diff --git a/machine_learning/self_organizing_map.py b/machine_learning/self_organizing_map.py new file mode 100644 index 000000000000..bd3d388f910f --- /dev/null +++ b/machine_learning/self_organizing_map.py @@ -0,0 +1,73 @@ +""" +https://en.wikipedia.org/wiki/Self-organizing_map +""" +import math + + +class SelfOrganizingMap: + def get_winner(self, weights: list[list[float]], sample: list[int]) -> int: + """ + Compute the winning vector by Euclidean distance + + >>> SelfOrganizingMap().get_winner([[1, 2, 3], [4, 5, 6]], [1, 2, 3]) + 1 + """ + d0 = 0.0 + d1 = 0.0 + for i in range(len(sample)): + d0 += math.pow((sample[i] - weights[0][i]), 2) + d1 += math.pow((sample[i] - weights[1][i]), 2) + return 0 if d0 > d1 else 1 + return 0 + + def update( + self, weights: list[list[int | float]], sample: list[int], j: int, alpha: float + ) -> list[list[int | float]]: + """ + Update the winning vector. + + >>> SelfOrganizingMap().update([[1, 2, 3], [4, 5, 6]], [1, 2, 3], 1, 0.1) + [[1, 2, 3], [3.7, 4.7, 6]] + """ + for i in range(len(weights)): + weights[j][i] += alpha * (sample[i] - weights[j][i]) + return weights + + +# Driver code +def main() -> None: + # Training Examples ( m, n ) + training_samples = [[1, 1, 0, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, 0, 1, 1]] + + # weight initialization ( n, C ) + weights = [[0.2, 0.6, 0.5, 0.9], [0.8, 0.4, 0.7, 0.3]] + + # training + self_organizing_map = SelfOrganizingMap() + epochs = 3 + alpha = 0.5 + + for i in range(epochs): + for j in range(len(training_samples)): + + # training sample + sample = training_samples[j] + + # Compute the winning vector + winner = self_organizing_map.get_winner(weights, sample) + + # Update the winning vector + weights = self_organizing_map.update(weights, sample, winner, alpha) + + # classify test sample + sample = [0, 0, 0, 1] + winner = self_organizing_map.get_winner(weights, sample) + + # results + print(f"Clusters that the test sample belongs to : {winner}") + print(f"Weights that have been trained : {weights}") + + +# running the main() function +if __name__ == "__main__": + main() From bae08adc86c44268faaa0fe05ea0f2f91567ac9a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 12 Oct 2022 21:56:07 +0200 Subject: [PATCH 1874/2908] README.md: Lose LGTM badge because we don't use it (#7063) * README.md: Lose LGTM badge because we don't use it * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 ++ README.md | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 9ef72c403f32..25272af4a708 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -14,6 +14,7 @@ ## Audio Filters * [Butterworth Filter](audio_filters/butterworth_filter.py) + * [Equal Loudness Filter](audio_filters/equal_loudness_filter.py) * [Iir Filter](audio_filters/iir_filter.py) * [Show Response](audio_filters/show_response.py) @@ -475,6 +476,7 @@ * [Binomial Coefficient](maths/binomial_coefficient.py) * [Binomial Distribution](maths/binomial_distribution.py) * [Bisection](maths/bisection.py) + * [Carmichael Number](maths/carmichael_number.py) * [Catalan Number](maths/catalan_number.py) * [Ceil](maths/ceil.py) * [Check Polygon](maths/check_polygon.py) diff --git a/README.md b/README.md index c787979607ee..c499c14e12b9 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,6 @@ GitHub Workflow Status - - LGTM - pre-commit From e2cd982b1154814debe2960498ccbb29d4829bf7 Mon Sep 17 00:00:00 2001 From: VARISH GAUTAM <48176176+Variiiest@users.noreply.github.com> Date: Thu, 13 Oct 2022 02:12:02 +0530 Subject: [PATCH 1875/2908] Weird numbers (#6871) * Create weird_number.py In number theory, a weird number is a natural number that is abundant but not semiperfect * check * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * resolved * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed * Update weird_number.py * Update weird_number.py * Update weird_number.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/weird_number.py | 100 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 maths/weird_number.py diff --git a/maths/weird_number.py b/maths/weird_number.py new file mode 100644 index 000000000000..2834a9fee31e --- /dev/null +++ b/maths/weird_number.py @@ -0,0 +1,100 @@ +""" +https://en.wikipedia.org/wiki/Weird_number + +Fun fact: The set of weird numbers has positive asymptotic density. +""" +from math import sqrt + + +def factors(number: int) -> list[int]: + """ + >>> factors(12) + [1, 2, 3, 4, 6] + >>> factors(1) + [1] + >>> factors(100) + [1, 2, 4, 5, 10, 20, 25, 50] + + # >>> factors(-12) + # [1, 2, 3, 4, 6] + """ + + values = [1] + for i in range(2, int(sqrt(number)) + 1, 1): + if number % i == 0: + values.append(i) + if int(number // i) != i: + values.append(int(number // i)) + return sorted(values) + + +def abundant(n: int) -> bool: + """ + >>> abundant(0) + True + >>> abundant(1) + False + >>> abundant(12) + True + >>> abundant(13) + False + >>> abundant(20) + True + + # >>> abundant(-12) + # True + """ + return sum(factors(n)) > n + + +def semi_perfect(number: int) -> bool: + """ + >>> semi_perfect(0) + True + >>> semi_perfect(1) + True + >>> semi_perfect(12) + True + >>> semi_perfect(13) + False + + # >>> semi_perfect(-12) + # True + """ + values = factors(number) + r = len(values) + subset = [[0 for i in range(number + 1)] for j in range(r + 1)] + for i in range(r + 1): + subset[i][0] = True + + for i in range(1, number + 1): + subset[0][i] = False + + for i in range(1, r + 1): + for j in range(1, number + 1): + if j < values[i - 1]: + subset[i][j] = subset[i - 1][j] + else: + subset[i][j] = subset[i - 1][j] or subset[i - 1][j - values[i - 1]] + + return subset[r][number] != 0 + + +def weird(number: int) -> bool: + """ + >>> weird(0) + False + >>> weird(70) + True + >>> weird(77) + False + """ + return abundant(number) and not semi_perfect(number) + + +if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) + for number in (69, 70, 71): + print(f"{number} is {'' if weird(number) else 'not '}weird.") From 07e991d55330bf1363ba53858a98cf6fd8d45026 Mon Sep 17 00:00:00 2001 From: Caeden Date: Wed, 12 Oct 2022 23:54:20 +0100 Subject: [PATCH 1876/2908] Add pep8-naming to pre-commit hooks and fixes incorrect naming conventions (#7062) * ci(pre-commit): Add pep8-naming to `pre-commit` hooks (#7038) * refactor: Fix naming conventions (#7038) * Update arithmetic_analysis/lu_decomposition.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor(lu_decomposition): Replace `NDArray` with `ArrayLike` (#7038) * chore: Fix naming conventions in doctests (#7038) * fix: Temporarily disable project euler problem 104 (#7069) * chore: Fix naming conventions in doctests (#7038) Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 1 + arithmetic_analysis/lu_decomposition.py | 6 +- backtracking/n_queens.py | 4 +- ciphers/affine_cipher.py | 52 ++--- ciphers/bifid.py | 2 +- ciphers/brute_force_caesar_cipher.py | 2 +- ciphers/elgamal_key_generator.py | 10 +- ciphers/hill_cipher.py | 4 +- ciphers/polybius.py | 2 +- ciphers/rabin_miller.py | 14 +- ciphers/rsa_cipher.py | 14 +- ciphers/rsa_factorization.py | 10 +- ciphers/rsa_key_generator.py | 28 +-- ciphers/simple_substitution_cipher.py | 48 ++--- ciphers/trafid_cipher.py | 56 +++--- ciphers/transposition_cipher.py | 36 ++-- ...ansposition_cipher_encrypt_decrypt_file.py | 32 +-- ciphers/vigenere_cipher.py | 30 +-- compression/lempel_ziv_decompress.py | 6 +- compression/peak_signal_to_noise_ratio.py | 4 +- computer_vision/harris_corner.py | 4 +- conversions/binary_to_hexadecimal.py | 2 +- conversions/decimal_to_any.py | 2 +- conversions/prefix_conversions.py | 32 +-- conversions/roman_numerals.py | 2 +- data_structures/binary_tree/avl_tree.py | 42 ++-- .../binary_tree/lazy_segment_tree.py | 8 +- data_structures/binary_tree/segment_tree.py | 14 +- data_structures/binary_tree/treap.py | 16 +- data_structures/heap/min_heap.py | 22 +- .../stacks/infix_to_prefix_conversion.py | 60 +++--- data_structures/stacks/postfix_evaluation.py | 38 ++-- data_structures/stacks/stock_span_problem.py | 12 +- .../edge_detection/canny.py | 24 +-- .../filters/bilateral_filter.py | 20 +- .../histogram_stretch.py | 12 +- digital_image_processing/index_calculation.py | 190 +++++++++--------- .../test_digital_image_processing.py | 4 +- divide_and_conquer/inversions.py | 34 ++-- dynamic_programming/bitmask.py | 12 +- dynamic_programming/edit_distance.py | 34 ++-- dynamic_programming/floyd_warshall.py | 46 ++--- dynamic_programming/fractional_knapsack.py | 8 +- dynamic_programming/knapsack.py | 34 ++-- .../longest_common_subsequence.py | 10 +- .../longest_increasing_subsequence.py | 6 +- ...longest_increasing_subsequence_o(nlogn).py | 16 +- dynamic_programming/matrix_chain_order.py | 38 ++-- dynamic_programming/max_sub_array.py | 16 +- dynamic_programming/minimum_coin_change.py | 4 +- dynamic_programming/minimum_partition.py | 2 +- dynamic_programming/sum_of_subset.py | 18 +- fractals/sierpinski_triangle.py | 28 +-- geodesy/haversine_distance.py | 6 +- geodesy/lamberts_ellipsoidal_distance.py | 24 +-- graphs/articulation_points.py | 30 +-- graphs/basic_graphs.py | 78 +++---- graphs/check_bipartite_graph_bfs.py | 4 +- graphs/dijkstra.py | 12 +- graphs/dijkstra_2.py | 36 ++-- graphs/dijkstra_algorithm.py | 14 +- .../edmonds_karp_multiple_source_and_sink.py | 163 +++++++-------- ...n_path_and_circuit_for_undirected_graph.py | 20 +- graphs/frequent_pattern_graph_miner.py | 28 +-- graphs/kahns_algorithm_long.py | 12 +- graphs/kahns_algorithm_topo.py | 4 +- graphs/minimum_spanning_tree_prims.py | 48 ++--- graphs/multi_heuristic_astar.py | 12 +- graphs/scc_kosaraju.py | 12 +- graphs/tests/test_min_spanning_tree_prim.py | 2 +- hashes/adler32.py | 2 +- hashes/chaos_machine.py | 16 +- hashes/hamming_code.py | 170 ++++++++-------- hashes/md5.py | 92 ++++----- hashes/sha1.py | 2 +- hashes/sha256.py | 8 +- linear_algebra/src/power_iteration.py | 4 +- linear_algebra/src/rayleigh_quotient.py | 16 +- linear_algebra/src/test_linear_algebra.py | 60 +++--- machine_learning/decision_tree.py | 54 ++--- machine_learning/gaussian_naive_bayes.py | 12 +- .../gradient_boosting_regressor.py | 14 +- machine_learning/k_means_clust.py | 16 +- .../local_weighted_learning.py | 4 +- machine_learning/logistic_regression.py | 36 ++-- .../multilayer_perceptron_classifier.py | 4 +- machine_learning/random_forest_classifier.py | 6 +- machine_learning/random_forest_regressor.py | 6 +- .../sequential_minimum_optimization.py | 64 +++--- machine_learning/word_frequency_functions.py | 10 +- maths/binomial_coefficient.py | 8 +- maths/carmichael_number.py | 4 +- maths/decimal_isolate.py | 6 +- maths/euler_method.py | 6 +- maths/euler_modified.py | 6 +- maths/hardy_ramanujanalgo.py | 6 +- maths/jaccard_similarity.py | 48 ++--- maths/krishnamurthy_number.py | 6 +- maths/kth_lexicographic_permutation.py | 6 +- maths/lucas_lehmer_primality_test.py | 4 +- maths/primelib.py | 140 ++++++------- maths/qr_decomposition.py | 20 +- maths/radix2_fft.py | 72 +++---- maths/runge_kutta.py | 6 +- maths/softmax.py | 6 +- matrix/count_islands_in_matrix.py | 10 +- matrix/inverse_of_matrix.py | 6 +- matrix/sherman_morrison.py | 26 +-- networking_flow/ford_fulkerson.py | 8 +- networking_flow/minimum_cut.py | 4 +- neural_network/convolution_neural_network.py | 6 +- other/davisb_putnamb_logemannb_loveland.py | 36 ++-- other/greedy.py | 38 ++-- other/nested_brackets.py | 12 +- other/sdes.py | 8 +- other/tower_of_hanoi.py | 14 +- physics/n_body_simulation.py | 6 +- project_euler/problem_011/sol1.py | 30 +-- project_euler/problem_012/sol1.py | 16 +- project_euler/problem_023/sol1.py | 8 +- project_euler/problem_029/sol1.py | 16 +- project_euler/problem_032/sol32.py | 8 +- project_euler/problem_042/solution42.py | 4 +- project_euler/problem_054/test_poker_hand.py | 2 +- project_euler/problem_064/sol1.py | 2 +- project_euler/problem_097/sol1.py | 4 +- .../problem_104/{sol.py => sol.py.FIXME} | 0 project_euler/problem_125/sol1.py | 2 +- .../non_preemptive_shortest_job_first.py | 4 +- searches/tabu_search.py | 4 +- sorts/odd_even_transposition_parallel.py | 92 ++++----- sorts/radix_sort.py | 2 +- sorts/random_normal_distribution_quicksort.py | 44 ++-- sorts/random_pivot_quick_sort.py | 24 +-- sorts/tree_sort.py | 8 +- strings/boyer_moore_search.py | 8 +- .../can_string_be_rearranged_as_palindrome.py | 6 +- strings/check_anagrams.py | 8 +- strings/word_patterns.py | 8 +- web_programming/fetch_quotes.py | 4 +- 140 files changed, 1555 insertions(+), 1539 deletions(-) rename project_euler/problem_104/{sol.py => sol.py.FIXME} (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0abe647b017a..2f6a92814c66 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,7 @@ repos: - --ignore=E203,W503 - --max-complexity=25 - --max-line-length=88 + additional_dependencies: [pep8-naming] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.982 diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index 371f7b166b2e..1e98b9066c3f 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -6,13 +6,13 @@ from __future__ import annotations import numpy as np -import numpy.typing as NDArray from numpy import float64 +from numpy.typing import ArrayLike def lower_upper_decomposition( - table: NDArray[float64], -) -> tuple[NDArray[float64], NDArray[float64]]: + table: ArrayLike[float64], +) -> tuple[ArrayLike[float64], ArrayLike[float64]]: """Lower-Upper (LU) Decomposition Example: diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index b8ace59781f5..bbf0ce44f91c 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -12,7 +12,7 @@ solution = [] -def isSafe(board: list[list[int]], row: int, column: int) -> bool: +def is_safe(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. @@ -63,7 +63,7 @@ def solve(board: list[list[int]], row: int) -> bool: If all the combinations for that particular branch are successful the board is reinitialized for the next possible combination. """ - if isSafe(board, row, i): + if is_safe(board, row, i): board[row][i] = 1 solve(board, row + 1) board[row][i] = 0 diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index d3b806ba1eeb..cd1e33b88425 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -9,26 +9,26 @@ ) -def check_keys(keyA: int, keyB: int, mode: str) -> None: +def check_keys(key_a: int, key_b: int, mode: str) -> None: if mode == "encrypt": - if keyA == 1: + if key_a == 1: sys.exit( "The affine cipher becomes weak when key " "A is set to 1. Choose different key" ) - if keyB == 0: + if key_b == 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: + if key_a < 0 or key_b < 0 or key_b > len(SYMBOLS) - 1: sys.exit( "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(key_a, len(SYMBOLS)) != 1: sys.exit( - f"Key A {keyA} and the symbol set size {len(SYMBOLS)} " + f"Key A {key_a} and the symbol set size {len(SYMBOLS)} " "are not relatively prime. Choose a different key." ) @@ -39,16 +39,16 @@ def encrypt_message(key: int, message: str) -> str: ... '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)) - check_keys(keyA, keyB, "encrypt") - cipherText = "" + key_a, key_b = divmod(key, len(SYMBOLS)) + check_keys(key_a, key_b, "encrypt") + cipher_text = "" for symbol in message: if symbol in SYMBOLS: - symIndex = SYMBOLS.find(symbol) - cipherText += SYMBOLS[(symIndex * keyA + keyB) % len(SYMBOLS)] + sym_index = SYMBOLS.find(symbol) + cipher_text += SYMBOLS[(sym_index * key_a + key_b) % len(SYMBOLS)] else: - cipherText += symbol - return cipherText + cipher_text += symbol + return cipher_text def decrypt_message(key: int, message: str) -> str: @@ -57,25 +57,27 @@ def decrypt_message(key: int, message: str) -> str: ... '{xIp~{HL}Gi') 'The affine cipher is a type of monoalphabetic substitution cipher.' """ - keyA, keyB = divmod(key, len(SYMBOLS)) - check_keys(keyA, keyB, "decrypt") - plainText = "" - modInverseOfkeyA = cryptomath.find_mod_inverse(keyA, len(SYMBOLS)) + key_a, key_b = divmod(key, len(SYMBOLS)) + check_keys(key_a, key_b, "decrypt") + plain_text = "" + mod_inverse_of_key_a = cryptomath.find_mod_inverse(key_a, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: - symIndex = SYMBOLS.find(symbol) - plainText += SYMBOLS[(symIndex - keyB) * modInverseOfkeyA % len(SYMBOLS)] + sym_index = SYMBOLS.find(symbol) + plain_text += SYMBOLS[ + (sym_index - key_b) * mod_inverse_of_key_a % len(SYMBOLS) + ] else: - plainText += symbol - return plainText + plain_text += symbol + return plain_text def get_random_key() -> int: while True: - keyA = random.randint(2, len(SYMBOLS)) - keyB = random.randint(2, len(SYMBOLS)) - if cryptomath.gcd(keyA, len(SYMBOLS)) == 1 and keyB % len(SYMBOLS) != 0: - return keyA * len(SYMBOLS) + keyB + key_b = random.randint(2, len(SYMBOLS)) + key_b = random.randint(2, len(SYMBOLS)) + if cryptomath.gcd(key_b, len(SYMBOLS)) == 1 and key_b % len(SYMBOLS) != 0: + return key_b * len(SYMBOLS) + key_b def main() -> None: diff --git a/ciphers/bifid.py b/ciphers/bifid.py index c1b071155917..54d55574cdca 100644 --- a/ciphers/bifid.py +++ b/ciphers/bifid.py @@ -12,7 +12,7 @@ class BifidCipher: def __init__(self) -> None: - SQUARE = [ + SQUARE = [ # noqa: N806 ["a", "b", "c", "d", "e"], ["f", "g", "h", "i", "k"], ["l", "m", "n", "o", "p"], diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index 8ab6e77307b4..cc97111e05a7 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -28,7 +28,7 @@ def decrypt(message: str) -> None: Decryption using Key #24: VOFGVWZ ROFXW Decryption using Key #25: UNEFUVY QNEWV """ - LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # noqa: N806 for key in range(len(LETTERS)): translated = "" for symbol in message: diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 485b77595c7c..4d72128aed52 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -26,7 +26,7 @@ def primitive_root(p_val: int) -> int: def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, int]]: print("Generating prime p...") - p = rabin_miller.generateLargePrime(key_size) # select large prime number. + p = rabin_miller.generate_large_prime(key_size) # select large prime number. e_1 = primitive_root(p) # one primitive root on modulo p. d = random.randrange(3, p) # private_key -> have to be greater than 2 for safety. e_2 = cryptomath.find_mod_inverse(pow(e_1, d, p), p) @@ -37,7 +37,7 @@ def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, i return public_key, private_key -def make_key_files(name: str, keySize: int) -> None: +def make_key_files(name: str, key_size: int) -> None: if os.path.exists(f"{name}_pubkey.txt") or os.path.exists(f"{name}_privkey.txt"): print("\nWARNING:") print( @@ -47,16 +47,16 @@ def make_key_files(name: str, keySize: int) -> None: ) sys.exit() - publicKey, privateKey = generate_key(keySize) + public_key, private_key = generate_key(key_size) print(f"\nWriting public key to file {name}_pubkey.txt...") with open(f"{name}_pubkey.txt", "w") as fo: fo.write( - "%d,%d,%d,%d" % (publicKey[0], publicKey[1], publicKey[2], publicKey[3]) + "%d,%d,%d,%d" % (public_key[0], public_key[1], public_key[2], public_key[3]) ) print(f"Writing private key to file {name}_privkey.txt...") with open(f"{name}_privkey.txt", "w") as fo: - fo.write("%d,%d" % (privateKey[0], privateKey[1])) + fo.write("%d,%d" % (private_key[0], private_key[1])) def main() -> None: diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index d8e436e92c56..f646d567b4c8 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -201,11 +201,11 @@ def decrypt(self, text: str) -> str: def main() -> None: - N = int(input("Enter the order of the encryption key: ")) + n = int(input("Enter the order of the encryption key: ")) hill_matrix = [] print("Enter each row of the encryption key with space separated integers") - for _ in range(N): + for _ in range(n): row = [int(x) for x in input().split()] hill_matrix.append(row) diff --git a/ciphers/polybius.py b/ciphers/polybius.py index 2a45f02a3773..bf5d62f8d33e 100644 --- a/ciphers/polybius.py +++ b/ciphers/polybius.py @@ -11,7 +11,7 @@ class PolybiusCipher: def __init__(self) -> None: - SQUARE = [ + SQUARE = [ # noqa: N806 ["a", "b", "c", "d", "e"], ["f", "g", "h", "i", "k"], ["l", "m", "n", "o", "p"], diff --git a/ciphers/rabin_miller.py b/ciphers/rabin_miller.py index a9b834bfb4be..0aab80eb9175 100644 --- a/ciphers/rabin_miller.py +++ b/ciphers/rabin_miller.py @@ -3,7 +3,7 @@ import random -def rabinMiller(num: int) -> bool: +def rabin_miller(num: int) -> bool: s = num - 1 t = 0 @@ -29,7 +29,7 @@ def is_prime_low_num(num: int) -> bool: if num < 2: return False - lowPrimes = [ + low_primes = [ 2, 3, 5, @@ -200,17 +200,17 @@ def is_prime_low_num(num: int) -> bool: 997, ] - if num in lowPrimes: + if num in low_primes: return True - for prime in lowPrimes: + for prime in low_primes: if (num % prime) == 0: return False - return rabinMiller(num) + return rabin_miller(num) -def generateLargePrime(keysize: int = 1024) -> int: +def generate_large_prime(keysize: int = 1024) -> int: while True: num = random.randrange(2 ** (keysize - 1), 2 ** (keysize)) if is_prime_low_num(num): @@ -218,6 +218,6 @@ def generateLargePrime(keysize: int = 1024) -> int: if __name__ == "__main__": - num = generateLargePrime() + num = generate_large_prime() print(("Prime number:", num)) print(("is_prime_low_num:", is_prime_low_num(num))) diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index c6bfaa0fb00c..de26992f5eeb 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -37,12 +37,12 @@ def get_text_from_blocks( def encrypt_message( - message: str, key: tuple[int, int], blockSize: int = DEFAULT_BLOCK_SIZE + message: str, key: tuple[int, int], block_size: int = DEFAULT_BLOCK_SIZE ) -> list[int]: encrypted_blocks = [] n, e = key - for block in get_blocks_from_text(message, blockSize): + for block in get_blocks_from_text(message, block_size): encrypted_blocks.append(pow(block, e, n)) return encrypted_blocks @@ -63,8 +63,8 @@ def decrypt_message( def read_key_file(key_filename: str) -> tuple[int, int, int]: with open(key_filename) as fo: content = fo.read() - key_size, n, EorD = content.split(",") - return (int(key_size), int(n), int(EorD)) + key_size, n, eor_d = content.split(",") + return (int(key_size), int(n), int(eor_d)) def encrypt_and_write_to_file( @@ -125,15 +125,15 @@ def main() -> None: if mode == "encrypt": if not os.path.exists("rsa_pubkey.txt"): - rkg.makeKeyFiles("rsa", 1024) + rkg.make_key_files("rsa", 1024) message = input("\nEnter message: ") pubkey_filename = "rsa_pubkey.txt" print(f"Encrypting and writing to {filename}...") - encryptedText = encrypt_and_write_to_file(filename, pubkey_filename, message) + encrypted_text = encrypt_and_write_to_file(filename, pubkey_filename, message) print("\nEncrypted text:") - print(encryptedText) + print(encrypted_text) elif mode == "decrypt": privkey_filename = "rsa_privkey.txt" diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py index de4df27770c7..9ee52777ed83 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) -> list[int]: """ This function returns the factors of N, where p*q=N Return: [p, q] @@ -35,16 +35,16 @@ def rsafactor(d: int, e: int, N: int) -> list[int]: p = 0 q = 0 while p == 0: - g = random.randint(2, N - 1) + g = random.randint(2, n - 1) t = k while True: if t % 2 == 0: t = t // 2 - x = (g**t) % N - y = math.gcd(x - 1, N) + x = (g**t) % n + y = math.gcd(x - 1, n) if x > 1 and y > 1: p = y - q = N // y + q = n // y break # find the correct factors else: break # t is not divisible by 2, break and choose another g diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index d983c14f1d7e..f64bc7dd0557 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -2,38 +2,38 @@ import random import sys -from . import cryptomath_module as cryptoMath -from . import rabin_miller as rabinMiller +from . import cryptomath_module as cryptoMath # noqa: N812 +from . import rabin_miller as rabinMiller # noqa: N812 def main() -> None: print("Making key files...") - makeKeyFiles("rsa", 1024) + make_key_files("rsa", 1024) print("Key files generation successful.") -def generateKey(keySize: int) -> tuple[tuple[int, int], tuple[int, int]]: +def generate_key(key_size: int) -> tuple[tuple[int, int], tuple[int, int]]: print("Generating prime p...") - p = rabinMiller.generateLargePrime(keySize) + p = rabinMiller.generate_large_prime(key_size) print("Generating prime q...") - q = rabinMiller.generateLargePrime(keySize) + q = rabinMiller.generate_large_prime(key_size) n = p * q print("Generating e that is relatively prime to (p - 1) * (q - 1)...") while True: - e = random.randrange(2 ** (keySize - 1), 2 ** (keySize)) + e = random.randrange(2 ** (key_size - 1), 2 ** (key_size)) if cryptoMath.gcd(e, (p - 1) * (q - 1)) == 1: break print("Calculating d that is mod inverse of e...") d = cryptoMath.find_mod_inverse(e, (p - 1) * (q - 1)) - publicKey = (n, e) - privateKey = (n, d) - return (publicKey, privateKey) + public_key = (n, e) + private_key = (n, d) + return (public_key, private_key) -def makeKeyFiles(name: str, keySize: int) -> None: +def make_key_files(name: str, key_size: int) -> None: if os.path.exists(f"{name}_pubkey.txt") or os.path.exists(f"{name}_privkey.txt"): print("\nWARNING:") print( @@ -43,14 +43,14 @@ def makeKeyFiles(name: str, keySize: int) -> None: ) sys.exit() - publicKey, privateKey = generateKey(keySize) + public_key, private_key = generate_key(key_size) print(f"\nWriting public key to file {name}_pubkey.txt...") with open(f"{name}_pubkey.txt", "w") as out_file: - out_file.write(f"{keySize},{publicKey[0]},{publicKey[1]}") + out_file.write(f"{key_size},{public_key[0]},{public_key[1]}") print(f"Writing private key to file {name}_privkey.txt...") with open(f"{name}_privkey.txt", "w") as out_file: - out_file.write(f"{keySize},{privateKey[0]},{privateKey[1]}") + out_file.write(f"{key_size},{private_key[0]},{private_key[1]}") if __name__ == "__main__": diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index a763bd6b6b48..291a9bccd771 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -9,66 +9,66 @@ def main() -> None: key = "LFWOAYUISVKMNXPBDCRJTQEGHZ" resp = input("Encrypt/Decrypt [e/d]: ") - checkValidKey(key) + check_valid_key(key) if resp.lower().startswith("e"): mode = "encrypt" - translated = encryptMessage(key, message) + translated = encrypt_message(key, message) elif resp.lower().startswith("d"): mode = "decrypt" - translated = decryptMessage(key, message) + translated = decrypt_message(key, message) print(f"\n{mode.title()}ion: \n{translated}") -def checkValidKey(key: str) -> None: - keyList = list(key) - lettersList = list(LETTERS) - keyList.sort() - lettersList.sort() +def check_valid_key(key: str) -> None: + key_list = list(key) + letters_list = list(LETTERS) + key_list.sort() + letters_list.sort() - if keyList != lettersList: + if key_list != letters_list: sys.exit("Error in the key or symbol set.") -def encryptMessage(key: str, message: str) -> str: +def encrypt_message(key: str, message: str) -> str: """ - >>> encryptMessage('LFWOAYUISVKMNXPBDCRJTQEGHZ', 'Harshil Darji') + >>> encrypt_message('LFWOAYUISVKMNXPBDCRJTQEGHZ', 'Harshil Darji') 'Ilcrism Olcvs' """ - return translateMessage(key, message, "encrypt") + return translate_message(key, message, "encrypt") -def decryptMessage(key: str, message: str) -> str: +def decrypt_message(key: str, message: str) -> str: """ - >>> decryptMessage('LFWOAYUISVKMNXPBDCRJTQEGHZ', 'Ilcrism Olcvs') + >>> decrypt_message('LFWOAYUISVKMNXPBDCRJTQEGHZ', 'Ilcrism Olcvs') 'Harshil Darji' """ - return translateMessage(key, message, "decrypt") + return translate_message(key, message, "decrypt") -def translateMessage(key: str, message: str, mode: str) -> str: +def translate_message(key: str, message: str, mode: str) -> str: translated = "" - charsA = LETTERS - charsB = key + chars_a = LETTERS + chars_b = key if mode == "decrypt": - charsA, charsB = charsB, charsA + chars_a, chars_b = chars_b, chars_a for symbol in message: - if symbol.upper() in charsA: - symIndex = charsA.find(symbol.upper()) + if symbol.upper() in chars_a: + sym_index = chars_a.find(symbol.upper()) if symbol.isupper(): - translated += charsB[symIndex].upper() + translated += chars_b[sym_index].upper() else: - translated += charsB[symIndex].lower() + translated += chars_b[sym_index].lower() else: translated += symbol return translated -def getRandomKey() -> str: +def get_random_key() -> str: key = list(LETTERS) random.shuffle(key) return "".join(key) diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py index b12ceff72907..108ac652f0e4 100644 --- a/ciphers/trafid_cipher.py +++ b/ciphers/trafid_cipher.py @@ -2,12 +2,12 @@ from __future__ import annotations -def __encryptPart(messagePart: str, character2Number: dict[str, str]) -> str: +def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str: one, two, three = "", "", "" tmp = [] - for character in messagePart: - tmp.append(character2Number[character]) + for character in message_part: + tmp.append(character_to_number[character]) for each in tmp: one += each[0] @@ -17,18 +17,18 @@ def __encryptPart(messagePart: str, character2Number: dict[str, str]) -> str: return one + two + three -def __decryptPart( - messagePart: str, character2Number: dict[str, str] +def __decrypt_part( + message_part: str, character_to_number: dict[str, str] ) -> tuple[str, str, str]: - tmp, thisPart = "", "" + tmp, this_part = "", "" result = [] - for character in messagePart: - thisPart += character2Number[character] + for character in message_part: + this_part += character_to_number[character] - for digit in thisPart: + for digit in this_part: tmp += digit - if len(tmp) == len(messagePart): + if len(tmp) == len(message_part): result.append(tmp) tmp = "" @@ -79,51 +79,57 @@ def __prepare( "332", "333", ) - character2Number = {} - number2Character = {} + character_to_number = {} + number_to_character = {} for letter, number in zip(alphabet, numbers): - character2Number[letter] = number - number2Character[number] = letter + character_to_number[letter] = number + number_to_character[number] = letter - return message, alphabet, character2Number, number2Character + return message, alphabet, character_to_number, number_to_character -def encryptMessage( +def encrypt_message( message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 ) -> str: - message, alphabet, character2Number, number2Character = __prepare(message, alphabet) + message, alphabet, character_to_number, number_to_character = __prepare( + message, alphabet + ) encrypted, encrypted_numeric = "", "" for i in range(0, len(message) + 1, period): - encrypted_numeric += __encryptPart(message[i : i + period], character2Number) + encrypted_numeric += __encrypt_part( + message[i : i + period], character_to_number + ) for i in range(0, len(encrypted_numeric), 3): - encrypted += number2Character[encrypted_numeric[i : i + 3]] + encrypted += number_to_character[encrypted_numeric[i : i + 3]] return encrypted -def decryptMessage( +def decrypt_message( message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 ) -> str: - message, alphabet, character2Number, number2Character = __prepare(message, alphabet) + message, alphabet, character_to_number, number_to_character = __prepare( + message, alphabet + ) decrypted_numeric = [] decrypted = "" for i in range(0, len(message) + 1, period): - a, b, c = __decryptPart(message[i : i + period], character2Number) + a, b, c = __decrypt_part(message[i : i + period], character_to_number) for j in range(0, len(a)): decrypted_numeric.append(a[j] + b[j] + c[j]) for each in decrypted_numeric: - decrypted += number2Character[each] + decrypted += number_to_character[each] return decrypted if __name__ == "__main__": msg = "DEFEND THE EAST WALL OF THE CASTLE." - encrypted = encryptMessage(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") - decrypted = decryptMessage(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") + encrypted = encrypt_message(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") + decrypted = decrypt_message(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") print(f"Encrypted: {encrypted}\nDecrypted: {decrypted}") diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index ed9923a6ba46..f1f07ddc3f35 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -14,53 +14,53 @@ def main() -> None: mode = input("Encryption/Decryption [e/d]: ") if mode.lower().startswith("e"): - text = encryptMessage(key, message) + text = encrypt_message(key, message) elif mode.lower().startswith("d"): - text = decryptMessage(key, message) + text = decrypt_message(key, message) # Append pipe symbol (vertical bar) to identify spaces at the end. print(f"Output:\n{text + '|'}") -def encryptMessage(key: int, message: str) -> str: +def encrypt_message(key: int, message: str) -> str: """ - >>> encryptMessage(6, 'Harshil Darji') + >>> encrypt_message(6, 'Harshil Darji') 'Hlia rDsahrij' """ - cipherText = [""] * key + cipher_text = [""] * key for col in range(key): pointer = col while pointer < len(message): - cipherText[col] += message[pointer] + cipher_text[col] += message[pointer] pointer += key - return "".join(cipherText) + return "".join(cipher_text) -def decryptMessage(key: int, message: str) -> str: +def decrypt_message(key: int, message: str) -> str: """ - >>> decryptMessage(6, 'Hlia rDsahrij') + >>> decrypt_message(6, 'Hlia rDsahrij') 'Harshil Darji' """ - numCols = math.ceil(len(message) / key) - numRows = key - numShadedBoxes = (numCols * numRows) - len(message) - plainText = [""] * numCols + num_cols = math.ceil(len(message) / key) + num_rows = key + num_shaded_boxes = (num_cols * num_rows) - len(message) + plain_text = [""] * num_cols col = 0 row = 0 for symbol in message: - plainText[col] += symbol + plain_text[col] += symbol col += 1 if ( - (col == numCols) - or (col == numCols - 1) - and (row >= numRows - numShadedBoxes) + (col == num_cols) + or (col == num_cols - 1) + and (row >= num_rows - num_shaded_boxes) ): col = 0 row += 1 - return "".join(plainText) + return "".join(plain_text) if __name__ == "__main__": diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index 926a1b36ac44..6296b1e6d709 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -2,39 +2,39 @@ import sys import time -from . import transposition_cipher as transCipher +from . import transposition_cipher as trans_cipher def main() -> None: - inputFile = "Prehistoric Men.txt" - outputFile = "Output.txt" + input_file = "Prehistoric Men.txt" + output_file = "Output.txt" key = int(input("Enter key: ")) mode = input("Encrypt/Decrypt [e/d]: ") - if not os.path.exists(inputFile): - print(f"File {inputFile} does not exist. Quitting...") + if not os.path.exists(input_file): + print(f"File {input_file} does not exist. Quitting...") sys.exit() - if os.path.exists(outputFile): - print(f"Overwrite {outputFile}? [y/n]") + if os.path.exists(output_file): + print(f"Overwrite {output_file}? [y/n]") response = input("> ") if not response.lower().startswith("y"): sys.exit() - startTime = time.time() + start_time = time.time() if mode.lower().startswith("e"): - with open(inputFile) as f: + with open(input_file) as f: content = f.read() - translated = transCipher.encryptMessage(key, content) + translated = trans_cipher.encrypt_message(key, content) elif mode.lower().startswith("d"): - with open(outputFile) as f: + with open(output_file) as f: content = f.read() - translated = transCipher.decryptMessage(key, content) + translated = trans_cipher.decrypt_message(key, content) - with open(outputFile, "w") as outputObj: - outputObj.write(translated) + with open(output_file, "w") as output_obj: + output_obj.write(translated) - totalTime = round(time.time() - startTime, 2) - print(("Done (", totalTime, "seconds )")) + total_time = round(time.time() - start_time, 2) + print(("Done (", total_time, "seconds )")) if __name__ == "__main__": diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index 2e3987708d01..e76161351fb1 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -8,43 +8,43 @@ def main() -> None: if mode.lower().startswith("e"): mode = "encrypt" - translated = encryptMessage(key, message) + translated = encrypt_message(key, message) elif mode.lower().startswith("d"): mode = "decrypt" - translated = decryptMessage(key, message) + translated = decrypt_message(key, message) print(f"\n{mode.title()}ed message:") print(translated) -def encryptMessage(key: str, message: str) -> str: +def encrypt_message(key: str, message: str) -> str: """ - >>> encryptMessage('HDarji', 'This is Harshil Darji from Dharmaj.') + >>> encrypt_message('HDarji', 'This is Harshil Darji from Dharmaj.') 'Akij ra Odrjqqs Gaisq muod Mphumrs.' """ - return translateMessage(key, message, "encrypt") + return translate_message(key, message, "encrypt") -def decryptMessage(key: str, message: str) -> str: +def decrypt_message(key: str, message: str) -> str: """ - >>> decryptMessage('HDarji', 'Akij ra Odrjqqs Gaisq muod Mphumrs.') + >>> decrypt_message('HDarji', 'Akij ra Odrjqqs Gaisq muod Mphumrs.') 'This is Harshil Darji from Dharmaj.' """ - return translateMessage(key, message, "decrypt") + return translate_message(key, message, "decrypt") -def translateMessage(key: str, message: str, mode: str) -> str: +def translate_message(key: str, message: str, mode: str) -> str: translated = [] - keyIndex = 0 + key_index = 0 key = key.upper() for symbol in message: num = LETTERS.find(symbol.upper()) if num != -1: if mode == "encrypt": - num += LETTERS.find(key[keyIndex]) + num += LETTERS.find(key[key_index]) elif mode == "decrypt": - num -= LETTERS.find(key[keyIndex]) + num -= LETTERS.find(key[key_index]) num %= len(LETTERS) @@ -53,9 +53,9 @@ def translateMessage(key: str, message: str, mode: str) -> str: elif symbol.islower(): translated.append(LETTERS[num].lower()) - keyIndex += 1 - if keyIndex == len(key): - keyIndex = 0 + key_index += 1 + if key_index == len(key): + key_index = 0 else: translated.append(symbol) return "".join(translated) diff --git a/compression/lempel_ziv_decompress.py b/compression/lempel_ziv_decompress.py index 4d3c2c0d2cf3..ddedc3d6d32a 100644 --- a/compression/lempel_ziv_decompress.py +++ b/compression/lempel_ziv_decompress.py @@ -43,10 +43,10 @@ def decompress_data(data_bits: str) -> str: lexicon[curr_string] = last_match_id + "0" if math.log2(index).is_integer(): - newLex = {} + new_lex = {} for curr_key in list(lexicon): - newLex["0" + curr_key] = lexicon.pop(curr_key) - lexicon = newLex + new_lex["0" + curr_key] = lexicon.pop(curr_key) + lexicon = new_lex lexicon[bin(index)[2:]] = last_match_id + "1" index += 1 diff --git a/compression/peak_signal_to_noise_ratio.py b/compression/peak_signal_to_noise_ratio.py index dded2a712c7e..66b18b50b028 100644 --- a/compression/peak_signal_to_noise_ratio.py +++ b/compression/peak_signal_to_noise_ratio.py @@ -16,8 +16,8 @@ def psnr(original: float, contrast: float) -> float: mse = np.mean((original - contrast) ** 2) if mse == 0: return 100 - PIXEL_MAX = 255.0 - PSNR = 20 * math.log10(PIXEL_MAX / math.sqrt(mse)) + PIXEL_MAX = 255.0 # noqa: N806 + PSNR = 20 * math.log10(PIXEL_MAX / math.sqrt(mse)) # noqa: N806 return PSNR diff --git a/computer_vision/harris_corner.py b/computer_vision/harris_corner.py index 886ff52ea70b..7850085f8935 100644 --- a/computer_vision/harris_corner.py +++ b/computer_vision/harris_corner.py @@ -7,7 +7,7 @@ """ -class Harris_Corner: +class HarrisCorner: def __init__(self, k: float, window_size: int): """ @@ -70,6 +70,6 @@ def detect(self, img_path: str) -> tuple[cv2.Mat, list[list[int]]]: if __name__ == "__main__": - edge_detect = Harris_Corner(0.04, 3) + edge_detect = HarrisCorner(0.04, 3) color_img, _ = edge_detect.detect("path_to_image") cv2.imwrite("detect.png", color_img) diff --git a/conversions/binary_to_hexadecimal.py b/conversions/binary_to_hexadecimal.py index f94a12390607..61f335a4c465 100644 --- a/conversions/binary_to_hexadecimal.py +++ b/conversions/binary_to_hexadecimal.py @@ -17,7 +17,7 @@ def bin_to_hexadecimal(binary_str: str) -> str: ... ValueError: Empty string was passed to the function """ - BITS_TO_HEX = { + BITS_TO_HEX = { # noqa: N806 "0000": "0", "0001": "1", "0010": "2", diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index 3c72a7732ac6..e54fa154a0f7 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -66,7 +66,7 @@ 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', + ALPHABET_VALUES = {'10': 'A', '11': 'B', '12': 'C', '13': 'D', '14': 'E', '15': 'F', # noqa: N806, E501 '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', diff --git a/conversions/prefix_conversions.py b/conversions/prefix_conversions.py index a77556433c66..06b759e355a7 100644 --- a/conversions/prefix_conversions.py +++ b/conversions/prefix_conversions.py @@ -6,7 +6,7 @@ from enum import Enum -class SI_Unit(Enum): +class SIUnit(Enum): yotta = 24 zetta = 21 exa = 18 @@ -29,7 +29,7 @@ class SI_Unit(Enum): yocto = -24 -class Binary_Unit(Enum): +class BinaryUnit(Enum): yotta = 8 zetta = 7 exa = 6 @@ -42,17 +42,17 @@ class Binary_Unit(Enum): def convert_si_prefix( known_amount: float, - known_prefix: str | SI_Unit, - unknown_prefix: str | SI_Unit, + known_prefix: str | SIUnit, + unknown_prefix: str | SIUnit, ) -> 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) + >>> convert_si_prefix(1, SIUnit.giga, SIUnit.mega) 1000 - >>> convert_si_prefix(1, SI_Unit.mega, SI_Unit.giga) + >>> convert_si_prefix(1, SIUnit.mega, SIUnit.giga) 0.001 - >>> convert_si_prefix(1, SI_Unit.kilo, SI_Unit.kilo) + >>> convert_si_prefix(1, SIUnit.kilo, SIUnit.kilo) 1 >>> convert_si_prefix(1, 'giga', 'mega') 1000 @@ -60,9 +60,9 @@ def convert_si_prefix( 1000 """ if isinstance(known_prefix, str): - known_prefix = SI_Unit[known_prefix.lower()] + known_prefix = SIUnit[known_prefix.lower()] if isinstance(unknown_prefix, str): - unknown_prefix = SI_Unit[unknown_prefix.lower()] + unknown_prefix = SIUnit[unknown_prefix.lower()] unknown_amount: float = known_amount * ( 10 ** (known_prefix.value - unknown_prefix.value) ) @@ -71,16 +71,16 @@ def convert_si_prefix( def convert_binary_prefix( known_amount: float, - known_prefix: str | Binary_Unit, - unknown_prefix: str | Binary_Unit, + known_prefix: str | BinaryUnit, + unknown_prefix: str | BinaryUnit, ) -> float: """ Wikipedia reference: https://en.wikipedia.org/wiki/Metric_prefix - >>> convert_binary_prefix(1, Binary_Unit.giga, Binary_Unit.mega) + >>> convert_binary_prefix(1, BinaryUnit.giga, BinaryUnit.mega) 1024 - >>> convert_binary_prefix(1, Binary_Unit.mega, Binary_Unit.giga) + >>> convert_binary_prefix(1, BinaryUnit.mega, BinaryUnit.giga) 0.0009765625 - >>> convert_binary_prefix(1, Binary_Unit.kilo, Binary_Unit.kilo) + >>> convert_binary_prefix(1, BinaryUnit.kilo, BinaryUnit.kilo) 1 >>> convert_binary_prefix(1, 'giga', 'mega') 1024 @@ -88,9 +88,9 @@ def convert_binary_prefix( 1024 """ if isinstance(known_prefix, str): - known_prefix = Binary_Unit[known_prefix.lower()] + known_prefix = BinaryUnit[known_prefix.lower()] if isinstance(unknown_prefix, str): - unknown_prefix = Binary_Unit[unknown_prefix.lower()] + unknown_prefix = BinaryUnit[unknown_prefix.lower()] unknown_amount: float = known_amount * ( 2 ** ((known_prefix.value - unknown_prefix.value) * 10) ) diff --git a/conversions/roman_numerals.py b/conversions/roman_numerals.py index 9933e6a78a4d..960d41342276 100644 --- a/conversions/roman_numerals.py +++ b/conversions/roman_numerals.py @@ -29,7 +29,7 @@ def int_to_roman(number: int) -> str: >>> all(int_to_roman(value) == key for key, value in tests.items()) True """ - ROMAN = [ + ROMAN = [ # noqa: N806 (1000, "M"), (900, "CM"), (500, "D"), diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 1ab13777b7a6..2f4bd60d9749 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -12,7 +12,7 @@ from typing import Any -class my_queue: +class MyQueue: def __init__(self) -> None: self.data: list[Any] = [] self.head: int = 0 @@ -39,20 +39,20 @@ def print(self) -> None: print(self.data[self.head : self.tail]) -class my_node: +class MyNode: def __init__(self, data: Any) -> None: self.data = data - self.left: my_node | None = None - self.right: my_node | None = None + self.left: MyNode | None = None + self.right: MyNode | None = None self.height: int = 1 def get_data(self) -> Any: return self.data - def get_left(self) -> my_node | None: + def get_left(self) -> MyNode | None: return self.left - def get_right(self) -> my_node | None: + def get_right(self) -> MyNode | None: return self.right def get_height(self) -> int: @@ -62,11 +62,11 @@ def set_data(self, data: Any) -> None: self.data = data return - def set_left(self, node: my_node | None) -> None: + def set_left(self, node: MyNode | None) -> None: self.left = node return - def set_right(self, node: my_node | None) -> None: + def set_right(self, node: MyNode | None) -> None: self.right = node return @@ -75,7 +75,7 @@ def set_height(self, height: int) -> None: return -def get_height(node: my_node | None) -> int: +def get_height(node: MyNode | None) -> int: if node is None: return 0 return node.get_height() @@ -87,7 +87,7 @@ def my_max(a: int, b: int) -> int: return b -def right_rotation(node: my_node) -> my_node: +def right_rotation(node: MyNode) -> MyNode: r""" A B / \ / \ @@ -110,7 +110,7 @@ def right_rotation(node: my_node) -> my_node: return ret -def left_rotation(node: my_node) -> my_node: +def left_rotation(node: MyNode) -> MyNode: """ a mirror symmetry rotation of the left_rotation """ @@ -126,7 +126,7 @@ def left_rotation(node: my_node) -> my_node: return ret -def lr_rotation(node: my_node) -> my_node: +def lr_rotation(node: MyNode) -> MyNode: r""" A A Br / \ / \ / \ @@ -143,16 +143,16 @@ def lr_rotation(node: my_node) -> my_node: return right_rotation(node) -def rl_rotation(node: my_node) -> my_node: +def rl_rotation(node: MyNode) -> MyNode: right_child = node.get_right() assert right_child is not None node.set_right(right_rotation(right_child)) return left_rotation(node) -def insert_node(node: my_node | None, data: Any) -> my_node | None: +def insert_node(node: MyNode | None, data: Any) -> MyNode | None: if node is None: - return my_node(data) + return MyNode(data) if data < node.get_data(): node.set_left(insert_node(node.get_left(), data)) if ( @@ -180,7 +180,7 @@ def insert_node(node: my_node | None, data: Any) -> my_node | None: return node -def get_rightMost(root: my_node) -> Any: +def get_right_most(root: MyNode) -> Any: while True: right_child = root.get_right() if right_child is None: @@ -189,7 +189,7 @@ def get_rightMost(root: my_node) -> Any: return root.get_data() -def get_leftMost(root: my_node) -> Any: +def get_left_most(root: MyNode) -> Any: while True: left_child = root.get_left() if left_child is None: @@ -198,12 +198,12 @@ def get_leftMost(root: my_node) -> Any: return root.get_data() -def del_node(root: my_node, data: Any) -> my_node | None: +def del_node(root: MyNode, data: Any) -> MyNode | None: left_child = root.get_left() right_child = root.get_right() if root.get_data() == data: if left_child is not None and right_child is not None: - temp_data = get_leftMost(right_child) + temp_data = get_left_most(right_child) root.set_data(temp_data) root.set_right(del_node(right_child, temp_data)) elif left_child is not None: @@ -276,7 +276,7 @@ class AVLtree: """ def __init__(self) -> None: - self.root: my_node | None = None + self.root: MyNode | None = None def get_height(self) -> int: return get_height(self.root) @@ -296,7 +296,7 @@ def __str__( self, ) -> str: # a level traversale, gives a more intuitive look on the tree output = "" - q = my_queue() + q = MyQueue() q.push(self.root) layer = self.get_height() if layer == 0: diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 94329cb43a76..050dfe0a6f2f 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -37,14 +37,14 @@ 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] + 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.build(self.left(idx), left_element, mid, a) + self.build(self.right(idx), mid + 1, right_element, a) self.segment_tree[idx] = max( self.segment_tree[self.left(idx)], self.segment_tree[self.right(idx)] ) diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index 10451ae68bb2..949a3ecdd32c 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -2,8 +2,8 @@ class SegmentTree: - def __init__(self, A): - self.N = len(A) + def __init__(self, a): + self.N = len(a) self.st = [0] * ( 4 * self.N ) # approximate the overall size of segment tree with array N @@ -58,11 +58,11 @@ def query_recursive(self, idx, l, r, a, b): # noqa: E741 q2 = self.query_recursive(self.right(idx), mid + 1, r, a, b) return max(q1, q2) - def showData(self): - showList = [] + def show_data(self): + show_list = [] for i in range(1, N + 1): - showList += [self.query(i, i)] - print(showList) + show_list += [self.query(i, i)] + print(show_list) if __name__ == "__main__": @@ -75,4 +75,4 @@ def showData(self): segt.update(1, 3, 111) print(segt.query(1, 15)) segt.update(7, 8, 235) - segt.showData() + segt.show_data() diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 0526b139b3c7..a53ac566ed54 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -121,28 +121,28 @@ def inorder(root: Node | None) -> None: inorder(root.right) -def interactTreap(root: Node | None, args: str) -> Node | None: +def interact_treap(root: Node | None, args: str) -> Node | None: """ Commands: + value to add value into treap - value to erase all nodes with value - >>> root = interactTreap(None, "+1") + >>> root = interact_treap(None, "+1") >>> inorder(root) 1, - >>> root = interactTreap(root, "+3 +5 +17 +19 +2 +16 +4 +0") + >>> root = interact_treap(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") + >>> root = interact_treap(root, "+4 +4 +4") >>> inorder(root) 0,1,2,3,4,4,4,4,5,16,17,19, - >>> root = interactTreap(root, "-0") + >>> root = interact_treap(root, "-0") >>> inorder(root) 1,2,3,4,4,4,4,5,16,17,19, - >>> root = interactTreap(root, "-4") + >>> root = interact_treap(root, "-4") >>> inorder(root) 1,2,3,5,16,17,19, - >>> root = interactTreap(root, "=0") + >>> root = interact_treap(root, "=0") Unknown command """ for arg in args.split(): @@ -168,7 +168,7 @@ def main() -> None: args = input() while args != "q": - root = interactTreap(root, args) + root = interact_treap(root, args) print(root) args = input() diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index 9265c4839536..d8975eb2dcc7 100644 --- a/data_structures/heap/min_heap.py +++ b/data_structures/heap/min_heap.py @@ -52,14 +52,14 @@ 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) + last_idx = len(array) - 1 + start_from = self.get_parent_idx(last_idx) 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): + for i in range(start_from, -1, -1): self.sift_down(i, array) return array @@ -123,12 +123,12 @@ def insert(self, node): def is_empty(self): return True if len(self.heap) == 0 else False - def decrease_key(self, node, newValue): + def decrease_key(self, node, new_value): assert ( - self.heap[self.idx_of_element[node]].val > newValue + self.heap[self.idx_of_element[node]].val > new_value ), "newValue must be less that current value" - node.val = newValue - self.heap_dict[node.name] = newValue + node.val = new_value + self.heap_dict[node.name] = new_value self.sift_up(self.idx_of_element[node]) @@ -143,7 +143,7 @@ def decrease_key(self, node, newValue): # Use one of these two ways to generate Min-Heap # Generating Min-Heap from array -myMinHeap = MinHeap([r, b, a, x, e]) +my_min_heap = MinHeap([r, b, a, x, e]) # Generating Min-Heap by Insert method # myMinHeap.insert(a) @@ -154,14 +154,14 @@ def decrease_key(self, node, newValue): # Before print("Min Heap - before decrease key") -for i in myMinHeap.heap: +for i in my_min_heap.heap: print(i) print("Min Heap - After decrease key of node [B -> -17]") -myMinHeap.decrease_key(b, -17) +my_min_heap.decrease_key(b, -17) # After -for i in myMinHeap.heap: +for i in my_min_heap.heap: print(i) if __name__ == "__main__": diff --git a/data_structures/stacks/infix_to_prefix_conversion.py b/data_structures/stacks/infix_to_prefix_conversion.py index d3dc9e3e9c73..6f6d5d57e2cb 100644 --- a/data_structures/stacks/infix_to_prefix_conversion.py +++ b/data_structures/stacks/infix_to_prefix_conversion.py @@ -15,9 +15,9 @@ """ -def infix_2_postfix(Infix): - Stack = [] - Postfix = [] +def infix_2_postfix(infix): + stack = [] + post_fix = [] priority = { "^": 3, "*": 2, @@ -26,7 +26,7 @@ def infix_2_postfix(Infix): "+": 1, "-": 1, } # Priority of each operator - print_width = len(Infix) if (len(Infix) > 7) else 7 + print_width = len(infix) if (len(infix) > 7) else 7 # Print table header for output print( @@ -37,52 +37,52 @@ def infix_2_postfix(Infix): ) print("-" * (print_width * 3 + 7)) - for x in Infix: + for x in infix: if x.isalpha() or x.isdigit(): - Postfix.append(x) # if x is Alphabet / Digit, add it to Postfix + post_fix.append(x) # if x is Alphabet / Digit, add it to Postfix elif x == "(": - Stack.append(x) # if x is "(" push to Stack + 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() + while stack[-1] != "(": + post_fix.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 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 + while len(stack) > 0 and priority[x] <= priority[stack[-1]]: + post_fix.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), + ("".join(stack)).ljust(print_width), + ("".join(post_fix)).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 + while len(stack) > 0: # while stack is not empty + post_fix.append(stack.pop()) # pop stack & add to Postfix print( " ".center(8), - ("".join(Stack)).ljust(print_width), - ("".join(Postfix)).ljust(print_width), + ("".join(stack)).ljust(print_width), + ("".join(post_fix)).ljust(print_width), sep=" | ", ) # Output in tabular format - return "".join(Postfix) # return Postfix as str + return "".join(post_fix) # return Postfix as str -def infix_2_prefix(Infix): - Infix = list(Infix[::-1]) # reverse the infix equation +def infix_2_prefix(infix): + 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 "(" + 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)))[ + return (infix_2_postfix("".join(infix)))[ ::-1 ] # call infix_2_postfix on Infix, return reverse of Postfix diff --git a/data_structures/stacks/postfix_evaluation.py b/data_structures/stacks/postfix_evaluation.py index 574acac71c43..28128f82ec19 100644 --- a/data_structures/stacks/postfix_evaluation.py +++ b/data_structures/stacks/postfix_evaluation.py @@ -20,49 +20,49 @@ import operator as op -def Solve(Postfix): - Stack = [] - Div = lambda x, y: int(x / y) # noqa: E731 integer division operation - Opr = { +def solve(post_fix): + stack = [] + div = lambda x, y: int(x / y) # noqa: E731 integer division operation + opr = { "^": op.pow, "*": op.mul, - "/": Div, + "/": 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("-" * (30 + len(post_fix))) - for x in Postfix: + for x in post_fix: if x.isdigit(): # if x in digit - Stack.append(x) # append x to stack + stack.append(x) # append x to stack # output in tabular format - print(x.rjust(8), ("push(" + x + ")").ljust(12), ",".join(Stack), sep=" | ") + print(x.rjust(8), ("push(" + x + ")").ljust(12), ",".join(stack), sep=" | ") else: - B = Stack.pop() # pop stack + b = stack.pop() # pop stack # output in tabular format - print("".rjust(8), ("pop(" + B + ")").ljust(12), ",".join(Stack), sep=" | ") + print("".rjust(8), ("pop(" + b + ")").ljust(12), ",".join(stack), sep=" | ") - A = Stack.pop() # pop stack + a = stack.pop() # pop stack # output in tabular format - print("".rjust(8), ("pop(" + A + ")").ljust(12), ",".join(Stack), sep=" | ") + print("".rjust(8), ("pop(" + a + ")").ljust(12), ",".join(stack), sep=" | ") - Stack.append( - str(Opr[x](int(A), int(B))) + 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), + ("push(" + a + x + b + ")").ljust(12), + ",".join(stack), sep=" | ", ) - return int(Stack[0]) + return int(stack[0]) if __name__ == "__main__": Postfix = input("\n\nEnter a Postfix Equation (space separated) = ").split(" ") - print("\n\tResult = ", Solve(Postfix)) + print("\n\tResult = ", solve(Postfix)) diff --git a/data_structures/stacks/stock_span_problem.py b/data_structures/stacks/stock_span_problem.py index cc2adfdd6c21..19a81bd368de 100644 --- a/data_structures/stacks/stock_span_problem.py +++ b/data_structures/stacks/stock_span_problem.py @@ -8,7 +8,7 @@ """ -def calculateSpan(price, S): +def calculation_span(price, s): n = len(price) # Create a stack and push index of fist element to it @@ -16,7 +16,7 @@ def calculateSpan(price, S): st.append(0) # Span value of first element is always 1 - S[0] = 1 + s[0] = 1 # Calculate span values for rest of the elements for i in range(1, n): @@ -30,14 +30,14 @@ def calculateSpan(price, S): # 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) # A utility function to print elements of array -def printArray(arr, n): +def print_array(arr, n): for i in range(0, n): print(arr[i], end=" ") @@ -47,7 +47,7 @@ def printArray(arr, n): S = [0 for i in range(len(price) + 1)] # Fill the span values in array S[] -calculateSpan(price, S) +calculation_span(price, S) # Print the calculated span values -printArray(S, len(price)) +print_array(S, len(price)) diff --git a/digital_image_processing/edge_detection/canny.py b/digital_image_processing/edge_detection/canny.py index 295b4d825c12..a830355267c4 100644 --- a/digital_image_processing/edge_detection/canny.py +++ b/digital_image_processing/edge_detection/canny.py @@ -43,33 +43,33 @@ def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): 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: + 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: + 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: + 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: + 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] """ diff --git a/digital_image_processing/filters/bilateral_filter.py b/digital_image_processing/filters/bilateral_filter.py index 76ae4dd20345..1afa01d3fc1a 100644 --- a/digital_image_processing/filters/bilateral_filter.py +++ b/digital_image_processing/filters/bilateral_filter.py @@ -46,16 +46,16 @@ def bilateral_filter( 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) + gauss_ker = get_gauss_kernel(kernel_size, spatial_variance) + size_x, size_y = img.shape + for i in range(kernel_size // 2, size_x - kernel_size // 2): + for j in range(kernel_size // 2, size_y - kernel_size // 2): + + img_s = get_slice(img, i, j, kernel_size) + img_i = img_s - img_s[kernel_size // 2, kernel_size // 2] + img_ig = vec_gaussian(img_i, intensity_variance) + weights = np.multiply(gauss_ker, img_ig) + vals = np.multiply(img_s, weights) val = np.sum(vals) / np.sum(weights) img2[i, j] = val return img2 diff --git a/digital_image_processing/histogram_equalization/histogram_stretch.py b/digital_image_processing/histogram_equalization/histogram_stretch.py index 0288a2c1fcf5..5ea7773e32d9 100644 --- a/digital_image_processing/histogram_equalization/histogram_stretch.py +++ b/digital_image_processing/histogram_equalization/histogram_stretch.py @@ -11,7 +11,7 @@ from matplotlib import pyplot as plt -class contrastStretch: +class ConstantStretch: def __init__(self): self.img = "" self.original_image = "" @@ -45,10 +45,10 @@ def stretch(self, input_image): self.img[j][i] = self.last_list[num] cv2.imwrite("output_data/output.jpg", self.img) - def plotHistogram(self): + def plot_histogram(self): plt.hist(self.img.ravel(), 256, [0, 256]) - def showImage(self): + def show_image(self): cv2.imshow("Output-Image", self.img) cv2.imshow("Input-Image", self.original_image) cv2.waitKey(5000) @@ -57,7 +57,7 @@ def showImage(self): if __name__ == "__main__": file_path = os.path.join(os.path.basename(__file__), "image_data/input.jpg") - stretcher = contrastStretch() + stretcher = ConstantStretch() stretcher.stretch(file_path) - stretcher.plotHistogram() - stretcher.showImage() + stretcher.plot_histogram() + stretcher.show_image() diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index 033334af8a2a..2f8fdc066919 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -104,72 +104,72 @@ class IndexCalculation: #RGBIndex = ["GLI", "CI", "Hue", "I", "NGRDI", "RI", "S", "IF"] """ - def __init__(self, red=None, green=None, blue=None, redEdge=None, nir=None): + def __init__(self, red=None, green=None, blue=None, red_edge=None, nir=None): # print("Numpy version: " + np.__version__) - self.setMatrices(red=red, green=green, blue=blue, redEdge=redEdge, nir=nir) + self.set_matricies(red=red, green=green, blue=blue, red_edge=red_edge, nir=nir) - def setMatrices(self, red=None, green=None, blue=None, redEdge=None, nir=None): + def set_matricies(self, red=None, green=None, blue=None, red_edge=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 red_edge is not None: + self.redEdge = red_edge if nir is not None: self.nir = nir return True def calculation( - self, index="", red=None, green=None, blue=None, redEdge=None, nir=None + self, index="", red=None, green=None, blue=None, red_edge=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) + self.set_matricies(red=red, green=green, blue=blue, red_edge=red_edge, 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, + "ARVI2": self.arv12, + "CCCI": self.ccci, + "CVI": self.cvi, + "GLI": self.gli, + "NDVI": self.ndvi, + "BNDVI": self.bndvi, + "redEdgeNDVI": self.red_edge_ndvi, + "GNDVI": self.gndvi, + "GBNDVI": self.gbndvi, + "GRNDVI": self.grndvi, + "RBNDVI": self.rbndvi, + "PNDVI": self.pndvi, + "ATSAVI": self.atsavi, + "BWDRVI": self.bwdrvi, + "CIgreen": self.ci_green, + "CIrededge": self.ci_rededge, + "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.m_savi, + "NormG": self.norm_g, + "NormNIR": self.norm_nir, + "NormR": self.norm_r, + "NGRDI": self.ngrdi, + "RI": self.ri, + "S": self.s, + "IF": self._if, + "DVI": self.dvi, + "TVI": self.tvi, + "NDRE": self.ndre, } try: @@ -178,7 +178,7 @@ def calculation( print("Index not in the list!") return False - def ARVI2(self): + def arv12(self): """ Atmospherically Resistant Vegetation Index 2 https://www.indexdatabase.de/db/i-single.php?id=396 @@ -187,7 +187,7 @@ def ARVI2(self): """ return -0.18 + (1.17 * ((self.nir - self.red) / (self.nir + self.red))) - def CCCI(self): + def ccci(self): """ Canopy Chlorophyll Content Index https://www.indexdatabase.de/db/i-single.php?id=224 @@ -197,7 +197,7 @@ def CCCI(self): (self.nir - self.red) / (self.nir + self.red) ) - def CVI(self): + def cvi(self): """ Chlorophyll vegetation index https://www.indexdatabase.de/db/i-single.php?id=391 @@ -205,7 +205,7 @@ def CVI(self): """ return self.nir * (self.red / (self.green**2)) - def GLI(self): + def gli(self): """ self.green leaf index https://www.indexdatabase.de/db/i-single.php?id=375 @@ -215,7 +215,7 @@ def GLI(self): 2 * self.green + self.red + self.blue ) - def NDVI(self): + def ndvi(self): """ Normalized Difference self.nir/self.red Normalized Difference Vegetation Index, Calibrated NDVI - CDVI @@ -224,7 +224,7 @@ def NDVI(self): """ return (self.nir - self.red) / (self.nir + self.red) - def BNDVI(self): + def bndvi(self): """ Normalized Difference self.nir/self.blue self.blue-normalized difference vegetation index @@ -233,7 +233,7 @@ def BNDVI(self): """ return (self.nir - self.blue) / (self.nir + self.blue) - def redEdgeNDVI(self): + def red_edge_ndvi(self): """ Normalized Difference self.rededge/self.red https://www.indexdatabase.de/db/i-single.php?id=235 @@ -241,7 +241,7 @@ def redEdgeNDVI(self): """ return (self.redEdge - self.red) / (self.redEdge + self.red) - def GNDVI(self): + def gndvi(self): """ Normalized Difference self.nir/self.green self.green NDVI https://www.indexdatabase.de/db/i-single.php?id=401 @@ -249,7 +249,7 @@ def GNDVI(self): """ return (self.nir - self.green) / (self.nir + self.green) - def GBNDVI(self): + def gbndvi(self): """ self.green-self.blue NDVI https://www.indexdatabase.de/db/i-single.php?id=186 @@ -259,7 +259,7 @@ def GBNDVI(self): self.nir + (self.green + self.blue) ) - def GRNDVI(self): + def grndvi(self): """ self.green-self.red NDVI https://www.indexdatabase.de/db/i-single.php?id=185 @@ -269,7 +269,7 @@ def GRNDVI(self): self.nir + (self.green + self.red) ) - def RBNDVI(self): + def rbndvi(self): """ self.red-self.blue NDVI https://www.indexdatabase.de/db/i-single.php?id=187 @@ -277,7 +277,7 @@ def RBNDVI(self): """ return (self.nir - (self.blue + self.red)) / (self.nir + (self.blue + self.red)) - def PNDVI(self): + def pndvi(self): """ Pan NDVI https://www.indexdatabase.de/db/i-single.php?id=188 @@ -287,7 +287,7 @@ def PNDVI(self): self.nir + (self.green + self.red + self.blue) ) - def ATSAVI(self, X=0.08, a=1.22, b=0.03): + 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 @@ -295,10 +295,10 @@ def ATSAVI(self, X=0.08, a=1.22, b=0.03): """ return a * ( (self.nir - a * self.red - b) - / (a * self.nir + self.red - a * b + X * (1 + a**2)) + / (a * self.nir + self.red - a * b + x * (1 + a**2)) ) - def BWDRVI(self): + def bwdrvi(self): """ self.blue-wide dynamic range vegetation index https://www.indexdatabase.de/db/i-single.php?id=136 @@ -306,7 +306,7 @@ def BWDRVI(self): """ return (0.1 * self.nir - self.blue) / (0.1 * self.nir + self.blue) - def CIgreen(self): + def ci_green(self): """ Chlorophyll Index self.green https://www.indexdatabase.de/db/i-single.php?id=128 @@ -314,7 +314,7 @@ def CIgreen(self): """ return (self.nir / self.green) - 1 - def CIrededge(self): + def ci_rededge(self): """ Chlorophyll Index self.redEdge https://www.indexdatabase.de/db/i-single.php?id=131 @@ -322,7 +322,7 @@ def CIrededge(self): """ return (self.nir / self.redEdge) - 1 - def CI(self): + def ci(self): """ Coloration Index https://www.indexdatabase.de/db/i-single.php?id=11 @@ -330,16 +330,16 @@ def CI(self): """ return (self.red - self.blue) / self.red - def CTVI(self): + def ctvi(self): """ Corrected Transformed Vegetation Index https://www.indexdatabase.de/db/i-single.php?id=244 :return: index """ - ndvi = self.NDVI() + ndvi = self.ndvi() return ((ndvi + 0.5) / (abs(ndvi + 0.5))) * (abs(ndvi + 0.5) ** (1 / 2)) - def GDVI(self): + def gdvi(self): """ Difference self.nir/self.green self.green Difference Vegetation Index https://www.indexdatabase.de/db/i-single.php?id=27 @@ -347,7 +347,7 @@ def GDVI(self): """ return self.nir - self.green - def EVI(self): + def evi(self): """ Enhanced Vegetation Index https://www.indexdatabase.de/db/i-single.php?id=16 @@ -357,7 +357,7 @@ def EVI(self): (self.nir - self.red) / (self.nir + 6 * self.red - 7.5 * self.blue + 1) ) - def GEMI(self): + def gemi(self): """ Global Environment Monitoring Index https://www.indexdatabase.de/db/i-single.php?id=25 @@ -368,25 +368,25 @@ def GEMI(self): ) return n * (1 - 0.25 * n) - (self.red - 0.125) / (1 - self.red) - def GOSAVI(self, Y=0.16): + 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) + return (self.nir - self.green) / (self.nir + self.green + y) - def GSAVI(self, L=0.5): + def gsavi(self, n=0.5): """ self.green Soil Adjusted Vegetation Index https://www.indexdatabase.de/db/i-single.php?id=31 - mit L = 0,5 + mit N = 0,5 :return: index """ - return ((self.nir - self.green) / (self.nir + self.green + L)) * (1 + L) + return ((self.nir - self.green) / (self.nir + self.green + n)) * (1 + n) - def Hue(self): + def hue(self): """ Hue https://www.indexdatabase.de/db/i-single.php?id=34 @@ -396,7 +396,7 @@ def Hue(self): ((2 * self.red - self.green - self.blue) / 30.5) * (self.green - self.blue) ) - def IVI(self, a=None, b=None): + def ivi(self, a=None, b=None): """ Ideal vegetation index https://www.indexdatabase.de/db/i-single.php?id=276 @@ -406,15 +406,15 @@ def IVI(self, a=None, b=None): """ return (self.nir - b) / (a * self.red) - def IPVI(self): + 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) + return (self.nir / ((self.nir + self.red) / 2)) * (self.ndvi() + 1) - def I(self): # noqa: E741,E743 + def i(self): # noqa: E741,E743 """ Intensity https://www.indexdatabase.de/db/i-single.php?id=36 @@ -422,7 +422,7 @@ def I(self): # noqa: E741,E743 """ return (self.red + self.green + self.blue) / 30.5 - def RVI(self): + def rvi(self): """ Ratio-Vegetation-Index http://www.seos-project.eu/modules/remotesensing/remotesensing-c03-s01-p01.html @@ -430,15 +430,15 @@ def RVI(self): """ return self.nir / self.red - def MRVI(self): + 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) + return (self.rvi() - 1) / (self.rvi() + 1) - def MSAVI(self): + def m_savi(self): """ Modified Soil Adjusted Vegetation Index https://www.indexdatabase.de/db/i-single.php?id=44 @@ -449,7 +449,7 @@ def MSAVI(self): - ((2 * self.nir + 1) ** 2 - 8 * (self.nir - self.red)) ** (1 / 2) ) / 2 - def NormG(self): + def norm_g(self): """ Norm G https://www.indexdatabase.de/db/i-single.php?id=50 @@ -457,7 +457,7 @@ def NormG(self): """ return self.green / (self.nir + self.red + self.green) - def NormNIR(self): + def norm_nir(self): """ Norm self.nir https://www.indexdatabase.de/db/i-single.php?id=51 @@ -465,7 +465,7 @@ def NormNIR(self): """ return self.nir / (self.nir + self.red + self.green) - def NormR(self): + def norm_r(self): """ Norm R https://www.indexdatabase.de/db/i-single.php?id=52 @@ -473,7 +473,7 @@ def NormR(self): """ return self.red / (self.nir + self.red + self.green) - def NGRDI(self): + def ngrdi(self): """ Normalized Difference self.green/self.red Normalized self.green self.red difference index, Visible Atmospherically Resistant Indices self.green @@ -483,7 +483,7 @@ def NGRDI(self): """ return (self.green - self.red) / (self.green + self.red) - def RI(self): + def ri(self): """ Normalized Difference self.red/self.green self.redness Index https://www.indexdatabase.de/db/i-single.php?id=74 @@ -491,7 +491,7 @@ def RI(self): """ return (self.red - self.green) / (self.red + self.green) - def S(self): + def s(self): """ Saturation https://www.indexdatabase.de/db/i-single.php?id=77 @@ -501,7 +501,7 @@ def S(self): min = np.min([np.min(self.red), np.min(self.green), np.min(self.blue)]) return (max - min) / max - def IF(self): + def _if(self): """ Shape Index https://www.indexdatabase.de/db/i-single.php?id=79 @@ -509,7 +509,7 @@ def IF(self): """ return (2 * self.red - self.green - self.blue) / (self.green - self.blue) - def DVI(self): + def dvi(self): """ Simple Ratio self.nir/self.red Difference Vegetation Index, Vegetation Index Number (VIN) @@ -518,15 +518,15 @@ def DVI(self): """ return self.nir / self.red - def TVI(self): + 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) + return (self.ndvi() + 0.5) ** (1 / 2) - def NDRE(self): + def ndre(self): return (self.nir - self.redEdge) / (self.nir + self.redEdge) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 1f42fddf297a..fdcebfdad161 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -62,8 +62,8 @@ def test_gen_gaussian_kernel_filter(): def test_convolve_filter(): # laplace diagonals - Laplace = array([[0.25, 0.5, 0.25], [0.5, -3, 0.5], [0.25, 0.5, 0.25]]) - res = conv.img_convolve(gray, Laplace).astype(uint8) + laplace = array([[0.25, 0.5, 0.25], [0.5, -3, 0.5], [0.25, 0.5, 0.25]]) + res = conv.img_convolve(gray, laplace).astype(uint8) assert res.any() diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py index e20d35daccbe..35f78fe5cf1e 100644 --- a/divide_and_conquer/inversions.py +++ b/divide_and_conquer/inversions.py @@ -63,18 +63,18 @@ def count_inversions_recursive(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 - P = arr[0:mid] - Q = arr[mid:] + 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) + 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 + return c, num_inversions -def _count_cross_inversions(P, Q): +def _count_cross_inversions(p, q): """ Counts the inversions across two sorted arrays. And combine the two arrays into one sorted array @@ -96,26 +96,26 @@ def _count_cross_inversions(P, Q): ([1, 2, 3, 3, 4, 5], 0) """ - R = [] + r = [] i = j = num_inversion = 0 - while i < len(P) and j < len(Q): - if P[i] > Q[j]: + 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]) + num_inversion += len(p) - i + r.append(q[j]) j += 1 else: - R.append(P[i]) + r.append(p[i]) i += 1 - if i < len(P): - R.extend(P[i:]) + if i < len(p): + r.extend(p[i:]) else: - R.extend(Q[j:]) + r.extend(q[j:]) - return R, num_inversion + return r, num_inversion def main(): diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index 2994db5b5e1e..f45250c9cb84 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -28,7 +28,7 @@ def __init__(self, task_performed, total): # to 1 self.final_mask = (1 << len(task_performed)) - 1 - def CountWaysUtil(self, mask, task_no): + def count_ways_until(self, mask, task_no): # if mask == self.finalmask all persons are distributed tasks, return 1 if mask == self.final_mask: @@ -43,7 +43,7 @@ def CountWaysUtil(self, mask, task_no): return self.dp[mask][task_no] # Number of ways when we don't this task in the arrangement - total_ways_util = self.CountWaysUtil(mask, task_no + 1) + total_ways_util = self.count_ways_until(mask, task_no + 1) # now assign the tasks one by one to all possible persons and recursively # assign for the remaining tasks. @@ -56,14 +56,14 @@ def CountWaysUtil(self, mask, task_no): # 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) + total_ways_util += self.count_ways_until(mask | (1 << p), task_no + 1) # save the value. self.dp[mask][task_no] = total_ways_util return self.dp[mask][task_no] - def countNoOfWays(self, task_performed): + def count_no_of_ways(self, task_performed): # Store the list of persons for each task for i in range(len(task_performed)): @@ -71,7 +71,7 @@ def countNoOfWays(self, task_performed): self.task[j].append(i) # call the function to fill the DP table, final answer is stored in dp[0][1] - return self.CountWaysUtil(0, 1) + return self.count_ways_until(0, 1) if __name__ == "__main__": @@ -81,7 +81,7 @@ def countNoOfWays(self, task_performed): # the list of tasks that can be done by M persons. task_performed = [[1, 3, 4], [1, 2, 5], [3, 4]] print( - AssignmentUsingBitmask(task_performed, total_tasks).countNoOfWays( + AssignmentUsingBitmask(task_performed, total_tasks).count_no_of_ways( task_performed ) ) diff --git a/dynamic_programming/edit_distance.py b/dynamic_programming/edit_distance.py index 56877e0c50a2..d63e559e30da 100644 --- a/dynamic_programming/edit_distance.py +++ b/dynamic_programming/edit_distance.py @@ -21,10 +21,10 @@ class EditDistance: def __init__(self): self.__prepare__() - def __prepare__(self, N=0, M=0): - self.dp = [[-1 for y in range(0, M)] for x in range(0, N)] + def __prepare__(self, n=0, m=0): + self.dp = [[-1 for y in range(0, m)] for x in range(0, n)] - def __solveDP(self, x, y): + def __solve_dp(self, x, y): if x == -1: return y + 1 elif y == -1: @@ -32,30 +32,30 @@ def __solveDP(self, x, y): 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.__solve_dp(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.__solve_dp(x, y - 1), + self.__solve_dp(x - 1, y), + self.__solve_dp(x - 1, y - 1), ) return self.dp[x][y] - def solve(self, A, B): - if isinstance(A, bytes): - A = A.decode("ascii") + def solve(self, a, b): + 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.a = str(a) + self.b = str(b) - self.__prepare__(len(A), len(B)) + self.__prepare__(len(a), len(b)) - return self.__solveDP(len(A) - 1, len(B) - 1) + return self.__solve_dp(len(a) - 1, len(b) - 1) def min_distance_bottom_up(word1: str, word2: str) -> int: diff --git a/dynamic_programming/floyd_warshall.py b/dynamic_programming/floyd_warshall.py index a4b6c6a82568..614a3c72a992 100644 --- a/dynamic_programming/floyd_warshall.py +++ b/dynamic_programming/floyd_warshall.py @@ -2,41 +2,41 @@ class Graph: - 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) + 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) + [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): + def add_edge(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): + def show_min(self, u, v): return self.dp[u][v] 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.add_edge(0, 2, 9) + graph.add_edge(0, 4, 10) + graph.add_edge(1, 3, 5) + graph.add_edge(2, 3, 7) + graph.add_edge(3, 0, 10) + graph.add_edge(3, 1, 2) + graph.add_edge(3, 2, 1) + graph.add_edge(3, 4, 6) + graph.add_edge(4, 1, 3) + graph.add_edge(4, 2, 4) + graph.add_edge(4, 3, 9) graph.floyd_warshall() - graph.showMin(1, 4) - graph.showMin(0, 3) + graph.show_min(1, 4) + graph.show_min(0, 3) diff --git a/dynamic_programming/fractional_knapsack.py b/dynamic_programming/fractional_knapsack.py index c74af7ef8fc5..6f7a2a08cf9b 100644 --- a/dynamic_programming/fractional_knapsack.py +++ b/dynamic_programming/fractional_knapsack.py @@ -2,20 +2,20 @@ from itertools import accumulate -def fracKnapsack(vl, wt, W, n): +def frac_knapsack(vl, wt, w, n): """ - >>> fracKnapsack([60, 100, 120], [10, 20, 30], 50, 3) + >>> frac_knapsack([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] acc = list(accumulate(wt)) - k = bisect(acc, W) + k = bisect(acc, w) return ( 0 if k == 0 - else sum(vl[:k]) + (W - acc[k - 1]) * (vl[k]) / (wt[k]) + else sum(vl[:k]) + (w - acc[k - 1]) * (vl[k]) / (wt[k]) if k != n else sum(vl[:k]) ) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 804d7d4f12f5..9efb60bab98b 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -7,39 +7,39 @@ """ -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 F is a 2D array with -1s filled up """ - global F # a global dp table for knapsack - if F[i][j] < 0: + 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) + 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], + 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] + 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)] +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): + 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], dp + 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. @@ -90,9 +90,9 @@ def knapsack_with_example_solution(W: int, wt: list, val: list): f"got weight of type {type(wt[i])} at index {i}" ) - optimal_val, dp_table = knapsack(W, wt, val, num_items) + optimal_val, dp_table = knapsack(w, wt, val, num_items) example_optional_set: set = set() - _construct_solution(dp_table, wt, num_items, W, example_optional_set) + _construct_solution(dp_table, wt, num_items, w, example_optional_set) return optimal_val, example_optional_set @@ -136,10 +136,10 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set): 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)] + 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 + 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 diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index fdcf3311a017..3468fd87da8d 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -38,7 +38,7 @@ def longest_common_subsequence(x: str, y: str): n = len(y) # declaring the array for storing the dp values - L = [[0] * (n + 1) for _ in range(m + 1)] + l = [[0] * (n + 1) for _ in range(m + 1)] # noqa: E741 for i in range(1, m + 1): for j in range(1, n + 1): @@ -47,7 +47,7 @@ def longest_common_subsequence(x: str, y: str): 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 @@ -57,17 +57,17 @@ def longest_common_subsequence(x: str, y: str): else: match = 0 - if L[i][j] == L[i - 1][j - 1] + match: + 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]: + elif l[i][j] == l[i - 1][j]: i -= 1 else: j -= 1 - return L[m][n], seq + return l[m][n], seq if __name__ == "__main__": diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index a029f9be7d98..6feed23529f1 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -34,12 +34,12 @@ def longest_subsequence(array: list[int]) -> list[int]: # This function is recu return array # Else pivot = array[0] - isFound = False + is_found = False i = 1 longest_subseq: list[int] = [] - while not isFound and i < array_length: + while not is_found and i < array_length: if array[i] < pivot: - isFound = True + is_found = True 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): diff --git a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py index af536f8bbd01..5e11d729f395 100644 --- a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py @@ -7,7 +7,7 @@ from __future__ import annotations -def CeilIndex(v, l, r, key): # noqa: E741 +def ceil_index(v, l, r, key): # noqa: E741 while r - l > 1: m = (l + r) // 2 if v[m] >= key: @@ -17,16 +17,16 @@ def CeilIndex(v, l, r, key): # noqa: E741 return r -def LongestIncreasingSubsequenceLength(v: list[int]) -> int: +def longest_increasing_subsequence_length(v: list[int]) -> int: """ - >>> LongestIncreasingSubsequenceLength([2, 5, 3, 7, 11, 8, 10, 13, 6]) + >>> longest_increasing_subsequence_length([2, 5, 3, 7, 11, 8, 10, 13, 6]) 6 - >>> LongestIncreasingSubsequenceLength([]) + >>> longest_increasing_subsequence_length([]) 0 - >>> LongestIncreasingSubsequenceLength([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, - ... 11, 7, 15]) + >>> longest_increasing_subsequence_length([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, + ... 3, 11, 7, 15]) 6 - >>> LongestIncreasingSubsequenceLength([5, 4, 3, 2, 1]) + >>> longest_increasing_subsequence_length([5, 4, 3, 2, 1]) 1 """ if len(v) == 0: @@ -44,7 +44,7 @@ def LongestIncreasingSubsequenceLength(v: list[int]) -> int: tail[length] = v[i] length += 1 else: - tail[CeilIndex(tail, -1, length - 1, v[i])] = v[i] + tail[ceil_index(tail, -1, length - 1, v[i])] = v[i] return length diff --git a/dynamic_programming/matrix_chain_order.py b/dynamic_programming/matrix_chain_order.py index 9411bc704f1c..d612aea7b99d 100644 --- a/dynamic_programming/matrix_chain_order.py +++ b/dynamic_programming/matrix_chain_order.py @@ -8,34 +8,34 @@ """ -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)] +def matrix_chain_order(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)] - for ChainLength in range(2, N): - for a in range(1, N - ChainLength + 1): - b = a + ChainLength - 1 + for chain_length in range(2, n): + for a in range(1, n - chain_length + 1): + b = a + chain_length - 1 - Matrix[a][b] = sys.maxsize + 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] + 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 + 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): +def print_optiomal_solution(optimal_solution, 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_optiomal_solution(optimal_solution, i, optimal_solution[i][j]) + print_optiomal_solution(optimal_solution, optimal_solution[i][j] + 1, j) print(")", end=" ") @@ -44,10 +44,10 @@ def main(): 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, optimal_solution = matrix_chain_order(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])) + print_optiomal_solution(optimal_solution, 1, n - 1) if __name__ == "__main__": diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 3060010ef7c6..42eca79a931e 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -4,14 +4,14 @@ from __future__ import annotations -def find_max_sub_array(A, low, high): +def find_max_sub_array(a, low, high): if low == high: - return low, high, A[low] + 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) + 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: @@ -20,18 +20,18 @@ def find_max_sub_array(A, low, high): return cross_left, cross_right, cross_sum -def find_max_cross_sum(A, low, mid, high): +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] + 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] + summ += a[i] if summ > right_sum: right_sum = summ max_right = i diff --git a/dynamic_programming/minimum_coin_change.py b/dynamic_programming/minimum_coin_change.py index 2869b5857be1..848bd654d3b9 100644 --- a/dynamic_programming/minimum_coin_change.py +++ b/dynamic_programming/minimum_coin_change.py @@ -7,7 +7,7 @@ """ -def dp_count(S, n): +def dp_count(s, n): """ >>> dp_count([1, 2, 3], 4) 4 @@ -33,7 +33,7 @@ def dp_count(S, n): # Pick all coins one by one and update table[] values # after the index greater than or equal to the value of the # picked coin - for coin_val in S: + for coin_val in s: for j in range(coin_val, n + 1): table[j] += table[j - coin_val] diff --git a/dynamic_programming/minimum_partition.py b/dynamic_programming/minimum_partition.py index 8fad4ef3072f..3daa9767fde4 100644 --- a/dynamic_programming/minimum_partition.py +++ b/dynamic_programming/minimum_partition.py @@ -3,7 +3,7 @@ """ -def findMin(arr): +def find_min(arr): n = len(arr) s = sum(arr) diff --git a/dynamic_programming/sum_of_subset.py b/dynamic_programming/sum_of_subset.py index a12177b57c74..77672b0b83e5 100644 --- a/dynamic_programming/sum_of_subset.py +++ b/dynamic_programming/sum_of_subset.py @@ -1,25 +1,25 @@ -def isSumSubset(arr, arrLen, requiredSum): +def is_sum_subset(arr, arr_len, required_sum): """ - >>> isSumSubset([2, 4, 6, 8], 4, 5) + >>> is_sum_subset([2, 4, 6, 8], 4, 5) False - >>> isSumSubset([2, 4, 6, 8], 4, 14) + >>> is_sum_subset([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)] + subset = [[False for i in range(required_sum + 1)] for i in range(arr_len + 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): + for i in range(arr_len + 1): subset[i][0] = True # sum is not zero and set is empty then false - for i in range(1, requiredSum + 1): + for i in range(1, required_sum + 1): subset[0][i] = False - for i in range(1, arrLen + 1): - for j in range(1, requiredSum + 1): + for i in range(1, arr_len + 1): + for j in range(1, required_sum + 1): if arr[i - 1] > j: subset[i][j] = subset[i - 1][j] if arr[i - 1] <= j: @@ -28,7 +28,7 @@ def isSumSubset(arr, arrLen, requiredSum): # uncomment to print the subset # for i in range(arrLen+1): # print(subset[i]) - print(subset[arrLen][requiredSum]) + print(subset[arr_len][required_sum]) if __name__ == "__main__": diff --git a/fractals/sierpinski_triangle.py b/fractals/sierpinski_triangle.py index cf41ffa5f190..8be2897c152a 100644 --- a/fractals/sierpinski_triangle.py +++ b/fractals/sierpinski_triangle.py @@ -35,30 +35,30 @@ points = [[-175, -125], [0, 175], [175, -125]] # size of triangle -def getMid(p1, p2): +def get_mid(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.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]) + my_pen.up() + my_pen.goto(points[0][0], points[0][1]) + my_pen.down() + my_pen.goto(points[1][0], points[1][1]) + my_pen.goto(points[2][0], points[2][1]) + my_pen.goto(points[0][0], points[0][1]) if depth > 0: triangle( - [points[0], getMid(points[0], points[1]), getMid(points[0], points[2])], + [points[0], get_mid(points[0], points[1]), get_mid(points[0], points[2])], depth - 1, ) triangle( - [points[1], getMid(points[0], points[1]), getMid(points[1], points[2])], + [points[1], get_mid(points[0], points[1]), get_mid(points[1], points[2])], depth - 1, ) triangle( - [points[2], getMid(points[2], points[1]), getMid(points[0], points[2])], + [points[2], get_mid(points[2], points[1]), get_mid(points[0], points[2])], depth - 1, ) @@ -69,8 +69,8 @@ def triangle(points, depth): "right format for using this script: " "$python fractals.py " ) - myPen = turtle.Turtle() - myPen.ht() - myPen.speed(5) - myPen.pencolor("red") + my_pen = turtle.Turtle() + my_pen.ht() + my_pen.speed(5) + my_pen.pencolor("red") triangle(points, int(sys.argv[1])) diff --git a/geodesy/haversine_distance.py b/geodesy/haversine_distance.py index de8ac7f88302..b601d2fd1983 100644 --- a/geodesy/haversine_distance.py +++ b/geodesy/haversine_distance.py @@ -30,9 +30,9 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl """ # 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 + AXIS_A = 6378137.0 # noqa: N806 + AXIS_B = 6356752.314245 # noqa: N806 + RADIUS = 6378137 # noqa: N806 # Equation parameters # Equation https://en.wikipedia.org/wiki/Haversine_formula#Formulation flattening = (AXIS_A - AXIS_B) / AXIS_A diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py index bf8f1b9a5080..d36d399538de 100644 --- a/geodesy/lamberts_ellipsoidal_distance.py +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -45,9 +45,9 @@ def lamberts_ellipsoidal_distance( # 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 + AXIS_A = 6378137.0 # noqa: N806 + AXIS_B = 6356752.314245 # noqa: N806 + EQUATORIAL_RADIUS = 6378137 # noqa: N806 # Equation Parameters # https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines @@ -62,22 +62,22 @@ def lamberts_ellipsoidal_distance( 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 + 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) + 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) + 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))) + return EQUATORIAL_RADIUS * (sigma - ((flattening / 2) * (x_value + y_value))) if __name__ == "__main__": diff --git a/graphs/articulation_points.py b/graphs/articulation_points.py index 7197369de090..d28045282425 100644 --- a/graphs/articulation_points.py +++ b/graphs/articulation_points.py @@ -1,14 +1,14 @@ # Finding Articulation Points in Undirected Graph -def computeAP(l): # noqa: E741 +def compute_ap(l): # noqa: E741 n = len(l) - outEdgeCount = 0 + out_edge_count = 0 low = [0] * n visited = [False] * n - isArt = [False] * n + is_art = [False] * n - def dfs(root, at, parent, outEdgeCount): + def dfs(root, at, parent, out_edge_count): if parent == root: - outEdgeCount += 1 + out_edge_count += 1 visited[at] = True low[at] = at @@ -16,27 +16,27 @@ def dfs(root, at, parent, outEdgeCount): if to == parent: pass elif not visited[to]: - outEdgeCount = dfs(root, to, at, outEdgeCount) + out_edge_count = dfs(root, to, at, out_edge_count) low[at] = min(low[at], low[to]) # AP found via bridge if at < low[to]: - isArt[at] = True + is_art[at] = True # AP found via cycle if at == low[to]: - isArt[at] = True + is_art[at] = True else: low[at] = min(low[at], to) - return outEdgeCount + return out_edge_count for i in range(n): if not visited[i]: - outEdgeCount = 0 - outEdgeCount = dfs(i, i, -1, outEdgeCount) - isArt[i] = outEdgeCount > 1 + out_edge_count = 0 + out_edge_count = dfs(i, i, -1, out_edge_count) + is_art[i] = out_edge_count > 1 - for x in range(len(isArt)): - if isArt[x] is True: + for x in range(len(is_art)): + if is_art[x] is True: print(x) @@ -52,4 +52,4 @@ def dfs(root, at, parent, outEdgeCount): 7: [6, 8], 8: [5, 7], } -computeAP(data) +compute_ap(data) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index db0ef8e7b3ac..b02e9af65846 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -76,20 +76,20 @@ def initialize_weighted_undirected_graph( """ -def dfs(G, s): - vis, S = {s}, [s] +def dfs(g, s): + vis, _s = {s}, [s] print(s) - while S: + while _s: flag = 0 - for i in G[S[-1]]: + for i in g[_s[-1]]: if i not in vis: - S.append(i) + _s.append(i) vis.add(i) flag = 1 print(i) break if not flag: - S.pop() + _s.pop() """ @@ -103,15 +103,15 @@ def dfs(G, s): """ -def bfs(G, s): - vis, Q = {s}, deque([s]) +def bfs(g, s): + vis, q = {s}, deque([s]) print(s) - while Q: - u = Q.popleft() - for v in G[u]: + while q: + u = q.popleft() + for v in g[u]: if v not in vis: vis.add(v) - Q.append(v) + q.append(v) print(v) @@ -127,10 +127,10 @@ def bfs(G, s): """ -def dijk(G, s): +def dijk(g, s): dist, known, path = {s: 0}, set(), {s: 0} while True: - if len(known) == len(G) - 1: + if len(known) == len(g) - 1: break mini = 100000 for i in dist: @@ -138,7 +138,7 @@ def dijk(G, s): mini = dist[i] u = i known.add(u) - for v in G[u]: + for v in g[u]: if v[0] not in known: if dist[u] + v[1] < dist.get(v[0], 100000): dist[v[0]] = dist[u] + v[1] @@ -155,27 +155,27 @@ def dijk(G, s): """ -def topo(G, ind=None, Q=None): - if Q is 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: - for v in G[u]: + ind = [0] * (len(g) + 1) # SInce oth Index is ignored + for u in g: + for v in g[u]: ind[v] += 1 - Q = deque() - for i in G: + q = deque() + for i in g: if ind[i] == 0: - Q.append(i) - if len(Q) == 0: + q.append(i) + if len(q) == 0: return - v = Q.popleft() + v = q.popleft() print(v) - for w in G[v]: + for w in g[v]: ind[w] -= 1 if ind[w] == 0: - Q.append(w) - topo(G, ind, Q) + q.append(w) + topo(g, ind, q) """ @@ -206,9 +206,9 @@ def adjm(): """ -def floy(A_and_n): - (A, n) = A_and_n - dist = list(A) +def floy(a_and_n): + (a, n) = a_and_n + dist = list(a) path = [[0] * n for i in range(n)] for k in range(n): for i in range(n): @@ -231,10 +231,10 @@ def floy(A_and_n): """ -def prim(G, s): +def prim(g, s): dist, known, path = {s: 0}, set(), {s: 0} while True: - if len(known) == len(G) - 1: + if len(known) == len(g) - 1: break mini = 100000 for i in dist: @@ -242,7 +242,7 @@ def prim(G, s): mini = dist[i] u = i known.add(u) - for v in G[u]: + for v in g[u]: if v[0] not in known: if v[1] < dist.get(v[0], 100000): dist[v[0]] = v[1] @@ -279,16 +279,16 @@ def edglist(): """ -def krusk(E_and_n): +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]) + (e, n) = e_and_n + e.sort(reverse=True, key=lambda x: x[2]) s = [{i} for i in range(1, n + 1)] while True: if len(s) == 1: break print(s) - x = E.pop() + x = e.pop() for i in range(len(s)): if x[0] in s[i]: break diff --git a/graphs/check_bipartite_graph_bfs.py b/graphs/check_bipartite_graph_bfs.py index b5203b4c5c7d..552b7eee283d 100644 --- a/graphs/check_bipartite_graph_bfs.py +++ b/graphs/check_bipartite_graph_bfs.py @@ -9,7 +9,7 @@ from queue import Queue -def checkBipartite(graph): +def check_bipartite(graph): queue = Queue() visited = [False] * len(graph) color = [-1] * len(graph) @@ -45,4 +45,4 @@ def bfs(): if __name__ == "__main__": # Adjacency List of graph - print(checkBipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]})) + print(check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]})) diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index d15fcbbfeef0..62c60f2c6be6 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -103,14 +103,14 @@ def dijkstra(graph, start, end): "G": [["F", 1]], } -shortDistance = dijkstra(G, "E", "C") -print(shortDistance) # E -- 3 --> F -- 3 --> C == 6 +short_distance = dijkstra(G, "E", "C") +print(short_distance) # E -- 3 --> F -- 3 --> C == 6 -shortDistance = dijkstra(G2, "E", "F") -print(shortDistance) # E -- 3 --> F == 3 +short_distance = dijkstra(G2, "E", "F") +print(short_distance) # E -- 3 --> F == 3 -shortDistance = dijkstra(G3, "E", "F") -print(shortDistance) # E -- 2 --> G -- 1 --> F == 3 +short_distance = dijkstra(G3, "E", "F") +print(short_distance) # E -- 2 --> G -- 1 --> F == 3 if __name__ == "__main__": import doctest diff --git a/graphs/dijkstra_2.py b/graphs/dijkstra_2.py index 762884136e4a..3170765bc8a8 100644 --- a/graphs/dijkstra_2.py +++ b/graphs/dijkstra_2.py @@ -1,6 +1,6 @@ -def printDist(dist, V): +def print_dist(dist, v): print("\nVertex Distance") - for i in range(V): + for i in range(v): if dist[i] != float("inf"): print(i, "\t", int(dist[i]), end="\t") else: @@ -8,26 +8,26 @@ def printDist(dist, V): 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 +def min_dist(mdist, vset, v): + min_val = float("inf") + min_ind = -1 + for i in range(v): + if (not vset[i]) and mdist[i] < min_val: + min_ind = i + min_val = mdist[i] + return min_ind -def Dijkstra(graph, V, src): - mdist = [float("inf") for i in range(V)] - vset = [False for i in range(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) + for i in range(v - 1): + u = min_dist(mdist, vset, v) vset[u] = True - for v in range(V): + for v in range(v): if ( (not vset[v]) and graph[u][v] != float("inf") @@ -35,7 +35,7 @@ def Dijkstra(graph, V, src): ): mdist[v] = mdist[u] + graph[u][v] - printDist(mdist, V) + print_dist(mdist, v) if __name__ == "__main__": @@ -55,4 +55,4 @@ def Dijkstra(graph, V, src): graph[src][dst] = weight gsrc = int(input("\nEnter shortest path source:").strip()) - Dijkstra(graph, V, gsrc) + dijkstra(graph, V, gsrc) diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 6b64834acd81..122821a376ed 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -15,7 +15,7 @@ def __init__(self): self.array = [] self.pos = {} # To store the pos of node in array - def isEmpty(self): + def is_empty(self): return self.cur_size == 0 def min_heapify(self, idx): @@ -110,24 +110,24 @@ def dijkstra(self, src): self.par = [-1] * self.num_nodes # src is the source node self.dist[src] = 0 - Q = PriorityQueue() - Q.insert((0, src)) # (dist from src, node) + q = PriorityQueue() + q.insert((0, src)) # (dist from src, node) for u in self.adjList.keys(): if u != src: self.dist[u] = sys.maxsize # Infinity self.par[u] = -1 - while not Q.isEmpty(): - u = Q.extract_min() # Returns node with the min dist from source + while not q.is_empty(): + u = q.extract_min() # Returns node with the min dist from source # Update the distance of all the neighbours of u and # if their prev dist was INFINITY then push them in Q for v, w in self.adjList[u]: new_dist = self.dist[u] + w if self.dist[v] > new_dist: if self.dist[v] == sys.maxsize: - Q.insert((new_dist, v)) + q.insert((new_dist, v)) else: - Q.decrease_key((self.dist[v], v), new_dist) + q.decrease_key((self.dist[v], v), new_dist) self.dist[v] = new_dist self.par[v] = u diff --git a/graphs/edmonds_karp_multiple_source_and_sink.py b/graphs/edmonds_karp_multiple_source_and_sink.py index 0f359ff1aea3..070d758e63b6 100644 --- a/graphs/edmonds_karp_multiple_source_and_sink.py +++ b/graphs/edmonds_karp_multiple_source_and_sink.py @@ -1,15 +1,15 @@ class FlowNetwork: def __init__(self, graph, sources, sinks): - self.sourceIndex = None - self.sinkIndex = None + self.source_index = None + self.sink_index = None self.graph = graph - self._normalizeGraph(sources, sinks) - self.verticesCount = len(graph) - self.maximumFlowAlgorithm = None + self._normalize_graph(sources, sinks) + self.vertices_count = len(graph) + self.maximum_flow_algorithm = None # make only one source and one sink - def _normalizeGraph(self, sources, sinks): + def _normalize_graph(self, sources, sinks): if sources is int: sources = [sources] if sinks is int: @@ -18,54 +18,54 @@ def _normalizeGraph(self, sources, sinks): if len(sources) == 0 or len(sinks) == 0: return - self.sourceIndex = sources[0] - self.sinkIndex = sinks[0] + self.source_index = sources[0] + self.sink_index = sinks[0] # make fake vertex if there are more # than one source or sink if len(sources) > 1 or len(sinks) > 1: - maxInputFlow = 0 + max_input_flow = 0 for i in sources: - maxInputFlow += sum(self.graph[i]) + max_input_flow += 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.graph[0][i + 1] = max_input_flow + self.source_index = 0 size = len(self.graph) + 1 for room in self.graph: room.append(0) self.graph.append([0] * size) for i in sinks: - self.graph[i + 1][size - 1] = maxInputFlow - self.sinkIndex = size - 1 + self.graph[i + 1][size - 1] = max_input_flow + self.sink_index = size - 1 - def findMaximumFlow(self): - if self.maximumFlowAlgorithm is None: + def find_maximum_flow(self): + if self.maximum_flow_algorithm is None: raise Exception("You need to set maximum flow algorithm before.") - if self.sourceIndex is None or self.sinkIndex is None: + if self.source_index is None or self.sink_index is None: return 0 - self.maximumFlowAlgorithm.execute() - return self.maximumFlowAlgorithm.getMaximumFlow() + self.maximum_flow_algorithm.execute() + return self.maximum_flow_algorithm.getMaximumFlow() - def setMaximumFlowAlgorithm(self, Algorithm): - self.maximumFlowAlgorithm = Algorithm(self) + def set_maximum_flow_algorithm(self, algorithm): + self.maximum_flow_algorithm = algorithm(self) class FlowNetworkAlgorithmExecutor: - def __init__(self, flowNetwork): - self.flowNetwork = flowNetwork - self.verticesCount = flowNetwork.verticesCount - self.sourceIndex = flowNetwork.sourceIndex - self.sinkIndex = flowNetwork.sinkIndex + def __init__(self, flow_network): + self.flow_network = flow_network + self.verticies_count = flow_network.verticesCount + self.source_index = flow_network.sourceIndex + self.sink_index = flow_network.sinkIndex # it's just a reference, so you shouldn't change # it in your algorithms, use deep copy before doing that - self.graph = flowNetwork.graph + self.graph = flow_network.graph self.executed = False def execute(self): @@ -79,95 +79,96 @@ def _algorithm(self): class MaximumFlowAlgorithmExecutor(FlowNetworkAlgorithmExecutor): - def __init__(self, flowNetwork): - super().__init__(flowNetwork) + def __init__(self, flow_network): + super().__init__(flow_network) # use this to save your result - self.maximumFlow = -1 + self.maximum_flow = -1 - def getMaximumFlow(self): + def get_maximum_flow(self): if not self.executed: raise Exception("You should execute algorithm before using its result!") - return self.maximumFlow + return self.maximum_flow class PushRelabelExecutor(MaximumFlowAlgorithmExecutor): - def __init__(self, flowNetwork): - super().__init__(flowNetwork) + def __init__(self, flow_network): + super().__init__(flow_network) - self.preflow = [[0] * self.verticesCount for i in range(self.verticesCount)] + self.preflow = [[0] * self.verticies_count for i in range(self.verticies_count)] - self.heights = [0] * self.verticesCount - self.excesses = [0] * self.verticesCount + self.heights = [0] * self.verticies_count + self.excesses = [0] * self.verticies_count def _algorithm(self): - self.heights[self.sourceIndex] = self.verticesCount + self.heights[self.source_index] = self.verticies_count # push some substance to graph - for nextVertexIndex, bandwidth in enumerate(self.graph[self.sourceIndex]): - self.preflow[self.sourceIndex][nextVertexIndex] += bandwidth - self.preflow[nextVertexIndex][self.sourceIndex] -= bandwidth - self.excesses[nextVertexIndex] += bandwidth + for nextvertex_index, bandwidth in enumerate(self.graph[self.source_index]): + self.preflow[self.source_index][nextvertex_index] += bandwidth + self.preflow[nextvertex_index][self.source_index] -= bandwidth + self.excesses[nextvertex_index] += bandwidth # Relabel-to-front selection rule - verticesList = [ + vertices_list = [ i - for i in range(self.verticesCount) - if i != self.sourceIndex and i != self.sinkIndex + for i in range(self.verticies_count) + if i != self.source_index and i != self.sink_index ] # move through list i = 0 - while i < len(verticesList): - vertexIndex = verticesList[i] - previousHeight = self.heights[vertexIndex] - self.processVertex(vertexIndex) - if self.heights[vertexIndex] > previousHeight: + while i < len(vertices_list): + vertex_index = vertices_list[i] + previous_height = self.heights[vertex_index] + self.process_vertex(vertex_index) + if self.heights[vertex_index] > previous_height: # if it was relabeled, swap elements # and start from 0 index - verticesList.insert(0, verticesList.pop(i)) + vertices_list.insert(0, vertices_list.pop(i)) i = 0 else: i += 1 - self.maximumFlow = sum(self.preflow[self.sourceIndex]) + self.maximum_flow = sum(self.preflow[self.source_index]) - def processVertex(self, vertexIndex): - while self.excesses[vertexIndex] > 0: - for neighbourIndex in range(self.verticesCount): + def process_vertex(self, vertex_index): + while self.excesses[vertex_index] > 0: + for neighbour_index in range(self.verticies_count): # if it's neighbour and current vertex is higher if ( - self.graph[vertexIndex][neighbourIndex] - - self.preflow[vertexIndex][neighbourIndex] + self.graph[vertex_index][neighbour_index] + - self.preflow[vertex_index][neighbour_index] > 0 - and self.heights[vertexIndex] > self.heights[neighbourIndex] + and self.heights[vertex_index] > self.heights[neighbour_index] ): - self.push(vertexIndex, neighbourIndex) + self.push(vertex_index, neighbour_index) - self.relabel(vertexIndex) + self.relabel(vertex_index) - def push(self, fromIndex, toIndex): - preflowDelta = min( - self.excesses[fromIndex], - self.graph[fromIndex][toIndex] - self.preflow[fromIndex][toIndex], + def push(self, from_index, to_index): + preflow_delta = min( + self.excesses[from_index], + self.graph[from_index][to_index] - self.preflow[from_index][to_index], ) - self.preflow[fromIndex][toIndex] += preflowDelta - self.preflow[toIndex][fromIndex] -= preflowDelta - self.excesses[fromIndex] -= preflowDelta - self.excesses[toIndex] += preflowDelta - - def relabel(self, vertexIndex): - minHeight = None - for toIndex in range(self.verticesCount): + self.preflow[from_index][to_index] += preflow_delta + self.preflow[to_index][from_index] -= preflow_delta + self.excesses[from_index] -= preflow_delta + self.excesses[to_index] += preflow_delta + + def relabel(self, vertex_index): + min_height = None + for to_index in range(self.verticies_count): if ( - self.graph[vertexIndex][toIndex] - self.preflow[vertexIndex][toIndex] + self.graph[vertex_index][to_index] + - self.preflow[vertex_index][to_index] > 0 ): - if minHeight is None or self.heights[toIndex] < minHeight: - minHeight = self.heights[toIndex] + if min_height is None or self.heights[to_index] < min_height: + min_height = self.heights[to_index] - if minHeight is not None: - self.heights[vertexIndex] = minHeight + 1 + if min_height is not None: + self.heights[vertex_index] = min_height + 1 if __name__ == "__main__": @@ -184,10 +185,10 @@ def relabel(self, vertexIndex): graph = [[0, 7, 0, 0], [0, 0, 6, 0], [0, 0, 0, 8], [9, 0, 0, 0]] # prepare our network - flowNetwork = FlowNetwork(graph, entrances, exits) + flow_network = FlowNetwork(graph, entrances, exits) # set algorithm - flowNetwork.setMaximumFlowAlgorithm(PushRelabelExecutor) + flow_network.set_maximum_flow_algorithm(PushRelabelExecutor) # and calculate - maximumFlow = flowNetwork.findMaximumFlow() + maximum_flow = flow_network.find_maximum_flow() - print(f"maximum flow is {maximumFlow}") + print(f"maximum flow is {maximum_flow}") diff --git a/graphs/eulerian_path_and_circuit_for_undirected_graph.py b/graphs/eulerian_path_and_circuit_for_undirected_graph.py index fa4f73abd86f..6c43c5d3e6e3 100644 --- a/graphs/eulerian_path_and_circuit_for_undirected_graph.py +++ b/graphs/eulerian_path_and_circuit_for_undirected_graph.py @@ -50,21 +50,21 @@ 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]} - G5 = { + 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) + 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__": diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index 548ce3c54ffe..50081afa6728 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -151,16 +151,16 @@ def create_edge(nodes, graph, cluster, c1): def construct_graph(cluster, nodes): - X = cluster[max(cluster.keys())] + x = cluster[max(cluster.keys())] cluster[max(cluster.keys()) + 1] = "Header" graph = {} - for i in X: + for i in x: if tuple(["Header"]) in graph: - graph[tuple(["Header"])].append(X[i]) + graph[tuple(["Header"])].append(x[i]) else: - graph[tuple(["Header"])] = [X[i]] - for i in X: - graph[tuple(X[i])] = [["Header"]] + 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) @@ -168,7 +168,7 @@ def construct_graph(cluster, nodes): return graph -def myDFS(graph, start, end, path=None): +def my_dfs(graph, start, end, path=None): """ find different DFS walk from given node to Header node """ @@ -177,7 +177,7 @@ def myDFS(graph, start, end, path=None): paths.append(path) for node in graph[start]: if tuple(node) not in path: - myDFS(graph, tuple(node), end, path) + my_dfs(graph, tuple(node), end, path) def find_freq_subgraph_given_support(s, cluster, graph): @@ -186,23 +186,23 @@ def find_freq_subgraph_given_support(s, cluster, graph): """ k = int(s / 100 * (len(cluster) - 1)) for i in cluster[k].keys(): - myDFS(graph, tuple(cluster[k][i]), tuple(["Header"])) + my_dfs(graph, tuple(cluster[k][i]), tuple(["Header"])) def freq_subgraphs_edge_list(paths): """ returns Edge list for frequent subgraphs """ - freq_sub_EL = [] + freq_sub_el = [] for edges in paths: - EL = [] + 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 + el.append(edge) + freq_sub_el.append(el) + return freq_sub_el def preprocess(edge_array): diff --git a/graphs/kahns_algorithm_long.py b/graphs/kahns_algorithm_long.py index fed7517a21e2..776ae3a2f903 100644 --- a/graphs/kahns_algorithm_long.py +++ b/graphs/kahns_algorithm_long.py @@ -1,8 +1,8 @@ # Finding longest distance in Directed Acyclic Graph using KahnsAlgorithm -def longestDistance(graph): +def longest_distance(graph): indegree = [0] * len(graph) queue = [] - longDist = [1] * len(graph) + long_dist = [1] * len(graph) for key, values in graph.items(): for i in values: @@ -17,15 +17,15 @@ def longestDistance(graph): for x in graph[vertex]: indegree[x] -= 1 - if longDist[vertex] + 1 > longDist[x]: - longDist[x] = longDist[vertex] + 1 + if long_dist[vertex] + 1 > long_dist[x]: + long_dist[x] = long_dist[vertex] + 1 if indegree[x] == 0: queue.append(x) - print(max(longDist)) + print(max(long_dist)) # Adjacency list of Graph graph = {0: [2, 3, 4], 1: [2, 7], 2: [5], 3: [5, 7], 4: [7], 5: [6], 6: [7], 7: []} -longestDistance(graph) +longest_distance(graph) diff --git a/graphs/kahns_algorithm_topo.py b/graphs/kahns_algorithm_topo.py index bf9f90299361..6879b047fe35 100644 --- a/graphs/kahns_algorithm_topo.py +++ b/graphs/kahns_algorithm_topo.py @@ -1,4 +1,4 @@ -def topologicalSort(graph): +def topological_sort(graph): """ Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph using BFS @@ -33,4 +33,4 @@ def topologicalSort(graph): # Adjacency List of Graph graph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []} -topologicalSort(graph) +topological_sort(graph) diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 16b4286140ec..9b2c645f16df 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -2,15 +2,15 @@ from collections import defaultdict -def PrimsAlgorithm(l): # noqa: E741 +def prisms_algorithm(l): # noqa: E741 - nodePosition = [] + node_position = [] def get_position(vertex): - return nodePosition[vertex] + return node_position[vertex] def set_position(vertex, pos): - nodePosition[vertex] = pos + node_position[vertex] = pos def top_to_bottom(heap, start, size, positions): if start > size // 2 - 1: @@ -64,44 +64,44 @@ def heapify(heap, positions): for i in range(start, -1, -1): top_to_bottom(heap, i, len(heap), positions) - def deleteMinimum(heap, positions): + def delete_minimum(heap, positions): temp = positions[0] heap[0] = sys.maxsize top_to_bottom(heap, 0, len(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 - Positions = [] + distance_tv = [] # Heap of Distance of vertices from their neighboring vertex + positions = [] for x in range(len(l)): p = sys.maxsize - Distance_TV.append(p) - Positions.append(x) - nodePosition.append(x) + distance_tv.append(p) + positions.append(x) + node_position.append(x) - TreeEdges = [] + tree_edges = [] visited[0] = 1 - Distance_TV[0] = sys.maxsize + distance_tv[0] = sys.maxsize for x in l[0]: - Nbr_TV[x[0]] = 0 - Distance_TV[x[0]] = x[1] - heapify(Distance_TV, Positions) + nbr_tv[x[0]] = 0 + distance_tv[x[0]] = x[1] + heapify(distance_tv, positions) for i in range(1, len(l)): - vertex = deleteMinimum(Distance_TV, Positions) + vertex = delete_minimum(distance_tv, positions) if visited[vertex] == 0: - TreeEdges.append((Nbr_TV[vertex], vertex)) + tree_edges.append((nbr_tv[vertex], vertex)) visited[vertex] = 1 for v in l[vertex]: - 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 + 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 tree_edges if __name__ == "__main__": # pragma: no cover @@ -113,4 +113,4 @@ def deleteMinimum(heap, positions): 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)) + print(prisms_algorithm(adjlist)) diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index 8607f51d8f52..e16a983932d0 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -55,21 +55,21 @@ def get(self): return (priority, item) -def consistent_heuristic(P: TPos, goal: TPos): +def consistent_heuristic(p: TPos, goal: TPos): # euclidean distance - a = np.array(P) + a = np.array(p) b = np.array(goal) return np.linalg.norm(a - b) -def heuristic_2(P: TPos, goal: TPos): +def heuristic_2(p: TPos, goal: TPos): # integer division by time variable - return consistent_heuristic(P, goal) // t + return consistent_heuristic(p, goal) // t -def heuristic_1(P: TPos, goal: TPos): +def heuristic_1(p: TPos, goal: TPos): # manhattan distance - return abs(P[0] - goal[0]) + abs(P[1] - goal[1]) + return abs(p[0] - goal[0]) + abs(p[1] - goal[1]) def key(start: TPos, i: int, goal: TPos, g_function: dict[TPos, float]): diff --git a/graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py index fa182aa2faf1..ea9d35282858 100644 --- a/graphs/scc_kosaraju.py +++ b/graphs/scc_kosaraju.py @@ -2,7 +2,7 @@ def dfs(u): - global graph, reversedGraph, scc, component, visit, stack + global graph, reversed_graph, scc, component, visit, stack if visit[u]: return visit[u] = True @@ -12,17 +12,17 @@ def dfs(u): def dfs2(u): - global graph, reversedGraph, scc, component, visit, stack + global graph, reversed_graph, scc, component, visit, stack if visit[u]: return visit[u] = True component.append(u) - for v in reversedGraph[u]: + for v in reversed_graph[u]: dfs2(v) def kosaraju(): - global graph, reversedGraph, scc, component, visit, stack + global graph, reversed_graph, scc, component, visit, stack for i in range(n): dfs(i) visit = [False] * n @@ -40,12 +40,12 @@ def kosaraju(): n, m = list(map(int, input().strip().split())) graph: list[list[int]] = [[] for i in range(n)] # graph - reversedGraph: list[list[int]] = [[] for i in range(n)] # reversed graph + reversed_graph: list[list[int]] = [[] for i in range(n)] # reversed graph # input graph data (edges) for i in range(m): u, v = list(map(int, input().strip().split())) graph[u].append(v) - reversedGraph[v].append(u) + reversed_graph[v].append(u) stack: list[int] = [] visit: list[bool] = [False] * n diff --git a/graphs/tests/test_min_spanning_tree_prim.py b/graphs/tests/test_min_spanning_tree_prim.py index 048fbf595fa6..91feab28fc81 100644 --- a/graphs/tests/test_min_spanning_tree_prim.py +++ b/graphs/tests/test_min_spanning_tree_prim.py @@ -1,6 +1,6 @@ from collections import defaultdict -from graphs.minimum_spanning_tree_prims import PrimsAlgorithm as mst +from graphs.minimum_spanning_tree_prims import prisms_algorithm as mst def test_prim_successful_result(): diff --git a/hashes/adler32.py b/hashes/adler32.py index 4a61b97e3590..80229f04620a 100644 --- a/hashes/adler32.py +++ b/hashes/adler32.py @@ -20,7 +20,7 @@ def adler32(plain_text: str) -> int: >>> adler32('go adler em all') 708642122 """ - MOD_ADLER = 65521 + MOD_ADLER = 65521 # noqa: N806 a = 1 b = 0 for plain_chr in plain_text: diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index a6d476eb7320..69313fbb2065 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -43,11 +43,11 @@ def pull(): 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 + def xorshift(x, y): + x ^= y >> 13 + y ^= x << 17 + x ^= y >> 5 + return x # Choosing Dynamical Systems (Increment) key = machine_time % m @@ -63,13 +63,13 @@ def xorshift(X, Y): 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)) + x = int(buffer_space[(key + 2) % m] * (10**10)) + y = int(buffer_space[(key - 2) % m] * (10**10)) # Machine Time machine_time += 1 - return xorshift(X, Y) % 0xFFFFFFFF + return xorshift(x, y) % 0xFFFFFFFF def reset(): diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index ac20fe03b3fb..a62d092a172f 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -68,177 +68,177 @@ def text_from_bits(bits, encoding="utf-8", errors="surrogatepass"): # Functions of hamming code------------------------------------------- -def emitterConverter(sizePar, data): +def emitter_converter(size_par, data): """ - :param sizePar: how many parity bits the message must have + :param size_par: 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") + >>> emitter_converter(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): + if size_par + len(data) <= 2**size_par - (len(data) - 1): print("ERROR - size of parity don't match with size of data") exit(0) - dataOut = [] + data_out = [] parity = [] - binPos = [bin(x)[2:] for x in range(1, sizePar + len(data) + 1)] + bin_pos = [bin(x)[2:] for x in range(1, size_par + len(data) + 1)] # sorted information data for the size of the output data - dataOrd = [] + data_ord = [] # data position template + parity - dataOutGab = [] + data_out_gab = [] # parity bit counter - qtdBP = 0 + qtd_bp = 0 # counter position of data bits - contData = 0 + cont_data = 0 - for x in range(1, sizePar + len(data) + 1): + for x in range(1, size_par + len(data) + 1): # Performs a template of bit positions - who should be given, # and who should be parity - if qtdBP < sizePar: + if qtd_bp < size_par: if (np.log(x) / np.log(2)).is_integer(): - dataOutGab.append("P") - qtdBP = qtdBP + 1 + data_out_gab.append("P") + qtd_bp = qtd_bp + 1 else: - dataOutGab.append("D") + data_out_gab.append("D") else: - dataOutGab.append("D") + data_out_gab.append("D") # Sorts the data to the new output size - if dataOutGab[-1] == "D": - dataOrd.append(data[contData]) - contData += 1 + if data_out_gab[-1] == "D": + data_ord.append(data[cont_data]) + cont_data += 1 else: - dataOrd.append(None) + data_ord.append(None) # Calculates parity - qtdBP = 0 # parity bit counter - for bp in range(1, sizePar + 1): + qtd_bp = 0 # parity bit counter + for bp in range(1, size_par + 1): # Bit counter one for a given parity - contBO = 0 + cont_bo = 0 # counter to control the loop reading - contLoop = 0 - for x in dataOrd: + cont_loop = 0 + for x in data_ord: if x is not None: try: - aux = (binPos[contLoop])[-1 * (bp)] + aux = (bin_pos[cont_loop])[-1 * (bp)] except IndexError: aux = "0" if aux == "1": if x == "1": - contBO += 1 - contLoop += 1 - parity.append(contBO % 2) + cont_bo += 1 + cont_loop += 1 + parity.append(cont_bo % 2) - qtdBP += 1 + qtd_bp += 1 # Mount the message - ContBP = 0 # parity bit counter - for x in range(0, sizePar + len(data)): - if dataOrd[x] is None: - dataOut.append(str(parity[ContBP])) - ContBP += 1 + cont_bp = 0 # parity bit counter + for x in range(0, size_par + len(data)): + if data_ord[x] is None: + data_out.append(str(parity[cont_bp])) + cont_bp += 1 else: - dataOut.append(dataOrd[x]) + data_out.append(data_ord[x]) - return dataOut + return data_out -def receptorConverter(sizePar, data): +def receptor_converter(size_par, data): """ - >>> receptorConverter(4, "1111010010111111") + >>> receptor_converter(4, "1111010010111111") (['1', '0', '1', '0', '1', '0', '1', '1', '1', '1', '1', '1'], True) """ # data position template + parity - dataOutGab = [] + data_out_gab = [] # Parity bit counter - qtdBP = 0 + qtd_bp = 0 # Counter p data bit reading - contData = 0 + cont_data = 0 # list of parity received - parityReceived = [] - dataOutput = [] + parity_received = [] + data_output = [] 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 and (np.log(x) / np.log(2)).is_integer(): - dataOutGab.append("P") - qtdBP = qtdBP + 1 + if qtd_bp < size_par and (np.log(x) / np.log(2)).is_integer(): + data_out_gab.append("P") + qtd_bp = qtd_bp + 1 else: - dataOutGab.append("D") + data_out_gab.append("D") # Sorts the data to the new output size - if dataOutGab[-1] == "D": - dataOutput.append(data[contData]) + if data_out_gab[-1] == "D": + data_output.append(data[cont_data]) else: - parityReceived.append(data[contData]) - contData += 1 + parity_received.append(data[cont_data]) + cont_data += 1 # -----------calculates the parity with the data - dataOut = [] + data_out = [] parity = [] - binPos = [bin(x)[2:] for x in range(1, sizePar + len(dataOutput) + 1)] + bin_pos = [bin(x)[2:] for x in range(1, size_par + len(data_output) + 1)] # sorted information data for the size of the output data - dataOrd = [] + data_ord = [] # Data position feedback + parity - dataOutGab = [] + data_out_gab = [] # Parity bit counter - qtdBP = 0 + qtd_bp = 0 # Counter p data bit reading - contData = 0 + cont_data = 0 - for x in range(1, sizePar + len(dataOutput) + 1): + for x in range(1, size_par + len(data_output) + 1): # Performs a template position of bits - who should be given, # and who should be parity - if qtdBP < sizePar and (np.log(x) / np.log(2)).is_integer(): - dataOutGab.append("P") - qtdBP = qtdBP + 1 + if qtd_bp < size_par and (np.log(x) / np.log(2)).is_integer(): + data_out_gab.append("P") + qtd_bp = qtd_bp + 1 else: - dataOutGab.append("D") + data_out_gab.append("D") # Sorts the data to the new output size - if dataOutGab[-1] == "D": - dataOrd.append(dataOutput[contData]) - contData += 1 + if data_out_gab[-1] == "D": + data_ord.append(data_output[cont_data]) + cont_data += 1 else: - dataOrd.append(None) + data_ord.append(None) # Calculates parity - qtdBP = 0 # parity bit counter - for bp in range(1, sizePar + 1): + qtd_bp = 0 # parity bit counter + for bp in range(1, size_par + 1): # Bit counter one for a certain parity - contBO = 0 + cont_bo = 0 # Counter to control loop reading - contLoop = 0 - for x in dataOrd: + cont_loop = 0 + for x in data_ord: if x is not None: try: - aux = (binPos[contLoop])[-1 * (bp)] + aux = (bin_pos[cont_loop])[-1 * (bp)] except IndexError: aux = "0" if aux == "1" and x == "1": - contBO += 1 - contLoop += 1 - parity.append(str(contBO % 2)) + cont_bo += 1 + cont_loop += 1 + parity.append(str(cont_bo % 2)) - qtdBP += 1 + qtd_bp += 1 # Mount the message - ContBP = 0 # Parity bit counter - for x in range(0, sizePar + len(dataOutput)): - if dataOrd[x] is None: - dataOut.append(str(parity[ContBP])) - ContBP += 1 + cont_bp = 0 # Parity bit counter + for x in range(0, size_par + len(data_output)): + if data_ord[x] is None: + data_out.append(str(parity[cont_bp])) + cont_bp += 1 else: - dataOut.append(dataOrd[x]) + data_out.append(data_ord[x]) - ack = parityReceived == parity - return dataOutput, ack + ack = parity_received == parity + return data_output, ack # --------------------------------------------------------------------- diff --git a/hashes/md5.py b/hashes/md5.py index c56c073cc0c7..2020bf2e53bf 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -1,7 +1,7 @@ import math -def rearrange(bitString32): +def rearrange(bit_string_32): """[summary] Regroups the given binary string. @@ -17,21 +17,21 @@ def rearrange(bitString32): 'pqrstuvwhijklmno90abcdfg12345678' """ - if len(bitString32) != 32: + if len(bit_string_32) != 32: raise ValueError("Need length 32") - newString = "" + new_string = "" for i in [3, 2, 1, 0]: - newString += bitString32[8 * i : 8 * i + 8] - return newString + new_string += bit_string_32[8 * i : 8 * i + 8] + return new_string -def reformatHex(i): +def reformat_hex(i): """[summary] Converts the given integer into 8-digit hex number. Arguments: i {[int]} -- [integer] - >>> reformatHex(666) + >>> reformat_hex(666) '9a020000' """ @@ -42,7 +42,7 @@ def reformatHex(i): return thing -def pad(bitString): +def pad(bit_string): """[summary] Fills up the binary string to a 512 bit binary string @@ -52,33 +52,33 @@ def pad(bitString): 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 + start_length = len(bit_string) + bit_string += "1" + while len(bit_string) % 512 != 448: + bit_string += "0" + last_part = format(start_length, "064b") + bit_string += rearrange(last_part[32:]) + rearrange(last_part[:32]) + return bit_string -def getBlock(bitString): +def get_block(bit_string): """[summary] Iterator: Returns by each call a list of length 16 with the 32 bit integer blocks. Arguments: - bitString {[string]} -- [binary string >= 512] + bit_string {[string]} -- [binary string >= 512] """ - currPos = 0 - while currPos < len(bitString): - currPart = bitString[currPos : currPos + 512] - mySplits = [] + curr_pos = 0 + while curr_pos < len(bit_string): + curr_part = bit_string[curr_pos : curr_pos + 512] + my_splits = [] for i in range(16): - mySplits.append(int(rearrange(currPart[32 * i : 32 * i + 32]), 2)) - yield mySplits - currPos += 512 + my_splits.append(int(rearrange(curr_part[32 * i : 32 * i + 32]), 2)) + yield my_splits + curr_pos += 512 def not32(i): @@ -101,7 +101,7 @@ def leftrot32(i, s): return (i << s) ^ (i >> (32 - s)) -def md5me(testString): +def md5me(test_string): """[summary] Returns a 32-bit hash code of the string 'testString' @@ -110,7 +110,7 @@ def md5me(testString): """ bs = "" - for i in testString: + for i in test_string: bs += format(ord(i), "08b") bs = pad(bs) @@ -188,37 +188,37 @@ def md5me(testString): 21, ] - for m in getBlock(bs): - A = a0 - B = b0 - C = c0 - D = d0 + for m in get_block(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)) + f = d ^ (b & (c ^ d)) g = i elif i <= 31: # f = (D & B) | (not32(D) & C) - f = C ^ (D & (B ^ C)) + f = c ^ (d & (b ^ c)) g = (5 * i + 1) % 16 elif i <= 47: - f = B ^ C ^ D + f = b ^ c ^ d g = (3 * i + 5) % 16 else: - f = C ^ (B | not32(D)) + 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) + 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 = reformat_hex(a0) + reformat_hex(b0) + reformat_hex(c0) + reformat_hex(d0) return digest diff --git a/hashes/sha1.py b/hashes/sha1.py index dde1efc557bb..b19e0cfafea3 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -133,7 +133,7 @@ class SHA1HashTest(unittest.TestCase): Test class for the SHA1Hash class. Inherits the TestCase class from unittest """ - def testMatchHashes(self): + def testMatchHashes(self): # noqa: N802 msg = bytes("Test String", "utf-8") self.assertEqual(SHA1Hash(msg).final_hash(), hashlib.sha1(msg).hexdigest()) diff --git a/hashes/sha256.py b/hashes/sha256.py index 9d4f250fe353..98f7c096e3b6 100644 --- a/hashes/sha256.py +++ b/hashes/sha256.py @@ -157,14 +157,14 @@ def final_hash(self) -> None: ) % 0x100000000 # Compression - S1 = self.ror(e, 6) ^ self.ror(e, 11) ^ self.ror(e, 25) + s1 = self.ror(e, 6) ^ self.ror(e, 11) ^ self.ror(e, 25) ch = (e & f) ^ ((~e & (0xFFFFFFFF)) & g) temp1 = ( - h + S1 + ch + self.round_constants[index] + words[index] + h + s1 + ch + self.round_constants[index] + words[index] ) % 0x100000000 - S0 = self.ror(a, 2) ^ self.ror(a, 13) ^ self.ror(a, 22) + s0 = self.ror(a, 2) ^ self.ror(a, 13) ^ self.ror(a, 22) maj = (a & b) ^ (a & c) ^ (b & c) - temp2 = (S0 + maj) % 0x100000000 + temp2 = (s0 + maj) % 0x100000000 h, g, f, e, d, c, b, a = ( g, diff --git a/linear_algebra/src/power_iteration.py b/linear_algebra/src/power_iteration.py index 4b866331b8e3..24fbd9a5e002 100644 --- a/linear_algebra/src/power_iteration.py +++ b/linear_algebra/src/power_iteration.py @@ -63,8 +63,8 @@ def power_iteration( vector = w / np.linalg.norm(w) # Find rayleigh quotient # (faster than usual b/c we know vector is normalized already) - vectorH = vector.conj().T if is_complex else vector.T - lambda_ = np.dot(vectorH, np.dot(input_matrix, vector)) + vector_h = vector.conj().T if is_complex else vector.T + lambda_ = np.dot(vector_h, np.dot(input_matrix, vector)) # Check convergence. error = np.abs(lambda_ - lambda_previous) / lambda_ diff --git a/linear_algebra/src/rayleigh_quotient.py b/linear_algebra/src/rayleigh_quotient.py index 78083aa755f1..4773429cbf1b 100644 --- a/linear_algebra/src/rayleigh_quotient.py +++ b/linear_algebra/src/rayleigh_quotient.py @@ -26,7 +26,7 @@ def is_hermitian(matrix: np.ndarray) -> bool: return np.array_equal(matrix, matrix.conjugate().T) -def rayleigh_quotient(A: np.ndarray, v: np.ndarray) -> Any: +def rayleigh_quotient(a: np.ndarray, v: np.ndarray) -> Any: """ Returns the Rayleigh quotient of a Hermitian matrix A and vector v. @@ -45,20 +45,20 @@ def rayleigh_quotient(A: np.ndarray, v: np.ndarray) -> Any: array([[3.]]) """ v_star = v.conjugate().T - v_star_dot = v_star.dot(A) + v_star_dot = v_star.dot(a) assert isinstance(v_star_dot, np.ndarray) return (v_star_dot.dot(v)) / (v_star.dot(v)) def tests() -> None: - A = np.array([[2, 2 + 1j, 4], [2 - 1j, 3, 1j], [4, -1j, 1]]) + 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)) + assert is_hermitian(a), f"{a} is not hermitian." + print(rayleigh_quotient(a, v)) - 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) + 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) if __name__ == "__main__": diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 724ceef2599a..97c06cb44e15 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -85,13 +85,13 @@ def test_mul(self) -> None: self.assertEqual(str(x * 3.0), "(3.0,6.0,9.0)") self.assertEqual((a * b), 0) - def test_zeroVector(self) -> None: + def test_zero_vector(self) -> None: """ test for global function zero_vector() """ self.assertTrue(str(zero_vector(10)).count("0") == 10) - def test_unitBasisVector(self) -> None: + def test_unit_basis_vector(self) -> None: """ test for global function unit_basis_vector() """ @@ -113,7 +113,7 @@ def test_copy(self) -> None: y = x.copy() self.assertEqual(str(x), str(y)) - def test_changeComponent(self) -> None: + def test_change_component(self) -> None: """ test for method change_component() """ @@ -126,77 +126,77 @@ def test_str_matrix(self) -> None: """ test for Matrix method str() """ - 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_minor(self) -> None: """ test for Matrix method minor() """ - A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + a = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) minors = [[-3, -14, -10], [-5, -10, -5], [-2, -1, 0]] - for x in range(A.height()): - for y in range(A.width()): - self.assertEqual(minors[x][y], A.minor(x, y)) + for x in range(a.height()): + for y in range(a.width()): + self.assertEqual(minors[x][y], a.minor(x, y)) def test_cofactor(self) -> None: """ test for Matrix method cofactor() """ - A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + a = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) cofactors = [[-3, 14, -10], [5, -10, 5], [-2, 1, 0]] - for x in range(A.height()): - for y in range(A.width()): - self.assertEqual(cofactors[x][y], A.cofactor(x, y)) + for x in range(a.height()): + for y in range(a.width()): + self.assertEqual(cofactors[x][y], a.cofactor(x, y)) def test_determinant(self) -> None: """ test for Matrix method determinant() """ - A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) - self.assertEqual(-5, A.determinant()) + a = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + self.assertEqual(-5, a.determinant()) def test__mul__matrix(self) -> None: """ test for Matrix * operator """ - A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 3, 3) + 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)) + 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_change_component_matrix(self) -> None: """ test for Matrix method change_component() """ - A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) - A.change_component(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.change_component(0, 2, 5) + self.assertEqual("|1,2,5|\n|2,4,5|\n|6,7,8|\n", str(a)) def test_component_matrix(self) -> None: """ test for Matrix method component() """ - 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) -> None: """ test for Matrix + operator """ - 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) -> None: """ test for Matrix - operator """ - 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) -> None: + def test_square_zero_matrix(self) -> None: """ test for global function square_zero_matrix() """ diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index ace6fb0fa883..4a86e5322a27 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -6,7 +6,7 @@ import numpy as np -class Decision_Tree: +class DecisionTree: def __init__(self, depth=5, min_leaf_size=5): self.depth = depth self.decision_boundary = 0 @@ -22,17 +22,17 @@ def mean_squared_error(self, labels, prediction): @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() + >>> tester = DecisionTree() >>> test_labels = np.array([1,2,3,4,5,6,7,8,9,10]) >>> test_prediction = np.float(6) >>> tester.mean_squared_error(test_labels, test_prediction) == ( - ... Test_Decision_Tree.helper_mean_squared_error_test(test_labels, + ... TestDecisionTree.helper_mean_squared_error_test(test_labels, ... test_prediction)) True >>> test_labels = np.array([1,2,3]) >>> test_prediction = np.float(2) >>> tester.mean_squared_error(test_labels, test_prediction) == ( - ... Test_Decision_Tree.helper_mean_squared_error_test(test_labels, + ... TestDecisionTree.helper_mean_squared_error_test(test_labels, ... test_prediction)) True """ @@ -41,10 +41,10 @@ def mean_squared_error(self, labels, prediction): return np.mean((labels - prediction) ** 2) - def train(self, X, y): + def train(self, x, y): """ train: - @param X: a one dimensional numpy array + @param x: a one dimensional numpy array @param y: a one dimensional numpy array. The contents of y are the labels for the corresponding X values @@ -55,17 +55,17 @@ def train(self, X, y): this section is to check that the inputs conform to our dimensionality constraints """ - if X.ndim != 1: + if x.ndim != 1: print("Error: Input data set must be one dimensional") return - if len(X) != len(y): + if len(x) != len(y): print("Error: X and y have different lengths") return if y.ndim != 1: print("Error: Data set labels must be one dimensional") return - if len(X) < 2 * self.min_leaf_size: + if len(x) < 2 * self.min_leaf_size: self.prediction = np.mean(y) return @@ -74,7 +74,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. @@ -82,34 +82,34 @@ def train(self, X, y): 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: + for i in range(len(x)): + if len(x[:i]) < self.min_leaf_size: continue - elif len(X[i:]) < self.min_leaf_size: + elif len(x[i:]) < self.min_leaf_size: continue else: - error_left = self.mean_squared_error(X[:i], np.mean(y[:i])) - error_right = self.mean_squared_error(X[i:], np.mean(y[i:])) + error_left = self.mean_squared_error(x[:i], np.mean(y[:i])) + error_right = self.mean_squared_error(x[i:], np.mean(y[i:])) error = error_left + error_right if error < min_error: best_split = i min_error = error if best_split != 0: - left_X = X[:best_split] + left_x = x[:best_split] left_y = y[:best_split] - right_X = X[best_split:] + right_x = x[best_split:] right_y = y[best_split:] - self.decision_boundary = X[best_split] - self.left = Decision_Tree( + self.decision_boundary = x[best_split] + self.left = DecisionTree( depth=self.depth - 1, min_leaf_size=self.min_leaf_size ) - self.right = Decision_Tree( + self.right = DecisionTree( 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) + self.left.train(left_x, left_y) + self.right.train(right_x, right_y) else: self.prediction = np.mean(y) @@ -134,7 +134,7 @@ def predict(self, x): return None -class Test_Decision_Tree: +class TestDecisionTree: """Decision Tres test class""" @staticmethod @@ -159,11 +159,11 @@ def main(): 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) + 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 = DecisionTree(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]) diff --git a/machine_learning/gaussian_naive_bayes.py b/machine_learning/gaussian_naive_bayes.py index c200aa5a4d2d..77e7326626c4 100644 --- a/machine_learning/gaussian_naive_bayes.py +++ b/machine_learning/gaussian_naive_bayes.py @@ -17,19 +17,19 @@ def main(): iris = load_iris() # Split dataset into train and test data - X = iris["data"] # features - Y = iris["target"] + 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 + x, y, test_size=0.3, random_state=1 ) # Gaussian Naive Bayes - NB_model = GaussianNB() - NB_model.fit(x_train, y_train) + nb_model = GaussianNB() + nb_model.fit(x_train, y_train) # Display Confusion Matrix plot_confusion_matrix( - NB_model, + nb_model, x_test, y_test, display_labels=iris["target_names"], diff --git a/machine_learning/gradient_boosting_regressor.py b/machine_learning/gradient_boosting_regressor.py index c73e30680a67..c082f3cafe10 100644 --- a/machine_learning/gradient_boosting_regressor.py +++ b/machine_learning/gradient_boosting_regressor.py @@ -26,25 +26,25 @@ def main(): print(df_boston.describe().T) # Feature selection - X = df_boston.iloc[:, :-1] + 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 + 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) + 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) + 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) + y_pred = model.predict(x_test) # The mean squared error print(f"Mean squared error: {mean_squared_error(y_test, y_pred):.2f}") diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 60450b7f8493..5dc2b7118b56 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -69,8 +69,8 @@ def get_initial_centroids(data, k, seed=None): 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): @@ -197,8 +197,8 @@ def kmeans( plot_heterogeneity(heterogeneity, k) -def ReportGenerator( - df: pd.DataFrame, ClusteringVariables: np.ndarray, FillMissingReport=None +def report_generator( + df: pd.DataFrame, clustering_variables: np.ndarray, fill_missing_report=None ) -> pd.DataFrame: """ Function generates easy-erading clustering report. It takes 2 arguments as an input: @@ -214,7 +214,7 @@ def ReportGenerator( >>> data['col2'] = [100, 200, 300] >>> data['col3'] = [10, 20, 30] >>> data['Cluster'] = [1, 1, 2] - >>> ReportGenerator(data, ['col1', 'col2'], 0) + >>> report_generator(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 @@ -231,8 +231,8 @@ def ReportGenerator( [104 rows x 5 columns] """ # Fill missing values with given rules - if FillMissingReport: - df.fillna(value=FillMissingReport, inplace=True) + if fill_missing_report: + df.fillna(value=fill_missing_report, inplace=True) df["dummy"] = 1 numeric_cols = df.select_dtypes(np.number).columns report = ( @@ -313,7 +313,7 @@ def ReportGenerator( report = pd.concat( [report, a, clustersize, clusterproportion], axis=0 ) # concat report with clustert size and nan values - report["Mark"] = report["Features"].isin(ClusteringVariables) + report["Mark"] = report["Features"].isin(clustering_variables) cols = report.columns.tolist() cols = cols[0:2] + cols[-1:] + cols[2:-1] report = report[cols] diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index db6868687661..6c542ab825aa 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -41,11 +41,11 @@ def local_weight( [0.08272556]]) """ weight = weighted_matrix(point, training_data_x, bandwidth) - W = (training_data_x.T * (weight * training_data_x)).I * ( + w = (training_data_x.T * (weight * training_data_x)).I * ( training_data_x.T * weight * training_data_y.T ) - return W + return w def local_weight_regression( diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 48d88ef61185..87bc8f6681cc 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -35,25 +35,25 @@ 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))) +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, max_iterations=70000): - theta = np.zeros(X.shape[1]) +def logistic_reg(alpha, x, y, max_iterations=70000): + theta = np.zeros(x.shape[1]) for iterations in range(max_iterations): - z = np.dot(X, theta) + z = np.dot(x, theta) h = sigmoid_function(z) - gradient = np.dot(X.T, h - y) / y.size + gradient = np.dot(x.T, h - y) / y.size theta = theta - alpha * gradient # updating the weights - z = np.dot(X, theta) + z = np.dot(x, theta) h = sigmoid_function(z) - J = cost_function(h, y) + 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 @@ -61,23 +61,23 @@ def logistic_reg(alpha, X, y, max_iterations=70000): if __name__ == "__main__": iris = datasets.load_iris() - X = iris.data[:, :2] + x = iris.data[:, :2] 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) print("theta: ", theta) # printing the theta i.e our weights vector - def predict_prob(X): + def predict_prob(x): return sigmoid_function( - np.dot(X, theta) + 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") - (x1_min, x1_max) = (X[:, 0].min(), X[:, 0].max()) - (x2_min, x2_max) = (X[:, 1].min(), X[:, 1].max()) + 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)) grid = np.c_[xx1.ravel(), xx2.ravel()] probs = predict_prob(grid).reshape(xx1.shape) diff --git a/machine_learning/multilayer_perceptron_classifier.py b/machine_learning/multilayer_perceptron_classifier.py index 604185cef677..e99a4131e972 100644 --- a/machine_learning/multilayer_perceptron_classifier.py +++ b/machine_learning/multilayer_perceptron_classifier.py @@ -15,12 +15,12 @@ Y = clf.predict(test) -def wrapper(Y): +def wrapper(y): """ >>> wrapper(Y) [0, 0, 1] """ - return list(Y) + return list(y) if __name__ == "__main__": diff --git a/machine_learning/random_forest_classifier.py b/machine_learning/random_forest_classifier.py index 6370254090f7..3267fa209660 100644 --- a/machine_learning/random_forest_classifier.py +++ b/machine_learning/random_forest_classifier.py @@ -17,10 +17,10 @@ def main(): iris = load_iris() # Split dataset into train and test data - X = iris["data"] # features - Y = iris["target"] + 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 + x, y, test_size=0.3, random_state=1 ) # Random Forest Classifier diff --git a/machine_learning/random_forest_regressor.py b/machine_learning/random_forest_regressor.py index 0aade626b038..1001931a109d 100644 --- a/machine_learning/random_forest_regressor.py +++ b/machine_learning/random_forest_regressor.py @@ -17,10 +17,10 @@ def main(): print(boston.keys()) # Split dataset into train and test data - X = boston["data"] # features - Y = boston["target"] + 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 + x, y, test_size=0.3, random_state=1 ) # Random Forest Regressor diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index cc7868d0fd8e..fb4b35f31289 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -80,7 +80,7 @@ def __init__( # Calculate alphas using SMO algorithm def fit(self): - K = self._k + k = self._k state = None while True: @@ -106,14 +106,14 @@ def fit(self): # 3: update threshold(b) b1_new = np.float64( -e1 - - y1 * K(i1, i1) * (a1_new - a1) - - y2 * K(i2, i1) * (a2_new - a2) + - 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) + - y2 * k(i2, i2) * (a2_new - a2) + - y1 * k(i1, i2) * (a1_new - a1) + self._b ) if 0.0 < a1_new < self._c: @@ -134,8 +134,8 @@ def fit(self): if s == i1 or s == i2: continue self._error[s] += ( - y1 * (a1_new - a1) * K(i1, s) - + y2 * (a2_new - a2) * K(i2, s) + y1 * (a1_new - a1) * k(i1, s) + + y2 * (a2_new - a2) * k(i2, s) + (self._b - b_old) ) @@ -305,56 +305,56 @@ def _choose_a2(self, i1): # Get the new alpha2 and new alpha1 def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): - K = self._k + 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) + 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: + l, h = max(0.0, a2 + a1 - self._c), min(self._c, a2 + a1) + if l == h: # noqa: E741 return None, None # calculate eta - k11 = K(i1, i1) - k22 = K(i2, i2) - k12 = K(i1, i2) + 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 boundary - if a2_new_unc >= H: - a2_new = H - elif a2_new_unc <= L: - a2_new = L + 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) + 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) + 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) + + 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) + + h * f2 + + 1 / 2 * h1**2 * k(i1, i1) + + 1 / 2 * h**2 * k(i2, i2) + + s * h * h1 * k(i1, i2) ) """ # way 2 @@ -362,9 +362,9 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): objectives """ if ol < (oh - self._eps): - a2_new = L + a2_new = l elif ol > oh + self._eps: - a2_new = H + a2_new = h else: a2_new = a2 diff --git a/machine_learning/word_frequency_functions.py b/machine_learning/word_frequency_functions.py index 3e8faf39cf07..8fd2741f611c 100644 --- a/machine_learning/word_frequency_functions.py +++ b/machine_learning/word_frequency_functions.py @@ -83,7 +83,7 @@ def document_frequency(term: str, corpus: str) -> tuple[int, int]: return (len([doc for doc in docs if term in doc]), len(docs)) -def inverse_document_frequency(df: int, N: int, smoothing=False) -> 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 @@ -109,15 +109,15 @@ def inverse_document_frequency(df: int, N: int, smoothing=False) -> float: 1.477 """ if smoothing: - if N == 0: + if n == 0: raise ValueError("log10(0) is undefined.") - return round(1 + log10(N / (1 + df)), 3) + return round(1 + log10(n / (1 + df)), 3) if df == 0: raise ZeroDivisionError("df must be > 0") - elif N == 0: + elif n == 0: raise ValueError("log10(0) is undefined.") - return round(log10(N / df), 3) + return round(log10(n / df), 3) def tf_idf(tf: int, idf: int) -> float: diff --git a/maths/binomial_coefficient.py b/maths/binomial_coefficient.py index 4def041492f3..0d4b3d1a8d9a 100644 --- a/maths/binomial_coefficient.py +++ b/maths/binomial_coefficient.py @@ -5,16 +5,16 @@ def binomial_coefficient(n, r): >>> binomial_coefficient(10, 5) 252 """ - C = [0 for i in range(r + 1)] + c = [0 for i in range(r + 1)] # nc0 = 1 - C[0] = 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] + c[j] += c[j - 1] j -= 1 - return C[r] + return c[r] print(binomial_coefficient(n=10, r=5)) diff --git a/maths/carmichael_number.py b/maths/carmichael_number.py index 09a4fedfb763..c9c144759246 100644 --- a/maths/carmichael_number.py +++ b/maths/carmichael_number.py @@ -30,7 +30,7 @@ def power(x: int, y: int, mod: int) -> int: return temp -def isCarmichaelNumber(n: int) -> bool: +def is_carmichael_number(n: int) -> bool: b = 2 while b < n: if gcd(b, n) == 1 and power(b, n - 1, n) != 1: @@ -41,7 +41,7 @@ def isCarmichaelNumber(n: int) -> bool: if __name__ == "__main__": number = int(input("Enter number: ").strip()) - if isCarmichaelNumber(number): + if is_carmichael_number(number): print(f"{number} is a Carmichael Number.") else: print(f"{number} is not a Carmichael Number.") diff --git a/maths/decimal_isolate.py b/maths/decimal_isolate.py index 0e3967a4671d..1b8f6cbcad89 100644 --- a/maths/decimal_isolate.py +++ b/maths/decimal_isolate.py @@ -4,7 +4,7 @@ """ -def decimal_isolate(number, digitAmount): +def decimal_isolate(number, digit_amount): """ Isolates the decimal part of a number. @@ -28,8 +28,8 @@ def decimal_isolate(number, digitAmount): >>> decimal_isolate(-14.123, 3) -0.123 """ - if digitAmount > 0: - return round(number - int(number), digitAmount) + if digit_amount > 0: + return round(number - int(number), digit_amount) return number - int(number) diff --git a/maths/euler_method.py b/maths/euler_method.py index af7eecb2ff29..30f193e6daa5 100644 --- a/maths/euler_method.py +++ b/maths/euler_method.py @@ -29,12 +29,12 @@ def explicit_euler( >>> y[-1] 144.77277243257308 """ - N = int(np.ceil((x_end - x0) / step_size)) - y = np.zeros((N + 1,)) + n = int(np.ceil((x_end - x0) / step_size)) + y = np.zeros((n + 1,)) y[0] = y0 x = x0 - for k in range(N): + for k in range(n): y[k + 1] = y[k] + step_size * ode_func(x, y[k]) x += step_size diff --git a/maths/euler_modified.py b/maths/euler_modified.py index 5659fa063fc4..14bddadf4c53 100644 --- a/maths/euler_modified.py +++ b/maths/euler_modified.py @@ -33,12 +33,12 @@ def euler_modified( >>> y[-1] 0.5525976431951775 """ - N = int(np.ceil((x_end - x0) / step_size)) - y = np.zeros((N + 1,)) + n = int(np.ceil((x_end - x0) / step_size)) + y = np.zeros((n + 1,)) y[0] = y0 x = x0 - for k in range(N): + for k in range(n): y_get = y[k] + step_size * ode_func(x, y[k]) y[k + 1] = y[k] + ( (step_size / 2) * (ode_func(x, y[k]) + ode_func(x + step_size, y_get)) diff --git a/maths/hardy_ramanujanalgo.py b/maths/hardy_ramanujanalgo.py index e36f763da19e..6929533fc389 100644 --- a/maths/hardy_ramanujanalgo.py +++ b/maths/hardy_ramanujanalgo.py @@ -4,9 +4,9 @@ import math -def exactPrimeFactorCount(n): +def exact_prime_factor_count(n): """ - >>> exactPrimeFactorCount(51242183) + >>> exact_prime_factor_count(51242183) 3 """ count = 0 @@ -36,7 +36,7 @@ def exactPrimeFactorCount(n): if __name__ == "__main__": n = 51242183 - print(f"The number of distinct prime factors is/are {exactPrimeFactorCount(n)}") + print(f"The number of distinct prime factors is/are {exact_prime_factor_count(n)}") print(f"The value of log(log(n)) is {math.log(math.log(n)):.4f}") """ diff --git a/maths/jaccard_similarity.py b/maths/jaccard_similarity.py index 4f24d308f340..77f4b90ea79f 100644 --- a/maths/jaccard_similarity.py +++ b/maths/jaccard_similarity.py @@ -14,7 +14,7 @@ """ -def jaccard_similariy(setA, setB, alternativeUnion=False): +def jaccard_similariy(set_a, set_b, alternative_union=False): """ Finds the jaccard similarity between two sets. Essentially, its intersection over union. @@ -24,8 +24,8 @@ def jaccard_similariy(setA, setB, alternativeUnion=False): 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 + :set_a (set,list,tuple): A non-empty set/list + :set_b (set,list,tuple): A non-empty set/list :alternativeUnion (boolean): If True, use sum of number of items as union @@ -33,48 +33,48 @@ def jaccard_similariy(setA, setB, alternativeUnion=False): (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) + >>> set_a = {'a', 'b', 'c', 'd', 'e'} + >>> set_b = {'c', 'd', 'e', 'f', 'h', 'i'} + >>> jaccard_similariy(set_a, set_b) 0.375 - >>> jaccard_similariy(setA,setA) + >>> jaccard_similariy(set_a, set_a) 1.0 - >>> jaccard_similariy(setA,setA,True) + >>> jaccard_similariy(set_a, set_a, True) 0.5 - >>> setA = ['a', 'b', 'c', 'd', 'e'] - >>> setB = ('c', 'd', 'e', 'f', 'h', 'i') - >>> jaccard_similariy(setA,setB) + >>> set_a = ['a', 'b', 'c', 'd', 'e'] + >>> set_b = ('c', 'd', 'e', 'f', 'h', 'i') + >>> jaccard_similariy(set_a, set_b) 0.375 """ - if isinstance(setA, set) and isinstance(setB, set): + if isinstance(set_a, set) and isinstance(set_b, set): - intersection = len(setA.intersection(setB)) + intersection = len(set_a.intersection(set_b)) - if alternativeUnion: - union = len(setA) + len(setB) + if alternative_union: + union = len(set_a) + len(set_b) else: - union = len(setA.union(setB)) + union = len(set_a.union(set_b)) return intersection / union - if isinstance(setA, (list, tuple)) and isinstance(setB, (list, tuple)): + if isinstance(set_a, (list, tuple)) and isinstance(set_b, (list, tuple)): - intersection = [element for element in setA if element in setB] + intersection = [element for element in set_a if element in set_b] - if alternativeUnion: - union = len(setA) + len(setB) + if alternative_union: + union = len(set_a) + len(set_b) else: - union = setA + [element for element in setB if element not in setA] + union = set_a + [element for element in set_b if element not in set_a] 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)) + set_a = {"a", "b", "c", "d", "e"} + set_b = {"c", "d", "e", "f", "h", "i"} + print(jaccard_similariy(set_a, set_b)) diff --git a/maths/krishnamurthy_number.py b/maths/krishnamurthy_number.py index c88f68a07f27..c1d8a8fc5f56 100644 --- a/maths/krishnamurthy_number.py +++ b/maths/krishnamurthy_number.py @@ -33,12 +33,12 @@ def krishnamurthy(number: int) -> bool: True """ - factSum = 0 + fact_sum = 0 duplicate = number while duplicate > 0: duplicate, digit = divmod(duplicate, 10) - factSum += factorial(digit) - return factSum == number + fact_sum += factorial(digit) + return fact_sum == number if __name__ == "__main__": diff --git a/maths/kth_lexicographic_permutation.py b/maths/kth_lexicographic_permutation.py index 23eab626fbf8..b85558aca6d4 100644 --- a/maths/kth_lexicographic_permutation.py +++ b/maths/kth_lexicographic_permutation.py @@ -1,17 +1,17 @@ -def kthPermutation(k, n): +def kth_permutation(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) + >>> kth_permutation(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) + >>> kth_permutation(10,4) [1, 3, 0, 2] """ # Factorails from 1! to (n-1)! diff --git a/maths/lucas_lehmer_primality_test.py b/maths/lucas_lehmer_primality_test.py index 15e25cbfe996..916abfcc175e 100644 --- a/maths/lucas_lehmer_primality_test.py +++ b/maths/lucas_lehmer_primality_test.py @@ -30,9 +30,9 @@ def lucas_lehmer_test(p: int) -> bool: return True s = 4 - M = (1 << p) - 1 + m = (1 << p) - 1 for i in range(p - 2): - s = ((s * s) - 2) % M + s = ((s * s) - 2) % m return s == 0 diff --git a/maths/primelib.py b/maths/primelib.py index 3da9c56f66d6..7d2a22f39c59 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -8,27 +8,27 @@ Overview: -isPrime(number) -sieveEr(N) -getPrimeNumbers(N) -primeFactorization(number) -greatestPrimeFactor(number) -smallestPrimeFactor(number) -getPrime(n) -getPrimesBetween(pNumber1, pNumber2) +is_prime(number) +sieve_er(N) +get_prime_numbers(N) +prime_factorization(number) +greatest_prime_factor(number) +smallest_prime_factor(number) +get_prime(n) +get_primes_between(pNumber1, pNumber2) ---- -isEven(number) -isOdd(number) +is_even(number) +is_odd(number) gcd(number1, number2) // greatest common divisor -kgV(number1, number2) // least common multiple -getDivisors(number) // all divisors of 'number' inclusive 1, number -isPerfectNumber(number) +kg_v(number1, number2) // least common multiple +get_divisors(number) // all divisors of 'number' inclusive 1, number +is_perfect_number(number) NEW-FUNCTIONS -simplifyFraction(numerator, denominator) +simplify_fraction(numerator, denominator) factorial (n) // n! fib (n) // calculate the n-th fibonacci term. @@ -75,7 +75,7 @@ def is_prime(number: int) -> bool: # ------------------------------------------ -def sieveEr(N): +def sieve_er(n): """ input: positive integer 'N' > 2 returns a list of prime numbers from 2 up to N. @@ -86,23 +86,23 @@ 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: contains all natural numbers from 2 up to N - beginList = [x for x in range(2, N + 1)] + begin_list = [x for x in range(2, n + 1)] ans = [] # this list will be returns. # actual sieve of erathostenes - for i in range(len(beginList)): + for i in range(len(begin_list)): - for j in range(i + 1, len(beginList)): + for j in range(i + 1, len(begin_list)): - if (beginList[i] != 0) and (beginList[j] % beginList[i] == 0): - beginList[j] = 0 + if (begin_list[i] != 0) and (begin_list[j] % begin_list[i] == 0): + begin_list[j] = 0 # filters actual prime numbers. - ans = [x for x in beginList if x != 0] + ans = [x for x in begin_list if x != 0] # precondition assert isinstance(ans, list), "'ans' must been from type list" @@ -113,7 +113,7 @@ def sieveEr(N): # -------------------------------- -def getPrimeNumbers(N): +def get_prime_numbers(n): """ input: positive integer 'N' > 2 returns a list of prime numbers from 2 up to N (inclusive) @@ -121,13 +121,13 @@ 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 is_prime(number): @@ -142,7 +142,7 @@ def getPrimeNumbers(N): # ----------------------------------------- -def primeFactorization(number): +def prime_factorization(number): """ input: positive integer 'number' returns a list of the prime number factors of 'number' @@ -186,7 +186,7 @@ def primeFactorization(number): # ----------------------------------------- -def greatestPrimeFactor(number): +def greatest_prime_factor(number): """ input: positive integer 'number' >= 0 returns the greatest prime number factor of 'number' @@ -200,9 +200,9 @@ def greatestPrimeFactor(number): ans = 0 # prime factorization of 'number' - primeFactors = primeFactorization(number) + prime_factors = prime_factorization(number) - ans = max(primeFactors) + ans = max(prime_factors) # precondition assert isinstance(ans, int), "'ans' must been from type int" @@ -213,7 +213,7 @@ def greatestPrimeFactor(number): # ---------------------------------------------- -def smallestPrimeFactor(number): +def smallest_prime_factor(number): """ input: integer 'number' >= 0 returns the smallest prime number factor of 'number' @@ -227,9 +227,9 @@ def smallestPrimeFactor(number): ans = 0 # prime factorization of 'number' - primeFactors = primeFactorization(number) + prime_factors = prime_factorization(number) - ans = min(primeFactors) + ans = min(prime_factors) # precondition assert isinstance(ans, int), "'ans' must been from type int" @@ -240,7 +240,7 @@ def smallestPrimeFactor(number): # ---------------------- -def isEven(number): +def is_even(number): """ input: integer 'number' returns true if 'number' is even, otherwise false. @@ -256,7 +256,7 @@ def isEven(number): # ------------------------ -def isOdd(number): +def is_odd(number): """ input: integer 'number' returns true if 'number' is odd, otherwise false. @@ -281,14 +281,14 @@ def goldbach(number): # precondition assert ( - isinstance(number, int) and (number > 2) and isEven(number) + isinstance(number, int) and (number > 2) and is_even(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) + prime_numbers = get_prime_numbers(number) + len_pn = len(prime_numbers) # run variable for while-loops. i = 0 @@ -297,16 +297,16 @@ def goldbach(number): # exit variable. for break up the loops loop = True - while i < lenPN and loop: + while i < len_pn and loop: j = i + 1 - while j < lenPN and loop: + while j < len_pn and loop: - if primeNumbers[i] + primeNumbers[j] == number: + if prime_numbers[i] + prime_numbers[j] == number: loop = False - ans.append(primeNumbers[i]) - ans.append(primeNumbers[j]) + ans.append(prime_numbers[i]) + ans.append(prime_numbers[j]) j += 1 @@ -361,7 +361,7 @@ def gcd(number1, number2): # ---------------------------------------------------- -def kgV(number1, number2): +def kg_v(number1, number2): """ Least common multiple input: two positive integer 'number1' and 'number2' @@ -382,13 +382,13 @@ def kgV(number1, number2): if number1 > 1 and number2 > 1: # builds the prime factorization of 'number1' and 'number2' - primeFac1 = primeFactorization(number1) - primeFac2 = primeFactorization(number2) + prime_fac_1 = prime_factorization(number1) + prime_fac_2 = prime_factorization(number2) elif number1 == 1 or number2 == 1: - primeFac1 = [] - primeFac2 = [] + prime_fac_1 = [] + prime_fac_2 = [] ans = max(number1, number2) count1 = 0 @@ -397,21 +397,21 @@ def kgV(number1, number2): done = [] # captured numbers int both 'primeFac1' and 'primeFac2' # iterates through primeFac1 - for n in primeFac1: + for n in prime_fac_1: if n not in done: - if n in primeFac2: + if n in prime_fac_2: - count1 = primeFac1.count(n) - count2 = primeFac2.count(n) + count1 = prime_fac_1.count(n) + count2 = prime_fac_2.count(n) for i in range(max(count1, count2)): ans *= n else: - count1 = primeFac1.count(n) + count1 = prime_fac_1.count(n) for i in range(count1): ans *= n @@ -419,11 +419,11 @@ def kgV(number1, number2): done.append(n) # iterates through primeFac2 - for n in primeFac2: + for n in prime_fac_2: if n not in done: - count2 = primeFac2.count(n) + count2 = prime_fac_2.count(n) for i in range(count2): ans *= n @@ -441,7 +441,7 @@ def kgV(number1, number2): # ---------------------------------- -def getPrime(n): +def get_prime(n): """ Gets the n-th prime number. input: positive integer 'n' >= 0 @@ -476,7 +476,7 @@ def getPrime(n): # --------------------------------------------------- -def getPrimesBetween(pNumber1, pNumber2): +def get_primes_between(p_number_1, p_number_2): """ input: prime numbers 'pNumber1' and 'pNumber2' pNumber1 < pNumber2 @@ -486,10 +486,10 @@ def getPrimesBetween(pNumber1, pNumber2): # precondition assert ( - is_prime(pNumber1) and is_prime(pNumber2) and (pNumber1 < pNumber2) + is_prime(p_number_1) and is_prime(p_number_2) and (p_number_1 < p_number_2) ), "The arguments must been prime numbers and 'pNumber1' < 'pNumber2'" - number = pNumber1 + 1 # jump to the next number + number = p_number_1 + 1 # jump to the next number ans = [] # this list will be returns. @@ -498,7 +498,7 @@ def getPrimesBetween(pNumber1, pNumber2): while not is_prime(number): number += 1 - while number < pNumber2: + while number < p_number_2: ans.append(number) @@ -510,7 +510,9 @@ def getPrimesBetween(pNumber1, pNumber2): # precondition assert ( - isinstance(ans, list) and ans[0] != pNumber1 and ans[len(ans) - 1] != pNumber2 + isinstance(ans, list) + and ans[0] != p_number_1 + and ans[len(ans) - 1] != p_number_2 ), "'ans' must been a list without the arguments" # 'ans' contains not 'pNumber1' and 'pNumber2' ! @@ -520,7 +522,7 @@ def getPrimesBetween(pNumber1, pNumber2): # ---------------------------------------------------- -def getDivisors(n): +def get_divisors(n): """ input: positive integer 'n' >= 1 returns all divisors of n (inclusive 1 and 'n') @@ -545,7 +547,7 @@ def getDivisors(n): # ---------------------------------------------------- -def isPerfectNumber(number): +def is_perfect_number(number): """ input: positive integer 'number' > 1 returns true if 'number' is a perfect number otherwise false. @@ -556,7 +558,7 @@ def isPerfectNumber(number): number > 1 ), "'number' must been an int and >= 1" - divisors = getDivisors(number) + divisors = get_divisors(number) # precondition assert ( @@ -572,7 +574,7 @@ def isPerfectNumber(number): # ------------------------------------------------------------ -def simplifyFraction(numerator, denominator): +def simplify_fraction(numerator, denominator): """ input: two integer 'numerator' and 'denominator' assumes: 'denominator' != 0 @@ -587,16 +589,16 @@ def simplifyFraction(numerator, denominator): ), "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)) + gcd_of_fraction = gcd(abs(numerator), abs(denominator)) # precondition assert ( - isinstance(gcdOfFraction, int) - and (numerator % gcdOfFraction == 0) - and (denominator % gcdOfFraction == 0) + isinstance(gcd_of_fraction, int) + and (numerator % gcd_of_fraction == 0) + and (denominator % gcd_of_fraction == 0) ), "Error in function gcd(...,...)" - return (numerator // gcdOfFraction, denominator // gcdOfFraction) + return (numerator // gcd_of_fraction, denominator // gcd_of_fraction) # ----------------------------------------------------------------- diff --git a/maths/qr_decomposition.py b/maths/qr_decomposition.py index 5e15fede4f2a..a8414fbece87 100644 --- a/maths/qr_decomposition.py +++ b/maths/qr_decomposition.py @@ -1,7 +1,7 @@ import numpy as np -def qr_householder(A): +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 @@ -37,14 +37,14 @@ def qr_householder(A): >>> np.allclose(np.triu(R), R) True """ - m, n = A.shape + m, n = a.shape t = min(m, n) - Q = np.eye(m) - R = A.copy() + q = np.eye(m) + r = a.copy() for k in range(t - 1): # select a column of modified matrix A': - x = R[k:, [k]] + x = r[k:, [k]] # construct first basis vector e1 = np.zeros_like(x) e1[0] = 1.0 @@ -55,14 +55,14 @@ def qr_householder(A): 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 + return q, r if __name__ == "__main__": diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index 0a431a115fb8..52442134de59 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -49,10 +49,10 @@ class FFT: 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=None, polyB=None): + def __init__(self, poly_a=None, poly_b=None): # Input as list - self.polyA = list(polyA or [0])[:] - self.polyB = list(polyB or [0])[:] + self.polyA = list(poly_a or [0])[:] + self.polyB = list(poly_b or [0])[:] # Remove leading zero coefficients while self.polyA[-1] == 0: @@ -64,22 +64,22 @@ def __init__(self, polyA=None, polyB=None): self.len_B = len(self.polyB) # Add 0 to make lengths equal a power of 2 - self.C_max_length = int( + 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: + while len(self.polyA) < self.c_max_length: self.polyA.append(0) - while len(self.polyB) < self.C_max_length: + 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() # Discrete fourier transform of A and B - def __DFT(self, which): + def __dft(self, which): if which == "A": dft = [[x] for x in self.polyA] else: @@ -88,20 +88,20 @@ def __DFT(self, which): if len(dft) <= 1: return dft[0] # - next_ncol = self.C_max_length // 2 + 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 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 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 @@ -112,65 +112,65 @@ def __DFT(self, which): # 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 + dft_a = self.__dft("A") + dft_b = self.__dft("B") + inverce_c = [[dft_a[i] * dft_b[i] for i in range(self.c_max_length)]] + del dft_a + del dft_b # Corner Case - if len(inverseC[0]) <= 1: - return inverseC[0] + if len(inverce_c[0]) <= 1: + return inverce_c[0] # Inverse DFT next_ncol = 2 - while next_ncol <= self.C_max_length: - new_inverseC = [[] for i in range(next_ncol)] + while next_ncol <= self.c_max_length: + new_inverse_c = [[] 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 j in range(self.c_max_length // next_ncol): for i in range(next_ncol // 2): # Even positions - new_inverseC[i].append( + new_inverse_c[i].append( ( - inverseC[i][j] - + inverseC[i][j + self.C_max_length // next_ncol] + inverce_c[i][j] + + inverce_c[i][j + self.c_max_length // next_ncol] ) / 2 ) # Odd positions - new_inverseC[i + next_ncol // 2].append( + new_inverse_c[i + next_ncol // 2].append( ( - inverseC[i][j] - - inverseC[i][j + self.C_max_length // next_ncol] + inverce_c[i][j] + - inverce_c[i][j + self.c_max_length // next_ncol] ) / (2 * current_root) ) current_root *= root # Update - inverseC = new_inverseC + inverce_c = new_inverse_c next_ncol *= 2 # Unpack - inverseC = [round(x[0].real, 8) + round(x[0].imag, 8) * 1j for x in inverseC] + inverce_c = [round(x[0].real, 8) + round(x[0].imag, 8) * 1j for x in inverce_c] # Remove leading 0's - while inverseC[-1] == 0: - inverseC.pop() - return inverseC + while inverce_c[-1] == 0: + inverce_c.pop() + return inverce_c # Overwrite __str__ for print(); Shows A, B and A*B def __str__(self): - A = "A = " + " + ".join( + a = "A = " + " + ".join( f"{coef}*x^{i}" for coef, i in enumerate(self.polyA[: self.len_A]) ) - B = "B = " + " + ".join( + b = "B = " + " + ".join( f"{coef}*x^{i}" for coef, i in enumerate(self.polyB[: self.len_B]) ) - C = "A*B = " + " + ".join( + c = "A*B = " + " + ".join( f"{coef}*x^{i}" for coef, i in enumerate(self.product) ) - return "\n".join((A, B, C)) + return "\n".join((a, b, c)) # Unit tests diff --git a/maths/runge_kutta.py b/maths/runge_kutta.py index 383797daa5ac..4cac017ee89e 100644 --- a/maths/runge_kutta.py +++ b/maths/runge_kutta.py @@ -22,12 +22,12 @@ def runge_kutta(f, y0, x0, h, x_end): >>> y[-1] 148.41315904125113 """ - N = int(np.ceil((x_end - x0) / h)) - y = np.zeros((N + 1,)) + n = int(np.ceil((x_end - x0) / h)) + y = np.zeros((n + 1,)) y[0] = y0 x = x0 - for k in range(N): + 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) diff --git a/maths/softmax.py b/maths/softmax.py index e021a7f8a6fe..04cf77525420 100644 --- a/maths/softmax.py +++ b/maths/softmax.py @@ -41,13 +41,13 @@ def softmax(vector): # Calculate e^x for each x in your vector where e is Euler's # number (approximately 2.718) - exponentVector = np.exp(vector) + exponent_vector = np.exp(vector) # Add up the all the exponentials - sumOfExponents = np.sum(exponentVector) + sum_of_exponents = np.sum(exponent_vector) # Divide every exponent by the sum of all exponents - softmax_vector = exponentVector / sumOfExponents + softmax_vector = exponent_vector / sum_of_exponents return softmax_vector diff --git a/matrix/count_islands_in_matrix.py b/matrix/count_islands_in_matrix.py index 00f9e14362b2..64c595e8499d 100644 --- a/matrix/count_islands_in_matrix.py +++ b/matrix/count_islands_in_matrix.py @@ -3,7 +3,7 @@ # connections. -class matrix: # Public class to implement a graph +class Matrix: # Public class to implement a graph def __init__(self, row: int, col: int, graph: list[list[bool]]) -> None: self.ROW = row self.COL = col @@ -19,12 +19,12 @@ def is_safe(self, i: int, j: int, visited: list[list[bool]]) -> bool: def diffs(self, i: int, j: int, visited: list[list[bool]]) -> None: # 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] + row_nbr = [-1, -1, -1, 0, 0, 1, 1, 1] # Coordinate order + col_nbr = [-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) + if self.is_safe(i + row_nbr[k], j + col_nbr[k], visited): + self.diffs(i + row_nbr[k], j + col_nbr[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)] diff --git a/matrix/inverse_of_matrix.py b/matrix/inverse_of_matrix.py index 92780e656ea1..770ce39b584f 100644 --- a/matrix/inverse_of_matrix.py +++ b/matrix/inverse_of_matrix.py @@ -27,7 +27,7 @@ def inverse_of_matrix(matrix: list[list[float]]) -> list[list[float]]: [[0.25, -0.5], [-0.3, 1.0]] """ - D = Decimal # An abbreviation for conciseness + d = Decimal # An abbreviation for conciseness # Check if the provided matrix has 2 rows and 2 columns # since this implementation only works for 2x2 matrices @@ -35,7 +35,7 @@ def inverse_of_matrix(matrix: list[list[float]]) -> list[list[float]]: raise ValueError("Please provide a matrix of size 2x2.") # Calculate the determinant of the matrix - determinant = D(matrix[0][0]) * D(matrix[1][1]) - D(matrix[1][0]) * D(matrix[0][1]) + 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.") @@ -45,4 +45,4 @@ def inverse_of_matrix(matrix: list[list[float]]) -> list[list[float]]: 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] + return [[float(d(n) / determinant) or 0.0 for n in row] for row in swapped_matrix] diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index a0c93f11574e..29c9b3381b55 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -54,15 +54,15 @@ def single_line(row_vector: list[float]) -> str: def __repr__(self) -> str: return str(self) - def validateIndices(self, loc: tuple[int, int]) -> bool: + def validate_indicies(self, loc: tuple[int, int]) -> bool: """ - + Check if given indices are valid to pick element from matrix. Example: >>> a = Matrix(2, 6, 0) - >>> a.validateIndices((2, 7)) + >>> a.validate_indicies((2, 7)) False - >>> a.validateIndices((0, 0)) + >>> a.validate_indicies((0, 0)) True """ if not (isinstance(loc, (list, tuple)) and len(loc) == 2): @@ -81,7 +81,7 @@ def __getitem__(self, loc: tuple[int, int]) -> Any: >>> a[1, 0] 7 """ - assert self.validateIndices(loc) + assert self.validate_indicies(loc) return self.array[loc[0]][loc[1]] def __setitem__(self, loc: tuple[int, int], value: float) -> None: @@ -96,7 +96,7 @@ def __setitem__(self, loc: tuple[int, int], value: float) -> None: [ 1, 1, 1] [ 1, 1, 51] """ - assert self.validateIndices(loc) + assert self.validate_indicies(loc) self.array[loc[0]][loc[1]] = value def __add__(self, another: Matrix) -> Matrix: @@ -198,9 +198,9 @@ def transpose(self) -> Matrix: result[c, r] = self[r, c] return result - def ShermanMorrison(self, u: Matrix, v: Matrix) -> Any: + def sherman_morrison(self, u: Matrix, v: Matrix) -> Any: """ - + 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 @@ -216,7 +216,7 @@ def ShermanMorrison(self, u: Matrix, v: Matrix) -> Any: >>> 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) + >>> ainv.sherman_morrison(u, v) Matrix consist of 3 rows and 3 columns [ 1.2857142857142856, -0.14285714285714285, 0.3571428571428571] [ 0.5714285714285714, 0.7142857142857143, 0.7142857142857142] @@ -229,11 +229,11 @@ def ShermanMorrison(self, u: Matrix, v: Matrix) -> Any: assert u.column == v.column == 1 # u, v should be column vector # Calculate - vT = v.transpose() - numerator_factor = (vT * self * u)[0, 0] + 1 + v_t = v.transpose() + numerator_factor = (v_t * 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)) + return self - ((self * u) * (v_t * self) * (1.0 / numerator_factor)) # Testing @@ -254,7 +254,7 @@ def test1() -> None: print(f"v is {v}") print("uv^T is %s" % (u * v.transpose())) # Sherman Morrison - print(f"(a + uv^T)^(-1) is {ainv.ShermanMorrison(u, v)}") + print(f"(a + uv^T)^(-1) is {ainv.sherman_morrison(u, v)}") def test2() -> None: import doctest diff --git a/networking_flow/ford_fulkerson.py b/networking_flow/ford_fulkerson.py index 96b782649774..370e3848222a 100644 --- a/networking_flow/ford_fulkerson.py +++ b/networking_flow/ford_fulkerson.py @@ -6,7 +6,7 @@ """ -def BFS(graph, s, t, parent): +def bfs(graph, s, t, parent): # Return True if there is node that has not iterated. visited = [False] * len(graph) queue = [] @@ -24,11 +24,11 @@ def BFS(graph, s, t, parent): return True if visited[t] else False -def FordFulkerson(graph, source, sink): +def ford_fulkerson(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): + while bfs(graph, source, sink, parent): path_flow = float("Inf") s = sink @@ -58,4 +58,4 @@ def FordFulkerson(graph, source, sink): ] source, sink = 0, 5 -print(FordFulkerson(graph, source, sink)) +print(ford_fulkerson(graph, source, sink)) diff --git a/networking_flow/minimum_cut.py b/networking_flow/minimum_cut.py index d79f3619caf1..33131315f4e1 100644 --- a/networking_flow/minimum_cut.py +++ b/networking_flow/minimum_cut.py @@ -10,7 +10,7 @@ ] -def BFS(graph, s, t, parent): +def bfs(graph, s, t, parent): # Return True if there is node that has not iterated. visited = [False] * len(graph) queue = [s] @@ -36,7 +36,7 @@ def mincut(graph, source, sink): max_flow = 0 res = [] temp = [i[:] for i in graph] # Record original cut, copy. - while BFS(graph, source, sink, parent): + 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 e3993efb4249..bbade1c417d0 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -74,7 +74,7 @@ def save_model(self, save_path): print(f"Model saved: {save_path}") @classmethod - def ReadModel(cls, model_path): + def read_model(cls, model_path): # read saved model with open(model_path, "rb") as f: model_dic = pickle.load(f) @@ -119,7 +119,7 @@ def convolute(self, data, convs, w_convs, thre_convs, conv_step): data_focus.append(focus) # 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) + size_feature_map = int((size_data - size_conv) / conv_step + 1) for i_map in range(num_conv): featuremap = [] for i_focus in range(len(data_focus)): @@ -129,7 +129,7 @@ def convolute(self, data, convs, w_convs, thre_convs, conv_step): ) featuremap.append(self.sig(net_focus)) featuremap = np.asmatrix(featuremap).reshape( - Size_FeatureMap, Size_FeatureMap + size_feature_map, size_feature_map ) data_featuremap.append(featuremap) diff --git a/other/davisb_putnamb_logemannb_loveland.py b/other/davisb_putnamb_logemannb_loveland.py index 88aefabc8087..03d60a9a1aaf 100644 --- a/other/davisb_putnamb_logemannb_loveland.py +++ b/other/davisb_putnamb_logemannb_loveland.py @@ -255,14 +255,14 @@ def find_unit_clauses( if len(clause) == 1: unit_symbols.append(list(clause.literals.keys())[0]) else: - Fcount, Ncount = 0, 0 + f_count, n_count = 0, 0 for literal, value in clause.literals.items(): if value is False: - Fcount += 1 + f_count += 1 elif value is None: sym = literal - Ncount += 1 - if Fcount == len(clause) - 1 and Ncount == 1: + n_count += 1 + if f_count == len(clause) - 1 and n_count == 1: unit_symbols.append(sym) assignment: dict[str, bool | None] = dict() for i in unit_symbols: @@ -310,33 +310,33 @@ def dpll_algorithm( except RecursionError: print("raises a RecursionError and is") return None, {} - P = None + p = None if len(pure_symbols) > 0: - P, value = pure_symbols[0], assignment[pure_symbols[0]] + p, value = pure_symbols[0], assignment[pure_symbols[0]] - if P: + if p: tmp_model = model - tmp_model[P] = value + tmp_model[p] = value tmp_symbols = [i for i in symbols] - if P in tmp_symbols: - tmp_symbols.remove(P) + 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 + p = None if len(unit_symbols) > 0: - P, value = unit_symbols[0], assignment[unit_symbols[0]] - if P: + p, value = unit_symbols[0], assignment[unit_symbols[0]] + if p: tmp_model = model - tmp_model[P] = value + tmp_model[p] = value tmp_symbols = [i for i in symbols] - if P in tmp_symbols: - tmp_symbols.remove(P) + if p in tmp_symbols: + tmp_symbols.remove(p) return dpll_algorithm(clauses, tmp_symbols, tmp_model) - P = symbols[0] + p = symbols[0] rest = symbols[1:] tmp1, tmp2 = model, model - tmp1[P], tmp2[P] = True, False + tmp1[p], tmp2[p] = True, False return dpll_algorithm(clauses, rest, tmp1) or dpll_algorithm(clauses, rest, tmp2) diff --git a/other/greedy.py b/other/greedy.py index 4b78bf1c0415..72e05f451fbb 100644 --- a/other/greedy.py +++ b/other/greedy.py @@ -1,4 +1,4 @@ -class things: +class Things: def __init__(self, name, value, weight): self.name = name self.value = value @@ -16,27 +16,27 @@ def get_name(self): def get_weight(self): return self.weight - def value_Weight(self): + 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])) + menu.append(Things(name[i], value[i], weight[i])) return menu -def greedy(item, maxCost, keyFunc): - itemsCopy = sorted(item, key=keyFunc, reverse=True) +def greedy(item, max_cost, key_func): + items_copy = sorted(item, key=key_func, 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) + total_value, total_cost = 0.0, 0.0 + for i in range(len(items_copy)): + if (total_cost + items_copy[i].get_weight()) <= max_cost: + result.append(items_copy[i]) + total_cost += items_copy[i].get_weight() + total_value += items_copy[i].get_value() + return (result, total_value) def test_greedy(): @@ -47,13 +47,13 @@ def test_greedy(): >>> 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) + [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) """ diff --git a/other/nested_brackets.py b/other/nested_brackets.py index 99e2f3a38797..9dd9a0f042ed 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -14,21 +14,21 @@ """ -def is_balanced(S): +def is_balanced(s): stack = [] open_brackets = set({"(", "[", "{"}) closed_brackets = set({")", "]", "}"}) open_to_closed = dict({"{": "}", "[": "]", "(": ")"}) - for i in range(len(S)): + for i in range(len(s)): - if S[i] in open_brackets: - stack.append(S[i]) + if s[i] in open_brackets: + stack.append(s[i]) - elif S[i] in closed_brackets: + elif s[i] in closed_brackets: if len(stack) == 0 or ( - len(stack) > 0 and open_to_closed[stack.pop()] != S[i] + len(stack) > 0 and open_to_closed[stack.pop()] != s[i] ): return False diff --git a/other/sdes.py b/other/sdes.py index cfc5a53df2b2..695675000632 100644 --- a/other/sdes.py +++ b/other/sdes.py @@ -19,9 +19,9 @@ def left_shift(data): return data[1:] + data[0] -def XOR(a, b): +def xor(a, b): """ - >>> XOR("01010101", "00001111") + >>> xor("01010101", "00001111") '01011010' """ res = "" @@ -43,13 +43,13 @@ def function(expansion, s0, s1, key, message): left = message[:4] right = message[4:] temp = apply_table(right, expansion) - temp = XOR(temp, key) + temp = xor(temp, key) l = apply_sbox(s0, temp[:4]) # noqa: E741 r = apply_sbox(s1, temp[4:]) 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) + temp = xor(left, temp) return temp + right diff --git a/other/tower_of_hanoi.py b/other/tower_of_hanoi.py index 3cc0e40b369f..1fff45039891 100644 --- a/other/tower_of_hanoi.py +++ b/other/tower_of_hanoi.py @@ -1,6 +1,6 @@ -def moveTower(height, fromPole, toPole, withPole): +def move_tower(height, from_pole, to_pole, with_pole): """ - >>> moveTower(3, 'A', 'B', 'C') + >>> move_tower(3, 'A', 'B', 'C') moving disk from A to B moving disk from A to C moving disk from B to C @@ -10,18 +10,18 @@ def moveTower(height, fromPole, toPole, withPole): moving disk from A to B """ if height >= 1: - moveTower(height - 1, fromPole, withPole, toPole) - moveDisk(fromPole, toPole) - moveTower(height - 1, withPole, toPole, fromPole) + move_tower(height - 1, from_pole, with_pole, to_pole) + move_disk(from_pole, to_pole) + move_tower(height - 1, with_pole, to_pole, from_pole) -def moveDisk(fp, tp): +def move_disk(fp, tp): print("moving disk from", fp, "to", tp) def main(): height = int(input("Height of hanoi: ").strip()) - moveTower(height, "A", "B", "C") + move_tower(height, "A", "B", "C") if __name__ == "__main__": diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index 01083b9a272e..7e9fc1642c84 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -219,9 +219,11 @@ def plot( Utility function to plot how the given body-system evolves over time. No doctest provided since this function does not have a return value. """ + # Frame rate of the animation + INTERVAL = 20 # noqa: N806 - INTERVAL = 20 # Frame rate of the animation - DELTA_TIME = INTERVAL / 1000 # Time between time steps in seconds + # Time between time steps in seconds + DELTA_TIME = INTERVAL / 1000 # noqa: N806 fig = plt.figure() fig.canvas.set_window_title(title) diff --git a/project_euler/problem_011/sol1.py b/project_euler/problem_011/sol1.py index 9dea73e8cef2..ad45f0983a7c 100644 --- a/project_euler/problem_011/sol1.py +++ b/project_euler/problem_011/sol1.py @@ -28,23 +28,23 @@ def largest_product(grid): - nColumns = len(grid[0]) - nRows = len(grid) + n_columns = len(grid[0]) + n_rows = len(grid) largest = 0 - lrDiagProduct = 0 - rlDiagProduct = 0 + lr_diag_product = 0 + rl_diag_product = 0 # Check vertically, horizontally, diagonally at the same time (only works # 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] + for i in range(n_columns): + for j in range(n_rows - 3): + vert_product = grid[j][i] * grid[j + 1][i] * grid[j + 2][i] * grid[j + 3][i] + horz_product = 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 = ( + if i < n_columns - 3: + lr_diag_product = ( grid[i][j] * grid[i + 1][j + 1] * grid[i + 2][j + 2] @@ -53,16 +53,18 @@ def largest_product(grid): # Right-to-left diagonal(/) product if i > 2: - rlDiagProduct = ( + rl_diag_product = ( 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 + max_product = max( + vert_product, horz_product, lr_diag_product, rl_diag_product + ) + if max_product > largest: + largest = max_product return largest diff --git a/project_euler/problem_012/sol1.py b/project_euler/problem_012/sol1.py index 861d026ece5b..e42b03419c69 100644 --- a/project_euler/problem_012/sol1.py +++ b/project_euler/problem_012/sol1.py @@ -24,18 +24,18 @@ def count_divisors(n): - nDivisors = 1 + n_divisors = 1 i = 2 while i * i <= n: multiplicity = 0 while n % i == 0: n //= i multiplicity += 1 - nDivisors *= multiplicity + 1 + n_divisors *= multiplicity + 1 i += 1 if n > 1: - nDivisors *= 2 - return nDivisors + n_divisors *= 2 + return n_divisors def solution(): @@ -45,17 +45,17 @@ def solution(): >>> solution() 76576500 """ - tNum = 1 + t_num = 1 i = 1 while True: i += 1 - tNum += i + t_num += i - if count_divisors(tNum) > 500: + if count_divisors(t_num) > 500: break - return tNum + return t_num if __name__ == "__main__": diff --git a/project_euler/problem_023/sol1.py b/project_euler/problem_023/sol1.py index 83b85f3f721c..9fdf7284a3fd 100644 --- a/project_euler/problem_023/sol1.py +++ b/project_euler/problem_023/sol1.py @@ -28,18 +28,18 @@ def solution(limit=28123): >>> solution() 4179871 """ - sumDivs = [1] * (limit + 1) + sum_divs = [1] * (limit + 1) for i in range(2, int(limit**0.5) + 1): - sumDivs[i * i] += i + sum_divs[i * i] += i for k in range(i + 1, limit // i + 1): - sumDivs[k * i] += k + i + sum_divs[k * i] += k + i abundants = set() res = 0 for n in range(1, limit + 1): - if sumDivs[n] > n: + if sum_divs[n] > n: abundants.add(n) if not any((n - a in abundants) for a in abundants): diff --git a/project_euler/problem_029/sol1.py b/project_euler/problem_029/sol1.py index d3ab90ac7d25..d9a81e55ca35 100644 --- a/project_euler/problem_029/sol1.py +++ b/project_euler/problem_029/sol1.py @@ -33,17 +33,17 @@ def solution(n: int = 100) -> int: >>> solution(1) 0 """ - collectPowers = set() + collect_powers = set() - currentPow = 0 + current_pow = 0 - N = n + 1 # 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 - return len(collectPowers) + for a in range(2, n): + for b in range(2, n): + current_pow = a**b # calculates the current power + collect_powers.add(current_pow) # adds the result to the set + return len(collect_powers) if __name__ == "__main__": diff --git a/project_euler/problem_032/sol32.py b/project_euler/problem_032/sol32.py index 393218339e9f..c4d11e86c877 100644 --- a/project_euler/problem_032/sol32.py +++ b/project_euler/problem_032/sol32.py @@ -15,15 +15,15 @@ import itertools -def isCombinationValid(combination): +def is_combination_valid(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')) + >>> is_combination_valid(('3', '9', '1', '8', '6', '7', '2', '5', '4')) True - >>> isCombinationValid(('1', '2', '3', '4', '5', '6', '7', '8', '9')) + >>> is_combination_valid(('1', '2', '3', '4', '5', '6', '7', '8', '9')) False """ @@ -49,7 +49,7 @@ def solution(): { int("".join(pandigital[5:9])) for pandigital in itertools.permutations("123456789") - if isCombinationValid(pandigital) + if is_combination_valid(pandigital) } ) diff --git a/project_euler/problem_042/solution42.py b/project_euler/problem_042/solution42.py index b3aecf4cf144..6d22a8dfb655 100644 --- a/project_euler/problem_042/solution42.py +++ b/project_euler/problem_042/solution42.py @@ -27,10 +27,10 @@ def solution(): 162 """ script_dir = os.path.dirname(os.path.realpath(__file__)) - wordsFilePath = os.path.join(script_dir, "words.txt") + words_file_path = os.path.join(script_dir, "words.txt") words = "" - with open(wordsFilePath) as f: + with open(words_file_path) as f: words = f.readline() words = list(map(lambda word: word.strip('"'), words.strip("\r\n").split(","))) diff --git a/project_euler/problem_054/test_poker_hand.py b/project_euler/problem_054/test_poker_hand.py index 96317fc7df33..bf5a20a8e862 100644 --- a/project_euler/problem_054/test_poker_hand.py +++ b/project_euler/problem_054/test_poker_hand.py @@ -185,7 +185,7 @@ def test_compare_random(hand, other, expected): def test_hand_sorted(): - POKER_HANDS = [PokerHand(hand) for hand in SORTED_HANDS] + POKER_HANDS = [PokerHand(hand) for hand in SORTED_HANDS] # noqa: N806 list_copy = POKER_HANDS.copy() shuffle(list_copy) user_sorted = chain(sorted(list_copy)) diff --git a/project_euler/problem_064/sol1.py b/project_euler/problem_064/sol1.py index 5df64a90ae55..9edd9a1e7a64 100644 --- a/project_euler/problem_064/sol1.py +++ b/project_euler/problem_064/sol1.py @@ -33,7 +33,7 @@ def continuous_fraction_period(n: int) -> int: """ numerator = 0.0 denominator = 1.0 - ROOT = int(sqrt(n)) + ROOT = int(sqrt(n)) # noqa: N806 integer_part = ROOT period = 0 while integer_part != 2 * ROOT: diff --git a/project_euler/problem_097/sol1.py b/project_euler/problem_097/sol1.py index da5e8120b7c5..94a43894ee07 100644 --- a/project_euler/problem_097/sol1.py +++ b/project_euler/problem_097/sol1.py @@ -34,8 +34,8 @@ def solution(n: int = 10) -> str: """ if not isinstance(n, int) or n < 0: raise ValueError("Invalid input") - MODULUS = 10**n - NUMBER = 28433 * (pow(2, 7830457, MODULUS)) + 1 + MODULUS = 10**n # noqa: N806 + NUMBER = 28433 * (pow(2, 7830457, MODULUS)) + 1 # noqa: N806 return str(NUMBER % MODULUS) diff --git a/project_euler/problem_104/sol.py b/project_euler/problem_104/sol.py.FIXME similarity index 100% rename from project_euler/problem_104/sol.py rename to project_euler/problem_104/sol.py.FIXME diff --git a/project_euler/problem_125/sol1.py b/project_euler/problem_125/sol1.py index 7a8f908ed6a9..1812df36132e 100644 --- a/project_euler/problem_125/sol1.py +++ b/project_euler/problem_125/sol1.py @@ -35,7 +35,7 @@ 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 + LIMIT = 10**8 # noqa: N806 answer = set() first_square = 1 sum_squares = 5 diff --git a/scheduling/non_preemptive_shortest_job_first.py b/scheduling/non_preemptive_shortest_job_first.py index 96e571230ec0..69c974b0044d 100644 --- a/scheduling/non_preemptive_shortest_job_first.py +++ b/scheduling/non_preemptive_shortest_job_first.py @@ -102,9 +102,9 @@ def calculate_turnaroundtime( # Printing the Result print("PID\tBurst Time\tArrival Time\tWaiting Time\tTurnaround Time") - for i, process_ID in enumerate(list(range(1, 5))): + for i, process_id in enumerate(list(range(1, 5))): print( - f"{process_ID}\t{burst_time[i]}\t\t\t{arrival_time[i]}\t\t\t\t" + f"{process_id}\t{burst_time[i]}\t\t\t{arrival_time[i]}\t\t\t\t" f"{waiting_time[i]}\t\t\t\t{turn_around_time[i]}" ) print(f"\nAverage waiting time = {mean(waiting_time):.5f}") diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 24d0dbf6f1c2..45ce19d46b23 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -178,9 +178,9 @@ def find_neighborhood(solution, dict_of_neighbours): if _tmp not in neighborhood_of_solution: neighborhood_of_solution.append(_tmp) - indexOfLastItemInTheList = len(neighborhood_of_solution[0]) - 1 + index_of_last_item_in_the_list = len(neighborhood_of_solution[0]) - 1 - neighborhood_of_solution.sort(key=lambda x: x[indexOfLastItemInTheList]) + neighborhood_of_solution.sort(key=lambda x: x[index_of_last_item_in_the_list]) return neighborhood_of_solution diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index 5de7a016c628..b656df3a3a90 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -13,7 +13,7 @@ from multiprocessing import Lock, Pipe, Process # lock used to ensure that two processes do not access a pipe at the same time -processLock = Lock() +process_lock = Lock() """ The function run by the processes that sorts the list @@ -27,42 +27,42 @@ """ -def oeProcess(position, value, LSend, RSend, LRcv, RRcv, resultPipe): - global processLock +def oe_process(position, value, l_send, r_send, lr_cv, rr_cv, result_pipe): + global process_lock # 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 is not None: + if (i + position) % 2 == 0 and r_send is not None: # send your value to your right neighbor - processLock.acquire() - RSend[1].send(value) - processLock.release() + process_lock.acquire() + r_send[1].send(value) + process_lock.release() # receive your right neighbor's value - processLock.acquire() - temp = RRcv[0].recv() - processLock.release() + process_lock.acquire() + temp = rr_cv[0].recv() + process_lock.release() # take the lower value since you are on the left value = min(value, temp) - elif (i + position) % 2 != 0 and LSend is not None: + elif (i + position) % 2 != 0 and l_send is not None: # send your value to your left neighbor - processLock.acquire() - LSend[1].send(value) - processLock.release() + process_lock.acquire() + l_send[1].send(value) + process_lock.release() # receive your left neighbor's value - processLock.acquire() - temp = LRcv[0].recv() - processLock.release() + process_lock.acquire() + temp = lr_cv[0].recv() + process_lock.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) + result_pipe[1].send(value) """ @@ -72,61 +72,61 @@ def oeProcess(position, value, LSend, RSend, LRcv, RRcv, resultPipe): """ -def OddEvenTransposition(arr): - processArray = [] - resultPipe = [] +def odd_even_transposition(arr): + process_array_ = [] + result_pipe = [] # initialize the list of pipes where the values will be retrieved for _ in arr: - resultPipe.append(Pipe()) + result_pipe.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( + temp_rs = Pipe() + temp_rr = Pipe() + process_array_.append( Process( - target=oeProcess, - args=(0, arr[0], None, tempRs, None, tempRr, resultPipe[0]), + target=oe_process, + args=(0, arr[0], None, temp_rs, None, temp_rr, result_pipe[0]), ) ) - tempLr = tempRs - tempLs = tempRr + temp_lr = temp_rs + temp_ls = temp_rr for i in range(1, len(arr) - 1): - tempRs = Pipe() - tempRr = Pipe() - processArray.append( + temp_rs = Pipe() + temp_rr = Pipe() + process_array_.append( Process( - target=oeProcess, - args=(i, arr[i], tempLs, tempRs, tempLr, tempRr, resultPipe[i]), + target=oe_process, + args=(i, arr[i], temp_ls, temp_rs, temp_lr, temp_rr, result_pipe[i]), ) ) - tempLr = tempRs - tempLs = tempRr + temp_lr = temp_rs + temp_ls = temp_rr - processArray.append( + process_array_.append( Process( - target=oeProcess, + target=oe_process, args=( len(arr) - 1, arr[len(arr) - 1], - tempLs, + temp_ls, None, - tempLr, + temp_lr, None, - resultPipe[len(arr) - 1], + result_pipe[len(arr) - 1], ), ) ) # start the processes - for p in processArray: + for p in process_array_: 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() + for p in range(0, len(result_pipe)): + arr[p] = result_pipe[p][0].recv() + process_array_[p].join() return arr @@ -135,7 +135,7 @@ def main(): arr = list(range(10, 0, -1)) print("Initial List") print(*arr) - arr = OddEvenTransposition(arr) + arr = odd_even_transposition(arr) print("Sorted List\n") print(*arr) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index c3ff04f3d5e5..afe62bc7ec30 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -19,7 +19,7 @@ def radix_sort(list_of_ints: list[int]) -> list[int]: >>> radix_sort([1,100,10,1000]) == sorted([1,100,10,1000]) True """ - RADIX = 10 + RADIX = 10 # noqa: N806 placement = 1 max_digit = max(list_of_ints) while placement <= max_digit: diff --git a/sorts/random_normal_distribution_quicksort.py b/sorts/random_normal_distribution_quicksort.py index 73eb70bea07f..5777d5cb2e7a 100644 --- a/sorts/random_normal_distribution_quicksort.py +++ b/sorts/random_normal_distribution_quicksort.py @@ -4,41 +4,41 @@ import numpy as np -def _inPlaceQuickSort(A, start, end): +def _in_place_quick_sort(a, start, end): count = 0 if start < end: pivot = randint(start, end) - temp = A[end] - A[end] = A[pivot] - A[pivot] = temp + temp = a[end] + a[end] = a[pivot] + a[pivot] = temp - p, count = _inPlacePartition(A, start, end) - count += _inPlaceQuickSort(A, start, p - 1) - count += _inPlaceQuickSort(A, p + 1, end) + p, count = _in_place_partition(a, start, end) + count += _in_place_quick_sort(a, start, p - 1) + count += _in_place_quick_sort(a, p + 1, end) return count -def _inPlacePartition(A, start, end): +def _in_place_partition(a, start, end): count = 0 pivot = randint(start, end) - temp = A[end] - A[end] = A[pivot] - A[pivot] = temp - newPivotIndex = start - 1 + temp = a[end] + a[end] = a[pivot] + a[pivot] = temp + new_pivot_index = start - 1 for index in range(start, end): count += 1 - if A[index] < A[end]: # check if current val is less than pivot value - newPivotIndex = newPivotIndex + 1 - temp = A[newPivotIndex] - A[newPivotIndex] = A[index] - A[index] = temp + if a[index] < a[end]: # check if current val is less than pivot value + new_pivot_index = new_pivot_index + 1 + temp = a[new_pivot_index] + a[new_pivot_index] = a[index] + a[index] = temp - temp = A[newPivotIndex + 1] - A[newPivotIndex + 1] = A[end] - A[end] = temp - return newPivotIndex + 1, count + temp = a[new_pivot_index + 1] + a[new_pivot_index + 1] = a[end] + a[end] = temp + return new_pivot_index + 1, count outfile = TemporaryFile() @@ -55,7 +55,7 @@ def _inPlacePartition(A, start, end): outfile.seek(0) # using the same array M = np.load(outfile) r = len(M) - 1 -z = _inPlaceQuickSort(M, 0, r) +z = _in_place_quick_sort(M, 0, r) print( "No of Comparisons for 100 elements selected from a standard normal distribution" diff --git a/sorts/random_pivot_quick_sort.py b/sorts/random_pivot_quick_sort.py index d9cf4e981c2f..748b6741047e 100644 --- a/sorts/random_pivot_quick_sort.py +++ b/sorts/random_pivot_quick_sort.py @@ -4,30 +4,30 @@ import random -def partition(A, left_index, right_index): - pivot = A[left_index] +def partition(a, left_index, right_index): + pivot = a[left_index] i = left_index + 1 for j in range(left_index + 1, right_index): - if A[j] < pivot: - A[j], A[i] = A[i], A[j] + if a[j] < pivot: + a[j], a[i] = a[i], a[j] i += 1 - A[left_index], A[i - 1] = A[i - 1], A[left_index] + a[left_index], a[i - 1] = a[i - 1], a[left_index] return i - 1 -def quick_sort_random(A, left, right): +def quick_sort_random(a, left, right): if left < right: pivot = random.randint(left, right - 1) - A[pivot], A[left] = ( - A[left], - A[pivot], + a[pivot], a[left] = ( + a[left], + a[pivot], ) # switches the pivot with the left most bound - pivot_index = partition(A, left, right) + pivot_index = partition(a, left, right) quick_sort_random( - A, left, pivot_index + a, left, pivot_index ) # recursive quicksort to the left of the pivot point quick_sort_random( - A, pivot_index + 1, right + a, pivot_index + 1, right ) # recursive quicksort to the right of the pivot point diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index e445fb4520aa..78c3e893e0ce 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 @@ -16,12 +16,12 @@ def insert(self, val): if self.val: if val < self.val: if self.left is None: - self.left = node(val) + self.left = Node(val) else: self.left.insert(val) elif val > self.val: if self.right is None: - self.right = node(val) + self.right = Node(val) else: self.right.insert(val) else: @@ -40,7 +40,7 @@ def tree_sort(arr): # Build BST if len(arr) == 0: return arr - root = node(arr[0]) + root = Node(arr[0]) for i in range(1, len(arr)): root.insert(arr[i]) # Traverse BST in order. diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index 8d8ff22f67bd..117305d32fd3 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -41,13 +41,13 @@ def match_in_pattern(self, char: str) -> int: return i return -1 - def mismatch_in_text(self, currentPos: int) -> int: + def mismatch_in_text(self, current_pos: int) -> int: """ find the index of mis-matched character in text when compared with pattern from last Parameters : - currentPos (int): current index position of text + current_pos (int): current index position of text Returns : i (int): index of mismatched char from last in text @@ -55,8 +55,8 @@ def mismatch_in_text(self, currentPos: int) -> int: """ for i in range(self.patLen - 1, -1, -1): - if self.pattern[i] != self.text[currentPos + i]: - return currentPos + i + if self.pattern[i] != self.text[current_pos + i]: + return current_pos + i return -1 def bad_character_heuristic(self) -> list[int]: diff --git a/strings/can_string_be_rearranged_as_palindrome.py b/strings/can_string_be_rearranged_as_palindrome.py index ddc4828c773b..21d653db1405 100644 --- a/strings/can_string_be_rearranged_as_palindrome.py +++ b/strings/can_string_be_rearranged_as_palindrome.py @@ -67,12 +67,12 @@ def can_string_be_rearranged_as_palindrome(input_str: str = "") -> bool: 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 + odd_char = 0 for character_count in character_freq_dict.values(): if character_count % 2: - oddChar += 1 - if oddChar > 1: + odd_char += 1 + if odd_char > 1: return False return True diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index f652e2294db2..0d2f8091a3f0 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -48,8 +48,8 @@ def check_anagrams(first_str: str, second_str: str) -> bool: from doctest import testmod testmod() - input_A = input("Enter the first string ").strip() - input_B = input("Enter the second string ").strip() + 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.") + status = check_anagrams(input_a, input_b) + print(f"{input_a} and {input_b} are {'' if status else 'not '}anagrams.") diff --git a/strings/word_patterns.py b/strings/word_patterns.py index 90b092a20dc8..d12d267e7b35 100644 --- a/strings/word_patterns.py +++ b/strings/word_patterns.py @@ -26,10 +26,10 @@ def get_word_pattern(word: str) -> str: start_time = time.time() with open("dictionary.txt") as in_file: - wordList = in_file.read().splitlines() + word_list = in_file.read().splitlines() all_patterns: dict = {} - for word in wordList: + for word in word_list: pattern = get_word_pattern(word) if pattern in all_patterns: all_patterns[pattern].append(word) @@ -39,6 +39,6 @@ def get_word_pattern(word: str) -> str: with open("word_patterns.txt", "w") as out_file: out_file.write(pprint.pformat(all_patterns)) - totalTime = round(time.time() - start_time, 2) - print(f"Done! {len(all_patterns):,} word patterns found in {totalTime} seconds.") + total_time = round(time.time() - start_time, 2) + print(f"Done! {len(all_patterns):,} word patterns found in {total_time} seconds.") # Done! 9,581 word patterns found in 0.58 seconds. diff --git a/web_programming/fetch_quotes.py b/web_programming/fetch_quotes.py index 4a3b002e515f..a45f6ea0eaf1 100644 --- a/web_programming/fetch_quotes.py +++ b/web_programming/fetch_quotes.py @@ -12,12 +12,12 @@ def quote_of_the_day() -> list: - API_ENDPOINT_URL = "https://zenquotes.io/api/today/" + API_ENDPOINT_URL = "https://zenquotes.io/api/today/" # noqa: N806 return requests.get(API_ENDPOINT_URL).json() def random_quotes() -> list: - API_ENDPOINT_URL = "https://zenquotes.io/api/random/" + API_ENDPOINT_URL = "https://zenquotes.io/api/random/" # noqa: N806 return requests.get(API_ENDPOINT_URL).json() From 1aa7bd96164bf9f17acd770f4c6992d35c468541 Mon Sep 17 00:00:00 2001 From: Abinash Satapathy Date: Thu, 13 Oct 2022 00:56:10 +0200 Subject: [PATCH 1877/2908] Added barcode_validator.py (#6771) * Update README.md Added Google Cirq references * Create barcode_validator.py Barcode/EAN validator * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Included docstring and updated variables to snake_case * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Included docset and updated bugs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Implemented the changes asked in review. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Updated with f-string format * Update barcode_validator.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- quantum/README.md | 8 ++++ strings/barcode_validator.py | 88 ++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 strings/barcode_validator.py diff --git a/quantum/README.md b/quantum/README.md index 423d34fa3364..3ce364574486 100644 --- a/quantum/README.md +++ b/quantum/README.md @@ -6,6 +6,7 @@ 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 +* Zapata: https://www.zapatacomputing.com and https://github.com/zapatacomputing ## IBM Qiskit - Start using by installing `pip install qiskit`, refer the [docs](https://qiskit.org/documentation/install.html) for more info. @@ -13,3 +14,10 @@ Started at https://github.com/TheAlgorithms/Python/issues/1831 - 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 + +## Google Cirq +- Start using by installing `python -m pip install cirq`, refer the [docs](https://quantumai.google/cirq/start/install) for more info. +- Tutorials & references + - https://github.com/quantumlib/cirq + - https://quantumai.google/cirq/experiments + - https://tanishabassan.medium.com/quantum-programming-with-google-cirq-3209805279bc diff --git a/strings/barcode_validator.py b/strings/barcode_validator.py new file mode 100644 index 000000000000..05670007665c --- /dev/null +++ b/strings/barcode_validator.py @@ -0,0 +1,88 @@ +""" +https://en.wikipedia.org/wiki/Check_digit#Algorithms +""" + + +def get_check_digit(barcode: int) -> int: + """ + Returns the last digit of barcode by excluding the last digit first + and then computing to reach the actual last digit from the remaining + 12 digits. + + >>> get_check_digit(8718452538119) + 9 + >>> get_check_digit(87184523) + 5 + >>> get_check_digit(87193425381086) + 9 + >>> [get_check_digit(x) for x in range(0, 100, 10)] + [0, 7, 4, 1, 8, 5, 2, 9, 6, 3] + """ + barcode //= 10 # exclude the last digit + checker = False + s = 0 + + # extract and check each digit + while barcode != 0: + mult = 1 if checker else 3 + s += mult * (barcode % 10) + barcode //= 10 + checker = not checker + + return (10 - (s % 10)) % 10 + + +def is_valid(barcode: int) -> bool: + """ + Checks for length of barcode and last-digit + Returns boolean value of validity of barcode + + >>> is_valid(8718452538119) + True + >>> is_valid(87184525) + False + >>> is_valid(87193425381089) + False + >>> is_valid(0) + False + >>> is_valid(dwefgiweuf) + Traceback (most recent call last): + ... + NameError: name 'dwefgiweuf' is not defined + """ + return len(str(barcode)) == 13 and get_check_digit(barcode) == barcode % 10 + + +def get_barcode(barcode: str) -> int: + """ + Returns the barcode as an integer + + >>> get_barcode("8718452538119") + 8718452538119 + >>> get_barcode("dwefgiweuf") + Traceback (most recent call last): + ... + ValueError: Barcode 'dwefgiweuf' has alphabetic characters. + """ + if str(barcode).isalpha(): + raise ValueError(f"Barcode '{barcode}' has alphabetic characters.") + elif int(barcode) < 0: + raise ValueError("The entered barcode has a negative value. Try again.") + else: + return int(barcode) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + """ + Enter a barcode. + + """ + barcode = get_barcode(input("Barcode: ").strip()) + + if is_valid(barcode): + print(f"'{barcode}' is a valid Barcode") + else: + print(f"'{barcode}' is NOT is valid Barcode.") From 6118b05f0efd1c2839eb8bc4de36723af1fcc364 Mon Sep 17 00:00:00 2001 From: Carlos Villar Date: Thu, 13 Oct 2022 08:24:53 +0200 Subject: [PATCH 1878/2908] Convert snake_case to camelCase or PascalCase (#7028) (#7034) * Added snake_case to Camel or Pascal case Fixes: #7028 * Added suggested changes * Add ending empty line from suggestion Co-authored-by: Caeden * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update strings/snake_case_to_camel_pascal_case.py Co-authored-by: Christian Clauss Co-authored-by: Caeden Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- strings/snake_case_to_camel_pascal_case.py | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 strings/snake_case_to_camel_pascal_case.py diff --git a/strings/snake_case_to_camel_pascal_case.py b/strings/snake_case_to_camel_pascal_case.py new file mode 100644 index 000000000000..7b2b61d1d1cf --- /dev/null +++ b/strings/snake_case_to_camel_pascal_case.py @@ -0,0 +1,52 @@ +def snake_to_camel_case(input: str, use_pascal: bool = False) -> str: + """ + Transforms a snake_case given string to camelCase (or PascalCase if indicated) + (defaults to not use Pascal) + + >>> snake_to_camel_case("some_random_string") + 'someRandomString' + + >>> snake_to_camel_case("some_random_string", use_pascal=True) + 'SomeRandomString' + + >>> snake_to_camel_case("some_random_string_with_numbers_123") + 'someRandomStringWithNumbers123' + + >>> snake_to_camel_case("some_random_string_with_numbers_123", use_pascal=True) + 'SomeRandomStringWithNumbers123' + + >>> snake_to_camel_case(123) + Traceback (most recent call last): + ... + ValueError: Expected string as input, found + + >>> snake_to_camel_case("some_string", use_pascal="True") + Traceback (most recent call last): + ... + ValueError: Expected boolean as use_pascal parameter, found + """ + + if not isinstance(input, str): + raise ValueError(f"Expected string as input, found {type(input)}") + if not isinstance(use_pascal, bool): + raise ValueError( + f"Expected boolean as use_pascal parameter, found {type(use_pascal)}" + ) + + words = input.split("_") + + start_index = 0 if use_pascal else 1 + + words_to_capitalize = words[start_index:] + + capitalized_words = [word[0].upper() + word[1:] for word in words_to_capitalize] + + initial_word = "" if use_pascal else words[0] + + return "".join([initial_word] + capitalized_words) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 7ad6c6402945349fbca42cce5acad631b0930a1d Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Thu, 13 Oct 2022 15:15:20 +0530 Subject: [PATCH 1879/2908] Add typing to maths/add.py (#7064) * Add typing to maths/add.py https://stackoverflow.com/questions/50928592/mypy-type-hint-unionfloat-int-is-there-a-number-type * Update add.py * Update add.py --- maths/add.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/add.py b/maths/add.py index 0bc7da9697d3..c89252c645ea 100644 --- a/maths/add.py +++ b/maths/add.py @@ -3,7 +3,7 @@ """ -def add(a, b): +def add(a: float, b: float) -> float: """ >>> add(2, 2) 4 From 9b0909d6545df269dc2c943df2e470671f0d1bcf Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:17:52 +0530 Subject: [PATCH 1880/2908] Add typing and snake casing to maths/decimal_isolate.py (#7066) --- maths/decimal_isolate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/decimal_isolate.py b/maths/decimal_isolate.py index 1b8f6cbcad89..cdf43ea5d0ef 100644 --- a/maths/decimal_isolate.py +++ b/maths/decimal_isolate.py @@ -4,7 +4,7 @@ """ -def decimal_isolate(number, digit_amount): +def decimal_isolate(number: float, digit_amount: int) -> float: """ Isolates the decimal part of a number. From c73cb7e3f7324ab2715ffc74ab18c32e3a90a065 Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:18:28 +0530 Subject: [PATCH 1881/2908] Add typing to maths/sum_of_arithmetic_series.py (#7065) --- maths/sum_of_arithmetic_series.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/sum_of_arithmetic_series.py b/maths/sum_of_arithmetic_series.py index 74eef0f18a12..e0e22760bfbe 100644 --- a/maths/sum_of_arithmetic_series.py +++ b/maths/sum_of_arithmetic_series.py @@ -1,5 +1,5 @@ # DarkCoder -def sum_of_series(first_term, common_diff, num_of_terms): +def sum_of_series(first_term: int, common_diff: int, num_of_terms: int) -> float: """ Find the sum of n terms in an arithmetic progression. From e661b9882995718ed967e33c2c814866b26fa76d Mon Sep 17 00:00:00 2001 From: GURNEET SINGH <79376134+SinghGurneet21@users.noreply.github.com> Date: Thu, 13 Oct 2022 17:39:01 +0530 Subject: [PATCH 1882/2908] Binary Search Tree Inorder Traversal Algorithm (#6840) * Binary Search Tree Inorder Traversal * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Binary Search Tree Inorder Traversal v2 * Binary Search Tree Inorder Traversal * Binary Search Tree Inorder Traversal * Update inorder_tree_traversal_2022.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update inorder_tree_traversal_2022.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update inorder_tree_traversal_2022.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/binary_tree/inorder_tree_traversal_2022.py * Update data_structures/binary_tree/inorder_tree_traversal_2022.py * Updated * Updated * Update inorder_tree_traversal_2022.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update inorder_tree_traversal_2022.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/binary_tree/inorder_tree_traversal_2022.py Co-authored-by: Christian Clauss * Updated and removed print statement removed the print from inorder function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + .../inorder_tree_traversal_2022.py | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 data_structures/binary_tree/inorder_tree_traversal_2022.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 25272af4a708..2786e1f82de8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -153,6 +153,7 @@ * [Binary Tree Mirror](data_structures/binary_tree/binary_tree_mirror.py) * [Binary Tree Traversals](data_structures/binary_tree/binary_tree_traversals.py) * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) + * [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py) * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py) * [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py) diff --git a/data_structures/binary_tree/inorder_tree_traversal_2022.py b/data_structures/binary_tree/inorder_tree_traversal_2022.py new file mode 100644 index 000000000000..08001738f53d --- /dev/null +++ b/data_structures/binary_tree/inorder_tree_traversal_2022.py @@ -0,0 +1,83 @@ +""" +Illustrate how to implement inorder traversal in binary search tree. +Author: Gurneet Singh +https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/ +""" + + +class BinaryTreeNode: + """Defining the structure of BinaryTreeNode""" + + def __init__(self, data: int) -> None: + self.data = data + self.left_child: BinaryTreeNode | None = None + self.right_child: BinaryTreeNode | None = None + + +def insert(node: BinaryTreeNode | None, new_value: int) -> BinaryTreeNode | None: + """ + If the binary search tree is empty, make a new node and declare it as root. + >>> node_a = BinaryTreeNode(12345) + >>> node_b = insert(node_a, 67890) + >>> node_a.left_child == node_b.left_child + True + >>> node_a.right_child == node_b.right_child + True + >>> node_a.data == node_b.data + True + """ + if node is None: + node = BinaryTreeNode(new_value) + return node + + # binary search tree is not empty, + # so we will insert it into the tree + # if new_value is less than value of data in node, + # add it to left subtree and proceed recursively + if new_value < node.data: + node.left_child = insert(node.left_child, new_value) + else: + # if new_value is greater than value of data in node, + # add it to right subtree and proceed recursively + node.right_child = insert(node.right_child, new_value) + return node + + +def inorder(node: None | BinaryTreeNode) -> list[int]: # if node is None,return + """ + >>> inorder(make_tree()) + [6, 10, 14, 15, 20, 25, 60] + """ + if node: + inorder_array = inorder(node.left_child) + inorder_array = inorder_array + [node.data] + inorder_array = inorder_array + inorder(node.right_child) + else: + inorder_array = [] + return inorder_array + + +def make_tree() -> BinaryTreeNode | None: + + root = insert(None, 15) + insert(root, 10) + insert(root, 25) + insert(root, 6) + insert(root, 14) + insert(root, 20) + insert(root, 60) + return root + + +def main() -> None: + # main function + root = make_tree() + print("Printing values of binary search tree in Inorder Traversal.") + inorder(root) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From d5a9f649b8279858add6fe6dd5a84af2f40a4cc9 Mon Sep 17 00:00:00 2001 From: Caeden Date: Thu, 13 Oct 2022 15:23:59 +0100 Subject: [PATCH 1883/2908] Add flake8-builtins to pre-commit and fix errors (#7105) Ignore `A003` Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Dhruv Manilawala --- .flake8 | 3 +++ .pre-commit-config.yaml | 2 +- arithmetic_analysis/gaussian_elimination.py | 6 +++--- arithmetic_analysis/jacobi_iteration_method.py | 6 +++--- audio_filters/show_response.py | 8 ++++---- backtracking/hamiltonian_cycle.py | 6 +++--- data_structures/binary_tree/avl_tree.py | 2 +- data_structures/linked_list/__init__.py | 2 +- .../linked_list/singly_linked_list.py | 4 ++-- data_structures/queue/double_ended_queue.py | 16 ++++++++-------- data_structures/stacks/next_greater_element.py | 12 ++++++------ digital_image_processing/index_calculation.py | 6 +++--- .../optimal_binary_search_tree.py | 6 +++--- graphs/a_star.py | 8 ++++---- graphs/dijkstra.py | 4 ++-- graphs/finding_bridges.py | 14 +++++++------- graphs/prim.py | 4 ++-- hashes/djb2.py | 6 +++--- hashes/sdbm.py | 8 +++++--- maths/armstrong_numbers.py | 12 ++++++------ maths/bailey_borwein_plouffe.py | 6 +++--- maths/kadanes.py | 8 ++++---- maths/prime_numbers.py | 14 +++++++------- maths/sum_of_arithmetic_series.py | 4 ++-- neural_network/2_hidden_layers_neural_network.py | 10 ++++++---- neural_network/convolution_neural_network.py | 10 +++++----- neural_network/perceptron.py | 4 ++-- project_euler/problem_065/sol1.py | 4 ++-- project_euler/problem_070/sol1.py | 6 +++--- sorts/odd_even_sort.py | 10 +++++----- strings/snake_case_to_camel_pascal_case.py | 8 ++++---- 31 files changed, 113 insertions(+), 106 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000000..9a5863c9cd0b --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +extend-ignore = + A003 # Class attribute is shadowing a python builtin diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f6a92814c66..e0de70b01883 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: - --ignore=E203,W503 - --max-complexity=25 - --max-line-length=88 - additional_dependencies: [pep8-naming] + additional_dependencies: [flake8-builtins, pep8-naming] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.982 diff --git a/arithmetic_analysis/gaussian_elimination.py b/arithmetic_analysis/gaussian_elimination.py index 89ed3b323d03..f0f20af8e417 100644 --- a/arithmetic_analysis/gaussian_elimination.py +++ b/arithmetic_analysis/gaussian_elimination.py @@ -33,11 +33,11 @@ def retroactive_resolution( x: NDArray[float64] = np.zeros((rows, 1), dtype=float) for row in reversed(range(rows)): - sum = 0 + total = 0 for col in range(row + 1, columns): - sum += coefficients[row, col] * x[col] + total += coefficients[row, col] * x[col] - x[row, 0] = (vector[row] - sum) / coefficients[row, row] + x[row, 0] = (vector[row] - total) / coefficients[row, row] return x diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/arithmetic_analysis/jacobi_iteration_method.py index 4336aaa91623..0aab4db20595 100644 --- a/arithmetic_analysis/jacobi_iteration_method.py +++ b/arithmetic_analysis/jacobi_iteration_method.py @@ -147,14 +147,14 @@ def strictly_diagonally_dominant(table: NDArray[float64]) -> bool: is_diagonally_dominant = True for i in range(0, rows): - sum = 0 + total = 0 for j in range(0, cols - 1): if i == j: continue else: - sum += table[i][j] + total += table[i][j] - if table[i][i] <= sum: + if table[i][i] <= total: raise ValueError("Coefficient matrix is not strictly diagonally dominant") return is_diagonally_dominant diff --git a/audio_filters/show_response.py b/audio_filters/show_response.py index 6e2731a58419..097b8152b4e6 100644 --- a/audio_filters/show_response.py +++ b/audio_filters/show_response.py @@ -34,7 +34,7 @@ def get_bounds( return lowest, highest -def show_frequency_response(filter: FilterType, samplerate: int) -> None: +def show_frequency_response(filter_type: FilterType, samplerate: int) -> None: """ Show frequency response of a filter @@ -45,7 +45,7 @@ def show_frequency_response(filter: FilterType, samplerate: int) -> None: size = 512 inputs = [1] + [0] * (size - 1) - outputs = [filter.process(item) for item in inputs] + outputs = [filter_type.process(item) for item in inputs] filler = [0] * (samplerate - size) # zero-padding outputs += filler @@ -66,7 +66,7 @@ def show_frequency_response(filter: FilterType, samplerate: int) -> None: plt.show() -def show_phase_response(filter: FilterType, samplerate: int) -> None: +def show_phase_response(filter_type: FilterType, samplerate: int) -> None: """ Show phase response of a filter @@ -77,7 +77,7 @@ def show_phase_response(filter: FilterType, samplerate: int) -> None: size = 512 inputs = [1] + [0] * (size - 1) - outputs = [filter.process(item) for item in inputs] + outputs = [filter_type.process(item) for item in inputs] filler = [0] * (samplerate - size) # zero-padding outputs += filler diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index 500e993e5c8b..4c6ae46799f4 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -95,10 +95,10 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) 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): + for next_ver in range(0, len(graph)): + if valid_connection(graph, next_ver, curr_ind, path): # Insert current vertex into path as next transition - path[curr_ind] = next + path[curr_ind] = next_ver # Validate created path if util_hamilton_cycle(graph, path, curr_ind + 1): return True diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 2f4bd60d9749..320e7ed0d792 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -33,7 +33,7 @@ def pop(self) -> Any: def count(self) -> int: return self.tail - self.head - def print(self) -> None: + def print_queue(self) -> None: print(self.data) print("**************") print(self.data[self.head : self.tail]) diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index 6ba660231ae1..85660a6d2c27 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -11,7 +11,7 @@ class Node: - def __init__(self, item: Any, next: Any) -> None: + def __init__(self, item: Any, next: Any) -> None: # noqa: A002 self.item = item self.next = next diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index a4156b650776..59d7c512bad7 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -392,7 +392,7 @@ def test_singly_linked_list_2() -> None: This section of the test used varying data types for input. >>> test_singly_linked_list_2() """ - input = [ + test_input = [ -9, 100, Node(77345112), @@ -410,7 +410,7 @@ def test_singly_linked_list_2() -> None: ] linked_list = LinkedList() - for i in input: + for i in test_input: linked_list.insert_tail(i) # Check if it's empty or not diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index f38874788df1..7053879d4512 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -15,8 +15,8 @@ class Deque: ---------- append(val: Any) -> None appendleft(val: Any) -> None - extend(iter: Iterable) -> None - extendleft(iter: Iterable) -> None + extend(iterable: Iterable) -> None + extendleft(iterable: Iterable) -> None pop() -> Any popleft() -> Any Observers @@ -179,9 +179,9 @@ def appendleft(self, val: Any) -> None: # make sure there were no errors assert not self.is_empty(), "Error on appending value." - def extend(self, iter: Iterable[Any]) -> None: + def extend(self, iterable: Iterable[Any]) -> None: """ - Appends every value of iter to the end of the deque. + Appends every value of iterable to the end of the deque. Time complexity: O(n) >>> our_deque_1 = Deque([1, 2, 3]) >>> our_deque_1.extend([4, 5]) @@ -205,12 +205,12 @@ def extend(self, iter: Iterable[Any]) -> None: >>> list(our_deque_2) == list(deque_collections_2) True """ - for val in iter: + for val in iterable: self.append(val) - def extendleft(self, iter: Iterable[Any]) -> None: + def extendleft(self, iterable: Iterable[Any]) -> None: """ - Appends every value of iter to the beginning of the deque. + Appends every value of iterable to the beginning of the deque. Time complexity: O(n) >>> our_deque_1 = Deque([1, 2, 3]) >>> our_deque_1.extendleft([0, -1]) @@ -234,7 +234,7 @@ def extendleft(self, iter: Iterable[Any]) -> None: >>> list(our_deque_2) == list(deque_collections_2) True """ - for val in iter: + for val in iterable: self.appendleft(val) def pop(self) -> Any: diff --git a/data_structures/stacks/next_greater_element.py b/data_structures/stacks/next_greater_element.py index 5bab7c609b67..7d76d1f47dfa 100644 --- a/data_structures/stacks/next_greater_element.py +++ b/data_structures/stacks/next_greater_element.py @@ -17,12 +17,12 @@ def next_greatest_element_slow(arr: list[float]) -> list[float]: arr_size = len(arr) for i in range(arr_size): - next: float = -1 + next_element: float = -1 for j in range(i + 1, arr_size): if arr[i] < arr[j]: - next = arr[j] + next_element = arr[j] break - result.append(next) + result.append(next_element) return result @@ -36,12 +36,12 @@ def next_greatest_element_fast(arr: list[float]) -> list[float]: """ result = [] for i, outer in enumerate(arr): - next: float = -1 + next_item: float = -1 for inner in arr[i + 1 :]: if outer < inner: - next = inner + next_item = inner break - result.append(next) + result.append(next_item) return result diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index 2f8fdc066919..01cd79fc18ff 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -497,9 +497,9 @@ def s(self): 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 + max_value = np.max([np.max(self.red), np.max(self.green), np.max(self.blue)]) + min_value = np.min([np.min(self.red), np.min(self.green), np.min(self.blue)]) + return (max_value - min_value) / max_value def _if(self): """ diff --git a/dynamic_programming/optimal_binary_search_tree.py b/dynamic_programming/optimal_binary_search_tree.py index 0d94c1b61d39..b4f1181ac11c 100644 --- a/dynamic_programming/optimal_binary_search_tree.py +++ b/dynamic_programming/optimal_binary_search_tree.py @@ -104,7 +104,7 @@ def find_optimal_binary_search_tree(nodes): 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)] + total = [[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)] @@ -113,14 +113,14 @@ def find_optimal_binary_search_tree(nodes): j = i + interval_length - 1 dp[i][j] = sys.maxsize # set the value to "infinity" - sum[i][j] = sum[i][j - 1] + freqs[j] + total[i][j] = total[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 + cost = left + total[i][j] + right if dp[i][j] > cost: dp[i][j] = cost diff --git a/graphs/a_star.py b/graphs/a_star.py index e0f24734a4cb..793ba3bda6b2 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -40,10 +40,10 @@ def search( 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] - y = next[3] - g = next[1] + next_cell = cell.pop() + x = next_cell[2] + y = next_cell[3] + g = next_cell[1] if x == goal[0] and y == goal[1]: found = True diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index 62c60f2c6be6..b0bdfab60649 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -56,8 +56,8 @@ def dijkstra(graph, start, end): for v, c in graph[u]: if v in visited: continue - next = cost + c - heapq.heappush(heap, (next, v)) + next_item = cost + c + heapq.heappush(heap, (next_item, v)) return -1 diff --git a/graphs/finding_bridges.py b/graphs/finding_bridges.py index 3813c4ebbd2a..c17606745ad8 100644 --- a/graphs/finding_bridges.py +++ b/graphs/finding_bridges.py @@ -72,22 +72,22 @@ def compute_bridges(graph: dict[int, list[int]]) -> list[tuple[int, int]]: [] """ - id = 0 + id_ = 0 n = len(graph) # No of vertices in graph low = [0] * n visited = [False] * n - def dfs(at, parent, bridges, id): + def dfs(at, parent, bridges, id_): visited[at] = True - low[at] = id - id += 1 + low[at] = id_ + id_ += 1 for to in graph[at]: if to == parent: pass elif not visited[to]: - dfs(to, at, bridges, id) + dfs(to, at, bridges, id_) low[at] = min(low[at], low[to]) - if id <= low[to]: + if id_ <= low[to]: bridges.append((at, to) if at < to else (to, at)) else: # This edge is a back edge and cannot be a bridge @@ -96,7 +96,7 @@ def dfs(at, parent, bridges, id): bridges: list[tuple[int, int]] = [] for i in range(n): if not visited[i]: - dfs(i, -1, bridges, id) + dfs(i, -1, bridges, id_) return bridges diff --git a/graphs/prim.py b/graphs/prim.py index 55d0fbfa8e96..6cb1a6def359 100644 --- a/graphs/prim.py +++ b/graphs/prim.py @@ -13,7 +13,7 @@ class Vertex: """Class Vertex.""" - def __init__(self, id): + def __init__(self, id_): """ Arguments: id - input an id to identify the vertex @@ -21,7 +21,7 @@ def __init__(self, id): neighbors - a list of the vertices it is linked to edges - a dict to store the edges's weight """ - self.id = str(id) + self.id = str(id_) self.key = None self.pi = None self.neighbors = [] diff --git a/hashes/djb2.py b/hashes/djb2.py index 2d1c9aabb1fb..4c84635098f2 100644 --- a/hashes/djb2.py +++ b/hashes/djb2.py @@ -29,7 +29,7 @@ def djb2(s: str) -> int: >>> djb2('scramble bits') 1609059040 """ - hash = 5381 + hash_value = 5381 for x in s: - hash = ((hash << 5) + hash) + ord(x) - return hash & 0xFFFFFFFF + hash_value = ((hash_value << 5) + hash_value) + ord(x) + return hash_value & 0xFFFFFFFF diff --git a/hashes/sdbm.py b/hashes/sdbm.py index daf292717f75..a5432874ba7d 100644 --- a/hashes/sdbm.py +++ b/hashes/sdbm.py @@ -31,7 +31,9 @@ def sdbm(plain_text: str) -> int: >>> sdbm('scramble bits') 730247649148944819640658295400555317318720608290373040936089 """ - hash = 0 + hash_value = 0 for plain_chr in plain_text: - hash = ord(plain_chr) + (hash << 6) + (hash << 16) - hash - return hash + hash_value = ( + ord(plain_chr) + (hash_value << 6) + (hash_value << 16) - hash_value + ) + return hash_value diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index 65aebe93722e..f62991b7415b 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -25,7 +25,7 @@ def armstrong_number(n: int) -> bool: return False # Initialization of sum and number of digits. - sum = 0 + total = 0 number_of_digits = 0 temp = n # Calculation of digits of the number @@ -36,9 +36,9 @@ def armstrong_number(n: int) -> bool: temp = n while temp > 0: rem = temp % 10 - sum += rem**number_of_digits + total += rem**number_of_digits temp //= 10 - return n == sum + return n == total def pluperfect_number(n: int) -> bool: @@ -55,7 +55,7 @@ def pluperfect_number(n: int) -> bool: # Init a "histogram" of the digits digit_histogram = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] digit_total = 0 - sum = 0 + total = 0 temp = n while temp > 0: temp, rem = divmod(temp, 10) @@ -63,9 +63,9 @@ def pluperfect_number(n: int) -> bool: digit_total += 1 for (cnt, i) in zip(digit_histogram, range(len(digit_histogram))): - sum += cnt * i**digit_total + total += cnt * i**digit_total - return n == sum + return n == total def narcissistic_number(n: int) -> bool: diff --git a/maths/bailey_borwein_plouffe.py b/maths/bailey_borwein_plouffe.py index b647ae56dbac..389b1566e9de 100644 --- a/maths/bailey_borwein_plouffe.py +++ b/maths/bailey_borwein_plouffe.py @@ -67,7 +67,7 @@ def _subsum( @param precision: same as precision in main function @return: floating-point number whose integer part is not important """ - sum = 0.0 + total = 0.0 for sum_index in range(digit_pos_to_extract + precision): denominator = 8 * sum_index + denominator_addend if sum_index < digit_pos_to_extract: @@ -79,8 +79,8 @@ def _subsum( ) else: exponential_term = pow(16, digit_pos_to_extract - 1 - sum_index) - sum += exponential_term / denominator - return sum + total += exponential_term / denominator + return total if __name__ == "__main__": diff --git a/maths/kadanes.py b/maths/kadanes.py index d239d4a2589b..b23409e2b978 100644 --- a/maths/kadanes.py +++ b/maths/kadanes.py @@ -14,13 +14,13 @@ def negative_exist(arr: list) -> int: [-2, 0, 0, 0, 0] """ arr = arr or [0] - max = arr[0] + max_number = arr[0] for i in arr: if i >= 0: return 0 - elif max <= i: - max = i - return max + elif max_number <= i: + max_number = i + return max_number def kadanes(arr: list) -> int: diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index 7be4d3d95b0e..4e076fe317b4 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -2,7 +2,7 @@ from collections.abc import Generator -def slow_primes(max: int) -> Generator[int, None, None]: +def slow_primes(max_n: int) -> Generator[int, None, None]: """ Return a list of all primes numbers up to max. >>> list(slow_primes(0)) @@ -20,7 +20,7 @@ def slow_primes(max: int) -> Generator[int, None, None]: >>> list(slow_primes(10000))[-1] 9973 """ - numbers: Generator = (i for i in range(1, (max + 1))) + numbers: Generator = (i for i in range(1, (max_n + 1))) for i in (n for n in numbers if n > 1): for j in range(2, i): if (i % j) == 0: @@ -29,7 +29,7 @@ def slow_primes(max: int) -> Generator[int, None, None]: yield i -def primes(max: int) -> Generator[int, None, None]: +def primes(max_n: int) -> Generator[int, None, None]: """ Return a list of all primes numbers up to max. >>> list(primes(0)) @@ -47,7 +47,7 @@ def primes(max: int) -> Generator[int, None, None]: >>> list(primes(10000))[-1] 9973 """ - numbers: Generator = (i for i in range(1, (max + 1))) + numbers: Generator = (i for i in range(1, (max_n + 1))) for i in (n for n in numbers if n > 1): # only need to check for factors up to sqrt(i) bound = int(math.sqrt(i)) + 1 @@ -58,7 +58,7 @@ def primes(max: int) -> Generator[int, None, None]: yield i -def fast_primes(max: int) -> Generator[int, None, None]: +def fast_primes(max_n: int) -> Generator[int, None, None]: """ Return a list of all primes numbers up to max. >>> list(fast_primes(0)) @@ -76,9 +76,9 @@ def fast_primes(max: int) -> Generator[int, None, None]: >>> list(fast_primes(10000))[-1] 9973 """ - numbers: Generator = (i for i in range(1, (max + 1), 2)) + numbers: Generator = (i for i in range(1, (max_n + 1), 2)) # It's useless to test even numbers as they will not be prime - if max > 2: + if max_n > 2: yield 2 # Because 2 will not be tested, it's necessary to yield it now for i in (n for n in numbers if n > 1): bound = int(math.sqrt(i)) + 1 diff --git a/maths/sum_of_arithmetic_series.py b/maths/sum_of_arithmetic_series.py index e0e22760bfbe..3e381b8c20a8 100644 --- a/maths/sum_of_arithmetic_series.py +++ b/maths/sum_of_arithmetic_series.py @@ -8,9 +8,9 @@ def sum_of_series(first_term: int, common_diff: int, num_of_terms: int) -> float >>> sum_of_series(1, 10, 100) 49600.0 """ - sum = (num_of_terms / 2) * (2 * first_term + (num_of_terms - 1) * common_diff) + total = (num_of_terms / 2) * (2 * first_term + (num_of_terms - 1) * common_diff) # formula for sum of series - return sum + return total def main(): diff --git a/neural_network/2_hidden_layers_neural_network.py b/neural_network/2_hidden_layers_neural_network.py index 1cf78ec4c7c0..9c5772326165 100644 --- a/neural_network/2_hidden_layers_neural_network.py +++ b/neural_network/2_hidden_layers_neural_network.py @@ -182,7 +182,7 @@ def train(self, output: numpy.ndarray, iterations: int, give_loss: bool) -> None loss = numpy.mean(numpy.square(output - self.feedforward())) print(f"Iteration {iteration} Loss: {loss}") - def predict(self, input: numpy.ndarray) -> int: + def predict(self, input_arr: numpy.ndarray) -> int: """ Predict's the output for the given input values using the trained neural network. @@ -201,7 +201,7 @@ def predict(self, input: numpy.ndarray) -> int: """ # Input values for which the predictions are to be made. - self.array = input + self.array = input_arr self.layer_between_input_and_first_hidden_layer = sigmoid( numpy.dot(self.array, self.input_layer_and_first_hidden_layer_weights) @@ -264,7 +264,7 @@ def example() -> int: True """ # Input values. - input = numpy.array( + test_input = numpy.array( ( [0, 0, 0], [0, 0, 1], @@ -282,7 +282,9 @@ def example() -> int: 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) + neural_network = TwoHiddenLayerNeuralNetwork( + input_array=test_input, output_array=output + ) # Calling training function. # Set give_loss to True if you want to see loss in every iteration. diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index bbade1c417d0..9dfb6d091412 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -140,24 +140,24 @@ 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, pooling_type="average_pool"): # pooling process size_map = len(featuremaps[0]) size_pooled = int(size_map / size_pooling) featuremap_pooled = [] for i_map in range(len(featuremaps)): - map = featuremaps[i_map] + feature_map = featuremaps[i_map] map_pooled = [] for i_focus in range(0, size_map, size_pooling): for j_focus in range(0, size_map, size_pooling): - focus = map[ + focus = feature_map[ i_focus : i_focus + size_pooling, j_focus : j_focus + size_pooling, ] - if type == "average_pool": + if pooling_type == "average_pool": # average pooling map_pooled.append(np.average(focus)) - elif type == "max_pooling": + elif pooling_type == "max_pooling": # max pooling map_pooled.append(np.max(focus)) map_pooled = np.asmatrix(map_pooled).reshape(size_pooled, size_pooled) diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 063be5ea554c..a2bfdb326d77 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -182,7 +182,7 @@ def sign(self, u: float) -> int: [0.2012, 0.2611, 5.4631], ] -exit = [ +target = [ -1, -1, -1, @@ -222,7 +222,7 @@ def sign(self, u: float) -> int: doctest.testmod() network = Perceptron( - sample=samples, target=exit, learning_rate=0.01, epoch_number=1000, bias=-1 + sample=samples, target=target, learning_rate=0.01, epoch_number=1000, bias=-1 ) network.training() print("Finished training perceptron") diff --git a/project_euler/problem_065/sol1.py b/project_euler/problem_065/sol1.py index 229769a77d07..0a00cf4773d7 100644 --- a/project_euler/problem_065/sol1.py +++ b/project_euler/problem_065/sol1.py @@ -71,7 +71,7 @@ def sum_digits(num: int) -> int: return digit_sum -def solution(max: int = 100) -> int: +def solution(max_n: int = 100) -> int: """ Returns the sum of the digits in the numerator of the max-th convergent of the continued fraction for e. @@ -86,7 +86,7 @@ def solution(max: int = 100) -> int: pre_numerator = 1 cur_numerator = 2 - for i in range(2, max + 1): + for i in range(2, max_n + 1): temp = pre_numerator e_cont = 2 * i // 3 if i % 3 == 0 else 1 pre_numerator = cur_numerator diff --git a/project_euler/problem_070/sol1.py b/project_euler/problem_070/sol1.py index d42b017cc476..273f37efc5fc 100644 --- a/project_euler/problem_070/sol1.py +++ b/project_euler/problem_070/sol1.py @@ -72,7 +72,7 @@ def has_same_digits(num1: int, num2: int) -> bool: return sorted(str(num1)) == sorted(str(num2)) -def solution(max: int = 10000000) -> int: +def solution(max_n: int = 10000000) -> int: """ Finds the value of n from 1 to max such that n/φ(n) produces a minimum. @@ -85,9 +85,9 @@ def solution(max: int = 10000000) -> int: min_numerator = 1 # i min_denominator = 0 # φ(i) - totients = get_totients(max + 1) + totients = get_totients(max_n + 1) - for i in range(2, max + 1): + for i in range(2, max_n + 1): t = totients[i] if i * min_denominator < min_numerator * t and has_same_digits(i, t): diff --git a/sorts/odd_even_sort.py b/sorts/odd_even_sort.py index 557337ee77bc..532f829499e8 100644 --- a/sorts/odd_even_sort.py +++ b/sorts/odd_even_sort.py @@ -20,21 +20,21 @@ def odd_even_sort(input_list: list) -> list: >>> odd_even_sort([1 ,2 ,3 ,4]) [1, 2, 3, 4] """ - sorted = False - while sorted is False: # Until all the indices are traversed keep looping - sorted = True + is_sorted = False + while is_sorted is False: # Until all the indices are traversed keep looping + is_sorted = True for i in range(0, len(input_list) - 1, 2): # iterating over all even indices if input_list[i] > input_list[i + 1]: input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] # swapping if elements not in order - sorted = False + is_sorted = False for i in range(1, len(input_list) - 1, 2): # iterating over all odd indices if input_list[i] > input_list[i + 1]: input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] # swapping if elements not in order - sorted = False + is_sorted = False return input_list diff --git a/strings/snake_case_to_camel_pascal_case.py b/strings/snake_case_to_camel_pascal_case.py index 7b2b61d1d1cf..eaabdcb87a0f 100644 --- a/strings/snake_case_to_camel_pascal_case.py +++ b/strings/snake_case_to_camel_pascal_case.py @@ -1,4 +1,4 @@ -def snake_to_camel_case(input: str, use_pascal: bool = False) -> str: +def snake_to_camel_case(input_str: str, use_pascal: bool = False) -> str: """ Transforms a snake_case given string to camelCase (or PascalCase if indicated) (defaults to not use Pascal) @@ -26,14 +26,14 @@ def snake_to_camel_case(input: str, use_pascal: bool = False) -> str: ValueError: Expected boolean as use_pascal parameter, found """ - if not isinstance(input, str): - raise ValueError(f"Expected string as input, found {type(input)}") + if not isinstance(input_str, str): + raise ValueError(f"Expected string as input, found {type(input_str)}") if not isinstance(use_pascal, bool): raise ValueError( f"Expected boolean as use_pascal parameter, found {type(use_pascal)}" ) - words = input.split("_") + words = input_str.split("_") start_index = 0 if use_pascal else 1 From f176786d12ead5796644a9b37d96786cdaa55391 Mon Sep 17 00:00:00 2001 From: Praveen Date: Thu, 13 Oct 2022 21:04:52 +0530 Subject: [PATCH 1884/2908] Update open_google_results.py (#7085) * update crawl_google_results.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename crawl_google_results.py to open_google_results.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Create crawl_google_results.py * Update web_programming/open_google_results.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update open_google_results.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- web_programming/open_google_results.py | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 web_programming/open_google_results.py diff --git a/web_programming/open_google_results.py b/web_programming/open_google_results.py new file mode 100644 index 000000000000..0e1dba8c5856 --- /dev/null +++ b/web_programming/open_google_results.py @@ -0,0 +1,42 @@ +import webbrowser +from sys import argv +from urllib.parse import quote, parse_qs +from fake_useragent import UserAgent + +import requests +from bs4 import BeautifulSoup + +if __name__ == "__main__": + if len(argv) > 1: + query = "%20".join(argv[1:]) + else: + query = quote(str(input("Search: "))) + + print("Googling.....") + + url = f"https://www.google.com/search?q={query}&num=100" + + res = requests.get( + url, + headers={ + "User-Agent": str(UserAgent().random) + }, + ) + + try: + link = ( + BeautifulSoup(res.text, "html.parser") + .find("div", attrs={"class": "yuRUbf"}) + .find("a") + .get("href") + ) + + except AttributeError: + link = parse_qs( + BeautifulSoup(res.text, "html.parser") + .find("div", attrs={"class": "kCrYT"}) + .find("a") + .get("href") + )["url"][0] + + webbrowser.open(link) From 4d0c830d2c7a4a535501887a8eb97966a370ef57 Mon Sep 17 00:00:00 2001 From: Caeden Date: Thu, 13 Oct 2022 17:03:06 +0100 Subject: [PATCH 1885/2908] Add flake8 pluin flake8 bugbear to pre-commit (#7132) * ci(pre-commit): Add ``flake8-builtins`` additional dependency to ``pre-commit`` (#7104) * refactor: Fix ``flake8-builtins`` (#7104) * fix(lru_cache): Fix naming conventions in docstrings (#7104) * ci(pre-commit): Order additional dependencies alphabetically (#7104) * fix(lfu_cache): Correct function name in docstring (#7104) * Update strings/snake_case_to_camel_pascal_case.py Co-authored-by: Christian Clauss * Update data_structures/stacks/next_greater_element.py Co-authored-by: Christian Clauss * Update digital_image_processing/index_calculation.py Co-authored-by: Christian Clauss * Update graphs/prim.py Co-authored-by: Christian Clauss * Update hashes/djb2.py Co-authored-by: Christian Clauss * refactor: Rename `_builtin` to `builtin_` ( #7104) * fix: Rename all instances (#7104) * refactor: Update variable names (#7104) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ci: Create ``tox.ini`` and ignore ``A003`` (#7123) * revert: Remove function name changes (#7104) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename tox.ini to .flake8 * Update data_structures/heap/heap.py Co-authored-by: Dhruv Manilawala * refactor: Rename `next_` to `next_item` (#7104) * ci(pre-commit): Add `flake8` plugin `flake8-bugbear` (#7127) * refactor: Follow `flake8-bugbear` plugin (#7127) * fix: Correct `knapsack` code (#7127) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Dhruv Manilawala --- .pre-commit-config.yaml | 5 +++- .../jacobi_iteration_method.py | 2 +- .../newton_forward_interpolation.py | 2 +- arithmetic_analysis/secant_method.py | 2 +- audio_filters/butterworth_filter.py | 23 +++++++++++++------ backtracking/sum_of_subsets.py | 8 +++---- boolean_algebra/quine_mc_cluskey.py | 2 +- ciphers/mixed_keyword_cypher.py | 2 +- ciphers/rabin_miller.py | 2 +- compression/burrows_wheeler.py | 2 +- .../binary_search_tree_recursive.py | 8 +++---- .../linked_list/circular_linked_list.py | 8 +++---- .../linked_list/doubly_linked_list.py | 8 +++---- .../middle_element_of_linked_list.py | 2 +- .../linked_list/singly_linked_list.py | 6 ++--- data_structures/linked_list/skip_list.py | 4 ++-- data_structures/queue/queue_on_list.py | 2 +- .../queue/queue_on_pseudo_stack.py | 2 +- data_structures/stacks/stack.py | 6 ++--- divide_and_conquer/convex_hull.py | 8 +++---- .../strassen_matrix_multiplication.py | 6 ++--- dynamic_programming/all_construct.py | 2 +- dynamic_programming/knapsack.py | 10 ++++---- fractals/julia_sets.py | 2 +- fractals/koch_snowflake.py | 2 +- fractals/mandelbrot.py | 2 +- genetic_algorithm/basic_string.py | 7 ++++-- graphs/basic_graphs.py | 4 ++-- graphs/bellman_ford.py | 2 +- graphs/dijkstra_2.py | 18 +++++++-------- graphs/frequent_pattern_graph_miner.py | 2 +- graphs/kahns_algorithm_long.py | 2 +- graphs/kahns_algorithm_topo.py | 2 +- graphs/minimum_spanning_tree_prims.py | 2 +- graphs/page_rank.py | 2 +- graphs/scc_kosaraju.py | 4 ++-- greedy_methods/optimal_merge_pattern.py | 2 +- hashes/chaos_machine.py | 2 +- hashes/enigma_machine.py | 2 +- machine_learning/self_organizing_map.py | 2 +- maths/area_under_curve.py | 2 +- maths/line_length.py | 2 +- maths/lucas_lehmer_primality_test.py | 2 +- maths/lucas_series.py | 2 +- maths/miller_rabin.py | 2 +- maths/monte_carlo_dice.py | 2 +- maths/numerical_integration.py | 2 +- maths/pi_monte_carlo_estimation.py | 2 +- maths/pollard_rho.py | 2 +- maths/primelib.py | 8 +++---- maths/proth_number.py | 2 +- maths/square_root.py | 2 +- maths/ugly_numbers.py | 2 +- matrix/matrix_class.py | 2 +- ...h_fibonacci_using_matrix_exponentiation.py | 2 +- .../back_propagation_neural_network.py | 2 +- neural_network/perceptron.py | 2 +- other/lfu_cache.py | 2 +- other/lru_cache.py | 2 +- other/magicdiamondpattern.py | 8 +++---- other/scoring_algorithm.py | 2 +- physics/lorentz_transformation_four_vector.py | 2 +- physics/n_body_simulation.py | 2 +- project_euler/problem_011/sol2.py | 2 +- project_euler/problem_025/sol3.py | 2 +- project_euler/problem_026/sol1.py | 2 +- project_euler/problem_188/sol1.py | 2 +- project_euler/problem_203/sol1.py | 2 +- scheduling/multi_level_feedback_queue.py | 2 +- sorts/double_sort.py | 2 +- web_programming/open_google_results.py | 8 +++---- 71 files changed, 137 insertions(+), 124 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0de70b01883..d2558b90abb1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,10 @@ repos: - --ignore=E203,W503 - --max-complexity=25 - --max-line-length=88 - additional_dependencies: [flake8-builtins, pep8-naming] + additional_dependencies: + - flake8-bugbear + - flake8-builtins + - pep8-naming - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.982 diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/arithmetic_analysis/jacobi_iteration_method.py index 0aab4db20595..3087309e8c3d 100644 --- a/arithmetic_analysis/jacobi_iteration_method.py +++ b/arithmetic_analysis/jacobi_iteration_method.py @@ -110,7 +110,7 @@ def jacobi_iteration_method( strictly_diagonally_dominant(table) # Iterates the whole matrix for given number of times - for i in range(iterations): + for _ in range(iterations): new_val = [] for row in range(rows): temp = 0 diff --git a/arithmetic_analysis/newton_forward_interpolation.py b/arithmetic_analysis/newton_forward_interpolation.py index 490e0687f15f..466f6c18cf59 100644 --- a/arithmetic_analysis/newton_forward_interpolation.py +++ b/arithmetic_analysis/newton_forward_interpolation.py @@ -23,7 +23,7 @@ def ucal(u: float, p: int) -> float: def main() -> None: n = int(input("enter the numbers of values: ")) y: list[list[float]] = [] - for i in range(n): + for _ in range(n): y.append([]) for i in range(n): for j in range(n): diff --git a/arithmetic_analysis/secant_method.py b/arithmetic_analysis/secant_method.py index 45bcb185fc3e..d28a46206d40 100644 --- a/arithmetic_analysis/secant_method.py +++ b/arithmetic_analysis/secant_method.py @@ -20,7 +20,7 @@ def secant_method(lower_bound: float, upper_bound: float, repeats: int) -> float """ x0 = lower_bound x1 = upper_bound - for i in range(0, repeats): + for _ in range(0, repeats): x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0)) return x1 diff --git a/audio_filters/butterworth_filter.py b/audio_filters/butterworth_filter.py index 409cfeb1d95c..cffedb7a68fd 100644 --- a/audio_filters/butterworth_filter.py +++ b/audio_filters/butterworth_filter.py @@ -11,7 +11,7 @@ def make_lowpass( - frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) + frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008 ) -> IIRFilter: """ Creates a low-pass filter @@ -39,7 +39,7 @@ def make_lowpass( def make_highpass( - frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) + frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008 ) -> IIRFilter: """ Creates a high-pass filter @@ -67,7 +67,7 @@ def make_highpass( def make_bandpass( - frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) + frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008 ) -> IIRFilter: """ Creates a band-pass filter @@ -96,7 +96,7 @@ def make_bandpass( def make_allpass( - frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) + frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008 ) -> IIRFilter: """ Creates an all-pass filter @@ -121,7 +121,10 @@ def make_allpass( def make_peak( - frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2) + frequency: int, + samplerate: int, + gain_db: float, + q_factor: float = 1 / sqrt(2), # noqa: B008 ) -> IIRFilter: """ Creates a peak filter @@ -150,7 +153,10 @@ def make_peak( def make_lowshelf( - frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2) + frequency: int, + samplerate: int, + gain_db: float, + q_factor: float = 1 / sqrt(2), # noqa: B008 ) -> IIRFilter: """ Creates a low-shelf filter @@ -184,7 +190,10 @@ def make_lowshelf( def make_highshelf( - frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2) + frequency: int, + samplerate: int, + gain_db: float, + q_factor: float = 1 / sqrt(2), # noqa: B008 ) -> IIRFilter: """ Creates a high-shelf filter diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index 8348544c0175..128e290718cd 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -39,14 +39,14 @@ def create_state_space_tree( if sum(path) == max_sum: result.append(path) return - for num_index in range(num_index, len(nums)): + for index in range(num_index, len(nums)): create_state_space_tree( nums, max_sum, - num_index + 1, - path + [nums[num_index]], + index + 1, + path + [nums[index]], result, - remaining_nums_sum - nums[num_index], + remaining_nums_sum - nums[index], ) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 9aa9b10c8429..5bd7117bb3e7 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -56,7 +56,7 @@ def decimal_to_binary(no_of_variable: int, minterms: Sequence[float]) -> list[st temp = [] for minterm in minterms: string = "" - for i in range(no_of_variable): + for _ in range(no_of_variable): string = str(minterm % 2) + string minterm //= 2 temp.append(string) diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index 178902173477..f55c9c4286df 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -40,7 +40,7 @@ def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: k = 0 for _ in range(r): s = [] - for j in range(len_temp): + for _ in range(len_temp): s.append(temp[k]) if not (k < 25): break diff --git a/ciphers/rabin_miller.py b/ciphers/rabin_miller.py index 0aab80eb9175..410d559d4315 100644 --- a/ciphers/rabin_miller.py +++ b/ciphers/rabin_miller.py @@ -11,7 +11,7 @@ def rabin_miller(num: int) -> bool: s = s // 2 t += 1 - for trials in range(5): + for _ in range(5): a = random.randrange(2, num - 1) v = pow(a, s, num) if v != 1: diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 4ad99a642e49..0916b8a654d2 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -154,7 +154,7 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: ) ordered_rotations = [""] * len(bwt_string) - for x in range(len(bwt_string)): + for _ in range(len(bwt_string)): for i in range(len(bwt_string)): ordered_rotations[i] = bwt_string[i] + ordered_rotations[i] ordered_rotations.sort() diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index 0d0ac8fd1e22..97eb8e25bedd 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -357,7 +357,7 @@ def test_put(self) -> None: assert t.root.left.left.parent == t.root.left assert t.root.left.left.label == 1 - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 t.put(1) def test_search(self) -> None: @@ -369,7 +369,7 @@ def test_search(self) -> None: node = t.search(13) assert node.label == 13 - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 t.search(2) def test_remove(self) -> None: @@ -515,7 +515,7 @@ def test_get_max_label(self) -> None: assert t.get_max_label() == 14 t.empty() - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 t.get_max_label() def test_get_min_label(self) -> None: @@ -524,7 +524,7 @@ def test_get_min_label(self) -> None: assert t.get_min_label() == 1 t.empty() - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 t.get_min_label() def test_inorder_traversal(self) -> None: diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 6fec0a12542f..67a63cd55e19 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -94,25 +94,25 @@ def test_circular_linked_list() -> None: try: circular_linked_list.delete_front() - assert False # This should not happen + raise AssertionError() # This should not happen except IndexError: assert True # This should happen try: circular_linked_list.delete_tail() - assert False # This should not happen + raise AssertionError() # This should not happen except IndexError: assert True # This should happen try: circular_linked_list.delete_nth(-1) - assert False + raise AssertionError() except IndexError: assert True try: circular_linked_list.delete_nth(0) - assert False + raise AssertionError() except IndexError: assert True diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 0eb3cf101a3e..9e996ef0fb9d 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -96,7 +96,7 @@ def insert_at_nth(self, index: int, data): self.tail = new_node else: temp = self.head - for i in range(0, index): + for _ in range(0, index): temp = temp.next temp.previous.next = new_node new_node.previous = temp.previous @@ -145,7 +145,7 @@ def delete_at_nth(self, index: int): self.tail.next = None else: temp = self.head - for i in range(0, index): + for _ in range(0, index): temp = temp.next delete_node = temp temp.next.previous = temp.previous @@ -194,13 +194,13 @@ def test_doubly_linked_list() -> None: try: linked_list.delete_head() - assert False # This should not happen. + raise AssertionError() # This should not happen. except IndexError: assert True # This should happen. try: linked_list.delete_tail() - assert False # This should not happen. + raise AssertionError() # This should not happen. except IndexError: assert True # This should happen. 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 0c6250f3b731..86dad6b41d73 100644 --- a/data_structures/linked_list/middle_element_of_linked_list.py +++ b/data_structures/linked_list/middle_element_of_linked_list.py @@ -62,7 +62,7 @@ def middle_element(self) -> int | None: if __name__ == "__main__": link = LinkedList() - for i in range(int(input().strip())): + for _ in range(int(input().strip())): data = int(input().strip()) link.push(data) print(link.middle_element()) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 59d7c512bad7..89a05ae81d4c 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -132,7 +132,7 @@ def __setitem__(self, index: int, data: Any) -> None: if not 0 <= index < len(self): raise ValueError("list index out of range.") current = self.head - for i in range(index): + for _ in range(index): current = current.next current.data = data @@ -352,13 +352,13 @@ def test_singly_linked_list() -> None: try: linked_list.delete_head() - assert False # This should not happen. + raise AssertionError() # This should not happen. except IndexError: assert True # This should happen. try: linked_list.delete_tail() - assert False # This should not happen. + raise AssertionError() # This should not happen. except IndexError: assert True # This should happen. diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index 176049120aab..a667e3e9bc84 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -205,7 +205,7 @@ def insert(self, key: KT, value: VT): if level > self.level: # After level increase we have to add additional nodes to head. - for i in range(self.level - 1, level): + for _ in range(self.level - 1, level): update_vector.append(self.head) self.level = level @@ -407,7 +407,7 @@ def is_sorted(lst): def pytests(): - for i in range(100): + for _ in range(100): # Repeat test 100 times due to the probabilistic nature of skip list # random values == random bugs test_insert() diff --git a/data_structures/queue/queue_on_list.py b/data_structures/queue/queue_on_list.py index 485cf0b6f7a3..71fca6b2f5f4 100644 --- a/data_structures/queue/queue_on_list.py +++ b/data_structures/queue/queue_on_list.py @@ -37,7 +37,7 @@ def get(self): number of times to rotate queue""" def rotate(self, rotation): - for i in range(rotation): + for _ in range(rotation): self.put(self.get()) """Enqueues {@code item} diff --git a/data_structures/queue/queue_on_pseudo_stack.py b/data_structures/queue/queue_on_pseudo_stack.py index 9a0c16f61eb4..d9845100008e 100644 --- a/data_structures/queue/queue_on_pseudo_stack.py +++ b/data_structures/queue/queue_on_pseudo_stack.py @@ -37,7 +37,7 @@ def get(self) -> Any: number of times to rotate queue""" def rotate(self, rotation: int) -> None: - for i in range(rotation): + for _ in range(rotation): temp = self.stack[0] self.stack = self.stack[1:] self.put(temp) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index d1c73df43067..55d424d5018b 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -92,13 +92,13 @@ def test_stack() -> None: try: _ = stack.pop() - assert False # This should not happen + raise AssertionError() # This should not happen except StackUnderflowError: assert True # This should happen try: _ = stack.peek() - assert False # This should not happen + raise AssertionError() # This should not happen except StackUnderflowError: assert True # This should happen @@ -118,7 +118,7 @@ def test_stack() -> None: try: stack.push(200) - assert False # This should not happen + raise AssertionError() # This should not happen except StackOverflowError: assert True # This should happen diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 72da116398a9..39e78be04a71 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -458,16 +458,16 @@ def convex_hull_melkman(points: list[Point]) -> list[Point]: convex_hull[1] = points[i] i += 1 - for i in range(i, n): + for j in range(i, n): if ( - _det(convex_hull[0], convex_hull[-1], points[i]) > 0 + _det(convex_hull[0], convex_hull[-1], points[j]) > 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]) + convex_hull.insert(0, points[j]) + convex_hull.append(points[j]) 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: diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index 17efcfc7c8ee..0ee426e4b39a 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -132,12 +132,12 @@ def strassen(matrix1: list, matrix2: list) -> list: # power of 2 for i in range(0, maxim): if i < dimension1[0]: - for j in range(dimension1[1], maxim): + for _ 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): + for _ in range(dimension2[1], maxim): new_matrix2[i].append(0) else: new_matrix2.append([0] * maxim) @@ -147,7 +147,7 @@ def strassen(matrix1: list, matrix2: list) -> list: # Removing the additional zeros for i in range(0, maxim): if i < dimension1[0]: - for j in range(dimension2[1], maxim): + for _ in range(dimension2[1], maxim): final_matrix[i].pop() else: final_matrix.pop() diff --git a/dynamic_programming/all_construct.py b/dynamic_programming/all_construct.py index 5ffed2caa182..3839d01e6db0 100644 --- a/dynamic_programming/all_construct.py +++ b/dynamic_programming/all_construct.py @@ -21,7 +21,7 @@ def all_construct(target: str, word_bank: list[str] | None = None) -> list[list[ table_size: int = len(target) + 1 table: list[list[list[str]]] = [] - for i in range(table_size): + for _ in range(table_size): table.append([]) # seed value table[0] = [[]] # because empty string has empty combination diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 9efb60bab98b..093e15f49ba0 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -30,13 +30,13 @@ 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] + dp[i][w_] = dp[i - 1][w_] - return dp[n][w], dp + return dp[n][w_], dp def knapsack_with_example_solution(w: int, wt: list, val: list): diff --git a/fractals/julia_sets.py b/fractals/julia_sets.py index f273943851fc..28c675c750bc 100644 --- a/fractals/julia_sets.py +++ b/fractals/julia_sets.py @@ -118,7 +118,7 @@ def iterate_function( """ z_n = z_0.astype("complex64") - for i in range(nb_iterations): + for _ in range(nb_iterations): z_n = eval_function(function_params, z_n) if infinity is not None: numpy.nan_to_num(z_n, copy=False, nan=infinity) diff --git a/fractals/koch_snowflake.py b/fractals/koch_snowflake.py index 07c1835b41ed..b0aaa86b11d8 100644 --- a/fractals/koch_snowflake.py +++ b/fractals/koch_snowflake.py @@ -46,7 +46,7 @@ def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndar 0.28867513]), array([0.66666667, 0. ]), array([1, 0])] """ vectors = initial_vectors - for i in range(steps): + for _ in range(steps): vectors = iteration_step(vectors) return vectors diff --git a/fractals/mandelbrot.py b/fractals/mandelbrot.py index 5d61b72e172f..f97bcd17031c 100644 --- a/fractals/mandelbrot.py +++ b/fractals/mandelbrot.py @@ -36,7 +36,7 @@ def get_distance(x: float, y: float, max_step: int) -> float: """ a = x b = y - for step in range(max_step): + for step in range(max_step): # noqa: B007 a_new = a * a - b * b + x b = 2 * a * b + y a = a_new diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index 97dbe182bc82..bd7d8026866c 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -80,7 +80,7 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]: score = len( [g for position, g in enumerate(item) if g == main_target[position]] ) - return (item, float(score)) + return (item, float(score)) # noqa: B023 # Adding a bit of concurrency can make everything faster, # @@ -129,7 +129,10 @@ def select(parent_1: tuple[str, float]) -> list[str]: 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] + parent_2 = population_score[ # noqa: B023 + 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)) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index b02e9af65846..298a97bf0e17 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -188,7 +188,7 @@ def topo(g, ind=None, q=None): def adjm(): n = input().strip() a = [] - for i in range(n): + for _ in range(n): a.append(map(int, input().strip().split())) return a, n @@ -264,7 +264,7 @@ def prim(g, s): def edglist(): n, m = map(int, input().split(" ")) edges = [] - for i in range(m): + for _ in range(m): edges.append(map(int, input().split(" "))) return edges, n diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index 0f654a510b59..eb2cd25bf682 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -36,7 +36,7 @@ def bellman_ford( distance = [float("inf")] * vertex_count distance[src] = 0.0 - for i in range(vertex_count - 1): + for _ in range(vertex_count - 1): for j in range(edge_count): u, v, w = (graph[j][k] for k in ["src", "dst", "weight"]) diff --git a/graphs/dijkstra_2.py b/graphs/dijkstra_2.py index 3170765bc8a8..f548463ff7bd 100644 --- a/graphs/dijkstra_2.py +++ b/graphs/dijkstra_2.py @@ -19,23 +19,23 @@ def min_dist(mdist, vset, v): def dijkstra(graph, v, src): - mdist = [float("inf") for i in range(v)] - vset = [False for i in range(v)] + mdist = [float("inf") for _ in range(v)] + vset = [False for _ in range(v)] mdist[src] = 0.0 - for i in range(v - 1): + for _ in range(v - 1): u = min_dist(mdist, vset, v) vset[u] = True - for v in range(v): + for i in range(v): if ( - (not vset[v]) - and graph[u][v] != float("inf") - and mdist[u] + graph[u][v] < mdist[v] + (not vset[i]) + and graph[u][i] != float("inf") + and mdist[u] + graph[u][i] < mdist[i] ): - mdist[v] = mdist[u] + graph[u][v] + mdist[i] = mdist[u] + graph[u][i] - print_dist(mdist, v) + print_dist(mdist, i) if __name__ == "__main__": diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index 50081afa6728..a5ecbe6e8223 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -79,7 +79,7 @@ def get_nodes(frequency_table): {'11111': ['ab', 'ac', 'df', 'bd', 'bc']} """ nodes = {} - for i, item in enumerate(frequency_table): + for _, item in enumerate(frequency_table): nodes.setdefault(item[2], []).append(item[0]) return nodes diff --git a/graphs/kahns_algorithm_long.py b/graphs/kahns_algorithm_long.py index 776ae3a2f903..63cbeb909a8a 100644 --- a/graphs/kahns_algorithm_long.py +++ b/graphs/kahns_algorithm_long.py @@ -4,7 +4,7 @@ def longest_distance(graph): queue = [] long_dist = [1] * len(graph) - for key, values in graph.items(): + for values in graph.values(): for i in values: indegree[i] += 1 diff --git a/graphs/kahns_algorithm_topo.py b/graphs/kahns_algorithm_topo.py index 6879b047fe35..b1260bd5bd9b 100644 --- a/graphs/kahns_algorithm_topo.py +++ b/graphs/kahns_algorithm_topo.py @@ -8,7 +8,7 @@ def topological_sort(graph): topo = [] cnt = 0 - for key, values in graph.items(): + for values in graph.values(): for i in values: indegree[i] += 1 diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 9b2c645f16df..5b2eaa4bff40 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -91,7 +91,7 @@ def delete_minimum(heap, positions): distance_tv[x[0]] = x[1] heapify(distance_tv, positions) - for i in range(1, len(l)): + for _ in range(1, len(l)): vertex = delete_minimum(distance_tv, positions) if visited[vertex] == 0: tree_edges.append((nbr_tv[vertex], vertex)) diff --git a/graphs/page_rank.py b/graphs/page_rank.py index 672405b7345b..e1af35b34749 100644 --- a/graphs/page_rank.py +++ b/graphs/page_rank.py @@ -41,7 +41,7 @@ def page_rank(nodes, limit=3, d=0.85): for i in range(limit): print(f"======= Iteration {i + 1} =======") - for j, node in enumerate(nodes): + for _, node in enumerate(nodes): ranks[node.name] = (1 - d) + d * sum( ranks[ib] / outbounds[ib] for ib in node.inbound ) diff --git a/graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py index ea9d35282858..39211c64b687 100644 --- a/graphs/scc_kosaraju.py +++ b/graphs/scc_kosaraju.py @@ -39,10 +39,10 @@ def kosaraju(): # n - no of nodes, m - no of edges n, m = list(map(int, input().strip().split())) - graph: list[list[int]] = [[] for i in range(n)] # graph + graph: list[list[int]] = [[] for _ in range(n)] # graph reversed_graph: list[list[int]] = [[] for i in range(n)] # reversed graph # input graph data (edges) - for i in range(m): + for _ in range(m): u, v = list(map(int, input().strip().split())) graph[u].append(v) reversed_graph[v].append(u) diff --git a/greedy_methods/optimal_merge_pattern.py b/greedy_methods/optimal_merge_pattern.py index 911e1966f3b9..a1c934f84498 100644 --- a/greedy_methods/optimal_merge_pattern.py +++ b/greedy_methods/optimal_merge_pattern.py @@ -41,7 +41,7 @@ def optimal_merge_pattern(files: list) -> float: while len(files) > 1: temp = 0 # Consider two files with minimum cost to be merged - for i in range(2): + for _ in range(2): min_index = files.index(min(files)) temp += files[min_index] files.pop(min_index) diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 69313fbb2065..238fdb1c0634 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -53,7 +53,7 @@ def xorshift(x, y): key = machine_time % m # Evolution (Time Length) - for i in range(0, t): + for _ in range(0, t): # Variables (Position + Parameters) r = params_space[key] value = buffer_space[key] diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py index b0d45718e286..0194f7da7d6f 100644 --- a/hashes/enigma_machine.py +++ b/hashes/enigma_machine.py @@ -48,7 +48,7 @@ def engine(input_character): break except Exception as error: print(error) - for i in range(token): + for _ in range(token): rotator() for j in decode: engine(j) diff --git a/machine_learning/self_organizing_map.py b/machine_learning/self_organizing_map.py index bd3d388f910f..057c2a76b8ac 100644 --- a/machine_learning/self_organizing_map.py +++ b/machine_learning/self_organizing_map.py @@ -47,7 +47,7 @@ def main() -> None: epochs = 3 alpha = 0.5 - for i in range(epochs): + for _ in range(epochs): for j in range(len(training_samples)): # training sample diff --git a/maths/area_under_curve.py b/maths/area_under_curve.py index d345398b4c2c..b557b2029657 100644 --- a/maths/area_under_curve.py +++ b/maths/area_under_curve.py @@ -35,7 +35,7 @@ def trapezoidal_area( x1 = x_start fx1 = fnc(x_start) area = 0.0 - for i in range(steps): + for _ in range(steps): # Approximates small segments of curve as linear and solve # for trapezoidal area x2 = (x_end - x_start) / steps + x1 diff --git a/maths/line_length.py b/maths/line_length.py index ad12a816b93e..ea27ee904a24 100644 --- a/maths/line_length.py +++ b/maths/line_length.py @@ -40,7 +40,7 @@ def line_length( fx1 = fnc(x_start) length = 0.0 - for i in range(steps): + for _ in range(steps): # Approximates curve as a sequence of linear lines and sums their length x2 = (x_end - x_start) / steps + x1 diff --git a/maths/lucas_lehmer_primality_test.py b/maths/lucas_lehmer_primality_test.py index 916abfcc175e..0a5621aacd79 100644 --- a/maths/lucas_lehmer_primality_test.py +++ b/maths/lucas_lehmer_primality_test.py @@ -31,7 +31,7 @@ def lucas_lehmer_test(p: int) -> bool: s = 4 m = (1 << p) - 1 - for i in range(p - 2): + for _ in range(p - 2): s = ((s * s) - 2) % m return s == 0 diff --git a/maths/lucas_series.py b/maths/lucas_series.py index 6b32c2022e13..cae6c2815aec 100644 --- a/maths/lucas_series.py +++ b/maths/lucas_series.py @@ -50,7 +50,7 @@ def dynamic_lucas_number(n_th_number: int) -> int: if not isinstance(n_th_number, int): raise TypeError("dynamic_lucas_number accepts only integer arguments.") a, b = 2, 1 - for i in range(n_th_number): + for _ in range(n_th_number): a, b = b, a + b return a diff --git a/maths/miller_rabin.py b/maths/miller_rabin.py index b4dfed1290de..9f2668dbab14 100644 --- a/maths/miller_rabin.py +++ b/maths/miller_rabin.py @@ -33,7 +33,7 @@ def is_prime_big(n, prec=1000): b = bin_exp_mod(a, d, n) if b != 1: flag = True - for i in range(exp): + for _ in range(exp): if b == n - 1: flag = False break diff --git a/maths/monte_carlo_dice.py b/maths/monte_carlo_dice.py index 17cedbdbcb18..c4150b88f6cc 100644 --- a/maths/monte_carlo_dice.py +++ b/maths/monte_carlo_dice.py @@ -35,7 +35,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): + for _ 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 diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index a2bfce5b911d..8f32fd3564df 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -39,7 +39,7 @@ def trapezoidal_area( fx1 = fnc(x_start) area = 0.0 - for i in range(steps): + for _ in range(steps): # Approximates small segments of curve as linear and solve # for trapezoidal area diff --git a/maths/pi_monte_carlo_estimation.py b/maths/pi_monte_carlo_estimation.py index 81be083787bd..29b679907239 100644 --- a/maths/pi_monte_carlo_estimation.py +++ b/maths/pi_monte_carlo_estimation.py @@ -47,7 +47,7 @@ def estimate_pi(number_of_simulations: int) -> float: raise ValueError("At least one simulation is necessary to estimate PI.") number_in_unit_circle = 0 - for simulation_index in range(number_of_simulations): + for _ in range(number_of_simulations): random_point = Point.random_unit_square() if random_point.is_in_unit_circle(): diff --git a/maths/pollard_rho.py b/maths/pollard_rho.py index 0fc80cd4280b..5082f54f71a8 100644 --- a/maths/pollard_rho.py +++ b/maths/pollard_rho.py @@ -73,7 +73,7 @@ def rand_fn(value: int, step: int, modulus: int) -> int: """ return (pow(value, 2) + step) % modulus - for attempt in range(attempts): + for _ in range(attempts): # These track the position within the cycle detection logic. tortoise = seed hare = seed diff --git a/maths/primelib.py b/maths/primelib.py index 7d2a22f39c59..eb72a9f8ae6a 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -406,14 +406,14 @@ def kg_v(number1, number2): count1 = prime_fac_1.count(n) count2 = prime_fac_2.count(n) - for i in range(max(count1, count2)): + for _ in range(max(count1, count2)): ans *= n else: count1 = prime_fac_1.count(n) - for i in range(count1): + for _ in range(count1): ans *= n done.append(n) @@ -425,7 +425,7 @@ def kg_v(number1, number2): count2 = prime_fac_2.count(n) - for i in range(count2): + for _ in range(count2): ans *= n done.append(n) @@ -637,7 +637,7 @@ def fib(n): fib1 = 1 ans = 1 # this will be return - for i in range(n - 1): + for _ in range(n - 1): tmp = ans ans += fib1 diff --git a/maths/proth_number.py b/maths/proth_number.py index e175031435b0..6b15190249f0 100644 --- a/maths/proth_number.py +++ b/maths/proth_number.py @@ -49,7 +49,7 @@ def proth(number: int) -> int: proth_index = 2 increment = 3 for block in range(1, block_index): - for move in range(increment): + for _ in range(increment): proth_list.append(2 ** (block + 1) + proth_list[proth_index - 1]) proth_index += 1 increment *= 2 diff --git a/maths/square_root.py b/maths/square_root.py index b324c723037c..2cbf14beae18 100644 --- a/maths/square_root.py +++ b/maths/square_root.py @@ -49,7 +49,7 @@ def square_root_iterative( value = get_initial_point(a) - for i in range(max_iter): + for _ in range(max_iter): prev_value = value value = value - fx(value, a) / fx_derivative(value) if abs(prev_value - value) < tolerance: diff --git a/maths/ugly_numbers.py b/maths/ugly_numbers.py index 4451a68cdaad..81bd928c6b3d 100644 --- a/maths/ugly_numbers.py +++ b/maths/ugly_numbers.py @@ -32,7 +32,7 @@ def ugly_numbers(n: int) -> int: next_3 = ugly_nums[i3] * 3 next_5 = ugly_nums[i5] * 5 - for i in range(1, n): + for _ in range(1, n): next_num = min(next_2, next_3, next_5) ugly_nums.append(next_num) if next_num == next_2: diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 305cad0a5a9c..6495bd8fc88d 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -351,7 +351,7 @@ def __pow__(self, other: int) -> Matrix: "Only invertable matrices can be raised to a negative power" ) result = self - for i in range(other - 1): + for _ in range(other - 1): result *= self return result diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 7c964d884617..65f10c90d07a 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -65,7 +65,7 @@ def nth_fibonacci_bruteforce(n: int) -> int: return n fib0 = 0 fib1 = 1 - for i in range(2, n + 1): + for _ in range(2, n + 1): fib0, fib1 = fib1, fib0 + fib1 return fib1 diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index 43e796e77be3..23b818b0f3cf 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -128,7 +128,7 @@ def train(self, xdata, ydata, train_round, accuracy): self.ax_loss.hlines(self.accuracy, 0, self.train_round * 1.1) x_shape = np.shape(xdata) - for round_i in range(train_round): + for _ in range(train_round): all_loss = 0 for row in range(x_shape[0]): _xdata = np.asmatrix(xdata[row, :]).T diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index a2bfdb326d77..f04c81424c81 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -69,7 +69,7 @@ def training(self) -> None: for sample in self.sample: sample.insert(0, self.bias) - for i in range(self.col_sample): + for _ in range(self.col_sample): self.weight.append(random.random()) self.weight.insert(0, self.bias) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 072d00ab58c8..2f26bb6cc74a 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -303,7 +303,7 @@ def cache_decorator_wrapper(*args: T) -> U: def cache_info() -> LFUCache[T, U]: return cls.decorator_function_to_instance_map[func] - setattr(cache_decorator_wrapper, "cache_info", cache_info) + setattr(cache_decorator_wrapper, "cache_info", cache_info) # noqa: B010 return cache_decorator_wrapper diff --git a/other/lru_cache.py b/other/lru_cache.py index b68ae0a8e296..aa910e487406 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -321,7 +321,7 @@ def cache_decorator_wrapper(*args: T) -> U: def cache_info() -> LRUCache[T, U]: return cls.decorator_function_to_instance_map[func] - setattr(cache_decorator_wrapper, "cache_info", cache_info) + setattr(cache_decorator_wrapper, "cache_info", cache_info) # noqa: B010 return cache_decorator_wrapper diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py index 71bc50b51fc2..0fc41d7a25d8 100644 --- a/other/magicdiamondpattern.py +++ b/other/magicdiamondpattern.py @@ -8,9 +8,9 @@ def floyd(n): n : size of pattern """ for i in range(0, n): - for j in range(0, n - i - 1): # printing spaces + for _ in range(0, n - i - 1): # printing spaces print(" ", end="") - for k in range(0, i + 1): # printing stars + for _ in range(0, i + 1): # printing stars print("* ", end="") print() @@ -22,10 +22,10 @@ def reverse_floyd(n): n : size of pattern """ for i in range(n, 0, -1): - for j in range(i, 0, -1): # printing stars + for _ in range(i, 0, -1): # printing stars print("* ", end="") print() - for k in range(n - i + 1, 0, -1): # printing spaces + for _ in range(n - i + 1, 0, -1): # printing spaces print(" ", end="") diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py index aecd19c55927..1e6293f8465c 100644 --- a/other/scoring_algorithm.py +++ b/other/scoring_algorithm.py @@ -77,7 +77,7 @@ def procentual_proximity( final_scores: list[float] = [0 for i in range(len(score_lists[0]))] # generate final scores - for i, slist in enumerate(score_lists): + for slist in score_lists: for j, ele in enumerate(slist): final_scores[j] = final_scores[j] + ele diff --git a/physics/lorentz_transformation_four_vector.py b/physics/lorentz_transformation_four_vector.py index bda852c25520..f58b40e5906b 100644 --- a/physics/lorentz_transformation_four_vector.py +++ b/physics/lorentz_transformation_four_vector.py @@ -145,7 +145,7 @@ def transformation_matrix(velocity: float) -> np.array: def transform( - velocity: float, event: np.array = np.zeros(4), symbolic: bool = True + velocity: float, event: np.array = np.zeros(4), symbolic: bool = True # noqa: B008 ) -> np.array: """ >>> transform(29979245,np.array([1,2,3,4]), False) diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index 7e9fc1642c84..2f8153782663 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -310,7 +310,7 @@ def example_3() -> BodySystem: """ bodies = [] - for i in range(10): + for _ in range(10): velocity_x = random.uniform(-0.5, 0.5) velocity_y = random.uniform(-0.5, 0.5) diff --git a/project_euler/problem_011/sol2.py b/project_euler/problem_011/sol2.py index 839ca6717571..9ea0db991aaf 100644 --- a/project_euler/problem_011/sol2.py +++ b/project_euler/problem_011/sol2.py @@ -36,7 +36,7 @@ def solution(): """ with open(os.path.dirname(__file__) + "/grid.txt") as f: l = [] # noqa: E741 - for i in range(20): + for _ in range(20): l.append([int(x) for x in f.readline().split()]) maximum = 0 diff --git a/project_euler/problem_025/sol3.py b/project_euler/problem_025/sol3.py index c66411dc55fc..0b9f3a0c84ef 100644 --- a/project_euler/problem_025/sol3.py +++ b/project_euler/problem_025/sol3.py @@ -45,7 +45,7 @@ def solution(n: int = 1000) -> int: f = f1 + f2 f1, f2 = f2, f index += 1 - for j in str(f): + for _ in str(f): i += 1 if i == n: break diff --git a/project_euler/problem_026/sol1.py b/project_euler/problem_026/sol1.py index 75d48df7910c..ccf2c111d2c5 100644 --- a/project_euler/problem_026/sol1.py +++ b/project_euler/problem_026/sol1.py @@ -41,7 +41,7 @@ def solution(numerator: int = 1, digit: int = 1000) -> int: for divide_by_number in range(numerator, digit + 1): has_been_divided: list[int] = [] now_divide = numerator - for division_cycle in range(1, digit + 1): + for _ 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) diff --git a/project_euler/problem_188/sol1.py b/project_euler/problem_188/sol1.py index dd4360adb32b..88bd1327e917 100644 --- a/project_euler/problem_188/sol1.py +++ b/project_euler/problem_188/sol1.py @@ -58,7 +58,7 @@ def solution(base: int = 1777, height: int = 1855, digits: int = 8) -> int: # calculate base↑↑height by right-assiciative repeated modular # exponentiation result = base - for i in range(1, height): + for _ in range(1, height): result = _modexpt(base, result, 10**digits) return result diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py index dc93683da535..713b530b6af2 100644 --- a/project_euler/problem_203/sol1.py +++ b/project_euler/problem_203/sol1.py @@ -49,7 +49,7 @@ def get_pascal_triangle_unique_coefficients(depth: int) -> set[int]: """ coefficients = {1} previous_coefficients = [1] - for step in range(2, depth + 1): + for _ in range(2, depth + 1): coefficients_begins_one = previous_coefficients + [0] coefficients_ends_one = [0] + previous_coefficients previous_coefficients = [] diff --git a/scheduling/multi_level_feedback_queue.py b/scheduling/multi_level_feedback_queue.py index b54cc8719039..a3ba1b340e9b 100644 --- a/scheduling/multi_level_feedback_queue.py +++ b/scheduling/multi_level_feedback_queue.py @@ -205,7 +205,7 @@ def round_robin( """ finished: deque[Process] = deque() # sequence deque of terminated process # just for 1 cycle and unfinished processes will go back to queue - for i in range(len(ready_queue)): + for _ in range(len(ready_queue)): cp = ready_queue.popleft() # current process # if process's arrival time is later than current time, update current time diff --git a/sorts/double_sort.py b/sorts/double_sort.py index 4e08e27b3c21..5ca88a6745d5 100644 --- a/sorts/double_sort.py +++ b/sorts/double_sort.py @@ -15,7 +15,7 @@ def double_sort(lst): True """ no_of_elements = len(lst) - for i in range( + for _ in range( 0, int(((no_of_elements - 1) / 2) + 1) ): # we don't need to traverse to end of list as for j in range(0, no_of_elements - 1): diff --git a/web_programming/open_google_results.py b/web_programming/open_google_results.py index 0e1dba8c5856..2685bf62114d 100644 --- a/web_programming/open_google_results.py +++ b/web_programming/open_google_results.py @@ -1,10 +1,10 @@ import webbrowser from sys import argv -from urllib.parse import quote, parse_qs -from fake_useragent import UserAgent +from urllib.parse import parse_qs, quote import requests from bs4 import BeautifulSoup +from fake_useragent import UserAgent if __name__ == "__main__": if len(argv) > 1: @@ -18,9 +18,7 @@ res = requests.get( url, - headers={ - "User-Agent": str(UserAgent().random) - }, + headers={"User-Agent": str(UserAgent().random)}, ) try: From 71353ed79787cbbe3800ee32a1fb3d82c1335d19 Mon Sep 17 00:00:00 2001 From: Advik Sharma <70201060+advik-student-dev@users.noreply.github.com> Date: Thu, 13 Oct 2022 10:09:48 -0700 Subject: [PATCH 1886/2908] refined readme.md (#7081) * refined readme.md added some refinements to readme.md * Update README.md Co-authored-by: Christian Clauss --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c499c14e12b9..da80c012b0c6 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@

    All algorithms implemented in Python - for education

    -Implementations are for learning purposes only. As they may be less efficient than the implementations in the Python standard library, use them at your discretion. +Implementations are for learning purposes only. They may be less efficient than the implementations in the Python standard library. Use them at your discretion. ## Getting Started @@ -42,8 +42,8 @@ Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribut ## Community Channels -We're on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms)! Community channels are great for you to ask questions and get help. Please join us! +We are on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms)! Community channels are a great way for you to ask questions and get help. Please join us! ## List of Algorithms -See our [directory](DIRECTORY.md) for easier navigation and better overview of the project. +See our [directory](DIRECTORY.md) for easier navigation and a better overview of the project. From 3deb4a3042438007df7373c07c6280e55d3511da Mon Sep 17 00:00:00 2001 From: Anurag Shukla <76862299+anuragshuklajec@users.noreply.github.com> Date: Fri, 14 Oct 2022 01:33:15 +0530 Subject: [PATCH 1887/2908] Create binary_search_matrix.py (#6995) * Create binary_search_matrix.py Added an algorithm to search in matrix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update binary_search_matrix.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix Indentation * Update matrix/binary_search_matrix.py Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- matrix/binary_search_matrix.py | 57 ++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 matrix/binary_search_matrix.py diff --git a/matrix/binary_search_matrix.py b/matrix/binary_search_matrix.py new file mode 100644 index 000000000000..6f203b7a3484 --- /dev/null +++ b/matrix/binary_search_matrix.py @@ -0,0 +1,57 @@ +def binary_search(array: list, lower_bound: int, upper_bound: int, value: int) -> int: + """ + This function carries out Binary search on a 1d array and + return -1 if it do not exist + array: A 1d sorted array + value : the value meant to be searched + >>> matrix = [1, 4, 7, 11, 15] + >>> binary_search(matrix, 0, len(matrix) - 1, 1) + 0 + >>> binary_search(matrix, 0, len(matrix) - 1, 23) + -1 + """ + + r = int((lower_bound + upper_bound) // 2) + if array[r] == value: + return r + if lower_bound >= upper_bound: + return -1 + if array[r] < value: + return binary_search(array, r + 1, upper_bound, value) + else: + return binary_search(array, lower_bound, r - 1, value) + + +def mat_bin_search(value: int, matrix: list) -> list: + """ + This function loops over a 2d matrix and calls binarySearch on + the selected 1d array and returns [-1, -1] is it do not exist + value : value meant to be searched + matrix = a sorted 2d matrix + >>> matrix = [[1, 4, 7, 11, 15], + ... [2, 5, 8, 12, 19], + ... [3, 6, 9, 16, 22], + ... [10, 13, 14, 17, 24], + ... [18, 21, 23, 26, 30]] + >>> target = 1 + >>> mat_bin_search(target, matrix) + [0, 0] + >>> target = 34 + >>> mat_bin_search(target, matrix) + [-1, -1] + """ + index = 0 + if matrix[index][0] == value: + return [index, 0] + while index < len(matrix) and matrix[index][0] < value: + r = binary_search(matrix[index], 0, len(matrix[index]) - 1, value) + if r != -1: + return [index, r] + index += 1 + return [-1, -1] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 05e19128f7fd1bee9c8d037b3f84cd42374aad0d Mon Sep 17 00:00:00 2001 From: AkshajV1309 <79909101+AkshajV1309@users.noreply.github.com> Date: Fri, 14 Oct 2022 01:54:31 +0530 Subject: [PATCH 1888/2908] Create norgate.py (#7133) * Create norgate.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Create norgate.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update boolean_algebra/norgate.py * Update boolean_algebra/norgate.py * Update boolean_algebra/norgate.py * Update boolean_algebra/norgate.py * Update boolean_algebra/norgate.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- boolean_algebra/norgate.py | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 boolean_algebra/norgate.py diff --git a/boolean_algebra/norgate.py b/boolean_algebra/norgate.py new file mode 100644 index 000000000000..82a1fb2e33e5 --- /dev/null +++ b/boolean_algebra/norgate.py @@ -0,0 +1,46 @@ +""" A NOR Gate is a logic gate in boolean algebra which results to false(0) + if any of the input is 1, and True(1) if both the inputs are 0. + Following is the truth table of an NOR Gate: + | Input 1 | Input 2 | Output | + | 0 | 0 | 1 | + | 0 | 1 | 0 | + | 1 | 0 | 0 | + | 1 | 1 | 0 | +""" +"""Following is the code implementation of the NOR Gate""" + + +def nor_gate(input_1: int, input_2: int) -> int: + """ + >>> nor_gate(0, 0) + 1 + >>> nor_gate(0, 1) + 0 + >>> nor_gate(1, 0) + 0 + >>> nor_gate(1, 1) + 0 + >>> nor_gate(0.0, 0.0) + 1 + >>> nor_gate(0, -7) + 0 + """ + return int(bool(input_1 == input_2 == 0)) + + +def main() -> None: + print("Truth Table of NOR Gate:") + print("| Input 1 |", " Input 2 |", " Output |") + print("| 0 |", " 0 | ", nor_gate(0, 0), " |") + print("| 0 |", " 1 | ", nor_gate(0, 1), " |") + print("| 1 |", " 0 | ", nor_gate(1, 0), " |") + print("| 1 |", " 1 | ", nor_gate(1, 1), " |") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() +"""Code provided by Akshaj Vishwanathan""" +"""Reference: https://www.geeksforgeeks.org/logic-gates-in-python/""" From 26fe4c65390b7a2bfe2722b674943b64820d8442 Mon Sep 17 00:00:00 2001 From: Md Mahiuddin <68785084+mahiuddin-dev@users.noreply.github.com> Date: Fri, 14 Oct 2022 13:20:40 +0600 Subject: [PATCH 1889/2908] Remove extra Semicolon (#7152) --- data_structures/queue/linked_queue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/queue/linked_queue.py b/data_structures/queue/linked_queue.py index c6e9f53908dd..3675da7db78a 100644 --- a/data_structures/queue/linked_queue.py +++ b/data_structures/queue/linked_queue.py @@ -22,7 +22,7 @@ class LinkedQueue: >>> queue.put(5) >>> queue.put(9) >>> queue.put('python') - >>> queue.is_empty(); + >>> queue.is_empty() False >>> queue.get() 5 From e40c7b4bf1794c94993715c99e2a97b9d8f5e590 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Fri, 14 Oct 2022 20:04:44 +0530 Subject: [PATCH 1890/2908] refactor: move flake8 config (#7167) * refactor: move flake8 config * Update .pre-commit-config.yaml Co-authored-by: Christian Clauss --- .flake8 | 5 +++++ .pre-commit-config.yaml | 8 ++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.flake8 b/.flake8 index 9a5863c9cd0b..0d9ef18d142b 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,8 @@ [flake8] +max-line-length = 88 +max-complexity = 25 extend-ignore = A003 # Class attribute is shadowing a python builtin + # Formatting style for `black` + E203 # Whitespace before ':' + W503 # Line break occurred before a binary operator diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d2558b90abb1..d3ea9722f8f3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,11 +35,7 @@ repos: - repo: https://github.com/PyCQA/flake8 rev: 5.0.4 hooks: - - id: flake8 - args: - - --ignore=E203,W503 - - --max-complexity=25 - - --max-line-length=88 + - id: flake8 # See .flake8 for args additional_dependencies: - flake8-bugbear - flake8-builtins @@ -51,7 +47,7 @@ repos: - id: mypy args: - --ignore-missing-imports - - --install-types # See mirrors-mypy README.md + - --install-types # See mirrors-mypy README.md - --non-interactive additional_dependencies: [types-requests] From fd5ab454921b687af94927015d4ab06d3a84886b Mon Sep 17 00:00:00 2001 From: Abinash Satapathy Date: Fri, 14 Oct 2022 17:47:39 +0200 Subject: [PATCH 1891/2908] Doctest output simpler version (#7116) * Update README.md Added Google Cirq references * Create barcode_validator.py Barcode/EAN validator * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Included docstring and updated variables to snake_case * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Included docset and updated bugs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Implemented the changes asked in review. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Updated with f-string format * Update barcode_validator.py * Update volume_conversions.py Simpler doctest output * Update volume_conversions.py Fixed indentation Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- conversions/volume_conversions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/conversions/volume_conversions.py b/conversions/volume_conversions.py index de2290196fc2..44d29009120c 100644 --- a/conversions/volume_conversions.py +++ b/conversions/volume_conversions.py @@ -52,11 +52,7 @@ def volume_conversion(value: float, from_type: str, to_type: str) -> float: 0.000236588 >>> volume_conversion(4, "wrongUnit", "litre") Traceback (most recent call last): - File "/usr/lib/python3.8/doctest.py", line 1336, in __run - exec(compile(example.source, filename, "single", - File "", line 1, in - volume_conversion(4, "wrongUnit", "litre") - File "", line 62, in volume_conversion + ... ValueError: Invalid 'from_type' value: 'wrongUnit' Supported values are: cubicmeter, litre, kilolitre, gallon, cubicyard, cubicfoot, cup """ From 0c06b255822905512b9fa9c12cb09dabf8fa405f Mon Sep 17 00:00:00 2001 From: Abinash Satapathy Date: Fri, 14 Oct 2022 23:42:41 +0200 Subject: [PATCH 1892/2908] Create speed_conversions.py (#7128) * Update README.md Added Google Cirq references * Create barcode_validator.py Barcode/EAN validator * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Included docstring and updated variables to snake_case * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Included docset and updated bugs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Implemented the changes asked in review. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update barcode_validator.py Updated with f-string format * Update barcode_validator.py * Update volume_conversions.py Simpler doctest output * Create speed_conversions.py Conversion of speed units * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update speed_conversions.py Doctests updated, dictionary implemented. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update speed_conversions.py Reduced LOC * Update volume_conversions.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- conversions/speed_conversions.py | 70 ++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 conversions/speed_conversions.py diff --git a/conversions/speed_conversions.py b/conversions/speed_conversions.py new file mode 100644 index 000000000000..62da9e137bc7 --- /dev/null +++ b/conversions/speed_conversions.py @@ -0,0 +1,70 @@ +""" +Convert speed units + +https://en.wikipedia.org/wiki/Kilometres_per_hour +https://en.wikipedia.org/wiki/Miles_per_hour +https://en.wikipedia.org/wiki/Knot_(unit) +https://en.wikipedia.org/wiki/Metre_per_second +""" + +speed_chart: dict[str, float] = { + "km/h": 1.0, + "m/s": 3.6, + "mph": 1.609344, + "knot": 1.852, +} + +speed_chart_inverse: dict[str, float] = { + "km/h": 1.0, + "m/s": 0.277777778, + "mph": 0.621371192, + "knot": 0.539956803, +} + + +def convert_speed(speed: float, unit_from: str, unit_to: str) -> float: + """ + Convert speed from one unit to another using the speed_chart above. + + "km/h": 1.0, + "m/s": 3.6, + "mph": 1.609344, + "knot": 1.852, + + >>> convert_speed(100, "km/h", "m/s") + 27.778 + >>> convert_speed(100, "km/h", "mph") + 62.137 + >>> convert_speed(100, "km/h", "knot") + 53.996 + >>> convert_speed(100, "m/s", "km/h") + 360.0 + >>> convert_speed(100, "m/s", "mph") + 223.694 + >>> convert_speed(100, "m/s", "knot") + 194.384 + >>> convert_speed(100, "mph", "km/h") + 160.934 + >>> convert_speed(100, "mph", "m/s") + 44.704 + >>> convert_speed(100, "mph", "knot") + 86.898 + >>> convert_speed(100, "knot", "km/h") + 185.2 + >>> convert_speed(100, "knot", "m/s") + 51.444 + >>> convert_speed(100, "knot", "mph") + 115.078 + """ + if unit_to not in speed_chart or unit_from not in speed_chart_inverse: + raise ValueError( + f"Incorrect 'from_type' or 'to_type' value: {unit_from!r}, {unit_to!r}\n" + f"Valid values are: {', '.join(speed_chart_inverse)}" + ) + return round(speed * speed_chart[unit_from] * speed_chart_inverse[unit_to], 3) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 20587750051c3699b051579d7e97e5508958ea5a Mon Sep 17 00:00:00 2001 From: Caeden Date: Fri, 14 Oct 2022 23:25:15 +0100 Subject: [PATCH 1893/2908] refactor: Make code more understandable (#7196) * refactor: Make code more understandable * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../binary_tree/binary_tree_traversals.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 378598bb096d..54b1dc536f32 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -15,7 +15,20 @@ class Node: def make_tree() -> Node | None: - return Node(1, Node(2, Node(4), Node(5)), Node(3)) + r""" + The below tree + 1 + / \ + 2 3 + / \ + 4 5 + """ + tree = Node(1) + tree.left = Node(2) + tree.right = Node(3) + tree.left.left = Node(4) + tree.left.right = Node(5) + return tree def preorder(root: Node | None) -> list[int]: From 5dc0dc4d23eb1efa4564c0531402af3d2419012d Mon Sep 17 00:00:00 2001 From: Lukas Esc <55601315+Luk-ESC@users.noreply.github.com> Date: Fri, 14 Oct 2022 17:37:15 -0500 Subject: [PATCH 1894/2908] remove useless bool() call (#7189) --- boolean_algebra/norgate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boolean_algebra/norgate.py b/boolean_algebra/norgate.py index 82a1fb2e33e5..1c341e8a707b 100644 --- a/boolean_algebra/norgate.py +++ b/boolean_algebra/norgate.py @@ -25,7 +25,7 @@ def nor_gate(input_1: int, input_2: int) -> int: >>> nor_gate(0, -7) 0 """ - return int(bool(input_1 == input_2 == 0)) + return int(input_1 == input_2 == 0) def main() -> None: From dcca5351c9185bf8c568615782ffb28319a6539d Mon Sep 17 00:00:00 2001 From: Claudio Lucisano <43884655+Claudiocli@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:45:12 +0200 Subject: [PATCH 1895/2908] Added astronomical_length_scale_conversion.py (#7183) --- .../astronomical_length_scale_conversion.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 conversions/astronomical_length_scale_conversion.py diff --git a/conversions/astronomical_length_scale_conversion.py b/conversions/astronomical_length_scale_conversion.py new file mode 100644 index 000000000000..804d82487a25 --- /dev/null +++ b/conversions/astronomical_length_scale_conversion.py @@ -0,0 +1,104 @@ +""" +Conversion of length units. +Available Units: +Metre, Kilometre, Megametre, Gigametre, +Terametre, Petametre, Exametre, Zettametre, Yottametre + +USAGE : +-> Import this file into their respective project. +-> Use the function length_conversion() for conversion of length units. +-> Parameters : + -> value : The number of from units you want to convert + -> from_type : From which type you want to convert + -> to_type : To which type you want to convert + +REFERENCES : +-> Wikipedia reference: https://en.wikipedia.org/wiki/Meter +-> Wikipedia reference: https://en.wikipedia.org/wiki/Kilometer +-> Wikipedia reference: https://en.wikipedia.org/wiki/Orders_of_magnitude_(length) +""" + +UNIT_SYMBOL = { + "meter": "m", + "kilometer": "km", + "megametre": "Mm", + "gigametre": "Gm", + "terametre": "Tm", + "petametre": "Pm", + "exametre": "Em", + "zettametre": "Zm", + "yottametre": "Ym", +} +# Exponent of the factor(meter) +METRIC_CONVERSION = { + "m": 0, + "km": 3, + "Mm": 6, + "Gm": 9, + "Tm": 12, + "Pm": 15, + "Em": 18, + "Zm": 21, + "Ym": 24, +} + + +def length_conversion(value: float, from_type: str, to_type: str) -> float: + """ + Conversion between astronomical length units. + + >>> length_conversion(1, "meter", "kilometer") + 0.001 + >>> length_conversion(1, "meter", "megametre") + 1e-06 + >>> length_conversion(1, "gigametre", "meter") + 1000000000 + >>> length_conversion(1, "gigametre", "terametre") + 0.001 + >>> length_conversion(1, "petametre", "terametre") + 1000 + >>> length_conversion(1, "petametre", "exametre") + 0.001 + >>> length_conversion(1, "terametre", "zettametre") + 1e-09 + >>> length_conversion(1, "yottametre", "zettametre") + 1000 + >>> length_conversion(4, "wrongUnit", "inch") + Traceback (most recent call last): + ... + ValueError: Invalid 'from_type' value: 'wrongUnit'. + Conversion abbreviations are: m, km, Mm, Gm, Tm, Pm, Em, Zm, Ym + """ + + from_sanitized = from_type.lower().strip("s") + to_sanitized = to_type.lower().strip("s") + + from_sanitized = UNIT_SYMBOL.get(from_sanitized, from_sanitized) + to_sanitized = UNIT_SYMBOL.get(to_sanitized, to_sanitized) + + if from_sanitized not in METRIC_CONVERSION: + raise ValueError( + f"Invalid 'from_type' value: {from_type!r}.\n" + f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" + ) + if to_sanitized not in METRIC_CONVERSION: + raise ValueError( + f"Invalid 'to_type' value: {to_type!r}.\n" + f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" + ) + from_exponent = METRIC_CONVERSION[from_sanitized] + to_exponent = METRIC_CONVERSION[to_sanitized] + exponent = 1 + + if from_exponent > to_exponent: + exponent = from_exponent - to_exponent + else: + exponent = -(to_exponent - from_exponent) + + return value * pow(10, exponent) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 6e69181d1f592a08806717058720bf63e241eef2 Mon Sep 17 00:00:00 2001 From: Caeden Date: Sat, 15 Oct 2022 02:07:03 +0100 Subject: [PATCH 1896/2908] refactor: Replace `list()` and `dict()` calls with literals (#7198) --- data_structures/binary_tree/binary_search_tree.py | 2 +- data_structures/heap/heap_generic.py | 2 +- data_structures/trie/trie.py | 2 +- graphs/frequent_pattern_graph_miner.py | 2 +- maths/greedy_coin_change.py | 2 +- other/davisb_putnamb_logemannb_loveland.py | 4 ++-- project_euler/problem_107/sol1.py | 2 +- searches/tabu_search.py | 6 +++--- sorts/msd_radix_sort.py | 4 ++-- strings/aho_corasick.py | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index b9af23dc8b00..51a651be0f82 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -160,7 +160,7 @@ def postorder(curr_node): """ postOrder (left, right, self) """ - node_list = list() + node_list = [] if curr_node is not None: node_list = postorder(curr_node.left) + postorder(curr_node.right) + [curr_node] return node_list diff --git a/data_structures/heap/heap_generic.py b/data_structures/heap/heap_generic.py index e7831cd45b43..b4d7019f41f9 100644 --- a/data_structures/heap/heap_generic.py +++ b/data_structures/heap/heap_generic.py @@ -9,7 +9,7 @@ class Heap: def __init__(self, key: Callable | None = None) -> None: # Stores actual heap items. - self.arr: list = list() + self.arr: list = [] # Stores indexes of each item for supporting updates and deletion. self.pos_map: dict = {} # Stores current size of heap. diff --git a/data_structures/trie/trie.py b/data_structures/trie/trie.py index 162d08d1d678..46b93a499d14 100644 --- a/data_structures/trie/trie.py +++ b/data_structures/trie/trie.py @@ -8,7 +8,7 @@ class TrieNode: def __init__(self) -> None: - self.nodes: dict[str, TrieNode] = dict() # Mapping from char to TrieNode + self.nodes: dict[str, TrieNode] = {} # Mapping from char to TrieNode self.is_leaf = False def insert_many(self, words: list[str]) -> None: diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index a5ecbe6e8223..1d26702a480e 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -54,7 +54,7 @@ def get_frequency_table(edge_array): Returns Frequency Table """ distinct_edge = get_distinct_edge(edge_array) - frequency_table = dict() + frequency_table = {} for item in distinct_edge: bit = get_bitcode(edge_array, item) diff --git a/maths/greedy_coin_change.py b/maths/greedy_coin_change.py index 5233ee1cbc12..29c2f1803d5c 100644 --- a/maths/greedy_coin_change.py +++ b/maths/greedy_coin_change.py @@ -74,7 +74,7 @@ def find_minimum_change(denominations: list[int], value: str) -> list[int]: # Driver Code if __name__ == "__main__": - denominations = list() + denominations = [] value = "0" if ( diff --git a/other/davisb_putnamb_logemannb_loveland.py b/other/davisb_putnamb_logemannb_loveland.py index 03d60a9a1aaf..3110515d5874 100644 --- a/other/davisb_putnamb_logemannb_loveland.py +++ b/other/davisb_putnamb_logemannb_loveland.py @@ -199,7 +199,7 @@ def find_pure_symbols( {'A1': True, 'A2': False, 'A3': True, 'A5': False} """ pure_symbols = [] - assignment: dict[str, bool | None] = dict() + assignment: dict[str, bool | None] = {} literals = [] for clause in clauses: @@ -264,7 +264,7 @@ def find_unit_clauses( n_count += 1 if f_count == len(clause) - 1 and n_count == 1: unit_symbols.append(sym) - assignment: dict[str, bool | None] = dict() + assignment: dict[str, bool | None] = {} for i in unit_symbols: symbol = i[:2] assignment[symbol] = len(i) == 2 diff --git a/project_euler/problem_107/sol1.py b/project_euler/problem_107/sol1.py index 048cf033dc2e..b3f5685b95ef 100644 --- a/project_euler/problem_107/sol1.py +++ b/project_euler/problem_107/sol1.py @@ -100,7 +100,7 @@ def solution(filename: str = "p107_network.txt") -> int: 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() + edges: dict[EdgeT, int] = {} data: list[str] edge1: int edge2: int diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 45ce19d46b23..3e1728286d98 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -51,7 +51,7 @@ def generate_neighbours(path): with open(path) as f: for line in f: if line.split()[0] not in dict_of_neighbours: - _list = list() + _list = [] _list.append([line.split()[1], line.split()[2]]) dict_of_neighbours[line.split()[0]] = _list else: @@ -59,7 +59,7 @@ def generate_neighbours(path): [line.split()[1], line.split()[2]] ) if line.split()[1] not in dict_of_neighbours: - _list = list() + _list = [] _list.append([line.split()[0], line.split()[2]]) dict_of_neighbours[line.split()[1]] = _list else: @@ -206,7 +206,7 @@ def tabu_search( """ count = 1 solution = first_solution - tabu_list = list() + tabu_list = [] best_cost = distance_of_first_solution best_solution_ever = solution diff --git a/sorts/msd_radix_sort.py b/sorts/msd_radix_sort.py index 3cdec4bd0711..7430fc5a63c8 100644 --- a/sorts/msd_radix_sort.py +++ b/sorts/msd_radix_sort.py @@ -52,8 +52,8 @@ def _msd_radix_sort(list_of_ints: list[int], bit_position: int) -> list[int]: if bit_position == 0 or len(list_of_ints) in [0, 1]: return list_of_ints - zeros = list() - ones = list() + zeros = [] + ones = [] # Split numbers based on bit at bit_position from the right for number in list_of_ints: if (number >> (bit_position - 1)) & 1: diff --git a/strings/aho_corasick.py b/strings/aho_corasick.py index b9a6a80728f6..2d2f562df951 100644 --- a/strings/aho_corasick.py +++ b/strings/aho_corasick.py @@ -5,7 +5,7 @@ class Automaton: def __init__(self, keywords: list[str]): - self.adlist: list[dict] = list() + self.adlist: list[dict] = [] self.adlist.append( {"value": "", "next_states": [], "fail_state": 0, "output": []} ) From 70b60dc3231e1df72622db64f9b97fef772181e5 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sat, 15 Oct 2022 12:07:59 +0530 Subject: [PATCH 1897/2908] chore: remove inactive user from CODEOWNERS (#7205) * chore: remove inactive user from CODEOWNERS * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/CODEOWNERS | 6 +++--- DIRECTORY.md | 11 +++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fdce879f80c4..abf99ab227be 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -35,7 +35,7 @@ # /divide_and_conquer/ -/dynamic_programming/ @Kush1101 +# /dynamic_programming/ # /file_transfer/ @@ -59,7 +59,7 @@ # /machine_learning/ -/maths/ @Kush1101 +# /maths/ # /matrix/ @@ -69,7 +69,7 @@ # /other/ @cclauss # TODO: Uncomment this line after Hacktoberfest -/project_euler/ @dhruvmanila @Kush1101 +/project_euler/ @dhruvmanila # /quantum/ diff --git a/DIRECTORY.md b/DIRECTORY.md index 2786e1f82de8..239dafa65f2b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -52,6 +52,7 @@ * [Modular Division](blockchain/modular_division.py) ## Boolean Algebra + * [Norgate](boolean_algebra/norgate.py) * [Quine Mc Cluskey](boolean_algebra/quine_mc_cluskey.py) ## Cellular Automata @@ -121,6 +122,7 @@ * [Pooling Functions](computer_vision/pooling_functions.py) ## Conversions + * [Astronomical Length Scale Conversion](conversions/astronomical_length_scale_conversion.py) * [Binary To Decimal](conversions/binary_to_decimal.py) * [Binary To Hexadecimal](conversions/binary_to_hexadecimal.py) * [Binary To Octal](conversions/binary_to_octal.py) @@ -140,6 +142,7 @@ * [Pressure Conversions](conversions/pressure_conversions.py) * [Rgb Hsv Conversion](conversions/rgb_hsv_conversion.py) * [Roman Numerals](conversions/roman_numerals.py) + * [Speed Conversions](conversions/speed_conversions.py) * [Temperature Conversions](conversions/temperature_conversions.py) * [Volume Conversions](conversions/volume_conversions.py) * [Weight Conversion](conversions/weight_conversion.py) @@ -448,6 +451,7 @@ * [Random Forest Classifier](machine_learning/random_forest_classifier.py) * [Random Forest Regressor](machine_learning/random_forest_regressor.py) * [Scoring Functions](machine_learning/scoring_functions.py) + * [Self Organizing Map](machine_learning/self_organizing_map.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) * [Similarity Search](machine_learning/similarity_search.py) * [Support Vector Machines](machine_learning/support_vector_machines.py) @@ -586,9 +590,11 @@ * [Two Sum](maths/two_sum.py) * [Ugly Numbers](maths/ugly_numbers.py) * [Volume](maths/volume.py) + * [Weird Number](maths/weird_number.py) * [Zellers Congruence](maths/zellers_congruence.py) ## Matrix + * [Binary Search Matrix](matrix/binary_search_matrix.py) * [Count Islands In Matrix](matrix/count_islands_in_matrix.py) * [Inverse Of Matrix](matrix/inverse_of_matrix.py) * [Matrix Class](matrix/matrix_class.py) @@ -854,8 +860,6 @@ * [Sol1](project_euler/problem_101/sol1.py) * Problem 102 * [Sol1](project_euler/problem_102/sol1.py) - * Problem 104 - * [Sol](project_euler/problem_104/sol.py) * Problem 107 * [Sol1](project_euler/problem_107/sol1.py) * Problem 109 @@ -1010,6 +1014,7 @@ * [Alternative String Arrange](strings/alternative_string_arrange.py) * [Anagrams](strings/anagrams.py) * [Autocomplete Using Trie](strings/autocomplete_using_trie.py) + * [Barcode Validator](strings/barcode_validator.py) * [Boyer Moore Search](strings/boyer_moore_search.py) * [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py) * [Capitalize](strings/capitalize.py) @@ -1039,6 +1044,7 @@ * [Reverse Letters](strings/reverse_letters.py) * [Reverse Long Words](strings/reverse_long_words.py) * [Reverse Words](strings/reverse_words.py) + * [Snake Case To Camel Pascal Case](strings/snake_case_to_camel_pascal_case.py) * [Split](strings/split.py) * [Upper](strings/upper.py) * [Wave](strings/wave.py) @@ -1073,6 +1079,7 @@ * [Instagram Pic](web_programming/instagram_pic.py) * [Instagram Video](web_programming/instagram_video.py) * [Nasa Data](web_programming/nasa_data.py) + * [Open Google Results](web_programming/open_google_results.py) * [Random Anime Character](web_programming/random_anime_character.py) * [Recaptcha Verification](web_programming/recaptcha_verification.py) * [Reddit](web_programming/reddit.py) From 6be9500b2fb5d2e51432f9966e76a107dd604a41 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 15 Oct 2022 09:02:07 +0200 Subject: [PATCH 1898/2908] chore: remove checkbox in feature issue template (#7212) We do not assign issues in this repo Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/feature_request.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index bed3e8ab54ae..09a159b2193e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -17,10 +17,3 @@ body: implementations. validations: required: true - - - type: checkboxes - attributes: - label: Would you like to work on this feature? - options: - - label: Yes, I want to work on this feature! - required: false From 98a4c2487814cdfe0822526e05c4e63ff6aef7d0 Mon Sep 17 00:00:00 2001 From: Caeden Date: Sat, 15 Oct 2022 13:58:09 +0100 Subject: [PATCH 1899/2908] feat: Binary tree node sum (#7020) (#7162) * feat: Binary tree node sum (#7020) * feat: Sum of all nodes in binary tree explanation (#7020) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/binary_tree/binary_tree_node_sum.py Co-authored-by: Christian Clauss * refactor: Change replace method with `__iter__` overriding (#7020) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + .../binary_tree/binary_tree_node_sum.py | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 data_structures/binary_tree/binary_tree_node_sum.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 239dafa65f2b..92bed9cb4c6e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -154,6 +154,7 @@ * [Binary Search Tree](data_structures/binary_tree/binary_search_tree.py) * [Binary Search Tree Recursive](data_structures/binary_tree/binary_search_tree_recursive.py) * [Binary Tree Mirror](data_structures/binary_tree/binary_tree_mirror.py) + * [Binary Tree Node Sum](data_structures/binary_tree/binary_tree_node_sum.py) * [Binary Tree Traversals](data_structures/binary_tree/binary_tree_traversals.py) * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) * [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py) diff --git a/data_structures/binary_tree/binary_tree_node_sum.py b/data_structures/binary_tree/binary_tree_node_sum.py new file mode 100644 index 000000000000..5a13e74e3c9f --- /dev/null +++ b/data_structures/binary_tree/binary_tree_node_sum.py @@ -0,0 +1,76 @@ +""" +Sum of all nodes in a binary tree. + +Python implementation: + O(n) time complexity - Recurses through :meth:`depth_first_search` + with each element. + O(n) space complexity - At any point in time maximum number of stack + frames that could be in memory is `n` +""" + + +from __future__ import annotations + +from collections.abc import Iterator + + +class Node: + """ + A Node has a value variable and pointers to Nodes to its left and right. + """ + + def __init__(self, value: int) -> None: + self.value = value + self.left: Node | None = None + self.right: Node | None = None + + +class BinaryTreeNodeSum: + r""" + The below tree looks like this + 10 + / \ + 5 -3 + / / \ + 12 8 0 + + >>> tree = Node(10) + >>> sum(BinaryTreeNodeSum(tree)) + 10 + + >>> tree.left = Node(5) + >>> sum(BinaryTreeNodeSum(tree)) + 15 + + >>> tree.right = Node(-3) + >>> sum(BinaryTreeNodeSum(tree)) + 12 + + >>> tree.left.left = Node(12) + >>> sum(BinaryTreeNodeSum(tree)) + 24 + + >>> tree.right.left = Node(8) + >>> tree.right.right = Node(0) + >>> sum(BinaryTreeNodeSum(tree)) + 32 + """ + + def __init__(self, tree: Node) -> None: + self.tree = tree + + def depth_first_search(self, node: Node | None) -> int: + if node is None: + return 0 + return node.value + ( + self.depth_first_search(node.left) + self.depth_first_search(node.right) + ) + + def __iter__(self) -> Iterator[int]: + yield self.depth_first_search(self.tree) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a652905b605ddcc43626072366d1130315801dc9 Mon Sep 17 00:00:00 2001 From: Caeden Date: Sat, 15 Oct 2022 18:29:42 +0100 Subject: [PATCH 1900/2908] Add Flake8 comprehensions to pre-commit (#7235) * ci(pre-commit): Add ``flake8-comprehensions`` to ``pre-commit`` (#7233) * refactor: Fix ``flake8-comprehensions`` errors * fix: Replace `map` with generator (#7233) * fix: Cast `range` objects to `list` --- .pre-commit-config.yaml | 1 + ciphers/onepad_cipher.py | 2 +- ciphers/rail_fence_cipher.py | 2 +- data_structures/hashing/hash_table.py | 2 +- data_structures/linked_list/merge_two_lists.py | 2 +- dynamic_programming/fractional_knapsack.py | 2 +- graphs/bellman_ford.py | 2 +- graphs/frequent_pattern_graph_miner.py | 10 +++++----- hashes/enigma_machine.py | 8 ++++---- maths/primelib.py | 2 +- matrix/spiral_print.py | 4 ++-- other/davisb_putnamb_logemannb_loveland.py | 4 ++-- project_euler/problem_042/solution42.py | 4 ++-- project_euler/problem_052/sol1.py | 12 ++++++------ project_euler/problem_062/sol1.py | 2 +- project_euler/problem_067/sol1.py | 4 ++-- project_euler/problem_109/sol1.py | 2 +- project_euler/problem_551/sol1.py | 2 +- sorts/radix_sort.py | 2 +- strings/aho_corasick.py | 4 +--- 20 files changed, 36 insertions(+), 37 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3ea9722f8f3..3455135653cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,6 +39,7 @@ repos: additional_dependencies: - flake8-bugbear - flake8-builtins + - flake8-comprehensions - pep8-naming - repo: https://github.com/pre-commit/mirrors-mypy diff --git a/ciphers/onepad_cipher.py b/ciphers/onepad_cipher.py index 3ace9b098cba..4bfe35b7180a 100644 --- a/ciphers/onepad_cipher.py +++ b/ciphers/onepad_cipher.py @@ -22,7 +22,7 @@ def decrypt(cipher: list[int], key: list[int]) -> str: for i in range(len(key)): p = int((cipher[i] - (key[i]) ** 2) / key[i]) plain.append(chr(p)) - return "".join([i for i in plain]) + return "".join(plain) if __name__ == "__main__": diff --git a/ciphers/rail_fence_cipher.py b/ciphers/rail_fence_cipher.py index cba593ca7335..47ee7db89831 100644 --- a/ciphers/rail_fence_cipher.py +++ b/ciphers/rail_fence_cipher.py @@ -72,7 +72,7 @@ def decrypt(input_string: str, key: int) -> str: 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]) + grid.append(list(splice)) counter += len(row) output_string = "" # reads as zigzag diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 1cd71cc4baf3..607454c8255f 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -34,7 +34,7 @@ def hash_function(self, key): def _step_by_step(self, step_ord): print(f"step {step_ord}") - print([i for i in range(len(self.values))]) + print(list(range(len(self.values)))) print(self.values) def bulk_insert(self, values): diff --git a/data_structures/linked_list/merge_two_lists.py b/data_structures/linked_list/merge_two_lists.py index 43dd461867f1..93cf7a7e1602 100644 --- a/data_structures/linked_list/merge_two_lists.py +++ b/data_structures/linked_list/merge_two_lists.py @@ -19,7 +19,7 @@ class Node: class SortedLinkedList: def __init__(self, ints: Iterable[int]) -> None: self.head: Node | None = None - for i in reversed(sorted(ints)): + for i in sorted(ints, reverse=True): self.head = Node(i, self.head) def __iter__(self) -> Iterator[int]: diff --git a/dynamic_programming/fractional_knapsack.py b/dynamic_programming/fractional_knapsack.py index 6f7a2a08cf9b..58976d40c02b 100644 --- a/dynamic_programming/fractional_knapsack.py +++ b/dynamic_programming/fractional_knapsack.py @@ -8,7 +8,7 @@ def frac_knapsack(vl, wt, w, n): 240.0 """ - r = list(sorted(zip(vl, wt), key=lambda x: x[0] / x[1], reverse=True)) + r = 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) diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index eb2cd25bf682..9ac8bae85d4f 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -58,7 +58,7 @@ def bellman_ford( V = int(input("Enter number of vertices: ").strip()) E = int(input("Enter number of edges: ").strip()) - graph: list[dict[str, int]] = [dict() for j in range(E)] + graph: list[dict[str, int]] = [{} for _ in range(E)] for i in range(E): print("Edge ", i + 1) diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index 1d26702a480e..87d5605a0bc8 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -155,12 +155,12 @@ def construct_graph(cluster, nodes): cluster[max(cluster.keys()) + 1] = "Header" graph = {} for i in x: - if tuple(["Header"]) in graph: - graph[tuple(["Header"])].append(x[i]) + if (["Header"],) in graph: + graph[(["Header"],)].append(x[i]) else: - graph[tuple(["Header"])] = [x[i]] + graph[(["Header"],)] = [x[i]] for i in x: - graph[tuple(x[i])] = [["Header"]] + graph[(x[i],)] = [["Header"]] i = 1 while i < max(cluster) - 1: create_edge(nodes, graph, cluster, i) @@ -186,7 +186,7 @@ def find_freq_subgraph_given_support(s, cluster, graph): """ k = int(s / 100 * (len(cluster) - 1)) for i in cluster[k].keys(): - my_dfs(graph, tuple(cluster[k][i]), tuple(["Header"])) + my_dfs(graph, tuple(cluster[k][i]), (["Header"],)) def freq_subgraphs_edge_list(paths): diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py index 0194f7da7d6f..d95437d12c34 100644 --- a/hashes/enigma_machine.py +++ b/hashes/enigma_machine.py @@ -1,8 +1,8 @@ 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)))] +gear_one = list(range(len(alphabets))) +gear_two = list(range(len(alphabets))) +gear_three = list(range(len(alphabets))) +reflector = list(reversed(range(len(alphabets)))) code = [] gear_one_pos = gear_two_pos = gear_three_pos = 0 diff --git a/maths/primelib.py b/maths/primelib.py index eb72a9f8ae6a..9586227ea3ca 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -89,7 +89,7 @@ def sieve_er(n): assert isinstance(n, int) and (n > 2), "'N' must been an int and > 2" # beginList: contains all natural numbers from 2 up to N - begin_list = [x for x in range(2, n + 1)] + begin_list = list(range(2, n + 1)) ans = [] # this list will be returns. diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 2441f05d15ef..0cf732d60ca8 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -9,7 +9,7 @@ def check_matrix(matrix: list[list[int]]) -> bool: # must be - matrix = list(list(row) for row in matrix) + matrix = [list(row) for row in matrix] if matrix and isinstance(matrix, list): if isinstance(matrix[0], list): prev_len = 0 @@ -44,7 +44,7 @@ def spiral_print_clockwise(a: list[list[int]]) -> None: 7 """ if check_matrix(a) and len(a) > 0: - a = list(list(row) for row in a) + a = [list(row) for row in a] mat_row = len(a) if isinstance(a[0], list): mat_col = len(a[0]) diff --git a/other/davisb_putnamb_logemannb_loveland.py b/other/davisb_putnamb_logemannb_loveland.py index 3110515d5874..a1bea5b3992e 100644 --- a/other/davisb_putnamb_logemannb_loveland.py +++ b/other/davisb_putnamb_logemannb_loveland.py @@ -317,7 +317,7 @@ def dpll_algorithm( if p: tmp_model = model tmp_model[p] = value - tmp_symbols = [i for i in symbols] + tmp_symbols = list(symbols) if p in tmp_symbols: tmp_symbols.remove(p) return dpll_algorithm(clauses, tmp_symbols, tmp_model) @@ -329,7 +329,7 @@ def dpll_algorithm( if p: tmp_model = model tmp_model[p] = value - tmp_symbols = [i for i in symbols] + tmp_symbols = list(symbols) if p in tmp_symbols: tmp_symbols.remove(p) return dpll_algorithm(clauses, tmp_symbols, tmp_model) diff --git a/project_euler/problem_042/solution42.py b/project_euler/problem_042/solution42.py index 6d22a8dfb655..c0fb2ad50c11 100644 --- a/project_euler/problem_042/solution42.py +++ b/project_euler/problem_042/solution42.py @@ -33,11 +33,11 @@ def solution(): with open(words_file_path) as f: words = f.readline() - words = list(map(lambda word: word.strip('"'), words.strip("\r\n").split(","))) + words = [word.strip('"') for word in 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), + (sum(ord(x) - 64 for x in word) for word in words), ) ) return len(words) diff --git a/project_euler/problem_052/sol1.py b/project_euler/problem_052/sol1.py index df5c46ae05d1..21acfb633696 100644 --- a/project_euler/problem_052/sol1.py +++ b/project_euler/problem_052/sol1.py @@ -21,12 +21,12 @@ def solution(): 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))) + sorted(str(i)) + == sorted(str(2 * i)) + == sorted(str(3 * i)) + == sorted(str(4 * i)) + == sorted(str(5 * i)) + == sorted(str(6 * i)) ): return i diff --git a/project_euler/problem_062/sol1.py b/project_euler/problem_062/sol1.py index 0c9baf880497..3efdb3513bf6 100644 --- a/project_euler/problem_062/sol1.py +++ b/project_euler/problem_062/sol1.py @@ -55,7 +55,7 @@ def get_digits(num: int) -> str: >>> get_digits(123) '0166788' """ - return "".join(sorted(list(str(num**3)))) + return "".join(sorted(str(num**3))) if __name__ == "__main__": diff --git a/project_euler/problem_067/sol1.py b/project_euler/problem_067/sol1.py index 527d4dc592ac..ab305684dd0d 100644 --- a/project_euler/problem_067/sol1.py +++ b/project_euler/problem_067/sol1.py @@ -28,8 +28,8 @@ def solution(): with open(triangle) as f: triangle = f.readlines() - a = map(lambda x: x.rstrip("\r\n").split(" "), triangle) - a = list(map(lambda x: list(map(int, x)), a)) + a = (x.rstrip("\r\n").split(" ") for x in triangle) + a = [list(map(int, x)) for x in a] for i in range(1, len(a)): for j in range(len(a[i])): diff --git a/project_euler/problem_109/sol1.py b/project_euler/problem_109/sol1.py index 91c71eb9f4cb..852f001d38af 100644 --- a/project_euler/problem_109/sol1.py +++ b/project_euler/problem_109/sol1.py @@ -65,7 +65,7 @@ def solution(limit: int = 100) -> int: >>> solution(50) 12577 """ - singles: list[int] = [x for x in range(1, 21)] + [25] + singles: list[int] = list(range(1, 21)) + [25] doubles: list[int] = [2 * x for x in range(1, 21)] + [50] triples: list[int] = [3 * x for x in range(1, 21)] all_values: list[int] = singles + doubles + triples + [0] diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index c15445e4d7b0..2cd75efbb68d 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -13,7 +13,7 @@ """ -ks = [k for k in range(2, 20 + 1)] +ks = range(2, 20 + 1) base = [10**k for k in range(ks[-1] + 1)] memo: dict[int, dict[int, list[list[int]]]] = {} diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index afe62bc7ec30..a496cdc0c743 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -24,7 +24,7 @@ def radix_sort(list_of_ints: list[int]) -> list[int]: max_digit = max(list_of_ints) while placement <= max_digit: # declare and initialize empty buckets - buckets: list[list] = [list() for _ in range(RADIX)] + buckets: list[list] = [[] for _ in range(RADIX)] # split list_of_ints between the buckets for i in list_of_ints: tmp = int((i / placement) % RADIX) diff --git a/strings/aho_corasick.py b/strings/aho_corasick.py index 2d2f562df951..25ed649ce645 100644 --- a/strings/aho_corasick.py +++ b/strings/aho_corasick.py @@ -70,9 +70,7 @@ def search_in(self, string: str) -> dict[str, list[int]]: >>> A.search_in("whatever, err ... , wherever") {'what': [0], 'hat': [1], 'ver': [5, 25], 'er': [6, 10, 22, 26]} """ - result: dict = ( - dict() - ) # returns a dict with keywords and list of its occurrences + result: dict = {} # returns a dict with keywords and list of its occurrences current_state = 0 for i in range(len(string)): while ( From 553624fcd4d7e8a4c561b182967291a1cc44ade9 Mon Sep 17 00:00:00 2001 From: Paul <56065602+ZeroDayOwl@users.noreply.github.com> Date: Sat, 15 Oct 2022 23:39:27 +0600 Subject: [PATCH 1901/2908] Add algorithm for Casimir Effect (#7141) * Add algorithm for Casimir Effect * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix the line length * Fix the line length * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Import math module and use Pi * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update doctest results * from math import pi Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- physics/casimir_effect.py | 121 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 physics/casimir_effect.py diff --git a/physics/casimir_effect.py b/physics/casimir_effect.py new file mode 100644 index 000000000000..ee8a6c1eba53 --- /dev/null +++ b/physics/casimir_effect.py @@ -0,0 +1,121 @@ +""" +Title : Finding the value of magnitude of either the Casimir force, the surface area +of one of the plates or distance between the plates provided that the other +two parameters are given. + +Description : In quantum field theory, the Casimir effect is a physical force +acting on the macroscopic boundaries of a confined space which arises from the +quantum fluctuations of the field. It is a physical force exerted between separate +objects, which is due to neither charge, gravity, nor the exchange of particles, +but instead is due to resonance of all-pervasive energy fields in the intervening +space between the objects. Since the strength of the force falls off rapidly with +distance it is only measurable when the distance between the objects is extremely +small. On a submicron scale, this force becomes so strong that it becomes the +dominant force between uncharged conductors. + +Dutch physicist Hendrik B. G. Casimir first proposed the existence of the force, +and he formulated an experiment to detect it in 1948 while participating in research +at Philips Research Labs. The classic form of his experiment used a pair of uncharged +parallel metal plates in a vacuum, and successfully demonstrated the force to within +15% of the value he had predicted according to his theory. + +The Casimir force F for idealized, perfectly conducting plates of surface area +A square meter and placed at a distance of a meter apart with vacuum between +them is expressed as - + +F = - ((Reduced Planck Constant ℏ) * c * Pi^2 * A) / (240 * a^4) + +Here, the negative sign indicates the force is attractive in nature. For the ease +of calculation, only the magnitude of the force is considered. + +Source : +- https://en.wikipedia.org/wiki/Casimir_effect +- https://www.cs.mcgill.ca/~rwest/wikispeedia/wpcd/wp/c/Casimir_effect.htm +- Casimir, H. B. ; Polder, D. (1948) "The Influence of Retardation on the + London-van der Waals Forces", Physical Review, vol. 73, Issue 4, pp. 360-372 +""" + +from __future__ import annotations + +from math import pi + +# Define the Reduced Planck Constant ℏ (H bar), speed of light C, value of +# Pi and the function +REDUCED_PLANCK_CONSTANT = 1.054571817e-34 # unit of ℏ : J * s + +SPEED_OF_LIGHT = 3e8 # unit of c : m * s^-1 + + +def casimir_force(force: float, area: float, distance: float) -> dict[str, float]: + + """ + Input Parameters + ---------------- + force -> Casimir Force : magnitude in Newtons + + area -> Surface area of each plate : magnitude in square meters + + distance -> Distance between two plates : distance in Meters + + Returns + ------- + result : dict name, value pair of the parameter having Zero as it's value + + Returns the value of one of the parameters specified as 0, provided the values of + other parameters are given. + >>> casimir_force(force = 0, area = 4, distance = 0.03) + {'force': 6.4248189174864216e-21} + + >>> casimir_force(force = 2635e-13, area = 0.0023, distance = 0) + {'distance': 1.0323056015031114e-05} + + >>> casimir_force(force = 2737e-21, area = 0, distance = 0.0023746) + {'area': 0.06688838837354052} + + >>> casimir_force(force = 3457e-12, area = 0, distance = 0) + Traceback (most recent call last): + ... + ValueError: One and only one argument must be 0 + + >>> casimir_force(force = 3457e-12, area = 0, distance = -0.00344) + Traceback (most recent call last): + ... + ValueError: Distance can not be negative + + >>> casimir_force(force = -912e-12, area = 0, distance = 0.09374) + Traceback (most recent call last): + ... + ValueError: Magnitude of force can not be negative + """ + + if (force, area, distance).count(0) != 1: + raise ValueError("One and only one argument must be 0") + if force < 0: + raise ValueError("Magnitude of force can not be negative") + if distance < 0: + raise ValueError("Distance can not be negative") + if area < 0: + raise ValueError("Area can not be negative") + if force == 0: + force = (REDUCED_PLANCK_CONSTANT * SPEED_OF_LIGHT * pi**2 * area) / ( + 240 * (distance) ** 4 + ) + return {"force": force} + elif area == 0: + area = (240 * force * (distance) ** 4) / ( + REDUCED_PLANCK_CONSTANT * SPEED_OF_LIGHT * pi**2 + ) + return {"area": area} + elif distance == 0: + distance = ( + (REDUCED_PLANCK_CONSTANT * SPEED_OF_LIGHT * pi**2 * area) / (240 * force) + ) ** (1 / 4) + return {"distance": distance} + raise ValueError("One and only one argument must be 0") + + +# Run doctest +if __name__ == "__main__": + import doctest + + doctest.testmod() From c94e215c8dbdfe1f349eab5708be6b5f337b6ddd Mon Sep 17 00:00:00 2001 From: Caeden Date: Sat, 15 Oct 2022 23:51:23 +0100 Subject: [PATCH 1902/2908] types: Update binary search tree typehints (#7197) * types: Update binary search tree typehints * refactor: Don't return `self` in `:meth:insert` * test: Fix failing doctests * Apply suggestions from code review Co-authored-by: Dhruv Manilawala --- .../binary_tree/binary_search_tree.py | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 51a651be0f82..fc60540a1f3b 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -2,15 +2,18 @@ A binary search Tree """ +from collections.abc import Iterable +from typing import Any + class Node: - def __init__(self, value, parent): + def __init__(self, value: int | None = None): self.value = value - self.parent = parent # Added in order to delete a node easier - self.left = None - self.right = None + self.parent: Node | None = None # Added in order to delete a node easier + self.left: Node | None = None + self.right: Node | None = None - def __repr__(self): + def __repr__(self) -> str: from pprint import pformat if self.left is None and self.right is None: @@ -19,16 +22,16 @@ def __repr__(self): class BinarySearchTree: - def __init__(self, root=None): + def __init__(self, root: Node | None = None): self.root = root - def __str__(self): + def __str__(self) -> str: """ Return a string of all the Nodes using in order traversal """ return str(self.root) - def __reassign_nodes(self, node, new_children): + def __reassign_nodes(self, node: Node, new_children: Node | None) -> None: if new_children is not None: # reset its kids new_children.parent = node.parent if node.parent is not None: # reset its parent @@ -37,23 +40,27 @@ def __reassign_nodes(self, node, new_children): else: node.parent.left = new_children else: - self.root = new_children + self.root = None - def is_right(self, node): - return node == node.parent.right + def is_right(self, node: Node) -> bool: + if node.parent and node.parent.right: + return node == node.parent.right + return False - def empty(self): + def empty(self) -> bool: return self.root is None - def __insert(self, value): + def __insert(self, value) -> None: """ Insert a new node in Binary Search Tree with value label """ - new_node = Node(value, None) # create a new Node + new_node = Node(value) # 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 + if parent_node is None: + return None while True: # While we don't get to a leaf if value < parent_node.value: # We go left if parent_node.left is None: @@ -69,12 +76,11 @@ def __insert(self, value): parent_node = parent_node.right new_node.parent = parent_node - def insert(self, *values): + def insert(self, *values) -> None: for value in values: self.__insert(value) - return self - def search(self, value): + def search(self, value) -> Node | None: if self.empty(): raise IndexError("Warning: Tree is empty! please use another.") else: @@ -84,30 +90,35 @@ 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: Node | None = None) -> Node | None: """ We go deep on the right branch """ if node is None: + if self.root is None: + return None node = self.root + if not self.empty(): while node.right is not None: node = node.right return node - def get_min(self, node=None): + def get_min(self, node: Node | None = None) -> Node | None: """ We go deep on the left branch """ if node is None: node = self.root + if self.root is None: + return None if not self.empty(): node = self.root while node.left is not None: node = node.left return node - def remove(self, value): + def remove(self, value: int) -> None: 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 @@ -120,18 +131,18 @@ def remove(self, value): tmp_node = self.get_max( node.left ) # Gets the max value of the left branch - self.remove(tmp_node.value) + self.remove(tmp_node.value) # type: ignore node.value = ( - tmp_node.value + tmp_node.value # type: ignore ) # Assigns the value to the node to delete and keep tree structure - def preorder_traverse(self, node): + def preorder_traverse(self, node: Node | None) -> Iterable: 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, traversal_function=None): + def traversal_tree(self, traversal_function=None) -> Any: """ This function traversal the tree. You can pass a function to traversal the tree as needed by client code @@ -141,7 +152,7 @@ def traversal_tree(self, traversal_function=None): else: return traversal_function(self.root) - def inorder(self, arr: list, node: Node): + def inorder(self, arr: list, node: Node | None) -> None: """Perform an inorder traversal and append values of the nodes to a list named arr""" if node: @@ -151,12 +162,12 @@ def inorder(self, arr: list, node: Node): def find_kth_smallest(self, k: int, node: Node) -> int: """Return the kth smallest element in a binary search tree""" - arr: list = [] + arr: list[int] = [] self.inorder(arr, node) # append all values to list using inorder traversal return arr[k - 1] -def postorder(curr_node): +def postorder(curr_node: Node | None) -> list[Node]: """ postOrder (left, right, self) """ @@ -166,7 +177,7 @@ def postorder(curr_node): return node_list -def binary_search_tree(): +def binary_search_tree() -> None: r""" Example 8 @@ -177,7 +188,8 @@ def binary_search_tree(): / \ / 4 7 13 - >>> t = BinarySearchTree().insert(8, 3, 6, 1, 10, 14, 13, 4, 7) + >>> t = BinarySearchTree() + >>> t.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))) @@ -206,8 +218,8 @@ def binary_search_tree(): print("The value -1 doesn't exist") if not t.empty(): - print("Max Value: ", t.get_max().value) - print("Min Value: ", t.get_min().value) + print("Max Value: ", t.get_max().value) # type: ignore + print("Min Value: ", t.get_min().value) # type: ignore for i in testlist: t.remove(i) @@ -217,5 +229,4 @@ def binary_search_tree(): if __name__ == "__main__": import doctest - doctest.testmod() - # binary_search_tree() + doctest.testmod(verbose=True) From 04698538d816fc5f70c850e8b89c6d1f5599fa84 Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Sat, 15 Oct 2022 22:25:38 -0700 Subject: [PATCH 1903/2908] Misc fixes across multiple algorithms (#6912) Source: Snyk code quality Add scikit-fuzzy to requirements Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Dhruv Manilawala --- compression/huffman.py | 2 +- data_structures/linked_list/is_palindrome.py | 2 +- .../filters/local_binary_pattern.py | 2 +- fuzzy_logic/fuzzy_operations.py | 6 +----- graphs/dijkstra_algorithm.py | 4 ++-- .../directed_and_undirected_(weighted)_graph.py | 7 ------- hashes/hamming_code.py | 3 +-- linear_algebra/src/test_linear_algebra.py | 2 +- maths/extended_euclidean_algorithm.py | 5 +++-- maths/jaccard_similarity.py | 15 ++++++++------- matrix/matrix_class.py | 2 +- project_euler/problem_001/sol7.py | 4 +--- project_euler/problem_042/solution42.py | 11 +++++------ project_euler/problem_067/sol1.py | 8 ++++++-- project_euler/problem_089/sol1.py | 5 +++-- requirements.txt | 2 +- scheduling/first_come_first_served.py | 4 ++-- scheduling/multi_level_feedback_queue.py | 2 +- web_programming/emails_from_url.py | 2 +- 19 files changed, 40 insertions(+), 48 deletions(-) diff --git a/compression/huffman.py b/compression/huffman.py index d5d78b753c3f..f619ed82c764 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -31,7 +31,7 @@ def parse_file(file_path: str) -> list[Letter]: c = f.read(1) if not c: break - chars[c] = chars[c] + 1 if c in chars.keys() else 1 + chars[c] = chars[c] + 1 if c in chars else 1 return sorted((Letter(c, f) for c, f in chars.items()), key=lambda l: l.freq) diff --git a/data_structures/linked_list/is_palindrome.py b/data_structures/linked_list/is_palindrome.py index acc87c1c272b..ec19e99f78c0 100644 --- a/data_structures/linked_list/is_palindrome.py +++ b/data_structures/linked_list/is_palindrome.py @@ -55,7 +55,7 @@ def is_palindrome_dict(head): d = {} pos = 0 while head: - if head.val in d.keys(): + if head.val in d: d[head.val].append(pos) else: d[head.val] = [pos] diff --git a/digital_image_processing/filters/local_binary_pattern.py b/digital_image_processing/filters/local_binary_pattern.py index e73aa59bfa53..e92e554a3e5f 100644 --- a/digital_image_processing/filters/local_binary_pattern.py +++ b/digital_image_processing/filters/local_binary_pattern.py @@ -60,7 +60,7 @@ def local_binary_value(image: np.ndarray, x_coordinate: int, y_coordinate: int) ) -if __name__ == "main": +if __name__ == "__main__": # Reading the image and converting it to grayscale. image = cv2.imread( diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py index fbaca9421327..0786ef8b0c67 100644 --- a/fuzzy_logic/fuzzy_operations.py +++ b/fuzzy_logic/fuzzy_operations.py @@ -8,11 +8,7 @@ - 3.5 """ import numpy as np - -try: - import skfuzzy as fuzz -except ImportError: - fuzz = None +import skfuzzy as fuzz if __name__ == "__main__": # Create universe of discourse in Python using linspace () diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 122821a376ed..1845dad05db2 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -89,13 +89,13 @@ def add_edge(self, u, v, w): # Edge going from node u to v and v to u with weight w # u (w)-> v, v (w) -> u # Check if u already in graph - if u in self.adjList.keys(): + if u in self.adjList: self.adjList[u].append((v, w)) else: self.adjList[u] = [(v, w)] # Assuming undirected graph - if v in self.adjList.keys(): + if v in self.adjList: self.adjList[v].append((u, w)) else: self.adjList[v] = [(u, w)] diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index 5cfa9e13edd9..43a72b89e3a7 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -226,9 +226,6 @@ 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: stack.append(node[1]) visited.append(node[1]) @@ -454,10 +451,6 @@ 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: stack.append(node[1]) visited.append(node[1]) diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index a62d092a172f..481a6750773a 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -79,8 +79,7 @@ def emitter_converter(size_par, data): ['1', '1', '1', '1', '0', '1', '0', '0', '1', '0', '1', '1', '1', '1', '1', '1'] """ if size_par + len(data) <= 2**size_par - (len(data) - 1): - print("ERROR - size of parity don't match with size of data") - exit(0) + raise ValueError("size of parity don't match with size of data") data_out = [] parity = [] diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 97c06cb44e15..50d079572e0f 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -89,7 +89,7 @@ def test_zero_vector(self) -> None: """ test for global function zero_vector() """ - self.assertTrue(str(zero_vector(10)).count("0") == 10) + self.assertEqual(str(zero_vector(10)).count("0"), 10) def test_unit_basis_vector(self) -> None: """ diff --git a/maths/extended_euclidean_algorithm.py b/maths/extended_euclidean_algorithm.py index 72afd40aa707..c54909e19101 100644 --- a/maths/extended_euclidean_algorithm.py +++ b/maths/extended_euclidean_algorithm.py @@ -75,11 +75,12 @@ def main(): """Call Extended Euclidean Algorithm.""" if len(sys.argv) < 3: print("2 integer arguments required") - exit(1) + return 1 a = int(sys.argv[1]) b = int(sys.argv[2]) print(extended_euclidean_algorithm(a, b)) + return 0 if __name__ == "__main__": - main() + raise SystemExit(main()) diff --git a/maths/jaccard_similarity.py b/maths/jaccard_similarity.py index 77f4b90ea79f..b299a81476ab 100644 --- a/maths/jaccard_similarity.py +++ b/maths/jaccard_similarity.py @@ -14,7 +14,7 @@ """ -def jaccard_similariy(set_a, set_b, alternative_union=False): +def jaccard_similarity(set_a, set_b, alternative_union=False): """ Finds the jaccard similarity between two sets. Essentially, its intersection over union. @@ -35,18 +35,18 @@ def jaccard_similariy(set_a, set_b, alternative_union=False): Examples: >>> set_a = {'a', 'b', 'c', 'd', 'e'} >>> set_b = {'c', 'd', 'e', 'f', 'h', 'i'} - >>> jaccard_similariy(set_a, set_b) + >>> jaccard_similarity(set_a, set_b) 0.375 - >>> jaccard_similariy(set_a, set_a) + >>> jaccard_similarity(set_a, set_a) 1.0 - >>> jaccard_similariy(set_a, set_a, True) + >>> jaccard_similarity(set_a, set_a, True) 0.5 >>> set_a = ['a', 'b', 'c', 'd', 'e'] >>> set_b = ('c', 'd', 'e', 'f', 'h', 'i') - >>> jaccard_similariy(set_a, set_b) + >>> jaccard_similarity(set_a, set_b) 0.375 """ @@ -67,14 +67,15 @@ def jaccard_similariy(set_a, set_b, alternative_union=False): if alternative_union: union = len(set_a) + len(set_b) + return len(intersection) / union else: union = set_a + [element for element in set_b if element not in set_a] + return len(intersection) / len(union) return len(intersection) / len(union) if __name__ == "__main__": - set_a = {"a", "b", "c", "d", "e"} set_b = {"c", "d", "e", "f", "h", "i"} - print(jaccard_similariy(set_a, set_b)) + print(jaccard_similarity(set_a, set_b)) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 6495bd8fc88d..8b6fefa2124b 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -286,7 +286,7 @@ def add_column(self, column: list[int], position: int | None = None) -> None: # MATRIX OPERATIONS def __eq__(self, other: object) -> bool: if not isinstance(other, Matrix): - raise TypeError("A Matrix can only be compared with another Matrix") + return NotImplemented return self.rows == other.rows def __ne__(self, other: object) -> bool: diff --git a/project_euler/problem_001/sol7.py b/project_euler/problem_001/sol7.py index 8f5d1977fdde..6ada70c12dbd 100644 --- a/project_euler/problem_001/sol7.py +++ b/project_euler/problem_001/sol7.py @@ -26,9 +26,7 @@ def solution(n: int = 1000) -> int: result = 0 for i in range(n): - if i % 3 == 0: - result += i - elif i % 5 == 0: + if i % 3 == 0 or i % 5 == 0: result += i return result diff --git a/project_euler/problem_042/solution42.py b/project_euler/problem_042/solution42.py index c0fb2ad50c11..f8a54e40eaab 100644 --- a/project_euler/problem_042/solution42.py +++ b/project_euler/problem_042/solution42.py @@ -34,12 +34,11 @@ def solution(): words = f.readline() words = [word.strip('"') for word in words.strip("\r\n").split(",")] - words = list( - filter( - lambda word: word in TRIANGULAR_NUMBERS, - (sum(ord(x) - 64 for x in word) for word in words), - ) - ) + words = [ + word + for word in [sum(ord(x) - 64 for x in word) for word in words] + if word in TRIANGULAR_NUMBERS + ] return len(words) diff --git a/project_euler/problem_067/sol1.py b/project_euler/problem_067/sol1.py index ab305684dd0d..f20c206cca11 100644 --- a/project_euler/problem_067/sol1.py +++ b/project_euler/problem_067/sol1.py @@ -28,8 +28,12 @@ def solution(): with open(triangle) as f: triangle = f.readlines() - a = (x.rstrip("\r\n").split(" ") for x in triangle) - a = [list(map(int, x)) for x in a] + a = [] + for line in triangle: + numbers_from_line = [] + for number in line.strip().split(" "): + numbers_from_line.append(int(number)) + a.append(numbers_from_line) for i in range(1, len(a)): for j in range(len(a[i])): diff --git a/project_euler/problem_089/sol1.py b/project_euler/problem_089/sol1.py index 1c4e2600f847..83609cd236e1 100644 --- a/project_euler/problem_089/sol1.py +++ b/project_euler/problem_089/sol1.py @@ -125,8 +125,9 @@ def solution(roman_numerals_filename: str = "/p089_roman.txt") -> int: savings = 0 - file1 = open(os.path.dirname(__file__) + roman_numerals_filename) - lines = file1.readlines() + with open(os.path.dirname(__file__) + roman_numerals_filename) as file1: + lines = file1.readlines() + for line in lines: original = line.strip() num = parse_roman_numerals(original) diff --git a/requirements.txt b/requirements.txt index 0fbc1cc4b45c..b14a3eb0157c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pandas pillow qiskit requests -# scikit-fuzzy # Causing broken builds +scikit-fuzzy sklearn statsmodels sympy diff --git a/scheduling/first_come_first_served.py b/scheduling/first_come_first_served.py index c5f61720f97e..06cdb8ddf821 100644 --- a/scheduling/first_come_first_served.py +++ b/scheduling/first_come_first_served.py @@ -79,7 +79,7 @@ def calculate_average_waiting_time(waiting_times: list[int]) -> float: # ensure that we actually have processes if len(processes) == 0: print("Zero amount of processes") - exit() + raise SystemExit(0) # duration time of all processes duration_times = [19, 8, 9] @@ -87,7 +87,7 @@ def calculate_average_waiting_time(waiting_times: list[int]) -> float: # 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() + raise SystemExit(0) # get the waiting times and the turnaround times waiting_times = calculate_waiting_times(duration_times) diff --git a/scheduling/multi_level_feedback_queue.py b/scheduling/multi_level_feedback_queue.py index a3ba1b340e9b..abee3c85c5a5 100644 --- a/scheduling/multi_level_feedback_queue.py +++ b/scheduling/multi_level_feedback_queue.py @@ -276,7 +276,7 @@ def multi_level_feedback_queue(self) -> deque[Process]: queue = deque([P1, P2, P3, P4]) if len(time_slices) != number_of_queues - 1: - exit() + raise SystemExit(0) doctest.testmod(extraglobs={"queue": deque([P1, P2, P3, P4])}) diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index afaee5bbe854..074ef878c0d7 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -93,7 +93,7 @@ def emails_from_url(/service/url: str = "https://github.com/") -> list[str]: except ValueError: pass except ValueError: - exit(-1) + raise SystemExit(1) # Finally return a sorted list of email addresses with no duplicates. return sorted(valid_emails) From e7b6d2824a65985790d0044262f717898ffbeb4d Mon Sep 17 00:00:00 2001 From: Sagar Giri Date: Sun, 16 Oct 2022 16:43:29 +0900 Subject: [PATCH 1904/2908] Change to https. (#7277) * Change to https. * Revert the py_tf file. --- fractals/julia_sets.py | 2 +- fractals/sierpinski_triangle.py | 2 +- machine_learning/lstm/lstm_prediction.py | 2 +- machine_learning/sequential_minimum_optimization.py | 4 ++-- maths/matrix_exponentiation.py | 2 +- maths/test_prime_check.py | 2 +- physics/n_body_simulation.py | 4 ++-- strings/frequency_finder.py | 2 +- web_programming/crawl_google_results.py | 2 +- web_programming/crawl_google_scholar_citation.py | 2 +- web_programming/current_weather.py | 2 +- web_programming/giphy.py | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/fractals/julia_sets.py b/fractals/julia_sets.py index 28c675c750bc..35fdc45d020a 100644 --- a/fractals/julia_sets.py +++ b/fractals/julia_sets.py @@ -12,7 +12,7 @@ https://en.wikipedia.org/wiki/File:Julia_z2%2B0,25.png - Other examples from https://en.wikipedia.org/wiki/Julia_set - An exponential map Julia set, ambiantly homeomorphic to the examples in -http://www.math.univ-toulouse.fr/~cheritat/GalII/galery.html +https://www.math.univ-toulouse.fr/~cheritat/GalII/galery.html and https://ddd.uab.cat/pub/pubmat/02141493v43n1/02141493v43n1p27.pdf diff --git a/fractals/sierpinski_triangle.py b/fractals/sierpinski_triangle.py index 8be2897c152a..084f6661f425 100644 --- a/fractals/sierpinski_triangle.py +++ b/fractals/sierpinski_triangle.py @@ -24,7 +24,7 @@ - $python sierpinski_triangle.py Credits: This code was written by editing the code from -http://www.riannetrujillo.com/blog/python-fractal/ +https://www.riannetrujillo.com/blog/python-fractal/ """ import sys diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py index 6fd3cf29131d..74197c46a0ad 100644 --- a/machine_learning/lstm/lstm_prediction.py +++ b/machine_learning/lstm/lstm_prediction.py @@ -1,7 +1,7 @@ """ 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://colah.github.io/posts/2015-08-Understanding-LSTMs * https://en.wikipedia.org/wiki/Long_short-term_memory """ import numpy as np diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index fb4b35f31289..40adca7e0828 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -28,7 +28,7 @@ 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 + https://web.cs.iastate.edu/~honavar/smo-svm.pdf """ @@ -43,7 +43,7 @@ from sklearn.preprocessing import StandardScaler CANCER_DATASET_URL = ( - "http://archive.ics.uci.edu/ml/machine-learning-databases/" + "https://archive.ics.uci.edu/ml/machine-learning-databases/" "breast-cancer-wisconsin/wdbc.data" ) diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index 033ceb3f28a0..7c37151c87ca 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -5,7 +5,7 @@ """ 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://zobayer.blogspot.com/2010/11/matrix-exponentiation.html https://www.hackerearth.com/practice/notes/matrix-exponentiation-1/ """ diff --git a/maths/test_prime_check.py b/maths/test_prime_check.py index b6389684af9e..3ea3b2f1f88b 100644 --- a/maths/test_prime_check.py +++ b/maths/test_prime_check.py @@ -1,6 +1,6 @@ """ 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 +https://doc.pytest.org/en/latest/goodpractices.html#conventions-for-python-test-discovery """ from .prime_check import Test diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index 2f8153782663..e62e1de62757 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -8,7 +8,7 @@ numerical divergences when a particle comes too close to another (and the force goes to infinity). (Description adapted from https://en.wikipedia.org/wiki/N-body_simulation ) -(See also http://www.shodor.org/refdesk/Resources/Algorithms/EulersMethod/ ) +(See also https://www.shodor.org/refdesk/Resources/Algorithms/EulersMethod/ ) """ @@ -258,7 +258,7 @@ def example_1() -> BodySystem: Example 1: figure-8 solution to the 3-body-problem This example can be seen as a test of the implementation: given the right initial conditions, the bodies should move in a figure-8. - (initial conditions taken from http://www.artcompsci.org/vol_1/v1_web/node56.html) + (initial conditions taken from https://www.artcompsci.org/vol_1/v1_web/node56.html) >>> body_system = example_1() >>> len(body_system) 3 diff --git a/strings/frequency_finder.py b/strings/frequency_finder.py index 7024be17b8ab..19f97afbbe37 100644 --- a/strings/frequency_finder.py +++ b/strings/frequency_finder.py @@ -2,7 +2,7 @@ import string -# frequency taken from http://en.wikipedia.org/wiki/Letter_frequency +# frequency taken from https://en.wikipedia.org/wiki/Letter_frequency english_letter_freq = { "E": 12.70, "T": 9.06, diff --git a/web_programming/crawl_google_results.py b/web_programming/crawl_google_results.py index a33a3f3bbe5c..1f5e6d31992b 100644 --- a/web_programming/crawl_google_results.py +++ b/web_programming/crawl_google_results.py @@ -21,4 +21,4 @@ if link.text == "Maps": webbrowser.open(link.get("href")) else: - webbrowser.open(f"http://google.com{link.get(/'href')}") + webbrowser.open(f"https://google.com{link.get(/'href')}") diff --git a/web_programming/crawl_google_scholar_citation.py b/web_programming/crawl_google_scholar_citation.py index d023380c0818..f92a3d139520 100644 --- a/web_programming/crawl_google_scholar_citation.py +++ b/web_programming/crawl_google_scholar_citation.py @@ -29,4 +29,4 @@ def get_citation(base_url: str, params: dict) -> str: "year": 2018, "hl": "en", } - print(get_citation("http://scholar.google.com/scholar_lookup", params=params)) + print(get_citation("https://scholar.google.com/scholar_lookup", params=params)) diff --git a/web_programming/current_weather.py b/web_programming/current_weather.py index e043b438473f..3ed4c8a95a0c 100644 --- a/web_programming/current_weather.py +++ b/web_programming/current_weather.py @@ -1,7 +1,7 @@ import requests APPID = "" # <-- Put your OpenWeatherMap appid here! -URL_BASE = "http://api.openweathermap.org/data/2.5/" +URL_BASE = "https://api.openweathermap.org/data/2.5/" def current_weather(q: str = "Chicago", appid: str = APPID) -> dict: diff --git a/web_programming/giphy.py b/web_programming/giphy.py index dc8c6be08caa..a5c3f8f7493e 100644 --- a/web_programming/giphy.py +++ b/web_programming/giphy.py @@ -10,7 +10,7 @@ def get_gifs(query: str, api_key: str = giphy_api_key) -> list: Get a list of URLs of GIFs based on a given query.. """ formatted_query = "+".join(query.split()) - url = f"http://api.giphy.com/v1/gifs/search?q={formatted_query}&api_key={api_key}" + url = f"https://api.giphy.com/v1/gifs/search?q={formatted_query}&api_key={api_key}" gifs = requests.get(url).json()["data"] return [gif["url"] for gif in gifs] From 77764116217708933bdc65b29801092fa291398e Mon Sep 17 00:00:00 2001 From: Kevin Joven <59969678+KevinJoven11@users.noreply.github.com> Date: Sun, 16 Oct 2022 02:47:54 -0500 Subject: [PATCH 1905/2908] Create q_full_adder.py (#6735) * Create q_full_adder.py This is for the #Hacktoberfest. This circuit is the quantum full adder. I saw that in the repo is the half adder so I decided to build the full adder to complete the set of adders. I hope that this is enough to be consider a contribution. Best, Kevin * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Erase the unused numpy library * Create the doctest. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doctest for negative numbers, float, etc. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- quantum/q_full_adder.py | 112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 quantum/q_full_adder.py diff --git a/quantum/q_full_adder.py b/quantum/q_full_adder.py new file mode 100644 index 000000000000..597efb8342e1 --- /dev/null +++ b/quantum/q_full_adder.py @@ -0,0 +1,112 @@ +""" +Build the quantum full adder (QFA) for any sum of +two quantum registers and one carry in. This circuit +is designed using the Qiskit framework. This +experiment run in IBM Q simulator with 1000 shots. +. +References: +https://www.quantum-inspire.com/kbase/full-adder/ +""" + +import math + +import qiskit +from qiskit import Aer, ClassicalRegister, QuantumCircuit, QuantumRegister, execute + + +def quantum_full_adder( + input_1: int = 1, input_2: int = 1, carry_in: int = 1 +) -> qiskit.result.counts.Counts: + """ + # >>> q_full_adder(inp_1, inp_2, cin) + # the inputs can be 0/1 for qubits in define + # values, or can be in a superposition of both + # states with hadamard gate using the input value 2. + # result for default values: {11: 1000} + qr_0: ──■────■──────────────■── + │ ┌─┴─┐ ┌─┴─┐ + qr_1: ──■──┤ X ├──■────■──┤ X ├ + │ └───┘ │ ┌─┴─┐└───┘ + qr_2: ──┼─────────■──┤ X ├───── + ┌─┴─┐ ┌─┴─┐└───┘ + qr_3: ┤ X ├─────┤ X ├────────── + └───┘ └───┘ + cr: 2/═════════════════════════ + Args: + input_1: input 1 for the circuit. + input_2: input 2 for the circuit. + carry_in: carry in for the circuit. + Returns: + qiskit.result.counts.Counts: sum result counts. + >>> quantum_full_adder(1,1,1) + {'11': 1000} + >>> quantum_full_adder(0,0,1) + {'01': 1000} + >>> quantum_full_adder(1,0,1) + {'10': 1000} + >>> quantum_full_adder(1,-4,1) + Traceback (most recent call last): + ... + ValueError: inputs must be positive. + >>> quantum_full_adder('q',0,1) + Traceback (most recent call last): + ... + TypeError: inputs must be integers. + >>> quantum_full_adder(0.5,0,1) + Traceback (most recent call last): + ... + ValueError: inputs must be exact integers. + >>> quantum_full_adder(0,1,3) + Traceback (most recent call last): + ... + ValueError: inputs must be less or equal to 2. + """ + if (type(input_1) == str) or (type(input_2) == str) or (type(carry_in) == str): + raise TypeError("inputs must be integers.") + + if (input_1 < 0) or (input_2 < 0) or (carry_in < 0): + raise ValueError("inputs must be positive.") + + if ( + (math.floor(input_1) != input_1) + or (math.floor(input_2) != input_2) + or (math.floor(carry_in) != carry_in) + ): + raise ValueError("inputs must be exact integers.") + + if (input_1 > 2) or (input_2 > 2) or (carry_in > 2): + raise ValueError("inputs must be less or equal to 2.") + + # build registers + qr = QuantumRegister(4, "qr") + cr = ClassicalRegister(2, "cr") + # list the entries + entry = [input_1, input_2, carry_in] + + quantum_circuit = QuantumCircuit(qr, cr) + + for i in range(0, 3): + if entry[i] == 2: + quantum_circuit.h(i) # for hadamard entries + elif entry[i] == 1: + quantum_circuit.x(i) # for 1 entries + elif entry[i] == 0: + quantum_circuit.i(i) # for 0 entries + + # build the circuit + quantum_circuit.ccx(0, 1, 3) # ccx = toffoli gate + quantum_circuit.cx(0, 1) + quantum_circuit.ccx(1, 2, 3) + quantum_circuit.cx(1, 2) + quantum_circuit.cx(0, 1) + + quantum_circuit.measure([2, 3], cr) # measure the last two qbits + + backend = Aer.get_backend("qasm_simulator") + job = execute(quantum_circuit, backend, shots=1000) + + return job.result().get_counts(quantum_circuit) + + +if __name__ == "__main__": + print(f"Total sum count for state is: {quantum_full_adder(1,1,1)}") From c6582b35bf8b8aba622c63096e3ab2f01aa36854 Mon Sep 17 00:00:00 2001 From: Caeden Date: Sun, 16 Oct 2022 10:33:29 +0100 Subject: [PATCH 1906/2908] refactor: Move constants outside of variable scope (#7262) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Dhruv Manilawala Co-authored-by: Christian Clauss --- ciphers/bifid.py | 15 ++++---- ciphers/brute_force_caesar_cipher.py | 14 ++++--- ciphers/polybius.py | 16 ++++---- compression/peak_signal_to_noise_ratio.py | 13 ++++--- conversions/binary_to_hexadecimal.py | 39 ++++++++++---------- conversions/decimal_to_any.py | 11 ++---- conversions/roman_numerals.py | 32 ++++++++-------- geodesy/haversine_distance.py | 7 ++-- geodesy/lamberts_ellipsoidal_distance.py | 8 ++-- hashes/adler32.py | 3 +- physics/n_body_simulation.py | 12 +++--- project_euler/problem_054/test_poker_hand.py | 6 +-- project_euler/problem_064/sol1.py | 8 ++-- project_euler/problem_097/sol1.py | 6 +-- project_euler/problem_125/sol1.py | 3 +- sorts/radix_sort.py | 3 +- web_programming/fetch_quotes.py | 8 ++-- 17 files changed, 107 insertions(+), 97 deletions(-) diff --git a/ciphers/bifid.py b/ciphers/bifid.py index 54d55574cdca..c005e051a6ba 100644 --- a/ciphers/bifid.py +++ b/ciphers/bifid.py @@ -9,16 +9,17 @@ import numpy as np +SQUARE = [ + ["a", "b", "c", "d", "e"], + ["f", "g", "h", "i", "k"], + ["l", "m", "n", "o", "p"], + ["q", "r", "s", "t", "u"], + ["v", "w", "x", "y", "z"], +] + class BifidCipher: def __init__(self) -> None: - SQUARE = [ # noqa: N806 - ["a", "b", "c", "d", "e"], - ["f", "g", "h", "i", "k"], - ["l", "m", "n", "o", "p"], - ["q", "r", "s", "t", "u"], - ["v", "w", "x", "y", "z"], - ] self.SQUARE = np.array(SQUARE) def letter_to_numbers(self, letter: str) -> np.ndarray: diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index cc97111e05a7..458d08db2628 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -1,3 +1,6 @@ +import string + + def decrypt(message: str) -> None: """ >>> decrypt('TMDETUX PMDVU') @@ -28,16 +31,15 @@ def decrypt(message: str) -> None: Decryption using Key #24: VOFGVWZ ROFXW Decryption using Key #25: UNEFUVY QNEWV """ - LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # noqa: N806 - for key in range(len(LETTERS)): + for key in range(len(string.ascii_uppercase)): translated = "" for symbol in message: - if symbol in LETTERS: - num = LETTERS.find(symbol) + if symbol in string.ascii_uppercase: + num = string.ascii_uppercase.find(symbol) num = num - key if num < 0: - num = num + len(LETTERS) - translated = translated + LETTERS[num] + num = num + len(string.ascii_uppercase) + translated = translated + string.ascii_uppercase[num] else: translated = translated + symbol print(f"Decryption using Key #{key}: {translated}") diff --git a/ciphers/polybius.py b/ciphers/polybius.py index bf5d62f8d33e..c81c1d39533f 100644 --- a/ciphers/polybius.py +++ b/ciphers/polybius.py @@ -8,16 +8,18 @@ import numpy as np +SQUARE = [ + ["a", "b", "c", "d", "e"], + ["f", "g", "h", "i", "k"], + ["l", "m", "n", "o", "p"], + ["q", "r", "s", "t", "u"], + ["v", "w", "x", "y", "z"], +] + class PolybiusCipher: def __init__(self) -> None: - SQUARE = [ # noqa: N806 - ["a", "b", "c", "d", "e"], - ["f", "g", "h", "i", "k"], - ["l", "m", "n", "o", "p"], - ["q", "r", "s", "t", "u"], - ["v", "w", "x", "y", "z"], - ] + self.SQUARE = np.array(SQUARE) def letter_to_numbers(self, letter: str) -> np.ndarray: diff --git a/compression/peak_signal_to_noise_ratio.py b/compression/peak_signal_to_noise_ratio.py index 66b18b50b028..284f2904a21d 100644 --- a/compression/peak_signal_to_noise_ratio.py +++ b/compression/peak_signal_to_noise_ratio.py @@ -11,14 +11,15 @@ import cv2 import numpy as np +PIXEL_MAX = 255.0 -def psnr(original: float, contrast: float) -> float: + +def peak_signal_to_noise_ratio(original: float, contrast: float) -> float: mse = np.mean((original - contrast) ** 2) if mse == 0: return 100 - PIXEL_MAX = 255.0 # noqa: N806 - PSNR = 20 * math.log10(PIXEL_MAX / math.sqrt(mse)) # noqa: N806 - return PSNR + + return 20 * math.log10(PIXEL_MAX / math.sqrt(mse)) def main() -> None: @@ -34,11 +35,11 @@ def main() -> None: # Value expected: 29.73dB print("-- First Test --") - print(f"PSNR value is {psnr(original, contrast)} dB") + print(f"PSNR value is {peak_signal_to_noise_ratio(original, contrast)} dB") # # Value expected: 31.53dB (Wikipedia Example) print("\n-- Second Test --") - print(f"PSNR value is {psnr(original2, contrast2)} dB") + print(f"PSNR value is {peak_signal_to_noise_ratio(original2, contrast2)} dB") if __name__ == "__main__": diff --git a/conversions/binary_to_hexadecimal.py b/conversions/binary_to_hexadecimal.py index 61f335a4c465..89f7af696357 100644 --- a/conversions/binary_to_hexadecimal.py +++ b/conversions/binary_to_hexadecimal.py @@ -1,3 +1,23 @@ +BITS_TO_HEX = { + "0000": "0", + "0001": "1", + "0010": "2", + "0011": "3", + "0100": "4", + "0101": "5", + "0110": "6", + "0111": "7", + "1000": "8", + "1001": "9", + "1010": "a", + "1011": "b", + "1100": "c", + "1101": "d", + "1110": "e", + "1111": "f", +} + + def bin_to_hexadecimal(binary_str: str) -> str: """ Converting a binary string into hexadecimal using Grouping Method @@ -17,25 +37,6 @@ def bin_to_hexadecimal(binary_str: str) -> str: ... ValueError: Empty string was passed to the function """ - BITS_TO_HEX = { # noqa: N806 - "0000": "0", - "0001": "1", - "0010": "2", - "0011": "3", - "0100": "4", - "0101": "5", - "0110": "6", - "0111": "7", - "1000": "8", - "1001": "9", - "1010": "a", - "1011": "b", - "1100": "c", - "1101": "d", - "1110": "e", - "1111": "f", - } - # Sanitising parameter binary_str = str(binary_str).strip() diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index e54fa154a0f7..908c89e8fb6b 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -1,5 +1,9 @@ """Convert a positive Decimal Number to Any Other Representation""" +from string import ascii_uppercase + +ALPHABET_VALUES = {str(ord(c) - 55): c for c in ascii_uppercase} + def decimal_to_any(num: int, base: int) -> str: """ @@ -65,13 +69,6 @@ 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', # noqa: N806, E501 - '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 = "" mod = 0 div = 0 diff --git a/conversions/roman_numerals.py b/conversions/roman_numerals.py index 960d41342276..61215a0c0730 100644 --- a/conversions/roman_numerals.py +++ b/conversions/roman_numerals.py @@ -1,3 +1,20 @@ +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"), +] + + def roman_to_int(roman: str) -> int: """ LeetCode No. 13 Roman to Integer @@ -29,21 +46,6 @@ def int_to_roman(number: int) -> str: >>> all(int_to_roman(value) == key for key, value in tests.items()) True """ - ROMAN = [ # noqa: N806 - (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) diff --git a/geodesy/haversine_distance.py b/geodesy/haversine_distance.py index b601d2fd1983..93e625770f9d 100644 --- a/geodesy/haversine_distance.py +++ b/geodesy/haversine_distance.py @@ -1,5 +1,9 @@ from math import asin, atan, cos, radians, sin, sqrt, tan +AXIS_A = 6378137.0 +AXIS_B = 6356752.314245 +RADIUS = 6378137 + def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float: """ @@ -30,9 +34,6 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl """ # CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System # Distance in metres(m) - AXIS_A = 6378137.0 # noqa: N806 - AXIS_B = 6356752.314245 # noqa: N806 - RADIUS = 6378137 # noqa: N806 # Equation parameters # Equation https://en.wikipedia.org/wiki/Haversine_formula#Formulation flattening = (AXIS_A - AXIS_B) / AXIS_A diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py index d36d399538de..62ce59bb476f 100644 --- a/geodesy/lamberts_ellipsoidal_distance.py +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -2,6 +2,10 @@ from .haversine_distance import haversine_distance +AXIS_A = 6378137.0 +AXIS_B = 6356752.314245 +EQUATORIAL_RADIUS = 6378137 + def lamberts_ellipsoidal_distance( lat1: float, lon1: float, lat2: float, lon2: float @@ -45,10 +49,6 @@ def lamberts_ellipsoidal_distance( # CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System # Distance in metres(m) - AXIS_A = 6378137.0 # noqa: N806 - AXIS_B = 6356752.314245 # noqa: N806 - EQUATORIAL_RADIUS = 6378137 # noqa: N806 - # Equation Parameters # https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines flattening = (AXIS_A - AXIS_B) / AXIS_A diff --git a/hashes/adler32.py b/hashes/adler32.py index 80229f04620a..611ebc88b80f 100644 --- a/hashes/adler32.py +++ b/hashes/adler32.py @@ -8,6 +8,8 @@ source: https://en.wikipedia.org/wiki/Adler-32 """ +MOD_ADLER = 65521 + def adler32(plain_text: str) -> int: """ @@ -20,7 +22,6 @@ def adler32(plain_text: str) -> int: >>> adler32('go adler em all') 708642122 """ - MOD_ADLER = 65521 # noqa: N806 a = 1 b = 0 for plain_chr in plain_text: diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index e62e1de62757..f6efb0fec81c 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -19,6 +19,12 @@ from matplotlib import animation from matplotlib import pyplot as plt +# Frame rate of the animation +INTERVAL = 20 + +# Time between time steps in seconds +DELTA_TIME = INTERVAL / 1000 + class Body: def __init__( @@ -219,12 +225,6 @@ def plot( Utility function to plot how the given body-system evolves over time. No doctest provided since this function does not have a return value. """ - # Frame rate of the animation - INTERVAL = 20 # noqa: N806 - - # Time between time steps in seconds - DELTA_TIME = INTERVAL / 1000 # noqa: N806 - fig = plt.figure() fig.canvas.set_window_title(title) ax = plt.axes( diff --git a/project_euler/problem_054/test_poker_hand.py b/project_euler/problem_054/test_poker_hand.py index bf5a20a8e862..5735bfc37947 100644 --- a/project_euler/problem_054/test_poker_hand.py +++ b/project_euler/problem_054/test_poker_hand.py @@ -185,12 +185,12 @@ def test_compare_random(hand, other, expected): def test_hand_sorted(): - POKER_HANDS = [PokerHand(hand) for hand in SORTED_HANDS] # noqa: N806 - list_copy = POKER_HANDS.copy() + 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] + assert hand == poker_hands[index] def test_custom_sort_five_high_straight(): diff --git a/project_euler/problem_064/sol1.py b/project_euler/problem_064/sol1.py index 9edd9a1e7a64..81ebcc7b73c3 100644 --- a/project_euler/problem_064/sol1.py +++ b/project_euler/problem_064/sol1.py @@ -33,13 +33,13 @@ def continuous_fraction_period(n: int) -> int: """ numerator = 0.0 denominator = 1.0 - ROOT = int(sqrt(n)) # noqa: N806 - integer_part = ROOT + root = int(sqrt(n)) + integer_part = root period = 0 - while integer_part != 2 * ROOT: + while integer_part != 2 * root: numerator = denominator * integer_part - numerator denominator = (n - numerator**2) / denominator - integer_part = int((ROOT + numerator) / denominator) + integer_part = int((root + numerator) / denominator) period += 1 return period diff --git a/project_euler/problem_097/sol1.py b/project_euler/problem_097/sol1.py index 94a43894ee07..2807e893ded0 100644 --- a/project_euler/problem_097/sol1.py +++ b/project_euler/problem_097/sol1.py @@ -34,9 +34,9 @@ def solution(n: int = 10) -> str: """ if not isinstance(n, int) or n < 0: raise ValueError("Invalid input") - MODULUS = 10**n # noqa: N806 - NUMBER = 28433 * (pow(2, 7830457, MODULUS)) + 1 # noqa: N806 - return str(NUMBER % MODULUS) + modulus = 10**n + number = 28433 * (pow(2, 7830457, modulus)) + 1 + return str(number % modulus) if __name__ == "__main__": diff --git a/project_euler/problem_125/sol1.py b/project_euler/problem_125/sol1.py index 1812df36132e..616f6f122f97 100644 --- a/project_euler/problem_125/sol1.py +++ b/project_euler/problem_125/sol1.py @@ -13,6 +13,8 @@ be written as the sum of consecutive squares. """ +LIMIT = 10**8 + def is_palindrome(n: int) -> bool: """ @@ -35,7 +37,6 @@ 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 # noqa: N806 answer = set() first_square = 1 sum_squares = 5 diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index a496cdc0c743..832b6162f349 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -5,6 +5,8 @@ """ from __future__ import annotations +RADIX = 10 + def radix_sort(list_of_ints: list[int]) -> list[int]: """ @@ -19,7 +21,6 @@ def radix_sort(list_of_ints: list[int]) -> list[int]: >>> radix_sort([1,100,10,1000]) == sorted([1,100,10,1000]) True """ - RADIX = 10 # noqa: N806 placement = 1 max_digit = max(list_of_ints) while placement <= max_digit: diff --git a/web_programming/fetch_quotes.py b/web_programming/fetch_quotes.py index a45f6ea0eaf1..d557e2d95e74 100644 --- a/web_programming/fetch_quotes.py +++ b/web_programming/fetch_quotes.py @@ -10,15 +10,15 @@ import requests +API_ENDPOINT_URL = "https://zenquotes.io/api" + def quote_of_the_day() -> list: - API_ENDPOINT_URL = "https://zenquotes.io/api/today/" # noqa: N806 - return requests.get(API_ENDPOINT_URL).json() + return requests.get(API_ENDPOINT_URL + "/today").json() def random_quotes() -> list: - API_ENDPOINT_URL = "https://zenquotes.io/api/random/" # noqa: N806 - return requests.get(API_ENDPOINT_URL).json() + return requests.get(API_ENDPOINT_URL + "/random").json() if __name__ == "__main__": From d728f5a96bce1cb748d903de2f7dff2e2a2b54eb Mon Sep 17 00:00:00 2001 From: Advik Sharma <70201060+advik-student-dev@users.noreply.github.com> Date: Sun, 16 Oct 2022 06:28:10 -0700 Subject: [PATCH 1907/2908] Added some more comments to volume.py in maths folder (#7080) * Added some more comments added some more comments (to formulas which need it) which make the code more readable and understandable. might make a list of all the formulas on the top, later * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * The order changes the result * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix long line * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/volume.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/maths/volume.py b/maths/volume.py index 97c06d7e1c3a..a594e1b90feb 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -1,6 +1,7 @@ """ -Find Volumes of Various Shapes. -Wikipedia reference: https://en.wikipedia.org/wiki/Volume +Find the volume of various shapes. +* https://en.wikipedia.org/wiki/Volume +* https://en.wikipedia.org/wiki/Spherical_cap """ from __future__ import annotations @@ -30,8 +31,7 @@ def vol_cube(side_length: int | float) -> float: def vol_spherical_cap(height: float, radius: float) -> float: """ - Calculate the Volume of the spherical cap. - :return 1/3 pi * height ^ 2 * (3 * radius - height) + Calculate the volume of the spherical cap. >>> vol_spherical_cap(1, 2) 5.235987755982988 >>> vol_spherical_cap(1.6, 2.6) @@ -49,6 +49,7 @@ def vol_spherical_cap(height: float, radius: float) -> float: """ if height < 0 or radius < 0: raise ValueError("vol_spherical_cap() only accepts non-negative values") + # Volume is 1/3 pi * height squared * (3 * radius - height) return 1 / 3 * pi * pow(height, 2) * (3 * radius - height) @@ -263,6 +264,7 @@ def vol_sphere(radius: float) -> float: """ if radius < 0: raise ValueError("vol_sphere() only accepts non-negative values") + # Volume is 4/3 * pi * radius cubed return 4 / 3 * pi * pow(radius, 3) @@ -274,7 +276,7 @@ def vol_hemisphere(radius: float) -> float: >>> vol_hemisphere(1) 2.0943951023931953 >>> vol_hemisphere(7) - 718.3775201208659 + 718.377520120866 >>> vol_hemisphere(1.6) 8.57864233940253 >>> vol_hemisphere(0) @@ -286,7 +288,8 @@ def vol_hemisphere(radius: float) -> float: """ if radius < 0: raise ValueError("vol_hemisphere() only accepts non-negative values") - return 2 / 3 * pi * pow(radius, 3) + # Volume is radius cubed * pi * 2/3 + return pow(radius, 3) * pi * 2 / 3 def vol_circular_cylinder(radius: float, height: float) -> float: @@ -312,7 +315,8 @@ def vol_circular_cylinder(radius: float, height: float) -> float: """ if height < 0 or radius < 0: raise ValueError("vol_circular_cylinder() only accepts non-negative values") - return pi * pow(radius, 2) * height + # Volume is radius squared * height * pi + return pow(radius, 2) * height * pi def vol_hollow_circular_cylinder( @@ -344,6 +348,7 @@ def vol_hollow_circular_cylinder( ... ValueError: outer_radius must be greater than inner_radius """ + # Volume - (outer_radius squared - inner_radius squared) * pi * height if inner_radius < 0 or outer_radius < 0 or height < 0: raise ValueError( "vol_hollow_circular_cylinder() only accepts non-negative values" @@ -356,7 +361,7 @@ def vol_hollow_circular_cylinder( def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> float: """Calculate the Volume of a Conical Frustum. Wikipedia reference: https://en.wikipedia.org/wiki/Frustum - :return 1/3 * pi * height * (radius_1^2 + radius_top^2 + radius_1 * radius_2) + >>> vol_conical_frustum(45, 7, 28) 48490.482608158454 >>> vol_conical_frustum(1, 1, 2) @@ -378,6 +383,8 @@ def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> floa ... ValueError: vol_conical_frustum() only accepts non-negative values """ + # Volume is 1/3 * pi * height * + # (radius_1 squared + radius_2 squared + radius_1 * radius_2) if radius_1 < 0 or radius_2 < 0 or height < 0: raise ValueError("vol_conical_frustum() only accepts non-negative values") return ( From b5b1eb2f00f942955217ef6968fe8016476690ba Mon Sep 17 00:00:00 2001 From: Sagar Giri Date: Sun, 16 Oct 2022 22:45:25 +0900 Subject: [PATCH 1908/2908] Fix broken links by PR #7277 (#7319) --- bit_manipulation/count_1s_brian_kernighan_method.py | 2 +- machine_learning/sequential_minimum_optimization.py | 1 - physics/n_body_simulation.py | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bit_manipulation/count_1s_brian_kernighan_method.py b/bit_manipulation/count_1s_brian_kernighan_method.py index d217af90b3d9..e6d6d65345c4 100644 --- a/bit_manipulation/count_1s_brian_kernighan_method.py +++ b/bit_manipulation/count_1s_brian_kernighan_method.py @@ -1,7 +1,7 @@ def get_1s_count(number: int) -> int: """ Count the number of set bits in a 32 bit integer using Brian Kernighan's way. - Ref - http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan + Ref - https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan >>> get_1s_count(25) 3 >>> get_1s_count(37) diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 40adca7e0828..df5b03790804 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -28,7 +28,6 @@ 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 - https://web.cs.iastate.edu/~honavar/smo-svm.pdf """ diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index f6efb0fec81c..2b701283f166 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -8,7 +8,7 @@ numerical divergences when a particle comes too close to another (and the force goes to infinity). (Description adapted from https://en.wikipedia.org/wiki/N-body_simulation ) -(See also https://www.shodor.org/refdesk/Resources/Algorithms/EulersMethod/ ) +(See also http://www.shodor.org/refdesk/Resources/Algorithms/EulersMethod/ ) """ @@ -258,7 +258,7 @@ def example_1() -> BodySystem: Example 1: figure-8 solution to the 3-body-problem This example can be seen as a test of the implementation: given the right initial conditions, the bodies should move in a figure-8. - (initial conditions taken from https://www.artcompsci.org/vol_1/v1_web/node56.html) + (initial conditions taken from http://www.artcompsci.org/vol_1/v1_web/node56.html) >>> body_system = example_1() >>> len(body_system) 3 From 6d20e2b750839d978873f6a89ce6d844ba3cc0b8 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 16 Oct 2022 20:50:48 +0100 Subject: [PATCH 1909/2908] Add `flake8-broken-line` to `pre-commit` (#7338) * ci: Add ``flake8-broken-line`` plugin to ``pre-commit`` * refactor: Fix errors from ``flake8-broken-line`` --- .pre-commit-config.yaml | 1 + project_euler/problem_008/sol1.py | 42 ++++++++++++++++--------------- project_euler/problem_008/sol3.py | 42 ++++++++++++++++--------------- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3455135653cf..39af0f3b4370 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,6 +39,7 @@ repos: additional_dependencies: - flake8-bugbear - flake8-builtins + - flake8-broken-line - flake8-comprehensions - pep8-naming diff --git a/project_euler/problem_008/sol1.py b/project_euler/problem_008/sol1.py index 796080127778..69dd1b4736c1 100644 --- a/project_euler/problem_008/sol1.py +++ b/project_euler/problem_008/sol1.py @@ -33,26 +33,28 @@ import sys -N = """73167176531330624919225119674426574742355349194934\ -96983520312774506326239578318016984801869478851843\ -85861560789112949495459501737958331952853208805511\ -12540698747158523863050715693290963295227443043557\ -66896648950445244523161731856403098711121722383113\ -62229893423380308135336276614282806444486645238749\ -30358907296290491560440772390713810515859307960866\ -70172427121883998797908792274921901699720888093776\ -65727333001053367881220235421809751254540594752243\ -52584907711670556013604839586446706324415722155397\ -53697817977846174064955149290862569321978468622482\ -83972241375657056057490261407972968652414535100474\ -82166370484403199890008895243450658541227588666881\ -16427171479924442928230863465674813919123162824586\ -17866458359124566529476545682848912883142607690042\ -24219022671055626321111109370544217506941658960408\ -07198403850962455444362981230987879927244284909188\ -84580156166097919133875499200524063689912560717606\ -05886116467109405077541002256983155200055935729725\ -71636269561882670428252483600823257530420752963450""" +N = ( + "73167176531330624919225119674426574742355349194934" + "96983520312774506326239578318016984801869478851843" + "85861560789112949495459501737958331952853208805511" + "12540698747158523863050715693290963295227443043557" + "66896648950445244523161731856403098711121722383113" + "62229893423380308135336276614282806444486645238749" + "30358907296290491560440772390713810515859307960866" + "70172427121883998797908792274921901699720888093776" + "65727333001053367881220235421809751254540594752243" + "52584907711670556013604839586446706324415722155397" + "53697817977846174064955149290862569321978468622482" + "83972241375657056057490261407972968652414535100474" + "82166370484403199890008895243450658541227588666881" + "16427171479924442928230863465674813919123162824586" + "17866458359124566529476545682848912883142607690042" + "24219022671055626321111109370544217506941658960408" + "07198403850962455444362981230987879927244284909188" + "84580156166097919133875499200524063689912560717606" + "05886116467109405077541002256983155200055935729725" + "71636269561882670428252483600823257530420752963450" +) def solution(n: str = N) -> int: diff --git a/project_euler/problem_008/sol3.py b/project_euler/problem_008/sol3.py index 4b99d0ea6e76..c6081aa05e2c 100644 --- a/project_euler/problem_008/sol3.py +++ b/project_euler/problem_008/sol3.py @@ -32,26 +32,28 @@ """ import sys -N = """73167176531330624919225119674426574742355349194934\ -96983520312774506326239578318016984801869478851843\ -85861560789112949495459501737958331952853208805511\ -12540698747158523863050715693290963295227443043557\ -66896648950445244523161731856403098711121722383113\ -62229893423380308135336276614282806444486645238749\ -30358907296290491560440772390713810515859307960866\ -70172427121883998797908792274921901699720888093776\ -65727333001053367881220235421809751254540594752243\ -52584907711670556013604839586446706324415722155397\ -53697817977846174064955149290862569321978468622482\ -83972241375657056057490261407972968652414535100474\ -82166370484403199890008895243450658541227588666881\ -16427171479924442928230863465674813919123162824586\ -17866458359124566529476545682848912883142607690042\ -24219022671055626321111109370544217506941658960408\ -07198403850962455444362981230987879927244284909188\ -84580156166097919133875499200524063689912560717606\ -05886116467109405077541002256983155200055935729725\ -71636269561882670428252483600823257530420752963450""" +N = ( + "73167176531330624919225119674426574742355349194934" + "96983520312774506326239578318016984801869478851843" + "85861560789112949495459501737958331952853208805511" + "12540698747158523863050715693290963295227443043557" + "66896648950445244523161731856403098711121722383113" + "62229893423380308135336276614282806444486645238749" + "30358907296290491560440772390713810515859307960866" + "70172427121883998797908792274921901699720888093776" + "65727333001053367881220235421809751254540594752243" + "52584907711670556013604839586446706324415722155397" + "53697817977846174064955149290862569321978468622482" + "83972241375657056057490261407972968652414535100474" + "82166370484403199890008895243450658541227588666881" + "16427171479924442928230863465674813919123162824586" + "17866458359124566529476545682848912883142607690042" + "24219022671055626321111109370544217506941658960408" + "07198403850962455444362981230987879927244284909188" + "84580156166097919133875499200524063689912560717606" + "05886116467109405077541002256983155200055935729725" + "71636269561882670428252483600823257530420752963450" +) def str_eval(s: str) -> int: From 7f6e0b656f6362e452b11d06acde50b8b81cb31a Mon Sep 17 00:00:00 2001 From: SudhanshuSuman <51868273+SudhanshuSuman@users.noreply.github.com> Date: Mon, 17 Oct 2022 02:11:28 +0530 Subject: [PATCH 1910/2908] Corrected the directory of Fractional Knapsack algorithm (#7086) * Moved fractional knapsack from 'dynamic_programming' to 'greedy_methods' * Updated DIRECTORY.md --- DIRECTORY.md | 4 +- .../fractional_knapsack.py | 0 .../fractional_knapsack_2.py | 106 +++++++++--------- 3 files changed, 55 insertions(+), 55 deletions(-) rename {dynamic_programming => greedy_methods}/fractional_knapsack.py (100%) rename {dynamic_programming => greedy_methods}/fractional_knapsack_2.py (96%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 92bed9cb4c6e..fae9a5183f04 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -279,8 +279,6 @@ * [Fast Fibonacci](dynamic_programming/fast_fibonacci.py) * [Fibonacci](dynamic_programming/fibonacci.py) * [Floyd Warshall](dynamic_programming/floyd_warshall.py) - * [Fractional Knapsack](dynamic_programming/fractional_knapsack.py) - * [Fractional Knapsack 2](dynamic_programming/fractional_knapsack_2.py) * [Integer Partition](dynamic_programming/integer_partition.py) * [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py) * [Knapsack](dynamic_programming/knapsack.py) @@ -396,6 +394,8 @@ * [Test Min Spanning Tree Prim](graphs/tests/test_min_spanning_tree_prim.py) ## Greedy Methods + * [Fractional Knapsack](greedy_methods/fractional_knapsack.py) + * [Fractional Knapsack 2](greedy_methods/fractional_knapsack_2.py) * [Optimal Merge Pattern](greedy_methods/optimal_merge_pattern.py) ## Hashes diff --git a/dynamic_programming/fractional_knapsack.py b/greedy_methods/fractional_knapsack.py similarity index 100% rename from dynamic_programming/fractional_knapsack.py rename to greedy_methods/fractional_knapsack.py diff --git a/dynamic_programming/fractional_knapsack_2.py b/greedy_methods/fractional_knapsack_2.py similarity index 96% rename from dynamic_programming/fractional_knapsack_2.py rename to greedy_methods/fractional_knapsack_2.py index bd776723c146..6d9ed2ec3b6b 100644 --- a/dynamic_programming/fractional_knapsack_2.py +++ b/greedy_methods/fractional_knapsack_2.py @@ -1,53 +1,53 @@ -# 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 __future__ import annotations - - -def fractional_knapsack( - value: list[int], weight: list[int], capacity: int -) -> tuple[float, list[float]]: - """ - >>> 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: float = 0 - fractions: list[float] = [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__": - import doctest - - doctest.testmod() +# 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 __future__ import annotations + + +def fractional_knapsack( + value: list[int], weight: list[int], capacity: int +) -> tuple[float, list[float]]: + """ + >>> 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: float = 0 + fractions: list[float] = [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__": + import doctest + + doctest.testmod() From f15cc2f01c2a4124ff6dc0843c728a546f9d9f79 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 16 Oct 2022 21:50:11 +0100 Subject: [PATCH 1911/2908] Follow Flake8 pep3101 and remove modulo formatting (#7339) * ci: Add ``flake8-pep3101`` plugin to ``pre-commit`` * refactor: Remove all modulo string formatting * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: Remove ``flake8-pep3101`` plugin from ``pre-commit`` * revert: Revert to modulo formatting * refactor: Use f-string instead of `join` Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ciphers/elgamal_key_generator.py | 9 +++------ ciphers/rsa_key_generator.py | 3 +-- dynamic_programming/edit_distance.py | 4 ++-- genetic_algorithm/basic_string.py | 4 ++-- graphs/minimum_spanning_tree_boruvka.py | 2 +- machine_learning/linear_regression.py | 2 +- matrix/sherman_morrison.py | 6 +++--- neural_network/back_propagation_neural_network.py | 2 +- neural_network/convolution_neural_network.py | 2 +- 9 files changed, 15 insertions(+), 19 deletions(-) diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 4d72128aed52..17ba55c0d013 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -41,22 +41,19 @@ def make_key_files(name: str, key_size: int) -> None: if os.path.exists(f"{name}_pubkey.txt") or os.path.exists(f"{name}_privkey.txt"): print("\nWARNING:") print( - '"%s_pubkey.txt" or "%s_privkey.txt" already exists. \n' + f'"{name}_pubkey.txt" or "{name}_privkey.txt" already exists. \n' "Use a different name or delete these files and re-run this program." - % (name, name) ) sys.exit() public_key, private_key = generate_key(key_size) print(f"\nWriting public key to file {name}_pubkey.txt...") with open(f"{name}_pubkey.txt", "w") as fo: - fo.write( - "%d,%d,%d,%d" % (public_key[0], public_key[1], public_key[2], public_key[3]) - ) + fo.write(f"{public_key[0]},{public_key[1]},{public_key[2]},{public_key[3]}") print(f"Writing private key to file {name}_privkey.txt...") with open(f"{name}_privkey.txt", "w") as fo: - fo.write("%d,%d" % (private_key[0], private_key[1])) + fo.write(f"{private_key[0]},{private_key[1]}") def main() -> None: diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index f64bc7dd0557..2573ed01387b 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -37,9 +37,8 @@ def make_key_files(name: str, key_size: int) -> None: if os.path.exists(f"{name}_pubkey.txt") or os.path.exists(f"{name}_privkey.txt"): print("\nWARNING:") print( - '"%s_pubkey.txt" or "%s_privkey.txt" already exists. \n' + f'"{name}_pubkey.txt" or "{name}_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/dynamic_programming/edit_distance.py b/dynamic_programming/edit_distance.py index d63e559e30da..fe23431a7ea6 100644 --- a/dynamic_programming/edit_distance.py +++ b/dynamic_programming/edit_distance.py @@ -99,7 +99,7 @@ def min_distance_bottom_up(word1: str, word2: str) -> int: 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(f"The minimum Edit Distance is: {solver.solve(S1, S2)}") + print(f"The minimum Edit Distance is: {min_distance_bottom_up(S1, S2)}") print() print("*************** End of Testing Edit Distance DP Algorithm ***************") diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index bd7d8026866c..d2d305189983 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -172,7 +172,7 @@ def mutate(child: str) -> str: " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm" "nopqrstuvwxyz.,;!?+-*#@^'èéòà€ù=)(&%$£/\\" ) + generation, population, target = basic(target_str, genes_list) print( - "\nGeneration: %s\nTotal Population: %s\nTarget: %s" - % basic(target_str, genes_list) + f"\nGeneration: {generation}\nTotal Population: {population}\nTarget: {target}" ) diff --git a/graphs/minimum_spanning_tree_boruvka.py b/graphs/minimum_spanning_tree_boruvka.py index 32548b2ecb6c..6c72615cc729 100644 --- a/graphs/minimum_spanning_tree_boruvka.py +++ b/graphs/minimum_spanning_tree_boruvka.py @@ -63,7 +63,7 @@ def __str__(self): for tail in self.adjacency: for head in self.adjacency[tail]: weight = self.adjacency[head][tail] - string += "%d -> %d == %d\n" % (head, tail, weight) + string += f"{head} -> {tail} == {weight}\n" return string.rstrip("\n") def get_edges(self): diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 85fdfb0005ac..92ab91c01b95 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -82,7 +82,7 @@ def run_linear_regression(data_x, data_y): for i in range(0, iterations): 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(f"At Iteration {i + 1} - Error is {error:.5f}") return theta diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 29c9b3381b55..39eddfed81f3 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -31,14 +31,14 @@ def __str__(self) -> str: """ # Prefix - s = "Matrix consist of %d rows and %d columns\n" % (self.row, self.column) + s = f"Matrix consist of {self.row} rows and {self.column} columns\n" # 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,) + string_format_identifier = f"%{max_element_length}s" # Make string and return def single_line(row_vector: list[float]) -> str: @@ -252,7 +252,7 @@ def test1() -> None: v[0, 0], v[1, 0], v[2, 0] = 4, -2, 5 print(f"u is {u}") print(f"v is {v}") - print("uv^T is %s" % (u * v.transpose())) + print(f"uv^T is {u * v.transpose()}") # Sherman Morrison print(f"(a + uv^T)^(-1) is {ainv.sherman_morrison(u, v)}") diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index 23b818b0f3cf..cb47b829010c 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -117,7 +117,7 @@ def build(self): def summary(self): for i, layer in enumerate(self.layers[:]): - print("------- layer %d -------" % i) + print(f"------- layer {i} -------") print("weight.shape ", np.shape(layer.weight)) print("bias.shape ", np.shape(layer.bias)) diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index 9dfb6d091412..bd0550212157 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -219,7 +219,7 @@ def train( mse = 10000 while rp < n_repeat and mse >= error_accuracy: error_count = 0 - print("-------------Learning Time %d--------------" % rp) + print(f"-------------Learning Time {rp}--------------") for p in range(len(datas_train)): # print('------------Learning Image: %d--------------'%p) data_train = np.asmatrix(datas_train[p]) From a34b756fd40e5cdfb69abc06dcd42f5f1b5fa21e Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 16 Oct 2022 21:51:40 +0100 Subject: [PATCH 1912/2908] ci: Add ``yesqa`` (flake8-plugin) to ``pre-commit`` (#7340) --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39af0f3b4370..aea82d12cd13 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,6 +42,7 @@ repos: - flake8-broken-line - flake8-comprehensions - pep8-naming + - yesqa - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.982 From 0c7c5fa7b0161a7433467240155356c93ae106b8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 21:59:25 +0200 Subject: [PATCH 1913/2908] [pre-commit.ci] pre-commit autoupdate (#7387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/pyupgrade: v3.0.0 → v3.1.0](https://github.com/asottile/pyupgrade/compare/v3.0.0...v3.1.0) - [github.com/codespell-project/codespell: v2.2.1 → v2.2.2](https://github.com/codespell-project/codespell/compare/v2.2.1...v2.2.2) * updating DIRECTORY.md * Fix typo discovered by codespell * Fix typo discovered by codespell * Update .pre-commit-config.yaml * Update .pre-commit-config.yaml * Update .pre-commit-config.yaml Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 2 ++ .../local_weighted_learning/local_weighted_learning.md | 2 +- maths/is_square_free.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aea82d12cd13..5bdda50be0c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v3.0.0 + rev: v3.1.0 hooks: - id: pyupgrade args: @@ -55,14 +55,14 @@ repos: additional_dependencies: [types-requests] - repo: https://github.com/codespell-project/codespell - rev: v2.2.1 + rev: v2.2.2 hooks: - id: codespell args: - --ignore-words-list=ans,crate,damon,fo,followings,hist,iff,mater,secant,som,sur,tim,zar - - --skip="./.*,./strings/dictionary.txt,./strings/words.txt,./project_euler/problem_022/p022_names.txt" exclude: | (?x)^( + ciphers/prehistoric_men.txt | strings/dictionary.txt | strings/words.txt | project_euler/problem_022/p022_names.txt diff --git a/DIRECTORY.md b/DIRECTORY.md index fae9a5183f04..94ec42832e41 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -642,6 +642,7 @@ * [Tower Of Hanoi](other/tower_of_hanoi.py) ## Physics + * [Casimir Effect](physics/casimir_effect.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) * [N Body Simulation](physics/n_body_simulation.py) @@ -928,6 +929,7 @@ * [Deutsch Jozsa](quantum/deutsch_jozsa.py) * [Half Adder](quantum/half_adder.py) * [Not Gate](quantum/not_gate.py) + * [Q Full Adder](quantum/q_full_adder.py) * [Quantum Entanglement](quantum/quantum_entanglement.py) * [Ripple Adder Classic](quantum/ripple_adder_classic.py) * [Single Qubit Measure](quantum/single_qubit_measure.py) diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.md b/machine_learning/local_weighted_learning/local_weighted_learning.md index 5c7895e75104..ef4dbc958600 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.md +++ b/machine_learning/local_weighted_learning/local_weighted_learning.md @@ -29,7 +29,7 @@ This training phase is possible when data points are linear, but there again com So, here comes the role of non-parametric algorithm which doesn't compute predictions based on fixed set of params. Rather parameters $\theta$ are computed individually for each query point/data point x.

    -While Computing $\theta$ , a higher "preferance" is given to points in the vicinity of x than points farther from x. +While Computing $\theta$ , a higher preference is given to points in the vicinity of x than points farther from x. Cost Function J($\theta$) = $\sum_{i=1}^m$ $w^i$ (($\theta$)$^T$ $x^i$ - $y^i$)$^2$ diff --git a/maths/is_square_free.py b/maths/is_square_free.py index 8d83d95ffb67..4134398d258b 100644 --- a/maths/is_square_free.py +++ b/maths/is_square_free.py @@ -15,7 +15,7 @@ def is_square_free(factors: list[int]) -> bool: False These are wrong but should return some value - it simply checks for repition in the numbers. + it simply checks for repetition in the numbers. >>> is_square_free([1, 3, 4, 'sd', 0.0]) True From 3448ae5cec868d4a03349cb952765e9abff41243 Mon Sep 17 00:00:00 2001 From: Shubham Kondekar <40213815+kondekarshubham123@users.noreply.github.com> Date: Tue, 18 Oct 2022 02:00:01 +0530 Subject: [PATCH 1914/2908] [Binary Tree] Different views of binary tree added (#6965) * Different views of binary tree added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * mypy errors resolved * doc test for remaining functions * Flake8 comments resolved * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Example moved in if block * doctest cases added * Cases from if block removed * Update data_structures/binary_tree/diff_views_of_binary_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/diff_views_of_binary_tree.py Co-authored-by: Christian Clauss * PR Comments resolved * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * flake8 warning resolved * Changes revered * flake8 issue resolved * Put the diagrams just above the doctests * Update diff_views_of_binary_tree.py * Update diff_views_of_binary_tree.py * I love mypy * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../binary_tree/diff_views_of_binary_tree.py | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 data_structures/binary_tree/diff_views_of_binary_tree.py diff --git a/data_structures/binary_tree/diff_views_of_binary_tree.py b/data_structures/binary_tree/diff_views_of_binary_tree.py new file mode 100644 index 000000000000..3198d8065918 --- /dev/null +++ b/data_structures/binary_tree/diff_views_of_binary_tree.py @@ -0,0 +1,210 @@ +r""" +Problem: Given root of a binary tree, return the: +1. binary-tree-right-side-view +2. binary-tree-left-side-view +3. binary-tree-top-side-view +4. binary-tree-bottom-side-view +""" + +from __future__ import annotations + +from collections import defaultdict +from dataclasses import dataclass + + +@dataclass +class TreeNode: + val: int + left: TreeNode | None = None + right: TreeNode | None = None + + +def make_tree() -> TreeNode: + """ + >>> make_tree().val + 3 + """ + return TreeNode(3, TreeNode(9), TreeNode(20, TreeNode(15), TreeNode(7))) + + +def binary_tree_right_side_view(root: TreeNode) -> list[int]: + r""" + Function returns the right side view of binary tree. + + 3 <- 3 + / \ + 9 20 <- 20 + / \ + 15 7 <- 7 + + >>> binary_tree_right_side_view(make_tree()) + [3, 20, 7] + >>> binary_tree_right_side_view(None) + [] + """ + + def depth_first_search( + root: TreeNode | None, depth: int, right_view: list[int] + ) -> None: + """ + A depth first search preorder traversal to append the values at + right side of tree. + """ + if not root: + return + + if depth == len(right_view): + right_view.append(root.val) + + depth_first_search(root.right, depth + 1, right_view) + depth_first_search(root.left, depth + 1, right_view) + + right_view: list = [] + if not root: + return right_view + + depth_first_search(root, 0, right_view) + return right_view + + +def binary_tree_left_side_view(root: TreeNode) -> list[int]: + r""" + Function returns the left side view of binary tree. + + 3 -> 3 + / \ + 9 -> 9 20 + / \ + 15 -> 15 7 + + >>> binary_tree_left_side_view(make_tree()) + [3, 9, 15] + >>> binary_tree_left_side_view(None) + [] + """ + + def depth_first_search( + root: TreeNode | None, depth: int, left_view: list[int] + ) -> None: + """ + A depth first search preorder traversal to append the values + at left side of tree. + """ + if not root: + return + + if depth == len(left_view): + left_view.append(root.val) + + depth_first_search(root.left, depth + 1, left_view) + depth_first_search(root.right, depth + 1, left_view) + + left_view: list = [] + if not root: + return left_view + + depth_first_search(root, 0, left_view) + return left_view + + +def binary_tree_top_side_view(root: TreeNode) -> list[int]: + r""" + Function returns the top side view of binary tree. + + 9 3 20 7 + ⬇ ⬇ ⬇ ⬇ + + 3 + / \ + 9 20 + / \ + 15 7 + + >>> binary_tree_top_side_view(make_tree()) + [9, 3, 20, 7] + >>> binary_tree_top_side_view(None) + [] + """ + + def breadth_first_search(root: TreeNode, top_view: list[int]) -> None: + """ + A breadth first search traversal with defaultdict ds to append + the values of tree from top view + """ + queue = [(root, 0)] + lookup = defaultdict(list) + + while queue: + first = queue.pop(0) + node, hd = first + + lookup[hd].append(node.val) + + if node.left: + queue.append((node.left, hd - 1)) + if node.right: + queue.append((node.right, hd + 1)) + + for pair in sorted(lookup.items(), key=lambda each: each[0]): + top_view.append(pair[1][0]) + + top_view: list = [] + if not root: + return top_view + + breadth_first_search(root, top_view) + return top_view + + +def binary_tree_bottom_side_view(root: TreeNode) -> list[int]: + r""" + Function returns the bottom side view of binary tree + + 3 + / \ + 9 20 + / \ + 15 7 + ↑ ↑ ↑ ↑ + 9 15 20 7 + + >>> binary_tree_bottom_side_view(make_tree()) + [9, 15, 20, 7] + >>> binary_tree_bottom_side_view(None) + [] + """ + from collections import defaultdict + + def breadth_first_search(root: TreeNode, bottom_view: list[int]) -> None: + """ + A breadth first search traversal with defaultdict ds to append + the values of tree from bottom view + """ + queue = [(root, 0)] + lookup = defaultdict(list) + + while queue: + first = queue.pop(0) + node, hd = first + lookup[hd].append(node.val) + + if node.left: + queue.append((node.left, hd - 1)) + if node.right: + queue.append((node.right, hd + 1)) + + for pair in sorted(lookup.items(), key=lambda each: each[0]): + bottom_view.append(pair[1][-1]) + + bottom_view: list = [] + if not root: + return bottom_view + + breadth_first_search(root, bottom_view) + return bottom_view + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 49cd46acea37350c8c22488316f8cf3f5ea88925 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Tue, 18 Oct 2022 02:09:41 -0400 Subject: [PATCH 1915/2908] Update convolve function namespace (#7390) --- computer_vision/horn_schunck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computer_vision/horn_schunck.py b/computer_vision/horn_schunck.py index 2a153d06ddae..b63e0268294c 100644 --- a/computer_vision/horn_schunck.py +++ b/computer_vision/horn_schunck.py @@ -12,7 +12,7 @@ from typing import SupportsIndex import numpy as np -from scipy.ndimage.filters import convolve +from scipy.ndimage import convolve def warp( From 6d1e009f35dd172ef51d484d0310919cdbab189d Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Tue, 18 Oct 2022 05:57:03 -0400 Subject: [PATCH 1916/2908] Remove depreciated np.float (#7394) --- machine_learning/decision_tree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index 4a86e5322a27..7cd1b02c4181 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -24,13 +24,13 @@ def mean_squared_error(self, labels, prediction): estimate the labels >>> tester = DecisionTree() >>> test_labels = np.array([1,2,3,4,5,6,7,8,9,10]) - >>> test_prediction = np.float(6) + >>> test_prediction = float(6) >>> tester.mean_squared_error(test_labels, test_prediction) == ( ... TestDecisionTree.helper_mean_squared_error_test(test_labels, ... test_prediction)) True >>> test_labels = np.array([1,2,3]) - >>> test_prediction = np.float(2) + >>> test_prediction = float(2) >>> tester.mean_squared_error(test_labels, test_prediction) == ( ... TestDecisionTree.helper_mean_squared_error_test(test_labels, ... test_prediction)) @@ -145,11 +145,11 @@ def helper_mean_squared_error_test(labels, prediction): @param prediction: a floating point value return value: helper_mean_squared_error_test calculates the mean squared error """ - squared_error_sum = np.float(0) + squared_error_sum = float(0) for label in labels: squared_error_sum += (label - prediction) ** 2 - return np.float(squared_error_sum / labels.size) + return float(squared_error_sum / labels.size) def main(): From 2ca695b0fe28519d3449106bff9f9004d93a0b3f Mon Sep 17 00:00:00 2001 From: Shubham Kondekar <40213815+kondekarshubham123@users.noreply.github.com> Date: Tue, 18 Oct 2022 23:35:18 +0530 Subject: [PATCH 1917/2908] [Matrix] Max area of island problem solved DFS algorithm (#6918) * Maximum area of island program added * Update matrix/max_area_of_island.py Co-authored-by: Caeden * Update matrix/max_area_of_island.py Co-authored-by: Caeden * Update matrix/max_area_of_island.py Co-authored-by: Caeden * Review's comment resolved * max area of island * PR Comments resolved * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Test case fail fix * Grammer correction * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * flake8 issue resolved * some variable name fix * Update matrix/max_area_of_island.py Co-authored-by: Caeden Perelli-Harris * Update matrix/max_area_of_island.py Co-authored-by: Caeden Perelli-Harris * PR, comments resolved * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update matrix/max_area_of_island.py Co-authored-by: Christian Clauss * Update matrix/max_area_of_island.py Co-authored-by: Christian Clauss * PR, comments resolved * Update max_area_of_island.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Typo Co-authored-by: Caeden Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- matrix/max_area_of_island.py | 112 +++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 matrix/max_area_of_island.py diff --git a/matrix/max_area_of_island.py b/matrix/max_area_of_island.py new file mode 100644 index 000000000000..40950c303795 --- /dev/null +++ b/matrix/max_area_of_island.py @@ -0,0 +1,112 @@ +""" +Given an two dimensional binary matrix grid. An island is a group of 1's (representing +land) connected 4-directionally (horizontal or vertical.) You may assume all four edges +of the grid are surrounded by water. The area of an island is the number of cells with +a value 1 in the island. Return the maximum area of an island in a grid. If there is no +island, return 0. +""" + +matrix = [ + [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0], + [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0], +] + + +def is_safe(row: int, col: int, rows: int, cols: int) -> bool: + """ + Checking whether coordinate (row, col) is valid or not. + + >>> is_safe(0, 0, 5, 5) + True + >>> is_safe(-1,-1, 5, 5) + False + """ + return 0 <= row < rows and 0 <= col < cols + + +def depth_first_search(row: int, col: int, seen: set, mat: list[list[int]]) -> int: + """ + Returns the current area of the island + + >>> depth_first_search(0, 0, set(), matrix) + 0 + """ + rows = len(mat) + cols = len(mat[0]) + if is_safe(row, col, rows, cols) and (row, col) not in seen and mat[row][col] == 1: + seen.add((row, col)) + return ( + 1 + + depth_first_search(row + 1, col, seen, mat) + + depth_first_search(row - 1, col, seen, mat) + + depth_first_search(row, col + 1, seen, mat) + + depth_first_search(row, col - 1, seen, mat) + ) + else: + return 0 + + +def find_max_area(mat: list[list[int]]) -> int: + """ + Finds the area of all islands and returns the maximum area. + + >>> find_max_area(matrix) + 6 + """ + seen: set = set() + + max_area = 0 + for row, line in enumerate(mat): + for col, item in enumerate(line): + if item == 1 and (row, col) not in seen: + # Maximizing the area + max_area = max(max_area, depth_first_search(row, col, seen, mat)) + return max_area + + +if __name__ == "__main__": + import doctest + + print(find_max_area(matrix)) # Output -> 6 + + """ + Explanation: + We are allowed to move in four directions (horizontal or vertical) so the possible + in a matrix if we are at x and y position the possible moving are + + Directions are [(x, y+1), (x, y-1), (x+1, y), (x-1, y)] but we need to take care of + boundary cases as well which are x and y can not be smaller than 0 and greater than + the number of rows and columns respectively. + + Visualization + mat = [ + [0,0,A,0,0,0,0,B,0,0,0,0,0], + [0,0,0,0,0,0,0,B,B,B,0,0,0], + [0,C,C,0,D,0,0,0,0,0,0,0,0], + [0,C,0,0,D,D,0,0,E,0,E,0,0], + [0,C,0,0,D,D,0,0,E,E,E,0,0], + [0,0,0,0,0,0,0,0,0,0,E,0,0], + [0,0,0,0,0,0,0,F,F,F,0,0,0], + [0,0,0,0,0,0,0,F,F,0,0,0,0] + ] + + For visualization, I have defined the connected island with letters + by observation, we can see that + A island is of area 1 + B island is of area 4 + C island is of area 4 + D island is of area 5 + E island is of area 6 and + F island is of area 5 + + it has 6 unique islands of mentioned areas + and the maximum of all of them is 6 so we return 6. + """ + + doctest.testmod() From 5bfcab1aa4392e4e3f43927a7fbd8bf6c6815c88 Mon Sep 17 00:00:00 2001 From: Manish Kumar <73126278+ManishKumar219@users.noreply.github.com> Date: Wed, 19 Oct 2022 00:52:38 +0530 Subject: [PATCH 1918/2908] Create minmax.py (#7409) * Create minmax.py * Update minmax.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- backtracking/minmax.py | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 backtracking/minmax.py diff --git a/backtracking/minmax.py b/backtracking/minmax.py new file mode 100644 index 000000000000..9b87183cfdb7 --- /dev/null +++ b/backtracking/minmax.py @@ -0,0 +1,69 @@ +""" +Minimax helps to achieve maximum score in a game by checking all possible moves. + +""" +from __future__ import annotations + +import math + + +def minimax( + depth: int, node_index: int, is_max: bool, scores: list[int], height: float +) -> int: + """ + depth is current depth in game tree. + node_index is index of current node in scores[]. + scores[] contains the leaves of game tree. + height is maximum height of game tree. + + >>> 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 + """ + + if depth < 0: + raise ValueError("Depth cannot be less than 0") + + if not scores: + raise ValueError("Scores cannot be empty") + + if depth == height: + return scores[node_index] + + return ( + max( + minimax(depth + 1, node_index * 2, False, scores, height), + minimax(depth + 1, node_index * 2 + 1, False, scores, height), + ) + if is_max + else min( + minimax(depth + 1, node_index * 2, True, scores, height), + minimax(depth + 1, node_index * 2 + 1, True, scores, height), + ) + ) + + +def main() -> None: + scores = [90, 23, 6, 33, 21, 65, 123, 34423] + height = math.log(len(scores), 2) + print(f"Optimal value : {minimax(0, 0, True, scores, height)}") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From b90ec303989b864996e31e021863f8b2c8852054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nadirhan=20=C5=9Eahin?= Date: Tue, 18 Oct 2022 22:55:43 +0300 Subject: [PATCH 1919/2908] Create combination_sum.py (#7403) * Create combination_sum.py * Update DIRECTORY.md * Adds doctests Co-authored-by: Christian Clauss * Update combination_sum.py * Update combination_sum.py Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + backtracking/combination_sum.py | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 backtracking/combination_sum.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 94ec42832e41..c1fad8d9d794 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -23,6 +23,7 @@ * [All Permutations](backtracking/all_permutations.py) * [All Subsequences](backtracking/all_subsequences.py) * [Coloring](backtracking/coloring.py) + * [Combination Sum](backtracking/combination_sum.py) * [Hamiltonian Cycle](backtracking/hamiltonian_cycle.py) * [Knight Tour](backtracking/knight_tour.py) * [Minimax](backtracking/minimax.py) diff --git a/backtracking/combination_sum.py b/backtracking/combination_sum.py new file mode 100644 index 000000000000..f555adb751d0 --- /dev/null +++ b/backtracking/combination_sum.py @@ -0,0 +1,66 @@ +""" +In the Combination Sum problem, we are given a list consisting of distinct integers. +We need to find all the combinations whose sum equals to target given. +We can use an element more than one. + +Time complexity(Average Case): O(n!) + +Constraints: +1 <= candidates.length <= 30 +2 <= candidates[i] <= 40 +All elements of candidates are distinct. +1 <= target <= 40 +""" + + +def backtrack( + candidates: list, path: list, answer: list, target: int, previous_index: int +) -> None: + """ + A recursive function that searches for possible combinations. Backtracks in case + of a bigger current combination value than the target value. + + Parameters + ---------- + previous_index: Last index from the previous search + target: The value we need to obtain by summing our integers in the path list. + answer: A list of possible combinations + path: Current combination + candidates: A list of integers we can use. + """ + if target == 0: + answer.append(path.copy()) + else: + for index in range(previous_index, len(candidates)): + if target >= candidates[index]: + path.append(candidates[index]) + backtrack(candidates, path, answer, target - candidates[index], index) + path.pop(len(path) - 1) + + +def combination_sum(candidates: list, target: int) -> list: + """ + >>> combination_sum([2, 3, 5], 8) + [[2, 2, 2, 2], [2, 3, 3], [3, 5]] + >>> combination_sum([2, 3, 6, 7], 7) + [[2, 2, 3], [7]] + >>> combination_sum([-8, 2.3, 0], 1) + Traceback (most recent call last): + ... + RecursionError: maximum recursion depth exceeded in comparison + """ + path = [] # type: list[int] + answer = [] # type: list[int] + backtrack(candidates, path, answer, target, 0) + return answer + + +def main() -> None: + print(combination_sum([-8, 2.3, 0], 1)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From 80ff25ed38e62bcf2e51a4a51bf7bf8f9b03ea11 Mon Sep 17 00:00:00 2001 From: Sai Ganesh Manda <89340753+mvsg2@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:13:26 +0530 Subject: [PATCH 1920/2908] Update gaussian_naive_bayes.py (#7406) * Update gaussian_naive_bayes.py Just adding in a final metric of accuracy to declare... * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- machine_learning/gaussian_naive_bayes.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/machine_learning/gaussian_naive_bayes.py b/machine_learning/gaussian_naive_bayes.py index 77e7326626c4..7e9a8d7f6dcf 100644 --- a/machine_learning/gaussian_naive_bayes.py +++ b/machine_learning/gaussian_naive_bayes.py @@ -1,7 +1,9 @@ # Gaussian Naive Bayes Example +import time + from matplotlib import pyplot as plt from sklearn.datasets import load_iris -from sklearn.metrics import plot_confusion_matrix +from sklearn.metrics import accuracy_score, plot_confusion_matrix from sklearn.model_selection import train_test_split from sklearn.naive_bayes import GaussianNB @@ -25,7 +27,9 @@ def main(): # Gaussian Naive Bayes nb_model = GaussianNB() - nb_model.fit(x_train, y_train) + time.sleep(2.9) + model_fit = nb_model.fit(x_train, y_train) + y_pred = model_fit.predict(x_test) # Predictions on the test set # Display Confusion Matrix plot_confusion_matrix( @@ -33,12 +37,16 @@ def main(): x_test, y_test, display_labels=iris["target_names"], - cmap="Blues", + cmap="Blues", # although, Greys_r has a better contrast... normalize="true", ) plt.title("Normalized Confusion Matrix - IRIS Dataset") plt.show() + time.sleep(1.8) + final_accuracy = 100 * accuracy_score(y_true=y_test, y_pred=y_pred) + print(f"The overall accuracy of the model is: {round(final_accuracy, 2)}%") + if __name__ == "__main__": main() From b8281d79ef6fdfa11bdd697be3f4a1ef7824cf7f Mon Sep 17 00:00:00 2001 From: Kuldeep Borkar <74557588+KuldeepBorkar@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:16:56 +0530 Subject: [PATCH 1921/2908] Fixed a typo of 'a' and 'an' and used f string in print statement (#7398) --- boolean_algebra/norgate.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/boolean_algebra/norgate.py b/boolean_algebra/norgate.py index 1c341e8a707b..2c27b80afdbe 100644 --- a/boolean_algebra/norgate.py +++ b/boolean_algebra/norgate.py @@ -1,13 +1,15 @@ -""" A NOR Gate is a logic gate in boolean algebra which results to false(0) - if any of the input is 1, and True(1) if both the inputs are 0. - Following is the truth table of an NOR Gate: +""" +A NOR Gate is a logic gate in boolean algebra which results to false(0) +if any of the input is 1, and True(1) if both the inputs are 0. +Following is the truth table of a NOR Gate: | Input 1 | Input 2 | Output | | 0 | 0 | 1 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 0 | + +Following is the code implementation of the NOR Gate """ -"""Following is the code implementation of the NOR Gate""" def nor_gate(input_1: int, input_2: int) -> int: @@ -30,11 +32,11 @@ def nor_gate(input_1: int, input_2: int) -> int: def main() -> None: print("Truth Table of NOR Gate:") - print("| Input 1 |", " Input 2 |", " Output |") - print("| 0 |", " 0 | ", nor_gate(0, 0), " |") - print("| 0 |", " 1 | ", nor_gate(0, 1), " |") - print("| 1 |", " 0 | ", nor_gate(1, 0), " |") - print("| 1 |", " 1 | ", nor_gate(1, 1), " |") + print("| Input 1 | Input 2 | Output |") + print(f"| 0 | 0 | {nor_gate(0, 0)} |") + print(f"| 0 | 1 | {nor_gate(0, 1)} |") + print(f"| 1 | 0 | {nor_gate(1, 0)} |") + print(f"| 1 | 1 | {nor_gate(1, 1)} |") if __name__ == "__main__": From 50da472ddcdc2d79d1ad325ec05cda3558802fda Mon Sep 17 00:00:00 2001 From: Kuldeep Borkar <74557588+KuldeepBorkar@users.noreply.github.com> Date: Wed, 19 Oct 2022 22:48:33 +0530 Subject: [PATCH 1922/2908] Implemented Gelu Function (#7368) * Implemented Gelu Function * Renamed file and added more description to function * Extended the name GELU * Update gaussian_error_linear_unit.py Co-authored-by: Christian Clauss --- maths/gaussian_error_linear_unit.py | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 maths/gaussian_error_linear_unit.py diff --git a/maths/gaussian_error_linear_unit.py b/maths/gaussian_error_linear_unit.py new file mode 100644 index 000000000000..7b5f875143b9 --- /dev/null +++ b/maths/gaussian_error_linear_unit.py @@ -0,0 +1,53 @@ +""" +This script demonstrates an implementation of the Gaussian Error Linear Unit function. +* https://en.wikipedia.org/wiki/Activation_function#Comparison_of_activation_functions + +The function takes a vector of K real numbers as input and returns x * sigmoid(1.702*x). +Gaussian Error Linear Unit (GELU) is a high-performing neural network activation +function. + +This script is inspired by a corresponding research paper. +* https://arxiv.org/abs/1606.08415 +""" + +import numpy as np + + +def sigmoid(vector: np.array) -> np.array: + """ + Mathematical function sigmoid takes a vector x of K real numbers as input and + returns 1/ (1 + e^-x). + https://en.wikipedia.org/wiki/Sigmoid_function + + >>> sigmoid(np.array([-1.0, 1.0, 2.0])) + array([0.26894142, 0.73105858, 0.88079708]) + """ + return 1 / (1 + np.exp(-vector)) + + +def gaussian_error_linear_unit(vector: np.array) -> np.array: + """ + Implements the Gaussian Error Linear Unit (GELU) function + + Parameters: + vector (np.array): A numpy array of shape (1,n) + consisting of real values + + Returns: + gelu_vec (np.array): The input numpy array, after applying + gelu. + + Examples: + >>> gaussian_error_linear_unit(np.array([-1.0, 1.0, 2.0])) + array([-0.15420423, 0.84579577, 1.93565862]) + + >>> gaussian_error_linear_unit(np.array([-3])) + array([-0.01807131]) + """ + return vector * sigmoid(1.702 * vector) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2859d4bf3aa96737a4715c65d4a9051d9c62d24d Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 19 Oct 2022 16:12:44 -0400 Subject: [PATCH 1923/2908] Remove references to depreciated QasmSimulator (#7417) * Fix typos * Replace depreciated QasmSimulator in Deutsch-Jozsa algorithm * Replace depreciated QasmSimulator in half adder algorithm * Replace depreciated QasmSimulator in not gate algorithm * Replace depreciated QasmSimulator in full adder algorithm * Simplify qiskit import * Make formatting more consistent * Replace depreciated QasmSimulator in quantum entanglement algorithm * Replace depreciated QasmSimulator in ripple adder algorithm * Replace depreciated QasmSimulator in qubit measure algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updating DIRECTORY.md * updating DIRECTORY.md * Remove qiskit import alias for clarity Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 ++++ quantum/deutsch_jozsa.py | 28 +++++++++++++++------------- quantum/half_adder.py | 14 +++++++------- quantum/not_gate.py | 14 ++++++++------ quantum/q_full_adder.py | 27 +++++++++++++-------------- quantum/quantum_entanglement.py | 6 +++--- quantum/ripple_adder_classic.py | 16 ++++++++-------- quantum/single_qubit_measure.py | 16 +++++++++------- 8 files changed, 67 insertions(+), 58 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index c1fad8d9d794..1fad287988c4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -27,6 +27,7 @@ * [Hamiltonian Cycle](backtracking/hamiltonian_cycle.py) * [Knight Tour](backtracking/knight_tour.py) * [Minimax](backtracking/minimax.py) + * [Minmax](backtracking/minmax.py) * [N Queens](backtracking/n_queens.py) * [N Queens Math](backtracking/n_queens_math.py) * [Rat In Maze](backtracking/rat_in_maze.py) @@ -157,6 +158,7 @@ * [Binary Tree Mirror](data_structures/binary_tree/binary_tree_mirror.py) * [Binary Tree Node Sum](data_structures/binary_tree/binary_tree_node_sum.py) * [Binary Tree Traversals](data_structures/binary_tree/binary_tree_traversals.py) + * [Diff Views Of Binary Tree](data_structures/binary_tree/diff_views_of_binary_tree.py) * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) * [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py) * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) @@ -513,6 +515,7 @@ * [Gamma](maths/gamma.py) * [Gamma Recursive](maths/gamma_recursive.py) * [Gaussian](maths/gaussian.py) + * [Gaussian Error Linear Unit](maths/gaussian_error_linear_unit.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) * [Greedy Coin Change](maths/greedy_coin_change.py) * [Hamming Numbers](maths/hamming_numbers.py) @@ -601,6 +604,7 @@ * [Inverse Of Matrix](matrix/inverse_of_matrix.py) * [Matrix Class](matrix/matrix_class.py) * [Matrix Operation](matrix/matrix_operation.py) + * [Max Area Of Island](matrix/max_area_of_island.py) * [Nth Fibonacci Using Matrix Exponentiation](matrix/nth_fibonacci_using_matrix_exponentiation.py) * [Rotate Matrix](matrix/rotate_matrix.py) * [Searching In Sorted Matrix](matrix/searching_in_sorted_matrix.py) diff --git a/quantum/deutsch_jozsa.py b/quantum/deutsch_jozsa.py index d7e2d8335fb9..95c3e65b5edf 100755 --- a/quantum/deutsch_jozsa.py +++ b/quantum/deutsch_jozsa.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Deutsch-Josza Algorithm is one of the first examples of a quantum +Deutsch-Jozsa Algorithm is one of the first examples of a quantum algorithm that is exponentially faster than any possible deterministic classical algorithm @@ -22,10 +22,10 @@ """ import numpy as np -import qiskit as q +import qiskit -def dj_oracle(case: str, num_qubits: int) -> q.QuantumCircuit: +def dj_oracle(case: str, num_qubits: int) -> qiskit.QuantumCircuit: """ Returns a Quantum Circuit for the Oracle function. The circuit returned can represent balanced or constant function, @@ -33,7 +33,7 @@ def dj_oracle(case: str, num_qubits: int) -> q.QuantumCircuit: """ # This circuit has num_qubits+1 qubits: the size of the input, # plus one output qubit - oracle_qc = q.QuantumCircuit(num_qubits + 1) + oracle_qc = qiskit.QuantumCircuit(num_qubits + 1) # First, let's deal with the case in which oracle is balanced if case == "balanced": @@ -43,7 +43,7 @@ def dj_oracle(case: str, num_qubits: int) -> q.QuantumCircuit: # 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 + # corresponds 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": @@ -70,13 +70,15 @@ def dj_oracle(case: str, num_qubits: int) -> q.QuantumCircuit: return oracle_gate -def dj_algorithm(oracle: q.QuantumCircuit, num_qubits: int) -> q.QuantumCircuit: +def dj_algorithm( + oracle: qiskit.QuantumCircuit, num_qubits: int +) -> qiskit.QuantumCircuit: """ - Returns the complete Deustch-Jozsa Quantum Circuit, + Returns the complete Deutsch-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) + dj_circuit = qiskit.QuantumCircuit(num_qubits + 1, num_qubits) # Set up the output qubit: dj_circuit.x(num_qubits) dj_circuit.h(num_qubits) @@ -95,7 +97,7 @@ def dj_algorithm(oracle: q.QuantumCircuit, num_qubits: int) -> q.QuantumCircuit: return dj_circuit -def deutsch_jozsa(case: str, num_qubits: int) -> q.result.counts.Counts: +def deutsch_jozsa(case: str, num_qubits: int) -> qiskit.result.counts.Counts: """ Main function that builds the circuit using other helper functions, runs the experiment 1000 times & returns the resultant qubit counts @@ -104,14 +106,14 @@ def deutsch_jozsa(case: str, num_qubits: int) -> q.result.counts.Counts: >>> deutsch_jozsa("balanced", 3) {'111': 1000} """ - # Use Aer's qasm_simulator - simulator = q.Aer.get_backend("qasm_simulator") + # Use Aer's simulator + simulator = qiskit.Aer.get_backend("aer_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) + # Execute the circuit on the simulator + job = qiskit.execute(dj_circuit, simulator, shots=1000) # Return the histogram data of the results of the experiment. return job.result().get_counts(dj_circuit) diff --git a/quantum/half_adder.py b/quantum/half_adder.py index 4af704e640be..21a57ddcf2dd 100755 --- a/quantum/half_adder.py +++ b/quantum/half_adder.py @@ -10,10 +10,10 @@ https://qiskit.org/textbook/ch-states/atoms-computation.html#4.2-Remembering-how-to-add- """ -import qiskit as q +import qiskit -def half_adder(bit0: int, bit1: int) -> q.result.counts.Counts: +def half_adder(bit0: int, bit1: int) -> qiskit.result.counts.Counts: """ >>> half_adder(0, 0) {'00': 1000} @@ -24,10 +24,10 @@ def half_adder(bit0: int, bit1: int) -> q.result.counts.Counts: >>> half_adder(1, 1) {'10': 1000} """ - # Use Aer's qasm_simulator - simulator = q.Aer.get_backend("qasm_simulator") + # Use Aer's simulator + simulator = qiskit.Aer.get_backend("aer_simulator") - qc_ha = q.QuantumCircuit(4, 2) + qc_ha = qiskit.QuantumCircuit(4, 2) # encode inputs in qubits 0 and 1 if bit0 == 1: qc_ha.x(0) @@ -48,9 +48,9 @@ def half_adder(bit0: int, bit1: int) -> q.result.counts.Counts: qc_ha.measure(3, 1) # extract AND value # Execute the circuit on the qasm simulator - job = q.execute(qc_ha, simulator, shots=1000) + job = qiskit.execute(qc_ha, simulator, shots=1000) - # Return the histogram data of the results of the experiment. + # Return the histogram data of the results of the experiment return job.result().get_counts(qc_ha) diff --git a/quantum/not_gate.py b/quantum/not_gate.py index e68a780091c7..ee23272d7a08 100644 --- a/quantum/not_gate.py +++ b/quantum/not_gate.py @@ -6,21 +6,23 @@ Qiskit Docs: https://qiskit.org/documentation/getting_started.html """ -import qiskit as q +import qiskit -def single_qubit_measure(qubits: int, classical_bits: int) -> q.result.counts.Counts: +def single_qubit_measure( + qubits: int, classical_bits: int +) -> qiskit.result.counts.Counts: """ >>> 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") + # Use Aer's simulator + simulator = qiskit.Aer.get_backend("aer_simulator") # Create a Quantum Circuit acting on the q register - circuit = q.QuantumCircuit(qubits, classical_bits) + circuit = qiskit.QuantumCircuit(qubits, classical_bits) # Apply X (NOT) Gate to Qubits 0 & 1 circuit.x(0) @@ -30,7 +32,7 @@ def single_qubit_measure(qubits: int, classical_bits: int) -> q.result.counts.Co circuit.measure([0, 1], [0, 1]) # Execute the circuit on the qasm simulator - job = q.execute(circuit, simulator, shots=1000) + job = qiskit.execute(circuit, simulator, shots=1000) # Return the histogram data of the results of the experiment. return job.result().get_counts(circuit) diff --git a/quantum/q_full_adder.py b/quantum/q_full_adder.py index 597efb8342e1..c6d03d170659 100644 --- a/quantum/q_full_adder.py +++ b/quantum/q_full_adder.py @@ -11,7 +11,6 @@ import math import qiskit -from qiskit import Aer, ClassicalRegister, QuantumCircuit, QuantumRegister, execute def quantum_full_adder( @@ -38,25 +37,25 @@ def quantum_full_adder( carry_in: carry in for the circuit. Returns: qiskit.result.counts.Counts: sum result counts. - >>> quantum_full_adder(1,1,1) + >>> quantum_full_adder(1, 1, 1) {'11': 1000} - >>> quantum_full_adder(0,0,1) + >>> quantum_full_adder(0, 0, 1) {'01': 1000} - >>> quantum_full_adder(1,0,1) + >>> quantum_full_adder(1, 0, 1) {'10': 1000} - >>> quantum_full_adder(1,-4,1) + >>> quantum_full_adder(1, -4, 1) Traceback (most recent call last): ... ValueError: inputs must be positive. - >>> quantum_full_adder('q',0,1) + >>> quantum_full_adder('q', 0, 1) Traceback (most recent call last): ... TypeError: inputs must be integers. - >>> quantum_full_adder(0.5,0,1) + >>> quantum_full_adder(0.5, 0, 1) Traceback (most recent call last): ... ValueError: inputs must be exact integers. - >>> quantum_full_adder(0,1,3) + >>> quantum_full_adder(0, 1, 3) Traceback (most recent call last): ... ValueError: inputs must be less or equal to 2. @@ -78,12 +77,12 @@ def quantum_full_adder( raise ValueError("inputs must be less or equal to 2.") # build registers - qr = QuantumRegister(4, "qr") - cr = ClassicalRegister(2, "cr") + qr = qiskit.QuantumRegister(4, "qr") + cr = qiskit.ClassicalRegister(2, "cr") # list the entries entry = [input_1, input_2, carry_in] - quantum_circuit = QuantumCircuit(qr, cr) + quantum_circuit = qiskit.QuantumCircuit(qr, cr) for i in range(0, 3): if entry[i] == 2: @@ -102,11 +101,11 @@ def quantum_full_adder( quantum_circuit.measure([2, 3], cr) # measure the last two qbits - backend = Aer.get_backend("qasm_simulator") - job = execute(quantum_circuit, backend, shots=1000) + backend = qiskit.Aer.get_backend("aer_simulator") + job = qiskit.execute(quantum_circuit, backend, shots=1000) return job.result().get_counts(quantum_circuit) if __name__ == "__main__": - print(f"Total sum count for state is: {quantum_full_adder(1,1,1)}") + print(f"Total sum count for state is: {quantum_full_adder(1, 1, 1)}") diff --git a/quantum/quantum_entanglement.py b/quantum/quantum_entanglement.py index 3d8e2771361c..08fc32e493b2 100644 --- a/quantum/quantum_entanglement.py +++ b/quantum/quantum_entanglement.py @@ -29,8 +29,8 @@ def quantum_entanglement(qubits: int = 2) -> qiskit.result.counts.Counts: """ classical_bits = qubits - # Using Aer's qasm_simulator - simulator = qiskit.Aer.get_backend("qasm_simulator") + # Using Aer's simulator + simulator = qiskit.Aer.get_backend("aer_simulator") # Creating a Quantum Circuit acting on the q register circuit = qiskit.QuantumCircuit(qubits, classical_bits) @@ -48,7 +48,7 @@ def quantum_entanglement(qubits: int = 2) -> qiskit.result.counts.Counts: # 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 + # Executing the circuit on the simulator job = qiskit.execute(circuit, simulator, shots=1000) return job.result().get_counts(circuit) diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py index 1d3724476068..c07757af7fff 100644 --- a/quantum/ripple_adder_classic.py +++ b/quantum/ripple_adder_classic.py @@ -2,11 +2,11 @@ # https://en.wikipedia.org/wiki/Adder_(electronics)#Full_adder # https://en.wikipedia.org/wiki/Controlled_NOT_gate -from qiskit import Aer, QuantumCircuit, execute +import qiskit from qiskit.providers import Backend -def store_two_classics(val1: int, val2: int) -> tuple[QuantumCircuit, str, str]: +def store_two_classics(val1: int, val2: int) -> tuple[qiskit.QuantumCircuit, str, str]: """ Generates a Quantum Circuit which stores two classical integers Returns the circuit and binary representation of the integers @@ -21,10 +21,10 @@ def store_two_classics(val1: int, val2: int) -> tuple[QuantumCircuit, str, str]: # 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) + circuit = qiskit.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 + # Reversed because it's easier to perform ops on more significant bits for i in range(len(x)): if x[::-1][i] == "1": circuit.x(i) @@ -36,7 +36,7 @@ def store_two_classics(val1: int, val2: int) -> tuple[QuantumCircuit, str, str]: def full_adder( - circuit: QuantumCircuit, + circuit: qiskit.QuantumCircuit, input1_loc: int, input2_loc: int, carry_in: int, @@ -55,14 +55,14 @@ def full_adder( # The default value for **backend** is the result of a function call which is not # normally recommended and causes flake8-bugbear to raise a B008 error. However, -# in this case, this is accptable because `Aer.get_backend()` is called when the +# in this case, this is acceptable because `Aer.get_backend()` is called when the # function is defined and that same backend is then reused for all function calls. def ripple_adder( val1: int, val2: int, - backend: Backend = Aer.get_backend("qasm_simulator"), # noqa: B008 + backend: Backend = qiskit.Aer.get_backend("aer_simulator"), # noqa: B008 ) -> int: """ Quantum Equivalent of a Ripple Adder Circuit @@ -104,7 +104,7 @@ def ripple_adder( for i in range(len(x) + 1): circuit.measure([(len(x) * 2) + i], [i]) - res = execute(circuit, backend, shots=1).result() + res = qiskit.execute(circuit, backend, shots=1).result() # The result is in binary. Convert it back to int return int(list(res.get_counts())[0], 2) diff --git a/quantum/single_qubit_measure.py b/quantum/single_qubit_measure.py index 7f058c2179a9..605bd804314a 100755 --- a/quantum/single_qubit_measure.py +++ b/quantum/single_qubit_measure.py @@ -6,25 +6,27 @@ Qiskit Docs: https://qiskit.org/documentation/getting_started.html """ -import qiskit as q +import qiskit -def single_qubit_measure(qubits: int, classical_bits: int) -> q.result.counts.Counts: +def single_qubit_measure( + qubits: int, classical_bits: int +) -> qiskit.result.counts.Counts: """ >>> single_qubit_measure(1, 1) {'0': 1000} """ - # Use Aer's qasm_simulator - simulator = q.Aer.get_backend("qasm_simulator") + # Use Aer's simulator + simulator = qiskit.Aer.get_backend("aer_simulator") # Create a Quantum Circuit acting on the q register - circuit = q.QuantumCircuit(qubits, classical_bits) + circuit = qiskit.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) + # Execute the circuit on the simulator + job = qiskit.execute(circuit, simulator, shots=1000) # Return the histogram data of the results of the experiment. return job.result().get_counts(circuit) From 4829fea24dc2c75ffc49571538fc40bce2d7e64b Mon Sep 17 00:00:00 2001 From: Atul Rajput <92659293+AtulRajput01@users.noreply.github.com> Date: Thu, 20 Oct 2022 13:18:28 +0530 Subject: [PATCH 1924/2908] Create graphs/dijkstra_alternate.py (#7405) * Update dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dijkstra.py * Update graphs/dijkstra.py Co-authored-by: Christian Clauss * Update graphs/dijkstra.py Co-authored-by: Christian Clauss * Update graphs/dijkstra.py Co-authored-by: Christian Clauss * Update dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dijkstra.py * Apply suggestions from code review * Create dijkstra_alternate.py * Update dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * int(1e7) * Update dijkstra_alternate.py * Update graphs/dijkstra_alternate.py * sptset --> visited Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- graphs/dijkstra_alternate.py | 98 ++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 graphs/dijkstra_alternate.py diff --git a/graphs/dijkstra_alternate.py b/graphs/dijkstra_alternate.py new file mode 100644 index 000000000000..7beef6b04da1 --- /dev/null +++ b/graphs/dijkstra_alternate.py @@ -0,0 +1,98 @@ +from __future__ import annotations + + +class Graph: + def __init__(self, vertices: int) -> None: + """ + >>> graph = Graph(2) + >>> graph.vertices + 2 + >>> len(graph.graph) + 2 + >>> len(graph.graph[0]) + 2 + """ + self.vertices = vertices + self.graph = [[0] * vertices for _ in range(vertices)] + + def print_solution(self, distances_from_source: list[int]) -> None: + """ + >>> Graph(0).print_solution([]) # doctest: +NORMALIZE_WHITESPACE + Vertex Distance from Source + """ + print("Vertex \t Distance from Source") + for vertex in range(self.vertices): + print(vertex, "\t\t", distances_from_source[vertex]) + + def minimum_distance( + self, distances_from_source: list[int], visited: list[bool] + ) -> int: + """ + A utility function to find the vertex with minimum distance value, from the set + of vertices not yet included in shortest path tree. + + >>> Graph(3).minimum_distance([1, 2, 3], [False, False, True]) + 0 + """ + + # Initialize minimum distance for next node + minimum = 1e7 + min_index = 0 + + # Search not nearest vertex not in the shortest path tree + for vertex in range(self.vertices): + if distances_from_source[vertex] < minimum and visited[vertex] is False: + minimum = distances_from_source[vertex] + min_index = vertex + return min_index + + def dijkstra(self, source: int) -> None: + """ + Function that implements Dijkstra's single source shortest path algorithm for a + graph represented using adjacency matrix representation. + + >>> Graph(4).dijkstra(1) # doctest: +NORMALIZE_WHITESPACE + Vertex Distance from Source + 0 10000000 + 1 0 + 2 10000000 + 3 10000000 + """ + + distances = [int(1e7)] * self.vertices # distances from the source + distances[source] = 0 + visited = [False] * self.vertices + + for _ in range(self.vertices): + u = self.minimum_distance(distances, visited) + visited[u] = True + + # Update dist value of the adjacent vertices + # of the picked vertex only if the current + # distance is greater than new distance and + # the vertex in not in the shortest path tree + for v in range(self.vertices): + if ( + self.graph[u][v] > 0 + and visited[v] is False + and distances[v] > distances[u] + self.graph[u][v] + ): + distances[v] = distances[u] + self.graph[u][v] + + self.print_solution(distances) + + +if __name__ == "__main__": + graph = Graph(9) + graph.graph = [ + [0, 4, 0, 0, 0, 0, 0, 8, 0], + [4, 0, 8, 0, 0, 0, 0, 11, 0], + [0, 8, 0, 7, 0, 4, 0, 0, 2], + [0, 0, 7, 0, 9, 14, 0, 0, 0], + [0, 0, 0, 9, 0, 10, 0, 0, 0], + [0, 0, 4, 14, 10, 0, 2, 0, 0], + [0, 0, 0, 0, 0, 2, 0, 1, 6], + [8, 11, 0, 0, 0, 0, 1, 0, 7], + [0, 0, 2, 0, 0, 0, 6, 7, 0], + ] + graph.dijkstra(0) From 831280ceddb1e37bb0215fd32899a52acbbccf2d Mon Sep 17 00:00:00 2001 From: Alan Paul <57307037+Alanzz@users.noreply.github.com> Date: Thu, 20 Oct 2022 15:57:13 +0530 Subject: [PATCH 1925/2908] Add quantum_random.py (#7446) * Create quantum_random.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update quantum_random.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update quantum_random.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update quantum/quantum_random.py Co-authored-by: Christian Clauss * Update quantum/quantum_random.py Co-authored-by: Christian Clauss * Update quantum/quantum_random.py Co-authored-by: Christian Clauss * Update quantum_random.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update quantum_random.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * requirements.txt: Add projectq * Update quantum_random.py * Update quantum/quantum_random.py Co-authored-by: Christian Clauss * Update quantum_random.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update quantum_random.py * Update quantum_random.py * Update quantum/quantum_random.py * Update quantum/quantum_random.py * Update quantum_random.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- quantum/quantum_random.py | 30 ++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 31 insertions(+) create mode 100644 quantum/quantum_random.py diff --git a/quantum/quantum_random.py b/quantum/quantum_random.py new file mode 100644 index 000000000000..01c8faa12ac0 --- /dev/null +++ b/quantum/quantum_random.py @@ -0,0 +1,30 @@ +import doctest + +import projectq +from projectq.ops import H, Measure + + +def get_random_number(quantum_engine: projectq.cengines._main.MainEngine) -> int: + """ + >>> isinstance(get_random_number(projectq.MainEngine()), int) + True + """ + qubit = quantum_engine.allocate_qubit() + H | qubit + Measure | qubit + return int(qubit) + + +if __name__ == "__main__": + doctest.testmod() + + # initialises a new quantum backend + quantum_engine = projectq.MainEngine() + + # Generate a list of 10 random numbers + random_numbers_list = [get_random_number(quantum_engine) for _ in range(10)] + + # Flushes the quantum engine from memory + quantum_engine.flush() + + print("Random numbers", random_numbers_list) diff --git a/requirements.txt b/requirements.txt index b14a3eb0157c..25d2b4ef93d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ numpy opencv-python pandas pillow +projectq qiskit requests scikit-fuzzy From 42b56f2345ed4566ea48306d3a727f1aa5c88218 Mon Sep 17 00:00:00 2001 From: Modassir Afzal <60973906+Moddy2024@users.noreply.github.com> Date: Fri, 21 Oct 2022 03:29:11 +0530 Subject: [PATCH 1926/2908] XGBoost Classifier (#7106) * Fixes: #{6551} * Update xgboostclassifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update xgboostclassifier.py * Update xgboostclassifier.py * Update xgboostclassifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: #{6551} * Update xgboostclassifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update xgboostclassifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update xgboostclassifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update xgboostclassifier.py * Fixes : #6551 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes : #6551 * Fixes : #6551 * Fixes: #6551 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update xgboostclassifier.py * Update xgboostclassifier.py * Update xgboostclassifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: #6551 * Fixes #6551 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: {#6551} * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: {#6551} * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: #6551 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * FIXES: {#6551} * Fixes : { #6551} * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes : { #6551} * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: { #6551] * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update xgboostclassifier.py * Update xgboostclassifier.py * Apply suggestions from code review * Update xgboostclassifier.py * Update xgboostclassifier.py * Update xgboostclassifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: { #6551} * Update xgboostclassifier.py * Fixes: { #6551} * Update xgboostclassifier.py * Fixes: ( #6551) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: { #6551} Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- machine_learning/xgboostclassifier.py | 82 +++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 machine_learning/xgboostclassifier.py diff --git a/machine_learning/xgboostclassifier.py b/machine_learning/xgboostclassifier.py new file mode 100644 index 000000000000..bb5b48b7ab23 --- /dev/null +++ b/machine_learning/xgboostclassifier.py @@ -0,0 +1,82 @@ +# XGBoost Classifier Example +import numpy as np +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 +from xgboost import XGBClassifier + + +def data_handling(data: dict) -> tuple: + # Split dataset into features and target + # data is features + """ + >>> data_handling(({'data':'[5.1, 3.5, 1.4, 0.2]','target':([0])})) + ('[5.1, 3.5, 1.4, 0.2]', [0]) + >>> data_handling( + ... {'data': '[4.9, 3.0, 1.4, 0.2], [4.7, 3.2, 1.3, 0.2]', 'target': ([0, 0])} + ... ) + ('[4.9, 3.0, 1.4, 0.2], [4.7, 3.2, 1.3, 0.2]', [0, 0]) + """ + return (data["data"], data["target"]) + + +def xgboost(features: np.ndarray, target: np.ndarray) -> XGBClassifier: + """ + >>> xgboost(np.array([[5.1, 3.6, 1.4, 0.2]]), np.array([0])) + XGBClassifier(base_score=0.5, booster='gbtree', callbacks=None, + colsample_bylevel=1, colsample_bynode=1, colsample_bytree=1, + early_stopping_rounds=None, enable_categorical=False, + eval_metric=None, gamma=0, gpu_id=-1, grow_policy='depthwise', + importance_type=None, interaction_constraints='', + learning_rate=0.300000012, max_bin=256, max_cat_to_onehot=4, + max_delta_step=0, max_depth=6, max_leaves=0, min_child_weight=1, + missing=nan, monotone_constraints='()', n_estimators=100, + n_jobs=0, num_parallel_tree=1, predictor='auto', random_state=0, + reg_alpha=0, reg_lambda=1, ...) + """ + classifier = XGBClassifier() + classifier.fit(features, target) + return classifier + + +def main() -> None: + + """ + >>> main() + + Url for the algorithm: + https://xgboost.readthedocs.io/en/stable/ + Iris type dataset is used to demonstrate algorithm. + """ + + # Load Iris dataset + iris = load_iris() + features, targets = data_handling(iris) + x_train, x_test, y_train, y_test = train_test_split( + features, targets, test_size=0.25 + ) + + names = iris["target_names"] + + # Create an XGBoost Classifier from the training data + xgboost_classifier = xgboost(x_train, y_train) + + # Display the confusion matrix of the classifier with both training and test sets + plot_confusion_matrix( + xgboost_classifier, + x_test, + y_test, + display_labels=names, + cmap="Blues", + normalize="true", + ) + plt.title("Normalized Confusion Matrix - IRIS Dataset") + plt.show() + + +if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) + main() From 717f0e46d950060f2147f022f65b7e44e72cfdd8 Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Fri, 21 Oct 2022 20:03:57 +1300 Subject: [PATCH 1927/2908] Maclaurin series approximation of sin (#7451) * added maclaurin_sin.py function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added type hints and fixed line overflows * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed incompatable type examples * Update maths/maclaurin_sin.py Co-authored-by: Caeden Perelli-Harris * changed error details * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed grammatical errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * improved function accuracy and added test case * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/maclaurin_sin.py Co-authored-by: Christian Clauss * removed redundant return * fixed pytest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris Co-authored-by: Christian Clauss --- maths/maclaurin_sin.py | 64 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 maths/maclaurin_sin.py diff --git a/maths/maclaurin_sin.py b/maths/maclaurin_sin.py new file mode 100644 index 000000000000..3c27ccf63d70 --- /dev/null +++ b/maths/maclaurin_sin.py @@ -0,0 +1,64 @@ +""" +https://en.wikipedia.org/wiki/Taylor_series#Trigonometric_functions +""" +from math import factorial, pi + + +def maclaurin_sin(theta: float, accuracy: int = 30) -> float: + """ + Finds the maclaurin approximation of sin + + :param theta: the angle to which sin is found + :param accuracy: the degree of accuracy wanted minimum ~ 1.5 theta + :return: the value of sine in radians + + + >>> from math import isclose, sin + >>> all(isclose(maclaurin_sin(x, 50), sin(x)) for x in range(-25, 25)) + True + >>> maclaurin_sin(10) + -0.544021110889369 + >>> maclaurin_sin(-10) + 0.5440211108893703 + >>> maclaurin_sin(10, 15) + -0.5440211108893689 + >>> maclaurin_sin(-10, 15) + 0.5440211108893703 + >>> maclaurin_sin("10") + Traceback (most recent call last): + ... + ValueError: maclaurin_sin() requires either an int or float for theta + >>> maclaurin_sin(10, -30) + Traceback (most recent call last): + ... + ValueError: maclaurin_sin() requires a positive int for accuracy + >>> maclaurin_sin(10, 30.5) + Traceback (most recent call last): + ... + ValueError: maclaurin_sin() requires a positive int for accuracy + >>> maclaurin_sin(10, "30") + Traceback (most recent call last): + ... + ValueError: maclaurin_sin() requires a positive int for accuracy + """ + + if not isinstance(theta, (int, float)): + raise ValueError("maclaurin_sin() requires either an int or float for theta") + + if not isinstance(accuracy, int) or accuracy <= 0: + raise ValueError("maclaurin_sin() requires a positive int for accuracy") + + theta = float(theta) + div = theta // (2 * pi) + theta -= 2 * div * pi + return sum( + (((-1) ** r) * ((theta ** (2 * r + 1)) / factorial(2 * r + 1))) + for r in range(accuracy) + ) + + +if __name__ == "__main__": + print(maclaurin_sin(10)) + print(maclaurin_sin(-10)) + print(maclaurin_sin(10, 15)) + print(maclaurin_sin(-10, 15)) From cc10b20beb8f0b10b50c84bd523bf41095fe9f37 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 22 Oct 2022 07:33:51 -0400 Subject: [PATCH 1928/2908] Remove some print statements within algorithmic functions (#7499) * Remove commented-out print statements in algorithmic functions * Encapsulate non-algorithmic code in __main__ * Remove unused print_matrix function * Remove print statement in __init__ * Remove print statement from doctest * Encapsulate non-algorithmic code in __main__ * Modify algorithm to return instead of print * Encapsulate non-algorithmic code in __main__ * Refactor data_safety_checker to return instead of print * updating DIRECTORY.md * updating DIRECTORY.md * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 4 + cellular_automata/game_of_life.py | 1 - digital_image_processing/index_calculation.py | 1 - divide_and_conquer/max_subarray_sum.py | 12 ++- .../strassen_matrix_multiplication.py | 3 +- dynamic_programming/longest_sub_array.py | 1 - dynamic_programming/max_non_adjacent_sum.py | 2 +- dynamic_programming/subset_generation.py | 9 +- dynamic_programming/sum_of_subset.py | 14 ++- machine_learning/forecasting/run.py | 96 ++++++++++--------- 10 files changed, 74 insertions(+), 69 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 1fad287988c4..70644d0639dc 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -360,6 +360,7 @@ * [Dijkstra](graphs/dijkstra.py) * [Dijkstra 2](graphs/dijkstra_2.py) * [Dijkstra Algorithm](graphs/dijkstra_algorithm.py) + * [Dijkstra Alternate](graphs/dijkstra_alternate.py) * [Dinic](graphs/dinic.py) * [Directed And Undirected (Weighted) Graph](graphs/directed_and_undirected_(weighted)_graph.py) * [Edmonds Karp Multiple Source And Sink](graphs/edmonds_karp_multiple_source_and_sink.py) @@ -460,6 +461,7 @@ * [Similarity Search](machine_learning/similarity_search.py) * [Support Vector Machines](machine_learning/support_vector_machines.py) * [Word Frequency Functions](machine_learning/word_frequency_functions.py) + * [Xgboostclassifier](machine_learning/xgboostclassifier.py) ## Maths * [3N Plus 1](maths/3n_plus_1.py) @@ -534,6 +536,7 @@ * [Line Length](maths/line_length.py) * [Lucas Lehmer Primality Test](maths/lucas_lehmer_primality_test.py) * [Lucas Series](maths/lucas_series.py) + * [Maclaurin Sin](maths/maclaurin_sin.py) * [Matrix Exponentiation](maths/matrix_exponentiation.py) * [Max Sum Sliding Window](maths/max_sum_sliding_window.py) * [Median Of Two Arrays](maths/median_of_two_arrays.py) @@ -936,6 +939,7 @@ * [Not Gate](quantum/not_gate.py) * [Q Full Adder](quantum/q_full_adder.py) * [Quantum Entanglement](quantum/quantum_entanglement.py) + * [Quantum Random](quantum/quantum_random.py) * [Ripple Adder Classic](quantum/ripple_adder_classic.py) * [Single Qubit Measure](quantum/single_qubit_measure.py) diff --git a/cellular_automata/game_of_life.py b/cellular_automata/game_of_life.py index c5324da73dbf..8e54702519b9 100644 --- a/cellular_automata/game_of_life.py +++ b/cellular_automata/game_of_life.py @@ -66,7 +66,6 @@ def run(canvas: list[list[bool]]) -> list[list[bool]]: next_gen_canvas = np.array(create_canvas(current_canvas.shape[0])) for r, row in enumerate(current_canvas): for c, pt in enumerate(row): - # print(r-1,r+2,c-1,c+2) next_gen_canvas[r][c] = __judge_point( pt, current_canvas[r - 1 : r + 2, c - 1 : c + 2] ) diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index 01cd79fc18ff..be1855e99d10 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -105,7 +105,6 @@ class IndexCalculation: """ def __init__(self, red=None, green=None, blue=None, red_edge=None, nir=None): - # print("Numpy version: " + np.__version__) self.set_matricies(red=red, green=green, blue=blue, red_edge=red_edge, nir=nir) def set_matricies(self, red=None, green=None, blue=None, red_edge=None, nir=None): diff --git a/divide_and_conquer/max_subarray_sum.py b/divide_and_conquer/max_subarray_sum.py index 43f58086e078..f23e81719025 100644 --- a/divide_and_conquer/max_subarray_sum.py +++ b/divide_and_conquer/max_subarray_sum.py @@ -69,8 +69,10 @@ def max_subarray_sum(array, left, 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) -) +if __name__ == "__main__": + 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), + ) diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index 0ee426e4b39a..371605d6d4d4 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -68,8 +68,7 @@ def matrix_dimensions(matrix: list) -> tuple[int, int]: def print_matrix(matrix: list) -> None: - for i in range(len(matrix)): - print(matrix[i]) + print("\n".join(str(line) for line in matrix)) def actual_strassen(matrix_a: list, matrix_b: list) -> list: diff --git a/dynamic_programming/longest_sub_array.py b/dynamic_programming/longest_sub_array.py index 30159a1386c3..b477acf61e66 100644 --- a/dynamic_programming/longest_sub_array.py +++ b/dynamic_programming/longest_sub_array.py @@ -14,7 +14,6 @@ class SubArray: def __init__(self, arr): # we need a list not a string, so do something to change the type self.array = arr.split(",") - print(("the input array is:", self.array)) def solve_sub_array(self): rear = [int(self.array[0])] * len(self.array) diff --git a/dynamic_programming/max_non_adjacent_sum.py b/dynamic_programming/max_non_adjacent_sum.py index 5362b22ca9dc..e3cc23f4983e 100644 --- a/dynamic_programming/max_non_adjacent_sum.py +++ b/dynamic_programming/max_non_adjacent_sum.py @@ -7,7 +7,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])) + >>> maximum_non_adjacent_sum([1, 2, 3]) 4 >>> maximum_non_adjacent_sum([1, 5, 3, 7, 2, 2, 6]) 18 diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py index 4781b23b32eb..819fd8106def 100644 --- a/dynamic_programming/subset_generation.py +++ b/dynamic_programming/subset_generation.py @@ -37,7 +37,8 @@ def print_combination(arr, n, r): combination_util(arr, n, r, 0, data, 0) -# Driver function to check for above function -arr = [10, 20, 30, 40, 50] -print_combination(arr, len(arr), 3) -# This code is contributed by Ambuj sahu +if __name__ == "__main__": + # Driver code to check the function above + arr = [10, 20, 30, 40, 50] + print_combination(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 77672b0b83e5..96ebcf583a4b 100644 --- a/dynamic_programming/sum_of_subset.py +++ b/dynamic_programming/sum_of_subset.py @@ -1,13 +1,14 @@ -def is_sum_subset(arr, arr_len, required_sum): +def is_sum_subset(arr: list[int], required_sum: int) -> bool: """ - >>> is_sum_subset([2, 4, 6, 8], 4, 5) + >>> is_sum_subset([2, 4, 6, 8], 5) False - >>> is_sum_subset([2, 4, 6, 8], 4, 14) + >>> is_sum_subset([2, 4, 6, 8], 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(required_sum + 1)] for i in range(arr_len + 1)] + arr_len = len(arr) + subset = [[False] * (required_sum + 1) for _ in range(arr_len + 1)] # for each arr value, a sum of zero(0) can be formed by not taking any element # hence True/1 @@ -25,10 +26,7 @@ def is_sum_subset(arr, arr_len, required_sum): 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]) - print(subset[arr_len][required_sum]) + return subset[arr_len][required_sum] if __name__ == "__main__": diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py index b11a230129eb..0909b76d8907 100644 --- a/machine_learning/forecasting/run.py +++ b/machine_learning/forecasting/run.py @@ -1,7 +1,7 @@ """ 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 +for ex: you have an 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 @@ -91,14 +91,14 @@ def interquartile_range_checker(train_user: list) -> float: return low_lim -def data_safety_checker(list_vote: list, actual_result: float) -> None: +def data_safety_checker(list_vote: list, actual_result: float) -> bool: """ 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. + >>> data_safety_checker([2, 3, 4], 5.0) + False """ safe = 0 not_safe = 0 @@ -107,50 +107,54 @@ def data_safety_checker(list_vote: list, actual_result: float) -> None: safe = not_safe + 1 else: if abs(abs(i) - abs(actual_result)) <= 0.1: - safe = safe + 1 + safe += 1 else: - not_safe = not_safe + 1 - print(f"Today's data is {'not ' if safe <= not_safe else ''}safe.") + not_safe += 1 + return safe > not_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"]) +if __name__ == "__main__": + # 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) -""" + """ + 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) + # 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 regression & 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 = [ + linear_regression_prediction( + trn_date, trn_user, trn_match, tst_date, tst_match + ), + sarimax_predictor(trn_user, trn_match, tst_match), + support_vector_regressor(x_train, x_test, trn_user), + ] + + # check the safety of today's data + not_str = "" if data_safety_checker(res_vote, tst_user) else "not " + print("Today's data is {not_str}safe.") From a5dd07c3707a0d3ebde0321ce7984082b3d322ff Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Sun, 23 Oct 2022 05:17:07 +1300 Subject: [PATCH 1929/2908] Maclaurin approximation of cos (#7507) * renamed maclaurin_sin.py to maclaurin_series.py and included function for cos approximation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * attempt to fix pytest error Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/maclaurin_series.py | 121 ++++++++++++++++++++++++++++++++++++++ maths/maclaurin_sin.py | 64 -------------------- 2 files changed, 121 insertions(+), 64 deletions(-) create mode 100644 maths/maclaurin_series.py delete mode 100644 maths/maclaurin_sin.py diff --git a/maths/maclaurin_series.py b/maths/maclaurin_series.py new file mode 100644 index 000000000000..57edc90bf676 --- /dev/null +++ b/maths/maclaurin_series.py @@ -0,0 +1,121 @@ +""" +https://en.wikipedia.org/wiki/Taylor_series#Trigonometric_functions +""" +from math import factorial, pi + + +def maclaurin_sin(theta: float, accuracy: int = 30) -> float: + """ + Finds the maclaurin approximation of sin + + :param theta: the angle to which sin is found + :param accuracy: the degree of accuracy wanted minimum + :return: the value of sine in radians + + + >>> from math import isclose, sin + >>> all(isclose(maclaurin_sin(x, 50), sin(x)) for x in range(-25, 25)) + True + >>> maclaurin_sin(10) + -0.544021110889369 + >>> maclaurin_sin(-10) + 0.5440211108893703 + >>> maclaurin_sin(10, 15) + -0.5440211108893689 + >>> maclaurin_sin(-10, 15) + 0.5440211108893703 + >>> maclaurin_sin("10") + Traceback (most recent call last): + ... + ValueError: maclaurin_sin() requires either an int or float for theta + >>> maclaurin_sin(10, -30) + Traceback (most recent call last): + ... + ValueError: maclaurin_sin() requires a positive int for accuracy + >>> maclaurin_sin(10, 30.5) + Traceback (most recent call last): + ... + ValueError: maclaurin_sin() requires a positive int for accuracy + >>> maclaurin_sin(10, "30") + Traceback (most recent call last): + ... + ValueError: maclaurin_sin() requires a positive int for accuracy + """ + + if not isinstance(theta, (int, float)): + raise ValueError("maclaurin_sin() requires either an int or float for theta") + + if not isinstance(accuracy, int) or accuracy <= 0: + raise ValueError("maclaurin_sin() requires a positive int for accuracy") + + theta = float(theta) + div = theta // (2 * pi) + theta -= 2 * div * pi + return sum( + (((-1) ** r) * ((theta ** (2 * r + 1)) / factorial(2 * r + 1))) + for r in range(accuracy) + ) + + +def maclaurin_cos(theta: float, accuracy: int = 30) -> float: + """ + Finds the maclaurin approximation of cos + + :param theta: the angle to which cos is found + :param accuracy: the degree of accuracy wanted + :return: the value of cosine in radians + + + >>> from math import isclose, cos + >>> all(isclose(maclaurin_cos(x, 50), cos(x)) for x in range(-25, 25)) + True + >>> maclaurin_cos(5) + 0.28366218546322675 + >>> maclaurin_cos(-5) + 0.2836621854632266 + >>> maclaurin_cos(10, 15) + -0.8390715290764525 + >>> maclaurin_cos(-10, 15) + -0.8390715290764521 + >>> maclaurin_cos("10") + Traceback (most recent call last): + ... + ValueError: maclaurin_cos() requires either an int or float for theta + >>> maclaurin_cos(10, -30) + Traceback (most recent call last): + ... + ValueError: maclaurin_cos() requires a positive int for accuracy + >>> maclaurin_cos(10, 30.5) + Traceback (most recent call last): + ... + ValueError: maclaurin_cos() requires a positive int for accuracy + >>> maclaurin_cos(10, "30") + Traceback (most recent call last): + ... + ValueError: maclaurin_cos() requires a positive int for accuracy + """ + + if not isinstance(theta, (int, float)): + raise ValueError("maclaurin_cos() requires either an int or float for theta") + + if not isinstance(accuracy, int) or accuracy <= 0: + raise ValueError("maclaurin_cos() requires a positive int for accuracy") + + theta = float(theta) + div = theta // (2 * pi) + theta -= 2 * div * pi + return sum( + (((-1) ** r) * ((theta ** (2 * r)) / factorial(2 * r))) for r in range(accuracy) + ) + + +if __name__ == "__main__": + print(maclaurin_sin(10)) + print(maclaurin_sin(-10)) + print(maclaurin_sin(10, 15)) + print(maclaurin_sin(-10, 15)) + + print(maclaurin_cos(5)) + print(maclaurin_cos(-5)) + print(maclaurin_cos(10, 15)) + print(maclaurin_cos(-10, 15)) diff --git a/maths/maclaurin_sin.py b/maths/maclaurin_sin.py deleted file mode 100644 index 3c27ccf63d70..000000000000 --- a/maths/maclaurin_sin.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -https://en.wikipedia.org/wiki/Taylor_series#Trigonometric_functions -""" -from math import factorial, pi - - -def maclaurin_sin(theta: float, accuracy: int = 30) -> float: - """ - Finds the maclaurin approximation of sin - - :param theta: the angle to which sin is found - :param accuracy: the degree of accuracy wanted minimum ~ 1.5 theta - :return: the value of sine in radians - - - >>> from math import isclose, sin - >>> all(isclose(maclaurin_sin(x, 50), sin(x)) for x in range(-25, 25)) - True - >>> maclaurin_sin(10) - -0.544021110889369 - >>> maclaurin_sin(-10) - 0.5440211108893703 - >>> maclaurin_sin(10, 15) - -0.5440211108893689 - >>> maclaurin_sin(-10, 15) - 0.5440211108893703 - >>> maclaurin_sin("10") - Traceback (most recent call last): - ... - ValueError: maclaurin_sin() requires either an int or float for theta - >>> maclaurin_sin(10, -30) - Traceback (most recent call last): - ... - ValueError: maclaurin_sin() requires a positive int for accuracy - >>> maclaurin_sin(10, 30.5) - Traceback (most recent call last): - ... - ValueError: maclaurin_sin() requires a positive int for accuracy - >>> maclaurin_sin(10, "30") - Traceback (most recent call last): - ... - ValueError: maclaurin_sin() requires a positive int for accuracy - """ - - if not isinstance(theta, (int, float)): - raise ValueError("maclaurin_sin() requires either an int or float for theta") - - if not isinstance(accuracy, int) or accuracy <= 0: - raise ValueError("maclaurin_sin() requires a positive int for accuracy") - - theta = float(theta) - div = theta // (2 * pi) - theta -= 2 * div * pi - return sum( - (((-1) ** r) * ((theta ** (2 * r + 1)) / factorial(2 * r + 1))) - for r in range(accuracy) - ) - - -if __name__ == "__main__": - print(maclaurin_sin(10)) - print(maclaurin_sin(-10)) - print(maclaurin_sin(10, 15)) - print(maclaurin_sin(-10, 15)) From ed127032b303d06f2c1ceefd58a8680bb4c2ce50 Mon Sep 17 00:00:00 2001 From: Akshit Gulyan <103456810+AkshitGulyan@users.noreply.github.com> Date: Sun, 23 Oct 2022 09:59:10 +0530 Subject: [PATCH 1930/2908] Created sum_of_harmonic_series.py (#7504) * Created sum_of_harmonic_series.py Here in this code the formula for Harmonic sum is not used, Sum of the series is calculated by creating a list of the elements in the given Harmonic series and adding all the elements of that list ! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/sum_of_harmonic_series.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * Update maths/sum_of_harmonic_series.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * Update maths/sum_of_harmonic_series.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/sum_of_harmonic_series.py Co-authored-by: Christian Clauss * Update maths/sum_of_harmonic_series.py Co-authored-by: Christian Clauss * Update maths/sum_of_harmonic_series.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sum_of_harmonic_series.py * Add doctests * Update sum_of_harmonic_series.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/sum_of_harmonic_series.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 maths/sum_of_harmonic_series.py diff --git a/maths/sum_of_harmonic_series.py b/maths/sum_of_harmonic_series.py new file mode 100644 index 000000000000..9e0d6b19b95a --- /dev/null +++ b/maths/sum_of_harmonic_series.py @@ -0,0 +1,29 @@ +def sum_of_harmonic_progression( + first_term: float, common_difference: float, number_of_terms: int +) -> float: + """ + https://en.wikipedia.org/wiki/Harmonic_progression_(mathematics) + + Find the sum of n terms in an harmonic progression. The calculation starts with the + first_term and loops adding the common difference of Arithmetic Progression by which + the given Harmonic Progression is linked. + + >>> sum_of_harmonic_progression(1 / 2, 2, 2) + 0.75 + >>> sum_of_harmonic_progression(1 / 5, 5, 5) + 0.45666666666666667 + """ + arithmetic_progression = [1 / first_term] + first_term = 1 / first_term + for _ in range(number_of_terms - 1): + first_term += common_difference + arithmetic_progression.append(first_term) + harmonic_series = [1 / step for step in arithmetic_progression] + return sum(harmonic_series) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(sum_of_harmonic_progression(1 / 2, 2, 2)) From f32f78a9e0a4c2c1e2e9c985fd2375e7ede8925c Mon Sep 17 00:00:00 2001 From: Abhishek Chakraborty Date: Sun, 23 Oct 2022 03:42:02 -0700 Subject: [PATCH 1931/2908] Basic string grammar fix (#7534) * Grammar edit * Flake8 consistency fix * Apply suggestions from code review Co-authored-by: Christian Clauss --- genetic_algorithm/basic_string.py | 54 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index d2d305189983..3227adf53ae4 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -9,15 +9,15 @@ import random -# Maximum size of the population. bigger could be faster but is more memory expensive +# 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 +# Number of elements selected in every generation of evolution. The selection takes +# place from best to worst of that generation and 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 +# Probability that an element of a generation can mutate, changing one of its genes. +# This will guarantee that all genes will be used during evolution. MUTATION_PROBABILITY = 0.4 -# just a seed to improve randomness required by the algorithm +# Just a seed to improve randomness required by the algorithm. random.seed(random.randint(0, 1000)) @@ -56,20 +56,20 @@ def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int, f"{not_in_genes_list} is not in genes list, evolution cannot converge" ) - # Generate random starting population + # 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 + # 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 + # This loop will end when we find a perfect match for our target. while True: generation += 1 total_population += len(population) - # Random population created now it's time to evaluate + # 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 @@ -92,17 +92,17 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]: # 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 + # but with a simple algorithm like this, it will probably be slower. + # We just need to call evaluate for every item inside the population. population_score = [evaluate(item) for item in population] - # Check if there is a matching evolution + # 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 + # 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}" @@ -111,21 +111,21 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]: f"\nBest string: {population_score[0][0]}" ) - # Flush the old population keeping some of the best evolutions - # Keeping this avoid regression of evolution + # 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 + # Normalize population score to be between 0 and 1. population_score = [ (item, score / len(target)) for item, score in population_score ] - # Select, Crossover and Mutate a new population + # 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 + # Generate more children 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): @@ -134,32 +134,32 @@ def select(parent_1: tuple[str, float]) -> list[str]: ][0] child_1, child_2 = crossover(parent_1[0], parent_2) - # Append new string to the population list + # 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""" + """Slice and combine two string at 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""" + """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 + # 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 + # break the cycle. If this check is disabled, the algorithm will take + # forever to compute large strings, but will also calculate small strings in + # a far fewer generations. if len(population) > N_POPULATION: break From a0cbc2056e9b9ff4f8c5da682061996e783b13e3 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 23 Oct 2022 12:01:51 +0100 Subject: [PATCH 1932/2908] refactor: Make code more simple in maclaurin_series (#7522) --- maths/maclaurin_series.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/maths/maclaurin_series.py b/maths/maclaurin_series.py index 57edc90bf676..a2619d4e6b92 100644 --- a/maths/maclaurin_series.py +++ b/maths/maclaurin_series.py @@ -52,8 +52,7 @@ def maclaurin_sin(theta: float, accuracy: int = 30) -> float: div = theta // (2 * pi) theta -= 2 * div * pi return sum( - (((-1) ** r) * ((theta ** (2 * r + 1)) / factorial(2 * r + 1))) - for r in range(accuracy) + (-1) ** r * theta ** (2 * r + 1) / factorial(2 * r + 1) for r in range(accuracy) ) @@ -104,12 +103,14 @@ def maclaurin_cos(theta: float, accuracy: int = 30) -> float: theta = float(theta) div = theta // (2 * pi) theta -= 2 * div * pi - return sum( - (((-1) ** r) * ((theta ** (2 * r)) / factorial(2 * r))) for r in range(accuracy) - ) + return sum((-1) ** r * theta ** (2 * r) / factorial(2 * r) for r in range(accuracy)) if __name__ == "__main__": + import doctest + + doctest.testmod() + print(maclaurin_sin(10)) print(maclaurin_sin(-10)) print(maclaurin_sin(10, 15)) From 1bbb0092f3fc311fac9e56e12c1fa223dbe16465 Mon Sep 17 00:00:00 2001 From: Arjit Arora <42044030+arjitarora26@users.noreply.github.com> Date: Sun, 23 Oct 2022 16:47:30 +0530 Subject: [PATCH 1933/2908] Add signum function (#7526) * Add signum function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add typehints for functions * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update signum.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/signum.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 maths/signum.py diff --git a/maths/signum.py b/maths/signum.py new file mode 100644 index 000000000000..148f931767c1 --- /dev/null +++ b/maths/signum.py @@ -0,0 +1,34 @@ +""" +Signum function -- https://en.wikipedia.org/wiki/Sign_function +""" + + +def signum(num: float) -> int: + """ + Applies signum function on the number + + >>> signum(-10) + -1 + >>> signum(10) + 1 + >>> signum(0) + 0 + """ + if num < 0: + return -1 + return 1 if num else 0 + + +def test_signum() -> None: + """ + Tests the signum function + """ + assert signum(5) == 1 + assert signum(-5) == -1 + assert signum(0) == 0 + + +if __name__ == "__main__": + print(signum(12)) + print(signum(-12)) + print(signum(0)) From b092f9979f5afd3bd86cb46e891eb1f318b351d9 Mon Sep 17 00:00:00 2001 From: Modassir Afzal <60973906+Moddy2024@users.noreply.github.com> Date: Sun, 23 Oct 2022 17:17:19 +0530 Subject: [PATCH 1934/2908] XGB Regressor (#7107) * Fixes: #{6551} * Fixes: #{6551} * Update xgboostclassifier.py * Delete xgboostclassifier.py * Update xgboostregressor.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: #{6551} * Fixes : {#6551} * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes: {#6551] * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update xgboostregressor.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update xgboostregressor.py * Update xgboostregressor.py * Fixes: { #6551} * Update xgboostregressor.py * Fixes: { #6551} * Fixes: { #6551} * Update and rename xgboostregressor.py to xgboost_regressor.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- machine_learning/xgboost_regressor.py | 64 +++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 machine_learning/xgboost_regressor.py diff --git a/machine_learning/xgboost_regressor.py b/machine_learning/xgboost_regressor.py new file mode 100644 index 000000000000..023984fc1f59 --- /dev/null +++ b/machine_learning/xgboost_regressor.py @@ -0,0 +1,64 @@ +# XGBoost Regressor Example +import numpy as np +from sklearn.datasets import fetch_california_housing +from sklearn.metrics import mean_absolute_error, mean_squared_error +from sklearn.model_selection import train_test_split +from xgboost import XGBRegressor + + +def data_handling(data: dict) -> tuple: + # Split dataset into features and target. Data is features. + """ + >>> data_handling(( + ... {'data':'[ 8.3252 41. 6.9841269 1.02380952 322. 2.55555556 37.88 -122.23 ]' + ... ,'target':([4.526])})) + ('[ 8.3252 41. 6.9841269 1.02380952 322. 2.55555556 37.88 -122.23 ]', [4.526]) + """ + return (data["data"], data["target"]) + + +def xgboost( + features: np.ndarray, target: np.ndarray, test_features: np.ndarray +) -> np.ndarray: + """ + >>> xgboost(np.array([[ 2.3571 , 52. , 6.00813008, 1.06775068, + ... 907. , 2.45799458, 40.58 , -124.26]]),np.array([1.114]), + ... np.array([[1.97840000e+00, 3.70000000e+01, 4.98858447e+00, 1.03881279e+00, + ... 1.14300000e+03, 2.60958904e+00, 3.67800000e+01, -1.19780000e+02]])) + array([[1.1139996]], dtype=float32) + """ + xgb = XGBRegressor(verbosity=0, random_state=42) + xgb.fit(features, target) + # Predict target for test data + predictions = xgb.predict(test_features) + predictions = predictions.reshape(len(predictions), 1) + return predictions + + +def main() -> None: + """ + >>> main() + Mean Absolute Error : 0.30957163379906033 + Mean Square Error : 0.22611560196662744 + + The URL for this algorithm + https://xgboost.readthedocs.io/en/stable/ + California house price dataset is used to demonstrate the algorithm. + """ + # Load California house price dataset + california = fetch_california_housing() + data, target = data_handling(california) + x_train, x_test, y_train, y_test = train_test_split( + data, target, test_size=0.25, random_state=1 + ) + predictions = xgboost(x_train, y_train, x_test) + # Error printing + print(f"Mean Absolute Error : {mean_absolute_error(y_test, predictions)}") + print(f"Mean Square Error : {mean_squared_error(y_test, predictions)}") + + +if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) + main() From a3383ce3fd6bc30b01681503a4307df2462c8bd4 Mon Sep 17 00:00:00 2001 From: Pradyumn Singh Rahar Date: Sun, 23 Oct 2022 17:56:40 +0530 Subject: [PATCH 1935/2908] Reduced Time Complexity to O(sqrt(n)) (#7429) * Reduced Time Complexity to O(sqrt(n)) * Added testmod * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/factors.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/maths/factors.py b/maths/factors.py index e2fdc4063a13..ae2e5316cf65 100644 --- a/maths/factors.py +++ b/maths/factors.py @@ -1,3 +1,7 @@ +from doctest import testmod +from math import sqrt + + def factors_of_a_number(num: int) -> list: """ >>> factors_of_a_number(1) @@ -9,10 +13,22 @@ def factors_of_a_number(num: int) -> list: >>> factors_of_a_number(-24) [] """ - return [i for i in range(1, num + 1) if num % i == 0] + facs: list[int] = [] + if num < 1: + return facs + facs.append(1) + if num == 1: + return facs + facs.append(num) + for i in range(2, int(sqrt(num)) + 1): + if num % i == 0: # If i is a factor of num + facs.append(i) + d = num // i # num//i is the other factor of num + if d != i: # If d and i are distinct + facs.append(d) # we have found another factor + facs.sort() + return facs 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)}") + testmod(name="factors_of_a_number", verbose=True) From a5362799a5e73e199cda7f1acec71d1e97addc97 Mon Sep 17 00:00:00 2001 From: Kevin Joven <59969678+KevinJoven11@users.noreply.github.com> Date: Sun, 23 Oct 2022 08:54:27 -0400 Subject: [PATCH 1936/2908] Create superdense_coding.py (#7349) * Create superdense_coding.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- quantum/superdense_coding.py | 102 +++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 quantum/superdense_coding.py diff --git a/quantum/superdense_coding.py b/quantum/superdense_coding.py new file mode 100644 index 000000000000..c8eda381158b --- /dev/null +++ b/quantum/superdense_coding.py @@ -0,0 +1,102 @@ +""" +Build the superdense coding protocol. This quantum +circuit can send two classical bits using one quantum +bit. This circuit is designed using the Qiskit +framework. This experiment run in IBM Q simulator +with 1000 shots. +. +References: +https://qiskit.org/textbook/ch-algorithms/superdense-coding.html +https://en.wikipedia.org/wiki/Superdense_coding +""" + +import math + +import qiskit +from qiskit import Aer, ClassicalRegister, QuantumCircuit, QuantumRegister, execute + + +def superdense_coding(bit_1: int = 1, bit_2: int = 1) -> qiskit.result.counts.Counts: + """ + The input refer to the classical message + that you wants to send. {'00','01','10','11'} + result for default values: {11: 1000} + ┌───┐ ┌───┐ + qr_0: ─────┤ X ├──────────┤ X ├───── + ┌───┐└─┬─┘┌───┐┌───┐└─┬─┘┌───┐ + qr_1: ┤ H ├──■──┤ X ├┤ Z ├──■──┤ H ├ + └───┘ └───┘└───┘ └───┘ + cr: 2/══════════════════════════════ + Args: + bit_1: bit 1 of classical information to send. + bit_2: bit 2 of classical information to send. + Returns: + qiskit.result.counts.Counts: counts of send state. + >>> superdense_coding(0,0) + {'00': 1000} + >>> superdense_coding(0,1) + {'01': 1000} + >>> superdense_coding(-1,0) + Traceback (most recent call last): + ... + ValueError: inputs must be positive. + >>> superdense_coding(1,'j') + Traceback (most recent call last): + ... + TypeError: inputs must be integers. + >>> superdense_coding(1,0.5) + Traceback (most recent call last): + ... + ValueError: inputs must be exact integers. + >>> superdense_coding(2,1) + Traceback (most recent call last): + ... + ValueError: inputs must be less or equal to 1. + """ + if (type(bit_1) == str) or (type(bit_2) == str): + raise TypeError("inputs must be integers.") + if (bit_1 < 0) or (bit_2 < 0): + raise ValueError("inputs must be positive.") + if (math.floor(bit_1) != bit_1) or (math.floor(bit_2) != bit_2): + raise ValueError("inputs must be exact integers.") + if (bit_1 > 1) or (bit_2 > 1): + raise ValueError("inputs must be less or equal to 1.") + + # build registers + qr = QuantumRegister(2, "qr") + cr = ClassicalRegister(2, "cr") + + quantum_circuit = QuantumCircuit(qr, cr) + + # entanglement the qubits + quantum_circuit.h(1) + quantum_circuit.cx(1, 0) + + # send the information + c_information = str(bit_1) + str(bit_2) + + if c_information == "11": + quantum_circuit.x(1) + quantum_circuit.z(1) + elif c_information == "10": + quantum_circuit.z(1) + elif c_information == "01": + quantum_circuit.x(1) + else: + quantum_circuit.i(1) + + # unentangled the circuit + quantum_circuit.cx(1, 0) + quantum_circuit.h(1) + + # measure the circuit + quantum_circuit.measure(qr, cr) + + backend = Aer.get_backend("qasm_simulator") + job = execute(quantum_circuit, backend, shots=1000) + + return job.result().get_counts(quantum_circuit) + + +if __name__ == "__main__": + print(f"Counts for classical state send: {superdense_coding(1,1)}") From d5f322f5764f42fc846fbcdaefac238a9ab62c7f Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 23 Oct 2022 14:06:12 +0100 Subject: [PATCH 1937/2908] fix: Replace deprecated `qasm_simulator` with `aer_simulator` (#7308) (#7556) --- quantum/superdense_coding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/superdense_coding.py b/quantum/superdense_coding.py index c8eda381158b..10ebc2d3593c 100644 --- a/quantum/superdense_coding.py +++ b/quantum/superdense_coding.py @@ -92,7 +92,7 @@ def superdense_coding(bit_1: int = 1, bit_2: int = 1) -> qiskit.result.counts.Co # measure the circuit quantum_circuit.measure(qr, cr) - backend = Aer.get_backend("qasm_simulator") + backend = Aer.get_backend("aer_simulator") job = execute(quantum_circuit, backend, shots=1000) return job.result().get_counts(quantum_circuit) From 81ccf54c75edbf52cd2b5bd4e139cba3b6e5e5ab Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 23 Oct 2022 15:09:25 +0200 Subject: [PATCH 1938/2908] Rename xgboostclassifier.py to xgboost_classifier.py (#7550) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 7 +++++-- .../{xgboostclassifier.py => xgboost_classifier.py} | 0 2 files changed, 5 insertions(+), 2 deletions(-) rename machine_learning/{xgboostclassifier.py => xgboost_classifier.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 70644d0639dc..3fd1a3c383d7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -461,7 +461,8 @@ * [Similarity Search](machine_learning/similarity_search.py) * [Support Vector Machines](machine_learning/support_vector_machines.py) * [Word Frequency Functions](machine_learning/word_frequency_functions.py) - * [Xgboostclassifier](machine_learning/xgboostclassifier.py) + * [Xgboost Classifier](machine_learning/xgboost_classifier.py) + * [Xgboost Regressor](machine_learning/xgboost_regressor.py) ## Maths * [3N Plus 1](maths/3n_plus_1.py) @@ -536,7 +537,7 @@ * [Line Length](maths/line_length.py) * [Lucas Lehmer Primality Test](maths/lucas_lehmer_primality_test.py) * [Lucas Series](maths/lucas_series.py) - * [Maclaurin Sin](maths/maclaurin_sin.py) + * [Maclaurin Series](maths/maclaurin_series.py) * [Matrix Exponentiation](maths/matrix_exponentiation.py) * [Max Sum Sliding Window](maths/max_sum_sliding_window.py) * [Median Of Two Arrays](maths/median_of_two_arrays.py) @@ -582,6 +583,7 @@ * [P Series](maths/series/p_series.py) * [Sieve Of Eratosthenes](maths/sieve_of_eratosthenes.py) * [Sigmoid](maths/sigmoid.py) + * [Signum](maths/signum.py) * [Simpson Rule](maths/simpson_rule.py) * [Sin](maths/sin.py) * [Sock Merchant](maths/sock_merchant.py) @@ -590,6 +592,7 @@ * [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py) * [Sum Of Digits](maths/sum_of_digits.py) * [Sum Of Geometric Progression](maths/sum_of_geometric_progression.py) + * [Sum Of Harmonic Series](maths/sum_of_harmonic_series.py) * [Sylvester Sequence](maths/sylvester_sequence.py) * [Test Prime Check](maths/test_prime_check.py) * [Trapezoidal Rule](maths/trapezoidal_rule.py) diff --git a/machine_learning/xgboostclassifier.py b/machine_learning/xgboost_classifier.py similarity index 100% rename from machine_learning/xgboostclassifier.py rename to machine_learning/xgboost_classifier.py From 0f06a0b5ff43c4cfa98db33926d21ce688b69a10 Mon Sep 17 00:00:00 2001 From: Sagar Giri Date: Sun, 23 Oct 2022 23:35:27 +0900 Subject: [PATCH 1939/2908] Add web program to fetch top 10 real time billionaires using the forbes API. (#7538) * Add web program to fetch top 10 realtime billioners using forbes API. * Provide return type to function. * Use rich for tables and minor refactors. * Fix tiny typo. * Add the top {LIMIT} in rich table title. * Update web_programming/get_top_billioners.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Change the API path. * Update get_top_billioners.py Co-authored-by: Caeden Perelli-Harris Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- requirements.txt | 1 + web_programming/get_top_billioners.py | 84 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 web_programming/get_top_billioners.py diff --git a/requirements.txt b/requirements.txt index 25d2b4ef93d5..9ffe784c945d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ pillow projectq qiskit requests +rich scikit-fuzzy sklearn statsmodels diff --git a/web_programming/get_top_billioners.py b/web_programming/get_top_billioners.py new file mode 100644 index 000000000000..514ea1db9789 --- /dev/null +++ b/web_programming/get_top_billioners.py @@ -0,0 +1,84 @@ +""" +CAUTION: You may get a json.decoding error. This works for some of us but fails for others. +""" + +from datetime import datetime + +import requests +from rich import box +from rich import console as rich_console +from rich import table as rich_table + +LIMIT = 10 +TODAY = datetime.now() + +API_URL = ( + "https://www.forbes.com/forbesapi/person/rtb/0/position/true.json" + "?fields=personName,gender,source,countryOfCitizenship,birthDate,finalWorth" + f"&limit={LIMIT}" +) + + +def calculate_age(unix_date: int) -> str: + """Calculates age from given unix time format. + + Returns: + Age as string + + >>> calculate_age(-657244800000) + '73' + >>> calculate_age(46915200000) + '51' + """ + birthdate = datetime.fromtimestamp(unix_date / 1000).date() + return str( + TODAY.year + - birthdate.year + - ((TODAY.month, TODAY.day) < (birthdate.month, birthdate.day)) + ) + + +def get_forbes_real_time_billionaires() -> list[dict[str, str]]: + """Get top 10 realtime billionaires using forbes API. + + Returns: + List of top 10 realtime billionaires data. + """ + response_json = requests.get(API_URL).json() + return [ + { + "Name": person["personName"], + "Source": person["source"], + "Country": person["countryOfCitizenship"], + "Gender": person["gender"], + "Worth ($)": f"{person['finalWorth'] / 1000:.1f} Billion", + "Age": calculate_age(person["birthDate"]), + } + for person in response_json["personList"]["personsLists"] + ] + + +def display_billionaires(forbes_billionaires: list[dict[str, str]]) -> None: + """Display Forbes real time billionaires in a rich table. + + Args: + forbes_billionaires (list): Forbes top 10 real time billionaires + """ + + table = rich_table.Table( + title=f"Forbes Top {LIMIT} Real Time Billionaires at {TODAY:%Y-%m-%d %H:%M}", + style="green", + highlight=True, + box=box.SQUARE, + ) + for key in forbes_billionaires[0]: + table.add_column(key) + + for billionaire in forbes_billionaires: + table.add_row(*billionaire.values()) + + rich_console.Console().print(table) + + +if __name__ == "__main__": + display_billionaires(get_forbes_real_time_billionaires()) From 393b9605259fe19e03bdaac2b0866151e1a2afc2 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 23 Oct 2022 15:36:10 +0100 Subject: [PATCH 1940/2908] refactor: Replace doctest traceback with `...` (#7558) --- conversions/pressure_conversions.py | 6 +----- electronics/carrier_concentration.py | 8 ++++---- electronics/electric_power.py | 6 +++--- maths/nevilles_method.py | 3 +-- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/conversions/pressure_conversions.py b/conversions/pressure_conversions.py index 2018080b9327..e0cd18d234ba 100644 --- a/conversions/pressure_conversions.py +++ b/conversions/pressure_conversions.py @@ -56,11 +56,7 @@ def pressure_conversion(value: float, from_type: str, to_type: str) -> float: 0.019336718261000002 >>> pressure_conversion(4, "wrongUnit", "atm") Traceback (most recent call last): - File "/usr/lib/python3.8/doctest.py", line 1336, in __run - exec(compile(example.source, filename, "single", - File "", line 1, in - pressure_conversion(4, "wrongUnit", "atm") - File "", line 67, in pressure_conversion + ... ValueError: Invalid 'from_type' value: 'wrongUnit' Supported values are: atm, pascal, bar, kilopascal, megapascal, psi, inHg, torr """ diff --git a/electronics/carrier_concentration.py b/electronics/carrier_concentration.py index 03482f1e336e..1fb9f2430dcd 100644 --- a/electronics/carrier_concentration.py +++ b/electronics/carrier_concentration.py @@ -25,19 +25,19 @@ def carrier_concentration( ('hole_conc', 1440.0) >>> carrier_concentration(electron_conc=1000, hole_conc=400, intrinsic_conc=1200) Traceback (most recent call last): - File "", line 37, in + ... ValueError: You cannot supply more or less than 2 values >>> carrier_concentration(electron_conc=-1000, hole_conc=0, intrinsic_conc=1200) Traceback (most recent call last): - File "", line 40, in + ... ValueError: Electron concentration cannot be negative in a semiconductor >>> carrier_concentration(electron_conc=0, hole_conc=-400, intrinsic_conc=1200) Traceback (most recent call last): - File "", line 44, in + ... ValueError: Hole concentration cannot be negative in a semiconductor >>> carrier_concentration(electron_conc=0, hole_conc=400, intrinsic_conc=-1200) Traceback (most recent call last): - File "", line 48, in + ... ValueError: Intrinsic concentration cannot be negative in a semiconductor """ if (electron_conc, hole_conc, intrinsic_conc).count(0) != 1: diff --git a/electronics/electric_power.py b/electronics/electric_power.py index ac673d7e3a94..e59795601791 100644 --- a/electronics/electric_power.py +++ b/electronics/electric_power.py @@ -17,15 +17,15 @@ def electric_power(voltage: float, current: float, power: float) -> tuple: 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) diff --git a/maths/nevilles_method.py b/maths/nevilles_method.py index 5583e4269b32..1f48b43fbd22 100644 --- a/maths/nevilles_method.py +++ b/maths/nevilles_method.py @@ -31,8 +31,7 @@ def neville_interpolate(x_points: list, y_points: list, x0: int) -> list: 104.0 >>> neville_interpolate((1,2,3,4,6), (6,7,8,9,11), '') Traceback (most recent call last): - File "", line 1, in - ... + ... TypeError: unsupported operand type(s) for -: 'str' and 'int' """ n = len(x_points) From 10b6e7a658c4664ce823cc1d0f159cd717b506db Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 23 Oct 2022 16:14:45 +0100 Subject: [PATCH 1941/2908] fix: Fix line too long in doctest (#7566) --- web_programming/get_top_billioners.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_programming/get_top_billioners.py b/web_programming/get_top_billioners.py index 514ea1db9789..6a8054e26270 100644 --- a/web_programming/get_top_billioners.py +++ b/web_programming/get_top_billioners.py @@ -1,5 +1,6 @@ """ -CAUTION: You may get a json.decoding error. This works for some of us but fails for others. +CAUTION: You may get a json.decoding error. +This works for some of us but fails for others. """ from datetime import datetime From 0dc95c0a6be06f33153e8fcd84d2c854dac7a353 Mon Sep 17 00:00:00 2001 From: SwayamSahu <91021799+SwayamSahu@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:30:59 +0530 Subject: [PATCH 1942/2908] Update comments in check_pangram.py script (#7564) * Update comments in check_pangram.py script * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename check_pangram.py to is_pangram.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- strings/check_pangram.py | 74 ------------------------------- strings/is_pangram.py | 95 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 74 deletions(-) delete mode 100644 strings/check_pangram.py create mode 100644 strings/is_pangram.py diff --git a/strings/check_pangram.py b/strings/check_pangram.py deleted file mode 100644 index 81384bfd4cc6..000000000000 --- a/strings/check_pangram.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -wiki: https://en.wikipedia.org/wiki/Pangram -""" - - -def check_pangram( - input_str: str = "The quick brown fox jumps over the lazy dog", -) -> bool: - """ - 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( - " ", "" - ) # 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 - - -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_faster("Waltz, bad nymph, for quick jigs vex.") - True - >>> check_pangram_faster("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) - 97] = True - elif char.isupper(): - flag[ord(char) - 65] = 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() diff --git a/strings/is_pangram.py b/strings/is_pangram.py new file mode 100644 index 000000000000..c8b894b7ea31 --- /dev/null +++ b/strings/is_pangram.py @@ -0,0 +1,95 @@ +""" +wiki: https://en.wikipedia.org/wiki/Pangram +""" + + +def is_pangram( + input_str: str = "The quick brown fox jumps over the lazy dog", +) -> bool: + """ + A Pangram String contains all the alphabets at least once. + >>> is_pangram("The quick brown fox jumps over the lazy dog") + True + >>> is_pangram("Waltz, bad nymph, for quick jigs vex.") + True + >>> is_pangram("Jived fox nymph grabs quick waltz.") + True + >>> is_pangram("My name is Unknown") + False + >>> is_pangram("The quick brown fox jumps over the la_y dog") + False + >>> is_pangram() + True + """ + # Declare frequency as a set to have unique occurrences of letters + frequency = set() + + # Replace all the whitespace in our sentence + input_str = input_str.replace(" ", "") + for alpha in input_str: + if "a" <= alpha.lower() <= "z": + frequency.add(alpha.lower()) + return len(frequency) == 26 + + +def is_pangram_faster( + input_str: str = "The quick brown fox jumps over the lazy dog", +) -> bool: + """ + >>> is_pangram_faster("The quick brown fox jumps over the lazy dog") + True + >>> is_pangram_faster("Waltz, bad nymph, for quick jigs vex.") + True + >>> is_pangram_faster("Jived fox nymph grabs quick waltz.") + True + >>> is_pangram_faster("The quick brown fox jumps over the la_y dog") + False + >>> is_pangram_faster() + True + """ + flag = [False] * 26 + for char in input_str: + if char.islower(): + flag[ord(char) - 97] = True + elif char.isupper(): + flag[ord(char) - 65] = True + return all(flag) + + +def is_pangram_fastest( + input_str: str = "The quick brown fox jumps over the lazy dog", +) -> bool: + """ + >>> is_pangram_fastest("The quick brown fox jumps over the lazy dog") + True + >>> is_pangram_fastest("Waltz, bad nymph, for quick jigs vex.") + True + >>> is_pangram_fastest("Jived fox nymph grabs quick waltz.") + True + >>> is_pangram_fastest("The quick brown fox jumps over the la_y dog") + False + >>> is_pangram_fastest() + True + """ + return len({char for char in input_str.lower() if char.isalpha()}) == 26 + + +def benchmark() -> None: + """ + Benchmark code comparing different version. + """ + from timeit import timeit + + setup = "from __main__ import is_pangram, is_pangram_faster, is_pangram_fastest" + print(timeit("is_pangram()", setup=setup)) + print(timeit("is_pangram_faster()", setup=setup)) + print(timeit("is_pangram_fastest()", setup=setup)) + # 5.348480500048026, 2.6477354579837993, 1.8470395830227062 + # 5.036091582966037, 2.644472333951853, 1.8869528750656173 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + benchmark() From b8b63469efff57b8cb3c6e4aec4279c8e864b8db Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 23 Oct 2022 18:12:49 +0200 Subject: [PATCH 1943/2908] My favorite palindrome (#7455) * My favorite palindrome * updating DIRECTORY.md * Update is_palindrome.py * Update is_palindrome.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update strings/is_palindrome.py Co-authored-by: Caeden Perelli-Harris * Update is_palindrome.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris --- strings/is_palindrome.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/strings/is_palindrome.py b/strings/is_palindrome.py index 4776a5fc29c4..5758af0cef9b 100644 --- a/strings/is_palindrome.py +++ b/strings/is_palindrome.py @@ -1,9 +1,8 @@ def is_palindrome(s: str) -> bool: """ - Determine whether the string is palindrome - :param s: - :return: Boolean - >>> is_palindrome("a man a plan a canal panama".replace(" ", "")) + Determine if the string s is a palindrome. + + >>> is_palindrome("A man, A plan, A canal -- Panama!") True >>> is_palindrome("Hello") False @@ -14,15 +13,15 @@ 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. - s = "".join([character for character in s.lower() if character.isalnum()]) + # Since punctuation, capitalization, and spaces are often ignored while checking + # palindromes, we first remove them from our string. + s = "".join(character for character in s.lower() if character.isalnum()) return s == s[::-1] if __name__ == "__main__": - s = input("Enter string to determine whether its palindrome or not: ").strip() + s = input("Please enter a string to see if it is a palindrome: ") if is_palindrome(s): - print("Given string is palindrome") + print(f"'{s}' is a palindrome.") else: - print("Given string is not palindrome") + print(f"'{s}' is not a palindrome.") From 39a99b46f5e9b2c56951c22189a8ac3ea0730b01 Mon Sep 17 00:00:00 2001 From: Laukik Chahande <103280327+luciferx48@users.noreply.github.com> Date: Sun, 23 Oct 2022 22:56:22 +0530 Subject: [PATCH 1944/2908] check whether integer is even or odd using bit manupulation (#7099) * even_or_not file added * Updated DIRECTORY.md * modified DIRECTORY.md * Update bit_manipulation/even_or_not.py * updating DIRECTORY.md * Rename even_or_not.py to is_even.py * updating DIRECTORY.md Co-authored-by: luciferx48 Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + bit_manipulation/is_even.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 bit_manipulation/is_even.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 3fd1a3c383d7..10e78a92c00f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -45,6 +45,7 @@ * [Count 1S Brian Kernighan Method](bit_manipulation/count_1s_brian_kernighan_method.py) * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py) * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py) + * [Is Even](bit_manipulation/is_even.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py) diff --git a/bit_manipulation/is_even.py b/bit_manipulation/is_even.py new file mode 100644 index 000000000000..b7b0841a1427 --- /dev/null +++ b/bit_manipulation/is_even.py @@ -0,0 +1,37 @@ +def is_even(number: int) -> bool: + """ + return true if the input integer is even + Explanation: Lets take a look at the following deicmal to binary conversions + 2 => 10 + 14 => 1110 + 100 => 1100100 + 3 => 11 + 13 => 1101 + 101 => 1100101 + from the above examples we can observe that + for all the odd integers there is always 1 set bit at the end + also, 1 in binary can be represented as 001, 00001, or 0000001 + so for any odd integer n => n&1 is always equlas 1 else the integer is even + + >>> is_even(1) + False + >>> is_even(4) + True + >>> is_even(9) + False + >>> is_even(15) + False + >>> is_even(40) + True + >>> is_even(100) + True + >>> is_even(101) + False + """ + return number & 1 == 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From e2a83b3bc66630cb2667375fba9de5c5baac3aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nadirhan=20=C5=9Eahin?= Date: Sun, 23 Oct 2022 22:28:11 +0300 Subject: [PATCH 1945/2908] Update knapsack.py (#7271) * Update knapsack.py * Update dynamic_programming/knapsack.py Co-authored-by: Christian Clauss * Update knapsack.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/knapsack.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 093e15f49ba0..b12d30313e31 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -1,9 +1,9 @@ """ 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. +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. +using dynamic programming. """ @@ -27,7 +27,7 @@ def mf_knapsack(i, wt, val, j): def knapsack(w, wt, val, n): - dp = [[0 for i in range(w + 1)] for j in range(n + 1)] + dp = [[0] * (w + 1) for _ in range(n + 1)] for i in range(1, n + 1): for w_ in range(1, w + 1): @@ -108,7 +108,7 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set): 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 + 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. @@ -136,7 +136,7 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set): 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)] + f = [[0] * (w + 1)] + [[0] + [-1] * (w + 1) for _ 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 From bd490614a69cc9cdff367cb4a1775dd063c6e617 Mon Sep 17 00:00:00 2001 From: Arjit Arora <42044030+arjitarora26@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:43:01 +0530 Subject: [PATCH 1946/2908] Add function for AND gate (#7593) --- boolean_algebra/and_gate.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 boolean_algebra/and_gate.py diff --git a/boolean_algebra/and_gate.py b/boolean_algebra/and_gate.py new file mode 100644 index 000000000000..cbbcfde79f33 --- /dev/null +++ b/boolean_algebra/and_gate.py @@ -0,0 +1,48 @@ +""" +An AND Gate is a logic gate in boolean algebra which results to 1 (True) if both the +inputs are 1, and 0 (False) otherwise. + +Following is the truth table of an AND Gate: + ------------------------------ + | Input 1 | Input 2 | Output | + ------------------------------ + | 0 | 0 | 0 | + | 0 | 1 | 0 | + | 1 | 0 | 0 | + | 1 | 1 | 1 | + ------------------------------ + +Refer - https://www.geeksforgeeks.org/logic-gates-in-python/ +""" + + +def and_gate(input_1: int, input_2: int) -> int: + """ + Calculate AND of the input values + + >>> and_gate(0, 0) + 0 + >>> and_gate(0, 1) + 0 + >>> and_gate(1, 0) + 0 + >>> and_gate(1, 1) + 1 + """ + return int((input_1, input_2).count(0) == 0) + + +def test_and_gate() -> None: + """ + Tests the and_gate function + """ + assert and_gate(0, 0) == 0 + assert and_gate(0, 1) == 0 + assert and_gate(1, 0) == 0 + assert and_gate(1, 1) == 1 + + +if __name__ == "__main__": + print(and_gate(0, 0)) + print(and_gate(0, 1)) + print(and_gate(1, 1)) From bb078541dd030b4957ee1b5ac87b7a31bf1a7235 Mon Sep 17 00:00:00 2001 From: JatinR05 <71865805+JatinR05@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:43:39 +0530 Subject: [PATCH 1947/2908] Update count_number_of_one_bits.py (#7589) * Update count_number_of_one_bits.py removed the modulo operator as it is very time consuming in comparison to the and operator * Update count_number_of_one_bits.py Updated with the timeit library to compare. Moreover I have updated my code which helps us in reaching the output comparatively faster. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bit_manipulation/count_number_of_one_bits.py Co-authored-by: Christian Clauss * Update count_number_of_one_bits.py Updated the code * Update count_number_of_one_bits.py Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Run the tests before running the benchmarks * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * consistently Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- bit_manipulation/count_number_of_one_bits.py | 79 +++++++++++++++++--- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/bit_manipulation/count_number_of_one_bits.py b/bit_manipulation/count_number_of_one_bits.py index 51fd2b630483..a1687503a383 100644 --- a/bit_manipulation/count_number_of_one_bits.py +++ b/bit_manipulation/count_number_of_one_bits.py @@ -1,34 +1,91 @@ -def get_set_bits_count(number: int) -> int: +from timeit import timeit + + +def get_set_bits_count_using_brian_kernighans_algorithm(number: int) -> int: """ Count the number of set bits in a 32 bit integer - >>> get_set_bits_count(25) + >>> get_set_bits_count_using_brian_kernighans_algorithm(25) 3 - >>> get_set_bits_count(37) + >>> get_set_bits_count_using_brian_kernighans_algorithm(37) 3 - >>> get_set_bits_count(21) + >>> get_set_bits_count_using_brian_kernighans_algorithm(21) 3 - >>> get_set_bits_count(58) + >>> get_set_bits_count_using_brian_kernighans_algorithm(58) 4 - >>> get_set_bits_count(0) + >>> get_set_bits_count_using_brian_kernighans_algorithm(0) 0 - >>> get_set_bits_count(256) + >>> get_set_bits_count_using_brian_kernighans_algorithm(256) 1 - >>> get_set_bits_count(-1) + >>> get_set_bits_count_using_brian_kernighans_algorithm(-1) Traceback (most recent call last): ... - ValueError: the value of input must be positive + ValueError: the value of input must not be negative """ if number < 0: - raise ValueError("the value of input must be positive") + raise ValueError("the value of input must not be negative") + result = 0 + while number: + number &= number - 1 + result += 1 + return result + + +def get_set_bits_count_using_modulo_operator(number: int) -> int: + """ + Count the number of set bits in a 32 bit integer + >>> get_set_bits_count_using_modulo_operator(25) + 3 + >>> get_set_bits_count_using_modulo_operator(37) + 3 + >>> get_set_bits_count_using_modulo_operator(21) + 3 + >>> get_set_bits_count_using_modulo_operator(58) + 4 + >>> get_set_bits_count_using_modulo_operator(0) + 0 + >>> get_set_bits_count_using_modulo_operator(256) + 1 + >>> get_set_bits_count_using_modulo_operator(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must not be negative + """ + if number < 0: + raise ValueError("the value of input must not be negative") result = 0 while number: if number % 2 == 1: result += 1 - number = number >> 1 + number >>= 1 return result +def benchmark() -> None: + """ + Benchmark code for comparing 2 functions, with different length int values. + Brian Kernighan's algorithm is consistently faster than using modulo_operator. + """ + + def do_benchmark(number: int) -> None: + setup = "import __main__ as z" + print(f"Benchmark when {number = }:") + print(f"{get_set_bits_count_using_modulo_operator(number) = }") + timing = timeit("z.get_set_bits_count_using_modulo_operator(25)", setup=setup) + print(f"timeit() runs in {timing} seconds") + print(f"{get_set_bits_count_using_brian_kernighans_algorithm(number) = }") + timing = timeit( + "z.get_set_bits_count_using_brian_kernighans_algorithm(25)", + setup=setup, + ) + print(f"timeit() runs in {timing} seconds") + + for number in (25, 37, 58, 0): + do_benchmark(number) + print() + + if __name__ == "__main__": import doctest doctest.testmod() + benchmark() From d8ab8a0a0ebcb05783c93fe4ed04a940fc0b857f Mon Sep 17 00:00:00 2001 From: Carlos Villar Date: Mon, 24 Oct 2022 13:33:56 +0200 Subject: [PATCH 1948/2908] Add Spain National ID validator (#7574) (#7575) * Add Spain National ID validator (#7574) * is_spain_national_id() * Update is_spain_national_id.py * Some systems add a dash Co-authored-by: Christian Clauss --- strings/is_spain_national_id.py | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 strings/is_spain_national_id.py diff --git a/strings/is_spain_national_id.py b/strings/is_spain_national_id.py new file mode 100644 index 000000000000..67f49755f412 --- /dev/null +++ b/strings/is_spain_national_id.py @@ -0,0 +1,72 @@ +NUMBERS_PLUS_LETTER = "Input must be a string of 8 numbers plus letter" +LOOKUP_LETTERS = "TRWAGMYFPDXBNJZSQVHLCKE" + + +def is_spain_national_id(spanish_id: str) -> bool: + """ + Spain National Id is a string composed by 8 numbers plus a letter + The letter in fact is not part of the ID, it acts as a validator, + checking you didn't do a mistake when entering it on a system or + are giving a fake one. + + https://en.wikipedia.org/wiki/Documento_Nacional_de_Identidad_(Spain)#Number + + >>> is_spain_national_id("12345678Z") + True + >>> is_spain_national_id("12345678z") # It is case-insensitive + True + >>> is_spain_national_id("12345678x") + False + >>> is_spain_national_id("12345678I") + False + >>> is_spain_national_id("12345678-Z") # Some systems add a dash + True + >>> is_spain_national_id("12345678") + Traceback (most recent call last): + ... + ValueError: Input must be a string of 8 numbers plus letter + >>> is_spain_national_id("123456709") + Traceback (most recent call last): + ... + ValueError: Input must be a string of 8 numbers plus letter + >>> is_spain_national_id("1234567--Z") + Traceback (most recent call last): + ... + ValueError: Input must be a string of 8 numbers plus letter + >>> is_spain_national_id("1234Z") + Traceback (most recent call last): + ... + ValueError: Input must be a string of 8 numbers plus letter + >>> is_spain_national_id("1234ZzZZ") + Traceback (most recent call last): + ... + ValueError: Input must be a string of 8 numbers plus letter + >>> is_spain_national_id(12345678) + Traceback (most recent call last): + ... + TypeError: Expected string as input, found int + """ + + if not isinstance(spanish_id, str): + raise TypeError(f"Expected string as input, found {type(spanish_id).__name__}") + + spanish_id_clean = spanish_id.replace("-", "").upper() + if len(spanish_id_clean) != 9: + raise ValueError(NUMBERS_PLUS_LETTER) + + try: + number = int(spanish_id_clean[0:8]) + letter = spanish_id_clean[8] + except ValueError as ex: + raise ValueError(NUMBERS_PLUS_LETTER) from ex + + if letter.isdigit(): + raise ValueError(NUMBERS_PLUS_LETTER) + + return letter == LOOKUP_LETTERS[number % 23] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a041b64f7aaf7dd54f154ba1fb5cd10e3110c1eb Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 24 Oct 2022 16:29:49 +0300 Subject: [PATCH 1949/2908] feat: add Project Euler problem 073 solution 1 (#6273) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 2 ++ project_euler/problem_073/__init__.py | 0 project_euler/problem_073/sol1.py | 46 +++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 project_euler/problem_073/__init__.py create mode 100644 project_euler/problem_073/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 10e78a92c00f..16e6b7ae3e3e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -839,6 +839,8 @@ * Problem 072 * [Sol1](project_euler/problem_072/sol1.py) * [Sol2](project_euler/problem_072/sol2.py) + * Problem 073 + * [Sol1](project_euler/problem_073/sol1.py) * Problem 074 * [Sol1](project_euler/problem_074/sol1.py) * [Sol2](project_euler/problem_074/sol2.py) diff --git a/project_euler/problem_073/__init__.py b/project_euler/problem_073/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_073/sol1.py b/project_euler/problem_073/sol1.py new file mode 100644 index 000000000000..2b66b7d8769b --- /dev/null +++ b/project_euler/problem_073/sol1.py @@ -0,0 +1,46 @@ +""" +Project Euler Problem 73: https://projecteuler.net/problem=73 + +Consider the fraction, n/d, where n and d are positive integers. +If n int: + """ + Returns number of fractions lie between 1/3 and 1/2 in the sorted set + of reduced proper fractions for d ≤ max_d + + >>> solution(4) + 0 + + >>> solution(5) + 1 + + >>> solution(8) + 3 + """ + + fractions_number = 0 + for d in range(max_d + 1): + for n in range(d // 3 + 1, (d + 1) // 2): + if gcd(n, d) == 1: + fractions_number += 1 + return fractions_number + + +if __name__ == "__main__": + print(f"{solution() = }") From d407476531dd85db79e58aa2dd13d3b3031d8185 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 25 Oct 2022 03:57:03 +0300 Subject: [PATCH 1950/2908] fix: increase str conversion limit where required (#7604) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 8 +++++++- project_euler/problem_104/{sol.py.FIXME => sol1.py} | 10 +++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) rename project_euler/problem_104/{sol.py.FIXME => sol1.py} (95%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 16e6b7ae3e3e..3e722a8784e5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -55,6 +55,7 @@ * [Modular Division](blockchain/modular_division.py) ## Boolean Algebra + * [And Gate](boolean_algebra/and_gate.py) * [Norgate](boolean_algebra/norgate.py) * [Quine Mc Cluskey](boolean_algebra/quine_mc_cluskey.py) @@ -876,6 +877,8 @@ * [Sol1](project_euler/problem_101/sol1.py) * Problem 102 * [Sol1](project_euler/problem_102/sol1.py) + * Problem 104 + * [Sol1](project_euler/problem_104/sol1.py) * Problem 107 * [Sol1](project_euler/problem_107/sol1.py) * Problem 109 @@ -948,6 +951,7 @@ * [Quantum Random](quantum/quantum_random.py) * [Ripple Adder Classic](quantum/ripple_adder_classic.py) * [Single Qubit Measure](quantum/single_qubit_measure.py) + * [Superdense Coding](quantum/superdense_coding.py) ## Scheduling * [First Come First Served](scheduling/first_come_first_served.py) @@ -1037,7 +1041,6 @@ * [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py) * [Capitalize](strings/capitalize.py) * [Check Anagrams](strings/check_anagrams.py) - * [Check Pangram](strings/check_pangram.py) * [Credit Card Validator](strings/credit_card_validator.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py) * [Dna](strings/dna.py) @@ -1046,6 +1049,8 @@ * [Indian Phone Validator](strings/indian_phone_validator.py) * [Is Contains Unique Chars](strings/is_contains_unique_chars.py) * [Is Palindrome](strings/is_palindrome.py) + * [Is Pangram](strings/is_pangram.py) + * [Is Spain National Id](strings/is_spain_national_id.py) * [Jaro Winkler](strings/jaro_winkler.py) * [Join](strings/join.py) * [Knuth Morris Pratt](strings/knuth_morris_pratt.py) @@ -1090,6 +1095,7 @@ * [Fetch Well Rx Price](web_programming/fetch_well_rx_price.py) * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](web_programming/get_imdbtop.py) + * [Get Top Billioners](web_programming/get_top_billioners.py) * [Get Top Hn Posts](web_programming/get_top_hn_posts.py) * [Get User Tweets](web_programming/get_user_tweets.py) * [Giphy](web_programming/giphy.py) diff --git a/project_euler/problem_104/sol.py.FIXME b/project_euler/problem_104/sol1.py similarity index 95% rename from project_euler/problem_104/sol.py.FIXME rename to project_euler/problem_104/sol1.py index 0818ac401c3a..60fd6fe99adb 100644 --- a/project_euler/problem_104/sol.py.FIXME +++ b/project_euler/problem_104/sol1.py @@ -13,6 +13,10 @@ the last nine digits are 1-9 pandigital, find k. """ +import sys + +sys.set_int_max_str_digits(0) # type: ignore + def check(number: int) -> bool: """ @@ -34,7 +38,7 @@ def check(number: int) -> bool: check_front = [0] * 11 # mark last 9 numbers - for x in range(9): + for _ in range(9): check_last[int(number % 10)] = 1 number = number // 10 # flag @@ -51,7 +55,7 @@ def check(number: int) -> bool: # mark first 9 numbers number = int(str(number)[:9]) - for x in range(9): + for _ in range(9): check_front[int(number % 10)] = 1 number = number // 10 @@ -81,7 +85,7 @@ def check1(number: int) -> bool: check_last = [0] * 11 # mark last 9 numbers - for x in range(9): + for _ in range(9): check_last[int(number % 10)] = 1 number = number // 10 # flag From a662d96196d58c2415d6a6933fa78a59996cc3fa Mon Sep 17 00:00:00 2001 From: Arjit Arora <42044030+arjitarora26@users.noreply.github.com> Date: Wed, 26 Oct 2022 00:56:53 +0530 Subject: [PATCH 1951/2908] Add function for xor gate (#7588) * Add function for xor gate * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add test case for xor functions * Update boolean_algebra/xor_gate.py Co-authored-by: Christian Clauss * Update boolean_algebra/xor_gate.py Co-authored-by: Christian Clauss * Split long comment line into two lines * 88 characters per line Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- boolean_algebra/xor_gate.py | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 boolean_algebra/xor_gate.py diff --git a/boolean_algebra/xor_gate.py b/boolean_algebra/xor_gate.py new file mode 100644 index 000000000000..db4f5b45c3c6 --- /dev/null +++ b/boolean_algebra/xor_gate.py @@ -0,0 +1,46 @@ +""" +A XOR Gate is a logic gate in boolean algebra which results to 1 (True) if only one of +the two inputs is 1, and 0 (False) if an even number of inputs are 1. +Following is the truth table of a XOR Gate: + ------------------------------ + | Input 1 | Input 2 | Output | + ------------------------------ + | 0 | 0 | 0 | + | 0 | 1 | 1 | + | 1 | 0 | 1 | + | 1 | 1 | 0 | + ------------------------------ + +Refer - https://www.geeksforgeeks.org/logic-gates-in-python/ +""" + + +def xor_gate(input_1: int, input_2: int) -> int: + """ + calculate xor of the input values + + >>> xor_gate(0, 0) + 0 + >>> xor_gate(0, 1) + 1 + >>> xor_gate(1, 0) + 1 + >>> xor_gate(1, 1) + 0 + """ + return (input_1, input_2).count(0) % 2 + + +def test_xor_gate() -> None: + """ + Tests the xor_gate function + """ + assert xor_gate(0, 0) == 0 + assert xor_gate(0, 1) == 1 + assert xor_gate(1, 0) == 1 + assert xor_gate(1, 1) == 0 + + +if __name__ == "__main__": + print(xor_gate(0, 0)) + print(xor_gate(0, 1)) From cbdbe07ffd07619f1c3c5ab63ae6b2775e3c235d Mon Sep 17 00:00:00 2001 From: SparshRastogi <75373475+SparshRastogi@users.noreply.github.com> Date: Wed, 26 Oct 2022 01:13:02 +0530 Subject: [PATCH 1952/2908] Create kinetic_energy.py (#7620) * Create kinetic_energy.py Finding the kinetic energy of an object,by taking its mass and velocity as input * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update kinetic_energy.py * Update kinetic_energy.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- physics/kinetic_energy.py | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 physics/kinetic_energy.py diff --git a/physics/kinetic_energy.py b/physics/kinetic_energy.py new file mode 100644 index 000000000000..535ffc219251 --- /dev/null +++ b/physics/kinetic_energy.py @@ -0,0 +1,47 @@ +""" +Find the kinetic energy of an object, give its mass and velocity +Description : In physics, the kinetic energy of an object is the energy that it +possesses due to its motion. It is defined as the work needed to accelerate a body of a +given mass from rest to its stated velocity. Having gained this energy during its +acceleration, the body maintains this kinetic energy unless its speed changes. The same +amount of work is done by the body when decelerating from its current speed to a state +of rest. Formally, a kinetic energy is any term in a system's Lagrangian which includes +a derivative with respect to time. + +In classical mechanics, the kinetic energy of a non-rotating object of mass m traveling +at a speed v is ½mv². In relativistic mechanics, this is a good approximation only when +v is much less than the speed of light. The standard unit of kinetic energy is the +joule, while the English unit of kinetic energy is the foot-pound. + +Reference : https://en.m.wikipedia.org/wiki/Kinetic_energy +""" + + +def kinetic_energy(mass: float, velocity: float) -> float: + """ + The kinetic energy of a non-rotating object of mass m traveling at a speed v is ½mv² + + >>> kinetic_energy(10,10) + 500.0 + >>> kinetic_energy(0,10) + 0.0 + >>> kinetic_energy(10,0) + 0.0 + >>> kinetic_energy(20,-20) + 4000.0 + >>> kinetic_energy(0,0) + 0.0 + >>> kinetic_energy(2,2) + 4.0 + >>> kinetic_energy(100,100) + 500000.0 + """ + if mass < 0: + raise ValueError("The mass of a body cannot be negative") + return 0.5 * mass * abs(velocity) * abs(velocity) + + +if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) From 450842321d30ab072a84aee15dfdbf199f9914dc Mon Sep 17 00:00:00 2001 From: Havish <100441982+havishs9@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:47:52 -0700 Subject: [PATCH 1953/2908] Arc Length Algorithm (#7610) * Create decimal_conversions.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Create arc_length.py * Delete decimal_conversions.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed redundant statement, fixed line overflow * Update arc_length.py Changed rad to radius as not to get confused with radians * Update arc_length.py * Update arc_length.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> --- maths/arc_length.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 maths/arc_length.py diff --git a/maths/arc_length.py b/maths/arc_length.py new file mode 100644 index 000000000000..9e87ca38cc7d --- /dev/null +++ b/maths/arc_length.py @@ -0,0 +1,15 @@ +from math import pi + + +def arc_length(angle: int, radius: int) -> float: + """ + >>> arc_length(45, 5) + 3.9269908169872414 + >>> arc_length(120, 15) + 31.415926535897928 + """ + return 2 * pi * radius * (angle / 360) + + +if __name__ == "__main__": + print(arc_length(90, 10)) From 103c9e0876490d6cf683ba2d3f89e5198647bc32 Mon Sep 17 00:00:00 2001 From: Karthik S <73390717+karthiks2611@users.noreply.github.com> Date: Wed, 26 Oct 2022 01:23:21 +0530 Subject: [PATCH 1954/2908] Added Implementation of NAND, OR ,XNOR and NOT gates in python (#7596) * Added Implementation for XNOR gate * Added Implementation for OR gate * Added implementation of NAND gate * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added Implementation of NAND gate * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated nand_gate.py * updated xnor_gate.py after some changes * Delete due to duplicate file * Updated xnor_gate.py * Added Implementation of NOT gate in python * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed a typo error * Updated to a new logic * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated nand_gate.py file Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- boolean_algebra/nand_gate.py | 47 +++++++++++++++++++++++++++++++++++ boolean_algebra/not_gate.py | 37 +++++++++++++++++++++++++++ boolean_algebra/or_gate.py | 46 ++++++++++++++++++++++++++++++++++ boolean_algebra/xnor_gate.py | 48 ++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 boolean_algebra/nand_gate.py create mode 100644 boolean_algebra/not_gate.py create mode 100644 boolean_algebra/or_gate.py create mode 100644 boolean_algebra/xnor_gate.py diff --git a/boolean_algebra/nand_gate.py b/boolean_algebra/nand_gate.py new file mode 100644 index 000000000000..ea3303d16b25 --- /dev/null +++ b/boolean_algebra/nand_gate.py @@ -0,0 +1,47 @@ +""" +A NAND Gate is a logic gate in boolean algebra which results to 0 (False) if both +the inputs are 1, and 1 (True) otherwise. It's similar to adding +a NOT gate along with an AND gate. +Following is the truth table of a NAND Gate: + ------------------------------ + | Input 1 | Input 2 | Output | + ------------------------------ + | 0 | 0 | 1 | + | 0 | 1 | 1 | + | 1 | 0 | 1 | + | 1 | 1 | 0 | + ------------------------------ +Refer - https://www.geeksforgeeks.org/logic-gates-in-python/ +""" + + +def nand_gate(input_1: int, input_2: int) -> int: + """ + Calculate NAND of the input values + >>> nand_gate(0, 0) + 1 + >>> nand_gate(0, 1) + 1 + >>> nand_gate(1, 0) + 1 + >>> nand_gate(1, 1) + 0 + """ + return int((input_1, input_2).count(0) != 0) + + +def test_nand_gate() -> None: + """ + Tests the nand_gate function + """ + assert nand_gate(0, 0) == 1 + assert nand_gate(0, 1) == 1 + assert nand_gate(1, 0) == 1 + assert nand_gate(1, 1) == 0 + + +if __name__ == "__main__": + print(nand_gate(0, 0)) + print(nand_gate(0, 1)) + print(nand_gate(1, 0)) + print(nand_gate(1, 1)) diff --git a/boolean_algebra/not_gate.py b/boolean_algebra/not_gate.py new file mode 100644 index 000000000000..b41da602d936 --- /dev/null +++ b/boolean_algebra/not_gate.py @@ -0,0 +1,37 @@ +""" +A NOT Gate is a logic gate in boolean algebra which results to 0 (False) if the +input is high, and 1 (True) if the input is low. +Following is the truth table of a XOR Gate: + ------------------------------ + | Input | Output | + ------------------------------ + | 0 | 1 | + | 1 | 0 | + ------------------------------ +Refer - https://www.geeksforgeeks.org/logic-gates-in-python/ +""" + + +def not_gate(input_1: int) -> int: + """ + Calculate NOT of the input values + >>> not_gate(0) + 1 + >>> not_gate(1) + 0 + """ + + return 1 if input_1 == 0 else 0 + + +def test_not_gate() -> None: + """ + Tests the not_gate function + """ + assert not_gate(0) == 1 + assert not_gate(1) == 0 + + +if __name__ == "__main__": + print(not_gate(0)) + print(not_gate(1)) diff --git a/boolean_algebra/or_gate.py b/boolean_algebra/or_gate.py new file mode 100644 index 000000000000..aa7e6645e33f --- /dev/null +++ b/boolean_algebra/or_gate.py @@ -0,0 +1,46 @@ +""" +An OR Gate is a logic gate in boolean algebra which results to 0 (False) if both the +inputs are 0, and 1 (True) otherwise. +Following is the truth table of an AND Gate: + ------------------------------ + | Input 1 | Input 2 | Output | + ------------------------------ + | 0 | 0 | 0 | + | 0 | 1 | 1 | + | 1 | 0 | 1 | + | 1 | 1 | 1 | + ------------------------------ +Refer - https://www.geeksforgeeks.org/logic-gates-in-python/ +""" + + +def or_gate(input_1: int, input_2: int) -> int: + """ + Calculate OR of the input values + >>> or_gate(0, 0) + 0 + >>> or_gate(0, 1) + 1 + >>> or_gate(1, 0) + 1 + >>> or_gate(1, 1) + 1 + """ + return int((input_1, input_2).count(1) != 0) + + +def test_or_gate() -> None: + """ + Tests the or_gate function + """ + assert or_gate(0, 0) == 0 + assert or_gate(0, 1) == 1 + assert or_gate(1, 0) == 1 + assert or_gate(1, 1) == 1 + + +if __name__ == "__main__": + print(or_gate(0, 1)) + print(or_gate(1, 0)) + print(or_gate(0, 0)) + print(or_gate(1, 1)) diff --git a/boolean_algebra/xnor_gate.py b/boolean_algebra/xnor_gate.py new file mode 100644 index 000000000000..45ab2700ec35 --- /dev/null +++ b/boolean_algebra/xnor_gate.py @@ -0,0 +1,48 @@ +""" +A XNOR Gate is a logic gate in boolean algebra which results to 0 (False) if both the +inputs are different, and 1 (True), if the inputs are same. +It's similar to adding a NOT gate to an XOR gate + +Following is the truth table of a XNOR Gate: + ------------------------------ + | Input 1 | Input 2 | Output | + ------------------------------ + | 0 | 0 | 1 | + | 0 | 1 | 0 | + | 1 | 0 | 0 | + | 1 | 1 | 1 | + ------------------------------ +Refer - https://www.geeksforgeeks.org/logic-gates-in-python/ +""" + + +def xnor_gate(input_1: int, input_2: int) -> int: + """ + Calculate XOR of the input values + >>> xnor_gate(0, 0) + 1 + >>> xnor_gate(0, 1) + 0 + >>> xnor_gate(1, 0) + 0 + >>> xnor_gate(1, 1) + 1 + """ + return 1 if input_1 == input_2 else 0 + + +def test_xnor_gate() -> None: + """ + Tests the xnor_gate function + """ + assert xnor_gate(0, 0) == 1 + assert xnor_gate(0, 1) == 0 + assert xnor_gate(1, 0) == 0 + assert xnor_gate(1, 1) == 1 + + +if __name__ == "__main__": + print(xnor_gate(0, 0)) + print(xnor_gate(0, 1)) + print(xnor_gate(1, 0)) + print(xnor_gate(1, 1)) From d25187eb7f27227381a03ba800890af7848b57d5 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Tue, 25 Oct 2022 16:34:46 -0400 Subject: [PATCH 1955/2908] Remove type cast in combinations algorithm (#7607) * Remove commented-out print statements in algorithmic functions * Encapsulate non-algorithmic code in __main__ * Remove unused print_matrix function * Remove print statement in __init__ * Remove print statement from doctest * Encapsulate non-algorithmic code in __main__ * Modify algorithm to return instead of print * Encapsulate non-algorithmic code in __main__ * Refactor data_safety_checker to return instead of print * updating DIRECTORY.md * updating DIRECTORY.md * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updating DIRECTORY.md * Remove int cast and change float division to int division * Move new-line chars * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/combinations.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/maths/combinations.py b/maths/combinations.py index 40f4f7a9f850..6db1d773faa6 100644 --- a/maths/combinations.py +++ b/maths/combinations.py @@ -35,18 +35,18 @@ def combinations(n: int, k: int) -> int: # 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)))) + return factorial(n) // (factorial(k) * factorial(n - k)) if __name__ == "__main__": print( - "\nThe number of five-card hands possible from a standard", - f"fifty-two card deck is: {combinations(52, 5)}", + "The number of five-card hands possible from a standard", + f"fifty-two card deck is: {combinations(52, 5)}\n", ) print( - "\nIf a class of 40 students must be arranged into groups of", + "If 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", ) @@ -54,5 +54,5 @@ def combinations(n: int, k: int) -> int: 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", + "third place can be awarded.", ) From 7e3dff17c5046aad1c67fa689e5146a13e8cc052 Mon Sep 17 00:00:00 2001 From: Arya Samik <90042953+AryaSamik@users.noreply.github.com> Date: Wed, 26 Oct 2022 03:12:49 +0530 Subject: [PATCH 1956/2908] Docs: correct the shape of trajectory (#6255) The shape of the trajectory of projectile is a parabola. --- physics/horizontal_projectile_motion.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/physics/horizontal_projectile_motion.py b/physics/horizontal_projectile_motion.py index a747acd72072..dbde3660f62f 100644 --- a/physics/horizontal_projectile_motion.py +++ b/physics/horizontal_projectile_motion.py @@ -3,13 +3,13 @@ This algorithm solves a specific problem in which the motion starts from the ground as can be seen below: (v = 0) - ** - * * - * * - * * - * * - * * -GROUND GROUND + * * + * * + * * + * * + * * + * * +GROUND GROUND For more info: https://en.wikipedia.org/wiki/Projectile_motion """ From 2c959a749163365705a53b049aa1a3e093ee4e7a Mon Sep 17 00:00:00 2001 From: harshyadavcs <108284583+harshyadavcs@users.noreply.github.com> Date: Wed, 26 Oct 2022 03:13:45 +0530 Subject: [PATCH 1957/2908] Update documentation of cnn_classification.py (#7486) * Updated documentation of cnn_classification.py for much better understanding * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update computer_vision/cnn_classification.py Co-authored-by: Caeden Perelli-Harris Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris --- computer_vision/cnn_classification.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/computer_vision/cnn_classification.py b/computer_vision/cnn_classification.py index 6d4f19639c24..59e4556e069b 100644 --- a/computer_vision/cnn_classification.py +++ b/computer_vision/cnn_classification.py @@ -30,9 +30,12 @@ if __name__ == "__main__": # Initialising the CNN + # (Sequential- Building the model layer by layer) classifier = models.Sequential() # Step 1 - Convolution + # Here 64,64 is the length & breadth of dataset images and 3 is for the RGB channel + # (3,3) is the kernel size (filter matrix) classifier.add( layers.Conv2D(32, (3, 3), input_shape=(64, 64, 3), activation="relu") ) From c3bcfbf19d43e20e9145d8968659101c1fd8b747 Mon Sep 17 00:00:00 2001 From: Karthik Ayangar <66073214+kituuu@users.noreply.github.com> Date: Wed, 26 Oct 2022 03:25:31 +0530 Subject: [PATCH 1958/2908] Add Cramer's rule for solving system of linear equations in two variables (#7547) * added script for solving system of linear equations in two variables * implemented all the suggested changes * changed RuntimeError to ValueError * Update matrix/system_of_linear_equation_in_2_variables.py * Update matrix/system_of_linear_equation_in_2_variables.py * Update and rename system_of_linear_equation_in_2_variables.py to cramers_rule_2x2.py Co-authored-by: Christian Clauss --- matrix/cramers_rule_2x2.py | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 matrix/cramers_rule_2x2.py diff --git a/matrix/cramers_rule_2x2.py b/matrix/cramers_rule_2x2.py new file mode 100644 index 000000000000..a635d66fbb6c --- /dev/null +++ b/matrix/cramers_rule_2x2.py @@ -0,0 +1,82 @@ +# https://www.chilimath.com/lessons/advanced-algebra/cramers-rule-with-two-variables +# https://en.wikipedia.org/wiki/Cramer%27s_rule + + +def cramers_rule_2x2(equation1: list[int], equation2: list[int]) -> str: + """ + Solves the system of linear equation in 2 variables. + :param: equation1: list of 3 numbers + :param: equation2: list of 3 numbers + :return: String of result + input format : [a1, b1, d1], [a2, b2, d2] + determinant = [[a1, b1], [a2, b2]] + determinant_x = [[d1, b1], [d2, b2]] + determinant_y = [[a1, d1], [a2, d2]] + + >>> cramers_rule_2x2([2, 3, 0], [5, 1, 0]) + 'Trivial solution. (Consistent system) x = 0 and y = 0' + >>> cramers_rule_2x2([0, 4, 50], [2, 0, 26]) + 'Non-Trivial Solution (Consistent system) x = 13.0, y = 12.5' + >>> cramers_rule_2x2([11, 2, 30], [1, 0, 4]) + 'Non-Trivial Solution (Consistent system) x = 4.0, y = -7.0' + >>> cramers_rule_2x2([4, 7, 1], [1, 2, 0]) + 'Non-Trivial Solution (Consistent system) x = 2.0, y = -1.0' + + >>> cramers_rule_2x2([1, 2, 3], [2, 4, 6]) + Traceback (most recent call last): + ... + ValueError: Infinite solutions. (Consistent system) + >>> cramers_rule_2x2([1, 2, 3], [2, 4, 7]) + Traceback (most recent call last): + ... + ValueError: No solution. (Inconsistent system) + >>> cramers_rule_2x2([1, 2, 3], [11, 22]) + Traceback (most recent call last): + ... + ValueError: Please enter a valid equation. + >>> cramers_rule_2x2([0, 1, 6], [0, 0, 3]) + Traceback (most recent call last): + ... + ValueError: No solution. (Inconsistent system) + >>> cramers_rule_2x2([0, 0, 6], [0, 0, 3]) + Traceback (most recent call last): + ... + ValueError: Both a & b of two equations can't be zero. + >>> cramers_rule_2x2([1, 2, 3], [1, 2, 3]) + Traceback (most recent call last): + ... + ValueError: Infinite solutions. (Consistent system) + >>> cramers_rule_2x2([0, 4, 50], [0, 3, 99]) + Traceback (most recent call last): + ... + ValueError: No solution. (Inconsistent system) + """ + + # Check if the input is valid + if not len(equation1) == len(equation2) == 3: + raise ValueError("Please enter a valid equation.") + if equation1[0] == equation1[1] == equation2[0] == equation2[1] == 0: + raise ValueError("Both a & b of two equations can't be zero.") + + # Extract the coefficients + a1, b1, c1 = equation1 + a2, b2, c2 = equation2 + + # Calculate the determinants of the matrices + determinant = a1 * b2 - a2 * b1 + determinant_x = c1 * b2 - c2 * b1 + determinant_y = a1 * c2 - a2 * c1 + + # Check if the system of linear equations has a solution (using Cramer's rule) + if determinant == 0: + if determinant_x == determinant_y == 0: + raise ValueError("Infinite solutions. (Consistent system)") + else: + raise ValueError("No solution. (Inconsistent system)") + else: + if determinant_x == determinant_y == 0: + return "Trivial solution. (Consistent system) x = 0 and y = 0" + else: + x = determinant_x / determinant + y = determinant_y / determinant + return f"Non-Trivial Solution (Consistent system) x = {x}, y = {y}" From c31ef5e7782803b07e6d7eb4dca3b038cbdb095d Mon Sep 17 00:00:00 2001 From: RohitSingh107 <64142943+RohitSingh107@users.noreply.github.com> Date: Wed, 26 Oct 2022 03:25:48 +0530 Subject: [PATCH 1959/2908] Add longest common substring (#7488) * added longest common substring * added retrun type hint * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * changed t1, t2 to text1, text2 * Update longest_common_substring.py * Update dynamic_programming/longest_common_substring.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * Update dynamic_programming/longest_common_substring.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * applied suggested changes * Update dynamic_programming/longest_common_substring.py Co-authored-by: Caeden Perelli-Harris * removed space between line * return longest common substring * Update dynamic_programming/longest_common_substring.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: Caeden Perelli-Harris Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../longest_common_substring.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 dynamic_programming/longest_common_substring.py diff --git a/dynamic_programming/longest_common_substring.py b/dynamic_programming/longest_common_substring.py new file mode 100644 index 000000000000..84a9f18609f9 --- /dev/null +++ b/dynamic_programming/longest_common_substring.py @@ -0,0 +1,63 @@ +""" +Longest Common Substring Problem Statement: Given two sequences, find the +longest common substring present in both of them. A substring is +necessarily continuous. +Example: "abcdef" and "xabded" have two longest common substrings, "ab" or "de". +Therefore, algorithm should return any one of them. +""" + + +def longest_common_substring(text1: str, text2: str) -> str: + """ + Finds the longest common substring between two strings. + >>> longest_common_substring("", "") + '' + >>> longest_common_substring("a","") + '' + >>> longest_common_substring("", "a") + '' + >>> longest_common_substring("a", "a") + 'a' + >>> longest_common_substring("abcdef", "bcd") + 'bcd' + >>> longest_common_substring("abcdef", "xabded") + 'ab' + >>> longest_common_substring("GeeksforGeeks", "GeeksQuiz") + 'Geeks' + >>> longest_common_substring("abcdxyz", "xyzabcd") + 'abcd' + >>> longest_common_substring("zxabcdezy", "yzabcdezx") + 'abcdez' + >>> longest_common_substring("OldSite:GeeksforGeeks.org", "NewSite:GeeksQuiz.com") + 'Site:Geeks' + >>> longest_common_substring(1, 1) + Traceback (most recent call last): + ... + ValueError: longest_common_substring() takes two strings for inputs + """ + + if not (isinstance(text1, str) and isinstance(text2, str)): + raise ValueError("longest_common_substring() takes two strings for inputs") + + text1_length = len(text1) + text2_length = len(text2) + + dp = [[0] * (text2_length + 1) for _ in range(text1_length + 1)] + ans_index = 0 + ans_length = 0 + + for i in range(1, text1_length + 1): + for j in range(1, text2_length + 1): + if text1[i - 1] == text2[j - 1]: + dp[i][j] = 1 + dp[i - 1][j - 1] + if dp[i][j] > ans_length: + ans_index = i + ans_length = dp[i][j] + + return text1[ans_index - ans_length : ans_index] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 505c5e20fa7efec9f6c4cb5b8bafd8ff2001e3b7 Mon Sep 17 00:00:00 2001 From: Mislah <76743829+mislah@users.noreply.github.com> Date: Wed, 26 Oct 2022 03:56:05 +0530 Subject: [PATCH 1960/2908] Included area of n sided regular polygon (#7438) * Included area of n sided regular polygon Added a function to calculate the area of n sided regular polygons * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * code standard fixes as per PR comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/area.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/maths/area.py b/maths/area.py index abbf7aa85da5..5db7dac38973 100644 --- a/maths/area.py +++ b/maths/area.py @@ -1,12 +1,14 @@ """ Find the area of various geometric shapes +Wikipedia reference: https://en.wikipedia.org/wiki/Area """ -from math import pi, sqrt +from math import pi, sqrt, tan def surface_area_cube(side_length: float) -> float: """ Calculate the Surface Area of a Cube. + >>> surface_area_cube(1) 6 >>> surface_area_cube(1.6) @@ -28,6 +30,7 @@ def surface_area_cube(side_length: float) -> float: def surface_area_cuboid(length: float, breadth: float, height: float) -> float: """ Calculate the Surface Area of a Cuboid. + >>> surface_area_cuboid(1, 2, 3) 22 >>> surface_area_cuboid(0, 0, 0) @@ -57,6 +60,7 @@ def surface_area_sphere(radius: float) -> float: Calculate the Surface Area of a Sphere. Wikipedia reference: https://en.wikipedia.org/wiki/Sphere Formula: 4 * pi * r^2 + >>> surface_area_sphere(5) 314.1592653589793 >>> surface_area_sphere(1) @@ -79,6 +83,7 @@ def surface_area_hemisphere(radius: float) -> float: """ Calculate the Surface Area of a Hemisphere. Formula: 3 * pi * r^2 + >>> surface_area_hemisphere(5) 235.61944901923448 >>> surface_area_hemisphere(1) @@ -102,6 +107,7 @@ def surface_area_cone(radius: float, height: float) -> float: Calculate the Surface Area of a Cone. Wikipedia reference: https://en.wikipedia.org/wiki/Cone Formula: pi * r * (r + (h ** 2 + r ** 2) ** 0.5) + >>> surface_area_cone(10, 24) 1130.9733552923256 >>> surface_area_cone(6, 8) @@ -133,6 +139,7 @@ def surface_area_conical_frustum( ) -> float: """ Calculate the Surface Area of a Conical Frustum. + >>> surface_area_conical_frustum(1, 2, 3) 45.511728065337266 >>> surface_area_conical_frustum(4, 5, 6) @@ -167,6 +174,7 @@ def surface_area_cylinder(radius: float, height: float) -> float: Calculate the Surface Area of a Cylinder. Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder Formula: 2 * pi * r * (h + r) + >>> surface_area_cylinder(7, 10) 747.6990515543707 >>> surface_area_cylinder(1.6, 2.6) @@ -196,6 +204,7 @@ def surface_area_cylinder(radius: float, height: float) -> float: def area_rectangle(length: float, width: float) -> float: """ Calculate the area of a rectangle. + >>> area_rectangle(10, 20) 200 >>> area_rectangle(1.6, 2.6) @@ -223,6 +232,7 @@ def area_rectangle(length: float, width: float) -> float: def area_square(side_length: float) -> float: """ Calculate the area of a square. + >>> area_square(10) 100 >>> area_square(0) @@ -242,6 +252,7 @@ def area_square(side_length: float) -> float: def area_triangle(base: float, height: float) -> float: """ Calculate the area of a triangle given the base and height. + >>> area_triangle(10, 10) 50.0 >>> area_triangle(1.6, 2.6) @@ -270,6 +281,7 @@ 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) @@ -316,6 +328,7 @@ def area_triangle_three_sides(side1: float, side2: float, side3: float) -> float def area_parallelogram(base: float, height: float) -> float: """ Calculate the area of a parallelogram. + >>> area_parallelogram(10, 20) 200 >>> area_parallelogram(1.6, 2.6) @@ -343,6 +356,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. + >>> area_trapezium(10, 20, 30) 450.0 >>> area_trapezium(1.6, 2.6, 3.6) @@ -386,6 +400,7 @@ def area_trapezium(base1: float, base2: float, height: float) -> float: def area_circle(radius: float) -> float: """ Calculate the area of a circle. + >>> area_circle(20) 1256.6370614359173 >>> area_circle(1.6) @@ -405,6 +420,7 @@ def area_circle(radius: float) -> float: 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) @@ -434,6 +450,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. + >>> area_rhombus(10, 20) 100.0 >>> area_rhombus(1.6, 2.6) @@ -458,6 +475,51 @@ def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: return 1 / 2 * diagonal_1 * diagonal_2 +def area_reg_polygon(sides: int, length: float) -> float: + """ + Calculate the area of a regular polygon. + Wikipedia reference: https://en.wikipedia.org/wiki/Polygon#Regular_polygons + Formula: (n*s^2*cot(pi/n))/4 + + >>> area_reg_polygon(3, 10) + 43.301270189221945 + >>> area_reg_polygon(4, 10) + 100.00000000000001 + >>> area_reg_polygon(0, 0) + Traceback (most recent call last): + ... + ValueError: area_reg_polygon() only accepts integers greater than or equal to \ +three as number of sides + >>> area_reg_polygon(-1, -2) + Traceback (most recent call last): + ... + ValueError: area_reg_polygon() only accepts integers greater than or equal to \ +three as number of sides + >>> area_reg_polygon(5, -2) + Traceback (most recent call last): + ... + ValueError: area_reg_polygon() only accepts non-negative values as \ +length of a side + >>> area_reg_polygon(-1, 2) + Traceback (most recent call last): + ... + ValueError: area_reg_polygon() only accepts integers greater than or equal to \ +three as number of sides + """ + if not isinstance(sides, int) or sides < 3: + raise ValueError( + "area_reg_polygon() only accepts integers greater than or \ +equal to three as number of sides" + ) + elif length < 0: + raise ValueError( + "area_reg_polygon() only accepts non-negative values as \ +length of a side" + ) + return (sides * length**2) / (4 * tan(pi / sides)) + return (sides * length**2) / (4 * tan(pi / sides)) + + if __name__ == "__main__": import doctest @@ -481,3 +543,6 @@ def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: print(f"Cone: {surface_area_cone(10, 20) = }") print(f"Conical Frustum: {surface_area_conical_frustum(10, 20, 30) = }") print(f"Cylinder: {surface_area_cylinder(10, 20) = }") + print(f"Equilateral Triangle: {area_reg_polygon(3, 10) = }") + print(f"Square: {area_reg_polygon(4, 10) = }") + print(f"Reqular Pentagon: {area_reg_polygon(5, 10) = }") From 68f6e9ac305b75a7aa8455977e35eeb942051959 Mon Sep 17 00:00:00 2001 From: M3talM0nk3y Date: Tue, 25 Oct 2022 23:31:16 -0400 Subject: [PATCH 1961/2908] Added function that checks if a string is an isogram (#7608) * Added function that checks if a string is an isogram. * Added wiki reference and fixed comments. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Made function name more self-documenting. Raise ValueError if string contains 1 or more digits. Renamed file. Lowercase string inside function. * Removed check_isogram.py (file renamed). * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed test failure. * Raise ValueError when string has non-alpha characters. Removed import. Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- strings/is_isogram.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 strings/is_isogram.py diff --git a/strings/is_isogram.py b/strings/is_isogram.py new file mode 100644 index 000000000000..a9d9acc8138e --- /dev/null +++ b/strings/is_isogram.py @@ -0,0 +1,30 @@ +""" +wiki: https://en.wikipedia.org/wiki/Heterogram_(literature)#Isograms +""" + + +def is_isogram(string: str) -> bool: + """ + An isogram is a word in which no letter is repeated. + Examples of isograms are uncopyrightable and ambidextrously. + >>> is_isogram('Uncopyrightable') + True + >>> is_isogram('allowance') + False + >>> is_isogram('copy1') + Traceback (most recent call last): + ... + ValueError: String must only contain alphabetic characters. + """ + if not all(x.isalpha() for x in string): + raise ValueError("String must only contain alphabetic characters.") + + letters = sorted(string.lower()) + return len(letters) == len(set(letters)) + + +if __name__ == "__main__": + input_str = input("Enter a string ").strip() + + isogram = is_isogram(input_str) + print(f"{input_str} is {'an' if isogram else 'not an'} isogram.") From abf0909b6877d64c3adc9d666b85aa38bcd98566 Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Tue, 25 Oct 2022 23:09:28 -0700 Subject: [PATCH 1962/2908] Write a proper implementation for base16 (#6909) According to CONTRIBUTING.md: "Algorithms in this repo should not be how-to examples for existing Python packages." --- ciphers/base16.py | 75 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/ciphers/base16.py b/ciphers/base16.py index a149a6d8c5bf..6cd62846fc87 100644 --- a/ciphers/base16.py +++ b/ciphers/base16.py @@ -1,34 +1,63 @@ -import base64 - - -def base16_encode(inp: str) -> bytes: +def base16_encode(data: bytes) -> str: """ - Encodes a given utf-8 string into base-16. + Encodes the given bytes into base16. - >>> base16_encode('Hello World!') - b'48656C6C6F20576F726C6421' - >>> base16_encode('HELLO WORLD!') - b'48454C4C4F20574F524C4421' - >>> base16_encode('') - b'' + >>> base16_encode(b'Hello World!') + '48656C6C6F20576F726C6421' + >>> base16_encode(b'HELLO WORLD!') + '48454C4C4F20574F524C4421' + >>> base16_encode(b'') + '' """ - # encode the input into a bytes-like object and then encode b16encode that - return base64.b16encode(inp.encode("utf-8")) + # Turn the data into a list of integers (where each integer is a byte), + # Then turn each byte into its hexadecimal representation, make sure + # it is uppercase, and then join everything together and return it. + return "".join([hex(byte)[2:].zfill(2).upper() for byte in list(data)]) -def base16_decode(b16encoded: bytes) -> str: +def base16_decode(data: str) -> bytes: """ - Decodes from base-16 to a utf-8 string. + Decodes the given base16 encoded data into bytes. - >>> base16_decode(b'48656C6C6F20576F726C6421') - 'Hello World!' - >>> base16_decode(b'48454C4C4F20574F524C4421') - 'HELLO WORLD!' - >>> base16_decode(b'') - '' + >>> base16_decode('48656C6C6F20576F726C6421') + b'Hello World!' + >>> base16_decode('48454C4C4F20574F524C4421') + b'HELLO WORLD!' + >>> base16_decode('') + b'' + >>> base16_decode('486') + Traceback (most recent call last): + ... + ValueError: Base16 encoded data is invalid: + Data does not have an even number of hex digits. + >>> base16_decode('48656c6c6f20576f726c6421') + Traceback (most recent call last): + ... + ValueError: Base16 encoded data is invalid: + Data is not uppercase hex or it contains invalid characters. + >>> base16_decode('This is not base64 encoded data.') + Traceback (most recent call last): + ... + ValueError: Base16 encoded data is invalid: + Data is not uppercase hex or it contains invalid characters. """ - # b16decode the input into bytes and decode that into a human readable string - return base64.b16decode(b16encoded).decode("utf-8") + # Check data validity, following RFC3548 + # https://www.ietf.org/rfc/rfc3548.txt + if (len(data) % 2) != 0: + raise ValueError( + """Base16 encoded data is invalid: +Data does not have an even number of hex digits.""" + ) + # Check the character set - the standard base16 alphabet + # is uppercase according to RFC3548 section 6 + if not set(data) <= set("0123456789ABCDEF"): + raise ValueError( + """Base16 encoded data is invalid: +Data is not uppercase hex or it contains invalid characters.""" + ) + # For every two hexadecimal digits (= a byte), turn it into an integer. + # Then, string the result together into bytes, and return it. + return bytes(int(data[i] + data[i + 1], 16) for i in range(0, len(data), 2)) if __name__ == "__main__": From 93905653506c684e393d984ad814af66af8ee0e9 Mon Sep 17 00:00:00 2001 From: Karthik Ayangar <66073214+kituuu@users.noreply.github.com> Date: Wed, 26 Oct 2022 14:06:40 +0530 Subject: [PATCH 1963/2908] added support for inverse of 3x3 matrix (#7355) * added support for inverse of 3x3 matrix * Modified Docstring and improved code * fixed an error * Modified docstring * Apply all suggestions from code review Co-authored-by: Caeden Perelli-Harris Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Caeden Perelli-Harris Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> --- matrix/inverse_of_matrix.py | 137 ++++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 15 deletions(-) diff --git a/matrix/inverse_of_matrix.py b/matrix/inverse_of_matrix.py index 770ce39b584f..e53d90df8253 100644 --- a/matrix/inverse_of_matrix.py +++ b/matrix/inverse_of_matrix.py @@ -2,22 +2,25 @@ from decimal import Decimal +from numpy import array + 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. + This function finds the inverse of a 2x2 and 3x3 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 + Doctests for 2x2 >>> 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]] @@ -25,24 +28,128 @@ def inverse_of_matrix(matrix: list[list[float]]) -> list[list[float]]: [[0.16666666666666666, -0.0625], [-0.3333333333333333, 0.25]] >>> inverse_of_matrix([[10, 5], [3, 2.5]]) [[0.25, -0.5], [-0.3, 1.0]] + + Doctests for 3x3 + >>> inverse_of_matrix([[2, 5, 7], [2, 0, 1], [1, 2, 3]]) + [[2.0, 5.0, -4.0], [1.0, 1.0, -1.0], [-5.0, -12.0, 10.0]] + >>> inverse_of_matrix([[1, 2, 2], [1, 2, 2], [3, 2, -1]]) + Traceback (most recent call last): + ... + ValueError: This matrix has no inverse. + + >>> inverse_of_matrix([[],[]]) + Traceback (most recent call last): + ... + ValueError: Please provide a matrix of size 2x2 or 3x3. + + >>> inverse_of_matrix([[1, 2], [3, 4], [5, 6]]) + Traceback (most recent call last): + ... + ValueError: Please provide a matrix of size 2x2 or 3x3. + + >>> inverse_of_matrix([[1, 2, 1], [0,3, 4]]) + Traceback (most recent call last): + ... + ValueError: Please provide a matrix of size 2x2 or 3x3. + + >>> inverse_of_matrix([[1, 2, 3], [7, 8, 9], [7, 8, 9]]) + Traceback (most recent call last): + ... + ValueError: This matrix has no inverse. + + >>> inverse_of_matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] """ - d = Decimal # An abbreviation for conciseness + d = Decimal # Check if the provided matrix has 2 rows and 2 columns # since this implementation only works for 2x2 matrices - if len(matrix) != 2 or len(matrix[0]) != 2 or len(matrix[1]) != 2: - raise ValueError("Please provide a matrix of size 2x2.") + if len(matrix) == 2 and len(matrix[0]) == 2 and len(matrix[1]) == 2: + # Calculate the determinant of the matrix + determinant = float( + 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 + ] + elif ( + len(matrix) == 3 + and len(matrix[0]) == 3 + and len(matrix[1]) == 3 + and len(matrix[2]) == 3 + ): + # Calculate the determinant of the matrix using Sarrus rule + determinant = float( + ( + (d(matrix[0][0]) * d(matrix[1][1]) * d(matrix[2][2])) + + (d(matrix[0][1]) * d(matrix[1][2]) * d(matrix[2][0])) + + (d(matrix[0][2]) * d(matrix[1][0]) * d(matrix[2][1])) + ) + - ( + (d(matrix[0][2]) * d(matrix[1][1]) * d(matrix[2][0])) + + (d(matrix[0][1]) * d(matrix[1][0]) * d(matrix[2][2])) + + (d(matrix[0][0]) * d(matrix[1][2]) * d(matrix[2][1])) + ) + ) + if determinant == 0: + raise ValueError("This matrix has no inverse.") + + # Creating cofactor matrix + cofactor_matrix = [ + [d(0.0), d(0.0), d(0.0)], + [d(0.0), d(0.0), d(0.0)], + [d(0.0), d(0.0), d(0.0)], + ] + cofactor_matrix[0][0] = (d(matrix[1][1]) * d(matrix[2][2])) - ( + d(matrix[1][2]) * d(matrix[2][1]) + ) + cofactor_matrix[0][1] = -( + (d(matrix[1][0]) * d(matrix[2][2])) - (d(matrix[1][2]) * d(matrix[2][0])) + ) + cofactor_matrix[0][2] = (d(matrix[1][0]) * d(matrix[2][1])) - ( + d(matrix[1][1]) * d(matrix[2][0]) + ) + cofactor_matrix[1][0] = -( + (d(matrix[0][1]) * d(matrix[2][2])) - (d(matrix[0][2]) * d(matrix[2][1])) + ) + cofactor_matrix[1][1] = (d(matrix[0][0]) * d(matrix[2][2])) - ( + d(matrix[0][2]) * d(matrix[2][0]) + ) + cofactor_matrix[1][2] = -( + (d(matrix[0][0]) * d(matrix[2][1])) - (d(matrix[0][1]) * d(matrix[2][0])) + ) + cofactor_matrix[2][0] = (d(matrix[0][1]) * d(matrix[1][2])) - ( + d(matrix[0][2]) * d(matrix[1][1]) + ) + cofactor_matrix[2][1] = -( + (d(matrix[0][0]) * d(matrix[1][2])) - (d(matrix[0][2]) * d(matrix[1][0])) + ) + cofactor_matrix[2][2] = (d(matrix[0][0]) * d(matrix[1][1])) - ( + d(matrix[0][1]) * d(matrix[1][0]) + ) - # 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.") + # Transpose the cofactor matrix (Adjoint matrix) + adjoint_matrix = array(cofactor_matrix) + for i in range(3): + for j in range(3): + adjoint_matrix[i][j] = cofactor_matrix[j][i] - # 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] + # Inverse of the matrix using the formula (1/determinant) * adjoint matrix + inverse_matrix = array(cofactor_matrix) + for i in range(3): + for j in range(3): + inverse_matrix[i][j] /= d(determinant) - # Calculate the inverse of the matrix - return [[float(d(n) / determinant) or 0.0 for n in row] for row in swapped_matrix] + # Calculate the inverse of the matrix + return [[float(d(n)) or 0.0 for n in row] for row in inverse_matrix] + raise ValueError("Please provide a matrix of size 2x2 or 3x3.") From 8fd06efe22ec3e870ac1fa375bd4600cb30baad4 Mon Sep 17 00:00:00 2001 From: JatinR05 <71865805+JatinR05@users.noreply.github.com> Date: Wed, 26 Oct 2022 20:13:01 +0530 Subject: [PATCH 1964/2908] Create minimums_squares_to_represent_a_number.py (#7595) * Create minimums_squares_to_represent_a_number.py added a dynamic programming approach of finding the minimum number of square to represent a number. eg : 25 = 5*5 37 = 6*6 + 1*1 21 = 4*4 + 2*2 + 1*1 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename minimums_squares_to_represent_a_number.py to minimum_squares_to_represent_a_number.py updated the code * Update minimum_squares_to_represent_a_number.py I have added the appropriate checks for 0 and 12.34. It would be great if you could suggest a name for the dp array * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_squares_to_represent_a_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_squares_to_represent_a_number.py updated * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_squares_to_represent_a_number.py updated * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../minimum_squares_to_represent_a_number.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 dynamic_programming/minimum_squares_to_represent_a_number.py diff --git a/dynamic_programming/minimum_squares_to_represent_a_number.py b/dynamic_programming/minimum_squares_to_represent_a_number.py new file mode 100644 index 000000000000..bf5849f5bcb3 --- /dev/null +++ b/dynamic_programming/minimum_squares_to_represent_a_number.py @@ -0,0 +1,48 @@ +import math +import sys + + +def minimum_squares_to_represent_a_number(number: int) -> int: + """ + Count the number of minimum squares to represent a number + >>> minimum_squares_to_represent_a_number(25) + 1 + >>> minimum_squares_to_represent_a_number(37) + 2 + >>> minimum_squares_to_represent_a_number(21) + 3 + >>> minimum_squares_to_represent_a_number(58) + 2 + >>> minimum_squares_to_represent_a_number(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must not be a negative number + >>> minimum_squares_to_represent_a_number(0) + 1 + >>> minimum_squares_to_represent_a_number(12.34) + Traceback (most recent call last): + ... + ValueError: the value of input must be a natural number + """ + if number != int(number): + raise ValueError("the value of input must be a natural number") + if number < 0: + raise ValueError("the value of input must not be a negative number") + if number == 0: + return 1 + answers = [-1] * (number + 1) + answers[0] = 0 + for i in range(1, number + 1): + answer = sys.maxsize + root = int(math.sqrt(i)) + for j in range(1, root + 1): + current_answer = 1 + answers[i - (j**2)] + answer = min(answer, current_answer) + answers[i] = answer + return answers[number] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5c8a939c5a51104fce4b22ef56d29720c6ce47bb Mon Sep 17 00:00:00 2001 From: Shubham Kondekar <40213815+kondekarshubham123@users.noreply.github.com> Date: Wed, 26 Oct 2022 20:36:15 +0530 Subject: [PATCH 1965/2908] Create largest_square_area_in_matrix.py (#7673) * Create largest_square_area_in_matrix.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update matrix/largest_square_area_in_matrix.py Co-authored-by: Caeden Perelli-Harris * Update matrix/largest_square_area_in_matrix.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update largest_square_area_in_matrix.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris --- matrix/largest_square_area_in_matrix.py | 191 ++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 matrix/largest_square_area_in_matrix.py diff --git a/matrix/largest_square_area_in_matrix.py b/matrix/largest_square_area_in_matrix.py new file mode 100644 index 000000000000..cf975cb7ce1f --- /dev/null +++ b/matrix/largest_square_area_in_matrix.py @@ -0,0 +1,191 @@ +""" +Question: +Given a binary matrix mat of size n * m, find out the maximum size square +sub-matrix with all 1s. + +--- +Example 1: + +Input: +n = 2, m = 2 +mat = [[1, 1], + [1, 1]] + +Output: +2 + +Explanation: The maximum size of the square +sub-matrix is 2. The matrix itself is the +maximum sized sub-matrix in this case. +--- +Example 2 + +Input: +n = 2, m = 2 +mat = [[0, 0], + [0, 0]] +Output: 0 + +Explanation: There is no 1 in the matrix. + + +Approach: +We initialize another matrix (dp) with the same dimensions +as the original one initialized with all 0’s. + +dp_array(i,j) represents the side length of the maximum square whose +bottom right corner is the cell with index (i,j) in the original matrix. + +Starting from index (0,0), for every 1 found in the original matrix, +we update the value of the current element as + +dp_array(i,j)=dp_array(dp(i−1,j),dp_array(i−1,j−1),dp_array(i,j−1)) + 1. +""" + + +def largest_square_area_in_matrix_top_down_approch( + rows: int, cols: int, mat: list[list[int]] +) -> int: + """ + Function updates the largest_square_area[0], if recursive call found + square with maximum area. + + We aren't using dp_array here, so the time complexity would be exponential. + + >>> largest_square_area_in_matrix_top_down_approch(2, 2, [[1,1], [1,1]]) + 2 + >>> largest_square_area_in_matrix_top_down_approch(2, 2, [[0,0], [0,0]]) + 0 + """ + + def update_area_of_max_square(row: int, col: int) -> int: + + # BASE CASE + if row >= rows or col >= cols: + return 0 + + right = update_area_of_max_square(row, col + 1) + diagonal = update_area_of_max_square(row + 1, col + 1) + down = update_area_of_max_square(row + 1, col) + + if mat[row][col]: + sub_problem_sol = 1 + min([right, diagonal, down]) + largest_square_area[0] = max(largest_square_area[0], sub_problem_sol) + return sub_problem_sol + else: + return 0 + + largest_square_area = [0] + update_area_of_max_square(0, 0) + return largest_square_area[0] + + +def largest_square_area_in_matrix_top_down_approch_with_dp( + rows: int, cols: int, mat: list[list[int]] +) -> int: + """ + Function updates the largest_square_area[0], if recursive call found + square with maximum area. + + We are using dp_array here, so the time complexity would be O(N^2). + + >>> largest_square_area_in_matrix_top_down_approch_with_dp(2, 2, [[1,1], [1,1]]) + 2 + >>> largest_square_area_in_matrix_top_down_approch_with_dp(2, 2, [[0,0], [0,0]]) + 0 + """ + + def update_area_of_max_square_using_dp_array( + row: int, col: int, dp_array: list[list[int]] + ) -> int: + if row >= rows or col >= cols: + return 0 + if dp_array[row][col] != -1: + return dp_array[row][col] + + right = update_area_of_max_square_using_dp_array(row, col + 1, dp_array) + diagonal = update_area_of_max_square_using_dp_array(row + 1, col + 1, dp_array) + down = update_area_of_max_square_using_dp_array(row + 1, col, dp_array) + + if mat[row][col]: + sub_problem_sol = 1 + min([right, diagonal, down]) + largest_square_area[0] = max(largest_square_area[0], sub_problem_sol) + dp_array[row][col] = sub_problem_sol + return sub_problem_sol + else: + return 0 + + largest_square_area = [0] + dp_array = [[-1] * cols for _ in range(rows)] + update_area_of_max_square_using_dp_array(0, 0, dp_array) + + return largest_square_area[0] + + +def largest_square_area_in_matrix_bottom_up( + rows: int, cols: int, mat: list[list[int]] +) -> int: + """ + Function updates the largest_square_area, using bottom up approach. + + >>> largest_square_area_in_matrix_bottom_up(2, 2, [[1,1], [1,1]]) + 2 + >>> largest_square_area_in_matrix_bottom_up(2, 2, [[0,0], [0,0]]) + 0 + + """ + dp_array = [[0] * (cols + 1) for _ in range(rows + 1)] + largest_square_area = 0 + for row in range(rows - 1, -1, -1): + for col in range(cols - 1, -1, -1): + + right = dp_array[row][col + 1] + diagonal = dp_array[row + 1][col + 1] + bottom = dp_array[row + 1][col] + + if mat[row][col] == 1: + dp_array[row][col] = 1 + min(right, diagonal, bottom) + largest_square_area = max(dp_array[row][col], largest_square_area) + else: + dp_array[row][col] = 0 + + return largest_square_area + + +def largest_square_area_in_matrix_bottom_up_space_optimization( + rows: int, cols: int, mat: list[list[int]] +) -> int: + """ + Function updates the largest_square_area, using bottom up + approach. with space optimization. + + >>> largest_square_area_in_matrix_bottom_up_space_optimization(2, 2, [[1,1], [1,1]]) + 2 + >>> largest_square_area_in_matrix_bottom_up_space_optimization(2, 2, [[0,0], [0,0]]) + 0 + """ + current_row = [0] * (cols + 1) + next_row = [0] * (cols + 1) + largest_square_area = 0 + for row in range(rows - 1, -1, -1): + for col in range(cols - 1, -1, -1): + + right = current_row[col + 1] + diagonal = next_row[col + 1] + bottom = next_row[col] + + if mat[row][col] == 1: + current_row[col] = 1 + min(right, diagonal, bottom) + largest_square_area = max(current_row[col], largest_square_area) + else: + current_row[col] = 0 + next_row = current_row + + return largest_square_area + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(largest_square_area_in_matrix_bottom_up(2, 2, [[1, 1], [1, 1]])) From 614274a9dc996f64dd470d2029847cc229f19346 Mon Sep 17 00:00:00 2001 From: Shubham Kondekar <40213815+kondekarshubham123@users.noreply.github.com> Date: Wed, 26 Oct 2022 22:28:33 +0530 Subject: [PATCH 1966/2908] Update spiral_print.py (#7674) * Update spiral_print.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update matrix/spiral_print.py Co-authored-by: Caeden Perelli-Harris * Update matrix/spiral_print.py Co-authored-by: Caeden Perelli-Harris * Update matrix/spiral_print.py Co-authored-by: Caeden Perelli-Harris * Update matrix/spiral_print.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update spiral_print.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update spiral_print.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update spiral_print.py * Update spiral_print.py * Update spiral_print.py * Update spiral_print.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris --- matrix/spiral_print.py | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 0cf732d60ca8..0d0be1527aec 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -76,7 +76,56 @@ def spiral_print_clockwise(a: list[list[int]]) -> None: return +# Other Easy to understand Approach + + +def spiral_traversal(matrix: list[list]) -> list[int]: + """ + >>> spiral_traversal([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) + [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] + + Example: + matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] + Algorithm: + Step 1. first pop the 0 index list. (which is [1,2,3,4] and concatenate the + output of [step 2]) + Step 2. Now perform matrix’s Transpose operation (Change rows to column + and vice versa) and reverse the resultant matrix. + Step 3. Pass the output of [2nd step], to same recursive function till + base case hits. + Dry Run: + Stage 1. + [1, 2, 3, 4] + spiral_traversal([ + [8, 12], [7, 11], [6, 10], [5, 9]] + ]) + Stage 2. + [1, 2, 3, 4, 8, 12] + spiral_traversal([ + [11, 10, 9], [7, 6, 5] + ]) + Stage 3. + [1, 2, 3, 4, 8, 12, 11, 10, 9] + spiral_traversal([ + [5], [6], [7] + ]) + Stage 4. + [1, 2, 3, 4, 8, 12, 11, 10, 9, 5] + spiral_traversal([ + [5], [6], [7] + ]) + Stage 5. + [1, 2, 3, 4, 8, 12, 11, 10, 9, 5] + spiral_traversal([[6, 7]]) + Stage 6. + [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] + spiral_traversal([]) + """ + if matrix: + return list(matrix.pop(0)) + spiral_traversal(list(zip(*matrix))[::-1]) + else: + return [] + + # driver code if __name__ == "__main__": + import doctest + + doctest.testmod() + a = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] spiral_print_clockwise(a) From 74325d079cf4394f7b75c26b334a81e98b7e25b1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 26 Oct 2022 22:08:53 +0200 Subject: [PATCH 1967/2908] Rename quantum_random.py to quantum_random.py.DISABLED.txt (#7683) * Rename quantum_random.py to quantum_random.py.DISABLED.txt #7682 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 13 ++++++++++++- ...tum_random.py => quantum_random.py.DISABLED.txt} | 0 2 files changed, 12 insertions(+), 1 deletion(-) rename quantum/{quantum_random.py => quantum_random.py.DISABLED.txt} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 3e722a8784e5..ba7d3e62a9e1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -56,8 +56,13 @@ ## Boolean Algebra * [And Gate](boolean_algebra/and_gate.py) + * [Nand Gate](boolean_algebra/nand_gate.py) * [Norgate](boolean_algebra/norgate.py) + * [Not Gate](boolean_algebra/not_gate.py) + * [Or Gate](boolean_algebra/or_gate.py) * [Quine Mc Cluskey](boolean_algebra/quine_mc_cluskey.py) + * [Xnor Gate](boolean_algebra/xnor_gate.py) + * [Xor Gate](boolean_algebra/xor_gate.py) ## Cellular Automata * [Conways Game Of Life](cellular_automata/conways_game_of_life.py) @@ -288,6 +293,7 @@ * [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py) * [Knapsack](dynamic_programming/knapsack.py) * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) + * [Longest Common Substring](dynamic_programming/longest_common_substring.py) * [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py) * [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py) * [Longest Sub Array](dynamic_programming/longest_sub_array.py) @@ -298,6 +304,7 @@ * [Minimum Coin Change](dynamic_programming/minimum_coin_change.py) * [Minimum Cost Path](dynamic_programming/minimum_cost_path.py) * [Minimum Partition](dynamic_programming/minimum_partition.py) + * [Minimum Squares To Represent A Number](dynamic_programming/minimum_squares_to_represent_a_number.py) * [Minimum Steps To One](dynamic_programming/minimum_steps_to_one.py) * [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py) * [Rod Cutting](dynamic_programming/rod_cutting.py) @@ -474,6 +481,7 @@ * [Add](maths/add.py) * [Aliquot Sum](maths/aliquot_sum.py) * [Allocation Number](maths/allocation_number.py) + * [Arc Length](maths/arc_length.py) * [Area](maths/area.py) * [Area Under Curve](maths/area_under_curve.py) * [Armstrong Numbers](maths/armstrong_numbers.py) @@ -609,7 +617,9 @@ ## Matrix * [Binary Search Matrix](matrix/binary_search_matrix.py) * [Count Islands In Matrix](matrix/count_islands_in_matrix.py) + * [Cramers Rule 2X2](matrix/cramers_rule_2x2.py) * [Inverse Of Matrix](matrix/inverse_of_matrix.py) + * [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py) * [Matrix Class](matrix/matrix_class.py) * [Matrix Operation](matrix/matrix_operation.py) * [Max Area Of Island](matrix/max_area_of_island.py) @@ -657,6 +667,7 @@ ## Physics * [Casimir Effect](physics/casimir_effect.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) + * [Kinetic Energy](physics/kinetic_energy.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) * [N Body Simulation](physics/n_body_simulation.py) * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) @@ -948,7 +959,6 @@ * [Not Gate](quantum/not_gate.py) * [Q Full Adder](quantum/q_full_adder.py) * [Quantum Entanglement](quantum/quantum_entanglement.py) - * [Quantum Random](quantum/quantum_random.py) * [Ripple Adder Classic](quantum/ripple_adder_classic.py) * [Single Qubit Measure](quantum/single_qubit_measure.py) * [Superdense Coding](quantum/superdense_coding.py) @@ -1048,6 +1058,7 @@ * [Hamming Distance](strings/hamming_distance.py) * [Indian Phone Validator](strings/indian_phone_validator.py) * [Is Contains Unique Chars](strings/is_contains_unique_chars.py) + * [Is Isogram](strings/is_isogram.py) * [Is Palindrome](strings/is_palindrome.py) * [Is Pangram](strings/is_pangram.py) * [Is Spain National Id](strings/is_spain_national_id.py) diff --git a/quantum/quantum_random.py b/quantum/quantum_random.py.DISABLED.txt similarity index 100% rename from quantum/quantum_random.py rename to quantum/quantum_random.py.DISABLED.txt From b46b92a9160360ea09848893b90dd6022f371ffe Mon Sep 17 00:00:00 2001 From: Arjit Arora <42044030+arjitarora26@users.noreply.github.com> Date: Thu, 27 Oct 2022 01:39:23 +0530 Subject: [PATCH 1968/2908] Add function for highest set bit location (#7586) * Add function for highest set bit location * Address review comments --- bit_manipulation/highest_set_bit.py | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 bit_manipulation/highest_set_bit.py diff --git a/bit_manipulation/highest_set_bit.py b/bit_manipulation/highest_set_bit.py new file mode 100644 index 000000000000..21d92dcb9492 --- /dev/null +++ b/bit_manipulation/highest_set_bit.py @@ -0,0 +1,34 @@ +def get_highest_set_bit_position(number: int) -> int: + """ + Returns position of the highest set bit of a number. + Ref - https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious + >>> get_highest_set_bit_position(25) + 5 + >>> get_highest_set_bit_position(37) + 6 + >>> get_highest_set_bit_position(1) + 1 + >>> get_highest_set_bit_position(4) + 3 + >>> get_highest_set_bit_position(0) + 0 + >>> get_highest_set_bit_position(0.8) + Traceback (most recent call last): + ... + TypeError: Input value must be an 'int' type + """ + if not isinstance(number, int): + raise TypeError("Input value must be an 'int' type") + + position = 0 + while number: + position += 1 + number >>= 1 + + return position + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 71c7c0bd3592225c027d07a10d1c71946c0f677a Mon Sep 17 00:00:00 2001 From: SwayamSahu <91021799+SwayamSahu@users.noreply.github.com> Date: Thu, 27 Oct 2022 01:50:00 +0530 Subject: [PATCH 1969/2908] Updated a typo in print statement (#7696) * Updated a typo in print statement * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris Co-authored-by: Christian Clauss Co-authored-by: Caeden Perelli-Harris --- strings/barcode_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/strings/barcode_validator.py b/strings/barcode_validator.py index 05670007665c..2e1ea87039ef 100644 --- a/strings/barcode_validator.py +++ b/strings/barcode_validator.py @@ -83,6 +83,6 @@ def get_barcode(barcode: str) -> int: barcode = get_barcode(input("Barcode: ").strip()) if is_valid(barcode): - print(f"'{barcode}' is a valid Barcode") + print(f"'{barcode}' is a valid barcode.") else: - print(f"'{barcode}' is NOT is valid Barcode.") + print(f"'{barcode}' is NOT a valid barcode.") From d33f9b31fe96acf5201c39f565015444526a3e38 Mon Sep 17 00:00:00 2001 From: Sushant Srivastav <63559772+sushant4191@users.noreply.github.com> Date: Thu, 27 Oct 2022 02:45:02 +0530 Subject: [PATCH 1970/2908] Calculate GST Amount (#7694) * Calculate GST Amount The program helps to get the net amount after GST is added to it. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update financial/calculating GST.py Thanks! Co-authored-by: Christian Clauss * Update and rename calculating GST.py to price_plus_tax.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update price_plus_tax.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- financial/price_plus_tax.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 financial/price_plus_tax.py diff --git a/financial/price_plus_tax.py b/financial/price_plus_tax.py new file mode 100644 index 000000000000..43876d35e57c --- /dev/null +++ b/financial/price_plus_tax.py @@ -0,0 +1,18 @@ +""" +Calculate price plus tax of a good or service given its price and a tax rate. +""" + + +def price_plus_tax(price: float, tax_rate: float) -> float: + """ + >>> price_plus_tax(100, 0.25) + 125.0 + >>> price_plus_tax(125.50, 0.05) + 131.775 + """ + return price * (1 + tax_rate) + + +if __name__ == "__main__": + print(f"{price_plus_tax(100, 0.25) = }") + print(f"{price_plus_tax(125.50, 0.05) = }") From e906a5149a0a9c116e1a3dbade6eb6ea659ac68a Mon Sep 17 00:00:00 2001 From: SparshRastogi <75373475+SparshRastogi@users.noreply.github.com> Date: Thu, 27 Oct 2022 16:52:10 +0530 Subject: [PATCH 1971/2908] Create malus_law.py (#7710) * Create malus_law.py Finding the intensity of light transmitted through a polariser using Malus Law and by taking initial intensity and angle between polariser and axis as input * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update physics/malus_law.py Co-authored-by: Caeden Perelli-Harris * Update physics/malus_law.py Co-authored-by: Caeden Perelli-Harris * Update physics/malus_law.py Co-authored-by: Caeden Perelli-Harris * Update physics/malus_law.py Co-authored-by: Caeden Perelli-Harris * Update malus_law.py Made some changes in the error messages and the docstring testcases * Update malus_law.py Made changes for the passing the precommit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris --- physics/malus_law.py | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 physics/malus_law.py diff --git a/physics/malus_law.py b/physics/malus_law.py new file mode 100644 index 000000000000..ae77d45cf614 --- /dev/null +++ b/physics/malus_law.py @@ -0,0 +1,80 @@ +import math + +""" +Finding the intensity of light transmitted through a polariser using Malus Law +and by taking initial intensity and angle between polariser and axis as input + +Description : Malus's law, which is named after Étienne-Louis Malus, +says that when a perfect polarizer is placed in a polarized +beam of light, the irradiance, I, of the light that passes +through is given by + I=I'cos²θ +where I' is the initial intensity and θ is the angle between the light's +initial polarization direction and the axis of the polarizer. +A beam of unpolarized light can be thought of as containing a +uniform mixture of linear polarizations at all possible angles. +Since the average value of cos²θ is 1/2, the transmission coefficient becomes +I/I' = 1/2 +In practice, some light is lost in the polarizer and the actual transmission +will be somewhat lower than this, around 38% for Polaroid-type polarizers but +considerably higher (>49.9%) for some birefringent prism types. +If two polarizers are placed one after another (the second polarizer is +generally called an analyzer), the mutual angle between their polarizing axes +gives the value of θ in Malus's law. If the two axes are orthogonal, the +polarizers are crossed and in theory no light is transmitted, though again +practically speaking no polarizer is perfect and the transmission is not exactly +zero (for example, crossed Polaroid sheets appear slightly blue in colour because +their extinction ratio is better in the red). If a transparent object is placed +between the crossed polarizers, any polarization effects present in the sample +(such as birefringence) will be shown as an increase in transmission. +This effect is used in polarimetry to measure the optical activity of a sample. +Real polarizers are also not perfect blockers of the polarization orthogonal to +their polarization axis; the ratio of the transmission of the unwanted component +to the wanted component is called the extinction ratio, and varies from around +1:500 for Polaroid to about 1:106 for Glan–Taylor prism polarizers. + +Reference : "https://en.wikipedia.org/wiki/Polarizer#Malus's_law_and_other_properties" +""" + + +def malus_law(initial_intensity: float, angle: float) -> float: + """ + >>> round(malus_law(10,45),2) + 5.0 + >>> round(malus_law(100,60),2) + 25.0 + >>> round(malus_law(50,150),2) + 37.5 + >>> round(malus_law(75,270),2) + 0.0 + >>> round(malus_law(10,-900),2) + Traceback (most recent call last): + ... + ValueError: In Malus Law, the angle is in the range 0-360 degrees + >>> round(malus_law(10,900),2) + Traceback (most recent call last): + ... + ValueError: In Malus Law, the angle is in the range 0-360 degrees + >>> round(malus_law(-100,900),2) + Traceback (most recent call last): + ... + ValueError: The value of intensity cannot be negative + >>> round(malus_law(100,180),2) + 100.0 + >>> round(malus_law(100,360),2) + 100.0 + """ + + if initial_intensity < 0: + raise ValueError("The value of intensity cannot be negative") + # handling of negative values of initial intensity + if angle < 0 or angle > 360: + raise ValueError("In Malus Law, the angle is in the range 0-360 degrees") + # handling of values out of allowed range + return initial_intensity * (math.cos(math.radians(angle)) ** 2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod(name="malus_law") From e8915097c4a632419acc77c1ce08aae3e3c3b864 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 27 Oct 2022 14:15:15 +0100 Subject: [PATCH 1972/2908] refactor: Fix matrix display deprecation (#7729) --- machine_learning/xgboost_classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machine_learning/xgboost_classifier.py b/machine_learning/xgboost_classifier.py index bb5b48b7ab23..62a1b331baaf 100644 --- a/machine_learning/xgboost_classifier.py +++ b/machine_learning/xgboost_classifier.py @@ -2,7 +2,7 @@ import numpy as np from matplotlib import pyplot as plt from sklearn.datasets import load_iris -from sklearn.metrics import plot_confusion_matrix +from sklearn.metrics import ConfusionMatrixDisplay from sklearn.model_selection import train_test_split from xgboost import XGBClassifier @@ -63,7 +63,7 @@ def main() -> None: xgboost_classifier = xgboost(x_train, y_train) # Display the confusion matrix of the classifier with both training and test sets - plot_confusion_matrix( + ConfusionMatrixDisplay.from_estimator( xgboost_classifier, x_test, y_test, From 9bba42eca8c679a32f99984bbb5bb53795f4e71f Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 27 Oct 2022 18:42:30 +0100 Subject: [PATCH 1973/2908] refactor: Indent ... for visual purposes (#7744) --- arithmetic_analysis/bisection.py | 4 +-- arithmetic_analysis/intersection.py | 4 +-- .../jacobi_iteration_method.py | 10 +++--- arithmetic_analysis/lu_decomposition.py | 2 +- arithmetic_analysis/newton_method.py | 2 +- arithmetic_analysis/newton_raphson_new.py | 2 +- backtracking/knight_tour.py | 2 +- conversions/binary_to_decimal.py | 6 ++-- conversions/binary_to_hexadecimal.py | 4 +-- conversions/binary_to_octal.py | 4 +-- conversions/decimal_to_any.py | 12 +++---- conversions/decimal_to_binary.py | 4 +-- conversions/decimal_to_binary_recursion.py | 6 ++-- conversions/decimal_to_hexadecimal.py | 4 +-- conversions/hex_to_bin.py | 4 +-- conversions/hexadecimal_to_decimal.py | 6 ++-- conversions/octal_to_decimal.py | 20 ++++++------ conversions/temperature_conversions.py | 32 +++++++++---------- .../binary_tree/binary_search_tree.py | 2 +- .../binary_tree/binary_tree_mirror.py | 4 +-- .../number_of_possible_binary_trees.py | 2 +- .../linked_list/doubly_linked_list.py | 10 +++--- .../linked_list/singly_linked_list.py | 16 +++++----- data_structures/queue/linked_queue.py | 4 +-- .../queue/priority_queue_using_list.py | 2 +- .../stacks/infix_to_postfix_conversion.py | 2 +- .../stacks/stack_with_singly_linked_list.py | 2 +- .../longest_common_substring.py | 2 +- genetic_algorithm/basic_string.py | 6 ++-- linear_algebra/src/lib.py | 4 +-- machine_learning/similarity_search.py | 6 ++-- maths/bisection.py | 2 +- maths/catalan_number.py | 6 ++-- maths/fibonacci.py | 10 +++--- maths/maclaurin_series.py | 16 +++++----- maths/proth_number.py | 6 ++-- maths/sylvester_sequence.py | 4 +-- maths/zellers_congruence.py | 4 +-- neural_network/perceptron.py | 6 ++-- project_euler/problem_004/sol1.py | 2 +- project_euler/problem_010/sol3.py | 6 ++-- searches/interpolation_search.py | 2 +- sorts/bead_sort.py | 4 +-- sorts/msd_radix_sort.py | 4 +-- strings/barcode_validator.py | 4 +-- strings/join.py | 2 +- 46 files changed, 134 insertions(+), 134 deletions(-) diff --git a/arithmetic_analysis/bisection.py b/arithmetic_analysis/bisection.py index 640913a7acc0..e359cc170072 100644 --- a/arithmetic_analysis/bisection.py +++ b/arithmetic_analysis/bisection.py @@ -8,7 +8,7 @@ def bisection(function: Callable[[float], float], a: float, b: float) -> float: 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 @@ -16,7 +16,7 @@ def bisection(function: Callable[[float], float], a: float, b: float) -> float: 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 diff --git a/arithmetic_analysis/intersection.py b/arithmetic_analysis/intersection.py index 49213dd05988..826c0ead0a00 100644 --- a/arithmetic_analysis/intersection.py +++ b/arithmetic_analysis/intersection.py @@ -10,7 +10,7 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl 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 @@ -24,7 +24,7 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl 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: float = x0 diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/arithmetic_analysis/jacobi_iteration_method.py index 3087309e8c3d..fe506a94a65d 100644 --- a/arithmetic_analysis/jacobi_iteration_method.py +++ b/arithmetic_analysis/jacobi_iteration_method.py @@ -42,7 +42,7 @@ def jacobi_iteration_method( >>> iterations = 3 >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) Traceback (most recent call last): - ... + ... ValueError: Coefficient matrix dimensions must be nxn but received 2x3 >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) @@ -51,7 +51,7 @@ def jacobi_iteration_method( >>> iterations = 3 >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) Traceback (most recent call last): - ... + ... ValueError: Coefficient and constant matrices dimensions must be nxn and nx1 but received 3x3 and 2x1 @@ -61,7 +61,7 @@ def jacobi_iteration_method( >>> iterations = 3 >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) Traceback (most recent call last): - ... + ... ValueError: Number of initial values must be equal to number of rows in coefficient matrix but received 2 and 3 @@ -71,7 +71,7 @@ def jacobi_iteration_method( >>> iterations = 0 >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) Traceback (most recent call last): - ... + ... ValueError: Iterations must be at least 1 """ @@ -138,7 +138,7 @@ def strictly_diagonally_dominant(table: NDArray[float64]) -> bool: >>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 3, -4]]) >>> strictly_diagonally_dominant(table) Traceback (most recent call last): - ... + ... ValueError: Coefficient matrix is not strictly diagonally dominant """ diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index 1e98b9066c3f..217719cf4da1 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -31,7 +31,7 @@ def lower_upper_decomposition( >>> 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]] diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index c4018a0f260c..5127bfcafd9a 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -28,7 +28,7 @@ def newton( 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) diff --git a/arithmetic_analysis/newton_raphson_new.py b/arithmetic_analysis/newton_raphson_new.py index 19ea4ce21806..dd1d7e0929cf 100644 --- a/arithmetic_analysis/newton_raphson_new.py +++ b/arithmetic_analysis/newton_raphson_new.py @@ -32,7 +32,7 @@ def newton_raphson( 1.2186556186174883e-10 >>> newton_raphson('cos(x)', 0) Traceback (most recent call last): - ... + ... ZeroDivisionError: Could not find root """ diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py index 6e9b31bd1133..bb650ece3f5e 100644 --- a/backtracking/knight_tour.py +++ b/backtracking/knight_tour.py @@ -78,7 +78,7 @@ def open_knight_tour(n: int) -> list[list[int]]: >>> open_knight_tour(2) Traceback (most recent call last): - ... + ... ValueError: Open Kight Tour cannot be performed on a board of size 2 """ diff --git a/conversions/binary_to_decimal.py b/conversions/binary_to_decimal.py index a7625e475bdc..914a9318c225 100644 --- a/conversions/binary_to_decimal.py +++ b/conversions/binary_to_decimal.py @@ -12,15 +12,15 @@ def bin_to_decimal(bin_string: str) -> int: 0 >>> bin_to_decimal("a") Traceback (most recent call last): - ... + ... ValueError: Non-binary value was passed to the function >>> bin_to_decimal("") 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() diff --git a/conversions/binary_to_hexadecimal.py b/conversions/binary_to_hexadecimal.py index 89f7af696357..a3855bb70b52 100644 --- a/conversions/binary_to_hexadecimal.py +++ b/conversions/binary_to_hexadecimal.py @@ -30,11 +30,11 @@ def bin_to_hexadecimal(binary_str: str) -> str: '-0x1d' >>> bin_to_hexadecimal('a') Traceback (most recent call last): - ... + ... ValueError: Non-binary value was passed to the function >>> bin_to_hexadecimal('') Traceback (most recent call last): - ... + ... ValueError: Empty string was passed to the function """ # Sanitising parameter diff --git a/conversions/binary_to_octal.py b/conversions/binary_to_octal.py index 35ede95b134d..82f81e06234a 100644 --- a/conversions/binary_to_octal.py +++ b/conversions/binary_to_octal.py @@ -9,11 +9,11 @@ >>> 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 """ diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index 908c89e8fb6b..11a2af294829 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -29,32 +29,32 @@ def decimal_to_any(num: int, base: int) -> str: >>> # 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): diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index c21cdbcaec68..cfda57ca714a 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -19,12 +19,12 @@ def decimal_to_binary(num: int) -> str: >>> # 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 """ diff --git a/conversions/decimal_to_binary_recursion.py b/conversions/decimal_to_binary_recursion.py index c149ea86592f..05833ca670c3 100644 --- a/conversions/decimal_to_binary_recursion.py +++ b/conversions/decimal_to_binary_recursion.py @@ -7,7 +7,7 @@ def binary_recursive(decimal: int) -> str: '1001000' >>> binary_recursive("number") Traceback (most recent call last): - ... + ... ValueError: invalid literal for int() with base 10: 'number' """ decimal = int(decimal) @@ -30,11 +30,11 @@ def main(number: str) -> str: '-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() diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py index 2389c6d1f2a1..5ea48401f488 100644 --- a/conversions/decimal_to_hexadecimal.py +++ b/conversions/decimal_to_hexadecimal.py @@ -46,12 +46,12 @@ def decimal_to_hexadecimal(decimal: float) -> str: >>> # 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) diff --git a/conversions/hex_to_bin.py b/conversions/hex_to_bin.py index e358d810b581..b872ab5cbce6 100644 --- a/conversions/hex_to_bin.py +++ b/conversions/hex_to_bin.py @@ -21,11 +21,11 @@ def hex_to_bin(hex_num: str) -> int: -1111111111111111 >>> hex_to_bin("F-f") Traceback (most recent call last): - ... + ... ValueError: Invalid value was passed to the function >>> hex_to_bin("") Traceback (most recent call last): - ... + ... ValueError: No value was passed to the function """ diff --git a/conversions/hexadecimal_to_decimal.py b/conversions/hexadecimal_to_decimal.py index beb1c2c3ded6..209e4aebb368 100644 --- a/conversions/hexadecimal_to_decimal.py +++ b/conversions/hexadecimal_to_decimal.py @@ -18,15 +18,15 @@ def hex_to_decimal(hex_string: str) -> int: -255 >>> hex_to_decimal("F-f") Traceback (most recent call last): - ... + ... ValueError: Non-hexadecimal value was passed to the function >>> hex_to_decimal("") 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() diff --git a/conversions/octal_to_decimal.py b/conversions/octal_to_decimal.py index 551311e2651e..7f006f20e0c8 100644 --- a/conversions/octal_to_decimal.py +++ b/conversions/octal_to_decimal.py @@ -4,27 +4,27 @@ def oct_to_decimal(oct_string: str) -> int: >>> oct_to_decimal("") Traceback (most recent call last): - ... + ... ValueError: Empty string was passed to the function >>> oct_to_decimal("-") Traceback (most recent call last): - ... + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("e") Traceback (most recent call last): - ... + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("8") Traceback (most recent call last): - ... + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("-e") Traceback (most recent call last): - ... + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("-8") Traceback (most recent call last): - ... + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("1") 1 @@ -38,7 +38,7 @@ def oct_to_decimal(oct_string: str) -> int: -37 >>> oct_to_decimal("-") Traceback (most recent call last): - ... + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("0") 0 @@ -46,15 +46,15 @@ def oct_to_decimal(oct_string: str) -> int: -2093 >>> oct_to_decimal("2-0Fm") Traceback (most recent call last): - ... + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("") 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() diff --git a/conversions/temperature_conversions.py b/conversions/temperature_conversions.py index 167c9dc64727..e5af465561f9 100644 --- a/conversions/temperature_conversions.py +++ b/conversions/temperature_conversions.py @@ -23,7 +23,7 @@ def celsius_to_fahrenheit(celsius: float, ndigits: int = 2) -> float: 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, ndigits) @@ -47,7 +47,7 @@ def celsius_to_kelvin(celsius: float, ndigits: int = 2) -> float: 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) @@ -71,7 +71,7 @@ def celsius_to_rankine(celsius: float, ndigits: int = 2) -> float: 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) @@ -101,7 +101,7 @@ def fahrenheit_to_celsius(fahrenheit: float, ndigits: int = 2) -> float: 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, ndigits) @@ -131,7 +131,7 @@ def fahrenheit_to_kelvin(fahrenheit: float, ndigits: int = 2) -> float: 310.93 >>> fahrenheit_to_kelvin("fahrenheit") Traceback (most recent call last): - ... + ... ValueError: could not convert string to float: 'fahrenheit' """ return round(((float(fahrenheit) - 32) * 5 / 9) + 273.15, ndigits) @@ -161,7 +161,7 @@ def fahrenheit_to_rankine(fahrenheit: float, ndigits: int = 2) -> float: 559.67 >>> fahrenheit_to_rankine("fahrenheit") Traceback (most recent call last): - ... + ... ValueError: could not convert string to float: 'fahrenheit' """ return round(float(fahrenheit) + 459.67, ndigits) @@ -185,7 +185,7 @@ def kelvin_to_celsius(kelvin: float, ndigits: int = 2) -> float: 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, ndigits) @@ -209,7 +209,7 @@ def kelvin_to_fahrenheit(kelvin: float, ndigits: int = 2) -> float: 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) @@ -233,7 +233,7 @@ def kelvin_to_rankine(kelvin: float, ndigits: int = 2) -> float: 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) @@ -257,7 +257,7 @@ def rankine_to_celsius(rankine: float, ndigits: int = 2) -> float: -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) @@ -277,7 +277,7 @@ def rankine_to_fahrenheit(rankine: float, ndigits: int = 2) -> float: -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) @@ -297,7 +297,7 @@ def rankine_to_kelvin(rankine: float, ndigits: int = 2) -> float: 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) @@ -316,7 +316,7 @@ def reaumur_to_kelvin(reaumur: float, ndigits: int = 2) -> float: 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) @@ -335,7 +335,7 @@ def reaumur_to_fahrenheit(reaumur: float, ndigits: int = 2) -> float: 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) @@ -354,7 +354,7 @@ def reaumur_to_celsius(reaumur: float, ndigits: int = 2) -> float: 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) @@ -373,7 +373,7 @@ def reaumur_to_rankine(reaumur: float, ndigits: int = 2) -> float: 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) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index fc60540a1f3b..fc512944eb50 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -196,7 +196,7 @@ def binary_search_tree() -> None: 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) diff --git a/data_structures/binary_tree/binary_tree_mirror.py b/data_structures/binary_tree/binary_tree_mirror.py index cdd56e35d765..1ef950ad62d7 100644 --- a/data_structures/binary_tree/binary_tree_mirror.py +++ b/data_structures/binary_tree/binary_tree_mirror.py @@ -21,11 +21,11 @@ def binary_tree_mirror(binary_tree: dict, root: int = 1) -> dict: {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: 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 1ad8f2ed4287..684c518b1eb6 100644 --- a/data_structures/binary_tree/number_of_possible_binary_trees.py +++ b/data_structures/binary_tree/number_of_possible_binary_trees.py @@ -67,7 +67,7 @@ def factorial(n: int) -> int: True >>> factorial(-5) # doctest: +ELLIPSIS Traceback (most recent call last): - ... + ... ValueError: factorial() not defined for negative values """ if n < 0: diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 9e996ef0fb9d..90b6b6eb2a32 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -64,11 +64,11 @@ 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) @@ -78,7 +78,7 @@ def insert_at_nth(self, index: int, data): '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): @@ -114,7 +114,7 @@ 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) @@ -128,7 +128,7 @@ def delete_at_nth(self, index: int): '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: diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 89a05ae81d4c..3e52c7e43cf5 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -95,11 +95,11 @@ def __getitem__(self, index: int) -> Any: 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 not 0 <= index < len(self): @@ -122,11 +122,11 @@ def __setitem__(self, index: int, data: Any) -> None: -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): @@ -233,7 +233,7 @@ def delete_head(self) -> Any: 'third' >>> linked_list.delete_head() Traceback (most recent call last): - ... + ... IndexError: List index out of range. """ return self.delete_nth(0) @@ -260,7 +260,7 @@ def delete_tail(self) -> Any: # delete from tail 'first' >>> linked_list.delete_tail() Traceback (most recent call last): - ... + ... IndexError: List index out of range. """ return self.delete_nth(len(self) - 1) @@ -281,11 +281,11 @@ def delete_nth(self, index: int = 0) -> Any: first->third >>> linked_list.delete_nth(5) # this raises error Traceback (most recent call last): - ... + ... IndexError: List index out of range. >>> linked_list.delete_nth(-1) # this also raises error Traceback (most recent call last): - ... + ... IndexError: List index out of range. """ if not 0 <= index <= len(self) - 1: # test if index is valid diff --git a/data_structures/queue/linked_queue.py b/data_structures/queue/linked_queue.py index 3675da7db78a..3af97d28e4f7 100644 --- a/data_structures/queue/linked_queue.py +++ b/data_structures/queue/linked_queue.py @@ -96,7 +96,7 @@ def put(self, item: Any) -> None: >>> queue = LinkedQueue() >>> queue.get() Traceback (most recent call last): - ... + ... IndexError: dequeue from empty queue >>> for i in range(1, 6): ... queue.put(i) @@ -116,7 +116,7 @@ def get(self) -> Any: >>> queue = LinkedQueue() >>> queue.get() Traceback (most recent call last): - ... + ... IndexError: dequeue from empty queue >>> queue = LinkedQueue() >>> for i in range(1, 6): diff --git a/data_structures/queue/priority_queue_using_list.py b/data_structures/queue/priority_queue_using_list.py index c5cf26433fff..f61b5e8e664d 100644 --- a/data_structures/queue/priority_queue_using_list.py +++ b/data_structures/queue/priority_queue_using_list.py @@ -58,7 +58,7 @@ class FixedPriorityQueue: 4 >>> fpq.dequeue() Traceback (most recent call last): - ... + ... data_structures.queue.priority_queue_using_list.UnderFlowError: All queues are empty >>> print(fpq) Priority 0: [] diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index b812d108e290..9017443091cf 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -21,7 +21,7 @@ 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("") '' diff --git a/data_structures/stacks/stack_with_singly_linked_list.py b/data_structures/stacks/stack_with_singly_linked_list.py index 903ae39db4b5..f5ce83b863ce 100644 --- a/data_structures/stacks/stack_with_singly_linked_list.py +++ b/data_structures/stacks/stack_with_singly_linked_list.py @@ -109,7 +109,7 @@ def pop(self) -> T: >>> stack = LinkedStack() >>> stack.pop() Traceback (most recent call last): - ... + ... IndexError: pop from empty stack >>> stack.push("c") >>> stack.push("b") diff --git a/dynamic_programming/longest_common_substring.py b/dynamic_programming/longest_common_substring.py index 84a9f18609f9..e2f944a5e336 100644 --- a/dynamic_programming/longest_common_substring.py +++ b/dynamic_programming/longest_common_substring.py @@ -32,7 +32,7 @@ def longest_common_substring(text1: str, text2: str) -> str: 'Site:Geeks' >>> longest_common_substring(1, 1) Traceback (most recent call last): - ... + ... ValueError: longest_common_substring() takes two strings for inputs """ diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index 3227adf53ae4..5cf8d691b1d7 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -32,17 +32,17 @@ def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int, >>> 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 """ diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index b9791c860a74..079731487b3a 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -168,7 +168,7 @@ def euclidean_length(self) -> float: 9.539392014169456 >>> Vector([]).euclidean_length() Traceback (most recent call last): - ... + ... Exception: Vector is empty """ if len(self.__components) == 0: @@ -186,7 +186,7 @@ def angle(self, other: Vector, deg: bool = False) -> float: 85.40775111366095 >>> Vector([3, 4, -1]).angle(Vector([2, -1])) Traceback (most recent call last): - ... + ... Exception: invalid operand! """ num = self * other diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py index ec1b9f9e3e13..2f5fc46c065e 100644 --- a/machine_learning/similarity_search.py +++ b/machine_learning/similarity_search.py @@ -70,7 +70,7 @@ def similarity_search( >>> 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. @@ -80,7 +80,7 @@ def similarity_search( >>> 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. @@ -90,7 +90,7 @@ def similarity_search( >>> 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 """ diff --git a/maths/bisection.py b/maths/bisection.py index 93cc2247b64e..45f26d8d88e4 100644 --- a/maths/bisection.py +++ b/maths/bisection.py @@ -32,7 +32,7 @@ def bisection(a: float, b: float) -> float: 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 diff --git a/maths/catalan_number.py b/maths/catalan_number.py index 4a1280a45bf2..85607dc1eca4 100644 --- a/maths/catalan_number.py +++ b/maths/catalan_number.py @@ -18,15 +18,15 @@ def catalan(number: int) -> int: 14 >>> catalan(0) Traceback (most recent call last): - ... + ... ValueError: Input value of [number=0] must be > 0 >>> catalan(-1) Traceback (most recent call last): - ... + ... ValueError: Input value of [number=-1] must be > 0 >>> catalan(5.0) Traceback (most recent call last): - ... + ... TypeError: Input value of [number=5.0] must be an integer """ diff --git a/maths/fibonacci.py b/maths/fibonacci.py index 07bd6d2ece51..e0da66ee5e3b 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -47,7 +47,7 @@ def fib_iterative(n: int) -> list[int]: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] >>> fib_iterative(-1) Traceback (most recent call last): - ... + ... Exception: n is negative """ if n < 0: @@ -73,7 +73,7 @@ def fib_recursive(n: int) -> list[int]: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] >>> fib_iterative(-1) Traceback (most recent call last): - ... + ... Exception: n is negative """ @@ -105,7 +105,7 @@ def fib_memoization(n: int) -> list[int]: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] >>> fib_iterative(-1) Traceback (most recent call last): - ... + ... Exception: n is negative """ if n < 0: @@ -146,11 +146,11 @@ def fib_binet(n: int) -> list[int]: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] >>> fib_binet(-1) Traceback (most recent call last): - ... + ... Exception: n is negative >>> fib_binet(1475) Traceback (most recent call last): - ... + ... Exception: n is too large """ if n < 0: diff --git a/maths/maclaurin_series.py b/maths/maclaurin_series.py index a2619d4e6b92..e55839bc15ba 100644 --- a/maths/maclaurin_series.py +++ b/maths/maclaurin_series.py @@ -26,19 +26,19 @@ def maclaurin_sin(theta: float, accuracy: int = 30) -> float: 0.5440211108893703 >>> maclaurin_sin("10") Traceback (most recent call last): - ... + ... ValueError: maclaurin_sin() requires either an int or float for theta >>> maclaurin_sin(10, -30) Traceback (most recent call last): - ... + ... ValueError: maclaurin_sin() requires a positive int for accuracy >>> maclaurin_sin(10, 30.5) Traceback (most recent call last): - ... + ... ValueError: maclaurin_sin() requires a positive int for accuracy >>> maclaurin_sin(10, "30") Traceback (most recent call last): - ... + ... ValueError: maclaurin_sin() requires a positive int for accuracy """ @@ -78,19 +78,19 @@ def maclaurin_cos(theta: float, accuracy: int = 30) -> float: -0.8390715290764521 >>> maclaurin_cos("10") Traceback (most recent call last): - ... + ... ValueError: maclaurin_cos() requires either an int or float for theta >>> maclaurin_cos(10, -30) Traceback (most recent call last): - ... + ... ValueError: maclaurin_cos() requires a positive int for accuracy >>> maclaurin_cos(10, 30.5) Traceback (most recent call last): - ... + ... ValueError: maclaurin_cos() requires a positive int for accuracy >>> maclaurin_cos(10, "30") Traceback (most recent call last): - ... + ... ValueError: maclaurin_cos() requires a positive int for accuracy """ diff --git a/maths/proth_number.py b/maths/proth_number.py index 6b15190249f0..ce911473a2d2 100644 --- a/maths/proth_number.py +++ b/maths/proth_number.py @@ -16,15 +16,15 @@ def proth(number: int) -> int: 25 >>> proth(0) Traceback (most recent call last): - ... + ... ValueError: Input value of [number=0] must be > 0 >>> proth(-1) Traceback (most recent call last): - ... + ... ValueError: Input value of [number=-1] must be > 0 >>> proth(6.0) Traceback (most recent call last): - ... + ... TypeError: Input value of [number=6.0] must be an integer """ diff --git a/maths/sylvester_sequence.py b/maths/sylvester_sequence.py index 0cd99affe046..114c9dd58582 100644 --- a/maths/sylvester_sequence.py +++ b/maths/sylvester_sequence.py @@ -18,12 +18,12 @@ def sylvester(number: int) -> int: >>> sylvester(-1) Traceback (most recent call last): - ... + ... ValueError: The input value of [n=-1] has to be > 0 >>> sylvester(8.0) Traceback (most recent call last): - ... + ... AssertionError: The input value of [n=8.0] is not an integer """ assert isinstance(number, int), f"The input value of [n={number}] is not an integer" diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index 2d4a22a0a5ba..624bbfe1061c 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -14,11 +14,11 @@ def zeller(date_input: str) -> str: 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: diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index f04c81424c81..487842067ca3 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -29,15 +29,15 @@ def __init__( >>> 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 diff --git a/project_euler/problem_004/sol1.py b/project_euler/problem_004/sol1.py index db6133a1a1d2..b1e229289988 100644 --- a/project_euler/problem_004/sol1.py +++ b/project_euler/problem_004/sol1.py @@ -26,7 +26,7 @@ def solution(n: int = 998001) -> int: 39893 >>> solution(10000) Traceback (most recent call last): - ... + ... ValueError: That number is larger than our acceptable range. """ diff --git a/project_euler/problem_010/sol3.py b/project_euler/problem_010/sol3.py index 72e2894df293..60abbd57194b 100644 --- a/project_euler/problem_010/sol3.py +++ b/project_euler/problem_010/sol3.py @@ -30,15 +30,15 @@ def solution(n: int = 2000000) -> int: 10 >>> solution(7.1) # doctest: +ELLIPSIS Traceback (most recent call last): - ... + ... TypeError: 'float' object cannot be interpreted as an integer >>> solution(-7) # doctest: +ELLIPSIS Traceback (most recent call last): - ... + ... IndexError: list assignment index out of range >>> solution("seven") # doctest: +ELLIPSIS Traceback (most recent call last): - ... + ... TypeError: can only concatenate str (not "int") to str """ diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index f4fa8e1203df..35e6bc506661 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -101,7 +101,7 @@ def __assert_sorted(collection): True >>> __assert_sorted([10, -1, 5]) Traceback (most recent call last): - ... + ... ValueError: Collection must be ascending sorted """ if collection != sorted(collection): diff --git a/sorts/bead_sort.py b/sorts/bead_sort.py index d22367c52fa9..e51173643d81 100644 --- a/sorts/bead_sort.py +++ b/sorts/bead_sort.py @@ -20,12 +20,12 @@ def bead_sort(sequence: list) -> list: >>> bead_sort([1, .9, 0.0, 0, -1, -.9]) Traceback (most recent call last): - ... + ... TypeError: Sequence must be list of non-negative integers >>> bead_sort("Hello world") Traceback (most recent call last): - ... + ... TypeError: Sequence must be list of non-negative integers """ if any(not isinstance(x, int) or x < 0 for x in sequence): diff --git a/sorts/msd_radix_sort.py b/sorts/msd_radix_sort.py index 7430fc5a63c8..84460e47b440 100644 --- a/sorts/msd_radix_sort.py +++ b/sorts/msd_radix_sort.py @@ -23,7 +23,7 @@ def msd_radix_sort(list_of_ints: list[int]) -> list[int]: [1, 45, 1209, 540402, 834598] >>> msd_radix_sort([-1, 34, 45]) Traceback (most recent call last): - ... + ... ValueError: All numbers must be positive """ if not list_of_ints: @@ -93,7 +93,7 @@ def msd_radix_sort_inplace(list_of_ints: list[int]): >>> lst = [-1, 34, 23, 4, -42] >>> msd_radix_sort_inplace(lst) Traceback (most recent call last): - ... + ... ValueError: All numbers must be positive """ diff --git a/strings/barcode_validator.py b/strings/barcode_validator.py index 2e1ea87039ef..e050cd337d74 100644 --- a/strings/barcode_validator.py +++ b/strings/barcode_validator.py @@ -47,7 +47,7 @@ def is_valid(barcode: int) -> bool: False >>> is_valid(dwefgiweuf) Traceback (most recent call last): - ... + ... NameError: name 'dwefgiweuf' is not defined """ return len(str(barcode)) == 13 and get_check_digit(barcode) == barcode % 10 @@ -61,7 +61,7 @@ def get_barcode(barcode: str) -> int: 8718452538119 >>> get_barcode("dwefgiweuf") Traceback (most recent call last): - ... + ... ValueError: Barcode 'dwefgiweuf' has alphabetic characters. """ if str(barcode).isalpha(): diff --git a/strings/join.py b/strings/join.py index c17ddd144597..739856c1aa93 100644 --- a/strings/join.py +++ b/strings/join.py @@ -15,7 +15,7 @@ def join(separator: str, separated: list[str]) -> str: 'You are amazing!' >>> join("#", ["a", "b", "c", 1]) Traceback (most recent call last): - ... + ... Exception: join() accepts only strings to be joined """ joined = "" From 71e8ed81aeb24820a03b968633884ac10b047ad4 Mon Sep 17 00:00:00 2001 From: Matteo Messmer <40521259+matteomessmer@users.noreply.github.com> Date: Thu, 27 Oct 2022 19:45:58 +0200 Subject: [PATCH 1974/2908] Added spheres union (#6879) * Spheres union * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update volume.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update volume.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * f-strings * Update maths/volume.py Co-authored-by: Christian Clauss * more tests * fix non negative * fix 0 radius * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix tests * fix print * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix comment * fix comment * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update volume.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/volume.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/maths/volume.py b/maths/volume.py index a594e1b90feb..da4054646659 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -108,6 +108,51 @@ def vol_spheres_intersect( return vol_spherical_cap(h1, radius_2) + vol_spherical_cap(h2, radius_1) +def vol_spheres_union( + radius_1: float, radius_2: float, centers_distance: float +) -> float: + """ + Calculate the volume of the union of two spheres that possibly intersect. + It is the sum of sphere A and sphere B minus their intersection. + First, it calculates the volumes (v1, v2) of the spheres, + then the volume of the intersection (i) and it returns the sum v1+v2-i. + If centers_distance is 0 then it returns the volume of the larger sphere + :return vol_sphere(radius_1) + vol_sphere(radius_2) + - vol_spheres_intersect(radius_1, radius_2, centers_distance) + + >>> vol_spheres_union(2, 2, 1) + 45.814892864851146 + >>> vol_spheres_union(1.56, 2.2, 1.4) + 48.77802773671288 + >>> vol_spheres_union(0, 2, 1) + Traceback (most recent call last): + ... + ValueError: vol_spheres_union() only accepts non-negative values, non-zero radius + >>> vol_spheres_union('1.56', '2.2', '1.4') + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'str' and 'int' + >>> vol_spheres_union(1, None, 1) + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'NoneType' and 'int' + """ + + if radius_1 <= 0 or radius_2 <= 0 or centers_distance < 0: + raise ValueError( + "vol_spheres_union() only accepts non-negative values, non-zero radius" + ) + + if centers_distance == 0: + return vol_sphere(max(radius_1, radius_2)) + + return ( + vol_sphere(radius_1) + + vol_sphere(radius_2) + - vol_spheres_intersect(radius_1, radius_2, centers_distance) + ) + + def vol_cuboid(width: float, height: float, length: float) -> float: """ Calculate the Volume of a Cuboid. @@ -408,12 +453,13 @@ def main(): print(f"Sphere: {vol_sphere(2) = }") # ~= 33.5 print(f"Hemisphere: {vol_hemisphere(2) = }") # ~= 16.75 print(f"Circular Cylinder: {vol_circular_cylinder(2, 2) = }") # ~= 25.1 - print( - f"Hollow Circular Cylinder: {vol_hollow_circular_cylinder(1, 2, 3) = }" - ) # ~= 28.3 print(f"Conical Frustum: {vol_conical_frustum(2, 2, 4) = }") # ~= 58.6 print(f"Spherical cap: {vol_spherical_cap(1, 2) = }") # ~= 5.24 print(f"Spheres intersetion: {vol_spheres_intersect(2, 2, 1) = }") # ~= 21.21 + print(f"Spheres union: {vol_spheres_union(2, 2, 1) = }") # ~= 45.81 + print( + f"Hollow Circular Cylinder: {vol_hollow_circular_cylinder(1, 2, 3) = }" + ) # ~= 28.3 if __name__ == "__main__": From 501a1cf0c7b31773fb02bc2966f5c1db99311b36 Mon Sep 17 00:00:00 2001 From: Alexandre Velloso <4320811+AlexandreVelloso@users.noreply.github.com> Date: Thu, 27 Oct 2022 21:51:14 +0100 Subject: [PATCH 1975/2908] Remove unnecessary else statement (#7759) * Remove unnecessary else statement * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/karatsuba.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/maths/karatsuba.py b/maths/karatsuba.py index b772c0d77039..4bf4aecdc068 100644 --- a/maths/karatsuba.py +++ b/maths/karatsuba.py @@ -10,18 +10,18 @@ def karatsuba(a, b): """ 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) + m1 = max(len(str(a)), len(str(b))) + m2 = m1 // 2 - x = karatsuba(a2, b2) - y = karatsuba((a1 + a2), (b1 + b2)) - z = karatsuba(a1, b1) + a1, a2 = divmod(a, 10**m2) + b1, b2 = divmod(b, 10**m2) - return (z * 10 ** (2 * m2)) + ((y - z - x) * 10 ** (m2)) + (x) + 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(): From 61eedc16c392823e46ef37cc2a86864fa15e89fe Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 27 Oct 2022 21:52:00 +0100 Subject: [PATCH 1976/2908] Remove useless code in doctests (#7733) * refactor: Fix matrix display deprecation * refactor: Remove useless `print` and `pass` statements * revert: Replace broken doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * revert: Fix failing doctests * chore: Satisfy pre-commit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- backtracking/hamiltonian_cycle.py | 4 ++-- computer_vision/flip_augmentation.py | 3 --- computer_vision/mosaic_augmentation.py | 3 --- data_structures/heap/binomial_heap.py | 4 ++-- data_structures/heap/heap.py | 8 ++++---- data_structures/heap/min_heap.py | 2 +- data_structures/linked_list/skip_list.py | 3 +++ graphs/gale_shapley_bigraph.py | 2 +- graphs/graph_list.py | 6 +++--- graphs/minimum_spanning_tree_prims2.py | 8 ++++---- graphs/random_graph_generator.py | 2 +- .../local_weighted_learning.py | 2 -- maths/polynomial_evaluation.py | 2 +- maths/radix2_fft.py | 2 +- matrix/matrix_class.py | 6 +++--- searches/simple_binary_search.py | 20 +++++++++---------- sorts/bitonic_sort.py | 12 +++++------ sorts/normal_distribution_quick_sort.md | 4 ++-- sorts/recursive_insertion_sort.py | 12 +++++------ web_programming/reddit.py | 2 -- web_programming/search_books_by_isbn.py | 5 +---- 21 files changed, 51 insertions(+), 61 deletions(-) diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index 4c6ae46799f4..4a4156d70b32 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -71,7 +71,7 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) >>> curr_ind = 1 >>> util_hamilton_cycle(graph, path, curr_ind) True - >>> print(path) + >>> path [0, 1, 2, 4, 3, 0] Case 2: Use exact graph as in previous case, but in the properties taken from @@ -85,7 +85,7 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) >>> curr_ind = 3 >>> util_hamilton_cycle(graph, path, curr_ind) True - >>> print(path) + >>> path [0, 1, 2, 4, 3, 0] """ diff --git a/computer_vision/flip_augmentation.py b/computer_vision/flip_augmentation.py index 1272357fd03e..93b4e3f6da79 100644 --- a/computer_vision/flip_augmentation.py +++ b/computer_vision/flip_augmentation.py @@ -22,7 +22,6 @@ def main() -> None: Get images list and annotations list from input dir. Update new images and annotations. Save images and annotations in output dir. - >>> pass # A doctest is not possible for this function. """ img_paths, annos = get_dataset(LABEL_DIR, IMAGE_DIR) print("Processing...") @@ -48,7 +47,6 @@ def get_dataset(label_dir: str, img_dir: str) -> tuple[list, list]: - label_dir : Path to label include annotation of images - img_dir : Path to folder contain images Return : List of images path and labels - >>> pass # A doctest is not possible for this function. """ img_paths = [] labels = [] @@ -88,7 +86,6 @@ def update_image_and_anno( - new_imgs_list : image after resize - new_annos_lists : list of new annotation after scale - path_list : list the name of image file - >>> pass # A doctest is not possible for this function. """ new_annos_lists = [] path_list = [] diff --git a/computer_vision/mosaic_augmentation.py b/computer_vision/mosaic_augmentation.py index 4fd81957ce2a..e2953749753f 100644 --- a/computer_vision/mosaic_augmentation.py +++ b/computer_vision/mosaic_augmentation.py @@ -23,7 +23,6 @@ def main() -> None: Get images list and annotations list from input dir. Update new images and annotations. Save images and annotations in output dir. - >>> pass # A doctest is not possible for this function. """ img_paths, annos = get_dataset(LABEL_DIR, IMG_DIR) for index in range(NUMBER_IMAGES): @@ -60,7 +59,6 @@ def get_dataset(label_dir: str, img_dir: str) -> tuple[list, list]: - label_dir : Path to label include annotation of images - img_dir : Path to folder contain images Return : List of images path and labels - >>> pass # A doctest is not possible for this function. """ img_paths = [] labels = [] @@ -105,7 +103,6 @@ def update_image_and_anno( - output_img : image after resize - new_anno : list of new annotation after scale - path[0] : get the name of image file - >>> pass # A doctest is not possible for this function. """ output_img = np.zeros([output_size[0], output_size[1], 3], dtype=np.uint8) scale_x = scale_range[0] + random.random() * (scale_range[1] - scale_range[0]) diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index 334b444eaaff..6398c99439cd 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -71,7 +71,7 @@ class BinomialHeap: ... first_heap.insert(number) Size test - >>> print(first_heap.size) + >>> first_heap.size 30 Deleting - delete() test @@ -97,7 +97,7 @@ class BinomialHeap: # # # # preOrder() test - >>> print(second_heap.preOrder()) + >>> second_heap.preOrder() [(17, 0), ('#', 1), (31, 1), (20, 2), ('#', 3), ('#', 3), (34, 2), ('#', 3), ('#', 3)] printing Heap - __str__() test diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 4c19747ec823..071790d18448 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -9,20 +9,20 @@ class Heap: >>> unsorted = [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5] >>> h = Heap() >>> h.build_max_heap(unsorted) - >>> print(h) + >>> h [209, 201, 25, 103, 107, 15, 1, 9, 7, 11, 5] >>> >>> h.extract_max() 209 - >>> print(h) + >>> h [201, 107, 25, 103, 11, 15, 1, 9, 7, 5] >>> >>> h.insert(100) - >>> print(h) + >>> h [201, 107, 25, 103, 100, 15, 1, 9, 7, 5, 11] >>> >>> h.heap_sort() - >>> print(h) + >>> h [1, 5, 7, 9, 11, 15, 25, 100, 103, 107, 201] """ diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index d8975eb2dcc7..0403624f285a 100644 --- a/data_structures/heap/min_heap.py +++ b/data_structures/heap/min_heap.py @@ -27,7 +27,7 @@ class MinHeap: >>> myMinHeap.decrease_key(b, -17) >>> print(b) Node(B, -17) - >>> print(myMinHeap["B"]) + >>> myMinHeap["B"] -17 """ diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index a667e3e9bc84..96b0db7c896b 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -443,4 +443,7 @@ def main(): if __name__ == "__main__": + import doctest + + doctest.testmod() main() diff --git a/graphs/gale_shapley_bigraph.py b/graphs/gale_shapley_bigraph.py index 56b8c6c77bcb..f4b3153817c4 100644 --- a/graphs/gale_shapley_bigraph.py +++ b/graphs/gale_shapley_bigraph.py @@ -17,7 +17,7 @@ def stable_matching( >>> 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)) + >>> stable_matching(donor_pref, recipient_pref) [1, 2, 3, 0] """ assert len(donor_pref) == len(recipient_pref) diff --git a/graphs/graph_list.py b/graphs/graph_list.py index f04b7a92390d..e871f3b8a9d6 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -18,7 +18,7 @@ class GraphAdjacencyList(Generic[T]): Directed graph example: >>> d_graph = GraphAdjacencyList() - >>> d_graph + >>> print(d_graph) {} >>> d_graph.add_edge(0, 1) {0: [1], 1: []} @@ -26,7 +26,7 @@ class GraphAdjacencyList(Generic[T]): {0: [1], 1: [2, 4, 5], 2: [], 4: [], 5: []} >>> d_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} - >>> print(d_graph) + >>> d_graph {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} >>> print(repr(d_graph)) {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} @@ -68,7 +68,7 @@ class GraphAdjacencyList(Generic[T]): {'a': ['b'], 'b': ['a']} >>> char_graph.add_edge('b', 'c').add_edge('b', 'e').add_edge('b', 'f') {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} - >>> print(char_graph) + >>> char_graph {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} """ diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index d924ee3db1e5..707be783d087 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -69,16 +69,16 @@ class MinPriorityQueue(Generic[T]): >>> queue.push(3, 4000) >>> queue.push(4, 3000) - >>> print(queue.extract_min()) + >>> queue.extract_min() 2 >>> queue.update_key(4, 50) - >>> print(queue.extract_min()) + >>> queue.extract_min() 4 - >>> print(queue.extract_min()) + >>> queue.extract_min() 1 - >>> print(queue.extract_min()) + >>> queue.extract_min() 3 """ diff --git a/graphs/random_graph_generator.py b/graphs/random_graph_generator.py index 15ccee5b399c..0e7e18bc8fd9 100644 --- a/graphs/random_graph_generator.py +++ b/graphs/random_graph_generator.py @@ -53,7 +53,7 @@ def complete_graph(vertices_number: int) -> dict: @input: vertices_number (number of vertices), directed (False if the graph is undirected, True otherwise) @example: - >>> print(complete_graph(3)) + >>> complete_graph(3) {0: [1, 2], 1: [0, 2], 2: [0, 1]} """ return { diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index 6c542ab825aa..df03fe0a178d 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -71,7 +71,6 @@ def local_weight_regression( def load_data(dataset_name: str, cola_name: str, colb_name: str) -> np.mat: """ Function used for loading data from the seaborn splitting into x and y points - >>> pass # this function has no doctest """ import seaborn as sns @@ -112,7 +111,6 @@ def plot_preds( ) -> plt.plot: """ This function used to plot predictions and display the graph - >>> pass #this function has no doctest """ xsort = training_data_x.copy() xsort.sort(axis=0) diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index 8ee82467efa1..90a51f521e01 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -45,7 +45,7 @@ def horner(poly: Sequence[float], x: float) -> float: >>> 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 >>> # f(-13) = 7.0(-13)^4 + 9.3(-13)^3 + 5.0(-13)^2 = 180339.9 - >>> print(evaluate_poly(poly, x)) + >>> evaluate_poly(poly, x) 180339.9 """ poly = (0.0, 0.0, 5.0, 9.3, 7.0) diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index 52442134de59..1def58e1f226 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -39,7 +39,7 @@ class FFT: >>> x = FFT(A, B) Print product - >>> print(x.product) # 2x + 3x^2 + 8x^3 + 4x^4 + 6x^5 + >>> 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 diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 8b6fefa2124b..0c3078fe6dc8 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -21,9 +21,9 @@ class Matrix: [7. 8. 9.]] Matrix rows and columns are available as 2D arrays - >>> print(matrix.rows) + >>> matrix.rows [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - >>> print(matrix.columns()) + >>> matrix.columns() [[1, 4, 7], [2, 5, 8], [3, 6, 9]] Order is returned as a tuple @@ -55,7 +55,7 @@ class Matrix: [[-3. 6. -3.] [6. -12. 6.] [-3. 6. -3.]] - >>> print(matrix.inverse()) + >>> matrix.inverse() Traceback (most recent call last): ... TypeError: Only matrices with a non-zero determinant have an inverse diff --git a/searches/simple_binary_search.py b/searches/simple_binary_search.py index d1f7f7a51cbc..ff043d7369af 100644 --- a/searches/simple_binary_search.py +++ b/searches/simple_binary_search.py @@ -13,25 +13,25 @@ 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)) + >>> binary_search(test_list, 3) False - >>> print(binary_search(test_list, 13)) + >>> binary_search(test_list, 13) True - >>> print(binary_search([4, 4, 5, 6, 7], 4)) + >>> binary_search([4, 4, 5, 6, 7], 4) True - >>> print(binary_search([4, 4, 5, 6, 7], -10)) + >>> binary_search([4, 4, 5, 6, 7], -10) False - >>> print(binary_search([-18, 2], -18)) + >>> binary_search([-18, 2], -18) True - >>> print(binary_search([5], 5)) + >>> binary_search([5], 5) True - >>> print(binary_search(['a', 'c', 'd'], 'c')) + >>> binary_search(['a', 'c', 'd'], 'c') True - >>> print(binary_search(['a', 'c', 'd'], 'f')) + >>> binary_search(['a', 'c', 'd'], 'f') False - >>> print(binary_search([], 1)) + >>> binary_search([], 1) False - >>> print(binary_search([-.1, .1 , .8], .1)) + >>> binary_search([-.1, .1 , .8], .1) True >>> binary_search(range(-5000, 5000, 10), 80) True diff --git a/sorts/bitonic_sort.py b/sorts/bitonic_sort.py index 201fecd2ce86..b65f877a45e3 100644 --- a/sorts/bitonic_sort.py +++ b/sorts/bitonic_sort.py @@ -16,19 +16,19 @@ def comp_and_swap(array: list[int], index1: int, index2: int, direction: int) -> >>> arr = [12, 42, -21, 1] >>> comp_and_swap(arr, 1, 2, 1) - >>> print(arr) + >>> arr [12, -21, 42, 1] >>> comp_and_swap(arr, 1, 2, 0) - >>> print(arr) + >>> arr [12, 42, -21, 1] >>> comp_and_swap(arr, 0, 3, 1) - >>> print(arr) + >>> arr [1, 42, -21, 12] >>> comp_and_swap(arr, 0, 3, 0) - >>> print(arr) + >>> arr [12, 42, -21, 1] """ if (direction == 1 and array[index1] > array[index2]) or ( @@ -46,11 +46,11 @@ def bitonic_merge(array: list[int], low: int, length: int, direction: int) -> No >>> arr = [12, 42, -21, 1] >>> bitonic_merge(arr, 0, 4, 1) - >>> print(arr) + >>> arr [-21, 1, 12, 42] >>> bitonic_merge(arr, 0, 4, 0) - >>> print(arr) + >>> arr [42, 12, 1, -21] """ if length > 1: diff --git a/sorts/normal_distribution_quick_sort.md b/sorts/normal_distribution_quick_sort.md index c073f2cbc81c..27aca340fb3b 100644 --- a/sorts/normal_distribution_quick_sort.md +++ b/sorts/normal_distribution_quick_sort.md @@ -17,8 +17,8 @@ The array elements are taken from a Standard Normal Distribution, having mean = >>> mu, sigma = 0, 1 # mean and standard deviation >>> X = np.random.normal(mu, sigma, p) >>> np.save(outfile, X) ->>> print('The array is') ->>> print(X) +>>> 'The array is' +>>> X ``` diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index ab2716f8eae5..297dbe9457e6 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -14,17 +14,17 @@ def rec_insertion_sort(collection: list, n: int): >>> col = [1, 2, 1] >>> rec_insertion_sort(col, len(col)) - >>> print(col) + >>> col [1, 1, 2] >>> col = [2, 1, 0, -1, -2] >>> rec_insertion_sort(col, len(col)) - >>> print(col) + >>> col [-2, -1, 0, 1, 2] >>> col = [1] >>> rec_insertion_sort(col, len(col)) - >>> print(col) + >>> col [1] """ # Checks if the entire collection has been sorted @@ -41,17 +41,17 @@ def insert_next(collection: list, index: int): >>> col = [3, 2, 4, 2] >>> insert_next(col, 1) - >>> print(col) + >>> col [2, 3, 4, 2] >>> col = [3, 2, 3] >>> insert_next(col, 2) - >>> print(col) + >>> col [3, 2, 3] >>> col = [] >>> insert_next(col, 1) - >>> print(col) + >>> col [] """ # Checks order between adjacent elements diff --git a/web_programming/reddit.py b/web_programming/reddit.py index 672109f1399d..6a31c81c34bd 100644 --- a/web_programming/reddit.py +++ b/web_programming/reddit.py @@ -23,8 +23,6 @@ def get_subreddit_data( limit : Number of posts to fetch age : ["new", "top", "hot"] wanted_data : Get only the required data in the list - - >>> pass """ wanted_data = wanted_data or [] if invalid_search_terms := ", ".join(sorted(set(wanted_data) - valid_terms)): diff --git a/web_programming/search_books_by_isbn.py b/web_programming/search_books_by_isbn.py index 22a31dcb1db4..abac3c70b22e 100644 --- a/web_programming/search_books_by_isbn.py +++ b/web_programming/search_books_by_isbn.py @@ -19,7 +19,6 @@ def get_openlibrary_data(olid: str = "isbn/0140328726") -> dict: {'publishers': ['Puffin'], 'number_of_pages': 96, 'isbn_10': ['0140328726'], ... # >>> get_openlibrary_data(olid='/authors/OL7353617A') # doctest: +ELLIPSIS {'name': 'Adrian Brisku', 'created': {'type': '/type/datetime', ... - >>> pass # Placate https://github.com/apps/algorithms-keeper """ new_olid = olid.strip().strip("/") # Remove leading/trailing whitespace & slashes if new_olid.count("/") != 1: @@ -29,9 +28,7 @@ def get_openlibrary_data(olid: str = "isbn/0140328726") -> dict: def summarize_book(ol_book_data: dict) -> dict: """ - Given Open Library book data, return a summary as a Python dict. - - >>> pass # Placate https://github.com/apps/algorithms-keeper + Given Open Library book data, return a summary as a Python dict. """ desired_keys = { "title": "Title", From de3271ec80c76a8b79c913f68a94f693e8a00a0b Mon Sep 17 00:00:00 2001 From: SwayamSahu <91021799+SwayamSahu@users.noreply.github.com> Date: Fri, 28 Oct 2022 02:32:15 +0530 Subject: [PATCH 1977/2908] Refactoring the syntax using list comprehension (#7749) * Refactoring the syntax using list comprehension * Update detecting_english_programmatically.py * Update detecting_english_programmatically.py Co-authored-by: Christian Clauss --- strings/detecting_english_programmatically.py | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/strings/detecting_english_programmatically.py b/strings/detecting_english_programmatically.py index aa18db21027a..b9000101beb4 100644 --- a/strings/detecting_english_programmatically.py +++ b/strings/detecting_english_programmatically.py @@ -1,7 +1,7 @@ import os +from string import ascii_letters -UPPERLETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + " \t\n" +LETTERS_AND_SPACE = ascii_letters + " \t\n" def load_dictionary() -> dict[str, None]: @@ -20,24 +20,12 @@ def get_english_count(message: str) -> float: message = message.upper() message = remove_non_letters(message) possible_words = message.split() - - if possible_words == []: - return 0.0 - - matches = 0 - for word in possible_words: - if word in ENGLISH_WORDS: - matches += 1 - + matches = len([word for word in possible_words if word in ENGLISH_WORDS]) return float(matches) / len(possible_words) def remove_non_letters(message: str) -> str: - letters_only = [] - for symbol in message: - if symbol in LETTERS_AND_SPACE: - letters_only.append(symbol) - return "".join(letters_only) + return "".join(symbol for symbol in message if symbol in LETTERS_AND_SPACE) def is_english( From 25757e697cfbb5bc7abf47c1ffa13061cb1534e1 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 27 Oct 2022 22:03:01 +0100 Subject: [PATCH 1978/2908] Binary tree path sum (#7748) * feat: Implement binary tree path sum (#7135) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/binary_tree/binary_tree_path_sum.py Co-authored-by: Christian Clauss * refactor: Rename `dfs` to `depth_first_search` Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../binary_tree/binary_tree_path_sum.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 data_structures/binary_tree/binary_tree_path_sum.py diff --git a/data_structures/binary_tree/binary_tree_path_sum.py b/data_structures/binary_tree/binary_tree_path_sum.py new file mode 100644 index 000000000000..a3fe9ca7a7e2 --- /dev/null +++ b/data_structures/binary_tree/binary_tree_path_sum.py @@ -0,0 +1,88 @@ +""" +Given the root of a binary tree and an integer target, +find the number of paths where the sum of the values +along the path equals target. + + +Leetcode reference: https://leetcode.com/problems/path-sum-iii/ +""" + +from __future__ import annotations + + +class Node: + """ + A Node has value variable and pointers to Nodes to its left and right. + """ + + def __init__(self, value: int) -> None: + self.value = value + self.left: Node | None = None + self.right: Node | None = None + + +class BinaryTreePathSum: + r""" + The below tree looks like this + 10 + / \ + 5 -3 + / \ \ + 3 2 11 + / \ \ + 3 -2 1 + + + >>> tree = Node(10) + >>> tree.left = Node(5) + >>> tree.right = Node(-3) + >>> tree.left.left = Node(3) + >>> tree.left.right = Node(2) + >>> tree.right.right = Node(11) + >>> tree.left.left.left = Node(3) + >>> tree.left.left.right = Node(-2) + >>> tree.left.right.right = Node(1) + + >>> BinaryTreePathSum().path_sum(tree, 8) + 3 + >>> BinaryTreePathSum().path_sum(tree, 7) + 2 + >>> tree.right.right = Node(10) + >>> BinaryTreePathSum().path_sum(tree, 8) + 2 + """ + + target: int + + def __init__(self) -> None: + self.paths = 0 + + def depth_first_search(self, node: Node | None, path_sum: int) -> None: + if node is None: + return + + if path_sum == self.target: + self.paths += 1 + + if node.left: + self.depth_first_search(node.left, path_sum + node.left.value) + if node.right: + self.depth_first_search(node.right, path_sum + node.right.value) + + def path_sum(self, node: Node | None, target: int | None = None) -> int: + if node is None: + return 0 + if target is not None: + self.target = target + + self.depth_first_search(node, node.value) + self.path_sum(node.left) + self.path_sum(node.right) + + return self.paths + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 15c93e5f4bc5b03cecc000506bdf45c100b8f0b3 Mon Sep 17 00:00:00 2001 From: MoPaMo <67760881+MoPaMo@users.noreply.github.com> Date: Thu, 27 Oct 2022 23:03:34 +0200 Subject: [PATCH 1979/2908] fix typo in caesar_cipher.py (#7761) very character-> every character --- 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 8cd9fab58471..d19b9a337221 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -27,7 +27,7 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str: ========================= 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 + where every character in the plain-text is shifted by a certain number known as the "key" or "shift". Example: From 19bff003aa1c365bec86d3f4a13a9c3d6c36d230 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 28 Oct 2022 15:54:54 +0200 Subject: [PATCH 1980/2908] Adopt Python >= 3.8 assignment expressions using auto-walrus (#7737) * Adopt Python >= 3.8 assignment expressions using auto-walrus * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 5 + DIRECTORY.md | 3 + ciphers/enigma_machine2.py | 3 +- .../linked_list/doubly_linked_list_two.py | 3 +- dynamic_programming/fibonacci.py | 3 +- .../sequential_minimum_optimization.py | 1261 ++++++++--------- strings/indian_phone_validator.py | 3 +- 7 files changed, 642 insertions(+), 639 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5bdda50be0c4..7f6c206b49bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,11 @@ repos: )$ - id: requirements-txt-fixer + - repo: https://github.com/MarcoGorelli/auto-walrus + rev: v0.2.1 + hooks: + - id: auto-walrus + - repo: https://github.com/psf/black rev: 22.10.0 hooks: diff --git a/DIRECTORY.md b/DIRECTORY.md index ba7d3e62a9e1..7621427a6c34 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -45,6 +45,7 @@ * [Count 1S Brian Kernighan Method](bit_manipulation/count_1s_brian_kernighan_method.py) * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py) * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py) + * [Highest Set Bit](bit_manipulation/highest_set_bit.py) * [Is Even](bit_manipulation/is_even.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py) @@ -326,6 +327,7 @@ ## Financial * [Equated Monthly Installments](financial/equated_monthly_installments.py) * [Interest](financial/interest.py) + * [Price Plus Tax](financial/price_plus_tax.py) ## Fractals * [Julia Sets](fractals/julia_sets.py) @@ -669,6 +671,7 @@ * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) * [Kinetic Energy](physics/kinetic_energy.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) + * [Malus Law](physics/malus_law.py) * [N Body Simulation](physics/n_body_simulation.py) * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 9f9dbe6f7cd0..a877256ebeeb 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -86,8 +86,7 @@ def _validator( """ # Checks if there are 3 unique rotors - unique_rotsel = len(set(rotsel)) - if unique_rotsel < 3: + if (unique_rotsel := len(set(rotsel))) < 3: raise Exception(f"Please use 3 unique rotors (not {unique_rotsel})") # Checks if rotor positions are valid diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py index 184b6966b5a9..94b916a623f6 100644 --- a/data_structures/linked_list/doubly_linked_list_two.py +++ b/data_structures/linked_list/doubly_linked_list_two.py @@ -143,9 +143,8 @@ def get_node(self, item: int) -> Node: raise Exception("Node not found") def delete_value(self, value): - node = self.get_node(value) - if node is not None: + if (node := self.get_node(value)) is not None: if node == self.head: self.head = self.head.get_next() diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index 4abc60d4f3cc..7ec5993ef38d 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -18,8 +18,7 @@ def get(self, index: int) -> list: >>> Fibonacci().get(5) [0, 1, 1, 2, 3] """ - difference = index - (len(self.sequence) - 2) - if difference >= 1: + if (difference := index - (len(self.sequence) - 2)) >= 1: for _ in range(difference): self.sequence.append(self.sequence[-1] + self.sequence[-2]) return self.sequence[:index] diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index df5b03790804..66535e806c43 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -1,631 +1,630 @@ -""" - 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 -""" - - -import os -import sys -import urllib.request - -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 - -CANCER_DATASET_URL = ( - "https://archive.ics.uci.edu/ml/machine-learning-databases/" - "breast-cancer-wisconsin/wdbc.data" -) - - -class SmoSVM: - 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 algorithm - 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!\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 samples - 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: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. - """ - 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: 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)] - - 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: # noqa: E741 - 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 boundary - 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 boundary 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: - 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(f"smo algorithm cost {end_time - start_time} seconds") - - return call_func - - -@count_time -def test_cancel_data(): - 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( - 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: 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:] - - # 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(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("\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 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] - 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() +""" + 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 +""" + + +import os +import sys +import urllib.request + +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 + +CANCER_DATASET_URL = ( + "https://archive.ics.uci.edu/ml/machine-learning-databases/" + "breast-cancer-wisconsin/wdbc.data" +) + + +class SmoSVM: + 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 algorithm + 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!\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 samples + 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: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. + """ + 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: 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)] + + 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: # noqa: E741 + return None, None + + # calculate eta + k11 = k(i1, i1) + k22 = k(i2, i2) + k12 = k(i1, i2) + + # select the new alpha2 which could get the minimal objectives + if (eta := k11 + k22 - 2.0 * k12) > 0.0: + a2_new_unc = a2 + (y2 * (e1 - e2)) / eta + # a2_new has a boundary + 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 boundary 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: + 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(f"smo algorithm cost {end_time - start_time} seconds") + + return call_func + + +@count_time +def test_cancel_data(): + 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( + 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: 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:] + + # 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(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("\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 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] + 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() diff --git a/strings/indian_phone_validator.py b/strings/indian_phone_validator.py index 7f3fda5db949..07161a63a7af 100644 --- a/strings/indian_phone_validator.py +++ b/strings/indian_phone_validator.py @@ -20,8 +20,7 @@ def indian_phone_validator(phone: str) -> bool: True """ pat = re.compile(r"^(\+91[\-\s]?)?[0]?(91)?[789]\d{9}$") - match = re.search(pat, phone) - if match: + if match := re.search(pat, phone): return match.string == phone return False From 3a671b57a29e3c2b4e973b01bc5bbe1554aa5da2 Mon Sep 17 00:00:00 2001 From: Kuldeep Borkar <74557588+KuldeepBorkar@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:57:16 +0530 Subject: [PATCH 1981/2908] Implemented Swish Function (#7357) * Implemented Swish Function * Added more description and return hint in def * Changed the name and added more descrition including test for sigmoid function * Added * in front of links --- maths/sigmoid_linear_unit.py | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 maths/sigmoid_linear_unit.py diff --git a/maths/sigmoid_linear_unit.py b/maths/sigmoid_linear_unit.py new file mode 100644 index 000000000000..a8ada10dd8ec --- /dev/null +++ b/maths/sigmoid_linear_unit.py @@ -0,0 +1,57 @@ +""" +This script demonstrates the implementation of the Sigmoid Linear Unit (SiLU) +or swish function. +* https://en.wikipedia.org/wiki/Rectifier_(neural_networks) +* https://en.wikipedia.org/wiki/Swish_function + +The function takes a vector x of K real numbers as input and returns x * sigmoid(x). +Swish is a smooth, non-monotonic function defined as f(x) = x * sigmoid(x). +Extensive experiments shows that Swish consistently matches or outperforms ReLU +on deep networks applied to a variety of challenging domains such as +image classification and machine translation. + +This script is inspired by a corresponding research paper. +* https://arxiv.org/abs/1710.05941 +""" + +import numpy as np + + +def sigmoid(vector: np.array) -> np.array: + """ + Mathematical function sigmoid takes a vector x of K real numbers as input and + returns 1/ (1 + e^-x). + https://en.wikipedia.org/wiki/Sigmoid_function + + >>> sigmoid(np.array([-1.0, 1.0, 2.0])) + array([0.26894142, 0.73105858, 0.88079708]) + """ + return 1 / (1 + np.exp(-vector)) + + +def sigmoid_linear_unit(vector: np.array) -> np.array: + """ + Implements the Sigmoid Linear Unit (SiLU) or swish function + + Parameters: + vector (np.array): A numpy array consisting of real + values. + + Returns: + swish_vec (np.array): The input numpy array, after applying + swish. + + Examples: + >>> sigmoid_linear_unit(np.array([-1.0, 1.0, 2.0])) + array([-0.26894142, 0.73105858, 1.76159416]) + + >>> sigmoid_linear_unit(np.array([-2])) + array([-0.23840584]) + """ + return vector * sigmoid(vector) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 26cecea27198848e2c1c0bc6d7f887d4ed7adb87 Mon Sep 17 00:00:00 2001 From: SparshRastogi <75373475+SparshRastogi@users.noreply.github.com> Date: Fri, 28 Oct 2022 20:03:21 +0530 Subject: [PATCH 1982/2908] Create fetch_amazon_product_data.py (#7585) * Create fetch_amazon_product_data.py This file provides a function which will take a product name as input from the user,and fetch the necessary information about that kind of products from Amazon like the product title,link to that product,price of the product,the ratings of the product and the discount available on the product in the form of a csv file,this will help the users by improving searchability and navigability and find the right product easily and in a short period of time, it will also be beneficial for performing better analysis on products * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fetch_amazon_product_data.py Added type hints and modified files to pass precommit test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fetch_amazon_product_data.py Added type hints and made changes to pass the precommit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fetch_amazon_product_data.py Modified function to return the data in the form of Pandas Dataframe,modified type hints and added a functionality to let the user determine if they need the data in a csv file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fetch_amazon_product_data.py Made some bug fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename fetch_amazon_product_data.py to get_amazon_product_data.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update get_amazon_product_data.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- web_programming/get_amazon_product_data.py | 100 +++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 web_programming/get_amazon_product_data.py diff --git a/web_programming/get_amazon_product_data.py b/web_programming/get_amazon_product_data.py new file mode 100644 index 000000000000..c796793f2205 --- /dev/null +++ b/web_programming/get_amazon_product_data.py @@ -0,0 +1,100 @@ +""" +This file provides a function which will take a product name as input from the user, +and fetch from Amazon information about products of this name or category. The product +information will include title, URL, price, ratings, and the discount available. +""" + + +from itertools import zip_longest + +import requests +from bs4 import BeautifulSoup +from pandas import DataFrame + + +def get_amazon_product_data(product: str = "laptop") -> DataFrame: + """ + Take a product name or category as input and return product information from Amazon + including title, URL, price, ratings, and the discount available. + """ + url = f"https://www.amazon.in/laptop/s?k={product}" + header = { + "User-Agent": """Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 + (KHTML, like Gecko)Chrome/44.0.2403.157 Safari/537.36""", + "Accept-Language": "en-US, en;q=0.5", + } + soup = BeautifulSoup(requests.get(url, headers=header).text) + # Initialize a Pandas dataframe with the column titles + data_frame = DataFrame( + columns=[ + "Product Title", + "Product Link", + "Current Price of the product", + "Product Rating", + "MRP of the product", + "Discount", + ] + ) + # Loop through each entry and store them in the dataframe + for item, _ in zip_longest( + soup.find_all( + "div", + attrs={"class": "s-result-item", "data-component-type": "s-search-result"}, + ), + soup.find_all("div", attrs={"class": "a-row a-size-base a-color-base"}), + ): + try: + product_title = item.h2.text + product_link = "https://www.amazon.in/" + item.h2.a["href"] + product_price = item.find("span", attrs={"class": "a-offscreen"}).text + try: + product_rating = item.find("span", attrs={"class": "a-icon-alt"}).text + except AttributeError: + product_rating = "Not available" + try: + product_mrp = ( + "₹" + + item.find( + "span", attrs={"class": "a-price a-text-price"} + ).text.split("₹")[1] + ) + except AttributeError: + product_mrp = "" + try: + discount = float( + ( + ( + float(product_mrp.strip("₹").replace(",", "")) + - float(product_price.strip("₹").replace(",", "")) + ) + / float(product_mrp.strip("₹").replace(",", "")) + ) + * 100 + ) + except ValueError: + discount = float("nan") + except AttributeError: + pass + data_frame.loc[len(data_frame.index)] = [ + product_title, + product_link, + product_price, + product_rating, + product_mrp, + discount, + ] + data_frame.loc[ + data_frame["Current Price of the product"] > data_frame["MRP of the product"], + "MRP of the product", + ] = " " + data_frame.loc[ + data_frame["Current Price of the product"] > data_frame["MRP of the product"], + "Discount", + ] = " " + data_frame.index += 1 + return data_frame + + +if __name__ == "__main__": + product = "headphones" + get_amazon_product_data(product).to_csv(f"Amazon Product Data for {product}.csv") From d9efd7e25bbe937893a9818cfda62ca3f72ffe0d Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 28 Oct 2022 21:54:44 +0300 Subject: [PATCH 1983/2908] Update PR template (#7794) * Update PR template * Revert changes, reword line --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4d2265968612..b3ba8baf9c34 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -16,5 +16,5 @@ * [ ] 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. +* [ ] All new algorithms include at least one URL that points to Wikipedia or another similar explanation. * [ ] If this pull request resolves one or more open issues then the commit message contains `Fixes: #{$ISSUE_NO}`. From 528b1290194da09c2e762c2232502d2cfcdb1e3d Mon Sep 17 00:00:00 2001 From: Pronoy Mandal Date: Sat, 29 Oct 2022 00:38:41 +0530 Subject: [PATCH 1984/2908] Update maximum_subarray.py (#7757) * Update maximum_subarray.py 1. Rectify documentation to indicate the correct output: function doesn't return the subarray, but rather returns a sum. 2. Make the function more Pythonic and optimal. 3. Make function annotation generic i.e. can accept any sequence. 4. Raise value error when the input sequence is empty. * Update maximum_subarray.py 1. Use the conventions as mentioned in pep-0257. 2. Use negative infinity as the initial value for the current maximum and the answer. * Update maximum_subarray.py Avoid type conflict by returning the answer cast to an integer. * Update other/maximum_subarray.py Co-authored-by: Andrey * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maximum_subarray.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maximum_subarray.py Remove typecast to int for the final answer Co-authored-by: Andrey Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- other/maximum_subarray.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/other/maximum_subarray.py b/other/maximum_subarray.py index 756e009444fe..1c8c8cabcd2d 100644 --- a/other/maximum_subarray.py +++ b/other/maximum_subarray.py @@ -1,20 +1,26 @@ -def max_subarray(nums: list[int]) -> int: - """ - Returns the subarray with maximum sum - >>> max_subarray([1,2,3,4,-2]) +from collections.abc import Sequence + + +def max_subarray_sum(nums: Sequence[int]) -> int: + """Return the maximum possible sum amongst all non - empty subarrays. + + Raises: + ValueError: when nums is empty. + + >>> max_subarray_sum([1,2,3,4,-2]) 10 - >>> max_subarray([-2,1,-3,4,-1,2,1,-5,4]) + >>> max_subarray_sum([-2,1,-3,4,-1,2,1,-5,4]) 6 """ + if not nums: + raise ValueError("Input sequence should not be empty") curr_max = ans = nums[0] + nums_len = len(nums) - for i in range(1, len(nums)): - if curr_max >= 0: - curr_max = curr_max + nums[i] - else: - curr_max = nums[i] - + for i in range(1, nums_len): + num = nums[i] + curr_max = max(curr_max + num, num) ans = max(curr_max, ans) return ans @@ -23,4 +29,4 @@ def max_subarray(nums: list[int]) -> int: if __name__ == "__main__": n = int(input("Enter number of elements : ").strip()) array = list(map(int, input("\nEnter the numbers : ").strip().split()))[:n] - print(max_subarray(array)) + print(max_subarray_sum(array)) From fe5819c872abcbe1a96ee7bd20ab930b2892bbf5 Mon Sep 17 00:00:00 2001 From: Shubham Kondekar <40213815+kondekarshubham123@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:02:32 +0530 Subject: [PATCH 1985/2908] Create combination_sum_iv.py (#7672) * Create combination_sum_iv.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/combination_sum_iv.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/combination_sum_iv.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/combination_sum_iv.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update combination_sum_iv.py * Update combination_sum_iv.py * Resolved PR Comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * minor change, argument missing in function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/combination_sum_iv.py Co-authored-by: Christian Clauss * minor change Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris Co-authored-by: Christian Clauss --- dynamic_programming/combination_sum_iv.py | 102 ++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 dynamic_programming/combination_sum_iv.py diff --git a/dynamic_programming/combination_sum_iv.py b/dynamic_programming/combination_sum_iv.py new file mode 100644 index 000000000000..b2aeb0824f64 --- /dev/null +++ b/dynamic_programming/combination_sum_iv.py @@ -0,0 +1,102 @@ +""" +Question: +You are given an array of distinct integers and you have to tell how many +different ways of selecting the elements from the array are there such that +the sum of chosen elements is equal to the target number tar. + +Example + +Input: +N = 3 +target = 5 +array = [1, 2, 5] + +Output: +9 + +Approach: +The basic idea is to go over recursively to find the way such that the sum +of chosen elements is “tar”. For every element, we have two choices + 1. Include the element in our set of chosen elements. + 2. Don’t include the element in our set of chosen elements. +""" + + +def combination_sum_iv(n: int, array: list[int], target: int) -> int: + """ + Function checks the all possible combinations, and returns the count + of possible combination in exponential Time Complexity. + + >>> combination_sum_iv(3, [1,2,5], 5) + 9 + """ + + def count_of_possible_combinations(target: int) -> int: + if target < 0: + return 0 + if target == 0: + return 1 + return sum(count_of_possible_combinations(target - item) for item in array) + + return count_of_possible_combinations(target) + + +def combination_sum_iv_dp_array(n: int, array: list[int], target: int) -> int: + """ + Function checks the all possible combinations, and returns the count + of possible combination in O(N^2) Time Complexity as we are using Dynamic + programming array here. + + >>> combination_sum_iv_dp_array(3, [1,2,5], 5) + 9 + """ + + def count_of_possible_combinations_with_dp_array( + target: int, dp_array: list[int] + ) -> int: + if target < 0: + return 0 + if target == 0: + return 1 + if dp_array[target] != -1: + return dp_array[target] + answer = sum( + count_of_possible_combinations_with_dp_array(target - item, dp_array) + for item in array + ) + dp_array[target] = answer + return answer + + dp_array = [-1] * (target + 1) + return count_of_possible_combinations_with_dp_array(target, dp_array) + + +def combination_sum_iv_bottom_up(n: int, array: list[int], target: int) -> int: + """ + Function checks the all possible combinations with using bottom up approach, + and returns the count of possible combination in O(N^2) Time Complexity + as we are using Dynamic programming array here. + + >>> combination_sum_iv_bottom_up(3, [1,2,5], 5) + 9 + """ + + dp_array = [0] * (target + 1) + dp_array[0] = 1 + + for i in range(1, target + 1): + for j in range(n): + if i - array[j] >= 0: + dp_array[i] += dp_array[i - array[j]] + + return dp_array[target] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + n = 3 + target = 5 + array = [1, 2, 5] + print(combination_sum_iv(n, array, target)) From 762afc086f065f1d8fe1afcde8c8ad3fa46898a7 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 28 Oct 2022 23:27:39 +0300 Subject: [PATCH 1986/2908] Update breadth_first_search_2.py (#7765) * Cleanup the BFS * Add both functions and timeit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add performace results as comment * Update breadth_first_search_2.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- graphs/breadth_first_search_2.py | 45 +++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/graphs/breadth_first_search_2.py b/graphs/breadth_first_search_2.py index 2f060a90d40d..a0b92b90b456 100644 --- a/graphs/breadth_first_search_2.py +++ b/graphs/breadth_first_search_2.py @@ -14,7 +14,9 @@ """ from __future__ import annotations +from collections import deque from queue import Queue +from timeit import timeit G = { "A": ["B", "C"], @@ -26,12 +28,15 @@ } -def breadth_first_search(graph: dict, start: str) -> set[str]: +def breadth_first_search(graph: dict, start: str) -> list[str]: """ - >>> ''.join(sorted(breadth_first_search(G, 'A'))) + Implementation of breadth first search using queue.Queue. + + >>> ''.join(breadth_first_search(G, 'A')) 'ABCDEF' """ explored = {start} + result = [start] queue: Queue = Queue() queue.put(start) while not queue.empty(): @@ -39,12 +44,44 @@ def breadth_first_search(graph: dict, start: str) -> set[str]: for w in graph[v]: if w not in explored: explored.add(w) + result.append(w) queue.put(w) - return explored + return result + + +def breadth_first_search_with_deque(graph: dict, start: str) -> list[str]: + """ + Implementation of breadth first search using collection.queue. + + >>> ''.join(breadth_first_search_with_deque(G, 'A')) + 'ABCDEF' + """ + visited = {start} + result = [start] + queue = deque([start]) + while queue: + v = queue.popleft() + for child in graph[v]: + if child not in visited: + visited.add(child) + result.append(child) + queue.append(child) + return result + + +def benchmark_function(name: str) -> None: + setup = f"from __main__ import G, {name}" + number = 10000 + res = timeit(f"{name}(G, 'A')", setup=setup, number=number) + print(f"{name:<35} finished {number} runs in {res:.5f} seconds") if __name__ == "__main__": import doctest doctest.testmod() - print(breadth_first_search(G, "A")) + + benchmark_function("breadth_first_search") + benchmark_function("breadth_first_search_with_deque") + # breadth_first_search finished 10000 runs in 0.20999 seconds + # breadth_first_search_with_deque finished 10000 runs in 0.01421 seconds From cf08d9f5e7afdcfb9406032abcad328aa79c566a Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 29 Oct 2022 09:26:19 +0300 Subject: [PATCH 1987/2908] Format docs (#7821) * Reformat docs for odd_even_sort.py * Fix docstring formatting * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris Co-authored-by: Caeden Perelli-Harris --- machine_learning/data_transformations.py | 10 +++++++--- physics/kinetic_energy.py | 5 ++++- sorts/merge_sort.py | 9 ++++++--- sorts/odd_even_sort.py | 9 +++++++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/machine_learning/data_transformations.py b/machine_learning/data_transformations.py index 9e0d747e93fa..ecfd3b9e27c2 100644 --- a/machine_learning/data_transformations.py +++ b/machine_learning/data_transformations.py @@ -1,5 +1,7 @@ """ -Normalization Wikipedia: https://en.wikipedia.org/wiki/Normalization +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 @@ -28,7 +30,8 @@ def normalization(data: list, ndigits: int = 3) -> list: """ - Returns a normalized list of values + Return 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: @@ -46,7 +49,8 @@ def normalization(data: list, ndigits: int = 3) -> list: def standardization(data: list, ndigits: int = 3) -> list: """ - Returns a standardized list of values + Return 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: diff --git a/physics/kinetic_energy.py b/physics/kinetic_energy.py index 535ffc219251..8863919ac79f 100644 --- a/physics/kinetic_energy.py +++ b/physics/kinetic_energy.py @@ -1,5 +1,6 @@ """ -Find the kinetic energy of an object, give its mass and velocity +Find the kinetic energy of an object, given its mass and velocity. + Description : In physics, the kinetic energy of an object is the energy that it possesses due to its motion. It is defined as the work needed to accelerate a body of a given mass from rest to its stated velocity. Having gained this energy during its @@ -19,6 +20,8 @@ def kinetic_energy(mass: float, velocity: float) -> float: """ + Calculate kinetick energy. + The kinetic energy of a non-rotating object of mass m traveling at a speed v is ½mv² >>> kinetic_energy(10,10) diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index 4da29f32a36d..e80b1cb226ec 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -1,5 +1,6 @@ """ -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 or @@ -10,7 +11,7 @@ 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 @@ -24,7 +25,9 @@ def merge_sort(collection: list) -> list: """ def merge(left: list, right: list) -> list: - """merge left and right + """ + Merge left and right. + :param left: left collection :param right: right collection :return: merge result diff --git a/sorts/odd_even_sort.py b/sorts/odd_even_sort.py index 532f829499e8..9ef4462c72c0 100644 --- a/sorts/odd_even_sort.py +++ b/sorts/odd_even_sort.py @@ -1,10 +1,15 @@ -"""For reference +""" +Odd even sort implementation. + https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort """ def odd_even_sort(input_list: list) -> list: - """this algorithm uses the same idea of bubblesort, + """ + Sort input with odd even sort. + + This algorithm uses the same idea of bubblesort, but by first dividing in two phase (odd and even). Originally developed for use on parallel processors with local interconnections. From 301a520f0362261cddadc87e1bcfe20310308030 Mon Sep 17 00:00:00 2001 From: SparshRastogi <75373475+SparshRastogi@users.noreply.github.com> Date: Sat, 29 Oct 2022 16:44:44 +0530 Subject: [PATCH 1988/2908] Create potential_energy.py (#7666) * Create potential_energy.py Finding the gravitational potential energy of an object with reference to the earth, by taking its mass and height above the ground as input * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update physics/potential_energy.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * Update physics/potential_energy.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * Update physics/potential_energy.py Co-authored-by: Caeden Perelli-Harris * Update physics/potential_energy.py Co-authored-by: Caeden Perelli-Harris * Update physics/potential_energy.py Co-authored-by: Caeden Perelli-Harris * Update physics/potential_energy.py Co-authored-by: Caeden Perelli-Harris * Update physics/potential_energy.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris --- physics/potential_energy.py | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 physics/potential_energy.py diff --git a/physics/potential_energy.py b/physics/potential_energy.py new file mode 100644 index 000000000000..c6544f6f76d8 --- /dev/null +++ b/physics/potential_energy.py @@ -0,0 +1,61 @@ +from scipy.constants import g + +""" +Finding the gravitational potential energy of an object with reference +to the earth,by taking its mass and height above the ground as input + + +Description : Gravitational energy or gravitational potential energy +is the potential energy a massive object has in relation to another +massive object due to gravity. It is the potential energy associated +with the gravitational field, which is released (converted into +kinetic energy) when the objects fall towards each other. +Gravitational potential energy increases when two objects +are brought further apart. + +For two pairwise interacting point particles, the gravitational +potential energy U is given by +U=-GMm/R +where M and m are the masses of the two particles, R is the distance +between them, and G is the gravitational constant. +Close to the Earth's surface, the gravitational field is approximately +constant, and the gravitational potential energy of an object reduces to +U=mgh +where m is the object's mass, g=GM/R² is the gravity of Earth, and h is +the height of the object's center of mass above a chosen reference level. + +Reference : "https://en.m.wikipedia.org/wiki/Gravitational_energy" +""" + + +def potential_energy(mass: float, height: float) -> float: + # function will accept mass and height as parameters and return potential energy + """ + >>> potential_energy(10,10) + 980.665 + >>> potential_energy(0,5) + 0.0 + >>> potential_energy(8,0) + 0.0 + >>> potential_energy(10,5) + 490.3325 + >>> potential_energy(0,0) + 0.0 + >>> potential_energy(2,8) + 156.9064 + >>> potential_energy(20,100) + 19613.3 + """ + if mass < 0: + # handling of negative values of mass + raise ValueError("The mass of a body cannot be negative") + if height < 0: + # handling of negative values of height + raise ValueError("The height above the ground cannot be negative") + return mass * g * height + + +if __name__ == "__main__": + from doctest import testmod + + testmod(name="potential_energy") From a9bd68d96e519d0919c2e4385dbe433ff44b4c4f Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 29 Oct 2022 15:27:47 +0300 Subject: [PATCH 1989/2908] Add running doctest to pytest default (#7840) * Add default options for pytest * updating DIRECTORY.md * Move pytest settings to pyproject.toml * Move coverage settings to the pyproject.toml * Return --doctest-continue-on-failure to pytest * Convert pytest args to list * Update pyproject.toml Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .coveragerc | 4 ---- .github/workflows/build.yml | 2 +- DIRECTORY.md | 4 ++++ pyproject.toml | 20 ++++++++++++++++++++ pytest.ini | 5 ----- 5 files changed, 25 insertions(+), 10 deletions(-) delete mode 100644 .coveragerc create mode 100644 pyproject.toml delete mode 100644 pytest.ini diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index f7e6eb212bc8..000000000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[report] -sort = Cover -omit = - .env/* diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8481b962a256..159ce13b3fff 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/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . + run: pytest --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/DIRECTORY.md b/DIRECTORY.md index 7621427a6c34..1fa6af75d9c3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -165,6 +165,7 @@ * [Binary Search Tree Recursive](data_structures/binary_tree/binary_search_tree_recursive.py) * [Binary Tree Mirror](data_structures/binary_tree/binary_tree_mirror.py) * [Binary Tree Node Sum](data_structures/binary_tree/binary_tree_node_sum.py) + * [Binary Tree Path Sum](data_structures/binary_tree/binary_tree_path_sum.py) * [Binary Tree Traversals](data_structures/binary_tree/binary_tree_traversals.py) * [Diff Views Of Binary Tree](data_structures/binary_tree/diff_views_of_binary_tree.py) * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) @@ -285,6 +286,7 @@ * [Bitmask](dynamic_programming/bitmask.py) * [Catalan Numbers](dynamic_programming/catalan_numbers.py) * [Climbing Stairs](dynamic_programming/climbing_stairs.py) + * [Combination Sum Iv](dynamic_programming/combination_sum_iv.py) * [Edit Distance](dynamic_programming/edit_distance.py) * [Factorial](dynamic_programming/factorial.py) * [Fast Fibonacci](dynamic_programming/fast_fibonacci.py) @@ -595,6 +597,7 @@ * [P Series](maths/series/p_series.py) * [Sieve Of Eratosthenes](maths/sieve_of_eratosthenes.py) * [Sigmoid](maths/sigmoid.py) + * [Sigmoid Linear Unit](maths/sigmoid_linear_unit.py) * [Signum](maths/signum.py) * [Simpson Rule](maths/simpson_rule.py) * [Sin](maths/sin.py) @@ -1107,6 +1110,7 @@ * [Fetch Jobs](web_programming/fetch_jobs.py) * [Fetch Quotes](web_programming/fetch_quotes.py) * [Fetch Well Rx Price](web_programming/fetch_well_rx_price.py) + * [Get Amazon Product Data](web_programming/get_amazon_product_data.py) * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](web_programming/get_imdbtop.py) * [Get Top Billioners](web_programming/get_top_billioners.py) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000000..410e7655b2b5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.pytest.ini_options] +markers = [ + "mat_ops: mark a test as utilizing matrix operations.", +] +addopts = [ + "--durations=10", + "--doctest-modules", + "--showlocals", +] + + +[tool.coverage.report] +omit = [".env/*"] +sort = "Cover" + +#[report] +#sort = Cover +#omit = +# .env/* +# backtracking/* diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 488379278230..000000000000 --- a/pytest.ini +++ /dev/null @@ -1,5 +0,0 @@ -# Setup for pytest -[pytest] -markers = - mat_ops: mark a test as utilizing matrix operations. -addopts = --durations=10 From 6e809a25e33e2da07e03921bbf6614523a939e94 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 29 Oct 2022 15:31:56 +0300 Subject: [PATCH 1990/2908] Rename files (#7819) --- ...s_shortest_path.py => breadth_first_search_shortest_path_2.py} | 0 ...est_path.py => breadth_first_search_zero_one_shortest_path.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename graphs/{bfs_shortest_path.py => breadth_first_search_shortest_path_2.py} (100%) rename graphs/{bfs_zero_one_shortest_path.py => breadth_first_search_zero_one_shortest_path.py} (100%) diff --git a/graphs/bfs_shortest_path.py b/graphs/breadth_first_search_shortest_path_2.py similarity index 100% rename from graphs/bfs_shortest_path.py rename to graphs/breadth_first_search_shortest_path_2.py diff --git a/graphs/bfs_zero_one_shortest_path.py b/graphs/breadth_first_search_zero_one_shortest_path.py similarity index 100% rename from graphs/bfs_zero_one_shortest_path.py rename to graphs/breadth_first_search_zero_one_shortest_path.py From 327c38d6f0c6b79b46465406373ea7048bfec55e Mon Sep 17 00:00:00 2001 From: Sineth Sankalpa <66241389+sinsankio@users.noreply.github.com> Date: Sat, 29 Oct 2022 18:10:14 +0530 Subject: [PATCH 1991/2908] Srilankan phone number validation (#7706) * Add is_srilankan_phone_number.py * Update is_srilankan_phone_number.py --- strings/is_srilankan_phone_number.py | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 strings/is_srilankan_phone_number.py diff --git a/strings/is_srilankan_phone_number.py b/strings/is_srilankan_phone_number.py new file mode 100644 index 000000000000..7bded93f7f1d --- /dev/null +++ b/strings/is_srilankan_phone_number.py @@ -0,0 +1,35 @@ +import re + + +def is_sri_lankan_phone_number(phone: str) -> bool: + """ + Determine whether the string is a valid sri lankan mobile phone number or not + References: https://aye.sh/blog/sri-lankan-phone-number-regex + + >>> is_sri_lankan_phone_number("+94773283048") + True + >>> is_sri_lankan_phone_number("+9477-3283048") + True + >>> is_sri_lankan_phone_number("0718382399") + True + >>> is_sri_lankan_phone_number("0094702343221") + True + >>> is_sri_lankan_phone_number("075 3201568") + True + >>> is_sri_lankan_phone_number("07779209245") + False + >>> is_sri_lankan_phone_number("0957651234") + False + """ + + pattern = re.compile( + r"^(?:0|94|\+94|0{2}94)" r"7(0|1|2|4|5|6|7|8)" r"(-| |)" r"\d{7}$" + ) + + return bool(re.search(pattern, phone)) + + +if __name__ == "__main__": + phone = "0094702343221" + + print(is_sri_lankan_phone_number(phone)) From b0f68a0248d3eb48f3baf7e18f6420dc983bdb19 Mon Sep 17 00:00:00 2001 From: tarushirastogi <108577219+tarushirastogi@users.noreply.github.com> Date: Sat, 29 Oct 2022 18:13:51 +0530 Subject: [PATCH 1992/2908] Create centripetal_force.py (#7778) * Create centripetal_force.py Centripetal force is the force acting on an object in curvilinear motion directed towards the axis of rotation or centre of curvature. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update centripetal_force.py The value error should also handle negative values of the radius and using more descriptive names will be more beneficial for the users * Update centripetal_force.py Made some bug fixes Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: SparshRastogi <75373475+SparshRastogi@users.noreply.github.com> --- physics/centripetal_force.py | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 physics/centripetal_force.py diff --git a/physics/centripetal_force.py b/physics/centripetal_force.py new file mode 100644 index 000000000000..04069d256468 --- /dev/null +++ b/physics/centripetal_force.py @@ -0,0 +1,49 @@ +""" +Description : Centripetal force is the force acting on an object in +curvilinear motion directed towards the axis of rotation +or centre of curvature. + +The unit of centripetal force is newton. + +The centripetal force is always directed perpendicular to the +direction of the object’s displacement. Using Newton’s second +law of motion, it is found that the centripetal force of an object +moving in a circular path always acts towards the centre of the circle. +The Centripetal Force Formula is given as the product of mass (in kg) +and tangential velocity (in meters per second) squared, divided by the +radius (in meters) that implies that on doubling the tangential velocity, +the centripetal force will be quadrupled. Mathematically it is written as: +F = mv²/r +Where, F is the Centripetal force, m is the mass of the object, v is the +speed or velocity of the object and r is the radius. + +Reference: https://byjus.com/physics/centripetal-and-centrifugal-force/ +""" + + +def centripetal(mass: float, velocity: float, radius: float) -> float: + """ + The Centripetal Force formula is given as: (m*v*v)/r + + >>> round(centripetal(15.5,-30,10),2) + 1395.0 + >>> round(centripetal(10,15,5),2) + 450.0 + >>> round(centripetal(20,-50,15),2) + 3333.33 + >>> round(centripetal(12.25,40,25),2) + 784.0 + >>> round(centripetal(50,100,50),2) + 10000.0 + """ + if mass < 0: + raise ValueError("The mass of the body cannot be negative") + if radius <= 0: + raise ValueError("The radius is always a positive non zero integer") + return (mass * (velocity) ** 2) / radius + + +if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) From 18ffc4dec85a85837f71cd6c9b1e630b9d185001 Mon Sep 17 00:00:00 2001 From: Pronoy Mandal Date: Sat, 29 Oct 2022 18:24:13 +0530 Subject: [PATCH 1993/2908] Update password_generator.py (#7745) * Update password_generator.py 1. Use secrets module instead of random for passwords as it gives a secure source of randomness 2. Add type annotations for functions 3. Replace ctbi (variable for the characters to be included) with a more meaningful and short name 4. Use integer division instead of obtaining the integer part of a division computing a floating point * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- other/password_generator.py | 40 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/other/password_generator.py b/other/password_generator.py index c09afd7e6125..8f9d58a33b82 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -1,9 +1,10 @@ """Password Generator allows you to generate a random password of length N.""" -from random import choice, shuffle +import secrets +from random import shuffle from string import ascii_letters, digits, punctuation -def password_generator(length=8): +def password_generator(length: int = 8) -> str: """ >>> len(password_generator()) 8 @@ -17,58 +18,59 @@ def password_generator(length=8): 0 """ chars = ascii_letters + digits + punctuation - return "".join(choice(chars) for x in range(length)) + return "".join(secrets.choice(chars) for _ in range(length)) # ALTERNATIVE METHODS -# ctbi= characters that must be in password +# chars_incl= characters that must be in password # i= how many letters or characters the password length will be -def alternative_password_generator(ctbi, i): +def alternative_password_generator(chars_incl: str, i: int) -> str: # 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) + i -= len(chars_incl) + quotient = i // 3 remainder = i % 3 - # chars = ctbi + random_letters(ascii_letters, i / 3 + remainder) + + # chars = chars_incl + random_letters(ascii_letters, i / 3 + remainder) + # random_number(digits, i / 3) + random_characters(punctuation, i / 3) chars = ( - ctbi + chars_incl + random(ascii_letters, quotient + remainder) + random(digits, quotient) + random(punctuation, quotient) ) - chars = list(chars) - shuffle(chars) - return "".join(chars) + list_of_chars = list(chars) + shuffle(list_of_chars) + return "".join(list_of_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(chars_incl: str, i: int) -> str: + return "".join(secrets.choice(chars_incl) for _ in range(i)) -def random_number(ctbi, i): +def random_number(chars_incl, i): pass # Put your code here... -def random_letters(ctbi, i): +def random_letters(chars_incl, i): pass # Put your code here... -def random_characters(ctbi, i): +def random_characters(chars_incl, i): pass # Put your code here... def main(): length = int(input("Please indicate the max length of your password: ").strip()) - ctbi = input( + chars_incl = 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) + "Alternative Password generated:", + alternative_password_generator(chars_incl, length), ) print("[If you are thinking of using this passsword, You better save it.]") From 584e743422565decd35b1b6f94cef3ced840698b Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 29 Oct 2022 16:07:02 +0300 Subject: [PATCH 1994/2908] Fix yesqa hook (#7843) * fix yesqa hook * Remove redundant noqa * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 10 ++++++++-- DIRECTORY.md | 5 +++-- .../binary_tree/non_recursive_segment_tree.py | 2 +- digital_image_processing/index_calculation.py | 2 +- genetic_algorithm/basic_string.py | 2 +- maths/prime_sieve_eratosthenes.py | 2 -- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7f6c206b49bc..56946f5f240f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,13 +41,19 @@ repos: rev: 5.0.4 hooks: - id: flake8 # See .flake8 for args - additional_dependencies: + additional_dependencies: &flake8-plugins - flake8-bugbear - flake8-builtins - flake8-broken-line - flake8-comprehensions - pep8-naming - - yesqa + + - repo: https://github.com/asottile/yesqa + rev: v1.4.0 + hooks: + - id: yesqa + additional_dependencies: + *flake8-plugins - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.982 diff --git a/DIRECTORY.md b/DIRECTORY.md index 1fa6af75d9c3..198cc7077d2b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -356,14 +356,14 @@ * [Articulation Points](graphs/articulation_points.py) * [Basic Graphs](graphs/basic_graphs.py) * [Bellman Ford](graphs/bellman_ford.py) - * [Bfs Shortest Path](graphs/bfs_shortest_path.py) - * [Bfs Zero One Shortest Path](graphs/bfs_zero_one_shortest_path.py) * [Bidirectional A Star](graphs/bidirectional_a_star.py) * [Bidirectional Breadth First Search](graphs/bidirectional_breadth_first_search.py) * [Boruvka](graphs/boruvka.py) * [Breadth First Search](graphs/breadth_first_search.py) * [Breadth First Search 2](graphs/breadth_first_search_2.py) * [Breadth First Search Shortest Path](graphs/breadth_first_search_shortest_path.py) + * [Breadth First Search Shortest Path 2](graphs/breadth_first_search_shortest_path_2.py) + * [Breadth First Search Zero One Shortest Path](graphs/breadth_first_search_zero_one_shortest_path.py) * [Check Bipartite Graph Bfs](graphs/check_bipartite_graph_bfs.py) * [Check Bipartite Graph Dfs](graphs/check_bipartite_graph_dfs.py) * [Check Cycle](graphs/check_cycle.py) @@ -678,6 +678,7 @@ * [N Body Simulation](physics/n_body_simulation.py) * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) + * [Potential Energy](physics/potential_energy.py) ## Project Euler * Problem 001 diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index c29adefffd20..075ff6c912ff 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -103,7 +103,7 @@ def query(self, l: int, r: int) -> T | None: # noqa: E741 >>> st.query(2, 3) 7 """ - l, r = l + self.N, r + self.N # noqa: E741 + l, r = l + self.N, r + self.N res: T | None = None while l <= r: # noqa: E741 diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index be1855e99d10..67830668b0da 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -413,7 +413,7 @@ def ipvi(self): """ return (self.nir / ((self.nir + self.red) / 2)) * (self.ndvi() + 1) - def i(self): # noqa: E741,E743 + def i(self): """ Intensity https://www.indexdatabase.de/db/i-single.php?id=36 diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index 5cf8d691b1d7..45b8be651f6e 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -80,7 +80,7 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]: score = len( [g for position, g in enumerate(item) if g == main_target[position]] ) - return (item, float(score)) # noqa: B023 + return (item, float(score)) # Adding a bit of concurrency can make everything faster, # diff --git a/maths/prime_sieve_eratosthenes.py b/maths/prime_sieve_eratosthenes.py index 8d60e48c2140..3a3c55085218 100644 --- a/maths/prime_sieve_eratosthenes.py +++ b/maths/prime_sieve_eratosthenes.py @@ -1,5 +1,3 @@ -# flake8: noqa - """ Sieve of Eratosthenes From 93ad7db97fa211b6e9f77025513a45df83400f88 Mon Sep 17 00:00:00 2001 From: JatinR05 <71865805+JatinR05@users.noreply.github.com> Date: Sat, 29 Oct 2022 18:58:12 +0530 Subject: [PATCH 1995/2908] Create recursive_approach_knapsack.py (#7587) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create recursive_approach_knapsack.py Added a new naïve recursive approach to solve the knapsack problem. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update recursive_approach_knapsack.py Updated the code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update recursive_approach_knapsack.py Updated * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- knapsack/recursive_approach_knapsack.py | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 knapsack/recursive_approach_knapsack.py diff --git a/knapsack/recursive_approach_knapsack.py b/knapsack/recursive_approach_knapsack.py new file mode 100644 index 000000000000..d813981cb79c --- /dev/null +++ b/knapsack/recursive_approach_knapsack.py @@ -0,0 +1,52 @@ +# To get an insight into naive recursive way to solve the Knapsack problem + + +""" +A shopkeeper has bags of wheat that each have different weights and different profits. +eg. +no_of_items 4 +profit 5 4 8 6 +weight 1 2 4 5 +max_weight 5 +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. +""" + + +def knapsack( + weights: list, values: list, number_of_items: int, max_weight: int, index: int +) -> int: + """ + Function description is as follows- + :param weights: Take a list of weights + :param values: Take a list of profits corresponding to the weights + :param number_of_items: number of items available to pick from + :param max_weight: Maximum weight that could be carried + :param index: the element we are looking at + :return: Maximum expected gain + >>> knapsack([1, 2, 4, 5], [5, 4, 8, 6], 4, 5, 0) + 13 + >>> knapsack([3 ,4 , 5], [10, 9 , 8], 3, 25, 0) + 27 + """ + if index == number_of_items: + return 0 + ans1 = 0 + ans2 = 0 + ans1 = knapsack(weights, values, number_of_items, max_weight, index + 1) + if weights[index] <= max_weight: + ans2 = values[index] + knapsack( + weights, values, number_of_items, max_weight - weights[index], index + 1 + ) + return max(ans1, ans2) + + +if __name__ == "__main__": + + import doctest + + doctest.testmod() From efb4a3aee842e1db855e678f28b79588734ff146 Mon Sep 17 00:00:00 2001 From: Anshraj Shrivastava <42239140+rajansh87@users.noreply.github.com> Date: Sat, 29 Oct 2022 18:59:15 +0530 Subject: [PATCH 1996/2908] added algo for finding permutations of an array (#7614) * Add files via upload * Delete permutations.cpython-310.pyc * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update permutations.py * Update permutations.py * Add files via upload * Delete permutations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update permutations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update permutations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update permutations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/arrays/permutations.py Co-authored-by: Christian Clauss * Update permutations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update permutations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/arrays/permutations.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * Update permutations.py * Update permutations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update permutations.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> --- data_structures/arrays/permutations.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 data_structures/arrays/permutations.py diff --git a/data_structures/arrays/permutations.py b/data_structures/arrays/permutations.py new file mode 100644 index 000000000000..eb3f26517863 --- /dev/null +++ b/data_structures/arrays/permutations.py @@ -0,0 +1,26 @@ +def permute(nums: list[int]) -> list[list[int]]: + """ + Return all permutations. + + >>> from itertools import permutations + >>> numbers= [1,2,3] + >>> all(list(nums) in permute(numbers) for nums in permutations(numbers)) + True + """ + result = [] + if len(nums) == 1: + return [nums.copy()] + for _ in range(len(nums)): + n = nums.pop(0) + permutations = permute(nums) + for perm in permutations: + perm.append(n) + result.extend(permutations) + nums.append(n) + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 7b521b66cfe3d16960c3fa8e01ff947794cc44a6 Mon Sep 17 00:00:00 2001 From: Carlos Villar Date: Sat, 29 Oct 2022 15:44:18 +0200 Subject: [PATCH 1997/2908] Add Viterbi algorithm (#7509) * Added Viterbi algorithm Fixes: #7465 Squashed commits * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added doctest for validators * moved all extracted functions to the main function * Forgot a type hint Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/viterbi.py | 400 +++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 dynamic_programming/viterbi.py diff --git a/dynamic_programming/viterbi.py b/dynamic_programming/viterbi.py new file mode 100644 index 000000000000..93ab845e2ae8 --- /dev/null +++ b/dynamic_programming/viterbi.py @@ -0,0 +1,400 @@ +from typing import Any + + +def viterbi( + observations_space: list, + states_space: list, + initial_probabilities: dict, + transition_probabilities: dict, + emission_probabilities: dict, +) -> list: + """ + Viterbi Algorithm, to find the most likely path of + states from the start and the expected output. + https://en.wikipedia.org/wiki/Viterbi_algorithm + sdafads + Wikipedia example + >>> observations = ["normal", "cold", "dizzy"] + >>> states = ["Healthy", "Fever"] + >>> start_p = {"Healthy": 0.6, "Fever": 0.4} + >>> trans_p = { + ... "Healthy": {"Healthy": 0.7, "Fever": 0.3}, + ... "Fever": {"Healthy": 0.4, "Fever": 0.6}, + ... } + >>> emit_p = { + ... "Healthy": {"normal": 0.5, "cold": 0.4, "dizzy": 0.1}, + ... "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6}, + ... } + >>> viterbi(observations, states, start_p, trans_p, emit_p) + ['Healthy', 'Healthy', 'Fever'] + + >>> viterbi((), states, start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + + >>> viterbi(observations, (), start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + + >>> viterbi(observations, states, {}, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + + >>> viterbi(observations, states, start_p, {}, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + + >>> viterbi(observations, states, start_p, trans_p, {}) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + + >>> viterbi("invalid", states, start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: observations_space must be a list + + >>> viterbi(["valid", 123], states, start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: observations_space must be a list of strings + + >>> viterbi(observations, "invalid", start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: states_space must be a list + + >>> viterbi(observations, ["valid", 123], start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: states_space must be a list of strings + + >>> viterbi(observations, states, "invalid", trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: initial_probabilities must be a dict + + >>> viterbi(observations, states, {2:2}, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: initial_probabilities all keys must be strings + + >>> viterbi(observations, states, {"a":2}, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: initial_probabilities all values must be float + + >>> viterbi(observations, states, start_p, "invalid", emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities must be a dict + + >>> viterbi(observations, states, start_p, {"a":2}, emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities all values must be dict + + >>> viterbi(observations, states, start_p, {2:{2:2}}, emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities all keys must be strings + + >>> viterbi(observations, states, start_p, {"a":{2:2}}, emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities all keys must be strings + + >>> viterbi(observations, states, start_p, {"a":{"b":2}}, emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities nested dictionary all values must be float + + >>> viterbi(observations, states, start_p, trans_p, "invalid") + Traceback (most recent call last): + ... + ValueError: emission_probabilities must be a dict + + >>> viterbi(observations, states, start_p, trans_p, None) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + + """ + _validation( + observations_space, + states_space, + initial_probabilities, + transition_probabilities, + emission_probabilities, + ) + # Creates data structures and fill initial step + probabilities: dict = {} + pointers: dict = {} + for state in states_space: + observation = observations_space[0] + probabilities[(state, observation)] = ( + initial_probabilities[state] * emission_probabilities[state][observation] + ) + pointers[(state, observation)] = None + + # Fills the data structure with the probabilities of + # different transitions and pointers to previous states + for o in range(1, len(observations_space)): + observation = observations_space[o] + prior_observation = observations_space[o - 1] + for state in states_space: + # Calculates the argmax for probability function + arg_max = "" + max_probability = -1 + for k_state in states_space: + probability = ( + probabilities[(k_state, prior_observation)] + * transition_probabilities[k_state][state] + * emission_probabilities[state][observation] + ) + if probability > max_probability: + max_probability = probability + arg_max = k_state + + # Update probabilities and pointers dicts + probabilities[(state, observation)] = ( + probabilities[(arg_max, prior_observation)] + * transition_probabilities[arg_max][state] + * emission_probabilities[state][observation] + ) + + pointers[(state, observation)] = arg_max + + # The final observation + final_observation = observations_space[len(observations_space) - 1] + + # argmax for given final observation + arg_max = "" + max_probability = -1 + for k_state in states_space: + probability = probabilities[(k_state, final_observation)] + if probability > max_probability: + max_probability = probability + arg_max = k_state + last_state = arg_max + + # Process pointers backwards + previous = last_state + result = [] + for o in range(len(observations_space) - 1, -1, -1): + result.append(previous) + previous = pointers[previous, observations_space[o]] + result.reverse() + + return result + + +def _validation( + observations_space: Any, + states_space: Any, + initial_probabilities: Any, + transition_probabilities: Any, + emission_probabilities: Any, +) -> None: + """ + >>> observations = ["normal", "cold", "dizzy"] + >>> states = ["Healthy", "Fever"] + >>> start_p = {"Healthy": 0.6, "Fever": 0.4} + >>> trans_p = { + ... "Healthy": {"Healthy": 0.7, "Fever": 0.3}, + ... "Fever": {"Healthy": 0.4, "Fever": 0.6}, + ... } + >>> emit_p = { + ... "Healthy": {"normal": 0.5, "cold": 0.4, "dizzy": 0.1}, + ... "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6}, + ... } + >>> _validation(observations, states, start_p, trans_p, emit_p) + + >>> _validation([], states, start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + """ + _validate_not_empty( + observations_space, + states_space, + initial_probabilities, + transition_probabilities, + emission_probabilities, + ) + _validate_lists(observations_space, states_space) + _validate_dicts( + initial_probabilities, transition_probabilities, emission_probabilities + ) + + +def _validate_not_empty( + observations_space: Any, + states_space: Any, + initial_probabilities: Any, + transition_probabilities: Any, + emission_probabilities: Any, +) -> None: + """ + >>> _validate_not_empty(["a"], ["b"], {"c":0.5}, + ... {"d": {"e": 0.6}}, {"f": {"g": 0.7}}) + + >>> _validate_not_empty(["a"], ["b"], {"c":0.5}, {}, {"f": {"g": 0.7}}) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + >>> _validate_not_empty(["a"], ["b"], None, {"d": {"e": 0.6}}, {"f": {"g": 0.7}}) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + """ + if not all( + [ + observations_space, + states_space, + initial_probabilities, + transition_probabilities, + emission_probabilities, + ] + ): + raise ValueError("There's an empty parameter") + + +def _validate_lists(observations_space: Any, states_space: Any) -> None: + """ + >>> _validate_lists(["a"], ["b"]) + + >>> _validate_lists(1234, ["b"]) + Traceback (most recent call last): + ... + ValueError: observations_space must be a list + + >>> _validate_lists(["a"], [3]) + Traceback (most recent call last): + ... + ValueError: states_space must be a list of strings + """ + _validate_list(observations_space, "observations_space") + _validate_list(states_space, "states_space") + + +def _validate_list(_object: Any, var_name: str) -> None: + """ + >>> _validate_list(["a"], "mock_name") + + >>> _validate_list("a", "mock_name") + Traceback (most recent call last): + ... + ValueError: mock_name must be a list + >>> _validate_list([0.5], "mock_name") + Traceback (most recent call last): + ... + ValueError: mock_name must be a list of strings + + """ + if not isinstance(_object, list): + raise ValueError(f"{var_name} must be a list") + else: + for x in _object: + if not isinstance(x, str): + raise ValueError(f"{var_name} must be a list of strings") + + +def _validate_dicts( + initial_probabilities: Any, + transition_probabilities: Any, + emission_probabilities: Any, +) -> None: + """ + >>> _validate_dicts({"c":0.5}, {"d": {"e": 0.6}}, {"f": {"g": 0.7}}) + + >>> _validate_dicts("invalid", {"d": {"e": 0.6}}, {"f": {"g": 0.7}}) + Traceback (most recent call last): + ... + ValueError: initial_probabilities must be a dict + >>> _validate_dicts({"c":0.5}, {2: {"e": 0.6}}, {"f": {"g": 0.7}}) + Traceback (most recent call last): + ... + ValueError: transition_probabilities all keys must be strings + >>> _validate_dicts({"c":0.5}, {"d": {"e": 0.6}}, {"f": {2: 0.7}}) + Traceback (most recent call last): + ... + ValueError: emission_probabilities all keys must be strings + >>> _validate_dicts({"c":0.5}, {"d": {"e": 0.6}}, {"f": {"g": "h"}}) + Traceback (most recent call last): + ... + ValueError: emission_probabilities nested dictionary all values must be float + """ + _validate_dict(initial_probabilities, "initial_probabilities", float) + _validate_nested_dict(transition_probabilities, "transition_probabilities") + _validate_nested_dict(emission_probabilities, "emission_probabilities") + + +def _validate_nested_dict(_object: Any, var_name: str) -> None: + """ + >>> _validate_nested_dict({"a":{"b": 0.5}}, "mock_name") + + >>> _validate_nested_dict("invalid", "mock_name") + Traceback (most recent call last): + ... + ValueError: mock_name must be a dict + >>> _validate_nested_dict({"a": 8}, "mock_name") + Traceback (most recent call last): + ... + ValueError: mock_name all values must be dict + >>> _validate_nested_dict({"a":{2: 0.5}}, "mock_name") + Traceback (most recent call last): + ... + ValueError: mock_name all keys must be strings + >>> _validate_nested_dict({"a":{"b": 4}}, "mock_name") + Traceback (most recent call last): + ... + ValueError: mock_name nested dictionary all values must be float + """ + _validate_dict(_object, var_name, dict) + for x in _object.values(): + _validate_dict(x, var_name, float, True) + + +def _validate_dict( + _object: Any, var_name: str, value_type: type, nested: bool = False +) -> None: + """ + >>> _validate_dict({"b": 0.5}, "mock_name", float) + + >>> _validate_dict("invalid", "mock_name", float) + Traceback (most recent call last): + ... + ValueError: mock_name must be a dict + >>> _validate_dict({"a": 8}, "mock_name", dict) + Traceback (most recent call last): + ... + ValueError: mock_name all values must be dict + >>> _validate_dict({2: 0.5}, "mock_name",float, True) + Traceback (most recent call last): + ... + ValueError: mock_name all keys must be strings + >>> _validate_dict({"b": 4}, "mock_name", float,True) + Traceback (most recent call last): + ... + ValueError: mock_name nested dictionary all values must be float + """ + if not isinstance(_object, dict): + raise ValueError(f"{var_name} must be a dict") + if not all(isinstance(x, str) for x in _object): + raise ValueError(f"{var_name} all keys must be strings") + if not all(isinstance(x, value_type) for x in _object.values()): + nested_text = "nested dictionary " if nested else "" + raise ValueError( + f"{var_name} {nested_text}all values must be {value_type.__name__}" + ) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 038f8a00e56bda8e8e2903fe4acf2ca7e3c83a57 Mon Sep 17 00:00:00 2001 From: sadiqebrahim <75269485+sadiqebrahim@users.noreply.github.com> Date: Sat, 29 Oct 2022 19:22:19 +0530 Subject: [PATCH 1998/2908] add electric conductivity algorithm (#7449) * add electric conductivity algorithm * Update electric_conductivity.py * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris * Update electric_conductivity.py * Update electric_conductivity.py * Update electric_conductivity.py * add algorithm Co-authored-by: Caeden Perelli-Harris --- electronics/electric_conductivity.py | 53 ++++++++++++++++++++++++++++ physics/sheer_stress.py | 51 ++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 electronics/electric_conductivity.py create mode 100644 physics/sheer_stress.py diff --git a/electronics/electric_conductivity.py b/electronics/electric_conductivity.py new file mode 100644 index 000000000000..11f2a607d214 --- /dev/null +++ b/electronics/electric_conductivity.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +ELECTRON_CHARGE = 1.6021e-19 # units = C + + +def electric_conductivity( + conductivity: float, + electron_conc: float, + mobility: float, +) -> tuple[str, float]: + """ + This function can calculate any one of the three - + 1. Conductivity + 2. Electron Concentration + 3. Electron Mobility + This is calculated from the other two provided values + Examples - + >>> electric_conductivity(conductivity=25, electron_conc=100, mobility=0) + ('mobility', 1.5604519068722301e+18) + >>> electric_conductivity(conductivity=0, electron_conc=1600, mobility=200) + ('conductivity', 5.12672e-14) + >>> electric_conductivity(conductivity=1000, electron_conc=0, mobility=1200) + ('electron_conc', 5.201506356240767e+18) + """ + if (conductivity, electron_conc, mobility).count(0) != 1: + raise ValueError("You cannot supply more or less than 2 values") + elif conductivity < 0: + raise ValueError("Conductivity cannot be negative") + elif electron_conc < 0: + raise ValueError("Electron concentration cannot be negative") + elif mobility < 0: + raise ValueError("mobility cannot be negative") + elif conductivity == 0: + return ( + "conductivity", + mobility * electron_conc * ELECTRON_CHARGE, + ) + elif electron_conc == 0: + return ( + "electron_conc", + conductivity / (mobility * ELECTRON_CHARGE), + ) + else: + return ( + "mobility", + conductivity / (electron_conc * ELECTRON_CHARGE), + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/physics/sheer_stress.py b/physics/sheer_stress.py new file mode 100644 index 000000000000..74a2d36b1f45 --- /dev/null +++ b/physics/sheer_stress.py @@ -0,0 +1,51 @@ +from __future__ import annotations + + +def sheer_stress( + stress: float, + tangential_force: float, + area: float, +) -> tuple[str, float]: + """ + This function can calculate any one of the three - + 1. Sheer Stress + 2. Tangential Force + 3. Cross-sectional Area + This is calculated from the other two provided values + Examples - + >>> sheer_stress(stress=25, tangential_force=100, area=0) + ('area', 4.0) + >>> sheer_stress(stress=0, tangential_force=1600, area=200) + ('stress', 8.0) + >>> sheer_stress(stress=1000, tangential_force=0, area=1200) + ('tangential_force', 1200000) + """ + if (stress, tangential_force, area).count(0) != 1: + raise ValueError("You cannot supply more or less than 2 values") + elif stress < 0: + raise ValueError("Stress cannot be negative") + elif tangential_force < 0: + raise ValueError("Tangential Force cannot be negative") + elif area < 0: + raise ValueError("Area cannot be negative") + elif stress == 0: + return ( + "stress", + tangential_force / area, + ) + elif tangential_force == 0: + return ( + "tangential_force", + stress * area, + ) + else: + return ( + "area", + tangential_force / stress, + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a02e7a1331583829b2768f02c4b9c412bf26251b Mon Sep 17 00:00:00 2001 From: Harsh Verma <53353745+TheLameOne@users.noreply.github.com> Date: Sat, 29 Oct 2022 19:24:32 +0530 Subject: [PATCH 1999/2908] Added algorithm for Text Justification in Strings (#7354) * Added algorithm for Text Justification in Strings * Added algorithm for Text Justification in Strings --- strings/text_justification.py | 92 +++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 strings/text_justification.py diff --git a/strings/text_justification.py b/strings/text_justification.py new file mode 100644 index 000000000000..5e86456c2456 --- /dev/null +++ b/strings/text_justification.py @@ -0,0 +1,92 @@ +def text_justification(word: str, max_width: int) -> list: + """ + Will format the string such that each line has exactly + (max_width) characters and is fully (left and right) justified, + and return the list of justified text. + + example 1: + string = "This is an example of text justification." + max_width = 16 + + output = ['This is an', + 'example of text', + 'justification. '] + + >>> text_justification("This is an example of text justification.", 16) + ['This is an', 'example of text', 'justification. '] + + example 2: + string = "Two roads diverged in a yellow wood" + max_width = 16 + output = ['Two roads', + 'diverged in a', + 'yellow wood '] + + >>> text_justification("Two roads diverged in a yellow wood", 16) + ['Two roads', 'diverged in a', 'yellow wood '] + + Time complexity: O(m*n) + Space complexity: O(m*n) + """ + + # Converting string into list of strings split by a space + words = word.split() + + def justify(line: list, width: int, max_width: int) -> str: + + overall_spaces_count = max_width - width + words_count = len(line) + if len(line) == 1: + # if there is only word in line + # just insert overall_spaces_count for the remainder of line + return line[0] + " " * overall_spaces_count + else: + spaces_to_insert_between_words = words_count - 1 + # num_spaces_between_words_list[i] : tells you to insert + # num_spaces_between_words_list[i] spaces + # after word on line[i] + num_spaces_between_words_list = spaces_to_insert_between_words * [ + overall_spaces_count // spaces_to_insert_between_words + ] + spaces_count_in_locations = ( + overall_spaces_count % spaces_to_insert_between_words + ) + # distribute spaces via round robin to the left words + for i in range(spaces_count_in_locations): + num_spaces_between_words_list[i] += 1 + aligned_words_list = [] + for i in range(spaces_to_insert_between_words): + # add the word + aligned_words_list.append(line[i]) + # add the spaces to insert + aligned_words_list.append(num_spaces_between_words_list[i] * " ") + # just add the last word to the sentence + aligned_words_list.append(line[-1]) + # join the aligned words list to form a justified line + return "".join(aligned_words_list) + + answer = [] + line: list[str] = [] + width = 0 + for word in words: + if width + len(word) + len(line) <= max_width: + # keep adding words until we can fill out max_width + # width = sum of length of all words (without overall_spaces_count) + # len(word) = length of current word + # len(line) = number of overall_spaces_count to insert between words + line.append(word) + width += len(word) + else: + # justify the line and add it to result + answer.append(justify(line, width, max_width)) + # reset new line and new width + line, width = [word], len(word) + remaining_spaces = max_width - width - len(line) + answer.append(" ".join(line) + (remaining_spaces + 1) * " ") + return answer + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From d84452344ae1931c635245b1311a10e330223fc6 Mon Sep 17 00:00:00 2001 From: dmorozov001 <116645674+dmorozov001@users.noreply.github.com> Date: Sat, 29 Oct 2022 15:43:03 +0100 Subject: [PATCH 2000/2908] Correcting typos in CONTRIBUTING.md (#7845) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5a07af100ee..5cbb24e563da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Befo ### Contributor -We are very happy that you consider implementing algorithms and data structures 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: +We are very happy that you are considering implementing algorithms and data structures 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: - You did your work - no plagiarism allowed - Any plagiarized work will not be merged. From bd50a3068270261fe845aac0daf309c7134e2477 Mon Sep 17 00:00:00 2001 From: Shashank Kashyap <50551759+SKVKPandey@users.noreply.github.com> Date: Sat, 29 Oct 2022 20:55:26 +0530 Subject: [PATCH 2001/2908] Resonant Frequency & Electrical Impedance (#6983) * Resonant Frequency * Resonant Frequency of LC Circuit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update electronics/resonant_frequency.py Co-authored-by: Caeden * Update electronics/resonant_frequency.py Co-authored-by: Caeden * Update electronics/resonant_frequency.py Co-authored-by: Caeden * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated resonant_frequency.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update electronics/resonant_frequency.py Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> * Fixed doctest issues in resonant_frequency.py * Algorithm for Electrical Impedance * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated Algorithm for Electrical Impedance * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update resonant_frequency.py * Update electrical_impedance.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update resonant_frequency.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update electronics/electrical_impedance.py Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> * Update electronics/electrical_impedance.py Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> * Update electronics/resonant_frequency.py Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> --- electronics/electrical_impedance.py | 46 ++++++++++++++++++++++++++ electronics/resonant_frequency.py | 50 +++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 electronics/electrical_impedance.py create mode 100644 electronics/resonant_frequency.py diff --git a/electronics/electrical_impedance.py b/electronics/electrical_impedance.py new file mode 100644 index 000000000000..44041ff790b6 --- /dev/null +++ b/electronics/electrical_impedance.py @@ -0,0 +1,46 @@ +"""Electrical impedance is the measure of the opposition that a +circuit presents to a current when a voltage is applied. +Impedance extends the concept of resistance to alternating current (AC) circuits. +Source: https://en.wikipedia.org/wiki/Electrical_impedance +""" + +from __future__ import annotations + +from math import pow, sqrt + + +def electrical_impedance( + resistance: float, reactance: float, impedance: float +) -> dict[str, float]: + """ + Apply Electrical Impedance formula, on any two given electrical values, + which can be resistance, reactance, and impedance, and then in a Python dict + return name/value pair of the zero value. + + >>> electrical_impedance(3,4,0) + {'impedance': 5.0} + >>> electrical_impedance(0,4,5) + {'resistance': 3.0} + >>> electrical_impedance(3,0,5) + {'reactance': 4.0} + >>> electrical_impedance(3,4,5) + Traceback (most recent call last): + ... + ValueError: One and only one argument must be 0 + """ + if (resistance, reactance, impedance).count(0) != 1: + raise ValueError("One and only one argument must be 0") + if resistance == 0: + return {"resistance": sqrt(pow(impedance, 2) - pow(reactance, 2))} + elif reactance == 0: + return {"reactance": sqrt(pow(impedance, 2) - pow(resistance, 2))} + elif impedance == 0: + return {"impedance": sqrt(pow(resistance, 2) + pow(reactance, 2))} + else: + raise ValueError("Exactly one argument must be 0") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/electronics/resonant_frequency.py b/electronics/resonant_frequency.py new file mode 100644 index 000000000000..4f95043b600a --- /dev/null +++ b/electronics/resonant_frequency.py @@ -0,0 +1,50 @@ +# https://en.wikipedia.org/wiki/LC_circuit + +"""An LC circuit, also called a resonant circuit, tank circuit, or tuned circuit, +is an electric circuit consisting of an inductor, represented by the letter L, +and a capacitor, represented by the letter C, connected together. +The circuit can act as an electrical resonator, an electrical analogue of a +tuning fork, storing energy oscillating at the circuit's resonant frequency. +Source: https://en.wikipedia.org/wiki/LC_circuit +""" + +from __future__ import annotations + +from math import pi, sqrt + + +def resonant_frequency(inductance: float, capacitance: float) -> tuple: + """ + This function can calculate the resonant frequency of LC circuit, + for the given value of inductance and capacitnace. + + Examples are given below: + >>> resonant_frequency(inductance=10, capacitance=5) + ('Resonant frequency', 0.022507907903927652) + >>> resonant_frequency(inductance=0, capacitance=5) + Traceback (most recent call last): + ... + ValueError: Inductance cannot be 0 or negative + >>> resonant_frequency(inductance=10, capacitance=0) + Traceback (most recent call last): + ... + ValueError: Capacitance cannot be 0 or negative + """ + + if inductance <= 0: + raise ValueError("Inductance cannot be 0 or negative") + + elif capacitance <= 0: + raise ValueError("Capacitance cannot be 0 or negative") + + else: + return ( + "Resonant frequency", + float(1 / (2 * pi * (sqrt(inductance * capacitance)))), + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 47ddba1d914bf5955a244056e794e718dee9ead1 Mon Sep 17 00:00:00 2001 From: Kushagra Makharia Date: Sat, 29 Oct 2022 21:08:40 +0530 Subject: [PATCH 2002/2908] Added cosine similarity (#7001) * Added cosine similarity * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- machine_learning/similarity_search.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py index 2f5fc46c065e..72979181f67c 100644 --- a/machine_learning/similarity_search.py +++ b/machine_learning/similarity_search.py @@ -12,6 +12,7 @@ import math import numpy as np +from numpy.linalg import norm def euclidean(input_a: np.ndarray, input_b: np.ndarray) -> float: @@ -135,6 +136,22 @@ def similarity_search( return answer +def cosine_similarity(input_a: np.ndarray, input_b: np.ndarray) -> float: + """ + Calculates cosine similarity between two data. + :param input_a: ndarray of first vector. + :param input_b: ndarray of second vector. + :return: Cosine similarity of input_a and input_b. By using math.sqrt(), + result will be float. + + >>> cosine_similarity(np.array([1]), np.array([1])) + 1.0 + >>> cosine_similarity(np.array([1, 2]), np.array([6, 32])) + 0.9615239476408232 + """ + return np.dot(input_a, input_b) / (norm(input_a) * norm(input_b)) + + if __name__ == "__main__": import doctest From 1550731cb7457ddae216da2ffe0bc1587f5234f3 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 29 Oct 2022 23:45:21 +0300 Subject: [PATCH 2003/2908] Remove file-level flake8 suppression (#7844) * Remove file-level flake8 suppression * updating DIRECTORY.md * Fix tests Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 ++ data_structures/heap/binomial_heap.py | 50 +++++++++++++-------------- other/activity_selection.py | 8 ++--- searches/binary_tree_traversal.py | 10 +++--- 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 198cc7077d2b..9ea8f3140f35 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -671,6 +671,7 @@ ## Physics * [Casimir Effect](physics/casimir_effect.py) + * [Centripetal Force](physics/centripetal_force.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) * [Kinetic Energy](physics/kinetic_energy.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) @@ -1069,6 +1070,7 @@ * [Is Palindrome](strings/is_palindrome.py) * [Is Pangram](strings/is_pangram.py) * [Is Spain National Id](strings/is_spain_national_id.py) + * [Is Srilankan Phone Number](strings/is_srilankan_phone_number.py) * [Jaro Winkler](strings/jaro_winkler.py) * [Join](strings/join.py) * [Knuth Morris Pratt](strings/knuth_morris_pratt.py) diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index 6398c99439cd..d79fac7a99d5 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -1,5 +1,3 @@ -# flake8: noqa - """ Binomial Heap Reference: Advanced Data Structures, Peter Brass @@ -22,7 +20,7 @@ def __init__(self, val): self.right = None self.parent = None - def mergeTrees(self, other): + def merge_trees(self, other): """ In-place merge of two binomial trees of equal size. Returns the root of the resulting tree @@ -75,9 +73,8 @@ class BinomialHeap: 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 + >>> [first_heap.delete_min() for _ in range(20)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] Create a new Heap >>> second_heap = BinomialHeap() @@ -97,8 +94,8 @@ class BinomialHeap: # # # # preOrder() test - >>> second_heap.preOrder() - [(17, 0), ('#', 1), (31, 1), (20, 2), ('#', 3), ('#', 3), (34, 2), ('#', 3), ('#', 3)] + >>> " ".join(str(x) for x in second_heap.pre_order()) + "(17, 0) ('#', 1) (31, 1) (20, 2) ('#', 3) ('#', 3) (34, 2) ('#', 3) ('#', 3)" printing Heap - __str__() test >>> print(second_heap) @@ -113,14 +110,17 @@ class BinomialHeap: ---# mergeHeaps() test - >>> merged = second_heap.mergeHeaps(first_heap) + >>> + >>> merged = second_heap.merge_heaps(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 + >>> results = [] + >>> while not first_heap.is_empty(): + ... results.append(first_heap.delete_min()) + >>> results + [17, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 34] """ def __init__(self, bottom_root=None, min_node=None, heap_size=0): @@ -128,7 +128,7 @@ def __init__(self, bottom_root=None, min_node=None, heap_size=0): self.bottom_root = bottom_root self.min_node = min_node - def mergeHeaps(self, other): + def merge_heaps(self, other): """ In-place merge of two binomial heaps. Both of them become the resulting merged heap @@ -180,7 +180,7 @@ def mergeHeaps(self, other): next_node = i.parent.parent # Merging trees - i = i.mergeTrees(i.parent) + i = i.merge_trees(i.parent) # Updating links i.left = previous_node @@ -238,7 +238,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.merge_trees(self.bottom_root.parent) # Update Links self.bottom_root.parent = next_node @@ -252,10 +252,10 @@ def peek(self): """ return self.min_node.val - def isEmpty(self): + def is_empty(self): return self.size == 0 - def deleteMin(self): + def delete_min(self): """ delete min element and return it """ @@ -317,7 +317,7 @@ def deleteMin(self): return min_value # Remaining cases # Construct heap of right subtree - newHeap = BinomialHeap( + new_heap = BinomialHeap( bottom_root=bottom_of_new, min_node=min_of_new, heap_size=size_of_new ) @@ -354,11 +354,11 @@ def deleteMin(self): self.min_node = i i = i.parent # Merge heaps - self.mergeHeaps(newHeap) + self.merge_heaps(new_heap) return min_value - def preOrder(self): + def pre_order(self): """ Returns the Pre-order representation of the heap including values of nodes plus their level distance from the root; @@ -369,9 +369,9 @@ def preOrder(self): while top_root.parent: top_root = top_root.parent # preorder - heap_preOrder = [] - self.__traversal(top_root, heap_preOrder) - return heap_preOrder + heap_pre_order = [] + self.__traversal(top_root, heap_pre_order) + return heap_pre_order def __traversal(self, curr_node, preorder, level=0): """ @@ -389,9 +389,9 @@ 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(): + if self.is_empty(): return "" - preorder_heap = self.preOrder() + preorder_heap = self.pre_order() return "\n".join(("-" * level + str(value)) for value, level in preorder_heap) diff --git a/other/activity_selection.py b/other/activity_selection.py index d809bf90a3f3..18ff6a24c32a 100644 --- a/other/activity_selection.py +++ b/other/activity_selection.py @@ -1,5 +1,3 @@ -# flake8: noqa - """The following implementation assumes that the activities are already sorted according to their finish time""" @@ -10,11 +8,11 @@ # finish[] --> An array that contains finish time of all activities -def printMaxActivities(start: list[int], finish: list[int]) -> None: +def print_max_activities(start: list[int], finish: list[int]) -> None: """ >>> start = [1, 3, 0, 5, 8, 5] >>> finish = [2, 4, 6, 7, 9, 9] - >>> printMaxActivities(start, finish) + >>> print_max_activities(start, finish) The following activities are selected: 0,1,3,4, """ @@ -43,4 +41,4 @@ def printMaxActivities(start: list[int], finish: list[int]) -> None: start = [1, 3, 0, 5, 8, 5] finish = [2, 4, 6, 7, 9, 9] - printMaxActivities(start, finish) + print_max_activities(start, finish) diff --git a/searches/binary_tree_traversal.py b/searches/binary_tree_traversal.py index 033db83d789e..66814b47883d 100644 --- a/searches/binary_tree_traversal.py +++ b/searches/binary_tree_traversal.py @@ -1,5 +1,3 @@ -# flake8: noqa - """ This is pure Python implementation of tree traversal algorithms """ @@ -157,16 +155,16 @@ def level_order_actual(node: TreeNode) -> None: q: queue.Queue = queue.Queue() q.put(node) while not q.empty(): - list = [] + list_ = [] while not q.empty(): node_dequeued = q.get() print(node_dequeued.data, end=",") if node_dequeued.left: - list.append(node_dequeued.left) + list_.append(node_dequeued.left) if node_dequeued.right: - list.append(node_dequeued.right) + list_.append(node_dequeued.right) print() - for node in list: + for node in list_: q.put(node) From 3ec0aa85c0074d838d97dc030e582743586cd80e Mon Sep 17 00:00:00 2001 From: SparshRastogi <75373475+SparshRastogi@users.noreply.github.com> Date: Sun, 30 Oct 2022 02:54:59 +0530 Subject: [PATCH 2004/2908] Update kinetic_energy.py (#7848) Fixed a typo error in docstrings --- physics/kinetic_energy.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/physics/kinetic_energy.py b/physics/kinetic_energy.py index 8863919ac79f..77016e223c16 100644 --- a/physics/kinetic_energy.py +++ b/physics/kinetic_energy.py @@ -2,16 +2,16 @@ Find the kinetic energy of an object, given its mass and velocity. Description : In physics, the kinetic energy of an object is the energy that it -possesses due to its motion. It is defined as the work needed to accelerate a body of a -given mass from rest to its stated velocity. Having gained this energy during its -acceleration, the body maintains this kinetic energy unless its speed changes. The same +possesses due to its motion.It is defined as the work needed to accelerate a body of a +given mass from rest to its stated velocity.Having gained this energy during its +acceleration, the body maintains this kinetic energy unless its speed changes.The same amount of work is done by the body when decelerating from its current speed to a state -of rest. Formally, a kinetic energy is any term in a system's Lagrangian which includes +of rest.Formally, a kinetic energy is any term in a system's Lagrangian which includes a derivative with respect to time. In classical mechanics, the kinetic energy of a non-rotating object of mass m traveling -at a speed v is ½mv². In relativistic mechanics, this is a good approximation only when -v is much less than the speed of light. The standard unit of kinetic energy is the +at a speed v is ½mv².In relativistic mechanics, this is a good approximation only when +v is much less than the speed of light.The standard unit of kinetic energy is the joule, while the English unit of kinetic energy is the foot-pound. Reference : https://en.m.wikipedia.org/wiki/Kinetic_energy @@ -20,7 +20,7 @@ def kinetic_energy(mass: float, velocity: float) -> float: """ - Calculate kinetick energy. + Calculate kinetic energy. The kinetic energy of a non-rotating object of mass m traveling at a speed v is ½mv² From 7b7b3dd086eb3d8f6a82aa94b4398c0b95a7f186 Mon Sep 17 00:00:00 2001 From: Jason Devers <74424054+jdevers1@users.noreply.github.com> Date: Sun, 30 Oct 2022 01:20:07 -0400 Subject: [PATCH 2005/2908] matrix/count_paths.py (#7533) * added recursive dfs backtracking for count paths with doctests * fixed doc testing * added type hints * redefined r as row, c as col * fixed naming conventions, ran mypy, only tests that didn't pass were using List[], rathan list() * added another doctest, as well as a explanation above * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update matrix/count_paths.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * Update matrix/count_paths.py Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris Co-authored-by: J Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris --- matrix/count_paths.py | 75 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 matrix/count_paths.py diff --git a/matrix/count_paths.py b/matrix/count_paths.py new file mode 100644 index 000000000000..4861ad5fd0aa --- /dev/null +++ b/matrix/count_paths.py @@ -0,0 +1,75 @@ +""" +Given a grid, where you start from the top left position [0, 0], +you want to find how many paths you can take to get to the bottom right position. + +start here -> 0 0 0 0 + 1 1 0 0 + 0 0 0 1 + 0 1 0 0 <- finish here +how many 'distinct' paths can you take to get to the finish? +Using a recursive depth-first search algorithm below, you are able to +find the number of distinct unique paths (count). + +'*' will demonstrate a path +In the example above, there are two distinct paths: +1. 2. + * * * 0 * * * * + 1 1 * 0 1 1 * * + 0 0 * 1 0 0 * 1 + 0 1 * * 0 1 * * +""" + + +def depth_first_search(grid: list[list[int]], row: int, col: int, visit: set) -> int: + """ + Recursive Backtracking Depth First Search Algorithm + + Starting from top left of a matrix, count the number of + paths that can reach the bottom right of a matrix. + 1 represents a block (inaccessible) + 0 represents a valid space (accessible) + + 0 0 0 0 + 1 1 0 0 + 0 0 0 1 + 0 1 0 0 + >>> grid = [[0, 0, 0, 0], [1, 1, 0, 0], [0, 0, 0, 1], [0, 1, 0, 0]] + >>> depth_first_search(grid, 0, 0, set()) + 2 + + 0 0 0 0 0 + 0 1 1 1 0 + 0 1 1 1 0 + 0 0 0 0 0 + >>> grid = [[0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0]] + >>> depth_first_search(grid, 0, 0, set()) + 2 + """ + row_length, col_length = len(grid), len(grid[0]) + if ( + min(row, col) < 0 + or row == row_length + or col == col_length + or (row, col) in visit + or grid[row][col] == 1 + ): + return 0 + if row == row_length - 1 and col == col_length - 1: + return 1 + + visit.add((row, col)) + + count = 0 + count += depth_first_search(grid, row + 1, col, visit) + count += depth_first_search(grid, row - 1, col, visit) + count += depth_first_search(grid, row, col + 1, visit) + count += depth_first_search(grid, row, col - 1, visit) + + visit.remove((row, col)) + return count + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2d3985006f0c88e339a900caa4974493bc6fa861 Mon Sep 17 00:00:00 2001 From: Itssxxsalman <114142076+Itssxxsalman@users.noreply.github.com> Date: Sun, 30 Oct 2022 12:03:28 +0500 Subject: [PATCH 2006/2908] Fix grammatical mistakes in `simple_keyword_cypher.py` (#6385) * Fixed grammitical mistake * Update ciphers/simple_keyword_cypher.py Co-authored-by: Caeden Perelli-Harris Co-authored-by: Christian Clauss Co-authored-by: Caeden Perelli-Harris --- ciphers/simple_keyword_cypher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ciphers/simple_keyword_cypher.py b/ciphers/simple_keyword_cypher.py index 447bacfc2e6c..1635471aebd1 100644 --- a/ciphers/simple_keyword_cypher.py +++ b/ciphers/simple_keyword_cypher.py @@ -21,7 +21,7 @@ def create_cipher_map(key: str) -> dict[str, str]: :param key: keyword to use :return: dictionary cipher map """ - # Create alphabet list + # Create a list of the letters in the alphabet alphabet = [chr(i + 65) for i in range(26)] # Remove duplicate characters from key key = remove_duplicates(key.upper()) From f340bde6e047d86171385b90a023ac01e8914d0c Mon Sep 17 00:00:00 2001 From: Caio Cordeiro Date: Sun, 30 Oct 2022 04:05:44 -0300 Subject: [PATCH 2007/2908] Add simple neural network (#6452) * feat: add simple foward propagation implementation * fix: add PR requested changes * feat: add code example * fix: solve pre-commit failure * feat: add doctest inside code execution * fix: PR requested changes * fix: pr requested changes Co-authored-by: Caio Cordeiro --- neural_network/simple_neural_network.py | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 neural_network/simple_neural_network.py diff --git a/neural_network/simple_neural_network.py b/neural_network/simple_neural_network.py new file mode 100644 index 000000000000..f2a3234873b5 --- /dev/null +++ b/neural_network/simple_neural_network.py @@ -0,0 +1,63 @@ +""" +Forward propagation explanation: +https://towardsdatascience.com/forward-propagation-in-neural-networks-simplified-math-and-code-version-bbcfef6f9250 +""" + +import math +import random + + +# Sigmoid +def sigmoid_function(value: float, deriv: bool = False) -> float: + """Return the sigmoid function of a float. + + >>> sigmoid_function(3.5) + 0.9706877692486436 + >>> sigmoid_function(3.5, True) + -8.75 + """ + if deriv: + return value * (1 - value) + return 1 / (1 + math.exp(-value)) + + +# Initial Value +INITIAL_VALUE = 0.02 + + +def forward_propagation(expected: int, number_propagations: int) -> float: + """Return the value found after the forward propagation training. + + >>> res = forward_propagation(32, 10000000) + >>> res > 31 and res < 33 + True + + >>> res = forward_propagation(32, 1000) + >>> res > 31 and res < 33 + False + """ + + # Random weight + weight = float(2 * (random.randint(1, 100)) - 1) + + for _ in range(number_propagations): + # Forward propagation + layer_1 = sigmoid_function(INITIAL_VALUE * weight) + # How much did we miss? + layer_1_error = (expected / 100) - layer_1 + # Error delta + layer_1_delta = layer_1_error * sigmoid_function(layer_1, True) + # Update weight + weight += INITIAL_VALUE * layer_1_delta + + return layer_1 * 100 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + expected = int(input("Expected value: ")) + number_propagations = int(input("Number of propagations: ")) + print(forward_propagation(expected, number_propagations)) From 0c5f1c01302c8208251f61730ba74e078bfd0ac8 Mon Sep 17 00:00:00 2001 From: ok-open-sc <114725648+ok-open-sc@users.noreply.github.com> Date: Sun, 30 Oct 2022 03:11:17 -0400 Subject: [PATCH 2008/2908] Increased Readability Of Variables (#6400) * Increased Readability Of Variables * Update anagrams.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update anagrams.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- strings/anagrams.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/strings/anagrams.py b/strings/anagrams.py index b671d3f3d531..fb9ac0bd1f45 100644 --- a/strings/anagrams.py +++ b/strings/anagrams.py @@ -26,15 +26,15 @@ def anagram(my_word: str) -> list[str]: >>> anagram('final') ['final'] """ - return word_bysig[signature(my_word)] + return word_by_signature[signature(my_word)] data: str = Path(__file__).parent.joinpath("words.txt").read_text(encoding="utf-8") word_list = sorted({word.strip().lower() for word in data.splitlines()}) -word_bysig = collections.defaultdict(list) +word_by_signature = collections.defaultdict(list) for word in word_list: - word_bysig[signature(word)].append(word) + word_by_signature[signature(word)].append(word) if __name__ == "__main__": all_anagrams = {word: anagram(word) for word in word_list if len(anagram(word)) > 1} From f87de60b6d1cd6e9ce412503f48727015f46ada2 Mon Sep 17 00:00:00 2001 From: lostybtw <58177990+lostybtw@users.noreply.github.com> Date: Sun, 30 Oct 2022 07:22:52 +0000 Subject: [PATCH 2009/2908] fizzbuzz complete (#6504) * fizzbuzz * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * added doctests and function to fizzbuzz * Update fizz_buzz.py * Update fizz_buzz.py * Fixed FizzBuzz * fizzbuzz passing test * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * Update dynamic_programming/fizz_buzz.py Co-authored-by: Caeden * Update fizz_buzz.py * Update fizz_buzz.py * Update fizz_buzz.py * fixed fizzbuzz * Add files via upload * added mechanical energy calculation * Delete mechanical_energy.py * Update fizz_buzz.py * Update dynamic_programming/fizz_buzz.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fizz_buzz.py Co-authored-by: Caeden Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/fizz_buzz.py | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 dynamic_programming/fizz_buzz.py diff --git a/dynamic_programming/fizz_buzz.py b/dynamic_programming/fizz_buzz.py new file mode 100644 index 000000000000..dd1d21b1075e --- /dev/null +++ b/dynamic_programming/fizz_buzz.py @@ -0,0 +1,65 @@ +# https://en.wikipedia.org/wiki/Fizz_buzz#Programming + + +def fizz_buzz(number: int, iterations: int) -> str: + """ + Plays FizzBuzz. + Prints Fizz if number is a multiple of 3. + Prints Buzz if its a multiple of 5. + Prints FizzBuzz if its a multiple of both 3 and 5 or 15. + Else Prints The Number Itself. + >>> fizz_buzz(1,7) + '1 2 Fizz 4 Buzz Fizz 7 ' + >>> fizz_buzz(1,0) + Traceback (most recent call last): + ... + ValueError: Iterations must be done more than 0 times to play FizzBuzz + >>> fizz_buzz(-5,5) + Traceback (most recent call last): + ... + ValueError: starting number must be + and integer and be more than 0 + >>> fizz_buzz(10,-5) + Traceback (most recent call last): + ... + ValueError: Iterations must be done more than 0 times to play FizzBuzz + >>> fizz_buzz(1.5,5) + Traceback (most recent call last): + ... + ValueError: starting number must be + and integer and be more than 0 + >>> fizz_buzz(1,5.5) + Traceback (most recent call last): + ... + ValueError: iterations must be defined as integers + """ + + if not type(iterations) == int: + raise ValueError("iterations must be defined as integers") + if not type(number) == int or not number >= 1: + raise ValueError( + """starting number must be + and integer and be more than 0""" + ) + if not iterations >= 1: + raise ValueError("Iterations must be done more than 0 times to play FizzBuzz") + + out = "" + while number <= iterations: + if number % 3 == 0: + out += "Fizz" + if number % 5 == 0: + out += "Buzz" + if not number % 3 == 0 and not number % 5 == 0: + out += str(number) + + # print(out) + number += 1 + out += " " + return out + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 17d93cab783095dd1def3c382866cd94296db455 Mon Sep 17 00:00:00 2001 From: Carlos Villar Date: Sun, 30 Oct 2022 10:00:47 +0100 Subject: [PATCH 2010/2908] Added Manhattan distance algorithm (#7790) * Added Manhattan distance algorithm, Fixes: #7776 * Forgot that isinstance can accept a tuple * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update manhattan_distance.py * Update manhattan_distance.py Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/manhattan_distance.py | 126 ++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 maths/manhattan_distance.py diff --git a/maths/manhattan_distance.py b/maths/manhattan_distance.py new file mode 100644 index 000000000000..2711d4c8ccd6 --- /dev/null +++ b/maths/manhattan_distance.py @@ -0,0 +1,126 @@ +def manhattan_distance(point_a: list, point_b: list) -> float: + """ + Expectts two list of numbers representing two points in the same + n-dimensional space + + https://en.wikipedia.org/wiki/Taxicab_geometry + + >>> manhattan_distance([1,1], [2,2]) + 2.0 + >>> manhattan_distance([1.5,1.5], [2,2]) + 1.0 + >>> manhattan_distance([1.5,1.5], [2.5,2]) + 1.5 + >>> manhattan_distance([-3, -3, -3], [0, 0, 0]) + 9.0 + >>> manhattan_distance([1,1], None) + Traceback (most recent call last): + ... + ValueError: Missing an input + >>> manhattan_distance([1,1], [2, 2, 2]) + Traceback (most recent call last): + ... + ValueError: Both points must be in the same n-dimensional space + >>> manhattan_distance([1,"one"], [2, 2, 2]) + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found str + >>> manhattan_distance(1, [2, 2, 2]) + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found int + >>> manhattan_distance([1,1], "not_a_list") + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found str + """ + + _validate_point(point_a) + _validate_point(point_b) + if len(point_a) != len(point_b): + raise ValueError("Both points must be in the same n-dimensional space") + + return float(sum(abs(a - b) for a, b in zip(point_a, point_b))) + + +def _validate_point(point: list[float]) -> None: + """ + >>> _validate_point(None) + Traceback (most recent call last): + ... + ValueError: Missing an input + >>> _validate_point([1,"one"]) + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found str + >>> _validate_point(1) + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found int + >>> _validate_point("not_a_list") + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found str + """ + if point: + if isinstance(point, list): + for item in point: + if not isinstance(item, (int, float)): + raise TypeError( + f"Expected a list of numbers as input, " + f"found {type(item).__name__}" + ) + else: + raise TypeError( + f"Expected a list of numbers as input, found {type(point).__name__}" + ) + else: + raise ValueError("Missing an input") + + +def manhattan_distance_one_liner(point_a: list, point_b: list) -> float: + """ + Version with one liner + + >>> manhattan_distance_one_liner([1,1], [2,2]) + 2.0 + >>> manhattan_distance_one_liner([1.5,1.5], [2,2]) + 1.0 + >>> manhattan_distance_one_liner([1.5,1.5], [2.5,2]) + 1.5 + >>> manhattan_distance_one_liner([-3, -3, -3], [0, 0, 0]) + 9.0 + >>> manhattan_distance_one_liner([1,1], None) + Traceback (most recent call last): + ... + ValueError: Missing an input + >>> manhattan_distance_one_liner([1,1], [2, 2, 2]) + Traceback (most recent call last): + ... + ValueError: Both points must be in the same n-dimensional space + >>> manhattan_distance_one_liner([1,"one"], [2, 2, 2]) + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found str + >>> manhattan_distance_one_liner(1, [2, 2, 2]) + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found int + >>> manhattan_distance_one_liner([1,1], "not_a_list") + Traceback (most recent call last): + ... + TypeError: Expected a list of numbers as input, found str + """ + + _validate_point(point_a) + _validate_point(point_b) + if len(point_a) != len(point_b): + raise ValueError("Both points must be in the same n-dimensional space") + + return float(sum(abs(x - y) for x, y in zip(point_a, point_b))) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 57ccabbaeb0f32165271e3a218bc9c6dcfc21823 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sun, 30 Oct 2022 11:01:58 +0200 Subject: [PATCH 2011/2908] Update docs (#7867) * Update docs, remove unused excludes from pre-commit * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ---- CONTRIBUTING.md | 2 +- DIRECTORY.md | 12 ++++++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 56946f5f240f..004def5e4e8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,10 +7,6 @@ repos: - 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/MarcoGorelli/auto-walrus diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cbb24e563da..37e020b8fd8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ pre-commit run --all-files --show-diff-on-failure We want your work to be readable by others; therefore, we encourage you to note the following: -- Please write in Python 3.9+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. +- Please write in Python 3.10+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. - Please focus hard on the 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. diff --git a/DIRECTORY.md b/DIRECTORY.md index 9ea8f3140f35..8ac9c3be713a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -158,6 +158,8 @@ * [Weight Conversion](conversions/weight_conversion.py) ## Data Structures + * Arrays + * [Permutations](data_structures/arrays/permutations.py) * Binary Tree * [Avl Tree](data_structures/binary_tree/avl_tree.py) * [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py) @@ -291,6 +293,7 @@ * [Factorial](dynamic_programming/factorial.py) * [Fast Fibonacci](dynamic_programming/fast_fibonacci.py) * [Fibonacci](dynamic_programming/fibonacci.py) + * [Fizz Buzz](dynamic_programming/fizz_buzz.py) * [Floyd Warshall](dynamic_programming/floyd_warshall.py) * [Integer Partition](dynamic_programming/integer_partition.py) * [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py) @@ -313,12 +316,16 @@ * [Rod Cutting](dynamic_programming/rod_cutting.py) * [Subset Generation](dynamic_programming/subset_generation.py) * [Sum Of Subset](dynamic_programming/sum_of_subset.py) + * [Viterbi](dynamic_programming/viterbi.py) ## Electronics * [Carrier Concentration](electronics/carrier_concentration.py) * [Coulombs Law](electronics/coulombs_law.py) + * [Electric Conductivity](electronics/electric_conductivity.py) * [Electric Power](electronics/electric_power.py) + * [Electrical Impedance](electronics/electrical_impedance.py) * [Ohms Law](electronics/ohms_law.py) + * [Resonant Frequency](electronics/resonant_frequency.py) ## File Transfer * [Receive File](file_transfer/receive_file.py) @@ -430,6 +437,7 @@ ## Knapsack * [Greedy Knapsack](knapsack/greedy_knapsack.py) * [Knapsack](knapsack/knapsack.py) + * [Recursive Approach Knapsack](knapsack/recursive_approach_knapsack.py) * Tests * [Test Greedy Knapsack](knapsack/tests/test_greedy_knapsack.py) * [Test Knapsack](knapsack/tests/test_knapsack.py) @@ -622,6 +630,7 @@ ## Matrix * [Binary Search Matrix](matrix/binary_search_matrix.py) * [Count Islands In Matrix](matrix/count_islands_in_matrix.py) + * [Count Paths](matrix/count_paths.py) * [Cramers Rule 2X2](matrix/cramers_rule_2x2.py) * [Inverse Of Matrix](matrix/inverse_of_matrix.py) * [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py) @@ -645,6 +654,7 @@ * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Perceptron](neural_network/perceptron.py) + * [Simple Neural Network](neural_network/simple_neural_network.py) ## Other * [Activity Selection](other/activity_selection.py) @@ -680,6 +690,7 @@ * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) * [Potential Energy](physics/potential_energy.py) + * [Sheer Stress](physics/sheer_stress.py) ## Project Euler * Problem 001 @@ -1089,6 +1100,7 @@ * [Reverse Words](strings/reverse_words.py) * [Snake Case To Camel Pascal Case](strings/snake_case_to_camel_pascal_case.py) * [Split](strings/split.py) + * [Text Justification](strings/text_justification.py) * [Upper](strings/upper.py) * [Wave](strings/wave.py) * [Wildcard Pattern Matching](strings/wildcard_pattern_matching.py) From 5ba5c548584f44bac0bc3c0cb4e95233560627cf Mon Sep 17 00:00:00 2001 From: Sushant Srivastav <63559772+sushant4191@users.noreply.github.com> Date: Sun, 30 Oct 2022 14:38:54 +0530 Subject: [PATCH 2012/2908] Updated info (#7866) * Updated info Updated the readme section for sorts. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sorts/README.md Co-authored-by: Caeden Perelli-Harris * Update README.md Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris Co-authored-by: Christian Clauss --- sorts/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 sorts/README.md diff --git a/sorts/README.md b/sorts/README.md new file mode 100644 index 000000000000..ceb0207c2be4 --- /dev/null +++ b/sorts/README.md @@ -0,0 +1,11 @@ +# Sorting Algorithms +Sorting is the process of putting data in a specific order. The way to arrange data in a specific order +is specified by the sorting algorithm. The most typical orders are lexical or numerical. The significance +of sorting lies in the fact that, if data is stored in a sorted manner, data searching can be highly optimised. +Another use for sorting is to represent data in a more readable manner. + +This section contains a lot of important algorithms that helps us to use sorting algorithms in various scenarios. +## References +* +* +* From 87a5d919761e9ccb05e19e68a5307348c6264cd0 Mon Sep 17 00:00:00 2001 From: Kevin Joven <59969678+KevinJoven11@users.noreply.github.com> Date: Sun, 30 Oct 2022 05:49:33 -0400 Subject: [PATCH 2013/2908] quantum_teleportation.py (#6632) * quantum_teleportation.py This code is for the #Hacktoberfest. This file run the quantum teleportation circuit using Qiskit. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update quantum/quantum_teleportation.py Co-authored-by: Caeden * Update quantum/quantum_teleportation.py Co-authored-by: Caeden * Update Corrected some typos. Add more comments for adding the gates. Update the variable qc with quantum_circuit in the simulator and execute. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * python return typehint solved. * Fix long line Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Co-authored-by: Christian Clauss --- quantum/quantum_teleportation.py | 70 ++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 quantum/quantum_teleportation.py diff --git a/quantum/quantum_teleportation.py b/quantum/quantum_teleportation.py new file mode 100644 index 000000000000..5fbc57a66821 --- /dev/null +++ b/quantum/quantum_teleportation.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +Build quantum teleportation circuit using three quantum bits +and 1 classical bit. The main idea is to send one qubit from +Alice to Bob using the entanglement properties. This experiment +run in IBM Q simulator with 1000 shots. +. +References: +https://en.wikipedia.org/wiki/Quantum_teleportation +https://qiskit.org/textbook/ch-algorithms/teleportation.html +""" + +import numpy as np +import qiskit +from qiskit import Aer, ClassicalRegister, QuantumCircuit, QuantumRegister, execute + + +def quantum_teleportation( + theta: float = np.pi / 2, phi: float = np.pi / 2, lam: float = np.pi / 2 +) -> qiskit.result.counts.Counts: + + """ + # >>> quantum_teleportation() + #{'00': 500, '11': 500} # ideally + # ┌─────────────────┐ ┌───┐ + #qr_0: ┤ U(π/2,π/2,π/2) ├───────■──┤ H ├─■───────── + # └──────┬───┬──────┘ ┌─┴─┐└───┘ │ + #qr_1: ───────┤ H ├─────────■──┤ X ├──────┼───■───── + # └───┘ ┌─┴─┐└───┘ │ ┌─┴─┐┌─┐ + #qr_2: ───────────────────┤ X ├───────────■─┤ X ├┤M├ + # └───┘ └───┘└╥┘ + #cr: 1/═══════════════════════════════════════════╩═ + Args: + theta (float): Single qubit rotation U Gate theta parameter. Default to np.pi/2 + phi (float): Single qubit rotation U Gate phi parameter. Default to np.pi/2 + lam (float): Single qubit rotation U Gate lam parameter. Default to np.pi/2 + Returns: + qiskit.result.counts.Counts: Teleported qubit counts. + """ + + qr = QuantumRegister(3, "qr") # Define the number of quantum bits + cr = ClassicalRegister(1, "cr") # Define the number of classical bits + + quantum_circuit = QuantumCircuit(qr, cr) # Define the quantum circuit. + + # Build the circuit + quantum_circuit.u(theta, phi, lam, 0) # Quantum State to teleport + quantum_circuit.h(1) # add hadamard gate + quantum_circuit.cx( + 1, 2 + ) # add control gate with qubit 1 as control and 2 as target. + quantum_circuit.cx(0, 1) + quantum_circuit.h(0) + quantum_circuit.cz(0, 2) # add control z gate. + quantum_circuit.cx(1, 2) + + quantum_circuit.measure([2], [0]) # measure the qubit. + + # Simulate the circuit using qasm simulator + backend = Aer.get_backend("qasm_simulator") + job = execute(quantum_circuit, backend, shots=1000) + + return job.result().get_counts(quantum_circuit) + + +if __name__ == "__main__": + print( + "Total count for teleported state is: " + f"{quantum_teleportation(np.pi/2, np.pi/2, np.pi/2)}" + ) From 00dfad9d20abf755a91abc0ba35f5d92fcab9149 Mon Sep 17 00:00:00 2001 From: giladwo <25708271+giladwo@users.noreply.github.com> Date: Sun, 30 Oct 2022 11:59:10 +0200 Subject: [PATCH 2014/2908] Simplify climbing stairs and use constant memory (#6628) * Simplify climbing stairs and use constant memory * number_of_steps Co-authored-by: Christian Clauss --- dynamic_programming/climbing_stairs.py | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/dynamic_programming/climbing_stairs.py b/dynamic_programming/climbing_stairs.py index 048d57aed1be..d6273d025f08 100644 --- a/dynamic_programming/climbing_stairs.py +++ b/dynamic_programming/climbing_stairs.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 -def climb_stairs(n: int) -> int: +def climb_stairs(number_of_steps: 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. + Distinct ways to climb a number_of_steps staircase where each time you can either + climb 1 or 2 steps. Args: - n: number of steps of staircase + number_of_steps: number of steps on the staircase Returns: - Distinct ways to climb a n step staircase + Distinct ways to climb a number_of_steps staircase Raises: - AssertionError: n not positive integer + AssertionError: number_of_steps not positive integer >>> climb_stairs(3) 3 @@ -23,18 +23,17 @@ def climb_stairs(n: int) -> int: >>> climb_stairs(-7) # doctest: +ELLIPSIS Traceback (most recent call last): ... - AssertionError: n needs to be positive integer, your input -7 + AssertionError: number_of_steps needs to be positive integer, your input -7 """ assert ( - isinstance(n, int) and n > 0 - ), f"n needs to be positive integer, your input {n}" - if n == 1: + isinstance(number_of_steps, int) and number_of_steps > 0 + ), f"number_of_steps needs to be positive integer, your input {number_of_steps}" + if number_of_steps == 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] + previous, current = 1, 1 + for _ in range(number_of_steps - 1): + current, previous = current + previous, current + return current if __name__ == "__main__": From 84facb78b20be6a9a90307c79e318c65a04987ac Mon Sep 17 00:00:00 2001 From: Saksham1970 <45041294+Saksham1970@users.noreply.github.com> Date: Sun, 30 Oct 2022 15:40:16 +0530 Subject: [PATCH 2015/2908] Project Euler: 092 decreased the time (#6627) * Added explanation and increased speed of the solution of problem 092 * updating DIRECTORY.md * Added temporary fix to the failing of problem 104 * Reduced few seconds by minor improvements * Update sol.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- project_euler/problem_092/sol1.py | 42 +++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/project_euler/problem_092/sol1.py b/project_euler/problem_092/sol1.py index d326fc33fcca..33a6c06946f7 100644 --- a/project_euler/problem_092/sol1.py +++ b/project_euler/problem_092/sol1.py @@ -11,11 +11,11 @@ How many starting numbers below ten million will arrive at 89? """ - -DIGITS_SQUARED = [digit**2 for digit in range(10)] +DIGITS_SQUARED = [sum(int(c, 10) ** 2 for c in i.__str__()) for i in range(100000)] def next_number(number: int) -> int: + """ Returns the next number of the chain by adding the square of each digit to form a new number. @@ -28,15 +28,29 @@ def next_number(number: int) -> int: >>> next_number(32) 13 """ + sum_of_digits_squared = 0 while number: - sum_of_digits_squared += DIGITS_SQUARED[number % 10] - number //= 10 + + # Increased Speed Slightly by checking every 5 digits together. + sum_of_digits_squared += DIGITS_SQUARED[number % 100000] + number //= 100000 return sum_of_digits_squared -CHAINS = {1: True, 58: False} +# There are 2 Chains made, +# One ends with 89 with the chain member 58 being the one which when declared first, +# there will be the least number of iterations for all the members to be checked. + +# The other one ends with 1 and has only one element 1. + +# So 58 and 1 are chosen to be declared at the starting. + +# Changed dictionary to an array to quicken the solution +CHAINS: list[bool | None] = [None] * 10000000 +CHAINS[0] = True +CHAINS[57] = False def chain(number: int) -> bool: @@ -54,11 +68,16 @@ def chain(number: int) -> bool: >>> chain(1) True """ - if number in CHAINS: - return CHAINS[number] + + if CHAINS[number - 1] is not None: + return CHAINS[number - 1] # type: ignore number_chain = chain(next_number(number)) - CHAINS[number] = number_chain + CHAINS[number - 1] = number_chain + + while number < 10000000: + CHAINS[number - 1] = number_chain + number *= 10 return number_chain @@ -74,12 +93,15 @@ def solution(number: int = 10000000) -> int: >>> solution(10000000) 8581146 """ - return sum(1 for i in range(1, number) if not chain(i)) + for i in range(1, number): + if CHAINS[i] is None: + chain(i + 1) + + return CHAINS[:number].count(False) if __name__ == "__main__": import doctest doctest.testmod() - print(f"{solution() = }") From 48a73a28d477a1b634479001bc04e0886b265bfb Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Oct 2022 10:11:29 +0000 Subject: [PATCH 2016/2908] fix(quantum): Correct simulator deprecation (#7869) --- quantum/quantum_teleportation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/quantum_teleportation.py b/quantum/quantum_teleportation.py index 5fbc57a66821..d04b44d15a05 100644 --- a/quantum/quantum_teleportation.py +++ b/quantum/quantum_teleportation.py @@ -57,7 +57,7 @@ def quantum_teleportation( quantum_circuit.measure([2], [0]) # measure the qubit. # Simulate the circuit using qasm simulator - backend = Aer.get_backend("qasm_simulator") + backend = Aer.get_backend("aer_simulator") job = execute(quantum_circuit, backend, shots=1000) return job.result().get_counts(quantum_circuit) From ba576a9a0b0a41405cfa11606c39908a1bc2b01b Mon Sep 17 00:00:00 2001 From: Devesh Swarnkar <71492529+devesh-0419@users.noreply.github.com> Date: Sun, 30 Oct 2022 15:44:02 +0530 Subject: [PATCH 2017/2908] Create README.md (#6642) for blockchain file --- blockchain/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 blockchain/README.md diff --git a/blockchain/README.md b/blockchain/README.md new file mode 100644 index 000000000000..5ae7f95ec981 --- /dev/null +++ b/blockchain/README.md @@ -0,0 +1,8 @@ +# Blockchain + +A Blockchain is a type of distributed ledger technology (DLT) that consists of growing list of records, called blocks, that are securely linked together using cryptography. + +* +* +* +* From ca923389c0330b6b7afc935bdd7fa9a15d377079 Mon Sep 17 00:00:00 2001 From: Si Lam Date: Sun, 30 Oct 2022 05:25:51 -0500 Subject: [PATCH 2018/2908] Description of Double hasing (#6467) * Description of DOuble hasing * Fix sheebang * Update double_hash.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update double_hash.py Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/hashing/double_hash.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index bd1355fca65d..453e0d13106d 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -1,4 +1,16 @@ #!/usr/bin/env python3 +""" +Double hashing is a collision resolving technique in Open Addressed Hash tables. +Double hashing uses the idea of applying a second hash function to key when a collision +occurs. The advantage of Double hashing is that it is one of the best form of probing, +producing a uniform distribution of records throughout a hash table. This technique +does not yield any clusters. It is one of effective method for resolving collisions. + +Double hashing can be done using: (hash1(key) + i * hash2(key)) % TABLE_SIZE +Where hash1() and hash2() are hash functions and TABLE_SIZE is size of hash table. + +Reference: https://en.wikipedia.org/wiki/Double_hashing +""" from .hash_table import HashTable from .number_theory.prime_numbers import is_prime, next_prime From c0b0b128b7ad4a5a75ed866bc7c114c3cf7a89ef Mon Sep 17 00:00:00 2001 From: Kavienan J <45987371+kavienanj@users.noreply.github.com> Date: Sun, 30 Oct 2022 15:56:46 +0530 Subject: [PATCH 2019/2908] Add Ideal Gas Law for physics (#6503) * add physics ideal gas law * run pre commit * Update physics/ideal_gas_law.py Suggestion #1 Co-authored-by: Caeden * Update physics/ideal_gas_law.py Suggestion #2 Co-authored-by: Caeden * run pre commit * Update volume return line sugesstion Co-authored-by: Caeden Perelli-Harris * Add suggestions * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris Co-authored-by: Caeden Co-authored-by: Christian Clauss --- physics/ideal_gas_law.py | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 physics/ideal_gas_law.py diff --git a/physics/ideal_gas_law.py b/physics/ideal_gas_law.py new file mode 100644 index 000000000000..805da47b0079 --- /dev/null +++ b/physics/ideal_gas_law.py @@ -0,0 +1,59 @@ +""" +The ideal gas law, also called the general gas equation, is the +equation of state of a hypothetical ideal gas. It is a good approximation +of the behavior of many gases under many conditions, although it has +several limitations. It was first stated by Benoît Paul Émile Clapeyron +in 1834 as a combination of the empirical Boyle's law, Charles's law, +Avogadro's law, and Gay-Lussac's law.[1] The ideal gas law is often written +in an empirical form: + ------------ + | PV = nRT | + ------------ +P = Pressure (Pa) +V = Volume (m^3) +n = Amount of substance (mol) +R = Universal gas constant +T = Absolute temperature (Kelvin) + +(Description adapted from https://en.wikipedia.org/wiki/Ideal_gas_law ) +""" + +UNIVERSAL_GAS_CONSTANT = 8.314462 # Unit - J mol-1 K-1 + + +def pressure_of_gas_system(moles: float, kelvin: float, volume: float) -> float: + """ + >>> pressure_of_gas_system(2, 100, 5) + 332.57848 + >>> pressure_of_gas_system(0.5, 273, 0.004) + 283731.01575 + >>> pressure_of_gas_system(3, -0.46, 23.5) + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter positive value. + """ + if moles < 0 or kelvin < 0 or volume < 0: + raise ValueError("Invalid inputs. Enter positive value.") + return moles * kelvin * UNIVERSAL_GAS_CONSTANT / volume + + +def volume_of_gas_system(moles: float, kelvin: float, pressure: float) -> float: + """ + >>> volume_of_gas_system(2, 100, 5) + 332.57848 + >>> volume_of_gas_system(0.5, 273, 0.004) + 283731.01575 + >>> volume_of_gas_system(3, -0.46, 23.5) + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter positive value. + """ + if moles < 0 or kelvin < 0 or pressure < 0: + raise ValueError("Invalid inputs. Enter positive value.") + return moles * kelvin * UNIVERSAL_GAS_CONSTANT / pressure + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From b32903d22f3a0fc8985a3dd1e4c4645f12b9f961 Mon Sep 17 00:00:00 2001 From: Kavienan J <45987371+kavienanj@users.noreply.github.com> Date: Sun, 30 Oct 2022 15:59:00 +0530 Subject: [PATCH 2020/2908] Add root mean square speed of gas molecules to physics (#6569) * add rms speed of molecule to physics * Update physics/rms_speed_of_molecule.py Co-authored-by: Caeden Perelli-Harris Co-authored-by: Christian Clauss Co-authored-by: Caeden Perelli-Harris --- physics/rms_speed_of_molecule.py | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 physics/rms_speed_of_molecule.py diff --git a/physics/rms_speed_of_molecule.py b/physics/rms_speed_of_molecule.py new file mode 100644 index 000000000000..478cee01c7fd --- /dev/null +++ b/physics/rms_speed_of_molecule.py @@ -0,0 +1,52 @@ +""" +The root-mean-square speed is essential in measuring the average speed of particles +contained in a gas, defined as, + ----------------- + | Vrms = √3RT/M | + ----------------- + +In Kinetic Molecular Theory, gasified particles are in a condition of constant random +motion; each particle moves at a completely different pace, perpetually clashing and +changing directions consistently velocity is used to describe the movement of gas +particles, thereby taking into account both speed and direction. Although the velocity +of gaseous particles is constantly changing, the distribution of velocities does not +change. +We cannot gauge the velocity of every individual particle, thus we frequently reason +in terms of the particles average behavior. Particles moving in opposite directions +have velocities of opposite signs. Since gas particles are in random motion, it's +plausible that there'll be about as several moving in one direction as within the other +way, which means that the average velocity for a collection of gas particles equals +zero; as this value is unhelpful, the average of velocities can be determined using an +alternative method. +""" + + +UNIVERSAL_GAS_CONSTANT = 8.3144598 + + +def rms_speed_of_molecule(temperature: float, molar_mass: float) -> float: + """ + >>> rms_speed_of_molecule(100, 2) + 35.315279554323226 + >>> rms_speed_of_molecule(273, 12) + 23.821458421977443 + """ + if temperature < 0: + raise Exception("Temperature cannot be less than 0 K") + if molar_mass <= 0: + raise Exception("Molar mass cannot be less than or equal to 0 kg/mol") + else: + return (3 * UNIVERSAL_GAS_CONSTANT * temperature / molar_mass) ** 0.5 + + +if __name__ == "__main__": + import doctest + + # run doctest + doctest.testmod() + + # example + temperature = 300 + molar_mass = 28 + vrms = rms_speed_of_molecule(temperature, molar_mass) + print(f"Vrms of Nitrogen gas at 300 K is {vrms} m/s") From fcfe35c3d8ed15037c0f20e3ee2268eea840b1ff Mon Sep 17 00:00:00 2001 From: samyakpagariya <72349392+samyakpagariya@users.noreply.github.com> Date: Sun, 30 Oct 2022 16:13:41 +0530 Subject: [PATCH 2021/2908] For the better understanding of time taken. (#6583) * For the better understanding of time taken. In this change I have initialized a variable p with the value of (1e9+7) and then took the modulus of process time with it . This modification gives better time taken by the process . Firstly it was giving answer in the exponential now it gives in the integer form. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Caeden Perelli-Harris --- sorts/bubble_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index d4f0d25ca77c..aef2da272bd0 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -49,4 +49,4 @@ def bubble_sort(collection): 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}") + print(f"Processing time: {(time.process_time() - start)%1e9 + 7}") From 00fc53de9709648b495ecf707549d6068592fb76 Mon Sep 17 00:00:00 2001 From: happiestbee <87628038+happiestbee@users.noreply.github.com> Date: Sun, 30 Oct 2022 06:49:05 -0400 Subject: [PATCH 2022/2908] added sumset.py Fixes: #{6563} (#6742) * Create sumset.py * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add descriptive var names * Update maths/sumset.py Co-authored-by: Caeden * Update sumset.py * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Co-authored-by: Christian Clauss --- DIRECTORY.md | 5 +++++ maths/sumset.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 maths/sumset.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 8ac9c3be713a..38fd1d656488 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -560,6 +560,7 @@ * [Lucas Lehmer Primality Test](maths/lucas_lehmer_primality_test.py) * [Lucas Series](maths/lucas_series.py) * [Maclaurin Series](maths/maclaurin_series.py) + * [Manhattan Distance](maths/manhattan_distance.py) * [Matrix Exponentiation](maths/matrix_exponentiation.py) * [Max Sum Sliding Window](maths/max_sum_sliding_window.py) * [Median Of Two Arrays](maths/median_of_two_arrays.py) @@ -616,6 +617,7 @@ * [Sum Of Digits](maths/sum_of_digits.py) * [Sum Of Geometric Progression](maths/sum_of_geometric_progression.py) * [Sum Of Harmonic Series](maths/sum_of_harmonic_series.py) + * [Sumset](maths/sumset.py) * [Sylvester Sequence](maths/sylvester_sequence.py) * [Test Prime Check](maths/test_prime_check.py) * [Trapezoidal Rule](maths/trapezoidal_rule.py) @@ -683,6 +685,7 @@ * [Casimir Effect](physics/casimir_effect.py) * [Centripetal Force](physics/centripetal_force.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) + * [Ideal Gas Law](physics/ideal_gas_law.py) * [Kinetic Energy](physics/kinetic_energy.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) * [Malus Law](physics/malus_law.py) @@ -690,6 +693,7 @@ * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) * [Potential Energy](physics/potential_energy.py) + * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Sheer Stress](physics/sheer_stress.py) ## Project Euler @@ -978,6 +982,7 @@ * [Not Gate](quantum/not_gate.py) * [Q Full Adder](quantum/q_full_adder.py) * [Quantum Entanglement](quantum/quantum_entanglement.py) + * [Quantum Teleportation](quantum/quantum_teleportation.py) * [Ripple Adder Classic](quantum/ripple_adder_classic.py) * [Single Qubit Measure](quantum/single_qubit_measure.py) * [Superdense Coding](quantum/superdense_coding.py) diff --git a/maths/sumset.py b/maths/sumset.py new file mode 100644 index 000000000000..fa18f9e24b4c --- /dev/null +++ b/maths/sumset.py @@ -0,0 +1,37 @@ +""" + +Calculates the SumSet of two sets of numbers (A and B) + +Source: + https://en.wikipedia.org/wiki/Sumset + +""" + + +def sumset(set_a: set, set_b: set) -> set: + """ + :param first set: a set of numbers + :param second set: a set of numbers + :return: the nth number in Sylvester's sequence + + >>> sumset({1, 2, 3}, {4, 5, 6}) + {5, 6, 7, 8, 9} + + >>> sumset({1, 2, 3}, {4, 5, 6, 7}) + {5, 6, 7, 8, 9, 10} + + >>> sumset({1, 2, 3, 4}, 3) + Traceback (most recent call last): + ... + AssertionError: The input value of [set_b=3] is not a set + """ + assert isinstance(set_a, set), f"The input value of [set_a={set_a}] is not a set" + assert isinstance(set_b, set), f"The input value of [set_b={set_b}] is not a set" + + return {a + b for a in set_a for b in set_b} + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 6b6d8cc1110b16b38c7e6aafe91cb6f9583669ae Mon Sep 17 00:00:00 2001 From: Micael Pereira <8707982+micaelalex@users.noreply.github.com> Date: Sun, 30 Oct 2022 10:49:22 +0000 Subject: [PATCH 2023/2908] Adding ELFHash Algorithm (#6731) * Adding ELFHash Algorithm Adding a new Hash Algorithm. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update elf.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update elf.py * Update elf.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update elf.py * Apply suggestions from code review Co-authored-by: Caeden Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden --- hashes/elf.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 hashes/elf.py diff --git a/hashes/elf.py b/hashes/elf.py new file mode 100644 index 000000000000..87fe339da44d --- /dev/null +++ b/hashes/elf.py @@ -0,0 +1,23 @@ +def elf_hash(data: str) -> int: + """ + Implementation of ElfHash Algorithm, a variant of PJW hash function. + + Returns: + [int] -- [32 bit binary int] + >>> elf_hash('lorem ipsum') + 253956621 + """ + hash = x = 0 + for letter in data: + hash = (hash << 4) + ord(letter) + x = hash & 0xF0000000 + if x != 0: + hash ^= x >> 24 + hash &= ~x + return hash + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From cc423007800b8707ea87353be808a90bef13ba18 Mon Sep 17 00:00:00 2001 From: Pravin Date: Sun, 30 Oct 2022 16:20:08 +0530 Subject: [PATCH 2024/2908] Added Readme file to document the hashing algorithm. (#6743) * Added Readme file to document the hashing algorithm. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- hashes/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 hashes/README.md diff --git a/hashes/README.md b/hashes/README.md new file mode 100644 index 000000000000..6df9a2fb6360 --- /dev/null +++ b/hashes/README.md @@ -0,0 +1,17 @@ +# Hashes +Hashing is the process of mapping any amount of data to a specified size using an algorithm. This is known as a hash value (or, if you're feeling fancy, a hash code, hash sums, or even a hash digest). Hashing is a one-way function, whereas encryption is a two-way function. While it is functionally conceivable to reverse-hash stuff, the required computing power makes it impractical. Hashing is a one-way street. +Unlike encryption, which is intended to protect data in transit, hashing is intended to authenticate that a file or piece of data has not been altered—that it is authentic. In other words, it functions as a checksum. + +## Common hashing algorithms +### MD5 +This is one of the first algorithms that has gained widespread acceptance. MD5 is hashing algorithm made by Ray Rivest that is known to suffer vulnerabilities. It was created in 1992 as the successor to MD4. Currently MD6 is in the works, but as of 2009 Rivest had removed it from NIST consideration for SHA-3. + +### SHA +SHA stands for Security Hashing Algorithm and it’s probably best known as the hashing algorithm used in most SSL/TLS cipher suites. A cipher suite is a collection of ciphers and algorithms that are used for SSL/TLS connections. SHA handles the hashing aspects. SHA-1, as we mentioned earlier, is now deprecated. SHA-2 is now mandatory. SHA-2 is sometimes known has SHA-256, though variants with longer bit lengths are also available. + +### SHA256 +SHA 256 is a member of the SHA 2 algorithm family, under which SHA stands for Secure Hash Algorithm. It was a collaborative effort between both the NSA and NIST to implement a successor to the SHA 1 family, which was beginning to lose potency against brute force attacks. It was published in 2001. +The importance of the 256 in the name refers to the final hash digest value, i.e. the hash value will remain 256 bits regardless of the size of the plaintext/cleartext. Other algorithms in the SHA family are similar to SHA 256 in some ways. + +### Luhn +The Luhn algorithm, also renowned as the modulus 10 or mod 10 algorithm, is a straightforward checksum formula used to validate a wide range of identification numbers, including credit card numbers, IMEI numbers, and Canadian Social Insurance Numbers. A community of mathematicians developed the LUHN formula in the late 1960s. Companies offering credit cards quickly followed suit. Since the algorithm is in the public interest, anyone can use it. The algorithm is used by most credit cards and many government identification numbers as a simple method of differentiating valid figures from mistyped or otherwise incorrect numbers. It was created to guard against unintentional errors, not malicious attacks. \ No newline at end of file From b5d7f186f4c93e0a00635e9efabe33971b161fc6 Mon Sep 17 00:00:00 2001 From: Emmanuel Bauma Murairi <40155399+Emmastro@users.noreply.github.com> Date: Sun, 30 Oct 2022 14:52:50 +0400 Subject: [PATCH 2025/2908] Polynomial (#6745) * implement function to handle polynomial operations * edit documentation * fix type hint and linter errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix short variable name * fix spelling Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/polynomials/__init__.py | 0 .../single_indeterminate_operations.py | 188 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 maths/polynomials/__init__.py create mode 100644 maths/polynomials/single_indeterminate_operations.py diff --git a/maths/polynomials/__init__.py b/maths/polynomials/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/maths/polynomials/single_indeterminate_operations.py b/maths/polynomials/single_indeterminate_operations.py new file mode 100644 index 000000000000..8bafdb591793 --- /dev/null +++ b/maths/polynomials/single_indeterminate_operations.py @@ -0,0 +1,188 @@ +""" + +This module implements a single indeterminate polynomials class +with some basic operations + +Reference: https://en.wikipedia.org/wiki/Polynomial + +""" + +from __future__ import annotations + +from collections.abc import MutableSequence + + +class Polynomial: + def __init__(self, degree: int, coefficients: MutableSequence[float]) -> None: + """ + The coefficients should be in order of degree, from smallest to largest. + >>> p = Polynomial(2, [1, 2, 3]) + >>> p = Polynomial(2, [1, 2, 3, 4]) + Traceback (most recent call last): + ... + ValueError: The number of coefficients should be equal to the degree + 1. + + """ + if len(coefficients) != degree + 1: + raise ValueError( + "The number of coefficients should be equal to the degree + 1." + ) + + self.coefficients: list[float] = list(coefficients) + self.degree = degree + + def __add__(self, polynomial_2: Polynomial) -> Polynomial: + """ + Polynomial addition + >>> p = Polynomial(2, [1, 2, 3]) + >>> q = Polynomial(2, [1, 2, 3]) + >>> p + q + 6x^2 + 4x + 2 + """ + + if self.degree > polynomial_2.degree: + coefficients = self.coefficients[:] + for i in range(polynomial_2.degree + 1): + coefficients[i] += polynomial_2.coefficients[i] + return Polynomial(self.degree, coefficients) + else: + coefficients = polynomial_2.coefficients[:] + for i in range(self.degree + 1): + coefficients[i] += self.coefficients[i] + return Polynomial(polynomial_2.degree, coefficients) + + def __sub__(self, polynomial_2: Polynomial) -> Polynomial: + """ + Polynomial subtraction + >>> p = Polynomial(2, [1, 2, 4]) + >>> q = Polynomial(2, [1, 2, 3]) + >>> p - q + 1x^2 + """ + return self + polynomial_2 * Polynomial(0, [-1]) + + def __neg__(self) -> Polynomial: + """ + Polynomial negation + >>> p = Polynomial(2, [1, 2, 3]) + >>> -p + - 3x^2 - 2x - 1 + """ + return Polynomial(self.degree, [-c for c in self.coefficients]) + + def __mul__(self, polynomial_2: Polynomial) -> Polynomial: + """ + Polynomial multiplication + >>> p = Polynomial(2, [1, 2, 3]) + >>> q = Polynomial(2, [1, 2, 3]) + >>> p * q + 9x^4 + 12x^3 + 10x^2 + 4x + 1 + """ + coefficients: list[float] = [0] * (self.degree + polynomial_2.degree + 1) + for i in range(self.degree + 1): + for j in range(polynomial_2.degree + 1): + coefficients[i + j] += ( + self.coefficients[i] * polynomial_2.coefficients[j] + ) + + return Polynomial(self.degree + polynomial_2.degree, coefficients) + + def evaluate(self, substitution: int | float) -> int | float: + """ + Evaluates the polynomial at x. + >>> p = Polynomial(2, [1, 2, 3]) + >>> p.evaluate(2) + 17 + """ + result: int | float = 0 + for i in range(self.degree + 1): + result += self.coefficients[i] * (substitution**i) + return result + + def __str__(self) -> str: + """ + >>> p = Polynomial(2, [1, 2, 3]) + >>> print(p) + 3x^2 + 2x + 1 + """ + polynomial = "" + for i in range(self.degree, -1, -1): + if self.coefficients[i] == 0: + continue + elif self.coefficients[i] > 0: + if polynomial: + polynomial += " + " + else: + polynomial += " - " + + if i == 0: + polynomial += str(abs(self.coefficients[i])) + elif i == 1: + polynomial += str(abs(self.coefficients[i])) + "x" + else: + polynomial += str(abs(self.coefficients[i])) + "x^" + str(i) + + return polynomial + + def __repr__(self) -> str: + """ + >>> p = Polynomial(2, [1, 2, 3]) + >>> p + 3x^2 + 2x + 1 + """ + return self.__str__() + + def derivative(self) -> Polynomial: + """ + Returns the derivative of the polynomial. + >>> p = Polynomial(2, [1, 2, 3]) + >>> p.derivative() + 6x + 2 + """ + coefficients: list[float] = [0] * self.degree + for i in range(self.degree): + coefficients[i] = self.coefficients[i + 1] * (i + 1) + return Polynomial(self.degree - 1, coefficients) + + def integral(self, constant: int | float = 0) -> Polynomial: + """ + Returns the integral of the polynomial. + >>> p = Polynomial(2, [1, 2, 3]) + >>> p.integral() + 1.0x^3 + 1.0x^2 + 1.0x + """ + coefficients: list[float] = [0] * (self.degree + 2) + coefficients[0] = constant + for i in range(self.degree + 1): + coefficients[i + 1] = self.coefficients[i] / (i + 1) + return Polynomial(self.degree + 1, coefficients) + + def __eq__(self, polynomial_2: object) -> bool: + """ + Checks if two polynomials are equal. + >>> p = Polynomial(2, [1, 2, 3]) + >>> q = Polynomial(2, [1, 2, 3]) + >>> p == q + True + """ + if not isinstance(polynomial_2, Polynomial): + return False + + if self.degree != polynomial_2.degree: + return False + + for i in range(self.degree + 1): + if self.coefficients[i] != polynomial_2.coefficients[i]: + return False + + return True + + def __ne__(self, polynomial_2: object) -> bool: + """ + Checks if two polynomials are not equal. + >>> p = Polynomial(2, [1, 2, 3]) + >>> q = Polynomial(2, [1, 2, 3]) + >>> p != q + False + """ + return not self.__eq__(polynomial_2) From 9278d0c6cdaa30115dbfef510e31a805bd3027dd Mon Sep 17 00:00:00 2001 From: Dima I <79413560+DIvkov575@users.noreply.github.com> Date: Sun, 30 Oct 2022 06:54:23 -0400 Subject: [PATCH 2026/2908] Added archimedes principle (physics) (#7143) * Added archimedes principle (physics) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * reformated * reformatted archimedes principles Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- physics/archimedes_principle.py | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 physics/archimedes_principle.py diff --git a/physics/archimedes_principle.py b/physics/archimedes_principle.py new file mode 100644 index 000000000000..6ecfc65e7461 --- /dev/null +++ b/physics/archimedes_principle.py @@ -0,0 +1,49 @@ +""" +Calculates buoyant force on object submerged within static fluid. +Discovered by greek mathematician, Archimedes. The principle is named after him. + +Equation for calculating buoyant force: +Fb = ρ * V * g + +Source: +- https://en.wikipedia.org/wiki/Archimedes%27_principle +""" + + +# Acceleration Constant on Earth (unit m/s^2) +g = 9.80665 + + +def archimedes_principle( + fluid_density: float, volume: float, gravity: float = g +) -> float: + """ + Args: + fluid_density: density of fluid (kg/m^3) + volume: volume of object / liquid being displaced by object + gravity: Acceleration from gravity. Gravitational force on system, + Default is Earth Gravity + returns: + buoyant force on object in Newtons + + >>> archimedes_principle(fluid_density=997, volume=0.5, gravity=9.8) + 4885.3 + >>> archimedes_principle(fluid_density=997, volume=0.7) + 6844.061035 + """ + + if fluid_density <= 0: + raise ValueError("Impossible fluid density") + if volume < 0: + raise ValueError("Impossible Object volume") + if gravity <= 0: + raise ValueError("Impossible Gravity") + + return fluid_density * gravity * volume + + +if __name__ == "__main__": + import doctest + + # run doctest + doctest.testmod() From cafbbab125ebcdac4294f4cbda024b840d230b9a Mon Sep 17 00:00:00 2001 From: Lukas Esc <55601315+Luk-ESC@users.noreply.github.com> Date: Sun, 30 Oct 2022 11:56:54 +0100 Subject: [PATCH 2027/2908] shortened code using abs() and inplace ops (#7191) n = -n if n < 0 else n --> n = abs(n) n = n // 10 --> n //= 10 --- maths/sum_of_digits.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maths/sum_of_digits.py b/maths/sum_of_digits.py index 64da00d4634c..5ad5fe6c9877 100644 --- a/maths/sum_of_digits.py +++ b/maths/sum_of_digits.py @@ -14,11 +14,11 @@ def sum_of_digits(n: int) -> int: >>> sum_of_digits(0) 0 """ - n = -n if n < 0 else n + n = abs(n) res = 0 while n > 0: res += n % 10 - n = n // 10 + n //= 10 return res @@ -35,7 +35,7 @@ def sum_of_digits_recursion(n: int) -> int: >>> sum_of_digits_recursion(0) 0 """ - n = -n if n < 0 else n + n = abs(n) return n if n < 10 else n % 10 + sum_of_digits(n // 10) From ab9d8f3874ba550bea0103e0891160b8d9145208 Mon Sep 17 00:00:00 2001 From: Jeremias Moreira Gomes Date: Sun, 30 Oct 2022 08:09:23 -0300 Subject: [PATCH 2028/2908] Adding a Quine in Python. (#6807) * Adding a Quine in Python. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- other/quine.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 other/quine.py diff --git a/other/quine.py b/other/quine.py new file mode 100644 index 000000000000..01e03bbb02cb --- /dev/null +++ b/other/quine.py @@ -0,0 +1,10 @@ +#!/bin/python3 +""" +Quine: + +A quine is a computer program which takes no input and produces a copy of its +own source code as its only output (disregarding this docstring and the shebang). + +More info on: https://en.wikipedia.org/wiki/Quine_(computing) +""" +print((lambda quine: quine % quine)("print((lambda quine: quine %% quine)(%r))")) From 94b51f6a91def387b82369401a42710cae4ee4e0 Mon Sep 17 00:00:00 2001 From: sadiqebrahim <75269485+sadiqebrahim@users.noreply.github.com> Date: Sun, 30 Oct 2022 17:22:20 +0530 Subject: [PATCH 2029/2908] Added Builtin Voltage (#7850) * Added Builtin Voltage * Update builtin_voltage.py * Update electronics/builtin_voltage.py Co-authored-by: Caeden Perelli-Harris * Update electronics/builtin_voltage.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Create elf.py Co-authored-by: Caeden Perelli-Harris Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- electronics/builtin_voltage.py | 67 ++++++++++++++++++++++++++++++++++ hashes/elf.py | 14 +++---- 2 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 electronics/builtin_voltage.py diff --git a/electronics/builtin_voltage.py b/electronics/builtin_voltage.py new file mode 100644 index 000000000000..38fde4524d1a --- /dev/null +++ b/electronics/builtin_voltage.py @@ -0,0 +1,67 @@ +from math import log + +from scipy.constants import Boltzmann, physical_constants + +T = 300 # TEMPERATURE (unit = K) + + +def builtin_voltage( + donor_conc: float, # donor concentration + acceptor_conc: float, # acceptor concentration + intrinsic_conc: float, # intrinsic concentration +) -> float: + """ + This function can calculate the Builtin Voltage of a pn junction diode. + This is calculated from the given three values. + Examples - + >>> builtin_voltage(donor_conc=1e17, acceptor_conc=1e17, intrinsic_conc=1e10) + 0.833370010652644 + >>> builtin_voltage(donor_conc=0, acceptor_conc=1600, intrinsic_conc=200) + Traceback (most recent call last): + ... + ValueError: Donor concentration should be positive + >>> builtin_voltage(donor_conc=1000, acceptor_conc=0, intrinsic_conc=1200) + Traceback (most recent call last): + ... + ValueError: Acceptor concentration should be positive + >>> builtin_voltage(donor_conc=1000, acceptor_conc=1000, intrinsic_conc=0) + Traceback (most recent call last): + ... + ValueError: Intrinsic concentration should be positive + >>> builtin_voltage(donor_conc=1000, acceptor_conc=3000, intrinsic_conc=2000) + Traceback (most recent call last): + ... + ValueError: Donor concentration should be greater than intrinsic concentration + >>> builtin_voltage(donor_conc=3000, acceptor_conc=1000, intrinsic_conc=2000) + Traceback (most recent call last): + ... + ValueError: Acceptor concentration should be greater than intrinsic concentration + """ + + if donor_conc <= 0: + raise ValueError("Donor concentration should be positive") + elif acceptor_conc <= 0: + raise ValueError("Acceptor concentration should be positive") + elif intrinsic_conc <= 0: + raise ValueError("Intrinsic concentration should be positive") + elif donor_conc <= intrinsic_conc: + raise ValueError( + "Donor concentration should be greater than intrinsic concentration" + ) + elif acceptor_conc <= intrinsic_conc: + raise ValueError( + "Acceptor concentration should be greater than intrinsic concentration" + ) + else: + return ( + Boltzmann + * T + * log((donor_conc * acceptor_conc) / intrinsic_conc**2) + / physical_constants["electron volt"][0] + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/hashes/elf.py b/hashes/elf.py index 87fe339da44d..e4bfcec22c22 100644 --- a/hashes/elf.py +++ b/hashes/elf.py @@ -2,19 +2,17 @@ def elf_hash(data: str) -> int: """ Implementation of ElfHash Algorithm, a variant of PJW hash function. - Returns: - [int] -- [32 bit binary int] >>> elf_hash('lorem ipsum') 253956621 """ - hash = x = 0 + hash_ = x = 0 for letter in data: - hash = (hash << 4) + ord(letter) - x = hash & 0xF0000000 + hash_ = (hash_ << 4) + ord(letter) + x = hash_ & 0xF0000000 if x != 0: - hash ^= x >> 24 - hash &= ~x - return hash + hash_ ^= x >> 24 + hash_ &= ~x + return hash_ if __name__ == "__main__": From 69d04ff64468d5b2815c0f22190b741393496a9e Mon Sep 17 00:00:00 2001 From: Kushagra Makharia Date: Sun, 30 Oct 2022 18:12:59 +0530 Subject: [PATCH 2030/2908] Added mean absolute error in linear regression (#7003) * Added mean absolute error in linear regression * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Code feedback changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris * Apply suggestions from code review * Update linear_regression.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Caeden Perelli-Harris --- machine_learning/linear_regression.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 92ab91c01b95..75943ac9f2ad 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -17,9 +17,8 @@ def collect_dataset(): :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" + "https://raw.githubusercontent.com/yashLadha/The_Math_of_Intelligence/" + "master/Week1/ADRvsRating.csv" ) lines = response.text.splitlines() data = [] @@ -87,6 +86,16 @@ def run_linear_regression(data_x, data_y): return theta +def mean_absolute_error(predicted_y, original_y): + """Return sum of square error for error calculation + :param predicted_y : contains the output of prediction (result vector) + :param original_y : contains values of expected outcome + :return : mean absolute error computed from given feature's + """ + total = sum(abs(y - predicted_y[i]) for i, y in enumerate(original_y)) + return total / len(original_y) + + def main(): """Driver function""" data = collect_dataset() From 2c65597093efa80a572a6a739d8f13a8d3579c18 Mon Sep 17 00:00:00 2001 From: kumarsurajsk <104374726+kumarsurajsk@users.noreply.github.com> Date: Sun, 30 Oct 2022 18:22:37 +0530 Subject: [PATCH 2031/2908] addition_without_arithmetic (#6830) * Addition_without_arithmetic * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added_param * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added_param_in_first_sec * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * change_align * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update Addition_without_arithmetic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename Addition_without_arithmetic.py to addition_without_arithmetic.py * Update addition_without_arithmetic.py * Update addition_without_arithmetic.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/addition_without_arithmetic.py | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 maths/addition_without_arithmetic.py diff --git a/maths/addition_without_arithmetic.py b/maths/addition_without_arithmetic.py new file mode 100644 index 000000000000..409604e4c08a --- /dev/null +++ b/maths/addition_without_arithmetic.py @@ -0,0 +1,39 @@ +""" +Illustrate how to add the integer without arithmetic operation +Author: suraj Kumar +Time Complexity: 1 +https://en.wikipedia.org/wiki/Bitwise_operation +""" + + +def add(first: int, second: int) -> int: + """ + Implementation of addition of integer + + Examples: + >>> add(3, 5) + 8 + >>> add(13, 5) + 18 + >>> add(-7, 2) + -5 + >>> add(0, -7) + -7 + >>> add(-321, 0) + -321 + """ + while second != 0: + c = first & second + first ^= second + second = c << 1 + return first + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + first = int(input("Enter the first number: ").strip()) + second = int(input("Enter the second number: ").strip()) + print(f"{add(first, second) = }") From cf915e704285b1b40b6d0f180d60791204486fd3 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Sun, 30 Oct 2022 17:00:16 +0400 Subject: [PATCH 2032/2908] add Levinstein distance with Dynamic Programming: up -> down approach (#7171) * add Levinstein distance with Dynamic Programming: up -> down approach * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add type hint * fix flake8 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/min_distance_up_bottom.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update min_distance_up_bottom.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- dynamic_programming/min_distance_up_bottom.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 dynamic_programming/min_distance_up_bottom.py diff --git a/dynamic_programming/min_distance_up_bottom.py b/dynamic_programming/min_distance_up_bottom.py new file mode 100644 index 000000000000..49c361f24d45 --- /dev/null +++ b/dynamic_programming/min_distance_up_bottom.py @@ -0,0 +1,55 @@ +""" +Author : Alexander Pantyukhin +Date : October 14, 2022 +This is implementation Dynamic Programming up bottom approach +to find edit distance. +The aim is to demonstate up bottom approach for solving the task. +The implementation was tested on the +leetcode: https://leetcode.com/problems/edit-distance/ +""" + +""" +Levinstein distance +Dynamic Programming: up -> down. +""" + + +def min_distance_up_bottom(word1: str, word2: str) -> int: + """ + >>> min_distance_up_bottom("intention", "execution") + 5 + >>> min_distance_up_bottom("intention", "") + 9 + >>> min_distance_up_bottom("", "") + 0 + >>> min_distance_up_bottom("zooicoarchaeologist", "zoologist") + 10 + """ + + from functools import lru_cache + + len_word1 = len(word1) + len_word2 = len(word2) + + @lru_cache(maxsize=None) + def min_distance(index1: int, index2: int) -> int: + # if first word index is overflow - delete all from the second word + if index1 >= len_word1: + return len_word2 - index2 + # if second word index is overflow - delete all from the first word + if index2 >= len_word2: + return len_word1 - index1 + diff = int(word1[index1] != word2[index2]) # current letters not identical + return min( + 1 + min_distance(index1 + 1, index2), + 1 + min_distance(index1, index2 + 1), + diff + min_distance(index1 + 1, index2 + 1), + ) + + return min_distance(0, 0) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d1430aa36b0a15a9e018367db210061e7a76dec4 Mon Sep 17 00:00:00 2001 From: Wissam Fawaz <55150850+wissamfawaz@users.noreply.github.com> Date: Sun, 30 Oct 2022 15:14:22 +0200 Subject: [PATCH 2033/2908] Implemented a Pascal triangle generator (#7317) * Added a Pascal triangle implementation to the other folder * Added Pascal triangle implementation to the other folder. * Added Pascal triangle implementation to the other folder. * Added Pascal triangle implementation to the other folder. * Implemented a Pascal triangle generator. * Reversed Changes to DIRECTORY.md * Reversed changed to .md files * Update other/pascal_triangle.py Removed personal info Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> * Update pascal_triangle.py Expanded the description of the algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Printed output in triangular form * Update CONTRIBUTING.md Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- other/pascal_triangle.py | 96 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 other/pascal_triangle.py diff --git a/other/pascal_triangle.py b/other/pascal_triangle.py new file mode 100644 index 000000000000..5cc3cee8af56 --- /dev/null +++ b/other/pascal_triangle.py @@ -0,0 +1,96 @@ +""" +This implementation demonstrates how to generate the +elements of a Pascal's triangle. The element having +a row index of r and column index of c can be derived +as follows: +triangle[r][c] = triangle[r-1][c-1]+triangle[r-1][c] +What is Pascal's triangle? +- It is a triangular array containing binomial coefficients. +Refer to (https://en.wikipedia.org/wiki/Pascal%27s_triangle) +for more info about this triangle. +""" + + +def print_pascal_triangle(num_rows: int) -> None: + """ + Print Pascal's triangle for different number of rows + >>> print_pascal_triangle(5) + 1 + 1 1 + 1 2 1 + 1 3 3 1 + 1 4 6 4 1 + """ + triangle = generate_pascal_triangle(num_rows) + for row_idx in range(num_rows): + # Print left spaces + for _ in range(num_rows - row_idx - 1): + print(end=" ") + # Print row values + for col_idx in range(row_idx + 1): + if col_idx != row_idx: + print(triangle[row_idx][col_idx], end=" ") + else: + print(triangle[row_idx][col_idx], end="") + print() + + +def generate_pascal_triangle(num_rows: int) -> list[list[int]]: + """ + Create Pascal's triangle for different number of rows + >>> generate_pascal_triangle(1) + [[1]] + >>> generate_pascal_triangle(2) + [[1], [1, 1]] + >>> generate_pascal_triangle(3) + [[1], [1, 1], [1, 2, 1]] + >>> generate_pascal_triangle(4) + [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]] + >>> generate_pascal_triangle(5) + [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]] + """ + triangle: list[list[int]] = [] + for current_row_idx in range(num_rows): + current_row = populate_current_row(triangle, current_row_idx) + triangle.append(current_row) + return triangle + + +def populate_current_row(triangle: list[list[int]], current_row_idx: int) -> list[int]: + """ + >>> triangle = [[1]] + >>> populate_current_row(triangle, 1) + [1, 1] + """ + current_row = [-1] * (current_row_idx + 1) + # first and last elements of current row are equal to 1 + current_row[0], current_row[-1] = 1, 1 + for current_col_idx in range(1, current_row_idx): + calculate_current_element( + triangle, current_row, current_row_idx, current_col_idx + ) + return current_row + + +def calculate_current_element( + triangle: list[list[int]], + current_row: list[int], + current_row_idx: int, + current_col_idx: int, +) -> None: + """ + >>> triangle = [[1], [1, 1]] + >>> current_row = [1, -1, 1] + >>> calculate_current_element(triangle, current_row, 2, 1) + >>> current_row + [1, 2, 1] + """ + above_to_left_elt = triangle[current_row_idx - 1][current_col_idx - 1] + above_to_right_elt = triangle[current_row_idx - 1][current_col_idx] + current_row[current_col_idx] = above_to_left_elt + above_to_right_elt + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 47100b992aef2fd5a7ae001155e3d0411db99ec9 Mon Sep 17 00:00:00 2001 From: Agniv Ghosh <73717822+agnivg@users.noreply.github.com> Date: Sun, 30 Oct 2022 18:45:46 +0530 Subject: [PATCH 2034/2908] Added code for palindrome partitioning problem under dynamic programming (#7222) * Added code for palindrome partitioning problem under dynamic programming * Updated return type for function * Updated Line 24 according to suggestions * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris * Update palindrome_partitioning.py * Update palindrome_partitioning.py * is_palindromic Co-authored-by: Christian Clauss Co-authored-by: Caeden Perelli-Harris --- .../palindrome_partitioning.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 dynamic_programming/palindrome_partitioning.py diff --git a/dynamic_programming/palindrome_partitioning.py b/dynamic_programming/palindrome_partitioning.py new file mode 100644 index 000000000000..c1629440ef2e --- /dev/null +++ b/dynamic_programming/palindrome_partitioning.py @@ -0,0 +1,39 @@ +""" +Given a string s, partition s such that every substring of the +partition is a palindrome. +Find the minimum cuts needed for a palindrome partitioning of s. + +Time Complexity: O(n^2) +Space Complexity: O(n^2) +For other explanations refer to: https://www.youtube.com/watch?v=_H8V5hJUGd0 +""" + + +def find_minimum_partitions(string: str) -> int: + """ + Returns the minimum cuts needed for a palindrome partitioning of string + + >>> find_minimum_partitions("aab") + 1 + >>> find_minimum_partitions("aaa") + 0 + >>> find_minimum_partitions("ababbbabbababa") + 3 + """ + length = len(string) + cut = [0] * length + is_palindromic = [[False for i in range(length)] for j in range(length)] + for i, c in enumerate(string): + mincut = i + for j in range(i + 1): + if c == string[j] and (i - j < 2 or is_palindromic[j + 1][i - 1]): + is_palindromic[j][i] = True + mincut = min(mincut, 0 if j == 0 else (cut[j - 1] + 1)) + cut[i] = mincut + return cut[length - 1] + + +if __name__ == "__main__": + s = input("Enter the string: ").strip() + ans = find_minimum_partitions(s) + print(f"Minimum number of partitions required for the '{s}' is {ans}") From 11e6c6fcc485bf78e5d28c7cf311278a013685d5 Mon Sep 17 00:00:00 2001 From: Gautam Chaurasia <64725629+GautamChaurasia@users.noreply.github.com> Date: Sun, 30 Oct 2022 18:58:27 +0530 Subject: [PATCH 2035/2908] Added algorithm for finding index of rightmost set bit (#7234) * Added algorithm for finding index of rightmost set bit * applied suggested changes * applied suggested changes * Fixed failing Testcases * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../index_of_rightmost_set_bit.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 bit_manipulation/index_of_rightmost_set_bit.py diff --git a/bit_manipulation/index_of_rightmost_set_bit.py b/bit_manipulation/index_of_rightmost_set_bit.py new file mode 100644 index 000000000000..eb52ea4e63e3 --- /dev/null +++ b/bit_manipulation/index_of_rightmost_set_bit.py @@ -0,0 +1,43 @@ +# Reference: https://www.geeksforgeeks.org/position-of-rightmost-set-bit/ + + +def get_index_of_rightmost_set_bit(number: int) -> int: + """ + Take in a positive integer 'number'. + Returns the zero-based index of first set bit in that 'number' from right. + Returns -1, If no set bit found. + + >>> get_index_of_rightmost_set_bit(0) + -1 + >>> get_index_of_rightmost_set_bit(5) + 0 + >>> get_index_of_rightmost_set_bit(36) + 2 + >>> get_index_of_rightmost_set_bit(8) + 3 + >>> get_index_of_rightmost_set_bit(-18) + Traceback (most recent call last): + ... + ValueError: Input must be a non-negative integer + """ + + if number < 0 or not isinstance(number, int): + raise ValueError("Input must be a non-negative integer") + + intermediate = number & ~(number - 1) + index = 0 + while intermediate: + intermediate >>= 1 + index += 1 + return index - 1 + + +if __name__ == "__main__": + """ + Finding the index of rightmost set bit has some very peculiar use-cases, + especially in finding missing or/and repeating numbers in a list of + positive integers. + """ + import doctest + + doctest.testmod(verbose=True) From e12516debb977e0b3ec9b67d1ddc8770450ae8d1 Mon Sep 17 00:00:00 2001 From: Abhishek Chakraborty Date: Sun, 30 Oct 2022 14:11:05 -0700 Subject: [PATCH 2036/2908] Shear stress: typo + WIkipedia URL (#7896) --- physics/{sheer_stress.py => shear_stress.py} | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) rename physics/{sheer_stress.py => shear_stress.py} (70%) diff --git a/physics/sheer_stress.py b/physics/shear_stress.py similarity index 70% rename from physics/sheer_stress.py rename to physics/shear_stress.py index 74a2d36b1f45..129148943893 100644 --- a/physics/sheer_stress.py +++ b/physics/shear_stress.py @@ -1,23 +1,31 @@ from __future__ import annotations +""" +Shear stress is a component of stress that is coplanar to the material cross-section. +It arises due to a shear force, the component of the force vector parallel to the +material cross-section. -def sheer_stress( +https://en.wikipedia.org/wiki/Shear_stress +""" + + +def shear_stress( stress: float, tangential_force: float, area: float, ) -> tuple[str, float]: """ This function can calculate any one of the three - - 1. Sheer Stress + 1. Shear Stress 2. Tangential Force 3. Cross-sectional Area This is calculated from the other two provided values Examples - - >>> sheer_stress(stress=25, tangential_force=100, area=0) + >>> shear_stress(stress=25, tangential_force=100, area=0) ('area', 4.0) - >>> sheer_stress(stress=0, tangential_force=1600, area=200) + >>> shear_stress(stress=0, tangential_force=1600, area=200) ('stress', 8.0) - >>> sheer_stress(stress=1000, tangential_force=0, area=1200) + >>> shear_stress(stress=1000, tangential_force=0, area=1200) ('tangential_force', 1200000) """ if (stress, tangential_force, area).count(0) != 1: From c0168cd33f6670f7e32eaa04d77b6be70b3588d4 Mon Sep 17 00:00:00 2001 From: Gmuslow <54784260+Gmuslow@users.noreply.github.com> Date: Sun, 30 Oct 2022 16:33:13 -0500 Subject: [PATCH 2037/2908] Created equivalent_resistance under Electronics (#6782) * Create resistor_equivalence.py * Update resistor_equivalence.py * Update electronics/resistor_equivalence.py removed an unnecessary space Co-authored-by: Caeden * Update resistor_equivalence.py fixed the snake_case requirement * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update resistor_equivalence.py finalize the naming convention errors (hopefully) * Update resistor_equivalence.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: Caeden Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- electronics/resistor_equivalence.py | 58 +++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 electronics/resistor_equivalence.py diff --git a/electronics/resistor_equivalence.py b/electronics/resistor_equivalence.py new file mode 100644 index 000000000000..7142f838a065 --- /dev/null +++ b/electronics/resistor_equivalence.py @@ -0,0 +1,58 @@ +# https://byjus.com/equivalent-resistance-formula/ + +from __future__ import annotations + + +def resistor_parallel(resistors: list[float]) -> float: + """ + Req = 1/ (1/R1 + 1/R2 + ... + 1/Rn) + + >>> resistor_parallel([3.21389, 2, 3]) + 0.8737571620498019 + >>> resistor_parallel([3.21389, 2, -3]) + Traceback (most recent call last): + ... + ValueError: Resistor at index 2 has a negative or zero value! + >>> resistor_parallel([3.21389, 2, 0.000]) + Traceback (most recent call last): + ... + ValueError: Resistor at index 2 has a negative or zero value! + """ + + first_sum = 0.00 + index = 0 + for resistor in resistors: + if resistor <= 0: + raise ValueError(f"Resistor at index {index} has a negative or zero value!") + first_sum += 1 / float(resistor) + index += 1 + return 1 / first_sum + + +def resistor_series(resistors: list[float]) -> float: + """ + Req = R1 + R2 + ... + Rn + + Calculate the equivalent resistance for any number of resistors in parallel. + + >>> resistor_series([3.21389, 2, 3]) + 8.21389 + >>> resistor_series([3.21389, 2, -3]) + Traceback (most recent call last): + ... + ValueError: Resistor at index 2 has a negative value! + """ + sum_r = 0.00 + index = 0 + for resistor in resistors: + sum_r += resistor + if resistor < 0: + raise ValueError(f"Resistor at index {index} has a negative value!") + index += 1 + return sum_r + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f8958ebe20522f5b0d32f33fd78870185912a67a Mon Sep 17 00:00:00 2001 From: himanshit0304 <70479061+himanshit0304@users.noreply.github.com> Date: Mon, 31 Oct 2022 04:25:11 +0530 Subject: [PATCH 2038/2908] Add print_multiplication_table.py (#6607) * Add print_multiplication_table.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added return type description * Update print_multiplication_table.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/print_multiplication_table.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 maths/print_multiplication_table.py diff --git a/maths/print_multiplication_table.py b/maths/print_multiplication_table.py new file mode 100644 index 000000000000..dbe4a4be0ee8 --- /dev/null +++ b/maths/print_multiplication_table.py @@ -0,0 +1,26 @@ +def multiplication_table(number: int, number_of_terms: int) -> str: + """ + Prints the multiplication table of a given number till the given number of terms + + >>> print(multiplication_table(3, 5)) + 3 * 1 = 3 + 3 * 2 = 6 + 3 * 3 = 9 + 3 * 4 = 12 + 3 * 5 = 15 + + >>> print(multiplication_table(-4, 6)) + -4 * 1 = -4 + -4 * 2 = -8 + -4 * 3 = -12 + -4 * 4 = -16 + -4 * 5 = -20 + -4 * 6 = -24 + """ + return "\n".join( + f"{number} * {i} = {number * i}" for i in range(1, number_of_terms + 1) + ) + + +if __name__ == "__main__": + print(multiplication_table(number=5, number_of_terms=10)) From 39e5bc5980254582362ad02bb6616aaa58bfac8a Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 31 Oct 2022 01:13:21 -0400 Subject: [PATCH 2039/2908] Refactor bottom-up edit distance function to be class method (#7347) * Refactor bottom-up function to be class method * Add type hints * Update convolve function namespace * Remove depreciated np.float * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Renamed function for consistency * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> --- DIRECTORY.md | 15 ++- dynamic_programming/edit_distance.py | 134 +++++++++++++-------------- 2 files changed, 80 insertions(+), 69 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 38fd1d656488..be3a121c80bd 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -46,6 +46,7 @@ * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py) * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py) * [Highest Set Bit](bit_manipulation/highest_set_bit.py) + * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py) * [Is Even](bit_manipulation/is_even.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py) @@ -307,24 +308,28 @@ * [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py) * [Max Sub Array](dynamic_programming/max_sub_array.py) * [Max Sum Contiguous Subsequence](dynamic_programming/max_sum_contiguous_subsequence.py) + * [Min Distance Up Bottom](dynamic_programming/min_distance_up_bottom.py) * [Minimum Coin Change](dynamic_programming/minimum_coin_change.py) * [Minimum Cost Path](dynamic_programming/minimum_cost_path.py) * [Minimum Partition](dynamic_programming/minimum_partition.py) * [Minimum Squares To Represent A Number](dynamic_programming/minimum_squares_to_represent_a_number.py) * [Minimum Steps To One](dynamic_programming/minimum_steps_to_one.py) * [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py) + * [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py) * [Rod Cutting](dynamic_programming/rod_cutting.py) * [Subset Generation](dynamic_programming/subset_generation.py) * [Sum Of Subset](dynamic_programming/sum_of_subset.py) * [Viterbi](dynamic_programming/viterbi.py) ## Electronics + * [Builtin Voltage](electronics/builtin_voltage.py) * [Carrier Concentration](electronics/carrier_concentration.py) * [Coulombs Law](electronics/coulombs_law.py) * [Electric Conductivity](electronics/electric_conductivity.py) * [Electric Power](electronics/electric_power.py) * [Electrical Impedance](electronics/electrical_impedance.py) * [Ohms Law](electronics/ohms_law.py) + * [Resistor Equivalence](electronics/resistor_equivalence.py) * [Resonant Frequency](electronics/resonant_frequency.py) ## File Transfer @@ -426,6 +431,7 @@ * [Adler32](hashes/adler32.py) * [Chaos Machine](hashes/chaos_machine.py) * [Djb2](hashes/djb2.py) + * [Elf](hashes/elf.py) * [Enigma Machine](hashes/enigma_machine.py) * [Hamming Code](hashes/hamming_code.py) * [Luhn](hashes/luhn.py) @@ -491,6 +497,7 @@ * [Abs Max](maths/abs_max.py) * [Abs Min](maths/abs_min.py) * [Add](maths/add.py) + * [Addition Without Arithmetic](maths/addition_without_arithmetic.py) * [Aliquot Sum](maths/aliquot_sum.py) * [Allocation Number](maths/allocation_number.py) * [Arc Length](maths/arc_length.py) @@ -581,12 +588,15 @@ * [Points Are Collinear 3D](maths/points_are_collinear_3d.py) * [Pollard Rho](maths/pollard_rho.py) * [Polynomial Evaluation](maths/polynomial_evaluation.py) + * Polynomials + * [Single Indeterminate Operations](maths/polynomials/single_indeterminate_operations.py) * [Power Using Recursion](maths/power_using_recursion.py) * [Prime Check](maths/prime_check.py) * [Prime Factors](maths/prime_factors.py) * [Prime Numbers](maths/prime_numbers.py) * [Prime Sieve Eratosthenes](maths/prime_sieve_eratosthenes.py) * [Primelib](maths/primelib.py) + * [Print Multiplication Table](maths/print_multiplication_table.py) * [Proth Number](maths/proth_number.py) * [Pythagoras](maths/pythagoras.py) * [Qr Decomposition](maths/qr_decomposition.py) @@ -676,12 +686,15 @@ * [Magicdiamondpattern](other/magicdiamondpattern.py) * [Maximum Subarray](other/maximum_subarray.py) * [Nested Brackets](other/nested_brackets.py) + * [Pascal Triangle](other/pascal_triangle.py) * [Password Generator](other/password_generator.py) + * [Quine](other/quine.py) * [Scoring Algorithm](other/scoring_algorithm.py) * [Sdes](other/sdes.py) * [Tower Of Hanoi](other/tower_of_hanoi.py) ## Physics + * [Archimedes Principle](physics/archimedes_principle.py) * [Casimir Effect](physics/casimir_effect.py) * [Centripetal Force](physics/centripetal_force.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) @@ -694,7 +707,7 @@ * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) * [Potential Energy](physics/potential_energy.py) * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) - * [Sheer Stress](physics/sheer_stress.py) + * [Shear Stress](physics/shear_stress.py) ## Project Euler * Problem 001 diff --git a/dynamic_programming/edit_distance.py b/dynamic_programming/edit_distance.py index fe23431a7ea6..774aa047326e 100644 --- a/dynamic_programming/edit_distance.py +++ b/dynamic_programming/edit_distance.py @@ -19,74 +19,72 @@ class EditDistance: """ def __init__(self): - self.__prepare__() - - def __prepare__(self, n=0, m=0): - self.dp = [[-1 for y in range(0, m)] for x in range(0, n)] - - def __solve_dp(self, x, y): - if x == -1: - return y + 1 - elif y == -1: - return x + 1 - elif self.dp[x][y] > -1: - return self.dp[x][y] + self.word1 = "" + self.word2 = "" + self.dp = [] + + def __min_dist_top_down_dp(self, m: int, n: int) -> int: + if m == -1: + return n + 1 + elif n == -1: + return m + 1 + elif self.dp[m][n] > -1: + return self.dp[m][n] else: - if self.a[x] == self.b[y]: - self.dp[x][y] = self.__solve_dp(x - 1, y - 1) + if self.word1[m] == self.word2[n]: + self.dp[m][n] = self.__min_dist_top_down_dp(m - 1, n - 1) else: - self.dp[x][y] = 1 + min( - self.__solve_dp(x, y - 1), - self.__solve_dp(x - 1, y), - self.__solve_dp(x - 1, y - 1), - ) - - return self.dp[x][y] - - def solve(self, a, b): - if isinstance(a, bytes): - a = a.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.__solve_dp(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] + insert = self.__min_dist_top_down_dp(m, n - 1) + delete = self.__min_dist_top_down_dp(m - 1, n) + replace = self.__min_dist_top_down_dp(m - 1, n - 1) + self.dp[m][n] = 1 + min(insert, delete, replace) + + return self.dp[m][n] + + def min_dist_top_down(self, word1: str, word2: str) -> int: + """ + >>> EditDistance().min_dist_top_down("intention", "execution") + 5 + >>> EditDistance().min_dist_top_down("intention", "") + 9 + >>> EditDistance().min_dist_top_down("", "") + 0 + """ + self.word1 = word1 + self.word2 = word2 + self.dp = [[-1 for _ in range(len(word2))] for _ in range(len(word1))] + + return self.__min_dist_top_down_dp(len(word1) - 1, len(word2) - 1) + + def min_dist_bottom_up(self, word1: str, word2: str) -> int: + """ + >>> EditDistance().min_dist_bottom_up("intention", "execution") + 5 + >>> EditDistance().min_dist_bottom_up("intention", "") + 9 + >>> EditDistance().min_dist_bottom_up("", "") + 0 + """ + self.word1 = word1 + self.word2 = word2 + m = len(word1) + n = len(word2) + self.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 + self.dp[i][j] = j + elif j == 0: # second string is empty + self.dp[i][j] = i + elif word1[i - 1] == word2[j - 1]: # last characters are equal + self.dp[i][j] = self.dp[i - 1][j - 1] + else: + insert = self.dp[i][j - 1] + delete = self.dp[i - 1][j] + replace = self.dp[i - 1][j - 1] + self.dp[i][j] = 1 + min(insert, delete, replace) + return self.dp[m][n] if __name__ == "__main__": @@ -99,7 +97,7 @@ def min_distance_bottom_up(word1: str, word2: str) -> int: S2 = input("Enter the second string: ").strip() print() - print(f"The minimum Edit Distance is: {solver.solve(S1, S2)}") - print(f"The minimum Edit Distance is: {min_distance_bottom_up(S1, S2)}") + print(f"The minimum edit distance is: {solver.min_dist_top_down(S1, S2)}") + print(f"The minimum edit distance is: {solver.min_dist_bottom_up(S1, S2)}") print() print("*************** End of Testing Edit Distance DP Algorithm ***************") From 0fd1ccb13358feff2d6ea8dd62200cabe363ee8e Mon Sep 17 00:00:00 2001 From: Roberts Date: Mon, 31 Oct 2022 13:31:15 +0200 Subject: [PATCH 2040/2908] Adding inductive reactance calculation (#6625) * Adding inductive reactance calculation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * from math import pi * 0007957747154594767 * 36420441699332 * 2199114857512855 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- electronics/ind_reactance.py | 69 ++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 electronics/ind_reactance.py diff --git a/electronics/ind_reactance.py b/electronics/ind_reactance.py new file mode 100644 index 000000000000..3f77ef628203 --- /dev/null +++ b/electronics/ind_reactance.py @@ -0,0 +1,69 @@ +# https://en.wikipedia.org/wiki/Electrical_reactance#Inductive_reactance +from __future__ import annotations + +from math import pi + + +def ind_reactance( + inductance: float, frequency: float, reactance: float +) -> dict[str, float]: + """ + Calculate inductive reactance, frequency or inductance from two given electrical + properties then return name/value pair of the zero value in a Python dict. + + Parameters + ---------- + inductance : float with units in Henries + + frequency : float with units in Hertz + + reactance : float with units in Ohms + + >>> ind_reactance(-35e-6, 1e3, 0) + Traceback (most recent call last): + ... + ValueError: Inductance cannot be negative + + >>> ind_reactance(35e-6, -1e3, 0) + Traceback (most recent call last): + ... + ValueError: Frequency cannot be negative + + >>> ind_reactance(35e-6, 0, -1) + Traceback (most recent call last): + ... + ValueError: Inductive reactance cannot be negative + + >>> ind_reactance(0, 10e3, 50) + {'inductance': 0.0007957747154594767} + + >>> ind_reactance(35e-3, 0, 50) + {'frequency': 227.36420441699332} + + >>> ind_reactance(35e-6, 1e3, 0) + {'reactance': 0.2199114857512855} + + """ + + if (inductance, frequency, reactance).count(0) != 1: + raise ValueError("One and only one argument must be 0") + if inductance < 0: + raise ValueError("Inductance cannot be negative") + if frequency < 0: + raise ValueError("Frequency cannot be negative") + if reactance < 0: + raise ValueError("Inductive reactance cannot be negative") + if inductance == 0: + return {"inductance": reactance / (2 * pi * frequency)} + elif frequency == 0: + return {"frequency": reactance / (2 * pi * inductance)} + elif reactance == 0: + return {"reactance": 2 * pi * frequency * inductance} + else: + raise ValueError("Exactly one argument must be 0") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b2165a65fcf1a087236d2a1527b10b64a12f69e6 Mon Sep 17 00:00:00 2001 From: Alex de la Cruz <46356295+acrulopez@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:14:33 +0100 Subject: [PATCH 2041/2908] Added Radix Tree in data structures (#6616) * added radix tree to data structures * added doctests * solved flake8 * added type hints * added description for delete function * Update data_structures/trie/radix_tree.py * Update radix_tree.py * Update radix_tree.py * Update radix_tree.py Co-authored-by: Alex de la Cruz Co-authored-by: Christian Clauss --- data_structures/trie/radix_tree.py | 223 +++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 data_structures/trie/radix_tree.py diff --git a/data_structures/trie/radix_tree.py b/data_structures/trie/radix_tree.py new file mode 100644 index 000000000000..66890346ec2b --- /dev/null +++ b/data_structures/trie/radix_tree.py @@ -0,0 +1,223 @@ +""" +A Radix Tree is a data structure that represents a space-optimized +trie (prefix tree) in whicheach node that is the only child is merged +with its parent [https://en.wikipedia.org/wiki/Radix_tree] +""" + + +class RadixNode: + def __init__(self, prefix: str = "", is_leaf: bool = False) -> None: + # Mapping from the first character of the prefix of the node + self.nodes: dict[str, RadixNode] = {} + + # A node will be a leaf if the tree contains its word + self.is_leaf = is_leaf + + self.prefix = prefix + + def match(self, word: str) -> tuple[str, str, str]: + """Compute the common substring of the prefix of the node and a word + + Args: + word (str): word to compare + + Returns: + (str, str, str): common substring, remaining prefix, remaining word + + >>> RadixNode("myprefix").match("mystring") + ('my', 'prefix', 'string') + """ + x = 0 + for q, w in zip(self.prefix, word): + if q != w: + break + + x += 1 + + return self.prefix[:x], self.prefix[x:], word[x:] + + def insert_many(self, words: list[str]) -> None: + """Insert many words in the tree + + Args: + words (list[str]): list of words + + >>> RadixNode("myprefix").insert_many(["mystring", "hello"]) + """ + for word in words: + self.insert(word) + + def insert(self, word: str) -> None: + """Insert a word into the tree + + Args: + word (str): word to insert + + >>> RadixNode("myprefix").insert("mystring") + """ + # Case 1: If the word is the prefix of the node + # Solution: We set the current node as leaf + if self.prefix == word: + self.is_leaf = True + + # Case 2: The node has no edges that have a prefix to the word + # Solution: We create an edge from the current node to a new one + # containing the word + elif word[0] not in self.nodes: + self.nodes[word[0]] = RadixNode(prefix=word, is_leaf=True) + + else: + incoming_node = self.nodes[word[0]] + matching_string, remaining_prefix, remaining_word = incoming_node.match( + word + ) + + # Case 3: The node prefix is equal to the matching + # Solution: We insert remaining word on the next node + if remaining_prefix == "": + self.nodes[matching_string[0]].insert(remaining_word) + + # Case 4: The word is greater equal to the matching + # Solution: Create a node in between both nodes, change + # prefixes and add the new node for the remaining word + else: + incoming_node.prefix = remaining_prefix + + aux_node = self.nodes[matching_string[0]] + self.nodes[matching_string[0]] = RadixNode(matching_string, False) + self.nodes[matching_string[0]].nodes[remaining_prefix[0]] = aux_node + + if remaining_word == "": + self.nodes[matching_string[0]].is_leaf = True + else: + self.nodes[matching_string[0]].insert(remaining_word) + + def find(self, word: str) -> bool: + """Returns if the word is on the tree + + Args: + word (str): word to check + + Returns: + bool: True if the word appears on the tree + + >>> RadixNode("myprefix").find("mystring") + False + """ + incoming_node = self.nodes.get(word[0], None) + if not incoming_node: + return False + else: + matching_string, remaining_prefix, remaining_word = incoming_node.match( + word + ) + # If there is remaining prefix, the word can't be on the tree + if remaining_prefix != "": + return False + # This applies when the word and the prefix are equal + elif remaining_word == "": + return incoming_node.is_leaf + # We have word remaining so we check the next node + else: + return incoming_node.find(remaining_word) + + def delete(self, word: str) -> bool: + """Deletes a word from the tree if it exists + + Args: + word (str): word to be deleted + + Returns: + bool: True if the word was found and deleted. False if word is not found + + >>> RadixNode("myprefix").delete("mystring") + False + """ + incoming_node = self.nodes.get(word[0], None) + if not incoming_node: + return False + else: + matching_string, remaining_prefix, remaining_word = incoming_node.match( + word + ) + # If there is remaining prefix, the word can't be on the tree + if remaining_prefix != "": + return False + # We have word remaining so we check the next node + elif remaining_word != "": + return incoming_node.delete(remaining_word) + else: + # If it is not a leaf, we don't have to delete + if not incoming_node.is_leaf: + return False + else: + # We delete the nodes if no edges go from it + if len(incoming_node.nodes) == 0: + del self.nodes[word[0]] + # We merge the current node with its only child + if len(self.nodes) == 1 and not self.is_leaf: + merging_node = list(self.nodes.values())[0] + self.is_leaf = merging_node.is_leaf + self.prefix += merging_node.prefix + self.nodes = merging_node.nodes + # If there is more than 1 edge, we just mark it as non-leaf + elif len(incoming_node.nodes) > 1: + incoming_node.is_leaf = False + # If there is 1 edge, we merge it with its child + else: + merging_node = list(incoming_node.nodes.values())[0] + incoming_node.is_leaf = merging_node.is_leaf + incoming_node.prefix += merging_node.prefix + incoming_node.nodes = merging_node.nodes + + return True + + def print_tree(self, height: int = 0) -> None: + """Print the tree + + Args: + height (int, optional): Height of the printed node + """ + if self.prefix != "": + print("-" * height, self.prefix, " (leaf)" if self.is_leaf else "") + + for value in self.nodes.values(): + value.print_tree(height + 1) + + +def test_trie() -> bool: + words = "banana bananas bandana band apple all beast".split() + root = RadixNode() + root.insert_many(words) + + assert all(root.find(word) for word in words) + assert not root.find("bandanas") + assert not root.find("apps") + root.delete("all") + assert not root.find("all") + root.delete("banana") + assert not root.find("banana") + assert root.find("bananas") + + return True + + +def pytests() -> None: + assert test_trie() + + +def main() -> None: + """ + >>> pytests() + """ + root = RadixNode() + words = "banana bananas bandanas bandana band apple all beast".split() + root.insert_many(words) + + print("Words:", words) + print("Tree:") + root.print_tree() + + +if __name__ == "__main__": + main() From a31edd4477af958adb840dadd568c38eecc9567b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 31 Oct 2022 14:50:03 +0100 Subject: [PATCH 2042/2908] Test on Python 3.11 (#6591) * Test on Python 3.11 release candidate 2 * tensorflow; python<3.11 * tensorflow; python_version < 3.11 * tensorflow; python_version < "3.11" * sympy, tensorflow; python_version < "3.11" * sklearn; python_version < "3.11" * matplotlib, pandas, qiskit * statsmodels; python_version < "3.11" * Bring back Pandas * Problem deps are qiskit, statsmodels, and tensorflow * updating DIRECTORY.md * python-version: 3.11-dev --> 3.11 * updating DIRECTORY.md * Add pytest --ignore to pyproject.toml * Update build.yml * Update pyproject.toml * Update pyproject.toml * Python 3.11 Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 13 +++++++++++-- CONTRIBUTING.md | 2 +- DIRECTORY.md | 1 + requirements.txt | 6 +++--- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 159ce13b3fff..1069c68d215f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: 3.x + python-version: 3.11 - uses: actions/cache@v3 with: path: ~/.cache/pip @@ -22,6 +22,15 @@ jobs: python -m pip install --upgrade pip setuptools six wheel python -m pip install pytest-cov -r requirements.txt - name: Run tests - run: pytest --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . + # See: #6591 for re-enabling tests on Python v3.11 + run: pytest + --ignore=computer_vision/cnn_classification.py + --ignore=machine_learning/forecasting/run.py + --ignore=machine_learning/lstm/lstm_prediction.py + --ignore=quantum/ + --ignore=project_euler/ + --ignore=scripts/validate_solutions.py + --cov-report=term-missing:skip-covered + --cov=. . - if: ${{ success() }} run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 37e020b8fd8a..3ce5bd1edf68 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ pre-commit run --all-files --show-diff-on-failure We want your work to be readable by others; therefore, we encourage you to note the following: -- Please write in Python 3.10+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. +- Please write in Python 3.11+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. - Please focus hard on the 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. diff --git a/DIRECTORY.md b/DIRECTORY.md index be3a121c80bd..0b0d1e6a7c9d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -328,6 +328,7 @@ * [Electric Conductivity](electronics/electric_conductivity.py) * [Electric Power](electronics/electric_power.py) * [Electrical Impedance](electronics/electrical_impedance.py) + * [Ind Reactance](electronics/ind_reactance.py) * [Ohms Law](electronics/ohms_law.py) * [Resistor Equivalence](electronics/resistor_equivalence.py) * [Resonant Frequency](electronics/resonant_frequency.py) diff --git a/requirements.txt b/requirements.txt index 9ffe784c945d..ae62039988e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,14 +8,14 @@ opencv-python pandas pillow projectq -qiskit +qiskit; python_version < "3.11" requests rich scikit-fuzzy sklearn -statsmodels +statsmodels; python_version < "3.11" sympy -tensorflow +tensorflow; python_version < "3.11" texttable tweepy xgboost From fecbf59436702b34b987773aa872d79f5df466df Mon Sep 17 00:00:00 2001 From: TechFreak107 <62158210+TechFreak107@users.noreply.github.com> Date: Mon, 31 Oct 2022 22:28:42 +0530 Subject: [PATCH 2043/2908] Modified 'pascal_triangle.py' program (#7901) * Added pascals_triangle.py program to maths directory * Deleted 'pascals_triangle.py' because of duplication. Added a optimized function to generate pascal's triangle to 'pascal_triangle.py' program. Added some aadditional doctests to the existing function. Added some type check functionality to the existing function. * Modified type check hints in 'generate_pascal_triangle_optimized' function' q * Modified 'pascal_triangle' prgram * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pascal_triangle.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- other/pascal_triangle.py | 109 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/other/pascal_triangle.py b/other/pascal_triangle.py index 5cc3cee8af56..7f6555f9c8b9 100644 --- a/other/pascal_triangle.py +++ b/other/pascal_triangle.py @@ -1,13 +1,10 @@ """ -This implementation demonstrates how to generate the -elements of a Pascal's triangle. The element having -a row index of r and column index of c can be derived -as follows: +This implementation demonstrates how to generate the elements of a Pascal's triangle. +The element havingva row index of r and column index of c can be derivedvas follows: triangle[r][c] = triangle[r-1][c-1]+triangle[r-1][c] -What is Pascal's triangle? -- It is a triangular array containing binomial coefficients. -Refer to (https://en.wikipedia.org/wiki/Pascal%27s_triangle) -for more info about this triangle. + +A Pascal's triangle is a triangular array containing binomial coefficients. +https://en.wikipedia.org/wiki/Pascal%27s_triangle """ @@ -38,6 +35,8 @@ def print_pascal_triangle(num_rows: int) -> None: def generate_pascal_triangle(num_rows: int) -> list[list[int]]: """ Create Pascal's triangle for different number of rows + >>> generate_pascal_triangle(0) + [] >>> generate_pascal_triangle(1) [[1]] >>> generate_pascal_triangle(2) @@ -48,7 +47,26 @@ def generate_pascal_triangle(num_rows: int) -> list[list[int]]: [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]] >>> generate_pascal_triangle(5) [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]] + >>> generate_pascal_triangle(-5) + Traceback (most recent call last): + ... + ValueError: The input value of 'num_rows' should be greater than or equal to 0 + >>> generate_pascal_triangle(7.89) + Traceback (most recent call last): + ... + TypeError: The input value of 'num_rows' should be 'int' """ + + if not isinstance(num_rows, int): + raise TypeError("The input value of 'num_rows' should be 'int'") + + if num_rows == 0: + return [] + elif num_rows < 0: + raise ValueError( + "The input value of 'num_rows' should be greater than or equal to 0" + ) + triangle: list[list[int]] = [] for current_row_idx in range(num_rows): current_row = populate_current_row(triangle, current_row_idx) @@ -90,7 +108,82 @@ def calculate_current_element( current_row[current_col_idx] = above_to_left_elt + above_to_right_elt +def generate_pascal_triangle_optimized(num_rows: int) -> list[list[int]]: + """ + This function returns a matrix representing the corresponding pascal's triangle + according to the given input of number of rows of Pascal's triangle to be generated. + It reduces the operations done to generate a row by half + by eliminating redundant calculations. + + :param num_rows: Integer specifying the number of rows in the Pascal's triangle + :return: 2-D List (matrix) representing the Pascal's triangle + + Return the Pascal's triangle of given rows + >>> generate_pascal_triangle_optimized(3) + [[1], [1, 1], [1, 2, 1]] + >>> generate_pascal_triangle_optimized(1) + [[1]] + >>> generate_pascal_triangle_optimized(0) + [] + >>> generate_pascal_triangle_optimized(-5) + Traceback (most recent call last): + ... + ValueError: The input value of 'num_rows' should be greater than or equal to 0 + >>> generate_pascal_triangle_optimized(7.89) + Traceback (most recent call last): + ... + TypeError: The input value of 'num_rows' should be 'int' + """ + + if not isinstance(num_rows, int): + raise TypeError("The input value of 'num_rows' should be 'int'") + + if num_rows == 0: + return [] + elif num_rows < 0: + raise ValueError( + "The input value of 'num_rows' should be greater than or equal to 0" + ) + + result: list[list[int]] = [[1]] + + for row_index in range(1, num_rows): + temp_row = [0] + result[-1] + [0] + row_length = row_index + 1 + # Calculate the number of distinct elements in a row + distinct_elements = sum(divmod(row_length, 2)) + row_first_half = [ + temp_row[i - 1] + temp_row[i] for i in range(1, distinct_elements + 1) + ] + row_second_half = row_first_half[: (row_index + 1) // 2] + row_second_half.reverse() + row = row_first_half + row_second_half + result.append(row) + + return result + + +def benchmark() -> None: + """ + Benchmark multiple functions, with three different length int values. + """ + from collections.abc import Callable + from timeit import timeit + + def benchmark_a_function(func: Callable, value: int) -> None: + call = f"{func.__name__}({value})" + timing = timeit(f"__main__.{call}", setup="import __main__") + # print(f"{call:38} = {func(value)} -- {timing:.4f} seconds") + print(f"{call:38} -- {timing:.4f} seconds") + + for value in range(15): # (1, 7, 14): + for func in (generate_pascal_triangle, generate_pascal_triangle_optimized): + benchmark_a_function(func, value) + print() + + if __name__ == "__main__": import doctest doctest.testmod() + benchmark() From 506b63f02da11691f19c4fd86c120e1d54842ea4 Mon Sep 17 00:00:00 2001 From: Shreyas Kamath <42207943+s18k@users.noreply.github.com> Date: Mon, 31 Oct 2022 22:34:42 +0530 Subject: [PATCH 2044/2908] Create convert_number_to_words.py (#6788) * Create convert_number_to_words.py A Python Program to convert numerical digits to English words. An Application of this can be in a Payment Application for confirmation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- web_programming/convert_number_to_words.py | 111 +++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 web_programming/convert_number_to_words.py diff --git a/web_programming/convert_number_to_words.py b/web_programming/convert_number_to_words.py new file mode 100644 index 000000000000..50612dec20dd --- /dev/null +++ b/web_programming/convert_number_to_words.py @@ -0,0 +1,111 @@ +import math + + +def convert(number: int) -> str: + """ + Given a number return the number in words. + + >>> convert(123) + 'OneHundred,TwentyThree' + """ + if number == 0: + words = "Zero" + return words + else: + digits = math.log10(number) + digits = digits + 1 + singles = {} + singles[0] = "" + singles[1] = "One" + singles[2] = "Two" + singles[3] = "Three" + singles[4] = "Four" + singles[5] = "Five" + singles[6] = "Six" + singles[7] = "Seven" + singles[8] = "Eight" + singles[9] = "Nine" + + doubles = {} + doubles[0] = "" + doubles[2] = "Twenty" + doubles[3] = "Thirty" + doubles[4] = "Forty" + doubles[5] = "Fifty" + doubles[6] = "Sixty" + doubles[7] = "Seventy" + doubles[8] = "Eighty" + doubles[9] = "Ninety" + + teens = {} + teens[0] = "Ten" + teens[1] = "Eleven" + teens[2] = "Twelve" + teens[3] = "Thirteen" + teens[4] = "Fourteen" + teens[5] = "Fifteen" + teens[6] = "Sixteen" + teens[7] = "Seventeen" + teens[8] = "Eighteen" + teens[9] = "Nineteen" + + placevalue = {} + placevalue[2] = "Hundred," + placevalue[3] = "Thousand," + placevalue[5] = "Lakh," + placevalue[7] = "Crore," + + temp_num = number + words = "" + counter = 0 + digits = int(digits) + while counter < digits: + current = temp_num % 10 + if counter % 2 == 0: + addition = "" + if counter in placevalue.keys() and current != 0: + addition = placevalue[counter] + if counter == 2: + words = singles[current] + addition + words + elif counter == 0: + if ((temp_num % 100) // 10) == 1: + words = teens[current] + addition + words + temp_num = temp_num // 10 + counter += 1 + else: + words = singles[current] + addition + words + + else: + words = doubles[current] + addition + words + + else: + if counter == 1: + if current == 1: + words = teens[number % 10] + words + else: + addition = "" + if counter in placevalue.keys(): + addition = placevalue[counter] + words = doubles[current] + addition + words + else: + addition = "" + if counter in placevalue.keys(): + if current == 0 and ((temp_num % 100) // 10) == 0: + addition = "" + else: + addition = placevalue[counter] + if ((temp_num % 100) // 10) == 1: + words = teens[current] + addition + words + temp_num = temp_num // 10 + counter += 1 + else: + words = singles[current] + addition + words + counter += 1 + temp_num = temp_num // 10 + return words + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ded5deabe9f9f1c8f2a57da8657056480f142b55 Mon Sep 17 00:00:00 2001 From: Shriyans Gandhi <41372639+shri30yans@users.noreply.github.com> Date: Mon, 31 Oct 2022 22:45:37 +0530 Subject: [PATCH 2045/2908] Dodecahedron surface area and volume (#6606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Hexagonal number sequence A hexagonal number sequence is a sequence of figurate numbers where the nth hexagonal number hₙ is the number of distinct dots in a pattern of dots consisting of the outlines of regular hexagons with sides up to n dots, when the hexagons are overlaid so that they share one vertex. This program returns the hexagonal number sequence of n length. * Update hexagonalnumbers.py * Update hexagonalnumbers.py * Update hexagonalnumbers.py * Update hexagonalnumbers.py * Update and rename hexagonalnumbers.py to hexagonal_numbers.py * Length must be a positive integer * Create dodecahedron.py * Update dodecahedron.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dodecahedron.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dodecahedron.py * Update dodecahedron.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dodecahedron.py * Update dodecahedron.py * Apply suggestions from code review Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * Update dodecahedron.py Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Paul <56065602+ZeroDayOwl@users.noreply.github.com> --- maths/dodecahedron.py | 73 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 maths/dodecahedron.py diff --git a/maths/dodecahedron.py b/maths/dodecahedron.py new file mode 100644 index 000000000000..856245f4a868 --- /dev/null +++ b/maths/dodecahedron.py @@ -0,0 +1,73 @@ +# dodecahedron.py + +""" +A regular dodecahedron is a three-dimensional figure made up of +12 pentagon faces having the same equal size. +""" + + +def dodecahedron_surface_area(edge: float) -> float: + """ + Calculates the surface area of a regular dodecahedron + a = 3 * ((25 + 10 * (5** (1 / 2))) ** (1 / 2 )) * (e**2) + where: + a --> is the area of the dodecahedron + e --> is the length of the edge + reference-->"Dodecahedron" Study.com + + + :param edge: length of the edge of the dodecahedron + :type edge: float + :return: the surface area of the dodecahedron as a float + + + Tests: + >>> dodecahedron_surface_area(5) + 516.1432201766901 + >>> dodecahedron_surface_area(10) + 2064.5728807067603 + >>> dodecahedron_surface_area(-1) + Traceback (most recent call last): + ... + ValueError: Length must be a positive. + """ + + if edge <= 0 or not isinstance(edge, int): + raise ValueError("Length must be a positive.") + return 3 * ((25 + 10 * (5 ** (1 / 2))) ** (1 / 2)) * (edge**2) + + +def dodecahedron_volume(edge: float) -> float: + """ + Calculates the volume of a regular dodecahedron + v = ((15 + (7 * (5** (1 / 2)))) / 4) * (e**3) + where: + v --> is the volume of the dodecahedron + e --> is the length of the edge + reference-->"Dodecahedron" Study.com + + + :param edge: length of the edge of the dodecahedron + :type edge: float + :return: the volume of the dodecahedron as a float + + Tests: + >>> dodecahedron_volume(5) + 957.8898700780791 + >>> dodecahedron_volume(10) + 7663.118960624633 + >>> dodecahedron_volume(-1) + Traceback (most recent call last): + ... + ValueError: Length must be a positive. + """ + + if edge <= 0 or not isinstance(edge, int): + raise ValueError("Length must be a positive.") + return ((15 + (7 * (5 ** (1 / 2)))) / 4) * (edge**3) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 21601a4070830069101bbb0ddc2d662eac68d627 Mon Sep 17 00:00:00 2001 From: Kevin Joven <59969678+KevinJoven11@users.noreply.github.com> Date: Mon, 31 Oct 2022 13:32:54 -0400 Subject: [PATCH 2046/2908] create quantum_fourier_transform (#6682) * create quantum_fourier_transform This is part of the #Hacktoberfest. I build the quantum fourier transform for N qubits. (n = 3 in the example) Best, Kevin * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update q_fourier_transform.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add the doctest! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update q_fourier_transform.py * Pass first then fail Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- quantum/q_fourier_transform.py | 97 ++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 quantum/q_fourier_transform.py diff --git a/quantum/q_fourier_transform.py b/quantum/q_fourier_transform.py new file mode 100644 index 000000000000..d138dfb452ee --- /dev/null +++ b/quantum/q_fourier_transform.py @@ -0,0 +1,97 @@ +""" +Build the quantum fourier transform (qft) for a desire +number of quantum bits using Qiskit framework. This +experiment run in IBM Q simulator with 10000 shots. +This circuit can be use as a building block to design +the Shor's algorithm in quantum computing. As well as, +quantum phase estimation among others. +. +References: +https://en.wikipedia.org/wiki/Quantum_Fourier_transform +https://qiskit.org/textbook/ch-algorithms/quantum-fourier-transform.html +""" + +import math + +import numpy as np +import qiskit +from qiskit import Aer, ClassicalRegister, QuantumCircuit, QuantumRegister, execute + + +def quantum_fourier_transform(number_of_qubits: int = 3) -> qiskit.result.counts.Counts: + """ + # >>> quantum_fourier_transform(2) + # {'00': 2500, '01': 2500, '11': 2500, '10': 2500} + # quantum circuit for number_of_qubits = 3: + ┌───┐ + qr_0: ──────■──────────────────────■───────┤ H ├─X─ + │ ┌───┐ │P(π/2) └───┘ │ + qr_1: ──────┼────────■───────┤ H ├─■─────────────┼─ + ┌───┐ │P(π/4) │P(π/2) └───┘ │ + qr_2: ┤ H ├─■────────■───────────────────────────X─ + └───┘ + cr: 3/═════════════════════════════════════════════ + Args: + n : number of qubits + Returns: + qiskit.result.counts.Counts: distribute counts. + + >>> quantum_fourier_transform(2) + {'00': 2500, '01': 2500, '10': 2500, '11': 2500} + >>> quantum_fourier_transform(-1) + Traceback (most recent call last): + ... + ValueError: number of qubits must be > 0. + >>> quantum_fourier_transform('a') + Traceback (most recent call last): + ... + TypeError: number of qubits must be a integer. + >>> quantum_fourier_transform(100) + Traceback (most recent call last): + ... + ValueError: number of qubits too large to simulate(>10). + >>> quantum_fourier_transform(0.5) + Traceback (most recent call last): + ... + ValueError: number of qubits must be exact integer. + """ + if type(number_of_qubits) == str: + raise TypeError("number of qubits must be a integer.") + if not number_of_qubits > 0: + raise ValueError("number of qubits must be > 0.") + if math.floor(number_of_qubits) != number_of_qubits: + raise ValueError("number of qubits must be exact integer.") + if number_of_qubits > 10: + raise ValueError("number of qubits too large to simulate(>10).") + + qr = QuantumRegister(number_of_qubits, "qr") + cr = ClassicalRegister(number_of_qubits, "cr") + + quantum_circuit = QuantumCircuit(qr, cr) + + counter = number_of_qubits + + for i in range(counter): + + quantum_circuit.h(number_of_qubits - i - 1) + counter -= 1 + for j in range(counter): + quantum_circuit.cp(np.pi / 2 ** (counter - j), j, counter) + + for k in range(number_of_qubits // 2): + quantum_circuit.swap(k, number_of_qubits - k - 1) + + # measure all the qubits + quantum_circuit.measure(qr, cr) + # simulate with 10000 shots + backend = Aer.get_backend("qasm_simulator") + job = execute(quantum_circuit, backend, shots=10000) + + return job.result().get_counts(quantum_circuit) + + +if __name__ == "__main__": + print( + f"Total count for quantum fourier transform state is: \ + {quantum_fourier_transform(3)}" + ) From 6cd7c49525b520fc5fe44ac0568fe39393ff85b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 21:33:08 +0100 Subject: [PATCH 2047/2908] [pre-commit.ci] pre-commit autoupdate (#7920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0) * updating DIRECTORY.md Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 004def5e4e8b..a0ea03b9b8cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.2.0 hooks: - id: pyupgrade args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 0b0d1e6a7c9d..5c4a032db6cd 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -238,6 +238,7 @@ * [Stack With Singly Linked List](data_structures/stacks/stack_with_singly_linked_list.py) * [Stock Span Problem](data_structures/stacks/stock_span_problem.py) * Trie + * [Radix Tree](data_structures/trie/radix_tree.py) * [Trie](data_structures/trie/trie.py) ## Digital Image Processing @@ -526,6 +527,7 @@ * [Collatz Sequence](maths/collatz_sequence.py) * [Combinations](maths/combinations.py) * [Decimal Isolate](maths/decimal_isolate.py) + * [Dodecahedron](maths/dodecahedron.py) * [Double Factorial Iterative](maths/double_factorial_iterative.py) * [Double Factorial Recursive](maths/double_factorial_recursive.py) * [Entropy](maths/entropy.py) @@ -994,6 +996,7 @@ * [Deutsch Jozsa](quantum/deutsch_jozsa.py) * [Half Adder](quantum/half_adder.py) * [Not Gate](quantum/not_gate.py) + * [Q Fourier Transform](quantum/q_fourier_transform.py) * [Q Full Adder](quantum/q_full_adder.py) * [Quantum Entanglement](quantum/quantum_entanglement.py) * [Quantum Teleportation](quantum/quantum_teleportation.py) @@ -1129,6 +1132,7 @@ ## Web Programming * [Co2 Emission](web_programming/co2_emission.py) + * [Convert Number To Words](web_programming/convert_number_to_words.py) * [Covid Stats Via Xpath](web_programming/covid_stats_via_xpath.py) * [Crawl Google Results](web_programming/crawl_google_results.py) * [Crawl Google Scholar Citation](web_programming/crawl_google_scholar_citation.py) From 6c15f526e58dbb3d3a67e613323781df39b58620 Mon Sep 17 00:00:00 2001 From: Paradact <44441385+Paradact@users.noreply.github.com> Date: Mon, 31 Oct 2022 22:50:50 +0100 Subject: [PATCH 2048/2908] Added Torus surface area (#7906) * Added Torus surface area * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed error in test Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/area.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/maths/area.py b/maths/area.py index 5db7dac38973..ea7216c8fe3f 100644 --- a/maths/area.py +++ b/maths/area.py @@ -201,6 +201,40 @@ def surface_area_cylinder(radius: float, height: float) -> float: return 2 * pi * radius * (height + radius) +def surface_area_torus(torus_radius: float, tube_radius: float) -> float: + """Calculate the Area of a Torus. + Wikipedia reference: https://en.wikipedia.org/wiki/Torus + :return 4pi^2 * torus_radius * tube_radius + >>> surface_area_torus(1, 1) + 39.47841760435743 + >>> surface_area_torus(4, 3) + 473.7410112522892 + >>> surface_area_torus(3, 4) + Traceback (most recent call last): + ... + ValueError: surface_area_torus() does not support spindle or self intersecting tori + >>> surface_area_torus(1.6, 1.6) + 101.06474906715503 + >>> surface_area_torus(0, 0) + 0.0 + >>> surface_area_torus(-1, 1) + Traceback (most recent call last): + ... + ValueError: surface_area_torus() only accepts non-negative values + >>> surface_area_torus(1, -1) + Traceback (most recent call last): + ... + ValueError: surface_area_torus() only accepts non-negative values + """ + if torus_radius < 0 or tube_radius < 0: + raise ValueError("surface_area_torus() only accepts non-negative values") + if torus_radius < tube_radius: + raise ValueError( + "surface_area_torus() does not support spindle or self intersecting tori" + ) + return 4 * pow(pi, 2) * torus_radius * tube_radius + + def area_rectangle(length: float, width: float) -> float: """ Calculate the area of a rectangle. @@ -543,6 +577,7 @@ def area_reg_polygon(sides: int, length: float) -> float: print(f"Cone: {surface_area_cone(10, 20) = }") print(f"Conical Frustum: {surface_area_conical_frustum(10, 20, 30) = }") print(f"Cylinder: {surface_area_cylinder(10, 20) = }") + print(f"Torus: {surface_area_torus(20, 10) = }") print(f"Equilateral Triangle: {area_reg_polygon(3, 10) = }") print(f"Square: {area_reg_polygon(4, 10) = }") print(f"Reqular Pentagon: {area_reg_polygon(5, 10) = }") From 7addbccee72d2b18e6d095ab6675cbcb290412ce Mon Sep 17 00:00:00 2001 From: Paradact <44441385+Paradact@users.noreply.github.com> Date: Mon, 31 Oct 2022 22:51:45 +0100 Subject: [PATCH 2049/2908] Torus volume (#7905) * Added Torus volume algorithm * Updated Torus volume for simplicity (removed ref to vol_sphere()) * Refactoring * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/volume.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/maths/volume.py b/maths/volume.py index da4054646659..1da4584c893e 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -441,6 +441,34 @@ def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> floa ) +def vol_torus(torus_radius: float, tube_radius: float) -> float: + """Calculate the Volume of a Torus. + Wikipedia reference: https://en.wikipedia.org/wiki/Torus + :return 2pi^2 * torus_radius * tube_radius^2 + >>> vol_torus(1, 1) + 19.739208802178716 + >>> vol_torus(4, 3) + 710.6115168784338 + >>> vol_torus(3, 4) + 947.4820225045784 + >>> vol_torus(1.6, 1.6) + 80.85179925372404 + >>> vol_torus(0, 0) + 0.0 + >>> vol_torus(-1, 1) + Traceback (most recent call last): + ... + ValueError: vol_torus() only accepts non-negative values + >>> vol_torus(1, -1) + Traceback (most recent call last): + ... + ValueError: vol_torus() only accepts non-negative values + """ + if torus_radius < 0 or tube_radius < 0: + raise ValueError("vol_torus() only accepts non-negative values") + return 2 * pow(pi, 2) * torus_radius * pow(tube_radius, 2) + + def main(): """Print the Results of Various Volume Calculations.""" print("Volumes:") @@ -453,6 +481,7 @@ def main(): print(f"Sphere: {vol_sphere(2) = }") # ~= 33.5 print(f"Hemisphere: {vol_hemisphere(2) = }") # ~= 16.75 print(f"Circular Cylinder: {vol_circular_cylinder(2, 2) = }") # ~= 25.1 + print(f"Torus: {vol_torus(2, 2) = }") # ~= 157.9 print(f"Conical Frustum: {vol_conical_frustum(2, 2, 4) = }") # ~= 58.6 print(f"Spherical cap: {vol_spherical_cap(1, 2) = }") # ~= 5.24 print(f"Spheres intersetion: {vol_spheres_intersect(2, 2, 1) = }") # ~= 21.21 From 74aa9efa1d164e7dba56a88b4b3546232f3c3024 Mon Sep 17 00:00:00 2001 From: Gustavobflh <43830003+Gustavobflh@users.noreply.github.com> Date: Mon, 31 Oct 2022 19:04:42 -0300 Subject: [PATCH 2050/2908] Added a Hubble Parameter calculator file (#7921) --- physics/hubble_parameter.py | 110 ++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 physics/hubble_parameter.py diff --git a/physics/hubble_parameter.py b/physics/hubble_parameter.py new file mode 100644 index 000000000000..7985647222c9 --- /dev/null +++ b/physics/hubble_parameter.py @@ -0,0 +1,110 @@ +""" +Title : Calculating the Hubble Parameter + +Description : The Hubble parameter H is the Universe expansion rate +in any time. In cosmology is customary to use the redshift redshift +in place of time, becausethe redshift is directily mensure +in the light of galaxies moving away from us. + +So, the general relation that we obtain is + +H = hubble_constant*(radiation_density*(redshift+1)**4 + + matter_density*(redshift+1)**3 + + curvature*(redshift+1)**2 + dark_energy)**(1/2) + +where radiation_density, matter_density, dark_energy are the relativity +(the percentage) energy densities that exist +in the Universe today. Here, matter_density is the +sum of the barion density and the +dark matter. Curvature is the curvature parameter and can be written in term +of the densities by the completeness + + +curvature = 1 - (matter_density + radiation_density + dark_energy) + +Source : +https://www.sciencedirect.com/topics/mathematics/hubble-parameter +""" + + +def hubble_parameter( + hubble_constant: float, + radiation_density: float, + matter_density: float, + dark_energy: float, + redshift: float, +) -> float: + + """ + Input Parameters + ---------------- + hubble_constant: Hubble constante is the expansion rate today usually + given in km/(s*Mpc) + + radiation_density: relative radiation density today + + matter_density: relative mass density today + + dark_energy: relative dark energy density today + + redshift: the light redshift + + Returns + ------- + result : Hubble parameter in and the unit km/s/Mpc (the unit can be + changed if you want, just need to change the unit of the Hubble constant) + + >>> hubble_parameter(hubble_constant=68.3, radiation_density=1e-4, + ... matter_density=-0.3, dark_energy=0.7, redshift=1) + Traceback (most recent call last): + ... + ValueError: All input parameters must be positive + + >>> hubble_parameter(hubble_constant=68.3, radiation_density=1e-4, + ... matter_density= 1.2, dark_energy=0.7, redshift=1) + Traceback (most recent call last): + ... + ValueError: Relative densities cannot be greater than one + + >>> hubble_parameter(hubble_constant=68.3, radiation_density=1e-4, + ... matter_density= 0.3, dark_energy=0.7, redshift=0) + 68.3 + """ + parameters = [redshift, radiation_density, matter_density, dark_energy] + if any(0 > p for p in parameters): + raise ValueError("All input parameters must be positive") + + if any(1 < p for p in parameters[1:4]): + raise ValueError("Relative densities cannot be greater than one") + else: + curvature = 1 - (matter_density + radiation_density + dark_energy) + + e_2 = ( + radiation_density * (redshift + 1) ** 4 + + matter_density * (redshift + 1) ** 3 + + curvature * (redshift + 1) ** 2 + + dark_energy + ) + + hubble = hubble_constant * e_2 ** (1 / 2) + return hubble + + +if __name__ == "__main__": + import doctest + + # run doctest + doctest.testmod() + + # demo LCDM approximation + matter_density = 0.3 + + print( + hubble_parameter( + hubble_constant=68.3, + radiation_density=1e-4, + matter_density=matter_density, + dark_energy=1 - matter_density, + redshift=0, + ) + ) From 7d139ee7f1e48648cc8cf176b293d23d2ba85d13 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Tue, 1 Nov 2022 06:50:43 +0000 Subject: [PATCH 2051/2908] refactor(abs): Condense `abs_min` and `abs_max` (#7881) * refactor(abs): Condense `abs_min` and `abs_max` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/abs.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ maths/abs_max.py | 50 ------------------------------------ maths/abs_min.py | 35 ------------------------- 3 files changed, 66 insertions(+), 85 deletions(-) delete mode 100644 maths/abs_max.py delete mode 100644 maths/abs_min.py diff --git a/maths/abs.py b/maths/abs.py index dfea52dfbb97..cb0ffc8a5b61 100644 --- a/maths/abs.py +++ b/maths/abs.py @@ -15,6 +15,62 @@ def abs_val(num: float) -> float: return -num if num < 0 else num +def abs_min(x: list[int]) -> int: + """ + >>> abs_min([0,5,1,11]) + 0 + >>> abs_min([3,-10,-2]) + -2 + >>> abs_min([]) + Traceback (most recent call last): + ... + ValueError: abs_min() arg is an empty sequence + """ + if len(x) == 0: + raise ValueError("abs_min() arg is an empty sequence") + j = x[0] + for i in x: + if abs_val(i) < abs_val(j): + j = i + return j + + +def abs_max(x: list[int]) -> int: + """ + >>> abs_max([0,5,1,11]) + 11 + >>> abs_max([3,-10,-2]) + -10 + >>> abs_max([]) + Traceback (most recent call last): + ... + ValueError: abs_max() arg is an empty sequence + """ + if len(x) == 0: + raise ValueError("abs_max() arg is an empty sequence") + j = x[0] + for i in x: + if abs(i) > abs(j): + j = i + return j + + +def abs_max_sort(x: list[int]) -> int: + """ + >>> abs_max_sort([0,5,1,11]) + 11 + >>> abs_max_sort([3,-10,-2]) + -10 + >>> abs_max_sort([]) + Traceback (most recent call last): + ... + ValueError: abs_max_sort() arg is an empty sequence + """ + if len(x) == 0: + raise ValueError("abs_max_sort() arg is an empty sequence") + return sorted(x, key=abs)[-1] + + def test_abs_val(): """ >>> test_abs_val() @@ -23,6 +79,16 @@ def test_abs_val(): assert 34 == abs_val(34) assert 100000000000 == abs_val(-100000000000) + a = [-3, -1, 2, -11] + assert abs_max(a) == -11 + assert abs_max_sort(a) == -11 + assert abs_min(a) == -1 + if __name__ == "__main__": + import doctest + + doctest.testmod() + + test_abs_val() print(abs_val(-34)) # --> 34 diff --git a/maths/abs_max.py b/maths/abs_max.py deleted file mode 100644 index 4a4b4d9ebca3..000000000000 --- a/maths/abs_max.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - - -def abs_max(x: list[int]) -> int: - """ - >>> abs_max([0,5,1,11]) - 11 - >>> abs_max([3,-10,-2]) - -10 - >>> abs_max([]) - Traceback (most recent call last): - ... - ValueError: abs_max() arg is an empty sequence - """ - if len(x) == 0: - raise ValueError("abs_max() arg is an empty sequence") - j = x[0] - for i in x: - if abs(i) > abs(j): - j = i - return j - - -def abs_max_sort(x: list[int]) -> int: - """ - >>> abs_max_sort([0,5,1,11]) - 11 - >>> abs_max_sort([3,-10,-2]) - -10 - >>> abs_max_sort([]) - Traceback (most recent call last): - ... - ValueError: abs_max_sort() arg is an empty sequence - """ - if len(x) == 0: - raise ValueError("abs_max_sort() arg is an empty sequence") - return sorted(x, key=abs)[-1] - - -def main(): - a = [1, 2, -11] - assert abs_max(a) == -11 - assert abs_max_sort(a) == -11 - - -if __name__ == "__main__": - import doctest - - doctest.testmod(verbose=True) - main() diff --git a/maths/abs_min.py b/maths/abs_min.py deleted file mode 100644 index 00dbcb025cfb..000000000000 --- a/maths/abs_min.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from .abs import abs_val - - -def abs_min(x: list[int]) -> int: - """ - >>> abs_min([0,5,1,11]) - 0 - >>> abs_min([3,-10,-2]) - -2 - >>> abs_min([]) - Traceback (most recent call last): - ... - ValueError: abs_min() arg is an empty sequence - """ - if len(x) == 0: - raise ValueError("abs_min() arg is an empty sequence") - j = x[0] - for i in x: - if abs_val(i) < abs_val(j): - j = i - return j - - -def main(): - a = [-3, -1, 2, -11] - print(abs_min(a)) # = -1 - - -if __name__ == "__main__": - import doctest - - doctest.testmod(verbose=True) - main() From d23e709aea75647540e6ba57b3a5979854e80117 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 1 Nov 2022 14:07:11 +0100 Subject: [PATCH 2052/2908] maths/sum_of_digits.py: Streamline benchmarks (#7914) * maths/sum_of_digits.py: Streamline benchmarks ``` sum_of_digits(262144): 19 -- 0.3128329170285724 seconds sum_of_digits_recursion(262144): 19 -- 0.34008108399575576 seconds sum_of_digits_compact(262144): 19 -- 0.6086010000435635 seconds sum_of_digits(1125899906842624): 76 -- 0.8079068749793805 seconds sum_of_digits_recursion(1125899906842624): 76 -- 0.8435653329943307 seconds sum_of_digits_compact(1125899906842624): 76 -- 1.247976207989268 seconds sum_of_digits(1267650600228229401496703205376): 115 -- 1.6441589999594726 seconds sum_of_digits_recursion(1267650600228229401496703205376): 115 -- 1.713684624992311 seconds sum_of_digits_compact(1267650600228229401496703205376): 115 -- 2.2197747920290567 seconds ``` * updating DIRECTORY.md * Update sum_of_digits.py * Update sum_of_digits.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- maths/sum_of_digits.py | 99 +++++------------------------------------- 1 file changed, 12 insertions(+), 87 deletions(-) diff --git a/maths/sum_of_digits.py b/maths/sum_of_digits.py index 5ad5fe6c9877..d5488bb9e9e0 100644 --- a/maths/sum_of_digits.py +++ b/maths/sum_of_digits.py @@ -1,10 +1,6 @@ -from timeit import timeit - - def sum_of_digits(n: int) -> int: """ Find the sum of digits of a number. - >>> sum_of_digits(12345) 15 >>> sum_of_digits(123) @@ -25,7 +21,6 @@ def sum_of_digits(n: int) -> int: 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) @@ -42,7 +37,6 @@ def sum_of_digits_recursion(n: int) -> int: 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) @@ -57,93 +51,24 @@ def sum_of_digits_compact(n: int) -> int: def benchmark() -> None: """ - Benchmark code for comparing 3 functions, - with 3 different length int values. + Benchmark multiple functions, with three 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", - ) + from collections.abc import Callable + from timeit import timeit - 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", - ) + def benchmark_a_function(func: Callable, value: int) -> None: + call = f"{func.__name__}({value})" + timing = timeit(f"__main__.{call}", setup="import __main__") + print(f"{call:56} = {func(value)} -- {timing:.4f} 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", - ) + for value in (262144, 1125899906842624, 1267650600228229401496703205376): + for func in (sum_of_digits, sum_of_digits_recursion, sum_of_digits_compact): + benchmark_a_function(func, value) + print() if __name__ == "__main__": - small_num = 262144 - medium_num = 1125899906842624 - large_num = 1267650600228229401496703205376 - benchmark() import doctest doctest.testmod() + benchmark() From 4e6c1c049dffdc984232fe1fce1e4791fc527d11 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Tue, 1 Nov 2022 21:43:03 +0400 Subject: [PATCH 2053/2908] Is power of two (#7936) * add is power of two * fix comment * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Deal with negative numbers * Spelling: negative Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- bit_manipulation/is_power_of_two.py | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 bit_manipulation/is_power_of_two.py diff --git a/bit_manipulation/is_power_of_two.py b/bit_manipulation/is_power_of_two.py new file mode 100644 index 000000000000..023e979fe51c --- /dev/null +++ b/bit_manipulation/is_power_of_two.py @@ -0,0 +1,57 @@ +""" +Author : Alexander Pantyukhin +Date : November 1, 2022 + +Task: +Given a positive int number. Return True if this number is power of 2 +or False otherwise. + +Implementation notes: Use bit manipulation. +For example if the number is the power of two it's bits representation: +n = 0..100..00 +n - 1 = 0..011..11 + +n & (n - 1) - no intersections = 0 +""" + + +def is_power_of_two(number: int) -> bool: + """ + Return True if this number is power of 2 or False otherwise. + + >>> is_power_of_two(0) + True + >>> is_power_of_two(1) + True + >>> is_power_of_two(2) + True + >>> is_power_of_two(4) + True + >>> is_power_of_two(6) + False + >>> is_power_of_two(8) + True + >>> is_power_of_two(17) + False + >>> is_power_of_two(-1) + Traceback (most recent call last): + ... + ValueError: number must not be negative + >>> is_power_of_two(1.2) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for &: 'float' and 'float' + + # Test all powers of 2 from 0 to 10,000 + >>> all(is_power_of_two(int(2 ** i)) for i in range(10000)) + True + """ + if number < 0: + raise ValueError("number must not be negative") + return number & (number - 1) == 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f512b4d105b6f3188deced19761b6ed288378f0d Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Tue, 1 Nov 2022 19:25:39 +0000 Subject: [PATCH 2054/2908] refactor: Move pascals triange to maths/ (#7932) * refactor: Move pascals triange to maths/ * Update xgboost_classifier.py * statsmodels is now compatible with Python 3.11 * statsmodels is now compatible with Python 3.11 * cython>=0.29.28 * cython>=0.29.28 # For statsmodels on Python 3.11 Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 1 - machine_learning/xgboost_classifier.py | 2 +- {other => matrix}/pascal_triangle.py | 0 requirements.txt | 3 ++- 4 files changed, 3 insertions(+), 3 deletions(-) rename {other => matrix}/pascal_triangle.py (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1069c68d215f..6b9cc890b6af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,6 @@ jobs: # See: #6591 for re-enabling tests on Python v3.11 run: pytest --ignore=computer_vision/cnn_classification.py - --ignore=machine_learning/forecasting/run.py --ignore=machine_learning/lstm/lstm_prediction.py --ignore=quantum/ --ignore=project_euler/ diff --git a/machine_learning/xgboost_classifier.py b/machine_learning/xgboost_classifier.py index 62a1b331baaf..08967f1715a1 100644 --- a/machine_learning/xgboost_classifier.py +++ b/machine_learning/xgboost_classifier.py @@ -23,7 +23,7 @@ def data_handling(data: dict) -> tuple: def xgboost(features: np.ndarray, target: np.ndarray) -> XGBClassifier: """ - >>> xgboost(np.array([[5.1, 3.6, 1.4, 0.2]]), np.array([0])) + # THIS TEST IS BROKEN!! >>> xgboost(np.array([[5.1, 3.6, 1.4, 0.2]]), np.array([0])) XGBClassifier(base_score=0.5, booster='gbtree', callbacks=None, colsample_bylevel=1, colsample_bynode=1, colsample_bytree=1, early_stopping_rounds=None, enable_categorical=False, diff --git a/other/pascal_triangle.py b/matrix/pascal_triangle.py similarity index 100% rename from other/pascal_triangle.py rename to matrix/pascal_triangle.py diff --git a/requirements.txt b/requirements.txt index ae62039988e6..2e278245541d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ beautifulsoup4 +cython>=0.29.28 # For statsmodels on Python 3.11 fake_useragent keras lxml @@ -13,7 +14,7 @@ requests rich scikit-fuzzy sklearn -statsmodels; python_version < "3.11" +statsmodels sympy tensorflow; python_version < "3.11" texttable From f05baa2b2b9aeb5a9ae8184ff418a5ccdc56960a Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Wed, 2 Nov 2022 16:25:19 +0400 Subject: [PATCH 2055/2908] add dp up - down minimum cost for tickets (#7934) * add dp up - down minimum cost for tickets * add typints * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add new tests and checks. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add more tests * add types for the dp function * Update dynamic_programming/minimum_tickets_cost.py Co-authored-by: Christian Clauss * fix review notes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * small fix * Update dynamic_programming/minimum_tickets_cost.py Co-authored-by: Christian Clauss * Update dynamic_programming/minimum_tickets_cost.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix tests * Update dynamic_programming/minimum_tickets_cost.py Co-authored-by: Christian Clauss * Update dynamic_programming/minimum_tickets_cost.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- dynamic_programming/minimum_tickets_cost.py | 129 ++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 dynamic_programming/minimum_tickets_cost.py diff --git a/dynamic_programming/minimum_tickets_cost.py b/dynamic_programming/minimum_tickets_cost.py new file mode 100644 index 000000000000..261a5a7cf42a --- /dev/null +++ b/dynamic_programming/minimum_tickets_cost.py @@ -0,0 +1,129 @@ +""" +Author : Alexander Pantyukhin +Date : November 1, 2022 + +Task: +Given a list of days when you need to travel. Each day is integer from 1 to 365. +You are able to use tickets for 1 day, 7 days and 30 days. +Each ticket has a cost. + +Find the minimum cost you need to travel every day in the given list of days. + +Implementation notes: +implementation Dynamic Programming up bottom approach. + +Runtime complexity: O(n) + +The implementation was tested on the +leetcode: https://leetcode.com/problems/minimum-cost-for-tickets/ + + +Minimum Cost For Tickets +Dynamic Programming: up -> down. +""" + +from functools import lru_cache + + +def mincost_tickets(days: list[int], costs: list[int]) -> int: + """ + >>> mincost_tickets([1, 4, 6, 7, 8, 20], [2, 7, 15]) + 11 + + >>> mincost_tickets([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 7, 15]) + 17 + + >>> mincost_tickets([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 90, 150]) + 24 + + >>> mincost_tickets([2], [2, 90, 150]) + 2 + + >>> mincost_tickets([], [2, 90, 150]) + 0 + + >>> mincost_tickets('hello', [2, 90, 150]) + Traceback (most recent call last): + ... + ValueError: The parameter days should be a list of integers + + >>> mincost_tickets([], 'world') + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + + >>> mincost_tickets([0.25, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 90, 150]) + Traceback (most recent call last): + ... + ValueError: The parameter days should be a list of integers + + >>> mincost_tickets([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 0.9, 150]) + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + + >>> mincost_tickets([-1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 90, 150]) + Traceback (most recent call last): + ... + ValueError: All days elements should be greater than 0 + + >>> mincost_tickets([2, 367], [2, 90, 150]) + Traceback (most recent call last): + ... + ValueError: All days elements should be less than 366 + + >>> mincost_tickets([2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], []) + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + + >>> mincost_tickets([], []) + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + + >>> mincost_tickets([2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [1, 2, 3, 4]) + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + """ + + # Validation + if not isinstance(days, list) or not all(isinstance(day, int) for day in days): + raise ValueError("The parameter days should be a list of integers") + + if len(costs) != 3 or not all(isinstance(cost, int) for cost in costs): + raise ValueError("The parameter costs should be a list of three integers") + + if len(days) == 0: + return 0 + + if min(days) <= 0: + raise ValueError("All days elements should be greater than 0") + + if max(days) >= 366: + raise ValueError("All days elements should be less than 366") + + days_set = set(days) + + @lru_cache(maxsize=None) + def dynamic_programming(index: int) -> int: + if index > 365: + return 0 + + if index not in days_set: + return dp(index + 1) + + return min( + costs[0] + dp(index + 1), + costs[1] + dp(index + 7), + costs[2] + dp(index + 30), + ) + + return dynamic_programming(1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 598f6a26a14d815f5fd079f43787995b0f076c03 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Wed, 2 Nov 2022 16:20:57 +0000 Subject: [PATCH 2056/2908] refactor: Condense `password` related files in one (#7939) * refactor: Condense `password` related files in one * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update other/password.py Co-authored-by: Christian Clauss * dynamic_programming * test: Make test input `str` * requirements.txt: Remove cython>=0.29.28 # For statsmodels on Python 3.11 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- dynamic_programming/minimum_tickets_cost.py | 8 ++-- other/check_strong_password.py | 47 -------------------- other/{password_generator.py => password.py} | 44 +++++++++++++++++- requirements.txt | 1 - 4 files changed, 46 insertions(+), 54 deletions(-) delete mode 100644 other/check_strong_password.py rename other/{password_generator.py => password.py} (58%) diff --git a/dynamic_programming/minimum_tickets_cost.py b/dynamic_programming/minimum_tickets_cost.py index 261a5a7cf42a..d07056d9217f 100644 --- a/dynamic_programming/minimum_tickets_cost.py +++ b/dynamic_programming/minimum_tickets_cost.py @@ -112,12 +112,12 @@ def dynamic_programming(index: int) -> int: return 0 if index not in days_set: - return dp(index + 1) + return dynamic_programming(index + 1) return min( - costs[0] + dp(index + 1), - costs[1] + dp(index + 7), - costs[2] + dp(index + 30), + costs[0] + dynamic_programming(index + 1), + costs[1] + dynamic_programming(index + 7), + costs[2] + dynamic_programming(index + 30), ) return dynamic_programming(1) diff --git a/other/check_strong_password.py b/other/check_strong_password.py deleted file mode 100644 index 95bb327addf4..000000000000 --- a/other/check_strong_password.py +++ /dev/null @@ -1,47 +0,0 @@ -# This Will Check Whether A Given Password Is Strong Or Not -# It Follows The Rule that Length Of Password Should Be At Least 8 Characters -# And At Least 1 Lower, 1 Upper, 1 Number And 1 Special Character - -from string import ascii_lowercase, ascii_uppercase, digits, punctuation - - -def strong_password_detector(password: str, min_length: int = 8) -> str: - """ - >>> strong_password_detector('Hwea7$2!') - 'This is a strong Password' - - >>> strong_password_detector('Sh0r1') - 'Your Password must be at least 8 characters long' - - >>> strong_password_detector('Hello123') - 'Password should contain UPPERCASE, lowercase, numbers, special characters' - - >>> strong_password_detector('Hello1238udfhiaf038fajdvjjf!jaiuFhkqi1') - 'This is a strong Password' - - >>> strong_password_detector(0) - 'Your Password must be at least 8 characters long' - """ - - if len(str(password)) < 8: - return "Your Password must be at least 8 characters long" - - upper = any(char in ascii_uppercase for char in password) - lower = any(char in ascii_lowercase for char in password) - num = any(char in digits for char in password) - spec_char = any(char in punctuation for char in password) - - if upper and lower and num and spec_char: - return "This is a strong Password" - - else: - return ( - "Password should contain UPPERCASE, lowercase, " - "numbers, special characters" - ) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/other/password_generator.py b/other/password.py similarity index 58% rename from other/password_generator.py rename to other/password.py index 8f9d58a33b82..8f6833073288 100644 --- a/other/password_generator.py +++ b/other/password.py @@ -1,11 +1,12 @@ -"""Password Generator allows you to generate a random password of length N.""" import secrets from random import shuffle -from string import ascii_letters, digits, punctuation +from string import ascii_letters, ascii_lowercase, ascii_uppercase, digits, punctuation def password_generator(length: int = 8) -> str: """ + Password Generator allows you to generate a random password of length N. + >>> len(password_generator()) 8 >>> len(password_generator(length=16)) @@ -62,6 +63,45 @@ def random_characters(chars_incl, i): pass # Put your code here... +# This Will Check Whether A Given Password Is Strong Or Not +# It Follows The Rule that Length Of Password Should Be At Least 8 Characters +# And At Least 1 Lower, 1 Upper, 1 Number And 1 Special Character +def strong_password_detector(password: str, min_length: int = 8) -> str: + """ + >>> strong_password_detector('Hwea7$2!') + 'This is a strong Password' + + >>> strong_password_detector('Sh0r1') + 'Your Password must be at least 8 characters long' + + >>> strong_password_detector('Hello123') + 'Password should contain UPPERCASE, lowercase, numbers, special characters' + + >>> strong_password_detector('Hello1238udfhiaf038fajdvjjf!jaiuFhkqi1') + 'This is a strong Password' + + >>> strong_password_detector('0') + 'Your Password must be at least 8 characters long' + """ + + if len(password) < min_length: + return "Your Password must be at least 8 characters long" + + upper = any(char in ascii_uppercase for char in password) + lower = any(char in ascii_lowercase for char in password) + num = any(char in digits for char in password) + spec_char = any(char in punctuation for char in password) + + if upper and lower and num and spec_char: + return "This is a strong Password" + + else: + return ( + "Password should contain UPPERCASE, lowercase, " + "numbers, special characters" + ) + + def main(): length = int(input("Please indicate the max length of your password: ").strip()) chars_incl = input( diff --git a/requirements.txt b/requirements.txt index 2e278245541d..00f31b85e404 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ beautifulsoup4 -cython>=0.29.28 # For statsmodels on Python 3.11 fake_useragent keras lxml From 45b3383c3952f646e985972d1fcd772d3d9f5d3f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 2 Nov 2022 19:20:45 +0100 Subject: [PATCH 2057/2908] Flake8: Drop ignore of issue A003 (#7949) * Flake8: Drop ignore of issue A003 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .flake8 | 4 +-- DIRECTORY.md | 10 +++--- data_structures/binary_tree/fenwick_tree.py | 8 ++--- data_structures/heap/heap.py | 7 ----- .../linked_list/merge_two_lists.py | 4 +-- data_structures/queue/double_ended_queue.py | 31 +++++++++---------- linear_algebra/src/lib.py | 12 ------- other/lfu_cache.py | 14 ++++----- other/lru_cache.py | 14 ++++----- 9 files changed, 42 insertions(+), 62 deletions(-) diff --git a/.flake8 b/.flake8 index 0d9ef18d142b..2bb36b71a271 100644 --- a/.flake8 +++ b/.flake8 @@ -1,8 +1,8 @@ [flake8] max-line-length = 88 -max-complexity = 25 +# max-complexity should be 10 +max-complexity = 23 extend-ignore = - A003 # Class attribute is shadowing a python builtin # Formatting style for `black` E203 # Whitespace before ':' W503 # Line break occurred before a binary operator diff --git a/DIRECTORY.md b/DIRECTORY.md index 5c4a032db6cd..a2112bcfb7b4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -48,6 +48,7 @@ * [Highest Set Bit](bit_manipulation/highest_set_bit.py) * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py) * [Is Even](bit_manipulation/is_even.py) + * [Is Power Of Two](bit_manipulation/is_power_of_two.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py) @@ -315,6 +316,7 @@ * [Minimum Partition](dynamic_programming/minimum_partition.py) * [Minimum Squares To Represent A Number](dynamic_programming/minimum_squares_to_represent_a_number.py) * [Minimum Steps To One](dynamic_programming/minimum_steps_to_one.py) + * [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py) * [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py) * [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py) * [Rod Cutting](dynamic_programming/rod_cutting.py) @@ -496,8 +498,6 @@ ## Maths * [3N Plus 1](maths/3n_plus_1.py) * [Abs](maths/abs.py) - * [Abs Max](maths/abs_max.py) - * [Abs Min](maths/abs_min.py) * [Add](maths/add.py) * [Addition Without Arithmetic](maths/addition_without_arithmetic.py) * [Aliquot Sum](maths/aliquot_sum.py) @@ -653,6 +653,7 @@ * [Matrix Operation](matrix/matrix_operation.py) * [Max Area Of Island](matrix/max_area_of_island.py) * [Nth Fibonacci Using Matrix Exponentiation](matrix/nth_fibonacci_using_matrix_exponentiation.py) + * [Pascal Triangle](matrix/pascal_triangle.py) * [Rotate Matrix](matrix/rotate_matrix.py) * [Searching In Sorted Matrix](matrix/searching_in_sorted_matrix.py) * [Sherman Morrison](matrix/sherman_morrison.py) @@ -674,7 +675,6 @@ ## Other * [Activity Selection](other/activity_selection.py) * [Alternative List Arrange](other/alternative_list_arrange.py) - * [Check Strong Password](other/check_strong_password.py) * [Davisb Putnamb Logemannb Loveland](other/davisb_putnamb_logemannb_loveland.py) * [Dijkstra Bankers Algorithm](other/dijkstra_bankers_algorithm.py) * [Doomsday](other/doomsday.py) @@ -689,8 +689,7 @@ * [Magicdiamondpattern](other/magicdiamondpattern.py) * [Maximum Subarray](other/maximum_subarray.py) * [Nested Brackets](other/nested_brackets.py) - * [Pascal Triangle](other/pascal_triangle.py) - * [Password Generator](other/password_generator.py) + * [Password](other/password.py) * [Quine](other/quine.py) * [Scoring Algorithm](other/scoring_algorithm.py) * [Sdes](other/sdes.py) @@ -701,6 +700,7 @@ * [Casimir Effect](physics/casimir_effect.py) * [Centripetal Force](physics/centripetal_force.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) + * [Hubble Parameter](physics/hubble_parameter.py) * [Ideal Gas Law](physics/ideal_gas_law.py) * [Kinetic Energy](physics/kinetic_energy.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) diff --git a/data_structures/binary_tree/fenwick_tree.py b/data_structures/binary_tree/fenwick_tree.py index 96020d1427af..babd75ac4b31 100644 --- a/data_structures/binary_tree/fenwick_tree.py +++ b/data_structures/binary_tree/fenwick_tree.py @@ -46,7 +46,7 @@ def init(self, arr: list[int]) -> None: self.size = len(arr) self.tree = deepcopy(arr) for i in range(1, self.size): - j = self.next(i) + j = self.next_(i) if j < self.size: self.tree[j] += self.tree[i] @@ -64,13 +64,13 @@ def get_array(self) -> list[int]: """ arr = self.tree[:] for i in range(self.size - 1, 0, -1): - j = self.next(i) + j = self.next_(i) if j < self.size: arr[j] -= arr[i] return arr @staticmethod - def next(index: int) -> int: + def next_(index: int) -> int: return index + (index & (-index)) @staticmethod @@ -102,7 +102,7 @@ def add(self, index: int, value: int) -> None: return while index < self.size: self.tree[index] += value - index = self.next(index) + index = self.next_(index) def update(self, index: int, value: int) -> None: """ diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 071790d18448..b14c55d9db4c 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -88,13 +88,6 @@ def build_max_heap(self, collection: Iterable[float]) -> None: 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: diff --git a/data_structures/linked_list/merge_two_lists.py b/data_structures/linked_list/merge_two_lists.py index 93cf7a7e1602..61e2412aa7fd 100644 --- a/data_structures/linked_list/merge_two_lists.py +++ b/data_structures/linked_list/merge_two_lists.py @@ -13,7 +13,7 @@ @dataclass class Node: data: int - next: Node | None + next_node: Node | None class SortedLinkedList: @@ -32,7 +32,7 @@ def __iter__(self) -> Iterator[int]: node = self.head while node: yield node.data - node = node.next + node = node.next_node def __len__(self) -> int: """ diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 7053879d4512..11942db8305c 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -42,8 +42,8 @@ class _Node: """ val: Any = None - next: Deque._Node | None = None - prev: Deque._Node | None = None + next_node: Deque._Node | None = None + prev_node: Deque._Node | None = None class _Iterator: """ @@ -81,7 +81,7 @@ def __next__(self) -> Any: # finished iterating raise StopIteration val = self._cur.val - self._cur = self._cur.next + self._cur = self._cur.next_node return val @@ -128,8 +128,8 @@ def append(self, val: Any) -> None: self._len = 1 else: # connect nodes - self._back.next = node - node.prev = self._back + self._back.next_node = node + node.prev_node = self._back self._back = node # assign new back to the new node self._len += 1 @@ -170,8 +170,8 @@ def appendleft(self, val: Any) -> None: self._len = 1 else: # connect nodes - node.next = self._front - self._front.prev = node + node.next_node = self._front + self._front.prev_node = node self._front = node # assign new front to the new node self._len += 1 @@ -264,10 +264,9 @@ def pop(self) -> Any: assert not self.is_empty(), "Deque is empty." topop = self._back - self._back = self._back.prev # set new back - self._back.next = ( - None # drop the last node - python will deallocate memory automatically - ) + self._back = self._back.prev_node # set new back + # drop the last node - python will deallocate memory automatically + self._back.next_node = None self._len -= 1 @@ -300,8 +299,8 @@ def popleft(self) -> Any: assert not self.is_empty(), "Deque is empty." topop = self._front - self._front = self._front.next # set new front and drop the first node - self._front.prev = None + self._front = self._front.next_node # set new front and drop the first node + self._front.prev_node = None self._len -= 1 @@ -385,8 +384,8 @@ def __eq__(self, other: object) -> bool: # compare every value if me.val != oth.val: return False - me = me.next - oth = oth.next + me = me.next_node + oth = oth.next_node return True @@ -424,7 +423,7 @@ def __repr__(self) -> str: while aux is not None: # append the values in a list to display values_list.append(aux.val) - aux = aux.next + aux = aux.next_node return "[" + ", ".join(repr(val) for val in values_list) + "]" diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 079731487b3a..775e0244abb2 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -40,7 +40,6 @@ class Vector: __sub__(other: Vector): vector subtraction __mul__(other: float): scalar multiplication __mul__(other: Vector): dot product - set(components: Collection[float]): changes the vector components copy(): copies this vector and returns it component(i): gets the i-th component (0-indexed) change_component(pos: int, value: float): changes specified component @@ -119,17 +118,6 @@ def __mul__(self, other: float | Vector) -> float | Vector: else: # error case raise Exception("invalid operand!") - def set(self, components: Collection[float]) -> None: - """ - input: new components - changes the components of the vector. - replaces the components with newer one. - """ - if len(components) > 0: - self.__components = list(components) - else: - raise Exception("please give any vector") - def copy(self) -> Vector: """ copies this vector and returns it. diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 2f26bb6cc74a..b68ba3a4605c 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -166,14 +166,14 @@ class LFUCache(Generic[T, U]): or as a function decorator. >>> cache = LFUCache(2) - >>> cache.set(1, 1) - >>> cache.set(2, 2) + >>> cache.put(1, 1) + >>> cache.put(2, 2) >>> cache.get(1) 1 - >>> cache.set(3, 3) + >>> cache.put(3, 3) >>> cache.get(2) is None True - >>> cache.set(4, 4) + >>> cache.put(4, 4) >>> cache.get(1) is None True >>> cache.get(3) @@ -224,7 +224,7 @@ def __contains__(self, key: T) -> bool: >>> 1 in cache False - >>> cache.set(1, 1) + >>> cache.put(1, 1) >>> 1 in cache True """ @@ -250,7 +250,7 @@ def get(self, key: T) -> U | None: self.miss += 1 return None - def set(self, key: T, value: U) -> None: + def put(self, key: T, value: U) -> None: """ Sets the value for the input key and updates the Double Linked List """ @@ -297,7 +297,7 @@ def cache_decorator_wrapper(*args: T) -> U: result = cls.decorator_function_to_instance_map[func].get(args[0]) if result is None: result = func(*args) - cls.decorator_function_to_instance_map[func].set(args[0], result) + cls.decorator_function_to_instance_map[func].put(args[0], result) return result def cache_info() -> LFUCache[T, U]: diff --git a/other/lru_cache.py b/other/lru_cache.py index aa910e487406..1e5eeac45b4e 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -150,8 +150,8 @@ class LRUCache(Generic[T, U]): >>> cache = LRUCache(2) - >>> cache.set(1, 1) - >>> cache.set(2, 2) + >>> cache.put(1, 1) + >>> cache.put(2, 2) >>> cache.get(1) 1 @@ -166,7 +166,7 @@ class LRUCache(Generic[T, U]): {1: Node: key: 1, val: 1, has next: True, has prev: True, \ 2: Node: key: 2, val: 2, has next: True, has prev: True} - >>> cache.set(3, 3) + >>> cache.put(3, 3) >>> cache.list DoubleLinkedList, @@ -182,7 +182,7 @@ class LRUCache(Generic[T, U]): >>> cache.get(2) is None True - >>> cache.set(4, 4) + >>> cache.put(4, 4) >>> cache.get(1) is None True @@ -238,7 +238,7 @@ def __contains__(self, key: T) -> bool: >>> 1 in cache False - >>> cache.set(1, 1) + >>> cache.put(1, 1) >>> 1 in cache True @@ -266,7 +266,7 @@ def get(self, key: T) -> U | None: self.miss += 1 return None - def set(self, key: T, value: U) -> None: + def put(self, key: T, value: U) -> None: """ Sets the value for the input key and updates the Double Linked List """ @@ -315,7 +315,7 @@ def cache_decorator_wrapper(*args: T) -> U: result = cls.decorator_function_to_instance_map[func].get(args[0]) if result is None: result = func(*args) - cls.decorator_function_to_instance_map[func].set(args[0], result) + cls.decorator_function_to_instance_map[func].put(args[0], result) return result def cache_info() -> LRUCache[T, U]: From db5215f60e31820dba5525e8b5fbf3e73b76b8df Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 2 Nov 2022 21:40:25 +0300 Subject: [PATCH 2058/2908] Reduce the complexity of linear_algebra/src/polynom_for_points.py (#7948) * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Lower the --max-complexity threshold in the file .flake8 * Reduce the complexity of linear_algebra/src/polynom_for_points.py * Update linear_algebra/src/polynom_for_points.py Co-authored-by: Christian Clauss * Update linear_algebra/src/polynom_for_points.py Co-authored-by: Christian Clauss * Fix Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .flake8 | 2 +- linear_algebra/src/polynom_for_points.py | 139 ++++++++++------------- 2 files changed, 62 insertions(+), 79 deletions(-) diff --git a/.flake8 b/.flake8 index 2bb36b71a271..1a62d57f9a57 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] max-line-length = 88 # max-complexity should be 10 -max-complexity = 23 +max-complexity = 21 extend-ignore = # Formatting style for `black` E203 # Whitespace before ':' diff --git a/linear_algebra/src/polynom_for_points.py b/linear_algebra/src/polynom_for_points.py index 091849542ffe..1d702deb1e99 100644 --- a/linear_algebra/src/polynom_for_points.py +++ b/linear_algebra/src/polynom_for_points.py @@ -24,96 +24,79 @@ def points_to_polynomial(coordinates: list[list[int]]) -> str: >>> 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) == 0 or not all(len(pair) == 2 for pair in coordinates): + return "The program cannot work out a fitting polynomial." + + if len({tuple(pair) for pair in coordinates}) != len(coordinates): + return "The program cannot work out a fitting polynomial." - if len(coordinates) == 1 and coordinates[0][0] == 0: - check = 2 - solved = "x=0" - except Exception: - check = 3 + set_x = {x for x, _ in coordinates} + if len(set_x) == 1: + return f"x={coordinates[0][0]}" + + if len(set_x) != len(coordinates): + return "The program cannot work out a fitting polynomial." x = len(coordinates) - if check == 1: - count_of_line = 0 - matrix: list[list[float]] = [] - # 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: list[float] = [] - 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 + matrix: list[list[float]] = [] + # 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: list[float] = [] + 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: list[float] = [] - while count_of_line < x: - vector.append(coordinates[count_of_line][1]) - count_of_line += 1 + count_of_line = 0 + # put the y values into a vector + vector: list[float] = [] + while count_of_line < x: + vector.append(coordinates[count_of_line][1]) + count_of_line += 1 - count = 0 + 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 + while count < x: + zahlen = 0 + while zahlen < x: + if count == zahlen: zahlen += 1 - count += 1 - - count = 0 - # make solutions - solution: list[str] = [] - while count < x: - solution.append(str(vector[count] / matrix[count][count])) - count += 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 - solved = "f(x)=" + count = 0 + # make solutions + solution: list[str] = [] + while count < x: + solution.append(str(vector[count] / matrix[count][count])) + count += 1 - while count < x: - remove_e: list[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 + count = 0 + solved = "f(x)=" - return solved + while count < x: + remove_e: list[str] = solution[count].split("E") + if len(remove_e) > 1: + solution[count] = f"{remove_e[0]}*10^{remove_e[1]}" + solved += f"x^{x - (count + 1)}*{solution[count]}" + if count + 1 != x: + solved += "+" + count += 1 - elif check == 2: - return solved - else: - return "The program cannot work out a fitting polynomial." + return solved if __name__ == "__main__": From a02de964d137b803aad9bb9c9d7096eff62539fd Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 3 Nov 2022 00:16:44 +0300 Subject: [PATCH 2059/2908] Reduce the complexity of graphs/minimum_spanning_tree_prims.py (#7952) * Lower the --max-complexity threshold in the file .flake8 * Add test * Reduce the complexity of graphs/minimum_spanning_tree_prims.py * Remove backslashes * Remove # noqa: E741 * Fix the flake8 E741 issues * Refactor * Fix --- .flake8 | 2 +- graphs/minimum_spanning_tree_prims.py | 127 +++++++++++++++----------- 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/.flake8 b/.flake8 index 1a62d57f9a57..834d1f63d13e 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] max-line-length = 88 # max-complexity should be 10 -max-complexity = 21 +max-complexity = 20 extend-ignore = # Formatting style for `black` E203 # Whitespace before ':' diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 5b2eaa4bff40..f577866f0da6 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -2,40 +2,45 @@ from collections import defaultdict -def prisms_algorithm(l): # noqa: E741 +class Heap: + def __init__(self): + self.node_position = [] - node_position = [] + def get_position(self, vertex): + return self.node_position[vertex] - def get_position(vertex): - return node_position[vertex] + def set_position(self, vertex, pos): + self.node_position[vertex] = pos - def set_position(vertex, pos): - node_position[vertex] = pos - - def top_to_bottom(heap, start, size, positions): + def top_to_bottom(self, heap, start, size, positions): if start > size // 2 - 1: return else: if 2 * start + 2 >= size: - m = 2 * start + 1 + smallest_child = 2 * start + 1 else: if heap[2 * start + 1] < heap[2 * start + 2]: - m = 2 * start + 1 + smallest_child = 2 * start + 1 else: - m = 2 * start + 2 - if heap[m] < heap[start]: - temp, temp1 = heap[m], positions[m] - heap[m], positions[m] = heap[start], positions[start] + smallest_child = 2 * start + 2 + if heap[smallest_child] < heap[start]: + temp, temp1 = heap[smallest_child], positions[smallest_child] + heap[smallest_child], positions[smallest_child] = ( + heap[start], + positions[start], + ) heap[start], positions[start] = temp, temp1 - temp = get_position(positions[m]) - set_position(positions[m], get_position(positions[start])) - set_position(positions[start], temp) + temp = self.get_position(positions[smallest_child]) + self.set_position( + positions[smallest_child], self.get_position(positions[start]) + ) + self.set_position(positions[start], temp) - top_to_bottom(heap, m, size, positions) + self.top_to_bottom(heap, smallest_child, size, positions) # Update function if value of any node in min-heap decreases - def bottom_to_top(val, index, heap, position): + def bottom_to_top(self, val, index, heap, position): temp = position[index] while index != 0: @@ -47,70 +52,88 @@ def bottom_to_top(val, index, heap, position): if val < heap[parent]: heap[index] = heap[parent] position[index] = position[parent] - set_position(position[parent], index) + self.set_position(position[parent], index) else: heap[index] = val position[index] = temp - set_position(temp, index) + self.set_position(temp, index) break index = parent else: heap[0] = val position[0] = temp - set_position(temp, 0) + self.set_position(temp, 0) - def heapify(heap, positions): + def heapify(self, heap, positions): start = len(heap) // 2 - 1 for i in range(start, -1, -1): - top_to_bottom(heap, i, len(heap), positions) + self.top_to_bottom(heap, i, len(heap), positions) - def delete_minimum(heap, positions): + def delete_minimum(self, heap, positions): temp = positions[0] heap[0] = sys.maxsize - top_to_bottom(heap, 0, len(heap), positions) + self.top_to_bottom(heap, 0, len(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 + +def prisms_algorithm(adjacency_list): + """ + >>> adjacency_list = {0: [[1, 1], [3, 3]], + ... 1: [[0, 1], [2, 6], [3, 5], [4, 1]], + ... 2: [[1, 6], [4, 5], [5, 2]], + ... 3: [[0, 3], [1, 5], [4, 1]], + ... 4: [[1, 1], [2, 5], [3, 1], [5, 4]], + ... 5: [[2, 2], [4, 4]]} + >>> prisms_algorithm(adjacency_list) + [(0, 1), (1, 4), (4, 3), (4, 5), (5, 2)] + """ + + heap = Heap() + + visited = [0] * len(adjacency_list) + nbr_tv = [-1] * len(adjacency_list) # 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 positions = [] - for x in range(len(l)): - p = sys.maxsize - distance_tv.append(p) - positions.append(x) - node_position.append(x) + for vertex in range(len(adjacency_list)): + distance_tv.append(sys.maxsize) + positions.append(vertex) + heap.node_position.append(vertex) tree_edges = [] visited[0] = 1 distance_tv[0] = sys.maxsize - for x in l[0]: - nbr_tv[x[0]] = 0 - distance_tv[x[0]] = x[1] - heapify(distance_tv, positions) + for neighbor, distance in adjacency_list[0]: + nbr_tv[neighbor] = 0 + distance_tv[neighbor] = distance + heap.heapify(distance_tv, positions) - for _ in range(1, len(l)): - vertex = delete_minimum(distance_tv, positions) + for _ in range(1, len(adjacency_list)): + vertex = heap.delete_minimum(distance_tv, positions) if visited[vertex] == 0: tree_edges.append((nbr_tv[vertex], vertex)) visited[vertex] = 1 - for v in l[vertex]: - 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 + for neighbor, distance in adjacency_list[vertex]: + if ( + visited[neighbor] == 0 + and distance < distance_tv[heap.get_position(neighbor)] + ): + distance_tv[heap.get_position(neighbor)] = distance + heap.bottom_to_top( + distance, heap.get_position(neighbor), distance_tv, positions + ) + nbr_tv[neighbor] = vertex return tree_edges if __name__ == "__main__": # pragma: no cover # < --------- 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()] # noqa: E741 - adjlist[l[0]].append([l[1], l[2]]) - adjlist[l[1]].append([l[0], l[2]]) - print(prisms_algorithm(adjlist)) + edges_number = int(input("Enter number of edges: ").strip()) + adjacency_list = defaultdict(list) + for _ in range(edges_number): + edge = [int(x) for x in input().strip().split()] + adjacency_list[edge[0]].append([edge[1], edge[2]]) + adjacency_list[edge[1]].append([edge[0], edge[2]]) + print(prisms_algorithm(adjacency_list)) From 978414bd50ae294352e0e4d93566f49074450857 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 3 Nov 2022 01:56:30 +0300 Subject: [PATCH 2060/2908] Reduce the complexity of other/graham_scan.py (#7953) * Reduce the complexity of other/graham_scan.py * Lower the --max-complexity threshold in the file .flake8 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix tests * Update other/graham_scan.py Co-authored-by: Christian Clauss * Update graham_scan.py * Update other/graham_scan.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .flake8 | 2 +- other/graham_scan.py | 150 ++++++++++++++++++++++--------------------- 2 files changed, 77 insertions(+), 75 deletions(-) diff --git a/.flake8 b/.flake8 index 834d1f63d13e..2f74f421d020 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] max-line-length = 88 # max-complexity should be 10 -max-complexity = 20 +max-complexity = 19 extend-ignore = # Formatting style for `black` E203 # Whitespace before ':' diff --git a/other/graham_scan.py b/other/graham_scan.py index 91bb6812fefc..8e83bfcf4c49 100644 --- a/other/graham_scan.py +++ b/other/graham_scan.py @@ -14,6 +14,82 @@ from sys import maxsize +# traversal from the lowest and the most left point in anti-clockwise direction +# if direction gets right, the previous point is not the convex hull. +class Direction(Enum): + left = 1 + straight = 2 + right = 3 + + def __repr__(self): + return f"{self.__class__.__name__}.{self.name}" + + +def angle_comparer(point: tuple[int, int], minx: int, miny: int) -> float: + """Return the angle toward to point from (minx, miny) + + :param point: The target point + minx: The starting point's x + miny: The starting point's y + :return: the angle + + Examples: + >>> angle_comparer((1,1), 0, 0) + 45.0 + + >>> angle_comparer((100,1), 10, 10) + -5.710593137499642 + + >>> angle_comparer((5,5), 2, 3) + 33.690067525979785 + """ + # sort the points accorgind to the angle from the lowest and the most left point + x, y = point + return degrees(atan2(y - miny, x - minx)) + + +def check_direction( + starting: tuple[int, int], via: tuple[int, int], target: tuple[int, int] +) -> Direction: + """Return the direction toward to the line from via to target from starting + + :param starting: The starting point + via: The via point + target: The target point + :return: the Direction + + Examples: + >>> check_direction((1,1), (2,2), (3,3)) + Direction.straight + + >>> check_direction((60,1), (-50,199), (30,2)) + Direction.left + + >>> check_direction((0,0), (5,5), (10,0)) + Direction.right + """ + x0, y0 = starting + x1, y1 = via + x2, y2 = target + via_angle = degrees(atan2(y1 - y0, x1 - x0)) + via_angle %= 360 + target_angle = degrees(atan2(y2 - y0, x2 - x0)) + target_angle %= 360 + # t- + # \ \ + # \ v + # \| + # s + # via_angle is always lower than target_angle, if direction is left. + # If they are same, it means they are on a same line of convex hull. + if target_angle > via_angle: + return Direction.left + elif target_angle == via_angle: + return Direction.straight + else: + return Direction.right + + def graham_scan(points: list[tuple[int, int]]) -> list[tuple[int, int]]: """Pure implementation of graham scan algorithm in Python @@ -57,86 +133,12 @@ def graham_scan(points: list[tuple[int, int]]) -> list[tuple[int, int]]: # remove the lowest and the most left point from points for preparing for sort points.pop(minidx) - def angle_comparer(point: tuple[int, int], minx: int, miny: int) -> float: - """Return the angle toward to point from (minx, miny) - - :param point: The target point - minx: The starting point's x - miny: The starting point's y - :return: the angle - - Examples: - >>> angle_comparer((1,1), 0, 0) - 45.0 - - >>> angle_comparer((100,1), 10, 10) - -5.710593137499642 - - >>> angle_comparer((5,5), 2, 3) - 33.690067525979785 - """ - # sort the points accorgind to the angle from the lowest and the most left point - x = point[0] - y = point[1] - angle = degrees(atan2(y - miny, x - minx)) - return angle - sorted_points = sorted(points, key=lambda point: angle_comparer(point, minx, miny)) # This insert actually costs complexity, # and you should instead add (minx, miny) into stack later. # I'm using insert just for easy understanding. sorted_points.insert(0, (minx, miny)) - # traversal from the lowest and the most left point in anti-clockwise direction - # if direction gets right, the previous point is not the convex hull. - class Direction(Enum): - left = 1 - straight = 2 - right = 3 - - def check_direction( - starting: tuple[int, int], via: tuple[int, int], target: tuple[int, int] - ) -> Direction: - """Return the direction toward to the line from via to target from starting - - :param starting: The starting point - via: The via point - target: The target point - :return: the Direction - - Examples: - >>> check_direction((1,1), (2,2), (3,3)) - Direction.straight - - >>> check_direction((60,1), (-50,199), (30,2)) - Direction.left - - >>> check_direction((0,0), (5,5), (10,0)) - Direction.right - """ - x0, y0 = starting - x1, y1 = via - x2, y2 = target - via_angle = degrees(atan2(y1 - y0, x1 - x0)) - if via_angle < 0: - via_angle += 360 - target_angle = degrees(atan2(y2 - y0, x2 - x0)) - if target_angle < 0: - target_angle += 360 - # t- - # \ \ - # \ v - # \| - # s - # via_angle is always lower than target_angle, if direction is left. - # If they are same, it means they are on a same line of convex hull. - if target_angle > via_angle: - return Direction.left - elif target_angle == via_angle: - return Direction.straight - else: - return Direction.right - stack: deque[tuple[int, int]] = deque() stack.append(sorted_points[0]) stack.append(sorted_points[1]) From 3e1cb70abf9997af3a4903f77cb3506a301de893 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Fri, 4 Nov 2022 00:03:37 +0400 Subject: [PATCH 2061/2908] add algorithm to check binary search tree (#7947) * add algorithm to check binary search tree * add tests * add leetcode link * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix typehints * typehints fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/binary_tree/is_bst.py Co-authored-by: Caeden Perelli-Harris * Update data_structures/binary_tree/is_bst.py Co-authored-by: Caeden Perelli-Harris * Update data_structures/binary_tree/is_bst.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix flake8 * fix typehint * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add TreeNode resolving * Update data_structures/binary_tree/is_bst.py Co-authored-by: Caeden Perelli-Harris * Update data_structures/binary_tree/is_bst.py Co-authored-by: Caeden Perelli-Harris * Update data_structures/binary_tree/is_bst.py Co-authored-by: Caeden Perelli-Harris * Update data_structures/binary_tree/is_bst.py Co-authored-by: Christian Clauss * change func name * Update data_structures/binary_tree/is_bst.py Co-authored-by: Christian Clauss * review notes fixes. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix flake8 * fix flake 8 * fix doctest * Update data_structures/binary_tree/is_bst.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris Co-authored-by: Christian Clauss --- data_structures/binary_tree/is_bst.py | 131 ++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 data_structures/binary_tree/is_bst.py diff --git a/data_structures/binary_tree/is_bst.py b/data_structures/binary_tree/is_bst.py new file mode 100644 index 000000000000..0b2ef8c9ffde --- /dev/null +++ b/data_structures/binary_tree/is_bst.py @@ -0,0 +1,131 @@ +""" +Author : Alexander Pantyukhin +Date : November 2, 2022 + +Task: +Given the root of a binary tree, determine if it is a valid binary search +tree (BST). + +A valid binary search tree is defined as follows: + +- The left subtree of a node contains only nodes with keys less than the node's key. +- The right subtree of a node contains only nodes with keys greater than the node's key. +- Both the left and right subtrees must also be binary search trees. + +Implementation notes: +Depth-first search approach. + +leetcode: https://leetcode.com/problems/validate-binary-search-tree/ + +Let n is the number of nodes in tree +Runtime: O(n) +Space: O(1) +""" + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class TreeNode: + data: float + left: TreeNode | None = None + right: TreeNode | None = None + + +def is_binary_search_tree(root: TreeNode | None) -> bool: + """ + >>> is_binary_search_tree(TreeNode(data=2, + ... left=TreeNode(data=1), + ... right=TreeNode(data=3)) + ... ) + True + + >>> is_binary_search_tree(TreeNode(data=0, + ... left=TreeNode(data=-11), + ... right=TreeNode(data=3)) + ... ) + True + + >>> is_binary_search_tree(TreeNode(data=5, + ... left=TreeNode(data=1), + ... right=TreeNode(data=4, left=TreeNode(data=3))) + ... ) + False + + >>> is_binary_search_tree(TreeNode(data='a', + ... left=TreeNode(data=1), + ... right=TreeNode(data=4, left=TreeNode(data=3))) + ... ) + Traceback (most recent call last): + ... + ValueError: Each node should be type of TreeNode and data should be float. + + >>> is_binary_search_tree(TreeNode(data=2, + ... left=TreeNode([]), + ... right=TreeNode(data=4, left=TreeNode(data=3))) + ... ) + Traceback (most recent call last): + ... + ValueError: Each node should be type of TreeNode and data should be float. + """ + + # Validation + def is_valid_tree(node: TreeNode | None) -> bool: + """ + >>> is_valid_tree(None) + True + >>> is_valid_tree('abc') + False + >>> is_valid_tree(TreeNode(data='not a float')) + False + >>> is_valid_tree(TreeNode(data=1, left=TreeNode('123'))) + False + """ + if node is None: + return True + + if not isinstance(node, TreeNode): + return False + + try: + float(node.data) + except (TypeError, ValueError): + return False + + return is_valid_tree(node.left) and is_valid_tree(node.right) + + if not is_valid_tree(root): + raise ValueError( + "Each node should be type of TreeNode and data should be float." + ) + + def is_binary_search_tree_recursive_check( + node: TreeNode | None, left_bound: float, right_bound: float + ) -> bool: + """ + >>> is_binary_search_tree_recursive_check(None) + True + >>> is_binary_search_tree_recursive_check(TreeNode(data=1), 10, 20) + False + """ + + if node is None: + return True + + return ( + left_bound < node.data < right_bound + and is_binary_search_tree_recursive_check(node.left, left_bound, node.data) + and is_binary_search_tree_recursive_check( + node.right, node.data, right_bound + ) + ) + + return is_binary_search_tree_recursive_check(root, -float("inf"), float("inf")) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 7f1a5521f4b73d15df409a81f3da48427f9c6cdc Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Fri, 4 Nov 2022 11:30:32 +0400 Subject: [PATCH 2062/2908] add prefix sum (#7959) * add prefix sum * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 2 + data_structures/arrays/prefix_sum.py | 78 ++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 data_structures/arrays/prefix_sum.py diff --git a/DIRECTORY.md b/DIRECTORY.md index a2112bcfb7b4..76c7f9dea4e3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -162,6 +162,7 @@ ## Data Structures * Arrays * [Permutations](data_structures/arrays/permutations.py) + * [Prefix Sum](data_structures/arrays/prefix_sum.py) * Binary Tree * [Avl Tree](data_structures/binary_tree/avl_tree.py) * [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py) @@ -174,6 +175,7 @@ * [Diff Views Of Binary Tree](data_structures/binary_tree/diff_views_of_binary_tree.py) * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) * [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py) + * [Is Bst](data_structures/binary_tree/is_bst.py) * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py) * [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py) diff --git a/data_structures/arrays/prefix_sum.py b/data_structures/arrays/prefix_sum.py new file mode 100644 index 000000000000..2243a5308937 --- /dev/null +++ b/data_structures/arrays/prefix_sum.py @@ -0,0 +1,78 @@ +""" +Author : Alexander Pantyukhin +Date : November 3, 2022 + +Implement the class of prefix sum with useful functions based on it. + +""" + + +class PrefixSum: + def __init__(self, array: list[int]) -> None: + len_array = len(array) + self.prefix_sum = [0] * len_array + + if len_array > 0: + self.prefix_sum[0] = array[0] + + for i in range(1, len_array): + self.prefix_sum[i] = self.prefix_sum[i - 1] + array[i] + + def get_sum(self, start: int, end: int) -> int: + """ + The function returns the sum of array from the start to the end indexes. + Runtime : O(1) + Space: O(1) + + >>> PrefixSum([1,2,3]).get_sum(0, 2) + 6 + >>> PrefixSum([1,2,3]).get_sum(1, 2) + 5 + >>> PrefixSum([1,2,3]).get_sum(2, 2) + 3 + >>> PrefixSum([1,2,3]).get_sum(2, 3) + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + if start == 0: + return self.prefix_sum[end] + + return self.prefix_sum[end] - self.prefix_sum[start - 1] + + def contains_sum(self, target_sum: int) -> bool: + """ + The function returns True if array contains the target_sum, + False otherwise. + + Runtime : O(n) + Space: O(n) + + >>> PrefixSum([1,2,3]).contains_sum(6) + True + >>> PrefixSum([1,2,3]).contains_sum(5) + True + >>> PrefixSum([1,2,3]).contains_sum(3) + True + >>> PrefixSum([1,2,3]).contains_sum(4) + False + >>> PrefixSum([1,2,3]).contains_sum(7) + False + >>> PrefixSum([1,-2,3]).contains_sum(2) + True + """ + + sums = {0} + for sum_item in self.prefix_sum: + if sum_item - target_sum in sums: + return True + + sums.add(sum_item) + + return False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 51708530b6a46a5e53d12e750521a11c6bf5c986 Mon Sep 17 00:00:00 2001 From: Sanders Lin Date: Sun, 6 Nov 2022 17:35:40 +0800 Subject: [PATCH 2063/2908] Update 3n_plus_1.py (#7966) * Update 3n_plus_1.py 1. Minor issue with ValueError message: Given integer should be positive, not greater than 1, as 1 is allowed. 2. += calls underlying list extend method which might be slower. Calling apend seems more appropriate. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/3n_plus_1.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maths/3n_plus_1.py b/maths/3n_plus_1.py index e455a158e619..59fdec48e100 100644 --- a/maths/3n_plus_1.py +++ b/maths/3n_plus_1.py @@ -11,15 +11,15 @@ def n31(a: int) -> tuple[list[int], int]: if not isinstance(a, int): raise TypeError(f"Must be int, not {type(a).__name__}") if a < 1: - raise ValueError(f"Given integer must be greater than 1, not {a}") + raise ValueError(f"Given integer must be positive, not {a}") path = [a] while a != 1: if a % 2 == 0: - a = a // 2 + a //= 2 else: a = 3 * a + 1 - path += [a] + path.append(a) return path, len(path) From daa1c7529ac6491338adb81622d5041a4ba1f446 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 6 Nov 2022 14:54:44 +0000 Subject: [PATCH 2064/2908] Raise error not string (#7945) * ci: Add `B023` to `.flake8` ignores * refactor: Return `bool`/raise Exception * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * revert: Remove previous branch commit * Update data_structures/binary_tree/segment_tree_other.py Co-authored-by: Christian Clauss * feat: Apply `__repr__` changes * chore: Fix failing tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/binary_tree/segment_tree_other.py Co-authored-by: Christian Clauss * test: Fix doctests * random.choice(population_score[:N_SELECTED])[0] * Update basic_string.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- boolean_algebra/quine_mc_cluskey.py | 11 +- ciphers/shuffled_shift_cipher.py | 2 +- computer_vision/harris_corner.py | 3 +- .../binary_tree/segment_tree_other.py | 121 +++++++++--------- data_structures/binary_tree/wavelet_tree.py | 6 +- .../linked_list/doubly_linked_list.py | 2 +- data_structures/queue/double_ended_queue.py | 2 +- graphs/breadth_first_search_shortest_path.py | 8 +- graphs/page_rank.py | 2 +- linear_algebra/src/polynom_for_points.py | 14 +- maths/monte_carlo_dice.py | 3 - matrix/cramers_rule_2x2.py | 16 ++- other/password.py | 38 +++--- strings/dna.py | 15 ++- 14 files changed, 123 insertions(+), 120 deletions(-) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 5bd7117bb3e7..6788dfb28ba1 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,15 +1,16 @@ from __future__ import annotations from collections.abc import Sequence +from typing import Literal -def compare_string(string1: str, string2: str) -> str: +def compare_string(string1: str, string2: str) -> str | Literal[False]: """ >>> compare_string('0010','0110') '0_10' >>> compare_string('0110','1101') - 'X' + False """ list1 = list(string1) list2 = list(string2) @@ -19,7 +20,7 @@ def compare_string(string1: str, string2: str) -> str: count += 1 list1[i] = "_" if count > 1: - return "X" + return False else: return "".join(list1) @@ -36,10 +37,10 @@ def check(binary: list[str]) -> list[str]: for i in range(len(binary)): for j in range(i + 1, len(binary)): k = compare_string(binary[i], binary[j]) - if k != "X": + if k is False: check1[i] = "*" check1[j] = "*" - temp.append(k) + temp.append("X") for i in range(len(binary)): if check1[i] == "$": pi.append(binary[i]) diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index 714acd4b1afc..08b2cab97c69 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -42,7 +42,7 @@ def __str__(self) -> str: """ :return: passcode of the cipher object """ - return "Passcode is: " + "".join(self.__passcode) + return "".join(self.__passcode) def __neg_pos(self, iterlist: list[int]) -> list[int]: """ diff --git a/computer_vision/harris_corner.py b/computer_vision/harris_corner.py index 7850085f8935..c8905bb6a9cd 100644 --- a/computer_vision/harris_corner.py +++ b/computer_vision/harris_corner.py @@ -22,8 +22,7 @@ def __init__(self, k: float, window_size: int): raise ValueError("invalid k value") def __str__(self) -> str: - - return f"Harris Corner detection with k : {self.k}" + return str(self.k) def detect(self, img_path: str) -> tuple[cv2.Mat, list[list[int]]]: diff --git a/data_structures/binary_tree/segment_tree_other.py b/data_structures/binary_tree/segment_tree_other.py index 90afd7ca8b71..cc77c4951f1a 100644 --- a/data_structures/binary_tree/segment_tree_other.py +++ b/data_structures/binary_tree/segment_tree_other.py @@ -16,40 +16,36 @@ def __init__(self, start, end, val, left=None, right=None): self.left = left self.right = right - def __str__(self): - return f"val: {self.val}, start: {self.start}, end: {self.end}" + def __repr__(self): + return f"SegmentTreeNode(start={self.start}, end={self.end}, val={self.val})" class SegmentTree: """ >>> 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 + >>> tuple(num_arr.traverse()) # doctest: +NORMALIZE_WHITESPACE + (SegmentTreeNode(start=0, end=4, val=15), + SegmentTreeNode(start=0, end=2, val=8), + SegmentTreeNode(start=3, end=4, val=7), + SegmentTreeNode(start=0, end=1, val=3), + SegmentTreeNode(start=2, end=2, val=5), + SegmentTreeNode(start=3, end=3, val=3), + SegmentTreeNode(start=4, end=4, val=4), + SegmentTreeNode(start=0, end=0, val=2), + SegmentTreeNode(start=1, end=1, val=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 + >>> tuple(num_arr.traverse()) # doctest: +NORMALIZE_WHITESPACE + (SegmentTreeNode(start=0, end=4, val=19), + SegmentTreeNode(start=0, end=2, val=12), + SegmentTreeNode(start=3, end=4, val=7), + SegmentTreeNode(start=0, end=1, val=7), + SegmentTreeNode(start=2, end=2, val=5), + SegmentTreeNode(start=3, end=3, val=3), + SegmentTreeNode(start=4, end=4, val=4), + SegmentTreeNode(start=0, end=0, val=2), + SegmentTreeNode(start=1, end=1, val=5)) >>> >>> num_arr.query_range(3, 4) 7 @@ -62,29 +58,29 @@ class SegmentTree: >>> 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 + SegmentTreeNode(start=0, end=4, val=5) + SegmentTreeNode(start=0, end=2, val=5) + SegmentTreeNode(start=3, end=4, val=4) + SegmentTreeNode(start=0, end=1, val=2) + SegmentTreeNode(start=2, end=2, val=5) + SegmentTreeNode(start=3, end=3, val=3) + SegmentTreeNode(start=4, end=4, val=4) + SegmentTreeNode(start=0, end=0, val=2) + SegmentTreeNode(start=1, end=1, val=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 + SegmentTreeNode(start=0, end=4, val=5) + SegmentTreeNode(start=0, end=2, val=5) + SegmentTreeNode(start=3, end=4, val=4) + SegmentTreeNode(start=0, end=1, val=5) + SegmentTreeNode(start=2, end=2, val=5) + SegmentTreeNode(start=3, end=3, val=3) + SegmentTreeNode(start=4, end=4, val=4) + SegmentTreeNode(start=0, end=0, val=2) + SegmentTreeNode(start=1, end=1, val=5) >>> >>> max_arr.query_range(3, 4) 4 @@ -97,29 +93,29 @@ class SegmentTree: >>> 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 + SegmentTreeNode(start=0, end=4, val=1) + SegmentTreeNode(start=0, end=2, val=1) + SegmentTreeNode(start=3, end=4, val=3) + SegmentTreeNode(start=0, end=1, val=1) + SegmentTreeNode(start=2, end=2, val=5) + SegmentTreeNode(start=3, end=3, val=3) + SegmentTreeNode(start=4, end=4, val=4) + SegmentTreeNode(start=0, end=0, val=2) + SegmentTreeNode(start=1, end=1, val=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 + SegmentTreeNode(start=0, end=4, val=2) + SegmentTreeNode(start=0, end=2, val=2) + SegmentTreeNode(start=3, end=4, val=3) + SegmentTreeNode(start=0, end=1, val=2) + SegmentTreeNode(start=2, end=2, val=5) + SegmentTreeNode(start=3, end=3, val=3) + SegmentTreeNode(start=4, end=4, val=4) + SegmentTreeNode(start=0, end=0, val=2) + SegmentTreeNode(start=1, end=1, val=5) >>> >>> min_arr.query_range(3, 4) 3 @@ -128,7 +124,6 @@ class SegmentTree: >>> min_arr.query_range(1, 3) 3 >>> - """ def __init__(self, collection: Sequence, function): diff --git a/data_structures/binary_tree/wavelet_tree.py b/data_structures/binary_tree/wavelet_tree.py index 8d7145189018..041e140f5b15 100644 --- a/data_structures/binary_tree/wavelet_tree.py +++ b/data_structures/binary_tree/wavelet_tree.py @@ -24,11 +24,11 @@ def __repr__(self) -> str: """ >>> node = Node(length=27) >>> repr(node) - 'min_value: -1, max_value: -1' + 'Node(min_value=-1 max_value=-1)' >>> repr(node) == str(node) True """ - return f"min_value: {self.minn}, max_value: {self.maxx}" + return f"Node(min_value={self.minn} max_value={self.maxx})" def build_tree(arr: list[int]) -> Node | None: @@ -37,7 +37,7 @@ def build_tree(arr: list[int]) -> Node | None: of the constructed tree >>> build_tree(test_array) - min_value: 0, max_value: 9 + Node(min_value=0 max_value=9) """ root = Node(len(arr)) root.minn, root.maxx = min(arr), max(arr) diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 90b6b6eb2a32..6c81493fff85 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -159,7 +159,7 @@ def delete(self, data) -> str: if current.next: current = current.next else: # We have reached the end an no value matches - return "No data matching given value" + raise ValueError("No data matching given value") if current == self.head: self.delete_head() diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 11942db8305c..637b7f62fd2c 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -425,7 +425,7 @@ def __repr__(self) -> str: values_list.append(aux.val) aux = aux.next_node - return "[" + ", ".join(repr(val) for val in values_list) + "]" + return f"[{', '.join(repr(val) for val in values_list)}]" if __name__ == "__main__": diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 697a8c634859..cb21076f91d2 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -58,7 +58,9 @@ def shortest_path(self, target_vertex: str) -> str: Case 1 - No path is found. >>> g.shortest_path("Foo") - 'No path from vertex:G to vertex:Foo' + Traceback (most recent call last): + ... + ValueError: No path from vertex: G to vertex: Foo Case 2 - The path is found. >>> g.shortest_path("D") @@ -71,7 +73,9 @@ def shortest_path(self, target_vertex: str) -> str: target_vertex_parent = self.parent.get(target_vertex) if target_vertex_parent is None: - return f"No path from vertex:{self.source_vertex} to vertex:{target_vertex}" + raise ValueError( + f"No path from vertex: {self.source_vertex} to vertex: {target_vertex}" + ) return self.shortest_path(target_vertex_parent) + f"->{target_vertex}" diff --git a/graphs/page_rank.py b/graphs/page_rank.py index e1af35b34749..b9e4c4a72a93 100644 --- a/graphs/page_rank.py +++ b/graphs/page_rank.py @@ -27,7 +27,7 @@ def add_outbound(self, node): self.outbound.append(node) def __repr__(self): - return f"Node {self.name}: Inbound: {self.inbound} ; Outbound: {self.outbound}" + return f"" def page_rank(nodes, limit=3, d=0.85): diff --git a/linear_algebra/src/polynom_for_points.py b/linear_algebra/src/polynom_for_points.py index 1d702deb1e99..f5e3db0cbb13 100644 --- a/linear_algebra/src/polynom_for_points.py +++ b/linear_algebra/src/polynom_for_points.py @@ -4,9 +4,13 @@ def points_to_polynomial(coordinates: list[list[int]]) -> str: number of points you want to use >>> print(points_to_polynomial([])) - The program cannot work out a fitting polynomial. + Traceback (most recent call last): + ... + ValueError: The program cannot work out a fitting polynomial. >>> print(points_to_polynomial([[]])) - The program cannot work out a fitting polynomial. + Traceback (most recent call last): + ... + ValueError: 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]])) @@ -25,17 +29,17 @@ def points_to_polynomial(coordinates: list[list[int]]) -> str: f(x)=x^2*5.0+x^1*-18.0+x^0*18.0 """ if len(coordinates) == 0 or not all(len(pair) == 2 for pair in coordinates): - return "The program cannot work out a fitting polynomial." + raise ValueError("The program cannot work out a fitting polynomial.") if len({tuple(pair) for pair in coordinates}) != len(coordinates): - return "The program cannot work out a fitting polynomial." + raise ValueError("The program cannot work out a fitting polynomial.") set_x = {x for x, _ in coordinates} if len(set_x) == 1: return f"x={coordinates[0][0]}" if len(set_x) != len(coordinates): - return "The program cannot work out a fitting polynomial." + raise ValueError("The program cannot work out a fitting polynomial.") x = len(coordinates) diff --git a/maths/monte_carlo_dice.py b/maths/monte_carlo_dice.py index c4150b88f6cc..362f70b49828 100644 --- a/maths/monte_carlo_dice.py +++ b/maths/monte_carlo_dice.py @@ -13,9 +13,6 @@ def __init__(self): 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]: """ diff --git a/matrix/cramers_rule_2x2.py b/matrix/cramers_rule_2x2.py index a635d66fbb6c..4f52dbe646ad 100644 --- a/matrix/cramers_rule_2x2.py +++ b/matrix/cramers_rule_2x2.py @@ -2,7 +2,7 @@ # https://en.wikipedia.org/wiki/Cramer%27s_rule -def cramers_rule_2x2(equation1: list[int], equation2: list[int]) -> str: +def cramers_rule_2x2(equation1: list[int], equation2: list[int]) -> tuple[float, float]: """ Solves the system of linear equation in 2 variables. :param: equation1: list of 3 numbers @@ -14,13 +14,13 @@ def cramers_rule_2x2(equation1: list[int], equation2: list[int]) -> str: determinant_y = [[a1, d1], [a2, d2]] >>> cramers_rule_2x2([2, 3, 0], [5, 1, 0]) - 'Trivial solution. (Consistent system) x = 0 and y = 0' + (0.0, 0.0) >>> cramers_rule_2x2([0, 4, 50], [2, 0, 26]) - 'Non-Trivial Solution (Consistent system) x = 13.0, y = 12.5' + (13.0, 12.5) >>> cramers_rule_2x2([11, 2, 30], [1, 0, 4]) - 'Non-Trivial Solution (Consistent system) x = 4.0, y = -7.0' + (4.0, -7.0) >>> cramers_rule_2x2([4, 7, 1], [1, 2, 0]) - 'Non-Trivial Solution (Consistent system) x = 2.0, y = -1.0' + (2.0, -1.0) >>> cramers_rule_2x2([1, 2, 3], [2, 4, 6]) Traceback (most recent call last): @@ -75,8 +75,10 @@ def cramers_rule_2x2(equation1: list[int], equation2: list[int]) -> str: raise ValueError("No solution. (Inconsistent system)") else: if determinant_x == determinant_y == 0: - return "Trivial solution. (Consistent system) x = 0 and y = 0" + # Trivial solution (Inconsistent system) + return (0.0, 0.0) else: x = determinant_x / determinant y = determinant_y / determinant - return f"Non-Trivial Solution (Consistent system) x = {x}, y = {y}" + # Non-Trivial Solution (Consistent system) + return (x, y) diff --git a/other/password.py b/other/password.py index 8f6833073288..f463c7564536 100644 --- a/other/password.py +++ b/other/password.py @@ -66,26 +66,23 @@ def random_characters(chars_incl, i): # This Will Check Whether A Given Password Is Strong Or Not # It Follows The Rule that Length Of Password Should Be At Least 8 Characters # And At Least 1 Lower, 1 Upper, 1 Number And 1 Special Character -def strong_password_detector(password: str, min_length: int = 8) -> str: +def is_strong_password(password: str, min_length: int = 8) -> bool: """ - >>> strong_password_detector('Hwea7$2!') - 'This is a strong Password' - - >>> strong_password_detector('Sh0r1') - 'Your Password must be at least 8 characters long' - - >>> strong_password_detector('Hello123') - 'Password should contain UPPERCASE, lowercase, numbers, special characters' - - >>> strong_password_detector('Hello1238udfhiaf038fajdvjjf!jaiuFhkqi1') - 'This is a strong Password' - - >>> strong_password_detector('0') - 'Your Password must be at least 8 characters long' + >>> is_strong_password('Hwea7$2!') + True + >>> is_strong_password('Sh0r1') + False + >>> is_strong_password('Hello123') + False + >>> is_strong_password('Hello1238udfhiaf038fajdvjjf!jaiuFhkqi1') + True + >>> is_strong_password('0') + False """ if len(password) < min_length: - return "Your Password must be at least 8 characters long" + # Your Password must be at least 8 characters long + return False upper = any(char in ascii_uppercase for char in password) lower = any(char in ascii_lowercase for char in password) @@ -93,13 +90,12 @@ def strong_password_detector(password: str, min_length: int = 8) -> str: spec_char = any(char in punctuation for char in password) if upper and lower and num and spec_char: - return "This is a strong Password" + return True else: - return ( - "Password should contain UPPERCASE, lowercase, " - "numbers, special characters" - ) + # Passwords should contain UPPERCASE, lowerase + # numbers, and special characters + return False def main(): diff --git a/strings/dna.py b/strings/dna.py index 46e271d689db..c2b96110b893 100644 --- a/strings/dna.py +++ b/strings/dna.py @@ -14,13 +14,18 @@ def dna(dna: str) -> str: >>> dna("CTGA") 'GACT' >>> dna("GFGG") - 'Invalid Strand' + Traceback (most recent call last): + ... + ValueError: Invalid Strand """ - r = len(re.findall("[ATCG]", dna)) != len(dna) - val = dna.translate(dna.maketrans("ATCG", "TAGC")) - return "Invalid Strand" if r else val + if len(re.findall("[ATCG]", dna)) != len(dna): + raise ValueError("Invalid Strand") + + return dna.translate(dna.maketrans("ATCG", "TAGC")) if __name__ == "__main__": - __import__("doctest").testmod() + import doctest + + doctest.testmod() From 6aaf0a2c77b671f3e35e71dfccc569f51d8e3b00 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 8 Nov 2022 12:49:47 +0100 Subject: [PATCH 2065/2908] maths/number_of_digits.py: Streamline benchmarks (#7913) * maths/number_of_digits.py: Streamline benchmarks ``` num_digits(262144): 6 -- 0.2226011250168085 seconds num_digits_fast(262144): 6 -- 0.13145116699161008 seconds num_digits_faster(262144): 6 -- 0.09273383300751448 seconds num_digits(1125899906842624): 16 -- 0.6056742920191027 seconds num_digits_fast(1125899906842624): 16 -- 0.15698366600554436 seconds num_digits_faster(1125899906842624): 16 -- 0.1027024170034565 seconds num_digits(1267650600228229401496703205376): 31 -- 1.1957934170495719 seconds num_digits_fast(1267650600228229401496703205376): 31 -- 0.15552304196171463 seconds num_digits_faster(1267650600228229401496703205376): 31 -- 0.13062308297958225 seconds ``` * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update number_of_digits.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/number_of_digits.py | 96 ++++++--------------------------------- 1 file changed, 13 insertions(+), 83 deletions(-) diff --git a/maths/number_of_digits.py b/maths/number_of_digits.py index 3c0eb7b3863f..86bc67f72490 100644 --- a/maths/number_of_digits.py +++ b/maths/number_of_digits.py @@ -67,93 +67,23 @@ def num_digits_faster(n: int) -> int: def benchmark() -> None: """ - Benchmark code for comparing 3 functions, - with 3 different length int values. + Benchmark multiple functions, with three 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", - ) + from collections.abc import Callable + + def benchmark_a_function(func: Callable, value: int) -> None: + call = f"{func.__name__}({value})" + timing = timeit(f"__main__.{call}", setup="import __main__") + print(f"{call}: {func(value)} -- {timing} seconds") + + for value in (262144, 1125899906842624, 1267650600228229401496703205376): + for func in (num_digits, num_digits_fast, num_digits_faster): + benchmark_a_function(func, value) + print() if __name__ == "__main__": - small_num = 262144 - medium_num = 1125899906842624 - large_num = 1267650600228229401496703205376 - benchmark() import doctest doctest.testmod() + benchmark() From 8951d857fea2f30d30f64e63d906dc986c32308a Mon Sep 17 00:00:00 2001 From: Abhishek Chakraborty Date: Tue, 8 Nov 2022 09:24:21 -0800 Subject: [PATCH 2066/2908] BB84 QKD algorithm (#7898) * Added BB84 algorithm. * Function name lowercase + imports fix I thought uppercase was appropriate because they're initials. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update quantum/bb84.py Co-authored-by: Christian Clauss * Removed python < 3.11 restriction from qiskit * Removed python < 3.11 restriction from qiskit * scikit-learn * Update quantum/bb84.py Correct typo in `default_rng()` call Co-authored-by: Maxim Smolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Maxim Smolskiy --- quantum/bb84.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 +- 2 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 quantum/bb84.py diff --git a/quantum/bb84.py b/quantum/bb84.py new file mode 100644 index 000000000000..60d64371fe63 --- /dev/null +++ b/quantum/bb84.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Simulation of the Quantum Key Distribution (QKD) protocol called BB84, +created by Charles Bennett and Gilles Brassard in 1984. + +BB84 is a key-distribution protocol that ensures secure key distribution +using qubits instead of classical bits. The generated key is the result +of simulating a quantum circuit. Our algorithm to construct the circuit +is as follows: + +Alice generates two binary strings. One encodes the basis for each qubit: + + - 0 -> {0,1} basis. + - 1 -> {+,-} basis. + +The other encodes the state: + + - 0 -> |0> or |+>. + - 1 -> |1> or |->. + +Bob also generates a binary string and uses the same convention to choose +a basis for measurement. Based on the following results, we follow the +algorithm below: + +X|0> = |1> + +H|0> = |+> + +HX|0> = |-> + +1. Whenever Alice wants to encode 1 in a qubit, she applies an +X (NOT) gate to the qubit. To encode 0, no action is needed. + +2. Wherever she wants to encode it in the {+,-} basis, she applies +an H (Hadamard) gate. No action is necessary to encode a qubit in +the {0,1} basis. + +3. She then sends the qubits to Bob (symbolically represented in +this circuit using wires). + +4. Bob measures the qubits according to his binary string for +measurement. To measure a qubit in the {+,-} basis, he applies +an H gate to the corresponding qubit and then performs a measurement. + +References: +https://en.wikipedia.org/wiki/BB84 +https://qiskit.org/textbook/ch-algorithms/quantum-key-distribution.html +""" +import numpy as np +import qiskit + + +def bb84(key_len: int = 8, seed: int | None = None) -> str: + """ + Performs the BB84 protocol using a key made of `key_len` bits. + The two parties in the key distribution are called Alice and Bob. + Args: + key_len: The length of the generated key in bits. The default is 8. + + seed: Seed for the random number generator. + Mostly used for testing. Default is None. + + Returns: + key: The key generated using BB84 protocol. + + >>> bb84(16, seed=0) + '1101101100010000' + + >>> bb84(8, seed=0) + '01011011' + """ + # Set up the random number generator. + rng = np.random.default_rng(seed=seed) + + # Roughly 25% of the qubits will contribute to the key. + # So we take more than we need. + num_qubits = 6 * key_len + # Measurement basis for Alice's qubits. + alice_basis = rng.integers(2, size=num_qubits) + # The set of states Alice will prepare. + alice_state = rng.integers(2, size=num_qubits) + # Measurement basis for Bob's qubits. + bob_basis = rng.integers(2, size=num_qubits) + + # Quantum Circuit to simulate BB84 + bb84_circ = qiskit.QuantumCircuit(num_qubits, name="BB84") + + # Alice prepares her qubits according to rules above. + for index, _ in enumerate(alice_basis): + if alice_state[index] == 1: + bb84_circ.x(index) + if alice_basis[index] == 1: + bb84_circ.h(index) + bb84_circ.barrier() + + # Bob measures the received qubits according to rules above. + for index, _ in enumerate(bob_basis): + if bob_basis[index] == 1: + bb84_circ.h(index) + + bb84_circ.barrier() + bb84_circ.measure_all() + + # Simulate the quantum circuit. + sim = qiskit.Aer.get_backend("aer_simulator") + # We only need to run one shot because the key is unique. + # Multiple shots will produce the same key. + job = qiskit.execute(bb84_circ, sim, shots=1, seed_simulator=seed) + # Returns the result of measurement. + result = job.result().get_counts(bb84_circ).most_frequent() + + # Extracting the generated key from the simulation results. + # Only keep measurement results where Alice and Bob chose the same basis. + gen_key = "".join( + [ + result_bit + for alice_basis_bit, bob_basis_bit, result_bit in zip( + alice_basis, bob_basis, result + ) + if alice_basis_bit == bob_basis_bit + ] + ) + + # Get final key. Pad with 0 if too short, otherwise truncate. + key = gen_key[:key_len] if len(gen_key) >= key_len else gen_key.ljust(key_len, "0") + return key + + +if __name__ == "__main__": + print(f"The generated key is : {bb84(8, seed=0)}") + from doctest import testmod + + testmod() diff --git a/requirements.txt b/requirements.txt index 00f31b85e404..a1d607df07e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,11 +8,11 @@ opencv-python pandas pillow projectq -qiskit; python_version < "3.11" +qiskit requests rich scikit-fuzzy -sklearn +scikit-learn statsmodels sympy tensorflow; python_version < "3.11" From 3f9aae149dba5c9b68ff6f7fd83cadf3fd6b1d7d Mon Sep 17 00:00:00 2001 From: Akshay Dubey <38462415+itsAkshayDubey@users.noreply.github.com> Date: Wed, 9 Nov 2022 21:06:38 +0530 Subject: [PATCH 2067/2908] feat: Add automorphic number implementation (#7978) * feat: Add automorphic number implementation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: Add type checking for number * refactor: Rename variable n to number * test: Add doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test: Add unit test for number=0 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/automorphic_number.py | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 maths/automorphic_number.py diff --git a/maths/automorphic_number.py b/maths/automorphic_number.py new file mode 100644 index 000000000000..103fc7301831 --- /dev/null +++ b/maths/automorphic_number.py @@ -0,0 +1,58 @@ +""" +== Automorphic Numbers == +A number n is said to be a Automorphic number if +the square of n "ends" in the same digits as n itself. + +Examples of Automorphic Numbers: 0, 1, 5, 6, 25, 76, 376, 625, 9376, 90625, ... +https://en.wikipedia.org/wiki/Automorphic_number +""" + +# Author : Akshay Dubey (https://github.com/itsAkshayDubey) +# Time Complexity : O(log10n) + + +def is_automorphic_number(number: int) -> bool: + """ + # doctest: +NORMALIZE_WHITESPACE + This functions takes an integer number as input. + returns True if the number is automorphic. + >>> is_automorphic_number(-1) + False + >>> is_automorphic_number(0) + True + >>> is_automorphic_number(5) + True + >>> is_automorphic_number(6) + True + >>> is_automorphic_number(7) + False + >>> is_automorphic_number(25) + True + >>> is_automorphic_number(259918212890625) + True + >>> is_automorphic_number(259918212890636) + False + >>> is_automorphic_number(740081787109376) + True + >>> is_automorphic_number(5.0) + Traceback (most recent call last): + ... + TypeError: Input value of [number=5.0] must be an integer + """ + if not isinstance(number, int): + raise TypeError(f"Input value of [number={number}] must be an integer") + if number < 0: + return False + number_square = number * number + while number > 0: + if number % 10 != number_square % 10: + return False + number //= 10 + number_square //= 10 + return True + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 076193eefa161a2030ca4b1ee60b285d4a50e4c6 Mon Sep 17 00:00:00 2001 From: Akshay Dubey <38462415+itsAkshayDubey@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:09:47 +0530 Subject: [PATCH 2068/2908] feat: Add pronic number implementation (#7979) * feat: Add pronic number implementation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/pronic_number.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 maths/pronic_number.py diff --git a/maths/pronic_number.py b/maths/pronic_number.py new file mode 100644 index 000000000000..8b554dbbd602 --- /dev/null +++ b/maths/pronic_number.py @@ -0,0 +1,54 @@ +""" +== Pronic Number == +A number n is said to be a Proic number if +there exists an integer m such that n = m * (m + 1) + +Examples of Proic Numbers: 0, 2, 6, 12, 20, 30, 42, 56, 72, 90, 110 ... +https://en.wikipedia.org/wiki/Pronic_number +""" + +# Author : Akshay Dubey (https://github.com/itsAkshayDubey) + + +def is_pronic(number: int) -> bool: + """ + # doctest: +NORMALIZE_WHITESPACE + This functions takes an integer number as input. + returns True if the number is pronic. + >>> is_pronic(-1) + False + >>> is_pronic(0) + True + >>> is_pronic(2) + True + >>> is_pronic(5) + False + >>> is_pronic(6) + True + >>> is_pronic(8) + False + >>> is_pronic(30) + True + >>> is_pronic(32) + False + >>> is_pronic(2147441940) + True + >>> is_pronic(9223372033963249500) + True + >>> is_pronic(6.0) + Traceback (most recent call last): + ... + TypeError: Input value of [number=6.0] must be an integer + """ + if not isinstance(number, int): + raise TypeError(f"Input value of [number={number}] must be an integer") + if number < 0 or number % 2 == 1: + return False + number_sqrt = int(number**0.5) + return number == number_sqrt * (number_sqrt + 1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 4cddb26908bde48047e4b6e383c4b061c289a5e5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 10 Nov 2022 03:41:28 +0100 Subject: [PATCH 2069/2908] atbash.py: Tighten up the benchmarks (#7977) * atbash.py: Tighten up the benchmarks * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + ciphers/atbash.py | 21 ++++----------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 76c7f9dea4e3..5f314c31745d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -995,6 +995,7 @@ * [Sol1](project_euler/problem_686/sol1.py) ## Quantum + * [Bb84](quantum/bb84.py) * [Deutsch Jozsa](quantum/deutsch_jozsa.py) * [Half Adder](quantum/half_adder.py) * [Not Gate](quantum/not_gate.py) diff --git a/ciphers/atbash.py b/ciphers/atbash.py index 5c2aea610bff..0a86a800c51a 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -38,26 +38,13 @@ def atbash(sequence: str) -> str: def benchmark() -> None: - """Let's benchmark them side-by-side...""" + """Let's benchmark our functions 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", - ) + setup = "from string import printable ; from __main__ import atbash, atbash_slow" + print(f"> atbash_slow(): {timeit('atbash_slow(printable)', setup=setup)} seconds") + print(f"> atbash(): {timeit('atbash(printable)', setup=setup)} seconds") if __name__ == "__main__": From 5c92b7390e650494f49e1f9298c1a79421673385 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 10 Nov 2022 03:42:14 +0100 Subject: [PATCH 2070/2908] prime_numbers.py: Tighten up the benchmarks (#7976) * prime_numbers.py: Tighten up the benchmarks * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- maths/prime_numbers.py | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index 4e076fe317b4..c5297ed9264c 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -90,32 +90,20 @@ def fast_primes(max_n: int) -> Generator[int, None, None]: yield i +def benchmark(): + """ + Let's benchmark our functions side-by-side... + """ + from timeit import timeit + + setup = "from __main__ import slow_primes, primes, fast_primes" + print(timeit("slow_primes(1_000_000_000_000)", setup=setup, number=1_000_000)) + print(timeit("primes(1_000_000_000_000)", setup=setup, number=1_000_000)) + print(timeit("fast_primes(1_000_000_000_000)", setup=setup, number=1_000_000)) + + if __name__ == "__main__": 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_000_000)", - setup="from __main__ import slow_primes", - number=1_000_000, - ) - ) - print( - timeit( - "primes(1_000_000_000_000)", - setup="from __main__ import primes", - number=1_000_000, - ) - ) - print( - timeit( - "fast_primes(1_000_000_000_000)", - setup="from __main__ import fast_primes", - number=1_000_000, - ) - ) + benchmark() From 7b2eca0243f5c4454875e17971cb527037d2e281 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Thu, 10 Nov 2022 06:49:38 +0400 Subject: [PATCH 2071/2908] add distribute coins (#7975) * add distribute coins * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix review notes * fix typehint * fix type in TreeNode Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 1 + .../binary_tree/distribute_coins.py | 135 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 data_structures/binary_tree/distribute_coins.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 5f314c31745d..74243cd0687b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -173,6 +173,7 @@ * [Binary Tree Path Sum](data_structures/binary_tree/binary_tree_path_sum.py) * [Binary Tree Traversals](data_structures/binary_tree/binary_tree_traversals.py) * [Diff Views Of Binary Tree](data_structures/binary_tree/diff_views_of_binary_tree.py) + * [Distribute Coins](data_structures/binary_tree/distribute_coins.py) * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) * [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py) * [Is Bst](data_structures/binary_tree/is_bst.py) diff --git a/data_structures/binary_tree/distribute_coins.py b/data_structures/binary_tree/distribute_coins.py new file mode 100644 index 000000000000..ea02afc2cea6 --- /dev/null +++ b/data_structures/binary_tree/distribute_coins.py @@ -0,0 +1,135 @@ +""" +Author : Alexander Pantyukhin +Date : November 7, 2022 + +Task: +You are given a tree root of a binary tree with n nodes, where each node has +node.data coins. There are exactly n coins in whole tree. + +In one move, we may choose two adjacent nodes and move one coin from one node +to another. A move may be from parent to child, or from child to parent. + +Return the minimum number of moves required to make every node have exactly one coin. + +Example 1: + + 3 + / \ + 0 0 + +Result: 2 + +Example 2: + + 0 + / \ + 3 0 + +Result 3 + +leetcode: https://leetcode.com/problems/distribute-coins-in-binary-tree/ + +Implementation notes: +User depth-first search approach. + +Let n is the number of nodes in tree +Runtime: O(n) +Space: O(1) +""" + +from __future__ import annotations + +from collections import namedtuple +from dataclasses import dataclass + + +@dataclass +class TreeNode: + data: int + left: TreeNode | None = None + right: TreeNode | None = None + + +CoinsDistribResult = namedtuple("CoinsDistribResult", "moves excess") + + +def distribute_coins(root: TreeNode | None) -> int: + """ + >>> distribute_coins(TreeNode(3, TreeNode(0), TreeNode(0))) + 2 + >>> distribute_coins(TreeNode(0, TreeNode(3), TreeNode(0))) + 3 + >>> distribute_coins(TreeNode(0, TreeNode(0), TreeNode(3))) + 3 + >>> distribute_coins(None) + 0 + >>> distribute_coins(TreeNode(0, TreeNode(0), TreeNode(0))) + Traceback (most recent call last): + ... + ValueError: The nodes number should be same as the number of coins + >>> distribute_coins(TreeNode(0, TreeNode(1), TreeNode(1))) + Traceback (most recent call last): + ... + ValueError: The nodes number should be same as the number of coins + """ + + if root is None: + return 0 + + # Validation + def count_nodes(node: TreeNode | None) -> int: + """ + >>> count_nodes(None): + 0 + """ + if node is None: + return 0 + + return count_nodes(node.left) + count_nodes(node.right) + 1 + + def count_coins(node: TreeNode | None) -> int: + """ + >>> count_coins(None): + 0 + """ + if node is None: + return 0 + + return count_coins(node.left) + count_coins(node.right) + node.data + + if count_nodes(root) != count_coins(root): + raise ValueError("The nodes number should be same as the number of coins") + + # Main calculation + def get_distrib(node: TreeNode | None) -> CoinsDistribResult: + """ + >>> get_distrib(None) + namedtuple("CoinsDistribResult", "0 2") + """ + + if node is None: + return CoinsDistribResult(0, 1) + + left_distrib_moves, left_distrib_excess = get_distrib(node.left) + right_distrib_moves, right_distrib_excess = get_distrib(node.right) + + coins_to_left = 1 - left_distrib_excess + coins_to_right = 1 - right_distrib_excess + + result_moves = ( + left_distrib_moves + + right_distrib_moves + + abs(coins_to_left) + + abs(coins_to_right) + ) + result_excess = node.data - coins_to_left - coins_to_right + + return CoinsDistribResult(result_moves, result_excess) + + return get_distrib(root)[0] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From e1be882f72f85d5f7267b46f0ffd5203a6d81e2e Mon Sep 17 00:00:00 2001 From: Akshay Dubey <38462415+itsAkshayDubey@users.noreply.github.com> Date: Thu, 10 Nov 2022 16:25:50 +0530 Subject: [PATCH 2072/2908] algorithm: Twin prime (#7980) * feat: Add twin prime algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: Fix broken import statement Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/twin_prime.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 maths/twin_prime.py diff --git a/maths/twin_prime.py b/maths/twin_prime.py new file mode 100644 index 000000000000..e6ac0cc7805b --- /dev/null +++ b/maths/twin_prime.py @@ -0,0 +1,45 @@ +""" +== Twin Prime == +A number n+2 is said to be a Twin prime of number n if +both n and n+2 are prime. + +Examples of Twin pairs: (3, 5), (5, 7), (11, 13), (17, 19), (29, 31), (41, 43), ... +https://en.wikipedia.org/wiki/Twin_prime +""" + +# Author : Akshay Dubey (https://github.com/itsAkshayDubey) +from maths.prime_check import is_prime + + +def twin_prime(number: int) -> int: + """ + # doctest: +NORMALIZE_WHITESPACE + This functions takes an integer number as input. + returns n+2 if n and n+2 are prime numbers and -1 otherwise. + >>> twin_prime(3) + 5 + >>> twin_prime(4) + -1 + >>> twin_prime(5) + 7 + >>> twin_prime(17) + 19 + >>> twin_prime(0) + -1 + >>> twin_prime(6.0) + Traceback (most recent call last): + ... + TypeError: Input value of [number=6.0] must be an integer + """ + if not isinstance(number, int): + raise TypeError(f"Input value of [number={number}] must be an integer") + if is_prime(number) and is_prime(number + 2): + return number + 2 + else: + return -1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 316e71b03448b6adb8a32d96cb4d6488ee7b7787 Mon Sep 17 00:00:00 2001 From: Gayathri Krishnan Date: Tue, 15 Nov 2022 19:07:59 +0530 Subject: [PATCH 2073/2908] Additional intro blockchain doc (#7974) * A deeper introduction to blockchain technology * Update README.md Rectified errors as image was not visible * Delete img1.jpg Deleting the image as it is not getting accepted in PR merge * Delete img2.jpg Deleting the image as it is not getting accepted in PR merge * Update README.md Removed all image s in the document * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update README.md Commited the suggested changes and submitting for review. * Update README.md Changed a sentence that needed grammatical correction. * Update README.md Added the changes suggested by review panel Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- blockchain/README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/blockchain/README.md b/blockchain/README.md index 5ae7f95ec981..b5fab7b36eaa 100644 --- a/blockchain/README.md +++ b/blockchain/README.md @@ -1,7 +1,44 @@ # Blockchain -A Blockchain is a type of distributed ledger technology (DLT) that consists of growing list of records, called blocks, that are securely linked together using cryptography. +A Blockchain is a type of **distributed ledger** technology (DLT) that consists of growing list of records, called **blocks**, that are securely linked together using **cryptography**. +Let's breakdown the terminologies in the above definition. We find below terminologies, + +- Digital Ledger Technology (DLT) +- Blocks +- Cryptography + +## Digital Ledger Technology + + It is otherwise called as distributed ledger technology. It is simply the opposite of centralized database. Firstly, what is a **ledger**? A ledger is a book or collection of accounts that records account transactions. + + *Why is Blockchain addressed as digital ledger if it can record more than account transactions? What other transaction details and information can it hold?* + +Digital Ledger Technology is just a ledger which is shared among multiple nodes. This way there exist no need for central authority to hold the info. Okay, how is it differentiated from central database and what are their benefits? + +There is an organization which has 4 branches whose data are stored in a centralized database. So even if one branch needs any data from ledger they need an approval from database in charge. And if one hacks the central database he gets to tamper and control all the data. + +Now lets assume every branch has a copy of the ledger and then once anything is added to the ledger by anyone branch it is gonna automatically reflect in all other ledgers available in other branch. This is done using Peer-to-peer network. + +So this means even if information is tampered in one branch we can find out. If one branch is hacked we can be alerted ,so we can safeguard other branches. Now, assume these branches as computers or nodes and the ledger is a transaction record or digital receipt. If one ledger is hacked in a node we can detect since there will be a mismatch in comparison with other node information. So this is the concept of Digital Ledger Technology. + +*Is it required for all nodes to have access to all information in other nodes? Wouldn't this require enormous storage space in each node?* + +## Blocks + +In short a block is nothing but collections of records with a labelled header. These are connected cryptographically. Once a new block is added to a chain, the previous block is connected, more precisely said as locked and hence, will remain unaltered. We can understand this concept once we get a clear understanding of working mechanism of blockchain. + +## Cryptography + +It is the practice and study of secure communication techniques in the midst of adversarial behavior. More broadly, cryptography is the creation and analysis of protocols that prevent third parties or the general public from accessing private messages. + +*Which cryptography technology is most widely used in blockchain and why?* + +So, in general, blockchain technology is a distributed record holder which records the information about ownership of an asset. To define precisely, +> Blockchain is a distributed, immutable ledger that makes it easier to record transactions and track assets in a corporate network. +An asset could be tangible (such as a house, car, cash, or land) or intangible (such as a business) (intellectual property, patents, copyrights, branding). A blockchain network can track and sell almost anything of value, lowering risk and costs for everyone involved. + +So this is all about introduction to blockchain technology. To learn more about the topic refer below links.... * * * From 3bf86b91e7d438eb2b9ecbab68060c007d270332 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 15 Nov 2022 19:25:14 +0530 Subject: [PATCH 2074/2908] fix: no implicit optional (#7984) --- data_structures/binary_tree/fenwick_tree.py | 2 +- fractals/julia_sets.py | 2 +- linear_algebra/src/schur_complement.py | 2 +- machine_learning/linear_discriminant_analysis.py | 2 +- project_euler/problem_074/sol1.py | 2 +- sorts/strand_sort.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data_structures/binary_tree/fenwick_tree.py b/data_structures/binary_tree/fenwick_tree.py index babd75ac4b31..88b0873a10fb 100644 --- a/data_structures/binary_tree/fenwick_tree.py +++ b/data_structures/binary_tree/fenwick_tree.py @@ -8,7 +8,7 @@ class FenwickTree: More info: https://en.wikipedia.org/wiki/Fenwick_tree """ - def __init__(self, arr: list[int] = None, size: int = None) -> None: + def __init__(self, arr: list[int] | None = None, size: int | None = None) -> None: """ Constructor for the Fenwick tree diff --git a/fractals/julia_sets.py b/fractals/julia_sets.py index 35fdc45d020a..77d1d7c042ba 100644 --- a/fractals/julia_sets.py +++ b/fractals/julia_sets.py @@ -89,7 +89,7 @@ def iterate_function( function_params: Any, nb_iterations: int, z_0: numpy.ndarray, - infinity: float = None, + infinity: float | None = None, ) -> numpy.ndarray: """ Iterate the function "eval_function" exactly nb_iterations times. diff --git a/linear_algebra/src/schur_complement.py b/linear_algebra/src/schur_complement.py index f3cb736d9084..3a5f4443afd3 100644 --- a/linear_algebra/src/schur_complement.py +++ b/linear_algebra/src/schur_complement.py @@ -7,7 +7,7 @@ def schur_complement( mat_a: np.ndarray, mat_b: np.ndarray, mat_c: np.ndarray, - pseudo_inv: np.ndarray = None, + pseudo_inv: np.ndarray | None = None, ) -> np.ndarray: """ Schur complement of a symmetric matrix X given as a 2x2 block matrix diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 9ef42ed19bab..f4fb5ba76b64 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -256,7 +256,7 @@ def valid_input( input_msg: str, err_msg: str, condition: Callable[[num], bool] = lambda x: True, - default: str = None, + default: str | None = None, ) -> num: """ Ask for user value and validate that it fulfill a condition. diff --git a/project_euler/problem_074/sol1.py b/project_euler/problem_074/sol1.py index a40a629033fa..a257d4d94fa8 100644 --- a/project_euler/problem_074/sol1.py +++ b/project_euler/problem_074/sol1.py @@ -71,7 +71,7 @@ def sum_digit_factorials(n: int) -> int: return ret -def chain_length(n: int, previous: set = None) -> int: +def chain_length(n: int, previous: set | None = 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. diff --git a/sorts/strand_sort.py b/sorts/strand_sort.py index a89135a0691f..4cadd396178e 100644 --- a/sorts/strand_sort.py +++ b/sorts/strand_sort.py @@ -1,7 +1,7 @@ import operator -def strand_sort(arr: list, reverse: bool = False, solution: list = None) -> list: +def strand_sort(arr: list, reverse: bool = False, solution: list | None = None) -> list: """ Strand sort implementation source: https://en.wikipedia.org/wiki/Strand_sort From 4ce8ad9ce6e554360089e77e088df6dd8b4a69df Mon Sep 17 00:00:00 2001 From: Akshay Dubey <38462415+itsAkshayDubey@users.noreply.github.com> Date: Tue, 15 Nov 2022 22:58:49 +0530 Subject: [PATCH 2075/2908] algorithm: Liouville lambda function (#7986) * feat: Add liouville lambda function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: Refactor if-else block * refactor: Refactor error handling for -ve numbers * refactor: Remove # doctest: +NORMALIZE_WHITESPACE Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/liouville_lambda.py | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 maths/liouville_lambda.py diff --git a/maths/liouville_lambda.py b/maths/liouville_lambda.py new file mode 100644 index 000000000000..5993efa42d66 --- /dev/null +++ b/maths/liouville_lambda.py @@ -0,0 +1,45 @@ +""" +== Liouville Lambda Function == +The Liouville Lambda function, denoted by λ(n) +and λ(n) is 1 if n is the product of an even number of prime numbers, +and -1 if it is the product of an odd number of primes. + +https://en.wikipedia.org/wiki/Liouville_function +""" + +# Author : Akshay Dubey (https://github.com/itsAkshayDubey) +from maths.prime_factors import prime_factors + + +def liouville_lambda(number: int) -> int: + """ + This functions takes an integer number as input. + returns 1 if n has even number of prime factors and -1 otherwise. + >>> liouville_lambda(10) + 1 + >>> liouville_lambda(11) + -1 + >>> liouville_lambda(0) + Traceback (most recent call last): + ... + ValueError: Input must be a positive integer + >>> liouville_lambda(-1) + Traceback (most recent call last): + ... + ValueError: Input must be a positive integer + >>> liouville_lambda(11.0) + Traceback (most recent call last): + ... + TypeError: Input value of [number=11.0] must be an integer + """ + if not isinstance(number, int): + raise TypeError(f"Input value of [number={number}] must be an integer") + if number < 1: + raise ValueError("Input must be a positive integer") + return -1 if len(prime_factors(number)) % 2 else 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 8bfd1c844b388cb78b03952c7da28f07f3838fd1 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 15 Nov 2022 22:59:14 +0530 Subject: [PATCH 2076/2908] fix: mypy 0.991 issues (#7988) * fix: mypy 0.991 issues * fix: invalid condition for base case --- conversions/decimal_to_any.py | 5 +- data_structures/linked_list/__init__.py | 2 +- matrix/matrix_class.py | 732 ++++++++++++------------ 3 files changed, 370 insertions(+), 369 deletions(-) diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index 11a2af294829..c9c2e9a5fb71 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -76,8 +76,9 @@ def decimal_to_any(num: int, base: int) -> str: 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) + else: + actual_value = str(mod) + new_value += actual_value div = num // base num = div if div == 0: diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index 85660a6d2c27..56b0e51baa93 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -49,7 +49,7 @@ def __str__(self) -> str: >>> print(linked_list) 9 --> 14 --> 23 """ - if not self.is_empty: + if self.is_empty(): return "" else: iterate = self.head diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 0c3078fe6dc8..a73e8b92a286 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -1,366 +1,366 @@ -# An OOP approach to representing and manipulating matrices - -from __future__ import annotations - - -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 - >>> matrix.rows - [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - >>> 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.]] - >>> # won't be apparent due to the nature of the cofactor matrix - >>> print(matrix.adjugate()) - [[-3. 6. -3.] - [6. -12. 6.] - [-3. 6. -3.]] - >>> matrix.inverse() - Traceback (most recent call last): - ... - TypeError: Only matrices with a non-zero determinant have an inverse - - 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: list[list[int]]): - 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 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) -> list[list[int]]: - return [[row[i] for row in self.rows] for i in range(len(self.rows[0]))] - - @property - def num_rows(self) -> int: - return len(self.rows) - - @property - def num_columns(self) -> int: - return len(self.rows[0]) - - @property - def order(self) -> tuple[int, int]: - return (self.num_rows, self.num_columns) - - @property - def is_square(self) -> bool: - return self.order[0] == self.order[1] - - def identity(self) -> Matrix: - 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) -> int: - if not self.is_square: - return 0 - if self.order == (0, 0): - return 1 - if self.order == (1, 1): - return int(self.rows[0][0]) - if self.order == (2, 2): - return int( - (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) -> bool: - return bool(self.determinant()) - - def get_minor(self, row: int, column: int) -> int: - 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: int, column: int) -> int: - if (row + column) % 2 == 0: - return self.get_minor(row, column) - return -1 * self.get_minor(row, column) - - def minors(self) -> Matrix: - return Matrix( - [ - [self.get_minor(row, column) for column in range(self.num_columns)] - for row in range(self.num_rows) - ] - ) - - def cofactors(self) -> Matrix: - 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) -> Matrix: - 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) -> Matrix: - determinant = self.determinant() - if not determinant: - raise TypeError("Only matrices with a non-zero determinant have an inverse") - return self.adjugate() * (1 / determinant) - - def __repr__(self) -> str: - return str(self.rows) - - def __str__(self) -> str: - if self.num_rows == 0: - return "[]" - if self.num_rows == 1: - return "[[" + ". ".join(str(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: list[int], position: int | None = None) -> 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: list[int], position: int | None = None) -> 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: object) -> bool: - if not isinstance(other, Matrix): - return NotImplemented - return self.rows == other.rows - - def __ne__(self, other: object) -> bool: - return not self == other - - def __neg__(self) -> Matrix: - return self * -1 - - def __add__(self, other: Matrix) -> Matrix: - 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: Matrix) -> Matrix: - 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: Matrix | int | float) -> Matrix: - if isinstance(other, (int, float)): - return Matrix( - [[int(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" - ) - 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: int) -> Matrix: - 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 _ in range(other - 1): - result *= self - return result - - @classmethod - def dot_product(cls, row: list[int], column: list[int]) -> int: - return sum(row[i] * column[i] for i in range(len(row))) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() +# An OOP approach to representing and manipulating matrices + +from __future__ import annotations + + +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 + >>> matrix.rows + [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + >>> 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.]] + >>> # won't be apparent due to the nature of the cofactor matrix + >>> print(matrix.adjugate()) + [[-3. 6. -3.] + [6. -12. 6.] + [-3. 6. -3.]] + >>> matrix.inverse() + Traceback (most recent call last): + ... + TypeError: Only matrices with a non-zero determinant have an inverse + + 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: list[list[int]]): + 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 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) -> list[list[int]]: + return [[row[i] for row in self.rows] for i in range(len(self.rows[0]))] + + @property + def num_rows(self) -> int: + return len(self.rows) + + @property + def num_columns(self) -> int: + return len(self.rows[0]) + + @property + def order(self) -> tuple[int, int]: + return (self.num_rows, self.num_columns) + + @property + def is_square(self) -> bool: + return self.order[0] == self.order[1] + + def identity(self) -> Matrix: + 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) -> int: + if not self.is_square: + return 0 + if self.order == (0, 0): + return 1 + if self.order == (1, 1): + return int(self.rows[0][0]) + if self.order == (2, 2): + return int( + (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) -> bool: + return bool(self.determinant()) + + def get_minor(self, row: int, column: int) -> int: + 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: int, column: int) -> int: + if (row + column) % 2 == 0: + return self.get_minor(row, column) + return -1 * self.get_minor(row, column) + + def minors(self) -> Matrix: + return Matrix( + [ + [self.get_minor(row, column) for column in range(self.num_columns)] + for row in range(self.num_rows) + ] + ) + + def cofactors(self) -> Matrix: + 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) -> Matrix: + 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) -> Matrix: + determinant = self.determinant() + if not determinant: + raise TypeError("Only matrices with a non-zero determinant have an inverse") + return self.adjugate() * (1 / determinant) + + def __repr__(self) -> str: + return str(self.rows) + + def __str__(self) -> str: + if self.num_rows == 0: + return "[]" + if self.num_rows == 1: + return "[[" + ". ".join(str(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: list[int], position: int | None = None) -> 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: list[int], position: int | None = None) -> 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: object) -> bool: + if not isinstance(other, Matrix): + return NotImplemented + return self.rows == other.rows + + def __ne__(self, other: object) -> bool: + return not self == other + + def __neg__(self) -> Matrix: + return self * -1 + + def __add__(self, other: Matrix) -> Matrix: + 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: Matrix) -> Matrix: + 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: Matrix | int | float) -> Matrix: + if isinstance(other, (int, float)): + return Matrix( + [[int(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" + ) + 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: int) -> Matrix: + 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 _ in range(other - 1): + result *= self + return result + + @classmethod + def dot_product(cls, row: list[int], column: list[int]) -> int: + return sum(row[i] * column[i] for i in range(len(row))) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0684ccdd69c62d5dc816bdc488bc079d06b9685a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 18:34:17 +0100 Subject: [PATCH 2077/2908] [pre-commit.ci] pre-commit autoupdate (#7983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/pyupgrade: v3.2.0 → v3.2.2](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.2.2) - [github.com/pre-commit/mirrors-mypy: v0.982 → v0.990](https://github.com/pre-commit/mirrors-mypy/compare/v0.982...v0.990) * updating DIRECTORY.md * Update .pre-commit-config.yaml * Downgrade to mypy v0.991 --> v0.990 * mpyp v0.991 * Update DIRECTORY.md Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0ea03b9b8cd..324a021ee205 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,11 +27,11 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.2.2 hooks: - id: pyupgrade args: - - --py310-plus + - --py311-plus - repo: https://github.com/PyCQA/flake8 rev: 5.0.4 @@ -52,7 +52,7 @@ repos: *flake8-plugins - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.982 + rev: v0.991 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 74243cd0687b..e2fffec57380 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -509,6 +509,7 @@ * [Area](maths/area.py) * [Area Under Curve](maths/area_under_curve.py) * [Armstrong Numbers](maths/armstrong_numbers.py) + * [Automorphic Number](maths/automorphic_number.py) * [Average Absolute Deviation](maths/average_absolute_deviation.py) * [Average Mean](maths/average_mean.py) * [Average Median](maths/average_median.py) @@ -603,6 +604,7 @@ * [Prime Sieve Eratosthenes](maths/prime_sieve_eratosthenes.py) * [Primelib](maths/primelib.py) * [Print Multiplication Table](maths/print_multiplication_table.py) + * [Pronic Number](maths/pronic_number.py) * [Proth Number](maths/proth_number.py) * [Pythagoras](maths/pythagoras.py) * [Qr Decomposition](maths/qr_decomposition.py) @@ -638,6 +640,7 @@ * [Test Prime Check](maths/test_prime_check.py) * [Trapezoidal Rule](maths/trapezoidal_rule.py) * [Triplet Sum](maths/triplet_sum.py) + * [Twin Prime](maths/twin_prime.py) * [Two Pointer](maths/two_pointer.py) * [Two Sum](maths/two_sum.py) * [Ugly Numbers](maths/ugly_numbers.py) From b33ea81a7437eaf7d048d92a9b75330c9d9e165e Mon Sep 17 00:00:00 2001 From: Akshay Dubey <38462415+itsAkshayDubey@users.noreply.github.com> Date: Fri, 18 Nov 2022 13:48:47 +0530 Subject: [PATCH 2078/2908] algorithm: Add juggler sequence (#7985) * feat: Add juggler sequence * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: Remove temp variable * refactor: Change error type for negative numbers * refactor: Remove # doctest: +NORMALIZE_WHITESPACE * refactor: Remove int typecasting * test: Add unit tests for n=10 and n=25 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/juggler_sequence.py | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 maths/juggler_sequence.py diff --git a/maths/juggler_sequence.py b/maths/juggler_sequence.py new file mode 100644 index 000000000000..9daba8bc0e8a --- /dev/null +++ b/maths/juggler_sequence.py @@ -0,0 +1,61 @@ +""" +== Juggler Sequence == +Juggler sequence start with any positive integer n. The next term is +obtained as follows: + If n term is even, the next term is floor value of square root of n . + If n is odd, the next term is floor value of 3 time the square root of n. + +https://en.wikipedia.org/wiki/Juggler_sequence +""" + +# Author : Akshay Dubey (https://github.com/itsAkshayDubey) +import math + + +def juggler_sequence(number: int) -> list[int]: + """ + >>> juggler_sequence(0) + Traceback (most recent call last): + ... + ValueError: Input value of [number=0] must be a positive integer + >>> juggler_sequence(1) + [1] + >>> juggler_sequence(2) + [2, 1] + >>> juggler_sequence(3) + [3, 5, 11, 36, 6, 2, 1] + >>> juggler_sequence(5) + [5, 11, 36, 6, 2, 1] + >>> juggler_sequence(10) + [10, 3, 5, 11, 36, 6, 2, 1] + >>> juggler_sequence(25) + [25, 125, 1397, 52214, 228, 15, 58, 7, 18, 4, 2, 1] + >>> juggler_sequence(6.0) + Traceback (most recent call last): + ... + TypeError: Input value of [number=6.0] must be an integer + >>> juggler_sequence(-1) + Traceback (most recent call last): + ... + ValueError: Input value of [number=-1] must be a positive integer + """ + if not isinstance(number, int): + raise TypeError(f"Input value of [number={number}] must be an integer") + if number < 1: + raise ValueError(f"Input value of [number={number}] must be a positive integer") + sequence = [number] + while number != 1: + if number % 2 == 0: + number = math.floor(math.sqrt(number)) + else: + number = math.floor( + math.sqrt(number) * math.sqrt(number) * math.sqrt(number) + ) + sequence.append(number) + return sequence + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f01a1af1df28ba53fc4727ea0bb703b5744100a7 Mon Sep 17 00:00:00 2001 From: Swayam <74960567+practice404@users.noreply.github.com> Date: Sun, 20 Nov 2022 16:25:58 +0530 Subject: [PATCH 2079/2908] Bi directional dijkstra (#7982) * Added Bi-Directional Dijkstra * Added Bi-Directional Dijkstra * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added doctest and type hints * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename Bi_directional_Dijkstra.py to bi_directional_dijkstra.py * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bi_directional_dijkstra.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- graphs/bi_directional_dijkstra.py | 130 ++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 graphs/bi_directional_dijkstra.py diff --git a/graphs/bi_directional_dijkstra.py b/graphs/bi_directional_dijkstra.py new file mode 100644 index 000000000000..fc53e2f0d8f3 --- /dev/null +++ b/graphs/bi_directional_dijkstra.py @@ -0,0 +1,130 @@ +""" +Bi-directional Dijkstra's algorithm. + +A bi-directional approach is an efficient and +less time consuming optimization for Dijkstra's +searching algorithm + +Reference: shorturl.at/exHM7 +""" + +# Author: Swayam Singh (https://github.com/practice404) + + +from queue import PriorityQueue +from typing import Any + +import numpy as np + + +def bidirectional_dij( + source: str, destination: str, graph_forward: dict, graph_backward: dict +) -> int: + """ + Bi-directional Dijkstra's algorithm. + + Returns: + shortest_path_distance (int): length of the shortest path. + + Warnings: + If the destination is not reachable, function returns -1 + + >>> bidirectional_dij("E", "F", graph_fwd, graph_bwd) + 3 + """ + shortest_path_distance = -1 + + visited_forward = set() + visited_backward = set() + cst_fwd = {source: 0} + cst_bwd = {destination: 0} + parent_forward = {source: None} + parent_backward = {destination: None} + queue_forward: PriorityQueue[Any] = PriorityQueue() + queue_backward: PriorityQueue[Any] = PriorityQueue() + + shortest_distance = np.inf + + queue_forward.put((0, source)) + queue_backward.put((0, destination)) + + if source == destination: + return 0 + + while queue_forward and queue_backward: + while not queue_forward.empty(): + _, v_fwd = queue_forward.get() + + if v_fwd not in visited_forward: + break + else: + break + visited_forward.add(v_fwd) + + while not queue_backward.empty(): + _, v_bwd = queue_backward.get() + + if v_bwd not in visited_backward: + break + else: + break + visited_backward.add(v_bwd) + + # forward pass and relaxation + for nxt_fwd, d_forward in graph_forward[v_fwd]: + if nxt_fwd in visited_forward: + continue + old_cost_f = cst_fwd.get(nxt_fwd, np.inf) + new_cost_f = cst_fwd[v_fwd] + d_forward + if new_cost_f < old_cost_f: + queue_forward.put((new_cost_f, nxt_fwd)) + cst_fwd[nxt_fwd] = new_cost_f + parent_forward[nxt_fwd] = v_fwd + if nxt_fwd in visited_backward: + if cst_fwd[v_fwd] + d_forward + cst_bwd[nxt_fwd] < shortest_distance: + shortest_distance = cst_fwd[v_fwd] + d_forward + cst_bwd[nxt_fwd] + + # backward pass and relaxation + for nxt_bwd, d_backward in graph_backward[v_bwd]: + if nxt_bwd in visited_backward: + continue + old_cost_b = cst_bwd.get(nxt_bwd, np.inf) + new_cost_b = cst_bwd[v_bwd] + d_backward + if new_cost_b < old_cost_b: + queue_backward.put((new_cost_b, nxt_bwd)) + cst_bwd[nxt_bwd] = new_cost_b + parent_backward[nxt_bwd] = v_bwd + + if nxt_bwd in visited_forward: + if cst_bwd[v_bwd] + d_backward + cst_fwd[nxt_bwd] < shortest_distance: + shortest_distance = cst_bwd[v_bwd] + d_backward + cst_fwd[nxt_bwd] + + if cst_fwd[v_fwd] + cst_bwd[v_bwd] >= shortest_distance: + break + + if shortest_distance != np.inf: + shortest_path_distance = shortest_distance + return shortest_path_distance + + +graph_fwd = { + "B": [["C", 1]], + "C": [["D", 1]], + "D": [["F", 1]], + "E": [["B", 1], ["G", 2]], + "F": [], + "G": [["F", 1]], +} +graph_bwd = { + "B": [["E", 1]], + "C": [["B", 1]], + "D": [["C", 1]], + "F": [["D", 1], ["G", 1]], + "E": [[None, np.inf]], + "G": [["E", 2]], +} + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a25c53e8b0cc73ff718ec406ac04cca0c2ddbb02 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Sun, 20 Nov 2022 14:59:25 +0400 Subject: [PATCH 2080/2908] Fix argument validation for count_1s_brian_kernighan_method (#7994) * Fix argument validation for count_1s_brian_kernighan_method * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + .../count_1s_brian_kernighan_method.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index e2fffec57380..83da4b76abca 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -571,6 +571,7 @@ * [Largest Subarray Sum](maths/largest_subarray_sum.py) * [Least Common Multiple](maths/least_common_multiple.py) * [Line Length](maths/line_length.py) + * [Liouville Lambda](maths/liouville_lambda.py) * [Lucas Lehmer Primality Test](maths/lucas_lehmer_primality_test.py) * [Lucas Series](maths/lucas_series.py) * [Maclaurin Series](maths/maclaurin_series.py) diff --git a/bit_manipulation/count_1s_brian_kernighan_method.py b/bit_manipulation/count_1s_brian_kernighan_method.py index e6d6d65345c4..2ed81b09d675 100644 --- a/bit_manipulation/count_1s_brian_kernighan_method.py +++ b/bit_manipulation/count_1s_brian_kernighan_method.py @@ -17,16 +17,19 @@ def get_1s_count(number: int) -> int: >>> get_1s_count(-1) Traceback (most recent call last): ... - ValueError: the value of input must be positive + ValueError: Input must be a non-negative integer >>> get_1s_count(0.8) Traceback (most recent call last): ... - TypeError: Input value must be an 'int' type + ValueError: Input must be a non-negative integer + >>> get_1s_count("25") + Traceback (most recent call last): + ... + ValueError: Input must be a non-negative integer """ - if number < 0: - raise ValueError("the value of input must be positive") - elif isinstance(number, float): - raise TypeError("Input value must be an 'int' type") + if not isinstance(number, int) or number < 0: + raise ValueError("Input must be a non-negative integer") + count = 0 while number: # This way we arrive at next set bit (next 1) instead of looping From f32d611689dc72bda67f1c4636ab1599c60d27a4 Mon Sep 17 00:00:00 2001 From: Mark Mayo Date: Mon, 21 Nov 2022 00:00:27 +1300 Subject: [PATCH 2081/2908] clean of unnecessary checks, imports, calls (#7993) --- backtracking/rat_in_maze.py | 4 ++-- boolean_algebra/not_gate.py | 2 +- cellular_automata/nagel_schrekenberg.py | 3 +-- ciphers/mixed_keyword_cypher.py | 4 ++-- compression/huffman.py | 2 +- data_structures/heap/min_heap.py | 2 +- .../test_digital_image_processing.py | 2 +- dynamic_programming/fizz_buzz.py | 5 ++--- dynamic_programming/max_sub_array.py | 3 +-- graphs/directed_and_undirected_(weighted)_graph.py | 8 ++++---- graphs/multi_heuristic_astar.py | 3 ++- linear_algebra/src/lib.py | 2 +- machine_learning/sequential_minimum_optimization.py | 10 ++-------- maths/find_min.py | 3 +-- maths/kadanes.py | 6 ++---- maths/largest_subarray_sum.py | 6 ++---- maths/series/geometric_series.py | 2 +- networking_flow/ford_fulkerson.py | 2 +- networking_flow/minimum_cut.py | 2 +- other/password.py | 10 +++------- project_euler/problem_025/sol1.py | 2 +- project_euler/problem_036/sol1.py | 2 +- quantum/q_fourier_transform.py | 4 ++-- quantum/q_full_adder.py | 6 +++++- quantum/superdense_coding.py | 2 +- sorts/msd_radix_sort.py | 2 +- strings/aho_corasick.py | 2 +- 27 files changed, 44 insertions(+), 57 deletions(-) diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index 2860880db540..7bde886dd558 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -88,12 +88,12 @@ def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]]) solutions[i][j] = 1 return True - lower_flag = (not (i < 0)) and (not (j < 0)) # Check lower bounds + 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])) + block_flag = (not solutions[i][j]) and (not maze[i][j]) if block_flag: # check visited solutions[i][j] = 1 diff --git a/boolean_algebra/not_gate.py b/boolean_algebra/not_gate.py index b41da602d936..eb85e9e44cd3 100644 --- a/boolean_algebra/not_gate.py +++ b/boolean_algebra/not_gate.py @@ -34,4 +34,4 @@ def test_not_gate() -> None: if __name__ == "__main__": print(not_gate(0)) - print(not_gate(1)) + print(not_gate(1)) diff --git a/cellular_automata/nagel_schrekenberg.py b/cellular_automata/nagel_schrekenberg.py index be44761ecf82..3fd6afca0153 100644 --- a/cellular_automata/nagel_schrekenberg.py +++ b/cellular_automata/nagel_schrekenberg.py @@ -45,8 +45,7 @@ def construct_highway( highway = [[-1] * number_of_cells] # Create a highway without any car i = 0 - if initial_speed < 0: - initial_speed = 0 + initial_speed = max(initial_speed, 0) while i < number_of_cells: highway[0][i] = ( randint(0, max_speed) if random_speed else initial_speed diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index f55c9c4286df..806004faa079 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -42,7 +42,7 @@ def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: s = [] for _ in range(len_temp): s.append(temp[k]) - if not (k < 25): + if k >= 25: break k += 1 modalpha.append(s) @@ -52,7 +52,7 @@ def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: k = 0 for j in range(len_temp): for m in modalpha: - if not (len(m) - 1 >= j): + if not len(m) - 1 >= j: break d[alpha[k]] = m[j] if not k < 25: diff --git a/compression/huffman.py b/compression/huffman.py index f619ed82c764..b337ac3ec3ff 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -56,7 +56,7 @@ def traverse_tree(root: Letter | TreeNode, bitstring: str) -> list[Letter]: Recursively traverse the Huffman Tree to set each Letter's bitstring dictionary, and return the list of Letters """ - if type(root) is Letter: + if isinstance(root, Letter): root.bitstring[root.letter] = bitstring return [root] treenode: TreeNode = root # type: ignore diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index 0403624f285a..ecb1876493b0 100644 --- a/data_structures/heap/min_heap.py +++ b/data_structures/heap/min_heap.py @@ -121,7 +121,7 @@ def insert(self, node): self.sift_up(len(self.heap) - 1) def is_empty(self): - return True if len(self.heap) == 0 else False + return len(self.heap) == 0 def decrease_key(self, node, new_value): assert ( diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index fdcebfdad161..c999464ce85e 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -10,7 +10,7 @@ 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.edge_detection import 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 local_binary_pattern as lbp diff --git a/dynamic_programming/fizz_buzz.py b/dynamic_programming/fizz_buzz.py index dd1d21b1075e..e77ab3de7b4b 100644 --- a/dynamic_programming/fizz_buzz.py +++ b/dynamic_programming/fizz_buzz.py @@ -33,10 +33,9 @@ def fizz_buzz(number: int, iterations: int) -> str: ... ValueError: iterations must be defined as integers """ - - if not type(iterations) == int: + if not isinstance(iterations, int): raise ValueError("iterations must be defined as integers") - if not type(number) == int or not number >= 1: + if not isinstance(number, int) or not number >= 1: raise ValueError( """starting number must be and integer and be more than 0""" diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 42eca79a931e..07717fba4172 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -62,8 +62,7 @@ def max_sub_array(nums: list[int]) -> int: current = 0 for i in nums: current += i - if current < 0: - current = 0 + current = max(current, 0) best = max(best, current) return best diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index 43a72b89e3a7..b29485031083 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -167,7 +167,7 @@ def cycle_nodes(self): and not on_the_way_back ): len_stack = len(stack) - 1 - while True and len_stack >= 0: + while len_stack >= 0: if stack[len_stack] == node[1]: anticipating_nodes.add(node[1]) break @@ -220,7 +220,7 @@ def has_cycle(self): and not on_the_way_back ): len_stack_minus_one = len(stack) - 1 - while True and len_stack_minus_one >= 0: + while len_stack_minus_one >= 0: if stack[len_stack_minus_one] == node[1]: anticipating_nodes.add(node[1]) break @@ -392,7 +392,7 @@ def cycle_nodes(self): and not on_the_way_back ): len_stack = len(stack) - 1 - while True and len_stack >= 0: + while len_stack >= 0: if stack[len_stack] == node[1]: anticipating_nodes.add(node[1]) break @@ -445,7 +445,7 @@ def has_cycle(self): and not on_the_way_back ): len_stack_minus_one = len(stack) - 1 - while True and len_stack_minus_one >= 0: + while len_stack_minus_one >= 0: if stack[len_stack_minus_one] == node[1]: anticipating_nodes.add(node[1]) break diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index e16a983932d0..cd8e37b0099b 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -1,4 +1,5 @@ import heapq +import sys import numpy as np @@ -116,7 +117,7 @@ def do_something(back_pointer, goal, start): print(x, end=" ") x = back_pointer[x] print(x) - quit() + sys.exit() def valid(p: TPos): diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 775e0244abb2..ac0398a31a07 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -129,7 +129,7 @@ def component(self, i: int) -> float: input: index (0-indexed) output: the i-th component of the vector. """ - if type(i) is int and -len(self.__components) <= i < len(self.__components): + if isinstance(i, int) and -len(self.__components) <= i < len(self.__components): return self.__components[i] else: raise Exception("index out of range") diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 66535e806c43..3864f6421fcb 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -388,16 +388,10 @@ def _norm(self, data): 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 + return bool(0.0 < self.alphas[index] < self._c) def _is_support(self, index): - if self.alphas[index] > 0: - return True - else: - return False + return bool(self.alphas[index] > 0) @property def unbound(self): diff --git a/maths/find_min.py b/maths/find_min.py index 228205ed7feb..2eac087c6388 100644 --- a/maths/find_min.py +++ b/maths/find_min.py @@ -24,8 +24,7 @@ def find_min(nums: list[int | float]) -> int | float: raise ValueError("find_min() arg is an empty sequence") min_num = nums[0] for num in nums: - if min_num > num: - min_num = num + min_num = min(min_num, num) return min_num diff --git a/maths/kadanes.py b/maths/kadanes.py index b23409e2b978..c2ea53a6cc84 100644 --- a/maths/kadanes.py +++ b/maths/kadanes.py @@ -49,10 +49,8 @@ def kadanes(arr: list) -> int: 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 + max_sum = max(max_sum, max_till_element) + max_till_element = max(max_till_element, 0) return max_sum diff --git a/maths/largest_subarray_sum.py b/maths/largest_subarray_sum.py index 0449e72e64e3..90f92c7127bf 100644 --- a/maths/largest_subarray_sum.py +++ b/maths/largest_subarray_sum.py @@ -11,10 +11,8 @@ def max_sub_array_sum(a: list, size: int = 0): 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 + max_so_far = max(max_so_far, max_ending_here) + max_ending_here = max(max_ending_here, 0) return max_so_far diff --git a/maths/series/geometric_series.py b/maths/series/geometric_series.py index a875ab89a0c5..90c9fe77b733 100644 --- a/maths/series/geometric_series.py +++ b/maths/series/geometric_series.py @@ -52,7 +52,7 @@ def geometric_series( power = 1 multiple = common_ratio_r for _ in range(int(nth_term)): - if series == []: + if not series: series.append(start_term_a) else: power += 1 diff --git a/networking_flow/ford_fulkerson.py b/networking_flow/ford_fulkerson.py index 370e3848222a..716ed508e679 100644 --- a/networking_flow/ford_fulkerson.py +++ b/networking_flow/ford_fulkerson.py @@ -21,7 +21,7 @@ def bfs(graph, s, t, parent): visited[ind] = True parent[ind] = u - return True if visited[t] else False + return visited[t] def ford_fulkerson(graph, source, sink): diff --git a/networking_flow/minimum_cut.py b/networking_flow/minimum_cut.py index 33131315f4e1..164b45f1012a 100644 --- a/networking_flow/minimum_cut.py +++ b/networking_flow/minimum_cut.py @@ -24,7 +24,7 @@ def bfs(graph, s, t, parent): visited[ind] = True parent[ind] = u - return True if visited[t] else False + return visited[t] def mincut(graph, source, sink): diff --git a/other/password.py b/other/password.py index f463c7564536..9a6161af87d7 100644 --- a/other/password.py +++ b/other/password.py @@ -89,13 +89,9 @@ def is_strong_password(password: str, min_length: int = 8) -> bool: num = any(char in digits for char in password) spec_char = any(char in punctuation for char in password) - if upper and lower and num and spec_char: - return True - - else: - # Passwords should contain UPPERCASE, lowerase - # numbers, and special characters - return False + return upper and lower and num and spec_char + # Passwords should contain UPPERCASE, lowerase + # numbers, and special characters def main(): diff --git a/project_euler/problem_025/sol1.py b/project_euler/problem_025/sol1.py index c30a74a43cb0..803464b5d786 100644 --- a/project_euler/problem_025/sol1.py +++ b/project_euler/problem_025/sol1.py @@ -43,7 +43,7 @@ def fibonacci(n: int) -> int: 144 """ - if n == 1 or type(n) is not int: + if n == 1 or not isinstance(n, int): return 0 elif n == 2: return 1 diff --git a/project_euler/problem_036/sol1.py b/project_euler/problem_036/sol1.py index 425c41221395..1d27356ec51e 100644 --- a/project_euler/problem_036/sol1.py +++ b/project_euler/problem_036/sol1.py @@ -32,7 +32,7 @@ def is_palindrome(n: int | str) -> bool: False """ n = str(n) - return True if n == n[::-1] else False + return n == n[::-1] def solution(n: int = 1000000): diff --git a/quantum/q_fourier_transform.py b/quantum/q_fourier_transform.py index d138dfb452ee..07a257579529 100644 --- a/quantum/q_fourier_transform.py +++ b/quantum/q_fourier_transform.py @@ -55,9 +55,9 @@ def quantum_fourier_transform(number_of_qubits: int = 3) -> qiskit.result.counts ... ValueError: number of qubits must be exact integer. """ - if type(number_of_qubits) == str: + if isinstance(number_of_qubits, str): raise TypeError("number of qubits must be a integer.") - if not number_of_qubits > 0: + if number_of_qubits <= 0: raise ValueError("number of qubits must be > 0.") if math.floor(number_of_qubits) != number_of_qubits: raise ValueError("number of qubits must be exact integer.") diff --git a/quantum/q_full_adder.py b/quantum/q_full_adder.py index c6d03d170659..66d93198519e 100644 --- a/quantum/q_full_adder.py +++ b/quantum/q_full_adder.py @@ -60,7 +60,11 @@ def quantum_full_adder( ... ValueError: inputs must be less or equal to 2. """ - if (type(input_1) == str) or (type(input_2) == str) or (type(carry_in) == str): + if ( + isinstance(input_1, str) + or isinstance(input_2, str) + or isinstance(carry_in, str) + ): raise TypeError("inputs must be integers.") if (input_1 < 0) or (input_2 < 0) or (carry_in < 0): diff --git a/quantum/superdense_coding.py b/quantum/superdense_coding.py index 10ebc2d3593c..1087312f9f5d 100644 --- a/quantum/superdense_coding.py +++ b/quantum/superdense_coding.py @@ -53,7 +53,7 @@ def superdense_coding(bit_1: int = 1, bit_2: int = 1) -> qiskit.result.counts.Co ... ValueError: inputs must be less or equal to 1. """ - if (type(bit_1) == str) or (type(bit_2) == str): + if isinstance(bit_1, str) or isinstance(bit_2, str): raise TypeError("inputs must be integers.") if (bit_1 < 0) or (bit_2 < 0): raise ValueError("inputs must be positive.") diff --git a/sorts/msd_radix_sort.py b/sorts/msd_radix_sort.py index 84460e47b440..74ce21762906 100644 --- a/sorts/msd_radix_sort.py +++ b/sorts/msd_radix_sort.py @@ -133,7 +133,7 @@ def _msd_radix_sort_inplace( j = end_index - 1 while i <= j: changed = False - if not ((list_of_ints[i] >> bit_position) & 1): + if not (list_of_ints[i] >> bit_position) & 1: # found zero at the beginning i += 1 changed = True diff --git a/strings/aho_corasick.py b/strings/aho_corasick.py index 25ed649ce645..e32a4ba64fac 100644 --- a/strings/aho_corasick.py +++ b/strings/aho_corasick.py @@ -84,7 +84,7 @@ def search_in(self, string: str) -> dict[str, list[int]]: else: current_state = next_state for key in self.adlist[current_state]["output"]: - if not (key in result): + if key not in result: result[key] = [] result[key].append(i - len(key) + 1) return result From 08c22457058207dc465b9ba9fd95659d33b3f1dd Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 29 Nov 2022 16:56:41 +0100 Subject: [PATCH 2082/2908] Upgrade to flake8 v6 (#8007) * Upgrade to flake8 v6 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .flake8 | 6 ++++-- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 2 ++ compression/huffman.py | 4 ++-- data_structures/binary_tree/non_recursive_segment_tree.py | 2 +- data_structures/binary_tree/segment_tree.py | 6 +++--- machine_learning/sequential_minimum_optimization.py | 2 +- project_euler/problem_107/sol1.py | 1 - 8 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.flake8 b/.flake8 index 2f74f421d020..b68ee8533a61 100644 --- a/.flake8 +++ b/.flake8 @@ -4,5 +4,7 @@ max-line-length = 88 max-complexity = 19 extend-ignore = # Formatting style for `black` - E203 # Whitespace before ':' - W503 # Line break occurred before a binary operator + # E203 is whitespace before ':' + E203, + # W503 is line break occurred before a binary operator + W503 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 324a021ee205..74502b3ea757 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-executables-have-shebangs - id: check-yaml @@ -34,13 +34,13 @@ repos: - --py311-plus - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 # See .flake8 for args additional_dependencies: &flake8-plugins - flake8-bugbear - flake8-builtins - - flake8-broken-line + # - flake8-broken-line - flake8-comprehensions - pep8-naming diff --git a/DIRECTORY.md b/DIRECTORY.md index 83da4b76abca..b3b484f7358f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -375,6 +375,7 @@ * [Articulation Points](graphs/articulation_points.py) * [Basic Graphs](graphs/basic_graphs.py) * [Bellman Ford](graphs/bellman_ford.py) + * [Bi Directional Dijkstra](graphs/bi_directional_dijkstra.py) * [Bidirectional A Star](graphs/bidirectional_a_star.py) * [Bidirectional Breadth First Search](graphs/bidirectional_breadth_first_search.py) * [Boruvka](graphs/boruvka.py) @@ -563,6 +564,7 @@ * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) * [Is Square Free](maths/is_square_free.py) * [Jaccard Similarity](maths/jaccard_similarity.py) + * [Juggler Sequence](maths/juggler_sequence.py) * [Kadanes](maths/kadanes.py) * [Karatsuba](maths/karatsuba.py) * [Krishnamurthy Number](maths/krishnamurthy_number.py) diff --git a/compression/huffman.py b/compression/huffman.py index b337ac3ec3ff..65e5c2f25385 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -32,7 +32,7 @@ def parse_file(file_path: str) -> list[Letter]: if not c: break chars[c] = chars[c] + 1 if c in chars else 1 - return sorted((Letter(c, f) for c, f in chars.items()), key=lambda l: l.freq) + return sorted((Letter(c, f) for c, f in chars.items()), key=lambda x: x.freq) def build_tree(letters: list[Letter]) -> Letter | TreeNode: @@ -47,7 +47,7 @@ def build_tree(letters: list[Letter]) -> Letter | TreeNode: total_freq = left.freq + right.freq node = TreeNode(total_freq, left, right) response.append(node) - response.sort(key=lambda l: l.freq) + response.sort(key=lambda x: x.freq) return response[0] diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 075ff6c912ff..04164e5cba4e 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -106,7 +106,7 @@ def query(self, l: int, r: int) -> T | None: # noqa: E741 l, r = l + self.N, r + self.N res: T | None = None - while l <= r: # noqa: E741 + 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: diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index 949a3ecdd32c..b0580386954a 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -16,7 +16,7 @@ def right(self, idx): return idx * 2 + 1 def build(self, idx, l, r): # noqa: E741 - if l == r: # noqa: E741 + if l == r: self.st[idx] = A[l] else: mid = (l + r) // 2 @@ -33,7 +33,7 @@ def update_recursive(self, idx, l, r, a, b, val): # noqa: E741 """ if r < a or l > b: return True - if l == r: # noqa: E741 + if l == r: self.st[idx] = val return True mid = (l + r) // 2 @@ -51,7 +51,7 @@ def query_recursive(self, idx, l, r, a, b): # noqa: E741 """ if r < a or l > b: return -math.inf - if l >= a and r <= b: # noqa: E741 + if l >= a and r <= b: return self.st[idx] mid = (l + r) // 2 q1 = self.query_recursive(self.left(idx), l, mid, a, b) diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 3864f6421fcb..f5185e1d9576 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -314,7 +314,7 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): 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: # noqa: E741 + if l == h: return None, None # calculate eta diff --git a/project_euler/problem_107/sol1.py b/project_euler/problem_107/sol1.py index b3f5685b95ef..4659eac24bd3 100644 --- a/project_euler/problem_107/sol1.py +++ b/project_euler/problem_107/sol1.py @@ -99,7 +99,6 @@ def solution(filename: str = "p107_network.txt") -> int: """ 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] = {} data: list[str] edge1: int From 361ddaf29e8b8b2a1e6d2107ad41ee9c7f704325 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Nov 2022 21:55:30 +0530 Subject: [PATCH 2083/2908] [pre-commit.ci] pre-commit autoupdate (#8006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0) - [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss From 47bf3f58e04873ef609301b1e654f6ddcc02b0fa Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Tue, 29 Nov 2022 22:07:27 +0400 Subject: [PATCH 2084/2908] fix validation condition and add tests (#7997) * fix validation condition and add tests * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- bit_manipulation/index_of_rightmost_set_bit.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bit_manipulation/index_of_rightmost_set_bit.py b/bit_manipulation/index_of_rightmost_set_bit.py index eb52ea4e63e3..c9c911660b08 100644 --- a/bit_manipulation/index_of_rightmost_set_bit.py +++ b/bit_manipulation/index_of_rightmost_set_bit.py @@ -19,9 +19,17 @@ def get_index_of_rightmost_set_bit(number: int) -> int: Traceback (most recent call last): ... ValueError: Input must be a non-negative integer + >>> get_index_of_rightmost_set_bit('test') + Traceback (most recent call last): + ... + ValueError: Input must be a non-negative integer + >>> get_index_of_rightmost_set_bit(1.25) + Traceback (most recent call last): + ... + ValueError: Input must be a non-negative integer """ - if number < 0 or not isinstance(number, int): + if not isinstance(number, int) or number < 0: raise ValueError("Input must be a non-negative integer") intermediate = number & ~(number - 1) From 6a86fe48671adb90504412acc2589c3ab1b18564 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Tue, 29 Nov 2022 22:28:47 +0400 Subject: [PATCH 2085/2908] Add backtrack word search in matrix (#8005) * add backtracking word search * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * review notes fixes * additional fixes * add tests * small cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * small cleanup 2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update backtracking/word_search.py Co-authored-by: Christian Clauss * Update backtracking/word_search.py Co-authored-by: Christian Clauss * Update backtracking/word_search.py Co-authored-by: Christian Clauss * Update backtracking/word_search.py Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + backtracking/word_search.py | 160 ++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 backtracking/word_search.py diff --git a/DIRECTORY.md b/DIRECTORY.md index b3b484f7358f..51430a1e159e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -33,6 +33,7 @@ * [Rat In Maze](backtracking/rat_in_maze.py) * [Sudoku](backtracking/sudoku.py) * [Sum Of Subsets](backtracking/sum_of_subsets.py) + * [Word Search](backtracking/word_search.py) ## Bit Manipulation * [Binary And Operator](bit_manipulation/binary_and_operator.py) diff --git a/backtracking/word_search.py b/backtracking/word_search.py new file mode 100644 index 000000000000..25d1436be36e --- /dev/null +++ b/backtracking/word_search.py @@ -0,0 +1,160 @@ +""" +Author : Alexander Pantyukhin +Date : November 24, 2022 + +Task: +Given an m x n grid of characters board and a string word, +return true if word exists in the grid. + +The word can be constructed from letters of sequentially adjacent cells, +where adjacent cells are horizontally or vertically neighboring. +The same letter cell may not be used more than once. + +Example: + +Matrix: +--------- +|A|B|C|E| +|S|F|C|S| +|A|D|E|E| +--------- + +Word: +"ABCCED" + +Result: +True + +Implementation notes: Use backtracking approach. +At each point, check all neighbors to try to find the next letter of the word. + +leetcode: https://leetcode.com/problems/word-search/ + +""" + + +def word_exists(board: list[list[str]], word: str) -> bool: + """ + >>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCCED") + True + >>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "SEE") + True + >>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCB") + False + >>> word_exists([["A"]], "A") + True + >>> word_exists([["A","A","A","A","A","A"], + ... ["A","A","A","A","A","A"], + ... ["A","A","A","A","A","A"], + ... ["A","A","A","A","A","A"], + ... ["A","A","A","A","A","B"], + ... ["A","A","A","A","B","A"]], + ... "AAAAAAAAAAAAABB") + False + >>> word_exists([["A"]], 123) + Traceback (most recent call last): + ... + ValueError: The word parameter should be a string of length greater than 0. + >>> word_exists([["A"]], "") + Traceback (most recent call last): + ... + ValueError: The word parameter should be a string of length greater than 0. + >>> word_exists([[]], "AB") + Traceback (most recent call last): + ... + ValueError: The board should be a non empty matrix of single chars strings. + >>> word_exists([], "AB") + Traceback (most recent call last): + ... + ValueError: The board should be a non empty matrix of single chars strings. + >>> word_exists([["A"], [21]], "AB") + Traceback (most recent call last): + ... + ValueError: The board should be a non empty matrix of single chars strings. + """ + + # Validate board + board_error_message = ( + "The board should be a non empty matrix of single chars strings." + ) + if not isinstance(board, list) or len(board) == 0: + raise ValueError(board_error_message) + + for row in board: + if not isinstance(row, list) or len(row) == 0: + raise ValueError(board_error_message) + + for item in row: + if not isinstance(item, str) or len(item) != 1: + raise ValueError(board_error_message) + + # Validate word + if not isinstance(word, str) or len(word) == 0: + raise ValueError( + "The word parameter should be a string of length greater than 0." + ) + + traverts_directions = [(0, 1), (0, -1), (-1, 0), (1, 0)] + len_word = len(word) + len_board = len(board) + len_board_column = len(board[0]) + + # Returns the hash key of matrix indexes. + def get_point_key(row: int, column: int) -> int: + """ + >>> len_board=10 + >>> len_board_column=20 + >>> get_point_key(0, 0) + 200 + """ + + return len_board * len_board_column * row + column + + # Return True if it's possible to search the word suffix + # starting from the word_index. + def exits_word( + row: int, column: int, word_index: int, visited_points_set: set[int] + ) -> bool: + """ + >>> board=[["A"]] + >>> word="B" + >>> exits_word(0, 0, 0, set()) + False + """ + + if board[row][column] != word[word_index]: + return False + + if word_index == len_word - 1: + return True + + for direction in traverts_directions: + next_i = row + direction[0] + next_j = column + direction[1] + if not (0 <= next_i < len_board and 0 <= next_j < len_board_column): + continue + + key = get_point_key(next_i, next_j) + if key in visited_points_set: + continue + + visited_points_set.add(key) + if exits_word(next_i, next_j, word_index + 1, visited_points_set): + return True + + visited_points_set.remove(key) + + return False + + for i in range(len_board): + for j in range(len_board_column): + if exits_word(i, j, 0, {get_point_key(i, j)}): + return True + + return False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5654c6242ed5974fa8f2aa89d9689efa012bdafc Mon Sep 17 00:00:00 2001 From: Akshay Dubey <38462415+itsAkshayDubey@users.noreply.github.com> Date: Tue, 29 Nov 2022 23:59:21 +0530 Subject: [PATCH 2086/2908] algorithm: Hexagonal number (#8003) * feat: Add hexagonal number * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/hexagonal_number.py | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 maths/hexagonal_number.py diff --git a/maths/hexagonal_number.py b/maths/hexagonal_number.py new file mode 100644 index 000000000000..28735c638f80 --- /dev/null +++ b/maths/hexagonal_number.py @@ -0,0 +1,48 @@ +""" +== Hexagonal Number == +The nth hexagonal number hn is the number of distinct dots +in a pattern of dots consisting of the outlines of regular +hexagons with sides up to n dots, when the hexagons are +overlaid so that they share one vertex. + +https://en.wikipedia.org/wiki/Hexagonal_number +""" + +# Author : Akshay Dubey (https://github.com/itsAkshayDubey) + + +def hexagonal(number: int) -> int: + """ + :param number: nth hexagonal number to calculate + :return: the nth hexagonal number + Note: A hexagonal number is only defined for positive integers + >>> hexagonal(4) + 28 + >>> hexagonal(11) + 231 + >>> hexagonal(22) + 946 + >>> hexagonal(0) + Traceback (most recent call last): + ... + ValueError: Input must be a positive integer + >>> hexagonal(-1) + Traceback (most recent call last): + ... + ValueError: Input must be a positive integer + >>> hexagonal(11.0) + Traceback (most recent call last): + ... + TypeError: Input value of [number=11.0] must be an integer + """ + if not isinstance(number, int): + raise TypeError(f"Input value of [number={number}] must be an integer") + if number < 1: + raise ValueError("Input must be a positive integer") + return number * (2 * number - 1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d141fa8838369fafb3b28a8dd825ec1b20d34e03 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 21:34:24 +0100 Subject: [PATCH 2087/2908] [pre-commit.ci] pre-commit autoupdate (#8017) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0) * updating DIRECTORY.md Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 74502b3ea757..3d83499f0e71 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.3.0 hooks: - id: pyupgrade args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 51430a1e159e..382ff3a6fb25 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -561,6 +561,7 @@ * [Greedy Coin Change](maths/greedy_coin_change.py) * [Hamming Numbers](maths/hamming_numbers.py) * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) + * [Hexagonal Number](maths/hexagonal_number.py) * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) * [Is Square Free](maths/is_square_free.py) From b25915adf91cc39c98c597fce1eef9422f4e7d0d Mon Sep 17 00:00:00 2001 From: Aaryan Raj <97806283+iaaryanraj@users.noreply.github.com> Date: Sun, 11 Dec 2022 12:34:04 +0530 Subject: [PATCH 2088/2908] Add algorithm to convert decimal number to its simplest fraction form (#8001) * Added algorithm to convert decimal number to its simplest fraction form * Apply suggested changes --- maths/decimal_to_fraction.py | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 maths/decimal_to_fraction.py diff --git a/maths/decimal_to_fraction.py b/maths/decimal_to_fraction.py new file mode 100644 index 000000000000..9462bafe0171 --- /dev/null +++ b/maths/decimal_to_fraction.py @@ -0,0 +1,48 @@ +def decimal_to_fraction(decimal: int | float | str) -> tuple[int, int]: + """ + Return a decimal number in its simplest fraction form + >>> decimal_to_fraction(2) + (2, 1) + >>> decimal_to_fraction(89.) + (89, 1) + >>> decimal_to_fraction("67") + (67, 1) + >>> decimal_to_fraction("45.0") + (45, 1) + >>> decimal_to_fraction(1.5) + (3, 2) + >>> decimal_to_fraction("6.25") + (25, 4) + >>> decimal_to_fraction("78td") + Traceback (most recent call last): + ValueError: Please enter a valid number + """ + try: + decimal = float(decimal) + except ValueError: + raise ValueError("Please enter a valid number") + fractional_part = decimal - int(decimal) + if fractional_part == 0: + return int(decimal), 1 + else: + number_of_frac_digits = len(str(decimal).split(".")[1]) + numerator = int(decimal * (10**number_of_frac_digits)) + denominator = 10**number_of_frac_digits + divisor, dividend = denominator, numerator + while True: + remainder = dividend % divisor + if remainder == 0: + break + dividend, divisor = divisor, remainder + numerator, denominator = numerator / divisor, denominator / divisor + return int(numerator), int(denominator) + + +if __name__ == "__main__": + print(f"{decimal_to_fraction(2) = }") + print(f"{decimal_to_fraction(89.0) = }") + print(f"{decimal_to_fraction('67') = }") + print(f"{decimal_to_fraction('45.0') = }") + print(f"{decimal_to_fraction(1.5) = }") + print(f"{decimal_to_fraction('6.25') = }") + print(f"{decimal_to_fraction('78td') = }") From 40f165b789e9a2475415768db5acadf63e021e46 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 21:29:50 +0100 Subject: [PATCH 2089/2908] [pre-commit.ci] pre-commit autoupdate (#8026) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0) - [github.com/asottile/pyupgrade: v3.3.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.3.0...v3.3.1) * updating DIRECTORY.md Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d83499f0e71..7cf4bedd7dac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: auto-walrus - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 22.12.0 hooks: - id: black @@ -27,7 +27,7 @@ repos: - --profile=black - repo: https://github.com/asottile/pyupgrade - rev: v3.3.0 + rev: v3.3.1 hooks: - id: pyupgrade args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 382ff3a6fb25..0624eda2c585 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -533,6 +533,7 @@ * [Collatz Sequence](maths/collatz_sequence.py) * [Combinations](maths/combinations.py) * [Decimal Isolate](maths/decimal_isolate.py) + * [Decimal To Fraction](maths/decimal_to_fraction.py) * [Dodecahedron](maths/dodecahedron.py) * [Double Factorial Iterative](maths/double_factorial_iterative.py) * [Double Factorial Recursive](maths/double_factorial_recursive.py) From af8d52092232e1104154b733000716036e668444 Mon Sep 17 00:00:00 2001 From: Roberto Garcia <37519995+rga2@users.noreply.github.com> Date: Wed, 14 Dec 2022 22:10:09 -0600 Subject: [PATCH 2090/2908] Update is_even.py (#8028) --- bit_manipulation/is_even.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bit_manipulation/is_even.py b/bit_manipulation/is_even.py index b7b0841a1427..ba036f35aa1e 100644 --- a/bit_manipulation/is_even.py +++ b/bit_manipulation/is_even.py @@ -11,7 +11,7 @@ def is_even(number: int) -> bool: from the above examples we can observe that for all the odd integers there is always 1 set bit at the end also, 1 in binary can be represented as 001, 00001, or 0000001 - so for any odd integer n => n&1 is always equlas 1 else the integer is even + so for any odd integer n => n&1 is always equals 1 else the integer is even >>> is_even(1) False From 30277f8590a7bf636477fa4c4ad22cedf10588f5 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Thu, 15 Dec 2022 08:11:32 +0400 Subject: [PATCH 2091/2908] add numbers different signs algorithm. (#8008) --- DIRECTORY.md | 1 + bit_manipulation/numbers_different_signs.py | 39 +++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 bit_manipulation/numbers_different_signs.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 0624eda2c585..34ce88a4f2ab 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -50,6 +50,7 @@ * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py) * [Is Even](bit_manipulation/is_even.py) * [Is Power Of Two](bit_manipulation/is_power_of_two.py) + * [Numbers Different Signs](bit_manipulation/numbers_different_signs.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py) diff --git a/bit_manipulation/numbers_different_signs.py b/bit_manipulation/numbers_different_signs.py new file mode 100644 index 000000000000..cf8b6d86f1eb --- /dev/null +++ b/bit_manipulation/numbers_different_signs.py @@ -0,0 +1,39 @@ +""" +Author : Alexander Pantyukhin +Date : November 30, 2022 + +Task: +Given two int numbers. Return True these numbers have opposite signs +or False otherwise. + +Implementation notes: Use bit manipulation. +Use XOR for two numbers. +""" + + +def different_signs(num1: int, num2: int) -> bool: + """ + Return True if numbers have opposite signs False otherwise. + + >>> different_signs(1, -1) + True + >>> different_signs(1, 1) + False + >>> different_signs(1000000000000000000000000000, -1000000000000000000000000000) + True + >>> different_signs(-1000000000000000000000000000, 1000000000000000000000000000) + True + >>> different_signs(50, 278) + False + >>> different_signs(0, 2) + False + >>> different_signs(2, 0) + False + """ + return num1 ^ num2 < 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 3f8b2af14bd3b64b838098f9e1830c0fea926a1a Mon Sep 17 00:00:00 2001 From: Victor Rodrigues da Silva <63797831+VictorRS27@users.noreply.github.com> Date: Sun, 18 Dec 2022 19:26:39 -0300 Subject: [PATCH 2092/2908] Add autoclave cipher (#8029) * Add autoclave cipher * Update autoclave with the given suggestions * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixing errors * Another fixes * Update and rename autoclave.py to autokey.py * Rename gaussian_naive_bayes.py to gaussian_naive_bayes.py.broken.txt * Rename gradient_boosting_regressor.py to gradient_boosting_regressor.py.broken.txt * Rename random_forest_classifier.py to random_forest_classifier.py.broken.txt * Rename random_forest_regressor.py to random_forest_regressor.py.broken.txt * Rename equal_loudness_filter.py to equal_loudness_filter.py.broken.txt Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- ...py => equal_loudness_filter.py.broken.txt} | 0 ciphers/autokey.py | 131 ++++++++++++++++++ ....py => gaussian_naive_bayes.py.broken.txt} | 0 ...gradient_boosting_regressor.py.broken.txt} | 0 ...=> random_forest_classifier.py.broken.txt} | 0 ... => random_forest_regressor.py.broken.txt} | 0 6 files changed, 131 insertions(+) rename audio_filters/{equal_loudness_filter.py => equal_loudness_filter.py.broken.txt} (100%) create mode 100644 ciphers/autokey.py rename machine_learning/{gaussian_naive_bayes.py => gaussian_naive_bayes.py.broken.txt} (100%) rename machine_learning/{gradient_boosting_regressor.py => gradient_boosting_regressor.py.broken.txt} (100%) rename machine_learning/{random_forest_classifier.py => random_forest_classifier.py.broken.txt} (100%) rename machine_learning/{random_forest_regressor.py => random_forest_regressor.py.broken.txt} (100%) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py.broken.txt similarity index 100% rename from audio_filters/equal_loudness_filter.py rename to audio_filters/equal_loudness_filter.py.broken.txt diff --git a/ciphers/autokey.py b/ciphers/autokey.py new file mode 100644 index 000000000000..8683e6d37001 --- /dev/null +++ b/ciphers/autokey.py @@ -0,0 +1,131 @@ +""" +https://en.wikipedia.org/wiki/Autokey_cipher +An autokey cipher (also known as the autoclave cipher) is a cipher that +incorporates the message (the plaintext) into the key. +The key is generated from the message in some automated fashion, +sometimes by selecting certain letters from the text or, more commonly, +by adding a short primer key to the front of the message. +""" + + +def encrypt(plaintext: str, key: str) -> str: + """ + Encrypt a given plaintext (string) and key (string), returning the + encrypted ciphertext. + >>> encrypt("hello world", "coffee") + 'jsqqs avvwo' + >>> encrypt("coffee is good as python", "TheAlgorithms") + 'vvjfpk wj ohvp su ddylsv' + >>> encrypt("coffee is good as python", 2) + Traceback (most recent call last): + ... + TypeError: key must be a string + >>> encrypt("", "TheAlgorithms") + Traceback (most recent call last): + ... + ValueError: plaintext is empty + """ + if not isinstance(plaintext, str): + raise TypeError("plaintext must be a string") + if not isinstance(key, str): + raise TypeError("key must be a string") + + if not plaintext: + raise ValueError("plaintext is empty") + if not key: + raise ValueError("key is empty") + + key += plaintext + plaintext = plaintext.lower() + key = key.lower() + plaintext_iterator = 0 + key_iterator = 0 + ciphertext = "" + while plaintext_iterator < len(plaintext): + if ( + ord(plaintext[plaintext_iterator]) < 97 + or ord(plaintext[plaintext_iterator]) > 122 + ): + ciphertext += plaintext[plaintext_iterator] + plaintext_iterator += 1 + elif ord(key[key_iterator]) < 97 or ord(key[key_iterator]) > 122: + key_iterator += 1 + else: + ciphertext += chr( + ( + (ord(plaintext[plaintext_iterator]) - 97 + ord(key[key_iterator])) + - 97 + ) + % 26 + + 97 + ) + key_iterator += 1 + plaintext_iterator += 1 + return ciphertext + + +def decrypt(ciphertext: str, key: str) -> str: + """ + Decrypt a given ciphertext (string) and key (string), returning the decrypted + ciphertext. + >>> decrypt("jsqqs avvwo", "coffee") + 'hello world' + >>> decrypt("vvjfpk wj ohvp su ddylsv", "TheAlgorithms") + 'coffee is good as python' + >>> decrypt("vvjfpk wj ohvp su ddylsv", "") + Traceback (most recent call last): + ... + ValueError: key is empty + >>> decrypt(527.26, "TheAlgorithms") + Traceback (most recent call last): + ... + TypeError: ciphertext must be a string + """ + if not isinstance(ciphertext, str): + raise TypeError("ciphertext must be a string") + if not isinstance(key, str): + raise TypeError("key must be a string") + + if not ciphertext: + raise ValueError("ciphertext is empty") + if not key: + raise ValueError("key is empty") + + key = key.lower() + ciphertext_iterator = 0 + key_iterator = 0 + plaintext = "" + while ciphertext_iterator < len(ciphertext): + if ( + ord(ciphertext[ciphertext_iterator]) < 97 + or ord(ciphertext[ciphertext_iterator]) > 122 + ): + plaintext += ciphertext[ciphertext_iterator] + else: + plaintext += chr( + (ord(ciphertext[ciphertext_iterator]) - ord(key[key_iterator])) % 26 + + 97 + ) + key += chr( + (ord(ciphertext[ciphertext_iterator]) - ord(key[key_iterator])) % 26 + + 97 + ) + key_iterator += 1 + ciphertext_iterator += 1 + return plaintext + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + operation = int(input("Type 1 to encrypt or 2 to decrypt:")) + if operation == 1: + plaintext = input("Typeplaintext to be encrypted:\n") + key = input("Type the key:\n") + print(encrypt(plaintext, key)) + elif operation == 2: + ciphertext = input("Type the ciphertext to be decrypted:\n") + key = input("Type the key:\n") + print(decrypt(ciphertext, key)) + decrypt("jsqqs avvwo", "coffee") diff --git a/machine_learning/gaussian_naive_bayes.py b/machine_learning/gaussian_naive_bayes.py.broken.txt similarity index 100% rename from machine_learning/gaussian_naive_bayes.py rename to machine_learning/gaussian_naive_bayes.py.broken.txt diff --git a/machine_learning/gradient_boosting_regressor.py b/machine_learning/gradient_boosting_regressor.py.broken.txt similarity index 100% rename from machine_learning/gradient_boosting_regressor.py rename to machine_learning/gradient_boosting_regressor.py.broken.txt diff --git a/machine_learning/random_forest_classifier.py b/machine_learning/random_forest_classifier.py.broken.txt similarity index 100% rename from machine_learning/random_forest_classifier.py rename to machine_learning/random_forest_classifier.py.broken.txt diff --git a/machine_learning/random_forest_regressor.py b/machine_learning/random_forest_regressor.py.broken.txt similarity index 100% rename from machine_learning/random_forest_regressor.py rename to machine_learning/random_forest_regressor.py.broken.txt From d4c5b22424d05d3198dc2e5a49427e929b058ccf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 23:04:34 +0100 Subject: [PATCH 2093/2908] [pre-commit.ci] pre-commit autoupdate (#8037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/MarcoGorelli/auto-walrus: v0.2.1 → v0.2.2](https://github.com/MarcoGorelli/auto-walrus/compare/v0.2.1...v0.2.2) - [github.com/PyCQA/isort: 5.10.1 → v5.11.3](https://github.com/PyCQA/isort/compare/5.10.1...v5.11.3) * updating DIRECTORY.md Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7cf4bedd7dac..0f5fe20a8854 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/MarcoGorelli/auto-walrus - rev: v0.2.1 + rev: v0.2.2 hooks: - id: auto-walrus @@ -20,7 +20,7 @@ repos: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: v5.11.3 hooks: - id: isort args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 34ce88a4f2ab..bec857a38b69 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -14,7 +14,6 @@ ## Audio Filters * [Butterworth Filter](audio_filters/butterworth_filter.py) - * [Equal Loudness Filter](audio_filters/equal_loudness_filter.py) * [Iir Filter](audio_filters/iir_filter.py) * [Show Response](audio_filters/show_response.py) @@ -79,6 +78,7 @@ * [A1Z26](ciphers/a1z26.py) * [Affine Cipher](ciphers/affine_cipher.py) * [Atbash](ciphers/atbash.py) + * [Autokey](ciphers/autokey.py) * [Baconian Cipher](ciphers/baconian_cipher.py) * [Base16](ciphers/base16.py) * [Base32](ciphers/base32.py) @@ -475,8 +475,6 @@ * [Decision Tree](machine_learning/decision_tree.py) * Forecasting * [Run](machine_learning/forecasting/run.py) - * [Gaussian Naive Bayes](machine_learning/gaussian_naive_bayes.py) - * [Gradient Boosting Regressor](machine_learning/gradient_boosting_regressor.py) * [Gradient Descent](machine_learning/gradient_descent.py) * [K Means Clust](machine_learning/k_means_clust.py) * [K Nearest Neighbours](machine_learning/k_nearest_neighbours.py) @@ -490,8 +488,6 @@ * [Lstm Prediction](machine_learning/lstm/lstm_prediction.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polymonial Regression](machine_learning/polymonial_regression.py) - * [Random Forest Classifier](machine_learning/random_forest_classifier.py) - * [Random Forest Regressor](machine_learning/random_forest_regressor.py) * [Scoring Functions](machine_learning/scoring_functions.py) * [Self Organizing Map](machine_learning/self_organizing_map.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) From 79ef431cec53020709268507b6515ff1e7e47680 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 24 Dec 2022 17:57:28 +0300 Subject: [PATCH 2094/2908] Reduce the complexity of sorts/merge_insertion_sort.py (#7954) * Reduce the complexity of sorts/merge_insertion_sort.py * Add tests * Lower the --max-complexity threshold in the file .flake8 --- .flake8 | 2 +- sorts/merge_insertion_sort.py | 79 +++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/.flake8 b/.flake8 index b68ee8533a61..77ca7a328a77 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] max-line-length = 88 # max-complexity should be 10 -max-complexity = 19 +max-complexity = 17 extend-ignore = # Formatting style for `black` # E203 is whitespace before ':' diff --git a/sorts/merge_insertion_sort.py b/sorts/merge_insertion_sort.py index ecaa535457f4..4a5bdea0a33f 100644 --- a/sorts/merge_insertion_sort.py +++ b/sorts/merge_insertion_sort.py @@ -14,6 +14,53 @@ from __future__ import annotations +def binary_search_insertion(sorted_list, item): + """ + >>> binary_search_insertion([1, 2, 7, 9, 10], 4) + [1, 2, 4, 7, 9, 10] + """ + 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 merge(left, right): + """ + >>> merge([[1, 6], [9, 10]], [[2, 3], [4, 5], [7, 8]]) + [[1, 6], [2, 3], [4, 5], [7, 8], [9, 10]] + """ + 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 + + +def sortlist_2d(list_2d): + """ + >>> sortlist_2d([[9, 10], [1, 6], [7, 8], [2, 3], [4, 5]]) + [[1, 6], [2, 3], [4, 5], [7, 8], [9, 10]] + """ + 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:])) + + def merge_insertion_sort(collection: list[int]) -> list[int]: """Pure implementation of merge-insertion sort algorithm in Python @@ -38,38 +85,6 @@ def merge_insertion_sort(collection: list[int]) -> list[int]: True """ - 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 From 27d56ba3932d2ca2951a45232790794b2b0838d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Dec 2022 22:02:50 +0100 Subject: [PATCH 2095/2908] [pre-commit.ci] pre-commit autoupdate (#8047) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/PyCQA/isort: v5.11.3 → 5.11.4](https://github.com/PyCQA/isort/compare/v5.11.3...5.11.4) * Update .flake8 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .flake8 | 2 +- .pre-commit-config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.flake8 b/.flake8 index 77ca7a328a77..b68ee8533a61 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] max-line-length = 88 # max-complexity should be 10 -max-complexity = 17 +max-complexity = 19 extend-ignore = # Formatting style for `black` # E203 is whitespace before ':' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f5fe20a8854..8eb6d297e831 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - id: black - repo: https://github.com/PyCQA/isort - rev: v5.11.3 + rev: 5.11.4 hooks: - id: isort args: From 90686e39b9fd3b599a8cd77810e0fdbb74eae064 Mon Sep 17 00:00:00 2001 From: Lucia Harcekova <119792460+LuciaHarcekova@users.noreply.github.com> Date: Wed, 28 Dec 2022 17:34:35 +0000 Subject: [PATCH 2096/2908] Add LZ77 compression algorithm (#8059) * - add "lz77_compressor" class with compress and decompress methods using LZ77 compression algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * - use "list" instead "List", formatting * - fix spelling * - add Python type hints * - add 'Token' class to represent triplet (offset, length, indicator) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * - add test, hange type rom List to list * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * - remove extra import * - remove extra types in comments * - better test * - edit comments * - add return types * - add tests for __str__ and __repr__ * Update lz77.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- compression/lz77.py | 227 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 compression/lz77.py diff --git a/compression/lz77.py b/compression/lz77.py new file mode 100644 index 000000000000..7c1a6f6a4c19 --- /dev/null +++ b/compression/lz77.py @@ -0,0 +1,227 @@ +""" +LZ77 compression algorithm +- lossless data compression published in papers by Abraham Lempel and Jacob Ziv in 1977 +- also known as LZ1 or sliding-window compression +- form the basis for many variations including LZW, LZSS, LZMA and others + +It uses a “sliding window” method. Within the sliding window we have: + - search buffer + - look ahead buffer +len(sliding_window) = len(search_buffer) + len(look_ahead_buffer) + +LZ77 manages a dictionary that uses triples composed of: + - Offset into search buffer, it's the distance between the start of a phrase and + the beginning of a file. + - Length of the match, it's the number of characters that make up a phrase. + - The indicator is represented by a character that is going to be encoded next. + +As a file is parsed, the dictionary is dynamically updated to reflect the compressed +data contents and size. + +Examples: +"cabracadabrarrarrad" <-> [(0, 0, 'c'), (0, 0, 'a'), (0, 0, 'b'), (0, 0, 'r'), + (3, 1, 'c'), (2, 1, 'd'), (7, 4, 'r'), (3, 5, 'd')] +"ababcbababaa" <-> [(0, 0, 'a'), (0, 0, 'b'), (2, 2, 'c'), (4, 3, 'a'), (2, 2, 'a')] +"aacaacabcabaaac" <-> [(0, 0, 'a'), (1, 1, 'c'), (3, 4, 'b'), (3, 3, 'a'), (1, 2, 'c')] + +Sources: +en.wikipedia.org/wiki/LZ77_and_LZ78 +""" + + +from dataclasses import dataclass + +__version__ = "0.1" +__author__ = "Lucia Harcekova" + + +@dataclass +class Token: + """ + Dataclass representing triplet called token consisting of length, offset + and indicator. This triplet is used during LZ77 compression. + """ + + offset: int + length: int + indicator: str + + def __repr__(self) -> str: + """ + >>> token = Token(1, 2, "c") + >>> repr(token) + '(1, 2, c)' + >>> str(token) + '(1, 2, c)' + """ + return f"({self.offset}, {self.length}, {self.indicator})" + + +class LZ77Compressor: + """ + Class containing compress and decompress methods using LZ77 compression algorithm. + """ + + def __init__(self, window_size: int = 13, lookahead_buffer_size: int = 6) -> None: + self.window_size = window_size + self.lookahead_buffer_size = lookahead_buffer_size + self.search_buffer_size = self.window_size - self.lookahead_buffer_size + + def compress(self, text: str) -> list[Token]: + """ + Compress the given string text using LZ77 compression algorithm. + + Args: + text: string to be compressed + + Returns: + output: the compressed text as a list of Tokens + + >>> lz77_compressor = LZ77Compressor() + >>> str(lz77_compressor.compress("ababcbababaa")) + '[(0, 0, a), (0, 0, b), (2, 2, c), (4, 3, a), (2, 2, a)]' + >>> str(lz77_compressor.compress("aacaacabcabaaac")) + '[(0, 0, a), (1, 1, c), (3, 4, b), (3, 3, a), (1, 2, c)]' + """ + + output = [] + search_buffer = "" + + # while there are still characters in text to compress + while text: + + # find the next encoding phrase + # - triplet with offset, length, indicator (the next encoding character) + token = self._find_encoding_token(text, search_buffer) + + # update the search buffer: + # - add new characters from text into it + # - check if size exceed the max search buffer size, if so, drop the + # oldest elements + search_buffer += text[: token.length + 1] + if len(search_buffer) > self.search_buffer_size: + search_buffer = search_buffer[-self.search_buffer_size :] + + # update the text + text = text[token.length + 1 :] + + # append the token to output + output.append(token) + + return output + + def decompress(self, tokens: list[Token]) -> str: + """ + Convert the list of tokens into an output string. + + Args: + tokens: list containing triplets (offset, length, char) + + Returns: + output: decompressed text + + Tests: + >>> lz77_compressor = LZ77Compressor() + >>> lz77_compressor.decompress([Token(0, 0, 'c'), Token(0, 0, 'a'), + ... Token(0, 0, 'b'), Token(0, 0, 'r'), Token(3, 1, 'c'), + ... Token(2, 1, 'd'), Token(7, 4, 'r'), Token(3, 5, 'd')]) + 'cabracadabrarrarrad' + >>> lz77_compressor.decompress([Token(0, 0, 'a'), Token(0, 0, 'b'), + ... Token(2, 2, 'c'), Token(4, 3, 'a'), Token(2, 2, 'a')]) + 'ababcbababaa' + >>> lz77_compressor.decompress([Token(0, 0, 'a'), Token(1, 1, 'c'), + ... Token(3, 4, 'b'), Token(3, 3, 'a'), Token(1, 2, 'c')]) + 'aacaacabcabaaac' + """ + + output = "" + + for token in tokens: + for _ in range(token.length): + output += output[-token.offset] + output += token.indicator + + return output + + def _find_encoding_token(self, text: str, search_buffer: str) -> Token: + """Finds the encoding token for the first character in the text. + + Tests: + >>> lz77_compressor = LZ77Compressor() + >>> lz77_compressor._find_encoding_token("abrarrarrad", "abracad").offset + 7 + >>> lz77_compressor._find_encoding_token("adabrarrarrad", "cabrac").length + 1 + >>> lz77_compressor._find_encoding_token("abc", "xyz").offset + 0 + >>> lz77_compressor._find_encoding_token("", "xyz").offset + Traceback (most recent call last): + ... + ValueError: We need some text to work with. + >>> lz77_compressor._find_encoding_token("abc", "").offset + 0 + """ + + if not text: + raise ValueError("We need some text to work with.") + + # Initialise result parameters to default values + length, offset = 0, 0 + + if not search_buffer: + return Token(offset, length, text[length]) + + for i, character in enumerate(search_buffer): + found_offset = len(search_buffer) - i + if character == text[0]: + found_length = self._match_length_from_index(text, search_buffer, 0, i) + # if the found length is bigger than the current or if it's equal, + # which means it's offset is smaller: update offset and length + if found_length >= length: + offset, length = found_offset, found_length + + return Token(offset, length, text[length]) + + def _match_length_from_index( + self, text: str, window: str, text_index: int, window_index: int + ) -> int: + """Calculate the longest possible match of text and window characters from + text_index in text and window_index in window. + + Args: + text: _description_ + window: sliding window + text_index: index of character in text + window_index: index of character in sliding window + + Returns: + The maximum match between text and window, from given indexes. + + Tests: + >>> lz77_compressor = LZ77Compressor(13, 6) + >>> lz77_compressor._match_length_from_index("rarrad", "adabrar", 0, 4) + 5 + >>> lz77_compressor._match_length_from_index("adabrarrarrad", + ... "cabrac", 0, 1) + 1 + """ + if not text or text[text_index] != window[window_index]: + return 0 + return 1 + self._match_length_from_index( + text, window + text[text_index], text_index + 1, window_index + 1 + ) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + # Initialize compressor class + lz77_compressor = LZ77Compressor(window_size=13, lookahead_buffer_size=6) + + # Example + TEXT = "cabracadabrarrarrad" + compressed_text = lz77_compressor.compress(TEXT) + print(lz77_compressor.compress("ababcbababaa")) + decompressed_text = lz77_compressor.decompress(compressed_text) + assert decompressed_text == TEXT, "The LZ77 algorithm returned the invalid result." From b72d0681ec8fd6c02ee10ba04bae3fe97ffaebc6 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Thu, 29 Dec 2022 09:06:26 -0800 Subject: [PATCH 2097/2908] Remove extra imports in gamma.py doctests (#8060) * Refactor bottom-up function to be class method * Add type hints * Update convolve function namespace * Remove depreciated np.float * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Renamed function for consistency * updating DIRECTORY.md * Remove extra imports in gamma.py doctests Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Chris O <46587501+ChrisO345@users.noreply.github.com> --- maths/gamma.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/maths/gamma.py b/maths/gamma.py index 69cd819ef186..d5debc58764b 100644 --- a/maths/gamma.py +++ b/maths/gamma.py @@ -11,42 +11,27 @@ def gamma(num: float) -> float: 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(.99999999 < gamma(i) / math_gamma(i) <= 1.000000001 ... 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") From c6223c71d82c7ba57f3de9eed23963ec96de01bb Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Fri, 30 Dec 2022 09:47:40 +0400 Subject: [PATCH 2098/2908] add word_break dynamic approach up -> down. (#8039) * add word_break dynamic approach up -> down. * updating DIRECTORY.md * Update word_break.py fix review notes. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update word_break.py fix review notes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix review notes * add trie type * Update word_break.py add typing Any to trie. * Update dynamic_programming/word_break.py Co-authored-by: Caeden Perelli-Harris * Update dynamic_programming/word_break.py Co-authored-by: Christian Clauss * Update dynamic_programming/word_break.py Co-authored-by: Christian Clauss * Update dynamic_programming/word_break.py Co-authored-by: Christian Clauss * Update dynamic_programming/word_break.py Co-authored-by: Christian Clauss * fix review notes Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + dynamic_programming/word_break.py | 111 ++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 dynamic_programming/word_break.py diff --git a/DIRECTORY.md b/DIRECTORY.md index bec857a38b69..3437df12cbf5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -328,6 +328,7 @@ * [Subset Generation](dynamic_programming/subset_generation.py) * [Sum Of Subset](dynamic_programming/sum_of_subset.py) * [Viterbi](dynamic_programming/viterbi.py) + * [Word Break](dynamic_programming/word_break.py) ## Electronics * [Builtin Voltage](electronics/builtin_voltage.py) diff --git a/dynamic_programming/word_break.py b/dynamic_programming/word_break.py new file mode 100644 index 000000000000..642ea0edf40d --- /dev/null +++ b/dynamic_programming/word_break.py @@ -0,0 +1,111 @@ +""" +Author : Alexander Pantyukhin +Date : December 12, 2022 + +Task: +Given a string and a list of words, return true if the string can be +segmented into a space-separated sequence of one or more words. + +Note that the same word may be reused +multiple times in the segmentation. + +Implementation notes: Trie + Dynamic programming up -> down. +The Trie will be used to store the words. It will be useful for scanning +available words for the current position in the string. + +Leetcode: +https://leetcode.com/problems/word-break/description/ + +Runtime: O(n * n) +Space: O(n) +""" + +from functools import lru_cache +from typing import Any + + +def word_break(string: str, words: list[str]) -> bool: + """ + Return True if numbers have opposite signs False otherwise. + + >>> word_break("applepenapple", ["apple","pen"]) + True + >>> word_break("catsandog", ["cats","dog","sand","and","cat"]) + False + >>> word_break("cars", ["car","ca","rs"]) + True + >>> word_break('abc', []) + False + >>> word_break(123, ['a']) + Traceback (most recent call last): + ... + ValueError: the string should be not empty string + >>> word_break('', ['a']) + Traceback (most recent call last): + ... + ValueError: the string should be not empty string + >>> word_break('abc', [123]) + Traceback (most recent call last): + ... + ValueError: the words should be a list of non-empty strings + >>> word_break('abc', ['']) + Traceback (most recent call last): + ... + ValueError: the words should be a list of non-empty strings + """ + + # Validation + if not isinstance(string, str) or len(string) == 0: + raise ValueError("the string should be not empty string") + + if not isinstance(words, list) or not all( + isinstance(item, str) and len(item) > 0 for item in words + ): + raise ValueError("the words should be a list of non-empty strings") + + # Build trie + trie: dict[str, Any] = {} + word_keeper_key = "WORD_KEEPER" + + for word in words: + trie_node = trie + for c in word: + if c not in trie_node: + trie_node[c] = {} + + trie_node = trie_node[c] + + trie_node[word_keeper_key] = True + + len_string = len(string) + + # Dynamic programming method + @lru_cache(maxsize=None) + def is_breakable(index: int) -> bool: + """ + >>> string = 'a' + >>> is_breakable(1) + True + """ + if index == len_string: + return True + + trie_node = trie + for i in range(index, len_string): + trie_node = trie_node.get(string[i], None) + + if trie_node is None: + return False + + if trie_node.get(word_keeper_key, False) and is_breakable(i + 1): + return True + + return False + + return is_breakable(0) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d29afca93b278e7885f2395c1640aa90d109cc12 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 1 Jan 2023 05:30:14 -0800 Subject: [PATCH 2099/2908] Fix get_top_billioners.py file name typo (#8066) --- .../{get_top_billioners.py => get_top_billionaires.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web_programming/{get_top_billioners.py => get_top_billionaires.py} (100%) diff --git a/web_programming/get_top_billioners.py b/web_programming/get_top_billionaires.py similarity index 100% rename from web_programming/get_top_billioners.py rename to web_programming/get_top_billionaires.py From 7c1d23d4485904634a6755d5978d406be534421d Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 1 Jan 2023 17:10:59 -0800 Subject: [PATCH 2100/2908] Change prime_sieve_eratosthenes.py to return list (#8062) --- maths/prime_sieve_eratosthenes.py | 35 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/maths/prime_sieve_eratosthenes.py b/maths/prime_sieve_eratosthenes.py index 3a3c55085218..32eef9165bba 100644 --- a/maths/prime_sieve_eratosthenes.py +++ b/maths/prime_sieve_eratosthenes.py @@ -1,10 +1,10 @@ """ Sieve of Eratosthenes -Input : n =10 +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 @@ -12,34 +12,43 @@ """ -def prime_sieve_eratosthenes(num): +def prime_sieve_eratosthenes(num: int) -> list[int]: """ - print the prime numbers up to n + 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] + >>> prime_sieve_eratosthenes(2) + [2] + >>> prime_sieve_eratosthenes(1) + [] + >>> prime_sieve_eratosthenes(-1) + Traceback (most recent call last): + ... + ValueError: Input must be a positive integer """ - primes = [True for i in range(num + 1)] - p = 2 + if num <= 0: + raise ValueError("Input must be a positive integer") + + primes = [True] * (num + 1) + p = 2 while p * p <= num: if primes[p]: 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=",") + return [prime for prime in range(2, num + 1) if primes[prime]] if __name__ == "__main__": import doctest doctest.testmod() - num = int(input()) - prime_sieve_eratosthenes(num) + user_num = int(input("Enter a positive integer: ").strip()) + print(prime_sieve_eratosthenes(user_num)) From 725731c8d289f742bfde3f159a538a47d19c27dc Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 2 Jan 2023 05:07:39 -0800 Subject: [PATCH 2101/2908] Refactor `local_weighted_learning.py` to use `np.array` (#8069) * updating DIRECTORY.md * Format local_weighted_learning.py doctests for clarity * Refactor local_weighted_learning.py to use np.array instead of np.mat The np.matrix class is planned to be eventually depreciated in favor of np.array, and current use of the class raises warnings in pytest * Update local_weighted_learning.py documentation Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 +- .../local_weighted_learning.py | 116 ++++++++++-------- 2 files changed, 68 insertions(+), 51 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 3437df12cbf5..5ce9dca74c06 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -123,6 +123,7 @@ * [Huffman](compression/huffman.py) * [Lempel Ziv](compression/lempel_ziv.py) * [Lempel Ziv Decompress](compression/lempel_ziv_decompress.py) + * [Lz77](compression/lz77.py) * [Peak Signal To Noise Ratio](compression/peak_signal_to_noise_ratio.py) * [Run Length Encoding](compression/run_length_encoding.py) @@ -1162,7 +1163,7 @@ * [Get Amazon Product Data](web_programming/get_amazon_product_data.py) * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](web_programming/get_imdbtop.py) - * [Get Top Billioners](web_programming/get_top_billioners.py) + * [Get Top Billionaires](web_programming/get_top_billionaires.py) * [Get Top Hn Posts](web_programming/get_top_hn_posts.py) * [Get User Tweets](web_programming/get_user_tweets.py) * [Giphy](web_programming/giphy.py) diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index df03fe0a178d..6260e9ac6bfe 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -1,76 +1,86 @@ -# Required imports to run this file import matplotlib.pyplot as plt import numpy as np -# weighted matrix -def weighted_matrix(point: np.mat, training_data_x: np.mat, bandwidth: float) -> np.mat: +def weighted_matrix( + point: np.array, training_data_x: np.array, bandwidth: float +) -> np.array: """ - Calculate the weight for every point in the - data set. It takes training_point , query_point, and tau - Here Tau is not a fixed value it can be varied depends on output. - tau --> bandwidth - xmat -->Training data - point --> the x where we want to make predictions - >>> weighted_matrix(np.array([1., 1.]),np.mat([[16.99, 10.34], [21.01,23.68], - ... [24.59,25.69]]), 0.6) - matrix([[1.43807972e-207, 0.00000000e+000, 0.00000000e+000], - [0.00000000e+000, 0.00000000e+000, 0.00000000e+000], - [0.00000000e+000, 0.00000000e+000, 0.00000000e+000]]) + Calculate the weight for every point in the data set. + point --> the x value at which we want to make predictions + >>> weighted_matrix( + ... np.array([1., 1.]), + ... np.array([[16.99, 10.34], [21.01,23.68], [24.59,25.69]]), + ... 0.6 + ... ) + array([[1.43807972e-207, 0.00000000e+000, 0.00000000e+000], + [0.00000000e+000, 0.00000000e+000, 0.00000000e+000], + [0.00000000e+000, 0.00000000e+000, 0.00000000e+000]]) """ - # m is the number of training samples - m, n = np.shape(training_data_x) - # Initializing weights as identity matrix - weights = np.mat(np.eye(m)) + m, _ = np.shape(training_data_x) # m is the number of training samples + weights = np.eye(m) # Initializing weights as identity matrix + # calculating weights for all training examples [x(i)'s] for j in range(m): diff = point - training_data_x[j] - weights[j, j] = np.exp(diff * diff.T / (-2.0 * bandwidth**2)) + weights[j, j] = np.exp(diff @ diff.T / (-2.0 * bandwidth**2)) return weights def local_weight( - point: np.mat, training_data_x: np.mat, training_data_y: np.mat, bandwidth: float -) -> np.mat: + point: np.array, + training_data_x: np.array, + training_data_y: np.array, + bandwidth: float, +) -> np.array: """ Calculate the local weights using the weight_matrix function on training data. Return the weighted matrix. - >>> local_weight(np.array([1., 1.]),np.mat([[16.99, 10.34], [21.01,23.68], - ... [24.59,25.69]]),np.mat([[1.01, 1.66, 3.5]]), 0.6) - matrix([[0.00873174], - [0.08272556]]) + >>> local_weight( + ... np.array([1., 1.]), + ... np.array([[16.99, 10.34], [21.01,23.68], [24.59,25.69]]), + ... np.array([[1.01, 1.66, 3.5]]), + ... 0.6 + ... ) + array([[0.00873174], + [0.08272556]]) """ weight = weighted_matrix(point, training_data_x, bandwidth) - w = (training_data_x.T * (weight * training_data_x)).I * ( - training_data_x.T * weight * training_data_y.T + w = np.linalg.inv(training_data_x.T @ (weight @ training_data_x)) @ ( + training_data_x.T @ weight @ training_data_y.T ) return w def local_weight_regression( - training_data_x: np.mat, training_data_y: np.mat, bandwidth: float -) -> np.mat: + training_data_x: np.array, training_data_y: np.array, bandwidth: float +) -> np.array: """ - Calculate predictions for each data point on axis. - >>> local_weight_regression(np.mat([[16.99, 10.34], [21.01,23.68], - ... [24.59,25.69]]),np.mat([[1.01, 1.66, 3.5]]), 0.6) + Calculate predictions for each data point on axis + >>> local_weight_regression( + ... np.array([[16.99, 10.34], [21.01, 23.68], [24.59, 25.69]]), + ... np.array([[1.01, 1.66, 3.5]]), + ... 0.6 + ... ) array([1.07173261, 1.65970737, 3.50160179]) """ - m, n = np.shape(training_data_x) + m, _ = np.shape(training_data_x) ypred = np.zeros(m) for i, item in enumerate(training_data_x): - ypred[i] = item * local_weight( + ypred[i] = item @ local_weight( item, training_data_x, training_data_y, bandwidth ) return ypred -def load_data(dataset_name: str, cola_name: str, colb_name: str) -> np.mat: +def load_data( + dataset_name: str, cola_name: str, colb_name: str +) -> tuple[np.array, np.array, np.array, np.array]: """ - Function used for loading data from the seaborn splitting into x and y points + Load data from seaborn and split it into x and y points """ import seaborn as sns @@ -78,23 +88,25 @@ def load_data(dataset_name: str, cola_name: str, colb_name: str) -> np.mat: col_a = np.array(data[cola_name]) # total_bill col_b = np.array(data[colb_name]) # tip - mcol_a = np.mat(col_a) - mcol_b = np.mat(col_b) + mcol_a = col_a.copy() + mcol_b = col_b.copy() - m = np.shape(mcol_b)[1] - one = np.ones((1, m), dtype=int) + one = np.ones(np.shape(mcol_b)[0], dtype=int) - # horizontal stacking - training_data_x = np.hstack((one.T, mcol_a.T)) + # pairing elements of one and mcol_a + training_data_x = np.column_stack((one, mcol_a)) return training_data_x, mcol_b, col_a, col_b -def get_preds(training_data_x: np.mat, mcol_b: np.mat, tau: float) -> np.ndarray: +def get_preds(training_data_x: np.array, mcol_b: np.array, tau: float) -> np.array: """ Get predictions with minimum error for each training data - >>> get_preds(np.mat([[16.99, 10.34], [21.01,23.68], - ... [24.59,25.69]]),np.mat([[1.01, 1.66, 3.5]]), 0.6) + >>> get_preds( + ... np.array([[16.99, 10.34], [21.01, 23.68], [24.59, 25.69]]), + ... np.array([[1.01, 1.66, 3.5]]), + ... 0.6 + ... ) array([1.07173261, 1.65970737, 3.50160179]) """ ypred = local_weight_regression(training_data_x, mcol_b, tau) @@ -102,15 +114,15 @@ def get_preds(training_data_x: np.mat, mcol_b: np.mat, tau: float) -> np.ndarray def plot_preds( - training_data_x: np.mat, - predictions: np.ndarray, - col_x: np.ndarray, - col_y: np.ndarray, + training_data_x: np.array, + predictions: np.array, + col_x: np.array, + col_y: np.array, cola_name: str, colb_name: str, ) -> plt.plot: """ - This function used to plot predictions and display the graph + Plot predictions and display the graph """ xsort = training_data_x.copy() xsort.sort(axis=0) @@ -128,6 +140,10 @@ def plot_preds( if __name__ == "__main__": + import doctest + + doctest.testmod() + training_data_x, mcol_b, col_a, col_b = load_data("tips", "total_bill", "tip") predictions = get_preds(training_data_x, mcol_b, 0.5) plot_preds(training_data_x, predictions, col_a, col_b, "total_bill", "tip") From 9f041e9cc82dab21401359d4cfa1b966fc30ddc4 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 2 Jan 2023 05:15:14 -0800 Subject: [PATCH 2102/2908] Refactor `sierpinski_triangle.py` (#8068) * updating DIRECTORY.md * Update sierpinski_triangle.py header doc * Remove unused PROGNAME var in sierpinski_triangle.py The PROGNAME var was used to print an image description in the reference code that this implementation was taken from, but it's entirely unused here * Refactor triangle() function to not use list of vertices Since the number of vertices is always fixed at 3, there's no need to pass in the vertices as a list, and it's clearer to give the vertices distinct names rather than index them from the list * Refactor sierpinski_triangle.py to use tuples Tuples make more sense than lists for storing coordinate pairs * Flip if-statement condition in sierpinski_triangle.py to avoid nesting * Add type hints to sierpinski_triangle.py * Add doctests to sierpinski_triangle.py * Fix return types in doctests * Update fractals/sierpinski_triangle.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- fractals/sierpinski_triangle.py | 114 +++++++++++++++++--------------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/fractals/sierpinski_triangle.py b/fractals/sierpinski_triangle.py index 084f6661f425..c28ec00b27fe 100644 --- a/fractals/sierpinski_triangle.py +++ b/fractals/sierpinski_triangle.py @@ -1,76 +1,84 @@ -#!/usr/bin/python - -"""Author Anurag Kumar | anuragkumarak95@gmail.com | git/anuragkumarak95 - -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. +""" +Author Anurag Kumar | anuragkumarak95@gmail.com | git/anuragkumarak95 -Requirements(pip): - - turtle +Simple example of fractal generation using recursion. -Python: - - 2.6 +What is the Sierpiński Triangle? + The Sierpiński triangle (sometimes spelled Sierpinski), also called the +Sierpiński gasket or Sierpiński sieve, is a fractal 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—that is, it is a mathematically +generated pattern that is reproducible at any magnification or reduction. It is +named after the Polish mathematician Wacław Sierpiński, but appeared as a +decorative pattern many centuries before the work of Sierpiński. -Usage: - - $python sierpinski_triangle.py -Credits: This code was written by editing the code from -https://www.riannetrujillo.com/blog/python-fractal/ +Usage: python sierpinski_triangle.py +Credits: + The above description is taken from + https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle + This code was written by editing the code from + https://www.riannetrujillo.com/blog/python-fractal/ """ import sys import turtle -PROGNAME = "Sierpinski Triangle" - -points = [[-175, -125], [0, 175], [175, -125]] # size of triangle - - -def get_mid(p1, p2): - return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2) # find midpoint - - -def triangle(points, depth): +def get_mid(p1: tuple[float, float], p2: tuple[float, float]) -> tuple[float, float]: + """ + Find the midpoint of two points + + >>> get_mid((0, 0), (2, 2)) + (1.0, 1.0) + >>> get_mid((-3, -3), (3, 3)) + (0.0, 0.0) + >>> get_mid((1, 0), (3, 2)) + (2.0, 1.0) + >>> get_mid((0, 0), (1, 1)) + (0.5, 0.5) + >>> get_mid((0, 0), (0, 0)) + (0.0, 0.0) + """ + return (p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2 + + +def triangle( + vertex1: tuple[float, float], + vertex2: tuple[float, float], + vertex3: tuple[float, float], + depth: int, +) -> None: + """ + Recursively draw the Sierpinski triangle given the vertices of the triangle + and the recursion depth + """ my_pen.up() - my_pen.goto(points[0][0], points[0][1]) + my_pen.goto(vertex1[0], vertex1[1]) my_pen.down() - my_pen.goto(points[1][0], points[1][1]) - my_pen.goto(points[2][0], points[2][1]) - my_pen.goto(points[0][0], points[0][1]) + my_pen.goto(vertex2[0], vertex2[1]) + my_pen.goto(vertex3[0], vertex3[1]) + my_pen.goto(vertex1[0], vertex1[1]) - if depth > 0: - triangle( - [points[0], get_mid(points[0], points[1]), get_mid(points[0], points[2])], - depth - 1, - ) - triangle( - [points[1], get_mid(points[0], points[1]), get_mid(points[1], points[2])], - depth - 1, - ) - triangle( - [points[2], get_mid(points[2], points[1]), get_mid(points[0], points[2])], - depth - 1, - ) + if depth == 0: + return + + triangle(vertex1, get_mid(vertex1, vertex2), get_mid(vertex1, vertex3), depth - 1) + triangle(vertex2, get_mid(vertex1, vertex2), get_mid(vertex2, vertex3), depth - 1) + triangle(vertex3, get_mid(vertex3, vertex2), get_mid(vertex1, vertex3), depth - 1) if __name__ == "__main__": if len(sys.argv) != 2: raise ValueError( - "right format for using this script: " - "$python fractals.py " + "Correct format for using this script: " + "python fractals.py " ) my_pen = turtle.Turtle() my_pen.ht() my_pen.speed(5) my_pen.pencolor("red") - triangle(points, int(sys.argv[1])) + + vertices = [(-175, -125), (0, 175), (175, -125)] # vertices of triangle + triangle(vertices[0], vertices[1], vertices[2], int(sys.argv[1])) From 32a1ff9359b4de80b94ef26c55a5b24204d35382 Mon Sep 17 00:00:00 2001 From: Abhishek Mulik Date: Wed, 4 Jan 2023 06:17:15 +0530 Subject: [PATCH 2103/2908] Update is_palindrome.py (#8022) --- strings/is_palindrome.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/strings/is_palindrome.py b/strings/is_palindrome.py index 5758af0cef9b..9bf2abd98486 100644 --- a/strings/is_palindrome.py +++ b/strings/is_palindrome.py @@ -16,7 +16,24 @@ def is_palindrome(s: str) -> bool: # Since punctuation, capitalization, and spaces are often ignored while checking # palindromes, we first remove them from our string. s = "".join(character for character in s.lower() if character.isalnum()) - return s == s[::-1] + # return s == s[::-1] the slicing method + # uses extra spaces we can + # better with iteration method. + + end = len(s) // 2 + n = len(s) + + # We need to traverse till half of the length of string + # as we can get access of the i'th last element from + # i'th index. + # eg: [0,1,2,3,4,5] => 4th index can be accessed + # with the help of 1st index (i==n-i-1) + # where n is length of string + + for i in range(end): + if s[i] != s[n - i - 1]: + return False + return True if __name__ == "__main__": From 4939e8463fc34c936a309d513cfe8153343cb9d5 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sat, 7 Jan 2023 16:56:39 +0000 Subject: [PATCH 2104/2908] Create cached fibonacci algorithm (#8084) * feat: Add `fib_recursive_cached` func * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doc: Show difference in time when caching algorithm Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/fibonacci.py | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/maths/fibonacci.py b/maths/fibonacci.py index e0da66ee5e3b..d58c9fc68c67 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -16,6 +16,7 @@ fib_binet runtime: 0.0174 ms """ +from functools import lru_cache from math import sqrt from time import time @@ -92,6 +93,39 @@ def fib_recursive_term(i: int) -> int: return [fib_recursive_term(i) for i in range(n + 1)] +def fib_recursive_cached(n: int) -> list[int]: + """ + Calculates the first n (0-indexed) Fibonacci numbers using recursion + >>> fib_iterative(0) + [0] + >>> fib_iterative(1) + [0, 1] + >>> fib_iterative(5) + [0, 1, 1, 2, 3, 5] + >>> fib_iterative(10) + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + >>> fib_iterative(-1) + Traceback (most recent call last): + ... + Exception: n is negative + """ + + @lru_cache(maxsize=None) + def fib_recursive_term(i: int) -> int: + """ + Calculates the i-th (0-indexed) Fibonacci number using recursion + """ + if i < 0: + raise Exception("n is negative") + if i < 2: + return i + return fib_recursive_term(i - 1) + fib_recursive_term(i - 2) + + if n < 0: + raise Exception("n is negative") + return [fib_recursive_term(i) for i in range(n + 1)] + + def fib_memoization(n: int) -> list[int]: """ Calculates the first n (0-indexed) Fibonacci numbers using memoization @@ -163,8 +197,9 @@ def fib_binet(n: int) -> list[int]: if __name__ == "__main__": - num = 20 + num = 30 time_func(fib_iterative, num) - time_func(fib_recursive, num) + time_func(fib_recursive, num) # Around 3s runtime + time_func(fib_recursive_cached, num) # Around 0ms runtime time_func(fib_memoization, num) time_func(fib_binet, num) From 1a27258bd6c3a35a403629b4ea7fc0228bcc892d Mon Sep 17 00:00:00 2001 From: MohammadReza Balakhaniyan <51448587+balakhaniyan@users.noreply.github.com> Date: Wed, 11 Jan 2023 02:17:02 +0330 Subject: [PATCH 2105/2908] gcd_of_n_numbers (#8057) * add maths/Gcd of N Numbers * add maths/Gcd of N Numbers * add maths/Gcd of N Numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add maths/Gcd of N Numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add maths/Gcd of N Numbers * add maths/Gcd of N Numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add maths/Gcd of N Numbers * add maths/Gcd of N Numbers * more pythonic * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more pythonic * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * merged * merged * more readable * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/gcd_of_n_numbers.py | 109 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 maths/gcd_of_n_numbers.py diff --git a/maths/gcd_of_n_numbers.py b/maths/gcd_of_n_numbers.py new file mode 100644 index 000000000000..63236c236ada --- /dev/null +++ b/maths/gcd_of_n_numbers.py @@ -0,0 +1,109 @@ +""" +Gcd of N Numbers +Reference: https://en.wikipedia.org/wiki/Greatest_common_divisor +""" + +from collections import Counter + + +def get_factors( + number: int, factors: Counter | None = None, factor: int = 2 +) -> Counter: + """ + this is a recursive function for get all factors of number + >>> get_factors(45) + Counter({3: 2, 5: 1}) + >>> get_factors(2520) + Counter({2: 3, 3: 2, 5: 1, 7: 1}) + >>> get_factors(23) + Counter({23: 1}) + >>> get_factors(0) + Traceback (most recent call last): + ... + TypeError: number must be integer and greater than zero + >>> get_factors(-1) + Traceback (most recent call last): + ... + TypeError: number must be integer and greater than zero + >>> get_factors(1.5) + Traceback (most recent call last): + ... + TypeError: number must be integer and greater than zero + + factor can be all numbers from 2 to number that we check if number % factor == 0 + if it is equal to zero, we check again with number // factor + else we increase factor by one + """ + + match number: + case int(number) if number == 1: + return Counter({1: 1}) + case int(num) if number > 0: + number = num + case _: + raise TypeError("number must be integer and greater than zero") + + factors = factors or Counter() + + if number == factor: # break condition + # all numbers are factors of itself + factors[factor] += 1 + return factors + + if number % factor > 0: + # if it is greater than zero + # so it is not a factor of number and we check next number + return get_factors(number, factors, factor + 1) + + factors[factor] += 1 + # else we update factors (that is Counter(dict-like) type) and check again + return get_factors(number // factor, factors, factor) + + +def get_greatest_common_divisor(*numbers: int) -> int: + """ + get gcd of n numbers: + >>> get_greatest_common_divisor(18, 45) + 9 + >>> get_greatest_common_divisor(23, 37) + 1 + >>> get_greatest_common_divisor(2520, 8350) + 10 + >>> get_greatest_common_divisor(-10, 20) + Traceback (most recent call last): + ... + Exception: numbers must be integer and greater than zero + >>> get_greatest_common_divisor(1.5, 2) + Traceback (most recent call last): + ... + Exception: numbers must be integer and greater than zero + >>> get_greatest_common_divisor(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + 1 + >>> get_greatest_common_divisor("1", 2, 3, 4, 5, 6, 7, 8, 9, 10) + Traceback (most recent call last): + ... + Exception: numbers must be integer and greater than zero + """ + + # we just need factors, not numbers itself + try: + same_factors, *factors = map(get_factors, numbers) + except TypeError as e: + raise Exception("numbers must be integer and greater than zero") from e + + for factor in factors: + same_factors &= factor + # get common factor between all + # `&` return common elements with smaller value (for Counter type) + + # now, same_factors is something like {2: 2, 3: 4} that means 2 * 2 * 3 * 3 * 3 * 3 + mult = 1 + # power each factor and multiply + # for {2: 2, 3: 4}, it is [4, 81] and then 324 + for m in [factor**power for factor, power in same_factors.items()]: + mult *= m + return mult + + +if __name__ == "__main__": + print(get_greatest_common_divisor(18, 45)) # 9 From c00af459fe0a18ae6adca2aec5ca8c7ff64864c8 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 26 Jan 2023 07:12:11 +0000 Subject: [PATCH 2106/2908] feat: Concatenate both factorial implementations (#8099) * feat: Concatenate both factorial implementations * fix: Rename factorial recursive method --- .../{factorial_iterative.py => factorial.py} | 24 ++++++++++++++++ maths/factorial_recursive.py | 28 ------------------- 2 files changed, 24 insertions(+), 28 deletions(-) rename maths/{factorial_iterative.py => factorial.py} (58%) delete mode 100644 maths/factorial_recursive.py diff --git a/maths/factorial_iterative.py b/maths/factorial.py similarity index 58% rename from maths/factorial_iterative.py rename to maths/factorial.py index c6cf7de57ab2..bbf0efc011d8 100644 --- a/maths/factorial_iterative.py +++ b/maths/factorial.py @@ -34,6 +34,30 @@ def factorial(number: int) -> int: return value +def factorial_recursive(n: int) -> int: + """ + Calculate the factorial of a positive integer + https://en.wikipedia.org/wiki/Factorial + + >>> 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 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) + + if __name__ == "__main__": import doctest diff --git a/maths/factorial_recursive.py b/maths/factorial_recursive.py deleted file mode 100644 index 137112738905..000000000000 --- a/maths/factorial_recursive.py +++ /dev/null @@ -1,28 +0,0 @@ -def factorial(n: int) -> int: - """ - Calculate the factorial of a positive integer - https://en.wikipedia.org/wiki/Factorial - - >>> 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 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) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From 57c12fab2822df33b8da5a1fd9b95f2f7d64f130 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Thu, 26 Jan 2023 02:13:03 -0500 Subject: [PATCH 2107/2908] Fix `mypy` errors in `lorentz_transformation_four_vector.py` (#8075) * updating DIRECTORY.md * Fix mypy errors in lorentz_transformation_four_vector.py * Remove unused symbol vars * Add function documentation and rewrite algorithm explanation Previous explanation was misleading, as the code only calculates Lorentz transformations for movement in the x direction (0 velocity in the y and z directions) and not movement in any direction * updating DIRECTORY.md * Update error message for speed Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + physics/lorentz_transformation_four_vector.py | 144 ++++++++---------- 2 files changed, 65 insertions(+), 80 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 5ce9dca74c06..31e86ea59b79 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -557,6 +557,7 @@ * [Gamma Recursive](maths/gamma_recursive.py) * [Gaussian](maths/gaussian.py) * [Gaussian Error Linear Unit](maths/gaussian_error_linear_unit.py) + * [Gcd Of N Numbers](maths/gcd_of_n_numbers.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) * [Greedy Coin Change](maths/greedy_coin_change.py) * [Hamming Numbers](maths/hamming_numbers.py) diff --git a/physics/lorentz_transformation_four_vector.py b/physics/lorentz_transformation_four_vector.py index f58b40e5906b..64be97245f29 100644 --- a/physics/lorentz_transformation_four_vector.py +++ b/physics/lorentz_transformation_four_vector.py @@ -1,39 +1,33 @@ """ -Lorentz transformation describes the transition from a reference frame P -to another reference frame P', each of which is moving in a direction with -respect to the other. The Lorentz transformation implemented in this code -is the relativistic version using a four vector described by Minkowsky Space: -x0 = ct, x1 = x, x2 = y, and x3 = z - -NOTE: Please note that x0 is c (speed of light) times t (time). - -So, the Lorentz transformation using a four vector is defined as: - -|ct'| | γ -γβ 0 0| |ct| -|x' | = |-γβ γ 0 0| *|x | -|y' | | 0 0 1 0| |y | -|z' | | 0 0 0 1| |z | - -Where: - 1 -γ = --------------- - ----------- - / v^2 | - /(1 - --- - -/ c^2 - - v -β = ----- - c +Lorentz transformations describe the transition between two inertial reference +frames F and F', each of which is moving in some direction with respect to the +other. This code only calculates Lorentz transformations for movement in the x +direction with no spacial rotation (i.e., a Lorentz boost in the x direction). +The Lorentz transformations are calculated here as linear transformations of +four-vectors [ct, x, y, z] described by Minkowski space. Note that t (time) is +multiplied by c (the speed of light) in the first entry of each four-vector. + +Thus, if X = [ct; x; y; z] and X' = [ct'; x'; y'; z'] are the four-vectors for +two inertial reference frames and X' moves in the x direction with velocity v +with respect to X, then the Lorentz transformation from X to X' is X' = BX, +where + + | γ -γβ 0 0| +B = |-γβ γ 0 0| + | 0 0 1 0| + | 0 0 0 1| + +is the matrix describing the Lorentz boost between X and X', +γ = 1 / √(1 - v²/c²) is the Lorentz factor, and β = v/c is the velocity as +a fraction of c. Reference: https://en.wikipedia.org/wiki/Lorentz_transformation """ -from __future__ import annotations from math import sqrt -import numpy as np # type: ignore -from sympy import symbols # type: ignore +import numpy as np +from sympy import symbols # Coefficient # Speed of light (m/s) @@ -41,79 +35,77 @@ # Symbols ct, x, y, z = symbols("ct x y z") -ct_p, x_p, y_p, z_p = symbols("ct' x' y' z'") # Vehicle's speed divided by speed of light (no units) def beta(velocity: float) -> float: """ + Calculates β = v/c, the given velocity as a fraction of c >>> beta(c) 1.0 - >>> beta(199792458) 0.666435904801848 - >>> beta(1e5) 0.00033356409519815205 - >>> beta(0.2) Traceback (most recent call last): ... - ValueError: Speed must be greater than 1! + ValueError: Speed must be greater than or equal to 1! """ if velocity > c: - raise ValueError("Speed must not exceed Light Speed 299,792,458 [m/s]!") - - # Usually the speed u should be much higher than 1 (c order of magnitude) + raise ValueError("Speed must not exceed light speed 299,792,458 [m/s]!") elif velocity < 1: - raise ValueError("Speed must be greater than 1!") + # Usually the speed should be much higher than 1 (c order of magnitude) + raise ValueError("Speed must be greater than or equal to 1!") + return velocity / c def gamma(velocity: float) -> float: """ + Calculate the Lorentz factor γ = 1 / √(1 - v²/c²) for a given velocity >>> gamma(4) 1.0000000000000002 - >>> gamma(1e5) 1.0000000556325075 - >>> gamma(3e7) 1.005044845777813 - >>> gamma(2.8e8) 2.7985595722318277 - >>> gamma(299792451) 4627.49902669495 - >>> gamma(0.3) Traceback (most recent call last): ... - ValueError: Speed must be greater than 1! - - >>> gamma(2*c) + ValueError: Speed must be greater than or equal to 1! + >>> gamma(2 * c) Traceback (most recent call last): ... - ValueError: Speed must not exceed Light Speed 299,792,458 [m/s]! + ValueError: Speed must not exceed light speed 299,792,458 [m/s]! """ - return 1 / (sqrt(1 - beta(velocity) ** 2)) + return 1 / sqrt(1 - beta(velocity) ** 2) -def transformation_matrix(velocity: float) -> np.array: +def transformation_matrix(velocity: float) -> np.ndarray: """ + Calculate the Lorentz transformation matrix for movement in the x direction: + + | γ -γβ 0 0| + |-γβ γ 0 0| + | 0 0 1 0| + | 0 0 0 1| + + where γ is the Lorentz factor and β is the velocity as a fraction of c >>> transformation_matrix(29979245) array([[ 1.00503781, -0.10050378, 0. , 0. ], [-0.10050378, 1.00503781, 0. , 0. ], [ 0. , 0. , 1. , 0. ], [ 0. , 0. , 0. , 1. ]]) - >>> transformation_matrix(19979245.2) array([[ 1.00222811, -0.06679208, 0. , 0. ], [-0.06679208, 1.00222811, 0. , 0. ], [ 0. , 0. , 1. , 0. ], [ 0. , 0. , 0. , 1. ]]) - >>> transformation_matrix(1) array([[ 1.00000000e+00, -3.33564095e-09, 0.00000000e+00, 0.00000000e+00], @@ -123,16 +115,14 @@ def transformation_matrix(velocity: float) -> np.array: 0.00000000e+00], [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) - >>> transformation_matrix(0) Traceback (most recent call last): ... - ValueError: Speed must be greater than 1! - + ValueError: Speed must be greater than or equal to 1! >>> transformation_matrix(c * 1.5) Traceback (most recent call last): ... - ValueError: Speed must not exceed Light Speed 299,792,458 [m/s]! + ValueError: Speed must not exceed light speed 299,792,458 [m/s]! """ return np.array( [ @@ -144,44 +134,39 @@ def transformation_matrix(velocity: float) -> np.array: ) -def transform( - velocity: float, event: np.array = np.zeros(4), symbolic: bool = True # noqa: B008 -) -> np.array: +def transform(velocity: float, event: np.ndarray | None = None) -> np.ndarray: """ - >>> transform(29979245,np.array([1,2,3,4]), False) - array([ 3.01302757e+08, -3.01302729e+07, 3.00000000e+00, 4.00000000e+00]) + Calculate a Lorentz transformation for movement in the x direction given a + velocity and a four-vector for an inertial reference frame + If no four-vector is given, then calculate the transformation symbolically + with variables + >>> transform(29979245, np.array([1, 2, 3, 4])) + array([ 3.01302757e+08, -3.01302729e+07, 3.00000000e+00, 4.00000000e+00]) >>> transform(29979245) array([1.00503781498831*ct - 0.100503778816875*x, -0.100503778816875*ct + 1.00503781498831*x, 1.0*y, 1.0*z], dtype=object) - >>> transform(19879210.2) array([1.0022057787097*ct - 0.066456172618675*x, -0.066456172618675*ct + 1.0022057787097*x, 1.0*y, 1.0*z], dtype=object) - - >>> transform(299792459, np.array([1,1,1,1])) + >>> transform(299792459, np.array([1, 1, 1, 1])) Traceback (most recent call last): ... - ValueError: Speed must not exceed Light Speed 299,792,458 [m/s]! - - >>> transform(-1, np.array([1,1,1,1])) + ValueError: Speed must not exceed light speed 299,792,458 [m/s]! + >>> transform(-1, np.array([1, 1, 1, 1])) Traceback (most recent call last): ... - ValueError: Speed must be greater than 1! + ValueError: Speed must be greater than or equal to 1! """ - # Ensure event is not a vector of zeros - if not symbolic: - - # x0 is ct (speed of ligt * time) - event[0] = event[0] * c + # Ensure event is not empty + if event is None: + event = np.array([ct, x, y, z]) # Symbolic four vector else: + event[0] *= c # x0 is ct (speed of light * time) - # Symbolic four vector - event = np.array([ct, x, y, z]) - - return transformation_matrix(velocity).dot(event) + return transformation_matrix(velocity) @ event if __name__ == "__main__": @@ -197,9 +182,8 @@ def transform( print(f"y' = {four_vector[2]}") print(f"z' = {four_vector[3]}") - # Substitute symbols with numerical values: - values = np.array([1, 1, 1, 1]) - sub_dict = {ct: c * values[0], x: values[1], y: values[2], z: values[3]} - numerical_vector = [four_vector[i].subs(sub_dict) for i in range(0, 4)] + # Substitute symbols with numerical values + sub_dict = {ct: c, x: 1, y: 1, z: 1} + numerical_vector = [four_vector[i].subs(sub_dict) for i in range(4)] print(f"\n{numerical_vector}") From ed0a581f9347b8fddc1928e52232eea250108573 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 23:42:15 +0100 Subject: [PATCH 2108/2908] [pre-commit.ci] pre-commit autoupdate (#8107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/PyCQA/isort: 5.11.4 → 5.12.0](https://github.com/PyCQA/isort/compare/5.11.4...5.12.0) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8eb6d297e831..b97ef288981b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.11.4 + rev: 5.12.0 hooks: - id: isort args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 31e86ea59b79..a8786cc2591f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -543,8 +543,7 @@ * [Euler Modified](maths/euler_modified.py) * [Eulers Totient](maths/eulers_totient.py) * [Extended Euclidean Algorithm](maths/extended_euclidean_algorithm.py) - * [Factorial Iterative](maths/factorial_iterative.py) - * [Factorial Recursive](maths/factorial_recursive.py) + * [Factorial](maths/factorial.py) * [Factors](maths/factors.py) * [Fermat Little Theorem](maths/fermat_little_theorem.py) * [Fibonacci](maths/fibonacci.py) From c909da9b085957fcd16b6b30b6bdc0cf2855a150 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 1 Feb 2023 14:14:54 +0100 Subject: [PATCH 2109/2908] pre-commit: Upgrade psf/black for stable style 2023 (#8110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pre-commit: Upgrade psf/black for stable style 2023 Updating https://github.com/psf/black ... updating 22.12.0 -> 23.1.0 for their `2023 stable style`. * https://github.com/psf/black/blob/main/CHANGES.md#2310 > This is the first [psf/black] release of 2023, and following our stability policy, it comes with a number of improvements to our stable style… Also, add https://github.com/tox-dev/pyproject-fmt and https://github.com/abravalheri/validate-pyproject to pre-commit. I only modified `.pre-commit-config.yaml` and all other files were modified by pre-commit.ci and psf/black. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 12 +++++++++- arithmetic_analysis/newton_raphson_new.py | 1 - backtracking/n_queens_math.py | 1 - blockchain/chinese_remainder_theorem.py | 1 + ciphers/enigma_machine2.py | 1 - ciphers/playfair_cipher.py | 1 - ciphers/polybius.py | 1 - ciphers/xor_cipher.py | 2 -- compression/lz77.py | 1 - computer_vision/cnn_classification.py | 1 - computer_vision/harris_corner.py | 3 --- conversions/decimal_to_binary.py | 1 - conversions/molecular_chemistry.py | 1 - conversions/roman_numerals.py | 2 +- conversions/temperature_conversions.py | 1 - conversions/weight_conversion.py | 1 - .../binary_tree/binary_tree_traversals.py | 1 - .../inorder_tree_traversal_2022.py | 1 - data_structures/hashing/double_hash.py | 1 - data_structures/hashing/hash_table.py | 2 -- data_structures/heap/binomial_heap.py | 2 -- data_structures/heap/skew_heap.py | 1 - .../linked_list/doubly_linked_list_two.py | 2 -- data_structures/stacks/prefix_evaluation.py | 1 - .../stacks/stack_with_doubly_linked_list.py | 1 - data_structures/stacks/stock_span_problem.py | 2 -- .../filters/bilateral_filter.py | 1 - .../filters/local_binary_pattern.py | 1 - dynamic_programming/bitmask.py | 5 ---- .../iterating_through_submasks.py | 1 - electronics/coulombs_law.py | 1 - fractals/julia_sets.py | 1 - fractals/mandelbrot.py | 1 - geodesy/lamberts_ellipsoidal_distance.py | 1 - graphs/a_star.py | 1 - graphs/check_bipartite_graph_bfs.py | 1 - graphs/graph_matrix.py | 1 - graphs/karger.py | 1 - graphs/minimum_spanning_tree_boruvka.py | 1 - graphs/multi_heuristic_astar.py | 4 ++-- knapsack/recursive_approach_knapsack.py | 1 - linear_algebra/src/conjugate_gradient.py | 1 - machine_learning/k_means_clust.py | 3 --- machine_learning/self_organizing_map.py | 1 - .../sequential_minimum_optimization.py | 2 -- machine_learning/xgboost_classifier.py | 1 - maths/armstrong_numbers.py | 2 +- maths/binary_exponentiation.py | 1 - maths/combinations.py | 1 - maths/decimal_isolate.py | 1 - maths/fermat_little_theorem.py | 1 - maths/greedy_coin_change.py | 2 -- maths/integration_by_simpson_approx.py | 1 - maths/jaccard_similarity.py | 2 -- maths/least_common_multiple.py | 1 - maths/line_length.py | 2 -- maths/monte_carlo.py | 1 + maths/newton_raphson.py | 1 - maths/numerical_integration.py | 2 -- maths/primelib.py | 23 ------------------- maths/segmented_sieve.py | 1 - maths/two_pointer.py | 1 - maths/zellers_congruence.py | 1 - matrix/largest_square_area_in_matrix.py | 3 --- other/activity_selection.py | 1 - other/nested_brackets.py | 2 -- other/scoring_algorithm.py | 1 - other/sdes.py | 1 - physics/casimir_effect.py | 1 - physics/hubble_parameter.py | 1 - physics/newtons_law_of_gravitation.py | 1 - project_euler/problem_004/sol1.py | 2 -- project_euler/problem_074/sol2.py | 1 - project_euler/problem_089/sol1.py | 1 - project_euler/problem_092/sol1.py | 2 -- quantum/q_fourier_transform.py | 1 - quantum/quantum_teleportation.py | 1 - scheduling/highest_response_ratio_next.py | 2 -- searches/binary_search.py | 1 - searches/interpolation_search.py | 1 - searches/tabu_search.py | 1 - searches/ternary_search.py | 1 - sorts/comb_sort.py | 1 - sorts/odd_even_sort.py | 1 - sorts/odd_even_transposition_parallel.py | 1 - sorts/random_normal_distribution_quicksort.py | 2 -- sorts/shrink_shell_sort.py | 1 - sorts/stooge_sort.py | 1 - sorts/tim_sort.py | 1 - strings/dna.py | 1 - strings/hamming_distance.py | 1 - strings/levenshtein_distance.py | 2 -- strings/prefix_function.py | 1 - strings/text_justification.py | 1 - web_programming/fetch_anime_and_play.py | 9 ++------ web_programming/fetch_well_rx_price.py | 5 ---- web_programming/get_user_tweets.py | 1 - 97 files changed, 19 insertions(+), 154 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b97ef288981b..f8d1a65db27b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: auto-walrus - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 23.1.0 hooks: - id: black @@ -26,6 +26,16 @@ repos: args: - --profile=black + - repo: https://github.com/tox-dev/pyproject-fmt + rev: "0.6.0" + hooks: + - id: pyproject-fmt + + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.12.1 + hooks: + - id: validate-pyproject + - repo: https://github.com/asottile/pyupgrade rev: v3.3.1 hooks: diff --git a/arithmetic_analysis/newton_raphson_new.py b/arithmetic_analysis/newton_raphson_new.py index dd1d7e0929cf..472cb5b5ac54 100644 --- a/arithmetic_analysis/newton_raphson_new.py +++ b/arithmetic_analysis/newton_raphson_new.py @@ -59,7 +59,6 @@ def newton_raphson( # 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)}") diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py index 2de784ded06b..23bd1590618b 100644 --- a/backtracking/n_queens_math.py +++ b/backtracking/n_queens_math.py @@ -107,7 +107,6 @@ def depth_first_search( # 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 diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py index 54d861dd9f10..d3e75e77922a 100644 --- a/blockchain/chinese_remainder_theorem.py +++ b/blockchain/chinese_remainder_theorem.py @@ -53,6 +53,7 @@ def chinese_remainder_theorem(n1: int, r1: int, n2: int, r2: int) -> int: # ----------SAME SOLUTION USING InvertModulo instead ExtendedEuclid---------------- + # This function find the inverses of a i.e., a^(-1) def invert_modulo(a: int, n: int) -> int: """ diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index a877256ebeeb..07d21893f192 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -230,7 +230,6 @@ def enigma( # encryption/decryption process -------------------------- for symbol in text: if symbol in abc: - # 1st plugboard -------------------------- if symbol in plugboard: symbol = plugboard[symbol] diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 89aedb7afdb8..7279fb23ecb2 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -39,7 +39,6 @@ def prepare_input(dirty: str) -> str: def generate_table(key: str) -> list[str]: - # I and J are used interchangeably to allow # us to use a 5x5 table (25 letters) alphabet = "ABCDEFGHIKLMNOPQRSTUVWXYZ" diff --git a/ciphers/polybius.py b/ciphers/polybius.py index c81c1d39533f..3539ab70c303 100644 --- a/ciphers/polybius.py +++ b/ciphers/polybius.py @@ -19,7 +19,6 @@ class PolybiusCipher: def __init__(self) -> None: - self.SQUARE = np.array(SQUARE) def letter_to_numbers(self, letter: str) -> np.ndarray: diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index ca9dfe20f7b6..379ef0ef7e50 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -130,7 +130,6 @@ def encrypt_file(self, file: str, key: int = 0) -> bool: try: 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)) @@ -155,7 +154,6 @@ def decrypt_file(self, file: str, key: int) -> bool: try: 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)) diff --git a/compression/lz77.py b/compression/lz77.py index 7c1a6f6a4c19..1b201c59f186 100644 --- a/compression/lz77.py +++ b/compression/lz77.py @@ -89,7 +89,6 @@ def compress(self, text: str) -> list[Token]: # while there are still characters in text to compress while text: - # find the next encoding phrase # - triplet with offset, length, indicator (the next encoding character) token = self._find_encoding_token(text, search_buffer) diff --git a/computer_vision/cnn_classification.py b/computer_vision/cnn_classification.py index 59e4556e069b..1c193fcbb50b 100644 --- a/computer_vision/cnn_classification.py +++ b/computer_vision/cnn_classification.py @@ -28,7 +28,6 @@ from tensorflow.keras import layers, models if __name__ == "__main__": - # Initialising the CNN # (Sequential- Building the model layer by layer) classifier = models.Sequential() diff --git a/computer_vision/harris_corner.py b/computer_vision/harris_corner.py index c8905bb6a9cd..0cc7522bc3af 100644 --- a/computer_vision/harris_corner.py +++ b/computer_vision/harris_corner.py @@ -9,7 +9,6 @@ class HarrisCorner: def __init__(self, k: float, window_size: int): - """ k : is an empirically determined constant in [0.04,0.06] window_size : neighbourhoods considered @@ -25,7 +24,6 @@ def __str__(self) -> str: return str(self.k) def detect(self, img_path: str) -> tuple[cv2.Mat, list[list[int]]]: - """ Returns the image with corners identified img_path : path of the image @@ -68,7 +66,6 @@ def detect(self, img_path: str) -> tuple[cv2.Mat, list[list[int]]]: if __name__ == "__main__": - edge_detect = HarrisCorner(0.04, 3) color_img, _ = edge_detect.detect("path_to_image") cv2.imwrite("detect.png", color_img) diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index cfda57ca714a..973c47c8af67 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -2,7 +2,6 @@ def decimal_to_binary(num: int) -> str: - """ Convert an Integer Decimal Number to a Binary Number as str. >>> decimal_to_binary(0) diff --git a/conversions/molecular_chemistry.py b/conversions/molecular_chemistry.py index 0024eb5cb5b8..51ffe534dd0d 100644 --- a/conversions/molecular_chemistry.py +++ b/conversions/molecular_chemistry.py @@ -86,7 +86,6 @@ def pressure_and_volume_to_temperature( if __name__ == "__main__": - import doctest doctest.testmod() diff --git a/conversions/roman_numerals.py b/conversions/roman_numerals.py index 61215a0c0730..75af2ac72882 100644 --- a/conversions/roman_numerals.py +++ b/conversions/roman_numerals.py @@ -47,7 +47,7 @@ def int_to_roman(number: int) -> str: True """ result = [] - for (arabic, roman) in ROMAN: + for arabic, roman in ROMAN: (factor, number) = divmod(number, arabic) result.append(roman * factor) if number == 0: diff --git a/conversions/temperature_conversions.py b/conversions/temperature_conversions.py index e5af465561f9..f7af6c8f1e2b 100644 --- a/conversions/temperature_conversions.py +++ b/conversions/temperature_conversions.py @@ -380,7 +380,6 @@ def reaumur_to_rankine(reaumur: float, ndigits: int = 2) -> float: if __name__ == "__main__": - import doctest doctest.testmod() diff --git a/conversions/weight_conversion.py b/conversions/weight_conversion.py index 18c4037317da..5c032a497a7b 100644 --- a/conversions/weight_conversion.py +++ b/conversions/weight_conversion.py @@ -307,7 +307,6 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float: if __name__ == "__main__": - import doctest doctest.testmod() diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 54b1dc536f32..24dd1bd8cdc8 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -105,7 +105,6 @@ def populate_output(root: Node | None, level: int) -> None: if not root: return if level == 1: - output.append(root.data) elif level > 1: populate_output(root.left, level - 1) diff --git a/data_structures/binary_tree/inorder_tree_traversal_2022.py b/data_structures/binary_tree/inorder_tree_traversal_2022.py index 08001738f53d..e94ba7013a82 100644 --- a/data_structures/binary_tree/inorder_tree_traversal_2022.py +++ b/data_structures/binary_tree/inorder_tree_traversal_2022.py @@ -58,7 +58,6 @@ def inorder(node: None | BinaryTreeNode) -> list[int]: # if node is None,return def make_tree() -> BinaryTreeNode | None: - root = insert(None, 15) insert(root, 10) insert(root, 25) diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index 453e0d13106d..be21e74cadd0 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -24,7 +24,6 @@ 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 is_prime(value % self.size_table) diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 607454c8255f..7ca2f7c401cf 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -32,7 +32,6 @@ def hash_function(self, key): return key % self.size_table def _step_by_step(self, step_ord): - print(f"step {step_ord}") print(list(range(len(self.values)))) print(self.values) @@ -53,7 +52,6 @@ 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: - if self.values.count(None) > 0: new_key = self.hash_function(new_key + 1) else: diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index d79fac7a99d5..2e05c5c80a22 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -174,7 +174,6 @@ def merge_heaps(self, other): 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 @@ -233,7 +232,6 @@ def insert(self, val): and self.bottom_root.left_tree_size == self.bottom_root.parent.left_tree_size ): - # Next node next_node = self.bottom_root.parent.parent diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 490db061deac..c4c13b08276a 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -71,7 +71,6 @@ class SkewHeap(Generic[T]): """ def __init__(self, data: Iterable[T] | None = ()) -> None: - """ >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py index 94b916a623f6..c19309c9f5a7 100644 --- a/data_structures/linked_list/doubly_linked_list_two.py +++ b/data_structures/linked_list/doubly_linked_list_two.py @@ -80,7 +80,6 @@ def get_tail_data(self): return None def set_head(self, node: Node) -> None: - if self.head is None: self.head = node self.tail = node @@ -143,7 +142,6 @@ def get_node(self, item: int) -> Node: raise Exception("Node not found") def delete_value(self, value): - if (node := self.get_node(value)) is not None: if node == self.head: self.head = self.head.get_next() diff --git a/data_structures/stacks/prefix_evaluation.py b/data_structures/stacks/prefix_evaluation.py index 00df2c1e63b0..f48eca23d7b5 100644 --- a/data_structures/stacks/prefix_evaluation.py +++ b/data_structures/stacks/prefix_evaluation.py @@ -36,7 +36,6 @@ def evaluate(expression): # 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)) diff --git a/data_structures/stacks/stack_with_doubly_linked_list.py b/data_structures/stacks/stack_with_doubly_linked_list.py index a129665f209f..50c5236e073c 100644 --- a/data_structures/stacks/stack_with_doubly_linked_list.py +++ b/data_structures/stacks/stack_with_doubly_linked_list.py @@ -92,7 +92,6 @@ def print_stack(self) -> None: # Code execution starts here if __name__ == "__main__": - # Start with the empty stack stack: Stack[int] = Stack() diff --git a/data_structures/stacks/stock_span_problem.py b/data_structures/stacks/stock_span_problem.py index 19a81bd368de..de423c1ebf66 100644 --- a/data_structures/stacks/stock_span_problem.py +++ b/data_structures/stacks/stock_span_problem.py @@ -9,7 +9,6 @@ def calculation_span(price, s): - n = len(price) # Create a stack and push index of fist element to it st = [] @@ -20,7 +19,6 @@ def calculation_span(price, s): # Calculate span values for rest of the elements for i in range(1, n): - # 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]: diff --git a/digital_image_processing/filters/bilateral_filter.py b/digital_image_processing/filters/bilateral_filter.py index 1afa01d3fc1a..565da73f6b0e 100644 --- a/digital_image_processing/filters/bilateral_filter.py +++ b/digital_image_processing/filters/bilateral_filter.py @@ -50,7 +50,6 @@ def bilateral_filter( size_x, size_y = img.shape for i in range(kernel_size // 2, size_x - kernel_size // 2): for j in range(kernel_size // 2, size_y - kernel_size // 2): - img_s = get_slice(img, i, j, kernel_size) img_i = img_s - img_s[kernel_size // 2, kernel_size // 2] img_ig = vec_gaussian(img_i, intensity_variance) diff --git a/digital_image_processing/filters/local_binary_pattern.py b/digital_image_processing/filters/local_binary_pattern.py index e92e554a3e5f..907fe2cb0555 100644 --- a/digital_image_processing/filters/local_binary_pattern.py +++ b/digital_image_processing/filters/local_binary_pattern.py @@ -61,7 +61,6 @@ def local_binary_value(image: np.ndarray, x_coordinate: int, y_coordinate: int) if __name__ == "__main__": - # Reading the image and converting it to grayscale. image = cv2.imread( "digital_image_processing/image_data/lena.jpg", cv2.IMREAD_GRAYSCALE diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index f45250c9cb84..56bb8e96ba02 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -13,7 +13,6 @@ 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 @@ -29,7 +28,6 @@ def __init__(self, task_performed, total): self.final_mask = (1 << len(task_performed)) - 1 def count_ways_until(self, mask, task_no): - # if mask == self.finalmask all persons are distributed tasks, return 1 if mask == self.final_mask: return 1 @@ -49,7 +47,6 @@ def count_ways_until(self, mask, task_no): # assign for the remaining tasks. 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 @@ -64,7 +61,6 @@ def count_ways_until(self, mask, task_no): return self.dp[mask][task_no] def count_no_of_ways(self, task_performed): - # Store the list of persons for each task for i in range(len(task_performed)): for j in task_performed[i]: @@ -75,7 +71,6 @@ def count_no_of_ways(self, task_performed): if __name__ == "__main__": - total_tasks = 5 # total no of tasks (the value of N) # the list of tasks that can be done by M persons. diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index 21c64dba4ecc..4d0a250e8dfe 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -9,7 +9,6 @@ def list_of_submasks(mask: int) -> list[int]: - """ Args: mask : number which shows mask ( always integer > 0, zero does not have any diff --git a/electronics/coulombs_law.py b/electronics/coulombs_law.py index e41c0410cc9e..18c1a8179eb6 100644 --- a/electronics/coulombs_law.py +++ b/electronics/coulombs_law.py @@ -8,7 +8,6 @@ def couloumbs_law( force: float, charge1: float, charge2: float, distance: float ) -> dict[str, float]: - """ Apply Coulomb's Law on any three given values. These can be force, charge1, charge2, or distance, and then in a Python dict return name/value pair of diff --git a/fractals/julia_sets.py b/fractals/julia_sets.py index 77d1d7c042ba..482e1eddfecc 100644 --- a/fractals/julia_sets.py +++ b/fractals/julia_sets.py @@ -170,7 +170,6 @@ def ignore_overflow_warnings() -> None: if __name__ == "__main__": - z_0 = prepare_grid(window_size, nb_pixels) ignore_overflow_warnings() # See file header for explanations diff --git a/fractals/mandelbrot.py b/fractals/mandelbrot.py index f97bcd17031c..84dbda997562 100644 --- a/fractals/mandelbrot.py +++ b/fractals/mandelbrot.py @@ -114,7 +114,6 @@ def get_image( # loop through the image-coordinates for image_x in range(image_width): for image_y in range(image_height): - # determine the figure-coordinates based on the image-coordinates figure_height = figure_width / image_width * image_height figure_x = figure_center_x + (image_x / image_width - 0.5) * figure_width diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py index 62ce59bb476f..4805674e51ab 100644 --- a/geodesy/lamberts_ellipsoidal_distance.py +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -10,7 +10,6 @@ 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 diff --git a/graphs/a_star.py b/graphs/a_star.py index 793ba3bda6b2..e8735179eab9 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -16,7 +16,6 @@ def search( cost: int, heuristic: list[list[int]], ) -> tuple[list[list[int]], list[list[int]]]: - closed = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) ] # the reference grid diff --git a/graphs/check_bipartite_graph_bfs.py b/graphs/check_bipartite_graph_bfs.py index 552b7eee283d..7fc57cbc78bd 100644 --- a/graphs/check_bipartite_graph_bfs.py +++ b/graphs/check_bipartite_graph_bfs.py @@ -20,7 +20,6 @@ def bfs(): visited[u] = True for neighbour in graph[u]: - if neighbour == u: return False diff --git a/graphs/graph_matrix.py b/graphs/graph_matrix.py index 987168426ba5..4adc6c0bb93b 100644 --- a/graphs/graph_matrix.py +++ b/graphs/graph_matrix.py @@ -8,7 +8,6 @@ def add_edge(self, u, v): self.graph[v - 1][u - 1] = 1 def show(self): - for i in self.graph: for j in i: print(j, end=" ") diff --git a/graphs/karger.py b/graphs/karger.py index f72128c8178a..3ef65c0d6d32 100644 --- a/graphs/karger.py +++ b/graphs/karger.py @@ -47,7 +47,6 @@ def partition_graph(graph: dict[str, list[str]]) -> set[tuple[str, str]]: 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]) diff --git a/graphs/minimum_spanning_tree_boruvka.py b/graphs/minimum_spanning_tree_boruvka.py index 6c72615cc729..663d8e26cfad 100644 --- a/graphs/minimum_spanning_tree_boruvka.py +++ b/graphs/minimum_spanning_tree_boruvka.py @@ -4,7 +4,6 @@ class Graph: """ def __init__(self): - self.num_vertices = 0 self.num_edges = 0 self.adjacency = {} diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index cd8e37b0099b..0a18ede6ed41 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -33,7 +33,7 @@ def put(self, item, priority): temp.append((pri, x)) (pri, x) = heapq.heappop(self.elements) temp.append((priority, item)) - for (pro, xxx) in temp: + for pro, xxx in temp: heapq.heappush(self.elements, (pro, xxx)) def remove_element(self, item): @@ -44,7 +44,7 @@ def remove_element(self, item): while x != item: temp.append((pro, x)) (pro, x) = heapq.heappop(self.elements) - for (prito, yyy) in temp: + for prito, yyy in temp: heapq.heappush(self.elements, (prito, yyy)) def top_show(self): diff --git a/knapsack/recursive_approach_knapsack.py b/knapsack/recursive_approach_knapsack.py index d813981cb79c..9a8ed1886a5b 100644 --- a/knapsack/recursive_approach_knapsack.py +++ b/knapsack/recursive_approach_knapsack.py @@ -46,7 +46,6 @@ def knapsack( if __name__ == "__main__": - import doctest doctest.testmod() diff --git a/linear_algebra/src/conjugate_gradient.py b/linear_algebra/src/conjugate_gradient.py index 418ae88a5f41..4cf566ec9e36 100644 --- a/linear_algebra/src/conjugate_gradient.py +++ b/linear_algebra/src/conjugate_gradient.py @@ -115,7 +115,6 @@ def conjugate_gradient( iterations = 0 while error > tol: - # Save this value so we only calculate the matrix-vector product once. w = np.dot(spd_matrix, p0) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 5dc2b7118b56..b6305469ed7d 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -74,7 +74,6 @@ def centroid_pairwise_dist(x, centroids): 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) @@ -100,10 +99,8 @@ def revise_centroids(data, k, cluster_assignment): 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, :] diff --git a/machine_learning/self_organizing_map.py b/machine_learning/self_organizing_map.py index 057c2a76b8ac..32fdf1d2b41d 100644 --- a/machine_learning/self_organizing_map.py +++ b/machine_learning/self_organizing_map.py @@ -49,7 +49,6 @@ def main() -> None: for _ in range(epochs): for j in range(len(training_samples)): - # training sample sample = training_samples[j] diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index f5185e1d9576..9c45c351272f 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -82,7 +82,6 @@ def fit(self): k = self._k state = None while True: - # 1: Find alpha1, alpha2 try: i1, i2 = self.choose_alpha.send(state) @@ -146,7 +145,6 @@ def fit(self): # Predict test samples 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" diff --git a/machine_learning/xgboost_classifier.py b/machine_learning/xgboost_classifier.py index 08967f1715a1..1da933cf690f 100644 --- a/machine_learning/xgboost_classifier.py +++ b/machine_learning/xgboost_classifier.py @@ -41,7 +41,6 @@ def xgboost(features: np.ndarray, target: np.ndarray) -> XGBClassifier: def main() -> None: - """ >>> main() diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index f62991b7415b..26709b428b78 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -62,7 +62,7 @@ def pluperfect_number(n: int) -> bool: digit_histogram[rem] += 1 digit_total += 1 - for (cnt, i) in zip(digit_histogram, range(len(digit_histogram))): + for cnt, i in zip(digit_histogram, range(len(digit_histogram))): total += cnt * i**digit_total return n == total diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index 8dda5245cf44..147b4285ffa1 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -5,7 +5,6 @@ def binary_exponentiation(a, n): - if n == 0: return 1 diff --git a/maths/combinations.py b/maths/combinations.py index 6db1d773faa6..a2324012c01f 100644 --- a/maths/combinations.py +++ b/maths/combinations.py @@ -39,7 +39,6 @@ def combinations(n: int, k: int) -> int: if __name__ == "__main__": - print( "The number of five-card hands possible from a standard", f"fifty-two card deck is: {combinations(52, 5)}\n", diff --git a/maths/decimal_isolate.py b/maths/decimal_isolate.py index cdf43ea5d0ef..058ed1bb90d1 100644 --- a/maths/decimal_isolate.py +++ b/maths/decimal_isolate.py @@ -5,7 +5,6 @@ def decimal_isolate(number: float, digit_amount: int) -> float: - """ Isolates the decimal part of a number. If digitAmount > 0 round to that decimal place, else print the entire decimal. diff --git a/maths/fermat_little_theorem.py b/maths/fermat_little_theorem.py index 73af3e28c618..eea03be245cb 100644 --- a/maths/fermat_little_theorem.py +++ b/maths/fermat_little_theorem.py @@ -6,7 +6,6 @@ def binary_exponentiation(a, n, mod): - if n == 0: return 1 diff --git a/maths/greedy_coin_change.py b/maths/greedy_coin_change.py index 29c2f1803d5c..7cf669bcb8cb 100644 --- a/maths/greedy_coin_change.py +++ b/maths/greedy_coin_change.py @@ -62,7 +62,6 @@ def find_minimum_change(denominations: list[int], value: str) -> list[int]: # Traverse through all denomination for denomination in reversed(denominations): - # Find denominations while int(total_value) >= int(denomination): total_value -= int(denomination) @@ -73,7 +72,6 @@ def find_minimum_change(denominations: list[int], value: str) -> list[int]: # Driver Code if __name__ == "__main__": - denominations = [] value = "0" diff --git a/maths/integration_by_simpson_approx.py b/maths/integration_by_simpson_approx.py index 408041de93f1..f77ae76135ee 100644 --- a/maths/integration_by_simpson_approx.py +++ b/maths/integration_by_simpson_approx.py @@ -35,7 +35,6 @@ def f(x: float) -> float: def simpson_integration(function, a: float, b: float, precision: int = 4) -> float: - """ Args: function : the function which's integration is desired diff --git a/maths/jaccard_similarity.py b/maths/jaccard_similarity.py index b299a81476ab..eab25188b2fd 100644 --- a/maths/jaccard_similarity.py +++ b/maths/jaccard_similarity.py @@ -51,7 +51,6 @@ def jaccard_similarity(set_a, set_b, alternative_union=False): """ if isinstance(set_a, set) and isinstance(set_b, set): - intersection = len(set_a.intersection(set_b)) if alternative_union: @@ -62,7 +61,6 @@ def jaccard_similarity(set_a, set_b, alternative_union=False): return intersection / union if isinstance(set_a, (list, tuple)) and isinstance(set_b, (list, tuple)): - intersection = [element for element in set_a if element in set_b] if alternative_union: diff --git a/maths/least_common_multiple.py b/maths/least_common_multiple.py index 0d087643e869..621d93720c41 100644 --- a/maths/least_common_multiple.py +++ b/maths/least_common_multiple.py @@ -67,7 +67,6 @@ def benchmark(): class TestLeastCommonMultiple(unittest.TestCase): - test_inputs = [ (10, 20), (13, 15), diff --git a/maths/line_length.py b/maths/line_length.py index ea27ee904a24..b810f2d9ad1f 100644 --- a/maths/line_length.py +++ b/maths/line_length.py @@ -10,7 +10,6 @@ def line_length( x_end: 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 @@ -41,7 +40,6 @@ def line_length( length = 0.0 for _ 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) diff --git a/maths/monte_carlo.py b/maths/monte_carlo.py index c13b8d0a4f6b..474f1f65deb4 100644 --- a/maths/monte_carlo.py +++ b/maths/monte_carlo.py @@ -18,6 +18,7 @@ def pi_estimator(iterations: int): 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 is_in_circle(x: float, y: float) -> bool: distance_from_centre = sqrt((x**2) + (y**2)) diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index f2b7cb9766d2..2c9cd1de95b0 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -19,7 +19,6 @@ def calc_derivative(f, a, h=0.001): def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=False): - a = x0 # set the initial guess steps = [a] error = abs(f(a)) diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index 8f32fd3564df..f2d65f89e390 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -12,7 +12,6 @@ def trapezoidal_area( x_end: int | float, steps: int = 100, ) -> float: - """ Treats curve as a collection of linear lines and sums the area of the trapezium shape they form @@ -40,7 +39,6 @@ def trapezoidal_area( area = 0.0 for _ in range(steps): - # Approximates small segments of curve as linear and solve # for trapezoidal area x2 = (x_end - x_start) / steps + x1 diff --git a/maths/primelib.py b/maths/primelib.py index 9586227ea3ca..81d5737063f0 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -59,7 +59,6 @@ def is_prime(number: int) -> bool: status = False 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. if number % divisor == 0: @@ -95,9 +94,7 @@ def sieve_er(n): # actual sieve of erathostenes for i in range(len(begin_list)): - for j in range(i + 1, len(begin_list)): - if (begin_list[i] != 0) and (begin_list[j] % begin_list[i] == 0): begin_list[j] = 0 @@ -128,9 +125,7 @@ def get_prime_numbers(n): # 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 is_prime(number): - ans.append(number) # precondition @@ -160,14 +155,11 @@ def prime_factorization(number): quotient = number if number == 0 or number == 1: - ans.append(number) # if 'number' not prime then builds the prime factorization of 'number' elif not is_prime(number): - while quotient != 1: - if is_prime(factor) and (quotient % factor == 0): ans.append(factor) quotient /= factor @@ -298,11 +290,9 @@ def goldbach(number): loop = True while i < len_pn and loop: - j = i + 1 while j < len_pn and loop: - if prime_numbers[i] + prime_numbers[j] == number: loop = False ans.append(prime_numbers[i]) @@ -345,7 +335,6 @@ def gcd(number1, number2): rest = 0 while number2 != 0: - rest = number1 % number2 number1 = number2 number2 = rest @@ -380,13 +369,11 @@ def kg_v(number1, number2): # for kgV (x,1) if number1 > 1 and number2 > 1: - # builds the prime factorization of 'number1' and 'number2' prime_fac_1 = prime_factorization(number1) prime_fac_2 = prime_factorization(number2) elif number1 == 1 or number2 == 1: - prime_fac_1 = [] prime_fac_2 = [] ans = max(number1, number2) @@ -398,11 +385,8 @@ def kg_v(number1, number2): # iterates through primeFac1 for n in prime_fac_1: - if n not in done: - if n in prime_fac_2: - count1 = prime_fac_1.count(n) count2 = prime_fac_2.count(n) @@ -410,7 +394,6 @@ def kg_v(number1, number2): ans *= n else: - count1 = prime_fac_1.count(n) for _ in range(count1): @@ -420,9 +403,7 @@ def kg_v(number1, number2): # iterates through primeFac2 for n in prime_fac_2: - if n not in done: - count2 = prime_fac_2.count(n) for _ in range(count2): @@ -455,7 +436,6 @@ def get_prime(n): ans = 2 # this variable holds the answer while index < n: - index += 1 ans += 1 # counts to the next number @@ -499,7 +479,6 @@ def get_primes_between(p_number_1, p_number_2): number += 1 while number < p_number_2: - ans.append(number) number += 1 @@ -534,7 +513,6 @@ def get_divisors(n): ans = [] # will be returned. for divisor in range(1, n + 1): - if n % divisor == 0: ans.append(divisor) @@ -638,7 +616,6 @@ def fib(n): ans = 1 # this will be return for _ in range(n - 1): - tmp = ans ans += fib1 fib1 = tmp diff --git a/maths/segmented_sieve.py b/maths/segmented_sieve.py index 35ed9702b3be..e950a83b752a 100644 --- a/maths/segmented_sieve.py +++ b/maths/segmented_sieve.py @@ -25,7 +25,6 @@ def sieve(n: int) -> list[int]: while low <= n: temp = [True] * (high - low + 1) for each in in_prime: - t = math.floor(low / each) * each if t < low: t += each diff --git a/maths/two_pointer.py b/maths/two_pointer.py index ff234cddc9e4..d0fb0fc9c2f1 100644 --- a/maths/two_pointer.py +++ b/maths/two_pointer.py @@ -43,7 +43,6 @@ def two_pointer(nums: list[int], target: int) -> list[int]: j = len(nums) - 1 while i < j: - if nums[i] + nums[j] == target: return [i, j] elif nums[i] + nums[j] < target: diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index 624bbfe1061c..483fb000f86b 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -3,7 +3,6 @@ def zeller(date_input: str) -> str: - """ Zellers Congruence Algorithm Find the day of the week for nearly any Gregorian or Julian calendar date diff --git a/matrix/largest_square_area_in_matrix.py b/matrix/largest_square_area_in_matrix.py index cf975cb7ce1f..a93369c56bbd 100644 --- a/matrix/largest_square_area_in_matrix.py +++ b/matrix/largest_square_area_in_matrix.py @@ -59,7 +59,6 @@ def largest_square_area_in_matrix_top_down_approch( """ def update_area_of_max_square(row: int, col: int) -> int: - # BASE CASE if row >= rows or col >= cols: return 0 @@ -138,7 +137,6 @@ def largest_square_area_in_matrix_bottom_up( largest_square_area = 0 for row in range(rows - 1, -1, -1): for col in range(cols - 1, -1, -1): - right = dp_array[row][col + 1] diagonal = dp_array[row + 1][col + 1] bottom = dp_array[row + 1][col] @@ -169,7 +167,6 @@ def largest_square_area_in_matrix_bottom_up_space_optimization( largest_square_area = 0 for row in range(rows - 1, -1, -1): for col in range(cols - 1, -1, -1): - right = current_row[col + 1] diagonal = next_row[col + 1] bottom = next_row[col] diff --git a/other/activity_selection.py b/other/activity_selection.py index 18ff6a24c32a..2cc08d959862 100644 --- a/other/activity_selection.py +++ b/other/activity_selection.py @@ -25,7 +25,6 @@ def print_max_activities(start: list[int], finish: list[int]) -> None: # 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 diff --git a/other/nested_brackets.py b/other/nested_brackets.py index 9dd9a0f042ed..3f61a4e7006c 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -15,14 +15,12 @@ def is_balanced(s): - stack = [] open_brackets = set({"(", "[", "{"}) closed_brackets = set({")", "]", "}"}) open_to_closed = dict({"{": "}", "[": "]", "(": ")"}) for i in range(len(s)): - if s[i] in open_brackets: stack.append(s[i]) diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py index 1e6293f8465c..00d87cfc0b73 100644 --- a/other/scoring_algorithm.py +++ b/other/scoring_algorithm.py @@ -26,7 +26,6 @@ def procentual_proximity( source_data: list[list[float]], weights: list[int] ) -> list[list[float]]: - """ weights - int list possible values - 0 / 1 diff --git a/other/sdes.py b/other/sdes.py index 695675000632..31105984b9bb 100644 --- a/other/sdes.py +++ b/other/sdes.py @@ -54,7 +54,6 @@ def function(expansion, s0, s1, key, message): if __name__ == "__main__": - key = input("Enter 10 bit key: ") message = input("Enter 8 bit message: ") diff --git a/physics/casimir_effect.py b/physics/casimir_effect.py index ee8a6c1eba53..e4a77e5b593f 100644 --- a/physics/casimir_effect.py +++ b/physics/casimir_effect.py @@ -47,7 +47,6 @@ def casimir_force(force: float, area: float, distance: float) -> dict[str, float]: - """ Input Parameters ---------------- diff --git a/physics/hubble_parameter.py b/physics/hubble_parameter.py index 7985647222c9..6bc62e7131c5 100644 --- a/physics/hubble_parameter.py +++ b/physics/hubble_parameter.py @@ -34,7 +34,6 @@ def hubble_parameter( dark_energy: float, redshift: float, ) -> float: - """ Input Parameters ---------------- diff --git a/physics/newtons_law_of_gravitation.py b/physics/newtons_law_of_gravitation.py index 0bb27bb2415d..4bbeddd61d5b 100644 --- a/physics/newtons_law_of_gravitation.py +++ b/physics/newtons_law_of_gravitation.py @@ -28,7 +28,6 @@ def gravitational_law( force: float, mass_1: float, mass_2: float, distance: float ) -> dict[str, float]: - """ Input Parameters ---------------- diff --git a/project_euler/problem_004/sol1.py b/project_euler/problem_004/sol1.py index b1e229289988..f237afdd942d 100644 --- a/project_euler/problem_004/sol1.py +++ b/project_euler/problem_004/sol1.py @@ -32,12 +32,10 @@ def solution(n: int = 998001) -> int: # fetches the next number for number in range(n - 1, 9999, -1): - str_number = str(number) # 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 diff --git a/project_euler/problem_074/sol2.py b/project_euler/problem_074/sol2.py index d76bb014d629..b54bc023e387 100644 --- a/project_euler/problem_074/sol2.py +++ b/project_euler/problem_074/sol2.py @@ -111,7 +111,6 @@ def solution(chain_length: int = 60, number_limit: int = 1000000) -> int: chain_sets_lengths: dict[int, int] = {} for start_chain_element in range(1, number_limit): - # The temporary set will contain the elements of the chain chain_set = set() chain_set_length = 0 diff --git a/project_euler/problem_089/sol1.py b/project_euler/problem_089/sol1.py index 83609cd236e1..123159bdce09 100644 --- a/project_euler/problem_089/sol1.py +++ b/project_euler/problem_089/sol1.py @@ -138,5 +138,4 @@ def solution(roman_numerals_filename: str = "/p089_roman.txt") -> int: if __name__ == "__main__": - print(f"{solution() = }") diff --git a/project_euler/problem_092/sol1.py b/project_euler/problem_092/sol1.py index 33a6c06946f7..8d3f0c9ddd7b 100644 --- a/project_euler/problem_092/sol1.py +++ b/project_euler/problem_092/sol1.py @@ -15,7 +15,6 @@ def next_number(number: int) -> int: - """ Returns the next number of the chain by adding the square of each digit to form a new number. @@ -31,7 +30,6 @@ def next_number(number: int) -> int: sum_of_digits_squared = 0 while number: - # Increased Speed Slightly by checking every 5 digits together. sum_of_digits_squared += DIGITS_SQUARED[number % 100000] number //= 100000 diff --git a/quantum/q_fourier_transform.py b/quantum/q_fourier_transform.py index 07a257579529..762ac408190e 100644 --- a/quantum/q_fourier_transform.py +++ b/quantum/q_fourier_transform.py @@ -72,7 +72,6 @@ def quantum_fourier_transform(number_of_qubits: int = 3) -> qiskit.result.counts counter = number_of_qubits for i in range(counter): - quantum_circuit.h(number_of_qubits - i - 1) counter -= 1 for j in range(counter): diff --git a/quantum/quantum_teleportation.py b/quantum/quantum_teleportation.py index d04b44d15a05..5da79ed20183 100644 --- a/quantum/quantum_teleportation.py +++ b/quantum/quantum_teleportation.py @@ -18,7 +18,6 @@ def quantum_teleportation( theta: float = np.pi / 2, phi: float = np.pi / 2, lam: float = np.pi / 2 ) -> qiskit.result.counts.Counts: - """ # >>> quantum_teleportation() #{'00': 500, '11': 500} # ideally diff --git a/scheduling/highest_response_ratio_next.py b/scheduling/highest_response_ratio_next.py index a5c62ddbe952..9c999ec65053 100644 --- a/scheduling/highest_response_ratio_next.py +++ b/scheduling/highest_response_ratio_next.py @@ -37,7 +37,6 @@ def calculate_turn_around_time( arrival_time.sort() while no_of_process > finished_process_count: - """ If the current time is less than the arrival time of the process that arrives first among the processes that have not been performed, @@ -94,7 +93,6 @@ def calculate_waiting_time( if __name__ == "__main__": - no_of_process = 5 process_name = ["A", "B", "C", "D", "E"] arrival_time = [1, 2, 3, 4, 5] diff --git a/searches/binary_search.py b/searches/binary_search.py index 88fee47157c6..05dadd4fe965 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -261,7 +261,6 @@ def binary_search_std_lib(sorted_collection: list[int], item: int) -> int | None def binary_search_by_recursion( sorted_collection: list[int], item: int, left: int, right: int ) -> int | None: - """Pure implementation of binary search algorithm in Python by recursion Be careful collection must be ascending sorted, otherwise result will be diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index 35e6bc506661..49194c2600a0 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -49,7 +49,6 @@ def interpolation_search(sorted_collection, item): 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 ascending sorted, otherwise result will be unpredictable diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 3e1728286d98..d998ddc55976 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -220,7 +220,6 @@ def tabu_search( while not found: i = 0 while i < len(best_solution): - if best_solution[i] != solution[i]: first_exchange_node = best_solution[i] second_exchange_node = solution[i] diff --git a/searches/ternary_search.py b/searches/ternary_search.py index 9830cce36000..cb36e72faac6 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -103,7 +103,6 @@ def ite_ternary_search(array: list[int], target: int) -> int: left = two_third + 1 else: - left = one_third + 1 right = two_third - 1 else: diff --git a/sorts/comb_sort.py b/sorts/comb_sort.py index 16bd10c78fe5..3c8b1e99a454 100644 --- a/sorts/comb_sort.py +++ b/sorts/comb_sort.py @@ -37,7 +37,6 @@ def comb_sort(data: list) -> list: completed = False while not completed: - # Update the gap value for a next comb gap = int(gap / shrink_factor) if gap <= 1: diff --git a/sorts/odd_even_sort.py b/sorts/odd_even_sort.py index 9ef4462c72c0..7dfe03054bc3 100644 --- a/sorts/odd_even_sort.py +++ b/sorts/odd_even_sort.py @@ -30,7 +30,6 @@ def odd_even_sort(input_list: list) -> list: is_sorted = True for i in range(0, len(input_list) - 1, 2): # iterating over all even indices if input_list[i] > input_list[i + 1]: - input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] # swapping if elements not in order is_sorted = False diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index b656df3a3a90..87b0e4d1e20f 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -34,7 +34,6 @@ def oe_process(position, value, l_send, r_send, lr_cv, rr_cv, result_pipe): # 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 r_send is not None: # send your value to your right neighbor process_lock.acquire() diff --git a/sorts/random_normal_distribution_quicksort.py b/sorts/random_normal_distribution_quicksort.py index 5777d5cb2e7a..f7f60903c546 100644 --- a/sorts/random_normal_distribution_quicksort.py +++ b/sorts/random_normal_distribution_quicksort.py @@ -19,7 +19,6 @@ def _in_place_quick_sort(a, start, end): def _in_place_partition(a, start, end): - count = 0 pivot = randint(start, end) temp = a[end] @@ -27,7 +26,6 @@ def _in_place_partition(a, start, end): a[pivot] = temp new_pivot_index = start - 1 for index in range(start, end): - count += 1 if a[index] < a[end]: # check if current val is less than pivot value new_pivot_index = new_pivot_index + 1 diff --git a/sorts/shrink_shell_sort.py b/sorts/shrink_shell_sort.py index 69992bfb75bc..f77b73d013a7 100644 --- a/sorts/shrink_shell_sort.py +++ b/sorts/shrink_shell_sort.py @@ -44,7 +44,6 @@ def shell_sort(collection: list) -> list: # Continue sorting until the gap is 1 while gap > 1: - # Decrease the gap value gap = int(gap / shrink) diff --git a/sorts/stooge_sort.py b/sorts/stooge_sort.py index de997a85df12..9a5bedeae21b 100644 --- a/sorts/stooge_sort.py +++ b/sorts/stooge_sort.py @@ -12,7 +12,6 @@ def stooge_sort(arr): def stooge(arr, i, h): - if i >= h: return diff --git a/sorts/tim_sort.py b/sorts/tim_sort.py index b95ff34cf384..c90c7e80390b 100644 --- a/sorts/tim_sort.py +++ b/sorts/tim_sort.py @@ -73,7 +73,6 @@ def tim_sort(lst): def main(): - lst = [5, 9, 10, 3, -4, 5, 178, 92, 46, -18, 0, 7] sorted_lst = tim_sort(lst) print(sorted_lst) diff --git a/strings/dna.py b/strings/dna.py index c2b96110b893..33e1063f4124 100644 --- a/strings/dna.py +++ b/strings/dna.py @@ -2,7 +2,6 @@ def dna(dna: str) -> str: - """ https://en.wikipedia.org/wiki/DNA Returns the second side of a DNA strand diff --git a/strings/hamming_distance.py b/strings/hamming_distance.py index 5de27dc77f44..a28949172aa4 100644 --- a/strings/hamming_distance.py +++ b/strings/hamming_distance.py @@ -35,7 +35,6 @@ def hamming_distance(string1: str, string2: str) -> int: if __name__ == "__main__": - import doctest doctest.testmod() diff --git a/strings/levenshtein_distance.py b/strings/levenshtein_distance.py index 9f7a7e3e65c4..7be4074dc39b 100644 --- a/strings/levenshtein_distance.py +++ b/strings/levenshtein_distance.py @@ -44,11 +44,9 @@ def levenshtein_distance(first_word: str, second_word: str) -> int: previous_row = list(range(len(second_word) + 1)) for i, c1 in enumerate(first_word): - current_row = [i + 1] for j, c2 in enumerate(second_word): - # Calculate insertions, deletions and substitutions insertions = previous_row[j + 1] + 1 deletions = current_row[j] + 1 diff --git a/strings/prefix_function.py b/strings/prefix_function.py index 6eca01635fe3..65bbe9100735 100644 --- a/strings/prefix_function.py +++ b/strings/prefix_function.py @@ -29,7 +29,6 @@ def prefix_function(input_string: str) -> list: 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]: diff --git a/strings/text_justification.py b/strings/text_justification.py index 5e86456c2456..b0ef12231224 100644 --- a/strings/text_justification.py +++ b/strings/text_justification.py @@ -33,7 +33,6 @@ def text_justification(word: str, max_width: int) -> list: words = word.split() def justify(line: list, width: int, max_width: int) -> str: - overall_spaces_count = max_width - width words_count = len(line) if len(line) == 1: diff --git a/web_programming/fetch_anime_and_play.py b/web_programming/fetch_anime_and_play.py index e11948d0ae78..3bd4f704dd8d 100644 --- a/web_programming/fetch_anime_and_play.py +++ b/web_programming/fetch_anime_and_play.py @@ -8,7 +8,6 @@ def search_scraper(anime_name: str) -> list: - """[summary] Take an url and @@ -66,7 +65,6 @@ def search_scraper(anime_name: str) -> list: def search_anime_episode_list(episode_endpoint: str) -> list: - """[summary] Take an url and @@ -116,7 +114,6 @@ def search_anime_episode_list(episode_endpoint: str) -> list: def get_anime_episode(episode_endpoint: str) -> list: - """[summary] Get click url and download url from episode url @@ -153,7 +150,6 @@ def get_anime_episode(episode_endpoint: str) -> list: if __name__ == "__main__": - anime_name = input("Enter anime name: ").strip() anime_list = search_scraper(anime_name) print("\n") @@ -161,9 +157,8 @@ def get_anime_episode(episode_endpoint: str) -> list: if len(anime_list) == 0: print("No anime found with this name") else: - print(f"Found {len(anime_list)} results: ") - for (i, anime) in enumerate(anime_list): + for i, anime in enumerate(anime_list): anime_title = anime["title"] print(f"{i+1}. {anime_title}") @@ -176,7 +171,7 @@ def get_anime_episode(episode_endpoint: str) -> list: print("No episode found for this anime") else: print(f"Found {len(episode_list)} results: ") - for (i, episode) in enumerate(episode_list): + for i, episode in enumerate(episode_list): print(f"{i+1}. {episode['title']}") episode_choice = int(input("\nChoose an episode by serial no: ").strip()) diff --git a/web_programming/fetch_well_rx_price.py b/web_programming/fetch_well_rx_price.py index 5174f39f9532..ee51b9a5051b 100644 --- a/web_programming/fetch_well_rx_price.py +++ b/web_programming/fetch_well_rx_price.py @@ -37,7 +37,6 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None: """ try: - # Has user provided both inputs? if not drug_name or not zip_code: return None @@ -58,7 +57,6 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None: grid_list = soup.find_all("div", {"class": "grid-x pharmCard"}) if grid_list and len(grid_list) > 0: for grid in grid_list: - # Get the pharmacy price. pharmacy_name = grid.find("p", {"class": "list-title"}).text @@ -79,7 +77,6 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None: if __name__ == "__main__": - # Enter a drug name and a zip code drug_name = input("Enter drug name: ").strip() zip_code = input("Enter zip code: ").strip() @@ -89,10 +86,8 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None: ) if pharmacy_price_list: - print(f"\nSearch results for {drug_name} at location {zip_code}:") for pharmacy_price in pharmacy_price_list: - name = pharmacy_price["pharmacy_name"] price = pharmacy_price["price"] diff --git a/web_programming/get_user_tweets.py b/web_programming/get_user_tweets.py index 28cf85541dc4..3abc69715727 100644 --- a/web_programming/get_user_tweets.py +++ b/web_programming/get_user_tweets.py @@ -10,7 +10,6 @@ def get_all_tweets(screen_name: str) -> None: - # authorize twitter, initialize tweepy auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_key, access_secret) From 77b4fa8b3f2070ff708405cca1381b7860e316ab Mon Sep 17 00:00:00 2001 From: Damon Gregory <46330424+SheriffHobo@users.noreply.github.com> Date: Sun, 12 Feb 2023 07:55:25 -0800 Subject: [PATCH 2110/2908] fix_ci_badge (#8134) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da80c012b0c6..68a6e5e6fbce 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@
    - GitHub Workflow Status + GitHub Workflow Status pre-commit From 126e89d8a3983c1ffc9b3eefa1fbaff0f6fe4ead Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 22:05:56 +0100 Subject: [PATCH 2111/2908] [pre-commit.ci] pre-commit autoupdate (#8141) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/tox-dev/pyproject-fmt: 0.6.0 → 0.8.0](https://github.com/tox-dev/pyproject-fmt/compare/0.6.0...0.8.0) - [github.com/pre-commit/mirrors-mypy: v0.991 → v1.0.0](https://github.com/pre-commit/mirrors-mypy/compare/v0.991...v1.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f8d1a65db27b..a1496984f950 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - --profile=black - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.6.0" + rev: "0.8.0" hooks: - id: pyproject-fmt @@ -62,7 +62,7 @@ repos: *flake8-plugins - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 + rev: v1.0.0 hooks: - id: mypy args: From 1bf03889c5e34420001e72b5d26cc0846dcd122a Mon Sep 17 00:00:00 2001 From: Jan Wojciechowski <96974442+yanvoi@users.noreply.github.com> Date: Sun, 19 Feb 2023 23:14:01 +0100 Subject: [PATCH 2112/2908] Update bogo_sort.py (#8144) --- sorts/bogo_sort.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sorts/bogo_sort.py b/sorts/bogo_sort.py index b72f2089f3d2..9c133f0d8a55 100644 --- a/sorts/bogo_sort.py +++ b/sorts/bogo_sort.py @@ -31,8 +31,6 @@ def bogo_sort(collection): """ def is_sorted(collection): - if len(collection) < 2: - return True for i in range(len(collection) - 1): if collection[i] > collection[i + 1]: return False From 67676c3b790d9631ea99c89f71dc2bf65e9aa2ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 08:33:44 +0100 Subject: [PATCH 2113/2908] [pre-commit.ci] pre-commit autoupdate (#8149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/tox-dev/pyproject-fmt: 0.8.0 → 0.9.1](https://github.com/tox-dev/pyproject-fmt/compare/0.8.0...0.9.1) - [github.com/pre-commit/mirrors-mypy: v1.0.0 → v1.0.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.0...v1.0.1) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1496984f950..93064949e194 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - --profile=black - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.8.0" + rev: "0.9.1" hooks: - id: pyproject-fmt @@ -62,7 +62,7 @@ repos: *flake8-plugins - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.0 + rev: v1.0.1 hooks: - id: mypy args: diff --git a/pyproject.toml b/pyproject.toml index 410e7655b2b5..5f9b1aa06c0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,6 @@ addopts = [ "--showlocals", ] - [tool.coverage.report] omit = [".env/*"] sort = "Cover" From 1c15cdff70893bc27ced2b390959e1d9cc493628 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 23:08:40 +0100 Subject: [PATCH 2114/2908] [pre-commit.ci] pre-commit autoupdate (#8160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/tox-dev/pyproject-fmt: 0.9.1 → 0.9.2](https://github.com/tox-dev/pyproject-fmt/compare/0.9.1...0.9.2) * pre-commit: Add ruff --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93064949e194..9f27f985bb6a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - --profile=black - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.9.1" + rev: "0.9.2" hooks: - id: pyproject-fmt @@ -43,6 +43,13 @@ repos: args: - --py311-plus + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.253 + hooks: + - id: ruff + args: + - --ignore=E741 + - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: From 64543faa980b526f79d287a073ebb7554749faf9 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 1 Mar 2023 17:23:33 +0100 Subject: [PATCH 2115/2908] Make some ruff fixes (#8154) * Make some ruff fixes * Undo manual fix * Undo manual fix * Updates from ruff=0.0.251 --- audio_filters/iir_filter.py | 2 +- backtracking/n_queens_math.py | 6 +++--- backtracking/sum_of_subsets.py | 2 +- ciphers/bifid.py | 2 +- ciphers/diffie_hellman.py | 16 ++++++++-------- ciphers/polybius.py | 2 +- ciphers/xor_cipher.py | 18 ++++++++---------- computer_vision/mosaic_augmentation.py | 2 +- .../binary_tree/binary_search_tree.py | 2 +- .../binary_tree/binary_tree_traversals.py | 4 ++-- .../binary_tree/inorder_tree_traversal_2022.py | 2 +- data_structures/binary_tree/red_black_tree.py | 5 ++--- .../hashing/number_theory/prime_numbers.py | 2 +- data_structures/heap/binomial_heap.py | 4 ++-- .../linked_list/doubly_linked_list_two.py | 2 +- .../linked_list/singly_linked_list.py | 1 + data_structures/linked_list/skip_list.py | 5 +---- .../queue/circular_queue_linked_list.py | 2 +- .../dilation_operation.py | 2 +- .../erosion_operation.py | 2 +- dynamic_programming/all_construct.py | 2 +- dynamic_programming/fizz_buzz.py | 2 +- .../longest_common_subsequence.py | 10 ++-------- .../longest_increasing_subsequence.py | 2 +- graphs/basic_graphs.py | 14 ++++++-------- graphs/check_cycle.py | 9 ++++----- graphs/connected_components.py | 2 +- graphs/dijkstra_algorithm.py | 2 +- .../edmonds_karp_multiple_source_and_sink.py | 5 ++--- graphs/frequent_pattern_graph_miner.py | 6 +++--- graphs/minimum_spanning_tree_boruvka.py | 1 + graphs/minimum_spanning_tree_prims.py | 5 +---- graphs/minimum_spanning_tree_prims2.py | 16 +++++++--------- hashes/hamming_code.py | 5 ++--- linear_algebra/src/lib.py | 7 ++++--- machine_learning/gradient_descent.py | 2 ++ machine_learning/k_means_clust.py | 4 ++-- .../sequential_minimum_optimization.py | 9 ++++----- maths/abs.py | 6 +++--- maths/binary_exp_mod.py | 2 +- maths/jaccard_similarity.py | 1 + maths/largest_of_very_large_numbers.py | 1 + maths/radix2_fft.py | 5 +---- .../back_propagation_neural_network.py | 1 + other/graham_scan.py | 7 +++---- other/nested_brackets.py | 9 ++++----- physics/hubble_parameter.py | 4 ++-- project_euler/problem_005/sol1.py | 1 + project_euler/problem_009/sol1.py | 5 ++--- project_euler/problem_014/sol2.py | 5 +---- project_euler/problem_018/solution.py | 10 ++-------- project_euler/problem_019/sol1.py | 2 +- project_euler/problem_033/sol1.py | 8 +++----- project_euler/problem_064/sol1.py | 5 ++--- project_euler/problem_067/sol1.py | 10 ++-------- project_euler/problem_109/sol1.py | 2 +- project_euler/problem_203/sol1.py | 4 ++-- scheduling/shortest_job_first.py | 11 +++++------ scripts/build_directory_md.py | 5 ++--- searches/binary_tree_traversal.py | 1 + sorts/circle_sort.py | 13 ++++++------- sorts/counting_sort.py | 2 +- sorts/msd_radix_sort.py | 2 +- sorts/quick_sort.py | 2 +- sorts/recursive_quick_sort.py | 10 +++++----- sorts/tim_sort.py | 4 ++-- strings/autocomplete_using_trie.py | 5 +---- strings/check_anagrams.py | 5 +---- strings/is_palindrome.py | 5 +---- strings/snake_case_to_camel_pascal_case.py | 2 +- web_programming/convert_number_to_words.py | 6 +++--- web_programming/instagram_crawler.py | 2 +- web_programming/open_google_results.py | 5 +---- 73 files changed, 151 insertions(+), 203 deletions(-) diff --git a/audio_filters/iir_filter.py b/audio_filters/iir_filter.py index aae320365012..bd448175f6f3 100644 --- a/audio_filters/iir_filter.py +++ b/audio_filters/iir_filter.py @@ -47,7 +47,7 @@ def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None >>> filt.set_coefficients(a_coeffs, b_coeffs) """ if len(a_coeffs) < self.order: - a_coeffs = [1.0] + a_coeffs + a_coeffs = [1.0, *a_coeffs] if len(a_coeffs) != self.order + 1: raise ValueError( diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py index 23bd1590618b..f3b08ab0a05f 100644 --- a/backtracking/n_queens_math.py +++ b/backtracking/n_queens_math.py @@ -129,9 +129,9 @@ def depth_first_search( # 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], + [*possible_board, col], + [*diagonal_right_collisions, row - col], + [*diagonal_left_collisions, row + col], boards, n, ) diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index 128e290718cd..c5e23321cb0c 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -44,7 +44,7 @@ def create_state_space_tree( nums, max_sum, index + 1, - path + [nums[index]], + [*path, nums[index]], result, remaining_nums_sum - nums[index], ) diff --git a/ciphers/bifid.py b/ciphers/bifid.py index c005e051a6ba..a15b381640aa 100644 --- a/ciphers/bifid.py +++ b/ciphers/bifid.py @@ -33,7 +33,7 @@ def letter_to_numbers(self, letter: str) -> np.ndarray: >>> np.array_equal(BifidCipher().letter_to_numbers('u'), [4,5]) True """ - index1, index2 = np.where(self.SQUARE == letter) + index1, index2 = np.where(letter == self.SQUARE) indexes = np.concatenate([index1 + 1, index2 + 1]) return indexes diff --git a/ciphers/diffie_hellman.py b/ciphers/diffie_hellman.py index 072f4aaaa6da..cd40a6b9c3b3 100644 --- a/ciphers/diffie_hellman.py +++ b/ciphers/diffie_hellman.py @@ -228,10 +228,10 @@ def generate_public_key(self) -> str: def is_valid_public_key(self, key: int) -> bool: # check if the other public key is valid based on NIST SP800-56 - if 2 <= key and key <= self.prime - 2: - if pow(key, (self.prime - 1) // 2, self.prime) == 1: - return True - return False + return ( + 2 <= key <= self.prime - 2 + and pow(key, (self.prime - 1) // 2, self.prime) == 1 + ) def generate_shared_key(self, other_key_str: str) -> str: other_key = int(other_key_str, base=16) @@ -243,10 +243,10 @@ def generate_shared_key(self, other_key_str: str) -> str: @staticmethod def is_valid_public_key_static(remote_public_key_str: int, prime: int) -> bool: # check if the other public key is valid based on NIST SP800-56 - if 2 <= remote_public_key_str and remote_public_key_str <= prime - 2: - if pow(remote_public_key_str, (prime - 1) // 2, prime) == 1: - return True - return False + return ( + 2 <= remote_public_key_str <= prime - 2 + and pow(remote_public_key_str, (prime - 1) // 2, prime) == 1 + ) @staticmethod def generate_shared_key_static( diff --git a/ciphers/polybius.py b/ciphers/polybius.py index 3539ab70c303..d83badf4ac0a 100644 --- a/ciphers/polybius.py +++ b/ciphers/polybius.py @@ -31,7 +31,7 @@ def letter_to_numbers(self, letter: str) -> np.ndarray: >>> np.array_equal(PolybiusCipher().letter_to_numbers('u'), [4,5]) True """ - index1, index2 = np.where(self.SQUARE == letter) + index1, index2 = np.where(letter == self.SQUARE) indexes = np.concatenate([index1 + 1, index2 + 1]) return indexes diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 379ef0ef7e50..0f369e38f85f 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -128,11 +128,10 @@ def encrypt_file(self, file: str, key: int = 0) -> bool: assert isinstance(file, str) and isinstance(key, int) try: - 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)) + with open(file) as fin, open("encrypt.out", "w+") as fout: + # actual encrypt-process + for line in fin: + fout.write(self.encrypt_string(line, key)) except OSError: return False @@ -152,11 +151,10 @@ def decrypt_file(self, file: str, key: int) -> bool: assert isinstance(file, str) and isinstance(key, int) try: - 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)) + with open(file) as fin, open("decrypt.out", "w+") as fout: + # actual encrypt-process + for line in fin: + fout.write(self.decrypt_string(line, key)) except OSError: return False diff --git a/computer_vision/mosaic_augmentation.py b/computer_vision/mosaic_augmentation.py index e2953749753f..c150126d6bfb 100644 --- a/computer_vision/mosaic_augmentation.py +++ b/computer_vision/mosaic_augmentation.py @@ -159,7 +159,7 @@ def update_image_and_anno( new_anno.append([bbox[0], xmin, ymin, xmax, ymax]) # Remove bounding box small than scale of filter - if 0 < filter_scale: + if filter_scale > 0: new_anno = [ anno for anno in new_anno diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index fc512944eb50..cd88cc10e697 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -60,7 +60,7 @@ def __insert(self, value) -> None: else: # Tree is not empty parent_node = self.root # from root if parent_node is None: - return None + return while True: # While we don't get to a leaf if value < parent_node.value: # We go left if parent_node.left is None: diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 24dd1bd8cdc8..71a895e76ce4 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -37,7 +37,7 @@ def preorder(root: Node | None) -> list[int]: >>> preorder(make_tree()) [1, 2, 4, 5, 3] """ - return [root.data] + preorder(root.left) + preorder(root.right) if root else [] + return [root.data, *preorder(root.left), *preorder(root.right)] if root else [] def postorder(root: Node | None) -> list[int]: @@ -55,7 +55,7 @@ def inorder(root: Node | None) -> list[int]: >>> inorder(make_tree()) [4, 2, 5, 1, 3] """ - return inorder(root.left) + [root.data] + inorder(root.right) if root else [] + return [*inorder(root.left), root.data, *inorder(root.right)] if root else [] def height(root: Node | None) -> int: diff --git a/data_structures/binary_tree/inorder_tree_traversal_2022.py b/data_structures/binary_tree/inorder_tree_traversal_2022.py index e94ba7013a82..1357527d2953 100644 --- a/data_structures/binary_tree/inorder_tree_traversal_2022.py +++ b/data_structures/binary_tree/inorder_tree_traversal_2022.py @@ -50,7 +50,7 @@ def inorder(node: None | BinaryTreeNode) -> list[int]: # if node is None,return """ if node: inorder_array = inorder(node.left_child) - inorder_array = inorder_array + [node.data] + inorder_array = [*inorder_array, node.data] inorder_array = inorder_array + inorder(node.right_child) else: inorder_array = [] diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index a9dbd699c3c1..b50d75d33689 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -319,9 +319,8 @@ def check_coloring(self) -> bool: """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.color == 1 and 1 in (color(self.left), color(self.right)): + return False if self.left and not self.left.check_coloring(): return False if self.right and not self.right.check_coloring(): diff --git a/data_structures/hashing/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py index b88ab76ecc23..0c25896f9880 100644 --- a/data_structures/hashing/number_theory/prime_numbers.py +++ b/data_structures/hashing/number_theory/prime_numbers.py @@ -52,7 +52,7 @@ def next_prime(value, factor=1, **kwargs): first_value_val = value while not is_prime(value): - value += 1 if not ("desc" in kwargs.keys() and kwargs["desc"] is True) else -1 + value += 1 if not ("desc" in kwargs and kwargs["desc"] is True) else -1 if value == first_value_val: return next_prime(value + 1, **kwargs) diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index 2e05c5c80a22..099bd2871023 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -136,12 +136,12 @@ def merge_heaps(self, other): # Empty heaps corner cases if other.size == 0: - return + return None if self.size == 0: self.size = other.size self.bottom_root = other.bottom_root self.min_node = other.min_node - return + return None # Update size self.size = self.size + other.size diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py index c19309c9f5a7..e993cc5a20af 100644 --- a/data_structures/linked_list/doubly_linked_list_two.py +++ b/data_structures/linked_list/doubly_linked_list_two.py @@ -128,7 +128,7 @@ def insert_at_position(self, position: int, value: int) -> None: while node: if current_position == position: self.insert_before_node(node, new_node) - return None + return current_position += 1 node = node.next self.insert_after_node(self.tail, new_node) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 3e52c7e43cf5..bdeb5922ac67 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -107,6 +107,7 @@ def __getitem__(self, index: int) -> Any: for i, node in enumerate(self): if i == index: return node + return None # Used to change the data of a particular node def __setitem__(self, index: int, data: Any) -> None: diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index 96b0db7c896b..4413c53e520e 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -388,10 +388,7 @@ def traverse_keys(node): 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 + return all(next_item >= item for item, next_item in zip(lst, lst[1:])) skip_list = SkipList() for i in range(10): diff --git a/data_structures/queue/circular_queue_linked_list.py b/data_structures/queue/circular_queue_linked_list.py index e8c2b8bffc06..62042c4bce96 100644 --- a/data_structures/queue/circular_queue_linked_list.py +++ b/data_structures/queue/circular_queue_linked_list.py @@ -127,7 +127,7 @@ def dequeue(self) -> Any: """ self.check_can_perform_operation() if self.rear is None or self.front is None: - return + return None if self.front == self.rear: data = self.front.data self.front.data = None diff --git a/digital_image_processing/morphological_operations/dilation_operation.py b/digital_image_processing/morphological_operations/dilation_operation.py index 274880b0a50a..c8380737d219 100644 --- a/digital_image_processing/morphological_operations/dilation_operation.py +++ b/digital_image_processing/morphological_operations/dilation_operation.py @@ -32,7 +32,7 @@ def gray2binary(gray: np.array) -> np.array: [False, True, False], [False, True, False]]) """ - return (127 < gray) & (gray <= 255) + return (gray > 127) & (gray <= 255) def dilation(image: np.array, kernel: np.array) -> np.array: diff --git a/digital_image_processing/morphological_operations/erosion_operation.py b/digital_image_processing/morphological_operations/erosion_operation.py index 4b0a5eee8c03..c2cde2ea6990 100644 --- a/digital_image_processing/morphological_operations/erosion_operation.py +++ b/digital_image_processing/morphological_operations/erosion_operation.py @@ -32,7 +32,7 @@ def gray2binary(gray: np.array) -> np.array: [False, True, False], [False, True, False]]) """ - return (127 < gray) & (gray <= 255) + return (gray > 127) & (gray <= 255) def erosion(image: np.array, kernel: np.array) -> np.array: diff --git a/dynamic_programming/all_construct.py b/dynamic_programming/all_construct.py index 3839d01e6db0..6e53a702cbb1 100644 --- a/dynamic_programming/all_construct.py +++ b/dynamic_programming/all_construct.py @@ -34,7 +34,7 @@ def all_construct(target: str, word_bank: list[str] | None = None) -> list[list[ # slice condition if target[i : i + len(word)] == word: new_combinations: list[list[str]] = [ - [word] + way for way in table[i] + [word, *way] for way in table[i] ] # adds the word to every combination the current position holds # now,push that combination to the table[i+len(word)] diff --git a/dynamic_programming/fizz_buzz.py b/dynamic_programming/fizz_buzz.py index e77ab3de7b4b..e29116437a93 100644 --- a/dynamic_programming/fizz_buzz.py +++ b/dynamic_programming/fizz_buzz.py @@ -49,7 +49,7 @@ def fizz_buzz(number: int, iterations: int) -> str: out += "Fizz" if number % 5 == 0: out += "Buzz" - if not number % 3 == 0 and not number % 5 == 0: + if 0 not in (number % 3, number % 5): out += str(number) # print(out) diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 3468fd87da8d..178b4169b213 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -42,20 +42,14 @@ 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]: - match = 1 - else: - match = 0 + match = 1 if x[i - 1] == y[j - 1] else 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 j > 0: - if x[i - 1] == y[j - 1]: - match = 1 - else: - match = 0 + match = 1 if x[i - 1] == y[j - 1] else 0 if l[i][j] == l[i - 1][j - 1] + match: if match == 1: diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index 6feed23529f1..d827893763c5 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -48,7 +48,7 @@ def longest_subsequence(array: list[int]) -> list[int]: # This function is recu i += 1 temp_array = [element for element in array[1:] if element >= pivot] - temp_array = [pivot] + longest_subsequence(temp_array) + temp_array = [pivot, *longest_subsequence(temp_array)] if len(temp_array) > len(longest_subseq): return temp_array else: diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 298a97bf0e17..065b6185c123 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -139,10 +139,9 @@ def dijk(g, s): u = i known.add(u) for v in g[u]: - if v[0] not in known: - if dist[u] + v[1] < dist.get(v[0], 100000): - dist[v[0]] = dist[u] + v[1] - path[v[0]] = u + if v[0] not in known and dist[u] + v[1] < dist.get(v[0], 100000): + dist[v[0]] = dist[u] + v[1] + path[v[0]] = u for i in dist: if i != s: print(dist[i]) @@ -243,10 +242,9 @@ def prim(g, s): u = i known.add(u) for v in g[u]: - if v[0] not in known: - if v[1] < dist.get(v[0], 100000): - dist[v[0]] = v[1] - path[v[0]] = u + if v[0] not in known and v[1] < dist.get(v[0], 100000): + dist[v[0]] = v[1] + path[v[0]] = u return dist diff --git a/graphs/check_cycle.py b/graphs/check_cycle.py index dcc864988ca5..9fd1cd80f116 100644 --- a/graphs/check_cycle.py +++ b/graphs/check_cycle.py @@ -15,11 +15,10 @@ def check_cycle(graph: dict) -> bool: visited: set[int] = set() # To detect a back edge, keep track of vertices currently in the recursion stack rec_stk: set[int] = set() - for node in graph: - if node not in visited: - if depth_first_search(graph, node, visited, rec_stk): - return True - return False + return any( + node not in visited and depth_first_search(graph, node, visited, rec_stk) + for node in graph + ) def depth_first_search(graph: dict, vertex: int, visited: set, rec_stk: set) -> bool: diff --git a/graphs/connected_components.py b/graphs/connected_components.py index 4af7803d74a7..15c7633e13e8 100644 --- a/graphs/connected_components.py +++ b/graphs/connected_components.py @@ -27,7 +27,7 @@ def dfs(graph: dict, vert: int, visited: list) -> list: if not visited[neighbour]: connected_verts += dfs(graph, neighbour, visited) - return [vert] + connected_verts + return [vert, *connected_verts] def connected_components(graph: dict) -> list: diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 1845dad05db2..452138fe904b 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -112,7 +112,7 @@ def dijkstra(self, src): self.dist[src] = 0 q = PriorityQueue() q.insert((0, src)) # (dist from src, node) - for u in self.adjList.keys(): + for u in self.adjList: if u != src: self.dist[u] = sys.maxsize # Infinity self.par[u] = -1 diff --git a/graphs/edmonds_karp_multiple_source_and_sink.py b/graphs/edmonds_karp_multiple_source_and_sink.py index 070d758e63b6..d0610804109f 100644 --- a/graphs/edmonds_karp_multiple_source_and_sink.py +++ b/graphs/edmonds_karp_multiple_source_and_sink.py @@ -163,9 +163,8 @@ def relabel(self, vertex_index): self.graph[vertex_index][to_index] - self.preflow[vertex_index][to_index] > 0 - ): - if min_height is None or self.heights[to_index] < min_height: - min_height = self.heights[to_index] + ) and (min_height is None or self.heights[to_index] < min_height): + min_height = self.heights[to_index] if min_height is not None: self.heights[vertex_index] = min_height + 1 diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index 87d5605a0bc8..208e57f9b32f 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -130,11 +130,11 @@ def create_edge(nodes, graph, cluster, c1): """ create edge between the nodes """ - for i in cluster[c1].keys(): + for i in cluster[c1]: count = 0 c2 = c1 + 1 while c2 < max(cluster.keys()): - for j in cluster[c2].keys(): + for j in cluster[c2]: """ creates edge only if the condition satisfies """ @@ -185,7 +185,7 @@ 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(): + for i in cluster[k]: my_dfs(graph, tuple(cluster[k][i]), (["Header"],)) diff --git a/graphs/minimum_spanning_tree_boruvka.py b/graphs/minimum_spanning_tree_boruvka.py index 663d8e26cfad..3c6888037948 100644 --- a/graphs/minimum_spanning_tree_boruvka.py +++ b/graphs/minimum_spanning_tree_boruvka.py @@ -144,6 +144,7 @@ def union(self, item1, item2): self.rank[root1] += 1 self.parent[root2] = root1 return root1 + return None @staticmethod def boruvka_mst(graph): diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index f577866f0da6..5a08ec57ff4d 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -44,10 +44,7 @@ def bottom_to_top(self, val, index, heap, position): temp = position[index] while index != 0: - if index % 2 == 0: - parent = int((index - 2) / 2) - else: - parent = int((index - 1) / 2) + parent = int((index - 2) / 2) if index % 2 == 0 else int((index - 1) / 2) if val < heap[parent]: heap[index] = heap[parent] diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 707be783d087..81f30ef615fe 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -135,14 +135,14 @@ def _bubble_up(self, elem: T) -> None: # only] curr_pos = self.position_map[elem] if curr_pos == 0: - return + return None 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 + return None def _bubble_down(self, elem: T) -> None: # Place a node at the proper position (downward movement) [to be used @@ -154,24 +154,22 @@ def _bubble_down(self, elem: T) -> None: 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_right_weight < child_left_weight and 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 + return None 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 + return None def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None: # Swap the nodes at the given positions diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 481a6750773a..dc93032183e0 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -126,9 +126,8 @@ def emitter_converter(size_par, data): aux = (bin_pos[cont_loop])[-1 * (bp)] except IndexError: aux = "0" - if aux == "1": - if x == "1": - cont_bo += 1 + if aux == "1" and x == "1": + cont_bo += 1 cont_loop += 1 parity.append(cont_bo % 2) diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index ac0398a31a07..e3556e74c3f3 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -108,7 +108,7 @@ def __mul__(self, other: float | Vector) -> float | Vector: mul implements the scalar multiplication and the dot-product """ - if isinstance(other, float) or isinstance(other, int): + if isinstance(other, (float, int)): ans = [c * other for c in self.__components] return Vector(ans) elif isinstance(other, Vector) and len(self) == len(other): @@ -216,7 +216,7 @@ def axpy(scalar: float, x: Vector, y: Vector) -> Vector: assert ( isinstance(x, Vector) and isinstance(y, Vector) - and (isinstance(scalar, int) or isinstance(scalar, float)) + and (isinstance(scalar, (int, float))) ) return x * scalar + y @@ -337,12 +337,13 @@ def __mul__(self, other: float | Vector) -> Vector | Matrix: "vector must have the same size as the " "number of columns of the matrix!" ) - elif isinstance(other, int) or isinstance(other, float): # matrix-scalar + elif isinstance(other, (int, 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) + return None def height(self) -> int: """ diff --git a/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index 9fa460a07562..5b74dad082e7 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -55,6 +55,7 @@ def output(example_no, data_set): return train_data[example_no][1] elif data_set == "test": return test_data[example_no][1] + return None def calculate_hypothesis_value(example_no, data_set): @@ -68,6 +69,7 @@ def calculate_hypothesis_value(example_no, data_set): return _hypothesis_value(train_data[example_no][0]) elif data_set == "test": return _hypothesis_value(test_data[example_no][0]) + return None def summation_of_cost_derivative(index, end=m): diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index b6305469ed7d..7c8142aab878 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -229,7 +229,7 @@ def report_generator( """ # Fill missing values with given rules if fill_missing_report: - df.fillna(value=fill_missing_report, inplace=True) + df = df.fillna(value=fill_missing_report) df["dummy"] = 1 numeric_cols = df.select_dtypes(np.number).columns report = ( @@ -338,7 +338,7 @@ def report_generator( ) report.columns.name = "" report = report.reset_index() - report.drop(columns=["index"], inplace=True) + report = report.drop(columns=["index"]) return report diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 9c45c351272f..37172c8e9bf6 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -129,7 +129,7 @@ def fit(self): # 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: + if s in (i1, i2): continue self._error[s] += ( y1 * (a1_new - a1) * k(i1, s) @@ -225,7 +225,7 @@ def _predict(self, sample): def _choose_alphas(self): locis = yield from self._choose_a1() if not locis: - return + return None return locis def _choose_a1(self): @@ -423,9 +423,8 @@ 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") + if self._kernel == self._rbf and 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} diff --git a/maths/abs.py b/maths/abs.py index cb0ffc8a5b61..b357e98d8680 100644 --- a/maths/abs.py +++ b/maths/abs.py @@ -75,9 +75,9 @@ def test_abs_val(): """ >>> test_abs_val() """ - assert 0 == abs_val(0) - assert 34 == abs_val(34) - assert 100000000000 == abs_val(-100000000000) + assert abs_val(0) == 0 + assert abs_val(34) == 34 + assert abs_val(-100000000000) == 100000000000 a = [-3, -1, 2, -11] assert abs_max(a) == -11 diff --git a/maths/binary_exp_mod.py b/maths/binary_exp_mod.py index 67dd1e728b18..df688892d690 100644 --- a/maths/binary_exp_mod.py +++ b/maths/binary_exp_mod.py @@ -6,7 +6,7 @@ def bin_exp_mod(a, n, b): 7 """ # mod b - assert not (b == 0), "This cannot accept modulo that is == 0" + assert b != 0, "This cannot accept modulo that is == 0" if n == 0: return 1 diff --git a/maths/jaccard_similarity.py b/maths/jaccard_similarity.py index eab25188b2fd..32054414c0c2 100644 --- a/maths/jaccard_similarity.py +++ b/maths/jaccard_similarity.py @@ -71,6 +71,7 @@ def jaccard_similarity(set_a, set_b, alternative_union=False): return len(intersection) / len(union) return len(intersection) / len(union) + return None if __name__ == "__main__": diff --git a/maths/largest_of_very_large_numbers.py b/maths/largest_of_very_large_numbers.py index d2dc0af18126..7e7fea004958 100644 --- a/maths/largest_of_very_large_numbers.py +++ b/maths/largest_of_very_large_numbers.py @@ -12,6 +12,7 @@ def res(x, y): return 0 elif y == 0: return 1 # any number raised to 0 is 1 + raise AssertionError("This should never happen") if __name__ == "__main__": # Main function diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index 1def58e1f226..af98f24f9538 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -80,10 +80,7 @@ def __init__(self, poly_a=None, poly_b=None): # 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] + dft = [[x] for x in self.polyA] if which == "A" else [[x] for x in self.polyB] # Corner case if len(dft) <= 1: return dft[0] diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index cb47b829010c..9dd112115f5e 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -153,6 +153,7 @@ def train(self, xdata, ydata, train_round, accuracy): if mse < self.accuracy: print("----达到精度----") return mse + return None def cal_loss(self, ydata, ydata_): self.loss = np.sum(np.power((ydata - ydata_), 2)) diff --git a/other/graham_scan.py b/other/graham_scan.py index 8e83bfcf4c49..2eadb4e56668 100644 --- a/other/graham_scan.py +++ b/other/graham_scan.py @@ -125,10 +125,9 @@ def graham_scan(points: list[tuple[int, int]]) -> list[tuple[int, int]]: miny = y minx = x minidx = i - if y == miny: - if x < minx: - minx = x - minidx = i + if y == miny and x < minx: + minx = x + minidx = i # remove the lowest and the most left point from points for preparing for sort points.pop(minidx) diff --git a/other/nested_brackets.py b/other/nested_brackets.py index 3f61a4e7006c..ea48c0a5f532 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -24,11 +24,10 @@ def is_balanced(s): if s[i] in open_brackets: 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] - ): - return False + elif s[i] in closed_brackets and ( + len(stack) == 0 or (len(stack) > 0 and open_to_closed[stack.pop()] != s[i]) + ): + return False return len(stack) == 0 diff --git a/physics/hubble_parameter.py b/physics/hubble_parameter.py index 6bc62e7131c5..f7b2d28a6716 100644 --- a/physics/hubble_parameter.py +++ b/physics/hubble_parameter.py @@ -70,10 +70,10 @@ def hubble_parameter( 68.3 """ parameters = [redshift, radiation_density, matter_density, dark_energy] - if any(0 > p for p in parameters): + if any(p < 0 for p in parameters): raise ValueError("All input parameters must be positive") - if any(1 < p for p in parameters[1:4]): + if any(p > 1 for p in parameters[1:4]): raise ValueError("Relative densities cannot be greater than one") else: curvature = 1 - (matter_density + radiation_density + dark_energy) diff --git a/project_euler/problem_005/sol1.py b/project_euler/problem_005/sol1.py index f272c102d2bb..01cbd0e15ff7 100644 --- a/project_euler/problem_005/sol1.py +++ b/project_euler/problem_005/sol1.py @@ -63,6 +63,7 @@ def solution(n: int = 20) -> int: if i == 0: i = 1 return i + return None if __name__ == "__main__": diff --git a/project_euler/problem_009/sol1.py b/project_euler/problem_009/sol1.py index 1d908402b6b1..e65c9b857990 100644 --- a/project_euler/problem_009/sol1.py +++ b/project_euler/problem_009/sol1.py @@ -32,9 +32,8 @@ def solution() -> int: for a in range(300): for b in range(a + 1, 400): for c in range(b + 1, 500): - if (a + b + c) == 1000: - if (a**2) + (b**2) == (c**2): - return a * b * c + if (a + b + c) == 1000 and (a**2) + (b**2) == (c**2): + return a * b * c return -1 diff --git a/project_euler/problem_014/sol2.py b/project_euler/problem_014/sol2.py index d2a1d9f0e468..2448e652ce5b 100644 --- a/project_euler/problem_014/sol2.py +++ b/project_euler/problem_014/sol2.py @@ -34,10 +34,7 @@ def collatz_sequence_length(n: int) -> int: """Returns the Collatz sequence length for n.""" if n in COLLATZ_SEQUENCE_LENGTHS: return COLLATZ_SEQUENCE_LENGTHS[n] - if n % 2 == 0: - next_n = n // 2 - else: - next_n = 3 * n + 1 + next_n = n // 2 if n % 2 == 0 else 3 * n + 1 sequence_length = collatz_sequence_length(next_n) + 1 COLLATZ_SEQUENCE_LENGTHS[n] = sequence_length return sequence_length diff --git a/project_euler/problem_018/solution.py b/project_euler/problem_018/solution.py index 82fc3ce3c9db..70306148bb9e 100644 --- a/project_euler/problem_018/solution.py +++ b/project_euler/problem_018/solution.py @@ -48,14 +48,8 @@ def solution(): 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 + number1 = a[i - 1][j] if j != len(a[i - 1]) else 0 + number2 = a[i - 1][j - 1] if j > 0 else 0 a[i][j] += max(number1, number2) return max(a[-1]) diff --git a/project_euler/problem_019/sol1.py b/project_euler/problem_019/sol1.py index ab59365843b2..0e38137d4f01 100644 --- a/project_euler/problem_019/sol1.py +++ b/project_euler/problem_019/sol1.py @@ -39,7 +39,7 @@ def solution(): while year < 2001: day += 7 - if (year % 4 == 0 and not year % 100 == 0) or (year % 400 == 0): + if (year % 4 == 0 and 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] diff --git a/project_euler/problem_033/sol1.py b/project_euler/problem_033/sol1.py index e0c9a058af53..32be424b6a7b 100644 --- a/project_euler/problem_033/sol1.py +++ b/project_euler/problem_033/sol1.py @@ -20,11 +20,9 @@ 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 + return ( + num != den and num % 10 == den // 10 and (num // 10) / (den % 10) == num / den + ) def fraction_list(digit_len: int) -> list[str]: diff --git a/project_euler/problem_064/sol1.py b/project_euler/problem_064/sol1.py index 81ebcc7b73c3..12769decc62f 100644 --- a/project_euler/problem_064/sol1.py +++ b/project_euler/problem_064/sol1.py @@ -67,9 +67,8 @@ def solution(n: int = 10000) -> int: 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 + if sr - floor(sr) != 0 and continuous_fraction_period(i) % 2 == 1: + count_odd_periods += 1 return count_odd_periods diff --git a/project_euler/problem_067/sol1.py b/project_euler/problem_067/sol1.py index f20c206cca11..2b41fedc6784 100644 --- a/project_euler/problem_067/sol1.py +++ b/project_euler/problem_067/sol1.py @@ -37,14 +37,8 @@ def solution(): 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 + number1 = a[i - 1][j] if j != len(a[i - 1]) else 0 + number2 = a[i - 1][j - 1] if j > 0 else 0 a[i][j] += max(number1, number2) return max(a[-1]) diff --git a/project_euler/problem_109/sol1.py b/project_euler/problem_109/sol1.py index 852f001d38af..ef145dda590b 100644 --- a/project_euler/problem_109/sol1.py +++ b/project_euler/problem_109/sol1.py @@ -65,7 +65,7 @@ def solution(limit: int = 100) -> int: >>> solution(50) 12577 """ - singles: list[int] = list(range(1, 21)) + [25] + singles: list[int] = [*list(range(1, 21)), 25] doubles: list[int] = [2 * x for x in range(1, 21)] + [50] triples: list[int] = [3 * x for x in range(1, 21)] all_values: list[int] = singles + doubles + triples + [0] diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py index 713b530b6af2..da9436246a7c 100644 --- a/project_euler/problem_203/sol1.py +++ b/project_euler/problem_203/sol1.py @@ -50,8 +50,8 @@ def get_pascal_triangle_unique_coefficients(depth: int) -> set[int]: coefficients = {1} previous_coefficients = [1] for _ in range(2, depth + 1): - coefficients_begins_one = previous_coefficients + [0] - coefficients_ends_one = [0] + previous_coefficients + 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) diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index b3f81bfd10e7..871de8207308 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -36,12 +36,11 @@ def calculate_waitingtime( # 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 arrival_time[j] <= increment_time and remaining_time[j] > 0: + if remaining_time[j] < minm: + minm = remaining_time[j] + short = j + check = True if not check: increment_time += 1 diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index 7572ce342720..b95be9ebc254 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -21,9 +21,8 @@ def md_prefix(i): 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()}") + if (i + 1 > len(old_parts) or old_parts[i] != new_part) and new_part: + print(f"{md_prefix(i)} {new_part.replace('_', ' ').title()}") return new_path diff --git a/searches/binary_tree_traversal.py b/searches/binary_tree_traversal.py index 66814b47883d..76e80df25a13 100644 --- a/searches/binary_tree_traversal.py +++ b/searches/binary_tree_traversal.py @@ -37,6 +37,7 @@ def build_tree(): right_node = TreeNode(int(check)) node_found.right = right_node q.put(right_node) + return None def pre_order(node: TreeNode) -> None: diff --git a/sorts/circle_sort.py b/sorts/circle_sort.py index da3c59059516..271fa1e8d58a 100644 --- a/sorts/circle_sort.py +++ b/sorts/circle_sort.py @@ -58,14 +58,13 @@ def circle_sort_util(collection: list, low: int, high: int) -> bool: left += 1 right -= 1 - if left == right: - if collection[left] > collection[right + 1]: - collection[left], collection[right + 1] = ( - collection[right + 1], - collection[left], - ) + if left == right and collection[left] > collection[right + 1]: + collection[left], collection[right + 1] = ( + collection[right + 1], + collection[left], + ) - swapped = True + swapped = True mid = low + int((high - low) / 2) left_swap = circle_sort_util(collection, low, mid) diff --git a/sorts/counting_sort.py b/sorts/counting_sort.py index 892ec5d5f344..18c4b0323dcb 100644 --- a/sorts/counting_sort.py +++ b/sorts/counting_sort.py @@ -66,7 +66,7 @@ def counting_sort_string(string): if __name__ == "__main__": # Test string sort - assert "eghhiiinrsssttt" == counting_sort_string("thisisthestring") + assert counting_sort_string("thisisthestring") == "eghhiiinrsssttt" user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] diff --git a/sorts/msd_radix_sort.py b/sorts/msd_radix_sort.py index 74ce21762906..03f84c75b9d8 100644 --- a/sorts/msd_radix_sort.py +++ b/sorts/msd_radix_sort.py @@ -147,7 +147,7 @@ def _msd_radix_sort_inplace( list_of_ints[i], list_of_ints[j] = list_of_ints[j], list_of_ints[i] j -= 1 - if not j == i: + if j != i: i += 1 _msd_radix_sort_inplace(list_of_ints, bit_position, begin_index, i) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 70cd19d7afe0..b79d3eac3e48 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -39,7 +39,7 @@ def quick_sort(collection: list) -> list: for element in collection[pivot_index + 1 :]: (greater if element > pivot else lesser).append(element) - return quick_sort(lesser) + [pivot] + quick_sort(greater) + return [*quick_sort(lesser), pivot, *quick_sort(greater)] if __name__ == "__main__": diff --git a/sorts/recursive_quick_sort.py b/sorts/recursive_quick_sort.py index c28a14e37ebd..c29009aca673 100644 --- a/sorts/recursive_quick_sort.py +++ b/sorts/recursive_quick_sort.py @@ -9,11 +9,11 @@ def quick_sort(data: list) -> list: 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]]) - ) + 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__": diff --git a/sorts/tim_sort.py b/sorts/tim_sort.py index c90c7e80390b..138f11c71bcc 100644 --- a/sorts/tim_sort.py +++ b/sorts/tim_sort.py @@ -32,9 +32,9 @@ def merge(left, right): return left if left[0] < right[0]: - return [left[0]] + merge(left[1:], right) + return [left[0], *merge(left[1:], right)] - return [right[0]] + merge(left, right[1:]) + return [right[0], *merge(left, right[1:])] def tim_sort(lst): diff --git a/strings/autocomplete_using_trie.py b/strings/autocomplete_using_trie.py index 758260292a30..77a3050ab15f 100644 --- a/strings/autocomplete_using_trie.py +++ b/strings/autocomplete_using_trie.py @@ -27,10 +27,7 @@ def find_word(self, prefix: str) -> tuple | list: def _elements(self, d: dict) -> tuple: result = [] for c, v in d.items(): - if c == END: - sub_result = [" "] - else: - sub_result = [c + s for s in self._elements(v)] + sub_result = [" "] if c == END else [(c + s) for s in self._elements(v)] result.extend(sub_result) return tuple(result) diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index 0d2f8091a3f0..a364b98212ad 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -38,10 +38,7 @@ def check_anagrams(first_str: str, second_str: str) -> bool: count[first_str[i]] += 1 count[second_str[i]] -= 1 - for _count in count.values(): - if _count != 0: - return False - return True + return all(_count == 0 for _count in count.values()) if __name__ == "__main__": diff --git a/strings/is_palindrome.py b/strings/is_palindrome.py index 9bf2abd98486..406aa2e8d3c3 100644 --- a/strings/is_palindrome.py +++ b/strings/is_palindrome.py @@ -30,10 +30,7 @@ def is_palindrome(s: str) -> bool: # with the help of 1st index (i==n-i-1) # where n is length of string - for i in range(end): - if s[i] != s[n - i - 1]: - return False - return True + return all(s[i] == s[n - i - 1] for i in range(end)) if __name__ == "__main__": diff --git a/strings/snake_case_to_camel_pascal_case.py b/strings/snake_case_to_camel_pascal_case.py index eaabdcb87a0f..28a28b517a01 100644 --- a/strings/snake_case_to_camel_pascal_case.py +++ b/strings/snake_case_to_camel_pascal_case.py @@ -43,7 +43,7 @@ def snake_to_camel_case(input_str: str, use_pascal: bool = False) -> str: initial_word = "" if use_pascal else words[0] - return "".join([initial_word] + capitalized_words) + return "".join([initial_word, *capitalized_words]) if __name__ == "__main__": diff --git a/web_programming/convert_number_to_words.py b/web_programming/convert_number_to_words.py index 50612dec20dd..1e293df9660c 100644 --- a/web_programming/convert_number_to_words.py +++ b/web_programming/convert_number_to_words.py @@ -63,7 +63,7 @@ def convert(number: int) -> str: current = temp_num % 10 if counter % 2 == 0: addition = "" - if counter in placevalue.keys() and current != 0: + if counter in placevalue and current != 0: addition = placevalue[counter] if counter == 2: words = singles[current] + addition + words @@ -84,12 +84,12 @@ def convert(number: int) -> str: words = teens[number % 10] + words else: addition = "" - if counter in placevalue.keys(): + if counter in placevalue: addition = placevalue[counter] words = doubles[current] + addition + words else: addition = "" - if counter in placevalue.keys(): + if counter in placevalue: if current == 0 and ((temp_num % 100) // 10) == 0: addition = "" else: diff --git a/web_programming/instagram_crawler.py b/web_programming/instagram_crawler.py index 4536257a984e..0816cd181051 100644 --- a/web_programming/instagram_crawler.py +++ b/web_programming/instagram_crawler.py @@ -105,7 +105,7 @@ def test_instagram_user(username: str = "github") -> None: import os if os.environ.get("CI"): - return None # test failing on GitHub Actions + return # test failing on GitHub Actions instagram_user = InstagramUser(username) assert instagram_user.user_data assert isinstance(instagram_user.user_data, dict) diff --git a/web_programming/open_google_results.py b/web_programming/open_google_results.py index 2685bf62114d..f61e3666dd7e 100644 --- a/web_programming/open_google_results.py +++ b/web_programming/open_google_results.py @@ -7,10 +7,7 @@ from fake_useragent import UserAgent if __name__ == "__main__": - if len(argv) > 1: - query = "%20".join(argv[1:]) - else: - query = quote(str(input("Search: "))) + query = "%20".join(argv[1:]) if len(argv) > 1 else quote(str(input("Search: "))) print("Googling.....") From 069a14b1c55112bc4f4e08571fc3c2156bb69e5a Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 2 Mar 2023 07:55:47 +0300 Subject: [PATCH 2116/2908] Add Project Euler problem 082 solution 1 (#6282) Update DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_082/__init__.py | 0 project_euler/problem_082/input.txt | 80 +++++++++++++++++++++++ project_euler/problem_082/sol1.py | 65 ++++++++++++++++++ project_euler/problem_082/test_matrix.txt | 5 ++ 5 files changed, 152 insertions(+) create mode 100644 project_euler/problem_082/__init__.py create mode 100644 project_euler/problem_082/input.txt create mode 100644 project_euler/problem_082/sol1.py create mode 100644 project_euler/problem_082/test_matrix.txt diff --git a/DIRECTORY.md b/DIRECTORY.md index a8786cc2591f..3d1bc967e4b5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -918,6 +918,8 @@ * [Sol1](project_euler/problem_080/sol1.py) * Problem 081 * [Sol1](project_euler/problem_081/sol1.py) + * Problem 082 + * [Sol1](project_euler/problem_082/sol1.py) * Problem 085 * [Sol1](project_euler/problem_085/sol1.py) * Problem 086 diff --git a/project_euler/problem_082/__init__.py b/project_euler/problem_082/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_082/input.txt b/project_euler/problem_082/input.txt new file mode 100644 index 000000000000..f65322a7e541 --- /dev/null +++ b/project_euler/problem_082/input.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_082/sol1.py b/project_euler/problem_082/sol1.py new file mode 100644 index 000000000000..7b50dc887719 --- /dev/null +++ b/project_euler/problem_082/sol1.py @@ -0,0 +1,65 @@ +""" +Project Euler Problem 82: https://projecteuler.net/problem=82 + +The minimal path sum in the 5 by 5 matrix below, by starting in any cell +in the left column and finishing in any cell in the right column, +and only moving up, down, and right, is indicated in red and bold; +the sum is equal to 994. + + 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 left column to the right column in matrix.txt +(https://projecteuler.net/project/resources/p082_matrix.txt) +(right click and "Save Link/Target As..."), +a 31K text file containing an 80 by 80 matrix. +""" + +import os + + +def solution(filename: str = "input.txt") -> int: + """ + Returns the minimal path sum in the matrix from the file, by starting in any cell + in the left column and finishing in any cell in the right column, + and only moving up, down, and right + + >>> solution("test_matrix.txt") + 994 + """ + + with open(os.path.join(os.path.dirname(__file__), filename)) as input_file: + matrix = [ + [int(element) for element in line.split(",")] + for line in input_file.readlines() + ] + + rows = len(matrix) + cols = len(matrix[0]) + + minimal_path_sums = [[-1 for _ in range(cols)] for _ in range(rows)] + for i in range(rows): + minimal_path_sums[i][0] = matrix[i][0] + + for j in range(1, cols): + for i in range(rows): + minimal_path_sums[i][j] = minimal_path_sums[i][j - 1] + matrix[i][j] + + for i in range(1, rows): + minimal_path_sums[i][j] = min( + minimal_path_sums[i][j], minimal_path_sums[i - 1][j] + matrix[i][j] + ) + + for i in range(rows - 2, -1, -1): + minimal_path_sums[i][j] = min( + minimal_path_sums[i][j], minimal_path_sums[i + 1][j] + matrix[i][j] + ) + + return min(minimal_path_sums_row[-1] for minimal_path_sums_row in minimal_path_sums) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_082/test_matrix.txt b/project_euler/problem_082/test_matrix.txt new file mode 100644 index 000000000000..76167d9e7fc1 --- /dev/null +++ b/project_euler/problem_082/test_matrix.txt @@ -0,0 +1,5 @@ +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 From ee778128bdf8d4d6d386cfdc500f3b3173f56c06 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 2 Mar 2023 07:57:07 +0300 Subject: [PATCH 2117/2908] Reduce the complexity of other/scoring_algorithm.py (#8045) * Increase the --max-complexity threshold in the file .flake8 --- other/scoring_algorithm.py | 57 ++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py index 00d87cfc0b73..8e04a8f30dd7 100644 --- a/other/scoring_algorithm.py +++ b/other/scoring_algorithm.py @@ -23,29 +23,29 @@ """ -def procentual_proximity( - source_data: list[list[float]], weights: list[int] -) -> list[list[float]]: +def get_data(source_data: list[list[float]]) -> list[list[float]]: """ - 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 - - >>> 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]] + >>> get_data([[20, 60, 2012],[23, 90, 2015],[22, 50, 2011]]) + [[20.0, 23.0, 22.0], [60.0, 90.0, 50.0], [2012.0, 2015.0, 2011.0]] """ - - # getting data data_lists: list[list[float]] = [] for data in source_data: for i, el in enumerate(data): if len(data_lists) < i + 1: data_lists.append([]) data_lists[i].append(float(el)) + return data_lists + +def calculate_each_score( + data_lists: list[list[float]], weights: list[int] +) -> list[list[float]]: + """ + >>> calculate_each_score([[20, 23, 22], [60, 90, 50], [2012, 2015, 2011]], + ... [0, 0, 1]) + [[1.0, 0.0, 0.33333333333333337], [0.75, 0.0, 1.0], [0.25, 1.0, 0.0]] + """ score_lists: list[list[float]] = [] - # calculating each score for dlist, weight in zip(data_lists, weights): mind = min(dlist) maxd = max(dlist) @@ -72,14 +72,43 @@ def procentual_proximity( score_lists.append(score) + return score_lists + + +def generate_final_scores(score_lists: list[list[float]]) -> list[float]: + """ + >>> generate_final_scores([[1.0, 0.0, 0.33333333333333337], + ... [0.75, 0.0, 1.0], + ... [0.25, 1.0, 0.0]]) + [2.0, 1.0, 1.3333333333333335] + """ # initialize final scores final_scores: list[float] = [0 for i in range(len(score_lists[0]))] - # generate final scores for slist in score_lists: for j, ele in enumerate(slist): final_scores[j] = final_scores[j] + ele + return final_scores + + +def procentual_proximity( + source_data: list[list[float]], weights: list[int] +) -> list[list[float]]: + """ + 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 + + >>> 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]] + """ + + data_lists = get_data(source_data) + score_lists = calculate_each_score(data_lists, weights) + final_scores = generate_final_scores(score_lists) + # append scores to source data for i, ele in enumerate(final_scores): source_data[i].append(ele) From 9720e6a6cf52e2395e2d7ef3ef6ae91a355d318e Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 2 Mar 2023 19:51:48 +0300 Subject: [PATCH 2118/2908] Add Project Euler problem 117 solution 1 (#6872) Update DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_117/__init__.py | 0 project_euler/problem_117/sol1.py | 53 +++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 project_euler/problem_117/__init__.py create mode 100644 project_euler/problem_117/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 3d1bc967e4b5..4844841040d9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -956,6 +956,8 @@ * [Sol1](project_euler/problem_115/sol1.py) * Problem 116 * [Sol1](project_euler/problem_116/sol1.py) + * Problem 117 + * [Sol1](project_euler/problem_117/sol1.py) * Problem 119 * [Sol1](project_euler/problem_119/sol1.py) * Problem 120 diff --git a/project_euler/problem_117/__init__.py b/project_euler/problem_117/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_117/sol1.py b/project_euler/problem_117/sol1.py new file mode 100644 index 000000000000..e8214454fac5 --- /dev/null +++ b/project_euler/problem_117/sol1.py @@ -0,0 +1,53 @@ +""" +Project Euler Problem 117: https://projecteuler.net/problem=117 + +Using a combination of grey square tiles and oblong tiles chosen from: +red tiles (measuring two units), green tiles (measuring three units), +and blue tiles (measuring four units), +it is possible to tile a row measuring five units in length +in exactly fifteen different ways. + + |grey|grey|grey|grey|grey| |red,red|grey|grey|grey| + + |grey|red,red|grey|grey| |grey|grey|red,red|grey| + + |grey|grey|grey|red,red| |red,red|red,red|grey| + + |red,red|grey|red,red| |grey|red,red|red,red| + + |green,green,green|grey|grey| |grey|green,green,green|grey| + + |grey|grey|green,green,green| |red,red|green,green,green| + + |green,green,green|red,red| |blue,blue,blue,blue|grey| + + |grey|blue,blue,blue,blue| + +How many ways can a row measuring fifty units in length be tiled? + +NOTE: This is related to Problem 116 (https://projecteuler.net/problem=116). +""" + + +def solution(length: int = 50) -> int: + """ + Returns the number of ways can a row of the given length be tiled + + >>> solution(5) + 15 + """ + + ways_number = [1] * (length + 1) + + for row_length in range(length + 1): + for tile_length in range(2, 5): + for tile_start in range(row_length - tile_length + 1): + ways_number[row_length] += ways_number[ + row_length - tile_start - tile_length + ] + + return ways_number[length] + + +if __name__ == "__main__": + print(f"{solution() = }") From 41b633a841084acac5a640042d365c985e23b357 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 00:10:39 +0100 Subject: [PATCH 2119/2908] [pre-commit.ci] pre-commit autoupdate (#8168) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.253 → v0.0.254](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.253...v0.0.254) * Rename get_top_billionaires.py to get_top_billionaires.py.disabled * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 1 - ...get_top_billionaires.py => get_top_billionaires.py.disabled} | 0 3 files changed, 1 insertion(+), 2 deletions(-) rename web_programming/{get_top_billionaires.py => get_top_billionaires.py.disabled} (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f27f985bb6a..329407265a5a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: - --py311-plus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.253 + rev: v0.0.254 hooks: - id: ruff args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 4844841040d9..f25b0c6ff4e3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1167,7 +1167,6 @@ * [Get Amazon Product Data](web_programming/get_amazon_product_data.py) * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](web_programming/get_imdbtop.py) - * [Get Top Billionaires](web_programming/get_top_billionaires.py) * [Get Top Hn Posts](web_programming/get_top_hn_posts.py) * [Get User Tweets](web_programming/get_user_tweets.py) * [Giphy](web_programming/giphy.py) diff --git a/web_programming/get_top_billionaires.py b/web_programming/get_top_billionaires.py.disabled similarity index 100% rename from web_programming/get_top_billionaires.py rename to web_programming/get_top_billionaires.py.disabled From 9e28ecca28176254c39bcc791733589c6091422e Mon Sep 17 00:00:00 2001 From: Subhendu Dash <71781104+subhendudash02@users.noreply.github.com> Date: Tue, 7 Mar 2023 21:46:25 +0530 Subject: [PATCH 2120/2908] Add circular convolution (#8158) * add circular convolution * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add type hint for __init__ * rounding off final values to 2 and minor changes * add test case for unequal signals * changes in list comprehension and enumeraton --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- electronics/circular_convolution.py | 99 +++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 electronics/circular_convolution.py diff --git a/electronics/circular_convolution.py b/electronics/circular_convolution.py new file mode 100644 index 000000000000..f2e35742e944 --- /dev/null +++ b/electronics/circular_convolution.py @@ -0,0 +1,99 @@ +# https://en.wikipedia.org/wiki/Circular_convolution + +""" +Circular convolution, also known as cyclic convolution, +is a special case of periodic convolution, which is the convolution of two +periodic functions that have the same period. Periodic convolution arises, +for example, in the context of the discrete-time Fourier transform (DTFT). +In particular, the DTFT of the product of two discrete sequences is the periodic +convolution of the DTFTs of the individual sequences. And each DTFT is a periodic +summation of a continuous Fourier transform function. + +Source: https://en.wikipedia.org/wiki/Circular_convolution +""" + +import doctest +from collections import deque + +import numpy as np + + +class CircularConvolution: + """ + This class stores the first and second signal and performs the circular convolution + """ + + def __init__(self) -> None: + """ + First signal and second signal are stored as 1-D array + """ + + self.first_signal = [2, 1, 2, -1] + self.second_signal = [1, 2, 3, 4] + + def circular_convolution(self) -> list[float]: + """ + This function performs the circular convolution of the first and second signal + using matrix method + + Usage: + >>> import circular_convolution as cc + >>> convolution = cc.CircularConvolution() + >>> convolution.circular_convolution() + [10, 10, 6, 14] + + >>> convolution.first_signal = [0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6] + >>> convolution.second_signal = [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5] + >>> convolution.circular_convolution() + [5.2, 6.0, 6.48, 6.64, 6.48, 6.0, 5.2, 4.08] + + >>> convolution.first_signal = [-1, 1, 2, -2] + >>> convolution.second_signal = [0.5, 1, -1, 2, 0.75] + >>> convolution.circular_convolution() + [6.25, -3.0, 1.5, -2.0, -2.75] + + >>> convolution.first_signal = [1, -1, 2, 3, -1] + >>> convolution.second_signal = [1, 2, 3] + >>> convolution.circular_convolution() + [8, -2, 3, 4, 11] + + """ + + length_first_signal = len(self.first_signal) + length_second_signal = len(self.second_signal) + + max_length = max(length_first_signal, length_second_signal) + + # create a zero matrix of max_length x max_length + matrix = [[0] * max_length for i in range(max_length)] + + # fills the smaller signal with zeros to make both signals of same length + if length_first_signal < length_second_signal: + self.first_signal += [0] * (max_length - length_first_signal) + elif length_first_signal > length_second_signal: + self.second_signal += [0] * (max_length - length_second_signal) + + """ + Fills the matrix in the following way assuming 'x' is the signal of length 4 + [ + [x[0], x[3], x[2], x[1]], + [x[1], x[0], x[3], x[2]], + [x[2], x[1], x[0], x[3]], + [x[3], x[2], x[1], x[0]] + ] + """ + for i in range(max_length): + rotated_signal = deque(self.second_signal) + rotated_signal.rotate(i) + for j, item in enumerate(rotated_signal): + matrix[i][j] += item + + # multiply the matrix with the first signal + final_signal = np.matmul(np.transpose(matrix), np.transpose(self.first_signal)) + + # rounding-off to two decimal places + return [round(i, 2) for i in final_signal] + + +if __name__ == "__main__": + doctest.testmod() From f9cc25221c1521a0da9ee27d6a9bea1f14f4c986 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Fri, 10 Mar 2023 12:48:05 +0300 Subject: [PATCH 2121/2908] Reduce the complexity of backtracking/word_search.py (#8166) * Lower the --max-complexity threshold in the file .flake8 --- backtracking/word_search.py | 112 +++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/backtracking/word_search.py b/backtracking/word_search.py index 25d1436be36e..c9d52012b42b 100644 --- a/backtracking/word_search.py +++ b/backtracking/word_search.py @@ -33,6 +33,61 @@ """ +def get_point_key(len_board: int, len_board_column: int, row: int, column: int) -> int: + """ + Returns the hash key of matrix indexes. + + >>> get_point_key(10, 20, 1, 0) + 200 + """ + + return len_board * len_board_column * row + column + + +def exits_word( + board: list[list[str]], + word: str, + row: int, + column: int, + word_index: int, + visited_points_set: set[int], +) -> bool: + """ + Return True if it's possible to search the word suffix + starting from the word_index. + + >>> exits_word([["A"]], "B", 0, 0, 0, set()) + False + """ + + if board[row][column] != word[word_index]: + return False + + if word_index == len(word) - 1: + return True + + traverts_directions = [(0, 1), (0, -1), (-1, 0), (1, 0)] + len_board = len(board) + len_board_column = len(board[0]) + for direction in traverts_directions: + next_i = row + direction[0] + next_j = column + direction[1] + if not (0 <= next_i < len_board and 0 <= next_j < len_board_column): + continue + + key = get_point_key(len_board, len_board_column, next_i, next_j) + if key in visited_points_set: + continue + + visited_points_set.add(key) + if exits_word(board, word, next_i, next_j, word_index + 1, visited_points_set): + return True + + visited_points_set.remove(key) + + return False + + def word_exists(board: list[list[str]], word: str) -> bool: """ >>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCCED") @@ -77,6 +132,8 @@ def word_exists(board: list[list[str]], word: str) -> bool: board_error_message = ( "The board should be a non empty matrix of single chars strings." ) + + len_board = len(board) if not isinstance(board, list) or len(board) == 0: raise ValueError(board_error_message) @@ -94,61 +151,12 @@ def word_exists(board: list[list[str]], word: str) -> bool: "The word parameter should be a string of length greater than 0." ) - traverts_directions = [(0, 1), (0, -1), (-1, 0), (1, 0)] - len_word = len(word) - len_board = len(board) len_board_column = len(board[0]) - - # Returns the hash key of matrix indexes. - def get_point_key(row: int, column: int) -> int: - """ - >>> len_board=10 - >>> len_board_column=20 - >>> get_point_key(0, 0) - 200 - """ - - return len_board * len_board_column * row + column - - # Return True if it's possible to search the word suffix - # starting from the word_index. - def exits_word( - row: int, column: int, word_index: int, visited_points_set: set[int] - ) -> bool: - """ - >>> board=[["A"]] - >>> word="B" - >>> exits_word(0, 0, 0, set()) - False - """ - - if board[row][column] != word[word_index]: - return False - - if word_index == len_word - 1: - return True - - for direction in traverts_directions: - next_i = row + direction[0] - next_j = column + direction[1] - if not (0 <= next_i < len_board and 0 <= next_j < len_board_column): - continue - - key = get_point_key(next_i, next_j) - if key in visited_points_set: - continue - - visited_points_set.add(key) - if exits_word(next_i, next_j, word_index + 1, visited_points_set): - return True - - visited_points_set.remove(key) - - return False - for i in range(len_board): for j in range(len_board_column): - if exits_word(i, j, 0, {get_point_key(i, j)}): + if exits_word( + board, word, i, j, 0, {get_point_key(len_board, len_board_column, i, j)} + ): return True return False From 8959211100ba7a612d42a6e7db4755303b78c5a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 23:18:35 +0100 Subject: [PATCH 2122/2908] [pre-commit.ci] pre-commit autoupdate (#8177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.254 → v0.0.255](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.254...v0.0.255) - [github.com/pre-commit/mirrors-mypy: v1.0.1 → v1.1.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.1...v1.1.1) - [github.com/codespell-project/codespell: v2.2.2 → v2.2.4](https://github.com/codespell-project/codespell/compare/v2.2.2...v2.2.4) * updating DIRECTORY.md * Fixes for new version of codespell --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 8 ++++---- DIRECTORY.md | 1 + machine_learning/sequential_minimum_optimization.py | 2 +- physics/lorentz_transformation_four_vector.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 329407265a5a..9aa965e42aec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: - --py311-plus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.254 + rev: v0.0.255 hooks: - id: ruff args: @@ -69,7 +69,7 @@ repos: *flake8-plugins - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.1 + rev: v1.1.1 hooks: - id: mypy args: @@ -79,11 +79,11 @@ repos: additional_dependencies: [types-requests] - repo: https://github.com/codespell-project/codespell - rev: v2.2.2 + rev: v2.2.4 hooks: - id: codespell args: - - --ignore-words-list=ans,crate,damon,fo,followings,hist,iff,mater,secant,som,sur,tim,zar + - --ignore-words-list=3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar exclude: | (?x)^( ciphers/prehistoric_men.txt | diff --git a/DIRECTORY.md b/DIRECTORY.md index f25b0c6ff4e3..b2daaaa9c47d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -334,6 +334,7 @@ ## Electronics * [Builtin Voltage](electronics/builtin_voltage.py) * [Carrier Concentration](electronics/carrier_concentration.py) + * [Circular Convolution](electronics/circular_convolution.py) * [Coulombs Law](electronics/coulombs_law.py) * [Electric Conductivity](electronics/electric_conductivity.py) * [Electric Power](electronics/electric_power.py) diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 37172c8e9bf6..b68bd52f4de9 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -569,7 +569,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 distributed points with high desity and - prediced values of these points are calculated by using our tained model. Then we + prediced values of these points are calculated by using our trained 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/physics/lorentz_transformation_four_vector.py b/physics/lorentz_transformation_four_vector.py index 64be97245f29..f4fda4dff8cd 100644 --- a/physics/lorentz_transformation_four_vector.py +++ b/physics/lorentz_transformation_four_vector.py @@ -2,7 +2,7 @@ Lorentz transformations describe the transition between two inertial reference frames F and F', each of which is moving in some direction with respect to the other. This code only calculates Lorentz transformations for movement in the x -direction with no spacial rotation (i.e., a Lorentz boost in the x direction). +direction with no spatial rotation (i.e., a Lorentz boost in the x direction). The Lorentz transformations are calculated here as linear transformations of four-vectors [ct, x, y, z] described by Minkowski space. Note that t (time) is multiplied by c (the speed of light) in the first entry of each four-vector. From b797e437aeadcac50556d6606a547dc634cf5329 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 14 Mar 2023 01:31:27 +0100 Subject: [PATCH 2123/2908] Add hashmap implementation (#7967) --- data_structures/hashing/hash_map.py | 162 ++++++++++++++++++ .../hashing/tests/test_hash_map.py | 97 +++++++++++ 2 files changed, 259 insertions(+) create mode 100644 data_structures/hashing/hash_map.py create mode 100644 data_structures/hashing/tests/test_hash_map.py diff --git a/data_structures/hashing/hash_map.py b/data_structures/hashing/hash_map.py new file mode 100644 index 000000000000..1dfcc8bbf906 --- /dev/null +++ b/data_structures/hashing/hash_map.py @@ -0,0 +1,162 @@ +""" +Hash map with open addressing. + +https://en.wikipedia.org/wiki/Hash_table + +Another hash map implementation, with a good explanation. +Modern Dictionaries by Raymond Hettinger +https://www.youtube.com/watch?v=p33CVV29OG8 +""" +from collections.abc import Iterator, MutableMapping +from dataclasses import dataclass +from typing import Generic, TypeVar + +KEY = TypeVar("KEY") +VAL = TypeVar("VAL") + + +@dataclass(frozen=True, slots=True) +class _Item(Generic[KEY, VAL]): + key: KEY + val: VAL + + +class _DeletedItem(_Item): + def __init__(self) -> None: + super().__init__(None, None) + + def __bool__(self) -> bool: + return False + + +_deleted = _DeletedItem() + + +class HashMap(MutableMapping[KEY, VAL]): + """ + Hash map with open addressing. + """ + + def __init__( + self, initial_block_size: int = 8, capacity_factor: float = 0.75 + ) -> None: + self._initial_block_size = initial_block_size + self._buckets: list[_Item | None] = [None] * initial_block_size + assert 0.0 < capacity_factor < 1.0 + self._capacity_factor = capacity_factor + self._len = 0 + + def _get_bucket_index(self, key: KEY) -> int: + return hash(key) % len(self._buckets) + + def _get_next_ind(self, ind: int) -> int: + """ + Get next index. + + Implements linear open addressing. + """ + return (ind + 1) % len(self._buckets) + + def _try_set(self, ind: int, key: KEY, val: VAL) -> bool: + """ + Try to add value to the bucket. + + If bucket is empty or key is the same, does insert and return True. + + If bucket has another key or deleted placeholder, + that means that we need to check next bucket. + """ + stored = self._buckets[ind] + if not stored: + self._buckets[ind] = _Item(key, val) + self._len += 1 + return True + elif stored.key == key: + self._buckets[ind] = _Item(key, val) + return True + else: + return False + + def _is_full(self) -> bool: + """ + Return true if we have reached safe capacity. + + So we need to increase the number of buckets to avoid collisions. + """ + limit = len(self._buckets) * self._capacity_factor + return len(self) >= int(limit) + + def _is_sparse(self) -> bool: + """Return true if we need twice fewer buckets when we have now.""" + if len(self._buckets) <= self._initial_block_size: + return False + limit = len(self._buckets) * self._capacity_factor / 2 + return len(self) < limit + + def _resize(self, new_size: int) -> None: + old_buckets = self._buckets + self._buckets = [None] * new_size + self._len = 0 + for item in old_buckets: + if item: + self._add_item(item.key, item.val) + + def _size_up(self) -> None: + self._resize(len(self._buckets) * 2) + + def _size_down(self) -> None: + self._resize(len(self._buckets) // 2) + + def _iterate_buckets(self, key: KEY) -> Iterator[int]: + ind = self._get_bucket_index(key) + for _ in range(len(self._buckets)): + yield ind + ind = self._get_next_ind(ind) + + def _add_item(self, key: KEY, val: VAL) -> None: + for ind in self._iterate_buckets(key): + if self._try_set(ind, key, val): + break + + def __setitem__(self, key: KEY, val: VAL) -> None: + if self._is_full(): + self._size_up() + + self._add_item(key, val) + + def __delitem__(self, key: KEY) -> None: + for ind in self._iterate_buckets(key): + item = self._buckets[ind] + if item is None: + raise KeyError(key) + if item is _deleted: + continue + if item.key == key: + self._buckets[ind] = _deleted + self._len -= 1 + break + if self._is_sparse(): + self._size_down() + + def __getitem__(self, key: KEY) -> VAL: + for ind in self._iterate_buckets(key): + item = self._buckets[ind] + if item is None: + break + if item is _deleted: + continue + if item.key == key: + return item.val + raise KeyError(key) + + def __len__(self) -> int: + return self._len + + def __iter__(self) -> Iterator[KEY]: + yield from (item.key for item in self._buckets if item) + + def __repr__(self) -> str: + val_string = " ,".join( + f"{item.key}: {item.val}" for item in self._buckets if item + ) + return f"HashMap({val_string})" diff --git a/data_structures/hashing/tests/test_hash_map.py b/data_structures/hashing/tests/test_hash_map.py new file mode 100644 index 000000000000..929e67311996 --- /dev/null +++ b/data_structures/hashing/tests/test_hash_map.py @@ -0,0 +1,97 @@ +from operator import delitem, getitem, setitem + +import pytest + +from data_structures.hashing.hash_map import HashMap + + +def _get(k): + return getitem, k + + +def _set(k, v): + return setitem, k, v + + +def _del(k): + return delitem, k + + +def _run_operation(obj, fun, *args): + try: + return fun(obj, *args), None + except Exception as e: + return None, e + + +_add_items = ( + _set("key_a", "val_a"), + _set("key_b", "val_b"), +) + +_overwrite_items = [ + _set("key_a", "val_a"), + _set("key_a", "val_b"), +] + +_delete_items = [ + _set("key_a", "val_a"), + _set("key_b", "val_b"), + _del("key_a"), + _del("key_b"), + _set("key_a", "val_a"), + _del("key_a"), +] + +_access_absent_items = [ + _get("key_a"), + _del("key_a"), + _set("key_a", "val_a"), + _del("key_a"), + _del("key_a"), + _get("key_a"), +] + +_add_with_resize_up = [ + *[_set(x, x) for x in range(5)], # guaranteed upsize +] + +_add_with_resize_down = [ + *[_set(x, x) for x in range(5)], # guaranteed upsize + *[_del(x) for x in range(5)], + _set("key_a", "val_b"), +] + + +@pytest.mark.parametrize( + "operations", + ( + pytest.param(_add_items, id="add items"), + pytest.param(_overwrite_items, id="overwrite items"), + pytest.param(_delete_items, id="delete items"), + pytest.param(_access_absent_items, id="access absent items"), + pytest.param(_add_with_resize_up, id="add with resize up"), + pytest.param(_add_with_resize_down, id="add with resize down"), + ), +) +def test_hash_map_is_the_same_as_dict(operations): + my = HashMap(initial_block_size=4) + py = {} + for _, (fun, *args) in enumerate(operations): + my_res, my_exc = _run_operation(my, fun, *args) + py_res, py_exc = _run_operation(py, fun, *args) + assert my_res == py_res + assert str(my_exc) == str(py_exc) + assert set(py) == set(my) + assert len(py) == len(my) + assert set(my.items()) == set(py.items()) + + +def test_no_new_methods_was_added_to_api(): + def is_public(name: str) -> bool: + return not name.startswith("_") + + dict_public_names = {name for name in dir({}) if is_public(name)} + hash_public_names = {name for name in dir(HashMap()) if is_public(name)} + + assert dict_public_names > hash_public_names From 9701e459e884e883fc720277452ec592eae305d0 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 14 Mar 2023 08:39:36 +0300 Subject: [PATCH 2124/2908] Add Project Euler problem 100 solution 1 (#8175) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 2 ++ project_euler/problem_100/__init__.py | 0 project_euler/problem_100/sol1.py | 48 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 project_euler/problem_100/__init__.py create mode 100644 project_euler/problem_100/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index b2daaaa9c47d..e1ce44eedce1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -937,6 +937,8 @@ * [Sol1](project_euler/problem_097/sol1.py) * Problem 099 * [Sol1](project_euler/problem_099/sol1.py) + * Problem 100 + * [Sol1](project_euler/problem_100/sol1.py) * Problem 101 * [Sol1](project_euler/problem_101/sol1.py) * Problem 102 diff --git a/project_euler/problem_100/__init__.py b/project_euler/problem_100/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_100/sol1.py b/project_euler/problem_100/sol1.py new file mode 100644 index 000000000000..367378e7ab17 --- /dev/null +++ b/project_euler/problem_100/sol1.py @@ -0,0 +1,48 @@ +""" +Project Euler Problem 100: https://projecteuler.net/problem=100 + +If a box contains twenty-one coloured discs, composed of fifteen blue discs and +six red discs, and two discs were taken at random, it can be seen that +the probability of taking two blue discs, P(BB) = (15/21) x (14/20) = 1/2. + +The next such arrangement, for which there is exactly 50% chance of taking two blue +discs at random, is a box containing eighty-five blue discs and thirty-five red discs. + +By finding the first arrangement to contain over 10^12 = 1,000,000,000,000 discs +in total, determine the number of blue discs that the box would contain. +""" + + +def solution(min_total: int = 10**12) -> int: + """ + Returns the number of blue discs for the first arrangement to contain + over min_total discs in total + + >>> solution(2) + 3 + + >>> solution(4) + 15 + + >>> solution(21) + 85 + """ + + prev_numerator = 1 + prev_denominator = 0 + + numerator = 1 + denominator = 1 + + while numerator <= 2 * min_total - 1: + prev_numerator += 2 * numerator + numerator += 2 * prev_numerator + + prev_denominator += 2 * denominator + denominator += 2 * prev_denominator + + return (denominator + 1) // 2 + + +if __name__ == "__main__": + print(f"{solution() = }") From 47b3c729826e864fb1d0a30b03cf95fa2adae591 Mon Sep 17 00:00:00 2001 From: David Leal Date: Mon, 13 Mar 2023 23:46:52 -0600 Subject: [PATCH 2125/2908] docs: add the other/miscellaneous form (#8163) Co-authored-by: Christian Clauss Co-authored-by: Dhruv Manilawala --- .github/ISSUE_TEMPLATE/other.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/other.yml diff --git a/.github/ISSUE_TEMPLATE/other.yml b/.github/ISSUE_TEMPLATE/other.yml new file mode 100644 index 000000000000..44d6ff541506 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other.yml @@ -0,0 +1,19 @@ +name: Other +description: Use this for any other issues. PLEASE do not create blank issues +labels: ["awaiting triage"] +body: + - type: textarea + id: issuedescription + attributes: + label: What would you like to share? + description: Provide a clear and concise explanation of your issue. + validations: + required: true + + - type: textarea + id: extrainfo + attributes: + label: Additional information + description: Is there anything else we should know about this issue? + validations: + required: false From adc3ccdabede375df5cff62c3c8f06d8a191a803 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 15 Mar 2023 15:56:03 +0300 Subject: [PATCH 2126/2908] Add Project Euler problem 131 solution 1 (#8179) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 5 +++ project_euler/problem_131/__init__.py | 0 project_euler/problem_131/sol1.py | 56 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 project_euler/problem_131/__init__.py create mode 100644 project_euler/problem_131/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e1ce44eedce1..1d3177801a2c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -196,11 +196,14 @@ * [Disjoint Set](data_structures/disjoint_set/disjoint_set.py) * Hashing * [Double Hash](data_structures/hashing/double_hash.py) + * [Hash Map](data_structures/hashing/hash_map.py) * [Hash Table](data_structures/hashing/hash_table.py) * [Hash Table With Linked List](data_structures/hashing/hash_table_with_linked_list.py) * Number Theory * [Prime Numbers](data_structures/hashing/number_theory/prime_numbers.py) * [Quadratic Probing](data_structures/hashing/quadratic_probing.py) + * Tests + * [Test Hash Map](data_structures/hashing/tests/test_hash_map.py) * Heap * [Binomial Heap](data_structures/heap/binomial_heap.py) * [Heap](data_structures/heap/heap.py) @@ -973,6 +976,8 @@ * [Sol1](project_euler/problem_125/sol1.py) * Problem 129 * [Sol1](project_euler/problem_129/sol1.py) + * Problem 131 + * [Sol1](project_euler/problem_131/sol1.py) * Problem 135 * [Sol1](project_euler/problem_135/sol1.py) * Problem 144 diff --git a/project_euler/problem_131/__init__.py b/project_euler/problem_131/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_131/sol1.py b/project_euler/problem_131/sol1.py new file mode 100644 index 000000000000..f5302aac8644 --- /dev/null +++ b/project_euler/problem_131/sol1.py @@ -0,0 +1,56 @@ +""" +Project Euler Problem 131: https://projecteuler.net/problem=131 + +There are some prime values, p, for which there exists a positive integer, n, +such that the expression n^3 + n^2p is a perfect cube. + +For example, when p = 19, 8^3 + 8^2 x 19 = 12^3. + +What is perhaps most surprising is that for each prime with this property +the value of n is unique, and there are only four such primes below one-hundred. + +How many primes below one million have this remarkable property? +""" + +from math import isqrt + + +def is_prime(number: int) -> bool: + """ + Determines whether number is prime + + >>> is_prime(3) + True + + >>> is_prime(4) + False + """ + + for divisor in range(2, isqrt(number) + 1): + if number % divisor == 0: + return False + return True + + +def solution(max_prime: int = 10**6) -> int: + """ + Returns number of primes below max_prime with the property + + >>> solution(100) + 4 + """ + + primes_count = 0 + cube_index = 1 + prime_candidate = 7 + while prime_candidate < max_prime: + primes_count += is_prime(prime_candidate) + + cube_index += 1 + prime_candidate += 6 * cube_index + + return primes_count + + +if __name__ == "__main__": + print(f"{solution() = }") From c96241b5a5052af466894ef90c7a7c749ba872eb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 15 Mar 2023 13:58:25 +0100 Subject: [PATCH 2127/2908] Replace bandit, flake8, isort, and pyupgrade with ruff (#8178) * Replace bandit, flake8, isort, and pyupgrade with ruff * Comment on ruff rules * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .flake8 | 10 --- .github/workflows/ruff.yml | 16 ++++ .pre-commit-config.yaml | 78 +++++-------------- arithmetic_analysis/newton_raphson.py | 2 +- arithmetic_analysis/newton_raphson_new.py | 2 +- data_structures/heap/heap_generic.py | 1 - dynamic_programming/min_distance_up_bottom.py | 9 +-- dynamic_programming/minimum_tickets_cost.py | 4 +- dynamic_programming/word_break.py | 4 +- hashes/sha1.py | 12 +-- machine_learning/support_vector_machines.py | 4 +- maths/eulers_totient.py | 34 ++++---- maths/fibonacci.py | 4 +- maths/pythagoras.py | 6 +- other/quine.py | 1 + project_euler/problem_075/sol1.py | 3 +- pyproject.toml | 59 ++++++++++++-- sorts/external_sort.py | 2 +- strings/check_anagrams.py | 3 +- strings/word_occurrence.py | 3 +- web_programming/currency_converter.py | 2 +- 21 files changed, 127 insertions(+), 132 deletions(-) delete mode 100644 .flake8 create mode 100644 .github/workflows/ruff.yml diff --git a/.flake8 b/.flake8 deleted file mode 100644 index b68ee8533a61..000000000000 --- a/.flake8 +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -max-line-length = 88 -# max-complexity should be 10 -max-complexity = 19 -extend-ignore = - # Formatting style for `black` - # E203 is whitespace before ':' - E203, - # W503 is line break occurred before a binary operator - W503 diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 000000000000..ca2d5be47327 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,16 @@ +# https://beta.ruff.rs +name: ruff +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: pip install --user ruff + - run: ruff --format=github . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9aa965e42aec..82aad6c65a9b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,7 @@ repos: rev: v4.4.0 hooks: - id: check-executables-have-shebangs + - id: check-toml - id: check-yaml - id: end-of-file-fixer types: [python] @@ -14,60 +15,41 @@ repos: hooks: - id: auto-walrus + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.255 + hooks: + - id: ruff + - repo: https://github.com/psf/black rev: 23.1.0 hooks: - id: black - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + - repo: https://github.com/codespell-project/codespell + rev: v2.2.4 hooks: - - id: isort - args: - - --profile=black + - id: codespell + additional_dependencies: + - tomli - repo: https://github.com/tox-dev/pyproject-fmt rev: "0.9.2" hooks: - id: pyproject-fmt + - repo: local + hooks: + - id: validate-filenames + name: Validate filenames + entry: ./scripts/validate_filenames.py + language: script + pass_filenames: false + - repo: https://github.com/abravalheri/validate-pyproject rev: v0.12.1 hooks: - id: validate-pyproject - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: - - --py311-plus - - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.255 - hooks: - - id: ruff - args: - - --ignore=E741 - - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 # See .flake8 for args - additional_dependencies: &flake8-plugins - - flake8-bugbear - - flake8-builtins - # - flake8-broken-line - - flake8-comprehensions - - pep8-naming - - - repo: https://github.com/asottile/yesqa - rev: v1.4.0 - hooks: - - id: yesqa - additional_dependencies: - *flake8-plugins - - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.1.1 hooks: @@ -77,25 +59,3 @@ repos: - --install-types # See mirrors-mypy README.md - --non-interactive additional_dependencies: [types-requests] - - - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 - hooks: - - id: codespell - args: - - --ignore-words-list=3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar - exclude: | - (?x)^( - ciphers/prehistoric_men.txt | - strings/dictionary.txt | - strings/words.txt | - project_euler/problem_022/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/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index 86ff9d350dde..aee2f07e5743 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -5,7 +5,7 @@ from __future__ import annotations from decimal import Decimal -from math import * # noqa: F401, F403 +from math import * # noqa: F403 from sympy import diff diff --git a/arithmetic_analysis/newton_raphson_new.py b/arithmetic_analysis/newton_raphson_new.py index 472cb5b5ac54..f61841e2eb84 100644 --- a/arithmetic_analysis/newton_raphson_new.py +++ b/arithmetic_analysis/newton_raphson_new.py @@ -8,7 +8,7 @@ # Newton's Method - https://en.wikipedia.org/wiki/Newton's_method from sympy import diff, lambdify, symbols -from sympy.functions import * # noqa: F401, F403 +from sympy.functions import * # noqa: F403 def newton_raphson( diff --git a/data_structures/heap/heap_generic.py b/data_structures/heap/heap_generic.py index b4d7019f41f9..ee92149e25a9 100644 --- a/data_structures/heap/heap_generic.py +++ b/data_structures/heap/heap_generic.py @@ -166,7 +166,6 @@ def test_heap() -> None: >>> h.get_top() [9, -40] """ - pass if __name__ == "__main__": diff --git a/dynamic_programming/min_distance_up_bottom.py b/dynamic_programming/min_distance_up_bottom.py index 49c361f24d45..4870c7ef4499 100644 --- a/dynamic_programming/min_distance_up_bottom.py +++ b/dynamic_programming/min_distance_up_bottom.py @@ -6,13 +6,13 @@ The aim is to demonstate up bottom approach for solving the task. The implementation was tested on the leetcode: https://leetcode.com/problems/edit-distance/ -""" -""" Levinstein distance Dynamic Programming: up -> down. """ +import functools + def min_distance_up_bottom(word1: str, word2: str) -> int: """ @@ -25,13 +25,10 @@ def min_distance_up_bottom(word1: str, word2: str) -> int: >>> min_distance_up_bottom("zooicoarchaeologist", "zoologist") 10 """ - - from functools import lru_cache - len_word1 = len(word1) len_word2 = len(word2) - @lru_cache(maxsize=None) + @functools.cache def min_distance(index1: int, index2: int) -> int: # if first word index is overflow - delete all from the second word if index1 >= len_word1: diff --git a/dynamic_programming/minimum_tickets_cost.py b/dynamic_programming/minimum_tickets_cost.py index d07056d9217f..6790c21f16ed 100644 --- a/dynamic_programming/minimum_tickets_cost.py +++ b/dynamic_programming/minimum_tickets_cost.py @@ -22,7 +22,7 @@ Dynamic Programming: up -> down. """ -from functools import lru_cache +import functools def mincost_tickets(days: list[int], costs: list[int]) -> int: @@ -106,7 +106,7 @@ def mincost_tickets(days: list[int], costs: list[int]) -> int: days_set = set(days) - @lru_cache(maxsize=None) + @functools.cache def dynamic_programming(index: int) -> int: if index > 365: return 0 diff --git a/dynamic_programming/word_break.py b/dynamic_programming/word_break.py index 642ea0edf40d..4d7ac869080c 100644 --- a/dynamic_programming/word_break.py +++ b/dynamic_programming/word_break.py @@ -20,7 +20,7 @@ Space: O(n) """ -from functools import lru_cache +import functools from typing import Any @@ -80,7 +80,7 @@ def word_break(string: str, words: list[str]) -> bool: len_string = len(string) # Dynamic programming method - @lru_cache(maxsize=None) + @functools.cache def is_breakable(index: int) -> bool: """ >>> string = 'a' diff --git a/hashes/sha1.py b/hashes/sha1.py index b19e0cfafea3..9f0437f208fa 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -26,7 +26,6 @@ import argparse import hashlib # hashlib is only used inside the Test class import struct -import unittest class SHA1Hash: @@ -128,14 +127,9 @@ def final_hash(self): 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): # noqa: N802 - msg = bytes("Test String", "utf-8") - self.assertEqual(SHA1Hash(msg).final_hash(), hashlib.sha1(msg).hexdigest()) +def test_sha1_hash(): + msg = b"Test String" + assert SHA1Hash(msg).final_hash() == hashlib.sha1(msg).hexdigest() # noqa: S324 def main(): diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py index caec10175c50..df854cc850b1 100644 --- a/machine_learning/support_vector_machines.py +++ b/machine_learning/support_vector_machines.py @@ -56,7 +56,7 @@ def __init__( *, regularization: float = np.inf, kernel: str = "linear", - gamma: float = 0, + gamma: float = 0.0, ) -> None: self.regularization = regularization self.gamma = gamma @@ -65,7 +65,7 @@ def __init__( elif kernel == "rbf": if self.gamma == 0: raise ValueError("rbf kernel requires gamma") - if not (isinstance(self.gamma, float) or isinstance(self.gamma, int)): + if not isinstance(self.gamma, (float, int)): raise ValueError("gamma must be float or int") if not self.gamma > 0: raise ValueError("gamma must be > 0") diff --git a/maths/eulers_totient.py b/maths/eulers_totient.py index 6a35e69bde0b..a156647037b4 100644 --- a/maths/eulers_totient.py +++ b/maths/eulers_totient.py @@ -1,5 +1,20 @@ # Eulers Totient function finds the number of relative primes of a number n from 1 to n def totient(n: int) -> list: + """ + >>> 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. + """ is_prime = [True for i in range(n + 1)] totients = [i - 1 for i in range(n + 1)] primes = [] @@ -20,25 +35,6 @@ def totient(n: int) -> list: 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 diff --git a/maths/fibonacci.py b/maths/fibonacci.py index d58c9fc68c67..e810add69dc7 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -16,7 +16,7 @@ fib_binet runtime: 0.0174 ms """ -from functools import lru_cache +import functools from math import sqrt from time import time @@ -110,7 +110,7 @@ def fib_recursive_cached(n: int) -> list[int]: Exception: n is negative """ - @lru_cache(maxsize=None) + @functools.cache def fib_recursive_term(i: int) -> int: """ Calculates the i-th (0-indexed) Fibonacci number using recursion diff --git a/maths/pythagoras.py b/maths/pythagoras.py index 69a17731a0fd..7770e981d44d 100644 --- a/maths/pythagoras.py +++ b/maths/pythagoras.py @@ -14,17 +14,13 @@ 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)) - - -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 + return math.sqrt(abs((b.x - a.x) ** 2 + (b.y - a.y) ** 2 + (b.z - a.z) ** 2)) if __name__ == "__main__": diff --git a/other/quine.py b/other/quine.py index 01e03bbb02cb..500a351d38dc 100644 --- a/other/quine.py +++ b/other/quine.py @@ -1,4 +1,5 @@ #!/bin/python3 +# ruff: noqa """ Quine: diff --git a/project_euler/problem_075/sol1.py b/project_euler/problem_075/sol1.py index b57604d76a86..0ccaf5dee7ec 100644 --- a/project_euler/problem_075/sol1.py +++ b/project_euler/problem_075/sol1.py @@ -29,7 +29,6 @@ from collections import defaultdict from math import gcd -from typing import DefaultDict def solution(limit: int = 1500000) -> int: @@ -43,7 +42,7 @@ def solution(limit: int = 1500000) -> int: >>> solution(50000) 5502 """ - frequencies: DefaultDict = defaultdict(int) + 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): diff --git a/pyproject.toml b/pyproject.toml index 5f9b1aa06c0e..6552101d2faa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,8 +12,57 @@ addopts = [ omit = [".env/*"] sort = "Cover" -#[report] -#sort = Cover -#omit = -# .env/* -# backtracking/* +[tool.codespell] +ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar" +skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" + +[tool.ruff] +ignore = [ # `ruff rule S101` for a description of that rule + "B904", # B904: Within an `except` clause, raise exceptions with `raise ... from err` + "B905", # B905: `zip()` without an explicit `strict=` parameter + "E741", # E741: Ambiguous variable name 'l' + "G004", # G004 Logging statement uses f-string + "N999", # N999: Invalid module name + "PLC1901", # PLC1901: `{}` can be simplified to `{}` as an empty string is falsey + "PLR2004", # PLR2004: Magic value used in comparison + "PLR5501", # PLR5501: Consider using `elif` instead of `else` + "PLW0120", # PLW0120: `else` clause on loop without a `break` statement + "PLW060", # PLW060: Using global for `{name}` but no assignment is done -- DO NOT FIX + "PLW2901", # PLW2901: Redefined loop variable + "RUF00", # RUF00: Ambiguous unicode character -- DO NOT FIX + "RUF100", # RUF100: Unused `noqa` directive + "S101", # S101: Use of `assert` detected -- DO NOT FIX + "S105", # S105: Possible hardcoded password: 'password' + "S113", # S113: Probable use of requests call without timeout + "UP038", # UP038: Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX +] +select = [ # https://beta.ruff.rs/docs/rules + "A", # A: builtins + "B", # B: bugbear + "C40", # C40: comprehensions + "C90", # C90: mccabe code complexity + "E", # E: pycodestyle errors + "F", # F: pyflakes + "G", # G: logging format + "I", # I: isort + "N", # N: pep8 naming + "PL", # PL: pylint + "PIE", # PIE: pie + "PYI", # PYI: type hinting stub files + "RUF", # RUF: ruff + "S", # S: bandit + "TID", # TID: tidy imports + "UP", # UP: pyupgrade + "W", # W: pycodestyle warnings + "YTT", # YTT: year 2020 +] +target-version = "py311" + +[tool.ruff.mccabe] # DO NOT INCREASE THIS VALUE +max-complexity = 20 # default: 10 + +[tool.ruff.pylint] # DO NOT INCREASE THESE VALUES +max-args = 10 # default: 5 +max-branches = 20 # default: 12 +max-returns = 8 # default: 6 +max-statements = 88 # default: 50 diff --git a/sorts/external_sort.py b/sorts/external_sort.py index 7af7dc0a609d..e6b0d47f79f5 100644 --- a/sorts/external_sort.py +++ b/sorts/external_sort.py @@ -104,7 +104,7 @@ 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) # noqa: UP015 return files diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index a364b98212ad..9dcdffcfb921 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -2,7 +2,6 @@ wiki: https://en.wikipedia.org/wiki/Anagram """ from collections import defaultdict -from typing import DefaultDict def check_anagrams(first_str: str, second_str: str) -> bool: @@ -30,7 +29,7 @@ def check_anagrams(first_str: str, second_str: str) -> bool: return False # Default values for count should be 0 - count: DefaultDict[str, int] = defaultdict(int) + count: defaultdict[str, int] = defaultdict(int) # For each character in input strings, # increment count in the corresponding diff --git a/strings/word_occurrence.py b/strings/word_occurrence.py index 8260620c38a4..5a18ebf771e4 100644 --- a/strings/word_occurrence.py +++ b/strings/word_occurrence.py @@ -1,7 +1,6 @@ # Created by sarathkaul on 17/11/19 # Modified by Arkadip Bhattacharya(@darkmatter18) on 20/04/2020 from collections import defaultdict -from typing import DefaultDict def word_occurrence(sentence: str) -> dict: @@ -15,7 +14,7 @@ def word_occurrence(sentence: str) -> dict: >>> dict(word_occurrence("Two spaces")) {'Two': 1, 'spaces': 1} """ - occurrence: DefaultDict[str, int] = defaultdict(int) + occurrence: defaultdict[str, int] = defaultdict(int) # Creating a dictionary containing count of each word for word in sentence.split(): occurrence[word] += 1 diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py index 6fcc60e8feeb..69f2a2c4d421 100644 --- a/web_programming/currency_converter.py +++ b/web_programming/currency_converter.py @@ -8,7 +8,7 @@ import requests URL_BASE = "https://www.amdoren.com/api/currency.php" -TESTING = os.getenv("CI", False) +TESTING = os.getenv("CI", "") API_KEY = os.getenv("AMDOREN_API_KEY", "") if not API_KEY and not TESTING: From 521fbca61c6bdb84746564eb58c2ef2131260187 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 16 Mar 2023 13:31:29 +0100 Subject: [PATCH 2128/2908] Replace flake8 with ruff (#8184) --- CONTRIBUTING.md | 6 +++--- audio_filters/equal_loudness_filter.py.broken.txt | 2 +- data_structures/binary_tree/red_black_tree.py | 4 ++-- digital_image_processing/change_contrast.py | 4 ++-- maths/is_square_free.py | 4 ++-- maths/mobius_function.py | 4 ++-- other/linear_congruential_generator.py | 8 ++++---- pyproject.toml | 1 + quantum/ripple_adder_classic.py | 6 +++--- 9 files changed, 20 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ce5bd1edf68..6b6e4d21bfc7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,11 +81,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 . --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. +- All submissions will need to pass the test `ruff .` before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request. ```bash - python3 -m pip install flake8 # only required the first time - flake8 . --ignore=E203,W503 --max-line-length=88 --show-source + python3 -m pip install ruff # only required the first time + ruff . ``` - Original code submission require docstrings or comments to describe your work. diff --git a/audio_filters/equal_loudness_filter.py.broken.txt b/audio_filters/equal_loudness_filter.py.broken.txt index b9a3c50e1c33..88cba8533cf7 100644 --- a/audio_filters/equal_loudness_filter.py.broken.txt +++ b/audio_filters/equal_loudness_filter.py.broken.txt @@ -20,7 +20,7 @@ class EqualLoudnessFilter: samplerate, use with caution. Code based on matlab implementation at https://bit.ly/3eqh2HU - (url shortened for flake8) + (url shortened for ruff) Target curve: https://i.imgur.com/3g2VfaM.png Yulewalk response: https://i.imgur.com/J9LnJ4C.png diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index b50d75d33689..3ebc8d63939b 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -1,6 +1,6 @@ """ -python/black : true -flake8 : passed +psf/black : true +ruff : passed """ from __future__ import annotations diff --git a/digital_image_processing/change_contrast.py b/digital_image_processing/change_contrast.py index 6a150400249f..7e49694708f8 100644 --- a/digital_image_processing/change_contrast.py +++ b/digital_image_processing/change_contrast.py @@ -4,8 +4,8 @@ This algorithm is used in https://noivce.pythonanywhere.com/ Python web app. -python/black: True -flake8 : True +psf/black: True +ruff : True """ from PIL import Image diff --git a/maths/is_square_free.py b/maths/is_square_free.py index 4134398d258b..08c70dc32c38 100644 --- a/maths/is_square_free.py +++ b/maths/is_square_free.py @@ -1,7 +1,7 @@ """ References: wikipedia:square free number -python/black : True -flake8 : True +psf/black : True +ruff : True """ from __future__ import annotations diff --git a/maths/mobius_function.py b/maths/mobius_function.py index 4fcf35f21813..8abdc4cafcb4 100644 --- a/maths/mobius_function.py +++ b/maths/mobius_function.py @@ -1,8 +1,8 @@ """ References: https://en.wikipedia.org/wiki/M%C3%B6bius_function References: wikipedia:square free number -python/black : True -flake8 : True +psf/black : True +ruff : True """ from maths.is_square_free import is_square_free diff --git a/other/linear_congruential_generator.py b/other/linear_congruential_generator.py index 777ee6355b9b..c016310f9cfa 100644 --- a/other/linear_congruential_generator.py +++ b/other/linear_congruential_generator.py @@ -9,10 +9,10 @@ class LinearCongruentialGenerator: """ # The default value for **seed** is the result of a function call which is not - # normally recommended and causes flake8-bugbear to raise a B008 error. However, - # in this case, it is accptable because `LinearCongruentialGenerator.__init__()` - # will only be called once per instance and it ensures that each instance will - # generate a unique sequence of numbers. + # normally recommended and causes ruff to raise a B008 error. However, in this case, + # it is accptable because `LinearCongruentialGenerator.__init__()` will only be + # called once per instance and it ensures that each instance will generate a unique + # sequence of numbers. def __init__(self, multiplier, increment, modulo, seed=int(time())): # noqa: B008 """ diff --git a/pyproject.toml b/pyproject.toml index 6552101d2faa..169c3a71ba6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ select = [ # https://beta.ruff.rs/docs/rules "W", # W: pycodestyle warnings "YTT", # YTT: year 2020 ] +show-source = true target-version = "py311" [tool.ruff.mccabe] # DO NOT INCREASE THIS VALUE diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py index c07757af7fff..b604395bc583 100644 --- a/quantum/ripple_adder_classic.py +++ b/quantum/ripple_adder_classic.py @@ -54,9 +54,9 @@ def full_adder( # The default value for **backend** is the result of a function call which is not -# normally recommended and causes flake8-bugbear to raise a B008 error. However, -# in this case, this is acceptable because `Aer.get_backend()` is called when the -# function is defined and that same backend is then reused for all function calls. +# normally recommended and causes ruff to raise a B008 error. However, in this case, +# this is acceptable because `Aer.get_backend()` is called when the function is defined +# and that same backend is then reused for all function calls. def ripple_adder( From 3f9150c1b2dd15808a4962e03a1455f8d825512c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 22:16:13 +0100 Subject: [PATCH 2129/2908] [pre-commit.ci] pre-commit autoupdate (#8294) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.255 → v0.0.257](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.255...v0.0.257) * Fix PLR1711 Useless statement at end of function --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- data_structures/binary_tree/avl_tree.py | 4 ---- machine_learning/polymonial_regression.py | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82aad6c65a9b..58cec4ff6ee6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.255 + rev: v0.0.257 hooks: - id: ruff diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 320e7ed0d792..4c1fb17afe86 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -60,19 +60,15 @@ def get_height(self) -> int: def set_data(self, data: Any) -> None: self.data = data - return def set_left(self, node: MyNode | None) -> None: self.left = node - return def set_right(self, node: MyNode | None) -> None: self.right = node - return def set_height(self, height: int) -> None: self.height = height - return def get_height(node: MyNode | None) -> int: diff --git a/machine_learning/polymonial_regression.py b/machine_learning/polymonial_regression.py index 374c35f7f905..487fb814526f 100644 --- a/machine_learning/polymonial_regression.py +++ b/machine_learning/polymonial_regression.py @@ -34,7 +34,6 @@ def viz_polymonial(): plt.xlabel("Position level") plt.ylabel("Salary") plt.show() - return if __name__ == "__main__": From 7cdb011ba440a07768179bfaea190bddefc890d8 Mon Sep 17 00:00:00 2001 From: Genesis <128913081+KaixLina@users.noreply.github.com> Date: Sun, 26 Mar 2023 20:49:18 +0530 Subject: [PATCH 2130/2908] New gitter link added or replaced (#8551) * New gitter link added * ruff==0.0.258 * noqa: S310 * noqa: S310 * Update ruff.yml * Add Ruff rule S311 * Ruff v0.0.259 * return ("{:08x}" * 5).format(*self.h) * pickle.load(f) # noqa: S301 --------- Co-authored-by: Christian Clauss --- .github/stale.yml | 4 ++-- .pre-commit-config.yaml | 2 +- CONTRIBUTING.md | 4 ++-- README.md | 4 ++-- hashes/sha1.py | 2 +- machine_learning/sequential_minimum_optimization.py | 2 +- neural_network/convolution_neural_network.py | 2 +- project_euler/README.md | 2 +- pyproject.toml | 1 + web_programming/download_images_from_google_query.py | 2 +- 10 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 36ca56266b26..813f688348d8 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -45,7 +45,7 @@ pulls: 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) + some help, feel free to seek help from our [Gitter](https://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im) or ping one of the reviewers. Thank you for your contributions! issues: @@ -59,5 +59,5 @@ issues: 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 + from our [Gitter](https://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im) or ping one of the reviewers. Thank you for your contributions! diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58cec4ff6ee6..72a878387e15 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.257 + rev: v0.0.259 hooks: - id: ruff diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b6e4d21bfc7..75e4fb893723 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## 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). +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://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im). ## Contributing @@ -176,7 +176,7 @@ We want your work to be readable by others; therefore, we encourage you to note - Most importantly, - __Be consistent in the use of these guidelines when submitting.__ - - __Join__ us on [Discord](https://discord.com/invite/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms) __now!__ + - __Join__ us on [Discord](https://discord.com/invite/c7MnfGFGa6) and [Gitter](https://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im) __now!__ - Happy coding! Writer [@poyea](https://github.com/poyea), Jun 2019. diff --git a/README.md b/README.md index 68a6e5e6fbce..3d2f1a110780 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Discord chat - + Gitter chat @@ -42,7 +42,7 @@ Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribut ## Community Channels -We are on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms)! Community channels are a great way for you to ask questions and get help. Please join us! +We are on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im)! Community channels are a great way for you to ask questions and get help. Please join us! ## List of Algorithms diff --git a/hashes/sha1.py b/hashes/sha1.py index 9f0437f208fa..b325ce3e43bb 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -124,7 +124,7 @@ def final_hash(self): self.h[3] + d & 0xFFFFFFFF, self.h[4] + e & 0xFFFFFFFF, ) - return "%08x%08x%08x%08x%08x" % tuple(self.h) + return ("{:08x}" * 5).format(*self.h) def test_sha1_hash(): diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index b68bd52f4de9..b24f5669e2e8 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -458,7 +458,7 @@ def test_cancel_data(): CANCER_DATASET_URL, headers={"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"}, ) - response = urllib.request.urlopen(request) + response = urllib.request.urlopen(request) # noqa: S310 content = response.read().decode("utf-8") with open(r"cancel_data.csv", "w") as f: f.write(content) diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index bd0550212157..f5ec156f3593 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -77,7 +77,7 @@ def save_model(self, save_path): def read_model(cls, model_path): # read saved model with open(model_path, "rb") as f: - model_dic = pickle.load(f) + model_dic = pickle.load(f) # noqa: S301 conv_get = model_dic.get("conv1") conv_get.append(model_dic.get("step_conv1")) diff --git a/project_euler/README.md b/project_euler/README.md index e3dc035eee5e..4832d0078ebf 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -10,7 +10,7 @@ The solutions will be checked by our [automated testing on GitHub Actions](https ## 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. +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://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im). 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 diff --git a/pyproject.toml b/pyproject.toml index 169c3a71ba6c..23fe45e97d20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ ignore = [ # `ruff rule S101` for a description of that rule "S101", # S101: Use of `assert` detected -- DO NOT FIX "S105", # S105: Possible hardcoded password: 'password' "S113", # S113: Probable use of requests call without timeout + "S311", # S311: Standard pseudo-random generators are not suitable for cryptographic purposes "UP038", # UP038: Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX ] select = [ # https://beta.ruff.rs/docs/rules diff --git a/web_programming/download_images_from_google_query.py b/web_programming/download_images_from_google_query.py index 9c0c21dc804e..441347459f8e 100644 --- a/web_programming/download_images_from_google_query.py +++ b/web_programming/download_images_from_google_query.py @@ -86,7 +86,7 @@ def download_images_from_google_query(query: str = "dhaka", max_images: int = 5) path_name = f"query_{query.replace(' ', '_')}" if not os.path.exists(path_name): os.makedirs(path_name) - urllib.request.urlretrieve( + urllib.request.urlretrieve( # noqa: S310 original_size_img, f"{path_name}/original_size_img_{index}.jpg" ) return index From 86b2ab09aab359ef1b4bea58ed3c1fdf5b989500 Mon Sep 17 00:00:00 2001 From: Christian Veenhuis Date: Sun, 26 Mar 2023 18:20:47 +0200 Subject: [PATCH 2131/2908] Fix broken links to Gitter Community (Fixes: #8197) (#8546) Co-authored-by: Christian Clauss --- .github/stale.yml | 4 ++-- CONTRIBUTING.md | 4 ++-- README.md | 4 ++-- project_euler/README.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 813f688348d8..0939e1f223ff 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -45,7 +45,7 @@ pulls: 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://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im) + some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms/community) or ping one of the reviewers. Thank you for your contributions! issues: @@ -59,5 +59,5 @@ issues: 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://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im) or ping one of the + from our [Gitter](https://gitter.im/TheAlgorithms/community) or ping one of the reviewers. Thank you for your contributions! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75e4fb893723..2bb0c2e39eee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## 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://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im). +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/community). ## Contributing @@ -176,7 +176,7 @@ We want your work to be readable by others; therefore, we encourage you to note - Most importantly, - __Be consistent in the use of these guidelines when submitting.__ - - __Join__ us on [Discord](https://discord.com/invite/c7MnfGFGa6) and [Gitter](https://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im) __now!__ + - __Join__ us on [Discord](https://discord.com/invite/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms/community) __now!__ - Happy coding! Writer [@poyea](https://github.com/poyea), Jun 2019. diff --git a/README.md b/README.md index 3d2f1a110780..bf6e0ed3cf75 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Discord chat - + Gitter chat @@ -42,7 +42,7 @@ Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribut ## Community Channels -We are on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im)! Community channels are a great way for you to ask questions and get help. Please join us! +We are on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms/community)! Community channels are a great way for you to ask questions and get help. Please join us! ## List of Algorithms diff --git a/project_euler/README.md b/project_euler/README.md index 4832d0078ebf..16865edf2a67 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -10,7 +10,7 @@ The solutions will be checked by our [automated testing on GitHub Actions](https ## 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://app.gitter.im/#/room/#TheAlgorithms_community:gitter.im). 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. +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/community). 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 From ac111ee463065e372ad148dbafba630045ecf94c Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 29 Mar 2023 00:41:54 +0300 Subject: [PATCH 2132/2908] Reduce the complexity of graphs/bi_directional_dijkstra.py (#8165) * Reduce the complexity of graphs/bi_directional_dijkstra.py * Try to lower the --max-complexity threshold in the file .flake8 * Lower the --max-complexity threshold in the file .flake8 * updating DIRECTORY.md * updating DIRECTORY.md * Try to lower max-complexity * Try to lower max-complexity * Try to lower max-complexity --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- graphs/bi_directional_dijkstra.py | 95 +++++++++++++++++-------------- pyproject.toml | 2 +- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/graphs/bi_directional_dijkstra.py b/graphs/bi_directional_dijkstra.py index fc53e2f0d8f3..a4489026be80 100644 --- a/graphs/bi_directional_dijkstra.py +++ b/graphs/bi_directional_dijkstra.py @@ -17,6 +17,32 @@ import numpy as np +def pass_and_relaxation( + graph: dict, + v: str, + visited_forward: set, + visited_backward: set, + cst_fwd: dict, + cst_bwd: dict, + queue: PriorityQueue, + parent: dict, + shortest_distance: float | int, +) -> float | int: + for nxt, d in graph[v]: + if nxt in visited_forward: + continue + old_cost_f = cst_fwd.get(nxt, np.inf) + new_cost_f = cst_fwd[v] + d + if new_cost_f < old_cost_f: + queue.put((new_cost_f, nxt)) + cst_fwd[nxt] = new_cost_f + parent[nxt] = v + if nxt in visited_backward: + if cst_fwd[v] + d + cst_bwd[nxt] < shortest_distance: + shortest_distance = cst_fwd[v] + d + cst_bwd[nxt] + return shortest_distance + + def bidirectional_dij( source: str, destination: str, graph_forward: dict, graph_backward: dict ) -> int: @@ -51,53 +77,36 @@ def bidirectional_dij( if source == destination: return 0 - while queue_forward and queue_backward: - while not queue_forward.empty(): - _, v_fwd = queue_forward.get() - - if v_fwd not in visited_forward: - break - else: - break + while not queue_forward.empty() and not queue_backward.empty(): + _, v_fwd = queue_forward.get() visited_forward.add(v_fwd) - while not queue_backward.empty(): - _, v_bwd = queue_backward.get() - - if v_bwd not in visited_backward: - break - else: - break + _, v_bwd = queue_backward.get() visited_backward.add(v_bwd) - # forward pass and relaxation - for nxt_fwd, d_forward in graph_forward[v_fwd]: - if nxt_fwd in visited_forward: - continue - old_cost_f = cst_fwd.get(nxt_fwd, np.inf) - new_cost_f = cst_fwd[v_fwd] + d_forward - if new_cost_f < old_cost_f: - queue_forward.put((new_cost_f, nxt_fwd)) - cst_fwd[nxt_fwd] = new_cost_f - parent_forward[nxt_fwd] = v_fwd - if nxt_fwd in visited_backward: - if cst_fwd[v_fwd] + d_forward + cst_bwd[nxt_fwd] < shortest_distance: - shortest_distance = cst_fwd[v_fwd] + d_forward + cst_bwd[nxt_fwd] - - # backward pass and relaxation - for nxt_bwd, d_backward in graph_backward[v_bwd]: - if nxt_bwd in visited_backward: - continue - old_cost_b = cst_bwd.get(nxt_bwd, np.inf) - new_cost_b = cst_bwd[v_bwd] + d_backward - if new_cost_b < old_cost_b: - queue_backward.put((new_cost_b, nxt_bwd)) - cst_bwd[nxt_bwd] = new_cost_b - parent_backward[nxt_bwd] = v_bwd - - if nxt_bwd in visited_forward: - if cst_bwd[v_bwd] + d_backward + cst_fwd[nxt_bwd] < shortest_distance: - shortest_distance = cst_bwd[v_bwd] + d_backward + cst_fwd[nxt_bwd] + shortest_distance = pass_and_relaxation( + graph_forward, + v_fwd, + visited_forward, + visited_backward, + cst_fwd, + cst_bwd, + queue_forward, + parent_forward, + shortest_distance, + ) + + shortest_distance = pass_and_relaxation( + graph_backward, + v_bwd, + visited_backward, + visited_forward, + cst_bwd, + cst_fwd, + queue_backward, + parent_backward, + shortest_distance, + ) if cst_fwd[v_fwd] + cst_bwd[v_bwd] >= shortest_distance: break diff --git a/pyproject.toml b/pyproject.toml index 23fe45e97d20..48c3fbd4009d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ show-source = true target-version = "py311" [tool.ruff.mccabe] # DO NOT INCREASE THIS VALUE -max-complexity = 20 # default: 10 +max-complexity = 17 # default: 10 [tool.ruff.pylint] # DO NOT INCREASE THESE VALUES max-args = 10 # default: 5 From a71f22dae54f830dbf68b3bd5e5e8d540e338a4c Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Thu, 30 Mar 2023 10:39:21 +0530 Subject: [PATCH 2133/2908] Update cnn_classification.py (#8570) --- computer_vision/cnn_classification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computer_vision/cnn_classification.py b/computer_vision/cnn_classification.py index 1c193fcbb50b..9b5f8c95eebf 100644 --- a/computer_vision/cnn_classification.py +++ b/computer_vision/cnn_classification.py @@ -93,7 +93,7 @@ test_image = tf.keras.preprocessing.image.img_to_array(test_image) test_image = np.expand_dims(test_image, axis=0) result = classifier.predict(test_image) - training_set.class_indices + # training_set.class_indices if result[0][0] == 0: prediction = "Normal" if result[0][0] == 1: From a00492911a949a1e59072367bbabee22cd884106 Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Fri, 31 Mar 2023 16:47:13 +0530 Subject: [PATCH 2134/2908] added a problem on kadane's algo and its solution. (#8569) * added kadane's algorithm directory with one problem's solution. * added type hints * Rename kaadne_algorithm/max_product_subarray.py to dynamic_programming/max_product_subarray.py * Update dynamic_programming/max_product_subarray.py Co-authored-by: Christian Clauss * Update max_product_subarray.py * Update max_product_subarray.py * Update dynamic_programming/max_product_subarray.py Co-authored-by: Christian Clauss * Update max_product_subarray.py * Update max_product_subarray.py * Update max_product_subarray.py * Update max_product_subarray.py * Update max_product_subarray.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update max_product_subarray.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update max_product_subarray.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update max_product_subarray.py * Update max_product_subarray.py * Update dynamic_programming/max_product_subarray.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/max_product_subarray.py Co-authored-by: Christian Clauss * Update max_product_subarray.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/max_product_subarray.py | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 dynamic_programming/max_product_subarray.py diff --git a/dynamic_programming/max_product_subarray.py b/dynamic_programming/max_product_subarray.py new file mode 100644 index 000000000000..425859bc03e3 --- /dev/null +++ b/dynamic_programming/max_product_subarray.py @@ -0,0 +1,53 @@ +def max_product_subarray(numbers: list[int]) -> int: + """ + Returns the maximum product that can be obtained by multiplying a + contiguous subarray of the given integer list `nums`. + + Example: + >>> max_product_subarray([2, 3, -2, 4]) + 6 + >>> max_product_subarray((-2, 0, -1)) + 0 + >>> max_product_subarray([2, 3, -2, 4, -1]) + 48 + >>> max_product_subarray([-1]) + -1 + >>> max_product_subarray([0]) + 0 + >>> max_product_subarray([]) + 0 + >>> max_product_subarray("") + 0 + >>> max_product_subarray(None) + 0 + >>> max_product_subarray([2, 3, -2, 4.5, -1]) + Traceback (most recent call last): + ... + ValueError: numbers must be an iterable of integers + >>> max_product_subarray("ABC") + Traceback (most recent call last): + ... + ValueError: numbers must be an iterable of integers + """ + if not numbers: + return 0 + + if not isinstance(numbers, (list, tuple)) or not all( + isinstance(number, int) for number in numbers + ): + raise ValueError("numbers must be an iterable of integers") + + max_till_now = min_till_now = max_prod = numbers[0] + + for i in range(1, len(numbers)): + # update the maximum and minimum subarray products + number = numbers[i] + if number < 0: + max_till_now, min_till_now = min_till_now, max_till_now + max_till_now = max(number, max_till_now * number) + min_till_now = min(number, min_till_now * number) + + # update the maximum product found till now + max_prod = max(max_prod, max_till_now) + + return max_prod From 238fe8c494ab5be80c96441095d1c8958f95c04d Mon Sep 17 00:00:00 2001 From: NIKITA PANDEY <113332472+nikitapandeyy@users.noreply.github.com> Date: Fri, 31 Mar 2023 19:38:13 +0530 Subject: [PATCH 2135/2908] Update receive_file.py (#8541) * Update receive_file.py Here are the changes I made: Added the main() function and called it from if __name__ == "__main__" block. This makes it easier to test the code and import it into other programs. Added socket.AF_INET as the first argument to socket.socket(). This specifies the address family to be used, which is necessary when using connect(). Changed print(f"{data = }") to print("Received:", len(data), "bytes"). This makes it clearer what's happening and how much data is being received. Changed the final print statement to "Successfully received the file". This makes it more accurate and descriptive. Moved the import statement to the top of the file. This is a common convention in Python. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- file_transfer/receive_file.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/file_transfer/receive_file.py b/file_transfer/receive_file.py index 37a503036dc2..f50ad9fe1107 100644 --- a/file_transfer/receive_file.py +++ b/file_transfer/receive_file.py @@ -1,8 +1,9 @@ -if __name__ == "__main__": - import socket # Import socket module +import socket + - sock = socket.socket() # Create a socket object - host = socket.gethostname() # Get local machine name +def main(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + host = socket.gethostname() port = 12312 sock.connect((host, port)) @@ -13,11 +14,14 @@ print("Receiving data...") while True: data = sock.recv(1024) - print(f"{data = }") if not data: break - out_file.write(data) # Write data to a file + out_file.write(data) - print("Successfully got the file") + print("Successfully received the file") sock.close() print("Connection closed") + + +if __name__ == "__main__": + main() From 5ce63b5966b6ad9c7ce36c449fb31112c3e1d084 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 1 Apr 2023 01:11:24 -0400 Subject: [PATCH 2136/2908] Fix `mypy` errors in `lu_decomposition.py` (attempt 2) (#8100) * updating DIRECTORY.md * Fix mypy errors in lu_decomposition.py * Replace for-loops with comprehensions * Add explanation of LU decomposition and extra doctests Add an explanation of LU decomposition with conditions for when an LU decomposition exists Add extra doctests to handle each of the possible conditions for when a decomposition exists/doesn't exist * updating DIRECTORY.md * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- arithmetic_analysis/lu_decomposition.py | 91 ++++++++++++++++++------- 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index 217719cf4da1..941c1dadf556 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -1,62 +1,101 @@ -"""Lower-Upper (LU) Decomposition. +""" +Lower–upper (LU) decomposition factors a matrix as a product of a lower +triangular matrix and an upper triangular matrix. A square matrix has an LU +decomposition under the following conditions: + - If the matrix is invertible, then it has an LU decomposition if and only + if all of its leading principal minors are non-zero (see + https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of + leading principal minors of a matrix). + - If the matrix is singular (i.e., not invertible) and it has a rank of k + (i.e., it has k linearly independent columns), then it has an LU + decomposition if its first k leading principal minors are non-zero. + +This algorithm will simply attempt to perform LU decomposition on any square +matrix and raise an error if no such decomposition exists. -Reference: -- https://en.wikipedia.org/wiki/LU_decomposition +Reference: https://en.wikipedia.org/wiki/LU_decomposition """ from __future__ import annotations import numpy as np -from numpy import float64 -from numpy.typing import ArrayLike - -def lower_upper_decomposition( - table: ArrayLike[float64], -) -> tuple[ArrayLike[float64], ArrayLike[float64]]: - """Lower-Upper (LU) Decomposition - - Example: +def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """ + Perform LU decomposition on a given matrix and raises an error if the matrix + isn't square or if no such decomposition exists >>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]]) - >>> outcome = lower_upper_decomposition(matrix) - >>> outcome[0] + >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) + >>> lower_mat array([[1. , 0. , 0. ], [0. , 1. , 0. ], [2.5, 8. , 1. ]]) - >>> outcome[1] + >>> upper_mat array([[ 2. , -2. , 1. ], [ 0. , 1. , 2. ], [ 0. , 0. , -17.5]]) + >>> matrix = np.array([[4, 3], [6, 3]]) + >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) + >>> lower_mat + array([[1. , 0. ], + [1.5, 1. ]]) + >>> upper_mat + array([[ 4. , 3. ], + [ 0. , -1.5]]) + + # Matrix is not square >>> matrix = np.array([[2, -2, 1], [0, 1, 2]]) - >>> lower_upper_decomposition(matrix) + >>> lower_mat, upper_mat = 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]] + + # Matrix is invertible, but its first leading principal minor is 0 + >>> matrix = np.array([[0, 1], [1, 0]]) + >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) + Traceback (most recent call last): + ... + ArithmeticError: No LU decomposition exists + + # Matrix is singular, but its first leading principal minor is 1 + >>> matrix = np.array([[1, 0], [1, 0]]) + >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) + >>> lower_mat + array([[1., 0.], + [1., 1.]]) + >>> upper_mat + array([[1., 0.], + [0., 0.]]) + + # Matrix is singular, but its first leading principal minor is 0 + >>> matrix = np.array([[0, 1], [0, 1]]) + >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) + Traceback (most recent call last): + ... + ArithmeticError: No LU decomposition exists """ - # Table that contains our data - # Table has to be a square array so we need to check first + # Ensure that table is a square array rows, columns = np.shape(table) if rows != columns: raise ValueError( - f"'table' has to be of square shaped array but got a {rows}x{columns} " - + f"array:\n{table}" + f"'table' has to be of square shaped array but got a " + f"{rows}x{columns} array:\n{table}" ) + lower = np.zeros((rows, columns)) upper = np.zeros((rows, columns)) for i in range(columns): for j in range(i): - total = 0 - for k in range(j): - total += lower[i][k] * upper[k][j] + total = sum(lower[i][k] * upper[k][j] for k in range(j)) + if upper[j][j] == 0: + raise ArithmeticError("No LU decomposition exists") lower[i][j] = (table[i][j] - total) / upper[j][j] lower[i][i] = 1 for j in range(i, columns): - total = 0 - for k in range(i): - total += lower[i][k] * upper[k][j] + total = sum(lower[i][k] * upper[k][j] for k in range(j)) upper[i][j] = table[i][j] - total return lower, upper From dc4f603dad22eab31892855555999b552e97e9d8 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 1 Apr 2023 08:47:24 +0300 Subject: [PATCH 2137/2908] Add Project Euler problem 187 solution 1 (#8182) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_187/__init__.py | 0 project_euler/problem_187/sol1.py | 58 +++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 project_euler/problem_187/__init__.py create mode 100644 project_euler/problem_187/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 1d3177801a2c..1a641d8ecb59 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -990,6 +990,8 @@ * [Sol1](project_euler/problem_174/sol1.py) * Problem 180 * [Sol1](project_euler/problem_180/sol1.py) + * Problem 187 + * [Sol1](project_euler/problem_187/sol1.py) * Problem 188 * [Sol1](project_euler/problem_188/sol1.py) * Problem 191 diff --git a/project_euler/problem_187/__init__.py b/project_euler/problem_187/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_187/sol1.py b/project_euler/problem_187/sol1.py new file mode 100644 index 000000000000..12f03e2a7023 --- /dev/null +++ b/project_euler/problem_187/sol1.py @@ -0,0 +1,58 @@ +""" +Project Euler Problem 187: https://projecteuler.net/problem=187 + +A composite is a number containing at least two prime factors. +For example, 15 = 3 x 5; 9 = 3 x 3; 12 = 2 x 2 x 3. + +There are ten composites below thirty containing precisely two, +not necessarily distinct, prime factors: 4, 6, 9, 10, 14, 15, 21, 22, 25, 26. + +How many composite integers, n < 10^8, have precisely two, +not necessarily distinct, prime factors? +""" + +from math import isqrt + + +def calculate_prime_numbers(max_number: int) -> list[int]: + """ + Returns prime numbers below max_number + + >>> calculate_prime_numbers(10) + [2, 3, 5, 7] + """ + + is_prime = [True] * max_number + for i in range(2, isqrt(max_number - 1) + 1): + if is_prime[i]: + for j in range(i**2, max_number, i): + is_prime[j] = False + + return [i for i in range(2, max_number) if is_prime[i]] + + +def solution(max_number: int = 10**8) -> int: + """ + Returns the number of composite integers below max_number have precisely two, + not necessarily distinct, prime factors + + >>> solution(30) + 10 + """ + + prime_numbers = calculate_prime_numbers(max_number // 2) + + semiprimes_count = 0 + left = 0 + right = len(prime_numbers) - 1 + while left <= right: + while prime_numbers[left] * prime_numbers[right] >= max_number: + right -= 1 + semiprimes_count += right - left + 1 + left += 1 + + return semiprimes_count + + +if __name__ == "__main__": + print(f"{solution() = }") From e4d90e2d5b92fdcff558f1848843dfbe20d81035 Mon Sep 17 00:00:00 2001 From: amirsoroush <114881632+amirsoroush@users.noreply.github.com> Date: Sat, 1 Apr 2023 09:26:43 +0300 Subject: [PATCH 2138/2908] change space complexity of linked list's __len__ from O(n) to O(1) (#8183) --- data_structures/linked_list/circular_linked_list.py | 2 +- data_structures/linked_list/doubly_linked_list.py | 2 +- data_structures/linked_list/merge_two_lists.py | 2 +- data_structures/linked_list/singly_linked_list.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 67a63cd55e19..9092fb29e3ff 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -24,7 +24,7 @@ def __iter__(self) -> Iterator[Any]: break def __len__(self) -> int: - return len(tuple(iter(self))) + return sum(1 for _ in self) def __repr__(self): return "->".join(str(item) for item in iter(self)) diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 6c81493fff85..41d07d63e005 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -51,7 +51,7 @@ def __len__(self): >>> len(linked_list) == 5 True """ - return len(tuple(iter(self))) + return sum(1 for _ in self) def insert_at_head(self, data): self.insert_at_nth(0, data) diff --git a/data_structures/linked_list/merge_two_lists.py b/data_structures/linked_list/merge_two_lists.py index 61e2412aa7fd..ca0d3bb48540 100644 --- a/data_structures/linked_list/merge_two_lists.py +++ b/data_structures/linked_list/merge_two_lists.py @@ -44,7 +44,7 @@ def __len__(self) -> int: >>> len(SortedLinkedList(test_data_odd)) 8 """ - return len(tuple(iter(self))) + return sum(1 for _ in self) def __str__(self) -> str: """ diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index bdeb5922ac67..a8f9e8ebb977 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -72,7 +72,7 @@ def __len__(self) -> int: >>> len(linked_list) 0 """ - return len(tuple(iter(self))) + return sum(1 for _ in self) def __repr__(self) -> str: """ From 9e0c357a57f76abc354d704012040f3f5511a941 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sat, 1 Apr 2023 11:59:26 +0530 Subject: [PATCH 2139/2908] chore: additional Project Euler solution hash (#8593) --- scripts/project_euler_answers.json | 109 ++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/scripts/project_euler_answers.json b/scripts/project_euler_answers.json index 6d354363ee5f..f2b876934766 100644 --- a/scripts/project_euler_answers.json +++ b/scripts/project_euler_answers.json @@ -723,5 +723,112 @@ "722": "9687101dfe209fd65f57a10603baa38ba83c9152e43a8b802b96f1e07f568e0e", "723": "74832787e7d4e0cb7991256c8f6d02775dffec0684de234786f25f898003f2de", "724": "fa05e2b497e7eafa64574017a4c45aadef6b163d907b03d63ba3f4021096d329", - "725": "005c873563f51bbebfdb1f8dbc383259e9a98e506bc87ae8d8c9044b81fc6418" + "725": "005c873563f51bbebfdb1f8dbc383259e9a98e506bc87ae8d8c9044b81fc6418", + "726": "93e41c533136bf4b436e493090fd4e7b277234db2a69c62a871f775ff26681bf", + "727": "c366f7426ca9351dcdde2e3bea01181897cda4d9b44977678ea3828419b84851", + "728": "8de62a644511d27c7c23c7722f56112b3c1ab9b05a078a98a0891f09f92464c6", + "729": "0ae82177174eef99fc80a2ec921295f61a6ac4dfed86a1bf333a50c26d01955c", + "730": "78cd876a176c8fbf7c2155b80dccbdededdbc43c28ef17b5a6e554d649325d38", + "731": "54afb9f829be51d29f90eecbfe40e5ba91f3a3bf538de62f3e34674af15eb542", + "732": "c4dc4610dcafc806b30e5d3f5560b57f462218a04397809843a7110838f0ebac", + "733": "bdde7d98d057d6a6ae360fd2f872d8bccb7e7f2971df37a3c5f20712ea3c618f", + "734": "9a514875bd9af26fcc565337771f852d311cd77033186e4d957e7b6c7b8ce018", + "735": "8bbc5a27c0031d8c44f3f73c99622a202cd6ea9a080049d615a7ae80ce6024f9", + "736": "e0d4c78b9b3dae51940877aff28275d036eccfc641111c8e34227ff6015a0fab", + "737": "a600884bcaa01797310c83b198bad58c98530289305af29b0bf75f679af38d3a", + "738": "c85f15fdaafe7d5525acff960afef7e4b8ffded5a7ee0d1dc2b0e8d0c26b9b46", + "739": "8716e9302f0fb90153e2f522bd88a710361a897480e4ccc0542473c704793518", + "740": "6ff41ee34b263b742cda109aee3be9ad6c95eec2ce31d6a9fc5353bba1b41afd", + "741": "99ac0eb9589b895e5755895206bbad5febd6bc29b2912df1c7544c547e26bca3", + "742": "7d2761a240aa577348df4813ea248088d0d6d8d421142c712ed576cdc90d4df9", + "743": "d93c42a129c0961b4e36738efae3b7e8ffae3a4daeced20e85bb740d3d72522d", + "744": "211f76700a010461486dde6c723720be85e68c192cd8a8ed0a88860b8ae9b0f0", + "745": "2d32dc1fea2f1b8600c0ada927b057b566870ceb5362cce71ac3693dcb7136ae", + "746": "2df1c2a0181f0c25e8d13d2a1eadba55a6b06267a2b22075fcf6867fb2e10c02", + "747": "a8d8f93142e320c6f0dd386c7a3bfb011bbdc15b85291a9be8f0266b3608175e", + "748": "7de937e04c10386b240afb8bb2ff590009946df8b7850a0329ccdb59fca8955f", + "749": "1a55f5484ccf964aeb186faedefa01db05d87180891dc2280b6eb85b6efb4779", + "750": "fa4318c213179e6af1c949be7cf47210f4383e0a44d191e2bad44228d3192f14", + "751": "12fe650fcb3afc214b3d647c655070e8142cfd397441fc7636ad7e6ffcaefde2", + "752": "e416c0123bc6b82df8726b328494db31aa4781d938a0a6e2107b1e44c73c0434", + "753": "0ee3299bc89e1e4c2fc79285fb1cd84c887456358a825e56be92244b7115f5af", + "754": "1370574b16207c41d3dafb62aa898379ec101ac36843634b1633b7b509d4c35a", + "755": "78bb4b18b13f5254cfafe872c0e93791ab5206b2851960dc6aebea8f62b9580c", + "756": "6becaabbda2e9ea22373e62e989b6b70467efa24fbe2f0d124d7a99a53e93f74", + "757": "fbfee0a5c4fa57a1dd6cf0c9bb2423cf7e7bcb130e67114aa360e42234987314", + "758": "8e4dfc259cec9dfd89d4b4ac8c33c75af6e0f5f7926526ee22ad4d45f93d3c18", + "759": "40bac0ed2e4f7861a6d9a2d87191a9034e177c319aa40a43638cc1b69572e5f2", + "760": "7ab50386a211f0815593389ab05b57a1a5eb5cbf5b9a85fe4afc517dcab74e06", + "761": "1cdb0318ac16e11c8d2ae7b1d7ca7138f7b1a461e9d75bd69be0f9cdd3add0c5", + "762": "84c4662267d5809380a540dfc2881665b3019047d74d5ef0a01f86e45f4b5b59", + "763": "f0def5903139447fabe7d106db5fff660d94b45af7b8b48d789596cf65ab2514", + "764": "7b4131f4d1e13d091ca7dd4d32317a14a2a24e6e1abd214df1c14c215287b330", + "765": "7558b775727426bccd945f5aa6b3e131e6034a7b1ff8576332329ef65d6a1663", + "766": "23c309430fa9546adb617457dbfd30fb7432904595c8c000e9b67ea23f32a53b", + "767": "70aef22ac2db8a5bdfcc42ff8dafbd2901e85e268f5f3c45085aa40c590b1d42", + "768": "b69a808dfc654b037e2f47ace16f48fe3bb553b3c8eed3e2b6421942fbf521d0", + "769": "78537a30577e806c6d8d94725e54d2d52e56f7f39f89c133cd5d0a2aad7e46e4", + "770": "c9d80c19c4895d1498bf809fcc37c447fa961fb325e5667eb35d6aa992966b41", + "771": "9803ace30c0d90d422e703fdf25a10a9342d0178a277ebc20c7bd6feac4c7a15", + "772": "f5a1e391af815ea6453db58a1bd71790f433c44ed63e5e93d8f5c045dfd5a464", + "773": "e1b93fc323c4d9c383100603339548e1e56ce9c38bcdcc425024c12b862ea8cb", + "774": "3646cd098b213014fb7bbc9597871585e62ee0cf2770e141f1df771237cc09ab", + "775": "d9d7d515ce7350c9e5696d85f68bbb42daa74b9e171a601dd04c823b18bb7757", + "776": "83286074d3bc86a5b449facb5fe5eafc91eb4c8031e2fb5e716443402cd8ed0f", + "777": "e62616a387d05b619d47cee3d49d5d2db19393736bf54b6cdd20933c0531cb7e", + "778": "d4de958ba44d25353de5b380e04d06c7968794ad50dbf6231ad0049ff53e106b", + "779": "c08ce54a59afc4af62f28b80a9c9a5190822d124eed8d73fd6db3e19c81e2157", + "780": "fc7ba646c16482f0f4f5ce2b06d21183dba2bdeaf9469b36b55bc7bc2d87baf3", + "781": "8fa5733f06838fb61b55b3e9d59c5061d922147e59947fe52e566dd975b2199f", + "782": "9f757d92df401ee049bc066bb2625c6287e5e4bcd38c958396a77a578f036a24", + "783": "270ff37f60c267a673bd4b223e44941f01ae9cfbf6bbdf99ca57af89b1e9a66f", + "784": "388b17c4c7b829cef767f83b4686c903faeec1241edfe5f58ee91d2b0c7f8dfc", + "785": "77cf600204c5265e1d5d3d26bf28ba1e92e6f24def040c16977450bec8b1cb99", + "786": "fb14022b7edbc6c7bfde27f35b49f6acaa4f0fc383af27614cb9d4a1980e626b", + "787": "7516ba0ac1951665723dcc4adcc52764d9497e7b6ed30bdb9937ac9df82b7c4f", + "788": "adede1d30258bb0f353af11f559b67f8b823304c71e967f52db52d002760c24f", + "789": "0c82e744a1f9bc57fd8ae8b2f479998455bc45126de971c59b68541c254e303a", + "790": "319847122251afd20d4d650047c55981a509fa2be78abd7c9c3caa0555e60a05", + "791": "2e0bbdcd0a8460e1e33c55668d0dc9752379a78b9f3561d7a17b922a5541a3fb", + "792": "5f77834c5a509023dd95dd98411eae1dd4bafd125deca590632f409f92fd257b", + "793": "dbfd900a3b31eeec2f14b916f5151611541cb716d80b7b9a1229de12293a02ea", + "794": "d019fe415aba832c4c761140d60c466c9aaad52b504df3167c17f2d3f0b277a7", + "795": "617b259349da44c2af2664acde113673ab3bb03a85d31f1be8f01027d0ebd4d3", + "796": "cba6b30a818d073398e5802211987f0897523e4752987bb445b2bca079670e22", + "797": "61e42cac3d7858b8850111a8c64c56432a18dd058dfb6afd773f07d703703b1a", + "798": "ae8b155d6b77522af79f7e4017fefe92aaa5d45eff132c83dc4d4bcfc9686020", + "799": "a41cb14ddf8f1948a01f590fbe53d9ca4e2faf48375ce1c306f91acf7c94e005", + "800": "c6a47bc6f02cf06be16728fb308c83f2f2ae350325ef7016867f5bdaea849d71", + "801": "d14b358c76b55106613f9c0a2112393338dfd01513b0fd231b79fc8db20e41f0", + "802": "22ae33e67fb48accfaa3b36e70c5a19066b974194c3130680de0c7cdce2d0f2e", + "803": "d95b3f9bbb7054042c1fba4db02f7223a2dad94977a36f08c8aaf92f373f9e78", + "804": "b0b1cf7253593eb2334c75e66dbe22b4b4540347485f1ea24e80226b4b18171c", + "805": "41b1ff5db0e70984ad20c50d1a9ac2b5a53ccd5f42796c8e948ae8880005fbb9", + "806": "b9c813beb39671adb8e1530555cadca44c21ddc7127932274918df2091dbd9ca", + "807": "745fd9ba97970d85a29877942839e41fc192794420e86f3bde39fd26db7a8bff", + "808": "6c73b947eb603602a7e8afadc83eaaa381a46db8b82a6fb89c9c1d93cb023fce", + "809": "eebac7753da4c1230dfce0f15fc124ffff01b0e432f0b74623b60cff71bbc9a9", + "810": "42be7899672a1a0046823603ce60dbeda7250a56fcb8d0913093850c85394307", + "811": "8698cd28ae4d93db36631870c33e4a8a527d970050d994666115f54260b64138", + "812": "dc2495924f37353db8b846323b8085fae9db502e890c513ed2e64ed7281f567f", + "813": "92179dde05aa6557baca65699fda50ca024d33a77078d8e128caa3c5db84064b", + "814": "344ed8cb7684307c00b7f03d751729a7f9d2a5f4a4cb4574594113d69593c0c1", + "815": "f642cf15345af3feab60e26a02aee038f759914906a5b2b469b46fdeee50ff59", + "816": "058178444e85f2aedb2f75d824a469747381f0bd3235d8c72df4385fec86eb07", + "817": "582fdc2233298192b09ceaf1463d6be06a09894075532630aa9d9efcfcb31da4", + "818": "67f6964d6ff114a43371b8375c44db2f1362df4f110b4a7ce8d79cf1b76621a0", + "819": "c7a82513ad48dfc87f2c1e0f2915b71464b7f5a16501c71df4ae4a8741dceef3", + "820": "9b23ae0181f320aadda2637ac2179c8b41b00715630c3acb643c7aee3b81cf90", + "821": "0941e396ff15b98fd7827de8e33ef94996d48ba719a88ba8e2da7f2605df3e5c", + "822": "ed8ef7f568939b9df1b77ae58344940b91c7e154a4367fe2b179bc7b9484d4e6", + "823": "05139328571a86096032b57e3a6a02a61acad4fb0d8f8e1b5d0ffb0d063ba697", + "826": "7f40f14ca65e5c06dd9ec9bbb212adb4d97a503199cb3c30ed921a04373bbe1c", + "827": "80461f02c63654c642382a6ffb7a44d0a3554434dfcfcea00ba91537724c7106", + "828": "520c196175625a0230afb76579ea26033372de3ef4c78aceb146b84322bfa871", + "829": "ed0089e61cf5540dd4a8fef1c468b96cf57f1d2bb79968755ba856d547ddafdf", + "831": "8ec445084427419ca6da405e0ded9814a4b4e11a2be84d88a8dea421f8e49992", + "832": "cfcb9ebef9308823f64798b5e12a59bf77ff6f92b0eae3790a61c0a26f577010", + "833": "e6ff3a5b257eb53366a32bfc8ea410a00a78bafa63650c76ac2bceddfbb42ff5", + "834": "b0d2a7e7d629ef14db9e7352a9a06d6ca66f750429170bb169ca52c172b8cc96", + "835": "bdfa1b1eecbad79f5de48bc6daee4d2b07689d7fb172aa306dd6094172b396f0" } From d66e1e873288bf399559c9ca40310d4b031aec50 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 1 Apr 2023 15:18:13 +0300 Subject: [PATCH 2140/2908] Add Project Euler problem 800 solution 1 (#8567) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 3 ++ project_euler/problem_800/__init__.py | 0 project_euler/problem_800/sol1.py | 65 +++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 project_euler/problem_800/__init__.py create mode 100644 project_euler/problem_800/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 1a641d8ecb59..18c573909773 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -317,6 +317,7 @@ * [Longest Sub Array](dynamic_programming/longest_sub_array.py) * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) * [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py) + * [Max Product Subarray](dynamic_programming/max_product_subarray.py) * [Max Sub Array](dynamic_programming/max_sub_array.py) * [Max Sum Contiguous Subsequence](dynamic_programming/max_sum_contiguous_subsequence.py) * [Min Distance Up Bottom](dynamic_programming/min_distance_up_bottom.py) @@ -1016,6 +1017,8 @@ * [Sol1](project_euler/problem_587/sol1.py) * Problem 686 * [Sol1](project_euler/problem_686/sol1.py) + * Problem 800 + * [Sol1](project_euler/problem_800/sol1.py) ## Quantum * [Bb84](quantum/bb84.py) diff --git a/project_euler/problem_800/__init__.py b/project_euler/problem_800/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_800/sol1.py b/project_euler/problem_800/sol1.py new file mode 100644 index 000000000000..f887787bcbc6 --- /dev/null +++ b/project_euler/problem_800/sol1.py @@ -0,0 +1,65 @@ +""" +Project Euler Problem 800: https://projecteuler.net/problem=800 + +An integer of the form p^q q^p with prime numbers p != q is called a hybrid-integer. +For example, 800 = 2^5 5^2 is a hybrid-integer. + +We define C(n) to be the number of hybrid-integers less than or equal to n. +You are given C(800) = 2 and C(800^800) = 10790 + +Find C(800800^800800) +""" + +from math import isqrt, log2 + + +def calculate_prime_numbers(max_number: int) -> list[int]: + """ + Returns prime numbers below max_number + + >>> calculate_prime_numbers(10) + [2, 3, 5, 7] + """ + + is_prime = [True] * max_number + for i in range(2, isqrt(max_number - 1) + 1): + if is_prime[i]: + for j in range(i**2, max_number, i): + is_prime[j] = False + + return [i for i in range(2, max_number) if is_prime[i]] + + +def solution(base: int = 800800, degree: int = 800800) -> int: + """ + Returns the number of hybrid-integers less than or equal to base^degree + + >>> solution(800, 1) + 2 + + >>> solution(800, 800) + 10790 + """ + + upper_bound = degree * log2(base) + max_prime = int(upper_bound) + prime_numbers = calculate_prime_numbers(max_prime) + + hybrid_integers_count = 0 + left = 0 + right = len(prime_numbers) - 1 + while left < right: + while ( + prime_numbers[right] * log2(prime_numbers[left]) + + prime_numbers[left] * log2(prime_numbers[right]) + > upper_bound + ): + right -= 1 + hybrid_integers_count += right - left + left += 1 + + return hybrid_integers_count + + +if __name__ == "__main__": + print(f"{solution() = }") From 3d2012c4ba3a9d9ddd80e518f0b5b9ba6c52df7d Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 1 Apr 2023 15:20:08 +0300 Subject: [PATCH 2141/2908] Add Project Euler problem 94 solution 1 (#8599) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 2 ++ project_euler/problem_094/__init__.py | 0 project_euler/problem_094/sol1.py | 44 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 project_euler/problem_094/__init__.py create mode 100644 project_euler/problem_094/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 18c573909773..c781b17bf05f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -937,6 +937,8 @@ * [Sol1](project_euler/problem_091/sol1.py) * Problem 092 * [Sol1](project_euler/problem_092/sol1.py) + * Problem 094 + * [Sol1](project_euler/problem_094/sol1.py) * Problem 097 * [Sol1](project_euler/problem_097/sol1.py) * Problem 099 diff --git a/project_euler/problem_094/__init__.py b/project_euler/problem_094/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_094/sol1.py b/project_euler/problem_094/sol1.py new file mode 100644 index 000000000000..a41292fe26fd --- /dev/null +++ b/project_euler/problem_094/sol1.py @@ -0,0 +1,44 @@ +""" +Project Euler Problem 94: https://projecteuler.net/problem=94 + +It is easily proved that no equilateral triangle exists with integral length sides and +integral area. However, the almost equilateral triangle 5-5-6 has an area of 12 square +units. + +We shall define an almost equilateral triangle to be a triangle for which two sides are +equal and the third differs by no more than one unit. + +Find the sum of the perimeters of all almost equilateral triangles with integral side +lengths and area and whose perimeters do not exceed one billion (1,000,000,000). +""" + + +def solution(max_perimeter: int = 10**9) -> int: + """ + Returns the sum of the perimeters of all almost equilateral triangles with integral + side lengths and area and whose perimeters do not exceed max_perimeter + + >>> solution(20) + 16 + """ + + prev_value = 1 + value = 2 + + perimeters_sum = 0 + i = 0 + perimeter = 0 + while perimeter <= max_perimeter: + perimeters_sum += perimeter + + prev_value += 2 * value + value += prev_value + + perimeter = 2 * value + 2 if i % 2 == 0 else 2 * value - 2 + i += 1 + + return perimeters_sum + + +if __name__ == "__main__": + print(f"{solution() = }") From 63710883c8634772fadf0145899cea4a1eadc31d Mon Sep 17 00:00:00 2001 From: amirsoroush <114881632+amirsoroush@users.noreply.github.com> Date: Sat, 1 Apr 2023 15:23:21 +0300 Subject: [PATCH 2142/2908] Remove extra `len` calls in doubly-linked-list's methods (#8600) --- data_structures/linked_list/doubly_linked_list.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 41d07d63e005..69763d12da15 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -81,7 +81,9 @@ def insert_at_nth(self, index: int, data): .... IndexError: list index out of range """ - if not 0 <= index <= len(self): + length = len(self) + + if not 0 <= index <= length: raise IndexError("list index out of range") new_node = Node(data) if self.head is None: @@ -90,7 +92,7 @@ def insert_at_nth(self, index: int, data): self.head.previous = new_node new_node.next = self.head self.head = new_node - elif index == len(self): + elif index == length: self.tail.next = new_node new_node.previous = self.tail self.tail = new_node @@ -131,15 +133,17 @@ def delete_at_nth(self, index: int): .... IndexError: list index out of range """ - if not 0 <= index <= len(self) - 1: + length = len(self) + + if not 0 <= index <= length - 1: raise IndexError("list index out of range") delete_node = self.head # default first node - if len(self) == 1: + if length == 1: self.head = self.tail = None elif index == 0: self.head = self.head.next self.head.previous = None - elif index == len(self) - 1: + elif index == length - 1: delete_node = self.tail self.tail = self.tail.previous self.tail.next = None From 59cae167e0e6b830b7ff5c89f5f2b8c747fb84c2 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 1 Apr 2023 19:22:33 +0300 Subject: [PATCH 2143/2908] Reduce the complexity of digital_image_processing/edge detection/canny.py (#8167) * Reduce the complexity of digital_image_processing/edge_detection/canny.py * Fix * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Fix review issues * Rename dst to destination --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../edge_detection/canny.py | 129 ++++++++++-------- 1 file changed, 75 insertions(+), 54 deletions(-) diff --git a/digital_image_processing/edge_detection/canny.py b/digital_image_processing/edge_detection/canny.py index a830355267c4..f8cbeedb3874 100644 --- a/digital_image_processing/edge_detection/canny.py +++ b/digital_image_processing/edge_detection/canny.py @@ -18,105 +18,126 @@ def gen_gaussian_kernel(k_size, 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)) - +def suppress_non_maximum(image_shape, gradient_direction, sobel_grad): """ 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): + destination = np.zeros(image_shape) + + for row in range(1, image_shape[0] - 1): + for col in range(1, image_shape[1] - 1): direction = gradient_direction[row, col] if ( - 0 <= direction < 22.5 + 0 <= direction < 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] + destination[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] + destination[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] + destination[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: - 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 + destination[row, col] = sobel_grad[row, col] + + return destination + + +def detect_high_low_threshold( + image_shape, destination, threshold_low, threshold_high, weak, strong +): + """ + 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. + """ + for row in range(1, image_shape[0] - 1): + for col in range(1, image_shape[1] - 1): + if destination[row, col] >= threshold_high: + destination[row, col] = strong + elif destination[row, col] <= threshold_low: + destination[row, col] = 0 else: - dst[row, col] = weak + destination[row, col] = weak + +def track_edge(image_shape, destination, weak, strong): """ 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: + for row in range(1, image_shape[0]): + for col in range(1, image_shape[1]): + if destination[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], + destination[row, col + 1], + destination[row, col - 1], + destination[row - 1, col], + destination[row + 1, col], + destination[row - 1, col - 1], + destination[row + 1, col - 1], + destination[row - 1, col + 1], + destination[row + 1, col + 1], ): - dst[row, col] = strong + destination[row, col] = strong else: - dst[row, col] = 0 + destination[row, col] = 0 + + +def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): + # 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 = PI + np.rad2deg(sobel_theta) + + destination = suppress_non_maximum(image.shape, gradient_direction, sobel_grad) + + detect_high_low_threshold( + image.shape, destination, threshold_low, threshold_high, weak, strong + ) + + track_edge(image.shape, destination, weak, strong) - return dst + return destination 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) + canny_destination = canny(lena) + cv2.imshow("canny", canny_destination) cv2.waitKey(0) From a213cea5f5a74e0a6b19240526779a3b0b1f270d Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 1 Apr 2023 12:39:22 -0400 Subject: [PATCH 2144/2908] Fix `mypy` errors in `dilation_operation.py` (#8595) * updating DIRECTORY.md * Fix mypy errors in dilation_operation.py * Rename functions to use snake case * updating DIRECTORY.md * updating DIRECTORY.md * Replace raw file string with pathlib Path * Update digital_image_processing/morphological_operations/dilation_operation.py Co-authored-by: Christian Clauss --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../dilation_operation.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/digital_image_processing/morphological_operations/dilation_operation.py b/digital_image_processing/morphological_operations/dilation_operation.py index c8380737d219..e49b955c1480 100644 --- a/digital_image_processing/morphological_operations/dilation_operation.py +++ b/digital_image_processing/morphological_operations/dilation_operation.py @@ -1,33 +1,35 @@ +from pathlib import Path + import numpy as np from PIL import Image -def rgb2gray(rgb: np.array) -> np.array: +def rgb_to_gray(rgb: np.ndarray) -> np.ndarray: """ Return gray image from rgb image - >>> rgb2gray(np.array([[[127, 255, 0]]])) + >>> rgb_to_gray(np.array([[[127, 255, 0]]])) array([[187.6453]]) - >>> rgb2gray(np.array([[[0, 0, 0]]])) + >>> rgb_to_gray(np.array([[[0, 0, 0]]])) array([[0.]]) - >>> rgb2gray(np.array([[[2, 4, 1]]])) + >>> rgb_to_gray(np.array([[[2, 4, 1]]])) array([[3.0598]]) - >>> rgb2gray(np.array([[[26, 255, 14], [5, 147, 20], [1, 200, 0]]])) + >>> rgb_to_gray(np.array([[[26, 255, 14], [5, 147, 20], [1, 200, 0]]])) array([[159.0524, 90.0635, 117.6989]]) """ r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2] return 0.2989 * r + 0.5870 * g + 0.1140 * b -def gray2binary(gray: np.array) -> np.array: +def gray_to_binary(gray: np.ndarray) -> np.ndarray: """ Return binary image from gray image - >>> gray2binary(np.array([[127, 255, 0]])) + >>> gray_to_binary(np.array([[127, 255, 0]])) array([[False, True, False]]) - >>> gray2binary(np.array([[0]])) + >>> gray_to_binary(np.array([[0]])) array([[False]]) - >>> gray2binary(np.array([[26.2409, 4.9315, 1.4729]])) + >>> gray_to_binary(np.array([[26.2409, 4.9315, 1.4729]])) array([[False, False, False]]) - >>> gray2binary(np.array([[26, 255, 14], [5, 147, 20], [1, 200, 0]])) + >>> gray_to_binary(np.array([[26, 255, 14], [5, 147, 20], [1, 200, 0]])) array([[False, True, False], [False, True, False], [False, True, False]]) @@ -35,7 +37,7 @@ def gray2binary(gray: np.array) -> np.array: return (gray > 127) & (gray <= 255) -def dilation(image: np.array, kernel: np.array) -> np.array: +def dilation(image: np.ndarray, kernel: np.ndarray) -> np.ndarray: """ Return dilated image >>> dilation(np.array([[True, False, True]]), np.array([[0, 1, 0]])) @@ -61,14 +63,13 @@ def dilation(image: np.array, kernel: np.array) -> np.array: return output -# kernel to be applied -structuring_element = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]) - - if __name__ == "__main__": # read original image - image = np.array(Image.open(r"..\image_data\lena.jpg")) - output = dilation(gray2binary(rgb2gray(image)), structuring_element) + lena_path = Path(__file__).resolve().parent / "image_data" / "lena.jpg" + lena = np.array(Image.open(lena_path)) + # kernel to be applied + structuring_element = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]) + output = dilation(gray_to_binary(rgb_to_gray(lena)), structuring_element) # Save the output image pil_img = Image.fromarray(output).convert("RGB") pil_img.save("result_dilation.png") From 84b6852de80bb51c185c30942bff47f9c451c74d Mon Sep 17 00:00:00 2001 From: Blake Reimer Date: Sat, 1 Apr 2023 10:43:07 -0600 Subject: [PATCH 2145/2908] Graham's Law (#8162) * grahams law * doctest and type hints * doctest formatting * peer review updates --- physics/grahams_law.py | 208 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 physics/grahams_law.py diff --git a/physics/grahams_law.py b/physics/grahams_law.py new file mode 100644 index 000000000000..6e5d75127e83 --- /dev/null +++ b/physics/grahams_law.py @@ -0,0 +1,208 @@ +""" +Title: Graham's Law of Effusion + +Description: Graham's law of effusion states that the rate of effusion of a gas is +inversely proportional to the square root of the molar mass of its particles: + +r1/r2 = sqrt(m2/m1) + +r1 = Rate of effusion for the first gas. +r2 = Rate of effusion for the second gas. +m1 = Molar mass of the first gas. +m2 = Molar mass of the second gas. + +(Description adapted from https://en.wikipedia.org/wiki/Graham%27s_law) +""" + +from math import pow, sqrt + + +def validate(*values: float) -> bool: + """ + Input Parameters: + ----------------- + effusion_rate_1: Effustion rate of first gas (m^2/s, mm^2/s, etc.) + effusion_rate_2: Effustion rate of second gas (m^2/s, mm^2/s, etc.) + molar_mass_1: Molar mass of the first gas (g/mol, kg/kmol, etc.) + molar_mass_2: Molar mass of the second gas (g/mol, kg/kmol, etc.) + + Returns: + -------- + >>> validate(2.016, 4.002) + True + >>> validate(-2.016, 4.002) + False + >>> validate() + False + """ + result = len(values) > 0 and all(value > 0.0 for value in values) + return result + + +def effusion_ratio(molar_mass_1: float, molar_mass_2: float) -> float | ValueError: + """ + Input Parameters: + ----------------- + molar_mass_1: Molar mass of the first gas (g/mol, kg/kmol, etc.) + molar_mass_2: Molar mass of the second gas (g/mol, kg/kmol, etc.) + + Returns: + -------- + >>> effusion_ratio(2.016, 4.002) + 1.408943 + >>> effusion_ratio(-2.016, 4.002) + ValueError('Input Error: Molar mass values must greater than 0.') + >>> effusion_ratio(2.016) + Traceback (most recent call last): + ... + TypeError: effusion_ratio() missing 1 required positional argument: 'molar_mass_2' + """ + return ( + round(sqrt(molar_mass_2 / molar_mass_1), 6) + if validate(molar_mass_1, molar_mass_2) + else ValueError("Input Error: Molar mass values must greater than 0.") + ) + + +def first_effusion_rate( + effusion_rate: float, molar_mass_1: float, molar_mass_2: float +) -> float | ValueError: + """ + Input Parameters: + ----------------- + effusion_rate: Effustion rate of second gas (m^2/s, mm^2/s, etc.) + molar_mass_1: Molar mass of the first gas (g/mol, kg/kmol, etc.) + molar_mass_2: Molar mass of the second gas (g/mol, kg/kmol, etc.) + + Returns: + -------- + >>> first_effusion_rate(1, 2.016, 4.002) + 1.408943 + >>> first_effusion_rate(-1, 2.016, 4.002) + ValueError('Input Error: Molar mass and effusion rate values must greater than 0.') + >>> first_effusion_rate(1) + Traceback (most recent call last): + ... + TypeError: first_effusion_rate() missing 2 required positional arguments: \ +'molar_mass_1' and 'molar_mass_2' + >>> first_effusion_rate(1, 2.016) + Traceback (most recent call last): + ... + TypeError: first_effusion_rate() missing 1 required positional argument: \ +'molar_mass_2' + """ + return ( + round(effusion_rate * sqrt(molar_mass_2 / molar_mass_1), 6) + if validate(effusion_rate, molar_mass_1, molar_mass_2) + else ValueError( + "Input Error: Molar mass and effusion rate values must greater than 0." + ) + ) + + +def second_effusion_rate( + effusion_rate: float, molar_mass_1: float, molar_mass_2: float +) -> float | ValueError: + """ + Input Parameters: + ----------------- + effusion_rate: Effustion rate of second gas (m^2/s, mm^2/s, etc.) + molar_mass_1: Molar mass of the first gas (g/mol, kg/kmol, etc.) + molar_mass_2: Molar mass of the second gas (g/mol, kg/kmol, etc.) + + Returns: + -------- + >>> second_effusion_rate(1, 2.016, 4.002) + 0.709752 + >>> second_effusion_rate(-1, 2.016, 4.002) + ValueError('Input Error: Molar mass and effusion rate values must greater than 0.') + >>> second_effusion_rate(1) + Traceback (most recent call last): + ... + TypeError: second_effusion_rate() missing 2 required positional arguments: \ +'molar_mass_1' and 'molar_mass_2' + >>> second_effusion_rate(1, 2.016) + Traceback (most recent call last): + ... + TypeError: second_effusion_rate() missing 1 required positional argument: \ +'molar_mass_2' + """ + return ( + round(effusion_rate / sqrt(molar_mass_2 / molar_mass_1), 6) + if validate(effusion_rate, molar_mass_1, molar_mass_2) + else ValueError( + "Input Error: Molar mass and effusion rate values must greater than 0." + ) + ) + + +def first_molar_mass( + molar_mass: float, effusion_rate_1: float, effusion_rate_2: float +) -> float | ValueError: + """ + Input Parameters: + ----------------- + molar_mass: Molar mass of the first gas (g/mol, kg/kmol, etc.) + effusion_rate_1: Effustion rate of first gas (m^2/s, mm^2/s, etc.) + effusion_rate_2: Effustion rate of second gas (m^2/s, mm^2/s, etc.) + + Returns: + -------- + >>> first_molar_mass(2, 1.408943, 0.709752) + 0.507524 + >>> first_molar_mass(-1, 2.016, 4.002) + ValueError('Input Error: Molar mass and effusion rate values must greater than 0.') + >>> first_molar_mass(1) + Traceback (most recent call last): + ... + TypeError: first_molar_mass() missing 2 required positional arguments: \ +'effusion_rate_1' and 'effusion_rate_2' + >>> first_molar_mass(1, 2.016) + Traceback (most recent call last): + ... + TypeError: first_molar_mass() missing 1 required positional argument: \ +'effusion_rate_2' + """ + return ( + round(molar_mass / pow(effusion_rate_1 / effusion_rate_2, 2), 6) + if validate(molar_mass, effusion_rate_1, effusion_rate_2) + else ValueError( + "Input Error: Molar mass and effusion rate values must greater than 0." + ) + ) + + +def second_molar_mass( + molar_mass: float, effusion_rate_1: float, effusion_rate_2: float +) -> float | ValueError: + """ + Input Parameters: + ----------------- + molar_mass: Molar mass of the first gas (g/mol, kg/kmol, etc.) + effusion_rate_1: Effustion rate of first gas (m^2/s, mm^2/s, etc.) + effusion_rate_2: Effustion rate of second gas (m^2/s, mm^2/s, etc.) + + Returns: + -------- + >>> second_molar_mass(2, 1.408943, 0.709752) + 1.970351 + >>> second_molar_mass(-2, 1.408943, 0.709752) + ValueError('Input Error: Molar mass and effusion rate values must greater than 0.') + >>> second_molar_mass(1) + Traceback (most recent call last): + ... + TypeError: second_molar_mass() missing 2 required positional arguments: \ +'effusion_rate_1' and 'effusion_rate_2' + >>> second_molar_mass(1, 2.016) + Traceback (most recent call last): + ... + TypeError: second_molar_mass() missing 1 required positional argument: \ +'effusion_rate_2' + """ + return ( + round(pow(effusion_rate_1 / effusion_rate_2, 2) / molar_mass, 6) + if validate(molar_mass, effusion_rate_1, effusion_rate_2) + else ValueError( + "Input Error: Molar mass and effusion rate values must greater than 0." + ) + ) From 56a40eb3ee9aa151defd97597f4e67acf294089f Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 1 Apr 2023 20:43:11 +0300 Subject: [PATCH 2146/2908] Reenable files when TensorFlow supports the current Python (#8602) * Remove python_version < "3.11" for tensorflow * Reenable neural_network/input_data.py_tf * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Try to fix ruff * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Try to fix ruff * Try to fix ruff * Try to fix ruff * Try to fix pre-commit * Try to fix * Fix * Fix * Reenable dynamic_programming/k_means_clustering_tensorflow.py_tf * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Try to fix ruff --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 2 + ...py_tf => k_means_clustering_tensorflow.py} | 9 +- .../{input_data.py_tf => input_data.py} | 98 +++++++++---------- requirements.txt | 2 +- 4 files changed, 55 insertions(+), 56 deletions(-) rename dynamic_programming/{k_means_clustering_tensorflow.py_tf => k_means_clustering_tensorflow.py} (98%) rename neural_network/{input_data.py_tf => input_data.py} (83%) diff --git a/DIRECTORY.md b/DIRECTORY.md index c781b17bf05f..34967082b359 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -309,6 +309,7 @@ * [Floyd Warshall](dynamic_programming/floyd_warshall.py) * [Integer Partition](dynamic_programming/integer_partition.py) * [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py) + * [K Means Clustering Tensorflow](dynamic_programming/k_means_clustering_tensorflow.py) * [Knapsack](dynamic_programming/knapsack.py) * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) * [Longest Common Substring](dynamic_programming/longest_common_substring.py) @@ -685,6 +686,7 @@ * [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) + * [Input Data](neural_network/input_data.py) * [Perceptron](neural_network/perceptron.py) * [Simple Neural Network](neural_network/simple_neural_network.py) diff --git a/dynamic_programming/k_means_clustering_tensorflow.py_tf b/dynamic_programming/k_means_clustering_tensorflow.py similarity index 98% rename from dynamic_programming/k_means_clustering_tensorflow.py_tf rename to dynamic_programming/k_means_clustering_tensorflow.py index 4fbcedeaa0dc..8d3f6f0dfbcb 100644 --- a/dynamic_programming/k_means_clustering_tensorflow.py_tf +++ b/dynamic_programming/k_means_clustering_tensorflow.py @@ -1,9 +1,10 @@ -import tensorflow as tf from random import shuffle + +import tensorflow as tf from numpy import array -def TFKMeansCluster(vectors, noofclusters): +def tf_k_means_cluster(vectors, noofclusters): """ K-Means Clustering using TensorFlow. 'vectors' should be a n*k 2-D NumPy array, where n is the number @@ -30,7 +31,6 @@ def TFKMeansCluster(vectors, noofclusters): graph = tf.Graph() with graph.as_default(): - # SESSION OF COMPUTATION sess = tf.Session() @@ -95,8 +95,7 @@ def TFKMeansCluster(vectors, noofclusters): # 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): - + for _ in range(noofiterations): ##EXPECTATION STEP ##Based on the centroid locations till last iteration, compute ##the _expected_ centroid assignments. diff --git a/neural_network/input_data.py_tf b/neural_network/input_data.py similarity index 83% rename from neural_network/input_data.py_tf rename to neural_network/input_data.py index 0e22ac0bcda5..2a32f0b82c37 100644 --- a/neural_network/input_data.py_tf +++ b/neural_network/input_data.py @@ -21,13 +21,10 @@ import collections import gzip import os +import urllib 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.framework import dtypes, random_seed from tensorflow.python.platform import gfile from tensorflow.python.util.deprecation import deprecated @@ -46,16 +43,16 @@ def _read32(bytestream): 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. + Args: + f: A file object that can be passed into a gzip reader. - Returns: - data: A 4D uint8 numpy array [index, y, x, depth]. + Returns: + data: A 4D uint8 numpy array [index, y, x, depth]. - Raises: - ValueError: If the bytestream does not start with 2051. + Raises: + ValueError: If the bytestream does not start with 2051. - """ + """ print("Extracting", f.name) with gzip.GzipFile(fileobj=f) as bytestream: magic = _read32(bytestream) @@ -86,17 +83,17 @@ def _dense_to_one_hot(labels_dense, num_classes): 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. + 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. + Returns: + labels: a 1D uint8 numpy array. - Raises: - ValueError: If the bystream doesn't start with 2049. - """ + Raises: + ValueError: If the bystream doesn't start with 2049. + """ print("Extracting", f.name) with gzip.GzipFile(fileobj=f) as bytestream: magic = _read32(bytestream) @@ -115,8 +112,8 @@ def _extract_labels(f, one_hot=False, num_classes=10): class _DataSet: """Container class for a _DataSet (deprecated). - THIS CLASS IS DEPRECATED. - """ + THIS CLASS IS DEPRECATED. + """ @deprecated( None, @@ -135,21 +132,21 @@ def __init__( ): """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. - """ + 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) @@ -206,8 +203,8 @@ def next_batch(self, batch_size, fake_data=False, shuffle=True): else: fake_label = 0 return ( - [fake_image for _ in xrange(batch_size)], - [fake_label for _ in xrange(batch_size)], + [fake_image for _ in range(batch_size)], + [fake_label for _ in range(batch_size)], ) start = self._index_in_epoch # Shuffle for the first epoch @@ -250,19 +247,19 @@ def next_batch(self, batch_size, fake_data=False, shuffle=True): 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. + 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. - """ + 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) + urllib.request.urlretrieve(source_url, filepath) # noqa: S310 with gfile.GFile(filepath) as f: size = f.size() print("Successfully downloaded", filename, size, "bytes.") @@ -328,7 +325,8 @@ def fake(): if not 0 <= validation_size <= len(train_images): raise ValueError( - f"Validation size should be between 0 and {len(train_images)}. Received: {validation_size}." + f"Validation size should be between 0 and {len(train_images)}. " + f"Received: {validation_size}." ) validation_images = train_images[:validation_size] @@ -336,7 +334,7 @@ def fake(): train_images = train_images[validation_size:] train_labels = train_labels[validation_size:] - options = dict(dtype=dtype, reshape=reshape, seed=seed) + options = {"dtype": dtype, "reshape": reshape, "seed": seed} train = _DataSet(train_images, train_labels, **options) validation = _DataSet(validation_images, validation_labels, **options) diff --git a/requirements.txt b/requirements.txt index a1d607df07e1..acfbc823e77f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ scikit-fuzzy scikit-learn statsmodels sympy -tensorflow; python_version < "3.11" +tensorflow texttable tweepy xgboost From 33114f0272bcc1fafa6ce0f40d92ded908747ce3 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 1 Apr 2023 16:05:01 -0400 Subject: [PATCH 2147/2908] Revamp `md5.py` (#8065) * Add type hints to md5.py * Rename some vars to snake case * Specify functions imported from math * Rename vars and functions to be more descriptive * Make tests from test function into doctests * Clarify more var names * Refactor some MD5 code into preprocess function * Simplify loop indices in get_block_words * Add more detailed comments, docs, and doctests * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Add type hints to md5.py * Rename some vars to snake case * Specify functions imported from math * Rename vars and functions to be more descriptive * Make tests from test function into doctests * Clarify more var names * Refactor some MD5 code into preprocess function * Simplify loop indices in get_block_words * Add more detailed comments, docs, and doctests * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Convert str types to bytes * Add tests comparing md5_me to hashlib's md5 * Replace line-break backslashes with parentheses --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + hashes/md5.py | 372 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 290 insertions(+), 83 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 34967082b359..b1adc23f6e61 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -717,6 +717,7 @@ * [Archimedes Principle](physics/archimedes_principle.py) * [Casimir Effect](physics/casimir_effect.py) * [Centripetal Force](physics/centripetal_force.py) + * [Grahams Law](physics/grahams_law.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) * [Hubble Parameter](physics/hubble_parameter.py) * [Ideal Gas Law](physics/ideal_gas_law.py) diff --git a/hashes/md5.py b/hashes/md5.py index 2020bf2e53bf..2187006ec8a9 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -1,91 +1,223 @@ -import math +""" +The MD5 algorithm is a hash function that's commonly used as a checksum to +detect data corruption. The algorithm works by processing a given message in +blocks of 512 bits, padding the message as needed. It uses the blocks to operate +a 128-bit state and performs a total of 64 such operations. Note that all values +are little-endian, so inputs are converted as needed. +Although MD5 was used as a cryptographic hash function in the past, it's since +been cracked, so it shouldn't be used for security purposes. -def rearrange(bit_string_32): - """[summary] - Regroups the given binary string. +For more info, see https://en.wikipedia.org/wiki/MD5 +""" + +from collections.abc import Generator +from math import sin + + +def to_little_endian(string_32: bytes) -> bytes: + """ + Converts the given string to little-endian in groups of 8 chars. Arguments: - bitString32 {[string]} -- [32 bit binary] + string_32 {[string]} -- [32-char string] Raises: - ValueError -- [if the given string not are 32 bit binary string] + ValueError -- [input is not 32 char] Returns: - [string] -- [32 bit binary string] - >>> rearrange('1234567890abcdfghijklmnopqrstuvw') - 'pqrstuvwhijklmno90abcdfg12345678' + 32-char little-endian string + >>> to_little_endian(b'1234567890abcdfghijklmnopqrstuvw') + b'pqrstuvwhijklmno90abcdfg12345678' + >>> to_little_endian(b'1234567890') + Traceback (most recent call last): + ... + ValueError: Input must be of length 32 """ + if len(string_32) != 32: + raise ValueError("Input must be of length 32") - if len(bit_string_32) != 32: - raise ValueError("Need length 32") - new_string = "" + little_endian = b"" for i in [3, 2, 1, 0]: - new_string += bit_string_32[8 * i : 8 * i + 8] - return new_string + little_endian += string_32[8 * i : 8 * i + 8] + return little_endian + + +def reformat_hex(i: int) -> bytes: + """ + Converts the given non-negative integer to hex string. + Example: Suppose the input is the following: + i = 1234 -def reformat_hex(i): - """[summary] - Converts the given integer into 8-digit hex number. + The input is 0x000004d2 in hex, so the little-endian hex string is + "d2040000". Arguments: - i {[int]} -- [integer] + i {[int]} -- [integer] + + Raises: + ValueError -- [input is negative] + + Returns: + 8-char little-endian hex string + + >>> reformat_hex(1234) + b'd2040000' >>> reformat_hex(666) - '9a020000' + b'9a020000' + >>> reformat_hex(0) + b'00000000' + >>> reformat_hex(1234567890) + b'd2029649' + >>> reformat_hex(1234567890987654321) + b'b11c6cb1' + >>> reformat_hex(-1) + Traceback (most recent call last): + ... + ValueError: Input must be non-negative """ + if i < 0: + raise ValueError("Input must be non-negative") - hexrep = format(i, "08x") - thing = "" + hex_rep = format(i, "08x")[-8:] + little_endian_hex = b"" for i in [3, 2, 1, 0]: - thing += hexrep[2 * i : 2 * i + 2] - return thing + little_endian_hex += hex_rep[2 * i : 2 * i + 2].encode("utf-8") + return little_endian_hex -def pad(bit_string): - """[summary] - Fills up the binary string to a 512 bit binary string +def preprocess(message: bytes) -> bytes: + """ + Preprocesses the message string: + - Convert message to bit string + - Pad bit string to a multiple of 512 chars: + - Append a 1 + - Append 0's until length = 448 (mod 512) + - Append length of original message (64 chars) + + Example: Suppose the input is the following: + message = "a" + + The message bit string is "01100001", which is 8 bits long. Thus, the + bit string needs 439 bits of padding so that + (bit_string + "1" + padding) = 448 (mod 512). + The message length is "000010000...0" in 64-bit little-endian binary. + The combined bit string is then 512 bits long. Arguments: - bitString {[string]} -- [binary string] + message {[string]} -- [message string] Returns: - [string] -- [binary string] + processed bit string padded to a multiple of 512 chars + + >>> preprocess(b"a") == (b"01100001" + b"1" + + ... (b"0" * 439) + b"00001000" + (b"0" * 56)) + True + >>> preprocess(b"") == b"1" + (b"0" * 447) + (b"0" * 64) + True """ - start_length = len(bit_string) - bit_string += "1" + bit_string = b"" + for char in message: + bit_string += format(char, "08b").encode("utf-8") + start_len = format(len(bit_string), "064b").encode("utf-8") + + # Pad bit_string to a multiple of 512 chars + bit_string += b"1" while len(bit_string) % 512 != 448: - bit_string += "0" - last_part = format(start_length, "064b") - bit_string += rearrange(last_part[32:]) + rearrange(last_part[:32]) + bit_string += b"0" + bit_string += to_little_endian(start_len[32:]) + to_little_endian(start_len[:32]) + return bit_string -def get_block(bit_string): - """[summary] - Iterator: - Returns by each call a list of length 16 with the 32 bit - integer blocks. +def get_block_words(bit_string: bytes) -> Generator[list[int], None, None]: + """ + Splits bit string into blocks of 512 chars and yields each block as a list + of 32-bit words + + Example: Suppose the input is the following: + bit_string = + "000000000...0" + # 0x00 (32 bits, padded to the right) + "000000010...0" + # 0x01 (32 bits, padded to the right) + "000000100...0" + # 0x02 (32 bits, padded to the right) + "000000110...0" + # 0x03 (32 bits, padded to the right) + ... + "000011110...0" # 0x0a (32 bits, padded to the right) + + Then len(bit_string) == 512, so there'll be 1 block. The block is split + into 32-bit words, and each word is converted to little endian. The + first word is interpreted as 0 in decimal, the second word is + interpreted as 1 in decimal, etc. + + Thus, block_words == [[0, 1, 2, 3, ..., 15]]. Arguments: - bit_string {[string]} -- [binary string >= 512] + bit_string {[string]} -- [bit string with multiple of 512 as length] + + Raises: + ValueError -- [length of bit string isn't multiple of 512] + + Yields: + a list of 16 32-bit words + + >>> test_string = ("".join(format(n << 24, "032b") for n in range(16)) + ... .encode("utf-8")) + >>> list(get_block_words(test_string)) + [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]] + >>> list(get_block_words(test_string * 4)) == [list(range(16))] * 4 + True + >>> list(get_block_words(b"1" * 512)) == [[4294967295] * 16] + True + >>> list(get_block_words(b"")) + [] + >>> list(get_block_words(b"1111")) + Traceback (most recent call last): + ... + ValueError: Input must have length that's a multiple of 512 """ + if len(bit_string) % 512 != 0: + raise ValueError("Input must have length that's a multiple of 512") - curr_pos = 0 - while curr_pos < len(bit_string): - curr_part = bit_string[curr_pos : curr_pos + 512] - my_splits = [] - for i in range(16): - my_splits.append(int(rearrange(curr_part[32 * i : 32 * i + 32]), 2)) - yield my_splits - curr_pos += 512 + for pos in range(0, len(bit_string), 512): + block = bit_string[pos : pos + 512] + block_words = [] + for i in range(0, 512, 32): + block_words.append(int(to_little_endian(block[i : i + 32]), 2)) + yield block_words -def not32(i): +def not_32(i: int) -> int: """ - >>> not32(34) + Perform bitwise NOT on given int. + + Arguments: + i {[int]} -- [given int] + + Raises: + ValueError -- [input is negative] + + Returns: + Result of bitwise NOT on i + + >>> not_32(34) 4294967261 + >>> not_32(1234) + 4294966061 + >>> not_32(4294966061) + 1234 + >>> not_32(0) + 4294967295 + >>> not_32(1) + 4294967294 + >>> not_32(-1) + Traceback (most recent call last): + ... + ValueError: Input must be non-negative """ + if i < 0: + raise ValueError("Input must be non-negative") + i_str = format(i, "032b") new_str = "" for c in i_str: @@ -93,35 +225,114 @@ def not32(i): return int(new_str, 2) -def sum32(a, b): +def sum_32(a: int, b: int) -> int: + """ + Add two numbers as 32-bit ints. + + Arguments: + a {[int]} -- [first given int] + b {[int]} -- [second given int] + + Returns: + (a + b) as an unsigned 32-bit int + + >>> sum_32(1, 1) + 2 + >>> sum_32(2, 3) + 5 + >>> sum_32(0, 0) + 0 + >>> sum_32(-1, -1) + 4294967294 + >>> sum_32(4294967295, 1) + 0 + """ return (a + b) % 2**32 -def leftrot32(i, s): - return (i << s) ^ (i >> (32 - s)) +def left_rotate_32(i: int, shift: int) -> int: + """ + Rotate the bits of a given int left by a given amount. + + Arguments: + i {[int]} -- [given int] + shift {[int]} -- [shift amount] + + Raises: + ValueError -- [either given int or shift is negative] + Returns: + `i` rotated to the left by `shift` bits + + >>> left_rotate_32(1234, 1) + 2468 + >>> left_rotate_32(1111, 4) + 17776 + >>> left_rotate_32(2147483648, 1) + 1 + >>> left_rotate_32(2147483648, 3) + 4 + >>> left_rotate_32(4294967295, 4) + 4294967295 + >>> left_rotate_32(1234, 0) + 1234 + >>> left_rotate_32(0, 0) + 0 + >>> left_rotate_32(-1, 0) + Traceback (most recent call last): + ... + ValueError: Input must be non-negative + >>> left_rotate_32(0, -1) + Traceback (most recent call last): + ... + ValueError: Shift must be non-negative + """ + if i < 0: + raise ValueError("Input must be non-negative") + if shift < 0: + raise ValueError("Shift must be non-negative") + return ((i << shift) ^ (i >> (32 - shift))) % 2**32 + + +def md5_me(message: bytes) -> bytes: + """ + Returns the 32-char MD5 hash of a given message. -def md5me(test_string): - """[summary] - Returns a 32-bit hash code of the string 'testString' + Reference: https://en.wikipedia.org/wiki/MD5#Algorithm Arguments: - testString {[string]} -- [message] + message {[string]} -- [message] + + Returns: + 32-char MD5 hash string + + >>> md5_me(b"") + b'd41d8cd98f00b204e9800998ecf8427e' + >>> md5_me(b"The quick brown fox jumps over the lazy dog") + b'9e107d9d372bb6826bd81d3542a419d6' + >>> md5_me(b"The quick brown fox jumps over the lazy dog.") + b'e4d909c290d0fb1ca068ffaddf22cbd0' + + >>> import hashlib + >>> from string import ascii_letters + >>> msgs = [b"", ascii_letters.encode("utf-8"), "Üñîçø∂é".encode("utf-8"), + ... b"The quick brown fox jumps over the lazy dog."] + >>> all(md5_me(msg) == hashlib.md5(msg).hexdigest().encode("utf-8") for msg in msgs) + True """ - bs = "" - for i in test_string: - bs += format(ord(i), "08b") - bs = pad(bs) + # Convert to bit string, add padding and append message length + bit_string = preprocess(message) - tvals = [int(2**32 * abs(math.sin(i + 1))) for i in range(64)] + added_consts = [int(2**32 * abs(sin(i + 1))) for i in range(64)] + # Starting states a0 = 0x67452301 b0 = 0xEFCDAB89 c0 = 0x98BADCFE d0 = 0x10325476 - s = [ + shift_amounts = [ 7, 12, 17, @@ -188,51 +399,46 @@ def md5me(test_string): 21, ] - for m in get_block(bs): + # Process bit string in chunks, each with 16 32-char words + for block_words in get_block_words(bit_string): a = a0 b = b0 c = c0 d = d0 + + # Hash current chunk for i in range(64): if i <= 15: - # f = (B & C) | (not32(B) & D) + # f = (b & c) | (not_32(b) & d) # Alternate definition for f f = d ^ (b & (c ^ d)) g = i elif i <= 31: - # f = (D & B) | (not32(D) & C) + # f = (d & b) | (not_32(d) & c) # Alternate definition for f 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)) + f = c ^ (b | not_32(d)) g = (7 * i) % 16 - dtemp = d + f = (f + a + added_consts[i] + block_words[g]) % 2**32 + a = 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) + b = sum_32(b, left_rotate_32(f, shift_amounts[i])) + + # Add hashed chunk to running total + a0 = sum_32(a0, a) + b0 = sum_32(b0, b) + c0 = sum_32(c0, c) + d0 = sum_32(d0, d) digest = reformat_hex(a0) + reformat_hex(b0) + reformat_hex(c0) + reformat_hex(d0) return digest -def test(): - assert md5me("") == "d41d8cd98f00b204e9800998ecf8427e" - assert ( - md5me("The quick brown fox jumps over the lazy dog") - == "9e107d9d372bb6826bd81d3542a419d6" - ) - print("Success.") - - if __name__ == "__main__": - test() import doctest doctest.testmod() From 5ca71895630719cc41f8171aba8be461fb8cc9d2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 2 Apr 2023 06:48:19 +0200 Subject: [PATCH 2148/2908] Rename quantum_random.py.DISABLED.txt to quantum_random.py (#8601) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + quantum/{quantum_random.py.DISABLED.txt => quantum_random.py} | 0 2 files changed, 1 insertion(+) rename quantum/{quantum_random.py.DISABLED.txt => quantum_random.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index b1adc23f6e61..8dd3fb5d9af1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1033,6 +1033,7 @@ * [Q Fourier Transform](quantum/q_fourier_transform.py) * [Q Full Adder](quantum/q_full_adder.py) * [Quantum Entanglement](quantum/quantum_entanglement.py) + * [Quantum Random](quantum/quantum_random.py) * [Quantum Teleportation](quantum/quantum_teleportation.py) * [Ripple Adder Classic](quantum/ripple_adder_classic.py) * [Single Qubit Measure](quantum/single_qubit_measure.py) diff --git a/quantum/quantum_random.py.DISABLED.txt b/quantum/quantum_random.py similarity index 100% rename from quantum/quantum_random.py.DISABLED.txt rename to quantum/quantum_random.py From ebc2d5d79f837931e80f7d5e7e1dece9ef48f760 Mon Sep 17 00:00:00 2001 From: Ishab Date: Sun, 2 Apr 2023 13:04:11 +0100 Subject: [PATCH 2149/2908] Add Project Euler problem 79 solution 1 (#8607) Co-authored-by: Dhruv Manilawala --- project_euler/problem_079/__init__.py | 0 project_euler/problem_079/keylog.txt | 50 ++++++++++++++++ project_euler/problem_079/keylog_test.txt | 16 ++++++ project_euler/problem_079/sol1.py | 69 +++++++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 project_euler/problem_079/__init__.py create mode 100644 project_euler/problem_079/keylog.txt create mode 100644 project_euler/problem_079/keylog_test.txt create mode 100644 project_euler/problem_079/sol1.py diff --git a/project_euler/problem_079/__init__.py b/project_euler/problem_079/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_079/keylog.txt b/project_euler/problem_079/keylog.txt new file mode 100644 index 000000000000..41f15673248d --- /dev/null +++ b/project_euler/problem_079/keylog.txt @@ -0,0 +1,50 @@ +319 +680 +180 +690 +129 +620 +762 +689 +762 +318 +368 +710 +720 +710 +629 +168 +160 +689 +716 +731 +736 +729 +316 +729 +729 +710 +769 +290 +719 +680 +318 +389 +162 +289 +162 +718 +729 +319 +790 +680 +890 +362 +319 +760 +316 +729 +380 +319 +728 +716 diff --git a/project_euler/problem_079/keylog_test.txt b/project_euler/problem_079/keylog_test.txt new file mode 100644 index 000000000000..2c7024bde948 --- /dev/null +++ b/project_euler/problem_079/keylog_test.txt @@ -0,0 +1,16 @@ +319 +680 +180 +690 +129 +620 +698 +318 +328 +310 +320 +610 +629 +198 +190 +631 diff --git a/project_euler/problem_079/sol1.py b/project_euler/problem_079/sol1.py new file mode 100644 index 000000000000..d34adcd243b0 --- /dev/null +++ b/project_euler/problem_079/sol1.py @@ -0,0 +1,69 @@ +""" +Project Euler Problem 79: https://projecteuler.net/problem=79 + +Passcode derivation + +A common security method used for online banking is to ask the user for three +random characters from a passcode. For example, if the passcode was 531278, +they may ask for the 2nd, 3rd, and 5th characters; the expected reply would +be: 317. + +The text file, keylog.txt, contains fifty successful login attempts. + +Given that the three characters are always asked for in order, analyse the file +so as to determine the shortest possible secret passcode of unknown length. +""" +import itertools +from pathlib import Path + + +def find_secret_passcode(logins: list[str]) -> int: + """ + Returns the shortest possible secret passcode of unknown length. + + >>> find_secret_passcode(["135", "259", "235", "189", "690", "168", "120", + ... "136", "289", "589", "160", "165", "580", "369", "250", "280"]) + 12365890 + + >>> find_secret_passcode(["426", "281", "061", "819" "268", "406", "420", + ... "428", "209", "689", "019", "421", "469", "261", "681", "201"]) + 4206819 + """ + + # Split each login by character e.g. '319' -> ('3', '1', '9') + split_logins = [tuple(login) for login in logins] + + unique_chars = {char for login in split_logins for char in login} + + for permutation in itertools.permutations(unique_chars): + satisfied = True + for login in logins: + if not ( + permutation.index(login[0]) + < permutation.index(login[1]) + < permutation.index(login[2]) + ): + satisfied = False + break + + if satisfied: + return int("".join(permutation)) + + raise Exception("Unable to find the secret passcode") + + +def solution(input_file: str = "keylog.txt") -> int: + """ + Returns the shortest possible secret passcode of unknown length + for successful login attempts given by `input_file` text file. + + >>> solution("keylog_test.txt") + 6312980 + """ + logins = Path(__file__).parent.joinpath(input_file).read_text().splitlines() + + return find_secret_passcode(logins) + + +if __name__ == "__main__": + print(f"{solution() = }") From 740ecfb121009612310ab9e1bc9d6ffe22b62ae4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 07:00:31 +0530 Subject: [PATCH 2150/2908] [pre-commit.ci] pre-commit autoupdate (#8611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.259 → v0.0.260](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.259...v0.0.260) - [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0) - [github.com/abravalheri/validate-pyproject: v0.12.1 → v0.12.2](https://github.com/abravalheri/validate-pyproject/compare/v0.12.1...v0.12.2) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 72a878387e15..d54ce5adddce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.259 + rev: v0.0.260 hooks: - id: ruff - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black @@ -46,7 +46,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.12.1 + rev: v0.12.2 hooks: - id: validate-pyproject diff --git a/DIRECTORY.md b/DIRECTORY.md index 8dd3fb5d9af1..3764c471ce70 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -922,6 +922,8 @@ * [Sol1](project_euler/problem_077/sol1.py) * Problem 078 * [Sol1](project_euler/problem_078/sol1.py) + * Problem 079 + * [Sol1](project_euler/problem_079/sol1.py) * Problem 080 * [Sol1](project_euler/problem_080/sol1.py) * Problem 081 From b2b8585e63664a0c7aa18b95528e345c2738c4ae Mon Sep 17 00:00:00 2001 From: Ishan Dutta Date: Fri, 7 Apr 2023 21:21:25 +0530 Subject: [PATCH 2151/2908] Add LeNet Implementation in PyTorch (#7070) * add torch to requirements * add lenet architecture in pytorch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add type hints * remove file * add type hints * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update variable name * add fail test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add newline * reformatting --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- computer_vision/lenet_pytorch.py | 82 ++++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 83 insertions(+) create mode 100644 computer_vision/lenet_pytorch.py diff --git a/computer_vision/lenet_pytorch.py b/computer_vision/lenet_pytorch.py new file mode 100644 index 000000000000..177a5ebfcdb4 --- /dev/null +++ b/computer_vision/lenet_pytorch.py @@ -0,0 +1,82 @@ +""" +LeNet Network + +Paper: http://vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf +""" + +import numpy +import torch +import torch.nn as nn + + +class LeNet(nn.Module): + def __init__(self) -> None: + super().__init__() + + self.tanh = nn.Tanh() + self.avgpool = nn.AvgPool2d(kernel_size=2, stride=2) + + self.conv1 = nn.Conv2d( + in_channels=1, + out_channels=6, + kernel_size=(5, 5), + stride=(1, 1), + padding=(0, 0), + ) + self.conv2 = nn.Conv2d( + in_channels=6, + out_channels=16, + kernel_size=(5, 5), + stride=(1, 1), + padding=(0, 0), + ) + self.conv3 = nn.Conv2d( + in_channels=16, + out_channels=120, + kernel_size=(5, 5), + stride=(1, 1), + padding=(0, 0), + ) + + self.linear1 = nn.Linear(120, 84) + self.linear2 = nn.Linear(84, 10) + + def forward(self, image_array: numpy.ndarray) -> numpy.ndarray: + image_array = self.tanh(self.conv1(image_array)) + image_array = self.avgpool(image_array) + image_array = self.tanh(self.conv2(image_array)) + image_array = self.avgpool(image_array) + image_array = self.tanh(self.conv3(image_array)) + + image_array = image_array.reshape(image_array.shape[0], -1) + image_array = self.tanh(self.linear1(image_array)) + image_array = self.linear2(image_array) + return image_array + + +def test_model(image_tensor: torch.tensor) -> bool: + """ + Test the model on an input batch of 64 images + + Args: + image_tensor (torch.tensor): Batch of Images for the model + + >>> test_model(torch.randn(64, 1, 32, 32)) + True + + """ + try: + model = LeNet() + output = model(image_tensor) + except RuntimeError: + return False + + return output.shape == torch.zeros([64, 10]).shape + + +if __name__ == "__main__": + random_image_1 = torch.randn(64, 1, 32, 32) + random_image_2 = torch.randn(1, 32, 32) + + print(f"random_image_1 Model Passed: {test_model(random_image_1)}") + print(f"\nrandom_image_2 Model Passed: {test_model(random_image_2)}") diff --git a/requirements.txt b/requirements.txt index acfbc823e77f..e159fe010dc4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ statsmodels sympy tensorflow texttable +torch tweepy xgboost yulewalker From 179298e3a291470ef30e850f23d98c2fb9055202 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 8 Apr 2023 02:52:26 +0200 Subject: [PATCH 2152/2908] Revert "Add LeNet Implementation in PyTorch (#7070)" (#8621) This reverts commit b2b8585e63664a0c7aa18b95528e345c2738c4ae. --- computer_vision/lenet_pytorch.py | 82 -------------------------------- requirements.txt | 1 - 2 files changed, 83 deletions(-) delete mode 100644 computer_vision/lenet_pytorch.py diff --git a/computer_vision/lenet_pytorch.py b/computer_vision/lenet_pytorch.py deleted file mode 100644 index 177a5ebfcdb4..000000000000 --- a/computer_vision/lenet_pytorch.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -LeNet Network - -Paper: http://vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf -""" - -import numpy -import torch -import torch.nn as nn - - -class LeNet(nn.Module): - def __init__(self) -> None: - super().__init__() - - self.tanh = nn.Tanh() - self.avgpool = nn.AvgPool2d(kernel_size=2, stride=2) - - self.conv1 = nn.Conv2d( - in_channels=1, - out_channels=6, - kernel_size=(5, 5), - stride=(1, 1), - padding=(0, 0), - ) - self.conv2 = nn.Conv2d( - in_channels=6, - out_channels=16, - kernel_size=(5, 5), - stride=(1, 1), - padding=(0, 0), - ) - self.conv3 = nn.Conv2d( - in_channels=16, - out_channels=120, - kernel_size=(5, 5), - stride=(1, 1), - padding=(0, 0), - ) - - self.linear1 = nn.Linear(120, 84) - self.linear2 = nn.Linear(84, 10) - - def forward(self, image_array: numpy.ndarray) -> numpy.ndarray: - image_array = self.tanh(self.conv1(image_array)) - image_array = self.avgpool(image_array) - image_array = self.tanh(self.conv2(image_array)) - image_array = self.avgpool(image_array) - image_array = self.tanh(self.conv3(image_array)) - - image_array = image_array.reshape(image_array.shape[0], -1) - image_array = self.tanh(self.linear1(image_array)) - image_array = self.linear2(image_array) - return image_array - - -def test_model(image_tensor: torch.tensor) -> bool: - """ - Test the model on an input batch of 64 images - - Args: - image_tensor (torch.tensor): Batch of Images for the model - - >>> test_model(torch.randn(64, 1, 32, 32)) - True - - """ - try: - model = LeNet() - output = model(image_tensor) - except RuntimeError: - return False - - return output.shape == torch.zeros([64, 10]).shape - - -if __name__ == "__main__": - random_image_1 = torch.randn(64, 1, 32, 32) - random_image_2 = torch.randn(1, 32, 32) - - print(f"random_image_1 Model Passed: {test_model(random_image_1)}") - print(f"\nrandom_image_2 Model Passed: {test_model(random_image_2)}") diff --git a/requirements.txt b/requirements.txt index e159fe010dc4..acfbc823e77f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,6 @@ statsmodels sympy tensorflow texttable -torch tweepy xgboost yulewalker From 5cb0a000c47398c6d8af1ac43e2f83ae018f7182 Mon Sep 17 00:00:00 2001 From: amirsoroush <114881632+amirsoroush@users.noreply.github.com> Date: Sat, 8 Apr 2023 14:41:08 +0300 Subject: [PATCH 2153/2908] Queue implementation using two Stacks (#8617) * Queue implementation using two Stacks * fix typo in queue/queue_on_two_stacks.py * add 'iterable' to queue_on_two_stacks initializer * make queue_on_two_stacks.py generic class * fix ruff-UP007 in queue_on_two_stacks.py * enhance readability in queue_on_two_stacks.py * Create queue_by_two_stacks.py --------- Co-authored-by: Christian Clauss --- data_structures/queue/queue_by_two_stacks.py | 115 ++++++++++++++++ data_structures/queue/queue_on_two_stacks.py | 137 +++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 data_structures/queue/queue_by_two_stacks.py create mode 100644 data_structures/queue/queue_on_two_stacks.py diff --git a/data_structures/queue/queue_by_two_stacks.py b/data_structures/queue/queue_by_two_stacks.py new file mode 100644 index 000000000000..cd62f155a63b --- /dev/null +++ b/data_structures/queue/queue_by_two_stacks.py @@ -0,0 +1,115 @@ +"""Queue implementation using two stacks""" + +from collections.abc import Iterable +from typing import Generic, TypeVar + +_T = TypeVar("_T") + + +class QueueByTwoStacks(Generic[_T]): + def __init__(self, iterable: Iterable[_T] | None = None) -> None: + """ + >>> QueueByTwoStacks() + Queue(()) + >>> QueueByTwoStacks([10, 20, 30]) + Queue((10, 20, 30)) + >>> QueueByTwoStacks((i**2 for i in range(1, 4))) + Queue((1, 4, 9)) + """ + self._stack1: list[_T] = list(iterable or []) + self._stack2: list[_T] = [] + + def __len__(self) -> int: + """ + >>> len(QueueByTwoStacks()) + 0 + >>> from string import ascii_lowercase + >>> len(QueueByTwoStacks(ascii_lowercase)) + 26 + >>> queue = QueueByTwoStacks() + >>> for i in range(1, 11): + ... queue.put(i) + ... + >>> len(queue) + 10 + >>> for i in range(2): + ... queue.get() + 1 + 2 + >>> len(queue) + 8 + """ + + return len(self._stack1) + len(self._stack2) + + def __repr__(self) -> str: + """ + >>> queue = QueueByTwoStacks() + >>> queue + Queue(()) + >>> str(queue) + 'Queue(())' + >>> queue.put(10) + >>> queue + Queue((10,)) + >>> queue.put(20) + >>> queue.put(30) + >>> queue + Queue((10, 20, 30)) + """ + return f"Queue({tuple(self._stack2[::-1] + self._stack1)})" + + def put(self, item: _T) -> None: + """ + Put `item` into the Queue + + >>> queue = QueueByTwoStacks() + >>> queue.put(10) + >>> queue.put(20) + >>> len(queue) + 2 + >>> queue + Queue((10, 20)) + """ + + self._stack1.append(item) + + def get(self) -> _T: + """ + Get `item` from the Queue + + >>> queue = QueueByTwoStacks((10, 20, 30)) + >>> queue.get() + 10 + >>> queue.put(40) + >>> queue.get() + 20 + >>> queue.get() + 30 + >>> len(queue) + 1 + >>> queue.get() + 40 + >>> queue.get() + Traceback (most recent call last): + ... + IndexError: Queue is empty + """ + + # To reduce number of attribute look-ups in `while` loop. + stack1_pop = self._stack1.pop + stack2_append = self._stack2.append + + if not self._stack2: + while self._stack1: + stack2_append(stack1_pop()) + + if not self._stack2: + raise IndexError("Queue is empty") + return self._stack2.pop() + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/data_structures/queue/queue_on_two_stacks.py b/data_structures/queue/queue_on_two_stacks.py new file mode 100644 index 000000000000..61db2b512136 --- /dev/null +++ b/data_structures/queue/queue_on_two_stacks.py @@ -0,0 +1,137 @@ +"""Queue implementation using two stacks""" + +from collections.abc import Iterable +from typing import Generic, TypeVar + +_T = TypeVar("_T") + + +class QueueByTwoStacks(Generic[_T]): + def __init__(self, iterable: Iterable[_T] | None = None) -> None: + """ + >>> queue1 = QueueByTwoStacks() + >>> str(queue1) + 'Queue([])' + >>> queue2 = QueueByTwoStacks([10, 20, 30]) + >>> str(queue2) + 'Queue([10, 20, 30])' + >>> queue3 = QueueByTwoStacks((i**2 for i in range(1, 4))) + >>> str(queue3) + 'Queue([1, 4, 9])' + """ + + self._stack1: list[_T] = [] if iterable is None else list(iterable) + self._stack2: list[_T] = [] + + def __len__(self) -> int: + """ + >>> queue = QueueByTwoStacks() + >>> for i in range(1, 11): + ... queue.put(i) + ... + >>> len(queue) == 10 + True + >>> for i in range(2): + ... queue.get() + 1 + 2 + >>> len(queue) == 8 + True + """ + + return len(self._stack1) + len(self._stack2) + + def __repr__(self) -> str: + """ + >>> queue = QueueByTwoStacks() + >>> queue + Queue([]) + >>> str(queue) + 'Queue([])' + >>> queue.put(10) + >>> queue + Queue([10]) + >>> queue.put(20) + >>> queue.put(30) + >>> queue + Queue([10, 20, 30]) + """ + + items = self._stack2[::-1] + self._stack1 + return f"Queue({items})" + + def put(self, item: _T) -> None: + """ + Put `item` into the Queue + + >>> queue = QueueByTwoStacks() + >>> queue.put(10) + >>> queue.put(20) + >>> len(queue) == 2 + True + >>> str(queue) + 'Queue([10, 20])' + """ + + self._stack1.append(item) + + def get(self) -> _T: + """ + Get `item` from the Queue + + >>> queue = QueueByTwoStacks() + >>> for i in (10, 20, 30): + ... queue.put(i) + >>> queue.get() + 10 + >>> queue.put(40) + >>> queue.get() + 20 + >>> queue.get() + 30 + >>> len(queue) == 1 + True + >>> queue.get() + 40 + >>> queue.get() + Traceback (most recent call last): + ... + IndexError: Queue is empty + """ + + # To reduce number of attribute look-ups in `while` loop. + stack1_pop = self._stack1.pop + stack2_append = self._stack2.append + + if not self._stack2: + while self._stack1: + stack2_append(stack1_pop()) + + if not self._stack2: + raise IndexError("Queue is empty") + return self._stack2.pop() + + def size(self) -> int: + """ + Returns the length of the Queue + + >>> queue = QueueByTwoStacks() + >>> queue.size() + 0 + >>> queue.put(10) + >>> queue.put(20) + >>> queue.size() + 2 + >>> queue.get() + 10 + >>> queue.size() == 1 + True + """ + + return len(self) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 2f9b03393c75f3ab14b491becae4ac5caf26de17 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 8 Apr 2023 14:16:19 +0200 Subject: [PATCH 2154/2908] Delete queue_on_two_stacks.py which duplicates queue_by_two_stacks.py (#8624) * Delete queue_on_two_stacks.py which duplicates queue_by_two_stacks.py * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + data_structures/queue/queue_on_two_stacks.py | 137 ------------------- 2 files changed, 1 insertion(+), 137 deletions(-) delete mode 100644 data_structures/queue/queue_on_two_stacks.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 3764c471ce70..e3e0748ecf75 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -232,6 +232,7 @@ * [Double Ended Queue](data_structures/queue/double_ended_queue.py) * [Linked Queue](data_structures/queue/linked_queue.py) * [Priority Queue Using List](data_structures/queue/priority_queue_using_list.py) + * [Queue By Two Stacks](data_structures/queue/queue_by_two_stacks.py) * [Queue On List](data_structures/queue/queue_on_list.py) * [Queue On Pseudo Stack](data_structures/queue/queue_on_pseudo_stack.py) * Stacks diff --git a/data_structures/queue/queue_on_two_stacks.py b/data_structures/queue/queue_on_two_stacks.py deleted file mode 100644 index 61db2b512136..000000000000 --- a/data_structures/queue/queue_on_two_stacks.py +++ /dev/null @@ -1,137 +0,0 @@ -"""Queue implementation using two stacks""" - -from collections.abc import Iterable -from typing import Generic, TypeVar - -_T = TypeVar("_T") - - -class QueueByTwoStacks(Generic[_T]): - def __init__(self, iterable: Iterable[_T] | None = None) -> None: - """ - >>> queue1 = QueueByTwoStacks() - >>> str(queue1) - 'Queue([])' - >>> queue2 = QueueByTwoStacks([10, 20, 30]) - >>> str(queue2) - 'Queue([10, 20, 30])' - >>> queue3 = QueueByTwoStacks((i**2 for i in range(1, 4))) - >>> str(queue3) - 'Queue([1, 4, 9])' - """ - - self._stack1: list[_T] = [] if iterable is None else list(iterable) - self._stack2: list[_T] = [] - - def __len__(self) -> int: - """ - >>> queue = QueueByTwoStacks() - >>> for i in range(1, 11): - ... queue.put(i) - ... - >>> len(queue) == 10 - True - >>> for i in range(2): - ... queue.get() - 1 - 2 - >>> len(queue) == 8 - True - """ - - return len(self._stack1) + len(self._stack2) - - def __repr__(self) -> str: - """ - >>> queue = QueueByTwoStacks() - >>> queue - Queue([]) - >>> str(queue) - 'Queue([])' - >>> queue.put(10) - >>> queue - Queue([10]) - >>> queue.put(20) - >>> queue.put(30) - >>> queue - Queue([10, 20, 30]) - """ - - items = self._stack2[::-1] + self._stack1 - return f"Queue({items})" - - def put(self, item: _T) -> None: - """ - Put `item` into the Queue - - >>> queue = QueueByTwoStacks() - >>> queue.put(10) - >>> queue.put(20) - >>> len(queue) == 2 - True - >>> str(queue) - 'Queue([10, 20])' - """ - - self._stack1.append(item) - - def get(self) -> _T: - """ - Get `item` from the Queue - - >>> queue = QueueByTwoStacks() - >>> for i in (10, 20, 30): - ... queue.put(i) - >>> queue.get() - 10 - >>> queue.put(40) - >>> queue.get() - 20 - >>> queue.get() - 30 - >>> len(queue) == 1 - True - >>> queue.get() - 40 - >>> queue.get() - Traceback (most recent call last): - ... - IndexError: Queue is empty - """ - - # To reduce number of attribute look-ups in `while` loop. - stack1_pop = self._stack1.pop - stack2_append = self._stack2.append - - if not self._stack2: - while self._stack1: - stack2_append(stack1_pop()) - - if not self._stack2: - raise IndexError("Queue is empty") - return self._stack2.pop() - - def size(self) -> int: - """ - Returns the length of the Queue - - >>> queue = QueueByTwoStacks() - >>> queue.size() - 0 - >>> queue.put(10) - >>> queue.put(20) - >>> queue.size() - 2 - >>> queue.get() - 10 - >>> queue.size() == 1 - True - """ - - return len(self) - - -if __name__ == "__main__": - from doctest import testmod - - testmod() From 14bdd174bba7828ac2bf476f3697aa13fa179492 Mon Sep 17 00:00:00 2001 From: isidroas Date: Sat, 8 Apr 2023 19:39:24 +0200 Subject: [PATCH 2155/2908] Bloom Filter (#8615) * Bloom filter with tests * has functions constant * fix type * isort * passing ruff * type hints * type hints * from fail to erro * captital leter * type hints requested by boot * descriptive name for m * more descriptibe arguments II * moved movies_test to doctest * commented doctest * removed test_probability * estimated error * added types * again hash_ * Update data_structures/hashing/bloom_filter.py Co-authored-by: Christian Clauss * from b to bloom * Update data_structures/hashing/bloom_filter.py Co-authored-by: Christian Clauss * Update data_structures/hashing/bloom_filter.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * syntax error in dict comprehension * from goodfather to godfather * removed Interestellar * forgot the last Godfather * Revert "removed Interestellar" This reverts commit 35fa5f5c4bf101d073aad43c37b0a423d8975071. * pretty dict * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bloom_filter.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/hashing/bloom_filter.py | 105 ++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 data_structures/hashing/bloom_filter.py diff --git a/data_structures/hashing/bloom_filter.py b/data_structures/hashing/bloom_filter.py new file mode 100644 index 000000000000..7fd0985bdc33 --- /dev/null +++ b/data_structures/hashing/bloom_filter.py @@ -0,0 +1,105 @@ +""" +See https://en.wikipedia.org/wiki/Bloom_filter + +The use of this data structure is to test membership in a set. +Compared to Python's built-in set() it is more space-efficient. +In the following example, only 8 bits of memory will be used: +>>> bloom = Bloom(size=8) + +Initially, the filter contains all zeros: +>>> bloom.bitstring +'00000000' + +When an element is added, two bits are set to 1 +since there are 2 hash functions in this implementation: +>>> "Titanic" in bloom +False +>>> bloom.add("Titanic") +>>> bloom.bitstring +'01100000' +>>> "Titanic" in bloom +True + +However, sometimes only one bit is added +because both hash functions return the same value +>>> bloom.add("Avatar") +>>> "Avatar" in bloom +True +>>> bloom.format_hash("Avatar") +'00000100' +>>> bloom.bitstring +'01100100' + +Not added elements should return False ... +>>> not_present_films = ("The Godfather", "Interstellar", "Parasite", "Pulp Fiction") +>>> { +... film: bloom.format_hash(film) for film in not_present_films +... } # doctest: +NORMALIZE_WHITESPACE +{'The Godfather': '00000101', + 'Interstellar': '00000011', + 'Parasite': '00010010', + 'Pulp Fiction': '10000100'} +>>> any(film in bloom for film in not_present_films) +False + +but sometimes there are false positives: +>>> "Ratatouille" in bloom +True +>>> bloom.format_hash("Ratatouille") +'01100000' + +The probability increases with the number of elements added. +The probability decreases with the number of bits in the bitarray. +>>> bloom.estimated_error_rate +0.140625 +>>> bloom.add("The Godfather") +>>> bloom.estimated_error_rate +0.25 +>>> bloom.bitstring +'01100101' +""" +from hashlib import md5, sha256 + +HASH_FUNCTIONS = (sha256, md5) + + +class Bloom: + def __init__(self, size: int = 8) -> None: + self.bitarray = 0b0 + self.size = size + + def add(self, value: str) -> None: + h = self.hash_(value) + self.bitarray |= h + + def exists(self, value: str) -> bool: + h = self.hash_(value) + return (h & self.bitarray) == h + + def __contains__(self, other: str) -> bool: + return self.exists(other) + + def format_bin(self, bitarray: int) -> str: + res = bin(bitarray)[2:] + return res.zfill(self.size) + + @property + def bitstring(self) -> str: + return self.format_bin(self.bitarray) + + def hash_(self, value: str) -> int: + res = 0b0 + for func in HASH_FUNCTIONS: + position = ( + int.from_bytes(func(value.encode()).digest(), "little") % self.size + ) + res |= 2**position + return res + + def format_hash(self, value: str) -> str: + return self.format_bin(self.hash_(value)) + + @property + def estimated_error_rate(self) -> float: + n_ones = bin(self.bitarray).count("1") + return (n_ones / self.size) ** len(HASH_FUNCTIONS) From d182f95646aa7c515afe0912a34e8c2a11a34ca3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Apr 2023 23:43:17 +0200 Subject: [PATCH 2156/2908] [pre-commit.ci] pre-commit autoupdate (#8634) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.260 → v0.0.261](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.260...v0.0.261) - [github.com/pre-commit/mirrors-mypy: v1.1.1 → v1.2.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.1.1...v1.2.0) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d54ce5adddce..55345a574ce9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.260 + rev: v0.0.261 hooks: - id: ruff @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.1.1 + rev: v1.2.0 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index e3e0748ecf75..36f5a752c48b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -195,6 +195,7 @@ * [Alternate Disjoint Set](data_structures/disjoint_set/alternate_disjoint_set.py) * [Disjoint Set](data_structures/disjoint_set/disjoint_set.py) * Hashing + * [Bloom Filter](data_structures/hashing/bloom_filter.py) * [Double Hash](data_structures/hashing/double_hash.py) * [Hash Map](data_structures/hashing/hash_map.py) * [Hash Table](data_structures/hashing/hash_table.py) From 54dedf844a30d39bd42c66ebf9cd67ec186f47bb Mon Sep 17 00:00:00 2001 From: Diego Gasco <62801631+Diegomangasco@users.noreply.github.com> Date: Mon, 17 Apr 2023 00:34:22 +0200 Subject: [PATCH 2157/2908] Dimensionality reduction (#8590) --- machine_learning/dimensionality_reduction.py | 198 +++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 machine_learning/dimensionality_reduction.py diff --git a/machine_learning/dimensionality_reduction.py b/machine_learning/dimensionality_reduction.py new file mode 100644 index 000000000000..d2046f81af04 --- /dev/null +++ b/machine_learning/dimensionality_reduction.py @@ -0,0 +1,198 @@ +# Copyright (c) 2023 Diego Gasco (diego.gasco99@gmail.com), Diegomangasco on GitHub + +""" +Requirements: + - numpy version 1.21 + - scipy version 1.3.3 +Notes: + - Each column of the features matrix corresponds to a class item +""" + +import logging + +import numpy as np +import pytest +from scipy.linalg import eigh + +logging.basicConfig(level=logging.INFO, format="%(message)s") + + +def column_reshape(input_array: np.ndarray) -> np.ndarray: + """Function to reshape a row Numpy array into a column Numpy array + >>> input_array = np.array([1, 2, 3]) + >>> column_reshape(input_array) + array([[1], + [2], + [3]]) + """ + + return input_array.reshape((input_array.size, 1)) + + +def covariance_within_classes( + features: np.ndarray, labels: np.ndarray, classes: int +) -> np.ndarray: + """Function to compute the covariance matrix inside each class. + >>> features = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> labels = np.array([0, 1, 0]) + >>> covariance_within_classes(features, labels, 2) + array([[0.66666667, 0.66666667, 0.66666667], + [0.66666667, 0.66666667, 0.66666667], + [0.66666667, 0.66666667, 0.66666667]]) + """ + + covariance_sum = np.nan + for i in range(classes): + data = features[:, labels == i] + data_mean = data.mean(1) + # Centralize the data of class i + centered_data = data - column_reshape(data_mean) + if i > 0: + # If covariance_sum is not None + covariance_sum += np.dot(centered_data, centered_data.T) + else: + # If covariance_sum is np.nan (i.e. first loop) + covariance_sum = np.dot(centered_data, centered_data.T) + + return covariance_sum / features.shape[1] + + +def covariance_between_classes( + features: np.ndarray, labels: np.ndarray, classes: int +) -> np.ndarray: + """Function to compute the covariance matrix between multiple classes + >>> features = np.array([[9, 2, 3], [4, 3, 6], [1, 8, 9]]) + >>> labels = np.array([0, 1, 0]) + >>> covariance_between_classes(features, labels, 2) + array([[ 3.55555556, 1.77777778, -2.66666667], + [ 1.77777778, 0.88888889, -1.33333333], + [-2.66666667, -1.33333333, 2. ]]) + """ + + general_data_mean = features.mean(1) + covariance_sum = np.nan + for i in range(classes): + data = features[:, labels == i] + device_data = data.shape[1] + data_mean = data.mean(1) + if i > 0: + # If covariance_sum is not None + covariance_sum += device_data * np.dot( + column_reshape(data_mean) - column_reshape(general_data_mean), + (column_reshape(data_mean) - column_reshape(general_data_mean)).T, + ) + else: + # If covariance_sum is np.nan (i.e. first loop) + covariance_sum = device_data * np.dot( + column_reshape(data_mean) - column_reshape(general_data_mean), + (column_reshape(data_mean) - column_reshape(general_data_mean)).T, + ) + + return covariance_sum / features.shape[1] + + +def principal_component_analysis(features: np.ndarray, dimensions: int) -> np.ndarray: + """ + Principal Component Analysis. + + For more details, see: https://en.wikipedia.org/wiki/Principal_component_analysis. + Parameters: + * features: the features extracted from the dataset + * dimensions: to filter the projected data for the desired dimension + + >>> test_principal_component_analysis() + """ + + # Check if the features have been loaded + if features.any(): + data_mean = features.mean(1) + # Center the dataset + centered_data = features - np.reshape(data_mean, (data_mean.size, 1)) + covariance_matrix = np.dot(centered_data, centered_data.T) / features.shape[1] + _, eigenvectors = np.linalg.eigh(covariance_matrix) + # Take all the columns in the reverse order (-1), and then takes only the first + filtered_eigenvectors = eigenvectors[:, ::-1][:, 0:dimensions] + # Project the database on the new space + projected_data = np.dot(filtered_eigenvectors.T, features) + logging.info("Principal Component Analysis computed") + + return projected_data + else: + logging.basicConfig(level=logging.ERROR, format="%(message)s", force=True) + logging.error("Dataset empty") + raise AssertionError + + +def linear_discriminant_analysis( + features: np.ndarray, labels: np.ndarray, classes: int, dimensions: int +) -> np.ndarray: + """ + Linear Discriminant Analysis. + + For more details, see: https://en.wikipedia.org/wiki/Linear_discriminant_analysis. + Parameters: + * features: the features extracted from the dataset + * labels: the class labels of the features + * classes: the number of classes present in the dataset + * dimensions: to filter the projected data for the desired dimension + + >>> test_linear_discriminant_analysis() + """ + + # Check if the dimension desired is less than the number of classes + assert classes > dimensions + + # Check if features have been already loaded + if features.any: + _, eigenvectors = eigh( + covariance_between_classes(features, labels, classes), + covariance_within_classes(features, labels, classes), + ) + filtered_eigenvectors = eigenvectors[:, ::-1][:, :dimensions] + svd_matrix, _, _ = np.linalg.svd(filtered_eigenvectors) + filtered_svd_matrix = svd_matrix[:, 0:dimensions] + projected_data = np.dot(filtered_svd_matrix.T, features) + logging.info("Linear Discriminant Analysis computed") + + return projected_data + else: + logging.basicConfig(level=logging.ERROR, format="%(message)s", force=True) + logging.error("Dataset empty") + raise AssertionError + + +def test_linear_discriminant_analysis() -> None: + # Create dummy dataset with 2 classes and 3 features + features = np.array([[1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7]]) + labels = np.array([0, 0, 0, 1, 1]) + classes = 2 + dimensions = 2 + + # Assert that the function raises an AssertionError if dimensions > classes + with pytest.raises(AssertionError) as error_info: + projected_data = linear_discriminant_analysis( + features, labels, classes, dimensions + ) + if isinstance(projected_data, np.ndarray): + raise AssertionError( + "Did not raise AssertionError for dimensions > classes" + ) + assert error_info.type is AssertionError + + +def test_principal_component_analysis() -> None: + features = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + dimensions = 2 + expected_output = np.array([[6.92820323, 8.66025404, 10.39230485], [3.0, 3.0, 3.0]]) + + with pytest.raises(AssertionError) as error_info: + output = principal_component_analysis(features, dimensions) + if not np.allclose(expected_output, output): + raise AssertionError + assert error_info.type is AssertionError + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2b051a2de4adf711857f5453286dff47d1d87636 Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Tue, 18 Apr 2023 03:47:48 +0530 Subject: [PATCH 2158/2908] Create real_and_reactive_power.py (#8665) --- electronics/real_and_reactive_power.py | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 electronics/real_and_reactive_power.py diff --git a/electronics/real_and_reactive_power.py b/electronics/real_and_reactive_power.py new file mode 100644 index 000000000000..81dcba800e82 --- /dev/null +++ b/electronics/real_and_reactive_power.py @@ -0,0 +1,49 @@ +import math + + +def real_power(apparent_power: float, power_factor: float) -> float: + """ + Calculate real power from apparent power and power factor. + + Examples: + >>> real_power(100, 0.9) + 90.0 + >>> real_power(0, 0.8) + 0.0 + >>> real_power(100, -0.9) + -90.0 + """ + if ( + not isinstance(power_factor, (int, float)) + or power_factor < -1 + or power_factor > 1 + ): + raise ValueError("power_factor must be a valid float value between -1 and 1.") + return apparent_power * power_factor + + +def reactive_power(apparent_power: float, power_factor: float) -> float: + """ + Calculate reactive power from apparent power and power factor. + + Examples: + >>> reactive_power(100, 0.9) + 43.58898943540673 + >>> reactive_power(0, 0.8) + 0.0 + >>> reactive_power(100, -0.9) + 43.58898943540673 + """ + if ( + not isinstance(power_factor, (int, float)) + or power_factor < -1 + or power_factor > 1 + ): + raise ValueError("power_factor must be a valid float value between -1 and 1.") + return apparent_power * math.sqrt(1 - power_factor**2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b5047cfa114c6343b92370419772b9cf0f13e634 Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Tue, 18 Apr 2023 13:00:01 +0530 Subject: [PATCH 2159/2908] Create apparent_power.py (#8664) * Create apparent_power.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update apparent_power.py * Update apparent_power.py * Update apparent_power.py * Update electronics/apparent_power.py Co-authored-by: Christian Clauss * Update electronics/apparent_power.py Co-authored-by: Christian Clauss * Update apparent_power.py * Update electronics/apparent_power.py Co-authored-by: Christian Clauss * Update apparent_power.py * Update apparent_power.py * Update apparent_power.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update apparent_power.py * Update apparent_power.py * Update apparent_power.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- electronics/apparent_power.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 electronics/apparent_power.py diff --git a/electronics/apparent_power.py b/electronics/apparent_power.py new file mode 100644 index 000000000000..a6f1a50822f7 --- /dev/null +++ b/electronics/apparent_power.py @@ -0,0 +1,35 @@ +import cmath +import math + + +def apparent_power( + voltage: float, current: float, voltage_angle: float, current_angle: float +) -> complex: + """ + Calculate the apparent power in a single-phase AC circuit. + + >>> apparent_power(100, 5, 0, 0) + (500+0j) + >>> apparent_power(100, 5, 90, 0) + (3.061616997868383e-14+500j) + >>> apparent_power(100, 5, -45, -60) + (-129.40952255126027-482.9629131445341j) + >>> apparent_power(200, 10, -30, -90) + (-999.9999999999998-1732.0508075688776j) + """ + # Convert angles from degrees to radians + voltage_angle_rad = math.radians(voltage_angle) + current_angle_rad = math.radians(current_angle) + + # Convert voltage and current to rectangular form + voltage_rect = cmath.rect(voltage, voltage_angle_rad) + current_rect = cmath.rect(current, current_angle_rad) + + # Calculate apparent power + return voltage_rect * current_rect + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 93ce8cb75da2740089df8db23fa493ce104a011b Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Tue, 18 Apr 2023 13:14:06 +0530 Subject: [PATCH 2160/2908] added reference link. (#8667) * added reference link. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- electronics/apparent_power.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/electronics/apparent_power.py b/electronics/apparent_power.py index a6f1a50822f7..0ce1c2aa95b9 100644 --- a/electronics/apparent_power.py +++ b/electronics/apparent_power.py @@ -8,6 +8,8 @@ def apparent_power( """ Calculate the apparent power in a single-phase AC circuit. + Reference: https://en.wikipedia.org/wiki/AC_power#Apparent_power + >>> apparent_power(100, 5, 0, 0) (500+0j) >>> apparent_power(100, 5, 90, 0) From 458debc237d41752c6c4223264a4bb23efb2ecec Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Tue, 18 Apr 2023 13:32:20 +0530 Subject: [PATCH 2161/2908] added a problem with solution on sliding window. (#8566) * added a problem with solution on sliding window. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added hint for return type and parameter * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_size_subarray_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_size_subarray_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_size_subarray_sum.py * Update minimum_size_subarray_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_size_subarray_sum.py * removed un-necessary docs and added 2 test cases * Rename sliding_window/minimum_size_subarray_sum.py to dynamic_programming/minimum_size_subarray_sum.py * Update minimum_size_subarray_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_size_subarray_sum.py * Update minimum_size_subarray_sum.py * Update minimum_size_subarray_sum.py * Update minimum_size_subarray_sum.py * Update minimum_size_subarray_sum.py * Update minimum_size_subarray_sum.py * Update dynamic_programming/minimum_size_subarray_sum.py Co-authored-by: Christian Clauss * Update dynamic_programming/minimum_size_subarray_sum.py Co-authored-by: Christian Clauss * Update dynamic_programming/minimum_size_subarray_sum.py Co-authored-by: Christian Clauss * Update dynamic_programming/minimum_size_subarray_sum.py Co-authored-by: Christian Clauss * Update dynamic_programming/minimum_size_subarray_sum.py Co-authored-by: Christian Clauss * Update dynamic_programming/minimum_size_subarray_sum.py Co-authored-by: Christian Clauss * Update dynamic_programming/minimum_size_subarray_sum.py Co-authored-by: Christian Clauss * Update minimum_size_subarray_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_size_subarray_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_size_subarray_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update minimum_size_subarray_sum.py * Update minimum_size_subarray_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../minimum_size_subarray_sum.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 dynamic_programming/minimum_size_subarray_sum.py diff --git a/dynamic_programming/minimum_size_subarray_sum.py b/dynamic_programming/minimum_size_subarray_sum.py new file mode 100644 index 000000000000..3868d73535fb --- /dev/null +++ b/dynamic_programming/minimum_size_subarray_sum.py @@ -0,0 +1,62 @@ +import sys + + +def minimum_subarray_sum(target: int, numbers: list[int]) -> int: + """ + Return the length of the shortest contiguous subarray in a list of numbers whose sum + is at least target. Reference: https://stackoverflow.com/questions/8269916 + + >>> minimum_subarray_sum(7, [2, 3, 1, 2, 4, 3]) + 2 + >>> minimum_subarray_sum(7, [2, 3, -1, 2, 4, -3]) + 4 + >>> minimum_subarray_sum(11, [1, 1, 1, 1, 1, 1, 1, 1]) + 0 + >>> minimum_subarray_sum(10, [1, 2, 3, 4, 5, 6, 7]) + 2 + >>> minimum_subarray_sum(5, [1, 1, 1, 1, 1, 5]) + 1 + >>> minimum_subarray_sum(0, []) + 0 + >>> minimum_subarray_sum(0, [1, 2, 3]) + 1 + >>> minimum_subarray_sum(10, [10, 20, 30]) + 1 + >>> minimum_subarray_sum(7, [1, 1, 1, 1, 1, 1, 10]) + 1 + >>> minimum_subarray_sum(6, []) + 0 + >>> minimum_subarray_sum(2, [1, 2, 3]) + 1 + >>> minimum_subarray_sum(-6, []) + 0 + >>> minimum_subarray_sum(-6, [3, 4, 5]) + 1 + >>> minimum_subarray_sum(8, None) + 0 + >>> minimum_subarray_sum(2, "ABC") + Traceback (most recent call last): + ... + ValueError: numbers must be an iterable of integers + """ + if not numbers: + return 0 + if target == 0 and target in numbers: + return 0 + if not isinstance(numbers, (list, tuple)) or not all( + isinstance(number, int) for number in numbers + ): + raise ValueError("numbers must be an iterable of integers") + + left = right = curr_sum = 0 + min_len = sys.maxsize + + while right < len(numbers): + curr_sum += numbers[right] + while curr_sum >= target and left <= right: + min_len = min(min_len, right - left + 1) + curr_sum -= numbers[left] + left += 1 + right += 1 + + return 0 if min_len == sys.maxsize else min_len From 11582943a555ae3b6a22938df6d3645b0327562e Mon Sep 17 00:00:00 2001 From: JulianStiebler <68881884+JulianStiebler@users.noreply.github.com> Date: Tue, 18 Apr 2023 11:57:48 +0200 Subject: [PATCH 2162/2908] Create maths/pi_generator.py (#8666) * Create pi_generator.py * Update pi_generator.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pi_generator.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pi_generator.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pi_generator.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pi_generator.py * Update pi_generator.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated commentary on line 28, added math.pi comparison & math.isclose() test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed # noqa: E501 * printf() added as recommended by cclaus --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/pi_generator.py | 94 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 maths/pi_generator.py diff --git a/maths/pi_generator.py b/maths/pi_generator.py new file mode 100644 index 000000000000..dcd218aae309 --- /dev/null +++ b/maths/pi_generator.py @@ -0,0 +1,94 @@ +def calculate_pi(limit: int) -> str: + """ + https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80 + Leibniz Formula for Pi + + The Leibniz formula is the special case arctan 1 = 1/4 Pi . + Leibniz's formula converges extremely slowly: it exhibits sublinear convergence. + + Convergence (https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80#Convergence) + + We cannot try to prove against an interrupted, uncompleted generation. + https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80#Unusual_behaviour + The errors can in fact be predicted; + but those calculations also approach infinity for accuracy. + + Our output will always be a string since we can defintely store all digits in there. + For simplicity' sake, let's just compare against known values and since our outpit + is a string, we need to convert to float. + + >>> import math + >>> float(calculate_pi(15)) == math.pi + True + + Since we cannot predict errors or interrupt any infinite alternating + series generation since they approach infinity, + or interrupt any alternating series, we are going to need math.isclose() + + >>> math.isclose(float(calculate_pi(50)), math.pi) + True + + >>> math.isclose(float(calculate_pi(100)), math.pi) + True + + Since math.pi-constant contains only 16 digits, here some test with preknown values: + + >>> calculate_pi(50) + '3.14159265358979323846264338327950288419716939937510' + >>> calculate_pi(80) + '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899' + + To apply the Leibniz formula for calculating pi, + the variables q, r, t, k, n, and l are used for the iteration process. + """ + q = 1 + r = 0 + t = 1 + k = 1 + n = 3 + l = 3 + decimal = limit + counter = 0 + + result = "" + + """ + We will avoid using yield since we otherwise get a Generator-Object, + which we can't just compare against anything. We would have to make a list out of it + after the generation, so we will just stick to plain return logic: + """ + while counter != decimal + 1: + if 4 * q + r - t < n * t: + result += str(n) + if counter == 0: + result += "." + + if decimal == counter: + break + + counter += 1 + nr = 10 * (r - n * t) + n = ((10 * (3 * q + r)) // t) - 10 * n + q *= 10 + r = nr + else: + nr = (2 * q + r) * l + nn = (q * (7 * k) + 2 + (r * l)) // (t * l) + q *= k + t *= l + l += 2 + k += 1 + n = nn + r = nr + return result + + +def main() -> None: + print(f"{calculate_pi(50) = }") + import doctest + + doctest.testmod() + + +if __name__ == "__main__": + main() From bf30b18192dd7ff9a43523ee6efe5c015ae6b99c Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Mon, 24 Apr 2023 10:58:30 +0530 Subject: [PATCH 2163/2908] Update linear_discriminant_analysis.py and rsa_cipher.py (#8680) * Update rsa_cipher.py by replacing %s with {} * Update rsa_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_discriminant_analysis.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_discriminant_analysis.py * Update linear_discriminant_analysis.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_discriminant_analysis.py * Update linear_discriminant_analysis.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_discriminant_analysis.py * Update machine_learning/linear_discriminant_analysis.py Co-authored-by: Christian Clauss * Update linear_discriminant_analysis.py * updated --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- ciphers/rsa_cipher.py | 14 ++++++++------ machine_learning/linear_discriminant_analysis.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index de26992f5eeb..9c41cdc5d472 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -76,10 +76,11 @@ def encrypt_and_write_to_file( key_size, n, e = read_key_file(key_filename) if key_size < block_size * 8: sys.exit( - "ERROR: Block size is %s bits and key size is %s bits. The RSA cipher " + "ERROR: Block size is {} bits and key size is {} 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." - % (block_size * 8, key_size) + "Either decrease the block size or use different keys.".format( + block_size * 8, key_size + ) ) encrypted_blocks = [str(i) for i in encrypt_message(message, (n, e), block_size)] @@ -101,10 +102,11 @@ def read_from_file_and_decrypt(message_filename: str, key_filename: str) -> str: if key_size < block_size * 8: sys.exit( - "ERROR: Block size is %s bits and key size is %s bits. The RSA cipher " + "ERROR: Block size is {} bits and key size is {} 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?" - % (block_size * 8, key_size) + "Did you specify the correct key file and encrypted file?".format( + block_size * 8, key_size + ) ) encrypted_blocks = [] diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index f4fb5ba76b64..c0a477be10c7 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -399,7 +399,7 @@ def main(): 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") + system("clear" if name == "posix" else "cls") # noqa: S605 if __name__ == "__main__": From a650426350dc7833ff1110bc2e434763caed631e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 06:05:45 +0200 Subject: [PATCH 2164/2908] [pre-commit.ci] pre-commit autoupdate (#8691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.261 → v0.0.262](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.261...v0.0.262) - [github.com/tox-dev/pyproject-fmt: 0.9.2 → 0.10.0](https://github.com/tox-dev/pyproject-fmt/compare/0.9.2...0.10.0) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 55345a574ce9..288473ca365f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.261 + rev: v0.0.262 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.9.2" + rev: "0.10.0" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index 36f5a752c48b..8e67c85c6fa8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -327,6 +327,7 @@ * [Minimum Coin Change](dynamic_programming/minimum_coin_change.py) * [Minimum Cost Path](dynamic_programming/minimum_cost_path.py) * [Minimum Partition](dynamic_programming/minimum_partition.py) + * [Minimum Size Subarray Sum](dynamic_programming/minimum_size_subarray_sum.py) * [Minimum Squares To Represent A Number](dynamic_programming/minimum_squares_to_represent_a_number.py) * [Minimum Steps To One](dynamic_programming/minimum_steps_to_one.py) * [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py) @@ -339,6 +340,7 @@ * [Word Break](dynamic_programming/word_break.py) ## Electronics + * [Apparent Power](electronics/apparent_power.py) * [Builtin Voltage](electronics/builtin_voltage.py) * [Carrier Concentration](electronics/carrier_concentration.py) * [Circular Convolution](electronics/circular_convolution.py) @@ -348,6 +350,7 @@ * [Electrical Impedance](electronics/electrical_impedance.py) * [Ind Reactance](electronics/ind_reactance.py) * [Ohms Law](electronics/ohms_law.py) + * [Real And Reactive Power](electronics/real_and_reactive_power.py) * [Resistor Equivalence](electronics/resistor_equivalence.py) * [Resonant Frequency](electronics/resonant_frequency.py) @@ -483,6 +486,7 @@ * [Astar](machine_learning/astar.py) * [Data Transformations](machine_learning/data_transformations.py) * [Decision Tree](machine_learning/decision_tree.py) + * [Dimensionality Reduction](machine_learning/dimensionality_reduction.py) * Forecasting * [Run](machine_learning/forecasting/run.py) * [Gradient Descent](machine_learning/gradient_descent.py) @@ -604,6 +608,7 @@ * [Perfect Number](maths/perfect_number.py) * [Perfect Square](maths/perfect_square.py) * [Persistence](maths/persistence.py) + * [Pi Generator](maths/pi_generator.py) * [Pi Monte Carlo Estimation](maths/pi_monte_carlo_estimation.py) * [Points Are Collinear 3D](maths/points_are_collinear_3d.py) * [Pollard Rho](maths/pollard_rho.py) From c1b3ea5355266bb47daba378ca10200c4d359453 Mon Sep 17 00:00:00 2001 From: Dipankar Mitra <50228537+Mitra-babu@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:36:14 +0530 Subject: [PATCH 2165/2908] The tanh activation function is added (#8689) * tanh function been added * tanh function been added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tanh function is added * tanh function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tanh function added * tanh function added * tanh function is added * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/tanh.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 maths/tanh.py diff --git a/maths/tanh.py b/maths/tanh.py new file mode 100644 index 000000000000..ddab3e1ab717 --- /dev/null +++ b/maths/tanh.py @@ -0,0 +1,42 @@ +""" +This script demonstrates the implementation of the tangent hyperbolic +or tanh function. + +The function takes a vector of K real numbers as input and +then (e^x - e^(-x))/(e^x + e^(-x)). After through tanh, the +element of the vector mostly -1 between 1. + +Script inspired from its corresponding Wikipedia article +https://en.wikipedia.org/wiki/Activation_function +""" +import numpy as np + + +def tangent_hyperbolic(vector: np.array) -> np.array: + """ + Implements the tanh function + + Parameters: + vector: np.array + + Returns: + tanh (np.array): The input numpy array after applying tanh. + + mathematically (e^x - e^(-x))/(e^x + e^(-x)) can be written as (2/(1+e^(-2x))-1 + + Examples: + >>> tangent_hyperbolic(np.array([1,5,6,-0.67])) + array([ 0.76159416, 0.9999092 , 0.99998771, -0.58497988]) + + >>> tangent_hyperbolic(np.array([8,10,2,-0.98,13])) + array([ 0.99999977, 1. , 0.96402758, -0.7530659 , 1. ]) + + """ + + return (2 / (1 + np.exp(-2 * vector))) - 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 4c1f876567673db0934ba65d662ea221465ec921 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 27 Apr 2023 19:32:07 +0200 Subject: [PATCH 2166/2908] Solving the `Top k most frequent words` problem using a max-heap (#8685) * Solving the `Top k most frequent words` problem using a max-heap * Mentioning Python standard library solution in `Top k most frequent words` docstring * ruff --fix . * updating DIRECTORY.md --------- Co-authored-by: Amos Paribocci Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + data_structures/heap/heap.py | 31 ++++-- .../linear_discriminant_analysis.py | 2 +- strings/top_k_frequent_words.py | 101 ++++++++++++++++++ 4 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 strings/top_k_frequent_words.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 8e67c85c6fa8..681d252b232d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1167,6 +1167,7 @@ * [Snake Case To Camel Pascal Case](strings/snake_case_to_camel_pascal_case.py) * [Split](strings/split.py) * [Text Justification](strings/text_justification.py) + * [Top K Frequent Words](strings/top_k_frequent_words.py) * [Upper](strings/upper.py) * [Wave](strings/wave.py) * [Wildcard Pattern Matching](strings/wildcard_pattern_matching.py) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index b14c55d9db4c..c1004f349479 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -1,9 +1,28 @@ from __future__ import annotations +from abc import abstractmethod from collections.abc import Iterable +from typing import Generic, Protocol, TypeVar -class Heap: +class Comparable(Protocol): + @abstractmethod + def __lt__(self: T, other: T) -> bool: + pass + + @abstractmethod + def __gt__(self: T, other: T) -> bool: + pass + + @abstractmethod + def __eq__(self: T, other: object) -> bool: + pass + + +T = TypeVar("T", bound=Comparable) + + +class Heap(Generic[T]): """A Max Heap Implementation >>> unsorted = [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5] @@ -27,7 +46,7 @@ class Heap: """ def __init__(self) -> None: - self.h: list[float] = [] + self.h: list[T] = [] self.heap_size: int = 0 def __repr__(self) -> str: @@ -79,7 +98,7 @@ def max_heapify(self, index: int) -> None: # fix the subsequent violation recursively if any self.max_heapify(violation) - def build_max_heap(self, collection: Iterable[float]) -> None: + def build_max_heap(self, collection: Iterable[T]) -> None: """build max heap from an unsorted array""" self.h = list(collection) self.heap_size = len(self.h) @@ -88,7 +107,7 @@ def build_max_heap(self, collection: Iterable[float]) -> None: for i in range(self.heap_size // 2 - 1, -1, -1): self.max_heapify(i) - def extract_max(self) -> float: + def extract_max(self) -> T: """get and remove max from heap""" if self.heap_size >= 2: me = self.h[0] @@ -102,7 +121,7 @@ def extract_max(self) -> float: else: raise Exception("Empty heap") - def insert(self, value: float) -> None: + def insert(self, value: T) -> None: """insert a new value into the max heap""" self.h.append(value) idx = (self.heap_size - 1) // 2 @@ -144,7 +163,7 @@ def heap_sort(self) -> None: ]: print(f"unsorted array: {unsorted}") - heap = Heap() + heap: Heap[int] = Heap() heap.build_max_heap(unsorted) print(f"after build heap: {heap}") diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index c0a477be10c7..88c047157893 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -399,7 +399,7 @@ def main(): if input("Press any key to restart or 'q' for quit: ").strip().lower() == "q": print("\n" + "GoodBye!".center(100, "-") + "\n") break - system("clear" if name == "posix" else "cls") # noqa: S605 + system("cls" if name == "nt" else "clear") # noqa: S605 if __name__ == "__main__": diff --git a/strings/top_k_frequent_words.py b/strings/top_k_frequent_words.py new file mode 100644 index 000000000000..f3d1e0cd5ca7 --- /dev/null +++ b/strings/top_k_frequent_words.py @@ -0,0 +1,101 @@ +""" +Finds the top K most frequent words from the provided word list. + +This implementation aims to show how to solve the problem using the Heap class +already present in this repository. +Computing order statistics is, in fact, a typical usage of heaps. + +This is mostly shown for educational purposes, since the problem can be solved +in a few lines using collections.Counter from the Python standard library: + +from collections import Counter +def top_k_frequent_words(words, k_value): + return [x[0] for x in Counter(words).most_common(k_value)] +""" + + +from collections import Counter +from functools import total_ordering + +from data_structures.heap.heap import Heap + + +@total_ordering +class WordCount: + def __init__(self, word: str, count: int) -> None: + self.word = word + self.count = count + + def __eq__(self, other: object) -> bool: + """ + >>> WordCount('a', 1).__eq__(WordCount('b', 1)) + True + >>> WordCount('a', 1).__eq__(WordCount('a', 1)) + True + >>> WordCount('a', 1).__eq__(WordCount('a', 2)) + False + >>> WordCount('a', 1).__eq__(WordCount('b', 2)) + False + >>> WordCount('a', 1).__eq__(1) + NotImplemented + """ + if not isinstance(other, WordCount): + return NotImplemented + return self.count == other.count + + def __lt__(self, other: object) -> bool: + """ + >>> WordCount('a', 1).__lt__(WordCount('b', 1)) + False + >>> WordCount('a', 1).__lt__(WordCount('a', 1)) + False + >>> WordCount('a', 1).__lt__(WordCount('a', 2)) + True + >>> WordCount('a', 1).__lt__(WordCount('b', 2)) + True + >>> WordCount('a', 2).__lt__(WordCount('a', 1)) + False + >>> WordCount('a', 2).__lt__(WordCount('b', 1)) + False + >>> WordCount('a', 1).__lt__(1) + NotImplemented + """ + if not isinstance(other, WordCount): + return NotImplemented + return self.count < other.count + + +def top_k_frequent_words(words: list[str], k_value: int) -> list[str]: + """ + Returns the `k_value` most frequently occurring words, + in non-increasing order of occurrence. + In this context, a word is defined as an element in the provided list. + + In case `k_value` is greater than the number of distinct words, a value of k equal + to the number of distinct words will be considered, instead. + + >>> top_k_frequent_words(['a', 'b', 'c', 'a', 'c', 'c'], 3) + ['c', 'a', 'b'] + >>> top_k_frequent_words(['a', 'b', 'c', 'a', 'c', 'c'], 2) + ['c', 'a'] + >>> top_k_frequent_words(['a', 'b', 'c', 'a', 'c', 'c'], 1) + ['c'] + >>> top_k_frequent_words(['a', 'b', 'c', 'a', 'c', 'c'], 0) + [] + >>> top_k_frequent_words([], 1) + [] + >>> top_k_frequent_words(['a', 'a'], 2) + ['a'] + """ + heap: Heap[WordCount] = Heap() + count_by_word = Counter(words) + heap.build_max_heap( + [WordCount(word, count) for word, count in count_by_word.items()] + ) + return [heap.extract_max().word for _ in range(min(k_value, len(count_by_word)))] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c4dcc44dd44f7e3e7c65debc8e173080fc693150 Mon Sep 17 00:00:00 2001 From: Sahil Goel <55365655+sahilg13@users.noreply.github.com> Date: Sun, 30 Apr 2023 13:33:22 -0400 Subject: [PATCH 2167/2908] Added an algorithm to calculate the present value of cash flows (#8700) * Added an algorithm to calculate the present value of cash flows * added doctest and reference * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Resolving deprecation issues with typing module * Fixing argument type checks and adding doctest case * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixing failing doctest case by requiring less precision due to floating point inprecision * Updating return type * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added test cases for more coverage * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Make improvements based on Rohan's suggestions * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update financial/present_value.py Committed first suggestion Co-authored-by: Christian Clauss * Update financial/present_value.py Committed second suggestion Co-authored-by: Christian Clauss * Update financial/present_value.py Committed third suggestion Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- financial/present_value.py | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 financial/present_value.py diff --git a/financial/present_value.py b/financial/present_value.py new file mode 100644 index 000000000000..dc8191a6ef53 --- /dev/null +++ b/financial/present_value.py @@ -0,0 +1,41 @@ +""" +Reference: https://www.investopedia.com/terms/p/presentvalue.asp + +An algorithm that calculates the present value of a stream of yearly cash flows given... +1. The discount rate (as a decimal, not a percent) +2. An array of cash flows, with the index of the cash flow being the associated year + +Note: This algorithm assumes that cash flows are paid at the end of the specified year + + +def present_value(discount_rate: float, cash_flows: list[float]) -> float: + """ + >>> present_value(0.13, [10, 20.70, -293, 297]) + 4.69 + >>> present_value(0.07, [-109129.39, 30923.23, 15098.93, 29734,39]) + -42739.63 + >>> present_value(0.07, [109129.39, 30923.23, 15098.93, 29734,39]) + 175519.15 + >>> present_value(-1, [109129.39, 30923.23, 15098.93, 29734,39]) + Traceback (most recent call last): + ... + ValueError: Discount rate cannot be negative + >>> present_value(0.03, []) + Traceback (most recent call last): + ... + ValueError: Cash flows list cannot be empty + """ + if discount_rate < 0: + raise ValueError("Discount rate cannot be negative") + if not cash_flows: + raise ValueError("Cash flows list cannot be empty") + present_value = sum( + cash_flow / ((1 + discount_rate) ** i) for i, cash_flow in enumerate(cash_flows) + ) + return round(present_value, ndigits=2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f6df26bf0f5c05d53b6fd24552de9e3eec2334aa Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 1 May 2023 02:59:42 +0200 Subject: [PATCH 2168/2908] Fix docstring in present_value.py (#8702) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 ++ financial/present_value.py | 1 + 2 files changed, 3 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 681d252b232d..167d062b4a9f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -363,6 +363,7 @@ ## Financial * [Equated Monthly Installments](financial/equated_monthly_installments.py) * [Interest](financial/interest.py) + * [Present Value](financial/present_value.py) * [Price Plus Tax](financial/price_plus_tax.py) ## Fractals @@ -655,6 +656,7 @@ * [Sum Of Harmonic Series](maths/sum_of_harmonic_series.py) * [Sumset](maths/sumset.py) * [Sylvester Sequence](maths/sylvester_sequence.py) + * [Tanh](maths/tanh.py) * [Test Prime Check](maths/test_prime_check.py) * [Trapezoidal Rule](maths/trapezoidal_rule.py) * [Triplet Sum](maths/triplet_sum.py) diff --git a/financial/present_value.py b/financial/present_value.py index dc8191a6ef53..f74612b923af 100644 --- a/financial/present_value.py +++ b/financial/present_value.py @@ -6,6 +6,7 @@ 2. An array of cash flows, with the index of the cash flow being the associated year Note: This algorithm assumes that cash flows are paid at the end of the specified year +""" def present_value(discount_rate: float, cash_flows: list[float]) -> float: From e966c5cc0f856afab11a8bb150ef3b48f0c63112 Mon Sep 17 00:00:00 2001 From: Himanshu Tomar Date: Mon, 1 May 2023 15:53:03 +0530 Subject: [PATCH 2169/2908] Added minimum waiting time problem solution using greedy algorithm (#8701) * Added minimum waiting time problem solution using greedy algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ruff --fix * Add type hints * Added two more doc test * Removed unnecessary comments * updated type hints * Updated the code as per the code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 1 + greedy_methods/minimum_waiting_time.py | 48 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 greedy_methods/minimum_waiting_time.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 167d062b4a9f..021669d13b4a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -450,6 +450,7 @@ * [Fractional Knapsack](greedy_methods/fractional_knapsack.py) * [Fractional Knapsack 2](greedy_methods/fractional_knapsack_2.py) * [Optimal Merge Pattern](greedy_methods/optimal_merge_pattern.py) + * [Minimum Waiting Time ](greedy_methods/minimum_waiting_time.py) ## Hashes * [Adler32](hashes/adler32.py) diff --git a/greedy_methods/minimum_waiting_time.py b/greedy_methods/minimum_waiting_time.py new file mode 100644 index 000000000000..aaae8cf8f720 --- /dev/null +++ b/greedy_methods/minimum_waiting_time.py @@ -0,0 +1,48 @@ +""" +Calculate the minimum waiting time using a greedy algorithm. +reference: https://www.youtube.com/watch?v=Sf3eiO12eJs + +For doctests run following command: +python -m doctest -v minimum_waiting_time.py + +The minimum_waiting_time function uses a greedy algorithm to calculate the minimum +time for queries to complete. It sorts the list in non-decreasing order, calculates +the waiting time for each query by multiplying its position in the list with the +sum of all remaining query times, and returns the total waiting time. A doctest +ensures that the function produces the correct output. +""" + + +def minimum_waiting_time(queries: list[int]) -> int: + """ + This function takes a list of query times and returns the minimum waiting time + for all queries to be completed. + + Args: + queries: A list of queries measured in picoseconds + + Returns: + total_waiting_time: Minimum waiting time measured in picoseconds + + Examples: + >>> minimum_waiting_time([3, 2, 1, 2, 6]) + 17 + >>> minimum_waiting_time([3, 2, 1]) + 4 + >>> minimum_waiting_time([1, 2, 3, 4]) + 10 + >>> minimum_waiting_time([5, 5, 5, 5]) + 30 + >>> minimum_waiting_time([]) + 0 + """ + n = len(queries) + if n in (0, 1): + return 0 + return sum(query * (n - i - 1) for i, query in enumerate(sorted(queries))) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 777f966893d7042d350b44b05ce7f8431f561509 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 23:48:56 +0200 Subject: [PATCH 2170/2908] [pre-commit.ci] pre-commit autoupdate (#8704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.262 → v0.0.263](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.262...v0.0.263) - [github.com/tox-dev/pyproject-fmt: 0.10.0 → 0.11.1](https://github.com/tox-dev/pyproject-fmt/compare/0.10.0...0.11.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 288473ca365f..accb57da35d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.262 + rev: v0.0.263 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.10.0" + rev: "0.11.1" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index 021669d13b4a..826bd6fd39d4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -449,8 +449,8 @@ ## Greedy Methods * [Fractional Knapsack](greedy_methods/fractional_knapsack.py) * [Fractional Knapsack 2](greedy_methods/fractional_knapsack_2.py) + * [Minimum Waiting Time](greedy_methods/minimum_waiting_time.py) * [Optimal Merge Pattern](greedy_methods/optimal_merge_pattern.py) - * [Minimum Waiting Time ](greedy_methods/minimum_waiting_time.py) ## Hashes * [Adler32](hashes/adler32.py) From 73105145090f0ce972f6fa29cc5d71f012dd8c92 Mon Sep 17 00:00:00 2001 From: Dipankar Mitra <50228537+Mitra-babu@users.noreply.github.com> Date: Tue, 2 May 2023 20:06:28 +0530 Subject: [PATCH 2171/2908] The ELU activation is added (#8699) * tanh function been added * tanh function been added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tanh function is added * tanh function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tanh function added * tanh function added * tanh function is added * Apply suggestions from code review * ELU activation function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * elu activation is added * ELU activation is added * Update maths/elu_activation.py Co-authored-by: Christian Clauss * Exponential_linear_unit activation is added * Exponential_linear_unit activation is added --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../exponential_linear_unit.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 neural_network/activation_functions/exponential_linear_unit.py diff --git a/neural_network/activation_functions/exponential_linear_unit.py b/neural_network/activation_functions/exponential_linear_unit.py new file mode 100644 index 000000000000..7a3cf1d84e71 --- /dev/null +++ b/neural_network/activation_functions/exponential_linear_unit.py @@ -0,0 +1,40 @@ +""" +Implements the Exponential Linear Unit or ELU function. + +The function takes a vector of K real numbers and a real number alpha as +input and then applies the ELU function to each element of the vector. + +Script inspired from its corresponding Wikipedia article +https://en.wikipedia.org/wiki/Rectifier_(neural_networks) +""" + +import numpy as np + + +def exponential_linear_unit(vector: np.ndarray, alpha: float) -> np.ndarray: + """ + Implements the ELU activation function. + Parameters: + vector: the array containing input of elu activation + alpha: hyper-parameter + return: + elu (np.array): The input numpy array after applying elu. + + Mathematically, f(x) = x, x>0 else (alpha * (e^x -1)), x<=0, alpha >=0 + + Examples: + >>> exponential_linear_unit(vector=np.array([2.3,0.6,-2,-3.8]), alpha=0.3) + array([ 2.3 , 0.6 , -0.25939942, -0.29328877]) + + >>> exponential_linear_unit(vector=np.array([-9.2,-0.3,0.45,-4.56]), alpha=0.067) + array([-0.06699323, -0.01736518, 0.45 , -0.06629904]) + + + """ + return np.where(vector > 0, vector, (alpha * (np.exp(vector) - 1))) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 91cc3a240f05922024d4c5523422138857c48ae0 Mon Sep 17 00:00:00 2001 From: Pronoy Mandal Date: Wed, 10 May 2023 15:04:36 +0530 Subject: [PATCH 2172/2908] Update game_of_life.py (#8703) Rectify spelling in docstring --- cellular_automata/game_of_life.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellular_automata/game_of_life.py b/cellular_automata/game_of_life.py index 8e54702519b9..3382af7b5db6 100644 --- a/cellular_automata/game_of_life.py +++ b/cellular_automata/game_of_life.py @@ -34,7 +34,7 @@ from matplotlib import pyplot as plt from matplotlib.colors import ListedColormap -usage_doc = "Usage of script: script_nama " +usage_doc = "Usage of script: script_name " choice = [0] * 100 + [1] * 10 random.shuffle(choice) From 209a59ee562dd4b0358d8d1a12b112ec3f3e68ed Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Wed, 10 May 2023 15:08:52 +0530 Subject: [PATCH 2173/2908] Update and_gate.py (#8690) * Update and_gate.py addressing issue #8656 by calling `test_and_gate()` , ensuring that all the assertions are verified before the actual output is printed. * Update and_gate.py addressing issue #8632 --- boolean_algebra/and_gate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/boolean_algebra/and_gate.py b/boolean_algebra/and_gate.py index cbbcfde79f33..834116772ee7 100644 --- a/boolean_algebra/and_gate.py +++ b/boolean_algebra/and_gate.py @@ -43,6 +43,8 @@ def test_and_gate() -> None: if __name__ == "__main__": + test_and_gate() + print(and_gate(1, 0)) print(and_gate(0, 0)) print(and_gate(0, 1)) print(and_gate(1, 1)) From 44aa17fb86b0c04508580425b588c0f8a0cf4ce9 Mon Sep 17 00:00:00 2001 From: shricubed Date: Wed, 10 May 2023 14:50:32 -0400 Subject: [PATCH 2174/2908] Working binary insertion sort in Python (#8024) --- sorts/binary_insertion_sort.py | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 sorts/binary_insertion_sort.py diff --git a/sorts/binary_insertion_sort.py b/sorts/binary_insertion_sort.py new file mode 100644 index 000000000000..8d41025583b1 --- /dev/null +++ b/sorts/binary_insertion_sort.py @@ -0,0 +1,61 @@ +""" +This is a pure Python implementation of the binary insertion sort algorithm + +For doctests run following command: +python -m doctest -v binary_insertion_sort.py +or +python3 -m doctest -v binary_insertion_sort.py + +For manual testing run: +python binary_insertion_sort.py +""" + + +def binary_insertion_sort(collection: list) -> list: + """Pure implementation of the binary insertion sort algorithm in Python + :param collection: some mutable ordered collection with heterogeneous + comparable items inside + :return: the same collection ordered by ascending + + Examples: + >>> binary_insertion_sort([0, 4, 1234, 4, 1]) + [0, 1, 4, 4, 1234] + >>> binary_insertion_sort([]) == sorted([]) + True + >>> binary_insertion_sort([-1, -2, -3]) == sorted([-1, -2, -3]) + True + >>> lst = ['d', 'a', 'b', 'e', 'c'] + >>> binary_insertion_sort(lst) == sorted(lst) + True + >>> import random + >>> collection = random.sample(range(-50, 50), 100) + >>> binary_insertion_sort(collection) == sorted(collection) + True + >>> import string + >>> collection = random.choices(string.ascii_letters + string.digits, k=100) + >>> binary_insertion_sort(collection) == sorted(collection) + True + """ + + n = len(collection) + for i in range(1, n): + val = collection[i] + low = 0 + high = i - 1 + + while low <= high: + mid = (low + high) // 2 + if val < collection[mid]: + high = mid - 1 + else: + low = mid + 1 + for j in range(i, low, -1): + collection[j] = collection[j - 1] + collection[low] = val + 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(binary_insertion_sort(unsorted)) From 997d56fb633e3bd726c1fac32a2d37277361d5e9 Mon Sep 17 00:00:00 2001 From: Margaret <62753112+meg-1@users.noreply.github.com> Date: Wed, 10 May 2023 21:53:47 +0300 Subject: [PATCH 2175/2908] Switch case (#7995) --- strings/string_switch_case.py | 108 ++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 strings/string_switch_case.py diff --git a/strings/string_switch_case.py b/strings/string_switch_case.py new file mode 100644 index 000000000000..9a07472dfd71 --- /dev/null +++ b/strings/string_switch_case.py @@ -0,0 +1,108 @@ +import re + +""" +general info: +https://en.wikipedia.org/wiki/Naming_convention_(programming)#Python_and_Ruby + +pascal case [ an upper Camel Case ]: https://en.wikipedia.org/wiki/Camel_case + +camel case: https://en.wikipedia.org/wiki/Camel_case + +kebab case [ can be found in general info ]: +https://en.wikipedia.org/wiki/Naming_convention_(programming)#Python_and_Ruby + +snake case: https://en.wikipedia.org/wiki/Snake_case +""" + + +# assistant functions +def split_input(str_: str) -> list: + """ + >>> split_input("one two 31235three4four") + [['one', 'two', '31235three4four']] + """ + return [char.split() for char in re.split(r"[^ a-z A-Z 0-9 \s]", str_)] + + +def to_simple_case(str_: str) -> str: + """ + >>> to_simple_case("one two 31235three4four") + 'OneTwo31235three4four' + """ + string_split = split_input(str_) + return "".join( + ["".join([char.capitalize() for char in sub_str]) for sub_str in string_split] + ) + + +def to_complex_case(text: str, upper: bool, separator: str) -> str: + """ + >>> to_complex_case("one two 31235three4four", True, "_") + 'ONE_TWO_31235THREE4FOUR' + >>> to_complex_case("one two 31235three4four", False, "-") + 'one-two-31235three4four' + """ + try: + string_split = split_input(text) + if upper: + res_str = "".join( + [ + separator.join([char.upper() for char in sub_str]) + for sub_str in string_split + ] + ) + else: + res_str = "".join( + [ + separator.join([char.lower() for char in sub_str]) + for sub_str in string_split + ] + ) + return res_str + except IndexError: + return "not valid string" + + +# main content +def to_pascal_case(text: str) -> str: + """ + >>> to_pascal_case("one two 31235three4four") + 'OneTwo31235three4four' + """ + return to_simple_case(text) + + +def to_camel_case(text: str) -> str: + """ + >>> to_camel_case("one two 31235three4four") + 'oneTwo31235three4four' + """ + try: + res_str = to_simple_case(text) + return res_str[0].lower() + res_str[1:] + except IndexError: + return "not valid string" + + +def to_snake_case(text: str, upper: bool) -> str: + """ + >>> to_snake_case("one two 31235three4four", True) + 'ONE_TWO_31235THREE4FOUR' + >>> to_snake_case("one two 31235three4four", False) + 'one_two_31235three4four' + """ + return to_complex_case(text, upper, "_") + + +def to_kebab_case(text: str, upper: bool) -> str: + """ + >>> to_kebab_case("one two 31235three4four", True) + 'ONE-TWO-31235THREE4FOUR' + >>> to_kebab_case("one two 31235three4four", False) + 'one-two-31235three4four' + """ + return to_complex_case(text, upper, "-") + + +if __name__ == "__main__": + __import__("doctest").testmod() From 6939538a41202bf05f958c9c2d7c1c20e2f87430 Mon Sep 17 00:00:00 2001 From: Margaret <62753112+meg-1@users.noreply.github.com> Date: Wed, 10 May 2023 21:55:48 +0300 Subject: [PATCH 2176/2908] adding the remove digit algorithm (#6708) --- maths/remove_digit.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 maths/remove_digit.py diff --git a/maths/remove_digit.py b/maths/remove_digit.py new file mode 100644 index 000000000000..db14ac902a6f --- /dev/null +++ b/maths/remove_digit.py @@ -0,0 +1,37 @@ +def remove_digit(num: int) -> int: + """ + + returns the biggest possible result + that can be achieved by removing + one digit from the given number + + >>> remove_digit(152) + 52 + >>> remove_digit(6385) + 685 + >>> remove_digit(-11) + 1 + >>> remove_digit(2222222) + 222222 + >>> remove_digit("2222222") + Traceback (most recent call last): + TypeError: only integers accepted as input + >>> remove_digit("string input") + Traceback (most recent call last): + TypeError: only integers accepted as input + """ + + if not isinstance(num, int): + raise TypeError("only integers accepted as input") + else: + num_str = str(abs(num)) + num_transpositions = [list(num_str) for char in range(len(num_str))] + for index in range(len(num_str)): + num_transpositions[index].pop(index) + return max( + int("".join(list(transposition))) for transposition in num_transpositions + ) + + +if __name__ == "__main__": + __import__("doctest").testmod() From 793e564e1d4bd6e00b6e2f80869c5fd1fd2872b3 Mon Sep 17 00:00:00 2001 From: Pronoy Mandal Date: Thu, 11 May 2023 00:30:59 +0530 Subject: [PATCH 2177/2908] Create maximum_subsequence.py (#7811) --- DIRECTORY.md | 1 + other/maximum_subsequence.py | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 other/maximum_subsequence.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 826bd6fd39d4..a70ad6861d6f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -716,6 +716,7 @@ * [Lru Cache](other/lru_cache.py) * [Magicdiamondpattern](other/magicdiamondpattern.py) * [Maximum Subarray](other/maximum_subarray.py) + * [Maximum Subsequence](other/maximum_subsequence.py) * [Nested Brackets](other/nested_brackets.py) * [Password](other/password.py) * [Quine](other/quine.py) diff --git a/other/maximum_subsequence.py b/other/maximum_subsequence.py new file mode 100644 index 000000000000..f81717596532 --- /dev/null +++ b/other/maximum_subsequence.py @@ -0,0 +1,42 @@ +from collections.abc import Sequence + + +def max_subsequence_sum(nums: Sequence[int] | None = None) -> int: + """Return the maximum possible sum amongst all non - empty subsequences. + + Raises: + ValueError: when nums is empty. + + >>> max_subsequence_sum([1,2,3,4,-2]) + 10 + >>> max_subsequence_sum([-2, -3, -1, -4, -6]) + -1 + >>> max_subsequence_sum([]) + Traceback (most recent call last): + . . . + ValueError: Input sequence should not be empty + >>> max_subsequence_sum() + Traceback (most recent call last): + . . . + ValueError: Input sequence should not be empty + """ + if nums is None or not nums: + raise ValueError("Input sequence should not be empty") + + ans = nums[0] + for i in range(1, len(nums)): + num = nums[i] + ans = max(ans, ans + num, num) + + return ans + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Try on a sample input from the user + n = int(input("Enter number of elements : ").strip()) + array = list(map(int, input("\nEnter the numbers : ").strip().split()))[:n] + print(max_subsequence_sum(array)) From 1faf10b5c2dff8cef3f5d59f60a126bd19bb1c44 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 14 May 2023 22:03:13 +0100 Subject: [PATCH 2178/2908] Correct ruff failures (#8732) * fix: Correct ruff problems * updating DIRECTORY.md * fix: Fix pre-commit errors * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 6 +++++- conversions/prefix_conversions_string.py | 4 ++-- conversions/rgb_hsv_conversion.py | 4 ++-- .../test_digital_image_processing.py | 2 +- ...ion.py => strassen_matrix_multiplication.py.BROKEN} | 2 +- dynamic_programming/fibonacci.py | 2 +- maths/euclidean_distance.py | 6 +++--- physics/horizontal_projectile_motion.py | 6 +++--- searches/binary_tree_traversal.py | 10 ++++------ 9 files changed, 22 insertions(+), 20 deletions(-) rename divide_and_conquer/{strassen_matrix_multiplication.py => strassen_matrix_multiplication.py.BROKEN} (99%) diff --git a/DIRECTORY.md b/DIRECTORY.md index a70ad6861d6f..fc6cbaf7ff41 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -294,7 +294,6 @@ * [Mergesort](divide_and_conquer/mergesort.py) * [Peak](divide_and_conquer/peak.py) * [Power](divide_and_conquer/power.py) - * [Strassen Matrix Multiplication](divide_and_conquer/strassen_matrix_multiplication.py) ## Dynamic Programming * [Abbreviation](dynamic_programming/abbreviation.py) @@ -632,6 +631,7 @@ * [Radians](maths/radians.py) * [Radix2 Fft](maths/radix2_fft.py) * [Relu](maths/relu.py) + * [Remove Digit](maths/remove_digit.py) * [Runge Kutta](maths/runge_kutta.py) * [Segmented Sieve](maths/segmented_sieve.py) * Series @@ -694,6 +694,8 @@ ## Neural Network * [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py) + * Activation Functions + * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Input Data](neural_network/input_data.py) @@ -1080,6 +1082,7 @@ ## Sorts * [Bead Sort](sorts/bead_sort.py) + * [Binary Insertion Sort](sorts/binary_insertion_sort.py) * [Bitonic Sort](sorts/bitonic_sort.py) * [Bogo Sort](sorts/bogo_sort.py) * [Bubble Sort](sorts/bubble_sort.py) @@ -1170,6 +1173,7 @@ * [Reverse Words](strings/reverse_words.py) * [Snake Case To Camel Pascal Case](strings/snake_case_to_camel_pascal_case.py) * [Split](strings/split.py) + * [String Switch Case](strings/string_switch_case.py) * [Text Justification](strings/text_justification.py) * [Top K Frequent Words](strings/top_k_frequent_words.py) * [Upper](strings/upper.py) diff --git a/conversions/prefix_conversions_string.py b/conversions/prefix_conversions_string.py index 3851d7c8b993..9344c9672a1f 100644 --- a/conversions/prefix_conversions_string.py +++ b/conversions/prefix_conversions_string.py @@ -96,7 +96,7 @@ def add_si_prefix(value: float) -> str: for name_prefix, value_prefix in prefixes.items(): numerical_part = value / (10**value_prefix) if numerical_part > 1: - return f"{str(numerical_part)} {name_prefix}" + return f"{numerical_part!s} {name_prefix}" return str(value) @@ -111,7 +111,7 @@ def add_binary_prefix(value: float) -> str: for prefix in BinaryUnit: numerical_part = value / (2**prefix.value) if numerical_part > 1: - return f"{str(numerical_part)} {prefix.name}" + return f"{numerical_part!s} {prefix.name}" return str(value) diff --git a/conversions/rgb_hsv_conversion.py b/conversions/rgb_hsv_conversion.py index 081cfe1d75e0..74b3d33e49e7 100644 --- a/conversions/rgb_hsv_conversion.py +++ b/conversions/rgb_hsv_conversion.py @@ -121,8 +121,8 @@ def rgb_to_hsv(red: int, green: int, blue: int) -> list[float]: float_red = red / 255 float_green = green / 255 float_blue = blue / 255 - value = max(max(float_red, float_green), float_blue) - chroma = value - min(min(float_red, float_green), float_blue) + value = max(float_red, float_green, float_blue) + chroma = value - min(float_red, float_green, float_blue) saturation = 0 if value == 0 else chroma / value if chroma == 0: diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index c999464ce85e..fee7ab247b55 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -96,7 +96,7 @@ def test_nearest_neighbour( def test_local_binary_pattern(): - file_path: str = "digital_image_processing/image_data/lena.jpg" + file_path = "digital_image_processing/image_data/lena.jpg" # Reading the image and converting it to grayscale. image = imread(file_path, 0) diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py.BROKEN similarity index 99% rename from divide_and_conquer/strassen_matrix_multiplication.py rename to divide_and_conquer/strassen_matrix_multiplication.py.BROKEN index 371605d6d4d4..2ca91c63bf4c 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py.BROKEN @@ -122,7 +122,7 @@ def strassen(matrix1: list, matrix2: list) -> list: if dimension1[0] == dimension1[1] and dimension2[0] == dimension2[1]: return [matrix1, matrix2] - maximum = max(max(dimension1), max(dimension2)) + maximum = max(dimension1, dimension2) maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) new_matrix1 = matrix1 new_matrix2 = matrix2 diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index 7ec5993ef38d..c102493aa00b 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -24,7 +24,7 @@ def get(self, index: int) -> list: return self.sequence[:index] -def main(): +def main() -> None: print( "Fibonacci Series Using Dynamic Programming\n", "Enter the index of the Fibonacci number you want to calculate ", diff --git a/maths/euclidean_distance.py b/maths/euclidean_distance.py index 22012e92c9cf..9b29b37b0ce6 100644 --- a/maths/euclidean_distance.py +++ b/maths/euclidean_distance.py @@ -1,12 +1,12 @@ from __future__ import annotations +import typing from collections.abc import Iterable -from typing import Union import numpy as np -Vector = Union[Iterable[float], Iterable[int], np.ndarray] -VectorOut = Union[np.float64, int, float] +Vector = typing.Union[Iterable[float], Iterable[int], np.ndarray] # noqa: UP007 +VectorOut = typing.Union[np.float64, int, float] # noqa: UP007 def euclidean_distance(vector_1: Vector, vector_2: Vector) -> VectorOut: diff --git a/physics/horizontal_projectile_motion.py b/physics/horizontal_projectile_motion.py index dbde3660f62f..80f85a1b7146 100644 --- a/physics/horizontal_projectile_motion.py +++ b/physics/horizontal_projectile_motion.py @@ -147,6 +147,6 @@ def test_motion() -> None: # Print results print() print("Results: ") - print(f"Horizontal Distance: {str(horizontal_distance(init_vel, angle))} [m]") - print(f"Maximum Height: {str(max_height(init_vel, angle))} [m]") - print(f"Total Time: {str(total_time(init_vel, angle))} [s]") + print(f"Horizontal Distance: {horizontal_distance(init_vel, angle)!s} [m]") + print(f"Maximum Height: {max_height(init_vel, angle)!s} [m]") + print(f"Total Time: {total_time(init_vel, angle)!s} [s]") diff --git a/searches/binary_tree_traversal.py b/searches/binary_tree_traversal.py index 76e80df25a13..6fb841af4294 100644 --- a/searches/binary_tree_traversal.py +++ b/searches/binary_tree_traversal.py @@ -13,11 +13,9 @@ def __init__(self, data): self.left = None -def build_tree(): +def build_tree() -> TreeNode: print("\n********Press N to stop entering at any point of time********\n") - check = input("Enter the value of the root node: ").strip().lower() or "n" - if check == "n": - return None + check = input("Enter the value of the root node: ").strip().lower() q: queue.Queue = queue.Queue() tree_node = TreeNode(int(check)) q.put(tree_node) @@ -37,7 +35,7 @@ def build_tree(): right_node = TreeNode(int(check)) node_found.right = right_node q.put(right_node) - return None + raise def pre_order(node: TreeNode) -> None: @@ -272,7 +270,7 @@ def prompt(s: str = "", width=50, char="*") -> str: doctest.testmod() print(prompt("Binary Tree Traversals")) - node = build_tree() + node: TreeNode = build_tree() print(prompt("Pre Order Traversal")) pre_order(node) print(prompt() + "\n") From 2a57dafce096b51b4b28d1495116e79472c8a3f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 22:27:59 +0100 Subject: [PATCH 2179/2908] [pre-commit.ci] pre-commit autoupdate (#8716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.263 → v0.0.267](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.263...v0.0.267) - [github.com/tox-dev/pyproject-fmt: 0.11.1 → 0.11.2](https://github.com/tox-dev/pyproject-fmt/compare/0.11.1...0.11.2) - [github.com/pre-commit/mirrors-mypy: v1.2.0 → v1.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.2.0...v1.3.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index accb57da35d3..6bdbc7370c9c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.263 + rev: v0.0.267 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.11.1" + rev: "0.11.2" hooks: - id: pyproject-fmt @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.2.0 + rev: v1.3.0 hooks: - id: mypy args: From c0892a06515b8ea5030db2e8344dee2292bb10ad Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 16 May 2023 00:47:50 +0300 Subject: [PATCH 2180/2908] Reduce the complexity of genetic_algorithm/basic_string.py (#8606) --- genetic_algorithm/basic_string.py | 95 ++++++++++++++++--------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index 45b8be651f6e..388e7219f54b 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -21,6 +21,54 @@ random.seed(random.randint(0, 1000)) +def evaluate(item: str, main_target: str) -> 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.0) + """ + score = len([g for position, g in enumerate(item) if g == main_target[position]]) + return (item, float(score)) + + +def crossover(parent_1: str, parent_2: str) -> tuple[str, str]: + """Slice and combine two string at 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, genes: list[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) + + +# Select, crossover and mutate a new population. +def select( + parent_1: tuple[str, float], + population_score: list[tuple[str, float]], + genes: list[str], +) -> list[str]: + """Select the second parent and generate new population""" + pop = [] + # Generate more children 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, genes)) + pop.append(mutate(child_2, genes)) + return pop + + 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. @@ -70,17 +118,6 @@ 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]: - """ - 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, # @@ -94,7 +131,7 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]: # # but with a simple algorithm like this, it will probably be slower. # We just need to call evaluate for every item inside the population. - population_score = [evaluate(item) for item in population] + population_score = [evaluate(item, target) for item in population] # Check if there is a matching evolution. population_score = sorted(population_score, key=lambda x: x[1], reverse=True) @@ -121,41 +158,9 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]: (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 children 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[ # noqa: B023 - 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 at 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)])) + population.extend(select(population_score[int(i)], population_score, genes)) # 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 strings in From 8102424950f2d3801eda7817d7f69288fd984a63 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Tue, 16 May 2023 17:05:55 -0700 Subject: [PATCH 2181/2908] `local_weighted_learning.py`: fix `mypy` errors and more (#8073) --- .../local_weighted_learning.py | 188 +++++++++++------- 1 file changed, 112 insertions(+), 76 deletions(-) diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index 6260e9ac6bfe..8dd0e55d41df 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -1,14 +1,55 @@ +""" +Locally weighted linear regression, also called local regression, is a type of +non-parametric linear regression that prioritizes data closest to a given +prediction point. The algorithm estimates the vector of model coefficients β +using weighted least squares regression: + +β = (XᵀWX)⁻¹(XᵀWy), + +where X is the design matrix, y is the response vector, and W is the diagonal +weight matrix. + +This implementation calculates wᵢ, the weight of the ith training sample, using +the Gaussian weight: + +wᵢ = exp(-‖xᵢ - x‖²/(2τ²)), + +where xᵢ is the ith training sample, x is the prediction point, τ is the +"bandwidth", and ‖x‖ is the Euclidean norm (also called the 2-norm or the L² +norm). The bandwidth τ controls how quickly the weight of a training sample +decreases as its distance from the prediction point increases. One can think of +the Gaussian weight as a bell curve centered around the prediction point: a +training sample is weighted lower if it's farther from the center, and τ +controls the spread of the bell curve. + +Other types of locally weighted regression such as locally estimated scatterplot +smoothing (LOESS) typically use different weight functions. + +References: + - https://en.wikipedia.org/wiki/Local_regression + - https://en.wikipedia.org/wiki/Weighted_least_squares + - https://cs229.stanford.edu/notes2022fall/main_notes.pdf +""" + import matplotlib.pyplot as plt import numpy as np -def weighted_matrix( - point: np.array, training_data_x: np.array, bandwidth: float -) -> np.array: +def weight_matrix(point: np.ndarray, x_train: np.ndarray, tau: float) -> np.ndarray: """ - Calculate the weight for every point in the data set. - point --> the x value at which we want to make predictions - >>> weighted_matrix( + Calculate the weight of every point in the training data around a given + prediction point + + Args: + point: x-value at which the prediction is being made + x_train: ndarray of x-values for training + tau: bandwidth value, controls how quickly the weight of training values + decreases as the distance from the prediction point increases + + Returns: + m x m weight matrix around the prediction point, where m is the size of + the training set + >>> weight_matrix( ... np.array([1., 1.]), ... np.array([[16.99, 10.34], [21.01,23.68], [24.59,25.69]]), ... 0.6 @@ -17,25 +58,30 @@ def weighted_matrix( [0.00000000e+000, 0.00000000e+000, 0.00000000e+000], [0.00000000e+000, 0.00000000e+000, 0.00000000e+000]]) """ - m, _ = np.shape(training_data_x) # m is the number of training samples - weights = np.eye(m) # Initializing weights as identity matrix - - # calculating weights for all training examples [x(i)'s] + m = len(x_train) # Number of training samples + weights = np.eye(m) # Initialize weights as identity matrix for j in range(m): - diff = point - training_data_x[j] - weights[j, j] = np.exp(diff @ diff.T / (-2.0 * bandwidth**2)) + diff = point - x_train[j] + weights[j, j] = np.exp(diff @ diff.T / (-2.0 * tau**2)) + return weights def local_weight( - point: np.array, - training_data_x: np.array, - training_data_y: np.array, - bandwidth: float, -) -> np.array: + point: np.ndarray, x_train: np.ndarray, y_train: np.ndarray, tau: float +) -> np.ndarray: """ - Calculate the local weights using the weight_matrix function on training data. - Return the weighted matrix. + Calculate the local weights at a given prediction point using the weight + matrix for that point + + Args: + point: x-value at which the prediction is being made + x_train: ndarray of x-values for training + y_train: ndarray of y-values for training + tau: bandwidth value, controls how quickly the weight of training values + decreases as the distance from the prediction point increases + Returns: + ndarray of local weights >>> local_weight( ... np.array([1., 1.]), ... np.array([[16.99, 10.34], [21.01,23.68], [24.59,25.69]]), @@ -45,19 +91,28 @@ def local_weight( array([[0.00873174], [0.08272556]]) """ - weight = weighted_matrix(point, training_data_x, bandwidth) - w = np.linalg.inv(training_data_x.T @ (weight @ training_data_x)) @ ( - training_data_x.T @ weight @ training_data_y.T + weight_mat = weight_matrix(point, x_train, tau) + weight = np.linalg.inv(x_train.T @ weight_mat @ x_train) @ ( + x_train.T @ weight_mat @ y_train.T ) - return w + return weight def local_weight_regression( - training_data_x: np.array, training_data_y: np.array, bandwidth: float -) -> np.array: + x_train: np.ndarray, y_train: np.ndarray, tau: float +) -> np.ndarray: """ - Calculate predictions for each data point on axis + Calculate predictions for each point in the training data + + Args: + x_train: ndarray of x-values for training + y_train: ndarray of y-values for training + tau: bandwidth value, controls how quickly the weight of training values + decreases as the distance from the prediction point increases + + Returns: + ndarray of predictions >>> local_weight_regression( ... np.array([[16.99, 10.34], [21.01, 23.68], [24.59, 25.69]]), ... np.array([[1.01, 1.66, 3.5]]), @@ -65,77 +120,57 @@ def local_weight_regression( ... ) array([1.07173261, 1.65970737, 3.50160179]) """ - m, _ = np.shape(training_data_x) - ypred = np.zeros(m) + y_pred = np.zeros(len(x_train)) # Initialize array of predictions + for i, item in enumerate(x_train): + y_pred[i] = item @ local_weight(item, x_train, y_train, tau) - for i, item in enumerate(training_data_x): - ypred[i] = item @ local_weight( - item, training_data_x, training_data_y, bandwidth - ) - - return ypred + return y_pred def load_data( - dataset_name: str, cola_name: str, colb_name: str -) -> tuple[np.array, np.array, np.array, np.array]: + dataset_name: str, x_name: str, y_name: str +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ Load data from seaborn and split it into x and y points + >>> pass # No doctests, function is for demo purposes only """ import seaborn as sns data = sns.load_dataset(dataset_name) - col_a = np.array(data[cola_name]) # total_bill - col_b = np.array(data[colb_name]) # tip - - mcol_a = col_a.copy() - mcol_b = col_b.copy() - - one = np.ones(np.shape(mcol_b)[0], dtype=int) + x_data = np.array(data[x_name]) + y_data = np.array(data[y_name]) - # pairing elements of one and mcol_a - training_data_x = np.column_stack((one, mcol_a)) + one = np.ones(len(y_data)) - return training_data_x, mcol_b, col_a, col_b + # pairing elements of one and x_data + x_train = np.column_stack((one, x_data)) - -def get_preds(training_data_x: np.array, mcol_b: np.array, tau: float) -> np.array: - """ - Get predictions with minimum error for each training data - >>> get_preds( - ... np.array([[16.99, 10.34], [21.01, 23.68], [24.59, 25.69]]), - ... np.array([[1.01, 1.66, 3.5]]), - ... 0.6 - ... ) - array([1.07173261, 1.65970737, 3.50160179]) - """ - ypred = local_weight_regression(training_data_x, mcol_b, tau) - return ypred + return x_train, x_data, y_data def plot_preds( - training_data_x: np.array, - predictions: np.array, - col_x: np.array, - col_y: np.array, - cola_name: str, - colb_name: str, -) -> plt.plot: + x_train: np.ndarray, + preds: np.ndarray, + x_data: np.ndarray, + y_data: np.ndarray, + x_name: str, + y_name: str, +) -> None: """ Plot predictions and display the graph + >>> pass # No doctests, function is for demo purposes only """ - xsort = training_data_x.copy() - xsort.sort(axis=0) - plt.scatter(col_x, col_y, color="blue") + x_train_sorted = np.sort(x_train, axis=0) + plt.scatter(x_data, y_data, color="blue") plt.plot( - xsort[:, 1], - predictions[training_data_x[:, 1].argsort(0)], + x_train_sorted[:, 1], + preds[x_train[:, 1].argsort(0)], color="yellow", linewidth=5, ) plt.title("Local Weighted Regression") - plt.xlabel(cola_name) - plt.ylabel(colb_name) + plt.xlabel(x_name) + plt.ylabel(y_name) plt.show() @@ -144,6 +179,7 @@ def plot_preds( doctest.testmod() - training_data_x, mcol_b, col_a, col_b = load_data("tips", "total_bill", "tip") - predictions = get_preds(training_data_x, mcol_b, 0.5) - plot_preds(training_data_x, predictions, col_a, col_b, "total_bill", "tip") + # Demo with a dataset from the seaborn module + training_data_x, total_bill, tip = load_data("tips", "total_bill", "tip") + predictions = local_weight_regression(training_data_x, tip, 5) + plot_preds(training_data_x, predictions, total_bill, tip, "total_bill", "tip") From 3dc143f7218a1221f346c0fccb516d1199850e18 Mon Sep 17 00:00:00 2001 From: Rohan Saraogi <62804340+r0sa2@users.noreply.github.com> Date: Wed, 17 May 2023 05:38:56 +0530 Subject: [PATCH 2182/2908] Added odd_sieve.py (#8740) --- maths/odd_sieve.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 maths/odd_sieve.py diff --git a/maths/odd_sieve.py b/maths/odd_sieve.py new file mode 100644 index 000000000000..60e92921a94c --- /dev/null +++ b/maths/odd_sieve.py @@ -0,0 +1,42 @@ +from itertools import compress, repeat +from math import ceil, sqrt + + +def odd_sieve(num: int) -> list[int]: + """ + Returns the prime numbers < `num`. The prime numbers are calculated using an + odd sieve implementation of the Sieve of Eratosthenes algorithm + (see for reference https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes). + + >>> odd_sieve(2) + [] + >>> odd_sieve(3) + [2] + >>> odd_sieve(10) + [2, 3, 5, 7] + >>> odd_sieve(20) + [2, 3, 5, 7, 11, 13, 17, 19] + """ + + if num <= 2: + return [] + if num == 3: + return [2] + + # Odd sieve for numbers in range [3, num - 1] + sieve = bytearray(b"\x01") * ((num >> 1) - 1) + + for i in range(3, int(sqrt(num)) + 1, 2): + if sieve[(i >> 1) - 1]: + i_squared = i**2 + sieve[(i_squared >> 1) - 1 :: i] = repeat( + 0, ceil((num - i_squared) / (i << 1)) + ) + + return [2] + list(compress(range(3, num, 2), sieve)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 61cfb43d2b9246d1e2019ce7f03cb91f452ed2ba Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Wed, 17 May 2023 04:21:16 +0400 Subject: [PATCH 2183/2908] Add h index (#8036) --- DIRECTORY.md | 1 + other/h_index.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 other/h_index.py diff --git a/DIRECTORY.md b/DIRECTORY.md index fc6cbaf7ff41..46bd51ce91ea 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -712,6 +712,7 @@ * [Gauss Easter](other/gauss_easter.py) * [Graham Scan](other/graham_scan.py) * [Greedy](other/greedy.py) + * [H Index](other/h_index.py) * [Least Recently Used](other/least_recently_used.py) * [Lfu Cache](other/lfu_cache.py) * [Linear Congruential Generator](other/linear_congruential_generator.py) diff --git a/other/h_index.py b/other/h_index.py new file mode 100644 index 000000000000..e91389675b16 --- /dev/null +++ b/other/h_index.py @@ -0,0 +1,71 @@ +""" +Task: +Given an array of integers citations where citations[i] is the number of +citations a researcher received for their ith paper, return compute the +researcher's h-index. + +According to the definition of h-index on Wikipedia: A scientist has an +index h if h of their n papers have at least h citations each, and the other +n - h papers have no more than h citations each. + +If there are several possible values for h, the maximum one is taken as the +h-index. + +H-Index link: https://en.wikipedia.org/wiki/H-index + +Implementation notes: +Use sorting of array + +Leetcode link: https://leetcode.com/problems/h-index/description/ + +n = len(citations) +Runtime Complexity: O(n * log(n)) +Space Complexity: O(1) + +""" + + +def h_index(citations: list[int]) -> int: + """ + Return H-index of citations + + >>> h_index([3, 0, 6, 1, 5]) + 3 + >>> h_index([1, 3, 1]) + 1 + >>> h_index([1, 2, 3]) + 2 + >>> h_index('test') + Traceback (most recent call last): + ... + ValueError: The citations should be a list of non negative integers. + >>> h_index([1,2,'3']) + Traceback (most recent call last): + ... + ValueError: The citations should be a list of non negative integers. + >>> h_index([1,2,-3]) + Traceback (most recent call last): + ... + ValueError: The citations should be a list of non negative integers. + """ + + # validate: + if not isinstance(citations, list) or not all( + isinstance(item, int) and item >= 0 for item in citations + ): + raise ValueError("The citations should be a list of non negative integers.") + + citations.sort() + len_citations = len(citations) + + for i in range(len_citations): + if citations[len_citations - 1 - i] <= i: + return i + + return len_citations + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a2783c6597a154a87f60bb5878770d2f152a1d09 Mon Sep 17 00:00:00 2001 From: Harkishan Khuva <78949167+hakiKhuva@users.noreply.github.com> Date: Wed, 17 May 2023 05:52:24 +0530 Subject: [PATCH 2184/2908] Create guess_the_number_search.py (#7937) --- other/guess_the_number_search.py | 165 +++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 other/guess_the_number_search.py diff --git a/other/guess_the_number_search.py b/other/guess_the_number_search.py new file mode 100644 index 000000000000..0439223f2ec9 --- /dev/null +++ b/other/guess_the_number_search.py @@ -0,0 +1,165 @@ +""" +guess the number using lower,higher and the value to find or guess + +solution works by dividing lower and higher of number guessed + +suppose lower is 0, higher is 1000 and the number to guess is 355 + +>>> guess_the_number(10, 1000, 17) +started... +guess the number : 17 +details : [505, 257, 133, 71, 40, 25, 17] + +""" + + +def temp_input_value( + min_val: int = 10, max_val: int = 1000, option: bool = True +) -> int: + """ + Temporary input values for tests + + >>> temp_input_value(option=True) + 10 + + >>> temp_input_value(option=False) + 1000 + + >>> temp_input_value(min_val=100, option=True) + 100 + + >>> temp_input_value(min_val=100, max_val=50) + Traceback (most recent call last): + ... + ValueError: Invalid value for min_val or max_val (min_value < max_value) + + >>> temp_input_value("ten","fifty",1) + Traceback (most recent call last): + ... + AssertionError: Invalid type of value(s) specified to function! + + >>> temp_input_value(min_val=-100, max_val=500) + -100 + + >>> temp_input_value(min_val=-5100, max_val=-100) + -5100 + """ + assert ( + isinstance(min_val, int) + and isinstance(max_val, int) + and isinstance(option, bool) + ), "Invalid type of value(s) specified to function!" + + if min_val > max_val: + raise ValueError("Invalid value for min_val or max_val (min_value < max_value)") + return min_val if option else max_val + + +def get_avg(number_1: int, number_2: int) -> int: + """ + Return the mid-number(whole) of two integers a and b + + >>> get_avg(10, 15) + 12 + + >>> get_avg(20, 300) + 160 + + >>> get_avg("abcd", 300) + Traceback (most recent call last): + ... + TypeError: can only concatenate str (not "int") to str + + >>> get_avg(10.5,50.25) + 30 + """ + return int((number_1 + number_2) / 2) + + +def guess_the_number(lower: int, higher: int, to_guess: int) -> None: + """ + The `guess_the_number` function that guess the number by some operations + and using inner functions + + >>> guess_the_number(10, 1000, 17) + started... + guess the number : 17 + details : [505, 257, 133, 71, 40, 25, 17] + + >>> guess_the_number(-10000, 10000, 7) + started... + guess the number : 7 + details : [0, 5000, 2500, 1250, 625, 312, 156, 78, 39, 19, 9, 4, 6, 7] + + >>> guess_the_number(10, 1000, "a") + Traceback (most recent call last): + ... + AssertionError: argument values must be type of "int" + + >>> guess_the_number(10, 1000, 5) + Traceback (most recent call last): + ... + ValueError: guess value must be within the range of lower and higher value + + >>> guess_the_number(10000, 100, 5) + Traceback (most recent call last): + ... + ValueError: argument value for lower and higher must be(lower > higher) + """ + assert ( + isinstance(lower, int) and isinstance(higher, int) and isinstance(to_guess, int) + ), 'argument values must be type of "int"' + + if lower > higher: + raise ValueError("argument value for lower and higher must be(lower > higher)") + + if not lower < to_guess < higher: + raise ValueError( + "guess value must be within the range of lower and higher value" + ) + + def answer(number: int) -> str: + """ + Returns value by comparing with entered `to_guess` number + """ + if number > to_guess: + return "high" + elif number < to_guess: + return "low" + else: + return "same" + + print("started...") + + last_lowest = lower + last_highest = higher + + last_numbers = [] + + while True: + number = get_avg(last_lowest, last_highest) + last_numbers.append(number) + + if answer(number) == "low": + last_lowest = number + elif answer(number) == "high": + last_highest = number + else: + break + + print(f"guess the number : {last_numbers[-1]}") + print(f"details : {str(last_numbers)}") + + +def main() -> None: + """ + starting point or function of script + """ + lower = int(input("Enter lower value : ").strip()) + higher = int(input("Enter high value : ").strip()) + guess = int(input("Enter value to guess : ").strip()) + guess_the_number(lower, higher, guess) + + +if __name__ == "__main__": + main() From 9b3e4028c6927a17656e590e878c2a101bc4e951 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Wed, 17 May 2023 07:47:23 +0100 Subject: [PATCH 2185/2908] Fixes broken "Create guess_the_number_search.py" (#8746) --- DIRECTORY.md | 2 ++ other/guess_the_number_search.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 46bd51ce91ea..82791cde183d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -605,6 +605,7 @@ * [Newton Raphson](maths/newton_raphson.py) * [Number Of Digits](maths/number_of_digits.py) * [Numerical Integration](maths/numerical_integration.py) + * [Odd Sieve](maths/odd_sieve.py) * [Perfect Cube](maths/perfect_cube.py) * [Perfect Number](maths/perfect_number.py) * [Perfect Square](maths/perfect_square.py) @@ -712,6 +713,7 @@ * [Gauss Easter](other/gauss_easter.py) * [Graham Scan](other/graham_scan.py) * [Greedy](other/greedy.py) + * [Guess The Number Search](other/guess_the_number_search.py) * [H Index](other/h_index.py) * [Least Recently Used](other/least_recently_used.py) * [Lfu Cache](other/lfu_cache.py) diff --git a/other/guess_the_number_search.py b/other/guess_the_number_search.py index 0439223f2ec9..01e8898bbb8a 100644 --- a/other/guess_the_number_search.py +++ b/other/guess_the_number_search.py @@ -148,7 +148,7 @@ def answer(number: int) -> str: break print(f"guess the number : {last_numbers[-1]}") - print(f"details : {str(last_numbers)}") + print(f"details : {last_numbers!s}") def main() -> None: From cf5e34d4794fbba04d18c98d5d09854029c83466 Mon Sep 17 00:00:00 2001 From: Rohan Saraogi <62804340+r0sa2@users.noreply.github.com> Date: Fri, 19 May 2023 05:18:22 +0530 Subject: [PATCH 2186/2908] Added is_palindrome.py (#8748) --- maths/is_palindrome.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 maths/is_palindrome.py diff --git a/maths/is_palindrome.py b/maths/is_palindrome.py new file mode 100644 index 000000000000..ba60573ab022 --- /dev/null +++ b/maths/is_palindrome.py @@ -0,0 +1,34 @@ +def is_palindrome(num: int) -> bool: + """ + Returns whether `num` is a palindrome or not + (see for reference https://en.wikipedia.org/wiki/Palindromic_number). + + >>> is_palindrome(-121) + False + >>> is_palindrome(0) + True + >>> is_palindrome(10) + False + >>> is_palindrome(11) + True + >>> is_palindrome(101) + True + >>> is_palindrome(120) + False + """ + if num < 0: + return False + + num_copy: int = num + rev_num: int = 0 + while num > 0: + rev_num = rev_num * 10 + (num % 10) + num //= 10 + + return num_copy == rev_num + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From edc17b60e00e704cb4109a0e6b18c6ad43234c26 Mon Sep 17 00:00:00 2001 From: Daniel Luo <103051750+DanielLuo7@users.noreply.github.com> Date: Thu, 18 May 2023 20:40:52 -0400 Subject: [PATCH 2187/2908] add __main__ around print (#8747) --- ciphers/mixed_keyword_cypher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index 806004faa079..93a0e3acb7b1 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -65,4 +65,5 @@ def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: return cypher -print(mixed_keyword("college", "UNIVERSITY")) +if __name__ == "__main__": + print(mixed_keyword("college", "UNIVERSITY")) From ce43a8ac4ad14e1639014d374b1137906218cfe3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 05:54:30 +0200 Subject: [PATCH 2188/2908] [pre-commit.ci] pre-commit autoupdate (#8759) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.267 → v0.0.269](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.267...v0.0.269) - [github.com/abravalheri/validate-pyproject: v0.12.2 → v0.13](https://github.com/abravalheri/validate-pyproject/compare/v0.12.2...v0.13) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6bdbc7370c9c..bd5bca8f05ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.267 + rev: v0.0.269 hooks: - id: ruff @@ -46,7 +46,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.12.2 + rev: v0.13 hooks: - id: validate-pyproject diff --git a/DIRECTORY.md b/DIRECTORY.md index 82791cde183d..3181a93f393d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -577,6 +577,7 @@ * [Hexagonal Number](maths/hexagonal_number.py) * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) + * [Is Palindrome](maths/is_palindrome.py) * [Is Square Free](maths/is_square_free.py) * [Jaccard Similarity](maths/jaccard_similarity.py) * [Juggler Sequence](maths/juggler_sequence.py) From df88771905e68c0639069a92144d6b7af1d491ce Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 25 May 2023 06:59:15 +0100 Subject: [PATCH 2189/2908] Mark fetch anime and play as broken (#8763) * updating DIRECTORY.md * updating DIRECTORY.md * fix: Correct ruff errors * fix: Mark anime algorithm as broken * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 - .../{fetch_anime_and_play.py => fetch_anime_and_play.py.BROKEN} | 0 2 files changed, 1 deletion(-) rename web_programming/{fetch_anime_and_play.py => fetch_anime_and_play.py.BROKEN} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 3181a93f393d..71bdf30b2ddb 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1199,7 +1199,6 @@ * [Daily Horoscope](web_programming/daily_horoscope.py) * [Download Images From Google Query](web_programming/download_images_from_google_query.py) * [Emails From Url](web_programming/emails_from_url.py) - * [Fetch Anime And Play](web_programming/fetch_anime_and_play.py) * [Fetch Bbc News](web_programming/fetch_bbc_news.py) * [Fetch Github Info](web_programming/fetch_github_info.py) * [Fetch Jobs](web_programming/fetch_jobs.py) diff --git a/web_programming/fetch_anime_and_play.py b/web_programming/fetch_anime_and_play.py.BROKEN similarity index 100% rename from web_programming/fetch_anime_and_play.py rename to web_programming/fetch_anime_and_play.py.BROKEN From 200429fc4739c3757180635016614b984cfd2206 Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Thu, 25 May 2023 18:04:42 +1200 Subject: [PATCH 2190/2908] Dual Number Automatic Differentiation (#8760) * Added dual_number_automatic_differentiation.py * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/dual_number_automatic_differentiation.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 1 + .../dual_number_automatic_differentiation.py | 141 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 maths/dual_number_automatic_differentiation.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 71bdf30b2ddb..a75723369b06 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -549,6 +549,7 @@ * [Dodecahedron](maths/dodecahedron.py) * [Double Factorial Iterative](maths/double_factorial_iterative.py) * [Double Factorial Recursive](maths/double_factorial_recursive.py) + * [Dual Number Automatic Differentiation](maths/dual_number_automatic_differentiation.py) * [Entropy](maths/entropy.py) * [Euclidean Distance](maths/euclidean_distance.py) * [Euclidean Gcd](maths/euclidean_gcd.py) diff --git a/maths/dual_number_automatic_differentiation.py b/maths/dual_number_automatic_differentiation.py new file mode 100644 index 000000000000..9aa75830c4a1 --- /dev/null +++ b/maths/dual_number_automatic_differentiation.py @@ -0,0 +1,141 @@ +from math import factorial + +""" +https://en.wikipedia.org/wiki/Automatic_differentiation#Automatic_differentiation_using_dual_numbers +https://blog.jliszka.org/2013/10/24/exact-numeric-nth-derivatives.html + +Note this only works for basic functions, f(x) where the power of x is positive. +""" + + +class Dual: + def __init__(self, real, rank): + self.real = real + if isinstance(rank, int): + self.duals = [1] * rank + else: + self.duals = rank + + def __repr__(self): + return ( + f"{self.real}+" + f"{'+'.join(str(dual)+'E'+str(n+1)for n,dual in enumerate(self.duals))}" + ) + + def reduce(self): + cur = self.duals.copy() + while cur[-1] == 0: + cur.pop(-1) + return Dual(self.real, cur) + + def __add__(self, other): + if not isinstance(other, Dual): + return Dual(self.real + other, self.duals) + s_dual = self.duals.copy() + o_dual = other.duals.copy() + if len(s_dual) > len(o_dual): + o_dual.extend([1] * (len(s_dual) - len(o_dual))) + elif len(s_dual) < len(o_dual): + s_dual.extend([1] * (len(o_dual) - len(s_dual))) + new_duals = [] + for i in range(len(s_dual)): + new_duals.append(s_dual[i] + o_dual[i]) + return Dual(self.real + other.real, new_duals) + + __radd__ = __add__ + + def __sub__(self, other): + return self + other * -1 + + def __mul__(self, other): + if not isinstance(other, Dual): + new_duals = [] + for i in self.duals: + new_duals.append(i * other) + return Dual(self.real * other, new_duals) + new_duals = [0] * (len(self.duals) + len(other.duals) + 1) + for i, item in enumerate(self.duals): + for j, jtem in enumerate(other.duals): + new_duals[i + j + 1] += item * jtem + for k in range(len(self.duals)): + new_duals[k] += self.duals[k] * other.real + for index in range(len(other.duals)): + new_duals[index] += other.duals[index] * self.real + return Dual(self.real * other.real, new_duals) + + __rmul__ = __mul__ + + def __truediv__(self, other): + if not isinstance(other, Dual): + new_duals = [] + for i in self.duals: + new_duals.append(i / other) + return Dual(self.real / other, new_duals) + raise ValueError() + + def __floordiv__(self, other): + if not isinstance(other, Dual): + new_duals = [] + for i in self.duals: + new_duals.append(i // other) + return Dual(self.real // other, new_duals) + raise ValueError() + + def __pow__(self, n): + if n < 0 or isinstance(n, float): + raise ValueError("power must be a positive integer") + if n == 0: + return 1 + if n == 1: + return self + x = self + for _ in range(n - 1): + x *= self + return x + + +def differentiate(func, position, order): + """ + >>> differentiate(lambda x: x**2, 2, 2) + 2 + >>> differentiate(lambda x: x**2 * x**4, 9, 2) + 196830 + >>> differentiate(lambda y: 0.5 * (y + 3) ** 6, 3.5, 4) + 7605.0 + >>> differentiate(lambda y: y ** 2, 4, 3) + 0 + >>> differentiate(8, 8, 8) + Traceback (most recent call last): + ... + ValueError: differentiate() requires a function as input for func + >>> differentiate(lambda x: x **2, "", 1) + Traceback (most recent call last): + ... + ValueError: differentiate() requires a float as input for position + >>> differentiate(lambda x: x**2, 3, "") + Traceback (most recent call last): + ... + ValueError: differentiate() requires an int as input for order + """ + if not callable(func): + raise ValueError("differentiate() requires a function as input for func") + if not isinstance(position, (float, int)): + raise ValueError("differentiate() requires a float as input for position") + if not isinstance(order, int): + raise ValueError("differentiate() requires an int as input for order") + d = Dual(position, 1) + result = func(d) + if order == 0: + return result.real + return result.duals[order - 1] * factorial(order) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + def f(y): + return y**2 * y**4 + + print(differentiate(f, 9, 2)) From a6631487b0a9d6a310d8c45d211e8b7b7bd93cab Mon Sep 17 00:00:00 2001 From: Ratnesh Kumar <89133941+ratneshrt@users.noreply.github.com> Date: Thu, 25 May 2023 16:04:11 +0530 Subject: [PATCH 2191/2908] Fix CI badge in the README.md (#8137) From cfbbfd9896cc96379f7374a68ff04b245bb3527c Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 25 May 2023 11:56:23 +0100 Subject: [PATCH 2192/2908] Merge and add benchmarks to palindrome algorithms in the strings/ directory (#8749) * refactor: Merge and add benchmarks to palindrome * updating DIRECTORY.md * chore: Fix failing tests * Update strings/palindrome.py Co-authored-by: Christian Clauss * Update palindrome.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 - strings/is_palindrome.py | 41 ---------------------------------------- strings/palindrome.py | 40 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 43 deletions(-) delete mode 100644 strings/is_palindrome.py diff --git a/DIRECTORY.md b/DIRECTORY.md index a75723369b06..fe4baac863d0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1156,7 +1156,6 @@ * [Indian Phone Validator](strings/indian_phone_validator.py) * [Is Contains Unique Chars](strings/is_contains_unique_chars.py) * [Is Isogram](strings/is_isogram.py) - * [Is Palindrome](strings/is_palindrome.py) * [Is Pangram](strings/is_pangram.py) * [Is Spain National Id](strings/is_spain_national_id.py) * [Is Srilankan Phone Number](strings/is_srilankan_phone_number.py) diff --git a/strings/is_palindrome.py b/strings/is_palindrome.py deleted file mode 100644 index 406aa2e8d3c3..000000000000 --- a/strings/is_palindrome.py +++ /dev/null @@ -1,41 +0,0 @@ -def is_palindrome(s: str) -> bool: - """ - Determine if the string s is a palindrome. - - >>> is_palindrome("A man, A plan, A canal -- Panama!") - 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 often ignored while checking - # palindromes, we first remove them from our string. - s = "".join(character for character in s.lower() if character.isalnum()) - # return s == s[::-1] the slicing method - # uses extra spaces we can - # better with iteration method. - - end = len(s) // 2 - n = len(s) - - # We need to traverse till half of the length of string - # as we can get access of the i'th last element from - # i'th index. - # eg: [0,1,2,3,4,5] => 4th index can be accessed - # with the help of 1st index (i==n-i-1) - # where n is length of string - - return all(s[i] == s[n - i - 1] for i in range(end)) - - -if __name__ == "__main__": - s = input("Please enter a string to see if it is a palindrome: ") - if is_palindrome(s): - print(f"'{s}' is a palindrome.") - else: - print(f"'{s}' is not a palindrome.") diff --git a/strings/palindrome.py b/strings/palindrome.py index dd1fe316f479..bfdb3ddcf396 100644 --- a/strings/palindrome.py +++ b/strings/palindrome.py @@ -1,5 +1,7 @@ # Algorithms to determine if a string is palindrome +from timeit import timeit + test_data = { "MALAYALAM": True, "String": False, @@ -33,6 +35,25 @@ def is_palindrome(s: str) -> bool: return True +def is_palindrome_traversal(s: str) -> bool: + """ + Return True if s is a palindrome otherwise return False. + + >>> all(is_palindrome_traversal(key) is value for key, value in test_data.items()) + True + """ + end = len(s) // 2 + n = len(s) + + # We need to traverse till half of the length of string + # as we can get access of the i'th last element from + # i'th index. + # eg: [0,1,2,3,4,5] => 4th index can be accessed + # with the help of 1st index (i==n-i-1) + # where n is length of string + return all(s[i] == s[n - i - 1] for i in range(end)) + + def is_palindrome_recursive(s: str) -> bool: """ Return True if s is a palindrome otherwise return False. @@ -40,7 +61,7 @@ def is_palindrome_recursive(s: str) -> bool: >>> all(is_palindrome_recursive(key) is value for key, value in test_data.items()) True """ - if len(s) <= 1: + if len(s) <= 2: return True if s[0] == s[len(s) - 1]: return is_palindrome_recursive(s[1:-1]) @@ -58,9 +79,26 @@ def is_palindrome_slice(s: str) -> bool: return s == s[::-1] +def benchmark_function(name: str) -> None: + stmt = f"all({name}(key) is value for key, value in test_data.items())" + setup = f"from __main__ import test_data, {name}" + number = 500000 + result = timeit(stmt=stmt, setup=setup, number=number) + print(f"{name:<35} finished {number:,} runs in {result:.5f} seconds") + + if __name__ == "__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") + + # finished 500,000 runs in 0.46793 seconds + benchmark_function("is_palindrome_slice") + # finished 500,000 runs in 0.85234 seconds + benchmark_function("is_palindrome") + # finished 500,000 runs in 1.32028 seconds + benchmark_function("is_palindrome_recursive") + # finished 500,000 runs in 2.08679 seconds + benchmark_function("is_palindrome_traversal") From a17791d022bdc942c8badabc52307c354069a7ae Mon Sep 17 00:00:00 2001 From: Juyoung Kim <61103343+JadeKim042386@users.noreply.github.com> Date: Thu, 25 May 2023 21:54:18 +0900 Subject: [PATCH 2193/2908] fix: graphs/greedy_best_first typo (#8766) #8764 --- graphs/greedy_best_first.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index d49e65b9d814..35f7ca9feeef 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -58,8 +58,8 @@ 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) + dx = abs(self.pos_x - self.goal_x) + dy = abs(self.pos_y - self.goal_y) return dx + dy def __lt__(self, other) -> bool: From dd3b499bfa972507759d0705b77e2e1946f42596 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 26 May 2023 08:50:33 +0200 Subject: [PATCH 2194/2908] Rename is_palindrome.py to is_int_palindrome.py (#8768) * Rename is_palindrome.py to is_int_palindrome.py * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- maths/{is_palindrome.py => is_int_palindrome.py} | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) rename maths/{is_palindrome.py => is_int_palindrome.py} (67%) diff --git a/DIRECTORY.md b/DIRECTORY.md index fe4baac863d0..11ff93c91430 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -577,8 +577,8 @@ * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) * [Hexagonal Number](maths/hexagonal_number.py) * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) + * [Is Int Palindrome](maths/is_int_palindrome.py) * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) - * [Is Palindrome](maths/is_palindrome.py) * [Is Square Free](maths/is_square_free.py) * [Jaccard Similarity](maths/jaccard_similarity.py) * [Juggler Sequence](maths/juggler_sequence.py) diff --git a/maths/is_palindrome.py b/maths/is_int_palindrome.py similarity index 67% rename from maths/is_palindrome.py rename to maths/is_int_palindrome.py index ba60573ab022..63dc9e2138e8 100644 --- a/maths/is_palindrome.py +++ b/maths/is_int_palindrome.py @@ -1,19 +1,19 @@ -def is_palindrome(num: int) -> bool: +def is_int_palindrome(num: int) -> bool: """ Returns whether `num` is a palindrome or not (see for reference https://en.wikipedia.org/wiki/Palindromic_number). - >>> is_palindrome(-121) + >>> is_int_palindrome(-121) False - >>> is_palindrome(0) + >>> is_int_palindrome(0) True - >>> is_palindrome(10) + >>> is_int_palindrome(10) False - >>> is_palindrome(11) + >>> is_int_palindrome(11) True - >>> is_palindrome(101) + >>> is_int_palindrome(101) True - >>> is_palindrome(120) + >>> is_int_palindrome(120) False """ if num < 0: From 4b79d771cd81a820c195e62430100c416a1618ea Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 26 May 2023 09:34:17 +0200 Subject: [PATCH 2195/2908] Add more ruff rules (#8767) * Add more ruff rules * Add more ruff rules * pre-commit: Update ruff v0.0.269 -> v0.0.270 * Apply suggestions from code review * Fix doctest * Fix doctest (ignore whitespace) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Dhruv Manilawala Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../jacobi_iteration_method.py | 30 ++-- arithmetic_analysis/lu_decomposition.py | 5 +- audio_filters/iir_filter.py | 14 +- backtracking/knight_tour.py | 3 +- bit_manipulation/reverse_bits.py | 3 +- ciphers/base64.py | 12 +- ciphers/beaufort_cipher.py | 2 +- ciphers/cryptomath_module.py | 3 +- ciphers/enigma_machine2.py | 30 ++-- ciphers/hill_cipher.py | 7 +- .../astronomical_length_scale_conversion.py | 6 +- conversions/length_conversion.py | 6 +- conversions/speed_conversions.py | 3 +- conversions/weight_conversion.py | 3 +- .../binary_search_tree_recursive.py | 6 +- .../binary_tree/binary_tree_mirror.py | 3 +- data_structures/disjoint_set/disjoint_set.py | 3 +- .../linked_list/circular_linked_list.py | 8 +- .../linked_list/doubly_linked_list.py | 4 +- .../linked_list/singly_linked_list.py | 4 +- data_structures/stacks/stack.py | 6 +- digital_image_processing/dithering/burkes.py | 3 +- divide_and_conquer/convex_hull.py | 8 +- dynamic_programming/knapsack.py | 15 +- dynamic_programming/minimum_steps_to_one.py | 3 +- dynamic_programming/rod_cutting.py | 10 +- dynamic_programming/viterbi.py | 17 ++- electronics/resistor_equivalence.py | 6 +- genetic_algorithm/basic_string.py | 8 +- graphics/vector3_for_2d_rendering.py | 8 +- graphs/breadth_first_search_shortest_path.py | 3 +- linear_algebra/src/schur_complement.py | 14 +- machine_learning/similarity_search.py | 21 +-- machine_learning/support_vector_machines.py | 3 +- maths/3n_plus_1.py | 6 +- maths/automorphic_number.py | 3 +- maths/catalan_number.py | 6 +- .../dual_number_automatic_differentiation.py | 4 +- maths/hexagonal_number.py | 3 +- maths/juggler_sequence.py | 6 +- maths/liouville_lambda.py | 3 +- maths/manhattan_distance.py | 18 +-- maths/pronic_number.py | 3 +- maths/proth_number.py | 6 +- maths/radix2_fft.py | 2 +- maths/sieve_of_eratosthenes.py | 3 +- maths/sylvester_sequence.py | 3 +- maths/twin_prime.py | 3 +- matrix/matrix_operation.py | 12 +- matrix/sherman_morrison.py | 3 +- neural_network/input_data.py | 12 +- other/nested_brackets.py | 2 +- other/scoring_algorithm.py | 3 +- project_euler/problem_054/sol1.py | 6 +- project_euler/problem_068/sol1.py | 3 +- project_euler/problem_131/sol1.py | 5 +- pyproject.toml | 139 +++++++++++++----- scripts/build_directory_md.py | 2 +- sorts/dutch_national_flag_sort.py | 5 +- strings/barcode_validator.py | 3 +- strings/capitalize.py | 2 +- strings/is_spain_national_id.py | 3 +- strings/snake_case_to_camel_pascal_case.py | 8 +- web_programming/reddit.py | 3 +- web_programming/search_books_by_isbn.py | 3 +- web_programming/slack_message.py | 7 +- 67 files changed, 349 insertions(+), 223 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd5bca8f05ab..4c70ae219f74 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.269 + rev: v0.0.270 hooks: - id: ruff diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/arithmetic_analysis/jacobi_iteration_method.py index fe506a94a65d..17edf4bf4b8b 100644 --- a/arithmetic_analysis/jacobi_iteration_method.py +++ b/arithmetic_analysis/jacobi_iteration_method.py @@ -49,7 +49,9 @@ def jacobi_iteration_method( >>> constant = np.array([[2], [-6]]) >>> init_val = [0.5, -0.5, -0.5] >>> iterations = 3 - >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + >>> jacobi_iteration_method( + ... coefficient, constant, init_val, iterations + ... ) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: Coefficient and constant matrices dimensions must be nxn and nx1 but @@ -59,7 +61,9 @@ def jacobi_iteration_method( >>> constant = np.array([[2], [-6], [-4]]) >>> init_val = [0.5, -0.5] >>> iterations = 3 - >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + >>> jacobi_iteration_method( + ... coefficient, constant, init_val, iterations + ... ) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: Number of initial values must be equal to number of rows in coefficient @@ -79,24 +83,26 @@ def jacobi_iteration_method( rows2, cols2 = constant_matrix.shape if rows1 != cols1: - raise ValueError( - f"Coefficient matrix dimensions must be nxn but received {rows1}x{cols1}" - ) + msg = f"Coefficient matrix dimensions must be nxn but received {rows1}x{cols1}" + raise ValueError(msg) if cols2 != 1: - raise ValueError(f"Constant matrix must be nx1 but received {rows2}x{cols2}") + msg = f"Constant matrix must be nx1 but received {rows2}x{cols2}" + raise ValueError(msg) if rows1 != rows2: - raise ValueError( - f"""Coefficient and constant matrices dimensions must be nxn and nx1 but - received {rows1}x{cols1} and {rows2}x{cols2}""" + msg = ( + "Coefficient and constant matrices dimensions must be nxn and nx1 but " + f"received {rows1}x{cols1} and {rows2}x{cols2}" ) + raise ValueError(msg) if len(init_val) != rows1: - raise ValueError( - f"""Number of initial values must be equal to number of rows in coefficient - matrix but received {len(init_val)} and {rows1}""" + msg = ( + "Number of initial values must be equal to number of rows in coefficient " + f"matrix but received {len(init_val)} and {rows1}" ) + raise ValueError(msg) if iterations <= 0: raise ValueError("Iterations must be at least 1") diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index 941c1dadf556..eaabce5449c5 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -80,10 +80,11 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray # Ensure that table is a square array rows, columns = np.shape(table) if rows != columns: - raise ValueError( - f"'table' has to be of square shaped array but got a " + msg = ( + "'table' has to be of square shaped array but got a " f"{rows}x{columns} array:\n{table}" ) + raise ValueError(msg) lower = np.zeros((rows, columns)) upper = np.zeros((rows, columns)) diff --git a/audio_filters/iir_filter.py b/audio_filters/iir_filter.py index bd448175f6f3..f3c1ad43b001 100644 --- a/audio_filters/iir_filter.py +++ b/audio_filters/iir_filter.py @@ -50,16 +50,18 @@ def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None a_coeffs = [1.0, *a_coeffs] if len(a_coeffs) != self.order + 1: - raise ValueError( - f"Expected a_coeffs to have {self.order + 1} elements for {self.order}" - f"-order filter, got {len(a_coeffs)}" + msg = ( + f"Expected a_coeffs to have {self.order + 1} elements " + f"for {self.order}-order filter, got {len(a_coeffs)}" ) + raise ValueError(msg) if len(b_coeffs) != self.order + 1: - raise ValueError( - f"Expected b_coeffs to have {self.order + 1} elements for {self.order}" - f"-order filter, got {len(a_coeffs)}" + msg = ( + f"Expected b_coeffs to have {self.order + 1} elements " + f"for {self.order}-order filter, got {len(a_coeffs)}" ) + raise ValueError(msg) self.a_coeffs = a_coeffs self.b_coeffs = b_coeffs diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py index bb650ece3f5e..cc88307b7fe8 100644 --- a/backtracking/knight_tour.py +++ b/backtracking/knight_tour.py @@ -91,7 +91,8 @@ def open_knight_tour(n: int) -> list[list[int]]: return board board[i][j] = 0 - raise ValueError(f"Open Kight Tour cannot be performed on a board of size {n}") + msg = f"Open Kight Tour cannot be performed on a board of size {n}" + raise ValueError(msg) if __name__ == "__main__": diff --git a/bit_manipulation/reverse_bits.py b/bit_manipulation/reverse_bits.py index 55608ae12908..a8c77c11bfdd 100644 --- a/bit_manipulation/reverse_bits.py +++ b/bit_manipulation/reverse_bits.py @@ -14,10 +14,11 @@ def get_reverse_bit_string(number: int) -> str: TypeError: operation can not be conducted on a object of type str """ if not isinstance(number, int): - raise TypeError( + msg = ( "operation can not be conducted on a object of type " f"{type(number).__name__}" ) + raise TypeError(msg) bit_string = "" for _ in range(0, 32): bit_string += str(number % 2) diff --git a/ciphers/base64.py b/ciphers/base64.py index 38a952acc307..2b950b1be37d 100644 --- a/ciphers/base64.py +++ b/ciphers/base64.py @@ -34,9 +34,8 @@ def base64_encode(data: bytes) -> bytes: """ # 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__}'" - ) + msg = f"a bytes-like object is required, not '{data.__class__.__name__}'" + raise TypeError(msg) binary_stream = "".join(bin(byte)[2:].zfill(8) for byte in data) @@ -88,10 +87,11 @@ def base64_decode(encoded_data: str) -> bytes: """ # 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__}'" + msg = ( + "argument should be a bytes-like object or ASCII string, " + f"not '{encoded_data.__class__.__name__}'" ) + raise TypeError(msg) # In case encoded_data is a bytes-like object, make sure it contains only # ASCII characters so we convert it to a string object diff --git a/ciphers/beaufort_cipher.py b/ciphers/beaufort_cipher.py index 8eae847a7ff7..788fc72b89c3 100644 --- a/ciphers/beaufort_cipher.py +++ b/ciphers/beaufort_cipher.py @@ -5,7 +5,7 @@ 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)} +dict2 = dict(enumerate(ascii_uppercase)) # This function generates the key in diff --git a/ciphers/cryptomath_module.py b/ciphers/cryptomath_module.py index be8764ff38c3..6f15f7b733e6 100644 --- a/ciphers/cryptomath_module.py +++ b/ciphers/cryptomath_module.py @@ -6,7 +6,8 @@ def gcd(a: int, b: int) -> int: def find_mod_inverse(a: int, m: int) -> int: if gcd(a, m) != 1: - raise ValueError(f"mod inverse of {a!r} and {m!r} does not exist") + msg = f"mod inverse of {a!r} and {m!r} does not exist" + raise ValueError(msg) u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 07d21893f192..ec0d44e4a6c6 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -87,22 +87,20 @@ def _validator( # Checks if there are 3 unique rotors if (unique_rotsel := len(set(rotsel))) < 3: - raise Exception(f"Please use 3 unique rotors (not {unique_rotsel})") + msg = f"Please use 3 unique rotors (not {unique_rotsel})" + raise Exception(msg) # Checks if rotor positions are valid rotorpos1, rotorpos2, rotorpos3 = rotpos if not 0 < rotorpos1 <= len(abc): - raise ValueError( - "First rotor position is not within range of 1..26 (" f"{rotorpos1}" - ) + msg = f"First rotor position is not within range of 1..26 ({rotorpos1}" + raise ValueError(msg) if not 0 < rotorpos2 <= len(abc): - raise ValueError( - "Second rotor position is not within range of 1..26 (" f"{rotorpos2})" - ) + msg = f"Second rotor position is not within range of 1..26 ({rotorpos2})" + raise ValueError(msg) if not 0 < rotorpos3 <= len(abc): - raise ValueError( - "Third rotor position is not within range of 1..26 (" f"{rotorpos3})" - ) + msg = f"Third rotor position is not within range of 1..26 ({rotorpos3})" + raise ValueError(msg) # Validates string and returns dict pbdict = _plugboard(pb) @@ -130,9 +128,11 @@ def _plugboard(pbstring: str) -> dict[str, str]: # 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)})") + msg = f"Plugboard setting isn't type string ({type(pbstring)})" + raise TypeError(msg) elif len(pbstring) % 2 != 0: - raise Exception(f"Odd number of symbols ({len(pbstring)})") + msg = f"Odd number of symbols ({len(pbstring)})" + raise Exception(msg) elif pbstring == "": return {} @@ -142,9 +142,11 @@ def _plugboard(pbstring: str) -> dict[str, str]: tmppbl = set() for i in pbstring: if i not in abc: - raise Exception(f"'{i}' not in list of symbols") + msg = f"'{i}' not in list of symbols" + raise Exception(msg) elif i in tmppbl: - raise Exception(f"Duplicate symbol ({i})") + msg = f"Duplicate symbol ({i})" + raise Exception(msg) else: tmppbl.add(i) del tmppbl diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index f646d567b4c8..b4424e82298e 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -104,10 +104,11 @@ 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 " - f"w.r.t {req_l}.\nTry another key." + msg = ( + f"determinant modular {req_l} of encryption key({det}) " + f"is not co prime w.r.t {req_l}.\nTry another key." ) + raise ValueError(msg) def process_text(self, text: str) -> str: """ diff --git a/conversions/astronomical_length_scale_conversion.py b/conversions/astronomical_length_scale_conversion.py index 804d82487a25..0f413644906d 100644 --- a/conversions/astronomical_length_scale_conversion.py +++ b/conversions/astronomical_length_scale_conversion.py @@ -77,15 +77,17 @@ def length_conversion(value: float, from_type: str, to_type: str) -> float: to_sanitized = UNIT_SYMBOL.get(to_sanitized, to_sanitized) if from_sanitized not in METRIC_CONVERSION: - raise ValueError( + msg = ( f"Invalid 'from_type' value: {from_type!r}.\n" f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" ) + raise ValueError(msg) if to_sanitized not in METRIC_CONVERSION: - raise ValueError( + msg = ( f"Invalid 'to_type' value: {to_type!r}.\n" f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" ) + raise ValueError(msg) from_exponent = METRIC_CONVERSION[from_sanitized] to_exponent = METRIC_CONVERSION[to_sanitized] exponent = 1 diff --git a/conversions/length_conversion.py b/conversions/length_conversion.py index 790d9c116845..d8f39515255e 100644 --- a/conversions/length_conversion.py +++ b/conversions/length_conversion.py @@ -104,15 +104,17 @@ def length_conversion(value: float, from_type: str, to_type: str) -> float: new_to = to_type.lower().rstrip("s") new_to = TYPE_CONVERSION.get(new_to, new_to) if new_from not in METRIC_CONVERSION: - raise ValueError( + msg = ( f"Invalid 'from_type' value: {from_type!r}.\n" f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" ) + raise ValueError(msg) if new_to not in METRIC_CONVERSION: - raise ValueError( + msg = ( f"Invalid 'to_type' value: {to_type!r}.\n" f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" ) + raise ValueError(msg) return value * METRIC_CONVERSION[new_from].from_ * METRIC_CONVERSION[new_to].to diff --git a/conversions/speed_conversions.py b/conversions/speed_conversions.py index 62da9e137bc7..ba497119d3f5 100644 --- a/conversions/speed_conversions.py +++ b/conversions/speed_conversions.py @@ -57,10 +57,11 @@ def convert_speed(speed: float, unit_from: str, unit_to: str) -> float: 115.078 """ if unit_to not in speed_chart or unit_from not in speed_chart_inverse: - raise ValueError( + msg = ( f"Incorrect 'from_type' or 'to_type' value: {unit_from!r}, {unit_to!r}\n" f"Valid values are: {', '.join(speed_chart_inverse)}" ) + raise ValueError(msg) return round(speed * speed_chart[unit_from] * speed_chart_inverse[unit_to], 3) diff --git a/conversions/weight_conversion.py b/conversions/weight_conversion.py index 5c032a497a7b..e8326e0b688f 100644 --- a/conversions/weight_conversion.py +++ b/conversions/weight_conversion.py @@ -299,10 +299,11 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float: 1.999999998903455 """ if to_type not in KILOGRAM_CHART or from_type not in WEIGHT_TYPE_CHART: - raise ValueError( + msg = ( f"Invalid 'from_type' or 'to_type' value: {from_type!r}, {to_type!r}\n" f"Supported values are: {', '.join(WEIGHT_TYPE_CHART)}" ) + raise ValueError(msg) return value * KILOGRAM_CHART[to_type] * WEIGHT_TYPE_CHART[from_type] diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index 97eb8e25bedd..b5b983b9ba4c 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -77,7 +77,8 @@ def _put(self, node: Node | None, label: int, parent: Node | None = None) -> Nod elif label > node.label: node.right = self._put(node.right, label, node) else: - raise Exception(f"Node with label {label} already exists") + msg = f"Node with label {label} already exists" + raise Exception(msg) return node @@ -100,7 +101,8 @@ def search(self, label: int) -> Node: def _search(self, node: Node | None, label: int) -> Node: if node is None: - raise Exception(f"Node with label {label} does not exist") + msg = f"Node with label {label} does not exist" + raise Exception(msg) else: if label < node.label: node = self._search(node.left, label) diff --git a/data_structures/binary_tree/binary_tree_mirror.py b/data_structures/binary_tree/binary_tree_mirror.py index 1ef950ad62d7..b8548f4ec515 100644 --- a/data_structures/binary_tree/binary_tree_mirror.py +++ b/data_structures/binary_tree/binary_tree_mirror.py @@ -31,7 +31,8 @@ def binary_tree_mirror(binary_tree: dict, root: int = 1) -> dict: 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") + msg = f"root {root} is not present in the binary_tree" + raise ValueError(msg) binary_tree_mirror_dictionary = dict(binary_tree) binary_tree_mirror_dict(binary_tree_mirror_dictionary, root) return binary_tree_mirror_dictionary diff --git a/data_structures/disjoint_set/disjoint_set.py b/data_structures/disjoint_set/disjoint_set.py index f8500bf2c3af..12dafb2d935e 100644 --- a/data_structures/disjoint_set/disjoint_set.py +++ b/data_structures/disjoint_set/disjoint_set.py @@ -56,7 +56,8 @@ def find_python_set(node: Node) -> set: for s in sets: if node.data in s: return s - raise ValueError(f"{node.data} is not in {sets}") + msg = f"{node.data} is not in {sets}" + raise ValueError(msg) def test_disjoint_set() -> None: diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 9092fb29e3ff..325d91026137 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -94,25 +94,25 @@ def test_circular_linked_list() -> None: try: circular_linked_list.delete_front() - raise AssertionError() # This should not happen + raise AssertionError # This should not happen except IndexError: assert True # This should happen try: circular_linked_list.delete_tail() - raise AssertionError() # This should not happen + raise AssertionError # This should not happen except IndexError: assert True # This should happen try: circular_linked_list.delete_nth(-1) - raise AssertionError() + raise AssertionError except IndexError: assert True try: circular_linked_list.delete_nth(0) - raise AssertionError() + raise AssertionError except IndexError: assert True diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 69763d12da15..1a6c48191c4e 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -198,13 +198,13 @@ def test_doubly_linked_list() -> None: try: linked_list.delete_head() - raise AssertionError() # This should not happen. + raise AssertionError # This should not happen. except IndexError: assert True # This should happen. try: linked_list.delete_tail() - raise AssertionError() # This should not happen. + raise AssertionError # This should not happen. except IndexError: assert True # This should happen. diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index a8f9e8ebb977..890e21c9b404 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -353,13 +353,13 @@ def test_singly_linked_list() -> None: try: linked_list.delete_head() - raise AssertionError() # This should not happen. + raise AssertionError # This should not happen. except IndexError: assert True # This should happen. try: linked_list.delete_tail() - raise AssertionError() # This should not happen. + raise AssertionError # This should not happen. except IndexError: assert True # This should happen. diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 55d424d5018b..a14f4648a399 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -92,13 +92,13 @@ def test_stack() -> None: try: _ = stack.pop() - raise AssertionError() # This should not happen + raise AssertionError # This should not happen except StackUnderflowError: assert True # This should happen try: _ = stack.peek() - raise AssertionError() # This should not happen + raise AssertionError # This should not happen except StackUnderflowError: assert True # This should happen @@ -118,7 +118,7 @@ def test_stack() -> None: try: stack.push(200) - raise AssertionError() # This should not happen + raise AssertionError # This should not happen except StackOverflowError: assert True # This should happen diff --git a/digital_image_processing/dithering/burkes.py b/digital_image_processing/dithering/burkes.py index 2bf0bbe03225..0804104abe58 100644 --- a/digital_image_processing/dithering/burkes.py +++ b/digital_image_processing/dithering/burkes.py @@ -21,7 +21,8 @@ def __init__(self, input_img, threshold: int): 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}") + msg = f"Factor value should be from 0 to {self.max_threshold}" + raise ValueError(msg) self.input_img = input_img self.threshold = threshold diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 39e78be04a71..1ad933417da6 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -174,12 +174,12 @@ def _validate_input(points: list[Point] | list[list[float]]) -> list[Point]: """ if not hasattr(points, "__iter__"): - raise ValueError( - f"Expecting an iterable object but got an non-iterable type {points}" - ) + msg = f"Expecting an iterable object but got an non-iterable type {points}" + raise ValueError(msg) if not points: - raise ValueError(f"Expecting a list of points but got {points}") + msg = f"Expecting a list of points but got {points}" + raise ValueError(msg) return _construct_points(points) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index b12d30313e31..489b5ada450a 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -78,17 +78,18 @@ def knapsack_with_example_solution(w: int, wt: list, val: list): 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 " - f"got {num_items} weights and {len(val)} values" + msg = ( + "The number of weights must be the same as the number of values.\n" + f"But got {num_items} weights and {len(val)} values" ) + raise ValueError(msg) for i in range(num_items): if not isinstance(wt[i], int): - raise TypeError( - "All weights must be integers but " - f"got weight of type {type(wt[i])} at index {i}" + msg = ( + "All weights must be integers but got weight of " + f"type {type(wt[i])} at index {i}" ) + raise TypeError(msg) optimal_val, dp_table = knapsack(w, wt, val, num_items) example_optional_set: set = set() diff --git a/dynamic_programming/minimum_steps_to_one.py b/dynamic_programming/minimum_steps_to_one.py index f4eb7033dd20..8785027fbff3 100644 --- a/dynamic_programming/minimum_steps_to_one.py +++ b/dynamic_programming/minimum_steps_to_one.py @@ -42,7 +42,8 @@ def min_steps_to_one(number: int) -> int: """ if number <= 0: - raise ValueError(f"n must be greater than 0. Got n = {number}") + msg = f"n must be greater than 0. Got n = {number}" + raise ValueError(msg) table = [number + 1] * (number + 1) diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index 79104d8f4044..f80fa440ae86 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -177,13 +177,15 @@ def _enforce_args(n: int, prices: list): the rod """ if n < 0: - raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") + msg = f"n must be greater than or equal to 0. Got n = {n}" + raise ValueError(msg) if n > len(prices): - raise ValueError( - "Each integral piece of rod must have a corresponding " - f"price. Got n = {n} but length of prices = {len(prices)}" + msg = ( + "Each integral piece of rod must have a corresponding price. " + f"Got n = {n} but length of prices = {len(prices)}" ) + raise ValueError(msg) def main(): diff --git a/dynamic_programming/viterbi.py b/dynamic_programming/viterbi.py index 93ab845e2ae8..764d45dc2c05 100644 --- a/dynamic_programming/viterbi.py +++ b/dynamic_programming/viterbi.py @@ -297,11 +297,13 @@ def _validate_list(_object: Any, var_name: str) -> None: """ if not isinstance(_object, list): - raise ValueError(f"{var_name} must be a list") + msg = f"{var_name} must be a list" + raise ValueError(msg) else: for x in _object: if not isinstance(x, str): - raise ValueError(f"{var_name} must be a list of strings") + msg = f"{var_name} must be a list of strings" + raise ValueError(msg) def _validate_dicts( @@ -384,14 +386,15 @@ def _validate_dict( ValueError: mock_name nested dictionary all values must be float """ if not isinstance(_object, dict): - raise ValueError(f"{var_name} must be a dict") + msg = f"{var_name} must be a dict" + raise ValueError(msg) if not all(isinstance(x, str) for x in _object): - raise ValueError(f"{var_name} all keys must be strings") + msg = f"{var_name} all keys must be strings" + raise ValueError(msg) if not all(isinstance(x, value_type) for x in _object.values()): nested_text = "nested dictionary " if nested else "" - raise ValueError( - f"{var_name} {nested_text}all values must be {value_type.__name__}" - ) + msg = f"{var_name} {nested_text}all values must be {value_type.__name__}" + raise ValueError(msg) if __name__ == "__main__": diff --git a/electronics/resistor_equivalence.py b/electronics/resistor_equivalence.py index 7142f838a065..55e7f2d6b5d2 100644 --- a/electronics/resistor_equivalence.py +++ b/electronics/resistor_equivalence.py @@ -23,7 +23,8 @@ def resistor_parallel(resistors: list[float]) -> float: index = 0 for resistor in resistors: if resistor <= 0: - raise ValueError(f"Resistor at index {index} has a negative or zero value!") + msg = f"Resistor at index {index} has a negative or zero value!" + raise ValueError(msg) first_sum += 1 / float(resistor) index += 1 return 1 / first_sum @@ -47,7 +48,8 @@ def resistor_series(resistors: list[float]) -> float: for resistor in resistors: sum_r += resistor if resistor < 0: - raise ValueError(f"Resistor at index {index} has a negative value!") + msg = f"Resistor at index {index} has a negative value!" + raise ValueError(msg) index += 1 return sum_r diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index 388e7219f54b..089c5c99a1ec 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -96,13 +96,13 @@ def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int, # 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}") + msg = f"{N_POPULATION} must be bigger than {N_SELECTED}" + raise ValueError(msg) # 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" - ) + msg = f"{not_in_genes_list} is not in genes list, evolution cannot converge" + raise ValueError(msg) # Generate random starting population. population = [] diff --git a/graphics/vector3_for_2d_rendering.py b/graphics/vector3_for_2d_rendering.py index dfa22262a8d8..a332206e67b6 100644 --- a/graphics/vector3_for_2d_rendering.py +++ b/graphics/vector3_for_2d_rendering.py @@ -28,9 +28,8 @@ def convert_to_2d( 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())}" - ) + msg = f"Input values must either be float or int: {list(locals().values())}" + raise TypeError(msg) projected_x = ((x * distance) / (z + distance)) * scale projected_y = ((y * distance) / (z + distance)) * scale return projected_x, projected_y @@ -71,10 +70,11 @@ def rotate( input_variables = locals() del input_variables["axis"] if not all(isinstance(val, (float, int)) for val in input_variables.values()): - raise TypeError( + msg = ( "Input values except axis must either be float or int: " f"{list(input_variables.values())}" ) + raise TypeError(msg) angle = (angle % 360) / 450 * 180 / math.pi if axis == "z": new_x = x * math.cos(angle) - y * math.sin(angle) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index cb21076f91d2..d489b110b3a7 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -73,9 +73,10 @@ def shortest_path(self, target_vertex: str) -> str: target_vertex_parent = self.parent.get(target_vertex) if target_vertex_parent is None: - raise ValueError( + msg = ( f"No path from vertex: {self.source_vertex} to vertex: {target_vertex}" ) + raise ValueError(msg) return self.shortest_path(target_vertex_parent) + f"->{target_vertex}" diff --git a/linear_algebra/src/schur_complement.py b/linear_algebra/src/schur_complement.py index 3a5f4443afd3..750f4de5e397 100644 --- a/linear_algebra/src/schur_complement.py +++ b/linear_algebra/src/schur_complement.py @@ -31,16 +31,18 @@ def schur_complement( shape_c = np.shape(mat_c) if shape_a[0] != shape_b[0]: - raise ValueError( - f"Expected the same number of rows for A and B. \ - Instead found A of size {shape_a} and B of size {shape_b}" + msg = ( + "Expected the same number of rows for A and B. " + f"Instead found A of size {shape_a} and B of size {shape_b}" ) + raise ValueError(msg) if shape_b[1] != shape_c[1]: - raise ValueError( - f"Expected the same number of columns for B and C. \ - Instead found B of size {shape_b} and C of size {shape_c}" + msg = ( + "Expected the same number of columns for B and C. " + f"Instead found B of size {shape_b} and C of size {shape_c}" ) + raise ValueError(msg) a_inv = pseudo_inv if a_inv is None: diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py index 72979181f67c..7a23ec463c8f 100644 --- a/machine_learning/similarity_search.py +++ b/machine_learning/similarity_search.py @@ -97,26 +97,29 @@ def similarity_search( """ if dataset.ndim != value_array.ndim: - raise ValueError( - f"Wrong input data's dimensions... dataset : {dataset.ndim}, " - f"value_array : {value_array.ndim}" + msg = ( + "Wrong input data's dimensions... " + f"dataset : {dataset.ndim}, value_array : {value_array.ndim}" ) + raise ValueError(msg) 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]}" + msg = ( + "Wrong input data's shape... " + f"dataset : {dataset.shape[1]}, value_array : {value_array.shape[1]}" ) + raise ValueError(msg) 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}" + msg = ( + "Input data have different datatype... " + f"dataset : {dataset.dtype}, value_array : {value_array.dtype}" ) + raise TypeError(msg) answer = [] diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py index df854cc850b1..24046115ebc4 100644 --- a/machine_learning/support_vector_machines.py +++ b/machine_learning/support_vector_machines.py @@ -74,7 +74,8 @@ def __init__( # sklear: def_gamma = 1/(n_features * X.var()) (wiki) # previously it was 1/(n_features) else: - raise ValueError(f"Unknown kernel: {kernel}") + msg = f"Unknown kernel: {kernel}" + raise ValueError(msg) # kernels def __linear(self, vector1: ndarray, vector2: ndarray) -> float: diff --git a/maths/3n_plus_1.py b/maths/3n_plus_1.py index 59fdec48e100..f9f6dfeb9faa 100644 --- a/maths/3n_plus_1.py +++ b/maths/3n_plus_1.py @@ -9,9 +9,11 @@ def n31(a: int) -> tuple[list[int], int]: """ if not isinstance(a, int): - raise TypeError(f"Must be int, not {type(a).__name__}") + msg = f"Must be int, not {type(a).__name__}" + raise TypeError(msg) if a < 1: - raise ValueError(f"Given integer must be positive, not {a}") + msg = f"Given integer must be positive, not {a}" + raise ValueError(msg) path = [a] while a != 1: diff --git a/maths/automorphic_number.py b/maths/automorphic_number.py index 103fc7301831..8ed9375632a4 100644 --- a/maths/automorphic_number.py +++ b/maths/automorphic_number.py @@ -40,7 +40,8 @@ def is_automorphic_number(number: int) -> bool: TypeError: Input value of [number=5.0] must be an integer """ if not isinstance(number, int): - raise TypeError(f"Input value of [number={number}] must be an integer") + msg = f"Input value of [number={number}] must be an integer" + raise TypeError(msg) if number < 0: return False number_square = number * number diff --git a/maths/catalan_number.py b/maths/catalan_number.py index 85607dc1eca4..20c2cfb17c06 100644 --- a/maths/catalan_number.py +++ b/maths/catalan_number.py @@ -31,10 +31,12 @@ def catalan(number: int) -> int: """ if not isinstance(number, int): - raise TypeError(f"Input value of [number={number}] must be an integer") + msg = f"Input value of [number={number}] must be an integer" + raise TypeError(msg) if number < 1: - raise ValueError(f"Input value of [number={number}] must be > 0") + msg = f"Input value of [number={number}] must be > 0" + raise ValueError(msg) current_number = 1 diff --git a/maths/dual_number_automatic_differentiation.py b/maths/dual_number_automatic_differentiation.py index 9aa75830c4a1..f98997c8be4d 100644 --- a/maths/dual_number_automatic_differentiation.py +++ b/maths/dual_number_automatic_differentiation.py @@ -71,7 +71,7 @@ def __truediv__(self, other): for i in self.duals: new_duals.append(i / other) return Dual(self.real / other, new_duals) - raise ValueError() + raise ValueError def __floordiv__(self, other): if not isinstance(other, Dual): @@ -79,7 +79,7 @@ def __floordiv__(self, other): for i in self.duals: new_duals.append(i // other) return Dual(self.real // other, new_duals) - raise ValueError() + raise ValueError def __pow__(self, n): if n < 0 or isinstance(n, float): diff --git a/maths/hexagonal_number.py b/maths/hexagonal_number.py index 28735c638f80..3677ab95ee00 100644 --- a/maths/hexagonal_number.py +++ b/maths/hexagonal_number.py @@ -36,7 +36,8 @@ def hexagonal(number: int) -> int: TypeError: Input value of [number=11.0] must be an integer """ if not isinstance(number, int): - raise TypeError(f"Input value of [number={number}] must be an integer") + msg = f"Input value of [number={number}] must be an integer" + raise TypeError(msg) if number < 1: raise ValueError("Input must be a positive integer") return number * (2 * number - 1) diff --git a/maths/juggler_sequence.py b/maths/juggler_sequence.py index 9daba8bc0e8a..7f65d1dff925 100644 --- a/maths/juggler_sequence.py +++ b/maths/juggler_sequence.py @@ -40,9 +40,11 @@ def juggler_sequence(number: int) -> list[int]: ValueError: Input value of [number=-1] must be a positive integer """ if not isinstance(number, int): - raise TypeError(f"Input value of [number={number}] must be an integer") + msg = f"Input value of [number={number}] must be an integer" + raise TypeError(msg) if number < 1: - raise ValueError(f"Input value of [number={number}] must be a positive integer") + msg = f"Input value of [number={number}] must be a positive integer" + raise ValueError(msg) sequence = [number] while number != 1: if number % 2 == 0: diff --git a/maths/liouville_lambda.py b/maths/liouville_lambda.py index 5993efa42d66..1ed228dd5434 100644 --- a/maths/liouville_lambda.py +++ b/maths/liouville_lambda.py @@ -33,7 +33,8 @@ def liouville_lambda(number: int) -> int: TypeError: Input value of [number=11.0] must be an integer """ if not isinstance(number, int): - raise TypeError(f"Input value of [number={number}] must be an integer") + msg = f"Input value of [number={number}] must be an integer" + raise TypeError(msg) if number < 1: raise ValueError("Input must be a positive integer") return -1 if len(prime_factors(number)) % 2 else 1 diff --git a/maths/manhattan_distance.py b/maths/manhattan_distance.py index 2711d4c8ccd6..413991468a49 100644 --- a/maths/manhattan_distance.py +++ b/maths/manhattan_distance.py @@ -15,15 +15,15 @@ def manhattan_distance(point_a: list, point_b: list) -> float: 9.0 >>> manhattan_distance([1,1], None) Traceback (most recent call last): - ... + ... ValueError: Missing an input >>> manhattan_distance([1,1], [2, 2, 2]) Traceback (most recent call last): - ... + ... ValueError: Both points must be in the same n-dimensional space >>> manhattan_distance([1,"one"], [2, 2, 2]) Traceback (most recent call last): - ... + ... TypeError: Expected a list of numbers as input, found str >>> manhattan_distance(1, [2, 2, 2]) Traceback (most recent call last): @@ -66,14 +66,14 @@ def _validate_point(point: list[float]) -> None: if isinstance(point, list): for item in point: if not isinstance(item, (int, float)): - raise TypeError( - f"Expected a list of numbers as input, " - f"found {type(item).__name__}" + msg = ( + "Expected a list of numbers as input, found " + f"{type(item).__name__}" ) + raise TypeError(msg) else: - raise TypeError( - f"Expected a list of numbers as input, found {type(point).__name__}" - ) + msg = f"Expected a list of numbers as input, found {type(point).__name__}" + raise TypeError(msg) else: raise ValueError("Missing an input") diff --git a/maths/pronic_number.py b/maths/pronic_number.py index 8b554dbbd602..cf4d3d2eb24b 100644 --- a/maths/pronic_number.py +++ b/maths/pronic_number.py @@ -41,7 +41,8 @@ def is_pronic(number: int) -> bool: TypeError: Input value of [number=6.0] must be an integer """ if not isinstance(number, int): - raise TypeError(f"Input value of [number={number}] must be an integer") + msg = f"Input value of [number={number}] must be an integer" + raise TypeError(msg) if number < 0 or number % 2 == 1: return False number_sqrt = int(number**0.5) diff --git a/maths/proth_number.py b/maths/proth_number.py index ce911473a2d2..47747ed260f7 100644 --- a/maths/proth_number.py +++ b/maths/proth_number.py @@ -29,10 +29,12 @@ def proth(number: int) -> int: """ if not isinstance(number, int): - raise TypeError(f"Input value of [number={number}] must be an integer") + msg = f"Input value of [number={number}] must be an integer" + raise TypeError(msg) if number < 1: - raise ValueError(f"Input value of [number={number}] must be > 0") + msg = f"Input value of [number={number}] must be > 0" + raise ValueError(msg) elif number == 1: return 3 elif number == 2: diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index af98f24f9538..2c5cdc004d1d 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -167,7 +167,7 @@ def __str__(self): f"{coef}*x^{i}" for coef, i in enumerate(self.product) ) - return "\n".join((a, b, c)) + return f"{a}\n{b}\n{c}" # Unit tests diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index 3cd6ce0b4d9d..a0520aa5cf50 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -34,7 +34,8 @@ def prime_sieve(num: int) -> list[int]: """ if num <= 0: - raise ValueError(f"{num}: Invalid input, please enter a positive integer.") + msg = f"{num}: Invalid input, please enter a positive integer." + raise ValueError(msg) sieve = [True] * (num + 1) prime = [] diff --git a/maths/sylvester_sequence.py b/maths/sylvester_sequence.py index 114c9dd58582..607424c6a90b 100644 --- a/maths/sylvester_sequence.py +++ b/maths/sylvester_sequence.py @@ -31,7 +31,8 @@ def sylvester(number: int) -> int: if number == 1: return 2 elif number < 1: - raise ValueError(f"The input value of [n={number}] has to be > 0") + msg = f"The input value of [n={number}] has to be > 0" + raise ValueError(msg) else: num = sylvester(number - 1) lower = num - 1 diff --git a/maths/twin_prime.py b/maths/twin_prime.py index e6ac0cc7805b..912b10b366c0 100644 --- a/maths/twin_prime.py +++ b/maths/twin_prime.py @@ -32,7 +32,8 @@ def twin_prime(number: int) -> int: TypeError: Input value of [number=6.0] must be an integer """ if not isinstance(number, int): - raise TypeError(f"Input value of [number={number}] must be an integer") + msg = f"Input value of [number={number}] must be an integer" + raise TypeError(msg) if is_prime(number) and is_prime(number + 2): return number + 2 else: diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 576094902af4..f189f1898d33 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -70,10 +70,11 @@ def multiply(matrix_a: list[list[int]], matrix_b: list[list[int]]) -> list[list[ 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]})" + msg = ( + "Cannot multiply matrix of dimensions " + f"({rows[0]},{cols[0]}) and ({rows[1]},{cols[1]})" ) + raise ValueError(msg) return [ [sum(m * n for m, n in zip(i, j)) for j in zip(*matrix_b)] for i in matrix_a ] @@ -174,10 +175,11 @@ def _verify_matrix_sizes( ) -> tuple[tuple[int, int], tuple[int, int]]: 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 " + msg = ( + "operands could not be broadcast together with shape " f"({shape[0], shape[1]}), ({shape[2], shape[3]})" ) + raise ValueError(msg) return (shape[0], shape[2]), (shape[1], shape[3]) diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 39eddfed81f3..256271e8a87d 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -173,7 +173,8 @@ def __mul__(self, another: int | float | Matrix) -> Matrix: result[r, c] += self[r, i] * another[i, c] return result else: - raise TypeError(f"Unsupported type given for another ({type(another)})") + msg = f"Unsupported type given for another ({type(another)})" + raise TypeError(msg) def transpose(self) -> Matrix: """ diff --git a/neural_network/input_data.py b/neural_network/input_data.py index 2a32f0b82c37..94c018ece9ba 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -198,10 +198,7 @@ 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 + fake_label = [1] + [0] * 9 if self.one_hot else 0 return ( [fake_image for _ in range(batch_size)], [fake_label for _ in range(batch_size)], @@ -324,10 +321,11 @@ def fake(): test_labels = _extract_labels(f, one_hot=one_hot) if not 0 <= validation_size <= len(train_images): - raise ValueError( - f"Validation size should be between 0 and {len(train_images)}. " - f"Received: {validation_size}." + msg = ( + "Validation size should be between 0 and " + f"{len(train_images)}. Received: {validation_size}." ) + raise ValueError(msg) validation_images = train_images[:validation_size] validation_labels = train_labels[:validation_size] diff --git a/other/nested_brackets.py b/other/nested_brackets.py index ea48c0a5f532..19c6dd53c8b2 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -18,7 +18,7 @@ def is_balanced(s): stack = [] open_brackets = set({"(", "[", "{"}) closed_brackets = set({")", "]", "}"}) - open_to_closed = dict({"{": "}", "[": "]", "(": ")"}) + open_to_closed = {"{": "}", "[": "]", "(": ")"} for i in range(len(s)): if s[i] in open_brackets: diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py index 8e04a8f30dd7..af04f432e433 100644 --- a/other/scoring_algorithm.py +++ b/other/scoring_algorithm.py @@ -68,7 +68,8 @@ def calculate_each_score( # weight not 0 or 1 else: - raise ValueError(f"Invalid weight of {weight:f} provided") + msg = f"Invalid weight of {weight:f} provided" + raise ValueError(msg) score_lists.append(score) diff --git a/project_euler/problem_054/sol1.py b/project_euler/problem_054/sol1.py index 9af7aef5a716..74409f32c712 100644 --- a/project_euler/problem_054/sol1.py +++ b/project_euler/problem_054/sol1.py @@ -119,10 +119,12 @@ def __init__(self, hand: str) -> None: For example: "6S 4C KC AS TH" """ if not isinstance(hand, str): - raise TypeError(f"Hand should be of type 'str': {hand!r}") + msg = f"Hand should be of type 'str': {hand!r}" + raise TypeError(msg) # 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}") + msg = f"Hand should contain only 5 cards: {hand!r}" + raise ValueError(msg) self._hand = hand self._first_pair = 0 self._second_pair = 0 diff --git a/project_euler/problem_068/sol1.py b/project_euler/problem_068/sol1.py index 772be359f630..cf814b001d57 100644 --- a/project_euler/problem_068/sol1.py +++ b/project_euler/problem_068/sol1.py @@ -73,7 +73,8 @@ def solution(gon_side: int = 5) -> int: if is_magic_gon(numbers): return int("".join(str(n) for n in numbers)) - raise ValueError(f"Magic {gon_side}-gon ring is impossible") + msg = f"Magic {gon_side}-gon ring is impossible" + raise ValueError(msg) def generate_gon_ring(gon_side: int, perm: list[int]) -> list[int]: diff --git a/project_euler/problem_131/sol1.py b/project_euler/problem_131/sol1.py index f5302aac8644..be3ea9c81ae4 100644 --- a/project_euler/problem_131/sol1.py +++ b/project_euler/problem_131/sol1.py @@ -26,10 +26,7 @@ def is_prime(number: int) -> bool: False """ - for divisor in range(2, isqrt(number) + 1): - if number % divisor == 0: - return False - return True + return all(number % divisor != 0 for divisor in range(2, isqrt(number) + 1)) def solution(max_prime: int = 10**6) -> int: diff --git a/pyproject.toml b/pyproject.toml index 48c3fbd4009d..a526196685f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,45 +17,88 @@ ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,sec skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" [tool.ruff] -ignore = [ # `ruff rule S101` for a description of that rule - "B904", # B904: Within an `except` clause, raise exceptions with `raise ... from err` - "B905", # B905: `zip()` without an explicit `strict=` parameter - "E741", # E741: Ambiguous variable name 'l' - "G004", # G004 Logging statement uses f-string - "N999", # N999: Invalid module name - "PLC1901", # PLC1901: `{}` can be simplified to `{}` as an empty string is falsey - "PLR2004", # PLR2004: Magic value used in comparison - "PLR5501", # PLR5501: Consider using `elif` instead of `else` - "PLW0120", # PLW0120: `else` clause on loop without a `break` statement - "PLW060", # PLW060: Using global for `{name}` but no assignment is done -- DO NOT FIX - "PLW2901", # PLW2901: Redefined loop variable - "RUF00", # RUF00: Ambiguous unicode character -- DO NOT FIX - "RUF100", # RUF100: Unused `noqa` directive - "S101", # S101: Use of `assert` detected -- DO NOT FIX - "S105", # S105: Possible hardcoded password: 'password' - "S113", # S113: Probable use of requests call without timeout - "S311", # S311: Standard pseudo-random generators are not suitable for cryptographic purposes - "UP038", # UP038: Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX +ignore = [ # `ruff rule S101` for a description of that rule + "ARG001", # Unused function argument `amount` -- FIX ME? + "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME + "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME + "DTZ001", # The use of `datetime.datetime()` without `tzinfo` argument is not allowed -- FIX ME + "DTZ005", # The use of `datetime.datetime.now()` without `tzinfo` argument is not allowed -- FIX ME + "E741", # Ambiguous variable name 'l' -- FIX ME + "EM101", # Exception must not use a string literal, assign to variable first + "EXE001", # Shebang is present but file is not executable" -- FIX ME + "G004", # Logging statement uses f-string + "ICN001", # `matplotlib.pyplot` should be imported as `plt` -- FIX ME + "INP001", # File `x/y/z.py` is part of an implicit namespace package. Add an `__init__.py`. -- FIX ME + "N999", # Invalid module name -- FIX ME + "NPY002", # Replace legacy `np.random.choice` call with `np.random.Generator` -- FIX ME + "PGH003", # Use specific rule codes when ignoring type issues -- FIX ME + "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey + "PLR5501", # Consider using `elif` instead of `else` -- FIX ME + "PLW0120", # `else` clause on loop without a `break` statement -- FIX ME + "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX + "PLW2901", # PLW2901: Redefined loop variable -- FIX ME + "RUF00", # Ambiguous unicode character and other rules + "RUF100", # Unused `noqa` directive -- FIX ME + "S101", # Use of `assert` detected -- DO NOT FIX + "S105", # Possible hardcoded password: 'password' + "S113", # Probable use of requests call without timeout -- FIX ME + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME + "SIM102", # Use a single `if` statement instead of nested `if` statements -- FIX ME + "SLF001", # Private member accessed: `_Iterator` -- FIX ME + "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX ] -select = [ # https://beta.ruff.rs/docs/rules - "A", # A: builtins - "B", # B: bugbear - "C40", # C40: comprehensions - "C90", # C90: mccabe code complexity - "E", # E: pycodestyle errors - "F", # F: pyflakes - "G", # G: logging format - "I", # I: isort - "N", # N: pep8 naming - "PL", # PL: pylint - "PIE", # PIE: pie - "PYI", # PYI: type hinting stub files - "RUF", # RUF: ruff - "S", # S: bandit - "TID", # TID: tidy imports - "UP", # UP: pyupgrade - "W", # W: pycodestyle warnings - "YTT", # YTT: year 2020 +select = [ # https://beta.ruff.rs/docs/rules + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "ASYNC", # flake8-async + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "C90", # McCabe cyclomatic complexity + "DTZ", # flake8-datetimez + "E", # pycodestyle + "EM", # flake8-errmsg + "EXE", # flake8-executable + "F", # Pyflakes + "FA", # flake8-future-annotations + "FLY", # flynt + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "INT", # flake8-gettext + "N", # pep8-naming + "NPY", # NumPy-specific rules + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # Pylint + "PYI", # flake8-pyi + "RSE", # flake8-raise + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify + "SLF", # flake8-self + "T10", # flake8-debugger + "TD", # flake8-todos + "TID", # flake8-tidy-imports + "UP", # pyupgrade + "W", # pycodestyle + "YTT", # flake8-2020 + # "ANN", # flake8-annotations # FIX ME? + # "COM", # flake8-commas + # "D", # pydocstyle -- FIX ME? + # "DJ", # flake8-django + # "ERA", # eradicate -- DO NOT FIX + # "FBT", # flake8-boolean-trap # FIX ME + # "ISC", # flake8-implicit-str-concat # FIX ME + # "PD", # pandas-vet + # "PT", # flake8-pytest-style + # "PTH", # flake8-use-pathlib # FIX ME + # "Q", # flake8-quotes + # "RET", # flake8-return # FIX ME? + # "T20", # flake8-print + # "TCH", # flake8-type-checking + # "TRY", # tryceratops ] show-source = true target-version = "py311" @@ -63,7 +106,27 @@ target-version = "py311" [tool.ruff.mccabe] # DO NOT INCREASE THIS VALUE max-complexity = 17 # default: 10 +[tool.ruff.per-file-ignores] +"arithmetic_analysis/newton_raphson.py" = ["PGH001"] +"audio_filters/show_response.py" = ["ARG002"] +"data_structures/binary_tree/binary_search_tree_recursive.py" = ["BLE001"] +"data_structures/binary_tree/treap.py" = ["SIM114"] +"data_structures/hashing/hash_table.py" = ["ARG002"] +"data_structures/hashing/quadratic_probing.py" = ["ARG002"] +"data_structures/hashing/tests/test_hash_map.py" = ["BLE001"] +"data_structures/heap/max_heap.py" = ["SIM114"] +"graphs/minimum_spanning_tree_prims.py" = ["SIM114"] +"hashes/enigma_machine.py" = ["BLE001"] +"machine_learning/decision_tree.py" = ["SIM114"] +"machine_learning/linear_discriminant_analysis.py" = ["ARG005"] +"machine_learning/sequential_minimum_optimization.py" = ["SIM115"] +"matrix/sherman_morrison.py" = ["SIM103", "SIM114"] +"physics/newtons_second_law_of_motion.py" = ["BLE001"] +"project_euler/problem_099/sol1.py" = ["SIM115"] +"sorts/external_sort.py" = ["SIM115"] + [tool.ruff.pylint] # DO NOT INCREASE THESE VALUES +allow-magic-value-types = ["float", "int", "str"] max-args = 10 # default: 5 max-branches = 20 # default: 12 max-returns = 8 # default: 6 diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index b95be9ebc254..24bc00cd036f 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -33,7 +33,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((filepath, filename)).replace(" ", "%20") + url = f"{filepath}/{filename}".replace(" ", "%20") filename = os.path.splitext(filename.replace("_", " ").title())[0] print(f"{md_prefix(indent)} [{filename}]({url})") diff --git a/sorts/dutch_national_flag_sort.py b/sorts/dutch_national_flag_sort.py index 79afefa73afe..758e3a887b84 100644 --- a/sorts/dutch_national_flag_sort.py +++ b/sorts/dutch_national_flag_sort.py @@ -84,9 +84,8 @@ def dutch_national_flag_sort(sequence: list) -> list: sequence[mid], sequence[high] = sequence[high], sequence[mid] high -= 1 else: - raise ValueError( - f"The elements inside the sequence must contains only {colors} values" - ) + msg = f"The elements inside the sequence must contains only {colors} values" + raise ValueError(msg) return sequence diff --git a/strings/barcode_validator.py b/strings/barcode_validator.py index e050cd337d74..b4f3864e2642 100644 --- a/strings/barcode_validator.py +++ b/strings/barcode_validator.py @@ -65,7 +65,8 @@ def get_barcode(barcode: str) -> int: ValueError: Barcode 'dwefgiweuf' has alphabetic characters. """ if str(barcode).isalpha(): - raise ValueError(f"Barcode '{barcode}' has alphabetic characters.") + msg = f"Barcode '{barcode}' has alphabetic characters." + raise ValueError(msg) elif int(barcode) < 0: raise ValueError("The entered barcode has a negative value. Try again.") else: diff --git a/strings/capitalize.py b/strings/capitalize.py index 63603aa07e2d..e7e97c2beb53 100644 --- a/strings/capitalize.py +++ b/strings/capitalize.py @@ -17,7 +17,7 @@ def capitalize(sentence: str) -> str: """ if not sentence: return "" - lower_to_upper = {lc: uc for lc, uc in zip(ascii_lowercase, ascii_uppercase)} + lower_to_upper = dict(zip(ascii_lowercase, ascii_uppercase)) return lower_to_upper.get(sentence[0], sentence[0]) + sentence[1:] diff --git a/strings/is_spain_national_id.py b/strings/is_spain_national_id.py index 67f49755f412..60d06e123aae 100644 --- a/strings/is_spain_national_id.py +++ b/strings/is_spain_national_id.py @@ -48,7 +48,8 @@ def is_spain_national_id(spanish_id: str) -> bool: """ if not isinstance(spanish_id, str): - raise TypeError(f"Expected string as input, found {type(spanish_id).__name__}") + msg = f"Expected string as input, found {type(spanish_id).__name__}" + raise TypeError(msg) spanish_id_clean = spanish_id.replace("-", "").upper() if len(spanish_id_clean) != 9: diff --git a/strings/snake_case_to_camel_pascal_case.py b/strings/snake_case_to_camel_pascal_case.py index 28a28b517a01..8219337a63b0 100644 --- a/strings/snake_case_to_camel_pascal_case.py +++ b/strings/snake_case_to_camel_pascal_case.py @@ -27,11 +27,11 @@ def snake_to_camel_case(input_str: str, use_pascal: bool = False) -> str: """ if not isinstance(input_str, str): - raise ValueError(f"Expected string as input, found {type(input_str)}") + msg = f"Expected string as input, found {type(input_str)}" + raise ValueError(msg) if not isinstance(use_pascal, bool): - raise ValueError( - f"Expected boolean as use_pascal parameter, found {type(use_pascal)}" - ) + msg = f"Expected boolean as use_pascal parameter, found {type(use_pascal)}" + raise ValueError(msg) words = input_str.split("_") diff --git a/web_programming/reddit.py b/web_programming/reddit.py index 6a31c81c34bd..5ca5f828c0fb 100644 --- a/web_programming/reddit.py +++ b/web_programming/reddit.py @@ -26,7 +26,8 @@ def get_subreddit_data( """ wanted_data = wanted_data or [] if invalid_search_terms := ", ".join(sorted(set(wanted_data) - valid_terms)): - raise ValueError(f"Invalid search term: {invalid_search_terms}") + msg = f"Invalid search term: {invalid_search_terms}" + raise ValueError(msg) response = requests.get( f"https://reddit.com/r/%7Bsubreddit%7D/%7Bage%7D.json?limit={limit}", headers={"User-agent": "A random string"}, diff --git a/web_programming/search_books_by_isbn.py b/web_programming/search_books_by_isbn.py index abac3c70b22e..d5d4cfe92f20 100644 --- a/web_programming/search_books_by_isbn.py +++ b/web_programming/search_books_by_isbn.py @@ -22,7 +22,8 @@ def get_openlibrary_data(olid: str = "isbn/0140328726") -> dict: """ new_olid = olid.strip().strip("/") # Remove leading/trailing whitespace & slashes if new_olid.count("/") != 1: - raise ValueError(f"{olid} is not a valid Open Library olid") + msg = f"{olid} is not a valid Open Library olid" + raise ValueError(msg) return requests.get(f"https://openlibrary.org/%7Bnew_olid%7D.json").json() diff --git a/web_programming/slack_message.py b/web_programming/slack_message.py index f35aa3ca587e..5e97d6b64c75 100644 --- a/web_programming/slack_message.py +++ b/web_programming/slack_message.py @@ -7,10 +7,11 @@ 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}" + msg = ( + "Request to slack returned an error " + f"{response.status_code}, the response is:\n{response.text}" ) + raise ValueError(msg) if __name__ == "__main__": From c93659d7ce65e3717f06333e3d049ebaa888e597 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 29 May 2023 17:37:54 -0700 Subject: [PATCH 2196/2908] Fix type error in `strassen_matrix_multiplication.py` (#8784) * Fix type error in strassen_matrix_multiplication.py * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + ...ion.py.BROKEN => strassen_matrix_multiplication.py} | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) rename divide_and_conquer/{strassen_matrix_multiplication.py.BROKEN => strassen_matrix_multiplication.py} (97%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 11ff93c91430..231b0e2f1d2f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -294,6 +294,7 @@ * [Mergesort](divide_and_conquer/mergesort.py) * [Peak](divide_and_conquer/peak.py) * [Power](divide_and_conquer/power.py) + * [Strassen Matrix Multiplication](divide_and_conquer/strassen_matrix_multiplication.py) ## Dynamic Programming * [Abbreviation](dynamic_programming/abbreviation.py) diff --git a/divide_and_conquer/strassen_matrix_multiplication.py.BROKEN b/divide_and_conquer/strassen_matrix_multiplication.py similarity index 97% rename from divide_and_conquer/strassen_matrix_multiplication.py.BROKEN rename to divide_and_conquer/strassen_matrix_multiplication.py index 2ca91c63bf4c..cbfc7e5655db 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py.BROKEN +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -112,17 +112,19 @@ def strassen(matrix1: list, matrix2: list) -> list: [[139, 163], [121, 134], [100, 121]] """ if matrix_dimensions(matrix1)[1] != matrix_dimensions(matrix2)[0]: - raise Exception( - "Unable to multiply these matrices, please check the dimensions. \n" - f"Matrix A:{matrix1} \nMatrix B:{matrix2}" + msg = ( + "Unable to multiply these matrices, please check the dimensions.\n" + f"Matrix A: {matrix1}\n" + f"Matrix B: {matrix2}" ) + raise Exception(msg) dimension1 = matrix_dimensions(matrix1) dimension2 = matrix_dimensions(matrix2) if dimension1[0] == dimension1[1] and dimension2[0] == dimension2[1]: return [matrix1, matrix2] - maximum = max(dimension1, dimension2) + maximum = max(*dimension1, *dimension2) maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) new_matrix1 = matrix1 new_matrix2 = matrix2 From 4a27b544303e6bab90ed57b72fa3acf3d785429e Mon Sep 17 00:00:00 2001 From: Sundaram Kumar Jha Date: Wed, 31 May 2023 06:26:59 +0530 Subject: [PATCH 2197/2908] Update permutations.py (#8102) --- data_structures/arrays/permutations.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/data_structures/arrays/permutations.py b/data_structures/arrays/permutations.py index eb3f26517863..4558bd8d468a 100644 --- a/data_structures/arrays/permutations.py +++ b/data_structures/arrays/permutations.py @@ -1,7 +1,6 @@ def permute(nums: list[int]) -> list[list[int]]: """ Return all permutations. - >>> from itertools import permutations >>> numbers= [1,2,3] >>> all(list(nums) in permute(numbers) for nums in permutations(numbers)) @@ -20,7 +19,32 @@ def permute(nums: list[int]) -> list[list[int]]: return result +def permute2(nums): + """ + Return all permutations of the given list. + + >>> permute2([1, 2, 3]) + [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 2, 1], [3, 1, 2]] + """ + + def backtrack(start): + if start == len(nums) - 1: + output.append(nums[:]) + else: + for i in range(start, len(nums)): + nums[start], nums[i] = nums[i], nums[start] + backtrack(start + 1) + nums[start], nums[i] = nums[i], nums[start] # backtrack + + output = [] + backtrack(0) + return output + + if __name__ == "__main__": import doctest + # use res to print the data in permute2 function + res = permute2([1, 2, 3]) + print(res) doctest.testmod() From e871540e37b834673f9e6650b8e2281d7d36a8c3 Mon Sep 17 00:00:00 2001 From: Rudransh Bhardwaj <115872354+rudransh61@users.noreply.github.com> Date: Wed, 31 May 2023 20:33:02 +0530 Subject: [PATCH 2198/2908] Added rank of matrix in linear algebra (#8687) * Added rank of matrix in linear algebra * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Corrected name of function * Corrected Rank_of_Matrix.py * Completed rank_of_matrix.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * delete to rename Rank_of_Matrix.py * created rank_of_matrix * added more doctests in rank_of_matrix.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed some issues in rank_of_matrix.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added moreeee doctestsss in rank_of_mtrix.py and fixed some bugss * Update linear_algebra/src/rank_of_matrix.py Co-authored-by: Christian Clauss * Update linear_algebra/src/rank_of_matrix.py Co-authored-by: Christian Clauss * Update linear_algebra/src/rank_of_matrix.py Co-authored-by: Christian Clauss * Update rank_of_matrix.py * Update linear_algebra/src/rank_of_matrix.py Co-authored-by: Caeden Perelli-Harris --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Caeden Perelli-Harris --- linear_algebra/src/rank_of_matrix.py | 89 ++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 linear_algebra/src/rank_of_matrix.py diff --git a/linear_algebra/src/rank_of_matrix.py b/linear_algebra/src/rank_of_matrix.py new file mode 100644 index 000000000000..7ff3c1699a69 --- /dev/null +++ b/linear_algebra/src/rank_of_matrix.py @@ -0,0 +1,89 @@ +""" +Calculate the rank of a matrix. + +See: https://en.wikipedia.org/wiki/Rank_(linear_algebra) +""" + + +def rank_of_matrix(matrix: list[list[int | float]]) -> int: + """ + Finds the rank of a matrix. + Args: + matrix: The matrix as a list of lists. + Returns: + The rank of the matrix. + Example: + >>> matrix1 = [[1, 2, 3], + ... [4, 5, 6], + ... [7, 8, 9]] + >>> rank_of_matrix(matrix1) + 2 + >>> matrix2 = [[1, 0, 0], + ... [0, 1, 0], + ... [0, 0, 0]] + >>> rank_of_matrix(matrix2) + 2 + >>> matrix3 = [[1, 2, 3, 4], + ... [5, 6, 7, 8], + ... [9, 10, 11, 12]] + >>> rank_of_matrix(matrix3) + 2 + >>> rank_of_matrix([[2,3,-1,-1], + ... [1,-1,-2,4], + ... [3,1,3,-2], + ... [6,3,0,-7]]) + 4 + >>> rank_of_matrix([[2,1,-3,-6], + ... [3,-3,1,2], + ... [1,1,1,2]]) + 3 + >>> rank_of_matrix([[2,-1,0], + ... [1,3,4], + ... [4,1,-3]]) + 3 + >>> rank_of_matrix([[3,2,1], + ... [-6,-4,-2]]) + 1 + >>> rank_of_matrix([[],[]]) + 0 + >>> rank_of_matrix([[1]]) + 1 + >>> rank_of_matrix([[]]) + 0 + """ + + rows = len(matrix) + columns = len(matrix[0]) + rank = min(rows, columns) + + for row in range(rank): + # Check if diagonal element is not zero + if matrix[row][row] != 0: + # Eliminate all the elements below the diagonal + for col in range(row + 1, rows): + multiplier = matrix[col][row] / matrix[row][row] + for i in range(row, columns): + matrix[col][i] -= multiplier * matrix[row][i] + else: + # Find a non-zero diagonal element to swap rows + reduce = True + for i in range(row + 1, rows): + if matrix[i][row] != 0: + matrix[row], matrix[i] = matrix[i], matrix[row] + reduce = False + break + if reduce: + rank -= 1 + for i in range(rows): + matrix[i][row] = matrix[i][rank] + + # Reduce the row pointer by one to stay on the same row + row -= 1 + + return rank + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 4621b0bb4f5d3fff2fa4f0e53d6cb862fe002c60 Mon Sep 17 00:00:00 2001 From: nith2001 <75632283+nith2001@users.noreply.github.com> Date: Wed, 31 May 2023 13:06:12 -0700 Subject: [PATCH 2199/2908] Improved Graph Implementations (#8730) * Improved Graph Implementations Provides new implementation for graph_list.py and graph_matrix.py along with pytest suites for each. Fixes #8709 * Graph implementation style fixes, corrections, and refactored tests * Helpful docs about graph implementation * Refactored code to separate files and applied enumerate() * Renamed files and refactored code to fail fast * Error handling style fix * Fixed f-string code quality issue * Last f-string fix * Added return types to test functions and more style fixes * Added more function return types * Added more function return types pt2 * Fixed error messages --- graphs/graph_adjacency_list.py | 589 ++++++++++++++++++++++++++++++ graphs/graph_adjacency_matrix.py | 608 +++++++++++++++++++++++++++++++ graphs/graph_matrix.py | 24 -- graphs/tests/__init__.py | 0 4 files changed, 1197 insertions(+), 24 deletions(-) create mode 100644 graphs/graph_adjacency_list.py create mode 100644 graphs/graph_adjacency_matrix.py delete mode 100644 graphs/graph_matrix.py create mode 100644 graphs/tests/__init__.py diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py new file mode 100644 index 000000000000..76f34f845860 --- /dev/null +++ b/graphs/graph_adjacency_list.py @@ -0,0 +1,589 @@ +#!/usr/bin/env python3 +""" +Author: Vikram Nithyanandam + +Description: +The following implementation is a robust unweighted Graph data structure +implemented using an adjacency list. This vertices and edges of this graph can be +effectively initialized and modified while storing your chosen generic +value in each vertex. + +Adjacency List: https://en.wikipedia.org/wiki/Adjacency_list + +Potential Future Ideas: +- Add a flag to set edge weights on and set edge weights +- Make edge weights and vertex values customizable to store whatever the client wants +- Support multigraph functionality if the client wants it +""" +from __future__ import annotations + +import random +import unittest +from pprint import pformat +from typing import Generic, TypeVar + +T = TypeVar("T") + + +class GraphAdjacencyList(Generic[T]): + def __init__( + self, vertices: list[T], edges: list[list[T]], directed: bool = True + ) -> None: + """ + Parameters: + - vertices: (list[T]) The list of vertex names the client wants to + pass in. Default is empty. + - edges: (list[list[T]]) The list of edges the client wants to + pass in. Each edge is a 2-element list. Default is empty. + - directed: (bool) Indicates if graph is directed or undirected. + Default is True. + """ + self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T + self.directed = directed + + # Falsey checks + edges = edges or [] + vertices = vertices or [] + + for vertex in vertices: + self.add_vertex(vertex) + + for edge in edges: + if len(edge) != 2: + msg = f"Invalid input: {edge} is the wrong length." + raise ValueError(msg) + self.add_edge(edge[0], edge[1]) + + def add_vertex(self, vertex: T) -> None: + """ + Adds a vertex to the graph. If the given vertex already exists, + a ValueError will be thrown. + """ + if self.contains_vertex(vertex): + msg = f"Incorrect input: {vertex} is already in the graph." + raise ValueError(msg) + self.adj_list[vertex] = [] + + def add_edge(self, source_vertex: T, destination_vertex: T) -> None: + """ + Creates an edge from source vertex to destination vertex. If any + given vertex doesn't exist or the edge already exists, a ValueError + will be thrown. + """ + if not ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + ): + msg = ( + f"Incorrect input: Either {source_vertex} or " + f"{destination_vertex} does not exist" + ) + raise ValueError(msg) + if self.contains_edge(source_vertex, destination_vertex): + msg = ( + "Incorrect input: The edge already exists between " + f"{source_vertex} and {destination_vertex}" + ) + raise ValueError(msg) + + # add the destination vertex to the list associated with the source vertex + # and vice versa if not directed + self.adj_list[source_vertex].append(destination_vertex) + if not self.directed: + self.adj_list[destination_vertex].append(source_vertex) + + def remove_vertex(self, vertex: T) -> None: + """ + Removes the given vertex from the graph and deletes all incoming and + outgoing edges from the given vertex as well. If the given vertex + does not exist, a ValueError will be thrown. + """ + if not self.contains_vertex(vertex): + msg = f"Incorrect input: {vertex} does not exist in this graph." + raise ValueError(msg) + + if not self.directed: + # If not directed, find all neighboring vertices and delete all references + # of edges connecting to the given vertex + for neighbor in self.adj_list[vertex]: + self.adj_list[neighbor].remove(vertex) + else: + # If directed, search all neighbors of all vertices and delete all + # references of edges connecting to the given vertex + for edge_list in self.adj_list.values(): + if vertex in edge_list: + edge_list.remove(vertex) + + # Finally, delete the given vertex and all of its outgoing edge references + self.adj_list.pop(vertex) + + def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: + """ + Removes the edge between the two vertices. If any given vertex + doesn't exist or the edge does not exist, a ValueError will be thrown. + """ + if not ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + ): + msg = ( + f"Incorrect input: Either {source_vertex} or " + f"{destination_vertex} does not exist" + ) + raise ValueError(msg) + if not self.contains_edge(source_vertex, destination_vertex): + msg = ( + "Incorrect input: The edge does NOT exist between " + f"{source_vertex} and {destination_vertex}" + ) + raise ValueError(msg) + + # remove the destination vertex from the list associated with the source + # vertex and vice versa if not directed + self.adj_list[source_vertex].remove(destination_vertex) + if not self.directed: + self.adj_list[destination_vertex].remove(source_vertex) + + def contains_vertex(self, vertex: T) -> bool: + """ + Returns True if the graph contains the vertex, False otherwise. + """ + return vertex in self.adj_list + + def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: + """ + Returns True if the graph contains the edge from the source_vertex to the + destination_vertex, False otherwise. If any given vertex doesn't exist, a + ValueError will be thrown. + """ + if not ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + ): + msg = ( + f"Incorrect input: Either {source_vertex} " + f"or {destination_vertex} does not exist." + ) + raise ValueError(msg) + + return destination_vertex in self.adj_list[source_vertex] + + def clear_graph(self) -> None: + """ + Clears all vertices and edges. + """ + self.adj_list = {} + + def __repr__(self) -> str: + return pformat(self.adj_list) + + +class TestGraphAdjacencyList(unittest.TestCase): + def __assert_graph_edge_exists_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + edge: list[int], + ) -> None: + self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_edge_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + edge: list[int], + ) -> None: + self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_vertex_exists_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + vertex: int, + ) -> None: + self.assertTrue(undirected_graph.contains_vertex(vertex)) + self.assertTrue(directed_graph.contains_vertex(vertex)) + + def __assert_graph_vertex_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + vertex: int, + ) -> None: + self.assertFalse(undirected_graph.contains_vertex(vertex)) + self.assertFalse(directed_graph.contains_vertex(vertex)) + + def __generate_random_edges( + self, vertices: list[int], edge_pick_count: int + ) -> list[list[int]]: + self.assertTrue(edge_pick_count <= len(vertices)) + + random_source_vertices: list[int] = random.sample( + vertices[0 : int(len(vertices) / 2)], edge_pick_count + ) + random_destination_vertices: list[int] = random.sample( + vertices[int(len(vertices) / 2) :], edge_pick_count + ) + random_edges: list[list[int]] = [] + + for source in random_source_vertices: + for dest in random_destination_vertices: + random_edges.append([source, dest]) + + return random_edges + + def __generate_graphs( + self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int + ) -> tuple[GraphAdjacencyList, GraphAdjacencyList, list[int], list[list[int]]]: + if max_val - min_val + 1 < vertex_count: + raise ValueError( + "Will result in duplicate vertices. Either increase range " + "between min_val and max_val or decrease vertex count." + ) + + # generate graph input + random_vertices: list[int] = random.sample( + range(min_val, max_val + 1), vertex_count + ) + random_edges: list[list[int]] = self.__generate_random_edges( + random_vertices, edge_pick_count + ) + + # build graphs + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=random_edges, directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=random_edges, directed=True + ) + + return undirected_graph, directed_graph, random_vertices, random_edges + + def test_init_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # test graph initialization with vertices and edges + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + self.assertFalse(undirected_graph.directed) + self.assertTrue(directed_graph.directed) + + def test_contains_vertex(self) -> None: + random_vertices: list[int] = random.sample(range(101), 20) + + # Build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # Test contains_vertex + for num in range(101): + self.assertEqual( + num in random_vertices, undirected_graph.contains_vertex(num) + ) + self.assertEqual( + num in random_vertices, directed_graph.contains_vertex(num) + ) + + def test_add_vertices(self) -> None: + random_vertices: list[int] = random.sample(range(101), 20) + + # build empty graphs + undirected_graph: GraphAdjacencyList = GraphAdjacencyList( + vertices=[], edges=[], directed=False + ) + directed_graph: GraphAdjacencyList = GraphAdjacencyList( + vertices=[], edges=[], directed=True + ) + + # run add_vertex + for num in random_vertices: + undirected_graph.add_vertex(num) + + for num in random_vertices: + directed_graph.add_vertex(num) + + # test add_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + def test_remove_vertices(self) -> None: + random_vertices: list[int] = random.sample(range(101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # test remove_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + undirected_graph.remove_vertex(num) + directed_graph.remove_vertex(num) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, num + ) + + def test_add_and_remove_vertices_repeatedly(self) -> None: + random_vertices1: list[int] = random.sample(range(51), 20) + random_vertices2: list[int] = random.sample(range(51, 101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices1, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices1, edges=[], directed=True + ) + + # test adding and removing vertices + for i, _ in enumerate(random_vertices1): + undirected_graph.add_vertex(random_vertices2[i]) + directed_graph.add_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + undirected_graph.remove_vertex(random_vertices1[i]) + directed_graph.remove_vertex(random_vertices1[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices1[i] + ) + + # remove all vertices + for i, _ in enumerate(random_vertices1): + undirected_graph.remove_vertex(random_vertices2[i]) + directed_graph.remove_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + def test_contains_edge(self) -> None: + # generate graphs and graph input + vertex_count = 20 + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(vertex_count, 0, 100, 4) + + # generate all possible edges for testing + all_possible_edges: list[list[int]] = [] + for i in range(vertex_count - 1): + for j in range(i + 1, vertex_count): + all_possible_edges.append([random_vertices[i], random_vertices[j]]) + all_possible_edges.append([random_vertices[j], random_vertices[i]]) + + # test contains_edge function + for edge in all_possible_edges: + if edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + elif [edge[1], edge[0]] in random_edges: + # since this edge exists for undirected but the reverse + # may not exist for directed + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, [edge[1], edge[0]] + ) + else: + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_edge(self) -> None: + # generate graph input + random_vertices: list[int] = random.sample(range(101), 15) + random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # run and test add_edge + for edge in random_edges: + undirected_graph.add_edge(edge[0], edge[1]) + directed_graph.add_edge(edge[0], edge[1]) + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + def test_remove_edge(self) -> None: + # generate graph input and graphs + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # run and test remove_edge + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + undirected_graph.remove_edge(edge[0], edge[1]) + directed_graph.remove_edge(edge[0], edge[1]) + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_and_remove_edges_repeatedly(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # make some more edge options! + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for i, _ in enumerate(random_edges): + undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, more_random_edges[i] + ) + + undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, random_edges[i] + ) + + def test_add_vertex_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.add_vertex(vertex) + with self.assertRaises(ValueError): + directed_graph.add_vertex(vertex) + + def test_remove_vertex_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for i in range(101): + if i not in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.remove_vertex(i) + with self.assertRaises(ValueError): + directed_graph.remove_vertex(i) + + def test_add_edge_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for edge in random_edges: + with self.assertRaises(ValueError): + undirected_graph.add_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.add_edge(edge[0], edge[1]) + + def test_remove_edge_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for edge in more_random_edges: + with self.assertRaises(ValueError): + undirected_graph.remove_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.remove_edge(edge[0], edge[1]) + + def test_contains_edge_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.contains_edge(vertex, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(vertex, 102) + + with self.assertRaises(ValueError): + undirected_graph.contains_edge(103, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(103, 102) + + +if __name__ == "__main__": + unittest.main() diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py new file mode 100644 index 000000000000..4d2e02f737f9 --- /dev/null +++ b/graphs/graph_adjacency_matrix.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python3 +""" +Author: Vikram Nithyanandam + +Description: +The following implementation is a robust unweighted Graph data structure +implemented using an adjacency matrix. This vertices and edges of this graph can be +effectively initialized and modified while storing your chosen generic +value in each vertex. + +Adjacency Matrix: https://mathworld.wolfram.com/AdjacencyMatrix.html + +Potential Future Ideas: +- Add a flag to set edge weights on and set edge weights +- Make edge weights and vertex values customizable to store whatever the client wants +- Support multigraph functionality if the client wants it +""" +from __future__ import annotations + +import random +import unittest +from pprint import pformat +from typing import Generic, TypeVar + +T = TypeVar("T") + + +class GraphAdjacencyMatrix(Generic[T]): + def __init__( + self, vertices: list[T], edges: list[list[T]], directed: bool = True + ) -> None: + """ + Parameters: + - vertices: (list[T]) The list of vertex names the client wants to + pass in. Default is empty. + - edges: (list[list[T]]) The list of edges the client wants to + pass in. Each edge is a 2-element list. Default is empty. + - directed: (bool) Indicates if graph is directed or undirected. + Default is True. + """ + self.directed = directed + self.vertex_to_index: dict[T, int] = {} + self.adj_matrix: list[list[int]] = [] + + # Falsey checks + edges = edges or [] + vertices = vertices or [] + + for vertex in vertices: + self.add_vertex(vertex) + + for edge in edges: + if len(edge) != 2: + msg = f"Invalid input: {edge} must have length 2." + raise ValueError(msg) + self.add_edge(edge[0], edge[1]) + + def add_edge(self, source_vertex: T, destination_vertex: T) -> None: + """ + Creates an edge from source vertex to destination vertex. If any + given vertex doesn't exist or the edge already exists, a ValueError + will be thrown. + """ + if not ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + ): + msg = ( + f"Incorrect input: Either {source_vertex} or " + f"{destination_vertex} does not exist" + ) + raise ValueError(msg) + if self.contains_edge(source_vertex, destination_vertex): + msg = ( + "Incorrect input: The edge already exists between " + f"{source_vertex} and {destination_vertex}" + ) + raise ValueError(msg) + + # Get the indices of the corresponding vertices and set their edge value to 1. + u: int = self.vertex_to_index[source_vertex] + v: int = self.vertex_to_index[destination_vertex] + self.adj_matrix[u][v] = 1 + if not self.directed: + self.adj_matrix[v][u] = 1 + + def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: + """ + Removes the edge between the two vertices. If any given vertex + doesn't exist or the edge does not exist, a ValueError will be thrown. + """ + if not ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + ): + msg = ( + f"Incorrect input: Either {source_vertex} or " + f"{destination_vertex} does not exist" + ) + raise ValueError(msg) + if not self.contains_edge(source_vertex, destination_vertex): + msg = ( + "Incorrect input: The edge does NOT exist between " + f"{source_vertex} and {destination_vertex}" + ) + raise ValueError(msg) + + # Get the indices of the corresponding vertices and set their edge value to 0. + u: int = self.vertex_to_index[source_vertex] + v: int = self.vertex_to_index[destination_vertex] + self.adj_matrix[u][v] = 0 + if not self.directed: + self.adj_matrix[v][u] = 0 + + def add_vertex(self, vertex: T) -> None: + """ + Adds a vertex to the graph. If the given vertex already exists, + a ValueError will be thrown. + """ + if self.contains_vertex(vertex): + msg = f"Incorrect input: {vertex} already exists in this graph." + raise ValueError(msg) + + # build column for vertex + for row in self.adj_matrix: + row.append(0) + + # build row for vertex and update other data structures + self.adj_matrix.append([0] * (len(self.adj_matrix) + 1)) + self.vertex_to_index[vertex] = len(self.adj_matrix) - 1 + + def remove_vertex(self, vertex: T) -> None: + """ + Removes the given vertex from the graph and deletes all incoming and + outgoing edges from the given vertex as well. If the given vertex + does not exist, a ValueError will be thrown. + """ + if not self.contains_vertex(vertex): + msg = f"Incorrect input: {vertex} does not exist in this graph." + raise ValueError(msg) + + # first slide up the rows by deleting the row corresponding to + # the vertex being deleted. + start_index = self.vertex_to_index[vertex] + self.adj_matrix.pop(start_index) + + # next, slide the columns to the left by deleting the values in + # the column corresponding to the vertex being deleted + for lst in self.adj_matrix: + lst.pop(start_index) + + # final clean up + self.vertex_to_index.pop(vertex) + + # decrement indices for vertices shifted by the deleted vertex in the adj matrix + for vertex in self.vertex_to_index: + if self.vertex_to_index[vertex] >= start_index: + self.vertex_to_index[vertex] = self.vertex_to_index[vertex] - 1 + + def contains_vertex(self, vertex: T) -> bool: + """ + Returns True if the graph contains the vertex, False otherwise. + """ + return vertex in self.vertex_to_index + + def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: + """ + Returns True if the graph contains the edge from the source_vertex to the + destination_vertex, False otherwise. If any given vertex doesn't exist, a + ValueError will be thrown. + """ + if not ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + ): + msg = ( + f"Incorrect input: Either {source_vertex} " + f"or {destination_vertex} does not exist." + ) + raise ValueError(msg) + + u = self.vertex_to_index[source_vertex] + v = self.vertex_to_index[destination_vertex] + return self.adj_matrix[u][v] == 1 + + def clear_graph(self) -> None: + """ + Clears all vertices and edges. + """ + self.vertex_to_index = {} + self.adj_matrix = [] + + def __repr__(self) -> str: + first = "Adj Matrix:\n" + pformat(self.adj_matrix) + second = "\nVertex to index mapping:\n" + pformat(self.vertex_to_index) + return first + second + + +class TestGraphMatrix(unittest.TestCase): + def __assert_graph_edge_exists_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + edge: list[int], + ) -> None: + self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_edge_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + edge: list[int], + ) -> None: + self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_vertex_exists_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + vertex: int, + ) -> None: + self.assertTrue(undirected_graph.contains_vertex(vertex)) + self.assertTrue(directed_graph.contains_vertex(vertex)) + + def __assert_graph_vertex_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + vertex: int, + ) -> None: + self.assertFalse(undirected_graph.contains_vertex(vertex)) + self.assertFalse(directed_graph.contains_vertex(vertex)) + + def __generate_random_edges( + self, vertices: list[int], edge_pick_count: int + ) -> list[list[int]]: + self.assertTrue(edge_pick_count <= len(vertices)) + + random_source_vertices: list[int] = random.sample( + vertices[0 : int(len(vertices) / 2)], edge_pick_count + ) + random_destination_vertices: list[int] = random.sample( + vertices[int(len(vertices) / 2) :], edge_pick_count + ) + random_edges: list[list[int]] = [] + + for source in random_source_vertices: + for dest in random_destination_vertices: + random_edges.append([source, dest]) + + return random_edges + + def __generate_graphs( + self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int + ) -> tuple[GraphAdjacencyMatrix, GraphAdjacencyMatrix, list[int], list[list[int]]]: + if max_val - min_val + 1 < vertex_count: + raise ValueError( + "Will result in duplicate vertices. Either increase " + "range between min_val and max_val or decrease vertex count" + ) + + # generate graph input + random_vertices: list[int] = random.sample( + range(min_val, max_val + 1), vertex_count + ) + random_edges: list[list[int]] = self.__generate_random_edges( + random_vertices, edge_pick_count + ) + + # build graphs + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=random_edges, directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=random_edges, directed=True + ) + + return undirected_graph, directed_graph, random_vertices, random_edges + + def test_init_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # test graph initialization with vertices and edges + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + self.assertFalse(undirected_graph.directed) + self.assertTrue(directed_graph.directed) + + def test_contains_vertex(self) -> None: + random_vertices: list[int] = random.sample(range(101), 20) + + # Build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=True + ) + + # Test contains_vertex + for num in range(101): + self.assertEqual( + num in random_vertices, undirected_graph.contains_vertex(num) + ) + self.assertEqual( + num in random_vertices, directed_graph.contains_vertex(num) + ) + + def test_add_vertices(self) -> None: + random_vertices: list[int] = random.sample(range(101), 20) + + # build empty graphs + undirected_graph: GraphAdjacencyMatrix = GraphAdjacencyMatrix( + vertices=[], edges=[], directed=False + ) + directed_graph: GraphAdjacencyMatrix = GraphAdjacencyMatrix( + vertices=[], edges=[], directed=True + ) + + # run add_vertex + for num in random_vertices: + undirected_graph.add_vertex(num) + + for num in random_vertices: + directed_graph.add_vertex(num) + + # test add_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + def test_remove_vertices(self) -> None: + random_vertices: list[int] = random.sample(range(101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=True + ) + + # test remove_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + undirected_graph.remove_vertex(num) + directed_graph.remove_vertex(num) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, num + ) + + def test_add_and_remove_vertices_repeatedly(self) -> None: + random_vertices1: list[int] = random.sample(range(51), 20) + random_vertices2: list[int] = random.sample(range(51, 101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices1, edges=[], directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices1, edges=[], directed=True + ) + + # test adding and removing vertices + for i, _ in enumerate(random_vertices1): + undirected_graph.add_vertex(random_vertices2[i]) + directed_graph.add_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + undirected_graph.remove_vertex(random_vertices1[i]) + directed_graph.remove_vertex(random_vertices1[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices1[i] + ) + + # remove all vertices + for i, _ in enumerate(random_vertices1): + undirected_graph.remove_vertex(random_vertices2[i]) + directed_graph.remove_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + def test_contains_edge(self) -> None: + # generate graphs and graph input + vertex_count = 20 + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(vertex_count, 0, 100, 4) + + # generate all possible edges for testing + all_possible_edges: list[list[int]] = [] + for i in range(vertex_count - 1): + for j in range(i + 1, vertex_count): + all_possible_edges.append([random_vertices[i], random_vertices[j]]) + all_possible_edges.append([random_vertices[j], random_vertices[i]]) + + # test contains_edge function + for edge in all_possible_edges: + if edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + elif [edge[1], edge[0]] in random_edges: + # since this edge exists for undirected but the reverse may + # not exist for directed + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, [edge[1], edge[0]] + ) + else: + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_edge(self) -> None: + # generate graph input + random_vertices: list[int] = random.sample(range(101), 15) + random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=True + ) + + # run and test add_edge + for edge in random_edges: + undirected_graph.add_edge(edge[0], edge[1]) + directed_graph.add_edge(edge[0], edge[1]) + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + def test_remove_edge(self) -> None: + # generate graph input and graphs + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # run and test remove_edge + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + undirected_graph.remove_edge(edge[0], edge[1]) + directed_graph.remove_edge(edge[0], edge[1]) + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_and_remove_edges_repeatedly(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # make some more edge options! + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for i, _ in enumerate(random_edges): + undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, more_random_edges[i] + ) + + undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, random_edges[i] + ) + + def test_add_vertex_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.add_vertex(vertex) + with self.assertRaises(ValueError): + directed_graph.add_vertex(vertex) + + def test_remove_vertex_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for i in range(101): + if i not in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.remove_vertex(i) + with self.assertRaises(ValueError): + directed_graph.remove_vertex(i) + + def test_add_edge_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for edge in random_edges: + with self.assertRaises(ValueError): + undirected_graph.add_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.add_edge(edge[0], edge[1]) + + def test_remove_edge_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for edge in more_random_edges: + with self.assertRaises(ValueError): + undirected_graph.remove_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.remove_edge(edge[0], edge[1]) + + def test_contains_edge_exception_check(self) -> None: + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.contains_edge(vertex, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(vertex, 102) + + with self.assertRaises(ValueError): + undirected_graph.contains_edge(103, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(103, 102) + + +if __name__ == "__main__": + unittest.main() diff --git a/graphs/graph_matrix.py b/graphs/graph_matrix.py deleted file mode 100644 index 4adc6c0bb93b..000000000000 --- a/graphs/graph_matrix.py +++ /dev/null @@ -1,24 +0,0 @@ -class Graph: - def __init__(self, vertex): - self.vertex = vertex - self.graph = [[0] * vertex for i in range(vertex)] - - def add_edge(self, u, v): - self.graph[u - 1][v - 1] = 1 - self.graph[v - 1][u - 1] = 1 - - def show(self): - for i in self.graph: - for j in i: - 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.show() diff --git a/graphs/tests/__init__.py b/graphs/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 From 3a9e5fa5ecea0df54ed3ffdcb74f46171199f552 Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Fri, 2 Jun 2023 17:14:25 +1200 Subject: [PATCH 2200/2908] Create a Simultaneous Equation Solver Algorithm (#8773) * Added simultaneous_linear_equation_solver.py * Removed Augment class, replaced with recursive functions * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed edge cases * Update settings.json --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .vscode/settings.json | 5 + maths/simultaneous_linear_equation_solver.py | 142 +++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 maths/simultaneous_linear_equation_solver.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000000..ef16fa1aa7ac --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "master" + ] +} diff --git a/maths/simultaneous_linear_equation_solver.py b/maths/simultaneous_linear_equation_solver.py new file mode 100644 index 000000000000..1287b2002d00 --- /dev/null +++ b/maths/simultaneous_linear_equation_solver.py @@ -0,0 +1,142 @@ +""" +https://en.wikipedia.org/wiki/Augmented_matrix + +This algorithm solves simultaneous linear equations of the form +λa + λb + λc + λd + ... = γ as [λ, λ, λ, λ, ..., γ] +Where λ & γ are individual coefficients, the no. of equations = no. of coefficients - 1 + +Note in order to work there must exist 1 equation where all instances of λ and γ != 0 +""" + + +def simplify(current_set: list[list]) -> list[list]: + """ + >>> simplify([[1, 2, 3], [4, 5, 6]]) + [[1.0, 2.0, 3.0], [0.0, 0.75, 1.5]] + >>> simplify([[5, 2, 5], [5, 1, 10]]) + [[1.0, 0.4, 1.0], [0.0, 0.2, -1.0]] + """ + # Divide each row by magnitude of first term --> creates 'unit' matrix + duplicate_set = current_set.copy() + for row_index, row in enumerate(duplicate_set): + magnitude = row[0] + for column_index, column in enumerate(row): + if magnitude == 0: + current_set[row_index][column_index] = column + continue + current_set[row_index][column_index] = column / magnitude + # Subtract to cancel term + first_row = current_set[0] + final_set = [first_row] + current_set = current_set[1::] + for row in current_set: + temp_row = [] + # If first term is 0, it is already in form we want, so we preserve it + if row[0] == 0: + final_set.append(row) + continue + for column_index in range(len(row)): + temp_row.append(first_row[column_index] - row[column_index]) + final_set.append(temp_row) + # Create next recursion iteration set + if len(final_set[0]) != 3: + current_first_row = final_set[0] + current_first_column = [] + next_iteration = [] + for row in final_set[1::]: + current_first_column.append(row[0]) + next_iteration.append(row[1::]) + resultant = simplify(next_iteration) + for i in range(len(resultant)): + resultant[i].insert(0, current_first_column[i]) + resultant.insert(0, current_first_row) + final_set = resultant + return final_set + + +def solve_simultaneous(equations: list[list]) -> list: + """ + >>> solve_simultaneous([[1, 2, 3],[4, 5, 6]]) + [-1.0, 2.0] + >>> solve_simultaneous([[0, -3, 1, 7],[3, 2, -1, 11],[5, 1, -2, 12]]) + [6.4, 1.2, 10.6] + >>> solve_simultaneous([]) + Traceback (most recent call last): + ... + IndexError: solve_simultaneous() requires n lists of length n+1 + >>> solve_simultaneous([[1, 2, 3],[1, 2]]) + Traceback (most recent call last): + ... + IndexError: solve_simultaneous() requires n lists of length n+1 + >>> solve_simultaneous([[1, 2, 3],["a", 7, 8]]) + Traceback (most recent call last): + ... + ValueError: solve_simultaneous() requires lists of integers + >>> solve_simultaneous([[0, 2, 3],[4, 0, 6]]) + Traceback (most recent call last): + ... + ValueError: solve_simultaneous() requires at least 1 full equation + """ + if len(equations) == 0: + raise IndexError("solve_simultaneous() requires n lists of length n+1") + _length = len(equations) + 1 + if any(len(item) != _length for item in equations): + raise IndexError("solve_simultaneous() requires n lists of length n+1") + for row in equations: + if any(not isinstance(column, (int, float)) for column in row): + raise ValueError("solve_simultaneous() requires lists of integers") + if len(equations) == 1: + return [equations[0][-1] / equations[0][0]] + data_set = equations.copy() + if any(0 in row for row in data_set): + temp_data = data_set.copy() + full_row = [] + for row_index, row in enumerate(temp_data): + if 0 not in row: + full_row = data_set.pop(row_index) + break + if not full_row: + raise ValueError("solve_simultaneous() requires at least 1 full equation") + data_set.insert(0, full_row) + useable_form = data_set.copy() + simplified = simplify(useable_form) + simplified = simplified[::-1] + solutions: list = [] + for row in simplified: + current_solution = row[-1] + if not solutions: + if row[-2] == 0: + solutions.append(0) + continue + solutions.append(current_solution / row[-2]) + continue + temp_row = row.copy()[: len(row) - 1 :] + while temp_row[0] == 0: + temp_row.pop(0) + if len(temp_row) == 0: + solutions.append(0) + continue + temp_row = temp_row[1::] + temp_row = temp_row[::-1] + for column_index, column in enumerate(temp_row): + current_solution -= column * solutions[column_index] + solutions.append(current_solution) + final = [] + for item in solutions: + final.append(float(round(item, 5))) + return final[::-1] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + eq = [ + [2, 1, 1, 1, 1, 4], + [1, 2, 1, 1, 1, 5], + [1, 1, 2, 1, 1, 6], + [1, 1, 1, 2, 1, 7], + [1, 1, 1, 1, 2, 8], + ] + print(solve_simultaneous(eq)) + print(solve_simultaneous([[4, 2]])) From 80d95fccc390d366a9f617d8628a546a7be7b2a3 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sat, 3 Jun 2023 17:16:33 +0100 Subject: [PATCH 2201/2908] Pytest locally fails due to API_KEY env variable (#8738) * fix: Pytest locally fails due to API_KEY env variable (#8737) * chore: Fix ruff errors --- web_programming/currency_converter.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py index 69f2a2c4d421..3bbcafa8f89b 100644 --- a/web_programming/currency_converter.py +++ b/web_programming/currency_converter.py @@ -8,13 +8,7 @@ import requests URL_BASE = "https://www.amdoren.com/api/currency.php" -TESTING = os.getenv("CI", "") -API_KEY = os.getenv("AMDOREN_API_KEY", "") -if not API_KEY and not TESTING: - raise KeyError( - "API key must be provided in the 'AMDOREN_API_KEY' environment variable." - ) # Currency and their description list_of_currencies = """ @@ -175,20 +169,31 @@ def convert_currency( - from_: str = "USD", to: str = "INR", amount: float = 1.0, api_key: str = API_KEY + from_: str = "USD", to: str = "INR", amount: float = 1.0, api_key: str = "" ) -> str: """https://www.amdoren.com/currency-api/""" + # Instead of manually generating parameters params = locals() + # from is a reserved keyword 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__": + TESTING = os.getenv("CI", "") + API_KEY = os.getenv("AMDOREN_API_KEY", "") + + if not API_KEY and not TESTING: + raise KeyError( + "API key must be provided in the 'AMDOREN_API_KEY' environment variable." + ) + print( convert_currency( input("Enter from currency: ").strip(), input("Enter to currency: ").strip(), float(input("Enter the amount: ").strip()), + API_KEY, ) ) From fa12b9a286bf42d250b30a772e8f226dc14953f4 Mon Sep 17 00:00:00 2001 From: ShivaDahal99 <130563462+ShivaDahal99@users.noreply.github.com> Date: Wed, 7 Jun 2023 23:47:27 +0200 Subject: [PATCH 2202/2908] Speed of sound (#8803) * Create TestShiva * Delete TestShiva * Add speed of sound * Update physics/speed_of_sound.py Co-authored-by: Christian Clauss * Update physics/speed_of_sound.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update speed_of_sound.py * Update speed_of_sound.py --------- Co-authored-by: jlhuhn <134317018+jlhuhn@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- physics/speed_of_sound.py | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 physics/speed_of_sound.py diff --git a/physics/speed_of_sound.py b/physics/speed_of_sound.py new file mode 100644 index 000000000000..a4658366a36c --- /dev/null +++ b/physics/speed_of_sound.py @@ -0,0 +1,52 @@ +""" +Title : Calculating the speed of sound + +Description : + The speed of sound (c) is the speed that a sound wave travels + per unit time (m/s). During propagation, the sound wave propagates + through an elastic medium. Its SI unit is meter per second (m/s). + + Only longitudinal waves can propagate in liquids and gas other then + solid where they also travel in transverse wave. The following Algo- + rithem calculates the speed of sound in fluid depanding on the bulk + module and the density of the fluid. + + Equation for calculating speed od sound in fluid: + c_fluid = (K_s*p)**0.5 + + c_fluid: speed of sound in fluid + K_s: isentropic bulk modulus + p: density of fluid + + + +Source : https://en.wikipedia.org/wiki/Speed_of_sound +""" + + +def speed_of_sound_in_a_fluid(density: float, bulk_modulus: float) -> float: + """ + This method calculates the speed of sound in fluid - + This is calculated from the other two provided values + Examples: + Example 1 --> Water 20°C: bulk_moduls= 2.15MPa, density=998kg/m³ + Example 2 --> Murcery 20°: bulk_moduls= 28.5MPa, density=13600kg/m³ + + >>> speed_of_sound_in_a_fluid(bulk_modulus=2.15*10**9, density=998) + 1467.7563207952705 + >>> speed_of_sound_in_a_fluid(bulk_modulus=28.5*10**9, density=13600) + 1447.614670861731 + """ + + if density <= 0: + raise ValueError("Impossible fluid density") + if bulk_modulus <= 0: + raise ValueError("Impossible bulk modulus") + + return (bulk_modulus / density) ** 0.5 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 7775de0ef779a28cec7d9f28af97a89b2bc29d7e Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 8 Jun 2023 13:40:38 +0100 Subject: [PATCH 2203/2908] Create number container system algorithm (#8808) * feat: Create number container system algorithm * updating DIRECTORY.md * chore: Fix failing tests * Update other/number_container_system.py Co-authored-by: Christian Clauss * Update other/number_container_system.py Co-authored-by: Christian Clauss * Update other/number_container_system.py Co-authored-by: Christian Clauss * chore: Add more tests * chore: Create binary_search_insert failing test * type: Update typehints to accept str, list and range --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 6 +- other/number_container_system.py | 180 +++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 other/number_container_system.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 231b0e2f1d2f..6dac4a9a5783 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -419,8 +419,9 @@ * [Frequent Pattern Graph Miner](graphs/frequent_pattern_graph_miner.py) * [G Topological Sort](graphs/g_topological_sort.py) * [Gale Shapley Bigraph](graphs/gale_shapley_bigraph.py) + * [Graph Adjacency List](graphs/graph_adjacency_list.py) + * [Graph Adjacency Matrix](graphs/graph_adjacency_matrix.py) * [Graph List](graphs/graph_list.py) - * [Graph Matrix](graphs/graph_matrix.py) * [Graphs Floyd Warshall](graphs/graphs_floyd_warshall.py) * [Greedy Best First](graphs/greedy_best_first.py) * [Greedy Min Vertex Cover](graphs/greedy_min_vertex_cover.py) @@ -479,6 +480,7 @@ * [Lib](linear_algebra/src/lib.py) * [Polynom For Points](linear_algebra/src/polynom_for_points.py) * [Power Iteration](linear_algebra/src/power_iteration.py) + * [Rank Of Matrix](linear_algebra/src/rank_of_matrix.py) * [Rayleigh Quotient](linear_algebra/src/rayleigh_quotient.py) * [Schur Complement](linear_algebra/src/schur_complement.py) * [Test Linear Algebra](linear_algebra/src/test_linear_algebra.py) @@ -651,6 +653,7 @@ * [Sigmoid Linear Unit](maths/sigmoid_linear_unit.py) * [Signum](maths/signum.py) * [Simpson Rule](maths/simpson_rule.py) + * [Simultaneous Linear Equation Solver](maths/simultaneous_linear_equation_solver.py) * [Sin](maths/sin.py) * [Sock Merchant](maths/sock_merchant.py) * [Softmax](maths/softmax.py) @@ -726,6 +729,7 @@ * [Maximum Subarray](other/maximum_subarray.py) * [Maximum Subsequence](other/maximum_subsequence.py) * [Nested Brackets](other/nested_brackets.py) + * [Number Container System](other/number_container_system.py) * [Password](other/password.py) * [Quine](other/quine.py) * [Scoring Algorithm](other/scoring_algorithm.py) diff --git a/other/number_container_system.py b/other/number_container_system.py new file mode 100644 index 000000000000..f547bc8a229e --- /dev/null +++ b/other/number_container_system.py @@ -0,0 +1,180 @@ +""" +A number container system that uses binary search to delete and insert values into +arrays with O(n logn) write times and O(1) read times. + +This container system holds integers at indexes. + +Further explained in this leetcode problem +> https://leetcode.com/problems/minimum-cost-tree-from-leaf-values +""" + + +class NumberContainer: + def __init__(self) -> None: + # numbermap keys are the number and its values are lists of indexes sorted + # in ascending order + self.numbermap: dict[int, list[int]] = {} + # indexmap keys are an index and it's values are the number at that index + self.indexmap: dict[int, int] = {} + + def binary_search_delete(self, array: list | str | range, item: int) -> list[int]: + """ + Removes the item from the sorted array and returns + the new array. + + >>> NumberContainer().binary_search_delete([1,2,3], 2) + [1, 3] + >>> NumberContainer().binary_search_delete([0, 0, 0], 0) + [0, 0] + >>> NumberContainer().binary_search_delete([-1, -1, -1], -1) + [-1, -1] + >>> NumberContainer().binary_search_delete([-1, 0], 0) + [-1] + >>> NumberContainer().binary_search_delete([-1, 0], -1) + [0] + >>> NumberContainer().binary_search_delete(range(7), 3) + [0, 1, 2, 4, 5, 6] + >>> NumberContainer().binary_search_delete([1.1, 2.2, 3.3], 2.2) + [1.1, 3.3] + >>> NumberContainer().binary_search_delete("abcde", "c") + ['a', 'b', 'd', 'e'] + >>> NumberContainer().binary_search_delete([0, -1, 2, 4], 0) + Traceback (most recent call last): + ... + ValueError: Either the item is not in the array or the array was unsorted + >>> NumberContainer().binary_search_delete([2, 0, 4, -1, 11], -1) + Traceback (most recent call last): + ... + ValueError: Either the item is not in the array or the array was unsorted + >>> NumberContainer().binary_search_delete(125, 1) + Traceback (most recent call last): + ... + TypeError: binary_search_delete() only accepts either a list, range or str + """ + if isinstance(array, (range, str)): + array = list(array) + elif not isinstance(array, list): + raise TypeError( + "binary_search_delete() only accepts either a list, range or str" + ) + + low = 0 + high = len(array) - 1 + + while low <= high: + mid = (low + high) // 2 + if array[mid] == item: + array.pop(mid) + return array + elif array[mid] < item: + low = mid + 1 + else: + high = mid - 1 + raise ValueError( + "Either the item is not in the array or the array was unsorted" + ) + + def binary_search_insert(self, array: list | str | range, index: int) -> list[int]: + """ + Inserts the index into the sorted array + at the correct position. + + >>> NumberContainer().binary_search_insert([1,2,3], 2) + [1, 2, 2, 3] + >>> NumberContainer().binary_search_insert([0,1,3], 2) + [0, 1, 2, 3] + >>> NumberContainer().binary_search_insert([-5, -3, 0, 0, 11, 103], 51) + [-5, -3, 0, 0, 11, 51, 103] + >>> NumberContainer().binary_search_insert([-5, -3, 0, 0, 11, 100, 103], 101) + [-5, -3, 0, 0, 11, 100, 101, 103] + >>> NumberContainer().binary_search_insert(range(10), 4) + [0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9] + >>> NumberContainer().binary_search_insert("abd", "c") + ['a', 'b', 'c', 'd'] + >>> NumberContainer().binary_search_insert(131, 23) + Traceback (most recent call last): + ... + TypeError: binary_search_insert() only accepts either a list, range or str + """ + if isinstance(array, (range, str)): + array = list(array) + elif not isinstance(array, list): + raise TypeError( + "binary_search_insert() only accepts either a list, range or str" + ) + + low = 0 + high = len(array) - 1 + + while low <= high: + mid = (low + high) // 2 + if array[mid] == index: + # If the item already exists in the array, + # insert it after the existing item + array.insert(mid + 1, index) + return array + elif array[mid] < index: + low = mid + 1 + else: + high = mid - 1 + + # If the item doesn't exist in the array, insert it at the appropriate position + array.insert(low, index) + return array + + def change(self, index: int, number: int) -> None: + """ + Changes (sets) the index as number + + >>> cont = NumberContainer() + >>> cont.change(0, 10) + >>> cont.change(0, 20) + >>> cont.change(-13, 20) + >>> cont.change(-100030, 20032903290) + """ + # Remove previous index + if index in self.indexmap: + n = self.indexmap[index] + if len(self.numbermap[n]) == 1: + del self.numbermap[n] + else: + self.numbermap[n] = self.binary_search_delete(self.numbermap[n], index) + + # Set new index + self.indexmap[index] = number + + # Number not seen before or empty so insert number value + if number not in self.numbermap: + self.numbermap[number] = [index] + + # Here we need to perform a binary search insertion in order to insert + # The item in the correct place + else: + self.numbermap[number] = self.binary_search_insert( + self.numbermap[number], index + ) + + def find(self, number: int) -> int: + """ + Returns the smallest index where the number is. + + >>> cont = NumberContainer() + >>> cont.find(10) + -1 + >>> cont.change(0, 10) + >>> cont.find(10) + 0 + >>> cont.change(0, 20) + >>> cont.find(10) + -1 + >>> cont.find(20) + 0 + """ + # Simply return the 0th index (smallest) of the indexes found (or -1) + return self.numbermap.get(number, [-1])[0] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9c9da8ebf1d35ae40ac5438c05cc273f7c6d4473 Mon Sep 17 00:00:00 2001 From: Jan Wojciechowski <96974442+yanvoi@users.noreply.github.com> Date: Fri, 9 Jun 2023 11:06:37 +0200 Subject: [PATCH 2204/2908] Improve readability of ciphers/mixed_keyword_cypher.py (#8626) * refactored the code * the code will now pass the test * looked more into it and fixed the logic * made the code easier to read, added comments and fixed the logic * got rid of redundant code + plaintext can contain chars that are not in the alphabet * fixed the reduntant conversion of ascii_uppercase to a list * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * keyword and plaintext won't have default values * ran the ruff command * Update linear_discriminant_analysis.py and rsa_cipher.py (#8680) * Update rsa_cipher.py by replacing %s with {} * Update rsa_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_discriminant_analysis.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_discriminant_analysis.py * Update linear_discriminant_analysis.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_discriminant_analysis.py * Update linear_discriminant_analysis.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_discriminant_analysis.py * Update machine_learning/linear_discriminant_analysis.py Co-authored-by: Christian Clauss * Update linear_discriminant_analysis.py * updated --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss * fixed some difficulties * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added comments, made printing mapping optional, added 1 test * shortened the line that was too long * Update ciphers/mixed_keyword_cypher.py Co-authored-by: Tianyi Zheng --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Tianyi Zheng --- ciphers/mixed_keyword_cypher.py | 100 +++++++++++++++++--------------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index 93a0e3acb7b1..b984808fced6 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -1,7 +1,11 @@ -def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: - """ +from string import ascii_uppercase + - For key:hello +def mixed_keyword( + keyword: str, plaintext: str, verbose: bool = False, alphabet: str = ascii_uppercase +) -> str: + """ + For keyword: hello H E L O A B C D @@ -12,58 +16,60 @@ def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: Y Z and map vertically - >>> mixed_keyword("college", "UNIVERSITY") # doctest: +NORMALIZE_WHITESPACE + >>> mixed_keyword("college", "UNIVERSITY", True) # 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' + + >>> mixed_keyword("college", "UNIVERSITY", False) # doctest: +NORMALIZE_WHITESPACE + 'XKJGUFMJST' """ - key = key.upper() - pt = pt.upper() - temp = [] - for i in key: - if i not in temp: - temp.append(i) - len_temp = len(temp) - # print(temp) - alpha = [] - modalpha = [] - for j in range(65, 91): - t = chr(j) - alpha.append(t) - if t not in temp: - temp.append(t) - # print(temp) - r = int(26 / 4) - # print(r) - k = 0 - for _ in range(r): - s = [] - for _ in range(len_temp): - s.append(temp[k]) - if k >= 25: - break - k += 1 - modalpha.append(s) - # print(modalpha) - d = {} - j = 0 - k = 0 - for j in range(len_temp): - for m in modalpha: - if not len(m) - 1 >= j: - break - d[alpha[k]] = m[j] - if not k < 25: + keyword = keyword.upper() + plaintext = plaintext.upper() + alphabet_set = set(alphabet) + + # create a list of unique characters in the keyword - their order matters + # it determines how we will map plaintext characters to the ciphertext + unique_chars = [] + for char in keyword: + if char in alphabet_set and char not in unique_chars: + unique_chars.append(char) + # the number of those unique characters will determine the number of rows + num_unique_chars_in_keyword = len(unique_chars) + + # create a shifted version of the alphabet + shifted_alphabet = unique_chars + [ + char for char in alphabet if char not in unique_chars + ] + + # create a modified alphabet by splitting the shifted alphabet into rows + modified_alphabet = [ + shifted_alphabet[k : k + num_unique_chars_in_keyword] + for k in range(0, 26, num_unique_chars_in_keyword) + ] + + # map the alphabet characters to the modified alphabet characters + # going 'vertically' through the modified alphabet - consider columns first + mapping = {} + letter_index = 0 + for column in range(num_unique_chars_in_keyword): + for row in modified_alphabet: + # if current row (the last one) is too short, break out of loop + if len(row) <= column: break - k += 1 - print(d) - cypher = "" - for i in pt: - cypher += d[i] - return cypher + + # map current letter to letter in modified alphabet + mapping[alphabet[letter_index]] = row[column] + letter_index += 1 + + if verbose: + print(mapping) + # create the encrypted text by mapping the plaintext to the modified alphabet + return "".join(mapping[char] if char in mapping else char for char in plaintext) if __name__ == "__main__": + # example use print(mixed_keyword("college", "UNIVERSITY")) From daa0c8f3d340485ce295570e6d76b38891e371bd Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sat, 10 Jun 2023 13:21:49 +0100 Subject: [PATCH 2205/2908] Create count negative numbers in matrix algorithm (#8813) * updating DIRECTORY.md * feat: Count negative numbers in sorted matrix * updating DIRECTORY.md * chore: Fix pre-commit * refactor: Combine functions into iteration * style: Reformat reference * feat: Add timings of each implementation * chore: Fix problems with algorithms-keeper bot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test: Remove doctest from benchmark function * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss * refactor: Use sum instead of large iteration * refactor: Use len not sum * Update count_negative_numbers_in_sorted_matrix.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 2 + ...count_negative_numbers_in_sorted_matrix.py | 151 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 matrix/count_negative_numbers_in_sorted_matrix.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 6dac4a9a5783..8511c261a3d2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -679,6 +679,7 @@ ## Matrix * [Binary Search Matrix](matrix/binary_search_matrix.py) * [Count Islands In Matrix](matrix/count_islands_in_matrix.py) + * [Count Negative Numbers In Sorted Matrix](matrix/count_negative_numbers_in_sorted_matrix.py) * [Count Paths](matrix/count_paths.py) * [Cramers Rule 2X2](matrix/cramers_rule_2x2.py) * [Inverse Of Matrix](matrix/inverse_of_matrix.py) @@ -753,6 +754,7 @@ * [Potential Energy](physics/potential_energy.py) * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Shear Stress](physics/shear_stress.py) + * [Speed Of Sound](physics/speed_of_sound.py) ## Project Euler * Problem 001 diff --git a/matrix/count_negative_numbers_in_sorted_matrix.py b/matrix/count_negative_numbers_in_sorted_matrix.py new file mode 100644 index 000000000000..2799ff3b45fe --- /dev/null +++ b/matrix/count_negative_numbers_in_sorted_matrix.py @@ -0,0 +1,151 @@ +""" +Given an matrix of numbers in which all rows and all columns are sorted in decreasing +order, return the number of negative numbers in grid. + +Reference: https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix +""" + + +def generate_large_matrix() -> list[list[int]]: + """ + >>> generate_large_matrix() # doctest: +ELLIPSIS + [[1000, ..., -999], [999, ..., -1001], ..., [2, ..., -1998]] + """ + return [list(range(1000 - i, -1000 - i, -1)) for i in range(1000)] + + +grid = generate_large_matrix() +test_grids = ( + [[4, 3, 2, -1], [3, 2, 1, -1], [1, 1, -1, -2], [-1, -1, -2, -3]], + [[3, 2], [1, 0]], + [[7, 7, 6]], + [[7, 7, 6], [-1, -2, -3]], + grid, +) + + +def validate_grid(grid: list[list[int]]) -> None: + """ + Validate that the rows and columns of the grid is sorted in decreasing order. + >>> for grid in test_grids: + ... validate_grid(grid) + """ + assert all(row == sorted(row, reverse=True) for row in grid) + assert all(list(col) == sorted(col, reverse=True) for col in zip(*grid)) + + +def find_negative_index(array: list[int]) -> int: + """ + Find the smallest negative index + + >>> find_negative_index([0,0,0,0]) + 4 + >>> find_negative_index([4,3,2,-1]) + 3 + >>> find_negative_index([1,0,-1,-10]) + 2 + >>> find_negative_index([0,0,0,-1]) + 3 + >>> find_negative_index([11,8,7,-3,-5,-9]) + 3 + >>> find_negative_index([-1,-1,-2,-3]) + 0 + >>> find_negative_index([5,1,0]) + 3 + >>> find_negative_index([-5,-5,-5]) + 0 + >>> find_negative_index([0]) + 1 + >>> find_negative_index([]) + 0 + """ + left = 0 + right = len(array) - 1 + + # Edge cases such as no values or all numbers are negative. + if not array or array[0] < 0: + return 0 + + while right + 1 > left: + mid = (left + right) // 2 + num = array[mid] + + # Num must be negative and the index must be greater than or equal to 0. + if num < 0 and array[mid - 1] >= 0: + return mid + + if num >= 0: + left = mid + 1 + else: + right = mid - 1 + # No negative numbers so return the last index of the array + 1 which is the length. + return len(array) + + +def count_negatives_binary_search(grid: list[list[int]]) -> int: + """ + An O(m logn) solution that uses binary search in order to find the boundary between + positive and negative numbers + + >>> [count_negatives_binary_search(grid) for grid in test_grids] + [8, 0, 0, 3, 1498500] + """ + total = 0 + bound = len(grid[0]) + + for i in range(len(grid)): + bound = find_negative_index(grid[i][:bound]) + total += bound + return (len(grid) * len(grid[0])) - total + + +def count_negatives_brute_force(grid: list[list[int]]) -> int: + """ + This solution is O(n^2) because it iterates through every column and row. + + >>> [count_negatives_brute_force(grid) for grid in test_grids] + [8, 0, 0, 3, 1498500] + """ + return len([number for row in grid for number in row if number < 0]) + + +def count_negatives_brute_force_with_break(grid: list[list[int]]) -> int: + """ + Similar to the brute force solution above but uses break in order to reduce the + number of iterations. + + >>> [count_negatives_brute_force_with_break(grid) for grid in test_grids] + [8, 0, 0, 3, 1498500] + """ + total = 0 + for row in grid: + for i, number in enumerate(row): + if number < 0: + total += len(row) - i + break + return total + + +def benchmark() -> None: + """Benchmark our functions next to each other""" + from timeit import timeit + + print("Running benchmarks") + setup = ( + "from __main__ import count_negatives_binary_search, " + "count_negatives_brute_force, count_negatives_brute_force_with_break, grid" + ) + for func in ( + "count_negatives_binary_search", # took 0.7727 seconds + "count_negatives_brute_force_with_break", # took 4.6505 seconds + "count_negatives_brute_force", # took 12.8160 seconds + ): + time = timeit(f"{func}(grid=grid)", setup=setup, number=500) + print(f"{func}() took {time:0.4f} seconds") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + benchmark() From 46379861257d43bb7140d261094bf17dc414950f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 00:09:33 +0200 Subject: [PATCH 2206/2908] [pre-commit.ci] pre-commit autoupdate (#8817) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.270 → v0.0.272](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.270...v0.0.272) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c70ae219f74..1d4b73681108 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.270 + rev: v0.0.272 hooks: - id: ruff From e6f89a6b89941ffed911e96362be3611a45420e7 Mon Sep 17 00:00:00 2001 From: Ilkin Mengusoglu <113149540+imengus@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:00:02 +0100 Subject: [PATCH 2207/2908] Simplex algorithm (#8825) * feat: added simplex.py * added docstrings * Update linear_programming/simplex.py Co-authored-by: Caeden Perelli-Harris * Update linear_programming/simplex.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_programming/simplex.py Co-authored-by: Caeden Perelli-Harris * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ruff fix Co-authored by: CaedenPH * removed README to add in separate PR * Update linear_programming/simplex.py Co-authored-by: Tianyi Zheng * Update linear_programming/simplex.py Co-authored-by: Tianyi Zheng * fix class docstring * add comments --------- Co-authored-by: Caeden Perelli-Harris Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- linear_programming/simplex.py | 311 ++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 linear_programming/simplex.py diff --git a/linear_programming/simplex.py b/linear_programming/simplex.py new file mode 100644 index 000000000000..ba64add40b5f --- /dev/null +++ b/linear_programming/simplex.py @@ -0,0 +1,311 @@ +""" +Python implementation of the simplex algorithm for solving linear programs in +tabular form with +- `>=`, `<=`, and `=` constraints and +- each variable `x1, x2, ...>= 0`. + +See https://gist.github.com/imengus/f9619a568f7da5bc74eaf20169a24d98 for how to +convert linear programs to simplex tableaus, and the steps taken in the simplex +algorithm. + +Resources: +https://en.wikipedia.org/wiki/Simplex_algorithm +https://tinyurl.com/simplex4beginners +""" +from typing import Any + +import numpy as np + + +class Tableau: + """Operate on simplex tableaus + + >>> t = Tableau(np.array([[-1,-1,0,0,-1],[1,3,1,0,4],[3,1,0,1,4.]]), 2) + Traceback (most recent call last): + ... + ValueError: RHS must be > 0 + """ + + def __init__(self, tableau: np.ndarray, n_vars: int) -> None: + # Check if RHS is negative + if np.any(tableau[:, -1], where=tableau[:, -1] < 0): + raise ValueError("RHS must be > 0") + + self.tableau = tableau + self.n_rows, _ = tableau.shape + + # Number of decision variables x1, x2, x3... + self.n_vars = n_vars + + # Number of artificial variables to be minimised + self.n_art_vars = len(np.where(tableau[self.n_vars : -1] == -1)[0]) + + # 2 if there are >= or == constraints (nonstandard), 1 otherwise (std) + self.n_stages = (self.n_art_vars > 0) + 1 + + # Number of slack variables added to make inequalities into equalities + self.n_slack = self.n_rows - self.n_stages + + # Objectives for each stage + self.objectives = ["max"] + + # In two stage simplex, first minimise then maximise + if self.n_art_vars: + self.objectives.append("min") + + self.col_titles = [""] + + # Index of current pivot row and column + self.row_idx = None + self.col_idx = None + + # Does objective row only contain (non)-negative values? + self.stop_iter = False + + @staticmethod + def generate_col_titles(*args: int) -> list[str]: + """Generate column titles for tableau of specific dimensions + + >>> Tableau.generate_col_titles(2, 3, 1) + ['x1', 'x2', 's1', 's2', 's3', 'a1', 'RHS'] + + >>> Tableau.generate_col_titles() + Traceback (most recent call last): + ... + ValueError: Must provide n_vars, n_slack, and n_art_vars + >>> Tableau.generate_col_titles(-2, 3, 1) + Traceback (most recent call last): + ... + ValueError: All arguments must be non-negative integers + """ + if len(args) != 3: + raise ValueError("Must provide n_vars, n_slack, and n_art_vars") + + if not all(x >= 0 and isinstance(x, int) for x in args): + raise ValueError("All arguments must be non-negative integers") + + # decision | slack | artificial + string_starts = ["x", "s", "a"] + titles = [] + for i in range(3): + for j in range(args[i]): + titles.append(string_starts[i] + str(j + 1)) + titles.append("RHS") + return titles + + def find_pivot(self, tableau: np.ndarray) -> tuple[Any, Any]: + """Finds the pivot row and column. + >>> t = Tableau(np.array([[-2,1,0,0,0], [3,1,1,0,6], [1,2,0,1,7.]]), 2) + >>> t.find_pivot(t.tableau) + (1, 0) + """ + objective = self.objectives[-1] + + # Find entries of highest magnitude in objective rows + sign = (objective == "min") - (objective == "max") + col_idx = np.argmax(sign * tableau[0, : self.n_vars]) + + # Choice is only valid if below 0 for maximise, and above for minimise + if sign * self.tableau[0, col_idx] <= 0: + self.stop_iter = True + return 0, 0 + + # Pivot row is chosen as having the lowest quotient when elements of + # the pivot column divide the right-hand side + + # Slice excluding the objective rows + s = slice(self.n_stages, self.n_rows) + + # RHS + dividend = tableau[s, -1] + + # Elements of pivot column within slice + divisor = tableau[s, col_idx] + + # Array filled with nans + nans = np.full(self.n_rows - self.n_stages, np.nan) + + # If element in pivot column is greater than zeron_stages, return + # quotient or nan otherwise + quotients = np.divide(dividend, divisor, out=nans, where=divisor > 0) + + # Arg of minimum quotient excluding the nan values. n_stages is added + # to compensate for earlier exclusion of objective columns + row_idx = np.nanargmin(quotients) + self.n_stages + return row_idx, col_idx + + def pivot(self, tableau: np.ndarray, row_idx: int, col_idx: int) -> np.ndarray: + """Pivots on value on the intersection of pivot row and column. + + >>> t = Tableau(np.array([[-2,-3,0,0,0],[1,3,1,0,4],[3,1,0,1,4.]]), 2) + >>> t.pivot(t.tableau, 1, 0).tolist() + ... # doctest: +NORMALIZE_WHITESPACE + [[0.0, 3.0, 2.0, 0.0, 8.0], + [1.0, 3.0, 1.0, 0.0, 4.0], + [0.0, -8.0, -3.0, 1.0, -8.0]] + """ + # Avoid changes to original tableau + piv_row = tableau[row_idx].copy() + + piv_val = piv_row[col_idx] + + # Entry becomes 1 + piv_row *= 1 / piv_val + + # Variable in pivot column becomes basic, ie the only non-zero entry + for idx, coeff in enumerate(tableau[:, col_idx]): + tableau[idx] += -coeff * piv_row + tableau[row_idx] = piv_row + return tableau + + def change_stage(self, tableau: np.ndarray) -> np.ndarray: + """Exits first phase of the two-stage method by deleting artificial + rows and columns, or completes the algorithm if exiting the standard + case. + + >>> t = Tableau(np.array([ + ... [3, 3, -1, -1, 0, 0, 4], + ... [2, 1, 0, 0, 0, 0, 0.], + ... [1, 2, -1, 0, 1, 0, 2], + ... [2, 1, 0, -1, 0, 1, 2] + ... ]), 2) + >>> t.change_stage(t.tableau).tolist() + ... # doctest: +NORMALIZE_WHITESPACE + [[2.0, 1.0, 0.0, 0.0, 0.0, 0.0], + [1.0, 2.0, -1.0, 0.0, 1.0, 2.0], + [2.0, 1.0, 0.0, -1.0, 0.0, 2.0]] + """ + # Objective of original objective row remains + self.objectives.pop() + + if not self.objectives: + return tableau + + # Slice containing ids for artificial columns + s = slice(-self.n_art_vars - 1, -1) + + # Delete the artificial variable columns + tableau = np.delete(tableau, s, axis=1) + + # Delete the objective row of the first stage + tableau = np.delete(tableau, 0, axis=0) + + self.n_stages = 1 + self.n_rows -= 1 + self.n_art_vars = 0 + self.stop_iter = False + return tableau + + def run_simplex(self) -> dict[Any, Any]: + """Operate on tableau until objective function cannot be + improved further. + + # Standard linear program: + Max: x1 + x2 + ST: x1 + 3x2 <= 4 + 3x1 + x2 <= 4 + >>> Tableau(np.array([[-1,-1,0,0,0],[1,3,1,0,4],[3,1,0,1,4.]]), + ... 2).run_simplex() + {'P': 2.0, 'x1': 1.0, 'x2': 1.0} + + # Optimal tableau input: + >>> Tableau(np.array([ + ... [0, 0, 0.25, 0.25, 2], + ... [0, 1, 0.375, -0.125, 1], + ... [1, 0, -0.125, 0.375, 1] + ... ]), 2).run_simplex() + {'P': 2.0, 'x1': 1.0, 'x2': 1.0} + + # Non-standard: >= constraints + Max: 2x1 + 3x2 + x3 + ST: x1 + x2 + x3 <= 40 + 2x1 + x2 - x3 >= 10 + - x2 + x3 >= 10 + >>> Tableau(np.array([ + ... [2, 0, 0, 0, -1, -1, 0, 0, 20], + ... [-2, -3, -1, 0, 0, 0, 0, 0, 0], + ... [1, 1, 1, 1, 0, 0, 0, 0, 40], + ... [2, 1, -1, 0, -1, 0, 1, 0, 10], + ... [0, -1, 1, 0, 0, -1, 0, 1, 10.] + ... ]), 3).run_simplex() + {'P': 70.0, 'x1': 10.0, 'x2': 10.0, 'x3': 20.0} + + # Non standard: minimisation and equalities + Min: x1 + x2 + ST: 2x1 + x2 = 12 + 6x1 + 5x2 = 40 + >>> Tableau(np.array([ + ... [8, 6, 0, -1, 0, -1, 0, 0, 52], + ... [1, 1, 0, 0, 0, 0, 0, 0, 0], + ... [2, 1, 1, 0, 0, 0, 0, 0, 12], + ... [2, 1, 0, -1, 0, 0, 1, 0, 12], + ... [6, 5, 0, 0, 1, 0, 0, 0, 40], + ... [6, 5, 0, 0, 0, -1, 0, 1, 40.] + ... ]), 2).run_simplex() + {'P': 7.0, 'x1': 5.0, 'x2': 2.0} + """ + # Stop simplex algorithm from cycling. + for _ in range(100): + # Completion of each stage removes an objective. If both stages + # are complete, then no objectives are left + if not self.objectives: + self.col_titles = self.generate_col_titles( + self.n_vars, self.n_slack, self.n_art_vars + ) + + # Find the values of each variable at optimal solution + return self.interpret_tableau(self.tableau, self.col_titles) + + row_idx, col_idx = self.find_pivot(self.tableau) + + # If there are no more negative values in objective row + if self.stop_iter: + # Delete artificial variable columns and rows. Update attributes + self.tableau = self.change_stage(self.tableau) + else: + self.tableau = self.pivot(self.tableau, row_idx, col_idx) + return {} + + def interpret_tableau( + self, tableau: np.ndarray, col_titles: list[str] + ) -> dict[str, float]: + """Given the final tableau, add the corresponding values of the basic + decision variables to the `output_dict` + >>> tableau = np.array([ + ... [0,0,0.875,0.375,5], + ... [0,1,0.375,-0.125,1], + ... [1,0,-0.125,0.375,1] + ... ]) + >>> t = Tableau(tableau, 2) + >>> t.interpret_tableau(tableau, ["x1", "x2", "s1", "s2", "RHS"]) + {'P': 5.0, 'x1': 1.0, 'x2': 1.0} + """ + # P = RHS of final tableau + output_dict = {"P": abs(tableau[0, -1])} + + for i in range(self.n_vars): + # Gives ids of nonzero entries in the ith column + nonzero = np.nonzero(tableau[:, i]) + n_nonzero = len(nonzero[0]) + + # First entry in the nonzero ids + nonzero_rowidx = nonzero[0][0] + nonzero_val = tableau[nonzero_rowidx, i] + + # If there is only one nonzero value in column, which is one + if n_nonzero == nonzero_val == 1: + rhs_val = tableau[nonzero_rowidx, -1] + output_dict[col_titles[i]] = rhs_val + + # Check for basic variables + for title in col_titles: + # Don't add RHS or slack variables to output dict + if title[0] not in "R-s-a": + output_dict.setdefault(title, 0) + return output_dict + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b0f871032e78dd1d2f2214acbaae2fac88fa55b0 Mon Sep 17 00:00:00 2001 From: Frank-1998 <77809242+Frank-1998@users.noreply.github.com> Date: Sun, 18 Jun 2023 10:30:06 -0600 Subject: [PATCH 2208/2908] Fix removing the root node in binary_search_tree.py removes the whole tree (#8752) * fix issue #8715 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/binary_tree/binary_search_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index cd88cc10e697..c72195424c7c 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -40,7 +40,7 @@ def __reassign_nodes(self, node: Node, new_children: Node | None) -> None: else: node.parent.left = new_children else: - self.root = None + self.root = new_children def is_right(self, node: Node) -> bool: if node.parent and node.parent.right: From ea6c6056cf2215358834710bf89422310f831178 Mon Sep 17 00:00:00 2001 From: Turro <42980188+smturro2@users.noreply.github.com> Date: Mon, 19 Jun 2023 06:46:29 -0500 Subject: [PATCH 2209/2908] Added apr_interest function to financial (#6025) * Added apr_interest function to financial * Update interest.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update financial/interest.py * float --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- financial/interest.py | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/financial/interest.py b/financial/interest.py index c69c730457d9..33d02e27ccb3 100644 --- a/financial/interest.py +++ b/financial/interest.py @@ -4,7 +4,7 @@ def simple_interest( - principal: float, daily_interest_rate: float, days_between_payments: int + principal: float, daily_interest_rate: float, days_between_payments: float ) -> float: """ >>> simple_interest(18000.0, 0.06, 3) @@ -42,7 +42,7 @@ def simple_interest( def compound_interest( principal: float, nominal_annual_interest_rate_percentage: float, - number_of_compounding_periods: int, + number_of_compounding_periods: float, ) -> float: """ >>> compound_interest(10000.0, 0.05, 3) @@ -77,6 +77,43 @@ def compound_interest( ) +def apr_interest( + principal: float, + nominal_annual_percentage_rate: float, + number_of_years: float, +) -> float: + """ + >>> apr_interest(10000.0, 0.05, 3) + 1618.223072263547 + >>> apr_interest(10000.0, 0.05, 1) + 512.6749646744732 + >>> apr_interest(0.5, 0.05, 3) + 0.08091115361317736 + >>> apr_interest(10000.0, 0.06, -4) + Traceback (most recent call last): + ... + ValueError: number_of_years must be > 0 + >>> apr_interest(10000.0, -3.5, 3.0) + Traceback (most recent call last): + ... + ValueError: nominal_annual_percentage_rate must be >= 0 + >>> apr_interest(-5500.0, 0.01, 5) + Traceback (most recent call last): + ... + ValueError: principal must be > 0 + """ + if number_of_years <= 0: + raise ValueError("number_of_years must be > 0") + if nominal_annual_percentage_rate < 0: + raise ValueError("nominal_annual_percentage_rate must be >= 0") + if principal <= 0: + raise ValueError("principal must be > 0") + + return compound_interest( + principal, nominal_annual_percentage_rate / 365, number_of_years * 365 + ) + + if __name__ == "__main__": import doctest From 0dee4a402c85981af0c2d4c53af27a69a7eb91bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:56:14 +0200 Subject: [PATCH 2210/2908] [pre-commit.ci] pre-commit autoupdate (#8827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/codespell-project/codespell: v2.2.4 → v2.2.5](https://github.com/codespell-project/codespell/compare/v2.2.4...v2.2.5) - [github.com/tox-dev/pyproject-fmt: 0.11.2 → 0.12.0](https://github.com/tox-dev/pyproject-fmt/compare/0.11.2...0.12.0) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d4b73681108..591fd7819a5a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,14 +26,14 @@ repos: - id: black - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.5 hooks: - id: codespell additional_dependencies: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.11.2" + rev: "0.12.0" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index 8511c261a3d2..6ec8d5111176 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -486,6 +486,9 @@ * [Test Linear Algebra](linear_algebra/src/test_linear_algebra.py) * [Transformations 2D](linear_algebra/src/transformations_2d.py) +## Linear Programming + * [Simplex](linear_programming/simplex.py) + ## Machine Learning * [Astar](machine_learning/astar.py) * [Data Transformations](machine_learning/data_transformations.py) From 07e68128883b84fb7e342c6bce88863a05fbbf62 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 20 Jun 2023 18:03:16 +0200 Subject: [PATCH 2211/2908] Update .pre-commit-config.yaml (#8828) * Update .pre-commit-config.yaml * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- pyproject.toml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a526196685f5..1dcce044a313 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,3 @@ -[tool.pytest.ini_options] -markers = [ - "mat_ops: mark a test as utilizing matrix operations.", -] -addopts = [ - "--durations=10", - "--doctest-modules", - "--showlocals", -] - -[tool.coverage.report] -omit = [".env/*"] -sort = "Cover" - -[tool.codespell] -ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar" -skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" - [tool.ruff] ignore = [ # `ruff rule S101` for a description of that rule "ARG001", # Unused function argument `amount` -- FIX ME? @@ -131,3 +113,21 @@ max-args = 10 # default: 5 max-branches = 20 # default: 12 max-returns = 8 # default: 6 max-statements = 88 # default: 50 + +[tool.pytest.ini_options] +markers = [ + "mat_ops: mark a test as utilizing matrix operations.", +] +addopts = [ + "--durations=10", + "--doctest-modules", + "--showlocals", +] + +[tool.coverage.report] +omit = [".env/*"] +sort = "Cover" + +[tool.codespell] +ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar" +skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" From 5b0890bd833eb85c58fae9afc4984d520e7e2ad6 Mon Sep 17 00:00:00 2001 From: "Linus M. Henkel" <86628476+linushenkel@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:49:09 +0200 Subject: [PATCH 2212/2908] Dijkstra algorithm with binary grid (#8802) * Create TestShiva * Delete TestShiva * Implementation of the Dijkstra-Algorithm in a binary grid * Update double_ended_queue.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update least_common_multiple.py * Update sol1.py * Update pyproject.toml * Update pyproject.toml * https://github.com/astral-sh/ruff-pre-commit v0.0.274 --------- Co-authored-by: ShivaDahal99 <130563462+ShivaDahal99@users.noreply.github.com> Co-authored-by: jlhuhn <134317018+jlhuhn@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 +- data_structures/queue/double_ended_queue.py | 4 +- graphs/dijkstra_binary_grid.py | 89 +++++++++++++++++++++ maths/least_common_multiple.py | 6 +- project_euler/problem_054/sol1.py | 18 ++--- pyproject.toml | 1 + 6 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 graphs/dijkstra_binary_grid.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 591fd7819a5a..3d4cc4084ccf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,8 +15,8 @@ repos: hooks: - id: auto-walrus - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.272 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.274 hooks: - id: ruff diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 637b7f62fd2c..2472371b42fe 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -32,7 +32,7 @@ class Deque: the number of nodes """ - __slots__ = ["_front", "_back", "_len"] + __slots__ = ("_front", "_back", "_len") @dataclass class _Node: @@ -54,7 +54,7 @@ class _Iterator: the current node of the iteration. """ - __slots__ = ["_cur"] + __slots__ = "_cur" def __init__(self, cur: Deque._Node | None) -> None: self._cur = cur diff --git a/graphs/dijkstra_binary_grid.py b/graphs/dijkstra_binary_grid.py new file mode 100644 index 000000000000..c23d8234328a --- /dev/null +++ b/graphs/dijkstra_binary_grid.py @@ -0,0 +1,89 @@ +""" +This script implements the Dijkstra algorithm on a binary grid. +The grid consists of 0s and 1s, where 1 represents +a walkable node and 0 represents an obstacle. +The algorithm finds the shortest path from a start node to a destination node. +Diagonal movement can be allowed or disallowed. +""" + +from heapq import heappop, heappush + +import numpy as np + + +def dijkstra( + grid: np.ndarray, + source: tuple[int, int], + destination: tuple[int, int], + allow_diagonal: bool, +) -> tuple[float | int, list[tuple[int, int]]]: + """ + Implements Dijkstra's algorithm on a binary grid. + + Args: + grid (np.ndarray): A 2D numpy array representing the grid. + 1 represents a walkable node and 0 represents an obstacle. + source (Tuple[int, int]): A tuple representing the start node. + destination (Tuple[int, int]): A tuple representing the + destination node. + allow_diagonal (bool): A boolean determining whether + diagonal movements are allowed. + + Returns: + Tuple[Union[float, int], List[Tuple[int, int]]]: + The shortest distance from the start node to the destination node + and the shortest path as a list of nodes. + + >>> dijkstra(np.array([[1, 1, 1], [0, 1, 0], [0, 1, 1]]), (0, 0), (2, 2), False) + (4.0, [(0, 0), (0, 1), (1, 1), (2, 1), (2, 2)]) + + >>> dijkstra(np.array([[1, 1, 1], [0, 1, 0], [0, 1, 1]]), (0, 0), (2, 2), True) + (2.0, [(0, 0), (1, 1), (2, 2)]) + + >>> dijkstra(np.array([[1, 1, 1], [0, 0, 1], [0, 1, 1]]), (0, 0), (2, 2), False) + (4.0, [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)]) + """ + rows, cols = grid.shape + dx = [-1, 1, 0, 0] + dy = [0, 0, -1, 1] + if allow_diagonal: + dx += [-1, -1, 1, 1] + dy += [-1, 1, -1, 1] + + queue, visited = [(0, source)], set() + matrix = np.full((rows, cols), np.inf) + matrix[source] = 0 + predecessors = np.empty((rows, cols), dtype=object) + predecessors[source] = None + + while queue: + (dist, (x, y)) = heappop(queue) + if (x, y) in visited: + continue + visited.add((x, y)) + + if (x, y) == destination: + path = [] + while (x, y) != source: + path.append((x, y)) + x, y = predecessors[x, y] + path.append(source) # add the source manually + path.reverse() + return matrix[destination], path + + for i in range(len(dx)): + nx, ny = x + dx[i], y + dy[i] + if 0 <= nx < rows and 0 <= ny < cols: + next_node = grid[nx][ny] + if next_node == 1 and matrix[nx, ny] > dist + 1: + heappush(queue, (dist + 1, (nx, ny))) + matrix[nx, ny] = dist + 1 + predecessors[nx, ny] = (x, y) + + return np.inf, [] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/least_common_multiple.py b/maths/least_common_multiple.py index 621d93720c41..10cc63ac7990 100644 --- a/maths/least_common_multiple.py +++ b/maths/least_common_multiple.py @@ -67,7 +67,7 @@ def benchmark(): class TestLeastCommonMultiple(unittest.TestCase): - test_inputs = [ + test_inputs = ( (10, 20), (13, 15), (4, 31), @@ -77,8 +77,8 @@ class TestLeastCommonMultiple(unittest.TestCase): (12, 25), (10, 25), (6, 9), - ] - expected_results = [20, 195, 124, 210, 1462, 60, 300, 50, 18] + ) + 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): diff --git a/project_euler/problem_054/sol1.py b/project_euler/problem_054/sol1.py index 74409f32c712..86dfa5edd2f5 100644 --- a/project_euler/problem_054/sol1.py +++ b/project_euler/problem_054/sol1.py @@ -47,18 +47,18 @@ 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 + 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 + hand: a 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. + player's hand is better than the opponent's hand. hand_name(): Returns a string made up of two parts: hand name and high card. @@ -66,11 +66,11 @@ class PokerHand: Supported operators: Rich comparison operators: <, >, <=, >=, ==, != - Supported builtin methods and functions: + Supported built-in methods and functions: list.sort(), sorted() """ - _HAND_NAME = [ + _HAND_NAME = ( "High card", "One pair", "Two pairs", @@ -81,10 +81,10 @@ class PokerHand: "Four of a kind", "Straight flush", "Royal flush", - ] + ) - _CARD_NAME = [ - "", # placeholder as lists are zero indexed + _CARD_NAME = ( + "", # placeholder as tuples are zero-indexed "One", "Two", "Three", @@ -99,7 +99,7 @@ class PokerHand: "Queen", "King", "Ace", - ] + ) def __init__(self, hand: str) -> None: """ diff --git a/pyproject.toml b/pyproject.toml index 1dcce044a313..4f21a95190da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,6 +103,7 @@ max-complexity = 17 # default: 10 "machine_learning/linear_discriminant_analysis.py" = ["ARG005"] "machine_learning/sequential_minimum_optimization.py" = ["SIM115"] "matrix/sherman_morrison.py" = ["SIM103", "SIM114"] +"other/l*u_cache.py" = ["RUF012"] "physics/newtons_second_law_of_motion.py" = ["BLE001"] "project_euler/problem_099/sol1.py" = ["SIM115"] "sorts/external_sort.py" = ["SIM115"] From 5ffe601c86a9b44691a4dce37480c6d904102d49 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Thu, 22 Jun 2023 05:24:34 -0700 Subject: [PATCH 2213/2908] Fix `mypy` errors in `maths/sigmoid_linear_unit.py` (#8786) * updating DIRECTORY.md * Fix mypy errors in sigmoid_linear_unit.py * updating DIRECTORY.md * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- maths/sigmoid_linear_unit.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/maths/sigmoid_linear_unit.py b/maths/sigmoid_linear_unit.py index a8ada10dd8ec..0ee09bf82d38 100644 --- a/maths/sigmoid_linear_unit.py +++ b/maths/sigmoid_linear_unit.py @@ -17,7 +17,7 @@ import numpy as np -def sigmoid(vector: np.array) -> np.array: +def sigmoid(vector: np.ndarray) -> np.ndarray: """ Mathematical function sigmoid takes a vector x of K real numbers as input and returns 1/ (1 + e^-x). @@ -29,17 +29,15 @@ def sigmoid(vector: np.array) -> np.array: return 1 / (1 + np.exp(-vector)) -def sigmoid_linear_unit(vector: np.array) -> np.array: +def sigmoid_linear_unit(vector: np.ndarray) -> np.ndarray: """ Implements the Sigmoid Linear Unit (SiLU) or swish function Parameters: - vector (np.array): A numpy array consisting of real - values. + vector (np.ndarray): A numpy array consisting of real values Returns: - swish_vec (np.array): The input numpy array, after applying - swish. + swish_vec (np.ndarray): The input numpy array, after applying swish Examples: >>> sigmoid_linear_unit(np.array([-1.0, 1.0, 2.0])) From f54a9668103e560f20b50559fb54ac38a74d1fe8 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Huhn <134317018+jlhuhn@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:31:48 +0200 Subject: [PATCH 2214/2908] Energy conversions (#8801) * Create TestShiva * Delete TestShiva * Create energy_conversions.py * Update conversions/energy_conversions.py Co-authored-by: Caeden Perelli-Harris --------- Co-authored-by: ShivaDahal99 <130563462+ShivaDahal99@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris --- conversions/energy_conversions.py | 114 ++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 conversions/energy_conversions.py diff --git a/conversions/energy_conversions.py b/conversions/energy_conversions.py new file mode 100644 index 000000000000..51de6b313928 --- /dev/null +++ b/conversions/energy_conversions.py @@ -0,0 +1,114 @@ +""" +Conversion of energy units. + +Available units: joule, kilojoule, megajoule, gigajoule,\ + wattsecond, watthour, kilowatthour, newtonmeter, calorie_nutr,\ + kilocalorie_nutr, electronvolt, britishthermalunit_it, footpound + +USAGE : +-> Import this file into their respective project. +-> Use the function energy_conversion() for conversion of energy 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/Units_of_energy +-> Wikipedia reference: https://en.wikipedia.org/wiki/Joule +-> Wikipedia reference: https://en.wikipedia.org/wiki/Kilowatt-hour +-> Wikipedia reference: https://en.wikipedia.org/wiki/Newton-metre +-> Wikipedia reference: https://en.wikipedia.org/wiki/Calorie +-> Wikipedia reference: https://en.wikipedia.org/wiki/Electronvolt +-> Wikipedia reference: https://en.wikipedia.org/wiki/British_thermal_unit +-> Wikipedia reference: https://en.wikipedia.org/wiki/Foot-pound_(energy) +-> Unit converter reference: https://www.unitconverters.net/energy-converter.html +""" + +ENERGY_CONVERSION: dict[str, float] = { + "joule": 1.0, + "kilojoule": 1_000, + "megajoule": 1_000_000, + "gigajoule": 1_000_000_000, + "wattsecond": 1.0, + "watthour": 3_600, + "kilowatthour": 3_600_000, + "newtonmeter": 1.0, + "calorie_nutr": 4_186.8, + "kilocalorie_nutr": 4_186_800.00, + "electronvolt": 1.602_176_634e-19, + "britishthermalunit_it": 1_055.055_85, + "footpound": 1.355_818, +} + + +def energy_conversion(from_type: str, to_type: str, value: float) -> float: + """ + Conversion of energy units. + >>> energy_conversion("joule", "joule", 1) + 1.0 + >>> energy_conversion("joule", "kilojoule", 1) + 0.001 + >>> energy_conversion("joule", "megajoule", 1) + 1e-06 + >>> energy_conversion("joule", "gigajoule", 1) + 1e-09 + >>> energy_conversion("joule", "wattsecond", 1) + 1.0 + >>> energy_conversion("joule", "watthour", 1) + 0.0002777777777777778 + >>> energy_conversion("joule", "kilowatthour", 1) + 2.7777777777777776e-07 + >>> energy_conversion("joule", "newtonmeter", 1) + 1.0 + >>> energy_conversion("joule", "calorie_nutr", 1) + 0.00023884589662749592 + >>> energy_conversion("joule", "kilocalorie_nutr", 1) + 2.388458966274959e-07 + >>> energy_conversion("joule", "electronvolt", 1) + 6.241509074460763e+18 + >>> energy_conversion("joule", "britishthermalunit_it", 1) + 0.0009478171226670134 + >>> energy_conversion("joule", "footpound", 1) + 0.7375621211696556 + >>> energy_conversion("joule", "megajoule", 1000) + 0.001 + >>> energy_conversion("calorie_nutr", "kilocalorie_nutr", 1000) + 1.0 + >>> energy_conversion("kilowatthour", "joule", 10) + 36000000.0 + >>> energy_conversion("britishthermalunit_it", "footpound", 1) + 778.1692306784539 + >>> energy_conversion("watthour", "joule", "a") # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'str' and 'float' + >>> energy_conversion("wrongunit", "joule", 1) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Incorrect 'from_type' or 'to_type' value: 'wrongunit', 'joule' + Valid values are: joule, ... footpound + >>> energy_conversion("joule", "wrongunit", 1) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Incorrect 'from_type' or 'to_type' value: 'joule', 'wrongunit' + Valid values are: joule, ... footpound + >>> energy_conversion("123", "abc", 1) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Incorrect 'from_type' or 'to_type' value: '123', 'abc' + Valid values are: joule, ... footpound + """ + if to_type not in ENERGY_CONVERSION or from_type not in ENERGY_CONVERSION: + msg = ( + f"Incorrect 'from_type' or 'to_type' value: {from_type!r}, {to_type!r}\n" + f"Valid values are: {', '.join(ENERGY_CONVERSION)}" + ) + raise ValueError(msg) + return value * ENERGY_CONVERSION[from_type] / ENERGY_CONVERSION[to_type] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 331585f3f866e210e23d11700b09a8770a1c2490 Mon Sep 17 00:00:00 2001 From: Himanshu Tomar Date: Fri, 23 Jun 2023 13:56:05 +0530 Subject: [PATCH 2215/2908] Algorithm: Calculating Product Sum from a Special Array with Nested Structures (#8761) * Added minimum waiting time problem solution using greedy algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ruff --fix * Add type hints * Added two more doc test * Removed unnecessary comments * updated type hints * Updated the code as per the code review * Added recursive algo to calculate product sum from an array * Added recursive algo to calculate product sum from an array * Update doc string * Added doctest for product_sum function * Updated the code and added more doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added more test coverage for product_sum method * Update product_sum.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + data_structures/arrays/product_sum.py | 98 +++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 data_structures/arrays/product_sum.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 6ec8d5111176..83389dab1f56 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -166,6 +166,7 @@ * Arrays * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) + * [Product Sum Array](data_structures/arrays/product_sum.py) * Binary Tree * [Avl Tree](data_structures/binary_tree/avl_tree.py) * [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py) diff --git a/data_structures/arrays/product_sum.py b/data_structures/arrays/product_sum.py new file mode 100644 index 000000000000..4fb906f369ab --- /dev/null +++ b/data_structures/arrays/product_sum.py @@ -0,0 +1,98 @@ +""" +Calculate the Product Sum from a Special Array. +reference: https://dev.to/sfrasica/algorithms-product-sum-from-an-array-dc6 + +Python doctests can be run with the following command: +python -m doctest -v product_sum.py + +Calculate the product sum of a "special" array which can contain integers or nested +arrays. The product sum is obtained by adding all elements and multiplying by their +respective depths. + +For example, in the array [x, y], the product sum is (x + y). In the array [x, [y, z]], +the product sum is x + 2 * (y + z). In the array [x, [y, [z]]], +the product sum is x + 2 * (y + 3z). + +Example Input: +[5, 2, [-7, 1], 3, [6, [-13, 8], 4]] +Output: 12 + +""" + + +def product_sum(arr: list[int | list], depth: int) -> int: + """ + Recursively calculates the product sum of an array. + + The product sum of an array is defined as the sum of its elements multiplied by + their respective depths. If an element is a list, its product sum is calculated + recursively by multiplying the sum of its elements with its depth plus one. + + Args: + arr: The array of integers and nested lists. + depth: The current depth level. + + Returns: + int: The product sum of the array. + + Examples: + >>> product_sum([1, 2, 3], 1) + 6 + >>> product_sum([-1, 2, [-3, 4]], 2) + 8 + >>> product_sum([1, 2, 3], -1) + -6 + >>> product_sum([1, 2, 3], 0) + 0 + >>> product_sum([1, 2, 3], 7) + 42 + >>> product_sum((1, 2, 3), 7) + 42 + >>> product_sum({1, 2, 3}, 7) + 42 + >>> product_sum([1, -1], 1) + 0 + >>> product_sum([1, -2], 1) + -1 + >>> product_sum([-3.5, [1, [0.5]]], 1) + 1.5 + + """ + total_sum = 0 + for ele in arr: + total_sum += product_sum(ele, depth + 1) if isinstance(ele, list) else ele + return total_sum * depth + + +def product_sum_array(array: list[int | list]) -> int: + """ + Calculates the product sum of an array. + + Args: + array (List[Union[int, List]]): The array of integers and nested lists. + + Returns: + int: The product sum of the array. + + Examples: + >>> product_sum_array([1, 2, 3]) + 6 + >>> product_sum_array([1, [2, 3]]) + 11 + >>> product_sum_array([1, [2, [3, 4]]]) + 47 + >>> product_sum_array([0]) + 0 + >>> product_sum_array([-3.5, [1, [0.5]]]) + 1.5 + >>> product_sum_array([1, -2]) + -1 + + """ + return product_sum(array, 1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 267a8b72f97762383e7c313ed20df859115e2815 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 23 Jun 2023 06:56:58 -0700 Subject: [PATCH 2216/2908] Clarify how to add issue numbers in PR template and CONTRIBUTING.md (#8833) * updating DIRECTORY.md * Clarify wording in PR template * Clarify CONTRIBUTING.md wording about adding issue numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add suggested change from review to CONTRIBUTING.md Co-authored-by: Christian Clauss * Incorporate review edit to CONTRIBUTING.md Co-authored-by: Christian Clauss --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .github/pull_request_template.md | 2 +- CONTRIBUTING.md | 7 ++++++- DIRECTORY.md | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b3ba8baf9c34..1f9797fae038 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 include at least one URL that points to Wikipedia or another similar explanation. -* [ ] If this pull request resolves one or more open issues then the commit message contains `Fixes: #{$ISSUE_NO}`. +* [ ] If this pull request resolves one or more open issues then the description above includes the issue number(s) with a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue): "Fixes #ISSUE-NUMBER". diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2bb0c2e39eee..618cca868d83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,12 @@ We appreciate any contribution, from fixing a grammar mistake in a comment to im Your contribution will be tested by our [automated testing on GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions 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 GitHub Actions 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. +Please help us keep our issue list small by adding `Fixes #{$ISSUE_NUMBER}` to the description of pull requests that resolve open issues. +For example, if your pull request fixes issue #10, then please add the following to its description: +``` +Fixes #10 +``` +GitHub will use this tag to [auto-close the issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if and when the PR is merged. #### What is an Algorithm? diff --git a/DIRECTORY.md b/DIRECTORY.md index 83389dab1f56..1414aacf95f7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -146,6 +146,7 @@ * [Decimal To Binary Recursion](conversions/decimal_to_binary_recursion.py) * [Decimal To Hexadecimal](conversions/decimal_to_hexadecimal.py) * [Decimal To Octal](conversions/decimal_to_octal.py) + * [Energy Conversions](conversions/energy_conversions.py) * [Excel Title To Column](conversions/excel_title_to_column.py) * [Hex To Bin](conversions/hex_to_bin.py) * [Hexadecimal To Decimal](conversions/hexadecimal_to_decimal.py) @@ -411,6 +412,7 @@ * [Dijkstra 2](graphs/dijkstra_2.py) * [Dijkstra Algorithm](graphs/dijkstra_algorithm.py) * [Dijkstra Alternate](graphs/dijkstra_alternate.py) + * [Dijkstra Binary Grid](graphs/dijkstra_binary_grid.py) * [Dinic](graphs/dinic.py) * [Directed And Undirected (Weighted) Graph](graphs/directed_and_undirected_(weighted)_graph.py) * [Edmonds Karp Multiple Source And Sink](graphs/edmonds_karp_multiple_source_and_sink.py) From 3bfa89dacf877b1d7a62b14f82d54e8de99a838e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 25 Jun 2023 18:28:01 +0200 Subject: [PATCH 2217/2908] GitHub Actions build: Add more tests (#8837) * GitHub Actions build: Add more tests Re-enable some tests that were disabled in #6591. Fixes #8818 * updating DIRECTORY.md * TODO: Re-enable quantum tests * fails: pytest quantum/bb84.py quantum/q_fourier_transform.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 7 +++---- DIRECTORY.md | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b9cc890b6af..5229edaf8659 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,11 +22,10 @@ jobs: python -m pip install --upgrade pip setuptools six wheel python -m pip install pytest-cov -r requirements.txt - name: Run tests - # See: #6591 for re-enabling tests on Python v3.11 + # TODO: #8818 Re-enable quantum tests run: pytest - --ignore=computer_vision/cnn_classification.py - --ignore=machine_learning/lstm/lstm_prediction.py - --ignore=quantum/ + --ignore=quantum/bb84.py + --ignore=quantum/q_fourier_transform.py --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered diff --git a/DIRECTORY.md b/DIRECTORY.md index 1414aacf95f7..0c21b9537fc1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -167,7 +167,7 @@ * Arrays * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) - * [Product Sum Array](data_structures/arrays/product_sum.py) + * [Product Sum](data_structures/arrays/product_sum.py) * Binary Tree * [Avl Tree](data_structures/binary_tree/avl_tree.py) * [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py) From d764eec655c1c51f5ef3490d27ea72430191a000 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 26 Jun 2023 05:24:50 +0200 Subject: [PATCH 2218/2908] Fix failing pytest quantum/bb84.py (#8838) * Fix failing pytest quantum/bb84.py * Update bb84.py test results to match current qiskit --- .github/workflows/build.yml | 1 - quantum/bb84.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5229edaf8659..fc8cb636979e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,6 @@ jobs: - name: Run tests # TODO: #8818 Re-enable quantum tests run: pytest - --ignore=quantum/bb84.py --ignore=quantum/q_fourier_transform.py --ignore=project_euler/ --ignore=scripts/validate_solutions.py diff --git a/quantum/bb84.py b/quantum/bb84.py index 60d64371fe63..e90a11c2aef3 100644 --- a/quantum/bb84.py +++ b/quantum/bb84.py @@ -64,10 +64,10 @@ def bb84(key_len: int = 8, seed: int | None = None) -> str: key: The key generated using BB84 protocol. >>> bb84(16, seed=0) - '1101101100010000' + '0111110111010010' >>> bb84(8, seed=0) - '01011011' + '10110001' """ # Set up the random number generator. rng = np.random.default_rng(seed=seed) From 62dcbea943e8cc4ea4d83eff115c4e6f6a4808af Mon Sep 17 00:00:00 2001 From: duongoku Date: Mon, 26 Jun 2023 14:39:18 +0700 Subject: [PATCH 2219/2908] Add power sum problem (#8832) * Add powersum problem * Add doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add more doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add more doctests * Improve paramater name * Fix line too long * Remove global variables * Apply suggestions from code review * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- backtracking/power_sum.py | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 backtracking/power_sum.py diff --git a/backtracking/power_sum.py b/backtracking/power_sum.py new file mode 100644 index 000000000000..fcf1429f8570 --- /dev/null +++ b/backtracking/power_sum.py @@ -0,0 +1,93 @@ +""" +Problem source: https://www.hackerrank.com/challenges/the-power-sum/problem +Find the number of ways that a given integer X, can be expressed as the sum +of the Nth powers of unique, natural numbers. For example, if X=13 and N=2. +We have to find all combinations of unique squares adding up to 13. +The only solution is 2^2+3^2. Constraints: 1<=X<=1000, 2<=N<=10. +""" + +from math import pow + + +def backtrack( + needed_sum: int, + power: int, + current_number: int, + current_sum: int, + solutions_count: int, +) -> tuple[int, int]: + """ + >>> backtrack(13, 2, 1, 0, 0) + (0, 1) + >>> backtrack(100, 2, 1, 0, 0) + (0, 3) + >>> backtrack(100, 3, 1, 0, 0) + (0, 1) + >>> backtrack(800, 2, 1, 0, 0) + (0, 561) + >>> backtrack(1000, 10, 1, 0, 0) + (0, 0) + >>> backtrack(400, 2, 1, 0, 0) + (0, 55) + >>> backtrack(50, 1, 1, 0, 0) + (0, 3658) + """ + if current_sum == needed_sum: + # If the sum of the powers is equal to needed_sum, then we have a solution. + solutions_count += 1 + return current_sum, solutions_count + + i_to_n = int(pow(current_number, power)) + if current_sum + i_to_n <= needed_sum: + # If the sum of the powers is less than needed_sum, then continue adding powers. + current_sum += i_to_n + current_sum, solutions_count = backtrack( + needed_sum, power, current_number + 1, current_sum, solutions_count + ) + current_sum -= i_to_n + if i_to_n < needed_sum: + # If the power of i is less than needed_sum, then try with the next power. + current_sum, solutions_count = backtrack( + needed_sum, power, current_number + 1, current_sum, solutions_count + ) + return current_sum, solutions_count + + +def solve(needed_sum: int, power: int) -> int: + """ + >>> solve(13, 2) + 1 + >>> solve(100, 2) + 3 + >>> solve(100, 3) + 1 + >>> solve(800, 2) + 561 + >>> solve(1000, 10) + 0 + >>> solve(400, 2) + 55 + >>> solve(50, 1) + Traceback (most recent call last): + ... + ValueError: Invalid input + needed_sum must be between 1 and 1000, power between 2 and 10. + >>> solve(-10, 5) + Traceback (most recent call last): + ... + ValueError: Invalid input + needed_sum must be between 1 and 1000, power between 2 and 10. + """ + if not (1 <= needed_sum <= 1000 and 2 <= power <= 10): + raise ValueError( + "Invalid input\n" + "needed_sum must be between 1 and 1000, power between 2 and 10." + ) + + return backtrack(needed_sum, power, 1, 0, 0)[1] # Return the solutions_count + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 69f20033e55ae62c337e2fb2146aea5fabf3e5a0 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 26 Jun 2023 02:15:31 -0700 Subject: [PATCH 2220/2908] Remove duplicate implementation of Collatz sequence (#8836) * updating DIRECTORY.md * Remove duplicate implementation of Collatz sequence * updating DIRECTORY.md * Add suggestions from PR review --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 - maths/3n_plus_1.py | 151 -------------------------------------- maths/collatz_sequence.py | 69 +++++++++++------ 3 files changed, 46 insertions(+), 175 deletions(-) delete mode 100644 maths/3n_plus_1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 0c21b9537fc1..1e0e450bca2b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -522,7 +522,6 @@ * [Xgboost Regressor](machine_learning/xgboost_regressor.py) ## Maths - * [3N Plus 1](maths/3n_plus_1.py) * [Abs](maths/abs.py) * [Add](maths/add.py) * [Addition Without Arithmetic](maths/addition_without_arithmetic.py) diff --git a/maths/3n_plus_1.py b/maths/3n_plus_1.py deleted file mode 100644 index f9f6dfeb9faa..000000000000 --- a/maths/3n_plus_1.py +++ /dev/null @@ -1,151 +0,0 @@ -from __future__ import annotations - - -def n31(a: int) -> tuple[list[int], int]: - """ - Returns the Collatz sequence and its length of any positive integer. - >>> n31(4) - ([4, 2, 1], 3) - """ - - if not isinstance(a, int): - msg = f"Must be int, not {type(a).__name__}" - raise TypeError(msg) - if a < 1: - msg = f"Given integer must be positive, not {a}" - raise ValueError(msg) - - path = [a] - while a != 1: - if a % 2 == 0: - a //= 2 - else: - a = 3 * a + 1 - path.append(a) - return path, len(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__": - num = 4 - path, length = n31(num) - print(f"The Collatz sequence of {num} took {length} steps. \nPath: {path}") diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py index 7b3636de69f4..4f3aa5582731 100644 --- a/maths/collatz_sequence.py +++ b/maths/collatz_sequence.py @@ -1,43 +1,66 @@ +""" +The Collatz conjecture is a famous unsolved problem in mathematics. Given a starting +positive integer, define the following sequence: +- If the current term n is even, then the next term is n/2. +- If the current term n is odd, then the next term is 3n + 1. +The conjecture claims that this sequence will always reach 1 for any starting number. + +Other names for this problem include the 3n + 1 problem, the Ulam conjecture, Kakutani's +problem, the Thwaites conjecture, Hasse's algorithm, the Syracuse problem, and the +hailstone sequence. + +Reference: https://en.wikipedia.org/wiki/Collatz_conjecture +""" + from __future__ import annotations +from collections.abc import Generator -def collatz_sequence(n: int) -> list[int]: + +def collatz_sequence(n: int) -> Generator[int, None, None]: """ - 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) + Generate the Collatz sequence starting at n. + >>> tuple(collatz_sequence(2.1)) Traceback (most recent call last): ... - Exception: Sequence only defined for natural numbers - >>> collatz_sequence(0) + Exception: Sequence only defined for positive integers + >>> tuple(collatz_sequence(0)) Traceback (most recent call last): ... - Exception: Sequence only defined for natural numbers - >>> 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] + Exception: Sequence only defined for positive integers + >>> tuple(collatz_sequence(4)) + (4, 2, 1) + >>> tuple(collatz_sequence(11)) + (11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1) + >>> tuple(collatz_sequence(31)) # doctest: +NORMALIZE_WHITESPACE + (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) + >>> tuple(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: - raise Exception("Sequence only defined for natural numbers") + raise Exception("Sequence only defined for positive integers") - sequence = [n] + yield n while n != 1: - n = 3 * n + 1 if n & 1 else n // 2 - sequence.append(n) - return sequence + if n % 2 == 0: + n //= 2 + else: + n = 3 * n + 1 + yield n def main(): n = 43 - sequence = collatz_sequence(n) + sequence = tuple(collatz_sequence(n)) print(sequence) - print(f"collatz sequence from {n} took {len(sequence)} steps.") + print(f"Collatz sequence from {n} took {len(sequence)} steps.") if __name__ == "__main__": From 929d3d9219020d2978d5560e3b931df69a6f2d50 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 07:23:54 +0200 Subject: [PATCH 2221/2908] [pre-commit.ci] pre-commit autoupdate (#8842) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.274 → v0.0.275](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.274...v0.0.275) - [github.com/tox-dev/pyproject-fmt: 0.12.0 → 0.12.1](https://github.com/tox-dev/pyproject-fmt/compare/0.12.0...0.12.1) - [github.com/pre-commit/mirrors-mypy: v1.3.0 → v1.4.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.3.0...v1.4.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d4cc4084ccf..1d92d2ff31c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.274 + rev: v0.0.275 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.12.0" + rev: "0.12.1" hooks: - id: pyproject-fmt @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.3.0 + rev: v1.4.1 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 1e0e450bca2b..d25d665ef28b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -29,6 +29,7 @@ * [Minmax](backtracking/minmax.py) * [N Queens](backtracking/n_queens.py) * [N Queens Math](backtracking/n_queens_math.py) + * [Power Sum](backtracking/power_sum.py) * [Rat In Maze](backtracking/rat_in_maze.py) * [Sudoku](backtracking/sudoku.py) * [Sum Of Subsets](backtracking/sum_of_subsets.py) From c9ee6ed1887fadd25c1c43c31ed55a99b2be5f24 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jul 2023 00:20:35 +0200 Subject: [PATCH 2222/2908] [pre-commit.ci] pre-commit autoupdate (#8853) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.275 → v0.0.276](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.275...v0.0.276) * Update double_ended_queue.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update double_ended_queue.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- data_structures/queue/double_ended_queue.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d92d2ff31c1..42ebeed14fa9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.275 + rev: v0.0.276 hooks: - id: ruff diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 2472371b42fe..44dc863b9a4e 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -54,7 +54,7 @@ class _Iterator: the current node of the iteration. """ - __slots__ = "_cur" + __slots__ = ("_cur",) def __init__(self, cur: Deque._Node | None) -> None: self._cur = cur From a0eec90466beeb3b6ce0f7afd905f96454e9b14c Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Tue, 11 Jul 2023 02:44:12 -0700 Subject: [PATCH 2223/2908] Consolidate duplicate implementations of max subarray (#8849) * Remove max subarray sum duplicate implementations * updating DIRECTORY.md * Rename max_sum_contiguous_subsequence.py * Fix typo in dynamic_programming/max_subarray_sum.py * Remove duplicate divide and conquer max subarray * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 8 +- divide_and_conquer/max_subarray.py | 112 ++++++++++++++++++ divide_and_conquer/max_subarray_sum.py | 78 ------------ dynamic_programming/max_sub_array.py | 93 --------------- dynamic_programming/max_subarray_sum.py | 60 ++++++++++ .../max_sum_contiguous_subsequence.py | 20 ---- maths/kadanes.py | 63 ---------- maths/largest_subarray_sum.py | 21 ---- other/maximum_subarray.py | 32 ----- 9 files changed, 174 insertions(+), 313 deletions(-) create mode 100644 divide_and_conquer/max_subarray.py delete mode 100644 divide_and_conquer/max_subarray_sum.py delete mode 100644 dynamic_programming/max_sub_array.py create mode 100644 dynamic_programming/max_subarray_sum.py delete mode 100644 dynamic_programming/max_sum_contiguous_subsequence.py delete mode 100644 maths/kadanes.py delete mode 100644 maths/largest_subarray_sum.py delete mode 100644 other/maximum_subarray.py diff --git a/DIRECTORY.md b/DIRECTORY.md index d25d665ef28b..77938f45011b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -293,7 +293,7 @@ * [Inversions](divide_and_conquer/inversions.py) * [Kth Order Statistic](divide_and_conquer/kth_order_statistic.py) * [Max Difference Pair](divide_and_conquer/max_difference_pair.py) - * [Max Subarray Sum](divide_and_conquer/max_subarray_sum.py) + * [Max Subarray](divide_and_conquer/max_subarray.py) * [Mergesort](divide_and_conquer/mergesort.py) * [Peak](divide_and_conquer/peak.py) * [Power](divide_and_conquer/power.py) @@ -324,8 +324,7 @@ * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) * [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py) * [Max Product Subarray](dynamic_programming/max_product_subarray.py) - * [Max Sub Array](dynamic_programming/max_sub_array.py) - * [Max Sum Contiguous Subsequence](dynamic_programming/max_sum_contiguous_subsequence.py) + * [Max Subarray Sum](dynamic_programming/max_subarray_sum.py) * [Min Distance Up Bottom](dynamic_programming/min_distance_up_bottom.py) * [Minimum Coin Change](dynamic_programming/minimum_coin_change.py) * [Minimum Cost Path](dynamic_programming/minimum_cost_path.py) @@ -591,12 +590,10 @@ * [Is Square Free](maths/is_square_free.py) * [Jaccard Similarity](maths/jaccard_similarity.py) * [Juggler Sequence](maths/juggler_sequence.py) - * [Kadanes](maths/kadanes.py) * [Karatsuba](maths/karatsuba.py) * [Krishnamurthy Number](maths/krishnamurthy_number.py) * [Kth Lexicographic Permutation](maths/kth_lexicographic_permutation.py) * [Largest Of Very Large Numbers](maths/largest_of_very_large_numbers.py) - * [Largest Subarray Sum](maths/largest_subarray_sum.py) * [Least Common Multiple](maths/least_common_multiple.py) * [Line Length](maths/line_length.py) * [Liouville Lambda](maths/liouville_lambda.py) @@ -733,7 +730,6 @@ * [Linear Congruential Generator](other/linear_congruential_generator.py) * [Lru Cache](other/lru_cache.py) * [Magicdiamondpattern](other/magicdiamondpattern.py) - * [Maximum Subarray](other/maximum_subarray.py) * [Maximum Subsequence](other/maximum_subsequence.py) * [Nested Brackets](other/nested_brackets.py) * [Number Container System](other/number_container_system.py) diff --git a/divide_and_conquer/max_subarray.py b/divide_and_conquer/max_subarray.py new file mode 100644 index 000000000000..851ef621a24c --- /dev/null +++ b/divide_and_conquer/max_subarray.py @@ -0,0 +1,112 @@ +""" +The maximum subarray problem is the task of finding the continuous subarray that has the +maximum sum within a given array of numbers. For example, given the array +[-2, 1, -3, 4, -1, 2, 1, -5, 4], the contiguous subarray with the maximum sum is +[4, -1, 2, 1], which has a sum of 6. + +This divide-and-conquer algorithm finds the maximum subarray in O(n log n) time. +""" +from __future__ import annotations + +import time +from collections.abc import Sequence +from random import randint + +from matplotlib import pyplot as plt + + +def max_subarray( + arr: Sequence[float], low: int, high: int +) -> tuple[int | None, int | None, float]: + """ + Solves the maximum subarray problem using divide and conquer. + :param arr: the given array of numbers + :param low: the start index + :param high: the end index + :return: the start index of the maximum subarray, the end index of the + maximum subarray, and the maximum subarray sum + + >>> nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] + >>> max_subarray(nums, 0, len(nums) - 1) + (3, 6, 6) + >>> nums = [2, 8, 9] + >>> max_subarray(nums, 0, len(nums) - 1) + (0, 2, 19) + >>> nums = [0, 0] + >>> max_subarray(nums, 0, len(nums) - 1) + (0, 0, 0) + >>> nums = [-1.0, 0.0, 1.0] + >>> max_subarray(nums, 0, len(nums) - 1) + (2, 2, 1.0) + >>> nums = [-2, -3, -1, -4, -6] + >>> max_subarray(nums, 0, len(nums) - 1) + (2, 2, -1) + >>> max_subarray([], 0, 0) + (None, None, 0) + """ + if not arr: + return None, None, 0 + if low == high: + return low, high, arr[low] + + mid = (low + high) // 2 + left_low, left_high, left_sum = max_subarray(arr, low, mid) + right_low, right_high, right_sum = max_subarray(arr, mid + 1, high) + cross_left, cross_right, cross_sum = max_cross_sum(arr, 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 + return cross_left, cross_right, cross_sum + + +def max_cross_sum( + arr: Sequence[float], low: int, mid: int, high: int +) -> tuple[int, int, float]: + left_sum, max_left = float("-inf"), -1 + right_sum, max_right = float("-inf"), -1 + + summ: int | float = 0 + for i in range(mid, low - 1, -1): + summ += arr[i] + if summ > left_sum: + left_sum = summ + max_left = i + + summ = 0 + for i in range(mid + 1, high + 1): + summ += arr[i] + if summ > right_sum: + right_sum = summ + max_right = i + + return max_left, max_right, (left_sum + right_sum) + + +def time_max_subarray(input_size: int) -> float: + arr = [randint(1, input_size) for _ in range(input_size)] + start = time.time() + max_subarray(arr, 0, input_size - 1) + end = time.time() + return end - start + + +def plot_runtimes() -> None: + input_sizes = [10, 100, 1000, 10000, 50000, 100000, 200000, 300000, 400000, 500000] + runtimes = [time_max_subarray(input_size) for input_size in input_sizes] + print("No of Inputs\t\tTime Taken") + for input_size, runtime in zip(input_sizes, runtimes): + print(input_size, "\t\t", runtime) + plt.plot(input_sizes, runtimes) + plt.xlabel("Number of Inputs") + plt.ylabel("Time taken in seconds") + plt.show() + + +if __name__ == "__main__": + """ + A random simulation of this algorithm. + """ + from doctest import testmod + + testmod() diff --git a/divide_and_conquer/max_subarray_sum.py b/divide_and_conquer/max_subarray_sum.py deleted file mode 100644 index f23e81719025..000000000000 --- a/divide_and_conquer/max_subarray_sum.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -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) - - -if __name__ == "__main__": - 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), - ) diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py deleted file mode 100644 index 07717fba4172..000000000000 --- a/dynamic_programming/max_sub_array.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -author : Mayank Kumar Jha (mk9440) -""" -from __future__ import annotations - - -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 - - -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] - if summ > 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: - """ - 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]) - 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, - 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 - for i in nums: - current += i - current = max(current, 0) - best = max(best, current) - return best - - -if __name__ == "__main__": - """ - A random simulation of this algorithm. - """ - import time - 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: - 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 ") - plt.show() diff --git a/dynamic_programming/max_subarray_sum.py b/dynamic_programming/max_subarray_sum.py new file mode 100644 index 000000000000..c76943472b97 --- /dev/null +++ b/dynamic_programming/max_subarray_sum.py @@ -0,0 +1,60 @@ +""" +The maximum subarray sum problem is the task of finding the maximum sum that can be +obtained from a contiguous subarray within a given array of numbers. For example, given +the array [-2, 1, -3, 4, -1, 2, 1, -5, 4], the contiguous subarray with the maximum sum +is [4, -1, 2, 1], so the maximum subarray sum is 6. + +Kadane's algorithm is a simple dynamic programming algorithm that solves the maximum +subarray sum problem in O(n) time and O(1) space. + +Reference: https://en.wikipedia.org/wiki/Maximum_subarray_problem +""" +from collections.abc import Sequence + + +def max_subarray_sum( + arr: Sequence[float], allow_empty_subarrays: bool = False +) -> float: + """ + Solves the maximum subarray sum problem using Kadane's algorithm. + :param arr: the given array of numbers + :param allow_empty_subarrays: if True, then the algorithm considers empty subarrays + + >>> max_subarray_sum([2, 8, 9]) + 19 + >>> max_subarray_sum([0, 0]) + 0 + >>> max_subarray_sum([-1.0, 0.0, 1.0]) + 1.0 + >>> max_subarray_sum([1, 2, 3, 4, -2]) + 10 + >>> max_subarray_sum([-2, 1, -3, 4, -1, 2, 1, -5, 4]) + 6 + >>> max_subarray_sum([2, 3, -9, 8, -2]) + 8 + >>> max_subarray_sum([-2, -3, -1, -4, -6]) + -1 + >>> max_subarray_sum([-2, -3, -1, -4, -6], allow_empty_subarrays=True) + 0 + >>> max_subarray_sum([]) + 0 + """ + if not arr: + return 0 + + max_sum = 0 if allow_empty_subarrays else float("-inf") + curr_sum = 0.0 + for num in arr: + curr_sum = max(0 if allow_empty_subarrays else num, curr_sum + num) + max_sum = max(max_sum, curr_sum) + + return max_sum + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + + nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] + print(f"{max_subarray_sum(nums) = }") diff --git a/dynamic_programming/max_sum_contiguous_subsequence.py b/dynamic_programming/max_sum_contiguous_subsequence.py deleted file mode 100644 index bac592370c5d..000000000000 --- a/dynamic_programming/max_sum_contiguous_subsequence.py +++ /dev/null @@ -1,20 +0,0 @@ -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) - - 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)) diff --git a/maths/kadanes.py b/maths/kadanes.py deleted file mode 100644 index c2ea53a6cc84..000000000000 --- a/maths/kadanes.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -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: tuple = ([-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_number = arr[0] - for i in arr: - if i >= 0: - return 0 - elif max_number <= i: - max_number = i - return max_number - - -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 - max_sum = max(max_sum, max_till_element) - max_till_element = max(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.") diff --git a/maths/largest_subarray_sum.py b/maths/largest_subarray_sum.py deleted file mode 100644 index 90f92c7127bf..000000000000 --- a/maths/largest_subarray_sum.py +++ /dev/null @@ -1,21 +0,0 @@ -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] - max_so_far = max(max_so_far, max_ending_here) - max_ending_here = max(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/other/maximum_subarray.py b/other/maximum_subarray.py deleted file mode 100644 index 1c8c8cabcd2d..000000000000 --- a/other/maximum_subarray.py +++ /dev/null @@ -1,32 +0,0 @@ -from collections.abc import Sequence - - -def max_subarray_sum(nums: Sequence[int]) -> int: - """Return the maximum possible sum amongst all non - empty subarrays. - - Raises: - ValueError: when nums is empty. - - >>> max_subarray_sum([1,2,3,4,-2]) - 10 - >>> max_subarray_sum([-2,1,-3,4,-1,2,1,-5,4]) - 6 - """ - if not nums: - raise ValueError("Input sequence should not be empty") - - curr_max = ans = nums[0] - nums_len = len(nums) - - for i in range(1, nums_len): - num = nums[i] - curr_max = max(curr_max + num, num) - ans = max(curr_max, ans) - - return ans - - -if __name__ == "__main__": - n = int(input("Enter number of elements : ").strip()) - array = list(map(int, input("\nEnter the numbers : ").strip().split()))[:n] - print(max_subarray_sum(array)) From 44b1bcc7c7e0f15385530bf54c59ad4eb86fef0b Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Tue, 11 Jul 2023 10:51:21 +0100 Subject: [PATCH 2224/2908] Fix failing tests from ruff/newton_raphson (ignore S307 "possibly insecure function") (#8862) * chore: Fix failing tests (ignore S307 "possibly insecure function") * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: Move noqa back to right line --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- arithmetic_analysis/newton_raphson.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index aee2f07e5743..1b90ad4177f6 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -25,9 +25,11 @@ def newton_raphson( """ x = a while True: - x = Decimal(x) - (Decimal(eval(func)) / Decimal(eval(str(diff(func))))) + x = Decimal(x) - ( + Decimal(eval(func)) / Decimal(eval(str(diff(func)))) # noqa: S307 + ) # This number dictates the accuracy of the answer - if abs(eval(func)) < precision: + if abs(eval(func)) < precision: # noqa: S307 return float(x) From f614ed72170011d2d439f7901e1c8daa7deac8c4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:55:32 +0200 Subject: [PATCH 2225/2908] [pre-commit.ci] pre-commit autoupdate (#8860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.276 → v0.0.277](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.276...v0.0.277) - [github.com/tox-dev/pyproject-fmt: 0.12.1 → 0.13.0](https://github.com/tox-dev/pyproject-fmt/compare/0.12.1...0.13.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42ebeed14fa9..bf30703bdffc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.276 + rev: v0.0.277 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.12.1" + rev: "0.13.0" hooks: - id: pyproject-fmt From 5aefc00f0f1c692ce772ddbc616d7cd91233236b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jul 2023 09:58:22 +0530 Subject: [PATCH 2226/2908] [pre-commit.ci] pre-commit autoupdate (#8872) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.277 → v0.0.278](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.277...v0.0.278) - [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf30703bdffc..13b955dd374f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.277 + rev: v0.0.278 hooks: - id: ruff - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black From 93fb169627ea9fe43436a312fdfa751818808180 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sat, 22 Jul 2023 13:05:10 +0300 Subject: [PATCH 2227/2908] [Upgrade Ruff] Fix all errors raised from ruff (#8879) * chore: Fix tests * chore: Fix failing ruff * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * chore: Fix ruff errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore: Fix ruff errors * chore: Fix ruff errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update cellular_automata/game_of_life.py Co-authored-by: Christian Clauss * chore: Update ruff version in pre-commit * chore: Fix ruff errors * Update edmonds_karp_multiple_source_and_sink.py * Update factorial.py * Update primelib.py * Update min_cost_string_conversion.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- cellular_automata/game_of_life.py | 2 +- data_structures/binary_tree/red_black_tree.py | 2 +- data_structures/trie/radix_tree.py | 4 ++-- divide_and_conquer/convex_hull.py | 2 +- ...directed_and_undirected_(weighted)_graph.py | 18 +++++++++--------- .../edmonds_karp_multiple_source_and_sink.py | 2 +- maths/factorial.py | 2 +- maths/primelib.py | 2 +- other/davisb_putnamb_logemannb_loveland.py | 2 +- project_euler/problem_009/sol3.py | 16 ++++++++++------ quantum/ripple_adder_classic.py | 2 +- strings/min_cost_string_conversion.py | 2 +- web_programming/convert_number_to_words.py | 4 +--- 14 files changed, 32 insertions(+), 30 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 13b955dd374f..5adf12cc70c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.278 + rev: v0.0.280 hooks: - id: ruff diff --git a/cellular_automata/game_of_life.py b/cellular_automata/game_of_life.py index 3382af7b5db6..b69afdce03eb 100644 --- a/cellular_automata/game_of_life.py +++ b/cellular_automata/game_of_life.py @@ -98,7 +98,7 @@ def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool: if pt: if alive < 2: state = False - elif alive == 2 or alive == 3: + elif alive in {2, 3}: state = True elif alive > 3: state = False diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 3ebc8d63939b..4ebe0e927ca0 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -152,7 +152,7 @@ def _insert_repair(self) -> None: self.grandparent.color = 1 self.grandparent._insert_repair() - def remove(self, label: int) -> RedBlackTree: + def remove(self, label: int) -> RedBlackTree: # noqa: PLR0912 """Remove label from this tree.""" if self.label == label: if self.left and self.right: diff --git a/data_structures/trie/radix_tree.py b/data_structures/trie/radix_tree.py index 66890346ec2b..cf2f25c29f13 100644 --- a/data_structures/trie/radix_tree.py +++ b/data_structures/trie/radix_tree.py @@ -156,7 +156,7 @@ def delete(self, word: str) -> bool: del self.nodes[word[0]] # We merge the current node with its only child if len(self.nodes) == 1 and not self.is_leaf: - merging_node = list(self.nodes.values())[0] + merging_node = next(iter(self.nodes.values())) self.is_leaf = merging_node.is_leaf self.prefix += merging_node.prefix self.nodes = merging_node.nodes @@ -165,7 +165,7 @@ def delete(self, word: str) -> bool: incoming_node.is_leaf = False # If there is 1 edge, we merge it with its child else: - merging_node = list(incoming_node.nodes.values())[0] + merging_node = next(iter(incoming_node.nodes.values())) incoming_node.is_leaf = merging_node.is_leaf incoming_node.prefix += merging_node.prefix incoming_node.nodes = merging_node.nodes diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 1ad933417da6..1d1bf301def5 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -266,7 +266,7 @@ def convex_hull_bf(points: list[Point]) -> list[Point]: 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: + if k not in {i, j}: det_k = _det(points[i], points[j], points[k]) if det_k > 0: diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index b29485031083..8ca645fdace8 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -39,7 +39,7 @@ def dfs(self, s=-2, d=-1): stack = [] visited = [] if s == -2: - s = list(self.graph)[0] + s = next(iter(self.graph)) stack.append(s) visited.append(s) ss = s @@ -87,7 +87,7 @@ def bfs(self, s=-2): d = deque() visited = [] if s == -2: - s = list(self.graph)[0] + s = next(iter(self.graph)) d.append(s) visited.append(s) while d: @@ -114,7 +114,7 @@ def topological_sort(self, s=-2): stack = [] visited = [] if s == -2: - s = list(self.graph)[0] + s = next(iter(self.graph)) stack.append(s) visited.append(s) ss = s @@ -146,7 +146,7 @@ def topological_sort(self, s=-2): def cycle_nodes(self): stack = [] visited = [] - s = list(self.graph)[0] + s = next(iter(self.graph)) stack.append(s) visited.append(s) parent = -2 @@ -199,7 +199,7 @@ def cycle_nodes(self): def has_cycle(self): stack = [] visited = [] - s = list(self.graph)[0] + s = next(iter(self.graph)) stack.append(s) visited.append(s) parent = -2 @@ -305,7 +305,7 @@ def dfs(self, s=-2, d=-1): stack = [] visited = [] if s == -2: - s = list(self.graph)[0] + s = next(iter(self.graph)) stack.append(s) visited.append(s) ss = s @@ -353,7 +353,7 @@ def bfs(self, s=-2): d = deque() visited = [] if s == -2: - s = list(self.graph)[0] + s = next(iter(self.graph)) d.append(s) visited.append(s) while d: @@ -371,7 +371,7 @@ def degree(self, u): def cycle_nodes(self): stack = [] visited = [] - s = list(self.graph)[0] + s = next(iter(self.graph)) stack.append(s) visited.append(s) parent = -2 @@ -424,7 +424,7 @@ def cycle_nodes(self): def has_cycle(self): stack = [] visited = [] - s = list(self.graph)[0] + s = next(iter(self.graph)) stack.append(s) visited.append(s) parent = -2 diff --git a/graphs/edmonds_karp_multiple_source_and_sink.py b/graphs/edmonds_karp_multiple_source_and_sink.py index d0610804109f..5c774f4b812b 100644 --- a/graphs/edmonds_karp_multiple_source_and_sink.py +++ b/graphs/edmonds_karp_multiple_source_and_sink.py @@ -113,7 +113,7 @@ def _algorithm(self): vertices_list = [ i for i in range(self.verticies_count) - if i != self.source_index and i != self.sink_index + if i not in {self.source_index, self.sink_index} ] # move through list diff --git a/maths/factorial.py b/maths/factorial.py index bbf0efc011d8..18cacdef9b1f 100644 --- a/maths/factorial.py +++ b/maths/factorial.py @@ -55,7 +55,7 @@ def factorial_recursive(n: int) -> 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) + return 1 if n in {0, 1} else n * factorial(n - 1) if __name__ == "__main__": diff --git a/maths/primelib.py b/maths/primelib.py index 81d5737063f0..28b5aee9dcc8 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -154,7 +154,7 @@ def prime_factorization(number): quotient = number - if number == 0 or number == 1: + if number in {0, 1}: ans.append(number) # if 'number' not prime then builds the prime factorization of 'number' diff --git a/other/davisb_putnamb_logemannb_loveland.py b/other/davisb_putnamb_logemannb_loveland.py index a1bea5b3992e..f5fb103ba528 100644 --- a/other/davisb_putnamb_logemannb_loveland.py +++ b/other/davisb_putnamb_logemannb_loveland.py @@ -253,7 +253,7 @@ def find_unit_clauses( unit_symbols = [] for clause in clauses: if len(clause) == 1: - unit_symbols.append(list(clause.literals.keys())[0]) + unit_symbols.append(next(iter(clause.literals.keys()))) else: f_count, n_count = 0, 0 for literal, value in clause.literals.items(): diff --git a/project_euler/problem_009/sol3.py b/project_euler/problem_009/sol3.py index d299f821d4f6..37340d3063bb 100644 --- a/project_euler/problem_009/sol3.py +++ b/project_euler/problem_009/sol3.py @@ -28,12 +28,16 @@ def solution() -> int: 31875000 """ - return [ - a * b * (1000 - a - b) - for a in range(1, 999) - for b in range(a, 999) - if (a * a + b * b == (1000 - a - b) ** 2) - ][0] + return next( + iter( + [ + a * b * (1000 - a - b) + for a in range(1, 999) + for b in range(a, 999) + if (a * a + b * b == (1000 - a - b) ** 2) + ] + ) + ) if __name__ == "__main__": diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py index b604395bc583..2284141ccac2 100644 --- a/quantum/ripple_adder_classic.py +++ b/quantum/ripple_adder_classic.py @@ -107,7 +107,7 @@ def ripple_adder( res = qiskit.execute(circuit, backend, shots=1).result() # The result is in binary. Convert it back to int - return int(list(res.get_counts())[0], 2) + return int(next(iter(res.get_counts())), 2) if __name__ == "__main__": diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index 089c2532f900..0fad0b88c370 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -61,7 +61,7 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: if i == 0 and j == 0: return [] else: - if ops[i][j][0] == "C" or ops[i][j][0] == "R": + if ops[i][j][0] in {"C", "R"}: seq = assemble_transformation(ops, i - 1, j - 1) seq.append(ops[i][j]) return seq diff --git a/web_programming/convert_number_to_words.py b/web_programming/convert_number_to_words.py index 1e293df9660c..dac9e3e38e7c 100644 --- a/web_programming/convert_number_to_words.py +++ b/web_programming/convert_number_to_words.py @@ -90,9 +90,7 @@ def convert(number: int) -> str: else: addition = "" if counter in placevalue: - if current == 0 and ((temp_num % 100) // 10) == 0: - addition = "" - else: + if current != 0 and ((temp_num % 100) // 10) != 0: addition = placevalue[counter] if ((temp_num % 100) // 10) == 1: words = teens[current] + addition + words From f7531d9874e0dd3682bf0ed7ae408927e1fae472 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 22 Jul 2023 03:11:04 -0700 Subject: [PATCH 2228/2908] Add note in `CONTRIBUTING.md` about not asking to be assigned to issues (#8871) * Add note in CONTRIBUTING.md about not asking to be assigned to issues Add a paragraph to CONTRIBUTING.md explicitly asking contributors to not ask to be assigned to issues * Update CONTRIBUTING.md * Update CONTRIBUTING.md --------- Co-authored-by: Christian Clauss --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 618cca868d83..4a1bb652738f 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 GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions 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 GitHub Actions 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. +If you are interested in resolving an [open issue](https://github.com/TheAlgorithms/Python/issues), simply make a pull request with your proposed fix. __We do not assign issues in this repo__ so please do not ask for permission to work on an issue. + Please help us keep our issue list small by adding `Fixes #{$ISSUE_NUMBER}` to the description of pull requests that resolve open issues. For example, if your pull request fixes issue #10, then please add the following to its description: ``` From 9e08c7726dee5b18585a76e54c71922ca96c0b3a Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sat, 22 Jul 2023 13:34:19 +0300 Subject: [PATCH 2229/2908] Small docstring time complexity fix in number_container _system (#8875) * fix: Write time is O(log n) not O(n log n) * chore: Update pre-commit ruff version * revert: Undo previous commit --- other/number_container_system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/number_container_system.py b/other/number_container_system.py index f547bc8a229e..6c95dd0a3544 100644 --- a/other/number_container_system.py +++ b/other/number_container_system.py @@ -1,6 +1,6 @@ """ A number container system that uses binary search to delete and insert values into -arrays with O(n logn) write times and O(1) read times. +arrays with O(log n) write times and O(1) read times. This container system holds integers at indexes. From a03b739d23b59890b59d2d2288ebaa56e3be47ce Mon Sep 17 00:00:00 2001 From: Sangmin Jeon Date: Mon, 24 Jul 2023 18:29:05 +0900 Subject: [PATCH 2230/2908] Fix `radix_tree.py` insertion fail in ["*X", "*XX"] cases (#8870) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix insertion fail in ["*X", "*XX"] cases Consider a word, and a copy of that word, but with the last letter repeating twice. (e.g., ["ABC", "ABCC"]) When adding the second word's last letter, it only compares the previous word's prefix—the last letter of the word already in the Radix Tree: 'C'—and the letter to be added—the last letter of the word we're currently adding: 'C'. So it wrongly passes the "Case 1" check, marks the current node as a leaf node when it already was, then returns when there's still one more letter to add. The issue arises because `prefix` includes the letter of the node itself. (e.g., `nodes: {'C' : RadixNode()}, is_leaf: True, prefix: 'C'`) It can be easily fixed by simply adding the `is_leaf` check, asking if there are more letters to be added. - Test Case: `"A AA AAA AAAA"` - Fixed correct output: ``` Words: ['A', 'AA', 'AAA', 'AAAA'] Tree: - A (leaf) -- A (leaf) --- A (leaf) ---- A (leaf) ``` - Current incorrect output: ``` Words: ['A', 'AA', 'AAA', 'AAAA'] Tree: - A (leaf) -- AA (leaf) --- A (leaf) ``` *N.B.* This passed test cases for [Croatian Open Competition in Informatics 2012/2013 Contest #3 Task 5 HERKABE](https://hsin.hr/coci/archive/2012_2013/) * Add a doctest for previous fix * improve doctest readability --- data_structures/trie/radix_tree.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/data_structures/trie/radix_tree.py b/data_structures/trie/radix_tree.py index cf2f25c29f13..fadc50cb49a7 100644 --- a/data_structures/trie/radix_tree.py +++ b/data_structures/trie/radix_tree.py @@ -54,10 +54,17 @@ def insert(self, word: str) -> None: word (str): word to insert >>> RadixNode("myprefix").insert("mystring") + + >>> root = RadixNode() + >>> root.insert_many(['myprefix', 'myprefixA', 'myprefixAA']) + >>> root.print_tree() + - myprefix (leaf) + -- A (leaf) + --- A (leaf) """ # Case 1: If the word is the prefix of the node # Solution: We set the current node as leaf - if self.prefix == word: + if self.prefix == word and not self.is_leaf: self.is_leaf = True # Case 2: The node has no edges that have a prefix to the word From b77e6adf3abba674eb83ab7c0182bd6c89c08891 Mon Sep 17 00:00:00 2001 From: HManiac74 <63391783+HManiac74@users.noreply.github.com> Date: Tue, 25 Jul 2023 22:23:20 +0200 Subject: [PATCH 2231/2908] Add Docker devcontainer configuration files (#8887) * Added Docker container configuration files * Update Dockerfile Copy and install requirements * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated Docker devcontainer configuration * Update requierements.txt * Update Dockerfile * Update Dockerfile * Update .devcontainer/devcontainer.json Co-authored-by: Christian Clauss * Update Dockerfile * Update Dockerfile. Add linebreak --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .devcontainer/Dockerfile | 6 +++++ .devcontainer/devcontainer.json | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000000..27b25c09b1c9 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,6 @@ +# https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/README.md +ARG VARIANT=3.11-bookworm +FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT} +COPY requirements.txt /tmp/pip-tmp/ +RUN python3 -m pip install --upgrade pip \ + && python3 -m pip install --no-cache-dir install ruff -r /tmp/pip-tmp/requirements.txt diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000000..c5a855b2550c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,42 @@ +{ + "name": "Python 3", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local on arm64/Apple Silicon. + "VARIANT": "3.11-bookworm", + } + }, + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance" + ] + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip3 install --user -r requirements.txt", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} From dbaff345724040b270b3097cb02759f36ce0ef46 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 28 Jul 2023 18:53:09 +0200 Subject: [PATCH 2232/2908] Fix ruff rules ISC flake8-implicit-str-concat (#8892) --- ciphers/diffie_hellman.py | 244 ++++++++++++------------- compression/burrows_wheeler.py | 2 +- neural_network/input_data.py | 4 +- pyproject.toml | 2 +- strings/is_srilankan_phone_number.py | 4 +- web_programming/world_covid19_stats.py | 5 +- 6 files changed, 128 insertions(+), 133 deletions(-) diff --git a/ciphers/diffie_hellman.py b/ciphers/diffie_hellman.py index cd40a6b9c3b3..aec7fb3eaf17 100644 --- a/ciphers/diffie_hellman.py +++ b/ciphers/diffie_hellman.py @@ -10,13 +10,13 @@ 5: { "prime": int( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" - + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" - + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" - + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" - + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" - + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" - + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" - + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", base=16, ), "generator": 2, @@ -25,16 +25,16 @@ 14: { "prime": int( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" - + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" - + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" - + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" - + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" - + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" - + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" - + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" - + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" - + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" - + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", base=16, ), "generator": 2, @@ -43,21 +43,21 @@ 15: { "prime": int( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" - + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" - + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" - + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" - + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" - + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" - + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" - + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" - + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" - + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" - + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" - + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" - + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" - + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" - + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" - + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", base=16, ), "generator": 2, @@ -66,27 +66,27 @@ 16: { "prime": int( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" - + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" - + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" - + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" - + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" - + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" - + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" - + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" - + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" - + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" - + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" - + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" - + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" - + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" - + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" - + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" - + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" - + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" - + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" - + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" - + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" - + "FFFFFFFFFFFFFFFF", + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + "FFFFFFFFFFFFFFFF", base=16, ), "generator": 2, @@ -95,33 +95,33 @@ 17: { "prime": int( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" - + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" - + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" - + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" - + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" - + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" - + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" - + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" - + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" - + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" - + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" - + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" - + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" - + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" - + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" - + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" - + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" - + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" - + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" - + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" - + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" - + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" - + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" - + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" - + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" - + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" - + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" - + "6DCC4024FFFFFFFFFFFFFFFF", + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + "6DCC4024FFFFFFFFFFFFFFFF", base=16, ), "generator": 2, @@ -130,48 +130,48 @@ 18: { "prime": int( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" - + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" - + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" - + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" - + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" - + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" - + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" - + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" - + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" - + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" - + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" - + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" - + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" - + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" - + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" - + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" - + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" - + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" - + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" - + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" - + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" - + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" - + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" - + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" - + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" - + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" - + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" - + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" - + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" - + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" - + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" - + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" - + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" - + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" - + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" - + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" - + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" - + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" - + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" - + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" - + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" - + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" - + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", base=16, ), "generator": 2, diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 0916b8a654d2..52bb045d9398 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -150,7 +150,7 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: 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/neural_network/input_data.py b/neural_network/input_data.py index 94c018ece9ba..a58e64907e45 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -263,9 +263,7 @@ def _maybe_download(filename, work_directory, source_url): return filepath -@deprecated( - None, "Please use alternatives such as:" " tensorflow_datasets.load('mnist')" -) +@deprecated(None, "Please use alternatives such as: tensorflow_datasets.load('mnist')") def read_data_sets( train_dir, fake_data=False, diff --git a/pyproject.toml b/pyproject.toml index 4f21a95190da..f9091fb8578d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ select = [ # https://beta.ruff.rs/docs/rules "ICN", # flake8-import-conventions "INP", # flake8-no-pep420 "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat "N", # pep8-naming "NPY", # NumPy-specific rules "PGH", # pygrep-hooks @@ -72,7 +73,6 @@ select = [ # https://beta.ruff.rs/docs/rules # "DJ", # flake8-django # "ERA", # eradicate -- DO NOT FIX # "FBT", # flake8-boolean-trap # FIX ME - # "ISC", # flake8-implicit-str-concat # FIX ME # "PD", # pandas-vet # "PT", # flake8-pytest-style # "PTH", # flake8-use-pathlib # FIX ME diff --git a/strings/is_srilankan_phone_number.py b/strings/is_srilankan_phone_number.py index 7bded93f7f1d..6456f85e1a3d 100644 --- a/strings/is_srilankan_phone_number.py +++ b/strings/is_srilankan_phone_number.py @@ -22,9 +22,7 @@ def is_sri_lankan_phone_number(phone: str) -> bool: False """ - pattern = re.compile( - r"^(?:0|94|\+94|0{2}94)" r"7(0|1|2|4|5|6|7|8)" r"(-| |)" r"\d{7}$" - ) + pattern = re.compile(r"^(?:0|94|\+94|0{2}94)7(0|1|2|4|5|6|7|8)(-| |)\d{7}$") return bool(re.search(pattern, phone)) diff --git a/web_programming/world_covid19_stats.py b/web_programming/world_covid19_stats.py index 1dd1ff6d188e..ca81abdc4ce9 100644 --- a/web_programming/world_covid19_stats.py +++ b/web_programming/world_covid19_stats.py @@ -22,6 +22,5 @@ def world_covid19_stats(url: str = "https://www.worldometers.info/coronavirus") 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") + print("\033[1m COVID-19 Status of the World \033[0m\n") + print("\n".join(f"{key}\n{value}" for key, value in world_covid19_stats().items())) From 46454e204cc587d1ef044e4b1a11050c30aab4f6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 28 Jul 2023 18:54:45 +0200 Subject: [PATCH 2233/2908] [skip-ci] In .devcontainer/Dockerfile: pipx install pre-commit ruff (#8893) [skip-ci] In .devcontainer/Dockerfile: pipx install pre-commit ruff --- .devcontainer/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 27b25c09b1c9..b5a5347c66b0 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,4 +3,6 @@ ARG VARIANT=3.11-bookworm FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT} COPY requirements.txt /tmp/pip-tmp/ RUN python3 -m pip install --upgrade pip \ - && python3 -m pip install --no-cache-dir install ruff -r /tmp/pip-tmp/requirements.txt + && python3 -m pip install --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ + && pipx install pre-commit ruff \ + && pre-commit install From 4a83e3f0b1b2a3b414134c3498e57c0fea3b9fcf Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Fri, 28 Jul 2023 21:12:31 +0300 Subject: [PATCH 2234/2908] Fix failing build due to missing requirement (#8900) * feat(cellular_automata): Create wa-tor algorithm * updating DIRECTORY.md * chore(quality): Implement algo-keeper bot changes * build: Fix broken ci * git rm cellular_automata/wa_tor.py * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index acfbc823e77f..2702523d542e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ pandas pillow projectq qiskit +qiskit-aer requests rich scikit-fuzzy From e406801f9e3967ff0533dfe8cb98a3249db48d33 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 28 Jul 2023 11:17:46 -0700 Subject: [PATCH 2235/2908] Reimplement polynomial_regression.py (#8889) * Reimplement polynomial_regression.py Rename machine_learning/polymonial_regression.py to machine_learning/polynomial_regression.py Reimplement machine_learning/polynomial_regression.py using numpy because the old original implementation was just a how-to on doing polynomial regression using sklearn Add detailed function documentation, doctests, and algorithm explanation * updating DIRECTORY.md * Fix matrix formatting in docstrings * Try to fix failing doctest * Debugging failing doctest * Fix failing doctest attempt 2 * Remove unnecessary return value descriptions in docstrings * Readd placeholder doctest for main function * Fix typo in algorithm description --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- machine_learning/polymonial_regression.py | 44 ----- machine_learning/polynomial_regression.py | 213 ++++++++++++++++++++++ 3 files changed, 214 insertions(+), 45 deletions(-) delete mode 100644 machine_learning/polymonial_regression.py create mode 100644 machine_learning/polynomial_regression.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 77938f45011b..133a1ab019d8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -511,7 +511,7 @@ * Lstm * [Lstm Prediction](machine_learning/lstm/lstm_prediction.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) - * [Polymonial Regression](machine_learning/polymonial_regression.py) + * [Polynomial Regression](machine_learning/polynomial_regression.py) * [Scoring Functions](machine_learning/scoring_functions.py) * [Self Organizing Map](machine_learning/self_organizing_map.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) diff --git a/machine_learning/polymonial_regression.py b/machine_learning/polymonial_regression.py deleted file mode 100644 index 487fb814526f..000000000000 --- a/machine_learning/polymonial_regression.py +++ /dev/null @@ -1,44 +0,0 @@ -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 -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( - "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 - - -X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0) - - -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() - - -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 diff --git a/machine_learning/polynomial_regression.py b/machine_learning/polynomial_regression.py new file mode 100644 index 000000000000..5bafea96f41e --- /dev/null +++ b/machine_learning/polynomial_regression.py @@ -0,0 +1,213 @@ +""" +Polynomial regression is a type of regression analysis that models the relationship +between a predictor x and the response y as an mth-degree polynomial: + +y = β₀ + β₁x + β₂x² + ... + βₘxᵐ + ε + +By treating x, x², ..., xᵐ as distinct variables, we see that polynomial regression is a +special case of multiple linear regression. Therefore, we can use ordinary least squares +(OLS) estimation to estimate the vector of model parameters β = (β₀, β₁, β₂, ..., βₘ) +for polynomial regression: + +β = (XᵀX)⁻¹Xᵀy = X⁺y + +where X is the design matrix, y is the response vector, and X⁺ denotes the Moore–Penrose +pseudoinverse of X. In the case of polynomial regression, the design matrix is + + |1 x₁ x₁² ⋯ x₁ᵐ| +X = |1 x₂ x₂² ⋯ x₂ᵐ| + |⋮ ⋮ ⋮ ⋱ ⋮ | + |1 xₙ xₙ² ⋯ xₙᵐ| + +In OLS estimation, inverting XᵀX to compute X⁺ can be very numerically unstable. This +implementation sidesteps this need to invert XᵀX by computing X⁺ using singular value +decomposition (SVD): + +β = VΣ⁺Uᵀy + +where UΣVᵀ is an SVD of X. + +References: + - https://en.wikipedia.org/wiki/Polynomial_regression + - https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_inverse + - https://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares + - https://en.wikipedia.org/wiki/Singular_value_decomposition +""" + +import matplotlib.pyplot as plt +import numpy as np + + +class PolynomialRegression: + __slots__ = "degree", "params" + + def __init__(self, degree: int) -> None: + """ + @raises ValueError: if the polynomial degree is negative + """ + if degree < 0: + raise ValueError("Polynomial degree must be non-negative") + + self.degree = degree + self.params = None + + @staticmethod + def _design_matrix(data: np.ndarray, degree: int) -> np.ndarray: + """ + Constructs a polynomial regression design matrix for the given input data. For + input data x = (x₁, x₂, ..., xₙ) and polynomial degree m, the design matrix is + the Vandermonde matrix + + |1 x₁ x₁² ⋯ x₁ᵐ| + X = |1 x₂ x₂² ⋯ x₂ᵐ| + |⋮ ⋮ ⋮ ⋱ ⋮ | + |1 xₙ xₙ² ⋯ xₙᵐ| + + Reference: https://en.wikipedia.org/wiki/Vandermonde_matrix + + @param data: the input predictor values x, either for model fitting or for + prediction + @param degree: the polynomial degree m + @returns: the Vandermonde matrix X (see above) + @raises ValueError: if input data is not N x 1 + + >>> x = np.array([0, 1, 2]) + >>> PolynomialRegression._design_matrix(x, degree=0) + array([[1], + [1], + [1]]) + >>> PolynomialRegression._design_matrix(x, degree=1) + array([[1, 0], + [1, 1], + [1, 2]]) + >>> PolynomialRegression._design_matrix(x, degree=2) + array([[1, 0, 0], + [1, 1, 1], + [1, 2, 4]]) + >>> PolynomialRegression._design_matrix(x, degree=3) + array([[1, 0, 0, 0], + [1, 1, 1, 1], + [1, 2, 4, 8]]) + >>> PolynomialRegression._design_matrix(np.array([[0, 0], [0 , 0]]), degree=3) + Traceback (most recent call last): + ... + ValueError: Data must have dimensions N x 1 + """ + rows, *remaining = data.shape + if remaining: + raise ValueError("Data must have dimensions N x 1") + + return np.vander(data, N=degree + 1, increasing=True) + + def fit(self, x_train: np.ndarray, y_train: np.ndarray) -> None: + """ + Computes the polynomial regression model parameters using ordinary least squares + (OLS) estimation: + + β = (XᵀX)⁻¹Xᵀy = X⁺y + + where X⁺ denotes the Moore–Penrose pseudoinverse of the design matrix X. This + function computes X⁺ using singular value decomposition (SVD). + + References: + - https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_inverse + - https://en.wikipedia.org/wiki/Singular_value_decomposition + - https://en.wikipedia.org/wiki/Multicollinearity + + @param x_train: the predictor values x for model fitting + @param y_train: the response values y for model fitting + @raises ArithmeticError: if X isn't full rank, then XᵀX is singular and β + doesn't exist + + >>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + >>> y = x**3 - 2 * x**2 + 3 * x - 5 + >>> poly_reg = PolynomialRegression(degree=3) + >>> poly_reg.fit(x, y) + >>> poly_reg.params + array([-5., 3., -2., 1.]) + >>> poly_reg = PolynomialRegression(degree=20) + >>> poly_reg.fit(x, y) + Traceback (most recent call last): + ... + ArithmeticError: Design matrix is not full rank, can't compute coefficients + + Make sure errors don't grow too large: + >>> coefs = np.array([-250, 50, -2, 36, 20, -12, 10, 2, -1, -15, 1]) + >>> y = PolynomialRegression._design_matrix(x, len(coefs) - 1) @ coefs + >>> poly_reg = PolynomialRegression(degree=len(coefs) - 1) + >>> poly_reg.fit(x, y) + >>> np.allclose(poly_reg.params, coefs, atol=10e-3) + True + """ + X = PolynomialRegression._design_matrix(x_train, self.degree) # noqa: N806 + _, cols = X.shape + if np.linalg.matrix_rank(X) < cols: + raise ArithmeticError( + "Design matrix is not full rank, can't compute coefficients" + ) + + # np.linalg.pinv() computes the Moore–Penrose pseudoinverse using SVD + self.params = np.linalg.pinv(X) @ y_train + + def predict(self, data: np.ndarray) -> np.ndarray: + """ + Computes the predicted response values y for the given input data by + constructing the design matrix X and evaluating y = Xβ. + + @param data: the predictor values x for prediction + @returns: the predicted response values y = Xβ + @raises ArithmeticError: if this function is called before the model + parameters are fit + + >>> x = np.array([0, 1, 2, 3, 4]) + >>> y = x**3 - 2 * x**2 + 3 * x - 5 + >>> poly_reg = PolynomialRegression(degree=3) + >>> poly_reg.fit(x, y) + >>> poly_reg.predict(np.array([-1])) + array([-11.]) + >>> poly_reg.predict(np.array([-2])) + array([-27.]) + >>> poly_reg.predict(np.array([6])) + array([157.]) + >>> PolynomialRegression(degree=3).predict(x) + Traceback (most recent call last): + ... + ArithmeticError: Predictor hasn't been fit yet + """ + if self.params is None: + raise ArithmeticError("Predictor hasn't been fit yet") + + return PolynomialRegression._design_matrix(data, self.degree) @ self.params + + +def main() -> None: + """ + Fit a polynomial regression model to predict fuel efficiency using seaborn's mpg + dataset + + >>> pass # Placeholder, function is only for demo purposes + """ + import seaborn as sns + + mpg_data = sns.load_dataset("mpg") + + poly_reg = PolynomialRegression(degree=2) + poly_reg.fit(mpg_data.weight, mpg_data.mpg) + + weight_sorted = np.sort(mpg_data.weight) + predictions = poly_reg.predict(weight_sorted) + + plt.scatter(mpg_data.weight, mpg_data.mpg, color="gray", alpha=0.5) + plt.plot(weight_sorted, predictions, color="red", linewidth=3) + plt.title("Predicting Fuel Efficiency Using Polynomial Regression") + plt.xlabel("Weight (lbs)") + plt.ylabel("Fuel Efficiency (mpg)") + plt.show() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + main() From a0b642cfe58c215b8ead3f2a40655e144e07aacc Mon Sep 17 00:00:00 2001 From: Alex Bernhardt <54606095+FatAnorexic@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:30:05 -0400 Subject: [PATCH 2236/2908] Physics/basic orbital capture (#8857) * Added file basic_orbital_capture * updating DIRECTORY.md * added second source * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed spelling errors * accepted changes * updating DIRECTORY.md * corrected spelling error * Added file basic_orbital_capture * added second source * fixed spelling errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * applied changes * reviewed and checked file * added doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed redundant constnant * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added scipy imports * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added doctests to capture_radii and scipy const * fixed conflicts * finalizing file. Added tests * Update physics/basic_orbital_capture.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + physics/basic_orbital_capture.py | 178 +++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 physics/basic_orbital_capture.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 133a1ab019d8..29514579ceb0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -741,6 +741,7 @@ ## Physics * [Archimedes Principle](physics/archimedes_principle.py) + * [Basic Orbital Capture](physics/basic_orbital_capture.py) * [Casimir Effect](physics/casimir_effect.py) * [Centripetal Force](physics/centripetal_force.py) * [Grahams Law](physics/grahams_law.py) diff --git a/physics/basic_orbital_capture.py b/physics/basic_orbital_capture.py new file mode 100644 index 000000000000..eeb45e60240c --- /dev/null +++ b/physics/basic_orbital_capture.py @@ -0,0 +1,178 @@ +from math import pow, sqrt + +from scipy.constants import G, c, pi + +""" +These two functions will return the radii of impact for a target object +of mass M and radius R as well as it's effective cross sectional area σ(sigma). +That is to say any projectile with velocity v passing within σ, will impact the +target object with mass M. The derivation of which is given at the bottom +of this file. + +The derivation shows that a projectile does not need to aim directly at the target +body in order to hit it, as R_capture>R_target. Astronomers refer to the effective +cross section for capture as σ=π*R_capture**2. + +This algorithm does not account for an N-body problem. + +""" + + +def capture_radii( + target_body_radius: float, target_body_mass: float, projectile_velocity: float +) -> float: + """ + Input Params: + ------------- + target_body_radius: Radius of the central body SI units: meters | m + target_body_mass: Mass of the central body SI units: kilograms | kg + projectile_velocity: Velocity of object moving toward central body + SI units: meters/second | m/s + Returns: + -------- + >>> capture_radii(6.957e8, 1.99e30, 25000.0) + 17209590691.0 + >>> capture_radii(-6.957e8, 1.99e30, 25000.0) + Traceback (most recent call last): + ... + ValueError: Radius cannot be less than 0 + >>> capture_radii(6.957e8, -1.99e30, 25000.0) + Traceback (most recent call last): + ... + ValueError: Mass cannot be less than 0 + >>> capture_radii(6.957e8, 1.99e30, c+1) + Traceback (most recent call last): + ... + ValueError: Cannot go beyond speed of light + + Returned SI units: + ------------------ + meters | m + """ + + if target_body_mass < 0: + raise ValueError("Mass cannot be less than 0") + if target_body_radius < 0: + raise ValueError("Radius cannot be less than 0") + if projectile_velocity > c: + raise ValueError("Cannot go beyond speed of light") + + escape_velocity_squared = (2 * G * target_body_mass) / target_body_radius + capture_radius = target_body_radius * sqrt( + 1 + escape_velocity_squared / pow(projectile_velocity, 2) + ) + return round(capture_radius, 0) + + +def capture_area(capture_radius: float) -> float: + """ + Input Param: + ------------ + capture_radius: The radius of orbital capture and impact for a central body of + mass M and a projectile moving towards it with velocity v + SI units: meters | m + Returns: + -------- + >>> capture_area(17209590691) + 9.304455331329126e+20 + >>> capture_area(-1) + Traceback (most recent call last): + ... + ValueError: Cannot have a capture radius less than 0 + + Returned SI units: + ------------------ + meters*meters | m**2 + """ + + if capture_radius < 0: + raise ValueError("Cannot have a capture radius less than 0") + sigma = pi * pow(capture_radius, 2) + return round(sigma, 0) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + +""" +Derivation: + +Let: Mt=target mass, Rt=target radius, v=projectile_velocity, + r_0=radius of projectile at instant 0 to CM of target + v_p=v at closest approach, + r_p=radius from projectile to target CM at closest approach, + R_capture= radius of impact for projectile with velocity v + +(1)At time=0 the projectile's energy falling from infinity| E=K+U=0.5*m*(v**2)+0 + + E_initial=0.5*m*(v**2) + +(2)at time=0 the angular momentum of the projectile relative to CM target| + L_initial=m*r_0*v*sin(Θ)->m*r_0*v*(R_capture/r_0)->m*v*R_capture + + L_i=m*v*R_capture + +(3)The energy of the projectile at closest approach will be its kinetic energy + at closest approach plus gravitational potential energy(-(GMm)/R)| + E_p=K_p+U_p->E_p=0.5*m*(v_p**2)-(G*Mt*m)/r_p + + E_p=0.0.5*m*(v_p**2)-(G*Mt*m)/r_p + +(4)The angular momentum of the projectile relative to the target at closest + approach will be L_p=m*r_p*v_p*sin(Θ), however relative to the target Θ=90° + sin(90°)=1| + + L_p=m*r_p*v_p +(5)Using conservation of angular momentum and energy, we can write a quadratic + equation that solves for r_p| + + (a) + Ei=Ep-> 0.5*m*(v**2)=0.5*m*(v_p**2)-(G*Mt*m)/r_p-> v**2=v_p**2-(2*G*Mt)/r_p + + (b) + Li=Lp-> m*v*R_capture=m*r_p*v_p-> v*R_capture=r_p*v_p-> v_p=(v*R_capture)/r_p + + (c) b plugs int a| + v**2=((v*R_capture)/r_p)**2-(2*G*Mt)/r_p-> + + v**2-(v**2)*(R_c**2)/(r_p**2)+(2*G*Mt)/r_p=0-> + + (v**2)*(r_p**2)+2*G*Mt*r_p-(v**2)*(R_c**2)=0 + + (d) Using the quadratic formula, we'll solve for r_p then rearrange to solve to + R_capture + + r_p=(-2*G*Mt ± sqrt(4*G^2*Mt^2+ 4(v^4*R_c^2)))/(2*v^2)-> + + r_p=(-G*Mt ± sqrt(G^2*Mt+v^4*R_c^2))/v^2-> + + r_p<0 is something we can ignore, as it has no physical meaning for our purposes.-> + + r_p=(-G*Mt)/v^2 + sqrt(G^2*Mt^2/v^4 + R_c^2) + + (e)We are trying to solve for R_c. We are looking for impact, so we want r_p=Rt + + Rt + G*Mt/v^2 = sqrt(G^2*Mt^2/v^4 + R_c^2)-> + + (Rt + G*Mt/v^2)^2 = G^2*Mt^2/v^4 + R_c^2-> + + Rt^2 + 2*G*Mt*Rt/v^2 + G^2*Mt^2/v^4 = G^2*Mt^2/v^4 + R_c^2-> + + Rt**2 + 2*G*Mt*Rt/v**2 = R_c**2-> + + Rt**2 * (1 + 2*G*Mt/Rt *1/v**2) = R_c**2-> + + escape velocity = sqrt(2GM/R)= v_escape**2=2GM/R-> + + Rt**2 * (1 + v_esc**2/v**2) = R_c**2-> + +(6) + R_capture = Rt * sqrt(1 + v_esc**2/v**2) + +Source: Problem Set 3 #8 c.Fall_2017|Honors Astronomy|Professor Rachel Bezanson + +Source #2: http://www.nssc.ac.cn/wxzygx/weixin/201607/P020160718380095698873.pdf + 8.8 Planetary Rendezvous: Pg.368 +""" From 0ef930697632a1f05dbbd956c4ccab0473025f5b Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 28 Jul 2023 13:08:40 -0700 Subject: [PATCH 2237/2908] Disable quantum/quantum_random.py (attempt 2) (#8902) * Disable quantum/quantum_random.py Temporarily disable quantum/quantum_random.py because it produces an illegal instruction error that causes all builds to fail * updating DIRECTORY.md * Disable quantum/quantum_random.py attempt 2 --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 - quantum/{quantum_random.py => quantum_random.py.DISABLED.txt} | 0 2 files changed, 1 deletion(-) rename quantum/{quantum_random.py => quantum_random.py.DISABLED.txt} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 29514579ceb0..af150b12984b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1063,7 +1063,6 @@ * [Q Fourier Transform](quantum/q_fourier_transform.py) * [Q Full Adder](quantum/q_full_adder.py) * [Quantum Entanglement](quantum/quantum_entanglement.py) - * [Quantum Random](quantum/quantum_random.py) * [Quantum Teleportation](quantum/quantum_teleportation.py) * [Ripple Adder Classic](quantum/ripple_adder_classic.py) * [Single Qubit Measure](quantum/single_qubit_measure.py) diff --git a/quantum/quantum_random.py b/quantum/quantum_random.py.DISABLED.txt similarity index 100% rename from quantum/quantum_random.py rename to quantum/quantum_random.py.DISABLED.txt From 2cfef0913a36e967d828881386ae78457cf65f33 Mon Sep 17 00:00:00 2001 From: Colin Leroy-Mira Date: Sat, 29 Jul 2023 19:03:43 +0200 Subject: [PATCH 2238/2908] Fix greyscale computation and inverted coords (#8905) * Fix greyscale computation and inverted coords * Fix test * Add test cases * Add reference to the greyscaling formula --------- Co-authored-by: Colin Leroy-Mira --- digital_image_processing/dithering/burkes.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/digital_image_processing/dithering/burkes.py b/digital_image_processing/dithering/burkes.py index 0804104abe58..35aedc16d404 100644 --- a/digital_image_processing/dithering/burkes.py +++ b/digital_image_processing/dithering/burkes.py @@ -39,9 +39,18 @@ def __init__(self, input_img, threshold: int): def get_greyscale(cls, blue: int, green: int, red: int) -> float: """ >>> Burkes.get_greyscale(3, 4, 5) - 3.753 + 4.185 + >>> Burkes.get_greyscale(0, 0, 0) + 0.0 + >>> Burkes.get_greyscale(255, 255, 255) + 255.0 """ - return 0.114 * blue + 0.587 * green + 0.2126 * red + """ + Formula from https://en.wikipedia.org/wiki/HSL_and_HSV + cf Lightness section, and Fig 13c. + We use the first of four possible. + """ + return 0.114 * blue + 0.587 * green + 0.299 * red def process(self) -> None: for y in range(self.height): @@ -49,10 +58,10 @@ def process(self) -> None: 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] + current_error = greyscale + self.error_table[y][x] else: self.output_img[y][x] = (255, 255, 255) - current_error = greyscale + self.error_table[x][y] - 255 + current_error = greyscale + self.error_table[y][x] - 255 """ Burkes error propagation (`*` is current pixel): From d31750adece86ebf39a09dd3adb2039098f58586 Mon Sep 17 00:00:00 2001 From: Yatharth Mathur <31852880+yatharthmathur@users.noreply.github.com> Date: Sun, 30 Jul 2023 02:27:45 -0700 Subject: [PATCH 2239/2908] Pythonic implementation of LRU Cache (#4630) * Added a more pythonic implementation of LRU_Cache.[#4628] * Added test cases and doctest * Fixed doc tests * Added more tests in doctests and fixed return types fixes [#4628] * better doctests * added doctests to main() * Added dutch_national_flag.py in sorts. fixing [#4636] * Delete dutch_national_flag.py incorrect commit * Update lru_cache_pythonic.py * Remove pontification --------- Co-authored-by: Christian Clauss --- other/lru_cache_pythonic.py | 113 ++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 other/lru_cache_pythonic.py diff --git a/other/lru_cache_pythonic.py b/other/lru_cache_pythonic.py new file mode 100644 index 000000000000..425691ef18cf --- /dev/null +++ b/other/lru_cache_pythonic.py @@ -0,0 +1,113 @@ +""" +This implementation of LRU Cache uses the in-built Python dictionary (dict) which from +Python 3.6 onward maintain the insertion order of keys and ensures O(1) operations on +insert, delete and access. https://docs.python.org/3/library/stdtypes.html#typesmapping +""" +from typing import Any, Hashable + + +class LRUCache(dict): + def __init__(self, capacity: int) -> None: + """ + Initialize an LRU Cache with given capacity. + capacity : int -> the capacity of the LRU Cache + >>> cache = LRUCache(2) + >>> cache + {} + """ + self.remaining: int = capacity + + def get(self, key: Hashable) -> Any: + """ + This method returns the value associated with the key. + key : A hashable object that is mapped to a value in the LRU cache. + return -> Any object that has been stored as a value in the LRU cache. + + >>> cache = LRUCache(2) + >>> cache.put(1,1) + >>> cache.get(1) + 1 + >>> cache.get(2) + Traceback (most recent call last): + ... + KeyError: '2 not found.' + """ + if key not in self: + raise KeyError(f"{key} not found.") + val = self.pop(key) # Pop the key-value and re-insert to maintain the order + self[key] = val + return val + + def put(self, key: Hashable, value: Any) -> None: + """ + This method puts the value associated with the key provided in the LRU cache. + key : A hashable object that is mapped to a value in the LRU cache. + value: Any object that is to be associated with the key in the LRU cache. + >>> cache = LRUCache(2) + >>> cache.put(3,3) + >>> cache + {3: 3} + >>> cache.put(2,2) + >>> cache + {3: 3, 2: 2} + """ + # To pop the last value inside of the LRU cache + if key in self: + self.pop(key) + self[key] = value + return + + if self.remaining > 0: + self.remaining -= 1 + # To pop the least recently used item from the dictionary + else: + self.pop(next(iter(self))) + self[key] = value + + +def main() -> None: + """Example test case with LRU_Cache of size 2 + >>> main() + 1 + Key=2 not found in cache + Key=1 not found in cache + 3 + 4 + """ + cache = LRUCache(2) # Creates an LRU cache with size 2 + cache.put(1, 1) # cache = {1:1} + cache.put(2, 2) # cache = {1:1, 2:2} + try: + print(cache.get(1)) # Prints 1 + except KeyError: + print("Key not found in cache") + cache.put( + 3, 3 + ) # cache = {1:1, 3:3} key=2 is evicted because it wasn't used recently + try: + print(cache.get(2)) + except KeyError: + print("Key=2 not found in cache") # Prints key not found + cache.put( + 4, 4 + ) # cache = {4:4, 3:3} key=1 is evicted because it wasn't used recently + try: + print(cache.get(1)) + except KeyError: + print("Key=1 not found in cache") # Prints key not found + try: + print(cache.get(3)) # Prints value 3 + except KeyError: + print("Key not found in cache") + + try: + print(cache.get(4)) # Prints value 4 + except KeyError: + print("Key not found in cache") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From 8b831cb60003443c9967ac8a33df4151dc883484 Mon Sep 17 00:00:00 2001 From: Bazif Rasool <45148731+Bazifrasool@users.noreply.github.com> Date: Sun, 30 Jul 2023 20:30:58 +0530 Subject: [PATCH 2240/2908] Added Altitude Pressure equation (#8909) * Added Altitude Pressure equation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed trailing whitespaces * Removed pylint * Fix lru_cache_pythonic.py * Fixed spellings * Fix again lru_cache_pythonic.py * Update .vscode/settings.json Co-authored-by: Christian Clauss * Third fix lru_cache_pythonic.py * Update .vscode/settings.json Co-authored-by: Christian Clauss * 4th fix lru_cache_pythonic.py * Update physics/altitude_pressure.py Co-authored-by: Christian Clauss * lru_cache_pythonic.py: def get(self, key: Any, /) -> Any | None: * Delete lru_cache_pythonic.py * Added positive and negative pressure test cases * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- other/lru_cache_pythonic.py | 113 ----------------------------------- physics/altitude_pressure.py | 52 ++++++++++++++++ 2 files changed, 52 insertions(+), 113 deletions(-) delete mode 100644 other/lru_cache_pythonic.py create mode 100644 physics/altitude_pressure.py diff --git a/other/lru_cache_pythonic.py b/other/lru_cache_pythonic.py deleted file mode 100644 index 425691ef18cf..000000000000 --- a/other/lru_cache_pythonic.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -This implementation of LRU Cache uses the in-built Python dictionary (dict) which from -Python 3.6 onward maintain the insertion order of keys and ensures O(1) operations on -insert, delete and access. https://docs.python.org/3/library/stdtypes.html#typesmapping -""" -from typing import Any, Hashable - - -class LRUCache(dict): - def __init__(self, capacity: int) -> None: - """ - Initialize an LRU Cache with given capacity. - capacity : int -> the capacity of the LRU Cache - >>> cache = LRUCache(2) - >>> cache - {} - """ - self.remaining: int = capacity - - def get(self, key: Hashable) -> Any: - """ - This method returns the value associated with the key. - key : A hashable object that is mapped to a value in the LRU cache. - return -> Any object that has been stored as a value in the LRU cache. - - >>> cache = LRUCache(2) - >>> cache.put(1,1) - >>> cache.get(1) - 1 - >>> cache.get(2) - Traceback (most recent call last): - ... - KeyError: '2 not found.' - """ - if key not in self: - raise KeyError(f"{key} not found.") - val = self.pop(key) # Pop the key-value and re-insert to maintain the order - self[key] = val - return val - - def put(self, key: Hashable, value: Any) -> None: - """ - This method puts the value associated with the key provided in the LRU cache. - key : A hashable object that is mapped to a value in the LRU cache. - value: Any object that is to be associated with the key in the LRU cache. - >>> cache = LRUCache(2) - >>> cache.put(3,3) - >>> cache - {3: 3} - >>> cache.put(2,2) - >>> cache - {3: 3, 2: 2} - """ - # To pop the last value inside of the LRU cache - if key in self: - self.pop(key) - self[key] = value - return - - if self.remaining > 0: - self.remaining -= 1 - # To pop the least recently used item from the dictionary - else: - self.pop(next(iter(self))) - self[key] = value - - -def main() -> None: - """Example test case with LRU_Cache of size 2 - >>> main() - 1 - Key=2 not found in cache - Key=1 not found in cache - 3 - 4 - """ - cache = LRUCache(2) # Creates an LRU cache with size 2 - cache.put(1, 1) # cache = {1:1} - cache.put(2, 2) # cache = {1:1, 2:2} - try: - print(cache.get(1)) # Prints 1 - except KeyError: - print("Key not found in cache") - cache.put( - 3, 3 - ) # cache = {1:1, 3:3} key=2 is evicted because it wasn't used recently - try: - print(cache.get(2)) - except KeyError: - print("Key=2 not found in cache") # Prints key not found - cache.put( - 4, 4 - ) # cache = {4:4, 3:3} key=1 is evicted because it wasn't used recently - try: - print(cache.get(1)) - except KeyError: - print("Key=1 not found in cache") # Prints key not found - try: - print(cache.get(3)) # Prints value 3 - except KeyError: - print("Key not found in cache") - - try: - print(cache.get(4)) # Prints value 4 - except KeyError: - print("Key not found in cache") - - -if __name__ == "__main__": - import doctest - - doctest.testmod() - main() diff --git a/physics/altitude_pressure.py b/physics/altitude_pressure.py new file mode 100644 index 000000000000..65307d223fa7 --- /dev/null +++ b/physics/altitude_pressure.py @@ -0,0 +1,52 @@ +""" +Title : Calculate altitude using Pressure + +Description : + The below algorithm approximates the altitude using Barometric formula + + +""" + + +def get_altitude_at_pressure(pressure: float) -> float: + """ + This method calculates the altitude from Pressure wrt to + Sea level pressure as reference .Pressure is in Pascals + https://en.wikipedia.org/wiki/Pressure_altitude + https://community.bosch-sensortec.com/t5/Question-and-answers/How-to-calculate-the-altitude-from-the-pressure-sensor-data/qaq-p/5702 + + H = 44330 * [1 - (P/p0)^(1/5.255) ] + + Where : + H = altitude (m) + P = measured pressure + p0 = reference pressure at sea level 101325 Pa + + Examples: + >>> get_altitude_at_pressure(pressure=100_000) + 105.47836610778828 + >>> get_altitude_at_pressure(pressure=101_325) + 0.0 + >>> get_altitude_at_pressure(pressure=80_000) + 1855.873388064995 + >>> get_altitude_at_pressure(pressure=201_325) + Traceback (most recent call last): + ... + ValueError: Value Higher than Pressure at Sea Level ! + >>> get_altitude_at_pressure(pressure=-80_000) + Traceback (most recent call last): + ... + ValueError: Atmospheric Pressure can not be negative ! + """ + + if pressure > 101325: + raise ValueError("Value Higher than Pressure at Sea Level !") + if pressure < 0: + raise ValueError("Atmospheric Pressure can not be negative !") + return 44_330 * (1 - (pressure / 101_325) ** (1 / 5.5255)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d4f2873e39f041513aa9f5c287ec9b46e2236dad Mon Sep 17 00:00:00 2001 From: AmirSoroush Date: Mon, 31 Jul 2023 03:54:15 +0300 Subject: [PATCH 2241/2908] add reverse_inorder traversal to binary_tree_traversals.py (#8726) * add reverse_inorder traversal to binary_tree_traversals.py * Apply suggestions from code review Co-authored-by: Tianyi Zheng --------- Co-authored-by: Tianyi Zheng --- .../binary_tree/binary_tree_traversals.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 71a895e76ce4..2afb7604f9c6 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -58,6 +58,19 @@ def inorder(root: Node | None) -> list[int]: return [*inorder(root.left), root.data, *inorder(root.right)] if root else [] +def reverse_inorder(root: Node | None) -> list[int]: + """ + Reverse in-order traversal visits right subtree, root node, left subtree. + >>> reverse_inorder(make_tree()) + [3, 1, 5, 2, 4] + """ + return ( + [*reverse_inorder(root.right), root.data, *reverse_inorder(root.left)] + if root + else [] + ) + + def height(root: Node | None) -> int: """ Recursive function for calculating the height of the binary tree. @@ -161,15 +174,12 @@ def zigzag(root: Node | None) -> Sequence[Node | None] | list[Any]: def main() -> None: # Main function for testing. - """ - Create binary tree. - """ + # Create binary tree. root = make_tree() - """ - All Traversals of the binary are as follows: - """ + # All Traversals of the binary are as follows: print(f"In-order Traversal: {inorder(root)}") + print(f"Reverse In-order Traversal: {reverse_inorder(root)}") print(f"Pre-order Traversal: {preorder(root)}") print(f"Post-order Traversal: {postorder(root)}", "\n") From 4710e51deb2dc07e32884391a36d40e08398e6be Mon Sep 17 00:00:00 2001 From: David Leal Date: Sun, 30 Jul 2023 19:15:30 -0600 Subject: [PATCH 2242/2908] chore: use newest Discord invite link (#8696) * updating DIRECTORY.md * chore: use newest Discord invite link --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf6e0ed3cf75..d8eba4e016fa 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Contributions Welcome - + Discord chat @@ -42,7 +42,7 @@ Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribut ## Community Channels -We are on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms/community)! Community channels are a great way for you to ask questions and get help. Please join us! +We are on [Discord](https://the-algorithms.com/discord) and [Gitter](https://gitter.im/TheAlgorithms/community)! Community channels are a great way for you to ask questions and get help. Please join us! ## List of Algorithms From 8cce9cf066396bb220515c03849fbc1a16d800d0 Mon Sep 17 00:00:00 2001 From: Almas Bekbayev <121730304+bekbayev@users.noreply.github.com> Date: Mon, 31 Jul 2023 07:32:05 +0600 Subject: [PATCH 2243/2908] Fix linear_search docstring return value (#8644) --- 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 777080d14e36..ba6e81d6bae4 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -15,7 +15,7 @@ def linear_search(sequence: list, target: int) -> int: :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 + :return: index of found item or -1 if item is not found Examples: >>> linear_search([0, 5, 7, 10, 15], 0) From 384c407a265ac44d15eecdd339bb154147cda4f8 Mon Sep 17 00:00:00 2001 From: AmirSoroush Date: Mon, 31 Jul 2023 05:07:35 +0300 Subject: [PATCH 2244/2908] Enhance the implementation of Queue using list (#8608) * enhance the implementation of queue using list * enhance readability of queue_on_list.py * rename 'queue_on_list' to 'queue_by_list' to match the class name --- data_structures/queue/queue_by_list.py | 141 +++++++++++++++++++++++++ data_structures/queue/queue_on_list.py | 52 --------- 2 files changed, 141 insertions(+), 52 deletions(-) create mode 100644 data_structures/queue/queue_by_list.py delete mode 100644 data_structures/queue/queue_on_list.py diff --git a/data_structures/queue/queue_by_list.py b/data_structures/queue/queue_by_list.py new file mode 100644 index 000000000000..4b05be9fd08e --- /dev/null +++ b/data_structures/queue/queue_by_list.py @@ -0,0 +1,141 @@ +"""Queue represented by a Python list""" + +from collections.abc import Iterable +from typing import Generic, TypeVar + +_T = TypeVar("_T") + + +class QueueByList(Generic[_T]): + def __init__(self, iterable: Iterable[_T] | None = None) -> None: + """ + >>> QueueByList() + Queue(()) + >>> QueueByList([10, 20, 30]) + Queue((10, 20, 30)) + >>> QueueByList((i**2 for i in range(1, 4))) + Queue((1, 4, 9)) + """ + self.entries: list[_T] = list(iterable or []) + + def __len__(self) -> int: + """ + >>> len(QueueByList()) + 0 + >>> from string import ascii_lowercase + >>> len(QueueByList(ascii_lowercase)) + 26 + >>> queue = QueueByList() + >>> for i in range(1, 11): + ... queue.put(i) + >>> len(queue) + 10 + >>> for i in range(2): + ... queue.get() + 1 + 2 + >>> len(queue) + 8 + """ + + return len(self.entries) + + def __repr__(self) -> str: + """ + >>> queue = QueueByList() + >>> queue + Queue(()) + >>> str(queue) + 'Queue(())' + >>> queue.put(10) + >>> queue + Queue((10,)) + >>> queue.put(20) + >>> queue.put(30) + >>> queue + Queue((10, 20, 30)) + """ + + return f"Queue({tuple(self.entries)})" + + def put(self, item: _T) -> None: + """Put `item` to the Queue + + >>> queue = QueueByList() + >>> queue.put(10) + >>> queue.put(20) + >>> len(queue) + 2 + >>> queue + Queue((10, 20)) + """ + + self.entries.append(item) + + def get(self) -> _T: + """ + Get `item` from the Queue + + >>> queue = QueueByList((10, 20, 30)) + >>> queue.get() + 10 + >>> queue.put(40) + >>> queue.get() + 20 + >>> queue.get() + 30 + >>> len(queue) + 1 + >>> queue.get() + 40 + >>> queue.get() + Traceback (most recent call last): + ... + IndexError: Queue is empty + """ + + if not self.entries: + raise IndexError("Queue is empty") + return self.entries.pop(0) + + def rotate(self, rotation: int) -> None: + """Rotate the items of the Queue `rotation` times + + >>> queue = QueueByList([10, 20, 30, 40]) + >>> queue + Queue((10, 20, 30, 40)) + >>> queue.rotate(1) + >>> queue + Queue((20, 30, 40, 10)) + >>> queue.rotate(2) + >>> queue + Queue((40, 10, 20, 30)) + """ + + put = self.entries.append + get = self.entries.pop + + for _ in range(rotation): + put(get(0)) + + def get_front(self) -> _T: + """Get the front item from the Queue + + >>> queue = QueueByList((10, 20, 30)) + >>> queue.get_front() + 10 + >>> queue + Queue((10, 20, 30)) + >>> queue.get() + 10 + >>> queue.get_front() + 20 + """ + + return self.entries[0] + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/data_structures/queue/queue_on_list.py b/data_structures/queue/queue_on_list.py deleted file mode 100644 index 71fca6b2f5f4..000000000000 --- a/data_structures/queue/queue_on_list.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Queue represented by a Python list""" - - -class Queue: - def __init__(self): - self.entries = [] - self.length = 0 - self.front = 0 - - def __str__(self): - 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.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 _ in range(rotation): - self.put(self.get()) - - """Enqueues {@code item} - @return item at front of self.entries""" - - def get_front(self): - return self.entries[0] - - """Returns the length of this.entries""" - - def size(self): - return self.length From 629eb86ce0d30dd6031fa482f4a477ac3df345ab Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 30 Jul 2023 22:23:23 -0700 Subject: [PATCH 2245/2908] Fix merge conflicts to merge change from #5080 (#8911) * Input for user choose his Collatz sequence Now the user can tell the algorithm what number he wants to run on the Collatz Sequence. * updating DIRECTORY.md --------- Co-authored-by: Hugo Folloni Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + maths/collatz_sequence.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index af150b12984b..aa9bd313b898 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -740,6 +740,7 @@ * [Tower Of Hanoi](other/tower_of_hanoi.py) ## Physics + * [Altitude Pressure](physics/altitude_pressure.py) * [Archimedes Principle](physics/archimedes_principle.py) * [Basic Orbital Capture](physics/basic_orbital_capture.py) * [Casimir Effect](physics/casimir_effect.py) diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py index 4f3aa5582731..b47017146a1e 100644 --- a/maths/collatz_sequence.py +++ b/maths/collatz_sequence.py @@ -57,7 +57,7 @@ def collatz_sequence(n: int) -> Generator[int, None, None]: def main(): - n = 43 + n = int(input("Your number: ")) sequence = tuple(collatz_sequence(n)) print(sequence) print(f"Collatz sequence from {n} took {len(sequence)} steps.") From 0b0214c42f563e7af885058c0e3a32d292f7f1da Mon Sep 17 00:00:00 2001 From: roger-sato Date: Tue, 1 Aug 2023 03:46:30 +0900 Subject: [PATCH 2246/2908] Handle empty input case in Segment Tree build process (#8718) --- data_structures/binary_tree/segment_tree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index b0580386954a..5f822407d8cb 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -7,7 +7,8 @@ def __init__(self, a): self.st = [0] * ( 4 * self.N ) # approximate the overall size of segment tree with array N - self.build(1, 0, self.N - 1) + if self.N: + self.build(1, 0, self.N - 1) def left(self, idx): return idx * 2 From 90a8e6e0d210a5c526c8f485fa825e1649d217e2 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Mon, 31 Jul 2023 15:50:00 -0300 Subject: [PATCH 2247/2908] Update `sorts/bubble_sort.py` (#5802) * Add missing type annotations in bubble_sort.py * Refactor bubble_sort function --- 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 aef2da272bd0..7da4362a5b97 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -1,4 +1,7 @@ -def bubble_sort(collection): +from typing import Any + + +def bubble_sort(collection: list[Any]) -> list[Any]: """Pure implementation of bubble sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous @@ -28,9 +31,9 @@ def bubble_sort(collection): True """ length = len(collection) - for i in range(length - 1): + for i in reversed(range(length)): swapped = False - for j in range(length - 1 - i): + for j in range(i): if collection[j] > collection[j + 1]: swapped = True collection[j], collection[j + 1] = collection[j + 1], collection[j] From 5cf34d901e32b65425103309bbad0068b1851238 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 31 Jul 2023 13:53:26 -0700 Subject: [PATCH 2248/2908] Ruff fixes (#8913) * updating DIRECTORY.md * Fix ruff error in eulerian_path_and_circuit_for_undirected_graph.py * Fix ruff error in newtons_second_law_of_motion.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- graphs/eulerian_path_and_circuit_for_undirected_graph.py | 2 +- physics/newtons_second_law_of_motion.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index aa9bd313b898..fdcf0ceedf1f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -236,8 +236,8 @@ * [Double Ended Queue](data_structures/queue/double_ended_queue.py) * [Linked Queue](data_structures/queue/linked_queue.py) * [Priority Queue Using List](data_structures/queue/priority_queue_using_list.py) + * [Queue By List](data_structures/queue/queue_by_list.py) * [Queue By Two Stacks](data_structures/queue/queue_by_two_stacks.py) - * [Queue On List](data_structures/queue/queue_on_list.py) * [Queue On Pseudo Stack](data_structures/queue/queue_on_pseudo_stack.py) * Stacks * [Balanced Parentheses](data_structures/stacks/balanced_parentheses.py) diff --git a/graphs/eulerian_path_and_circuit_for_undirected_graph.py b/graphs/eulerian_path_and_circuit_for_undirected_graph.py index 6c43c5d3e6e3..6b4ea8e21e8b 100644 --- a/graphs/eulerian_path_and_circuit_for_undirected_graph.py +++ b/graphs/eulerian_path_and_circuit_for_undirected_graph.py @@ -20,7 +20,7 @@ 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(): + if i not in graph: continue if len(graph[i]) % 2 == 1: odd_degree_nodes += 1 diff --git a/physics/newtons_second_law_of_motion.py b/physics/newtons_second_law_of_motion.py index cb53f8f6571f..53fab6ce78b9 100644 --- a/physics/newtons_second_law_of_motion.py +++ b/physics/newtons_second_law_of_motion.py @@ -60,7 +60,7 @@ def newtons_second_law_of_motion(mass: float, acceleration: float) -> float: >>> newtons_second_law_of_motion(2.0, 1) 2.0 """ - force = float() + force = 0.0 try: force = mass * acceleration except Exception: From f8fe72dc378232107100acc1924fef31b1198124 Mon Sep 17 00:00:00 2001 From: "Minha, Jeong" Date: Tue, 1 Aug 2023 06:24:12 +0900 Subject: [PATCH 2249/2908] Update game_of_life.py (#4921) * Update game_of_life.py docstring error fix delete no reason delete next_gen_canvas code(local variable) * Update cellular_automata/game_of_life.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Tianyi Zheng Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- cellular_automata/game_of_life.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cellular_automata/game_of_life.py b/cellular_automata/game_of_life.py index b69afdce03eb..d691a2b73af0 100644 --- a/cellular_automata/game_of_life.py +++ b/cellular_automata/game_of_life.py @@ -10,7 +10,7 @@ - 3.5 Usage: - - $python3 game_o_life + - $python3 game_of_life Game-Of-Life Rules: @@ -52,7 +52,8 @@ def seed(canvas: list[list[bool]]) -> None: def run(canvas: list[list[bool]]) -> list[list[bool]]: - """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: -- @@ -60,7 +61,7 @@ def run(canvas: list[list[bool]]) -> list[list[bool]]: @returns: -- - None + canvas of population after one step """ current_canvas = np.array(canvas) next_gen_canvas = np.array(create_canvas(current_canvas.shape[0])) @@ -70,10 +71,7 @@ def run(canvas: list[list[bool]]) -> list[list[bool]]: pt, current_canvas[r - 1 : r + 2, c - 1 : c + 2] ) - current_canvas = next_gen_canvas - del next_gen_canvas # cleaning memory as we move on. - return_canvas: list[list[bool]] = current_canvas.tolist() - return return_canvas + return next_gen_canvas.tolist() def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool: From f7c5e55609afa1e4e7ae2ee3f442bbd5d0b43b8a Mon Sep 17 00:00:00 2001 From: Jan Wojciechowski <96974442+yanvoi@users.noreply.github.com> Date: Tue, 1 Aug 2023 05:02:49 +0200 Subject: [PATCH 2250/2908] Window closing fix (#8625) * The window will now remain open after the fractal is finished being drawn, and will only close upon your click. * Update fractals/sierpinski_triangle.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Tianyi Zheng Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- fractals/sierpinski_triangle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fractals/sierpinski_triangle.py b/fractals/sierpinski_triangle.py index c28ec00b27fe..45f7ab84cfff 100644 --- a/fractals/sierpinski_triangle.py +++ b/fractals/sierpinski_triangle.py @@ -82,3 +82,4 @@ def triangle( vertices = [(-175, -125), (0, 175), (175, -125)] # vertices of triangle triangle(vertices[0], vertices[1], vertices[2], int(sys.argv[1])) + turtle.Screen().exitonclick() From c9a7234a954dd280dc8192ae77a564e647d013d4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 09:26:23 +0530 Subject: [PATCH 2251/2908] [pre-commit.ci] pre-commit autoupdate (#8914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.280 → v0.0.281](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.280...v0.0.281) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5adf12cc70c5..e158bd8d6879 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.280 + rev: v0.0.281 hooks: - id: ruff From ce218c57f1f494cfca69bc01ba660c97385e5330 Mon Sep 17 00:00:00 2001 From: AmirSoroush Date: Tue, 1 Aug 2023 21:23:34 +0300 Subject: [PATCH 2252/2908] =?UTF-8?q?fixes=20#8673;=20Add=20operator's=20a?= =?UTF-8?q?ssociativity=20check=20for=20stacks/infix=5Fto=5Fp=E2=80=A6=20(?= =?UTF-8?q?#8674)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixes #8673; Add operator's associativity check for stacks/infix_to_postfix_conversion.py * fix ruff N806 in stacks/infix_to_postfix_conversion.py * Update data_structures/stacks/infix_to_postfix_conversion.py Co-authored-by: Tianyi Zheng * Update data_structures/stacks/infix_to_postfix_conversion.py Co-authored-by: Tianyi Zheng --------- Co-authored-by: Tianyi Zheng --- .../stacks/infix_to_postfix_conversion.py | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index 9017443091cf..e697061937c9 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -4,9 +4,26 @@ https://en.wikipedia.org/wiki/Shunting-yard_algorithm """ +from typing import Literal + from .balanced_parentheses import balanced_parentheses from .stack import Stack +PRECEDENCES: dict[str, int] = { + "+": 1, + "-": 1, + "*": 2, + "/": 2, + "^": 3, +} +ASSOCIATIVITIES: dict[str, Literal["LR", "RL"]] = { + "+": "LR", + "-": "LR", + "*": "LR", + "/": "LR", + "^": "RL", +} + def precedence(char: str) -> int: """ @@ -14,7 +31,15 @@ def precedence(char: str) -> int: order of operation. https://en.wikipedia.org/wiki/Order_of_operations """ - return {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3}.get(char, -1) + return PRECEDENCES.get(char, -1) + + +def associativity(char: str) -> Literal["LR", "RL"]: + """ + Return the associativity of the operator `char`. + https://en.wikipedia.org/wiki/Operator_associativity + """ + return ASSOCIATIVITIES[char] def infix_to_postfix(expression_str: str) -> str: @@ -35,6 +60,8 @@ def infix_to_postfix(expression_str: str) -> str: 'a b c * + d e * f + g * +' >>> infix_to_postfix("x^y/(5*z)+2") 'x y ^ 5 z * / 2 +' + >>> infix_to_postfix("2^3^2") + '2 3 2 ^ ^' """ if not balanced_parentheses(expression_str): raise ValueError("Mismatched parentheses") @@ -50,9 +77,26 @@ def infix_to_postfix(expression_str: str) -> str: postfix.append(stack.pop()) stack.pop() else: - while not stack.is_empty() and precedence(char) <= precedence(stack.peek()): + while True: + if stack.is_empty(): + stack.push(char) + break + + char_precedence = precedence(char) + tos_precedence = precedence(stack.peek()) + + if char_precedence > tos_precedence: + stack.push(char) + break + if char_precedence < tos_precedence: + postfix.append(stack.pop()) + continue + # Precedences are equal + if associativity(char) == "RL": + stack.push(char) + break postfix.append(stack.pop()) - stack.push(char) + while not stack.is_empty(): postfix.append(stack.pop()) return " ".join(postfix) From db6bd4b17f471d4def7aa441f1da43bb6a0f18ae Mon Sep 17 00:00:00 2001 From: Dipankar Mitra <50228537+Mitra-babu@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:17:42 +0530 Subject: [PATCH 2253/2908] IQR function is added (#8851) * tanh function been added * tanh function been added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tanh function is added * tanh function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tanh function added * tanh function added * tanh function is added * Apply suggestions from code review * ELU activation function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * elu activation is added * ELU activation is added * Update maths/elu_activation.py Co-authored-by: Christian Clauss * Exponential_linear_unit activation is added * Exponential_linear_unit activation is added * SiLU activation is added * SiLU activation is added * mish added * mish activation is added * inter_quartile_range function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Mish activation function is added * Mish action is added * mish activation added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * mish activation added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * inter quartile range (IQR) function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * IQR function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * code optimized in IQR function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * interquartile_range function is added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/interquartile_range.py Co-authored-by: Christian Clauss * Changes on interquartile_range * numpy removed from interquartile_range * Fixes from code review * Update interquartile_range.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/interquartile_range.py | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 maths/interquartile_range.py diff --git a/maths/interquartile_range.py b/maths/interquartile_range.py new file mode 100644 index 000000000000..d4d72e73ef49 --- /dev/null +++ b/maths/interquartile_range.py @@ -0,0 +1,66 @@ +""" +An implementation of interquartile range (IQR) which is a measure of statistical +dispersion, which is the spread of the data. + +The function takes the list of numeric values as input and returns the IQR. + +Script inspired by this Wikipedia article: +https://en.wikipedia.org/wiki/Interquartile_range +""" +from __future__ import annotations + + +def find_median(nums: list[int | float]) -> float: + """ + This is the implementation of the median. + :param nums: The list of numeric nums + :return: Median of the list + >>> find_median(nums=([1, 2, 2, 3, 4])) + 2 + >>> find_median(nums=([1, 2, 2, 3, 4, 4])) + 2.5 + >>> find_median(nums=([-1, 2, 0, 3, 4, -4])) + 1.5 + >>> find_median(nums=([1.1, 2.2, 2, 3.3, 4.4, 4])) + 2.65 + """ + div, mod = divmod(len(nums), 2) + if mod: + return nums[div] + return (nums[div] + nums[(div) - 1]) / 2 + + +def interquartile_range(nums: list[int | float]) -> float: + """ + Return the interquartile range for a list of numeric values. + :param nums: The list of numeric values. + :return: interquartile range + + >>> interquartile_range(nums=[4, 1, 2, 3, 2]) + 2.0 + >>> interquartile_range(nums = [-2, -7, -10, 9, 8, 4, -67, 45]) + 17.0 + >>> interquartile_range(nums = [-2.1, -7.1, -10.1, 9.1, 8.1, 4.1, -67.1, 45.1]) + 17.2 + >>> interquartile_range(nums = [0, 0, 0, 0, 0]) + 0.0 + >>> interquartile_range(nums=[]) + Traceback (most recent call last): + ... + ValueError: The list is empty. Provide a non-empty list. + """ + if not nums: + raise ValueError("The list is empty. Provide a non-empty list.") + nums.sort() + length = len(nums) + div, mod = divmod(length, 2) + q1 = find_median(nums[:div]) + half_length = sum((div, mod)) + q3 = find_median(nums[half_length:length]) + return q3 - q1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ac62cdb94fe2478fd809d9ec91e3b85304a5ac6d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 19:52:39 -0400 Subject: [PATCH 2254/2908] [pre-commit.ci] pre-commit autoupdate (#8930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.281 → v0.0.282](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.281...v0.0.282) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e158bd8d6879..da6762123b04 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.281 + rev: v0.0.282 hooks: - id: ruff diff --git a/DIRECTORY.md b/DIRECTORY.md index fdcf0ceedf1f..e6a1ff356143 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -585,6 +585,7 @@ * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) * [Hexagonal Number](maths/hexagonal_number.py) * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) + * [Interquartile Range](maths/interquartile_range.py) * [Is Int Palindrome](maths/is_int_palindrome.py) * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) * [Is Square Free](maths/is_square_free.py) From 842d03fb2ab7d83e4d4081c248d71e89bb520809 Mon Sep 17 00:00:00 2001 From: AmirSoroush Date: Wed, 9 Aug 2023 00:47:09 +0300 Subject: [PATCH 2255/2908] improvements to jump_search.py (#8932) * improvements to jump_search.py * add more tests to jump_search.py --- searches/jump_search.py | 45 +++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/searches/jump_search.py b/searches/jump_search.py index 31a9656c55fe..3bc3c37809a1 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -4,14 +4,28 @@ 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. + +https://en.wikipedia.org/wiki/Jump_search """ import math +from collections.abc import Sequence +from typing import Any, Protocol, TypeVar + + +class Comparable(Protocol): + def __lt__(self, other: Any, /) -> bool: + ... + +T = TypeVar("T", bound=Comparable) -def jump_search(arr: list, x: int) -> int: + +def jump_search(arr: Sequence[T], item: T) -> int: """ - Pure Python implementation of the jump search algorithm. + Python implementation of the jump search algorithm. + Return the index if the `item` is found, otherwise return -1. + Examples: >>> jump_search([0, 1, 2, 3, 4, 5], 3) 3 @@ -21,31 +35,36 @@ def jump_search(arr: list, x: int) -> int: -1 >>> jump_search([0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610], 55) 10 + >>> jump_search(["aa", "bb", "cc", "dd", "ee", "ff"], "ee") + 4 """ - n = len(arr) - step = int(math.floor(math.sqrt(n))) + arr_size = len(arr) + block_size = int(math.sqrt(arr_size)) + prev = 0 - while arr[min(step, n) - 1] < x: + step = block_size + while arr[min(step, arr_size) - 1] < item: prev = step - step += int(math.floor(math.sqrt(n))) - if prev >= n: + step += block_size + if prev >= arr_size: return -1 - while arr[prev] < x: - prev = prev + 1 - if prev == min(step, n): + while arr[prev] < item: + prev += 1 + if prev == min(step, arr_size): return -1 - if arr[prev] == x: + if arr[prev] == item: return prev return -1 if __name__ == "__main__": user_input = input("Enter numbers separated by a comma:\n").strip() - arr = [int(item) for item in user_input.split(",")] + array = [int(item) for item in user_input.split(",")] x = int(input("Enter the number to be searched:\n")) - res = jump_search(arr, x) + + res = jump_search(array, x) if res == -1: print("Number not found!") else: From ae0fc85401efd9816193a06e554a66600cc09a97 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 9 Aug 2023 00:55:30 -0700 Subject: [PATCH 2256/2908] Fix ruff errors (#8936) * Fix ruff errors Renamed neural_network/input_data.py to neural_network/input_data.py_tf because it should be left out of the directory for the following reasons: 1. Its sole purpose is to be used by neural_network/gan.py_tf, which is itself left out of the directory because of issues with TensorFlow. 2. It was taken directly from TensorFlow's codebase and is actually already deprecated. If/when neural_network/gan.py_tf is eventually re-added back to the directory, its implementation should be changed to not use neural_network/input_data.py anyway. * updating DIRECTORY.md * Change input_data.py_tf file extension Change input_data.py_tf file extension because algorithms-keeper bot is being picky about it --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 - conversions/length_conversion.py | 30 +++++++++----- conversions/pressure_conversions.py | 28 ++++++++----- conversions/volume_conversions.py | 40 +++++++++++-------- .../binary_tree/distribute_coins.py | 10 +++-- electronics/electric_power.py | 22 +++++----- graphs/bi_directional_dijkstra.py | 4 +- maths/area_under_curve.py | 6 +-- maths/decimal_to_fraction.py | 2 +- maths/line_length.py | 6 +-- maths/numerical_integration.py | 6 +-- .../single_indeterminate_operations.py | 4 +- maths/series/geometric_series.py | 10 ++--- maths/series/p_series.py | 2 +- maths/volume.py | 2 +- matrix/matrix_class.py | 4 +- matrix/matrix_operation.py | 6 +-- matrix/searching_in_sorted_matrix.py | 4 +- matrix/sherman_morrison.py | 16 ++++---- ...t_data.py => input_data.py.DEPRECATED.txt} | 0 web_programming/covid_stats_via_xpath.py | 12 ++++-- 21 files changed, 121 insertions(+), 94 deletions(-) rename neural_network/{input_data.py => input_data.py.DEPRECATED.txt} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index e6a1ff356143..5578c1c9a6dd 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -710,7 +710,6 @@ * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) - * [Input Data](neural_network/input_data.py) * [Perceptron](neural_network/perceptron.py) * [Simple Neural Network](neural_network/simple_neural_network.py) diff --git a/conversions/length_conversion.py b/conversions/length_conversion.py index d8f39515255e..07fa93a198c7 100644 --- a/conversions/length_conversion.py +++ b/conversions/length_conversion.py @@ -22,9 +22,13 @@ -> Wikipedia reference: https://en.wikipedia.org/wiki/Millimeter """ -from collections import namedtuple +from typing import NamedTuple + + +class FromTo(NamedTuple): + from_factor: float + to_factor: float -from_to = namedtuple("from_to", "from_ to") TYPE_CONVERSION = { "millimeter": "mm", @@ -40,14 +44,14 @@ } METRIC_CONVERSION = { - "mm": from_to(0.001, 1000), - "cm": from_to(0.01, 100), - "m": from_to(1, 1), - "km": from_to(1000, 0.001), - "in": from_to(0.0254, 39.3701), - "ft": from_to(0.3048, 3.28084), - "yd": from_to(0.9144, 1.09361), - "mi": from_to(1609.34, 0.000621371), + "mm": FromTo(0.001, 1000), + "cm": FromTo(0.01, 100), + "m": FromTo(1, 1), + "km": FromTo(1000, 0.001), + "in": FromTo(0.0254, 39.3701), + "ft": FromTo(0.3048, 3.28084), + "yd": FromTo(0.9144, 1.09361), + "mi": FromTo(1609.34, 0.000621371), } @@ -115,7 +119,11 @@ def length_conversion(value: float, from_type: str, to_type: str) -> float: f"Conversion abbreviations are: {', '.join(METRIC_CONVERSION)}" ) raise ValueError(msg) - return value * METRIC_CONVERSION[new_from].from_ * METRIC_CONVERSION[new_to].to + return ( + value + * METRIC_CONVERSION[new_from].from_factor + * METRIC_CONVERSION[new_to].to_factor + ) if __name__ == "__main__": diff --git a/conversions/pressure_conversions.py b/conversions/pressure_conversions.py index e0cd18d234ba..fe78b1382677 100644 --- a/conversions/pressure_conversions.py +++ b/conversions/pressure_conversions.py @@ -19,19 +19,23 @@ -> https://www.unitconverters.net/pressure-converter.html """ -from collections import namedtuple +from typing import NamedTuple + + +class FromTo(NamedTuple): + from_factor: float + to_factor: float -from_to = namedtuple("from_to", "from_ to") PRESSURE_CONVERSION = { - "atm": from_to(1, 1), - "pascal": from_to(0.0000098, 101325), - "bar": from_to(0.986923, 1.01325), - "kilopascal": from_to(0.00986923, 101.325), - "megapascal": from_to(9.86923, 0.101325), - "psi": from_to(0.068046, 14.6959), - "inHg": from_to(0.0334211, 29.9213), - "torr": from_to(0.00131579, 760), + "atm": FromTo(1, 1), + "pascal": FromTo(0.0000098, 101325), + "bar": FromTo(0.986923, 1.01325), + "kilopascal": FromTo(0.00986923, 101.325), + "megapascal": FromTo(9.86923, 0.101325), + "psi": FromTo(0.068046, 14.6959), + "inHg": FromTo(0.0334211, 29.9213), + "torr": FromTo(0.00131579, 760), } @@ -71,7 +75,9 @@ def pressure_conversion(value: float, from_type: str, to_type: str) -> float: + ", ".join(PRESSURE_CONVERSION) ) return ( - value * PRESSURE_CONVERSION[from_type].from_ * PRESSURE_CONVERSION[to_type].to + value + * PRESSURE_CONVERSION[from_type].from_factor + * PRESSURE_CONVERSION[to_type].to_factor ) diff --git a/conversions/volume_conversions.py b/conversions/volume_conversions.py index 44d29009120c..cb240380534b 100644 --- a/conversions/volume_conversions.py +++ b/conversions/volume_conversions.py @@ -18,35 +18,39 @@ -> Wikipedia reference: https://en.wikipedia.org/wiki/Cup_(unit) """ -from collections import namedtuple +from typing import NamedTuple + + +class FromTo(NamedTuple): + from_factor: float + to_factor: float -from_to = namedtuple("from_to", "from_ to") METRIC_CONVERSION = { - "cubicmeter": from_to(1, 1), - "litre": from_to(0.001, 1000), - "kilolitre": from_to(1, 1), - "gallon": from_to(0.00454, 264.172), - "cubicyard": from_to(0.76455, 1.30795), - "cubicfoot": from_to(0.028, 35.3147), - "cup": from_to(0.000236588, 4226.75), + "cubic meter": FromTo(1, 1), + "litre": FromTo(0.001, 1000), + "kilolitre": FromTo(1, 1), + "gallon": FromTo(0.00454, 264.172), + "cubic yard": FromTo(0.76455, 1.30795), + "cubic foot": FromTo(0.028, 35.3147), + "cup": FromTo(0.000236588, 4226.75), } def volume_conversion(value: float, from_type: str, to_type: str) -> float: """ Conversion between volume units. - >>> volume_conversion(4, "cubicmeter", "litre") + >>> volume_conversion(4, "cubic meter", "litre") 4000 >>> volume_conversion(1, "litre", "gallon") 0.264172 - >>> volume_conversion(1, "kilolitre", "cubicmeter") + >>> volume_conversion(1, "kilolitre", "cubic meter") 1 - >>> volume_conversion(3, "gallon", "cubicyard") + >>> volume_conversion(3, "gallon", "cubic yard") 0.017814279 - >>> volume_conversion(2, "cubicyard", "litre") + >>> volume_conversion(2, "cubic yard", "litre") 1529.1 - >>> volume_conversion(4, "cubicfoot", "cup") + >>> volume_conversion(4, "cubic foot", "cup") 473.396 >>> volume_conversion(1, "cup", "kilolitre") 0.000236588 @@ -54,7 +58,7 @@ def volume_conversion(value: float, from_type: str, to_type: str) -> float: Traceback (most recent call last): ... ValueError: Invalid 'from_type' value: 'wrongUnit' Supported values are: - cubicmeter, litre, kilolitre, gallon, cubicyard, cubicfoot, cup + cubic meter, litre, kilolitre, gallon, cubic yard, cubic foot, cup """ if from_type not in METRIC_CONVERSION: raise ValueError( @@ -66,7 +70,11 @@ def volume_conversion(value: float, from_type: str, to_type: str) -> float: f"Invalid 'to_type' value: {to_type!r}. Supported values are:\n" + ", ".join(METRIC_CONVERSION) ) - return value * METRIC_CONVERSION[from_type].from_ * METRIC_CONVERSION[to_type].to + return ( + value + * METRIC_CONVERSION[from_type].from_factor + * METRIC_CONVERSION[to_type].to_factor + ) if __name__ == "__main__": diff --git a/data_structures/binary_tree/distribute_coins.py b/data_structures/binary_tree/distribute_coins.py index ea02afc2cea6..5712604cb87c 100644 --- a/data_structures/binary_tree/distribute_coins.py +++ b/data_structures/binary_tree/distribute_coins.py @@ -39,8 +39,8 @@ from __future__ import annotations -from collections import namedtuple from dataclasses import dataclass +from typing import NamedTuple @dataclass @@ -50,7 +50,9 @@ class TreeNode: right: TreeNode | None = None -CoinsDistribResult = namedtuple("CoinsDistribResult", "moves excess") +class CoinsDistribResult(NamedTuple): + moves: int + excess: int def distribute_coins(root: TreeNode | None) -> int: @@ -79,7 +81,7 @@ def distribute_coins(root: TreeNode | None) -> int: # Validation def count_nodes(node: TreeNode | None) -> int: """ - >>> count_nodes(None): + >>> count_nodes(None) 0 """ if node is None: @@ -89,7 +91,7 @@ def count_nodes(node: TreeNode | None) -> int: def count_coins(node: TreeNode | None) -> int: """ - >>> count_coins(None): + >>> count_coins(None) 0 """ if node is None: diff --git a/electronics/electric_power.py b/electronics/electric_power.py index e59795601791..8b92e320ace3 100644 --- a/electronics/electric_power.py +++ b/electronics/electric_power.py @@ -1,7 +1,12 @@ # https://en.m.wikipedia.org/wiki/Electric_power from __future__ import annotations -from collections import namedtuple +from typing import NamedTuple + + +class Result(NamedTuple): + name: str + value: float def electric_power(voltage: float, current: float, power: float) -> tuple: @@ -10,11 +15,11 @@ def electric_power(voltage: float, current: float, power: float) -> tuple: fundamental value of electrical system. examples are below: >>> electric_power(voltage=0, current=2, power=5) - result(name='voltage', value=2.5) + Result(name='voltage', value=2.5) >>> electric_power(voltage=2, current=2, power=0) - result(name='power', value=4.0) + Result(name='power', value=4.0) >>> electric_power(voltage=-2, current=3, power=0) - result(name='power', value=6.0) + Result(name='power', value=6.0) >>> electric_power(voltage=2, current=4, power=2) Traceback (most recent call last): ... @@ -28,9 +33,8 @@ def electric_power(voltage: float, current: float, power: float) -> tuple: ... ValueError: Power cannot be negative in any electrical/electronics system >>> electric_power(voltage=2.2, current=2.2, power=0) - result(name='power', value=4.84) + 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: @@ -38,11 +42,11 @@ def electric_power(voltage: float, current: float, power: float) -> tuple: "Power cannot be negative in any electrical/electronics system" ) elif voltage == 0: - return result("voltage", power / current) + return Result("voltage", power / current) elif current == 0: - return result("current", power / voltage) + return Result("current", power / voltage) elif power == 0: - return result("power", float(round(abs(voltage * current), 2))) + return Result("power", float(round(abs(voltage * current), 2))) else: raise ValueError("Exactly one argument must be 0") diff --git a/graphs/bi_directional_dijkstra.py b/graphs/bi_directional_dijkstra.py index a4489026be80..529a235db625 100644 --- a/graphs/bi_directional_dijkstra.py +++ b/graphs/bi_directional_dijkstra.py @@ -26,8 +26,8 @@ def pass_and_relaxation( cst_bwd: dict, queue: PriorityQueue, parent: dict, - shortest_distance: float | int, -) -> float | int: + shortest_distance: float, +) -> float: for nxt, d in graph[v]: if nxt in visited_forward: continue diff --git a/maths/area_under_curve.py b/maths/area_under_curve.py index b557b2029657..0da6546b2e36 100644 --- a/maths/area_under_curve.py +++ b/maths/area_under_curve.py @@ -7,9 +7,9 @@ def trapezoidal_area( - fnc: Callable[[int | float], int | float], - x_start: int | float, - x_end: int | float, + fnc: Callable[[float], float], + x_start: float, + x_end: float, steps: int = 100, ) -> float: """ diff --git a/maths/decimal_to_fraction.py b/maths/decimal_to_fraction.py index 9462bafe0171..2aa8e3c3dfd6 100644 --- a/maths/decimal_to_fraction.py +++ b/maths/decimal_to_fraction.py @@ -1,4 +1,4 @@ -def decimal_to_fraction(decimal: int | float | str) -> tuple[int, int]: +def decimal_to_fraction(decimal: float | str) -> tuple[int, int]: """ Return a decimal number in its simplest fraction form >>> decimal_to_fraction(2) diff --git a/maths/line_length.py b/maths/line_length.py index b810f2d9ad1f..ed2efc31e96e 100644 --- a/maths/line_length.py +++ b/maths/line_length.py @@ -5,9 +5,9 @@ def line_length( - fnc: Callable[[int | float], int | float], - x_start: int | float, - x_end: int | float, + fnc: Callable[[float], float], + x_start: float, + x_end: float, steps: int = 100, ) -> float: """ diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index f2d65f89e390..4ac562644a07 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -7,9 +7,9 @@ def trapezoidal_area( - fnc: Callable[[int | float], int | float], - x_start: int | float, - x_end: int | float, + fnc: Callable[[float], float], + x_start: float, + x_end: float, steps: int = 100, ) -> float: """ diff --git a/maths/polynomials/single_indeterminate_operations.py b/maths/polynomials/single_indeterminate_operations.py index 8bafdb591793..e31e6caa3988 100644 --- a/maths/polynomials/single_indeterminate_operations.py +++ b/maths/polynomials/single_indeterminate_operations.py @@ -87,7 +87,7 @@ def __mul__(self, polynomial_2: Polynomial) -> Polynomial: return Polynomial(self.degree + polynomial_2.degree, coefficients) - def evaluate(self, substitution: int | float) -> int | float: + def evaluate(self, substitution: float) -> float: """ Evaluates the polynomial at x. >>> p = Polynomial(2, [1, 2, 3]) @@ -144,7 +144,7 @@ def derivative(self) -> Polynomial: coefficients[i] = self.coefficients[i + 1] * (i + 1) return Polynomial(self.degree - 1, coefficients) - def integral(self, constant: int | float = 0) -> Polynomial: + def integral(self, constant: float = 0) -> Polynomial: """ Returns the integral of the polynomial. >>> p = Polynomial(2, [1, 2, 3]) diff --git a/maths/series/geometric_series.py b/maths/series/geometric_series.py index 90c9fe77b733..b8d6a86206be 100644 --- a/maths/series/geometric_series.py +++ b/maths/series/geometric_series.py @@ -14,10 +14,10 @@ def geometric_series( - nth_term: float | int, - start_term_a: float | int, - common_ratio_r: float | int, -) -> list[float | int]: + nth_term: float, + start_term_a: float, + common_ratio_r: float, +) -> list[float]: """ Pure Python implementation of Geometric Series algorithm @@ -48,7 +48,7 @@ def geometric_series( """ if not all((nth_term, start_term_a, common_ratio_r)): return [] - series: list[float | int] = [] + series: list[float] = [] power = 1 multiple = common_ratio_r for _ in range(int(nth_term)): diff --git a/maths/series/p_series.py b/maths/series/p_series.py index 34fa3f2399af..a091a6f3fecf 100644 --- a/maths/series/p_series.py +++ b/maths/series/p_series.py @@ -13,7 +13,7 @@ from __future__ import annotations -def p_series(nth_term: int | float | str, power: int | float | str) -> list[str]: +def p_series(nth_term: float | str, power: float | str) -> list[str]: """ Pure Python implementation of P-Series algorithm :return: The P-Series starting from 1 to last (nth) term diff --git a/maths/volume.py b/maths/volume.py index 1da4584c893e..721974e68b66 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -8,7 +8,7 @@ from math import pi, pow -def vol_cube(side_length: int | float) -> float: +def vol_cube(side_length: float) -> float: """ Calculate the Volume of a Cube. >>> vol_cube(1) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index a73e8b92a286..a5940a38e836 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -141,7 +141,7 @@ def num_columns(self) -> int: @property def order(self) -> tuple[int, int]: - return (self.num_rows, self.num_columns) + return self.num_rows, self.num_columns @property def is_square(self) -> bool: @@ -315,7 +315,7 @@ def __sub__(self, other: Matrix) -> Matrix: ] ) - def __mul__(self, other: Matrix | int | float) -> Matrix: + def __mul__(self, other: Matrix | float) -> Matrix: if isinstance(other, (int, float)): return Matrix( [[int(element * other) for element in row] for row in self.rows] diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index f189f1898d33..d63e758f1838 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -47,7 +47,7 @@ def subtract(matrix_a: list[list[int]], matrix_b: list[list[int]]) -> list[list[ raise TypeError("Expected a matrix, got int/list instead") -def scalar_multiply(matrix: list[list[int]], n: int | float) -> list[list[float]]: +def scalar_multiply(matrix: list[list[int]], n: float) -> list[list[float]]: """ >>> scalar_multiply([[1,2],[3,4]],5) [[5, 10], [15, 20]] @@ -189,9 +189,7 @@ def main() -> None: 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/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py index ddca3b1ce781..f55cc71d6f3a 100644 --- a/matrix/searching_in_sorted_matrix.py +++ b/matrix/searching_in_sorted_matrix.py @@ -1,9 +1,7 @@ from __future__ import annotations -def search_in_a_sorted_matrix( - mat: list[list[int]], m: int, n: int, key: int | float -) -> None: +def search_in_a_sorted_matrix(mat: list[list[int]], m: int, n: int, key: float) -> None: """ >>> search_in_a_sorted_matrix( ... [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 5) diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 256271e8a87d..b6e50f70fdcf 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -22,7 +22,7 @@ def __init__(self, row: int, column: int, default_value: float = 0) -> None: """ self.row, self.column = row, column - self.array = [[default_value for c in range(column)] for r in range(row)] + self.array = [[default_value for _ in range(column)] for _ in range(row)] def __str__(self) -> str: """ @@ -54,15 +54,15 @@ def single_line(row_vector: list[float]) -> str: def __repr__(self) -> str: return str(self) - def validate_indicies(self, loc: tuple[int, int]) -> bool: + def validate_indices(self, loc: tuple[int, int]) -> bool: """ Check if given indices are valid to pick element from matrix. Example: >>> a = Matrix(2, 6, 0) - >>> a.validate_indicies((2, 7)) + >>> a.validate_indices((2, 7)) False - >>> a.validate_indicies((0, 0)) + >>> a.validate_indices((0, 0)) True """ if not (isinstance(loc, (list, tuple)) and len(loc) == 2): @@ -81,7 +81,7 @@ def __getitem__(self, loc: tuple[int, int]) -> Any: >>> a[1, 0] 7 """ - assert self.validate_indicies(loc) + assert self.validate_indices(loc) return self.array[loc[0]][loc[1]] def __setitem__(self, loc: tuple[int, int], value: float) -> None: @@ -96,7 +96,7 @@ def __setitem__(self, loc: tuple[int, int], value: float) -> None: [ 1, 1, 1] [ 1, 1, 51] """ - assert self.validate_indicies(loc) + assert self.validate_indices(loc) self.array[loc[0]][loc[1]] = value def __add__(self, another: Matrix) -> Matrix: @@ -145,7 +145,7 @@ def __neg__(self) -> Matrix: def __sub__(self, another: Matrix) -> Matrix: return self + (-another) - def __mul__(self, another: int | float | Matrix) -> Matrix: + def __mul__(self, another: float | Matrix) -> Matrix: """ Return self * another. @@ -233,7 +233,7 @@ def sherman_morrison(self, u: Matrix, v: Matrix) -> Any: v_t = v.transpose() numerator_factor = (v_t * self * u)[0, 0] + 1 if numerator_factor == 0: - return None # It's not invertable + return None # It's not invertible return self - ((self * u) * (v_t * self) * (1.0 / numerator_factor)) diff --git a/neural_network/input_data.py b/neural_network/input_data.py.DEPRECATED.txt similarity index 100% rename from neural_network/input_data.py rename to neural_network/input_data.py.DEPRECATED.txt diff --git a/web_programming/covid_stats_via_xpath.py b/web_programming/covid_stats_via_xpath.py index 85ea5d940d85..a95130badad9 100644 --- a/web_programming/covid_stats_via_xpath.py +++ b/web_programming/covid_stats_via_xpath.py @@ -4,17 +4,21 @@ more convenient to use in Python web projects (e.g. Django or Flask-based) """ -from collections import namedtuple +from typing import NamedTuple import requests from lxml import html # type: ignore -covid_data = namedtuple("covid_data", "cases deaths recovered") +class CovidData(NamedTuple): + cases: int + deaths: int + recovered: int -def covid_stats(url: str = "https://www.worldometers.info/coronavirus/") -> covid_data: + +def covid_stats(url: str = "https://www.worldometers.info/coronavirus/") -> CovidData: xpath_str = '//div[@class = "maincounter-number"]/span/text()' - return covid_data(*html.fromstring(requests.get(url).content).xpath(xpath_str)) + return CovidData(*html.fromstring(requests.get(url).content).xpath(xpath_str)) fmt = """Total COVID-19 cases in the world: {} From c39b7eadbd4d81dda5e7ffe4c169d670483f0113 Mon Sep 17 00:00:00 2001 From: Suman <66205793+Suman2023@users.noreply.github.com> Date: Sun, 13 Aug 2023 03:28:37 +0530 Subject: [PATCH 2257/2908] updated the URL and HTML tags for scrapping yahoo finance (#8942) * updated the url and tags for yahoo finance * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated to return the error text --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- web_programming/current_stock_price.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index df44da4ef351..0c06354d8998 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -3,12 +3,18 @@ def stock_price(symbol: str = "AAPL") -> str: - url = f"https://in.finance.yahoo.com/quote/%7Bsymbol%7D?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 + url = f"https://finance.yahoo.com/quote/%7Bsymbol%7D?p={symbol}" + yahoo_finance_source = requests.get(url, headers={"USER-AGENT": "Mozilla/5.0"}).text + soup = BeautifulSoup(yahoo_finance_source, "html.parser") + specific_fin_streamer_tag = soup.find("fin-streamer", {"data-test": "qsp-price"}) + if specific_fin_streamer_tag: + text = specific_fin_streamer_tag.get_text() + return text + return "No tag with the specified data-test attribute found." + +# Search for the symbol at https://finance.yahoo.com/lookup 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 4f2a346c277076ce1d69578ef52a9766e5040176 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 13 Aug 2023 13:05:42 +0300 Subject: [PATCH 2258/2908] Reduce the complexity of linear_algebra/src/polynom_for_points.py (#8605) * Reduce the complexity of linear_algebra/src/polynom_for_points.py * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * Fix review issues --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- linear_algebra/src/polynom_for_points.py | 57 ++++++++---------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/linear_algebra/src/polynom_for_points.py b/linear_algebra/src/polynom_for_points.py index f5e3db0cbb13..a9a9a8117c18 100644 --- a/linear_algebra/src/polynom_for_points.py +++ b/linear_algebra/src/polynom_for_points.py @@ -43,62 +43,43 @@ def points_to_polynomial(coordinates: list[list[int]]) -> str: x = len(coordinates) - count_of_line = 0 - matrix: list[list[float]] = [] # 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: list[float] = [] - 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 + matrix: list[list[float]] = [ + [ + coordinates[count_of_line][0] ** (x - (count_in_line + 1)) + for count_in_line in range(x) + ] + for count_of_line in range(x) + ] - count_of_line = 0 # put the y values into a vector - vector: list[float] = [] - while count_of_line < x: - vector.append(coordinates[count_of_line][1]) - count_of_line += 1 + vector: list[float] = [coordinates[count_of_line][1] for count_of_line in range(x)] - 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 count in range(x): + for number in range(x): + if count == number: + continue + fraction = matrix[number][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 + matrix[number][counting_columns] -= item * fraction # manipulating the values in the vector - vector[zahlen] -= vector[count] * bruch - zahlen += 1 - count += 1 + vector[number] -= vector[count] * fraction - count = 0 # make solutions - solution: list[str] = [] - while count < x: - solution.append(str(vector[count] / matrix[count][count])) - count += 1 + solution: list[str] = [ + str(vector[count] / matrix[count][count]) for count in range(x) + ] - count = 0 solved = "f(x)=" - while count < x: + for count in range(x): remove_e: list[str] = solution[count].split("E") if len(remove_e) > 1: solution[count] = f"{remove_e[0]}*10^{remove_e[1]}" solved += f"x^{x - (count + 1)}*{solution[count]}" if count + 1 != x: solved += "+" - count += 1 return solved From 9d86d4edaa754af06e0da9cac4a717f3765db7f4 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Mon, 14 Aug 2023 01:58:17 +0100 Subject: [PATCH 2259/2908] Create wa-tor algorithm (#8899) * feat(cellular_automata): Create wa-tor algorithm * updating DIRECTORY.md * chore(quality): Implement algo-keeper bot changes * Update cellular_automata/wa_tor.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor(repr): Return repr as python object * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng * refactor(display): Rename to display_visually to visualise * refactor(wa-tor): Use double for loop * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(wa-tor): Implement suggestions from code review --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- DIRECTORY.md | 1 + cellular_automata/wa_tor.py | 550 ++++++++++++++++++++++++++++++++++++ 2 files changed, 551 insertions(+) create mode 100644 cellular_automata/wa_tor.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 5578c1c9a6dd..cdcd1a8ae8cc 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -74,6 +74,7 @@ * [Game Of Life](cellular_automata/game_of_life.py) * [Nagel Schrekenberg](cellular_automata/nagel_schrekenberg.py) * [One Dimensional](cellular_automata/one_dimensional.py) + * [Wa Tor](cellular_automata/wa_tor.py) ## Ciphers * [A1Z26](ciphers/a1z26.py) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py new file mode 100644 index 000000000000..e423d1595bdb --- /dev/null +++ b/cellular_automata/wa_tor.py @@ -0,0 +1,550 @@ +""" +Wa-Tor algorithm (1984) + +@ https://en.wikipedia.org/wiki/Wa-Tor +@ https://beltoforion.de/en/wator/ +@ https://beltoforion.de/en/wator/images/wator_medium.webm + +This solution aims to completely remove any systematic approach +to the Wa-Tor planet, and utilise fully random methods. + +The constants are a working set that allows the Wa-Tor planet +to result in one of the three possible results. +""" + +from collections.abc import Callable +from random import randint, shuffle +from time import sleep +from typing import Literal + +WIDTH = 50 # Width of the Wa-Tor planet +HEIGHT = 50 # Height of the Wa-Tor planet + +PREY_INITIAL_COUNT = 30 # The initial number of prey entities +PREY_REPRODUCTION_TIME = 5 # The chronons before reproducing + +PREDATOR_INITIAL_COUNT = 50 # The initial number of predator entities +# The initial energy value of predator entities +PREDATOR_INITIAL_ENERGY_VALUE = 15 +# The energy value provided when consuming prey +PREDATOR_FOOD_VALUE = 5 +PREDATOR_REPRODUCTION_TIME = 20 # The chronons before reproducing + +MAX_ENTITIES = 500 # The max number of organisms on the board +# The number of entities to delete from the unbalanced side +DELETE_UNBALANCED_ENTITIES = 50 + + +class Entity: + """ + Represents an entity (either prey or predator). + + >>> e = Entity(True, coords=(0, 0)) + >>> e.prey + True + >>> e.coords + (0, 0) + >>> e.alive + True + """ + + def __init__(self, prey: bool, coords: tuple[int, int]) -> None: + self.prey = prey + # The (row, col) pos of the entity + self.coords = coords + + self.remaining_reproduction_time = ( + PREY_REPRODUCTION_TIME if prey else PREDATOR_REPRODUCTION_TIME + ) + self.energy_value = None if prey is True else PREDATOR_INITIAL_ENERGY_VALUE + self.alive = True + + def reset_reproduction_time(self) -> None: + """ + >>> e = Entity(True, coords=(0, 0)) + >>> e.reset_reproduction_time() + >>> e.remaining_reproduction_time == PREY_REPRODUCTION_TIME + True + >>> e = Entity(False, coords=(0, 0)) + >>> e.reset_reproduction_time() + >>> e.remaining_reproduction_time == PREDATOR_REPRODUCTION_TIME + True + """ + self.remaining_reproduction_time = ( + PREY_REPRODUCTION_TIME if self.prey is True else PREDATOR_REPRODUCTION_TIME + ) + + def __repr__(self) -> str: + """ + >>> Entity(prey=True, coords=(1, 1)) + Entity(prey=True, coords=(1, 1), remaining_reproduction_time=5) + >>> Entity(prey=False, coords=(2, 1)) # doctest: +NORMALIZE_WHITESPACE + Entity(prey=False, coords=(2, 1), + remaining_reproduction_time=20, energy_value=15) + """ + repr_ = ( + f"Entity(prey={self.prey}, coords={self.coords}, " + f"remaining_reproduction_time={self.remaining_reproduction_time}" + ) + if self.energy_value is not None: + repr_ += f", energy_value={self.energy_value}" + return f"{repr_})" + + +class WaTor: + """ + Represents the main Wa-Tor algorithm. + + :attr time_passed: A function that is called every time + time passes (a chronon) in order to visually display + the new Wa-Tor planet. The time_passed function can block + using time.sleep to slow the algorithm progression. + + >>> wt = WaTor(10, 15) + >>> wt.width + 10 + >>> wt.height + 15 + >>> len(wt.planet) + 15 + >>> len(wt.planet[0]) + 10 + >>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT + True + """ + + time_passed: Callable[["WaTor", int], None] | None + + def __init__(self, width: int, height: int) -> None: + self.width = width + self.height = height + self.time_passed = None + + self.planet: list[list[Entity | None]] = [[None] * width for _ in range(height)] + + # Populate planet with predators and prey randomly + for _ in range(PREY_INITIAL_COUNT): + self.add_entity(prey=True) + for _ in range(PREDATOR_INITIAL_COUNT): + self.add_entity(prey=False) + self.set_planet(self.planet) + + def set_planet(self, planet: list[list[Entity | None]]) -> None: + """ + Ease of access for testing + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> planet = [ + ... [None, None, None], + ... [None, Entity(True, coords=(1, 1)), None] + ... ] + >>> wt.set_planet(planet) + >>> wt.planet == planet + True + >>> wt.width + 3 + >>> wt.height + 2 + """ + self.planet = planet + self.width = len(planet[0]) + self.height = len(planet) + + def add_entity(self, prey: bool) -> None: + """ + Adds an entity, making sure the entity does + not override another entity + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.set_planet([[None, None], [None, None]]) + >>> wt.add_entity(True) + >>> len(wt.get_entities()) + 1 + >>> wt.add_entity(False) + >>> len(wt.get_entities()) + 2 + """ + while True: + row, col = randint(0, self.height - 1), randint(0, self.width - 1) + if self.planet[row][col] is None: + self.planet[row][col] = Entity(prey=prey, coords=(row, col)) + return + + def get_entities(self) -> list[Entity]: + """ + Returns a list of all the entities within the planet. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT + True + """ + return [entity for column in self.planet for entity in column if entity] + + def balance_predators_and_prey(self) -> None: + """ + Balances predators and preys so that prey + can not dominate the predators, blocking up + space for them to reproduce. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> for i in range(2000): + ... row, col = i // HEIGHT, i % WIDTH + ... wt.planet[row][col] = Entity(True, coords=(row, col)) + >>> entities = len(wt.get_entities()) + >>> wt.balance_predators_and_prey() + >>> len(wt.get_entities()) == entities + False + """ + entities = self.get_entities() + shuffle(entities) + + if len(entities) >= MAX_ENTITIES - MAX_ENTITIES / 10: + prey = [entity for entity in entities if entity.prey] + predators = [entity for entity in entities if not entity.prey] + + prey_count, predator_count = len(prey), len(predators) + + entities_to_purge = ( + prey[:DELETE_UNBALANCED_ENTITIES] + if prey_count > predator_count + else predators[:DELETE_UNBALANCED_ENTITIES] + ) + for entity in entities_to_purge: + self.planet[entity.coords[0]][entity.coords[1]] = None + + def get_surrounding_prey(self, entity: Entity) -> list[Entity]: + """ + Returns all the prey entities around (N, S, E, W) a predator entity. + + Subtly different to the try_to_move_to_unoccupied square. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.set_planet([ + ... [None, Entity(True, (0, 1)), None], + ... [None, Entity(False, (1, 1)), None], + ... [None, Entity(True, (2, 1)), None]]) + >>> wt.get_surrounding_prey( + ... Entity(False, (1, 1))) # doctest: +NORMALIZE_WHITESPACE + [Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5), + Entity(prey=True, coords=(2, 1), remaining_reproduction_time=5)] + >>> wt.set_planet([[Entity(False, (0, 0))]]) + >>> wt.get_surrounding_prey(Entity(False, (0, 0))) + [] + >>> wt.set_planet([ + ... [Entity(True, (0, 0)), Entity(False, (1, 0)), Entity(False, (2, 0))], + ... [None, Entity(False, (1, 1)), Entity(True, (2, 1))], + ... [None, None, None]]) + >>> wt.get_surrounding_prey(Entity(False, (1, 0))) + [Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5)] + """ + row, col = entity.coords + adjacent: list[tuple[int, int]] = [ + (row - 1, col), # North + (row + 1, col), # South + (row, col - 1), # West + (row, col + 1), # East + ] + + return [ + ent + for r, c in adjacent + if 0 <= r < self.height + and 0 <= c < self.width + and (ent := self.planet[r][c]) is not None + and ent.prey + ] + + def move_and_reproduce( + self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]] + ) -> None: + """ + Attempts to move to an unoccupied neighbouring square + in either of the four directions (North, South, East, West). + If the move was successful and the remaining_reproduction time is + equal to 0, then a new prey or predator can also be created + in the previous square. + + :param direction_orders: Ordered list (like priority queue) depicting + order to attempt to move. Removes any systematic + approach of checking neighbouring squares. + + >>> planet = [ + ... [None, None, None], + ... [None, Entity(True, coords=(1, 1)), None], + ... [None, None, None] + ... ] + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.set_planet(planet) + >>> wt.move_and_reproduce(Entity(True, coords=(1, 1)), direction_orders=["N"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[None, Entity(prey=True, coords=(0, 1), remaining_reproduction_time=4), None], + [None, None, None], + [None, None, None]] + >>> wt.planet[0][0] = Entity(True, coords=(0, 0)) + >>> wt.move_and_reproduce(Entity(True, coords=(0, 1)), + ... direction_orders=["N", "W", "E", "S"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), None, + Entity(prey=True, coords=(0, 2), remaining_reproduction_time=4)], + [None, None, None], + [None, None, None]] + >>> wt.planet[0][1] = wt.planet[0][2] + >>> wt.planet[0][2] = None + >>> wt.move_and_reproduce(Entity(True, coords=(0, 1)), + ... direction_orders=["N", "W", "S", "E"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), None, None], + [None, Entity(prey=True, coords=(1, 1), remaining_reproduction_time=4), None], + [None, None, None]] + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> reproducable_entity = Entity(False, coords=(0, 1)) + >>> reproducable_entity.remaining_reproduction_time = 0 + >>> wt.planet = [[None, reproducable_entity]] + >>> wt.move_and_reproduce(reproducable_entity, + ... direction_orders=["N", "W", "S", "E"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[Entity(prey=False, coords=(0, 0), + remaining_reproduction_time=20, energy_value=15), + Entity(prey=False, coords=(0, 1), remaining_reproduction_time=20, + energy_value=15)]] + """ + row, col = coords = entity.coords + + adjacent_squares: dict[Literal["N", "E", "S", "W"], tuple[int, int]] = { + "N": (row - 1, col), # North + "S": (row + 1, col), # South + "W": (row, col - 1), # West + "E": (row, col + 1), # East + } + # Weight adjacent locations + adjacent: list[tuple[int, int]] = [] + for order in direction_orders: + adjacent.append(adjacent_squares[order]) + + for r, c in adjacent: + if ( + 0 <= r < self.height + and 0 <= c < self.width + and self.planet[r][c] is None + ): + # Move entity to empty adjacent square + self.planet[r][c] = entity + self.planet[row][col] = None + entity.coords = (r, c) + break + + # (2.) See if it possible to reproduce in previous square + if coords != entity.coords and entity.remaining_reproduction_time <= 0: + # Check if the entities on the planet is less than the max limit + if len(self.get_entities()) < MAX_ENTITIES: + # Reproduce in previous square + self.planet[row][col] = Entity(prey=entity.prey, coords=coords) + entity.reset_reproduction_time() + else: + entity.remaining_reproduction_time -= 1 + + def perform_prey_actions( + self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]] + ) -> None: + """ + Performs the actions for a prey entity + + For prey the rules are: + 1. At each chronon, a prey moves randomly to one of the adjacent unoccupied + squares. If there are no free squares, no movement takes place. + 2. Once a prey has survived a certain number of chronons it may reproduce. + This is done as it moves to a neighbouring square, + leaving behind a new prey in its old position. + Its reproduction time is also reset to zero. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> reproducable_entity = Entity(True, coords=(0, 1)) + >>> reproducable_entity.remaining_reproduction_time = 0 + >>> wt.planet = [[None, reproducable_entity]] + >>> wt.perform_prey_actions(reproducable_entity, + ... direction_orders=["N", "W", "S", "E"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), + Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5)]] + """ + self.move_and_reproduce(entity, direction_orders) + + def perform_predator_actions( + self, + entity: Entity, + occupied_by_prey_coords: tuple[int, int] | None, + direction_orders: list[Literal["N", "E", "S", "W"]], + ) -> None: + """ + Performs the actions for a predator entity + + :param occupied_by_prey_coords: Move to this location if there is prey there + + For predators the rules are: + 1. At each chronon, a predator moves randomly to an adjacent square occupied + by a prey. If there is none, the predator moves to a random adjacent + unoccupied square. If there are no free squares, no movement takes place. + 2. At each chronon, each predator is deprived of a unit of energy. + 3. Upon reaching zero energy, a predator dies. + 4. If a predator moves to a square occupied by a prey, + it eats the prey and earns a certain amount of energy. + 5. Once a predator has survived a certain number of chronons + it may reproduce in exactly the same way as the prey. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]]) + >>> wt.perform_predator_actions(Entity(False, coords=(0, 1)), (0, 0), []) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[Entity(prey=False, coords=(0, 0), + remaining_reproduction_time=20, energy_value=19), None]] + """ + assert entity.energy_value is not None # [type checking] + + # (3.) If the entity has 0 energy, it will die + if entity.energy_value == 0: + self.planet[entity.coords[0]][entity.coords[1]] = None + return + + # (1.) Move to entity if possible + if occupied_by_prey_coords is not None: + # Kill the prey + prey = self.planet[occupied_by_prey_coords[0]][occupied_by_prey_coords[1]] + assert prey is not None + prey.alive = False + + # Move onto prey + self.planet[occupied_by_prey_coords[0]][occupied_by_prey_coords[1]] = entity + self.planet[entity.coords[0]][entity.coords[1]] = None + + entity.coords = occupied_by_prey_coords + # (4.) Eats the prey and earns energy + entity.energy_value += PREDATOR_FOOD_VALUE + else: + # (5.) If it has survived the certain number of chronons it will also + # reproduce in this function + self.move_and_reproduce(entity, direction_orders) + + # (2.) Each chronon, the predator is deprived of a unit of energy + entity.energy_value -= 1 + + def run(self, *, iteration_count: int) -> None: + """ + Emulate time passing by looping iteration_count times + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1) + >>> len(list(filter(lambda entity: entity.prey is False, + ... wt.get_entities()))) >= PREDATOR_INITIAL_COUNT + True + """ + for iter_num in range(iteration_count): + # Generate list of all entities in order to randomly + # pop an entity at a time to simulate true randomness + # This removes the systematic approach of iterating + # through each entity width by height + all_entities = self.get_entities() + + for __ in range(len(all_entities)): + entity = all_entities.pop(randint(0, len(all_entities) - 1)) + if entity.alive is False: + continue + + directions: list[Literal["N", "E", "S", "W"]] = ["N", "E", "S", "W"] + shuffle(directions) # Randomly shuffle directions + + if entity.prey: + self.perform_prey_actions(entity, directions) + else: + # Create list of surrounding prey + surrounding_prey = self.get_surrounding_prey(entity) + surrounding_prey_coords = None + + if surrounding_prey: + # Again, randomly shuffle directions + shuffle(surrounding_prey) + surrounding_prey_coords = surrounding_prey[0].coords + + self.perform_predator_actions( + entity, surrounding_prey_coords, directions + ) + + # Balance out the predators and prey + self.balance_predators_and_prey() + + if self.time_passed is not None: + # Call time_passed function for Wa-Tor planet + # visualisation in a terminal or a graph. + self.time_passed(self, iter_num) + + +def visualise(wt: WaTor, iter_number: int, *, colour: bool = True) -> None: + """ + Visually displays the Wa-Tor planet using + an ascii code in terminal to clear and re-print + the Wa-Tor planet at intervals. + + Uses ascii colour codes to colourfully display + the predators and prey. + + (0x60f197) Prey = # + (0xfffff) Predator = x + + >>> wt = WaTor(30, 30) + >>> wt.set_planet([ + ... [Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1)), None], + ... [Entity(False, coords=(1, 0)), None, Entity(False, coords=(1, 2))], + ... [None, Entity(True, coords=(2, 1)), None] + ... ]) + >>> visualise(wt, 0, colour=False) # doctest: +NORMALIZE_WHITESPACE + # x . + x . x + . # . + + Iteration: 0 | Prey count: 2 | Predator count: 3 | + """ + if colour: + __import__("os").system("") + print("\x1b[0;0H\x1b[2J\x1b[?25l") + + reprint = "\x1b[0;0H" if colour else "" + ansi_colour_end = "\x1b[0m " if colour else " " + + planet = wt.planet + output = "" + + # Iterate over every entity in the planet + for row in planet: + for entity in row: + if entity is None: + output += " . " + else: + if colour is True: + output += ( + "\x1b[38;2;96;241;151m" + if entity.prey + else "\x1b[38;2;255;255;15m" + ) + output += f" {'#' if entity.prey else 'x'}{ansi_colour_end}" + + output += "\n" + + entities = wt.get_entities() + prey_count = sum(entity.prey for entity in entities) + + print( + f"{output}\n Iteration: {iter_number} | Prey count: {prey_count} | " + f"Predator count: {len(entities) - prey_count} | {reprint}" + ) + # Block the thread to be able to visualise seeing the algorithm + sleep(0.05) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + wt = WaTor(WIDTH, HEIGHT) + wt.time_passed = visualise + wt.run(iteration_count=100_000) From f24ab2c60dabb11c37667c5899c39713e84fc871 Mon Sep 17 00:00:00 2001 From: Amir Hosseini <19665344+itsamirhn@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:07:41 +0330 Subject: [PATCH 2260/2908] Add: Two Regex match algorithm (Recursive & DP) (#6321) * Add recursive solution to regex_match.py * Add dp solution to regex_match.py * Add link to regex_match.py * Minor edit * Minor change * Minor change * Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng * Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng * Fix ruff formatting in if statements * Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Tianyi Zheng Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/regex_match.py | 97 ++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 dynamic_programming/regex_match.py diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py new file mode 100644 index 000000000000..200a882831c0 --- /dev/null +++ b/dynamic_programming/regex_match.py @@ -0,0 +1,97 @@ +""" +Regex matching check if a text matches pattern or not. +Pattern: + '.' Matches any single character. + '*' Matches zero or more of the preceding element. +More info: + https://medium.com/trick-the-interviwer/regular-expression-matching-9972eb74c03 +""" + + +def recursive_match(text: str, pattern: str) -> bool: + """ + Recursive matching algorithm. + + Time complexity: O(2 ^ (|text| + |pattern|)) + Space complexity: Recursion depth is O(|text| + |pattern|). + + :param text: Text to match. + :param pattern: Pattern to match. + :return: True if text matches pattern, False otherwise. + + >>> recursive_match('abc', 'a.c') + True + >>> recursive_match('abc', 'af*.c') + True + >>> recursive_match('abc', 'a.c*') + True + >>> recursive_match('abc', 'a.c*d') + False + >>> recursive_match('aa', '.*') + True + """ + if not pattern: + return not text + + if not text: + return pattern[-1] == "*" and recursive_match(text, pattern[:-2]) + + if text[-1] == pattern[-1] or pattern[-1] == ".": + return recursive_match(text[:-1], pattern[:-1]) + + if pattern[-1] == "*": + return recursive_match(text[:-1], pattern) or recursive_match( + text, pattern[:-2] + ) + + return False + + +def dp_match(text: str, pattern: str) -> bool: + """ + Dynamic programming matching algorithm. + + Time complexity: O(|text| * |pattern|) + Space complexity: O(|text| * |pattern|) + + :param text: Text to match. + :param pattern: Pattern to match. + :return: True if text matches pattern, False otherwise. + + >>> dp_match('abc', 'a.c') + True + >>> dp_match('abc', 'af*.c') + True + >>> dp_match('abc', 'a.c*') + True + >>> dp_match('abc', 'a.c*d') + False + >>> dp_match('aa', '.*') + True + """ + m = len(text) + n = len(pattern) + dp = [[False for _ in range(n + 1)] for _ in range(m + 1)] + dp[0][0] = True + + for j in range(1, n + 1): + dp[0][j] = pattern[j - 1] == "*" and dp[0][j - 2] + + for i in range(1, m + 1): + for j in range(1, n + 1): + if pattern[j - 1] in {".", text[i - 1]}: + dp[i][j] = dp[i - 1][j - 1] + elif pattern[j - 1] == "*": + dp[i][j] = dp[i][j - 2] + if pattern[j - 2] in {".", text[i - 1]}: + dp[i][j] |= dp[i - 1][j] + else: + dp[i][j] = False + + return dp[m][n] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 02d89bde679488e97cdb077c511b3dbfb660e2b8 Mon Sep 17 00:00:00 2001 From: Ajinkya Chikhale <86607732+ajinkyac03@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:42:42 +0530 Subject: [PATCH 2261/2908] Added implementation for Tribonacci sequence using dp (#6356) * Added implementation for Tribonacci sequence using dp * Updated parameter name * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- dynamic_programming/tribonacci.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 dynamic_programming/tribonacci.py diff --git a/dynamic_programming/tribonacci.py b/dynamic_programming/tribonacci.py new file mode 100644 index 000000000000..58e15da918e2 --- /dev/null +++ b/dynamic_programming/tribonacci.py @@ -0,0 +1,24 @@ +# Tribonacci sequence using Dynamic Programming + + +def tribonacci(num: int) -> list[int]: + """ + Given a number, return first n Tribonacci Numbers. + >>> tribonacci(5) + [0, 0, 1, 1, 2] + >>> tribonacci(8) + [0, 0, 1, 1, 2, 4, 7, 13] + """ + dp = [0] * num + dp[2] = 1 + + for i in range(3, num): + dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3] + + return dp + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c290dd6a433b43b242336d49d227f5e25bbb76de Mon Sep 17 00:00:00 2001 From: Adithya Awati Date: Mon, 14 Aug 2023 12:46:24 +0530 Subject: [PATCH 2262/2908] Update run.py in machine_learning/forecasting (#8957) * Fixed reading CSV file, added type check for data_safety_checker function * Formatted run.py * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + machine_learning/forecasting/ex_data.csv | 2 +- machine_learning/forecasting/run.py | 35 ++++++++++++------------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index cdcd1a8ae8cc..3a244ca6caaf 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -336,6 +336,7 @@ * [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py) * [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py) * [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py) + * [Regex Match](dynamic_programming/regex_match.py) * [Rod Cutting](dynamic_programming/rod_cutting.py) * [Subset Generation](dynamic_programming/subset_generation.py) * [Sum Of Subset](dynamic_programming/sum_of_subset.py) diff --git a/machine_learning/forecasting/ex_data.csv b/machine_learning/forecasting/ex_data.csv index 1c429e649755..e6e73c4a1ca4 100644 --- a/machine_learning/forecasting/ex_data.csv +++ b/machine_learning/forecasting/ex_data.csv @@ -1,4 +1,4 @@ -total_user,total_events,days +total_users,total_events,days 18231,0.0,1 22621,1.0,2 15675,0.0,3 diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py index 0909b76d8907..88c4a537b302 100644 --- a/machine_learning/forecasting/run.py +++ b/machine_learning/forecasting/run.py @@ -1,6 +1,6 @@ """ this is code for forecasting -but i modified it and used it for safety checker of data +but I modified it and used it for safety checker of data for ex: you have an 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 @@ -102,6 +102,10 @@ def data_safety_checker(list_vote: list, actual_result: float) -> bool: """ safe = 0 not_safe = 0 + + if not isinstance(actual_result, float): + raise TypeError("Actual result should be float. Value passed is a list") + for i in list_vote: if i > actual_result: safe = not_safe + 1 @@ -114,16 +118,11 @@ def data_safety_checker(list_vote: list, actual_result: float) -> bool: if __name__ == "__main__": - # 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) """ + data_input_df = pd.read_csv("ex_data.csv") # start normalization normalize_df = Normalizer().fit_transform(data_input_df.values) @@ -138,23 +137,23 @@ def data_safety_checker(list_vote: list, actual_result: float) -> bool: x_test = x[len(x) - 1 :] # for linear regression & 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] + train_date = total_date[: len(total_date) - 1] + train_user = total_user[: len(total_user) - 1] + train_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 :] + test_date = total_date[len(total_date) - 1 :] + test_user = total_user[len(total_user) - 1 :] + test_match = total_match[len(total_match) - 1 :] # voting system with forecasting res_vote = [ linear_regression_prediction( - trn_date, trn_user, trn_match, tst_date, tst_match + train_date, train_user, train_match, test_date, test_match ), - sarimax_predictor(trn_user, trn_match, tst_match), - support_vector_regressor(x_train, x_test, trn_user), + sarimax_predictor(train_user, train_match, test_match), + support_vector_regressor(x_train, x_test, train_user), ] # check the safety of today's data - not_str = "" if data_safety_checker(res_vote, tst_user) else "not " - print("Today's data is {not_str}safe.") + not_str = "" if data_safety_checker(res_vote, test_user[0]) else "not " + print(f"Today's data is {not_str}safe.") From 4b7ecb6a8134379481dd3d5035cb99a627930462 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Mon, 14 Aug 2023 09:28:52 +0100 Subject: [PATCH 2263/2908] Create is valid email address algorithm (#8907) * feat(strings): Create is valid email address * updating DIRECTORY.md * feat(strings): Create is_valid_email_address algorithm * chore(is_valid_email_address): Implement changes from code review * Update strings/is_valid_email_address.py Co-authored-by: Tianyi Zheng * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(is_valid_email_address): Fix ruff error * Update strings/is_valid_email_address.py Co-authored-by: Tianyi Zheng --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 1 + strings/is_valid_email_address.py | 117 ++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 strings/is_valid_email_address.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 3a244ca6caaf..14152e4abd04 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1171,6 +1171,7 @@ * [Is Pangram](strings/is_pangram.py) * [Is Spain National Id](strings/is_spain_national_id.py) * [Is Srilankan Phone Number](strings/is_srilankan_phone_number.py) + * [Is Valid Email Address](strings/is_valid_email_address.py) * [Jaro Winkler](strings/jaro_winkler.py) * [Join](strings/join.py) * [Knuth Morris Pratt](strings/knuth_morris_pratt.py) diff --git a/strings/is_valid_email_address.py b/strings/is_valid_email_address.py new file mode 100644 index 000000000000..205394f81297 --- /dev/null +++ b/strings/is_valid_email_address.py @@ -0,0 +1,117 @@ +""" +Implements an is valid email address algorithm + +@ https://en.wikipedia.org/wiki/Email_address +""" + +import string + +email_tests: tuple[tuple[str, bool], ...] = ( + ("simple@example.com", True), + ("very.common@example.com", True), + ("disposable.style.email.with+symbol@example.com", True), + ("other-email-with-hyphen@and.subdomains.example.com", True), + ("fully-qualified-domain@example.com", True), + ("user.name+tag+sorting@example.com", True), + ("x@example.com", True), + ("example-indeed@strange-example.com", True), + ("test/test@test.com", True), + ( + "123456789012345678901234567890123456789012345678901234567890123@example.com", + True, + ), + ("admin@mailserver1", True), + ("example@s.example", True), + ("Abc.example.com", False), + ("A@b@c@example.com", False), + ("abc@example..com", False), + ("a(c)d,e:f;gi[j\\k]l@example.com", False), + ( + "12345678901234567890123456789012345678901234567890123456789012345@example.com", + False, + ), + ("i.like.underscores@but_its_not_allowed_in_this_part", False), + ("", False), +) + +# The maximum octets (one character as a standard unicode character is one byte) +# that the local part and the domain part can have +MAX_LOCAL_PART_OCTETS = 64 +MAX_DOMAIN_OCTETS = 255 + + +def is_valid_email_address(email: str) -> bool: + """ + Returns True if the passed email address is valid. + + The local part of the email precedes the singular @ symbol and + is associated with a display-name. For example, "john.smith" + The domain is stricter than the local part and follows the @ symbol. + + Global email checks: + 1. There can only be one @ symbol in the email address. Technically if the + @ symbol is quoted in the local-part, then it is valid, however this + implementation ignores "" for now. + (See https://en.wikipedia.org/wiki/Email_address#:~:text=If%20quoted,) + 2. The local-part and the domain are limited to a certain number of octets. With + unicode storing a single character in one byte, each octet is equivalent to + a character. Hence, we can just check the length of the string. + Checks for the local-part: + 3. The local-part may contain: upper and lowercase latin letters, digits 0 to 9, + and printable characters (!#$%&'*+-/=?^_`{|}~) + 4. The local-part may also contain a "." in any place that is not the first or + last character, and may not have more than one "." consecutively. + + Checks for the domain: + 5. The domain may contain: upper and lowercase latin letters and digits 0 to 9 + 6. Hyphen "-", provided that it is not the first or last character + 7. The domain may also contain a "." in any place that is not the first or + last character, and may not have more than one "." consecutively. + + >>> for email, valid in email_tests: + ... assert is_valid_email_address(email) == valid + """ + + # (1.) Make sure that there is only one @ symbol in the email address + if email.count("@") != 1: + return False + + local_part, domain = email.split("@") + # (2.) Check octet length of the local part and domain + if len(local_part) > MAX_LOCAL_PART_OCTETS or len(domain) > MAX_DOMAIN_OCTETS: + return False + + # (3.) Validate the characters in the local-part + if any( + char not in string.ascii_letters + string.digits + ".(!#$%&'*+-/=?^_`{|}~)" + for char in local_part + ): + return False + + # (4.) Validate the placement of "." characters in the local-part + if local_part.startswith(".") or local_part.endswith(".") or ".." in local_part: + return False + + # (5.) Validate the characters in the domain + if any(char not in string.ascii_letters + string.digits + ".-" for char in domain): + return False + + # (6.) Validate the placement of "-" characters + if domain.startswith("-") or domain.endswith("."): + return False + + # (7.) Validate the placement of "." characters + if domain.startswith(".") or domain.endswith(".") or ".." in domain: + return False + return True + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + for email, valid in email_tests: + is_valid = is_valid_email_address(email) + assert is_valid == valid, f"{email} is {is_valid}" + print(f"Email address {email} is {'not ' if not is_valid else ''}valid") From ac68dc1128535b6798af256fcdab67340f6c0fd9 Mon Sep 17 00:00:00 2001 From: Adithya Awati <1ds21ai001@dsce.edu.in> Date: Mon, 14 Aug 2023 14:04:16 +0530 Subject: [PATCH 2264/2908] Fixed Pytest warnings for machine_learning/forecasting (#8958) * updating DIRECTORY.md * Fixed pyTest Warnings --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + machine_learning/forecasting/run.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 14152e4abd04..384ce1b2209d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -340,6 +340,7 @@ * [Rod Cutting](dynamic_programming/rod_cutting.py) * [Subset Generation](dynamic_programming/subset_generation.py) * [Sum Of Subset](dynamic_programming/sum_of_subset.py) + * [Tribonacci](dynamic_programming/tribonacci.py) * [Viterbi](dynamic_programming/viterbi.py) * [Word Break](dynamic_programming/word_break.py) diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py index 88c4a537b302..64e719daacc2 100644 --- a/machine_learning/forecasting/run.py +++ b/machine_learning/forecasting/run.py @@ -11,6 +11,8 @@ u can just adjust it for ur own purpose """ +from warnings import simplefilter + import numpy as np import pandas as pd from sklearn.preprocessing import Normalizer @@ -45,8 +47,10 @@ def sarimax_predictor(train_user: list, train_match: list, test_match: list) -> >>> sarimax_predictor([4,2,6,8], [3,1,2,4], [2]) 6.6666671111109626 """ + # Suppress the User Warning raised by SARIMAX due to insufficient observations + simplefilter("ignore", UserWarning) order = (1, 2, 1) - seasonal_order = (1, 1, 0, 7) + seasonal_order = (1, 1, 1, 7) model = SARIMAX( train_user, exog=train_match, order=order, seasonal_order=seasonal_order ) From 2ab3bf2689d21e7375539c79ecee358e9d7c3359 Mon Sep 17 00:00:00 2001 From: robertjcalistri <85811008+robertjcalistri@users.noreply.github.com> Date: Mon, 14 Aug 2023 05:31:53 -0400 Subject: [PATCH 2265/2908] =?UTF-8?q?Added=20functions=20to=20calculate=20?= =?UTF-8?q?temperature=20of=20an=20ideal=20gas=20and=20number=20o=E2=80=A6?= =?UTF-8?q?=20(#8919)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added functions to calculate temperature of an ideal gas and number of moles of an ideal gas * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update physics/ideal_gas_law.py Renamed function name Co-authored-by: Tianyi Zheng * Update physics/ideal_gas_law.py Updated formatting Co-authored-by: Tianyi Zheng * Update physics/ideal_gas_law.py Removed unnecessary parentheses Co-authored-by: Tianyi Zheng * Update physics/ideal_gas_law.py Removed unnecessary parentheses Co-authored-by: Tianyi Zheng * Update ideal_gas_law.py Updated incorrect function calls moles of gas system doctests * Update physics/ideal_gas_law.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- physics/ideal_gas_law.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/physics/ideal_gas_law.py b/physics/ideal_gas_law.py index 805da47b0079..09b4fb3a9c14 100644 --- a/physics/ideal_gas_law.py +++ b/physics/ideal_gas_law.py @@ -53,6 +53,40 @@ def volume_of_gas_system(moles: float, kelvin: float, pressure: float) -> float: return moles * kelvin * UNIVERSAL_GAS_CONSTANT / pressure +def temperature_of_gas_system(moles: float, volume: float, pressure: float) -> float: + """ + >>> temperature_of_gas_system(2, 100, 5) + 30.068090996146232 + >>> temperature_of_gas_system(11, 5009, 1000) + 54767.66101807144 + >>> temperature_of_gas_system(3, -0.46, 23.5) + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter positive value. + """ + if moles < 0 or volume < 0 or pressure < 0: + raise ValueError("Invalid inputs. Enter positive value.") + + return pressure * volume / (moles * UNIVERSAL_GAS_CONSTANT) + + +def moles_of_gas_system(kelvin: float, volume: float, pressure: float) -> float: + """ + >>> moles_of_gas_system(100, 5, 10) + 0.06013618199229246 + >>> moles_of_gas_system(110, 5009, 1000) + 5476.766101807144 + >>> moles_of_gas_system(3, -0.46, 23.5) + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter positive value. + """ + if kelvin < 0 or volume < 0 or pressure < 0: + raise ValueError("Invalid inputs. Enter positive value.") + + return pressure * volume / (kelvin * UNIVERSAL_GAS_CONSTANT) + + if __name__ == "__main__": from doctest import testmod From fb1b939a89fb08370297cbb455846f61f66847bc Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Mon, 14 Aug 2023 12:17:27 +0100 Subject: [PATCH 2266/2908] Consolidate find_min and find_min recursive and find_max and find_max_recursive (#8960) * updating DIRECTORY.md * refactor(min-max): Consolidate implementations * updating DIRECTORY.md * refactor(min-max): Append _iterative to func name --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 -- maths/find_max.py | 65 +++++++++++++++++++++++++++++++++---- maths/find_max_recursion.py | 58 --------------------------------- maths/find_min.py | 65 +++++++++++++++++++++++++++++++++---- maths/find_min_recursion.py | 58 --------------------------------- 5 files changed, 118 insertions(+), 130 deletions(-) delete mode 100644 maths/find_max_recursion.py delete mode 100644 maths/find_min_recursion.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 384ce1b2209d..be5fa3584a58 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -573,9 +573,7 @@ * [Fermat Little Theorem](maths/fermat_little_theorem.py) * [Fibonacci](maths/fibonacci.py) * [Find Max](maths/find_max.py) - * [Find Max Recursion](maths/find_max_recursion.py) * [Find Min](maths/find_min.py) - * [Find Min Recursion](maths/find_min_recursion.py) * [Floor](maths/floor.py) * [Gamma](maths/gamma.py) * [Gamma Recursive](maths/gamma_recursive.py) diff --git a/maths/find_max.py b/maths/find_max.py index 684fbe8161e8..729a80ab421c 100644 --- a/maths/find_max.py +++ b/maths/find_max.py @@ -1,23 +1,23 @@ from __future__ import annotations -def find_max(nums: list[int | float]) -> int | float: +def find_max_iterative(nums: list[int | float]) -> int | float: """ >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): - ... find_max(nums) == max(nums) + ... find_max_iterative(nums) == max(nums) True True True True - >>> find_max([2, 4, 9, 7, 19, 94, 5]) + >>> find_max_iterative([2, 4, 9, 7, 19, 94, 5]) 94 - >>> find_max([]) + >>> find_max_iterative([]) Traceback (most recent call last): ... - ValueError: find_max() arg is an empty sequence + ValueError: find_max_iterative() arg is an empty sequence """ if len(nums) == 0: - raise ValueError("find_max() arg is an empty sequence") + raise ValueError("find_max_iterative() arg is an empty sequence") max_num = nums[0] for x in nums: if x > max_num: @@ -25,6 +25,59 @@ def find_max(nums: list[int | float]) -> int | float: return max_num +# Divide and Conquer algorithm +def find_max_recursive(nums: list[int | float], left: int, right: int) -> int | float: + """ + 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 + + >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): + ... find_max_recursive(nums, 0, len(nums) - 1) == max(nums) + True + True + True + True + >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] + >>> find_max_recursive(nums, 0, len(nums) - 1) == max(nums) + True + >>> find_max_recursive([], 0, 0) + Traceback (most recent call last): + ... + ValueError: find_max_recursive() arg is an empty sequence + >>> find_max_recursive(nums, 0, len(nums)) == max(nums) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> find_max_recursive(nums, -len(nums), -1) == max(nums) + True + >>> find_max_recursive(nums, -len(nums) - 1, -1) == max(nums) + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + if len(nums) == 0: + raise ValueError("find_max_recursive() arg is an empty sequence") + if ( + left >= len(nums) + or left < -len(nums) + or right >= len(nums) + or right < -len(nums) + ): + raise IndexError("list index out of range") + if left == right: + return nums[left] + mid = (left + right) >> 1 # the middle + left_max = find_max_recursive(nums, left, mid) # find max in range[left, mid] + right_max = find_max_recursive( + 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__": import doctest diff --git a/maths/find_max_recursion.py b/maths/find_max_recursion.py deleted file mode 100644 index 629932e0818f..000000000000 --- a/maths/find_max_recursion.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - - -# Divide and Conquer algorithm -def find_max(nums: list[int | float], left: int, right: int) -> int | float: - """ - 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 - - >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): - ... find_max(nums, 0, len(nums) - 1) == max(nums) - True - True - True - True - >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] - >>> find_max(nums, 0, len(nums) - 1) == max(nums) - True - >>> find_max([], 0, 0) - Traceback (most recent call last): - ... - ValueError: find_max() arg is an empty sequence - >>> find_max(nums, 0, len(nums)) == max(nums) - Traceback (most recent call last): - ... - IndexError: list index out of range - >>> find_max(nums, -len(nums), -1) == max(nums) - True - >>> find_max(nums, -len(nums) - 1, -1) == max(nums) - Traceback (most recent call last): - ... - IndexError: list index out of range - """ - if len(nums) == 0: - raise ValueError("find_max() arg is an empty sequence") - if ( - left >= len(nums) - or left < -len(nums) - or right >= len(nums) - or right < -len(nums) - ): - raise IndexError("list index out of range") - 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__": - import doctest - - doctest.testmod(verbose=True) diff --git a/maths/find_min.py b/maths/find_min.py index 2eac087c6388..762562e36ef9 100644 --- a/maths/find_min.py +++ b/maths/find_min.py @@ -1,33 +1,86 @@ from __future__ import annotations -def find_min(nums: list[int | float]) -> int | float: +def find_min_iterative(nums: list[int | float]) -> int | float: """ Find Minimum Number in a List :param nums: contains elements :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) + ... find_min_iterative(nums) == min(nums) True True True True - >>> find_min([0, 1, 2, 3, 4, 5, -3, 24, -56]) + >>> find_min_iterative([0, 1, 2, 3, 4, 5, -3, 24, -56]) -56 - >>> find_min([]) + >>> find_min_iterative([]) Traceback (most recent call last): ... - ValueError: find_min() arg is an empty sequence + ValueError: find_min_iterative() arg is an empty sequence """ if len(nums) == 0: - raise ValueError("find_min() arg is an empty sequence") + raise ValueError("find_min_iterative() arg is an empty sequence") min_num = nums[0] for num in nums: min_num = min(min_num, num) return min_num +# Divide and Conquer algorithm +def find_min_recursive(nums: list[int | float], left: int, right: int) -> int | float: + """ + 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 + + >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): + ... find_min_recursive(nums, 0, len(nums) - 1) == min(nums) + True + True + True + True + >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] + >>> find_min_recursive(nums, 0, len(nums) - 1) == min(nums) + True + >>> find_min_recursive([], 0, 0) + Traceback (most recent call last): + ... + ValueError: find_min_recursive() arg is an empty sequence + >>> find_min_recursive(nums, 0, len(nums)) == min(nums) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> find_min_recursive(nums, -len(nums), -1) == min(nums) + True + >>> find_min_recursive(nums, -len(nums) - 1, -1) == min(nums) + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + if len(nums) == 0: + raise ValueError("find_min_recursive() arg is an empty sequence") + if ( + left >= len(nums) + or left < -len(nums) + or right >= len(nums) + or right < -len(nums) + ): + raise IndexError("list index out of range") + if left == right: + return nums[left] + mid = (left + right) >> 1 # the middle + left_min = find_min_recursive(nums, left, mid) # find min in range[left, mid] + right_min = find_min_recursive( + 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__": import doctest diff --git a/maths/find_min_recursion.py b/maths/find_min_recursion.py deleted file mode 100644 index 4d11015efcd5..000000000000 --- a/maths/find_min_recursion.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - - -# Divide and Conquer algorithm -def find_min(nums: list[int | float], left: int, right: int) -> int | float: - """ - 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 - - >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): - ... find_min(nums, 0, len(nums) - 1) == min(nums) - True - True - True - True - >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] - >>> find_min(nums, 0, len(nums) - 1) == min(nums) - True - >>> find_min([], 0, 0) - Traceback (most recent call last): - ... - ValueError: find_min() arg is an empty sequence - >>> find_min(nums, 0, len(nums)) == min(nums) - Traceback (most recent call last): - ... - IndexError: list index out of range - >>> find_min(nums, -len(nums), -1) == min(nums) - True - >>> find_min(nums, -len(nums) - 1, -1) == min(nums) - Traceback (most recent call last): - ... - IndexError: list index out of range - """ - if len(nums) == 0: - raise ValueError("find_min() arg is an empty sequence") - if ( - left >= len(nums) - or left < -len(nums) - or right >= len(nums) - or right < -len(nums) - ): - raise IndexError("list index out of range") - 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__": - import doctest - - doctest.testmod(verbose=True) From 7021afda047b034958bfdb67e8479af2e8c7aeb9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 23:12:11 -0400 Subject: [PATCH 2267/2908] [pre-commit.ci] pre-commit autoupdate (#8963) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.282 → v0.0.284](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.282...v0.0.284) - [github.com/tox-dev/pyproject-fmt: 0.13.0 → 0.13.1](https://github.com/tox-dev/pyproject-fmt/compare/0.13.0...0.13.1) - [github.com/pre-commit/mirrors-mypy: v1.4.1 → v1.5.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.4.1...v1.5.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da6762123b04..b08139561639 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.282 + rev: v0.0.284 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.13.0" + rev: "0.13.1" hooks: - id: pyproject-fmt @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.1 + rev: v1.5.0 hooks: - id: mypy args: From 7618a92fee002475b3bed9227944972d346db440 Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Wed, 16 Aug 2023 00:07:49 +0330 Subject: [PATCH 2268/2908] Remove a slash in path to save the file correctly on Linux (#8053) --- computer_vision/flip_augmentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/computer_vision/flip_augmentation.py b/computer_vision/flip_augmentation.py index 93b4e3f6da79..77a8cbd7b14f 100644 --- a/computer_vision/flip_augmentation.py +++ b/computer_vision/flip_augmentation.py @@ -32,13 +32,13 @@ def main() -> None: letter_code = random_chars(32) file_name = paths[index].split(os.sep)[-1].rsplit(".", 1)[0] file_root = f"{OUTPUT_DIR}/{file_name}_FLIP_{letter_code}" - cv2.imwrite(f"/{file_root}.jpg", image, [cv2.IMWRITE_JPEG_QUALITY, 85]) + cv2.imwrite(f"{file_root}.jpg", image, [cv2.IMWRITE_JPEG_QUALITY, 85]) print(f"Success {index+1}/{len(new_images)} with {file_name}") annos_list = [] for anno in new_annos[index]: obj = f"{anno[0]} {anno[1]} {anno[2]} {anno[3]} {anno[4]}" annos_list.append(obj) - with open(f"/{file_root}.txt", "w") as outfile: + with open(f"{file_root}.txt", "w") as outfile: outfile.write("\n".join(line for line in annos_list)) From 490e645ed3b7ae50f0d7e23e047d088ba069ed56 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Tue, 15 Aug 2023 22:27:41 +0100 Subject: [PATCH 2269/2908] Fix minor typing errors in maths/ (#8959) * updating DIRECTORY.md * types(maths): Fix pylance issues in maths * reset(vsc): Reset settings changes * Update maths/jaccard_similarity.py Co-authored-by: Tianyi Zheng * revert(erosion_operation): Revert erosion_operation * test(jaccard_similarity): Add doctest to test alternative_union * types(newton_raphson): Add typehints to func bodies --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- .../erosion_operation.py | 1 + digital_image_processing/rotation/rotation.py | 4 +- maths/average_median.py | 4 +- maths/euler_modified.py | 2 +- maths/gaussian_error_linear_unit.py | 4 +- maths/jaccard_similarity.py | 45 ++++++++++++------- maths/newton_raphson.py | 33 +++++++++----- maths/qr_decomposition.py | 2 +- maths/sigmoid.py | 2 +- maths/tanh.py | 4 +- 10 files changed, 65 insertions(+), 36 deletions(-) diff --git a/digital_image_processing/morphological_operations/erosion_operation.py b/digital_image_processing/morphological_operations/erosion_operation.py index c2cde2ea6990..c0e1ef847237 100644 --- a/digital_image_processing/morphological_operations/erosion_operation.py +++ b/digital_image_processing/morphological_operations/erosion_operation.py @@ -21,6 +21,7 @@ def rgb2gray(rgb: np.array) -> np.array: def gray2binary(gray: np.array) -> np.array: """ Return binary image from gray image + >>> gray2binary(np.array([[127, 255, 0]])) array([[False, True, False]]) >>> gray2binary(np.array([[0]])) diff --git a/digital_image_processing/rotation/rotation.py b/digital_image_processing/rotation/rotation.py index 958d16fafb91..0f5e36ddd5be 100644 --- a/digital_image_processing/rotation/rotation.py +++ b/digital_image_processing/rotation/rotation.py @@ -10,12 +10,12 @@ def get_rotation( ) -> np.ndarray: """ Get image rotation - :param img: np.array + :param img: np.ndarray :param pt1: 3x2 list :param pt2: 3x2 list :param rows: columns image shape :param cols: rows image shape - :return: np.array + :return: np.ndarray """ matrix = cv2.getAffineTransform(pt1, pt2) return cv2.warpAffine(img, matrix, (rows, cols)) diff --git a/maths/average_median.py b/maths/average_median.py index cd1ec1574893..f24e525736b3 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -19,7 +19,9 @@ def median(nums: list) -> int | float: Returns: Median. """ - sorted_list = sorted(nums) + # The sorted function returns list[SupportsRichComparisonT@sorted] + # which does not support `+` + sorted_list: list[int] = sorted(nums) length = len(sorted_list) mid_index = length >> 1 return ( diff --git a/maths/euler_modified.py b/maths/euler_modified.py index 14bddadf4c53..d02123e1e2fb 100644 --- a/maths/euler_modified.py +++ b/maths/euler_modified.py @@ -5,7 +5,7 @@ def euler_modified( ode_func: Callable, y0: float, x0: float, step_size: float, x_end: float -) -> np.array: +) -> np.ndarray: """ Calculate solution at each step to an ODE using Euler's Modified Method The Euler Method is straightforward to implement, but can't give accurate solutions. diff --git a/maths/gaussian_error_linear_unit.py b/maths/gaussian_error_linear_unit.py index 7b5f875143b9..18384bb6c864 100644 --- a/maths/gaussian_error_linear_unit.py +++ b/maths/gaussian_error_linear_unit.py @@ -13,7 +13,7 @@ import numpy as np -def sigmoid(vector: np.array) -> np.array: +def sigmoid(vector: np.ndarray) -> np.ndarray: """ Mathematical function sigmoid takes a vector x of K real numbers as input and returns 1/ (1 + e^-x). @@ -25,7 +25,7 @@ def sigmoid(vector: np.array) -> np.array: return 1 / (1 + np.exp(-vector)) -def gaussian_error_linear_unit(vector: np.array) -> np.array: +def gaussian_error_linear_unit(vector: np.ndarray) -> np.ndarray: """ Implements the Gaussian Error Linear Unit (GELU) function diff --git a/maths/jaccard_similarity.py b/maths/jaccard_similarity.py index 32054414c0c2..6b6243458fa8 100644 --- a/maths/jaccard_similarity.py +++ b/maths/jaccard_similarity.py @@ -14,7 +14,11 @@ """ -def jaccard_similarity(set_a, set_b, alternative_union=False): +def jaccard_similarity( + set_a: set[str] | list[str] | tuple[str], + set_b: set[str] | list[str] | tuple[str], + alternative_union=False, +): """ Finds the jaccard similarity between two sets. Essentially, its intersection over union. @@ -37,41 +41,52 @@ def jaccard_similarity(set_a, set_b, alternative_union=False): >>> set_b = {'c', 'd', 'e', 'f', 'h', 'i'} >>> jaccard_similarity(set_a, set_b) 0.375 - >>> jaccard_similarity(set_a, set_a) 1.0 - >>> jaccard_similarity(set_a, set_a, True) 0.5 - >>> set_a = ['a', 'b', 'c', 'd', 'e'] >>> set_b = ('c', 'd', 'e', 'f', 'h', 'i') >>> jaccard_similarity(set_a, set_b) 0.375 + >>> set_a = ('c', 'd', 'e', 'f', 'h', 'i') + >>> set_b = ['a', 'b', 'c', 'd', 'e'] + >>> jaccard_similarity(set_a, set_b) + 0.375 + >>> set_a = ('c', 'd', 'e', 'f', 'h', 'i') + >>> set_b = ['a', 'b', 'c', 'd'] + >>> jaccard_similarity(set_a, set_b, True) + 0.2 + >>> set_a = {'a', 'b'} + >>> set_b = ['c', 'd'] + >>> jaccard_similarity(set_a, set_b) + Traceback (most recent call last): + ... + ValueError: Set a and b must either both be sets or be either a list or a tuple. """ if isinstance(set_a, set) and isinstance(set_b, set): - intersection = len(set_a.intersection(set_b)) + intersection_length = len(set_a.intersection(set_b)) if alternative_union: - union = len(set_a) + len(set_b) + union_length = len(set_a) + len(set_b) else: - union = len(set_a.union(set_b)) + union_length = len(set_a.union(set_b)) - return intersection / union + return intersection_length / union_length - if isinstance(set_a, (list, tuple)) and isinstance(set_b, (list, tuple)): + elif isinstance(set_a, (list, tuple)) and isinstance(set_b, (list, tuple)): intersection = [element for element in set_a if element in set_b] if alternative_union: - union = len(set_a) + len(set_b) - return len(intersection) / union + return len(intersection) / (len(set_a) + len(set_b)) else: - union = set_a + [element for element in set_b if element not in set_a] + # Cast set_a to list because tuples cannot be mutated + union = list(set_a) + [element for element in set_b if element not in set_a] return len(intersection) / len(union) - - return len(intersection) / len(union) - return None + raise ValueError( + "Set a and b must either both be sets or be either a list or a tuple." + ) if __name__ == "__main__": diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index 2c9cd1de95b0..f6b227b5c9c1 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -1,16 +1,20 @@ """ - 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 - x[n+1] = x[n] + f(x[n])/f'(x[n]) - 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. - """ +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 + x[n+1] = x[n] + f(x[n])/f'(x[n]) +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 +from collections.abc import Callable + +DerivativeFunc = Callable[[float], float] -def calc_derivative(f, a, h=0.001): +def calc_derivative(f: DerivativeFunc, a: float, h: float = 0.001) -> float: """ Calculates derivative at point a for function f using finite difference method @@ -18,7 +22,14 @@ def calc_derivative(f, a, h=0.001): 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: DerivativeFunc, + x0: float = 0, + maxiter: int = 100, + step: float = 0.0001, + maxerror: float = 1e-6, + logsteps: bool = False, +) -> tuple[float, float, list[float]]: a = x0 # set the initial guess steps = [a] error = abs(f(a)) @@ -36,7 +47,7 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=Fa if logsteps: # If logstep is true, then log intermediate steps return a, error, steps - return a, error + return a, error, [] if __name__ == "__main__": diff --git a/maths/qr_decomposition.py b/maths/qr_decomposition.py index a8414fbece87..670b49206aa7 100644 --- a/maths/qr_decomposition.py +++ b/maths/qr_decomposition.py @@ -1,7 +1,7 @@ import numpy as np -def qr_householder(a): +def qr_householder(a: np.ndarray): """Return a QR-decomposition of the matrix A using Householder reflection. The QR-decomposition decomposes the matrix A of shape (m, n) into an diff --git a/maths/sigmoid.py b/maths/sigmoid.py index 147588e8871f..cb45bde2702c 100644 --- a/maths/sigmoid.py +++ b/maths/sigmoid.py @@ -11,7 +11,7 @@ import numpy as np -def sigmoid(vector: np.array) -> np.array: +def sigmoid(vector: np.ndarray) -> np.ndarray: """ Implements the sigmoid function diff --git a/maths/tanh.py b/maths/tanh.py index ddab3e1ab717..38a369d9118d 100644 --- a/maths/tanh.py +++ b/maths/tanh.py @@ -12,12 +12,12 @@ import numpy as np -def tangent_hyperbolic(vector: np.array) -> np.array: +def tangent_hyperbolic(vector: np.ndarray) -> np.ndarray: """ Implements the tanh function Parameters: - vector: np.array + vector: np.ndarray Returns: tanh (np.array): The input numpy array after applying tanh. From cecf1fdd529782d754e1aa4d6df099e391003c76 Mon Sep 17 00:00:00 2001 From: Juyoung Kim <61103343+JadeKim042386@users.noreply.github.com> Date: Wed, 16 Aug 2023 07:52:51 +0900 Subject: [PATCH 2270/2908] Fix greedy_best_first (#8775) * fix: typo #8770 * refactor: delete unnecessary continue * add test grids * fix: add \_\_eq\_\_ in Node class #8770 * fix: delete unnecessary code - node in self.open_nodes is always better node #8770 * fix: docstring * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: docstring max length * refactor: get the successors using a list comprehension * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- graphs/greedy_best_first.py | 120 ++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index 35f7ca9feeef..bb3160047e34 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -6,14 +6,32 @@ Path = list[tuple[int, int]] -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], +# 0's are free path whereas 1's are obstacles +TEST_GRIDS = [ + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], + [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], + ], + [ + [0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 0, 1, 0, 1], + [0, 0, 0, 1, 1, 0, 0], + [0, 1, 0, 0, 1, 0, 0], + [1, 0, 0, 1, 1, 0, 1], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 1, 0, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 1], + [1, 0, 0, 1, 1], + [0, 0, 0, 0, 0], + ], ] delta = ([-1, 0], [0, -1], [1, 0], [0, 1]) # up, left, down, right @@ -65,10 +83,14 @@ def calculate_heuristic(self) -> float: def __lt__(self, other) -> bool: return self.f_cost < other.f_cost + def __eq__(self, other) -> bool: + return self.pos == other.pos + class GreedyBestFirst: """ - >>> gbf = GreedyBestFirst((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> grid = TEST_GRIDS[2] + >>> gbf = GreedyBestFirst(grid, (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]) @@ -78,11 +100,14 @@ class GreedyBestFirst: >>> 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)] + [(0, 0), (1, 0), (2, 0), (2, 1), (3, 1), (4, 1), (4, 2), (4, 3), + (4, 4)] """ - def __init__(self, start: tuple[int, int], goal: tuple[int, int]): + def __init__( + self, grid: list[list[int]], start: tuple[int, int], goal: tuple[int, int] + ): + self.grid = grid 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) @@ -114,14 +139,6 @@ def search(self) -> Path | None: 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] @@ -131,28 +148,22 @@ 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 [ + Node( + pos_x, + pos_y, + self.target.pos_x, + self.target.pos_y, + parent.g_cost + 1, + parent, + ) + for action in delta + if ( + 0 <= (pos_x := parent.pos_x + action[1]) < len(self.grid[0]) + and 0 <= (pos_y := parent.pos_y + action[0]) < len(self.grid) + and self.grid[pos_y][pos_x] == 0 ) - return successors + ] def retrace_path(self, node: Node | None) -> Path: """ @@ -168,18 +179,21 @@ def retrace_path(self, node: Node | None) -> 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() - if path: - for pos_x, pos_y in path: - grid[pos_x][pos_y] = 2 + for idx, grid in enumerate(TEST_GRIDS): + print(f"==grid-{idx + 1}==") + init = (0, 0) + goal = (len(grid) - 1, len(grid[0]) - 1) for elem in grid: print(elem) + + print("------") + + greedy_bf = GreedyBestFirst(grid, init, goal) + path = greedy_bf.search() + if path: + for pos_x, pos_y in path: + grid[pos_x][pos_y] = 2 + + for elem in grid: + print(elem) From efaf526737a83815a609a00fd59370f25f6d2e09 Mon Sep 17 00:00:00 2001 From: isidroas Date: Wed, 16 Aug 2023 01:04:53 +0200 Subject: [PATCH 2271/2908] BST and RSA doctest (#8693) * rsa key doctest * move doctest to module docstring * all tests to doctest * moved is_right to property * is right test * fixed rsa doctest import * Test error when deleting non-existing element * fixing ruff EM102 * convert property 'is_right' to one-liner Also use 'is' instead of '==' Co-authored-by: Tianyi Zheng * child instead of children Co-authored-by: Tianyi Zheng * remove type hint * Update data_structures/binary_tree/binary_search_tree.py --------- Co-authored-by: Tianyi Zheng --- ciphers/rsa_key_generator.py | 25 +-- .../binary_tree/binary_search_tree.py | 155 ++++++++++-------- 2 files changed, 98 insertions(+), 82 deletions(-) diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 2573ed01387b..eedc7336804a 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -2,8 +2,7 @@ import random import sys -from . import cryptomath_module as cryptoMath # noqa: N812 -from . import rabin_miller as rabinMiller # noqa: N812 +from . import cryptomath_module, rabin_miller def main() -> None: @@ -13,20 +12,26 @@ def main() -> None: def generate_key(key_size: int) -> tuple[tuple[int, int], tuple[int, int]]: - print("Generating prime p...") - p = rabinMiller.generate_large_prime(key_size) - print("Generating prime q...") - q = rabinMiller.generate_large_prime(key_size) + """ + >>> random.seed(0) # for repeatability + >>> public_key, private_key = generate_key(8) + >>> public_key + (26569, 239) + >>> private_key + (26569, 2855) + """ + p = rabin_miller.generate_large_prime(key_size) + q = rabin_miller.generate_large_prime(key_size) n = p * q - print("Generating e that is relatively prime to (p - 1) * (q - 1)...") + # Generate e that is relatively prime to (p - 1) * (q - 1) while True: e = random.randrange(2 ** (key_size - 1), 2 ** (key_size)) - if cryptoMath.gcd(e, (p - 1) * (q - 1)) == 1: + if cryptomath_module.gcd(e, (p - 1) * (q - 1)) == 1: break - print("Calculating d that is mod inverse of e...") - d = cryptoMath.find_mod_inverse(e, (p - 1) * (q - 1)) + # Calculate d that is mod inverse of e + d = cryptomath_module.find_mod_inverse(e, (p - 1) * (q - 1)) public_key = (n, e) private_key = (n, d) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index c72195424c7c..a706d21e3bb2 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -1,5 +1,62 @@ -""" +r""" A binary search Tree + +Example + 8 + / \ + 3 10 + / \ \ + 1 6 14 + / \ / + 4 7 13 + +>>> t = BinarySearchTree() +>>> t.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 +>>> t.remove(20) +Traceback (most recent call last): + ... +ValueError: Value 20 not found +>>> BinarySearchTree().search(6) +Traceback (most recent call last): + ... +IndexError: Warning: Tree is empty! please use another. + +Other example: + +>>> testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7) +>>> t = BinarySearchTree() +>>> for i in testlist: +... t.insert(i) + +Prints all the elements of the list in order traversal +>>> print(t) +{'8': ({'3': (1, {'6': (4, 7)})}, {'10': (None, {'14': (13, None)})})} + +Test existence +>>> t.search(6) is not None +True +>>> t.search(-1) is not None +False + +>>> t.search(6).is_right +True +>>> t.search(1).is_right +False + +>>> t.get_max().value +14 +>>> t.get_min().value +1 +>>> t.empty() +False +>>> for i in testlist: +... t.remove(i) +>>> t.empty() +True """ from collections.abc import Iterable @@ -20,6 +77,10 @@ def __repr__(self) -> str: return str(self.value) return pformat({f"{self.value}": (self.left, self.right)}, indent=1) + @property + def is_right(self) -> bool: + return self.parent is not None and self is self.parent.right + class BinarySearchTree: def __init__(self, root: Node | None = None): @@ -35,18 +96,13 @@ def __reassign_nodes(self, node: Node, new_children: Node | None) -> None: 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 + if node.is_right: # If it is the right child node.parent.right = new_children else: node.parent.left = new_children else: self.root = new_children - def is_right(self, node: Node) -> bool: - if node.parent and node.parent.right: - return node == node.parent.right - return False - def empty(self) -> bool: return self.root is None @@ -119,22 +175,26 @@ def get_min(self, node: Node | None = None) -> Node | None: return node def remove(self, value: int) -> None: - 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) - 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: - tmp_node = self.get_max( - node.left - ) # Gets the max value of the left branch - self.remove(tmp_node.value) # type: ignore - node.value = ( - tmp_node.value # type: ignore - ) # Assigns the value to the node to delete and keep tree structure + # Look for the node with that label + node = self.search(value) + if node is None: + msg = f"Value {value} not found" + raise ValueError(msg) + + if node.left is None and node.right is None: # If it has no children + self.__reassign_nodes(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: + predecessor = self.get_max( + node.left + ) # Gets the max value of the left branch + self.remove(predecessor.value) # type: ignore + node.value = ( + predecessor.value # type: ignore + ) # Assigns the value to the node to delete and keep tree structure def preorder_traverse(self, node: Node | None) -> Iterable: if node is not None: @@ -177,55 +237,6 @@ def postorder(curr_node: Node | None) -> list[Node]: return node_list -def binary_search_tree() -> None: - r""" - Example - 8 - / \ - 3 10 - / \ \ - 1 6 14 - / \ / - 4 7 13 - - >>> t = BinarySearchTree() - >>> t.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() - for i in testlist: - t.insert(i) - - # Prints all the elements of the list in order traversal - print(t) - - 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: - print("The value -1 exists") - else: - print("The value -1 doesn't exist") - - if not t.empty(): - print("Max Value: ", t.get_max().value) # type: ignore - print("Min Value: ", t.get_min().value) # type: ignore - - for i in testlist: - t.remove(i) - print(t) - - if __name__ == "__main__": import doctest From f66568e981edf5e384fe28a357daee3e13f16de9 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 16 Aug 2023 02:10:22 +0300 Subject: [PATCH 2272/2908] Reduce the complexity of boolean_algebra/quine_mc_cluskey.py (#8604) * Reduce the complexity of boolean_algebra/quine_mc_cluskey.py * updating DIRECTORY.md * Fix * Fix review issues * Fix * Fix review issues --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- boolean_algebra/quine_mc_cluskey.py | 49 ++++++++++++----------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 6788dfb28ba1..8e22e66726d4 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -74,10 +74,7 @@ def is_for_table(string1: str, string2: str, count: int) -> bool: """ list1 = list(string1) list2 = list(string2) - count_n = 0 - for i in range(len(list1)): - if list1[i] != list2[i]: - count_n += 1 + count_n = sum(item1 != item2 for item1, item2 in zip(list1, list2)) return count_n == count @@ -92,40 +89,34 @@ def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]: 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 + count = sum(row[i] == 1 for row in chart) if count == 1: + rem = max(j for j, row in enumerate(chart) if row[i] == 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]) + for i, item in enumerate(select): + if item != 1: + continue + for j in range(len(chart[0])): + if chart[i][j] != 1: + continue + for row in chart: + row[j] = 0 + temp.append(prime_implicants[i]) while True: - 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 + counts = [chart[i].count(1) for i in range(len(chart))] + max_n = max(counts) + rem = counts.index(max_n) 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 + for j in range(len(chart[0])): + if chart[rem][j] != 1: + continue + for i in range(len(chart)): + chart[i][j] = 0 def prime_implicant_chart( From bfed2fb7883fb7c472cd09afea1aad4e3f87d71b Mon Sep 17 00:00:00 2001 From: Saksham1970 <45041294+Saksham1970@users.noreply.github.com> Date: Wed, 16 Aug 2023 12:54:12 +0530 Subject: [PATCH 2273/2908] Added Continued fractions (#6846) * updating DIRECTORY.md * added continued fractions * updating DIRECTORY.md * Update maths/continued_fraction.py Co-authored-by: Caeden Perelli-Harris * Update maths/continued_fraction.py Co-authored-by: Caeden Perelli-Harris --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Caeden Perelli-Harris Co-authored-by: Tianyi Zheng --- DIRECTORY.md | 1 + maths/continued_fraction.py | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 maths/continued_fraction.py diff --git a/DIRECTORY.md b/DIRECTORY.md index be5fa3584a58..8d1567465fbc 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -555,6 +555,7 @@ * [Chudnovsky Algorithm](maths/chudnovsky_algorithm.py) * [Collatz Sequence](maths/collatz_sequence.py) * [Combinations](maths/combinations.py) + * [Continued Fraction](maths/continued_fraction.py) * [Decimal Isolate](maths/decimal_isolate.py) * [Decimal To Fraction](maths/decimal_to_fraction.py) * [Dodecahedron](maths/dodecahedron.py) diff --git a/maths/continued_fraction.py b/maths/continued_fraction.py new file mode 100644 index 000000000000..25ff649db77a --- /dev/null +++ b/maths/continued_fraction.py @@ -0,0 +1,51 @@ +""" +Finding the continuous fraction for a rational number using python + +https://en.wikipedia.org/wiki/Continued_fraction +""" + + +from fractions import Fraction + + +def continued_fraction(num: Fraction) -> list[int]: + """ + :param num: + Fraction of the number whose continued fractions to be found. + Use Fraction(str(number)) for more accurate results due to + float inaccuracies. + + :return: + The continued fraction of rational number. + It is the all commas in the (n + 1)-tuple notation. + + >>> continued_fraction(Fraction(2)) + [2] + >>> continued_fraction(Fraction("3.245")) + [3, 4, 12, 4] + >>> continued_fraction(Fraction("2.25")) + [2, 4] + >>> continued_fraction(1/Fraction("2.25")) + [0, 2, 4] + >>> continued_fraction(Fraction("415/93")) + [4, 2, 6, 7] + """ + numerator, denominator = num.as_integer_ratio() + continued_fraction_list: list[int] = [] + while True: + integer_part = int(numerator / denominator) + continued_fraction_list.append(integer_part) + numerator -= integer_part * denominator + if numerator == 0: + break + numerator, denominator = denominator, numerator + + return continued_fraction_list + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print("Continued Fraction of 0.84375 is: ", continued_fraction(Fraction("0.84375"))) From 5c276a8377b9f4139dac9cfff83fd47b88511a40 Mon Sep 17 00:00:00 2001 From: homsim <103424895+homsim@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:07:50 +0200 Subject: [PATCH 2274/2908] Quick fix: fig.canvas.set_window_title deprecated (#8961) Co-authored-by: homsim --- physics/n_body_simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index 2b701283f166..46330844df61 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -226,7 +226,7 @@ def plot( No doctest provided since this function does not have a return value. """ fig = plt.figure() - fig.canvas.set_window_title(title) + fig.canvas.manager.set_window_title(title) ax = plt.axes( xlim=(x_start, x_end), ylim=(y_start, y_end) ) # Set section to be plotted From beb43517c3552b72b9c8fc1710f681b0180418ec Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 16 Aug 2023 04:36:10 -0700 Subject: [PATCH 2275/2908] Fix `mypy` errors in `maths/gaussian_error_linear_unit.py` (#8610) * updating DIRECTORY.md * Fix mypy errors in gaussian_error_linear_unit.py * updating DIRECTORY.md * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- maths/gaussian_error_linear_unit.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/maths/gaussian_error_linear_unit.py b/maths/gaussian_error_linear_unit.py index 18384bb6c864..b3cbd7810716 100644 --- a/maths/gaussian_error_linear_unit.py +++ b/maths/gaussian_error_linear_unit.py @@ -30,12 +30,10 @@ def gaussian_error_linear_unit(vector: np.ndarray) -> np.ndarray: Implements the Gaussian Error Linear Unit (GELU) function Parameters: - vector (np.array): A numpy array of shape (1,n) - consisting of real values + vector (np.ndarray): A numpy array of shape (1, n) consisting of real values Returns: - gelu_vec (np.array): The input numpy array, after applying - gelu. + gelu_vec (np.ndarray): The input numpy array, after applying gelu Examples: >>> gaussian_error_linear_unit(np.array([-1.0, 1.0, 2.0])) From fd7cc4cf8e731c16a5dd2cf30c4ddb0dd017d59e Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Thu, 17 Aug 2023 02:21:00 +0100 Subject: [PATCH 2276/2908] Rename norgate to nor_gate to keep consistency (#8968) * refactor(boolean-algebra): Rename norgate to nor_gate * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- boolean_algebra/{norgate.py => nor_gate.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename boolean_algebra/{norgate.py => nor_gate.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 8d1567465fbc..d4a2bb48511a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -62,7 +62,7 @@ ## Boolean Algebra * [And Gate](boolean_algebra/and_gate.py) * [Nand Gate](boolean_algebra/nand_gate.py) - * [Norgate](boolean_algebra/norgate.py) + * [Nor Gate](boolean_algebra/nor_gate.py) * [Not Gate](boolean_algebra/not_gate.py) * [Or Gate](boolean_algebra/or_gate.py) * [Quine Mc Cluskey](boolean_algebra/quine_mc_cluskey.py) diff --git a/boolean_algebra/norgate.py b/boolean_algebra/nor_gate.py similarity index 100% rename from boolean_algebra/norgate.py rename to boolean_algebra/nor_gate.py From f6b12420ce2a16ddf55c5226ea6f188936af33ad Mon Sep 17 00:00:00 2001 From: Kausthub Kannan <99611070+kausthub-kannan@users.noreply.github.com> Date: Thu, 17 Aug 2023 06:52:15 +0530 Subject: [PATCH 2277/2908] Added Leaky ReLU Activation Function (#8962) * Added Leaky ReLU activation function * Added Leaky ReLU activation function * Added Leaky ReLU activation function * Formatting and spelling fixes done --- .../leaky_rectified_linear_unit.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 neural_network/activation_functions/leaky_rectified_linear_unit.py diff --git a/neural_network/activation_functions/leaky_rectified_linear_unit.py b/neural_network/activation_functions/leaky_rectified_linear_unit.py new file mode 100644 index 000000000000..019086fd9821 --- /dev/null +++ b/neural_network/activation_functions/leaky_rectified_linear_unit.py @@ -0,0 +1,39 @@ +""" +Leaky Rectified Linear Unit (Leaky ReLU) + +Use Case: Leaky ReLU addresses the problem of the vanishing gradient. +For more detailed information, you can refer to the following link: +https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Leaky_ReLU +""" + +import numpy as np + + +def leaky_rectified_linear_unit(vector: np.ndarray, alpha: float) -> np.ndarray: + """ + Implements the LeakyReLU activation function. + + Parameters: + vector (np.ndarray): The input array for LeakyReLU activation. + alpha (float): The slope for negative values. + + Returns: + np.ndarray: The input array after applying the LeakyReLU activation. + + Formula: f(x) = x if x > 0 else f(x) = alpha * x + + Examples: + >>> leaky_rectified_linear_unit(vector=np.array([2.3,0.6,-2,-3.8]), alpha=0.3) + array([ 2.3 , 0.6 , -0.6 , -1.14]) + + >>> leaky_rectified_linear_unit(np.array([-9.2, -0.3, 0.45, -4.56]), alpha=0.067) + array([-0.6164 , -0.0201 , 0.45 , -0.30552]) + + """ + return np.where(vector > 0, vector, alpha * vector) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a207187ddb368edb121153d4f6e190fcfb857427 Mon Sep 17 00:00:00 2001 From: Ilkin Mengusoglu <113149540+imengus@users.noreply.github.com> Date: Thu, 17 Aug 2023 22:34:53 +0100 Subject: [PATCH 2278/2908] Fix simplex.py (#8843) * changes to accommodate special case * changed n_slack calculation method * fix precommit typehints * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * n_art_vars inputs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: docstrings and typehints * fix: doctest issues when running code * additional check and doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix ruff * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix whitespace --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- linear_programming/simplex.py | 229 +++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 101 deletions(-) diff --git a/linear_programming/simplex.py b/linear_programming/simplex.py index ba64add40b5f..bbc97d8e22bf 100644 --- a/linear_programming/simplex.py +++ b/linear_programming/simplex.py @@ -20,40 +20,60 @@ class Tableau: """Operate on simplex tableaus - >>> t = Tableau(np.array([[-1,-1,0,0,-1],[1,3,1,0,4],[3,1,0,1,4.]]), 2) + >>> Tableau(np.array([[-1,-1,0,0,1],[1,3,1,0,4],[3,1,0,1,4]]), 2, 2) + Traceback (most recent call last): + ... + TypeError: Tableau must have type float64 + + >>> Tableau(np.array([[-1,-1,0,0,-1],[1,3,1,0,4],[3,1,0,1,4.]]), 2, 2) Traceback (most recent call last): ... ValueError: RHS must be > 0 + + >>> Tableau(np.array([[-1,-1,0,0,1],[1,3,1,0,4],[3,1,0,1,4.]]), -2, 2) + Traceback (most recent call last): + ... + ValueError: number of (artificial) variables must be a natural number """ - def __init__(self, tableau: np.ndarray, n_vars: int) -> None: + # Max iteration number to prevent cycling + maxiter = 100 + + def __init__( + self, tableau: np.ndarray, n_vars: int, n_artificial_vars: int + ) -> None: + if tableau.dtype != "float64": + raise TypeError("Tableau must have type float64") + # Check if RHS is negative - if np.any(tableau[:, -1], where=tableau[:, -1] < 0): + if not (tableau[:, -1] >= 0).all(): raise ValueError("RHS must be > 0") + if n_vars < 2 or n_artificial_vars < 0: + raise ValueError( + "number of (artificial) variables must be a natural number" + ) + self.tableau = tableau - self.n_rows, _ = tableau.shape + self.n_rows, n_cols = tableau.shape # Number of decision variables x1, x2, x3... - self.n_vars = n_vars - - # Number of artificial variables to be minimised - self.n_art_vars = len(np.where(tableau[self.n_vars : -1] == -1)[0]) + self.n_vars, self.n_artificial_vars = n_vars, n_artificial_vars # 2 if there are >= or == constraints (nonstandard), 1 otherwise (std) - self.n_stages = (self.n_art_vars > 0) + 1 + self.n_stages = (self.n_artificial_vars > 0) + 1 # Number of slack variables added to make inequalities into equalities - self.n_slack = self.n_rows - self.n_stages + self.n_slack = n_cols - self.n_vars - self.n_artificial_vars - 1 # Objectives for each stage self.objectives = ["max"] # In two stage simplex, first minimise then maximise - if self.n_art_vars: + if self.n_artificial_vars: self.objectives.append("min") - self.col_titles = [""] + self.col_titles = self.generate_col_titles() # Index of current pivot row and column self.row_idx = None @@ -62,48 +82,39 @@ def __init__(self, tableau: np.ndarray, n_vars: int) -> None: # Does objective row only contain (non)-negative values? self.stop_iter = False - @staticmethod - def generate_col_titles(*args: int) -> list[str]: + def generate_col_titles(self) -> list[str]: """Generate column titles for tableau of specific dimensions - >>> Tableau.generate_col_titles(2, 3, 1) - ['x1', 'x2', 's1', 's2', 's3', 'a1', 'RHS'] - - >>> Tableau.generate_col_titles() - Traceback (most recent call last): - ... - ValueError: Must provide n_vars, n_slack, and n_art_vars - >>> Tableau.generate_col_titles(-2, 3, 1) - Traceback (most recent call last): - ... - ValueError: All arguments must be non-negative integers - """ - if len(args) != 3: - raise ValueError("Must provide n_vars, n_slack, and n_art_vars") + >>> Tableau(np.array([[-1,-1,0,0,1],[1,3,1,0,4],[3,1,0,1,4.]]), + ... 2, 0).generate_col_titles() + ['x1', 'x2', 's1', 's2', 'RHS'] - if not all(x >= 0 and isinstance(x, int) for x in args): - raise ValueError("All arguments must be non-negative integers") + >>> Tableau(np.array([[-1,-1,0,0,1],[1,3,1,0,4],[3,1,0,1,4.]]), + ... 2, 2).generate_col_titles() + ['x1', 'x2', 'RHS'] + """ + args = (self.n_vars, self.n_slack) - # decision | slack | artificial - string_starts = ["x", "s", "a"] + # decision | slack + string_starts = ["x", "s"] titles = [] - for i in range(3): + for i in range(2): for j in range(args[i]): titles.append(string_starts[i] + str(j + 1)) titles.append("RHS") return titles - def find_pivot(self, tableau: np.ndarray) -> tuple[Any, Any]: + def find_pivot(self) -> tuple[Any, Any]: """Finds the pivot row and column. - >>> t = Tableau(np.array([[-2,1,0,0,0], [3,1,1,0,6], [1,2,0,1,7.]]), 2) - >>> t.find_pivot(t.tableau) + >>> Tableau(np.array([[-2,1,0,0,0], [3,1,1,0,6], [1,2,0,1,7.]]), + ... 2, 0).find_pivot() (1, 0) """ objective = self.objectives[-1] # Find entries of highest magnitude in objective rows sign = (objective == "min") - (objective == "max") - col_idx = np.argmax(sign * tableau[0, : self.n_vars]) + col_idx = np.argmax(sign * self.tableau[0, :-1]) # Choice is only valid if below 0 for maximise, and above for minimise if sign * self.tableau[0, col_idx] <= 0: @@ -117,15 +128,15 @@ def find_pivot(self, tableau: np.ndarray) -> tuple[Any, Any]: s = slice(self.n_stages, self.n_rows) # RHS - dividend = tableau[s, -1] + dividend = self.tableau[s, -1] # Elements of pivot column within slice - divisor = tableau[s, col_idx] + divisor = self.tableau[s, col_idx] # Array filled with nans nans = np.full(self.n_rows - self.n_stages, np.nan) - # If element in pivot column is greater than zeron_stages, return + # If element in pivot column is greater than zero, return # quotient or nan otherwise quotients = np.divide(dividend, divisor, out=nans, where=divisor > 0) @@ -134,18 +145,18 @@ def find_pivot(self, tableau: np.ndarray) -> tuple[Any, Any]: row_idx = np.nanargmin(quotients) + self.n_stages return row_idx, col_idx - def pivot(self, tableau: np.ndarray, row_idx: int, col_idx: int) -> np.ndarray: + def pivot(self, row_idx: int, col_idx: int) -> np.ndarray: """Pivots on value on the intersection of pivot row and column. - >>> t = Tableau(np.array([[-2,-3,0,0,0],[1,3,1,0,4],[3,1,0,1,4.]]), 2) - >>> t.pivot(t.tableau, 1, 0).tolist() + >>> Tableau(np.array([[-2,-3,0,0,0],[1,3,1,0,4],[3,1,0,1,4.]]), + ... 2, 2).pivot(1, 0).tolist() ... # doctest: +NORMALIZE_WHITESPACE [[0.0, 3.0, 2.0, 0.0, 8.0], [1.0, 3.0, 1.0, 0.0, 4.0], [0.0, -8.0, -3.0, 1.0, -8.0]] """ # Avoid changes to original tableau - piv_row = tableau[row_idx].copy() + piv_row = self.tableau[row_idx].copy() piv_val = piv_row[col_idx] @@ -153,48 +164,47 @@ def pivot(self, tableau: np.ndarray, row_idx: int, col_idx: int) -> np.ndarray: piv_row *= 1 / piv_val # Variable in pivot column becomes basic, ie the only non-zero entry - for idx, coeff in enumerate(tableau[:, col_idx]): - tableau[idx] += -coeff * piv_row - tableau[row_idx] = piv_row - return tableau + for idx, coeff in enumerate(self.tableau[:, col_idx]): + self.tableau[idx] += -coeff * piv_row + self.tableau[row_idx] = piv_row + return self.tableau - def change_stage(self, tableau: np.ndarray) -> np.ndarray: + def change_stage(self) -> np.ndarray: """Exits first phase of the two-stage method by deleting artificial rows and columns, or completes the algorithm if exiting the standard case. - >>> t = Tableau(np.array([ + >>> Tableau(np.array([ ... [3, 3, -1, -1, 0, 0, 4], ... [2, 1, 0, 0, 0, 0, 0.], ... [1, 2, -1, 0, 1, 0, 2], ... [2, 1, 0, -1, 0, 1, 2] - ... ]), 2) - >>> t.change_stage(t.tableau).tolist() + ... ]), 2, 2).change_stage().tolist() ... # doctest: +NORMALIZE_WHITESPACE - [[2.0, 1.0, 0.0, 0.0, 0.0, 0.0], - [1.0, 2.0, -1.0, 0.0, 1.0, 2.0], - [2.0, 1.0, 0.0, -1.0, 0.0, 2.0]] + [[2.0, 1.0, 0.0, 0.0, 0.0], + [1.0, 2.0, -1.0, 0.0, 2.0], + [2.0, 1.0, 0.0, -1.0, 2.0]] """ # Objective of original objective row remains self.objectives.pop() if not self.objectives: - return tableau + return self.tableau # Slice containing ids for artificial columns - s = slice(-self.n_art_vars - 1, -1) + s = slice(-self.n_artificial_vars - 1, -1) # Delete the artificial variable columns - tableau = np.delete(tableau, s, axis=1) + self.tableau = np.delete(self.tableau, s, axis=1) # Delete the objective row of the first stage - tableau = np.delete(tableau, 0, axis=0) + self.tableau = np.delete(self.tableau, 0, axis=0) self.n_stages = 1 self.n_rows -= 1 - self.n_art_vars = 0 + self.n_artificial_vars = 0 self.stop_iter = False - return tableau + return self.tableau def run_simplex(self) -> dict[Any, Any]: """Operate on tableau until objective function cannot be @@ -205,15 +215,29 @@ def run_simplex(self) -> dict[Any, Any]: ST: x1 + 3x2 <= 4 3x1 + x2 <= 4 >>> Tableau(np.array([[-1,-1,0,0,0],[1,3,1,0,4],[3,1,0,1,4.]]), - ... 2).run_simplex() + ... 2, 0).run_simplex() {'P': 2.0, 'x1': 1.0, 'x2': 1.0} + # Standard linear program with 3 variables: + Max: 3x1 + x2 + 3x3 + ST: 2x1 + x2 + x3 ≤ 2 + x1 + 2x2 + 3x3 ≤ 5 + 2x1 + 2x2 + x3 ≤ 6 + >>> Tableau(np.array([ + ... [-3,-1,-3,0,0,0,0], + ... [2,1,1,1,0,0,2], + ... [1,2,3,0,1,0,5], + ... [2,2,1,0,0,1,6.] + ... ]),3,0).run_simplex() # doctest: +ELLIPSIS + {'P': 5.4, 'x1': 0.199..., 'x3': 1.6} + + # Optimal tableau input: >>> Tableau(np.array([ ... [0, 0, 0.25, 0.25, 2], ... [0, 1, 0.375, -0.125, 1], ... [1, 0, -0.125, 0.375, 1] - ... ]), 2).run_simplex() + ... ]), 2, 0).run_simplex() {'P': 2.0, 'x1': 1.0, 'x2': 1.0} # Non-standard: >= constraints @@ -227,7 +251,7 @@ def run_simplex(self) -> dict[Any, Any]: ... [1, 1, 1, 1, 0, 0, 0, 0, 40], ... [2, 1, -1, 0, -1, 0, 1, 0, 10], ... [0, -1, 1, 0, 0, -1, 0, 1, 10.] - ... ]), 3).run_simplex() + ... ]), 3, 2).run_simplex() {'P': 70.0, 'x1': 10.0, 'x2': 10.0, 'x3': 20.0} # Non standard: minimisation and equalities @@ -235,73 +259,76 @@ def run_simplex(self) -> dict[Any, Any]: ST: 2x1 + x2 = 12 6x1 + 5x2 = 40 >>> Tableau(np.array([ - ... [8, 6, 0, -1, 0, -1, 0, 0, 52], - ... [1, 1, 0, 0, 0, 0, 0, 0, 0], - ... [2, 1, 1, 0, 0, 0, 0, 0, 12], - ... [2, 1, 0, -1, 0, 0, 1, 0, 12], - ... [6, 5, 0, 0, 1, 0, 0, 0, 40], - ... [6, 5, 0, 0, 0, -1, 0, 1, 40.] - ... ]), 2).run_simplex() + ... [8, 6, 0, 0, 52], + ... [1, 1, 0, 0, 0], + ... [2, 1, 1, 0, 12], + ... [6, 5, 0, 1, 40.], + ... ]), 2, 2).run_simplex() {'P': 7.0, 'x1': 5.0, 'x2': 2.0} + + + # Pivot on slack variables + Max: 8x1 + 6x2 + ST: x1 + 3x2 <= 33 + 4x1 + 2x2 <= 48 + 2x1 + 4x2 <= 48 + x1 + x2 >= 10 + x1 >= 2 + >>> Tableau(np.array([ + ... [2, 1, 0, 0, 0, -1, -1, 0, 0, 12.0], + ... [-8, -6, 0, 0, 0, 0, 0, 0, 0, 0.0], + ... [1, 3, 1, 0, 0, 0, 0, 0, 0, 33.0], + ... [4, 2, 0, 1, 0, 0, 0, 0, 0, 60.0], + ... [2, 4, 0, 0, 1, 0, 0, 0, 0, 48.0], + ... [1, 1, 0, 0, 0, -1, 0, 1, 0, 10.0], + ... [1, 0, 0, 0, 0, 0, -1, 0, 1, 2.0] + ... ]), 2, 2).run_simplex() # doctest: +ELLIPSIS + {'P': 132.0, 'x1': 12.000... 'x2': 5.999...} """ # Stop simplex algorithm from cycling. - for _ in range(100): + for _ in range(Tableau.maxiter): # Completion of each stage removes an objective. If both stages # are complete, then no objectives are left if not self.objectives: - self.col_titles = self.generate_col_titles( - self.n_vars, self.n_slack, self.n_art_vars - ) - # Find the values of each variable at optimal solution - return self.interpret_tableau(self.tableau, self.col_titles) + return self.interpret_tableau() - row_idx, col_idx = self.find_pivot(self.tableau) + row_idx, col_idx = self.find_pivot() # If there are no more negative values in objective row if self.stop_iter: # Delete artificial variable columns and rows. Update attributes - self.tableau = self.change_stage(self.tableau) + self.tableau = self.change_stage() else: - self.tableau = self.pivot(self.tableau, row_idx, col_idx) + self.tableau = self.pivot(row_idx, col_idx) return {} - def interpret_tableau( - self, tableau: np.ndarray, col_titles: list[str] - ) -> dict[str, float]: + def interpret_tableau(self) -> dict[str, float]: """Given the final tableau, add the corresponding values of the basic decision variables to the `output_dict` - >>> tableau = np.array([ + >>> Tableau(np.array([ ... [0,0,0.875,0.375,5], ... [0,1,0.375,-0.125,1], ... [1,0,-0.125,0.375,1] - ... ]) - >>> t = Tableau(tableau, 2) - >>> t.interpret_tableau(tableau, ["x1", "x2", "s1", "s2", "RHS"]) + ... ]),2, 0).interpret_tableau() {'P': 5.0, 'x1': 1.0, 'x2': 1.0} """ # P = RHS of final tableau - output_dict = {"P": abs(tableau[0, -1])} + output_dict = {"P": abs(self.tableau[0, -1])} for i in range(self.n_vars): - # Gives ids of nonzero entries in the ith column - nonzero = np.nonzero(tableau[:, i]) + # Gives indices of nonzero entries in the ith column + nonzero = np.nonzero(self.tableau[:, i]) n_nonzero = len(nonzero[0]) - # First entry in the nonzero ids + # First entry in the nonzero indices nonzero_rowidx = nonzero[0][0] - nonzero_val = tableau[nonzero_rowidx, i] + nonzero_val = self.tableau[nonzero_rowidx, i] # If there is only one nonzero value in column, which is one - if n_nonzero == nonzero_val == 1: - rhs_val = tableau[nonzero_rowidx, -1] - output_dict[col_titles[i]] = rhs_val - - # Check for basic variables - for title in col_titles: - # Don't add RHS or slack variables to output dict - if title[0] not in "R-s-a": - output_dict.setdefault(title, 0) + if n_nonzero == 1 and nonzero_val == 1: + rhs_val = self.tableau[nonzero_rowidx, -1] + output_dict[self.col_titles[i]] = rhs_val return output_dict From 72c7b05caa7e5b109b7b42c796a8af39f99a5100 Mon Sep 17 00:00:00 2001 From: Boris Galochkin Date: Fri, 18 Aug 2023 04:38:19 +0300 Subject: [PATCH 2279/2908] Fix `sorts/bucket_sort.py` implementation (#5786) * Fix sorts/bucket_sort.py * updating DIRECTORY.md * Remove unused var in bucket_sort.py * Fix list index in bucket_sort.py --------- Co-authored-by: Tianyi Zheng Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + sorts/bucket_sort.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index d4a2bb48511a..e39a0674743a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -710,6 +710,7 @@ * [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py) * Activation Functions * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) + * [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Perceptron](neural_network/perceptron.py) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 7bcbe61a4526..c016e9e26e73 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -30,7 +30,7 @@ from __future__ import annotations -def bucket_sort(my_list: list) -> list: +def bucket_sort(my_list: list, bucket_count: int = 10) -> list: """ >>> data = [-1, 2, -5, 0] >>> bucket_sort(data) == sorted(data) @@ -43,21 +43,27 @@ def bucket_sort(my_list: list) -> list: True >>> bucket_sort([]) == sorted([]) True + >>> data = [-1e10, 1e10] + >>> bucket_sort(data) == sorted(data) + True >>> import random >>> collection = random.sample(range(-50, 50), 50) >>> bucket_sort(collection) == sorted(collection) True """ - if len(my_list) == 0: + + if len(my_list) == 0 or bucket_count <= 0: return [] + min_value, max_value = min(my_list), max(my_list) - bucket_count = int(max_value - min_value) + 1 + bucket_size = (max_value - min_value) / bucket_count buckets: list[list] = [[] for _ in range(bucket_count)] - for i in my_list: - buckets[int(i - min_value)].append(i) + for val in my_list: + index = min(int((val - min_value) / bucket_size), bucket_count - 1) + buckets[index].append(val) - return [v for bucket in buckets for v in sorted(bucket)] + return [val for bucket in buckets for val in sorted(bucket)] if __name__ == "__main__": From 5f7819e1cd192ecc89a7b7b929db63e045a47b45 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Fri, 18 Aug 2023 13:13:38 +0100 Subject: [PATCH 2280/2908] Fix get top billionaires BROKEN file (#8970) * updating DIRECTORY.md * fix(get-top-billionaires): Handle timestamp before epoch * updating DIRECTORY.md * revert(pyproject): Re-implement ignore lru_cache * fix(age): Update age to current year * fix(doctest): Make years since dynamic --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + ...es.py.disabled => get_top_billionaires.py} | 27 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) rename web_programming/{get_top_billionaires.py.disabled => get_top_billionaires.py} (72%) diff --git a/DIRECTORY.md b/DIRECTORY.md index e39a0674743a..1ff093d88766 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1221,6 +1221,7 @@ * [Get Amazon Product Data](web_programming/get_amazon_product_data.py) * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](web_programming/get_imdbtop.py) + * [Get Top Billionaires](web_programming/get_top_billionaires.py) * [Get Top Hn Posts](web_programming/get_top_hn_posts.py) * [Get User Tweets](web_programming/get_user_tweets.py) * [Giphy](web_programming/giphy.py) diff --git a/web_programming/get_top_billionaires.py.disabled b/web_programming/get_top_billionaires.py similarity index 72% rename from web_programming/get_top_billionaires.py.disabled rename to web_programming/get_top_billionaires.py index 6a8054e26270..6f986acb9181 100644 --- a/web_programming/get_top_billionaires.py.disabled +++ b/web_programming/get_top_billionaires.py @@ -3,7 +3,7 @@ This works for some of us but fails for others. """ -from datetime import datetime +from datetime import UTC, datetime, timedelta import requests from rich import box @@ -20,18 +20,31 @@ ) -def calculate_age(unix_date: int) -> str: +def calculate_age(unix_date: float) -> str: """Calculates age from given unix time format. Returns: Age as string - >>> calculate_age(-657244800000) - '73' - >>> calculate_age(46915200000) - '51' + >>> from datetime import datetime, UTC + >>> years_since_create = datetime.now(tz=UTC).year - 2022 + >>> int(calculate_age(-657244800000)) - years_since_create + 73 + >>> int(calculate_age(46915200000)) - years_since_create + 51 """ - birthdate = datetime.fromtimestamp(unix_date / 1000).date() + # Convert date from milliseconds to seconds + unix_date /= 1000 + + if unix_date < 0: + # Handle timestamp before epoch + epoch = datetime.fromtimestamp(0, tz=UTC) + seconds_since_epoch = (datetime.now(tz=UTC) - epoch).seconds + birthdate = ( + epoch - timedelta(seconds=abs(unix_date) - seconds_since_epoch) + ).date() + else: + birthdate = datetime.fromtimestamp(unix_date, tz=UTC).date() return str( TODAY.year - birthdate.year From 945803f65d79d0277c663a0e043228ed10996a92 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Fri, 18 Aug 2023 13:19:25 +0100 Subject: [PATCH 2281/2908] Unmark fetch anime and play as BROKEN and fix type errors (#8988) * updating DIRECTORY.md * type(fetch-anime-and-play): Fix type errors and re-enable * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + ...play.py.BROKEN => fetch_anime_and_play.py} | 71 ++++++++++--------- 2 files changed, 38 insertions(+), 34 deletions(-) rename web_programming/{fetch_anime_and_play.py.BROKEN => fetch_anime_and_play.py} (70%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 1ff093d88766..6af4ead56ebd 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1213,6 +1213,7 @@ * [Daily Horoscope](web_programming/daily_horoscope.py) * [Download Images From Google Query](web_programming/download_images_from_google_query.py) * [Emails From Url](web_programming/emails_from_url.py) + * [Fetch Anime And Play](web_programming/fetch_anime_and_play.py) * [Fetch Bbc News](web_programming/fetch_bbc_news.py) * [Fetch Github Info](web_programming/fetch_github_info.py) * [Fetch Jobs](web_programming/fetch_jobs.py) diff --git a/web_programming/fetch_anime_and_play.py.BROKEN b/web_programming/fetch_anime_and_play.py similarity index 70% rename from web_programming/fetch_anime_and_play.py.BROKEN rename to web_programming/fetch_anime_and_play.py index 3bd4f704dd8d..366807785e85 100644 --- a/web_programming/fetch_anime_and_play.py.BROKEN +++ b/web_programming/fetch_anime_and_play.py @@ -1,7 +1,5 @@ -from xml.dom import NotFoundErr - import requests -from bs4 import BeautifulSoup, NavigableString +from bs4 import BeautifulSoup, NavigableString, Tag from fake_useragent import UserAgent BASE_URL = "https://ww1.gogoanime2.org/" @@ -41,25 +39,23 @@ def search_scraper(anime_name: str) -> list: # get list of anime anime_ul = soup.find("ul", {"class": "items"}) + if anime_ul is None or isinstance(anime_ul, NavigableString): + msg = f"Could not find and anime with name {anime_name}" + raise ValueError(msg) anime_li = anime_ul.children # for each anime, insert to list. the name and url. anime_list = [] for anime in anime_li: - if not isinstance(anime, NavigableString): - try: - anime_url, anime_title = ( - anime.find("a")["href"], - anime.find("a")["title"], - ) - anime_list.append( - { - "title": anime_title, - "url": anime_url, - } - ) - except (NotFoundErr, KeyError): - pass + if isinstance(anime, Tag): + anime_url = anime.find("a") + if anime_url is None or isinstance(anime_url, NavigableString): + continue + anime_title = anime.find("a") + if anime_title is None or isinstance(anime_title, NavigableString): + continue + + anime_list.append({"title": anime_title["title"], "url": anime_url["href"]}) return anime_list @@ -93,22 +89,24 @@ def search_anime_episode_list(episode_endpoint: str) -> list: # With this id. get the episode list. episode_page_ul = soup.find("ul", {"id": "episode_related"}) + if episode_page_ul is None or isinstance(episode_page_ul, NavigableString): + msg = f"Could not find any anime eposiodes with name {anime_name}" + raise ValueError(msg) episode_page_li = episode_page_ul.children episode_list = [] for episode in episode_page_li: - try: - if not isinstance(episode, NavigableString): - episode_list.append( - { - "title": episode.find("div", {"class": "name"}).text.replace( - " ", "" - ), - "url": episode.find("a")["href"], - } - ) - except (KeyError, NotFoundErr): - pass + if isinstance(episode, Tag): + url = episode.find("a") + if url is None or isinstance(url, NavigableString): + continue + title = episode.find("div", {"class": "name"}) + if title is None or isinstance(title, NavigableString): + continue + + episode_list.append( + {"title": title.text.replace(" ", ""), "url": url["href"]} + ) return episode_list @@ -140,11 +138,16 @@ def get_anime_episode(episode_endpoint: str) -> list: soup = BeautifulSoup(response.text, "html.parser") - try: - episode_url = soup.find("iframe", {"id": "playerframe"})["src"] - download_url = episode_url.replace("/embed/", "/playlist/") + ".m3u8" - except (KeyError, NotFoundErr) as e: - raise e + url = soup.find("iframe", {"id": "playerframe"}) + if url is None or isinstance(url, NavigableString): + msg = f"Could not find url and download url from {episode_endpoint}" + raise RuntimeError(msg) + + episode_url = url["src"] + if not isinstance(episode_url, str): + msg = f"Could not find url and download url from {episode_endpoint}" + raise RuntimeError(msg) + download_url = episode_url.replace("/embed/", "/playlist/") + ".m3u8" return [f"{BASE_URL}{episode_url}", f"{BASE_URL}{download_url}"] From e887c14f1252cd7de3d99ef0553c448c8c9711df Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 18 Aug 2023 13:53:17 -0700 Subject: [PATCH 2282/2908] Fix continued_fraction.py to work for negative numbers (#8985) * Add doctests to continued_fraction.py for 0 and neg nums * Fix continued_fraction.py to work for negative nums Fix continued_fraction.py to work for negative nums by replacing int() call with floor() * Move comment in doctest --- maths/continued_fraction.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/maths/continued_fraction.py b/maths/continued_fraction.py index 25ff649db77a..04ff0b6ff0d2 100644 --- a/maths/continued_fraction.py +++ b/maths/continued_fraction.py @@ -6,6 +6,7 @@ from fractions import Fraction +from math import floor def continued_fraction(num: Fraction) -> list[int]: @@ -29,11 +30,17 @@ def continued_fraction(num: Fraction) -> list[int]: [0, 2, 4] >>> continued_fraction(Fraction("415/93")) [4, 2, 6, 7] + >>> continued_fraction(Fraction(0)) + [0] + >>> continued_fraction(Fraction(0.75)) + [0, 1, 3] + >>> continued_fraction(Fraction("-2.25")) # -2.25 = -3 + 0.75 + [-3, 1, 3] """ numerator, denominator = num.as_integer_ratio() continued_fraction_list: list[int] = [] while True: - integer_part = int(numerator / denominator) + integer_part = floor(numerator / denominator) continued_fraction_list.append(integer_part) numerator -= integer_part * denominator if numerator == 0: From 5ecb6baef8bf52f9bb99a1bb7cec4899b6df7ab4 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 20 Aug 2023 05:36:00 -0700 Subject: [PATCH 2283/2908] Move and reimplement `convert_number_to_words.py` (#8998) * Move and reimplement convert_number_to_words.py - Move convert_number_to_words.py from web_programming/ to conversions/ - Reimplement the algorithm from scratch because the logic was very opaque and too heavily nested - Add support for the Western numbering system (both short and long) because the original implementation only supported the Indian numbering system - Add extensive doctests and error handling * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- conversions/convert_number_to_words.py | 205 +++++++++++++++++++++ web_programming/convert_number_to_words.py | 109 ----------- 3 files changed, 206 insertions(+), 110 deletions(-) create mode 100644 conversions/convert_number_to_words.py delete mode 100644 web_programming/convert_number_to_words.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 6af4ead56ebd..653c1831d820 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -143,6 +143,7 @@ * [Binary To Decimal](conversions/binary_to_decimal.py) * [Binary To Hexadecimal](conversions/binary_to_hexadecimal.py) * [Binary To Octal](conversions/binary_to_octal.py) + * [Convert Number To Words](conversions/convert_number_to_words.py) * [Decimal To Any](conversions/decimal_to_any.py) * [Decimal To Binary](conversions/decimal_to_binary.py) * [Decimal To Binary Recursion](conversions/decimal_to_binary_recursion.py) @@ -1203,7 +1204,6 @@ ## Web Programming * [Co2 Emission](web_programming/co2_emission.py) - * [Convert Number To Words](web_programming/convert_number_to_words.py) * [Covid Stats Via Xpath](web_programming/covid_stats_via_xpath.py) * [Crawl Google Results](web_programming/crawl_google_results.py) * [Crawl Google Scholar Citation](web_programming/crawl_google_scholar_citation.py) diff --git a/conversions/convert_number_to_words.py b/conversions/convert_number_to_words.py new file mode 100644 index 000000000000..0e4405319f1f --- /dev/null +++ b/conversions/convert_number_to_words.py @@ -0,0 +1,205 @@ +from enum import Enum +from typing import ClassVar, Literal + + +class NumberingSystem(Enum): + SHORT = ( + (15, "quadrillion"), + (12, "trillion"), + (9, "billion"), + (6, "million"), + (3, "thousand"), + (2, "hundred"), + ) + + LONG = ( + (15, "billiard"), + (9, "milliard"), + (6, "million"), + (3, "thousand"), + (2, "hundred"), + ) + + INDIAN = ( + (14, "crore crore"), + (12, "lakh crore"), + (7, "crore"), + (5, "lakh"), + (3, "thousand"), + (2, "hundred"), + ) + + @classmethod + def max_value(cls, system: str) -> int: + """ + Gets the max value supported by the given number system. + + >>> NumberingSystem.max_value("short") == 10**18 - 1 + True + >>> NumberingSystem.max_value("long") == 10**21 - 1 + True + >>> NumberingSystem.max_value("indian") == 10**19 - 1 + True + """ + match (system_enum := cls[system.upper()]): + case cls.SHORT: + max_exp = system_enum.value[0][0] + 3 + case cls.LONG: + max_exp = system_enum.value[0][0] + 6 + case cls.INDIAN: + max_exp = 19 + case _: + raise ValueError("Invalid numbering system") + return 10**max_exp - 1 + + +class NumberWords(Enum): + ONES: ClassVar = { + 0: "", + 1: "one", + 2: "two", + 3: "three", + 4: "four", + 5: "five", + 6: "six", + 7: "seven", + 8: "eight", + 9: "nine", + } + + TEENS: ClassVar = { + 0: "ten", + 1: "eleven", + 2: "twelve", + 3: "thirteen", + 4: "fourteen", + 5: "fifteen", + 6: "sixteen", + 7: "seventeen", + 8: "eighteen", + 9: "nineteen", + } + + TENS: ClassVar = { + 2: "twenty", + 3: "thirty", + 4: "forty", + 5: "fifty", + 6: "sixty", + 7: "seventy", + 8: "eighty", + 9: "ninety", + } + + +def convert_small_number(num: int) -> str: + """ + Converts small, non-negative integers with irregular constructions in English (i.e., + numbers under 100) into words. + + >>> convert_small_number(0) + 'zero' + >>> convert_small_number(5) + 'five' + >>> convert_small_number(10) + 'ten' + >>> convert_small_number(15) + 'fifteen' + >>> convert_small_number(20) + 'twenty' + >>> convert_small_number(25) + 'twenty-five' + >>> convert_small_number(-1) + Traceback (most recent call last): + ... + ValueError: This function only accepts non-negative integers + >>> convert_small_number(123) + Traceback (most recent call last): + ... + ValueError: This function only converts numbers less than 100 + """ + if num < 0: + raise ValueError("This function only accepts non-negative integers") + if num >= 100: + raise ValueError("This function only converts numbers less than 100") + tens, ones = divmod(num, 10) + if tens == 0: + return NumberWords.ONES.value[ones] or "zero" + if tens == 1: + return NumberWords.TEENS.value[ones] + return ( + NumberWords.TENS.value[tens] + + ("-" if NumberWords.ONES.value[ones] else "") + + NumberWords.ONES.value[ones] + ) + + +def convert_number( + num: int, system: Literal["short", "long", "indian"] = "short" +) -> str: + """ + Converts an integer to English words. + + :param num: The integer to be converted + :param system: The numbering system (short, long, or Indian) + + >>> convert_number(0) + 'zero' + >>> convert_number(1) + 'one' + >>> convert_number(100) + 'one hundred' + >>> convert_number(-100) + 'negative one hundred' + >>> convert_number(123_456_789_012_345) # doctest: +NORMALIZE_WHITESPACE + 'one hundred twenty-three trillion four hundred fifty-six billion + seven hundred eighty-nine million twelve thousand three hundred forty-five' + >>> convert_number(123_456_789_012_345, "long") # doctest: +NORMALIZE_WHITESPACE + 'one hundred twenty-three thousand four hundred fifty-six milliard + seven hundred eighty-nine million twelve thousand three hundred forty-five' + >>> convert_number(12_34_56_78_90_12_345, "indian") # doctest: +NORMALIZE_WHITESPACE + 'one crore crore twenty-three lakh crore + forty-five thousand six hundred seventy-eight crore + ninety lakh twelve thousand three hundred forty-five' + >>> convert_number(10**18) + Traceback (most recent call last): + ... + ValueError: Input number is too large + >>> convert_number(10**21, "long") + Traceback (most recent call last): + ... + ValueError: Input number is too large + >>> convert_number(10**19, "indian") + Traceback (most recent call last): + ... + ValueError: Input number is too large + """ + word_groups = [] + + if num < 0: + word_groups.append("negative") + num *= -1 + + if num > NumberingSystem.max_value(system): + raise ValueError("Input number is too large") + + for power, unit in NumberingSystem[system.upper()].value: + digit_group, num = divmod(num, 10**power) + if digit_group > 0: + word_group = ( + convert_number(digit_group, system) + if digit_group >= 100 + else convert_small_number(digit_group) + ) + word_groups.append(f"{word_group} {unit}") + if num > 0 or not word_groups: # word_groups is only empty if input num was 0 + word_groups.append(convert_small_number(num)) + return " ".join(word_groups) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print(f"{convert_number(123456789) = }") diff --git a/web_programming/convert_number_to_words.py b/web_programming/convert_number_to_words.py deleted file mode 100644 index dac9e3e38e7c..000000000000 --- a/web_programming/convert_number_to_words.py +++ /dev/null @@ -1,109 +0,0 @@ -import math - - -def convert(number: int) -> str: - """ - Given a number return the number in words. - - >>> convert(123) - 'OneHundred,TwentyThree' - """ - if number == 0: - words = "Zero" - return words - else: - digits = math.log10(number) - digits = digits + 1 - singles = {} - singles[0] = "" - singles[1] = "One" - singles[2] = "Two" - singles[3] = "Three" - singles[4] = "Four" - singles[5] = "Five" - singles[6] = "Six" - singles[7] = "Seven" - singles[8] = "Eight" - singles[9] = "Nine" - - doubles = {} - doubles[0] = "" - doubles[2] = "Twenty" - doubles[3] = "Thirty" - doubles[4] = "Forty" - doubles[5] = "Fifty" - doubles[6] = "Sixty" - doubles[7] = "Seventy" - doubles[8] = "Eighty" - doubles[9] = "Ninety" - - teens = {} - teens[0] = "Ten" - teens[1] = "Eleven" - teens[2] = "Twelve" - teens[3] = "Thirteen" - teens[4] = "Fourteen" - teens[5] = "Fifteen" - teens[6] = "Sixteen" - teens[7] = "Seventeen" - teens[8] = "Eighteen" - teens[9] = "Nineteen" - - placevalue = {} - placevalue[2] = "Hundred," - placevalue[3] = "Thousand," - placevalue[5] = "Lakh," - placevalue[7] = "Crore," - - temp_num = number - words = "" - counter = 0 - digits = int(digits) - while counter < digits: - current = temp_num % 10 - if counter % 2 == 0: - addition = "" - if counter in placevalue and current != 0: - addition = placevalue[counter] - if counter == 2: - words = singles[current] + addition + words - elif counter == 0: - if ((temp_num % 100) // 10) == 1: - words = teens[current] + addition + words - temp_num = temp_num // 10 - counter += 1 - else: - words = singles[current] + addition + words - - else: - words = doubles[current] + addition + words - - else: - if counter == 1: - if current == 1: - words = teens[number % 10] + words - else: - addition = "" - if counter in placevalue: - addition = placevalue[counter] - words = doubles[current] + addition + words - else: - addition = "" - if counter in placevalue: - if current != 0 and ((temp_num % 100) // 10) != 0: - addition = placevalue[counter] - if ((temp_num % 100) // 10) == 1: - words = teens[current] + addition + words - temp_num = temp_num // 10 - counter += 1 - else: - words = singles[current] + addition + words - counter += 1 - temp_num = temp_num // 10 - return words - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From 062957ef27fcaaf59753e3739052928ec37f220e Mon Sep 17 00:00:00 2001 From: Bama Charan Chhandogi Date: Sun, 20 Aug 2023 18:10:23 +0530 Subject: [PATCH 2284/2908] Octal to Binary Convert (#8949) * Octal to Binary Convert * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * mention return type * code scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * mentioned return type * remove comment * added documention and some test cases * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add another test case * fixes documention * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Documention and test cases added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * documention problem solved * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * error in exit 1 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review --------- Co-authored-by: BamaCharanChhandogi Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- conversions/octal_to_binary.py | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 conversions/octal_to_binary.py diff --git a/conversions/octal_to_binary.py b/conversions/octal_to_binary.py new file mode 100644 index 000000000000..84e1e85f33ca --- /dev/null +++ b/conversions/octal_to_binary.py @@ -0,0 +1,54 @@ +""" +* Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) +* Description: Convert a Octal number to Binary. + +References for better understanding: +https://en.wikipedia.org/wiki/Binary_number +https://en.wikipedia.org/wiki/Octal +""" + + +def octal_to_binary(octal_number: str) -> str: + """ + Convert an Octal number to Binary. + + >>> octal_to_binary("17") + '001111' + >>> octal_to_binary("7") + '111' + >>> octal_to_binary("Av") + Traceback (most recent call last): + ... + ValueError: Non-octal value was passed to the function + >>> octal_to_binary("@#") + Traceback (most recent call last): + ... + ValueError: Non-octal value was passed to the function + >>> octal_to_binary("") + Traceback (most recent call last): + ... + ValueError: Empty string was passed to the function + """ + if not octal_number: + raise ValueError("Empty string was passed to the function") + + binary_number = "" + octal_digits = "01234567" + for digit in octal_number: + if digit not in octal_digits: + raise ValueError("Non-octal value was passed to the function") + + binary_digit = "" + value = int(digit) + for _ in range(3): + binary_digit = str(value % 2) + binary_digit + value //= 2 + binary_number += binary_digit + + return binary_number + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 672e7bde2e5fad38a3bc4038d11a9c343e3667f7 Mon Sep 17 00:00:00 2001 From: Guduly <133545858+Guduly@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:39:29 -0500 Subject: [PATCH 2285/2908] Update arc_length.py (#8964) * Update arc_length.py Wrote the output of testcase * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update arc_length.py Added the requested changes * Update arc_length.py followed the change request * Update arc_length.py followed suggestions --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/arc_length.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maths/arc_length.py b/maths/arc_length.py index 9e87ca38cc7d..4c518f321dc7 100644 --- a/maths/arc_length.py +++ b/maths/arc_length.py @@ -7,6 +7,8 @@ def arc_length(angle: int, radius: int) -> float: 3.9269908169872414 >>> arc_length(120, 15) 31.415926535897928 + >>> arc_length(90, 10) + 15.707963267948966 """ return 2 * pi * radius * (angle / 360) From 1984d9717158c89f9acca2b635a373bad7048633 Mon Sep 17 00:00:00 2001 From: Dom <97384583+tosemml@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:43:09 -0700 Subject: [PATCH 2286/2908] Refactorings (#8987) * use np.dot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * further improvements using array slicing Co-authored-by: Tianyi Zheng --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- arithmetic_analysis/gaussian_elimination.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/arithmetic_analysis/gaussian_elimination.py b/arithmetic_analysis/gaussian_elimination.py index f0f20af8e417..13f509a4f117 100644 --- a/arithmetic_analysis/gaussian_elimination.py +++ b/arithmetic_analysis/gaussian_elimination.py @@ -33,10 +33,7 @@ def retroactive_resolution( x: NDArray[float64] = np.zeros((rows, 1), dtype=float) for row in reversed(range(rows)): - total = 0 - for col in range(row + 1, columns): - total += coefficients[row, col] * x[col] - + total = np.dot(coefficients[row, row + 1 :], x[row + 1 :]) x[row, 0] = (vector[row] - total) / coefficients[row, row] return x From 1210559deb60b44cb9f57ce16c9bf6d79c0f443c Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Mon, 21 Aug 2023 14:25:20 +0100 Subject: [PATCH 2287/2908] Consolidate decimal to binary iterative and recursive (#8999) * updating DIRECTORY.md * refactor(decimal-to-binary): Consolidate implementations * updating DIRECTORY.md * refactor(decimal-to-binary): Rename main and helper recursive --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 - conversions/decimal_to_binary.py | 67 +++++++++++++++++++--- conversions/decimal_to_binary_recursion.py | 53 ----------------- 3 files changed, 59 insertions(+), 62 deletions(-) delete mode 100644 conversions/decimal_to_binary_recursion.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 653c1831d820..dd4404edd364 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -146,7 +146,6 @@ * [Convert Number To Words](conversions/convert_number_to_words.py) * [Decimal To Any](conversions/decimal_to_any.py) * [Decimal To Binary](conversions/decimal_to_binary.py) - * [Decimal To Binary Recursion](conversions/decimal_to_binary_recursion.py) * [Decimal To Hexadecimal](conversions/decimal_to_hexadecimal.py) * [Decimal To Octal](conversions/decimal_to_octal.py) * [Energy Conversions](conversions/energy_conversions.py) diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index 973c47c8af67..cf2b6040ec2a 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -1,27 +1,27 @@ """Convert a Decimal Number to a Binary Number.""" -def decimal_to_binary(num: int) -> str: +def decimal_to_binary_iterative(num: int) -> str: """ Convert an Integer Decimal Number to a Binary Number as str. - >>> decimal_to_binary(0) + >>> decimal_to_binary_iterative(0) '0b0' - >>> decimal_to_binary(2) + >>> decimal_to_binary_iterative(2) '0b10' - >>> decimal_to_binary(7) + >>> decimal_to_binary_iterative(7) '0b111' - >>> decimal_to_binary(35) + >>> decimal_to_binary_iterative(35) '0b100011' >>> # negatives work too - >>> decimal_to_binary(-2) + >>> decimal_to_binary_iterative(-2) '-0b10' >>> # other floats will error - >>> decimal_to_binary(16.16) # doctest: +ELLIPSIS + >>> decimal_to_binary_iterative(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 + >>> decimal_to_binary_iterative('0xfffff') # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: 'str' object cannot be interpreted as an integer @@ -52,7 +52,58 @@ def decimal_to_binary(num: int) -> str: return "0b" + "".join(str(e) for e in binary) +def decimal_to_binary_recursive_helper(decimal: int) -> str: + """ + Take a positive integer value and return its binary equivalent. + >>> decimal_to_binary_recursive_helper(1000) + '1111101000' + >>> decimal_to_binary_recursive_helper("72") + '1001000' + >>> decimal_to_binary_recursive_helper("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 decimal_to_binary_recursive_helper(div) + str(mod) + + +def decimal_to_binary_recursive(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. + >>> decimal_to_binary_recursive(0) + '0b0' + >>> decimal_to_binary_recursive(40) + '0b101000' + >>> decimal_to_binary_recursive(-40) + '-0b101000' + >>> decimal_to_binary_recursive(40.8) + Traceback (most recent call last): + ... + ValueError: Input value is not an integer + >>> decimal_to_binary_recursive("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{decimal_to_binary_recursive_helper(int(number))}" + + if __name__ == "__main__": import doctest doctest.testmod() + + print(decimal_to_binary_recursive(input("Input a decimal number: "))) diff --git a/conversions/decimal_to_binary_recursion.py b/conversions/decimal_to_binary_recursion.py deleted file mode 100644 index 05833ca670c3..000000000000 --- a/conversions/decimal_to_binary_recursion.py +++ /dev/null @@ -1,53 +0,0 @@ -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 b3dc6ef035f097c9eb91911d8970668049e47d62 Mon Sep 17 00:00:00 2001 From: AmirSoroush Date: Tue, 22 Aug 2023 02:17:02 +0300 Subject: [PATCH 2288/2908] fixes #9002; improve insertion_sort algorithm (#9005) * fixes #9002; improve insertion_sort algorithm * add type hints to sorts/insertion_sort.py --- sorts/insertion_sort.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index 6d5bb2b46013..f11ddac349a0 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -13,8 +13,19 @@ python3 insertion_sort.py """ +from collections.abc import MutableSequence +from typing import Any, Protocol, TypeVar -def insertion_sort(collection: list) -> list: + +class Comparable(Protocol): + def __lt__(self, other: Any, /) -> bool: + ... + + +T = TypeVar("T", bound=Comparable) + + +def insertion_sort(collection: MutableSequence[T]) -> MutableSequence[T]: """A pure Python implementation of the insertion sort algorithm :param collection: some mutable ordered collection with heterogeneous @@ -40,13 +51,12 @@ def insertion_sort(collection: list) -> list: True """ - 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] + for insert_index in range(1, len(collection)): + insert_value = collection[insert_index] + while insert_index > 0 and insert_value < collection[insert_index - 1]: + collection[insert_index] = collection[insert_index - 1] insert_index -= 1 - if insert_index != temp_index: - collection[insert_index + 1] = insert_value + collection[insert_index] = insert_value return collection From 04fd5c1b5e7880017d874f4305ca3396f868ee37 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Tue, 22 Aug 2023 00:20:51 +0100 Subject: [PATCH 2289/2908] Create langtons ant algorithm (#8967) * updating DIRECTORY.md * feat(cellular_automata): Langonts ant algorithm * updating DIRECTORY.md * Update cellular_automata/langtons_ant.py Co-authored-by: Tianyi Zheng * Apply suggestions from code review Co-authored-by: Tianyi Zheng * fix(langtons-ant): Set funcanimation interval to 1 --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- DIRECTORY.md | 1 + cellular_automata/langtons_ant.py | 106 ++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 cellular_automata/langtons_ant.py diff --git a/DIRECTORY.md b/DIRECTORY.md index dd4404edd364..866a3084f67b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -72,6 +72,7 @@ ## Cellular Automata * [Conways Game Of Life](cellular_automata/conways_game_of_life.py) * [Game Of Life](cellular_automata/game_of_life.py) + * [Langtons Ant](cellular_automata/langtons_ant.py) * [Nagel Schrekenberg](cellular_automata/nagel_schrekenberg.py) * [One Dimensional](cellular_automata/one_dimensional.py) * [Wa Tor](cellular_automata/wa_tor.py) diff --git a/cellular_automata/langtons_ant.py b/cellular_automata/langtons_ant.py new file mode 100644 index 000000000000..983c626546ad --- /dev/null +++ b/cellular_automata/langtons_ant.py @@ -0,0 +1,106 @@ +""" +Langton's ant + +@ https://en.wikipedia.org/wiki/Langton%27s_ant +@ https://upload.wikimedia.org/wikipedia/commons/0/09/LangtonsAntAnimated.gif +""" + +from functools import partial + +from matplotlib import pyplot as plt +from matplotlib.animation import FuncAnimation + +WIDTH = 80 +HEIGHT = 80 + + +class LangtonsAnt: + """ + Represents the main LangonsAnt algorithm. + + >>> la = LangtonsAnt(2, 2) + >>> la.board + [[True, True], [True, True]] + >>> la.ant_position + (1, 1) + """ + + def __init__(self, width: int, height: int) -> None: + # Each square is either True or False where True is white and False is black + self.board = [[True] * width for _ in range(height)] + self.ant_position: tuple[int, int] = (width // 2, height // 2) + + # Initially pointing left (similar to the the wikipedia image) + # (0 = 0° | 1 = 90° | 2 = 180 ° | 3 = 270°) + self.ant_direction: int = 3 + + def move_ant(self, axes: plt.Axes | None, display: bool, _frame: int) -> None: + """ + Performs three tasks: + 1. The ant turns either clockwise or anti-clockwise according to the colour + of the square that it is currently on. If the square is white, the ant + turns clockwise, and if the square is black the ant turns anti-clockwise + 2. The ant moves one square in the direction that it is currently facing + 3. The square the ant was previously on is inverted (White -> Black and + Black -> White) + + If display is True, the board will also be displayed on the axes + + >>> la = LangtonsAnt(2, 2) + >>> la.move_ant(None, True, 0) + >>> la.board + [[True, True], [True, False]] + >>> la.move_ant(None, True, 0) + >>> la.board + [[True, False], [True, False]] + """ + directions = { + 0: (-1, 0), # 0° + 1: (0, 1), # 90° + 2: (1, 0), # 180° + 3: (0, -1), # 270° + } + x, y = self.ant_position + + # Turn clockwise or anti-clockwise according to colour of square + if self.board[x][y] is True: + # The square is white so turn 90° clockwise + self.ant_direction = (self.ant_direction + 1) % 4 + else: + # The square is black so turn 90° anti-clockwise + self.ant_direction = (self.ant_direction - 1) % 4 + + # Move ant + move_x, move_y = directions[self.ant_direction] + self.ant_position = (x + move_x, y + move_y) + + # Flip colour of square + self.board[x][y] = not self.board[x][y] + + if display and axes: + # Display the board on the axes + axes.get_xaxis().set_ticks([]) + axes.get_yaxis().set_ticks([]) + axes.imshow(self.board, cmap="gray", interpolation="nearest") + + def display(self, frames: int = 100_000) -> None: + """ + Displays the board without delay in a matplotlib plot + to visually understand and track the ant. + + >>> _ = LangtonsAnt(WIDTH, HEIGHT) + """ + fig, ax = plt.subplots() + # Assign animation to a variable to prevent it from getting garbage collected + self.animation = FuncAnimation( + fig, partial(self.move_ant, ax, True), frames=frames, interval=1 + ) + plt.show() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + LangtonsAnt(WIDTH, HEIGHT).display() From c7aeaa3fd8a114ecf9b1e800dfb8cc3cc7a3cbaa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 07:42:14 +0200 Subject: [PATCH 2290/2908] [pre-commit.ci] pre-commit autoupdate (#9006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.284 → v0.0.285](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.284...v0.0.285) - [github.com/abravalheri/validate-pyproject: v0.13 → v0.14](https://github.com/abravalheri/validate-pyproject/compare/v0.13...v0.14) - [github.com/pre-commit/mirrors-mypy: v1.5.0 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.0...v1.5.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b08139561639..ad3e0cd87f2e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.284 + rev: v0.0.285 hooks: - id: ruff @@ -46,12 +46,12 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.13 + rev: v0.14 hooks: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.0 + rev: v1.5.1 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 866a3084f67b..ebb164d0496c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -155,6 +155,7 @@ * [Hexadecimal To Decimal](conversions/hexadecimal_to_decimal.py) * [Length Conversion](conversions/length_conversion.py) * [Molecular Chemistry](conversions/molecular_chemistry.py) + * [Octal To Binary](conversions/octal_to_binary.py) * [Octal To Decimal](conversions/octal_to_decimal.py) * [Prefix Conversions](conversions/prefix_conversions.py) * [Prefix Conversions String](conversions/prefix_conversions_string.py) From fceacf977f0e4567d00f297686527ac9b4e5561f Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Tue, 22 Aug 2023 10:33:47 +0100 Subject: [PATCH 2291/2908] Fix type errors in permutations (#9007) * updating DIRECTORY.md * types(permuations): Rename permute2 * Apply suggestions from code review Co-authored-by: Tianyi Zheng * fix(permutations): Call permute_recursive * fix(permutations): Correct permutations order --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- data_structures/arrays/permutations.py | 28 ++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/data_structures/arrays/permutations.py b/data_structures/arrays/permutations.py index 4558bd8d468a..0f029187b92b 100644 --- a/data_structures/arrays/permutations.py +++ b/data_structures/arrays/permutations.py @@ -1,17 +1,16 @@ -def permute(nums: list[int]) -> list[list[int]]: +def permute_recursive(nums: list[int]) -> list[list[int]]: """ Return all permutations. - >>> from itertools import permutations - >>> numbers= [1,2,3] - >>> all(list(nums) in permute(numbers) for nums in permutations(numbers)) - True + + >>> permute_recursive([1, 2, 3]) + [[3, 2, 1], [2, 3, 1], [1, 3, 2], [3, 1, 2], [2, 1, 3], [1, 2, 3]] """ - result = [] - if len(nums) == 1: - return [nums.copy()] + result: list[list[int]] = [] + if len(nums) == 0: + return [[]] for _ in range(len(nums)): n = nums.pop(0) - permutations = permute(nums) + permutations = permute_recursive(nums) for perm in permutations: perm.append(n) result.extend(permutations) @@ -19,15 +18,15 @@ def permute(nums: list[int]) -> list[list[int]]: return result -def permute2(nums): +def permute_backtrack(nums: list[int]) -> list[list[int]]: """ Return all permutations of the given list. - >>> permute2([1, 2, 3]) + >>> permute_backtrack([1, 2, 3]) [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 2, 1], [3, 1, 2]] """ - def backtrack(start): + def backtrack(start: int) -> None: if start == len(nums) - 1: output.append(nums[:]) else: @@ -36,7 +35,7 @@ def backtrack(start): backtrack(start + 1) nums[start], nums[i] = nums[i], nums[start] # backtrack - output = [] + output: list[list[int]] = [] backtrack(0) return output @@ -44,7 +43,6 @@ def backtrack(start): if __name__ == "__main__": import doctest - # use res to print the data in permute2 function - res = permute2([1, 2, 3]) + res = permute_backtrack([1, 2, 3]) print(res) doctest.testmod() From 0a9438071ee08121f069c77a5cb662206a4d348f Mon Sep 17 00:00:00 2001 From: Arijit De Date: Wed, 23 Aug 2023 18:06:59 +0530 Subject: [PATCH 2292/2908] Updated postfix_evaluation.py to support Unary operators (#8787) * Updated postfix_evaluation.py to support Unary operators and floating point numbers Fixes #8754 and #8724 Also merged evaluate_postfix_notations.py and postfix_evaluation.py into postfix_evaluation.py Signed-off-by: Arijit De * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated postfix_evaluation.py to support Unary operators and floating point numbers. Fixes #8754 and formatted code to pass ruff and black test. Also merged evaluate_postfix_notations.py and postfix_evaluation.py into postfix_evaluation.py which fixes #8724 and made sure it passes doctest Signed-off-by: Arijit De * Fixed return type hinting required by pre commit for evaluate function Signed-off-by: Arijit De * Changed line 186 to return only top of stack instead of calling the get_number function as it was converting float values to int, resulting in data loss. Fixes #8754 and #8724 Signed-off-by: Arijit De * Made the requested changes Also changed the code to make the evaluate function first convert all the numbers and then process the valid expression. * Fixes #8754, #8724 Updated postfix_evaluation.py postfix_evaluation.py now supports Unary operators and floating point numbers. Also merged evaluate_postfix_notations.py and postfix_evaluation.py into postfix_evaluation.py which fixes #8724. Added a doctest example with unary operator. * Fixes #8754, #8724 Updated postfix_evaluation.py postfix_evaluation.py now supports Unary operators and floating point numbers. Also merged evaluate_postfix_notations.py and postfix_evaluation.py into postfix_evaluation.py which fixes #8724. Added a doctest example with unary operator. * Fixes #8754, #8724 Updated the parse_token function of postfix_evaluation.py ostfix_evaluation.py now supports Unary operators and floating point numbers. Also merged evaluate_postfix_notations.py and postfix_evaluation.py into postfix_evaluation.py which fixes #8724. Added a doctest example with unary operator and invalid expression. * Fixes #8754, #8724 Updated postfix_evaluation.py postfix_evaluation.py now supports Unary operators and floating point numbers. Also merged evaluate_postfix_notations.py and postfix_evaluation.py into postfix_evaluation.py which fixes #8724. Added a doctest example with unary operator and invalid expression. * Update postfix_evaluation.py * Update postfix_evaluation.py * Update postfix_evaluation.py * Update postfix_evaluation.py * Update postfix_evaluation.py --------- Signed-off-by: Arijit De Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../stacks/evaluate_postfix_notations.py | 52 ----- data_structures/stacks/postfix_evaluation.py | 200 +++++++++++++++--- 2 files changed, 166 insertions(+), 86 deletions(-) delete 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 deleted file mode 100644 index 51ea353b17de..000000000000 --- a/data_structures/stacks/evaluate_postfix_notations.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -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. -""" -from __future__ import annotations - -from typing import Any - - -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: list[Any] = [] - - 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() diff --git a/data_structures/stacks/postfix_evaluation.py b/data_structures/stacks/postfix_evaluation.py index 28128f82ec19..03a87b9e0fa3 100644 --- a/data_structures/stacks/postfix_evaluation.py +++ b/data_structures/stacks/postfix_evaluation.py @@ -1,4 +1,11 @@ """ +Reverse Polish Nation is 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. + Output: Enter a Postfix Equation (space separated) = 5 6 9 * + @@ -17,52 +24,177 @@ Result = 59 """ -import operator as op +# Defining valid unary operator symbols +UNARY_OP_SYMBOLS = ("-", "+") + +# operators & their respective operation +OPERATORS = { + "^": lambda p, q: p**q, + "*": lambda p, q: p * q, + "/": lambda p, q: p / q, + "+": lambda p, q: p + q, + "-": lambda p, q: p - q, +} + + +def parse_token(token: str | float) -> float | str: + """ + Converts the given data to the appropriate number if it is indeed a number, else + returns the data as it is with a False flag. This function also serves as a check + of whether the input is a number or not. + + Parameters + ---------- + token: The data that needs to be converted to the appropriate operator or number. + + Returns + ------- + float or str + Returns a float if `token` is a number or a str if `token` is an operator + """ + if token in OPERATORS: + return token + try: + return float(token) + except ValueError: + msg = f"{token} is neither a number nor a valid operator" + raise ValueError(msg) + + +def evaluate(post_fix: list[str], verbose: bool = False) -> float: + """ + Evaluate postfix expression using a stack. + >>> evaluate(["0"]) + 0.0 + >>> evaluate(["-0"]) + -0.0 + >>> evaluate(["1"]) + 1.0 + >>> evaluate(["-1"]) + -1.0 + >>> evaluate(["-1.1"]) + -1.1 + >>> evaluate(["2", "1", "+", "3", "*"]) + 9.0 + >>> evaluate(["2", "1.9", "+", "3", "*"]) + 11.7 + >>> evaluate(["2", "-1.9", "+", "3", "*"]) + 0.30000000000000027 + >>> evaluate(["4", "13", "5", "/", "+"]) + 6.6 + >>> evaluate(["2", "-", "3", "+"]) + 1.0 + >>> evaluate(["-4", "5", "*", "6", "-"]) + -26.0 + >>> evaluate([]) + 0 + >>> evaluate(["4", "-", "6", "7", "/", "9", "8"]) + Traceback (most recent call last): + ... + ArithmeticError: Input is not a valid postfix expression + + Parameters + ---------- + post_fix: + The postfix expression is tokenized into operators and operands and stored + as a Python list + verbose: + Display stack contents while evaluating the expression if verbose is True -def solve(post_fix): + Returns + ------- + float + The evaluated value + """ + if not post_fix: + return 0 + # Checking the list to find out whether the postfix expression is valid + valid_expression = [parse_token(token) for token in post_fix] + if verbose: + # print table header + print("Symbol".center(8), "Action".center(12), "Stack", sep=" | ") + print("-" * (30 + len(post_fix))) stack = [] - div = lambda x, y: int(x / y) # noqa: E731 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(post_fix))) - - for x in post_fix: - if x.isdigit(): # if x in digit + for x in valid_expression: + if x not in OPERATORS: stack.append(x) # append x to stack - # output in tabular format - print(x.rjust(8), ("push(" + x + ")").ljust(12), ",".join(stack), sep=" | ") - else: + if verbose: + # output in tabular format + print( + f"{x}".rjust(8), + f"push({x})".ljust(12), + stack, + sep=" | ", + ) + continue + # If x is operator + # If only 1 value is inside the stack and + or - is encountered + # then this is unary + or - case + if x in UNARY_OP_SYMBOLS and len(stack) < 2: b = stack.pop() # pop stack + if x == "-": + b *= -1 # negate b + stack.append(b) + if verbose: + # output in tabular format + print( + "".rjust(8), + f"pop({b})".ljust(12), + stack, + sep=" | ", + ) + print( + str(x).rjust(8), + f"push({x}{b})".ljust(12), + stack, + sep=" | ", + ) + continue + b = stack.pop() # pop stack + if verbose: # output in tabular format - print("".rjust(8), ("pop(" + b + ")").ljust(12), ",".join(stack), sep=" | ") + print( + "".rjust(8), + f"pop({b})".ljust(12), + stack, + sep=" | ", + ) - a = stack.pop() # pop stack + a = stack.pop() # pop stack + if verbose: # 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 + print( + "".rjust(8), + f"pop({a})".ljust(12), + stack, + sep=" | ", + ) + # evaluate the 2 values popped from stack & push result to stack + stack.append(OPERATORS[x](a, b)) # type: ignore[index] + if verbose: # output in tabular format print( - x.rjust(8), - ("push(" + a + x + b + ")").ljust(12), - ",".join(stack), + f"{x}".rjust(8), + f"push({a}{x}{b})".ljust(12), + stack, sep=" | ", ) - - return int(stack[0]) + # If everything is executed correctly, the stack will contain + # only one element which is the result + if len(stack) != 1: + raise ArithmeticError("Input is not a valid postfix expression") + return float(stack[0]) if __name__ == "__main__": - Postfix = input("\n\nEnter a Postfix Equation (space separated) = ").split(" ") - print("\n\tResult = ", solve(Postfix)) + # Create a loop so that the user can evaluate postfix expressions multiple times + while True: + expression = input("Enter a Postfix Expression (space separated): ").split(" ") + prompt = "Do you want to see stack contents while evaluating? [y/N]: " + verbose = input(prompt).strip().lower() == "y" + output = evaluate(expression, verbose) + print("Result = ", output) + prompt = "Do you want to enter another expression? [y/N]: " + if input(prompt).strip().lower() != "y": + break From 421ace81edb0d9af3a173f4ca7e66cc900078c1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:18:10 +0200 Subject: [PATCH 2293/2908] [pre-commit.ci] pre-commit autoupdate (#9013) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.285 → v0.0.286](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.285...v0.0.286) - [github.com/tox-dev/pyproject-fmt: 0.13.1 → 1.1.0](https://github.com/tox-dev/pyproject-fmt/compare/0.13.1...1.1.0) * updating DIRECTORY.md * Fis ruff rules PIE808,PLR1714 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 1 - arithmetic_analysis/jacobi_iteration_method.py | 4 ++-- arithmetic_analysis/secant_method.py | 2 +- backtracking/hamiltonian_cycle.py | 2 +- backtracking/sudoku.py | 2 +- bit_manipulation/reverse_bits.py | 2 +- ciphers/trafid_cipher.py | 2 +- data_structures/binary_tree/lazy_segment_tree.py | 6 +++--- data_structures/linked_list/circular_linked_list.py | 2 +- data_structures/linked_list/doubly_linked_list.py | 6 +++--- data_structures/linked_list/is_palindrome.py | 2 +- data_structures/linked_list/singly_linked_list.py | 8 ++++---- data_structures/stacks/stock_span_problem.py | 2 +- digital_image_processing/filters/bilateral_filter.py | 4 ++-- digital_image_processing/filters/convolve.py | 4 ++-- .../filters/local_binary_pattern.py | 4 ++-- .../test_digital_image_processing.py | 4 ++-- divide_and_conquer/strassen_matrix_multiplication.py | 4 ++-- dynamic_programming/floyd_warshall.py | 10 +++++----- hashes/chaos_machine.py | 2 +- hashes/hamming_code.py | 4 ++-- hashes/sha1.py | 2 +- hashes/sha256.py | 2 +- machine_learning/gradient_descent.py | 2 +- machine_learning/linear_regression.py | 4 ++-- machine_learning/lstm/lstm_prediction.py | 4 ++-- maths/entropy.py | 2 +- maths/eulers_totient.py | 2 +- maths/greedy_coin_change.py | 2 +- maths/persistence.py | 4 ++-- maths/series/harmonic.py | 2 +- matrix/spiral_print.py | 2 +- other/magicdiamondpattern.py | 6 +++--- project_euler/problem_070/sol1.py | 2 +- project_euler/problem_112/sol1.py | 2 +- quantum/q_full_adder.py | 2 +- scheduling/highest_response_ratio_next.py | 6 +++--- sorts/counting_sort.py | 2 +- sorts/cycle_sort.py | 2 +- sorts/double_sort.py | 4 ++-- sorts/odd_even_transposition_parallel.py | 4 ++-- strings/rabin_karp.py | 2 +- 43 files changed, 70 insertions(+), 71 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad3e0cd87f2e..5c4e8579e116 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.285 + rev: v0.0.286 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.13.1" + rev: "1.1.0" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index ebb164d0496c..43da91cb818e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -245,7 +245,6 @@ * Stacks * [Balanced Parentheses](data_structures/stacks/balanced_parentheses.py) * [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py) - * [Evaluate Postfix Notations](data_structures/stacks/evaluate_postfix_notations.py) * [Infix To Postfix Conversion](data_structures/stacks/infix_to_postfix_conversion.py) * [Infix To Prefix Conversion](data_structures/stacks/infix_to_prefix_conversion.py) * [Next Greater Element](data_structures/stacks/next_greater_element.py) diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/arithmetic_analysis/jacobi_iteration_method.py index 17edf4bf4b8b..dba8a9ff44d3 100644 --- a/arithmetic_analysis/jacobi_iteration_method.py +++ b/arithmetic_analysis/jacobi_iteration_method.py @@ -152,9 +152,9 @@ def strictly_diagonally_dominant(table: NDArray[float64]) -> bool: is_diagonally_dominant = True - for i in range(0, rows): + for i in range(rows): total = 0 - for j in range(0, cols - 1): + for j in range(cols - 1): if i == j: continue else: diff --git a/arithmetic_analysis/secant_method.py b/arithmetic_analysis/secant_method.py index d28a46206d40..d39cb0ff30ef 100644 --- a/arithmetic_analysis/secant_method.py +++ b/arithmetic_analysis/secant_method.py @@ -20,7 +20,7 @@ def secant_method(lower_bound: float, upper_bound: float, repeats: int) -> float """ x0 = lower_bound x1 = upper_bound - for _ in range(0, repeats): + for _ in range(repeats): x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0)) return x1 diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index 4a4156d70b32..e9916f83f861 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -95,7 +95,7 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) return graph[path[curr_ind - 1]][path[0]] == 1 # Recursive Step - for next_ver in range(0, len(graph)): + for next_ver in range(len(graph)): if valid_connection(graph, next_ver, curr_ind, path): # Insert current vertex into path as next transition path[curr_ind] = next_ver diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index 698dedcc2125..6e4e3e8780f2 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -48,7 +48,7 @@ def is_safe(grid: Matrix, row: int, column: int, n: int) -> bool: is found) else returns True if it is 'safe' """ for i in range(9): - if grid[row][i] == n or grid[i][column] == n: + if n in {grid[row][i], grid[i][column]}: return False for i in range(3): diff --git a/bit_manipulation/reverse_bits.py b/bit_manipulation/reverse_bits.py index a8c77c11bfdd..74b4f2563234 100644 --- a/bit_manipulation/reverse_bits.py +++ b/bit_manipulation/reverse_bits.py @@ -20,7 +20,7 @@ def get_reverse_bit_string(number: int) -> str: ) raise TypeError(msg) bit_string = "" - for _ in range(0, 32): + for _ in range(32): bit_string += str(number % 2) number = number >> 1 return bit_string diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py index 108ac652f0e4..8aa2263ca5ac 100644 --- a/ciphers/trafid_cipher.py +++ b/ciphers/trafid_cipher.py @@ -119,7 +119,7 @@ def decrypt_message( for i in range(0, len(message) + 1, period): a, b, c = __decrypt_part(message[i : i + period], character_to_number) - for j in range(0, len(a)): + for j in range(len(a)): decrypted_numeric.append(a[j] + b[j] + c[j]) for each in decrypted_numeric: diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 050dfe0a6f2f..c26b0619380c 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -7,10 +7,10 @@ class SegmentTree: 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)] + self.segment_tree = [0 for i in range(4 * size)] # create array to store 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 + self.lazy = [0 for i in range(4 * size)] + self.flag = [0 for i in range(4 * size)] # flag for lazy update def left(self, idx: int) -> int: """ diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 325d91026137..d9544f4263a6 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -125,7 +125,7 @@ def test_circular_linked_list() -> None: 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 str(circular_linked_list) == "->".join(str(i) for i in range(7)) assert circular_linked_list.delete_front() == 0 assert circular_linked_list.delete_tail() == 6 diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 1a6c48191c4e..bd3445f9f6c5 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -98,7 +98,7 @@ def insert_at_nth(self, index: int, data): self.tail = new_node else: temp = self.head - for _ in range(0, index): + for _ in range(index): temp = temp.next temp.previous.next = new_node new_node.previous = temp.previous @@ -149,7 +149,7 @@ def delete_at_nth(self, index: int): self.tail.next = None else: temp = self.head - for _ in range(0, index): + for _ in range(index): temp = temp.next delete_node = temp temp.next.previous = temp.previous @@ -215,7 +215,7 @@ def test_doubly_linked_list() -> None: 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 str(linked_list) == "->".join(str(i) for i in range(12)) assert linked_list.delete_head() == 0 assert linked_list.delete_at_nth(9) == 10 diff --git a/data_structures/linked_list/is_palindrome.py b/data_structures/linked_list/is_palindrome.py index ec19e99f78c0..d540fb69f36b 100644 --- a/data_structures/linked_list/is_palindrome.py +++ b/data_structures/linked_list/is_palindrome.py @@ -68,7 +68,7 @@ def is_palindrome_dict(head): middle += 1 else: step = 0 - for i in range(0, len(v)): + for i in range(len(v)): if v[i] + v[len(v) - 1 - step] != checksum: return False step += 1 diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 890e21c9b404..f4b2ddce12d7 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -370,7 +370,7 @@ def test_singly_linked_list() -> None: linked_list.insert_head(0) linked_list.insert_tail(11) - assert str(linked_list) == "->".join(str(i) for i in range(0, 12)) + assert str(linked_list) == "->".join(str(i) for i in range(12)) assert linked_list.delete_head() == 0 assert linked_list.delete_nth(9) == 10 @@ -378,11 +378,11 @@ def test_singly_linked_list() -> None: 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 + assert all(linked_list[i] == i + 1 for i in range(9)) is True - for i in range(0, 9): + for i in range(9): linked_list[i] = -i - assert all(linked_list[i] == -i for i in range(0, 9)) is True + assert all(linked_list[i] == -i for i in range(9)) is True linked_list.reverse() assert str(linked_list) == "->".join(str(i) for i in range(-8, 1)) diff --git a/data_structures/stacks/stock_span_problem.py b/data_structures/stacks/stock_span_problem.py index de423c1ebf66..5efe58d25798 100644 --- a/data_structures/stacks/stock_span_problem.py +++ b/data_structures/stacks/stock_span_problem.py @@ -36,7 +36,7 @@ def calculation_span(price, s): # A utility function to print elements of array def print_array(arr, n): - for i in range(0, n): + for i in range(n): print(arr[i], end=" ") diff --git a/digital_image_processing/filters/bilateral_filter.py b/digital_image_processing/filters/bilateral_filter.py index 565da73f6b0e..199ac4d9939a 100644 --- a/digital_image_processing/filters/bilateral_filter.py +++ b/digital_image_processing/filters/bilateral_filter.py @@ -31,8 +31,8 @@ def get_slice(img: np.ndarray, x: int, y: int, kernel_size: int) -> np.ndarray: 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): + for i in range(kernel_size): + for j in range(kernel_size): arr[i, j] = math.sqrt( abs(i - kernel_size // 2) ** 2 + abs(j - kernel_size // 2) ** 2 ) diff --git a/digital_image_processing/filters/convolve.py b/digital_image_processing/filters/convolve.py index 299682010da6..004402f29ba9 100644 --- a/digital_image_processing/filters/convolve.py +++ b/digital_image_processing/filters/convolve.py @@ -11,8 +11,8 @@ def im2col(image, block_size): 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): + for i in range(dst_height): + for j in range(dst_width): window = ravel(image[i : i + block_size[0], j : j + block_size[1]]) image_array[row, :] = window row += 1 diff --git a/digital_image_processing/filters/local_binary_pattern.py b/digital_image_processing/filters/local_binary_pattern.py index 907fe2cb0555..861369ba6a32 100644 --- a/digital_image_processing/filters/local_binary_pattern.py +++ b/digital_image_processing/filters/local_binary_pattern.py @@ -71,8 +71,8 @@ def local_binary_value(image: np.ndarray, x_coordinate: int, y_coordinate: int) # Iterating through the image and calculating the # local binary pattern value for each pixel. - for i in range(0, image.shape[0]): - for j in range(0, image.shape[1]): + for i in range(image.shape[0]): + for j in range(image.shape[1]): lbp_image[i][j] = local_binary_value(image, i, j) cv2.imshow("local binary pattern", lbp_image) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index fee7ab247b55..528b4bc3b74c 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -118,8 +118,8 @@ def test_local_binary_pattern(): # Iterating through the image and calculating the local binary pattern value # for each pixel. - for i in range(0, image.shape[0]): - for j in range(0, image.shape[1]): + for i in range(image.shape[0]): + for j in range(image.shape[1]): lbp_image[i][j] = lbp.local_binary_value(image, i, j) assert lbp_image.any() diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index cbfc7e5655db..1d03950ef9fe 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -131,7 +131,7 @@ def strassen(matrix1: list, matrix2: list) -> list: # Adding zeros to the matrices so that the arrays dimensions are the same and also # power of 2 - for i in range(0, maxim): + for i in range(maxim): if i < dimension1[0]: for _ in range(dimension1[1], maxim): new_matrix1[i].append(0) @@ -146,7 +146,7 @@ def strassen(matrix1: list, matrix2: list) -> list: final_matrix = actual_strassen(new_matrix1, new_matrix2) # Removing the additional zeros - for i in range(0, maxim): + for i in range(maxim): if i < dimension1[0]: for _ in range(dimension2[1], maxim): final_matrix[i].pop() diff --git a/dynamic_programming/floyd_warshall.py b/dynamic_programming/floyd_warshall.py index 614a3c72a992..2331f3e65483 100644 --- a/dynamic_programming/floyd_warshall.py +++ b/dynamic_programming/floyd_warshall.py @@ -5,19 +5,19 @@ class Graph: 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) + [math.inf for j in range(n)] for i in range(n) ] # adjacency matrix for weight self.dp = [ - [math.inf for j in range(0, n)] for i in range(0, n) + [math.inf for j in range(n)] for i in range(n) ] # dp[i][j] stores minimum distance from i to j def add_edge(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(self.n): + for i in range(self.n): + for j in range(self.n): self.dp[i][j] = min(self.dp[i][j], self.dp[i][k] + self.dp[k][j]) def show_min(self, u, v): diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 238fdb1c0634..d2fde2f5e371 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -53,7 +53,7 @@ def xorshift(x, y): key = machine_time % m # Evolution (Time Length) - for _ in range(0, t): + for _ in range(t): # Variables (Position + Parameters) r = params_space[key] value = buffer_space[key] diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index dc93032183e0..8498ca920b36 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -135,7 +135,7 @@ def emitter_converter(size_par, data): # Mount the message cont_bp = 0 # parity bit counter - for x in range(0, size_par + len(data)): + for x in range(size_par + len(data)): if data_ord[x] is None: data_out.append(str(parity[cont_bp])) cont_bp += 1 @@ -228,7 +228,7 @@ def receptor_converter(size_par, data): # Mount the message cont_bp = 0 # Parity bit counter - for x in range(0, size_par + len(data_output)): + for x in range(size_par + len(data_output)): if data_ord[x] is None: data_out.append(str(parity[cont_bp])) cont_bp += 1 diff --git a/hashes/sha1.py b/hashes/sha1.py index b325ce3e43bb..8a03673f3c9f 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -97,7 +97,7 @@ def final_hash(self): for block in self.blocks: expanded_block = self.expand_block(block) a, b, c, d, e = self.h - for i in range(0, 80): + for i in range(80): if 0 <= i < 20: f = (b & c) | ((~b) & d) k = 0x5A827999 diff --git a/hashes/sha256.py b/hashes/sha256.py index 98f7c096e3b6..ba9aff8dbf41 100644 --- a/hashes/sha256.py +++ b/hashes/sha256.py @@ -138,7 +138,7 @@ def final_hash(self) -> None: a, b, c, d, e, f, g, h = self.hashes - for index in range(0, 64): + for index in range(64): if index > 15: # modify the zero-ed indexes at the end of the array s0 = ( diff --git a/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index 5b74dad082e7..9ffc02bbc284 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -110,7 +110,7 @@ def run_gradient_descent(): while True: j += 1 temp_parameter_vector = [0, 0, 0, 0] - for i in range(0, len(parameter_vector)): + for i in range(len(parameter_vector)): cost_derivative = get_cost_derivative(i - 1) temp_parameter_vector[i] = ( parameter_vector[i] - LEARNING_RATE * cost_derivative diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 75943ac9f2ad..0847112ad538 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -78,7 +78,7 @@ def run_linear_regression(data_x, data_y): theta = np.zeros((1, no_features)) - for i in range(0, iterations): + for i in range(iterations): 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(f"At Iteration {i + 1} - Error is {error:.5f}") @@ -107,7 +107,7 @@ def main(): theta = run_linear_regression(data_x, data_y) len_result = theta.shape[1] print("Resultant Feature vector : ") - for i in range(0, len_result): + for i in range(len_result): print(f"{theta[0, i]:.5f}") diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py index 74197c46a0ad..16530e935ea7 100644 --- a/machine_learning/lstm/lstm_prediction.py +++ b/machine_learning/lstm/lstm_prediction.py @@ -32,10 +32,10 @@ train_x, train_y = [], [] test_x, test_y = [], [] - for i in range(0, len(train_data) - forward_days - look_back + 1): + for i in range(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): + for i in range(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) diff --git a/maths/entropy.py b/maths/entropy.py index 498c28f31bc4..23753d884484 100644 --- a/maths/entropy.py +++ b/maths/entropy.py @@ -101,7 +101,7 @@ def analyze_text(text: str) -> tuple[dict, dict]: # first case when we have space at start. two_char_strings[" " + text[0]] += 1 - for i in range(0, len(text) - 1): + for i in range(len(text) - 1): single_char_strings[text[i]] += 1 two_char_strings[text[i : i + 2]] += 1 return single_char_strings, two_char_strings diff --git a/maths/eulers_totient.py b/maths/eulers_totient.py index a156647037b4..00f0254c215a 100644 --- a/maths/eulers_totient.py +++ b/maths/eulers_totient.py @@ -21,7 +21,7 @@ def totient(n: int) -> list: for i in range(2, n + 1): if is_prime[i]: primes.append(i) - for j in range(0, len(primes)): + for j in range(len(primes)): if i * primes[j] >= n: break is_prime[i * primes[j]] = False diff --git a/maths/greedy_coin_change.py b/maths/greedy_coin_change.py index 7cf669bcb8cb..db2c381bc84a 100644 --- a/maths/greedy_coin_change.py +++ b/maths/greedy_coin_change.py @@ -81,7 +81,7 @@ def find_minimum_change(denominations: list[int], value: str) -> list[int]: ): n = int(input("Enter the number of denominations you want to add: ").strip()) - for i in range(0, n): + for i in range(n): denominations.append(int(input(f"Denomination {i}: ").strip())) value = input("Enter the change you want to make in Indian Currency: ").strip() else: diff --git a/maths/persistence.py b/maths/persistence.py index 607641e67200..c61a69a7c27d 100644 --- a/maths/persistence.py +++ b/maths/persistence.py @@ -28,7 +28,7 @@ def multiplicative_persistence(num: int) -> int: numbers = [int(i) for i in num_string] total = 1 - for i in range(0, len(numbers)): + for i in range(len(numbers)): total *= numbers[i] num_string = str(total) @@ -67,7 +67,7 @@ def additive_persistence(num: int) -> int: numbers = [int(i) for i in num_string] total = 0 - for i in range(0, len(numbers)): + for i in range(len(numbers)): total += numbers[i] num_string = str(total) diff --git a/maths/series/harmonic.py b/maths/series/harmonic.py index 50f29c93dd5f..35792d38af9b 100644 --- a/maths/series/harmonic.py +++ b/maths/series/harmonic.py @@ -45,7 +45,7 @@ def is_harmonic_series(series: list) -> bool: return True rec_series = [] series_len = len(series) - for i in range(0, series_len): + for i in range(series_len): if series[i] == 0: raise ValueError("Input series cannot have 0 as an element") rec_series.append(1 / series[i]) diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 0d0be1527aec..5eef263f7aef 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -54,7 +54,7 @@ def spiral_print_clockwise(a: list[list[int]]) -> None: return # horizotal printing increasing - for i in range(0, mat_col): + for i in range(mat_col): print(a[0][i]) # vertical printing down for i in range(1, mat_row): diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py index 0fc41d7a25d8..89b973bb41e8 100644 --- a/other/magicdiamondpattern.py +++ b/other/magicdiamondpattern.py @@ -7,10 +7,10 @@ def floyd(n): Parameters: n : size of pattern """ - for i in range(0, n): - for _ in range(0, n - i - 1): # printing spaces + for i in range(n): + for _ in range(n - i - 1): # printing spaces print(" ", end="") - for _ in range(0, i + 1): # printing stars + for _ in range(i + 1): # printing stars print("* ", end="") print() diff --git a/project_euler/problem_070/sol1.py b/project_euler/problem_070/sol1.py index 273f37efc5fc..57a6c1916374 100644 --- a/project_euler/problem_070/sol1.py +++ b/project_euler/problem_070/sol1.py @@ -44,7 +44,7 @@ def get_totients(max_one: int) -> list[int]: """ totients = [0] * max_one - for i in range(0, max_one): + for i in range(max_one): totients[i] = i for i in range(2, max_one): diff --git a/project_euler/problem_112/sol1.py b/project_euler/problem_112/sol1.py index b3ea6b35654a..31996d070771 100644 --- a/project_euler/problem_112/sol1.py +++ b/project_euler/problem_112/sol1.py @@ -49,7 +49,7 @@ def check_bouncy(n: int) -> bool: raise ValueError("check_bouncy() accepts only integer arguments") str_n = str(n) sorted_str_n = "".join(sorted(str_n)) - return sorted_str_n != str_n and sorted_str_n[::-1] != str_n + return str_n not in {sorted_str_n, sorted_str_n[::-1]} def solution(percent: float = 99) -> int: diff --git a/quantum/q_full_adder.py b/quantum/q_full_adder.py index 66d93198519e..ec4efa4346a5 100644 --- a/quantum/q_full_adder.py +++ b/quantum/q_full_adder.py @@ -88,7 +88,7 @@ def quantum_full_adder( quantum_circuit = qiskit.QuantumCircuit(qr, cr) - for i in range(0, 3): + for i in range(3): if entry[i] == 2: quantum_circuit.h(i) # for hadamard entries elif entry[i] == 1: diff --git a/scheduling/highest_response_ratio_next.py b/scheduling/highest_response_ratio_next.py index 9c999ec65053..057bd64cc729 100644 --- a/scheduling/highest_response_ratio_next.py +++ b/scheduling/highest_response_ratio_next.py @@ -53,7 +53,7 @@ def calculate_turn_around_time( loc = 0 # Saves the current response ratio. temp = 0 - for i in range(0, no_of_process): + for i in range(no_of_process): if finished_process[i] == 0 and arrival_time[i] <= current_time: temp = (burst_time[i] + (current_time - arrival_time[i])) / burst_time[ i @@ -87,7 +87,7 @@ def calculate_waiting_time( """ waiting_time = [0] * no_of_process - for i in range(0, no_of_process): + for i in range(no_of_process): waiting_time[i] = turn_around_time[i] - burst_time[i] return waiting_time @@ -106,7 +106,7 @@ def calculate_waiting_time( ) print("Process name \tArrival time \tBurst time \tTurn around time \tWaiting time") - for i in range(0, no_of_process): + for i in range(no_of_process): print( f"{process_name[i]}\t\t{arrival_time[i]}\t\t{burst_time[i]}\t\t" f"{turn_around_time[i]}\t\t\t{waiting_time[i]}" diff --git a/sorts/counting_sort.py b/sorts/counting_sort.py index 18c4b0323dcb..256952df52d2 100644 --- a/sorts/counting_sort.py +++ b/sorts/counting_sort.py @@ -49,7 +49,7 @@ 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)): + for i in reversed(range(coll_len)): ordered[counting_arr[collection[i] - coll_min] - 1] = collection[i] counting_arr[collection[i] - coll_min] -= 1 diff --git a/sorts/cycle_sort.py b/sorts/cycle_sort.py index 806f40441d79..7177c8ea110d 100644 --- a/sorts/cycle_sort.py +++ b/sorts/cycle_sort.py @@ -19,7 +19,7 @@ def cycle_sort(array: list) -> list: [] """ array_len = len(array) - for cycle_start in range(0, array_len - 1): + for cycle_start in range(array_len - 1): item = array[cycle_start] pos = cycle_start diff --git a/sorts/double_sort.py b/sorts/double_sort.py index 5ca88a6745d5..a19641d94752 100644 --- a/sorts/double_sort.py +++ b/sorts/double_sort.py @@ -16,9 +16,9 @@ def double_sort(lst): """ no_of_elements = len(lst) for _ in range( - 0, int(((no_of_elements - 1) / 2) + 1) + int(((no_of_elements - 1) / 2) + 1) ): # we don't need to traverse to end of list as - for j in range(0, no_of_elements - 1): + for j in range(no_of_elements - 1): if ( lst[j + 1] < lst[j] ): # applying bubble sort algorithm from left to right (or forwards) diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index 87b0e4d1e20f..9e0d228bdc5b 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -33,7 +33,7 @@ def oe_process(position, value, l_send, r_send, lr_cv, rr_cv, result_pipe): # 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): + for i in range(10): if (i + position) % 2 == 0 and r_send is not None: # send your value to your right neighbor process_lock.acquire() @@ -123,7 +123,7 @@ def odd_even_transposition(arr): p.start() # wait for the processes to end and write their values to the list - for p in range(0, len(result_pipe)): + for p in range(len(result_pipe)): arr[p] = result_pipe[p][0].recv() process_array_[p].join() return arr diff --git a/strings/rabin_karp.py b/strings/rabin_karp.py index 81ca611a76b3..532c689f8a97 100644 --- a/strings/rabin_karp.py +++ b/strings/rabin_karp.py @@ -38,7 +38,7 @@ def rabin_karp(pattern: str, text: str) -> bool: continue modulus_power = (modulus_power * alphabet_size) % modulus - for i in range(0, t_len - p_len + 1): + for i in range(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: From 5a4ea233cd30723628fb184bc05f969ad463b0af Mon Sep 17 00:00:00 2001 From: Kotmin <70173732+Kotmin@users.noreply.github.com> Date: Mon, 4 Sep 2023 19:38:26 +0200 Subject: [PATCH 2294/2908] Style sigmoid function in harmony with pep guideness (#6677) * Style sigmoid function in harmony with pep guideness * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- neural_network/back_propagation_neural_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index 9dd112115f5e..bdd096b3f653 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -21,8 +21,8 @@ from matplotlib import pyplot as plt -def sigmoid(x): - return 1 / (1 + np.exp(-1 * x)) +def sigmoid(x: np.ndarray) -> np.ndarray: + return 1 / (1 + np.exp(-x)) class DenseLayer: From ac73be217863cc78af97bb86a9156ac38c4ae1e5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 08:27:05 +0530 Subject: [PATCH 2295/2908] [pre-commit.ci] pre-commit autoupdate (#9042) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c4e8579e116..c046789463cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.286 + rev: v0.0.287 hooks: - id: ruff From 79b043d35ca266cf5053f5b62b2fe0f7bc6344d9 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Tue, 5 Sep 2023 01:04:36 -0300 Subject: [PATCH 2296/2908] Texture analysis using Haralick Descriptors for Computer Vision tasks (#8004) * Create haralick_descriptors * Working on creating Unit Testing for Haralick Descriptors module * Type hinting for Haralick descriptors * Fixed docstrings, unit testing and formatting choices * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed line size formatting * Added final doctests * Changed main callable * Updated requirements.txt * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update computer_vision/haralick_descriptors.py No! What if the Kernel is empty? Example: >>> kernel = np.zeros((1)) >>> kernel or np.ones((3, 3)) array([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]) Co-authored-by: Christian Clauss * Undone wrong commit * Update haralick_descriptors.py * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix ruff errors in haralick_descriptors.py * Add type hint to haralick_descriptors.py to fix ruff error * Update haralick_descriptors.py * Update haralick_descriptors.py * Update haralick_descriptors.py * Update haralick_descriptors.py * Try to fix mypy errors in haralick_descriptors.py * Update haralick_descriptors.py * Fix type hint in haralick_descriptors.py --------- Co-authored-by: Rafael Zimmer Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Tianyi Zheng --- computer_vision/haralick_descriptors.py | 431 ++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 432 insertions(+) create mode 100644 computer_vision/haralick_descriptors.py diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py new file mode 100644 index 000000000000..1a86d84ea14b --- /dev/null +++ b/computer_vision/haralick_descriptors.py @@ -0,0 +1,431 @@ +""" +https://en.wikipedia.org/wiki/Image_texture +https://en.wikipedia.org/wiki/Co-occurrence_matrix#Application_to_image_analysis +""" +import imageio.v2 as imageio +import numpy as np + + +def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float: + """Simple implementation of Root Mean Squared Error + for two N dimensional numpy arrays. + + Examples: + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([1, 2, 3])) + 0.0 + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([2, 2, 2])) + 0.816496580927726 + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([6, 4, 2])) + 3.1622776601683795 + """ + return np.sqrt(((original - reference) ** 2).mean()) + + +def normalize_image( + image: np.ndarray, cap: float = 255.0, data_type: np.dtype = np.uint8 +) -> np.ndarray: + """ + Normalizes image in Numpy 2D array format, between ranges 0-cap, + as to fit uint8 type. + + Args: + image: 2D numpy array representing image as matrix, with values in any range + cap: Maximum cap amount for normalization + data_type: numpy data type to set output variable to + Returns: + return 2D numpy array of type uint8, corresponding to limited range matrix + + Examples: + >>> normalize_image(np.array([[1, 2, 3], [4, 5, 10]]), + ... cap=1.0, data_type=np.float64) + array([[0. , 0.11111111, 0.22222222], + [0.33333333, 0.44444444, 1. ]]) + >>> normalize_image(np.array([[4, 4, 3], [1, 7, 2]])) + array([[127, 127, 85], + [ 0, 255, 42]], dtype=uint8) + """ + normalized = (image - np.min(image)) / (np.max(image) - np.min(image)) * cap + return normalized.astype(data_type) + + +def normalize_array(array: np.ndarray, cap: float = 1) -> np.ndarray: + """Normalizes a 1D array, between ranges 0-cap. + + Args: + array: List containing values to be normalized between cap range. + cap: Maximum cap amount for normalization. + Returns: + return 1D numpy array, corresponding to limited range array + + Examples: + >>> normalize_array(np.array([2, 3, 5, 7])) + array([0. , 0.2, 0.6, 1. ]) + >>> normalize_array(np.array([[5], [7], [11], [13]])) + array([[0. ], + [0.25], + [0.75], + [1. ]]) + """ + diff = np.max(array) - np.min(array) + return (array - np.min(array)) / (1 if diff == 0 else diff) * cap + + +def grayscale(image: np.ndarray) -> np.ndarray: + """ + Uses luminance weights to transform RGB channel to greyscale, by + taking the dot product between the channel and the weights. + + Example: + >>> grayscale(np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]])) + array([[158, 97], + [ 56, 200]], dtype=uint8) + """ + return np.dot(image[:, :, 0:3], [0.299, 0.587, 0.114]).astype(np.uint8) + + +def binarize(image: np.ndarray, threshold: float = 127.0) -> np.ndarray: + """ + Binarizes a grayscale image based on a given threshold value, + setting values to 1 or 0 accordingly. + + Examples: + >>> binarize(np.array([[128, 255], [101, 156]])) + array([[1, 1], + [0, 1]]) + >>> binarize(np.array([[0.07, 1], [0.51, 0.3]]), threshold=0.5) + array([[0, 1], + [1, 0]]) + """ + return np.where(image > threshold, 1, 0) + + +def transform(image: np.ndarray, kind: str, kernel: np.ndarray = None) -> np.ndarray: + """ + Simple image transformation using one of two available filter functions: + Erosion and Dilation. + + Args: + image: binarized input image, onto which to apply transformation + kind: Can be either 'erosion', in which case the :func:np.max + function is called, or 'dilation', when :func:np.min is used instead. + kernel: n x n kernel with shape < :attr:image.shape, + to be used when applying convolution to original image + + Returns: + returns a numpy array with same shape as input image, + corresponding to applied binary transformation. + + Examples: + >>> img = np.array([[1, 0.5], [0.2, 0.7]]) + >>> img = binarize(img, threshold=0.5) + >>> transform(img, 'erosion') + array([[1, 1], + [1, 1]], dtype=uint8) + >>> transform(img, 'dilation') + array([[0, 0], + [0, 0]], dtype=uint8) + """ + if kernel is None: + kernel = np.ones((3, 3)) + + if kind == "erosion": + constant = 1 + apply = np.max + else: + constant = 0 + apply = np.min + + center_x, center_y = (x // 2 for x in kernel.shape) + + # Use padded image when applying convolotion + # to not go out of bounds of the original the image + transformed = np.zeros(image.shape, dtype=np.uint8) + padded = np.pad(image, 1, "constant", constant_values=constant) + + for x in range(center_x, padded.shape[0] - center_x): + for y in range(center_y, padded.shape[1] - center_y): + center = padded[ + x - center_x : x + center_x + 1, y - center_y : y + center_y + 1 + ] + # Apply transformation method to the centered section of the image + transformed[x - center_x, y - center_y] = apply(center[kernel == 1]) + + return transformed + + +def opening_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: + """ + Opening filter, defined as the sequence of + erosion and then a dilation filter on the same image. + + Examples: + >>> img = np.array([[1, 0.5], [0.2, 0.7]]) + >>> img = binarize(img, threshold=0.5) + >>> opening_filter(img) + array([[1, 1], + [1, 1]], dtype=uint8) + """ + if kernel is None: + np.ones((3, 3)) + + return transform(transform(image, "dilation", kernel), "erosion", kernel) + + +def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: + """ + Opening filter, defined as the sequence of + dilation and then erosion filter on the same image. + + Examples: + >>> img = np.array([[1, 0.5], [0.2, 0.7]]) + >>> img = binarize(img, threshold=0.5) + >>> closing_filter(img) + array([[0, 0], + [0, 0]], dtype=uint8) + """ + if kernel is None: + kernel = np.ones((3, 3)) + return transform(transform(image, "erosion", kernel), "dilation", kernel) + + +def binary_mask( + image_gray: np.ndarray, image_map: np.ndarray +) -> tuple[np.ndarray, np.ndarray]: + """ + Apply binary mask, or thresholding based + on bit mask value (mapping mask is binary). + + Returns the mapped true value mask and its complementary false value mask. + + Example: + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> binary_mask(gray, morphological) + (array([[1, 1], + [1, 1]], dtype=uint8), array([[158, 97], + [ 56, 200]], dtype=uint8)) + """ + true_mask, false_mask = image_gray.copy(), image_gray.copy() + true_mask[image_map == 1] = 1 + false_mask[image_map == 0] = 0 + + return true_mask, false_mask + + +def matrix_concurrency(image: np.ndarray, coordinate: tuple[int, int]) -> np.ndarray: + """ + Calculate sample co-occurrence matrix based on input image + as well as selected coordinates on image. + + Implementation is made using basic iteration, + as function to be performed (np.max) is non-linear and therefore + not callable on the frequency domain. + + Example: + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> mask_1 = binary_mask(gray, morphological)[0] + >>> matrix_concurrency(mask_1, (0, 1)) + array([[0., 0.], + [0., 0.]]) + """ + matrix = np.zeros([np.max(image) + 1, np.max(image) + 1]) + + offset_x, offset_y = coordinate + + for x in range(1, image.shape[0] - 1): + for y in range(1, image.shape[1] - 1): + base_pixel = image[x, y] + offset_pixel = image[x + offset_x, y + offset_y] + + matrix[base_pixel, offset_pixel] += 1 + matrix_sum = np.sum(matrix) + return matrix / (1 if matrix_sum == 0 else matrix_sum) + + +def haralick_descriptors(matrix: np.ndarray) -> list[float]: + """Calculates all 8 Haralick descriptors based on co-occurence input matrix. + All descriptors are as follows: + Maximum probability, Inverse Difference, Homogeneity, Entropy, + Energy, Dissimilarity, Contrast and Correlation + + Args: + matrix: Co-occurence matrix to use as base for calculating descriptors. + + Returns: + Reverse ordered list of resulting descriptors + + Example: + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> mask_1 = binary_mask(gray, morphological)[0] + >>> concurrency = matrix_concurrency(mask_1, (0, 1)) + >>> haralick_descriptors(concurrency) + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + """ + # Function np.indices could be used for bigger input types, + # but np.ogrid works just fine + i, j = np.ogrid[0 : matrix.shape[0], 0 : matrix.shape[1]] # np.indices() + + # Pre-calculate frequent multiplication and subtraction + prod = np.multiply(i, j) + sub = np.subtract(i, j) + + # Calculate numerical value of Maximum Probability + maximum_prob = np.max(matrix) + # Using the definition for each descriptor individually to calculate its matrix + correlation = prod * matrix + energy = np.power(matrix, 2) + contrast = matrix * np.power(sub, 2) + + dissimilarity = matrix * np.abs(sub) + inverse_difference = matrix / (1 + np.abs(sub)) + homogeneity = matrix / (1 + np.power(sub, 2)) + entropy = -(matrix[matrix > 0] * np.log(matrix[matrix > 0])) + + # Sum values for descriptors ranging from the first one to the last, + # as all are their respective origin matrix and not the resulting value yet. + return [ + maximum_prob, + correlation.sum(), + energy.sum(), + contrast.sum(), + dissimilarity.sum(), + inverse_difference.sum(), + homogeneity.sum(), + entropy.sum(), + ] + + +def get_descriptors( + masks: tuple[np.ndarray, np.ndarray], coordinate: tuple[int, int] +) -> np.ndarray: + """ + Calculate all Haralick descriptors for a sequence of + different co-occurrence matrices, given input masks and coordinates. + + Example: + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> get_descriptors(binary_mask(gray, morphological), (0, 1)) + array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) + """ + descriptors = np.array( + [haralick_descriptors(matrix_concurrency(mask, coordinate)) for mask in masks] + ) + + # Concatenate each individual descriptor into + # one single list containing sequence of descriptors + return np.concatenate(descriptors, axis=None) + + +def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32: + """ + Simple method for calculating the euclidean distance between two points, + with type np.ndarray. + + Example: + >>> a = np.array([1, 0, -2]) + >>> b = np.array([2, -1, 1]) + >>> euclidean(a, b) + 3.3166247903554 + """ + return np.sqrt(np.sum(np.square(point_1 - point_2))) + + +def get_distances(descriptors: np.ndarray, base: int) -> list[tuple[int, float]]: + """ + Calculate all Euclidean distances between a selected base descriptor + and all other Haralick descriptors + The resulting comparison is return in decreasing order, + showing which descriptor is the most similar to the selected base. + + Args: + descriptors: Haralick descriptors to compare with base index + base: Haralick descriptor index to use as base when calculating respective + euclidean distance to other descriptors. + + Returns: + Ordered distances between descriptors + + Example: + >>> index = 1 + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> get_distances(get_descriptors( + ... binary_mask(gray, morphological), (0, 1)), + ... index) + [(0, 0.0), (1, 0.0), (2, 0.0), (3, 0.0), (4, 0.0), (5, 0.0), \ +(6, 0.0), (7, 0.0), (8, 0.0), (9, 0.0), (10, 0.0), (11, 0.0), (12, 0.0), \ +(13, 0.0), (14, 0.0), (15, 0.0)] + """ + distances = np.array( + [euclidean(descriptor, descriptors[base]) for descriptor in descriptors] + ) + # Normalize distances between range [0, 1] + normalized_distances: list[float] = normalize_array(distances, 1).tolist() + enum_distances = list(enumerate(normalized_distances)) + enum_distances.sort(key=lambda tup: tup[1], reverse=True) + return enum_distances + + +if __name__ == "__main__": + # Index to compare haralick descriptors to + index = int(input()) + q_value_list = [int(value) for value in input().split()] + q_value = (q_value_list[0], q_value_list[1]) + + # Format is the respective filter to apply, + # can be either 1 for the opening filter or else for the closing + parameters = {"format": int(input()), "threshold": int(input())} + + # Number of images to perform methods on + b_number = int(input()) + + files, descriptors = [], [] + + for _ in range(b_number): + file = input().rstrip() + files.append(file) + + # Open given image and calculate morphological filter, + # respective masks and correspondent Harralick Descriptors. + image = imageio.imread(file).astype(np.float32) + gray = grayscale(image) + threshold = binarize(gray, parameters["threshold"]) + + morphological = ( + opening_filter(threshold) + if parameters["format"] == 1 + else closing_filter(threshold) + ) + masks = binary_mask(gray, morphological) + descriptors.append(get_descriptors(masks, q_value)) + + # Transform ordered distances array into a sequence of indexes + # corresponding to original file position + distances = get_distances(np.array(descriptors), index) + indexed_distances = np.array(distances).astype(np.uint8)[:, 0] + + # Finally, print distances considering the Haralick descriptions from the base + # file to all other images using the morphology method of choice. + print(f"Query: {files[index]}") + print("Ranking:") + for idx, file_idx in enumerate(indexed_distances): + print(f"({idx}) {files[file_idx]}", end="\n") diff --git a/requirements.txt b/requirements.txt index 2702523d542e..1128e9d66820 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ beautifulsoup4 fake_useragent +imageio keras lxml matplotlib From 72f600036511c4999fa56bf007bf92ec465e94d7 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Tue, 5 Sep 2023 05:49:00 +0100 Subject: [PATCH 2297/2908] Fix get amazon product data erroring due to whitespace in headers (#9009) * updating DIRECTORY.md * fix(get-amazon-product-data): Remove whitespace in headers * refactor(get-amazon-product-data): Don't print to_csv --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- web_programming/get_amazon_product_data.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/web_programming/get_amazon_product_data.py b/web_programming/get_amazon_product_data.py index c796793f2205..a16175688667 100644 --- a/web_programming/get_amazon_product_data.py +++ b/web_programming/get_amazon_product_data.py @@ -19,11 +19,13 @@ def get_amazon_product_data(product: str = "laptop") -> DataFrame: """ url = f"https://www.amazon.in/laptop/s?k={product}" header = { - "User-Agent": """Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 - (KHTML, like Gecko)Chrome/44.0.2403.157 Safari/537.36""", + "User-Agent": ( + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" + "(KHTML, like Gecko)Chrome/44.0.2403.157 Safari/537.36" + ), "Accept-Language": "en-US, en;q=0.5", } - soup = BeautifulSoup(requests.get(url, headers=header).text) + soup = BeautifulSoup(requests.get(url, headers=header).text, features="lxml") # Initialize a Pandas dataframe with the column titles data_frame = DataFrame( columns=[ @@ -74,8 +76,8 @@ def get_amazon_product_data(product: str = "laptop") -> DataFrame: except ValueError: discount = float("nan") except AttributeError: - pass - data_frame.loc[len(data_frame.index)] = [ + continue + data_frame.loc[str(len(data_frame.index))] = [ product_title, product_link, product_price, From 9e4f9962a02ae584b392670a13d54ef8731e8f7f Mon Sep 17 00:00:00 2001 From: David Ekong <66387173+davidekong@users.noreply.github.com> Date: Wed, 6 Sep 2023 15:00:09 +0100 Subject: [PATCH 2298/2908] Created harshad_numbers.py (#9023) * Created harshad_numbers.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update harshad_numbers.py Fixed a few errors * Update harshad_numbers.py Added function type hints * Update harshad_numbers.py Fixed depreciated Tuple and List usage * Update harshad_numbers.py Fixed incompatible types in assignments * Update harshad_numbers.py Fixed incompatible type assignments * Update maths/harshad_numbers.py Co-authored-by: Tianyi Zheng * Update maths/harshad_numbers.py Co-authored-by: Tianyi Zheng * Raised Value Error for negative inputs * Update maths/harshad_numbers.py Co-authored-by: Tianyi Zheng * Update maths/harshad_numbers.py Co-authored-by: Tianyi Zheng * Update maths/harshad_numbers.py Co-authored-by: Tianyi Zheng * Update harshad_numbers.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update harshad_numbers.py * Update harshad_numbers.py * Update harshad_numbers.py * Update harshad_numbers.py Added doc test to int_to_base, fixed nested loop, other minor changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/harshad_numbers.py | 158 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 maths/harshad_numbers.py diff --git a/maths/harshad_numbers.py b/maths/harshad_numbers.py new file mode 100644 index 000000000000..050c69e0bd15 --- /dev/null +++ b/maths/harshad_numbers.py @@ -0,0 +1,158 @@ +""" +A harshad number (or more specifically an n-harshad number) is a number that's +divisible by the sum of its digits in some given base n. +Reference: https://en.wikipedia.org/wiki/Harshad_number +""" + + +def int_to_base(number: int, base: int) -> str: + """ + Convert a given positive decimal integer to base 'base'. + Where 'base' ranges from 2 to 36. + + Examples: + >>> int_to_base(23, 2) + '10111' + >>> int_to_base(58, 5) + '213' + >>> int_to_base(167, 16) + 'A7' + >>> # bases below 2 and beyond 36 will error + >>> int_to_base(98, 1) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + >>> int_to_base(98, 37) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + """ + + if base < 2 or base > 36: + raise ValueError("'base' must be between 2 and 36 inclusive") + + digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + result = "" + + if number < 0: + raise ValueError("number must be a positive integer") + + while number > 0: + number, remainder = divmod(number, base) + result = digits[remainder] + result + + if result == "": + result = "0" + + return result + + +def sum_of_digits(num: int, base: int) -> str: + """ + Calculate the sum of digit values in a positive integer + converted to the given 'base'. + Where 'base' ranges from 2 to 36. + + Examples: + >>> sum_of_digits(103, 12) + '13' + >>> sum_of_digits(1275, 4) + '30' + >>> sum_of_digits(6645, 2) + '1001' + >>> # bases below 2 and beyond 36 will error + >>> sum_of_digits(543, 1) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + >>> sum_of_digits(543, 37) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + """ + + if base < 2 or base > 36: + raise ValueError("'base' must be between 2 and 36 inclusive") + + num_str = int_to_base(num, base) + res = sum(int(char, base) for char in num_str) + res_str = int_to_base(res, base) + return res_str + + +def harshad_numbers_in_base(limit: int, base: int) -> list[str]: + """ + Finds all Harshad numbers smaller than num in base 'base'. + Where 'base' ranges from 2 to 36. + + Examples: + >>> harshad_numbers_in_base(15, 2) + ['1', '10', '100', '110', '1000', '1010', '1100'] + >>> harshad_numbers_in_base(12, 34) + ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B'] + >>> harshad_numbers_in_base(12, 4) + ['1', '2', '3', '10', '12', '20', '21'] + >>> # bases below 2 and beyond 36 will error + >>> harshad_numbers_in_base(234, 37) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + >>> harshad_numbers_in_base(234, 1) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + """ + + if base < 2 or base > 36: + raise ValueError("'base' must be between 2 and 36 inclusive") + + if limit < 0: + return [] + + numbers = [ + int_to_base(i, base) + for i in range(1, limit) + if i % int(sum_of_digits(i, base), base) == 0 + ] + + return numbers + + +def is_harshad_number_in_base(num: int, base: int) -> bool: + """ + Determines whether n in base 'base' is a harshad number. + Where 'base' ranges from 2 to 36. + + Examples: + >>> is_harshad_number_in_base(18, 10) + True + >>> is_harshad_number_in_base(21, 10) + True + >>> is_harshad_number_in_base(-21, 5) + False + >>> # bases below 2 and beyond 36 will error + >>> is_harshad_number_in_base(45, 37) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + >>> is_harshad_number_in_base(45, 1) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + """ + + if base < 2 or base > 36: + raise ValueError("'base' must be between 2 and 36 inclusive") + + if num < 0: + return False + + n = int_to_base(num, base) + d = sum_of_digits(num, base) + return int(n, base) % int(d, base) == 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 153c35eac02b5f043824dfa72e071d2b3f756607 Mon Sep 17 00:00:00 2001 From: Adarsh Acharya <132294330+AdarshAcharya5@users.noreply.github.com> Date: Thu, 7 Sep 2023 00:46:51 +0530 Subject: [PATCH 2299/2908] Added Scaled Exponential Linear Unit Activation Function (#9027) * Added Scaled Exponential Linear Unit Activation Function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update scaled_exponential_linear_unit.py * Update scaled_exponential_linear_unit.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update scaled_exponential_linear_unit.py * Update scaled_exponential_linear_unit.py * Update scaled_exponential_linear_unit.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update scaled_exponential_linear_unit.py * Update scaled_exponential_linear_unit.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../scaled_exponential_linear_unit.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 neural_network/activation_functions/scaled_exponential_linear_unit.py diff --git a/neural_network/activation_functions/scaled_exponential_linear_unit.py b/neural_network/activation_functions/scaled_exponential_linear_unit.py new file mode 100644 index 000000000000..f91dc6852136 --- /dev/null +++ b/neural_network/activation_functions/scaled_exponential_linear_unit.py @@ -0,0 +1,44 @@ +""" +Implements the Scaled Exponential Linear Unit or SELU function. +The function takes a vector of K real numbers and two real numbers +alpha(default = 1.6732) & lambda (default = 1.0507) as input and +then applies the SELU function to each element of the vector. +SELU is a self-normalizing activation function. It is a variant +of the ELU. The main advantage of SELU is that we can be sure +that the output will always be standardized due to its +self-normalizing behavior. That means there is no need to +include Batch-Normalization layers. +References : +https://iq.opengenus.org/scaled-exponential-linear-unit/ +""" + +import numpy as np + + +def scaled_exponential_linear_unit( + vector: np.ndarray, alpha: float = 1.6732, lambda_: float = 1.0507 +) -> np.ndarray: + """ + Applies the Scaled Exponential Linear Unit function to each element of the vector. + Parameters : + vector : np.ndarray + alpha : float (default = 1.6732) + lambda_ : float (default = 1.0507) + + Returns : np.ndarray + Formula : f(x) = lambda_ * x if x > 0 + lambda_ * alpha * (e**x - 1) if x <= 0 + Examples : + >>> scaled_exponential_linear_unit(vector=np.array([1.3, 3.7, 2.4])) + array([1.36591, 3.88759, 2.52168]) + + >>> scaled_exponential_linear_unit(vector=np.array([1.3, 4.7, 8.2])) + array([1.36591, 4.93829, 8.61574]) + """ + return lambda_ * np.where(vector > 0, vector, alpha * (np.exp(vector) - 1)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0cae02451a214cd70b36f2bf0b7a043c25aea99d Mon Sep 17 00:00:00 2001 From: Rohan Saraogi <62804340+r0sa2@users.noreply.github.com> Date: Thu, 7 Sep 2023 03:52:36 -0400 Subject: [PATCH 2300/2908] Added nth_sgonal_num.py (#8753) * Added nth_sgonal_num.py * Update and rename nth_sgonal_num.py to polygonal_numbers.py --------- Co-authored-by: Tianyi Zheng --- maths/polygonal_numbers.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 maths/polygonal_numbers.py diff --git a/maths/polygonal_numbers.py b/maths/polygonal_numbers.py new file mode 100644 index 000000000000..7a7dc91acb26 --- /dev/null +++ b/maths/polygonal_numbers.py @@ -0,0 +1,32 @@ +def polygonal_num(num: int, sides: int) -> int: + """ + Returns the `num`th `sides`-gonal number. It is assumed that `num` >= 0 and + `sides` >= 3 (see for reference https://en.wikipedia.org/wiki/Polygonal_number). + + >>> polygonal_num(0, 3) + 0 + >>> polygonal_num(3, 3) + 6 + >>> polygonal_num(5, 4) + 25 + >>> polygonal_num(2, 5) + 5 + >>> polygonal_num(-1, 0) + Traceback (most recent call last): + ... + ValueError: Invalid input: num must be >= 0 and sides must be >= 3. + >>> polygonal_num(0, 2) + Traceback (most recent call last): + ... + ValueError: Invalid input: num must be >= 0 and sides must be >= 3. + """ + if num < 0 or sides < 3: + raise ValueError("Invalid input: num must be >= 0 and sides must be >= 3.") + + return ((sides - 2) * num**2 - (sides - 4) * num) // 2 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c9b4b8002f24a33ea49c16dff5ef9cbebbd64b1d Mon Sep 17 00:00:00 2001 From: Saksham Saha Date: Fri, 8 Sep 2023 17:50:28 +0530 Subject: [PATCH 2301/2908] Added an add at position subroutiune to linked list (#9020) * added addAtPosition to simple linked list * added addAtPosition to simple linked list * modified the add function to take an optional position command * fixed type safety errors: * fixed type safety errors: * fixed type safety errors: * fixed type safety errors: * fixed size error * fixed size error * added doctest and updates the else after checking if posiiton argument less than 0 or not * added doctest and updates the else after checking if posiiton argument less than 0 or not * fixed the contributing.md mistake * added doctest for out of bounds position value, both negative and positive --- data_structures/linked_list/__init__.py | 52 ++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index 56b0e51baa93..225113f72cee 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -21,8 +21,56 @@ def __init__(self) -> None: self.head: Node | None = None self.size = 0 - def add(self, item: Any) -> None: - self.head = Node(item, self.head) + def add(self, item: Any, position: int = 0) -> None: + """ + Add an item to the LinkedList at the specified position. + Default position is 0 (the head). + + Args: + item (Any): The item to add to the LinkedList. + position (int, optional): The position at which to add the item. + Defaults to 0. + + Raises: + ValueError: If the position is negative or out of bounds. + + >>> linked_list = LinkedList() + >>> linked_list.add(1) + >>> linked_list.add(2) + >>> linked_list.add(3) + >>> linked_list.add(4, 2) + >>> print(linked_list) + 3 --> 2 --> 4 --> 1 + + # Test adding to a negative position + >>> linked_list.add(5, -3) + Traceback (most recent call last): + ... + ValueError: Position must be non-negative + + # Test adding to an out-of-bounds position + >>> linked_list.add(5,7) + Traceback (most recent call last): + ... + ValueError: Out of bounds + >>> linked_list.add(5, 4) + >>> print(linked_list) + 3 --> 2 --> 4 --> 1 --> 5 + """ + if position < 0: + raise ValueError("Position must be non-negative") + + if position == 0 or self.head is None: + new_node = Node(item, self.head) + self.head = new_node + else: + current = self.head + for _ in range(position - 1): + current = current.next + if current is None: + raise ValueError("Out of bounds") + new_node = Node(item, current.next) + current.next = new_node self.size += 1 def remove(self) -> Any: From 5a5ca06944148ad7232dd61dcf7c609c0c74c252 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Sat, 9 Sep 2023 23:28:43 +0530 Subject: [PATCH 2302/2908] Update `actions/checkout` with `fetch-depth: 0` (#9046) * Update `actions/checkout` with `fetch-depth: 0` * Update directory_writer.yml * Create junk.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update directory_writer.yml * Update directory_writer.yml --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/directory_writer.yml | 4 +++- arithmetic_analysis/junk.py | 0 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 arithmetic_analysis/junk.py diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index 331962cef11e..702c15f1e29b 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -6,7 +6,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 # v1, NOT v2 or v3 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: 3.x diff --git a/arithmetic_analysis/junk.py b/arithmetic_analysis/junk.py new file mode 100644 index 000000000000..e69de29bb2d1 From 97e2de0763d75b1875428d87818ef111481d5953 Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:11:22 +0500 Subject: [PATCH 2303/2908] Euler 070 partial replacement of numpy loops. (#9055) * Euler 070 partial replacement of numpy loops. * Update project_euler/problem_070/sol1.py * project_euler.yml: Upgrade actions/checkout@v4 and add numpy * Update project_euler.yml --------- Co-authored-by: Christian Clauss --- .github/workflows/project_euler.yml | 8 ++++---- project_euler/problem_070/sol1.py | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml index 460938219c14..7bbccf76e192 100644 --- a/.github/workflows/project_euler.yml +++ b/.github/workflows/project_euler.yml @@ -14,26 +14,26 @@ jobs: project-euler: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.x - name: Install pytest and pytest-cov run: | python -m pip install --upgrade pip - python -m pip install --upgrade pytest pytest-cov + python -m pip install --upgrade numpy pytest pytest-cov - run: pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ validate-solutions: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.x - name: Install pytest and requests run: | python -m pip install --upgrade pip - python -m pip install --upgrade pytest requests + python -m pip install --upgrade numpy pytest requests - run: pytest scripts/validate_solutions.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/project_euler/problem_070/sol1.py b/project_euler/problem_070/sol1.py index 57a6c1916374..f1114a280a31 100644 --- a/project_euler/problem_070/sol1.py +++ b/project_euler/problem_070/sol1.py @@ -30,6 +30,8 @@ """ from __future__ import annotations +import numpy as np + def get_totients(max_one: int) -> list[int]: """ @@ -42,17 +44,14 @@ def get_totients(max_one: int) -> list[int]: >>> get_totients(10) [0, 1, 1, 2, 2, 4, 2, 6, 4, 6] """ - totients = [0] * max_one - - for i in range(max_one): - totients[i] = i + totients = np.arange(max_one) for i in range(2, max_one): if totients[i] == i: - for j in range(i, max_one, i): - totients[j] -= totients[j] // i + x = np.arange(i, max_one, i) # array of indexes to select + totients[x] -= totients[x] // i - return totients + return totients.tolist() def has_same_digits(num1: int, num2: int) -> bool: From 4246da387f8b48da5147320344d336886787aea1 Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:05:32 +0500 Subject: [PATCH 2304/2908] jacobi_iteration_method.py the use of vector operations, which reduces the calculation time by dozens of times (#8938) * Replaced loops in jacobi_iteration_method function with vector operations. That gives a reduction in the time for calculating the algorithm. * Replaced loops in jacobi_iteration_method function with vector operations. That gives a reduction in the time for calculating the algorithm. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Delete main.py * Update jacobi_iteration_method.py Changed a line that was too long. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update jacobi_iteration_method.py Changed the type of the returned list as required. * Update jacobi_iteration_method.py Replaced init_val with new_val. * Update jacobi_iteration_method.py Fixed bug: init_val: list[int] to list[float]. Since the numbers are fractional: init_val = [0.5, -0.5, -0.5]. * Update jacobi_iteration_method.py Changed comments, made variable names more understandable. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update jacobi_iteration_method.py left the old algorithm commented out, as it clearly shows what is being done. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update jacobi_iteration_method.py Edits upon request. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../jacobi_iteration_method.py | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/arithmetic_analysis/jacobi_iteration_method.py index dba8a9ff44d3..44c52dd44640 100644 --- a/arithmetic_analysis/jacobi_iteration_method.py +++ b/arithmetic_analysis/jacobi_iteration_method.py @@ -12,7 +12,7 @@ def jacobi_iteration_method( coefficient_matrix: NDArray[float64], constant_matrix: NDArray[float64], - init_val: list[int], + init_val: list[float], iterations: int, ) -> list[float]: """ @@ -115,6 +115,7 @@ def jacobi_iteration_method( strictly_diagonally_dominant(table) + """ # Iterates the whole matrix for given number of times for _ in range(iterations): new_val = [] @@ -130,8 +131,37 @@ def jacobi_iteration_method( temp = (temp + val) / denom new_val.append(temp) init_val = new_val + """ + + # denominator - a list of values along the diagonal + denominator = np.diag(coefficient_matrix) + + # val_last - values of the last column of the table array + val_last = table[:, -1] + + # masks - boolean mask of all strings without diagonal + # elements array coefficient_matrix + masks = ~np.eye(coefficient_matrix.shape[0], dtype=bool) + + # no_diagonals - coefficient_matrix array values without diagonal elements + no_diagonals = coefficient_matrix[masks].reshape(-1, rows - 1) + + # Here we get 'i_col' - these are the column numbers, for each row + # without diagonal elements, except for the last column. + i_row, i_col = np.where(masks) + ind = i_col.reshape(-1, rows - 1) + + #'i_col' is converted to a two-dimensional list 'ind', which will be + # used to make selections from 'init_val' ('arr' array see below). + + # Iterates the whole matrix for given number of times + for _ in range(iterations): + arr = np.take(init_val, ind) + sum_product_rows = np.sum((-1) * no_diagonals * arr, axis=1) + new_val = (sum_product_rows + val_last) / denominator + init_val = new_val - return [float(i) for i in new_val] + return new_val.tolist() # Checks if the given matrix is strictly diagonally dominant From 1488cdea708485eb1d81c73126eab13cb9b04a47 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 01:56:50 +0200 Subject: [PATCH 2305/2908] [pre-commit.ci] pre-commit autoupdate (#9056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.287 → v0.0.288](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.287...v0.0.288) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c046789463cc..722b408ee9e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.287 + rev: v0.0.288 hooks: - id: ruff - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black diff --git a/DIRECTORY.md b/DIRECTORY.md index 43da91cb818e..1b802564f939 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -5,6 +5,7 @@ * [In Static Equilibrium](arithmetic_analysis/in_static_equilibrium.py) * [Intersection](arithmetic_analysis/intersection.py) * [Jacobi Iteration Method](arithmetic_analysis/jacobi_iteration_method.py) + * [Junk](arithmetic_analysis/junk.py) * [Lu Decomposition](arithmetic_analysis/lu_decomposition.py) * [Newton Forward Interpolation](arithmetic_analysis/newton_forward_interpolation.py) * [Newton Method](arithmetic_analysis/newton_method.py) @@ -133,6 +134,7 @@ ## Computer Vision * [Cnn Classification](computer_vision/cnn_classification.py) * [Flip Augmentation](computer_vision/flip_augmentation.py) + * [Haralick Descriptors](computer_vision/haralick_descriptors.py) * [Harris Corner](computer_vision/harris_corner.py) * [Horn Schunck](computer_vision/horn_schunck.py) * [Mean Threshold](computer_vision/mean_threshold.py) @@ -586,6 +588,7 @@ * [Greedy Coin Change](maths/greedy_coin_change.py) * [Hamming Numbers](maths/hamming_numbers.py) * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) + * [Harshad Numbers](maths/harshad_numbers.py) * [Hexagonal Number](maths/hexagonal_number.py) * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) * [Interquartile Range](maths/interquartile_range.py) @@ -626,6 +629,7 @@ * [Pi Monte Carlo Estimation](maths/pi_monte_carlo_estimation.py) * [Points Are Collinear 3D](maths/points_are_collinear_3d.py) * [Pollard Rho](maths/pollard_rho.py) + * [Polygonal Numbers](maths/polygonal_numbers.py) * [Polynomial Evaluation](maths/polynomial_evaluation.py) * Polynomials * [Single Indeterminate Operations](maths/polynomials/single_indeterminate_operations.py) @@ -712,6 +716,7 @@ * Activation Functions * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) * [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py) + * [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Perceptron](neural_network/perceptron.py) From fbad85d3ecbbb826a5891807c823149d38bbaed3 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 16 Sep 2023 18:12:31 -0400 Subject: [PATCH 2306/2908] Delete empty junk file (#9062) * updating DIRECTORY.md * updating DIRECTORY.md * Delete empty junk file * updating DIRECTORY.md * Fix ruff errors * Fix more ruff errors --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 - arithmetic_analysis/junk.py | 0 computer_vision/haralick_descriptors.py | 8 +++++--- conversions/convert_number_to_words.py | 6 +++--- graphs/tarjans_scc.py | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) delete mode 100644 arithmetic_analysis/junk.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 1b802564f939..d81e4ec1ee83 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -5,7 +5,6 @@ * [In Static Equilibrium](arithmetic_analysis/in_static_equilibrium.py) * [Intersection](arithmetic_analysis/intersection.py) * [Jacobi Iteration Method](arithmetic_analysis/jacobi_iteration_method.py) - * [Junk](arithmetic_analysis/junk.py) * [Lu Decomposition](arithmetic_analysis/lu_decomposition.py) * [Newton Forward Interpolation](arithmetic_analysis/newton_forward_interpolation.py) * [Newton Method](arithmetic_analysis/newton_method.py) diff --git a/arithmetic_analysis/junk.py b/arithmetic_analysis/junk.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 1a86d84ea14b..413cea304f6c 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -100,7 +100,9 @@ def binarize(image: np.ndarray, threshold: float = 127.0) -> np.ndarray: return np.where(image > threshold, 1, 0) -def transform(image: np.ndarray, kind: str, kernel: np.ndarray = None) -> np.ndarray: +def transform( + image: np.ndarray, kind: str, kernel: np.ndarray | None = None +) -> np.ndarray: """ Simple image transformation using one of two available filter functions: Erosion and Dilation. @@ -154,7 +156,7 @@ def transform(image: np.ndarray, kind: str, kernel: np.ndarray = None) -> np.nda return transformed -def opening_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: +def opening_filter(image: np.ndarray, kernel: np.ndarray | None = None) -> np.ndarray: """ Opening filter, defined as the sequence of erosion and then a dilation filter on the same image. @@ -172,7 +174,7 @@ def opening_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: return transform(transform(image, "dilation", kernel), "erosion", kernel) -def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: +def closing_filter(image: np.ndarray, kernel: np.ndarray | None = None) -> np.ndarray: """ Opening filter, defined as the sequence of dilation and then erosion filter on the same image. diff --git a/conversions/convert_number_to_words.py b/conversions/convert_number_to_words.py index 0e4405319f1f..0c428928b31d 100644 --- a/conversions/convert_number_to_words.py +++ b/conversions/convert_number_to_words.py @@ -54,7 +54,7 @@ def max_value(cls, system: str) -> int: class NumberWords(Enum): - ONES: ClassVar = { + ONES: ClassVar[dict[int, str]] = { 0: "", 1: "one", 2: "two", @@ -67,7 +67,7 @@ class NumberWords(Enum): 9: "nine", } - TEENS: ClassVar = { + TEENS: ClassVar[dict[int, str]] = { 0: "ten", 1: "eleven", 2: "twelve", @@ -80,7 +80,7 @@ class NumberWords(Enum): 9: "nineteen", } - TENS: ClassVar = { + TENS: ClassVar[dict[int, str]] = { 2: "twenty", 3: "thirty", 4: "forty", diff --git a/graphs/tarjans_scc.py b/graphs/tarjans_scc.py index 30f8ca8a204f..dfd2e52704d5 100644 --- a/graphs/tarjans_scc.py +++ b/graphs/tarjans_scc.py @@ -77,7 +77,7 @@ def create_graph(n, edges): n_vertices = 7 source = [0, 0, 1, 2, 3, 3, 4, 4, 6] target = [1, 3, 2, 0, 1, 4, 5, 6, 5] - edges = [(u, v) for u, v in zip(source, target)] + edges = list(zip(source, target)) g = create_graph(n_vertices, edges) assert [[5], [6], [4], [3, 2, 1, 0]] == tarjan(g) From dc50add8a78ebf34bc7bb050c1a0e61d207b9544 Mon Sep 17 00:00:00 2001 From: Rohan Anand <96521078+rohan472000@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:21:36 +0530 Subject: [PATCH 2307/2908] Update xgboost_regressor.py (#9078) * Update xgboost_regressor.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- machine_learning/xgboost_regressor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/machine_learning/xgboost_regressor.py b/machine_learning/xgboost_regressor.py index 023984fc1f59..a540e3ab03eb 100644 --- a/machine_learning/xgboost_regressor.py +++ b/machine_learning/xgboost_regressor.py @@ -27,7 +27,9 @@ def xgboost( ... 1.14300000e+03, 2.60958904e+00, 3.67800000e+01, -1.19780000e+02]])) array([[1.1139996]], dtype=float32) """ - xgb = XGBRegressor(verbosity=0, random_state=42) + xgb = XGBRegressor( + verbosity=0, random_state=42, tree_method="exact", base_score=0.5 + ) xgb.fit(features, target) # Predict target for test data predictions = xgb.predict(test_features) From b203150ac481743a6d8c1ef01091712a54dfbf6c Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Sat, 23 Sep 2023 10:53:09 +0200 Subject: [PATCH 2308/2908] Fix typos (#9076) * fix typo * fix typo * fix typos * fix typo --- cellular_automata/langtons_ant.py | 2 +- compression/README.md | 4 ++-- hashes/README.md | 4 ++-- sorts/README.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cellular_automata/langtons_ant.py b/cellular_automata/langtons_ant.py index 983c626546ad..9847c50a5c3e 100644 --- a/cellular_automata/langtons_ant.py +++ b/cellular_automata/langtons_ant.py @@ -30,7 +30,7 @@ def __init__(self, width: int, height: int) -> None: self.board = [[True] * width for _ in range(height)] self.ant_position: tuple[int, int] = (width // 2, height // 2) - # Initially pointing left (similar to the the wikipedia image) + # Initially pointing left (similar to the wikipedia image) # (0 = 0° | 1 = 90° | 2 = 180 ° | 3 = 270°) self.ant_direction: int = 3 diff --git a/compression/README.md b/compression/README.md index cf54ea986175..bad7ae1a2f76 100644 --- a/compression/README.md +++ b/compression/README.md @@ -1,9 +1,9 @@ # Compression Data compression is everywhere, you need it to store data without taking too much space. -Either the compression lose some data (then we talk about lossy compression, such as .jpg) or it does not (and then it is lossless compression, such as .png) +Either the compression loses some data (then we talk about lossy compression, such as .jpg) or it does not (and then it is lossless compression, such as .png) -Lossless compression is mainly used for archive purpose as it allow storing data without losing information about the file archived. On the other hand, lossy compression is used for transfer of file where quality isn't necessarily what is required (i.e: images on Twitter). +Lossless compression is mainly used for archive purpose as it allows storing data without losing information about the file archived. On the other hand, lossy compression is used for transfer of file where quality isn't necessarily what is required (i.e: images on Twitter). * * diff --git a/hashes/README.md b/hashes/README.md index 6df9a2fb6360..0237260eaa67 100644 --- a/hashes/README.md +++ b/hashes/README.md @@ -7,11 +7,11 @@ Unlike encryption, which is intended to protect data in transit, hashing is inte This is one of the first algorithms that has gained widespread acceptance. MD5 is hashing algorithm made by Ray Rivest that is known to suffer vulnerabilities. It was created in 1992 as the successor to MD4. Currently MD6 is in the works, but as of 2009 Rivest had removed it from NIST consideration for SHA-3. ### SHA -SHA stands for Security Hashing Algorithm and it’s probably best known as the hashing algorithm used in most SSL/TLS cipher suites. A cipher suite is a collection of ciphers and algorithms that are used for SSL/TLS connections. SHA handles the hashing aspects. SHA-1, as we mentioned earlier, is now deprecated. SHA-2 is now mandatory. SHA-2 is sometimes known has SHA-256, though variants with longer bit lengths are also available. +SHA stands for Security Hashing Algorithm and it’s probably best known as the hashing algorithm used in most SSL/TLS cipher suites. A cipher suite is a collection of ciphers and algorithms that are used for SSL/TLS connections. SHA handles the hashing aspects. SHA-1, as we mentioned earlier, is now deprecated. SHA-2 is now mandatory. SHA-2 is sometimes known as SHA-256, though variants with longer bit lengths are also available. ### SHA256 SHA 256 is a member of the SHA 2 algorithm family, under which SHA stands for Secure Hash Algorithm. It was a collaborative effort between both the NSA and NIST to implement a successor to the SHA 1 family, which was beginning to lose potency against brute force attacks. It was published in 2001. The importance of the 256 in the name refers to the final hash digest value, i.e. the hash value will remain 256 bits regardless of the size of the plaintext/cleartext. Other algorithms in the SHA family are similar to SHA 256 in some ways. ### Luhn -The Luhn algorithm, also renowned as the modulus 10 or mod 10 algorithm, is a straightforward checksum formula used to validate a wide range of identification numbers, including credit card numbers, IMEI numbers, and Canadian Social Insurance Numbers. A community of mathematicians developed the LUHN formula in the late 1960s. Companies offering credit cards quickly followed suit. Since the algorithm is in the public interest, anyone can use it. The algorithm is used by most credit cards and many government identification numbers as a simple method of differentiating valid figures from mistyped or otherwise incorrect numbers. It was created to guard against unintentional errors, not malicious attacks. \ No newline at end of file +The Luhn algorithm, also renowned as the modulus 10 or mod 10 algorithm, is a straightforward checksum formula used to validate a wide range of identification numbers, including credit card numbers, IMEI numbers, and Canadian Social Insurance Numbers. A community of mathematicians developed the LUHN formula in the late 1960s. Companies offering credit cards quickly followed suit. Since the algorithm is in the public interest, anyone can use it. The algorithm is used by most credit cards and many government identification numbers as a simple method of differentiating valid figures from mistyped or otherwise incorrect numbers. It was created to guard against unintentional errors, not malicious attacks. diff --git a/sorts/README.md b/sorts/README.md index ceb0207c2be4..f24427d582e7 100644 --- a/sorts/README.md +++ b/sorts/README.md @@ -4,7 +4,7 @@ is specified by the sorting algorithm. The most typical orders are lexical or nu of sorting lies in the fact that, if data is stored in a sorted manner, data searching can be highly optimised. Another use for sorting is to represent data in a more readable manner. -This section contains a lot of important algorithms that helps us to use sorting algorithms in various scenarios. +This section contains a lot of important algorithms that help us to use sorting algorithms in various scenarios. ## References * * From 53a51b3529ad5f985e6f65b5b3a4e155af1d2d63 Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Sun, 24 Sep 2023 19:09:32 +1300 Subject: [PATCH 2309/2908] Rewrite of base32.py algorithm (#9068) * rewrite of base32.py * changed maps to list comprehension * Apply suggestions from code review Co-authored-by: Tianyi Zheng --------- Co-authored-by: Tianyi Zheng --- ciphers/base32.py | 51 +++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/ciphers/base32.py b/ciphers/base32.py index fee53ccaf0c4..1924d1e185d7 100644 --- a/ciphers/base32.py +++ b/ciphers/base32.py @@ -1,42 +1,45 @@ -import base64 +""" +Base32 encoding and decoding +https://en.wikipedia.org/wiki/Base32 +""" +B32_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" -def base32_encode(string: str) -> bytes: + +def base32_encode(data: bytes) -> bytes: """ - Encodes a given string to base32, returning a bytes-like object - >>> base32_encode("Hello World!") + >>> base32_encode(b"Hello World!") b'JBSWY3DPEBLW64TMMQQQ====' - >>> base32_encode("123456") + >>> base32_encode(b"123456") b'GEZDGNBVGY======' - >>> base32_encode("some long complex string") + >>> base32_encode(b"some long complex string") b'ONXW2ZJANRXW4ZZAMNXW24DMMV4CA43UOJUW4ZY=' """ - - # encoded the input (we need a bytes like object) - # then, b32encoded the bytes-like object - return base64.b32encode(string.encode("utf-8")) + binary_data = "".join(bin(ord(d))[2:].zfill(8) for d in data.decode("utf-8")) + binary_data = binary_data.ljust(5 * ((len(binary_data) // 5) + 1), "0") + b32_chunks = map("".join, zip(*[iter(binary_data)] * 5)) + b32_result = "".join(B32_CHARSET[int(chunk, 2)] for chunk in b32_chunks) + return bytes(b32_result.ljust(8 * ((len(b32_result) // 8) + 1), "="), "utf-8") -def base32_decode(encoded_bytes: bytes) -> str: +def base32_decode(data: bytes) -> bytes: """ - Decodes a given bytes-like object to a string, returning a string >>> base32_decode(b'JBSWY3DPEBLW64TMMQQQ====') - 'Hello World!' + b'Hello World!' >>> base32_decode(b'GEZDGNBVGY======') - '123456' + b'123456' >>> base32_decode(b'ONXW2ZJANRXW4ZZAMNXW24DMMV4CA43UOJUW4ZY=') - 'some long complex string' + b'some long complex string' """ - - # decode the bytes from base32 - # then, decode the bytes-like object to return as a string - return base64.b32decode(encoded_bytes).decode("utf-8") + binary_chunks = "".join( + bin(B32_CHARSET.index(_d))[2:].zfill(5) + for _d in data.decode("utf-8").strip("=") + ) + binary_data = list(map("".join, zip(*[iter(binary_chunks)] * 8))) + return bytes("".join([chr(int(_d, 2)) for _d in binary_data]), "utf-8") if __name__ == "__main__": - test = "Hello World!" - encoded = base32_encode(test) - print(encoded) + import doctest - decoded = base32_decode(encoded) - print(decoded) + doctest.testmod() From 708d9061413a5c049d63b97b08540aa4867d5523 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Sep 2023 12:04:47 +0530 Subject: [PATCH 2310/2908] [pre-commit.ci] pre-commit autoupdate (#9067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.288 → v0.0.290](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.288...v0.0.290) * Update .pre-commit-config.yaml --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 722b408ee9e9..809b841d0ea3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.288 + rev: v0.0.291 hooks: - id: ruff From 882fb2f3c972e67303dd65873f05b8f3d58724e1 Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Sun, 24 Sep 2023 20:36:06 +1300 Subject: [PATCH 2311/2908] Rewrite of base85.py algorithm (#9069) * rewrite of base85.py * changed maps to list comprehension * Apply suggestions from code review Co-authored-by: Tianyi Zheng --------- Co-authored-by: Tianyi Zheng --- ciphers/base85.py | 57 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/ciphers/base85.py b/ciphers/base85.py index afd1aff79d11..f0228e5052dd 100644 --- a/ciphers/base85.py +++ b/ciphers/base85.py @@ -1,30 +1,55 @@ -import base64 +""" +Base85 (Ascii85) encoding and decoding +https://en.wikipedia.org/wiki/Ascii85 +""" -def base85_encode(string: str) -> bytes: + +def _base10_to_85(d: int) -> str: + return "".join(chr(d % 85 + 33)) + _base10_to_85(d // 85) if d > 0 else "" + + +def _base85_to_10(digits: list) -> int: + return sum(char * 85**i for i, char in enumerate(reversed(digits))) + + +def ascii85_encode(data: bytes) -> bytes: """ - >>> base85_encode("") + >>> ascii85_encode(b"") b'' - >>> base85_encode("12345") + >>> ascii85_encode(b"12345") b'0etOA2#' - >>> base85_encode("base 85") + >>> ascii85_encode(b"base 85") b'@UX=h+?24' """ - # encoded the input to a bytes-like object and then a85encode that - return base64.a85encode(string.encode("utf-8")) + binary_data = "".join(bin(ord(d))[2:].zfill(8) for d in data.decode("utf-8")) + null_values = (32 * ((len(binary_data) // 32) + 1) - len(binary_data)) // 8 + binary_data = binary_data.ljust(32 * ((len(binary_data) // 32) + 1), "0") + b85_chunks = [int(_s, 2) for _s in map("".join, zip(*[iter(binary_data)] * 32))] + result = "".join(_base10_to_85(chunk)[::-1] for chunk in b85_chunks) + return bytes(result[:-null_values] if null_values % 4 != 0 else result, "utf-8") -def base85_decode(a85encoded: bytes) -> str: +def ascii85_decode(data: bytes) -> bytes: """ - >>> base85_decode(b"") - '' - >>> base85_decode(b"0etOA2#") - '12345' - >>> base85_decode(b"@UX=h+?24") - 'base 85' + >>> ascii85_decode(b"") + b'' + >>> ascii85_decode(b"0etOA2#") + b'12345' + >>> ascii85_decode(b"@UX=h+?24") + b'base 85' """ - # a85decode the input into bytes and decode that into a human readable string - return base64.a85decode(a85encoded).decode("utf-8") + null_values = 5 * ((len(data) // 5) + 1) - len(data) + binary_data = data.decode("utf-8") + "u" * null_values + b85_chunks = map("".join, zip(*[iter(binary_data)] * 5)) + b85_segments = [[ord(_s) - 33 for _s in chunk] for chunk in b85_chunks] + results = [bin(_base85_to_10(chunk))[2::].zfill(32) for chunk in b85_segments] + char_chunks = [ + [chr(int(_s, 2)) for _s in map("".join, zip(*[iter(r)] * 8))] for r in results + ] + result = "".join("".join(char) for char in char_chunks) + offset = int(null_values % 5 == 0) + return bytes(result[: offset - null_values], "utf-8") if __name__ == "__main__": From 211247ef82fd54540e4cb832fbbb612ca5845700 Mon Sep 17 00:00:00 2001 From: Amir Lavasani Date: Mon, 25 Sep 2023 00:38:51 +0330 Subject: [PATCH 2312/2908] Add MFCC Feature Extraction Algorithm (#9057) * Add MFCC feature extraction to machine learning * Add standalone usage in comments * Apply suggestions from code review Co-authored-by: Christian Clauss * Delete empty junk file (#9062) * updating DIRECTORY.md * updating DIRECTORY.md * Delete empty junk file * updating DIRECTORY.md * Fix ruff errors * Fix more ruff errors --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> * [main] Fix typo due to auto review change * Add doctests for all functions * Add MFCC feature extraction to machine learning * Add standalone usage in comments * Apply suggestions from code review Co-authored-by: Christian Clauss * [main] Fix typo due to auto review change * Add doctests for all functions * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix some pre-commit issues * Update review issues * Remove types from docstring * Rename dct * Add mfcc docstring * Add typing to several functions * Apply suggestions from code review * Update mfcc.py * get_filter_points() -> tuple[np.ndarray, np.ndarray]: * algorithm --------- Co-authored-by: Christian Clauss Co-authored-by: Tianyi Zheng Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- machine_learning/mfcc.py | 479 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 machine_learning/mfcc.py diff --git a/machine_learning/mfcc.py b/machine_learning/mfcc.py new file mode 100644 index 000000000000..7ce8ceb50ff2 --- /dev/null +++ b/machine_learning/mfcc.py @@ -0,0 +1,479 @@ +""" +Mel Frequency Cepstral Coefficients (MFCC) Calculation + +MFCC is an algorithm widely used in audio and speech processing to represent the +short-term power spectrum of a sound signal in a more compact and +discriminative way. It is particularly popular in speech and audio processing +tasks such as speech recognition and speaker identification. + +How Mel Frequency Cepstral Coefficients are Calculated: +1. Preprocessing: + - Load an audio signal and normalize it to ensure that the values fall + within a specific range (e.g., between -1 and 1). + - Frame the audio signal into overlapping, fixed-length segments, typically + using a technique like windowing to reduce spectral leakage. + +2. Fourier Transform: + - Apply a Fast Fourier Transform (FFT) to each audio frame to convert it + from the time domain to the frequency domain. This results in a + representation of the audio frame as a sequence of frequency components. + +3. Power Spectrum: + - Calculate the power spectrum by taking the squared magnitude of each + frequency component obtained from the FFT. This step measures the energy + distribution across different frequency bands. + +4. Mel Filterbank: + - Apply a set of triangular filterbanks spaced in the Mel frequency scale + to the power spectrum. These filters mimic the human auditory system's + frequency response. Each filterbank sums the power spectrum values within + its band. + +5. Logarithmic Compression: + - Take the logarithm (typically base 10) of the filterbank values to + compress the dynamic range. This step mimics the logarithmic response of + the human ear to sound intensity. + +6. Discrete Cosine Transform (DCT): + - Apply the Discrete Cosine Transform to the log filterbank energies to + obtain the MFCC coefficients. This transformation helps decorrelate the + filterbank energies and captures the most important features of the audio + signal. + +7. Feature Extraction: + - Select a subset of the DCT coefficients to form the feature vector. + Often, the first few coefficients (e.g., 12-13) are used for most + applications. + +References: +- Mel-Frequency Cepstral Coefficients (MFCCs): + https://en.wikipedia.org/wiki/Mel-frequency_cepstrum +- Speech and Language Processing by Daniel Jurafsky & James H. Martin: + https://web.stanford.edu/~jurafsky/slp3/ +- Mel Frequency Cepstral Coefficient (MFCC) tutorial + http://practicalcryptography.com/miscellaneous/machine-learning + /guide-mel-frequency-cepstral-coefficients-mfccs/ + +Author: Amir Lavasani +""" + + +import logging + +import numpy as np +import scipy.fftpack as fft +from scipy.signal import get_window + +logging.basicConfig(filename=f"{__file__}.log", level=logging.INFO) + + +def mfcc( + audio: np.ndarray, + sample_rate: int, + ftt_size: int = 1024, + hop_length: int = 20, + mel_filter_num: int = 10, + dct_filter_num: int = 40, +) -> np.ndarray: + """ + Calculate Mel Frequency Cepstral Coefficients (MFCCs) from an audio signal. + + Args: + audio: The input audio signal. + sample_rate: The sample rate of the audio signal (in Hz). + ftt_size: The size of the FFT window (default is 1024). + hop_length: The hop length for frame creation (default is 20ms). + mel_filter_num: The number of Mel filters (default is 10). + dct_filter_num: The number of DCT filters (default is 40). + + Returns: + A matrix of MFCCs for the input audio. + + Raises: + ValueError: If the input audio is empty. + + Example: + >>> sample_rate = 44100 # Sample rate of 44.1 kHz + >>> duration = 2.0 # Duration of 1 second + >>> t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) + >>> audio = 0.5 * np.sin(2 * np.pi * 440.0 * t) # Generate a 440 Hz sine wave + >>> mfccs = mfcc(audio, sample_rate) + >>> mfccs.shape + (40, 101) + """ + logging.info(f"Sample rate: {sample_rate}Hz") + logging.info(f"Audio duration: {len(audio) / sample_rate}s") + logging.info(f"Audio min: {np.min(audio)}") + logging.info(f"Audio max: {np.max(audio)}") + + # normalize audio + audio_normalized = normalize(audio) + + logging.info(f"Normalized audio min: {np.min(audio_normalized)}") + logging.info(f"Normalized audio max: {np.max(audio_normalized)}") + + # frame audio into + audio_framed = audio_frames( + audio_normalized, sample_rate, ftt_size=ftt_size, hop_length=hop_length + ) + + logging.info(f"Framed audio shape: {audio_framed.shape}") + logging.info(f"First frame: {audio_framed[0]}") + + # convert to frequency domain + # For simplicity we will choose the Hanning window. + window = get_window("hann", ftt_size, fftbins=True) + audio_windowed = audio_framed * window + + logging.info(f"Windowed audio shape: {audio_windowed.shape}") + logging.info(f"First frame: {audio_windowed[0]}") + + audio_fft = calculate_fft(audio_windowed, ftt_size) + logging.info(f"fft audio shape: {audio_fft.shape}") + logging.info(f"First frame: {audio_fft[0]}") + + audio_power = calculate_signal_power(audio_fft) + logging.info(f"power audio shape: {audio_power.shape}") + logging.info(f"First frame: {audio_power[0]}") + + filters = mel_spaced_filterbank(sample_rate, mel_filter_num, ftt_size) + logging.info(f"filters shape: {filters.shape}") + + audio_filtered = np.dot(filters, np.transpose(audio_power)) + audio_log = 10.0 * np.log10(audio_filtered) + logging.info(f"audio_log shape: {audio_log.shape}") + + dct_filters = discrete_cosine_transform(dct_filter_num, mel_filter_num) + cepstral_coefficents = np.dot(dct_filters, audio_log) + + logging.info(f"cepstral_coefficents shape: {cepstral_coefficents.shape}") + return cepstral_coefficents + + +def normalize(audio: np.ndarray) -> np.ndarray: + """ + Normalize an audio signal by scaling it to have values between -1 and 1. + + Args: + audio: The input audio signal. + + Returns: + The normalized audio signal. + + Examples: + >>> audio = np.array([1, 2, 3, 4, 5]) + >>> normalized_audio = normalize(audio) + >>> np.max(normalized_audio) + 1.0 + >>> np.min(normalized_audio) + 0.2 + """ + # Divide the entire audio signal by the maximum absolute value + return audio / np.max(np.abs(audio)) + + +def audio_frames( + audio: np.ndarray, + sample_rate: int, + hop_length: int = 20, + ftt_size: int = 1024, +) -> np.ndarray: + """ + Split an audio signal into overlapping frames. + + Args: + audio: The input audio signal. + sample_rate: The sample rate of the audio signal. + hop_length: The length of the hopping (default is 20ms). + ftt_size: The size of the FFT window (default is 1024). + + Returns: + An array of overlapping frames. + + Examples: + >>> audio = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]*1000) + >>> sample_rate = 8000 + >>> frames = audio_frames(audio, sample_rate, hop_length=10, ftt_size=512) + >>> frames.shape + (126, 512) + """ + + hop_size = np.round(sample_rate * hop_length / 1000).astype(int) + + # Pad the audio signal to handle edge cases + audio = np.pad(audio, int(ftt_size / 2), mode="reflect") + + # Calculate the number of frames + frame_count = int((len(audio) - ftt_size) / hop_size) + 1 + + # Initialize an array to store the frames + frames = np.zeros((frame_count, ftt_size)) + + # Split the audio signal into frames + for n in range(frame_count): + frames[n] = audio[n * hop_size : n * hop_size + ftt_size] + + return frames + + +def calculate_fft(audio_windowed: np.ndarray, ftt_size: int = 1024) -> np.ndarray: + """ + Calculate the Fast Fourier Transform (FFT) of windowed audio data. + + Args: + audio_windowed: The windowed audio signal. + ftt_size: The size of the FFT (default is 1024). + + Returns: + The FFT of the audio data. + + Examples: + >>> audio_windowed = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) + >>> audio_fft = calculate_fft(audio_windowed, ftt_size=4) + >>> np.allclose(audio_fft[0], np.array([6.0+0.j, -1.5+0.8660254j, -1.5-0.8660254j])) + True + """ + # Transpose the audio data to have time in rows and channels in columns + audio_transposed = np.transpose(audio_windowed) + + # Initialize an array to store the FFT results + audio_fft = np.empty( + (int(1 + ftt_size // 2), audio_transposed.shape[1]), + dtype=np.complex64, + order="F", + ) + + # Compute FFT for each channel + for n in range(audio_fft.shape[1]): + audio_fft[:, n] = fft.fft(audio_transposed[:, n], axis=0)[: audio_fft.shape[0]] + + # Transpose the FFT results back to the original shape + return np.transpose(audio_fft) + + +def calculate_signal_power(audio_fft: np.ndarray) -> np.ndarray: + """ + Calculate the power of the audio signal from its FFT. + + Args: + audio_fft: The FFT of the audio signal. + + Returns: + The power of the audio signal. + + Examples: + >>> audio_fft = np.array([1+2j, 2+3j, 3+4j, 4+5j]) + >>> power = calculate_signal_power(audio_fft) + >>> np.allclose(power, np.array([5, 13, 25, 41])) + True + """ + # Calculate the power by squaring the absolute values of the FFT coefficients + return np.square(np.abs(audio_fft)) + + +def freq_to_mel(freq: float) -> float: + """ + Convert a frequency in Hertz to the mel scale. + + Args: + freq: The frequency in Hertz. + + Returns: + The frequency in mel scale. + + Examples: + >>> round(freq_to_mel(1000), 2) + 999.99 + """ + # Use the formula to convert frequency to the mel scale + return 2595.0 * np.log10(1.0 + freq / 700.0) + + +def mel_to_freq(mels: float) -> float: + """ + Convert a frequency in the mel scale to Hertz. + + Args: + mels: The frequency in mel scale. + + Returns: + The frequency in Hertz. + + Examples: + >>> round(mel_to_freq(999.99), 2) + 1000.01 + """ + # Use the formula to convert mel scale to frequency + return 700.0 * (10.0 ** (mels / 2595.0) - 1.0) + + +def mel_spaced_filterbank( + sample_rate: int, mel_filter_num: int = 10, ftt_size: int = 1024 +) -> np.ndarray: + """ + Create a Mel-spaced filter bank for audio processing. + + Args: + sample_rate: The sample rate of the audio. + mel_filter_num: The number of mel filters (default is 10). + ftt_size: The size of the FFT (default is 1024). + + Returns: + Mel-spaced filter bank. + + Examples: + >>> round(mel_spaced_filterbank(8000, 10, 1024)[0][1], 10) + 0.0004603981 + """ + freq_min = 0 + freq_high = sample_rate // 2 + + logging.info(f"Minimum frequency: {freq_min}") + logging.info(f"Maximum frequency: {freq_high}") + + # Calculate filter points and mel frequencies + filter_points, mel_freqs = get_filter_points( + sample_rate, + freq_min, + freq_high, + mel_filter_num, + ftt_size, + ) + + filters = get_filters(filter_points, ftt_size) + + # normalize filters + # taken from the librosa library + enorm = 2.0 / (mel_freqs[2 : mel_filter_num + 2] - mel_freqs[:mel_filter_num]) + return filters * enorm[:, np.newaxis] + + +def get_filters(filter_points: np.ndarray, ftt_size: int) -> np.ndarray: + """ + Generate filters for audio processing. + + Args: + filter_points: A list of filter points. + ftt_size: The size of the FFT. + + Returns: + A matrix of filters. + + Examples: + >>> get_filters(np.array([0, 20, 51, 95, 161, 256], dtype=int), 512).shape + (4, 257) + """ + num_filters = len(filter_points) - 2 + filters = np.zeros((num_filters, int(ftt_size / 2) + 1)) + + for n in range(num_filters): + start = filter_points[n] + mid = filter_points[n + 1] + end = filter_points[n + 2] + + # Linearly increase values from 0 to 1 + filters[n, start:mid] = np.linspace(0, 1, mid - start) + + # Linearly decrease values from 1 to 0 + filters[n, mid:end] = np.linspace(1, 0, end - mid) + + return filters + + +def get_filter_points( + sample_rate: int, + freq_min: int, + freq_high: int, + mel_filter_num: int = 10, + ftt_size: int = 1024, +) -> tuple[np.ndarray, np.ndarray]: + """ + Calculate the filter points and frequencies for mel frequency filters. + + Args: + sample_rate: The sample rate of the audio. + freq_min: The minimum frequency in Hertz. + freq_high: The maximum frequency in Hertz. + mel_filter_num: The number of mel filters (default is 10). + ftt_size: The size of the FFT (default is 1024). + + Returns: + Filter points and corresponding frequencies. + + Examples: + >>> filter_points = get_filter_points(8000, 0, 4000, mel_filter_num=4, ftt_size=512) + >>> filter_points[0] + array([ 0, 20, 51, 95, 161, 256]) + >>> filter_points[1] + array([ 0. , 324.46707094, 799.33254207, 1494.30973963, + 2511.42581671, 4000. ]) + """ + # Convert minimum and maximum frequencies to mel scale + fmin_mel = freq_to_mel(freq_min) + fmax_mel = freq_to_mel(freq_high) + + logging.info(f"MEL min: {fmin_mel}") + logging.info(f"MEL max: {fmax_mel}") + + # Generate equally spaced mel frequencies + mels = np.linspace(fmin_mel, fmax_mel, num=mel_filter_num + 2) + + # Convert mel frequencies back to Hertz + freqs = mel_to_freq(mels) + + # Calculate filter points as integer values + filter_points = np.floor((ftt_size + 1) / sample_rate * freqs).astype(int) + + return filter_points, freqs + + +def discrete_cosine_transform(dct_filter_num: int, filter_num: int) -> np.ndarray: + """ + Compute the Discrete Cosine Transform (DCT) basis matrix. + + Args: + dct_filter_num: The number of DCT filters to generate. + filter_num: The number of the fbank filters. + + Returns: + The DCT basis matrix. + + Examples: + >>> round(discrete_cosine_transform(3, 5)[0][0], 5) + 0.44721 + """ + basis = np.empty((dct_filter_num, filter_num)) + basis[0, :] = 1.0 / np.sqrt(filter_num) + + samples = np.arange(1, 2 * filter_num, 2) * np.pi / (2.0 * filter_num) + + for i in range(1, dct_filter_num): + basis[i, :] = np.cos(i * samples) * np.sqrt(2.0 / filter_num) + + return basis + + +def example(wav_file_path: str = "./path-to-file/sample.wav") -> np.ndarray: + """ + Example function to calculate Mel Frequency Cepstral Coefficients + (MFCCs) from an audio file. + + Args: + wav_file_path: The path to the WAV audio file. + + Returns: + np.ndarray: The computed MFCCs for the audio. + """ + from scipy.io import wavfile + + # Load the audio from the WAV file + sample_rate, audio = wavfile.read(wav_file_path) + + # Calculate MFCCs + return mfcc(audio, sample_rate) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From eace4cea32b831a1683b4c431379f0cd7b9061db Mon Sep 17 00:00:00 2001 From: gudlu1925 <120262240+gudlu1925@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:14:06 +0530 Subject: [PATCH 2313/2908] Added Coulomb_Law (#8714) * Create coulomb_law.py * Update coulomb_law.py * Update coulomb_law.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename coulomb_law.py to coulombs_law.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update coulombs_law.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update coulombs_law.py * Update coulombs_law.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update coulombs_law.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update coulombs_law.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- physics/coulombs_law.py | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 physics/coulombs_law.py diff --git a/physics/coulombs_law.py b/physics/coulombs_law.py new file mode 100644 index 000000000000..252e8ec0f74e --- /dev/null +++ b/physics/coulombs_law.py @@ -0,0 +1,42 @@ +""" +Coulomb's law states that the magnitude of the electrostatic force of attraction +or repulsion between two point charges is directly proportional to the product +of the magnitudes of charges and inversely proportional to the square of the +distance between them. + +F = k * q1 * q2 / r^2 + +k is Coulomb's constant and equals 1/(4π*ε0) +q1 is charge of first body (C) +q2 is charge of second body (C) +r is distance between two charged bodies (m) + +Reference: https://en.wikipedia.org/wiki/Coulomb%27s_law +""" + + +def coulombs_law(q1: float, q2: float, radius: float) -> float: + """ + Calculate the electrostatic force of attraction or repulsion + between two point charges + + >>> coulombs_law(15.5, 20, 15) + 12382849136.06 + >>> coulombs_law(1, 15, 5) + 5392531075.38 + >>> coulombs_law(20, -50, 15) + -39944674632.44 + >>> coulombs_law(-5, -8, 10) + 3595020716.92 + >>> coulombs_law(50, 100, 50) + 17975103584.6 + """ + if radius <= 0: + raise ValueError("The radius is always a positive non zero integer") + return round(((8.9875517923 * 10**9) * q1 * q2) / (radius**2), 2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b2e186f4b769ae98d04f7f2408d3ac86da44c06f Mon Sep 17 00:00:00 2001 From: Okza Pradhana Date: Wed, 27 Sep 2023 13:06:19 +0700 Subject: [PATCH 2314/2908] feat(maths): add function to perform calculation (#6602) * feat(maths): add function to perform calculation - Add single function to calculate sum of two positive numbers using bitwise operator * docs: add wikipedia url as explanation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Caeden Perelli-Harris * Update sum_of_two_positive_numbers_bitwise.py * Update sum_of_two_positive_numbers_bitwise.py * Update sum_of_two_positive_numbers_bitwise.py --------- Co-authored-by: Okza Pradhana Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng Co-authored-by: Caeden Perelli-Harris --- maths/sum_of_two_positive_numbers_bitwise.py | 55 ++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 maths/sum_of_two_positive_numbers_bitwise.py diff --git a/maths/sum_of_two_positive_numbers_bitwise.py b/maths/sum_of_two_positive_numbers_bitwise.py new file mode 100644 index 000000000000..70eaf6887b64 --- /dev/null +++ b/maths/sum_of_two_positive_numbers_bitwise.py @@ -0,0 +1,55 @@ +""" +Calculates the sum of two non-negative integers using bitwise operators +Wikipedia explanation: https://en.wikipedia.org/wiki/Binary_number +""" + + +def bitwise_addition_recursive(number: int, other_number: int) -> int: + """ + >>> bitwise_addition_recursive(4, 5) + 9 + >>> bitwise_addition_recursive(8, 9) + 17 + >>> bitwise_addition_recursive(0, 4) + 4 + >>> bitwise_addition_recursive(4.5, 9) + Traceback (most recent call last): + ... + TypeError: Both arguments MUST be integers! + >>> bitwise_addition_recursive('4', 9) + Traceback (most recent call last): + ... + TypeError: Both arguments MUST be integers! + >>> bitwise_addition_recursive('4.5', 9) + Traceback (most recent call last): + ... + TypeError: Both arguments MUST be integers! + >>> bitwise_addition_recursive(-1, 9) + Traceback (most recent call last): + ... + ValueError: Both arguments MUST be non-negative! + >>> bitwise_addition_recursive(1, -9) + Traceback (most recent call last): + ... + ValueError: Both arguments MUST be non-negative! + """ + + if not isinstance(number, int) or not isinstance(other_number, int): + raise TypeError("Both arguments MUST be integers!") + + if number < 0 or other_number < 0: + raise ValueError("Both arguments MUST be non-negative!") + + bitwise_sum = number ^ other_number + carry = number & other_number + + if carry == 0: + return bitwise_sum + + return bitwise_addition_recursive(bitwise_sum, carry << 1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 84ec9414e45380a5e946d4f73b921b274ecd4be7 Mon Sep 17 00:00:00 2001 From: thor-harsh <105957576+thor-harsh@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:01:42 +0530 Subject: [PATCH 2315/2908] Update k_means_clust.py (#8996) * Update k_means_clust.py * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- machine_learning/k_means_clust.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 7c8142aab878..d93c5addf2ee 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -11,10 +11,10 @@ - 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 + - heterogeneity , empty list that will be filled with heterogeneity values if passed to kmeans func. Usage: - 1. define 'k' value, 'X' features array and 'hetrogeneity' empty list + 1. define 'k' value, 'X' features array and 'heterogeneity' empty list 2. create initial_centroids, initial_centroids = get_initial_centroids( X, @@ -31,8 +31,8 @@ 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. + 4. Plot the loss function and heterogeneity values for every iteration saved in + heterogeneity list. plot_heterogeneity( heterogeneity, k @@ -198,13 +198,10 @@ def report_generator( df: pd.DataFrame, clustering_variables: np.ndarray, fill_missing_report=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 + Generates a clustering report. This function takes 2 arguments as input: + df - dataframe with predicted cluster column + fill_missing_report - dictionary of rules on how we are going to fill in missing + values for final generated report (not included in modelling); >>> data = pd.DataFrame() >>> data['numbers'] = [1, 2, 3] >>> data['col1'] = [0.5, 2.5, 4.5] @@ -306,10 +303,10 @@ def report_generator( a.columns = report.columns # rename columns to match report report = report.drop( report[report.Type == "count"].index - ) # drop count values except cluster size + ) # drop count values except for cluster size report = pd.concat( [report, a, clustersize, clusterproportion], axis=0 - ) # concat report with clustert size and nan values + ) # concat report with cluster size and nan values report["Mark"] = report["Features"].isin(clustering_variables) cols = report.columns.tolist() cols = cols[0:2] + cols[-1:] + cols[2:-1] From 5830b29e7ecf5437ce46bcdefda88eedea693043 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 27 Sep 2023 08:00:34 -0400 Subject: [PATCH 2316/2908] Fix `mypy` errors in `erosion_operation.py` (#8603) * updating DIRECTORY.md * Fix mypy errors in erosion_operation.py * Rename functions to use snake case * updating DIRECTORY.md * updating DIRECTORY.md * Replace raw file string with pathlib Path * Fix function name in erosion_operation.py doctest --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../erosion_operation.py | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/digital_image_processing/morphological_operations/erosion_operation.py b/digital_image_processing/morphological_operations/erosion_operation.py index c0e1ef847237..53001da83468 100644 --- a/digital_image_processing/morphological_operations/erosion_operation.py +++ b/digital_image_processing/morphological_operations/erosion_operation.py @@ -1,34 +1,37 @@ +from pathlib import Path + import numpy as np from PIL import Image -def rgb2gray(rgb: np.array) -> np.array: +def rgb_to_gray(rgb: np.ndarray) -> np.ndarray: """ Return gray image from rgb image - >>> rgb2gray(np.array([[[127, 255, 0]]])) + + >>> rgb_to_gray(np.array([[[127, 255, 0]]])) array([[187.6453]]) - >>> rgb2gray(np.array([[[0, 0, 0]]])) + >>> rgb_to_gray(np.array([[[0, 0, 0]]])) array([[0.]]) - >>> rgb2gray(np.array([[[2, 4, 1]]])) + >>> rgb_to_gray(np.array([[[2, 4, 1]]])) array([[3.0598]]) - >>> rgb2gray(np.array([[[26, 255, 14], [5, 147, 20], [1, 200, 0]]])) + >>> rgb_to_gray(np.array([[[26, 255, 14], [5, 147, 20], [1, 200, 0]]])) array([[159.0524, 90.0635, 117.6989]]) """ r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2] return 0.2989 * r + 0.5870 * g + 0.1140 * b -def gray2binary(gray: np.array) -> np.array: +def gray_to_binary(gray: np.ndarray) -> np.ndarray: """ Return binary image from gray image - >>> gray2binary(np.array([[127, 255, 0]])) + >>> gray_to_binary(np.array([[127, 255, 0]])) array([[False, True, False]]) - >>> gray2binary(np.array([[0]])) + >>> gray_to_binary(np.array([[0]])) array([[False]]) - >>> gray2binary(np.array([[26.2409, 4.9315, 1.4729]])) + >>> gray_to_binary(np.array([[26.2409, 4.9315, 1.4729]])) array([[False, False, False]]) - >>> gray2binary(np.array([[26, 255, 14], [5, 147, 20], [1, 200, 0]])) + >>> gray_to_binary(np.array([[26, 255, 14], [5, 147, 20], [1, 200, 0]])) array([[False, True, False], [False, True, False], [False, True, False]]) @@ -36,9 +39,10 @@ def gray2binary(gray: np.array) -> np.array: return (gray > 127) & (gray <= 255) -def erosion(image: np.array, kernel: np.array) -> np.array: +def erosion(image: np.ndarray, kernel: np.ndarray) -> np.ndarray: """ Return eroded image + >>> erosion(np.array([[True, True, False]]), np.array([[0, 1, 0]])) array([[False, False, False]]) >>> erosion(np.array([[True, False, False]]), np.array([[1, 1, 0]])) @@ -62,14 +66,17 @@ def erosion(image: np.array, kernel: np.array) -> np.array: return output -# kernel to be applied -structuring_element = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]) - if __name__ == "__main__": # read original image - image = np.array(Image.open(r"..\image_data\lena.jpg")) + lena_path = Path(__file__).resolve().parent / "image_data" / "lena.jpg" + lena = np.array(Image.open(lena_path)) + + # kernel to be applied + structuring_element = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]) + # Apply erosion operation to a binary image - output = erosion(gray2binary(rgb2gray(image)), structuring_element) + output = erosion(gray_to_binary(rgb_to_gray(lena)), structuring_element) + # Save the output image pil_img = Image.fromarray(output).convert("RGB") pil_img.save("result_erosion.png") From 76767d2f09d15aeff0a54cfc44652207eda2314e Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 27 Sep 2023 08:01:18 -0400 Subject: [PATCH 2317/2908] Consolidate the two existing kNN implementations (#8903) * Add type hints to k_nearest_neighbours.py * Refactor k_nearest_neighbours.py into class * Add documentation to k_nearest_neighbours.py * Use heap-based priority queue for k_nearest_neighbours.py * Delete knn_sklearn.py * updating DIRECTORY.md * Use optional args in k_nearest_neighbours.py for demo purposes * Fix wrong function arg in k_nearest_neighbours.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 - machine_learning/k_nearest_neighbours.py | 128 ++++++++++++++--------- machine_learning/knn_sklearn.py | 31 ------ 3 files changed, 79 insertions(+), 81 deletions(-) delete mode 100644 machine_learning/knn_sklearn.py diff --git a/DIRECTORY.md b/DIRECTORY.md index d81e4ec1ee83..902999460fe5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -507,7 +507,6 @@ * [Gradient Descent](machine_learning/gradient_descent.py) * [K Means Clust](machine_learning/k_means_clust.py) * [K Nearest Neighbours](machine_learning/k_nearest_neighbours.py) - * [Knn Sklearn](machine_learning/knn_sklearn.py) * [Linear Discriminant Analysis](machine_learning/linear_discriminant_analysis.py) * [Linear Regression](machine_learning/linear_regression.py) * Local Weighted Learning diff --git a/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py index 2a90cfe5987a..a43757c5c20e 100644 --- a/machine_learning/k_nearest_neighbours.py +++ b/machine_learning/k_nearest_neighbours.py @@ -1,58 +1,88 @@ +""" +k-Nearest Neighbours (kNN) is a simple non-parametric supervised learning +algorithm used for classification. Given some labelled training data, a given +point is classified using its k nearest neighbours according to some distance +metric. The most commonly occurring label among the neighbours becomes the label +of the given point. In effect, the label of the given point is decided by a +majority vote. + +This implementation uses the commonly used Euclidean distance metric, but other +distance metrics can also be used. + +Reference: https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm +""" + from collections import Counter +from heapq import nsmallest import numpy as np 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 classified - - >>> 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 occurring class among them - # is the class into which the point is classified - result = Counter(votes).most_common(1)[0][0] - return classes[result] + +class KNN: + def __init__( + self, + train_data: np.ndarray[float], + train_target: np.ndarray[int], + class_labels: list[str], + ) -> None: + """ + Create a kNN classifier using the given training data and class labels + """ + self.data = zip(train_data, train_target) + self.labels = class_labels + + @staticmethod + def _euclidean_distance(a: np.ndarray[float], b: np.ndarray[float]) -> float: + """ + Calculate the Euclidean distance between two points + >>> KNN._euclidean_distance(np.array([0, 0]), np.array([3, 4])) + 5.0 + >>> KNN._euclidean_distance(np.array([1, 2, 3]), np.array([1, 8, 11])) + 10.0 + """ + return np.linalg.norm(a - b) + + def classify(self, pred_point: np.ndarray[float], k: int = 5) -> str: + """ + Classify a given point using the kNN algorithm + >>> train_X = np.array( + ... [[0, 0], [1, 0], [0, 1], [0.5, 0.5], [3, 3], [2, 3], [3, 2]] + ... ) + >>> train_y = np.array([0, 0, 0, 0, 1, 1, 1]) + >>> classes = ['A', 'B'] + >>> knn = KNN(train_X, train_y, classes) + >>> point = np.array([1.2, 1.2]) + >>> knn.classify(point) + 'A' + """ + # Distances of all points from the point to be classified + distances = ( + (self._euclidean_distance(data_point[0], pred_point), data_point[1]) + for data_point in self.data + ) + + # Choosing k points with the shortest distances + votes = (i[1] for i in nsmallest(k, distances)) + + # Most commonly occurring class is the one into which the point is classified + result = Counter(votes).most_common(1)[0][0] + return self.labels[result] if __name__ == "__main__": - print(classifier(X_train, y_train, classes, [4.4, 3.1, 1.3, 1.4])) + import doctest + + doctest.testmod() + + iris = datasets.load_iris() + + X = np.array(iris["data"]) + y = np.array(iris["target"]) + iris_classes = iris["target_names"] + + X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0) + iris_point = np.array([4.4, 3.1, 1.3, 1.4]) + classifier = KNN(X_train, y_train, iris_classes) + print(classifier.classify(iris_point, k=3)) diff --git a/machine_learning/knn_sklearn.py b/machine_learning/knn_sklearn.py deleted file mode 100644 index 4a621a4244b6..000000000000 --- a/machine_learning/knn_sklearn.py +++ /dev/null @@ -1,31 +0,0 @@ -from sklearn.datasets import load_iris -from sklearn.model_selection import train_test_split -from sklearn.neighbors import KNeighborsClassifier - -# Load iris file -iris = load_iris() -iris.keys() - - -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( - 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( - f"\nNew array: \n {X_new}\n\nTarget Names Prediction: \n" - f" {iris['target_names'][prediction]}" -) From f9b8759ba82cd7ca4e4a99b9bc9b661ace5a93cc Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 27 Sep 2023 09:54:40 -0400 Subject: [PATCH 2318/2908] Move bitwise add (#9097) * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Move and rename maths/sum_of_two_positive_numbers_bitwise.py * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 +++ .../bitwise_addition_recursive.py | 0 2 files changed, 3 insertions(+) rename maths/sum_of_two_positive_numbers_bitwise.py => bit_manipulation/bitwise_addition_recursive.py (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 902999460fe5..e596d96e5e83 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -43,6 +43,7 @@ * [Binary Shifts](bit_manipulation/binary_shifts.py) * [Binary Twos Complement](bit_manipulation/binary_twos_complement.py) * [Binary Xor Operator](bit_manipulation/binary_xor_operator.py) + * [Bitwise Addition Recursive](bit_manipulation/bitwise_addition_recursive.py) * [Count 1S Brian Kernighan Method](bit_manipulation/count_1s_brian_kernighan_method.py) * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py) * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py) @@ -514,6 +515,7 @@ * [Logistic Regression](machine_learning/logistic_regression.py) * Lstm * [Lstm Prediction](machine_learning/lstm/lstm_prediction.py) + * [Mfcc](machine_learning/mfcc.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polynomial Regression](machine_learning/polynomial_regression.py) * [Scoring Functions](machine_learning/scoring_functions.py) @@ -752,6 +754,7 @@ * [Basic Orbital Capture](physics/basic_orbital_capture.py) * [Casimir Effect](physics/casimir_effect.py) * [Centripetal Force](physics/centripetal_force.py) + * [Coulombs Law](physics/coulombs_law.py) * [Grahams Law](physics/grahams_law.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) * [Hubble Parameter](physics/hubble_parameter.py) diff --git a/maths/sum_of_two_positive_numbers_bitwise.py b/bit_manipulation/bitwise_addition_recursive.py similarity index 100% rename from maths/sum_of_two_positive_numbers_bitwise.py rename to bit_manipulation/bitwise_addition_recursive.py From 38c2b839819549d1ab8566675fab09db449875cc Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Wed, 27 Sep 2023 19:26:01 +0530 Subject: [PATCH 2319/2908] Deleted euclidean_gcd.py. Fixes#8063 (#9108) --- maths/euclidean_gcd.py | 47 ------------------------------------------ 1 file changed, 47 deletions(-) delete mode 100644 maths/euclidean_gcd.py diff --git a/maths/euclidean_gcd.py b/maths/euclidean_gcd.py deleted file mode 100644 index de4b250243db..000000000000 --- a/maths/euclidean_gcd.py +++ /dev/null @@ -1,47 +0,0 @@ -""" https://en.wikipedia.org/wiki/Euclidean_algorithm """ - - -def euclidean_gcd(a: int, b: int) -> int: - """ - Examples: - >>> euclidean_gcd(3, 5) - 1 - - >>> euclidean_gcd(6, 3) - 3 - """ - while b: - 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(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__": - main() From 35dd529c85fc433e0780cdaff586c684208aa1b7 Mon Sep 17 00:00:00 2001 From: Hetarth Jain Date: Thu, 28 Sep 2023 23:54:46 +0530 Subject: [PATCH 2320/2908] Returning Index instead of boolean in knuth_morris_pratt (kmp) function, making it compatible with str.find(). (#9083) * Update knuth_morris_pratt.py - changed Boolean to Index * Update knuth_morris_pratt.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update knuth_morris_pratt.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update knuth_morris_pratt.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update back_propagation_neural_network.py * Update back_propagation_neural_network.py * Update strings/knuth_morris_pratt.py * Update knuth_morris_pratt.py * Update knuth_morris_pratt.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- strings/knuth_morris_pratt.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/strings/knuth_morris_pratt.py b/strings/knuth_morris_pratt.py index a488c171a93b..8a04eb2532c0 100644 --- a/strings/knuth_morris_pratt.py +++ b/strings/knuth_morris_pratt.py @@ -1,7 +1,7 @@ from __future__ import annotations -def kmp(pattern: str, text: str) -> bool: +def knuth_morris_pratt(text: str, pattern: str) -> int: """ The Knuth-Morris-Pratt Algorithm for finding a pattern within a piece of text with complexity O(n + m) @@ -14,6 +14,12 @@ def kmp(pattern: str, text: str) -> bool: 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 + >>> kmp = "knuth_morris_pratt" + >>> all( + ... knuth_morris_pratt(kmp, s) == kmp.find(s) + ... for s in ("kn", "h_m", "rr", "tt", "not there") + ... ) + True """ # 1) Construct the failure array @@ -24,7 +30,7 @@ def kmp(pattern: str, text: str) -> bool: while i < len(text): if pattern[j] == text[i]: if j == (len(pattern) - 1): - return True + return i - j j += 1 # if this is a prefix in our pattern @@ -33,7 +39,7 @@ def kmp(pattern: str, text: str) -> bool: j = failure[j - 1] continue i += 1 - return False + return -1 def get_failure_array(pattern: str) -> list[int]: @@ -57,27 +63,38 @@ def get_failure_array(pattern: str) -> list[int]: if __name__ == "__main__": + import doctest + + doctest.testmod() + # Test 1) pattern = "abc1abc12" text1 = "alskfjaldsabc1abc1abc12k23adsfabcabc" text2 = "alskfjaldsk23adsfabcabc" - assert kmp(pattern, text1) and not kmp(pattern, text2) + assert knuth_morris_pratt(text1, pattern) and knuth_morris_pratt(text2, pattern) # Test 2) pattern = "ABABX" text = "ABABZABABYABABX" - assert kmp(pattern, text) + assert knuth_morris_pratt(text, pattern) # Test 3) pattern = "AAAB" text = "ABAAAAAB" - assert kmp(pattern, text) + assert knuth_morris_pratt(text, pattern) # Test 4) pattern = "abcdabcy" text = "abcxabcdabxabcdabcdabcy" - assert kmp(pattern, text) + assert knuth_morris_pratt(text, pattern) + + # Test 5) -> Doctests + kmp = "knuth_morris_pratt" + assert all( + knuth_morris_pratt(kmp, s) == kmp.find(s) + for s in ("kn", "h_m", "rr", "tt", "not there") + ) - # Test 5) + # Test 6) pattern = "aabaabaaa" assert get_failure_array(pattern) == [0, 1, 0, 1, 2, 3, 4, 5, 2] From 467903aa33ad746262bd46d803231d0930131197 Mon Sep 17 00:00:00 2001 From: Belhadj Ahmed Walid <80895522+BAW2501@users.noreply.github.com> Date: Sat, 30 Sep 2023 05:33:13 +0100 Subject: [PATCH 2321/2908] added smith waterman algorithm (#9001) * added smith waterman algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * descriptive names for the parameters a and b * doctesting lowercase upcase empty string cases * updated block quot,fixed traceback and doctests * shorter block quote Co-authored-by: Tianyi Zheng * global vars to func params,more doctests * updated doctests * user access to SW params * formating --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- dynamic_programming/smith_waterman.py | 193 ++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 dynamic_programming/smith_waterman.py diff --git a/dynamic_programming/smith_waterman.py b/dynamic_programming/smith_waterman.py new file mode 100644 index 000000000000..4c5d58379f07 --- /dev/null +++ b/dynamic_programming/smith_waterman.py @@ -0,0 +1,193 @@ +""" +https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorithm +The Smith-Waterman algorithm is a dynamic programming algorithm used for sequence +alignment. It is particularly useful for finding similarities between two sequences, +such as DNA or protein sequences. In this implementation, gaps are penalized +linearly, meaning that the score is reduced by a fixed amount for each gap introduced +in the alignment. However, it's important to note that the Smith-Waterman algorithm +supports other gap penalty methods as well. +""" + + +def score_function( + source_char: str, + target_char: str, + match: int = 1, + mismatch: int = -1, + gap: int = -2, +) -> int: + """ + Calculate the score for a character pair based on whether they match or mismatch. + Returns 1 if the characters match, -1 if they mismatch, and -2 if either of the + characters is a gap. + >>> score_function('A', 'A') + 1 + >>> score_function('A', 'C') + -1 + >>> score_function('-', 'A') + -2 + >>> score_function('A', '-') + -2 + >>> score_function('-', '-') + -2 + """ + if "-" in (source_char, target_char): + return gap + return match if source_char == target_char else mismatch + + +def smith_waterman( + query: str, + subject: str, + match: int = 1, + mismatch: int = -1, + gap: int = -2, +) -> list[list[int]]: + """ + Perform the Smith-Waterman local sequence alignment algorithm. + Returns a 2D list representing the score matrix. Each value in the matrix + corresponds to the score of the best local alignment ending at that point. + >>> smith_waterman('ACAC', 'CA') + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]] + >>> smith_waterman('acac', 'ca') + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]] + >>> smith_waterman('ACAC', 'ca') + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]] + >>> smith_waterman('acac', 'CA') + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]] + >>> smith_waterman('ACAC', '') + [[0], [0], [0], [0], [0]] + >>> smith_waterman('', 'CA') + [[0, 0, 0]] + >>> smith_waterman('ACAC', 'CA') + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]] + + >>> smith_waterman('acac', 'ca') + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]] + + >>> smith_waterman('ACAC', 'ca') + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]] + + >>> smith_waterman('acac', 'CA') + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]] + + >>> smith_waterman('ACAC', '') + [[0], [0], [0], [0], [0]] + + >>> smith_waterman('', 'CA') + [[0, 0, 0]] + + >>> smith_waterman('AGT', 'AGT') + [[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3]] + + >>> smith_waterman('AGT', 'GTA') + [[0, 0, 0, 0], [0, 0, 0, 1], [0, 1, 0, 0], [0, 0, 2, 0]] + + >>> smith_waterman('AGT', 'GTC') + [[0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 2, 0]] + + >>> smith_waterman('AGT', 'G') + [[0, 0], [0, 0], [0, 1], [0, 0]] + + >>> smith_waterman('G', 'AGT') + [[0, 0, 0, 0], [0, 0, 1, 0]] + + >>> smith_waterman('AGT', 'AGTCT') + [[0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 2, 0, 0, 0], [0, 0, 0, 3, 1, 1]] + + >>> smith_waterman('AGTCT', 'AGT') + [[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3], [0, 0, 0, 1], [0, 0, 0, 1]] + + >>> smith_waterman('AGTCT', 'GTC') + [[0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3], [0, 0, 1, 1]] + """ + # make both query and subject uppercase + query = query.upper() + subject = subject.upper() + + # Initialize score matrix + m = len(query) + n = len(subject) + score = [[0] * (n + 1) for _ in range(m + 1)] + kwargs = {"match": match, "mismatch": mismatch, "gap": gap} + + for i in range(1, m + 1): + for j in range(1, n + 1): + # Calculate scores for each cell + match = score[i - 1][j - 1] + score_function( + query[i - 1], subject[j - 1], **kwargs + ) + delete = score[i - 1][j] + gap + insert = score[i][j - 1] + gap + + # Take maximum score + score[i][j] = max(0, match, delete, insert) + + return score + + +def traceback(score: list[list[int]], query: str, subject: str) -> str: + r""" + Perform traceback to find the optimal local alignment. + Starts from the highest scoring cell in the matrix and traces back recursively + until a 0 score is found. Returns the alignment strings. + >>> traceback([[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]], 'ACAC', 'CA') + 'CA\nCA' + >>> traceback([[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]], 'acac', 'ca') + 'CA\nCA' + >>> traceback([[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]], 'ACAC', 'ca') + 'CA\nCA' + >>> traceback([[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 2], [0, 1, 0]], 'acac', 'CA') + 'CA\nCA' + >>> traceback([[0, 0, 0]], 'ACAC', '') + '' + """ + # make both query and subject uppercase + query = query.upper() + subject = subject.upper() + # find the indices of the maximum value in the score matrix + max_value = float("-inf") + i_max = j_max = 0 + for i, row in enumerate(score): + for j, value in enumerate(row): + if value > max_value: + max_value = value + i_max, j_max = i, j + # Traceback logic to find optimal alignment + i = i_max + j = j_max + align1 = "" + align2 = "" + gap = score_function("-", "-") + # guard against empty query or subject + if i == 0 or j == 0: + return "" + while i > 0 and j > 0: + if score[i][j] == score[i - 1][j - 1] + score_function( + query[i - 1], subject[j - 1] + ): + # optimal path is a diagonal take both letters + align1 = query[i - 1] + align1 + align2 = subject[j - 1] + align2 + i -= 1 + j -= 1 + elif score[i][j] == score[i - 1][j] + gap: + # optimal path is a vertical + align1 = query[i - 1] + align1 + align2 = f"-{align2}" + i -= 1 + else: + # optimal path is a horizontal + align1 = f"-{align1}" + align2 = subject[j - 1] + align2 + j -= 1 + + return f"{align1}\n{align2}" + + +if __name__ == "__main__": + query = "HEAGAWGHEE" + subject = "PAWHEAE" + + score = smith_waterman(query, subject, match=1, mismatch=-1, gap=-2) + print(traceback(score, query, subject)) From dec96438be1a165eaa300a4d6df33e338b4e44c6 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sat, 30 Sep 2023 05:57:56 +0100 Subject: [PATCH 2322/2908] Create word search algorithm (#8906) * feat(other): Create word_search algorithm * updating DIRECTORY.md * doc(word_search): Link to wikipedia article * Apply suggestions from code review Co-authored-by: Tianyi Zheng * Update word_search.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- DIRECTORY.md | 1 + other/word_search.py | 396 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 397 insertions(+) create mode 100644 other/word_search.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e596d96e5e83..aabbf27512ce 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -747,6 +747,7 @@ * [Scoring Algorithm](other/scoring_algorithm.py) * [Sdes](other/sdes.py) * [Tower Of Hanoi](other/tower_of_hanoi.py) + * [Word Search](other/word_search.py) ## Physics * [Altitude Pressure](physics/altitude_pressure.py) diff --git a/other/word_search.py b/other/word_search.py new file mode 100644 index 000000000000..a4796e220c7c --- /dev/null +++ b/other/word_search.py @@ -0,0 +1,396 @@ +""" +Creates a random wordsearch with eight different directions +that are best described as compass locations. + +@ https://en.wikipedia.org/wiki/Word_search +""" + + +from random import choice, randint, shuffle + +# The words to display on the word search - +# can be made dynamic by randonly selecting a certain number of +# words from a predefined word file, while ensuring the character +# count fits within the matrix size (n x m) +WORDS = ["cat", "dog", "snake", "fish"] + +WIDTH = 10 +HEIGHT = 10 + + +class WordSearch: + """ + >>> ws = WordSearch(WORDS, WIDTH, HEIGHT) + >>> ws.board # doctest: +ELLIPSIS + [[None, ..., None], ..., [None, ..., None]] + >>> ws.generate_board() + """ + + def __init__(self, words: list[str], width: int, height: int) -> None: + self.words = words + self.width = width + self.height = height + + # Board matrix holding each letter + self.board: list[list[str | None]] = [[None] * width for _ in range(height)] + + def insert_north(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_north("cat", [2], [2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, 't'], + [None, None, 'a'], + [None, None, 'c']] + >>> ws.insert_north("at", [0, 1, 2], [2, 1]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, 't', 't'], + [None, 'a', 'a'], + [None, None, 'c']] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space above the row to fit in the word + if word_length > row + 1: + continue + + # Attempt to insert the word into each column + for col in cols: + # Only check to be made here is if there are existing letters + # above the column that will be overwritten + letters_above = [self.board[row - i][col] for i in range(word_length)] + if all(letter is None for letter in letters_above): + # Successful, insert the word north + for i in range(word_length): + self.board[row - i][col] = word[i] + return + + def insert_northeast(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_northeast("cat", [2], [0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, 't'], + [None, 'a', None], + ['c', None, None]] + >>> ws.insert_northeast("at", [0, 1], [2, 1, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, 't', 't'], + ['a', 'a', None], + ['c', None, None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space for the word above the row + if word_length > row + 1: + continue + + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the right of the word as well as above + if word_length + col > self.width: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_diagonal_left = [ + self.board[row - i][col + i] for i in range(word_length) + ] + if all(letter is None for letter in letters_diagonal_left): + # Successful, insert the word northeast + for i in range(word_length): + self.board[row - i][col + i] = word[i] + return + + def insert_east(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_east("cat", [1], [0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, None], + ['c', 'a', 't'], + [None, None, None]] + >>> ws.insert_east("at", [1, 0], [2, 1, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, 'a', 't'], + ['c', 'a', 't'], + [None, None, None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the right of the word + if word_length + col > self.width: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_left = [self.board[row][col + i] for i in range(word_length)] + if all(letter is None for letter in letters_left): + # Successful, insert the word east + for i in range(word_length): + self.board[row][col + i] = word[i] + return + + def insert_southeast(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_southeast("cat", [0], [0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['c', None, None], + [None, 'a', None], + [None, None, 't']] + >>> ws.insert_southeast("at", [1, 0], [2, 1, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['c', None, None], + ['a', 'a', None], + [None, 't', 't']] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space for the word below the row + if word_length + row > self.height: + continue + + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the right of the word as well as below + if word_length + col > self.width: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_diagonal_left = [ + self.board[row + i][col + i] for i in range(word_length) + ] + if all(letter is None for letter in letters_diagonal_left): + # Successful, insert the word southeast + for i in range(word_length): + self.board[row + i][col + i] = word[i] + return + + def insert_south(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_south("cat", [0], [0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['c', None, None], + ['a', None, None], + ['t', None, None]] + >>> ws.insert_south("at", [2, 1, 0], [0, 1, 2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['c', None, None], + ['a', 'a', None], + ['t', 't', None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space below the row to fit in the word + if word_length + row > self.height: + continue + + # Attempt to insert the word into each column + for col in cols: + # Only check to be made here is if there are existing letters + # below the column that will be overwritten + letters_below = [self.board[row + i][col] for i in range(word_length)] + if all(letter is None for letter in letters_below): + # Successful, insert the word south + for i in range(word_length): + self.board[row + i][col] = word[i] + return + + def insert_southwest(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_southwest("cat", [0], [2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, 'c'], + [None, 'a', None], + ['t', None, None]] + >>> ws.insert_southwest("at", [1, 2], [2, 1, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, 'c'], + [None, 'a', 'a'], + ['t', 't', None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space for the word below the row + if word_length + row > self.height: + continue + + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the left of the word as well as below + if word_length > col + 1: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_diagonal_left = [ + self.board[row + i][col - i] for i in range(word_length) + ] + if all(letter is None for letter in letters_diagonal_left): + # Successful, insert the word southwest + for i in range(word_length): + self.board[row + i][col - i] = word[i] + return + + def insert_west(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_west("cat", [1], [2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, None], + ['t', 'a', 'c'], + [None, None, None]] + >>> ws.insert_west("at", [1, 0], [1, 2, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['t', 'a', None], + ['t', 'a', 'c'], + [None, None, None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the left of the word + if word_length > col + 1: + continue + + # Check if there are existing letters + # to the left of the column that will be overwritten + letters_left = [self.board[row][col - i] for i in range(word_length)] + if all(letter is None for letter in letters_left): + # Successful, insert the word west + for i in range(word_length): + self.board[row][col - i] = word[i] + return + + def insert_northwest(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_northwest("cat", [2], [2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['t', None, None], + [None, 'a', None], + [None, None, 'c']] + >>> ws.insert_northwest("at", [1, 2], [0, 1]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['t', None, None], + ['t', 'a', None], + [None, 'a', 'c']] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space for the word above the row + if word_length > row + 1: + continue + + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the left of the word as well as above + if word_length > col + 1: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_diagonal_left = [ + self.board[row - i][col - i] for i in range(word_length) + ] + if all(letter is None for letter in letters_diagonal_left): + # Successful, insert the word northwest + for i in range(word_length): + self.board[row - i][col - i] = word[i] + return + + def generate_board(self) -> None: + """ + Generates a board with a random direction for each word. + + >>> wt = WordSearch(WORDS, WIDTH, HEIGHT) + >>> wt.generate_board() + >>> len(list(filter(lambda word: word is not None, sum(wt.board, start=[]))) + ... ) == sum(map(lambda word: len(word), WORDS)) + True + """ + directions = ( + self.insert_north, + self.insert_northeast, + self.insert_east, + self.insert_southeast, + self.insert_south, + self.insert_southwest, + self.insert_west, + self.insert_northwest, + ) + for word in self.words: + # Shuffle the row order and column order that is used when brute forcing + # the insertion of the word + rows, cols = list(range(self.height)), list(range(self.width)) + shuffle(rows) + shuffle(cols) + + # Insert the word via the direction + choice(directions)(word, rows, cols) + + +def visualise_word_search( + board: list[list[str | None]] | None = None, *, add_fake_chars: bool = True +) -> None: + """ + Graphically displays the word search in the terminal. + + >>> ws = WordSearch(WORDS, 5, 5) + >>> ws.insert_north("cat", [4], [4]) + >>> visualise_word_search( + ... ws.board, add_fake_chars=False) # doctest: +NORMALIZE_WHITESPACE + # # # # # + # # # # # + # # # # t + # # # # a + # # # # c + >>> ws.insert_northeast("snake", [4], [4, 3, 2, 1, 0]) + >>> visualise_word_search( + ... ws.board, add_fake_chars=False) # doctest: +NORMALIZE_WHITESPACE + # # # # e + # # # k # + # # a # t + # n # # a + s # # # c + """ + if board is None: + word_search = WordSearch(WORDS, WIDTH, HEIGHT) + word_search.generate_board() + board = word_search.board + + result = "" + for row in range(len(board)): + for col in range(len(board[0])): + character = "#" + if (letter := board[row][col]) is not None: + character = letter + # Empty char, so add a fake char + elif add_fake_chars: + character = chr(randint(97, 122)) + result += f"{character} " + result += "\n" + print(result, end="") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + visualise_word_search() From aaf7195465ddfe743cda707cac0feacf70287ecd Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 30 Sep 2023 23:10:33 -0400 Subject: [PATCH 2323/2908] Fix mypy error in web_programming/reddit.py (#9162) * updating DIRECTORY.md * updating DIRECTORY.md * Fix mypy error in web_programming/reddit.py web_programming/reddit.py:36: error: Missing named argument "response" for "HTTPError" [call-arg] --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- web_programming/reddit.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index aabbf27512ce..001da2c15b99 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -341,6 +341,7 @@ * [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py) * [Regex Match](dynamic_programming/regex_match.py) * [Rod Cutting](dynamic_programming/rod_cutting.py) + * [Smith Waterman](dynamic_programming/smith_waterman.py) * [Subset Generation](dynamic_programming/subset_generation.py) * [Sum Of Subset](dynamic_programming/sum_of_subset.py) * [Tribonacci](dynamic_programming/tribonacci.py) @@ -567,7 +568,6 @@ * [Dual Number Automatic Differentiation](maths/dual_number_automatic_differentiation.py) * [Entropy](maths/entropy.py) * [Euclidean Distance](maths/euclidean_distance.py) - * [Euclidean Gcd](maths/euclidean_gcd.py) * [Euler Method](maths/euler_method.py) * [Euler Modified](maths/euler_modified.py) * [Eulers Totient](maths/eulers_totient.py) diff --git a/web_programming/reddit.py b/web_programming/reddit.py index 5ca5f828c0fb..1c165ecc49ec 100644 --- a/web_programming/reddit.py +++ b/web_programming/reddit.py @@ -33,7 +33,7 @@ def get_subreddit_data( headers={"User-agent": "A random string"}, ) if response.status_code == 429: - raise requests.HTTPError + raise requests.HTTPError(response=response) data = response.json() if not wanted_data: From 5f8d1cb5c99cccf6e5ce62fbca9c3dcd60a75292 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 30 Sep 2023 23:31:35 -0400 Subject: [PATCH 2324/2908] Fix DeprecationWarning in local_weighted_learning.py (#9165) Fix DeprecationWarning that occurs during build due to converting an np.ndarray to a scalar implicitly --- .../local_weighted_learning/local_weighted_learning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index 8dd0e55d41df..ada6f7cd2520 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -122,7 +122,7 @@ def local_weight_regression( """ y_pred = np.zeros(len(x_train)) # Initialize array of predictions for i, item in enumerate(x_train): - y_pred[i] = item @ local_weight(item, x_train, y_train, tau) + y_pred[i] = np.dot(item, local_weight(item, x_train, y_train, tau)) return y_pred From 320d895b86133b4b5c489df39ab245fa6be4bce8 Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Sun, 1 Oct 2023 09:36:15 +0530 Subject: [PATCH 2325/2908] Fixed permute_recursive() by passing nums.copy(). Fixes #9014 (#9161) * Fixes #9014 * Fixed permute_recursive() by passing nums.copy() --- data_structures/arrays/permutations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/arrays/permutations.py b/data_structures/arrays/permutations.py index 0f029187b92b..4906dd5c2ae1 100644 --- a/data_structures/arrays/permutations.py +++ b/data_structures/arrays/permutations.py @@ -10,7 +10,7 @@ def permute_recursive(nums: list[int]) -> list[list[int]]: return [[]] for _ in range(len(nums)): n = nums.pop(0) - permutations = permute_recursive(nums) + permutations = permute_recursive(nums.copy()) for perm in permutations: perm.append(n) result.extend(permutations) @@ -43,6 +43,6 @@ def backtrack(start: int) -> None: if __name__ == "__main__": import doctest - res = permute_backtrack([1, 2, 3]) - print(res) + result = permute_backtrack([1, 2, 3]) + print(result) doctest.testmod() From 280dfc1a22adb08aa71984ee4b22e4df220a8e68 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 1 Oct 2023 00:07:25 -0400 Subject: [PATCH 2326/2908] Fix DeprecationWarning in local_weighted_learning.py (Attempt 2) (#9170) * Fix DeprecationWarning in local_weighted_learning.py Fix DeprecationWarning that occurs during build due to converting an np.ndarray to a scalar implicitly * DeprecationWarning fix attempt 2 --- .../local_weighted_learning/local_weighted_learning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index ada6f7cd2520..f3056da40e24 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -122,7 +122,7 @@ def local_weight_regression( """ y_pred = np.zeros(len(x_train)) # Initialize array of predictions for i, item in enumerate(x_train): - y_pred[i] = np.dot(item, local_weight(item, x_train, y_train, tau)) + y_pred[i] = np.dot(item, local_weight(item, x_train, y_train, tau)).item() return y_pred From 832610ab1d05c8cea2814adcc8db5597e7e5ede7 Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Sun, 1 Oct 2023 10:10:53 +0530 Subject: [PATCH 2327/2908] Deleted sorts/random_pivot_quick_sort.py (#9178) --- sorts/random_pivot_quick_sort.py | 44 -------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 sorts/random_pivot_quick_sort.py diff --git a/sorts/random_pivot_quick_sort.py b/sorts/random_pivot_quick_sort.py deleted file mode 100644 index 748b6741047e..000000000000 --- a/sorts/random_pivot_quick_sort.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Picks the random index as the pivot -""" -import random - - -def partition(a, left_index, right_index): - pivot = a[left_index] - i = left_index + 1 - for j in range(left_index + 1, right_index): - if a[j] < pivot: - a[j], a[i] = a[i], a[j] - i += 1 - a[left_index], a[i - 1] = a[i - 1], a[left_index] - return i - 1 - - -def quick_sort_random(a, left, right): - if left < right: - pivot = random.randint(left, right - 1) - a[pivot], a[left] = ( - a[left], - a[pivot], - ) # switches the pivot with the left most bound - pivot_index = partition(a, left, right) - quick_sort_random( - a, left, pivot_index - ) # recursive quicksort to the left of the pivot point - quick_sort_random( - a, pivot_index + 1, right - ) # recursive quicksort to the right of the pivot point - - -def main(): - user_input = input("Enter numbers separated by a comma:\n").strip() - arr = [int(item) for item in user_input.split(",")] - - quick_sort_random(arr, 0, len(arr)) - - print(arr) - - -if __name__ == "__main__": - main() From 3dbafd3f0db55e040a7fd277134d86ec3accfb57 Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Sun, 1 Oct 2023 10:51:46 +0530 Subject: [PATCH 2328/2908] Deleted random_normal_distribution_quicksort.py. Fixes #9124 (#9182) --- sorts/random_normal_distribution_quicksort.py | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 sorts/random_normal_distribution_quicksort.py diff --git a/sorts/random_normal_distribution_quicksort.py b/sorts/random_normal_distribution_quicksort.py deleted file mode 100644 index f7f60903c546..000000000000 --- a/sorts/random_normal_distribution_quicksort.py +++ /dev/null @@ -1,62 +0,0 @@ -from random import randint -from tempfile import TemporaryFile - -import numpy as np - - -def _in_place_quick_sort(a, start, end): - count = 0 - if start < end: - pivot = randint(start, end) - temp = a[end] - a[end] = a[pivot] - a[pivot] = temp - - p, count = _in_place_partition(a, start, end) - count += _in_place_quick_sort(a, start, p - 1) - count += _in_place_quick_sort(a, p + 1, end) - return count - - -def _in_place_partition(a, start, end): - count = 0 - pivot = randint(start, end) - temp = a[end] - a[end] = a[pivot] - a[pivot] = temp - new_pivot_index = start - 1 - for index in range(start, end): - count += 1 - if a[index] < a[end]: # check if current val is less than pivot value - new_pivot_index = new_pivot_index + 1 - temp = a[new_pivot_index] - a[new_pivot_index] = a[index] - a[index] = temp - - temp = a[new_pivot_index + 1] - a[new_pivot_index + 1] = a[end] - a[end] = temp - return new_pivot_index + 1, count - - -outfile = TemporaryFile() -p = 100 # 1000 elements are to be sorted - - -mu, sigma = 0, 1 # mean and standard deviation -X = np.random.normal(mu, sigma, p) -np.save(outfile, X) -print("The array is") -print(X) - - -outfile.seek(0) # using the same array -M = np.load(outfile) -r = len(M) - 1 -z = _in_place_quick_sort(M, 0, r) - -print( - "No of Comparisons for 100 elements selected from a standard normal distribution" - "is :" -) -print(z) From fbbbd5db05987e735ec35fc658136001d3e9e663 Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Sun, 1 Oct 2023 11:04:03 +0530 Subject: [PATCH 2329/2908] Deleted add.py. As stated in #6216 (#9180) --- maths/add.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 maths/add.py diff --git a/maths/add.py b/maths/add.py deleted file mode 100644 index c89252c645ea..000000000000 --- a/maths/add.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Just to check -""" - - -def add(a: float, b: float) -> float: - """ - >>> 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 {add(a, b)}") From eaa87bd791cdc18d210d775f3258767751f9d3fe Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Sun, 1 Oct 2023 14:13:48 +0530 Subject: [PATCH 2330/2908] Made binary tree memory-friendly using generators based travels. Fixes (#9208) #8725 --- .../binary_tree/binary_tree_traversals.py | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 2afb7604f9c6..5dbbbe623906 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -1,12 +1,12 @@ -# https://en.wikipedia.org/wiki/Tree_traversal from __future__ import annotations from collections import deque -from collections.abc import Sequence +from collections.abc import Generator, Sequence from dataclasses import dataclass from typing import Any +# https://en.wikipedia.org/wiki/Tree_traversal @dataclass class Node: data: int @@ -31,44 +31,56 @@ def make_tree() -> Node | None: return tree -def preorder(root: Node | None) -> list[int]: +def preorder(root: Node | None) -> Generator[int, None, None]: """ Pre-order traversal visits root node, left subtree, right subtree. - >>> preorder(make_tree()) + >>> list(preorder(make_tree())) [1, 2, 4, 5, 3] """ - return [root.data, *preorder(root.left), *preorder(root.right)] if root else [] + if not root: + return + yield root.data + yield from preorder(root.left) + yield from preorder(root.right) -def postorder(root: Node | None) -> list[int]: +def postorder(root: Node | None) -> Generator[int, None, None]: """ Post-order traversal visits left subtree, right subtree, root node. - >>> postorder(make_tree()) + >>> list(postorder(make_tree())) [4, 5, 2, 3, 1] """ - return postorder(root.left) + postorder(root.right) + [root.data] if root else [] + if not root: + return + yield from postorder(root.left) + yield from postorder(root.right) + yield root.data -def inorder(root: Node | None) -> list[int]: +def inorder(root: Node | None) -> Generator[int, None, None]: """ In-order traversal visits left subtree, root node, right subtree. - >>> inorder(make_tree()) + >>> list(inorder(make_tree())) [4, 2, 5, 1, 3] """ - return [*inorder(root.left), root.data, *inorder(root.right)] if root else [] + if not root: + return + yield from inorder(root.left) + yield root.data + yield from inorder(root.right) -def reverse_inorder(root: Node | None) -> list[int]: +def reverse_inorder(root: Node | None) -> Generator[int, None, None]: """ Reverse in-order traversal visits right subtree, root node, left subtree. - >>> reverse_inorder(make_tree()) + >>> list(reverse_inorder(make_tree())) [3, 1, 5, 2, 4] """ - return ( - [*reverse_inorder(root.right), root.data, *reverse_inorder(root.left)] - if root - else [] - ) + if not root: + return + yield from reverse_inorder(root.right) + yield root.data + yield from reverse_inorder(root.left) def height(root: Node | None) -> int: @@ -178,10 +190,10 @@ def main() -> None: # Main function for testing. root = make_tree() # All Traversals of the binary are as follows: - print(f"In-order Traversal: {inorder(root)}") - print(f"Reverse In-order Traversal: {reverse_inorder(root)}") - print(f"Pre-order Traversal: {preorder(root)}") - print(f"Post-order Traversal: {postorder(root)}", "\n") + print(f"In-order Traversal: {list(inorder(root))}") + print(f"Reverse In-order Traversal: {list(reverse_inorder(root))}") + print(f"Pre-order Traversal: {list(preorder(root))}") + print(f"Post-order Traversal: {list(postorder(root))}", "\n") print(f"Height of Tree: {height(root)}", "\n") From cfabd91a8ba83bbe23d2790494e2450118044fcc Mon Sep 17 00:00:00 2001 From: Shreya Bhalgat <85868386+shreyabhalgat@users.noreply.github.com> Date: Sun, 1 Oct 2023 16:58:20 +0530 Subject: [PATCH 2331/2908] Add missing number algorithm (#9203) * Added missing_number algorithm using bit manipulation * Update bit_manipulation/missing_number.py --------- Co-authored-by: Christian Clauss --- bit_manipulation/missing_number.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 bit_manipulation/missing_number.py diff --git a/bit_manipulation/missing_number.py b/bit_manipulation/missing_number.py new file mode 100644 index 000000000000..92502a778ace --- /dev/null +++ b/bit_manipulation/missing_number.py @@ -0,0 +1,21 @@ +def find_missing_number(nums: list[int]) -> int: + """ + Finds the missing number in a list of consecutive integers. + + Args: + nums: A list of integers. + + Returns: + The missing number. + + Example: + >>> find_missing_number([0, 1, 3, 4]) + 2 + """ + n = len(nums) + missing_number = n + + for i in range(n): + missing_number ^= i ^ nums[i] + + return missing_number From 596d93423862da8c8e419e9b74c1321b7d26b7a1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 1 Oct 2023 13:58:30 +0200 Subject: [PATCH 2332/2908] Fix ruff warning (#9272) --- .github/workflows/ruff.yml | 2 +- DIRECTORY.md | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index ca2d5be47327..e71ac8a4e933 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -13,4 +13,4 @@ jobs: steps: - uses: actions/checkout@v3 - run: pip install --user ruff - - run: ruff --format=github . + - run: ruff --output-format=github . diff --git a/DIRECTORY.md b/DIRECTORY.md index 001da2c15b99..4ae1c69f7099 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -530,7 +530,6 @@ ## Maths * [Abs](maths/abs.py) - * [Add](maths/add.py) * [Addition Without Arithmetic](maths/addition_without_arithmetic.py) * [Aliquot Sum](maths/aliquot_sum.py) * [Allocation Number](maths/allocation_number.py) @@ -1141,8 +1140,6 @@ * [Quick Sort](sorts/quick_sort.py) * [Quick Sort 3 Partition](sorts/quick_sort_3_partition.py) * [Radix Sort](sorts/radix_sort.py) - * [Random Normal Distribution Quicksort](sorts/random_normal_distribution_quicksort.py) - * [Random Pivot Quick Sort](sorts/random_pivot_quick_sort.py) * [Recursive Bubble Sort](sorts/recursive_bubble_sort.py) * [Recursive Insertion Sort](sorts/recursive_insertion_sort.py) * [Recursive Mergesort Array](sorts/recursive_mergesort_array.py) From 43c3f4ea4070bfbe1f41f4b861c7ff3f89953715 Mon Sep 17 00:00:00 2001 From: Bama Charan Chhandogi Date: Sun, 1 Oct 2023 20:16:12 +0530 Subject: [PATCH 2333/2908] add Three sum (#9177) * add Three sum * add Three sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * update * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add documention --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/three_sum.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 maths/three_sum.py diff --git a/maths/three_sum.py b/maths/three_sum.py new file mode 100644 index 000000000000..09956f8415a0 --- /dev/null +++ b/maths/three_sum.py @@ -0,0 +1,47 @@ +""" +https://en.wikipedia.org/wiki/3SUM +""" + + +def three_sum(nums: list[int]) -> list[list[int]]: + """ + Find all unique triplets in a sorted array of integers that sum up to zero. + + Args: + nums: A sorted list of integers. + + Returns: + A list of lists containing unique triplets that sum up to zero. + + >>> three_sum([-1, 0, 1, 2, -1, -4]) + [[-1, -1, 2], [-1, 0, 1]] + >>> three_sum([1, 2, 3, 4]) + [] + """ + nums.sort() + ans = [] + for i in range(len(nums) - 2): + if i == 0 or (nums[i] != nums[i - 1]): + low, high, c = i + 1, len(nums) - 1, 0 - nums[i] + while low < high: + if nums[low] + nums[high] == c: + ans.append([nums[i], nums[low], nums[high]]) + + while low < high and nums[low] == nums[low + 1]: + low += 1 + while low < high and nums[high] == nums[high - 1]: + high -= 1 + + low += 1 + high -= 1 + elif nums[low] + nums[high] < c: + low += 1 + else: + high -= 1 + return ans + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From bacad12a1f64d92a793ccc2ec88535c9a4092fb6 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Farooq <115654418+Muhammadummerr@users.noreply.github.com> Date: Sun, 1 Oct 2023 21:11:16 +0500 Subject: [PATCH 2334/2908] [NEW ALGORITHM] Rotate linked list by K. (#9278) * Rotate linked list by k. * Rotate linked list by k. * updated variable name. * Update data_structures/linked_list/rotate_linked_list_by_k.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update data_structures/linked_list/rotate_linked_list_by_k.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/rotate_linked_list_by_k.py * Make Node a dataclass --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../linked_list/rotate_to_the_right.py | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 data_structures/linked_list/rotate_to_the_right.py diff --git a/data_structures/linked_list/rotate_to_the_right.py b/data_structures/linked_list/rotate_to_the_right.py new file mode 100644 index 000000000000..51b10481c0ce --- /dev/null +++ b/data_structures/linked_list/rotate_to_the_right.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class Node: + data: int + next_node: Node | None = None + + +def print_linked_list(head: Node | None) -> None: + """ + Print the entire linked list iteratively. + + This function prints the elements of a linked list separated by '->'. + + Parameters: + head (Node | None): The head of the linked list to be printed, + or None if the linked list is empty. + + >>> head = insert_node(None, 0) + >>> head = insert_node(head, 2) + >>> head = insert_node(head, 1) + >>> print_linked_list(head) + 0->2->1 + >>> head = insert_node(head, 4) + >>> head = insert_node(head, 5) + >>> print_linked_list(head) + 0->2->1->4->5 + """ + if head is None: + return + while head.next_node is not None: + print(head.data, end="->") + head = head.next_node + print(head.data) + + +def insert_node(head: Node | None, data: int) -> Node: + """ + Insert a new node at the end of a linked list and return the new head. + + Parameters: + head (Node | None): The head of the linked list. + data (int): The data to be inserted into the new node. + + Returns: + Node: The new head of the linked list. + + >>> head = insert_node(None, 10) + >>> head = insert_node(head, 9) + >>> head = insert_node(head, 8) + >>> print_linked_list(head) + 10->9->8 + """ + new_node = Node(data) + # If the linked list is empty, the new_node becomes the head + if head is None: + return new_node + + temp_node = head + while temp_node.next_node: + temp_node = temp_node.next_node + + temp_node.next_node = new_node # type: ignore + return head + + +def rotate_to_the_right(head: Node, places: int) -> Node: + """ + Rotate a linked list to the right by places times. + + Parameters: + head: The head of the linked list. + places: The number of places to rotate. + + Returns: + Node: The head of the rotated linked list. + + >>> rotate_to_the_right(None, places=1) + Traceback (most recent call last): + ... + ValueError: The linked list is empty. + >>> head = insert_node(None, 1) + >>> rotate_to_the_right(head, places=1) == head + True + >>> head = insert_node(None, 1) + >>> head = insert_node(head, 2) + >>> head = insert_node(head, 3) + >>> head = insert_node(head, 4) + >>> head = insert_node(head, 5) + >>> new_head = rotate_to_the_right(head, places=2) + >>> print_linked_list(new_head) + 4->5->1->2->3 + """ + # Check if the list is empty or has only one element + if not head: + raise ValueError("The linked list is empty.") + + if head.next_node is None: + return head + + # Calculate the length of the linked list + length = 1 + temp_node = head + while temp_node.next_node is not None: + length += 1 + temp_node = temp_node.next_node + + # Adjust the value of places to avoid places longer than the list. + places %= length + + if places == 0: + return head # As no rotation is needed. + + # Find the new head position after rotation. + new_head_index = length - places + + # Traverse to the new head position + temp_node = head + for _ in range(new_head_index - 1): + assert temp_node.next_node + temp_node = temp_node.next_node + + # Update pointers to perform rotation + assert temp_node.next_node + new_head = temp_node.next_node + temp_node.next_node = None + temp_node = new_head + while temp_node.next_node: + temp_node = temp_node.next_node + temp_node.next_node = head + + assert new_head + return new_head + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + head = insert_node(None, 5) + head = insert_node(head, 1) + head = insert_node(head, 2) + head = insert_node(head, 4) + head = insert_node(head, 3) + + print("Original list: ", end="") + print_linked_list(head) + + places = 3 + new_head = rotate_to_the_right(head, places) + + print(f"After {places} iterations: ", end="") + print_linked_list(new_head) From 18cdbc416504391bc9246f1874bd752ea730c710 Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Sun, 1 Oct 2023 22:24:05 +0530 Subject: [PATCH 2335/2908] binary_search_traversals.py made memory-friendly using generators. Fixes #8725 completely. (#9237) * Made binary tree memory-friendly using generators based travels. Fixes #8725 * Made binary tree memory-friendly using generators based travels. Fixes #8725 * Fixed pre-commit errors --- .../binary_tree/binary_tree_traversals.py | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 5dbbbe623906..2b33cdca4fed 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -1,9 +1,8 @@ from __future__ import annotations from collections import deque -from collections.abc import Generator, Sequence +from collections.abc import Generator from dataclasses import dataclass -from typing import Any # https://en.wikipedia.org/wiki/Tree_traversal @@ -94,96 +93,86 @@ def height(root: Node | None) -> int: return (max(height(root.left), height(root.right)) + 1) if root else 0 -def level_order(root: Node | None) -> Sequence[Node | None]: +def level_order(root: Node | None) -> Generator[int, None, None]: """ Returns a list of nodes value from a whole binary tree in Level Order Traverse. Level Order traverse: Visit nodes of the tree level-by-level. """ - output: list[Any] = [] if root is None: - return output + return process_queue = deque([root]) while process_queue: node = process_queue.popleft() - output.append(node.data) + yield node.data if node.left: process_queue.append(node.left) if node.right: process_queue.append(node.right) - return output def get_nodes_from_left_to_right( root: Node | None, level: int -) -> Sequence[Node | None]: +) -> Generator[int, None, None]: """ Returns a list of nodes value from a particular level: Left to right direction of the binary tree. """ - output: list[Any] = [] - def populate_output(root: Node | None, level: int) -> None: + def populate_output(root: Node | None, level: int) -> Generator[int, None, None]: if not root: return if level == 1: - output.append(root.data) + yield root.data elif level > 1: - populate_output(root.left, level - 1) - populate_output(root.right, level - 1) + yield from populate_output(root.left, level - 1) + yield from populate_output(root.right, level - 1) - populate_output(root, level) - return output + yield from populate_output(root, level) def get_nodes_from_right_to_left( root: Node | None, level: int -) -> Sequence[Node | None]: +) -> Generator[int, None, None]: """ Returns a list of nodes value from a particular level: Right to left direction of the binary tree. """ - output: list[Any] = [] - def populate_output(root: Node | None, level: int) -> None: + def populate_output(root: Node | None, level: int) -> Generator[int, None, None]: if root is None: return if level == 1: - output.append(root.data) + yield root.data elif level > 1: - populate_output(root.right, level - 1) - populate_output(root.left, level - 1) + yield from populate_output(root.right, level - 1) + yield from populate_output(root.left, level - 1) - populate_output(root, level) - return output + yield from populate_output(root, level) -def zigzag(root: Node | None) -> Sequence[Node | None] | list[Any]: +def zigzag(root: Node | None) -> Generator[int, None, None]: """ ZigZag traverse: Returns a list of nodes value from left to right and right to left, alternatively. """ if root is None: - return [] - - output: list[Sequence[Node | None]] = [] + return flag = 0 height_tree = height(root) for h in range(1, height_tree + 1): if not flag: - output.append(get_nodes_from_left_to_right(root, h)) + yield from get_nodes_from_left_to_right(root, h) flag = 1 else: - output.append(get_nodes_from_right_to_left(root, h)) + yield from get_nodes_from_right_to_left(root, h) flag = 0 - return output - def main() -> None: # Main function for testing. # Create binary tree. @@ -198,15 +187,15 @@ def main() -> None: # Main function for testing. print(f"Height of Tree: {height(root)}", "\n") print("Complete Level Order Traversal: ") - print(level_order(root), "\n") + print(f"{list(level_order(root))} \n") print("Level-wise order Traversal: ") for level in range(1, height(root) + 1): - print(f"Level {level}:", get_nodes_from_left_to_right(root, level=level)) + print(f"Level {level}:", list(get_nodes_from_left_to_right(root, level=level))) print("\nZigZag order Traversal: ") - print(zigzag(root)) + print(f"{list(zigzag(root))}") if __name__ == "__main__": From 8d94f7745f81c8f7c33bdd3d0c0740861b9c98e7 Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Sun, 1 Oct 2023 23:14:58 +0500 Subject: [PATCH 2336/2908] Euler072 - application of vector operations to reduce calculation time and refactoring numpy (#9229) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Application of vector operations to reduce calculation time and refactoring numpy. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- project_euler/problem_072/sol1.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/project_euler/problem_072/sol1.py b/project_euler/problem_072/sol1.py index a2a0eeeb31c5..5a28be564556 100644 --- a/project_euler/problem_072/sol1.py +++ b/project_euler/problem_072/sol1.py @@ -21,6 +21,8 @@ Time: 1 sec """ +import numpy as np + def solution(limit: int = 1_000_000) -> int: """ @@ -33,14 +35,15 @@ def solution(limit: int = 1_000_000) -> int: 304191 """ - phi = [i - 1 for i in range(limit + 1)] + # generating an array from -1 to limit + phi = np.arange(-1, limit) for i in range(2, limit + 1): if phi[i] == i - 1: - for j in range(2 * i, limit + 1, i): - phi[j] -= phi[j] // i + ind = np.arange(2 * i, limit + 1, i) # indexes for selection + phi[ind] -= phi[ind] // i - return sum(phi[2 : limit + 1]) + return np.sum(phi[2 : limit + 1]) if __name__ == "__main__": From 24e7edbe5bc771023335544a7a9cf7895140c1fe Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Mon, 2 Oct 2023 02:48:16 +0530 Subject: [PATCH 2337/2908] Remove myself from CODEOWNERS (#9325) --- .github/CODEOWNERS | 2 +- DIRECTORY.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index abf99ab227be..05cd709a8f62 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -69,7 +69,7 @@ # /other/ @cclauss # TODO: Uncomment this line after Hacktoberfest -/project_euler/ @dhruvmanila +# /project_euler/ # /quantum/ diff --git a/DIRECTORY.md b/DIRECTORY.md index 4ae1c69f7099..7d3ceee144be 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -51,6 +51,7 @@ * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py) * [Is Even](bit_manipulation/is_even.py) * [Is Power Of Two](bit_manipulation/is_power_of_two.py) + * [Missing Number](bit_manipulation/missing_number.py) * [Numbers Different Signs](bit_manipulation/numbers_different_signs.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py) @@ -232,6 +233,7 @@ * [Merge Two Lists](data_structures/linked_list/merge_two_lists.py) * [Middle Element Of Linked List](data_structures/linked_list/middle_element_of_linked_list.py) * [Print Reverse](data_structures/linked_list/print_reverse.py) + * [Rotate To The Right](data_structures/linked_list/rotate_to_the_right.py) * [Singly Linked List](data_structures/linked_list/singly_linked_list.py) * [Skip List](data_structures/linked_list/skip_list.py) * [Swap Nodes](data_structures/linked_list/swap_nodes.py) @@ -676,6 +678,7 @@ * [Sylvester Sequence](maths/sylvester_sequence.py) * [Tanh](maths/tanh.py) * [Test Prime Check](maths/test_prime_check.py) + * [Three Sum](maths/three_sum.py) * [Trapezoidal Rule](maths/trapezoidal_rule.py) * [Triplet Sum](maths/triplet_sum.py) * [Twin Prime](maths/twin_prime.py) From e798e5acdee69416d61c8ab65cea4da8a5c16355 Mon Sep 17 00:00:00 2001 From: Bama Charan Chhandogi Date: Mon, 2 Oct 2023 05:49:39 +0530 Subject: [PATCH 2338/2908] add reverse k group linkedlist (#9323) * add reverse k group linkedlist * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * Update reverse_k_group.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update reverse_k_group.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update reverse_k_group.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../linked_list/reverse_k_group.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 data_structures/linked_list/reverse_k_group.py diff --git a/data_structures/linked_list/reverse_k_group.py b/data_structures/linked_list/reverse_k_group.py new file mode 100644 index 000000000000..5fc45491a540 --- /dev/null +++ b/data_structures/linked_list/reverse_k_group.py @@ -0,0 +1,118 @@ +from __future__ import annotations + +from collections.abc import Iterable, Iterator +from dataclasses import dataclass + + +@dataclass +class Node: + data: int + next_node: Node | None = None + + +class LinkedList: + def __init__(self, ints: Iterable[int]) -> None: + self.head: Node | None = None + for i in ints: + self.append(i) + + def __iter__(self) -> Iterator[int]: + """ + >>> ints = [] + >>> list(LinkedList(ints)) == ints + True + >>> ints = tuple(range(5)) + >>> tuple(LinkedList(ints)) == ints + True + """ + node = self.head + while node: + yield node.data + node = node.next_node + + def __len__(self) -> int: + """ + >>> for i in range(3): + ... len(LinkedList(range(i))) == i + True + True + True + >>> len(LinkedList("abcdefgh")) + 8 + """ + return sum(1 for _ in self) + + def __str__(self) -> str: + """ + >>> str(LinkedList([])) + '' + >>> str(LinkedList(range(5))) + '0 -> 1 -> 2 -> 3 -> 4' + """ + return " -> ".join([str(node) for node in self]) + + def append(self, data: int) -> None: + """ + >>> ll = LinkedList([1, 2]) + >>> tuple(ll) + (1, 2) + >>> ll.append(3) + >>> tuple(ll) + (1, 2, 3) + >>> ll.append(4) + >>> tuple(ll) + (1, 2, 3, 4) + >>> len(ll) + 4 + """ + if not self.head: + self.head = Node(data) + return + node = self.head + while node.next_node: + node = node.next_node + node.next_node = Node(data) + + def reverse_k_nodes(self, group_size: int) -> None: + """ + reverse nodes within groups of size k + >>> ll = LinkedList([1, 2, 3, 4, 5]) + >>> ll.reverse_k_nodes(2) + >>> tuple(ll) + (2, 1, 4, 3, 5) + >>> str(ll) + '2 -> 1 -> 4 -> 3 -> 5' + """ + if self.head is None or self.head.next_node is None: + return + + length = len(self) + dummy_head = Node(0) + dummy_head.next_node = self.head + previous_node = dummy_head + + while length >= group_size: + current_node = previous_node.next_node + assert current_node + next_node = current_node.next_node + for _ in range(1, group_size): + assert next_node, current_node + current_node.next_node = next_node.next_node + assert previous_node + next_node.next_node = previous_node.next_node + previous_node.next_node = next_node + next_node = current_node.next_node + previous_node = current_node + length -= group_size + self.head = dummy_head.next_node + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + ll = LinkedList([1, 2, 3, 4, 5]) + print(f"Original Linked List: {ll}") + k = 2 + ll.reverse_k_nodes(k) + print(f"After reversing groups of size {k}: {ll}") From 9640a4041a7b331e506daab1b31dd30fb47b228d Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Mon, 2 Oct 2023 19:58:36 +0530 Subject: [PATCH 2339/2908] Add typing to binary_exponentiation_2.py (#9475) --- maths/binary_exponentiation_2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/binary_exponentiation_2.py b/maths/binary_exponentiation_2.py index 51ec4baf2598..af8f776dd266 100644 --- a/maths/binary_exponentiation_2.py +++ b/maths/binary_exponentiation_2.py @@ -11,7 +11,7 @@ """ -def b_expo(a, b): +def b_expo(a: int, b: int) -> int: res = 0 while b > 0: if b & 1: @@ -23,7 +23,7 @@ def b_expo(a, b): return res -def b_expo_mod(a, b, c): +def b_expo_mod(a: int, b: int, c: int) -> int: res = 0 while b > 0: if b & 1: From 89a65a861724d2eb8c6a60a9e1655d7af9cdc836 Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Mon, 2 Oct 2023 19:59:06 +0530 Subject: [PATCH 2340/2908] Add typing to binary_exponentiation.py (#9471) * Add typing to binary_exponentiation.py * Update binary_exponentiation.py * float to int division change as per review --- maths/binary_exponentiation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index 147b4285ffa1..05de939d1bde 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -4,7 +4,7 @@ # Time Complexity : O(logn) -def binary_exponentiation(a, n): +def binary_exponentiation(a: int, n: int) -> int: if n == 0: return 1 @@ -12,7 +12,7 @@ def binary_exponentiation(a, n): return binary_exponentiation(a, n - 1) * a else: - b = binary_exponentiation(a, n / 2) + b = binary_exponentiation(a, n // 2) return b * b From 97154cfa351e35ddf0727691a92998cfd7be4e5b Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:00:34 +0530 Subject: [PATCH 2341/2908] Add typing to binary_exp_mod.py (#9469) * Add typing to binary_exp_mod.py * Update binary_exp_mod.py * review changes --- maths/binary_exp_mod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/binary_exp_mod.py b/maths/binary_exp_mod.py index df688892d690..8893182a3496 100644 --- a/maths/binary_exp_mod.py +++ b/maths/binary_exp_mod.py @@ -1,4 +1,4 @@ -def bin_exp_mod(a, n, b): +def bin_exp_mod(a: int, n: int, b: int) -> int: """ >>> bin_exp_mod(3, 4, 5) 1 @@ -13,7 +13,7 @@ def bin_exp_mod(a, n, b): if n % 2 == 1: return (bin_exp_mod(a, n - 1, b) * a) % b - r = bin_exp_mod(a, n / 2, b) + r = bin_exp_mod(a, n // 2, b) return (r * r) % b From 73118b9f67f49fae14eb9a39e47ec9127ef1f155 Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:11:34 +0530 Subject: [PATCH 2342/2908] Add typing to binary_exponentiation_3.py (#9477) --- maths/binary_exponentiation_3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/binary_exponentiation_3.py b/maths/binary_exponentiation_3.py index dd4e70e74129..9cd143e09207 100644 --- a/maths/binary_exponentiation_3.py +++ b/maths/binary_exponentiation_3.py @@ -11,7 +11,7 @@ """ -def b_expo(a, b): +def b_expo(a: int, b: int) -> int: res = 1 while b > 0: if b & 1: @@ -23,7 +23,7 @@ def b_expo(a, b): return res -def b_expo_mod(a, b, c): +def b_expo_mod(a: int, b: int, c: int) -> int: res = 1 while b > 0: if b & 1: From 95345f6f5b0e6ae10f54a33850298634e05766ee Mon Sep 17 00:00:00 2001 From: Saksham Chawla <51916697+saksham-chawla@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:51:45 +0530 Subject: [PATCH 2343/2908] Add typng to binomial_coefficient.py (#9480) --- maths/binomial_coefficient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/binomial_coefficient.py b/maths/binomial_coefficient.py index 0d4b3d1a8d9a..6d5b46cb5861 100644 --- a/maths/binomial_coefficient.py +++ b/maths/binomial_coefficient.py @@ -1,4 +1,4 @@ -def binomial_coefficient(n, r): +def binomial_coefficient(n: int, r: int) -> int: """ Find binomial coefficient using pascals triangle. From 8c7bd1c48d1e4029aa115d50fb3034e199bef7f9 Mon Sep 17 00:00:00 2001 From: Varshaa Shetty Date: Tue, 3 Oct 2023 03:17:10 +0530 Subject: [PATCH 2344/2908] Deleted minmax.py (#9482) --- backtracking/minmax.py | 69 ------------------------------------------ 1 file changed, 69 deletions(-) delete mode 100644 backtracking/minmax.py diff --git a/backtracking/minmax.py b/backtracking/minmax.py deleted file mode 100644 index 9b87183cfdb7..000000000000 --- a/backtracking/minmax.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Minimax helps to achieve maximum score in a game by checking all possible moves. - -""" -from __future__ import annotations - -import math - - -def minimax( - depth: int, node_index: int, is_max: bool, scores: list[int], height: float -) -> int: - """ - depth is current depth in game tree. - node_index is index of current node in scores[]. - scores[] contains the leaves of game tree. - height is maximum height of game tree. - - >>> 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 - """ - - if depth < 0: - raise ValueError("Depth cannot be less than 0") - - if not scores: - raise ValueError("Scores cannot be empty") - - if depth == height: - return scores[node_index] - - return ( - max( - minimax(depth + 1, node_index * 2, False, scores, height), - minimax(depth + 1, node_index * 2 + 1, False, scores, height), - ) - if is_max - else min( - minimax(depth + 1, node_index * 2, True, scores, height), - minimax(depth + 1, node_index * 2 + 1, True, scores, height), - ) - ) - - -def main() -> None: - scores = [90, 23, 6, 33, 21, 65, 123, 34423] - height = math.log(len(scores), 2) - print(f"Optimal value : {minimax(0, 0, True, scores, height)}") - - -if __name__ == "__main__": - import doctest - - doctest.testmod() - main() From f8fe8fe41f74c8ecc5c8555ca43d65bd12b4f073 Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Tue, 3 Oct 2023 03:27:00 +0530 Subject: [PATCH 2345/2908] Removed maths/miller_rabin.py , Double implementation. #8098 (#9228) * Removed ciphers/rabin_miller.py as it is already there maths/miller_rabin.py * Renamed miller_rabin.py to rabain_miller.py * Restore ciphers/rabin_miller.py and removed maths/rabin_miller.py --- maths/miller_rabin.py | 51 ------------------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 maths/miller_rabin.py diff --git a/maths/miller_rabin.py b/maths/miller_rabin.py deleted file mode 100644 index 9f2668dbab14..000000000000 --- a/maths/miller_rabin.py +++ /dev/null @@ -1,51 +0,0 @@ -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_big(n, prec=1000): - """ - >>> from maths.prime_check import is_prime - >>> # all(is_prime_big(i) == is_prime(i) for i in range(1000)) # 3.45s - >>> all(is_prime_big(i) == is_prime(i) for i in range(256)) - 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 _ 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_big(i))) From f964dcbf2ff7c70e4aca20532a38dfb02ce8a4c0 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 3 Oct 2023 05:05:43 +0200 Subject: [PATCH 2346/2908] pre-commit autoupdate && pre-commit run --all-files (#9516) * pre-commit autoupdate && pre-commit run --all-files * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 809b841d0ea3..dbf7ff341243 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.291 + rev: v0.0.292 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.1.0" + rev: "1.2.0" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index 7d3ceee144be..24c68171c9bc 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -233,6 +233,7 @@ * [Merge Two Lists](data_structures/linked_list/merge_two_lists.py) * [Middle Element Of Linked List](data_structures/linked_list/middle_element_of_linked_list.py) * [Print Reverse](data_structures/linked_list/print_reverse.py) + * [Reverse K Group](data_structures/linked_list/reverse_k_group.py) * [Rotate To The Right](data_structures/linked_list/rotate_to_the_right.py) * [Singly Linked List](data_structures/linked_list/singly_linked_list.py) * [Skip List](data_structures/linked_list/skip_list.py) From 0f4e51245f33175b4fb311f633d3821210741bdd Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 3 Oct 2023 11:17:10 +0200 Subject: [PATCH 2347/2908] Upgrade to Python 3.12 (#9576) * DRAFT: GitHub Actions: Test on Python 3.12 Repeats #8777 * #8777 Some of our dependencies will not be ready yet. * Python 3.12: Disable qiskit and tensorflow algorithms * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/build.yml | 5 +++-- .github/workflows/ruff.yml | 2 +- CONTRIBUTING.md | 2 +- DIRECTORY.md | 19 ------------------- backtracking/combination_sum.py | 2 +- ....py => cnn_classification.py.DISABLED.txt} | 0 ...ans_clustering_tensorflow.py.DISABLED.txt} | 0 ...ns.py => fuzzy_operations.py.DISABLED.txt} | 0 ...ion.py => lstm_prediction.py.DISABLED.txt} | 0 maths/maclaurin_series.py | 8 ++++---- quantum/{bb84.py => bb84.py.DISABLED.txt} | 0 ...jozsa.py => deutsch_jozsa.py.DISABLED.txt} | 1 + ...lf_adder.py => half_adder.py.DISABLED.txt} | 1 + .../{not_gate.py => not_gate.py.DISABLED.txt} | 0 ..._adder.py => q_full_adder.py.DISABLED.txt} | 0 ...y => quantum_entanglement.py.DISABLED.txt} | 0 ... => quantum_teleportation.py.DISABLED.txt} | 0 ...y => ripple_adder_classic.py.DISABLED.txt} | 0 ...y => single_qubit_measure.py.DISABLED.txt} | 0 ...g.py => superdense_coding.py.DISABLED.txt} | 0 requirements.txt | 6 +++--- 21 files changed, 15 insertions(+), 31 deletions(-) rename computer_vision/{cnn_classification.py => cnn_classification.py.DISABLED.txt} (100%) rename dynamic_programming/{k_means_clustering_tensorflow.py => k_means_clustering_tensorflow.py.DISABLED.txt} (100%) rename fuzzy_logic/{fuzzy_operations.py => fuzzy_operations.py.DISABLED.txt} (100%) rename machine_learning/lstm/{lstm_prediction.py => lstm_prediction.py.DISABLED.txt} (100%) rename quantum/{bb84.py => bb84.py.DISABLED.txt} (100%) rename quantum/{deutsch_jozsa.py => deutsch_jozsa.py.DISABLED.txt} (99%) mode change 100755 => 100644 rename quantum/{half_adder.py => half_adder.py.DISABLED.txt} (99%) mode change 100755 => 100644 rename quantum/{not_gate.py => not_gate.py.DISABLED.txt} (100%) rename quantum/{q_full_adder.py => q_full_adder.py.DISABLED.txt} (100%) rename quantum/{quantum_entanglement.py => quantum_entanglement.py.DISABLED.txt} (100%) rename quantum/{quantum_teleportation.py => quantum_teleportation.py.DISABLED.txt} (100%) rename quantum/{ripple_adder_classic.py => ripple_adder_classic.py.DISABLED.txt} (100%) rename quantum/{single_qubit_measure.py => single_qubit_measure.py.DISABLED.txt} (100%) rename quantum/{superdense_coding.py => superdense_coding.py.DISABLED.txt} (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc8cb636979e..60c1d6d119d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,10 +9,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: - python-version: 3.11 + python-version: 3.12 + allow-prereleases: true - uses: actions/cache@v3 with: path: ~/.cache/pip diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index e71ac8a4e933..496f1460e074 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -11,6 +11,6 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: pip install --user ruff - run: ruff --output-format=github . diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a1bb652738f..7a67ce33cd62 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,7 +73,7 @@ pre-commit run --all-files --show-diff-on-failure We want your work to be readable by others; therefore, we encourage you to note the following: -- Please write in Python 3.11+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. +- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. - Please focus hard on the 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. diff --git a/DIRECTORY.md b/DIRECTORY.md index 24c68171c9bc..9a913aa786e1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -26,7 +26,6 @@ * [Hamiltonian Cycle](backtracking/hamiltonian_cycle.py) * [Knight Tour](backtracking/knight_tour.py) * [Minimax](backtracking/minimax.py) - * [Minmax](backtracking/minmax.py) * [N Queens](backtracking/n_queens.py) * [N Queens Math](backtracking/n_queens_math.py) * [Power Sum](backtracking/power_sum.py) @@ -133,7 +132,6 @@ * [Run Length Encoding](compression/run_length_encoding.py) ## Computer Vision - * [Cnn Classification](computer_vision/cnn_classification.py) * [Flip Augmentation](computer_vision/flip_augmentation.py) * [Haralick Descriptors](computer_vision/haralick_descriptors.py) * [Harris Corner](computer_vision/harris_corner.py) @@ -321,7 +319,6 @@ * [Floyd Warshall](dynamic_programming/floyd_warshall.py) * [Integer Partition](dynamic_programming/integer_partition.py) * [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py) - * [K Means Clustering Tensorflow](dynamic_programming/k_means_clustering_tensorflow.py) * [Knapsack](dynamic_programming/knapsack.py) * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) * [Longest Common Substring](dynamic_programming/longest_common_substring.py) @@ -384,9 +381,6 @@ * [Mandelbrot](fractals/mandelbrot.py) * [Sierpinski Triangle](fractals/sierpinski_triangle.py) -## Fuzzy Logic - * [Fuzzy Operations](fuzzy_logic/fuzzy_operations.py) - ## Genetic Algorithm * [Basic String](genetic_algorithm/basic_string.py) @@ -517,8 +511,6 @@ * Local Weighted Learning * [Local Weighted Learning](machine_learning/local_weighted_learning/local_weighted_learning.py) * [Logistic Regression](machine_learning/logistic_regression.py) - * Lstm - * [Lstm Prediction](machine_learning/lstm/lstm_prediction.py) * [Mfcc](machine_learning/mfcc.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polynomial Regression](machine_learning/polynomial_regression.py) @@ -613,7 +605,6 @@ * [Matrix Exponentiation](maths/matrix_exponentiation.py) * [Max Sum Sliding Window](maths/max_sum_sliding_window.py) * [Median Of Two Arrays](maths/median_of_two_arrays.py) - * [Miller Rabin](maths/miller_rabin.py) * [Mobius Function](maths/mobius_function.py) * [Modular Exponential](maths/modular_exponential.py) * [Monte Carlo](maths/monte_carlo.py) @@ -1071,17 +1062,7 @@ * [Sol1](project_euler/problem_800/sol1.py) ## Quantum - * [Bb84](quantum/bb84.py) - * [Deutsch Jozsa](quantum/deutsch_jozsa.py) - * [Half Adder](quantum/half_adder.py) - * [Not Gate](quantum/not_gate.py) * [Q Fourier Transform](quantum/q_fourier_transform.py) - * [Q Full Adder](quantum/q_full_adder.py) - * [Quantum Entanglement](quantum/quantum_entanglement.py) - * [Quantum Teleportation](quantum/quantum_teleportation.py) - * [Ripple Adder Classic](quantum/ripple_adder_classic.py) - * [Single Qubit Measure](quantum/single_qubit_measure.py) - * [Superdense Coding](quantum/superdense_coding.py) ## Scheduling * [First Come First Served](scheduling/first_come_first_served.py) diff --git a/backtracking/combination_sum.py b/backtracking/combination_sum.py index f555adb751d0..3c6ed81f44f0 100644 --- a/backtracking/combination_sum.py +++ b/backtracking/combination_sum.py @@ -47,7 +47,7 @@ def combination_sum(candidates: list, target: int) -> list: >>> combination_sum([-8, 2.3, 0], 1) Traceback (most recent call last): ... - RecursionError: maximum recursion depth exceeded in comparison + RecursionError: maximum recursion depth exceeded """ path = [] # type: list[int] answer = [] # type: list[int] diff --git a/computer_vision/cnn_classification.py b/computer_vision/cnn_classification.py.DISABLED.txt similarity index 100% rename from computer_vision/cnn_classification.py rename to computer_vision/cnn_classification.py.DISABLED.txt diff --git a/dynamic_programming/k_means_clustering_tensorflow.py b/dynamic_programming/k_means_clustering_tensorflow.py.DISABLED.txt similarity index 100% rename from dynamic_programming/k_means_clustering_tensorflow.py rename to dynamic_programming/k_means_clustering_tensorflow.py.DISABLED.txt diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py.DISABLED.txt similarity index 100% rename from fuzzy_logic/fuzzy_operations.py rename to fuzzy_logic/fuzzy_operations.py.DISABLED.txt diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py.DISABLED.txt similarity index 100% rename from machine_learning/lstm/lstm_prediction.py rename to machine_learning/lstm/lstm_prediction.py.DISABLED.txt diff --git a/maths/maclaurin_series.py b/maths/maclaurin_series.py index e55839bc15ba..806e5f9b0788 100644 --- a/maths/maclaurin_series.py +++ b/maths/maclaurin_series.py @@ -17,9 +17,9 @@ def maclaurin_sin(theta: float, accuracy: int = 30) -> float: >>> all(isclose(maclaurin_sin(x, 50), sin(x)) for x in range(-25, 25)) True >>> maclaurin_sin(10) - -0.544021110889369 + -0.5440211108893691 >>> maclaurin_sin(-10) - 0.5440211108893703 + 0.5440211108893704 >>> maclaurin_sin(10, 15) -0.5440211108893689 >>> maclaurin_sin(-10, 15) @@ -69,9 +69,9 @@ def maclaurin_cos(theta: float, accuracy: int = 30) -> float: >>> all(isclose(maclaurin_cos(x, 50), cos(x)) for x in range(-25, 25)) True >>> maclaurin_cos(5) - 0.28366218546322675 + 0.2836621854632268 >>> maclaurin_cos(-5) - 0.2836621854632266 + 0.2836621854632265 >>> maclaurin_cos(10, 15) -0.8390715290764525 >>> maclaurin_cos(-10, 15) diff --git a/quantum/bb84.py b/quantum/bb84.py.DISABLED.txt similarity index 100% rename from quantum/bb84.py rename to quantum/bb84.py.DISABLED.txt diff --git a/quantum/deutsch_jozsa.py b/quantum/deutsch_jozsa.py.DISABLED.txt old mode 100755 new mode 100644 similarity index 99% rename from quantum/deutsch_jozsa.py rename to quantum/deutsch_jozsa.py.DISABLED.txt index 95c3e65b5edf..5c8a379debfc --- a/quantum/deutsch_jozsa.py +++ b/quantum/deutsch_jozsa.py.DISABLED.txt @@ -1,3 +1,4 @@ +# DISABLED!! #!/usr/bin/env python3 """ Deutsch-Jozsa Algorithm is one of the first examples of a quantum diff --git a/quantum/half_adder.py b/quantum/half_adder.py.DISABLED.txt old mode 100755 new mode 100644 similarity index 99% rename from quantum/half_adder.py rename to quantum/half_adder.py.DISABLED.txt index 21a57ddcf2dd..800d563ec76f --- a/quantum/half_adder.py +++ b/quantum/half_adder.py.DISABLED.txt @@ -1,3 +1,4 @@ +# DISABLED!! #!/usr/bin/env python3 """ Build a half-adder quantum circuit that takes two bits as input, diff --git a/quantum/not_gate.py b/quantum/not_gate.py.DISABLED.txt similarity index 100% rename from quantum/not_gate.py rename to quantum/not_gate.py.DISABLED.txt diff --git a/quantum/q_full_adder.py b/quantum/q_full_adder.py.DISABLED.txt similarity index 100% rename from quantum/q_full_adder.py rename to quantum/q_full_adder.py.DISABLED.txt diff --git a/quantum/quantum_entanglement.py b/quantum/quantum_entanglement.py.DISABLED.txt similarity index 100% rename from quantum/quantum_entanglement.py rename to quantum/quantum_entanglement.py.DISABLED.txt diff --git a/quantum/quantum_teleportation.py b/quantum/quantum_teleportation.py.DISABLED.txt similarity index 100% rename from quantum/quantum_teleportation.py rename to quantum/quantum_teleportation.py.DISABLED.txt diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py.DISABLED.txt similarity index 100% rename from quantum/ripple_adder_classic.py rename to quantum/ripple_adder_classic.py.DISABLED.txt diff --git a/quantum/single_qubit_measure.py b/quantum/single_qubit_measure.py.DISABLED.txt similarity index 100% rename from quantum/single_qubit_measure.py rename to quantum/single_qubit_measure.py.DISABLED.txt diff --git a/quantum/superdense_coding.py b/quantum/superdense_coding.py.DISABLED.txt similarity index 100% rename from quantum/superdense_coding.py rename to quantum/superdense_coding.py.DISABLED.txt diff --git a/requirements.txt b/requirements.txt index 1128e9d66820..25dba6f5a250 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,15 +9,15 @@ opencv-python pandas pillow projectq -qiskit -qiskit-aer +qiskit ; python_version < '3.12' +qiskit-aer ; python_version < '3.12' requests rich scikit-fuzzy scikit-learn statsmodels sympy -tensorflow +tensorflow ; python_version < '3.12' texttable tweepy xgboost From da03c14d39ec8c6a3c253951541b902172bb92fc Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 3 Oct 2023 11:48:58 +0200 Subject: [PATCH 2348/2908] Fix accuracy in maclaurin_series on Python 3.12 (#9581) --- maths/maclaurin_series.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maths/maclaurin_series.py b/maths/maclaurin_series.py index 806e5f9b0788..d5c3c3ab958b 100644 --- a/maths/maclaurin_series.py +++ b/maths/maclaurin_series.py @@ -21,9 +21,9 @@ def maclaurin_sin(theta: float, accuracy: int = 30) -> float: >>> maclaurin_sin(-10) 0.5440211108893704 >>> maclaurin_sin(10, 15) - -0.5440211108893689 + -0.544021110889369 >>> maclaurin_sin(-10, 15) - 0.5440211108893703 + 0.5440211108893704 >>> maclaurin_sin("10") Traceback (most recent call last): ... @@ -73,7 +73,7 @@ def maclaurin_cos(theta: float, accuracy: int = 30) -> float: >>> maclaurin_cos(-5) 0.2836621854632265 >>> maclaurin_cos(10, 15) - -0.8390715290764525 + -0.8390715290764524 >>> maclaurin_cos(-10, 15) -0.8390715290764521 >>> maclaurin_cos("10") From e60779c202880275e786f0f857f4261b90a41d51 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 3 Oct 2023 12:04:59 +0200 Subject: [PATCH 2349/2908] Upgrade our Devcontainer to Python 3.12 on Debian bookworm (#9580) --- .devcontainer/Dockerfile | 2 +- .devcontainer/README.md | 1 + .devcontainer/devcontainer.json | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 .devcontainer/README.md diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b5a5347c66b0..6aa0073bf95b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,5 @@ # https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/README.md -ARG VARIANT=3.11-bookworm +ARG VARIANT=3.12-bookworm FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT} COPY requirements.txt /tmp/pip-tmp/ RUN python3 -m pip install --upgrade pip \ diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 000000000000..ec3cdb61de7a --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1 @@ +https://code.visualstudio.com/docs/devcontainers/tutorial diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c5a855b2550c..ae1d4fb7494d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,10 +4,10 @@ "dockerfile": "Dockerfile", "context": "..", "args": { - // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 + // Update 'VARIANT' to pick a Python version: 3, 3.11, 3.10, 3.9, 3.8 // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local on arm64/Apple Silicon. - "VARIANT": "3.11-bookworm", + "VARIANT": "3.12-bookworm", } }, From b60a94b5b305487ca5f5755ab6de2bf0adeb3d78 Mon Sep 17 00:00:00 2001 From: dekomori_sanae09 Date: Tue, 3 Oct 2023 19:23:27 +0530 Subject: [PATCH 2350/2908] merge double_factorial (#9431) * merge double_factorial * fix ruff error * fix merge issues * change test case * fix import error --- maths/double_factorial.py | 60 +++++++++++++++++++++++++++++ maths/double_factorial_iterative.py | 33 ---------------- maths/double_factorial_recursive.py | 31 --------------- 3 files changed, 60 insertions(+), 64 deletions(-) create mode 100644 maths/double_factorial.py delete mode 100644 maths/double_factorial_iterative.py delete mode 100644 maths/double_factorial_recursive.py diff --git a/maths/double_factorial.py b/maths/double_factorial.py new file mode 100644 index 000000000000..3c3a28304e95 --- /dev/null +++ b/maths/double_factorial.py @@ -0,0 +1,60 @@ +def double_factorial_recursive(n: int) -> int: + """ + Compute double factorial using recursive method. + Recursion can be costly for large numbers. + + To learn about the theory behind this algorithm: + https://en.wikipedia.org/wiki/Double_factorial + + >>> from math import prod + >>> all(double_factorial_recursive(i) == prod(range(i, 0, -2)) for i in range(20)) + True + >>> double_factorial_recursive(0.1) + Traceback (most recent call last): + ... + ValueError: double_factorial_recursive() only accepts integral values + >>> double_factorial_recursive(-1) + Traceback (most recent call last): + ... + ValueError: double_factorial_recursive() not defined for negative values + """ + if not isinstance(n, int): + raise ValueError("double_factorial_recursive() only accepts integral values") + if n < 0: + raise ValueError("double_factorial_recursive() not defined for negative values") + return 1 if n <= 1 else n * double_factorial_recursive(n - 2) + + +def double_factorial_iterative(num: int) -> int: + """ + Compute double factorial using iterative method. + + To learn about the theory behind this algorithm: + https://en.wikipedia.org/wiki/Double_factorial + + >>> from math import prod + >>> all(double_factorial_iterative(i) == prod(range(i, 0, -2)) for i in range(20)) + True + >>> double_factorial_iterative(0.1) + Traceback (most recent call last): + ... + ValueError: double_factorial_iterative() only accepts integral values + >>> double_factorial_iterative(-1) + Traceback (most recent call last): + ... + ValueError: double_factorial_iterative() not defined for negative values + """ + if not isinstance(num, int): + raise ValueError("double_factorial_iterative() only accepts integral values") + if num < 0: + raise ValueError("double_factorial_iterative() not defined for negative values") + value = 1 + for i in range(num, 0, -2): + value *= i + return value + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/double_factorial_iterative.py b/maths/double_factorial_iterative.py deleted file mode 100644 index b2b58aa04c28..000000000000 --- a/maths/double_factorial_iterative.py +++ /dev/null @@ -1,33 +0,0 @@ -def double_factorial(num: int) -> int: - """ - Compute double factorial using iterative method. - - To learn about the theory behind this algorithm: - https://en.wikipedia.org/wiki/Double_factorial - - >>> import math - >>> all(double_factorial(i) == math.prod(range(i, 0, -2)) for i in range(20)) - True - >>> double_factorial(0.1) - Traceback (most recent call last): - ... - ValueError: double_factorial() only accepts integral values - >>> double_factorial(-1) - Traceback (most recent call last): - ... - ValueError: double_factorial() not defined for negative values - """ - if not isinstance(num, int): - raise ValueError("double_factorial() only accepts integral values") - if num < 0: - raise ValueError("double_factorial() not defined for negative values") - value = 1 - for i in range(num, 0, -2): - value *= i - return value - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/maths/double_factorial_recursive.py b/maths/double_factorial_recursive.py deleted file mode 100644 index 05c9b29680a7..000000000000 --- a/maths/double_factorial_recursive.py +++ /dev/null @@ -1,31 +0,0 @@ -def double_factorial(n: int) -> int: - """ - Compute double factorial using recursive method. - Recursion can be costly for large numbers. - - To learn about the theory behind this algorithm: - https://en.wikipedia.org/wiki/Double_factorial - - >>> import math - >>> all(double_factorial(i) == math.prod(range(i, 0, -2)) for i in range(20)) - True - >>> double_factorial(0.1) - Traceback (most recent call last): - ... - ValueError: double_factorial() only accepts integral values - >>> double_factorial(-1) - Traceback (most recent call last): - ... - ValueError: double_factorial() not defined for negative values - """ - if not isinstance(n, int): - raise ValueError("double_factorial() only accepts integral values") - if n < 0: - raise ValueError("double_factorial() not defined for negative values") - return 1 if n <= 1 else n * double_factorial(n - 2) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From 0a84b8f842c4c72f400d96313d992b608d621d07 Mon Sep 17 00:00:00 2001 From: Aasheesh <126905285+AasheeshLikePanner@users.noreply.github.com> Date: Tue, 3 Oct 2023 21:10:11 +0530 Subject: [PATCH 2351/2908] Changing Name of file and adding doctests in file. (#9513) * Adding doctests and changing file name * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update binary_multiplication.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update binary_multiplication.py * Changing comment and changing name function * Changing comment and changing name function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update binary_multiplication.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update binary_multiplication.py * Update binary_multiplication.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/binary_exponentiation_2.py | 50 --------------- maths/binary_multiplication.py | 101 +++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 50 deletions(-) delete mode 100644 maths/binary_exponentiation_2.py create mode 100644 maths/binary_multiplication.py diff --git a/maths/binary_exponentiation_2.py b/maths/binary_exponentiation_2.py deleted file mode 100644 index af8f776dd266..000000000000 --- a/maths/binary_exponentiation_2.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -* Binary Exponentiation with Multiplication -* This is a method to find a*b in a time complexity of O(log b) -* This is one of the most commonly used methods of finding result of multiplication. -* Also useful in cases where solution to (a*b)%c is required, -* where a,b,c can be numbers over the computers calculation limits. -* Done using iteration, can also be done using recursion - -* @author chinmoy159 -* @version 1.0 dated 10/08/2017 -""" - - -def b_expo(a: int, b: int) -> int: - res = 0 - while b > 0: - if b & 1: - res += a - - a += a - b >>= 1 - - return res - - -def b_expo_mod(a: int, b: int, c: int) -> int: - res = 0 - while b > 0: - if b & 1: - res = ((res % c) + (a % c)) % c - - a += a - b >>= 1 - - return res - - -""" -* Wondering how this method works ! -* It's pretty simple. -* Let's say you need to calculate a ^ b -* RULE 1 : a * b = (a+a) * (b/2) ---- example : 4 * 4 = (4+4) * (4/2) = 8 * 2 -* RULE 2 : IF b is ODD, then ---- a * b = a + (a * (b - 1)) :: where (b - 1) is even. -* Once b is even, repeat the process to get a * b -* Repeat the process till b = 1 OR b = 0, because a*1 = a AND a*0 = 0 -* -* As far as the modulo is concerned, -* the fact : (a+b) % c = ((a%c) + (b%c)) % c -* Now apply RULE 1 OR 2, whichever is required. -""" diff --git a/maths/binary_multiplication.py b/maths/binary_multiplication.py new file mode 100644 index 000000000000..0cc5a575f445 --- /dev/null +++ b/maths/binary_multiplication.py @@ -0,0 +1,101 @@ +""" +Binary Multiplication +This is a method to find a*b in a time complexity of O(log b) +This is one of the most commonly used methods of finding result of multiplication. +Also useful in cases where solution to (a*b)%c is required, +where a,b,c can be numbers over the computers calculation limits. +Done using iteration, can also be done using recursion + +Let's say you need to calculate a * b +RULE 1 : a * b = (a+a) * (b/2) ---- example : 4 * 4 = (4+4) * (4/2) = 8 * 2 +RULE 2 : IF b is odd, then ---- a * b = a + (a * (b - 1)), where (b - 1) is even. +Once b is even, repeat the process to get a * b +Repeat the process until b = 1 or b = 0, because a*1 = a and a*0 = 0 + +As far as the modulo is concerned, +the fact : (a+b) % c = ((a%c) + (b%c)) % c +Now apply RULE 1 or 2, whichever is required. + +@author chinmoy159 +""" + + +def binary_multiply(a: int, b: int) -> int: + """ + Multiply 'a' and 'b' using bitwise multiplication. + + Parameters: + a (int): The first number. + b (int): The second number. + + Returns: + int: a * b + + Examples: + >>> binary_multiply(2, 3) + 6 + >>> binary_multiply(5, 0) + 0 + >>> binary_multiply(3, 4) + 12 + >>> binary_multiply(10, 5) + 50 + >>> binary_multiply(0, 5) + 0 + >>> binary_multiply(2, 1) + 2 + >>> binary_multiply(1, 10) + 10 + """ + res = 0 + while b > 0: + if b & 1: + res += a + + a += a + b >>= 1 + + return res + + +def binary_mod_multiply(a: int, b: int, modulus: int) -> int: + """ + Calculate (a * b) % c using binary multiplication and modular arithmetic. + + Parameters: + a (int): The first number. + b (int): The second number. + modulus (int): The modulus. + + Returns: + int: (a * b) % modulus. + + Examples: + >>> binary_mod_multiply(2, 3, 5) + 1 + >>> binary_mod_multiply(5, 0, 7) + 0 + >>> binary_mod_multiply(3, 4, 6) + 0 + >>> binary_mod_multiply(10, 5, 13) + 11 + >>> binary_mod_multiply(2, 1, 5) + 2 + >>> binary_mod_multiply(1, 10, 3) + 1 + """ + res = 0 + while b > 0: + if b & 1: + res = ((res % modulus) + (a % modulus)) % modulus + + a += a + b >>= 1 + + return res + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 81661bd2d0c34363de7d3e1e802fe2f75b9a1fa4 Mon Sep 17 00:00:00 2001 From: Ayush Yadav <115359450+ayush-yadavv@users.noreply.github.com> Date: Wed, 4 Oct 2023 05:17:26 +0530 Subject: [PATCH 2352/2908] Update newtons_law_of_gravitation.py : Typo(Space Removed) (#9351) --- physics/newtons_law_of_gravitation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physics/newtons_law_of_gravitation.py b/physics/newtons_law_of_gravitation.py index 4bbeddd61d5b..ae9da2f1e949 100644 --- a/physics/newtons_law_of_gravitation.py +++ b/physics/newtons_law_of_gravitation.py @@ -3,7 +3,7 @@ provided that the other three parameters are given. Description : Newton's Law of Universal Gravitation explains the presence of force of -attraction between bodies having a definite mass situated at a distance. It is usually +attraction between bodies having a definite mass situated at a distance. It is usually stated as that, every particle attracts every other particle in the universe with a force that is directly proportional to the product of their masses and inversely proportional to the square of the distance between their centers. The publication of the From 12431389e32c290aae8c046ce9d8504d698d5f41 Mon Sep 17 00:00:00 2001 From: "Tan Kai Qun, Jeremy" Date: Wed, 4 Oct 2023 10:47:03 +0900 Subject: [PATCH 2353/2908] Add typing to topological_sort.py (#9650) * Add typing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Jeremy Tan Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sorts/topological_sort.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py index 59a0c8571b53..efce8165fcac 100644 --- a/sorts/topological_sort.py +++ b/sorts/topological_sort.py @@ -5,11 +5,17 @@ # b c # / \ # d e -edges = {"a": ["c", "b"], "b": ["d", "e"], "c": [], "d": [], "e": []} -vertices = ["a", "b", "c", "d", "e"] +edges: dict[str, list[str]] = { + "a": ["c", "b"], + "b": ["d", "e"], + "c": [], + "d": [], + "e": [], +} +vertices: list[str] = ["a", "b", "c", "d", "e"] -def topological_sort(start, visited, sort): +def topological_sort(start: str, visited: list[str], sort: list[str]) -> list[str]: """Perform topological sort on a directed acyclic graph.""" current = start # add current to visited From 28f1e68f005f99eb628efd1af899bdfe1c1bc99e Mon Sep 17 00:00:00 2001 From: "Tan Kai Qun, Jeremy" Date: Wed, 4 Oct 2023 11:05:47 +0900 Subject: [PATCH 2354/2908] Add typing (#9651) Co-authored-by: Jeremy Tan --- sorts/stooge_sort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorts/stooge_sort.py b/sorts/stooge_sort.py index 9a5bedeae21b..767c6a05924f 100644 --- a/sorts/stooge_sort.py +++ b/sorts/stooge_sort.py @@ -1,4 +1,4 @@ -def stooge_sort(arr): +def stooge_sort(arr: list[int]) -> list[int]: """ Examples: >>> stooge_sort([18.1, 0, -7.1, -1, 2, 2]) @@ -11,7 +11,7 @@ def stooge_sort(arr): return arr -def stooge(arr, i, h): +def stooge(arr: list[int], i: int, h: int) -> None: if i >= h: return From a7133eca13d312fa729e2872048c7d9a662f6c8c Mon Sep 17 00:00:00 2001 From: "Tan Kai Qun, Jeremy" Date: Wed, 4 Oct 2023 11:06:52 +0900 Subject: [PATCH 2355/2908] Add typing (#9652) Co-authored-by: Jeremy Tan --- sorts/shell_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/shell_sort.py b/sorts/shell_sort.py index 10ae9ba407ec..b65609c974b7 100644 --- a/sorts/shell_sort.py +++ b/sorts/shell_sort.py @@ -3,7 +3,7 @@ """ -def shell_sort(collection): +def shell_sort(collection: list[int]) -> list[int]: """Pure implementation of shell sort algorithm in Python :param collection: Some mutable ordered collection with heterogeneous comparable items inside From 8c23cc5117b338ea907045260274ac40301a4e0e Mon Sep 17 00:00:00 2001 From: "Tan Kai Qun, Jeremy" Date: Wed, 4 Oct 2023 11:07:25 +0900 Subject: [PATCH 2356/2908] Add typing (#9654) Co-authored-by: Jeremy Tan --- sorts/selection_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/selection_sort.py b/sorts/selection_sort.py index f3beb31b7070..28971a5e1aad 100644 --- a/sorts/selection_sort.py +++ b/sorts/selection_sort.py @@ -11,7 +11,7 @@ """ -def selection_sort(collection): +def selection_sort(collection: list[int]) -> list[int]: """Pure implementation of the selection sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous comparable items inside From 700df39ad446da895d413c0383632871459f0e9f Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Wed, 4 Oct 2023 09:04:55 +0530 Subject: [PATCH 2357/2908] Fixed file name in transposition_cipher_encrypt_decrypt_file.py. Fixing bug file not found. (#9426) * Fixed file name in trnasposition_cipher_encrypt_decrypt_file.py * Removed Output.txt * Removed Output.txt * Fixed build errors --- ciphers/prehistoric_men.txt | 1196 ++++++++--------- ...ansposition_cipher_encrypt_decrypt_file.py | 4 +- 2 files changed, 600 insertions(+), 600 deletions(-) diff --git a/ciphers/prehistoric_men.txt b/ciphers/prehistoric_men.txt index a58e533a8405..8d1b2bd8c8d1 100644 --- a/ciphers/prehistoric_men.txt +++ b/ciphers/prehistoric_men.txt @@ -40,8 +40,8 @@ Transcriber's note: version referred to above. One example of this might occur in the second paragraph under "Choppers and Adze-like Tools", page 46, which contains the phrase - �an adze cutting edge is ? shaped�. The symbol before - �shaped� looks like a sharply-italicized sans-serif �L�. + �an adze cutting edge is ? shaped�. The symbol before + �shaped� looks like a sharply-italicized sans-serif �L�. Devices that cannot display that symbol may substitute a question mark, a square, or other symbol. @@ -98,7 +98,7 @@ forced or pedantic; at least I have done my very best to tell the story simply and clearly. Many friends have aided in the preparation of the book. The whimsical -charm of Miss Susan Richert�s illustrations add enormously to the +charm of Miss Susan Richert�s illustrations add enormously to the spirit I wanted. She gave freely of her own time on the drawings and in planning the book with me. My colleagues at the University of Chicago, especially Professor Wilton M. Krogman (now of the University @@ -108,7 +108,7 @@ the Department of Anthropology, gave me counsel in matters bearing on their special fields, and the Department of Anthropology bore some of the expense of the illustrations. From Mrs. Irma Hunter and Mr. Arnold Maremont, who are not archeologists at all and have only an intelligent -layman�s notion of archeology, I had sound advice on how best to tell +layman�s notion of archeology, I had sound advice on how best to tell the story. I am deeply indebted to all these friends. While I was preparing the second edition, I had the great fortune @@ -117,13 +117,13 @@ Washburn, now of the Department of Anthropology of the University of California, and the fourth, fifth, and sixth chapters with Professor Hallum L. Movius, Jr., of the Peabody Museum, Harvard University. The book has gained greatly in accuracy thereby. In matters of dating, -Professor Movius and the indications of Professor W. F. Libby�s Carbon +Professor Movius and the indications of Professor W. F. Libby�s Carbon 14 chronology project have both encouraged me to choose the lowest dates now current for the events of the Pleistocene Ice Age. There is still no certain way of fixing a direct chronology for most of the -Pleistocene, but Professor Libby�s method appears very promising for +Pleistocene, but Professor Libby�s method appears very promising for its end range and for proto-historic dates. In any case, this book -names �periods,� and new dates may be written in against mine, if new +names �periods,� and new dates may be written in against mine, if new and better dating systems appear. I wish to thank Dr. Clifford C. Gregg, Director of Chicago Natural @@ -150,7 +150,7 @@ Clark Howell of the Department of Anthropology of the University of Chicago in reworking the earlier chapters, and he was very patient in the matter, which I sincerely appreciate. -All of Mrs. Susan Richert Allen�s original drawings appear, but a few +All of Mrs. Susan Richert Allen�s original drawings appear, but a few necessary corrections have been made in some of the charts and some new drawings have been added by Mr. John Pfiffner, Staff Artist, Chicago Natural History Museum. @@ -200,7 +200,7 @@ HOW WE LEARN about Prehistoric Men Prehistory means the time before written history began. Actually, more -than 99 per cent of man�s story is prehistory. Man is at least half a +than 99 per cent of man�s story is prehistory. Man is at least half a million years old, but he did not begin to write history (or to write anything) until about 5,000 years ago. @@ -216,7 +216,7 @@ The scientists who study the bones and teeth and any other parts they find of the bodies of prehistoric men, are called _physical anthropologists_. Physical anthropologists are trained, much like doctors, to know all about the human body. They study living people, -too; they know more about the biological facts of human �races� than +too; they know more about the biological facts of human �races� than anybody else. If the police find a badly decayed body in a trunk, they ask a physical anthropologist to tell them what the person originally looked like. The physical anthropologists who specialize in @@ -228,14 +228,14 @@ ARCHEOLOGISTS There is a kind of scientist who studies the things that prehistoric men made and did. Such a scientist is called an _archeologist_. It is -the archeologist�s business to look for the stone and metal tools, the +the archeologist�s business to look for the stone and metal tools, the pottery, the graves, and the caves or huts of the men who lived before history began. But there is more to archeology than just looking for things. In -Professor V. Gordon Childe�s words, archeology �furnishes a sort of +Professor V. Gordon Childe�s words, archeology �furnishes a sort of history of human activity, provided always that the actions have -produced concrete results and left recognizable material traces.� You +produced concrete results and left recognizable material traces.� You will see that there are at least three points in what Childe says: 1. The archeologists have to find the traces of things left behind by @@ -245,7 +245,7 @@ will see that there are at least three points in what Childe says: too soft or too breakable to last through the years. However, 3. The archeologist must use whatever he can find to tell a story--to - make a �sort of history�--from the objects and living-places and + make a �sort of history�--from the objects and living-places and graves that have escaped destruction. What I mean is this: Let us say you are walking through a dump yard, @@ -253,8 +253,8 @@ and you find a rusty old spark plug. If you want to think about what the spark plug means, you quickly remember that it is a part of an automobile motor. This tells you something about the man who threw the spark plug on the dump. He either had an automobile, or he knew -or lived near someone who did. He can�t have lived so very long ago, -you�ll remember, because spark plugs and automobiles are only about +or lived near someone who did. He can�t have lived so very long ago, +you�ll remember, because spark plugs and automobiles are only about sixty years old. When you think about the old spark plug in this way you have @@ -264,8 +264,8 @@ It is the same way with the man-made things we archeologists find and put in museums. Usually, only a few of these objects are pretty to look at; but each of them has some sort of story to tell. Making the interpretation of his finds is the most important part of the -archeologist�s job. It is the way he gets at the �sort of history of -human activity� which is expected of archeology. +archeologist�s job. It is the way he gets at the �sort of history of +human activity� which is expected of archeology. SOME OTHER SCIENTISTS @@ -274,7 +274,7 @@ There are many other scientists who help the archeologist and the physical anthropologist find out about prehistoric men. The geologists help us tell the age of the rocks or caves or gravel beds in which human bones or man-made objects are found. There are other scientists -with names which all begin with �paleo� (the Greek word for �old�). The +with names which all begin with �paleo� (the Greek word for �old�). The _paleontologists_ study fossil animals. There are also, for example, such scientists as _paleobotanists_ and _paleoclimatologists_, who study ancient plants and climates. These scientists help us to know @@ -306,20 +306,20 @@ systems. The rate of disappearance of radioactivity as time passes.[1]] [1] It is important that the limitations of the radioactive carbon - �dating� system be held in mind. As the statistics involved in + �dating� system be held in mind. As the statistics involved in the system are used, there are two chances in three that the - �date� of the sample falls within the range given as plus or - minus an added number of years. For example, the �date� for the - Jarmo village (see chart), given as 6750 � 200 B.C., really + �date� of the sample falls within the range given as plus or + minus an added number of years. For example, the �date� for the + Jarmo village (see chart), given as 6750 � 200 B.C., really means that there are only two chances in three that the real date of the charcoal sampled fell between 6950 and 6550 B.C. We have also begun to suspect that there are ways in which the - samples themselves may have become �contaminated,� either on + samples themselves may have become �contaminated,� either on the early or on the late side. We now tend to be suspicious of single radioactive carbon determinations, or of determinations from one site alone. But as a fabric of consistent determinations for several or more sites of one archeological - period, we gain confidence in the �dates.� + period, we gain confidence in the dates. HOW THE SCIENTISTS FIND OUT @@ -330,9 +330,9 @@ about prehistoric men. We also need a word about _how_ they find out. All our finds came by accident until about a hundred years ago. Men digging wells, or digging in caves for fertilizer, often turned up ancient swords or pots or stone arrowheads. People also found some odd -pieces of stone that didn�t look like natural forms, but they also -didn�t look like any known tool. As a result, the people who found them -gave them queer names; for example, �thunderbolts.� The people thought +pieces of stone that didn�t look like natural forms, but they also +didn�t look like any known tool. As a result, the people who found them +gave them queer names; for example, �thunderbolts.� The people thought the strange stones came to earth as bolts of lightning. We know now that these strange stones were prehistoric stone tools. @@ -349,7 +349,7 @@ story of cave men on Mount Carmel, in Palestine, began to be known. Planned archeological digging is only about a century old. Even before this, however, a few men realized the significance of objects they dug from the ground; one of these early archeologists was our own Thomas -Jefferson. The first real mound-digger was a German grocer�s clerk, +Jefferson. The first real mound-digger was a German grocer�s clerk, Heinrich Schliemann. Schliemann made a fortune as a merchant, first in Europe and then in the California gold-rush of 1849. He became an American citizen. Then he retired and had both money and time to test @@ -389,16 +389,16 @@ used had been a soft, unbaked mud-brick, and most of the debris consisted of fallen or rain-melted mud from these mud-bricks. This idea of _stratification_, like the cake layers, was already a -familiar one to the geologists by Schliemann�s time. They could show +familiar one to the geologists by Schliemann�s time. They could show that their lowest layer of rock was oldest or earliest, and that the -overlying layers became more recent as one moved upward. Schliemann�s +overlying layers became more recent as one moved upward. Schliemann�s digging proved the same thing at Troy. His first (lowest and earliest) city had at least nine layers above it; he thought that the second -layer contained the remains of Homer�s Troy. We now know that Homeric +layer contained the remains of Homer�s Troy. We now know that Homeric Troy was layer VIIa from the bottom; also, we count eleven layers or sub-layers in total. -Schliemann�s work marks the beginnings of modern archeology. Scholars +Schliemann�s work marks the beginnings of modern archeology. Scholars soon set out to dig on ancient sites, from Egypt to Central America. @@ -410,21 +410,21 @@ Archeologists began to get ideas as to the kinds of objects that belonged together. If you compared a mail-order catalogue of 1890 with one of today, you would see a lot of differences. If you really studied the two catalogues hard, you would also begin to see that certain -objects �go together.� Horseshoes and metal buggy tires and pieces of +objects �go together.� Horseshoes and metal buggy tires and pieces of harness would begin to fit into a picture with certain kinds of coal stoves and furniture and china dishes and kerosene lamps. Our friend the spark plug, and radios and electric refrigerators and light bulbs would fit into a picture with different kinds of furniture and dishes -and tools. You won�t be old enough to remember the kind of hats that -women wore in 1890, but you�ve probably seen pictures of them, and you -know very well they couldn�t be worn with the fashions of today. +and tools. You won�t be old enough to remember the kind of hats that +women wore in 1890, but you�ve probably seen pictures of them, and you +know very well they couldn�t be worn with the fashions of today. This is one of the ways that archeologists study their materials. The various tools and weapons and jewelry, the pottery, the kinds of houses, and even the ways of burying the dead tend to fit into pictures. Some archeologists call all of the things that go together to make such a picture an _assemblage_. The assemblage of the first layer -of Schliemann�s Troy was as different from that of the seventh layer as +of Schliemann�s Troy was as different from that of the seventh layer as our 1900 mail-order catalogue is from the one of today. The archeologists who came after Schliemann began to notice other @@ -433,23 +433,23 @@ idea that people will buy better mousetraps goes back into very ancient times. Today, if we make good automobiles or radios, we can sell some of them in Turkey or even in Timbuktu. This means that a few present-day types of American automobiles and radios form part -of present-day �assemblages� in both Turkey and Timbuktu. The total -present-day �assemblage� of Turkey is quite different from that of +of present-day �assemblages� in both Turkey and Timbuktu. The total +present-day �assemblage� of Turkey is quite different from that of Timbuktu or that of America, but they have at least some automobiles and some radios in common. Now these automobiles and radios will eventually wear out. Let us suppose we could go to some remote part of Turkey or to Timbuktu in a -dream. We don�t know what the date is, in our dream, but we see all +dream. We don�t know what the date is, in our dream, but we see all sorts of strange things and ways of living in both places. Nobody tells us what the date is. But suddenly we see a 1936 Ford; so we know that in our dream it has to be at least the year 1936, and only as many years after that as we could reasonably expect a Ford to keep -in running order. The Ford would probably break down in twenty years� -time, so the Turkish or Timbuktu �assemblage� we�re seeing in our dream +in running order. The Ford would probably break down in twenty years� +time, so the Turkish or Timbuktu �assemblage� we�re seeing in our dream has to date at about A.D. 1936-56. -Archeologists not only �date� their ancient materials in this way; they +Archeologists not only �date� their ancient materials in this way; they also see over what distances and between which peoples trading was done. It turns out that there was a good deal of trading in ancient times, probably all on a barter and exchange basis. @@ -480,13 +480,13 @@ site. They find the remains of everything that would last through time, in several different layers. They know that the assemblage in the bottom layer was laid down earlier than the assemblage in the next layer above, and so on up to the topmost layer, which is the latest. -They look at the results of other �digs� and find that some other +They look at the results of other �digs� and find that some other archeologist 900 miles away has found ax-heads in his lowest layer, exactly like the ax-heads of their fifth layer. This means that their fifth layer must have been lived in at about the same time as was the first layer in the site 200 miles away. It also may mean that the people who lived in the two layers knew and traded with each other. Or -it could mean that they didn�t necessarily know each other, but simply +it could mean that they didn�t necessarily know each other, but simply that both traded with a third group at about the same time. You can see that the more we dig and find, the more clearly the main @@ -501,8 +501,8 @@ those of domesticated animals, for instance, sheep or cattle, and therefore the people must have kept herds. More important than anything else--as our structure grows more -complicated and our materials increase--is the fact that �a sort -of history of human activity� does begin to appear. The habits or +complicated and our materials increase--is the fact that �a sort +of history of human activity� does begin to appear. The habits or traditions that men formed in the making of their tools and in the ways they did things, begin to stand out for us. How characteristic were these habits and traditions? What areas did they spread over? @@ -519,7 +519,7 @@ method--chemical tests of the bones--that will enable them to discover what the blood-type may have been. One thing is sure. We have never found a group of skeletons so absolutely similar among themselves--so cast from a single mould, so to speak--that we could claim to have a -�pure� race. I am sure we never shall. +�pure� race. I am sure we never shall. We become particularly interested in any signs of change--when new materials and tool types and ways of doing things replace old ones. We @@ -527,7 +527,7 @@ watch for signs of social change and progress in one way or another. We must do all this without one word of written history to aid us. Everything we are concerned with goes back to the time _before_ men -learned to write. That is the prehistorian�s job--to find out what +learned to write. That is the prehistorian�s job--to find out what happened before history began. @@ -538,9 +538,9 @@ THE CHANGING WORLD in which Prehistoric Men Lived [Illustration] -Mankind, we�ll say, is at least a half million years old. It is very +Mankind, we�ll say, is at least a half million years old. It is very hard to understand how long a time half a million years really is. -If we were to compare this whole length of time to one day, we�d get +If we were to compare this whole length of time to one day, we�d get something like this: The present time is midnight, and Jesus was born just five minutes and thirty-six seconds ago. Earliest history began less than fifteen minutes ago. Everything before 11:45 was in @@ -569,7 +569,7 @@ book; it would mainly affect the dates earlier than 25,000 years ago. CHANGES IN ENVIRONMENT -The earth probably hasn�t changed much in the last 5,000 years (250 +The earth probably hasn�t changed much in the last 5,000 years (250 generations). Men have built things on its surface and dug into it and drawn boundaries on maps of it, but the places where rivers, lakes, seas, and mountains now stand have changed very little. @@ -605,7 +605,7 @@ the glaciers covered most of Canada and the northern United States and reached down to southern England and France in Europe. Smaller ice sheets sat like caps on the Rockies, the Alps, and the Himalayas. The continental glaciation only happened north of the equator, however, so -remember that �Ice Age� is only half true. +remember that �Ice Age� is only half true. As you know, the amount of water on and about the earth does not vary. These large glaciers contained millions of tons of water frozen into @@ -677,9 +677,9 @@ their dead. At about the time when the last great glacier was finally melting away, men in the Near East made the first basic change in human economy. They began to plant grain, and they learned to raise and herd certain -animals. This meant that they could store food in granaries and �on the -hoof� against the bad times of the year. This first really basic change -in man�s way of living has been called the �food-producing revolution.� +animals. This meant that they could store food in granaries and �on the +hoof� against the bad times of the year. This first really basic change +in man�s way of living has been called the �food-producing revolution.� By the time it happened, a modern kind of climate was beginning. Men had already grown to look as they do now. Know-how in ways of living had developed and progressed, slowly but surely, up to a point. It was @@ -698,25 +698,25 @@ Prehistoric Men THEMSELVES DO WE KNOW WHERE MAN ORIGINATED? -For a long time some scientists thought the �cradle of mankind� was in +For a long time some scientists thought the �cradle of mankind� was in central Asia. Other scientists insisted it was in Africa, and still -others said it might have been in Europe. Actually, we don�t know -where it was. We don�t even know that there was only _one_ �cradle.� -If we had to choose a �cradle� at this moment, we would probably say +others said it might have been in Europe. Actually, we don�t know +where it was. We don�t even know that there was only _one_ �cradle.� +If we had to choose a �cradle� at this moment, we would probably say Africa. But the southern portions of Asia and Europe may also have been included in the general area. The scene of the early development of -mankind was certainly the Old World. It is pretty certain men didn�t +mankind was certainly the Old World. It is pretty certain men didn�t reach North or South America until almost the end of the Ice Age--had they done so earlier we would certainly have found some trace of them by now. The earliest tools we have yet found come from central and south -Africa. By the dating system I�m using, these tools must be over +Africa. By the dating system I�m using, these tools must be over 500,000 years old. There are now reports that a few such early tools have been found--at the Sterkfontein cave in South Africa--along with -the bones of small fossil men called �australopithecines.� +the bones of small fossil men called �australopithecines.� -Not all scientists would agree that the australopithecines were �men,� +Not all scientists would agree that the australopithecines were �men,� or would agree that the tools were made by the australopithecines themselves. For these sticklers, the earliest bones of men come from the island of Java. The date would be about 450,000 years ago. So far, @@ -727,12 +727,12 @@ Let me say it another way. How old are the earliest traces of men we now have? Over half a million years. This was a time when the first alpine glaciation was happening in the north. What has been found so far? The tools which the men of those times made, in different parts -of Africa. It is now fairly generally agreed that the �men� who made -the tools were the australopithecines. There is also a more �man-like� +of Africa. It is now fairly generally agreed that the �men� who made +the tools were the australopithecines. There is also a more �man-like� jawbone at Kanam in Kenya, but its find-spot has been questioned. The next earliest bones we have were found in Java, and they may be almost a hundred thousand years younger than the earliest African finds. We -haven�t yet found the tools of these early Javanese. Our knowledge of +haven�t yet found the tools of these early Javanese. Our knowledge of tool-using in Africa spreads quickly as time goes on: soon after the appearance of tools in the south we shall have them from as far north as Algeria. @@ -758,30 +758,30 @@ prove it. MEN AND APES Many people used to get extremely upset at the ill-formed notion -that �man descended from the apes.� Such words were much more likely -to start fights or �monkey trials� than the correct notion that all +that �man descended from the apes.� Such words were much more likely +to start fights or �monkey trials� than the correct notion that all living animals, including man, ascended or evolved from a single-celled organism which lived in the primeval seas hundreds of millions of years -ago. Men are mammals, of the order called Primates, and man�s living -relatives are the great apes. Men didn�t �descend� from the apes or +ago. Men are mammals, of the order called Primates, and man�s living +relatives are the great apes. Men didn�t �descend� from the apes or apes from men, and mankind must have had much closer relatives who have since become extinct. Men stand erect. They also walk and run on their two feet. Apes are happiest in trees, swinging with their arms from branch to branch. Few branches of trees will hold the mighty gorilla, although he still -manages to sleep in trees. Apes can�t stand really erect in our sense, +manages to sleep in trees. Apes can�t stand really erect in our sense, and when they have to run on the ground, they use the knuckles of their hands as well as their feet. A key group of fossil bones here are the south African australopithecines. These are called the _Australopithecinae_ or -�man-apes� or sometimes even �ape-men.� We do not _know_ that they were +�man-apes� or sometimes even �ape-men.� We do not _know_ that they were directly ancestral to men but they can hardly have been so to apes. -Presently I�ll describe them a bit more. The reason I mention them +Presently I�ll describe them a bit more. The reason I mention them here is that while they had brains no larger than those of apes, their hipbones were enough like ours so that they must have stood erect. -There is no good reason to think they couldn�t have walked as we do. +There is no good reason to think they couldn�t have walked as we do. BRAINS, HANDS, AND TOOLS @@ -801,12 +801,12 @@ Nobody knows which of these three is most important, or which came first. Most probably the growth of all three things was very much blended together. If you think about each of the things, you will see what I mean. Unless your hand is more flexible than a paw, and your -thumb will work against (or oppose) your fingers, you can�t hold a tool -very well. But you wouldn�t get the idea of using a tool unless you had +thumb will work against (or oppose) your fingers, you can�t hold a tool +very well. But you wouldn�t get the idea of using a tool unless you had enough brain to help you see cause and effect. And it is rather hard to see how your hand and brain would develop unless they had something to -practice on--like using tools. In Professor Krogman�s words, �the hand -must become the obedient servant of the eye and the brain.� It is the +practice on--like using tools. In Professor Krogman�s words, �the hand +must become the obedient servant of the eye and the brain.� It is the _co-ordination_ of these things that counts. Many other things must have been happening to the bodies of the @@ -820,17 +820,17 @@ little by little, all together. Men became men very slowly. WHEN SHALL WE CALL MEN MEN? -What do I mean when I say �men�? People who looked pretty much as we +What do I mean when I say �men�? People who looked pretty much as we do, and who used different tools to do different things, are men to me. -We�ll probably never know whether the earliest ones talked or not. They +We�ll probably never know whether the earliest ones talked or not. They probably had vocal cords, so they could make sounds, but did they know how to make sounds work as symbols to carry meanings? But if the fossil -bones look like our skeletons, and if we find tools which we�ll agree -couldn�t have been made by nature or by animals, then I�d say we had +bones look like our skeletons, and if we find tools which we�ll agree +couldn�t have been made by nature or by animals, then I�d say we had traces of _men_. The australopithecine finds of the Transvaal and Bechuanaland, in -south Africa, are bound to come into the discussion here. I�ve already +south Africa, are bound to come into the discussion here. I�ve already told you that the australopithecines could have stood upright and walked on their two hind legs. They come from the very base of the Pleistocene or Ice Age, and a few coarse stone tools have been found @@ -848,17 +848,17 @@ bones. The doubt as to whether the australopithecines used the tools themselves goes like this--just suppose some man-like creature (whose bones we have not yet found) made the tools and used them to kill and butcher australopithecines. Hence a few experts tend to let -australopithecines still hang in limbo as �man-apes.� +australopithecines still hang in limbo as �man-apes.� THE EARLIEST MEN WE KNOW -I�ll postpone talking about the tools of early men until the next +I�ll postpone talking about the tools of early men until the next chapter. The men whose bones were the earliest of the Java lot have been given the name _Meganthropus_. The bones are very fragmentary. We would not understand them very well unless we had the somewhat later -Javanese lot--the more commonly known _Pithecanthropus_ or �Java -man�--against which to refer them for study. One of the less well-known +Javanese lot--the more commonly known _Pithecanthropus_ or �Java +man�--against which to refer them for study. One of the less well-known and earliest fragments, a piece of lower jaw and some teeth, rather strongly resembles the lower jaws and teeth of the australopithecine type. Was _Meganthropus_ a sort of half-way point between the @@ -872,7 +872,7 @@ finds of Java man were made in 1891-92 by Dr. Eugene Dubois, a Dutch doctor in the colonial service. Finds have continued to be made. There are now bones enough to account for four skulls. There are also four jaws and some odd teeth and thigh bones. Java man, generally speaking, -was about five feet six inches tall, and didn�t hold his head very +was about five feet six inches tall, and didn�t hold his head very erect. His skull was very thick and heavy and had room for little more than two-thirds as large a brain as we have. He had big teeth and a big jaw and enormous eyebrow ridges. @@ -885,22 +885,22 @@ belonged to his near descendants. Remember that there are several varieties of men in the whole early Java lot, at least two of which are earlier than the _Pithecanthropus_, -�Java man.� Some of the earlier ones seem to have gone in for +�Java man.� Some of the earlier ones seem to have gone in for bigness, in tooth-size at least. _Meganthropus_ is one of these earlier varieties. As we said, he _may_ turn out to be a link to the australopithecines, who _may_ or _may not_ be ancestral to men. _Meganthropus_ is best understandable in terms of _Pithecanthropus_, who appeared later in the same general area. _Pithecanthropus_ is pretty well understandable from the bones he left us, and also because -of his strong resemblance to the fully tool-using cave-dwelling �Peking -man,� _Sinanthropus_, about whom we shall talk next. But you can see +of his strong resemblance to the fully tool-using cave-dwelling �Peking +man,� _Sinanthropus_, about whom we shall talk next. But you can see that the physical anthropologists and prehistoric archeologists still have a lot of work to do on the problem of earliest men. PEKING MEN AND SOME EARLY WESTERNERS -The earliest known Chinese are called _Sinanthropus_, or �Peking man,� +The earliest known Chinese are called _Sinanthropus_, or �Peking man,� because the finds were made near that city. In World War II, the United States Marine guard at our Embassy in Peking tried to help get the bones out of the city before the Japanese attack. Nobody knows where @@ -913,9 +913,9 @@ casts of the bones. Peking man lived in a cave in a limestone hill, made tools, cracked animal bones to get the marrow out, and used fire. Incidentally, the bones of Peking man were found because Chinese dig for what they call -�dragon bones� and �dragon teeth.� Uneducated Chinese buy these things +�dragon bones� and �dragon teeth.� Uneducated Chinese buy these things in their drug stores and grind them into powder for medicine. The -�dragon teeth� and �bones� are really fossils of ancient animals, and +�dragon teeth� and �bones� are really fossils of ancient animals, and sometimes of men. The people who supply the drug stores have learned where to dig for strange bones and teeth. Paleontologists who get to China go to the drug stores to buy fossils. In a roundabout way, this @@ -924,7 +924,7 @@ is how the fallen-in cave of Peking man at Choukoutien was discovered. Peking man was not quite as tall as Java man but he probably stood straighter. His skull looked very much like that of the Java skull except that it had room for a slightly larger brain. His face was less -brutish than was Java man�s face, but this isn�t saying much. +brutish than was Java man�s face, but this isn�t saying much. Peking man dates from early in the interglacial period following the second alpine glaciation. He probably lived close to 350,000 years @@ -946,9 +946,9 @@ big ridges over the eyes. The more fragmentary skull from Swanscombe in England (p. 11) has been much more carefully studied. Only the top and back of that skull have been found. Since the skull rounds up nicely, it has been assumed that the face and forehead must have been quite -�modern.� Careful comparison with Steinheim shows that this was not +�modern.� Careful comparison with Steinheim shows that this was not necessarily so. This is important because it bears on the question of -how early truly �modern� man appeared. +how early truly �modern� man appeared. Recently two fragmentary jaws were found at Ternafine in Algeria, northwest Africa. They look like the jaws of Peking man. Tools were @@ -971,22 +971,22 @@ modern Australian natives. During parts of the Ice Age there was a land bridge all the way from Java to Australia. -TWO ENGLISHMEN WHO WEREN�T OLD +TWO ENGLISHMEN WHO WEREN�T OLD The older textbooks contain descriptions of two English finds which were thought to be very old. These were called Piltdown (_Eoanthropus dawsoni_) and Galley Hill. The skulls were very modern in appearance. In 1948-49, British scientists began making chemical tests which proved that neither of these finds is very old. It is now known that both -�Piltdown man� and the tools which were said to have been found with +�Piltdown man� and the tools which were said to have been found with him were part of an elaborate fake! -TYPICAL �CAVE MEN� +TYPICAL �CAVE MEN� The next men we have to talk about are all members of a related group. -These are the Neanderthal group. �Neanderthal man� himself was found in -the Neander Valley, near D�sseldorf, Germany, in 1856. He was the first +These are the Neanderthal group. �Neanderthal man� himself was found in +the Neander Valley, near D�sseldorf, Germany, in 1856. He was the first human fossil to be recognized as such. [Illustration: PRINCIPAL KNOWN TYPES OF FOSSIL MEN @@ -999,7 +999,7 @@ human fossil to be recognized as such. PITHECANTHROPUS] Some of us think that the neanderthaloids proper are only those people -of western Europe who didn�t get out before the beginning of the last +of western Europe who didn�t get out before the beginning of the last great glaciation, and who found themselves hemmed in by the glaciers in the Alps and northern Europe. Being hemmed in, they intermarried a bit too much and developed into a special type. Professor F. Clark @@ -1010,7 +1010,7 @@ pre-neanderthaloids. There are traces of these pre-neanderthaloids pretty much throughout Europe during the third interglacial period--say 100,000 years ago. The pre-neanderthaloids are represented by such finds as the ones at Ehringsdorf in Germany and Saccopastore in Italy. -I won�t describe them for you, since they are simply less extreme than +I won�t describe them for you, since they are simply less extreme than the neanderthaloids proper--about half way between Steinheim and the classic Neanderthal people. @@ -1019,24 +1019,24 @@ get caught in the pocket of the southwest corner of Europe at the onset of the last great glaciation became the classic Neanderthalers. Out in the Near East, Howell thinks, it is possible to see traces of people evolving from the pre-neanderthaloid type toward that of fully modern -man. Certainly, we don�t see such extreme cases of �neanderthaloidism� +man. Certainly, we don�t see such extreme cases of �neanderthaloidism� outside of western Europe. There are at least a dozen good examples in the main or classic Neanderthal group in Europe. They date to just before and in the earlier part of the last great glaciation (85,000 to 40,000 years ago). -Many of the finds have been made in caves. The �cave men� the movies +Many of the finds have been made in caves. The �cave men� the movies and the cartoonists show you are probably meant to be Neanderthalers. -I�m not at all sure they dragged their women by the hair; the women +I�m not at all sure they dragged their women by the hair; the women were probably pretty tough, too! Neanderthal men had large bony heads, but plenty of room for brains. Some had brain cases even larger than the average for modern man. Their faces were heavy, and they had eyebrow ridges of bone, but the ridges were not as big as those of Java man. Their foreheads were very low, -and they didn�t have much chin. They were about five feet three inches -tall, but were heavy and barrel-chested. But the Neanderthalers didn�t -slouch as much as they�ve been blamed for, either. +and they didn�t have much chin. They were about five feet three inches +tall, but were heavy and barrel-chested. But the Neanderthalers didn�t +slouch as much as they�ve been blamed for, either. One important thing about the Neanderthal group is that there is a fair number of them to study. Just as important is the fact that we know @@ -1059,10 +1059,10 @@ different-looking people. EARLY MODERN MEN -How early is modern man (_Homo sapiens_), the �wise man�? Some people +How early is modern man (_Homo sapiens_), the �wise man�? Some people have thought that he was very early, a few still think so. Piltdown and Galley Hill, which were quite modern in anatomical appearance and -_supposedly_ very early in date, were the best �evidence� for very +_supposedly_ very early in date, were the best �evidence� for very early modern men. Now that Piltdown has been liquidated and Galley Hill is known to be very late, what is left of the idea? @@ -1073,13 +1073,13 @@ the Ternafine jaws, you might come to the conclusion that the crown of the Swanscombe head was that of a modern-like man. Two more skulls, again without faces, are available from a French -cave site, Font�chevade. They come from the time of the last great +cave site, Font�chevade. They come from the time of the last great interglacial, as did the pre-neanderthaloids. The crowns of the -Font�chevade skulls also look quite modern. There is a bit of the +Font�chevade skulls also look quite modern. There is a bit of the forehead preserved on one of these skulls and the brow-ridge is not heavy. Nevertheless, there is a suggestion that the bones belonged to an immature individual. In this case, his (or even more so, if _her_) -brow-ridges would have been weak anyway. The case for the Font�chevade +brow-ridges would have been weak anyway. The case for the Font�chevade fossils, as modern type men, is little stronger than that for Swanscombe, although Professor Vallois believes it a good case. @@ -1101,8 +1101,8 @@ of the onset of colder weather, when the last glaciation was beginning in the north--say 75,000 years ago. The 70 per cent modern group came from only one cave, Mugharet es-Skhul -(�cave of the kids�). The other group, from several caves, had bones of -men of the type we�ve been calling pre-neanderthaloid which we noted +(�cave of the kids�). The other group, from several caves, had bones of +men of the type we�ve been calling pre-neanderthaloid which we noted were widespread in Europe and beyond. The tools which came with each of these finds were generally similar, and McCown and Keith, and other scholars since their study, have tended to assume that both the Skhul @@ -1131,26 +1131,26 @@ important fossil men of later Europe are shown in the chart on page DIFFERENCES IN THE EARLY MODERNS The main early European moderns have been divided into two groups, the -Cro-Magnon group and the Combe Capelle-Br�nn group. Cro-Magnon people +Cro-Magnon group and the Combe Capelle-Br�nn group. Cro-Magnon people were tall and big-boned, with large, long, and rugged heads. They must have been built like many present-day Scandinavians. The Combe -Capelle-Br�nn people were shorter; they had narrow heads and faces, and -big eyebrow-ridges. Of course we don�t find the skin or hair of these -people. But there is little doubt they were Caucasoids (�Whites�). +Capelle-Br�nn people were shorter; they had narrow heads and faces, and +big eyebrow-ridges. Of course we don�t find the skin or hair of these +people. But there is little doubt they were Caucasoids (�Whites�). Another important find came in the Italian Riviera, near Monte Carlo. Here, in a cave near Grimaldi, there was a grave containing a woman and a young boy, buried together. The two skeletons were first called -�Negroid� because some features of their bones were thought to resemble +�Negroid� because some features of their bones were thought to resemble certain features of modern African Negro bones. But more recently, Professor E. A. Hooton and other experts questioned the use of the word -�Negroid� in describing the Grimaldi skeletons. It is true that nothing +�Negroid� in describing the Grimaldi skeletons. It is true that nothing is known of the skin color, hair form, or any other fleshy feature of -the Grimaldi people, so that the word �Negroid� in its usual meaning is +the Grimaldi people, so that the word �Negroid� in its usual meaning is not proper here. It is also not clear whether the features of the bones -claimed to be �Negroid� are really so at all. +claimed to be �Negroid� are really so at all. -From a place called Wadjak, in Java, we have �proto-Australoid� skulls +From a place called Wadjak, in Java, we have �proto-Australoid� skulls which closely resemble those of modern Australian natives. Some of the skulls found in South Africa, especially the Boskop skull, look like those of modern Bushmen, but are much bigger. The ancestors of @@ -1159,12 +1159,12 @@ Desert. True African Negroes were forest people who apparently expanded out of the west central African area only in the last several thousand years. Although dark in skin color, neither the Australians nor the Bushmen are Negroes; neither the Wadjak nor the Boskop skulls are -�Negroid.� +�Negroid.� -As we�ve already mentioned, Professor Weidenreich believed that Peking +As we�ve already mentioned, Professor Weidenreich believed that Peking man was already on the way to becoming a Mongoloid. Anyway, the -Mongoloids would seem to have been present by the time of the �Upper -Cave� at Choukoutien, the _Sinanthropus_ find-spot. +Mongoloids would seem to have been present by the time of the �Upper +Cave� at Choukoutien, the _Sinanthropus_ find-spot. WHAT THE DIFFERENCES MEAN @@ -1175,14 +1175,14 @@ From area to area, men tended to look somewhat different, just as they do today. This is all quite natural. People _tended_ to mate near home; in the anthropological jargon, they made up geographically localized breeding populations. The simple continental division of -�stocks�--black = Africa, yellow = Asia, white = Europe--is too simple +�stocks�--black = Africa, yellow = Asia, white = Europe--is too simple a picture to fit the facts. People became accustomed to life in some -particular area within a continent (we might call it a �natural area�). +particular area within a continent (we might call it a �natural area�). As they went on living there, they evolved towards some particular physical variety. It would, of course, have been difficult to draw a clear boundary between two adjacent areas. There must always have been some mating across the boundaries in every case. One thing human -beings don�t do, and never have done, is to mate for �purity.� It is +beings don�t do, and never have done, is to mate for �purity.� It is self-righteous nonsense when we try to kid ourselves into thinking that they do. @@ -1195,28 +1195,28 @@ and they must do the writing about races. I shall, however, give two modern definitions of race, and then make one comment. Dr. William G. Boyd, professor of Immunochemistry, School of - Medicine, Boston University: �We may define a human race as a + Medicine, Boston University: �We may define a human race as a population which differs significantly from other human populations in regard to the frequency of one or more of the genes it - possesses.� + possesses.� Professor Sherwood L. Washburn, professor of Physical Anthropology, - Department of Anthropology, the University of California: �A �race� + Department of Anthropology, the University of California: �A �race� is a group of genetically similar populations, and races intergrade - because there are always intermediate populations.� + because there are always intermediate populations.� My comment is that the ideas involved here are all biological: they concern groups, _not_ individuals. Boyd and Washburn may differ a bit -on what they want to consider a �population,� but a population is a +on what they want to consider a �population,� but a population is a group nevertheless, and genetics is biology to the hilt. Now a lot of people still think of race in terms of how people dress or fix their food or of other habits or customs they have. The next step is to talk -about racial �purity.� None of this has anything whatever to do with +about racial �purity.� None of this has anything whatever to do with race proper, which is a matter of the biology of groups. -Incidentally, I�m told that if man very carefully _controls_ +Incidentally, I�m told that if man very carefully _controls_ the breeding of certain animals over generations--dogs, cattle, -chickens--he might achieve a �pure� race of animals. But he doesn�t do +chickens--he might achieve a �pure� race of animals. But he doesn�t do it. Some unfortunate genetic trait soon turns up, so this has just as carefully to be bred out again, and so on. @@ -1240,20 +1240,20 @@ date to the second great interglacial period, about 350,000 years ago. Piltdown and Galley Hill are out, and with them, much of the starch in the old idea that there were two distinct lines of development -in human evolution: (1) a line of �paleoanthropic� development from +in human evolution: (1) a line of �paleoanthropic� development from Heidelberg to the Neanderthalers where it became extinct, and (2) a -very early �modern� line, through Piltdown, Galley Hill, Swanscombe, to +very early �modern� line, through Piltdown, Galley Hill, Swanscombe, to us. Swanscombe, Steinheim, and Ternafine are just as easily cases of very early pre-neanderthaloids. The pre-neanderthaloids were very widespread during the third interglacial: Ehringsdorf, Saccopastore, some of the Mount Carmel -people, and probably Font�chevade are cases in point. A variety of +people, and probably Font�chevade are cases in point. A variety of their descendants can be seen, from Java (Solo), Africa (Rhodesian man), and about the Mediterranean and in western Europe. As the acute cold of the last glaciation set in, the western Europeans found themselves surrounded by water, ice, or bitter cold tundra. To vastly -over-simplify it, they �bred in� and became classic neanderthaloids. +over-simplify it, they �bred in� and became classic neanderthaloids. But on Mount Carmel, the Skhul cave-find with its 70 per cent modern features shows what could happen elsewhere at the same time. @@ -1263,12 +1263,12 @@ modern skeletons of men. The modern skeletons differ from place to place, just as different groups of men living in different places still look different. -What became of the Neanderthalers? Nobody can tell me for sure. I�ve a -hunch they were simply �bred out� again when the cold weather was over. +What became of the Neanderthalers? Nobody can tell me for sure. I�ve a +hunch they were simply �bred out� again when the cold weather was over. Many Americans, as the years go by, are no longer ashamed to claim they -have �Indian blood in their veins.� Give us a few more generations +have �Indian blood in their veins.� Give us a few more generations and there will not be very many other Americans left to whom we can -brag about it. It certainly isn�t inconceivable to me to imagine a +brag about it. It certainly isn�t inconceivable to me to imagine a little Cro-Magnon boy bragging to his friends about his tough, strong, Neanderthaler great-great-great-great-grandfather! @@ -1281,15 +1281,15 @@ Cultural BEGINNINGS Men, unlike the lower animals, are made up of much more than flesh and -blood and bones; for men have �culture.� +blood and bones; for men have �culture.� WHAT IS CULTURE? -�Culture� is a word with many meanings. The doctors speak of making a -�culture� of a certain kind of bacteria, and ants are said to have a -�culture.� Then there is the Emily Post kind of �culture�--you say a -person is �cultured,� or that he isn�t, depending on such things as +�Culture� is a word with many meanings. The doctors speak of making a +�culture� of a certain kind of bacteria, and ants are said to have a +�culture.� Then there is the Emily Post kind of �culture�--you say a +person is �cultured,� or that he isn�t, depending on such things as whether or not he eats peas with his knife. The anthropologists use the word too, and argue heatedly over its finer @@ -1300,7 +1300,7 @@ men from another. In this sense, a CULTURE means the way the members of a group of people think and believe and live, the tools they make, and the way they do things. Professor Robert Redfield says a culture is an organized or formalized body of conventional understandings. -�Conventional understandings� means the whole set of rules, beliefs, +�Conventional understandings� means the whole set of rules, beliefs, and standards which a group of people lives by. These understandings show themselves in art, and in the other things a people may make and do. The understandings continue to last, through tradition, from one @@ -1325,12 +1325,12 @@ Egyptians. I mean their beliefs as to why grain grew, as well as their ability to make tools with which to reap the grain. I mean their beliefs about life after death. What I am thinking about as culture is a thing which lasted in time. If any one Egyptian, even the Pharaoh, -died, it didn�t affect the Egyptian culture of that particular moment. +died, it didn�t affect the Egyptian culture of that particular moment. PREHISTORIC CULTURES -For that long period of man�s history that is all prehistory, we have +For that long period of man�s history that is all prehistory, we have no written descriptions of cultures. We find only the tools men made, the places where they lived, the graves in which they buried their dead. Fortunately for us, these tools and living places and graves all @@ -1345,15 +1345,15 @@ of the classic European Neanderthal group of men, we have found few cave-dwelling places of very early prehistoric men. First, there is the fallen-in cave where Peking man was found, near Peking. Then there are two or three other _early_, but not _very early_, possibilities. The -finds at the base of the French cave of Font�chevade, those in one of +finds at the base of the French cave of Font�chevade, those in one of the Makapan caves in South Africa, and several open sites such as Dr. -L. S. B. Leakey�s Olorgesailie in Kenya doubtless all lie earlier than +L. S. B. Leakey�s Olorgesailie in Kenya doubtless all lie earlier than the time of the main European Neanderthal group, but none are so early as the Peking finds. You can see that we know very little about the home life of earlier prehistoric men. We find different kinds of early stone tools, but we -can�t even be really sure which tools may have been used together. +can�t even be really sure which tools may have been used together. WHY LITTLE HAS LASTED FROM EARLY TIMES @@ -1380,11 +1380,11 @@ there first! The front of this enormous sheet of ice moved down over the country, crushing and breaking and plowing up everything, like a gigantic bulldozer. You can see what happened to our camp site. -Everything the glacier couldn�t break, it pushed along in front of it +Everything the glacier couldn�t break, it pushed along in front of it or plowed beneath it. Rocks were ground to gravel, and soil was caught into the ice, which afterwards melted and ran off as muddy water. Hard -tools of flint sometimes remained whole. Human bones weren�t so hard; -it�s a wonder _any_ of them lasted. Gushing streams of melt water +tools of flint sometimes remained whole. Human bones weren�t so hard; +it�s a wonder _any_ of them lasted. Gushing streams of melt water flushed out the debris from underneath the glacier, and water flowed off the surface and through great crevasses. The hard materials these waters carried were even more rolled and ground up. Finally, such @@ -1407,26 +1407,26 @@ all up, and so we cannot say which particular sets of tools belonged together in the first place. -�EOLITHS� +�EOLITHS� But what sort of tools do we find earliest? For almost a century, people have been picking up odd bits of flint and other stone in the oldest Ice Age gravels in England and France. It is now thought these -odd bits of stone weren�t actually worked by prehistoric men. The -stones were given a name, _eoliths_, or �dawn stones.� You can see them +odd bits of stone weren�t actually worked by prehistoric men. The +stones were given a name, _eoliths_, or �dawn stones.� You can see them in many museums; but you can be pretty sure that very few of them were actually fashioned by men. -It is impossible to pick out �eoliths� that seem to be made in any -one _tradition_. By �tradition� I mean a set of habits for making one -kind of tool for some particular job. No two �eoliths� look very much +It is impossible to pick out �eoliths� that seem to be made in any +one _tradition_. By �tradition� I mean a set of habits for making one +kind of tool for some particular job. No two �eoliths� look very much alike: tools made as part of some one tradition all look much alike. -Now it�s easy to suppose that the very earliest prehistoric men picked -up and used almost any sort of stone. This wouldn�t be surprising; you -and I do it when we go camping. In other words, some of these �eoliths� +Now it�s easy to suppose that the very earliest prehistoric men picked +up and used almost any sort of stone. This wouldn�t be surprising; you +and I do it when we go camping. In other words, some of these �eoliths� may actually have been used by prehistoric men. They must have used anything that might be handy when they needed it. We could have figured -that out without the �eoliths.� +that out without the �eoliths.� THE ROAD TO STANDARDIZATION @@ -1434,7 +1434,7 @@ THE ROAD TO STANDARDIZATION Reasoning from what we know or can easily imagine, there should have been three major steps in the prehistory of tool-making. The first step would have been simple _utilization_ of what was at hand. This is the -step into which the �eoliths� would fall. The second step would have +step into which the �eoliths� would fall. The second step would have been _fashioning_--the haphazard preparation of a tool when there was a need for it. Probably many of the earlier pebble tools, which I shall describe next, fall into this group. The third step would have been @@ -1447,7 +1447,7 @@ tradition appears. PEBBLE TOOLS -At the beginning of the last chapter, you�ll remember that I said there +At the beginning of the last chapter, you�ll remember that I said there were tools from very early geological beds. The earliest bones of men have not yet been found in such early beds although the Sterkfontein australopithecine cave approaches this early date. The earliest tools @@ -1467,7 +1467,7 @@ Old World besides Africa; in fact, some prehistorians already claim to have identified a few. Since the forms and the distinct ways of making the earlier pebble tools had not yet sufficiently jelled into a set tradition, they are difficult for us to recognize. It is not -so difficult, however, if there are great numbers of �possibles� +so difficult, however, if there are great numbers of �possibles� available. A little later in time the tradition becomes more clearly set, and pebble tools are easier to recognize. So far, really large collections of pebble tools have only been found and examined in Africa. @@ -1475,9 +1475,9 @@ collections of pebble tools have only been found and examined in Africa. CORE-BIFACE TOOLS -The next tradition we�ll look at is the _core_ or biface one. The tools +The next tradition we�ll look at is the _core_ or biface one. The tools are large pear-shaped pieces of stone trimmed flat on the two opposite -sides or �faces.� Hence �biface� has been used to describe these tools. +sides or �faces.� Hence �biface� has been used to describe these tools. The front view is like that of a pear with a rather pointed top, and the back view looks almost exactly the same. Look at them side on, and you can see that the front and back faces are the same and have been @@ -1488,7 +1488,7 @@ illustration. [Illustration: ABBEVILLIAN BIFACE] We have very little idea of the way in which these core-bifaces were -used. They have been called �hand axes,� but this probably gives the +used. They have been called �hand axes,� but this probably gives the wrong idea, for an ax, to us, is not a pointed tool. All of these early tools must have been used for a number of jobs--chopping, scraping, cutting, hitting, picking, and prying. Since the core-bifaces tend to @@ -1505,7 +1505,7 @@ a big block of stone. You had to break off the flake in such a way that it was broad and thin, and also had a good sharp cutting edge. Once you really got on to the trick of doing it, this was probably a simpler way to make a good cutting tool than preparing a biface. You have to know -how, though; I�ve tried it and have mashed my fingers more than once. +how, though; I�ve tried it and have mashed my fingers more than once. The flake tools look as if they were meant mainly for chopping, scraping, and cutting jobs. When one made a flake tool, the idea seems @@ -1535,9 +1535,9 @@ tradition. It probably has its earliest roots in the pebble tool tradition of African type. There are several kinds of tools in this tradition, but all differ from the western core-bifaces and flakes. There are broad, heavy scrapers or cleavers, and tools with an -adze-like cutting edge. These last-named tools are called �hand adzes,� -just as the core-bifaces of the west have often been called �hand -axes.� The section of an adze cutting edge is ? shaped; the section of +adze-like cutting edge. These last-named tools are called �hand adzes,� +just as the core-bifaces of the west have often been called �hand +axes.� The section of an adze cutting edge is ? shaped; the section of an ax is < shaped. [Illustration: ANYATHIAN ADZE-LIKE TOOL] @@ -1581,17 +1581,17 @@ stratification.[3] Soan (India) Flake: - �Typical Mousterian� + �Typical Mousterian� Levalloiso-Mousterian Levalloisian Tayacian Clactonian (localized in England) Core-biface: - Some blended elements in �Mousterian� + Some blended elements in �Mousterian� Micoquian (= Acheulean 6 and 7) Acheulean - Abbevillian (once called �Chellean�) + Abbevillian (once called �Chellean�) Pebble tool: Oldowan @@ -1608,8 +1608,8 @@ out of glacial gravels the easiest thing to do first is to isolate individual types of tools into groups. First you put a bushel-basketful of tools on a table and begin matching up types. Then you give names to the groups of each type. The groups and the types are really matters of -the archeologists� choice; in real life, they were probably less exact -than the archeologists� lists of them. We now know pretty well in which +the archeologists� choice; in real life, they were probably less exact +than the archeologists� lists of them. We now know pretty well in which of the early traditions the various early groups belong. @@ -1635,9 +1635,9 @@ production must have been passed on from one generation to another. I could even guess that the notions of the ideal type of one or the other of these tools stood out in the minds of men of those times -somewhat like a symbol of �perfect tool for good job.� If this were -so--remember it�s only a wild guess of mine--then men were already -symbol users. Now let�s go on a further step to the fact that the words +somewhat like a symbol of �perfect tool for good job.� If this were +so--remember it�s only a wild guess of mine--then men were already +symbol users. Now let�s go on a further step to the fact that the words men speak are simply sounds, each different sound being a symbol for a different meaning. If standardized tool-making suggests symbol-making, is it also possible that crude word-symbols were also being made? I @@ -1650,7 +1650,7 @@ of our second step is more suggestive, although we may not yet feel sure that many of the earlier pebble tools were man-made products. But with the step to standardization and the appearance of the traditions, I believe we must surely be dealing with the traces of culture-bearing -_men_. The �conventional understandings� which Professor Redfield�s +_men_. The �conventional understandings� which Professor Redfield�s definition of culture suggests are now evidenced for us in the persistent habits for the preparation of stone tools. Were we able to see the other things these prehistoric men must have made--in materials @@ -1666,19 +1666,19 @@ In the last chapter, I told you that many of the older archeologists and human paleontologists used to think that modern man was very old. The supposed ages of Piltdown and Galley Hill were given as evidence of the great age of anatomically modern man, and some interpretations -of the Swanscombe and Font�chevade fossils were taken to support +of the Swanscombe and Font�chevade fossils were taken to support this view. The conclusion was that there were two parallel lines or -�phyla� of men already present well back in the Pleistocene. The -first of these, the more primitive or �paleoanthropic� line, was +�phyla� of men already present well back in the Pleistocene. The +first of these, the more primitive or �paleoanthropic� line, was said to include Heidelberg, the proto-neanderthaloids and classic -Neanderthal. The more anatomically modern or �neanthropic� line was +Neanderthal. The more anatomically modern or �neanthropic� line was thought to consist of Piltdown and the others mentioned above. The Neanderthaler or paleoanthropic line was thought to have become extinct after the first phase of the last great glaciation. Of course, the modern or neanthropic line was believed to have persisted into the -present, as the basis for the world�s population today. But with +present, as the basis for the world�s population today. But with Piltdown liquidated, Galley Hill known to be very late, and Swanscombe -and Font�chevade otherwise interpreted, there is little left of the +and Font�chevade otherwise interpreted, there is little left of the so-called parallel phyla theory. While the theory was in vogue, however, and as long as the European @@ -1695,9 +1695,9 @@ where they had actually been dropped by the men who made and used them. The tools came, rather, from the secondary hodge-podge of the glacial gravels. I tried to give you a picture of the bulldozing action of glaciers (p. 40) and of the erosion and weathering that were -side-effects of a glacially conditioned climate on the earth�s surface. +side-effects of a glacially conditioned climate on the earth�s surface. As we said above, if one simply plucks tools out of the redeposited -gravels, his natural tendency is to �type� the tools by groups, and to +gravels, his natural tendency is to �type� the tools by groups, and to think that the groups stand for something _on their own_. In 1906, M. Victor Commont actually made a rare find of what seems @@ -1705,15 +1705,15 @@ to have been a kind of workshop site, on a terrace above the Somme river in France. Here, Commont realized, flake tools appeared clearly in direct association with core-biface tools. Few prehistorians paid attention to Commont or his site, however. It was easier to believe -that flake tools represented a distinct �culture� and that this -�culture� was that of the Neanderthaler or paleoanthropic line, and -that the core-bifaces stood for another �culture� which was that of the +that flake tools represented a distinct �culture� and that this +�culture� was that of the Neanderthaler or paleoanthropic line, and +that the core-bifaces stood for another �culture� which was that of the supposed early modern or neanthropic line. Of course, I am obviously skipping many details here. Some later sites with Neanderthal fossils do seem to have only flake tools, but other such sites have both types of tools. The flake tools which appeared _with_ the core-bifaces in the Swanscombe gravels were never made much of, although it -was embarrassing for the parallel phyla people that Font�chevade +was embarrassing for the parallel phyla people that Font�chevade ran heavily to flake tools. All in all, the parallel phyla theory flourished because it seemed so neat and easy to understand. @@ -1722,20 +1722,20 @@ TRADITIONS ARE TOOL-MAKING HABITS, NOT CULTURES In case you think I simply enjoy beating a dead horse, look in any standard book on prehistory written twenty (or even ten) years ago, or -in most encyclopedias. You�ll find that each of the individual tool -types, of the West, at least, was supposed to represent a �culture.� -The �cultures� were believed to correspond to parallel lines of human +in most encyclopedias. You�ll find that each of the individual tool +types, of the West, at least, was supposed to represent a �culture.� +The �cultures� were believed to correspond to parallel lines of human evolution. In 1937, Mr. Harper Kelley strongly re-emphasized the importance -of Commont�s workshop site and the presence of flake tools with -core-bifaces. Next followed Dr. Movius� clear delineation of the +of Commont�s workshop site and the presence of flake tools with +core-bifaces. Next followed Dr. Movius� clear delineation of the chopper-chopping tool tradition of the Far East. This spoiled the nice symmetry of the flake-tool = paleoanthropic, core-biface = neanthropic equations. Then came increasing understanding of the importance of the pebble tools in Africa, and the location of several more workshop sites there, especially at Olorgesailie in Kenya. Finally came the -liquidation of Piltdown and the deflation of Galley Hill�s date. So it +liquidation of Piltdown and the deflation of Galley Hill�s date. So it is at last possible to picture an individual prehistoric man making a flake tool to do one job and a core-biface tool to do another. Commont showed us this picture in 1906, but few believed him. @@ -1751,7 +1751,7 @@ that of the cave on Mount Carmel in Palestine, where the blended pre-neanderthaloid, 70 per cent modern-type skulls were found. Here, in the same level with the skulls, were 9,784 flint tools. Of these, only three--doubtless strays--were core-bifaces; all the rest were flake -tools or flake chips. We noted above how the Font�chevade cave ran to +tools or flake chips. We noted above how the Font�chevade cave ran to flake tools. The only conclusion I would draw from this is that times and circumstances did exist in which prehistoric men needed only flake tools. So they only made flake tools for those particular times and @@ -1773,13 +1773,13 @@ piece of bone. From the gravels which yield the Clactonian flakes of England comes the fire-hardened point of a wooden spear. There are also the chance finds of the fossil human bones themselves, of which we spoke in the last chapter. Aside from the cave of Peking man, none -of the earliest tools have been found in caves. Open air or �workshop� +of the earliest tools have been found in caves. Open air or �workshop� sites which do not seem to have been disturbed later by some geological agency are very rare. The chart on page 65 shows graphically what the situation in west-central Europe seems to have been. It is not yet certain whether -there were pebble tools there or not. The Font�chevade cave comes +there were pebble tools there or not. The Font�chevade cave comes into the picture about 100,000 years ago or more. But for the earlier hundreds of thousands of years--below the red-dotted line on the chart--the tools we find come almost entirely from the haphazard @@ -1790,13 +1790,13 @@ kinds of all-purpose tools. Almost any one of them could be used for hacking, chopping, cutting, and scraping; so the men who used them must have been living in a rough and ready sort of way. They found or hunted their food wherever they could. In the anthropological jargon, they -were �food-gatherers,� pure and simple. +were �food-gatherers,� pure and simple. Because of the mixture in the gravels and in the materials they -carried, we can�t be sure which animals these men hunted. Bones of +carried, we can�t be sure which animals these men hunted. Bones of the larger animals turn up in the gravels, but they could just as well belong to the animals who hunted the men, rather than the other -way about. We don�t know. This is why camp sites like Commont�s and +way about. We don�t know. This is why camp sites like Commont�s and Olorgesailie in Kenya are so important when we do find them. The animal bones at Olorgesailie belonged to various mammals of extremely large size. Probably they were taken in pit-traps, but there are a number of @@ -1809,18 +1809,18 @@ animal. Professor F. Clark Howell recently returned from excavating another important open air site at Isimila in Tanganyika. The site yielded the bones of many fossil animals and also thousands of core-bifaces, -flakes, and choppers. But Howell�s reconstruction of the food-getting -habits of the Isimila people certainly suggests that the word �hunting� -is too dignified for what they did; �scavenging� would be much nearer +flakes, and choppers. But Howell�s reconstruction of the food-getting +habits of the Isimila people certainly suggests that the word �hunting� +is too dignified for what they did; �scavenging� would be much nearer the mark. During a great part of this time the climate was warm and pleasant. The second interglacial period (the time between the second and third great alpine glaciations) lasted a long time, and during much of this time -the climate may have been even better than ours is now. We don�t know +the climate may have been even better than ours is now. We don�t know that earlier prehistoric men in Europe or Africa lived in caves. They may not have needed to; much of the weather may have been so nice that -they lived in the open. Perhaps they didn�t wear clothes, either. +they lived in the open. Perhaps they didn�t wear clothes, either. WHAT THE PEKING CAVE-FINDS TELL US @@ -1832,7 +1832,7 @@ were bones of dangerous animals, members of the wolf, bear, and cat families. Some of the cat bones belonged to beasts larger than tigers. There were also bones of other wild animals: buffalo, camel, deer, elephants, horses, sheep, and even ostriches. Seventy per cent of the -animals Peking man killed were fallow deer. It�s much too cold and dry +animals Peking man killed were fallow deer. It�s much too cold and dry in north China for all these animals to live there today. So this list helps us know that the weather was reasonably warm, and that there was enough rain to grow grass for the grazing animals. The list also helps @@ -1840,7 +1840,7 @@ the paleontologists to date the find. Peking man also seems to have eaten plant food, for there are hackberry seeds in the debris of the cave. His tools were made of sandstone and -quartz and sometimes of a rather bad flint. As we�ve already seen, they +quartz and sometimes of a rather bad flint. As we�ve already seen, they belong in the chopper-tool tradition. It seems fairly clear that some of the edges were chipped by right-handed people. There are also many split pieces of heavy bone. Peking man probably split them so he could @@ -1850,10 +1850,10 @@ Many of these split bones were the bones of Peking men. Each one of the skulls had already had the base broken out of it. In no case were any of the bones resting together in their natural relation to one another. There is nothing like a burial; all of the bones are scattered. Now -it�s true that animals could have scattered bodies that were not cared +it�s true that animals could have scattered bodies that were not cared for or buried. But splitting bones lengthwise and carefully removing the base of a skull call for both the tools and the people to use them. -It�s pretty clear who the people were. Peking man was a cannibal. +It�s pretty clear who the people were. Peking man was a cannibal. * * * * * @@ -1862,8 +1862,8 @@ prehistoric men. In those days life was rough. You evidently had to watch out not only for dangerous animals but also for your fellow men. You ate whatever you could catch or find growing. But you had sense enough to build fires, and you had already formed certain habits for -making the kinds of stone tools you needed. That�s about all we know. -But I think we�ll have to admit that cultural beginnings had been made, +making the kinds of stone tools you needed. That�s about all we know. +But I think we�ll have to admit that cultural beginnings had been made, and that these early people were really _men_. @@ -1876,16 +1876,16 @@ MORE EVIDENCE of Culture While the dating is not yet sure, the material that we get from caves in Europe must go back to about 100,000 years ago; the time of the -classic Neanderthal group followed soon afterwards. We don�t know why +classic Neanderthal group followed soon afterwards. We don�t know why there is no earlier material in the caves; apparently they were not used before the last interglacial phase (the period just before the last great glaciation). We know that men of the classic Neanderthal group were living in caves from about 75,000 to 45,000 years ago. New radioactive carbon dates even suggest that some of the traces of -culture we�ll describe in this chapter may have lasted to about 35,000 +culture we�ll describe in this chapter may have lasted to about 35,000 years ago. Probably some of the pre-neanderthaloid types of men had also lived in caves. But we have so far found their bones in caves only -in Palestine and at Font�chevade. +in Palestine and at Font�chevade. THE CAVE LAYERS @@ -1893,7 +1893,7 @@ THE CAVE LAYERS In parts of France, some peasants still live in caves. In prehistoric time, many generations of people lived in them. As a result, many caves have deep layers of debris. The first people moved in and lived -on the rock floor. They threw on the floor whatever they didn�t want, +on the rock floor. They threw on the floor whatever they didn�t want, and they tracked in mud; nobody bothered to clean house in those days. Their debris--junk and mud and garbage and what not--became packed into a layer. As time went on, and generations passed, the layer grew @@ -1910,20 +1910,20 @@ earliest to latest. This is the _stratification_ we talked about (p. [Illustration: SECTION OF SHELTER ON LOWER TERRACE, LE MOUSTIER] -While we may find a mix-up in caves, it�s not nearly as bad as the +While we may find a mix-up in caves, it�s not nearly as bad as the mixing up that was done by glaciers. The animal bones and shells, the fireplaces, the bones of men, and the tools the men made all belong -together, if they come from one layer. That�s the reason why the cave +together, if they come from one layer. That�s the reason why the cave of Peking man is so important. It is also the reason why the caves in Europe and the Near East are so important. We can get an idea of which things belong together and which lot came earliest and which latest. In most cases, prehistoric men lived only in the mouths of caves. -They didn�t like the dark inner chambers as places to live in. They +They didn�t like the dark inner chambers as places to live in. They preferred rock-shelters, at the bases of overhanging cliffs, if there was enough overhang to give shelter. When the weather was good, they no -doubt lived in the open air as well. I�ll go on using the term �cave� -since it�s more familiar, but remember that I really mean rock-shelter, +doubt lived in the open air as well. I�ll go on using the term �cave� +since it�s more familiar, but remember that I really mean rock-shelter, as a place in which people actually lived. The most important European cave sites are in Spain, France, and @@ -1933,29 +1933,29 @@ found when the out-of-the-way parts of Europe, Africa, and Asia are studied. -AN �INDUSTRY� DEFINED +AN �INDUSTRY� DEFINED We have already seen that the earliest European cave materials are -those from the cave of Font�chevade. Movius feels certain that the +those from the cave of Font�chevade. Movius feels certain that the lowest materials here date back well into the third interglacial stage, -that which lay between the Riss (next to the last) and the W�rm I +that which lay between the Riss (next to the last) and the W�rm I (first stage of the last) alpine glaciations. This material consists of an _industry_ of stone tools, apparently all made in the flake -tradition. This is the first time we have used the word �industry.� +tradition. This is the first time we have used the word �industry.� It is useful to call all of the different tools found together in one layer and made of _one kind of material_ an industry; that is, the tools must be found together as men left them. Tools taken from the glacial gravels (or from windswept desert surfaces or river gravels -or any geological deposit) are not �together� in this sense. We might -say the latter have only �geological,� not �archeological� context. +or any geological deposit) are not �together� in this sense. We might +say the latter have only �geological,� not �archeological� context. Archeological context means finding things just as men left them. We -can tell what tools go together in an �industrial� sense only if we +can tell what tools go together in an �industrial� sense only if we have archeological context. -Up to now, the only things we could have called �industries� were the +Up to now, the only things we could have called �industries� were the worked stone industry and perhaps the worked (?) bone industry of the Peking cave. We could add some of the very clear cases of open air -sites, like Olorgesailie. We couldn�t use the term for the stone tools +sites, like Olorgesailie. We couldn�t use the term for the stone tools from the glacial gravels, because we do not know which tools belonged together. But when the cave materials begin to appear in Europe, we can begin to speak of industries. Most of the European caves of this time @@ -1964,16 +1964,16 @@ contain industries of flint tools alone. THE EARLIEST EUROPEAN CAVE LAYERS -We�ve just mentioned the industry from what is said to be the oldest +We�ve just mentioned the industry from what is said to be the oldest inhabited cave in Europe; that is, the industry from the deepest layer -of the site at Font�chevade. Apparently it doesn�t amount to much. The +of the site at Font�chevade. Apparently it doesn�t amount to much. The tools are made of stone, in the flake tradition, and are very poorly worked. This industry is called _Tayacian_. Its type tool seems to be a smallish flake tool, but there are also larger flakes which seem to have been fashioned for hacking. In fact, the type tool seems to be simply a smaller edition of the Clactonian tool (pictured on p. 45). -None of the Font�chevade tools are really good. There are scrapers, +None of the Font�chevade tools are really good. There are scrapers, and more or less pointed tools, and tools that may have been used for hacking and chopping. Many of the tools from the earlier glacial gravels are better made than those of this first industry we see in @@ -2005,7 +2005,7 @@ core-biface and the flake traditions. The core-biface tools usually make up less than half of all the tools in the industry. However, the name of the biface type of tool is generally given to the whole industry. It is called the _Acheulean_, actually a late form of it, as -�Acheulean� is also used for earlier core-biface tools taken from the +�Acheulean� is also used for earlier core-biface tools taken from the glacial gravels. In western Europe, the name used is _Upper Acheulean_ or _Micoquian_. The same terms have been borrowed to name layers E and F in the Tabun cave, on Mount Carmel in Palestine. @@ -2029,7 +2029,7 @@ those used for at least one of the flake industries we shall mention presently. There is very little else in these early cave layers. We do not have -a proper �industry� of bone tools. There are traces of fire, and of +a proper �industry� of bone tools. There are traces of fire, and of animal bones, and a few shells. In Palestine, there are many more bones of deer than of gazelle in these layers; the deer lives in a wetter climate than does the gazelle. In the European cave layers, the @@ -2043,18 +2043,18 @@ bones of fossil men definitely in place with this industry. FLAKE INDUSTRIES FROM THE CAVES Two more stone industries--the _Levalloisian_ and the -�_Mousterian_�--turn up at approximately the same time in the European +�_Mousterian_�--turn up at approximately the same time in the European cave layers. Their tools seem to be mainly in the flake tradition, but according to some of the authorities their preparation also shows some combination with the habits by which the core-biface tools were prepared. -Now notice that I don�t tell you the Levalloisian and the �Mousterian� +Now notice that I don�t tell you the Levalloisian and the �Mousterian� layers are both above the late Acheulean layers. Look at the cave -section (p. 57) and you�ll find that some �Mousterian of Acheulean -tradition� appears above some �typical Mousterian.� This means that +section (p. 57) and you�ll find that some �Mousterian of Acheulean +tradition� appears above some �typical Mousterian.� This means that there may be some kinds of Acheulean industries that are later than -some kinds of �Mousterian.� The same is true of the Levalloisian. +some kinds of �Mousterian.� The same is true of the Levalloisian. There were now several different kinds of habits that men used in making stone tools. These habits were based on either one or the other @@ -2072,7 +2072,7 @@ were no patent laws in those days. The extremely complicated interrelationships of the different habits used by the tool-makers of this range of time are at last being -systematically studied. M. Fran�ois Bordes has developed a statistical +systematically studied. M. Fran�ois Bordes has developed a statistical method of great importance for understanding these tool preparation habits. @@ -2081,22 +2081,22 @@ THE LEVALLOISIAN AND MOUSTERIAN The easiest Levalloisian tool to spot is a big flake tool. The trick in making it was to fashion carefully a big chunk of stone (called -the Levalloisian �tortoise core,� because it resembles the shape of +the Levalloisian �tortoise core,� because it resembles the shape of a turtle-shell) and then to whack this in such a way that a large flake flew off. This large thin flake, with sharp cutting edges, is the finished Levalloisian tool. There were various other tools in a Levalloisian industry, but this is the characteristic _Levalloisian_ tool. -There are several �typical Mousterian� stone tools. Different from -the tools of the Levalloisian type, these were made from �disc-like -cores.� There are medium-sized flake �side scrapers.� There are also -some small pointed tools and some small �hand axes.� The last of these +There are several �typical Mousterian� stone tools. Different from +the tools of the Levalloisian type, these were made from �disc-like +cores.� There are medium-sized flake �side scrapers.� There are also +some small pointed tools and some small �hand axes.� The last of these tool types is often a flake worked on both of the flat sides (that is, bifacially). There are also pieces of flint worked into the form of crude balls. The pointed tools may have been fixed on shafts to make short jabbing spears; the round flint balls may have been used as -bolas. Actually, we don�t _know_ what either tool was used for. The +bolas. Actually, we don�t _know_ what either tool was used for. The points and side scrapers are illustrated (pp. 64 and 66). [Illustration: LEVALLOIS FLAKE] @@ -2108,9 +2108,9 @@ Nowadays the archeologists are less and less sure of the importance of any one specific tool type and name. Twenty years ago, they used to speak simply of Acheulean or Levalloisian or Mousterian tools. Now, more and more, _all_ of the tools from some one layer in a -cave are called an �industry,� which is given a mixed name. Thus we -have �Levalloiso-Mousterian,� and �Acheuleo-Levalloisian,� and even -�Acheuleo-Mousterian� (or �Mousterian of Acheulean tradition�). Bordes� +cave are called an �industry,� which is given a mixed name. Thus we +have �Levalloiso-Mousterian,� and �Acheuleo-Levalloisian,� and even +�Acheuleo-Mousterian� (or �Mousterian of Acheulean tradition�). Bordes� systematic work is beginning to clear up some of our confusion. The time of these late Acheuleo-Levalloiso-Mousterioid industries @@ -2120,16 +2120,16 @@ phase of the last great glaciation. It was also the time that the classic group of Neanderthal men was living in Europe. A number of the Neanderthal fossil finds come from these cave layers. Before the different habits of tool preparation were understood it used to be -popular to say Neanderthal man was �Mousterian man.� I think this is -wrong. What used to be called �Mousterian� is now known to be a variety +popular to say Neanderthal man was �Mousterian man.� I think this is +wrong. What used to be called �Mousterian� is now known to be a variety of industries with tools of both core-biface and flake habits, and -so mixed that the word �Mousterian� used alone really doesn�t mean +so mixed that the word �Mousterian� used alone really doesn�t mean anything. The Neanderthalers doubtless understood the tool preparation habits by means of which Acheulean, Levalloisian and Mousterian type tools were produced. We also have the more modern-like Mount Carmel people, found in a cave layer of Palestine with tools almost entirely -in the flake tradition, called �Levalloiso-Mousterian,� and the -Font�chevade-Tayacian (p. 59). +in the flake tradition, called �Levalloiso-Mousterian,� and the +Font�chevade-Tayacian (p. 59). [Illustration: MOUSTERIAN POINT] @@ -2165,7 +2165,7 @@ which seem to have served as anvils or chopping blocks, are fairly common. Bits of mineral, used as coloring matter, have also been found. We -don�t know what the color was used for. +don�t know what the color was used for. [Illustration: MOUSTERIAN SIDE SCRAPER] @@ -2230,7 +2230,7 @@ might suggest some notion of hoarding up the spirits or the strength of bears killed in the hunt. Probably the people lived in small groups, as hunting and food-gathering seldom provide enough food for large groups of people. These groups probably had some kind of leader or -�chief.� Very likely the rude beginnings of rules for community life +�chief.� Very likely the rude beginnings of rules for community life and politics, and even law, were being made. But what these were, we do not know. We can only guess about such things, as we can only guess about many others; for example, how the idea of a family must have been @@ -2246,8 +2246,8 @@ small. The mixtures and blendings of the habits used in making stone tools must mean that there were also mixtures and blends in many of the other ideas and beliefs of these small groups. And what this probably means is that there was no one _culture_ of the time. It is -certainly unlikely that there were simply three cultures, �Acheulean,� -�Levalloisian,� and �Mousterian,� as has been thought in the past. +certainly unlikely that there were simply three cultures, �Acheulean,� +�Levalloisian,� and �Mousterian,� as has been thought in the past. Rather there must have been a great variety of loosely related cultures at about the same stage of advancement. We could say, too, that here we really begin to see, for the first time, that remarkable ability @@ -2272,7 +2272,7 @@ related habits for the making of tools. But the men who made them must have looked much like the men of the West. Their tools were different, but just as useful. -As to what the men of the West looked like, I�ve already hinted at all +As to what the men of the West looked like, I�ve already hinted at all we know so far (pp. 29 ff.). The Neanderthalers were present at the time. Some more modern-like men must have been about, too, since fossils of them have turned up at Mount Carmel in Palestine, and at @@ -2306,7 +2306,7 @@ A NEW TRADITION APPEARS Something new was probably beginning to happen in the European-Mediterranean area about 40,000 years ago, though all the rest of the Old World seems to have been going on as it had been. I -can�t be sure of this because the information we are using as a basis +can�t be sure of this because the information we are using as a basis for dates is very inaccurate for the areas outside of Europe and the Mediterranean. @@ -2325,7 +2325,7 @@ drawing shows. It has sharp cutting edges, and makes a very useful knife. The real trick is to be able to make one. It is almost impossible to make a blade out of any stone but flint or a natural volcanic glass called obsidian. And even if you have flint or obsidian, -you first have to work up a special cone-shaped �blade-core,� from +you first have to work up a special cone-shaped �blade-core,� from which to whack off blades. [Illustration: PLAIN BLADE] @@ -2351,8 +2351,8 @@ found in equally early cave levels in Syria; their popularity there seems to fluctuate a bit. Some more or less parallel-sided flakes are known in the Levalloisian industry in France, but they are probably no earlier than Tabun E. The Tabun blades are part of a local late -�Acheulean� industry, which is characterized by core-biface �hand -axes,� but which has many flake tools as well. Professor F. E. +�Acheulean� industry, which is characterized by core-biface �hand +axes,� but which has many flake tools as well. Professor F. E. Zeuner believes that this industry may be more than 120,000 years old; actually its date has not yet been fixed, but it is very old--older than the fossil finds of modern-like men in the same caves. @@ -2371,7 +2371,7 @@ We are not sure just where the earliest _persisting_ habits for the production of blade tools developed. Impressed by the very early momentary appearance of blades at Tabun on Mount Carmel, Professor Dorothy A. Garrod first favored the Near East as a center of origin. -She spoke of �some as yet unidentified Asiatic centre,� which she +She spoke of �some as yet unidentified Asiatic centre,� which she thought might be in the highlands of Iran or just beyond. But more recent work has been done in this area, especially by Professor Coon, and the blade tools do not seem to have an early appearance there. When @@ -2395,21 +2395,21 @@ core (and the striking of the Levalloisian flake from it) might have followed through to the conical core and punch technique for the production of blades. Professor Garrod is much impressed with the speed of change during the later phases of the last glaciation, and its -probable consequences. She speaks of �the greater number of industries +probable consequences. She speaks of �the greater number of industries having enough individual character to be classified as distinct ... -since evolution now starts to outstrip diffusion.� Her �evolution� here +since evolution now starts to outstrip diffusion.� Her �evolution� here is of course an industrial evolution rather than a biological one. Certainly the people of Europe had begun to make blade tools during the warm spell after the first phase of the last glaciation. By about 40,000 years ago blades were well established. The bones of the blade -tool makers we�ve found so far indicate that anatomically modern men +tool makers we�ve found so far indicate that anatomically modern men had now certainly appeared. Unfortunately, only a few fossil men have so far been found from the very beginning of the blade tool range in Europe (or elsewhere). What I certainly shall _not_ tell you is that conquering bands of fine, strong, anatomically modern men, armed with superior blade tools, came sweeping out of the East to exterminate the -lowly Neanderthalers. Even if we don�t know exactly what happened, I�d -lay a good bet it wasn�t that simple. +lowly Neanderthalers. Even if we don�t know exactly what happened, I�d +lay a good bet it wasn�t that simple. We do know a good deal about different blade industries in Europe. Almost all of them come from cave layers. There is a great deal of @@ -2418,7 +2418,7 @@ this complication; in fact, it doubtless simplifies it too much. But it may suggest all the complication of industries which is going on at this time. You will note that the upper portion of my much simpler chart (p. 65) covers the same material (in the section -marked �Various Blade-Tool Industries�). That chart is certainly too +marked �Various Blade-Tool Industries�). That chart is certainly too simplified. You will realize that all this complication comes not only from @@ -2429,7 +2429,7 @@ a good deal of climatic change at this time. The plants and animals that men used for food were changing, too. The great variety of tools and industries we now find reflect these changes and the ability of men to keep up with the times. Now, for example, is the first time we are -sure that there are tools to _make_ other tools. They also show men�s +sure that there are tools to _make_ other tools. They also show men�s increasing ability to adapt themselves. @@ -2437,15 +2437,15 @@ SPECIAL TYPES OF BLADE TOOLS The most useful tools that appear at this time were made from blades. - 1. The �backed� blade. This is a knife made of a flint blade, with - one edge purposely blunted, probably to save the user�s fingers + 1. The �backed� blade. This is a knife made of a flint blade, with + one edge purposely blunted, probably to save the user�s fingers from being cut. There are several shapes of backed blades (p. 73). [Illustration: TWO BURINS] - 2. The _burin_ or �graver.� The burin was the original chisel. Its - cutting edge is _transverse_, like a chisel�s. Some burins are + 2. The _burin_ or �graver.� The burin was the original chisel. Its + cutting edge is _transverse_, like a chisel�s. Some burins are made like a screw-driver, save that burins are sharp. Others have edges more like the blade of a chisel or a push plane, with only one bevel. Burins were probably used to make slots in wood @@ -2456,29 +2456,29 @@ The most useful tools that appear at this time were made from blades. [Illustration: TANGED POINT] - 3. The �tanged� point. These stone points were used to tip arrows or + 3. The �tanged� point. These stone points were used to tip arrows or light spears. They were made from blades, and they had a long tang at the bottom where they were fixed to the shaft. At the place where the tang met the main body of the stone point, there was - a marked �shoulder,� the beginnings of a barb. Such points had + a marked �shoulder,� the beginnings of a barb. Such points had either one or two shoulders. [Illustration: NOTCHED BLADE] - 4. The �notched� or �strangulated� blade. Along with the points for + 4. The �notched� or �strangulated� blade. Along with the points for arrows or light spears must go a tool to prepare the arrow or - spear shaft. Today, such a tool would be called a �draw-knife� or - a �spoke-shave,� and this is what the notched blades probably are. + spear shaft. Today, such a tool would be called a �draw-knife� or + a �spoke-shave,� and this is what the notched blades probably are. Our spoke-shaves have sharp straight cutting blades and really - �shave.� Notched blades of flint probably scraped rather than cut. + �shave.� Notched blades of flint probably scraped rather than cut. - 5. The �awl,� �drill,� or �borer.� These blade tools are worked out + 5. The �awl,� �drill,� or �borer.� These blade tools are worked out to a spike-like point. They must have been used for making holes in wood, bone, shell, skin, or other things. [Illustration: DRILL OR AWL] - 6. The �end-scraper on a blade� is a tool with one or both ends + 6. The �end-scraper on a blade� is a tool with one or both ends worked so as to give a good scraping edge. It could have been used to hollow out wood or bone, scrape hides, remove bark from trees, and a number of other things (p. 78). @@ -2489,11 +2489,11 @@ usually made of blades, but the best examples are so carefully worked on both sides (bifacially) that it is impossible to see the original blade. This tool is - 7. The �laurel leaf� point. Some of these tools were long and + 7. The �laurel leaf� point. Some of these tools were long and dagger-like, and must have been used as knives or daggers. Others - were small, called �willow leaf,� and must have been mounted on + were small, called �willow leaf,� and must have been mounted on spear or arrow shafts. Another typical Solutrean tool is the - �shouldered� point. Both the �laurel leaf� and �shouldered� point + �shouldered� point. Both the �laurel leaf� and �shouldered� point types are illustrated (see above and p. 79). [Illustration: END-SCRAPER ON A BLADE] @@ -2507,17 +2507,17 @@ second is a core tool. [Illustration: SHOULDERED POINT] - 8. The �keel-shaped round scraper� is usually small and quite round, + 8. The �keel-shaped round scraper� is usually small and quite round, and has had chips removed up to a peak in the center. It is called - �keel-shaped� because it is supposed to look (when upside down) + �keel-shaped� because it is supposed to look (when upside down) like a section through a boat. Actually, it looks more like a tent or an umbrella. Its outer edges are sharp all the way around, and it was probably a general purpose scraping tool (see illustration, p. 81). - 9. The �keel-shaped nosed scraper� is a much larger and heavier tool + 9. The �keel-shaped nosed scraper� is a much larger and heavier tool than the round scraper. It was made on a core with a flat bottom, - and has one nicely worked end or �nose.� Such tools are usually + and has one nicely worked end or �nose.� Such tools are usually large enough to be easily grasped, and probably were used like push planes (see illustration, p. 81). @@ -2530,7 +2530,7 @@ the most easily recognized blade tools, although they show differences in detail at different times. There are also many other kinds. Not all of these tools appear in any one industry at one time. Thus the different industries shown in the chart (p. 72) each have only some -of the blade tools we�ve just listed, and also a few flake tools. Some +of the blade tools we�ve just listed, and also a few flake tools. Some industries even have a few core tools. The particular types of blade tools appearing in one cave layer or another, and the frequency of appearance of the different types, tell which industry we have in each @@ -2545,15 +2545,15 @@ to appear. There are knives, pins, needles with eyes, and little double-pointed straight bars of bone that were probably fish-hooks. The fish-line would have been fastened in the center of the bar; when the fish swallowed the bait, the bar would have caught cross-wise in the -fish�s mouth. +fish�s mouth. One quite special kind of bone tool is a long flat point for a light spear. It has a deep notch cut up into the breadth of its base, and is -called a �split-based bone point� (p. 82). We know examples of bone +called a �split-based bone point� (p. 82). We know examples of bone beads from these times, and of bone handles for flint tools. Pierced teeth of some animals were worn as beads or pendants, but I am not sure -that elks� teeth were worn this early. There are even spool-shaped -�buttons� or toggles. +that elks� teeth were worn this early. There are even spool-shaped +�buttons� or toggles. [Illustration: SPLIT-BASED BONE POINT] @@ -2595,12 +2595,12 @@ almost to have served as sketch blocks. The surfaces of these various objects may show animals, or rather abstract floral designs, or geometric designs. -[Illustration: �VENUS� FIGURINE FROM WILLENDORF] +[Illustration: �VENUS� FIGURINE FROM WILLENDORF] Some of the movable art is not done on tools. The most remarkable examples of this class are little figures of women. These women seem to be pregnant, and their most female characteristics are much emphasized. -It is thought that these �Venus� or �Mother-goddess� figurines may be +It is thought that these �Venus� or �Mother-goddess� figurines may be meant to show the great forces of nature--fertility and the birth of life. @@ -2616,21 +2616,21 @@ are different styles in the cave art. The really great cave art is pretty well restricted to southern France and Cantabrian (northwestern) Spain. -There are several interesting things about the �Franco-Cantabrian� cave +There are several interesting things about the �Franco-Cantabrian� cave art. It was done deep down in the darkest and most dangerous parts of the caves, although the men lived only in the openings of caves. If you think what they must have had for lights--crude lamps of hollowed stone have been found, which must have burned some kind of oil or grease, with a matted hair or fiber wick--and of the animals that may have -lurked in the caves, you�ll understand the part about danger. Then, -too, we�re sure the pictures these people painted were not simply to be +lurked in the caves, you�ll understand the part about danger. Then, +too, we�re sure the pictures these people painted were not simply to be looked at and admired, for they painted one picture right over other pictures which had been done earlier. Clearly, it was the _act_ of _painting_ that counted. The painter had to go way down into the most mysterious depths of the earth and create an animal in paint. Possibly he believed that by doing this he gained some sort of magic power over the same kind of animal when he hunted it in the open air. It certainly -doesn�t look as if he cared very much about the picture he painted--as +doesn�t look as if he cared very much about the picture he painted--as a finished product to be admired--for he or somebody else soon went down and painted another animal right over the one he had done. @@ -2683,10 +2683,10 @@ it. Their art is another example of the direction the human mind was taking. And when I say human, I mean it in the fullest sense, for this is the time in which fully modern man has appeared. On page 34, we -spoke of the Cro-Magnon group and of the Combe Capelle-Br�nn group of -Caucasoids and of the Grimaldi �Negroids,� who are no longer believed +spoke of the Cro-Magnon group and of the Combe Capelle-Br�nn group of +Caucasoids and of the Grimaldi �Negroids,� who are no longer believed to be Negroid. I doubt that any one of these groups produced most of -the achievements of the times. It�s not yet absolutely sure which +the achievements of the times. It�s not yet absolutely sure which particular group produced the great cave art. The artists were almost certainly a blend of several (no doubt already mixed) groups. The pair of Grimaldians were buried in a grave with a sprinkling of red ochre, @@ -2705,9 +2705,9 @@ also found about the shore of the Mediterranean basin, and it moved into northern Europe as the last glaciation pulled northward. People began making blade tools of very small size. They learned how to chip very slender and tiny blades from a prepared core. Then they made these -little blades into tiny triangles, half-moons (�lunates�), trapezoids, +little blades into tiny triangles, half-moons (�lunates�), trapezoids, and several other geometric forms. These little tools are called -�microliths.� They are so small that most of them must have been fixed +�microliths.� They are so small that most of them must have been fixed in handles or shafts. [Illustration: MICROLITHS @@ -2726,7 +2726,7 @@ One corner of each little triangle stuck out, and the whole thing made a fine barbed harpoon. In historic times in Egypt, geometric trapezoidal microliths were still in use as arrowheads. They were fastened--broad end out--on the end of an arrow shaft. It seems queer -to give an arrow a point shaped like a �T.� Actually, the little points +to give an arrow a point shaped like a �T.� Actually, the little points were very sharp, and must have pierced the hides of animals very easily. We also think that the broader cutting edge of the point may have caused more bleeding than a pointed arrowhead would. In hunting @@ -2739,7 +2739,7 @@ is some evidence that they appear early in the Near East. Their use was very common in northwest Africa but this came later. The microlith makers who reached south Russia and central Europe possibly moved up out of the Near East. Or it may have been the other way around; we -simply don�t yet know. +simply don�t yet know. Remember that the microliths we are talking about here were made from carefully prepared little blades, and are often geometric in outline. @@ -2749,7 +2749,7 @@ even some flake scrapers, in most microlithic industries. I emphasize this bladelet and the geometric character of the microlithic industries of the western Old World, since there has sometimes been confusion in the matter. Sometimes small flake chips, utilized as minute pointed -tools, have been called �microliths.� They may be _microlithic_ in size +tools, have been called �microliths.� They may be _microlithic_ in size in terms of the general meaning of the word, but they do not seem to belong to the sub-tradition of the blade tool preparation habits which we have been discussing here. @@ -2763,10 +2763,10 @@ in western Asia too, and early, although Professor Garrod is no longer sure that the whole tradition originated in the Near East. If you look again at my chart (p. 72) you will note that in western Asia I list some of the names of the western European industries, but with the -qualification �-like� (for example, �Gravettian-like�). The western +qualification �-like� (for example, �Gravettian-like�). The western Asiatic blade-tool industries do vaguely recall some aspects of those of western Europe, but we would probably be better off if we used -completely local names for them. The �Emiran� of my chart is such an +completely local names for them. The �Emiran� of my chart is such an example; its industry includes a long spike-like blade point which has no western European counterpart. @@ -2774,13 +2774,13 @@ When we last spoke of Africa (p. 66), I told you that stone tools there were continuing in the Levalloisian flake tradition, and were becoming smaller. At some time during this process, two new tool types appeared in northern Africa: one was the Aterian point with -a tang (p. 67), and the other was a sort of �laurel leaf� point, -called the �Sbaikian.� These two tool types were both produced from +a tang (p. 67), and the other was a sort of �laurel leaf� point, +called the �Sbaikian.� These two tool types were both produced from flakes. The Sbaikian points, especially, are roughly similar to some of the Solutrean points of Europe. It has been suggested that both the Sbaikian and Aterian points may be seen on their way to France through their appearance in the Spanish cave deposits of Parpallo, but there is -also a rival �pre-Solutrean� in central Europe. We still do not know +also a rival �pre-Solutrean� in central Europe. We still do not know whether there was any contact between the makers of these north African tools and the Solutrean tool-makers. What does seem clear is that the blade-tool tradition itself arrived late in northern Africa. @@ -2788,11 +2788,11 @@ blade-tool tradition itself arrived late in northern Africa. NETHER AFRICA -Blade tools and �laurel leaf� points and some other probably late +Blade tools and �laurel leaf� points and some other probably late stone tool types also appear in central and southern Africa. There are geometric microliths on bladelets and even some coarse pottery in east Africa. There is as yet no good way of telling just where these -items belong in time; in broad geological terms they are �late.� +items belong in time; in broad geological terms they are �late.� Some people have guessed that they are as early as similar European and Near Eastern examples, but I doubt it. The makers of small-sized Levalloisian flake tools occupied much of Africa until very late in @@ -2823,18 +2823,18 @@ ancestors of the American Indians came from Asia. The stone-tool traditions of Europe, Africa, the Near and Middle East, and central Siberia, did _not_ move into the New World. With only a very few special or late exceptions, there are _no_ core-bifaces, -flakes, or blade tools of the Old World. Such things just haven�t been +flakes, or blade tools of the Old World. Such things just haven�t been found here. -This is why I say it�s a shame we don�t know more of the end of the +This is why I say it�s a shame we don�t know more of the end of the chopper-tool tradition in the Far East. According to Weidenreich, the Mongoloids were in the Far East long before the end of the last glaciation. If the genetics of the blood group types do demand a non-Mongoloid ancestry for the American Indians, who else may have been in the Far East 25,000 years ago? We know a little about the habits for making stone tools which these first people brought with them, -and these habits don�t conform with those of the western Old World. -We�d better keep our eyes open for whatever happened to the end of +and these habits don�t conform with those of the western Old World. +We�d better keep our eyes open for whatever happened to the end of the chopper-tool tradition in northern China; already there are hints that it lasted late there. Also we should watch future excavations in eastern Siberia. Perhaps we shall find the chopper-tool tradition @@ -2846,13 +2846,13 @@ THE NEW ERA Perhaps it comes in part from the way I read the evidence and perhaps in part it is only intuition, but I feel that the materials of this chapter suggest a new era in the ways of life. Before about 40,000 -years ago, people simply �gathered� their food, wandering over large +years ago, people simply �gathered� their food, wandering over large areas to scavenge or to hunt in a simple sort of way. But here we -have seen them �settling-in� more, perhaps restricting themselves in +have seen them �settling-in� more, perhaps restricting themselves in their wanderings and adapting themselves to a given locality in more intensive ways. This intensification might be suggested by the word -�collecting.� The ways of life we described in the earlier chapters -were �food-gathering� ways, but now an era of �food-collecting� has +�collecting.� The ways of life we described in the earlier chapters +were �food-gathering� ways, but now an era of �food-collecting� has begun. We shall see further intensifications of it in the next chapter. @@ -2883,8 +2883,8 @@ The last great glaciation of the Ice Age was a two-part affair, with a sub-phase at the end of the second part. In Europe the last sub-phase of this glaciation commenced somewhere around 15,000 years ago. Then the glaciers began to melt back, for the last time. Remember that -Professor Antevs (p. 19) isn�t sure the Ice Age is over yet! This -melting sometimes went by fits and starts, and the weather wasn�t +Professor Antevs (p. 19) isn�t sure the Ice Age is over yet! This +melting sometimes went by fits and starts, and the weather wasn�t always changing for the better; but there was at least one time when European weather was even better than it is now. @@ -2927,16 +2927,16 @@ Sweden. Much of this north European material comes from bogs and swamps where it had become water-logged and has kept very well. Thus we have much more complete _assemblages_[4] than for any time earlier. - [4] �Assemblage� is a useful word when there are different kinds of + [4] �Assemblage� is a useful word when there are different kinds of archeological materials belonging together, from one area and of - one time. An assemblage is made up of a number of �industries� + one time. An assemblage is made up of a number of �industries� (that is, all the tools in chipped stone, all the tools in bone, all the tools in wood, the traces of houses, etc.) and everything else that manages to survive, such as the art, the burials, the bones of the animals used as food, and the traces of plant foods; in fact, everything that has been left to us and can be used to help reconstruct the lives of the people to - whom it once belonged. Our own present-day �assemblage� would be + whom it once belonged. Our own present-day �assemblage� would be the sum total of all the objects in our mail-order catalogues, department stores and supply houses of every sort, our churches, our art galleries and other buildings, together with our roads, @@ -2976,7 +2976,7 @@ found. It seems likely that the Maglemosian bog finds are remains of summer camps, and that in winter the people moved to higher and drier regions. -Childe calls them the �Forest folk�; they probably lived much the +Childe calls them the �Forest folk�; they probably lived much the same sort of life as did our pre-agricultural Indians of the north central states. They hunted small game or deer; they did a great deal of fishing; they collected what plant food they could find. In fact, @@ -3010,7 +3010,7 @@ South of the north European belt the hunting-food-collecting peoples were living on as best they could during this time. One interesting group, which seems to have kept to the regions of sandy soil and scrub forest, made great quantities of geometric microliths. These are the -materials called _Tardenoisian_. The materials of the �Forest folk� of +materials called _Tardenoisian_. The materials of the �Forest folk� of France and central Europe generally are called _Azilian_; Dr. Movius believes the term might best be restricted to the area south of the Loire River. @@ -3032,24 +3032,24 @@ to it than this. Professor Mathiassen of Copenhagen, who knows the archeological remains of this time very well, poses a question. He speaks of the material -as being neither rich nor progressive, in fact �rather stagnant,� but -he goes on to add that the people had a certain �receptiveness� and +as being neither rich nor progressive, in fact �rather stagnant,� but +he goes on to add that the people had a certain �receptiveness� and were able to adapt themselves quickly when the next change did come. -My own understanding of the situation is that the �Forest folk� made +My own understanding of the situation is that the �Forest folk� made nothing as spectacular as had the producers of the earlier Magdalenian assemblage and the Franco-Cantabrian art. On the other hand, they _seem_ to have been making many more different kinds of tools for many more different kinds of tasks than had their Ice Age forerunners. I -emphasize �seem� because the preservation in the Maglemosian bogs +emphasize �seem� because the preservation in the Maglemosian bogs is very complete; certainly we cannot list anywhere near as many different things for earlier times as we did for the Maglemosians (p. 94). I believe this experimentation with all kinds of new tools and gadgets, this intensification of adaptiveness (p. 91), this -�receptiveness,� even if it is still only pointed toward hunting, +�receptiveness,� even if it is still only pointed toward hunting, fishing, and food-collecting, is an important thing. Remember that the only marker we have handy for the _beginning_ of -this tendency toward �receptiveness� and experimentation is the +this tendency toward �receptiveness� and experimentation is the little microlithic blade tools of various geometric forms. These, we saw, began before the last ice had melted away, and they lasted on in use for a very long time. I wish there were a better marker than @@ -3063,7 +3063,7 @@ CHANGES IN OTHER AREAS? All this last section was about Europe. How about the rest of the world when the last glaciers were melting away? -We simply don�t know much about this particular time in other parts +We simply don�t know much about this particular time in other parts of the world except in Europe, the Mediterranean basin and the Middle East. People were certainly continuing to move into the New World by way of Siberia and the Bering Strait about this time. But for the @@ -3075,10 +3075,10 @@ clear information. REAL CHANGE AND PRELUDE IN THE NEAR EAST The appearance of the microliths and the developments made by the -�Forest folk� of northwestern Europe also mark an end. They show us +�Forest folk� of northwestern Europe also mark an end. They show us the terminal phase of the old food-collecting way of life. It grows increasingly clear that at about the same time that the Maglemosian and -other �Forest folk� were adapting themselves to hunting, fishing, and +other �Forest folk� were adapting themselves to hunting, fishing, and collecting in new ways to fit the post-glacial environment, something completely new was being made ready in western Asia. @@ -3098,7 +3098,7 @@ simply gathering or collecting it. When their food-production became reasonably effective, people could and did settle down in village-farming communities. With the appearance of the little farming villages, a new way of life was actually under way. Professor Childe -has good reason to speak of the �food-producing revolution,� for it was +has good reason to speak of the �food-producing revolution,� for it was indeed a revolution. @@ -3117,8 +3117,8 @@ before the _how_ and _why_ answers begin to appear. Anthropologically trained archeologists are fascinated with the cultures of men in times of great change. About ten or twelve thousand years ago, the general level of culture in many parts of the world seems to have been ready -for change. In northwestern Europe, we saw that cultures �changed -just enough so that they would not have to change.� We linked this to +for change. In northwestern Europe, we saw that cultures �changed +just enough so that they would not have to change.� We linked this to environmental changes with the coming of post-glacial times. In western Asia, we archeologists can prove that the food-producing @@ -3155,7 +3155,7 @@ living as the Maglemosians did? These are the questions we still have to face. -CULTURAL �RECEPTIVENESS� AND PROMISING ENVIRONMENTS +CULTURAL �RECEPTIVENESS� AND PROMISING ENVIRONMENTS Until the archeologists and the natural scientists--botanists, geologists, zoologists, and general ecologists--have spent many more @@ -3163,15 +3163,15 @@ years on the problem, we shall not have full _how_ and _why_ answers. I do think, however, that we are beginning to understand what to look for. We shall have to learn much more of what makes the cultures of men -�receptive� and experimental. Did change in the environment alone -force it? Was it simply a case of Professor Toynbee�s �challenge and -response?� I cannot believe the answer is quite that simple. Were it -so simple, we should want to know why the change hadn�t come earlier, +�receptive� and experimental. Did change in the environment alone +force it? Was it simply a case of Professor Toynbee�s �challenge and +response?� I cannot believe the answer is quite that simple. Were it +so simple, we should want to know why the change hadn�t come earlier, along with earlier environmental changes. We shall not know the answer, however, until we have excavated the traces of many more cultures of the time in question. We shall doubtless also have to learn more about, and think imaginatively about, the simpler cultures still left today. -The �mechanics� of culture in general will be bound to interest us. +The �mechanics� of culture in general will be bound to interest us. It will also be necessary to learn much more of the environments of 10,000 to 12,000 years ago. In which regions of the world were the @@ -3228,7 +3228,7 @@ THE OLD THEORY TOO SIMPLE FOR THE FACTS This theory was set up before we really knew anything in detail about the later prehistory of the Near and Middle East. We now know that -the facts which have been found don�t fit the old theory at all well. +the facts which have been found don�t fit the old theory at all well. Also, I have yet to find an American meteorologist who feels that we know enough about the changes in the weather pattern to say that it can have been so simple and direct. And, of course, the glacial ice which @@ -3238,7 +3238,7 @@ of great alpine glaciers, and long periods of warm weather in between. If the rain belt moved north as the glaciers melted for the last time, it must have moved in the same direction in earlier times. Thus, the forced neighborliness of men, plants, and animals in river valleys and -oases must also have happened earlier. Why didn�t domestication happen +oases must also have happened earlier. Why didn�t domestication happen earlier, then? Furthermore, it does not seem to be in the oases and river valleys @@ -3275,20 +3275,20 @@ archeologists, probably through habit, favor an old scheme of Grecized names for the subdivisions: paleolithic, mesolithic, neolithic. I refuse to use these words myself. They have meant too many different things to too many different people and have tended to hide some pretty -fuzzy thinking. Probably you haven�t even noticed my own scheme of -subdivision up to now, but I�d better tell you in general what it is. +fuzzy thinking. Probably you haven�t even noticed my own scheme of +subdivision up to now, but I�d better tell you in general what it is. I think of the earliest great group of archeological materials, from which we can deduce only a food-gathering way of culture, as the -_food-gathering stage_. I say �stage� rather than �age,� because it +_food-gathering stage_. I say �stage� rather than �age,� because it is not quite over yet; there are still a few primitive people in out-of-the-way parts of the world who remain in the _food-gathering stage_. In fact, Professor Julian Steward would probably prefer to call it a food-gathering _level_ of existence, rather than a stage. This would be perfectly acceptable to me. I also tend to find myself using _collecting_, rather than _gathering_, for the more recent aspects or -era of the stage, as the word �collecting� appears to have more sense -of purposefulness and specialization than does �gathering� (see p. +era of the stage, as the word �collecting� appears to have more sense +of purposefulness and specialization than does �gathering� (see p. 91). Now, while I think we could make several possible subdivisions of the @@ -3297,22 +3297,22 @@ believe the only one which means much to us here is the last or _terminal sub-era of food-collecting_ of the whole food-gathering stage. The microliths seem to mark its approach in the northwestern part of the Old World. It is really shown best in the Old World by -the materials of the �Forest folk,� the cultural adaptation to the +the materials of the �Forest folk,� the cultural adaptation to the post-glacial environment in northwestern Europe. We talked about -the �Forest folk� at the beginning of this chapter, and I used the +the �Forest folk� at the beginning of this chapter, and I used the Maglemosian assemblage of Denmark as an example. [5] It is difficult to find words which have a sequence or gradation of meaning with respect to both development and a range of time in the past, or with a range of time from somewhere in the past which is perhaps not yet ended. One standard Webster definition - of _stage_ is: �One of the steps into which the material - development of man ... is divided.� I cannot find any dictionary + of _stage_ is: �One of the steps into which the material + development of man ... is divided.� I cannot find any dictionary definition that suggests which of the words, _stage_ or _era_, has the meaning of a longer span of time. Therefore, I have chosen to let my eras be shorter, and to subdivide my stages - into eras. Webster gives _era_ as: �A signal stage of history, - an epoch.� When I want to subdivide my eras, I find myself using + into eras. Webster gives _era_ as: �A signal stage of history, + an epoch.� When I want to subdivide my eras, I find myself using _sub-eras_. Thus I speak of the _eras_ within a _stage_ and of the _sub-eras_ within an _era_; that is, I do so when I feel that I really have to, and when the evidence is clear enough to @@ -3328,9 +3328,9 @@ realms of culture. It is rather that for most of prehistoric time the materials left to the archeologists tend to limit our deductions to technology and economics. -I�m so soon out of my competence, as conventional ancient history +I�m so soon out of my competence, as conventional ancient history begins, that I shall only suggest the earlier eras of the -food-producing stage to you. This book is about prehistory, and I�m not +food-producing stage to you. This book is about prehistory, and I�m not a universal historian. @@ -3339,28 +3339,28 @@ THE TWO EARLIEST ERAS OF THE FOOD-PRODUCING STAGE The food-producing stage seems to appear in western Asia with really revolutionary suddenness. It is seen by the relative speed with which the traces of new crafts appear in the earliest village-farming -community sites we�ve dug. It is seen by the spread and multiplication +community sites we�ve dug. It is seen by the spread and multiplication of these sites themselves, and the remarkable growth in human -population we deduce from this increase in sites. We�ll look at some +population we deduce from this increase in sites. We�ll look at some of these sites and the archeological traces they yield in the next chapter. When such village sites begin to appear, I believe we are in the _era of the primary village-farming community_. I also believe this is the second era of the food-producing stage. The first era of the food-producing stage, I believe, was an _era of -incipient cultivation and animal domestication_. I keep saying �I -believe� because the actual evidence for this earlier era is so slight +incipient cultivation and animal domestication_. I keep saying �I +believe� because the actual evidence for this earlier era is so slight that one has to set it up mainly by playing a hunch for it. The reason for playing the hunch goes about as follows. One thing we seem to be able to see, in the food-collecting era in general, is a tendency for people to begin to settle down. This settling down seemed to become further intensified in the terminal -era. How this is connected with Professor Mathiassen�s �receptiveness� +era. How this is connected with Professor Mathiassen�s �receptiveness� and the tendency to be experimental, we do not exactly know. The evidence from the New World comes into play here as well as that from the Old World. With this settling down in one place, the people of the -terminal era--especially the �Forest folk� whom we know best--began +terminal era--especially the �Forest folk� whom we know best--began making a great variety of new things. I remarked about this earlier in the chapter. Dr. Robert M. Adams is of the opinion that this atmosphere of experimentation with new tools--with new ways of collecting food--is @@ -3368,9 +3368,9 @@ the kind of atmosphere in which one might expect trials at planting and at animal domestication to have been made. We first begin to find traces of more permanent life in outdoor camp sites, although caves were still inhabited at the beginning of the terminal era. It is not -surprising at all that the �Forest folk� had already domesticated the +surprising at all that the �Forest folk� had already domesticated the dog. In this sense, the whole era of food-collecting was becoming ready -and almost �incipient� for cultivation and animal domestication. +and almost �incipient� for cultivation and animal domestication. Northwestern Europe was not the place for really effective beginnings in agriculture and animal domestication. These would have had to take @@ -3425,13 +3425,13 @@ zone which surrounds the drainage basin of the Tigris and Euphrates Rivers at elevations of from approximately 2,000 to 5,000 feet. The lower alluvial land of the Tigris-Euphrates basin itself has very little rainfall. Some years ago Professor James Henry Breasted called -the alluvial lands of the Tigris-Euphrates a part of the �fertile -crescent.� These alluvial lands are very fertile if irrigated. Breasted +the alluvial lands of the Tigris-Euphrates a part of the �fertile +crescent.� These alluvial lands are very fertile if irrigated. Breasted was most interested in the oriental civilizations of conventional ancient history, and irrigation had been discovered before they appeared. -The country of hilly flanks above Breasted�s crescent receives from +The country of hilly flanks above Breasted�s crescent receives from 10 to 20 or more inches of winter rainfall each year, which is about what Kansas has. Above the hilly-flanks zone tower the peaks and ridges of the Lebanon-Amanus chain bordering the coast-line from Palestine @@ -3440,7 +3440,7 @@ range of the Iraq-Iran borderland. This rugged mountain frame for our hilly-flanks zone rises to some magnificent alpine scenery, with peaks of from ten to fifteen thousand feet in elevation. There are several gaps in the Mediterranean coastal portion of the frame, through which -the winter�s rain-bearing winds from the sea may break so as to carry +the winter�s rain-bearing winds from the sea may break so as to carry rain to the foothills of the Taurus and the Zagros. The picture I hope you will have from this description is that of an @@ -3482,7 +3482,7 @@ hilly-flanks zone in their wild state. With a single exception--that of the dog--the earliest positive evidence of domestication includes the two forms of wheat, the barley, and the goat. The evidence comes from within the hilly-flanks zone. -However, it comes from a settled village proper, Jarmo (which I�ll +However, it comes from a settled village proper, Jarmo (which I�ll describe in the next chapter), and is thus from the era of the primary village-farming community. We are still without positive evidence of domesticated grain and animals in the first era of the food-producing @@ -3534,9 +3534,9 @@ and the spread of ideas of people who had passed on into one of the more developed eras. In many cases, the terminal era of food-collecting was ended by the incoming of the food-producing peoples themselves. For example, the practices of food-production were carried into Europe -by the actual movement of some numbers of peoples (we don�t know how +by the actual movement of some numbers of peoples (we don�t know how many) who had reached at least the level of the primary village-farming -community. The �Forest folk� learned food-production from them. There +community. The �Forest folk� learned food-production from them. There was never an era of incipient cultivation and domestication proper in Europe, if my hunch is right. @@ -3547,16 +3547,16 @@ The way I see it, two things were required in order that an era of incipient cultivation and domestication could begin. First, there had to be the natural environment of a nuclear area, with its whole group of plants and animals capable of domestication. This is the aspect of -the matter which we�ve said is directly given by nature. But it is +the matter which we�ve said is directly given by nature. But it is quite possible that such an environment with such a group of plants and animals in it may have existed well before ten thousand years ago in the Near East. It is also quite possible that the same promising condition may have existed in regions which never developed into nuclear areas proper. Here, again, we come back to the cultural factor. -I think it was that �atmosphere of experimentation� we�ve talked about -once or twice before. I can�t define it for you, other than to say that +I think it was that �atmosphere of experimentation� we�ve talked about +once or twice before. I can�t define it for you, other than to say that by the end of the Ice Age, the general level of many cultures was ready -for change. Ask me how and why this was so, and I�ll tell you we don�t +for change. Ask me how and why this was so, and I�ll tell you we don�t know yet, and that if we did understand this kind of question, there would be no need for me to go on being a prehistorian! @@ -3590,7 +3590,7 @@ such collections for the modern wild forms of animals and plants from some of our nuclear areas. In the nuclear area in the Near East, some of the wild animals, at least, have already become extinct. There are no longer wild cattle or wild horses in western Asia. We know they were -there from the finds we�ve made in caves of late Ice Age times, and +there from the finds we�ve made in caves of late Ice Age times, and from some slightly later sites. @@ -3601,7 +3601,7 @@ incipient era of cultivation and animal domestication. I am closing this chapter with descriptions of two of the best Near Eastern examples I know of. You may not be satisfied that what I am able to describe makes a full-bodied era of development at all. Remember, however, that -I�ve told you I�m largely playing a kind of a hunch, and also that the +I�ve told you I�m largely playing a kind of a hunch, and also that the archeological materials of this era will always be extremely difficult to interpret. At the beginning of any new way of life, there will be a great tendency for people to make-do, at first, with tools and habits @@ -3613,7 +3613,7 @@ THE NATUFIAN, AN ASSEMBLAGE OF THE INCIPIENT ERA The assemblage called the Natufian comes from the upper layers of a number of caves in Palestine. Traces of its flint industry have also -turned up in Syria and Lebanon. We don�t know just how old it is. I +turned up in Syria and Lebanon. We don�t know just how old it is. I guess that it probably falls within five hundred years either way of about 5000 B.C. @@ -3662,7 +3662,7 @@ pendants. There were also beads and pendants of pierced teeth and shell. A number of Natufian burials have been found in the caves; some burials were grouped together in one grave. The people who were buried within the Mount Carmel cave were laid on their backs in an extended position, -while those on the terrace seem to have been �flexed� (placed in their +while those on the terrace seem to have been �flexed� (placed in their graves in a curled-up position). This may mean no more than that it was easier to dig a long hole in cave dirt than in the hard-packed dirt of the terrace. The people often had some kind of object buried with them, @@ -3679,7 +3679,7 @@ beads. GROUND STONE BONE] -The animal bones of the Natufian layers show beasts of a �modern� type, +The animal bones of the Natufian layers show beasts of a �modern� type, but with some differences from those of present-day Palestine. The bones of the gazelle far outnumber those of the deer; since gazelles like a much drier climate than deer, Palestine must then have had much @@ -3692,9 +3692,9 @@ Maglemosian of northern Europe. More recently, it has been reported that a domesticated goat is also part of the Natufian finds. The study of the human bones from the Natufian burials is not yet -complete. Until Professor McCown�s study becomes available, we may note -Professor Coon�s assessment that these people were of a �basically -Mediterranean type.� +complete. Until Professor McCown�s study becomes available, we may note +Professor Coon�s assessment that these people were of a �basically +Mediterranean type.� THE KARIM SHAHIR ASSEMBLAGE @@ -3704,11 +3704,11 @@ of a temporary open site or encampment. It lies on the top of a bluff in the Kurdish hill-country of northeastern Iraq. It was dug by Dr. Bruce Howe of the expedition I directed in 1950-51 for the Oriental Institute and the American Schools of Oriental Research. In 1954-55, -our expedition located another site, M�lefaat, with general resemblance +our expedition located another site, M�lefaat, with general resemblance to Karim Shahir, but about a hundred miles north of it. In 1956, Dr. Ralph Solecki located still another Karim Shahir type of site called Zawi Chemi Shanidar. The Zawi Chemi site has a radiocarbon date of 8900 -� 300 B.C. +� 300 B.C. Karim Shahir has evidence of only one very shallow level of occupation. It was probably not lived on very long, although the people who lived @@ -3717,7 +3717,7 @@ layer yielded great numbers of fist-sized cracked pieces of limestone, which had been carried up from the bed of a stream at the bottom of the bluff. We think these cracked stones had something to do with a kind of architecture, but we were unable to find positive traces of hut plans. -At M�lefaat and Zawi Chemi, there were traces of rounded hut plans. +At M�lefaat and Zawi Chemi, there were traces of rounded hut plans. As in the Natufian, the great bulk of small objects of the Karim Shahir assemblage was in chipped flint. A large proportion of the flint tools @@ -3737,7 +3737,7 @@ clay figurines which seemed to be of animal form. UNBAKED CLAY SHELL BONE - �ARCHITECTURE�] + �ARCHITECTURE�] Karim Shahir did not yield direct evidence of the kind of vegetable food its people ate. The animal bones showed a considerable @@ -3746,7 +3746,7 @@ domestication--sheep, goat, cattle, horse, dog--as compared with animal bones from the earlier cave sites of the area, which have a high proportion of bones of wild forms like deer and gazelle. But we do not know that any of the Karim Shahir animals were actually domesticated. -Some of them may have been, in an �incipient� way, but we have no means +Some of them may have been, in an �incipient� way, but we have no means at the moment that will tell us from the bones alone. @@ -3761,7 +3761,7 @@ goat, and the general animal situation at Karim Shahir to hint at an incipient approach to food-production. At Karim Shahir, there was the tendency to settle down out in the open; this is echoed by the new reports of open air Natufian sites. The large number of cracked stones -certainly indicates that it was worth the peoples� while to have some +certainly indicates that it was worth the peoples� while to have some kind of structure, even if the site as a whole was short-lived. It is a part of my hunch that these things all point toward @@ -3771,13 +3771,13 @@ which we shall look at next, are fully food-producing, the Natufian and Karim Shahir folk had not yet arrived. I think they were part of a general build-up to full scale food-production. They were possibly controlling a few animals of several kinds and perhaps one or two -plants, without realizing the full possibilities of this �control� as a +plants, without realizing the full possibilities of this �control� as a new way of life. This is why I think of the Karim Shahir and Natufian folk as being at a level, or in an era, of incipient cultivation and domestication. But we shall have to do a great deal more excavation in this range of time -before we�ll get the kind of positive information we need. +before we�ll get the kind of positive information we need. SUMMARY @@ -3798,7 +3798,7 @@ history. We know the earliest village-farming communities appeared in western Asia, in a nuclear area. We do not yet know why the Near Eastern -experiment came first, or why it didn�t happen earlier in some other +experiment came first, or why it didn�t happen earlier in some other nuclear area. Apparently, the level of culture and the promise of the natural environment were ready first in western Asia. The next sites we look at will show a simple but effective food-production already @@ -3835,7 +3835,7 @@ contrast between food-collecting and food-producing as ways of life. THE DIFFERENCE BETWEEN FOOD-COLLECTORS AND FOOD-PRODUCERS -Childe used the word �revolution� because of the radical change that +Childe used the word �revolution� because of the radical change that took place in the habits and customs of man. Food-collectors--that is, hunters, fishers, berry- and nut-gatherers--had to live in small groups or bands, for they had to be ready to move wherever their food supply @@ -3851,7 +3851,7 @@ for clothing beyond the tools that were probably used to dress the skins of animals; no time to think of much of anything but food and protection and disposal of the dead when death did come: an existence which takes nature as it finds it, which does little or nothing to -modify nature--all in all, a savage�s existence, and a very tough one. +modify nature--all in all, a savage�s existence, and a very tough one. A man who spends his whole life following animals just to kill them to eat, or moving from one berry patch to another, is really living just like an animal himself. @@ -3859,10 +3859,10 @@ like an animal himself. THE FOOD-PRODUCING ECONOMY -Against this picture let me try to draw another--that of man�s life -after food-production had begun. His meat was stored �on the hoof,� +Against this picture let me try to draw another--that of man�s life +after food-production had begun. His meat was stored �on the hoof,� his grain in silos or great pottery jars. He lived in a house: it was -worth his while to build one, because he couldn�t move far from his +worth his while to build one, because he couldn�t move far from his fields and flocks. In his neighborhood enough food could be grown and enough animals bred so that many people were kept busy. They all lived close to their flocks and fields, in a village. The village was @@ -3872,7 +3872,7 @@ Children and old men could shepherd the animals by day or help with the lighter work in the fields. After the crops had been harvested the younger men might go hunting and some of them would fish, but the food they brought in was only an addition to the food in the village; the -villagers wouldn�t starve, even if the hunters and fishermen came home +villagers wouldn�t starve, even if the hunters and fishermen came home empty-handed. There was more time to do different things, too. They began to modify @@ -3885,23 +3885,23 @@ people in the village who were becoming full-time craftsmen. Other things were changing, too. The villagers must have had to agree on new rules for living together. The head man of the village had problems different from those of the chief of the small -food-collectors� band. If somebody�s flock of sheep spoiled a wheat +food-collectors� band. If somebody�s flock of sheep spoiled a wheat field, the owner wanted payment for the grain he lost. The chief of the hunters was never bothered with such questions. Even the gods had changed. The spirits and the magic that had been used by hunters -weren�t of any use to the villagers. They needed gods who would watch +weren�t of any use to the villagers. They needed gods who would watch over the fields and the flocks, and they eventually began to erect buildings where their gods might dwell, and where the men who knew most about the gods might live. -WAS FOOD-PRODUCTION A �REVOLUTION�? +WAS FOOD-PRODUCTION A �REVOLUTION�? If you can see the difference between these two pictures--between life in the food-collecting stage and life after food-production -had begun--you�ll see why Professor Childe speaks of a revolution. -By revolution, he doesn�t mean that it happened over night or that -it happened only once. We don�t know exactly how long it took. Some +had begun--you�ll see why Professor Childe speaks of a revolution. +By revolution, he doesn�t mean that it happened over night or that +it happened only once. We don�t know exactly how long it took. Some people think that all these changes may have occurred in less than 500 years, but I doubt that. The incipient era was probably an affair of some duration. Once the level of the village-farming community had @@ -3915,7 +3915,7 @@ been achieved with truly revolutionary suddenness. GAPS IN OUR KNOWLEDGE OF THE NEAR EAST -If you�ll look again at the chart (p. 111) you�ll see that I have +If you�ll look again at the chart (p. 111) you�ll see that I have very few sites and assemblages to name in the incipient era of cultivation and domestication, and not many in the earlier part of the primary village-farming level either. Thanks in no small part @@ -3926,20 +3926,20 @@ yard-stick here. But I am far from being able to show you a series of Sears Roebuck catalogues, even century by century, for any part of the nuclear area. There is still a great deal of earth to move, and a great mass of material to recover and interpret before we even begin to -understand �how� and �why.� +understand �how� and �why.� Perhaps here, because this kind of archeology is really my specialty, -you�ll excuse it if I become personal for a moment. I very much look +you�ll excuse it if I become personal for a moment. I very much look forward to having further part in closing some of the gaps in knowledge -of the Near East. This is not, as I�ve told you, the spectacular +of the Near East. This is not, as I�ve told you, the spectacular range of Near Eastern archeology. There are no royal tombs, no gold, no great buildings or sculpture, no writing, in fact nothing to excite the normal museum at all. Nevertheless it is a range which, idea-wise, gives the archeologist tremendous satisfaction. The country of the hilly flanks is an exciting combination of green grasslands and mountainous ridges. The Kurds, who inhabit the part of the area -in which I�ve worked most recently, are an extremely interesting and -hospitable people. Archeologists don�t become rich, but I�ll forego +in which I�ve worked most recently, are an extremely interesting and +hospitable people. Archeologists don�t become rich, but I�ll forego the Cadillac for any bright spring morning in the Kurdish hills, on a good site with a happy crew of workmen and an interested and efficient staff. It is probably impossible to convey the full feeling which life @@ -3965,15 +3965,15 @@ like the use of pottery borrowed from the more developed era of the same time in the nuclear area. The same general explanation doubtless holds true for certain materials in Egypt, along the upper Nile and in the Kharga oasis: these materials, called Sebilian III, the Khartoum -�neolithic,� and the Khargan microlithic, are from surface sites, +�neolithic,� and the Khargan microlithic, are from surface sites, not from caves. The chart (p. 111) shows where I would place these materials in era and time. [Illustration: THE HILLY FLANKS OF THE CRESCENT AND EARLY SITES OF THE NEAR EAST] -Both M�lefaat and Dr. Solecki�s Zawi Chemi Shanidar site appear to have -been slightly more �settled in� than was Karim Shahir itself. But I do +Both M�lefaat and Dr. Solecki�s Zawi Chemi Shanidar site appear to have +been slightly more �settled in� than was Karim Shahir itself. But I do not think they belong to the era of farming-villages proper. The first site of this era, in the hills of Iraqi Kurdistan, is Jarmo, on which we have spent three seasons of work. Following Jarmo comes a variety of @@ -3989,9 +3989,9 @@ times when their various cultures flourished, there must have been many little villages which shared the same general assemblage. We are only now beginning to locate them again. Thus, if I speak of Jarmo, or Jericho, or Sialk as single examples of their particular kinds of -assemblages, I don�t mean that they were unique at all. I think I could +assemblages, I don�t mean that they were unique at all. I think I could take you to the sites of at least three more Jarmos, within twenty -miles of the original one. They are there, but they simply haven�t yet +miles of the original one. They are there, but they simply haven�t yet been excavated. In 1956, a Danish expedition discovered material of Jarmo type at Shimshara, only two dozen miles northeast of Jarmo, and below an assemblage of Hassunan type (which I shall describe presently). @@ -4000,15 +4000,15 @@ below an assemblage of Hassunan type (which I shall describe presently). THE GAP BETWEEN KARIM SHAHIR AND JARMO As we see the matter now, there is probably still a gap in the -available archeological record between the Karim Shahir-M�lefaat-Zawi +available archeological record between the Karim Shahir-M�lefaat-Zawi Chemi group (of the incipient era) and that of Jarmo (of the village-farming era). Although some items of the Jarmo type materials do reflect the beginnings of traditions set in the Karim Shahir group (see p. 120), there is not a clear continuity. Moreover--to the degree that we may trust a few radiocarbon dates--there would appear to be around two thousand years of difference in time. The single -available Zawi Chemi �date� is 8900 � 300 B.C.; the most reasonable -group of �dates� from Jarmo average to about 6750 � 200 B.C. I am +available Zawi Chemi �date� is 8900 � 300 B.C.; the most reasonable +group of �dates� from Jarmo average to about 6750 � 200 B.C. I am uncertain about this two thousand years--I do not think it can have been so long. @@ -4021,7 +4021,7 @@ JARMO, IN THE KURDISH HILLS, IRAQ The site of Jarmo has a depth of deposit of about twenty-seven feet, and approximately a dozen layers of architectural renovation and -change. Nevertheless it is a �one period� site: its assemblage remains +change. Nevertheless it is a �one period� site: its assemblage remains essentially the same throughout, although one or two new items are added in later levels. It covers about four acres of the top of a bluff, below which runs a small stream. Jarmo lies in the hill country @@ -4078,7 +4078,7 @@ human beings in clay; one type of human figurine they favored was that of a markedly pregnant woman, probably the expression of some sort of fertility spirit. They provided their house floors with baked-in-place depressions, either as basins or hearths, and later with domed ovens of -clay. As we�ve noted, the houses themselves were of clay or mud; one +clay. As we�ve noted, the houses themselves were of clay or mud; one could almost say they were built up like a house-sized pot. Then, finally, the idea of making portable pottery itself appeared, although I very much doubt that the people of the Jarmo village discovered the @@ -4095,11 +4095,11 @@ over three hundred miles to the north. Already a bulk carrying trade had been established--the forerunner of commerce--and the routes were set by which, in later times, the metal trade was to move. -There are now twelve radioactive carbon �dates� from Jarmo. The most -reasonable cluster of determinations averages to about 6750 � 200 -B.C., although there is a completely unreasonable range of �dates� +There are now twelve radioactive carbon �dates� from Jarmo. The most +reasonable cluster of determinations averages to about 6750 � 200 +B.C., although there is a completely unreasonable range of �dates� running from 3250 to 9250 B.C.! _If_ I am right in what I take to be -�reasonable,� the first flush of the food-producing revolution had been +�reasonable,� the first flush of the food-producing revolution had been achieved almost nine thousand years ago. @@ -4117,7 +4117,7 @@ it, but the Hassunan sites seem to cluster at slightly lower elevations than those we have been talking about so far. The catalogue of the Hassuna assemblage is of course more full and -elaborate than that of Jarmo. The Iraqi government�s archeologists +elaborate than that of Jarmo. The Iraqi government�s archeologists who dug Hassuna itself, exposed evidence of increasing architectural know-how. The walls of houses were still formed of puddled mud; sun-dried bricks appear only in later periods. There were now several @@ -4130,16 +4130,16 @@ largely disappeared by Hassunan times. The flint work of the Hassunan catalogue is, by and large, a wretched affair. We might guess that the kinaesthetic concentration of the Hassuna craftsmen now went into other categories; that is, they suddenly discovered they might have more fun -working with the newer materials. It�s a shame, for example, that none +working with the newer materials. It�s a shame, for example, that none of their weaving is preserved for us. The two available radiocarbon determinations from Hassunan contexts -stand at about 5100 and 5600 B.C. � 250 years. +stand at about 5100 and 5600 B.C. � 250 years. OTHER EARLY VILLAGE SITES IN THE NUCLEAR AREA -I�ll now name and very briefly describe a few of the other early +I�ll now name and very briefly describe a few of the other early village assemblages either in or adjacent to the hilly flanks of the crescent. Unfortunately, we do not have radioactive carbon dates for many of these materials. We may guess that some particular assemblage, @@ -4177,7 +4177,7 @@ ecological niche, some seven hundred feet below sea level; it is geographically within the hilly-flanks zone but environmentally not part of it. -Several radiocarbon �dates� for Jericho fall within the range of those +Several radiocarbon �dates� for Jericho fall within the range of those I find reasonable for Jarmo, and their internal statistical consistency is far better than that for the Jarmo determinations. It is not yet clear exactly what this means. @@ -4226,7 +4226,7 @@ how things were made are different; the Sialk assemblage represents still another cultural pattern. I suspect it appeared a bit later in time than did that of Hassuna. There is an important new item in the Sialk catalogue. The Sialk people made small drills or pins of -hammered copper. Thus the metallurgist�s specialized craft had made its +hammered copper. Thus the metallurgist�s specialized craft had made its appearance. There is at least one very early Iranian site on the inward slopes @@ -4246,7 +4246,7 @@ shore of the Fayum lake. The Fayum materials come mainly from grain bins or silos. Another site, Merimde, in the western part of the Nile delta, shows the remains of a true village, but it may be slightly later than the settlement of the Fayum. There are radioactive carbon -�dates� for the Fayum materials at about 4275 B.C. � 320 years, which +�dates� for the Fayum materials at about 4275 B.C. � 320 years, which is almost fifteen hundred years later than the determinations suggested for the Hassunan or Syro-Cilician assemblages. I suspect that this is a somewhat over-extended indication of the time it took for the @@ -4260,13 +4260,13 @@ the mound called Shaheinab. The Shaheinab catalogue roughly corresponds to that of the Fayum; the distance between the two places, as the Nile flows, is roughly 1,500 miles. Thus it took almost a thousand years for the new way of life to be carried as far south into Africa as Khartoum; -the two Shaheinab �dates� average about 3300 B.C. � 400 years. +the two Shaheinab �dates� average about 3300 B.C. � 400 years. If the movement was up the Nile (southward), as these dates suggest, then I suspect that the earliest available village material of middle Egypt, the so-called Tasian, is also later than that of the Fayum. The Tasian materials come from a few graves near a village called Deir -Tasa, and I have an uncomfortable feeling that the Tasian �assemblage� +Tasa, and I have an uncomfortable feeling that the Tasian �assemblage� may be mainly an artificial selection of poor examples of objects which belong in the following range of time. @@ -4280,7 +4280,7 @@ spread outward in space from the nuclear area, as time went on. There is good archeological evidence that both these processes took place. For the hill country of northeastern Iraq, in the nuclear area, we have already noticed how the succession (still with gaps) from Karim -Shahir, through M�lefaat and Jarmo, to Hassuna can be charted (see +Shahir, through M�lefaat and Jarmo, to Hassuna can be charted (see chart, p. 111). In the next chapter, we shall continue this charting and description of what happened in Iraq upward through time. We also watched traces of the new way of life move through space up the Nile @@ -4299,7 +4299,7 @@ appearance of the village-farming community there--is still an open one. In the last chapter, we noted the probability of an independent nuclear area in southeastern Asia. Professor Carl Sauer strongly champions the great importance of this area as _the_ original center -of agricultural pursuits, as a kind of �cradle� of all incipient eras +of agricultural pursuits, as a kind of �cradle� of all incipient eras of the Old World at least. While there is certainly not the slightest archeological evidence to allow us to go that far, we may easily expect that an early southeast Asian development would have been felt in @@ -4311,13 +4311,13 @@ way of life moved well beyond Khartoum in Africa. THE SPREAD OF THE VILLAGE-FARMING COMMUNITY WAY OF LIFE INTO EUROPE -How about Europe? I won�t give you many details. You can easily imagine +How about Europe? I won�t give you many details. You can easily imagine that the late prehistoric prelude to European history is a complicated affair. We all know very well how complicated an area Europe is now, with its welter of different languages and cultures. Remember, however, that a great deal of archeology has been done on the late prehistory of Europe, and very little on that of further Asia and Africa. If we knew -as much about these areas as we do of Europe, I expect we�d find them +as much about these areas as we do of Europe, I expect we�d find them just as complicated. This much is clear for Europe, as far as the spread of the @@ -4329,21 +4329,21 @@ in western Asia. I do not, of course, mean that there were traveling salesmen who carried these ideas and things to Europe with a commercial gleam in their eyes. The process took time, and the ideas and things must have been passed on from one group of people to the next. There -was also some actual movement of peoples, but we don�t know the size of +was also some actual movement of peoples, but we don�t know the size of the groups that moved. -The story of the �colonization� of Europe by the first farmers is +The story of the �colonization� of Europe by the first farmers is thus one of (1) the movement from the eastern Mediterranean lands of some people who were farmers; (2) the spread of ideas and things beyond the Near East itself and beyond the paths along which the -�colonists� moved; and (3) the adaptations of the ideas and things -by the indigenous �Forest folk�, about whose �receptiveness� Professor +�colonists� moved; and (3) the adaptations of the ideas and things +by the indigenous �Forest folk�, about whose �receptiveness� Professor Mathiassen speaks (p. 97). It is important to note that the resulting cultures in the new European environment were European, not Near -Eastern. The late Professor Childe remarked that �the peoples of the +Eastern. The late Professor Childe remarked that �the peoples of the West were not slavish imitators; they adapted the gifts from the East ... into a new and organic whole capable of developing on its own -original lines.� +original lines.� THE WAYS TO EUROPE @@ -4389,19 +4389,19 @@ Hill, the earliest known trace of village-farming communities in England, is about 2500 B.C. I would expect about 5500 B.C. to be a safe date to give for the well-developed early village communities of Syro-Cilicia. We suspect that the spread throughout Europe did not -proceed at an even rate. Professor Piggott writes that �at a date +proceed at an even rate. Professor Piggott writes that �at a date probably about 2600 B.C., simple agricultural communities were being established in Spain and southern France, and from the latter region a spread northwards can be traced ... from points on the French seaboard of the [English] Channel ... there were emigrations of a certain number of these tribes by boat, across to the chalk lands of Wessex and Sussex [in England], probably not more than three or four generations later -than the formation of the south French colonies.� +than the formation of the south French colonies.� New radiocarbon determinations are becoming available all the time--already several suggest that the food-producing way of life had reached the lower Rhine and Holland by 4000 B.C. But not all -prehistorians accept these �dates,� so I do not show them on my map +prehistorians accept these �dates,� so I do not show them on my map (p. 139). @@ -4427,7 +4427,7 @@ concentric sets of banks and ditches. Traces of oblong timber houses have been found, but not within the enclosures. The second type of structure is mine-shafts, dug down into the chalk beds where good flint for the making of axes or hoes could be found. The third type -of structure is long simple mounds or �unchambered barrows,� in one +of structure is long simple mounds or �unchambered barrows,� in one end of which burials were made. It has been commonly believed that the Windmill Hill assemblage belonged entirely to the cultural tradition which moved up through France to the Channel. Professor Piggott is now @@ -4443,12 +4443,12 @@ consists mainly of tombs and the contents of tombs, with only very rare settlement sites. The tombs were of some size and received the bodies of many people. The tombs themselves were built of stone, heaped over with earth; the stones enclosed a passage to a central chamber -(�passage graves�), or to a simple long gallery, along the sides of -which the bodies were laid (�gallery graves�). The general type of -construction is called �megalithic� (= great stone), and the whole +(�passage graves�), or to a simple long gallery, along the sides of +which the bodies were laid (�gallery graves�). The general type of +construction is called �megalithic� (= great stone), and the whole earth-mounded structure is often called a _barrow_. Since many have -proper chambers, in one sense or another, we used the term �unchambered -barrow� above to distinguish those of the Windmill Hill type from these +proper chambers, in one sense or another, we used the term �unchambered +barrow� above to distinguish those of the Windmill Hill type from these megalithic structures. There is some evidence for sacrifice, libations, and ceremonial fires, and it is clear that some form of community ritual was focused on the megalithic tombs. @@ -4466,7 +4466,7 @@ The third early British group of antiquities of this general time It is not so certain that the people who made this assemblage, called Peterborough, were actually farmers. While they may on occasion have practiced a simple agriculture, many items of their assemblage link -them closely with that of the �Forest folk� of earlier times in +them closely with that of the �Forest folk� of earlier times in England and in the Baltic countries. Their pottery is decorated with impressions of cords and is quite different from that of Windmill Hill and the megalithic builders. In addition, the distribution of their @@ -4479,7 +4479,7 @@ to acquire the raw material for stone axes. A probably slightly later culture, whose traces are best known from Skara Brae on Orkney, also had its roots in those cultures of the -Baltic area which fused out of the meeting of the �Forest folk� and +Baltic area which fused out of the meeting of the �Forest folk� and the peoples who took the eastern way into Europe. Skara Brae is very well preserved, having been built of thin stone slabs about which dune-sand drifted after the village died. The individual houses, the @@ -4498,14 +4498,14 @@ details which I have omitted in order to shorten the story. I believe some of the difficulty we have in understanding the establishment of the first farming communities in Europe is with -the word �colonization.� We have a natural tendency to think of -�colonization� as it has happened within the last few centuries. In the +the word �colonization.� We have a natural tendency to think of +�colonization� as it has happened within the last few centuries. In the case of the colonization of the Americas, for example, the colonists came relatively quickly, and in increasingly vast numbers. They had vastly superior technical, political, and war-making skills, compared with those of the Indians. There was not much mixing with the Indians. The case in Europe five or six thousand years ago must have been very -different. I wonder if it is even proper to call people �colonists� +different. I wonder if it is even proper to call people �colonists� who move some miles to a new region, settle down and farm it for some years, then move on again, generation after generation? The ideas and the things which these new people carried were only _potentially_ @@ -4521,12 +4521,12 @@ migrants were moving by boat, long distances may have been covered in a short time. Remember, however, we seem to have about three thousand years between the early Syro-Cilician villages and Windmill Hill. -Let me repeat Professor Childe again. �The peoples of the West were +Let me repeat Professor Childe again. �The peoples of the West were not slavish imitators: they adapted the gifts from the East ... into a new and organic whole capable of developing on its own original -lines.� Childe is of course completely conscious of the fact that his -�peoples of the West� were in part the descendants of migrants who came -originally from the �East,� bringing their �gifts� with them. This +lines.� Childe is of course completely conscious of the fact that his +�peoples of the West� were in part the descendants of migrants who came +originally from the �East,� bringing their �gifts� with them. This was the late prehistoric achievement of Europe--to take new ideas and things and some migrant peoples and, by mixing them with the old in its own environments, to forge a new and unique series of cultures. @@ -4553,14 +4553,14 @@ things first happened there and also because I know it best. There is another interesting thing, too. We have seen that the first experiment in village-farming took place in the Near East. So did -the first experiment in civilization. Both experiments �took.� The +the first experiment in civilization. Both experiments �took.� The traditions we live by today are based, ultimately, on those ancient beginnings in food-production and civilization in the Near East. -WHAT �CIVILIZATION� MEANS +WHAT �CIVILIZATION� MEANS -I shall not try to define �civilization� for you; rather, I shall +I shall not try to define �civilization� for you; rather, I shall tell you what the word brings to my mind. To me civilization means urbanization: the fact that there are cities. It means a formal political set-up--that there are kings or governing bodies that the @@ -4606,7 +4606,7 @@ of Mexico, the Mayas of Yucatan and Guatemala, and the Incas of the Andes were civilized. -WHY DIDN�T CIVILIZATION COME TO ALL FOOD-PRODUCERS? +WHY DIDN�T CIVILIZATION COME TO ALL FOOD-PRODUCERS? Once you have food-production, even at the well-advanced level of the village-farming community, what else has to happen before you @@ -4625,13 +4625,13 @@ early civilization, is still an open and very interesting question. WHERE CIVILIZATION FIRST APPEARED IN THE NEAR EAST You remember that our earliest village-farming communities lay along -the hilly flanks of a great �crescent.� (See map on p. 125.) -Professor Breasted�s �fertile crescent� emphasized the rich river +the hilly flanks of a great �crescent.� (See map on p. 125.) +Professor Breasted�s �fertile crescent� emphasized the rich river valleys of the Nile and the Tigris-Euphrates Rivers. Our hilly-flanks area of the crescent zone arches up from Egypt through Palestine and Syria, along southern Turkey into northern Iraq, and down along the southwestern fringe of Iran. The earliest food-producing villages we -know already existed in this area by about 6750 B.C. (� 200 years). +know already existed in this area by about 6750 B.C. (� 200 years). Now notice that this hilly-flanks zone does not include southern Mesopotamia, the alluvial land of the lower Tigris and Euphrates in @@ -4639,7 +4639,7 @@ Iraq, or the Nile Valley proper. The earliest known villages of classic Mesopotamia and Egypt seem to appear fifteen hundred or more years after those of the hilly-flanks zone. For example, the early Fayum village which lies near a lake west of the Nile Valley proper (see p. -135) has a radiocarbon date of 4275 B.C. � 320 years. It was in the +135) has a radiocarbon date of 4275 B.C. � 320 years. It was in the river lands, however, that the immediate beginnings of civilization were made. @@ -4657,8 +4657,8 @@ THE HILLY-FLANKS ZONE VERSUS THE RIVER LANDS Why did these two civilizations spring up in these two river lands which apparently were not even part of the area where the -village-farming community began? Why didn�t we have the first -civilizations in Palestine, Syria, north Iraq, or Iran, where we�re +village-farming community began? Why didn�t we have the first +civilizations in Palestine, Syria, north Iraq, or Iran, where we�re sure food-production had had a long time to develop? I think the probable answer gives a clue to the ways in which civilization began in Egypt and Mesopotamia. @@ -4669,7 +4669,7 @@ and Syria. There are pleasant mountain slopes, streams running out to the sea, and rain, at least in the winter months. The rain belt and the foothills of the Turkish mountains also extend to northern Iraq and on to the Iranian plateau. The Iranian plateau has its mountain valleys, -streams, and some rain. These hilly flanks of the �crescent,� through +streams, and some rain. These hilly flanks of the �crescent,� through most of its arc, are almost made-to-order for beginning farmers. The grassy slopes of the higher hills would be pasture for their herds and flocks. As soon as the earliest experiments with agriculture and @@ -4720,10 +4720,10 @@ Obviously, we can no longer find the first dikes or reservoirs of the Nile Valley, or the first canals or ditches of Mesopotamia. The same land has been lived on far too long for any traces of the first attempts to be left; or, especially in Egypt, it has been covered by -the yearly deposits of silt, dropped by the river floods. But we�re +the yearly deposits of silt, dropped by the river floods. But we�re pretty sure the first food-producers of Egypt and southern Mesopotamia must have made such dikes, canals, and ditches. In the first place, -there can�t have been enough rain for them to grow things otherwise. +there can�t have been enough rain for them to grow things otherwise. In the second place, the patterns for such projects seem to have been pretty well set by historic times. @@ -4733,10 +4733,10 @@ CONTROL OF THE RIVERS THE BUSINESS OF EVERYONE Here, then, is a _part_ of the reason why civilization grew in Egypt and Mesopotamia first--not in Palestine, Syria, or Iran. In the latter areas, people could manage to produce their food as individuals. It -wasn�t too hard; there were rain and some streams, and good pasturage +wasn�t too hard; there were rain and some streams, and good pasturage for the animals even if a crop or two went wrong. In Egypt and Mesopotamia, people had to put in a much greater amount of work, and -this work couldn�t be individual work. Whole villages or groups of +this work couldn�t be individual work. Whole villages or groups of people had to turn out to fix dikes or dig ditches. The dikes had to be repaired and the ditches carefully cleared of silt each year, or they would become useless. @@ -4745,7 +4745,7 @@ There also had to be hard and fast rules. The person who lived nearest the ditch or the reservoir must not be allowed to take all the water and leave none for his neighbors. It was not only a business of learning to control the rivers and of making their waters do the -farmer�s work. It also meant controlling men. But once these men had +farmer�s work. It also meant controlling men. But once these men had managed both kinds of controls, what a wonderful yield they had! The soil was already fertile, and the silt which came in the floods and ditches kept adding fertile soil. @@ -4756,7 +4756,7 @@ THE GERM OF CIVILIZATION IN EGYPT AND MESOPOTAMIA This learning to work together for the common good was the real germ of the Egyptian and the Mesopotamian civilizations. The bare elements of civilization were already there: the need for a governing hand and for -laws to see that the communities� work was done and that the water was +laws to see that the communities� work was done and that the water was justly shared. You may object that there is a sort of chicken and egg paradox in this idea. How could the people set up the rules until they had managed to get a way to live, and how could they manage to get a @@ -4781,12 +4781,12 @@ My explanation has been pointed particularly at Egypt and Mesopotamia. I have already told you that the irrigation and water-control part of it does not apply to the development of the Aztecs or the Mayas, or perhaps anybody else. But I think that a fair part of the story of -Egypt and Mesopotamia must be as I�ve just told you. +Egypt and Mesopotamia must be as I�ve just told you. I am particularly anxious that you do _not_ understand me to mean that irrigation _caused_ civilization. I am sure it was not that simple at all. For, in fact, a complex and highly engineered irrigation system -proper did not come until later times. Let�s say rather that the simple +proper did not come until later times. Let�s say rather that the simple beginnings of irrigation allowed and in fact encouraged a great number of things in the technological, political, social, and moral realms of culture. We do not yet understand what all these things were or how @@ -4842,7 +4842,7 @@ the mound which later became the holy Sumerian city of Eridu, Iraqi archeologists uncovered a handsome painted pottery. Pottery of the same type had been noticed earlier by German archeologists on the surface of a small mound, awash in the spring floods, near the remains of the -Biblical city of Erich (Sumerian = Uruk; Arabic = Warka). This �Eridu� +Biblical city of Erich (Sumerian = Uruk; Arabic = Warka). This �Eridu� pottery, which is about all we have of the assemblage of the people who once produced it, may be seen as a blend of the Samarran and Halafian painted pottery styles. This may over-simplify the case, but as yet we @@ -4864,7 +4864,7 @@ seems to move into place before the Halaf manifestation is finished, and to blend with it. The Ubaidian assemblage in the south is by far the more spectacular. The development of the temple has been traced at Eridu from a simple little structure to a monumental building some -62 feet long, with a pilaster-decorated fa�ade and an altar in its +62 feet long, with a pilaster-decorated fa�ade and an altar in its central chamber. There is painted Ubaidian pottery, but the style is hurried and somewhat careless and gives the _impression_ of having been a cheap mass-production means of decoration when compared with the @@ -4879,7 +4879,7 @@ turtle-like faces are another item in the southern Ubaidian assemblage. There is a large Ubaid cemetery at Eridu, much of it still awaiting excavation. The few skeletons so far tentatively studied reveal a -completely modern type of �Mediterraneanoid�; the individuals whom the +completely modern type of �Mediterraneanoid�; the individuals whom the skeletons represent would undoubtedly blend perfectly into the modern population of southern Iraq. What the Ubaidian assemblage says to us is that these people had already adapted themselves and their culture to @@ -4925,7 +4925,7 @@ woven stuffs must have been the mediums of exchange. Over what area did the trading net-work of Ubaid extend? We start with the idea that the Ubaidian assemblage is most richly developed in the south. We assume, I think, correctly, that it represents a cultural flowering of the south. -On the basis of the pottery of the still elusive �Eridu� immigrants +On the basis of the pottery of the still elusive �Eridu� immigrants who had first followed the rivers into alluvial Mesopotamia, we get the notion that the characteristic painted pottery style of Ubaid was developed in the southland. If this reconstruction is correct @@ -4935,7 +4935,7 @@ assemblage of (and from the southern point of view, _fairly_ pure) Ubaidian material in northern Iraq. The pottery appears all along the Iranian flanks, even well east of the head of the Persian Gulf, and ends in a later and spectacular flourish in an extremely handsome -painted style called the �Susa� style. Ubaidian pottery has been noted +painted style called the �Susa� style. Ubaidian pottery has been noted up the valleys of both of the great rivers, well north of the Iraqi and Syrian borders on the southern flanks of the Anatolian plateau. It reaches the Mediterranean Sea and the valley of the Orontes in @@ -4965,10 +4965,10 @@ Mesopotamia. Next, much to our annoyance, we have what is almost a temporary black-out. According to the system of terminology I favor, our next -�assemblage� after that of Ubaid is called the _Warka_ phase, from +�assemblage� after that of Ubaid is called the _Warka_ phase, from the Arabic name for the site of Uruk or Erich. We know it only from six or seven levels in a narrow test-pit at Warka, and from an even -smaller hole at another site. This �assemblage,� so far, is known only +smaller hole at another site. This �assemblage,� so far, is known only by its pottery, some of which still bears Ubaidian style painting. The characteristic Warkan pottery is unpainted, with smoothed red or gray surfaces and peculiar shapes. Unquestionably, there must be a great @@ -4979,7 +4979,7 @@ have to excavate it! THE DAWN OF CIVILIZATION After our exasperation with the almost unknown Warka interlude, -following the brilliant �false dawn� of Ubaid, we move next to an +following the brilliant �false dawn� of Ubaid, we move next to an assemblage which yields traces of a preponderance of those elements which we noted (p. 144) as meaning civilization. This assemblage is that called _Proto-Literate_; it already contains writing. On @@ -4988,8 +4988,8 @@ history--and no longer prehistory--the assemblage is named for the historical implications of its content, and no longer after the name of the site where it was first found. Since some of the older books used site-names for this assemblage, I will tell you that the Proto-Literate -includes the latter half of what used to be called the �Uruk period� -_plus_ all of what used to be called the �Jemdet Nasr period.� It shows +includes the latter half of what used to be called the �Uruk period� +_plus_ all of what used to be called the �Jemdet Nasr period.� It shows a consistent development from beginning to end. I shall, in fact, leave much of the description and the historic @@ -5033,18 +5033,18 @@ mental block seems to have been removed. Clay tablets bearing pictographic signs are the Proto-Literate forerunners of cuneiform writing. The earliest examples are not well -understood but they seem to be �devices for making accounts and -for remembering accounts.� Different from the later case in Egypt, +understood but they seem to be �devices for making accounts and +for remembering accounts.� Different from the later case in Egypt, where writing appears fully formed in the earliest examples, the development from simple pictographic signs to proper cuneiform writing may be traced, step by step, in Mesopotamia. It is most probable that the development of writing was connected with the temple and -the need for keeping account of the temple�s possessions. Professor +the need for keeping account of the temple�s possessions. Professor Jacobsen sees writing as a means for overcoming space, time, and the -increasing complications of human affairs: �Literacy, which began +increasing complications of human affairs: �Literacy, which began with ... civilization, enhanced mightily those very tendencies in its development which characterize it as a civilization and mark it off as -such from other types of culture.� +such from other types of culture.� [Illustration: RELIEF ON A PROTO-LITERATE STONE VASE, WARKA @@ -5098,7 +5098,7 @@ civilized way of life. I suppose you could say that the difference in the approach is that as a prehistorian I have been looking forward or upward in time, while the -historians look backward to glimpse what I�ve been describing here. My +historians look backward to glimpse what I�ve been describing here. My base-line was half a million years ago with a being who had little more than the capacity to make tools and fire to distinguish him from the animals about him. Thus my point of view and that of the conventional @@ -5114,17 +5114,17 @@ End of PREHISTORY [Illustration] -You�ll doubtless easily recall your general course in ancient history: +You�ll doubtless easily recall your general course in ancient history: how the Sumerian dynasties of Mesopotamia were supplanted by those of Babylonia, how the Hittite kingdom appeared in Anatolian Turkey, and about the three great phases of Egyptian history. The literate kingdom of Crete arose, and by 1500 B.C. there were splendid fortified Mycenean towns on the mainland of Greece. This was the time--about the whole eastern end of the Mediterranean--of what Professor Breasted called the -�first great internationalism,� with flourishing trade, international +�first great internationalism,� with flourishing trade, international treaties, and royal marriages between Egyptians, Babylonians, and -Hittites. By 1200 B.C., the whole thing had fragmented: �the peoples of -the sea were restless in their isles,� and the great ancient centers in +Hittites. By 1200 B.C., the whole thing had fragmented: �the peoples of +the sea were restless in their isles,� and the great ancient centers in Egypt, Mesopotamia, and Anatolia were eclipsed. Numerous smaller states arose--Assyria, Phoenicia, Israel--and the Trojan war was fought. Finally Assyria became the paramount power of all the Near East, @@ -5135,7 +5135,7 @@ but casting them with its own tradition into a new mould, arose in mainland Greece. I once shocked my Classical colleagues to the core by referring to -Greece as �a second degree derived civilization,� but there is much +Greece as �a second degree derived civilization,� but there is much truth in this. The principles of bronze- and then of iron-working, of the alphabet, and of many other elements in Greek culture were borrowed from western Asia. Our debt to the Greeks is too well known for me even @@ -5146,7 +5146,7 @@ Greece fell in its turn to Rome, and in 55 B.C. Caesar invaded Britain. I last spoke of Britain on page 142; I had chosen it as my single example for telling you something of how the earliest farming communities were established in Europe. Now I will continue with -Britain�s later prehistory, so you may sense something of the end of +Britain�s later prehistory, so you may sense something of the end of prehistory itself. Remember that Britain is simply a single example we select; the same thing could be done for all the other countries of Europe, and will be possible also, some day, for further Asia and @@ -5186,20 +5186,20 @@ few Battle-axe folk elements, including, in fact, stone battle-axes, reached England with the earliest Beaker folk,[6] coming from the Rhineland. - [6] The British authors use the term �Beaker folk� to mean both + [6] The British authors use the term �Beaker folk� to mean both archeological assemblage and human physical type. They speak - of a �... tall, heavy-boned, rugged, and round-headed� strain + of a �... tall, heavy-boned, rugged, and round-headed� strain which they take to have developed, apparently in the Rhineland, by a mixture of the original (Spanish?) beaker-makers and the northeast European battle-axe makers. However, since the science of physical anthropology is very much in flux at the moment, and since I am not able to assess the evidence for these - physical types, I _do not_ use the term �folk� in this book with + physical types, I _do not_ use the term �folk� in this book with its usual meaning of standardized physical type. When I use - �folk� here, I mean simply _the makers of a given archeological + �folk� here, I mean simply _the makers of a given archeological assemblage_. The difficulty only comes when assemblages are named for some item in them; it is too clumsy to make an - adjective of the item and refer to a �beakerian� assemblage. + adjective of the item and refer to a �beakerian� assemblage. The Beaker folk settled earliest in the agriculturally fertile south and east. There seem to have been several phases of Beaker folk @@ -5211,7 +5211,7 @@ folk are known. They buried their dead singly, sometimes in conspicuous individual barrows with the dead warrior in his full trappings. The spectacular element in the assemblage of the Beaker folk is a group of large circular monuments with ditches and with uprights of wood or -stone. These �henges� became truly monumental several hundred years +stone. These �henges� became truly monumental several hundred years later; while they were occasionally dedicated with a burial, they were not primarily tombs. The effect of the invasion of the Beaker folk seems to cut across the whole fabric of life in Britain. @@ -5221,7 +5221,7 @@ seems to cut across the whole fabric of life in Britain. There was, however, a second major element in British life at this time. It shows itself in the less well understood traces of a group again called after one of the items in their catalogue, the Food-vessel -folk. There are many burials in these �food-vessel� pots in northern +folk. There are many burials in these �food-vessel� pots in northern England, Scotland, and Ireland, and the pottery itself seems to link back to that of the Peterborough assemblage. Like the earlier Peterborough people in the highland zone before them, the makers of @@ -5238,8 +5238,8 @@ MORE INVASIONS About 1500 B.C., the situation became further complicated by the arrival of new people in the region of southern England anciently called Wessex. The traces suggest the Brittany coast of France as a -source, and the people seem at first to have been a small but �heroic� -group of aristocrats. Their �heroes� are buried with wealth and +source, and the people seem at first to have been a small but �heroic� +group of aristocrats. Their �heroes� are buried with wealth and ceremony, surrounded by their axes and daggers of bronze, their gold ornaments, and amber and jet beads. These rich finds show that the trade-linkage these warriors patronized spread from the Baltic sources @@ -5265,10 +5265,10 @@ which must have been necessary before such a great monument could have been built. -�THIS ENGLAND� +�THIS ENGLAND� The range from 1900 to about 1400 B.C. includes the time of development -of the archeological features usually called the �Early Bronze Age� +of the archeological features usually called the �Early Bronze Age� in Britain. In fact, traces of the Wessex warriors persisted down to about 1200 B.C. The main regions of the island were populated, and the adjustments to the highland and lowland zones were distinct and well @@ -5279,7 +5279,7 @@ trading role, separated from the European continent but conveniently adjacent to it. The tin of Cornwall--so important in the production of good bronze--as well as the copper of the west and of Ireland, taken with the gold of Ireland and the general excellence of Irish -metal work, assured Britain a trader�s place in the then known world. +metal work, assured Britain a trader�s place in the then known world. Contacts with the eastern Mediterranean may have been by sea, with Cornish tin as the attraction, or may have been made by the Food-vessel middlemen on their trips to the Baltic coast. There they would have @@ -5292,9 +5292,9 @@ relative isolation gave some peace and also gave time for a leveling and further fusion of culture. The separate cultural traditions began to have more in common. The growing of barley, the herding of sheep and cattle, and the production of woolen garments were already features -common to all Britain�s inhabitants save a few in the remote highlands, +common to all Britain�s inhabitants save a few in the remote highlands, the far north, and the distant islands not yet fully touched by -food-production. The �personality of Britain� was being formed. +food-production. The �personality of Britain� was being formed. CREMATION BURIALS BEGIN @@ -5325,9 +5325,9 @@ which we shall mention below. The British cremation-burial-in-urns folk survived a long time in the highland zone. In the general British scheme, they make up what is -called the �Middle Bronze Age,� but in the highland zone they last +called the �Middle Bronze Age,� but in the highland zone they last until after 900 B.C. and are considered to be a specialized highland -�Late Bronze Age.� In the highland zone, these later cremation-burial +�Late Bronze Age.� In the highland zone, these later cremation-burial folk seem to have continued the older Food-vessel tradition of being middlemen in the metal market. @@ -5379,12 +5379,12 @@ to get a picture of estate or tribal boundaries which included village communities; we find a variety of tools in bronze, and even whetstones which show that iron has been honed on them (although the scarce iron has not been found). Let me give you the picture in Professor S. -Piggott�s words: �The ... Late Bronze Age of southern England was but +Piggott�s words: �The ... Late Bronze Age of southern England was but the forerunner of the earliest Iron Age in the same region, not only in the techniques of agriculture, but almost certainly in terms of ethnic kinship ... we can with some assurance talk of the Celts ... the great early Celtic expansion of the Continent is recognized to be that of the -Urnfield people.� +Urnfield people.� Thus, certainly by 500 B.C., there were people in Britain, some of whose descendants we may recognize today in name or language in remote @@ -5399,11 +5399,11 @@ efficient set of tools than does bronze. Iron tools seem first to have been made in quantity in Hittite Anatolia about 1500 B.C. In continental Europe, the earliest, so-called Hallstatt, iron-using cultures appeared in Germany soon after 750 B.C. Somewhat later, -Greek and especially Etruscan exports of _objets d�art_--which moved +Greek and especially Etruscan exports of _objets d�art_--which moved with a flourishing trans-Alpine wine trade--influenced the Hallstatt iron-working tradition. Still later new classical motifs, together with older Hallstatt, oriental, and northern nomad motifs, gave rise to a -new style in metal decoration which characterizes the so-called La T�ne +new style in metal decoration which characterizes the so-called La T�ne phase. A few iron users reached Britain a little before 400 B.C. Not long @@ -5422,7 +5422,7 @@ HILL-FORTS AND FARMS The earliest iron-users seem to have entrenched themselves temporarily within hill-top forts, mainly in the south. Gradually, they moved inland, establishing _individual_ farm sites with extensive systems -of rectangular fields. We recognize these fields by the �lynchets� or +of rectangular fields. We recognize these fields by the �lynchets� or lines of soil-creep which plowing left on the slopes of hills. New crops appeared; there were now bread wheat, oats, and rye, as well as barley. @@ -5434,7 +5434,7 @@ various outbuildings and pits for the storage of grain. Weaving was done on the farm, but not blacksmithing, which must have been a specialized trade. Save for the lack of firearms, the place might almost be taken for a farmstead on the American frontier in the early -1800�s. +1800�s. Toward 250 B.C. there seems to have been a hasty attempt to repair the hill-forts and to build new ones, evidently in response to signs of @@ -5446,9 +5446,9 @@ THE SECOND PHASE Perhaps the hill-forts were not entirely effective or perhaps a compromise was reached. In any case, the newcomers from the Marne district did establish themselves, first in the southeast and then to -the north and west. They brought iron with decoration of the La T�ne +the north and west. They brought iron with decoration of the La T�ne type and also the two-wheeled chariot. Like the Wessex warriors of -over a thousand years earlier, they made �heroes�� graves, with their +over a thousand years earlier, they made �heroes�� graves, with their warriors buried in the war-chariots and dressed in full trappings. [Illustration: CELTIC BUCKLE] @@ -5457,7 +5457,7 @@ The metal work of these Marnian newcomers is excellent. The peculiar Celtic art style, based originally on the classic tendril motif, is colorful and virile, and fits with Greek and Roman descriptions of Celtic love of color in dress. There is a strong trace of these -newcomers northward in Yorkshire, linked by Ptolemy�s description to +newcomers northward in Yorkshire, linked by Ptolemy�s description to the Parisii, doubtless part of the Celtic tribe which originally gave its name to Paris on the Seine. Near Glastonbury, in Somerset, two villages in swamps have been excavated. They seem to date toward the @@ -5469,7 +5469,7 @@ villagers. In Scotland, which yields its first iron tools at a date of about 100 B.C., and in northern Ireland even slightly earlier, the effects of the -two phases of newcomers tend especially to blend. Hill-forts, �brochs� +two phases of newcomers tend especially to blend. Hill-forts, �brochs� (stone-built round towers) and a variety of other strange structures seem to appear as the new ideas develop in the comparative isolation of northern Britain. @@ -5493,27 +5493,27 @@ at last, we can even begin to speak of dynasties and individuals. Some time before 55 B.C., the Catuvellauni, originally from the Marne district in France, had possessed themselves of a large part of southeastern England. They evidently sailed up the Thames and built a -town of over a hundred acres in area. Here ruled Cassivellaunus, �the -first man in England whose name we know,� and whose town Caesar sacked. +town of over a hundred acres in area. Here ruled Cassivellaunus, �the +first man in England whose name we know,� and whose town Caesar sacked. The town sprang up elsewhere again, however. THE END OF PREHISTORY Prehistory, strictly speaking, is now over in southern Britain. -Claudius� effective invasion took place in 43 A.D.; by 83 A.D., a raid +Claudius� effective invasion took place in 43 A.D.; by 83 A.D., a raid had been made as far north as Aberdeen in Scotland. But by 127 A.D., Hadrian had completed his wall from the Solway to the Tyne, and the Romans settled behind it. In Scotland, Romanization can have affected -the countryside very little. Professor Piggott adds that �... it is +the countryside very little. Professor Piggott adds that �... it is when the pressure of Romanization is relaxed by the break-up of the Dark Ages that we see again the Celtic metal-smiths handling their material with the same consummate skill as they had before the Roman Conquest, and with traditional styles that had not even then forgotten -their Marnian and Belgic heritage.� +their Marnian and Belgic heritage.� In fact, many centuries go by, in Britain as well as in the rest of -Europe, before the archeologist�s task is complete and the historian on +Europe, before the archeologist�s task is complete and the historian on his own is able to describe the ways of men in the past. @@ -5524,7 +5524,7 @@ you will have noticed how often I had to refer to the European continent itself. Britain, beyond the English Channel for all of her later prehistory, had a much simpler course of events than did most of the rest of Europe in later prehistoric times. This holds, in spite -of all the �invasions� and �reverberations� from the continent. Most +of all the �invasions� and �reverberations� from the continent. Most of Europe was the scene of an even more complicated ebb and flow of cultural change, save in some of its more remote mountain valleys and peninsulas. @@ -5536,7 +5536,7 @@ accounts and some good general accounts of part of the range from about 3000 B.C. to A.D. 1. I suspect that the difficulty of making a good book that covers all of its later prehistory is another aspect of what makes Europe so very complicated a continent today. The prehistoric -foundations for Europe�s very complicated set of civilizations, +foundations for Europe�s very complicated set of civilizations, cultures, and sub-cultures--which begin to appear as history proceeds--were in themselves very complicated. @@ -5552,8 +5552,8 @@ of their journeys. But by the same token, they had had time en route to take on their characteristic European aspects. Some time ago, Sir Cyril Fox wrote a famous book called _The -Personality of Britain_, sub-titled �Its Influence on Inhabitant and -Invader in Prehistoric and Early Historic Times.� We have not gone +Personality of Britain_, sub-titled �Its Influence on Inhabitant and +Invader in Prehistoric and Early Historic Times.� We have not gone into the post-Roman early historic period here; there are still the Anglo-Saxons and Normans to account for as well as the effects of the Romans. But what I have tried to do was to begin the story of @@ -5570,7 +5570,7 @@ Summary In the pages you have read so far, you have been brought through the -earliest 99 per cent of the story of man�s life on this planet. I have +earliest 99 per cent of the story of man�s life on this planet. I have left only 1 per cent of the story for the historians to tell. @@ -5601,7 +5601,7 @@ But I think there may have been a few. Certainly the pace of the first act accelerated with the swing from simple gathering to more intensified collecting. The great cave art of France and Spain was probably an expression of a climax. Even the ideas of burying the dead -and of the �Venus� figurines must also point to levels of human thought +and of the �Venus� figurines must also point to levels of human thought and activity that were over and above pure food-getting. @@ -5629,7 +5629,7 @@ five thousand years after the second act began. But it could never have happened in the first act at all. There is another curious thing about the first act. Many of the players -didn�t know it was over and they kept on with their roles long after +didn�t know it was over and they kept on with their roles long after the second act had begun. On the edges of the stage there are today some players who are still going on with the first act. The Eskimos, and the native Australians, and certain tribes in the Amazon jungle are @@ -5680,20 +5680,20 @@ act may have lessons for us and give depth to our thinking. I know there are at least _some_ lessons, even in the present incomplete state of our knowledge. The players who began the second act--that of food-production--separately, in different parts of the world, were not -all of one �pure race� nor did they have �pure� cultural traditions. +all of one �pure race� nor did they have �pure� cultural traditions. Some apparently quite mixed Mediterraneans got off to the first start on the second act and brought it to its first two climaxes as well. Peoples of quite different physical type achieved the first climaxes in China and in the New World. In our British example of how the late prehistory of Europe worked, we -listed a continuous series of �invasions� and �reverberations.� After +listed a continuous series of �invasions� and �reverberations.� After each of these came fusion. Even though the Channel protected Britain from some of the extreme complications of the mixture and fusion of continental Europe, you can see how silly it would be to refer to a -�pure� British race or a �pure� British culture. We speak of the United -States as a �melting pot.� But this is nothing new. Actually, Britain -and all the rest of the world have been �melting pots� at one time or +�pure� British race or a �pure� British culture. We speak of the United +States as a �melting pot.� But this is nothing new. Actually, Britain +and all the rest of the world have been �melting pots� at one time or another. By the time the written records of Mesopotamia and Egypt begin to turn @@ -5703,12 +5703,12 @@ itself, we are thrown back on prehistoric archeology. And this is as true for China, India, Middle America, and the Andes, as it is for the Near East. -There are lessons to be learned from all of man�s past, not simply +There are lessons to be learned from all of man�s past, not simply lessons of how to fight battles or win peace conferences, but of how human society evolves from one stage to another. Many of these lessons can only be looked for in the prehistoric past. So far, we have only made a beginning. There is much still to do, and many gaps in the story -are yet to be filled. The prehistorian�s job is to find the evidence, +are yet to be filled. The prehistorian�s job is to find the evidence, to fill the gaps, and to discover the lessons men have learned in the past. As I see it, this is not only an exciting but a very practical goal for which to strive. @@ -5745,7 +5745,7 @@ paperbound books.) GEOCHRONOLOGY AND THE ICE AGE -(Two general books. Some Pleistocene geologists disagree with Zeuner�s +(Two general books. Some Pleistocene geologists disagree with Zeuner�s interpretation of the dating evidence, but their points of view appear in professional journals, in articles too cumbersome to list here.) @@ -5815,7 +5815,7 @@ GENERAL PREHISTORY Press. Movius, Hallam L., Jr. - �Old World Prehistory: Paleolithic� in _Anthropology Today_. + �Old World Prehistory: Paleolithic� in _Anthropology Today_. Kroeber, A. L., ed. 1953. University of Chicago Press. Oakley, Kenneth P. @@ -5826,7 +5826,7 @@ GENERAL PREHISTORY _British Prehistory._ 1949. Oxford University Press. Pittioni, Richard - _Die Urgeschichtlichen Grundlagen der Europ�ischen Kultur._ + _Die Urgeschichtlichen Grundlagen der Europ�ischen Kultur._ 1949. Deuticke. (A single book which does attempt to cover the whole range of European prehistory to ca. 1 A.D.) @@ -5834,7 +5834,7 @@ GENERAL PREHISTORY THE NEAR EAST Adams, Robert M. - �Developmental Stages in Ancient Mesopotamia,� _in_ Steward, + �Developmental Stages in Ancient Mesopotamia,� _in_ Steward, Julian, _et al_, _Irrigation Civilizations: A Comparative Study_. 1955. Pan American Union. @@ -6000,7 +6000,7 @@ Index Bolas, 54 - Bordes, Fran�ois, 62 + Bordes, Fran�ois, 62 Borer, 77 @@ -6028,7 +6028,7 @@ Index killed by stampede, 86 Burials, 66, 86; - in �henges,� 164; + in �henges,� 164; in urns, 168 Burins, 75 @@ -6085,7 +6085,7 @@ Index Combe Capelle, 30 - Combe Capelle-Br�nn group, 34 + Combe Capelle-Br�nn group, 34 Commont, Victor, 51 @@ -6097,7 +6097,7 @@ Index Corrals for cattle, 140 - �Cradle of mankind,� 136 + �Cradle of mankind,� 136 Cremation, 167 @@ -6123,7 +6123,7 @@ Index Domestication, of animals, 100, 105, 107; of plants, 100 - �Dragon teeth� fossils in China, 28 + �Dragon teeth� fossils in China, 28 Drill, 77 @@ -6176,9 +6176,9 @@ Index Fayum, 135; radiocarbon date, 146 - �Fertile Crescent,� 107, 146 + �Fertile Crescent,� 107, 146 - Figurines, �Venus,� 84; + Figurines, �Venus,� 84; at Jarmo, 128; at Ubaid, 153 @@ -6197,7 +6197,7 @@ Index Flint industry, 127 - Font�chevade, 32, 56, 58 + Font�chevade, 32, 56, 58 Food-collecting, 104, 121; end of, 104 @@ -6223,7 +6223,7 @@ Index Food-vessel folk, 164 - �Forest folk,� 97, 98, 104, 110 + �Forest folk,� 97, 98, 104, 110 Fox, Sir Cyril, 174 @@ -6379,7 +6379,7 @@ Index Land bridges in Mediterranean, 19 - La T�ne phase, 170 + La T�ne phase, 170 Laurel leaf point, 78, 89 @@ -6404,7 +6404,7 @@ Index Mammoth, 93; in cave art, 85 - �Man-apes,� 26 + �Man-apes,� 26 Mango, 107 @@ -6435,7 +6435,7 @@ Index Microliths, 87; at Jarmo, 130; - �lunates,� 87; + �lunates,� 87; trapezoids, 87; triangles, 87 @@ -6443,7 +6443,7 @@ Index Mine-shafts, 140 - M�lefaat, 126, 127 + M�lefaat, 126, 127 Mongoloids, 29, 90 @@ -6453,9 +6453,9 @@ Index Mount Carmel, 11, 33, 52, 59, 64, 69, 113, 114 - �Mousterian man,� 64 + �Mousterian man,� 64 - �Mousterian� tools, 61, 62; + �Mousterian� tools, 61, 62; of Acheulean tradition, 62 Movius, H. L., 47 @@ -6471,7 +6471,7 @@ Index Near East, beginnings of civilization in, 20, 144; cave sites, 58; climate in Ice Age, 99; - �Fertile Crescent,� 107, 146; + �Fertile Crescent,� 107, 146; food-production in, 99; Natufian assemblage in, 113-115; stone tools, 114 @@ -6539,7 +6539,7 @@ Index Pig, wild, 108 - �Piltdown man,� 29 + �Piltdown man,� 29 Pins, 80 @@ -6578,7 +6578,7 @@ Index Race, 35; biological, 36; - �pure,� 16 + �pure,� 16 Radioactivity, 9, 10 @@ -6795,7 +6795,7 @@ Index Writing, 158; cuneiform, 158 - W�rm I glaciation, 58 + W�rm I glaciation, 58 Zebu cattle, domestication of, 107 @@ -6810,7 +6810,7 @@ Index -Transcriber�s note: +Transcriber�s note: Punctuation, hyphenation, and spelling were made consistent when a predominant preference was found in this book; otherwise they were not diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index 6296b1e6d709..b9630243d7f3 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -6,8 +6,8 @@ def main() -> None: - input_file = "Prehistoric Men.txt" - output_file = "Output.txt" + input_file = "./prehistoric_men.txt" + output_file = "./Output.txt" key = int(input("Enter key: ")) mode = input("Encrypt/Decrypt [e/d]: ") From 24dbdd0b88bdfd4ddb940cf0b681075c66842cc3 Mon Sep 17 00:00:00 2001 From: Raghav <83136390+Raghav-Bell@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:38:13 +0530 Subject: [PATCH 2358/2908] Update coulombs_law.py docs (#9667) * Update coulombs_law.py distance is positive non zero real number (float type) hence corrected docs which says only integer. * Update physics/coulombs_law.py --------- Co-authored-by: Tianyi Zheng --- physics/coulombs_law.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physics/coulombs_law.py b/physics/coulombs_law.py index 252e8ec0f74e..fe2d358f653e 100644 --- a/physics/coulombs_law.py +++ b/physics/coulombs_law.py @@ -32,7 +32,7 @@ def coulombs_law(q1: float, q2: float, radius: float) -> float: 17975103584.6 """ if radius <= 0: - raise ValueError("The radius is always a positive non zero integer") + raise ValueError("The radius is always a positive number") return round(((8.9875517923 * 10**9) * q1 * q2) / (radius**2), 2) From 3fd3497f15982a7286326b520b5e7b52767da1f3 Mon Sep 17 00:00:00 2001 From: Siddhant Totade Date: Wed, 4 Oct 2023 14:55:26 +0530 Subject: [PATCH 2359/2908] Add Comments (#9668) * docs : add comment in circular_linked_list.py and swap_nodes.py * docs : improve comments * docs : improved docs and tested on pre-commit * docs : add comment in circular_linked_list.py and swap_nodes.py * docs : improve comments * docs : improved docs and tested on pre-commit * docs : modified comments * Update circular_linked_list.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docs : improved * Update data_structures/linked_list/circular_linked_list.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/circular_linked_list.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/swap_nodes.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/swap_nodes.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/swap_nodes.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/swap_nodes.py Co-authored-by: Christian Clauss * Update requirements.txt Co-authored-by: Christian Clauss * Update data_structures/linked_list/circular_linked_list.py Co-authored-by: Christian Clauss * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update circular_linked_list.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../linked_list/circular_linked_list.py | 87 ++++++++++++++++--- data_structures/linked_list/swap_nodes.py | 47 ++++++++-- 2 files changed, 113 insertions(+), 21 deletions(-) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index d9544f4263a6..72212f46be15 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -6,16 +6,29 @@ class Node: def __init__(self, data: Any): + """ + Initialize a new Node with the given data. + Args: + data: The data to be stored in the node. + """ self.data: Any = data - self.next: Node | None = None + self.next: Node | None = None # Reference to the next node class CircularLinkedList: - def __init__(self): - self.head = None - self.tail = None + def __init__(self) -> None: + """ + Initialize an empty Circular Linked List. + """ + self.head = None # Reference to the head (first node) + self.tail = None # Reference to the tail (last node) def __iter__(self) -> Iterator[Any]: + """ + Iterate through all nodes in the Circular Linked List yielding their data. + Yields: + The data of each node in the linked list. + """ node = self.head while self.head: yield node.data @@ -24,25 +37,48 @@ def __iter__(self) -> Iterator[Any]: break def __len__(self) -> int: + """ + Get the length (number of nodes) in the Circular Linked List. + """ return sum(1 for _ in self) - def __repr__(self): + def __repr__(self) -> str: + """ + Generate a string representation of the Circular Linked List. + Returns: + A string of the format "1->2->....->N". + """ return "->".join(str(item) for item in iter(self)) def insert_tail(self, data: Any) -> None: + """ + Insert a node with the given data at the end of the Circular Linked List. + """ self.insert_nth(len(self), data) def insert_head(self, data: Any) -> None: + """ + Insert a node with the given data at the beginning of the Circular Linked List. + """ self.insert_nth(0, data) def insert_nth(self, index: int, data: Any) -> None: + """ + Insert the data of the node at the nth pos in the Circular Linked List. + Args: + index: The index at which the data should be inserted. + data: The data to be inserted. + + Raises: + IndexError: If the index is out of range. + """ if index < 0 or index > len(self): raise IndexError("list index out of range.") new_node = Node(data) if self.head is None: - new_node.next = new_node # first node points itself + new_node.next = new_node # First node points to itself self.tail = self.head = new_node - elif index == 0: # insert at head + elif index == 0: # Insert at the head new_node.next = self.head self.head = self.tail.next = new_node else: @@ -51,22 +87,43 @@ def insert_nth(self, index: int, data: Any) -> None: temp = temp.next new_node.next = temp.next temp.next = new_node - if index == len(self) - 1: # insert at tail + if index == len(self) - 1: # Insert at the tail self.tail = new_node - def delete_front(self): + def delete_front(self) -> Any: + """ + Delete and return the data of the node at the front of the Circular Linked List. + Raises: + IndexError: If the list is empty. + """ return self.delete_nth(0) def delete_tail(self) -> Any: + """ + Delete and return the data of the node at the end of the Circular Linked List. + Returns: + Any: The data of the deleted node. + Raises: + IndexError: If the index is out of range. + """ return self.delete_nth(len(self) - 1) def delete_nth(self, index: int = 0) -> Any: + """ + Delete and return the data of the node at the nth pos in Circular Linked List. + Args: + index (int): The index of the node to be deleted. Defaults to 0. + Returns: + Any: The data of the deleted node. + Raises: + IndexError: If the index is out of range. + """ 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 + if self.head == self.tail: # Just one node self.head = self.tail = None - elif index == 0: # delete head node + elif index == 0: # Delete head node self.tail.next = self.tail.next.next self.head = self.head.next else: @@ -75,16 +132,22 @@ def delete_nth(self, index: int = 0) -> Any: temp = temp.next delete_node = temp.next temp.next = temp.next.next - if index == len(self) - 1: # delete at tail + if index == len(self) - 1: # Delete at tail self.tail = temp return delete_node.data def is_empty(self) -> bool: + """ + Check if the Circular Linked List is empty. + Returns: + bool: True if the list is empty, False otherwise. + """ return len(self) == 0 def test_circular_linked_list() -> None: """ + Test cases for the CircularLinkedList class. >>> test_circular_linked_list() """ circular_linked_list = CircularLinkedList() diff --git a/data_structures/linked_list/swap_nodes.py b/data_structures/linked_list/swap_nodes.py index 3f825756b3d2..da6aa07a79fd 100644 --- a/data_structures/linked_list/swap_nodes.py +++ b/data_structures/linked_list/swap_nodes.py @@ -2,30 +2,56 @@ class Node: - def __init__(self, data: Any): + def __init__(self, data: Any) -> None: + """ + Initialize a new Node with the given data. + + Args: + data: The data to be stored in the node. + + """ self.data = data - self.next = None + self.next = None # Reference to the next node class LinkedList: - def __init__(self): - self.head = None + def __init__(self) -> None: + """ + Initialize an empty Linked List. + """ + self.head = None # Reference to the head (first node) def print_list(self): + """ + Print the elements of the Linked List in order. + """ temp = self.head while temp is not None: print(temp.data, end=" ") temp = temp.next print() - # adding nodes - def push(self, new_data: Any): + def push(self, new_data: Any) -> None: + """ + Add a new node with the given data to the beginning of the Linked List. + Args: + new_data (Any): The data to be added to the new node. + """ new_node = Node(new_data) new_node.next = self.head self.head = new_node - # swapping nodes - def swap_nodes(self, node_data_1, node_data_2): + def swap_nodes(self, node_data_1, node_data_2) -> None: + """ + Swap the positions of two nodes in the Linked List based on their data values. + Args: + node_data_1: Data value of the first node to be swapped. + node_data_2: Data value of the second node to be swapped. + + + Note: + If either of the specified data values isn't found then, no swapping occurs. + """ if node_data_1 == node_data_2: return else: @@ -40,6 +66,7 @@ def swap_nodes(self, node_data_1, node_data_2): if node_1 is None or node_2 is None: return + # Swap the data values of the two nodes node_1.data, node_2.data = node_2.data, node_1.data @@ -48,8 +75,10 @@ def swap_nodes(self, node_data_1, node_data_2): for i in range(5, 0, -1): ll.push(i) + print("Original Linked List:") ll.print_list() ll.swap_nodes(1, 4) - print("After swapping") + print("After swapping the nodes whose data is 1 and 4:") + ll.print_list() From dfdd78135df938d948ba3044aca628aca08886e7 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 4 Oct 2023 12:05:00 -0400 Subject: [PATCH 2360/2908] Fix mypy errors in circular_linked_list.py and swap_nodes.py (#9707) * updating DIRECTORY.md * Fix mypy errors in circular_linked_list.py * Fix mypy errors in swap_nodes.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 ++--- .../linked_list/circular_linked_list.py | 22 +++++++++++++------ data_structures/linked_list/swap_nodes.py | 4 ++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 9a913aa786e1..4f4cc423d678 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -541,8 +541,8 @@ * [Basic Maths](maths/basic_maths.py) * [Binary Exp Mod](maths/binary_exp_mod.py) * [Binary Exponentiation](maths/binary_exponentiation.py) - * [Binary Exponentiation 2](maths/binary_exponentiation_2.py) * [Binary Exponentiation 3](maths/binary_exponentiation_3.py) + * [Binary Multiplication](maths/binary_multiplication.py) * [Binomial Coefficient](maths/binomial_coefficient.py) * [Binomial Distribution](maths/binomial_distribution.py) * [Bisection](maths/bisection.py) @@ -557,8 +557,7 @@ * [Decimal Isolate](maths/decimal_isolate.py) * [Decimal To Fraction](maths/decimal_to_fraction.py) * [Dodecahedron](maths/dodecahedron.py) - * [Double Factorial Iterative](maths/double_factorial_iterative.py) - * [Double Factorial Recursive](maths/double_factorial_recursive.py) + * [Double Factorial](maths/double_factorial.py) * [Dual Number Automatic Differentiation](maths/dual_number_automatic_differentiation.py) * [Entropy](maths/entropy.py) * [Euclidean Distance](maths/euclidean_distance.py) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 72212f46be15..ef6658733a95 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -20,8 +20,8 @@ def __init__(self) -> None: """ Initialize an empty Circular Linked List. """ - self.head = None # Reference to the head (first node) - self.tail = None # Reference to the tail (last node) + self.head: Node | None = None # Reference to the head (first node) + self.tail: Node | None = None # Reference to the tail (last node) def __iter__(self) -> Iterator[Any]: """ @@ -30,7 +30,7 @@ def __iter__(self) -> Iterator[Any]: The data of each node in the linked list. """ node = self.head - while self.head: + while node: yield node.data node = node.next if node == self.head: @@ -74,17 +74,20 @@ 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: Node = Node(data) if self.head is None: new_node.next = new_node # First node points to itself self.tail = self.head = new_node elif index == 0: # Insert at the head new_node.next = self.head + assert self.tail is not None # List is not empty, tail exists self.head = self.tail.next = new_node else: - temp = self.head + temp: Node | None = self.head for _ in range(index - 1): + assert temp is not None temp = temp.next + assert temp is not None new_node.next = temp.next temp.next = new_node if index == len(self) - 1: # Insert at the tail @@ -120,16 +123,21 @@ def delete_nth(self, index: int = 0) -> Any: """ if not 0 <= index < len(self): raise IndexError("list index out of range.") - delete_node = self.head + + assert self.head is not None and self.tail is not None + delete_node: Node = self.head if self.head == self.tail: # Just one node self.head = self.tail = None elif index == 0: # Delete head node + assert self.tail.next is not None self.tail.next = self.tail.next.next self.head = self.head.next else: - temp = self.head + temp: Node | None = self.head for _ in range(index - 1): + assert temp is not None temp = temp.next + assert temp is not None and temp.next is not None delete_node = temp.next temp.next = temp.next.next if index == len(self) - 1: # Delete at tail diff --git a/data_structures/linked_list/swap_nodes.py b/data_structures/linked_list/swap_nodes.py index da6aa07a79fd..31dcb02bfa9a 100644 --- a/data_structures/linked_list/swap_nodes.py +++ b/data_structures/linked_list/swap_nodes.py @@ -11,7 +11,7 @@ def __init__(self, data: Any) -> None: """ self.data = data - self.next = None # Reference to the next node + self.next: Node | None = None # Reference to the next node class LinkedList: @@ -19,7 +19,7 @@ def __init__(self) -> None: """ Initialize an empty Linked List. """ - self.head = None # Reference to the head (first node) + self.head: Node | None = None # Reference to the head (first node) def print_list(self): """ From d74349793b613b0948608409a572426a9800c3a1 Mon Sep 17 00:00:00 2001 From: halfhearted <99018821+Arunsiva003@users.noreply.github.com> Date: Wed, 4 Oct 2023 22:09:28 +0530 Subject: [PATCH 2361/2908] Arunsiva003 patch 1 flatten tree (#9695) * infix to prefix missing feature added * infix to prefix missing feature added * infix to prefix missing feature added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * infix to prefix missing feature added (comments) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * infix to prefix missing feature added (comments) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * newly updated infix_to_prefix * newly updated infix_to_prefix_2 * newly updated infix_to_prefix_3 * from the beginning * Created flatten_binarytree_to_linkedlist.py * Update flatten_binarytree_to_linkedlist.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update flatten_binarytree_to_linkedlist.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update flatten_binarytree_to_linkedlist.py * Update flatten_binarytree_to_linkedlist.py * Update flatten_binarytree_to_linkedlist.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update flatten_binarytree_to_linkedlist.py (space added) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update flatten_binarytree_to_linkedlist.py space added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update flatten_binarytree_to_linkedlist.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * flatten binary tree to linked list - 1 * flatten binary tree to linked list final * flatten binary tree to linked list final * review updated * Update flatten_binarytree_to_linkedlist.py * Update .pre-commit-config.yaml * Update flatten_binarytree_to_linkedlist.py * Update flatten_binarytree_to_linkedlist.py --------- Co-authored-by: ArunSiva Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../flatten_binarytree_to_linkedlist.py | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 data_structures/binary_tree/flatten_binarytree_to_linkedlist.py diff --git a/data_structures/binary_tree/flatten_binarytree_to_linkedlist.py b/data_structures/binary_tree/flatten_binarytree_to_linkedlist.py new file mode 100644 index 000000000000..8820a509ecba --- /dev/null +++ b/data_structures/binary_tree/flatten_binarytree_to_linkedlist.py @@ -0,0 +1,138 @@ +""" +Binary Tree Flattening Algorithm + +This code defines an algorithm to flatten a binary tree into a linked list +represented using the right pointers of the tree nodes. It uses in-place +flattening and demonstrates the flattening process along with a display +function to visualize the flattened linked list. +https://www.geeksforgeeks.org/flatten-a-binary-tree-into-linked-list + +Author: Arunkumar A +Date: 04/09/2023 +""" +from __future__ import annotations + + +class TreeNode: + """ + A TreeNode has data variable and pointers to TreeNode objects + for its left and right children. + """ + + def __init__(self, data: int) -> None: + self.data = data + self.left: TreeNode | None = None + self.right: TreeNode | None = None + + +def build_tree() -> TreeNode: + """ + Build and return a sample binary tree. + + Returns: + TreeNode: The root of the binary tree. + + Examples: + >>> root = build_tree() + >>> root.data + 1 + >>> root.left.data + 2 + >>> root.right.data + 5 + >>> root.left.left.data + 3 + >>> root.left.right.data + 4 + >>> root.right.right.data + 6 + """ + root = TreeNode(1) + root.left = TreeNode(2) + root.right = TreeNode(5) + root.left.left = TreeNode(3) + root.left.right = TreeNode(4) + root.right.right = TreeNode(6) + return root + + +def flatten(root: TreeNode | None) -> None: + """ + Flatten a binary tree into a linked list in-place, where the linked list is + represented using the right pointers of the tree nodes. + + Args: + root (TreeNode): The root of the binary tree to be flattened. + + Examples: + >>> root = TreeNode(1) + >>> root.left = TreeNode(2) + >>> root.right = TreeNode(5) + >>> root.left.left = TreeNode(3) + >>> root.left.right = TreeNode(4) + >>> root.right.right = TreeNode(6) + >>> flatten(root) + >>> root.data + 1 + >>> root.right.right is None + False + >>> root.right.right = TreeNode(3) + >>> root.right.right.right is None + True + """ + if not root: + return + + # Flatten the left subtree + flatten(root.left) + + # Save the right subtree + right_subtree = root.right + + # Make the left subtree the new right subtree + root.right = root.left + root.left = None + + # Find the end of the new right subtree + current = root + while current.right: + current = current.right + + # Append the original right subtree to the end + current.right = right_subtree + + # Flatten the updated right subtree + flatten(right_subtree) + + +def display_linked_list(root: TreeNode | None) -> None: + """ + Display the flattened linked list. + + Args: + root (TreeNode | None): The root of the flattened linked list. + + Examples: + >>> root = TreeNode(1) + >>> root.right = TreeNode(2) + >>> root.right.right = TreeNode(3) + >>> display_linked_list(root) + 1 2 3 + >>> root = None + >>> display_linked_list(root) + + """ + current = root + while current: + if current.right is None: + print(current.data, end="") + break + print(current.data, end=" ") + current = current.right + + +if __name__ == "__main__": + print("Flattened Linked List:") + root = build_tree() + flatten(root) + display_linked_list(root) From 922d6a88b3be2ff0dd69dd47d90e40aa95afd105 Mon Sep 17 00:00:00 2001 From: Bama Charan Chhandogi Date: Wed, 4 Oct 2023 22:51:46 +0530 Subject: [PATCH 2362/2908] add median of matrix (#9363) * add median of matrix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix formating * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- matrix/median_matrix.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 matrix/median_matrix.py diff --git a/matrix/median_matrix.py b/matrix/median_matrix.py new file mode 100644 index 000000000000..116e609a587c --- /dev/null +++ b/matrix/median_matrix.py @@ -0,0 +1,38 @@ +""" +https://en.wikipedia.org/wiki/Median +""" + + +def median(matrix: list[list[int]]) -> int: + """ + Calculate the median of a sorted matrix. + + Args: + matrix: A 2D matrix of integers. + + Returns: + The median value of the matrix. + + Examples: + >>> matrix = [[1, 3, 5], [2, 6, 9], [3, 6, 9]] + >>> median(matrix) + 5 + + >>> matrix = [[1, 2, 3], [4, 5, 6]] + >>> median(matrix) + 3 + """ + # Flatten the matrix into a sorted 1D list + linear = sorted(num for row in matrix for num in row) + + # Calculate the middle index + mid = (len(linear) - 1) // 2 + + # Return the median + return linear[mid] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d5806258d4f9eb0e5652e1edfac0613aacb71fb6 Mon Sep 17 00:00:00 2001 From: Bama Charan Chhandogi Date: Wed, 4 Oct 2023 23:48:59 +0530 Subject: [PATCH 2363/2908] add median of two sorted array (#9386) * add median of two sorted array * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix syntax * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix syntax * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * improve code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add documentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/arrays/median_two_array.py | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 data_structures/arrays/median_two_array.py diff --git a/data_structures/arrays/median_two_array.py b/data_structures/arrays/median_two_array.py new file mode 100644 index 000000000000..972b0ee44201 --- /dev/null +++ b/data_structures/arrays/median_two_array.py @@ -0,0 +1,61 @@ +""" +https://www.enjoyalgorithms.com/blog/median-of-two-sorted-arrays +""" + + +def find_median_sorted_arrays(nums1: list[int], nums2: list[int]) -> float: + """ + Find the median of two arrays. + + Args: + nums1: The first array. + nums2: The second array. + + Returns: + The median of the two arrays. + + Examples: + >>> find_median_sorted_arrays([1, 3], [2]) + 2.0 + + >>> find_median_sorted_arrays([1, 2], [3, 4]) + 2.5 + + >>> find_median_sorted_arrays([0, 0], [0, 0]) + 0.0 + + >>> find_median_sorted_arrays([], []) + Traceback (most recent call last): + ... + ValueError: Both input arrays are empty. + + >>> find_median_sorted_arrays([], [1]) + 1.0 + + >>> find_median_sorted_arrays([-1000], [1000]) + 0.0 + + >>> find_median_sorted_arrays([-1.1, -2.2], [-3.3, -4.4]) + -2.75 + """ + if not nums1 and not nums2: + raise ValueError("Both input arrays are empty.") + + # Merge the arrays into a single sorted array. + merged = sorted(nums1 + nums2) + total = len(merged) + + if total % 2 == 1: # If the total number of elements is odd + return float(merged[total // 2]) # then return the middle element + + # If the total number of elements is even, calculate + # the average of the two middle elements as the median. + middle1 = merged[total // 2 - 1] + middle2 = merged[total // 2] + return (float(middle1) + float(middle2)) / 2.0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c16d2f8865c8ce28ae6d4d815d3f6c3008e94f74 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Farooq <115654418+Muhammadummerr@users.noreply.github.com> Date: Wed, 4 Oct 2023 23:43:17 +0500 Subject: [PATCH 2364/2908] UPDATED rat_in_maze.py (#9148) * UPDATED rat_in_maze.py * Update reddit.py in Webprogramming b/c it was causing error in pre-commit tests while raising PR. * UPDATED rat_in_maze.py * fixed return type to only maze,otherwise raise valueError. * fixed whitespaces error,improved matrix visual. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated. * Try * updated * updated * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- backtracking/rat_in_maze.py | 181 ++++++++++++++++++++++++++---------- 1 file changed, 130 insertions(+), 51 deletions(-) diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index 7bde886dd558..626c83cb4a15 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -1,91 +1,164 @@ from __future__ import annotations -def solve_maze(maze: list[list[int]]) -> bool: +def solve_maze( + maze: list[list[int]], + source_row: int, + source_column: int, + destination_row: int, + destination_column: int, +) -> list[list[int]]: """ 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 + - maze: A two dimensional matrix of zeros and ones. + - source_row: The row index of the starting point. + - source_column: The column index of the starting point. + - destination_row: The row index of the destination point. + - destination_column: The column index of the destination point. Returns: - Return: True if the maze has a solution or False if it does not. + - solution: A 2D matrix representing the solution path if it exists. + Raises: + - ValueError: If no solution exists or if the source or + destination coordinates are invalid. + Description: + This method navigates through a maze represented as an n by n matrix, + starting from a specified source cell and + aiming to reach a destination cell. + The maze consists of walls (1s) and open paths (0s). + By providing custom row and column values, the source and destination + cells can be adjusted. >>> 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 + >>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE + [[0, 1, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 1, 1, 0, 1], + [1, 1, 1, 0, 0], + [1, 1, 1, 1, 0]] + + Note: + In the output maze, the zeros (0s) represent one of the possible + paths from the source to the destination. >>> 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 + >>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE + [[0, 1, 1, 1, 1], + [0, 1, 1, 1, 1], + [0, 1, 1, 1, 1], + [0, 1, 1, 1, 1], + [0, 0, 0, 0, 0]] >>> maze = [[0, 0, 0], ... [0, 1, 0], ... [1, 0, 0]] - >>> solve_maze(maze) - [1, 1, 1] - [0, 0, 1] - [0, 0, 1] - True + >>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE + [[0, 0, 0], + [1, 1, 0], + [1, 1, 0]] - >>> maze = [[0, 1, 0], + >>> maze = [[1, 0, 0], ... [0, 1, 0], ... [1, 0, 0]] - >>> solve_maze(maze) - No solution exists! - False + >>> solve_maze(maze,0,1,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE + [[1, 0, 0], + [1, 1, 0], + [1, 1, 0]] + + >>> maze = [[1, 1, 0, 0, 1, 0, 0, 1], + ... [1, 0, 1, 0, 0, 1, 1, 1], + ... [0, 1, 0, 1, 0, 0, 1, 0], + ... [1, 1, 1, 0, 0, 1, 0, 1], + ... [0, 1, 0, 0, 1, 0, 1, 1], + ... [0, 0, 0, 1, 1, 1, 0, 1], + ... [0, 1, 0, 1, 0, 1, 1, 1], + ... [1, 1, 0, 0, 0, 0, 0, 1]] + >>> solve_maze(maze,0,2,len(maze)-1,2) # doctest: +NORMALIZE_WHITESPACE + [[1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1]] + >>> maze = [[1, 0, 0], + ... [0, 1, 1], + ... [1, 0, 1]] + >>> solve_maze(maze,0,1,len(maze)-1,len(maze)-1) + Traceback (most recent call last): + ... + ValueError: No solution exists! + + >>> maze = [[0, 0], + ... [1, 1]] + >>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) + Traceback (most recent call last): + ... + ValueError: No solution exists! >>> maze = [[0, 1], ... [1, 0]] - >>> solve_maze(maze) - No solution exists! - False + >>> solve_maze(maze,2,0,len(maze)-1,len(maze)-1) + Traceback (most recent call last): + ... + ValueError: Invalid source or destination coordinates + + >>> maze = [[1, 0, 0], + ... [0, 1, 0], + ... [1, 0, 0]] + >>> solve_maze(maze,0,1,len(maze),len(maze)-1) + Traceback (most recent call last): + ... + ValueError: Invalid source or destination coordinates """ size = len(maze) + # Check if source and destination coordinates are Invalid. + if not (0 <= source_row <= size - 1 and 0 <= source_column <= size - 1) or ( + not (0 <= destination_row <= size - 1 and 0 <= destination_column <= size - 1) + ): + raise ValueError("Invalid source or destination coordinates") # 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) + solutions = [[1 for _ in range(size)] for _ in range(size)] + solved = run_maze( + maze, source_row, source_column, destination_row, destination_column, solutions + ) if solved: - print("\n".join(str(row) for row in solutions)) + return solutions else: - print("No solution exists!") - return solved + raise ValueError("No solution exists!") -def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]]) -> bool: +def run_maze( + maze: list[list[int]], + i: int, + j: int, + destination_row: int, + destination_column: 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. If a path is found to destination it returns True otherwise it returns False. - Parameters: - maze(2D matrix) : maze + Parameters + maze: A two dimensional matrix of zeros and ones. i, j : coordinates of matrix - solutions(2D matrix) : solutions + solutions: A two dimensional matrix of 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 + if i == destination_row and j == destination_column and maze[i][j] == 0: + solutions[i][j] = 0 return True lower_flag = (not i < 0) and (not j < 0) # Check lower bounds @@ -93,21 +166,27 @@ def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]]) if lower_flag and upper_flag: # check for already visited and block points. - block_flag = (not solutions[i][j]) and (not maze[i][j]) + block_flag = (solutions[i][j]) and (not maze[i][j]) if block_flag: # check visited - solutions[i][j] = 1 + solutions[i][j] = 0 # 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) + run_maze(maze, i + 1, j, destination_row, destination_column, solutions) + or run_maze( + maze, i, j + 1, destination_row, destination_column, solutions + ) + or run_maze( + maze, i - 1, j, destination_row, destination_column, solutions + ) + or run_maze( + maze, i, j - 1, destination_row, destination_column, solutions + ) ): return True - solutions[i][j] = 0 + solutions[i][j] = 1 return False return False @@ -115,4 +194,4 @@ def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]]) if __name__ == "__main__": import doctest - doctest.testmod() + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) From 26d650ec2820e265e69c88608959a3e18f28c5d5 Mon Sep 17 00:00:00 2001 From: piyush-poddar <143445461+piyush-poddar@users.noreply.github.com> Date: Thu, 5 Oct 2023 01:58:19 +0530 Subject: [PATCH 2365/2908] Moved relu.py from maths/ to neural_network/activation_functions (#9753) * Moved file relu.py from maths/ to neural_network/activation_functions * Renamed relu.py to rectified_linear_unit.py * Renamed relu.py to rectified_linear_unit.py in DIRECTORY.md --- DIRECTORY.md | 2 +- .../activation_functions/rectified_linear_unit.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename maths/relu.py => neural_network/activation_functions/rectified_linear_unit.py (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 4f4cc423d678..696a059bb4c8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -639,7 +639,6 @@ * [Quadratic Equations Complex Numbers](maths/quadratic_equations_complex_numbers.py) * [Radians](maths/radians.py) * [Radix2 Fft](maths/radix2_fft.py) - * [Relu](maths/relu.py) * [Remove Digit](maths/remove_digit.py) * [Runge Kutta](maths/runge_kutta.py) * [Segmented Sieve](maths/segmented_sieve.py) @@ -710,6 +709,7 @@ * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) * [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py) * [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py) + * [Rectified Linear Unit](neural_network/activation_functions/rectified_linear_unit.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Perceptron](neural_network/perceptron.py) diff --git a/maths/relu.py b/neural_network/activation_functions/rectified_linear_unit.py similarity index 100% rename from maths/relu.py rename to neural_network/activation_functions/rectified_linear_unit.py From 6a391d113d8f0efdd69e69c8da7b44766594449a Mon Sep 17 00:00:00 2001 From: Raghav <83136390+Raghav-Bell@users.noreply.github.com> Date: Thu, 5 Oct 2023 04:46:19 +0530 Subject: [PATCH 2366/2908] Added Photoelectric effect equation (#9666) * Added Photoelectric effect equation Photoelectric effect is one of the demonstration of quanta of energy. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed doctest Co-authored-by: Rohan Anand <96521078+rohan472000@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Rohan Anand <96521078+rohan472000@users.noreply.github.com> --- physics/photoelectric_effect.py | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 physics/photoelectric_effect.py diff --git a/physics/photoelectric_effect.py b/physics/photoelectric_effect.py new file mode 100644 index 000000000000..3a0138ffe045 --- /dev/null +++ b/physics/photoelectric_effect.py @@ -0,0 +1,67 @@ +""" +The photoelectric effect is the emission of electrons when electromagnetic radiation , +such as light, hits a material. Electrons emitted in this manner are called +photoelectrons. + +In 1905, Einstein proposed a theory of the photoelectric effect using a concept that +light consists of tiny packets of energy known as photons or light quanta. Each packet +carries energy hv that is proportional to the frequency v of the corresponding +electromagnetic wave. The proportionality constant h has become known as the +Planck constant. In the range of kinetic energies of the electrons that are removed from +their varying atomic bindings by the absorption of a photon of energy hv, the highest +kinetic energy K_max is : + +K_max = hv-W + +Here, W is the minimum energy required to remove an electron from the surface of the +material. It is called the work function of the surface + +Reference: https://en.wikipedia.org/wiki/Photoelectric_effect + +""" + +PLANCK_CONSTANT_JS = 6.6261 * pow(10, -34) # in SI (Js) +PLANCK_CONSTANT_EVS = 4.1357 * pow(10, -15) # in eVs + + +def maximum_kinetic_energy( + frequency: float, work_function: float, in_ev: bool = False +) -> float: + """ + Calculates the maximum kinetic energy of emitted electron from the surface. + if the maximum kinetic energy is zero then no electron will be emitted + or given electromagnetic wave frequency is small. + + frequency (float): Frequency of electromagnetic wave. + work_function (float): Work function of the surface. + in_ev (optional)(bool): Pass True if values are in eV. + + Usage example: + >>> maximum_kinetic_energy(1000000,2) + 0 + >>> maximum_kinetic_energy(1000000,2,True) + 0 + >>> maximum_kinetic_energy(10000000000000000,2,True) + 39.357000000000006 + >>> maximum_kinetic_energy(-9,20) + Traceback (most recent call last): + ... + ValueError: Frequency can't be negative. + + >>> maximum_kinetic_energy(1000,"a") + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'float' and 'str' + + """ + if frequency < 0: + raise ValueError("Frequency can't be negative.") + if in_ev: + return max(PLANCK_CONSTANT_EVS * frequency - work_function, 0) + return max(PLANCK_CONSTANT_JS * frequency - work_function, 0) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2fd43c0f7ff1d7f72fa65a528ddabccf90c89a0d Mon Sep 17 00:00:00 2001 From: Tauseef Hilal Tantary Date: Thu, 5 Oct 2023 05:03:12 +0530 Subject: [PATCH 2367/2908] [New Algorithm] - Bell Numbers (#9324) * Add Bell Numbers * Use descriptive variable names * Add type hints * Fix failing tests --- maths/bell_numbers.py | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 maths/bell_numbers.py diff --git a/maths/bell_numbers.py b/maths/bell_numbers.py new file mode 100644 index 000000000000..660ec6e6aa09 --- /dev/null +++ b/maths/bell_numbers.py @@ -0,0 +1,78 @@ +""" +Bell numbers represent the number of ways to partition a set into non-empty +subsets. This module provides functions to calculate Bell numbers for sets of +integers. In other words, the first (n + 1) Bell numbers. + +For more information about Bell numbers, refer to: +https://en.wikipedia.org/wiki/Bell_number +""" + + +def bell_numbers(max_set_length: int) -> list[int]: + """ + Calculate Bell numbers for the sets of lengths from 0 to max_set_length. + In other words, calculate first (max_set_length + 1) Bell numbers. + + Args: + max_set_length (int): The maximum length of the sets for which + Bell numbers are calculated. + + Returns: + list: A list of Bell numbers for sets of lengths from 0 to max_set_length. + + Examples: + >>> bell_numbers(0) + [1] + >>> bell_numbers(1) + [1, 1] + >>> bell_numbers(5) + [1, 1, 2, 5, 15, 52] + """ + if max_set_length < 0: + raise ValueError("max_set_length must be non-negative") + + bell = [0] * (max_set_length + 1) + bell[0] = 1 + + for i in range(1, max_set_length + 1): + for j in range(i): + bell[i] += _binomial_coefficient(i - 1, j) * bell[j] + + return bell + + +def _binomial_coefficient(total_elements: int, elements_to_choose: int) -> int: + """ + Calculate the binomial coefficient C(total_elements, elements_to_choose) + + Args: + total_elements (int): The total number of elements. + elements_to_choose (int): The number of elements to choose. + + Returns: + int: The binomial coefficient C(total_elements, elements_to_choose). + + Examples: + >>> _binomial_coefficient(5, 2) + 10 + >>> _binomial_coefficient(6, 3) + 20 + """ + if elements_to_choose in {0, total_elements}: + return 1 + + if elements_to_choose > total_elements - elements_to_choose: + elements_to_choose = total_elements - elements_to_choose + + coefficient = 1 + for i in range(elements_to_choose): + coefficient *= total_elements - i + coefficient //= i + 1 + + return coefficient + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 1fda96b7044d9fa08c84f09f54a345ebf052b2eb Mon Sep 17 00:00:00 2001 From: Sanket Kittad <86976526+sanketkittad@users.noreply.github.com> Date: Thu, 5 Oct 2023 05:10:14 +0530 Subject: [PATCH 2368/2908] Palindromic (#9288) * added longest palindromic subsequence * removed * added longest palindromic subsequence * added longest palindromic subsequence link * added comments --- .../longest_palindromic_subsequence.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 dynamic_programming/longest_palindromic_subsequence.py diff --git a/dynamic_programming/longest_palindromic_subsequence.py b/dynamic_programming/longest_palindromic_subsequence.py new file mode 100644 index 000000000000..a60d95e460e6 --- /dev/null +++ b/dynamic_programming/longest_palindromic_subsequence.py @@ -0,0 +1,44 @@ +""" +author: Sanket Kittad +Given a string s, find the longest palindromic subsequence's length in s. +Input: s = "bbbab" +Output: 4 +Explanation: One possible longest palindromic subsequence is "bbbb". +Leetcode link: https://leetcode.com/problems/longest-palindromic-subsequence/description/ +""" + + +def longest_palindromic_subsequence(input_string: str) -> int: + """ + This function returns the longest palindromic subsequence in a string + >>> longest_palindromic_subsequence("bbbab") + 4 + >>> longest_palindromic_subsequence("bbabcbcab") + 7 + """ + n = len(input_string) + rev = input_string[::-1] + m = len(rev) + dp = [[-1] * (m + 1) for i in range(n + 1)] + for i in range(n + 1): + dp[i][0] = 0 + for i in range(m + 1): + dp[0][i] = 0 + + # create and initialise dp array + for i in range(1, n + 1): + for j in range(1, m + 1): + # If characters at i and j are the same + # include them in the palindromic subsequence + if input_string[i - 1] == rev[j - 1]: + dp[i][j] = 1 + dp[i - 1][j - 1] + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + + return dp[n][m] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 935d1d3225ede4c0650165d5dfd8f5eb35b54f5e Mon Sep 17 00:00:00 2001 From: Vipin Karthic <143083087+vipinkarthic@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:27:55 +0530 Subject: [PATCH 2369/2908] Added Mirror Formulae Equation (#9717) * Python mirror_formulae.py is added to the repository * Changes done after reading readme.md * Changes for running doctest on all platforms * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Change 2 for Doctests * Changes for doctest 2 * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 9 ++- physics/mirror_formulae.py | 127 +++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 physics/mirror_formulae.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 696a059bb4c8..5f23cbd6c922 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -170,6 +170,7 @@ ## Data Structures * Arrays + * [Median Two Array](data_structures/arrays/median_two_array.py) * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) * [Product Sum](data_structures/arrays/product_sum.py) @@ -185,6 +186,7 @@ * [Diff Views Of Binary Tree](data_structures/binary_tree/diff_views_of_binary_tree.py) * [Distribute Coins](data_structures/binary_tree/distribute_coins.py) * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) + * [Flatten Binarytree To Linkedlist](data_structures/binary_tree/flatten_binarytree_to_linkedlist.py) * [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py) * [Is Bst](data_structures/binary_tree/is_bst.py) * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) @@ -324,6 +326,7 @@ * [Longest Common Substring](dynamic_programming/longest_common_substring.py) * [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py) * [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py) + * [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py) * [Longest Sub Array](dynamic_programming/longest_sub_array.py) * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) * [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py) @@ -539,6 +542,7 @@ * [Average Mode](maths/average_mode.py) * [Bailey Borwein Plouffe](maths/bailey_borwein_plouffe.py) * [Basic Maths](maths/basic_maths.py) + * [Bell Numbers](maths/bell_numbers.py) * [Binary Exp Mod](maths/binary_exp_mod.py) * [Binary Exponentiation](maths/binary_exponentiation.py) * [Binary Exponentiation 3](maths/binary_exponentiation_3.py) @@ -690,6 +694,7 @@ * [Matrix Class](matrix/matrix_class.py) * [Matrix Operation](matrix/matrix_operation.py) * [Max Area Of Island](matrix/max_area_of_island.py) + * [Median Matrix](matrix/median_matrix.py) * [Nth Fibonacci Using Matrix Exponentiation](matrix/nth_fibonacci_using_matrix_exponentiation.py) * [Pascal Triangle](matrix/pascal_triangle.py) * [Rotate Matrix](matrix/rotate_matrix.py) @@ -708,8 +713,8 @@ * Activation Functions * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) * [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py) - * [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py) * [Rectified Linear Unit](neural_network/activation_functions/rectified_linear_unit.py) + * [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Perceptron](neural_network/perceptron.py) @@ -756,9 +761,11 @@ * [Kinetic Energy](physics/kinetic_energy.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) * [Malus Law](physics/malus_law.py) + * [Mirror Formulae](physics/mirror_formulae.py) * [N Body Simulation](physics/n_body_simulation.py) * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) + * [Photoelectric Effect](physics/photoelectric_effect.py) * [Potential Energy](physics/potential_energy.py) * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Shear Stress](physics/shear_stress.py) diff --git a/physics/mirror_formulae.py b/physics/mirror_formulae.py new file mode 100644 index 000000000000..f1b4ac2c7baf --- /dev/null +++ b/physics/mirror_formulae.py @@ -0,0 +1,127 @@ +""" +This module contains the functions to calculate the focal length, object distance +and image distance of a mirror. + +The mirror formula is an equation that relates the object distance (u), +image distance (v), and focal length (f) of a spherical mirror. +It is commonly used in optics to determine the position and characteristics +of an image formed by a mirror. It is expressed using the formulae : + +------------------- +| 1/f = 1/v + 1/u | +------------------- + +Where, +f = Focal length of the spherical mirror (metre) +v = Image distance from the mirror (metre) +u = Object distance from the mirror (metre) + + +The signs of the distances are taken with respect to the sign convention. +The sign convention is as follows: + 1) Object is always placed to the left of mirror + 2) Distances measured in the direction of the incident ray are positive + and the distances measured in the direction opposite to that of the incident + rays are negative. + 3) All distances are measured from the pole of the mirror. + + +There are a few assumptions that are made while using the mirror formulae. +They are as follows: + 1) Thin Mirror: The mirror is assumed to be thin, meaning its thickness is + negligible compared to its radius of curvature. This assumption allows + us to treat the mirror as a two-dimensional surface. + 2) Spherical Mirror: The mirror is assumed to have a spherical shape. While this + assumption may not hold exactly for all mirrors, it is a reasonable approximation + for most practical purposes. + 3) Small Angles: The angles involved in the derivation are assumed to be small. + This assumption allows us to use the small-angle approximation, where the tangent + of a small angle is approximately equal to the angle itself. It simplifies the + calculations and makes the derivation more manageable. + 4) Paraxial Rays: The mirror formula is derived using paraxial rays, which are + rays that are close to the principal axis and make small angles with it. This + assumption ensures that the rays are close enough to the principal axis, making the + calculations more accurate. + 5) Reflection and Refraction Laws: The derivation assumes that the laws of + reflection and refraction hold. + These laws state that the angle of incidence is equal to the angle of reflection + for reflection, and the incident and refracted rays lie in the same plane and + obey Snell's law for refraction. + +(Description and Assumptions adapted from +https://www.collegesearch.in/articles/mirror-formula-derivation) + +(Sign Convention adapted from +https://www.toppr.com/ask/content/concept/sign-convention-for-mirrors-210189/) + + +""" + + +def focal_length(distance_of_object: float, distance_of_image: float) -> float: + """ + >>> from math import isclose + >>> isclose(focal_length(10, 20), 6.66666666666666) + True + >>> from math import isclose + >>> isclose(focal_length(9.5, 6.7), 3.929012346) + True + >>> focal_length(0, 20) + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter non zero values with respect + to the sign convention. + """ + + if distance_of_object == 0 or distance_of_image == 0: + raise ValueError( + "Invalid inputs. Enter non zero values with respect to the sign convention." + ) + focal_length = 1 / ((1 / distance_of_object) + (1 / distance_of_image)) + return focal_length + + +def object_distance(focal_length: float, distance_of_image: float) -> float: + """ + >>> from math import isclose + >>> isclose(object_distance(30, 20), -60.0) + True + >>> from math import isclose + >>> isclose(object_distance(10.5, 11.7), 102.375) + True + >>> object_distance(90, 0) + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter non zero values with respect + to the sign convention. + """ + + if distance_of_image == 0 or focal_length == 0: + raise ValueError( + "Invalid inputs. Enter non zero values with respect to the sign convention." + ) + object_distance = 1 / ((1 / focal_length) - (1 / distance_of_image)) + return object_distance + + +def image_distance(focal_length: float, distance_of_object: float) -> float: + """ + >>> from math import isclose + >>> isclose(image_distance(10, 40), 13.33333333) + True + >>> from math import isclose + >>> isclose(image_distance(1.5, 6.7), 1.932692308) + True + >>> image_distance(0, 0) + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter non zero values with respect + to the sign convention. + """ + + if distance_of_object == 0 or focal_length == 0: + raise ValueError( + "Invalid inputs. Enter non zero values with respect to the sign convention." + ) + image_distance = 1 / ((1 / focal_length) - (1 / distance_of_object)) + return image_distance From 4b6301d4ce91638d39689f7be7db797f99623964 Mon Sep 17 00:00:00 2001 From: rtang09 <49603415+rtang09@users.noreply.github.com> Date: Wed, 4 Oct 2023 23:12:08 -0700 Subject: [PATCH 2370/2908] Fletcher 16 (#9775) * Add files via upload * Update fletcher16.py * Update fletcher16.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fletcher16.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fletcher16.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fletcher16.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- hashes/fletcher16.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 hashes/fletcher16.py diff --git a/hashes/fletcher16.py b/hashes/fletcher16.py new file mode 100644 index 000000000000..7c23c98d72c5 --- /dev/null +++ b/hashes/fletcher16.py @@ -0,0 +1,36 @@ +""" +The Fletcher checksum is an algorithm for computing a position-dependent +checksum devised by John G. Fletcher (1934–2012) at Lawrence Livermore Labs +in the late 1970s.[1] The objective of the Fletcher checksum was to +provide error-detection properties approaching those of a cyclic +redundancy check but with the lower computational effort associated +with summation techniques. + +Source: https://en.wikipedia.org/wiki/Fletcher%27s_checksum +""" + + +def fletcher16(text: str) -> int: + """ + Loop through every character in the data and add to two sums. + + >>> fletcher16('hello world') + 6752 + >>> fletcher16('onethousandfourhundredthirtyfour') + 28347 + >>> fletcher16('The quick brown fox jumps over the lazy dog.') + 5655 + """ + data = bytes(text, "ascii") + sum1 = 0 + sum2 = 0 + for character in data: + sum1 = (sum1 + character) % 255 + sum2 = (sum1 + sum2) % 255 + return (sum2 << 8) | sum1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0d324de7ab9c354d958fd93f6046d0111014d95a Mon Sep 17 00:00:00 2001 From: Vipin Karthic <143083087+vipinkarthic@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:18:15 +0530 Subject: [PATCH 2371/2908] Doctest Error Correction of mirror_formulae.py (#9782) * Python mirror_formulae.py is added to the repository * Changes done after reading readme.md * Changes for running doctest on all platforms * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Change 2 for Doctests * Changes for doctest 2 * updating DIRECTORY.md * Doctest whitespace error rectification to mirror_formulae.py * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + physics/mirror_formulae.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 5f23cbd6c922..b0ba3c3852da 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -469,6 +469,7 @@ * [Djb2](hashes/djb2.py) * [Elf](hashes/elf.py) * [Enigma Machine](hashes/enigma_machine.py) + * [Fletcher16](hashes/fletcher16.py) * [Hamming Code](hashes/hamming_code.py) * [Luhn](hashes/luhn.py) * [Md5](hashes/md5.py) diff --git a/physics/mirror_formulae.py b/physics/mirror_formulae.py index f1b4ac2c7baf..7efc52438140 100644 --- a/physics/mirror_formulae.py +++ b/physics/mirror_formulae.py @@ -66,7 +66,7 @@ def focal_length(distance_of_object: float, distance_of_image: float) -> float: >>> from math import isclose >>> isclose(focal_length(9.5, 6.7), 3.929012346) True - >>> focal_length(0, 20) + >>> focal_length(0, 20) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: Invalid inputs. Enter non zero values with respect @@ -89,7 +89,7 @@ def object_distance(focal_length: float, distance_of_image: float) -> float: >>> from math import isclose >>> isclose(object_distance(10.5, 11.7), 102.375) True - >>> object_distance(90, 0) + >>> object_distance(90, 0) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: Invalid inputs. Enter non zero values with respect @@ -112,7 +112,7 @@ def image_distance(focal_length: float, distance_of_object: float) -> float: >>> from math import isclose >>> isclose(image_distance(1.5, 6.7), 1.932692308) True - >>> image_distance(0, 0) + >>> image_distance(0, 0) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: Invalid inputs. Enter non zero values with respect From f3be0ae9e60a0ed2185e55c0758ddf401e604f8c Mon Sep 17 00:00:00 2001 From: Naman <37952726+namansharma18899@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:07:23 +0530 Subject: [PATCH 2372/2908] Added largest pow of 2 le num (#9374) --- bit_manipulation/largest_pow_of_two_le_num.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 bit_manipulation/largest_pow_of_two_le_num.py diff --git a/bit_manipulation/largest_pow_of_two_le_num.py b/bit_manipulation/largest_pow_of_two_le_num.py new file mode 100644 index 000000000000..6ef827312199 --- /dev/null +++ b/bit_manipulation/largest_pow_of_two_le_num.py @@ -0,0 +1,60 @@ +""" +Author : Naman Sharma +Date : October 2, 2023 + +Task: +To Find the largest power of 2 less than or equal to a given number. + +Implementation notes: Use bit manipulation. +We start from 1 & left shift the set bit to check if (res<<1)<=number. +Each left bit shift represents a pow of 2. + +For example: +number: 15 +res: 1 0b1 + 2 0b10 + 4 0b100 + 8 0b1000 + 16 0b10000 (Exit) +""" + + +def largest_pow_of_two_le_num(number: int) -> int: + """ + Return the largest power of two less than or equal to a number. + + >>> largest_pow_of_two_le_num(0) + 0 + >>> largest_pow_of_two_le_num(1) + 1 + >>> largest_pow_of_two_le_num(-1) + 0 + >>> largest_pow_of_two_le_num(3) + 2 + >>> largest_pow_of_two_le_num(15) + 8 + >>> largest_pow_of_two_le_num(99) + 64 + >>> largest_pow_of_two_le_num(178) + 128 + >>> largest_pow_of_two_le_num(999999) + 524288 + >>> largest_pow_of_two_le_num(99.9) + Traceback (most recent call last): + ... + TypeError: Input value must be a 'int' type + """ + if isinstance(number, float): + raise TypeError("Input value must be a 'int' type") + if number <= 0: + return 0 + res = 1 + while (res << 1) <= number: + res <<= 1 + return res + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From e29024d14ade8ff4cdb43d1da6a7738f44685e5e Mon Sep 17 00:00:00 2001 From: Rohan Sardar <77870108+RohanSardar@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:22:40 +0530 Subject: [PATCH 2373/2908] Program to convert a given string to Pig Latin (#9712) * Program to convert a given string to Pig Latin This is a program to convert a user given string to its respective Pig Latin form As per wikipedia (link: https://en.wikipedia.org/wiki/Pig_Latin#Rules) For words that begin with consonant sounds, all letters before the initial vowel are placed at the end of the word sequence. Then, "ay" is added, as in the following examples: "pig" = "igpay" "latin" = "atinlay" "banana" = "ananabay" When words begin with consonant clusters (multiple consonants that form one sound), the whole sound is added to the end when speaking or writing. "friends" = "iendsfray" "smile" = "ilesmay" "string" = "ingstray" For words that begin with vowel sounds, one just adds "hay", "way" or "yay" to the end. Examples are: "eat" = "eatway" "omelet" = "omeletway" "are" = "areway" * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pig_latin.py Added f-string * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pig_latin.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pig_latin.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pig_latin.py * Update pig_latin.py * Update pig_latin.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- strings/pig_latin.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 strings/pig_latin.py diff --git a/strings/pig_latin.py b/strings/pig_latin.py new file mode 100644 index 000000000000..457dbb5a6cf6 --- /dev/null +++ b/strings/pig_latin.py @@ -0,0 +1,44 @@ +def pig_latin(word: str) -> str: + """Compute the piglatin of a given string. + + https://en.wikipedia.org/wiki/Pig_Latin + + Usage examples: + >>> pig_latin("pig") + 'igpay' + >>> pig_latin("latin") + 'atinlay' + >>> pig_latin("banana") + 'ananabay' + >>> pig_latin("friends") + 'iendsfray' + >>> pig_latin("smile") + 'ilesmay' + >>> pig_latin("string") + 'ingstray' + >>> pig_latin("eat") + 'eatway' + >>> pig_latin("omelet") + 'omeletway' + >>> pig_latin("are") + 'areway' + >>> pig_latin(" ") + '' + >>> pig_latin(None) + '' + """ + if not (word or "").strip(): + return "" + word = word.lower() + if word[0] in "aeiou": + return f"{word}way" + for i, char in enumerate(word): # noqa: B007 + if char in "aeiou": + break + return f"{word[i:]}{word[:i]}ay" + + +if __name__ == "__main__": + print(f"{pig_latin('friends') = }") + word = input("Enter a word: ") + print(f"{pig_latin(word) = }") From dffbe458c07d492b9c599376233f9f6295527339 Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Fri, 6 Oct 2023 00:26:33 +1300 Subject: [PATCH 2374/2908] Update contributing guidelines to say not to open new issues for algorithms (#9760) * updated CONTRIBUTING.md with markdown anchors and issues * removed testing header from previous PR --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a67ce33cd62..bf3420185c1a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,12 @@ We appreciate any contribution, from fixing a grammar mistake in a comment to im Your contribution will be tested by our [automated testing on GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions 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 GitHub Actions 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. +#### Issues + If you are interested in resolving an [open issue](https://github.com/TheAlgorithms/Python/issues), simply make a pull request with your proposed fix. __We do not assign issues in this repo__ so please do not ask for permission to work on an issue. +__Do not__ create an issue to contribute an algorithm. Please submit a pull request instead. + Please help us keep our issue list small by adding `Fixes #{$ISSUE_NUMBER}` to the description of pull requests that resolve open issues. For example, if your pull request fixes issue #10, then please add the following to its description: ``` From 0e3ea3fbab0297f38ed48b9e2f694cc43f8af567 Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:30:39 +0500 Subject: [PATCH 2375/2908] Fermat_little_theorem type annotation (#9794) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Added type annotation. * Update fermat_little_theorem.py Used other syntax. * Update fermat_little_theorem.py * Update maths/fermat_little_theorem.py --------- Co-authored-by: Tianyi Zheng --- maths/fermat_little_theorem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/fermat_little_theorem.py b/maths/fermat_little_theorem.py index eea03be245cb..4a3ecd05ce91 100644 --- a/maths/fermat_little_theorem.py +++ b/maths/fermat_little_theorem.py @@ -5,7 +5,7 @@ # Wikipedia reference: https://en.wikipedia.org/wiki/Fermat%27s_little_theorem -def binary_exponentiation(a, n, mod): +def binary_exponentiation(a: int, n: float, mod: int) -> int: if n == 0: return 1 From 1b6c5cc2713743b8a74fd9c92e0a1b6442d63a7f Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:30:43 +0500 Subject: [PATCH 2376/2908] Karatsuba type annotation (#9800) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Added type annotation. --- maths/karatsuba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/karatsuba.py b/maths/karatsuba.py index 4bf4aecdc068..3d29e31d2107 100644 --- a/maths/karatsuba.py +++ b/maths/karatsuba.py @@ -1,7 +1,7 @@ """ Multiply two numbers using Karatsuba algorithm """ -def karatsuba(a, b): +def karatsuba(a: int, b: int) -> int: """ >>> karatsuba(15463, 23489) == 15463 * 23489 True From f159a3350650843e0b3e856e612cda56eabb4237 Mon Sep 17 00:00:00 2001 From: Abul Hasan <33129246+haxkd@users.noreply.github.com> Date: Thu, 5 Oct 2023 18:09:14 +0530 Subject: [PATCH 2377/2908] convert to the base minus 2 of a number (#9748) * Fix: Issue 9588 * Fix: Issue 9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix: Issue 9588 * Fix: Issue #9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix: Issue #9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix: Issue #9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: issue #9793 * fix: issue #9793 * fix: issue #9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/base_neg2_conversion.py | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 maths/base_neg2_conversion.py diff --git a/maths/base_neg2_conversion.py b/maths/base_neg2_conversion.py new file mode 100644 index 000000000000..81d40d37e79d --- /dev/null +++ b/maths/base_neg2_conversion.py @@ -0,0 +1,37 @@ +def decimal_to_negative_base_2(num: int) -> int: + """ + This function returns the number negative base 2 + of the decimal number of the input data. + + Args: + int: The decimal number to convert. + + Returns: + int: The negative base 2 number. + + Examples: + >>> decimal_to_negative_base_2(0) + 0 + >>> decimal_to_negative_base_2(-19) + 111101 + >>> decimal_to_negative_base_2(4) + 100 + >>> decimal_to_negative_base_2(7) + 11011 + """ + if num == 0: + return 0 + ans = "" + while num != 0: + num, rem = divmod(num, -2) + if rem < 0: + rem += 2 + num += 1 + ans = str(rem) + ans + return int(ans) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9bfc314e878e36a5f5d8974ec188ad7f0db8c5a1 Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:39:29 +0500 Subject: [PATCH 2378/2908] hardy_ramanujanalgo type annotation (#9799) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Added type annotation. --- maths/hardy_ramanujanalgo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/hardy_ramanujanalgo.py b/maths/hardy_ramanujanalgo.py index 6929533fc389..31ec76fbe10b 100644 --- a/maths/hardy_ramanujanalgo.py +++ b/maths/hardy_ramanujanalgo.py @@ -4,7 +4,7 @@ import math -def exact_prime_factor_count(n): +def exact_prime_factor_count(n: int) -> int: """ >>> exact_prime_factor_count(51242183) 3 From 6643c955376174c307c982b1d5cc39778c40bea1 Mon Sep 17 00:00:00 2001 From: Adebisi Ahmed Date: Thu, 5 Oct 2023 14:18:54 +0100 Subject: [PATCH 2379/2908] add gas station (#9446) * feat: add gas station * make code more readable make code more readable * update test * Update gas_station.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tuple[GasStation, ...] * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- greedy_methods/gas_station.py | 97 +++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 greedy_methods/gas_station.py diff --git a/greedy_methods/gas_station.py b/greedy_methods/gas_station.py new file mode 100644 index 000000000000..2427375d2664 --- /dev/null +++ b/greedy_methods/gas_station.py @@ -0,0 +1,97 @@ +""" +Task: +There are n gas stations along a circular route, where the amount of gas +at the ith station is gas_quantities[i]. + +You have a car with an unlimited gas tank and it costs costs[i] of gas +to travel from the ith station to its next (i + 1)th station. +You begin the journey with an empty tank at one of the gas stations. + +Given two integer arrays gas_quantities and costs, return the starting +gas station's index if you can travel around the circuit once +in the clockwise direction otherwise, return -1. +If there exists a solution, it is guaranteed to be unique + +Reference: https://leetcode.com/problems/gas-station/description + +Implementation notes: +First, check whether the total gas is enough to complete the journey. If not, return -1. +However, if there is enough gas, it is guaranteed that there is a valid +starting index to reach the end of the journey. +Greedily calculate the net gain (gas_quantity - cost) at each station. +If the net gain ever goes below 0 while iterating through the stations, +start checking from the next station. + +""" +from dataclasses import dataclass + + +@dataclass +class GasStation: + gas_quantity: int + cost: int + + +def get_gas_stations( + gas_quantities: list[int], costs: list[int] +) -> tuple[GasStation, ...]: + """ + This function returns a tuple of gas stations. + + Args: + gas_quantities: Amount of gas available at each station + costs: The cost of gas required to move from one station to the next + + Returns: + A tuple of gas stations + + >>> gas_stations = get_gas_stations([1, 2, 3, 4, 5], [3, 4, 5, 1, 2]) + >>> len(gas_stations) + 5 + >>> gas_stations[0] + GasStation(gas_quantity=1, cost=3) + >>> gas_stations[-1] + GasStation(gas_quantity=5, cost=2) + """ + return tuple( + GasStation(quantity, cost) for quantity, cost in zip(gas_quantities, costs) + ) + + +def can_complete_journey(gas_stations: tuple[GasStation, ...]) -> int: + """ + This function returns the index from which to start the journey + in order to reach the end. + + Args: + gas_quantities [list]: Amount of gas available at each station + cost [list]: The cost of gas required to move from one station to the next + + Returns: + start [int]: start index needed to complete the journey + + Examples: + >>> can_complete_journey(get_gas_stations([1, 2, 3, 4, 5], [3, 4, 5, 1, 2])) + 3 + >>> can_complete_journey(get_gas_stations([2, 3, 4], [3, 4, 3])) + -1 + """ + total_gas = sum(gas_station.gas_quantity for gas_station in gas_stations) + total_cost = sum(gas_station.cost for gas_station in gas_stations) + if total_gas < total_cost: + return -1 + + start = 0 + net = 0 + for i, gas_station in enumerate(gas_stations): + net += gas_station.gas_quantity - gas_station.cost + if net < 0: + start = i + 1 + net = 0 + return start + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 55ee273419ae76ddeda250374921644615b88393 Mon Sep 17 00:00:00 2001 From: Wei Jiang <42140605+Jiang15@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:00:48 +0200 Subject: [PATCH 2380/2908] [bug fixing] Edge case of the double ended queue (#9823) * fix the edge case of the double ended queue pop the last element * refactoring doc --------- Co-authored-by: Jiang15 --- data_structures/queue/double_ended_queue.py | 62 +++++++++++++++------ 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 44dc863b9a4e..17a23038d288 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -242,12 +242,20 @@ def pop(self) -> Any: Removes the last element of the deque and returns it. Time complexity: O(1) @returns topop.val: the value of the node to pop. - >>> our_deque = Deque([1, 2, 3, 15182]) - >>> our_popped = our_deque.pop() - >>> our_popped + >>> our_deque1 = Deque([1]) + >>> our_popped1 = our_deque1.pop() + >>> our_popped1 + 1 + >>> our_deque1 + [] + + >>> our_deque2 = Deque([1, 2, 3, 15182]) + >>> our_popped2 = our_deque2.pop() + >>> our_popped2 15182 - >>> our_deque + >>> our_deque2 [1, 2, 3] + >>> from collections import deque >>> deque_collections = deque([1, 2, 3, 15182]) >>> collections_popped = deque_collections.pop() @@ -255,18 +263,24 @@ def pop(self) -> Any: 15182 >>> deque_collections deque([1, 2, 3]) - >>> list(our_deque) == list(deque_collections) + >>> list(our_deque2) == list(deque_collections) True - >>> our_popped == collections_popped + >>> our_popped2 == collections_popped True """ # make sure the deque has elements to pop assert not self.is_empty(), "Deque is empty." topop = self._back - self._back = self._back.prev_node # set new back - # drop the last node - python will deallocate memory automatically - self._back.next_node = None + # if only one element in the queue: point the front and back to None + # else remove one element from back + if self._front == self._back: + self._front = None + self._back = None + else: + self._back = self._back.prev_node # set new back + # drop the last node, python will deallocate memory automatically + self._back.next_node = None self._len -= 1 @@ -277,11 +291,17 @@ def popleft(self) -> Any: Removes the first element of the deque and returns it. Time complexity: O(1) @returns topop.val: the value of the node to pop. - >>> our_deque = Deque([15182, 1, 2, 3]) - >>> our_popped = our_deque.popleft() - >>> our_popped + >>> our_deque1 = Deque([1]) + >>> our_popped1 = our_deque1.pop() + >>> our_popped1 + 1 + >>> our_deque1 + [] + >>> our_deque2 = Deque([15182, 1, 2, 3]) + >>> our_popped2 = our_deque2.popleft() + >>> our_popped2 15182 - >>> our_deque + >>> our_deque2 [1, 2, 3] >>> from collections import deque >>> deque_collections = deque([15182, 1, 2, 3]) @@ -290,17 +310,23 @@ def popleft(self) -> Any: 15182 >>> deque_collections deque([1, 2, 3]) - >>> list(our_deque) == list(deque_collections) + >>> list(our_deque2) == list(deque_collections) True - >>> our_popped == collections_popped + >>> our_popped2 == collections_popped True """ # make sure the deque has elements to pop assert not self.is_empty(), "Deque is empty." topop = self._front - self._front = self._front.next_node # set new front and drop the first node - self._front.prev_node = None + # if only one element in the queue: point the front and back to None + # else remove one element from front + if self._front == self._back: + self._front = None + self._back = None + else: + self._front = self._front.next_node # set new front and drop the first node + self._front.prev_node = None self._len -= 1 @@ -432,3 +458,5 @@ def __repr__(self) -> str: import doctest doctest.testmod() + dq = Deque([3]) + dq.pop() From deb0480b3a07e50b93f88d4351d1fce000574d05 Mon Sep 17 00:00:00 2001 From: Aasheesh <126905285+AasheeshLikePanner@users.noreply.github.com> Date: Thu, 5 Oct 2023 19:37:44 +0530 Subject: [PATCH 2381/2908] Changing the directory of sigmoid_linear_unit.py (#9824) * Changing the directory of sigmoid_linear_unit.py * Delete neural_network/activation_functions/__init__.py --------- Co-authored-by: Tianyi Zheng --- .../activation_functions}/sigmoid_linear_unit.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {maths => neural_network/activation_functions}/sigmoid_linear_unit.py (100%) diff --git a/maths/sigmoid_linear_unit.py b/neural_network/activation_functions/sigmoid_linear_unit.py similarity index 100% rename from maths/sigmoid_linear_unit.py rename to neural_network/activation_functions/sigmoid_linear_unit.py From 87494f1fa1022368d154477bdc035fd01f9e4382 Mon Sep 17 00:00:00 2001 From: Parth <100679824+pa-kh039@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:51:28 +0530 Subject: [PATCH 2382/2908] largest divisible subset (#9825) * largest divisible subset * minor tweaks * adding more test cases Co-authored-by: Christian Clauss * improving code for better readability Co-authored-by: Christian Clauss * update Co-authored-by: Christian Clauss * update Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * suggested changes done, and further modfications * final update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update largest_divisible_subset.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update largest_divisible_subset.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../largest_divisible_subset.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 dynamic_programming/largest_divisible_subset.py diff --git a/dynamic_programming/largest_divisible_subset.py b/dynamic_programming/largest_divisible_subset.py new file mode 100644 index 000000000000..db38636e29db --- /dev/null +++ b/dynamic_programming/largest_divisible_subset.py @@ -0,0 +1,74 @@ +from __future__ import annotations + + +def largest_divisible_subset(items: list[int]) -> list[int]: + """ + Algorithm to find the biggest subset in the given array such that for any 2 elements + x and y in the subset, either x divides y or y divides x. + >>> largest_divisible_subset([1, 16, 7, 8, 4]) + [16, 8, 4, 1] + >>> largest_divisible_subset([1, 2, 3]) + [2, 1] + >>> largest_divisible_subset([-1, -2, -3]) + [-3] + >>> largest_divisible_subset([1, 2, 4, 8]) + [8, 4, 2, 1] + >>> largest_divisible_subset((1, 2, 4, 8)) + [8, 4, 2, 1] + >>> largest_divisible_subset([1, 1, 1]) + [1, 1, 1] + >>> largest_divisible_subset([0, 0, 0]) + [0, 0, 0] + >>> largest_divisible_subset([-1, -1, -1]) + [-1, -1, -1] + >>> largest_divisible_subset([]) + [] + """ + # Sort the array in ascending order as the sequence does not matter we only have to + # pick up a subset. + items = sorted(items) + + number_of_items = len(items) + + # Initialize memo with 1s and hash with increasing numbers + memo = [1] * number_of_items + hash_array = list(range(number_of_items)) + + # Iterate through the array + for i, item in enumerate(items): + for prev_index in range(i): + if ((items[prev_index] != 0 and item % items[prev_index]) == 0) and ( + (1 + memo[prev_index]) > memo[i] + ): + memo[i] = 1 + memo[prev_index] + hash_array[i] = prev_index + + ans = -1 + last_index = -1 + + # Find the maximum length and its corresponding index + for i, memo_item in enumerate(memo): + if memo_item > ans: + ans = memo_item + last_index = i + + # Reconstruct the divisible subset + if last_index == -1: + return [] + result = [items[last_index]] + while hash_array[last_index] != last_index: + last_index = hash_array[last_index] + result.append(items[last_index]) + + return result + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + + items = [1, 16, 7, 8, 4] + print( + f"The longest divisible subset of {items} is {largest_divisible_subset(items)}." + ) From b76115e8d184fbad1d6c400fcdd964e821f09e9b Mon Sep 17 00:00:00 2001 From: Pronay Debnath Date: Thu, 5 Oct 2023 23:03:05 +0530 Subject: [PATCH 2383/2908] Updated check_bipartite_graph_dfs.py (#9525) * Create dijkstra_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dijkstra_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dijkstra_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dijkstra_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Delete greedy_methods/dijkstra_algorithm.py * Update check_bipartite_graph_dfs.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update check_bipartite_graph_dfs.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update graphs/check_bipartite_graph_dfs.py Co-authored-by: Christian Clauss * Update graphs/check_bipartite_graph_dfs.py Co-authored-by: Christian Clauss * Update check_bipartite_graph_dfs.py * Update check_bipartite_graph_dfs.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update check_bipartite_graph_dfs.py * Update check_bipartite_graph_dfs.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update check_bipartite_graph_dfs.py * Update check_bipartite_graph_dfs.py * Update check_bipartite_graph_dfs.py * Let's use self-documenting variable names This is complex code so let's use self-documenting function and variable names to help readers to understand. We should not shorten names to simplify the code formatting but use understandable name and leave to code formatting to psf/black. I am not sure if `nbor` was supposed to be `neighbour`. ;-) * Update check_bipartite_graph_dfs.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- graphs/check_bipartite_graph_dfs.py | 73 +++++++++++++++++++---------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/graphs/check_bipartite_graph_dfs.py b/graphs/check_bipartite_graph_dfs.py index fd644230449c..b13a9eb95afb 100644 --- a/graphs/check_bipartite_graph_dfs.py +++ b/graphs/check_bipartite_graph_dfs.py @@ -1,34 +1,55 @@ -# Check whether Graph is Bipartite or Not using DFS +from collections import defaultdict -# 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(graph): - visited = [False] * len(graph) - color = [-1] * len(graph) +def is_bipartite(graph: defaultdict[int, list[int]]) -> bool: + """ + Check whether a graph is Bipartite or not using Depth-First Search (DFS). - def dfs(v, c): - visited[v] = True - color[v] = c - for u in graph[v]: - if not visited[u]: - dfs(u, 1 - c) + 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. There is + no edge that connects vertices of the same set. - for i in range(len(graph)): - if not visited[i]: - dfs(i, 0) + Args: + graph: An adjacency list representing the graph. - for i in range(len(graph)): - for j in graph[i]: - if color[i] == color[j]: - return False + Returns: + True if there's no edge that connects vertices of the same set, False otherwise. - return True + Examples: + >>> is_bipartite( + ... defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4], 3: [1], 4: [2]}) + ... ) + False + >>> is_bipartite(defaultdict(list, {0: [1, 2], 1: [0, 2], 2: [0, 1]})) + True + """ + def depth_first_search(node: int, color: int) -> bool: + visited[node] = color + return any( + visited[neighbour] == color + or ( + visited[neighbour] == -1 + and not depth_first_search(neighbour, 1 - color) + ) + for neighbour in graph[node] + ) -# Adjacency list of graph -graph = {0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: []} -print(check_bipartite_dfs(graph)) + visited: defaultdict[int, int] = defaultdict(lambda: -1) + + return all( + not (visited[node] == -1 and not depth_first_search(node, 0)) for node in graph + ) + + +if __name__ == "__main__": + import doctest + + result = doctest.testmod() + + if result.failed: + print(f"{result.failed} test(s) failed.") + else: + print("All tests passed!") From cffdf99c55dcda89a5ce0fb2bf3cb685d168d136 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Farooq <115654418+Muhammadummerr@users.noreply.github.com> Date: Thu, 5 Oct 2023 23:44:55 +0500 Subject: [PATCH 2384/2908] Updated prime_numbers.py testcases. (#9851) * Updated prime_numbers.py testcases. * revert __main__ code. --- maths/prime_numbers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index c5297ed9264c..38cc6670385d 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -17,8 +17,8 @@ def slow_primes(max_n: int) -> Generator[int, None, None]: [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 + >>> list(slow_primes(1000))[-1] + 997 """ numbers: Generator = (i for i in range(1, (max_n + 1))) for i in (n for n in numbers if n > 1): @@ -44,8 +44,8 @@ def primes(max_n: int) -> Generator[int, None, None]: [2, 3, 5, 7, 11] >>> list(primes(33)) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31] - >>> list(primes(10000))[-1] - 9973 + >>> list(primes(1000))[-1] + 997 """ numbers: Generator = (i for i in range(1, (max_n + 1))) for i in (n for n in numbers if n > 1): @@ -73,8 +73,8 @@ def fast_primes(max_n: int) -> Generator[int, None, None]: [2, 3, 5, 7, 11] >>> list(fast_primes(33)) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31] - >>> list(fast_primes(10000))[-1] - 9973 + >>> list(fast_primes(1000))[-1] + 997 """ numbers: Generator = (i for i in range(1, (max_n + 1), 2)) # It's useless to test even numbers as they will not be prime From 5869fda74245b55a3bda4ccc5ac62a84ab40766f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 5 Oct 2023 23:55:13 +0200 Subject: [PATCH 2385/2908] print reverse: A LinkedList with a tail pointer (#9875) * print reverse: A LinkedList with a tail pointer * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 7 +- data_structures/linked_list/print_reverse.py | 134 +++++++++++++------ 2 files changed, 101 insertions(+), 40 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index b0ba3c3852da..c199a4329202 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -50,6 +50,7 @@ * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py) * [Is Even](bit_manipulation/is_even.py) * [Is Power Of Two](bit_manipulation/is_power_of_two.py) + * [Largest Pow Of Two Le Num](bit_manipulation/largest_pow_of_two_le_num.py) * [Missing Number](bit_manipulation/missing_number.py) * [Numbers Different Signs](bit_manipulation/numbers_different_signs.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) @@ -322,6 +323,7 @@ * [Integer Partition](dynamic_programming/integer_partition.py) * [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py) * [Knapsack](dynamic_programming/knapsack.py) + * [Largest Divisible Subset](dynamic_programming/largest_divisible_subset.py) * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) * [Longest Common Substring](dynamic_programming/longest_common_substring.py) * [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py) @@ -460,6 +462,7 @@ ## Greedy Methods * [Fractional Knapsack](greedy_methods/fractional_knapsack.py) * [Fractional Knapsack 2](greedy_methods/fractional_knapsack_2.py) + * [Gas Station](greedy_methods/gas_station.py) * [Minimum Waiting Time](greedy_methods/minimum_waiting_time.py) * [Optimal Merge Pattern](greedy_methods/optimal_merge_pattern.py) @@ -542,6 +545,7 @@ * [Average Median](maths/average_median.py) * [Average Mode](maths/average_mode.py) * [Bailey Borwein Plouffe](maths/bailey_borwein_plouffe.py) + * [Base Neg2 Conversion](maths/base_neg2_conversion.py) * [Basic Maths](maths/basic_maths.py) * [Bell Numbers](maths/bell_numbers.py) * [Binary Exp Mod](maths/binary_exp_mod.py) @@ -657,7 +661,6 @@ * [P Series](maths/series/p_series.py) * [Sieve Of Eratosthenes](maths/sieve_of_eratosthenes.py) * [Sigmoid](maths/sigmoid.py) - * [Sigmoid Linear Unit](maths/sigmoid_linear_unit.py) * [Signum](maths/signum.py) * [Simpson Rule](maths/simpson_rule.py) * [Simultaneous Linear Equation Solver](maths/simultaneous_linear_equation_solver.py) @@ -716,6 +719,7 @@ * [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py) * [Rectified Linear Unit](neural_network/activation_functions/rectified_linear_unit.py) * [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py) + * [Sigmoid Linear Unit](neural_network/activation_functions/sigmoid_linear_unit.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Perceptron](neural_network/perceptron.py) @@ -1180,6 +1184,7 @@ * [Naive String Search](strings/naive_string_search.py) * [Ngram](strings/ngram.py) * [Palindrome](strings/palindrome.py) + * [Pig Latin](strings/pig_latin.py) * [Prefix Function](strings/prefix_function.py) * [Rabin Karp](strings/rabin_karp.py) * [Remove Duplicate](strings/remove_duplicate.py) diff --git a/data_structures/linked_list/print_reverse.py b/data_structures/linked_list/print_reverse.py index f83d5607ffdd..a023745dee69 100644 --- a/data_structures/linked_list/print_reverse.py +++ b/data_structures/linked_list/print_reverse.py @@ -1,22 +1,91 @@ from __future__ import annotations +from collections.abc import Iterable, Iterator +from dataclasses import dataclass + +@dataclass class Node: - def __init__(self, data=None): - self.data = data - self.next = None + data: int + next_node: Node | None = None + + +class LinkedList: + """A class to represent a Linked List. + Use a tail pointer to speed up the append() operation. + """ + + def __init__(self) -> None: + """Initialize a LinkedList with the head node set to None. + >>> linked_list = LinkedList() + >>> (linked_list.head, linked_list.tail) + (None, None) + """ + self.head: Node | None = None + self.tail: Node | None = None # Speeds up the append() operation + + def __iter__(self) -> Iterator[int]: + """Iterate the LinkedList yielding each Node's data. + >>> linked_list = LinkedList() + >>> items = (1, 2, 3, 4, 5) + >>> linked_list.extend(items) + >>> tuple(linked_list) == items + True + """ + node = self.head + while node: + yield node.data + node = node.next_node + + def __repr__(self) -> str: + """Returns a string representation of the LinkedList. + >>> linked_list = LinkedList() + >>> str(linked_list) + '' + >>> linked_list.append(1) + >>> str(linked_list) + '1' + >>> linked_list.extend([2, 3, 4, 5]) + >>> str(linked_list) + '1 -> 2 -> 3 -> 4 -> 5' + """ + return " -> ".join([str(data) for data in self]) - def __repr__(self): - """Returns a visual representation of the node and all its following nodes.""" - string_rep = [] - temp = self - while temp: - string_rep.append(f"{temp.data}") - temp = temp.next - return "->".join(string_rep) + def append(self, data: int) -> None: + """Appends a new node with the given data to the end of the LinkedList. + >>> linked_list = LinkedList() + >>> str(linked_list) + '' + >>> linked_list.append(1) + >>> str(linked_list) + '1' + >>> linked_list.append(2) + >>> str(linked_list) + '1 -> 2' + """ + if self.tail: + self.tail.next_node = self.tail = Node(data) + else: + self.head = self.tail = Node(data) + def extend(self, items: Iterable[int]) -> None: + """Appends each item to the end of the LinkedList. + >>> linked_list = LinkedList() + >>> linked_list.extend([]) + >>> str(linked_list) + '' + >>> linked_list.extend([1, 2]) + >>> str(linked_list) + '1 -> 2' + >>> linked_list.extend([3,4]) + >>> str(linked_list) + '1 -> 2 -> 3 -> 4' + """ + for item in items: + self.append(item) -def make_linked_list(elements_list: list): + +def make_linked_list(elements_list: Iterable[int]) -> LinkedList: """Creates a Linked List from the elements of the given sequence (list/tuple) and returns the head of the Linked List. >>> make_linked_list([]) @@ -28,43 +97,30 @@ def make_linked_list(elements_list: list): >>> make_linked_list(['abc']) abc >>> make_linked_list([7, 25]) - 7->25 + 7 -> 25 """ if not elements_list: raise Exception("The Elements List is empty") - 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 + linked_list = LinkedList() + linked_list.extend(elements_list) + return linked_list -def print_reverse(head_node: Node) -> None: +def in_reverse(linked_list: LinkedList) -> str: """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 + >>> in_reverse(LinkedList()) + '' + >>> in_reverse(make_linked_list([69, 88, 73])) + '73 <- 88 <- 69' """ - if head_node is not None and isinstance(head_node, Node): - print_reverse(head_node.next) - print(head_node.data) + return " <- ".join(str(line) for line in reversed(tuple(linked_list))) -def main(): +if __name__ == "__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() + linked_list = make_linked_list((14, 52, 14, 12, 43)) + print(f"Linked List: {linked_list}") + print(f"Reverse List: {in_reverse(linked_list)}") From 7f94a73eec45edfd215e8f07148c9c657b4e4b89 Mon Sep 17 00:00:00 2001 From: Marek Mazij <112333347+Mrk-Mzj@users.noreply.github.com> Date: Fri, 6 Oct 2023 00:05:23 +0200 Subject: [PATCH 2386/2908] camelCase to snake_case conversion - Fixes #9726 (#9727) * First commit camel case to snake case conversion algorithm, including numbers * code modified to not use regex --- strings/camel_case_to_snake_case.py | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 strings/camel_case_to_snake_case.py diff --git a/strings/camel_case_to_snake_case.py b/strings/camel_case_to_snake_case.py new file mode 100644 index 000000000000..582907be2edb --- /dev/null +++ b/strings/camel_case_to_snake_case.py @@ -0,0 +1,60 @@ +def camel_to_snake_case(input_str: str) -> str: + """ + Transforms a camelCase (or PascalCase) string to snake_case + + >>> camel_to_snake_case("someRandomString") + 'some_random_string' + + >>> camel_to_snake_case("SomeRandomStr#ng") + 'some_random_str_ng' + + >>> camel_to_snake_case("123someRandom123String123") + '123_some_random_123_string_123' + + >>> camel_to_snake_case("123SomeRandom123String123") + '123_some_random_123_string_123' + + >>> camel_to_snake_case(123) + Traceback (most recent call last): + ... + ValueError: Expected string as input, found + + """ + + # check for invalid input type + if not isinstance(input_str, str): + msg = f"Expected string as input, found {type(input_str)}" + raise ValueError(msg) + + snake_str = "" + + for index, char in enumerate(input_str): + if char.isupper(): + snake_str += "_" + char.lower() + + # if char is lowercase but proceeded by a digit: + elif input_str[index - 1].isdigit() and char.islower(): + snake_str += "_" + char + + # if char is a digit proceeded by a letter: + elif input_str[index - 1].isalpha() and char.isnumeric(): + snake_str += "_" + char.lower() + + # if char is not alphanumeric: + elif not char.isalnum(): + snake_str += "_" + + else: + snake_str += char + + # remove leading underscore + if snake_str[0] == "_": + snake_str = snake_str[1:] + + return snake_str + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 13317e4f7f260f59e6e53595f802c9d12ec0db4a Mon Sep 17 00:00:00 2001 From: Akshay B Shetty <107768228+NinjaSoulPirate@users.noreply.github.com> Date: Fri, 6 Oct 2023 03:57:13 +0530 Subject: [PATCH 2387/2908] feat: :sparkles: calculating the resitance of resistor using color codes (#9874) --- electronics/resistor_color_code.py | 373 +++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 electronics/resistor_color_code.py diff --git a/electronics/resistor_color_code.py b/electronics/resistor_color_code.py new file mode 100644 index 000000000000..b0534b813def --- /dev/null +++ b/electronics/resistor_color_code.py @@ -0,0 +1,373 @@ +""" +Title : Calculating the resistance of a n band resistor using the color codes + +Description : + Resistors resist the flow of electrical current.Each one has a value that tells how + strongly it resists current flow.This value's unit is the ohm, often noted with the + Greek letter omega: Ω. + + The colored bands on a resistor can tell you everything you need to know about its + value and tolerance, as long as you understand how to read them. The order in which + the colors are arranged is very important, and each value of resistor has its own + unique combination. + + The color coding for resistors is an international standard that is defined in IEC + 60062. + + The number of bands present in a resistor varies from three to six. These represent + significant figures, multiplier, tolerance, reliability, and temperature coefficient + Each color used for a type of band has a value assigned to it. It is read from left + to right. + All resistors will have significant figures and multiplier bands. In a three band + resistor first two bands from the left represent significant figures and the third + represents the multiplier band. + + Significant figures - The number of significant figures band in a resistor can vary + from two to three. + Colors and values associated with significant figure bands - + (Black = 0, Brown = 1, Red = 2, Orange = 3, Yellow = 4, Green = 5, Blue = 6, + Violet = 7, Grey = 8, White = 9) + + Multiplier - There will be one multiplier band in a resistor. It is multiplied with + the significant figures obtained from previous bands. + Colors and values associated with multiplier band - + (Black = 100, Brown = 10^1, Red = 10^2, Orange = 10^3, Yellow = 10^4, Green = 10^5, + Blue = 10^6, Violet = 10^7, Grey = 10^8, White = 10^9, Gold = 10^-1, Silver = 10^-2) + Note that multiplier bands use Gold and Silver which are not used for significant + figure bands. + + Tolerance - The tolerance band is not always present. It can be seen in four band + resistors and above. This is a percentage by which the resistor value can vary. + Colors and values associated with tolerance band - + (Brown = 1%, Red = 2%, Orange = 0.05%, Yellow = 0.02%, Green = 0.5%,Blue = 0.25%, + Violet = 0.1%, Grey = 0.01%, Gold = 5%, Silver = 10%) + If no color is mentioned then by default tolerance is 20% + Note that tolerance band does not use Black and White colors. + + Temperature Coeffecient - Indicates the change in resistance of the component as + a function of ambient temperature in terms of ppm/K. + It is present in six band resistors. + Colors and values associated with Temperature coeffecient - + (Black = 250 ppm/K, Brown = 100 ppm/K, Red = 50 ppm/K, Orange = 15 ppm/K, + Yellow = 25 ppm/K, Green = 20 ppm/K, Blue = 10 ppm/K, Violet = 5 ppm/K, + Grey = 1 ppm/K) + Note that temperature coeffecient band does not use White, Gold, Silver colors. + +Sources : + https://www.calculator.net/resistor-calculator.html + https://learn.parallax.com/support/reference/resistor-color-codes + https://byjus.com/physics/resistor-colour-codes/ +""" +valid_colors: list = [ + "Black", + "Brown", + "Red", + "Orange", + "Yellow", + "Green", + "Blue", + "Violet", + "Grey", + "White", + "Gold", + "Silver", +] + +significant_figures_color_values: dict[str, int] = { + "Black": 0, + "Brown": 1, + "Red": 2, + "Orange": 3, + "Yellow": 4, + "Green": 5, + "Blue": 6, + "Violet": 7, + "Grey": 8, + "White": 9, +} + +multiplier_color_values: dict[str, float] = { + "Black": 10**0, + "Brown": 10**1, + "Red": 10**2, + "Orange": 10**3, + "Yellow": 10**4, + "Green": 10**5, + "Blue": 10**6, + "Violet": 10**7, + "Grey": 10**8, + "White": 10**9, + "Gold": 10**-1, + "Silver": 10**-2, +} + +tolerance_color_values: dict[str, float] = { + "Brown": 1, + "Red": 2, + "Orange": 0.05, + "Yellow": 0.02, + "Green": 0.5, + "Blue": 0.25, + "Violet": 0.1, + "Grey": 0.01, + "Gold": 5, + "Silver": 10, +} + +temperature_coeffecient_color_values: dict[str, int] = { + "Black": 250, + "Brown": 100, + "Red": 50, + "Orange": 15, + "Yellow": 25, + "Green": 20, + "Blue": 10, + "Violet": 5, + "Grey": 1, +} + +band_types: dict[int, dict[str, int]] = { + 3: {"significant": 2, "multiplier": 1}, + 4: {"significant": 2, "multiplier": 1, "tolerance": 1}, + 5: {"significant": 3, "multiplier": 1, "tolerance": 1}, + 6: {"significant": 3, "multiplier": 1, "tolerance": 1, "temp_coeffecient": 1}, +} + + +def get_significant_digits(colors: list) -> str: + """ + Function returns the digit associated with the color. Function takes a + list containing colors as input and returns digits as string + + >>> get_significant_digits(['Black','Blue']) + '06' + + >>> get_significant_digits(['Aqua','Blue']) + Traceback (most recent call last): + ... + ValueError: Aqua is not a valid color for significant figure bands + + """ + digit = "" + for color in colors: + if color not in significant_figures_color_values: + msg = f"{color} is not a valid color for significant figure bands" + raise ValueError(msg) + digit = digit + str(significant_figures_color_values[color]) + return str(digit) + + +def get_multiplier(color: str) -> float: + """ + Function returns the multiplier value associated with the color. + Function takes color as input and returns multiplier value + + >>> get_multiplier('Gold') + 0.1 + + >>> get_multiplier('Ivory') + Traceback (most recent call last): + ... + ValueError: Ivory is not a valid color for multiplier band + + """ + if color not in multiplier_color_values: + msg = f"{color} is not a valid color for multiplier band" + raise ValueError(msg) + return multiplier_color_values[color] + + +def get_tolerance(color: str) -> float: + """ + Function returns the tolerance value associated with the color. + Function takes color as input and returns tolerance value. + + >>> get_tolerance('Green') + 0.5 + + >>> get_tolerance('Indigo') + Traceback (most recent call last): + ... + ValueError: Indigo is not a valid color for tolerance band + + """ + if color not in tolerance_color_values: + msg = f"{color} is not a valid color for tolerance band" + raise ValueError(msg) + return tolerance_color_values[color] + + +def get_temperature_coeffecient(color: str) -> int: + """ + Function returns the temperature coeffecient value associated with the color. + Function takes color as input and returns temperature coeffecient value. + + >>> get_temperature_coeffecient('Yellow') + 25 + + >>> get_temperature_coeffecient('Cyan') + Traceback (most recent call last): + ... + ValueError: Cyan is not a valid color for temperature coeffecient band + + """ + if color not in temperature_coeffecient_color_values: + msg = f"{color} is not a valid color for temperature coeffecient band" + raise ValueError(msg) + return temperature_coeffecient_color_values[color] + + +def get_band_type_count(total_number_of_bands: int, type_of_band: str) -> int: + """ + Function returns the number of bands of a given type in a resistor with n bands + Function takes total_number_of_bands and type_of_band as input and returns + number of bands belonging to that type in the given resistor + + >>> get_band_type_count(3,'significant') + 2 + + >>> get_band_type_count(2,'significant') + Traceback (most recent call last): + ... + ValueError: 2 is not a valid number of bands + + >>> get_band_type_count(3,'sign') + Traceback (most recent call last): + ... + ValueError: sign is not valid for a 3 band resistor + + >>> get_band_type_count(3,'tolerance') + Traceback (most recent call last): + ... + ValueError: tolerance is not valid for a 3 band resistor + + >>> get_band_type_count(5,'temp_coeffecient') + Traceback (most recent call last): + ... + ValueError: temp_coeffecient is not valid for a 5 band resistor + + """ + if total_number_of_bands not in band_types: + msg = f"{total_number_of_bands} is not a valid number of bands" + raise ValueError(msg) + if type_of_band not in band_types[total_number_of_bands]: + msg = f"{type_of_band} is not valid for a {total_number_of_bands} band resistor" + raise ValueError(msg) + return band_types[total_number_of_bands][type_of_band] + + +def check_validity(number_of_bands: int, colors: list) -> bool: + """ + Function checks if the input provided is valid or not. + Function takes number_of_bands and colors as input and returns + True if it is valid + + >>> check_validity(3, ["Black","Blue","Orange"]) + True + + >>> check_validity(4, ["Black","Blue","Orange"]) + Traceback (most recent call last): + ... + ValueError: Expecting 4 colors, provided 3 colors + + >>> check_validity(3, ["Cyan","Red","Yellow"]) + Traceback (most recent call last): + ... + ValueError: Cyan is not a valid color + + """ + if number_of_bands >= 3 and number_of_bands <= 6: + if number_of_bands == len(colors): + for color in colors: + if color not in valid_colors: + msg = f"{color} is not a valid color" + raise ValueError(msg) + return True + else: + msg = f"Expecting {number_of_bands} colors, provided {len(colors)} colors" + raise ValueError(msg) + else: + msg = "Invalid number of bands. Resistor bands must be 3 to 6" + raise ValueError(msg) + + +def calculate_resistance(number_of_bands: int, color_code_list: list) -> dict: + """ + Function calculates the total resistance of the resistor using the color codes. + Function takes number_of_bands, color_code_list as input and returns + resistance + + >>> calculate_resistance(3, ["Black","Blue","Orange"]) + {'resistance': '6000Ω ±20% '} + + >>> calculate_resistance(4, ["Orange","Green","Blue","Gold"]) + {'resistance': '35000000Ω ±5% '} + + >>> calculate_resistance(5, ["Violet","Brown","Grey","Silver","Green"]) + {'resistance': '7.18Ω ±0.5% '} + + >>> calculate_resistance(6, ["Red","Green","Blue","Yellow","Orange","Grey"]) + {'resistance': '2560000Ω ±0.05% 1 ppm/K'} + + >>> calculate_resistance(0, ["Violet","Brown","Grey","Silver","Green"]) + Traceback (most recent call last): + ... + ValueError: Invalid number of bands. Resistor bands must be 3 to 6 + + >>> calculate_resistance(4, ["Violet","Brown","Grey","Silver","Green"]) + Traceback (most recent call last): + ... + ValueError: Expecting 4 colors, provided 5 colors + + >>> calculate_resistance(4, ["Violet","Silver","Brown","Grey"]) + Traceback (most recent call last): + ... + ValueError: Silver is not a valid color for significant figure bands + + >>> calculate_resistance(4, ["Violet","Blue","Lime","Grey"]) + Traceback (most recent call last): + ... + ValueError: Lime is not a valid color + + """ + is_valid = check_validity(number_of_bands, color_code_list) + if is_valid: + number_of_significant_bands = get_band_type_count( + number_of_bands, "significant" + ) + significant_colors = color_code_list[:number_of_significant_bands] + significant_digits = int(get_significant_digits(significant_colors)) + multiplier_color = color_code_list[number_of_significant_bands] + multiplier = get_multiplier(multiplier_color) + if number_of_bands == 3: + tolerance_color = None + else: + tolerance_color = color_code_list[number_of_significant_bands + 1] + tolerance = ( + 20 if tolerance_color is None else get_tolerance(str(tolerance_color)) + ) + if number_of_bands != 6: + temperature_coeffecient_color = None + else: + temperature_coeffecient_color = color_code_list[ + number_of_significant_bands + 2 + ] + temperature_coeffecient = ( + 0 + if temperature_coeffecient_color is None + else get_temperature_coeffecient(str(temperature_coeffecient_color)) + ) + resisitance = significant_digits * multiplier + if temperature_coeffecient == 0: + answer = f"{resisitance}Ω ±{tolerance}% " + else: + answer = f"{resisitance}Ω ±{tolerance}% {temperature_coeffecient} ppm/K" + return {"resistance": answer} + else: + raise ValueError("Input is invalid") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b316a9612826905b963a465f0f02febaed761ccc Mon Sep 17 00:00:00 2001 From: Abul Hasan <33129246+haxkd@users.noreply.github.com> Date: Fri, 6 Oct 2023 04:15:10 +0530 Subject: [PATCH 2388/2908] Match a pattern and String using backtracking (#9861) * Fix: Issue 9588 * Fix: Issue 9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix: Issue 9588 * Fix: Issue #9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix: Issue #9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix: Issue #9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: issue #9793 * fix: issue #9793 * fix: issue #9588 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: issue #9844 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: issue #9844 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: issue #9844 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: issue #9844 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- backtracking/match_word_pattern.py | 61 ++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 backtracking/match_word_pattern.py diff --git a/backtracking/match_word_pattern.py b/backtracking/match_word_pattern.py new file mode 100644 index 000000000000..bfa9b1354d51 --- /dev/null +++ b/backtracking/match_word_pattern.py @@ -0,0 +1,61 @@ +def match_word_pattern(pattern: str, input_string: str) -> bool: + """ + Determine if a given pattern matches a string using backtracking. + + pattern: The pattern to match. + input_string: The string to match against the pattern. + return: True if the pattern matches the string, False otherwise. + + >>> match_word_pattern("aba", "GraphTreesGraph") + True + + >>> match_word_pattern("xyx", "PythonRubyPython") + True + + >>> match_word_pattern("GG", "PythonJavaPython") + False + """ + + def backtrack(pattern_index: int, str_index: int) -> bool: + """ + >>> backtrack(0, 0) + True + + >>> backtrack(0, 1) + True + + >>> backtrack(0, 4) + False + """ + if pattern_index == len(pattern) and str_index == len(input_string): + return True + if pattern_index == len(pattern) or str_index == len(input_string): + return False + char = pattern[pattern_index] + if char in pattern_map: + mapped_str = pattern_map[char] + if input_string.startswith(mapped_str, str_index): + return backtrack(pattern_index + 1, str_index + len(mapped_str)) + else: + return False + for end in range(str_index + 1, len(input_string) + 1): + substr = input_string[str_index:end] + if substr in str_map: + continue + pattern_map[char] = substr + str_map[substr] = char + if backtrack(pattern_index + 1, end): + return True + del pattern_map[char] + del str_map[substr] + return False + + pattern_map: dict[str, str] = {} + str_map: dict[str, str] = {} + return backtrack(0, 0) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From cd684fd94762c4df5529d19d1ede6fc927428815 Mon Sep 17 00:00:00 2001 From: Dean Bring Date: Thu, 5 Oct 2023 15:45:40 -0700 Subject: [PATCH 2389/2908] Added algorithm to deeply clone a graph (#9765) * Added algorithm to deeply clone a graph * Fixed file name and removed a function call * Removed nested function and fixed class parameter types * Fixed doctests * bug fix * Added class decorator * Updated doctests and fixed precommit errors * Cleaned up code * Simplified doctest * Added doctests * Code simplification --- graphs/deep_clone_graph.py | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 graphs/deep_clone_graph.py diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py new file mode 100644 index 000000000000..55678b4c01ec --- /dev/null +++ b/graphs/deep_clone_graph.py @@ -0,0 +1,77 @@ +""" +LeetCode 133. Clone Graph +https://leetcode.com/problems/clone-graph/ + +Given a reference of a node in a connected undirected graph. + +Return a deep copy (clone) of the graph. + +Each node in the graph contains a value (int) and a list (List[Node]) of its +neighbors. +""" +from dataclasses import dataclass + + +@dataclass +class Node: + value: int = 0 + neighbors: list["Node"] | None = None + + def __post_init__(self) -> None: + """ + >>> Node(3).neighbors + [] + """ + self.neighbors = self.neighbors or [] + + def __hash__(self) -> int: + """ + >>> hash(Node(3)) != 0 + True + """ + return id(self) + + +def clone_graph(node: Node | None) -> Node | None: + """ + This function returns a clone of a connected undirected graph. + >>> clone_graph(Node(1)) + Node(value=1, neighbors=[]) + >>> clone_graph(Node(1, [Node(2)])) + Node(value=1, neighbors=[Node(value=2, neighbors=[])]) + >>> clone_graph(None) is None + True + """ + if not node: + return None + + originals_to_clones = {} # map nodes to clones + + stack = [node] + + while stack: + original = stack.pop() + + if original in originals_to_clones: + continue + + originals_to_clones[original] = Node(original.value) + + stack.extend(original.neighbors or []) + + for original, clone in originals_to_clones.items(): + for neighbor in original.neighbors or []: + cloned_neighbor = originals_to_clones[neighbor] + + if not clone.neighbors: + clone.neighbors = [] + + clone.neighbors.append(cloned_neighbor) + + return originals_to_clones[node] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9200c64464492117bff792f1f43b19050070af4a Mon Sep 17 00:00:00 2001 From: Aroson <74296409+Aroson1@users.noreply.github.com> Date: Fri, 6 Oct 2023 04:46:51 +0530 Subject: [PATCH 2390/2908] Added Wheatstone Bridge Algorithm (#9872) * Add files via upload * Update wheatstone_bridge.py * Update wheatstone_bridge.py --- electronics/wheatstone_bridge.py | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 electronics/wheatstone_bridge.py diff --git a/electronics/wheatstone_bridge.py b/electronics/wheatstone_bridge.py new file mode 100644 index 000000000000..3529a09339c4 --- /dev/null +++ b/electronics/wheatstone_bridge.py @@ -0,0 +1,41 @@ +# https://en.wikipedia.org/wiki/Wheatstone_bridge +from __future__ import annotations + + +def wheatstone_solver( + resistance_1: float, resistance_2: float, resistance_3: float +) -> float: + """ + This function can calculate the unknown resistance in an wheatstone network, + given that the three other resistances in the network are known. + The formula to calculate the same is: + + --------------- + |Rx=(R2/R1)*R3| + --------------- + + Usage examples: + >>> wheatstone_solver(resistance_1=2, resistance_2=4, resistance_3=5) + 10.0 + >>> wheatstone_solver(resistance_1=356, resistance_2=234, resistance_3=976) + 641.5280898876405 + >>> wheatstone_solver(resistance_1=2, resistance_2=-1, resistance_3=2) + Traceback (most recent call last): + ... + ValueError: All resistance values must be positive + >>> wheatstone_solver(resistance_1=0, resistance_2=0, resistance_3=2) + Traceback (most recent call last): + ... + ValueError: All resistance values must be positive + """ + + if resistance_1 <= 0 or resistance_2 <= 0 or resistance_3 <= 0: + raise ValueError("All resistance values must be positive") + else: + return float((resistance_2 / resistance_1) * resistance_3) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 19fc788197474f75c56cc3755582cc583be9e52f Mon Sep 17 00:00:00 2001 From: ojas wani <52542740+ojas-wani@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:43:45 -0700 Subject: [PATCH 2391/2908] added laplacian_filter file (#9783) * added laplacian_filter file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * required changes to laplacian file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update laplacian_filter.py * update laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * changed laplacian_filter.py * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update laplacian_filter.py * Add a test --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../filters/laplacian_filter.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 digital_image_processing/filters/laplacian_filter.py diff --git a/digital_image_processing/filters/laplacian_filter.py b/digital_image_processing/filters/laplacian_filter.py new file mode 100644 index 000000000000..69b9616e4d30 --- /dev/null +++ b/digital_image_processing/filters/laplacian_filter.py @@ -0,0 +1,81 @@ +# @Author : ojas-wani +# @File : laplacian_filter.py +# @Date : 10/04/2023 + +import numpy as np +from cv2 import ( + BORDER_DEFAULT, + COLOR_BGR2GRAY, + CV_64F, + cvtColor, + filter2D, + imread, + imshow, + waitKey, +) + +from digital_image_processing.filters.gaussian_filter import gaussian_filter + + +def my_laplacian(src: np.ndarray, ksize: int) -> np.ndarray: + """ + :param src: the source image, which should be a grayscale or color image. + :param ksize: the size of the kernel used to compute the Laplacian filter, + which can be 1, 3, 5, or 7. + + >>> my_laplacian(src=np.array([]), ksize=0) + Traceback (most recent call last): + ... + ValueError: ksize must be in (1, 3, 5, 7) + """ + kernels = { + 1: np.array([[0, -1, 0], [-1, 4, -1], [0, -1, 0]]), + 3: np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]]), + 5: np.array( + [ + [0, 0, -1, 0, 0], + [0, -1, -2, -1, 0], + [-1, -2, 16, -2, -1], + [0, -1, -2, -1, 0], + [0, 0, -1, 0, 0], + ] + ), + 7: np.array( + [ + [0, 0, 0, -1, 0, 0, 0], + [0, 0, -2, -3, -2, 0, 0], + [0, -2, -7, -10, -7, -2, 0], + [-1, -3, -10, 68, -10, -3, -1], + [0, -2, -7, -10, -7, -2, 0], + [0, 0, -2, -3, -2, 0, 0], + [0, 0, 0, -1, 0, 0, 0], + ] + ), + } + if ksize not in kernels: + msg = f"ksize must be in {tuple(kernels)}" + raise ValueError(msg) + + # Apply the Laplacian kernel using convolution + return filter2D( + src, CV_64F, kernels[ksize], 0, borderType=BORDER_DEFAULT, anchor=(0, 0) + ) + + +if __name__ == "__main__": + # read original image + img = imread(r"../image_data/lena.jpg") + + # turn image in gray scale value + gray = cvtColor(img, COLOR_BGR2GRAY) + + # Applying gaussian filter + blur_image = gaussian_filter(gray, 3, sigma=1) + + # Apply multiple Kernel to detect edges + laplacian_image = my_laplacian(ksize=3, src=blur_image) + + imshow("Original image", img) + imshow("Detected edges using laplacian filter", laplacian_image) + + waitKey(0) From 17af6444497a64dbe803904e2ef27d0e2a280f8c Mon Sep 17 00:00:00 2001 From: JeevaRamanathan <64531160+JeevaRamanathan@users.noreply.github.com> Date: Fri, 6 Oct 2023 05:30:58 +0530 Subject: [PATCH 2392/2908] Symmetric tree (#9871) * symmectric tree * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed trailing spaces * escape sequence fix * added return type * added class * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wordings fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added static method * added type * added static method * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wordings fix * testcase added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * testcase added for mirror function * testcase added for mirror function * made the requested changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * made the requested changes * doc test added for symmetric, asymmetric * Update symmetric_tree.py --------- Co-authored-by: jeevaramanthan.m Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/binary_tree/symmetric_tree.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 data_structures/binary_tree/symmetric_tree.py diff --git a/data_structures/binary_tree/symmetric_tree.py b/data_structures/binary_tree/symmetric_tree.py new file mode 100644 index 000000000000..331a25849c1c --- /dev/null +++ b/data_structures/binary_tree/symmetric_tree.py @@ -0,0 +1,101 @@ +""" +Given the root of a binary tree, check whether it is a mirror of itself +(i.e., symmetric around its center). + +Leetcode reference: https://leetcode.com/problems/symmetric-tree/ +""" +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class Node: + """ + A Node has data variable and pointers to Nodes to its left and right. + """ + + data: int + left: Node | None = None + right: Node | None = None + + +def make_symmetric_tree() -> Node: + r""" + Create a symmetric tree for testing. + The tree looks like this: + 1 + / \ + 2 2 + / \ / \ + 3 4 4 3 + """ + root = Node(1) + root.left = Node(2) + root.right = Node(2) + root.left.left = Node(3) + root.left.right = Node(4) + root.right.left = Node(4) + root.right.right = Node(3) + return root + + +def make_asymmetric_tree() -> Node: + r""" + Create a asymmetric tree for testing. + The tree looks like this: + 1 + / \ + 2 2 + / \ / \ + 3 4 3 4 + """ + root = Node(1) + root.left = Node(2) + root.right = Node(2) + root.left.left = Node(3) + root.left.right = Node(4) + root.right.left = Node(3) + root.right.right = Node(4) + return root + + +def is_symmetric_tree(tree: Node) -> bool: + """ + Test cases for is_symmetric_tree function + >>> is_symmetric_tree(make_symmetric_tree()) + True + >>> is_symmetric_tree(make_asymmetric_tree()) + False + """ + if tree: + return is_mirror(tree.left, tree.right) + return True # An empty tree is considered symmetric. + + +def is_mirror(left: Node | None, right: Node | None) -> bool: + """ + >>> tree1 = make_symmetric_tree() + >>> tree1.right.right = Node(3) + >>> is_mirror(tree1.left, tree1.right) + True + >>> tree2 = make_asymmetric_tree() + >>> is_mirror(tree2.left, tree2.right) + False + """ + if left is None and right is None: + # Both sides are empty, which is symmetric. + return True + if left is None or right is None: + # One side is empty while the other is not, which is not symmetric. + return False + if left.data == right.data: + # The values match, so check the subtree + return is_mirror(left.left, right.right) and is_mirror(left.right, right.left) + return False + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From d0c54acd75cedf14cff353869482a0487fea1697 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 6 Oct 2023 04:31:11 +0200 Subject: [PATCH 2393/2908] Use dataclasses in singly_linked_list.py (#9886) --- DIRECTORY.md | 7 + .../linked_list/singly_linked_list.py | 151 ++++++++++-------- 2 files changed, 93 insertions(+), 65 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index c199a4329202..a975b9264be0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -25,6 +25,7 @@ * [Combination Sum](backtracking/combination_sum.py) * [Hamiltonian Cycle](backtracking/hamiltonian_cycle.py) * [Knight Tour](backtracking/knight_tour.py) + * [Match Word Pattern](backtracking/match_word_pattern.py) * [Minimax](backtracking/minimax.py) * [N Queens](backtracking/n_queens.py) * [N Queens Math](backtracking/n_queens_math.py) @@ -199,6 +200,7 @@ * [Red Black Tree](data_structures/binary_tree/red_black_tree.py) * [Segment Tree](data_structures/binary_tree/segment_tree.py) * [Segment Tree Other](data_structures/binary_tree/segment_tree_other.py) + * [Symmetric Tree](data_structures/binary_tree/symmetric_tree.py) * [Treap](data_structures/binary_tree/treap.py) * [Wavelet Tree](data_structures/binary_tree/wavelet_tree.py) * Disjoint Set @@ -277,6 +279,7 @@ * [Convolve](digital_image_processing/filters/convolve.py) * [Gabor Filter](digital_image_processing/filters/gabor_filter.py) * [Gaussian Filter](digital_image_processing/filters/gaussian_filter.py) + * [Laplacian Filter](digital_image_processing/filters/laplacian_filter.py) * [Local Binary Pattern](digital_image_processing/filters/local_binary_pattern.py) * [Median Filter](digital_image_processing/filters/median_filter.py) * [Sobel Filter](digital_image_processing/filters/sobel_filter.py) @@ -365,8 +368,10 @@ * [Ind Reactance](electronics/ind_reactance.py) * [Ohms Law](electronics/ohms_law.py) * [Real And Reactive Power](electronics/real_and_reactive_power.py) + * [Resistor Color Code](electronics/resistor_color_code.py) * [Resistor Equivalence](electronics/resistor_equivalence.py) * [Resonant Frequency](electronics/resonant_frequency.py) + * [Wheatstone Bridge](electronics/wheatstone_bridge.py) ## File Transfer * [Receive File](file_transfer/receive_file.py) @@ -415,6 +420,7 @@ * [Check Bipartite Graph Dfs](graphs/check_bipartite_graph_dfs.py) * [Check Cycle](graphs/check_cycle.py) * [Connected Components](graphs/connected_components.py) + * [Deep Clone Graph](graphs/deep_clone_graph.py) * [Depth First Search](graphs/depth_first_search.py) * [Depth First Search 2](graphs/depth_first_search_2.py) * [Dijkstra](graphs/dijkstra.py) @@ -1159,6 +1165,7 @@ * [Autocomplete Using Trie](strings/autocomplete_using_trie.py) * [Barcode Validator](strings/barcode_validator.py) * [Boyer Moore Search](strings/boyer_moore_search.py) + * [Camel Case To Snake Case](strings/camel_case_to_snake_case.py) * [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py) * [Capitalize](strings/capitalize.py) * [Check Anagrams](strings/check_anagrams.py) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index f4b2ddce12d7..2c6713a47ad9 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -1,27 +1,38 @@ +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass from typing import Any +@dataclass class Node: - def __init__(self, data: Any): - """ - Create and initialize Node class instance. - >>> Node(20) - Node(20) - >>> Node("Hello, world!") - Node(Hello, world!) - >>> Node(None) - Node(None) - >>> Node(True) - Node(True) - """ - self.data = data - self.next = None + """ + Create and initialize Node class instance. + >>> Node(20) + Node(20) + >>> Node("Hello, world!") + Node(Hello, world!) + >>> Node(None) + Node(None) + >>> Node(True) + Node(True) + """ + + data: Any + next_node: Node | None = None def __repr__(self) -> str: """ Get the string representation of this node. >>> Node(10).__repr__() 'Node(10)' + >>> repr(Node(10)) + 'Node(10)' + >>> str(Node(10)) + 'Node(10)' + >>> Node(10) + Node(10) """ return f"Node({self.data})" @@ -31,10 +42,12 @@ def __init__(self): """ Create and initialize LinkedList class instance. >>> linked_list = LinkedList() + >>> linked_list.head is None + True """ self.head = None - def __iter__(self) -> Any: + def __iter__(self) -> Iterator[Any]: """ This function is intended for iterators to access and iterate through data inside linked list. @@ -51,7 +64,7 @@ def __iter__(self) -> Any: node = self.head while node: yield node.data - node = node.next + node = node.next_node def __len__(self) -> int: """ @@ -81,9 +94,16 @@ def __repr__(self) -> str: >>> linked_list.insert_tail(1) >>> linked_list.insert_tail(3) >>> linked_list.__repr__() - '1->3' + '1 -> 3' + >>> repr(linked_list) + '1 -> 3' + >>> str(linked_list) + '1 -> 3' + >>> linked_list.insert_tail(5) + >>> f"{linked_list}" + '1 -> 3 -> 5' """ - return "->".join([str(item) for item in self]) + return " -> ".join([str(item) for item in self]) def __getitem__(self, index: int) -> Any: """ @@ -134,7 +154,7 @@ def __setitem__(self, index: int, data: Any) -> None: raise ValueError("list index out of range.") current = self.head for _ in range(index): - current = current.next + current = current.next_node current.data = data def insert_tail(self, data: Any) -> None: @@ -146,10 +166,10 @@ def insert_tail(self, data: Any) -> None: tail >>> linked_list.insert_tail("tail_2") >>> linked_list - tail->tail_2 + tail -> tail_2 >>> linked_list.insert_tail("tail_3") >>> linked_list - tail->tail_2->tail_3 + tail -> tail_2 -> tail_3 """ self.insert_nth(len(self), data) @@ -162,10 +182,10 @@ def insert_head(self, data: Any) -> None: head >>> linked_list.insert_head("head_2") >>> linked_list - head_2->head + head_2 -> head >>> linked_list.insert_head("head_3") >>> linked_list - head_3->head_2->head + head_3 -> head_2 -> head """ self.insert_nth(0, data) @@ -177,13 +197,13 @@ def insert_nth(self, index: int, data: Any) -> None: >>> linked_list.insert_tail("second") >>> linked_list.insert_tail("third") >>> linked_list - first->second->third + first -> second -> third >>> linked_list.insert_nth(1, "fourth") >>> linked_list - first->fourth->second->third + first -> fourth -> second -> third >>> linked_list.insert_nth(3, "fifth") >>> linked_list - first->fourth->second->fifth->third + first -> fourth -> second -> fifth -> third """ if not 0 <= index <= len(self): raise IndexError("list index out of range") @@ -191,14 +211,14 @@ def insert_nth(self, index: int, data: Any) -> None: if self.head is None: self.head = new_node elif index == 0: - new_node.next = self.head # link new_node to head + new_node.next_node = self.head # link new_node to head self.head = new_node else: temp = self.head for _ in range(index - 1): - temp = temp.next - new_node.next = temp.next - temp.next = new_node + temp = temp.next_node + new_node.next_node = temp.next_node + temp.next_node = new_node def print_list(self) -> None: # print every node data """ @@ -208,7 +228,7 @@ def print_list(self) -> None: # print every node data >>> linked_list.insert_tail("second") >>> linked_list.insert_tail("third") >>> linked_list - first->second->third + first -> second -> third """ print(self) @@ -221,11 +241,11 @@ def delete_head(self) -> Any: >>> linked_list.insert_tail("second") >>> linked_list.insert_tail("third") >>> linked_list - first->second->third + first -> second -> third >>> linked_list.delete_head() 'first' >>> linked_list - second->third + second -> third >>> linked_list.delete_head() 'second' >>> linked_list @@ -248,11 +268,11 @@ def delete_tail(self) -> Any: # delete from tail >>> linked_list.insert_tail("second") >>> linked_list.insert_tail("third") >>> linked_list - first->second->third + first -> second -> third >>> linked_list.delete_tail() 'third' >>> linked_list - first->second + first -> second >>> linked_list.delete_tail() 'second' >>> linked_list @@ -275,11 +295,11 @@ def delete_nth(self, index: int = 0) -> Any: >>> linked_list.insert_tail("second") >>> linked_list.insert_tail("third") >>> linked_list - first->second->third + first -> second -> third >>> linked_list.delete_nth(1) # delete middle 'second' >>> linked_list - first->third + first -> third >>> linked_list.delete_nth(5) # this raises error Traceback (most recent call last): ... @@ -293,13 +313,13 @@ def delete_nth(self, index: int = 0) -> Any: raise IndexError("List index out of range.") delete_node = self.head # default first node if index == 0: - self.head = self.head.next + self.head = self.head.next_node else: temp = self.head for _ in range(index - 1): - temp = temp.next - delete_node = temp.next - temp.next = temp.next.next + temp = temp.next_node + delete_node = temp.next_node + temp.next_node = temp.next_node.next_node return delete_node.data def is_empty(self) -> bool: @@ -322,22 +342,22 @@ def reverse(self) -> None: >>> linked_list.insert_tail("second") >>> linked_list.insert_tail("third") >>> linked_list - first->second->third + first -> second -> third >>> linked_list.reverse() >>> linked_list - third->second->first + third -> second -> first """ prev = None current = self.head while current: # Store the current node's next node. - next_node = current.next - # Make the current node's next point backwards - current.next = prev + next_node = current.next_node + # Make the current node's next_node point backwards + current.next_node = prev # Make the previous node be the current node prev = current - # Make the current node the next node (to progress iteration) + # Make the current node the next_node node (to progress iteration) current = next_node # Return prev in order to put the head at the end self.head = prev @@ -366,17 +386,17 @@ def test_singly_linked_list() -> None: 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)) + assert str(linked_list) == " -> ".join(str(i) for i in range(1, 11)) linked_list.insert_head(0) linked_list.insert_tail(11) - assert str(linked_list) == "->".join(str(i) for i in range(12)) + assert str(linked_list) == " -> ".join(str(i) for i in range(12)) 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 str(linked_list) == " -> ".join(str(i) for i in range(1, 10)) assert all(linked_list[i] == i + 1 for i in range(9)) is True @@ -385,7 +405,7 @@ def test_singly_linked_list() -> None: assert all(linked_list[i] == -i for i in range(9)) is True linked_list.reverse() - assert str(linked_list) == "->".join(str(i) for i in range(-8, 1)) + assert str(linked_list) == " -> ".join(str(i) for i in range(-8, 1)) def test_singly_linked_list_2() -> None: @@ -417,56 +437,57 @@ def test_singly_linked_list_2() -> None: # Check if it's empty or not assert linked_list.is_empty() is False assert ( - str(linked_list) == "-9->100->Node(77345112)->dlrow olleH->7->5555->0->" - "-192.55555->Hello, world!->77.9->Node(10)->None->None->12.2" + str(linked_list) + == "-9 -> 100 -> Node(77345112) -> dlrow olleH -> 7 -> 5555 -> " + "0 -> -192.55555 -> Hello, world! -> 77.9 -> Node(10) -> None -> None -> 12.2" ) # Delete the head result = linked_list.delete_head() assert result == -9 assert ( - str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->" - "Hello, world!->77.9->Node(10)->None->None->12.2" + str(linked_list) == "100 -> Node(77345112) -> dlrow olleH -> 7 -> 5555 -> 0 -> " + "-192.55555 -> Hello, world! -> 77.9 -> Node(10) -> None -> None -> 12.2" ) # Delete the tail result = linked_list.delete_tail() assert result == 12.2 assert ( - str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->" - "Hello, world!->77.9->Node(10)->None->None" + str(linked_list) == "100 -> Node(77345112) -> dlrow olleH -> 7 -> 5555 -> 0 -> " + "-192.55555 -> Hello, world! -> 77.9 -> Node(10) -> None -> None" ) # Delete a node in specific location in linked list result = linked_list.delete_nth(10) assert result is None assert ( - str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->" - "Hello, world!->77.9->Node(10)->None" + str(linked_list) == "100 -> Node(77345112) -> dlrow olleH -> 7 -> 5555 -> 0 -> " + "-192.55555 -> Hello, world! -> 77.9 -> Node(10) -> None" ) # Add a Node instance to its head linked_list.insert_head(Node("Hello again, world!")) assert ( str(linked_list) - == "Node(Hello again, world!)->100->Node(77345112)->dlrow olleH->" - "7->5555->0->-192.55555->Hello, world!->77.9->Node(10)->None" + == "Node(Hello again, world!) -> 100 -> Node(77345112) -> dlrow olleH -> " + "7 -> 5555 -> 0 -> -192.55555 -> Hello, world! -> 77.9 -> Node(10) -> None" ) # Add None to its tail linked_list.insert_tail(None) assert ( str(linked_list) - == "Node(Hello again, world!)->100->Node(77345112)->dlrow olleH->" - "7->5555->0->-192.55555->Hello, world!->77.9->Node(10)->None->None" + == "Node(Hello again, world!) -> 100 -> Node(77345112) -> dlrow olleH -> 7 -> " + "5555 -> 0 -> -192.55555 -> Hello, world! -> 77.9 -> Node(10) -> None -> None" ) # Reverse the linked list linked_list.reverse() assert ( str(linked_list) - == "None->None->Node(10)->77.9->Hello, world!->-192.55555->0->5555->" - "7->dlrow olleH->Node(77345112)->100->Node(Hello again, world!)" + == "None -> None -> Node(10) -> 77.9 -> Hello, world! -> -192.55555 -> 0 -> " + "5555 -> 7 -> dlrow olleH -> Node(77345112) -> 100 -> Node(Hello again, world!)" ) From 795e97e87f6760a693769097613ace56a6addc8d Mon Sep 17 00:00:00 2001 From: Sarvjeet Singh <63469455+aazad20@users.noreply.github.com> Date: Fri, 6 Oct 2023 19:19:34 +0530 Subject: [PATCH 2394/2908] Added Majority Voting Algorithm (#9866) * Create MajorityVoteAlgorithm.py * Update and rename MajorityVoteAlgorithm.py to majorityvotealgorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename majorityvotealgorithm.py to majority_vote_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update majority_vote_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update majority_vote_algorithm.py * Update majority_vote_algorithm.py * Update other/majority_vote_algorithm.py Co-authored-by: Christian Clauss * renaming variables majority_vote_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update majority_vote_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update majority_vote_algorithm.py * Update majority_vote_algorithm.py * Update majority_vote_algorithm.py * Update majority_vote_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update other/majority_vote_algorithm.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update other/majority_vote_algorithm.py Co-authored-by: Christian Clauss * adding more testcases majority_vote_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update majority_vote_algorithm.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update majority_vote_algorithm.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- other/majority_vote_algorithm.py | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 other/majority_vote_algorithm.py diff --git a/other/majority_vote_algorithm.py b/other/majority_vote_algorithm.py new file mode 100644 index 000000000000..ab8b386dd2e5 --- /dev/null +++ b/other/majority_vote_algorithm.py @@ -0,0 +1,37 @@ +""" +This is Booyer-Moore Majority Vote Algorithm. The problem statement goes like this: +Given an integer array of size n, find all elements that appear more than ⌊ n/k ⌋ times. +We have to solve in O(n) time and O(1) Space. +URL : https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm +""" +from collections import Counter + + +def majority_vote(votes: list[int], votes_needed_to_win: int) -> list[int]: + """ + >>> majority_vote([1, 2, 2, 3, 1, 3, 2], 3) + [2] + >>> majority_vote([1, 2, 2, 3, 1, 3, 2], 2) + [] + >>> majority_vote([1, 2, 2, 3, 1, 3, 2], 4) + [1, 2, 3] + """ + majority_candidate_counter: Counter[int] = Counter() + for vote in votes: + majority_candidate_counter[vote] += 1 + if len(majority_candidate_counter) == votes_needed_to_win: + majority_candidate_counter -= Counter(set(majority_candidate_counter)) + majority_candidate_counter = Counter( + vote for vote in votes if vote in majority_candidate_counter + ) + return [ + vote + for vote in majority_candidate_counter + if majority_candidate_counter[vote] > len(votes) / votes_needed_to_win + ] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 995c5533c645250c120b11f0eddc53909fc3d012 Mon Sep 17 00:00:00 2001 From: fxdup <47389903+fxdup@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:46:58 -0400 Subject: [PATCH 2395/2908] Consolidate gamma (#9769) * refactor(gamma): Append _iterative to func name * refactor(gamma): Consolidate implementations * refactor(gamma): Redundant test function removal * Update maths/gamma.py --------- Co-authored-by: Tianyi Zheng --- maths/gamma.py | 91 ++++++++++++++++++++++++++++++++++------ maths/gamma_recursive.py | 77 ---------------------------------- 2 files changed, 79 insertions(+), 89 deletions(-) delete mode 100644 maths/gamma_recursive.py diff --git a/maths/gamma.py b/maths/gamma.py index d5debc58764b..822bbc74456f 100644 --- a/maths/gamma.py +++ b/maths/gamma.py @@ -1,35 +1,43 @@ +""" +Gamma function is a very useful tool in math and physics. +It helps calculating complex integral in a convenient way. +for more info: 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 +Python's Standard Library math.gamma() function overflows around gamma(171.624). +""" import math from numpy import inf from scipy.integrate import quad -def gamma(num: float) -> float: +def gamma_iterative(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) + Calculates the value of Gamma function of num + where num is either an integer (1, 2, 3..) or a half-integer (0.5, 1.5, 2.5 ...). + + >>> gamma_iterative(-1) Traceback (most recent call last): ... ValueError: math domain error - >>> gamma(0) + >>> gamma_iterative(0) Traceback (most recent call last): ... ValueError: math domain error - >>> gamma(9) + >>> gamma_iterative(9) 40320.0 >>> from math import gamma as math_gamma - >>> all(.99999999 < gamma(i) / math_gamma(i) <= 1.000000001 + >>> all(.99999999 < gamma_iterative(i) / math_gamma(i) <= 1.000000001 ... for i in range(1, 50)) True - >>> gamma(-1)/math_gamma(-1) <= 1.000000001 + >>> gamma_iterative(-1)/math_gamma(-1) <= 1.000000001 Traceback (most recent call last): ... ValueError: math domain error - >>> gamma(3.3) - math_gamma(3.3) <= 0.00000001 + >>> gamma_iterative(3.3) - math_gamma(3.3) <= 0.00000001 True """ if num <= 0: @@ -42,7 +50,66 @@ def integrand(x: float, z: float) -> float: return math.pow(x, z - 1) * math.exp(-x) +def gamma_recursive(num: float) -> float: + """ + Calculates the value of Gamma function of num + where num is either an integer (1, 2, 3..) or a half-integer (0.5, 1.5, 2.5 ...). + Implemented using recursion + Examples: + >>> from math import isclose, gamma as math_gamma + >>> gamma_recursive(0.5) + 1.7724538509055159 + >>> gamma_recursive(1) + 1.0 + >>> gamma_recursive(2) + 1.0 + >>> gamma_recursive(3.5) + 3.3233509704478426 + >>> gamma_recursive(171.5) + 9.483367566824795e+307 + >>> all(isclose(gamma_recursive(num), math_gamma(num)) + ... for num in (0.5, 2, 3.5, 171.5)) + True + >>> gamma_recursive(0) + Traceback (most recent call last): + ... + ValueError: math domain error + >>> gamma_recursive(-1.1) + Traceback (most recent call last): + ... + ValueError: math domain error + >>> gamma_recursive(-4) + Traceback (most recent call last): + ... + ValueError: math domain error + >>> gamma_recursive(172) + Traceback (most recent call last): + ... + OverflowError: math range error + >>> gamma_recursive(1.1) + Traceback (most recent call last): + ... + NotImplementedError: num must be an integer or a half-integer + """ + if num <= 0: + raise ValueError("math domain error") + if num > 171.5: + raise OverflowError("math range error") + elif num - int(num) not in (0, 0.5): + raise NotImplementedError("num must be an integer or a half-integer") + elif num == 0.5: + return math.sqrt(math.pi) + else: + return 1.0 if num == 1 else (num - 1) * gamma_recursive(num - 1) + + if __name__ == "__main__": from doctest import testmod testmod() + num = 1.0 + while num: + num = float(input("Gamma of: ")) + print(f"gamma_iterative({num}) = {gamma_iterative(num)}") + print(f"gamma_recursive({num}) = {gamma_recursive(num)}") + print("\nEnter 0 to exit...") diff --git a/maths/gamma_recursive.py b/maths/gamma_recursive.py deleted file mode 100644 index 3d6b8c5e8138..000000000000 --- a/maths/gamma_recursive.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Gamma function is a very useful tool in math and physics. -It helps calculating complex integral in a convenient way. -for more info: https://en.wikipedia.org/wiki/Gamma_function -Python's Standard Library math.gamma() function overflows around gamma(171.624). -""" -from math import pi, sqrt - - -def gamma(num: float) -> float: - """ - Calculates the value of Gamma function of num - where num is either an integer (1, 2, 3..) or a half-integer (0.5, 1.5, 2.5 ...). - Implemented using recursion - Examples: - >>> from math import isclose, gamma as math_gamma - >>> gamma(0.5) - 1.7724538509055159 - >>> gamma(2) - 1.0 - >>> gamma(3.5) - 3.3233509704478426 - >>> gamma(171.5) - 9.483367566824795e+307 - >>> all(isclose(gamma(num), math_gamma(num)) for num in (0.5, 2, 3.5, 171.5)) - True - >>> gamma(0) - Traceback (most recent call last): - ... - ValueError: math domain error - >>> gamma(-1.1) - Traceback (most recent call last): - ... - ValueError: math domain error - >>> gamma(-4) - Traceback (most recent call last): - ... - ValueError: math domain error - >>> gamma(172) - Traceback (most recent call last): - ... - OverflowError: math range error - >>> gamma(1.1) - Traceback (most recent call last): - ... - NotImplementedError: num must be an integer or a half-integer - """ - if num <= 0: - raise ValueError("math domain error") - if num > 171.5: - raise OverflowError("math range error") - elif num - int(num) not in (0, 0.5): - raise NotImplementedError("num must be an integer or a half-integer") - elif num == 0.5: - return sqrt(pi) - else: - return 1.0 if num == 1 else (num - 1) * gamma(num - 1) - - -def test_gamma() -> None: - """ - >>> test_gamma() - """ - assert gamma(0.5) == sqrt(pi) - assert gamma(1) == 1.0 - assert gamma(2) == 1.0 - - -if __name__ == "__main__": - from doctest import testmod - - testmod() - num = 1.0 - while num: - num = float(input("Gamma of: ")) - print(f"gamma({num}) = {gamma(num)}") - print("\nEnter 0 to exit...") From c6ec99d57140cbf8b54077d379dfffeb6c7ad280 Mon Sep 17 00:00:00 2001 From: Kausthub Kannan Date: Sat, 7 Oct 2023 00:53:05 +0530 Subject: [PATCH 2396/2908] Added Mish Activation Function (#9942) * Added Mish Activation Function * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- neural_network/activation_functions/mish.py | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 neural_network/activation_functions/mish.py diff --git a/neural_network/activation_functions/mish.py b/neural_network/activation_functions/mish.py new file mode 100644 index 000000000000..e4f98307f2ba --- /dev/null +++ b/neural_network/activation_functions/mish.py @@ -0,0 +1,39 @@ +""" +Mish Activation Function + +Use Case: Improved version of the ReLU activation function used in Computer Vision. +For more detailed information, you can refer to the following link: +https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Mish +""" + +import numpy as np + + +def mish(vector: np.ndarray) -> np.ndarray: + """ + Implements the Mish activation function. + + Parameters: + vector (np.ndarray): The input array for Mish activation. + + Returns: + np.ndarray: The input array after applying the Mish activation. + + Formula: + f(x) = x * tanh(softplus(x)) = x * tanh(ln(1 + e^x)) + + Examples: + >>> mish(vector=np.array([2.3,0.6,-2,-3.8])) + array([ 2.26211893, 0.46613649, -0.25250148, -0.08405831]) + + >>> mish(np.array([-9.2, -0.3, 0.45, -4.56])) + array([-0.00092952, -0.15113318, 0.33152014, -0.04745745]) + + """ + return vector * np.tanh(np.log(1 + np.exp(vector))) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 80a2087e0aa349b81fb6bbc5d73dae920f560e75 Mon Sep 17 00:00:00 2001 From: Kausthub Kannan Date: Sat, 7 Oct 2023 01:56:09 +0530 Subject: [PATCH 2397/2908] Added Softplus activation function (#9944) --- .../activation_functions/softplus.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 neural_network/activation_functions/softplus.py diff --git a/neural_network/activation_functions/softplus.py b/neural_network/activation_functions/softplus.py new file mode 100644 index 000000000000..35fdf41afc96 --- /dev/null +++ b/neural_network/activation_functions/softplus.py @@ -0,0 +1,37 @@ +""" +Softplus Activation Function + +Use Case: The Softplus function is a smooth approximation of the ReLU function. +For more detailed information, you can refer to the following link: +https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Softplus +""" + +import numpy as np + + +def softplus(vector: np.ndarray) -> np.ndarray: + """ + Implements the Softplus activation function. + + Parameters: + vector (np.ndarray): The input array for the Softplus activation. + + Returns: + np.ndarray: The input array after applying the Softplus activation. + + Formula: f(x) = ln(1 + e^x) + + Examples: + >>> softplus(np.array([2.3, 0.6, -2, -3.8])) + array([2.39554546, 1.03748795, 0.12692801, 0.02212422]) + + >>> softplus(np.array([-9.2, -0.3, 0.45, -4.56])) + array([1.01034298e-04, 5.54355244e-01, 9.43248946e-01, 1.04077103e-02]) + """ + return np.log(1 + np.exp(vector)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2122474e41f2b85500e1f9347d98c9efc15aba4e Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:09:39 +0500 Subject: [PATCH 2398/2908] Segmented sieve - doctests (#9945) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Added doctests. * Update segmented_sieve.py Removed unnecessary check. * Update segmented_sieve.py Added checks for 0 and negative numbers. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update segmented_sieve.py * Update segmented_sieve.py Added float number check. * Update segmented_sieve.py * Update segmented_sieve.py simplified verification * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update segmented_sieve.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update segmented_sieve.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ValueError: Number 22.2 must instead be a positive integer --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/segmented_sieve.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/maths/segmented_sieve.py b/maths/segmented_sieve.py index e950a83b752a..125390edc588 100644 --- a/maths/segmented_sieve.py +++ b/maths/segmented_sieve.py @@ -4,7 +4,36 @@ def sieve(n: int) -> list[int]: - """Segmented Sieve.""" + """ + Segmented Sieve. + + Examples: + >>> sieve(8) + [2, 3, 5, 7] + + >>> sieve(27) + [2, 3, 5, 7, 11, 13, 17, 19, 23] + + >>> sieve(0) + Traceback (most recent call last): + ... + ValueError: Number 0 must instead be a positive integer + + >>> sieve(-1) + Traceback (most recent call last): + ... + ValueError: Number -1 must instead be a positive integer + + >>> sieve(22.2) + Traceback (most recent call last): + ... + ValueError: Number 22.2 must instead be a positive integer + """ + + if n <= 0 or isinstance(n, float): + msg = f"Number {n} must instead be a positive integer" + raise ValueError(msg) + in_prime = [] start = 2 end = int(math.sqrt(n)) # Size of every segment @@ -42,4 +71,9 @@ def sieve(n: int) -> list[int]: return prime -print(sieve(10**6)) +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print(f"{sieve(10**6) = }") From 678e0aa8cfdaae1d17536fdcf489bebe1e12cfc6 Mon Sep 17 00:00:00 2001 From: Saahil Mahato <115351000+saahil-mahato@users.noreply.github.com> Date: Sat, 7 Oct 2023 15:20:23 +0545 Subject: [PATCH 2399/2908] Mention square matrices in strassen docs and make it more clear (#9839) * refactor: fix strassen matrix multiplication docs * refactor: make docs more clear --- divide_and_conquer/strassen_matrix_multiplication.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index 1d03950ef9fe..f529a255d2ef 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -74,7 +74,7 @@ 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. + Algorithm. It only supports square matrices of any size that is a power of 2. """ if matrix_dimensions(matrix_a) == (2, 2): return default_matrix_multiplication(matrix_a, matrix_b) @@ -129,8 +129,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 to convert them both into square matrices of equal + # dimensions that are a power of 2 for i in range(maxim): if i < dimension1[0]: for _ in range(dimension1[1], maxim): From 78af0c43c623332029c9ad1d240d81577aac5d72 Mon Sep 17 00:00:00 2001 From: Pronay Debnath Date: Sat, 7 Oct 2023 21:21:30 +0530 Subject: [PATCH 2400/2908] Create fractional_cover_problem.py (#9973) * Create fractional_cover_problem.py * Update fractional_cover_problem.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fractional_cover_problem.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fractional_cover_problem.py * Update fractional_cover_problem.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fractional_cover_problem.py * Update fractional_cover_problem.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fractional_cover_problem.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Lose __eq__() * Update fractional_cover_problem.py * Define Item property ratio --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- greedy_methods/fractional_cover_problem.py | 102 +++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 greedy_methods/fractional_cover_problem.py diff --git a/greedy_methods/fractional_cover_problem.py b/greedy_methods/fractional_cover_problem.py new file mode 100644 index 000000000000..e37c363f1db9 --- /dev/null +++ b/greedy_methods/fractional_cover_problem.py @@ -0,0 +1,102 @@ +# https://en.wikipedia.org/wiki/Set_cover_problem + +from dataclasses import dataclass +from operator import attrgetter + + +@dataclass +class Item: + weight: int + value: int + + @property + def ratio(self) -> float: + """ + Return the value-to-weight ratio for the item. + + Returns: + float: The value-to-weight ratio for the item. + + Examples: + >>> Item(10, 65).ratio + 6.5 + + >>> Item(20, 100).ratio + 5.0 + + >>> Item(30, 120).ratio + 4.0 + """ + return self.value / self.weight + + +def fractional_cover(items: list[Item], capacity: int) -> float: + """ + Solve the Fractional Cover Problem. + + Args: + items: A list of items, where each item has weight and value attributes. + capacity: The maximum weight capacity of the knapsack. + + Returns: + The maximum value that can be obtained by selecting fractions of items to cover + the knapsack's capacity. + + Raises: + ValueError: If capacity is negative. + + Examples: + >>> fractional_cover((Item(10, 60), Item(20, 100), Item(30, 120)), capacity=50) + 240.0 + + >>> fractional_cover([Item(20, 100), Item(30, 120), Item(10, 60)], capacity=25) + 135.0 + + >>> fractional_cover([Item(10, 60), Item(20, 100), Item(30, 120)], capacity=60) + 280.0 + + >>> fractional_cover(items=[Item(5, 30), Item(10, 60), Item(15, 90)], capacity=30) + 180.0 + + >>> fractional_cover(items=[], capacity=50) + 0.0 + + >>> fractional_cover(items=[Item(10, 60)], capacity=5) + 30.0 + + >>> fractional_cover(items=[Item(10, 60)], capacity=1) + 6.0 + + >>> fractional_cover(items=[Item(10, 60)], capacity=0) + 0.0 + + >>> fractional_cover(items=[Item(10, 60)], capacity=-1) + Traceback (most recent call last): + ... + ValueError: Capacity cannot be negative + """ + if capacity < 0: + raise ValueError("Capacity cannot be negative") + + total_value = 0.0 + remaining_capacity = capacity + + # Sort the items by their value-to-weight ratio in descending order + for item in sorted(items, key=attrgetter("ratio"), reverse=True): + if remaining_capacity == 0: + break + + weight_taken = min(item.weight, remaining_capacity) + total_value += weight_taken * item.ratio + remaining_capacity -= weight_taken + + return total_value + + +if __name__ == "__main__": + import doctest + + if result := doctest.testmod().failed: + print(f"{result} test(s) failed") + else: + print("All tests passed") From 112daddc4de91d60bbdd3201fc9a6a4afc60f57a Mon Sep 17 00:00:00 2001 From: dhruvtrigotra <72982592+dhruvtrigotra@users.noreply.github.com> Date: Sun, 8 Oct 2023 00:34:24 +0530 Subject: [PATCH 2401/2908] charging_capacitor (#10016) * charging_capacitor * charging_capacitor * Final edits --------- Co-authored-by: Christian Clauss --- electronics/charging_capacitor.py | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 electronics/charging_capacitor.py diff --git a/electronics/charging_capacitor.py b/electronics/charging_capacitor.py new file mode 100644 index 000000000000..4029b0ecf267 --- /dev/null +++ b/electronics/charging_capacitor.py @@ -0,0 +1,71 @@ +# source - The ARRL Handbook for Radio Communications +# https://en.wikipedia.org/wiki/RC_time_constant + +""" +Description +----------- +When a capacitor is connected with a potential source (AC or DC). It starts to charge +at a general speed but when a resistor is connected in the circuit with in series to +a capacitor then the capacitor charges slowly means it will take more time than usual. +while the capacitor is being charged, the voltage is in exponential function with time. + +'resistance(ohms) * capacitance(farads)' is called RC-timeconstant which may also be +represented as τ (tau). By using this RC-timeconstant we can find the voltage at any +time 't' from the initiation of charging a capacitor with the help of the exponential +function containing RC. Both at charging and discharging of a capacitor. +""" +from math import exp # value of exp = 2.718281828459… + + +def charging_capacitor( + source_voltage: float, # voltage in volts. + resistance: float, # resistance in ohms. + capacitance: float, # capacitance in farads. + time_sec: float, # time in seconds after charging initiation of capacitor. +) -> float: + """ + Find capacitor voltage at any nth second after initiating its charging. + + Examples + -------- + >>> charging_capacitor(source_voltage=.2,resistance=.9,capacitance=8.4,time_sec=.5) + 0.013 + + >>> charging_capacitor(source_voltage=2.2,resistance=3.5,capacitance=2.4,time_sec=9) + 1.446 + + >>> charging_capacitor(source_voltage=15,resistance=200,capacitance=20,time_sec=2) + 0.007 + + >>> charging_capacitor(20, 2000, 30*pow(10,-5), 4) + 19.975 + + >>> charging_capacitor(source_voltage=0,resistance=10.0,capacitance=.30,time_sec=3) + Traceback (most recent call last): + ... + ValueError: Source voltage must be positive. + + >>> charging_capacitor(source_voltage=20,resistance=-2000,capacitance=30,time_sec=4) + Traceback (most recent call last): + ... + ValueError: Resistance must be positive. + + >>> charging_capacitor(source_voltage=30,resistance=1500,capacitance=0,time_sec=4) + Traceback (most recent call last): + ... + ValueError: Capacitance must be positive. + """ + + if source_voltage <= 0: + raise ValueError("Source voltage must be positive.") + if resistance <= 0: + raise ValueError("Resistance must be positive.") + if capacitance <= 0: + raise ValueError("Capacitance must be positive.") + return round(source_voltage * (1 - exp(-time_sec / (resistance * capacitance))), 3) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 60291738d2552999545c414bb8a8e90f86c69678 Mon Sep 17 00:00:00 2001 From: Kosuri L Indu <118645569+kosuri-indu@users.noreply.github.com> Date: Sun, 8 Oct 2023 00:38:38 +0530 Subject: [PATCH 2402/2908] add : trapped water program under dynamic programming (#10027) * to add the trapped water program * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * to make changes for error : B006 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * to make changes for error : B006 * to make changes for error : B006 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * to make changes in doctest * to make changes in doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/trapped_water.py Co-authored-by: Christian Clauss * Update dynamic_programming/trapped_water.py Co-authored-by: Christian Clauss * to make changes in parameters * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * to make changes in parameters * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/trapped_water.py Co-authored-by: Christian Clauss * to make changes in parameters * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * for negative heights * Update dynamic_programming/trapped_water.py Co-authored-by: Christian Clauss * to remove falsy * Final edits * tuple[int, ...] --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- dynamic_programming/trapped_water.py | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 dynamic_programming/trapped_water.py diff --git a/dynamic_programming/trapped_water.py b/dynamic_programming/trapped_water.py new file mode 100644 index 000000000000..8bec9fac5fef --- /dev/null +++ b/dynamic_programming/trapped_water.py @@ -0,0 +1,60 @@ +""" +Given an array of non-negative integers representing an elevation map where the width +of each bar is 1, this program calculates how much rainwater can be trapped. + +Example - height = (0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1) +Output: 6 +This problem can be solved using the concept of "DYNAMIC PROGRAMMING". + +We calculate the maximum height of bars on the left and right of every bar in array. +Then iterate over the width of structure and at each index. +The amount of water that will be stored is equal to minimum of maximum height of bars +on both sides minus height of bar at current position. +""" + + +def trapped_rainwater(heights: tuple[int, ...]) -> int: + """ + The trapped_rainwater function calculates the total amount of rainwater that can be + trapped given an array of bar heights. + It uses a dynamic programming approach, determining the maximum height of bars on + both sides for each bar, and then computing the trapped water above each bar. + The function returns the total trapped water. + + >>> trapped_rainwater((0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1)) + 6 + >>> trapped_rainwater((7, 1, 5, 3, 6, 4)) + 9 + >>> trapped_rainwater((7, 1, 5, 3, 6, -1)) + Traceback (most recent call last): + ... + ValueError: No height can be negative + """ + if not heights: + return 0 + if any(h < 0 for h in heights): + raise ValueError("No height can be negative") + length = len(heights) + + left_max = [0] * length + left_max[0] = heights[0] + for i, height in enumerate(heights[1:], start=1): + left_max[i] = max(height, left_max[i - 1]) + + right_max = [0] * length + right_max[-1] = heights[-1] + for i in range(length - 2, -1, -1): + right_max[i] = max(heights[i], right_max[i + 1]) + + return sum( + min(left, right) - height + for left, right, height in zip(left_max, right_max, heights) + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{trapped_rainwater((0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1)) = }") + print(f"{trapped_rainwater((7, 1, 5, 3, 6, 4)) = }") From 895dffb412d80f29c65a062bf6d91fd2a70d8818 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Oct 2023 21:32:28 +0200 Subject: [PATCH 2403/2908] [pre-commit.ci] pre-commit autoupdate (#9543) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.291 → v0.0.292](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.291...v0.0.292) - [github.com/codespell-project/codespell: v2.2.5 → v2.2.6](https://github.com/codespell-project/codespell/compare/v2.2.5...v2.2.6) - [github.com/tox-dev/pyproject-fmt: 1.1.0 → 1.2.0](https://github.com/tox-dev/pyproject-fmt/compare/1.1.0...1.2.0) * updating DIRECTORY.md * Fix typos in test_min_spanning_tree_prim.py * Fix typos * codespell --ignore-words-list=manuel --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- .../cnn_classification.py.DISABLED.txt | 4 +-- computer_vision/mosaic_augmentation.py | 2 +- dynamic_programming/min_distance_up_bottom.py | 11 +++--- graphs/tests/test_min_spanning_tree_prim.py | 8 ++--- hashes/sha1.py | 36 ++++++++++--------- maths/pi_generator.py | 31 +++++++--------- maths/radians.py | 4 +-- maths/square_root.py | 7 ++-- neural_network/convolution_neural_network.py | 8 ++--- neural_network/gan.py_tf | 2 +- other/graham_scan.py | 8 ++--- other/linear_congruential_generator.py | 4 +-- other/password.py | 12 +++---- physics/speed_of_sound.py | 30 +++++++--------- project_euler/problem_035/sol1.py | 12 +++---- project_euler/problem_135/sol1.py | 30 +++++++--------- project_euler/problem_493/sol1.py | 2 +- pyproject.toml | 2 +- 19 files changed, 97 insertions(+), 118 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dbf7ff341243..8a88dcc07622 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: - id: black - repo: https://github.com/codespell-project/codespell - rev: v2.2.5 + rev: v2.2.6 hooks: - id: codespell additional_dependencies: diff --git a/computer_vision/cnn_classification.py.DISABLED.txt b/computer_vision/cnn_classification.py.DISABLED.txt index 9b5f8c95eebf..b813b71033f3 100644 --- a/computer_vision/cnn_classification.py.DISABLED.txt +++ b/computer_vision/cnn_classification.py.DISABLED.txt @@ -11,10 +11,10 @@ Download dataset from : https://lhncbc.nlm.nih.gov/LHC-publications/pubs/TuberculosisChestXrayImageDataSets.html 1. Download the dataset folder and create two folder training set and test set -in the parent dataste folder +in the parent dataset folder 2. Move 30-40 image from both TB positive and TB Negative folder in the test set folder -3. The labels of the iamges will be extracted from the folder name +3. The labels of the images will be extracted from the folder name the image is present in. """ diff --git a/computer_vision/mosaic_augmentation.py b/computer_vision/mosaic_augmentation.py index c150126d6bfb..cd923dfe095f 100644 --- a/computer_vision/mosaic_augmentation.py +++ b/computer_vision/mosaic_augmentation.py @@ -8,7 +8,7 @@ import cv2 import numpy as np -# Parrameters +# Parameters OUTPUT_SIZE = (720, 1280) # Height, Width SCALE_RANGE = (0.4, 0.6) # if height or width lower than this scale, drop it. FILTER_TINY_SCALE = 1 / 100 diff --git a/dynamic_programming/min_distance_up_bottom.py b/dynamic_programming/min_distance_up_bottom.py index 4870c7ef4499..6b38a41a1c0a 100644 --- a/dynamic_programming/min_distance_up_bottom.py +++ b/dynamic_programming/min_distance_up_bottom.py @@ -1,11 +1,8 @@ """ Author : Alexander Pantyukhin Date : October 14, 2022 -This is implementation Dynamic Programming up bottom approach -to find edit distance. -The aim is to demonstate up bottom approach for solving the task. -The implementation was tested on the -leetcode: https://leetcode.com/problems/edit-distance/ +This is an implementation of the up-bottom approach to find edit distance. +The implementation was tested on Leetcode: https://leetcode.com/problems/edit-distance/ Levinstein distance Dynamic Programming: up -> down. @@ -30,10 +27,10 @@ def min_distance_up_bottom(word1: str, word2: str) -> int: @functools.cache def min_distance(index1: int, index2: int) -> int: - # if first word index is overflow - delete all from the second word + # if first word index overflows - delete all from the second word if index1 >= len_word1: return len_word2 - index2 - # if second word index is overflow - delete all from the first word + # if second word index overflows - delete all from the first word if index2 >= len_word2: return len_word1 - index1 diff = int(word1[index1] != word2[index2]) # current letters not identical diff --git a/graphs/tests/test_min_spanning_tree_prim.py b/graphs/tests/test_min_spanning_tree_prim.py index 91feab28fc81..66e5706dadb1 100644 --- a/graphs/tests/test_min_spanning_tree_prim.py +++ b/graphs/tests/test_min_spanning_tree_prim.py @@ -22,12 +22,12 @@ def test_prim_successful_result(): [1, 7, 11], ] - adjancency = defaultdict(list) + adjacency = defaultdict(list) for node1, node2, cost in edges: - adjancency[node1].append([node2, cost]) - adjancency[node2].append([node1, cost]) + adjacency[node1].append([node2, cost]) + adjacency[node2].append([node1, cost]) - result = mst(adjancency) + result = mst(adjacency) expected = [ [7, 6, 1], diff --git a/hashes/sha1.py b/hashes/sha1.py index 8a03673f3c9f..a0fa688f863e 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -1,26 +1,28 @@ """ -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. +Implementation of the SHA1 hash function and gives utilities to find hash of string or +hash of text from a file. Also contains a Test class to verify that the generated hash +matches what is returned by the hashlib library + 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" -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 cryptographic 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 -securely, send encrypted messages and is very useful in payment systems, blockchain -and cryptocurrency etc. -The Algorithm as described in the reference: +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 for communicating +securely, send encrypted messages and is very useful in payment systems, blockchain and +cryptocurrency etc. + +The algorithm as described in the reference: First we start with a message. The message is padded and the length of the message is added to the end. It is then split into blocks of 512 bits or 64 bytes. The blocks are then processed one at a time. Each block must be expanded and compressed. -The value after each compression is added to a 160bit buffer called the current hash -state. After the last block is processed the current hash state is returned as +The value after each compression is added to a 160-bit buffer called the current hash +state. After the last block is processed, the current hash state is returned as the final hash. + Reference: https://deadhacker.com/2006/02/21/sha-1-illustrated/ """ import argparse @@ -30,18 +32,18 @@ class SHA1Hash: """ - Class to contain the entire pipeline for SHA1 Hashing Algorithm + Class to contain the entire pipeline for SHA1 hashing algorithm >>> 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 + Initiates the variables data and h. h is a list of 5 8-digit hexadecimal 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 + hexadecimal numbers in Python """ self.data = data self.h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0] @@ -90,7 +92,7 @@ def final_hash(self): 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 so on. This h becomes our final hash which is returned. """ self.padded_data = self.padding() self.blocks = self.split_blocks() @@ -135,7 +137,7 @@ def test_sha1_hash(): 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 + hash. unittest.main() has been commented out because we probably don't want to run the test each time. """ # unittest.main() diff --git a/maths/pi_generator.py b/maths/pi_generator.py index dcd218aae309..addd921747ba 100644 --- a/maths/pi_generator.py +++ b/maths/pi_generator.py @@ -3,60 +3,53 @@ def calculate_pi(limit: int) -> str: https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80 Leibniz Formula for Pi - The Leibniz formula is the special case arctan 1 = 1/4 Pi . + The Leibniz formula is the special case arctan(1) = pi / 4. Leibniz's formula converges extremely slowly: it exhibits sublinear convergence. Convergence (https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80#Convergence) We cannot try to prove against an interrupted, uncompleted generation. https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80#Unusual_behaviour - The errors can in fact be predicted; - but those calculations also approach infinity for accuracy. + The errors can in fact be predicted, but those calculations also approach infinity + for accuracy. - Our output will always be a string since we can defintely store all digits in there. - For simplicity' sake, let's just compare against known values and since our outpit - is a string, we need to convert to float. + Our output will be a string so that we can definitely store all digits. >>> import math >>> float(calculate_pi(15)) == math.pi True - Since we cannot predict errors or interrupt any infinite alternating - series generation since they approach infinity, - or interrupt any alternating series, we are going to need math.isclose() + Since we cannot predict errors or interrupt any infinite alternating series + generation since they approach infinity, or interrupt any alternating series, we'll + need math.isclose() >>> math.isclose(float(calculate_pi(50)), math.pi) True - >>> math.isclose(float(calculate_pi(100)), math.pi) True - Since math.pi-constant contains only 16 digits, here some test with preknown values: + Since math.pi contains only 16 digits, here are some tests with known values: >>> calculate_pi(50) '3.14159265358979323846264338327950288419716939937510' >>> calculate_pi(80) '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899' - - To apply the Leibniz formula for calculating pi, - the variables q, r, t, k, n, and l are used for the iteration process. """ + # Variables used for the iteration process q = 1 r = 0 t = 1 k = 1 n = 3 l = 3 + decimal = limit counter = 0 result = "" - """ - We will avoid using yield since we otherwise get a Generator-Object, - which we can't just compare against anything. We would have to make a list out of it - after the generation, so we will just stick to plain return logic: - """ + # We can't compare against anything if we make a generator, + # so we'll stick with plain return logic while counter != decimal + 1: if 4 * q + r - t < n * t: result += str(n) diff --git a/maths/radians.py b/maths/radians.py index 465467a3ba08..b8ac61cb135c 100644 --- a/maths/radians.py +++ b/maths/radians.py @@ -3,7 +3,7 @@ def radians(degree: float) -> float: """ - Coverts the given angle from degrees to radians + Converts the given angle from degrees to radians https://en.wikipedia.org/wiki/Radian >>> radians(180) @@ -16,7 +16,7 @@ def radians(degree: float) -> float: 1.9167205845401725 >>> from math import radians as math_radians - >>> all(abs(radians(i)-math_radians(i)) <= 0.00000001 for i in range(-2, 361)) + >>> all(abs(radians(i) - math_radians(i)) <= 1e-8 for i in range(-2, 361)) True """ diff --git a/maths/square_root.py b/maths/square_root.py index 2cbf14beae18..4462ccb75261 100644 --- a/maths/square_root.py +++ b/maths/square_root.py @@ -19,14 +19,13 @@ def get_initial_point(a: float) -> float: def square_root_iterative( - a: float, max_iter: int = 9999, tolerance: float = 0.00000000000001 + a: float, max_iter: int = 9999, tolerance: float = 1e-14 ) -> float: """ - Square root is aproximated using Newtons method. + Square root approximated using Newton's method. https://en.wikipedia.org/wiki/Newton%27s_method - >>> all(abs(square_root_iterative(i)-math.sqrt(i)) <= .00000000000001 - ... for i in range(500)) + >>> all(abs(square_root_iterative(i) - math.sqrt(i)) <= 1e-14 for i in range(500)) True >>> square_root_iterative(-1) diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index f5ec156f3593..f2e88fe7bd88 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -2,7 +2,7 @@ - - - - - -- - - - - - - - - - - - - - - - - - - - - - - Name - - CNN - Convolution Neural Network For Photo Recognizing Goal - - Recognize Handing Writing Word Photo - Detail:Total 5 layers neural network + Detail: Total 5 layers neural network * Convolution layer * Pooling layer * Input layer layer of BP @@ -24,7 +24,7 @@ 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 conv1_get: [a,c,d], size, number, step of convolution kernel :param size_p1: pooling size :param bp_num1: units number of flatten layer :param bp_num2: units number of hidden layer @@ -71,7 +71,7 @@ def save_model(self, save_path): with open(save_path, "wb") as f: pickle.dump(model_dic, f) - print(f"Model saved: {save_path}") + print(f"Model saved: {save_path}") @classmethod def read_model(cls, model_path): @@ -210,7 +210,7 @@ def _calculate_gradient_from_pool( def train( self, patterns, datas_train, datas_teach, n_repeat, error_accuracy, draw_e=bool ): - # model traning + # model training print("----------------------Start Training-------------------------") print((" - - Shape: Train_Data ", np.shape(datas_train))) print((" - - Shape: Teach_Data ", np.shape(datas_teach))) diff --git a/neural_network/gan.py_tf b/neural_network/gan.py_tf index deb062c48dc7..9c6e1c05b8b4 100644 --- a/neural_network/gan.py_tf +++ b/neural_network/gan.py_tf @@ -158,7 +158,7 @@ if __name__ == "__main__": # G_b2 = np.random.normal(size=(784),scale=(1. / np.sqrt(784 / 2.))) *0.002 G_b7 = np.zeros(784) - # 3. For Adam Optimzier + # 3. For Adam Optimizer v1, m1 = 0, 0 v2, m2 = 0, 0 v3, m3 = 0, 0 diff --git a/other/graham_scan.py b/other/graham_scan.py index 2eadb4e56668..3f11d40f141c 100644 --- a/other/graham_scan.py +++ b/other/graham_scan.py @@ -1,5 +1,5 @@ """ -This is a pure Python implementation of the merge-insertion sort algorithm +This is a pure Python implementation of the Graham scan algorithm Source: https://en.wikipedia.org/wiki/Graham_scan For doctests run following command: @@ -142,8 +142,8 @@ def graham_scan(points: list[tuple[int, int]]) -> list[tuple[int, int]]: stack.append(sorted_points[0]) stack.append(sorted_points[1]) stack.append(sorted_points[2]) - # In any ways, the first 3 points line are towards left. - # Because we sort them the angle from minx, miny. + # The first 3 points lines are towards the left because we sort them by their angle + # from minx, miny. current_direction = Direction.left for i in range(3, len(sorted_points)): @@ -164,7 +164,7 @@ def graham_scan(points: list[tuple[int, int]]) -> list[tuple[int, int]]: break elif current_direction == Direction.right: # If the straight line is towards right, - # every previous points on those straigh line is not convex hull. + # every previous points on that straight line is not convex hull. stack.pop() if next_direction == Direction.right: stack.pop() diff --git a/other/linear_congruential_generator.py b/other/linear_congruential_generator.py index c016310f9cfa..c7de15b94bbd 100644 --- a/other/linear_congruential_generator.py +++ b/other/linear_congruential_generator.py @@ -8,9 +8,9 @@ class LinearCongruentialGenerator: A pseudorandom number generator. """ - # The default value for **seed** is the result of a function call which is not + # The default value for **seed** is the result of a function call, which is not # normally recommended and causes ruff to raise a B008 error. However, in this case, - # it is accptable because `LinearCongruentialGenerator.__init__()` will only be + # it is acceptable because `LinearCongruentialGenerator.__init__()` will only be # called once per instance and it ensures that each instance will generate a unique # sequence of numbers. diff --git a/other/password.py b/other/password.py index 9a6161af87d7..1ce0d52316e6 100644 --- a/other/password.py +++ b/other/password.py @@ -63,11 +63,12 @@ def random_characters(chars_incl, i): pass # Put your code here... -# This Will Check Whether A Given Password Is Strong Or Not -# It Follows The Rule that Length Of Password Should Be At Least 8 Characters -# And At Least 1 Lower, 1 Upper, 1 Number And 1 Special Character def is_strong_password(password: str, min_length: int = 8) -> bool: """ + This will check whether a given password is strong or not. The password must be at + least as long as the provided minimum length, and it must contain at least 1 + lowercase letter, 1 uppercase letter, 1 number and 1 special character. + >>> is_strong_password('Hwea7$2!') True >>> is_strong_password('Sh0r1') @@ -81,7 +82,6 @@ def is_strong_password(password: str, min_length: int = 8) -> bool: """ if len(password) < min_length: - # Your Password must be at least 8 characters long return False upper = any(char in ascii_uppercase for char in password) @@ -90,8 +90,6 @@ def is_strong_password(password: str, min_length: int = 8) -> bool: spec_char = any(char in punctuation for char in password) return upper and lower and num and spec_char - # Passwords should contain UPPERCASE, lowerase - # numbers, and special characters def main(): @@ -104,7 +102,7 @@ def main(): "Alternative Password generated:", alternative_password_generator(chars_incl, length), ) - print("[If you are thinking of using this passsword, You better save it.]") + print("[If you are thinking of using this password, You better save it.]") if __name__ == "__main__": diff --git a/physics/speed_of_sound.py b/physics/speed_of_sound.py index a4658366a36c..3fa952cdb411 100644 --- a/physics/speed_of_sound.py +++ b/physics/speed_of_sound.py @@ -2,39 +2,35 @@ Title : Calculating the speed of sound Description : - The speed of sound (c) is the speed that a sound wave travels - per unit time (m/s). During propagation, the sound wave propagates - through an elastic medium. Its SI unit is meter per second (m/s). + The speed of sound (c) is the speed that a sound wave travels per unit time (m/s). + During propagation, the sound wave propagates through an elastic medium. - Only longitudinal waves can propagate in liquids and gas other then - solid where they also travel in transverse wave. The following Algo- - rithem calculates the speed of sound in fluid depanding on the bulk - module and the density of the fluid. + Sound propagates as longitudinal waves in liquids and gases and as transverse waves + in solids. This file calculates the speed of sound in a fluid based on its bulk + module and density. - Equation for calculating speed od sound in fluid: - c_fluid = (K_s*p)**0.5 + Equation for the speed of sound in a fluid: + c_fluid = sqrt(K_s / p) c_fluid: speed of sound in fluid K_s: isentropic bulk modulus p: density of fluid - - Source : https://en.wikipedia.org/wiki/Speed_of_sound """ def speed_of_sound_in_a_fluid(density: float, bulk_modulus: float) -> float: """ - This method calculates the speed of sound in fluid - - This is calculated from the other two provided values + Calculates the speed of sound in a fluid from its density and bulk modulus + Examples: - Example 1 --> Water 20°C: bulk_moduls= 2.15MPa, density=998kg/m³ - Example 2 --> Murcery 20°: bulk_moduls= 28.5MPa, density=13600kg/m³ + Example 1 --> Water 20°C: bulk_modulus= 2.15MPa, density=998kg/m³ + Example 2 --> Mercury 20°C: bulk_modulus= 28.5MPa, density=13600kg/m³ - >>> speed_of_sound_in_a_fluid(bulk_modulus=2.15*10**9, density=998) + >>> speed_of_sound_in_a_fluid(bulk_modulus=2.15e9, density=998) 1467.7563207952705 - >>> speed_of_sound_in_a_fluid(bulk_modulus=28.5*10**9, density=13600) + >>> speed_of_sound_in_a_fluid(bulk_modulus=28.5e9, density=13600) 1447.614670861731 """ diff --git a/project_euler/problem_035/sol1.py b/project_euler/problem_035/sol1.py index 17a4e9088ae2..644c992ed8a5 100644 --- a/project_euler/problem_035/sol1.py +++ b/project_euler/problem_035/sol1.py @@ -11,18 +11,18 @@ 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 +below 1 million using the Sieve 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 __future__ import annotations -seive = [True] * 1000001 +sieve = [True] * 1000001 i = 2 while i * i <= 1000000: - if seive[i]: + if sieve[i]: for j in range(i * i, 1000001, i): - seive[j] = False + sieve[j] = False i += 1 @@ -36,7 +36,7 @@ def is_prime(n: int) -> bool: >>> is_prime(25363) False """ - return seive[n] + return sieve[n] def contains_an_even_digit(n: int) -> bool: diff --git a/project_euler/problem_135/sol1.py b/project_euler/problem_135/sol1.py index d71a0439c7e9..ac91fa4e2b9d 100644 --- a/project_euler/problem_135/sol1.py +++ b/project_euler/problem_135/sol1.py @@ -1,28 +1,22 @@ """ 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, +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. +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? +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. - +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 a +multiple of a. Total no of steps = n * (1/1 + 1/2 + 1/3 + 1/4 + ... + 1/n), so roughly +O(nlogn) time complexity. """ @@ -42,15 +36,15 @@ def solution(limit: int = 1000000) -> int: 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 + if common_difference % 4: # d must be divisible 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 0, a > d and 4d < a count = sum(1 for x in frequency[1:limit] if x == 10) diff --git a/project_euler/problem_493/sol1.py b/project_euler/problem_493/sol1.py index c9879a528230..4d96c6c3207e 100644 --- a/project_euler/problem_493/sol1.py +++ b/project_euler/problem_493/sol1.py @@ -9,7 +9,7 @@ This combinatorial problem can be solved by decomposing the problem into the following steps: -1. Calculate the total number of possible picking cominations +1. Calculate the total number of possible picking combinations [combinations := binom_coeff(70, 20)] 2. Calculate the number of combinations with one colour missing [missing := binom_coeff(60, 20)] diff --git a/pyproject.toml b/pyproject.toml index f9091fb8578d..75da7a04513e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,5 +130,5 @@ omit = [".env/*"] sort = "Cover" [tool.codespell] -ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar" +ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,zar" skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" From fa077e6703758afcae4f19347a4388b9230d568f Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Sun, 8 Oct 2023 16:58:48 +0800 Subject: [PATCH 2404/2908] Add doctests, type hints; fix bug for dynamic_programming/minimum_partition.py (#10012) * Add doctests, type hints; fix bug * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/minimum_partition.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/dynamic_programming/minimum_partition.py b/dynamic_programming/minimum_partition.py index 3daa9767fde4..e6188cb33b3a 100644 --- a/dynamic_programming/minimum_partition.py +++ b/dynamic_programming/minimum_partition.py @@ -3,13 +3,25 @@ """ -def find_min(arr): +def find_min(arr: list[int]) -> int: + """ + >>> find_min([1, 2, 3, 4, 5]) + 1 + >>> find_min([5, 5, 5, 5, 5]) + 5 + >>> find_min([5, 5, 5, 5]) + 0 + >>> find_min([3]) + 3 + >>> find_min([]) + 0 + """ n = len(arr) s = sum(arr) 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(n + 1): dp[i][0] = True for i in range(1, s + 1): @@ -17,7 +29,7 @@ def find_min(arr): for i in range(1, n + 1): for j in range(1, s + 1): - dp[i][j] = dp[i][j - 1] + dp[i][j] = dp[i - 1][j] if arr[i - 1] <= j: dp[i][j] = dp[i][j] or dp[i - 1][j - arr[i - 1]] @@ -28,3 +40,9 @@ def find_min(arr): break return diff + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 937ce83b150f0a217c7fa63c75a095534ae8bfeb Mon Sep 17 00:00:00 2001 From: Om Alve Date: Sun, 8 Oct 2023 16:35:01 +0530 Subject: [PATCH 2405/2908] Added fractionated_morse_cipher (#9442) * Added fractionated_morse_cipher * Added return type hint for main function * Added doctest for main * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Replaced main function * changed the references section Co-authored-by: Christian Clauss * removed repetitive datatype hint in the docstring Co-authored-by: Christian Clauss * changed dictionary comprehension variable names to something more compact Co-authored-by: Christian Clauss * Update fractionated_morse_cipher.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- ciphers/fractionated_morse_cipher.py | 167 +++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 ciphers/fractionated_morse_cipher.py diff --git a/ciphers/fractionated_morse_cipher.py b/ciphers/fractionated_morse_cipher.py new file mode 100644 index 000000000000..c1d5dc6d50aa --- /dev/null +++ b/ciphers/fractionated_morse_cipher.py @@ -0,0 +1,167 @@ +""" +Python program for the Fractionated Morse Cipher. + +The Fractionated Morse cipher first converts the plaintext to Morse code, +then enciphers fixed-size blocks of Morse code back to letters. +This procedure means plaintext letters are mixed into the ciphertext letters, +making it more secure than substitution ciphers. + +http://practicalcryptography.com/ciphers/fractionated-morse-cipher/ +""" +import string + +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": "--..", + " ": "", +} + +# Define possible trigrams of Morse code +MORSE_COMBINATIONS = [ + "...", + "..-", + "..x", + ".-.", + ".--", + ".-x", + ".x.", + ".x-", + ".xx", + "-..", + "-.-", + "-.x", + "--.", + "---", + "--x", + "-x.", + "-x-", + "-xx", + "x..", + "x.-", + "x.x", + "x-.", + "x--", + "x-x", + "xx.", + "xx-", + "xxx", +] + +# Create a reverse dictionary for Morse code +REVERSE_DICT = {value: key for key, value in MORSE_CODE_DICT.items()} + + +def encode_to_morse(plaintext: str) -> str: + """Encode a plaintext message into Morse code. + + Args: + plaintext: The plaintext message to encode. + + Returns: + The Morse code representation of the plaintext message. + + Example: + >>> encode_to_morse("defend the east") + '-..x.x..-.x.x-.x-..xx-x....x.xx.x.-x...x-' + """ + return "x".join([MORSE_CODE_DICT.get(letter.upper(), "") for letter in plaintext]) + + +def encrypt_fractionated_morse(plaintext: str, key: str) -> str: + """Encrypt a plaintext message using Fractionated Morse Cipher. + + Args: + plaintext: The plaintext message to encrypt. + key: The encryption key. + + Returns: + The encrypted ciphertext. + + Example: + >>> encrypt_fractionated_morse("defend the east","Roundtable") + 'ESOAVVLJRSSTRX' + + """ + morse_code = encode_to_morse(plaintext) + key = key.upper() + string.ascii_uppercase + key = "".join(sorted(set(key), key=key.find)) + + # Ensure morse_code length is a multiple of 3 + padding_length = 3 - (len(morse_code) % 3) + morse_code += "x" * padding_length + + fractionated_morse_dict = {v: k for k, v in zip(key, MORSE_COMBINATIONS)} + fractionated_morse_dict["xxx"] = "" + encrypted_text = "".join( + [ + fractionated_morse_dict[morse_code[i : i + 3]] + for i in range(0, len(morse_code), 3) + ] + ) + return encrypted_text + + +def decrypt_fractionated_morse(ciphertext: str, key: str) -> str: + """Decrypt a ciphertext message encrypted with Fractionated Morse Cipher. + + Args: + ciphertext: The ciphertext message to decrypt. + key: The decryption key. + + Returns: + The decrypted plaintext message. + + Example: + >>> decrypt_fractionated_morse("ESOAVVLJRSSTRX","Roundtable") + 'DEFEND THE EAST' + """ + key = key.upper() + string.ascii_uppercase + key = "".join(sorted(set(key), key=key.find)) + + inverse_fractionated_morse_dict = dict(zip(key, MORSE_COMBINATIONS)) + morse_code = "".join( + [inverse_fractionated_morse_dict.get(letter, "") for letter in ciphertext] + ) + decrypted_text = "".join( + [REVERSE_DICT[code] for code in morse_code.split("x")] + ).strip() + return decrypted_text + + +if __name__ == "__main__": + """ + Example usage of Fractionated Morse Cipher. + """ + plaintext = "defend the east" + print("Plain Text:", plaintext) + key = "ROUNDTABLE" + + ciphertext = encrypt_fractionated_morse(plaintext, key) + print("Encrypted:", ciphertext) + + decrypted_text = decrypt_fractionated_morse(ciphertext, key) + print("Decrypted:", decrypted_text) From 08d394126c9d46fc9d227a0dc1e343ad1fa70679 Mon Sep 17 00:00:00 2001 From: Kausthub Kannan Date: Sun, 8 Oct 2023 21:18:22 +0530 Subject: [PATCH 2406/2908] Changed Mish Activation Function to use Softplus (#10111) --- neural_network/activation_functions/mish.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neural_network/activation_functions/mish.py b/neural_network/activation_functions/mish.py index e4f98307f2ba..e51655df8a3f 100644 --- a/neural_network/activation_functions/mish.py +++ b/neural_network/activation_functions/mish.py @@ -7,6 +7,7 @@ """ import numpy as np +from softplus import softplus def mish(vector: np.ndarray) -> np.ndarray: @@ -30,7 +31,7 @@ def mish(vector: np.ndarray) -> np.ndarray: array([-0.00092952, -0.15113318, 0.33152014, -0.04745745]) """ - return vector * np.tanh(np.log(1 + np.exp(vector))) + return vector * np.tanh(softplus(vector)) if __name__ == "__main__": From 6860daea60a512b202481bd5dd00d6534e162b77 Mon Sep 17 00:00:00 2001 From: Aarya Balwadkar <142713127+AaryaBalwadkar@users.noreply.github.com> Date: Sun, 8 Oct 2023 21:23:38 +0530 Subject: [PATCH 2407/2908] Made Changes shifted CRT, modular division to maths directory (#10084) --- {blockchain => maths}/chinese_remainder_theorem.py | 0 {blockchain => maths}/modular_division.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {blockchain => maths}/chinese_remainder_theorem.py (100%) rename {blockchain => maths}/modular_division.py (100%) diff --git a/blockchain/chinese_remainder_theorem.py b/maths/chinese_remainder_theorem.py similarity index 100% rename from blockchain/chinese_remainder_theorem.py rename to maths/chinese_remainder_theorem.py diff --git a/blockchain/modular_division.py b/maths/modular_division.py similarity index 100% rename from blockchain/modular_division.py rename to maths/modular_division.py From 81b29066d206217cb689fe2c9c8d530a1aa66cbe Mon Sep 17 00:00:00 2001 From: Arnav Kohli <95236897+THEGAMECHANGER416@users.noreply.github.com> Date: Sun, 8 Oct 2023 21:34:43 +0530 Subject: [PATCH 2408/2908] Created folder for losses in Machine_Learning (#9969) * Created folder for losses in Machine_Learning * Update binary_cross_entropy.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update mean_squared_error.py * Update binary_cross_entropy.py * Update mean_squared_error.py * Update binary_cross_entropy.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update mean_squared_error.py * Update binary_cross_entropy.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update mean_squared_error.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update binary_cross_entropy.py * Update mean_squared_error.py * Update binary_cross_entropy.py * Update mean_squared_error.py * Update machine_learning/losses/binary_cross_entropy.py Co-authored-by: Christian Clauss * Update machine_learning/losses/mean_squared_error.py Co-authored-by: Christian Clauss * Update machine_learning/losses/binary_cross_entropy.py Co-authored-by: Christian Clauss * Update mean_squared_error.py * Update machine_learning/losses/mean_squared_error.py Co-authored-by: Tianyi Zheng * Update binary_cross_entropy.py * Update mean_squared_error.py * Update binary_cross_entropy.py * Update mean_squared_error.py * Update mean_squared_error.py * Update binary_cross_entropy.py * renamed: losses -> loss_functions * updated 2 files * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update mean_squared_error.py * Update mean_squared_error.py * Update binary_cross_entropy.py * Update mean_squared_error.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Tianyi Zheng --- .../loss_functions/binary_cross_entropy.py | 59 +++++++++++++++++++ .../loss_functions/mean_squared_error.py | 51 ++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 machine_learning/loss_functions/binary_cross_entropy.py create mode 100644 machine_learning/loss_functions/mean_squared_error.py diff --git a/machine_learning/loss_functions/binary_cross_entropy.py b/machine_learning/loss_functions/binary_cross_entropy.py new file mode 100644 index 000000000000..4ebca7f21757 --- /dev/null +++ b/machine_learning/loss_functions/binary_cross_entropy.py @@ -0,0 +1,59 @@ +""" +Binary Cross-Entropy (BCE) Loss Function + +Description: +Quantifies dissimilarity between true labels (0 or 1) and predicted probabilities. +It's widely used in binary classification tasks. + +Formula: +BCE = -Σ(y_true * log(y_pred) + (1 - y_true) * log(1 - y_pred)) + +Source: +[Wikipedia - Cross entropy](https://en.wikipedia.org/wiki/Cross_entropy) +""" + +import numpy as np + + +def binary_cross_entropy( + y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-15 +) -> float: + """ + Calculate the BCE Loss between true labels and predicted probabilities. + + Parameters: + - y_true: True binary labels (0 or 1). + - y_pred: Predicted probabilities for class 1. + - epsilon: Small constant to avoid numerical instability. + + Returns: + - bce_loss: Binary Cross-Entropy Loss. + + Example Usage: + >>> true_labels = np.array([0, 1, 1, 0, 1]) + >>> predicted_probs = np.array([0.2, 0.7, 0.9, 0.3, 0.8]) + >>> binary_cross_entropy(true_labels, predicted_probs) + 0.2529995012327421 + >>> true_labels = np.array([0, 1, 1, 0, 1]) + >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) + >>> binary_cross_entropy(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + # Clip predicted probabilities to avoid log(0) and log(1) + y_pred = np.clip(y_pred, epsilon, 1 - epsilon) + + # Calculate binary cross-entropy loss + bce_loss = -(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)) + + # Take the mean over all samples + return np.mean(bce_loss) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/machine_learning/loss_functions/mean_squared_error.py b/machine_learning/loss_functions/mean_squared_error.py new file mode 100644 index 000000000000..d2b0e1e158ba --- /dev/null +++ b/machine_learning/loss_functions/mean_squared_error.py @@ -0,0 +1,51 @@ +""" +Mean Squared Error (MSE) Loss Function + +Description: +MSE measures the mean squared difference between true values and predicted values. +It serves as a measure of the model's accuracy in regression tasks. + +Formula: +MSE = (1/n) * Σ(y_true - y_pred)^2 + +Source: +[Wikipedia - Mean squared error](https://en.wikipedia.org/wiki/Mean_squared_error) +""" + +import numpy as np + + +def mean_squared_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Calculate the Mean Squared Error (MSE) between two arrays. + + Parameters: + - y_true: The true values (ground truth). + - y_pred: The predicted values. + + Returns: + - mse: The Mean Squared Error between y_true and y_pred. + + Example usage: + >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) + >>> mean_squared_error(true_values, predicted_values) + 0.028000000000000032 + >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) + >>> mean_squared_error(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + squared_errors = (y_true - y_pred) ** 2 + return np.mean(squared_errors) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a12b07f352d51af1cb86c14f865cf2b18aba3ea1 Mon Sep 17 00:00:00 2001 From: Kausthub Kannan Date: Sun, 8 Oct 2023 21:38:37 +0530 Subject: [PATCH 2409/2908] Added Squareplus Activation Function (#9977) * Added Squareplus Activation Function * Added parameter beta to the function * Fixed Squareplus Function * Update neural_network/activation_functions/squareplus.py --------- Co-authored-by: Tianyi Zheng --- .../activation_functions/squareplus.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 neural_network/activation_functions/squareplus.py diff --git a/neural_network/activation_functions/squareplus.py b/neural_network/activation_functions/squareplus.py new file mode 100644 index 000000000000..40fa800d6b4a --- /dev/null +++ b/neural_network/activation_functions/squareplus.py @@ -0,0 +1,38 @@ +""" +Squareplus Activation Function + +Use Case: Squareplus designed to enhance positive values and suppress negative values. +For more detailed information, you can refer to the following link: +https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Squareplus +""" + +import numpy as np + + +def squareplus(vector: np.ndarray, beta: float) -> np.ndarray: + """ + Implements the SquarePlus activation function. + + Parameters: + vector (np.ndarray): The input array for the SquarePlus activation. + beta (float): size of the curved region + + Returns: + np.ndarray: The input array after applying the SquarePlus activation. + + Formula: f(x) = ( x + sqrt(x^2 + b) ) / 2 + + Examples: + >>> squareplus(np.array([2.3, 0.6, -2, -3.8]), beta=2) + array([2.5 , 1.06811457, 0.22474487, 0.12731349]) + + >>> squareplus(np.array([-9.2, -0.3, 0.45, -4.56]), beta=3) + array([0.0808119 , 0.72891979, 1.11977651, 0.15893419]) + """ + return (vector + np.sqrt(vector**2 + beta)) / 2 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From e89ae55d8e157cb7c6c3f855188a0fde29083c35 Mon Sep 17 00:00:00 2001 From: Saurabh Mahapatra <98408932+its-100rabh@users.noreply.github.com> Date: Sun, 8 Oct 2023 21:40:41 +0530 Subject: [PATCH 2410/2908] Create strip.py (#10011) * Create strip.py * Update strip.py --- strings/strip.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 strings/strip.py diff --git a/strings/strip.py b/strings/strip.py new file mode 100644 index 000000000000..d4f901f0c7ea --- /dev/null +++ b/strings/strip.py @@ -0,0 +1,33 @@ +def strip(user_string: str, characters: str = " \t\n\r") -> str: + """ + Remove leading and trailing characters (whitespace by default) from a string. + + Args: + user_string (str): The input string to be stripped. + characters (str, optional): Optional characters to be removed + (default is whitespace). + + Returns: + str: The stripped string. + + Examples: + >>> strip(" hello ") + 'hello' + >>> strip("...world...", ".") + 'world' + >>> strip("123hello123", "123") + 'hello' + >>> strip("") + '' + """ + + start = 0 + end = len(user_string) + + while start < end and user_string[start] in characters: + start += 1 + + while end > start and user_string[end - 1] in characters: + end -= 1 + + return user_string[start:end] From 982bc2735872592d036c20389859071f36b13469 Mon Sep 17 00:00:00 2001 From: Kosuri L Indu <118645569+kosuri-indu@users.noreply.github.com> Date: Sun, 8 Oct 2023 22:37:02 +0530 Subject: [PATCH 2411/2908] add : Best time to buy and sell stock program under GREEDY methods (#10114) * to add best_time_stock program * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update best_time_to_buy_and_sell_stock.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../best_time_to_buy_and_sell_stock.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 greedy_methods/best_time_to_buy_and_sell_stock.py diff --git a/greedy_methods/best_time_to_buy_and_sell_stock.py b/greedy_methods/best_time_to_buy_and_sell_stock.py new file mode 100644 index 000000000000..4aea19172ece --- /dev/null +++ b/greedy_methods/best_time_to_buy_and_sell_stock.py @@ -0,0 +1,42 @@ +""" +Given a list of stock prices calculate the maximum profit that can be made from a +single buy and sell of one share of stock. We only allowed to complete one buy +transaction and one sell transaction but must buy before we sell. + +Example : prices = [7, 1, 5, 3, 6, 4] +max_profit will return 5 - which is by buying at price 1 and selling at price 6. + +This problem can be solved using the concept of "GREEDY ALGORITHM". + +We iterate over the price array once, keeping track of the lowest price point +(buy) and the maximum profit we can get at each point. The greedy choice at each point +is to either buy at the current price if it's less than our current buying price, or +sell at the current price if the profit is more than our current maximum profit. +""" + + +def max_profit(prices: list[int]) -> int: + """ + >>> max_profit([7, 1, 5, 3, 6, 4]) + 5 + >>> max_profit([7, 6, 4, 3, 1]) + 0 + """ + if not prices: + return 0 + + min_price = prices[0] + max_profit: int = 0 + + for price in prices: + min_price = min(price, min_price) + max_profit = max(price - min_price, max_profit) + + return max_profit + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(max_profit([7, 1, 5, 3, 6, 4])) From e7a59bfff5b182fb01530aa4b1a29b804efb1425 Mon Sep 17 00:00:00 2001 From: SubhranShu2332 <124662904+SubhranShu2332@users.noreply.github.com> Date: Mon, 9 Oct 2023 00:47:02 +0530 Subject: [PATCH 2412/2908] In place of calculating the factorial several times we can run a loop k times to calculate the combination (#10051) * In place of calculating the factorial several times we can run a loop k times to calculate the combination for example: 5 C 3 = 5! / (3! * (5-3)! ) = (5*4*3*2*1)/[(3*2*1)*(2*1)] =(5*4*3)/(3*2*1) so running a loop k times will reduce the time complexity to O(k) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/combinations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/combinations.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/maths/combinations.py b/maths/combinations.py index a2324012c01f..6e9e1a807067 100644 --- a/maths/combinations.py +++ b/maths/combinations.py @@ -1,7 +1,6 @@ """ https://en.wikipedia.org/wiki/Combination """ -from math import factorial def combinations(n: int, k: int) -> int: @@ -35,7 +34,11 @@ def combinations(n: int, k: int) -> int: # 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 factorial(n) // (factorial(k) * factorial(n - k)) + res = 1 + for i in range(k): + res *= n - i + res //= i + 1 + return res if __name__ == "__main__": From c8f6f79f8038ef090a396725c80fa77d9186fb4b Mon Sep 17 00:00:00 2001 From: Siddharth Warrier <117698635+siddwarr@users.noreply.github.com> Date: Mon, 9 Oct 2023 01:10:14 +0530 Subject: [PATCH 2413/2908] Power of 4 (#9505) * added power_of_4 * updated power_of_4 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated power_of_4 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated power_of_4 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated power_of_4 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated power_of_4 * added type check * added tescase --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- bit_manipulation/power_of_4.py | 67 ++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 bit_manipulation/power_of_4.py diff --git a/bit_manipulation/power_of_4.py b/bit_manipulation/power_of_4.py new file mode 100644 index 000000000000..09e6e28621df --- /dev/null +++ b/bit_manipulation/power_of_4.py @@ -0,0 +1,67 @@ +""" + +Task: +Given a positive int number. Return True if this number is power of 4 +or False otherwise. + +Implementation notes: Use bit manipulation. +For example if the number is the power of 2 it's bits representation: +n = 0..100..00 +n - 1 = 0..011..11 + +n & (n - 1) - no intersections = 0 +If the number is a power of 4 then it should be a power of 2 +and the set bit should be at an odd position. +""" + + +def power_of_4(number: int) -> bool: + """ + Return True if this number is power of 4 or False otherwise. + + >>> power_of_4(0) + Traceback (most recent call last): + ... + ValueError: number must be positive + >>> power_of_4(1) + True + >>> power_of_4(2) + False + >>> power_of_4(4) + True + >>> power_of_4(6) + False + >>> power_of_4(8) + False + >>> power_of_4(17) + False + >>> power_of_4(64) + True + >>> power_of_4(-1) + Traceback (most recent call last): + ... + ValueError: number must be positive + >>> power_of_4(1.2) + Traceback (most recent call last): + ... + TypeError: number must be an integer + + """ + if not isinstance(number, int): + raise TypeError("number must be an integer") + if number <= 0: + raise ValueError("number must be positive") + if number & (number - 1) == 0: + c = 0 + while number: + c += 1 + number >>= 1 + return c % 2 == 1 + else: + return False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 17de908d1ad5a3eb7eb0c850e64394b62e4674c3 Mon Sep 17 00:00:00 2001 From: Achal Jain Date: Mon, 9 Oct 2023 01:11:30 +0530 Subject: [PATCH 2414/2908] Added Median of Medians Algorithm (#9864) * Added Median of Medians Algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update median_of_medians.py as per requested changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- searches/median_of_medians.py | 107 ++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 searches/median_of_medians.py diff --git a/searches/median_of_medians.py b/searches/median_of_medians.py new file mode 100644 index 000000000000..a8011a34af76 --- /dev/null +++ b/searches/median_of_medians.py @@ -0,0 +1,107 @@ +""" +A Python implementation of the Median of Medians algorithm +to select pivots for quick_select, 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. Search in time complexity O(n) at any rank +deterministically +https://en.wikipedia.org/wiki/Median_of_medians +""" + + +def median_of_five(arr: list) -> int: + """ + Return the median of the input list + :param arr: Array to find median of + :return: median of arr + + >>> median_of_five([2, 4, 5, 7, 899]) + 5 + >>> median_of_five([5, 7, 899, 54, 32]) + 32 + >>> median_of_five([5, 4, 3, 2]) + 4 + >>> median_of_five([3, 5, 7, 10, 2]) + 5 + """ + arr = sorted(arr) + return arr[len(arr) // 2] + + +def median_of_medians(arr: list) -> int: + """ + Return a pivot to partition data on by calculating + Median of medians of input data + :param arr: The data to be checked (a list) + :return: median of medians of input array + + >>> median_of_medians([2, 4, 5, 7, 899, 54, 32]) + 54 + >>> median_of_medians([5, 7, 899, 54, 32]) + 32 + >>> median_of_medians([5, 4, 3, 2]) + 4 + >>> median_of_medians([3, 5, 7, 10, 2, 12]) + 12 + """ + + if len(arr) <= 5: + return median_of_five(arr) + medians = [] + i = 0 + while i < len(arr): + if (i + 4) <= len(arr): + medians.append(median_of_five(arr[i:].copy())) + else: + medians.append(median_of_five(arr[i : i + 5].copy())) + i += 5 + return median_of_medians(medians) + + +def quick_select(arr: list, target: int) -> int: + """ + Two way partition the data into smaller and greater lists, + in relationship to the pivot + :param arr: The data to be searched (a list) + :param target: The rank to be searched + :return: element at rank target + + >>> quick_select([2, 4, 5, 7, 899, 54, 32], 5) + 32 + >>> quick_select([2, 4, 5, 7, 899, 54, 32], 1) + 2 + >>> quick_select([5, 4, 3, 2], 2) + 3 + >>> quick_select([3, 5, 7, 10, 2, 12], 3) + 5 + """ + + # Invalid Input + if target > len(arr): + return -1 + + # x is the estimated pivot by median of medians algorithm + x = median_of_medians(arr) + left = [] + right = [] + check = False + for i in range(len(arr)): + if arr[i] < x: + left.append(arr[i]) + elif arr[i] > x: + right.append(arr[i]) + elif arr[i] == x and not check: + check = True + else: + right.append(arr[i]) + rank_x = len(left) + 1 + if rank_x == target: + answer = x + elif rank_x > target: + answer = quick_select(left, target) + elif rank_x < target: + answer = quick_select(right, target - rank_x) + return answer + + +print(median_of_five([5, 4, 3, 2])) From 8e108ed92ab9a50d5a3e6f647fa33238270e21d1 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 8 Oct 2023 15:43:07 -0400 Subject: [PATCH 2415/2908] Rename maths/binary_exponentiation_3.py (#9656) * updating DIRECTORY.md * Rename binary_exponentiation_3.py Rename binary_exponentiation_3.py to binary_exponentiation_2.py because the original binary_exponentiation_2.py was renamed to binary_multiplication.py in PR #9513 * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 +++-- ...binary_exponentiation_3.py => binary_exponentiation_2.py} | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename maths/{binary_exponentiation_3.py => binary_exponentiation_2.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index a975b9264be0..55b270624438 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -556,7 +556,7 @@ * [Bell Numbers](maths/bell_numbers.py) * [Binary Exp Mod](maths/binary_exp_mod.py) * [Binary Exponentiation](maths/binary_exponentiation.py) - * [Binary Exponentiation 3](maths/binary_exponentiation_3.py) + * [Binary Exponentiation 2](maths/binary_exponentiation_2.py) * [Binary Multiplication](maths/binary_multiplication.py) * [Binomial Coefficient](maths/binomial_coefficient.py) * [Binomial Distribution](maths/binomial_distribution.py) @@ -588,7 +588,6 @@ * [Find Min](maths/find_min.py) * [Floor](maths/floor.py) * [Gamma](maths/gamma.py) - * [Gamma Recursive](maths/gamma_recursive.py) * [Gaussian](maths/gaussian.py) * [Gaussian Error Linear Unit](maths/gaussian_error_linear_unit.py) * [Gcd Of N Numbers](maths/gcd_of_n_numbers.py) @@ -723,6 +722,7 @@ * Activation Functions * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) * [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py) + * [Mish](neural_network/activation_functions/mish.py) * [Rectified Linear Unit](neural_network/activation_functions/rectified_linear_unit.py) * [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py) * [Sigmoid Linear Unit](neural_network/activation_functions/sigmoid_linear_unit.py) @@ -748,6 +748,7 @@ * [Linear Congruential Generator](other/linear_congruential_generator.py) * [Lru Cache](other/lru_cache.py) * [Magicdiamondpattern](other/magicdiamondpattern.py) + * [Majority Vote Algorithm](other/majority_vote_algorithm.py) * [Maximum Subsequence](other/maximum_subsequence.py) * [Nested Brackets](other/nested_brackets.py) * [Number Container System](other/number_container_system.py) diff --git a/maths/binary_exponentiation_3.py b/maths/binary_exponentiation_2.py similarity index 100% rename from maths/binary_exponentiation_3.py rename to maths/binary_exponentiation_2.py From 2d02500332533bb314f91675a3c30ea05bd52b5a Mon Sep 17 00:00:00 2001 From: halfhearted <99018821+Arunsiva003@users.noreply.github.com> Date: Mon, 9 Oct 2023 01:14:49 +0530 Subject: [PATCH 2416/2908] equilibrium index in an array (#9856) * equilibrium index in an array * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * equilibrium index in an array * equilibrium index in an array * equilibrium index in an array removed type in docstring --------- Co-authored-by: ArunSiva Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../arrays/equilibrium_index_in_array.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 data_structures/arrays/equilibrium_index_in_array.py diff --git a/data_structures/arrays/equilibrium_index_in_array.py b/data_structures/arrays/equilibrium_index_in_array.py new file mode 100644 index 000000000000..4099896d226d --- /dev/null +++ b/data_structures/arrays/equilibrium_index_in_array.py @@ -0,0 +1,59 @@ +""" +Find the Equilibrium Index of an Array. +Reference: https://www.geeksforgeeks.org/equilibrium-index-of-an-array/ + +Python doctests can be run with the following command: +python -m doctest -v equilibrium_index.py + +Given a sequence arr[] of size n, this function returns +an equilibrium index (if any) or -1 if no equilibrium index exists. + +The equilibrium index of an array is an index such that the sum of +elements at lower indexes is equal to the sum of elements at higher indexes. + + + +Example Input: +arr = [-7, 1, 5, 2, -4, 3, 0] +Output: 3 + +""" + + +def equilibrium_index(arr: list[int], size: int) -> int: + """ + Find the equilibrium index of an array. + + Args: + arr : The input array of integers. + size : The size of the array. + + Returns: + int: The equilibrium index or -1 if no equilibrium index exists. + + Examples: + >>> equilibrium_index([-7, 1, 5, 2, -4, 3, 0], 7) + 3 + >>> equilibrium_index([1, 2, 3, 4, 5], 5) + -1 + >>> equilibrium_index([1, 1, 1, 1, 1], 5) + 2 + >>> equilibrium_index([2, 4, 6, 8, 10, 3], 6) + -1 + """ + total_sum = sum(arr) + left_sum = 0 + + for i in range(size): + total_sum -= arr[i] + if left_sum == total_sum: + return i + left_sum += arr[i] + + return -1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 66e4ea6a621cccabd6116f1543432899a4411daa Mon Sep 17 00:00:00 2001 From: Anshu Sharma <142900182+AnshuSharma111@users.noreply.github.com> Date: Mon, 9 Oct 2023 01:47:22 +0530 Subject: [PATCH 2417/2908] Consolidated two scripts reverse_letters.py and reverse_long_words.py into one (#10140) * Conolidated two scripts reverse_letters.py and reverse_long_words.py into one because of similar functionality * Added a new line to accomodate characters without going over 88 char limit * fixed grammar to pass pre-commit * Changed faulty test case entirely to pass pre commit * fixed a test case which was wrong --------- Co-authored-by: Keyboard-1 <142900182+Keyboard-1@users.noreply.github.com> --- DIRECTORY.md | 1 - strings/reverse_letters.py | 27 ++++++++++++++++----------- strings/reverse_long_words.py | 21 --------------------- 3 files changed, 16 insertions(+), 33 deletions(-) delete mode 100644 strings/reverse_long_words.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 55b270624438..b1a23a239b01 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1197,7 +1197,6 @@ * [Rabin Karp](strings/rabin_karp.py) * [Remove Duplicate](strings/remove_duplicate.py) * [Reverse Letters](strings/reverse_letters.py) - * [Reverse Long Words](strings/reverse_long_words.py) * [Reverse Words](strings/reverse_words.py) * [Snake Case To Camel Pascal Case](strings/snake_case_to_camel_pascal_case.py) * [Split](strings/split.py) diff --git a/strings/reverse_letters.py b/strings/reverse_letters.py index 10b8a6d72a0f..4f73f816b382 100644 --- a/strings/reverse_letters.py +++ b/strings/reverse_letters.py @@ -1,19 +1,24 @@ -def reverse_letters(input_str: str) -> str: +def reverse_letters(sentence: str, length: int = 0) -> 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' + Reverse all words that are longer than the given length of characters in a sentence. + If unspecified, length is taken as 0 + + >>> reverse_letters("Hey wollef sroirraw", 3) + 'Hey fellow warriors' + >>> reverse_letters("nohtyP is nohtyP", 2) + 'Python is Python' + >>> reverse_letters("1 12 123 1234 54321 654321", 0) + '1 21 321 4321 12345 123456' + >>> reverse_letters("racecar") + 'racecar' """ - return " ".join([word[::-1] for word in input_str.split()]) + return " ".join( + "".join(word[::-1]) if len(word) > length else word for word in sentence.split() + ) if __name__ == "__main__": import doctest doctest.testmod() + print(reverse_letters("Hey wollef sroirraw")) diff --git a/strings/reverse_long_words.py b/strings/reverse_long_words.py deleted file mode 100644 index 39ef11513f40..000000000000 --- a/strings/reverse_long_words.py +++ /dev/null @@ -1,21 +0,0 @@ -def reverse_long_words(sentence: str) -> str: - """ - Reverse all words that are longer than 4 characters in a sentence. - - >>> reverse_long_words("Hey wollef sroirraw") - 'Hey fellow warriors' - >>> reverse_long_words("nohtyP is nohtyP") - 'Python is Python' - >>> reverse_long_words("1 12 123 1234 54321 654321") - '1 12 123 1234 12345 123456' - """ - return " ".join( - "".join(word[::-1]) if len(word) > 4 else word for word in sentence.split() - ) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() - print(reverse_long_words("Hey wollef sroirraw")) From a2b695dabd6e9c6dd82784bd534c2e7570939be2 Mon Sep 17 00:00:00 2001 From: Megan Payne Date: Sun, 8 Oct 2023 23:33:50 +0200 Subject: [PATCH 2418/2908] Added Germain primes algorithm to the maths folder (#10120) * Added algorithm for Germain Primes to maths folder * Fixed test errors Germain primes. * Formatting Germain primes after pre-commit * Fixed path to maths * Update maths/germain_primes.py Co-authored-by: Tianyi Zheng * Update maths/germain_primes.py Co-authored-by: Tianyi Zheng * Added function for safe primes * Update maths/germain_primes.py Co-authored-by: Tianyi Zheng * Apply suggestions from code review --------- Co-authored-by: Megan Payne Co-authored-by: Tianyi Zheng --- maths/germain_primes.py | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 maths/germain_primes.py diff --git a/maths/germain_primes.py b/maths/germain_primes.py new file mode 100644 index 000000000000..078d1967f41a --- /dev/null +++ b/maths/germain_primes.py @@ -0,0 +1,72 @@ +""" +A Sophie Germain prime is any prime p, where 2p + 1 is also prime. +The second number, 2p + 1 is called a safe prime. + +Examples of Germain primes include: 2, 3, 5, 11, 23 + +Their corresponding safe primes: 5, 7, 11, 23, 47 +https://en.wikipedia.org/wiki/Safe_and_Sophie_Germain_primes +""" + +from maths.prime_check import is_prime + + +def is_germain_prime(number: int) -> bool: + """Checks if input number and 2*number + 1 are prime. + + >>> is_germain_prime(3) + True + >>> is_germain_prime(11) + True + >>> is_germain_prime(4) + False + >>> is_germain_prime(23) + True + >>> is_germain_prime(13) + False + >>> is_germain_prime(20) + False + >>> is_germain_prime('abc') + Traceback (most recent call last): + ... + TypeError: Input value must be a positive integer. Input value: abc + """ + if not isinstance(number, int) or number < 1: + msg = f"Input value must be a positive integer. Input value: {number}" + raise TypeError(msg) + + return is_prime(number) and is_prime(2 * number + 1) + + +def is_safe_prime(number: int) -> bool: + """Checks if input number and (number - 1)/2 are prime. + The smallest safe prime is 5, with the Germain prime is 2. + + >>> is_safe_prime(5) + True + >>> is_safe_prime(11) + True + >>> is_safe_prime(1) + False + >>> is_safe_prime(2) + False + >>> is_safe_prime(3) + False + >>> is_safe_prime(47) + True + >>> is_safe_prime('abc') + Traceback (most recent call last): + ... + TypeError: Input value must be a positive integer. Input value: abc + """ + if not isinstance(number, int) or number < 1: + msg = f"Input value must be a positive integer. Input value: {number}" + raise TypeError(msg) + + return (number - 1) % 2 == 0 and is_prime(number) and is_prime((number - 1) // 2) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 2260961a803ebd037f471ef18fa5032a547d42da Mon Sep 17 00:00:00 2001 From: Saahil Mahato <115351000+saahil-mahato@users.noreply.github.com> Date: Mon, 9 Oct 2023 05:04:28 +0545 Subject: [PATCH 2419/2908] Add Soboleva Modified Hyberbolic Tangent function (#10043) * Add Sobovela Modified Hyberbolic Tangent function * fix: typo * Update and rename sobovela_modified_hyperbolic_tangent.py to soboleva_modified_hyperbolic_tangent.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: typo * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- .../soboleva_modified_hyperbolic_tangent.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py diff --git a/neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py b/neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py new file mode 100644 index 000000000000..603ac0b7e120 --- /dev/null +++ b/neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py @@ -0,0 +1,49 @@ +""" +This script implements the Soboleva Modified Hyperbolic Tangent function. + +The function applies the Soboleva Modified Hyperbolic Tangent function +to each element of the vector. + +More details about the activation function can be found on: +https://en.wikipedia.org/wiki/Soboleva_modified_hyperbolic_tangent +""" + + +import numpy as np + + +def soboleva_modified_hyperbolic_tangent( + vector: np.ndarray, a_value: float, b_value: float, c_value: float, d_value: float +) -> np.ndarray: + """ + Implements the Soboleva Modified Hyperbolic Tangent function + + Parameters: + vector (ndarray): A vector that consists of numeric values + a_value (float): parameter a of the equation + b_value (float): parameter b of the equation + c_value (float): parameter c of the equation + d_value (float): parameter d of the equation + + Returns: + vector (ndarray): Input array after applying SMHT function + + >>> vector = np.array([5.4, -2.4, 6.3, -5.23, 3.27, 0.56]) + >>> soboleva_modified_hyperbolic_tangent(vector, 0.2, 0.4, 0.6, 0.8) + array([ 0.11075085, -0.28236685, 0.07861169, -0.1180085 , 0.22999056, + 0.1566043 ]) + """ + + # Separate the numerator and denominator for simplicity + # Calculate the numerator and denominator element-wise + numerator = np.exp(a_value * vector) - np.exp(-b_value * vector) + denominator = np.exp(c_value * vector) + np.exp(-d_value * vector) + + # Calculate and return the final result element-wise + return numerator / denominator + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ed19b1cf0c3d8284027e17cc025d65b3f924acc0 Mon Sep 17 00:00:00 2001 From: Saahil Mahato <115351000+saahil-mahato@users.noreply.github.com> Date: Mon, 9 Oct 2023 05:19:50 +0545 Subject: [PATCH 2420/2908] Add binary step activation function (#10030) * Add binary step activation function * fix: ruff line too long error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: add link to directory * revert: add link to directory * fix: algorithm bug and docs * Update neural_network/activation_functions/binary_step.py * fix: ruff line too long error --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- .../activation_functions/binary_step.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 neural_network/activation_functions/binary_step.py diff --git a/neural_network/activation_functions/binary_step.py b/neural_network/activation_functions/binary_step.py new file mode 100644 index 000000000000..8f8f4d405fd2 --- /dev/null +++ b/neural_network/activation_functions/binary_step.py @@ -0,0 +1,36 @@ +""" +This script demonstrates the implementation of the Binary Step function. + +It's an activation function in which the neuron is activated if the input is positive +or 0, else it is deactivated + +It's a simple activation function which is mentioned in this wikipedia article: +https://en.wikipedia.org/wiki/Activation_function +""" + + +import numpy as np + + +def binary_step(vector: np.ndarray) -> np.ndarray: + """ + Implements the binary step function + + Parameters: + vector (ndarray): A vector that consists of numeric values + + Returns: + vector (ndarray): Input vector after applying binary step function + + >>> vector = np.array([-1.2, 0, 2, 1.45, -3.7, 0.3]) + >>> binary_step(vector) + array([0, 1, 1, 1, 0, 1]) + """ + + return np.where(vector >= 0, 1, 0) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 12e8e9ca876ed3ae7f1effa1de407ca29a06cb36 Mon Sep 17 00:00:00 2001 From: Sai Harsha Kottapalli Date: Mon, 9 Oct 2023 17:36:16 +0530 Subject: [PATCH 2421/2908] Add DocTests to is_palindrome.py (#10081) * add doctest ut * test complete * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * ruff update * cover line 154 * Update data_structures/linked_list/is_palindrome.py Co-authored-by: Christian Clauss * use dataclass * pre-commit fix * Fix mypy errors * use future annotations --------- Co-authored-by: Harsha Kottapalli Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/linked_list/is_palindrome.py | 176 +++++++++++++++---- 1 file changed, 142 insertions(+), 34 deletions(-) diff --git a/data_structures/linked_list/is_palindrome.py b/data_structures/linked_list/is_palindrome.py index d540fb69f36b..7d89f085c67f 100644 --- a/data_structures/linked_list/is_palindrome.py +++ b/data_structures/linked_list/is_palindrome.py @@ -1,65 +1,167 @@ -def is_palindrome(head): +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class ListNode: + val: int = 0 + next_node: ListNode | None = None + + +def is_palindrome(head: ListNode | None) -> bool: + """ + Check if a linked list is a palindrome. + + Args: + head: The head of the linked list. + + Returns: + bool: True if the linked list is a palindrome, False otherwise. + + Examples: + >>> is_palindrome(None) + True + + >>> is_palindrome(ListNode(1)) + True + + >>> is_palindrome(ListNode(1, ListNode(2))) + False + + >>> is_palindrome(ListNode(1, ListNode(2, ListNode(1)))) + True + + >>> is_palindrome(ListNode(1, ListNode(2, ListNode(2, ListNode(1))))) + True + """ if not head: return True # split the list to two parts - fast, slow = head.next, head - while fast and fast.next: - fast = fast.next.next - slow = slow.next - second = slow.next - slow.next = None # Don't forget here! But forget still works! + fast: ListNode | None = head.next_node + slow: ListNode | None = head + while fast and fast.next_node: + fast = fast.next_node.next_node + slow = slow.next_node if slow else None + if slow: + # slow will always be defined, + # adding this check to resolve mypy static check + second = slow.next_node + slow.next_node = None # Don't forget here! But forget still works! # reverse the second part - node = None + node: ListNode | None = None while second: - nxt = second.next - second.next = node + nxt = second.next_node + second.next_node = node node = second second = nxt # compare two parts # second part has the same or one less node - while node: + while node and head: if node.val != head.val: return False - node = node.next - head = head.next + node = node.next_node + head = head.next_node return True -def is_palindrome_stack(head): - if not head or not head.next: +def is_palindrome_stack(head: ListNode | None) -> bool: + """ + Check if a linked list is a palindrome using a stack. + + Args: + head (ListNode): The head of the linked list. + + Returns: + bool: True if the linked list is a palindrome, False otherwise. + + Examples: + >>> is_palindrome_stack(None) + True + + >>> is_palindrome_stack(ListNode(1)) + True + + >>> is_palindrome_stack(ListNode(1, ListNode(2))) + False + + >>> is_palindrome_stack(ListNode(1, ListNode(2, ListNode(1)))) + True + + >>> is_palindrome_stack(ListNode(1, ListNode(2, ListNode(2, ListNode(1))))) + True + """ + if not head or not head.next_node: return True # 1. Get the midpoint (slow) - slow = fast = cur = head - while fast and fast.next: - fast, slow = fast.next.next, slow.next - - # 2. Push the second half into the stack - stack = [slow.val] - while slow.next: - slow = slow.next - stack.append(slow.val) - - # 3. Comparison - while stack: - if stack.pop() != cur.val: - return False - cur = cur.next + slow: ListNode | None = head + fast: ListNode | None = head + while fast and fast.next_node: + fast = fast.next_node.next_node + slow = slow.next_node if slow else None + + # slow will always be defined, + # adding this check to resolve mypy static check + if slow: + stack = [slow.val] + + # 2. Push the second half into the stack + while slow.next_node: + slow = slow.next_node + stack.append(slow.val) + + # 3. Comparison + cur: ListNode | None = head + while stack and cur: + if stack.pop() != cur.val: + return False + cur = cur.next_node return True -def is_palindrome_dict(head): - if not head or not head.next: +def is_palindrome_dict(head: ListNode | None) -> bool: + """ + Check if a linked list is a palindrome using a dictionary. + + Args: + head (ListNode): The head of the linked list. + + Returns: + bool: True if the linked list is a palindrome, False otherwise. + + Examples: + >>> is_palindrome_dict(None) + True + + >>> is_palindrome_dict(ListNode(1)) + True + + >>> is_palindrome_dict(ListNode(1, ListNode(2))) + False + + >>> is_palindrome_dict(ListNode(1, ListNode(2, ListNode(1)))) + True + + >>> is_palindrome_dict(ListNode(1, ListNode(2, ListNode(2, ListNode(1))))) + True + + >>> is_palindrome_dict(\ + ListNode(\ + 1, ListNode(2, ListNode(1, ListNode(3, ListNode(2, ListNode(1))))))) + False + """ + if not head or not head.next_node: return True - d = {} + d: dict[int, list[int]] = {} pos = 0 while head: if head.val in d: d[head.val].append(pos) else: d[head.val] = [pos] - head = head.next + head = head.next_node pos += 1 checksum = pos - 1 middle = 0 @@ -75,3 +177,9 @@ def is_palindrome_dict(head): if middle > 1: return False return True + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 876087be998d5b366d68cbb9394b6b92b7f619f6 Mon Sep 17 00:00:00 2001 From: Sai Harsha Kottapalli Date: Mon, 9 Oct 2023 17:46:43 +0530 Subject: [PATCH 2422/2908] Add DocTests to magicdiamondpattern.py (#10135) * magicdiamondpattern doctest * remove start part --------- Co-authored-by: Harsha Kottapalli --- other/magicdiamondpattern.py | 76 ++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py index 89b973bb41e8..58889280ab17 100644 --- a/other/magicdiamondpattern.py +++ b/other/magicdiamondpattern.py @@ -4,52 +4,76 @@ # Function to print upper half of diamond (pyramid) def floyd(n): """ - Parameters: - n : size of pattern + Print the upper half of a diamond pattern with '*' characters. + + Args: + n (int): Size of the pattern. + + Examples: + >>> floyd(3) + ' * \\n * * \\n* * * \\n' + + >>> floyd(5) + ' * \\n * * \\n * * * \\n * * * * \\n* * * * * \\n' """ + result = "" for i in range(n): for _ in range(n - i - 1): # printing spaces - print(" ", end="") + result += " " for _ in range(i + 1): # printing stars - print("* ", end="") - print() + result += "* " + result += "\n" + return result # Function to print lower half of diamond (pyramid) def reverse_floyd(n): """ - Parameters: - n : size of pattern + Print the lower half of a diamond pattern with '*' characters. + + Args: + n (int): Size of the pattern. + + Examples: + >>> reverse_floyd(3) + '* * * \\n * * \\n * \\n ' + + >>> reverse_floyd(5) + '* * * * * \\n * * * * \\n * * * \\n * * \\n * \\n ' """ + result = "" for i in range(n, 0, -1): for _ in range(i, 0, -1): # printing stars - print("* ", end="") - print() + result += "* " + result += "\n" for _ in range(n - i + 1, 0, -1): # printing spaces - print(" ", end="") + result += " " + return result # Function to print complete diamond pattern of "*" def pretty_print(n): """ - Parameters: - n : size of pattern + Print a complete diamond pattern with '*' characters. + + Args: + n (int): Size of the pattern. + + Examples: + >>> pretty_print(0) + ' ... .... nothing printing :(' + + >>> pretty_print(3) + ' * \\n * * \\n* * * \\n* * * \\n * * \\n * \\n ' """ if n <= 0: - print(" ... .... nothing printing :(") - return - floyd(n) # upper half - reverse_floyd(n) # lower half + return " ... .... nothing printing :(" + upper_half = floyd(n) # upper half + lower_half = reverse_floyd(n) # lower half + return upper_half + 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...") + import doctest + + doctest.testmod() From 583a614fefaa9c932e6d650abfea2eaa75a93b05 Mon Sep 17 00:00:00 2001 From: Siddik Patel <70135775+Siddikpatel@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:49:12 +0530 Subject: [PATCH 2423/2908] Removed redundant greatest_common_divisor code (#9358) * Deleted greatest_common_divisor def from many files and instead imported the method from Maths folder * Deleted greatest_common_divisor def from many files and instead imported the method from Maths folder, also fixed comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Deleted greatest_common_divisor def from many files and instead imported the method from Maths folder, also fixed comments * Imports organized * recursive gcd function implementation rolledback * more gcd duplicates removed * more gcd duplicates removed * Update maths/carmichael_number.py * updated files * moved a file to another location --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- blockchain/diophantine_equation.py | 32 +++--------------------- ciphers/affine_cipher.py | 6 +++-- ciphers/cryptomath_module.py | 7 ++---- ciphers/hill_cipher.py | 14 +---------- ciphers/rsa_key_generator.py | 4 ++- maths/carmichael_number.py | 11 ++------ maths/least_common_multiple.py | 22 ++-------------- maths/primelib.py | 40 +++--------------------------- project_euler/problem_005/sol2.py | 19 ++------------ 9 files changed, 24 insertions(+), 131 deletions(-) diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py index 22b0cad75c63..7110d90230c9 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -1,11 +1,13 @@ from __future__ import annotations +from maths.greatest_common_divisor import greatest_common_divisor + 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. + iff greatest_common_divisor(a,b) divides c. GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) @@ -22,7 +24,7 @@ def diophantine(a: int, b: int, c: int) -> tuple[float, float]: assert ( c % greatest_common_divisor(a, b) == 0 - ) # greatest_common_divisor(a,b) function implemented below + ) # greatest_common_divisor(a,b) is in maths directory (d, x, y) = extended_gcd(a, b) # extended_gcd(a,b) function implemented below r = c / d return (r * x, r * y) @@ -69,32 +71,6 @@ def diophantine_all_soln(a: int, b: int, c: int, n: int = 2) -> None: print(x, y) -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 - - 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 - - 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 diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index cd1e33b88425..10d16367cced 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -1,6 +1,8 @@ import random import sys +from maths.greatest_common_divisor import gcd_by_iterative + from . import cryptomath_module as cryptomath SYMBOLS = ( @@ -26,7 +28,7 @@ def check_keys(key_a: int, key_b: int, mode: str) -> None: "Key A must be greater than 0 and key B must " f"be between 0 and {len(SYMBOLS) - 1}." ) - if cryptomath.gcd(key_a, len(SYMBOLS)) != 1: + if gcd_by_iterative(key_a, len(SYMBOLS)) != 1: sys.exit( f"Key A {key_a} and the symbol set size {len(SYMBOLS)} " "are not relatively prime. Choose a different key." @@ -76,7 +78,7 @@ def get_random_key() -> int: while True: key_b = random.randint(2, len(SYMBOLS)) key_b = random.randint(2, len(SYMBOLS)) - if cryptomath.gcd(key_b, len(SYMBOLS)) == 1 and key_b % len(SYMBOLS) != 0: + if gcd_by_iterative(key_b, len(SYMBOLS)) == 1 and key_b % len(SYMBOLS) != 0: return key_b * len(SYMBOLS) + key_b diff --git a/ciphers/cryptomath_module.py b/ciphers/cryptomath_module.py index 6f15f7b733e6..02e94e4b9e92 100644 --- a/ciphers/cryptomath_module.py +++ b/ciphers/cryptomath_module.py @@ -1,11 +1,8 @@ -def gcd(a: int, b: int) -> int: - while a != 0: - a, b = b % a, a - return b +from maths.greatest_common_divisor import gcd_by_iterative def find_mod_inverse(a: int, m: int) -> int: - if gcd(a, m) != 1: + if gcd_by_iterative(a, m) != 1: msg = f"mod inverse of {a!r} and {m!r} does not exist" raise ValueError(msg) u1, u2, u3 = 1, 0, a diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index b4424e82298e..1201fda901e5 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -39,19 +39,7 @@ import numpy - -def greatest_common_divisor(a: int, b: int) -> int: - """ - >>> greatest_common_divisor(4, 8) - 4 - >>> greatest_common_divisor(8, 4) - 4 - >>> greatest_common_divisor(4, 7) - 1 - >>> greatest_common_divisor(0, 10) - 10 - """ - return b if a == 0 else greatest_common_divisor(b % a, a) +from maths.greatest_common_divisor import greatest_common_divisor class HillCipher: diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index eedc7336804a..44970e8cbc15 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -2,6 +2,8 @@ import random import sys +from maths.greatest_common_divisor import gcd_by_iterative + from . import cryptomath_module, rabin_miller @@ -27,7 +29,7 @@ def generate_key(key_size: int) -> tuple[tuple[int, int], tuple[int, int]]: # Generate e that is relatively prime to (p - 1) * (q - 1) while True: e = random.randrange(2 ** (key_size - 1), 2 ** (key_size)) - if cryptomath_module.gcd(e, (p - 1) * (q - 1)) == 1: + if gcd_by_iterative(e, (p - 1) * (q - 1)) == 1: break # Calculate d that is mod inverse of e diff --git a/maths/carmichael_number.py b/maths/carmichael_number.py index c9c144759246..81712520ffc7 100644 --- a/maths/carmichael_number.py +++ b/maths/carmichael_number.py @@ -10,14 +10,7 @@ Examples of Carmichael Numbers: 561, 1105, ... https://en.wikipedia.org/wiki/Carmichael_number """ - - -def gcd(a: int, b: int) -> int: - if a < b: - return gcd(b, a) - if a % b == 0: - return b - return gcd(b, a % b) +from maths.greatest_common_divisor import greatest_common_divisor def power(x: int, y: int, mod: int) -> int: @@ -33,7 +26,7 @@ def power(x: int, y: int, mod: int) -> int: def is_carmichael_number(n: int) -> bool: b = 2 while b < n: - if gcd(b, n) == 1 and power(b, n - 1, n) != 1: + if greatest_common_divisor(b, n) == 1 and power(b, n - 1, n) != 1: return False b += 1 return True diff --git a/maths/least_common_multiple.py b/maths/least_common_multiple.py index 10cc63ac7990..4f28da8ab2a7 100644 --- a/maths/least_common_multiple.py +++ b/maths/least_common_multiple.py @@ -1,6 +1,8 @@ import unittest from timeit import timeit +from maths.greatest_common_divisor import greatest_common_divisor + def least_common_multiple_slow(first_num: int, second_num: int) -> int: """ @@ -20,26 +22,6 @@ def least_common_multiple_slow(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. diff --git a/maths/primelib.py b/maths/primelib.py index 28b5aee9dcc8..cf01750cf912 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -21,7 +21,6 @@ is_even(number) is_odd(number) -gcd(number1, number2) // greatest common divisor kg_v(number1, number2) // least common multiple get_divisors(number) // all divisors of 'number' inclusive 1, number is_perfect_number(number) @@ -40,6 +39,8 @@ from math import sqrt +from maths.greatest_common_divisor import gcd_by_iterative + def is_prime(number: int) -> bool: """ @@ -317,39 +318,6 @@ 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' - """ - - # precondition - assert ( - isinstance(number1, int) - and isinstance(number2, int) - and (number1 >= 0) - and (number2 >= 0) - ), "'number1' and 'number2' must been positive integer." - - 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" - - return number1 - - -# ---------------------------------------------------- - - def kg_v(number1, number2): """ Least common multiple @@ -567,14 +535,14 @@ def simplify_fraction(numerator, denominator): ), "The arguments must been from type int and 'denominator' != 0" # build the greatest common divisor of numerator and denominator. - gcd_of_fraction = gcd(abs(numerator), abs(denominator)) + gcd_of_fraction = gcd_by_iterative(abs(numerator), abs(denominator)) # precondition assert ( isinstance(gcd_of_fraction, int) and (numerator % gcd_of_fraction == 0) and (denominator % gcd_of_fraction == 0) - ), "Error in function gcd(...,...)" + ), "Error in function gcd_by_iterative(...,...)" return (numerator // gcd_of_fraction, denominator // gcd_of_fraction) diff --git a/project_euler/problem_005/sol2.py b/project_euler/problem_005/sol2.py index 1b3e5e130f03..4558e21fd0f0 100644 --- a/project_euler/problem_005/sol2.py +++ b/project_euler/problem_005/sol2.py @@ -1,3 +1,5 @@ +from maths.greatest_common_divisor import greatest_common_divisor + """ Project Euler Problem 5: https://projecteuler.net/problem=5 @@ -16,23 +18,6 @@ """ -def greatest_common_divisor(x: int, y: int) -> int: - """ - Euclidean Greatest Common Divisor algorithm - - >>> greatest_common_divisor(0, 0) - 0 - >>> greatest_common_divisor(23, 42) - 1 - >>> greatest_common_divisor(15, 33) - 3 - >>> greatest_common_divisor(12345, 67890) - 15 - """ - - return x if y == 0 else greatest_common_divisor(y, x % y) - - def lcm(x: int, y: int) -> int: """ Least Common Multiple. From 53d78b9cc09021c8f65fae41f8b345304a88aedd Mon Sep 17 00:00:00 2001 From: Kausthub Kannan Date: Mon, 9 Oct 2023 20:03:47 +0530 Subject: [PATCH 2424/2908] Added Huber Loss Function (#10141) --- machine_learning/loss_functions/huber_loss.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 machine_learning/loss_functions/huber_loss.py diff --git a/machine_learning/loss_functions/huber_loss.py b/machine_learning/loss_functions/huber_loss.py new file mode 100644 index 000000000000..202e013f2928 --- /dev/null +++ b/machine_learning/loss_functions/huber_loss.py @@ -0,0 +1,52 @@ +""" +Huber Loss Function + +Description: +Huber loss function describes the penalty incurred by an estimation procedure. +It serves as a measure of the model's accuracy in regression tasks. + +Formula: +Huber Loss = if |y_true - y_pred| <= delta then 0.5 * (y_true - y_pred)^2 + else delta * |y_true - y_pred| - 0.5 * delta^2 + +Source: +[Wikipedia - Huber Loss](https://en.wikipedia.org/wiki/Huber_loss) +""" + +import numpy as np + + +def huber_loss(y_true: np.ndarray, y_pred: np.ndarray, delta: float) -> float: + """ + Calculate the mean of Huber Loss. + + Parameters: + - y_true: The true values (ground truth). + - y_pred: The predicted values. + + Returns: + - huber_loss: The mean of Huber Loss between y_true and y_pred. + + Example usage: + >>> true_values = np.array([0.9, 10.0, 2.0, 1.0, 5.2]) + >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) + >>> np.isclose(huber_loss(true_values, predicted_values, 1.0), 2.102) + True + >>> true_labels = np.array([11.0, 21.0, 3.32, 4.0, 5.0]) + >>> predicted_probs = np.array([8.3, 20.8, 2.9, 11.2, 5.0]) + >>> np.isclose(huber_loss(true_labels, predicted_probs, 1.0), 1.80164) + True + """ + + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + huber_mse = 0.5 * (y_true - y_pred) ** 2 + huber_mae = delta * (np.abs(y_true - y_pred) - 0.5 * delta) + return np.where(np.abs(y_true - y_pred) <= delta, huber_mse, huber_mae).mean() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c0da015b7d49f9f6e99fffd279f65c5605a0ebe1 Mon Sep 17 00:00:00 2001 From: Sai Harsha Kottapalli Date: Mon, 9 Oct 2023 20:49:05 +0530 Subject: [PATCH 2425/2908] Add DocTests to diffie.py (#10156) * diffie doctest * fix ut * update doctest --------- Co-authored-by: Harsha Kottapalli --- ciphers/diffie.py | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/ciphers/diffie.py b/ciphers/diffie.py index 4ff90be009c1..1e1e868999b6 100644 --- a/ciphers/diffie.py +++ b/ciphers/diffie.py @@ -1,11 +1,28 @@ from __future__ import annotations -def find_primitive(n: int) -> int | None: - for r in range(1, n): +def find_primitive(modulus: int) -> int | None: + """ + Find a primitive root modulo modulus, if one exists. + + Args: + modulus : The modulus for which to find a primitive root. + + Returns: + The primitive root if one exists, or None if there is none. + + Examples: + >>> find_primitive(7) # Modulo 7 has primitive root 3 + 3 + >>> find_primitive(11) # Modulo 11 has primitive root 2 + 2 + >>> find_primitive(8) == None # Modulo 8 has no primitive root + True + """ + for r in range(1, modulus): li = [] - for x in range(n - 1): - val = pow(r, x, n) + for x in range(modulus - 1): + val = pow(r, x, modulus) if val in li: break li.append(val) @@ -15,18 +32,22 @@ def find_primitive(n: int) -> int | None: if __name__ == "__main__": - q = int(input("Enter a prime number q: ")) - a = find_primitive(q) - if a is None: - print(f"Cannot find the primitive for the value: {a!r}") + import doctest + + doctest.testmod() + + prime = int(input("Enter a prime number q: ")) + primitive_root = find_primitive(prime) + if primitive_root is None: + print(f"Cannot find the primitive for the value: {primitive_root!r}") else: a_private = int(input("Enter private key of A: ")) - a_public = pow(a, a_private, q) + a_public = pow(primitive_root, a_private, prime) b_private = int(input("Enter private key of B: ")) - b_public = pow(a, b_private, q) + b_public = pow(primitive_root, b_private, prime) - a_secret = pow(b_public, a_private, q) - b_secret = pow(a_public, b_private, q) + a_secret = pow(b_public, a_private, prime) + b_secret = pow(a_public, b_private, prime) print("The key value generated by A is: ", a_secret) print("The key value generated by B is: ", b_secret) From ba828fe621d1f5623fffcf0014b243da3a6122fc Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Mon, 9 Oct 2023 20:46:38 +0500 Subject: [PATCH 2426/2908] test_digital_image_processing -> test_local_binary_pattern replacing a large image with a smaller one (#10161) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Replaced lena.jpg with lena_small.jpg to make tests faster. * Update digital_image_processing/test_digital_image_processing.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test_digital_image_processing.py tests fail, I'll try an empty commit * Apply suggestions from code review * Update test_digital_image_processing.py added clarifications * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test_digital_image_processing.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- .../test_digital_image_processing.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 528b4bc3b74c..2e5630458c8e 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -96,9 +96,16 @@ def test_nearest_neighbour( def test_local_binary_pattern(): - file_path = "digital_image_processing/image_data/lena.jpg" + # pull request 10161 before: + # "digital_image_processing/image_data/lena.jpg" + # after: "digital_image_processing/image_data/lena_small.jpg" - # Reading the image and converting it to grayscale. + from os import getenv # Speed up our Continuous Integration tests + + file_name = "lena_small.jpg" if getenv("CI") else "lena.jpg" + file_path = f"digital_image_processing/image_data/{file_name}" + + # Reading the image and converting it to grayscale image = imread(file_path, 0) # Test for get_neighbors_pixel function() return not None From 844270c6e91387940e062a1522f58bde1026bb08 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 9 Oct 2023 19:42:07 +0200 Subject: [PATCH 2427/2908] Remove backslashes from is_palindrome.py (#10169) @SaiHarshaK Fixes https://github.com/TheAlgorithms/Python/pull/10081#discussion_r1349651289 --- data_structures/linked_list/is_palindrome.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data_structures/linked_list/is_palindrome.py b/data_structures/linked_list/is_palindrome.py index 7d89f085c67f..f949d9a2f201 100644 --- a/data_structures/linked_list/is_palindrome.py +++ b/data_structures/linked_list/is_palindrome.py @@ -147,9 +147,11 @@ def is_palindrome_dict(head: ListNode | None) -> bool: >>> is_palindrome_dict(ListNode(1, ListNode(2, ListNode(2, ListNode(1))))) True - >>> is_palindrome_dict(\ - ListNode(\ - 1, ListNode(2, ListNode(1, ListNode(3, ListNode(2, ListNode(1))))))) + >>> is_palindrome_dict( + ... ListNode( + ... 1, ListNode(2, ListNode(1, ListNode(3, ListNode(2, ListNode(1))))) + ... ) + ... ) False """ if not head or not head.next_node: From 5d0a46814e5b69f79d623187912c0f81ab5ab7a7 Mon Sep 17 00:00:00 2001 From: aryan1165 <111041731+aryan1165@users.noreply.github.com> Date: Tue, 10 Oct 2023 01:08:04 +0530 Subject: [PATCH 2428/2908] Added ciphers/permutation_cipher.py. (#9163) * Added permutation_cipher.py * Added type hints for parameters * Added doctest in functions * Update ciphers/permutation_cipher.py Ya i felt the same but held back because there is a implementation of transposition_cipher.py. But that's is different from the one i have implemented here. Co-authored-by: Tianyi Zheng * Update ciphers/permutation_cipher.py Co-authored-by: Tianyi Zheng * Update ciphers/permutation_cipher.py Co-authored-by: Tianyi Zheng * Update ciphers/permutation_cipher.py Co-authored-by: Tianyi Zheng * Update ciphers/permutation_cipher.py Co-authored-by: Tianyi Zheng * Update ciphers/permutation_cipher.py Co-authored-by: Tianyi Zheng * Update ciphers/permutation_cipher.py Co-authored-by: Tianyi Zheng * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes --------- Co-authored-by: Tianyi Zheng Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ciphers/permutation_cipher.py | 142 ++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 ciphers/permutation_cipher.py diff --git a/ciphers/permutation_cipher.py b/ciphers/permutation_cipher.py new file mode 100644 index 000000000000..c3f3fd1f7f94 --- /dev/null +++ b/ciphers/permutation_cipher.py @@ -0,0 +1,142 @@ +""" +The permutation cipher, also called the transposition cipher, is a simple encryption +technique that rearranges the characters in a message based on a secret key. It +divides the message into blocks and applies a permutation to the characters within +each block according to the key. The key is a sequence of unique integers that +determine the order of character rearrangement. + +For more info: https://www.nku.edu/~christensen/1402%20permutation%20ciphers.pdf +""" +import random + + +def generate_valid_block_size(message_length: int) -> int: + """ + Generate a valid block size that is a factor of the message length. + + Args: + message_length (int): The length of the message. + + Returns: + int: A valid block size. + + Example: + >>> random.seed(1) + >>> generate_valid_block_size(12) + 3 + """ + block_sizes = [ + block_size + for block_size in range(2, message_length + 1) + if message_length % block_size == 0 + ] + return random.choice(block_sizes) + + +def generate_permutation_key(block_size: int) -> list[int]: + """ + Generate a random permutation key of a specified block size. + + Args: + block_size (int): The size of each permutation block. + + Returns: + list[int]: A list containing a random permutation of digits. + + Example: + >>> random.seed(0) + >>> generate_permutation_key(4) + [2, 0, 1, 3] + """ + digits = list(range(block_size)) + random.shuffle(digits) + return digits + + +def encrypt( + message: str, key: list[int] | None = None, block_size: int | None = None +) -> tuple[str, list[int]]: + """ + Encrypt a message using a permutation cipher with block rearrangement using a key. + + Args: + message (str): The plaintext message to be encrypted. + key (list[int]): The permutation key for decryption. + block_size (int): The size of each permutation block. + + Returns: + tuple: A tuple containing the encrypted message and the encryption key. + + Example: + >>> encrypted_message, key = encrypt("HELLO WORLD") + >>> decrypted_message = decrypt(encrypted_message, key) + >>> decrypted_message + 'HELLO WORLD' + """ + message = message.upper() + message_length = len(message) + + if key is None or block_size is None: + block_size = generate_valid_block_size(message_length) + key = generate_permutation_key(block_size) + + encrypted_message = "" + + for i in range(0, message_length, block_size): + block = message[i : i + block_size] + rearranged_block = [block[digit] for digit in key] + encrypted_message += "".join(rearranged_block) + + return encrypted_message, key + + +def decrypt(encrypted_message: str, key: list[int]) -> str: + """ + Decrypt an encrypted message using a permutation cipher with block rearrangement. + + Args: + encrypted_message (str): The encrypted message. + key (list[int]): The permutation key for decryption. + + Returns: + str: The decrypted plaintext message. + + Example: + >>> encrypted_message, key = encrypt("HELLO WORLD") + >>> decrypted_message = decrypt(encrypted_message, key) + >>> decrypted_message + 'HELLO WORLD' + """ + key_length = len(key) + decrypted_message = "" + + for i in range(0, len(encrypted_message), key_length): + block = encrypted_message[i : i + key_length] + original_block = [""] * key_length + for j, digit in enumerate(key): + original_block[digit] = block[j] + decrypted_message += "".join(original_block) + + return decrypted_message + + +def main() -> None: + """ + Driver function to pass message to get encrypted, then decrypted. + + Example: + >>> main() + Decrypted message: HELLO WORLD + """ + message = "HELLO WORLD" + encrypted_message, key = encrypt(message) + + decrypted_message = decrypt(encrypted_message, key) + print(f"Decrypted message: {decrypted_message}") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From b0aa35c7b360f1d141705b97c89d51603a3461a6 Mon Sep 17 00:00:00 2001 From: Dean Bring Date: Mon, 9 Oct 2023 14:21:46 -0700 Subject: [PATCH 2429/2908] Added the Chebyshev distance function (#10144) * Added the Chebyshev distance function * Remove float cast and made error handling more precise --- maths/chebyshev_distance.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 maths/chebyshev_distance.py diff --git a/maths/chebyshev_distance.py b/maths/chebyshev_distance.py new file mode 100644 index 000000000000..4801d391621f --- /dev/null +++ b/maths/chebyshev_distance.py @@ -0,0 +1,20 @@ +def chebyshev_distance(point_a: list[float], point_b: list[float]) -> float: + """ + This function calculates the Chebyshev distance (also known as the + Chessboard distance) between two n-dimensional points represented as lists. + + https://en.wikipedia.org/wiki/Chebyshev_distance + + >>> chebyshev_distance([1.0, 1.0], [2.0, 2.0]) + 1.0 + >>> chebyshev_distance([1.0, 1.0, 9.0], [2.0, 2.0, -5.2]) + 14.2 + >>> chebyshev_distance([1.0], [2.0, 2.0]) + Traceback (most recent call last): + ... + ValueError: Both points must have the same dimension. + """ + if len(point_a) != len(point_b): + raise ValueError("Both points must have the same dimension.") + + return max(abs(a - b) for a, b in zip(point_a, point_b)) From 53638fcec4ff990ced9afb569c18b927df652596 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 23:38:32 +0200 Subject: [PATCH 2430/2908] [pre-commit.ci] pre-commit autoupdate (#10197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a88dcc07622..7340a0fd08ee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-executables-have-shebangs - id: check-toml From b9a797f3d4f1a66da1e213bd92e08fa9cf6c3643 Mon Sep 17 00:00:00 2001 From: Dean Bring Date: Mon, 9 Oct 2023 16:00:37 -0700 Subject: [PATCH 2431/2908] Added the Minkowski distance function (#10143) * Added the Minkowski distance function * Made error handling more precise * Added note about floating point errors and corresponding doctest --- maths/minkowski_distance.py | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 maths/minkowski_distance.py diff --git a/maths/minkowski_distance.py b/maths/minkowski_distance.py new file mode 100644 index 000000000000..3237124e8d36 --- /dev/null +++ b/maths/minkowski_distance.py @@ -0,0 +1,45 @@ +def minkowski_distance( + point_a: list[float], + point_b: list[float], + order: int, +) -> float: + """ + This function calculates the Minkowski distance for a given order between + two n-dimensional points represented as lists. For the case of order = 1, + the Minkowski distance degenerates to the Manhattan distance. For + order = 2, the usual Euclidean distance is obtained. + + https://en.wikipedia.org/wiki/Minkowski_distance + + Note: due to floating point calculation errors the output of this + function may be inaccurate. + + >>> minkowski_distance([1.0, 1.0], [2.0, 2.0], 1) + 2.0 + >>> minkowski_distance([1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0], 2) + 8.0 + >>> import numpy as np + >>> np.isclose(5.0, minkowski_distance([5.0], [0.0], 3)) + True + >>> minkowski_distance([1.0], [2.0], -1) + Traceback (most recent call last): + ... + ValueError: The order must be greater than or equal to 1. + >>> minkowski_distance([1.0], [1.0, 2.0], 1) + Traceback (most recent call last): + ... + ValueError: Both points must have the same dimension. + """ + if order < 1: + raise ValueError("The order must be greater than or equal to 1.") + + if len(point_a) != len(point_b): + raise ValueError("Both points must have the same dimension.") + + return sum(abs(a - b) ** order for a, b in zip(point_a, point_b)) ** (1 / order) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 7b996e2c221aa88b5688ea08f2bb3a391b5be2c6 Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:16:02 +0500 Subject: [PATCH 2432/2908] backtracking -> word_search - replacing the example in doctest (#10188) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Replacing the example in doctest with a less resource-intensive example. --- backtracking/word_search.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/backtracking/word_search.py b/backtracking/word_search.py index c9d52012b42b..8a9b2f1b5359 100644 --- a/backtracking/word_search.py +++ b/backtracking/word_search.py @@ -98,13 +98,7 @@ def word_exists(board: list[list[str]], word: str) -> bool: False >>> word_exists([["A"]], "A") True - >>> word_exists([["A","A","A","A","A","A"], - ... ["A","A","A","A","A","A"], - ... ["A","A","A","A","A","A"], - ... ["A","A","A","A","A","A"], - ... ["A","A","A","A","A","B"], - ... ["A","A","A","A","B","A"]], - ... "AAAAAAAAAAAAABB") + >>> word_exists([["B", "A", "A"], ["A", "A", "A"], ["A", "B", "A"]], "ABB") False >>> word_exists([["A"]], 123) Traceback (most recent call last): From 4f8fa3c44a29cafaed64a73588a309e88d1f3ded Mon Sep 17 00:00:00 2001 From: Md Mahiuddin <68785084+mahiuddin-dev@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:19:40 +0600 Subject: [PATCH 2433/2908] TypeError for non-integer input (#9250) * type error check * remove str input * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/number_of_digits.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/maths/number_of_digits.py b/maths/number_of_digits.py index 86bc67f72490..bb9c0d248fd1 100644 --- a/maths/number_of_digits.py +++ b/maths/number_of_digits.py @@ -16,7 +16,15 @@ def num_digits(n: int) -> int: 1 >>> num_digits(-123456) 6 + >>> num_digits('123') # Raises a TypeError for non-integer input + Traceback (most recent call last): + ... + TypeError: Input must be an integer """ + + if not isinstance(n, int): + raise TypeError("Input must be an integer") + digits = 0 n = abs(n) while True: @@ -42,7 +50,15 @@ def num_digits_fast(n: int) -> int: 1 >>> num_digits_fast(-123456) 6 + >>> num_digits('123') # Raises a TypeError for non-integer input + Traceback (most recent call last): + ... + TypeError: Input must be an integer """ + + if not isinstance(n, int): + raise TypeError("Input must be an integer") + return 1 if n == 0 else math.floor(math.log(abs(n), 10) + 1) @@ -61,7 +77,15 @@ def num_digits_faster(n: int) -> int: 1 >>> num_digits_faster(-123456) 6 + >>> num_digits('123') # Raises a TypeError for non-integer input + Traceback (most recent call last): + ... + TypeError: Input must be an integer """ + + if not isinstance(n, int): + raise TypeError("Input must be an integer") + return len(str(abs(n))) From 1b4c4e7db216305e059cc087c3f09bc6d3e17575 Mon Sep 17 00:00:00 2001 From: dimonalik <114773527+dimonalik@users.noreply.github.com> Date: Tue, 10 Oct 2023 07:34:36 +0300 Subject: [PATCH 2434/2908] Made problem explanation more clear (#9841) * Update minimum_steps_to_one.py Made the problem explanation more clear and readable * updating DIRECTORY.md * Apply suggestions from code review --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- dynamic_programming/minimum_steps_to_one.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic_programming/minimum_steps_to_one.py b/dynamic_programming/minimum_steps_to_one.py index 8785027fbff3..68eaf56e21a7 100644 --- a/dynamic_programming/minimum_steps_to_one.py +++ b/dynamic_programming/minimum_steps_to_one.py @@ -1,7 +1,7 @@ """ YouTube Explanation: https://www.youtube.com/watch?v=f2xi3c1S95M -Given an integer n, return the minimum steps to 1 +Given an integer n, return the minimum steps from n to 1 AVAILABLE STEPS: * Decrement by 1 From 9c02f1220e571f2922855e245c5a92d4f2220f8a Mon Sep 17 00:00:00 2001 From: AkhilYadavPadala <142014008+AkhilYadavPadala@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:43:32 +0530 Subject: [PATCH 2435/2908] seperation between description and docstrings (#9687) * seperation between description and docstrings * Update maths/factorial.py --------- Co-authored-by: sarayu sree Co-authored-by: Tianyi Zheng --- maths/factorial.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maths/factorial.py b/maths/factorial.py index 18cacdef9b1f..aaf90f384bb9 100644 --- a/maths/factorial.py +++ b/maths/factorial.py @@ -1,4 +1,5 @@ -"""Factorial of a positive integer -- https://en.wikipedia.org/wiki/Factorial +""" +Factorial of a positive integer -- https://en.wikipedia.org/wiki/Factorial """ From f3acb52cadade9e7d012bf7f50cad32669b67b75 Mon Sep 17 00:00:00 2001 From: Paarth Goyal <138299656+pluto-tofu@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:54:04 +0530 Subject: [PATCH 2436/2908] Added the algorithm to compute Reynolds number in the physics section (#9913) * added the algorithm to compute Reynolds number * fixed file name issue * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- physics/reynolds_number.py | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 physics/reynolds_number.py diff --git a/physics/reynolds_number.py b/physics/reynolds_number.py new file mode 100644 index 000000000000..dffe690f8822 --- /dev/null +++ b/physics/reynolds_number.py @@ -0,0 +1,63 @@ +""" +Title : computing the Reynolds number to find + out the type of flow (laminar or turbulent) + +Reynolds number is a dimensionless quantity that is used to determine +the type of flow pattern as laminar or turbulent while flowing through a +pipe. Reynolds number is defined by the ratio of inertial forces to that of +viscous forces. + +R = Inertial Forces / Viscous Forces +R = (ρ * V * D)/μ + +where : +ρ = Density of fluid (in Kg/m^3) +D = Diameter of pipe through which fluid flows (in m) +V = Velocity of flow of the fluid (in m/s) +μ = Viscosity of the fluid (in Ns/m^2) + +If the Reynolds number calculated is high (greater than 2000), then the +flow through the pipe is said to be turbulent. If Reynolds number is low +(less than 2000), the flow is said to be laminar. Numerically, these are +acceptable values, although in general the laminar and turbulent flows +are classified according to a range. Laminar flow falls below Reynolds +number of 1100 and turbulent falls in a range greater than 2200. +Laminar flow is the type of flow in which the fluid travels smoothly in +regular paths. Conversely, turbulent flow isn't smooth and follows an +irregular path with lots of mixing. + +Reference : https://byjus.com/physics/reynolds-number/ +""" + + +def reynolds_number( + density: float, velocity: float, diameter: float, viscosity: float +) -> float: + """ + >>> reynolds_number(900, 2.5, 0.05, 0.4) + 281.25 + >>> reynolds_number(450, 3.86, 0.078, 0.23) + 589.0695652173912 + >>> reynolds_number(234, -4.5, 0.3, 0.44) + 717.9545454545454 + >>> reynolds_number(-90, 2, 0.045, 1) + Traceback (most recent call last): + ... + ValueError: please ensure that density, diameter and viscosity are positive + >>> reynolds_number(0, 2, -0.4, -2) + Traceback (most recent call last): + ... + ValueError: please ensure that density, diameter and viscosity are positive + """ + + if density <= 0 or diameter <= 0 or viscosity <= 0: + raise ValueError( + "please ensure that density, diameter and viscosity are positive" + ) + return (density * abs(velocity) * diameter) / viscosity + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 6d136036672072a2c4870da7741d4ad3026a7357 Mon Sep 17 00:00:00 2001 From: Vipin Karthic <143083087+vipinkarthic@users.noreply.github.com> Date: Tue, 10 Oct 2023 11:22:37 +0530 Subject: [PATCH 2437/2908] Fixes #9943 Added Doctests to binary_exponentiation_3.py (#10121) * Python mirror_formulae.py is added to the repository * Changes done after reading readme.md * Changes for running doctest on all platforms * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Change 2 for Doctests * Changes for doctest 2 * updating DIRECTORY.md * Doctest whitespace error rectification to mirror_formulae.py * updating DIRECTORY.md * Adding Thermodynamic Work Done Formulae * Work done on/by body in a thermodynamic setting * updating DIRECTORY.md * updating DIRECTORY.md * Doctest adiition to binary_exponentiation_3.py * Change 1 * updating DIRECTORY.md * Rename binary_exponentiation_3.py to binary_exponentiation_2.py * updating DIRECTORY.md * updating DIRECTORY.md * Formatting --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Tianyi Zheng --- DIRECTORY.md | 26 ++++++++++++-- maths/binary_exponentiation_2.py | 59 +++++++++++++++++++------------- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index b1a23a239b01..015efb3c796d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -54,13 +54,12 @@ * [Largest Pow Of Two Le Num](bit_manipulation/largest_pow_of_two_le_num.py) * [Missing Number](bit_manipulation/missing_number.py) * [Numbers Different Signs](bit_manipulation/numbers_different_signs.py) + * [Power Of 4](bit_manipulation/power_of_4.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py) ## Blockchain - * [Chinese Remainder Theorem](blockchain/chinese_remainder_theorem.py) * [Diophantine Equation](blockchain/diophantine_equation.py) - * [Modular Division](blockchain/modular_division.py) ## Boolean Algebra * [And Gate](boolean_algebra/and_gate.py) @@ -101,11 +100,13 @@ * [Diffie Hellman](ciphers/diffie_hellman.py) * [Elgamal Key Generator](ciphers/elgamal_key_generator.py) * [Enigma Machine2](ciphers/enigma_machine2.py) + * [Fractionated Morse Cipher](ciphers/fractionated_morse_cipher.py) * [Hill Cipher](ciphers/hill_cipher.py) * [Mixed Keyword Cypher](ciphers/mixed_keyword_cypher.py) * [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py) * [Morse Code](ciphers/morse_code.py) * [Onepad Cipher](ciphers/onepad_cipher.py) + * [Permutation Cipher](ciphers/permutation_cipher.py) * [Playfair Cipher](ciphers/playfair_cipher.py) * [Polybius](ciphers/polybius.py) * [Porta Cipher](ciphers/porta_cipher.py) @@ -172,6 +173,7 @@ ## Data Structures * Arrays + * [Equilibrium Index In Array](data_structures/arrays/equilibrium_index_in_array.py) * [Median Two Array](data_structures/arrays/median_two_array.py) * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) @@ -352,6 +354,7 @@ * [Smith Waterman](dynamic_programming/smith_waterman.py) * [Subset Generation](dynamic_programming/subset_generation.py) * [Sum Of Subset](dynamic_programming/sum_of_subset.py) + * [Trapped Water](dynamic_programming/trapped_water.py) * [Tribonacci](dynamic_programming/tribonacci.py) * [Viterbi](dynamic_programming/viterbi.py) * [Word Break](dynamic_programming/word_break.py) @@ -360,6 +363,7 @@ * [Apparent Power](electronics/apparent_power.py) * [Builtin Voltage](electronics/builtin_voltage.py) * [Carrier Concentration](electronics/carrier_concentration.py) + * [Charging Capacitor](electronics/charging_capacitor.py) * [Circular Convolution](electronics/circular_convolution.py) * [Coulombs Law](electronics/coulombs_law.py) * [Electric Conductivity](electronics/electric_conductivity.py) @@ -466,6 +470,8 @@ * [Test Min Spanning Tree Prim](graphs/tests/test_min_spanning_tree_prim.py) ## Greedy Methods + * [Best Time To Buy And Sell Stock](greedy_methods/best_time_to_buy_and_sell_stock.py) + * [Fractional Cover Problem](greedy_methods/fractional_cover_problem.py) * [Fractional Knapsack](greedy_methods/fractional_knapsack.py) * [Fractional Knapsack 2](greedy_methods/fractional_knapsack_2.py) * [Gas Station](greedy_methods/gas_station.py) @@ -524,6 +530,10 @@ * Local Weighted Learning * [Local Weighted Learning](machine_learning/local_weighted_learning/local_weighted_learning.py) * [Logistic Regression](machine_learning/logistic_regression.py) + * Loss Functions + * [Binary Cross Entropy](machine_learning/loss_functions/binary_cross_entropy.py) + * [Huber Loss](machine_learning/loss_functions/huber_loss.py) + * [Mean Squared Error](machine_learning/loss_functions/mean_squared_error.py) * [Mfcc](machine_learning/mfcc.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polynomial Regression](machine_learning/polynomial_regression.py) @@ -564,7 +574,9 @@ * [Carmichael Number](maths/carmichael_number.py) * [Catalan Number](maths/catalan_number.py) * [Ceil](maths/ceil.py) + * [Chebyshev Distance](maths/chebyshev_distance.py) * [Check Polygon](maths/check_polygon.py) + * [Chinese Remainder Theorem](maths/chinese_remainder_theorem.py) * [Chudnovsky Algorithm](maths/chudnovsky_algorithm.py) * [Collatz Sequence](maths/collatz_sequence.py) * [Combinations](maths/combinations.py) @@ -591,6 +603,7 @@ * [Gaussian](maths/gaussian.py) * [Gaussian Error Linear Unit](maths/gaussian_error_linear_unit.py) * [Gcd Of N Numbers](maths/gcd_of_n_numbers.py) + * [Germain Primes](maths/germain_primes.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) * [Greedy Coin Change](maths/greedy_coin_change.py) * [Hamming Numbers](maths/hamming_numbers.py) @@ -618,7 +631,9 @@ * [Matrix Exponentiation](maths/matrix_exponentiation.py) * [Max Sum Sliding Window](maths/max_sum_sliding_window.py) * [Median Of Two Arrays](maths/median_of_two_arrays.py) + * [Minkowski Distance](maths/minkowski_distance.py) * [Mobius Function](maths/mobius_function.py) + * [Modular Division](maths/modular_division.py) * [Modular Exponential](maths/modular_exponential.py) * [Monte Carlo](maths/monte_carlo.py) * [Monte Carlo Dice](maths/monte_carlo_dice.py) @@ -720,12 +735,16 @@ ## Neural Network * [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py) * Activation Functions + * [Binary Step](neural_network/activation_functions/binary_step.py) * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) * [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py) * [Mish](neural_network/activation_functions/mish.py) * [Rectified Linear Unit](neural_network/activation_functions/rectified_linear_unit.py) * [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py) * [Sigmoid Linear Unit](neural_network/activation_functions/sigmoid_linear_unit.py) + * [Soboleva Modified Hyperbolic Tangent](neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py) + * [Softplus](neural_network/activation_functions/softplus.py) + * [Squareplus](neural_network/activation_functions/squareplus.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Perceptron](neural_network/perceptron.py) @@ -779,6 +798,7 @@ * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) * [Photoelectric Effect](physics/photoelectric_effect.py) * [Potential Energy](physics/potential_energy.py) + * [Reynolds Number](physics/reynolds_number.py) * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Shear Stress](physics/shear_stress.py) * [Speed Of Sound](physics/speed_of_sound.py) @@ -1101,6 +1121,7 @@ * [Interpolation Search](searches/interpolation_search.py) * [Jump Search](searches/jump_search.py) * [Linear Search](searches/linear_search.py) + * [Median Of Medians](searches/median_of_medians.py) * [Quick Select](searches/quick_select.py) * [Sentinel Linear Search](searches/sentinel_linear_search.py) * [Simple Binary Search](searches/simple_binary_search.py) @@ -1201,6 +1222,7 @@ * [Snake Case To Camel Pascal Case](strings/snake_case_to_camel_pascal_case.py) * [Split](strings/split.py) * [String Switch Case](strings/string_switch_case.py) + * [Strip](strings/strip.py) * [Text Justification](strings/text_justification.py) * [Top K Frequent Words](strings/top_k_frequent_words.py) * [Upper](strings/upper.py) diff --git a/maths/binary_exponentiation_2.py b/maths/binary_exponentiation_2.py index 9cd143e09207..edb6b66b2594 100644 --- a/maths/binary_exponentiation_2.py +++ b/maths/binary_exponentiation_2.py @@ -1,17 +1,33 @@ """ -* Binary Exponentiation for Powers -* This is a method to find a^b in a time complexity of O(log b) -* This is one of the most commonly used methods of finding powers. -* Also useful in cases where solution to (a^b)%c is required, -* where a,b,c can be numbers over the computers calculation limits. -* Done using iteration, can also be done using recursion - -* @author chinmoy159 -* @version 1.0 dated 10/08/2017 +Binary Exponentiation +This is a method to find a^b in O(log b) time complexity +This is one of the most commonly used methods of exponentiation +It's also useful when the solution to (a^b) % c is required because a, b, c may be +over the computer's calculation limits + +Let's say you need to calculate a ^ b +- RULE 1 : a ^ b = (a*a) ^ (b/2) ---- example : 4 ^ 4 = (4*4) ^ (4/2) = 16 ^ 2 +- RULE 2 : IF b is odd, then a ^ b = a * (a ^ (b - 1)), where b - 1 is even +Once b is even, repeat the process until b = 1 or b = 0, because a^1 = a and a^0 = 1 + +For modular exponentiation, we use the fact that (a*b) % c = ((a%c) * (b%c)) % c +Now apply RULE 1 or 2 as required + +@author chinmoy159 """ def b_expo(a: int, b: int) -> int: + """ + >>> b_expo(2, 10) + 1024 + >>> b_expo(9, 0) + 1 + >>> b_expo(0, 12) + 0 + >>> b_expo(4, 12) + 16777216 + """ res = 1 while b > 0: if b & 1: @@ -24,6 +40,16 @@ def b_expo(a: int, b: int) -> int: def b_expo_mod(a: int, b: int, c: int) -> int: + """ + >>> b_expo_mod(2, 10, 1000000007) + 1024 + >>> b_expo_mod(11, 13, 19) + 11 + >>> b_expo_mod(0, 19, 20) + 0 + >>> b_expo_mod(15, 5, 4) + 3 + """ res = 1 while b > 0: if b & 1: @@ -33,18 +59,3 @@ def b_expo_mod(a: int, b: int, c: int) -> int: b >>= 1 return res - - -""" -* Wondering how this method works ! -* It's pretty simple. -* Let's say you need to calculate a ^ b -* RULE 1 : a ^ b = (a*a) ^ (b/2) ---- example : 4 ^ 4 = (4*4) ^ (4/2) = 16 ^ 2 -* RULE 2 : IF b is ODD, then ---- a ^ b = a * (a ^ (b - 1)) :: where (b - 1) is even. -* Once b is even, repeat the process to get a ^ b -* Repeat the process till b = 1 OR b = 0, because a^1 = a AND a^0 = 1 -* -* As far as the modulo is concerned, -* the fact : (a*b) % c = ((a%c) * (b%c)) % c -* Now apply RULE 1 OR 2 whichever is required. -""" From 59fc0cefefce77718044eb797e2c33cf8a7e1f9a Mon Sep 17 00:00:00 2001 From: Arnav Kohli <95236897+THEGAMECHANGER416@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:50:49 +0530 Subject: [PATCH 2438/2908] Added categorical_crossentropy loss function (#10152) --- .../categorical_cross_entropy.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 machine_learning/loss_functions/categorical_cross_entropy.py diff --git a/machine_learning/loss_functions/categorical_cross_entropy.py b/machine_learning/loss_functions/categorical_cross_entropy.py new file mode 100644 index 000000000000..68f98902b473 --- /dev/null +++ b/machine_learning/loss_functions/categorical_cross_entropy.py @@ -0,0 +1,85 @@ +""" +Categorical Cross-Entropy Loss + +This function calculates the Categorical Cross-Entropy Loss between true class +labels and predicted class probabilities. + +Formula: +Categorical Cross-Entropy Loss = -Σ(y_true * ln(y_pred)) + +Resources: +- [Wikipedia - Cross entropy](https://en.wikipedia.org/wiki/Cross_entropy) +""" + +import numpy as np + + +def categorical_cross_entropy( + y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-15 +) -> float: + """ + Calculate Categorical Cross-Entropy Loss between true class labels and + predicted class probabilities. + + Parameters: + - y_true: True class labels (one-hot encoded) as a NumPy array. + - y_pred: Predicted class probabilities as a NumPy array. + - epsilon: Small constant to avoid numerical instability. + + Returns: + - ce_loss: Categorical Cross-Entropy Loss as a floating-point number. + + Example: + >>> true_labels = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1], [0.0, 0.1, 0.9]]) + >>> categorical_cross_entropy(true_labels, pred_probs) + 0.567395975254385 + + >>> y_true = np.array([[1, 0], [0, 1]]) + >>> y_pred = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(y_true, y_pred) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same shape. + + >>> y_true = np.array([[2, 0, 1], [1, 0, 0]]) + >>> y_pred = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(y_true, y_pred) + Traceback (most recent call last): + ... + ValueError: y_true must be one-hot encoded. + + >>> y_true = np.array([[1, 0, 1], [1, 0, 0]]) + >>> y_pred = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(y_true, y_pred) + Traceback (most recent call last): + ... + ValueError: y_true must be one-hot encoded. + + >>> y_true = np.array([[1, 0, 0], [0, 1, 0]]) + >>> y_pred = np.array([[0.9, 0.1, 0.1], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(y_true, y_pred) + Traceback (most recent call last): + ... + ValueError: Predicted probabilities must sum to approximately 1. + """ + if y_true.shape != y_pred.shape: + raise ValueError("Input arrays must have the same shape.") + + if np.any((y_true != 0) & (y_true != 1)) or np.any(y_true.sum(axis=1) != 1): + raise ValueError("y_true must be one-hot encoded.") + + if not np.all(np.isclose(np.sum(y_pred, axis=1), 1, rtol=epsilon, atol=epsilon)): + raise ValueError("Predicted probabilities must sum to approximately 1.") + + # Clip predicted probabilities to avoid log(0) + y_pred = np.clip(y_pred, epsilon, 1) + + # Calculate categorical cross-entropy loss + return -np.sum(y_true * np.log(y_pred)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0b440285e813c54cda188eac278bda6fa4b1169f Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:24:51 +0500 Subject: [PATCH 2439/2908] Gaussian_elemination - change to remove warning (#10221) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Removes the warning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation --- arithmetic_analysis/gaussian_elimination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arithmetic_analysis/gaussian_elimination.py b/arithmetic_analysis/gaussian_elimination.py index 13f509a4f117..a1a35131b157 100644 --- a/arithmetic_analysis/gaussian_elimination.py +++ b/arithmetic_analysis/gaussian_elimination.py @@ -34,7 +34,7 @@ def retroactive_resolution( x: NDArray[float64] = np.zeros((rows, 1), dtype=float) for row in reversed(range(rows)): total = np.dot(coefficients[row, row + 1 :], x[row + 1 :]) - x[row, 0] = (vector[row] - total) / coefficients[row, row] + x[row, 0] = (vector[row][0] - total[0]) / coefficients[row, row] return x From 5be5d21bed4bb546c81b5771bebca336978111e7 Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Wed, 11 Oct 2023 00:52:53 +0800 Subject: [PATCH 2440/2908] Add tests for infix_2_postfix() in infix_to_prefix_conversion.py (#10095) * Add doctests, exceptions, type hints and fix bug for infix_to_prefix_conversion.py Add doctests Add exceptions for expressions with invalid bracket positions Add type hints for functions Fix a bug on line 53 (57 in PR) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Change type hints in infix_to_prefix_conversion.py * Remove printing trailing whitespace in the output table * Fix type hint errors * Fix doctests * Adjust table convention in output and doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add doctests for infix_2_postfix() * Update print_width * Update print_width * Fix the doctests --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../stacks/infix_to_prefix_conversion.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/data_structures/stacks/infix_to_prefix_conversion.py b/data_structures/stacks/infix_to_prefix_conversion.py index 6f6d5d57e2cb..1127211d59f9 100644 --- a/data_structures/stacks/infix_to_prefix_conversion.py +++ b/data_structures/stacks/infix_to_prefix_conversion.py @@ -16,6 +16,39 @@ def infix_2_postfix(infix): + """ + >>> infix_2_postfix("a+b^c") # doctest: +NORMALIZE_WHITESPACE + Symbol | Stack | Postfix + ---------------------------- + a | | a + + | + | a + b | + | ab + ^ | +^ | ab + c | +^ | abc + | + | abc^ + | | abc^+ + 'abc^+' + >>> infix_2_postfix("1*((-a)*2+b)") + Traceback (most recent call last): + ... + KeyError: '(' + >>> infix_2_postfix("") + Symbol | Stack | Postfix + ---------------------------- + '' + >>> infix_2_postfix("(()") # doctest: +NORMALIZE_WHITESPACE + Symbol | Stack | Postfix + ---------------------------- + ( | ( | + ( | (( | + ) | ( | + | | ( + '(' + >>> infix_2_postfix("())") + Traceback (most recent call last): + ... + IndexError: list index out of range + """ stack = [] post_fix = [] priority = { @@ -74,6 +107,42 @@ def infix_2_postfix(infix): def infix_2_prefix(infix): + """ + >>> infix_2_prefix("a+b^c") # doctest: +NORMALIZE_WHITESPACE + Symbol | Stack | Postfix + ---------------------------- + c | | c + ^ | ^ | c + b | ^ | cb + + | + | cb^ + a | + | cb^a + | | cb^a+ + '+a^bc' + + >>> infix_2_prefix("1*((-a)*2+b)") + Traceback (most recent call last): + ... + KeyError: '(' + + >>> infix_2_prefix('') + Symbol | Stack | Postfix + ---------------------------- + '' + + >>> infix_2_prefix('(()') + Traceback (most recent call last): + ... + IndexError: list index out of range + + >>> infix_2_prefix('())') # doctest: +NORMALIZE_WHITESPACE + Symbol | Stack | Postfix + ---------------------------- + ( | ( | + ( | (( | + ) | ( | + | | ( + '(' + """ infix = list(infix[::-1]) # reverse the infix equation for i in range(len(infix)): @@ -88,6 +157,10 @@ def infix_2_prefix(infix): if __name__ == "__main__": + from doctest import testmod + + testmod() + 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)") From 9a5a6c663cefb8cbc63329c27188f64462072a4c Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Wed, 11 Oct 2023 01:14:13 +0500 Subject: [PATCH 2441/2908] carmichael_number - add doctests (#10038) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Added doctests * Update carmichael_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update carmichael_number.py I make an empty commit to reset: tests are failing. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update carmichael_number.py Made changes taking into account the addition: from maths.greatest_common_divisor import greatest_common_divisor. Now instead of gcd it is used: greatest_common_divisor. * Update carmichael_number.py * Update carmichael_number.py * Update carmichael_number.py I added a check for 0 and negative numbers in the tests and the code itself. Simplified obtaining the final result. * Update carmichael_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/carmichael_number.py Co-authored-by: Tianyi Zheng * Update carmichael_number.py * Update carmichael_number.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/carmichael_number.py | 55 +++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/maths/carmichael_number.py b/maths/carmichael_number.py index 81712520ffc7..08b5c70e8fe7 100644 --- a/maths/carmichael_number.py +++ b/maths/carmichael_number.py @@ -10,10 +10,21 @@ Examples of Carmichael Numbers: 561, 1105, ... https://en.wikipedia.org/wiki/Carmichael_number """ + from maths.greatest_common_divisor import greatest_common_divisor def power(x: int, y: int, mod: int) -> int: + """ + + Examples: + >>> power(2, 15, 3) + 2 + + >>> power(5, 1, 30) + 5 + """ + if y == 0: return 1 temp = power(x, y // 2, mod) % mod @@ -24,15 +35,47 @@ def power(x: int, y: int, mod: int) -> int: def is_carmichael_number(n: int) -> bool: - b = 2 - while b < n: - if greatest_common_divisor(b, n) == 1 and power(b, n - 1, n) != 1: - return False - b += 1 - return True + """ + + Examples: + >>> is_carmichael_number(562) + False + + >>> is_carmichael_number(561) + True + + >>> is_carmichael_number(5.1) + Traceback (most recent call last): + ... + ValueError: Number 5.1 must instead be a positive integer + + >>> is_carmichael_number(-7) + Traceback (most recent call last): + ... + ValueError: Number -7 must instead be a positive integer + + >>> is_carmichael_number(0) + Traceback (most recent call last): + ... + ValueError: Number 0 must instead be a positive integer + """ + + if n <= 0 or not isinstance(n, int): + msg = f"Number {n} must instead be a positive integer" + raise ValueError(msg) + + return all( + power(b, n - 1, n) == 1 + for b in range(2, n) + if greatest_common_divisor(b, n) == 1 + ) if __name__ == "__main__": + import doctest + + doctest.testmod() + number = int(input("Enter number: ").strip()) if is_carmichael_number(number): print(f"{number} is a Carmichael Number.") From 00707392332b90cc9babf7258b1de3e0efa0a580 Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Wed, 11 Oct 2023 01:18:31 +0500 Subject: [PATCH 2442/2908] k_means_clust - change to remove warning (#10244) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * the change removes the warning: /home/runner/work/Python/Python/machine_learning/k_means_clust.py:236: FutureWarning: The provided callable is currently using SeriesGroupBy.sum. In a future version of pandas, the provided callable will be used directly. To keep current behavior pass the string "sum" instead. .agg( And /home/runner/work/Python/Python/machine_learning/k_means_clust.py:236: FutureWarning: The provided callable is currently using SeriesGroupBy.mean. In a future version of pandas, the provided callable will be used directly. To keep current behavior pass the string "mean" instead. .agg( --- machine_learning/k_means_clust.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index d93c5addf2ee..3fe151442e2e 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -235,7 +235,7 @@ def report_generator( ] # group by cluster number .agg( [ - ("sum", np.sum), + ("sum", "sum"), ("mean_with_zeros", lambda x: np.mean(np.nan_to_num(x))), ("mean_without_zeros", lambda x: x.replace(0, np.NaN).mean()), ( @@ -248,7 +248,7 @@ def report_generator( ) ), ), - ("mean_with_na", np.mean), + ("mean_with_na", "mean"), ("min", lambda x: x.min()), ("5%", lambda x: x.quantile(0.05)), ("25%", lambda x: x.quantile(0.25)), From c850227bee5efd9383d1cb8150500eb304c809fc Mon Sep 17 00:00:00 2001 From: cornbread-eater <146371786+cornbread-eater@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:07:07 -0700 Subject: [PATCH 2443/2908] Add doctests to primelib.py (#10242) * Update primelib.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/primelib.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/maths/primelib.py b/maths/primelib.py index cf01750cf912..d5c124255e56 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -574,6 +574,11 @@ def fib(n): """ input: positive integer 'n' returns the n-th fibonacci term , indexing by 0 + + >>> fib(5) + 8 + >>> fib(99) + 354224848179261915075 """ # precondition @@ -589,3 +594,9 @@ def fib(n): fib1 = tmp return ans + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 672fda913087ab64f9eb7b3a5600cbf83680fb8e Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:00:49 +0800 Subject: [PATCH 2444/2908] Fix bug and edit doctests for infix_to_prefix_conversion (#10259) * Fix bug and edit doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add type hints, raiseError and other minor adjustments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleaning code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../stacks/infix_to_prefix_conversion.py | 101 +++++++++++------- 1 file changed, 64 insertions(+), 37 deletions(-) diff --git a/data_structures/stacks/infix_to_prefix_conversion.py b/data_structures/stacks/infix_to_prefix_conversion.py index 1127211d59f9..beff421c0cfa 100644 --- a/data_structures/stacks/infix_to_prefix_conversion.py +++ b/data_structures/stacks/infix_to_prefix_conversion.py @@ -15,7 +15,7 @@ """ -def infix_2_postfix(infix): +def infix_2_postfix(infix: str) -> str: """ >>> infix_2_postfix("a+b^c") # doctest: +NORMALIZE_WHITESPACE Symbol | Stack | Postfix @@ -28,22 +28,35 @@ def infix_2_postfix(infix): | + | abc^ | | abc^+ 'abc^+' - >>> infix_2_postfix("1*((-a)*2+b)") - Traceback (most recent call last): - ... - KeyError: '(' + + >>> infix_2_postfix("1*((-a)*2+b)") # doctest: +NORMALIZE_WHITESPACE + Symbol | Stack | Postfix + ------------------------------------------- + 1 | | 1 + * | * | 1 + ( | *( | 1 + ( | *(( | 1 + - | *((- | 1 + a | *((- | 1a + ) | *( | 1a- + * | *(* | 1a- + 2 | *(* | 1a-2 + + | *(+ | 1a-2* + b | *(+ | 1a-2*b + ) | * | 1a-2*b+ + | | 1a-2*b+* + '1a-2*b+*' + >>> infix_2_postfix("") Symbol | Stack | Postfix ---------------------------- '' - >>> infix_2_postfix("(()") # doctest: +NORMALIZE_WHITESPACE - Symbol | Stack | Postfix - ---------------------------- - ( | ( | - ( | (( | - ) | ( | - | | ( - '(' + + >>> infix_2_postfix("(()") + Traceback (most recent call last): + ... + ValueError: invalid expression + >>> infix_2_postfix("())") Traceback (most recent call last): ... @@ -59,7 +72,7 @@ def infix_2_postfix(infix): "+": 1, "-": 1, } # Priority of each operator - print_width = len(infix) if (len(infix) > 7) else 7 + print_width = max(len(infix), 7) # Print table header for output print( @@ -76,6 +89,9 @@ def infix_2_postfix(infix): elif x == "(": stack.append(x) # if x is "(" push to Stack elif x == ")": # if x is ")" pop stack until "(" is encountered + if len(stack) == 0: # close bracket without open bracket + raise IndexError("list index out of range") + while stack[-1] != "(": post_fix.append(stack.pop()) # Pop stack & add the content to Postfix stack.pop() @@ -83,7 +99,7 @@ def infix_2_postfix(infix): if len(stack) == 0: stack.append(x) # If stack is empty, push x to stack else: # while priority of x is not > priority of element in the stack - while len(stack) > 0 and priority[x] <= priority[stack[-1]]: + while stack and stack[-1] != "(" and priority[x] <= priority[stack[-1]]: post_fix.append(stack.pop()) # pop stack & add to Postfix stack.append(x) # push x to stack @@ -95,6 +111,9 @@ def infix_2_postfix(infix): ) # Output in tabular format while len(stack) > 0: # while stack is not empty + if stack[-1] == "(": # open bracket with no close bracket + raise ValueError("invalid expression") + post_fix.append(stack.pop()) # pop stack & add to Postfix print( " ".center(8), @@ -106,7 +125,7 @@ def infix_2_postfix(infix): return "".join(post_fix) # return Postfix as str -def infix_2_prefix(infix): +def infix_2_prefix(infix: str) -> str: """ >>> infix_2_prefix("a+b^c") # doctest: +NORMALIZE_WHITESPACE Symbol | Stack | Postfix @@ -119,10 +138,23 @@ def infix_2_prefix(infix): | | cb^a+ '+a^bc' - >>> infix_2_prefix("1*((-a)*2+b)") - Traceback (most recent call last): - ... - KeyError: '(' + >>> infix_2_prefix("1*((-a)*2+b)") # doctest: +NORMALIZE_WHITESPACE + Symbol | Stack | Postfix + ------------------------------------------- + ( | ( | + b | ( | b + + | (+ | b + 2 | (+ | b2 + * | (+* | b2 + ( | (+*( | b2 + a | (+*( | b2a + - | (+*(- | b2a + ) | (+* | b2a- + ) | | b2a-*+ + * | * | b2a-*+ + 1 | * | b2a-*+1 + | | b2a-*+1* + '*1+*-a2b' >>> infix_2_prefix('') Symbol | Stack | Postfix @@ -134,26 +166,21 @@ def infix_2_prefix(infix): ... IndexError: list index out of range - >>> infix_2_prefix('())') # doctest: +NORMALIZE_WHITESPACE - Symbol | Stack | Postfix - ---------------------------- - ( | ( | - ( | (( | - ) | ( | - | | ( - '(' + >>> infix_2_prefix('())') + Traceback (most recent call last): + ... + ValueError: invalid expression """ - infix = list(infix[::-1]) # reverse the infix equation + reversed_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 "(" + for i in range(len(reversed_infix)): + if reversed_infix[i] == "(": + reversed_infix[i] = ")" # change "(" to ")" + elif reversed_infix[i] == ")": + reversed_infix[i] = "(" # change ")" to "(" - return (infix_2_postfix("".join(infix)))[ - ::-1 - ] # call infix_2_postfix on Infix, return reverse of Postfix + # call infix_2_postfix on Infix, return reverse of Postfix + return (infix_2_postfix("".join(reversed_infix)))[::-1] if __name__ == "__main__": From 5fb6496d1bcd076018e6c829c312f486ed7bb2ee Mon Sep 17 00:00:00 2001 From: Ricardo Martinez Peinado <43684906+rmp2000@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:11:05 +0200 Subject: [PATCH 2445/2908] Improve primelib.py test coverage #9943 (#10251) * Update the doctest of primelib.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Correct errors for the doctest of primelib.py * last error for the doctest of primelib.py * last error for the doctest of primelib.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/primelib.py | 243 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 5 deletions(-) diff --git a/maths/primelib.py b/maths/primelib.py index d5c124255e56..7e33844be12b 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -46,6 +46,19 @@ def is_prime(number: int) -> bool: """ input: positive integer 'number' returns true if 'number' is prime otherwise false. + + >>> is_prime(3) + True + >>> is_prime(10) + False + >>> is_prime(-1) + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and positive + >>> is_prime("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and positive """ # precondition @@ -83,6 +96,16 @@ def sieve_er(n): This function implements the algorithm called sieve of erathostenes. + >>> sieve_er(8) + [2, 3, 5, 7] + >>> sieve_er(-1) + Traceback (most recent call last): + ... + AssertionError: 'N' must been an int and > 2 + >>> sieve_er("test") + Traceback (most recent call last): + ... + AssertionError: 'N' must been an int and > 2 """ # precondition @@ -116,6 +139,17 @@ def get_prime_numbers(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(...)' + + >>> get_prime_numbers(8) + [2, 3, 5, 7] + >>> get_prime_numbers(-1) + Traceback (most recent call last): + ... + AssertionError: 'N' must been an int and > 2 + >>> get_prime_numbers("test") + Traceback (most recent call last): + ... + AssertionError: 'N' must been an int and > 2 """ # precondition @@ -142,6 +176,21 @@ def prime_factorization(number): """ input: positive integer 'number' returns a list of the prime number factors of 'number' + + >>> prime_factorization(0) + [0] + >>> prime_factorization(8) + [2, 2, 2] + >>> prime_factorization(287) + [7, 41] + >>> prime_factorization(-1) + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and >= 0 + >>> prime_factorization("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and >= 0 """ # precondition @@ -183,12 +232,27 @@ def greatest_prime_factor(number): """ input: positive integer 'number' >= 0 returns the greatest prime number factor of 'number' + + >>> greatest_prime_factor(0) + 0 + >>> greatest_prime_factor(8) + 2 + >>> greatest_prime_factor(287) + 41 + >>> greatest_prime_factor(-1) + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and >= 0 + >>> greatest_prime_factor("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and >= 0 """ # precondition assert isinstance(number, int) and ( number >= 0 - ), "'number' bust been an int and >= 0" + ), "'number' must been an int and >= 0" ans = 0 @@ -210,12 +274,27 @@ def smallest_prime_factor(number): """ input: integer 'number' >= 0 returns the smallest prime number factor of 'number' + + >>> smallest_prime_factor(0) + 0 + >>> smallest_prime_factor(8) + 2 + >>> smallest_prime_factor(287) + 7 + >>> smallest_prime_factor(-1) + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and >= 0 + >>> smallest_prime_factor("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and >= 0 """ # precondition assert isinstance(number, int) and ( number >= 0 - ), "'number' bust been an int and >= 0" + ), "'number' must been an int and >= 0" ans = 0 @@ -237,11 +316,24 @@ def is_even(number): """ input: integer 'number' returns true if 'number' is even, otherwise false. + + >>> is_even(0) + True + >>> is_even(8) + True + >>> is_even(287) + False + >>> is_even(-1) + False + >>> is_even("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int """ # precondition assert isinstance(number, int), "'number' must been an int" - assert isinstance(number % 2 == 0, bool), "compare bust been from type bool" + assert isinstance(number % 2 == 0, bool), "compare must been from type bool" return number % 2 == 0 @@ -253,11 +345,24 @@ def is_odd(number): """ input: integer 'number' returns true if 'number' is odd, otherwise false. + + >>> is_odd(0) + False + >>> is_odd(8) + False + >>> is_odd(287) + True + >>> is_odd(-1) + True + >>> is_odd("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int """ # precondition assert isinstance(number, int), "'number' must been an int" - assert isinstance(number % 2 != 0, bool), "compare bust been from type bool" + assert isinstance(number % 2 != 0, bool), "compare must been from type bool" return number % 2 != 0 @@ -270,6 +375,23 @@ 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(8) + [3, 5] + >>> goldbach(824) + [3, 821] + >>> goldbach(0) + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int, even and > 2 + >>> goldbach(-1) + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int, even and > 2 + >>> goldbach("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int, even and > 2 """ # precondition @@ -323,6 +445,23 @@ def kg_v(number1, number2): Least common multiple input: two positive integer 'number1' and 'number2' returns the least common multiple of 'number1' and 'number2' + + >>> kg_v(8,10) + 40 + >>> kg_v(824,67) + 55208 + >>> kg_v(0) + Traceback (most recent call last): + ... + TypeError: kg_v() missing 1 required positional argument: 'number2' + >>> kg_v(10,-1) + Traceback (most recent call last): + ... + AssertionError: 'number1' and 'number2' must been positive integer. + >>> kg_v("test","test2") + Traceback (most recent call last): + ... + AssertionError: 'number1' and 'number2' must been positive integer. """ # precondition @@ -395,6 +534,21 @@ def get_prime(n): Gets the n-th prime number. input: positive integer 'n' >= 0 returns the n-th prime number, beginning at index 0 + + >>> get_prime(0) + 2 + >>> get_prime(8) + 23 + >>> get_prime(824) + 6337 + >>> get_prime(-1) + Traceback (most recent call last): + ... + AssertionError: 'number' must been a positive int + >>> get_prime("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been a positive int """ # precondition @@ -430,6 +584,25 @@ def get_primes_between(p_number_1, p_number_2): pNumber1 < pNumber2 returns a list of all prime numbers between 'pNumber1' (exclusive) and 'pNumber2' (exclusive) + + >>> get_primes_between(3, 67) + [5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61] + >>> get_primes_between(0) + Traceback (most recent call last): + ... + TypeError: get_primes_between() missing 1 required positional argument: 'p_number_2' + >>> get_primes_between(0, 1) + Traceback (most recent call last): + ... + AssertionError: The arguments must been prime numbers and 'pNumber1' < 'pNumber2' + >>> get_primes_between(-1, 3) + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and positive + >>> get_primes_between("test","test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and positive """ # precondition @@ -473,6 +646,19 @@ def get_divisors(n): """ input: positive integer 'n' >= 1 returns all divisors of n (inclusive 1 and 'n') + + >>> get_divisors(8) + [1, 2, 4, 8] + >>> get_divisors(824) + [1, 2, 4, 8, 103, 206, 412, 824] + >>> get_divisors(-1) + Traceback (most recent call last): + ... + AssertionError: 'n' must been int and >= 1 + >>> get_divisors("test") + Traceback (most recent call last): + ... + AssertionError: 'n' must been int and >= 1 """ # precondition @@ -497,6 +683,19 @@ def is_perfect_number(number): """ input: positive integer 'number' > 1 returns true if 'number' is a perfect number otherwise false. + + >>> is_perfect_number(28) + True + >>> is_perfect_number(824) + False + >>> is_perfect_number(-1) + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and >= 1 + >>> is_perfect_number("test") + Traceback (most recent call last): + ... + AssertionError: 'number' must been an int and >= 1 """ # precondition @@ -525,6 +724,15 @@ def simplify_fraction(numerator, denominator): input: two integer 'numerator' and 'denominator' assumes: 'denominator' != 0 returns: a tuple with simplify numerator and denominator. + + >>> simplify_fraction(10, 20) + (1, 2) + >>> simplify_fraction(10, -1) + (10, -1) + >>> simplify_fraction("test","test") + Traceback (most recent call last): + ... + AssertionError: The arguments must been from type int and 'denominator' != 0 """ # precondition @@ -554,6 +762,19 @@ def factorial(n): """ input: positive integer 'n' returns the factorial of 'n' (n!) + + >>> factorial(0) + 1 + >>> factorial(20) + 2432902008176640000 + >>> factorial(-1) + Traceback (most recent call last): + ... + AssertionError: 'n' must been a int and >= 0 + >>> factorial("test") + Traceback (most recent call last): + ... + AssertionError: 'n' must been a int and >= 0 """ # precondition @@ -570,15 +791,27 @@ def factorial(n): # ------------------------------------------------------------------- -def fib(n): +def fib(n: int) -> int: """ input: positive integer 'n' returns the n-th fibonacci term , indexing by 0 + >>> fib(0) + 1 >>> fib(5) 8 + >>> fib(20) + 10946 >>> fib(99) 354224848179261915075 + >>> fib(-1) + Traceback (most recent call last): + ... + AssertionError: 'n' must been an int and >= 0 + >>> fib("test") + Traceback (most recent call last): + ... + AssertionError: 'n' must been an int and >= 0 """ # precondition From d5323dbaee21a9ae209efa17852b02c3101a0220 Mon Sep 17 00:00:00 2001 From: Aasheesh <126905285+AasheeshLikePanner@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:50:18 +0530 Subject: [PATCH 2446/2908] Adding doctests in simpson_rule.py (#10269) * Adding doctests in simpson_rule.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/simpson_rule.py Co-authored-by: Christian Clauss * Update maths/simpson_rule.py Co-authored-by: Christian Clauss * Adding doctests in simpson_rule.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Adding doctests in simpson_rule.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Adding doctests in simpson_rule.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Adding doctests in simpson_rule.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Adding doctests in simpson_rule.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update simpson_rule.py * Adding doctests in simpson_rule.py * Adding doctests in simpson_rule.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/simpson_rule.py | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/maths/simpson_rule.py b/maths/simpson_rule.py index d66dc39a7171..e75fb557a2f5 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 approach of suming 'Equally Spaced Abscissas' +This method is the classical approach of summing 'Equally Spaced Abscissas' method 2: "Simpson Rule" @@ -9,9 +9,41 @@ """ -def method_2(boundary, steps): +def method_2(boundary: list[int], steps: int) -> float: # "Simpson Rule" # int(f) = delta_x/2 * (b-a)/3*(f1 + 4f2 + 2f_3 + ... + fn) + """ + Calculate the definite integral of a function using Simpson's Rule. + :param boundary: A list containing the lower and upper bounds of integration. + :param steps: The number of steps or resolution for the integration. + :return: The approximate integral value. + + >>> round(method_2([0, 2, 4], 10), 10) + 2.6666666667 + >>> round(method_2([2, 0], 10), 10) + -0.2666666667 + >>> round(method_2([-2, -1], 10), 10) + 2.172 + >>> round(method_2([0, 1], 10), 10) + 0.3333333333 + >>> round(method_2([0, 2], 10), 10) + 2.6666666667 + >>> round(method_2([0, 2], 100), 10) + 2.5621226667 + >>> round(method_2([0, 1], 1000), 10) + 0.3320026653 + >>> round(method_2([0, 2], 0), 10) + Traceback (most recent call last): + ... + ZeroDivisionError: Number of steps must be greater than zero + >>> round(method_2([0, 2], -10), 10) + Traceback (most recent call last): + ... + ZeroDivisionError: Number of steps must be greater than zero + """ + if steps <= 0: + raise ZeroDivisionError("Number of steps must be greater than zero") + h = (boundary[1] - boundary[0]) / steps a = boundary[0] b = boundary[1] @@ -41,11 +73,14 @@ 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 - boundary = [a, b] # define boundary of integration + steps = 10.0 # number of steps or resolution + boundary = [a, b] # boundary of integration y = method_2(boundary, steps) print(f"y = {y}") if __name__ == "__main__": + import doctest + + doctest.testmod() main() From 3f094fe49d14e64d2c8f0e2c14d339ab6d0ee735 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 11 Oct 2023 20:30:02 +0200 Subject: [PATCH 2447/2908] Ruff pandas vet (#10281) * Python linting: Add ruff rules for Pandas-vet and Pytest-style * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 1 + blockchain/diophantine_equation.py | 6 +- ciphers/xor_cipher.py | 18 +++-- conversions/decimal_to_hexadecimal.py | 3 +- .../binary_search_tree_recursive.py | 28 ++++---- .../hashing/tests/test_hash_map.py | 4 +- .../linked_list/circular_linked_list.py | 6 +- .../test_digital_image_processing.py | 3 +- graphs/graph_adjacency_list.py | 60 ++++++++-------- graphs/graph_adjacency_matrix.py | 60 ++++++++-------- hashes/sha256.py | 2 +- knapsack/tests/test_greedy_knapsack.py | 16 ++--- knapsack/tests/test_knapsack.py | 8 +-- linear_algebra/src/lib.py | 11 ++- linear_algebra/src/schur_complement.py | 7 +- linear_algebra/src/test_linear_algebra.py | 69 ++++++++++--------- machine_learning/dimensionality_reduction.py | 4 +- machine_learning/k_means_clust.py | 69 +++++++++---------- maths/least_common_multiple.py | 4 +- maths/modular_division.py | 10 ++- maths/prime_check.py | 48 ++++++------- matrix/sherman_morrison.py | 6 +- matrix/tests/test_matrix_operation.py | 28 ++++---- project_euler/problem_054/test_poker_hand.py | 14 ++-- pyproject.toml | 8 ++- strings/knuth_morris_pratt.py | 3 +- strings/rabin_karp.py | 3 +- 28 files changed, 260 insertions(+), 241 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7340a0fd08ee..84f4a7770d00 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.6.0 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 015efb3c796d..2c6000c94ed4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -532,6 +532,7 @@ * [Logistic Regression](machine_learning/logistic_regression.py) * Loss Functions * [Binary Cross Entropy](machine_learning/loss_functions/binary_cross_entropy.py) + * [Categorical Cross Entropy](machine_learning/loss_functions/categorical_cross_entropy.py) * [Huber Loss](machine_learning/loss_functions/huber_loss.py) * [Mean Squared Error](machine_learning/loss_functions/mean_squared_error.py) * [Mfcc](machine_learning/mfcc.py) diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py index 7110d90230c9..ae6a145d2922 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -83,7 +83,8 @@ def extended_gcd(a: int, b: int) -> tuple[int, int, int]: (1, -2, 3) """ - assert a >= 0 and b >= 0 + assert a >= 0 + assert b >= 0 if b == 0: d, x, y = a, 1, 0 @@ -92,7 +93,8 @@ def extended_gcd(a: int, b: int) -> tuple[int, int, int]: x = q y = p - q * (a // b) - assert a % d == 0 and b % d == 0 + assert a % d == 0 + assert b % d == 0 assert d == a * x + b * y return (d, x, y) diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 0f369e38f85f..559036d305c5 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -38,7 +38,8 @@ def encrypt(self, content: str, key: int) -> list[str]: """ # precondition - assert isinstance(key, int) and isinstance(content, str) + assert isinstance(key, int) + assert isinstance(content, str) key = key or self.__key or 1 @@ -56,7 +57,8 @@ def decrypt(self, content: str, key: int) -> list[str]: """ # precondition - assert isinstance(key, int) and isinstance(content, list) + assert isinstance(key, int) + assert isinstance(content, list) key = key or self.__key or 1 @@ -74,7 +76,8 @@ def encrypt_string(self, content: str, key: int = 0) -> str: """ # precondition - assert isinstance(key, int) and isinstance(content, str) + assert isinstance(key, int) + assert isinstance(content, str) key = key or self.__key or 1 @@ -99,7 +102,8 @@ def decrypt_string(self, content: str, key: int = 0) -> str: """ # precondition - assert isinstance(key, int) and isinstance(content, str) + assert isinstance(key, int) + assert isinstance(content, str) key = key or self.__key or 1 @@ -125,7 +129,8 @@ def encrypt_file(self, file: str, key: int = 0) -> bool: """ # precondition - assert isinstance(file, str) and isinstance(key, int) + assert isinstance(file, str) + assert isinstance(key, int) try: with open(file) as fin, open("encrypt.out", "w+") as fout: @@ -148,7 +153,8 @@ def decrypt_file(self, file: str, key: int) -> bool: """ # precondition - assert isinstance(file, str) and isinstance(key, int) + assert isinstance(file, str) + assert isinstance(key, int) try: with open(file) as fin, open("decrypt.out", "w+") as fout: diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py index 5ea48401f488..b1fb4f082242 100644 --- a/conversions/decimal_to_hexadecimal.py +++ b/conversions/decimal_to_hexadecimal.py @@ -57,7 +57,8 @@ def decimal_to_hexadecimal(decimal: float) -> str: >>> decimal_to_hexadecimal(-256) == hex(-256) True """ - assert type(decimal) in (int, float) and decimal == int(decimal) + assert isinstance(decimal, (int, float)) + assert decimal == int(decimal) decimal = int(decimal) hexadecimal = "" negative = False diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index b5b983b9ba4c..13b9b392175c 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -12,6 +12,8 @@ import unittest from collections.abc import Iterator +import pytest + class Node: def __init__(self, label: int, parent: Node | None) -> None: @@ -78,7 +80,7 @@ def _put(self, node: Node | None, label: int, parent: Node | None = None) -> Nod node.right = self._put(node.right, label, node) else: msg = f"Node with label {label} already exists" - raise Exception(msg) + raise ValueError(msg) return node @@ -95,14 +97,14 @@ def search(self, label: int) -> Node: >>> node = t.search(3) Traceback (most recent call last): ... - Exception: Node with label 3 does not exist + ValueError: Node with label 3 does not exist """ return self._search(self.root, label) def _search(self, node: Node | None, label: int) -> Node: if node is None: msg = f"Node with label {label} does not exist" - raise Exception(msg) + raise ValueError(msg) else: if label < node.label: node = self._search(node.left, label) @@ -124,7 +126,7 @@ def remove(self, label: int) -> None: >>> t.remove(3) Traceback (most recent call last): ... - Exception: Node with label 3 does not exist + ValueError: Node with label 3 does not exist """ node = self.search(label) if node.right and node.left: @@ -179,7 +181,7 @@ def exists(self, label: int) -> bool: try: self.search(label) return True - except Exception: + except ValueError: return False def get_max_label(self) -> int: @@ -190,7 +192,7 @@ def get_max_label(self) -> int: >>> t.get_max_label() Traceback (most recent call last): ... - Exception: Binary search tree is empty + ValueError: Binary search tree is empty >>> t.put(8) >>> t.put(10) @@ -198,7 +200,7 @@ def get_max_label(self) -> int: 10 """ if self.root is None: - raise Exception("Binary search tree is empty") + raise ValueError("Binary search tree is empty") node = self.root while node.right is not None: @@ -214,7 +216,7 @@ def get_min_label(self) -> int: >>> t.get_min_label() Traceback (most recent call last): ... - Exception: Binary search tree is empty + ValueError: Binary search tree is empty >>> t.put(8) >>> t.put(10) @@ -222,7 +224,7 @@ def get_min_label(self) -> int: 8 """ if self.root is None: - raise Exception("Binary search tree is empty") + raise ValueError("Binary search tree is empty") node = self.root while node.left is not None: @@ -359,7 +361,7 @@ def test_put(self) -> None: assert t.root.left.left.parent == t.root.left assert t.root.left.left.label == 1 - with self.assertRaises(Exception): # noqa: B017 + with pytest.raises(ValueError): t.put(1) def test_search(self) -> None: @@ -371,7 +373,7 @@ def test_search(self) -> None: node = t.search(13) assert node.label == 13 - with self.assertRaises(Exception): # noqa: B017 + with pytest.raises(ValueError): t.search(2) def test_remove(self) -> None: @@ -517,7 +519,7 @@ def test_get_max_label(self) -> None: assert t.get_max_label() == 14 t.empty() - with self.assertRaises(Exception): # noqa: B017 + with pytest.raises(ValueError): t.get_max_label() def test_get_min_label(self) -> None: @@ -526,7 +528,7 @@ def test_get_min_label(self) -> None: assert t.get_min_label() == 1 t.empty() - with self.assertRaises(Exception): # noqa: B017 + with pytest.raises(ValueError): t.get_min_label() def test_inorder_traversal(self) -> None: diff --git a/data_structures/hashing/tests/test_hash_map.py b/data_structures/hashing/tests/test_hash_map.py index 929e67311996..4292c0178b7b 100644 --- a/data_structures/hashing/tests/test_hash_map.py +++ b/data_structures/hashing/tests/test_hash_map.py @@ -65,14 +65,14 @@ def _run_operation(obj, fun, *args): @pytest.mark.parametrize( "operations", - ( + [ pytest.param(_add_items, id="add items"), pytest.param(_overwrite_items, id="overwrite items"), pytest.param(_delete_items, id="delete items"), pytest.param(_access_absent_items, id="access absent items"), pytest.param(_add_with_resize_up, id="add with resize up"), pytest.param(_add_with_resize_down, id="add with resize down"), - ), + ], ) def test_hash_map_is_the_same_as_dict(operations): my = HashMap(initial_block_size=4) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index ef6658733a95..54343c80a30f 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -124,7 +124,8 @@ def delete_nth(self, index: int = 0) -> Any: if not 0 <= index < len(self): raise IndexError("list index out of range.") - assert self.head is not None and self.tail is not None + assert self.head is not None + assert self.tail is not None delete_node: Node = self.head if self.head == self.tail: # Just one node self.head = self.tail = None @@ -137,7 +138,8 @@ def delete_nth(self, index: int = 0) -> Any: for _ in range(index - 1): assert temp is not None temp = temp.next - assert temp is not None and temp.next is not None + assert temp is not None + assert temp.next is not None delete_node = temp.next temp.next = temp.next.next if index == len(self) - 1: # Delete at tail diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 2e5630458c8e..7993110d6bdd 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -73,7 +73,8 @@ def test_median_filter(): def test_sobel_filter(): grad, theta = sob.sobel_filter(gray) - assert grad.any() and theta.any() + assert grad.any() + assert theta.any() def test_sepia(): diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py index 76f34f845860..d0b94f03e9b4 100644 --- a/graphs/graph_adjacency_list.py +++ b/graphs/graph_adjacency_list.py @@ -22,6 +22,8 @@ from pprint import pformat from typing import Generic, TypeVar +import pytest + T = TypeVar("T") @@ -185,9 +187,9 @@ def __assert_graph_edge_exists_check( directed_graph: GraphAdjacencyList, edge: list[int], ) -> None: - self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + assert undirected_graph.contains_edge(edge[0], edge[1]) + assert undirected_graph.contains_edge(edge[1], edge[0]) + assert directed_graph.contains_edge(edge[0], edge[1]) def __assert_graph_edge_does_not_exist_check( self, @@ -195,9 +197,9 @@ def __assert_graph_edge_does_not_exist_check( directed_graph: GraphAdjacencyList, edge: list[int], ) -> None: - self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + assert not undirected_graph.contains_edge(edge[0], edge[1]) + assert not undirected_graph.contains_edge(edge[1], edge[0]) + assert not directed_graph.contains_edge(edge[0], edge[1]) def __assert_graph_vertex_exists_check( self, @@ -205,8 +207,8 @@ def __assert_graph_vertex_exists_check( directed_graph: GraphAdjacencyList, vertex: int, ) -> None: - self.assertTrue(undirected_graph.contains_vertex(vertex)) - self.assertTrue(directed_graph.contains_vertex(vertex)) + assert undirected_graph.contains_vertex(vertex) + assert directed_graph.contains_vertex(vertex) def __assert_graph_vertex_does_not_exist_check( self, @@ -214,13 +216,13 @@ def __assert_graph_vertex_does_not_exist_check( directed_graph: GraphAdjacencyList, vertex: int, ) -> None: - self.assertFalse(undirected_graph.contains_vertex(vertex)) - self.assertFalse(directed_graph.contains_vertex(vertex)) + assert not undirected_graph.contains_vertex(vertex) + assert not directed_graph.contains_vertex(vertex) def __generate_random_edges( self, vertices: list[int], edge_pick_count: int ) -> list[list[int]]: - self.assertTrue(edge_pick_count <= len(vertices)) + assert edge_pick_count <= len(vertices) random_source_vertices: list[int] = random.sample( vertices[0 : int(len(vertices) / 2)], edge_pick_count @@ -281,8 +283,8 @@ def test_init_check(self) -> None: self.__assert_graph_edge_exists_check( undirected_graph, directed_graph, edge ) - self.assertFalse(undirected_graph.directed) - self.assertTrue(directed_graph.directed) + assert not undirected_graph.directed + assert directed_graph.directed def test_contains_vertex(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) @@ -297,12 +299,8 @@ def test_contains_vertex(self) -> None: # Test contains_vertex for num in range(101): - self.assertEqual( - num in random_vertices, undirected_graph.contains_vertex(num) - ) - self.assertEqual( - num in random_vertices, directed_graph.contains_vertex(num) - ) + assert (num in random_vertices) == undirected_graph.contains_vertex(num) + assert (num in random_vertices) == directed_graph.contains_vertex(num) def test_add_vertices(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) @@ -507,9 +505,9 @@ def test_add_vertex_exception_check(self) -> None: ) = self.__generate_graphs(20, 0, 100, 4) for vertex in random_vertices: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.add_vertex(vertex) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.add_vertex(vertex) def test_remove_vertex_exception_check(self) -> None: @@ -522,9 +520,9 @@ def test_remove_vertex_exception_check(self) -> None: for i in range(101): if i not in random_vertices: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.remove_vertex(i) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.remove_vertex(i) def test_add_edge_exception_check(self) -> None: @@ -536,9 +534,9 @@ def test_add_edge_exception_check(self) -> None: ) = self.__generate_graphs(20, 0, 100, 4) for edge in random_edges: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.add_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.add_edge(edge[0], edge[1]) def test_remove_edge_exception_check(self) -> None: @@ -560,9 +558,9 @@ def test_remove_edge_exception_check(self) -> None: more_random_edges.append(edge) for edge in more_random_edges: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.remove_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.remove_edge(edge[0], edge[1]) def test_contains_edge_exception_check(self) -> None: @@ -574,14 +572,14 @@ def test_contains_edge_exception_check(self) -> None: ) = self.__generate_graphs(20, 0, 100, 4) for vertex in random_vertices: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.contains_edge(vertex, 102) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.contains_edge(vertex, 102) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.contains_edge(103, 102) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.contains_edge(103, 102) diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py index 4d2e02f737f9..cdef388d9098 100644 --- a/graphs/graph_adjacency_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -22,6 +22,8 @@ from pprint import pformat from typing import Generic, TypeVar +import pytest + T = TypeVar("T") @@ -203,9 +205,9 @@ def __assert_graph_edge_exists_check( directed_graph: GraphAdjacencyMatrix, edge: list[int], ) -> None: - self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + assert undirected_graph.contains_edge(edge[0], edge[1]) + assert undirected_graph.contains_edge(edge[1], edge[0]) + assert directed_graph.contains_edge(edge[0], edge[1]) def __assert_graph_edge_does_not_exist_check( self, @@ -213,9 +215,9 @@ def __assert_graph_edge_does_not_exist_check( directed_graph: GraphAdjacencyMatrix, edge: list[int], ) -> None: - self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + assert not undirected_graph.contains_edge(edge[0], edge[1]) + assert not undirected_graph.contains_edge(edge[1], edge[0]) + assert not directed_graph.contains_edge(edge[0], edge[1]) def __assert_graph_vertex_exists_check( self, @@ -223,8 +225,8 @@ def __assert_graph_vertex_exists_check( directed_graph: GraphAdjacencyMatrix, vertex: int, ) -> None: - self.assertTrue(undirected_graph.contains_vertex(vertex)) - self.assertTrue(directed_graph.contains_vertex(vertex)) + assert undirected_graph.contains_vertex(vertex) + assert directed_graph.contains_vertex(vertex) def __assert_graph_vertex_does_not_exist_check( self, @@ -232,13 +234,13 @@ def __assert_graph_vertex_does_not_exist_check( directed_graph: GraphAdjacencyMatrix, vertex: int, ) -> None: - self.assertFalse(undirected_graph.contains_vertex(vertex)) - self.assertFalse(directed_graph.contains_vertex(vertex)) + assert not undirected_graph.contains_vertex(vertex) + assert not directed_graph.contains_vertex(vertex) def __generate_random_edges( self, vertices: list[int], edge_pick_count: int ) -> list[list[int]]: - self.assertTrue(edge_pick_count <= len(vertices)) + assert edge_pick_count <= len(vertices) random_source_vertices: list[int] = random.sample( vertices[0 : int(len(vertices) / 2)], edge_pick_count @@ -300,8 +302,8 @@ def test_init_check(self) -> None: undirected_graph, directed_graph, edge ) - self.assertFalse(undirected_graph.directed) - self.assertTrue(directed_graph.directed) + assert not undirected_graph.directed + assert directed_graph.directed def test_contains_vertex(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) @@ -316,12 +318,8 @@ def test_contains_vertex(self) -> None: # Test contains_vertex for num in range(101): - self.assertEqual( - num in random_vertices, undirected_graph.contains_vertex(num) - ) - self.assertEqual( - num in random_vertices, directed_graph.contains_vertex(num) - ) + assert (num in random_vertices) == undirected_graph.contains_vertex(num) + assert (num in random_vertices) == directed_graph.contains_vertex(num) def test_add_vertices(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) @@ -526,9 +524,9 @@ def test_add_vertex_exception_check(self) -> None: ) = self.__generate_graphs(20, 0, 100, 4) for vertex in random_vertices: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.add_vertex(vertex) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.add_vertex(vertex) def test_remove_vertex_exception_check(self) -> None: @@ -541,9 +539,9 @@ def test_remove_vertex_exception_check(self) -> None: for i in range(101): if i not in random_vertices: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.remove_vertex(i) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.remove_vertex(i) def test_add_edge_exception_check(self) -> None: @@ -555,9 +553,9 @@ def test_add_edge_exception_check(self) -> None: ) = self.__generate_graphs(20, 0, 100, 4) for edge in random_edges: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.add_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.add_edge(edge[0], edge[1]) def test_remove_edge_exception_check(self) -> None: @@ -579,9 +577,9 @@ def test_remove_edge_exception_check(self) -> None: more_random_edges.append(edge) for edge in more_random_edges: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.remove_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.remove_edge(edge[0], edge[1]) def test_contains_edge_exception_check(self) -> None: @@ -593,14 +591,14 @@ def test_contains_edge_exception_check(self) -> None: ) = self.__generate_graphs(20, 0, 100, 4) for vertex in random_vertices: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.contains_edge(vertex, 102) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.contains_edge(vertex, 102) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): undirected_graph.contains_edge(103, 102) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): directed_graph.contains_edge(103, 102) diff --git a/hashes/sha256.py b/hashes/sha256.py index ba9aff8dbf41..bcc83edca480 100644 --- a/hashes/sha256.py +++ b/hashes/sha256.py @@ -203,7 +203,7 @@ def test_match_hashes(self) -> None: import hashlib msg = bytes("Test String", "utf-8") - self.assertEqual(SHA256(msg).hash, hashlib.sha256(msg).hexdigest()) + assert SHA256(msg).hash == hashlib.sha256(msg).hexdigest() def main() -> None: diff --git a/knapsack/tests/test_greedy_knapsack.py b/knapsack/tests/test_greedy_knapsack.py index b7b62d5d80b4..e6a40084109e 100644 --- a/knapsack/tests/test_greedy_knapsack.py +++ b/knapsack/tests/test_greedy_knapsack.py @@ -1,5 +1,7 @@ import unittest +import pytest + from knapsack import greedy_knapsack as kp @@ -16,7 +18,7 @@ def test_sorted(self): 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) + assert kp.calc_profit(profit, weight, max_weight) == 210 def test_negative_max_weight(self): """ @@ -26,7 +28,7 @@ def test_negative_max_weight(self): # 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.") + pytest.raises(ValueError, match="max_weight must greater than zero.") def test_negative_profit_value(self): """ @@ -36,7 +38,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.") + pytest.raises(ValueError, match="Weight can not be negative.") def test_negative_weight_value(self): """ @@ -46,7 +48,7 @@ def test_negative_weight_value(self): # 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.") + pytest.raises(ValueError, match="Profit can not be negative.") def test_null_max_weight(self): """ @@ -56,7 +58,7 @@ def test_null_max_weight(self): # 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.") + pytest.raises(ValueError, match="max_weight must greater than zero.") def test_unequal_list_length(self): """ @@ -66,9 +68,7 @@ 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." - ) + pytest.raises(IndexError, match="The length of profit and weight must be same.") if __name__ == "__main__": diff --git a/knapsack/tests/test_knapsack.py b/knapsack/tests/test_knapsack.py index 248855fbce53..6932bbb3536b 100644 --- a/knapsack/tests/test_knapsack.py +++ b/knapsack/tests/test_knapsack.py @@ -20,12 +20,12 @@ def test_base_case(self): val = [0] w = [0] c = len(val) - self.assertEqual(k.knapsack(cap, w, val, c), 0) + assert k.knapsack(cap, w, val, c) == 0 val = [60] w = [10] c = len(val) - self.assertEqual(k.knapsack(cap, w, val, c), 0) + assert k.knapsack(cap, w, val, c) == 0 def test_easy_case(self): """ @@ -35,7 +35,7 @@ def test_easy_case(self): val = [1, 2, 3] w = [3, 2, 1] c = len(val) - self.assertEqual(k.knapsack(cap, w, val, c), 5) + assert k.knapsack(cap, w, val, c) == 5 def test_knapsack(self): """ @@ -45,7 +45,7 @@ def test_knapsack(self): val = [60, 100, 120] w = [10, 20, 30] c = len(val) - self.assertEqual(k.knapsack(cap, w, val, c), 220) + assert k.knapsack(cap, w, val, c) == 220 if __name__ == "__main__": diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index e3556e74c3f3..5074faf31d1d 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -200,7 +200,8 @@ def unit_basis_vector(dimension: int, pos: int) -> Vector: at index 'pos' (indexing at 0) """ # precondition - assert isinstance(dimension, int) and (isinstance(pos, int)) + assert isinstance(dimension, int) + assert isinstance(pos, int) ans = [0] * dimension ans[pos] = 1 return Vector(ans) @@ -213,11 +214,9 @@ def axpy(scalar: float, x: Vector, y: Vector) -> Vector: computes the axpy operation """ # precondition - assert ( - isinstance(x, Vector) - and isinstance(y, Vector) - and (isinstance(scalar, (int, float))) - ) + assert isinstance(x, Vector) + assert isinstance(y, Vector) + assert isinstance(scalar, (int, float)) return x * scalar + y diff --git a/linear_algebra/src/schur_complement.py b/linear_algebra/src/schur_complement.py index 750f4de5e397..1cc084043856 100644 --- a/linear_algebra/src/schur_complement.py +++ b/linear_algebra/src/schur_complement.py @@ -1,6 +1,7 @@ import unittest import numpy as np +import pytest def schur_complement( @@ -70,14 +71,14 @@ def test_schur_complement(self) -> None: det_a = np.linalg.det(a) det_s = np.linalg.det(s) - self.assertAlmostEqual(det_x, det_a * det_s) + assert np.is_close(det_x, det_a * det_s) def test_improper_a_b_dimensions(self) -> None: a = np.array([[1, 2, 1], [2, 1, 2], [3, 2, 4]]) b = np.array([[0, 3], [3, 0], [2, 3]]) c = np.array([[2, 1], [6, 3]]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): schur_complement(a, b, c) def test_improper_b_c_dimensions(self) -> None: @@ -85,7 +86,7 @@ def test_improper_b_c_dimensions(self) -> None: b = np.array([[0, 3], [3, 0], [2, 3]]) c = np.array([[2, 1, 3], [6, 3, 5]]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): schur_complement(a, b, c) diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 50d079572e0f..95ab408b3d86 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -8,6 +8,8 @@ """ import unittest +import pytest + from .lib import ( Matrix, Vector, @@ -24,8 +26,8 @@ def test_component(self) -> None: test for method component() """ x = Vector([1, 2, 3]) - self.assertEqual(x.component(0), 1) - self.assertEqual(x.component(2), 3) + assert x.component(0) == 1 + assert x.component(2) == 3 _ = Vector() def test_str(self) -> None: @@ -33,14 +35,14 @@ def test_str(self) -> None: test for method toString() """ x = Vector([0, 0, 0, 0, 0, 1]) - self.assertEqual(str(x), "(0,0,0,0,0,1)") + assert str(x) == "(0,0,0,0,0,1)" def test_size(self) -> None: """ test for method size() """ x = Vector([1, 2, 3, 4]) - self.assertEqual(len(x), 4) + assert len(x) == 4 def test_euclidean_length(self) -> None: """ @@ -50,10 +52,10 @@ def test_euclidean_length(self) -> None: y = Vector([1, 2, 3, 4, 5]) z = Vector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) w = Vector([1, -1, 1, -1, 2, -3, 4, -5]) - self.assertAlmostEqual(x.euclidean_length(), 2.236, 3) - self.assertAlmostEqual(y.euclidean_length(), 7.416, 3) - self.assertEqual(z.euclidean_length(), 0) - self.assertAlmostEqual(w.euclidean_length(), 7.616, 3) + assert x.euclidean_length() == pytest.approx(2.236, abs=1e-3) + assert y.euclidean_length() == pytest.approx(7.416, abs=1e-3) + assert z.euclidean_length() == 0 + assert w.euclidean_length() == pytest.approx(7.616, abs=1e-3) def test_add(self) -> None: """ @@ -61,9 +63,9 @@ def test_add(self) -> None: """ 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) + assert (x + y).component(0) == 2 + assert (x + y).component(1) == 3 + assert (x + y).component(2) == 4 def test_sub(self) -> None: """ @@ -71,9 +73,9 @@ def test_sub(self) -> None: """ 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) + assert (x - y).component(0) == 0 + assert (x - y).component(1) == 1 + assert (x - y).component(2) == 2 def test_mul(self) -> None: """ @@ -82,20 +84,20 @@ def test_mul(self) -> None: 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) + assert str(x * 3.0) == "(3.0,6.0,9.0)" + assert a * b == 0 def test_zero_vector(self) -> None: """ test for global function zero_vector() """ - self.assertEqual(str(zero_vector(10)).count("0"), 10) + assert str(zero_vector(10)).count("0") == 10 def test_unit_basis_vector(self) -> None: """ test for global function unit_basis_vector() """ - self.assertEqual(str(unit_basis_vector(3, 1)), "(0,1,0)") + assert str(unit_basis_vector(3, 1)) == "(0,1,0)" def test_axpy(self) -> None: """ @@ -103,7 +105,7 @@ def test_axpy(self) -> None: """ x = Vector([1, 2, 3]) y = Vector([1, 0, 1]) - self.assertEqual(str(axpy(2, x, y)), "(3,4,7)") + assert str(axpy(2, x, y)) == "(3,4,7)" def test_copy(self) -> None: """ @@ -111,7 +113,7 @@ def test_copy(self) -> None: """ x = Vector([1, 0, 0, 0, 0, 0]) y = x.copy() - self.assertEqual(str(x), str(y)) + assert str(x) == str(y) def test_change_component(self) -> None: """ @@ -120,14 +122,14 @@ def test_change_component(self) -> None: x = Vector([1, 0, 0]) x.change_component(0, 0) x.change_component(1, 1) - self.assertEqual(str(x), "(0,1,0)") + assert str(x) == "(0,1,0)" def test_str_matrix(self) -> None: """ test for Matrix method str() """ 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)) + assert str(a) == "|1,2,3|\n|2,4,5|\n|6,7,8|\n" def test_minor(self) -> None: """ @@ -137,7 +139,7 @@ def test_minor(self) -> None: minors = [[-3, -14, -10], [-5, -10, -5], [-2, -1, 0]] for x in range(a.height()): for y in range(a.width()): - self.assertEqual(minors[x][y], a.minor(x, y)) + assert minors[x][y] == a.minor(x, y) def test_cofactor(self) -> None: """ @@ -147,14 +149,14 @@ def test_cofactor(self) -> None: cofactors = [[-3, 14, -10], [5, -10, 5], [-2, 1, 0]] for x in range(a.height()): for y in range(a.width()): - self.assertEqual(cofactors[x][y], a.cofactor(x, y)) + assert cofactors[x][y] == a.cofactor(x, y) def test_determinant(self) -> None: """ test for Matrix method determinant() """ a = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) - self.assertEqual(-5, a.determinant()) + assert a.determinant() == -5 def test__mul__matrix(self) -> None: """ @@ -162,8 +164,8 @@ def test__mul__matrix(self) -> None: """ 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)) + assert str(a * x) == "(14,32,50)" + assert str(a * 2) == "|2,4,6|\n|8,10,12|\n|14,16,18|\n" def test_change_component_matrix(self) -> None: """ @@ -171,14 +173,14 @@ def test_change_component_matrix(self) -> None: """ a = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) a.change_component(0, 2, 5) - self.assertEqual("|1,2,5|\n|2,4,5|\n|6,7,8|\n", str(a)) + assert str(a) == "|1,2,5|\n|2,4,5|\n|6,7,8|\n" def test_component_matrix(self) -> None: """ test for Matrix method component() """ a = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) - self.assertEqual(7, a.component(2, 1), 0.01) + assert a.component(2, 1) == 7, 0.01 def test__add__matrix(self) -> None: """ @@ -186,7 +188,7 @@ def test__add__matrix(self) -> None: """ 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)) + assert str(a + b) == "|2,4,10|\n|4,8,10|\n|12,14,18|\n" def test__sub__matrix(self) -> None: """ @@ -194,15 +196,14 @@ def test__sub__matrix(self) -> None: """ 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)) + assert str(a - b) == "|0,0,-4|\n|0,0,0|\n|0,0,-2|\n" def test_square_zero_matrix(self) -> None: """ test for global function square_zero_matrix() """ - 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(square_zero_matrix(5)), + assert str(square_zero_matrix(5)) == ( + "|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" ) diff --git a/machine_learning/dimensionality_reduction.py b/machine_learning/dimensionality_reduction.py index d2046f81af04..50d442ecc3de 100644 --- a/machine_learning/dimensionality_reduction.py +++ b/machine_learning/dimensionality_reduction.py @@ -169,7 +169,7 @@ def test_linear_discriminant_analysis() -> None: dimensions = 2 # Assert that the function raises an AssertionError if dimensions > classes - with pytest.raises(AssertionError) as error_info: + with pytest.raises(AssertionError) as error_info: # noqa: PT012 projected_data = linear_discriminant_analysis( features, labels, classes, dimensions ) @@ -185,7 +185,7 @@ def test_principal_component_analysis() -> None: dimensions = 2 expected_output = np.array([[6.92820323, 8.66025404, 10.39230485], [3.0, 3.0, 3.0]]) - with pytest.raises(AssertionError) as error_info: + with pytest.raises(AssertionError) as error_info: # noqa: PT012 output = principal_component_analysis(features, dimensions) if not np.allclose(expected_output, output): raise AssertionError diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 3fe151442e2e..ebad66ac8e8f 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -128,7 +128,7 @@ def plot_heterogeneity(heterogeneity, k): 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. + """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 @@ -195,20 +195,20 @@ def kmeans( def report_generator( - df: pd.DataFrame, clustering_variables: np.ndarray, fill_missing_report=None + predicted: pd.DataFrame, clustering_variables: np.ndarray, fill_missing_report=None ) -> pd.DataFrame: """ - Generates a clustering report. This function takes 2 arguments as input: - df - dataframe with predicted cluster column + Generate a clustering report given these two arguments: + predicted - dataframe with predicted cluster column fill_missing_report - dictionary of rules on how we are going to fill in missing values for final generated report (not included in modelling); - >>> 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] - >>> report_generator(data, ['col1', 'col2'], 0) + >>> predicted = pd.DataFrame() + >>> predicted['numbers'] = [1, 2, 3] + >>> predicted['col1'] = [0.5, 2.5, 4.5] + >>> predicted['col2'] = [100, 200, 300] + >>> predicted['col3'] = [10, 20, 30] + >>> predicted['Cluster'] = [1, 1, 2] + >>> report_generator(predicted, ['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 @@ -226,11 +226,11 @@ def report_generator( """ # Fill missing values with given rules if fill_missing_report: - df = df.fillna(value=fill_missing_report) - df["dummy"] = 1 - numeric_cols = df.select_dtypes(np.number).columns + predicted = predicted.fillna(value=fill_missing_report) + predicted["dummy"] = 1 + numeric_cols = predicted.select_dtypes(np.number).columns report = ( - df.groupby(["Cluster"])[ # construct report dataframe + predicted.groupby(["Cluster"])[ # construct report dataframe numeric_cols ] # group by cluster number .agg( @@ -267,46 +267,43 @@ def report_generator( .rename(index=str, columns={"level_0": "Features", "level_1": "Type"}) ) # rename columns # calculate the size of cluster(count of clientID's) + # avoid SettingWithCopyWarning clustersize = report[ (report["Features"] == "dummy") & (report["Type"] == "count") - ].copy() # avoid SettingWithCopyWarning - clustersize.Type = ( - "ClusterSize" # rename created cluster df to match report column names - ) + ].copy() + # rename created predicted cluster to match report column names + clustersize.Type = "ClusterSize" clustersize.Features = "# of Customers" + # calculating the proportion of cluster clusterproportion = pd.DataFrame( - clustersize.iloc[:, 2:].values - / clustersize.iloc[:, 2:].values.sum() # calculating the proportion of cluster + clustersize.iloc[:, 2:].to_numpy() / clustersize.iloc[:, 2:].to_numpy().sum() ) - clusterproportion[ - "Type" - ] = "% of Customers" # rename created cluster df to match report column names + # rename created predicted cluster to match report column names + clusterproportion["Type"] = "% of Customers" clusterproportion["Features"] = "ClusterProportion" cols = clusterproportion.columns.tolist() cols = cols[-2:] + cols[:-2] clusterproportion = clusterproportion[cols] # rearrange columns to match report clusterproportion.columns = report.columns + # generating dataframe with count of nan values a = pd.DataFrame( abs( - report[report["Type"] == "count"].iloc[:, 2:].values - - clustersize.iloc[:, 2:].values + report[report["Type"] == "count"].iloc[:, 2:].to_numpy() + - clustersize.iloc[:, 2:].to_numpy() ) - ) # 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 + # filling values in order to match report + a.Features = report[report["Type"] == "count"].Features.tolist() 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 for cluster size - report = pd.concat( - [report, a, clustersize, clusterproportion], axis=0 - ) # concat report with cluster size and nan values + # drop count values except for cluster size + report = report.drop(report[report.Type == "count"].index) + # concat report with cluster size and nan values + report = pd.concat([report, a, clustersize, clusterproportion], axis=0) report["Mark"] = report["Features"].isin(clustering_variables) cols = report.columns.tolist() cols = cols[0:2] + cols[-1:] + cols[2:-1] diff --git a/maths/least_common_multiple.py b/maths/least_common_multiple.py index 4f28da8ab2a7..a5c4bf8e3625 100644 --- a/maths/least_common_multiple.py +++ b/maths/least_common_multiple.py @@ -67,8 +67,8 @@ def test_lcm_function(self): 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(slow_result, self.expected_results[i]) - self.assertEqual(fast_result, self.expected_results[i]) + assert slow_result == self.expected_results[i] + assert fast_result == self.expected_results[i] if __name__ == "__main__": diff --git a/maths/modular_division.py b/maths/modular_division.py index a9d0f65c5b27..260d5683705d 100644 --- a/maths/modular_division.py +++ b/maths/modular_division.py @@ -28,7 +28,9 @@ def modular_division(a: int, b: int, n: int) -> int: 4 """ - assert n > 1 and a > 0 and greatest_common_divisor(a, n) == 1 + assert n > 1 + assert a > 0 + assert greatest_common_divisor(a, n) == 1 (d, t, s) = extended_gcd(n, a) # Implemented below x = (b * s) % n return x @@ -86,7 +88,8 @@ def extended_gcd(a: int, b: int) -> tuple[int, int, int]: ** extended_gcd function is used when d = gcd(a,b) is required in output """ - assert a >= 0 and b >= 0 + assert a >= 0 + assert b >= 0 if b == 0: d, x, y = a, 1, 0 @@ -95,7 +98,8 @@ def extended_gcd(a: int, b: int) -> tuple[int, int, int]: x = q y = p - q * (a // b) - assert a % d == 0 and b % d == 0 + assert a % d == 0 + assert b % d == 0 assert d == a * x + b * y return (d, x, y) diff --git a/maths/prime_check.py b/maths/prime_check.py index 80ab8bc5d2cd..c17877a57705 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -3,6 +3,8 @@ import math import unittest +import pytest + def is_prime(number: int) -> bool: """Checks to see if a number is a prime in O(sqrt(n)). @@ -50,33 +52,31 @@ def is_prime(number: int) -> bool: class Test(unittest.TestCase): def test_primes(self): - self.assertTrue(is_prime(2)) - self.assertTrue(is_prime(3)) - self.assertTrue(is_prime(5)) - self.assertTrue(is_prime(7)) - self.assertTrue(is_prime(11)) - self.assertTrue(is_prime(13)) - self.assertTrue(is_prime(17)) - self.assertTrue(is_prime(19)) - self.assertTrue(is_prime(23)) - self.assertTrue(is_prime(29)) + assert is_prime(2) + assert is_prime(3) + assert is_prime(5) + assert is_prime(7) + assert is_prime(11) + assert is_prime(13) + assert is_prime(17) + assert is_prime(19) + assert is_prime(23) + assert is_prime(29) def test_not_primes(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): is_prime(-19) - self.assertFalse( - is_prime(0), - "Zero doesn't have any positive factors, primes must have exactly two.", - ) - self.assertFalse( - is_prime(1), - "One only has 1 positive factor, primes must have exactly two.", - ) - self.assertFalse(is_prime(2 * 2)) - self.assertFalse(is_prime(2 * 3)) - self.assertFalse(is_prime(3 * 3)) - self.assertFalse(is_prime(3 * 5)) - self.assertFalse(is_prime(3 * 5 * 7)) + assert not is_prime( + 0 + ), "Zero doesn't have any positive factors, primes must have exactly two." + assert not is_prime( + 1 + ), "One only has 1 positive factor, primes must have exactly two." + assert not is_prime(2 * 2) + assert not is_prime(2 * 3) + assert not is_prime(3 * 3) + assert not is_prime(3 * 5) + assert not is_prime(3 * 5 * 7) if __name__ == "__main__": diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index b6e50f70fdcf..7f10ae706e85 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -114,7 +114,8 @@ def __add__(self, another: Matrix) -> Matrix: # Validation assert isinstance(another, Matrix) - assert self.row == another.row and self.column == another.column + assert self.row == another.row + assert self.column == another.column # Add result = Matrix(self.row, self.column) @@ -225,7 +226,8 @@ def sherman_morrison(self, u: Matrix, v: Matrix) -> Any: """ # Size validation - assert isinstance(u, Matrix) and isinstance(v, Matrix) + assert isinstance(u, Matrix) + assert 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 diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index 65b35fd7e78b..638f97daa2ed 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -31,14 +31,14 @@ logger.addHandler(stream_handler) -@pytest.mark.mat_ops +@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): + logger.info(f"\n\t{test_addition.__name__} returned integer") 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") @@ -46,19 +46,19 @@ def test_addition(mat1, mat2): theo = matop.add(mat1, mat2) assert theo == act else: + logger.info(f"\n\t{test_addition.__name__} with different matrix dims") 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.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): + logger.info(f"\n\t{test_subtraction.__name__} returned integer") 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") @@ -66,12 +66,12 @@ def test_subtraction(mat1, mat2): theo = matop.subtract(mat1, mat2) assert theo == act else: + logger.info(f"\n\t{test_subtraction.__name__} with different matrix dims") 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.mat_ops() @pytest.mark.parametrize( ("mat1", "mat2"), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), (mat_f, mat_h)] ) @@ -86,33 +86,33 @@ def test_multiplication(mat1, mat2): theo = matop.multiply(mat1, mat2) assert theo == act else: + logger.info( + f"\n\t{test_multiplication.__name__} does not meet dim requirements" + ) 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 +@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 +@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.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): + logger.info(f"\n\t{test_transpose.__name__} returned integer") with pytest.raises(TypeError): - logger.info(f"\n\t{test_transpose.__name__} returned integer") matop.transpose(mat) else: act = (np.transpose(mat)).tolist() diff --git a/project_euler/problem_054/test_poker_hand.py b/project_euler/problem_054/test_poker_hand.py index 5735bfc37947..ba5e0c8a2643 100644 --- a/project_euler/problem_054/test_poker_hand.py +++ b/project_euler/problem_054/test_poker_hand.py @@ -147,39 +147,39 @@ 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) +@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) +@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) +@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) +@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) +@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) +@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()) +@pytest.mark.parametrize(("hand", "other", "expected"), generate_random_hands()) def test_compare_random(hand, other, expected): assert PokerHand(hand).compare_with(PokerHand(other)) == expected diff --git a/pyproject.toml b/pyproject.toml index 75da7a04513e..fe5f2f09c4ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,8 @@ ignore = [ # `ruff rule S101` for a description of that rule "PLW0120", # `else` clause on loop without a `break` statement -- FIX ME "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX "PLW2901", # PLW2901: Redefined loop variable -- FIX ME + "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception + "PT018", # Assertion should be broken down into multiple parts "RUF00", # Ambiguous unicode character and other rules "RUF100", # Unused `noqa` directive -- FIX ME "S101", # Use of `assert` detected -- DO NOT FIX @@ -37,6 +39,7 @@ select = [ # https://beta.ruff.rs/docs/rules "BLE", # flake8-blind-except "C4", # flake8-comprehensions "C90", # McCabe cyclomatic complexity + "DJ", # flake8-django "DTZ", # flake8-datetimez "E", # pycodestyle "EM", # flake8-errmsg @@ -52,9 +55,11 @@ select = [ # https://beta.ruff.rs/docs/rules "ISC", # flake8-implicit-str-concat "N", # pep8-naming "NPY", # NumPy-specific rules + "PD", # pandas-vet "PGH", # pygrep-hooks "PIE", # flake8-pie "PL", # Pylint + "PT", # flake8-pytest-style "PYI", # flake8-pyi "RSE", # flake8-raise "RUF", # Ruff-specific rules @@ -70,11 +75,8 @@ select = [ # https://beta.ruff.rs/docs/rules # "ANN", # flake8-annotations # FIX ME? # "COM", # flake8-commas # "D", # pydocstyle -- FIX ME? - # "DJ", # flake8-django # "ERA", # eradicate -- DO NOT FIX # "FBT", # flake8-boolean-trap # FIX ME - # "PD", # pandas-vet - # "PT", # flake8-pytest-style # "PTH", # flake8-use-pathlib # FIX ME # "Q", # flake8-quotes # "RET", # flake8-return # FIX ME? diff --git a/strings/knuth_morris_pratt.py b/strings/knuth_morris_pratt.py index 8a04eb2532c0..5120779c571e 100644 --- a/strings/knuth_morris_pratt.py +++ b/strings/knuth_morris_pratt.py @@ -71,7 +71,8 @@ def get_failure_array(pattern: str) -> list[int]: pattern = "abc1abc12" text1 = "alskfjaldsabc1abc1abc12k23adsfabcabc" text2 = "alskfjaldsk23adsfabcabc" - assert knuth_morris_pratt(text1, pattern) and knuth_morris_pratt(text2, pattern) + assert knuth_morris_pratt(text1, pattern) + assert knuth_morris_pratt(text2, pattern) # Test 2) pattern = "ABABX" diff --git a/strings/rabin_karp.py b/strings/rabin_karp.py index 532c689f8a97..9c0d0fe5c739 100644 --- a/strings/rabin_karp.py +++ b/strings/rabin_karp.py @@ -60,7 +60,8 @@ def test_rabin_karp() -> None: pattern = "abc1abc12" text1 = "alskfjaldsabc1abc1abc12k23adsfabcabc" text2 = "alskfjaldsk23adsfabcabc" - assert rabin_karp(pattern, text1) and not rabin_karp(pattern, text2) + assert rabin_karp(pattern, text1) + assert not rabin_karp(pattern, text2) # Test 2) pattern = "ABABX" From 92fbe60082b782d8b85e9667bd6d7832b5383fa3 Mon Sep 17 00:00:00 2001 From: Vipin Karthic <143083087+vipinkarthic@users.noreply.github.com> Date: Thu, 12 Oct 2023 00:35:24 +0530 Subject: [PATCH 2448/2908] Added doctests to carmichael_number.py (#10210) Co-authored-by: Tianyi Zheng --- maths/carmichael_number.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/maths/carmichael_number.py b/maths/carmichael_number.py index 08b5c70e8fe7..c73908545702 100644 --- a/maths/carmichael_number.py +++ b/maths/carmichael_number.py @@ -16,11 +16,9 @@ def power(x: int, y: int, mod: int) -> int: """ - Examples: >>> power(2, 15, 3) 2 - >>> power(5, 1, 30) 5 """ @@ -36,14 +34,19 @@ def power(x: int, y: int, mod: int) -> int: def is_carmichael_number(n: int) -> bool: """ - Examples: - >>> is_carmichael_number(562) + >>> is_carmichael_number(4) False - >>> is_carmichael_number(561) True - + >>> is_carmichael_number(562) + False + >>> is_carmichael_number(900) + False + >>> is_carmichael_number(1105) + True + >>> is_carmichael_number(8911) + True >>> is_carmichael_number(5.1) Traceback (most recent call last): ... From 09ce6b23d7529aa0e02a6b5cfef1a9b831a3c9ad Mon Sep 17 00:00:00 2001 From: Siddharth Warrier <117698635+siddwarr@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:38:55 +0530 Subject: [PATCH 2449/2908] Count pairs with given sum (#10282) * added power_of_4 * deleted power_of_4 * added pairs_with_given_sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated the comment * updated return hint * updated type hints * updated the variable * updated annotation * updated code * updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added the problem link and used defaultdict * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * corrected import formatting * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pairs_with_given_sum.py * Update data_structures/arrays/pairs_with_given_sum.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../arrays/pairs_with_given_sum.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 data_structures/arrays/pairs_with_given_sum.py diff --git a/data_structures/arrays/pairs_with_given_sum.py b/data_structures/arrays/pairs_with_given_sum.py new file mode 100644 index 000000000000..c4a5ceeae456 --- /dev/null +++ b/data_structures/arrays/pairs_with_given_sum.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +""" +Given an array of integers and an integer req_sum, find the number of pairs of array +elements whose sum is equal to req_sum. + +https://practice.geeksforgeeks.org/problems/count-pairs-with-given-sum5022/0 +""" +from itertools import combinations + + +def pairs_with_sum(arr: list, req_sum: int) -> int: + """ + Return the no. of pairs with sum "sum" + >>> pairs_with_sum([1, 5, 7, 1], 6) + 2 + >>> pairs_with_sum([1, 1, 1, 1, 1, 1, 1, 1], 2) + 28 + >>> pairs_with_sum([1, 7, 6, 2, 5, 4, 3, 1, 9, 8], 7) + 4 + """ + return len([1 for a, b in combinations(arr, 2) if a + b == req_sum]) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 7ea812996c8ee1fa2eb9fbc72b7caaae8eb8ff0e Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Thu, 12 Oct 2023 19:24:07 +0530 Subject: [PATCH 2450/2908] Adds exponential moving average algorithm (#10273) * Adds exponential moving average algorithm * code clean up * spell correction * Modifies I/O types of function * Replaces generator function * Resolved mypy type error * readibility of code and documentation * Update exponential_moving_average.py --------- Co-authored-by: Christian Clauss --- financial/exponential_moving_average.py | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 financial/exponential_moving_average.py diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py new file mode 100644 index 000000000000..0b6cea3b4c91 --- /dev/null +++ b/financial/exponential_moving_average.py @@ -0,0 +1,73 @@ +""" + Calculate the exponential moving average (EMA) on the series of stock prices. + Wikipedia Reference: https://en.wikipedia.org/wiki/Exponential_smoothing + https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential + -moving-average-ema + + Exponential moving average is used in finance to analyze changes stock prices. + EMA is used in conjunction with Simple moving average (SMA), EMA reacts to the + changes in the value quicker than SMA, which is one of the advantages of using EMA. +""" + +from collections.abc import Iterator + + +def exponential_moving_average( + stock_prices: Iterator[float], window_size: int +) -> Iterator[float]: + """ + Yields exponential moving averages of the given stock prices. + >>> tuple(exponential_moving_average(iter([2, 5, 3, 8.2, 6, 9, 10]), 3)) + (2, 3.5, 3.25, 5.725, 5.8625, 7.43125, 8.715625) + + :param stock_prices: A stream of stock prices + :param window_size: The number of stock prices that will trigger a new calculation + of the exponential average (window_size > 0) + :return: Yields a sequence of exponential moving averages + + Formula: + + st = alpha * xt + (1 - alpha) * st_prev + + Where, + st : Exponential moving average at timestamp t + xt : stock price in from the stock prices at timestamp t + st_prev : Exponential moving average at timestamp t-1 + alpha : 2/(1 + window_size) - smoothing factor + + Exponential moving average (EMA) is a rule of thumb technique for + smoothing time series data using an exponential window function. + """ + + if window_size <= 0: + raise ValueError("window_size must be > 0") + + # Calculating smoothing factor + alpha = 2 / (1 + window_size) + + # Exponential average at timestamp t + moving_average = 0.0 + + for i, stock_price in enumerate(stock_prices): + if i <= window_size: + # Assigning simple moving average till the window_size for the first time + # is reached + moving_average = (moving_average + stock_price) * 0.5 if i else stock_price + else: + # Calculating exponential moving average based on current timestamp data + # point and previous exponential average value + moving_average = (alpha * stock_price) + ((1 - alpha) * moving_average) + yield moving_average + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + stock_prices = [2.0, 5, 3, 8.2, 6, 9, 10] + window_size = 3 + result = tuple(exponential_moving_average(iter(stock_prices), window_size)) + print(f"{stock_prices = }") + print(f"{window_size = }") + print(f"{result = }") From ecf21bfc87c1d1cd4730e628279b609151bc6c57 Mon Sep 17 00:00:00 2001 From: Daniela Large <133594563+dannylarge144@users.noreply.github.com> Date: Thu, 12 Oct 2023 16:51:06 +0100 Subject: [PATCH 2451/2908] Added imply gate to boolean algebra (#9849) * Add files via upload * Update imply_gate.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update boolean_algebra/imply_gate.py Co-authored-by: Tianyi Zheng * Update imply_gate.py Made changes requested * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update imply_gate.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- boolean_algebra/imply_gate.py | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 boolean_algebra/imply_gate.py diff --git a/boolean_algebra/imply_gate.py b/boolean_algebra/imply_gate.py new file mode 100644 index 000000000000..151a7ad6439a --- /dev/null +++ b/boolean_algebra/imply_gate.py @@ -0,0 +1,40 @@ +""" +An IMPLY Gate is a logic gate in boolean algebra which results to 1 if +either input 1 is 0, or if input 1 is 1, then the output is 1 only if input 2 is 1. +It is true if input 1 implies input 2. + +Following is the truth table of an IMPLY Gate: + ------------------------------ + | Input 1 | Input 2 | Output | + ------------------------------ + | 0 | 0 | 1 | + | 0 | 1 | 1 | + | 1 | 0 | 0 | + | 1 | 1 | 1 | + ------------------------------ + +Refer - https://en.wikipedia.org/wiki/IMPLY_gate +""" + + +def imply_gate(input_1: int, input_2: int) -> int: + """ + Calculate IMPLY of the input values + + >>> imply_gate(0, 0) + 1 + >>> imply_gate(0, 1) + 1 + >>> imply_gate(1, 0) + 0 + >>> imply_gate(1, 1) + 1 + """ + return int(input_1 == 0 or input_2 == 1) + + +if __name__ == "__main__": + print(imply_gate(0, 0)) + print(imply_gate(0, 1)) + print(imply_gate(1, 0)) + print(imply_gate(1, 1)) From b94cdbab1a7f3793e63526cd29a8f415ff0b55ac Mon Sep 17 00:00:00 2001 From: Pranavkumar Mallela <87595299+pranav-mallela@users.noreply.github.com> Date: Fri, 13 Oct 2023 01:21:53 +0530 Subject: [PATCH 2452/2908] add find triplets with 0 sum (3sum) (#10040) * add find triplets with 0 sum (3sum) * Update find_triplets_with_0_sum.py * Update find_triplets_with_0_sum.py --------- Co-authored-by: Christian Clauss --- .../arrays/find_triplets_with_0_sum.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 data_structures/arrays/find_triplets_with_0_sum.py diff --git a/data_structures/arrays/find_triplets_with_0_sum.py b/data_structures/arrays/find_triplets_with_0_sum.py new file mode 100644 index 000000000000..8217ff857e3d --- /dev/null +++ b/data_structures/arrays/find_triplets_with_0_sum.py @@ -0,0 +1,24 @@ +from itertools import combinations + + +def find_triplets_with_0_sum(nums: list[int]) -> list[list[int]]: + """ + Given a list of integers, return elements a, b, c such that a + b + c = 0. + Args: + nums: list of integers + Returns: + list of lists of integers where sum(each_list) == 0 + Examples: + >>> find_triplets_with_0_sum([-1, 0, 1, 2, -1, -4]) + [[-1, -1, 2], [-1, 0, 1]] + >>> find_triplets_with_0_sum([]) + [] + >>> find_triplets_with_0_sum([0, 0, 0]) + [[0, 0, 0]] + >>> find_triplets_with_0_sum([1, 2, 3, 0, -1, -2, -3]) + [[-3, 0, 3], [-3, 1, 2], [-2, -1, 3], [-2, 0, 2], [-1, 0, 1]] + """ + return [ + list(x) + for x in sorted({abc for abc in combinations(sorted(nums), 3) if not sum(abc)}) + ] From 24f6f8c137a6ba9784c06da3694a1d36781b7a88 Mon Sep 17 00:00:00 2001 From: Daniela Large <133594563+dannylarge144@users.noreply.github.com> Date: Fri, 13 Oct 2023 05:29:39 +0100 Subject: [PATCH 2453/2908] Added nimply gate to boolean_algebra (#10344) * Add files via upload * Update imply_gate.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update boolean_algebra/imply_gate.py Co-authored-by: Tianyi Zheng * Update imply_gate.py Made changes requested * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update imply_gate.py * Added nimply gate * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- boolean_algebra/nimply_gate.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 boolean_algebra/nimply_gate.py diff --git a/boolean_algebra/nimply_gate.py b/boolean_algebra/nimply_gate.py new file mode 100644 index 000000000000..6e34332d9112 --- /dev/null +++ b/boolean_algebra/nimply_gate.py @@ -0,0 +1,40 @@ +""" +An NIMPLY Gate is a logic gate in boolean algebra which results to 0 if +either input 1 is 0, or if input 1 is 1, then it is 0 only if input 2 is 1. +It is false if input 1 implies input 2. It is the negated form of imply + +Following is the truth table of an NIMPLY Gate: + ------------------------------ + | Input 1 | Input 2 | Output | + ------------------------------ + | 0 | 0 | 0 | + | 0 | 1 | 0 | + | 1 | 0 | 1 | + | 1 | 1 | 0 | + ------------------------------ + +Refer - https://en.wikipedia.org/wiki/NIMPLY_gate +""" + + +def nimply_gate(input_1: int, input_2: int) -> int: + """ + Calculate NIMPLY of the input values + + >>> nimply_gate(0, 0) + 0 + >>> nimply_gate(0, 1) + 0 + >>> nimply_gate(1, 0) + 1 + >>> nimply_gate(1, 1) + 0 + """ + return int(input_1 == 1 and input_2 == 0) + + +if __name__ == "__main__": + print(nimply_gate(0, 0)) + print(nimply_gate(0, 1)) + print(nimply_gate(1, 0)) + print(nimply_gate(1, 1)) From ebe66935d2842a0e0cbea58dcc647428f357f15e Mon Sep 17 00:00:00 2001 From: Saahil Mahato <115351000+saahil-mahato@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:49:48 +0545 Subject: [PATCH 2454/2908] Add Solovay-Strassen Primality test (#10335) * Add Solovay-Strassen Primality test * fix: resolve comments * refactor: docs change --- maths/solovay_strassen_primality_test.py | 107 +++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 maths/solovay_strassen_primality_test.py diff --git a/maths/solovay_strassen_primality_test.py b/maths/solovay_strassen_primality_test.py new file mode 100644 index 000000000000..1d11d458369a --- /dev/null +++ b/maths/solovay_strassen_primality_test.py @@ -0,0 +1,107 @@ +""" +This script implements the Solovay-Strassen Primality test. + +This probabilistic primality test is based on Euler's criterion. It is similar +to the Fermat test but uses quadratic residues. It can quickly identify +composite numbers but may occasionally classify composite numbers as prime. + +More details and concepts about this can be found on: +https://en.wikipedia.org/wiki/Solovay%E2%80%93Strassen_primality_test +""" + + +import random + + +def jacobi_symbol(random_a: int, number: int) -> int: + """ + Calculate the Jacobi symbol. The Jacobi symbol is a generalization + of the Legendre symbol, which can be used to simplify computations involving + quadratic residues. The Jacobi symbol is used in primality tests, like the + Solovay-Strassen test, because it helps determine if an integer is a + quadratic residue modulo a given modulus, providing valuable information + about the number's potential primality or compositeness. + + Parameters: + random_a: A randomly chosen integer from 2 to n-2 (inclusive) + number: The number that is tested for primality + + Returns: + jacobi_symbol: The Jacobi symbol is a mathematical function + used to determine whether an integer is a quadratic residue modulo + another integer (usually prime) or not. + + >>> jacobi_symbol(2, 13) + -1 + >>> jacobi_symbol(5, 19) + 1 + >>> jacobi_symbol(7, 14) + 0 + """ + + if random_a in (0, 1): + return random_a + + random_a %= number + t = 1 + + while random_a != 0: + while random_a % 2 == 0: + random_a //= 2 + r = number % 8 + if r in (3, 5): + t = -t + + random_a, number = number, random_a + + if random_a % 4 == number % 4 == 3: + t = -t + + random_a %= number + + return t if number == 1 else 0 + + +def solovay_strassen(number: int, iterations: int) -> bool: + """ + Check whether the input number is prime or not using + the Solovay-Strassen Primality test + + Parameters: + number: The number that is tested for primality + iterations: The number of times that the test is run + which effects the accuracy + + Returns: + result: True if number is probably prime and false + if not + + >>> random.seed(10) + >>> solovay_strassen(13, 5) + True + >>> solovay_strassen(9, 10) + False + >>> solovay_strassen(17, 15) + True + """ + + if number <= 1: + return False + if number <= 3: + return True + + for _ in range(iterations): + a = random.randint(2, number - 2) + x = jacobi_symbol(a, number) + y = pow(a, (number - 1) // 2, number) + + if x == 0 or y != x % number: + return False + + return True + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c85506262d8fc6fcf154651ce8affdfb96b57ece Mon Sep 17 00:00:00 2001 From: Saahil Mahato <115351000+saahil-mahato@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:03:52 +0545 Subject: [PATCH 2455/2908] Add Damerau-Levenshtein distance algorithm (#10159) * Add Damerau-Levenshtein distance algorithm * fix: precommit check * fix: doc correction * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: use variable for length and doc correction * Update damerau_levenshtein_distance.py * Update damerau_levenshtein_distance.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- strings/damerau_levenshtein_distance.py | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 strings/damerau_levenshtein_distance.py diff --git a/strings/damerau_levenshtein_distance.py b/strings/damerau_levenshtein_distance.py new file mode 100644 index 000000000000..72de019499e2 --- /dev/null +++ b/strings/damerau_levenshtein_distance.py @@ -0,0 +1,71 @@ +""" +This script is a implementation of the Damerau-Levenshtein distance algorithm. + +It's an algorithm that measures the edit distance between two string sequences + +More information about this algorithm can be found in this wikipedia article: +https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance +""" + + +def damerau_levenshtein_distance(first_string: str, second_string: str) -> int: + """ + Implements the Damerau-Levenshtein distance algorithm that measures + the edit distance between two strings. + + Parameters: + first_string: The first string to compare + second_string: The second string to compare + + Returns: + distance: The edit distance between the first and second strings + + >>> damerau_levenshtein_distance("cat", "cut") + 1 + >>> damerau_levenshtein_distance("kitten", "sitting") + 3 + >>> damerau_levenshtein_distance("hello", "world") + 4 + >>> damerau_levenshtein_distance("book", "back") + 2 + >>> damerau_levenshtein_distance("container", "containment") + 3 + >>> damerau_levenshtein_distance("container", "containment") + 3 + """ + # Create a dynamic programming matrix to store the distances + dp_matrix = [[0] * (len(second_string) + 1) for _ in range(len(first_string) + 1)] + + # Initialize the matrix + for i in range(len(first_string) + 1): + dp_matrix[i][0] = i + for j in range(len(second_string) + 1): + dp_matrix[0][j] = j + + # Fill the matrix + for i, first_char in enumerate(first_string, start=1): + for j, second_char in enumerate(second_string, start=1): + cost = int(first_char != second_char) + + dp_matrix[i][j] = min( + dp_matrix[i - 1][j] + 1, # Deletion + dp_matrix[i][j - 1] + 1, # Insertion + dp_matrix[i - 1][j - 1] + cost, # Substitution + ) + + if ( + i > 1 + and j > 1 + and first_string[i - 1] == second_string[j - 2] + and first_string[i - 2] == second_string[j - 1] + ): + # Transposition + dp_matrix[i][j] = min(dp_matrix[i][j], dp_matrix[i - 2][j - 2] + cost) + + return dp_matrix[-1][-1] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 1117a50665b053ef7716cf1e80b29e11d30886c7 Mon Sep 17 00:00:00 2001 From: Saurabh Mahapatra <98408932+its-100rabh@users.noreply.github.com> Date: Fri, 13 Oct 2023 21:25:32 +0530 Subject: [PATCH 2456/2908] Modified comments on lower.py (#10369) --- strings/lower.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/strings/lower.py b/strings/lower.py index 9ae419123ceb..49256b0169ef 100644 --- a/strings/lower.py +++ b/strings/lower.py @@ -14,9 +14,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, obtaining the integer representation + # and checking to see if the character is a capital letter. + # If it is a capital letter, it is shifted by 32, making it a lowercase letter. return "".join(chr(ord(char) + 32) if "A" <= char <= "Z" else char for char in word) From d96029e13d181229c692b8e4cafe2661cdae919e Mon Sep 17 00:00:00 2001 From: SalmanSi <114280969+SalmanSi@users.noreply.github.com> Date: Fri, 13 Oct 2023 22:48:31 +0500 Subject: [PATCH 2457/2908] added doctests for dynamicprogramming/minimum_partition (#10033) * added doctests * added doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add doctests to integer_partition.py * Update minimum_partition.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- dynamic_programming/integer_partition.py | 24 +++++++++++++++ dynamic_programming/minimum_partition.py | 38 ++++++++++++++++++++---- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/dynamic_programming/integer_partition.py b/dynamic_programming/integer_partition.py index 8ed2e51bd4bd..145bc29d0fca 100644 --- a/dynamic_programming/integer_partition.py +++ b/dynamic_programming/integer_partition.py @@ -3,10 +3,34 @@ 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. +* https://en.wikipedia.org/wiki/Partition_(number_theory) +* https://en.wikipedia.org/wiki/Partition_function_(number_theory) """ def partition(m: int) -> int: + """ + >>> partition(5) + 7 + >>> partition(7) + 15 + >>> partition(100) + 190569292 + >>> partition(1_000) + 24061467864032622473692149727991 + >>> partition(-7) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> partition(0) + Traceback (most recent call last): + ... + IndexError: list assignment index out of range + >>> partition(7.8) + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + """ memo: list[list[int]] = [[0 for _ in range(m)] for _ in range(m + 1)] for i in range(m + 1): memo[i][0] = 1 diff --git a/dynamic_programming/minimum_partition.py b/dynamic_programming/minimum_partition.py index e6188cb33b3a..748c0599efb0 100644 --- a/dynamic_programming/minimum_partition.py +++ b/dynamic_programming/minimum_partition.py @@ -3,7 +3,7 @@ """ -def find_min(arr: list[int]) -> int: +def find_min(numbers: list[int]) -> int: """ >>> find_min([1, 2, 3, 4, 5]) 1 @@ -15,9 +15,37 @@ def find_min(arr: list[int]) -> int: 3 >>> find_min([]) 0 + >>> find_min([1, 2, 3, 4]) + 0 + >>> find_min([0, 0, 0, 0]) + 0 + >>> find_min([-1, -5, 5, 1]) + 0 + >>> find_min([-1, -5, 5, 1]) + 0 + >>> find_min([9, 9, 9, 9, 9]) + 9 + >>> find_min([1, 5, 10, 3]) + 1 + >>> find_min([-1, 0, 1]) + 0 + >>> find_min(range(10, 0, -1)) + 1 + >>> find_min([-1]) + Traceback (most recent call last): + -- + IndexError: list assignment index out of range + >>> find_min([0, 0, 0, 1, 2, -4]) + Traceback (most recent call last): + ... + IndexError: list assignment index out of range + >>> find_min([-1, -5, -10, -3]) + Traceback (most recent call last): + ... + IndexError: list assignment index out of range """ - n = len(arr) - s = sum(arr) + n = len(numbers) + s = sum(numbers) dp = [[False for x in range(s + 1)] for y in range(n + 1)] @@ -31,8 +59,8 @@ def find_min(arr: list[int]) -> int: for j in range(1, s + 1): dp[i][j] = dp[i - 1][j] - if arr[i - 1] <= j: - dp[i][j] = dp[i][j] or dp[i - 1][j - arr[i - 1]] + if numbers[i - 1] <= j: + dp[i][j] = dp[i][j] or dp[i - 1][j - numbers[i - 1]] for j in range(int(s / 2), -1, -1): if dp[n][j] is True: From 9fb0cd271efec0fc651a5143aedda42f3dc93ea8 Mon Sep 17 00:00:00 2001 From: Dale Dai <145884899+CouldNot@users.noreply.github.com> Date: Fri, 13 Oct 2023 23:47:08 -0700 Subject: [PATCH 2458/2908] Expand euler phi function doctest (#10401) --- maths/basic_maths.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/maths/basic_maths.py b/maths/basic_maths.py index 26c52c54983e..c9e3d00fa23b 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -98,7 +98,17 @@ def euler_phi(n: int) -> int: """Calculate Euler's Phi Function. >>> euler_phi(100) 40 + >>> euler_phi(0) + Traceback (most recent call last): + ... + ValueError: Only positive numbers are accepted + >>> euler_phi(-10) + Traceback (most recent call last): + ... + ValueError: Only positive numbers are accepted """ + if n <= 0: + raise ValueError("Only positive numbers are accepted") s = n for x in set(prime_factors(n)): s *= (x - 1) / x From 0b2c9fb6f164468b51baa4866c1b8c4f01ec8b64 Mon Sep 17 00:00:00 2001 From: Baron105 <76466796+Baron105@users.noreply.github.com> Date: Sat, 14 Oct 2023 12:31:23 +0530 Subject: [PATCH 2459/2908] Adding avg and mps speed formulae for ideal gases (#10229) * avg and mps speed formulae added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * avg and mps speed formulae added * fixed_spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ws * added amicable numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added amicable numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed * changed name of file and added code improvements * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * issues fixed due to pi * requested changes added --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- physics/speeds_of_gas_molecules.py | 111 +++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 physics/speeds_of_gas_molecules.py diff --git a/physics/speeds_of_gas_molecules.py b/physics/speeds_of_gas_molecules.py new file mode 100644 index 000000000000..a50d1c0f6d76 --- /dev/null +++ b/physics/speeds_of_gas_molecules.py @@ -0,0 +1,111 @@ +""" +The root-mean-square, average and most probable speeds of gas molecules are +derived from the Maxwell-Boltzmann distribution. The Maxwell-Boltzmann +distribution is a probability distribution that describes the distribution of +speeds of particles in an ideal gas. + +The distribution is given by the following equation: + + ------------------------------------------------- + | f(v) = (M/2πRT)^(3/2) * 4πv^2 * e^(-Mv^2/2RT) | + ------------------------------------------------- + +where: + f(v) is the fraction of molecules with a speed v + M is the molar mass of the gas in kg/mol + R is the gas constant + T is the absolute temperature + +More information about the Maxwell-Boltzmann distribution can be found here: +https://en.wikipedia.org/wiki/Maxwell%E2%80%93Boltzmann_distribution + +The average speed can be calculated by integrating the Maxwell-Boltzmann distribution +from 0 to infinity and dividing by the total number of molecules. The result is: + + --------------------- + | vavg = √(8RT/πM) | + --------------------- + +The most probable speed is the speed at which the Maxwell-Boltzmann distribution +is at its maximum. This can be found by differentiating the Maxwell-Boltzmann +distribution with respect to v and setting the result equal to zero. The result is: + + --------------------- + | vmp = √(2RT/M) | + --------------------- + +The root-mean-square speed is another measure of the average speed +of the molecules in a gas. It is calculated by taking the square root +of the average of the squares of the speeds of the molecules. The result is: + + --------------------- + | vrms = √(3RT/M) | + --------------------- + +Here we have defined functions to calculate the average and +most probable speeds of molecules in a gas given the +temperature and molar mass of the gas. +""" + +# import the constants R and pi from the scipy.constants library +from scipy.constants import R, pi + + +def avg_speed_of_molecule(temperature: float, molar_mass: float) -> float: + """ + Takes the temperature (in K) and molar mass (in kg/mol) of a gas + and returns the average speed of a molecule in the gas (in m/s). + + Examples: + >>> avg_speed_of_molecule(273, 0.028) # nitrogen at 273 K + 454.3488755020387 + >>> avg_speed_of_molecule(300, 0.032) # oxygen at 300 K + 445.52572733919885 + >>> avg_speed_of_molecule(-273, 0.028) # invalid temperature + Traceback (most recent call last): + ... + Exception: Absolute temperature cannot be less than 0 K + >>> avg_speed_of_molecule(273, 0) # invalid molar mass + Traceback (most recent call last): + ... + Exception: Molar mass should be greater than 0 kg/mol + """ + + if temperature < 0: + raise Exception("Absolute temperature cannot be less than 0 K") + if molar_mass <= 0: + raise Exception("Molar mass should be greater than 0 kg/mol") + return (8 * R * temperature / (pi * molar_mass)) ** 0.5 + + +def mps_speed_of_molecule(temperature: float, molar_mass: float) -> float: + """ + Takes the temperature (in K) and molar mass (in kg/mol) of a gas + and returns the most probable speed of a molecule in the gas (in m/s). + + Examples: + >>> mps_speed_of_molecule(273, 0.028) # nitrogen at 273 K + 402.65620701908966 + >>> mps_speed_of_molecule(300, 0.032) # oxygen at 300 K + 394.836895549922 + >>> mps_speed_of_molecule(-273, 0.028) # invalid temperature + Traceback (most recent call last): + ... + Exception: Absolute temperature cannot be less than 0 K + >>> mps_speed_of_molecule(273, 0) # invalid molar mass + Traceback (most recent call last): + ... + Exception: Molar mass should be greater than 0 kg/mol + """ + + if temperature < 0: + raise Exception("Absolute temperature cannot be less than 0 K") + if molar_mass <= 0: + raise Exception("Molar mass should be greater than 0 kg/mol") + return (2 * R * temperature / molar_mass) ** 0.5 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 37cae3f56169348e97262b1b8f7671785be77a5b Mon Sep 17 00:00:00 2001 From: Muhammad Umer Farooq <115654418+Muhammadummerr@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:31:43 +0500 Subject: [PATCH 2460/2908] Updated test cases of power_sum.py (#9978) * Updated test cases of power_sum.py * updated * updated. * remove extra comment and used ** instead of pow * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update backtracking/power_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- backtracking/power_sum.py | 42 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/backtracking/power_sum.py b/backtracking/power_sum.py index fcf1429f8570..ee2eac426ec7 100644 --- a/backtracking/power_sum.py +++ b/backtracking/power_sum.py @@ -6,8 +6,6 @@ The only solution is 2^2+3^2. Constraints: 1<=X<=1000, 2<=N<=10. """ -from math import pow - def backtrack( needed_sum: int, @@ -19,25 +17,25 @@ def backtrack( """ >>> backtrack(13, 2, 1, 0, 0) (0, 1) - >>> backtrack(100, 2, 1, 0, 0) - (0, 3) - >>> backtrack(100, 3, 1, 0, 0) + >>> backtrack(10, 2, 1, 0, 0) + (0, 1) + >>> backtrack(10, 3, 1, 0, 0) + (0, 0) + >>> backtrack(20, 2, 1, 0, 0) (0, 1) - >>> backtrack(800, 2, 1, 0, 0) - (0, 561) - >>> backtrack(1000, 10, 1, 0, 0) + >>> backtrack(15, 10, 1, 0, 0) (0, 0) - >>> backtrack(400, 2, 1, 0, 0) - (0, 55) - >>> backtrack(50, 1, 1, 0, 0) - (0, 3658) + >>> backtrack(16, 2, 1, 0, 0) + (0, 1) + >>> backtrack(20, 1, 1, 0, 0) + (0, 64) """ if current_sum == needed_sum: # If the sum of the powers is equal to needed_sum, then we have a solution. solutions_count += 1 return current_sum, solutions_count - i_to_n = int(pow(current_number, power)) + i_to_n = current_number**power if current_sum + i_to_n <= needed_sum: # If the sum of the powers is less than needed_sum, then continue adding powers. current_sum += i_to_n @@ -57,17 +55,17 @@ def solve(needed_sum: int, power: int) -> int: """ >>> solve(13, 2) 1 - >>> solve(100, 2) - 3 - >>> solve(100, 3) + >>> solve(10, 2) 1 - >>> solve(800, 2) - 561 - >>> solve(1000, 10) + >>> solve(10, 3) 0 - >>> solve(400, 2) - 55 - >>> solve(50, 1) + >>> solve(20, 2) + 1 + >>> solve(15, 10) + 0 + >>> solve(16, 2) + 1 + >>> solve(20, 1) Traceback (most recent call last): ... ValueError: Invalid input From 71b372f5e2fd313268018df237d401efd7795464 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 14 Oct 2023 09:34:05 -0400 Subject: [PATCH 2461/2908] Remove doctest in `xgboost_regressor.py` main function (#10422) * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Update xgboost_regressor.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- machine_learning/xgboost_regressor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/machine_learning/xgboost_regressor.py b/machine_learning/xgboost_regressor.py index a540e3ab03eb..52e041c55ea2 100644 --- a/machine_learning/xgboost_regressor.py +++ b/machine_learning/xgboost_regressor.py @@ -39,13 +39,13 @@ def xgboost( def main() -> None: """ - >>> main() - Mean Absolute Error : 0.30957163379906033 - Mean Square Error : 0.22611560196662744 - The URL for this algorithm https://xgboost.readthedocs.io/en/stable/ California house price dataset is used to demonstrate the algorithm. + + Expected error values: + Mean Absolute Error: 0.30957163379906033 + Mean Square Error: 0.22611560196662744 """ # Load California house price dataset california = fetch_california_housing() @@ -55,8 +55,8 @@ def main() -> None: ) predictions = xgboost(x_train, y_train, x_test) # Error printing - print(f"Mean Absolute Error : {mean_absolute_error(y_test, predictions)}") - print(f"Mean Square Error : {mean_squared_error(y_test, predictions)}") + print(f"Mean Absolute Error: {mean_absolute_error(y_test, predictions)}") + print(f"Mean Square Error: {mean_squared_error(y_test, predictions)}") if __name__ == "__main__": From 212cdfe36c3599804027c79c26ee814e53a12703 Mon Sep 17 00:00:00 2001 From: Dean Bring Date: Sat, 14 Oct 2023 08:35:12 -0700 Subject: [PATCH 2462/2908] Added validate sudoku board function (#9881) * Added algorithm to deeply clone a graph * Fixed file name and removed a function call * Removed nested function and fixed class parameter types * Fixed doctests * bug fix * Added class decorator * Updated doctests and fixed precommit errors * Cleaned up code * Simplified doctest * Added doctests * Code simplification * Created function which validates sudoku boards * Update matrix/validate_sudoku_board.py * Fixed precommit errors * Removed file accidentally included * Improved readability and simplicity * Add timeit benchmarks * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update validate_sudoku_board.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- matrix/validate_sudoku_board.py | 107 ++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 matrix/validate_sudoku_board.py diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py new file mode 100644 index 000000000000..0ee7b3df0b83 --- /dev/null +++ b/matrix/validate_sudoku_board.py @@ -0,0 +1,107 @@ +""" +LeetCode 36. Valid Sudoku +https://leetcode.com/problems/valid-sudoku/ +https://en.wikipedia.org/wiki/Sudoku + +Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be +validated according to the following rules: + +- Each row must contain the digits 1-9 without repetition. +- Each column must contain the digits 1-9 without repetition. +- Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 + without repetition. + +Note: + +A Sudoku board (partially filled) could be valid but is not necessarily +solvable. + +Only the filled cells need to be validated according to the mentioned rules. +""" + +from collections import defaultdict + +NUM_SQUARES = 9 +EMPTY_CELL = "." + + +def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: + """ + This function validates (but does not solve) a sudoku board. + The board may be valid but unsolvable. + + >>> is_valid_sudoku_board([ + ... ["5","3",".",".","7",".",".",".","."] + ... ,["6",".",".","1","9","5",".",".","."] + ... ,[".","9","8",".",".",".",".","6","."] + ... ,["8",".",".",".","6",".",".",".","3"] + ... ,["4",".",".","8",".","3",".",".","1"] + ... ,["7",".",".",".","2",".",".",".","6"] + ... ,[".","6",".",".",".",".","2","8","."] + ... ,[".",".",".","4","1","9",".",".","5"] + ... ,[".",".",".",".","8",".",".","7","9"] + ... ]) + True + >>> is_valid_sudoku_board([ + ... ["8","3",".",".","7",".",".",".","."] + ... ,["6",".",".","1","9","5",".",".","."] + ... ,[".","9","8",".",".",".",".","6","."] + ... ,["8",".",".",".","6",".",".",".","3"] + ... ,["4",".",".","8",".","3",".",".","1"] + ... ,["7",".",".",".","2",".",".",".","6"] + ... ,[".","6",".",".",".",".","2","8","."] + ... ,[".",".",".","4","1","9",".",".","5"] + ... ,[".",".",".",".","8",".",".","7","9"] + ... ]) + False + >>> is_valid_sudoku_board([["1", "2", "3", "4", "5", "6", "7", "8", "9"]]) + Traceback (most recent call last): + ... + ValueError: Sudoku boards must be 9x9 squares. + >>> is_valid_sudoku_board( + ... [["1"], ["2"], ["3"], ["4"], ["5"], ["6"], ["7"], ["8"], ["9"]] + ... ) + Traceback (most recent call last): + ... + ValueError: Sudoku boards must be 9x9 squares. + """ + if len(sudoku_board) != NUM_SQUARES or ( + any(len(row) != NUM_SQUARES for row in sudoku_board) + ): + error_message = f"Sudoku boards must be {NUM_SQUARES}x{NUM_SQUARES} squares." + raise ValueError(error_message) + + row_values: defaultdict[int, set[str]] = defaultdict(set) + col_values: defaultdict[int, set[str]] = defaultdict(set) + box_values: defaultdict[tuple[int, int], set[str]] = defaultdict(set) + + for row in range(NUM_SQUARES): + for col in range(NUM_SQUARES): + value = sudoku_board[row][col] + + if value == EMPTY_CELL: + continue + + box = (row // 3, col // 3) + + if ( + value in row_values[row] + or value in col_values[col] + or value in box_values[box] + ): + return False + + row_values[row].add(value) + col_values[col].add(value) + box_values[box].add(value) + + return True + + +if __name__ == "__main__": + from doctest import testmod + from timeit import timeit + + testmod() + print(timeit("is_valid_sudoku_board(valid_board)", globals=globals())) + print(timeit("is_valid_sudoku_board(invalid_board)", globals=globals())) From 3ba23384794bc5ce61a300b96d2b721d9d58eccd Mon Sep 17 00:00:00 2001 From: Aakash Giri Date: Sat, 14 Oct 2023 21:47:11 +0530 Subject: [PATCH 2463/2908] Add Title Case Conversion (#10439) [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci added more test case and type hint [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci updated naming convention --- strings/title.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 strings/title.py diff --git a/strings/title.py b/strings/title.py new file mode 100644 index 000000000000..1ec2df548e2d --- /dev/null +++ b/strings/title.py @@ -0,0 +1,57 @@ +def to_title_case(word: str) -> str: + """ + Converts a string to capitalized case, preserving the input as is + + >>> to_title_case("Aakash") + 'Aakash' + + >>> to_title_case("aakash") + 'Aakash' + + >>> to_title_case("AAKASH") + 'Aakash' + + >>> to_title_case("aAkAsH") + 'Aakash' + """ + + """ + Convert the first character to uppercase if it's lowercase + """ + if "a" <= word[0] <= "z": + word = chr(ord(word[0]) - 32) + word[1:] + + """ + Convert the remaining characters to lowercase if they are uppercase + """ + for i in range(1, len(word)): + if "A" <= word[i] <= "Z": + word = word[:i] + chr(ord(word[i]) + 32) + word[i + 1 :] + + return word + + +def sentence_to_title_case(input_str: str) -> str: + """ + Converts a string to title case, preserving the input as is + + >>> sentence_to_title_case("Aakash Giri") + 'Aakash Giri' + + >>> sentence_to_title_case("aakash giri") + 'Aakash Giri' + + >>> sentence_to_title_case("AAKASH GIRI") + 'Aakash Giri' + + >>> sentence_to_title_case("aAkAsH gIrI") + 'Aakash Giri' + """ + + return " ".join(to_title_case(word) for word in input_str.split()) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 1969259868451684ab05663cc208f06af20d483f Mon Sep 17 00:00:00 2001 From: Manpreet Singh <63737630+ManpreetSingh2004@users.noreply.github.com> Date: Sat, 14 Oct 2023 23:05:01 +0530 Subject: [PATCH 2464/2908] Performance: 80% faster Project Euler 145 (#10445) * Performance: 80% faster Project Euler145 * Added timeit benchmark * >>> slow_solution() doctest --- project_euler/problem_145/sol1.py | 70 +++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py index e9fc1a199161..71b851178fdb 100644 --- a/project_euler/problem_145/sol1.py +++ b/project_euler/problem_145/sol1.py @@ -17,17 +17,17 @@ ODD_DIGITS = [1, 3, 5, 7, 9] -def reversible_numbers( +def slow_reversible_numbers( remaining_length: int, remainder: int, digits: list[int], length: int ) -> int: """ Count the number of reversible numbers of given length. Iterate over possible digits considering parity of current sum remainder. - >>> reversible_numbers(1, 0, [0], 1) + >>> slow_reversible_numbers(1, 0, [0], 1) 0 - >>> reversible_numbers(2, 0, [0] * 2, 2) + >>> slow_reversible_numbers(2, 0, [0] * 2, 2) 20 - >>> reversible_numbers(3, 0, [0] * 3, 3) + >>> slow_reversible_numbers(3, 0, [0] * 3, 3) 100 """ if remaining_length == 0: @@ -51,7 +51,7 @@ def reversible_numbers( result = 0 for digit in range(10): digits[length // 2] = digit - result += reversible_numbers( + result += slow_reversible_numbers( 0, (remainder + 2 * digit) // 10, digits, length ) return result @@ -67,7 +67,7 @@ def reversible_numbers( for digit2 in other_parity_digits: digits[(length - remaining_length) // 2] = digit2 - result += reversible_numbers( + result += slow_reversible_numbers( remaining_length - 2, (remainder + digit1 + digit2) // 10, digits, @@ -76,6 +76,42 @@ def reversible_numbers( return result +def slow_solution(max_power: int = 9) -> int: + """ + To evaluate the solution, use solution() + >>> slow_solution(3) + 120 + >>> slow_solution(6) + 18720 + >>> slow_solution(7) + 68720 + """ + result = 0 + for length in range(1, max_power + 1): + result += slow_reversible_numbers(length, 0, [0] * length, length) + return result + + +def reversible_numbers( + remaining_length: int, remainder: int, digits: list[int], length: int +) -> int: + """ + Count the number of reversible numbers of given length. + Iterate over possible digits considering parity of current sum remainder. + >>> reversible_numbers(1, 0, [0], 1) + 0 + >>> reversible_numbers(2, 0, [0] * 2, 2) + 20 + >>> reversible_numbers(3, 0, [0] * 3, 3) + 100 + """ + # There exist no reversible 1, 5, 9, 13 (ie. 4k+1) digit numbers + if (length - 1) % 4 == 0: + return 0 + + return slow_reversible_numbers(length, 0, [0] * length, length) + + def solution(max_power: int = 9) -> int: """ To evaluate the solution, use solution() @@ -92,5 +128,25 @@ def solution(max_power: int = 9) -> int: return result +def benchmark() -> None: + """ + Benchmarks + """ + # Running performance benchmarks... + # slow_solution : 292.9300301000003 + # solution : 54.90970860000016 + + from timeit import timeit + + print("Running performance benchmarks...") + + print(f"slow_solution : {timeit('slow_solution()', globals=globals(), number=10)}") + print(f"solution : {timeit('solution()', globals=globals(), number=10)}") + + if __name__ == "__main__": - print(f"{solution() = }") + print(f"Solution : {solution()}") + benchmark() + + # for i in range(1, 15): + # print(f"{i}. {reversible_numbers(i, 0, [0]*i, i)}") From f968dda5e9b81bd7dd3c5e9b7a69a9a08ed3ead7 Mon Sep 17 00:00:00 2001 From: Saurabh Mahapatra <98408932+its-100rabh@users.noreply.github.com> Date: Sun, 15 Oct 2023 00:32:37 +0530 Subject: [PATCH 2465/2908] Updated Comments on upper.py (#10442) * Updated Comments on upper.py * Update upper.py * Update upper.py --------- Co-authored-by: Christian Clauss --- strings/upper.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/strings/upper.py b/strings/upper.py index 5edd40b79808..0f68a27b99c6 100644 --- a/strings/upper.py +++ b/strings/upper.py @@ -1,6 +1,8 @@ def upper(word: str) -> str: """ - Will convert the entire string to uppercase letters + Convert an entire string to ASCII uppercase letters by looking for lowercase ASCII + letters and subtracting 32 from their integer representation to get the uppercase + letter. >>> upper("wow") 'WOW' @@ -11,10 +13,6 @@ def upper(word: str) -> str: >>> 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 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 c9ba5e1b6f319e34815660542d8ca0c777c8008a Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 14 Oct 2023 16:08:52 -0400 Subject: [PATCH 2466/2908] Disable unused dependencies (#10467) Comment out dependencies in requirements.txt that are only used by currently-disabled files --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 25dba6f5a250..1e64818bbb6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,19 @@ beautifulsoup4 fake_useragent imageio -keras +keras ; python_version < '3.12' lxml matplotlib numpy opencv-python pandas pillow -projectq +# projectq # uncomment once quantum/quantum_random.py is fixed qiskit ; python_version < '3.12' qiskit-aer ; python_version < '3.12' requests rich -scikit-fuzzy +# scikit-fuzzy # uncomment once fuzzy_logic/fuzzy_operations.py is fixed scikit-learn statsmodels sympy @@ -21,4 +21,4 @@ tensorflow ; python_version < '3.12' texttable tweepy xgboost -yulewalker +# yulewalker # uncomment once audio_filters/equal_loudness_filter.py is fixed From 3ecad36f92d26676cc73276553cd99763b025b33 Mon Sep 17 00:00:00 2001 From: Manpreet Singh <63737630+ManpreetSingh2004@users.noreply.github.com> Date: Sun, 15 Oct 2023 10:15:44 +0530 Subject: [PATCH 2467/2908] fix: incorrect range detection in find_missing_number (#10361) * Fix incorrect range detection in find_missing_number * Support consecutive decreasing numbers Added support for consecutive decreasing numbers in the find_missing_number function. * Support unordered numbers --- bit_manipulation/missing_number.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bit_manipulation/missing_number.py b/bit_manipulation/missing_number.py index 92502a778ace..32b949daa717 100644 --- a/bit_manipulation/missing_number.py +++ b/bit_manipulation/missing_number.py @@ -11,11 +11,18 @@ def find_missing_number(nums: list[int]) -> int: Example: >>> find_missing_number([0, 1, 3, 4]) 2 + >>> find_missing_number([1, 3, 4, 5, 6]) + 2 + >>> find_missing_number([6, 5, 4, 2, 1]) + 3 + >>> find_missing_number([6, 1, 5, 3, 4]) + 2 """ - n = len(nums) - missing_number = n + low = min(nums) + high = max(nums) + missing_number = high - for i in range(n): - missing_number ^= i ^ nums[i] + for i in range(low, high): + missing_number ^= i ^ nums[i - low] return missing_number From 7dbc30181826aa26600f8d24c92b1587b31677c6 Mon Sep 17 00:00:00 2001 From: Ravi Kumar <119737193+ravi-ivar-7@users.noreply.github.com> Date: Sun, 15 Oct 2023 14:37:29 +0530 Subject: [PATCH 2468/2908] added rkf45 method (#10438) * added rkf45 method * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated rkf45.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated rkf45.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update rkf45.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update rkf45.py with suggestions * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Improved Code Quality rkf45.py * Added more test cases and exception rkf45.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update rkf45.py * corrected some spellings. rkf45.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update rkf45.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update rkf45.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/rkf45.py | 112 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 maths/rkf45.py diff --git a/maths/rkf45.py b/maths/rkf45.py new file mode 100644 index 000000000000..29fd447b61b8 --- /dev/null +++ b/maths/rkf45.py @@ -0,0 +1,112 @@ +""" +Use the Runge-Kutta-Fehlberg method to solve Ordinary Differential Equations. +""" + +from collections.abc import Callable + +import numpy as np + + +def runge_futta_fehlberg_45( + func: Callable, + x_initial: float, + y_initial: float, + step_size: float, + x_final: float, +) -> np.ndarray: + """ + Solve an Ordinary Differential Equations using Runge-Kutta-Fehlberg Method (rkf45) + of order 5. + + https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta%E2%80%93Fehlberg_method + + args: + func: An ordinary differential equation (ODE) as function of x and y. + x_initial: The initial value of x. + y_initial: The initial value of y. + step_size: The increment value of x. + x_final: The final value of x. + + Returns: + Solution of y at each nodal point + + # exact value of y[1] is tan(0.2) = 0.2027100937470787 + >>> def f(x, y): + ... return 1 + y**2 + >>> y = runge_futta_fehlberg_45(f, 0, 0, 0.2, 1) + >>> y[1] + 0.2027100937470787 + >>> def f(x,y): + ... return x + >>> y = runge_futta_fehlberg_45(f, -1, 0, 0.2, 0) + >>> y[1] + -0.18000000000000002 + >>> y = runge_futta_fehlberg_45(5, 0, 0, 0.1, 1) + Traceback (most recent call last): + ... + TypeError: 'int' object is not callable + >>> def f(x, y): + ... return x + y + >>> y = runge_futta_fehlberg_45(f, 0, 0, 0.2, -1) + Traceback (most recent call last): + ... + ValueError: The final value x must be greater than initial value of x. + >>> def f(x, y): + ... return x + >>> y = runge_futta_fehlberg_45(f, -1, 0, -0.2, 0) + Traceback (most recent call last): + ... + ValueError: Step size must be positive. + """ + if x_initial >= x_final: + raise ValueError("The final value x must be greater than initial value of x.") + + if step_size <= 0: + raise ValueError("Step size must be positive.") + + n = int((x_final - x_initial) / step_size) + y = np.zeros( + (n + 1), + ) + x = np.zeros(n + 1) + y[0] = y_initial + x[0] = x_initial + for i in range(n): + k1 = step_size * func(x[i], y[i]) + k2 = step_size * func(x[i] + step_size / 4, y[i] + k1 / 4) + k3 = step_size * func( + x[i] + (3 / 8) * step_size, y[i] + (3 / 32) * k1 + (9 / 32) * k2 + ) + k4 = step_size * func( + x[i] + (12 / 13) * step_size, + y[i] + (1932 / 2197) * k1 - (7200 / 2197) * k2 + (7296 / 2197) * k3, + ) + k5 = step_size * func( + x[i] + step_size, + y[i] + (439 / 216) * k1 - 8 * k2 + (3680 / 513) * k3 - (845 / 4104) * k4, + ) + k6 = step_size * func( + x[i] + step_size / 2, + y[i] + - (8 / 27) * k1 + + 2 * k2 + - (3544 / 2565) * k3 + + (1859 / 4104) * k4 + - (11 / 40) * k5, + ) + y[i + 1] = ( + y[i] + + (16 / 135) * k1 + + (6656 / 12825) * k3 + + (28561 / 56430) * k4 + - (9 / 50) * k5 + + (2 / 55) * k6 + ) + x[i + 1] = step_size + x[i] + return y + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 1ebae5d43e2ce23ef98a0804bf1fa077d2fa5daf Mon Sep 17 00:00:00 2001 From: Manpreet Singh <63737630+ManpreetSingh2004@users.noreply.github.com> Date: Sun, 15 Oct 2023 14:47:22 +0530 Subject: [PATCH 2469/2908] Performance: 75% faster Project Euler 187 (#10503) * Add comments and wikipedia link in calculate_prime_numbers * Add improved calculate_prime_numbers * Separate slow_solution and new_solution * Use for loops in solution * Separate while_solution and new solution * Add performance benchmark * Add doctest for calculate_prime_numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed white space --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- project_euler/problem_187/sol1.py | 118 ++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_187/sol1.py b/project_euler/problem_187/sol1.py index 12f03e2a7023..8944776fef50 100644 --- a/project_euler/problem_187/sol1.py +++ b/project_euler/problem_187/sol1.py @@ -14,29 +14,89 @@ from math import isqrt -def calculate_prime_numbers(max_number: int) -> list[int]: +def slow_calculate_prime_numbers(max_number: int) -> list[int]: """ - Returns prime numbers below max_number + Returns prime numbers below max_number. + See: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes - >>> calculate_prime_numbers(10) + >>> slow_calculate_prime_numbers(10) [2, 3, 5, 7] + + >>> slow_calculate_prime_numbers(2) + [] """ + # List containing a bool value for every number below max_number/2 is_prime = [True] * max_number + for i in range(2, isqrt(max_number - 1) + 1): if is_prime[i]: + # Mark all multiple of i as not prime for j in range(i**2, max_number, i): is_prime[j] = False return [i for i in range(2, max_number) if is_prime[i]] -def solution(max_number: int = 10**8) -> int: +def calculate_prime_numbers(max_number: int) -> list[int]: + """ + Returns prime numbers below max_number. + See: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes + + >>> calculate_prime_numbers(10) + [2, 3, 5, 7] + + >>> calculate_prime_numbers(2) + [] + """ + + if max_number <= 2: + return [] + + # List containing a bool value for every odd number below max_number/2 + is_prime = [True] * (max_number // 2) + + for i in range(3, isqrt(max_number - 1) + 1, 2): + if is_prime[i // 2]: + # Mark all multiple of i as not prime using list slicing + is_prime[i**2 // 2 :: i] = [False] * ( + # Same as: (max_number - (i**2)) // (2 * i) + 1 + # but faster than len(is_prime[i**2 // 2 :: i]) + len(range(i**2 // 2, max_number // 2, i)) + ) + + return [2] + [2 * i + 1 for i in range(1, max_number // 2) if is_prime[i]] + + +def slow_solution(max_number: int = 10**8) -> int: """ Returns the number of composite integers below max_number have precisely two, - not necessarily distinct, prime factors + not necessarily distinct, prime factors. - >>> solution(30) + >>> slow_solution(30) + 10 + """ + + prime_numbers = slow_calculate_prime_numbers(max_number // 2) + + semiprimes_count = 0 + left = 0 + right = len(prime_numbers) - 1 + while left <= right: + while prime_numbers[left] * prime_numbers[right] >= max_number: + right -= 1 + semiprimes_count += right - left + 1 + left += 1 + + return semiprimes_count + + +def while_solution(max_number: int = 10**8) -> int: + """ + Returns the number of composite integers below max_number have precisely two, + not necessarily distinct, prime factors. + + >>> while_solution(30) 10 """ @@ -54,5 +114,49 @@ def solution(max_number: int = 10**8) -> int: return semiprimes_count +def solution(max_number: int = 10**8) -> int: + """ + Returns the number of composite integers below max_number have precisely two, + not necessarily distinct, prime factors. + + >>> solution(30) + 10 + """ + + prime_numbers = calculate_prime_numbers(max_number // 2) + + semiprimes_count = 0 + right = len(prime_numbers) - 1 + for left in range(len(prime_numbers)): + if left > right: + break + for r in range(right, left - 2, -1): + if prime_numbers[left] * prime_numbers[r] < max_number: + break + right = r + semiprimes_count += right - left + 1 + + return semiprimes_count + + +def benchmark() -> None: + """ + Benchmarks + """ + # Running performance benchmarks... + # slow_solution : 108.50874730000032 + # while_sol : 28.09581200000048 + # solution : 25.063097400000515 + + from timeit import timeit + + print("Running performance benchmarks...") + + print(f"slow_solution : {timeit('slow_solution()', globals=globals(), number=10)}") + print(f"while_sol : {timeit('while_solution()', globals=globals(), number=10)}") + print(f"solution : {timeit('solution()', globals=globals(), number=10)}") + + if __name__ == "__main__": - print(f"{solution() = }") + print(f"Solution: {solution()}") + benchmark() From 85cdb93a0d7a306633faa03a134d0d39da7076a8 Mon Sep 17 00:00:00 2001 From: Kosuri L Indu <118645569+kosuri-indu@users.noreply.github.com> Date: Sun, 15 Oct 2023 15:48:28 +0530 Subject: [PATCH 2470/2908] [Add] : Job Sequence program under GREEDY methods (#10482) * to add job seq program * to add job seq program * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * to add definitions in parameters * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * to add definitions in parameters * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * to add definitions in parameters * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changes as recommended * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * type hint error resolved * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed lambda * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * import stmts order * Update and rename job_sequence.py to job_sequence_with_deadline.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- scheduling/job_sequence_with_deadline.py | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 scheduling/job_sequence_with_deadline.py diff --git a/scheduling/job_sequence_with_deadline.py b/scheduling/job_sequence_with_deadline.py new file mode 100644 index 000000000000..fccb49cd88e8 --- /dev/null +++ b/scheduling/job_sequence_with_deadline.py @@ -0,0 +1,62 @@ +""" +Given a list of tasks, each with a deadline and reward, calculate which tasks can be +completed to yield the maximum reward. Each task takes one unit of time to complete, +and we can only work on one task at a time. Once a task has passed its deadline, it +can no longer be scheduled. + +Example : +tasks_info = [(4, 20), (1, 10), (1, 40), (1, 30)] +max_tasks will return (2, [2, 0]) - +Scheduling these tasks would result in a reward of 40 + 20 + +This problem can be solved using the concept of "GREEDY ALGORITHM". +Time Complexity - O(n log n) +https://medium.com/@nihardudhat2000/job-sequencing-with-deadline-17ddbb5890b5 +""" +from dataclasses import dataclass +from operator import attrgetter + + +@dataclass +class Task: + task_id: int + deadline: int + reward: int + + +def max_tasks(tasks_info: list[tuple[int, int]]) -> list[int]: + """ + Create a list of Task objects that are sorted so the highest rewards come first. + Return a list of those task ids that can be completed before i becomes too high. + >>> max_tasks([(4, 20), (1, 10), (1, 40), (1, 30)]) + [2, 0] + >>> max_tasks([(1, 10), (2, 20), (3, 30), (2, 40)]) + [3, 2] + >>> max_tasks([(9, 10)]) + [0] + >>> max_tasks([(-9, 10)]) + [] + >>> max_tasks([]) + [] + >>> max_tasks([(0, 10), (0, 20), (0, 30), (0, 40)]) + [] + >>> max_tasks([(-1, 10), (-2, 20), (-3, 30), (-4, 40)]) + [] + """ + tasks = sorted( + ( + Task(task_id, deadline, reward) + for task_id, (deadline, reward) in enumerate(tasks_info) + ), + key=attrgetter("reward"), + reverse=True, + ) + return [task.task_id for i, task in enumerate(tasks, start=1) if task.deadline >= i] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{max_tasks([(4, 20), (1, 10), (1, 40), (1, 30)]) = }") + print(f"{max_tasks([(1, 10), (2, 20), (3, 30), (2, 40)]) = }") From 777eca813a8030e7a674072c79da144e92dde07a Mon Sep 17 00:00:00 2001 From: Ravi Kumar <119737193+ravi-ivar-7@users.noreply.github.com> Date: Sun, 15 Oct 2023 16:25:56 +0530 Subject: [PATCH 2471/2908] Corrected typo in function name and doctests. rkf45.py (#10518) * Corrected typo in function name and doctests. rkf45.py There was a mistake in name of function (runge_futta_fehlberg instead of runge_kutta_fehlberg) . I have corrected this in function name and also doctest. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename rkf45.py to runge_kutta_fehlberg_45.py * Update runge_kutta_fehlberg_45.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/{rkf45.py => runge_kutta_fehlberg_45.py} | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) rename maths/{rkf45.py => runge_kutta_fehlberg_45.py} (84%) diff --git a/maths/rkf45.py b/maths/runge_kutta_fehlberg_45.py similarity index 84% rename from maths/rkf45.py rename to maths/runge_kutta_fehlberg_45.py index 29fd447b61b8..8181fe3015fc 100644 --- a/maths/rkf45.py +++ b/maths/runge_kutta_fehlberg_45.py @@ -7,7 +7,7 @@ import numpy as np -def runge_futta_fehlberg_45( +def runge_kutta_fehlberg_45( func: Callable, x_initial: float, y_initial: float, @@ -33,33 +33,35 @@ def runge_futta_fehlberg_45( # exact value of y[1] is tan(0.2) = 0.2027100937470787 >>> def f(x, y): ... return 1 + y**2 - >>> y = runge_futta_fehlberg_45(f, 0, 0, 0.2, 1) + >>> y = runge_kutta_fehlberg_45(f, 0, 0, 0.2, 1) >>> y[1] 0.2027100937470787 >>> def f(x,y): ... return x - >>> y = runge_futta_fehlberg_45(f, -1, 0, 0.2, 0) + >>> y = runge_kutta_fehlberg_45(f, -1, 0, 0.2, 0) >>> y[1] -0.18000000000000002 - >>> y = runge_futta_fehlberg_45(5, 0, 0, 0.1, 1) + >>> y = runge_kutta_fehlberg_45(5, 0, 0, 0.1, 1) Traceback (most recent call last): ... TypeError: 'int' object is not callable >>> def f(x, y): ... return x + y - >>> y = runge_futta_fehlberg_45(f, 0, 0, 0.2, -1) + >>> y = runge_kutta_fehlberg_45(f, 0, 0, 0.2, -1) Traceback (most recent call last): ... - ValueError: The final value x must be greater than initial value of x. + ValueError: The final value of x must be greater than initial value of x. >>> def f(x, y): ... return x - >>> y = runge_futta_fehlberg_45(f, -1, 0, -0.2, 0) + >>> y = runge_kutta_fehlberg_45(f, -1, 0, -0.2, 0) Traceback (most recent call last): ... ValueError: Step size must be positive. """ if x_initial >= x_final: - raise ValueError("The final value x must be greater than initial value of x.") + raise ValueError( + "The final value of x must be greater than initial value of x." + ) if step_size <= 0: raise ValueError("Step size must be positive.") From 79a91cca956b99acf5e4bd785ff0640c9e591b89 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 15 Oct 2023 16:57:08 +0200 Subject: [PATCH 2472/2908] Fix typo in filename: ciphers/trifid_cipher.py (#10516) * Update and rename trafid_cipher.py to trifid_cipher.py * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 14 +++++++++++++- ciphers/{trafid_cipher.py => trifid_cipher.py} | 0 2 files changed, 13 insertions(+), 1 deletion(-) rename ciphers/{trafid_cipher.py => trifid_cipher.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 2c6000c94ed4..ceee9972dd97 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -63,7 +63,9 @@ ## Boolean Algebra * [And Gate](boolean_algebra/and_gate.py) + * [Imply Gate](boolean_algebra/imply_gate.py) * [Nand Gate](boolean_algebra/nand_gate.py) + * [Nimply Gate](boolean_algebra/nimply_gate.py) * [Nor Gate](boolean_algebra/nor_gate.py) * [Not Gate](boolean_algebra/not_gate.py) * [Or Gate](boolean_algebra/or_gate.py) @@ -119,9 +121,9 @@ * [Shuffled Shift Cipher](ciphers/shuffled_shift_cipher.py) * [Simple Keyword Cypher](ciphers/simple_keyword_cypher.py) * [Simple Substitution Cipher](ciphers/simple_substitution_cipher.py) - * [Trafid Cipher](ciphers/trafid_cipher.py) * [Transposition Cipher](ciphers/transposition_cipher.py) * [Transposition Cipher Encrypt Decrypt File](ciphers/transposition_cipher_encrypt_decrypt_file.py) + * [Trifid Cipher](ciphers/trifid_cipher.py) * [Vigenere Cipher](ciphers/vigenere_cipher.py) * [Xor Cipher](ciphers/xor_cipher.py) @@ -174,7 +176,9 @@ ## Data Structures * Arrays * [Equilibrium Index In Array](data_structures/arrays/equilibrium_index_in_array.py) + * [Find Triplets With 0 Sum](data_structures/arrays/find_triplets_with_0_sum.py) * [Median Two Array](data_structures/arrays/median_two_array.py) + * [Pairs With Given Sum](data_structures/arrays/pairs_with_given_sum.py) * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) * [Product Sum](data_structures/arrays/product_sum.py) @@ -385,6 +389,7 @@ ## Financial * [Equated Monthly Installments](financial/equated_monthly_installments.py) + * [Exponential Moving Average](financial/exponential_moving_average.py) * [Interest](financial/interest.py) * [Present Value](financial/present_value.py) * [Price Plus Tax](financial/price_plus_tax.py) @@ -670,6 +675,7 @@ * [Radians](maths/radians.py) * [Radix2 Fft](maths/radix2_fft.py) * [Remove Digit](maths/remove_digit.py) + * [Rkf45](maths/rkf45.py) * [Runge Kutta](maths/runge_kutta.py) * [Segmented Sieve](maths/segmented_sieve.py) * Series @@ -688,6 +694,7 @@ * [Sin](maths/sin.py) * [Sock Merchant](maths/sock_merchant.py) * [Softmax](maths/softmax.py) + * [Solovay Strassen Primality Test](maths/solovay_strassen_primality_test.py) * [Square Root](maths/square_root.py) * [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py) * [Sum Of Digits](maths/sum_of_digits.py) @@ -728,6 +735,7 @@ * [Spiral Print](matrix/spiral_print.py) * Tests * [Test Matrix Operation](matrix/tests/test_matrix_operation.py) + * [Validate Sudoku Board](matrix/validate_sudoku_board.py) ## Networking Flow * [Ford Fulkerson](networking_flow/ford_fulkerson.py) @@ -803,6 +811,7 @@ * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Shear Stress](physics/shear_stress.py) * [Speed Of Sound](physics/speed_of_sound.py) + * [Speeds Of Gas Molecules](physics/speeds_of_gas_molecules.py) ## Project Euler * Problem 001 @@ -1106,6 +1115,7 @@ ## Scheduling * [First Come First Served](scheduling/first_come_first_served.py) * [Highest Response Ratio Next](scheduling/highest_response_ratio_next.py) + * [Job Sequence With Deadline](scheduling/job_sequence_with_deadline.py) * [Job Sequencing With Deadline](scheduling/job_sequencing_with_deadline.py) * [Multi Level Feedback Queue](scheduling/multi_level_feedback_queue.py) * [Non Preemptive Shortest Job First](scheduling/non_preemptive_shortest_job_first.py) @@ -1193,6 +1203,7 @@ * [Capitalize](strings/capitalize.py) * [Check Anagrams](strings/check_anagrams.py) * [Credit Card Validator](strings/credit_card_validator.py) + * [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py) * [Dna](strings/dna.py) * [Frequency Finder](strings/frequency_finder.py) @@ -1225,6 +1236,7 @@ * [String Switch Case](strings/string_switch_case.py) * [Strip](strings/strip.py) * [Text Justification](strings/text_justification.py) + * [Title](strings/title.py) * [Top K Frequent Words](strings/top_k_frequent_words.py) * [Upper](strings/upper.py) * [Wave](strings/wave.py) diff --git a/ciphers/trafid_cipher.py b/ciphers/trifid_cipher.py similarity index 100% rename from ciphers/trafid_cipher.py rename to ciphers/trifid_cipher.py From b5474ab68a0e1eea6bbfba445feca39db471c62f Mon Sep 17 00:00:00 2001 From: Rahul Jangra <106389897+leonado10000@users.noreply.github.com> Date: Sun, 15 Oct 2023 20:33:03 +0530 Subject: [PATCH 2473/2908] [ADD] : maths joint probabilty distribution (#10508) * Create joint_probability_distribution.py * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update joint_probability_distribution.py * Update joint_probability_distribution.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maclaurin_series.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Revert changes to maclaurin_series.py * Revert changes to maclaurin_series.py * Update joint_probability_distribution.py * Update joint_probability_distribution.py * Update joint_probability_distribution.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/joint_probability_distribution.py | 124 ++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 maths/joint_probability_distribution.py diff --git a/maths/joint_probability_distribution.py b/maths/joint_probability_distribution.py new file mode 100644 index 000000000000..6fbcea40c358 --- /dev/null +++ b/maths/joint_probability_distribution.py @@ -0,0 +1,124 @@ +""" +Calculate joint probability distribution +https://en.wikipedia.org/wiki/Joint_probability_distribution +""" + + +def joint_probability_distribution( + x_values: list[int], + y_values: list[int], + x_probabilities: list[float], + y_probabilities: list[float], +) -> dict: + """ + >>> joint_distribution = joint_probability_distribution( + ... [1, 2], [-2, 5, 8], [0.7, 0.3], [0.3, 0.5, 0.2] + ... ) + >>> from math import isclose + >>> isclose(joint_distribution.pop((1, 8)), 0.14) + True + >>> joint_distribution + {(1, -2): 0.21, (1, 5): 0.35, (2, -2): 0.09, (2, 5): 0.15, (2, 8): 0.06} + """ + return { + (x, y): x_prob * y_prob + for x, x_prob in zip(x_values, x_probabilities) + for y, y_prob in zip(y_values, y_probabilities) + } + + +# Function to calculate the expectation (mean) +def expectation(values: list, probabilities: list) -> float: + """ + >>> from math import isclose + >>> isclose(expectation([1, 2], [0.7, 0.3]), 1.3) + True + """ + return sum(x * p for x, p in zip(values, probabilities)) + + +# Function to calculate the variance +def variance(values: list[int], probabilities: list[float]) -> float: + """ + >>> from math import isclose + >>> isclose(variance([1,2],[0.7,0.3]), 0.21) + True + """ + mean = expectation(values, probabilities) + return sum((x - mean) ** 2 * p for x, p in zip(values, probabilities)) + + +# Function to calculate the covariance +def covariance( + x_values: list[int], + y_values: list[int], + x_probabilities: list[float], + y_probabilities: list[float], +) -> float: + """ + >>> covariance([1, 2], [-2, 5, 8], [0.7, 0.3], [0.3, 0.5, 0.2]) + -2.7755575615628914e-17 + """ + mean_x = expectation(x_values, x_probabilities) + mean_y = expectation(y_values, y_probabilities) + return sum( + (x - mean_x) * (y - mean_y) * px * py + for x, px in zip(x_values, x_probabilities) + for y, py in zip(y_values, y_probabilities) + ) + + +# Function to calculate the standard deviation +def standard_deviation(variance: float) -> float: + """ + >>> standard_deviation(0.21) + 0.458257569495584 + """ + return variance**0.5 + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + # Input values for X and Y + x_vals = input("Enter values of X separated by spaces: ").split() + y_vals = input("Enter values of Y separated by spaces: ").split() + + # Convert input values to integers + x_values = [int(x) for x in x_vals] + y_values = [int(y) for y in y_vals] + + # Input probabilities for X and Y + x_probs = input("Enter probabilities for X separated by spaces: ").split() + y_probs = input("Enter probabilities for Y separated by spaces: ").split() + assert len(x_values) == len(x_probs) + assert len(y_values) == len(y_probs) + + # Convert input probabilities to floats + x_probabilities = [float(p) for p in x_probs] + y_probabilities = [float(p) for p in y_probs] + + # Calculate the joint probability distribution + jpd = joint_probability_distribution( + x_values, y_values, x_probabilities, y_probabilities + ) + + # Print the joint probability distribution + print( + "\n".join( + f"P(X={x}, Y={y}) = {probability}" for (x, y), probability in jpd.items() + ) + ) + mean_xy = expectation( + [x * y for x in x_values for y in y_values], + [px * py for px in x_probabilities for py in y_probabilities], + ) + print(f"x mean: {expectation(x_values, x_probabilities) = }") + print(f"y mean: {expectation(y_values, y_probabilities) = }") + print(f"xy mean: {mean_xy}") + print(f"x: {variance(x_values, x_probabilities) = }") + print(f"y: {variance(y_values, y_probabilities) = }") + print(f"{covariance(x_values, y_values, x_probabilities, y_probabilities) = }") + print(f"x: {standard_deviation(variance(x_values, x_probabilities)) = }") + print(f"y: {standard_deviation(variance(y_values, y_probabilities)) = }") From 755659a62f2c976e1e359a4c0af576b2aa8843a8 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 15 Oct 2023 11:16:56 -0400 Subject: [PATCH 2474/2908] Omit `project_euler/` from coverage reports (#10469) * Omit project_euler/ and scripts/ from coverage reports * Add scripts/ back into coverage reports --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fe5f2f09c4ec..9c9262d77748 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,7 +128,10 @@ addopts = [ ] [tool.coverage.report] -omit = [".env/*"] +omit = [ + ".env/*", + "project_euler/*" +] sort = "Cover" [tool.codespell] From 52040a7bf1795e32cbf3863729c010aa55020063 Mon Sep 17 00:00:00 2001 From: Aroson <74296409+Aroson1@users.noreply.github.com> Date: Sun, 15 Oct 2023 21:05:02 +0530 Subject: [PATCH 2475/2908] Added 555 timer duty cycle and freqency in astable mode. (#10456) * Add files via upload * Update wheatstone_bridge.py * Update wheatstone_bridge.py * Create IC_555_Timer.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update IC_555_Timer.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update IC_555_Timer.py * Update and rename IC_555_Timer.py to ic_555_timer.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update ic_555_timer.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update ic_555_timer.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update ic_555_timer.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup ic_555_timer.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- electronics/ic_555_timer.py | 75 +++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 electronics/ic_555_timer.py diff --git a/electronics/ic_555_timer.py b/electronics/ic_555_timer.py new file mode 100644 index 000000000000..e187e1928dca --- /dev/null +++ b/electronics/ic_555_timer.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +""" + Calculate the frequency and/or duty cycle of an astable 555 timer. + * https://en.wikipedia.org/wiki/555_timer_IC#Astable + + These functions take in the value of the external resistances (in ohms) + and capacitance (in Microfarad), and calculates the following: + + ------------------------------------- + | Freq = 1.44 /[( R1+ 2 x R2) x C1] | ... in Hz + ------------------------------------- + where Freq is the frequency, + R1 is the first resistance in ohms, + R2 is the second resistance in ohms, + C1 is the capacitance in Microfarads. + + ------------------------------------------------ + | Duty Cycle = (R1 + R2) / (R1 + 2 x R2) x 100 | ... in % + ------------------------------------------------ + where R1 is the first resistance in ohms, + R2 is the second resistance in ohms. +""" + + +def astable_frequency( + resistance_1: float, resistance_2: float, capacitance: float +) -> float: + """ + Usage examples: + >>> astable_frequency(resistance_1=45, resistance_2=45, capacitance=7) + 1523.8095238095239 + >>> astable_frequency(resistance_1=356, resistance_2=234, capacitance=976) + 1.7905459175553078 + >>> astable_frequency(resistance_1=2, resistance_2=-1, capacitance=2) + Traceback (most recent call last): + ... + ValueError: All values must be positive + >>> astable_frequency(resistance_1=45, resistance_2=45, capacitance=0) + Traceback (most recent call last): + ... + ValueError: All values must be positive + """ + + if resistance_1 <= 0 or resistance_2 <= 0 or capacitance <= 0: + raise ValueError("All values must be positive") + return (1.44 / ((resistance_1 + 2 * resistance_2) * capacitance)) * 10**6 + + +def astable_duty_cycle(resistance_1: float, resistance_2: float) -> float: + """ + Usage examples: + >>> astable_duty_cycle(resistance_1=45, resistance_2=45) + 66.66666666666666 + >>> astable_duty_cycle(resistance_1=356, resistance_2=234) + 71.60194174757282 + >>> astable_duty_cycle(resistance_1=2, resistance_2=-1) + Traceback (most recent call last): + ... + ValueError: All values must be positive + >>> astable_duty_cycle(resistance_1=0, resistance_2=0) + Traceback (most recent call last): + ... + ValueError: All values must be positive + """ + + if resistance_1 <= 0 or resistance_2 <= 0: + raise ValueError("All values must be positive") + return (resistance_1 + resistance_2) / (resistance_1 + 2 * resistance_2) * 100 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b2636d90b3fe697ff64a62b928edfbeccf216e8a Mon Sep 17 00:00:00 2001 From: K Anamithra Date: Sun, 15 Oct 2023 22:11:29 +0530 Subject: [PATCH 2476/2908] added implementing stack using two queues (#10076) * added implementing stack using two queues * Update Stack using two queues * Update stack_using_two_queues.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update stack_using_two_queues.py * Update stack_using_two_queues.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update stack_using_two_queues.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update stack_using_two_queues.py * Update stack_using_two_queues.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../stacks/stack_using_two_queues.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 data_structures/stacks/stack_using_two_queues.py diff --git a/data_structures/stacks/stack_using_two_queues.py b/data_structures/stacks/stack_using_two_queues.py new file mode 100644 index 000000000000..4b73246a045c --- /dev/null +++ b/data_structures/stacks/stack_using_two_queues.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +from collections import deque +from dataclasses import dataclass, field + + +@dataclass +class StackWithQueues: + """ + https://www.geeksforgeeks.org/implement-stack-using-queue/ + + >>> stack = StackWithQueues() + >>> stack.push(1) + >>> stack.push(2) + >>> stack.push(3) + >>> stack.peek() + 3 + >>> stack.pop() + 3 + >>> stack.peek() + 2 + >>> stack.pop() + 2 + >>> stack.pop() + 1 + >>> stack.peek() is None + True + >>> stack.pop() + Traceback (most recent call last): + ... + IndexError: pop from an empty deque + """ + + main_queue: deque[int] = field(default_factory=deque) + temp_queue: deque[int] = field(default_factory=deque) + + def push(self, item: int) -> None: + self.temp_queue.append(item) + while self.main_queue: + self.temp_queue.append(self.main_queue.popleft()) + self.main_queue, self.temp_queue = self.temp_queue, self.main_queue + + def pop(self) -> int: + return self.main_queue.popleft() + + def peek(self) -> int | None: + return self.main_queue[0] if self.main_queue else None + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + stack: StackWithQueues | None = StackWithQueues() + while stack: + print("\nChoose operation:") + print("1. Push") + print("2. Pop") + print("3. Peek") + print("4. Quit") + + choice = input("Enter choice (1/2/3/4): ") + + if choice == "1": + element = int(input("Enter an integer to push: ").strip()) + stack.push(element) + print(f"{element} pushed onto the stack.") + elif choice == "2": + popped_element = stack.pop() + if popped_element is not None: + print(f"Popped element: {popped_element}") + else: + print("Stack is empty.") + elif choice == "3": + peeked_element = stack.peek() + if peeked_element is not None: + print(f"Top element: {peeked_element}") + else: + print("Stack is empty.") + elif choice == "4": + del stack + stack = None + else: + print("Invalid choice. Please try again.") From 68e6d5ad7e9af8929a22a889b1182706abbfcb50 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 15 Oct 2023 19:11:05 +0200 Subject: [PATCH 2477/2908] validate_solutions.py: os.getenv('GITHUB_TOKEN', '') (#10546) * validate_solutions.py: os.getenv('GITHUB_TOKEN', '') @tianyizheng02 * updating DIRECTORY.md * f this --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 +++- scripts/validate_solutions.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index ceee9972dd97..6213f26b6d93 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -373,6 +373,7 @@ * [Electric Conductivity](electronics/electric_conductivity.py) * [Electric Power](electronics/electric_power.py) * [Electrical Impedance](electronics/electrical_impedance.py) + * [Ic 555 Timer](electronics/ic_555_timer.py) * [Ind Reactance](electronics/ind_reactance.py) * [Ohms Law](electronics/ohms_law.py) * [Real And Reactive Power](electronics/real_and_reactive_power.py) @@ -622,6 +623,7 @@ * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) * [Is Square Free](maths/is_square_free.py) * [Jaccard Similarity](maths/jaccard_similarity.py) + * [Joint Probability Distribution](maths/joint_probability_distribution.py) * [Juggler Sequence](maths/juggler_sequence.py) * [Karatsuba](maths/karatsuba.py) * [Krishnamurthy Number](maths/krishnamurthy_number.py) @@ -675,8 +677,8 @@ * [Radians](maths/radians.py) * [Radix2 Fft](maths/radix2_fft.py) * [Remove Digit](maths/remove_digit.py) - * [Rkf45](maths/rkf45.py) * [Runge Kutta](maths/runge_kutta.py) + * [Runge Kutta Fehlberg 45](maths/runge_kutta_fehlberg_45.py) * [Segmented Sieve](maths/segmented_sieve.py) * Series * [Arithmetic](maths/series/arithmetic.py) diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index ca4af5261a8f..f27ec9ca60aa 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -55,7 +55,7 @@ def added_solution_file_path() -> list[pathlib.Path]: solution_file_paths = [] headers = { "Accept": "application/vnd.github.v3+json", - "Authorization": "token " + os.environ["GITHUB_TOKEN"], + "Authorization": f"token {os.getenv('GITHUB_TOKEN', '')}", } files = requests.get(get_files_url(), headers=headers).json() for file in files: From 7bdd1cd2beadf494685d1da63fb410343290de98 Mon Sep 17 00:00:00 2001 From: Barun Parua <76466796+Baron105@users.noreply.github.com> Date: Sun, 15 Oct 2023 22:43:40 +0530 Subject: [PATCH 2478/2908] updated physics/archimedes_principle.py (#10479) * avg and mps speed formulae added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * avg and mps speed formulae added * fixed_spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ws * added amicable numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added amicable numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed * changed name of file and added code improvements * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * issues fixed due to pi * requested changes added * added some doctests for exception handling, imported g from scipy and allowed zero gravity * removed_scipy_import * Update and rename archimedes_principle.py to archimedes_principle_of_buoyant_force.py * Update archimedes_principle_of_buoyant_force.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- physics/archimedes_principle.py | 49 --------------- .../archimedes_principle_of_buoyant_force.py | 63 +++++++++++++++++++ 2 files changed, 63 insertions(+), 49 deletions(-) delete mode 100644 physics/archimedes_principle.py create mode 100644 physics/archimedes_principle_of_buoyant_force.py diff --git a/physics/archimedes_principle.py b/physics/archimedes_principle.py deleted file mode 100644 index 6ecfc65e7461..000000000000 --- a/physics/archimedes_principle.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Calculates buoyant force on object submerged within static fluid. -Discovered by greek mathematician, Archimedes. The principle is named after him. - -Equation for calculating buoyant force: -Fb = ρ * V * g - -Source: -- https://en.wikipedia.org/wiki/Archimedes%27_principle -""" - - -# Acceleration Constant on Earth (unit m/s^2) -g = 9.80665 - - -def archimedes_principle( - fluid_density: float, volume: float, gravity: float = g -) -> float: - """ - Args: - fluid_density: density of fluid (kg/m^3) - volume: volume of object / liquid being displaced by object - gravity: Acceleration from gravity. Gravitational force on system, - Default is Earth Gravity - returns: - buoyant force on object in Newtons - - >>> archimedes_principle(fluid_density=997, volume=0.5, gravity=9.8) - 4885.3 - >>> archimedes_principle(fluid_density=997, volume=0.7) - 6844.061035 - """ - - if fluid_density <= 0: - raise ValueError("Impossible fluid density") - if volume < 0: - raise ValueError("Impossible Object volume") - if gravity <= 0: - raise ValueError("Impossible Gravity") - - return fluid_density * gravity * volume - - -if __name__ == "__main__": - import doctest - - # run doctest - doctest.testmod() diff --git a/physics/archimedes_principle_of_buoyant_force.py b/physics/archimedes_principle_of_buoyant_force.py new file mode 100644 index 000000000000..5f569837220f --- /dev/null +++ b/physics/archimedes_principle_of_buoyant_force.py @@ -0,0 +1,63 @@ +""" +Calculate the buoyant force of any body completely or partially submerged in a static +fluid. This principle was discovered by the Greek mathematician Archimedes. + +Equation for calculating buoyant force: +Fb = ρ * V * g + +https://en.wikipedia.org/wiki/Archimedes%27_principle +""" + + +# Acceleration Constant on Earth (unit m/s^2) +g = 9.80665 # Also available in scipy.constants.g + + +def archimedes_principle( + fluid_density: float, volume: float, gravity: float = g +) -> float: + """ + Args: + fluid_density: density of fluid (kg/m^3) + volume: volume of object/liquid being displaced by the object (m^3) + gravity: Acceleration from gravity. Gravitational force on the system, + The default is Earth Gravity + returns: + the buoyant force on an object in Newtons + + >>> archimedes_principle(fluid_density=500, volume=4, gravity=9.8) + 19600.0 + >>> archimedes_principle(fluid_density=997, volume=0.5, gravity=9.8) + 4885.3 + >>> archimedes_principle(fluid_density=997, volume=0.7) + 6844.061035 + >>> archimedes_principle(fluid_density=997, volume=-0.7) + Traceback (most recent call last): + ... + ValueError: Impossible object volume + >>> archimedes_principle(fluid_density=0, volume=0.7) + Traceback (most recent call last): + ... + ValueError: Impossible fluid density + >>> archimedes_principle(fluid_density=997, volume=0.7, gravity=0) + 0.0 + >>> archimedes_principle(fluid_density=997, volume=0.7, gravity=-9.8) + Traceback (most recent call last): + ... + ValueError: Impossible gravity + """ + + if fluid_density <= 0: + raise ValueError("Impossible fluid density") + if volume <= 0: + raise ValueError("Impossible object volume") + if gravity < 0: + raise ValueError("Impossible gravity") + + return fluid_density * gravity * volume + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 89d12dfe99d51f7df983ddbc6b0c93e1130fc47b Mon Sep 17 00:00:00 2001 From: Kosuri L Indu <118645569+kosuri-indu@users.noreply.github.com> Date: Mon, 16 Oct 2023 00:57:47 +0530 Subject: [PATCH 2479/2908] [Add] : Wildcard Matching program under DYNAMIC PROGRAMMING (#10403) * To add wildcard_matching.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changes for doctest errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * code changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/wildcard_matching.py | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 dynamic_programming/wildcard_matching.py diff --git a/dynamic_programming/wildcard_matching.py b/dynamic_programming/wildcard_matching.py new file mode 100644 index 000000000000..4ffc4b5d46aa --- /dev/null +++ b/dynamic_programming/wildcard_matching.py @@ -0,0 +1,62 @@ +""" +Given two strings, an input string and a pattern, +this program checks if the input string matches the pattern. + +Example : +input_string = "baaabab" +pattern = "*****ba*****ab" +Output: True + +This problem can be solved using the concept of "DYNAMIC PROGRAMMING". + +We create a 2D boolean matrix, where each entry match_matrix[i][j] is True +if the first i characters in input_string match the first j characters +of pattern. We initialize the first row and first column based on specific +rules, then fill up the rest of the matrix using a bottom-up dynamic +programming approach. + +The amount of match that will be determined is equal to match_matrix[n][m] +where n and m are lengths of the input_string and pattern respectively. + +""" + + +def is_pattern_match(input_string: str, pattern: str) -> bool: + """ + >>> is_pattern_match('baaabab','*****ba*****ba') + False + >>> is_pattern_match('baaabab','*****ba*****ab') + True + >>> is_pattern_match('aa','*') + True + """ + + input_length = len(input_string) + pattern_length = len(pattern) + + match_matrix = [[False] * (pattern_length + 1) for _ in range(input_length + 1)] + + match_matrix[0][0] = True + + for j in range(1, pattern_length + 1): + if pattern[j - 1] == "*": + match_matrix[0][j] = match_matrix[0][j - 1] + + for i in range(1, input_length + 1): + for j in range(1, pattern_length + 1): + if pattern[j - 1] in ("?", input_string[i - 1]): + match_matrix[i][j] = match_matrix[i - 1][j - 1] + elif pattern[j - 1] == "*": + match_matrix[i][j] = match_matrix[i - 1][j] or match_matrix[i][j - 1] + else: + match_matrix[i][j] = False + + return match_matrix[input_length][pattern_length] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print(f"{is_pattern_match('baaabab','*****ba*****ab')}") From 4004b862d583a32cb1a809c4ea54d87635a273eb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 15 Oct 2023 21:40:13 +0200 Subject: [PATCH 2480/2908] Revert "validate_solutions.py: os.getenv('GITHUB_TOKEN', '')" (#10552) * Revert "validate_solutions.py: os.getenv('GITHUB_TOKEN', '') (#10546)" This reverts commit 68e6d5ad7e9af8929a22a889b1182706abbfcb50. * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 +++- scripts/validate_solutions.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 6213f26b6d93..5c63e6316547 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -265,6 +265,7 @@ * [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py) * [Prefix Evaluation](data_structures/stacks/prefix_evaluation.py) * [Stack](data_structures/stacks/stack.py) + * [Stack Using Two Queues](data_structures/stacks/stack_using_two_queues.py) * [Stack With Doubly Linked List](data_structures/stacks/stack_with_doubly_linked_list.py) * [Stack With Singly Linked List](data_structures/stacks/stack_with_singly_linked_list.py) * [Stock Span Problem](data_structures/stacks/stock_span_problem.py) @@ -361,6 +362,7 @@ * [Trapped Water](dynamic_programming/trapped_water.py) * [Tribonacci](dynamic_programming/tribonacci.py) * [Viterbi](dynamic_programming/viterbi.py) + * [Wildcard Matching](dynamic_programming/wildcard_matching.py) * [Word Break](dynamic_programming/word_break.py) ## Electronics @@ -791,7 +793,7 @@ ## Physics * [Altitude Pressure](physics/altitude_pressure.py) - * [Archimedes Principle](physics/archimedes_principle.py) + * [Archimedes Principle Of Buoyant Force](physics/archimedes_principle_of_buoyant_force.py) * [Basic Orbital Capture](physics/basic_orbital_capture.py) * [Casimir Effect](physics/casimir_effect.py) * [Centripetal Force](physics/centripetal_force.py) diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index f27ec9ca60aa..ca4af5261a8f 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -55,7 +55,7 @@ def added_solution_file_path() -> list[pathlib.Path]: solution_file_paths = [] headers = { "Accept": "application/vnd.github.v3+json", - "Authorization": f"token {os.getenv('GITHUB_TOKEN', '')}", + "Authorization": "token " + os.environ["GITHUB_TOKEN"], } files = requests.get(get_files_url(), headers=headers).json() for file in files: From 902278f656b38ed68e148cf8c9ac2cbd10fcfb7e Mon Sep 17 00:00:00 2001 From: Aasheesh <126905285+AasheeshLikePanner@users.noreply.github.com> Date: Mon, 16 Oct 2023 01:26:02 +0530 Subject: [PATCH 2481/2908] Changes the code To return the list in dynamic_programming/subset_generation.py (#10191) * Changing the code to return tuple * Changing the code to return tuple * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/subset_generation.py Co-authored-by: Christian Clauss * Adding doctests in subset_generation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update subset_generation.py * Update subset_generation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update subset_generation.py * Update subset_generation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/subset_generation.py Co-authored-by: Christian Clauss * Update stock_span_problem.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update subset_generation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update subset_generation.py * Update subset_generation.py * Update subset_generation.py * Update subset_generation.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- dynamic_programming/subset_generation.py | 90 ++++++++++++++---------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py index 819fd8106def..1be412b9374d 100644 --- a/dynamic_programming/subset_generation.py +++ b/dynamic_programming/subset_generation.py @@ -1,44 +1,60 @@ -# Print all subset combinations of n element in given set of r element. - - -def combination_util(arr, n, r, index, data, i): +def subset_combinations(elements: list[int], n: int) -> list: """ - 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 + Compute n-element combinations from a given list using dynamic programming. + Args: + elements: The list of elements from which combinations will be generated. + n: The number of elements in each combination. + Returns: + A list of tuples, each representing a combination of n elements. + >>> subset_combinations(elements=[10, 20, 30, 40], n=2) + [(10, 20), (10, 30), (10, 40), (20, 30), (20, 40), (30, 40)] + >>> subset_combinations(elements=[1, 2, 3], n=1) + [(1,), (2,), (3,)] + >>> subset_combinations(elements=[1, 2, 3], n=3) + [(1, 2, 3)] + >>> subset_combinations(elements=[42], n=1) + [(42,)] + >>> subset_combinations(elements=[6, 7, 8, 9], n=4) + [(6, 7, 8, 9)] + >>> subset_combinations(elements=[10, 20, 30, 40, 50], n=0) + [()] + >>> subset_combinations(elements=[1, 2, 3, 4], n=2) + [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)] + >>> subset_combinations(elements=[1, 'apple', 3.14], n=2) + [(1, 'apple'), (1, 3.14), ('apple', 3.14)] + >>> subset_combinations(elements=['single'], n=0) + [()] + >>> subset_combinations(elements=[], n=9) + [] + >>> from itertools import combinations + >>> all(subset_combinations(items, n) == list(combinations(items, n)) + ... for items, n in ( + ... ([10, 20, 30, 40], 2), ([1, 2, 3], 1), ([1, 2, 3], 3), ([42], 1), + ... ([6, 7, 8, 9], 4), ([10, 20, 30, 40, 50], 1), ([1, 2, 3, 4], 2), + ... ([1, 'apple', 3.14], 2), (['single'], 0), ([], 9))) + True """ - 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] - 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) - 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() + r = len(elements) + if n > r: + return [] + + dp: list[list[tuple]] = [[] for _ in range(r + 1)] + dp[0].append(()) -def print_combination(arr, n, r): - # A temporary array to store all combination one by one - data = [0] * r - # Print all combination using temporary array 'data[]' - combination_util(arr, n, r, 0, data, 0) + for i in range(1, r + 1): + for j in range(i, 0, -1): + for prev_combination in dp[j - 1]: + dp[j].append(tuple(prev_combination) + (elements[i - 1],)) + + try: + return sorted(dp[n]) + except TypeError: + return dp[n] if __name__ == "__main__": - # Driver code to check the function above - arr = [10, 20, 30, 40, 50] - print_combination(arr, len(arr), 3) - # This code is contributed by Ambuj sahu + from doctest import testmod + + testmod() + print(f"{subset_combinations(elements=[10, 20, 30, 40], n=2) = }") From 3d6f3c41881da75653b804d7a5964ea90df9d2ad Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Mon, 16 Oct 2023 04:13:27 +0800 Subject: [PATCH 2482/2908] Added data_structures/arrays/sparse_table.py (#10437) * Create sparse_table.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Descriptive names for variables * Fix ruff check error * Update sparse_table.py * Add comments, change variable names * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix typo * Update sparse_table.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/arrays/sparse_table.py | 94 ++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 data_structures/arrays/sparse_table.py diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py new file mode 100644 index 000000000000..a15d5649e712 --- /dev/null +++ b/data_structures/arrays/sparse_table.py @@ -0,0 +1,94 @@ +""" + Sparse table is a data structure that allows answering range queries on + a static number list, i.e. the elements do not change throughout all the queries. + + The implementation below will solve the problem of Range Minimum Query: + Finding the minimum value of a subset [L..R] of a static number list. + + Overall time complexity: O(nlogn) + Overall space complexity: O(nlogn) + + Wikipedia link: https://en.wikipedia.org/wiki/Range_minimum_query +""" +from math import log2 + + +def build_sparse_table(number_list: list[int]) -> list[list[int]]: + """ + Precompute range minimum queries with power of two length and store the precomputed + values in a table. + + >>> build_sparse_table([8, 1, 0, 3, 4, 9, 3]) + [[8, 1, 0, 3, 4, 9, 3], [1, 0, 0, 3, 4, 3, 0], [0, 0, 0, 3, 0, 0, 0]] + >>> build_sparse_table([3, 1, 9]) + [[3, 1, 9], [1, 1, 0]] + >>> build_sparse_table([]) + Traceback (most recent call last): + ... + ValueError: empty number list not allowed + """ + if not number_list: + raise ValueError("empty number list not allowed") + + length = len(number_list) + # Initialise sparse_table -- sparse_table[j][i] represents the minimum value of the + # subset of length (2 ** j) of number_list, starting from index i. + + # smallest power of 2 subset length that fully covers number_list + row = int(log2(length)) + 1 + sparse_table = [[0 for i in range(length)] for j in range(row)] + + # minimum of subset of length 1 is that value itself + for i, value in enumerate(number_list): + sparse_table[0][i] = value + j = 1 + + # compute the minimum value for all intervals with size (2 ** j) + while (1 << j) <= length: + i = 0 + # while subset starting from i still have at least (2 ** j) elements + while (i + (1 << j) - 1) < length: + # split range [i, i + 2 ** j] and find minimum of 2 halves + sparse_table[j][i] = min( + sparse_table[j - 1][i + (1 << (j - 1))], sparse_table[j - 1][i] + ) + i += 1 + j += 1 + return sparse_table + + +def query(sparse_table: list[list[int]], left_bound: int, right_bound: int) -> int: + """ + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 4) + 0 + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 4, 6) + 3 + >>> query(build_sparse_table([3, 1, 9]), 2, 2) + 9 + >>> query(build_sparse_table([3, 1, 9]), 0, 1) + 1 + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 11) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> query(build_sparse_table([]), 0, 0) + Traceback (most recent call last): + ... + ValueError: empty number list not allowed + """ + if left_bound < 0 or right_bound >= len(sparse_table[0]): + raise IndexError("list index out of range") + + # highest subset length of power of 2 that is within range [left_bound, right_bound] + j = int(log2(right_bound - left_bound + 1)) + + # minimum of 2 overlapping smaller subsets: + # [left_bound, left_bound + 2 ** j - 1] and [right_bound - 2 ** j + 1, right_bound] + return min(sparse_table[j][right_bound - (1 << j) + 1], sparse_table[j][left_bound]) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + print(f"{query(build_sparse_table([3, 1, 9]), 2, 2) = }") From ec952927baea776bcb0f35d282448d32f3721047 Mon Sep 17 00:00:00 2001 From: dhruvtrigotra <72982592+dhruvtrigotra@users.noreply.github.com> Date: Mon, 16 Oct 2023 02:11:39 +0530 Subject: [PATCH 2483/2908] charging_inductor (#10427) * charging_capacitor * charging_capacitor * Final edits * charging_inductor --------- Co-authored-by: Christian Clauss --- electronics/charging_inductor.py | 96 ++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 electronics/charging_inductor.py diff --git a/electronics/charging_inductor.py b/electronics/charging_inductor.py new file mode 100644 index 000000000000..e5c0126c248a --- /dev/null +++ b/electronics/charging_inductor.py @@ -0,0 +1,96 @@ +# source - The ARRL Handbook for Radio Communications +# https://en.wikipedia.org/wiki/RL_circuit + +""" +Description +----------- +Inductor is a passive electronic device which stores energy but unlike capacitor, it +stores energy in its 'magnetic field' or 'magnetostatic field'. + +When inductor is connected to 'DC' current source nothing happens it just works like a +wire because it's real effect cannot be seen while 'DC' is connected, its not even +going to store energy. Inductor stores energy only when it is working on 'AC' current. + +Connecting a inductor in series with a resistor(when R = 0) to a 'AC' potential source, +from zero to a finite value causes a sudden voltage to induced in inductor which +opposes the current. which results in initially slowly current rise. However it would +cease if there is no further changes in current. With resistance zero current will never +stop rising. + +'Resistance(ohms) / Inductance(henrys)' is known as RL-timeconstant. It also represents +as τ (tau). While the charging of a inductor with a resistor results in +a exponential function. + +when inductor is connected across 'AC' potential source. It starts to store the energy +in its 'magnetic field'.with the help 'RL-time-constant' we can find current at any time +in inductor while it is charging. +""" +from math import exp # value of exp = 2.718281828459… + + +def charging_inductor( + source_voltage: float, # source_voltage should be in volts. + resistance: float, # resistance should be in ohms. + inductance: float, # inductance should be in henrys. + time: float, # time should in seconds. +) -> float: + """ + Find inductor current at any nth second after initiating its charging. + + Examples + -------- + >>> charging_inductor(source_voltage=5.8,resistance=1.5,inductance=2.3,time=2) + 2.817 + + >>> charging_inductor(source_voltage=8,resistance=5,inductance=3,time=2) + 1.543 + + >>> charging_inductor(source_voltage=8,resistance=5*pow(10,2),inductance=3,time=2) + 0.016 + + >>> charging_inductor(source_voltage=-8,resistance=100,inductance=15,time=12) + Traceback (most recent call last): + ... + ValueError: Source voltage must be positive. + + >>> charging_inductor(source_voltage=80,resistance=-15,inductance=100,time=5) + Traceback (most recent call last): + ... + ValueError: Resistance must be positive. + + >>> charging_inductor(source_voltage=12,resistance=200,inductance=-20,time=5) + Traceback (most recent call last): + ... + ValueError: Inductance must be positive. + + >>> charging_inductor(source_voltage=0,resistance=200,inductance=20,time=5) + Traceback (most recent call last): + ... + ValueError: Source voltage must be positive. + + >>> charging_inductor(source_voltage=10,resistance=0,inductance=20,time=5) + Traceback (most recent call last): + ... + ValueError: Resistance must be positive. + + >>> charging_inductor(source_voltage=15, resistance=25, inductance=0, time=5) + Traceback (most recent call last): + ... + ValueError: Inductance must be positive. + """ + + if source_voltage <= 0: + raise ValueError("Source voltage must be positive.") + if resistance <= 0: + raise ValueError("Resistance must be positive.") + if inductance <= 0: + raise ValueError("Inductance must be positive.") + return round( + source_voltage / resistance * (1 - exp((-time * resistance) / inductance)), 3 + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From bcda3bf64ea20db11cb4b1b81536e2f05ee584fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Azevedo?= Date: Sun, 15 Oct 2023 18:31:11 -0300 Subject: [PATCH 2484/2908] test: adding more tests to a star algorithm (#10397) * test: adding more tests to a star algorithm * Apply suggestions from code review * Update a_star.py --------- Co-authored-by: Tianyi Zheng --- graphs/a_star.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/graphs/a_star.py b/graphs/a_star.py index e8735179eab9..06da3b5cd863 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -16,6 +16,31 @@ def search( cost: int, heuristic: list[list[int]], ) -> tuple[list[list[int]], list[list[int]]]: + """ + Search for a path on a grid avoiding obstacles. + >>> grid = [[0, 1, 0, 0, 0, 0], + ... [0, 1, 0, 0, 0, 0], + ... [0, 1, 0, 0, 0, 0], + ... [0, 1, 0, 0, 1, 0], + ... [0, 0, 0, 0, 1, 0]] + >>> init = [0, 0] + >>> goal = [len(grid) - 1, len(grid[0]) - 1] + >>> cost = 1 + >>> heuristic = [[0] * len(grid[0]) for _ in range(len(grid))] + >>> 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 + >>> path, action = search(grid, init, goal, cost, heuristic) + >>> path # doctest: +NORMALIZE_WHITESPACE + [[0, 0], [1, 0], [2, 0], [3, 0], [4, 0], [4, 1], [4, 2], [4, 3], [3, 3], + [2, 3], [2, 4], [2, 5], [3, 5], [4, 5]] + >>> action # doctest: +NORMALIZE_WHITESPACE + [[0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0], [2, 0, 0, 0, 3, 3], + [2, 0, 0, 0, 0, 2], [2, 3, 3, 3, 0, 2]] + """ closed = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) ] # the reference grid From d00888de7629b093bcf750ae046318be1e9a1fa3 Mon Sep 17 00:00:00 2001 From: Jeel Gajera <83470656+JeelGajera@users.noreply.github.com> Date: Mon, 16 Oct 2023 03:19:53 +0530 Subject: [PATCH 2485/2908] feat: adding Apriori Algorithm (#10491) * feat: adding Apriori Algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: doctest, typo * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: type error, code refactore * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: refactore code * fix: doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: E501, B007 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: err * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: arg typ err * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: typo * fix: typo * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Replace generate_candidates() with itertools.combinations() * mypy * Update apriori_algorithm.py --------- Co-authored-by: Jeel Gajera Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + machine_learning/apriori_algorithm.py | 112 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 machine_learning/apriori_algorithm.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 5c63e6316547..55781df03b91 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -554,6 +554,7 @@ * [Word Frequency Functions](machine_learning/word_frequency_functions.py) * [Xgboost Classifier](machine_learning/xgboost_classifier.py) * [Xgboost Regressor](machine_learning/xgboost_regressor.py) + * [Apriori Algorithm](machine_learning/apriori_algorithm.py) ## Maths * [Abs](maths/abs.py) diff --git a/machine_learning/apriori_algorithm.py b/machine_learning/apriori_algorithm.py new file mode 100644 index 000000000000..d9fd1f82ea3c --- /dev/null +++ b/machine_learning/apriori_algorithm.py @@ -0,0 +1,112 @@ +""" +Apriori Algorithm is a Association rule mining technique, also known as market basket +analysis, aims to discover interesting relationships or associations among a set of +items in a transactional or relational database. + +For example, Apriori Algorithm states: "If a customer buys item A and item B, then they +are likely to buy item C." This rule suggests a relationship between items A, B, and C, +indicating that customers who purchased A and B are more likely to also purchase item C. + +WIKI: https://en.wikipedia.org/wiki/Apriori_algorithm +Examples: https://www.kaggle.com/code/earthian/apriori-association-rules-mining +""" +from itertools import combinations + + +def load_data() -> list[list[str]]: + """ + Returns a sample transaction dataset. + + >>> load_data() + [['milk'], ['milk', 'butter'], ['milk', 'bread'], ['milk', 'bread', 'chips']] + """ + return [["milk"], ["milk", "butter"], ["milk", "bread"], ["milk", "bread", "chips"]] + + +def prune(itemset: list, candidates: list, length: int) -> list: + """ + Prune candidate itemsets that are not frequent. + The goal of pruning is to filter out candidate itemsets that are not frequent. This + is done by checking if all the (k-1) subsets of a candidate itemset are present in + the frequent itemsets of the previous iteration (valid subsequences of the frequent + itemsets from the previous iteration). + + Prunes candidate itemsets that are not frequent. + + >>> itemset = ['X', 'Y', 'Z'] + >>> candidates = [['X', 'Y'], ['X', 'Z'], ['Y', 'Z']] + >>> prune(itemset, candidates, 2) + [['X', 'Y'], ['X', 'Z'], ['Y', 'Z']] + + >>> itemset = ['1', '2', '3', '4'] + >>> candidates = ['1', '2', '4'] + >>> prune(itemset, candidates, 3) + [] + """ + pruned = [] + for candidate in candidates: + is_subsequence = True + for item in candidate: + if item not in itemset or itemset.count(item) < length - 1: + is_subsequence = False + break + if is_subsequence: + pruned.append(candidate) + return pruned + + +def apriori(data: list[list[str]], min_support: int) -> list[tuple[list[str], int]]: + """ + Returns a list of frequent itemsets and their support counts. + + >>> data = [['A', 'B', 'C'], ['A', 'B'], ['A', 'C'], ['A', 'D'], ['B', 'C']] + >>> apriori(data, 2) + [(['A', 'B'], 1), (['A', 'C'], 2), (['B', 'C'], 2)] + + >>> data = [['1', '2', '3'], ['1', '2'], ['1', '3'], ['1', '4'], ['2', '3']] + >>> apriori(data, 3) + [] + """ + itemset = [list(transaction) for transaction in data] + frequent_itemsets = [] + length = 1 + + while itemset: + # Count itemset support + counts = [0] * len(itemset) + for transaction in data: + for j, candidate in enumerate(itemset): + if all(item in transaction for item in candidate): + counts[j] += 1 + + # Prune infrequent itemsets + itemset = [item for i, item in enumerate(itemset) if counts[i] >= min_support] + + # Append frequent itemsets (as a list to maintain order) + for i, item in enumerate(itemset): + frequent_itemsets.append((sorted(item), counts[i])) + + length += 1 + itemset = prune(itemset, list(combinations(itemset, length)), length) + + return frequent_itemsets + + +if __name__ == "__main__": + """ + Apriori algorithm for finding frequent itemsets. + + Args: + data: A list of transactions, where each transaction is a list of items. + min_support: The minimum support threshold for frequent itemsets. + + Returns: + A list of frequent itemsets along with their support counts. + """ + import doctest + + doctest.testmod() + + # user-defined threshold or minimum support level + frequent_itemsets = apriori(data=load_data(), min_support=2) + print("\n".join(f"{itemset}: {support}" for itemset, support in frequent_itemsets)) From e6aae1cf66b7e962b886255703b5802d58f27fd3 Mon Sep 17 00:00:00 2001 From: Pooja Sharma <75516191+Shailaputri@users.noreply.github.com> Date: Mon, 16 Oct 2023 05:02:45 +0530 Subject: [PATCH 2486/2908] Dynamic programming/matrix chain multiplication (#10562) * updating DIRECTORY.md * spell changes * updating DIRECTORY.md * real world applications * updating DIRECTORY.md * Update matrix_chain_multiplication.py Add a non-dp solution with benchmarks. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update matrix_chain_multiplication.py * Update matrix_chain_multiplication.py * Update matrix_chain_multiplication.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Pooja Sharma Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 5 +- .../matrix_chain_multiplication.py | 143 ++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 dynamic_programming/matrix_chain_multiplication.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 55781df03b91..cef1e06b78aa 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -182,6 +182,7 @@ * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) * [Product Sum](data_structures/arrays/product_sum.py) + * [Sparse Table](data_structures/arrays/sparse_table.py) * Binary Tree * [Avl Tree](data_structures/binary_tree/avl_tree.py) * [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py) @@ -340,6 +341,7 @@ * [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py) * [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py) * [Longest Sub Array](dynamic_programming/longest_sub_array.py) + * [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py) * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) * [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py) * [Max Product Subarray](dynamic_programming/max_product_subarray.py) @@ -370,6 +372,7 @@ * [Builtin Voltage](electronics/builtin_voltage.py) * [Carrier Concentration](electronics/carrier_concentration.py) * [Charging Capacitor](electronics/charging_capacitor.py) + * [Charging Inductor](electronics/charging_inductor.py) * [Circular Convolution](electronics/circular_convolution.py) * [Coulombs Law](electronics/coulombs_law.py) * [Electric Conductivity](electronics/electric_conductivity.py) @@ -524,6 +527,7 @@ * [Simplex](linear_programming/simplex.py) ## Machine Learning + * [Apriori Algorithm](machine_learning/apriori_algorithm.py) * [Astar](machine_learning/astar.py) * [Data Transformations](machine_learning/data_transformations.py) * [Decision Tree](machine_learning/decision_tree.py) @@ -554,7 +558,6 @@ * [Word Frequency Functions](machine_learning/word_frequency_functions.py) * [Xgboost Classifier](machine_learning/xgboost_classifier.py) * [Xgboost Regressor](machine_learning/xgboost_regressor.py) - * [Apriori Algorithm](machine_learning/apriori_algorithm.py) ## Maths * [Abs](maths/abs.py) diff --git a/dynamic_programming/matrix_chain_multiplication.py b/dynamic_programming/matrix_chain_multiplication.py new file mode 100644 index 000000000000..084254a61f6c --- /dev/null +++ b/dynamic_programming/matrix_chain_multiplication.py @@ -0,0 +1,143 @@ +""" +Find the minimum number of multiplications needed to multiply chain of matrices. +Reference: https://www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/ + +The algorithm has interesting real-world applications. Example: +1. Image transformations in Computer Graphics as images are composed of matrix. +2. Solve complex polynomial equations in the field of algebra using least processing + power. +3. Calculate overall impact of macroeconomic decisions as economic equations involve a + number of variables. +4. Self-driving car navigation can be made more accurate as matrix multiplication can + accurately determine position and orientation of obstacles in short time. + +Python doctests can be run with the following command: +python -m doctest -v matrix_chain_multiply.py + +Given a sequence arr[] that represents chain of 2D matrices such that the dimension of +the ith matrix is arr[i-1]*arr[i]. +So suppose arr = [40, 20, 30, 10, 30] means we have 4 matrices of dimensions +40*20, 20*30, 30*10 and 10*30. + +matrix_chain_multiply() returns an integer denoting minimum number of multiplications to +multiply the chain. + +We do not need to perform actual multiplication here. +We only need to decide the order in which to perform the multiplication. + +Hints: +1. Number of multiplications (ie cost) to multiply 2 matrices +of size m*p and p*n is m*p*n. +2. Cost of matrix multiplication is associative ie (M1*M2)*M3 != M1*(M2*M3) +3. Matrix multiplication is not commutative. So, M1*M2 does not mean M2*M1 can be done. +4. To determine the required order, we can try different combinations. +So, this problem has overlapping sub-problems and can be solved using recursion. +We use Dynamic Programming for optimal time complexity. + +Example input: +arr = [40, 20, 30, 10, 30] +output: 26000 +""" +from collections.abc import Iterator +from contextlib import contextmanager +from functools import cache +from sys import maxsize + + +def matrix_chain_multiply(arr: list[int]) -> int: + """ + Find the minimum number of multiplcations required to multiply the chain of matrices + + Args: + arr: The input array of integers. + + Returns: + Minimum number of multiplications needed to multiply the chain + + Examples: + >>> matrix_chain_multiply([1, 2, 3, 4, 3]) + 30 + >>> matrix_chain_multiply([10]) + 0 + >>> matrix_chain_multiply([10, 20]) + 0 + >>> matrix_chain_multiply([19, 2, 19]) + 722 + >>> matrix_chain_multiply(list(range(1, 100))) + 323398 + + # >>> matrix_chain_multiply(list(range(1, 251))) + # 2626798 + """ + if len(arr) < 2: + return 0 + # initialising 2D dp matrix + n = len(arr) + dp = [[maxsize for j in range(n)] for i in range(n)] + # we want minimum cost of multiplication of matrices + # of dimension (i*k) and (k*j). This cost is arr[i-1]*arr[k]*arr[j]. + for i in range(n - 1, 0, -1): + for j in range(i, n): + if i == j: + dp[i][j] = 0 + continue + for k in range(i, j): + dp[i][j] = min( + dp[i][j], dp[i][k] + dp[k + 1][j] + arr[i - 1] * arr[k] * arr[j] + ) + + return dp[1][n - 1] + + +def matrix_chain_order(dims: list[int]) -> int: + """ + Source: https://en.wikipedia.org/wiki/Matrix_chain_multiplication + The dynamic programming solution is faster than cached the recursive solution and + can handle larger inputs. + >>> matrix_chain_order([1, 2, 3, 4, 3]) + 30 + >>> matrix_chain_order([10]) + 0 + >>> matrix_chain_order([10, 20]) + 0 + >>> matrix_chain_order([19, 2, 19]) + 722 + >>> matrix_chain_order(list(range(1, 100))) + 323398 + + # >>> matrix_chain_order(list(range(1, 251))) # Max before RecursionError is raised + # 2626798 + """ + + @cache + def a(i: int, j: int) -> int: + return min( + (a(i, k) + dims[i] * dims[k] * dims[j] + a(k, j) for k in range(i + 1, j)), + default=0, + ) + + return a(0, len(dims) - 1) + + +@contextmanager +def elapsed_time(msg: str) -> Iterator: + # print(f"Starting: {msg}") + from time import perf_counter_ns + + start = perf_counter_ns() + yield + print(f"Finished: {msg} in {(perf_counter_ns() - start) / 10 ** 9} seconds.") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + with elapsed_time("matrix_chain_order"): + print(f"{matrix_chain_order(list(range(1, 251))) = }") + with elapsed_time("matrix_chain_multiply"): + print(f"{matrix_chain_multiply(list(range(1, 251))) = }") + with elapsed_time("matrix_chain_order"): + print(f"{matrix_chain_order(list(range(1, 251))) = }") + with elapsed_time("matrix_chain_multiply"): + print(f"{matrix_chain_multiply(list(range(1, 251))) = }") From b6b45eb1cee564e3c563966244f124051c28b8e7 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 15 Oct 2023 19:41:45 -0400 Subject: [PATCH 2487/2908] Fix numpy deprecation warning in `2_hidden_layers_neural_network.py` (#10424) * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Fix deprecation warning in 2_hidden_layers_neural_network.py Fix numpy deprecation warning: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.) --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- neural_network/2_hidden_layers_neural_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neural_network/2_hidden_layers_neural_network.py b/neural_network/2_hidden_layers_neural_network.py index 9c5772326165..7b374a93d039 100644 --- a/neural_network/2_hidden_layers_neural_network.py +++ b/neural_network/2_hidden_layers_neural_network.py @@ -196,7 +196,7 @@ def predict(self, input_arr: numpy.ndarray) -> int: >>> 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]) in (0, 1) + >>> nn.predict([0, 1, 0]) in (0, 1) True """ @@ -221,7 +221,7 @@ def predict(self, input_arr: numpy.ndarray) -> int: ) ) - return int(self.layer_between_second_hidden_layer_and_output > 0.6) + return int((self.layer_between_second_hidden_layer_and_output > 0.6)[0]) def sigmoid(value: numpy.ndarray) -> numpy.ndarray: From 73ebf7bdb12f4bced39f25766ac4d2cd9b6ab525 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 15 Oct 2023 19:42:55 -0400 Subject: [PATCH 2488/2908] Move and rename `maths/greedy_coin_change.py` (#10418) * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * updating DIRECTORY.md * Move greedy_coin_change.py to greedy_methods/ and rename file --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../minimum_coin_change.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename maths/greedy_coin_change.py => greedy_methods/minimum_coin_change.py (100%) diff --git a/maths/greedy_coin_change.py b/greedy_methods/minimum_coin_change.py similarity index 100% rename from maths/greedy_coin_change.py rename to greedy_methods/minimum_coin_change.py From c2f14e8a78c1700a4101746a1a6e3d70be50aa07 Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:44:06 +1300 Subject: [PATCH 2489/2908] Add note to feature_request.yml about not opening issues for new algorithms (#10142) --- .github/ISSUE_TEMPLATE/feature_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 09a159b2193e..20823bd58ab1 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -6,6 +6,7 @@ body: attributes: value: > Before requesting please search [existing issues](https://github.com/TheAlgorithms/Python/labels/enhancement). + Do not create issues to implement new algorithms as these will be closed. Usage questions such as "How do I...?" belong on the [Discord](https://discord.gg/c7MnfGFGa6) and will be closed. @@ -13,7 +14,6 @@ body: attributes: label: "Feature description" description: > - This could be new algorithms, data structures or improving any existing - implementations. + This could include new topics or improving any existing implementations. validations: required: true From bb8f194957c4308cbb0bf16a4e07acbe34d2087e Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sun, 15 Oct 2023 20:01:01 -0400 Subject: [PATCH 2490/2908] Delete `texttable` from dependencies (#10565) * Disable unused dependencies Comment out dependencies in requirements.txt that are only used by currently-disabled files * Delete unused dependency --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1e64818bbb6a..05d9f1e8c545 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,6 @@ scikit-learn statsmodels sympy tensorflow ; python_version < '3.12' -texttable tweepy xgboost # yulewalker # uncomment once audio_filters/equal_loudness_filter.py is fixed From 1a26d76c60422030cf0c57c62623866d3f3229f2 Mon Sep 17 00:00:00 2001 From: "Gabrielly de S. Pinto Dantas" Date: Sun, 15 Oct 2023 21:44:10 -0300 Subject: [PATCH 2491/2908] add tests for tree_sort (#10015) * add tests for tree_sort * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update tree_sort.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- sorts/tree_sort.py | 99 +++++++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index 78c3e893e0ce..e63a3253ba19 100644 --- a/sorts/tree_sort.py +++ b/sorts/tree_sort.py @@ -1,53 +1,70 @@ """ Tree_sort algorithm. -Build a BST and in order traverse. +Build a Binary Search Tree and then iterate thru it to get a sorted list. """ +from __future__ import annotations +from collections.abc import Iterator +from dataclasses import dataclass + +@dataclass class Node: - # BST data structure - def __init__(self, val): - self.val = val - self.left = None - self.right = None - - def insert(self, val): - if self.val: - if val < self.val: - if self.left is None: - self.left = Node(val) - else: - self.left.insert(val) - elif val > self.val: - if self.right is None: - self.right = Node(val) - else: - self.right.insert(val) - else: - self.val = val - - -def inorder(root, res): - # Recursive traversal - if root: - inorder(root.left, res) - res.append(root.val) - inorder(root.right, res) - - -def tree_sort(arr): - # Build BST + val: int + left: Node | None = None + right: Node | None = None + + def __iter__(self) -> Iterator[int]: + if self.left: + yield from self.left + yield self.val + if self.right: + yield from self.right + + def __len__(self) -> int: + return sum(1 for _ in self) + + def insert(self, val: int) -> None: + if val < self.val: + if self.left is None: + self.left = Node(val) + else: + self.left.insert(val) + elif val > self.val: + if self.right is None: + self.right = Node(val) + else: + self.right.insert(val) + + +def tree_sort(arr: list[int]) -> tuple[int, ...]: + """ + >>> tree_sort([]) + () + >>> tree_sort((1,)) + (1,) + >>> tree_sort((1, 2)) + (1, 2) + >>> tree_sort([5, 2, 7]) + (2, 5, 7) + >>> tree_sort((5, -4, 9, 2, 7)) + (-4, 2, 5, 7, 9) + >>> tree_sort([5, 6, 1, -1, 4, 37, 2, 7]) + (-1, 1, 2, 4, 5, 6, 7, 37) + >>> tree_sort(range(10, -10, -1)) == tuple(sorted(range(10, -10, -1))) + True + """ if len(arr) == 0: - return arr + return tuple(arr) root = Node(arr[0]) - for i in range(1, len(arr)): - root.insert(arr[i]) - # Traverse BST in order. - res = [] - inorder(root, res) - return res + for item in arr[1:]: + root.insert(item) + return tuple(root) if __name__ == "__main__": - print(tree_sort([10, 1, 3, 2, 9, 14, 13])) + import doctest + + doctest.testmod() + print(f"{tree_sort([5, 6, 1, -1, 4, 37, -3, 7]) = }") From cc0405d05cb4c5009e8bf826e3f641c427ba70d5 Mon Sep 17 00:00:00 2001 From: Yousha Mahamuni <40205524+yousha806@users.noreply.github.com> Date: Mon, 16 Oct 2023 08:17:27 +0530 Subject: [PATCH 2492/2908] Update volume.py with volume of Icosahedron (#9628) * Update volume.py with volume of Icosahedron Added function to find volume of a regular Icosahedron * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * Update volume.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/volume.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/maths/volume.py b/maths/volume.py index 721974e68b66..b4df4e475783 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -469,6 +469,35 @@ def vol_torus(torus_radius: float, tube_radius: float) -> float: return 2 * pow(pi, 2) * torus_radius * pow(tube_radius, 2) +def vol_icosahedron(tri_side: float) -> float: + """Calculate the Volume of an Icosahedron. + Wikipedia reference: https://en.wikipedia.org/wiki/Regular_icosahedron + + >>> from math import isclose + >>> isclose(vol_icosahedron(2.5), 34.088984228514256) + True + >>> isclose(vol_icosahedron(10), 2181.694990624912374) + True + >>> isclose(vol_icosahedron(5), 272.711873828114047) + True + >>> isclose(vol_icosahedron(3.49), 92.740688412033628) + True + >>> vol_icosahedron(0) + 0.0 + >>> vol_icosahedron(-1) + Traceback (most recent call last): + ... + ValueError: vol_icosahedron() only accepts non-negative values + >>> vol_icosahedron(-0.2) + Traceback (most recent call last): + ... + ValueError: vol_icosahedron() only accepts non-negative values + """ + if tri_side < 0: + raise ValueError("vol_icosahedron() only accepts non-negative values") + return tri_side**3 * (3 + 5**0.5) * 5 / 12 + + def main(): """Print the Results of Various Volume Calculations.""" print("Volumes:") @@ -489,6 +518,7 @@ def main(): print( f"Hollow Circular Cylinder: {vol_hollow_circular_cylinder(1, 2, 3) = }" ) # ~= 28.3 + print(f"Icosahedron: {vol_icosahedron(2.5) = }") # ~=34.09 if __name__ == "__main__": From f4ff73b1bdaa4349315beaf44e093c59f6c87fd3 Mon Sep 17 00:00:00 2001 From: Akshar Goyal Date: Mon, 16 Oct 2023 03:21:43 -0400 Subject: [PATCH 2493/2908] Converted tests into doctests (#10572) * Converted tests into doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed commented code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- boolean_algebra/and_gate.py | 18 +++--------------- boolean_algebra/imply_gate.py | 7 +++---- boolean_algebra/nand_gate.py | 17 +++-------------- boolean_algebra/nimply_gate.py | 7 +++---- boolean_algebra/not_gate.py | 13 +++---------- boolean_algebra/or_gate.py | 17 +++-------------- boolean_algebra/xnor_gate.py | 17 +++-------------- boolean_algebra/xor_gate.py | 15 +++------------ 8 files changed, 24 insertions(+), 87 deletions(-) diff --git a/boolean_algebra/and_gate.py b/boolean_algebra/and_gate.py index 834116772ee7..f0fd45c9f81e 100644 --- a/boolean_algebra/and_gate.py +++ b/boolean_algebra/and_gate.py @@ -32,19 +32,7 @@ def and_gate(input_1: int, input_2: int) -> int: return int((input_1, input_2).count(0) == 0) -def test_and_gate() -> None: - """ - Tests the and_gate function - """ - assert and_gate(0, 0) == 0 - assert and_gate(0, 1) == 0 - assert and_gate(1, 0) == 0 - assert and_gate(1, 1) == 1 - - if __name__ == "__main__": - test_and_gate() - print(and_gate(1, 0)) - print(and_gate(0, 0)) - print(and_gate(0, 1)) - print(and_gate(1, 1)) + import doctest + + doctest.testmod() diff --git a/boolean_algebra/imply_gate.py b/boolean_algebra/imply_gate.py index 151a7ad6439a..b64ebaceb306 100644 --- a/boolean_algebra/imply_gate.py +++ b/boolean_algebra/imply_gate.py @@ -34,7 +34,6 @@ def imply_gate(input_1: int, input_2: int) -> int: if __name__ == "__main__": - print(imply_gate(0, 0)) - print(imply_gate(0, 1)) - print(imply_gate(1, 0)) - print(imply_gate(1, 1)) + import doctest + + doctest.testmod() diff --git a/boolean_algebra/nand_gate.py b/boolean_algebra/nand_gate.py index ea3303d16b25..80f9d12db89a 100644 --- a/boolean_algebra/nand_gate.py +++ b/boolean_algebra/nand_gate.py @@ -30,18 +30,7 @@ def nand_gate(input_1: int, input_2: int) -> int: return int((input_1, input_2).count(0) != 0) -def test_nand_gate() -> None: - """ - Tests the nand_gate function - """ - assert nand_gate(0, 0) == 1 - assert nand_gate(0, 1) == 1 - assert nand_gate(1, 0) == 1 - assert nand_gate(1, 1) == 0 - - if __name__ == "__main__": - print(nand_gate(0, 0)) - print(nand_gate(0, 1)) - print(nand_gate(1, 0)) - print(nand_gate(1, 1)) + import doctest + + doctest.testmod() diff --git a/boolean_algebra/nimply_gate.py b/boolean_algebra/nimply_gate.py index 6e34332d9112..68e82c8db8d9 100644 --- a/boolean_algebra/nimply_gate.py +++ b/boolean_algebra/nimply_gate.py @@ -34,7 +34,6 @@ def nimply_gate(input_1: int, input_2: int) -> int: if __name__ == "__main__": - print(nimply_gate(0, 0)) - print(nimply_gate(0, 1)) - print(nimply_gate(1, 0)) - print(nimply_gate(1, 1)) + import doctest + + doctest.testmod() diff --git a/boolean_algebra/not_gate.py b/boolean_algebra/not_gate.py index eb85e9e44cd3..cfa74cf42204 100644 --- a/boolean_algebra/not_gate.py +++ b/boolean_algebra/not_gate.py @@ -24,14 +24,7 @@ def not_gate(input_1: int) -> int: return 1 if input_1 == 0 else 0 -def test_not_gate() -> None: - """ - Tests the not_gate function - """ - assert not_gate(0) == 1 - assert not_gate(1) == 0 - - if __name__ == "__main__": - print(not_gate(0)) - print(not_gate(1)) + import doctest + + doctest.testmod() diff --git a/boolean_algebra/or_gate.py b/boolean_algebra/or_gate.py index aa7e6645e33f..0fd4e5a5dc18 100644 --- a/boolean_algebra/or_gate.py +++ b/boolean_algebra/or_gate.py @@ -29,18 +29,7 @@ def or_gate(input_1: int, input_2: int) -> int: return int((input_1, input_2).count(1) != 0) -def test_or_gate() -> None: - """ - Tests the or_gate function - """ - assert or_gate(0, 0) == 0 - assert or_gate(0, 1) == 1 - assert or_gate(1, 0) == 1 - assert or_gate(1, 1) == 1 - - if __name__ == "__main__": - print(or_gate(0, 1)) - print(or_gate(1, 0)) - print(or_gate(0, 0)) - print(or_gate(1, 1)) + import doctest + + doctest.testmod() diff --git a/boolean_algebra/xnor_gate.py b/boolean_algebra/xnor_gate.py index 45ab2700ec35..05b756da2960 100644 --- a/boolean_algebra/xnor_gate.py +++ b/boolean_algebra/xnor_gate.py @@ -31,18 +31,7 @@ def xnor_gate(input_1: int, input_2: int) -> int: return 1 if input_1 == input_2 else 0 -def test_xnor_gate() -> None: - """ - Tests the xnor_gate function - """ - assert xnor_gate(0, 0) == 1 - assert xnor_gate(0, 1) == 0 - assert xnor_gate(1, 0) == 0 - assert xnor_gate(1, 1) == 1 - - if __name__ == "__main__": - print(xnor_gate(0, 0)) - print(xnor_gate(0, 1)) - print(xnor_gate(1, 0)) - print(xnor_gate(1, 1)) + import doctest + + doctest.testmod() diff --git a/boolean_algebra/xor_gate.py b/boolean_algebra/xor_gate.py index db4f5b45c3c6..f3922e426e3d 100644 --- a/boolean_algebra/xor_gate.py +++ b/boolean_algebra/xor_gate.py @@ -31,16 +31,7 @@ def xor_gate(input_1: int, input_2: int) -> int: return (input_1, input_2).count(0) % 2 -def test_xor_gate() -> None: - """ - Tests the xor_gate function - """ - assert xor_gate(0, 0) == 0 - assert xor_gate(0, 1) == 1 - assert xor_gate(1, 0) == 1 - assert xor_gate(1, 1) == 0 - - if __name__ == "__main__": - print(xor_gate(0, 0)) - print(xor_gate(0, 1)) + import doctest + + doctest.testmod() From 3c14e6ae3aa6506ca8e5baa73321f3a04caf83d0 Mon Sep 17 00:00:00 2001 From: Kamil <32775019+quant12345@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:29:46 +0500 Subject: [PATCH 2494/2908] Refactoring and optimization of the lu_decomposition algorithm (#9231) * Replacing the generator with numpy vector operations from lu_decomposition. * Revert "Replacing the generator with numpy vector operations from lu_decomposition." This reverts commit ad217c66165898d62b76cc89ba09c2d7049b6448. * Replacing the generator with numpy vector operations from lu_decomposition. --- arithmetic_analysis/lu_decomposition.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index eaabce5449c5..094b20abfecc 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -88,15 +88,19 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray lower = np.zeros((rows, columns)) upper = np.zeros((rows, columns)) + + # in 'total', the necessary data is extracted through slices + # and the sum of the products is obtained. + for i in range(columns): for j in range(i): - total = sum(lower[i][k] * upper[k][j] for k in range(j)) + total = np.sum(lower[i, :i] * upper[:i, j]) if upper[j][j] == 0: raise ArithmeticError("No LU decomposition exists") lower[i][j] = (table[i][j] - total) / upper[j][j] lower[i][i] = 1 for j in range(i, columns): - total = sum(lower[i][k] * upper[k][j] for k in range(j)) + total = np.sum(lower[i, :i] * upper[:i, j]) upper[i][j] = table[i][j] - total return lower, upper From e9b3f20cec28b492b2e22e68ea61ec75ce3b9df8 Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:03:16 +0800 Subject: [PATCH 2495/2908] Delete dynamic_programming/longest_sub_array.py (#10073) --- dynamic_programming/longest_sub_array.py | 33 ------------------------ 1 file changed, 33 deletions(-) delete mode 100644 dynamic_programming/longest_sub_array.py diff --git a/dynamic_programming/longest_sub_array.py b/dynamic_programming/longest_sub_array.py deleted file mode 100644 index b477acf61e66..000000000000 --- a/dynamic_programming/longest_sub_array.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Author : 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(",") - - def solve_sub_array(self): - 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] - - -if __name__ == "__main__": - whole_array = input("please input some numbers:") - array = SubArray(whole_array) - re = array.solve_sub_array() - print(("the results is:", re)) From 96f81770d7e047f24c3203e913bf346754936330 Mon Sep 17 00:00:00 2001 From: Praful Katare <47990928+Kpraful@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:43:34 +0530 Subject: [PATCH 2496/2908] Adds Doc test in depth_first_search_2.py (#10094) * Adds Doc test in depth_first_search_2.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixes depth_first_search_2.py formatting * Cleanup --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- graphs/depth_first_search_2.py | 80 ++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/graphs/depth_first_search_2.py b/graphs/depth_first_search_2.py index 3072d527c1c7..5ff13af33168 100644 --- a/graphs/depth_first_search_2.py +++ b/graphs/depth_first_search_2.py @@ -9,12 +9,44 @@ def __init__(self): # for printing the Graph vertices def print_graph(self) -> None: + """ + Print the graph vertices. + + Example: + >>> g = Graph() + >>> 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]} + 0 -> 1 -> 2 + 1 -> 2 + 2 -> 0 -> 3 + 3 -> 3 + """ print(self.vertex) for i in self.vertex: print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) # for adding the edge between two vertices def add_edge(self, from_vertex: int, to_vertex: int) -> None: + """ + Add an edge between two vertices. + + :param from_vertex: The source vertex. + :param to_vertex: The destination vertex. + + Example: + >>> g = Graph() + >>> g.add_edge(0, 1) + >>> g.add_edge(0, 2) + >>> g.print_graph() + {0: [1, 2]} + 0 -> 1 -> 2 + """ # check if vertex is already present, if from_vertex in self.vertex: self.vertex[from_vertex].append(to_vertex) @@ -23,6 +55,21 @@ def add_edge(self, from_vertex: int, to_vertex: int) -> None: self.vertex[from_vertex] = [to_vertex] def dfs(self) -> None: + """ + Perform depth-first search (DFS) traversal on the graph + and print the visited vertices. + + Example: + >>> g = Graph() + >>> 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.dfs() + 0 1 2 3 + """ # visited array for storing already visited nodes visited = [False] * len(self.vertex) @@ -32,18 +79,41 @@ def dfs(self) -> None: self.dfs_recursive(i, visited) def dfs_recursive(self, start_vertex: int, visited: list) -> None: + """ + Perform a recursive depth-first search (DFS) traversal on the graph. + + :param start_vertex: The starting vertex for the traversal. + :param visited: A list to track visited vertices. + + Example: + >>> g = Graph() + >>> 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) + >>> visited = [False] * len(g.vertex) + >>> g.dfs_recursive(0, visited) + 0 1 2 3 + """ # mark start vertex as visited visited[start_vertex] = True - print(start_vertex, end=" ") + print(start_vertex, end="") # Recur for all the vertices that are adjacent to this node for i in self.vertex: if not visited[i]: + print(" ", end="") self.dfs_recursive(i, visited) if __name__ == "__main__": + import doctest + + doctest.testmod() + g = Graph() g.add_edge(0, 1) g.add_edge(0, 2) @@ -55,11 +125,3 @@ def dfs_recursive(self, start_vertex: int, visited: list) -> None: g.print_graph() print("DFS:") g.dfs() - - # OUTPUT: - # 0 -> 1 -> 2 - # 1 -> 2 - # 2 -> 0 -> 3 - # 3 -> 3 - # DFS: - # 0 1 2 3 From 69707bf6939d63a93b0d4b278cc367c42a976c6d Mon Sep 17 00:00:00 2001 From: Dwarkadhish Kamthane <72198604+dwarka-9504@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:51:03 +0530 Subject: [PATCH 2497/2908] Minimization of while loop in Armstrong Numbers (#9976) * Minimization of while loop in Armstrong Numbers The while loop is removed and simple length calculation is used so the task of minimization of while loop is achieved * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/armstrong_numbers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index 26709b428b78..e1c25d4676c3 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -29,9 +29,7 @@ def armstrong_number(n: int) -> bool: number_of_digits = 0 temp = n # Calculation of digits of the number - while temp > 0: - number_of_digits += 1 - temp //= 10 + number_of_digits = len(str(n)) # Dividing number into separate digits and find Armstrong number temp = n while temp > 0: From 7acf4bf73b5a43bdb375f7a34da227bf6deeaf35 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 16 Oct 2023 16:16:09 +0200 Subject: [PATCH 2498/2908] Rename binary_tree_traversals.md to README.md (#10599) --- DIRECTORY.md | 3 +-- .../binary_tree/{binary_tree_traversals.md => README.md} | 0 2 files changed, 1 insertion(+), 2 deletions(-) rename data_structures/binary_tree/{binary_tree_traversals.md => README.md} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index cef1e06b78aa..65628be59a92 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -340,7 +340,6 @@ * [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py) * [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py) * [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py) - * [Longest Sub Array](dynamic_programming/longest_sub_array.py) * [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py) * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) * [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py) @@ -486,6 +485,7 @@ * [Fractional Knapsack](greedy_methods/fractional_knapsack.py) * [Fractional Knapsack 2](greedy_methods/fractional_knapsack_2.py) * [Gas Station](greedy_methods/gas_station.py) + * [Minimum Coin Change](greedy_methods/minimum_coin_change.py) * [Minimum Waiting Time](greedy_methods/minimum_waiting_time.py) * [Optimal Merge Pattern](greedy_methods/optimal_merge_pattern.py) @@ -618,7 +618,6 @@ * [Gcd Of N Numbers](maths/gcd_of_n_numbers.py) * [Germain Primes](maths/germain_primes.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) - * [Greedy Coin Change](maths/greedy_coin_change.py) * [Hamming Numbers](maths/hamming_numbers.py) * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) * [Harshad Numbers](maths/harshad_numbers.py) diff --git a/data_structures/binary_tree/binary_tree_traversals.md b/data_structures/binary_tree/README.md similarity index 100% rename from data_structures/binary_tree/binary_tree_traversals.md rename to data_structures/binary_tree/README.md From 3923e590d77979de31fabd4df34e69e8933e690d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 16 Oct 2023 16:17:48 +0200 Subject: [PATCH 2499/2908] Tree_sort.py: Disable slow doctest (#10584) --- sorts/tree_sort.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index e63a3253ba19..dc95856f44c8 100644 --- a/sorts/tree_sort.py +++ b/sorts/tree_sort.py @@ -52,8 +52,9 @@ def tree_sort(arr: list[int]) -> tuple[int, ...]: (-4, 2, 5, 7, 9) >>> tree_sort([5, 6, 1, -1, 4, 37, 2, 7]) (-1, 1, 2, 4, 5, 6, 7, 37) - >>> tree_sort(range(10, -10, -1)) == tuple(sorted(range(10, -10, -1))) - True + + # >>> tree_sort(range(10, -10, -1)) == tuple(sorted(range(10, -10, -1))) + # True """ if len(arr) == 0: return tuple(arr) From c15dda405a26bd9cb1554a43598c4c85a6320d4c Mon Sep 17 00:00:00 2001 From: Saswat Susmoy <72549122+Saswatsusmoy@users.noreply.github.com> Date: Mon, 16 Oct 2023 20:13:53 +0530 Subject: [PATCH 2500/2908] Update basic_binary_tree.py (#10388) * Update basic_binary_tree.py * Update basic_binary_tree.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../binary_tree/basic_binary_tree.py | 197 +++++++++--------- 1 file changed, 103 insertions(+), 94 deletions(-) diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 65dccf247b51..0439413d95b5 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -1,101 +1,110 @@ from __future__ import annotations +from collections.abc import Iterator +from dataclasses import dataclass + +@dataclass 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: Node | None = None - self.right: Node | None = None - - -def display(tree: Node | None) -> 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) - display(tree.right) - - -def depth_of_tree(tree: Node | None) -> 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: 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 and tree.right: - return is_full_binary_tree(tree.left) and is_full_binary_tree(tree.right) - else: - return not tree.left and not tree.right - - -def main() -> None: # Main function for testing. - tree = Node(1) - tree.left = Node(2) - tree.right = Node(3) - tree.left.left = Node(4) - tree.left.right = Node(5) - tree.left.right.left = Node(6) - tree.right.left = Node(7) - tree.right.left.left = Node(8) - tree.right.left.left.right = Node(9) - - print(is_full_binary_tree(tree)) - print(depth_of_tree(tree)) - print("Tree is: ") - display(tree) + data: int + left: Node | None = None + right: Node | None = None + + def __iter__(self) -> Iterator[int]: + if self.left: + yield from self.left + yield self.data + if self.right: + yield from self.right + + def __len__(self) -> int: + return sum(1 for _ in self) + + def is_full(self) -> bool: + if not self or (not self.left and not self.right): + return True + if self.left and self.right: + return self.left.is_full() and self.right.is_full() + return False + + +@dataclass +class BinaryTree: + root: Node + + def __iter__(self) -> Iterator[int]: + return iter(self.root) + + def __len__(self) -> int: + return len(self.root) + + @classmethod + def small_tree(cls) -> BinaryTree: + """ + Return a small binary tree with 3 nodes. + >>> binary_tree = BinaryTree.small_tree() + >>> len(binary_tree) + 3 + >>> list(binary_tree) + [1, 2, 3] + """ + binary_tree = BinaryTree(Node(2)) + binary_tree.root.left = Node(1) + binary_tree.root.right = Node(3) + return binary_tree + + @classmethod + def medium_tree(cls) -> BinaryTree: + """ + Return a medium binary tree with 3 nodes. + >>> binary_tree = BinaryTree.medium_tree() + >>> len(binary_tree) + 7 + >>> list(binary_tree) + [1, 2, 3, 4, 5, 6, 7] + """ + binary_tree = BinaryTree(Node(4)) + binary_tree.root.left = two = Node(2) + two.left = Node(1) + two.right = Node(3) + binary_tree.root.right = five = Node(5) + five.right = six = Node(6) + six.right = Node(7) + return binary_tree + + def depth(self) -> int: + """ + Returns the depth of the tree + + >>> BinaryTree(Node(1)).depth() + 1 + >>> BinaryTree.small_tree().depth() + 2 + >>> BinaryTree.medium_tree().depth() + 4 + """ + return self._depth(self.root) + + def _depth(self, node: Node | None) -> int: # noqa: UP007 + if not node: + return 0 + return 1 + max(self._depth(node.left), self._depth(node.right)) + + def is_full(self) -> bool: + """ + Returns True if the tree is full + + >>> BinaryTree(Node(1)).is_full() + True + >>> BinaryTree.small_tree().is_full() + True + >>> BinaryTree.medium_tree().is_full() + False + """ + return self.root.is_full() if __name__ == "__main__": - main() + import doctest + + doctest.testmod() From 5a1305b6fe98808bf534c54e12ac64c1e4e4ce0f Mon Sep 17 00:00:00 2001 From: ivan53 Date: Mon, 16 Oct 2023 07:48:26 -0700 Subject: [PATCH 2501/2908] Fix benchmark to test with the provided number instead on 25 (#10587) --- bit_manipulation/count_number_of_one_bits.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bit_manipulation/count_number_of_one_bits.py b/bit_manipulation/count_number_of_one_bits.py index a1687503a383..f0c9f927620a 100644 --- a/bit_manipulation/count_number_of_one_bits.py +++ b/bit_manipulation/count_number_of_one_bits.py @@ -70,11 +70,13 @@ def do_benchmark(number: int) -> None: setup = "import __main__ as z" print(f"Benchmark when {number = }:") print(f"{get_set_bits_count_using_modulo_operator(number) = }") - timing = timeit("z.get_set_bits_count_using_modulo_operator(25)", setup=setup) + timing = timeit( + f"z.get_set_bits_count_using_modulo_operator({number})", setup=setup + ) print(f"timeit() runs in {timing} seconds") print(f"{get_set_bits_count_using_brian_kernighans_algorithm(number) = }") timing = timeit( - "z.get_set_bits_count_using_brian_kernighans_algorithm(25)", + f"z.get_set_bits_count_using_brian_kernighans_algorithm({number})", setup=setup, ) print(f"timeit() runs in {timing} seconds") From 778e2010d6ae89c61a93672e49b86041b6ca1108 Mon Sep 17 00:00:00 2001 From: Vinayak Upadhyay Date: Mon, 16 Oct 2023 22:16:44 +0530 Subject: [PATCH 2502/2908] Added functionality to calculate the diameter of given binary tree (#10526) * Added code to find diameter of given binary tree * Modified diameter_of_binary_tree file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update diameter_of_binary_tree.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update diameter_of_binary_tree.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../binary_tree/diameter_of_binary_tree.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 data_structures/binary_tree/diameter_of_binary_tree.py diff --git a/data_structures/binary_tree/diameter_of_binary_tree.py b/data_structures/binary_tree/diameter_of_binary_tree.py new file mode 100644 index 000000000000..bbe70b028d24 --- /dev/null +++ b/data_structures/binary_tree/diameter_of_binary_tree.py @@ -0,0 +1,72 @@ +""" +The diameter/width of a tree is defined as the number of nodes on the longest path +between two end nodes. +""" +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class Node: + data: int + left: Node | None = None + right: Node | None = None + + def depth(self) -> int: + """ + >>> root = Node(1) + >>> root.depth() + 1 + >>> root.left = Node(2) + >>> root.depth() + 2 + >>> root.left.depth() + 1 + >>> root.right = Node(3) + >>> root.depth() + 2 + """ + left_depth = self.left.depth() if self.left else 0 + right_depth = self.right.depth() if self.right else 0 + return max(left_depth, right_depth) + 1 + + def diameter(self) -> int: + """ + >>> root = Node(1) + >>> root.diameter() + 1 + >>> root.left = Node(2) + >>> root.diameter() + 2 + >>> root.left.diameter() + 1 + >>> root.right = Node(3) + >>> root.diameter() + 3 + """ + left_depth = self.left.depth() if self.left else 0 + right_depth = self.right.depth() if self.right else 0 + return left_depth + right_depth + 1 + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + root = Node(1) + root.left = Node(2) + root.right = Node(3) + root.left.left = Node(4) + root.left.right = Node(5) + r""" + Constructed binary tree is + 1 + / \ + 2 3 + / \ + 4 5 + """ + print(f"{root.diameter() = }") # 4 + print(f"{root.left.diameter() = }") # 3 + print(f"{root.right.diameter() = }") # 1 From 1e468c1028e407ea38bd7e9511dc0f3d0d45a6e0 Mon Sep 17 00:00:00 2001 From: halfhearted <99018821+Arunsiva003@users.noreply.github.com> Date: Mon, 16 Oct 2023 22:42:33 +0530 Subject: [PATCH 2503/2908] Floor and ceil in Binary search tree added (#10432) * earliest deadline first scheduling algo added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * earliest deadline first scheduling algo added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ceil and floor and bst * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ceil and floor and bst 2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ceil and floor and bst 3 * Update and rename floor_ceil_in_bst.py to floor_and_ceiling.py * Delete scheduling/shortest_deadline_first.py --------- Co-authored-by: ArunSiva Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../binary_tree/floor_and_ceiling.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 data_structures/binary_tree/floor_and_ceiling.py diff --git a/data_structures/binary_tree/floor_and_ceiling.py b/data_structures/binary_tree/floor_and_ceiling.py new file mode 100644 index 000000000000..f8a1adbd967b --- /dev/null +++ b/data_structures/binary_tree/floor_and_ceiling.py @@ -0,0 +1,87 @@ +""" +In a binary search tree (BST): +* The floor of key 'k' is the maximum value that is smaller than or equal to 'k'. +* The ceiling of key 'k' is the minimum value that is greater than or equal to 'k'. + +Reference: +https://bit.ly/46uB0a2 + +Author : Arunkumar +Date : 14th October 2023 +""" +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass + + +@dataclass +class Node: + key: int + left: Node | None = None + right: Node | None = None + + def __iter__(self) -> Iterator[int]: + if self.left: + yield from self.left + yield self.key + if self.right: + yield from self.right + + def __len__(self) -> int: + return sum(1 for _ in self) + + +def floor_ceiling(root: Node | None, key: int) -> tuple[int | None, int | None]: + """ + Find the floor and ceiling values for a given key in a Binary Search Tree (BST). + + Args: + root: The root of the binary search tree. + key: The key for which to find the floor and ceiling. + + Returns: + A tuple containing the floor and ceiling values, respectively. + + Examples: + >>> root = Node(10) + >>> root.left = Node(5) + >>> root.right = Node(20) + >>> root.left.left = Node(3) + >>> root.left.right = Node(7) + >>> root.right.left = Node(15) + >>> root.right.right = Node(25) + >>> tuple(root) + (3, 5, 7, 10, 15, 20, 25) + >>> floor_ceiling(root, 8) + (7, 10) + >>> floor_ceiling(root, 14) + (10, 15) + >>> floor_ceiling(root, -1) + (None, 3) + >>> floor_ceiling(root, 30) + (25, None) + """ + floor_val = None + ceiling_val = None + + while root: + if root.key == key: + floor_val = root.key + ceiling_val = root.key + break + + if key < root.key: + ceiling_val = root.key + root = root.left + else: + floor_val = root.key + root = root.right + + return floor_val, ceiling_val + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 922bbee80ce292ca27eee33d38e82ecf73e33dcd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 20:23:33 +0200 Subject: [PATCH 2504/2908] [pre-commit.ci] pre-commit autoupdate (#10613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/abravalheri/validate-pyproject: v0.14 → v0.15](https://github.com/abravalheri/validate-pyproject/compare/v0.14...v0.15) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 84f4a7770d00..b3def463ded2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.14 + rev: v0.15 hooks: - id: validate-pyproject diff --git a/DIRECTORY.md b/DIRECTORY.md index 65628be59a92..d878f1c79a2d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -192,10 +192,12 @@ * [Binary Tree Node Sum](data_structures/binary_tree/binary_tree_node_sum.py) * [Binary Tree Path Sum](data_structures/binary_tree/binary_tree_path_sum.py) * [Binary Tree Traversals](data_structures/binary_tree/binary_tree_traversals.py) + * [Diameter Of Binary Tree](data_structures/binary_tree/diameter_of_binary_tree.py) * [Diff Views Of Binary Tree](data_structures/binary_tree/diff_views_of_binary_tree.py) * [Distribute Coins](data_structures/binary_tree/distribute_coins.py) * [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py) * [Flatten Binarytree To Linkedlist](data_structures/binary_tree/flatten_binarytree_to_linkedlist.py) + * [Floor And Ceiling](data_structures/binary_tree/floor_and_ceiling.py) * [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py) * [Is Bst](data_structures/binary_tree/is_bst.py) * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) From fcea18c9f0b68e2ba35c8f91bf0702d7c727c4df Mon Sep 17 00:00:00 2001 From: Adarsh Sidnal <97141741+Adarshsidnal@users.noreply.github.com> Date: Tue, 17 Oct 2023 04:26:14 +0530 Subject: [PATCH 2505/2908] Added an algorithm transfrom bst to greater sum tree (#9777) * Added an algorithm transfrom bst to greater sum tree * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename transform_bst_sum_tree.py to is_sum_tree.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/binary_tree/is_sum_tree.py | 161 +++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 data_structures/binary_tree/is_sum_tree.py diff --git a/data_structures/binary_tree/is_sum_tree.py b/data_structures/binary_tree/is_sum_tree.py new file mode 100644 index 000000000000..3f9cf1d560a6 --- /dev/null +++ b/data_structures/binary_tree/is_sum_tree.py @@ -0,0 +1,161 @@ +""" +Is a binary tree a sum tree where the value of every non-leaf node is equal to the sum +of the values of its left and right subtrees? +https://www.geeksforgeeks.org/check-if-a-given-binary-tree-is-sumtree +""" +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass + + +@dataclass +class Node: + data: int + left: Node | None = None + right: Node | None = None + + def __iter__(self) -> Iterator[int]: + """ + >>> root = Node(2) + >>> list(root) + [2] + >>> root.left = Node(1) + >>> tuple(root) + (1, 2) + """ + if self.left: + yield from self.left + yield self.data + if self.right: + yield from self.right + + def __len__(self) -> int: + """ + >>> root = Node(2) + >>> len(root) + 1 + >>> root.left = Node(1) + >>> len(root) + 2 + """ + return sum(1 for _ in self) + + @property + def is_sum_node(self) -> bool: + """ + >>> root = Node(3) + >>> root.is_sum_node + True + >>> root.left = Node(1) + >>> root.is_sum_node + False + >>> root.right = Node(2) + >>> root.is_sum_node + True + """ + if not self.left and not self.right: + return True # leaf nodes are considered sum nodes + left_sum = sum(self.left) if self.left else 0 + right_sum = sum(self.right) if self.right else 0 + return all( + ( + self.data == left_sum + right_sum, + self.left.is_sum_node if self.left else True, + self.right.is_sum_node if self.right else True, + ) + ) + + +@dataclass +class BinaryTree: + root: Node + + def __iter__(self) -> Iterator[int]: + """ + >>> list(BinaryTree.build_a_tree()) + [1, 2, 7, 11, 15, 29, 35, 40] + """ + return iter(self.root) + + def __len__(self) -> int: + """ + >>> len(BinaryTree.build_a_tree()) + 8 + """ + return len(self.root) + + def __str__(self) -> str: + """ + Returns a string representation of the inorder traversal of the binary tree. + + >>> str(list(BinaryTree.build_a_tree())) + '[1, 2, 7, 11, 15, 29, 35, 40]' + """ + return str(list(self)) + + @property + def is_sum_tree(self) -> bool: + """ + >>> BinaryTree.build_a_tree().is_sum_tree + False + >>> BinaryTree.build_a_sum_tree().is_sum_tree + True + """ + return self.root.is_sum_node + + @classmethod + def build_a_tree(cls) -> BinaryTree: + r""" + Create a binary tree with the specified structure: + 11 + / \ + 2 29 + / \ / \ + 1 7 15 40 + \ + 35 + >>> list(BinaryTree.build_a_tree()) + [1, 2, 7, 11, 15, 29, 35, 40] + """ + tree = BinaryTree(Node(11)) + root = tree.root + root.left = Node(2) + root.right = Node(29) + root.left.left = Node(1) + root.left.right = Node(7) + root.right.left = Node(15) + root.right.right = Node(40) + root.right.right.left = Node(35) + return tree + + @classmethod + def build_a_sum_tree(cls) -> BinaryTree: + r""" + Create a binary tree with the specified structure: + 26 + / \ + 10 3 + / \ \ + 4 6 3 + >>> list(BinaryTree.build_a_sum_tree()) + [4, 10, 6, 26, 3, 3] + """ + tree = BinaryTree(Node(26)) + root = tree.root + root.left = Node(10) + root.right = Node(3) + root.left.left = Node(4) + root.left.right = Node(6) + root.right.right = Node(3) + return tree + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + tree = BinaryTree.build_a_tree() + print(f"{tree} has {len(tree)} nodes and {tree.is_sum_tree = }.") + tree = BinaryTree.build_a_sum_tree() + print(f"{tree} has {len(tree)} nodes and {tree.is_sum_tree = }.") From 5f629b60499cfb3ac27f6520bf947764b5b45c28 Mon Sep 17 00:00:00 2001 From: Sandeepa Dilshan Alagiyawanna <108791571+SandeepaDilshanAlagiyawanna@users.noreply.github.com> Date: Tue, 17 Oct 2023 04:47:49 +0530 Subject: [PATCH 2506/2908] Optimize and_gate and nand_gate (#10591) * Added more optimized sudoku solver algorithm * Added more optimized sudoku solver algorithm and File Renamed * and_gate is Optimized * and_gate is Optimized * and_gate is Optimized * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- boolean_algebra/and_gate.py | 2 +- boolean_algebra/nand_gate.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/boolean_algebra/and_gate.py b/boolean_algebra/and_gate.py index f0fd45c9f81e..6ae66b5b0a77 100644 --- a/boolean_algebra/and_gate.py +++ b/boolean_algebra/and_gate.py @@ -29,7 +29,7 @@ def and_gate(input_1: int, input_2: int) -> int: >>> and_gate(1, 1) 1 """ - return int((input_1, input_2).count(0) == 0) + return int(input_1 and input_2) if __name__ == "__main__": diff --git a/boolean_algebra/nand_gate.py b/boolean_algebra/nand_gate.py index 80f9d12db89a..ea7a6815dcc9 100644 --- a/boolean_algebra/nand_gate.py +++ b/boolean_algebra/nand_gate.py @@ -27,7 +27,7 @@ def nand_gate(input_1: int, input_2: int) -> int: >>> nand_gate(1, 1) 0 """ - return int((input_1, input_2).count(0) != 0) + return int(not (input_1 and input_2)) if __name__ == "__main__": From b5786c87d820cc4d68707731df0812507063bf8b Mon Sep 17 00:00:00 2001 From: aryandgandhi <44215148+aryandgandhi@users.noreply.github.com> Date: Mon, 16 Oct 2023 20:25:07 -0500 Subject: [PATCH 2507/2908] update segmenttree docstrings Fixes #9943 (#9975) * update docstrings * update docstrings * update docstrings --- data_structures/binary_tree/segment_tree.py | 41 ++++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index 5f822407d8cb..3b0b32946f6e 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -3,7 +3,8 @@ class SegmentTree: def __init__(self, a): - self.N = len(a) + self.A = a + self.N = len(self.A) self.st = [0] * ( 4 * self.N ) # approximate the overall size of segment tree with array N @@ -11,14 +12,32 @@ def __init__(self, a): self.build(1, 0, self.N - 1) def left(self, idx): + """ + Returns the left child index for a given index in a binary tree. + + >>> s = SegmentTree([1, 2, 3]) + >>> s.left(1) + 2 + >>> s.left(2) + 4 + """ return idx * 2 def right(self, idx): + """ + Returns the right child index for a given index in a binary tree. + + >>> s = SegmentTree([1, 2, 3]) + >>> s.right(1) + 3 + >>> s.right(2) + 5 + """ return idx * 2 + 1 def build(self, idx, l, r): # noqa: E741 if l == r: - self.st[idx] = A[l] + self.st[idx] = self.A[l] else: mid = (l + r) // 2 self.build(self.left(idx), l, mid) @@ -26,6 +45,15 @@ def build(self, idx, l, r): # noqa: E741 self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) def update(self, a, b, val): + """ + Update the values in the segment tree in the range [a,b] with the given value. + + >>> s = SegmentTree([1, 2, 3, 4, 5]) + >>> s.update(2, 4, 10) + True + >>> s.query(1, 5) + 10 + """ return self.update_recursive(1, 0, self.N - 1, a - 1, b - 1, val) def update_recursive(self, idx, l, r, a, b, val): # noqa: E741 @@ -44,6 +72,15 @@ def update_recursive(self, idx, l, r, a, b, val): # noqa: E741 return True def query(self, a, b): + """ + Query the maximum value in the range [a,b]. + + >>> s = SegmentTree([1, 2, 3, 4, 5]) + >>> s.query(1, 3) + 3 + >>> s.query(1, 5) + 5 + """ return self.query_recursive(1, 0, self.N - 1, a - 1, b - 1) def query_recursive(self, idx, l, r, a, b): # noqa: E741 From 00165a5fb2d125c7e6ab33e424bdcac8dec2b5b6 Mon Sep 17 00:00:00 2001 From: Saurabh Mahapatra <98408932+its-100rabh@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:06:12 +0530 Subject: [PATCH 2508/2908] Added test cases to join.py (#10629) * Added test cases to join.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- strings/join.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/strings/join.py b/strings/join.py index 739856c1aa93..5c02f65a20ce 100644 --- a/strings/join.py +++ b/strings/join.py @@ -1,10 +1,21 @@ """ -Program to join a list of strings with a given separator +Program to join a list of strings with a separator """ def join(separator: str, separated: list[str]) -> str: """ + Joins a list of strings using a separator + and returns the result. + + :param separator: Separator to be used + for joining the strings. + :param separated: List of strings to be joined. + + :return: Joined string with the specified separator. + + Examples: + >>> join("", ["a", "b", "c", "d"]) 'abcd' >>> join("#", ["a", "b", "c", "d"]) @@ -13,16 +24,27 @@ def join(separator: str, separated: list[str]) -> str: 'a' >>> join(" ", ["You", "are", "amazing!"]) 'You are amazing!' + + This example should raise an + exception for non-string elements: >>> join("#", ["a", "b", "c", 1]) Traceback (most recent call last): ... - Exception: join() accepts only strings to be joined + Exception: join() accepts only strings + + Additional test case with a different separator: + >>> join("-", ["apple", "banana", "cherry"]) + 'apple-banana-cherry' """ + joined = "" for word_or_phrase in separated: if not isinstance(word_or_phrase, str): - raise Exception("join() accepts only strings to be joined") + raise Exception("join() accepts only strings") joined += word_or_phrase + separator + + # Remove the trailing separator + # by stripping it from the result return joined.strip(separator) From c6c3bd339947eb6f10f77754f34a49915799c82f Mon Sep 17 00:00:00 2001 From: Kushagra Agarwal <94402194+developer-kush@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:40:24 +0530 Subject: [PATCH 2509/2908] Hacktoberfest: Added Octal Number to Hexadecimal Number Conversion Algorithm (#10533) * Added Octal to Hexadecimal Conversion program under 'conversions' directory * Update conversions/octal_to_hexadecimal.py fix: minor improvement to directly return hexadecimal value Co-authored-by: Tianyi Zheng * Update conversions/octal_to_hexadecimal.py fix: improvement updates to octal to hexadecimal Co-authored-by: Tianyi Zheng * Update conversions/octal_to_hexadecimal.py fix: Readablility improvements to octal to hexadecimal convertor Co-authored-by: Tianyi Zheng * Update conversions/octal_to_hexadecimal.py fix: readability improvements in octal_to_hexadecimal.py Co-authored-by: Tianyi Zheng * Update conversions/octal_to_hexadecimal.py fix: readability improvements in octal_to_hexadecimal.py Co-authored-by: Tianyi Zheng * fix: Fixed all the errors in octal_to_hexadecimal.py after commiting suggested changes * fix: modified the prefix of hex numbers to the '0x' standard in octal_to_hexadecimal.py --------- Co-authored-by: Tianyi Zheng --- conversions/octal_to_hexadecimal.py | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 conversions/octal_to_hexadecimal.py diff --git a/conversions/octal_to_hexadecimal.py b/conversions/octal_to_hexadecimal.py new file mode 100644 index 000000000000..0615d79b5c53 --- /dev/null +++ b/conversions/octal_to_hexadecimal.py @@ -0,0 +1,65 @@ +def octal_to_hex(octal: str) -> str: + """ + Convert an Octal number to Hexadecimal number. + For more information: https://en.wikipedia.org/wiki/Octal + + >>> octal_to_hex("100") + '0x40' + >>> octal_to_hex("235") + '0x9D' + >>> octal_to_hex(17) + Traceback (most recent call last): + ... + TypeError: Expected a string as input + >>> octal_to_hex("Av") + Traceback (most recent call last): + ... + ValueError: Not a Valid Octal Number + >>> octal_to_hex("") + Traceback (most recent call last): + ... + ValueError: Empty string was passed to the function + """ + + if not isinstance(octal, str): + raise TypeError("Expected a string as input") + if octal.startswith("0o"): + octal = octal[2:] + if octal == "": + raise ValueError("Empty string was passed to the function") + if any(char not in "01234567" for char in octal): + raise ValueError("Not a Valid Octal Number") + + decimal = 0 + for char in octal: + decimal <<= 3 + decimal |= int(char) + + hex_char = "0123456789ABCDEF" + + revhex = "" + while decimal: + revhex += hex_char[decimal & 15] + decimal >>= 4 + + return "0x" + revhex[::-1] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + nums = ["030", "100", "247", "235", "007"] + + ## Main Tests + + for num in nums: + hexadecimal = octal_to_hex(num) + expected = "0x" + hex(int(num, 8))[2:].upper() + + assert hexadecimal == expected + + print(f"Hex of '0o{num}' is: {hexadecimal}") + print(f"Expected was: {expected}") + print("---") From ac3bd1032c02ff5c2f6eb16f2bf5a1b24d106d1c Mon Sep 17 00:00:00 2001 From: ojas wani <52542740+ojas-wani@users.noreply.github.com> Date: Tue, 17 Oct 2023 02:25:25 -0700 Subject: [PATCH 2510/2908] Add matrix_multiplication (#10045) * added laplacian_filter file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * required changes to laplacian file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update laplacian_filter.py * update laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * changed laplacian_filter.py * changed laplacian_filter.py * add matrix_multiplication.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update matrix_multiplication * update matrix_multiplication * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * make changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * update * updates * resolve conflict * add doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * make changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update laplacian.py * add doctests * more doctest added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * try to resolve ruff error * try to reslve ruff error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update doctest * attemp - resolve ruff error * resolve build error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * resolve build issue * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update build * doctest update * update doctest * update doctest * update doctest * fix ruff error * file location changed * Delete digital_image_processing/filters/laplacian_filter.py * Create laplacian_filter.py * Update matrix_multiplication_recursion.py * Update matrix_multiplication_recursion.py * Update matrix_multiplication_recursion.py * Update matrix_multiplication_recursion.py * Update matrix_multiplication_recursion.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- matrix/matrix_multiplication_recursion.py | 180 ++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 matrix/matrix_multiplication_recursion.py diff --git a/matrix/matrix_multiplication_recursion.py b/matrix/matrix_multiplication_recursion.py new file mode 100644 index 000000000000..287142480ce7 --- /dev/null +++ b/matrix/matrix_multiplication_recursion.py @@ -0,0 +1,180 @@ +# @Author : ojas-wani +# @File : matrix_multiplication_recursion.py +# @Date : 10/06/2023 + + +""" +Perform matrix multiplication using a recursive algorithm. +https://en.wikipedia.org/wiki/Matrix_multiplication +""" +# type Matrix = list[list[int]] # psf/black currenttly fails on this line +Matrix = list[list[int]] + +matrix_1_to_4 = [ + [1, 2], + [3, 4], +] + +matrix_5_to_8 = [ + [5, 6], + [7, 8], +] + +matrix_5_to_9_high = [ + [5, 6], + [7, 8], + [9], +] + +matrix_5_to_9_wide = [ + [5, 6], + [7, 8, 9], +] + +matrix_count_up = [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], +] + +matrix_unordered = [ + [5, 8, 1, 2], + [6, 7, 3, 0], + [4, 5, 9, 1], + [2, 6, 10, 14], +] +matrices = ( + matrix_1_to_4, + matrix_5_to_8, + matrix_5_to_9_high, + matrix_5_to_9_wide, + matrix_count_up, + matrix_unordered, +) + + +def is_square(matrix: Matrix) -> bool: + """ + >>> is_square([]) + True + >>> is_square(matrix_1_to_4) + True + >>> is_square(matrix_5_to_9_high) + False + """ + len_matrix = len(matrix) + return all(len(row) == len_matrix for row in matrix) + + +def matrix_multiply(matrix_a: Matrix, matrix_b: Matrix) -> Matrix: + """ + >>> matrix_multiply(matrix_1_to_4, matrix_5_to_8) + [[19, 22], [43, 50]] + """ + return [ + [sum(a * b for a, b in zip(row, col)) for col in zip(*matrix_b)] + for row in matrix_a + ] + + +def matrix_multiply_recursive(matrix_a: Matrix, matrix_b: Matrix) -> Matrix: + """ + :param matrix_a: A square Matrix. + :param matrix_b: Another square Matrix with the same dimensions as matrix_a. + :return: Result of matrix_a * matrix_b. + :raises ValueError: If the matrices cannot be multiplied. + + >>> matrix_multiply_recursive([], []) + [] + >>> matrix_multiply_recursive(matrix_1_to_4, matrix_5_to_8) + [[19, 22], [43, 50]] + >>> matrix_multiply_recursive(matrix_count_up, matrix_unordered) + [[37, 61, 74, 61], [105, 165, 166, 129], [173, 269, 258, 197], [241, 373, 350, 265]] + >>> matrix_multiply_recursive(matrix_1_to_4, matrix_5_to_9_wide) + Traceback (most recent call last): + ... + ValueError: Invalid matrix dimensions + >>> matrix_multiply_recursive(matrix_1_to_4, matrix_5_to_9_high) + Traceback (most recent call last): + ... + ValueError: Invalid matrix dimensions + >>> matrix_multiply_recursive(matrix_1_to_4, matrix_count_up) + Traceback (most recent call last): + ... + ValueError: Invalid matrix dimensions + """ + if not matrix_a or not matrix_b: + return [] + if not all( + (len(matrix_a) == len(matrix_b), is_square(matrix_a), is_square(matrix_b)) + ): + raise ValueError("Invalid matrix dimensions") + + # Initialize the result matrix with zeros + result = [[0] * len(matrix_b[0]) for _ in range(len(matrix_a))] + + # Recursive multiplication of matrices + def multiply( + i_loop: int, + j_loop: int, + k_loop: int, + matrix_a: Matrix, + matrix_b: Matrix, + result: Matrix, + ) -> None: + """ + :param matrix_a: A square Matrix. + :param matrix_b: Another square Matrix with the same dimensions as matrix_a. + :param result: Result matrix + :param i: Index used for iteration during multiplication. + :param j: Index used for iteration during multiplication. + :param k: Index used for iteration during multiplication. + >>> 0 > 1 # Doctests in inner functions are never run + True + """ + if i_loop >= len(matrix_a): + return + if j_loop >= len(matrix_b[0]): + return multiply(i_loop + 1, 0, 0, matrix_a, matrix_b, result) + if k_loop >= len(matrix_b): + return multiply(i_loop, j_loop + 1, 0, matrix_a, matrix_b, result) + result[i_loop][j_loop] += matrix_a[i_loop][k_loop] * matrix_b[k_loop][j_loop] + return multiply(i_loop, j_loop, k_loop + 1, matrix_a, matrix_b, result) + + # Perform the recursive matrix multiplication + multiply(0, 0, 0, matrix_a, matrix_b, result) + return result + + +if __name__ == "__main__": + from doctest import testmod + + failure_count, test_count = testmod() + if not failure_count: + matrix_a = matrices[0] + for matrix_b in matrices[1:]: + print("Multiplying:") + for row in matrix_a: + print(row) + print("By:") + for row in matrix_b: + print(row) + print("Result:") + try: + result = matrix_multiply_recursive(matrix_a, matrix_b) + for row in result: + print(row) + assert result == matrix_multiply(matrix_a, matrix_b) + except ValueError as e: + print(f"{e!r}") + print() + matrix_a = matrix_b + + print("Benchmark:") + from functools import partial + from timeit import timeit + + mytimeit = partial(timeit, globals=globals(), number=100_000) + for func in ("matrix_multiply", "matrix_multiply_recursive"): + print(f"{func:>25}(): {mytimeit(f'{func}(matrix_count_up, matrix_unordered)')}") From 72bd653e04a944f51ae6c047204b62d8a07db9d4 Mon Sep 17 00:00:00 2001 From: RaymondDashWu <33266041+RaymondDashWu@users.noreply.github.com> Date: Tue, 17 Oct 2023 07:57:33 -0700 Subject: [PATCH 2511/2908] Test cases for all_combinations (#10633) * [ADD] Test cases for all_combinations * [DEL] documentation reverted b/c redundant * Update all_combinations.py --------- Co-authored-by: Christian Clauss --- backtracking/all_combinations.py | 47 ++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index bde60f0328ba..ecbcc5882ec1 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -1,15 +1,40 @@ """ 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)!))) + + Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))), """ from __future__ import annotations +from itertools import combinations + + +def combination_lists(n: int, k: int) -> list[list[int]]: + """ + >>> combination_lists(n=4, k=2) + [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] + """ + return [list(x) for x in combinations(range(1, n + 1), k)] + 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]] + >>> generate_all_combinations(n=0, k=0) + [[]] + >>> generate_all_combinations(n=10, k=-1) + Traceback (most recent call last): + ... + RecursionError: maximum recursion depth exceeded + >>> generate_all_combinations(n=-1, k=10) + [] + >>> generate_all_combinations(n=5, k=4) + [[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]] + >>> from itertools import combinations + >>> all(generate_all_combinations(n, k) == combination_lists(n, k) + ... for n in range(1, 6) for k in range(1, 6)) + True """ result: list[list[int]] = [] @@ -34,13 +59,17 @@ def create_all_state( current_list.pop() -def print_all_state(total_list: list[list[int]]) -> None: - for i in total_list: - print(*i) +if __name__ == "__main__": + from doctest import testmod + testmod() + print(generate_all_combinations(n=4, k=2)) + tests = ((n, k) for n in range(1, 5) for k in range(1, 5)) + for n, k in tests: + print(n, k, generate_all_combinations(n, k) == combination_lists(n, k)) -if __name__ == "__main__": - n = 4 - k = 2 - total_list = generate_all_combinations(n, k) - print_all_state(total_list) + print("Benchmark:") + from timeit import timeit + + for func in ("combination_lists", "generate_all_combinations"): + print(f"{func:>25}(): {timeit(f'{func}(n=4, k = 2)', globals=globals())}") From 09c2b2d006e3ca217f2ef082d62a0c35560667ef Mon Sep 17 00:00:00 2001 From: Anubhavpandey27 <61093307+Anubhavpandey27@users.noreply.github.com> Date: Tue, 17 Oct 2023 22:37:40 +0530 Subject: [PATCH 2512/2908] Add arrays/sudoku_solver.py (#10623) * Create Sudoku_Solver Each of the digits 1-9 must occur exactly once in each row. Each of the digits 1-9 must occur exactly once in each column. Each of the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid. The '.' character indicates empty cells. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename Sudoku_Solver to sudoku_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sudoku_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/arrays/sudoku_solver.py | 220 ++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 data_structures/arrays/sudoku_solver.py diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py new file mode 100644 index 000000000000..8d38bd7295ea --- /dev/null +++ b/data_structures/arrays/sudoku_solver.py @@ -0,0 +1,220 @@ +""" +Please do not modify this file! It is published at https://norvig.com/sudoku.html with +only minimal changes to work with modern versions of Python. If you have improvements, +please make them in a separate file. +""" +import random +import time + + +def cross(items_a, items_b): + "Cross product of elements in A and elements in B." + return [a + b for a in items_a for b in items_b] + + +digits = "123456789" +rows = "ABCDEFGHI" +cols = digits +squares = cross(rows, cols) +unitlist = ( + [cross(rows, c) for c in cols] + + [cross(r, cols) for r in rows] + + [cross(rs, cs) for rs in ("ABC", "DEF", "GHI") for cs in ("123", "456", "789")] +) +units = {s: [u for u in unitlist if s in u] for s in squares} +peers = {s: set(sum(units[s], [])) - {s} for s in squares} + + +def test(): + "A set of unit tests." + assert len(squares) == 81 + assert len(unitlist) == 27 + assert all(len(units[s]) == 3 for s in squares) + assert all(len(peers[s]) == 20 for s in squares) + assert units["C2"] == [ + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "I2"], + ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"], + ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"], + ] + # fmt: off + assert peers["C2"] == { + "A2", "B2", "D2", "E2", "F2", "G2", "H2", "I2", "C1", "C3", + "C4", "C5", "C6", "C7", "C8", "C9", "A1", "A3", "B1", "B3" + } + # fmt: on + print("All tests pass.") + + +def parse_grid(grid): + """Convert grid to a dict of possible values, {square: digits}, or + return False if a contradiction is detected.""" + ## To start, every square can be any digit; then assign values from the grid. + values = {s: digits for s in squares} + for s, d in grid_values(grid).items(): + if d in digits and not assign(values, s, d): + return False ## (Fail if we can't assign d to square s.) + return values + + +def grid_values(grid): + "Convert grid into a dict of {square: char} with '0' or '.' for empties." + chars = [c for c in grid if c in digits or c in "0."] + assert len(chars) == 81 + return dict(zip(squares, chars)) + + +def assign(values, s, d): + """Eliminate all the other values (except d) from values[s] and propagate. + Return values, except return False if a contradiction is detected.""" + other_values = values[s].replace(d, "") + if all(eliminate(values, s, d2) for d2 in other_values): + return values + else: + return False + + +def eliminate(values, s, d): + """Eliminate d from values[s]; propagate when values or places <= 2. + Return values, except return False if a contradiction is detected.""" + if d not in values[s]: + return values ## Already eliminated + values[s] = values[s].replace(d, "") + ## (1) If a square s is reduced to one value d2, then eliminate d2 from the peers. + if len(values[s]) == 0: + return False ## Contradiction: removed last value + elif len(values[s]) == 1: + d2 = values[s] + if not all(eliminate(values, s2, d2) for s2 in peers[s]): + return False + ## (2) If a unit u is reduced to only one place for a value d, then put it there. + for u in units[s]: + dplaces = [s for s in u if d in values[s]] + if len(dplaces) == 0: + return False ## Contradiction: no place for this value + elif len(dplaces) == 1: + # d can only be in one place in unit; assign it there + if not assign(values, dplaces[0], d): + return False + return values + + +def display(values): + "Display these values as a 2-D grid." + width = 1 + max(len(values[s]) for s in squares) + line = "+".join(["-" * (width * 3)] * 3) + for r in rows: + print( + "".join( + values[r + c].center(width) + ("|" if c in "36" else "") for c in cols + ) + ) + if r in "CF": + print(line) + print() + + +def solve(grid): + return search(parse_grid(grid)) + + +def some(seq): + "Return some element of seq that is true." + for e in seq: + if e: + return e + return False + + +def search(values): + "Using depth-first search and propagation, try all possible values." + if values is False: + return False ## Failed earlier + if all(len(values[s]) == 1 for s in squares): + return values ## Solved! + ## Chose the unfilled square s with the fewest possibilities + n, s = min((len(values[s]), s) for s in squares if len(values[s]) > 1) + return some(search(assign(values.copy(), s, d)) for d in values[s]) + + +def solve_all(grids, name="", showif=0.0): + """Attempt to solve a sequence of grids. Report results. + When showif is a number of seconds, display puzzles that take longer. + When showif is None, don't display any puzzles.""" + + def time_solve(grid): + start = time.monotonic() + values = solve(grid) + t = time.monotonic() - start + ## Display puzzles that take long enough + if showif is not None and t > showif: + display(grid_values(grid)) + if values: + display(values) + print("(%.5f seconds)\n" % t) + return (t, solved(values)) + + times, results = zip(*[time_solve(grid) for grid in grids]) + if (n := len(grids)) > 1: + print( + "Solved %d of %d %s puzzles (avg %.2f secs (%d Hz), max %.2f secs)." + % (sum(results), n, name, sum(times) / n, n / sum(times), max(times)) + ) + + +def solved(values): + "A puzzle is solved if each unit is a permutation of the digits 1 to 9." + + def unitsolved(unit): + return {values[s] for s in unit} == set(digits) + + return values is not False and all(unitsolved(unit) for unit in unitlist) + + +def from_file(filename, sep="\n"): + "Parse a file into a list of strings, separated by sep." + return open(filename).read().strip().split(sep) # noqa: SIM115 + + +def random_puzzle(assignments=17): + """Make a random puzzle with N or more assignments. Restart on contradictions. + Note the resulting puzzle is not guaranteed to be solvable, but empirically + about 99.8% of them are solvable. Some have multiple solutions.""" + values = {s: digits for s in squares} + for s in shuffled(squares): + if not assign(values, s, random.choice(values[s])): + break + ds = [values[s] for s in squares if len(values[s]) == 1] + if len(ds) >= assignments and len(set(ds)) >= 8: + return "".join(values[s] if len(values[s]) == 1 else "." for s in squares) + return random_puzzle(assignments) ## Give up and make a new puzzle + + +def shuffled(seq): + "Return a randomly shuffled copy of the input sequence." + seq = list(seq) + random.shuffle(seq) + return seq + + +grid1 = ( + "003020600900305001001806400008102900700000008006708200002609500800203009005010300" +) +grid2 = ( + "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......" +) +hard1 = ( + ".....6....59.....82....8....45........3........6..3.54...325..6.................." +) + +if __name__ == "__main__": + test() + # solve_all(from_file("easy50.txt", '========'), "easy", None) + # solve_all(from_file("top95.txt"), "hard", None) + # solve_all(from_file("hardest.txt"), "hardest", None) + solve_all([random_puzzle() for _ in range(99)], "random", 100.0) + for puzzle in (grid1, grid2): # , hard1): # Takes 22 sec to solve on my M1 Mac. + display(parse_grid(puzzle)) + start = time.monotonic() + solve(puzzle) + t = time.monotonic() - start + print("Solved: %.5f sec" % t) From 9de1c49fe13f009e08dcf5009a798bef43f2230b Mon Sep 17 00:00:00 2001 From: Marek Mazij <112333347+Mrk-Mzj@users.noreply.github.com> Date: Tue, 17 Oct 2023 20:24:16 +0200 Subject: [PATCH 2513/2908] feat: Polish ID (PESEL) checker added (#10618) * feat: Polish ID (PESEL) checker added * refactor: 'sum' variable renamed to 'subtotal' * style: typos * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- strings/is_polish_national_id.py | 92 ++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 strings/is_polish_national_id.py diff --git a/strings/is_polish_national_id.py b/strings/is_polish_national_id.py new file mode 100644 index 000000000000..8b463a24532a --- /dev/null +++ b/strings/is_polish_national_id.py @@ -0,0 +1,92 @@ +def is_polish_national_id(input_str: str) -> bool: + """ + Verification of the correctness of the PESEL number. + www-gov-pl.translate.goog/web/gov/czym-jest-numer-pesel?_x_tr_sl=auto&_x_tr_tl=en + + PESEL can start with 0, that's why we take str as input, + but convert it to int for some calculations. + + + >>> is_polish_national_id(123) + Traceback (most recent call last): + ... + ValueError: Expected str as input, found + + >>> is_polish_national_id("abc") + Traceback (most recent call last): + ... + ValueError: Expected number as input + + >>> is_polish_national_id("02070803628") # correct PESEL + True + + >>> is_polish_national_id("02150803629") # wrong month + False + + >>> is_polish_national_id("02075503622") # wrong day + False + + >>> is_polish_national_id("-99012212349") # wrong range + False + + >>> is_polish_national_id("990122123499999") # wrong range + False + + >>> is_polish_national_id("02070803621") # wrong checksum + False + """ + + # check for invalid input type + if not isinstance(input_str, str): + msg = f"Expected str as input, found {type(input_str)}" + raise ValueError(msg) + + # check if input can be converted to int + try: + input_int = int(input_str) + except ValueError: + msg = "Expected number as input" + raise ValueError(msg) + + # check number range + if not 10100000 <= input_int <= 99923199999: + return False + + # check month correctness + month = int(input_str[2:4]) + + if ( + month not in range(1, 13) # year 1900-1999 + and month not in range(21, 33) # 2000-2099 + and month not in range(41, 53) # 2100-2199 + and month not in range(61, 73) # 2200-2299 + and month not in range(81, 93) # 1800-1899 + ): + return False + + # check day correctness + day = int(input_str[4:6]) + + if day not in range(1, 32): + return False + + # check the checksum + multipliers = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3] + subtotal = 0 + + digits_to_check = str(input_str)[:-1] # cut off the checksum + + for index, digit in enumerate(digits_to_check): + # Multiply corresponding digits and multipliers. + # In case of a double-digit result, add only the last digit. + subtotal += (int(digit) * multipliers[index]) % 10 + + checksum = 10 - subtotal % 10 + + return checksum == input_int % 10 + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 9da6f48b46f41c6361416c259dcfec531fb39a01 Mon Sep 17 00:00:00 2001 From: Manmita Das <34617961+manmita@users.noreply.github.com> Date: Wed, 18 Oct 2023 04:07:57 +0530 Subject: [PATCH 2514/2908] Add binary_coded_decimal.py (#10656) * added decimal to bcd sequence * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated with fixes * Update and rename bcd_sequence.py to binary_coded_decimal.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update binary_coded_decimal.py * Update binary_coded_decimal.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- bit_manipulation/binary_coded_decimal.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 bit_manipulation/binary_coded_decimal.py diff --git a/bit_manipulation/binary_coded_decimal.py b/bit_manipulation/binary_coded_decimal.py new file mode 100644 index 000000000000..676fd6d54fc5 --- /dev/null +++ b/bit_manipulation/binary_coded_decimal.py @@ -0,0 +1,29 @@ +def binary_coded_decimal(number: int) -> str: + """ + Find binary coded decimal (bcd) of integer base 10. + Each digit of the number is represented by a 4-bit binary. + Example: + >>> binary_coded_decimal(-2) + '0b0000' + >>> binary_coded_decimal(-1) + '0b0000' + >>> binary_coded_decimal(0) + '0b0000' + >>> binary_coded_decimal(3) + '0b0011' + >>> binary_coded_decimal(2) + '0b0010' + >>> binary_coded_decimal(12) + '0b00010010' + >>> binary_coded_decimal(987) + '0b100110000111' + """ + return "0b" + "".join( + str(bin(int(digit)))[2:].zfill(4) for digit in str(max(0, number)) + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 788e4ede9bf4eb180e4b784268d98d657efbd9da Mon Sep 17 00:00:00 2001 From: Jai Vignesh J <108923524+Jaivignesh-afk@users.noreply.github.com> Date: Wed, 18 Oct 2023 04:20:57 +0530 Subject: [PATCH 2515/2908] Fix doctest power recursion (#10659) * Added doctests to power_using_recursion.py * Added doctest to power_using_recursion.py * Update power_using_recursion.py * Update power_using_recursion.py --------- Co-authored-by: Christian Clauss --- maths/power_using_recursion.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/maths/power_using_recursion.py b/maths/power_using_recursion.py index f82097f6d8ec..e82635ba0005 100644 --- a/maths/power_using_recursion.py +++ b/maths/power_using_recursion.py @@ -15,18 +15,35 @@ def power(base: int, exponent: int) -> float: """ - power(3, 4) + >>> 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 + >>> power('a', 1) + 'a' + >>> power('a', 2) + Traceback (most recent call last): + ... + TypeError: can't multiply sequence by non-int of type 'str' + >>> power('a', 'b') + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'str' and 'int' + >>> power(2, -1) + Traceback (most recent call last): + ... + RecursionError: maximum recursion depth exceeded """ return base * power(base, (exponent - 1)) if exponent else 1 if __name__ == "__main__": + from doctests import testmod + + testmod() print("Raise base to the power of exponent using recursion...") base = int(input("Enter the base: ").strip()) exponent = int(input("Enter the exponent: ").strip()) From 361f64c21d7d2528828e20e2eedd59b8d69e5c18 Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Wed, 18 Oct 2023 19:39:13 +0530 Subject: [PATCH 2516/2908] Adds hinge loss function algorithm (#10628) * Adds exponential moving average algorithm * code clean up * spell correction * Modifies I/O types of function * Replaces generator function * Resolved mypy type error * readibility of code and documentation * Update exponential_moving_average.py * Adds hinge loss function * suggested doc and refactoring changes * refactoring --------- Co-authored-by: Christian Clauss --- machine_learning/loss_functions/hinge_loss.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 machine_learning/loss_functions/hinge_loss.py diff --git a/machine_learning/loss_functions/hinge_loss.py b/machine_learning/loss_functions/hinge_loss.py new file mode 100644 index 000000000000..5480a8cd62ee --- /dev/null +++ b/machine_learning/loss_functions/hinge_loss.py @@ -0,0 +1,64 @@ +""" +Hinge Loss + +Description: +Compute the Hinge loss used for training SVM (Support Vector Machine). + +Formula: +loss = max(0, 1 - true * pred) + +Reference: https://en.wikipedia.org/wiki/Hinge_loss + +Author: Poojan Smart +Email: smrtpoojan@gmail.com +""" + +import numpy as np + + +def hinge_loss(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Calculate the mean hinge loss for y_true and y_pred for binary classification. + + Args: + y_true: Array of actual values (ground truth) encoded as -1 and 1. + y_pred: Array of predicted values. + + Returns: + The hinge loss between y_true and y_pred. + + Examples: + >>> y_true = np.array([-1, 1, 1, -1, 1]) + >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) + >>> hinge_loss(y_true, pred) + 1.52 + >>> y_true = np.array([-1, 1, 1, -1, 1, 1]) + >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) + >>> hinge_loss(y_true, pred) + Traceback (most recent call last): + ... + ValueError: Length of predicted and actual array must be same. + >>> y_true = np.array([-1, 1, 10, -1, 1]) + >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) + >>> hinge_loss(y_true, pred) + Traceback (most recent call last): + ... + ValueError: y_true can have values -1 or 1 only. + """ + + if len(y_true) != len(y_pred): + raise ValueError("Length of predicted and actual array must be same.") + + # Raise value error when y_true (encoded labels) have any other values + # than -1 and 1 + if np.any((y_true != -1) & (y_true != 1)): + raise ValueError("y_true can have values -1 or 1 only.") + + hinge_losses = np.maximum(0, 1.0 - (y_true * y_pred)) + return np.mean(hinge_losses) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 572de4f15e266057e806a751006156a212a3812e Mon Sep 17 00:00:00 2001 From: Shivansh Bhatnagar Date: Wed, 18 Oct 2023 20:20:18 +0530 Subject: [PATCH 2517/2908] Added A General Swish Activation Function inNeural Networks (#10415) * Added A General Swish Activation Function inNeural Networks * Added the general swish function in the SiLU function and renamed it as swish.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Shivansh Bhatnagar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../{sigmoid_linear_unit.py => swish.py} | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) rename neural_network/activation_functions/{sigmoid_linear_unit.py => swish.py} (72%) diff --git a/neural_network/activation_functions/sigmoid_linear_unit.py b/neural_network/activation_functions/swish.py similarity index 72% rename from neural_network/activation_functions/sigmoid_linear_unit.py rename to neural_network/activation_functions/swish.py index 0ee09bf82d38..ab3d8fa1203b 100644 --- a/neural_network/activation_functions/sigmoid_linear_unit.py +++ b/neural_network/activation_functions/swish.py @@ -12,6 +12,7 @@ This script is inspired by a corresponding research paper. * https://arxiv.org/abs/1710.05941 +* https://blog.paperspace.com/swish-activation-function/ """ import numpy as np @@ -49,6 +50,25 @@ def sigmoid_linear_unit(vector: np.ndarray) -> np.ndarray: return vector * sigmoid(vector) +def swish(vector: np.ndarray, trainable_parameter: int) -> np.ndarray: + """ + Parameters: + vector (np.ndarray): A numpy array consisting of real values + trainable_parameter: Use to implement various Swish Activation Functions + + Returns: + swish_vec (np.ndarray): The input numpy array, after applying swish + + Examples: + >>> swish(np.array([-1.0, 1.0, 2.0]), 2) + array([-0.11920292, 0.88079708, 1.96402758]) + + >>> swish(np.array([-2]), 1) + array([-0.23840584]) + """ + return vector * sigmoid(trainable_parameter * vector) + + if __name__ == "__main__": import doctest From 9adb7ced16725e3f6cf24cf93ac81a8dcd351665 Mon Sep 17 00:00:00 2001 From: rtang09 <49603415+rtang09@users.noreply.github.com> Date: Thu, 19 Oct 2023 05:02:04 -0700 Subject: [PATCH 2518/2908] Update primelib.py (#10209) * Update primelib.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/primelib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/maths/primelib.py b/maths/primelib.py index 7e33844be12b..e2d432e1846a 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -51,6 +51,10 @@ def is_prime(number: int) -> bool: True >>> is_prime(10) False + >>> is_prime(97) + True + >>> is_prime(9991) + False >>> is_prime(-1) Traceback (most recent call last): ... From 30c8d5573a8b052210238487167a3ec2d7241d06 Mon Sep 17 00:00:00 2001 From: rtang09 <49603415+rtang09@users.noreply.github.com> Date: Thu, 19 Oct 2023 05:15:23 -0700 Subject: [PATCH 2519/2908] Update binary_exponentiation.py (#10253) * Update binary_exponentiation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/binary_exponentiation.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index 05de939d1bde..7eeca89262a9 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -5,6 +5,12 @@ def binary_exponentiation(a: int, n: int) -> int: + """ + >>> binary_exponentiation(3, 5) + 243 + >>> binary_exponentiation(10, 3) + 1000 + """ if n == 0: return 1 @@ -17,6 +23,10 @@ def binary_exponentiation(a: int, n: int) -> int: if __name__ == "__main__": + import doctest + + doctest.testmod() + try: BASE = int(input("Enter Base : ").strip()) POWER = int(input("Enter Power : ").strip()) From b301e589e2c68f583bf3a09f6d4ca224175383b9 Mon Sep 17 00:00:00 2001 From: Iyiola Aloko <48067557+ialoko@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:21:48 -0400 Subject: [PATCH 2520/2908] Update binary_exponentiation.py (#10342) Co-authored-by: Tianyi Zheng --- maths/binary_exponentiation.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index 7eeca89262a9..f613767f547e 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -6,10 +6,21 @@ def binary_exponentiation(a: int, n: int) -> int: """ + Compute a number raised by some quantity + >>> binary_exponentiation(-1, 3) + -1 + >>> binary_exponentiation(-1, 4) + 1 + >>> binary_exponentiation(2, 2) + 4 >>> binary_exponentiation(3, 5) 243 >>> binary_exponentiation(10, 3) 1000 + >>> binary_exponentiation(5e3, 1) + 5000.0 + >>> binary_exponentiation(-5e3, 1) + -5000.0 """ if n == 0: return 1 @@ -28,7 +39,7 @@ def binary_exponentiation(a: int, n: int) -> int: doctest.testmod() try: - BASE = int(input("Enter Base : ").strip()) + BASE = int(float(input("Enter Base : ").strip())) POWER = int(input("Enter Power : ").strip()) except ValueError: print("Invalid literal for integer") From 33888646af9d74e46da0175df75b3e5892a72fc7 Mon Sep 17 00:00:00 2001 From: anshul-2010 <96651393+anshul-2010@users.noreply.github.com> Date: Thu, 19 Oct 2023 18:08:02 +0530 Subject: [PATCH 2521/2908] Edit Distance Algorithm for String Matching (#10571) * Edit Distance Algorithm for String Matching * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update edit_distance.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- strings/edit_distance.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 strings/edit_distance.py diff --git a/strings/edit_distance.py b/strings/edit_distance.py new file mode 100644 index 000000000000..e842c8555c8e --- /dev/null +++ b/strings/edit_distance.py @@ -0,0 +1,32 @@ +def edit_distance(source: str, target: str) -> int: + """ + Edit distance algorithm is a string metric, i.e., it is a way of quantifying how + dissimilar two strings are to one another. It is measured by counting the minimum + number of operations required to transform one string into another. + + This implementation assumes that the cost of operations (insertion, deletion and + substitution) is always 1 + + Args: + source: the initial string with respect to which we are calculating the edit + distance for the target + target: the target string, formed after performing n operations on the source string + + >>> edit_distance("GATTIC", "GALTIC") + 1 + """ + if len(source) == 0: + return len(target) + elif len(target) == 0: + return len(source) + + delta = int(source[-1] != target[-1]) # Substitution + return min( + edit_distance(source[:-1], target[:-1]) + delta, + edit_distance(source, target[:-1]) + 1, + edit_distance(source[:-1], target) + 1, + ) + + +if __name__ == "__main__": + print(edit_distance("ATCGCTG", "TAGCTAA")) # Answer is 4 From 289a4dd6d35a3dd402c98db04d2f39cfc08ea1be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Azevedo?= Date: Thu, 19 Oct 2023 11:35:41 -0300 Subject: [PATCH 2522/2908] docs: add test scenarios to pull request template (#10396) * docs: add test scenarios to pull request template * Update .github/pull_request_template.md --------- Co-authored-by: Christian Clauss --- .github/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1f9797fae038..e2ae0966cda5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,6 +4,7 @@ * [ ] Add an algorithm? * [ ] Fix a bug or typo in an existing algorithm? +* [ ] Add or change doctests? -- Note: Please avoid changing both code and tests in a single pull request. * [ ] Documentation change? ### Checklist: From bd3072b84512b33a6fd7d788812340daa8ac3465 Mon Sep 17 00:00:00 2001 From: Ankit Avinash <128812932+Void426@users.noreply.github.com> Date: Thu, 19 Oct 2023 20:16:39 +0530 Subject: [PATCH 2523/2908] Added Mean Squared Logarithmic Error (MSLE) Loss Function (#10637) * Added Mean Squared Logarithmic Error (MSLE) * Added Mean Squared Logarithmic Error (MSLE) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../mean_squared_logarithmic_error.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 machine_learning/loss_functions/mean_squared_logarithmic_error.py diff --git a/machine_learning/loss_functions/mean_squared_logarithmic_error.py b/machine_learning/loss_functions/mean_squared_logarithmic_error.py new file mode 100644 index 000000000000..935ebff37a51 --- /dev/null +++ b/machine_learning/loss_functions/mean_squared_logarithmic_error.py @@ -0,0 +1,55 @@ +""" +Mean Squared Logarithmic Error (MSLE) Loss Function + +Description: +MSLE measures the mean squared logarithmic difference between +true values and predicted values, particularly useful when +dealing with regression problems involving skewed or large-value +targets. It is often used when the relative differences between +predicted and true values are more important than absolute +differences. + +Formula: +MSLE = (1/n) * Σ(log(1 + y_true) - log(1 + y_pred))^2 + +Source: +(https://insideaiml.com/blog/MeanSquared-Logarithmic-Error-Loss-1035) +""" + +import numpy as np + + +def mean_squared_logarithmic_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Calculate the Mean Squared Logarithmic Error (MSLE) between two arrays. + + Parameters: + - y_true: The true values (ground truth). + - y_pred: The predicted values. + + Returns: + - msle: The Mean Squared Logarithmic Error between y_true and y_pred. + + Example usage: + >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) + >>> mean_squared_logarithmic_error(true_values, predicted_values) + 0.0030860877925181344 + >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) + >>> mean_squared_logarithmic_error(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + squared_logarithmic_errors = (np.log1p(y_true) - np.log1p(y_pred)) ** 2 + return np.mean(squared_logarithmic_errors) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 69876140673881efefcb177e3ba2575b0c221438 Mon Sep 17 00:00:00 2001 From: ketan96-m <40893179+ketan96-m@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:48:53 -0500 Subject: [PATCH 2524/2908] *added docstring and doctest for find_isolated_nodes (#10684) *added docstring and doctest for edglist *added docstring and doctest for adjm Co-authored-by: Ketan --- graphs/basic_graphs.py | 81 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 065b6185c123..25c8045b3d2b 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -185,10 +185,29 @@ def topo(g, ind=None, q=None): def adjm(): - n = input().strip() + r""" + Reading an Adjacency matrix + + Parameters: + None + + Returns: + tuple: A tuple containing a list of edges and number of edges + + Example: + >>> # Simulate user input for 3 nodes + >>> input_data = "4\n0 1 0 1\n1 0 1 0\n0 1 0 1\n1 0 1 0\n" + >>> import sys,io + >>> original_input = sys.stdin + >>> sys.stdin = io.StringIO(input_data) # Redirect stdin for testing + >>> adjm() + ([(0, 1, 0, 1), (1, 0, 1, 0), (0, 1, 0, 1), (1, 0, 1, 0)], 4) + >>> sys.stdin = original_input # Restore original stdin + """ + n = int(input().strip()) a = [] for _ in range(n): - a.append(map(int, input().strip().split())) + a.append(tuple(map(int, input().strip().split()))) return a, n @@ -260,10 +279,29 @@ def prim(g, s): def edglist(): - n, m = map(int, input().split(" ")) + r""" + Get the edges and number of edges from the user + + Parameters: + None + + Returns: + tuple: A tuple containing a list of edges and number of edges + + Example: + >>> # Simulate user input for 3 edges and 4 vertices: (1, 2), (2, 3), (3, 4) + >>> input_data = "4 3\n1 2\n2 3\n3 4\n" + >>> import sys,io + >>> original_input = sys.stdin + >>> sys.stdin = io.StringIO(input_data) # Redirect stdin for testing + >>> edglist() + ([(1, 2), (2, 3), (3, 4)], 4) + >>> sys.stdin = original_input # Restore original stdin + """ + n, m = tuple(map(int, input().split(" "))) edges = [] for _ in range(m): - edges.append(map(int, input().split(" "))) + edges.append(tuple(map(int, input().split(" ")))) return edges, n @@ -278,7 +316,9 @@ def edglist(): def krusk(e_and_n): - # Sort edges on the basis of distance + """ + Sort edges on the basis of distance + """ (e, n) = e_and_n e.sort(reverse=True, key=lambda x: x[2]) s = [{i} for i in range(1, n + 1)] @@ -299,8 +339,37 @@ def krusk(e_and_n): break -# find the isolated node in the graph def find_isolated_nodes(graph): + """ + Find the isolated node in the graph + + Parameters: + graph (dict): A dictionary representing a graph. + + Returns: + list: A list of isolated nodes. + + Examples: + >>> graph1 = {1: [2, 3], 2: [1, 3], 3: [1, 2], 4: []} + >>> find_isolated_nodes(graph1) + [4] + + >>> graph2 = {'A': ['B', 'C'], 'B': ['A'], 'C': ['A'], 'D': []} + >>> find_isolated_nodes(graph2) + ['D'] + + >>> graph3 = {'X': [], 'Y': [], 'Z': []} + >>> find_isolated_nodes(graph3) + ['X', 'Y', 'Z'] + + >>> graph4 = {1: [2, 3], 2: [1, 3], 3: [1, 2]} + >>> find_isolated_nodes(graph4) + [] + + >>> graph5 = {} + >>> find_isolated_nodes(graph5) + [] + """ isolated = [] for node in graph: if not graph[node]: From 26ffad9d17232668d0630edb70167e5123a7f35c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 19 Oct 2023 19:31:51 +0200 Subject: [PATCH 2525/2908] Simplify is_bst.py (#10627) * Simplify is_bst.py * updating DIRECTORY.md * Update is_bst.py * Rename is_bst.py to is_sorted.py * updating DIRECTORY.md * Update data_structures/binary_tree/is_sorted.py Co-authored-by: Tianyi Zheng --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- DIRECTORY.md | 3 +- data_structures/binary_tree/is_bst.py | 131 ----------------------- data_structures/binary_tree/is_sorted.py | 97 +++++++++++++++++ 3 files changed, 99 insertions(+), 132 deletions(-) delete mode 100644 data_structures/binary_tree/is_bst.py create mode 100644 data_structures/binary_tree/is_sorted.py diff --git a/DIRECTORY.md b/DIRECTORY.md index d878f1c79a2d..0999d2e8687a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -199,7 +199,8 @@ * [Flatten Binarytree To Linkedlist](data_structures/binary_tree/flatten_binarytree_to_linkedlist.py) * [Floor And Ceiling](data_structures/binary_tree/floor_and_ceiling.py) * [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py) - * [Is Bst](data_structures/binary_tree/is_bst.py) + * [Is Sorted](data_structures/binary_tree/is_sorted.py) + * [Is Sum Tree](data_structures/binary_tree/is_sum_tree.py) * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py) * [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py) diff --git a/data_structures/binary_tree/is_bst.py b/data_structures/binary_tree/is_bst.py deleted file mode 100644 index 0b2ef8c9ffde..000000000000 --- a/data_structures/binary_tree/is_bst.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -Author : Alexander Pantyukhin -Date : November 2, 2022 - -Task: -Given the root of a binary tree, determine if it is a valid binary search -tree (BST). - -A valid binary search tree is defined as follows: - -- The left subtree of a node contains only nodes with keys less than the node's key. -- The right subtree of a node contains only nodes with keys greater than the node's key. -- Both the left and right subtrees must also be binary search trees. - -Implementation notes: -Depth-first search approach. - -leetcode: https://leetcode.com/problems/validate-binary-search-tree/ - -Let n is the number of nodes in tree -Runtime: O(n) -Space: O(1) -""" - -from __future__ import annotations - -from dataclasses import dataclass - - -@dataclass -class TreeNode: - data: float - left: TreeNode | None = None - right: TreeNode | None = None - - -def is_binary_search_tree(root: TreeNode | None) -> bool: - """ - >>> is_binary_search_tree(TreeNode(data=2, - ... left=TreeNode(data=1), - ... right=TreeNode(data=3)) - ... ) - True - - >>> is_binary_search_tree(TreeNode(data=0, - ... left=TreeNode(data=-11), - ... right=TreeNode(data=3)) - ... ) - True - - >>> is_binary_search_tree(TreeNode(data=5, - ... left=TreeNode(data=1), - ... right=TreeNode(data=4, left=TreeNode(data=3))) - ... ) - False - - >>> is_binary_search_tree(TreeNode(data='a', - ... left=TreeNode(data=1), - ... right=TreeNode(data=4, left=TreeNode(data=3))) - ... ) - Traceback (most recent call last): - ... - ValueError: Each node should be type of TreeNode and data should be float. - - >>> is_binary_search_tree(TreeNode(data=2, - ... left=TreeNode([]), - ... right=TreeNode(data=4, left=TreeNode(data=3))) - ... ) - Traceback (most recent call last): - ... - ValueError: Each node should be type of TreeNode and data should be float. - """ - - # Validation - def is_valid_tree(node: TreeNode | None) -> bool: - """ - >>> is_valid_tree(None) - True - >>> is_valid_tree('abc') - False - >>> is_valid_tree(TreeNode(data='not a float')) - False - >>> is_valid_tree(TreeNode(data=1, left=TreeNode('123'))) - False - """ - if node is None: - return True - - if not isinstance(node, TreeNode): - return False - - try: - float(node.data) - except (TypeError, ValueError): - return False - - return is_valid_tree(node.left) and is_valid_tree(node.right) - - if not is_valid_tree(root): - raise ValueError( - "Each node should be type of TreeNode and data should be float." - ) - - def is_binary_search_tree_recursive_check( - node: TreeNode | None, left_bound: float, right_bound: float - ) -> bool: - """ - >>> is_binary_search_tree_recursive_check(None) - True - >>> is_binary_search_tree_recursive_check(TreeNode(data=1), 10, 20) - False - """ - - if node is None: - return True - - return ( - left_bound < node.data < right_bound - and is_binary_search_tree_recursive_check(node.left, left_bound, node.data) - and is_binary_search_tree_recursive_check( - node.right, node.data, right_bound - ) - ) - - return is_binary_search_tree_recursive_check(root, -float("inf"), float("inf")) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/data_structures/binary_tree/is_sorted.py b/data_structures/binary_tree/is_sorted.py new file mode 100644 index 000000000000..5876c5a9c96a --- /dev/null +++ b/data_structures/binary_tree/is_sorted.py @@ -0,0 +1,97 @@ +""" +Given the root of a binary tree, determine if it is a valid binary search tree (BST). + +A valid binary search tree is defined as follows: +- The left subtree of a node contains only nodes with keys less than the node's key. +- The right subtree of a node contains only nodes with keys greater than the node's key. +- Both the left and right subtrees must also be binary search trees. + +In effect, a binary tree is a valid BST if its nodes are sorted in ascending order. +leetcode: https://leetcode.com/problems/validate-binary-search-tree/ + +If n is the number of nodes in the tree then: +Runtime: O(n) +Space: O(1) +""" +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass + + +@dataclass +class Node: + data: float + left: Node | None = None + right: Node | None = None + + def __iter__(self) -> Iterator[float]: + """ + >>> root = Node(data=2.1) + >>> list(root) + [2.1] + >>> root.left=Node(data=2.0) + >>> list(root) + [2.0, 2.1] + >>> root.right=Node(data=2.2) + >>> list(root) + [2.0, 2.1, 2.2] + """ + if self.left: + yield from self.left + yield self.data + if self.right: + yield from self.right + + @property + def is_sorted(self) -> bool: + """ + >>> Node(data='abc').is_sorted + True + >>> Node(data=2, + ... left=Node(data=1.999), + ... right=Node(data=3)).is_sorted + True + >>> Node(data=0, + ... left=Node(data=0), + ... right=Node(data=0)).is_sorted + True + >>> Node(data=0, + ... left=Node(data=-11), + ... right=Node(data=3)).is_sorted + True + >>> Node(data=5, + ... left=Node(data=1), + ... right=Node(data=4, left=Node(data=3))).is_sorted + False + >>> Node(data='a', + ... left=Node(data=1), + ... right=Node(data=4, left=Node(data=3))).is_sorted + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + >>> Node(data=2, + ... left=Node([]), + ... right=Node(data=4, left=Node(data=3))).is_sorted + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'int' and 'list' + """ + if self.left and (self.data < self.left.data or not self.left.is_sorted): + return False + if self.right and (self.data > self.right.data or not self.right.is_sorted): + return False + return True + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + tree = Node(data=2.1, left=Node(data=2.0), right=Node(data=2.2)) + print(f"Tree {list(tree)} is sorted: {tree.is_sorted = }.") + assert tree.right + tree.right.data = 2.0 + print(f"Tree {list(tree)} is sorted: {tree.is_sorted = }.") + tree.right.data = 2.1 + print(f"Tree {list(tree)} is sorted: {tree.is_sorted = }.") From be94690decde9f0e1df78b41d2a22e7e69bc176d Mon Sep 17 00:00:00 2001 From: NikhithaBandari <91549688+NikhithaBandari@users.noreply.github.com> Date: Thu, 19 Oct 2023 23:19:47 +0530 Subject: [PATCH 2526/2908] Create swap_all_odd_and_even_bits.py (#10692) * Create swap_all_odd_and_even_bits.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update swap_all_odd_and_even_bits.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * 6: 00000110 --> 9: 00001001 * Update swap_all_odd_and_even_bits.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../swap_all_odd_and_even_bits.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 bit_manipulation/swap_all_odd_and_even_bits.py diff --git a/bit_manipulation/swap_all_odd_and_even_bits.py b/bit_manipulation/swap_all_odd_and_even_bits.py new file mode 100644 index 000000000000..5ec84417bea6 --- /dev/null +++ b/bit_manipulation/swap_all_odd_and_even_bits.py @@ -0,0 +1,58 @@ +def show_bits(before: int, after: int) -> str: + """ + >>> print(show_bits(0, 0xFFFF)) + 0: 00000000 + 65535: 1111111111111111 + """ + return f"{before:>5}: {before:08b}\n{after:>5}: {after:08b}" + + +def swap_odd_even_bits(num: int) -> int: + """ + 1. We use bitwise AND operations to separate the even bits (0, 2, 4, 6, etc.) and + odd bits (1, 3, 5, 7, etc.) in the input number. + 2. We then right-shift the even bits by 1 position and left-shift the odd bits by + 1 position to swap them. + 3. Finally, we combine the swapped even and odd bits using a bitwise OR operation + to obtain the final result. + >>> print(show_bits(0, swap_odd_even_bits(0))) + 0: 00000000 + 0: 00000000 + >>> print(show_bits(1, swap_odd_even_bits(1))) + 1: 00000001 + 2: 00000010 + >>> print(show_bits(2, swap_odd_even_bits(2))) + 2: 00000010 + 1: 00000001 + >>> print(show_bits(3, swap_odd_even_bits(3))) + 3: 00000011 + 3: 00000011 + >>> print(show_bits(4, swap_odd_even_bits(4))) + 4: 00000100 + 8: 00001000 + >>> print(show_bits(5, swap_odd_even_bits(5))) + 5: 00000101 + 10: 00001010 + >>> print(show_bits(6, swap_odd_even_bits(6))) + 6: 00000110 + 9: 00001001 + >>> print(show_bits(23, swap_odd_even_bits(23))) + 23: 00010111 + 43: 00101011 + """ + # Get all even bits - 0xAAAAAAAA is a 32-bit number with all even bits set to 1 + even_bits = num & 0xAAAAAAAA + + # Get all odd bits - 0x55555555 is a 32-bit number with all odd bits set to 1 + odd_bits = num & 0x55555555 + + # Right shift even bits and left shift odd bits and swap them + return even_bits >> 1 | odd_bits << 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + for i in (-1, 0, 1, 2, 3, 4, 23, 24): + print(show_bits(i, swap_odd_even_bits(i)), "\n") From 34f48b684bce39cb24667e5181b268c9f3bf9980 Mon Sep 17 00:00:00 2001 From: Anupamaraie <91787285+Anupamaraie@users.noreply.github.com> Date: Fri, 20 Oct 2023 01:50:16 +0545 Subject: [PATCH 2527/2908] Create vernam_cipher.py (#10702) * Create vernam_cipher.py added vernam cipher * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py added return type * Update vernam_cipher.py added type hint for plaintext and key * Update vernam_cipher.py added tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py Added tests * Update vernam_cipher.py * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * Update vernam_cipher.py * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update vernam_cipher.py * Update ciphers/vernam_cipher.py * Update vernam_cipher.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- ciphers/vernam_cipher.py | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 ciphers/vernam_cipher.py diff --git a/ciphers/vernam_cipher.py b/ciphers/vernam_cipher.py new file mode 100644 index 000000000000..197f28635a1c --- /dev/null +++ b/ciphers/vernam_cipher.py @@ -0,0 +1,42 @@ +def vernam_encrypt(plaintext: str, key: str) -> str: + """ + >>> vernam_encrypt("HELLO","KEY") + 'RIJVS' + """ + ciphertext = "" + for i in range(len(plaintext)): + ct = ord(key[i % len(key)]) - 65 + ord(plaintext[i]) - 65 + while ct > 25: + ct = ct - 26 + ciphertext += chr(65 + ct) + return ciphertext + + +def vernam_decrypt(ciphertext: str, key: str) -> str: + """ + >>> vernam_decrypt("RIJVS","KEY") + 'HELLO' + """ + decrypted_text = "" + for i in range(len(ciphertext)): + ct = ord(ciphertext[i]) - ord(key[i % len(key)]) + while ct < 0: + ct = 26 + ct + decrypted_text += chr(65 + ct) + return decrypted_text + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + + # Example usage + plaintext = "HELLO" + key = "KEY" + encrypted_text = vernam_encrypt(plaintext, key) + decrypted_text = vernam_decrypt(encrypted_text, key) + print("\n\n") + print("Plaintext:", plaintext) + print("Encrypted:", encrypted_text) + print("Decrypted:", decrypted_text) From 9875f374f4762d6219067b2e7909a762f25b68ba Mon Sep 17 00:00:00 2001 From: Adam Ross <14985050+R055A@users.noreply.github.com> Date: Thu, 19 Oct 2023 22:45:51 +0200 Subject: [PATCH 2528/2908] Consolidate bubble sort iterative and recursive (#10651) * Consolidate bubble sort iterative and recursive * Update sorts/bubble_sort.py Co-authored-by: Christian Clauss * Refactor bubble sort func signature, doctest, timer * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bubble_sort.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sorts/bubble_sort.py | 109 ++++++++++++++++++++++++++++----- sorts/recursive_bubble_sort.py | 42 ------------- 2 files changed, 92 insertions(+), 59 deletions(-) delete mode 100644 sorts/recursive_bubble_sort.py diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index 7da4362a5b97..bdf85c70dd35 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -1,7 +1,7 @@ from typing import Any -def bubble_sort(collection: list[Any]) -> list[Any]: +def bubble_sort_iterative(collection: list[Any]) -> list[Any]: """Pure implementation of bubble sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous @@ -9,25 +9,37 @@ def bubble_sort(collection: list[Any]) -> list[Any]: :return: the same collection ordered by ascending Examples: - >>> bubble_sort([0, 5, 2, 3, 2]) + >>> bubble_sort_iterative([0, 5, 2, 3, 2]) [0, 2, 2, 3, 5] - >>> bubble_sort([0, 5, 2, 3, 2]) == sorted([0, 5, 2, 3, 2]) + >>> bubble_sort_iterative([]) + [] + >>> bubble_sort_iterative([-2, -45, -5]) + [-45, -5, -2] + >>> bubble_sort_iterative([-23, 0, 6, -4, 34]) + [-23, -4, 0, 6, 34] + >>> bubble_sort_iterative([0, 5, 2, 3, 2]) == sorted([0, 5, 2, 3, 2]) True - >>> bubble_sort([]) == sorted([]) + >>> bubble_sort_iterative([]) == sorted([]) True - >>> bubble_sort([-2, -45, -5]) == sorted([-2, -45, -5]) + >>> bubble_sort_iterative([-2, -45, -5]) == sorted([-2, -45, -5]) True - >>> bubble_sort([-23, 0, 6, -4, 34]) == sorted([-23, 0, 6, -4, 34]) + >>> bubble_sort_iterative([-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']) + >>> bubble_sort_iterative(['d', 'a', 'b', 'e']) == sorted(['d', 'a', 'b', 'e']) True + >>> bubble_sort_iterative(['z', 'a', 'y', 'b', 'x', 'c']) + ['a', 'b', 'c', 'x', 'y', 'z'] + >>> bubble_sort_iterative([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] + >>> bubble_sort_iterative([1, 3.3, 5, 7.7, 2, 4.4, 6]) + [1, 2, 3.3, 4.4, 5, 6, 7.7] >>> import random - >>> collection = random.sample(range(-50, 50), 100) - >>> bubble_sort(collection) == sorted(collection) + >>> collection_arg = random.sample(range(-50, 50), 100) + >>> bubble_sort_iterative(collection_arg) == sorted(collection_arg) True >>> import string - >>> collection = random.choices(string.ascii_letters + string.digits, k=100) - >>> bubble_sort(collection) == sorted(collection) + >>> collection_arg = random.choices(string.ascii_letters + string.digits, k=100) + >>> bubble_sort_iterative(collection_arg) == sorted(collection_arg) True """ length = len(collection) @@ -42,14 +54,77 @@ def bubble_sort(collection: list[Any]) -> list[Any]: return collection +def bubble_sort_recursive(collection: list[Any]) -> list[Any]: + """It is similar iterative bubble sort but recursive. + + :param collection: mutable ordered sequence of elements + :return: the same list in ascending order + + Examples: + >>> bubble_sort_recursive([0, 5, 2, 3, 2]) + [0, 2, 2, 3, 5] + >>> bubble_sort_iterative([]) + [] + >>> bubble_sort_recursive([-2, -45, -5]) + [-45, -5, -2] + >>> bubble_sort_recursive([-23, 0, 6, -4, 34]) + [-23, -4, 0, 6, 34] + >>> bubble_sort_recursive([0, 5, 2, 3, 2]) == sorted([0, 5, 2, 3, 2]) + True + >>> bubble_sort_recursive([]) == sorted([]) + True + >>> bubble_sort_recursive([-2, -45, -5]) == sorted([-2, -45, -5]) + True + >>> bubble_sort_recursive([-23, 0, 6, -4, 34]) == sorted([-23, 0, 6, -4, 34]) + True + >>> bubble_sort_recursive(['d', 'a', 'b', 'e']) == sorted(['d', 'a', 'b', 'e']) + True + >>> bubble_sort_recursive(['z', 'a', 'y', 'b', 'x', 'c']) + ['a', 'b', 'c', 'x', 'y', 'z'] + >>> bubble_sort_recursive([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] + >>> bubble_sort_recursive([1, 3.3, 5, 7.7, 2, 4.4, 6]) + [1, 2, 3.3, 4.4, 5, 6, 7.7] + >>> import random + >>> collection_arg = random.sample(range(-50, 50), 100) + >>> bubble_sort_recursive(collection_arg) == sorted(collection_arg) + True + >>> import string + >>> collection_arg = random.choices(string.ascii_letters + string.digits, k=100) + >>> bubble_sort_recursive(collection_arg) == sorted(collection_arg) + True + """ + length = len(collection) + swapped = False + for i in range(length - 1): + if collection[i] > collection[i + 1]: + collection[i], collection[i + 1] = collection[i + 1], collection[i] + swapped = True + + return collection if not swapped else bubble_sort_recursive(collection) + + if __name__ == "__main__": import doctest - import time + from random import sample + from timeit import timeit 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() - print(*bubble_sort(unsorted), sep=",") - print(f"Processing time: {(time.process_time() - start)%1e9 + 7}") + # Benchmark: Iterative seems slightly faster than recursive. + num_runs = 10_000 + unsorted = sample(range(-50, 50), 100) + timer_iterative = timeit( + "bubble_sort_iterative(unsorted[:])", globals=globals(), number=num_runs + ) + print("\nIterative bubble sort:") + print(*bubble_sort_iterative(unsorted), sep=",") + print(f"Processing time (iterative): {timer_iterative:.5f}s for {num_runs:,} runs") + + unsorted = sample(range(-50, 50), 100) + timer_recursive = timeit( + "bubble_sort_recursive(unsorted[:])", globals=globals(), number=num_runs + ) + print("\nRecursive bubble sort:") + print(*bubble_sort_recursive(unsorted), sep=",") + print(f"Processing time (recursive): {timer_recursive:.5f}s for {num_runs:,} runs") diff --git a/sorts/recursive_bubble_sort.py b/sorts/recursive_bubble_sort.py deleted file mode 100644 index 82af89593e5b..000000000000 --- a/sorts/recursive_bubble_sort.py +++ /dev/null @@ -1,42 +0,0 @@ -def bubble_sort(list_data: list, length: int = 0) -> list: - """ - It is similar is bubble sort but recursive. - :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], 5) - [0, 2, 2, 3, 5] - - >>> bubble_sort([], 0) - [] - - >>> bubble_sort([-2, -45, -5], 3) - [-45, -5, -2] - - >>> bubble_sort([-23, 0, 6, -4, 34], 5) - [-23, -4, 0, 6, 34] - - >>> bubble_sort([-23, 0, 6, -4, 34], 5) == sorted([-23, 0, 6, -4, 34]) - True - - >>> 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 - - return list_data if not swapped else bubble_sort(list_data, length - 1) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From 51805338afbbf76c3d1371b60ba301eaaf094359 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Thu, 19 Oct 2023 23:35:38 -0400 Subject: [PATCH 2529/2908] Fix ruff error in `machine_learning/sequential_minimum_optimization.py` (#10717) * updating DIRECTORY.md * Try to fix ruff error in sequential_minimum_optimization.py * Update sequential_minimum_optimization.py * Update sequential_minimum_optimization.py * Update sequential_minimum_optimization.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 13 +++++++++++-- machine_learning/sequential_minimum_optimization.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 0999d2e8687a..1aaabf782fe3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -37,6 +37,7 @@ ## Bit Manipulation * [Binary And Operator](bit_manipulation/binary_and_operator.py) + * [Binary Coded Decimal](bit_manipulation/binary_coded_decimal.py) * [Binary Count Setbits](bit_manipulation/binary_count_setbits.py) * [Binary Count Trailing Zeros](bit_manipulation/binary_count_trailing_zeros.py) * [Binary Or Operator](bit_manipulation/binary_or_operator.py) @@ -57,6 +58,7 @@ * [Power Of 4](bit_manipulation/power_of_4.py) * [Reverse Bits](bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py) + * [Swap All Odd And Even Bits](bit_manipulation/swap_all_odd_and_even_bits.py) ## Blockchain * [Diophantine Equation](blockchain/diophantine_equation.py) @@ -124,6 +126,7 @@ * [Transposition Cipher](ciphers/transposition_cipher.py) * [Transposition Cipher Encrypt Decrypt File](ciphers/transposition_cipher_encrypt_decrypt_file.py) * [Trifid Cipher](ciphers/trifid_cipher.py) + * [Vernam Cipher](ciphers/vernam_cipher.py) * [Vigenere Cipher](ciphers/vigenere_cipher.py) * [Xor Cipher](ciphers/xor_cipher.py) @@ -163,6 +166,7 @@ * [Molecular Chemistry](conversions/molecular_chemistry.py) * [Octal To Binary](conversions/octal_to_binary.py) * [Octal To Decimal](conversions/octal_to_decimal.py) + * [Octal To Hexadecimal](conversions/octal_to_hexadecimal.py) * [Prefix Conversions](conversions/prefix_conversions.py) * [Prefix Conversions String](conversions/prefix_conversions_string.py) * [Pressure Conversions](conversions/pressure_conversions.py) @@ -183,6 +187,7 @@ * [Prefix Sum](data_structures/arrays/prefix_sum.py) * [Product Sum](data_structures/arrays/product_sum.py) * [Sparse Table](data_structures/arrays/sparse_table.py) + * [Sudoku Solver](data_structures/arrays/sudoku_solver.py) * Binary Tree * [Avl Tree](data_structures/binary_tree/avl_tree.py) * [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py) @@ -548,8 +553,10 @@ * Loss Functions * [Binary Cross Entropy](machine_learning/loss_functions/binary_cross_entropy.py) * [Categorical Cross Entropy](machine_learning/loss_functions/categorical_cross_entropy.py) + * [Hinge Loss](machine_learning/loss_functions/hinge_loss.py) * [Huber Loss](machine_learning/loss_functions/huber_loss.py) * [Mean Squared Error](machine_learning/loss_functions/mean_squared_error.py) + * [Mean Squared Logarithmic Error](machine_learning/loss_functions/mean_squared_logarithmic_error.py) * [Mfcc](machine_learning/mfcc.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polynomial Regression](machine_learning/polynomial_regression.py) @@ -734,6 +741,7 @@ * [Inverse Of Matrix](matrix/inverse_of_matrix.py) * [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py) * [Matrix Class](matrix/matrix_class.py) + * [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py) * [Matrix Operation](matrix/matrix_operation.py) * [Max Area Of Island](matrix/max_area_of_island.py) * [Median Matrix](matrix/median_matrix.py) @@ -760,10 +768,10 @@ * [Mish](neural_network/activation_functions/mish.py) * [Rectified Linear Unit](neural_network/activation_functions/rectified_linear_unit.py) * [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py) - * [Sigmoid Linear Unit](neural_network/activation_functions/sigmoid_linear_unit.py) * [Soboleva Modified Hyperbolic Tangent](neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py) * [Softplus](neural_network/activation_functions/softplus.py) * [Squareplus](neural_network/activation_functions/squareplus.py) + * [Swish](neural_network/activation_functions/swish.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Perceptron](neural_network/perceptron.py) @@ -1185,7 +1193,6 @@ * [Quick Sort](sorts/quick_sort.py) * [Quick Sort 3 Partition](sorts/quick_sort_3_partition.py) * [Radix Sort](sorts/radix_sort.py) - * [Recursive Bubble Sort](sorts/recursive_bubble_sort.py) * [Recursive Insertion Sort](sorts/recursive_insertion_sort.py) * [Recursive Mergesort Array](sorts/recursive_mergesort_array.py) * [Recursive Quick Sort](sorts/recursive_quick_sort.py) @@ -1216,12 +1223,14 @@ * [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py) * [Dna](strings/dna.py) + * [Edit Distance](strings/edit_distance.py) * [Frequency Finder](strings/frequency_finder.py) * [Hamming Distance](strings/hamming_distance.py) * [Indian Phone Validator](strings/indian_phone_validator.py) * [Is Contains Unique Chars](strings/is_contains_unique_chars.py) * [Is Isogram](strings/is_isogram.py) * [Is Pangram](strings/is_pangram.py) + * [Is Polish National Id](strings/is_polish_national_id.py) * [Is Spain National Id](strings/is_spain_national_id.py) * [Is Srilankan Phone Number](strings/is_srilankan_phone_number.py) * [Is Valid Email Address](strings/is_valid_email_address.py) diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index b24f5669e2e8..9e2304859f8d 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -454,7 +454,7 @@ def test_cancel_data(): 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( + request = urllib.request.Request( # noqa: S310 CANCER_DATASET_URL, headers={"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"}, ) From 4154428351cd60db504eb232e3b7900987a2fa19 Mon Sep 17 00:00:00 2001 From: Saptadeep Banerjee <69459134+imSanko@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:59:24 +0530 Subject: [PATCH 2530/2908] [ADD]: Improved tests in power recursion! (#10664) * Added new tests! * [ADD]: Inproved Tests * fixed * Removed spaces * Changed the file name * Added Changes * changed the code and kept the test cases * changed the code and kept the test cases * missed the line * removed spaces * Update power_using_recursion.py --------- Co-authored-by: Christian Clauss --- maths/power_using_recursion.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maths/power_using_recursion.py b/maths/power_using_recursion.py index e82635ba0005..462fc45bff64 100644 --- a/maths/power_using_recursion.py +++ b/maths/power_using_recursion.py @@ -15,6 +15,8 @@ def power(base: int, exponent: int) -> float: """ + Calculate the power of a base raised to an exponent. + >>> power(3, 4) 81 >>> power(2, 0) From 82fc24ce96036b6e1180de06c513bbaacda6a550 Mon Sep 17 00:00:00 2001 From: RaymondDashWu <33266041+RaymondDashWu@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:42:20 -0700 Subject: [PATCH 2531/2908] Test cases for check_bipartite_graph_bfs (#10688) * [ADD] tests for check_bipartite_graph_bfs * linter fix? * linter fix * [ADD] more test cases check_bipartite_graph_bfs * doctest fixes. Forgot to add 'Traceback...' * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * missed a Traceback * Update check_bipartite_graph_bfs.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- graphs/check_bipartite_graph_bfs.py | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/graphs/check_bipartite_graph_bfs.py b/graphs/check_bipartite_graph_bfs.py index 7fc57cbc78bd..6c385d54e0b6 100644 --- a/graphs/check_bipartite_graph_bfs.py +++ b/graphs/check_bipartite_graph_bfs.py @@ -10,6 +10,48 @@ def check_bipartite(graph): + """ + >>> check_bipartite({}) + True + >>> check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) + True + >>> check_bipartite({0: [1, 2, 3], 1: [0, 2], 2: [0, 1, 3], 3: [0, 2]}) + False + >>> check_bipartite({0: [4], 1: [], 2: [4], 3: [4], 4: [0, 2, 3]}) + True + >>> check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) + False + >>> check_bipartite({7: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) + Traceback (most recent call last): + ... + KeyError: 0 + >>> check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]}) + Traceback (most recent call last): + ... + KeyError: 4 + >>> check_bipartite({0: [-1, 3], 1: [0, -2]}) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> check_bipartite({-1: [0, 2], 0: [-1, 1], 1: [0, 2], 2: [-1, 1]}) + True + >>> check_bipartite({0.9: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) + Traceback (most recent call last): + ... + KeyError: 0 + >>> check_bipartite({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]}) + Traceback (most recent call last): + ... + TypeError: list indices must be integers or slices, not float + >>> check_bipartite({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]}) + Traceback (most recent call last): + ... + KeyError: 0 + >>> check_bipartite({0: ["b", "d"], 1: ["a", "c"], 2: ["b", "d"], 3: ["a", "c"]}) + Traceback (most recent call last): + ... + TypeError: list indices must be integers or slices, not str + """ queue = Queue() visited = [False] * len(graph) color = [-1] * len(graph) @@ -45,3 +87,6 @@ def bfs(): if __name__ == "__main__": # Adjacency List of graph print(check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]})) + import doctest + + doctest.testmod() From 197604898b85e84cfbaee0a0dd06095db8d1c7b6 Mon Sep 17 00:00:00 2001 From: shivaparihar6119 <122152343+shivaparihar6119@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:39:58 +0530 Subject: [PATCH 2532/2908] Concatenates both check bipatrite graphs(bfs&dfs) (#10708) * sync * fixes#8098 * deleted: graphs/check_bipartite_graph_all.py new file: graphs/check_bipatrite,py * renamed: graphs/check_bipatrite,py -> graphs/check_bipatrite.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add the new tests --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 10 -- graphs/check_bipartite_graph_bfs.py | 92 -------------- graphs/check_bipartite_graph_dfs.py | 55 --------- graphs/check_bipatrite.py | 179 ++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 157 deletions(-) delete mode 100644 graphs/check_bipartite_graph_bfs.py delete mode 100644 graphs/check_bipartite_graph_dfs.py create mode 100644 graphs/check_bipatrite.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 1aaabf782fe3..1320c70ef629 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -65,9 +65,7 @@ ## Boolean Algebra * [And Gate](boolean_algebra/and_gate.py) - * [Imply Gate](boolean_algebra/imply_gate.py) * [Nand Gate](boolean_algebra/nand_gate.py) - * [Nimply Gate](boolean_algebra/nimply_gate.py) * [Nor Gate](boolean_algebra/nor_gate.py) * [Not Gate](boolean_algebra/not_gate.py) * [Or Gate](boolean_algebra/or_gate.py) @@ -180,9 +178,7 @@ ## Data Structures * Arrays * [Equilibrium Index In Array](data_structures/arrays/equilibrium_index_in_array.py) - * [Find Triplets With 0 Sum](data_structures/arrays/find_triplets_with_0_sum.py) * [Median Two Array](data_structures/arrays/median_two_array.py) - * [Pairs With Given Sum](data_structures/arrays/pairs_with_given_sum.py) * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) * [Product Sum](data_structures/arrays/product_sum.py) @@ -402,7 +398,6 @@ ## Financial * [Equated Monthly Installments](financial/equated_monthly_installments.py) - * [Exponential Moving Average](financial/exponential_moving_average.py) * [Interest](financial/interest.py) * [Present Value](financial/present_value.py) * [Price Plus Tax](financial/price_plus_tax.py) @@ -711,7 +706,6 @@ * [Sin](maths/sin.py) * [Sock Merchant](maths/sock_merchant.py) * [Softmax](maths/softmax.py) - * [Solovay Strassen Primality Test](maths/solovay_strassen_primality_test.py) * [Square Root](maths/square_root.py) * [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py) * [Sum Of Digits](maths/sum_of_digits.py) @@ -753,7 +747,6 @@ * [Spiral Print](matrix/spiral_print.py) * Tests * [Test Matrix Operation](matrix/tests/test_matrix_operation.py) - * [Validate Sudoku Board](matrix/validate_sudoku_board.py) ## Networking Flow * [Ford Fulkerson](networking_flow/ford_fulkerson.py) @@ -829,7 +822,6 @@ * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Shear Stress](physics/shear_stress.py) * [Speed Of Sound](physics/speed_of_sound.py) - * [Speeds Of Gas Molecules](physics/speeds_of_gas_molecules.py) ## Project Euler * Problem 001 @@ -1220,7 +1212,6 @@ * [Capitalize](strings/capitalize.py) * [Check Anagrams](strings/check_anagrams.py) * [Credit Card Validator](strings/credit_card_validator.py) - * [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py) * [Dna](strings/dna.py) * [Edit Distance](strings/edit_distance.py) @@ -1255,7 +1246,6 @@ * [String Switch Case](strings/string_switch_case.py) * [Strip](strings/strip.py) * [Text Justification](strings/text_justification.py) - * [Title](strings/title.py) * [Top K Frequent Words](strings/top_k_frequent_words.py) * [Upper](strings/upper.py) * [Wave](strings/wave.py) diff --git a/graphs/check_bipartite_graph_bfs.py b/graphs/check_bipartite_graph_bfs.py deleted file mode 100644 index 6c385d54e0b6..000000000000 --- a/graphs/check_bipartite_graph_bfs.py +++ /dev/null @@ -1,92 +0,0 @@ -# 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. -from queue import Queue - - -def check_bipartite(graph): - """ - >>> check_bipartite({}) - True - >>> check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) - True - >>> check_bipartite({0: [1, 2, 3], 1: [0, 2], 2: [0, 1, 3], 3: [0, 2]}) - False - >>> check_bipartite({0: [4], 1: [], 2: [4], 3: [4], 4: [0, 2, 3]}) - True - >>> check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) - False - >>> check_bipartite({7: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) - Traceback (most recent call last): - ... - KeyError: 0 - >>> check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]}) - Traceback (most recent call last): - ... - KeyError: 4 - >>> check_bipartite({0: [-1, 3], 1: [0, -2]}) - Traceback (most recent call last): - ... - IndexError: list index out of range - >>> check_bipartite({-1: [0, 2], 0: [-1, 1], 1: [0, 2], 2: [-1, 1]}) - True - >>> check_bipartite({0.9: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) - Traceback (most recent call last): - ... - KeyError: 0 - >>> check_bipartite({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]}) - Traceback (most recent call last): - ... - TypeError: list indices must be integers or slices, not float - >>> check_bipartite({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]}) - Traceback (most recent call last): - ... - KeyError: 0 - >>> check_bipartite({0: ["b", "d"], 1: ["a", "c"], 2: ["b", "d"], 3: ["a", "c"]}) - Traceback (most recent call last): - ... - TypeError: list indices must be integers or slices, not str - """ - queue = Queue() - visited = [False] * len(graph) - color = [-1] * len(graph) - - def bfs(): - while not queue.empty(): - u = queue.get() - visited[u] = True - - for neighbour in graph[u]: - if neighbour == u: - return False - - if color[neighbour] == -1: - color[neighbour] = 1 - color[u] - queue.put(neighbour) - - elif color[neighbour] == color[u]: - return False - - return True - - for i in range(len(graph)): - if not visited[i]: - queue.put(i) - color[i] = 0 - if bfs() is False: - return False - - return True - - -if __name__ == "__main__": - # Adjacency List of graph - print(check_bipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]})) - import doctest - - doctest.testmod() diff --git a/graphs/check_bipartite_graph_dfs.py b/graphs/check_bipartite_graph_dfs.py deleted file mode 100644 index b13a9eb95afb..000000000000 --- a/graphs/check_bipartite_graph_dfs.py +++ /dev/null @@ -1,55 +0,0 @@ -from collections import defaultdict - - -def is_bipartite(graph: defaultdict[int, list[int]]) -> bool: - """ - Check whether a graph is Bipartite or not using Depth-First Search (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. There is - no edge that connects vertices of the same set. - - Args: - graph: An adjacency list representing the graph. - - Returns: - True if there's no edge that connects vertices of the same set, False otherwise. - - Examples: - >>> is_bipartite( - ... defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4], 3: [1], 4: [2]}) - ... ) - False - >>> is_bipartite(defaultdict(list, {0: [1, 2], 1: [0, 2], 2: [0, 1]})) - True - """ - - def depth_first_search(node: int, color: int) -> bool: - visited[node] = color - return any( - visited[neighbour] == color - or ( - visited[neighbour] == -1 - and not depth_first_search(neighbour, 1 - color) - ) - for neighbour in graph[node] - ) - - visited: defaultdict[int, int] = defaultdict(lambda: -1) - - return all( - not (visited[node] == -1 and not depth_first_search(node, 0)) for node in graph - ) - - -if __name__ == "__main__": - import doctest - - result = doctest.testmod() - - if result.failed: - print(f"{result.failed} test(s) failed.") - else: - print("All tests passed!") diff --git a/graphs/check_bipatrite.py b/graphs/check_bipatrite.py new file mode 100644 index 000000000000..10b9cc965251 --- /dev/null +++ b/graphs/check_bipatrite.py @@ -0,0 +1,179 @@ +from collections import defaultdict, deque + + +def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool: + """ + Check if a graph is bipartite using depth-first search (DFS). + + Args: + graph: Adjacency list representing the graph. + + Returns: + True if bipartite, False otherwise. + + Checks if the graph can be divided into two sets of vertices, such that no two + vertices within the same set are connected by an edge. + + Examples: + # FIXME: This test should pass. + >>> is_bipartite_dfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4]})) + Traceback (most recent call last): + ... + RuntimeError: dictionary changed size during iteration + >>> is_bipartite_dfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 1]})) + False + >>> is_bipartite_dfs({}) + True + >>> is_bipartite_dfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) + True + >>> is_bipartite_dfs({0: [1, 2, 3], 1: [0, 2], 2: [0, 1, 3], 3: [0, 2]}) + False + >>> is_bipartite_dfs({0: [4], 1: [], 2: [4], 3: [4], 4: [0, 2, 3]}) + True + >>> is_bipartite_dfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) + False + >>> is_bipartite_dfs({7: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) + Traceback (most recent call last): + ... + KeyError: 0 + + # FIXME: This test should fails with KeyError: 4. + >>> is_bipartite_dfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]}) + False + >>> is_bipartite_dfs({0: [-1, 3], 1: [0, -2]}) + Traceback (most recent call last): + ... + KeyError: -1 + >>> is_bipartite_dfs({-1: [0, 2], 0: [-1, 1], 1: [0, 2], 2: [-1, 1]}) + True + >>> is_bipartite_dfs({0.9: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) + Traceback (most recent call last): + ... + KeyError: 0 + + # FIXME: This test should fails with TypeError: list indices must be integers or... + >>> is_bipartite_dfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]}) + True + >>> is_bipartite_dfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]}) + Traceback (most recent call last): + ... + KeyError: 1 + >>> is_bipartite_dfs({0: ["b", "d"], 1: ["a", "c"], 2: ["b", "d"], 3: ["a", "c"]}) + Traceback (most recent call last): + ... + KeyError: 'b' + """ + + def depth_first_search(node: int, color: int) -> bool: + """ + Perform Depth-First Search (DFS) on the graph starting from a node. + + Args: + node: The current node being visited. + color: The color assigned to the current node. + + Returns: + True if the graph is bipartite starting from the current node, + False otherwise. + """ + if visited[node] == -1: + visited[node] = color + for neighbor in graph[node]: + if not depth_first_search(neighbor, 1 - color): + return False + return visited[node] == color + + visited: defaultdict[int, int] = defaultdict(lambda: -1) + for node in graph: + if visited[node] == -1 and not depth_first_search(node, 0): + return False + return True + + +def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool: + """ + Check if a graph is bipartite using a breadth-first search (BFS). + + Args: + graph: Adjacency list representing the graph. + + Returns: + True if bipartite, False otherwise. + + Check if the graph can be divided into two sets of vertices, such that no two + vertices within the same set are connected by an edge. + + Examples: + # FIXME: This test should pass. + >>> is_bipartite_bfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4]})) + Traceback (most recent call last): + ... + RuntimeError: dictionary changed size during iteration + >>> is_bipartite_bfs(defaultdict(list, {0: [1, 2], 1: [0, 2], 2: [0, 1]})) + False + >>> is_bipartite_bfs({}) + True + >>> is_bipartite_bfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) + True + >>> is_bipartite_bfs({0: [1, 2, 3], 1: [0, 2], 2: [0, 1, 3], 3: [0, 2]}) + False + >>> is_bipartite_bfs({0: [4], 1: [], 2: [4], 3: [4], 4: [0, 2, 3]}) + True + >>> is_bipartite_bfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) + False + >>> is_bipartite_bfs({7: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) + Traceback (most recent call last): + ... + KeyError: 0 + + # FIXME: This test should fails with KeyError: 4. + >>> is_bipartite_bfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]}) + False + >>> is_bipartite_bfs({0: [-1, 3], 1: [0, -2]}) + Traceback (most recent call last): + ... + KeyError: -1 + >>> is_bipartite_bfs({-1: [0, 2], 0: [-1, 1], 1: [0, 2], 2: [-1, 1]}) + True + >>> is_bipartite_bfs({0.9: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) + Traceback (most recent call last): + ... + KeyError: 0 + + # FIXME: This test should fails with TypeError: list indices must be integers or... + >>> is_bipartite_bfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]}) + True + >>> is_bipartite_bfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]}) + Traceback (most recent call last): + ... + KeyError: 1 + >>> is_bipartite_bfs({0: ["b", "d"], 1: ["a", "c"], 2: ["b", "d"], 3: ["a", "c"]}) + Traceback (most recent call last): + ... + KeyError: 'b' + """ + visited: defaultdict[int, int] = defaultdict(lambda: -1) + for node in graph: + if visited[node] == -1: + queue: deque[int] = deque() + queue.append(node) + visited[node] = 0 + while queue: + curr_node = queue.popleft() + for neighbor in graph[curr_node]: + if visited[neighbor] == -1: + visited[neighbor] = 1 - visited[curr_node] + queue.append(neighbor) + elif visited[neighbor] == visited[curr_node]: + return False + return True + + +if __name__ == "__main": + import doctest + + result = doctest.testmod() + if result.failed: + print(f"{result.failed} test(s) failed.") + else: + print("All tests passed!") From 6f2d6f72d56f832dcfaaf226688c1dab4cdb9d0e Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 20 Oct 2023 02:17:31 -0400 Subject: [PATCH 2533/2908] Move files for special numbers to own directory (#10714) --- .../armstrong_numbers.py | 196 +++++------ .../automorphic_number.py | 0 maths/{ => special_numbers}/bell_numbers.py | 0 .../carmichael_number.py | 0 maths/{ => special_numbers}/catalan_number.py | 0 .../{ => special_numbers}/hamming_numbers.py | 0 .../{ => special_numbers}/harshad_numbers.py | 316 +++++++++--------- .../{ => special_numbers}/hexagonal_number.py | 0 .../krishnamurthy_number.py | 0 maths/{ => special_numbers}/perfect_number.py | 0 .../polygonal_numbers.py | 0 maths/{ => special_numbers}/pronic_number.py | 0 maths/{ => special_numbers}/proth_number.py | 0 maths/{ => special_numbers}/ugly_numbers.py | 108 +++--- maths/{ => special_numbers}/weird_number.py | 0 15 files changed, 310 insertions(+), 310 deletions(-) rename maths/{ => special_numbers}/armstrong_numbers.py (96%) rename maths/{ => special_numbers}/automorphic_number.py (100%) rename maths/{ => special_numbers}/bell_numbers.py (100%) rename maths/{ => special_numbers}/carmichael_number.py (100%) rename maths/{ => special_numbers}/catalan_number.py (100%) rename maths/{ => special_numbers}/hamming_numbers.py (100%) rename maths/{ => special_numbers}/harshad_numbers.py (96%) rename maths/{ => special_numbers}/hexagonal_number.py (100%) rename maths/{ => special_numbers}/krishnamurthy_number.py (100%) rename maths/{ => special_numbers}/perfect_number.py (100%) rename maths/{ => special_numbers}/polygonal_numbers.py (100%) rename maths/{ => special_numbers}/pronic_number.py (100%) rename maths/{ => special_numbers}/proth_number.py (100%) rename maths/{ => special_numbers}/ugly_numbers.py (96%) rename maths/{ => special_numbers}/weird_number.py (100%) diff --git a/maths/armstrong_numbers.py b/maths/special_numbers/armstrong_numbers.py similarity index 96% rename from maths/armstrong_numbers.py rename to maths/special_numbers/armstrong_numbers.py index e1c25d4676c3..b037aacb16c3 100644 --- a/maths/armstrong_numbers.py +++ b/maths/special_numbers/armstrong_numbers.py @@ -1,98 +1,98 @@ -""" -An Armstrong number is equal to the sum of its own digits each raised to the -power of the number of digits. - -For example, 370 is an Armstrong number because 3*3*3 + 7*7*7 + 0*0*0 = 370. - -Armstrong numbers are also called Narcissistic numbers and Pluperfect numbers. - -On-Line Encyclopedia of Integer Sequences entry: https://oeis.org/A005188 -""" -PASSING = (1, 153, 370, 371, 1634, 24678051, 115132219018763992565095597973971522401) -FAILING: tuple = (-153, -1, 0, 1.2, 200, "A", [], {}, None) - - -def armstrong_number(n: int) -> bool: - """ - Return True if n is an Armstrong number or False if it is not. - - >>> all(armstrong_number(n) for n in PASSING) - True - >>> any(armstrong_number(n) for n in FAILING) - False - """ - if not isinstance(n, int) or n < 1: - return False - - # Initialization of sum and number of digits. - total = 0 - number_of_digits = 0 - temp = n - # Calculation of digits of the number - number_of_digits = len(str(n)) - # Dividing number into separate digits and find Armstrong number - temp = n - while temp > 0: - rem = temp % 10 - total += rem**number_of_digits - temp //= 10 - return n == total - - -def pluperfect_number(n: int) -> bool: - """Return True if n is a pluperfect number or False if it is not - - >>> all(armstrong_number(n) for n in PASSING) - True - >>> any(armstrong_number(n) for n in FAILING) - False - """ - if not isinstance(n, int) or n < 1: - return False - - # Init a "histogram" of the digits - digit_histogram = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - digit_total = 0 - total = 0 - temp = n - while temp > 0: - temp, rem = divmod(temp, 10) - digit_histogram[rem] += 1 - digit_total += 1 - - for cnt, i in zip(digit_histogram, range(len(digit_histogram))): - total += cnt * i**digit_total - - return n == total - - -def narcissistic_number(n: int) -> bool: - """Return True if n is a narcissistic number or False if it is not. - - >>> all(armstrong_number(n) for n in PASSING) - True - >>> any(armstrong_number(n) for n in FAILING) - False - """ - if not isinstance(n, int) or n < 1: - return False - expo = len(str(n)) # the power that all digits will be raised to - # check if sum of each digit multiplied expo times is equal to number - return n == sum(int(i) ** expo for i in str(n)) - - -def main(): - """ - 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.") - print(f"{num} is {'' if narcissistic_number(num) else 'not '}an Armstrong number.") - print(f"{num} is {'' if pluperfect_number(num) else 'not '}an Armstrong number.") - - -if __name__ == "__main__": - import doctest - - doctest.testmod() - main() +""" +An Armstrong number is equal to the sum of its own digits each raised to the +power of the number of digits. + +For example, 370 is an Armstrong number because 3*3*3 + 7*7*7 + 0*0*0 = 370. + +Armstrong numbers are also called Narcissistic numbers and Pluperfect numbers. + +On-Line Encyclopedia of Integer Sequences entry: https://oeis.org/A005188 +""" +PASSING = (1, 153, 370, 371, 1634, 24678051, 115132219018763992565095597973971522401) +FAILING: tuple = (-153, -1, 0, 1.2, 200, "A", [], {}, None) + + +def armstrong_number(n: int) -> bool: + """ + Return True if n is an Armstrong number or False if it is not. + + >>> all(armstrong_number(n) for n in PASSING) + True + >>> any(armstrong_number(n) for n in FAILING) + False + """ + if not isinstance(n, int) or n < 1: + return False + + # Initialization of sum and number of digits. + total = 0 + number_of_digits = 0 + temp = n + # Calculation of digits of the number + number_of_digits = len(str(n)) + # Dividing number into separate digits and find Armstrong number + temp = n + while temp > 0: + rem = temp % 10 + total += rem**number_of_digits + temp //= 10 + return n == total + + +def pluperfect_number(n: int) -> bool: + """Return True if n is a pluperfect number or False if it is not + + >>> all(armstrong_number(n) for n in PASSING) + True + >>> any(armstrong_number(n) for n in FAILING) + False + """ + if not isinstance(n, int) or n < 1: + return False + + # Init a "histogram" of the digits + digit_histogram = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + digit_total = 0 + total = 0 + temp = n + while temp > 0: + temp, rem = divmod(temp, 10) + digit_histogram[rem] += 1 + digit_total += 1 + + for cnt, i in zip(digit_histogram, range(len(digit_histogram))): + total += cnt * i**digit_total + + return n == total + + +def narcissistic_number(n: int) -> bool: + """Return True if n is a narcissistic number or False if it is not. + + >>> all(armstrong_number(n) for n in PASSING) + True + >>> any(armstrong_number(n) for n in FAILING) + False + """ + if not isinstance(n, int) or n < 1: + return False + expo = len(str(n)) # the power that all digits will be raised to + # check if sum of each digit multiplied expo times is equal to number + return n == sum(int(i) ** expo for i in str(n)) + + +def main(): + """ + 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.") + print(f"{num} is {'' if narcissistic_number(num) else 'not '}an Armstrong number.") + print(f"{num} is {'' if pluperfect_number(num) else 'not '}an Armstrong number.") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() diff --git a/maths/automorphic_number.py b/maths/special_numbers/automorphic_number.py similarity index 100% rename from maths/automorphic_number.py rename to maths/special_numbers/automorphic_number.py diff --git a/maths/bell_numbers.py b/maths/special_numbers/bell_numbers.py similarity index 100% rename from maths/bell_numbers.py rename to maths/special_numbers/bell_numbers.py diff --git a/maths/carmichael_number.py b/maths/special_numbers/carmichael_number.py similarity index 100% rename from maths/carmichael_number.py rename to maths/special_numbers/carmichael_number.py diff --git a/maths/catalan_number.py b/maths/special_numbers/catalan_number.py similarity index 100% rename from maths/catalan_number.py rename to maths/special_numbers/catalan_number.py diff --git a/maths/hamming_numbers.py b/maths/special_numbers/hamming_numbers.py similarity index 100% rename from maths/hamming_numbers.py rename to maths/special_numbers/hamming_numbers.py diff --git a/maths/harshad_numbers.py b/maths/special_numbers/harshad_numbers.py similarity index 96% rename from maths/harshad_numbers.py rename to maths/special_numbers/harshad_numbers.py index 050c69e0bd15..61667adfa127 100644 --- a/maths/harshad_numbers.py +++ b/maths/special_numbers/harshad_numbers.py @@ -1,158 +1,158 @@ -""" -A harshad number (or more specifically an n-harshad number) is a number that's -divisible by the sum of its digits in some given base n. -Reference: https://en.wikipedia.org/wiki/Harshad_number -""" - - -def int_to_base(number: int, base: int) -> str: - """ - Convert a given positive decimal integer to base 'base'. - Where 'base' ranges from 2 to 36. - - Examples: - >>> int_to_base(23, 2) - '10111' - >>> int_to_base(58, 5) - '213' - >>> int_to_base(167, 16) - 'A7' - >>> # bases below 2 and beyond 36 will error - >>> int_to_base(98, 1) - Traceback (most recent call last): - ... - ValueError: 'base' must be between 2 and 36 inclusive - >>> int_to_base(98, 37) - Traceback (most recent call last): - ... - ValueError: 'base' must be between 2 and 36 inclusive - """ - - if base < 2 or base > 36: - raise ValueError("'base' must be between 2 and 36 inclusive") - - digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - result = "" - - if number < 0: - raise ValueError("number must be a positive integer") - - while number > 0: - number, remainder = divmod(number, base) - result = digits[remainder] + result - - if result == "": - result = "0" - - return result - - -def sum_of_digits(num: int, base: int) -> str: - """ - Calculate the sum of digit values in a positive integer - converted to the given 'base'. - Where 'base' ranges from 2 to 36. - - Examples: - >>> sum_of_digits(103, 12) - '13' - >>> sum_of_digits(1275, 4) - '30' - >>> sum_of_digits(6645, 2) - '1001' - >>> # bases below 2 and beyond 36 will error - >>> sum_of_digits(543, 1) - Traceback (most recent call last): - ... - ValueError: 'base' must be between 2 and 36 inclusive - >>> sum_of_digits(543, 37) - Traceback (most recent call last): - ... - ValueError: 'base' must be between 2 and 36 inclusive - """ - - if base < 2 or base > 36: - raise ValueError("'base' must be between 2 and 36 inclusive") - - num_str = int_to_base(num, base) - res = sum(int(char, base) for char in num_str) - res_str = int_to_base(res, base) - return res_str - - -def harshad_numbers_in_base(limit: int, base: int) -> list[str]: - """ - Finds all Harshad numbers smaller than num in base 'base'. - Where 'base' ranges from 2 to 36. - - Examples: - >>> harshad_numbers_in_base(15, 2) - ['1', '10', '100', '110', '1000', '1010', '1100'] - >>> harshad_numbers_in_base(12, 34) - ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B'] - >>> harshad_numbers_in_base(12, 4) - ['1', '2', '3', '10', '12', '20', '21'] - >>> # bases below 2 and beyond 36 will error - >>> harshad_numbers_in_base(234, 37) - Traceback (most recent call last): - ... - ValueError: 'base' must be between 2 and 36 inclusive - >>> harshad_numbers_in_base(234, 1) - Traceback (most recent call last): - ... - ValueError: 'base' must be between 2 and 36 inclusive - """ - - if base < 2 or base > 36: - raise ValueError("'base' must be between 2 and 36 inclusive") - - if limit < 0: - return [] - - numbers = [ - int_to_base(i, base) - for i in range(1, limit) - if i % int(sum_of_digits(i, base), base) == 0 - ] - - return numbers - - -def is_harshad_number_in_base(num: int, base: int) -> bool: - """ - Determines whether n in base 'base' is a harshad number. - Where 'base' ranges from 2 to 36. - - Examples: - >>> is_harshad_number_in_base(18, 10) - True - >>> is_harshad_number_in_base(21, 10) - True - >>> is_harshad_number_in_base(-21, 5) - False - >>> # bases below 2 and beyond 36 will error - >>> is_harshad_number_in_base(45, 37) - Traceback (most recent call last): - ... - ValueError: 'base' must be between 2 and 36 inclusive - >>> is_harshad_number_in_base(45, 1) - Traceback (most recent call last): - ... - ValueError: 'base' must be between 2 and 36 inclusive - """ - - if base < 2 or base > 36: - raise ValueError("'base' must be between 2 and 36 inclusive") - - if num < 0: - return False - - n = int_to_base(num, base) - d = sum_of_digits(num, base) - return int(n, base) % int(d, base) == 0 - - -if __name__ == "__main__": - import doctest - - doctest.testmod() +""" +A harshad number (or more specifically an n-harshad number) is a number that's +divisible by the sum of its digits in some given base n. +Reference: https://en.wikipedia.org/wiki/Harshad_number +""" + + +def int_to_base(number: int, base: int) -> str: + """ + Convert a given positive decimal integer to base 'base'. + Where 'base' ranges from 2 to 36. + + Examples: + >>> int_to_base(23, 2) + '10111' + >>> int_to_base(58, 5) + '213' + >>> int_to_base(167, 16) + 'A7' + >>> # bases below 2 and beyond 36 will error + >>> int_to_base(98, 1) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + >>> int_to_base(98, 37) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + """ + + if base < 2 or base > 36: + raise ValueError("'base' must be between 2 and 36 inclusive") + + digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + result = "" + + if number < 0: + raise ValueError("number must be a positive integer") + + while number > 0: + number, remainder = divmod(number, base) + result = digits[remainder] + result + + if result == "": + result = "0" + + return result + + +def sum_of_digits(num: int, base: int) -> str: + """ + Calculate the sum of digit values in a positive integer + converted to the given 'base'. + Where 'base' ranges from 2 to 36. + + Examples: + >>> sum_of_digits(103, 12) + '13' + >>> sum_of_digits(1275, 4) + '30' + >>> sum_of_digits(6645, 2) + '1001' + >>> # bases below 2 and beyond 36 will error + >>> sum_of_digits(543, 1) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + >>> sum_of_digits(543, 37) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + """ + + if base < 2 or base > 36: + raise ValueError("'base' must be between 2 and 36 inclusive") + + num_str = int_to_base(num, base) + res = sum(int(char, base) for char in num_str) + res_str = int_to_base(res, base) + return res_str + + +def harshad_numbers_in_base(limit: int, base: int) -> list[str]: + """ + Finds all Harshad numbers smaller than num in base 'base'. + Where 'base' ranges from 2 to 36. + + Examples: + >>> harshad_numbers_in_base(15, 2) + ['1', '10', '100', '110', '1000', '1010', '1100'] + >>> harshad_numbers_in_base(12, 34) + ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B'] + >>> harshad_numbers_in_base(12, 4) + ['1', '2', '3', '10', '12', '20', '21'] + >>> # bases below 2 and beyond 36 will error + >>> harshad_numbers_in_base(234, 37) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + >>> harshad_numbers_in_base(234, 1) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + """ + + if base < 2 or base > 36: + raise ValueError("'base' must be between 2 and 36 inclusive") + + if limit < 0: + return [] + + numbers = [ + int_to_base(i, base) + for i in range(1, limit) + if i % int(sum_of_digits(i, base), base) == 0 + ] + + return numbers + + +def is_harshad_number_in_base(num: int, base: int) -> bool: + """ + Determines whether n in base 'base' is a harshad number. + Where 'base' ranges from 2 to 36. + + Examples: + >>> is_harshad_number_in_base(18, 10) + True + >>> is_harshad_number_in_base(21, 10) + True + >>> is_harshad_number_in_base(-21, 5) + False + >>> # bases below 2 and beyond 36 will error + >>> is_harshad_number_in_base(45, 37) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + >>> is_harshad_number_in_base(45, 1) + Traceback (most recent call last): + ... + ValueError: 'base' must be between 2 and 36 inclusive + """ + + if base < 2 or base > 36: + raise ValueError("'base' must be between 2 and 36 inclusive") + + if num < 0: + return False + + n = int_to_base(num, base) + d = sum_of_digits(num, base) + return int(n, base) % int(d, base) == 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/hexagonal_number.py b/maths/special_numbers/hexagonal_number.py similarity index 100% rename from maths/hexagonal_number.py rename to maths/special_numbers/hexagonal_number.py diff --git a/maths/krishnamurthy_number.py b/maths/special_numbers/krishnamurthy_number.py similarity index 100% rename from maths/krishnamurthy_number.py rename to maths/special_numbers/krishnamurthy_number.py diff --git a/maths/perfect_number.py b/maths/special_numbers/perfect_number.py similarity index 100% rename from maths/perfect_number.py rename to maths/special_numbers/perfect_number.py diff --git a/maths/polygonal_numbers.py b/maths/special_numbers/polygonal_numbers.py similarity index 100% rename from maths/polygonal_numbers.py rename to maths/special_numbers/polygonal_numbers.py diff --git a/maths/pronic_number.py b/maths/special_numbers/pronic_number.py similarity index 100% rename from maths/pronic_number.py rename to maths/special_numbers/pronic_number.py diff --git a/maths/proth_number.py b/maths/special_numbers/proth_number.py similarity index 100% rename from maths/proth_number.py rename to maths/special_numbers/proth_number.py diff --git a/maths/ugly_numbers.py b/maths/special_numbers/ugly_numbers.py similarity index 96% rename from maths/ugly_numbers.py rename to maths/special_numbers/ugly_numbers.py index 81bd928c6b3d..c6ceb784622a 100644 --- a/maths/ugly_numbers.py +++ b/maths/special_numbers/ugly_numbers.py @@ -1,54 +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 _ 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) = }") +""" +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 _ 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) = }") diff --git a/maths/weird_number.py b/maths/special_numbers/weird_number.py similarity index 100% rename from maths/weird_number.py rename to maths/special_numbers/weird_number.py From ce0ede6476fb14ba18ef03246b169a7e5615bdec Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 20 Oct 2023 03:08:23 -0400 Subject: [PATCH 2534/2908] Fix typo in DPLL file name (#10723) * Fix DPLL file name * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 46 +++++++++++-------- ...d.py => davis_putnam_logemann_loveland.py} | 0 2 files changed, 28 insertions(+), 18 deletions(-) rename other/{davisb_putnamb_logemannb_loveland.py => davis_putnam_logemann_loveland.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 1320c70ef629..5b7ca856ea15 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -65,7 +65,9 @@ ## Boolean Algebra * [And Gate](boolean_algebra/and_gate.py) + * [Imply Gate](boolean_algebra/imply_gate.py) * [Nand Gate](boolean_algebra/nand_gate.py) + * [Nimply Gate](boolean_algebra/nimply_gate.py) * [Nor Gate](boolean_algebra/nor_gate.py) * [Not Gate](boolean_algebra/not_gate.py) * [Or Gate](boolean_algebra/or_gate.py) @@ -178,7 +180,9 @@ ## Data Structures * Arrays * [Equilibrium Index In Array](data_structures/arrays/equilibrium_index_in_array.py) + * [Find Triplets With 0 Sum](data_structures/arrays/find_triplets_with_0_sum.py) * [Median Two Array](data_structures/arrays/median_two_array.py) + * [Pairs With Given Sum](data_structures/arrays/pairs_with_given_sum.py) * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) * [Product Sum](data_structures/arrays/product_sum.py) @@ -398,6 +402,7 @@ ## Financial * [Equated Monthly Installments](financial/equated_monthly_installments.py) + * [Exponential Moving Average](financial/exponential_moving_average.py) * [Interest](financial/interest.py) * [Present Value](financial/present_value.py) * [Price Plus Tax](financial/price_plus_tax.py) @@ -433,8 +438,7 @@ * [Breadth First Search Shortest Path](graphs/breadth_first_search_shortest_path.py) * [Breadth First Search Shortest Path 2](graphs/breadth_first_search_shortest_path_2.py) * [Breadth First Search Zero One Shortest Path](graphs/breadth_first_search_zero_one_shortest_path.py) - * [Check Bipartite Graph Bfs](graphs/check_bipartite_graph_bfs.py) - * [Check Bipartite Graph Dfs](graphs/check_bipartite_graph_dfs.py) + * [Check Bipatrite](graphs/check_bipatrite.py) * [Check Cycle](graphs/check_cycle.py) * [Connected Components](graphs/connected_components.py) * [Deep Clone Graph](graphs/deep_clone_graph.py) @@ -572,8 +576,6 @@ * [Arc Length](maths/arc_length.py) * [Area](maths/area.py) * [Area Under Curve](maths/area_under_curve.py) - * [Armstrong Numbers](maths/armstrong_numbers.py) - * [Automorphic Number](maths/automorphic_number.py) * [Average Absolute Deviation](maths/average_absolute_deviation.py) * [Average Mean](maths/average_mean.py) * [Average Median](maths/average_median.py) @@ -581,7 +583,6 @@ * [Bailey Borwein Plouffe](maths/bailey_borwein_plouffe.py) * [Base Neg2 Conversion](maths/base_neg2_conversion.py) * [Basic Maths](maths/basic_maths.py) - * [Bell Numbers](maths/bell_numbers.py) * [Binary Exp Mod](maths/binary_exp_mod.py) * [Binary Exponentiation](maths/binary_exponentiation.py) * [Binary Exponentiation 2](maths/binary_exponentiation_2.py) @@ -589,8 +590,6 @@ * [Binomial Coefficient](maths/binomial_coefficient.py) * [Binomial Distribution](maths/binomial_distribution.py) * [Bisection](maths/bisection.py) - * [Carmichael Number](maths/carmichael_number.py) - * [Catalan Number](maths/catalan_number.py) * [Ceil](maths/ceil.py) * [Chebyshev Distance](maths/chebyshev_distance.py) * [Check Polygon](maths/check_polygon.py) @@ -623,10 +622,7 @@ * [Gcd Of N Numbers](maths/gcd_of_n_numbers.py) * [Germain Primes](maths/germain_primes.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) - * [Hamming Numbers](maths/hamming_numbers.py) * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) - * [Harshad Numbers](maths/harshad_numbers.py) - * [Hexagonal Number](maths/hexagonal_number.py) * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) * [Interquartile Range](maths/interquartile_range.py) * [Is Int Palindrome](maths/is_int_palindrome.py) @@ -636,7 +632,6 @@ * [Joint Probability Distribution](maths/joint_probability_distribution.py) * [Juggler Sequence](maths/juggler_sequence.py) * [Karatsuba](maths/karatsuba.py) - * [Krishnamurthy Number](maths/krishnamurthy_number.py) * [Kth Lexicographic Permutation](maths/kth_lexicographic_permutation.py) * [Largest Of Very Large Numbers](maths/largest_of_very_large_numbers.py) * [Least Common Multiple](maths/least_common_multiple.py) @@ -661,14 +656,12 @@ * [Numerical Integration](maths/numerical_integration.py) * [Odd Sieve](maths/odd_sieve.py) * [Perfect Cube](maths/perfect_cube.py) - * [Perfect Number](maths/perfect_number.py) * [Perfect Square](maths/perfect_square.py) * [Persistence](maths/persistence.py) * [Pi Generator](maths/pi_generator.py) * [Pi Monte Carlo Estimation](maths/pi_monte_carlo_estimation.py) * [Points Are Collinear 3D](maths/points_are_collinear_3d.py) * [Pollard Rho](maths/pollard_rho.py) - * [Polygonal Numbers](maths/polygonal_numbers.py) * [Polynomial Evaluation](maths/polynomial_evaluation.py) * Polynomials * [Single Indeterminate Operations](maths/polynomials/single_indeterminate_operations.py) @@ -679,8 +672,6 @@ * [Prime Sieve Eratosthenes](maths/prime_sieve_eratosthenes.py) * [Primelib](maths/primelib.py) * [Print Multiplication Table](maths/print_multiplication_table.py) - * [Pronic Number](maths/pronic_number.py) - * [Proth Number](maths/proth_number.py) * [Pythagoras](maths/pythagoras.py) * [Qr Decomposition](maths/qr_decomposition.py) * [Quadratic Equations Complex Numbers](maths/quadratic_equations_complex_numbers.py) @@ -706,6 +697,23 @@ * [Sin](maths/sin.py) * [Sock Merchant](maths/sock_merchant.py) * [Softmax](maths/softmax.py) + * [Solovay Strassen Primality Test](maths/solovay_strassen_primality_test.py) + * Special Numbers + * [Armstrong Numbers](maths/special_numbers/armstrong_numbers.py) + * [Automorphic Number](maths/special_numbers/automorphic_number.py) + * [Bell Numbers](maths/special_numbers/bell_numbers.py) + * [Carmichael Number](maths/special_numbers/carmichael_number.py) + * [Catalan Number](maths/special_numbers/catalan_number.py) + * [Hamming Numbers](maths/special_numbers/hamming_numbers.py) + * [Harshad Numbers](maths/special_numbers/harshad_numbers.py) + * [Hexagonal Number](maths/special_numbers/hexagonal_number.py) + * [Krishnamurthy Number](maths/special_numbers/krishnamurthy_number.py) + * [Perfect Number](maths/special_numbers/perfect_number.py) + * [Polygonal Numbers](maths/special_numbers/polygonal_numbers.py) + * [Pronic Number](maths/special_numbers/pronic_number.py) + * [Proth Number](maths/special_numbers/proth_number.py) + * [Ugly Numbers](maths/special_numbers/ugly_numbers.py) + * [Weird Number](maths/special_numbers/weird_number.py) * [Square Root](maths/square_root.py) * [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py) * [Sum Of Digits](maths/sum_of_digits.py) @@ -721,9 +729,7 @@ * [Twin Prime](maths/twin_prime.py) * [Two Pointer](maths/two_pointer.py) * [Two Sum](maths/two_sum.py) - * [Ugly Numbers](maths/ugly_numbers.py) * [Volume](maths/volume.py) - * [Weird Number](maths/weird_number.py) * [Zellers Congruence](maths/zellers_congruence.py) ## Matrix @@ -747,6 +753,7 @@ * [Spiral Print](matrix/spiral_print.py) * Tests * [Test Matrix Operation](matrix/tests/test_matrix_operation.py) + * [Validate Sudoku Board](matrix/validate_sudoku_board.py) ## Networking Flow * [Ford Fulkerson](networking_flow/ford_fulkerson.py) @@ -773,7 +780,7 @@ ## Other * [Activity Selection](other/activity_selection.py) * [Alternative List Arrange](other/alternative_list_arrange.py) - * [Davisb Putnamb Logemannb Loveland](other/davisb_putnamb_logemannb_loveland.py) + * [Davis Putnam Logemann Loveland](other/davis_putnam_logemann_loveland.py) * [Dijkstra Bankers Algorithm](other/dijkstra_bankers_algorithm.py) * [Doomsday](other/doomsday.py) * [Fischer Yates Shuffle](other/fischer_yates_shuffle.py) @@ -822,6 +829,7 @@ * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Shear Stress](physics/shear_stress.py) * [Speed Of Sound](physics/speed_of_sound.py) + * [Speeds Of Gas Molecules](physics/speeds_of_gas_molecules.py) ## Project Euler * Problem 001 @@ -1212,6 +1220,7 @@ * [Capitalize](strings/capitalize.py) * [Check Anagrams](strings/check_anagrams.py) * [Credit Card Validator](strings/credit_card_validator.py) + * [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py) * [Dna](strings/dna.py) * [Edit Distance](strings/edit_distance.py) @@ -1246,6 +1255,7 @@ * [String Switch Case](strings/string_switch_case.py) * [Strip](strings/strip.py) * [Text Justification](strings/text_justification.py) + * [Title](strings/title.py) * [Top K Frequent Words](strings/top_k_frequent_words.py) * [Upper](strings/upper.py) * [Wave](strings/wave.py) diff --git a/other/davisb_putnamb_logemannb_loveland.py b/other/davis_putnam_logemann_loveland.py similarity index 100% rename from other/davisb_putnamb_logemannb_loveland.py rename to other/davis_putnam_logemann_loveland.py From 579937613a6dc7e099b710e3d57767a2fab115ad Mon Sep 17 00:00:00 2001 From: Saptadeep Banerjee <69459134+imSanko@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:32:30 +0530 Subject: [PATCH 2535/2908] Added New Tests in Signum (#10724) * Added new tests! * [ADD]: Inproved Tests * fixed * Removed spaces * Changed the file name * Added Changes * changed the code and kept the test cases * changed the code and kept the test cases * missed the line * removed spaces * Update power_using_recursion.py * Added new tests in Signum * Few things added * Removed few stuff and added few changes * Fixed few things * Reverted the function * Update maths/signum.py Co-authored-by: Christian Clauss * Added few things * Update maths/signum.py Co-authored-by: Christian Clauss * Added the type hint back * Update signum.py --------- Co-authored-by: Christian Clauss --- maths/signum.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/maths/signum.py b/maths/signum.py index 148f931767c1..c89753e76637 100644 --- a/maths/signum.py +++ b/maths/signum.py @@ -7,12 +7,29 @@ def signum(num: float) -> int: """ Applies signum function on the number + Custom test cases: >>> signum(-10) -1 >>> signum(10) 1 >>> signum(0) 0 + >>> signum(-20.5) + -1 + >>> signum(20.5) + 1 + >>> signum(-1e-6) + -1 + >>> signum(1e-6) + 1 + >>> signum("Hello") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + >>> signum([]) + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'list' and 'int' """ if num < 0: return -1 @@ -22,10 +39,17 @@ def signum(num: float) -> int: def test_signum() -> None: """ Tests the signum function + >>> test_signum() """ assert signum(5) == 1 assert signum(-5) == -1 assert signum(0) == 0 + assert signum(10.5) == 1 + assert signum(-10.5) == -1 + assert signum(1e-6) == 1 + assert signum(-1e-6) == -1 + assert signum(123456789) == 1 + assert signum(-123456789) == -1 if __name__ == "__main__": From 52a987ea2f299c8215c1107b8dd793919c962f10 Mon Sep 17 00:00:00 2001 From: Ope Oluwaferanmi <111365699+FEROS01@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:28:21 +0100 Subject: [PATCH 2536/2908] Add docstrings and doctests and fix a bug ciphers/trifid_cipher.py (#10716) * Added docstrings,doctests and fixed a bug * Added docstrings,doctests and fixed a bug * Added docstrings,doctests and fixed a bug * Added docstrings and doctests with a bug fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added docstrings and doctests with a bug fix * Update ciphers/trifid_cipher.py Co-authored-by: Christian Clauss * Update ciphers/trifid_cipher.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Docstrings edit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update trifid_cipher.py * Update pyproject.toml --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- ciphers/trifid_cipher.py | 191 +++++++++++++++++++++++++++------------ pyproject.toml | 2 +- 2 files changed, 134 insertions(+), 59 deletions(-) diff --git a/ciphers/trifid_cipher.py b/ciphers/trifid_cipher.py index 8aa2263ca5ac..16b9faf67688 100644 --- a/ciphers/trifid_cipher.py +++ b/ciphers/trifid_cipher.py @@ -1,15 +1,35 @@ -# https://en.wikipedia.org/wiki/Trifid_cipher +""" +The trifid cipher uses a table to fractionate each plaintext letter into a trigram, +mixes the constituents of the trigrams, and then applies the table in reverse to turn +these mixed trigrams into ciphertext letters. + +https://en.wikipedia.org/wiki/Trifid_cipher +""" + from __future__ import annotations +# fmt: off +TEST_CHARACTER_TO_NUMBER = { + "A": "111", "B": "112", "C": "113", "D": "121", "E": "122", "F": "123", "G": "131", + "H": "132", "I": "133", "J": "211", "K": "212", "L": "213", "M": "221", "N": "222", + "O": "223", "P": "231", "Q": "232", "R": "233", "S": "311", "T": "312", "U": "313", + "V": "321", "W": "322", "X": "323", "Y": "331", "Z": "332", "+": "333", +} +# fmt: off -def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str: - one, two, three = "", "", "" - tmp = [] +TEST_NUMBER_TO_CHARACTER = {val: key for key, val in TEST_CHARACTER_TO_NUMBER.items()} - for character in message_part: - tmp.append(character_to_number[character]) - for each in tmp: +def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str: + """ + Arrange the triagram value of each letter of 'message_part' vertically and join + them horizontally. + + >>> __encrypt_part('ASK', TEST_CHARACTER_TO_NUMBER) + '132111112' + """ + one, two, three = "", "", "" + for each in (character_to_number[character] for character in message_part): one += each[0] two += each[1] three += each[2] @@ -20,12 +40,16 @@ def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> st def __decrypt_part( message_part: str, character_to_number: dict[str, str] ) -> tuple[str, str, str]: - tmp, this_part = "", "" + """ + Convert each letter of the input string into their respective trigram values, join + them and split them into three equal groups of strings which are returned. + + >>> __decrypt_part('ABCDE', TEST_CHARACTER_TO_NUMBER) + ('11111', '21131', '21122') + """ + this_part = "".join(character_to_number[character] for character in message_part) result = [] - - for character in message_part: - this_part += character_to_number[character] - + tmp = "" for digit in this_part: tmp += digit if len(tmp) == len(message_part): @@ -38,6 +62,42 @@ def __decrypt_part( def __prepare( message: str, alphabet: str ) -> tuple[str, str, dict[str, str], dict[str, str]]: + """ + A helper function that generates the triagrams and assigns each letter of the + alphabet to its corresponding triagram and stores this in a dictionary + ("character_to_number" and "number_to_character") after confirming if the + alphabet's length is 27. + + >>> test = __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxYZ+') + >>> expected = ('IAMABOY','ABCDEFGHIJKLMNOPQRSTUVWXYZ+', + ... TEST_CHARACTER_TO_NUMBER, TEST_NUMBER_TO_CHARACTER) + >>> test == expected + True + + Testing with incomplete alphabet + >>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVw') + Traceback (most recent call last): + ... + KeyError: 'Length of alphabet has to be 27.' + + Testing with extra long alphabets + >>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxyzzwwtyyujjgfd') + Traceback (most recent call last): + ... + KeyError: 'Length of alphabet has to be 27.' + + Testing with punctuations that are not in the given alphabet + >>> __prepare('am i a boy?','abCdeFghijkLmnopqrStuVwxYZ+') + Traceback (most recent call last): + ... + ValueError: Each message character has to be included in alphabet! + + Testing with numbers + >>> __prepare(500,'abCdeFghijkLmnopqrStuVwxYZ+') + Traceback (most recent call last): + ... + AttributeError: 'int' object has no attribute 'replace' + """ # Validate message and alphabet, set to upper and remove spaces alphabet = alphabet.replace(" ", "").upper() message = message.replace(" ", "").upper() @@ -45,45 +105,14 @@ def __prepare( # 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!") + if any(char not in alphabet for char in message): + 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", - ) - character_to_number = {} - number_to_character = {} - for letter, number in zip(alphabet, numbers): - character_to_number[letter] = number - number_to_character[number] = letter + character_to_number = dict(zip(alphabet, TEST_CHARACTER_TO_NUMBER.values())) + number_to_character = { + number: letter for letter, number in character_to_number.items() + } return message, alphabet, character_to_number, number_to_character @@ -91,44 +120,90 @@ def __prepare( def encrypt_message( message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 ) -> str: + """ + encrypt_message + =============== + + Encrypts a message using the trifid_cipher. Any punctuatuions that + would be used should be added to the alphabet. + + PARAMETERS + ---------- + + * message: The message you want to encrypt. + * alphabet (optional): The characters to be used for the cipher . + * period (optional): The number of characters you want in a group whilst + encrypting. + + >>> encrypt_message('I am a boy') + 'BCDGBQY' + + >>> encrypt_message(' ') + '' + + >>> encrypt_message(' aide toi le c iel ta id era ', + ... 'FELIXMARDSTBCGHJKNOPQUVWYZ+',5) + 'FMJFVOISSUFTFPUFEQQC' + + """ message, alphabet, character_to_number, number_to_character = __prepare( message, alphabet ) - encrypted, encrypted_numeric = "", "" + encrypted_numeric = "" for i in range(0, len(message) + 1, period): encrypted_numeric += __encrypt_part( message[i : i + period], character_to_number ) + encrypted = "" for i in range(0, len(encrypted_numeric), 3): encrypted += number_to_character[encrypted_numeric[i : i + 3]] - return encrypted def decrypt_message( message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 ) -> str: + """ + decrypt_message + =============== + + Decrypts a trifid_cipher encrypted message . + + PARAMETERS + ---------- + + * message: The message you want to decrypt . + * alphabet (optional): The characters used for the cipher. + * period (optional): The number of characters used in grouping when it + was encrypted. + + >>> decrypt_message('BCDGBQY') + 'IAMABOY' + + Decrypting with your own alphabet and period + >>> decrypt_message('FMJFVOISSUFTFPUFEQQC','FELIXMARDSTBCGHJKNOPQUVWYZ+',5) + 'AIDETOILECIELTAIDERA' + """ message, alphabet, character_to_number, number_to_character = __prepare( message, alphabet ) - decrypted_numeric = [] - decrypted = "" - for i in range(0, len(message) + 1, period): + decrypted_numeric = [] + for i in range(0, len(message), period): a, b, c = __decrypt_part(message[i : i + period], character_to_number) for j in range(len(a)): decrypted_numeric.append(a[j] + b[j] + c[j]) - for each in decrypted_numeric: - decrypted += number_to_character[each] - - return decrypted + return "".join(number_to_character[each] for each in decrypted_numeric) if __name__ == "__main__": + import doctest + + doctest.testmod() msg = "DEFEND THE EAST WALL OF THE CASTLE." encrypted = encrypt_message(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") decrypted = decrypt_message(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") diff --git a/pyproject.toml b/pyproject.toml index 9c9262d77748..790a328b3564 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,5 +135,5 @@ omit = [ sort = "Cover" [tool.codespell] -ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,zar" +ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" From 5645084dcd5cf398caefa40641ac99144a40e572 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 20 Oct 2023 17:29:42 -0400 Subject: [PATCH 2537/2908] Consolidate loss functions into a single file (#10737) * Consolidate loss functions into single file * updating DIRECTORY.md * Fix typo --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 8 +- machine_learning/loss_functions.py | 252 ++++++++++++++++++ .../loss_functions/binary_cross_entropy.py | 59 ---- .../categorical_cross_entropy.py | 85 ------ machine_learning/loss_functions/hinge_loss.py | 64 ----- machine_learning/loss_functions/huber_loss.py | 52 ---- .../loss_functions/mean_squared_error.py | 51 ---- .../mean_squared_logarithmic_error.py | 55 ---- 8 files changed, 253 insertions(+), 373 deletions(-) create mode 100644 machine_learning/loss_functions.py delete mode 100644 machine_learning/loss_functions/binary_cross_entropy.py delete mode 100644 machine_learning/loss_functions/categorical_cross_entropy.py delete mode 100644 machine_learning/loss_functions/hinge_loss.py delete mode 100644 machine_learning/loss_functions/huber_loss.py delete mode 100644 machine_learning/loss_functions/mean_squared_error.py delete mode 100644 machine_learning/loss_functions/mean_squared_logarithmic_error.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 5b7ca856ea15..b92f8f877e97 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -549,13 +549,7 @@ * Local Weighted Learning * [Local Weighted Learning](machine_learning/local_weighted_learning/local_weighted_learning.py) * [Logistic Regression](machine_learning/logistic_regression.py) - * Loss Functions - * [Binary Cross Entropy](machine_learning/loss_functions/binary_cross_entropy.py) - * [Categorical Cross Entropy](machine_learning/loss_functions/categorical_cross_entropy.py) - * [Hinge Loss](machine_learning/loss_functions/hinge_loss.py) - * [Huber Loss](machine_learning/loss_functions/huber_loss.py) - * [Mean Squared Error](machine_learning/loss_functions/mean_squared_error.py) - * [Mean Squared Logarithmic Error](machine_learning/loss_functions/mean_squared_logarithmic_error.py) + * [Loss Functions](machine_learning/loss_functions.py) * [Mfcc](machine_learning/mfcc.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polynomial Regression](machine_learning/polynomial_regression.py) diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py new file mode 100644 index 000000000000..0fa0956ed572 --- /dev/null +++ b/machine_learning/loss_functions.py @@ -0,0 +1,252 @@ +import numpy as np + + +def binary_cross_entropy( + y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-15 +) -> float: + """ + Calculate the mean binary cross-entropy (BCE) loss between true labels and predicted + probabilities. + + BCE loss quantifies dissimilarity between true labels (0 or 1) and predicted + probabilities. It's widely used in binary classification tasks. + + BCE = -Σ(y_true * ln(y_pred) + (1 - y_true) * ln(1 - y_pred)) + + Reference: https://en.wikipedia.org/wiki/Cross_entropy + + Parameters: + - y_true: True binary labels (0 or 1) + - y_pred: Predicted probabilities for class 1 + - epsilon: Small constant to avoid numerical instability + + >>> true_labels = np.array([0, 1, 1, 0, 1]) + >>> predicted_probs = np.array([0.2, 0.7, 0.9, 0.3, 0.8]) + >>> binary_cross_entropy(true_labels, predicted_probs) + 0.2529995012327421 + >>> true_labels = np.array([0, 1, 1, 0, 1]) + >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) + >>> binary_cross_entropy(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + y_pred = np.clip(y_pred, epsilon, 1 - epsilon) # Clip predictions to avoid log(0) + bce_loss = -(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)) + return np.mean(bce_loss) + + +def categorical_cross_entropy( + y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-15 +) -> float: + """ + Calculate categorical cross-entropy (CCE) loss between true class labels and + predicted class probabilities. + + CCE = -Σ(y_true * ln(y_pred)) + + Reference: https://en.wikipedia.org/wiki/Cross_entropy + + Parameters: + - y_true: True class labels (one-hot encoded) + - y_pred: Predicted class probabilities + - epsilon: Small constant to avoid numerical instability + + >>> true_labels = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1], [0.0, 0.1, 0.9]]) + >>> categorical_cross_entropy(true_labels, pred_probs) + 0.567395975254385 + >>> true_labels = np.array([[1, 0], [0, 1]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(true_labels, pred_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same shape. + >>> true_labels = np.array([[2, 0, 1], [1, 0, 0]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(true_labels, pred_probs) + Traceback (most recent call last): + ... + ValueError: y_true must be one-hot encoded. + >>> true_labels = np.array([[1, 0, 1], [1, 0, 0]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(true_labels, pred_probs) + Traceback (most recent call last): + ... + ValueError: y_true must be one-hot encoded. + >>> true_labels = np.array([[1, 0, 0], [0, 1, 0]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.1], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(true_labels, pred_probs) + Traceback (most recent call last): + ... + ValueError: Predicted probabilities must sum to approximately 1. + """ + if y_true.shape != y_pred.shape: + raise ValueError("Input arrays must have the same shape.") + + if np.any((y_true != 0) & (y_true != 1)) or np.any(y_true.sum(axis=1) != 1): + raise ValueError("y_true must be one-hot encoded.") + + if not np.all(np.isclose(np.sum(y_pred, axis=1), 1, rtol=epsilon, atol=epsilon)): + raise ValueError("Predicted probabilities must sum to approximately 1.") + + y_pred = np.clip(y_pred, epsilon, 1) # Clip predictions to avoid log(0) + return -np.sum(y_true * np.log(y_pred)) + + +def hinge_loss(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Calculate the mean hinge loss for between true labels and predicted probabilities + for training support vector machines (SVMs). + + Hinge loss = max(0, 1 - true * pred) + + Reference: https://en.wikipedia.org/wiki/Hinge_loss + + Args: + - y_true: actual values (ground truth) encoded as -1 or 1 + - y_pred: predicted values + + >>> true_labels = np.array([-1, 1, 1, -1, 1]) + >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) + >>> hinge_loss(true_labels, pred) + 1.52 + >>> true_labels = np.array([-1, 1, 1, -1, 1, 1]) + >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) + >>> hinge_loss(true_labels, pred) + Traceback (most recent call last): + ... + ValueError: Length of predicted and actual array must be same. + >>> true_labels = np.array([-1, 1, 10, -1, 1]) + >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) + >>> hinge_loss(true_labels, pred) + Traceback (most recent call last): + ... + ValueError: y_true can have values -1 or 1 only. + """ + if len(y_true) != len(y_pred): + raise ValueError("Length of predicted and actual array must be same.") + + if np.any((y_true != -1) & (y_true != 1)): + raise ValueError("y_true can have values -1 or 1 only.") + + hinge_losses = np.maximum(0, 1.0 - (y_true * y_pred)) + return np.mean(hinge_losses) + + +def huber_loss(y_true: np.ndarray, y_pred: np.ndarray, delta: float) -> float: + """ + Calculate the mean Huber loss between the given ground truth and predicted values. + + The Huber loss describes the penalty incurred by an estimation procedure, and it + serves as a measure of accuracy for regression models. + + Huber loss = + 0.5 * (y_true - y_pred)^2 if |y_true - y_pred| <= delta + delta * |y_true - y_pred| - 0.5 * delta^2 otherwise + + Reference: https://en.wikipedia.org/wiki/Huber_loss + + Parameters: + - y_true: The true values (ground truth) + - y_pred: The predicted values + + >>> true_values = np.array([0.9, 10.0, 2.0, 1.0, 5.2]) + >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) + >>> np.isclose(huber_loss(true_values, predicted_values, 1.0), 2.102) + True + >>> true_labels = np.array([11.0, 21.0, 3.32, 4.0, 5.0]) + >>> predicted_probs = np.array([8.3, 20.8, 2.9, 11.2, 5.0]) + >>> np.isclose(huber_loss(true_labels, predicted_probs, 1.0), 1.80164) + True + >>> true_labels = np.array([11.0, 21.0, 3.32, 4.0]) + >>> predicted_probs = np.array([8.3, 20.8, 2.9, 11.2, 5.0]) + >>> huber_loss(true_labels, predicted_probs, 1.0) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + huber_mse = 0.5 * (y_true - y_pred) ** 2 + huber_mae = delta * (np.abs(y_true - y_pred) - 0.5 * delta) + return np.where(np.abs(y_true - y_pred) <= delta, huber_mse, huber_mae).mean() + + +def mean_squared_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Calculate the mean squared error (MSE) between ground truth and predicted values. + + MSE measures the squared difference between true values and predicted values, and it + serves as a measure of accuracy for regression models. + + MSE = (1/n) * Σ(y_true - y_pred)^2 + + Reference: https://en.wikipedia.org/wiki/Mean_squared_error + + Parameters: + - y_true: The true values (ground truth) + - y_pred: The predicted values + + >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) + >>> np.isclose(mean_squared_error(true_values, predicted_values), 0.028) + True + >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) + >>> mean_squared_error(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + squared_errors = (y_true - y_pred) ** 2 + return np.mean(squared_errors) + + +def mean_squared_logarithmic_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Calculate the mean squared logarithmic error (MSLE) between ground truth and + predicted values. + + MSLE measures the squared logarithmic difference between true values and predicted + values for regression models. It's particularly useful for dealing with skewed or + large-value data, and it's often used when the relative differences between + predicted and true values are more important than absolute differences. + + MSLE = (1/n) * Σ(log(1 + y_true) - log(1 + y_pred))^2 + + Reference: https://insideaiml.com/blog/MeanSquared-Logarithmic-Error-Loss-1035 + + Parameters: + - y_true: The true values (ground truth) + - y_pred: The predicted values + + >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) + >>> mean_squared_logarithmic_error(true_values, predicted_values) + 0.0030860877925181344 + >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) + >>> mean_squared_logarithmic_error(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + squared_logarithmic_errors = (np.log1p(y_true) - np.log1p(y_pred)) ** 2 + return np.mean(squared_logarithmic_errors) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/machine_learning/loss_functions/binary_cross_entropy.py b/machine_learning/loss_functions/binary_cross_entropy.py deleted file mode 100644 index 4ebca7f21757..000000000000 --- a/machine_learning/loss_functions/binary_cross_entropy.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -Binary Cross-Entropy (BCE) Loss Function - -Description: -Quantifies dissimilarity between true labels (0 or 1) and predicted probabilities. -It's widely used in binary classification tasks. - -Formula: -BCE = -Σ(y_true * log(y_pred) + (1 - y_true) * log(1 - y_pred)) - -Source: -[Wikipedia - Cross entropy](https://en.wikipedia.org/wiki/Cross_entropy) -""" - -import numpy as np - - -def binary_cross_entropy( - y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-15 -) -> float: - """ - Calculate the BCE Loss between true labels and predicted probabilities. - - Parameters: - - y_true: True binary labels (0 or 1). - - y_pred: Predicted probabilities for class 1. - - epsilon: Small constant to avoid numerical instability. - - Returns: - - bce_loss: Binary Cross-Entropy Loss. - - Example Usage: - >>> true_labels = np.array([0, 1, 1, 0, 1]) - >>> predicted_probs = np.array([0.2, 0.7, 0.9, 0.3, 0.8]) - >>> binary_cross_entropy(true_labels, predicted_probs) - 0.2529995012327421 - >>> true_labels = np.array([0, 1, 1, 0, 1]) - >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) - >>> binary_cross_entropy(true_labels, predicted_probs) - Traceback (most recent call last): - ... - ValueError: Input arrays must have the same length. - """ - if len(y_true) != len(y_pred): - raise ValueError("Input arrays must have the same length.") - # Clip predicted probabilities to avoid log(0) and log(1) - y_pred = np.clip(y_pred, epsilon, 1 - epsilon) - - # Calculate binary cross-entropy loss - bce_loss = -(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)) - - # Take the mean over all samples - return np.mean(bce_loss) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/machine_learning/loss_functions/categorical_cross_entropy.py b/machine_learning/loss_functions/categorical_cross_entropy.py deleted file mode 100644 index 68f98902b473..000000000000 --- a/machine_learning/loss_functions/categorical_cross_entropy.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Categorical Cross-Entropy Loss - -This function calculates the Categorical Cross-Entropy Loss between true class -labels and predicted class probabilities. - -Formula: -Categorical Cross-Entropy Loss = -Σ(y_true * ln(y_pred)) - -Resources: -- [Wikipedia - Cross entropy](https://en.wikipedia.org/wiki/Cross_entropy) -""" - -import numpy as np - - -def categorical_cross_entropy( - y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-15 -) -> float: - """ - Calculate Categorical Cross-Entropy Loss between true class labels and - predicted class probabilities. - - Parameters: - - y_true: True class labels (one-hot encoded) as a NumPy array. - - y_pred: Predicted class probabilities as a NumPy array. - - epsilon: Small constant to avoid numerical instability. - - Returns: - - ce_loss: Categorical Cross-Entropy Loss as a floating-point number. - - Example: - >>> true_labels = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) - >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1], [0.0, 0.1, 0.9]]) - >>> categorical_cross_entropy(true_labels, pred_probs) - 0.567395975254385 - - >>> y_true = np.array([[1, 0], [0, 1]]) - >>> y_pred = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) - >>> categorical_cross_entropy(y_true, y_pred) - Traceback (most recent call last): - ... - ValueError: Input arrays must have the same shape. - - >>> y_true = np.array([[2, 0, 1], [1, 0, 0]]) - >>> y_pred = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) - >>> categorical_cross_entropy(y_true, y_pred) - Traceback (most recent call last): - ... - ValueError: y_true must be one-hot encoded. - - >>> y_true = np.array([[1, 0, 1], [1, 0, 0]]) - >>> y_pred = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) - >>> categorical_cross_entropy(y_true, y_pred) - Traceback (most recent call last): - ... - ValueError: y_true must be one-hot encoded. - - >>> y_true = np.array([[1, 0, 0], [0, 1, 0]]) - >>> y_pred = np.array([[0.9, 0.1, 0.1], [0.2, 0.7, 0.1]]) - >>> categorical_cross_entropy(y_true, y_pred) - Traceback (most recent call last): - ... - ValueError: Predicted probabilities must sum to approximately 1. - """ - if y_true.shape != y_pred.shape: - raise ValueError("Input arrays must have the same shape.") - - if np.any((y_true != 0) & (y_true != 1)) or np.any(y_true.sum(axis=1) != 1): - raise ValueError("y_true must be one-hot encoded.") - - if not np.all(np.isclose(np.sum(y_pred, axis=1), 1, rtol=epsilon, atol=epsilon)): - raise ValueError("Predicted probabilities must sum to approximately 1.") - - # Clip predicted probabilities to avoid log(0) - y_pred = np.clip(y_pred, epsilon, 1) - - # Calculate categorical cross-entropy loss - return -np.sum(y_true * np.log(y_pred)) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/machine_learning/loss_functions/hinge_loss.py b/machine_learning/loss_functions/hinge_loss.py deleted file mode 100644 index 5480a8cd62ee..000000000000 --- a/machine_learning/loss_functions/hinge_loss.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Hinge Loss - -Description: -Compute the Hinge loss used for training SVM (Support Vector Machine). - -Formula: -loss = max(0, 1 - true * pred) - -Reference: https://en.wikipedia.org/wiki/Hinge_loss - -Author: Poojan Smart -Email: smrtpoojan@gmail.com -""" - -import numpy as np - - -def hinge_loss(y_true: np.ndarray, y_pred: np.ndarray) -> float: - """ - Calculate the mean hinge loss for y_true and y_pred for binary classification. - - Args: - y_true: Array of actual values (ground truth) encoded as -1 and 1. - y_pred: Array of predicted values. - - Returns: - The hinge loss between y_true and y_pred. - - Examples: - >>> y_true = np.array([-1, 1, 1, -1, 1]) - >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) - >>> hinge_loss(y_true, pred) - 1.52 - >>> y_true = np.array([-1, 1, 1, -1, 1, 1]) - >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) - >>> hinge_loss(y_true, pred) - Traceback (most recent call last): - ... - ValueError: Length of predicted and actual array must be same. - >>> y_true = np.array([-1, 1, 10, -1, 1]) - >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) - >>> hinge_loss(y_true, pred) - Traceback (most recent call last): - ... - ValueError: y_true can have values -1 or 1 only. - """ - - if len(y_true) != len(y_pred): - raise ValueError("Length of predicted and actual array must be same.") - - # Raise value error when y_true (encoded labels) have any other values - # than -1 and 1 - if np.any((y_true != -1) & (y_true != 1)): - raise ValueError("y_true can have values -1 or 1 only.") - - hinge_losses = np.maximum(0, 1.0 - (y_true * y_pred)) - return np.mean(hinge_losses) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/machine_learning/loss_functions/huber_loss.py b/machine_learning/loss_functions/huber_loss.py deleted file mode 100644 index 202e013f2928..000000000000 --- a/machine_learning/loss_functions/huber_loss.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Huber Loss Function - -Description: -Huber loss function describes the penalty incurred by an estimation procedure. -It serves as a measure of the model's accuracy in regression tasks. - -Formula: -Huber Loss = if |y_true - y_pred| <= delta then 0.5 * (y_true - y_pred)^2 - else delta * |y_true - y_pred| - 0.5 * delta^2 - -Source: -[Wikipedia - Huber Loss](https://en.wikipedia.org/wiki/Huber_loss) -""" - -import numpy as np - - -def huber_loss(y_true: np.ndarray, y_pred: np.ndarray, delta: float) -> float: - """ - Calculate the mean of Huber Loss. - - Parameters: - - y_true: The true values (ground truth). - - y_pred: The predicted values. - - Returns: - - huber_loss: The mean of Huber Loss between y_true and y_pred. - - Example usage: - >>> true_values = np.array([0.9, 10.0, 2.0, 1.0, 5.2]) - >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) - >>> np.isclose(huber_loss(true_values, predicted_values, 1.0), 2.102) - True - >>> true_labels = np.array([11.0, 21.0, 3.32, 4.0, 5.0]) - >>> predicted_probs = np.array([8.3, 20.8, 2.9, 11.2, 5.0]) - >>> np.isclose(huber_loss(true_labels, predicted_probs, 1.0), 1.80164) - True - """ - - if len(y_true) != len(y_pred): - raise ValueError("Input arrays must have the same length.") - - huber_mse = 0.5 * (y_true - y_pred) ** 2 - huber_mae = delta * (np.abs(y_true - y_pred) - 0.5 * delta) - return np.where(np.abs(y_true - y_pred) <= delta, huber_mse, huber_mae).mean() - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/machine_learning/loss_functions/mean_squared_error.py b/machine_learning/loss_functions/mean_squared_error.py deleted file mode 100644 index d2b0e1e158ba..000000000000 --- a/machine_learning/loss_functions/mean_squared_error.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -Mean Squared Error (MSE) Loss Function - -Description: -MSE measures the mean squared difference between true values and predicted values. -It serves as a measure of the model's accuracy in regression tasks. - -Formula: -MSE = (1/n) * Σ(y_true - y_pred)^2 - -Source: -[Wikipedia - Mean squared error](https://en.wikipedia.org/wiki/Mean_squared_error) -""" - -import numpy as np - - -def mean_squared_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: - """ - Calculate the Mean Squared Error (MSE) between two arrays. - - Parameters: - - y_true: The true values (ground truth). - - y_pred: The predicted values. - - Returns: - - mse: The Mean Squared Error between y_true and y_pred. - - Example usage: - >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) - >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) - >>> mean_squared_error(true_values, predicted_values) - 0.028000000000000032 - >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) - >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) - >>> mean_squared_error(true_labels, predicted_probs) - Traceback (most recent call last): - ... - ValueError: Input arrays must have the same length. - """ - if len(y_true) != len(y_pred): - raise ValueError("Input arrays must have the same length.") - - squared_errors = (y_true - y_pred) ** 2 - return np.mean(squared_errors) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/machine_learning/loss_functions/mean_squared_logarithmic_error.py b/machine_learning/loss_functions/mean_squared_logarithmic_error.py deleted file mode 100644 index 935ebff37a51..000000000000 --- a/machine_learning/loss_functions/mean_squared_logarithmic_error.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -Mean Squared Logarithmic Error (MSLE) Loss Function - -Description: -MSLE measures the mean squared logarithmic difference between -true values and predicted values, particularly useful when -dealing with regression problems involving skewed or large-value -targets. It is often used when the relative differences between -predicted and true values are more important than absolute -differences. - -Formula: -MSLE = (1/n) * Σ(log(1 + y_true) - log(1 + y_pred))^2 - -Source: -(https://insideaiml.com/blog/MeanSquared-Logarithmic-Error-Loss-1035) -""" - -import numpy as np - - -def mean_squared_logarithmic_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: - """ - Calculate the Mean Squared Logarithmic Error (MSLE) between two arrays. - - Parameters: - - y_true: The true values (ground truth). - - y_pred: The predicted values. - - Returns: - - msle: The Mean Squared Logarithmic Error between y_true and y_pred. - - Example usage: - >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) - >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) - >>> mean_squared_logarithmic_error(true_values, predicted_values) - 0.0030860877925181344 - >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) - >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) - >>> mean_squared_logarithmic_error(true_labels, predicted_probs) - Traceback (most recent call last): - ... - ValueError: Input arrays must have the same length. - """ - if len(y_true) != len(y_pred): - raise ValueError("Input arrays must have the same length.") - - squared_logarithmic_errors = (np.log1p(y_true) - np.log1p(y_pred)) ** 2 - return np.mean(squared_logarithmic_errors) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From 47c19d9b2da6a56f47b520e6c5ca6b654a5eff47 Mon Sep 17 00:00:00 2001 From: Jeel Gajera <83470656+JeelGajera@users.noreply.github.com> Date: Sat, 21 Oct 2023 20:21:29 +0530 Subject: [PATCH 2538/2908] Add: FP Growth Algorithm (#10746) * Add: FP Growth Algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changes names * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Revert "changes names" This reverts commit c0470094d01391294617df6a92734b78b470b127. * refactore code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update frequent_pattern_growth.py --------- Co-authored-by: Jeel Gajera Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + machine_learning/frequent_pattern_growth.py | 349 ++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 machine_learning/frequent_pattern_growth.py diff --git a/DIRECTORY.md b/DIRECTORY.md index b92f8f877e97..916d993c563a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -541,6 +541,7 @@ * [Dimensionality Reduction](machine_learning/dimensionality_reduction.py) * Forecasting * [Run](machine_learning/forecasting/run.py) + * [Frequent Pattern Growth Algorithm](machine_learning/frequent_pattern_growth.py) * [Gradient Descent](machine_learning/gradient_descent.py) * [K Means Clust](machine_learning/k_means_clust.py) * [K Nearest Neighbours](machine_learning/k_nearest_neighbours.py) diff --git a/machine_learning/frequent_pattern_growth.py b/machine_learning/frequent_pattern_growth.py new file mode 100644 index 000000000000..205d598464a1 --- /dev/null +++ b/machine_learning/frequent_pattern_growth.py @@ -0,0 +1,349 @@ +""" +The Frequent Pattern Growth algorithm (FP-Growth) is a widely used data mining +technique for discovering frequent itemsets in large transaction databases. + +It overcomes some of the limitations of traditional methods such as Apriori by +efficiently constructing the FP-Tree + +WIKI: https://athena.ecs.csus.edu/~mei/associationcw/FpGrowth.html + +Examples: https://www.javatpoint.com/fp-growth-algorithm-in-data-mining +""" +from __future__ import annotations + +from dataclasses import dataclass, field + + +@dataclass +class TreeNode: + """ + A node in a Frequent Pattern tree. + + Args: + name: The name of this node. + num_occur: The number of occurrences of the node. + parent_node: The parent node. + + Example: + >>> parent = TreeNode("Parent", 1, None) + >>> child = TreeNode("Child", 2, parent) + >>> child.name + 'Child' + >>> child.count + 2 + """ + + name: str + count: int + parent: TreeNode | None = None + children: dict[str, TreeNode] = field(default_factory=dict) + node_link: TreeNode | None = None + + def __repr__(self) -> str: + return f"TreeNode({self.name!r}, {self.count!r}, {self.parent!r})" + + def inc(self, num_occur: int) -> None: + self.count += num_occur + + def disp(self, ind: int = 1) -> None: + print(f"{' ' * ind} {self.name} {self.count}") + for child in self.children.values(): + child.disp(ind + 1) + + +def create_tree(data_set: list, min_sup: int = 1) -> tuple[TreeNode, dict]: + """ + Create Frequent Pattern tree + + Args: + data_set: A list of transactions, where each transaction is a list of items. + min_sup: The minimum support threshold. + Items with support less than this will be pruned. Default is 1. + + Returns: + The root of the FP-Tree. + header_table: The header table dictionary with item information. + + Example: + >>> data_set = [ + ... ['A', 'B', 'C'], + ... ['A', 'C'], + ... ['A', 'B', 'E'], + ... ['A', 'B', 'C', 'E'], + ... ['B', 'E'] + ... ] + >>> min_sup = 2 + >>> fp_tree, header_table = create_tree(data_set, min_sup) + >>> fp_tree + TreeNode('Null Set', 1, None) + >>> len(header_table) + 4 + >>> header_table["A"] + [[4, None], TreeNode('A', 4, TreeNode('Null Set', 1, None))] + >>> header_table["E"][1] # doctest: +NORMALIZE_WHITESPACE + TreeNode('E', 1, TreeNode('B', 3, TreeNode('A', 4, TreeNode('Null Set', 1, None)))) + >>> sorted(header_table) + ['A', 'B', 'C', 'E'] + >>> fp_tree.name + 'Null Set' + >>> sorted(fp_tree.children) + ['A', 'B'] + >>> fp_tree.children['A'].name + 'A' + >>> sorted(fp_tree.children['A'].children) + ['B', 'C'] + """ + header_table: dict = {} + for trans in data_set: + for item in trans: + header_table[item] = header_table.get(item, [0, None]) + header_table[item][0] += 1 + + for k in list(header_table): + if header_table[k][0] < min_sup: + del header_table[k] + + if not (freq_item_set := set(header_table)): + return TreeNode("Null Set", 1, None), {} + + for k in header_table: + header_table[k] = [header_table[k], None] + + fp_tree = TreeNode("Null Set", 1, None) # Parent is None for the root node + for tran_set in data_set: + local_d = { + item: header_table[item][0] for item in tran_set if item in freq_item_set + } + if local_d: + sorted_items = sorted( + local_d.items(), key=lambda item_info: item_info[1], reverse=True + ) + ordered_items = [item[0] for item in sorted_items] + update_tree(ordered_items, fp_tree, header_table, 1) + + return fp_tree, header_table + + +def update_tree(items: list, in_tree: TreeNode, header_table: dict, count: int) -> None: + """ + Update the FP-Tree with a transaction. + + Args: + items: List of items in the transaction. + in_tree: The current node in the FP-Tree. + header_table: The header table dictionary with item information. + count: The count of the transaction. + + Example: + >>> data_set = [ + ... ['A', 'B', 'C'], + ... ['A', 'C'], + ... ['A', 'B', 'E'], + ... ['A', 'B', 'C', 'E'], + ... ['B', 'E'] + ... ] + >>> min_sup = 2 + >>> fp_tree, header_table = create_tree(data_set, min_sup) + >>> fp_tree + TreeNode('Null Set', 1, None) + >>> transaction = ['A', 'B', 'E'] + >>> update_tree(transaction, fp_tree, header_table, 1) + >>> fp_tree + TreeNode('Null Set', 1, None) + >>> fp_tree.children['A'].children['B'].children['E'].children + {} + >>> fp_tree.children['A'].children['B'].children['E'].count + 2 + >>> header_table['E'][1].name + 'E' + """ + if items[0] in in_tree.children: + in_tree.children[items[0]].inc(count) + else: + in_tree.children[items[0]] = TreeNode(items[0], count, in_tree) + if header_table[items[0]][1] is None: + header_table[items[0]][1] = in_tree.children[items[0]] + else: + update_header(header_table[items[0]][1], in_tree.children[items[0]]) + if len(items) > 1: + update_tree(items[1:], in_tree.children[items[0]], header_table, count) + + +def update_header(node_to_test: TreeNode, target_node: TreeNode) -> TreeNode: + """ + Update the header table with a node link. + + Args: + node_to_test: The node to be updated in the header table. + target_node: The node to link to. + + Example: + >>> data_set = [ + ... ['A', 'B', 'C'], + ... ['A', 'C'], + ... ['A', 'B', 'E'], + ... ['A', 'B', 'C', 'E'], + ... ['B', 'E'] + ... ] + >>> min_sup = 2 + >>> fp_tree, header_table = create_tree(data_set, min_sup) + >>> fp_tree + TreeNode('Null Set', 1, None) + >>> node1 = TreeNode("A", 3, None) + >>> node2 = TreeNode("B", 4, None) + >>> node1 + TreeNode('A', 3, None) + >>> node1 = update_header(node1, node2) + >>> node1 + TreeNode('A', 3, None) + >>> node1.node_link + TreeNode('B', 4, None) + >>> node2.node_link is None + True + """ + while node_to_test.node_link is not None: + node_to_test = node_to_test.node_link + if node_to_test.node_link is None: + node_to_test.node_link = target_node + # Return the updated node + return node_to_test + + +def ascend_tree(leaf_node: TreeNode, prefix_path: list[str]) -> None: + """ + Ascend the FP-Tree from a leaf node to its root, adding item names to the prefix + path. + + Args: + leaf_node: The leaf node to start ascending from. + prefix_path: A list to store the item as they are ascended. + + Example: + >>> data_set = [ + ... ['A', 'B', 'C'], + ... ['A', 'C'], + ... ['A', 'B', 'E'], + ... ['A', 'B', 'C', 'E'], + ... ['B', 'E'] + ... ] + >>> min_sup = 2 + >>> fp_tree, header_table = create_tree(data_set, min_sup) + + >>> path = [] + >>> ascend_tree(fp_tree.children['A'], path) + >>> path # ascending from a leaf node 'A' + ['A'] + """ + if leaf_node.parent is not None: + prefix_path.append(leaf_node.name) + ascend_tree(leaf_node.parent, prefix_path) + + +def find_prefix_path(base_pat: frozenset, tree_node: TreeNode | None) -> dict: + """ + Find the conditional pattern base for a given base pattern. + + Args: + base_pat: The base pattern for which to find the conditional pattern base. + tree_node: The node in the FP-Tree. + + Example: + >>> data_set = [ + ... ['A', 'B', 'C'], + ... ['A', 'C'], + ... ['A', 'B', 'E'], + ... ['A', 'B', 'C', 'E'], + ... ['B', 'E'] + ... ] + >>> min_sup = 2 + >>> fp_tree, header_table = create_tree(data_set, min_sup) + >>> fp_tree + TreeNode('Null Set', 1, None) + >>> len(header_table) + 4 + >>> base_pattern = frozenset(['A']) + >>> sorted(find_prefix_path(base_pattern, fp_tree.children['A'])) + [] + """ + cond_pats: dict = {} + while tree_node is not None: + prefix_path: list = [] + ascend_tree(tree_node, prefix_path) + if len(prefix_path) > 1: + cond_pats[frozenset(prefix_path[1:])] = tree_node.count + tree_node = tree_node.node_link + return cond_pats + + +def mine_tree( + in_tree: TreeNode, + header_table: dict, + min_sup: int, + pre_fix: set, + freq_item_list: list, +) -> None: + """ + Mine the FP-Tree recursively to discover frequent itemsets. + + Args: + in_tree: The FP-Tree to mine. + header_table: The header table dictionary with item information. + min_sup: The minimum support threshold. + pre_fix: A set of items as a prefix for the itemsets being mined. + freq_item_list: A list to store the frequent itemsets. + + Example: + >>> data_set = [ + ... ['A', 'B', 'C'], + ... ['A', 'C'], + ... ['A', 'B', 'E'], + ... ['A', 'B', 'C', 'E'], + ... ['B', 'E'] + ... ] + >>> min_sup = 2 + >>> fp_tree, header_table = create_tree(data_set, min_sup) + >>> fp_tree + TreeNode('Null Set', 1, None) + >>> frequent_itemsets = [] + >>> mine_tree(fp_tree, header_table, min_sup, set([]), frequent_itemsets) + >>> expe_itm = [{'C'}, {'C', 'A'}, {'E'}, {'A', 'E'}, {'E', 'B'}, {'A'}, {'B'}] + >>> all(expected in frequent_itemsets for expected in expe_itm) + True + """ + sorted_items = sorted(header_table.items(), key=lambda item_info: item_info[1][0]) + big_l = [item[0] for item in sorted_items] + for base_pat in big_l: + new_freq_set = pre_fix.copy() + new_freq_set.add(base_pat) + freq_item_list.append(new_freq_set) + cond_patt_bases = find_prefix_path(base_pat, header_table[base_pat][1]) + my_cond_tree, my_head = create_tree(list(cond_patt_bases), min_sup) + if my_head is not None: + # Pass header_table[base_pat][1] as node_to_test to update_header + header_table[base_pat][1] = update_header( + header_table[base_pat][1], my_cond_tree + ) + mine_tree(my_cond_tree, my_head, min_sup, new_freq_set, freq_item_list) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + data_set: list[frozenset] = [ + frozenset(["bread", "milk", "cheese"]), + frozenset(["bread", "milk"]), + frozenset(["bread", "diapers"]), + frozenset(["bread", "milk", "diapers"]), + frozenset(["milk", "diapers"]), + frozenset(["milk", "cheese"]), + frozenset(["diapers", "cheese"]), + frozenset(["bread", "milk", "cheese", "diapers"]), + ] + print(f"{len(data_set) = }") + fp_tree, header_table = create_tree(data_set, min_sup=3) + print(f"{fp_tree = }") + print(f"{len(header_table) = }") + freq_items: list = [] + mine_tree(fp_tree, header_table, 3, set(), freq_items) + print(f"{freq_items = }") From 06edc0eea0220f29491f75351cde1af9716aca8d Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 21 Oct 2023 13:27:36 -0400 Subject: [PATCH 2539/2908] Consolidate binary exponentiation files (#10742) * Consolidate binary exponentiation files * updating DIRECTORY.md * Fix typos in doctests * Add suggestions from code review * Fix timeit benchmarks --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 - maths/binary_exp_mod.py | 28 ---- maths/binary_exponentiation.py | 214 ++++++++++++++++++++++++++----- maths/binary_exponentiation_2.py | 61 --------- 4 files changed, 181 insertions(+), 124 deletions(-) delete mode 100644 maths/binary_exp_mod.py delete mode 100644 maths/binary_exponentiation_2.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 916d993c563a..9e0166ad80c5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -578,9 +578,7 @@ * [Bailey Borwein Plouffe](maths/bailey_borwein_plouffe.py) * [Base Neg2 Conversion](maths/base_neg2_conversion.py) * [Basic Maths](maths/basic_maths.py) - * [Binary Exp Mod](maths/binary_exp_mod.py) * [Binary Exponentiation](maths/binary_exponentiation.py) - * [Binary Exponentiation 2](maths/binary_exponentiation_2.py) * [Binary Multiplication](maths/binary_multiplication.py) * [Binomial Coefficient](maths/binomial_coefficient.py) * [Binomial Distribution](maths/binomial_distribution.py) diff --git a/maths/binary_exp_mod.py b/maths/binary_exp_mod.py deleted file mode 100644 index 8893182a3496..000000000000 --- a/maths/binary_exp_mod.py +++ /dev/null @@ -1,28 +0,0 @@ -def bin_exp_mod(a: int, n: int, b: int) -> int: - """ - >>> bin_exp_mod(3, 4, 5) - 1 - >>> bin_exp_mod(7, 13, 10) - 7 - """ - # mod b - assert 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/binary_exponentiation.py b/maths/binary_exponentiation.py index f613767f547e..51ce86d26c41 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -1,48 +1,196 @@ -"""Binary Exponentiation.""" +""" +Binary Exponentiation -# Author : Junth Basnet -# Time Complexity : O(logn) +This is a method to find a^b in O(log b) time complexity and is one of the most commonly +used methods of exponentiation. The method is also useful for modular exponentiation, +when the solution to (a^b) % c is required. +To calculate a^b: +- If b is even, then a^b = (a * a)^(b / 2) +- If b is odd, then a^b = a * a^(b - 1) +Repeat until b = 1 or b = 0 -def binary_exponentiation(a: int, n: int) -> int: +For modular exponentiation, we use the fact that (a * b) % c = ((a % c) * (b % c)) % c +""" + + +def binary_exp_recursive(base: float, exponent: int) -> float: """ - Compute a number raised by some quantity - >>> binary_exponentiation(-1, 3) + Computes a^b recursively, where a is the base and b is the exponent + + >>> binary_exp_recursive(3, 5) + 243 + >>> binary_exp_recursive(11, 13) + 34522712143931 + >>> binary_exp_recursive(-1, 3) -1 - >>> binary_exponentiation(-1, 4) + >>> binary_exp_recursive(0, 5) + 0 + >>> binary_exp_recursive(3, 1) + 3 + >>> binary_exp_recursive(3, 0) 1 - >>> binary_exponentiation(2, 2) - 4 - >>> binary_exponentiation(3, 5) + >>> binary_exp_recursive(1.5, 4) + 5.0625 + >>> binary_exp_recursive(3, -1) + Traceback (most recent call last): + ... + ValueError: Exponent must be a non-negative integer + """ + if exponent < 0: + raise ValueError("Exponent must be a non-negative integer") + + if exponent == 0: + return 1 + + if exponent % 2 == 1: + return binary_exp_recursive(base, exponent - 1) * base + + b = binary_exp_recursive(base, exponent // 2) + return b * b + + +def binary_exp_iterative(base: float, exponent: int) -> float: + """ + Computes a^b iteratively, where a is the base and b is the exponent + + >>> binary_exp_iterative(3, 5) 243 - >>> binary_exponentiation(10, 3) - 1000 - >>> binary_exponentiation(5e3, 1) - 5000.0 - >>> binary_exponentiation(-5e3, 1) - -5000.0 - """ - if n == 0: + >>> binary_exp_iterative(11, 13) + 34522712143931 + >>> binary_exp_iterative(-1, 3) + -1 + >>> binary_exp_iterative(0, 5) + 0 + >>> binary_exp_iterative(3, 1) + 3 + >>> binary_exp_iterative(3, 0) + 1 + >>> binary_exp_iterative(1.5, 4) + 5.0625 + >>> binary_exp_iterative(3, -1) + Traceback (most recent call last): + ... + ValueError: Exponent must be a non-negative integer + """ + if exponent < 0: + raise ValueError("Exponent must be a non-negative integer") + + res: int | float = 1 + while exponent > 0: + if exponent & 1: + res *= base + + base *= base + exponent >>= 1 + + return res + + +def binary_exp_mod_recursive(base: float, exponent: int, modulus: int) -> float: + """ + Computes a^b % c recursively, where a is the base, b is the exponent, and c is the + modulus + + >>> binary_exp_mod_recursive(3, 4, 5) + 1 + >>> binary_exp_mod_recursive(11, 13, 7) + 4 + >>> binary_exp_mod_recursive(1.5, 4, 3) + 2.0625 + >>> binary_exp_mod_recursive(7, -1, 10) + Traceback (most recent call last): + ... + ValueError: Exponent must be a non-negative integer + >>> binary_exp_mod_recursive(7, 13, 0) + Traceback (most recent call last): + ... + ValueError: Modulus must be a positive integer + """ + if exponent < 0: + raise ValueError("Exponent must be a non-negative integer") + if modulus <= 0: + raise ValueError("Modulus must be a positive integer") + + if exponent == 0: return 1 - elif n % 2 == 1: - return binary_exponentiation(a, n - 1) * a + if exponent % 2 == 1: + return (binary_exp_mod_recursive(base, exponent - 1, modulus) * base) % modulus - else: - b = binary_exponentiation(a, n // 2) - return b * b + r = binary_exp_mod_recursive(base, exponent // 2, modulus) + return (r * r) % modulus -if __name__ == "__main__": - import doctest +def binary_exp_mod_iterative(base: float, exponent: int, modulus: int) -> float: + """ + Computes a^b % c iteratively, where a is the base, b is the exponent, and c is the + modulus - doctest.testmod() + >>> binary_exp_mod_iterative(3, 4, 5) + 1 + >>> binary_exp_mod_iterative(11, 13, 7) + 4 + >>> binary_exp_mod_iterative(1.5, 4, 3) + 2.0625 + >>> binary_exp_mod_iterative(7, -1, 10) + Traceback (most recent call last): + ... + ValueError: Exponent must be a non-negative integer + >>> binary_exp_mod_iterative(7, 13, 0) + Traceback (most recent call last): + ... + ValueError: Modulus must be a positive integer + """ + if exponent < 0: + raise ValueError("Exponent must be a non-negative integer") + if modulus <= 0: + raise ValueError("Modulus must be a positive integer") + + res: int | float = 1 + while exponent > 0: + if exponent & 1: + res = ((res % modulus) * (base % modulus)) % modulus + + base *= base + exponent >>= 1 + + return res + + +if __name__ == "__main__": + from timeit import timeit - try: - BASE = int(float(input("Enter Base : ").strip())) - POWER = int(input("Enter Power : ").strip()) - except ValueError: - print("Invalid literal for integer") + a = 1269380576 + b = 374 + c = 34 - RESULT = binary_exponentiation(BASE, POWER) - print(f"{BASE}^({POWER}) : {RESULT}") + runs = 100_000 + print( + timeit( + f"binary_exp_recursive({a}, {b})", + setup="from __main__ import binary_exp_recursive", + number=runs, + ) + ) + print( + timeit( + f"binary_exp_iterative({a}, {b})", + setup="from __main__ import binary_exp_iterative", + number=runs, + ) + ) + print( + timeit( + f"binary_exp_mod_recursive({a}, {b}, {c})", + setup="from __main__ import binary_exp_mod_recursive", + number=runs, + ) + ) + print( + timeit( + f"binary_exp_mod_iterative({a}, {b}, {c})", + setup="from __main__ import binary_exp_mod_iterative", + number=runs, + ) + ) diff --git a/maths/binary_exponentiation_2.py b/maths/binary_exponentiation_2.py deleted file mode 100644 index edb6b66b2594..000000000000 --- a/maths/binary_exponentiation_2.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Binary Exponentiation -This is a method to find a^b in O(log b) time complexity -This is one of the most commonly used methods of exponentiation -It's also useful when the solution to (a^b) % c is required because a, b, c may be -over the computer's calculation limits - -Let's say you need to calculate a ^ b -- RULE 1 : a ^ b = (a*a) ^ (b/2) ---- example : 4 ^ 4 = (4*4) ^ (4/2) = 16 ^ 2 -- RULE 2 : IF b is odd, then a ^ b = a * (a ^ (b - 1)), where b - 1 is even -Once b is even, repeat the process until b = 1 or b = 0, because a^1 = a and a^0 = 1 - -For modular exponentiation, we use the fact that (a*b) % c = ((a%c) * (b%c)) % c -Now apply RULE 1 or 2 as required - -@author chinmoy159 -""" - - -def b_expo(a: int, b: int) -> int: - """ - >>> b_expo(2, 10) - 1024 - >>> b_expo(9, 0) - 1 - >>> b_expo(0, 12) - 0 - >>> b_expo(4, 12) - 16777216 - """ - res = 1 - while b > 0: - if b & 1: - res *= a - - a *= a - b >>= 1 - - return res - - -def b_expo_mod(a: int, b: int, c: int) -> int: - """ - >>> b_expo_mod(2, 10, 1000000007) - 1024 - >>> b_expo_mod(11, 13, 19) - 11 - >>> b_expo_mod(0, 19, 20) - 0 - >>> b_expo_mod(15, 5, 4) - 3 - """ - res = 1 - while b > 0: - if b & 1: - res = ((res % c) * (a % c)) % c - - a *= a - b >>= 1 - - return res From b814cf3781a97c273a779823b8b8ab388417b7b4 Mon Sep 17 00:00:00 2001 From: Kiarash Hajian <133909368+kiarash8112@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:53:34 -0400 Subject: [PATCH 2540/2908] add exponential search algorithm (#10732) * add exponential_search algorithm * replace binary_search with binary_search_recursion * convert left type to int to be useable in binary_search_recursion * add docs and tests for exponential_search algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * move exponential_search to binary_search.py to pass github auto build tests delete exponential_search.py file * Update searches/binary_search.py Co-authored-by: Christian Clauss * remove additional space searches/binary_search.py Co-authored-by: Christian Clauss * return single data type in exponential_search searches/binary_search.py Co-authored-by: Christian Clauss * add doctest mod searches/binary_search.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * use // instread of int() convert searches/binary_search.py Co-authored-by: Christian Clauss * change test according to new code searches/binary_search.py Co-authored-by: Christian Clauss * fix binary_search_recursion multiple type return error * add a timeit benchmark for exponential_search * sort input of binary search to be equal in performance test with exponential_search * raise value error instead of sorting input in binary and exonential search to fix bugs * Update binary_search.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: user --- searches/binary_search.py | 149 +++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 49 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 05dadd4fe965..586be39c9a0d 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 """ -This is pure Python implementation of binary search algorithms +Pure Python implementations of binary search algorithms -For doctests run following command: +For doctests run the following command: python3 -m doctest -v binary_search.py For manual testing run: @@ -34,16 +34,12 @@ def bisect_left( 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 """ @@ -79,16 +75,12 @@ def bisect_right( 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 """ @@ -124,7 +116,6 @@ def insort_left( >>> 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) @@ -134,12 +125,10 @@ def insort_left( 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 @@ -167,7 +156,6 @@ def insort_right( >>> 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) @@ -177,12 +165,10 @@ def insort_right( 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 @@ -191,29 +177,28 @@ def insort_right( sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item) -def binary_search(sorted_collection: list[int], item: int) -> int | None: - """Pure implementation of binary search algorithm in Python +def binary_search(sorted_collection: list[int], item: int) -> int: + """Pure implementation of a binary search algorithm in Python - Be careful collection must be ascending sorted, otherwise result will be + Be careful collection must be ascending sorted otherwise, the result will be unpredictable :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 + :return: index of the found item or -1 if the item is not found Examples: >>> binary_search([0, 5, 7, 10, 15], 0) 0 - >>> binary_search([0, 5, 7, 10, 15], 15) 4 - >>> binary_search([0, 5, 7, 10, 15], 5) 1 - >>> binary_search([0, 5, 7, 10, 15], 6) - + -1 """ + if list(sorted_collection) != sorted(sorted_collection): + raise ValueError("sorted_collection must be sorted in ascending order") left = 0 right = len(sorted_collection) - 1 @@ -226,66 +211,66 @@ def binary_search(sorted_collection: list[int], item: int) -> int | None: right = midpoint - 1 else: left = midpoint + 1 - return None + return -1 -def binary_search_std_lib(sorted_collection: list[int], item: int) -> int | None: - """Pure implementation of binary search algorithm in Python using stdlib +def binary_search_std_lib(sorted_collection: list[int], item: int) -> int: + """Pure implementation of a binary search algorithm in Python using stdlib - Be careful collection must be ascending sorted, otherwise result will be + Be careful collection must be ascending sorted otherwise, the result will be unpredictable :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 + :return: index of the found item or -1 if the item is not found Examples: >>> binary_search_std_lib([0, 5, 7, 10, 15], 0) 0 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 15) 4 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 5) 1 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 6) - + -1 """ + if list(sorted_collection) != sorted(sorted_collection): + raise ValueError("sorted_collection must be sorted in ascending order") index = bisect.bisect_left(sorted_collection, item) if index != len(sorted_collection) and sorted_collection[index] == item: return index - return None + return -1 def binary_search_by_recursion( - sorted_collection: list[int], item: int, left: int, right: int -) -> int | None: - """Pure implementation of binary search algorithm in Python by recursion + sorted_collection: list[int], item: int, left: int = 0, right: int = -1 +) -> int: + """Pure implementation of a binary search algorithm in Python by recursion - Be careful collection must be ascending sorted, otherwise result will be + Be careful collection must be ascending sorted otherwise, the result will be unpredictable First recursion should be started with left=0 and right=(len(sorted_collection)-1) :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 + :return: index of the found item or -1 if the item is not found Examples: >>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4) 0 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4) 4 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4) 1 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4) - + -1 """ + if right < 0: + right = len(sorted_collection) - 1 + if list(sorted_collection) != sorted(sorted_collection): + raise ValueError("sorted_collection must be sorted in ascending order") if right < left: - return None + return -1 midpoint = left + (right - left) // 2 @@ -297,12 +282,78 @@ def binary_search_by_recursion( return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right) +def exponential_search(sorted_collection: list[int], item: int) -> int: + """Pure implementation of an exponential search algorithm in Python + Resources used: + https://en.wikipedia.org/wiki/Exponential_search + + Be careful collection must be ascending sorted otherwise, result will be + unpredictable + + :param sorted_collection: some ascending sorted collection with comparable items + :param item: item value to search + :return: index of the found item or -1 if the item is not found + + the order of this algorithm is O(lg I) where I is index position of item if exist + + Examples: + >>> exponential_search([0, 5, 7, 10, 15], 0) + 0 + >>> exponential_search([0, 5, 7, 10, 15], 15) + 4 + >>> exponential_search([0, 5, 7, 10, 15], 5) + 1 + >>> exponential_search([0, 5, 7, 10, 15], 6) + -1 + """ + if list(sorted_collection) != sorted(sorted_collection): + raise ValueError("sorted_collection must be sorted in ascending order") + bound = 1 + while bound < len(sorted_collection) and sorted_collection[bound] < item: + bound *= 2 + left = bound // 2 + right = min(bound, len(sorted_collection) - 1) + last_result = binary_search_by_recursion( + sorted_collection=sorted_collection, item=item, left=left, right=right + ) + if last_result is None: + return -1 + return last_result + + +searches = ( # Fastest to slowest... + binary_search_std_lib, + binary_search, + exponential_search, + binary_search_by_recursion, +) + + if __name__ == "__main__": - user_input = input("Enter numbers separated by comma:\n").strip() + import doctest + import timeit + + doctest.testmod() + for search in searches: + name = f"{search.__name__:>26}" + print(f"{name}: {search([0, 5, 7, 10, 15], 10) = }") # type: ignore[operator] + + print("\nBenchmarks...") + setup = "collection = range(1000)" + for search in searches: + name = search.__name__ + print( + f"{name:>26}:", + timeit.timeit( + f"{name}(collection, 500)", setup=setup, number=5_000, globals=globals() + ), + ) + + user_input = input("\nEnter numbers separated by comma: ").strip() 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 None: + target = int(input("Enter a single number to be found in the list: ")) + result = binary_search(sorted_collection=collection, item=target) + if result == -1: print(f"{target} was not found in {collection}.") else: - print(f"{target} was found at position {result} in {collection}.") + print(f"{target} was found at position {result} of {collection}.") From 4707fdb0f27bdc1e7442ce5940da335d58885104 Mon Sep 17 00:00:00 2001 From: Saptadeep Banerjee <69459134+imSanko@users.noreply.github.com> Date: Sun, 22 Oct 2023 03:35:37 +0530 Subject: [PATCH 2541/2908] Add tests for Perfect_Number (#10745) * Added new tests! * [ADD]: Inproved Tests * fixed * Removed spaces * Changed the file name * Added Changes * changed the code and kept the test cases * changed the code and kept the test cases * missed the line * removed spaces * Update power_using_recursion.py * Added new tests in Signum * Few things added * Removed few stuff and added few changes * Fixed few things * Reverted the function * Update maths/signum.py Co-authored-by: Christian Clauss * Added few things * Update maths/signum.py Co-authored-by: Christian Clauss * Added the type hint back * Update signum.py * Added NEW tests for Perfect_Number * Update maths/special_numbers/perfect_number.py Co-authored-by: Christian Clauss * Added the line back * Update maths/special_numbers/perfect_number.py Co-authored-by: Christian Clauss * Fixed a space * Updated * Reverted changes * Added the old code and FIXED few LINES * Fixed few things * Changed Test CASES * Update perfect_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/perfect_number.py | 77 +++++++++++++++++++++++++ maths/special_numbers/perfect_number.py | 29 ++++++++-- 2 files changed, 102 insertions(+), 4 deletions(-) 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..df6b6e3d91d8 --- /dev/null +++ b/maths/perfect_number.py @@ -0,0 +1,77 @@ +""" +== 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: + """ + Check if a number is a perfect number. + + A perfect number is a positive integer that is equal to the sum of its proper + divisors (excluding itself). + + Args: + number: The number to be checked. + + Returns: + True if the number is a perfect number otherwise, 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. + Examples: + >>> perfect(27) + False + >>> perfect(28) + True + >>> perfect(29) + False + >>> perfect(6) + True + >>> perfect(12) + False + >>> perfect(496) + True + >>> perfect(8128) + True + >>> perfect(0) + False + >>> perfect(-1) + False + >>> perfect(12.34) + Traceback (most recent call last): + ... + ValueError: number must an integer + >>> perfect("Hello") + Traceback (most recent call last): + ... + ValueError: number must an integer + """ + if not isinstance(number, int): + raise ValueError("number must an integer") + if number <= 0: + return False + return sum(i for i in range(1, number // 2 + 1) if number % i == 0) == number + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + print("Program to check whether a number is a Perfect number or not...") + try: + number = int(input("Enter a positive integer: ").strip()) + except ValueError: + msg = "number must an integer" + print(msg) + raise ValueError(msg) + + print(f"{number} is {'' if perfect(number) else 'not '}a Perfect Number.") diff --git a/maths/special_numbers/perfect_number.py b/maths/special_numbers/perfect_number.py index 148e988fb4c5..160ab2d967ad 100644 --- a/maths/special_numbers/perfect_number.py +++ b/maths/special_numbers/perfect_number.py @@ -14,16 +14,37 @@ def perfect(number: int) -> bool: """ + Check if a number is a perfect number. + + A perfect number is a positive integer that is equal to the sum of its proper + divisors (excluding itself). + + Args: + number: The number to be checked. + + Returns: + True if the number is a perfect number, False otherwise. + + Examples: >>> 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. + >>> perfect(6) + True + >>> perfect(12) + False + >>> perfect(496) + True + >>> perfect(8128) + True + >>> perfect(0) + >>> perfect(-3) + >>> perfect(12.34) + >>> perfect("day") + >>> perfect(["call"]) """ return sum(i for i in range(1, number // 2 + 1) if number % i == 0) == number From d9562033f6b15c17e0b48181c087731751abd7a6 Mon Sep 17 00:00:00 2001 From: Barun Parua <76466796+Baron105@users.noreply.github.com> Date: Sun, 22 Oct 2023 04:03:50 +0530 Subject: [PATCH 2542/2908] added a function to calculate perceived frequency by observer using Doppler Effect (#10776) * avg and mps speed formulae added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * avg and mps speed formulae added * fixed_spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ws * added amicable numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added amicable numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed * changed name of file and added code improvements * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * issues fixed due to pi * requested changes added * Created doppler_effect_of_sound.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated doppler_effect_of_sound.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added desc names * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed spacing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * renamed doppler_effect_of_sound.py to doppler_frequency.py * used expection handling rather than print statements * fixed spacing for ruff * Update doppler_frequency.py This is super slick! Well done. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/special_numbers/perfect_number.py | 32 +++++- .../{perceptron.py => perceptron.py.DISABLED} | 0 physics/doppler_frequency.py | 104 ++++++++++++++++++ 3 files changed, 132 insertions(+), 4 deletions(-) rename neural_network/{perceptron.py => perceptron.py.DISABLED} (100%) create mode 100644 physics/doppler_frequency.py diff --git a/maths/special_numbers/perfect_number.py b/maths/special_numbers/perfect_number.py index 160ab2d967ad..a022dc677638 100644 --- a/maths/special_numbers/perfect_number.py +++ b/maths/special_numbers/perfect_number.py @@ -25,6 +25,10 @@ def perfect(number: int) -> bool: Returns: True if the number is a perfect number, False otherwise. + 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. + Examples: >>> perfect(27) False @@ -41,15 +45,35 @@ def perfect(number: int) -> bool: >>> perfect(8128) True >>> perfect(0) - >>> perfect(-3) + False + >>> perfect(-1) + False >>> perfect(12.34) - >>> perfect("day") - >>> perfect(["call"]) + Traceback (most recent call last): + ... + ValueError: number must be an integer + >>> perfect("Hello") + Traceback (most recent call last): + ... + ValueError: number must be an integer """ + if not isinstance(number, int): + raise ValueError("number must be an integer") + if number <= 0: + return False return sum(i for i in range(1, number // 2 + 1) if number % i == 0) == number if __name__ == "__main__": + from doctest import testmod + + testmod() print("Program to check whether a number is a Perfect number or not...") - number = int(input("Enter number: ").strip()) + try: + number = int(input("Enter a positive integer: ").strip()) + except ValueError: + msg = "number must be an integer" + print(msg) + raise ValueError(msg) + print(f"{number} is {'' if perfect(number) else 'not '}a Perfect Number.") diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py.DISABLED similarity index 100% rename from neural_network/perceptron.py rename to neural_network/perceptron.py.DISABLED diff --git a/physics/doppler_frequency.py b/physics/doppler_frequency.py new file mode 100644 index 000000000000..2a761c72d9b8 --- /dev/null +++ b/physics/doppler_frequency.py @@ -0,0 +1,104 @@ +""" +Doppler's effect + +The Doppler effect (also Doppler shift) is the change in the frequency of a wave in +relation to an observer who is moving relative to the source of the wave. The Doppler +effect is named after the physicist Christian Doppler. A common example of Doppler +shift is the change of pitch heard when a vehicle sounding a horn approaches and +recedes from an observer. + +The reason for the Doppler effect is that when the source of the waves is moving +towards the observer, each successive wave crest is emitted from a position closer to +the observer than the crest of the previous wave. Therefore, each wave takes slightly +less time to reach the observer than the previous wave. Hence, the time between the +arrivals of successive wave crests at the observer is reduced, causing an increase in +the frequency. Similarly, if the source of waves is moving away from the observer, +each wave is emitted from a position farther from the observer than the previous wave, +so the arrival time between successive waves is increased, reducing the frequency. + +If the source of waves is stationary but the observer is moving with respect to the +source, the transmission velocity of the waves changes (ie the rate at which the +observer receives waves) even if the wavelength and frequency emitted from the source +remain constant. + +These results are all summarized by the Doppler formula: + + f = (f0 * (v + v0)) / (v - vs) + +where: + f: frequency of the wave + f0: frequency of the wave when the source is stationary + v: velocity of the wave in the medium + v0: velocity of the observer, positive if the observer is moving towards the source + vs: velocity of the source, positive if the source is moving towards the observer + +Doppler's effect has many applications in physics and engineering, such as radar, +astronomy, medical imaging, and seismology. + +References: +https://en.wikipedia.org/wiki/Doppler_effect + +Now, we will implement a function that calculates the frequency of a wave as a function +of the frequency of the wave when the source is stationary, the velocity of the wave +in the medium, the velocity of the observer and the velocity of the source. +""" + + +def doppler_effect( + org_freq: float, wave_vel: float, obs_vel: float, src_vel: float +) -> float: + """ + Input Parameters: + ----------------- + org_freq: frequency of the wave when the source is stationary + wave_vel: velocity of the wave in the medium + obs_vel: velocity of the observer, +ve if the observer is moving towards the source + src_vel: velocity of the source, +ve if the source is moving towards the observer + + Returns: + -------- + f: frequency of the wave as perceived by the observer + + Docstring Tests: + >>> doppler_effect(100, 330, 10, 0) # observer moving towards the source + 103.03030303030303 + >>> doppler_effect(100, 330, -10, 0) # observer moving away from the source + 96.96969696969697 + >>> doppler_effect(100, 330, 0, 10) # source moving towards the observer + 103.125 + >>> doppler_effect(100, 330, 0, -10) # source moving away from the observer + 97.05882352941177 + >>> doppler_effect(100, 330, 10, 10) # source & observer moving towards each other + 106.25 + >>> doppler_effect(100, 330, -10, -10) # source and observer moving away + 94.11764705882354 + >>> doppler_effect(100, 330, 10, 330) # source moving at same speed as the wave + Traceback (most recent call last): + ... + ZeroDivisionError: Division by zero implies vs=v and observer in front of the source + >>> doppler_effect(100, 330, 10, 340) # source moving faster than the wave + Traceback (most recent call last): + ... + ValueError: Non-positive frequency implies vs>v or v0>v (in the opposite direction) + >>> doppler_effect(100, 330, -340, 10) # observer moving faster than the wave + Traceback (most recent call last): + ... + ValueError: Non-positive frequency implies vs>v or v0>v (in the opposite direction) + """ + + if wave_vel == src_vel: + raise ZeroDivisionError( + "Division by zero implies vs=v and observer in front of the source" + ) + doppler_freq = (org_freq * (wave_vel + obs_vel)) / (wave_vel - src_vel) + if doppler_freq <= 0: + raise ValueError( + "Non-positive frequency implies vs>v or v0>v (in the opposite direction)" + ) + return doppler_freq + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c92e86bd7950b443fe39ccb19b587df44feaa068 Mon Sep 17 00:00:00 2001 From: "Precious C. Jacob" <72174492+PreciousJac0b@users.noreply.github.com> Date: Sun, 22 Oct 2023 00:33:49 +0100 Subject: [PATCH 2543/2908] Add tests to data_structures/linked_list/swap_nodes.py (#10751) * Added doctests to the swap_nodes file under linkedlist data structure * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added doctests to the swap_nodes file under linkedlist data structure * Added doctests to the swap_nodes file under linkedlist data structure * Added doctests to the swap_nodes file under linkedlist data structure * Update swap_nodes.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/linked_list/swap_nodes.py | 152 +++++++++++++++------- 1 file changed, 108 insertions(+), 44 deletions(-) diff --git a/data_structures/linked_list/swap_nodes.py b/data_structures/linked_list/swap_nodes.py index 31dcb02bfa9a..d66512087d2d 100644 --- a/data_structures/linked_list/swap_nodes.py +++ b/data_structures/linked_list/swap_nodes.py @@ -1,49 +1,73 @@ +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass from typing import Any +@dataclass class Node: - def __init__(self, data: Any) -> None: - """ - Initialize a new Node with the given data. - - Args: - data: The data to be stored in the node. - - """ - self.data = data - self.next: Node | None = None # Reference to the next node + data: Any + next_node: Node | None = None +@dataclass class LinkedList: - def __init__(self) -> None: + head: Node | None = None + + def __iter__(self) -> Iterator: """ - Initialize an empty Linked List. + >>> linked_list = LinkedList() + >>> list(linked_list) + [] + >>> linked_list.push(0) + >>> tuple(linked_list) + (0,) """ - self.head: Node | None = None # Reference to the head (first node) + node = self.head + while node: + yield node.data + node = node.next_node - def print_list(self): + def __len__(self) -> int: """ - Print the elements of the Linked List in order. + >>> linked_list = LinkedList() + >>> len(linked_list) + 0 + >>> linked_list.push(0) + >>> len(linked_list) + 1 """ - temp = self.head - while temp is not None: - print(temp.data, end=" ") - temp = temp.next - print() + return sum(1 for _ in self) def push(self, new_data: Any) -> None: """ Add a new node with the given data to the beginning of the Linked List. + Args: new_data (Any): The data to be added to the new node. + + Returns: + None + + Examples: + >>> linked_list = LinkedList() + >>> linked_list.push(5) + >>> linked_list.push(4) + >>> linked_list.push(3) + >>> linked_list.push(2) + >>> linked_list.push(1) + >>> list(linked_list) + [1, 2, 3, 4, 5] """ new_node = Node(new_data) - new_node.next = self.head + new_node.next_node = self.head self.head = new_node - def swap_nodes(self, node_data_1, node_data_2) -> None: + def swap_nodes(self, node_data_1: Any, node_data_2: Any) -> None: """ Swap the positions of two nodes in the Linked List based on their data values. + Args: node_data_1: Data value of the first node to be swapped. node_data_2: Data value of the second node to be swapped. @@ -51,34 +75,74 @@ def swap_nodes(self, node_data_1, node_data_2) -> None: Note: If either of the specified data values isn't found then, no swapping occurs. + + Examples: + When both values are present in a linked list. + >>> linked_list = LinkedList() + >>> linked_list.push(5) + >>> linked_list.push(4) + >>> linked_list.push(3) + >>> linked_list.push(2) + >>> linked_list.push(1) + >>> list(linked_list) + [1, 2, 3, 4, 5] + >>> linked_list.swap_nodes(1, 5) + >>> tuple(linked_list) + (5, 2, 3, 4, 1) + + When one value is present and the other isn't in the linked list. + >>> second_list = LinkedList() + >>> second_list.push(6) + >>> second_list.push(7) + >>> second_list.push(8) + >>> second_list.push(9) + >>> second_list.swap_nodes(1, 6) is None + True + + When both values are absent in the linked list. + >>> second_list = LinkedList() + >>> second_list.push(10) + >>> second_list.push(9) + >>> second_list.push(8) + >>> second_list.push(7) + >>> second_list.swap_nodes(1, 3) is None + True + + When linkedlist is empty. + >>> second_list = LinkedList() + >>> second_list.swap_nodes(1, 3) is None + True + + Returns: + None """ if node_data_1 == node_data_2: return - else: - 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 - # Swap the data values of the two nodes - node_1.data, node_2.data = node_2.data, node_1.data + node_1 = self.head + while node_1 and node_1.data != node_data_1: + node_1 = node_1.next_node + node_2 = self.head + while node_2 and node_2.data != node_data_2: + node_2 = node_2.next_node + if node_1 is None or node_2 is None: + return + # Swap the data values of the two nodes + node_1.data, node_2.data = node_2.data, node_1.data if __name__ == "__main__": - ll = LinkedList() - for i in range(5, 0, -1): - ll.push(i) + """ + Python script that outputs the swap of nodes in a linked list. + """ + from doctest import testmod - print("Original Linked List:") - ll.print_list() - - ll.swap_nodes(1, 4) - print("After swapping the nodes whose data is 1 and 4:") + testmod() + linked_list = LinkedList() + for i in range(5, 0, -1): + linked_list.push(i) - ll.print_list() + print(f"Original Linked List: {list(linked_list)}") + linked_list.swap_nodes(1, 4) + print(f"Modified Linked List: {list(linked_list)}") + print("After swapping the nodes whose data is 1 and 4.") From d73a4c2ee035698de437086230985574766f195b Mon Sep 17 00:00:00 2001 From: santiditomas <72716997+santiditomas@users.noreply.github.com> Date: Sat, 21 Oct 2023 20:59:41 -0300 Subject: [PATCH 2544/2908] adding new physics algorithm: center of mass (#10743) * adding new physics algorithm: center of mass * Add changes requested by the reviewer * Add changes requested by the reviewer * Update center_of_mass.py * Update center_of_mass.py --------- Co-authored-by: Christian Clauss --- physics/center_of_mass.py | 109 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 physics/center_of_mass.py diff --git a/physics/center_of_mass.py b/physics/center_of_mass.py new file mode 100644 index 000000000000..bd9ba2480584 --- /dev/null +++ b/physics/center_of_mass.py @@ -0,0 +1,109 @@ +""" +Calculating the center of mass for a discrete system of particles, given their +positions and masses. + +Description: + +In physics, the center of mass of a distribution of mass in space (sometimes referred +to as the barycenter or balance point) is the unique point at any given time where the +weighted relative position of the distributed mass sums to zero. This is the point to +which a force may be applied to cause a linear acceleration without an angular +acceleration. + +Calculations in mechanics are often simplified when formulated with respect to the +center of mass. It is a hypothetical point where the entire mass of an object may be +assumed to be concentrated to visualize its motion. In other words, the center of mass +is the particle equivalent of a given object for the application of Newton's laws of +motion. + +In the case of a system of particles P_i, i = 1, ..., n , each with mass m_i that are +located in space with coordinates r_i, i = 1, ..., n , the coordinates R of the center +of mass corresponds to: + +R = (Σ(mi * ri) / Σ(mi)) + +Reference: https://en.wikipedia.org/wiki/Center_of_mass +""" +from collections import namedtuple + +Particle = namedtuple("Particle", "x y z mass") # noqa: PYI024 +Coord3D = namedtuple("Coord3D", "x y z") # noqa: PYI024 + + +def center_of_mass(particles: list[Particle]) -> Coord3D: + """ + Input Parameters + ---------------- + particles: list(Particle): + A list of particles where each particle is a tuple with it´s (x, y, z) position and + it´s mass. + + Returns + ------- + Coord3D: + A tuple with the coordinates of the center of mass (Xcm, Ycm, Zcm) rounded to two + decimal places. + + Examples + -------- + >>> center_of_mass([ + ... Particle(1.5, 4, 3.4, 4), + ... Particle(5, 6.8, 7, 8.1), + ... Particle(9.4, 10.1, 11.6, 12) + ... ]) + Coord3D(x=6.61, y=7.98, z=8.69) + + >>> center_of_mass([ + ... Particle(1, 2, 3, 4), + ... Particle(5, 6, 7, 8), + ... Particle(9, 10, 11, 12) + ... ]) + Coord3D(x=6.33, y=7.33, z=8.33) + + >>> center_of_mass([ + ... Particle(1, 2, 3, -4), + ... Particle(5, 6, 7, 8), + ... Particle(9, 10, 11, 12) + ... ]) + Traceback (most recent call last): + ... + ValueError: Mass of all particles must be greater than 0 + + >>> center_of_mass([ + ... Particle(1, 2, 3, 0), + ... Particle(5, 6, 7, 8), + ... Particle(9, 10, 11, 12) + ... ]) + Traceback (most recent call last): + ... + ValueError: Mass of all particles must be greater than 0 + + >>> center_of_mass([]) + Traceback (most recent call last): + ... + ValueError: No particles provided + """ + if not particles: + raise ValueError("No particles provided") + + if any(particle.mass <= 0 for particle in particles): + raise ValueError("Mass of all particles must be greater than 0") + + total_mass = sum(particle.mass for particle in particles) + + center_of_mass_x = round( + sum(particle.x * particle.mass for particle in particles) / total_mass, 2 + ) + center_of_mass_y = round( + sum(particle.y * particle.mass for particle in particles) / total_mass, 2 + ) + center_of_mass_z = round( + sum(particle.z * particle.mass for particle in particles) / total_mass, 2 + ) + return Coord3D(center_of_mass_x, center_of_mass_y, center_of_mass_z) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0601b56173021fe96fb070d20085962b036e85c3 Mon Sep 17 00:00:00 2001 From: gio-puter <103840942+gio-puter@users.noreply.github.com> Date: Sat, 21 Oct 2023 22:42:26 -0700 Subject: [PATCH 2545/2908] Add tests without modifying code (#10740) * Contributes to #9943 Added doctest to largest_of_very_large_numbers.py Added doctest to word_patterns.py Added doctest to onepad_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Contributes to #9943 Added doctest to maths/largest_of_very_large_numbers.py Added doctest to strings/word_patterns.py Added doctest to ciphers/onepad_cipher.py * Add tests without modifying code #10740 Added test to maths/largest_of_very_large_numbers Added test to strings/word_patterns.py Added test to ciphers/onepad_cipher.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ciphers/onepad_cipher.py | 37 ++++++++++++++++++++++++-- maths/largest_of_very_large_numbers.py | 13 +++++++++ strings/word_patterns.py | 21 +++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/ciphers/onepad_cipher.py b/ciphers/onepad_cipher.py index 4bfe35b7180a..c4fb22e14a06 100644 --- a/ciphers/onepad_cipher.py +++ b/ciphers/onepad_cipher.py @@ -4,7 +4,27 @@ class Onepad: @staticmethod def encrypt(text: str) -> tuple[list[int], list[int]]: - """Function to encrypt text using pseudo-random numbers""" + """ + Function to encrypt text using pseudo-random numbers + >>> Onepad().encrypt("") + ([], []) + >>> Onepad().encrypt([]) + ([], []) + >>> random.seed(1) + >>> Onepad().encrypt(" ") + ([6969], [69]) + >>> random.seed(1) + >>> Onepad().encrypt("Hello") + ([9729, 114756, 4653, 31309, 10492], [69, 292, 33, 131, 61]) + >>> Onepad().encrypt(1) + Traceback (most recent call last): + ... + TypeError: 'int' object is not iterable + >>> Onepad().encrypt(1.1) + Traceback (most recent call last): + ... + TypeError: 'float' object is not iterable + """ plain = [ord(i) for i in text] key = [] cipher = [] @@ -17,7 +37,20 @@ def encrypt(text: str) -> tuple[list[int], list[int]]: @staticmethod def decrypt(cipher: list[int], key: list[int]) -> str: - """Function to decrypt text using pseudo-random numbers.""" + """ + Function to decrypt text using pseudo-random numbers. + >>> Onepad().decrypt([], []) + '' + >>> Onepad().decrypt([35], []) + '' + >>> Onepad().decrypt([], [35]) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> random.seed(1) + >>> Onepad().decrypt([9729, 114756, 4653, 31309, 10492], [69, 292, 33, 131, 61]) + 'Hello' + """ plain = [] for i in range(len(key)): p = int((cipher[i] - (key[i]) ** 2) / key[i]) diff --git a/maths/largest_of_very_large_numbers.py b/maths/largest_of_very_large_numbers.py index 7e7fea004958..eb5c121fd262 100644 --- a/maths/largest_of_very_large_numbers.py +++ b/maths/largest_of_very_large_numbers.py @@ -4,6 +4,19 @@ def res(x, y): + """ + Reduces large number to a more manageable number + >>> res(5, 7) + 4.892790030352132 + >>> res(0, 5) + 0 + >>> res(3, 0) + 1 + >>> res(-1, 5) + Traceback (most recent call last): + ... + ValueError: math domain error + """ 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) diff --git a/strings/word_patterns.py b/strings/word_patterns.py index d12d267e7b35..ed603e9fefeb 100644 --- a/strings/word_patterns.py +++ b/strings/word_patterns.py @@ -1,11 +1,32 @@ def get_word_pattern(word: str) -> str: """ + Returns numerical pattern of character appearances in given word + >>> get_word_pattern("") + '' + >>> get_word_pattern(" ") + '0' >>> 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' + >>> get_word_pattern() + Traceback (most recent call last): + ... + TypeError: get_word_pattern() missing 1 required positional argument: 'word' + >>> get_word_pattern(1) + Traceback (most recent call last): + ... + AttributeError: 'int' object has no attribute 'upper' + >>> get_word_pattern(1.1) + Traceback (most recent call last): + ... + AttributeError: 'float' object has no attribute 'upper' + >>> get_word_pattern([]) + Traceback (most recent call last): + ... + AttributeError: 'list' object has no attribute 'upper' """ word = word.upper() next_num = 0 From 7d0f6e012acb42271652f9a398675305b7e270d2 Mon Sep 17 00:00:00 2001 From: Kento <75509362+nkstonks@users.noreply.github.com> Date: Sun, 22 Oct 2023 20:08:08 +1100 Subject: [PATCH 2546/2908] Updated doctests for nor_gate (#10791) * added other possible cases * added test for correct output of truth table * updating DIRECTORY.md * Update nor_gate.py --------- Co-authored-by: = <=> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 6 ++-- boolean_algebra/nor_gate.py | 55 +++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 9e0166ad80c5..c37c4f99ba88 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -541,7 +541,7 @@ * [Dimensionality Reduction](machine_learning/dimensionality_reduction.py) * Forecasting * [Run](machine_learning/forecasting/run.py) - * [Frequent Pattern Growth Algorithm](machine_learning/frequent_pattern_growth.py) + * [Frequent Pattern Growth](machine_learning/frequent_pattern_growth.py) * [Gradient Descent](machine_learning/gradient_descent.py) * [K Means Clust](machine_learning/k_means_clust.py) * [K Nearest Neighbours](machine_learning/k_nearest_neighbours.py) @@ -649,6 +649,7 @@ * [Numerical Integration](maths/numerical_integration.py) * [Odd Sieve](maths/odd_sieve.py) * [Perfect Cube](maths/perfect_cube.py) + * [Perfect Number](maths/perfect_number.py) * [Perfect Square](maths/perfect_square.py) * [Persistence](maths/persistence.py) * [Pi Generator](maths/pi_generator.py) @@ -767,7 +768,6 @@ * [Swish](neural_network/activation_functions/swish.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) - * [Perceptron](neural_network/perceptron.py) * [Simple Neural Network](neural_network/simple_neural_network.py) ## Other @@ -803,8 +803,10 @@ * [Archimedes Principle Of Buoyant Force](physics/archimedes_principle_of_buoyant_force.py) * [Basic Orbital Capture](physics/basic_orbital_capture.py) * [Casimir Effect](physics/casimir_effect.py) + * [Center Of Mass](physics/center_of_mass.py) * [Centripetal Force](physics/centripetal_force.py) * [Coulombs Law](physics/coulombs_law.py) + * [Doppler Frequency](physics/doppler_frequency.py) * [Grahams Law](physics/grahams_law.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) * [Hubble Parameter](physics/hubble_parameter.py) diff --git a/boolean_algebra/nor_gate.py b/boolean_algebra/nor_gate.py index 2c27b80afdbe..0c8ab1c0af61 100644 --- a/boolean_algebra/nor_gate.py +++ b/boolean_algebra/nor_gate.py @@ -1,15 +1,18 @@ """ -A NOR Gate is a logic gate in boolean algebra which results to false(0) -if any of the input is 1, and True(1) if both the inputs are 0. +A NOR Gate is a logic gate in boolean algebra which results in false(0) if any of the +inputs is 1, and True(1) if all inputs are 0. Following is the truth table of a NOR Gate: - | Input 1 | Input 2 | Output | - | 0 | 0 | 1 | - | 0 | 1 | 0 | - | 1 | 0 | 0 | - | 1 | 1 | 0 | + Truth Table of NOR Gate: + | Input 1 | Input 2 | Output | + | 0 | 0 | 1 | + | 0 | 1 | 0 | + | 1 | 0 | 0 | + | 1 | 1 | 0 | -Following is the code implementation of the NOR Gate + Code provided by Akshaj Vishwanathan +https://www.geeksforgeeks.org/logic-gates-in-python """ +from collections.abc import Callable def nor_gate(input_1: int, input_2: int) -> int: @@ -30,19 +33,35 @@ def nor_gate(input_1: int, input_2: int) -> int: return int(input_1 == input_2 == 0) -def main() -> None: - print("Truth Table of NOR Gate:") - print("| Input 1 | Input 2 | Output |") - print(f"| 0 | 0 | {nor_gate(0, 0)} |") - print(f"| 0 | 1 | {nor_gate(0, 1)} |") - print(f"| 1 | 0 | {nor_gate(1, 0)} |") - print(f"| 1 | 1 | {nor_gate(1, 1)} |") +def truth_table(func: Callable) -> str: + """ + >>> print(truth_table(nor_gate)) + Truth Table of NOR Gate: + | Input 1 | Input 2 | Output | + | 0 | 0 | 1 | + | 0 | 1 | 0 | + | 1 | 0 | 0 | + | 1 | 1 | 0 | + """ + + def make_table_row(items: list | tuple) -> str: + """ + >>> make_table_row(("One", "Two", "Three")) + '| One | Two | Three |' + """ + return f"| {' | '.join(f'{item:^8}' for item in items)} |" + + return "\n".join( + ( + "Truth Table of NOR Gate:", + make_table_row(("Input 1", "Input 2", "Output")), + *[make_table_row((i, j, func(i, j))) for i in (0, 1) for j in (0, 1)], + ) + ) if __name__ == "__main__": import doctest doctest.testmod() - main() -"""Code provided by Akshaj Vishwanathan""" -"""Reference: https://www.geeksforgeeks.org/logic-gates-in-python/""" + print(truth_table(nor_gate)) From 6c8743f1e62c785e58a45f785b380f27693aadf9 Mon Sep 17 00:00:00 2001 From: Jeel Gajera <83470656+JeelGajera@users.noreply.github.com> Date: Sun, 22 Oct 2023 19:21:30 +0530 Subject: [PATCH 2547/2908] Add: Time Conversion Function (#10749) * Add: Time Conversion Function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update conversions/time_conversions.py Co-authored-by: Christian Clauss * fix: required changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: err * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update time_conversions.py --------- Co-authored-by: Jeel Gajera Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + conversions/time_conversions.py | 86 +++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 conversions/time_conversions.py diff --git a/DIRECTORY.md b/DIRECTORY.md index c37c4f99ba88..f45102ae1ce1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -174,6 +174,7 @@ * [Roman Numerals](conversions/roman_numerals.py) * [Speed Conversions](conversions/speed_conversions.py) * [Temperature Conversions](conversions/temperature_conversions.py) + * [Time Conversions](conversions/time_conversions.py) * [Volume Conversions](conversions/volume_conversions.py) * [Weight Conversion](conversions/weight_conversion.py) diff --git a/conversions/time_conversions.py b/conversions/time_conversions.py new file mode 100644 index 000000000000..8c30f5bc4a45 --- /dev/null +++ b/conversions/time_conversions.py @@ -0,0 +1,86 @@ +""" +A unit of time is any particular time interval, used as a standard way of measuring or +expressing duration. The base unit of time in the International System of Units (SI), +and by extension most of the Western world, is the second, defined as about 9 billion +oscillations of the caesium atom. + +https://en.wikipedia.org/wiki/Unit_of_time +""" + +time_chart: dict[str, float] = { + "seconds": 1.0, + "minutes": 60.0, # 1 minute = 60 sec + "hours": 3600.0, # 1 hour = 60 minutes = 3600 seconds + "days": 86400.0, # 1 day = 24 hours = 1440 min = 86400 sec + "weeks": 604800.0, # 1 week=7d=168hr=10080min = 604800 sec + "months": 2629800.0, # Approximate value for a month in seconds + "years": 31557600.0, # Approximate value for a year in seconds +} + +time_chart_inverse: dict[str, float] = { + key: 1 / value for key, value in time_chart.items() +} + + +def convert_time(time_value: float, unit_from: str, unit_to: str) -> float: + """ + Convert time from one unit to another using the time_chart above. + + >>> convert_time(3600, "seconds", "hours") + 1.0 + >>> convert_time(3500, "Seconds", "Hours") + 0.972 + >>> convert_time(1, "DaYs", "hours") + 24.0 + >>> convert_time(120, "minutes", "SeCoNdS") + 7200.0 + >>> convert_time(2, "WEEKS", "days") + 14.0 + >>> convert_time(0.5, "hours", "MINUTES") + 30.0 + >>> convert_time(-3600, "seconds", "hours") + Traceback (most recent call last): + ... + ValueError: 'time_value' must be a non-negative number. + >>> convert_time("Hello", "hours", "minutes") + Traceback (most recent call last): + ... + ValueError: 'time_value' must be a non-negative number. + >>> convert_time([0, 1, 2], "weeks", "days") + Traceback (most recent call last): + ... + ValueError: 'time_value' must be a non-negative number. + >>> convert_time(1, "cool", "century") # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Invalid unit cool is not in seconds, minutes, hours, days, weeks, ... + >>> convert_time(1, "seconds", "hot") # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Invalid unit hot is not in seconds, minutes, hours, days, weeks, ... + """ + if not isinstance(time_value, (int, float)) or time_value < 0: + msg = "'time_value' must be a non-negative number." + raise ValueError(msg) + + unit_from = unit_from.lower() + unit_to = unit_to.lower() + if unit_from not in time_chart or unit_to not in time_chart: + invalid_unit = unit_from if unit_from not in time_chart else unit_to + msg = f"Invalid unit {invalid_unit} is not in {', '.join(time_chart)}." + raise ValueError(msg) + + return round( + time_value * time_chart[unit_from] * time_chart_inverse[unit_to], + 3, + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{convert_time(3600,'seconds', 'hours') = :,}") + print(f"{convert_time(360, 'days', 'months') = :,}") + print(f"{convert_time(360, 'months', 'years') = :,}") + print(f"{convert_time(1, 'years', 'seconds') = :,}") From a8b94abc8b9131e260a5281f4c95a0d4f2d03325 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Mon, 23 Oct 2023 00:21:56 +0530 Subject: [PATCH 2548/2908] Enhance readability of N Queens (#9265) * Enhance readability of N Queens * Simplify is_safe code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- backtracking/n_queens.py | 53 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index bbf0ce44f91c..0f237d95e7c8 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -17,40 +17,39 @@ def is_safe(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. - Parameters : - board(2D matrix) : board - row ,column : coordinates of the cell on a board + Parameters: + board (2D matrix): The chessboard + row, column: Coordinates of the cell on the board - Returns : + 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 + + n = len(board) # Size of the board + + # Check if there is any queen in the same row, column, + # left upper diagonal, and right upper diagonal + return ( + all(board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, n))) + and all( + board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, -1, -1)) + ) + and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, n))) + and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, -1, -1))) + ) 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 + This function 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 a board with a successful combination and that combination is appended to the solution list and the board is printed. - """ solution.append(board) printboard(board) @@ -58,9 +57,9 @@ def solve(board: list[list[int]], row: int) -> bool: return True for i in range(len(board)): """ - For every row it iterates through each column to check if it is feasible to + 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 is_safe(board, row, i): @@ -77,14 +76,14 @@ def printboard(board: list[list[int]]) -> None: for i in range(len(board)): for j in range(len(board)): if board[i][j] == 1: - print("Q", end=" ") + print("Q", end=" ") # Queen is present else: - print(".", end=" ") + print(".", end=" ") # Empty cell print() -# n=int(input("The no. of queens")) +# Number of queens (e.g., n=8 for an 8x8 board) 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)) +print("The total number of solutions are:", len(solution)) From fdb0635c71318da758fafcda80154d03dbbd5c5a Mon Sep 17 00:00:00 2001 From: Anshu Sharma <142900182+AnshuSharma111@users.noreply.github.com> Date: Mon, 23 Oct 2023 03:09:31 +0530 Subject: [PATCH 2549/2908] added doctest to playfair_cipher.py (#10823) * added doctest to playfair_cipher.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added newline to EOF andremoved trailing whitespace * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review --------- Co-authored-by: Keyboard-1 <142900182+Keyboard-1@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- ciphers/playfair_cipher.py | 59 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 7279fb23ecb2..86b45bc4fb6a 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -1,3 +1,24 @@ +""" +https://en.wikipedia.org/wiki/Playfair_cipher#Description + +The Playfair cipher was developed by Charles Wheatstone in 1854 +It's use was heavily promotedby Lord Playfair, hence its name + +Some features of the Playfair cipher are: + +1) It was the first literal diagram substitution cipher +2) It is a manual symmetric encryption technique +3) It is a multiple letter encryption cipher + +The implementation in the code below encodes alphabets only. +It removes spaces, special characters and numbers from the +code. + +Playfair is no longer used by military forces because of known +insecurities and of the advent of automated encryption devices. +This cipher is regarded as insecure since before World War I. +""" + import itertools import string from collections.abc import Generator, Iterable @@ -60,11 +81,26 @@ def generate_table(key: str) -> list[str]: def encode(plaintext: str, key: str) -> str: + """ + Encode the given plaintext using the Playfair cipher. + Takes the plaintext and the key as input and returns the encoded string. + + >>> encode("Hello", "MONARCHY") + 'CFSUPM' + >>> encode("attack on the left flank", "EMERGENCY") + 'DQZSBYFSDZFMFNLOHFDRSG' + >>> encode("Sorry!", "SPECIAL") + 'AVXETX' + >>> encode("Number 1", "NUMBER") + 'UMBENF' + >>> encode("Photosynthesis!", "THE SUN") + 'OEMHQHVCHESUKE' + """ + table = generate_table(key) plaintext = prepare_input(plaintext) ciphertext = "" - # https://en.wikipedia.org/wiki/Playfair_cipher#Description for char1, char2 in chunker(plaintext, 2): row1, col1 = divmod(table.index(char1), 5) row2, col2 = divmod(table.index(char2), 5) @@ -83,10 +119,20 @@ def encode(plaintext: str, key: str) -> str: def decode(ciphertext: str, key: str) -> str: + """ + Decode the input string using the provided key. + + >>> decode("BMZFAZRZDH", "HAZARD") + 'FIREHAZARD' + >>> decode("HNBWBPQT", "AUTOMOBILE") + 'DRIVINGX' + >>> decode("SLYSSAQS", "CASTLE") + 'ATXTACKX' + """ + table = generate_table(key) plaintext = "" - # https://en.wikipedia.org/wiki/Playfair_cipher#Description for char1, char2 in chunker(ciphertext, 2): row1, col1 = divmod(table.index(char1), 5) row2, col2 = divmod(table.index(char2), 5) @@ -102,3 +148,12 @@ def decode(ciphertext: str, key: str) -> str: plaintext += table[row2 * 5 + col1] return plaintext + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print("Encoded:", encode("BYE AND THANKS", "GREETING")) + print("Decoded:", decode("CXRBANRLBALQ", "GREETING")) From abd6bca074e8a846d5e306311845b46f7581012e Mon Sep 17 00:00:00 2001 From: Ankit Avinash <128812932+Void426@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:54:51 +0530 Subject: [PATCH 2550/2908] Added Binary Focal Cross Entropy (#10674) * Added Binary Focal Cross Entropy * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed Issue * Fixed Issue * Added BFCE loss to loss_functions.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update machine_learning/loss_functions.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- machine_learning/loss_functions.py | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py index 0fa0956ed572..ef34296360e2 100644 --- a/machine_learning/loss_functions.py +++ b/machine_learning/loss_functions.py @@ -39,6 +39,57 @@ def binary_cross_entropy( return np.mean(bce_loss) +def binary_focal_cross_entropy( + y_true: np.ndarray, + y_pred: np.ndarray, + gamma: float = 2.0, + alpha: float = 0.25, + epsilon: float = 1e-15, +) -> float: + """ + Calculate the mean binary focal cross-entropy (BFCE) loss between true labels + and predicted probabilities. + + BFCE loss quantifies dissimilarity between true labels (0 or 1) and predicted + probabilities. It's a variation of binary cross-entropy that addresses class + imbalance by focusing on hard examples. + + BCFE = -Σ(alpha * (1 - y_pred)**gamma * y_true * log(y_pred) + + (1 - alpha) * y_pred**gamma * (1 - y_true) * log(1 - y_pred)) + + Reference: [Lin et al., 2018](https://arxiv.org/pdf/1708.02002.pdf) + + Parameters: + - y_true: True binary labels (0 or 1). + - y_pred: Predicted probabilities for class 1. + - gamma: Focusing parameter for modulating the loss (default: 2.0). + - alpha: Weighting factor for class 1 (default: 0.25). + - epsilon: Small constant to avoid numerical instability. + + >>> true_labels = np.array([0, 1, 1, 0, 1]) + >>> predicted_probs = np.array([0.2, 0.7, 0.9, 0.3, 0.8]) + >>> binary_focal_cross_entropy(true_labels, predicted_probs) + 0.008257977659239775 + >>> true_labels = np.array([0, 1, 1, 0, 1]) + >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) + >>> binary_focal_cross_entropy(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + # Clip predicted probabilities to avoid log(0) + y_pred = np.clip(y_pred, epsilon, 1 - epsilon) + + bcfe_loss = -( + alpha * (1 - y_pred) ** gamma * y_true * np.log(y_pred) + + (1 - alpha) * y_pred**gamma * (1 - y_true) * np.log(1 - y_pred) + ) + + return np.mean(bcfe_loss) + + def categorical_cross_entropy( y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-15 ) -> float: From dc4e89805a642d1c6e3fe031276edbfde3c1f40c Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:57:33 +0530 Subject: [PATCH 2551/2908] Added docstring/documentation for sigmoid_function (#10756) * Added doctest for sigmoid_function * Added doctest for sigmoid_function * Added doctest for sigmoid_function --- machine_learning/logistic_regression.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 87bc8f6681cc..f9da0104ab4b 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -28,6 +28,21 @@ def sigmoid_function(z): + """ + Also known as Logistic Function. + + 1 + f(x) = ------- + 1 + e⁻ˣ + + The sigmoid function approaches a value of 1 as its input 'x' becomes + increasing positive. Opposite for negative values. + + Reference: https://en.wikipedia.org/wiki/Sigmoid_function + + @param z: input to the function + @returns: returns value in the range 0 to 1 + """ return 1 / (1 + np.exp(-z)) From 68faebe711899bf6072ceedb16ccf1fbdc7d2434 Mon Sep 17 00:00:00 2001 From: Pratik Tripathy <117454569+SilverDragonOfR@users.noreply.github.com> Date: Mon, 23 Oct 2023 11:05:10 +0530 Subject: [PATCH 2552/2908] feat: Add mass energy equivalence in physics and doctests (#10202) * updating DIRECTORY.md * feat: Add mass energy equivalence in physics * updating DIRECTORY.md * updating DIRECTORY.md * Apply suggestions from code review * Update physics/mass_energy_equivalence.py * Update mass_energy_equivalence.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- DIRECTORY.md | 1 + physics/mass_energy_equivalence.py | 77 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 physics/mass_energy_equivalence.py diff --git a/DIRECTORY.md b/DIRECTORY.md index f45102ae1ce1..c07e1550d1eb 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -815,6 +815,7 @@ * [Kinetic Energy](physics/kinetic_energy.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) * [Malus Law](physics/malus_law.py) + * [Mass Energy Equivalence](physics/mass_energy_equivalence.py) * [Mirror Formulae](physics/mirror_formulae.py) * [N Body Simulation](physics/n_body_simulation.py) * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) diff --git a/physics/mass_energy_equivalence.py b/physics/mass_energy_equivalence.py new file mode 100644 index 000000000000..4a4c7890f4e0 --- /dev/null +++ b/physics/mass_energy_equivalence.py @@ -0,0 +1,77 @@ +""" +Title: +Finding the energy equivalence of mass and mass equivalence of energy +by Einstein's equation. + +Description: +Einstein's mass-energy equivalence is a pivotal concept in theoretical physics. +It asserts that energy (E) and mass (m) are directly related by the speed of +light in vacuum (c) squared, as described in the equation E = mc². This means that +mass and energy are interchangeable; a mass increase corresponds to an energy increase, +and vice versa. This principle has profound implications in nuclear reactions, +explaining the release of immense energy from minuscule changes in atomic nuclei. + +Equations: +E = mc² and m = E/c², where m is mass, E is Energy, c is speed of light in vacuum. + +Reference: +https://en.wikipedia.org/wiki/Mass%E2%80%93energy_equivalence +""" + +from scipy.constants import c # speed of light in vacuum (299792458 m/s) + + +def energy_from_mass(mass: float) -> float: + """ + Calculates the Energy equivalence of the Mass using E = mc² + in SI units J from Mass in kg. + + mass (float): Mass of body. + + Usage example: + >>> energy_from_mass(124.56) + 1.11948945063458e+19 + >>> energy_from_mass(320) + 2.8760165719578165e+19 + >>> energy_from_mass(0) + 0.0 + >>> energy_from_mass(-967.9) + Traceback (most recent call last): + ... + ValueError: Mass can't be negative. + + """ + if mass < 0: + raise ValueError("Mass can't be negative.") + return mass * c**2 + + +def mass_from_energy(energy: float) -> float: + """ + Calculates the Mass equivalence of the Energy using m = E/c² + in SI units kg from Energy in J. + + energy (float): Mass of body. + + Usage example: + >>> mass_from_energy(124.56) + 1.3859169098203872e-15 + >>> mass_from_energy(320) + 3.560480179371579e-15 + >>> mass_from_energy(0) + 0.0 + >>> mass_from_energy(-967.9) + Traceback (most recent call last): + ... + ValueError: Energy can't be negative. + + """ + if energy < 0: + raise ValueError("Energy can't be negative.") + return energy / c**2 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From abc390967d5479ec74bfd384a86cefa5ddbf6d40 Mon Sep 17 00:00:00 2001 From: Paarth Goyal <138299656+pluto-tofu@users.noreply.github.com> Date: Mon, 23 Oct 2023 11:13:30 +0530 Subject: [PATCH 2553/2908] =?UTF-8?q?Added=20the=20algorithm=20to=20comput?= =?UTF-8?q?e=20the=20terminal=20velocity=20of=20an=20object=20fal=E2=80=A6?= =?UTF-8?q?=20(#10237)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added the algorithm to compute the terminal velocity of an object falling in a fluid * fixed spelling mistake * fixed issues in topic description * imported the value of g from scipy and changed the doctests accordingly * fixed formatting * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- physics/terminal_velocity.py | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 physics/terminal_velocity.py diff --git a/physics/terminal_velocity.py b/physics/terminal_velocity.py new file mode 100644 index 000000000000..cec54162e2b4 --- /dev/null +++ b/physics/terminal_velocity.py @@ -0,0 +1,60 @@ +""" +Title : Computing the terminal velocity of an object falling + through a fluid. + +Terminal velocity is defined as the highest velocity attained by an +object falling through a fluid. It is observed when the sum of drag force +and buoyancy is equal to the downward gravity force acting on the +object. The acceleration of the object is zero as the net force acting on +the object is zero. + +Vt = ((2 * m * g)/(ρ * A * Cd))^0.5 + +where : +Vt = Terminal velocity (in m/s) +m = Mass of the falling object (in Kg) +g = Acceleration due to gravity (value taken : imported from scipy) +ρ = Density of the fluid through which the object is falling (in Kg/m^3) +A = Projected area of the object (in m^2) +Cd = Drag coefficient (dimensionless) + +Reference : https://byjus.com/physics/derivation-of-terminal-velocity/ +""" + +from scipy.constants import g + + +def terminal_velocity( + mass: float, density: float, area: float, drag_coefficient: float +) -> float: + """ + >>> terminal_velocity(1, 25, 0.6, 0.77) + 1.3031197996044768 + >>> terminal_velocity(2, 100, 0.45, 0.23) + 1.9467947148674276 + >>> terminal_velocity(5, 50, 0.2, 0.5) + 4.428690551393267 + >>> terminal_velocity(-5, 50, -0.2, -2) + Traceback (most recent call last): + ... + ValueError: mass, density, area and the drag coefficient all need to be positive + >>> terminal_velocity(3, -20, -1, 2) + Traceback (most recent call last): + ... + ValueError: mass, density, area and the drag coefficient all need to be positive + >>> terminal_velocity(-2, -1, -0.44, -1) + Traceback (most recent call last): + ... + ValueError: mass, density, area and the drag coefficient all need to be positive + """ + if mass <= 0 or density <= 0 or area <= 0 or drag_coefficient <= 0: + raise ValueError( + "mass, density, area and the drag coefficient all need to be positive" + ) + return ((2 * mass * g) / (density * area * drag_coefficient)) ** 0.5 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a9cee1d933606092eb966a601eb1d9efd6e054af Mon Sep 17 00:00:00 2001 From: Dale Dai <145884899+CouldNot@users.noreply.github.com> Date: Sun, 22 Oct 2023 22:56:59 -0700 Subject: [PATCH 2554/2908] Add perfect cube binary search (#10477) * Add perfect cube binary search algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add support for testing negative perfect cubes * Add TypeError check for invalid inputs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/perfect_cube.py | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/maths/perfect_cube.py b/maths/perfect_cube.py index 9ad287e41e75..a732b7cce6c8 100644 --- a/maths/perfect_cube.py +++ b/maths/perfect_cube.py @@ -11,6 +11,45 @@ def perfect_cube(n: int) -> bool: return (val * val * val) == n +def perfect_cube_binary_search(n: int) -> bool: + """ + Check if a number is a perfect cube or not using binary search. + Time complexity : O(Log(n)) + Space complexity: O(1) + + >>> perfect_cube_binary_search(27) + True + >>> perfect_cube_binary_search(64) + True + >>> perfect_cube_binary_search(4) + False + >>> perfect_cube_binary_search("a") + Traceback (most recent call last): + ... + TypeError: perfect_cube_binary_search() only accepts integers + >>> perfect_cube_binary_search(0.1) + Traceback (most recent call last): + ... + TypeError: perfect_cube_binary_search() only accepts integers + """ + if not isinstance(n, int): + raise TypeError("perfect_cube_binary_search() only accepts integers") + if n < 0: + n = -n + left = 0 + right = n + while left <= right: + mid = left + (right - left) // 2 + if mid * mid * mid == n: + return True + elif mid * mid * mid < n: + left = mid + 1 + else: + right = mid - 1 + return False + + if __name__ == "__main__": - print(perfect_cube(27)) - print(perfect_cube(4)) + import doctest + + doctest.testmod() From a8b6bda993484b3be9fd541a10dd9ac9c4111dda Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 23 Oct 2023 03:31:30 -0400 Subject: [PATCH 2555/2908] Delete `arithmetic_analysis/` directory and relocate its contents (#10824) * Remove eval from arithmetic_analysis/newton_raphson.py * Relocate contents of arithmetic_analysis/ Delete the arithmetic_analysis/ directory and relocate its files because the purpose of the directory was always ill-defined. "Arithmetic analysis" isn't a field of math, and the directory's files contained algorithms for linear algebra, numerical analysis, and physics. Relocated the directory's linear algebra algorithms to linear_algebra/, its numerical analysis algorithms to a new subdirectory called maths/numerical_analysis/, and its single physics algorithm to physics/. * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 43 +- arithmetic_analysis/README.md | 7 - arithmetic_analysis/image_data/__init__.py | 0 .../gaussian_elimination.py | 0 .../jacobi_iteration_method.py | 406 +++++++++--------- .../lu_decomposition.py | 0 .../numerical_analysis}/bisection.py | 0 .../bisection_2.py} | 0 .../integration_by_simpson_approx.py | 0 .../numerical_analysis}/intersection.py | 0 .../nevilles_method.py | 0 .../newton_forward_interpolation.py | 0 .../numerical_analysis}/newton_method.py | 0 .../numerical_analysis}/newton_raphson.py | 35 +- .../newton_raphson_2.py} | 0 .../numerical_analysis}/newton_raphson_new.py | 0 .../numerical_integration.py | 0 maths/{ => numerical_analysis}/runge_kutta.py | 0 .../runge_kutta_fehlberg_45.py | 0 .../numerical_analysis}/secant_method.py | 0 .../{ => numerical_analysis}/simpson_rule.py | 0 maths/{ => numerical_analysis}/square_root.py | 0 .../image_data/2D_problems.jpg | Bin .../image_data/2D_problems_1.jpg | Bin .../image_data}/__init__.py | 0 .../in_static_equilibrium.py | 188 ++++---- 26 files changed, 335 insertions(+), 344 deletions(-) delete mode 100644 arithmetic_analysis/README.md delete mode 100644 arithmetic_analysis/image_data/__init__.py rename {arithmetic_analysis => linear_algebra}/gaussian_elimination.py (100%) rename {arithmetic_analysis => linear_algebra}/jacobi_iteration_method.py (96%) rename {arithmetic_analysis => linear_algebra}/lu_decomposition.py (100%) rename {arithmetic_analysis => maths/numerical_analysis}/bisection.py (100%) rename maths/{bisection.py => numerical_analysis/bisection_2.py} (100%) rename maths/{ => numerical_analysis}/integration_by_simpson_approx.py (100%) rename {arithmetic_analysis => maths/numerical_analysis}/intersection.py (100%) rename maths/{ => numerical_analysis}/nevilles_method.py (100%) rename {arithmetic_analysis => maths/numerical_analysis}/newton_forward_interpolation.py (100%) rename {arithmetic_analysis => maths/numerical_analysis}/newton_method.py (100%) rename {arithmetic_analysis => maths/numerical_analysis}/newton_raphson.py (61%) rename maths/{newton_raphson.py => numerical_analysis/newton_raphson_2.py} (100%) rename {arithmetic_analysis => maths/numerical_analysis}/newton_raphson_new.py (100%) rename maths/{ => numerical_analysis}/numerical_integration.py (100%) rename maths/{ => numerical_analysis}/runge_kutta.py (100%) rename maths/{ => numerical_analysis}/runge_kutta_fehlberg_45.py (100%) rename {arithmetic_analysis => maths/numerical_analysis}/secant_method.py (100%) rename maths/{ => numerical_analysis}/simpson_rule.py (100%) rename maths/{ => numerical_analysis}/square_root.py (100%) rename {arithmetic_analysis => physics}/image_data/2D_problems.jpg (100%) rename {arithmetic_analysis => physics}/image_data/2D_problems_1.jpg (100%) rename {arithmetic_analysis => physics/image_data}/__init__.py (100%) rename {arithmetic_analysis => physics}/in_static_equilibrium.py (96%) diff --git a/DIRECTORY.md b/DIRECTORY.md index c07e1550d1eb..1e3711fe8dda 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,17 +1,4 @@ -## Arithmetic Analysis - * [Bisection](arithmetic_analysis/bisection.py) - * [Gaussian Elimination](arithmetic_analysis/gaussian_elimination.py) - * [In Static Equilibrium](arithmetic_analysis/in_static_equilibrium.py) - * [Intersection](arithmetic_analysis/intersection.py) - * [Jacobi Iteration Method](arithmetic_analysis/jacobi_iteration_method.py) - * [Lu Decomposition](arithmetic_analysis/lu_decomposition.py) - * [Newton Forward Interpolation](arithmetic_analysis/newton_forward_interpolation.py) - * [Newton Method](arithmetic_analysis/newton_method.py) - * [Newton Raphson](arithmetic_analysis/newton_raphson.py) - * [Newton Raphson New](arithmetic_analysis/newton_raphson_new.py) - * [Secant Method](arithmetic_analysis/secant_method.py) - ## Audio Filters * [Butterworth Filter](audio_filters/butterworth_filter.py) * [Iir Filter](audio_filters/iir_filter.py) @@ -520,6 +507,9 @@ * [Test Knapsack](knapsack/tests/test_knapsack.py) ## Linear Algebra + * [Gaussian Elimination](linear_algebra/gaussian_elimination.py) + * [Jacobi Iteration Method](linear_algebra/jacobi_iteration_method.py) + * [Lu Decomposition](linear_algebra/lu_decomposition.py) * Src * [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py) * [Lib](linear_algebra/src/lib.py) @@ -583,7 +573,6 @@ * [Binary Multiplication](maths/binary_multiplication.py) * [Binomial Coefficient](maths/binomial_coefficient.py) * [Binomial Distribution](maths/binomial_distribution.py) - * [Bisection](maths/bisection.py) * [Ceil](maths/ceil.py) * [Chebyshev Distance](maths/chebyshev_distance.py) * [Check Polygon](maths/check_polygon.py) @@ -617,7 +606,6 @@ * [Germain Primes](maths/germain_primes.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) - * [Integration By Simpson Approx](maths/integration_by_simpson_approx.py) * [Interquartile Range](maths/interquartile_range.py) * [Is Int Palindrome](maths/is_int_palindrome.py) * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) @@ -644,10 +632,24 @@ * [Modular Exponential](maths/modular_exponential.py) * [Monte Carlo](maths/monte_carlo.py) * [Monte Carlo Dice](maths/monte_carlo_dice.py) - * [Nevilles Method](maths/nevilles_method.py) - * [Newton Raphson](maths/newton_raphson.py) * [Number Of Digits](maths/number_of_digits.py) - * [Numerical Integration](maths/numerical_integration.py) + * Numerical Analysis + * [Bisection](maths/numerical_analysis/bisection.py) + * [Bisection 2](maths/numerical_analysis/bisection_2.py) + * [Integration By Simpson Approx](maths/numerical_analysis/integration_by_simpson_approx.py) + * [Intersection](maths/numerical_analysis/intersection.py) + * [Nevilles Method](maths/numerical_analysis/nevilles_method.py) + * [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py) + * [Newton Method](maths/numerical_analysis/newton_method.py) + * [Newton Raphson](maths/numerical_analysis/newton_raphson.py) + * [Newton Raphson 2](maths/numerical_analysis/newton_raphson_2.py) + * [Newton Raphson New](maths/numerical_analysis/newton_raphson_new.py) + * [Numerical Integration](maths/numerical_analysis/numerical_integration.py) + * [Runge Kutta](maths/numerical_analysis/runge_kutta.py) + * [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py) + * [Secant Method](maths/numerical_analysis/secant_method.py) + * [Simpson Rule](maths/numerical_analysis/simpson_rule.py) + * [Square Root](maths/numerical_analysis/square_root.py) * [Odd Sieve](maths/odd_sieve.py) * [Perfect Cube](maths/perfect_cube.py) * [Perfect Number](maths/perfect_number.py) @@ -673,8 +675,6 @@ * [Radians](maths/radians.py) * [Radix2 Fft](maths/radix2_fft.py) * [Remove Digit](maths/remove_digit.py) - * [Runge Kutta](maths/runge_kutta.py) - * [Runge Kutta Fehlberg 45](maths/runge_kutta_fehlberg_45.py) * [Segmented Sieve](maths/segmented_sieve.py) * Series * [Arithmetic](maths/series/arithmetic.py) @@ -687,7 +687,6 @@ * [Sieve Of Eratosthenes](maths/sieve_of_eratosthenes.py) * [Sigmoid](maths/sigmoid.py) * [Signum](maths/signum.py) - * [Simpson Rule](maths/simpson_rule.py) * [Simultaneous Linear Equation Solver](maths/simultaneous_linear_equation_solver.py) * [Sin](maths/sin.py) * [Sock Merchant](maths/sock_merchant.py) @@ -709,7 +708,6 @@ * [Proth Number](maths/special_numbers/proth_number.py) * [Ugly Numbers](maths/special_numbers/ugly_numbers.py) * [Weird Number](maths/special_numbers/weird_number.py) - * [Square Root](maths/square_root.py) * [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py) * [Sum Of Digits](maths/sum_of_digits.py) * [Sum Of Geometric Progression](maths/sum_of_geometric_progression.py) @@ -812,6 +810,7 @@ * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) * [Hubble Parameter](physics/hubble_parameter.py) * [Ideal Gas Law](physics/ideal_gas_law.py) + * [In Static Equilibrium](physics/in_static_equilibrium.py) * [Kinetic Energy](physics/kinetic_energy.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) * [Malus Law](physics/malus_law.py) diff --git a/arithmetic_analysis/README.md b/arithmetic_analysis/README.md deleted file mode 100644 index 45cf321eb6ad..000000000000 --- a/arithmetic_analysis/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Arithmetic analysis - -Arithmetic analysis is a branch of mathematics that deals with solving linear equations. - -* -* -* diff --git a/arithmetic_analysis/image_data/__init__.py b/arithmetic_analysis/image_data/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/arithmetic_analysis/gaussian_elimination.py b/linear_algebra/gaussian_elimination.py similarity index 100% rename from arithmetic_analysis/gaussian_elimination.py rename to linear_algebra/gaussian_elimination.py diff --git a/arithmetic_analysis/jacobi_iteration_method.py b/linear_algebra/jacobi_iteration_method.py similarity index 96% rename from arithmetic_analysis/jacobi_iteration_method.py rename to linear_algebra/jacobi_iteration_method.py index 44c52dd44640..8c91a19ef1b0 100644 --- a/arithmetic_analysis/jacobi_iteration_method.py +++ b/linear_algebra/jacobi_iteration_method.py @@ -1,203 +1,203 @@ -""" -Jacobi Iteration Method - https://en.wikipedia.org/wiki/Jacobi_method -""" -from __future__ import annotations - -import numpy as np -from numpy import float64 -from numpy.typing import NDArray - - -# Method to find solution of system of linear equations -def jacobi_iteration_method( - coefficient_matrix: NDArray[float64], - constant_matrix: NDArray[float64], - init_val: list[float], - iterations: int, -) -> list[float]: - """ - Jacobi Iteration Method: - An iterative algorithm to determine the solutions of strictly diagonally dominant - system of linear equations - - 4x1 + x2 + x3 = 2 - x1 + 5x2 + 2x3 = -6 - x1 + 2x2 + 4x3 = -4 - - x_init = [0.5, -0.5 , -0.5] - - Examples: - - >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) - >>> constant = np.array([[2], [-6], [-4]]) - >>> init_val = [0.5, -0.5, -0.5] - >>> iterations = 3 - >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) - [0.909375, -1.14375, -0.7484375] - - - >>> coefficient = np.array([[4, 1, 1], [1, 5, 2]]) - >>> constant = np.array([[2], [-6], [-4]]) - >>> init_val = [0.5, -0.5, -0.5] - >>> iterations = 3 - >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) - Traceback (most recent call last): - ... - ValueError: Coefficient matrix dimensions must be nxn but received 2x3 - - >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) - >>> constant = np.array([[2], [-6]]) - >>> init_val = [0.5, -0.5, -0.5] - >>> iterations = 3 - >>> jacobi_iteration_method( - ... coefficient, constant, init_val, iterations - ... ) # doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - ValueError: Coefficient and constant matrices dimensions must be nxn and nx1 but - received 3x3 and 2x1 - - >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) - >>> constant = np.array([[2], [-6], [-4]]) - >>> init_val = [0.5, -0.5] - >>> iterations = 3 - >>> jacobi_iteration_method( - ... coefficient, constant, init_val, iterations - ... ) # doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - ValueError: Number of initial values must be equal to number of rows in coefficient - matrix but received 2 and 3 - - >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) - >>> constant = np.array([[2], [-6], [-4]]) - >>> init_val = [0.5, -0.5, -0.5] - >>> iterations = 0 - >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) - Traceback (most recent call last): - ... - ValueError: Iterations must be at least 1 - """ - - rows1, cols1 = coefficient_matrix.shape - rows2, cols2 = constant_matrix.shape - - if rows1 != cols1: - msg = f"Coefficient matrix dimensions must be nxn but received {rows1}x{cols1}" - raise ValueError(msg) - - if cols2 != 1: - msg = f"Constant matrix must be nx1 but received {rows2}x{cols2}" - raise ValueError(msg) - - if rows1 != rows2: - msg = ( - "Coefficient and constant matrices dimensions must be nxn and nx1 but " - f"received {rows1}x{cols1} and {rows2}x{cols2}" - ) - raise ValueError(msg) - - if len(init_val) != rows1: - msg = ( - "Number of initial values must be equal to number of rows in coefficient " - f"matrix but received {len(init_val)} and {rows1}" - ) - raise ValueError(msg) - - if iterations <= 0: - raise ValueError("Iterations must be at least 1") - - table: NDArray[float64] = np.concatenate( - (coefficient_matrix, constant_matrix), axis=1 - ) - - rows, cols = table.shape - - strictly_diagonally_dominant(table) - - """ - # Iterates the whole matrix for given number of times - for _ in range(iterations): - new_val = [] - for row in range(rows): - temp = 0 - for col in range(cols): - if col == row: - denom = table[row][col] - elif col == cols - 1: - val = table[row][col] - else: - temp += (-1) * table[row][col] * init_val[col] - temp = (temp + val) / denom - new_val.append(temp) - init_val = new_val - """ - - # denominator - a list of values along the diagonal - denominator = np.diag(coefficient_matrix) - - # val_last - values of the last column of the table array - val_last = table[:, -1] - - # masks - boolean mask of all strings without diagonal - # elements array coefficient_matrix - masks = ~np.eye(coefficient_matrix.shape[0], dtype=bool) - - # no_diagonals - coefficient_matrix array values without diagonal elements - no_diagonals = coefficient_matrix[masks].reshape(-1, rows - 1) - - # Here we get 'i_col' - these are the column numbers, for each row - # without diagonal elements, except for the last column. - i_row, i_col = np.where(masks) - ind = i_col.reshape(-1, rows - 1) - - #'i_col' is converted to a two-dimensional list 'ind', which will be - # used to make selections from 'init_val' ('arr' array see below). - - # Iterates the whole matrix for given number of times - for _ in range(iterations): - arr = np.take(init_val, ind) - sum_product_rows = np.sum((-1) * no_diagonals * arr, axis=1) - new_val = (sum_product_rows + val_last) / denominator - init_val = new_val - - return new_val.tolist() - - -# Checks if the given matrix is strictly diagonally dominant -def strictly_diagonally_dominant(table: NDArray[float64]) -> bool: - """ - >>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 4, -4]]) - >>> strictly_diagonally_dominant(table) - True - - >>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 3, -4]]) - >>> strictly_diagonally_dominant(table) - Traceback (most recent call last): - ... - ValueError: Coefficient matrix is not strictly diagonally dominant - """ - - rows, cols = table.shape - - is_diagonally_dominant = True - - for i in range(rows): - total = 0 - for j in range(cols - 1): - if i == j: - continue - else: - total += table[i][j] - - if table[i][i] <= total: - raise ValueError("Coefficient matrix is not strictly diagonally dominant") - - return is_diagonally_dominant - - -# Test Cases -if __name__ == "__main__": - import doctest - - doctest.testmod() +""" +Jacobi Iteration Method - https://en.wikipedia.org/wiki/Jacobi_method +""" +from __future__ import annotations + +import numpy as np +from numpy import float64 +from numpy.typing import NDArray + + +# Method to find solution of system of linear equations +def jacobi_iteration_method( + coefficient_matrix: NDArray[float64], + constant_matrix: NDArray[float64], + init_val: list[float], + iterations: int, +) -> list[float]: + """ + Jacobi Iteration Method: + An iterative algorithm to determine the solutions of strictly diagonally dominant + system of linear equations + + 4x1 + x2 + x3 = 2 + x1 + 5x2 + 2x3 = -6 + x1 + 2x2 + 4x3 = -4 + + x_init = [0.5, -0.5 , -0.5] + + Examples: + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) + >>> constant = np.array([[2], [-6], [-4]]) + >>> init_val = [0.5, -0.5, -0.5] + >>> iterations = 3 + >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + [0.909375, -1.14375, -0.7484375] + + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2]]) + >>> constant = np.array([[2], [-6], [-4]]) + >>> init_val = [0.5, -0.5, -0.5] + >>> iterations = 3 + >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + Traceback (most recent call last): + ... + ValueError: Coefficient matrix dimensions must be nxn but received 2x3 + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) + >>> constant = np.array([[2], [-6]]) + >>> init_val = [0.5, -0.5, -0.5] + >>> iterations = 3 + >>> jacobi_iteration_method( + ... coefficient, constant, init_val, iterations + ... ) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + ValueError: Coefficient and constant matrices dimensions must be nxn and nx1 but + received 3x3 and 2x1 + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) + >>> constant = np.array([[2], [-6], [-4]]) + >>> init_val = [0.5, -0.5] + >>> iterations = 3 + >>> jacobi_iteration_method( + ... coefficient, constant, init_val, iterations + ... ) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + ValueError: Number of initial values must be equal to number of rows in coefficient + matrix but received 2 and 3 + + >>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]]) + >>> constant = np.array([[2], [-6], [-4]]) + >>> init_val = [0.5, -0.5, -0.5] + >>> iterations = 0 + >>> jacobi_iteration_method(coefficient, constant, init_val, iterations) + Traceback (most recent call last): + ... + ValueError: Iterations must be at least 1 + """ + + rows1, cols1 = coefficient_matrix.shape + rows2, cols2 = constant_matrix.shape + + if rows1 != cols1: + msg = f"Coefficient matrix dimensions must be nxn but received {rows1}x{cols1}" + raise ValueError(msg) + + if cols2 != 1: + msg = f"Constant matrix must be nx1 but received {rows2}x{cols2}" + raise ValueError(msg) + + if rows1 != rows2: + msg = ( + "Coefficient and constant matrices dimensions must be nxn and nx1 but " + f"received {rows1}x{cols1} and {rows2}x{cols2}" + ) + raise ValueError(msg) + + if len(init_val) != rows1: + msg = ( + "Number of initial values must be equal to number of rows in coefficient " + f"matrix but received {len(init_val)} and {rows1}" + ) + raise ValueError(msg) + + if iterations <= 0: + raise ValueError("Iterations must be at least 1") + + table: NDArray[float64] = np.concatenate( + (coefficient_matrix, constant_matrix), axis=1 + ) + + rows, cols = table.shape + + strictly_diagonally_dominant(table) + + """ + # Iterates the whole matrix for given number of times + for _ in range(iterations): + new_val = [] + for row in range(rows): + temp = 0 + for col in range(cols): + if col == row: + denom = table[row][col] + elif col == cols - 1: + val = table[row][col] + else: + temp += (-1) * table[row][col] * init_val[col] + temp = (temp + val) / denom + new_val.append(temp) + init_val = new_val + """ + + # denominator - a list of values along the diagonal + denominator = np.diag(coefficient_matrix) + + # val_last - values of the last column of the table array + val_last = table[:, -1] + + # masks - boolean mask of all strings without diagonal + # elements array coefficient_matrix + masks = ~np.eye(coefficient_matrix.shape[0], dtype=bool) + + # no_diagonals - coefficient_matrix array values without diagonal elements + no_diagonals = coefficient_matrix[masks].reshape(-1, rows - 1) + + # Here we get 'i_col' - these are the column numbers, for each row + # without diagonal elements, except for the last column. + i_row, i_col = np.where(masks) + ind = i_col.reshape(-1, rows - 1) + + #'i_col' is converted to a two-dimensional list 'ind', which will be + # used to make selections from 'init_val' ('arr' array see below). + + # Iterates the whole matrix for given number of times + for _ in range(iterations): + arr = np.take(init_val, ind) + sum_product_rows = np.sum((-1) * no_diagonals * arr, axis=1) + new_val = (sum_product_rows + val_last) / denominator + init_val = new_val + + return new_val.tolist() + + +# Checks if the given matrix is strictly diagonally dominant +def strictly_diagonally_dominant(table: NDArray[float64]) -> bool: + """ + >>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 4, -4]]) + >>> strictly_diagonally_dominant(table) + True + + >>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 3, -4]]) + >>> strictly_diagonally_dominant(table) + Traceback (most recent call last): + ... + ValueError: Coefficient matrix is not strictly diagonally dominant + """ + + rows, cols = table.shape + + is_diagonally_dominant = True + + for i in range(rows): + total = 0 + for j in range(cols - 1): + if i == j: + continue + else: + total += table[i][j] + + if table[i][i] <= total: + raise ValueError("Coefficient matrix is not strictly diagonally dominant") + + return is_diagonally_dominant + + +# Test Cases +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/arithmetic_analysis/lu_decomposition.py b/linear_algebra/lu_decomposition.py similarity index 100% rename from arithmetic_analysis/lu_decomposition.py rename to linear_algebra/lu_decomposition.py diff --git a/arithmetic_analysis/bisection.py b/maths/numerical_analysis/bisection.py similarity index 100% rename from arithmetic_analysis/bisection.py rename to maths/numerical_analysis/bisection.py diff --git a/maths/bisection.py b/maths/numerical_analysis/bisection_2.py similarity index 100% rename from maths/bisection.py rename to maths/numerical_analysis/bisection_2.py diff --git a/maths/integration_by_simpson_approx.py b/maths/numerical_analysis/integration_by_simpson_approx.py similarity index 100% rename from maths/integration_by_simpson_approx.py rename to maths/numerical_analysis/integration_by_simpson_approx.py diff --git a/arithmetic_analysis/intersection.py b/maths/numerical_analysis/intersection.py similarity index 100% rename from arithmetic_analysis/intersection.py rename to maths/numerical_analysis/intersection.py diff --git a/maths/nevilles_method.py b/maths/numerical_analysis/nevilles_method.py similarity index 100% rename from maths/nevilles_method.py rename to maths/numerical_analysis/nevilles_method.py diff --git a/arithmetic_analysis/newton_forward_interpolation.py b/maths/numerical_analysis/newton_forward_interpolation.py similarity index 100% rename from arithmetic_analysis/newton_forward_interpolation.py rename to maths/numerical_analysis/newton_forward_interpolation.py diff --git a/arithmetic_analysis/newton_method.py b/maths/numerical_analysis/newton_method.py similarity index 100% rename from arithmetic_analysis/newton_method.py rename to maths/numerical_analysis/newton_method.py diff --git a/arithmetic_analysis/newton_raphson.py b/maths/numerical_analysis/newton_raphson.py similarity index 61% rename from arithmetic_analysis/newton_raphson.py rename to maths/numerical_analysis/newton_raphson.py index 1b90ad4177f6..8491ca8003bc 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/maths/numerical_analysis/newton_raphson.py @@ -5,42 +5,41 @@ from __future__ import annotations from decimal import Decimal -from math import * # noqa: F403 -from sympy import diff +from sympy import diff, lambdify, symbols -def newton_raphson( - func: str, a: float | Decimal, precision: float = 10**-10 -) -> float: +def newton_raphson(func: str, a: float | Decimal, precision: float = 1e-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) + >>> 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) + >>> newton_raphson("log(x) - 1", 2) 2.718281828458938 """ - x = a + x = symbols("x") + f = lambdify(x, func, "math") + f_derivative = lambdify(x, diff(func), "math") + x_curr = a while True: - x = Decimal(x) - ( - Decimal(eval(func)) / Decimal(eval(str(diff(func)))) # noqa: S307 - ) - # This number dictates the accuracy of the answer - if abs(eval(func)) < precision: # noqa: S307 - return float(x) + x_curr = Decimal(x_curr) - Decimal(f(x_curr)) / Decimal(f_derivative(x_curr)) + if abs(f(x_curr)) < precision: + return float(x_curr) -# Let's Execute if __name__ == "__main__": - # Find root of trigonometric function + import doctest + + doctest.testmod() + # 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 + # Find value of e print(f"The root of log(x) - 1 = 0 is {newton_raphson('log(x) - 1', 2)}") - # Exponential Roots + # Find root of exponential function print(f"The root of exp(x) - 1 = 0 is {newton_raphson('exp(x) - 1', 0)}") diff --git a/maths/newton_raphson.py b/maths/numerical_analysis/newton_raphson_2.py similarity index 100% rename from maths/newton_raphson.py rename to maths/numerical_analysis/newton_raphson_2.py diff --git a/arithmetic_analysis/newton_raphson_new.py b/maths/numerical_analysis/newton_raphson_new.py similarity index 100% rename from arithmetic_analysis/newton_raphson_new.py rename to maths/numerical_analysis/newton_raphson_new.py diff --git a/maths/numerical_integration.py b/maths/numerical_analysis/numerical_integration.py similarity index 100% rename from maths/numerical_integration.py rename to maths/numerical_analysis/numerical_integration.py diff --git a/maths/runge_kutta.py b/maths/numerical_analysis/runge_kutta.py similarity index 100% rename from maths/runge_kutta.py rename to maths/numerical_analysis/runge_kutta.py diff --git a/maths/runge_kutta_fehlberg_45.py b/maths/numerical_analysis/runge_kutta_fehlberg_45.py similarity index 100% rename from maths/runge_kutta_fehlberg_45.py rename to maths/numerical_analysis/runge_kutta_fehlberg_45.py diff --git a/arithmetic_analysis/secant_method.py b/maths/numerical_analysis/secant_method.py similarity index 100% rename from arithmetic_analysis/secant_method.py rename to maths/numerical_analysis/secant_method.py diff --git a/maths/simpson_rule.py b/maths/numerical_analysis/simpson_rule.py similarity index 100% rename from maths/simpson_rule.py rename to maths/numerical_analysis/simpson_rule.py diff --git a/maths/square_root.py b/maths/numerical_analysis/square_root.py similarity index 100% rename from maths/square_root.py rename to maths/numerical_analysis/square_root.py diff --git a/arithmetic_analysis/image_data/2D_problems.jpg b/physics/image_data/2D_problems.jpg similarity index 100% rename from arithmetic_analysis/image_data/2D_problems.jpg rename to physics/image_data/2D_problems.jpg diff --git a/arithmetic_analysis/image_data/2D_problems_1.jpg b/physics/image_data/2D_problems_1.jpg similarity index 100% rename from arithmetic_analysis/image_data/2D_problems_1.jpg rename to physics/image_data/2D_problems_1.jpg diff --git a/arithmetic_analysis/__init__.py b/physics/image_data/__init__.py similarity index 100% rename from arithmetic_analysis/__init__.py rename to physics/image_data/__init__.py diff --git a/arithmetic_analysis/in_static_equilibrium.py b/physics/in_static_equilibrium.py similarity index 96% rename from arithmetic_analysis/in_static_equilibrium.py rename to physics/in_static_equilibrium.py index 7aaecf174a5e..d56299f60858 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/physics/in_static_equilibrium.py @@ -1,94 +1,94 @@ -""" -Checks if a system of forces is in static equilibrium. -""" -from __future__ import annotations - -from numpy import array, cos, cross, float64, radians, sin -from numpy.typing import NDArray - - -def polar_force( - magnitude: float, angle: float, radian_mode: bool = False -) -> list[float]: - """ - Resolves force along rectangular components. - (force, angle) => (force_x, force_y) - >>> import math - >>> force = polar_force(10, 45) - >>> math.isclose(force[0], 7.071067811865477) - True - >>> math.isclose(force[1], 7.0710678118654755) - True - >>> force = polar_force(10, 3.14, radian_mode=True) - >>> math.isclose(force[0], -9.999987317275396) - True - >>> math.isclose(force[1], 0.01592652916486828) - True - """ - if radian_mode: - return [magnitude * cos(angle), magnitude * sin(angle)] - return [magnitude * cos(radians(angle)), magnitude * sin(radians(angle))] - - -def in_static_equilibrium( - forces: NDArray[float64], location: NDArray[float64], 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: NDArray[float64] = 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: NDArray[float64] = 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() +""" +Checks if a system of forces is in static equilibrium. +""" +from __future__ import annotations + +from numpy import array, cos, cross, float64, radians, sin +from numpy.typing import NDArray + + +def polar_force( + magnitude: float, angle: float, radian_mode: bool = False +) -> list[float]: + """ + Resolves force along rectangular components. + (force, angle) => (force_x, force_y) + >>> import math + >>> force = polar_force(10, 45) + >>> math.isclose(force[0], 7.071067811865477) + True + >>> math.isclose(force[1], 7.0710678118654755) + True + >>> force = polar_force(10, 3.14, radian_mode=True) + >>> math.isclose(force[0], -9.999987317275396) + True + >>> math.isclose(force[1], 0.01592652916486828) + True + """ + if radian_mode: + return [magnitude * cos(angle), magnitude * sin(angle)] + return [magnitude * cos(radians(angle)), magnitude * sin(radians(angle))] + + +def in_static_equilibrium( + forces: NDArray[float64], location: NDArray[float64], 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: NDArray[float64] = 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: NDArray[float64] = 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() From 417b7edfc3fdfe9534a56e3e7d0a368f76b3edb4 Mon Sep 17 00:00:00 2001 From: Krishna-singhal <65902764+Krishna-Singhal@users.noreply.github.com> Date: Mon, 23 Oct 2023 13:21:27 +0530 Subject: [PATCH 2556/2908] code enhancement in `sort.double_sort` (#10798) * don't need to return list because list is mutable * Don't need to return list as list is mutable * use advantage of python in swapping * filter blank inputs from input list * minor changes * minor mistake * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more readable * Update double_sort.py * last fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- sorts/double_sort.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/sorts/double_sort.py b/sorts/double_sort.py index a19641d94752..bd5fdca1e63c 100644 --- a/sorts/double_sort.py +++ b/sorts/double_sort.py @@ -1,4 +1,7 @@ -def double_sort(lst): +from typing import Any + + +def double_sort(collection: list[Any]) -> list[Any]: """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, it's called "Double sort" @@ -14,29 +17,28 @@ def double_sort(lst): >>> double_sort([-3, 10, 16, -42, 29]) == sorted([-3, 10, 16, -42, 29]) True """ - no_of_elements = len(lst) + no_of_elements = len(collection) for _ in range( int(((no_of_elements - 1) / 2) + 1) ): # we don't need to traverse to end of list as for j in range(no_of_elements - 1): - if ( - lst[j + 1] < lst[j] - ): # applying bubble sort algorithm from left to right (or forwards) - temp = lst[j + 1] - lst[j + 1] = lst[j] - lst[j] = temp - if ( - lst[no_of_elements - 1 - j] < lst[no_of_elements - 2 - j] - ): # applying bubble sort algorithm from right to left (or backwards) - temp = lst[no_of_elements - 1 - j] - lst[no_of_elements - 1 - j] = lst[no_of_elements - 2 - j] - lst[no_of_elements - 2 - j] = temp - return lst + # apply the bubble sort algorithm from left to right (or forwards) + if collection[j + 1] < collection[j]: + collection[j], collection[j + 1] = collection[j + 1], collection[j] + # apply the bubble sort algorithm from right to left (or backwards) + if collection[no_of_elements - 1 - j] < collection[no_of_elements - 2 - j]: + ( + collection[no_of_elements - 1 - j], + collection[no_of_elements - 2 - j], + ) = ( + collection[no_of_elements - 2 - j], + collection[no_of_elements - 1 - j], + ) + return collection if __name__ == "__main__": - print("enter the list to be sorted") - lst = [int(x) for x in input().split()] # inputing elements of the list in one line - sorted_lst = double_sort(lst) + # allow the user to input the elements of the list on one line + unsorted = [int(x) for x in input("Enter the list to be sorted: ").split() if x] print("the sorted list is") - print(sorted_lst) + print(f"{double_sort(unsorted) = }") From d051db1f14cbb0edd2b0db1e4edef76cce6c7823 Mon Sep 17 00:00:00 2001 From: Berat Osman Demiralay Date: Mon, 23 Oct 2023 16:25:07 +0300 Subject: [PATCH 2557/2908] Add Simple Moving Average (SMA) Calculation (#9300) * Add Simple Moving Average (SMA) Calculation This commit adds a Python script for calculating the Simple Moving Average (SMA) of a time series data. The script also includes a doctest that verifies the correctness of the SMA calculations for a sample dataset. Usage: - Run the script with your own time series data and specify the window size for SMA calculations. * Update financial/simple_moving_average.py Co-authored-by: Tianyi Zheng * Update financial/simple_moving_average.py Co-authored-by: Tianyi Zheng * Update financial/simple_moving_average.py Co-authored-by: Tianyi Zheng * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update simple_moving_average.py * Update financial/simple_moving_average.py * Update simple_moving_average.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Tianyi Zheng Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- financial/simple_moving_average.py | 68 ++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 financial/simple_moving_average.py diff --git a/financial/simple_moving_average.py b/financial/simple_moving_average.py new file mode 100644 index 000000000000..d5d68ffd3dab --- /dev/null +++ b/financial/simple_moving_average.py @@ -0,0 +1,68 @@ +""" +The Simple Moving Average (SMA) is a statistical calculation used to analyze data points +by creating a constantly updated average price over a specific time period. +In finance, SMA is often used in time series analysis to smooth out price data +and identify trends. + +Reference: https://en.wikipedia.org/wiki/Moving_average +""" +from collections.abc import Sequence + + +def simple_moving_average( + data: Sequence[float], window_size: int +) -> list[float | None]: + """ + Calculate the simple moving average (SMA) for some given time series data. + + :param data: A list of numerical data points. + :param window_size: An integer representing the size of the SMA window. + :return: A list of SMA values with the same length as the input data. + + Examples: + >>> sma = simple_moving_average([10, 12, 15, 13, 14, 16, 18, 17, 19, 21], 3) + >>> [round(value, 2) if value is not None else None for value in sma] + [None, None, 12.33, 13.33, 14.0, 14.33, 16.0, 17.0, 18.0, 19.0] + >>> simple_moving_average([10, 12, 15], 5) + [None, None, None] + >>> simple_moving_average([10, 12, 15, 13, 14, 16, 18, 17, 19, 21], 0) + Traceback (most recent call last): + ... + ValueError: Window size must be a positive integer + """ + if window_size < 1: + raise ValueError("Window size must be a positive integer") + + sma: list[float | None] = [] + + for i in range(len(data)): + if i < window_size - 1: + sma.append(None) # SMA not available for early data points + else: + window = data[i - window_size + 1 : i + 1] + sma_value = sum(window) / window_size + sma.append(sma_value) + return sma + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Example data (replace with your own time series data) + data = [10, 12, 15, 13, 14, 16, 18, 17, 19, 21] + + # Specify the window size for the SMA + window_size = 3 + + # Calculate the Simple Moving Average + sma_values = simple_moving_average(data, window_size) + + # Print the SMA values + print("Simple Moving Average (SMA) Values:") + for i, value in enumerate(sma_values): + if value is not None: + print(f"Day {i + 1}: {value:.2f}") + else: + print(f"Day {i + 1}: Not enough data for SMA") From 4cbefadbd7adee486e33a6b66014a2474e81f82e Mon Sep 17 00:00:00 2001 From: Tauseef Hilal Tantary Date: Mon, 23 Oct 2023 19:21:09 +0530 Subject: [PATCH 2558/2908] [New Algorithm] - Triangular Numbers (#10663) * Add New Algorithm: Triangular Numbers * Calculate nth triangular number instead of generating a list * Handle 0th position and update function name and docstring --- maths/special_numbers/triangular_numbers.py | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 maths/special_numbers/triangular_numbers.py diff --git a/maths/special_numbers/triangular_numbers.py b/maths/special_numbers/triangular_numbers.py new file mode 100644 index 000000000000..5be89e6108b2 --- /dev/null +++ b/maths/special_numbers/triangular_numbers.py @@ -0,0 +1,43 @@ +""" +A triangular number or triangle number counts objects arranged in an +equilateral triangle. This module provides a function to generate n'th +triangular number. + +For more information about triangular numbers, refer to: +https://en.wikipedia.org/wiki/Triangular_number +""" + + +def triangular_number(position: int) -> int: + """ + Generate the triangular number at the specified position. + + Args: + position (int): The position of the triangular number to generate. + + Returns: + int: The triangular number at the specified position. + + Raises: + ValueError: If `position` is negative. + + Examples: + >>> triangular_number(1) + 1 + >>> triangular_number(3) + 6 + >>> triangular_number(-1) + Traceback (most recent call last): + ... + ValueError: param `position` must be non-negative + """ + if position < 0: + raise ValueError("param `position` must be non-negative") + + return position * (position + 1) // 2 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 54e2aa67e8f74435b15e2a2864a7fb00981979af Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Mon, 23 Oct 2023 23:12:02 +0530 Subject: [PATCH 2559/2908] Enhance readability of Minimax (#10838) * Enhance readability of Minimax * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Reduce line overflow * Update backtracking/minimax.py Co-authored-by: Tianyi Zheng * Update backtracking/minimax.py Co-authored-by: Tianyi Zheng * Update backtracking/minimax.py Co-authored-by: Tianyi Zheng * Remove line overflow --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- backtracking/minimax.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/backtracking/minimax.py b/backtracking/minimax.py index 6e310131e069..6dece2990a1c 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -16,6 +16,22 @@ def minimax( depth: int, node_index: int, is_max: bool, scores: list[int], height: float ) -> int: """ + This function implements the minimax algorithm, which helps achieve the optimal + score for a player in a two-player game by checking all possible moves. + If the player is the maximizer, then the score is maximized. + If the player is the minimizer, then the score is minimized. + + Parameters: + - depth: Current depth in the game tree. + - node_index: Index of the current node in the scores list. + - is_max: A boolean indicating whether the current move + is for the maximizer (True) or minimizer (False). + - scores: A list containing the scores of the leaves of the game tree. + - height: The maximum height of the game tree. + + Returns: + - An integer representing the optimal score for the current player. + >>> import math >>> scores = [90, 23, 6, 33, 21, 65, 123, 34423] >>> height = math.log(len(scores), 2) @@ -37,19 +53,24 @@ def minimax( if depth < 0: raise ValueError("Depth cannot be less than 0") - if len(scores) == 0: raise ValueError("Scores cannot be empty") + # Base case: If the current depth equals the height of the tree, + # return the score of the current node. if depth == height: return scores[node_index] + # If it's the maximizer's turn, choose the maximum score + # between the two possible moves. if is_max: return max( minimax(depth + 1, node_index * 2, False, scores, height), minimax(depth + 1, node_index * 2 + 1, False, scores, height), ) + # If it's the minimizer's turn, choose the minimum score + # between the two possible moves. return min( minimax(depth + 1, node_index * 2, True, scores, height), minimax(depth + 1, node_index * 2 + 1, True, scores, height), @@ -57,8 +78,11 @@ def minimax( def main() -> None: + # Sample scores and height calculation scores = [90, 23, 6, 33, 21, 65, 123, 34423] height = math.log(len(scores), 2) + + # Calculate and print the optimal value using the minimax algorithm print("Optimal value : ", end="") print(minimax(0, 0, True, scores, height)) From 30122062b93cdeba8bacb0a4a3c783bc8069b7a0 Mon Sep 17 00:00:00 2001 From: Aqib Javid Bhat Date: Mon, 23 Oct 2023 23:26:43 +0530 Subject: [PATCH 2560/2908] Add Floyd's Cycle Detection Algorithm (#10833) * Add Floyd's Cycle Detection Algorithm * Add tests for add_node function * Apply suggestions from code review * Update floyds_cycle_detection.py --------- Co-authored-by: Tianyi Zheng --- .../linked_list/floyds_cycle_detection.py | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 data_structures/linked_list/floyds_cycle_detection.py diff --git a/data_structures/linked_list/floyds_cycle_detection.py b/data_structures/linked_list/floyds_cycle_detection.py new file mode 100644 index 000000000000..6c3f13760260 --- /dev/null +++ b/data_structures/linked_list/floyds_cycle_detection.py @@ -0,0 +1,150 @@ +""" +Floyd's cycle detection algorithm is a popular algorithm used to detect cycles +in a linked list. It uses two pointers, a slow pointer and a fast pointer, +to traverse the linked list. The slow pointer moves one node at a time while the fast +pointer moves two nodes at a time. If there is a cycle in the linked list, +the fast pointer will eventually catch up to the slow pointer and they will +meet at the same node. If there is no cycle, the fast pointer will reach the end of +the linked list and the algorithm will terminate. + +For more information: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare +""" + +from collections.abc import Iterator +from dataclasses import dataclass +from typing import Any, Self + + +@dataclass +class Node: + """ + A class representing a node in a singly linked list. + """ + + data: Any + next_node: Self | None = None + + +@dataclass +class LinkedList: + """ + A class representing a singly linked list. + """ + + head: Node | None = None + + def __iter__(self) -> Iterator: + """ + Iterates through the linked list. + + Returns: + Iterator: An iterator over the linked list. + + Examples: + >>> linked_list = LinkedList() + >>> list(linked_list) + [] + >>> linked_list.add_node(1) + >>> tuple(linked_list) + (1,) + """ + visited = [] + node = self.head + while node: + # Avoid infinite loop in there's a cycle + if node in visited: + return + visited.append(node) + yield node.data + node = node.next_node + + def add_node(self, data: Any) -> None: + """ + Adds a new node to the end of the linked list. + + Args: + data (Any): The data to be stored in the new node. + + Examples: + >>> linked_list = LinkedList() + >>> linked_list.add_node(1) + >>> linked_list.add_node(2) + >>> linked_list.add_node(3) + >>> linked_list.add_node(4) + >>> tuple(linked_list) + (1, 2, 3, 4) + """ + new_node = Node(data) + + if self.head is None: + self.head = new_node + return + + current_node = self.head + while current_node.next_node is not None: + current_node = current_node.next_node + + current_node.next_node = new_node + + def detect_cycle(self) -> bool: + """ + Detects if there is a cycle in the linked list using + Floyd's cycle detection algorithm. + + Returns: + bool: True if there is a cycle, False otherwise. + + Examples: + >>> linked_list = LinkedList() + >>> linked_list.add_node(1) + >>> linked_list.add_node(2) + >>> linked_list.add_node(3) + >>> linked_list.add_node(4) + + >>> linked_list.detect_cycle() + False + + # Create a cycle in the linked list + >>> linked_list.head.next_node.next_node.next_node = linked_list.head.next_node + + >>> linked_list.detect_cycle() + True + """ + if self.head is None: + return False + + slow_pointer: Node | None = self.head + fast_pointer: Node | None = self.head + + while fast_pointer is not None and fast_pointer.next_node is not None: + slow_pointer = slow_pointer.next_node if slow_pointer else None + fast_pointer = fast_pointer.next_node.next_node + if slow_pointer == fast_pointer: + return True + + return False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + linked_list = LinkedList() + linked_list.add_node(1) + linked_list.add_node(2) + linked_list.add_node(3) + linked_list.add_node(4) + + # Create a cycle in the linked list + # It first checks if the head, next_node, and next_node.next_node attributes of the + # linked list are not None to avoid any potential type errors. + if ( + linked_list.head + and linked_list.head.next_node + and linked_list.head.next_node.next_node + ): + linked_list.head.next_node.next_node.next_node = linked_list.head.next_node + + has_cycle = linked_list.detect_cycle() + print(has_cycle) # Output: True From ffd3a56c35f5ec274c819e8f2596ab5134cf9c36 Mon Sep 17 00:00:00 2001 From: SEIKH NABAB UDDIN <93948993+nababuddin@users.noreply.github.com> Date: Mon, 23 Oct 2023 23:42:28 +0530 Subject: [PATCH 2561/2908] Updated Selection Sort (#10855) * Update selection_sort.py * Update selection_sort.py --- sorts/selection_sort.py | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/sorts/selection_sort.py b/sorts/selection_sort.py index 28971a5e1aad..506836b53e44 100644 --- a/sorts/selection_sort.py +++ b/sorts/selection_sort.py @@ -1,22 +1,9 @@ -""" -This is a pure Python implementation of the selection sort algorithm - -For doctests run following command: -python -m doctest -v selection_sort.py -or -python3 -m doctest -v selection_sort.py - -For manual testing run: -python selection_sort.py -""" - - def selection_sort(collection: list[int]) -> list[int]: - """Pure implementation of the selection sort algorithm in Python - :param collection: some mutable ordered collection with heterogeneous - comparable items inside - :return: the same collection ordered by ascending + """ + Sorts a list in ascending order using the selection sort algorithm. + :param collection: A list of integers to be sorted. + :return: The sorted list. Examples: >>> selection_sort([0, 5, 3, 2, 2]) @@ -31,16 +18,17 @@ def selection_sort(collection: list[int]) -> list[int]: length = len(collection) for i in range(length - 1): - least = i + min_index = i for k in range(i + 1, length): - if collection[k] < collection[least]: - least = k - if least != i: - collection[least], collection[i] = (collection[i], collection[least]) + if collection[k] < collection[min_index]: + min_index = k + if min_index != i: + collection[i], collection[min_index] = collection[min_index], collection[i] 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(selection_sort(unsorted)) + sorted_list = selection_sort(unsorted) + print("Sorted List:", sorted_list) From e5d6969f38ecf03f3e3a1e35fcfd3ae2484b6e08 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 22:29:16 +0200 Subject: [PATCH 2562/2908] [pre-commit.ci] pre-commit autoupdate (#10856) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.292 → v0.1.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.292...v0.1.1) - [github.com/psf/black: 23.9.1 → 23.10.0](https://github.com/psf/black/compare/23.9.1...23.10.0) - [github.com/pre-commit/mirrors-mypy: v1.6.0 → v1.6.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.0...v1.6.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b3def463ded2..e0b9922fae7e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.292 + rev: v0.1.1 hooks: - id: ruff - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.0 hooks: - id: black @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.6.0 + rev: v1.6.1 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 1e3711fe8dda..dfd1a2c0c809 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -233,6 +233,7 @@ * [Deque Doubly](data_structures/linked_list/deque_doubly.py) * [Doubly Linked List](data_structures/linked_list/doubly_linked_list.py) * [Doubly Linked List Two](data_structures/linked_list/doubly_linked_list_two.py) + * [Floyds Cycle Detection](data_structures/linked_list/floyds_cycle_detection.py) * [From Sequence](data_structures/linked_list/from_sequence.py) * [Has Loop](data_structures/linked_list/has_loop.py) * [Is Palindrome](data_structures/linked_list/is_palindrome.py) @@ -394,6 +395,7 @@ * [Interest](financial/interest.py) * [Present Value](financial/present_value.py) * [Price Plus Tax](financial/price_plus_tax.py) + * [Simple Moving Average](financial/simple_moving_average.py) ## Fractals * [Julia Sets](fractals/julia_sets.py) @@ -706,6 +708,7 @@ * [Polygonal Numbers](maths/special_numbers/polygonal_numbers.py) * [Pronic Number](maths/special_numbers/pronic_number.py) * [Proth Number](maths/special_numbers/proth_number.py) + * [Triangular Numbers](maths/special_numbers/triangular_numbers.py) * [Ugly Numbers](maths/special_numbers/ugly_numbers.py) * [Weird Number](maths/special_numbers/weird_number.py) * [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py) @@ -826,6 +829,7 @@ * [Shear Stress](physics/shear_stress.py) * [Speed Of Sound](physics/speed_of_sound.py) * [Speeds Of Gas Molecules](physics/speeds_of_gas_molecules.py) + * [Terminal Velocity](physics/terminal_velocity.py) ## Project Euler * Problem 001 From b98312ca9f2df491017e189b353e6b382b323eed Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 23 Oct 2023 16:37:17 -0400 Subject: [PATCH 2563/2908] Consolidate Newton-Raphson implementations (#10859) * updating DIRECTORY.md * updating DIRECTORY.md * Consolidate Newton-Raphson duplicates * Rename consolidated Newton-Raphson file * updating DIRECTORY.md * updating DIRECTORY.md * Fix doctest precision * Fix doctest precision again --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 - maths/numerical_analysis/newton_method.py | 54 ------- maths/numerical_analysis/newton_raphson.py | 142 +++++++++++++----- maths/numerical_analysis/newton_raphson_2.py | 64 -------- .../numerical_analysis/newton_raphson_new.py | 83 ---------- 5 files changed, 105 insertions(+), 241 deletions(-) delete mode 100644 maths/numerical_analysis/newton_method.py delete mode 100644 maths/numerical_analysis/newton_raphson_2.py delete mode 100644 maths/numerical_analysis/newton_raphson_new.py diff --git a/DIRECTORY.md b/DIRECTORY.md index dfd1a2c0c809..f0b1f7c13c2b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -642,10 +642,7 @@ * [Intersection](maths/numerical_analysis/intersection.py) * [Nevilles Method](maths/numerical_analysis/nevilles_method.py) * [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py) - * [Newton Method](maths/numerical_analysis/newton_method.py) * [Newton Raphson](maths/numerical_analysis/newton_raphson.py) - * [Newton Raphson 2](maths/numerical_analysis/newton_raphson_2.py) - * [Newton Raphson New](maths/numerical_analysis/newton_raphson_new.py) * [Numerical Integration](maths/numerical_analysis/numerical_integration.py) * [Runge Kutta](maths/numerical_analysis/runge_kutta.py) * [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py) diff --git a/maths/numerical_analysis/newton_method.py b/maths/numerical_analysis/newton_method.py deleted file mode 100644 index 5127bfcafd9a..000000000000 --- a/maths/numerical_analysis/newton_method.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Newton's Method.""" - -# Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method -from collections.abc 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: - try: - next_guess = prev_guess - function(prev_guess) / derivative(prev_guess) - except ZeroDivisionError: - raise ZeroDivisionError("Could not find root") from None - if abs(prev_guess - next_guess) < 10**-5: - return next_guess - prev_guess = next_guess - - -def f(x: float) -> float: - return (x**3) - (2 * x) - 5 - - -def f1(x: float) -> float: - return 3 * (x**2) - 2 - - -if __name__ == "__main__": - print(newton(f, f1, 3)) diff --git a/maths/numerical_analysis/newton_raphson.py b/maths/numerical_analysis/newton_raphson.py index 8491ca8003bc..feee38f905dd 100644 --- a/maths/numerical_analysis/newton_raphson.py +++ b/maths/numerical_analysis/newton_raphson.py @@ -1,45 +1,113 @@ -# 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 __future__ import annotations - -from decimal import Decimal - -from sympy import diff, lambdify, symbols - - -def newton_raphson(func: str, a: float | Decimal, precision: float = 1e-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 +""" +The Newton-Raphson method (aka the Newton method) is a root-finding algorithm that +approximates a root of a given real-valued function f(x). It is an iterative method +given by the formula + +x_{n + 1} = x_n + f(x_n) / f'(x_n) + +with the precision of the approximation increasing as the number of iterations increase. + +Reference: https://en.wikipedia.org/wiki/Newton%27s_method +""" +from collections.abc import Callable + +RealFunc = Callable[[float], float] + + +def calc_derivative(f: RealFunc, x: float, delta_x: float = 1e-3) -> float: + """ + Approximate the derivative of a function f(x) at a point x using the finite + difference method + + >>> import math + >>> tolerance = 1e-5 + >>> derivative = calc_derivative(lambda x: x**2, 2) + >>> math.isclose(derivative, 4, abs_tol=tolerance) + True + >>> derivative = calc_derivative(math.sin, 0) + >>> math.isclose(derivative, 1, abs_tol=tolerance) + True + """ + return (f(x + delta_x / 2) - f(x - delta_x / 2)) / delta_x + + +def newton_raphson( + f: RealFunc, + x0: float = 0, + max_iter: int = 100, + step: float = 1e-6, + max_error: float = 1e-6, + log_steps: bool = False, +) -> tuple[float, float, list[float]]: """ - x = symbols("x") - f = lambdify(x, func, "math") - f_derivative = lambdify(x, diff(func), "math") - x_curr = a - while True: - x_curr = Decimal(x_curr) - Decimal(f(x_curr)) / Decimal(f_derivative(x_curr)) - if abs(f(x_curr)) < precision: - return float(x_curr) + Find a root of the given function f using the Newton-Raphson method. + + :param f: A real-valued single-variable function + :param x0: Initial guess + :param max_iter: Maximum number of iterations + :param step: Step size of x, used to approximate f'(x) + :param max_error: Maximum approximation error + :param log_steps: bool denoting whether to log intermediate steps + + :return: A tuple containing the approximation, the error, and the intermediate + steps. If log_steps is False, then an empty list is returned for the third + element of the tuple. + + :raises ZeroDivisionError: The derivative approaches 0. + :raises ArithmeticError: No solution exists, or the solution isn't found before the + iteration limit is reached. + + >>> import math + >>> tolerance = 1e-15 + >>> root, *_ = newton_raphson(lambda x: x**2 - 5*x + 2, 0.4, max_error=tolerance) + >>> math.isclose(root, (5 - math.sqrt(17)) / 2, abs_tol=tolerance) + True + >>> root, *_ = newton_raphson(lambda x: math.log(x) - 1, 2, max_error=tolerance) + >>> math.isclose(root, math.e, abs_tol=tolerance) + True + >>> root, *_ = newton_raphson(math.sin, 1, max_error=tolerance) + >>> math.isclose(root, 0, abs_tol=tolerance) + True + >>> newton_raphson(math.cos, 0) + Traceback (most recent call last): + ... + ZeroDivisionError: No converging solution found, zero derivative + >>> newton_raphson(lambda x: x**2 + 1, 2) + Traceback (most recent call last): + ... + ArithmeticError: No converging solution found, iteration limit reached + """ + + def f_derivative(x: float) -> float: + return calc_derivative(f, x, step) + + a = x0 # Set initial guess + steps = [] + for _ in range(max_iter): + if log_steps: # Log intermediate steps + steps.append(a) + + error = abs(f(a)) + if error < max_error: + return a, error, steps + + if f_derivative(a) == 0: + raise ZeroDivisionError("No converging solution found, zero derivative") + a -= f(a) / f_derivative(a) # Calculate next estimate + raise ArithmeticError("No converging solution found, iteration limit reached") if __name__ == "__main__": import doctest + from math import exp, tanh doctest.testmod() - # 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 value of e - print(f"The root of log(x) - 1 = 0 is {newton_raphson('log(x) - 1', 2)}") - # Find root of exponential function - print(f"The root of exp(x) - 1 = 0 is {newton_raphson('exp(x) - 1', 0)}") + def func(x: float) -> float: + return tanh(x) ** 2 - exp(3 * x) + + solution, err, steps = newton_raphson( + func, x0=10, max_iter=100, step=1e-6, log_steps=True + ) + print(f"{solution=}, {err=}") + print("\n".join(str(x) for x in steps)) diff --git a/maths/numerical_analysis/newton_raphson_2.py b/maths/numerical_analysis/newton_raphson_2.py deleted file mode 100644 index f6b227b5c9c1..000000000000 --- a/maths/numerical_analysis/newton_raphson_2.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -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 - x[n+1] = x[n] + f(x[n])/f'(x[n]) -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 -from collections.abc import Callable - -DerivativeFunc = Callable[[float], float] - - -def calc_derivative(f: DerivativeFunc, a: float, h: float = 0.001) -> float: - """ - Calculates derivative at point a for function f using finite difference - method - """ - return (f(a + h) - f(a - h)) / (2 * h) - - -def newton_raphson( - f: DerivativeFunc, - x0: float = 0, - maxiter: int = 100, - step: float = 0.0001, - maxerror: float = 1e-6, - logsteps: bool = False, -) -> tuple[float, float, list[float]]: - a = x0 # set the initial guess - steps = [a] - error = abs(f(a)) - 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") - a = a - f(a) / f1(a) # Calculate the next estimate - if logsteps: - steps.append(a) - if error < maxerror: - break - else: - raise ValueError("Iteration limit reached, no converging solution found") - if logsteps: - # If logstep is true, then log intermediate steps - return a, error, steps - return a, error, [] - - -if __name__ == "__main__": - from matplotlib import pyplot as plt - - 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 - ) - plt.plot([abs(f(x)) for x in steps]) - plt.xlabel("step") - plt.ylabel("error") - plt.show() - print(f"solution = {{{solution:f}}}, error = {{{error:f}}}") diff --git a/maths/numerical_analysis/newton_raphson_new.py b/maths/numerical_analysis/newton_raphson_new.py deleted file mode 100644 index f61841e2eb84..000000000000 --- a/maths/numerical_analysis/newton_raphson_new.py +++ /dev/null @@ -1,83 +0,0 @@ -# Implementing Newton Raphson method in Python -# Author: Saksham Gupta -# -# The Newton-Raphson method (also known as Newton's method) is a way to -# quickly find a good approximation for the root of a functreal-valued ion -# The method can also be extended to complex functions -# -# Newton's Method - https://en.wikipedia.org/wiki/Newton's_method - -from sympy import diff, lambdify, symbols -from sympy.functions import * # noqa: F403 - - -def newton_raphson( - function: str, - starting_point: complex, - variable: str = "x", - precision: float = 10**-10, - multiplicity: int = 1, -) -> complex: - """Finds root from the 'starting_point' onwards by Newton-Raphson method - Refer to https://docs.sympy.org/latest/modules/functions/index.html - for usable mathematical functions - - >>> newton_raphson("sin(x)", 2) - 3.141592653589793 - >>> newton_raphson("x**4 -5", 0.4 + 5j) - (-7.52316384526264e-37+1.4953487812212207j) - >>> newton_raphson('log(y) - 1', 2, variable='y') - 2.7182818284590455 - >>> newton_raphson('exp(x) - 1', 10, precision=0.005) - 1.2186556186174883e-10 - >>> newton_raphson('cos(x)', 0) - Traceback (most recent call last): - ... - ZeroDivisionError: Could not find root - """ - - x = symbols(variable) - func = lambdify(x, function) - diff_function = lambdify(x, diff(function, x)) - - prev_guess = starting_point - - while True: - if diff_function(prev_guess) != 0: - next_guess = prev_guess - multiplicity * func(prev_guess) / diff_function( - prev_guess - ) - else: - raise ZeroDivisionError("Could not find root") from None - - # Precision is checked by comparing the difference of consecutive guesses - if abs(next_guess - prev_guess) < precision: - return next_guess - - prev_guess = next_guess - - -# 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 - # Find fourth Root of 5 - print(f"The root of x**4 - 5 = 0 is {newton_raphson('x**4 -5', 0.4 +5j)}") - - # Find value of e - print( - "The root of log(y) - 1 = 0 is ", - f"{newton_raphson('log(y) - 1', 2, variable='y')}", - ) - - # Exponential Roots - print( - "The root of exp(x) - 1 = 0 is", - f"{newton_raphson('exp(x) - 1', 10, precision=0.005)}", - ) - - # Find root of cos(x) - print(f"The root of cos(x) = 0 is {newton_raphson('cos(x)', 0)}") From 6971af2416af051b13f888bebdfefa222c89c15d Mon Sep 17 00:00:00 2001 From: Marek Mazij <112333347+Mrk-Mzj@users.noreply.github.com> Date: Tue, 24 Oct 2023 00:22:09 +0200 Subject: [PATCH 2564/2908] feat: RGB to CMYK color converter (#10741) * feat: code functional, commented, tested * fix: compering types, exception msg, line length * fix: type hints --- conversions/rgb_cmyk_conversion.py | 71 ++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 conversions/rgb_cmyk_conversion.py diff --git a/conversions/rgb_cmyk_conversion.py b/conversions/rgb_cmyk_conversion.py new file mode 100644 index 000000000000..07d65b704c44 --- /dev/null +++ b/conversions/rgb_cmyk_conversion.py @@ -0,0 +1,71 @@ +def rgb_to_cmyk(r_input: int, g_input: int, b_input: int) -> tuple[int, int, int, int]: + """ + Simple RGB to CMYK conversion. Returns percentages of CMYK paint. + https://www.programmingalgorithms.com/algorithm/rgb-to-cmyk/ + + Note: this is a very popular algorithm that converts colors linearly and gives + only approximate results. Actual preparation for printing requires advanced color + conversion considering the color profiles and parameters of the target device. + + >>> rgb_to_cmyk(255, 200, "a") + Traceback (most recent call last): + ... + ValueError: Expected int, found (, , ) + + >>> rgb_to_cmyk(255, 255, 999) + Traceback (most recent call last): + ... + ValueError: Expected int of the range 0..255 + + >>> rgb_to_cmyk(255, 255, 255) # white + (0, 0, 0, 0) + + >>> rgb_to_cmyk(128, 128, 128) # gray + (0, 0, 0, 50) + + >>> rgb_to_cmyk(0, 0, 0) # black + (0, 0, 0, 100) + + >>> rgb_to_cmyk(255, 0, 0) # red + (0, 100, 100, 0) + + >>> rgb_to_cmyk(0, 255, 0) # green + (100, 0, 100, 0) + + >>> rgb_to_cmyk(0, 0, 255) # blue + (100, 100, 0, 0) + """ + + if ( + not isinstance(r_input, int) + or not isinstance(g_input, int) + or not isinstance(b_input, int) + ): + msg = f"Expected int, found {type(r_input), type(g_input), type(b_input)}" + raise ValueError(msg) + + if not 0 <= r_input < 256 or not 0 <= g_input < 256 or not 0 <= b_input < 256: + raise ValueError("Expected int of the range 0..255") + + # changing range from 0..255 to 0..1 + r = r_input / 255 + g = g_input / 255 + b = b_input / 255 + + k = 1 - max(r, g, b) + + if k == 1: # pure black + return 0, 0, 0, 100 + + c = round(100 * (1 - r - k) / (1 - k)) + m = round(100 * (1 - g - k) / (1 - k)) + y = round(100 * (1 - b - k) / (1 - k)) + k = round(100 * k) + + return c, m, y, k + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 481aff7928b6a352c3cfa49045f0dd390d9d0868 Mon Sep 17 00:00:00 2001 From: Gourav Raj <59208847+gouravrajbit@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:24:38 +0530 Subject: [PATCH 2565/2908] Add `Mirror a Binary Tree` solution (#9534) * Add `Invert a Binary Tree` solution * Add type * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add `doctest` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add `test` to `get_tree_inorder` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add `test` changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix lint errors * Fix precommit errors * Update and rename invert_binary_tree.py to mirror_binary_tree.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../binary_tree/mirror_binary_tree.py | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 data_structures/binary_tree/mirror_binary_tree.py diff --git a/data_structures/binary_tree/mirror_binary_tree.py b/data_structures/binary_tree/mirror_binary_tree.py new file mode 100644 index 000000000000..39305c2a9da2 --- /dev/null +++ b/data_structures/binary_tree/mirror_binary_tree.py @@ -0,0 +1,153 @@ +""" +Given the root of a binary tree, mirror the tree, and return its root. + +Leetcode problem reference: https://leetcode.com/problems/mirror-binary-tree/ +""" +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass + + +@dataclass +class Node: + """ + A Node has value variable and pointers to Nodes to its left and right. + """ + + value: int + left: Node | None = None + right: Node | None = None + + def __iter__(self) -> Iterator[int]: + if self.left: + yield from self.left + yield self.value + if self.right: + yield from self.right + + def __len__(self) -> int: + return sum(1 for _ in self) + + def mirror(self) -> Node: + """ + Mirror the binary tree rooted at this node by swapping left and right children. + + >>> tree = Node(0) + >>> list(tree) + [0] + >>> list(tree.mirror()) + [0] + >>> tree = Node(1, Node(0), Node(3, Node(2), Node(4, None, Node(5)))) + >>> tuple(tree) + (0, 1, 2, 3, 4, 5) + >>> tuple(tree.mirror()) + (5, 4, 3, 2, 1, 0) + """ + self.left, self.right = self.right, self.left + if self.left: + self.left.mirror() + if self.right: + self.right.mirror() + return self + + +def make_tree_seven() -> Node: + r""" + Return a binary tree with 7 nodes that looks like this: + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + + >>> tree_seven = make_tree_seven() + >>> len(tree_seven) + 7 + >>> list(tree_seven) + [4, 2, 5, 1, 6, 3, 7] + """ + tree = Node(1) + tree.left = Node(2) + tree.right = Node(3) + tree.left.left = Node(4) + tree.left.right = Node(5) + tree.right.left = Node(6) + tree.right.right = Node(7) + return tree + + +def make_tree_nine() -> Node: + r""" + Return a binary tree with 9 nodes that looks like this: + 1 + / \ + 2 3 + / \ \ + 4 5 6 + / \ \ + 7 8 9 + + >>> tree_nine = make_tree_nine() + >>> len(tree_nine) + 9 + >>> list(tree_nine) + [7, 4, 8, 2, 5, 9, 1, 3, 6] + """ + tree = Node(1) + tree.left = Node(2) + tree.right = Node(3) + tree.left.left = Node(4) + tree.left.right = Node(5) + tree.right.right = Node(6) + tree.left.left.left = Node(7) + tree.left.left.right = Node(8) + tree.left.right.right = Node(9) + return tree + + +def main() -> None: + r""" + Mirror binary trees with the given root and returns the root + + >>> tree = make_tree_nine() + >>> tuple(tree) + (7, 4, 8, 2, 5, 9, 1, 3, 6) + >>> tuple(tree.mirror()) + (6, 3, 1, 9, 5, 2, 8, 4, 7) + + nine_tree: + 1 + / \ + 2 3 + / \ \ + 4 5 6 + / \ \ + 7 8 9 + + The mirrored tree looks like this: + 1 + / \ + 3 2 + / / \ + 6 5 4 + / / \ + 9 8 7 + """ + trees = {"zero": Node(0), "seven": make_tree_seven(), "nine": make_tree_nine()} + for name, tree in trees.items(): + print(f" The {name} tree: {tuple(tree)}") + # (0,) + # (4, 2, 5, 1, 6, 3, 7) + # (7, 4, 8, 2, 5, 9, 1, 3, 6) + print(f"Mirror of {name} tree: {tuple(tree.mirror())}") + # (0,) + # (7, 3, 6, 1, 5, 2, 4) + # (6, 3, 1, 9, 5, 2, 8, 4, 7) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From 17059b7ece0e9b2aa0f6e1789d635d6c3eef93ca Mon Sep 17 00:00:00 2001 From: Bhavesh Mathur <130584844+bhavesh1oo@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:33:22 +0530 Subject: [PATCH 2566/2908] Added doctests , type hints for other/nested_brackets.py (#10872) * Added doctests , type hints * Update nested_brackets.py --------- Co-authored-by: Christian Clauss --- other/nested_brackets.py | 69 ++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/other/nested_brackets.py b/other/nested_brackets.py index 19c6dd53c8b2..5760fa29b2fd 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. @@ -14,31 +14,60 @@ """ -def is_balanced(s): - stack = [] - open_brackets = set({"(", "[", "{"}) - closed_brackets = set({")", "]", "}"}) +def is_balanced(s: str) -> bool: + """ + >>> is_balanced("") + True + >>> is_balanced("()") + True + >>> is_balanced("[]") + True + >>> is_balanced("{}") + True + >>> is_balanced("()[]{}") + True + >>> is_balanced("(())") + True + >>> is_balanced("[[") + False + >>> is_balanced("([{}])") + True + >>> is_balanced("(()[)]") + False + >>> is_balanced("([)]") + False + >>> is_balanced("[[()]]") + True + >>> is_balanced("(()(()))") + True + >>> is_balanced("]") + False + >>> is_balanced("Life is a bowl of cherries.") + True + >>> is_balanced("Life is a bowl of che{}ies.") + True + >>> is_balanced("Life is a bowl of che}{ies.") + False + """ open_to_closed = {"{": "}", "[": "]", "(": ")"} - - for i in range(len(s)): - if s[i] in open_brackets: - stack.append(s[i]) - - elif s[i] in closed_brackets and ( - len(stack) == 0 or (len(stack) > 0 and open_to_closed[stack.pop()] != s[i]) + stack = [] + for symbol in s: + if symbol in open_to_closed: + stack.append(symbol) + elif symbol in open_to_closed.values() and ( + not stack or open_to_closed[stack.pop()] != symbol ): return False - - return len(stack) == 0 + return not stack # stack should be empty def main(): s = input("Enter sequence of brackets: ") - if is_balanced(s): - print(s, "is balanced") - else: - print(s, "is not balanced") + print(f"'{s}' is {'' if is_balanced(s) else 'not '}balanced.") if __name__ == "__main__": + from doctest import testmod + + testmod() main() From eb17fcf8f5e77a6d3c870427db02b258515b4997 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 24 Oct 2023 14:45:36 +0200 Subject: [PATCH 2567/2908] Use dataclasses in circular_linked_list.py (#10884) * Use dataclasses in circular_linked_list.py * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + .../linked_list/circular_linked_list.py | 49 ++++++++----------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f0b1f7c13c2b..5f8eabb6df88 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -157,6 +157,7 @@ * [Prefix Conversions](conversions/prefix_conversions.py) * [Prefix Conversions String](conversions/prefix_conversions_string.py) * [Pressure Conversions](conversions/pressure_conversions.py) + * [Rgb Cmyk Conversion](conversions/rgb_cmyk_conversion.py) * [Rgb Hsv Conversion](conversions/rgb_hsv_conversion.py) * [Roman Numerals](conversions/roman_numerals.py) * [Speed Conversions](conversions/speed_conversions.py) @@ -198,6 +199,7 @@ * [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py) * [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py) * [Merge Two Binary Trees](data_structures/binary_tree/merge_two_binary_trees.py) + * [Mirror Binary Tree](data_structures/binary_tree/mirror_binary_tree.py) * [Non Recursive Segment Tree](data_structures/binary_tree/non_recursive_segment_tree.py) * [Number Of Possible Binary Trees](data_structures/binary_tree/number_of_possible_binary_trees.py) * [Red Black Tree](data_structures/binary_tree/red_black_tree.py) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 54343c80a30f..bb64441d4560 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -1,27 +1,20 @@ from __future__ import annotations from collections.abc import Iterator +from dataclasses import dataclass from typing import Any +@dataclass class Node: - def __init__(self, data: Any): - """ - Initialize a new Node with the given data. - Args: - data: The data to be stored in the node. - """ - self.data: Any = data - self.next: Node | None = None # Reference to the next node + data: Any + next_node: Node | None = None +@dataclass class CircularLinkedList: - def __init__(self) -> None: - """ - Initialize an empty Circular Linked List. - """ - self.head: Node | None = None # Reference to the head (first node) - self.tail: Node | None = None # Reference to the tail (last node) + head: Node | None = None # Reference to the head (first node) + tail: Node | None = None # Reference to the tail (last node) def __iter__(self) -> Iterator[Any]: """ @@ -32,7 +25,7 @@ def __iter__(self) -> Iterator[Any]: node = self.head while node: yield node.data - node = node.next + node = node.next_node if node == self.head: break @@ -76,20 +69,20 @@ def insert_nth(self, index: int, data: Any) -> None: raise IndexError("list index out of range.") new_node: Node = Node(data) if self.head is None: - new_node.next = new_node # First node points to itself + new_node.next_node = new_node # First node points to itself self.tail = self.head = new_node elif index == 0: # Insert at the head - new_node.next = self.head + new_node.next_node = self.head assert self.tail is not None # List is not empty, tail exists - self.head = self.tail.next = new_node + self.head = self.tail.next_node = new_node else: temp: Node | None = self.head for _ in range(index - 1): assert temp is not None - temp = temp.next + temp = temp.next_node assert temp is not None - new_node.next = temp.next - temp.next = new_node + new_node.next_node = temp.next_node + temp.next_node = new_node if index == len(self) - 1: # Insert at the tail self.tail = new_node @@ -130,18 +123,18 @@ def delete_nth(self, index: int = 0) -> Any: if self.head == self.tail: # Just one node self.head = self.tail = None elif index == 0: # Delete head node - assert self.tail.next is not None - self.tail.next = self.tail.next.next - self.head = self.head.next + assert self.tail.next_node is not None + self.tail.next_node = self.tail.next_node.next_node + self.head = self.head.next_node else: temp: Node | None = self.head for _ in range(index - 1): assert temp is not None - temp = temp.next + temp = temp.next_node assert temp is not None - assert temp.next is not None - delete_node = temp.next - temp.next = temp.next.next + assert temp.next_node is not None + delete_node = temp.next_node + temp.next_node = temp.next_node.next_node if index == len(self) - 1: # Delete at tail self.tail = temp return delete_node.data From a23dd7ecbea89be8f6b3c7fcf214425274db0d02 Mon Sep 17 00:00:00 2001 From: SEIKH NABAB UDDIN <93948993+nababuddin@users.noreply.github.com> Date: Tue, 24 Oct 2023 18:42:32 +0530 Subject: [PATCH 2568/2908] Change from only weatherstack to both (#10882) * Update current_weather.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_weather.py * Update current_weather.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_weather.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_weather.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_weather.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_weather.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_weather.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_weather.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_weather.py * Update current_weather.py * Update current_weather.py * Update current_weather.py * import requests --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- web_programming/current_weather.py | 64 ++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/web_programming/current_weather.py b/web_programming/current_weather.py index 3ed4c8a95a0c..3b6cd177cdfb 100644 --- a/web_programming/current_weather.py +++ b/web_programming/current_weather.py @@ -1,30 +1,50 @@ import requests -APPID = "" # <-- Put your OpenWeatherMap appid here! -URL_BASE = "https://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 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() +# Put your API key(s) here +OPENWEATHERMAP_API_KEY = "" +WEATHERSTACK_API_KEY = "" + +# Define the URL for the APIs with placeholders +OPENWEATHERMAP_URL_BASE = "https://api.openweathermap.org/data/2.5/weather" +WEATHERSTACK_URL_BASE = "http://api.weatherstack.com/current" + + +def current_weather(location: str) -> list[dict]: + """ + >>> current_weather("location") + Traceback (most recent call last): + ... + ValueError: No API keys provided or no valid data returned. + """ + weather_data = [] + if OPENWEATHERMAP_API_KEY: + params_openweathermap = {"q": location, "appid": OPENWEATHERMAP_API_KEY} + response_openweathermap = requests.get( + OPENWEATHERMAP_URL_BASE, params=params_openweathermap + ) + weather_data.append({"OpenWeatherMap": response_openweathermap.json()}) + if WEATHERSTACK_API_KEY: + params_weatherstack = {"query": location, "access_key": WEATHERSTACK_API_KEY} + response_weatherstack = requests.get( + WEATHERSTACK_URL_BASE, params=params_weatherstack + ) + weather_data.append({"Weatherstack": response_weatherstack.json()}) + if not weather_data: + raise ValueError("No API keys provided or no valid data returned.") + return weather_data if __name__ == "__main__": from pprint import pprint - while True: - location = input("Enter a location:").strip() + location = "to be determined..." + while location: + location = input("Enter a location (city name or latitude,longitude): ").strip() if location: - pprint(current_weather(location)) - else: - break + try: + weather_data = current_weather(location) + for forecast in weather_data: + pprint(forecast) + except ValueError as e: + print(repr(e)) + location = "" From 28f4c16132170bf1e00d414809aff0c31d043e22 Mon Sep 17 00:00:00 2001 From: Saptadeep Banerjee <69459134+imSanko@users.noreply.github.com> Date: Tue, 24 Oct 2023 19:16:00 +0530 Subject: [PATCH 2569/2908] Tried new TESTS for the binomial_coefficient (#10822) * Tried new TESTS for the binomial_coefficient * Fix the tests request * Update binomial_coefficient.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update binomial_coefficient.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/binomial_coefficient.py | 46 +++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/maths/binomial_coefficient.py b/maths/binomial_coefficient.py index 6d5b46cb5861..24c54326e305 100644 --- a/maths/binomial_coefficient.py +++ b/maths/binomial_coefficient.py @@ -1,10 +1,48 @@ def binomial_coefficient(n: int, r: int) -> int: """ - Find binomial coefficient using pascals triangle. + Find binomial coefficient using Pascal's triangle. + + Calculate C(n, r) using Pascal's triangle. + + :param n: The total number of items. + :param r: The number of items to choose. + :return: The binomial coefficient C(n, r). >>> binomial_coefficient(10, 5) 252 + >>> binomial_coefficient(10, 0) + 1 + >>> binomial_coefficient(0, 10) + 1 + >>> binomial_coefficient(10, 10) + 1 + >>> binomial_coefficient(5, 2) + 10 + >>> binomial_coefficient(5, 6) + 0 + >>> binomial_coefficient(3, 5) + 0 + >>> binomial_coefficient(-2, 3) + Traceback (most recent call last): + ... + ValueError: n and r must be non-negative integers + >>> binomial_coefficient(5, -1) + Traceback (most recent call last): + ... + ValueError: n and r must be non-negative integers + >>> binomial_coefficient(10.1, 5) + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> binomial_coefficient(10, 5.1) + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer """ + if n < 0 or r < 0: + raise ValueError("n and r must be non-negative integers") + if 0 in (n, r): + return 1 c = [0 for i in range(r + 1)] # nc0 = 1 c[0] = 1 @@ -17,4 +55,8 @@ def binomial_coefficient(n: int, r: int) -> int: return c[r] -print(binomial_coefficient(n=10, r=5)) +if __name__ == "__main__": + from doctest import testmod + + testmod() + print(binomial_coefficient(n=10, r=5)) From aeee0f42a5684e42cb77b664570dd2d29e04b7c1 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 24 Oct 2023 20:06:24 +0530 Subject: [PATCH 2570/2908] Add doctests for fractional knapsack (#10891) * Add doctests for fractional knapsack * Update greedy_methods/fractional_knapsack.py Co-authored-by: Christian Clauss * Run doctests * Update greedy_methods/fractional_knapsack.py Co-authored-by: Christian Clauss * Update greedy_methods/fractional_knapsack.py --------- Co-authored-by: Christian Clauss --- greedy_methods/fractional_knapsack.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/greedy_methods/fractional_knapsack.py b/greedy_methods/fractional_knapsack.py index 58976d40c02b..d52b56f23569 100644 --- a/greedy_methods/fractional_knapsack.py +++ b/greedy_methods/fractional_knapsack.py @@ -6,6 +6,30 @@ def frac_knapsack(vl, wt, w, n): """ >>> frac_knapsack([60, 100, 120], [10, 20, 30], 50, 3) 240.0 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6, 3], 10, 4) + 105.0 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6, 3], 8, 4) + 95.0 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6], 8, 4) + 60.0 + >>> frac_knapsack([10, 40, 30], [5, 4, 6, 3], 8, 4) + 60.0 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6, 3], 0, 4) + 0 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6, 3], 8, 0) + 95.0 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6, 3], -8, 4) + 0 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6, 3], 8, -4) + 95.0 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6, 3], 800, 4) + 130 + >>> frac_knapsack([10, 40, 30, 50], [5, 4, 6, 3], 8, 400) + 95.0 + >>> frac_knapsack("ABCD", [5, 4, 6, 3], 8, 400) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'str' and 'int' """ r = sorted(zip(vl, wt), key=lambda x: x[0] / x[1], reverse=True) From 28302db9417daf769bec3aface9016afabeb5133 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 24 Oct 2023 21:23:17 +0530 Subject: [PATCH 2571/2908] Remove myself from CODEOWNERS (#10220) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 05cd709a8f62..a0531cdeec69 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,7 +7,7 @@ # Order is important. The last matching pattern has the most precedence. -/.* @cclauss @dhruvmanila +/.* @cclauss # /arithmetic_analysis/ From fd227d802661d4be4babae66075542dc153b4569 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 25 Oct 2023 03:05:38 +0530 Subject: [PATCH 2572/2908] Add function docstrings, comments and type hints (#10893) * Add function docstrings, comments and type hints * Fix type mismatch * Fix type hint error * Fix float to int error * Update ford_fulkerson.py * Update ford_fulkerson.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update ford_fulkerson.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- networking_flow/ford_fulkerson.py | 109 ++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/networking_flow/ford_fulkerson.py b/networking_flow/ford_fulkerson.py index 716ed508e679..7d5fb522e012 100644 --- a/networking_flow/ford_fulkerson.py +++ b/networking_flow/ford_fulkerson.py @@ -1,39 +1,95 @@ -# Ford-Fulkerson Algorithm for Maximum Flow Problem """ +Ford-Fulkerson Algorithm for Maximum Flow Problem +* https://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm + Description: - (1) Start with initial flow as 0; - (2) Choose augmenting path from source to sink and add path to flow; + (1) Start with initial flow as 0 + (2) Choose the augmenting path from source to sink and add the path to 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], +] + + +def breadth_first_search(graph: list, source: int, sink: int, parents: list) -> bool: + """ + This function returns True if there is a node that has not iterated. + + Args: + graph: Adjacency matrix of graph + source: Source + sink: Sink + parents: Parent list + + Returns: + True if there is a node that has not iterated. + >>> breadth_first_search(graph, 0, 5, [-1, -1, -1, -1, -1, -1]) + True + >>> breadth_first_search(graph, 0, 6, [-1, -1, -1, -1, -1, -1]) + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + visited = [False] * len(graph) # Mark all nodes as not visited + queue = [] # breadth-first search queue -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[s] = True + # Source node + queue.append(source) + visited[source] = True while queue: - u = queue.pop(0) - for ind in range(len(graph[u])): - if visited[ind] is False and graph[u][ind] > 0: + u = queue.pop(0) # Pop the front node + # Traverse all adjacent nodes of u + for ind, node in enumerate(graph[u]): + if visited[ind] is False and node > 0: queue.append(ind) visited[ind] = True - parent[ind] = u + parents[ind] = u + return visited[sink] - return visited[t] +def ford_fulkerson(graph: list, source: int, sink: int) -> int: + """ + This function returns the maximum flow from source to sink in the given graph. -def ford_fulkerson(graph, source, sink): - # This array is filled by BFS and to store path + CAUTION: This function changes the given graph. + + Args: + graph: Adjacency matrix of graph + source: Source + sink: Sink + + Returns: + Maximum flow + + >>> 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], + ... ] + >>> ford_fulkerson(test_graph, 0, 5) + 23 + """ + # This array is filled by breadth-first search and to store path parent = [-1] * (len(graph)) max_flow = 0 - while bfs(graph, source, sink, parent): - path_flow = float("Inf") + + # While there is a path from source to sink + while breadth_first_search(graph, source, sink, parent): + path_flow = int(1e9) # Infinite value s = sink while s != source: - # Find the minimum value in select path + # Find the minimum value in the selected path path_flow = min(path_flow, graph[parent[s]][s]) s = parent[s] @@ -45,17 +101,12 @@ def ford_fulkerson(graph, source, sink): 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], -] +if __name__ == "__main__": + from doctest import testmod -source, sink = 0, 5 -print(ford_fulkerson(graph, source, sink)) + testmod() + print(f"{ford_fulkerson(graph, source=0, sink=5) = }") From dab4e648965a92a7f73aa5fe6ad8b8afc0fde7f9 Mon Sep 17 00:00:00 2001 From: Bisma nadeem <130698042+Bisma-Nadeemm@users.noreply.github.com> Date: Wed, 25 Oct 2023 02:51:04 +0500 Subject: [PATCH 2573/2908] Code enhancements in binary_insertion_sort.py (#10918) * Code enhancements in binary_insertion_sort.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- sorts/binary_insertion_sort.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/sorts/binary_insertion_sort.py b/sorts/binary_insertion_sort.py index 8d41025583b1..50653a99e7ce 100644 --- a/sorts/binary_insertion_sort.py +++ b/sorts/binary_insertion_sort.py @@ -12,10 +12,11 @@ def binary_insertion_sort(collection: list) -> list: - """Pure implementation of the binary insertion sort algorithm in Python - :param collection: some mutable ordered collection with heterogeneous - comparable items inside - :return: the same collection ordered by ascending + """ + Sorts a list using the binary insertion sort algorithm. + + :param collection: A mutable ordered collection with comparable items. + :return: The same collection ordered in ascending order. Examples: >>> binary_insertion_sort([0, 4, 1234, 4, 1]) @@ -39,23 +40,27 @@ def binary_insertion_sort(collection: list) -> list: n = len(collection) for i in range(1, n): - val = collection[i] + value_to_insert = collection[i] low = 0 high = i - 1 while low <= high: mid = (low + high) // 2 - if val < collection[mid]: + if value_to_insert < collection[mid]: high = mid - 1 else: low = mid + 1 for j in range(i, low, -1): collection[j] = collection[j - 1] - collection[low] = val + collection[low] = value_to_insert return collection -if __name__ == "__main__": +if __name__ == "__main": user_input = input("Enter numbers separated by a comma:\n").strip() - unsorted = [int(item) for item in user_input.split(",")] - print(binary_insertion_sort(unsorted)) + try: + unsorted = [int(item) for item in user_input.split(",")] + except ValueError: + print("Invalid input. Please enter valid integers separated by commas.") + raise + print(f"{binary_insertion_sort(unsorted) = }") From 76acc6de607eebdc0d0d5c68396030d8e240a6ea Mon Sep 17 00:00:00 2001 From: Iyiola Aloko <48067557+ialoko@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:53:31 -0400 Subject: [PATCH 2574/2908] Adding doctests to frequency_finder.py (#10341) * Update frequency_finder.py * Update frequency_finder.py --------- Co-authored-by: Christian Clauss --- strings/frequency_finder.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/strings/frequency_finder.py b/strings/frequency_finder.py index 19f97afbbe37..8479c81ae464 100644 --- a/strings/frequency_finder.py +++ b/strings/frequency_finder.py @@ -49,6 +49,15 @@ def get_item_at_index_zero(x: tuple) -> str: def get_frequency_order(message: str) -> str: + """ + Get the frequency order of the letters in the given string + >>> get_frequency_order('Hello World') + 'LOWDRHEZQXJKVBPYGFMUCSNIAT' + >>> get_frequency_order('Hello@') + 'LHOEZQXJKVBPYGFWMUCDRSNIAT' + >>> get_frequency_order('h') + 'HZQXJKVBPYGFWMUCLDRSNIOATE' + """ letter_to_freq = get_letter_count(message) freq_to_letter: dict[int, list[str]] = { freq: [] for letter, freq in letter_to_freq.items() From c2c6cb0f5c46346cab99121d236b2f5748e3c1df Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 25 Oct 2023 22:28:23 +0200 Subject: [PATCH 2575/2908] Add dataclasses to binary_search_tree.py (#10920) --- .../binary_tree/binary_search_tree.py | 69 ++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index a706d21e3bb2..38691c4755c9 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -14,6 +14,16 @@ >>> t.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 + +>>> tuple(i.value for i in t.traversal_tree(inorder)) +(1, 3, 4, 6, 7, 8, 10, 13, 14) +>>> tuple(t) +(1, 3, 4, 6, 7, 8, 10, 13, 14) +>>> t.find_kth_smallest(3, t.root) +4 +>>> tuple(t)[3-1] +4 + >>> print(" ".join(repr(i.value) for i in t.traversal_tree(postorder))) 1 4 7 6 3 13 14 10 8 >>> t.remove(20) @@ -39,8 +49,12 @@ Test existence >>> t.search(6) is not None True +>>> 6 in t +True >>> t.search(-1) is not None False +>>> -1 in t +False >>> t.search(6).is_right True @@ -49,26 +63,47 @@ >>> t.get_max().value 14 +>>> max(t) +14 >>> t.get_min().value 1 +>>> min(t) +1 >>> t.empty() False +>>> not t +False >>> for i in testlist: ... t.remove(i) >>> t.empty() True +>>> not t +True """ +from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Iterable, Iterator +from dataclasses import dataclass from typing import Any +@dataclass class Node: - def __init__(self, value: int | None = None): - self.value = value - self.parent: Node | None = None # Added in order to delete a node easier - self.left: Node | None = None - self.right: Node | None = None + value: int + left: Node | None = None + right: Node | None = None + parent: Node | None = None # Added in order to delete a node easier + + def __iter__(self) -> Iterator[int]: + """ + >>> list(Node(0)) + [0] + >>> list(Node(0, Node(-1), Node(1), None)) + [-1, 0, 1] + """ + yield from self.left or [] + yield self.value + yield from self.right or [] def __repr__(self) -> str: from pprint import pformat @@ -79,12 +114,18 @@ def __repr__(self) -> str: @property def is_right(self) -> bool: - return self.parent is not None and self is self.parent.right + return bool(self.parent and self is self.parent.right) +@dataclass class BinarySearchTree: - def __init__(self, root: Node | None = None): - self.root = root + root: Node | None = None + + def __bool__(self) -> bool: + return bool(self.root) + + def __iter__(self) -> Iterator[int]: + yield from self.root or [] def __str__(self) -> str: """ @@ -227,6 +268,16 @@ def find_kth_smallest(self, k: int, node: Node) -> int: return arr[k - 1] +def inorder(curr_node: Node | None) -> list[Node]: + """ + inorder (left, self, right) + """ + node_list = [] + if curr_node is not None: + node_list = inorder(curr_node.left) + [curr_node] + inorder(curr_node.right) + return node_list + + def postorder(curr_node: Node | None) -> list[Node]: """ postOrder (left, right, self) From 3d0a409ce119e1b7734ebaa2ffea660f5359080b Mon Sep 17 00:00:00 2001 From: AdityaAtoZ <129844626+AdityaAtoZ@users.noreply.github.com> Date: Thu, 26 Oct 2023 02:03:35 +0530 Subject: [PATCH 2576/2908] Improved Equilibrium Index of an Array. (#10899) * Improved Equilibrium Index of an Array. This is the modifications made to the original code: 1. Create Doctest Instructions: Python "doctest" can be executed by running the following command: python -m doctest -v equilibrium_index.py. 2. Deleted Argument {size}: Deleted the `size` argument because `len(arr)} allows the array's length to be determined inside the function, simplifying and improving the readability of the function signature. 3. Used {enumerate}: To improve code readability and indicate that we're working with element-index pairs, we iterated through the array using both elements and their indices using the `enumerate` function. 4. Optimized the Loop: To prevent pointless additions, the loop was improved by initializing {left_sum} with the value of the first element (arr[0]). Furthermore, since the beginning and last items (0 and size - 1) cannot be equilibrium indices, there is no need to check them, saving further computations. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../arrays/equilibrium_index_in_array.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/data_structures/arrays/equilibrium_index_in_array.py b/data_structures/arrays/equilibrium_index_in_array.py index 4099896d226d..8802db6206bb 100644 --- a/data_structures/arrays/equilibrium_index_in_array.py +++ b/data_structures/arrays/equilibrium_index_in_array.py @@ -2,7 +2,7 @@ Find the Equilibrium Index of an Array. Reference: https://www.geeksforgeeks.org/equilibrium-index-of-an-array/ -Python doctests can be run with the following command: +Python doctest can be run with the following command: python -m doctest -v equilibrium_index.py Given a sequence arr[] of size n, this function returns @@ -20,35 +20,34 @@ """ -def equilibrium_index(arr: list[int], size: int) -> int: +def equilibrium_index(arr: list[int]) -> int: """ Find the equilibrium index of an array. Args: - arr : The input array of integers. - size : The size of the array. + arr (list[int]): The input array of integers. Returns: int: The equilibrium index or -1 if no equilibrium index exists. Examples: - >>> equilibrium_index([-7, 1, 5, 2, -4, 3, 0], 7) + >>> equilibrium_index([-7, 1, 5, 2, -4, 3, 0]) 3 - >>> equilibrium_index([1, 2, 3, 4, 5], 5) + >>> equilibrium_index([1, 2, 3, 4, 5]) -1 - >>> equilibrium_index([1, 1, 1, 1, 1], 5) + >>> equilibrium_index([1, 1, 1, 1, 1]) 2 - >>> equilibrium_index([2, 4, 6, 8, 10, 3], 6) + >>> equilibrium_index([2, 4, 6, 8, 10, 3]) -1 """ total_sum = sum(arr) left_sum = 0 - for i in range(size): - total_sum -= arr[i] + for i, value in enumerate(arr): + total_sum -= value if left_sum == total_sum: return i - left_sum += arr[i] + left_sum += value return -1 From e1e5963812c3f59a60181307bccf15792ad2406c Mon Sep 17 00:00:00 2001 From: Bisma nadeem <130698042+Bisma-Nadeemm@users.noreply.github.com> Date: Thu, 26 Oct 2023 02:26:54 +0500 Subject: [PATCH 2577/2908] Code Enhancements in merge_sort.py (#10911) * Code Enhancements in merge_sort.py This enhanced code includes improved variable naming, error handling for user input, and more detailed docstrings. It's now more robust and readable. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sorts/merge_sort.py | 47 ++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index e80b1cb226ec..0628b848b794 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -12,9 +12,13 @@ def merge_sort(collection: list) -> list: """ - :param collection: some mutable ordered collection with heterogeneous - comparable items inside - :return: the same collection ordered by ascending + Sorts a list using the merge sort algorithm. + + :param collection: A mutable ordered collection with comparable items. + :return: The same collection ordered in ascending order. + + Time Complexity: O(n log n) + Examples: >>> merge_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] @@ -26,31 +30,34 @@ def merge_sort(collection: list) -> list: def merge(left: list, right: list) -> list: """ - Merge left and right. + Merge two sorted lists into a single sorted list. - :param left: left collection - :param right: right collection - :return: merge result + :param left: Left collection + :param right: Right collection + :return: Merged 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()) + result = [] + while left and right: + result.append(left.pop(0) if left[0] <= right[0] else right.pop(0)) + result.extend(left) + result.extend(right) + return result if len(collection) <= 1: return collection - mid = len(collection) // 2 - return merge(merge_sort(collection[:mid]), merge_sort(collection[mid:])) + mid_index = len(collection) // 2 + return merge(merge_sort(collection[:mid_index]), merge_sort(collection[mid_index:])) 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=",") + + try: + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + sorted_list = merge_sort(unsorted) + print(*sorted_list, sep=",") + except ValueError: + print("Invalid input. Please enter valid integers separated by commas.") From 0ffe506ea79fcd9820a6c9bf3194a3bfcd677b57 Mon Sep 17 00:00:00 2001 From: Humzafazal72 <125209604+Humzafazal72@users.noreply.github.com> Date: Thu, 26 Oct 2023 04:05:35 +0500 Subject: [PATCH 2578/2908] added mean absolute percentage error (#10464) * added mean absolute percentage error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added mean_absolute_percentage_error * added mean_absolute_percentage_error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added mean_absolute_percentage_error * added mean_absolute_percentage_error * added mean absolute percentage error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added mean absolute percentage error * added mean absolute percentage error * added mean absolute percentage error * added mean absolute percentage error * added mean absolute percentage error * Update machine_learning/loss_functions.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- machine_learning/loss_functions.py | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py index ef34296360e2..e5b7a713b6f2 100644 --- a/machine_learning/loss_functions.py +++ b/machine_learning/loss_functions.py @@ -297,6 +297,51 @@ def mean_squared_logarithmic_error(y_true: np.ndarray, y_pred: np.ndarray) -> fl return np.mean(squared_logarithmic_errors) +def mean_absolute_percentage_error( + y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-15 +) -> float: + """ + Calculate the Mean Absolute Percentage Error between y_true and y_pred. + + Mean Absolute Percentage Error calculates the average of the absolute + percentage differences between the predicted and true values. + + Formula = (Σ|y_true[i]-Y_pred[i]/y_true[i]|)/n + + Source: https://stephenallwright.com/good-mape-score/ + + Parameters: + y_true (np.ndarray): Numpy array containing true/target values. + y_pred (np.ndarray): Numpy array containing predicted values. + + Returns: + float: The Mean Absolute Percentage error between y_true and y_pred. + + Examples: + >>> y_true = np.array([10, 20, 30, 40]) + >>> y_pred = np.array([12, 18, 33, 45]) + >>> mean_absolute_percentage_error(y_true, y_pred) + 0.13125 + + >>> y_true = np.array([1, 2, 3, 4]) + >>> y_pred = np.array([2, 3, 4, 5]) + >>> mean_absolute_percentage_error(y_true, y_pred) + 0.5208333333333333 + + >>> y_true = np.array([34, 37, 44, 47, 48, 48, 46, 43, 32, 27, 26, 24]) + >>> y_pred = np.array([37, 40, 46, 44, 46, 50, 45, 44, 34, 30, 22, 23]) + >>> mean_absolute_percentage_error(y_true, y_pred) + 0.064671076436071 + """ + if len(y_true) != len(y_pred): + raise ValueError("The length of the two arrays should be the same.") + + y_true = np.where(y_true == 0, epsilon, y_true) + absolute_percentage_diff = np.abs((y_true - y_pred) / y_true) + + return np.mean(absolute_percentage_diff) + + if __name__ == "__main__": import doctest From 0e7f8284a32286534691e437d67405b6a09b10e1 Mon Sep 17 00:00:00 2001 From: Dale Dai <145884899+CouldNot@users.noreply.github.com> Date: Wed, 25 Oct 2023 22:27:46 -0700 Subject: [PATCH 2579/2908] Add error tests in doctest and fix error message (#10930) * Add error tests in doctest and fix error message * Change AssertationError to ValueError * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/prime_check.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/maths/prime_check.py b/maths/prime_check.py index c17877a57705..f1bc4def2469 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -29,12 +29,19 @@ def is_prime(number: int) -> bool: True >>> is_prime(67483) False + >>> is_prime(16.1) + Traceback (most recent call last): + ... + ValueError: is_prime() only accepts positive integers + >>> is_prime(-4) + Traceback (most recent call last): + ... + ValueError: is_prime() only accepts positive integers """ # precondition - assert isinstance(number, int) and ( - number >= 0 - ), "'number' must been an int and positive" + if not isinstance(number, int) or not number >= 0: + raise ValueError("is_prime() only accepts positive integers") if 1 < number < 4: # 2 and 3 are primes @@ -64,7 +71,7 @@ def test_primes(self): assert is_prime(29) def test_not_primes(self): - with pytest.raises(AssertionError): + with pytest.raises(ValueError): is_prime(-19) assert not is_prime( 0 From 1a5d5cf93d30fc123af680ee9c58eb955932972b Mon Sep 17 00:00:00 2001 From: Megan Payne Date: Thu, 26 Oct 2023 07:31:47 +0200 Subject: [PATCH 2580/2908] Mean absolute error (#10927) * added mean absolute error to loss_functions.py * added doctest to mean absolute error to loss_functions.py * fixed long line in loss_functions.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed error in MAE * Update machine_learning/loss_functions.py Co-authored-by: Tianyi Zheng --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- machine_learning/loss_functions.py | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py index e5b7a713b6f2..ea1f390e358a 100644 --- a/machine_learning/loss_functions.py +++ b/machine_learning/loss_functions.py @@ -261,6 +261,43 @@ def mean_squared_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: return np.mean(squared_errors) +def mean_absolute_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Calculates the Mean Absolute Error (MAE) between ground truth (observed) + and predicted values. + + MAE measures the absolute difference between true values and predicted values. + + Equation: + MAE = (1/n) * Σ(abs(y_true - y_pred)) + + Reference: https://en.wikipedia.org/wiki/Mean_absolute_error + + Parameters: + - y_true: The true values (ground truth) + - y_pred: The predicted values + + >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) + >>> np.isclose(mean_absolute_error(true_values, predicted_values), 0.16) + True + >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) + >>> np.isclose(mean_absolute_error(true_values, predicted_values), 2.16) + False + >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + >>> predicted_probs = np.array([0.3, 0.8, 0.9, 5.2]) + >>> mean_absolute_error(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + return np.mean(abs(y_true - y_pred)) + + def mean_squared_logarithmic_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: """ Calculate the mean squared logarithmic error (MSLE) between ground truth and From a8f05fe0a5d8b7e88d99c160b177ff3f3f07edcc Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 26 Oct 2023 00:02:35 -0700 Subject: [PATCH 2581/2908] Add doctests and type hints (#10974) * Add doctests and type hints * Apply suggestions from code review * Update tarjans_scc.py * Update tarjans_scc.py --------- Co-authored-by: Tianyi Zheng --- graphs/tarjans_scc.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/graphs/tarjans_scc.py b/graphs/tarjans_scc.py index dfd2e52704d5..a75dc4d2ca95 100644 --- a/graphs/tarjans_scc.py +++ b/graphs/tarjans_scc.py @@ -1,7 +1,7 @@ from collections import deque -def tarjan(g): +def tarjan(g: list[list[int]]) -> list[list[int]]: """ Tarjan's algo for finding strongly connected components in a directed graph @@ -19,15 +19,30 @@ def tarjan(g): 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) + + >>> tarjan([[2, 3, 4], [2, 3, 4], [0, 1, 3], [0, 1, 2], [1]]) + [[4, 3, 1, 2, 0]] + >>> tarjan([[], [], [], []]) + [[0], [1], [2], [3]] + >>> a = [0, 1, 2, 3, 4, 5, 4] + >>> b = [1, 0, 3, 2, 5, 4, 0] + >>> n = 7 + >>> sorted(tarjan(create_graph(n, list(zip(a, b))))) == sorted( + ... tarjan(create_graph(n, list(zip(a[::-1], b[::-1]))))) + True + >>> a = [0, 1, 2, 3, 4, 5, 6] + >>> b = [0, 1, 2, 3, 4, 5, 6] + >>> sorted(tarjan(create_graph(n, list(zip(a, b))))) + [[0], [1], [2], [3], [4], [5], [6]] """ n = len(g) - stack = deque() + stack: deque[int] = deque() on_stack = [False for _ in range(n)] index_of = [-1 for _ in range(n)] lowlink_of = index_of[:] - def strong_connect(v, index, components): + def strong_connect(v: int, index: int, components: list[list[int]]) -> int: index_of[v] = index # the number when this node is seen lowlink_of[v] = index # lowest rank node reachable from here index += 1 @@ -57,7 +72,7 @@ def strong_connect(v, index, components): components.append(component) return index - components = [] + components: list[list[int]] = [] for v in range(n): if index_of[v] == -1: strong_connect(v, 0, components) @@ -65,8 +80,16 @@ def strong_connect(v, index, components): return components -def create_graph(n, edges): - g = [[] for _ in range(n)] +def create_graph(n: int, edges: list[tuple[int, int]]) -> list[list[int]]: + """ + >>> n = 7 + >>> source = [0, 0, 1, 2, 3, 3, 4, 4, 6] + >>> target = [1, 3, 2, 0, 1, 4, 5, 6, 5] + >>> edges = list(zip(source, target)) + >>> create_graph(n, edges) + [[1, 3], [2], [0], [1, 4], [5, 6], [], [5]] + """ + g: list[list[int]] = [[] for _ in range(n)] for u, v in edges: g[u].append(v) return g From c71c280726fb4e9487833993042e54598fe94fd9 Mon Sep 17 00:00:00 2001 From: Ravi Kumar <119737193+ravi-ivar-7@users.noreply.github.com> Date: Thu, 26 Oct 2023 12:50:28 +0530 Subject: [PATCH 2582/2908] added runge kutta gills method to maths/ numerical_analysis (#10967) * added runge kutta gills method * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- maths/numerical_analysis/runge_kutta_gills.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 maths/numerical_analysis/runge_kutta_gills.py diff --git a/maths/numerical_analysis/runge_kutta_gills.py b/maths/numerical_analysis/runge_kutta_gills.py new file mode 100644 index 000000000000..2bd9cd6129b8 --- /dev/null +++ b/maths/numerical_analysis/runge_kutta_gills.py @@ -0,0 +1,89 @@ +""" +Use the Runge-Kutta-Gill's method of order 4 to solve Ordinary Differential Equations. + +https://www.geeksforgeeks.org/gills-4th-order-method-to-solve-differential-equations/ +Author : Ravi Kumar +""" +from collections.abc import Callable +from math import sqrt + +import numpy as np + + +def runge_kutta_gills( + func: Callable[[float, float], float], + x_initial: float, + y_initial: float, + step_size: float, + x_final: float, +) -> np.ndarray: + """ + Solve an Ordinary Differential Equations using Runge-Kutta-Gills Method of order 4. + + args: + func: An ordinary differential equation (ODE) as function of x and y. + x_initial: The initial value of x. + y_initial: The initial value of y. + step_size: The increment value of x. + x_final: The final value of x. + + Returns: + Solution of y at each nodal point + + >>> def f(x, y): + ... return (x-y)/2 + >>> y = runge_kutta_gills(f, 0, 3, 0.2, 5) + >>> y[-1] + 3.4104259225717537 + + >>> def f(x,y): + ... return x + >>> y = runge_kutta_gills(f, -1, 0, 0.2, 0) + >>> y + array([ 0. , -0.18, -0.32, -0.42, -0.48, -0.5 ]) + + >>> def f(x, y): + ... return x + y + >>> y = runge_kutta_gills(f, 0, 0, 0.2, -1) + Traceback (most recent call last): + ... + ValueError: The final value of x must be greater than initial value of x. + + >>> def f(x, y): + ... return x + >>> y = runge_kutta_gills(f, -1, 0, -0.2, 0) + Traceback (most recent call last): + ... + ValueError: Step size must be positive. + """ + if x_initial >= x_final: + raise ValueError( + "The final value of x must be greater than initial value of x." + ) + + if step_size <= 0: + raise ValueError("Step size must be positive.") + + n = int((x_final - x_initial) / step_size) + y = np.zeros(n + 1) + y[0] = y_initial + for i in range(n): + k1 = step_size * func(x_initial, y[i]) + k2 = step_size * func(x_initial + step_size / 2, y[i] + k1 / 2) + k3 = step_size * func( + x_initial + step_size / 2, + y[i] + (-0.5 + 1 / sqrt(2)) * k1 + (1 - 1 / sqrt(2)) * k2, + ) + k4 = step_size * func( + x_initial + step_size, y[i] - (1 / sqrt(2)) * k2 + (1 + 1 / sqrt(2)) * k3 + ) + + y[i + 1] = y[i] + (k1 + (2 - sqrt(2)) * k2 + (2 + sqrt(2)) * k3 + k4) / 6 + x_initial += step_size + return y + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From dd7d18d49e9edc635f692b1f3db933e8ea717023 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Thu, 26 Oct 2023 13:25:56 +0530 Subject: [PATCH 2583/2908] Added doctest, docstring and typehint for sigmoid_function & cost_function (#10828) * Added doctest for sigmoid_function & cost_function * Update logistic_regression.py * Update logistic_regression.py * Minor formatting changes in doctests * Apply suggestions from code review * Made requested changes in logistic_regression.py * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- machine_learning/logistic_regression.py | 60 ++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index f9da0104ab4b..59a70fd65cf9 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -27,7 +27,7 @@ # classification problems -def sigmoid_function(z): +def sigmoid_function(z: float | np.ndarray) -> float | np.ndarray: """ Also known as Logistic Function. @@ -42,11 +42,63 @@ def sigmoid_function(z): @param z: input to the function @returns: returns value in the range 0 to 1 + + Examples: + >>> sigmoid_function(4) + 0.9820137900379085 + >>> sigmoid_function(np.array([-3, 3])) + array([0.04742587, 0.95257413]) + >>> sigmoid_function(np.array([-3, 3, 1])) + array([0.04742587, 0.95257413, 0.73105858]) + >>> sigmoid_function(np.array([-0.01, -2, -1.9])) + array([0.49750002, 0.11920292, 0.13010847]) + >>> sigmoid_function(np.array([-1.3, 5.3, 12])) + array([0.21416502, 0.9950332 , 0.99999386]) + >>> sigmoid_function(np.array([0.01, 0.02, 4.1])) + array([0.50249998, 0.50499983, 0.9836975 ]) + >>> sigmoid_function(np.array([0.8])) + array([0.68997448]) """ return 1 / (1 + np.exp(-z)) -def cost_function(h, y): +def cost_function(h: np.ndarray, y: np.ndarray) -> float: + """ + Cost function quantifies the error between predicted and expected values. + The cost function used in Logistic Regression is called Log Loss + or Cross Entropy Function. + + J(θ) = (1/m) * Σ [ -y * log(hθ(x)) - (1 - y) * log(1 - hθ(x)) ] + + Where: + - J(θ) is the cost that we want to minimize during training + - m is the number of training examples + - Σ represents the summation over all training examples + - y is the actual binary label (0 or 1) for a given example + - hθ(x) is the predicted probability that x belongs to the positive class + + @param h: the output of sigmoid function. It is the estimated probability + that the input example 'x' belongs to the positive class + + @param y: the actual binary label associated with input example 'x' + + Examples: + >>> estimations = sigmoid_function(np.array([0.3, -4.3, 8.1])) + >>> cost_function(h=estimations,y=np.array([1, 0, 1])) + 0.18937868932131605 + >>> estimations = sigmoid_function(np.array([4, 3, 1])) + >>> cost_function(h=estimations,y=np.array([1, 0, 0])) + 1.459999655669926 + >>> estimations = sigmoid_function(np.array([4, -3, -1])) + >>> cost_function(h=estimations,y=np.array([1,0,0])) + 0.1266663223365915 + >>> estimations = sigmoid_function(0) + >>> cost_function(h=estimations,y=np.array([1])) + 0.6931471805599453 + + References: + - https://en.wikipedia.org/wiki/Logistic_regression + """ return (-y * np.log(h) - (1 - y) * np.log(1 - h)).mean() @@ -75,6 +127,10 @@ def logistic_reg(alpha, x, y, max_iterations=70000): # In[68]: if __name__ == "__main__": + import doctest + + doctest.testmod() + iris = datasets.load_iris() x = iris.data[:, :2] y = (iris.target != 0) * 1 From e5a6a97c3277fbf849b77d1328720782128ecafd Mon Sep 17 00:00:00 2001 From: Sanjay <146640686+san-jay-14@users.noreply.github.com> Date: Thu, 26 Oct 2023 13:28:40 +0530 Subject: [PATCH 2584/2908] Added Lens formulae to the Physics repository (#10187) * Added Lens formulae to the Physics repository * Resolved the commented issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update lens_formulae.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- physics/lens_formulae.py | 131 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 physics/lens_formulae.py diff --git a/physics/lens_formulae.py b/physics/lens_formulae.py new file mode 100644 index 000000000000..162f3a8f3b29 --- /dev/null +++ b/physics/lens_formulae.py @@ -0,0 +1,131 @@ +""" +This module has functions which calculate focal length of lens, distance of +image from the lens and distance of object from the lens. +The above is calculated using the lens formula. + +In optics, the relationship between the distance of the image (v), +the distance of the object (u), and +the focal length (f) of the lens is given by the formula known as the Lens formula. +The Lens formula is applicable for convex as well as concave lenses. The formula +is given as follows: + +------------------- +| 1/f = 1/v + 1/u | +------------------- + +Where + f = focal length of the lens in meters. + v = distance of the image from the lens in meters. + u = distance of the object from the lens in meters. + +To make our calculations easy few assumptions are made while deriving the formula +which are important to keep in mind before solving this equation. +The assumptions are as follows: + 1. The object O is a point object lying somewhere on the principle axis. + 2. The lens is thin. + 3. The aperture of the lens taken must be small. + 4. The angles of incidence and angle of refraction should be small. + +Sign convention is a set of rules to set signs for image distance, object distance, +focal length, etc +for mathematical analysis of image formation. According to it: + 1. Object is always placed to the left of lens. + 2. All distances are measured from the optical centre of the mirror. + 3. Distances measured in the direction of the incident ray are positive and + the distances measured in the direction opposite + to that of the incident rays are negative. + 4. Distances measured along y-axis above the principal axis are positive and + that measured along y-axis below the principal + axis are negative. + +Note: Sign convention can be reversed and will still give the correct results. + +Reference for Sign convention: +https://www.toppr.com/ask/content/concept/sign-convention-for-lenses-210246/ + +Reference for assumptions: +https://testbook.com/physics/derivation-of-lens-maker-formula +""" + + +def focal_length_of_lens( + object_distance_from_lens: float, image_distance_from_lens: float +) -> float: + """ + Doctests: + >>> from math import isclose + >>> isclose(focal_length_of_lens(10,4), 6.666666666666667) + True + >>> from math import isclose + >>> isclose(focal_length_of_lens(2.7,5.8), -5.0516129032258075) + True + >>> focal_length_of_lens(0, 20) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter non zero values with respect + to the sign convention. + """ + + if object_distance_from_lens == 0 or image_distance_from_lens == 0: + raise ValueError( + "Invalid inputs. Enter non zero values with respect to the sign convention." + ) + focal_length = 1 / ( + (1 / image_distance_from_lens) - (1 / object_distance_from_lens) + ) + return focal_length + + +def object_distance( + focal_length_of_lens: float, image_distance_from_lens: float +) -> float: + """ + Doctests: + >>> from math import isclose + >>> isclose(object_distance(10,40), -13.333333333333332) + True + + >>> from math import isclose + >>> isclose(object_distance(6.2,1.5), 1.9787234042553192) + True + + >>> object_distance(0, 20) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter non zero values with respect + to the sign convention. + """ + + if image_distance_from_lens == 0 or focal_length_of_lens == 0: + raise ValueError( + "Invalid inputs. Enter non zero values with respect to the sign convention." + ) + + object_distance = 1 / ((1 / image_distance_from_lens) - (1 / focal_length_of_lens)) + return object_distance + + +def image_distance( + focal_length_of_lens: float, object_distance_from_lens: float +) -> float: + """ + Doctests: + >>> from math import isclose + >>> isclose(image_distance(50,40), 22.22222222222222) + True + >>> from math import isclose + >>> isclose(image_distance(5.3,7.9), 3.1719696969696973) + True + + >>> object_distance(0, 20) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + ValueError: Invalid inputs. Enter non zero values with respect + to the sign convention. + """ + if object_distance_from_lens == 0 or focal_length_of_lens == 0: + raise ValueError( + "Invalid inputs. Enter non zero values with respect to the sign convention." + ) + image_distance = 1 / ((1 / object_distance_from_lens) + (1 / focal_length_of_lens)) + return image_distance From e791a2067baf3b23c0413f32c7388e3b2a95744e Mon Sep 17 00:00:00 2001 From: Mary-0165 <146911989+Mary-0165@users.noreply.github.com> Date: Thu, 26 Oct 2023 13:40:13 +0530 Subject: [PATCH 2585/2908] Capacitor equivalence algorithm (#9814) * capacitor equivalence algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * Update capacitor_equivalence.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- electronics/capacitor_equivalence.py | 53 ++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 electronics/capacitor_equivalence.py diff --git a/electronics/capacitor_equivalence.py b/electronics/capacitor_equivalence.py new file mode 100644 index 000000000000..274b18afb3ef --- /dev/null +++ b/electronics/capacitor_equivalence.py @@ -0,0 +1,53 @@ +# https://farside.ph.utexas.edu/teaching/316/lectures/node46.html + +from __future__ import annotations + + +def capacitor_parallel(capacitors: list[float]) -> float: + """ + Ceq = C1 + C2 + ... + Cn + Calculate the equivalent resistance for any number of capacitors in parallel. + >>> capacitor_parallel([5.71389, 12, 3]) + 20.71389 + >>> capacitor_parallel([5.71389, 12, -3]) + Traceback (most recent call last): + ... + ValueError: Capacitor at index 2 has a negative value! + """ + sum_c = 0.0 + for index, capacitor in enumerate(capacitors): + if capacitor < 0: + msg = f"Capacitor at index {index} has a negative value!" + raise ValueError(msg) + sum_c += capacitor + return sum_c + + +def capacitor_series(capacitors: list[float]) -> float: + """ + Ceq = 1/ (1/C1 + 1/C2 + ... + 1/Cn) + >>> capacitor_series([5.71389, 12, 3]) + 1.6901062252507735 + >>> capacitor_series([5.71389, 12, -3]) + Traceback (most recent call last): + ... + ValueError: Capacitor at index 2 has a negative or zero value! + >>> capacitor_series([5.71389, 12, 0.000]) + Traceback (most recent call last): + ... + ValueError: Capacitor at index 2 has a negative or zero value! + """ + + first_sum = 0.0 + for index, capacitor in enumerate(capacitors): + if capacitor <= 0: + msg = f"Capacitor at index {index} has a negative or zero value!" + raise ValueError(msg) + first_sum += 1 / capacitor + return 1 / first_sum + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ade2837e410ec286819f0f4fd977bb411a95b379 Mon Sep 17 00:00:00 2001 From: Saurabh Mahapatra <98408932+its-100rabh@users.noreply.github.com> Date: Thu, 26 Oct 2023 13:55:08 +0530 Subject: [PATCH 2586/2908] Update capitalize.py (#10573) * Update capitalize.py * Update strings/capitalize.py --------- Co-authored-by: Tianyi Zheng --- strings/capitalize.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/strings/capitalize.py b/strings/capitalize.py index e7e97c2beb53..c0b45e0d9614 100644 --- a/strings/capitalize.py +++ b/strings/capitalize.py @@ -3,7 +3,8 @@ def capitalize(sentence: str) -> str: """ - This function will capitalize the first letter of a sentence or a word + Capitalizes the first letter of a sentence or word. + >>> capitalize("hello world") 'Hello world' >>> capitalize("123 hello world") @@ -17,6 +18,10 @@ def capitalize(sentence: str) -> str: """ if not sentence: return "" + + # Create a dictionary that maps lowercase letters to uppercase letters + # Capitalize the first character if it's a lowercase letter + # Concatenate the capitalized character with the rest of the string lower_to_upper = dict(zip(ascii_lowercase, ascii_uppercase)) return lower_to_upper.get(sentence[0], sentence[0]) + sentence[1:] From 6497917352c73371730e50f063acd61cf4268076 Mon Sep 17 00:00:00 2001 From: Neha <129765919+neha3423@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:24:30 +0530 Subject: [PATCH 2587/2908] Added Kth largest element algorithm (#10687) * neha3423 * neha3423 * neha3423 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * neha3423 * neha3423 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * neha323 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * neha3423 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * neha3423 * neha3423 * neha3423 * neha3423 * Added test case for tuple * Update kth_largest_element.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/arrays/kth_largest_element.py | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 data_structures/arrays/kth_largest_element.py diff --git a/data_structures/arrays/kth_largest_element.py b/data_structures/arrays/kth_largest_element.py new file mode 100644 index 000000000000..f25cc68e9b72 --- /dev/null +++ b/data_structures/arrays/kth_largest_element.py @@ -0,0 +1,117 @@ +""" +Given an array of integers and an integer k, find the kth largest element in the array. + +https://stackoverflow.com/questions/251781 +""" + + +def partition(arr: list[int], low: int, high: int) -> int: + """ + Partitions list based on the pivot element. + + This function rearranges the elements in the input list 'elements' such that + all elements greater than or equal to the chosen pivot are on the right side + of the pivot, and all elements smaller than the pivot are on the left side. + + Args: + arr: The list to be partitioned + low: The lower index of the list + high: The higher index of the list + + Returns: + int: The index of pivot element after partitioning + + Examples: + >>> partition([3, 1, 4, 5, 9, 2, 6, 5, 3, 5], 0, 9) + 4 + >>> partition([7, 1, 4, 5, 9, 2, 6, 5, 8], 0, 8) + 1 + >>> partition(['apple', 'cherry', 'date', 'banana'], 0, 3) + 2 + >>> partition([3.1, 1.2, 5.6, 4.7], 0, 3) + 1 + """ + pivot = arr[high] + i = low - 1 + for j in range(low, high): + if arr[j] >= pivot: + i += 1 + arr[i], arr[j] = arr[j], arr[i] + arr[i + 1], arr[high] = arr[high], arr[i + 1] + return i + 1 + + +def kth_largest_element(arr: list[int], position: int) -> int: + """ + Finds the kth largest element in a list. + Should deliver similar results to: + ```python + def kth_largest_element(arr, position): + return sorted(arr)[-position] + ``` + + Args: + nums: The list of numbers. + k: The position of the desired kth largest element. + + Returns: + int: The kth largest element. + + Examples: + >>> kth_largest_element([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5], 3) + 5 + >>> kth_largest_element([2, 5, 6, 1, 9, 3, 8, 4, 7, 3, 5], 1) + 9 + >>> kth_largest_element([2, 5, 6, 1, 9, 3, 8, 4, 7, 3, 5], -2) + Traceback (most recent call last): + ... + ValueError: Invalid value of 'position' + >>> kth_largest_element([9, 1, 3, 6, 7, 9, 8, 4, 2, 4, 9], 110) + Traceback (most recent call last): + ... + ValueError: Invalid value of 'position' + >>> kth_largest_element([1, 2, 4, 3, 5, 9, 7, 6, 5, 9, 3], 0) + Traceback (most recent call last): + ... + ValueError: Invalid value of 'position' + >>> kth_largest_element(['apple', 'cherry', 'date', 'banana'], 2) + 'cherry' + >>> kth_largest_element([3.1, 1.2, 5.6, 4.7,7.9,5,0], 2) + 5.6 + >>> kth_largest_element([-2, -5, -4, -1], 1) + -1 + >>> kth_largest_element([], 1) + -1 + >>> kth_largest_element([3.1, 1.2, 5.6, 4.7, 7.9, 5, 0], 1.5) + Traceback (most recent call last): + ... + ValueError: The position should be an integer + >>> kth_largest_element((4, 6, 1, 2), 4) + Traceback (most recent call last): + ... + TypeError: 'tuple' object does not support item assignment + """ + if not arr: + return -1 + if not isinstance(position, int): + raise ValueError("The position should be an integer") + if not 1 <= position <= len(arr): + raise ValueError("Invalid value of 'position'") + low, high = 0, len(arr) - 1 + while low <= high: + if low > len(arr) - 1 or high < 0: + return -1 + pivot_index = partition(arr, low, high) + if pivot_index == position - 1: + return arr[pivot_index] + elif pivot_index > position - 1: + high = pivot_index - 1 + else: + low = pivot_index + 1 + return -1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 42c49ee1174506dd04dc2dff422328cdb7dc7201 Mon Sep 17 00:00:00 2001 From: Habip Akyol <127725897+habipakyol@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:24:17 +0300 Subject: [PATCH 2588/2908] Fix typo in haralick_descriptors.py (#10988) --- computer_vision/haralick_descriptors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 413cea304f6c..007421e34263 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -253,13 +253,13 @@ def matrix_concurrency(image: np.ndarray, coordinate: tuple[int, int]) -> np.nda def haralick_descriptors(matrix: np.ndarray) -> list[float]: - """Calculates all 8 Haralick descriptors based on co-occurence input matrix. + """Calculates all 8 Haralick descriptors based on co-occurrence input matrix. All descriptors are as follows: Maximum probability, Inverse Difference, Homogeneity, Entropy, Energy, Dissimilarity, Contrast and Correlation Args: - matrix: Co-occurence matrix to use as base for calculating descriptors. + matrix: Co-occurrence matrix to use as base for calculating descriptors. Returns: Reverse ordered list of resulting descriptors From 29b8ccdc2f685e815f12fd6e9e8b9faee21e338d Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Thu, 26 Oct 2023 17:42:28 +0530 Subject: [PATCH 2589/2908] Added doctest to hash_table.py (#10984) --- data_structures/hashing/hash_table.py | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 7ca2f7c401cf..5bf431328da4 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -21,6 +21,29 @@ def __init__( self._keys: dict = {} def keys(self): + """ + The keys function returns a dictionary containing the key value pairs. + key being the index number in hash table and value being the data value. + + Examples: + 1. creating HashTable with size 10 and inserting 3 elements + >>> ht = HashTable(10) + >>> ht.insert_data(10) + >>> ht.insert_data(20) + >>> ht.insert_data(30) + >>> ht.keys() + {0: 10, 1: 20, 2: 30} + + 2. creating HashTable with size 5 and inserting 5 elements + >>> ht = HashTable(5) + >>> ht.insert_data(5) + >>> ht.insert_data(4) + >>> ht.insert_data(3) + >>> ht.insert_data(2) + >>> ht.insert_data(1) + >>> ht.keys() + {0: 5, 4: 4, 3: 3, 2: 2, 1: 1} + """ return self._keys def balanced_factor(self): @@ -37,6 +60,43 @@ def _step_by_step(self, step_ord): print(self.values) def bulk_insert(self, values): + """ + bulk_insert is used for entering more than one element at a time + in the HashTable. + + Examples: + 1. + >>> ht = HashTable(5) + >>> ht.bulk_insert((10,20,30)) + step 1 + [0, 1, 2, 3, 4] + [10, None, None, None, None] + step 2 + [0, 1, 2, 3, 4] + [10, 20, None, None, None] + step 3 + [0, 1, 2, 3, 4] + [10, 20, 30, None, None] + + 2. + >>> ht = HashTable(5) + >>> ht.bulk_insert([5,4,3,2,1]) + step 1 + [0, 1, 2, 3, 4] + [5, None, None, None, None] + step 2 + [0, 1, 2, 3, 4] + [5, None, None, None, 4] + step 3 + [0, 1, 2, 3, 4] + [5, None, None, 3, 4] + step 4 + [0, 1, 2, 3, 4] + [5, None, 2, 3, 4] + step 5 + [0, 1, 2, 3, 4] + [5, 1, 2, 3, 4] + """ i = 1 self.__aux_list = values for value in values: @@ -69,6 +129,21 @@ def rehashing(self): self.insert_data(value) def insert_data(self, data): + """ + insert_data is used for inserting a single element at a time in the HashTable. + + Examples: + + >>> ht = HashTable(3) + >>> ht.insert_data(5) + >>> ht.keys() + {2: 5} + >>> ht = HashTable(5) + >>> ht.insert_data(30) + >>> ht.insert_data(50) + >>> ht.keys() + {0: 30, 1: 50} + """ key = self.hash_function(data) if self.values[key] is None: @@ -84,3 +159,9 @@ def insert_data(self, data): else: self.rehashing() self.insert_data(data) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 69f7f3208e0297cea9ccd9d02b9fb690f2ee3b93 Mon Sep 17 00:00:00 2001 From: Akash_Jambulkar <97665573+Akash-Jambulkar@users.noreply.github.com> Date: Thu, 26 Oct 2023 17:57:31 +0530 Subject: [PATCH 2590/2908] Update cocktail_shaker_sort.py (#10987) * Update cocktail_shaker_sort.py Added a docstring with clear explanations of the function and its parameters. Changed variable names i, start, and end for better readability. Improved comments to describe the purpose of each section of the algorithm. Adjusted the loop ranges to make the code more concise and readable. Removed redundant comments and variable assignments. Provided a clear message when printing the sorted list. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update cocktail_shaker_sort.py * typing: ignore[operator] * Update cocktail_shaker_sort.py * Update cocktail_shaker_sort.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- sorts/cocktail_shaker_sort.py | 52 +++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/sorts/cocktail_shaker_sort.py b/sorts/cocktail_shaker_sort.py index b738ff31d768..de126426d986 100644 --- a/sorts/cocktail_shaker_sort.py +++ b/sorts/cocktail_shaker_sort.py @@ -1,40 +1,62 @@ -""" https://en.wikipedia.org/wiki/Cocktail_shaker_sort """ +""" +An implementation of the cocktail shaker sort algorithm in pure Python. +https://en.wikipedia.org/wiki/Cocktail_shaker_sort +""" -def cocktail_shaker_sort(unsorted: list) -> list: + +def cocktail_shaker_sort(arr: list[int]) -> list[int]: """ - Pure implementation of the cocktail shaker sort algorithm in Python. + Sorts a list using the Cocktail Shaker Sort algorithm. + + :param arr: List of elements to be sorted. + :return: Sorted list. + >>> 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] + >>> cocktail_shaker_sort(["elderberry", "banana", "date", "apple", "cherry"]) + ['apple', 'banana', 'cherry', 'date', 'elderberry'] + >>> cocktail_shaker_sort((-4, -5, -24, -7, -11)) + Traceback (most recent call last): + ... + TypeError: 'tuple' object does not support item assignment """ - for i in range(len(unsorted) - 1, 0, -1): + start, end = 0, len(arr) - 1 + + while start < end: 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] + # Pass from left to right + for i in range(start, end): + if arr[i] > arr[i + 1]: + arr[i], arr[i + 1] = arr[i + 1], arr[i] swapped = True - for j in range(i): - if unsorted[j] > unsorted[j + 1]: - unsorted[j], unsorted[j + 1] = unsorted[j + 1], unsorted[j] + if not swapped: + break + + end -= 1 # Decrease the end pointer after each pass + + # Pass from right to left + for i in range(end, start, -1): + if arr[i] < arr[i - 1]: + arr[i], arr[i - 1] = arr[i - 1], arr[i] swapped = True if not swapped: break - return unsorted + + start += 1 # Increase the start pointer after each pass + + return arr if __name__ == "__main__": From 579250363db1975440c75f4f6d486b88ff568cdb Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Thu, 26 Oct 2023 08:36:53 -0400 Subject: [PATCH 2591/2908] Speed up `dijkstra_bankers_algorithm.py` (#10861) * updating DIRECTORY.md * Rename dijkstra_bankers_algorithm.py * Remove sleep() call * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- other/{dijkstra_bankers_algorithm.py => bankers_algorithm.py} | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) rename other/{dijkstra_bankers_algorithm.py => bankers_algorithm.py} (99%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 5f8eabb6df88..d108acf8dcfb 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -774,8 +774,8 @@ ## Other * [Activity Selection](other/activity_selection.py) * [Alternative List Arrange](other/alternative_list_arrange.py) + * [Bankers Algorithm](other/bankers_algorithm.py) * [Davis Putnam Logemann Loveland](other/davis_putnam_logemann_loveland.py) - * [Dijkstra Bankers Algorithm](other/dijkstra_bankers_algorithm.py) * [Doomsday](other/doomsday.py) * [Fischer Yates Shuffle](other/fischer_yates_shuffle.py) * [Gauss Easter](other/gauss_easter.py) diff --git a/other/dijkstra_bankers_algorithm.py b/other/bankers_algorithm.py similarity index 99% rename from other/dijkstra_bankers_algorithm.py rename to other/bankers_algorithm.py index be7bceba125d..858eb0b2c524 100644 --- a/other/dijkstra_bankers_algorithm.py +++ b/other/bankers_algorithm.py @@ -17,8 +17,6 @@ from __future__ import annotations -import time - import numpy as np test_claim_vector = [8, 5, 9, 7] @@ -216,7 +214,6 @@ def __pretty_data(self): "Initial Available Resources: " + " ".join(str(x) for x in self.__available_resources()) ) - time.sleep(1) if __name__ == "__main__": From 8adbf47c75e6881f8778fc4e9490628c71cc9fa1 Mon Sep 17 00:00:00 2001 From: Kishan Kumar Rai Date: Thu, 26 Oct 2023 18:21:28 +0530 Subject: [PATCH 2592/2908] Fix Typo & Grammatical Errors (#10980) --- CONTRIBUTING.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf3420185c1a..096582e45afa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,20 +2,20 @@ ## 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/community). +Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Before submitting your pull requests, please ensure that you __read the whole guidelines__. If you have any doubts about the contributing guide, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Python/issues/new) or ask the community on [Gitter](https://gitter.im/TheAlgorithms/community). ## Contributing ### Contributor -We are very happy that you are considering implementing algorithms and data structures 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: +We are delighted that you are considering implementing algorithms and data structures for others! This repository is referenced and used by learners from all over the globe. By being one of our contributors, you agree and confirm that: -- You 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.md) once your pull request is merged -- Your submitted work fulfils or mostly fulfils our styles and standards +- Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged. +- Your submitted work fulfills or mostly fulfills 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 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. +__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. @@ -23,7 +23,7 @@ __Improving comments__ and __writing proper tests__ are also highly welcome. 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 GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions 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 GitHub Actions 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. +Your contribution will be tested by our [automated testing on GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions tests start to run at the bottom of your submission page. If those tests fail, then click on the ___details___ button to read through the GitHub Actions 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. #### Issues @@ -58,7 +58,7 @@ Algorithms should: * 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. +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: @@ -77,7 +77,7 @@ pre-commit run --all-files --show-diff-on-failure We want your work to be readable by others; therefore, we encourage you to note the following: -- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. +- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. - Please focus hard on the 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. @@ -145,7 +145,7 @@ We want your work to be readable by others; therefore, we encourage you to note python3 -m doctest -v my_submission.py ``` - The use of the Python builtin `input()` function is __not__ encouraged: + The use of the Python built-in `input()` function is __not__ encouraged: ```python input('Enter your input:') From 34b25c0c769b417e82bc32cd4d3a801637ee57ab Mon Sep 17 00:00:00 2001 From: Tiela Rose Black-Law <26930264+tielarose@users.noreply.github.com> Date: Thu, 26 Oct 2023 05:52:47 -0700 Subject: [PATCH 2593/2908] Add doctest for maths/primelib (#10978) --- maths/primelib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maths/primelib.py b/maths/primelib.py index e2d432e1846a..a26b0eaeb328 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -454,6 +454,8 @@ def kg_v(number1, number2): 40 >>> kg_v(824,67) 55208 + >>> kg_v(1, 10) + 10 >>> kg_v(0) Traceback (most recent call last): ... From a8dfd403f6df2275272190a55edb6a739880f6a9 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 26 Oct 2023 07:33:42 -0700 Subject: [PATCH 2594/2908] Add new algorithm index_2d_array_in_1d (#10973) * Add new algorithm index_2d_array_in_1d * Add doctest for iter function * The power of dataclasses * Update index_2d_array_in_1d.py --------- Co-authored-by: Christian Clauss --- .../arrays/index_2d_array_in_1d.py | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 data_structures/arrays/index_2d_array_in_1d.py diff --git a/data_structures/arrays/index_2d_array_in_1d.py b/data_structures/arrays/index_2d_array_in_1d.py new file mode 100644 index 000000000000..27a9fa5f9121 --- /dev/null +++ b/data_structures/arrays/index_2d_array_in_1d.py @@ -0,0 +1,105 @@ +""" +Retrieves the value of an 0-indexed 1D index from a 2D array. +There are two ways to retrieve value(s): + +1. Index2DArrayIterator(matrix) -> Iterator[int] +This iterator allows you to iterate through a 2D array by passing in the matrix and +calling next(your_iterator). You can also use the iterator in a loop. +Examples: +list(Index2DArrayIterator(matrix)) +set(Index2DArrayIterator(matrix)) +tuple(Index2DArrayIterator(matrix)) +sum(Index2DArrayIterator(matrix)) +-5 in Index2DArrayIterator(matrix) + +2. index_2d_array_in_1d(array: list[int], index: int) -> int +This function allows you to provide a 2D array and a 0-indexed 1D integer index, +and retrieves the integer value at that index. + +Python doctests can be run using this command: +python3 -m doctest -v index_2d_array_in_1d.py +""" + +from collections.abc import Iterator +from dataclasses import dataclass + + +@dataclass +class Index2DArrayIterator: + matrix: list[list[int]] + + def __iter__(self) -> Iterator[int]: + """ + >>> tuple(Index2DArrayIterator([[5], [-523], [-1], [34], [0]])) + (5, -523, -1, 34, 0) + >>> tuple(Index2DArrayIterator([[5, -523, -1], [34, 0]])) + (5, -523, -1, 34, 0) + >>> tuple(Index2DArrayIterator([[5, -523, -1, 34, 0]])) + (5, -523, -1, 34, 0) + >>> t = Index2DArrayIterator([[5, 2, 25], [23, 14, 5], [324, -1, 0]]) + >>> tuple(t) + (5, 2, 25, 23, 14, 5, 324, -1, 0) + >>> list(t) + [5, 2, 25, 23, 14, 5, 324, -1, 0] + >>> sorted(t) + [-1, 0, 2, 5, 5, 14, 23, 25, 324] + >>> tuple(t)[3] + 23 + >>> sum(t) + 397 + >>> -1 in t + True + >>> t = iter(Index2DArrayIterator([[5], [-523], [-1], [34], [0]])) + >>> next(t) + 5 + >>> next(t) + -523 + """ + for row in self.matrix: + yield from row + + +def index_2d_array_in_1d(array: list[list[int]], index: int) -> int: + """ + Retrieves the value of the one-dimensional index from a two-dimensional array. + + Args: + array: A 2D array of integers where all rows are the same size and all + columns are the same size. + index: A 1D index. + + Returns: + int: The 0-indexed value of the 1D index in the array. + + Examples: + >>> index_2d_array_in_1d([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], 5) + 5 + >>> index_2d_array_in_1d([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], -1) + Traceback (most recent call last): + ... + ValueError: index out of range + >>> index_2d_array_in_1d([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], 12) + Traceback (most recent call last): + ... + ValueError: index out of range + >>> index_2d_array_in_1d([[]], 0) + Traceback (most recent call last): + ... + ValueError: no items in array + """ + rows = len(array) + cols = len(array[0]) + + if rows == 0 or cols == 0: + raise ValueError("no items in array") + + if index < 0 or index >= rows * cols: + raise ValueError("index out of range") + + return array[index // cols][index % cols] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From fe4aad0ec94a2d2f28470dd8eaad3ff1bf74c5c8 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:51:45 +0530 Subject: [PATCH 2595/2908] Added doctest & docstring to quadratic_probing.py (#10996) * Added doctest & docstring to quadratic_probing.py * Update quadratic_probing.py * Update quadratic_probing.py --- data_structures/hashing/quadratic_probing.py | 55 ++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/data_structures/hashing/quadratic_probing.py b/data_structures/hashing/quadratic_probing.py index 0930340a347f..2f3401ec8918 100644 --- a/data_structures/hashing/quadratic_probing.py +++ b/data_structures/hashing/quadratic_probing.py @@ -12,6 +12,55 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _collision_resolution(self, key, data=None): + """ + Quadratic probing is an open addressing scheme used for resolving + collisions in hash table. + + It works by taking the original hash index and adding successive + values of an arbitrary quadratic polynomial until open slot is found. + + Hash + 1², Hash + 2², Hash + 3² .... Hash + n² + + reference: + - https://en.wikipedia.org/wiki/Quadratic_probing + e.g: + 1. Create hash table with size 7 + >>> qp = QuadraticProbing(7) + >>> qp.insert_data(90) + >>> qp.insert_data(340) + >>> qp.insert_data(24) + >>> qp.insert_data(45) + >>> qp.insert_data(99) + >>> qp.insert_data(73) + >>> qp.insert_data(7) + >>> qp.keys() + {11: 45, 14: 99, 7: 24, 0: 340, 5: 73, 6: 90, 8: 7} + + 2. Create hash table with size 8 + >>> qp = QuadraticProbing(8) + >>> qp.insert_data(0) + >>> qp.insert_data(999) + >>> qp.insert_data(111) + >>> qp.keys() + {0: 0, 7: 999, 3: 111} + + 3. Try to add three data elements when the size is two + >>> qp = QuadraticProbing(2) + >>> qp.insert_data(0) + >>> qp.insert_data(999) + >>> qp.insert_data(111) + >>> qp.keys() + {0: 0, 4: 999, 1: 111} + + 4. Try to add three data elements when the size is one + >>> qp = QuadraticProbing(1) + >>> qp.insert_data(0) + >>> qp.insert_data(999) + >>> qp.insert_data(111) + >>> qp.keys() + {4: 999, 1: 111} + """ + i = 1 new_key = self.hash_function(key + i * i) @@ -27,3 +76,9 @@ def _collision_resolution(self, key, data=None): break return new_key + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5987f861926c7560cd46c1e33c3cc2c0506c0ee1 Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:17:24 +0530 Subject: [PATCH 2596/2908] Add automatic differentiation algorithm (#10977) * Added automatic differentiation algorithm * file name changed * Resolved pre commit errors * updated dependency * added noqa for ignoring check * adding typing_extension for adding Self type in __new__ * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * sorted requirement.text dependency * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * resolved ruff --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- machine_learning/automatic_differentiation.py | 327 ++++++++++++++++++ requirements.txt | 3 +- 2 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 machine_learning/automatic_differentiation.py diff --git a/machine_learning/automatic_differentiation.py b/machine_learning/automatic_differentiation.py new file mode 100644 index 000000000000..cd2e5cdaa782 --- /dev/null +++ b/machine_learning/automatic_differentiation.py @@ -0,0 +1,327 @@ +""" +Demonstration of the Automatic Differentiation (Reverse mode). + +Reference: https://en.wikipedia.org/wiki/Automatic_differentiation + +Author: Poojan Smart +Email: smrtpoojan@gmail.com +""" +from __future__ import annotations + +from collections import defaultdict +from enum import Enum +from types import TracebackType +from typing import Any + +import numpy as np +from typing_extensions import Self # noqa: UP035 + + +class OpType(Enum): + """ + Class represents list of supported operations on Variable for gradient calculation. + """ + + ADD = 0 + SUB = 1 + MUL = 2 + DIV = 3 + MATMUL = 4 + POWER = 5 + NOOP = 6 + + +class Variable: + """ + Class represents n-dimensional object which is used to wrap numpy array on which + operations will be performed and the gradient will be calculated. + + Examples: + >>> Variable(5.0) + Variable(5.0) + >>> Variable([5.0, 2.9]) + Variable([5. 2.9]) + >>> Variable([5.0, 2.9]) + Variable([1.0, 5.5]) + Variable([6. 8.4]) + >>> Variable([[8.0, 10.0]]) + Variable([[ 8. 10.]]) + """ + + def __init__(self, value: Any) -> None: + self.value = np.array(value) + + # pointers to the operations to which the Variable is input + self.param_to: list[Operation] = [] + # pointer to the operation of which the Variable is output of + self.result_of: Operation = Operation(OpType.NOOP) + + def __repr__(self) -> str: + return f"Variable({self.value})" + + def to_ndarray(self) -> np.ndarray: + return self.value + + def __add__(self, other: Variable) -> Variable: + result = Variable(self.value + other.value) + + with GradientTracker() as tracker: + # if tracker is enabled, computation graph will be updated + if tracker.enabled: + tracker.append(OpType.ADD, params=[self, other], output=result) + return result + + def __sub__(self, other: Variable) -> Variable: + result = Variable(self.value - other.value) + + with GradientTracker() as tracker: + # if tracker is enabled, computation graph will be updated + if tracker.enabled: + tracker.append(OpType.SUB, params=[self, other], output=result) + return result + + def __mul__(self, other: Variable) -> Variable: + result = Variable(self.value * other.value) + + with GradientTracker() as tracker: + # if tracker is enabled, computation graph will be updated + if tracker.enabled: + tracker.append(OpType.MUL, params=[self, other], output=result) + return result + + def __truediv__(self, other: Variable) -> Variable: + result = Variable(self.value / other.value) + + with GradientTracker() as tracker: + # if tracker is enabled, computation graph will be updated + if tracker.enabled: + tracker.append(OpType.DIV, params=[self, other], output=result) + return result + + def __matmul__(self, other: Variable) -> Variable: + result = Variable(self.value @ other.value) + + with GradientTracker() as tracker: + # if tracker is enabled, computation graph will be updated + if tracker.enabled: + tracker.append(OpType.MATMUL, params=[self, other], output=result) + return result + + def __pow__(self, power: int) -> Variable: + result = Variable(self.value**power) + + with GradientTracker() as tracker: + # if tracker is enabled, computation graph will be updated + if tracker.enabled: + tracker.append( + OpType.POWER, + params=[self], + output=result, + other_params={"power": power}, + ) + return result + + def add_param_to(self, param_to: Operation) -> None: + self.param_to.append(param_to) + + def add_result_of(self, result_of: Operation) -> None: + self.result_of = result_of + + +class Operation: + """ + Class represents operation between single or two Variable objects. + Operation objects contains type of operation, pointers to input Variable + objects and pointer to resulting Variable from the operation. + """ + + def __init__( + self, + op_type: OpType, + other_params: dict | None = None, + ) -> None: + self.op_type = op_type + self.other_params = {} if other_params is None else other_params + + def add_params(self, params: list[Variable]) -> None: + self.params = params + + def add_output(self, output: Variable) -> None: + self.output = output + + def __eq__(self, value) -> bool: + return self.op_type == value if isinstance(value, OpType) else False + + +class GradientTracker: + """ + Class contains methods to compute partial derivatives of Variable + based on the computation graph. + + Examples: + + >>> with GradientTracker() as tracker: + ... a = Variable([2.0, 5.0]) + ... b = Variable([1.0, 2.0]) + ... m = Variable([1.0, 2.0]) + ... c = a + b + ... d = a * b + ... e = c / d + >>> tracker.gradient(e, a) + array([-0.25, -0.04]) + >>> tracker.gradient(e, b) + array([-1. , -0.25]) + >>> tracker.gradient(e, m) is None + True + + >>> with GradientTracker() as tracker: + ... a = Variable([[2.0, 5.0]]) + ... b = Variable([[1.0], [2.0]]) + ... c = a @ b + >>> tracker.gradient(c, a) + array([[1., 2.]]) + >>> tracker.gradient(c, b) + array([[2.], + [5.]]) + + >>> with GradientTracker() as tracker: + ... a = Variable([[2.0, 5.0]]) + ... b = a ** 3 + >>> tracker.gradient(b, a) + array([[12., 75.]]) + """ + + instance = None + + def __new__(cls) -> Self: + """ + Executes at the creation of class object and returns if + object is already created. This class follows singleton + design pattern. + """ + if cls.instance is None: + cls.instance = super().__new__(cls) + return cls.instance + + def __init__(self) -> None: + self.enabled = False + + def __enter__(self) -> Self: + self.enabled = True + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: TracebackType | None, + ) -> None: + self.enabled = False + + def append( + self, + op_type: OpType, + params: list[Variable], + output: Variable, + other_params: dict | None = None, + ) -> None: + """ + Adds Operation object to the related Variable objects for + creating computational graph for calculating gradients. + + Args: + op_type: Operation type + params: Input parameters to the operation + output: Output variable of the operation + """ + operation = Operation(op_type, other_params=other_params) + param_nodes = [] + for param in params: + param.add_param_to(operation) + param_nodes.append(param) + output.add_result_of(operation) + + operation.add_params(param_nodes) + operation.add_output(output) + + def gradient(self, target: Variable, source: Variable) -> np.ndarray | None: + """ + Reverse accumulation of partial derivatives to calculate gradients + of target variable with respect to source variable. + + Args: + target: target variable for which gradients are calculated. + source: source variable with respect to which the gradients are + calculated. + + Returns: + Gradient of the source variable with respect to the target variable + """ + + # partial derivatives with respect to target + partial_deriv = defaultdict(lambda: 0) + partial_deriv[target] = np.ones_like(target.to_ndarray()) + + # iterating through each operations in the computation graph + operation_queue = [target.result_of] + while len(operation_queue) > 0: + operation = operation_queue.pop() + for param in operation.params: + # as per the chain rule, multiplying partial derivatives + # of variables with respect to the target + dparam_doutput = self.derivative(param, operation) + dparam_dtarget = dparam_doutput * partial_deriv[operation.output] + partial_deriv[param] += dparam_dtarget + + if param.result_of and param.result_of != OpType.NOOP: + operation_queue.append(param.result_of) + + return partial_deriv.get(source) + + def derivative(self, param: Variable, operation: Operation) -> np.ndarray: + """ + Compute the derivative of given operation/function + + Args: + param: variable to be differentiated + operation: function performed on the input variable + + Returns: + Derivative of input variable with respect to the output of + the operation + """ + params = operation.params + + if operation == OpType.ADD: + return np.ones_like(params[0].to_ndarray(), dtype=np.float64) + if operation == OpType.SUB: + if params[0] == param: + return np.ones_like(params[0].to_ndarray(), dtype=np.float64) + return -np.ones_like(params[1].to_ndarray(), dtype=np.float64) + if operation == OpType.MUL: + return ( + params[1].to_ndarray().T + if params[0] == param + else params[0].to_ndarray().T + ) + if operation == OpType.DIV: + if params[0] == param: + return 1 / params[1].to_ndarray() + return -params[0].to_ndarray() / (params[1].to_ndarray() ** 2) + if operation == OpType.MATMUL: + return ( + params[1].to_ndarray().T + if params[0] == param + else params[0].to_ndarray().T + ) + if operation == OpType.POWER: + power = operation.other_params["power"] + return power * (params[0].to_ndarray() ** (power - 1)) + + err_msg = f"invalid operation type: {operation.op_type}" + raise ValueError(err_msg) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/requirements.txt b/requirements.txt index 05d9f1e8c545..8937f6bb0dae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,5 +19,6 @@ statsmodels sympy tensorflow ; python_version < '3.12' tweepy -xgboost # yulewalker # uncomment once audio_filters/equal_loudness_filter.py is fixed +typing_extensions +xgboost From 34eb9c529a74c3f3d1b878a1c7ca2529686b41f8 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:06:43 +0530 Subject: [PATCH 2597/2908] Added doctest to hash_table.py (#11023) * Added doctest to hash_table.py * Update hash_table.py * Update hash_table.py * Update hash_table.py * Update hash_table.py * Apply suggestions from code review * Update hash_table.py --------- Co-authored-by: Christian Clauss --- data_structures/hashing/hash_table.py | 113 ++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 5bf431328da4..7fe57068f6a3 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -52,6 +52,30 @@ def balanced_factor(self): ) def hash_function(self, key): + """ + Generates hash for the given key value + + Examples: + + Creating HashTable with size 5 + >>> ht = HashTable(5) + >>> ht.hash_function(10) + 0 + >>> ht.hash_function(20) + 0 + >>> ht.hash_function(4) + 4 + >>> ht.hash_function(18) + 3 + >>> ht.hash_function(-18) + 2 + >>> ht.hash_function(18.5) + 3.5 + >>> ht.hash_function(0) + 0 + >>> ht.hash_function(-0) + 0 + """ return key % self.size_table def _step_by_step(self, step_ord): @@ -105,10 +129,99 @@ def bulk_insert(self, values): i += 1 def _set_value(self, key, data): + """ + _set_value functions allows to update value at a particular hash + + Examples: + 1. _set_value in HashTable of size 5 + >>> ht = HashTable(5) + >>> ht.insert_data(10) + >>> ht.insert_data(20) + >>> ht.insert_data(30) + >>> ht._set_value(0,15) + >>> ht.keys() + {0: 15, 1: 20, 2: 30} + + 2. _set_value in HashTable of size 2 + >>> ht = HashTable(2) + >>> ht.insert_data(17) + >>> ht.insert_data(18) + >>> ht.insert_data(99) + >>> ht._set_value(3,15) + >>> ht.keys() + {3: 15, 2: 17, 4: 99} + + 3. _set_value in HashTable when hash is not present + >>> ht = HashTable(2) + >>> ht.insert_data(17) + >>> ht.insert_data(18) + >>> ht.insert_data(99) + >>> ht._set_value(0,15) + >>> ht.keys() + {3: 18, 2: 17, 4: 99, 0: 15} + + 4. _set_value in HashTable when multiple hash are not present + >>> ht = HashTable(2) + >>> ht.insert_data(17) + >>> ht.insert_data(18) + >>> ht.insert_data(99) + >>> ht._set_value(0,15) + >>> ht._set_value(1,20) + >>> ht.keys() + {3: 18, 2: 17, 4: 99, 0: 15, 1: 20} + """ self.values[key] = data self._keys[key] = data def _collision_resolution(self, key, data=None): + """ + This method is a type of open addressing which is used for handling collision. + + In this implementation the concept of linear probing has been used. + + The hash table is searched sequentially from the original location of the + hash, if the new hash/location we get is already occupied we check for the next + hash/location. + + references: + - https://en.wikipedia.org/wiki/Linear_probing + + Examples: + 1. The collision will be with keys 18 & 99, so new hash will be created for 99 + >>> ht = HashTable(3) + >>> ht.insert_data(17) + >>> ht.insert_data(18) + >>> ht.insert_data(99) + >>> ht.keys() + {2: 17, 0: 18, 1: 99} + + 2. The collision will be with keys 17 & 101, so new hash + will be created for 101 + >>> ht = HashTable(4) + >>> ht.insert_data(17) + >>> ht.insert_data(18) + >>> ht.insert_data(99) + >>> ht.insert_data(101) + >>> ht.keys() + {1: 17, 2: 18, 3: 99, 0: 101} + + 2. The collision will be with all keys, so new hash will be created for all + >>> ht = HashTable(1) + >>> ht.insert_data(17) + >>> ht.insert_data(18) + >>> ht.insert_data(99) + >>> ht.keys() + {2: 17, 3: 18, 4: 99} + + 3. Trying to insert float key in hash + >>> ht = HashTable(1) + >>> ht.insert_data(17) + >>> ht.insert_data(18) + >>> ht.insert_data(99.99) + Traceback (most recent call last): + ... + TypeError: list indices must be integers or slices, not float + """ new_key = self.hash_function(key + 1) while self.values[new_key] is not None and self.values[new_key] != key: From e4eda145833565443be2e5ed4c805fbaaa9d964e Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Fri, 27 Oct 2023 20:14:33 +0530 Subject: [PATCH 2598/2908] Add perplexity loss algorithm (#11028) --- machine_learning/loss_functions.py | 92 ++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py index ea1f390e358a..36a760326f3d 100644 --- a/machine_learning/loss_functions.py +++ b/machine_learning/loss_functions.py @@ -379,6 +379,98 @@ def mean_absolute_percentage_error( return np.mean(absolute_percentage_diff) +def perplexity_loss( + y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1e-7 +) -> float: + """ + Calculate the perplexity for the y_true and y_pred. + + Compute the Perplexity which useful in predicting language model + accuracy in Natural Language Processing (NLP.) + Perplexity is measure of how certain the model in its predictions. + + Perplexity Loss = exp(-1/N (Σ ln(p(x))) + + Reference: + https://en.wikipedia.org/wiki/Perplexity + + Args: + y_true: Actual label encoded sentences of shape (batch_size, sentence_length) + y_pred: Predicted sentences of shape (batch_size, sentence_length, vocab_size) + epsilon: Small floating point number to avoid getting inf for log(0) + + Returns: + Perplexity loss between y_true and y_pred. + + >>> y_true = np.array([[1, 4], [2, 3]]) + >>> y_pred = np.array( + ... [[[0.28, 0.19, 0.21 , 0.15, 0.15], + ... [0.24, 0.19, 0.09, 0.18, 0.27]], + ... [[0.03, 0.26, 0.21, 0.18, 0.30], + ... [0.28, 0.10, 0.33, 0.15, 0.12]]] + ... ) + >>> perplexity_loss(y_true, y_pred) + 5.0247347775367945 + >>> y_true = np.array([[1, 4], [2, 3]]) + >>> y_pred = np.array( + ... [[[0.28, 0.19, 0.21 , 0.15, 0.15], + ... [0.24, 0.19, 0.09, 0.18, 0.27], + ... [0.30, 0.10, 0.20, 0.15, 0.25]], + ... [[0.03, 0.26, 0.21, 0.18, 0.30], + ... [0.28, 0.10, 0.33, 0.15, 0.12], + ... [0.30, 0.10, 0.20, 0.15, 0.25]],] + ... ) + >>> perplexity_loss(y_true, y_pred) + Traceback (most recent call last): + ... + ValueError: Sentence length of y_true and y_pred must be equal. + >>> y_true = np.array([[1, 4], [2, 11]]) + >>> y_pred = np.array( + ... [[[0.28, 0.19, 0.21 , 0.15, 0.15], + ... [0.24, 0.19, 0.09, 0.18, 0.27]], + ... [[0.03, 0.26, 0.21, 0.18, 0.30], + ... [0.28, 0.10, 0.33, 0.15, 0.12]]] + ... ) + >>> perplexity_loss(y_true, y_pred) + Traceback (most recent call last): + ... + ValueError: Label value must not be greater than vocabulary size. + >>> y_true = np.array([[1, 4]]) + >>> y_pred = np.array( + ... [[[0.28, 0.19, 0.21 , 0.15, 0.15], + ... [0.24, 0.19, 0.09, 0.18, 0.27]], + ... [[0.03, 0.26, 0.21, 0.18, 0.30], + ... [0.28, 0.10, 0.33, 0.15, 0.12]]] + ... ) + >>> perplexity_loss(y_true, y_pred) + Traceback (most recent call last): + ... + ValueError: Batch size of y_true and y_pred must be equal. + """ + + vocab_size = y_pred.shape[2] + + if y_true.shape[0] != y_pred.shape[0]: + raise ValueError("Batch size of y_true and y_pred must be equal.") + if y_true.shape[1] != y_pred.shape[1]: + raise ValueError("Sentence length of y_true and y_pred must be equal.") + if np.max(y_true) > vocab_size: + raise ValueError("Label value must not be greater than vocabulary size.") + + # Matrix to select prediction value only for true class + filter_matrix = np.array( + [[list(np.eye(vocab_size)[word]) for word in sentence] for sentence in y_true] + ) + + # Getting the matrix containing prediction for only true class + true_class_pred = np.sum(y_pred * filter_matrix, axis=2).clip(epsilon, 1) + + # Calculating perplexity for each sentence + perp_losses = np.exp(np.negative(np.mean(np.log(true_class_pred), axis=1))) + + return np.mean(perp_losses) + + if __name__ == "__main__": import doctest From f336cca8f8b2989d612068845f147ce885676148 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:10:42 +0530 Subject: [PATCH 2599/2908] Added doctest to double_hash.py (#11020) * Added doctest to double_hash.py * Update double_hash.py --- data_structures/hashing/double_hash.py | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index be21e74cadd0..76c6c86814ec 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -35,6 +35,33 @@ def __hash_double_function(self, key, data, increment): return (increment * self.__hash_function_2(key, data)) % self.size_table def _collision_resolution(self, key, data=None): + """ + Examples: + + 1. Try to add three data elements when the size is three + >>> dh = DoubleHash(3) + >>> dh.insert_data(10) + >>> dh.insert_data(20) + >>> dh.insert_data(30) + >>> dh.keys() + {1: 10, 2: 20, 0: 30} + + 2. Try to add three data elements when the size is two + >>> dh = DoubleHash(2) + >>> dh.insert_data(10) + >>> dh.insert_data(20) + >>> dh.insert_data(30) + >>> dh.keys() + {10: 10, 9: 20, 8: 30} + + 3. Try to add three data elements when the size is four + >>> dh = DoubleHash(4) + >>> dh.insert_data(10) + >>> dh.insert_data(20) + >>> dh.insert_data(30) + >>> dh.keys() + {9: 20, 10: 10, 8: 30} + """ i = 1 new_key = self.hash_function(data) @@ -50,3 +77,9 @@ def _collision_resolution(self, key, data=None): i += 1 return new_key + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 185a35589ab14bf27f23266a25d8e1bcced646b2 Mon Sep 17 00:00:00 2001 From: Khushi Shukla Date: Fri, 27 Oct 2023 22:12:34 +0530 Subject: [PATCH 2600/2908] Create monotonic_array.py (#11025) * Create monotonic_array.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update monotonic_array.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/arrays/monotonic_array.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 data_structures/arrays/monotonic_array.py diff --git a/data_structures/arrays/monotonic_array.py b/data_structures/arrays/monotonic_array.py new file mode 100644 index 000000000000..c50a21530814 --- /dev/null +++ b/data_structures/arrays/monotonic_array.py @@ -0,0 +1,23 @@ +# https://leetcode.com/problems/monotonic-array/ +def is_monotonic(nums: list[int]) -> bool: + """ + Check if a list is monotonic. + + >>> is_monotonic([1, 2, 2, 3]) + True + >>> is_monotonic([6, 5, 4, 4]) + True + >>> is_monotonic([1, 3, 2]) + False + """ + return all(nums[i] <= nums[i + 1] for i in range(len(nums) - 1)) or all( + nums[i] >= nums[i + 1] for i in range(len(nums) - 1) + ) + + +# Test the function with your examples +if __name__ == "__main__": + # Test the function with your examples + print(is_monotonic([1, 2, 2, 3])) # Output: True + print(is_monotonic([6, 5, 4, 4])) # Output: True + print(is_monotonic([1, 3, 2])) # Output: False From b0837d39859452ed7bd6e5b7adbdf172f70228bf Mon Sep 17 00:00:00 2001 From: Adam Ross <14985050+R055A@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:10:32 +0200 Subject: [PATCH 2601/2908] Increase code coverage for dijkstra algorithm (#10695) * Increase code coverage for dijkstra algorithm * Add missing code coverage Refactor to pass mypy * Fix missing code coverage * Remove code changes, keep doctest * Remove ALL of the code changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dijkstra_algorithm.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- graphs/dijkstra_algorithm.py | 313 +++++++++++++++++++++++++++++++++-- 1 file changed, 299 insertions(+), 14 deletions(-) diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 452138fe904b..2efa2cb634ff 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -11,35 +11,127 @@ class PriorityQueue: # Based on Min Heap def __init__(self): + """ + Priority queue class constructor method. + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.cur_size + 0 + >>> priority_queue_test.array + [] + >>> priority_queue_test.pos + {} + """ self.cur_size = 0 self.array = [] self.pos = {} # To store the pos of node in array def is_empty(self): + """ + Conditional boolean method to determine if the priority queue is empty or not. + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.is_empty() + True + >>> priority_queue_test.insert((2, 'A')) + >>> priority_queue_test.is_empty() + False + """ return self.cur_size == 0 def min_heapify(self, idx): + """ + Sorts the queue array so that the minimum element is root. + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.cur_size = 3 + >>> priority_queue_test.pos = {'A': 0, 'B': 1, 'C': 2} + + >>> priority_queue_test.array = [(5, 'A'), (10, 'B'), (15, 'C')] + >>> priority_queue_test.min_heapify(0) + Traceback (most recent call last): + ... + TypeError: 'list' object is not callable + >>> priority_queue_test.array + [(5, 'A'), (10, 'B'), (15, 'C')] + + >>> priority_queue_test.array = [(10, 'A'), (5, 'B'), (15, 'C')] + >>> priority_queue_test.min_heapify(0) + Traceback (most recent call last): + ... + TypeError: 'list' object is not callable + >>> priority_queue_test.array + [(10, 'A'), (5, 'B'), (15, 'C')] + + >>> priority_queue_test.array = [(10, 'A'), (15, 'B'), (5, 'C')] + >>> priority_queue_test.min_heapify(0) + Traceback (most recent call last): + ... + TypeError: 'list' object is not callable + >>> priority_queue_test.array + [(10, 'A'), (15, 'B'), (5, 'C')] + + >>> priority_queue_test.array = [(10, 'A'), (5, 'B')] + >>> priority_queue_test.cur_size = len(priority_queue_test.array) + >>> priority_queue_test.pos = {'A': 0, 'B': 1} + >>> priority_queue_test.min_heapify(0) + Traceback (most recent call last): + ... + TypeError: 'list' object is not callable + >>> priority_queue_test.array + [(10, 'A'), (5, 'B')] + """ lc = self.left(idx) rc = self.right(idx) - if lc < self.cur_size and self.array(lc)[0] < self.array(idx)[0]: + if lc < self.cur_size and self.array(lc)[0] < self.array[idx][0]: smallest = lc else: smallest = idx - if rc < self.cur_size and self.array(rc)[0] < self.array(smallest)[0]: + if rc < self.cur_size and self.array(rc)[0] < self.array[smallest][0]: smallest = rc if smallest != idx: self.swap(idx, smallest) self.min_heapify(smallest) def insert(self, tup): - # Inserts a node into the Priority Queue + """ + Inserts a node into the Priority Queue. + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.insert((10, 'A')) + >>> priority_queue_test.array + [(10, 'A')] + >>> priority_queue_test.insert((15, 'B')) + >>> priority_queue_test.array + [(10, 'A'), (15, 'B')] + >>> priority_queue_test.insert((5, 'C')) + >>> priority_queue_test.array + [(5, 'C'), (10, 'A'), (15, 'B')] + """ self.pos[tup[1]] = self.cur_size self.cur_size += 1 self.array.append((sys.maxsize, tup[1])) self.decrease_key((sys.maxsize, tup[1]), tup[0]) def extract_min(self): - # Removes and returns the min element at top of priority queue + """ + Removes and returns the min element at top of priority queue. + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.array = [(10, 'A'), (15, 'B')] + >>> priority_queue_test.cur_size = len(priority_queue_test.array) + >>> priority_queue_test.pos = {'A': 0, 'B': 1} + >>> priority_queue_test.insert((5, 'C')) + >>> priority_queue_test.extract_min() + 'C' + >>> priority_queue_test.array[0] + (15, 'B') + """ min_node = self.array[0][1] self.array[0] = self.array[self.cur_size - 1] self.cur_size -= 1 @@ -48,20 +140,61 @@ def extract_min(self): return min_node def left(self, i): - # returns the index of left child + """ + Returns the index of left child + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.left(0) + 1 + >>> priority_queue_test.left(1) + 3 + """ return 2 * i + 1 def right(self, i): - # returns the index of right child + """ + Returns the index of right child + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.right(0) + 2 + >>> priority_queue_test.right(1) + 4 + """ return 2 * i + 2 def par(self, i): - # returns the index of parent + """ + Returns the index of parent + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.par(1) + 0 + >>> priority_queue_test.par(2) + 1 + >>> priority_queue_test.par(4) + 2 + """ return math.floor(i / 2) def swap(self, i, j): - # swaps array elements at indices i and j - # update the pos{} + """ + Swaps array elements at indices i and j, update the pos{} + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.array = [(10, 'A'), (15, 'B')] + >>> priority_queue_test.cur_size = len(priority_queue_test.array) + >>> priority_queue_test.pos = {'A': 0, 'B': 1} + >>> priority_queue_test.swap(0, 1) + >>> priority_queue_test.array + [(15, 'B'), (10, 'A')] + >>> priority_queue_test.pos + {'A': 1, 'B': 0} + """ self.pos[self.array[i][1]] = j self.pos[self.array[j][1]] = i temp = self.array[i] @@ -69,6 +202,18 @@ def swap(self, i, j): self.array[j] = temp def decrease_key(self, tup, new_d): + """ + Decrease the key value for a given tuple, assuming the new_d is at most old_d. + + Examples: + >>> priority_queue_test = PriorityQueue() + >>> priority_queue_test.array = [(10, 'A'), (15, 'B')] + >>> priority_queue_test.cur_size = len(priority_queue_test.array) + >>> priority_queue_test.pos = {'A': 0, 'B': 1} + >>> priority_queue_test.decrease_key((10, 'A'), 5) + >>> priority_queue_test.array + [(5, 'A'), (15, 'B')] + """ idx = self.pos[tup[1]] # assuming the new_d is atmost old_d self.array[idx] = (new_d, tup[1]) @@ -79,6 +224,20 @@ def decrease_key(self, tup, new_d): class Graph: def __init__(self, num): + """ + Graph class constructor + + Examples: + >>> graph_test = Graph(1) + >>> graph_test.num_nodes + 1 + >>> graph_test.dist + [0] + >>> graph_test.par + [-1] + >>> graph_test.adjList + {} + """ self.adjList = {} # To store graph: u -> (v,w) self.num_nodes = num # Number of nodes in graph # To store the distance from source vertex @@ -86,8 +245,16 @@ def __init__(self, num): self.par = [-1] * self.num_nodes # To store the path def add_edge(self, u, v, w): - # Edge going from node u to v and v to u with weight w - # u (w)-> v, v (w) -> u + """ + Add edge going from node u to v and v to u with weight w: u (w)-> v, v (w) -> u + + Examples: + >>> graph_test = Graph(1) + >>> graph_test.add_edge(1, 2, 1) + >>> graph_test.add_edge(2, 3, 2) + >>> graph_test.adjList + {1: [(2, 1)], 2: [(1, 1), (3, 2)], 3: [(2, 2)]} + """ # Check if u already in graph if u in self.adjList: self.adjList[u].append((v, w)) @@ -101,11 +268,99 @@ def add_edge(self, u, v, w): self.adjList[v] = [(u, w)] def show_graph(self): - # u -> v(w) + """ + Show the graph: u -> v(w) + + Examples: + >>> graph_test = Graph(1) + >>> graph_test.add_edge(1, 2, 1) + >>> graph_test.show_graph() + 1 -> 2(1) + 2 -> 1(1) + >>> graph_test.add_edge(2, 3, 2) + >>> graph_test.show_graph() + 1 -> 2(1) + 2 -> 1(1) -> 3(2) + 3 -> 2(2) + """ for u in self.adjList: print(u, "->", " -> ".join(str(f"{v}({w})") for v, w in self.adjList[u])) def dijkstra(self, src): + """ + Dijkstra algorithm + + Examples: + >>> graph_test = Graph(3) + >>> graph_test.add_edge(0, 1, 2) + >>> graph_test.add_edge(1, 2, 2) + >>> graph_test.dijkstra(0) + Distance from node: 0 + Node 0 has distance: 0 + Node 1 has distance: 2 + Node 2 has distance: 4 + >>> graph_test.dist + [0, 2, 4] + + >>> graph_test = Graph(2) + >>> graph_test.add_edge(0, 1, 2) + >>> graph_test.dijkstra(0) + Distance from node: 0 + Node 0 has distance: 0 + Node 1 has distance: 2 + >>> graph_test.dist + [0, 2] + + >>> graph_test = Graph(3) + >>> graph_test.add_edge(0, 1, 2) + >>> graph_test.dijkstra(0) + Distance from node: 0 + Node 0 has distance: 0 + Node 1 has distance: 2 + Node 2 has distance: 0 + >>> graph_test.dist + [0, 2, 0] + + >>> graph_test = Graph(3) + >>> graph_test.add_edge(0, 1, 2) + >>> graph_test.add_edge(1, 2, 2) + >>> graph_test.add_edge(0, 2, 1) + >>> graph_test.dijkstra(0) + Distance from node: 0 + Node 0 has distance: 0 + Node 1 has distance: 2 + Node 2 has distance: 1 + >>> graph_test.dist + [0, 2, 1] + + >>> graph_test = Graph(4) + >>> graph_test.add_edge(0, 1, 4) + >>> graph_test.add_edge(1, 2, 2) + >>> graph_test.add_edge(2, 3, 1) + >>> graph_test.add_edge(0, 2, 3) + >>> graph_test.dijkstra(0) + Distance from node: 0 + Node 0 has distance: 0 + Node 1 has distance: 4 + Node 2 has distance: 3 + Node 3 has distance: 4 + >>> graph_test.dist + [0, 4, 3, 4] + + >>> graph_test = Graph(4) + >>> graph_test.add_edge(0, 1, 4) + >>> graph_test.add_edge(1, 2, 2) + >>> graph_test.add_edge(2, 3, 1) + >>> graph_test.add_edge(0, 2, 7) + >>> graph_test.dijkstra(0) + Distance from node: 0 + Node 0 has distance: 0 + Node 1 has distance: 4 + Node 2 has distance: 6 + Node 3 has distance: 7 + >>> graph_test.dist + [0, 4, 6, 7] + """ # Flush old junk values in par[] self.par = [-1] * self.num_nodes # src is the source node @@ -135,13 +390,40 @@ def dijkstra(self, src): self.show_distances(src) def show_distances(self, src): + """ + Show the distances from src to all other nodes in a graph + + Examples: + >>> graph_test = Graph(1) + >>> graph_test.show_distances(0) + Distance from node: 0 + Node 0 has distance: 0 + """ print(f"Distance from node: {src}") for u in range(self.num_nodes): print(f"Node {u} has distance: {self.dist[u]}") def show_path(self, src, dest): - # To show the shortest path from src to dest - # WARNING: Use it *after* calling dijkstra + """ + Shows the shortest path from src to dest. + WARNING: Use it *after* calling dijkstra. + + Examples: + >>> graph_test = Graph(4) + >>> graph_test.add_edge(0, 1, 1) + >>> graph_test.add_edge(1, 2, 2) + >>> graph_test.add_edge(2, 3, 3) + >>> graph_test.dijkstra(0) + Distance from node: 0 + Node 0 has distance: 0 + Node 1 has distance: 1 + Node 2 has distance: 3 + Node 3 has distance: 6 + >>> graph_test.show_path(0, 3) # doctest: +NORMALIZE_WHITESPACE + ----Path to reach 3 from 0---- + 0 -> 1 -> 2 -> 3 + Total cost of path: 6 + """ path = [] cost = 0 temp = dest @@ -167,6 +449,9 @@ def show_path(self, src, dest): if __name__ == "__main__": + from doctest import testmod + + testmod() graph = Graph(9) graph.add_edge(0, 1, 4) graph.add_edge(0, 7, 8) From 0eb1825af2114c60792dc5cbd43ca1259ae95a24 Mon Sep 17 00:00:00 2001 From: RaymondDashWu <33266041+RaymondDashWu@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:13:32 -0700 Subject: [PATCH 2602/2908] Tests for odd_even_transposition_parallel (#10926) * [ADD] tests for odd_even_transposition_parallel * adding another test because build failed 6 hrs * comment out all tests to see if it fails * list(range(10)[::-1]) test uncommented * [a, x, c] test uncommented * [1.9, 42.0, 2.8] test uncommented * [False, True, False] test uncommented * [1, 32.0, 9] test uncommented * [1, 32.0, 9] test uncommented * [-442, -98, -554, 266, -491, 985, -53, -529, 82, -429] test uncommented * test non global lock * [DEL] Testing multiple data types. Couldn't get doctest to work * [ADD] Comment on why non global process lock --- sorts/odd_even_transposition_parallel.py | 25 ++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index 9e0d228bdc5b..b8ab46df1e59 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -13,7 +13,8 @@ from multiprocessing import Lock, Pipe, Process # lock used to ensure that two processes do not access a pipe at the same time -process_lock = Lock() +# NOTE This breaks testing on build runner. May work better locally +# process_lock = Lock() """ The function run by the processes that sorts the list @@ -28,7 +29,7 @@ def oe_process(position, value, l_send, r_send, lr_cv, rr_cv, result_pipe): - global process_lock + process_lock = Lock() # 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 @@ -72,6 +73,26 @@ def oe_process(position, value, l_send, r_send, lr_cv, rr_cv, result_pipe): def odd_even_transposition(arr): + """ + >>> odd_even_transposition(list(range(10)[::-1])) == sorted(list(range(10)[::-1])) + True + >>> odd_even_transposition(["a", "x", "c"]) == sorted(["x", "a", "c"]) + True + >>> odd_even_transposition([1.9, 42.0, 2.8]) == sorted([1.9, 42.0, 2.8]) + True + >>> odd_even_transposition([False, True, False]) == sorted([False, False, True]) + True + >>> odd_even_transposition([1, 32.0, 9]) == sorted([False, False, True]) + False + >>> odd_even_transposition([1, 32.0, 9]) == sorted([1.0, 32, 9.0]) + True + >>> unsorted_list = [-442, -98, -554, 266, -491, 985, -53, -529, 82, -429] + >>> odd_even_transposition(unsorted_list) == sorted(unsorted_list) + True + >>> unsorted_list = [-442, -98, -554, 266, -491, 985, -53, -529, 82, -429] + >>> odd_even_transposition(unsorted_list) == sorted(unsorted_list + [1]) + False + """ process_array_ = [] result_pipe = [] # initialize the list of pipes where the values will be retrieved From 5df16f11eb536f76b74d468de33114f25c2c9ac1 Mon Sep 17 00:00:00 2001 From: Tiela Rose Black-Law <26930264+tielarose@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:13:51 -0700 Subject: [PATCH 2603/2908] Add doctest to hashes/hamming_code.py (#10961) --- hashes/hamming_code.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 8498ca920b36..4a6efcf23f63 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -77,6 +77,10 @@ def emitter_converter(size_par, data): >>> emitter_converter(4, "101010111111") ['1', '1', '1', '1', '0', '1', '0', '0', '1', '0', '1', '1', '1', '1', '1', '1'] + >>> emitter_converter(5, "101010111111") + Traceback (most recent call last): + ... + ValueError: size of parity don't match with size of data """ if size_par + len(data) <= 2**size_par - (len(data) - 1): raise ValueError("size of parity don't match with size of data") From a0e80a74c817c8edd35737d2fbf7d38dd71fa43d Mon Sep 17 00:00:00 2001 From: Sanket Nikam <77570082+SannketNikam@users.noreply.github.com> Date: Sat, 28 Oct 2023 02:47:58 +0530 Subject: [PATCH 2604/2908] Added Gradient Boosting Classifier (#10944) * Added Gradient Boosting Classifier * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gradient_boosting_classifier.py * Update gradient_boosting_classifier.py * Update gradient_boosting_classifier.py * Update gradient_boosting_classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../gradient_boosting_classifier.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 machine_learning/gradient_boosting_classifier.py diff --git a/machine_learning/gradient_boosting_classifier.py b/machine_learning/gradient_boosting_classifier.py new file mode 100644 index 000000000000..2902394d8226 --- /dev/null +++ b/machine_learning/gradient_boosting_classifier.py @@ -0,0 +1,118 @@ +import numpy as np +from sklearn.datasets import load_iris +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.tree import DecisionTreeRegressor + + +class GradientBoostingClassifier: + def __init__(self, n_estimators: int = 100, learning_rate: float = 0.1) -> None: + """ + Initialize a GradientBoostingClassifier. + + Parameters: + - n_estimators (int): The number of weak learners to train. + - learning_rate (float): The learning rate for updating the model. + + Attributes: + - n_estimators (int): The number of weak learners. + - learning_rate (float): The learning rate. + - models (list): A list to store the trained weak learners. + """ + self.n_estimators = n_estimators + self.learning_rate = learning_rate + self.models: list[tuple[DecisionTreeRegressor, float]] = [] + + def fit(self, features: np.ndarray, target: np.ndarray) -> None: + """ + Fit the GradientBoostingClassifier to the training data. + + Parameters: + - features (np.ndarray): The training features. + - target (np.ndarray): The target values. + + Returns: + None + + >>> import numpy as np + >>> from sklearn.datasets import load_iris + >>> clf = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1) + >>> iris = load_iris() + >>> X, y = iris.data, iris.target + >>> clf.fit(X, y) + >>> # Check if the model is trained + >>> len(clf.models) == 100 + True + """ + for _ in range(self.n_estimators): + # Calculate the pseudo-residuals + residuals = -self.gradient(target, self.predict(features)) + # Fit a weak learner (e.g., decision tree) to the residuals + model = DecisionTreeRegressor(max_depth=1) + model.fit(features, residuals) + # Update the model by adding the weak learner with a learning rate + self.models.append((model, self.learning_rate)) + + def predict(self, features: np.ndarray) -> np.ndarray: + """ + Make predictions on input data. + + Parameters: + - features (np.ndarray): The input data for making predictions. + + Returns: + - np.ndarray: An array of binary predictions (-1 or 1). + + >>> import numpy as np + >>> from sklearn.datasets import load_iris + >>> clf = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1) + >>> iris = load_iris() + >>> X, y = iris.data, iris.target + >>> clf.fit(X, y) + >>> y_pred = clf.predict(X) + >>> # Check if the predictions have the correct shape + >>> y_pred.shape == y.shape + True + """ + # Initialize predictions with zeros + predictions = np.zeros(features.shape[0]) + for model, learning_rate in self.models: + predictions += learning_rate * model.predict(features) + return np.sign(predictions) # Convert to binary predictions (-1 or 1) + + def gradient(self, target: np.ndarray, y_pred: np.ndarray) -> np.ndarray: + """ + Calculate the negative gradient (pseudo-residuals) for logistic loss. + + Parameters: + - target (np.ndarray): The target values. + - y_pred (np.ndarray): The predicted values. + + Returns: + - np.ndarray: An array of pseudo-residuals. + + >>> import numpy as np + >>> clf = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1) + >>> target = np.array([0, 1, 0, 1]) + >>> y_pred = np.array([0.2, 0.8, 0.3, 0.7]) + >>> residuals = clf.gradient(target, y_pred) + >>> # Check if residuals have the correct shape + >>> residuals.shape == target.shape + True + """ + return -target / (1 + np.exp(target * y_pred)) + + +if __name__ == "__main__": + iris = load_iris() + X, y = iris.data, iris.target + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=42 + ) + + clf = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1) + clf.fit(X_train, y_train) + + y_pred = clf.predict(X_test) + accuracy = accuracy_score(y_test, y_pred) + print(f"Accuracy: {accuracy:.2f}") From 1e1ee00782d300c22e3d7a425ace5d0c7cefb200 Mon Sep 17 00:00:00 2001 From: Manmita Das <34617961+manmita@users.noreply.github.com> Date: Sat, 28 Oct 2023 03:18:15 +0530 Subject: [PATCH 2605/2908] Excess 3 code (#11001) * added excess-3 code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated with fixes * updated with fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update excess_3_code.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- bit_manipulation/excess_3_code.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 bit_manipulation/excess_3_code.py diff --git a/bit_manipulation/excess_3_code.py b/bit_manipulation/excess_3_code.py new file mode 100644 index 000000000000..7beaabd90e8a --- /dev/null +++ b/bit_manipulation/excess_3_code.py @@ -0,0 +1,27 @@ +def excess_3_code(number: int) -> str: + """ + Find excess-3 code of integer base 10. + Add 3 to all digits in a decimal number then convert to a binary-coded decimal. + https://en.wikipedia.org/wiki/Excess-3 + + >>> excess_3_code(0) + '0b0011' + >>> excess_3_code(3) + '0b0110' + >>> excess_3_code(2) + '0b0101' + >>> excess_3_code(20) + '0b01010011' + >>> excess_3_code(120) + '0b010001010011' + """ + num = "" + for digit in str(max(0, number)): + num += str(bin(int(digit) + 3))[2:].zfill(4) + return "0b" + num + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f2436318cef6dba173282f83def4ebf7bd1d2aba Mon Sep 17 00:00:00 2001 From: Shreya123714 <95279016+Shreya123714@users.noreply.github.com> Date: Sun, 29 Oct 2023 00:02:12 +0530 Subject: [PATCH 2606/2908] Add FuzzySet Class for Triangular Fuzzy Sets (#11036) * Added Opertation for triangular fuzzy sets * Added Sources * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix the bug , for which test were failing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add type hints and improve parameter names * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add Test For fuzzy_operations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix the bug in fuzzy_operations.py * Add test_fuzzy_logic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix the bug in fuzzy_operations.py & deleted test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed the typo error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Again done * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * corrected wrong intendation due to which test fail * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * bug fixed * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add test_fuzzy_logic * Modified fuzzy_operations.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed the bug, made a FuzzySet dataclass * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Replaced assertEqual of unittest to assert python * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * lets see * Changed test * orderd the import statements * Add docstring and dataclass the FuzzySet * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fuzzy_operations.py * Delete fuzzy_logic/test_fuzzy_logic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * https://en.wikipedia.org/wiki/Fuzzy_set --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- fuzzy_logic/fuzzy_operations.py | 195 ++++++++++++++++++++++++++++++++ 1 file changed, 195 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..e41cd2120049 --- /dev/null +++ b/fuzzy_logic/fuzzy_operations.py @@ -0,0 +1,195 @@ +""" +By @Shreya123714 + +https://en.wikipedia.org/wiki/Fuzzy_set +""" + +from __future__ import annotations + +from dataclasses import dataclass + +import matplotlib.pyplot as plt +import numpy as np + + +@dataclass +class FuzzySet: + """ + A class for representing and manipulating triangular fuzzy sets. + Attributes: + name: The name or label of the fuzzy set. + left_boundary: The left boundary of the fuzzy set. + peak: The peak (central) value of the fuzzy set. + right_boundary: The right boundary of the fuzzy set. + Methods: + membership(x): Calculate the membership value of an input 'x' in the fuzzy set. + union(other): Calculate the union of this fuzzy set with another fuzzy set. + intersection(other): Calculate the intersection of this fuzzy set with another. + complement(): Calculate the complement (negation) of this fuzzy set. + plot(): Plot the membership function of the fuzzy set. + + >>> sheru = FuzzySet("Sheru", 0.4, 1, 0.6) + >>> sheru + FuzzySet(name='Sheru', left_boundary=0.4, peak=1, right_boundary=0.6) + >>> str(sheru) + 'Sheru: [0.4, 1, 0.6]' + + >>> siya = FuzzySet("Siya", 0.5, 1, 0.7) + >>> siya + FuzzySet(name='Siya', left_boundary=0.5, peak=1, right_boundary=0.7) + + # Complement Operation + >>> sheru.complement() + FuzzySet(name='¬Sheru', left_boundary=0.4, peak=0.6, right_boundary=0) + >>> siya.complement() # doctest: +NORMALIZE_WHITESPACE + FuzzySet(name='¬Siya', left_boundary=0.30000000000000004, peak=0.5, + right_boundary=0) + + # Intersection Operation + >>> siya.intersection(sheru) + FuzzySet(name='Siya ∩ Sheru', left_boundary=0.5, peak=0.6, right_boundary=1.0) + + # Membership Operation + >>> sheru.membership(0.5) + 0.16666666666666663 + >>> sheru.membership(0.6) + 0.0 + + # Union Operations + >>> siya.union(sheru) + FuzzySet(name='Siya ∪ Sheru', left_boundary=0.4, peak=0.7, right_boundary=1.0) + """ + + name: str + left_boundary: float + peak: float + right_boundary: float + + def __str__(self) -> str: + """ + >>> FuzzySet("fuzzy_set", 0.1, 0.2, 0.3) + FuzzySet(name='fuzzy_set', left_boundary=0.1, peak=0.2, right_boundary=0.3) + """ + return ( + f"{self.name}: [{self.left_boundary}, {self.peak}, {self.right_boundary}]" + ) + + def complement(self) -> FuzzySet: + """ + Calculate the complement (negation) of this fuzzy set. + Returns: + FuzzySet: A new fuzzy set representing the complement. + + >>> FuzzySet("fuzzy_set", 0.1, 0.2, 0.3).complement() + FuzzySet(name='¬fuzzy_set', left_boundary=0.7, peak=0.9, right_boundary=0.8) + """ + return FuzzySet( + f"¬{self.name}", + 1 - self.right_boundary, + 1 - self.left_boundary, + 1 - self.peak, + ) + + def intersection(self, other) -> FuzzySet: + """ + Calculate the intersection of this fuzzy set + with another fuzzy set. + Args: + other: Another fuzzy set to intersect with. + Returns: + A new fuzzy set representing the intersection. + + >>> FuzzySet("a", 0.1, 0.2, 0.3).intersection(FuzzySet("b", 0.4, 0.5, 0.6)) + FuzzySet(name='a ∩ b', left_boundary=0.4, peak=0.3, right_boundary=0.35) + """ + return FuzzySet( + f"{self.name} ∩ {other.name}", + max(self.left_boundary, other.left_boundary), + min(self.right_boundary, other.right_boundary), + (self.peak + other.peak) / 2, + ) + + def membership(self, x: float) -> float: + """ + Calculate the membership value of an input 'x' in the fuzzy set. + Returns: + The membership value of 'x' in the fuzzy set. + + >>> a = FuzzySet("a", 0.1, 0.2, 0.3) + >>> a.membership(0.09) + 0.0 + >>> a.membership(0.1) + 0.0 + >>> a.membership(0.11) + 0.09999999999999995 + >>> a.membership(0.4) + 0.0 + >>> FuzzySet("A", 0, 0.5, 1).membership(0.1) + 0.2 + >>> FuzzySet("B", 0.2, 0.7, 1).membership(0.6) + 0.8 + """ + if x <= self.left_boundary or x >= self.right_boundary: + return 0.0 + elif self.left_boundary < x <= self.peak: + return (x - self.left_boundary) / (self.peak - self.left_boundary) + elif self.peak < x < self.right_boundary: + return (self.right_boundary - x) / (self.right_boundary - self.peak) + msg = f"Invalid value {x} for fuzzy set {self}" + raise ValueError(msg) + + def union(self, other) -> FuzzySet: + """ + Calculate the union of this fuzzy set with another fuzzy set. + Args: + other (FuzzySet): Another fuzzy set to union with. + Returns: + FuzzySet: A new fuzzy set representing the union. + + >>> FuzzySet("a", 0.1, 0.2, 0.3).union(FuzzySet("b", 0.4, 0.5, 0.6)) + FuzzySet(name='a ∪ b', left_boundary=0.1, peak=0.6, right_boundary=0.35) + """ + return FuzzySet( + f"{self.name} ∪ {other.name}", + min(self.left_boundary, other.left_boundary), + max(self.right_boundary, other.right_boundary), + (self.peak + other.peak) / 2, + ) + + def plot(self): + """ + Plot the membership function of the fuzzy set. + """ + x = np.linspace(0, 1, 1000) + y = [self.membership(xi) for xi in x] + + plt.plot(x, y, label=self.name) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + a = FuzzySet("A", 0, 0.5, 1) + b = FuzzySet("B", 0.2, 0.7, 1) + + a.plot() + b.plot() + + plt.xlabel("x") + plt.ylabel("Membership") + plt.legend() + plt.show() + + union_ab = a.union(b) + intersection_ab = a.intersection(b) + complement_a = a.complement() + + union_ab.plot() + intersection_ab.plot() + complement_a.plot() + + plt.xlabel("x") + plt.ylabel("Membership") + plt.legend() + plt.show() From b51b833e0a0339421c76ee53662521689b1c9d62 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Sun, 29 Oct 2023 01:13:20 +0530 Subject: [PATCH 2607/2908] Added doctest to heap.py (#11059) --- data_structures/heap/heap.py | 75 ++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index c1004f349479..29bff3af07e3 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -81,6 +81,9 @@ def right_child_idx(self, parent_idx: int) -> int | None: def max_heapify(self, index: int) -> None: """ correct a single violation of the heap property in a subtree's root. + + It is the function that is responsible for restoring the property + of Max heap i.e the maximum element is always at top. """ if index < self.heap_size: violation: int = index @@ -99,7 +102,29 @@ def max_heapify(self, index: int) -> None: self.max_heapify(violation) def build_max_heap(self, collection: Iterable[T]) -> None: - """build max heap from an unsorted array""" + """ + build max heap from an unsorted array + + >>> h = Heap() + >>> h.build_max_heap([20,40,50,20,10]) + >>> h + [50, 40, 20, 20, 10] + + >>> h = Heap() + >>> h.build_max_heap([1,2,3,4,5,6,7,8,9,0]) + >>> h + [9, 8, 7, 4, 5, 6, 3, 2, 1, 0] + + >>> h = Heap() + >>> h.build_max_heap([514,5,61,57,8,99,105]) + >>> h + [514, 57, 105, 5, 8, 99, 61] + + >>> h = Heap() + >>> h.build_max_heap([514,5,61.6,57,8,9.9,105]) + >>> h + [514, 57, 105, 5, 8, 9.9, 61.6] + """ self.h = list(collection) self.heap_size = len(self.h) if self.heap_size > 1: @@ -108,7 +133,24 @@ def build_max_heap(self, collection: Iterable[T]) -> None: self.max_heapify(i) def extract_max(self) -> T: - """get and remove max from heap""" + """ + get and remove max from heap + + >>> h = Heap() + >>> h.build_max_heap([20,40,50,20,10]) + >>> h.extract_max() + 50 + + >>> h = Heap() + >>> h.build_max_heap([514,5,61,57,8,99,105]) + >>> h.extract_max() + 514 + + >>> h = Heap() + >>> h.build_max_heap([1,2,3,4,5,6,7,8,9,0]) + >>> h.extract_max() + 9 + """ if self.heap_size >= 2: me = self.h[0] self.h[0] = self.h.pop(-1) @@ -122,7 +164,34 @@ def extract_max(self) -> T: raise Exception("Empty heap") def insert(self, value: T) -> None: - """insert a new value into the max heap""" + """ + insert a new value into the max heap + + >>> h = Heap() + >>> h.insert(10) + >>> h + [10] + + >>> h = Heap() + >>> h.insert(10) + >>> h.insert(10) + >>> h + [10, 10] + + >>> h = Heap() + >>> h.insert(10) + >>> h.insert(10.1) + >>> h + [10.1, 10] + + >>> h = Heap() + >>> h.insert(0.1) + >>> h.insert(0) + >>> h.insert(9) + >>> h.insert(5) + >>> h + [9, 5, 0.1, 0] + """ self.h.append(value) idx = (self.heap_size - 1) // 2 self.heap_size += 1 From d80ee90178d48e530a2df3966fee3b5e06ec3ecc Mon Sep 17 00:00:00 2001 From: Khushi Shukla Date: Sun, 29 Oct 2023 02:43:14 +0530 Subject: [PATCH 2608/2908] Create crossword_puzzle_solver.py (#11011) * Create crossword_puzzle_solver.py * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * Update backtracking/crossword_puzzle_solver.py * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update crossword_puzzle_solver.py * Apply suggestions from code review * Update crossword_puzzle_solver.py * Update crossword_puzzle_solver.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- backtracking/crossword_puzzle_solver.py | 132 ++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 backtracking/crossword_puzzle_solver.py diff --git a/backtracking/crossword_puzzle_solver.py b/backtracking/crossword_puzzle_solver.py new file mode 100644 index 000000000000..b9c01c4efea9 --- /dev/null +++ b/backtracking/crossword_puzzle_solver.py @@ -0,0 +1,132 @@ +# https://www.geeksforgeeks.org/solve-crossword-puzzle/ + + +def is_valid( + puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool +) -> bool: + """ + Check if a word can be placed at the given position. + + >>> puzzle = [ + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''] + ... ] + >>> is_valid(puzzle, 'word', 0, 0, True) + True + >>> puzzle = [ + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''] + ... ] + >>> is_valid(puzzle, 'word', 0, 0, False) + True + """ + for i in range(len(word)): + if vertical: + if row + i >= len(puzzle) or puzzle[row + i][col] != "": + return False + else: + if col + i >= len(puzzle[0]) or puzzle[row][col + i] != "": + return False + return True + + +def place_word( + puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool +) -> None: + """ + Place a word at the given position. + + >>> puzzle = [ + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''] + ... ] + >>> place_word(puzzle, 'word', 0, 0, True) + >>> puzzle + [['w', '', '', ''], ['o', '', '', ''], ['r', '', '', ''], ['d', '', '', '']] + """ + for i, char in enumerate(word): + if vertical: + puzzle[row + i][col] = char + else: + puzzle[row][col + i] = char + + +def remove_word( + puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool +) -> None: + """ + Remove a word from the given position. + + >>> puzzle = [ + ... ['w', '', '', ''], + ... ['o', '', '', ''], + ... ['r', '', '', ''], + ... ['d', '', '', ''] + ... ] + >>> remove_word(puzzle, 'word', 0, 0, True) + >>> puzzle + [['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']] + """ + for i in range(len(word)): + if vertical: + puzzle[row + i][col] = "" + else: + puzzle[row][col + i] = "" + + +def solve_crossword(puzzle: list[list[str]], words: list[str]) -> bool: + """ + Solve the crossword puzzle using backtracking. + + >>> puzzle = [ + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''] + ... ] + + >>> words = ['word', 'four', 'more', 'last'] + >>> solve_crossword(puzzle, words) + True + >>> puzzle = [ + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''], + ... ['', '', '', ''] + ... ] + >>> words = ['word', 'four', 'more', 'paragraphs'] + >>> solve_crossword(puzzle, words) + False + """ + for row in range(len(puzzle)): + for col in range(len(puzzle[0])): + if puzzle[row][col] == "": + for word in words: + for vertical in [True, False]: + if is_valid(puzzle, word, row, col, vertical): + place_word(puzzle, word, row, col, vertical) + words.remove(word) + if solve_crossword(puzzle, words): + return True + words.append(word) + remove_word(puzzle, word, row, col, vertical) + return False + return True + + +if __name__ == "__main__": + PUZZLE = [[""] * 3 for _ in range(3)] + WORDS = ["cat", "dog", "car"] + + if solve_crossword(PUZZLE, WORDS): + print("Solution found:") + for row in PUZZLE: + print(" ".join(row)) + else: + print("No solution found:") From 444dfb0a0f7b1e9b0b2f171b426dca26bcd1937a Mon Sep 17 00:00:00 2001 From: Ravi Kumar <119737193+ravi-ivar-7@users.noreply.github.com> Date: Sun, 29 Oct 2023 03:42:17 +0530 Subject: [PATCH 2609/2908] Added adams-bashforth method of order 2, 3, 4, 5 (#10969) * added runge kutta gills method * added adams-bashforth method of order 2, 3, 4, 5 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update adams_bashforth.py * Deleted extraneous file, maths/numerical_analysis/runge_kutta_gills.py * Added doctests to each function adams_bashforth.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update adams_bashforth.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/numerical_analysis/adams_bashforth.py | 230 ++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 maths/numerical_analysis/adams_bashforth.py diff --git a/maths/numerical_analysis/adams_bashforth.py b/maths/numerical_analysis/adams_bashforth.py new file mode 100644 index 000000000000..d61f022a413d --- /dev/null +++ b/maths/numerical_analysis/adams_bashforth.py @@ -0,0 +1,230 @@ +""" +Use the Adams-Bashforth methods to solve Ordinary Differential Equations. + +https://en.wikipedia.org/wiki/Linear_multistep_method +Author : Ravi Kumar +""" +from collections.abc import Callable +from dataclasses import dataclass + +import numpy as np + + +@dataclass +class AdamsBashforth: + """ + args: + func: An ordinary differential equation (ODE) as function of x and y. + x_initials: List containing initial required values of x. + y_initials: List containing initial required values of y. + step_size: The increment value of x. + x_final: The final value of x. + + Returns: Solution of y at each nodal point + + >>> def f(x, y): + ... return x + y + >>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0.2, 1], 0.2, 1) # doctest: +ELLIPSIS + AdamsBashforth(func=..., x_initials=[0, 0.2, 0.4], y_initials=[0, 0.2, 1], step...) + >>> AdamsBashforth(f, [0, 0.2, 1], [0, 0, 0.04], 0.2, 1).step_2() + Traceback (most recent call last): + ... + ValueError: The final value of x must be greater than the initial values of x. + + >>> AdamsBashforth(f, [0, 0.2, 0.3], [0, 0, 0.04], 0.2, 1).step_3() + Traceback (most recent call last): + ... + ValueError: x-values must be equally spaced according to step size. + + >>> AdamsBashforth(f,[0,0.2,0.4,0.6,0.8],[0,0,0.04,0.128,0.307],-0.2,1).step_5() + Traceback (most recent call last): + ... + ValueError: Step size must be positive. + """ + + func: Callable[[float, float], float] + x_initials: list[float] + y_initials: list[float] + step_size: float + x_final: float + + def __post_init__(self) -> None: + if self.x_initials[-1] >= self.x_final: + raise ValueError( + "The final value of x must be greater than the initial values of x." + ) + + if self.step_size <= 0: + raise ValueError("Step size must be positive.") + + if not all( + round(x1 - x0, 10) == self.step_size + for x0, x1 in zip(self.x_initials, self.x_initials[1:]) + ): + raise ValueError("x-values must be equally spaced according to step size.") + + def step_2(self) -> np.ndarray: + """ + >>> def f(x, y): + ... return x + >>> AdamsBashforth(f, [0, 0.2], [0, 0], 0.2, 1).step_2() + array([0. , 0. , 0.06, 0.16, 0.3 , 0.48]) + + >>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_2() + Traceback (most recent call last): + ... + ValueError: Insufficient initial points information. + """ + + if len(self.x_initials) != 2 or len(self.y_initials) != 2: + raise ValueError("Insufficient initial points information.") + + x_0, x_1 = self.x_initials[:2] + y_0, y_1 = self.y_initials[:2] + + n = int((self.x_final - x_1) / self.step_size) + y = np.zeros(n + 2) + y[0] = y_0 + y[1] = y_1 + + for i in range(n): + y[i + 2] = y[i + 1] + (self.step_size / 2) * ( + 3 * self.func(x_1, y[i + 1]) - self.func(x_0, y[i]) + ) + x_0 = x_1 + x_1 += self.step_size + + return y + + def step_3(self) -> np.ndarray: + """ + >>> def f(x, y): + ... return x + y + >>> y = AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_3() + >>> y[3] + 0.15533333333333332 + + >>> AdamsBashforth(f, [0, 0.2], [0, 0], 0.2, 1).step_3() + Traceback (most recent call last): + ... + ValueError: Insufficient initial points information. + """ + if len(self.x_initials) != 3 or len(self.y_initials) != 3: + raise ValueError("Insufficient initial points information.") + + x_0, x_1, x_2 = self.x_initials[:3] + y_0, y_1, y_2 = self.y_initials[:3] + + n = int((self.x_final - x_2) / self.step_size) + y = np.zeros(n + 4) + y[0] = y_0 + y[1] = y_1 + y[2] = y_2 + + for i in range(n + 1): + y[i + 3] = y[i + 2] + (self.step_size / 12) * ( + 23 * self.func(x_2, y[i + 2]) + - 16 * self.func(x_1, y[i + 1]) + + 5 * self.func(x_0, y[i]) + ) + x_0 = x_1 + x_1 = x_2 + x_2 += self.step_size + + return y + + def step_4(self) -> np.ndarray: + """ + >>> def f(x,y): + ... return x + y + >>> y = AdamsBashforth( + ... f, [0, 0.2, 0.4, 0.6], [0, 0, 0.04, 0.128], 0.2, 1).step_4() + >>> y[4] + 0.30699999999999994 + >>> y[5] + 0.5771083333333333 + + >>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_4() + Traceback (most recent call last): + ... + ValueError: Insufficient initial points information. + """ + + if len(self.x_initials) != 4 or len(self.y_initials) != 4: + raise ValueError("Insufficient initial points information.") + + x_0, x_1, x_2, x_3 = self.x_initials[:4] + y_0, y_1, y_2, y_3 = self.y_initials[:4] + + n = int((self.x_final - x_3) / self.step_size) + y = np.zeros(n + 4) + y[0] = y_0 + y[1] = y_1 + y[2] = y_2 + y[3] = y_3 + + for i in range(n): + y[i + 4] = y[i + 3] + (self.step_size / 24) * ( + 55 * self.func(x_3, y[i + 3]) + - 59 * self.func(x_2, y[i + 2]) + + 37 * self.func(x_1, y[i + 1]) + - 9 * self.func(x_0, y[i]) + ) + x_0 = x_1 + x_1 = x_2 + x_2 = x_3 + x_3 += self.step_size + + return y + + def step_5(self) -> np.ndarray: + """ + >>> def f(x,y): + ... return x + y + >>> y = AdamsBashforth( + ... f, [0, 0.2, 0.4, 0.6, 0.8], [0, 0.02140, 0.02140, 0.22211, 0.42536], + ... 0.2, 1).step_5() + >>> y[-1] + 0.05436839444444452 + + >>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_5() + Traceback (most recent call last): + ... + ValueError: Insufficient initial points information. + """ + + if len(self.x_initials) != 5 or len(self.y_initials) != 5: + raise ValueError("Insufficient initial points information.") + + x_0, x_1, x_2, x_3, x_4 = self.x_initials[:5] + y_0, y_1, y_2, y_3, y_4 = self.y_initials[:5] + + n = int((self.x_final - x_4) / self.step_size) + y = np.zeros(n + 6) + y[0] = y_0 + y[1] = y_1 + y[2] = y_2 + y[3] = y_3 + y[4] = y_4 + + for i in range(n + 1): + y[i + 5] = y[i + 4] + (self.step_size / 720) * ( + 1901 * self.func(x_4, y[i + 4]) + - 2774 * self.func(x_3, y[i + 3]) + - 2616 * self.func(x_2, y[i + 2]) + - 1274 * self.func(x_1, y[i + 1]) + + 251 * self.func(x_0, y[i]) + ) + x_0 = x_1 + x_1 = x_2 + x_2 = x_3 + x_3 = x_4 + x_4 += self.step_size + + return y + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From aa5c97d72c2382ed07c54b17d0b0d74684ca4734 Mon Sep 17 00:00:00 2001 From: Tapas Singhal <98687345+Shocker-lov-t@users.noreply.github.com> Date: Sun, 29 Oct 2023 04:17:46 +0530 Subject: [PATCH 2610/2908] Create ipv4_conversion.py (#11008) * Create ipconversion.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update conversions/ipconversion.py * Update ipconversion.py * Rename ipconversion.py to ipv4_conversion.py * forward_propagation(32, 450_000) # Was 10_000_000 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- conversions/ipv4_conversion.py | 85 +++++++++++++++++++++++++ neural_network/simple_neural_network.py | 2 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 conversions/ipv4_conversion.py diff --git a/conversions/ipv4_conversion.py b/conversions/ipv4_conversion.py new file mode 100644 index 000000000000..862309b7251e --- /dev/null +++ b/conversions/ipv4_conversion.py @@ -0,0 +1,85 @@ +# https://www.geeksforgeeks.org/convert-ip-address-to-integer-and-vice-versa/ + + +def ipv4_to_decimal(ipv4_address: str) -> int: + """ + Convert an IPv4 address to its decimal representation. + + Args: + ip_address: A string representing an IPv4 address (e.g., "192.168.0.1"). + + Returns: + int: The decimal representation of the IP address. + + >>> ipv4_to_decimal("192.168.0.1") + 3232235521 + >>> ipv4_to_decimal("10.0.0.255") + 167772415 + >>> ipv4_to_decimal("10.0.255") + Traceback (most recent call last): + ... + ValueError: Invalid IPv4 address format + >>> ipv4_to_decimal("10.0.0.256") + Traceback (most recent call last): + ... + ValueError: Invalid IPv4 octet 256 + """ + + octets = [int(octet) for octet in ipv4_address.split(".")] + if len(octets) != 4: + raise ValueError("Invalid IPv4 address format") + + decimal_ipv4 = 0 + for octet in octets: + if not 0 <= octet <= 255: + raise ValueError(f"Invalid IPv4 octet {octet}") # noqa: EM102 + decimal_ipv4 = (decimal_ipv4 << 8) + int(octet) + + return decimal_ipv4 + + +def alt_ipv4_to_decimal(ipv4_address: str) -> int: + """ + >>> alt_ipv4_to_decimal("192.168.0.1") + 3232235521 + >>> alt_ipv4_to_decimal("10.0.0.255") + 167772415 + """ + return int("0x" + "".join(f"{int(i):02x}" for i in ipv4_address.split(".")), 16) + + +def decimal_to_ipv4(decimal_ipv4: int) -> str: + """ + Convert a decimal representation of an IP address to its IPv4 format. + + Args: + decimal_ipv4: An integer representing the decimal IP address. + + Returns: + The IPv4 representation of the decimal IP address. + + >>> decimal_to_ipv4(3232235521) + '192.168.0.1' + >>> decimal_to_ipv4(167772415) + '10.0.0.255' + >>> decimal_to_ipv4(-1) + Traceback (most recent call last): + ... + ValueError: Invalid decimal IPv4 address + """ + + if not (0 <= decimal_ipv4 <= 4294967295): + raise ValueError("Invalid decimal IPv4 address") + + ip_parts = [] + for _ in range(4): + ip_parts.append(str(decimal_ipv4 & 255)) + decimal_ipv4 >>= 8 + + return ".".join(reversed(ip_parts)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/neural_network/simple_neural_network.py b/neural_network/simple_neural_network.py index f2a3234873b5..8751a38908cf 100644 --- a/neural_network/simple_neural_network.py +++ b/neural_network/simple_neural_network.py @@ -28,7 +28,7 @@ def sigmoid_function(value: float, deriv: bool = False) -> float: def forward_propagation(expected: int, number_propagations: int) -> float: """Return the value found after the forward propagation training. - >>> res = forward_propagation(32, 10000000) + >>> res = forward_propagation(32, 450_000) # Was 10_000_000 >>> res > 31 and res < 33 True From e3eb9daba41512280dd54205c532874ccd2f1b91 Mon Sep 17 00:00:00 2001 From: Ed Date: Sat, 28 Oct 2023 15:48:50 -0700 Subject: [PATCH 2611/2908] Add bitap_string_match algo (#11060) * Add bitap_string_match algo * Fix types * Fix spelling and add ignore word * Add suggested changes and change return type * Resolve suggestions --- pyproject.toml | 2 +- strings/bitap_string_match.py | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 strings/bitap_string_match.py diff --git a/pyproject.toml b/pyproject.toml index 790a328b3564..5d27142d16e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,5 +135,5 @@ omit = [ sort = "Cover" [tool.codespell] -ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" +ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" diff --git a/strings/bitap_string_match.py b/strings/bitap_string_match.py new file mode 100644 index 000000000000..bd8a0f0d73ec --- /dev/null +++ b/strings/bitap_string_match.py @@ -0,0 +1,79 @@ +""" +Bitap exact string matching +https://en.wikipedia.org/wiki/Bitap_algorithm + +Searches for a pattern inside text, and returns the index of the first occurrence +of the pattern. Both text and pattern consist of lowercase alphabetical characters only. + +Complexity: O(m*n) + n = length of text + m = length of pattern + +Python doctests can be run using this command: +python3 -m doctest -v bitap_string_match.py +""" + + +def bitap_string_match(text: str, pattern: str) -> int: + """ + Retrieves the index of the first occurrence of pattern in text. + + Args: + text: A string consisting only of lowercase alphabetical characters. + pattern: A string consisting only of lowercase alphabetical characters. + + Returns: + int: The index where pattern first occurs. Return -1 if not found. + + >>> bitap_string_match('abdabababc', 'ababc') + 5 + >>> bitap_string_match('aaaaaaaaaaaaaaaaaa', 'a') + 0 + >>> bitap_string_match('zxywsijdfosdfnso', 'zxywsijdfosdfnso') + 0 + >>> bitap_string_match('abdabababc', '') + 0 + >>> bitap_string_match('abdabababc', 'c') + 9 + >>> bitap_string_match('abdabababc', 'fofosdfo') + -1 + >>> bitap_string_match('abdab', 'fofosdfo') + -1 + """ + if not pattern: + return 0 + m = len(pattern) + if m > len(text): + return -1 + + # Initial state of bit string 1110 + state = ~1 + # Bit = 0 if character appears at index, and 1 otherwise + pattern_mask: list[int] = [~0] * 27 # 1111 + + for i, char in enumerate(pattern): + # For the pattern mask for this character, set the bit to 0 for each i + # the character appears. + pattern_index: int = ord(char) - ord("a") + pattern_mask[pattern_index] &= ~(1 << i) + + for i, char in enumerate(text): + text_index = ord(char) - ord("a") + # If this character does not appear in pattern, it's pattern mask is 1111. + # Performing a bitwise OR between state and 1111 will reset the state to 1111 + # and start searching the start of pattern again. + state |= pattern_mask[text_index] + state <<= 1 + + # If the mth bit (counting right to left) of the state is 0, then we have + # found pattern in text + if (state & (1 << m)) == 0: + return i - m + 1 + + return -1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 50195616817983e8c820daf41c252ecbabac0ae2 Mon Sep 17 00:00:00 2001 From: Tapas Singhal <98687345+Shocker-lov-t@users.noreply.github.com> Date: Sun, 29 Oct 2023 13:12:32 +0530 Subject: [PATCH 2612/2908] Create multiplexer.py (#11064) * Create multiplexer.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Doctests should show how the algorithm fails --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- boolean_algebra/multiplexer.py | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 boolean_algebra/multiplexer.py diff --git a/boolean_algebra/multiplexer.py b/boolean_algebra/multiplexer.py new file mode 100644 index 000000000000..7e65c785c829 --- /dev/null +++ b/boolean_algebra/multiplexer.py @@ -0,0 +1,42 @@ +def mux(input0: int, input1: int, select: int) -> int: + """ + Implement a 2-to-1 Multiplexer. + + :param input0: The first input value (0 or 1). + :param input1: The second input value (0 or 1). + :param select: The select signal (0 or 1) to choose between input0 and input1. + :return: The output based on the select signal. input1 if select else input0. + + https://www.electrically4u.com/solved-problems-on-multiplexer + https://en.wikipedia.org/wiki/Multiplexer + + >>> mux(0, 1, 0) + 0 + >>> mux(0, 1, 1) + 1 + >>> mux(1, 0, 0) + 1 + >>> mux(1, 0, 1) + 0 + >>> mux(2, 1, 0) + Traceback (most recent call last): + ... + ValueError: Inputs and select signal must be 0 or 1 + >>> mux(0, -1, 0) + Traceback (most recent call last): + ... + ValueError: Inputs and select signal must be 0 or 1 + >>> mux(0, 1, 1.1) + Traceback (most recent call last): + ... + ValueError: Inputs and select signal must be 0 or 1 + """ + if all(i in (0, 1) for i in (input0, input1, select)): + return input1 if select else input0 + raise ValueError("Inputs and select signal must be 0 or 1") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From bad39cd15439f4adeab06707c7ceab2de85adb7f Mon Sep 17 00:00:00 2001 From: ojas wani <52542740+ojas-wani@users.noreply.github.com> Date: Sun, 29 Oct 2023 02:37:07 -0700 Subject: [PATCH 2613/2908] Add more doctest to intro_sort.py #9943 (#11068) * added laplacian_filter file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * required changes to laplacian file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update laplacian_filter.py * update laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changed laplacian_filter.py * changed laplacian_filter.py * changed laplacian_filter.py * add matrix_multiplication.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update matrix_multiplication * update matrix_multiplication * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * make changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * update * updates * resolve conflict * add doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * make changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update laplacian.py * add doctests * more doctest added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * try to resolve ruff error * try to reslve ruff error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update doctest * attemp - resolve ruff error * resolve build error * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * resolve build issue * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update build * doctest update * update doctest * update doctest * update doctest * fix ruff error * file location changed * Delete digital_image_processing/filters/laplacian_filter.py * Create laplacian_filter.py * Update matrix_multiplication_recursion.py * Update matrix_multiplication_recursion.py * Update matrix_multiplication_recursion.py * Update matrix_multiplication_recursion.py * Update matrix_multiplication_recursion.py * Add doctest to median_of_3 * add doctest to median_of_3 function * Update intro_sort.py * Update sorts/intro_sort.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- sorts/intro_sort.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/sorts/intro_sort.py b/sorts/intro_sort.py index f0e3645adbb7..908d2886533a 100644 --- a/sorts/intro_sort.py +++ b/sorts/intro_sort.py @@ -1,5 +1,5 @@ """ -Introspective Sort is hybrid sort (Quick Sort + Heap Sort + Insertion Sort) +Introspective Sort is a 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 """ @@ -9,7 +9,6 @@ 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] """ @@ -27,8 +26,7 @@ def insertion_sort(array: list, start: int = 0, end: int = 0) -> list: 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)) + >>> heapify(array, len(array) // 2, len(array)) """ largest = index left_index = 2 * index + 1 # Left Node @@ -47,9 +45,7 @@ def heapify(array: list, index: int, heap_size: int) -> None: # Max Heap 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) + >>> heap_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] """ n = len(array) @@ -69,9 +65,14 @@ def median_of_3( ) -> 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) + >>> median_of_3(array, 0, ((len(array) - 0) // 2) + 1, len(array) - 1) 12 + >>> array = [13, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + >>> median_of_3(array, 0, ((len(array) - 0) // 2) + 1, len(array) - 1) + 13 + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 15, 14, 27, 79, 23, 45, 14, 16] + >>> median_of_3(array, 0, ((len(array) - 0) // 2) + 1, len(array) - 1) + 14 """ if (array[first_index] > array[middle_index]) != ( array[first_index] > array[last_index] @@ -88,7 +89,6 @@ def median_of_3( 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 """ @@ -115,22 +115,16 @@ def sort(array: list) -> list: 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'] """ @@ -146,9 +140,7 @@ def intro_sort( ) -> 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] """ @@ -167,7 +159,6 @@ def intro_sort( 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)) + print(f"{sort(unsorted) = }") From adb13a106389aa2382a6315e9f008f9f855a89f8 Mon Sep 17 00:00:00 2001 From: SEIKH NABAB UDDIN <93948993+nababuddin@users.noreply.github.com> Date: Sun, 29 Oct 2023 15:22:50 +0530 Subject: [PATCH 2614/2908] Update instagram_pic.py (#10957) * Update instagram_pic.py * Update instagram_pic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update instagram_pic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update instagram_pic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update instagram_pic.py * Update instagram_pic.py * Update instagram_pic.py * Update instagram_pic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update instagram_pic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update instagram_pic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update instagram_pic.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fast fail instead of nested ifs and PEP8: Keep try/except blocks small * Update instagram_pic.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- web_programming/instagram_pic.py | 51 +++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/web_programming/instagram_pic.py b/web_programming/instagram_pic.py index 8521da674d7d..2630c8659232 100644 --- a/web_programming/instagram_pic.py +++ b/web_programming/instagram_pic.py @@ -3,14 +3,45 @@ 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 + +def download_image(url: str) -> str: + """ + Download an image from a given URL by scraping the 'og:image' meta tag. + + Parameters: + url: The URL to scrape. + + Returns: + A message indicating the result of the operation. + """ + try: + response = requests.get(url) + response.raise_for_status() + except requests.exceptions.RequestException as e: + return f"An error occurred during the HTTP request to {url}: {e!r}" + + soup = BeautifulSoup(response.text, "html.parser") + image_meta_tag = soup.find("meta", {"property": "og:image"}) + if not image_meta_tag: + return "No meta tag with property 'og:image' was found." + + image_url = image_meta_tag.get("content") + if not image_url: + return f"Image URL not found in meta tag {image_meta_tag}." + + try: + image_data = requests.get(image_url).content + except requests.exceptions.RequestException as e: + return f"An error occurred during the HTTP request to {image_url}: {e!r}" + if not image_data: + return f"Failed to download the image from {image_url}." + 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}.") + with open(file_name, "wb") as out_file: + out_file.write(image_data) + return f"Image downloaded and saved in the file {file_name}" + + +if __name__ == "__main__": + url = input("Enter image URL: ").strip() or "https://www.instagram.com/" + print(f"download_image({url}): {download_image(url)}") From 8217f9bd35e5975e3660217b37b2aac62c1280da Mon Sep 17 00:00:00 2001 From: Tapas Singhal <98687345+Shocker-lov-t@users.noreply.github.com> Date: Sun, 29 Oct 2023 15:55:39 +0530 Subject: [PATCH 2615/2908] Create find_previous_power_of_two.py (#11004) * Create find_previous_power_of_two.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update find_previous_power_of_two.py This change avoids the unnecessary left shift operation * Update find_previous_power_of_two.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../find_previous_power_of_two.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 bit_manipulation/find_previous_power_of_two.py diff --git a/bit_manipulation/find_previous_power_of_two.py b/bit_manipulation/find_previous_power_of_two.py new file mode 100644 index 000000000000..8ac74ac98478 --- /dev/null +++ b/bit_manipulation/find_previous_power_of_two.py @@ -0,0 +1,30 @@ +def find_previous_power_of_two(number: int) -> int: + """ + Find the largest power of two that is less than or equal to a given integer. + https://stackoverflow.com/questions/1322510 + + >>> [find_previous_power_of_two(i) for i in range(18)] + [0, 1, 2, 2, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16] + >>> find_previous_power_of_two(-5) + Traceback (most recent call last): + ... + ValueError: Input must be a non-negative integer + >>> find_previous_power_of_two(10.5) + Traceback (most recent call last): + ... + ValueError: Input must be a non-negative integer + """ + if not isinstance(number, int) or number < 0: + raise ValueError("Input must be a non-negative integer") + if number == 0: + return 0 + power = 1 + while power <= number: + power <<= 1 # Equivalent to multiplying by 2 + return power >> 1 if number > 1 else 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 403d3b8a29e754b8f3bbb1000a54fee42a87341b Mon Sep 17 00:00:00 2001 From: Aqib Javid Bhat Date: Sun, 29 Oct 2023 16:28:28 +0530 Subject: [PATCH 2616/2908] Add Integer Square Root Algorithm (#10949) * Add Integer Square Root Algorithm * Update integer_square_root.py * Update integer_square_root.py --------- Co-authored-by: Christian Clauss --- maths/integer_square_root.py | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 maths/integer_square_root.py diff --git a/maths/integer_square_root.py b/maths/integer_square_root.py new file mode 100644 index 000000000000..27e874a43c79 --- /dev/null +++ b/maths/integer_square_root.py @@ -0,0 +1,73 @@ +""" +Integer Square Root Algorithm -- An efficient method to calculate the square root of a +non-negative integer 'num' rounded down to the nearest integer. It uses a binary search +approach to find the integer square root without using any built-in exponent functions +or operators. +* https://en.wikipedia.org/wiki/Integer_square_root +* https://docs.python.org/3/library/math.html#math.isqrt +Note: + - This algorithm is designed for non-negative integers only. + - The result is rounded down to the nearest integer. + - The algorithm has a time complexity of O(log(x)). + - Original algorithm idea based on binary search. +""" + + +def integer_square_root(num: int) -> int: + """ + Returns the integer square root of a non-negative integer num. + Args: + num: A non-negative integer. + Returns: + The integer square root of num. + Raises: + ValueError: If num is not an integer or is negative. + >>> [integer_square_root(i) for i in range(18)] + [0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4] + >>> integer_square_root(625) + 25 + >>> integer_square_root(2_147_483_647) + 46340 + >>> from math import isqrt + >>> all(integer_square_root(i) == isqrt(i) for i in range(20)) + True + >>> integer_square_root(-1) + Traceback (most recent call last): + ... + ValueError: num must be non-negative integer + >>> integer_square_root(1.5) + Traceback (most recent call last): + ... + ValueError: num must be non-negative integer + >>> integer_square_root("0") + Traceback (most recent call last): + ... + ValueError: num must be non-negative integer + """ + if not isinstance(num, int) or num < 0: + raise ValueError("num must be non-negative integer") + + if num < 2: + return num + + left_bound = 0 + right_bound = num // 2 + + while left_bound <= right_bound: + mid = left_bound + (right_bound - left_bound) // 2 + mid_squared = mid * mid + if mid_squared == num: + return mid + + if mid_squared < num: + left_bound = mid + 1 + else: + right_bound = mid - 1 + + return right_bound + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From cc22d0b0bac9fec13913ba07bc67d58c06482c83 Mon Sep 17 00:00:00 2001 From: aayushsoni4 <120650736+aayushsoni4@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:25:31 +0530 Subject: [PATCH 2617/2908] Generate parentheses (#10903) * Add: Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Matrix Prefix Sum * Add: Distinct Subsequences * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Distinct Subsequences * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changes made in Distinct Subsequences * Changes made in Distinct Subsequences * Changes made in Distinct Subsequences * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed Distinct Subsequences * Add: Generate Parentheses * Add: Generate Parentheses * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add: Generate Parentheses * Add: Generate Parentheses * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add: Generate Parentheses * Add: Generate Parentheses * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update backtracking/generate_parentheses.py * Delete matrix/matrix_prefix_sum.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- backtracking/generate_parentheses.py | 77 ++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 backtracking/generate_parentheses.py diff --git a/backtracking/generate_parentheses.py b/backtracking/generate_parentheses.py new file mode 100644 index 000000000000..18c21e2a9b51 --- /dev/null +++ b/backtracking/generate_parentheses.py @@ -0,0 +1,77 @@ +""" +author: Aayush Soni +Given n pairs of parentheses, write a function to generate all +combinations of well-formed parentheses. +Input: n = 2 +Output: ["(())","()()"] +Leetcode link: https://leetcode.com/problems/generate-parentheses/description/ +""" + + +def backtrack( + partial: str, open_count: int, close_count: int, n: int, result: list[str] +) -> None: + """ + Generate valid combinations of balanced parentheses using recursion. + + :param partial: A string representing the current combination. + :param open_count: An integer representing the count of open parentheses. + :param close_count: An integer representing the count of close parentheses. + :param n: An integer representing the total number of pairs. + :param result: A list to store valid combinations. + :return: None + + This function uses recursion to explore all possible combinations, + ensuring that at each step, the parentheses remain balanced. + + Example: + >>> result = [] + >>> backtrack("", 0, 0, 2, result) + >>> result + ['(())', '()()'] + """ + if len(partial) == 2 * n: + # When the combination is complete, add it to the result. + result.append(partial) + return + + if open_count < n: + # If we can add an open parenthesis, do so, and recurse. + backtrack(partial + "(", open_count + 1, close_count, n, result) + + if close_count < open_count: + # If we can add a close parenthesis (it won't make the combination invalid), + # do so, and recurse. + backtrack(partial + ")", open_count, close_count + 1, n, result) + + +def generate_parenthesis(n: int) -> list[str]: + """ + Generate valid combinations of balanced parentheses for a given n. + + :param n: An integer representing the number of pairs of parentheses. + :return: A list of strings with valid combinations. + + This function uses a recursive approach to generate the combinations. + + Time Complexity: O(2^(2n)) - In the worst case, we have 2^(2n) combinations. + Space Complexity: O(n) - where 'n' is the number of pairs. + + Example 1: + >>> generate_parenthesis(3) + ['((()))', '(()())', '(())()', '()(())', '()()()'] + + Example 2: + >>> generate_parenthesis(1) + ['()'] + """ + + result: list[str] = [] + backtrack("", 0, 0, n, result) + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 7c1dfec08644e4034717844b139e8db948706ccc Mon Sep 17 00:00:00 2001 From: Farzad Hayat Date: Sun, 29 Oct 2023 22:57:04 +1000 Subject: [PATCH 2618/2908] XOR Cipher: doctests and bug fixes (#10840) * Fixed bug with key modulus wrapping. Should be wrapping on 256, not 255. * Fixed bug with incorrect assertion type in decrypt function. * Added doctests for 4 out of 6 methods --- ciphers/xor_cipher.py | 91 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 9 deletions(-) diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 559036d305c5..e30955d41ff1 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -35,6 +35,22 @@ def encrypt(self, content: str, key: int) -> list[str]: output: encrypted string 'content' as a list of chars if key not passed the method uses the key by the constructor. otherwise key = 1 + + Empty list + >>> XORCipher().encrypt("", 5) + [] + + One key + >>> XORCipher().encrypt("hallo welt", 1) + ['i', '`', 'm', 'm', 'n', '!', 'v', 'd', 'm', 'u'] + + Normal key + >>> XORCipher().encrypt("HALLO WELT", 32) + ['h', 'a', 'l', 'l', 'o', '\\x00', 'w', 'e', 'l', 't'] + + Key greater than 255 + >>> XORCipher().encrypt("hallo welt", 256) + ['h', 'a', 'l', 'l', 'o', ' ', 'w', 'e', 'l', 't'] """ # precondition @@ -44,7 +60,7 @@ def encrypt(self, content: str, key: int) -> list[str]: key = key or self.__key or 1 # make sure key is an appropriate size - key %= 255 + key %= 256 return [chr(ord(ch) ^ key) for ch in content] @@ -54,16 +70,32 @@ def decrypt(self, content: str, key: int) -> list[str]: output: decrypted string 'content' as a list of chars if key not passed the method uses the key by the constructor. otherwise key = 1 + + Empty list + >>> XORCipher().decrypt("", 5) + [] + + One key + >>> XORCipher().decrypt("hallo welt", 1) + ['i', '`', 'm', 'm', 'n', '!', 'v', 'd', 'm', 'u'] + + Normal key + >>> XORCipher().decrypt("HALLO WELT", 32) + ['h', 'a', 'l', 'l', 'o', '\\x00', 'w', 'e', 'l', 't'] + + Key greater than 255 + >>> XORCipher().decrypt("hallo welt", 256) + ['h', 'a', 'l', 'l', 'o', ' ', 'w', 'e', 'l', 't'] """ # precondition assert isinstance(key, int) - assert isinstance(content, list) + assert isinstance(content, str) key = key or self.__key or 1 # make sure key is an appropriate size - key %= 255 + key %= 256 return [chr(ord(ch) ^ key) for ch in content] @@ -73,6 +105,22 @@ def encrypt_string(self, content: str, key: int = 0) -> str: output: encrypted string 'content' if key not passed the method uses the key by the constructor. otherwise key = 1 + + Empty list + >>> XORCipher().encrypt_string("", 5) + '' + + One key + >>> XORCipher().encrypt_string("hallo welt", 1) + 'i`mmn!vdmu' + + Normal key + >>> XORCipher().encrypt_string("HALLO WELT", 32) + 'hallo\\x00welt' + + Key greater than 255 + >>> XORCipher().encrypt_string("hallo welt", 256) + 'hallo welt' """ # precondition @@ -81,9 +129,8 @@ def encrypt_string(self, content: str, key: int = 0) -> str: key = key or self.__key or 1 - # make sure key can be any size - while key > 255: - key -= 255 + # make sure key is an appropriate size + key %= 256 # This will be returned ans = "" @@ -99,6 +146,22 @@ def decrypt_string(self, content: str, key: int = 0) -> str: output: decrypted string 'content' if key not passed the method uses the key by the constructor. otherwise key = 1 + + Empty list + >>> XORCipher().decrypt_string("", 5) + '' + + One key + >>> XORCipher().decrypt_string("hallo welt", 1) + 'i`mmn!vdmu' + + Normal key + >>> XORCipher().decrypt_string("HALLO WELT", 32) + 'hallo\\x00welt' + + Key greater than 255 + >>> XORCipher().decrypt_string("hallo welt", 256) + 'hallo welt' """ # precondition @@ -107,9 +170,8 @@ def decrypt_string(self, content: str, key: int = 0) -> str: key = key or self.__key or 1 - # make sure key can be any size - while key > 255: - key -= 255 + # make sure key is an appropriate size + key %= 256 # This will be returned ans = "" @@ -132,6 +194,9 @@ def encrypt_file(self, file: str, key: int = 0) -> bool: assert isinstance(file, str) assert isinstance(key, int) + # make sure key is an appropriate size + key %= 256 + try: with open(file) as fin, open("encrypt.out", "w+") as fout: # actual encrypt-process @@ -156,6 +221,9 @@ def decrypt_file(self, file: str, key: int) -> bool: assert isinstance(file, str) assert isinstance(key, int) + # make sure key is an appropriate size + key %= 256 + try: with open(file) as fin, open("decrypt.out", "w+") as fout: # actual encrypt-process @@ -168,6 +236,11 @@ def decrypt_file(self, file: str, key: int) -> bool: return True +if __name__ == "__main__": + from doctest import testmod + + testmod() + # Tests # crypt = XORCipher() # key = 67 From 6b588e4d44085d8f2a60b023f09558442ea7ae91 Mon Sep 17 00:00:00 2001 From: Kento <75509362+nkstonks@users.noreply.github.com> Date: Sun, 29 Oct 2023 23:57:40 +1100 Subject: [PATCH 2619/2908] Added doctests for fibonacci.py (#10836) * added other possible cases * added test for correct output of truth table * few fibonacci tests added * updating DIRECTORY.md * Update nor_gate.py * updating DIRECTORY.md * Update fibonacci.py removed whitespace * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: = <=> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- maths/fibonacci.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/maths/fibonacci.py b/maths/fibonacci.py index e810add69dc7..8cdd6cdb160e 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -81,6 +81,18 @@ def fib_recursive(n: int) -> list[int]: def fib_recursive_term(i: int) -> int: """ Calculates the i-th (0-indexed) Fibonacci number using recursion + >>> fib_recursive_term(0) + 0 + >>> fib_recursive_term(1) + 1 + >>> fib_recursive_term(5) + 5 + >>> fib_recursive_term(10) + 55 + >>> fib_recursive_term(-1) + Traceback (most recent call last): + ... + Exception: n is negative """ if i < 0: raise Exception("n is negative") @@ -197,6 +209,10 @@ def fib_binet(n: int) -> list[int]: if __name__ == "__main__": + import doctest + + doctest.testmod() + num = 30 time_func(fib_iterative, num) time_func(fib_recursive, num) # Around 3s runtime From d59cf1734fd8216d90fa21ed579e18a41b63755f Mon Sep 17 00:00:00 2001 From: Arshdeep Singh Sachdeva Date: Sun, 29 Oct 2023 07:55:37 -0700 Subject: [PATCH 2620/2908] Add running key cipher (#10834) * Add running key cipher * update running key cipher add doctests and hints * Add test case * Update return value * range(len()) is almost always a hint to use enumerate() --------- Co-authored-by: Christian Clauss --- ciphers/running_key_cipher.py | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 ciphers/running_key_cipher.py diff --git a/ciphers/running_key_cipher.py b/ciphers/running_key_cipher.py new file mode 100644 index 000000000000..6bda417be898 --- /dev/null +++ b/ciphers/running_key_cipher.py @@ -0,0 +1,75 @@ +""" +https://en.wikipedia.org/wiki/Running_key_cipher +""" + + +def running_key_encrypt(key: str, plaintext: str) -> str: + """ + Encrypts the plaintext using the Running Key Cipher. + + :param key: The running key (long piece of text). + :param plaintext: The plaintext to be encrypted. + :return: The ciphertext. + """ + plaintext = plaintext.replace(" ", "").upper() + key = key.replace(" ", "").upper() + key_length = len(key) + ciphertext = [] + ord_a = ord("A") + + for i, char in enumerate(plaintext): + p = ord(char) - ord_a + k = ord(key[i % key_length]) - ord_a + c = (p + k) % 26 + ciphertext.append(chr(c + ord_a)) + + return "".join(ciphertext) + + +def running_key_decrypt(key: str, ciphertext: str) -> str: + """ + Decrypts the ciphertext using the Running Key Cipher. + + :param key: The running key (long piece of text). + :param ciphertext: The ciphertext to be decrypted. + :return: The plaintext. + """ + ciphertext = ciphertext.replace(" ", "").upper() + key = key.replace(" ", "").upper() + key_length = len(key) + plaintext = [] + ord_a = ord("A") + + for i, char in enumerate(ciphertext): + c = ord(char) - ord_a + k = ord(key[i % key_length]) - ord_a + p = (c - k) % 26 + plaintext.append(chr(p + ord_a)) + + return "".join(plaintext) + + +def test_running_key_encrypt() -> None: + """ + >>> key = "How does the duck know that? said Victor" + >>> ciphertext = running_key_encrypt(key, "DEFEND THIS") + >>> running_key_decrypt(key, ciphertext) == "DEFENDTHIS" + True + """ + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + test_running_key_encrypt() + + plaintext = input("Enter the plaintext: ").upper() + print(f"\n{plaintext = }") + + key = "How does the duck know that? said Victor" + encrypted_text = running_key_encrypt(key, plaintext) + print(f"{encrypted_text = }") + + decrypted_text = running_key_decrypt(key, encrypted_text) + print(f"{decrypted_text = }") From 3ad90cea831ee12d9c168735cbd6fab3acac446f Mon Sep 17 00:00:00 2001 From: dragon <51738561+08183080@users.noreply.github.com> Date: Sun, 29 Oct 2023 23:40:01 +0800 Subject: [PATCH 2621/2908] add a yield method to fibonaci (#10826) * add a yiled method to fibonaci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fibonaci * Update fibonacci.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update fibonacci.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/fibonacci.py | 79 ++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/maths/fibonacci.py b/maths/fibonacci.py index 8cdd6cdb160e..927700b0418e 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -1,4 +1,3 @@ -# fibonacci.py """ Calculates the Fibonacci sequence using iteration, recursion, memoization, and a simplified form of Binet's formula @@ -9,14 +8,12 @@ NOTE 2: the Binet's formula function is much more limited in the size of inputs that it can handle due to the size limitations of Python floats -RESULTS: (n = 20) -fib_iterative runtime: 0.0055 ms -fib_recursive runtime: 6.5627 ms -fib_memoization runtime: 0.0107 ms -fib_binet runtime: 0.0174 ms +See benchmark numbers in __main__ for performance comparisons/ +https://en.wikipedia.org/wiki/Fibonacci_number for more information """ import functools +from collections.abc import Iterator from math import sqrt from time import time @@ -35,6 +32,31 @@ def time_func(func, *args, **kwargs): return output +def fib_iterative_yield(n: int) -> Iterator[int]: + """ + Calculates the first n (1-indexed) Fibonacci numbers using iteration with yield + >>> list(fib_iterative_yield(0)) + [0] + >>> tuple(fib_iterative_yield(1)) + (0, 1) + >>> tuple(fib_iterative_yield(5)) + (0, 1, 1, 2, 3, 5) + >>> tuple(fib_iterative_yield(10)) + (0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55) + >>> tuple(fib_iterative_yield(-1)) + Traceback (most recent call last): + ... + ValueError: n is negative + """ + if n < 0: + raise ValueError("n is negative") + a, b = 0, 1 + yield a + for _ in range(n): + yield b + a, b = b, a + b + + def fib_iterative(n: int) -> list[int]: """ Calculates the first n (0-indexed) Fibonacci numbers using iteration @@ -49,10 +71,10 @@ def fib_iterative(n: int) -> list[int]: >>> fib_iterative(-1) Traceback (most recent call last): ... - Exception: n is negative + ValueError: n is negative """ if n < 0: - raise Exception("n is negative") + raise ValueError("n is negative") if n == 0: return [0] fib = [0, 1] @@ -75,7 +97,7 @@ def fib_recursive(n: int) -> list[int]: >>> fib_iterative(-1) Traceback (most recent call last): ... - Exception: n is negative + ValueError: n is negative """ def fib_recursive_term(i: int) -> int: @@ -95,13 +117,13 @@ def fib_recursive_term(i: int) -> int: Exception: n is negative """ if i < 0: - raise Exception("n is negative") + raise ValueError("n is negative") if i < 2: return i return fib_recursive_term(i - 1) + fib_recursive_term(i - 2) if n < 0: - raise Exception("n is negative") + raise ValueError("n is negative") return [fib_recursive_term(i) for i in range(n + 1)] @@ -119,7 +141,7 @@ def fib_recursive_cached(n: int) -> list[int]: >>> fib_iterative(-1) Traceback (most recent call last): ... - Exception: n is negative + ValueError: n is negative """ @functools.cache @@ -128,13 +150,13 @@ def fib_recursive_term(i: int) -> int: Calculates the i-th (0-indexed) Fibonacci number using recursion """ if i < 0: - raise Exception("n is negative") + raise ValueError("n is negative") if i < 2: return i return fib_recursive_term(i - 1) + fib_recursive_term(i - 2) if n < 0: - raise Exception("n is negative") + raise ValueError("n is negative") return [fib_recursive_term(i) for i in range(n + 1)] @@ -152,10 +174,10 @@ def fib_memoization(n: int) -> list[int]: >>> fib_iterative(-1) Traceback (most recent call last): ... - Exception: n is negative + ValueError: n is negative """ if n < 0: - raise Exception("n is negative") + raise ValueError("n is negative") # Cache must be outside recursuive function # other it will reset every time it calls itself. cache: dict[int, int] = {0: 0, 1: 1, 2: 1} # Prefilled cache @@ -193,29 +215,30 @@ def fib_binet(n: int) -> list[int]: >>> fib_binet(-1) Traceback (most recent call last): ... - Exception: n is negative + ValueError: n is negative >>> fib_binet(1475) Traceback (most recent call last): ... - Exception: n is too large + ValueError: n is too large """ if n < 0: - raise Exception("n is negative") + raise ValueError("n is negative") if n >= 1475: - raise Exception("n is too large") + raise ValueError("n is too large") sqrt_5 = sqrt(5) phi = (1 + sqrt_5) / 2 return [round(phi**i / sqrt_5) for i in range(n + 1)] if __name__ == "__main__": - import doctest - - doctest.testmod() + from doctest import testmod + testmod() + # Time on an M1 MacBook Pro -- Fastest to slowest num = 30 - time_func(fib_iterative, num) - time_func(fib_recursive, num) # Around 3s runtime - time_func(fib_recursive_cached, num) # Around 0ms runtime - time_func(fib_memoization, num) - time_func(fib_binet, num) + time_func(fib_iterative_yield, num) # 0.0012 ms + time_func(fib_iterative, num) # 0.0031 ms + time_func(fib_binet, num) # 0.0062 ms + time_func(fib_memoization, num) # 0.0100 ms + time_func(fib_recursive_cached, num) # 0.0153 ms + time_func(fib_recursive, num) # 257.0910 ms From 67c85ee289b66f9c8ac02c6732240965eec879a2 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Sun, 29 Oct 2023 21:31:54 +0530 Subject: [PATCH 2622/2908] Added doctest to hash_map.py (#11082) * Added doctest to heap.py * Added doctest to hash_map.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update hash_map.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/hashing/hash_map.py | 111 +++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/data_structures/hashing/hash_map.py b/data_structures/hashing/hash_map.py index 1dfcc8bbf906..1689e07afd9f 100644 --- a/data_structures/hashing/hash_map.py +++ b/data_structures/hashing/hash_map.py @@ -54,6 +54,14 @@ def _get_next_ind(self, ind: int) -> int: Get next index. Implements linear open addressing. + >>> HashMap(5)._get_next_ind(3) + 4 + >>> HashMap(5)._get_next_ind(5) + 1 + >>> HashMap(5)._get_next_ind(6) + 2 + >>> HashMap(5)._get_next_ind(9) + 0 """ return (ind + 1) % len(self._buckets) @@ -82,6 +90,14 @@ def _is_full(self) -> bool: Return true if we have reached safe capacity. So we need to increase the number of buckets to avoid collisions. + + >>> hm = HashMap(2) + >>> hm._add_item(1, 10) + >>> hm._add_item(2, 20) + >>> hm._is_full() + True + >>> HashMap(2)._is_full() + False """ limit = len(self._buckets) * self._capacity_factor return len(self) >= int(limit) @@ -114,17 +130,104 @@ def _iterate_buckets(self, key: KEY) -> Iterator[int]: ind = self._get_next_ind(ind) def _add_item(self, key: KEY, val: VAL) -> None: + """ + Try to add 3 elements when the size is 5 + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm._add_item(2, 20) + >>> hm._add_item(3, 30) + >>> hm + HashMap(1: 10, 2: 20, 3: 30) + + Try to add 3 elements when the size is 5 + >>> hm = HashMap(5) + >>> hm._add_item(-5, 10) + >>> hm._add_item(6, 30) + >>> hm._add_item(-7, 20) + >>> hm + HashMap(-5: 10, 6: 30, -7: 20) + + Try to add 3 elements when size is 1 + >>> hm = HashMap(1) + >>> hm._add_item(10, 13.2) + >>> hm._add_item(6, 5.26) + >>> hm._add_item(7, 5.155) + >>> hm + HashMap(10: 13.2) + + Trying to add an element with a key that is a floating point value + >>> hm = HashMap(5) + >>> hm._add_item(1.5, 10) + >>> hm + HashMap(1.5: 10) + + 5. Trying to add an item with the same key + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm._add_item(1, 20) + >>> hm + HashMap(1: 20) + """ for ind in self._iterate_buckets(key): if self._try_set(ind, key, val): break def __setitem__(self, key: KEY, val: VAL) -> None: + """ + 1. Changing value of item whose key is present + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm.__setitem__(1, 20) + >>> hm + HashMap(1: 20) + + 2. Changing value of item whose key is not present + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm.__setitem__(0, 20) + >>> hm + HashMap(0: 20, 1: 10) + + 3. Changing the value of the same item multiple times + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm.__setitem__(1, 20) + >>> hm.__setitem__(1, 30) + >>> hm + HashMap(1: 30) + """ if self._is_full(): self._size_up() self._add_item(key, val) def __delitem__(self, key: KEY) -> None: + """ + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm._add_item(2, 20) + >>> hm._add_item(3, 30) + >>> hm.__delitem__(3) + >>> hm + HashMap(1: 10, 2: 20) + >>> hm = HashMap(5) + >>> hm._add_item(-5, 10) + >>> hm._add_item(6, 30) + >>> hm._add_item(-7, 20) + >>> hm.__delitem__(-5) + >>> hm + HashMap(6: 30, -7: 20) + + # Trying to remove a non-existing item + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm._add_item(2, 20) + >>> hm._add_item(3, 30) + >>> hm.__delitem__(4) + Traceback (most recent call last): + ... + KeyError: 4 + """ for ind in self._iterate_buckets(key): item = self._buckets[ind] if item is None: @@ -156,7 +259,13 @@ def __iter__(self) -> Iterator[KEY]: yield from (item.key for item in self._buckets if item) def __repr__(self) -> str: - val_string = " ,".join( + val_string = ", ".join( f"{item.key}: {item.val}" for item in self._buckets if item ) return f"HashMap({val_string})" + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From be60f42a5fe29c0e83a049a803f17992bf66be47 Mon Sep 17 00:00:00 2001 From: Aqib Javid Bhat Date: Sun, 29 Oct 2023 22:12:41 +0530 Subject: [PATCH 2623/2908] Add Josephus Problem (#10928) * Add Josephus Problem * Add iterative implementation of Josephus Problem * Add descriptive variable names * Update maths/josephus_problem.py * Update josephus_problem.py --------- Co-authored-by: Christian Clauss --- maths/josephus_problem.py | 130 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 maths/josephus_problem.py diff --git a/maths/josephus_problem.py b/maths/josephus_problem.py new file mode 100644 index 000000000000..271292ba1d9f --- /dev/null +++ b/maths/josephus_problem.py @@ -0,0 +1,130 @@ +""" +The Josephus problem is a famous theoretical problem related to a certain +counting-out game. This module provides functions to solve the Josephus problem +for num_people and a step_size. + +The Josephus problem is defined as follows: +- num_people are standing in a circle. +- Starting with a specified person, you count around the circle, + skipping a fixed number of people (step_size). +- The person at which you stop counting is eliminated from the circle. +- The counting continues until only one person remains. + +For more information about the Josephus problem, refer to: +https://en.wikipedia.org/wiki/Josephus_problem +""" + + +def josephus_recursive(num_people: int, step_size: int) -> int: + """ + Solve the Josephus problem for num_people and a step_size recursively. + + Args: + num_people: A positive integer representing the number of people. + step_size: A positive integer representing the step size for elimination. + + Returns: + The position of the last person remaining. + + Raises: + ValueError: If num_people or step_size is not a positive integer. + + Examples: + >>> josephus_recursive(7, 3) + 3 + >>> josephus_recursive(10, 2) + 4 + >>> josephus_recursive(0, 2) + Traceback (most recent call last): + ... + ValueError: num_people or step_size is not a positive integer. + >>> josephus_recursive(1.9, 2) + Traceback (most recent call last): + ... + ValueError: num_people or step_size is not a positive integer. + >>> josephus_recursive(-2, 2) + Traceback (most recent call last): + ... + ValueError: num_people or step_size is not a positive integer. + >>> josephus_recursive(7, 0) + Traceback (most recent call last): + ... + ValueError: num_people or step_size is not a positive integer. + >>> josephus_recursive(7, -2) + Traceback (most recent call last): + ... + ValueError: num_people or step_size is not a positive integer. + >>> josephus_recursive(1_000, 0.01) + Traceback (most recent call last): + ... + ValueError: num_people or step_size is not a positive integer. + >>> josephus_recursive("cat", "dog") + Traceback (most recent call last): + ... + ValueError: num_people or step_size is not a positive integer. + """ + if ( + not isinstance(num_people, int) + or not isinstance(step_size, int) + or num_people <= 0 + or step_size <= 0 + ): + raise ValueError("num_people or step_size is not a positive integer.") + + if num_people == 1: + return 0 + + return (josephus_recursive(num_people - 1, step_size) + step_size) % num_people + + +def find_winner(num_people: int, step_size: int) -> int: + """ + Find the winner of the Josephus problem for num_people and a step_size. + + Args: + num_people (int): Number of people. + step_size (int): Step size for elimination. + + Returns: + int: The position of the last person remaining (1-based index). + + Examples: + >>> find_winner(7, 3) + 4 + >>> find_winner(10, 2) + 5 + """ + return josephus_recursive(num_people, step_size) + 1 + + +def josephus_iterative(num_people: int, step_size: int) -> int: + """ + Solve the Josephus problem for num_people and a step_size iteratively. + + Args: + num_people (int): The number of people in the circle. + step_size (int): The number of steps to take before eliminating someone. + + Returns: + int: The position of the last person standing. + + Examples: + >>> josephus_iterative(5, 2) + 3 + >>> josephus_iterative(7, 3) + 4 + """ + circle = list(range(1, num_people + 1)) + current = 0 + + while len(circle) > 1: + current = (current + step_size - 1) % len(circle) + circle.pop(current) + + return circle[0] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From eafdb8b86697eb9dbdc03916679719dff2f6425a Mon Sep 17 00:00:00 2001 From: dahhou ilyas <110790236+dahhou-ilyas@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:24:37 +0100 Subject: [PATCH 2624/2908] Dahhou ilyas (#10058) * add new programme in dynamique programming wildcard_matching * add new programme in dynamique programming wildcard_matching * fix bug * fix * fix * fix * fix * fix * fix error recrusion * fix error recrusion * bug fix * add doctest * The power of enumerate() --------- Co-authored-by: Christian Clauss --- dynamic_programming/wildcard_matching.py | 92 +++++++++++++----------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/dynamic_programming/wildcard_matching.py b/dynamic_programming/wildcard_matching.py index 4ffc4b5d46aa..d9a1392720bd 100644 --- a/dynamic_programming/wildcard_matching.py +++ b/dynamic_programming/wildcard_matching.py @@ -1,62 +1,68 @@ """ -Given two strings, an input string and a pattern, -this program checks if the input string matches the pattern. +Author : ilyas dahhou +Date : Oct 7, 2023 -Example : -input_string = "baaabab" -pattern = "*****ba*****ab" -Output: True +Task: +Given an input string and a pattern, implement wildcard pattern matching with support +for '?' and '*' where: +'?' matches any single character. +'*' matches any sequence of characters (including the empty sequence). +The matching should cover the entire input string (not partial). -This problem can be solved using the concept of "DYNAMIC PROGRAMMING". - -We create a 2D boolean matrix, where each entry match_matrix[i][j] is True -if the first i characters in input_string match the first j characters -of pattern. We initialize the first row and first column based on specific -rules, then fill up the rest of the matrix using a bottom-up dynamic -programming approach. - -The amount of match that will be determined is equal to match_matrix[n][m] -where n and m are lengths of the input_string and pattern respectively. +Runtime complexity: O(m * n) +The implementation was tested on the +leetcode: https://leetcode.com/problems/wildcard-matching/ """ -def is_pattern_match(input_string: str, pattern: str) -> bool: +def is_match(string: str, pattern: str) -> bool: """ - >>> is_pattern_match('baaabab','*****ba*****ba') + >>> is_match("", "") + True + >>> is_match("aa", "a") False - >>> is_pattern_match('baaabab','*****ba*****ab') + >>> is_match("abc", "abc") + True + >>> is_match("abc", "*c") + True + >>> is_match("abc", "a*") True - >>> is_pattern_match('aa','*') + >>> is_match("abc", "*a*") + True + >>> is_match("abc", "?b?") + True + >>> is_match("abc", "*?") + True + >>> is_match("abc", "a*d") + False + >>> is_match("abc", "a*c?") + False + >>> is_match('baaabab','*****ba*****ba') + False + >>> is_match('baaabab','*****ba*****ab') + True + >>> is_match('aa','*') True """ - - input_length = len(input_string) - pattern_length = len(pattern) - - match_matrix = [[False] * (pattern_length + 1) for _ in range(input_length + 1)] - - match_matrix[0][0] = True - - for j in range(1, pattern_length + 1): - if pattern[j - 1] == "*": - match_matrix[0][j] = match_matrix[0][j - 1] - - for i in range(1, input_length + 1): - for j in range(1, pattern_length + 1): - if pattern[j - 1] in ("?", input_string[i - 1]): - match_matrix[i][j] = match_matrix[i - 1][j - 1] + dp = [[False] * (len(pattern) + 1) for _ in string + "1"] + dp[0][0] = True + # Fill in the first row + for j, char in enumerate(pattern, 1): + if char == "*": + dp[0][j] = dp[0][j - 1] + # Fill in the rest of the DP table + for i, s_char in enumerate(string, 1): + for j, p_char in enumerate(pattern, 1): + if p_char in (s_char, "?"): + dp[i][j] = dp[i - 1][j - 1] elif pattern[j - 1] == "*": - match_matrix[i][j] = match_matrix[i - 1][j] or match_matrix[i][j - 1] - else: - match_matrix[i][j] = False - - return match_matrix[input_length][pattern_length] + dp[i][j] = dp[i - 1][j] or dp[i][j - 1] + return dp[len(string)][len(pattern)] if __name__ == "__main__": import doctest doctest.testmod() - - print(f"{is_pattern_match('baaabab','*****ba*****ab')}") + print(f"{is_match('baaabab','*****ba*****ab') = }") From 760d9bedc1a7ff06a75fafaeb519a5b1979a2885 Mon Sep 17 00:00:00 2001 From: Aryansh B Date: Mon, 30 Oct 2023 02:27:37 +0530 Subject: [PATCH 2625/2908] Added Fast Inverse Square Root (#11054) * Feat: Added Fast inverse square root * Fix: Added typehint * Fix: Added doctests that break the code, changed var name * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix: fixed length of docstring * Update fast_inverse_sqrt.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 10 +++++++ maths/fast_inverse_sqrt.py | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 maths/fast_inverse_sqrt.py diff --git a/DIRECTORY.md b/DIRECTORY.md index d108acf8dcfb..9b2c8ce735c3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -34,6 +34,7 @@ * [Bitwise Addition Recursive](bit_manipulation/bitwise_addition_recursive.py) * [Count 1S Brian Kernighan Method](bit_manipulation/count_1s_brian_kernighan_method.py) * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py) + * [Excess 3 Code](bit_manipulation/excess_3_code.py) * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py) * [Highest Set Bit](bit_manipulation/highest_set_bit.py) * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py) @@ -170,7 +171,10 @@ * Arrays * [Equilibrium Index In Array](data_structures/arrays/equilibrium_index_in_array.py) * [Find Triplets With 0 Sum](data_structures/arrays/find_triplets_with_0_sum.py) + * [Index 2D Array In 1D](data_structures/arrays/index_2d_array_in_1d.py) + * [Kth Largest Element](data_structures/arrays/kth_largest_element.py) * [Median Two Array](data_structures/arrays/median_two_array.py) + * [Monotonic Array](data_structures/arrays/monotonic_array.py) * [Pairs With Given Sum](data_structures/arrays/pairs_with_given_sum.py) * [Permutations](data_structures/arrays/permutations.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py) @@ -368,6 +372,7 @@ ## Electronics * [Apparent Power](electronics/apparent_power.py) * [Builtin Voltage](electronics/builtin_voltage.py) + * [Capacitor Equivalence](electronics/capacitor_equivalence.py) * [Carrier Concentration](electronics/carrier_concentration.py) * [Charging Capacitor](electronics/charging_capacitor.py) * [Charging Inductor](electronics/charging_inductor.py) @@ -531,12 +536,14 @@ ## Machine Learning * [Apriori Algorithm](machine_learning/apriori_algorithm.py) * [Astar](machine_learning/astar.py) + * [Automatic Differentiation](machine_learning/automatic_differentiation.py) * [Data Transformations](machine_learning/data_transformations.py) * [Decision Tree](machine_learning/decision_tree.py) * [Dimensionality Reduction](machine_learning/dimensionality_reduction.py) * Forecasting * [Run](machine_learning/forecasting/run.py) * [Frequent Pattern Growth](machine_learning/frequent_pattern_growth.py) + * [Gradient Boosting Classifier](machine_learning/gradient_boosting_classifier.py) * [Gradient Descent](machine_learning/gradient_descent.py) * [K Means Clust](machine_learning/k_means_clust.py) * [K Nearest Neighbours](machine_learning/k_nearest_neighbours.py) @@ -598,6 +605,7 @@ * [Extended Euclidean Algorithm](maths/extended_euclidean_algorithm.py) * [Factorial](maths/factorial.py) * [Factors](maths/factors.py) + * [Fast Inverse Sqrt](maths/fast_inverse_sqrt.py) * [Fermat Little Theorem](maths/fermat_little_theorem.py) * [Fibonacci](maths/fibonacci.py) * [Find Max](maths/find_max.py) @@ -648,6 +656,7 @@ * [Numerical Integration](maths/numerical_analysis/numerical_integration.py) * [Runge Kutta](maths/numerical_analysis/runge_kutta.py) * [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py) + * [Runge Kutta Gills](maths/numerical_analysis/runge_kutta_gills.py) * [Secant Method](maths/numerical_analysis/secant_method.py) * [Simpson Rule](maths/numerical_analysis/simpson_rule.py) * [Square Root](maths/numerical_analysis/square_root.py) @@ -814,6 +823,7 @@ * [Ideal Gas Law](physics/ideal_gas_law.py) * [In Static Equilibrium](physics/in_static_equilibrium.py) * [Kinetic Energy](physics/kinetic_energy.py) + * [Lens Formulae](physics/lens_formulae.py) * [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py) * [Malus Law](physics/malus_law.py) * [Mass Energy Equivalence](physics/mass_energy_equivalence.py) diff --git a/maths/fast_inverse_sqrt.py b/maths/fast_inverse_sqrt.py new file mode 100644 index 000000000000..79385bb84877 --- /dev/null +++ b/maths/fast_inverse_sqrt.py @@ -0,0 +1,54 @@ +""" +Fast inverse square root (1/sqrt(x)) using the Quake III algorithm. +Reference: https://en.wikipedia.org/wiki/Fast_inverse_square_root +Accuracy: https://en.wikipedia.org/wiki/Fast_inverse_square_root#Accuracy +""" + +import struct + + +def fast_inverse_sqrt(number: float) -> float: + """ + Compute the fast inverse square root of a floating-point number using the famous + Quake III algorithm. + + :param float number: Input number for which to calculate the inverse square root. + :return float: The fast inverse square root of the input number. + + Example: + >>> fast_inverse_sqrt(10) + 0.3156857923527257 + >>> fast_inverse_sqrt(4) + 0.49915357479239103 + >>> fast_inverse_sqrt(4.1) + 0.4932849504615651 + >>> fast_inverse_sqrt(0) + Traceback (most recent call last): + ... + ValueError: Input must be a positive number. + >>> fast_inverse_sqrt(-1) + Traceback (most recent call last): + ... + ValueError: Input must be a positive number. + >>> from math import isclose, sqrt + >>> all(isclose(fast_inverse_sqrt(i), 1 / sqrt(i), rel_tol=0.00132) + ... for i in range(50, 60)) + True + """ + if number <= 0: + raise ValueError("Input must be a positive number.") + i = struct.unpack(">i", struct.pack(">f", number))[0] + i = 0x5F3759DF - (i >> 1) + y = struct.unpack(">f", struct.pack(">i", i))[0] + return y * (1.5 - 0.5 * number * y * y) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + # https://en.wikipedia.org/wiki/Fast_inverse_square_root#Accuracy + from math import sqrt + + for i in range(5, 101, 5): + print(f"{i:>3}: {(1 / sqrt(i)) - fast_inverse_sqrt(i):.5f}") From c7a1331b34d6644f546f049058c1d9738fbc9b4c Mon Sep 17 00:00:00 2001 From: Khushi Shukla Date: Mon, 30 Oct 2023 02:50:57 +0530 Subject: [PATCH 2626/2908] Create karnaugh_map_simplification.py (#11056) * Create karnaugh_map_simplification.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update karnaugh_map_simplification.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update karnaugh_map_simplification.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update karnaugh_map_simplification.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update karnaugh_map_simplification.py * Update boolean_algebra/karnaugh_map_simplification.py Co-authored-by: Christian Clauss * Update karnaugh_map_simplification.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update karnaugh_map_simplification.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update karnaugh_map_simplification.py * Update karnaugh_map_simplification.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../karnaugh_map_simplification.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 boolean_algebra/karnaugh_map_simplification.py diff --git a/boolean_algebra/karnaugh_map_simplification.py b/boolean_algebra/karnaugh_map_simplification.py new file mode 100644 index 000000000000..c7f2d4c6b897 --- /dev/null +++ b/boolean_algebra/karnaugh_map_simplification.py @@ -0,0 +1,55 @@ +""" +https://en.wikipedia.org/wiki/Karnaugh_map +https://www.allaboutcircuits.com/technical-articles/karnaugh-map-boolean-algebraic-simplification-technique +""" + + +def simplify_kmap(kmap: list[list[int]]) -> str: + """ + Simplify the Karnaugh map. + >>> simplify_kmap(kmap=[[0, 1], [1, 1]]) + "A'B + AB' + AB" + >>> simplify_kmap(kmap=[[0, 0], [0, 0]]) + '' + >>> simplify_kmap(kmap=[[0, 1], [1, -1]]) + "A'B + AB' + AB" + >>> simplify_kmap(kmap=[[0, 1], [1, 2]]) + "A'B + AB' + AB" + >>> simplify_kmap(kmap=[[0, 1], [1, 1.1]]) + "A'B + AB' + AB" + >>> simplify_kmap(kmap=[[0, 1], [1, 'a']]) + "A'B + AB' + AB" + """ + simplified_f = [] + for a, row in enumerate(kmap): + for b, item in enumerate(row): + if item: + term = ("A" if a else "A'") + ("B" if b else "B'") + simplified_f.append(term) + return " + ".join(simplified_f) + + +def main() -> None: + """ + Main function to create and simplify a K-Map. + + >>> main() + [0, 1] + [1, 1] + Simplified Expression: + A'B + AB' + AB + """ + kmap = [[0, 1], [1, 1]] + + # Manually generate the product of [0, 1] and [0, 1] + + for row in kmap: + print(row) + + print("Simplified Expression:") + print(simplify_kmap(kmap)) + + +if __name__ == "__main__": + main() + print(f"{simplify_kmap(kmap=[[0, 1], [1, 1]]) = }") From 13e66c18d2738dd7a223c12ebbfc989faa4bcfce Mon Sep 17 00:00:00 2001 From: chien liu Date: Sun, 29 Oct 2023 22:22:19 +0100 Subject: [PATCH 2627/2908] Fix typo power_using_recursion.py (#11083) --- maths/power_using_recursion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/power_using_recursion.py b/maths/power_using_recursion.py index 462fc45bff64..29283ca0f67c 100644 --- a/maths/power_using_recursion.py +++ b/maths/power_using_recursion.py @@ -43,7 +43,7 @@ def power(base: int, exponent: int) -> float: if __name__ == "__main__": - from doctests import testmod + from doctest import testmod testmod() print("Raise base to the power of exponent using recursion...") From 2531f8e221f04014821e16eb5eb1d3c52e5f174c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Azevedo?= Date: Sun, 29 Oct 2023 18:43:32 -0300 Subject: [PATCH 2628/2908] test: adding more tests to missing number algorithm (#10394) * test: adding more tests to missing number algorithm * Update missing_number.py --------- Co-authored-by: Christian Clauss --- bit_manipulation/missing_number.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bit_manipulation/missing_number.py b/bit_manipulation/missing_number.py index 32b949daa717..554887b17562 100644 --- a/bit_manipulation/missing_number.py +++ b/bit_manipulation/missing_number.py @@ -11,6 +11,12 @@ def find_missing_number(nums: list[int]) -> int: Example: >>> find_missing_number([0, 1, 3, 4]) 2 + >>> find_missing_number([4, 3, 1, 0]) + 2 + >>> find_missing_number([-4, -3, -1, 0]) + -2 + >>> find_missing_number([-2, 2, 1, 3, 0]) + -1 >>> find_missing_number([1, 3, 4, 5, 6]) 2 >>> find_missing_number([6, 5, 4, 2, 1]) @@ -26,3 +32,9 @@ def find_missing_number(nums: list[int]) -> int: missing_number ^= i ^ nums[i - low] return missing_number + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c38b222212de921295440b2b1236376136f37136 Mon Sep 17 00:00:00 2001 From: dekomori_sanae09 Date: Mon, 30 Oct 2023 04:37:21 +0530 Subject: [PATCH 2629/2908] serialize deserialize binary tree (#9625) * added serialize and desrialize bin tree * format files * added type hints * added type hints * Use dataclass .__eq__(), .__iter__(), and .__repr__() --------- Co-authored-by: Christian Clauss --- .../serialize_deserialize_binary_tree.py | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 data_structures/binary_tree/serialize_deserialize_binary_tree.py diff --git a/data_structures/binary_tree/serialize_deserialize_binary_tree.py b/data_structures/binary_tree/serialize_deserialize_binary_tree.py new file mode 100644 index 000000000000..7d3e0c61f96d --- /dev/null +++ b/data_structures/binary_tree/serialize_deserialize_binary_tree.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass + + +@dataclass +class TreeNode: + """ + A binary tree node has a value, left child, and right child. + + Props: + value: The value of the node. + left: The left child of the node. + right: The right child of the node. + """ + + value: int = 0 + left: TreeNode | None = None + right: TreeNode | None = None + + def __post_init__(self): + if not isinstance(self.value, int): + raise TypeError("Value must be an integer.") + + def __iter__(self) -> Iterator[TreeNode]: + """ + Iterate through the tree in preorder. + + Returns: + An iterator of the tree nodes. + + >>> list(TreeNode(1)) + [1,null,null] + >>> tuple(TreeNode(1, TreeNode(2), TreeNode(3))) + (1,2,null,null,3,null,null, 2,null,null, 3,null,null) + """ + yield self + yield from self.left or () + yield from self.right or () + + def __len__(self) -> int: + """ + Count the number of nodes in the tree. + + Returns: + The number of nodes in the tree. + + >>> len(TreeNode(1)) + 1 + >>> len(TreeNode(1, TreeNode(2), TreeNode(3))) + 3 + """ + return sum(1 for _ in self) + + def __repr__(self) -> str: + """ + Represent the tree as a string. + + Returns: + A string representation of the tree. + + >>> repr(TreeNode(1)) + '1,null,null' + >>> repr(TreeNode(1, TreeNode(2), TreeNode(3))) + '1,2,null,null,3,null,null' + >>> repr(TreeNode(1, TreeNode(2), TreeNode(3, TreeNode(4), TreeNode(5)))) + '1,2,null,null,3,4,null,null,5,null,null' + """ + return f"{self.value},{self.left!r},{self.right!r}".replace("None", "null") + + @classmethod + def five_tree(cls) -> TreeNode: + """ + >>> repr(TreeNode.five_tree()) + '1,2,null,null,3,4,null,null,5,null,null' + """ + root = TreeNode(1) + root.left = TreeNode(2) + root.right = TreeNode(3) + root.right.left = TreeNode(4) + root.right.right = TreeNode(5) + return root + + +def deserialize(data: str) -> TreeNode | None: + """ + Deserialize a string to a binary tree. + + Args: + data(str): The serialized string. + + Returns: + The root of the binary tree. + + >>> root = TreeNode.five_tree() + >>> serialzed_data = repr(root) + >>> deserialized = deserialize(serialzed_data) + >>> root == deserialized + True + >>> root is deserialized # two separate trees + False + >>> root.right.right.value = 6 + >>> root == deserialized + False + >>> serialzed_data = repr(root) + >>> deserialized = deserialize(serialzed_data) + >>> root == deserialized + True + >>> deserialize("") + Traceback (most recent call last): + ... + ValueError: Data cannot be empty. + """ + + if not data: + raise ValueError("Data cannot be empty.") + + # Split the serialized string by a comma to get node values + nodes = data.split(",") + + def build_tree() -> TreeNode | None: + # Get the next value from the list + value = nodes.pop(0) + + if value == "null": + return None + + node = TreeNode(int(value)) + node.left = build_tree() # Recursively build left subtree + node.right = build_tree() # Recursively build right subtree + return node + + return build_tree() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ad9948d5d4d65d1457b58d278e780a1b9470a715 Mon Sep 17 00:00:00 2001 From: Mohammad Esfandiyar Date: Mon, 30 Oct 2023 16:50:47 +0330 Subject: [PATCH 2630/2908] implementation of Gaussian Elimination pivoting as a numerical linear algebra algorithm (#10457) * Adding new implementation Adding my python implementation of Gaussian Elimination pivoting as a numerical linear algebra algorithm * Delete linear_algebra/src/GaussianEliminationpivoting.py * Adding new implementation Adding my python implementation of Gaussian Elimination pivoting as a numerical linear algebra algorithm * Delete linear_algebra/src/gaussianeliminationpivoting.py * Adding new implementation Adding my python implementation of Gaussian Elimination pivoting as a numerical linear algebra algorithm for the third time because the last two times had conflict with the rules in PR * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Delete linear_algebra/src/gaussianeliminationpivoting.py * Adding gaussianeliminationpivoting.py Adding my python implementation of Gaussian Elimination pivoting as a numerical linear algebra algorithm for the fourth time because the last three times had conflict with the rules in PR and bots * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussianeliminationpivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussianeliminationpivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussianeliminationpivoting.py I changed a to matrix and coeff_matrix for better clarity * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussianeliminationpivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussianeliminationpivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussianeliminationpivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename gaussianeliminationpivoting.py to gaussian_elimination_pivoting.py renamed the file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Delete linear_algebra/src/gaussian_elimination_pivoting.py * Add files via upload * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Delete linear_algebra/src/gaussian_elimination_pivoting/text.py * Add files via upload * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py Co-authored-by: Christian Clauss * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * Update gaussian_elimination_pivoting.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../gaussian_elimination_pivoting.py | 101 ++++++++++++++++++ .../gaussian_elimination_pivoting/matrix.txt | 4 + 2 files changed, 105 insertions(+) create mode 100644 linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py create mode 100644 linear_algebra/src/gaussian_elimination_pivoting/matrix.txt diff --git a/linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py b/linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py new file mode 100644 index 000000000000..2a86350e9fc6 --- /dev/null +++ b/linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py @@ -0,0 +1,101 @@ +import numpy as np + +matrix = np.array( + [ + [5.0, -5.0, -3.0, 4.0, -11.0], + [1.0, -4.0, 6.0, -4.0, -10.0], + [-2.0, -5.0, 4.0, -5.0, -12.0], + [-3.0, -3.0, 5.0, -5.0, 8.0], + ], + dtype=float, +) + + +def solve_linear_system(matrix: np.ndarray) -> np.ndarray: + """ + Solve a linear system of equations using Gaussian elimination with partial pivoting + + Args: + - matrix: Coefficient matrix with the last column representing the constants. + + Returns: + - Solution vector. + + Raises: + - ValueError: If the matrix is not correct (i.e., singular). + + https://courses.engr.illinois.edu/cs357/su2013/lect.htm Lecture 7 + + Example: + >>> A = np.array([[2, 1, -1], [-3, -1, 2], [-2, 1, 2]], dtype=float) + >>> B = np.array([8, -11, -3], dtype=float) + >>> solution = solve_linear_system(np.column_stack((A, B))) + >>> np.allclose(solution, np.array([2., 3., -1.])) + True + >>> solve_linear_system(np.array([[0, 0], [0, 0]], dtype=float)) + array([nan, nan]) + """ + ab = np.copy(matrix) + num_of_rows = ab.shape[0] + num_of_columns = ab.shape[1] - 1 + x_lst: list[float] = [] + + # Lead element search + for column_num in range(num_of_rows): + for i in range(column_num, num_of_columns): + if abs(ab[i][column_num]) > abs(ab[column_num][column_num]): + ab[[column_num, i]] = ab[[i, column_num]] + if ab[column_num, column_num] == 0.0: + raise ValueError("Matrix is not correct") + else: + pass + if column_num != 0: + for i in range(column_num, num_of_rows): + ab[i, :] -= ( + ab[i, column_num - 1] + / ab[column_num - 1, column_num - 1] + * ab[column_num - 1, :] + ) + + # Upper triangular matrix + for column_num in range(num_of_rows): + for i in range(column_num, num_of_columns): + if abs(ab[i][column_num]) > abs(ab[column_num][column_num]): + ab[[column_num, i]] = ab[[i, column_num]] + if ab[column_num, column_num] == 0.0: + raise ValueError("Matrix is not correct") + else: + pass + if column_num != 0: + for i in range(column_num, num_of_rows): + ab[i, :] -= ( + ab[i, column_num - 1] + / ab[column_num - 1, column_num - 1] + * ab[column_num - 1, :] + ) + + # Find x vector (Back Substitution) + for column_num in range(num_of_rows - 1, -1, -1): + x = ab[column_num, -1] / ab[column_num, column_num] + x_lst.insert(0, x) + for i in range(column_num - 1, -1, -1): + ab[i, -1] -= ab[i, column_num] * x + + # Return the solution vector + return np.asarray(x_lst) + + +if __name__ == "__main__": + from doctest import testmod + from pathlib import Path + + testmod() + file_path = Path(__file__).parent / "matrix.txt" + try: + matrix = np.loadtxt(file_path) + except FileNotFoundError: + print(f"Error: {file_path} not found. Using default matrix instead.") + + # Example usage: + print(f"Matrix:\n{matrix}") + print(f"{solve_linear_system(matrix) = }") diff --git a/linear_algebra/src/gaussian_elimination_pivoting/matrix.txt b/linear_algebra/src/gaussian_elimination_pivoting/matrix.txt new file mode 100644 index 000000000000..dd895ad856ee --- /dev/null +++ b/linear_algebra/src/gaussian_elimination_pivoting/matrix.txt @@ -0,0 +1,4 @@ +5.0 -5.0 -3.0 4.0 -11.0 +1.0 -4.0 6.0 -4.0 -10.0 +-2.0 -5.0 4.0 -5.0 -12.0 +-3.0 -3.0 5.0 -5.0 8.0 \ No newline at end of file From ddd4023fe66cd4a0605d4f7de5ae85680ac94167 Mon Sep 17 00:00:00 2001 From: Devashri Deulkar <95555641+Devadeut@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:45:49 +0530 Subject: [PATCH 2631/2908] Happy number (new algorithm) (#10864) * Happy number (new algorithm) adding new algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/special_numbers/happy_number.py Co-authored-by: Christian Clauss * Update happy_number.py added new changes * Update happy_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update happy_number.py * Update happy_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update happy_number.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update happy_number.py added ValueError part in code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update happy_number.py modified and added raise Error code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update happy_number.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- maths/special_numbers/happy_number.py | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 maths/special_numbers/happy_number.py diff --git a/maths/special_numbers/happy_number.py b/maths/special_numbers/happy_number.py new file mode 100644 index 000000000000..eac3167e304b --- /dev/null +++ b/maths/special_numbers/happy_number.py @@ -0,0 +1,48 @@ +def is_happy_number(number: int) -> bool: + """ + A happy number is a number which eventually reaches 1 when replaced by the sum of + the square of each digit. + + :param number: The number to check for happiness. + :return: True if the number is a happy number, False otherwise. + + >>> is_happy_number(19) + True + >>> is_happy_number(2) + False + >>> is_happy_number(23) + True + >>> is_happy_number(1) + True + >>> is_happy_number(0) + Traceback (most recent call last): + ... + ValueError: number=0 must be a positive integer + >>> is_happy_number(-19) + Traceback (most recent call last): + ... + ValueError: number=-19 must be a positive integer + >>> is_happy_number(19.1) + Traceback (most recent call last): + ... + ValueError: number=19.1 must be a positive integer + >>> is_happy_number("happy") + Traceback (most recent call last): + ... + ValueError: number='happy' must be a positive integer + """ + if not isinstance(number, int) or number <= 0: + msg = f"{number=} must be a positive integer" + raise ValueError(msg) + + seen = set() + while number != 1 and number not in seen: + seen.add(number) + number = sum(int(digit) ** 2 for digit in str(number)) + return number == 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 79a327fc07388a093e132d9df94723f24c162315 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:17:00 +0100 Subject: [PATCH 2632/2908] [pre-commit.ci] pre-commit autoupdate (#11106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.1 → v0.1.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.1...v0.1.3) - [github.com/psf/black: 23.10.0 → 23.10.1](https://github.com/psf/black/compare/23.10.0...23.10.1) - [github.com/tox-dev/pyproject-fmt: 1.2.0 → 1.3.0](https://github.com/tox-dev/pyproject-fmt/compare/1.2.0...1.3.0) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0b9922fae7e..784993e6b00c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.1 + rev: v0.1.3 hooks: - id: ruff - repo: https://github.com/psf/black - rev: 23.10.0 + rev: 23.10.1 hooks: - id: black @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.2.0" + rev: "1.3.0" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index 9b2c8ce735c3..ee4a521f708b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -10,6 +10,8 @@ * [All Subsequences](backtracking/all_subsequences.py) * [Coloring](backtracking/coloring.py) * [Combination Sum](backtracking/combination_sum.py) + * [Crossword Puzzle Solver](backtracking/crossword_puzzle_solver.py) + * [Generate Parentheses](backtracking/generate_parentheses.py) * [Hamiltonian Cycle](backtracking/hamiltonian_cycle.py) * [Knight Tour](backtracking/knight_tour.py) * [Match Word Pattern](backtracking/match_word_pattern.py) @@ -35,6 +37,7 @@ * [Count 1S Brian Kernighan Method](bit_manipulation/count_1s_brian_kernighan_method.py) * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py) * [Excess 3 Code](bit_manipulation/excess_3_code.py) + * [Find Previous Power Of Two](bit_manipulation/find_previous_power_of_two.py) * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py) * [Highest Set Bit](bit_manipulation/highest_set_bit.py) * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py) @@ -54,6 +57,8 @@ ## Boolean Algebra * [And Gate](boolean_algebra/and_gate.py) * [Imply Gate](boolean_algebra/imply_gate.py) + * [Karnaugh Map Simplification](boolean_algebra/karnaugh_map_simplification.py) + * [Multiplexer](boolean_algebra/multiplexer.py) * [Nand Gate](boolean_algebra/nand_gate.py) * [Nimply Gate](boolean_algebra/nimply_gate.py) * [Nor Gate](boolean_algebra/nor_gate.py) @@ -108,6 +113,7 @@ * [Rsa Cipher](ciphers/rsa_cipher.py) * [Rsa Factorization](ciphers/rsa_factorization.py) * [Rsa Key Generator](ciphers/rsa_key_generator.py) + * [Running Key Cipher](ciphers/running_key_cipher.py) * [Shuffled Shift Cipher](ciphers/shuffled_shift_cipher.py) * [Simple Keyword Cypher](ciphers/simple_keyword_cypher.py) * [Simple Substitution Cipher](ciphers/simple_substitution_cipher.py) @@ -150,6 +156,7 @@ * [Excel Title To Column](conversions/excel_title_to_column.py) * [Hex To Bin](conversions/hex_to_bin.py) * [Hexadecimal To Decimal](conversions/hexadecimal_to_decimal.py) + * [Ipv4 Conversion](conversions/ipv4_conversion.py) * [Length Conversion](conversions/length_conversion.py) * [Molecular Chemistry](conversions/molecular_chemistry.py) * [Octal To Binary](conversions/octal_to_binary.py) @@ -209,6 +216,7 @@ * [Red Black Tree](data_structures/binary_tree/red_black_tree.py) * [Segment Tree](data_structures/binary_tree/segment_tree.py) * [Segment Tree Other](data_structures/binary_tree/segment_tree_other.py) + * [Serialize Deserialize Binary Tree](data_structures/binary_tree/serialize_deserialize_binary_tree.py) * [Symmetric Tree](data_structures/binary_tree/symmetric_tree.py) * [Treap](data_structures/binary_tree/treap.py) * [Wavelet Tree](data_structures/binary_tree/wavelet_tree.py) @@ -410,6 +418,9 @@ * [Mandelbrot](fractals/mandelbrot.py) * [Sierpinski Triangle](fractals/sierpinski_triangle.py) +## Fuzzy Logic + * [Fuzzy Operations](fuzzy_logic/fuzzy_operations.py) + ## Genetic Algorithm * [Basic String](genetic_algorithm/basic_string.py) @@ -521,6 +532,8 @@ * [Lu Decomposition](linear_algebra/lu_decomposition.py) * Src * [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py) + * Gaussian Elimination Pivoting + * [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py) * [Lib](linear_algebra/src/lib.py) * [Polynom For Points](linear_algebra/src/polynom_for_points.py) * [Power Iteration](linear_algebra/src/power_iteration.py) @@ -618,12 +631,14 @@ * [Germain Primes](maths/germain_primes.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) + * [Integer Square Root](maths/integer_square_root.py) * [Interquartile Range](maths/interquartile_range.py) * [Is Int Palindrome](maths/is_int_palindrome.py) * [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py) * [Is Square Free](maths/is_square_free.py) * [Jaccard Similarity](maths/jaccard_similarity.py) * [Joint Probability Distribution](maths/joint_probability_distribution.py) + * [Josephus Problem](maths/josephus_problem.py) * [Juggler Sequence](maths/juggler_sequence.py) * [Karatsuba](maths/karatsuba.py) * [Kth Lexicographic Permutation](maths/kth_lexicographic_permutation.py) @@ -646,6 +661,7 @@ * [Monte Carlo Dice](maths/monte_carlo_dice.py) * [Number Of Digits](maths/number_of_digits.py) * Numerical Analysis + * [Adams Bashforth](maths/numerical_analysis/adams_bashforth.py) * [Bisection](maths/numerical_analysis/bisection.py) * [Bisection 2](maths/numerical_analysis/bisection_2.py) * [Integration By Simpson Approx](maths/numerical_analysis/integration_by_simpson_approx.py) @@ -1223,6 +1239,7 @@ * [Anagrams](strings/anagrams.py) * [Autocomplete Using Trie](strings/autocomplete_using_trie.py) * [Barcode Validator](strings/barcode_validator.py) + * [Bitap String Match](strings/bitap_string_match.py) * [Boyer Moore Search](strings/boyer_moore_search.py) * [Camel Case To Snake Case](strings/camel_case_to_snake_case.py) * [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py) From b072ba657f045a899ad133006d54ce5c9035c7f4 Mon Sep 17 00:00:00 2001 From: Akshar Goyal Date: Mon, 30 Oct 2023 20:00:48 -0400 Subject: [PATCH 2633/2908] Added tests for validate_sudoku_board.py (#11108) --- matrix/validate_sudoku_board.py | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py index 0ee7b3df0b83..a7e08d169059 100644 --- a/matrix/validate_sudoku_board.py +++ b/matrix/validate_sudoku_board.py @@ -54,6 +54,66 @@ def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: ... ,[".",".",".",".","8",".",".","7","9"] ... ]) False + >>> is_valid_sudoku_board([ + ... ["1","2","3","4","5","6","7","8","9"] + ... ,["4","5","6","7","8","9","1","2","3"] + ... ,["7","8","9","1","2","3","4","5","6"] + ... ,[".",".",".",".",".",".",".",".","."] + ... ,[".",".",".",".",".",".",".",".","."] + ... ,[".",".",".",".",".",".",".",".","."] + ... ,[".",".",".",".",".",".",".",".","."] + ... ,[".",".",".",".",".",".",".",".","."] + ... ,[".",".",".",".",".",".",".",".","."] + ... ]) + True + >>> is_valid_sudoku_board([ + ... ["1","2","3",".",".",".",".",".","."] + ... ,["4","5","6",".",".",".",".",".","."] + ... ,["7","8","9",".",".",".",".",".","."] + ... ,[".",".",".","4","5","6",".",".","."] + ... ,[".",".",".","7","8","9",".",".","."] + ... ,[".",".",".","1","2","3",".",".","."] + ... ,[".",".",".",".",".",".","7","8","9"] + ... ,[".",".",".",".",".",".","1","2","3"] + ... ,[".",".",".",".",".",".","4","5","6"] + ... ]) + True + >>> is_valid_sudoku_board([ + ... ["1","2","3",".",".",".","5","6","4"] + ... ,["4","5","6",".",".",".","8","9","7"] + ... ,["7","8","9",".",".",".","2","3","1"] + ... ,[".",".",".","4","5","6",".",".","."] + ... ,[".",".",".","7","8","9",".",".","."] + ... ,[".",".",".","1","2","3",".",".","."] + ... ,["3","1","2",".",".",".","7","8","9"] + ... ,["6","4","5",".",".",".","1","2","3"] + ... ,["9","7","8",".",".",".","4","5","6"] + ... ]) + True + >>> is_valid_sudoku_board([ + ... ["1","2","3","4","5","6","7","8","9"] + ... ,["2",".",".",".",".",".",".",".","8"] + ... ,["3",".",".",".",".",".",".",".","7"] + ... ,["4",".",".",".",".",".",".",".","6"] + ... ,["5",".",".",".",".",".",".",".","5"] + ... ,["6",".",".",".",".",".",".",".","4"] + ... ,["7",".",".",".",".",".",".",".","3"] + ... ,["8",".",".",".",".",".",".",".","2"] + ... ,["9","8","7","6","5","4","3","2","1"] + ... ]) + False + >>> is_valid_sudoku_board([ + ... ["1","2","3","8","9","7","5","6","4"] + ... ,["4","5","6","2","3","1","8","9","7"] + ... ,["7","8","9","5","6","4","2","3","1"] + ... ,["2","3","1","4","5","6","9","7","8"] + ... ,["5","6","4","7","8","9","3","1","2"] + ... ,["8","9","7","1","2","3","6","4","5"] + ... ,["3","1","2","6","4","5","7","8","9"] + ... ,["6","4","5","9","7","8","1","2","3"] + ... ,["9","7","8","3","1","2","4","5","6"] + ... ]) + True >>> is_valid_sudoku_board([["1", "2", "3", "4", "5", "6", "7", "8", "9"]]) Traceback (most recent call last): ... From 99f3a0e4c9b1a6d9ff5bba2adf65d90d55f2250a Mon Sep 17 00:00:00 2001 From: Arya Hariharan <84255987+Arya-Hari@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:23:38 +0530 Subject: [PATCH 2634/2908] adding-docstrings (#11114) * adding-docstrings * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update intro_sort.py * Update intro_sort.py * Remove blank lines --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- sorts/intro_sort.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/sorts/intro_sort.py b/sorts/intro_sort.py index 908d2886533a..5a5741dc8375 100644 --- a/sorts/intro_sort.py +++ b/sorts/intro_sort.py @@ -11,6 +11,18 @@ 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] + >>> array = [21, 15, 11, 45, -2, -11, 46] + >>> insertion_sort(array, 0, len(array)) + [-11, -2, 11, 15, 21, 45, 46] + >>> array = [-2, 0, 89, 11, 48, 79, 12] + >>> insertion_sort(array, 0, len(array)) + [-2, 0, 11, 12, 48, 79, 89] + >>> array = ['a', 'z', 'd', 'p', 'v', 'l', 'o', 'o'] + >>> insertion_sort(array, 0, len(array)) + ['a', 'd', 'l', 'o', 'o', 'p', 'v', 'z'] + >>> array = [73.568, 73.56, -45.03, 1.7, 0, 89.45] + >>> insertion_sort(array, 0, len(array)) + [-45.03, 0, 1.7, 73.56, 73.568, 89.45] """ end = end or len(array) for i in range(start, end): @@ -47,6 +59,12 @@ def heap_sort(array: list) -> list: """ >>> heap_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] + >>> heap_sort([-2, -11, 0, 0, 0, 87, 45, -69, 78, 12, 10, 103, 89, 52]) + [-69, -11, -2, 0, 0, 0, 10, 12, 45, 52, 78, 87, 89, 103] + >>> heap_sort(['b', 'd', 'e', 'f', 'g', 'p', 'x', 'z', 'b', 's', 'e', 'u', 'v']) + ['b', 'b', 'd', 'e', 'e', 'f', 'g', 'p', 's', 'u', 'v', 'x', 'z'] + >>> heap_sort([6.2, -45.54, 8465.20, 758.56, -457.0, 0, 1, 2.879, 1.7, 11.7]) + [-457.0, -45.54, 0, 1, 1.7, 2.879, 6.2, 11.7, 758.56, 8465.2] """ n = len(array) @@ -91,6 +109,15 @@ 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 + >>> array = [21, 15, 11, 45, -2, -11, 46] + >>> partition(array, 0, len(array), 15) + 3 + >>> array = ['a', 'z', 'd', 'p', 'v', 'l', 'o', 'o'] + >>> partition(array, 0, len(array), 'p') + 5 + >>> array = [6.2, -45.54, 8465.20, 758.56, -457.0, 0, 1, 2.879, 1.7, 11.7] + >>> partition(array, 0, len(array), 2.879) + 6 """ i = low j = high From ebfdb127e76e76c122d3110155abf644474b9fa9 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Sat, 4 Nov 2023 17:34:57 +0530 Subject: [PATCH 2635/2908] Added doctest to hash_map.py (#11105) * Added doctest to heap.py * Added doctest to hash_map.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update hash_map.py * Added doctest to hash_map.py * Added doctest to hash_map.py * Added doctest to detecting_english_programmatically.py * Update detecting_english_programmatically.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/hashing/hash_map.py | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/data_structures/hashing/hash_map.py b/data_structures/hashing/hash_map.py index 1689e07afd9f..6a6f8e54d5e9 100644 --- a/data_structures/hashing/hash_map.py +++ b/data_structures/hashing/hash_map.py @@ -242,6 +242,25 @@ def __delitem__(self, key: KEY) -> None: self._size_down() def __getitem__(self, key: KEY) -> VAL: + """ + Returns the item at the given key + + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm.__getitem__(1) + 10 + + >>> hm = HashMap(5) + >>> hm._add_item(10, -10) + >>> hm._add_item(20, -20) + >>> hm.__getitem__(20) + -20 + + >>> hm = HashMap(5) + >>> hm._add_item(-1, 10) + >>> hm.__getitem__(-1) + 10 + """ for ind in self._iterate_buckets(key): item = self._buckets[ind] if item is None: @@ -253,6 +272,20 @@ def __getitem__(self, key: KEY) -> VAL: raise KeyError(key) def __len__(self) -> int: + """ + Returns the number of items present in hashmap + + >>> hm = HashMap(5) + >>> hm._add_item(1, 10) + >>> hm._add_item(2, 20) + >>> hm._add_item(3, 30) + >>> hm.__len__() + 3 + + >>> hm = HashMap(5) + >>> hm.__len__() + 0 + """ return self._len def __iter__(self) -> Iterator[KEY]: From 257cfbdf6e2a55d48727f533ef15295065e0057b Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Sun, 5 Nov 2023 13:46:00 +0530 Subject: [PATCH 2636/2908] Added doctest to decision_tree.py (#11143) * Added doctest to decision_tree.py * Update decision_tree.py * Update machine_learning/decision_tree.py * Update machine_learning/decision_tree.py * raise ValueError() * Update decision_tree.py --------- Co-authored-by: Christian Clauss --- machine_learning/decision_tree.py | 49 ++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index 7cd1b02c4181..c67e09c7f114 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -18,7 +18,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 @@ -44,26 +44,47 @@ def mean_squared_error(self, labels, prediction): def train(self, x, y): """ train: - @param x: a one dimensional numpy array - @param y: a one dimensional numpy array. + @param x: 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 - """ - - """ - this section is to check that the inputs conform to our dimensionality + train() does not have a return value + + Examples: + 1. Try to train when x & y are of same length & 1 dimensions (No errors) + >>> dt = DecisionTree() + >>> dt.train(np.array([10,20,30,40,50]),np.array([0,0,0,1,1])) + + 2. Try to train when x is 2 dimensions + >>> dt = DecisionTree() + >>> dt.train(np.array([[1,2,3,4,5],[1,2,3,4,5]]),np.array([0,0,0,1,1])) + Traceback (most recent call last): + ... + ValueError: Input data set must be one-dimensional + + 3. Try to train when x and y are not of the same length + >>> dt = DecisionTree() + >>> dt.train(np.array([1,2,3,4,5]),np.array([[0,0,0,1,1],[0,0,0,1,1]])) + Traceback (most recent call last): + ... + ValueError: x and y have different lengths + + 4. Try to train when x & y are of the same length but different dimensions + >>> dt = DecisionTree() + >>> dt.train(np.array([1,2,3,4,5]),np.array([[1],[2],[3],[4],[5]])) + Traceback (most recent call last): + ... + ValueError: Data set labels must be one-dimensional + + 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") - return + raise ValueError("Input data set must be one-dimensional") if len(x) != len(y): - print("Error: X and y have different lengths") - return + raise ValueError("x and y have different lengths") if y.ndim != 1: - print("Error: Data set labels must be one dimensional") - return + raise ValueError("Data set labels must be one-dimensional") if len(x) < 2 * self.min_leaf_size: self.prediction = np.mean(y) From 1e50cf366022a5c44abfa5adf5e01bef62524cc3 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Sun, 5 Nov 2023 14:08:39 +0530 Subject: [PATCH 2637/2908] Added doctest to binary_search_tree.py (#11141) * Added doctest to binary_search_tree.py * Update binary_search_tree.py * Update binary_search_tree.py --------- Co-authored-by: Christian Clauss --- .../binary_tree/binary_search_tree.py | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 38691c4755c9..f08f278a8e47 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -10,8 +10,7 @@ / \ / 4 7 13 ->>> t = BinarySearchTree() ->>> t.insert(8, 3, 6, 1, 10, 14, 13, 4, 7) +>>> 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 @@ -40,7 +39,16 @@ >>> testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7) >>> t = BinarySearchTree() >>> for i in testlist: -... t.insert(i) +... t.insert(i) # doctest: +ELLIPSIS +BinarySearchTree(root=8) +BinarySearchTree(root={'8': (3, None)}) +BinarySearchTree(root={'8': ({'3': (None, 6)}, None)}) +BinarySearchTree(root={'8': ({'3': (1, 6)}, None)}) +BinarySearchTree(root={'8': ({'3': (1, 6)}, 10)}) +BinarySearchTree(root={'8': ({'3': (1, 6)}, {'10': (None, 14)})}) +BinarySearchTree(root={'8': ({'3': (1, 6)}, {'10': (None, {'14': (13, None)})})}) +BinarySearchTree(root={'8': ({'3': (1, {'6': (4, None)})}, {'10': (None, {'14': ... +BinarySearchTree(root={'8': ({'3': (1, {'6': (4, 7)})}, {'10': (None, {'14': (13, ... Prints all the elements of the list in order traversal >>> print(t) @@ -84,7 +92,7 @@ from collections.abc import Iterable, Iterator from dataclasses import dataclass -from typing import Any +from typing import Any, Self @dataclass @@ -145,7 +153,18 @@ def __reassign_nodes(self, node: Node, new_children: Node | None) -> None: self.root = new_children def empty(self) -> bool: - return self.root is None + """ + Returns True if the tree does not have any element(s). + False if the tree has element(s). + + >>> BinarySearchTree().empty() + True + >>> BinarySearchTree().insert(1).empty() + False + >>> BinarySearchTree().insert(8, 3, 6, 1, 10, 14, 13, 4, 7).empty() + False + """ + return not self.root def __insert(self, value) -> None: """ @@ -173,9 +192,10 @@ def __insert(self, value) -> None: parent_node = parent_node.right new_node.parent = parent_node - def insert(self, *values) -> None: + def insert(self, *values) -> Self: for value in values: self.__insert(value) + return self def search(self, value) -> Node | None: if self.empty(): From e48ea7d39643f3c15f830ccf63a363378858a001 Mon Sep 17 00:00:00 2001 From: SEIKH NABAB UDDIN <93948993+nababuddin@users.noreply.github.com> Date: Sun, 5 Nov 2023 14:13:52 +0530 Subject: [PATCH 2638/2908] Create get_ip_geolocation.py (#10902) * Create get_ip_geolocation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update get_ip_geolocation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update get_ip_geolocation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- web_programming/get_ip_geolocation.py | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 web_programming/get_ip_geolocation.py diff --git a/web_programming/get_ip_geolocation.py b/web_programming/get_ip_geolocation.py new file mode 100644 index 000000000000..62eaeafceb7e --- /dev/null +++ b/web_programming/get_ip_geolocation.py @@ -0,0 +1,40 @@ +import requests + + +# Function to get geolocation data for an IP address +def get_ip_geolocation(ip_address: str) -> str: + try: + # Construct the URL for the IP geolocation API + url = f"https://ipinfo.io/%7Bip_address%7D/json" + + # Send a GET request to the API + response = requests.get(url) + + # Check if the HTTP request was successful + response.raise_for_status() + + # Parse the response as JSON + data = response.json() + + # Check if city, region, and country information is available + if "city" in data and "region" in data and "country" in data: + location = f"Location: {data['city']}, {data['region']}, {data['country']}" + else: + location = "Location data not found." + + return location + except requests.exceptions.RequestException as e: + # Handle network-related exceptions + return f"Request error: {e}" + except ValueError as e: + # Handle JSON parsing errors + return f"JSON parsing error: {e}" + + +if __name__ == "__main__": + # Prompt the user to enter an IP address + ip_address = input("Enter an IP address: ") + + # Get the geolocation data and print it + location = get_ip_geolocation(ip_address) + print(location) From eb989c08cdbf82e1a4db6481371f3e9ccb3bcf99 Mon Sep 17 00:00:00 2001 From: Sunny Kumar <37464973+Skyad@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:40:50 +0530 Subject: [PATCH 2639/2908] Data structures/arrays/triplet sum (#11134) * updated code for find triplets with 0 sum Signed-off-by: Skyad <777.sunnykumar@gmail.com> * extra line added at the end of file Signed-off-by: Sunny Kumar * extra line added at the end of file Signed-off-by: Skyad <777.sunnykumar@gmail.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * file updated with comments Signed-off-by: Skyad <777.sunnykumar@gmail.com> * updated the comments as suggested by community Signed-off-by: Sunny Kumar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * file updated according to community comments Signed-off-by: Skyad <777.sunnykumar@gmail.com> * Update find_triplets_with_0_sum.py --------- Signed-off-by: Skyad <777.sunnykumar@gmail.com> Signed-off-by: Sunny Kumar Co-authored-by: Sunny Kumar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../arrays/find_triplets_with_0_sum.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/data_structures/arrays/find_triplets_with_0_sum.py b/data_structures/arrays/find_triplets_with_0_sum.py index 8217ff857e3d..52e521906873 100644 --- a/data_structures/arrays/find_triplets_with_0_sum.py +++ b/data_structures/arrays/find_triplets_with_0_sum.py @@ -22,3 +22,66 @@ def find_triplets_with_0_sum(nums: list[int]) -> list[list[int]]: list(x) for x in sorted({abc for abc in combinations(sorted(nums), 3) if not sum(abc)}) ] + + +def find_triplets_with_0_sum_hashing(arr: list[int]) -> list[list[int]]: + """ + Function for finding the triplets with a given sum in the array using hashing. + + Given a list of integers, return elements a, b, c such that a + b + c = 0. + + Args: + nums: list of integers + Returns: + list of lists of integers where sum(each_list) == 0 + Examples: + >>> find_triplets_with_0_sum_hashing([-1, 0, 1, 2, -1, -4]) + [[-1, 0, 1], [-1, -1, 2]] + >>> find_triplets_with_0_sum_hashing([]) + [] + >>> find_triplets_with_0_sum_hashing([0, 0, 0]) + [[0, 0, 0]] + >>> find_triplets_with_0_sum_hashing([1, 2, 3, 0, -1, -2, -3]) + [[-1, 0, 1], [-3, 1, 2], [-2, 0, 2], [-2, -1, 3], [-3, 0, 3]] + + Time complexity: O(N^2) + Auxiliary Space: O(N) + + """ + target_sum = 0 + + # Initialize the final output array with blank. + output_arr = [] + + # Set the initial element as arr[i]. + for index, item in enumerate(arr[:-2]): + # to store second elements that can complement the final sum. + set_initialize = set() + + # current sum needed for reaching the target sum + current_sum = target_sum - item + + # Traverse the subarray arr[i+1:]. + for other_item in arr[index + 1 :]: + # required value for the second element + required_value = current_sum - other_item + + # Verify if the desired value exists in the set. + if required_value in set_initialize: + # finding triplet elements combination. + combination_array = sorted([item, other_item, required_value]) + if combination_array not in output_arr: + output_arr.append(combination_array) + + # Include the current element in the set + # for subsequent complement verification. + set_initialize.add(other_item) + + # Return all the triplet combinations. + return output_arr + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From fa508d7b8bf9696805e97deac71e657256500ab7 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:44:39 +0530 Subject: [PATCH 2640/2908] Added doctest to detecting_english_programmatically.py (#11135) --- strings/detecting_english_programmatically.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/strings/detecting_english_programmatically.py b/strings/detecting_english_programmatically.py index b9000101beb4..e30e2ea8dd8b 100644 --- a/strings/detecting_english_programmatically.py +++ b/strings/detecting_english_programmatically.py @@ -25,6 +25,18 @@ def get_english_count(message: str) -> float: def remove_non_letters(message: str) -> str: + """ + >>> remove_non_letters("Hi! how are you?") + 'Hi how are you' + >>> remove_non_letters("P^y%t)h@o*n") + 'Python' + >>> remove_non_letters("1+1=2") + '' + >>> remove_non_letters("www.google.com/") + 'wwwgooglecom' + >>> remove_non_letters("") + '' + """ return "".join(symbol for symbol in message if symbol in LETTERS_AND_SPACE) From 12e401650c8afd4b6cf69ddab09a882d1eb6ff5c Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:48:41 +0530 Subject: [PATCH 2641/2908] Added doctest to string_switch_case.py (#11136) * Added doctest to string_switch_case.py * Update string_switch_case.py --- strings/string_switch_case.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/strings/string_switch_case.py b/strings/string_switch_case.py index 9a07472dfd71..c16d9fa552f9 100644 --- a/strings/string_switch_case.py +++ b/strings/string_switch_case.py @@ -28,6 +28,12 @@ def to_simple_case(str_: str) -> str: """ >>> to_simple_case("one two 31235three4four") 'OneTwo31235three4four' + >>> to_simple_case("This should be combined") + 'ThisShouldBeCombined' + >>> to_simple_case("The first letters are capitalized, then string is merged") + 'TheFirstLettersAreCapitalizedThenStringIsMerged' + >>> to_simple_case("special characters :, ', %, ^, $, are ignored") + 'SpecialCharactersAreIgnored' """ string_split = split_input(str_) return "".join( @@ -37,6 +43,14 @@ def to_simple_case(str_: str) -> str: def to_complex_case(text: str, upper: bool, separator: str) -> str: """ + Returns the string concatenated with the delimiter we provide. + + Parameters: + @text: The string on which we want to perform operation + @upper: Boolean value to determine whether we want capitalized result or not + @separator: The delimiter with which we want to concatenate words + + Examples: >>> to_complex_case("one two 31235three4four", True, "_") 'ONE_TWO_31235THREE4FOUR' >>> to_complex_case("one two 31235three4four", False, "-") From a13e9c21374caf40652ee75cc3620f3ac0c72ff3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 06:49:09 +0600 Subject: [PATCH 2642/2908] [pre-commit.ci] pre-commit autoupdate (#11146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.3 → v0.1.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.3...v0.1.4) - [github.com/tox-dev/pyproject-fmt: 1.3.0 → 1.4.1](https://github.com/tox-dev/pyproject-fmt/compare/1.3.0...1.4.1) * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 2 ++ pyproject.toml | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 784993e6b00c..1bb3de782275 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.3 + rev: v0.1.4 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.3.0" + rev: "1.4.1" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index ee4a521f708b..cb4b00b045b5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -725,6 +725,7 @@ * [Carmichael Number](maths/special_numbers/carmichael_number.py) * [Catalan Number](maths/special_numbers/catalan_number.py) * [Hamming Numbers](maths/special_numbers/hamming_numbers.py) + * [Happy Number](maths/special_numbers/happy_number.py) * [Harshad Numbers](maths/special_numbers/harshad_numbers.py) * [Hexagonal Number](maths/special_numbers/hexagonal_number.py) * [Krishnamurthy Number](maths/special_numbers/krishnamurthy_number.py) @@ -1310,6 +1311,7 @@ * [Get Amazon Product Data](web_programming/get_amazon_product_data.py) * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](web_programming/get_imdbtop.py) + * [Get Ip Geolocation](web_programming/get_ip_geolocation.py) * [Get Top Billionaires](web_programming/get_top_billionaires.py) * [Get Top Hn Posts](web_programming/get_top_hn_posts.py) * [Get User Tweets](web_programming/get_user_tweets.py) diff --git a/pyproject.toml b/pyproject.toml index 5d27142d16e2..c7163dc78371 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,6 +117,10 @@ max-branches = 20 # default: 12 max-returns = 8 # default: 6 max-statements = 88 # default: 50 +[tool.codespell] +ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" +skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" + [tool.pytest.ini_options] markers = [ "mat_ops: mark a test as utilizing matrix operations.", @@ -133,7 +137,3 @@ omit = [ "project_euler/*" ] sort = "Cover" - -[tool.codespell] -ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" -skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" From 8b7352626e54b619113b771a7e9586aabe603fa7 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Sun, 12 Nov 2023 07:43:04 +0530 Subject: [PATCH 2643/2908] Added doctest to randomized_heap.py (#11151) --- data_structures/heap/randomized_heap.py | 30 +++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/data_structures/heap/randomized_heap.py b/data_structures/heap/randomized_heap.py index c0f9888f80c7..12888c1f4089 100644 --- a/data_structures/heap/randomized_heap.py +++ b/data_structures/heap/randomized_heap.py @@ -22,14 +22,40 @@ def __init__(self, value: T) -> None: @property def value(self) -> T: - """Return the value of the node.""" + """ + Return the value of the node. + + >>> rhn = RandomizedHeapNode(10) + >>> rhn.value + 10 + >>> rhn = RandomizedHeapNode(-10) + >>> rhn.value + -10 + """ return self._value @staticmethod def merge( root1: RandomizedHeapNode[T] | None, root2: RandomizedHeapNode[T] | None ) -> RandomizedHeapNode[T] | None: - """Merge 2 nodes together.""" + """ + Merge 2 nodes together. + + >>> rhn1 = RandomizedHeapNode(10) + >>> rhn2 = RandomizedHeapNode(20) + >>> RandomizedHeapNode.merge(rhn1, rhn2).value + 10 + + >>> rhn1 = RandomizedHeapNode(20) + >>> rhn2 = RandomizedHeapNode(10) + >>> RandomizedHeapNode.merge(rhn1, rhn2).value + 10 + + >>> rhn1 = RandomizedHeapNode(5) + >>> rhn2 = RandomizedHeapNode(0) + >>> RandomizedHeapNode.merge(rhn1, rhn2).value + 0 + """ if not root1: return root2 From fb17eeab7d1fbc608a538b6d154d2c08781e087d Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Sun, 12 Nov 2023 07:46:43 +0530 Subject: [PATCH 2644/2908] Added doctest to stack.py (#11149) --- data_structures/stacks/stack.py | 92 +++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index a14f4648a399..93698f5aa116 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -33,7 +33,23 @@ def __str__(self) -> str: return str(self.stack) def push(self, data: T) -> None: - """Push an element to the top of the stack.""" + """ + Push an element to the top of the stack. + + >>> S = Stack(2) # stack size = 2 + >>> S.push(10) + >>> S.push(20) + >>> print(S) + [10, 20] + + >>> S = Stack(1) # stack size = 1 + >>> S.push(10) + >>> S.push(20) + Traceback (most recent call last): + ... + data_structures.stacks.stack.StackOverflowError + + """ if len(self.stack) >= self.limit: raise StackOverflowError self.stack.append(data) @@ -42,6 +58,12 @@ def pop(self) -> T: """ Pop an element off of the top of the stack. + >>> S = Stack() + >>> S.push(-5) + >>> S.push(10) + >>> S.pop() + 10 + >>> Stack().pop() Traceback (most recent call last): ... @@ -55,7 +77,13 @@ def peek(self) -> T: """ Peek at the top-most element of the stack. - >>> Stack().pop() + >>> S = Stack() + >>> S.push(-5) + >>> S.push(10) + >>> S.peek() + 10 + + >>> Stack().peek() Traceback (most recent call last): ... data_structures.stacks.stack.StackUnderflowError @@ -65,18 +93,68 @@ def peek(self) -> T: return self.stack[-1] def is_empty(self) -> bool: - """Check if a stack is empty.""" + """ + Check if a stack is empty. + + >>> S = Stack() + >>> S.is_empty() + True + + >>> S = Stack() + >>> S.push(10) + >>> S.is_empty() + False + """ return not bool(self.stack) def is_full(self) -> bool: + """ + >>> S = Stack() + >>> S.is_full() + False + + >>> S = Stack(1) + >>> S.push(10) + >>> S.is_full() + True + """ return self.size() == self.limit def size(self) -> int: - """Return the size of the stack.""" + """ + Return the size of the stack. + + >>> S = Stack(3) + >>> S.size() + 0 + + >>> S = Stack(3) + >>> S.push(10) + >>> S.size() + 1 + + >>> S = Stack(3) + >>> S.push(10) + >>> S.push(20) + >>> S.size() + 2 + """ return len(self.stack) def __contains__(self, item: T) -> bool: - """Check if item is in stack""" + """ + Check if item is in stack + + >>> S = Stack(3) + >>> S.push(10) + >>> 10 in S + True + + >>> S = Stack(3) + >>> S.push(10) + >>> 20 in S + False + """ return item in self.stack @@ -131,3 +209,7 @@ def test_stack() -> None: if __name__ == "__main__": test_stack() + + import doctest + + doctest.testmod() From 0e2e6abd6f24d0d816212ff0480a18abecd3028b Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:35:22 +0530 Subject: [PATCH 2645/2908] Added doctest to heap.py (#11129) * Added doctest to heap.py * Update heap.py --- data_structures/heap/heap.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 29bff3af07e3..7b15e69f13ca 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -53,7 +53,37 @@ def __repr__(self) -> str: return str(self.h) def parent_index(self, child_idx: int) -> int | None: - """return the parent index of given child""" + """ + returns the parent index based on the given child index + + >>> h = Heap() + >>> h.build_max_heap([103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5]) + >>> h + [209, 201, 25, 103, 107, 15, 1, 9, 7, 11, 5] + + >>> h.parent_index(-1) # returns none if index is <=0 + + >>> h.parent_index(0) # returns none if index is <=0 + + >>> h.parent_index(1) + 0 + >>> h.parent_index(2) + 0 + >>> h.parent_index(3) + 1 + >>> h.parent_index(4) + 1 + >>> h.parent_index(5) + 2 + >>> h.parent_index(10.5) + 4.0 + >>> h.parent_index(209.0) + 104.0 + >>> h.parent_index("Test") + Traceback (most recent call last): + ... + TypeError: '>' not supported between instances of 'str' and 'int' + """ if child_idx > 0: return (child_idx - 1) // 2 return None From 5f61af4fbbab33704b4aebd6523c64f8e6360869 Mon Sep 17 00:00:00 2001 From: MC <129918860+FishyGitHubUser@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:00:48 +0800 Subject: [PATCH 2646/2908] Fix ignore venv in build_directory_md.py (#11156) Co-authored-by: MICHAEL CASTLE --- scripts/build_directory_md.py | 6 +++++- web_programming/{get_imdbtop.py => get_imdbtop.py.DISABLED} | 0 2 files changed, 5 insertions(+), 1 deletion(-) rename web_programming/{get_imdbtop.py => get_imdbtop.py.DISABLED} (100%) diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index 24bc00cd036f..aa95b95db4b5 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -6,7 +6,11 @@ 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 "._"] + dir_names[:] = [ + d + for d in dir_names + if d != "scripts" and d[0] not in "._" and "venv" not in d + ] for filename in filenames: if filename == "__init__.py": continue diff --git a/web_programming/get_imdbtop.py b/web_programming/get_imdbtop.py.DISABLED similarity index 100% rename from web_programming/get_imdbtop.py rename to web_programming/get_imdbtop.py.DISABLED From 3999abfea392209fcb67c2218774a229878cf4cb Mon Sep 17 00:00:00 2001 From: Margaret <62753112+meg-1@users.noreply.github.com> Date: Fri, 24 Nov 2023 20:00:21 +0200 Subject: [PATCH 2647/2908] adding a geometry module (#11138) * adding a geometry module * fixing errors and adding type hints * Create code_review_feedback.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * implementing suggestions * fixing ruff errors * Update geometry/code_review_feedback.py * Update geometry/code_review_feedback.py * Update geometry/geometry.py * Apply suggestions from code review * Delete geometry/code_review_feedback.py * Update geometry/geometry.py * Update geometry/geometry.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- geometry/geometry.py | 259 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 geometry/geometry.py diff --git a/geometry/geometry.py b/geometry/geometry.py new file mode 100644 index 000000000000..9e353dee17a7 --- /dev/null +++ b/geometry/geometry.py @@ -0,0 +1,259 @@ +from __future__ import annotations + +import math +from dataclasses import dataclass, field +from types import NoneType +from typing import Self + +# Building block classes + + +@dataclass +class Angle: + """ + An Angle in degrees (unit of measurement) + + >>> Angle() + Angle(degrees=90) + >>> Angle(45.5) + Angle(degrees=45.5) + >>> Angle(-1) + Traceback (most recent call last): + ... + TypeError: degrees must be a numeric value between 0 and 360. + >>> Angle(361) + Traceback (most recent call last): + ... + TypeError: degrees must be a numeric value between 0 and 360. + """ + + degrees: float = 90 + + def __post_init__(self) -> None: + if not isinstance(self.degrees, (int, float)) or not 0 <= self.degrees <= 360: + raise TypeError("degrees must be a numeric value between 0 and 360.") + + +@dataclass +class Side: + """ + A side of a two dimensional Shape such as Polygon, etc. + adjacent_sides: a list of sides which are adjacent to the current side + angle: the angle in degrees between each adjacent side + length: the length of the current side in meters + + >>> Side(5) + Side(length=5, angle=Angle(degrees=90), next_side=None) + >>> Side(5, Angle(45.6)) + Side(length=5, angle=Angle(degrees=45.6), next_side=None) + >>> Side(5, Angle(45.6), Side(1, Angle(2))) # doctest: +ELLIPSIS + Side(length=5, angle=Angle(degrees=45.6), next_side=Side(length=1, angle=Angle(d... + """ + + length: float + angle: Angle = field(default_factory=Angle) + next_side: Side | None = None + + def __post_init__(self) -> None: + if not isinstance(self.length, (int, float)) or self.length <= 0: + raise TypeError("length must be a positive numeric value.") + if not isinstance(self.angle, Angle): + raise TypeError("angle must be an Angle object.") + if not isinstance(self.next_side, (Side, NoneType)): + raise TypeError("next_side must be a Side or None.") + + +@dataclass +class Ellipse: + """ + A geometric Ellipse on a 2D surface + + >>> Ellipse(5, 10) + Ellipse(major_radius=5, minor_radius=10) + >>> Ellipse(5, 10) is Ellipse(5, 10) + False + >>> Ellipse(5, 10) == Ellipse(5, 10) + True + """ + + major_radius: float + minor_radius: float + + @property + def area(self) -> float: + """ + >>> Ellipse(5, 10).area + 157.07963267948966 + """ + return math.pi * self.major_radius * self.minor_radius + + @property + def perimeter(self) -> float: + """ + >>> Ellipse(5, 10).perimeter + 47.12388980384689 + """ + return math.pi * (self.major_radius + self.minor_radius) + + +class Circle(Ellipse): + """ + A geometric Circle on a 2D surface + + >>> Circle(5) + Circle(radius=5) + >>> Circle(5) is Circle(5) + False + >>> Circle(5) == Circle(5) + True + >>> Circle(5).area + 78.53981633974483 + >>> Circle(5).perimeter + 31.41592653589793 + """ + + def __init__(self, radius: float) -> None: + super().__init__(radius, radius) + self.radius = radius + + def __repr__(self) -> str: + return f"Circle(radius={self.radius})" + + @property + def diameter(self) -> float: + """ + >>> Circle(5).diameter + 10 + """ + return self.radius * 2 + + def max_parts(self, num_cuts: float) -> float: + """ + Return the maximum number of parts that circle can be divided into if cut + 'num_cuts' times. + + >>> circle = Circle(5) + >>> circle.max_parts(0) + 1.0 + >>> circle.max_parts(7) + 29.0 + >>> circle.max_parts(54) + 1486.0 + >>> circle.max_parts(22.5) + 265.375 + >>> circle.max_parts(-222) + Traceback (most recent call last): + ... + TypeError: num_cuts must be a positive numeric value. + >>> circle.max_parts("-222") + Traceback (most recent call last): + ... + TypeError: num_cuts must be a positive numeric value. + """ + if not isinstance(num_cuts, (int, float)) or num_cuts < 0: + raise TypeError("num_cuts must be a positive numeric value.") + return (num_cuts + 2 + num_cuts**2) * 0.5 + + +@dataclass +class Polygon: + """ + An abstract class which represents Polygon on a 2D surface. + + >>> Polygon() + Polygon(sides=[]) + """ + + sides: list[Side] = field(default_factory=list) + + def add_side(self, side: Side) -> Self: + """ + >>> Polygon().add_side(Side(5)) + Polygon(sides=[Side(length=5, angle=Angle(degrees=90), next_side=None)]) + """ + self.sides.append(side) + return self + + def get_side(self, index: int) -> Side: + """ + >>> Polygon().get_side(0) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> Polygon().add_side(Side(5)).get_side(-1) + Side(length=5, angle=Angle(degrees=90), next_side=None) + """ + return self.sides[index] + + def set_side(self, index: int, side: Side) -> Self: + """ + >>> Polygon().set_side(0, Side(5)) + Traceback (most recent call last): + ... + IndexError: list assignment index out of range + >>> Polygon().add_side(Side(5)).set_side(0, Side(10)) + Polygon(sides=[Side(length=10, angle=Angle(degrees=90), next_side=None)]) + """ + self.sides[index] = side + return self + + +class Rectangle(Polygon): + """ + A geometric rectangle on a 2D surface. + + >>> rectangle_one = Rectangle(5, 10) + >>> rectangle_one.perimeter() + 30 + >>> rectangle_one.area() + 50 + """ + + def __init__(self, short_side_length: float, long_side_length: float) -> None: + super().__init__() + self.short_side_length = short_side_length + self.long_side_length = long_side_length + self.post_init() + + def post_init(self) -> None: + """ + >>> Rectangle(5, 10) # doctest: +NORMALIZE_WHITESPACE + Rectangle(sides=[Side(length=5, angle=Angle(degrees=90), next_side=None), + Side(length=10, angle=Angle(degrees=90), next_side=None)]) + """ + self.short_side = Side(self.short_side_length) + self.long_side = Side(self.long_side_length) + super().add_side(self.short_side) + super().add_side(self.long_side) + + def perimeter(self) -> float: + return (self.short_side.length + self.long_side.length) * 2 + + def area(self) -> float: + return self.short_side.length * self.long_side.length + + +@dataclass +class Square(Rectangle): + """ + a structure which represents a + geometrical square on a 2D surface + >>> square_one = Square(5) + >>> square_one.perimeter() + 20 + >>> square_one.area() + 25 + """ + + def __init__(self, side_length: float) -> None: + super().__init__(side_length, side_length) + + def perimeter(self) -> float: + return super().perimeter() + + def area(self) -> float: + return super().area() + + +if __name__ == "__main__": + __import__("doctest").testmod() From b8e7a4c76c4a4929ac2c7e784b0c151be47c1e6e Mon Sep 17 00:00:00 2001 From: MC <129918860+FishyGitHubUser@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:17:59 +0800 Subject: [PATCH 2648/2908] Fix typo in knight_tour.py (#11173) --- backtracking/knight_tour.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py index cc88307b7fe8..5f7dee8d97bf 100644 --- a/backtracking/knight_tour.py +++ b/backtracking/knight_tour.py @@ -79,7 +79,7 @@ def open_knight_tour(n: int) -> list[list[int]]: >>> open_knight_tour(2) Traceback (most recent call last): ... - ValueError: Open Kight Tour cannot be performed on a board of size 2 + ValueError: Open Knight Tour cannot be performed on a board of size 2 """ board = [[0 for i in range(n)] for j in range(n)] @@ -91,7 +91,7 @@ def open_knight_tour(n: int) -> list[list[int]]: return board board[i][j] = 0 - msg = f"Open Kight Tour cannot be performed on a board of size {n}" + msg = f"Open Knight Tour cannot be performed on a board of size {n}" raise ValueError(msg) From 5898b9603bbe9b449cf5a2e331cf0c7d3245a788 Mon Sep 17 00:00:00 2001 From: Rahid Zeynalov <44039543+rahidzeynal@users.noreply.github.com> Date: Sat, 25 Nov 2023 15:25:46 +0400 Subject: [PATCH 2649/2908] Typo deicmal -> decimal (#11169) --- bit_manipulation/is_even.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bit_manipulation/is_even.py b/bit_manipulation/is_even.py index ba036f35aa1e..6f95a1160797 100644 --- a/bit_manipulation/is_even.py +++ b/bit_manipulation/is_even.py @@ -1,7 +1,7 @@ def is_even(number: int) -> bool: """ return true if the input integer is even - Explanation: Lets take a look at the following deicmal to binary conversions + Explanation: Lets take a look at the following decimal to binary conversions 2 => 10 14 => 1110 100 => 1100100 From 4151a13b57fbd881d3fce3bb61101fe58ad541ae Mon Sep 17 00:00:00 2001 From: Clark <1009013283@qq.com> Date: Sat, 25 Nov 2023 20:26:03 +0800 Subject: [PATCH 2650/2908] add graphs/ant_colony_optimization_algorithms.py (#11163) * add ant_colonyant_colony_optimization_algorithms.py * Modify details * Modify type annotation * Add tests for KeyError, IndexError, StopIteration, etc. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- graphs/ant_colony_optimization_algorithms.py | 226 +++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 graphs/ant_colony_optimization_algorithms.py diff --git a/graphs/ant_colony_optimization_algorithms.py b/graphs/ant_colony_optimization_algorithms.py new file mode 100644 index 000000000000..652ad6144297 --- /dev/null +++ b/graphs/ant_colony_optimization_algorithms.py @@ -0,0 +1,226 @@ +""" +Use an ant colony optimization algorithm to solve the travelling salesman problem (TSP) +which asks the following question: +"Given a list of cities and the distances between each pair of cities, what is the + shortest possible route that visits each city exactly once and returns to the origin + city?" + +https://en.wikipedia.org/wiki/Ant_colony_optimization_algorithms +https://en.wikipedia.org/wiki/Travelling_salesman_problem + +Author: Clark +""" + +import copy +import random + +cities = { + 0: [0, 0], + 1: [0, 5], + 2: [3, 8], + 3: [8, 10], + 4: [12, 8], + 5: [12, 4], + 6: [8, 0], + 7: [6, 2], +} + + +def main( + cities: dict[int, list[int]], + ants_num: int, + iterations_num: int, + pheromone_evaporation: float, + alpha: float, + beta: float, + q: float, # Pheromone system parameters Q,which is a constant +) -> tuple[list[int], float]: + """ + Ant colony algorithm main function + >>> main(cities=cities, ants_num=10, iterations_num=20, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + ([0, 1, 2, 3, 4, 5, 6, 7, 0], 37.909778143828696) + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + ([0, 1, 0], 5.656854249492381) + >>> main(cities={0: [0, 0], 1: [2, 2], 4: [4, 4]}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> main(cities={}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + Traceback (most recent call last): + ... + StopIteration + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=0, iterations_num=5, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + ([], inf) + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=0, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + ([], inf) + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=1, alpha=1.0, beta=5.0, q=10) + ([0, 1, 0], 5.656854249492381) + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=0, alpha=1.0, beta=5.0, q=10) + ([0, 1, 0], 5.656854249492381) + """ + # Initialize the pheromone matrix + cities_num = len(cities) + pheromone = [[1.0] * cities_num] * cities_num + + best_path: list[int] = [] + best_distance = float("inf") + for _ in range(iterations_num): + ants_route = [] + for _ in range(ants_num): + unvisited_cities = copy.deepcopy(cities) + current_city = {next(iter(cities.keys())): next(iter(cities.values()))} + del unvisited_cities[next(iter(current_city.keys()))] + ant_route = [next(iter(current_city.keys()))] + while unvisited_cities: + current_city, unvisited_cities = city_select( + pheromone, current_city, unvisited_cities, alpha, beta + ) + ant_route.append(next(iter(current_city.keys()))) + ant_route.append(0) + ants_route.append(ant_route) + + pheromone, best_path, best_distance = pheromone_update( + pheromone, + cities, + pheromone_evaporation, + ants_route, + q, + best_path, + best_distance, + ) + return best_path, best_distance + + +def distance(city1: list[int], city2: list[int]) -> float: + """ + Calculate the distance between two coordinate points + >>> distance([0, 0], [3, 4] ) + 5.0 + >>> distance([0, 0], [-3, 4] ) + 5.0 + >>> distance([0, 0], [-3, -4] ) + 5.0 + """ + return (((city1[0] - city2[0]) ** 2) + ((city1[1] - city2[1]) ** 2)) ** 0.5 + + +def pheromone_update( + pheromone: list[list[float]], + cities: dict[int, list[int]], + pheromone_evaporation: float, + ants_route: list[list[int]], + q: float, # Pheromone system parameters Q,which is a constant + best_path: list[int], + best_distance: float, +) -> tuple[list[list[float]], list[int], float]: + """ + Update pheromones on the route and update the best route + >>> + >>> pheromone_update(pheromone=[[1.0, 1.0], [1.0, 1.0]], + ... cities={0: [0,0], 1: [2,2]}, pheromone_evaporation=0.7, + ... ants_route=[[0, 1, 0]], q=10, best_path=[], + ... best_distance=float("inf")) + ([[0.7, 4.235533905932737], [4.235533905932737, 0.7]], [0, 1, 0], 5.656854249492381) + >>> pheromone_update(pheromone=[], + ... cities={0: [0,0], 1: [2,2]}, pheromone_evaporation=0.7, + ... ants_route=[[0, 1, 0]], q=10, best_path=[], + ... best_distance=float("inf")) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> pheromone_update(pheromone=[[1.0, 1.0], [1.0, 1.0]], + ... cities={}, pheromone_evaporation=0.7, + ... ants_route=[[0, 1, 0]], q=10, best_path=[], + ... best_distance=float("inf")) + Traceback (most recent call last): + ... + KeyError: 0 + """ + for a in range(len(cities)): # Update the volatilization of pheromone on all routes + for b in range(len(cities)): + pheromone[a][b] *= pheromone_evaporation + for ant_route in ants_route: + total_distance = 0.0 + for i in range(len(ant_route) - 1): # Calculate total distance + total_distance += distance(cities[ant_route[i]], cities[ant_route[i + 1]]) + delta_pheromone = q / total_distance + for i in range(len(ant_route) - 1): # Update pheromones + pheromone[ant_route[i]][ant_route[i + 1]] += delta_pheromone + pheromone[ant_route[i + 1]][ant_route[i]] = pheromone[ant_route[i]][ + ant_route[i + 1] + ] + + if total_distance < best_distance: + best_path = ant_route + best_distance = total_distance + + return pheromone, best_path, best_distance + + +def city_select( + pheromone: list[list[float]], + current_city: dict[int, list[int]], + unvisited_cities: dict[int, list[int]], + alpha: float, + beta: float, +) -> tuple[dict[int, list[int]], dict[int, list[int]]]: + """ + Choose the next city for ants + >>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={0: [0, 0]}, + ... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0) + ({1: [2, 2]}, {}) + >>> city_select(pheromone=[], current_city={0: [0,0]}, + ... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={}, + ... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0) + Traceback (most recent call last): + ... + StopIteration + >>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={0: [0, 0]}, + ... unvisited_cities={}, alpha=1.0, beta=5.0) + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + probabilities = [] + for city in unvisited_cities: + city_distance = distance( + unvisited_cities[city], next(iter(current_city.values())) + ) + probability = (pheromone[city][next(iter(current_city.keys()))] ** alpha) * ( + (1 / city_distance) ** beta + ) + probabilities.append(probability) + + chosen_city_i = random.choices( + list(unvisited_cities.keys()), weights=probabilities + )[0] + chosen_city = {chosen_city_i: unvisited_cities[chosen_city_i]} + del unvisited_cities[next(iter(chosen_city.keys()))] + return chosen_city, unvisited_cities + + +if __name__ == "__main__": + best_path, best_distance = main( + cities=cities, + ants_num=10, + iterations_num=20, + pheromone_evaporation=0.7, + alpha=1.0, + beta=5.0, + q=10, + ) + + print(f"{best_path = }") + print(f"{best_distance = }") From 050b2a6e2cf0e474b75cf48abe4aa134b97643e4 Mon Sep 17 00:00:00 2001 From: moaldeen <132774635+moaldeen@users.noreply.github.com> Date: Sat, 25 Nov 2023 08:31:17 -0500 Subject: [PATCH 2651/2908] Bug fix combinations (#11158) * Update all_combinations.py The original implementation had limitations in handling edge cases and certain input parameters, leading to potential RecursionError. * Update all_combinations.py Added checks to handle cases where n or k are negative or where k is greater than n. In such scenarios, the function now returns an empty list, avoiding invalid recursive calls. * Update error handling Added checks to handle cases where `n` or `k` are negative or where `k` is greater than `n`. In such scenarios, the function now returns an empty list, avoiding invalid recursive calls. * Update backtracking/all_combinations.py * Update all_combinations.py --------- Co-authored-by: Christian Clauss --- backtracking/all_combinations.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index ecbcc5882ec1..407304948c39 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -26,9 +26,11 @@ def generate_all_combinations(n: int, k: int) -> list[list[int]]: >>> generate_all_combinations(n=10, k=-1) Traceback (most recent call last): ... - RecursionError: maximum recursion depth exceeded + ValueError: k must not be negative >>> generate_all_combinations(n=-1, k=10) - [] + Traceback (most recent call last): + ... + ValueError: n must not be negative >>> generate_all_combinations(n=5, k=4) [[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]] >>> from itertools import combinations @@ -36,6 +38,10 @@ def generate_all_combinations(n: int, k: int) -> list[list[int]]: ... for n in range(1, 6) for k in range(1, 6)) True """ + if k < 0: + raise ValueError("k must not be negative") + if n < 0: + raise ValueError("n must not be negative") result: list[list[int]] = [] create_all_state(1, n, k, [], result) From 8b39a0fb54d0f63489952606d2036d1a63f981e3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:53:18 +0100 Subject: [PATCH 2652/2908] [pre-commit.ci] pre-commit autoupdate (#11154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.4 → v0.1.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.4...v0.1.6) - [github.com/psf/black: 23.10.1 → 23.11.0](https://github.com/psf/black/compare/23.10.1...23.11.0) - [github.com/tox-dev/pyproject-fmt: 1.4.1 → 1.5.1](https://github.com/tox-dev/pyproject-fmt/compare/1.4.1...1.5.1) - [github.com/pre-commit/mirrors-mypy: v1.6.1 → v1.7.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.1...v1.7.0) * updating DIRECTORY.md * Update spiral_print.py * Update matrix/spiral_print.py * Update matrix/spiral_print.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 8 ++++---- DIRECTORY.md | 1 - matrix/spiral_print.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1bb3de782275..9a0f78fdde5a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.6 hooks: - id: ruff - repo: https://github.com/psf/black - rev: 23.10.1 + rev: 23.11.0 hooks: - id: black @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.4.1" + rev: "1.5.1" hooks: - id: pyproject-fmt @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.6.1 + rev: v1.7.0 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index cb4b00b045b5..438950325380 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1310,7 +1310,6 @@ * [Fetch Well Rx Price](web_programming/fetch_well_rx_price.py) * [Get Amazon Product Data](web_programming/get_amazon_product_data.py) * [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py) - * [Get Imdbtop](web_programming/get_imdbtop.py) * [Get Ip Geolocation](web_programming/get_ip_geolocation.py) * [Get Top Billionaires](web_programming/get_top_billionaires.py) * [Get Top Hn Posts](web_programming/get_top_hn_posts.py) diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 5eef263f7aef..7ba0a275157b 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -116,7 +116,7 @@ def spiral_traversal(matrix: list[list]) -> list[int]: [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] + spiral_traversal([]) """ if matrix: - return list(matrix.pop(0)) + spiral_traversal(list(zip(*matrix))[::-1]) + return list(matrix.pop(0)) + spiral_traversal(list(zip(*matrix))[::-1]) # type: ignore else: return [] From 86ae30d29e4813c2ef071d7d27f1302b6be6cc0c Mon Sep 17 00:00:00 2001 From: Harsh Kumar <61012869+cyrixninja@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:50:42 +0530 Subject: [PATCH 2653/2908] Create Spearman's rank correlation coefficient (#11155) * Create spearman_rank_correlation_coefficient.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed Issues * Added More Description * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed Issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Tried Fixing Issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Tried Fixing Issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed Issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed Issues * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/spearman_rank_correlation_coefficient.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../spearman_rank_correlation_coefficient.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 maths/spearman_rank_correlation_coefficient.py diff --git a/maths/spearman_rank_correlation_coefficient.py b/maths/spearman_rank_correlation_coefficient.py new file mode 100644 index 000000000000..32ff6b9e3d71 --- /dev/null +++ b/maths/spearman_rank_correlation_coefficient.py @@ -0,0 +1,82 @@ +from collections.abc import Sequence + + +def assign_ranks(data: Sequence[float]) -> list[int]: + """ + Assigns ranks to elements in the array. + + :param data: List of floats. + :return: List of ints representing the ranks. + + Example: + >>> assign_ranks([3.2, 1.5, 4.0, 2.7, 5.1]) + [3, 1, 4, 2, 5] + + >>> assign_ranks([10.5, 8.1, 12.4, 9.3, 11.0]) + [3, 1, 5, 2, 4] + """ + ranked_data = sorted((value, index) for index, value in enumerate(data)) + ranks = [0] * len(data) + + for position, (_, index) in enumerate(ranked_data): + ranks[index] = position + 1 + + return ranks + + +def calculate_spearman_rank_correlation( + variable_1: Sequence[float], variable_2: Sequence[float] +) -> float: + """ + Calculates Spearman's rank correlation coefficient. + + :param variable_1: List of floats representing the first variable. + :param variable_2: List of floats representing the second variable. + :return: Spearman's rank correlation coefficient. + + Example Usage: + + >>> x = [1, 2, 3, 4, 5] + >>> y = [5, 4, 3, 2, 1] + >>> calculate_spearman_rank_correlation(x, y) + -1.0 + + >>> x = [1, 2, 3, 4, 5] + >>> y = [2, 4, 6, 8, 10] + >>> calculate_spearman_rank_correlation(x, y) + 1.0 + + >>> x = [1, 2, 3, 4, 5] + >>> y = [5, 1, 2, 9, 5] + >>> calculate_spearman_rank_correlation(x, y) + 0.6 + """ + n = len(variable_1) + rank_var1 = assign_ranks(variable_1) + rank_var2 = assign_ranks(variable_2) + + # Calculate differences of ranks + d = [rx - ry for rx, ry in zip(rank_var1, rank_var2)] + + # Calculate the sum of squared differences + d_squared = sum(di**2 for di in d) + + # Calculate the Spearman's rank correlation coefficient + rho = 1 - (6 * d_squared) / (n * (n**2 - 1)) + + return rho + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Example usage: + print( + f"{calculate_spearman_rank_correlation([1, 2, 3, 4, 5], [2, 4, 6, 8, 10]) = }" + ) + + print(f"{calculate_spearman_rank_correlation([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]) = }") + + print(f"{calculate_spearman_rank_correlation([1, 2, 3, 4, 5], [5, 1, 2, 9, 5]) = }") From 84a1533fd5d262dae767a9298de1c1d7fcb2bec9 Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:59:44 +0530 Subject: [PATCH 2654/2908] Added doctest to binary_search_tree.py (#11145) * Added doctest to binary_search_tree.py * Apply suggestions from code review --------- Co-authored-by: Christian Clauss --- .../binary_tree/binary_search_tree.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index f08f278a8e47..9071f03dcc8c 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -198,6 +198,30 @@ def insert(self, *values) -> Self: return self def search(self, value) -> Node | None: + """ + >>> tree = BinarySearchTree().insert(10, 20, 30, 40, 50) + >>> tree.search(10) + {'10': (None, {'20': (None, {'30': (None, {'40': (None, 50)})})})} + >>> tree.search(20) + {'20': (None, {'30': (None, {'40': (None, 50)})})} + >>> tree.search(30) + {'30': (None, {'40': (None, 50)})} + >>> tree.search(40) + {'40': (None, 50)} + >>> tree.search(50) + 50 + >>> tree.search(5) is None # element not present + True + >>> tree.search(0) is None # element not present + True + >>> tree.search(-5) is None # element not present + True + >>> BinarySearchTree().search(10) + Traceback (most recent call last): + ... + IndexError: Warning: Tree is empty! please use another. + """ + if self.empty(): raise IndexError("Warning: Tree is empty! please use another.") else: @@ -210,6 +234,15 @@ def search(self, value) -> Node | None: def get_max(self, node: Node | None = None) -> Node | None: """ We go deep on the right branch + + >>> BinarySearchTree().insert(10, 20, 30, 40, 50).get_max() + 50 + >>> BinarySearchTree().insert(-5, -1, 0.1, -0.3, -4.5).get_max() + {'0.1': (-0.3, None)} + >>> BinarySearchTree().insert(1, 78.3, 30, 74.0, 1).get_max() + {'78.3': ({'30': (1, 74.0)}, None)} + >>> BinarySearchTree().insert(1, 783, 30, 740, 1).get_max() + {'783': ({'30': (1, 740)}, None)} """ if node is None: if self.root is None: @@ -224,6 +257,15 @@ def get_max(self, node: Node | None = None) -> Node | None: def get_min(self, node: Node | None = None) -> Node | None: """ We go deep on the left branch + + >>> BinarySearchTree().insert(10, 20, 30, 40, 50).get_min() + {'10': (None, {'20': (None, {'30': (None, {'40': (None, 50)})})})} + >>> BinarySearchTree().insert(-5, -1, 0, -0.3, -4.5).get_min() + {'-5': (None, {'-1': (-4.5, {'0': (-0.3, None)})})} + >>> BinarySearchTree().insert(1, 78.3, 30, 74.0, 1).get_min() + {'1': (None, {'78.3': ({'30': (1, 74.0)}, None)})} + >>> BinarySearchTree().insert(1, 783, 30, 740, 1).get_min() + {'1': (None, {'783': ({'30': (1, 740)}, None)})} """ if node is None: node = self.root From 154e5e8681b7ae9711fbef0b89f0ce365a8bf5bf Mon Sep 17 00:00:00 2001 From: Pedram_Mohajer <48964282+pedram-mohajer@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:46:54 -0500 Subject: [PATCH 2655/2908] Update levenshtein_distance.py (#11171) * Update levenshtein_distance.py * Update levenshtein_distance.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update levenshtein_distance.py * Update levenshtein_distance.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update levenshtein_distance.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update levenshtein_distance.py * Update levenshtein_distance.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- strings/levenshtein_distance.py | 93 ++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/strings/levenshtein_distance.py b/strings/levenshtein_distance.py index 7be4074dc39b..3af6608723a5 100644 --- a/strings/levenshtein_distance.py +++ b/strings/levenshtein_distance.py @@ -1,20 +1,9 @@ -""" -This is a Python implementation of the levenshtein distance. -Levenshtein distance is a string metric for measuring the -difference between two sequences. - -For doctests run following command: -python -m doctest -v levenshtein-distance.py -or -python3 -m doctest -v levenshtein-distance.py - -For manual testing run: -python levenshtein-distance.py -""" +from collections.abc import Callable def levenshtein_distance(first_word: str, second_word: str) -> int: - """Implementation of the levenshtein distance in Python. + """ + 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. :return: the levenshtein distance between the two words. @@ -47,7 +36,7 @@ def levenshtein_distance(first_word: str, second_word: str) -> int: current_row = [i + 1] for j, c2 in enumerate(second_word): - # Calculate insertions, deletions and substitutions + # Calculate insertions, deletions, and substitutions insertions = previous_row[j + 1] + 1 deletions = current_row[j] + 1 substitutions = previous_row[j] + (c1 != c2) @@ -62,9 +51,75 @@ def levenshtein_distance(first_word: str, second_word: str) -> int: return previous_row[-1] +def levenshtein_distance_optimized(first_word: str, second_word: str) -> int: + """ + Compute the Levenshtein distance between two words (strings). + The function is optimized for efficiency by modifying rows in place. + :param first_word: the first word to measure the difference. + :param second_word: the second word to measure the difference. + :return: the Levenshtein distance between the two words. + Examples: + >>> levenshtein_distance_optimized("planet", "planetary") + 3 + >>> levenshtein_distance_optimized("", "test") + 4 + >>> levenshtein_distance_optimized("book", "back") + 2 + >>> levenshtein_distance_optimized("book", "book") + 0 + >>> levenshtein_distance_optimized("test", "") + 4 + >>> levenshtein_distance_optimized("", "") + 0 + >>> levenshtein_distance_optimized("orchestration", "container") + 10 + """ + if len(first_word) < len(second_word): + return levenshtein_distance_optimized(second_word, first_word) + + if len(second_word) == 0: + return len(first_word) + + previous_row = list(range(len(second_word) + 1)) + + for i, c1 in enumerate(first_word): + current_row = [i + 1] + [0] * len(second_word) + + for j, c2 in enumerate(second_word): + insertions = previous_row[j + 1] + 1 + deletions = current_row[j] + 1 + substitutions = previous_row[j] + (c1 != c2) + current_row[j + 1] = min(insertions, deletions, substitutions) + + previous_row = current_row + + return previous_row[-1] + + +def benchmark_levenshtein_distance(func: Callable) -> None: + """ + Benchmark the Levenshtein distance function. + :param str: The name of the function being benchmarked. + :param func: The function to be benchmarked. + """ + from timeit import timeit + + stmt = f"{func.__name__}('sitting', 'kitten')" + setup = f"from __main__ import {func.__name__}" + number = 25_000 + result = timeit(stmt=stmt, setup=setup, number=number) + print(f"{func.__name__:<30} finished {number:,} runs in {result:.5f} seconds") + + if __name__ == "__main__": - first_word = input("Enter the first word:\n").strip() - second_word = input("Enter the second word:\n").strip() + # Get user input for words + first_word = input("Enter the first word for Levenshtein distance:\n").strip() + second_word = input("Enter the second word for Levenshtein distance:\n").strip() + + # Calculate and print Levenshtein distances + print(f"{levenshtein_distance(first_word, second_word) = }") + print(f"{levenshtein_distance_optimized(first_word, second_word) = }") - result = levenshtein_distance(first_word, second_word) - print(f"Levenshtein distance between {first_word} and {second_word} is {result}") + # Benchmark the Levenshtein distance functions + benchmark_levenshtein_distance(levenshtein_distance) + benchmark_levenshtein_distance(levenshtein_distance_optimized) From b8600035768da179adc709814f4b455b844982cc Mon Sep 17 00:00:00 2001 From: Pedram_Mohajer <48964282+pedram-mohajer@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:43:51 -0500 Subject: [PATCH 2656/2908] Add doctest to is_safe function (#11183) --- backtracking/n_queens.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index 0f237d95e7c8..2cd8c703fc72 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -24,6 +24,10 @@ def is_safe(board: list[list[int]], row: int, column: int) -> bool: Returns: Boolean Value + >>> is_safe([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1) + True + >>> is_safe([[1, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1) + False """ n = len(board) # Size of the board From 0ac97f359f2c4b1a4b96db6a083fac95ca0cfe97 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:13:24 +0100 Subject: [PATCH 2657/2908] [pre-commit.ci] pre-commit autoupdate (#11184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/pre-commit/mirrors-mypy: v1.7.0 → v1.7.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.0...v1.7.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9a0f78fdde5a..28f83a638d7b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.0 + rev: v1.7.1 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 438950325380..ea0ba22bcc13 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -428,12 +428,16 @@ * [Haversine Distance](geodesy/haversine_distance.py) * [Lamberts Ellipsoidal Distance](geodesy/lamberts_ellipsoidal_distance.py) +## Geometry + * [Geometry](geometry/geometry.py) + ## Graphics * [Bezier Curve](graphics/bezier_curve.py) * [Vector3 For 2D Rendering](graphics/vector3_for_2d_rendering.py) ## Graphs * [A Star](graphs/a_star.py) + * [Ant Colony Optimization Algorithms](graphs/ant_colony_optimization_algorithms.py) * [Articulation Points](graphs/articulation_points.py) * [Basic Graphs](graphs/basic_graphs.py) * [Bellman Ford](graphs/bellman_ford.py) @@ -718,6 +722,7 @@ * [Sock Merchant](maths/sock_merchant.py) * [Softmax](maths/softmax.py) * [Solovay Strassen Primality Test](maths/solovay_strassen_primality_test.py) + * [Spearman Rank Correlation Coefficient](maths/spearman_rank_correlation_coefficient.py) * Special Numbers * [Armstrong Numbers](maths/special_numbers/armstrong_numbers.py) * [Automorphic Number](maths/special_numbers/automorphic_number.py) From 82e539dc8226abe803aa562402cfe9f19ded9e22 Mon Sep 17 00:00:00 2001 From: Pedram_Mohajer <48964282+pedram-mohajer@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:53:47 -0500 Subject: [PATCH 2658/2908] Create smallestRange.py (#11179) * Create smallestRange.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update smallestRange.py * Update smallestRange.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update smallestRange.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename smallestRange.py to smallestrange.py * Update smallestrange.py * Update smallestrange.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update smallestrange.py * Rename smallestrange.py to smallest_range.py * Update smallest_range.py * Update smallest_range.py * Update smallest_range.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- greedy_methods/smallest_range.py | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 greedy_methods/smallest_range.py diff --git a/greedy_methods/smallest_range.py b/greedy_methods/smallest_range.py new file mode 100644 index 000000000000..e2b7f8d7e96a --- /dev/null +++ b/greedy_methods/smallest_range.py @@ -0,0 +1,71 @@ +""" +smallest_range function takes a list of sorted integer lists and finds the smallest +range that includes at least one number from each list, using a min heap for efficiency. +""" + +from heapq import heappop, heappush +from sys import maxsize + + +def smallest_range(nums: list[list[int]]) -> list[int]: + """ + Find the smallest range from each list in nums. + + Uses min heap for efficiency. The range includes at least one number from each list. + + Args: + nums: List of k sorted integer lists. + + Returns: + list: Smallest range as a two-element list. + + Examples: + >>> smallest_range([[4, 10, 15, 24, 26], [0, 9, 12, 20], [5, 18, 22, 30]]) + [20, 24] + >>> smallest_range([[1, 2, 3], [1, 2, 3], [1, 2, 3]]) + [1, 1] + >>> smallest_range(((1, 2, 3), (1, 2, 3), (1, 2, 3))) + [1, 1] + >>> smallest_range(((-3, -2, -1), (0, 0, 0), (1, 2, 3))) + [-1, 1] + >>> smallest_range([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + [3, 7] + >>> smallest_range([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + [0, 0] + >>> smallest_range([[], [], []]) + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + + min_heap: list[tuple[int, int, int]] = [] + current_max = -maxsize - 1 + + for i, items in enumerate(nums): + heappush(min_heap, (items[0], i, 0)) + current_max = max(current_max, items[0]) + + # Initialize smallest_range with large integer values + smallest_range = [-maxsize - 1, maxsize] + + while min_heap: + current_min, list_index, element_index = heappop(min_heap) + + if current_max - current_min < smallest_range[1] - smallest_range[0]: + smallest_range = [current_min, current_max] + + if element_index == len(nums[list_index]) - 1: + break + + next_element = nums[list_index][element_index + 1] + heappush(min_heap, (next_element, list_index, element_index + 1)) + current_max = max(current_max, next_element) + + return smallest_range + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + print(f"{smallest_range([[1, 2, 3], [1, 2, 3], [1, 2, 3]])}") # Output: [1, 1] From a73f37b2ecf29aeee1b0417ac53016f5ad0fbeee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:00:34 +0100 Subject: [PATCH 2659/2908] [pre-commit.ci] pre-commit autoupdate (#11195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/tox-dev/pyproject-fmt: 1.5.1 → 1.5.3](https://github.com/tox-dev/pyproject-fmt/compare/1.5.1...1.5.3) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 28f83a638d7b..5ec7a5765817 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.5.1" + rev: "1.5.3" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index ea0ba22bcc13..2ee72df37f3f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -507,6 +507,7 @@ * [Minimum Coin Change](greedy_methods/minimum_coin_change.py) * [Minimum Waiting Time](greedy_methods/minimum_waiting_time.py) * [Optimal Merge Pattern](greedy_methods/optimal_merge_pattern.py) + * [Smallest Range](greedy_methods/smallest_range.py) ## Hashes * [Adler32](hashes/adler32.py) From c14a580c9e7340ee1d826a52af0f95c077b564b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:27:15 +0100 Subject: [PATCH 2660/2908] [pre-commit.ci] pre-commit autoupdate (#11210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.6 → v0.1.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.6...v0.1.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5ec7a5765817..9688f1cbb5fc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.7 hooks: - id: ruff From 2d0ed135a08dbe7da8c696d70ee7fb1a01f2cc91 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:17:46 +0100 Subject: [PATCH 2661/2908] [pre-commit.ci] pre-commit autoupdate (#11215) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.7 → v0.1.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.7...v0.1.8) - [github.com/psf/black: 23.11.0 → 23.12.0](https://github.com/psf/black/compare/23.11.0...23.12.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9688f1cbb5fc..c8a11e38aeab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.1.8 hooks: - id: ruff - repo: https://github.com/psf/black - rev: 23.11.0 + rev: 23.12.0 hooks: - id: black From b46fc1de04350f91971187d831d8e3292ea0bace Mon Sep 17 00:00:00 2001 From: Indrajeet Mishra Date: Wed, 20 Dec 2023 04:35:27 +0530 Subject: [PATCH 2662/2908] Corrected the Python Doctest command in equilibrium_index_in_array.py script (#11212) Co-authored-by: Indrajeet Mishra --- data_structures/arrays/equilibrium_index_in_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/arrays/equilibrium_index_in_array.py b/data_structures/arrays/equilibrium_index_in_array.py index 8802db6206bb..0717a45d9f4b 100644 --- a/data_structures/arrays/equilibrium_index_in_array.py +++ b/data_structures/arrays/equilibrium_index_in_array.py @@ -3,7 +3,7 @@ Reference: https://www.geeksforgeeks.org/equilibrium-index-of-an-array/ Python doctest can be run with the following command: -python -m doctest -v equilibrium_index.py +python -m doctest -v equilibrium_index_in_array.py Given a sequence arr[] of size n, this function returns an equilibrium index (if any) or -1 if no equilibrium index exists. From 7b9f82cc447c2d2ce91373c097bf610d5b0f906a Mon Sep 17 00:00:00 2001 From: Tushar Pamnani <121151091+tusharpamnani@users.noreply.github.com> Date: Wed, 20 Dec 2023 07:29:51 +0530 Subject: [PATCH 2663/2908] optimize quicksort implementation (#11196) * optimize quicksort implementation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * Update quick_sort.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- sorts/quick_sort.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index b79d3eac3e48..6b95fc144426 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -13,10 +13,10 @@ def quick_sort(collection: list) -> list: - """A pure Python implementation of quick sort algorithm + """A pure Python implementation of quicksort algorithm. :param collection: a mutable collection of comparable items - :return: the same collection ordered by ascending + :return: the same collection ordered in ascending order Examples: >>> quick_sort([0, 5, 3, 2, 2]) @@ -26,23 +26,26 @@ def quick_sort(collection: list) -> list: >>> quick_sort([-2, 5, 0, -45]) [-45, -2, 0, 5] """ + # Base case: if the collection has 0 or 1 elements, it is already sorted if len(collection) < 2: return collection - pivot_index = randrange(len(collection)) # Use random element as pivot - pivot = collection[pivot_index] - greater: list[int] = [] # All elements greater than pivot - lesser: list[int] = [] # All elements less than or equal to pivot - for element in collection[:pivot_index]: - (greater if element > pivot else lesser).append(element) + # Randomly select a pivot index and remove the pivot element from the collection + pivot_index = randrange(len(collection)) + pivot = collection.pop(pivot_index) - for element in collection[pivot_index + 1 :]: - (greater if element > pivot else lesser).append(element) + # Partition the remaining elements into two groups: lesser or equal, and greater + lesser = [item for item in collection if item <= pivot] + greater = [item for item in collection if item > pivot] + # Recursively sort the lesser and greater groups, and combine with the pivot return [*quick_sort(lesser), pivot, *quick_sort(greater)] if __name__ == "__main__": + # Get user input and convert it into a list of integers user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] + + # Print the result of sorting the user-provided list print(quick_sort(unsorted)) From 94c8e1ab73032d27bc8c60b733bb93393b9f1b02 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 19:24:39 +0100 Subject: [PATCH 2664/2908] [pre-commit.ci] pre-commit autoupdate (#11223) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.8 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.8...v0.1.9) - [github.com/psf/black: 23.12.0 → 23.12.1](https://github.com/psf/black/compare/23.12.0...23.12.1) - [github.com/pre-commit/mirrors-mypy: v1.7.1 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.1...v1.8.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8a11e38aeab..61ec3a54a69c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.8 + rev: v0.1.9 hooks: - id: ruff - repo: https://github.com/psf/black - rev: 23.12.0 + rev: 23.12.1 hooks: - id: black @@ -51,7 +51,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.1 + rev: v1.8.0 hooks: - id: mypy args: From 51c5c87b9ab4eb04c3825cd20cfdba0f31a098f5 Mon Sep 17 00:00:00 2001 From: Param Thakkar <128291516+ParamThakkar123@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:05:29 +0530 Subject: [PATCH 2665/2908] File moved to neural_network/activation_functions (#11216) * added GELU activation functions file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_error_linear_unit.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gaussian_error_linear_unit.py * Delete neural_network/activation_functions/gaussian_error_linear_unit.py * Rename maths/gaussian_error_linear_unit.py to neural_network/activation_functions/gaussian_error_linear_unit.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../activation_functions}/gaussian_error_linear_unit.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {maths => neural_network/activation_functions}/gaussian_error_linear_unit.py (100%) diff --git a/maths/gaussian_error_linear_unit.py b/neural_network/activation_functions/gaussian_error_linear_unit.py similarity index 100% rename from maths/gaussian_error_linear_unit.py rename to neural_network/activation_functions/gaussian_error_linear_unit.py From 9caf4784aada17dc75348f77cc8c356df503c0f3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:16:25 +0100 Subject: [PATCH 2666/2908] [pre-commit.ci] pre-commit autoupdate (#11231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.9 → v0.1.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.9...v0.1.11) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 61ec3a54a69c..0e06ba7a5250 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.9 + rev: v0.1.11 hooks: - id: ruff diff --git a/DIRECTORY.md b/DIRECTORY.md index 2ee72df37f3f..b5392fd09114 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -631,7 +631,6 @@ * [Floor](maths/floor.py) * [Gamma](maths/gamma.py) * [Gaussian](maths/gaussian.py) - * [Gaussian Error Linear Unit](maths/gaussian_error_linear_unit.py) * [Gcd Of N Numbers](maths/gcd_of_n_numbers.py) * [Germain Primes](maths/germain_primes.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) @@ -791,6 +790,7 @@ * Activation Functions * [Binary Step](neural_network/activation_functions/binary_step.py) * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) + * [Gaussian Error Linear Unit](neural_network/activation_functions/gaussian_error_linear_unit.py) * [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py) * [Mish](neural_network/activation_functions/mish.py) * [Rectified Linear Unit](neural_network/activation_functions/rectified_linear_unit.py) From 227944eb2933b22a102eb88703b4a0b648f39af5 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Fri, 12 Jan 2024 17:12:15 +0100 Subject: [PATCH 2667/2908] fix: consider months and days in `years_old` (#11234) * fix: do not consider months in `calculate_age` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update get_top_billionaires.py * Update get_top_billionaires.py * Update get_top_billionaires.py * TODAY = datetime.utcnow() * Update get_top_billionaires.py * Update build.yml --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 2 +- web_programming/get_top_billionaires.py | 72 ++++++++++++------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60c1d6d119d0..1631feb2ba06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.12 allow-prereleases: true diff --git a/web_programming/get_top_billionaires.py b/web_programming/get_top_billionaires.py index 6f986acb9181..703b635eef82 100644 --- a/web_programming/get_top_billionaires.py +++ b/web_programming/get_top_billionaires.py @@ -3,7 +3,7 @@ This works for some of us but fails for others. """ -from datetime import UTC, datetime, timedelta +from datetime import UTC, date, datetime import requests from rich import box @@ -11,8 +11,7 @@ from rich import table as rich_table LIMIT = 10 -TODAY = datetime.now() - +TODAY = datetime.now(tz=UTC) API_URL = ( "https://www.forbes.com/forbesapi/person/rtb/0/position/true.json" "?fields=personName,gender,source,countryOfCitizenship,birthDate,finalWorth" @@ -20,40 +19,40 @@ ) -def calculate_age(unix_date: float) -> str: - """Calculates age from given unix time format. +def years_old(birth_timestamp: int, today: date | None = None) -> int: + """ + Calculate the age in years based on the given birth date. Only the year, month, + and day are used in the calculation. The time of day is ignored. + + Args: + birth_timestamp: The date of birth. + today: (useful for writing tests) or if None then datetime.date.today(). Returns: - Age as string - - >>> from datetime import datetime, UTC - >>> years_since_create = datetime.now(tz=UTC).year - 2022 - >>> int(calculate_age(-657244800000)) - years_since_create - 73 - >>> int(calculate_age(46915200000)) - years_since_create - 51 + int: The age in years. + + Examples: + >>> today = date(2024, 1, 12) + >>> years_old(birth_timestamp=datetime(1959, 11, 20).timestamp(), today=today) + 64 + >>> years_old(birth_timestamp=datetime(1970, 2, 13).timestamp(), today=today) + 53 + >>> all( + ... years_old(datetime(today.year - i, 1, 12).timestamp(), today=today) == i + ... for i in range(1, 111) + ... ) + True """ - # Convert date from milliseconds to seconds - unix_date /= 1000 - - if unix_date < 0: - # Handle timestamp before epoch - epoch = datetime.fromtimestamp(0, tz=UTC) - seconds_since_epoch = (datetime.now(tz=UTC) - epoch).seconds - birthdate = ( - epoch - timedelta(seconds=abs(unix_date) - seconds_since_epoch) - ).date() - else: - birthdate = datetime.fromtimestamp(unix_date, tz=UTC).date() - return str( - TODAY.year - - birthdate.year - - ((TODAY.month, TODAY.day) < (birthdate.month, birthdate.day)) + today = today or TODAY.date() + birth_date = datetime.fromtimestamp(birth_timestamp, tz=UTC).date() + return (today.year - birth_date.year) - ( + (today.month, today.day) < (birth_date.month, birth_date.day) ) -def get_forbes_real_time_billionaires() -> list[dict[str, str]]: - """Get top 10 realtime billionaires using forbes API. +def get_forbes_real_time_billionaires() -> list[dict[str, int | str]]: + """ + Get the top 10 real-time billionaires using Forbes API. Returns: List of top 10 realtime billionaires data. @@ -66,21 +65,22 @@ def get_forbes_real_time_billionaires() -> list[dict[str, str]]: "Country": person["countryOfCitizenship"], "Gender": person["gender"], "Worth ($)": f"{person['finalWorth'] / 1000:.1f} Billion", - "Age": calculate_age(person["birthDate"]), + "Age": years_old(person["birthDate"]), } for person in response_json["personList"]["personsLists"] ] -def display_billionaires(forbes_billionaires: list[dict[str, str]]) -> None: - """Display Forbes real time billionaires in a rich table. +def display_billionaires(forbes_billionaires: list[dict[str, int | str]]) -> None: + """ + Display Forbes real-time billionaires in a rich table. Args: - forbes_billionaires (list): Forbes top 10 real time billionaires + forbes_billionaires (list): Forbes top 10 real-time billionaires """ table = rich_table.Table( - title=f"Forbes Top {LIMIT} Real Time Billionaires at {TODAY:%Y-%m-%d %H:%M}", + title=f"Forbes Top {LIMIT} Real-Time Billionaires at {TODAY:%Y-%m-%d %H:%M}", style="green", highlight=True, box=box.SQUARE, From a56f24e83d971c8f49d194b859b9b7acbf7df084 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Fri, 12 Jan 2024 17:46:26 +0100 Subject: [PATCH 2668/2908] fix: use `GITHUB_ACTOR` in `git config` (#11233) --- .github/workflows/directory_writer.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index 702c15f1e29b..e92c93604904 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -15,8 +15,8 @@ jobs: - 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 config --global user.name "$GITHUB_ACTOR" + 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: | From ffb93adf46971be35699e7642d79e90284b3c7f1 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:25:59 +0100 Subject: [PATCH 2669/2908] chore: update `actions/setup-python` to `v5` (#11236) --- .github/workflows/directory_writer.yml | 2 +- .github/workflows/project_euler.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index e92c93604904..55d89f455a25 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.x - name: Write DIRECTORY.md diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml index 7bbccf76e192..59e1208a650d 100644 --- a/.github/workflows/project_euler.yml +++ b/.github/workflows/project_euler.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.x - name: Install pytest and pytest-cov @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.x - name: Install pytest and requests From 13559aee437dab6ed88ecb1a6737cb39094c9e24 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Sat, 13 Jan 2024 12:24:58 +0100 Subject: [PATCH 2670/2908] style: use proper indentation in `ruff.yml` (#11237) * style: use proper indentation in `ruff.yml` * chore: run `prettier` on `yml` files * Update .pre-commit-config.yaml * Update .pre-commit-config.yaml * Update .pre-commit-config.yaml * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update .pre-commit-config.yaml * chore: run prettier on workflow files --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 10 +++++----- .github/workflows/ruff.yml | 6 +++--- .pre-commit-config.yaml | 8 +++++++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1631feb2ba06..906edfdae1ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,10 +25,10 @@ jobs: - name: Run tests # TODO: #8818 Re-enable quantum tests run: pytest - --ignore=quantum/q_fourier_transform.py - --ignore=project_euler/ - --ignore=scripts/validate_solutions.py - --cov-report=term-missing:skip-covered - --cov=. . + --ignore=quantum/q_fourier_transform.py + --ignore=project_euler/ + --ignore=scripts/validate_solutions.py + --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/ruff.yml b/.github/workflows/ruff.yml index 496f1460e074..9ebabed3600a 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -11,6 +11,6 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - run: pip install --user ruff - - run: ruff --output-format=github . + - uses: actions/checkout@v4 + - run: pip install --user ruff + - run: ruff --output-format=github . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e06ba7a5250..31e141049441 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - repo: https://github.com/MarcoGorelli/auto-walrus rev: v0.2.2 hooks: - - id: auto-walrus + - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.11 @@ -59,3 +59,9 @@ repos: - --install-types # See mirrors-mypy README.md - --non-interactive additional_dependencies: [types-requests] + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.1.0" + hooks: + - id: prettier + types_or: [toml, yaml] From dd47651bfca06b31941827ed3f41517bf5718508 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 19:19:36 +0100 Subject: [PATCH 2671/2908] [pre-commit.ci] pre-commit autoupdate (#11246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.11 → v0.1.13](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.11...v0.1.13) - [github.com/tox-dev/pyproject-fmt: 1.5.3 → 1.6.0](https://github.com/tox-dev/pyproject-fmt/compare/1.5.3...1.6.0) - [github.com/pre-commit/mirrors-prettier: v3.1.0 → v4.0.0-alpha.8](https://github.com/pre-commit/mirrors-prettier/compare/v3.1.0...v4.0.0-alpha.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31e141049441..97603510b426 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.11 + rev: v0.1.13 hooks: - id: ruff @@ -33,7 +33,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.5.3" + rev: "1.6.0" hooks: - id: pyproject-fmt @@ -61,7 +61,7 @@ repos: additional_dependencies: [types-requests] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.1.0" + rev: "v4.0.0-alpha.8" hooks: - id: prettier types_or: [toml, yaml] From 4b6f688344b8347f555f10ca04b80ee36b5a1e82 Mon Sep 17 00:00:00 2001 From: AtomicVar Date: Tue, 16 Jan 2024 16:39:54 +0800 Subject: [PATCH 2672/2908] Use compiled black as the pre-commit formatter (#11247) * Use compiled black as the pre-commit formatter * ruff-format * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Keep GitHub Actions up to date with Dependabot --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/.github/dependabot.yml | 8 ++++++++ .pre-commit-config.yaml | 6 +----- audio_filters/butterworth_filter.py | 16 ++++++++++++---- conversions/convert_number_to_words.py | 2 +- digital_image_processing/filters/gabor_filter.py | 6 +++--- ...rian_path_and_circuit_for_undirected_graph.py | 2 +- physics/n_body_simulation.py | 4 +--- project_euler/problem_056/sol1.py | 4 +--- 8 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 .github/.github/dependabot.yml diff --git a/.github/.github/dependabot.yml b/.github/.github/dependabot.yml new file mode 100644 index 000000000000..15e494ec867e --- /dev/null +++ b/.github/.github/dependabot.yml @@ -0,0 +1,8 @@ +# Keep GitHub Actions up to date with Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97603510b426..38cc7c8fc3ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,11 +19,7 @@ repos: rev: v0.1.13 hooks: - id: ruff - - - repo: https://github.com/psf/black - rev: 23.12.1 - hooks: - - id: black + - id: ruff-format - repo: https://github.com/codespell-project/codespell rev: v2.2.6 diff --git a/audio_filters/butterworth_filter.py b/audio_filters/butterworth_filter.py index cffedb7a68fd..6449bc3f3dce 100644 --- a/audio_filters/butterworth_filter.py +++ b/audio_filters/butterworth_filter.py @@ -11,7 +11,9 @@ def make_lowpass( - frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008 + frequency: int, + samplerate: int, + q_factor: float = 1 / sqrt(2), # noqa: B008 ) -> IIRFilter: """ Creates a low-pass filter @@ -39,7 +41,9 @@ def make_lowpass( def make_highpass( - frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008 + frequency: int, + samplerate: int, + q_factor: float = 1 / sqrt(2), # noqa: B008 ) -> IIRFilter: """ Creates a high-pass filter @@ -67,7 +71,9 @@ def make_highpass( def make_bandpass( - frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008 + frequency: int, + samplerate: int, + q_factor: float = 1 / sqrt(2), # noqa: B008 ) -> IIRFilter: """ Creates a band-pass filter @@ -96,7 +102,9 @@ def make_bandpass( def make_allpass( - frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008 + frequency: int, + samplerate: int, + q_factor: float = 1 / sqrt(2), # noqa: B008 ) -> IIRFilter: """ Creates an all-pass filter diff --git a/conversions/convert_number_to_words.py b/conversions/convert_number_to_words.py index 0c428928b31d..dbab44c72e1f 100644 --- a/conversions/convert_number_to_words.py +++ b/conversions/convert_number_to_words.py @@ -41,7 +41,7 @@ def max_value(cls, system: str) -> int: >>> NumberingSystem.max_value("indian") == 10**19 - 1 True """ - match (system_enum := cls[system.upper()]): + match system_enum := cls[system.upper()]: case cls.SHORT: max_exp = system_enum.value[0][0] + 3 case cls.LONG: diff --git a/digital_image_processing/filters/gabor_filter.py b/digital_image_processing/filters/gabor_filter.py index 8f9212a35a79..aaec567f4c99 100644 --- a/digital_image_processing/filters/gabor_filter.py +++ b/digital_image_processing/filters/gabor_filter.py @@ -48,9 +48,9 @@ def gabor_filter_kernel( _y = -sin_theta * px + cos_theta * py # fill kernel - gabor[y, x] = np.exp( - -(_x**2 + gamma**2 * _y**2) / (2 * sigma**2) - ) * np.cos(2 * np.pi * _x / lambd + psi) + gabor[y, x] = np.exp(-(_x**2 + gamma**2 * _y**2) / (2 * sigma**2)) * np.cos( + 2 * np.pi * _x / lambd + psi + ) return gabor diff --git a/graphs/eulerian_path_and_circuit_for_undirected_graph.py b/graphs/eulerian_path_and_circuit_for_undirected_graph.py index 6b4ea8e21e8b..5b146eaa845b 100644 --- a/graphs/eulerian_path_and_circuit_for_undirected_graph.py +++ b/graphs/eulerian_path_and_circuit_for_undirected_graph.py @@ -56,7 +56,7 @@ def main(): g4 = {1: [2, 3], 2: [1, 3], 3: [1, 2]} g5 = { 1: [], - 2: [] + 2: [], # all degree is zero } max_node = 10 diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index 46330844df61..ec008784ba62 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -165,9 +165,7 @@ def update_system(self, delta_time: float) -> None: # Calculation of the distance using Pythagoras's theorem # Extra factor due to the softening technique - distance = (dif_x**2 + dif_y**2 + self.softening_factor) ** ( - 1 / 2 - ) + distance = (dif_x**2 + dif_y**2 + self.softening_factor) ** (1 / 2) # Newton's law of universal gravitation. force_x += ( diff --git a/project_euler/problem_056/sol1.py b/project_euler/problem_056/sol1.py index c772bec58692..828dbd3a8ddf 100644 --- a/project_euler/problem_056/sol1.py +++ b/project_euler/problem_056/sol1.py @@ -30,9 +30,7 @@ def solution(a: int = 100, b: int = 100) -> 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) + sum(int(x) for x in str(base**power)) for base in range(a) for power in range(b) ) From 0101dd42dc83f567bddebc386b17f2b4f6bbaa36 Mon Sep 17 00:00:00 2001 From: Ataf Fazledin Ahamed Date: Tue, 16 Jan 2024 14:43:33 +0600 Subject: [PATCH 2673/2908] Fixed Inappropriate Logical Expression (#11203) Signed-off-by: fazledyn-or --- data_structures/binary_tree/red_black_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 4ebe0e927ca0..fc299301da8a 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -451,7 +451,7 @@ def is_left(self) -> bool: """Returns true iff this node is the left child of its parent.""" if self.parent is None: return False - return self.parent.left is self.parent.left is self + return self.parent.left is self def is_right(self) -> bool: """Returns true iff this node is the right child of its parent.""" From 05a5cdacc3cfd9814ad6f5cb2d4dec86109b640a Mon Sep 17 00:00:00 2001 From: Suyash Dongre <109069262+Suyashd999@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:09:27 +0530 Subject: [PATCH 2674/2908] Added doctest to skew_heap.py (#11147) * Added doctest to skew_heap.py * Update skew_heap.py * Update data_structures/heap/skew_heap.py Co-authored-by: Saptadeep Banerjee <69459134+imSanko@users.noreply.github.com> * Update skew_heap.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update skew_heap.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Saptadeep Banerjee <69459134+imSanko@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/heap/skew_heap.py | 45 +++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index c4c13b08276a..0839db711cb1 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -21,14 +21,55 @@ def __init__(self, value: T) -> None: @property def value(self) -> T: - """Return the value of the node.""" + """ + Return the value of the node. + + >>> SkewNode(0).value + 0 + >>> SkewNode(3.14159).value + 3.14159 + >>> SkewNode("hello").value + 'hello' + >>> SkewNode(None).value + + >>> SkewNode(True).value + True + >>> SkewNode([]).value + [] + >>> SkewNode({}).value + {} + >>> SkewNode(set()).value + set() + >>> SkewNode(0.0).value + 0.0 + >>> SkewNode(-1e-10).value + -1e-10 + >>> SkewNode(10).value + 10 + >>> SkewNode(-10.5).value + -10.5 + >>> SkewNode().value + Traceback (most recent call last): + ... + TypeError: SkewNode.__init__() missing 1 required positional argument: 'value' + """ return self._value @staticmethod def merge( root1: SkewNode[T] | None, root2: SkewNode[T] | None ) -> SkewNode[T] | None: - """Merge 2 nodes together.""" + """ + Merge 2 nodes together. + >>> SkewNode.merge(SkewNode(10),SkewNode(-10.5)).value + -10.5 + >>> SkewNode.merge(SkewNode(10),SkewNode(10.5)).value + 10 + >>> SkewNode.merge(SkewNode(10),SkewNode(10)).value + 10 + >>> SkewNode.merge(SkewNode(-100),SkewNode(-10.5)).value + -100 + """ if not root1: return root2 From 3952ba703a5b84a37891a001037c5c366d20941a Mon Sep 17 00:00:00 2001 From: AtomicVar Date: Thu, 18 Jan 2024 20:41:29 +0800 Subject: [PATCH 2675/2908] Add categorical focal cross-entropy loss algorithm (#11248) --- machine_learning/loss_functions.py | 102 +++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py index 36a760326f3d..f05fa0cbe686 100644 --- a/machine_learning/loss_functions.py +++ b/machine_learning/loss_functions.py @@ -148,6 +148,108 @@ def categorical_cross_entropy( return -np.sum(y_true * np.log(y_pred)) +def categorical_focal_cross_entropy( + y_true: np.ndarray, + y_pred: np.ndarray, + alpha: np.ndarray = None, + gamma: float = 2.0, + epsilon: float = 1e-15, +) -> float: + """ + Calculate the mean categorical focal cross-entropy (CFCE) loss between true + labels and predicted probabilities for multi-class classification. + + CFCE loss is a generalization of binary focal cross-entropy for multi-class + classification. It addresses class imbalance by focusing on hard examples. + + CFCE = -Σ alpha * (1 - y_pred)**gamma * y_true * log(y_pred) + + Reference: [Lin et al., 2018](https://arxiv.org/pdf/1708.02002.pdf) + + Parameters: + - y_true: True labels in one-hot encoded form. + - y_pred: Predicted probabilities for each class. + - alpha: Array of weighting factors for each class. + - gamma: Focusing parameter for modulating the loss (default: 2.0). + - epsilon: Small constant to avoid numerical instability. + + Returns: + - The mean categorical focal cross-entropy loss. + + >>> true_labels = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1], [0.0, 0.1, 0.9]]) + >>> alpha = np.array([0.6, 0.2, 0.7]) + >>> categorical_focal_cross_entropy(true_labels, pred_probs, alpha) + 0.0025966118981496423 + + >>> true_labels = np.array([[0, 1, 0], [0, 0, 1]]) + >>> pred_probs = np.array([[0.05, 0.95, 0], [0.1, 0.8, 0.1]]) + >>> alpha = np.array([0.25, 0.25, 0.25]) + >>> categorical_focal_cross_entropy(true_labels, pred_probs, alpha) + 0.23315276982014324 + + >>> true_labels = np.array([[1, 0], [0, 1]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_cross_entropy(true_labels, pred_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same shape. + + >>> true_labels = np.array([[2, 0, 1], [1, 0, 0]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_focal_cross_entropy(true_labels, pred_probs) + Traceback (most recent call last): + ... + ValueError: y_true must be one-hot encoded. + + >>> true_labels = np.array([[1, 0, 1], [1, 0, 0]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) + >>> categorical_focal_cross_entropy(true_labels, pred_probs) + Traceback (most recent call last): + ... + ValueError: y_true must be one-hot encoded. + + >>> true_labels = np.array([[1, 0, 0], [0, 1, 0]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.1], [0.2, 0.7, 0.1]]) + >>> categorical_focal_cross_entropy(true_labels, pred_probs) + Traceback (most recent call last): + ... + ValueError: Predicted probabilities must sum to approximately 1. + + >>> true_labels = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1], [0.0, 0.1, 0.9]]) + >>> alpha = np.array([0.6, 0.2]) + >>> categorical_focal_cross_entropy(true_labels, pred_probs, alpha) + Traceback (most recent call last): + ... + ValueError: Length of alpha must match the number of classes. + """ + if y_true.shape != y_pred.shape: + raise ValueError("Shape of y_true and y_pred must be the same.") + + if alpha is None: + alpha = np.ones(y_true.shape[1]) + + if np.any((y_true != 0) & (y_true != 1)) or np.any(y_true.sum(axis=1) != 1): + raise ValueError("y_true must be one-hot encoded.") + + if len(alpha) != y_true.shape[1]: + raise ValueError("Length of alpha must match the number of classes.") + + if not np.all(np.isclose(np.sum(y_pred, axis=1), 1, rtol=epsilon, atol=epsilon)): + raise ValueError("Predicted probabilities must sum to approximately 1.") + + # Clip predicted probabilities to avoid log(0) + y_pred = np.clip(y_pred, epsilon, 1 - epsilon) + + # Calculate loss for each class and sum across classes + cfce_loss = -np.sum( + alpha * np.power(1 - y_pred, gamma) * y_true * np.log(y_pred), axis=1 + ) + + return np.mean(cfce_loss) + + def hinge_loss(y_true: np.ndarray, y_pred: np.ndarray) -> float: """ Calculate the mean hinge loss for between true labels and predicted probabilities From b01571dc4f5754d3da44b8a0b6dabb44986c666e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:20:43 +0100 Subject: [PATCH 2676/2908] [pre-commit.ci] pre-commit autoupdate (#11255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.13 → v0.1.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.13...v0.1.14) - [github.com/tox-dev/pyproject-fmt: 1.6.0 → 1.7.0](https://github.com/tox-dev/pyproject-fmt/compare/1.6.0...1.7.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 38cc7c8fc3ff..7fae092d043c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.13 + rev: v0.1.14 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.6.0" + rev: "1.7.0" hooks: - id: pyproject-fmt From b092d7755cf94b4758440b68bc97ac30154f4c55 Mon Sep 17 00:00:00 2001 From: Geoffrey Logovi <52314615+geoffreylgv@users.noreply.github.com> Date: Wed, 24 Jan 2024 06:15:39 +0000 Subject: [PATCH 2677/2908] fixes #11256 : computer vision link update in .computer_vision/README.md (#11257) --- computer_vision/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computer_vision/README.md b/computer_vision/README.md index 8d2f4a130d05..1657128fd25e 100644 --- a/computer_vision/README.md +++ b/computer_vision/README.md @@ -8,4 +8,4 @@ Image processing and computer vision are a little different from each other. Ima 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 c0e700c91c63c1b3ea50575b10a6c1665dfd6404 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 21:00:37 +0100 Subject: [PATCH 2678/2908] [pre-commit.ci] pre-commit autoupdate (#11261) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/abravalheri/validate-pyproject: v0.15 → v0.16](https://github.com/abravalheri/validate-pyproject/compare/v0.15...v0.16) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fae092d043c..0d13745a5a47 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.15 + rev: v0.16 hooks: - id: validate-pyproject From c1d29ba459648bf8111e19e32988cb36ee8a94b0 Mon Sep 17 00:00:00 2001 From: AtomicVar Date: Tue, 30 Jan 2024 16:18:56 +0800 Subject: [PATCH 2679/2908] Add smooth l1 loss algorithm (#11239) --- machine_learning/loss_functions.py | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py index f05fa0cbe686..16e5a3278b73 100644 --- a/machine_learning/loss_functions.py +++ b/machine_learning/loss_functions.py @@ -573,6 +573,62 @@ def perplexity_loss( return np.mean(perp_losses) +def smooth_l1_loss(y_true: np.ndarray, y_pred: np.ndarray, beta: float = 1.0) -> float: + """ + Calculate the Smooth L1 Loss between y_true and y_pred. + + The Smooth L1 Loss is less sensitive to outliers than the L2 Loss and is often used + in regression problems, such as object detection. + + Smooth L1 Loss = + 0.5 * (x - y)^2 / beta, if |x - y| < beta + |x - y| - 0.5 * beta, otherwise + + Reference: + https://pytorch.org/docs/stable/generated/torch.nn.SmoothL1Loss.html + + Args: + y_true: Array of true values. + y_pred: Array of predicted values. + beta: Specifies the threshold at which to change between L1 and L2 loss. + + Returns: + The calculated Smooth L1 Loss between y_true and y_pred. + + Raises: + ValueError: If the length of the two arrays is not the same. + + >>> y_true = np.array([3, 5, 2, 7]) + >>> y_pred = np.array([2.9, 4.8, 2.1, 7.2]) + >>> smooth_l1_loss(y_true, y_pred, 1.0) + 0.012500000000000022 + + >>> y_true = np.array([2, 4, 6]) + >>> y_pred = np.array([1, 5, 7]) + >>> smooth_l1_loss(y_true, y_pred, 1.0) + 0.5 + + >>> y_true = np.array([1, 3, 5, 7]) + >>> y_pred = np.array([1, 3, 5, 7]) + >>> smooth_l1_loss(y_true, y_pred, 1.0) + 0.0 + + >>> y_true = np.array([1, 3, 5]) + >>> y_pred = np.array([1, 3, 5, 7]) + >>> smooth_l1_loss(y_true, y_pred, 1.0) + Traceback (most recent call last): + ... + ValueError: The length of the two arrays should be the same. + """ + + if len(y_true) != len(y_pred): + raise ValueError("The length of the two arrays should be the same.") + + diff = np.abs(y_true - y_pred) + loss = np.where(diff < beta, 0.5 * diff**2 / beta, diff - 0.5 * beta) + return np.mean(loss) + + if __name__ == "__main__": import doctest From 8995f45cb505e9cb1aafe3b35c6a00d9aff5f871 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 1 Feb 2024 07:10:35 +0100 Subject: [PATCH 2680/2908] Rename .github/.github/dependabot.yml to .github/dependabot.yml (#11264) * Rename .github/.github/dependabot.yml to .github/dependabot.yml * runs-on: macos-14 # ubuntu-latest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update build.yml --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/{.github => }/dependabot.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{.github => }/dependabot.yml (100%) diff --git a/.github/.github/dependabot.yml b/.github/dependabot.yml similarity index 100% rename from .github/.github/dependabot.yml rename to .github/dependabot.yml From 6a169740e8c71c6c8236b09eb7b523895fedcfbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 08:11:41 +0100 Subject: [PATCH 2681/2908] Bump actions/cache from 3 to 4 (#11265) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 906edfdae1ed..a113b4608678 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: with: python-version: 3.12 allow-prereleases: true - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} From 4128f19170135cf7ccadb2afa8b2ab0a464c5765 Mon Sep 17 00:00:00 2001 From: Anthony Klarman <148516349+tonguegrease@users.noreply.github.com> Date: Fri, 2 Feb 2024 03:22:58 -0500 Subject: [PATCH 2682/2908] Fixed lines that needed to be uncommented after Hacktoberfest (#11267) * uncommented lines * uncommented lines * Update CODEOWNERS --------- Co-authored-by: Christian Clauss --- .github/CODEOWNERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a0531cdeec69..d2ac43c7df31 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,15 +21,15 @@ # /cellular_automata/ -# /ciphers/ @cclauss # TODO: Uncomment this line after Hacktoberfest +# /ciphers/ # /compression/ # /computer_vision/ -# /conversions/ @cclauss # TODO: Uncomment this line after Hacktoberfest +# /conversions/ -# /data_structures/ @cclauss # TODO: Uncomment this line after Hacktoberfest +# /data_structures/ # /digital_image_processing/ @@ -67,7 +67,7 @@ # /neural_network/ -# /other/ @cclauss # TODO: Uncomment this line after Hacktoberfest +# /other/ # /project_euler/ @@ -81,7 +81,7 @@ # /sorts/ -# /strings/ @cclauss # TODO: Uncomment this line after Hacktoberfest +# /strings/ # /traversals/ From ed8d9209daff975eb3be6e0bf8cfa13e330347ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 20:48:10 +0100 Subject: [PATCH 2683/2908] [pre-commit.ci] pre-commit autoupdate (#11275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.14 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.14...v0.2.0) * Upgrade pyproject.toml * Revert sudoku_solver.py RUF017 Avoid quadratic list summation --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- ciphers/mixed_keyword_cypher.py | 2 +- data_structures/arrays/sudoku_solver.py | 2 +- data_structures/linked_list/is_palindrome.py | 4 +--- .../filters/gaussian_filter.py | 4 +--- electronics/resistor_equivalence.py | 8 ++------ hashes/hamming_code.py | 18 ++++++------------ machine_learning/k_means_clust.py | 2 +- .../sequential_minimum_optimization.py | 2 +- neural_network/convolution_neural_network.py | 6 +++--- pyproject.toml | 14 +++++++------- 11 files changed, 25 insertions(+), 39 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0d13745a5a47..c29c6982643e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.14 + rev: v0.2.0 hooks: - id: ruff - id: ruff-format diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index b984808fced6..1b186108a73e 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -67,7 +67,7 @@ def mixed_keyword( if verbose: print(mapping) # create the encrypted text by mapping the plaintext to the modified alphabet - return "".join(mapping[char] if char in mapping else char for char in plaintext) + return "".join(mapping.get(char, char) for char in plaintext) if __name__ == "__main__": diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index 8d38bd7295ea..20ac32e3b071 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -22,7 +22,7 @@ def cross(items_a, items_b): + [cross(rs, cs) for rs in ("ABC", "DEF", "GHI") for cs in ("123", "456", "789")] ) units = {s: [u for u in unitlist if s in u] for s in squares} -peers = {s: set(sum(units[s], [])) - {s} for s in squares} +peers = {s: set(sum(units[s], [])) - {s} for s in squares} # noqa: RUF017 def test(): diff --git a/data_structures/linked_list/is_palindrome.py b/data_structures/linked_list/is_palindrome.py index f949d9a2f201..da788e3e5045 100644 --- a/data_structures/linked_list/is_palindrome.py +++ b/data_structures/linked_list/is_palindrome.py @@ -171,11 +171,9 @@ def is_palindrome_dict(head: ListNode | None) -> bool: if len(v) % 2 != 0: middle += 1 else: - step = 0 - for i in range(len(v)): + for step, i in enumerate(range(len(v))): if v[i] + v[len(v) - 1 - step] != checksum: return False - step += 1 if middle > 1: return False return True diff --git a/digital_image_processing/filters/gaussian_filter.py b/digital_image_processing/filters/gaussian_filter.py index 87fa67fb65ea..634d836e5edc 100644 --- a/digital_image_processing/filters/gaussian_filter.py +++ b/digital_image_processing/filters/gaussian_filter.py @@ -22,11 +22,9 @@ 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)): + for row, (i, j) in enumerate(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) diff --git a/electronics/resistor_equivalence.py b/electronics/resistor_equivalence.py index 55e7f2d6b5d2..c4ea7d4b757e 100644 --- a/electronics/resistor_equivalence.py +++ b/electronics/resistor_equivalence.py @@ -20,13 +20,11 @@ def resistor_parallel(resistors: list[float]) -> float: """ first_sum = 0.00 - index = 0 - for resistor in resistors: + for index, resistor in enumerate(resistors): if resistor <= 0: msg = f"Resistor at index {index} has a negative or zero value!" raise ValueError(msg) first_sum += 1 / float(resistor) - index += 1 return 1 / first_sum @@ -44,13 +42,11 @@ def resistor_series(resistors: list[float]) -> float: ValueError: Resistor at index 2 has a negative value! """ sum_r = 0.00 - index = 0 - for resistor in resistors: + for index, resistor in enumerate(resistors): sum_r += resistor if resistor < 0: msg = f"Resistor at index {index} has a negative value!" raise ValueError(msg) - index += 1 return sum_r diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 4a6efcf23f63..b34fdd4c7a74 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -123,8 +123,7 @@ def emitter_converter(size_par, data): # Bit counter one for a given parity cont_bo = 0 # counter to control the loop reading - cont_loop = 0 - for x in data_ord: + for cont_loop, x in enumerate(data_ord): if x is not None: try: aux = (bin_pos[cont_loop])[-1 * (bp)] @@ -132,7 +131,6 @@ def emitter_converter(size_par, data): aux = "0" if aux == "1" and x == "1": cont_bo += 1 - cont_loop += 1 parity.append(cont_bo % 2) qtd_bp += 1 @@ -164,10 +162,10 @@ def receptor_converter(size_par, data): parity_received = [] data_output = [] - for x in range(1, len(data) + 1): + for i, item in enumerate(data, 1): # Performs a template of bit positions - who should be given, # and who should be parity - if qtd_bp < size_par and (np.log(x) / np.log(2)).is_integer(): + if qtd_bp < size_par and (np.log(i) / np.log(2)).is_integer(): data_out_gab.append("P") qtd_bp = qtd_bp + 1 else: @@ -175,10 +173,9 @@ def receptor_converter(size_par, data): # Sorts the data to the new output size if data_out_gab[-1] == "D": - data_output.append(data[cont_data]) + data_output.append(item) else: - parity_received.append(data[cont_data]) - cont_data += 1 + parity_received.append(item) # -----------calculates the parity with the data data_out = [] @@ -215,9 +212,7 @@ def receptor_converter(size_par, data): for bp in range(1, size_par + 1): # Bit counter one for a certain parity cont_bo = 0 - # Counter to control loop reading - cont_loop = 0 - for x in data_ord: + for cont_loop, x in enumerate(data_ord): if x is not None: try: aux = (bin_pos[cont_loop])[-1 * (bp)] @@ -225,7 +220,6 @@ def receptor_converter(size_par, data): aux = "0" if aux == "1" and x == "1": cont_bo += 1 - cont_loop += 1 parity.append(str(cont_bo % 2)) qtd_bp += 1 diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index ebad66ac8e8f..4a219edc3bb1 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -237,7 +237,7 @@ def report_generator( [ ("sum", "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_without_zeros", lambda x: x.replace(0, np.nan).mean()), ( "mean_25-75", lambda x: np.mean( diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 9e2304859f8d..9ee8c52fb2e9 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -589,7 +589,7 @@ def plot_partition_boundary( ax.contour( xrange, yrange, - np.mat(grid).T, + np.asmatrix(grid).T, levels=(-1, 0, 1), linestyles=("--", "-", "--"), linewidths=(1, 1, 1), diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index f2e88fe7bd88..e9726a0cb4a7 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -41,11 +41,11 @@ def __init__( 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) + np.asmatrix(-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.wkj = np.asmatrix(-1 * np.random.rand(self.num_bp3, self.num_bp2) + 0.5) + self.vji = np.asmatrix(-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 diff --git a/pyproject.toml b/pyproject.toml index c7163dc78371..2e7da519da8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.ruff] -ignore = [ # `ruff rule S101` for a description of that rule +lint.ignore = [ # `ruff rule S101` for a description of that rule "ARG001", # Unused function argument `amount` -- FIX ME? "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME @@ -31,7 +31,7 @@ ignore = [ # `ruff rule S101` for a description of that rule "SLF001", # Private member accessed: `_Iterator` -- FIX ME "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX ] -select = [ # https://beta.ruff.rs/docs/rules +lint.select = [ # https://beta.ruff.rs/docs/rules "A", # flake8-builtins "ARG", # flake8-unused-arguments "ASYNC", # flake8-async @@ -84,13 +84,13 @@ select = [ # https://beta.ruff.rs/docs/rules # "TCH", # flake8-type-checking # "TRY", # tryceratops ] -show-source = true -target-version = "py311" +output-format = "full" +target-version = "py312" -[tool.ruff.mccabe] # DO NOT INCREASE THIS VALUE +[tool.ruff.lint.mccabe] # DO NOT INCREASE THIS VALUE max-complexity = 17 # default: 10 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "arithmetic_analysis/newton_raphson.py" = ["PGH001"] "audio_filters/show_response.py" = ["ARG002"] "data_structures/binary_tree/binary_search_tree_recursive.py" = ["BLE001"] @@ -110,7 +110,7 @@ max-complexity = 17 # default: 10 "project_euler/problem_099/sol1.py" = ["SIM115"] "sorts/external_sort.py" = ["SIM115"] -[tool.ruff.pylint] # DO NOT INCREASE THESE VALUES +[tool.ruff.lint.pylint] # DO NOT INCREASE THESE VALUES allow-magic-value-types = ["float", "int", "str"] max-args = 10 # default: 5 max-branches = 20 # default: 12 From 5d6846b2bd1fa16edfc89025e00f69a802774faa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:53:20 +0100 Subject: [PATCH 2684/2908] [pre-commit.ci] pre-commit autoupdate (#11292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.2.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.2.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c29c6982643e..79d7d58d0863 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.0 + rev: v0.2.1 hooks: - id: ruff - id: ruff-format From c6ca1942e14a6e88c7ea1b96ef3a6d17ca843f52 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:00:06 +0100 Subject: [PATCH 2685/2908] [pre-commit.ci] pre-commit autoupdate (#11296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.1 → v0.2.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.1...v0.2.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 79d7d58d0863..be8364a7fc0b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.2.2 hooks: - id: ruff - id: ruff-format From fd27953d44416a5f1541ed6e6923844b6070d086 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 12 Mar 2024 11:35:49 +0300 Subject: [PATCH 2686/2908] Reenable files when TensorFlow supports the current Python (#11318) * Remove python_version < '3.12' for tensorflow * Reenable dynamic_programming/k_means_clustering_tensorflow.py * updating DIRECTORY.md * Try to fix ruff * Try to fix ruff * Try to fix ruff * Try to fix ruff * Try to fix ruff * Reenable machine_learning/lstm/lstm_prediction.py * updating DIRECTORY.md * Try to fix ruff * Reenable computer_vision/cnn_classification.py * updating DIRECTORY.md * Reenable neural_network/input_data.py * updating DIRECTORY.md * Try to fix ruff * Try to fix ruff * Try to fix mypy * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Try to fix ruff * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: MaximSmolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 5 +++++ ciphers/rsa_cipher.py | 17 +++++++---------- ...on.py.DISABLED.txt => cnn_classification.py} | 0 ...LED.txt => k_means_clustering_tensorflow.py} | 0 ...ction.py.DISABLED.txt => lstm_prediction.py} | 8 ++++---- ...put_data.py.DEPRECATED.txt => input_data.py} | 9 +++++++-- other/lfu_cache.py | 5 +++-- requirements.txt | 2 +- 8 files changed, 27 insertions(+), 19 deletions(-) rename computer_vision/{cnn_classification.py.DISABLED.txt => cnn_classification.py} (100%) rename dynamic_programming/{k_means_clustering_tensorflow.py.DISABLED.txt => k_means_clustering_tensorflow.py} (100%) rename machine_learning/lstm/{lstm_prediction.py.DISABLED.txt => lstm_prediction.py} (90%) rename neural_network/{input_data.py.DEPRECATED.txt => input_data.py} (98%) diff --git a/DIRECTORY.md b/DIRECTORY.md index b5392fd09114..2f828aa512a9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -134,6 +134,7 @@ * [Run Length Encoding](compression/run_length_encoding.py) ## Computer Vision + * [Cnn Classification](computer_vision/cnn_classification.py) * [Flip Augmentation](computer_vision/flip_augmentation.py) * [Haralick Descriptors](computer_vision/haralick_descriptors.py) * [Harris Corner](computer_vision/harris_corner.py) @@ -344,6 +345,7 @@ * [Floyd Warshall](dynamic_programming/floyd_warshall.py) * [Integer Partition](dynamic_programming/integer_partition.py) * [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py) + * [K Means Clustering Tensorflow](dynamic_programming/k_means_clustering_tensorflow.py) * [Knapsack](dynamic_programming/knapsack.py) * [Largest Divisible Subset](dynamic_programming/largest_divisible_subset.py) * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) @@ -571,6 +573,8 @@ * [Local Weighted Learning](machine_learning/local_weighted_learning/local_weighted_learning.py) * [Logistic Regression](machine_learning/logistic_regression.py) * [Loss Functions](machine_learning/loss_functions.py) + * Lstm + * [Lstm Prediction](machine_learning/lstm/lstm_prediction.py) * [Mfcc](machine_learning/mfcc.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polynomial Regression](machine_learning/polynomial_regression.py) @@ -801,6 +805,7 @@ * [Swish](neural_network/activation_functions/swish.py) * [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](neural_network/convolution_neural_network.py) + * [Input Data](neural_network/input_data.py) * [Simple Neural Network](neural_network/simple_neural_network.py) ## Other diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 9c41cdc5d472..3bc2ebe5fc74 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -76,11 +76,9 @@ def encrypt_and_write_to_file( key_size, n, e = read_key_file(key_filename) if key_size < block_size * 8: sys.exit( - "ERROR: Block size is {} bits and key size is {} 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.".format( - block_size * 8, key_size - ) + f"ERROR: Block size is {block_size * 8} bits and key size is {key_size} " + "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." ) encrypted_blocks = [str(i) for i in encrypt_message(message, (n, e), block_size)] @@ -102,11 +100,10 @@ def read_from_file_and_decrypt(message_filename: str, key_filename: str) -> str: if key_size < block_size * 8: sys.exit( - "ERROR: Block size is {} bits and key size is {} 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?".format( - block_size * 8, key_size - ) + f"ERROR: Block size is {block_size * 8} bits and key size is {key_size} " + "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?" ) encrypted_blocks = [] diff --git a/computer_vision/cnn_classification.py.DISABLED.txt b/computer_vision/cnn_classification.py similarity index 100% rename from computer_vision/cnn_classification.py.DISABLED.txt rename to computer_vision/cnn_classification.py diff --git a/dynamic_programming/k_means_clustering_tensorflow.py.DISABLED.txt b/dynamic_programming/k_means_clustering_tensorflow.py similarity index 100% rename from dynamic_programming/k_means_clustering_tensorflow.py.DISABLED.txt rename to dynamic_programming/k_means_clustering_tensorflow.py diff --git a/machine_learning/lstm/lstm_prediction.py.DISABLED.txt b/machine_learning/lstm/lstm_prediction.py similarity index 90% rename from machine_learning/lstm/lstm_prediction.py.DISABLED.txt rename to machine_learning/lstm/lstm_prediction.py index 16530e935ea7..ecbd451266ad 100644 --- a/machine_learning/lstm/lstm_prediction.py.DISABLED.txt +++ b/machine_learning/lstm/lstm_prediction.py @@ -17,11 +17,11 @@ 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] + sample_data = pd.read_csv("sample_data.csv", header=None) + len_data = sample_data.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 = sample_data.iloc[:, 1:2] + actual_data = actual_data.to_numpy().reshape(len_data, 1) actual_data = MinMaxScaler().fit_transform(actual_data) look_back = 10 forward_days = 5 diff --git a/neural_network/input_data.py.DEPRECATED.txt b/neural_network/input_data.py similarity index 98% rename from neural_network/input_data.py.DEPRECATED.txt rename to neural_network/input_data.py index a58e64907e45..2128449c03e9 100644 --- a/neural_network/input_data.py.DEPRECATED.txt +++ b/neural_network/input_data.py @@ -18,9 +18,9 @@ """ -import collections import gzip import os +import typing import urllib import numpy @@ -28,7 +28,12 @@ from tensorflow.python.platform import gfile from tensorflow.python.util.deprecation import deprecated -_Datasets = collections.namedtuple("_Datasets", ["train", "validation", "test"]) + +class _Datasets(typing.NamedTuple): + train: "_DataSet" + validation: "_DataSet" + test: "_DataSet" + # CVDF mirror of http://yann.lecun.com/exdb/mnist/ DEFAULT_SOURCE_URL = "https://storage.googleapis.com/cvdf-datasets/mnist/" diff --git a/other/lfu_cache.py b/other/lfu_cache.py index b68ba3a4605c..788fdf19bb60 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -24,8 +24,9 @@ def __init__(self, key: T | None, val: U | None): self.prev: DoubleLinkedListNode[T, U] | None = None def __repr__(self) -> str: - return "Node: key: {}, val: {}, freq: {}, has next: {}, has prev: {}".format( - self.key, self.val, self.freq, self.next is not None, self.prev is not None + return ( + f"Node: key: {self.key}, val: {self.val}, freq: {self.freq}, " + f"has next: {self.next is not None}, has prev: {self.prev is not None}" ) diff --git a/requirements.txt b/requirements.txt index 8937f6bb0dae..bb3d671393b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ rich scikit-learn statsmodels sympy -tensorflow ; python_version < '3.12' +tensorflow tweepy # yulewalker # uncomment once audio_filters/equal_loudness_filter.py is fixed typing_extensions From 5f95d6f805088aa7e21849b1ba97cdcf059333a9 Mon Sep 17 00:00:00 2001 From: guangwu Date: Tue, 12 Mar 2024 16:40:32 +0800 Subject: [PATCH 2687/2908] fix: function name typo (#11319) * fix: function name typo Signed-off-by: guoguangwu * lfu_cache.py: Use f-strings * rsa_cipher.py: Use f-strings --------- Signed-off-by: guoguangwu Co-authored-by: Christian Clauss --- ciphers/rsa_cipher.py | 3 +-- machine_learning/astar.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 3bc2ebe5fc74..ac9782a49fff 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -102,8 +102,7 @@ def read_from_file_and_decrypt(message_filename: str, key_filename: str) -> str: sys.exit( f"ERROR: Block size is {block_size * 8} bits and key size is {key_size} " "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?" + "than the key size. Were the correct key file and encrypted file specified?" ) encrypted_blocks = [] diff --git a/machine_learning/astar.py b/machine_learning/astar.py index 7a60ed225a2d..ff5208266343 100644 --- a/machine_learning/astar.py +++ b/machine_learning/astar.py @@ -57,7 +57,7 @@ def __init__(self, world_size=(5, 5)): def show(self): print(self.w) - def get_neigbours(self, cell): + def get_neighbours(self, cell): """ Return the neighbours of cell """ @@ -110,7 +110,7 @@ def astar(world, start, goal): _closed.append(_open.pop(min_f)) if current == goal: break - for n in world.get_neigbours(current): + for n in world.get_neighbours(current): for c in _closed: if c == n: continue From bc8df6de3143b417c4d174200fd7edd0dbba4ce3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 07:52:41 +0100 Subject: [PATCH 2688/2908] [pre-commit.ci] pre-commit autoupdate (#11322) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.2 → v0.3.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.2...v0.3.2) - [github.com/pre-commit/mirrors-mypy: v1.8.0 → v1.9.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.8.0...v1.9.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 +- backtracking/all_combinations.py | 7 +- backtracking/all_permutations.py | 9 ++- backtracking/all_subsequences.py | 1 + backtracking/coloring.py | 8 +- backtracking/hamiltonian_cycle.py | 10 +-- backtracking/minimax.py | 1 + backtracking/n_queens.py | 11 +-- backtracking/n_queens_math.py | 1 + backtracking/sudoku.py | 1 + backtracking/sum_of_subsets.py | 11 +-- boolean_algebra/nor_gate.py | 1 + cellular_automata/conways_game_of_life.py | 1 + cellular_automata/game_of_life.py | 3 +- cellular_automata/nagel_schrekenberg.py | 1 + ciphers/a1z26.py | 1 + ciphers/atbash.py | 3 +- ciphers/base32.py | 1 + ciphers/enigma_machine2.py | 1 + ciphers/fractionated_morse_cipher.py | 1 + ciphers/hill_cipher.py | 1 + ciphers/permutation_cipher.py | 1 + ciphers/rail_fence_cipher.py | 2 +- ciphers/rsa_factorization.py | 1 + ciphers/xor_cipher.py | 33 ++++---- compression/burrows_wheeler.py | 1 + compression/lempel_ziv.py | 4 +- compression/lempel_ziv_decompress.py | 4 +- compression/lz77.py | 1 - computer_vision/haralick_descriptors.py | 1 + computer_vision/horn_schunck.py | 16 ++-- conversions/decimal_to_hexadecimal.py | 2 +- conversions/prefix_conversions.py | 1 + conversions/temperature_conversions.py | 2 +- .../arrays/pairs_with_given_sum.py | 1 + data_structures/arrays/sparse_table.py | 15 ++-- data_structures/arrays/sudoku_solver.py | 1 + data_structures/binary_tree/avl_tree.py | 1 + .../binary_tree/binary_search_tree.py | 1 + .../binary_search_tree_recursive.py | 1 + .../binary_tree/binary_tree_node_sum.py | 1 - .../binary_tree/diameter_of_binary_tree.py | 1 + .../flatten_binarytree_to_linkedlist.py | 1 + .../binary_tree/floor_and_ceiling.py | 1 + data_structures/binary_tree/is_sorted.py | 1 + data_structures/binary_tree/is_sum_tree.py | 1 + .../binary_tree/merge_two_binary_trees.py | 1 + .../binary_tree/mirror_binary_tree.py | 1 + .../binary_tree/non_recursive_segment_tree.py | 1 + .../number_of_possible_binary_trees.py | 1 + data_structures/binary_tree/red_black_tree.py | 1 + .../binary_tree/segment_tree_other.py | 1 + data_structures/binary_tree/symmetric_tree.py | 1 + data_structures/binary_tree/wavelet_tree.py | 1 + data_structures/disjoint_set/disjoint_set.py | 4 +- data_structures/hashing/bloom_filter.py | 1 + data_structures/hashing/double_hash.py | 1 + data_structures/hashing/hash_map.py | 1 + .../hashing/number_theory/prime_numbers.py | 2 +- data_structures/linked_list/__init__.py | 1 + .../linked_list/merge_two_lists.py | 1 + data_structures/linked_list/skip_list.py | 1 + data_structures/queue/double_ended_queue.py | 1 + data_structures/queue/linked_queue.py | 3 +- .../queue/queue_on_pseudo_stack.py | 1 + .../stacks/dijkstras_two_stack_algorithm.py | 1 + .../stacks/stack_with_singly_linked_list.py | 3 +- .../convert_to_negative.py | 3 +- digital_image_processing/dithering/burkes.py | 1 + .../filters/bilateral_filter.py | 1 + .../filters/gaussian_filter.py | 1 + .../filters/median_filter.py | 1 + .../histogram_stretch.py | 1 + digital_image_processing/resize/resize.py | 3 +- digital_image_processing/sepia.py | 3 +- .../test_digital_image_processing.py | 1 + divide_and_conquer/convex_hull.py | 1 + divide_and_conquer/kth_order_statistic.py | 1 + divide_and_conquer/max_subarray.py | 1 + divide_and_conquer/peak.py | 1 + dynamic_programming/all_construct.py | 1 + dynamic_programming/bitmask.py | 1 + dynamic_programming/fast_fibonacci.py | 1 + .../iterating_through_submasks.py | 1 + .../longest_increasing_subsequence.py | 1 + .../matrix_chain_multiplication.py | 1 + dynamic_programming/max_subarray_sum.py | 1 + electronics/charging_capacitor.py | 1 + electronics/charging_inductor.py | 1 + electronics/resistor_color_code.py | 1 + financial/exponential_moving_average.py | 16 ++-- financial/simple_moving_average.py | 1 + fractals/koch_snowflake.py | 1 - fractals/mandelbrot.py | 1 - fractals/sierpinski_triangle.py | 1 + graphs/bi_directional_dijkstra.py | 1 - graphs/bidirectional_a_star.py | 1 + graphs/bidirectional_breadth_first_search.py | 1 + graphs/boruvka.py | 37 ++++----- graphs/breadth_first_search.py | 3 +- graphs/breadth_first_search_2.py | 1 + graphs/breadth_first_search_shortest_path.py | 1 + .../breadth_first_search_shortest_path_2.py | 9 ++- ...dth_first_search_zero_one_shortest_path.py | 1 + graphs/deep_clone_graph.py | 1 + graphs/depth_first_search.py | 1 + graphs/depth_first_search_2.py | 2 +- graphs/dijkstra.py | 1 + graphs/even_tree.py | 1 + graphs/frequent_pattern_graph_miner.py | 1 + graphs/graph_adjacency_list.py | 1 + graphs/graph_adjacency_matrix.py | 1 + graphs/graphs_floyd_warshall.py | 4 +- graphs/minimum_spanning_tree_prims2.py | 1 + graphs/page_rank.py | 1 + graphs/prim.py | 4 +- greedy_methods/gas_station.py | 1 + hashes/adler32.py | 12 +-- hashes/hamming_code.py | 76 +++++++++---------- hashes/luhn.py | 3 +- hashes/sdbm.py | 30 ++++---- hashes/sha1.py | 1 + knapsack/knapsack.py | 5 +- knapsack/tests/test_knapsack.py | 1 + linear_algebra/gaussian_elimination.py | 1 - linear_algebra/jacobi_iteration_method.py | 1 + linear_algebra/lu_decomposition.py | 1 + linear_algebra/src/conjugate_gradient.py | 1 + linear_algebra/src/lib.py | 13 ++-- linear_algebra/src/rayleigh_quotient.py | 1 + linear_algebra/src/test_linear_algebra.py | 1 + linear_algebra/src/transformations_2d.py | 1 + linear_programming/simplex.py | 1 + machine_learning/apriori_algorithm.py | 1 + machine_learning/astar.py | 1 + machine_learning/automatic_differentiation.py | 1 + machine_learning/data_transformations.py | 1 + machine_learning/decision_tree.py | 1 + machine_learning/frequent_pattern_growth.py | 1 + machine_learning/gradient_descent.py | 1 + machine_learning/k_means_clust.py | 1 + .../linear_discriminant_analysis.py | 65 ++++++++-------- machine_learning/linear_regression.py | 1 + machine_learning/logistic_regression.py | 1 + machine_learning/lstm/lstm_prediction.py | 9 ++- machine_learning/mfcc.py | 1 - machine_learning/self_organizing_map.py | 1 + .../sequential_minimum_optimization.py | 1 - machine_learning/similarity_search.py | 1 + maths/allocation_number.py | 1 + maths/area.py | 1 + maths/area_under_curve.py | 1 + maths/basic_maths.py | 1 + maths/binomial_distribution.py | 3 +- maths/chinese_remainder_theorem.py | 1 + maths/continued_fraction.py | 1 - maths/entropy.py | 1 + maths/gamma.py | 1 + maths/gaussian.py | 1 + maths/interquartile_range.py | 1 + maths/is_square_free.py | 1 + maths/karatsuba.py | 2 +- maths/lucas_lehmer_primality_test.py | 14 ++-- maths/maclaurin_series.py | 1 + maths/max_sum_sliding_window.py | 1 + maths/modular_exponential.py | 8 +- maths/monte_carlo.py | 1 + maths/numerical_analysis/adams_bashforth.py | 1 + maths/numerical_analysis/nevilles_method.py | 14 ++-- maths/numerical_analysis/newton_raphson.py | 1 + .../numerical_integration.py | 1 + maths/numerical_analysis/runge_kutta_gills.py | 1 + maths/numerical_analysis/secant_method.py | 1 + maths/prime_factors.py | 1 + maths/series/geometric_series.py | 1 - maths/series/p_series.py | 1 - maths/sieve_of_eratosthenes.py | 1 + maths/solovay_strassen_primality_test.py | 1 - maths/special_numbers/armstrong_numbers.py | 1 + maths/special_numbers/weird_number.py | 1 + maths/tanh.py | 1 + maths/triplet_sum.py | 1 + maths/two_pointer.py | 1 + maths/two_sum.py | 1 + maths/volume.py | 1 + matrix/matrix_multiplication_recursion.py | 1 + networking_flow/ford_fulkerson.py | 1 + .../activation_functions/binary_step.py | 1 - .../rectified_linear_unit.py | 1 + .../soboleva_modified_hyperbolic_tangent.py | 1 - .../back_propagation_neural_network.py | 1 + neural_network/convolution_neural_network.py | 27 +++---- neural_network/input_data.py | 1 - other/davis_putnam_logemann_loveland.py | 1 + other/fischer_yates_shuffle.py | 1 + other/gauss_easter.py | 1 + other/majority_vote_algorithm.py | 1 + other/quine.py | 1 + other/word_search.py | 1 - .../archimedes_principle_of_buoyant_force.py | 1 - physics/center_of_mass.py | 1 + physics/in_static_equilibrium.py | 1 + physics/n_body_simulation.py | 1 - physics/rms_speed_of_molecule.py | 1 - project_euler/problem_002/sol4.py | 1 + project_euler/problem_003/sol1.py | 1 + project_euler/problem_006/sol3.py | 1 + project_euler/problem_007/sol2.py | 1 + project_euler/problem_007/sol3.py | 1 + project_euler/problem_008/sol2.py | 1 + project_euler/problem_008/sol3.py | 1 + project_euler/problem_010/sol2.py | 1 + project_euler/problem_013/sol1.py | 1 + project_euler/problem_014/sol2.py | 1 + project_euler/problem_015/sol1.py | 1 + project_euler/problem_018/solution.py | 1 + project_euler/problem_020/sol2.py | 1 + project_euler/problem_020/sol3.py | 1 + project_euler/problem_021/sol1.py | 1 + project_euler/problem_022/sol1.py | 1 + project_euler/problem_022/sol2.py | 1 + project_euler/problem_024/sol1.py | 1 + project_euler/problem_025/sol2.py | 1 + project_euler/problem_030/sol1.py | 3 +- project_euler/problem_032/sol32.py | 1 + project_euler/problem_033/sol1.py | 1 + project_euler/problem_035/sol1.py | 1 + project_euler/problem_036/sol1.py | 1 + project_euler/problem_038/sol1.py | 1 + project_euler/problem_041/sol1.py | 1 + project_euler/problem_042/solution42.py | 1 + project_euler/problem_043/sol1.py | 1 - project_euler/problem_050/sol1.py | 1 + project_euler/problem_051/sol1.py | 1 + project_euler/problem_053/sol1.py | 1 + project_euler/problem_054/sol1.py | 1 + project_euler/problem_058/sol1.py | 1 + project_euler/problem_059/sol1.py | 1 + project_euler/problem_067/sol1.py | 1 + project_euler/problem_067/sol2.py | 1 + project_euler/problem_070/sol1.py | 1 + project_euler/problem_074/sol1.py | 1 - project_euler/problem_074/sol2.py | 1 + project_euler/problem_077/sol1.py | 1 + project_euler/problem_079/sol1.py | 1 + project_euler/problem_080/sol1.py | 1 + project_euler/problem_081/sol1.py | 1 + project_euler/problem_085/sol1.py | 1 + project_euler/problem_086/sol1.py | 1 - project_euler/problem_091/sol1.py | 1 - project_euler/problem_101/sol1.py | 1 + project_euler/problem_102/sol1.py | 1 + project_euler/problem_107/sol1.py | 1 + project_euler/problem_123/sol1.py | 1 + project_euler/problem_144/sol1.py | 1 - project_euler/problem_145/sol1.py | 1 + project_euler/problem_173/sol1.py | 1 - project_euler/problem_180/sol1.py | 1 + project_euler/problem_191/sol1.py | 1 - project_euler/problem_203/sol1.py | 1 + project_euler/problem_551/sol1.py | 1 - scheduling/highest_response_ratio_next.py | 1 + scheduling/job_sequence_with_deadline.py | 1 + .../non_preemptive_shortest_job_first.py | 1 - scheduling/round_robin.py | 1 + scheduling/shortest_job_first.py | 1 + searches/binary_search.py | 1 + searches/binary_tree_traversal.py | 1 + searches/fibonacci_search.py | 1 + searches/jump_search.py | 3 +- searches/quick_select.py | 1 + searches/simple_binary_search.py | 1 + searches/tabu_search.py | 1 + searches/ternary_search.py | 1 + sorts/bitonic_sort.py | 1 + sorts/bucket_sort.py | 1 + sorts/dutch_national_flag_sort.py | 1 - sorts/insertion_sort.py | 3 +- sorts/intro_sort.py | 1 + sorts/msd_radix_sort.py | 1 + sorts/odd_even_transposition_parallel.py | 1 + sorts/pigeon_sort.py | 15 ++-- sorts/quick_sort.py | 1 + sorts/radix_sort.py | 1 + sorts/recursive_insertion_sort.py | 1 + sorts/slowsort.py | 1 + sorts/tree_sort.py | 1 + strings/boyer_moore_search.py | 1 + strings/check_anagrams.py | 1 + strings/top_k_frequent_words.py | 1 - web_programming/co2_emission.py | 1 + web_programming/emails_from_url.py | 1 + web_programming/fetch_github_info.py | 1 + web_programming/fetch_jobs.py | 1 + web_programming/get_amazon_product_data.py | 1 - web_programming/recaptcha_verification.py | 1 + web_programming/search_books_by_isbn.py | 1 + 297 files changed, 498 insertions(+), 295 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index be8364a7fc0b..a17c4c323c30 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.2 + rev: v0.3.2 hooks: - id: ruff - id: ruff-format @@ -47,7 +47,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 + rev: v1.9.0 hooks: - id: mypy args: diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 407304948c39..390decf3a05b 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -1,9 +1,10 @@ """ - In this problem, we want to determine all possible combinations of k - numbers out of 1 ... n. We use backtracking to solve this problem. +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)!))), +Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))), """ + from __future__ import annotations from itertools import combinations diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index ff8a53e0dd0e..c483cd62c99b 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -1,10 +1,11 @@ """ - 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. """ + from __future__ import annotations diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index c465fc542407..7844a829d046 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -5,6 +5,7 @@ Time complexity: O(2^n), where n denotes the length of the given sequence. """ + from __future__ import annotations from typing import Any diff --git a/backtracking/coloring.py b/backtracking/coloring.py index 9d539de8a3c4..f10cdbcf9d26 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -1,9 +1,9 @@ """ - Graph Coloring also called "m coloring problem" - consists of coloring a given graph with at most m colors - such that no adjacent vertices are assigned the same color +Graph Coloring also called "m coloring problem" +consists of coloring a given graph with at most m colors +such that no adjacent vertices are assigned the same color - Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring +Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring """ diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index e9916f83f861..f6e4212e47f4 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -1,10 +1,10 @@ """ - 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. +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 +Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path """ diff --git a/backtracking/minimax.py b/backtracking/minimax.py index 6dece2990a1c..4eef90b75483 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -7,6 +7,7 @@ leaves of game tree is stored in scores[] height is maximum height of Game tree """ + from __future__ import annotations import math diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index 2cd8c703fc72..81668b17a0ac 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -1,12 +1,13 @@ """ - 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. +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. """ + from __future__ import annotations solution = [] diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py index f3b08ab0a05f..287d1f090373 100644 --- a/backtracking/n_queens_math.py +++ b/backtracking/n_queens_math.py @@ -75,6 +75,7 @@ for another one or vice versa. """ + from __future__ import annotations diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index 6e4e3e8780f2..8f5459c76d45 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -9,6 +9,7 @@ have solved the puzzle. else, we backtrack and place another number in that cell and repeat this process. """ + from __future__ import annotations Matrix = list[list[int]] diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index c5e23321cb0c..f34d3ca34339 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -1,11 +1,12 @@ """ - 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. """ + from __future__ import annotations diff --git a/boolean_algebra/nor_gate.py b/boolean_algebra/nor_gate.py index 0c8ab1c0af61..d4d6f0da23ea 100644 --- a/boolean_algebra/nor_gate.py +++ b/boolean_algebra/nor_gate.py @@ -12,6 +12,7 @@ Code provided by Akshaj Vishwanathan https://www.geeksforgeeks.org/logic-gates-in-python """ + from collections.abc import Callable diff --git a/cellular_automata/conways_game_of_life.py b/cellular_automata/conways_game_of_life.py index 84f4d5be40da..364a34c3aba6 100644 --- a/cellular_automata/conways_game_of_life.py +++ b/cellular_automata/conways_game_of_life.py @@ -2,6 +2,7 @@ Conway's Game of Life implemented in Python. https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life """ + from __future__ import annotations from PIL import Image diff --git a/cellular_automata/game_of_life.py b/cellular_automata/game_of_life.py index d691a2b73af0..67e647d6475b 100644 --- a/cellular_automata/game_of_life.py +++ b/cellular_automata/game_of_life.py @@ -26,7 +26,8 @@ 4. Any dead cell with exactly three live neighbours be- comes a live cell, as if by reproduction. - """ +""" + import random import sys diff --git a/cellular_automata/nagel_schrekenberg.py b/cellular_automata/nagel_schrekenberg.py index 3fd6afca0153..bcdca902afee 100644 --- a/cellular_automata/nagel_schrekenberg.py +++ b/cellular_automata/nagel_schrekenberg.py @@ -24,6 +24,7 @@ >>> simulate(construct_highway(5, 2, -2), 3, 0, 2) [[0, -1, 0, -1, 0], [0, -1, 0, -1, -1], [0, -1, -1, 1, -1], [-1, 1, -1, 0, -1]] """ + from random import randint, random diff --git a/ciphers/a1z26.py b/ciphers/a1z26.py index 0f0eb7c5c083..a1377ea6d397 100644 --- a/ciphers/a1z26.py +++ b/ciphers/a1z26.py @@ -5,6 +5,7 @@ https://www.dcode.fr/letter-number-cipher http://bestcodes.weebly.com/a1z26.html """ + from __future__ import annotations diff --git a/ciphers/atbash.py b/ciphers/atbash.py index 0a86a800c51a..4e8f663ed02d 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -1,4 +1,5 @@ -""" https://en.wikipedia.org/wiki/Atbash """ +"""https://en.wikipedia.org/wiki/Atbash""" + import string diff --git a/ciphers/base32.py b/ciphers/base32.py index 1924d1e185d7..911afa2452c0 100644 --- a/ciphers/base32.py +++ b/ciphers/base32.py @@ -3,6 +3,7 @@ https://en.wikipedia.org/wiki/Base32 """ + B32_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index ec0d44e4a6c6..163aa7172c11 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -14,6 +14,7 @@ Created by TrapinchO """ + from __future__ import annotations RotorPositionT = tuple[int, int, int] diff --git a/ciphers/fractionated_morse_cipher.py b/ciphers/fractionated_morse_cipher.py index c1d5dc6d50aa..6c4c415abac1 100644 --- a/ciphers/fractionated_morse_cipher.py +++ b/ciphers/fractionated_morse_cipher.py @@ -8,6 +8,7 @@ http://practicalcryptography.com/ciphers/fractionated-morse-cipher/ """ + import string MORSE_CODE_DICT = { diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 1201fda901e5..ea337a72dc04 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -35,6 +35,7 @@ https://www.youtube.com/watch?v=4RhLNDqcjpA """ + import string import numpy diff --git a/ciphers/permutation_cipher.py b/ciphers/permutation_cipher.py index c3f3fd1f7f94..9e1c64a7b4ea 100644 --- a/ciphers/permutation_cipher.py +++ b/ciphers/permutation_cipher.py @@ -7,6 +7,7 @@ For more info: https://www.nku.edu/~christensen/1402%20permutation%20ciphers.pdf """ + import random diff --git a/ciphers/rail_fence_cipher.py b/ciphers/rail_fence_cipher.py index 47ee7db89831..5b2311a115e4 100644 --- a/ciphers/rail_fence_cipher.py +++ b/ciphers/rail_fence_cipher.py @@ -1,4 +1,4 @@ -""" https://en.wikipedia.org/wiki/Rail_fence_cipher """ +"""https://en.wikipedia.org/wiki/Rail_fence_cipher""" def encrypt(input_string: str, key: int) -> str: diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py index 9ee52777ed83..0a358a4fc2d4 100644 --- a/ciphers/rsa_factorization.py +++ b/ciphers/rsa_factorization.py @@ -7,6 +7,7 @@ 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 diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index e30955d41ff1..24d88a0fd588 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -1,21 +1,22 @@ """ - 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 """ + from __future__ import annotations diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 52bb045d9398..ce493a70c8f9 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -10,6 +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 __future__ import annotations from typing import TypedDict diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py index ea6f33944a91..ac3f0c6cfc06 100644 --- a/compression/lempel_ziv.py +++ b/compression/lempel_ziv.py @@ -1,6 +1,6 @@ """ - One of the several implementations of Lempel–Ziv–Welch compression algorithm - https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch +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 diff --git a/compression/lempel_ziv_decompress.py b/compression/lempel_ziv_decompress.py index ddedc3d6d32a..0e49c83fb790 100644 --- a/compression/lempel_ziv_decompress.py +++ b/compression/lempel_ziv_decompress.py @@ -1,6 +1,6 @@ """ - One of the several implementations of Lempel–Ziv–Welch decompression algorithm - https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch +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 diff --git a/compression/lz77.py b/compression/lz77.py index 1b201c59f186..09b8b021e9d5 100644 --- a/compression/lz77.py +++ b/compression/lz77.py @@ -28,7 +28,6 @@ en.wikipedia.org/wiki/LZ77_and_LZ78 """ - from dataclasses import dataclass __version__ = "0.1" diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 007421e34263..712bd49668f8 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -2,6 +2,7 @@ https://en.wikipedia.org/wiki/Image_texture https://en.wikipedia.org/wiki/Co-occurrence_matrix#Application_to_image_analysis """ + import imageio.v2 as imageio import numpy as np diff --git a/computer_vision/horn_schunck.py b/computer_vision/horn_schunck.py index b63e0268294c..f33b5b1c794b 100644 --- a/computer_vision/horn_schunck.py +++ b/computer_vision/horn_schunck.py @@ -1,12 +1,12 @@ """ - The Horn-Schunck method estimates the optical flow for every single pixel of - a sequence of images. - It works by assuming brightness constancy between two consecutive frames - and smoothness in the optical flow. - - Useful resources: - Wikipedia: https://en.wikipedia.org/wiki/Horn%E2%80%93Schunck_method - Paper: http://image.diku.dk/imagecanon/material/HornSchunckOptical_Flow.pdf +The Horn-Schunck method estimates the optical flow for every single pixel of +a sequence of images. +It works by assuming brightness constancy between two consecutive frames +and smoothness in the optical flow. + +Useful resources: +Wikipedia: https://en.wikipedia.org/wiki/Horn%E2%80%93Schunck_method +Paper: http://image.diku.dk/imagecanon/material/HornSchunckOptical_Flow.pdf """ from typing import SupportsIndex diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py index b1fb4f082242..ee79592de5ca 100644 --- a/conversions/decimal_to_hexadecimal.py +++ b/conversions/decimal_to_hexadecimal.py @@ -1,4 +1,4 @@ -""" Convert Base 10 (Decimal) Values to Hexadecimal Representations """ +"""Convert Base 10 (Decimal) Values to Hexadecimal Representations""" # set decimal value for each hexadecimal digit values = { diff --git a/conversions/prefix_conversions.py b/conversions/prefix_conversions.py index 06b759e355a7..714677f3b242 100644 --- a/conversions/prefix_conversions.py +++ b/conversions/prefix_conversions.py @@ -1,6 +1,7 @@ """ Convert International System of Units (SI) and Binary prefixes """ + from __future__ import annotations from enum import Enum diff --git a/conversions/temperature_conversions.py b/conversions/temperature_conversions.py index f7af6c8f1e2b..dde1d2f0f166 100644 --- a/conversions/temperature_conversions.py +++ b/conversions/temperature_conversions.py @@ -1,4 +1,4 @@ -""" Convert between different units of temperature """ +"""Convert between different units of temperature""" def celsius_to_fahrenheit(celsius: float, ndigits: int = 2) -> float: diff --git a/data_structures/arrays/pairs_with_given_sum.py b/data_structures/arrays/pairs_with_given_sum.py index c4a5ceeae456..b27bd78e1e0f 100644 --- a/data_structures/arrays/pairs_with_given_sum.py +++ b/data_structures/arrays/pairs_with_given_sum.py @@ -6,6 +6,7 @@ https://practice.geeksforgeeks.org/problems/count-pairs-with-given-sum5022/0 """ + from itertools import combinations diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index a15d5649e712..4606fe908607 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -1,15 +1,16 @@ """ - Sparse table is a data structure that allows answering range queries on - a static number list, i.e. the elements do not change throughout all the queries. +Sparse table is a data structure that allows answering range queries on +a static number list, i.e. the elements do not change throughout all the queries. - The implementation below will solve the problem of Range Minimum Query: - Finding the minimum value of a subset [L..R] of a static number list. +The implementation below will solve the problem of Range Minimum Query: +Finding the minimum value of a subset [L..R] of a static number list. - Overall time complexity: O(nlogn) - Overall space complexity: O(nlogn) +Overall time complexity: O(nlogn) +Overall space complexity: O(nlogn) - Wikipedia link: https://en.wikipedia.org/wiki/Range_minimum_query +Wikipedia link: https://en.wikipedia.org/wiki/Range_minimum_query """ + from math import log2 diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index 20ac32e3b071..c9dffcde2379 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -3,6 +3,7 @@ only minimal changes to work with modern versions of Python. If you have improvements, please make them in a separate file. """ + import random import time diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 4c1fb17afe86..041ed7e36d16 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -5,6 +5,7 @@ For testing run: python avl_tree.py """ + from __future__ import annotations import math diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 9071f03dcc8c..08a60a12065d 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -88,6 +88,7 @@ >>> not t True """ + from __future__ import annotations from collections.abc import Iterable, Iterator diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index 13b9b392175c..6af1b053f42c 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -7,6 +7,7 @@ To run an example: python binary_search_tree_recursive.py """ + from __future__ import annotations import unittest diff --git a/data_structures/binary_tree/binary_tree_node_sum.py b/data_structures/binary_tree/binary_tree_node_sum.py index 5a13e74e3c9f..066617b616c4 100644 --- a/data_structures/binary_tree/binary_tree_node_sum.py +++ b/data_structures/binary_tree/binary_tree_node_sum.py @@ -8,7 +8,6 @@ frames that could be in memory is `n` """ - from __future__ import annotations from collections.abc import Iterator diff --git a/data_structures/binary_tree/diameter_of_binary_tree.py b/data_structures/binary_tree/diameter_of_binary_tree.py index bbe70b028d24..75e5e7373323 100644 --- a/data_structures/binary_tree/diameter_of_binary_tree.py +++ b/data_structures/binary_tree/diameter_of_binary_tree.py @@ -2,6 +2,7 @@ The diameter/width of a tree is defined as the number of nodes on the longest path between two end nodes. """ + from __future__ import annotations from dataclasses import dataclass diff --git a/data_structures/binary_tree/flatten_binarytree_to_linkedlist.py b/data_structures/binary_tree/flatten_binarytree_to_linkedlist.py index 8820a509ecba..9b2c7b9af24b 100644 --- a/data_structures/binary_tree/flatten_binarytree_to_linkedlist.py +++ b/data_structures/binary_tree/flatten_binarytree_to_linkedlist.py @@ -10,6 +10,7 @@ Author: Arunkumar A Date: 04/09/2023 """ + from __future__ import annotations diff --git a/data_structures/binary_tree/floor_and_ceiling.py b/data_structures/binary_tree/floor_and_ceiling.py index f8a1adbd967b..b464aefad3a2 100644 --- a/data_structures/binary_tree/floor_and_ceiling.py +++ b/data_structures/binary_tree/floor_and_ceiling.py @@ -9,6 +9,7 @@ Author : Arunkumar Date : 14th October 2023 """ + from __future__ import annotations from collections.abc import Iterator diff --git a/data_structures/binary_tree/is_sorted.py b/data_structures/binary_tree/is_sorted.py index 5876c5a9c96a..509a426611e5 100644 --- a/data_structures/binary_tree/is_sorted.py +++ b/data_structures/binary_tree/is_sorted.py @@ -13,6 +13,7 @@ Runtime: O(n) Space: O(1) """ + from __future__ import annotations from collections.abc import Iterator diff --git a/data_structures/binary_tree/is_sum_tree.py b/data_structures/binary_tree/is_sum_tree.py index 3f9cf1d560a6..846bea0fe0f2 100644 --- a/data_structures/binary_tree/is_sum_tree.py +++ b/data_structures/binary_tree/is_sum_tree.py @@ -3,6 +3,7 @@ of the values of its left and right subtrees? https://www.geeksforgeeks.org/check-if-a-given-binary-tree-is-sumtree """ + from __future__ import annotations from collections.abc import Iterator diff --git a/data_structures/binary_tree/merge_two_binary_trees.py b/data_structures/binary_tree/merge_two_binary_trees.py index 3380f8c5fb31..6bbb30428704 100644 --- a/data_structures/binary_tree/merge_two_binary_trees.py +++ b/data_structures/binary_tree/merge_two_binary_trees.py @@ -5,6 +5,7 @@ both nodes to the new value of the merged node. Otherwise, the NOT null node will be used as the node of new tree. """ + from __future__ import annotations diff --git a/data_structures/binary_tree/mirror_binary_tree.py b/data_structures/binary_tree/mirror_binary_tree.py index 39305c2a9da2..62e2f08dd4e0 100644 --- a/data_structures/binary_tree/mirror_binary_tree.py +++ b/data_structures/binary_tree/mirror_binary_tree.py @@ -3,6 +3,7 @@ Leetcode problem reference: https://leetcode.com/problems/mirror-binary-tree/ """ + from __future__ import annotations from collections.abc import Iterator diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 04164e5cba4e..42c78a3a1be0 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -35,6 +35,7 @@ >>> st.query(0, 2) [1, 2, 3] """ + from __future__ import annotations from collections.abc import Callable 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 684c518b1eb6..1c3dff37e7d9 100644 --- a/data_structures/binary_tree/number_of_possible_binary_trees.py +++ b/data_structures/binary_tree/number_of_possible_binary_trees.py @@ -6,6 +6,7 @@ Further details at Wikipedia: https://en.wikipedia.org/wiki/Catalan_number """ + """ Our Contribution: Basically we Create the 2 function: diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index fc299301da8a..3b5845cd957b 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -2,6 +2,7 @@ psf/black : true ruff : passed """ + from __future__ import annotations from collections.abc import Iterator diff --git a/data_structures/binary_tree/segment_tree_other.py b/data_structures/binary_tree/segment_tree_other.py index cc77c4951f1a..95f21ddd4777 100644 --- a/data_structures/binary_tree/segment_tree_other.py +++ b/data_structures/binary_tree/segment_tree_other.py @@ -3,6 +3,7 @@ allowing queries to be done later in log(N) time function takes 2 values and returns a same type value """ + from collections.abc import Sequence from queue import Queue diff --git a/data_structures/binary_tree/symmetric_tree.py b/data_structures/binary_tree/symmetric_tree.py index 331a25849c1c..98a766cab988 100644 --- a/data_structures/binary_tree/symmetric_tree.py +++ b/data_structures/binary_tree/symmetric_tree.py @@ -4,6 +4,7 @@ Leetcode reference: https://leetcode.com/problems/symmetric-tree/ """ + from __future__ import annotations from dataclasses import dataclass diff --git a/data_structures/binary_tree/wavelet_tree.py b/data_structures/binary_tree/wavelet_tree.py index 041e140f5b15..2da571e8d326 100644 --- a/data_structures/binary_tree/wavelet_tree.py +++ b/data_structures/binary_tree/wavelet_tree.py @@ -7,6 +7,7 @@ 2. https://www.youtube.com/watch?v=4aSv9PcecDw&t=811s 3. https://www.youtube.com/watch?v=CybAgVF-MMc&t=1178s """ + from __future__ import annotations test_array = [2, 1, 4, 5, 6, 0, 8, 9, 1, 2, 0, 6, 4, 2, 0, 6, 5, 3, 2, 7] diff --git a/data_structures/disjoint_set/disjoint_set.py b/data_structures/disjoint_set/disjoint_set.py index 12dafb2d935e..edc4736b6132 100644 --- a/data_structures/disjoint_set/disjoint_set.py +++ b/data_structures/disjoint_set/disjoint_set.py @@ -1,6 +1,6 @@ """ - Disjoint set. - Reference: https://en.wikipedia.org/wiki/Disjoint-set_data_structure +Disjoint set. +Reference: https://en.wikipedia.org/wiki/Disjoint-set_data_structure """ diff --git a/data_structures/hashing/bloom_filter.py b/data_structures/hashing/bloom_filter.py index 7fd0985bdc33..eb2cb4b79c46 100644 --- a/data_structures/hashing/bloom_filter.py +++ b/data_structures/hashing/bloom_filter.py @@ -58,6 +58,7 @@ >>> bloom.bitstring '01100101' """ + from hashlib import md5, sha256 HASH_FUNCTIONS = (sha256, md5) diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index 76c6c86814ec..324282cbfd8d 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -11,6 +11,7 @@ Reference: https://en.wikipedia.org/wiki/Double_hashing """ + from .hash_table import HashTable from .number_theory.prime_numbers import is_prime, next_prime diff --git a/data_structures/hashing/hash_map.py b/data_structures/hashing/hash_map.py index 6a6f8e54d5e9..9213d6930f67 100644 --- a/data_structures/hashing/hash_map.py +++ b/data_structures/hashing/hash_map.py @@ -7,6 +7,7 @@ Modern Dictionaries by Raymond Hettinger https://www.youtube.com/watch?v=p33CVV29OG8 """ + from collections.abc import Iterator, MutableMapping from dataclasses import dataclass from typing import Generic, TypeVar diff --git a/data_structures/hashing/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py index 0c25896f9880..2549a1477b2b 100644 --- a/data_structures/hashing/number_theory/prime_numbers.py +++ b/data_structures/hashing/number_theory/prime_numbers.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ - module to operations with prime numbers +module to operations with prime numbers """ import math diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index 225113f72cee..00ef337a1211 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -5,6 +5,7 @@ head node gives us access of the complete list - Last node: points to null """ + from __future__ import annotations from typing import Any diff --git a/data_structures/linked_list/merge_two_lists.py b/data_structures/linked_list/merge_two_lists.py index ca0d3bb48540..e47dbdadcf39 100644 --- a/data_structures/linked_list/merge_two_lists.py +++ b/data_structures/linked_list/merge_two_lists.py @@ -1,6 +1,7 @@ """ Algorithm that merges two sorted linked lists into one sorted linked list. """ + from __future__ import annotations from collections.abc import Iterable, Iterator diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index 4413c53e520e..88d3e0daddf0 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -2,6 +2,7 @@ Based on "Skip Lists: A Probabilistic Alternative to Balanced Trees" by William Pugh https://epaperpress.com/sortsearch/download/skiplist.pdf """ + from __future__ import annotations from random import random diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 17a23038d288..607d0bda3df4 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -1,6 +1,7 @@ """ Implementation of double ended queue. """ + from __future__ import annotations from collections.abc import Iterable diff --git a/data_structures/queue/linked_queue.py b/data_structures/queue/linked_queue.py index 3af97d28e4f7..80f6d309af9a 100644 --- a/data_structures/queue/linked_queue.py +++ b/data_structures/queue/linked_queue.py @@ -1,4 +1,5 @@ -""" A Queue using a linked list like structure """ +"""A Queue using a linked list like structure""" + from __future__ import annotations from collections.abc import Iterator diff --git a/data_structures/queue/queue_on_pseudo_stack.py b/data_structures/queue/queue_on_pseudo_stack.py index d9845100008e..2da67ecc263c 100644 --- a/data_structures/queue/queue_on_pseudo_stack.py +++ b/data_structures/queue/queue_on_pseudo_stack.py @@ -1,4 +1,5 @@ """Queue represented by a pseudo stack (represented by a list with pop and append)""" + from typing import Any diff --git a/data_structures/stacks/dijkstras_two_stack_algorithm.py b/data_structures/stacks/dijkstras_two_stack_algorithm.py index 976c9a53c931..94d19156f1c3 100644 --- a/data_structures/stacks/dijkstras_two_stack_algorithm.py +++ b/data_structures/stacks/dijkstras_two_stack_algorithm.py @@ -29,6 +29,7 @@ NOTE: It only works with whole numbers. """ + __author__ = "Alexander Joslin" import operator as op diff --git a/data_structures/stacks/stack_with_singly_linked_list.py b/data_structures/stacks/stack_with_singly_linked_list.py index f5ce83b863ce..8e77c2b967ef 100644 --- a/data_structures/stacks/stack_with_singly_linked_list.py +++ b/data_structures/stacks/stack_with_singly_linked_list.py @@ -1,4 +1,5 @@ -""" A Stack using a linked list like structure """ +"""A Stack using a linked list like structure""" + from __future__ import annotations from collections.abc import Iterator diff --git a/digital_image_processing/convert_to_negative.py b/digital_image_processing/convert_to_negative.py index 7df44138973c..9bf2d8f2c075 100644 --- a/digital_image_processing/convert_to_negative.py +++ b/digital_image_processing/convert_to_negative.py @@ -1,6 +1,7 @@ """ - Implemented an algorithm using opencv to convert a colored image into its negative +Implemented an algorithm using opencv to convert a colored image into its negative """ + from cv2 import destroyAllWindows, imread, imshow, waitKey diff --git a/digital_image_processing/dithering/burkes.py b/digital_image_processing/dithering/burkes.py index 35aedc16d404..4b59356d8f08 100644 --- a/digital_image_processing/dithering/burkes.py +++ b/digital_image_processing/dithering/burkes.py @@ -1,6 +1,7 @@ """ Implementation Burke's algorithm (dithering) """ + import numpy as np from cv2 import destroyAllWindows, imread, imshow, waitKey diff --git a/digital_image_processing/filters/bilateral_filter.py b/digital_image_processing/filters/bilateral_filter.py index 199ac4d9939a..6ef4434d959c 100644 --- a/digital_image_processing/filters/bilateral_filter.py +++ b/digital_image_processing/filters/bilateral_filter.py @@ -9,6 +9,7 @@ Output: img:A 2d zero padded image with values in between 0 and 1 """ + import math import sys diff --git a/digital_image_processing/filters/gaussian_filter.py b/digital_image_processing/filters/gaussian_filter.py index 634d836e5edc..0c34e59fafe5 100644 --- a/digital_image_processing/filters/gaussian_filter.py +++ b/digital_image_processing/filters/gaussian_filter.py @@ -1,6 +1,7 @@ """ Implementation of gaussian filter algorithm """ + from itertools import product from cv2 import COLOR_BGR2GRAY, cvtColor, imread, imshow, waitKey diff --git a/digital_image_processing/filters/median_filter.py b/digital_image_processing/filters/median_filter.py index 174018569d62..fc8b582ef67a 100644 --- a/digital_image_processing/filters/median_filter.py +++ b/digital_image_processing/filters/median_filter.py @@ -1,6 +1,7 @@ """ Implementation of median filter algorithm """ + from cv2 import COLOR_BGR2GRAY, cvtColor, imread, imshow, waitKey from numpy import divide, int8, multiply, ravel, sort, zeros_like diff --git a/digital_image_processing/histogram_equalization/histogram_stretch.py b/digital_image_processing/histogram_equalization/histogram_stretch.py index 5ea7773e32d9..1270c964dee6 100644 --- a/digital_image_processing/histogram_equalization/histogram_stretch.py +++ b/digital_image_processing/histogram_equalization/histogram_stretch.py @@ -3,6 +3,7 @@ @author: Binish125 """ + import copy import os diff --git a/digital_image_processing/resize/resize.py b/digital_image_processing/resize/resize.py index 4836521f9f58..7bde118da69b 100644 --- a/digital_image_processing/resize/resize.py +++ b/digital_image_processing/resize/resize.py @@ -1,4 +1,5 @@ -""" Multiple image resizing techniques """ +"""Multiple image resizing techniques""" + import numpy as np from cv2 import destroyAllWindows, imread, imshow, waitKey diff --git a/digital_image_processing/sepia.py b/digital_image_processing/sepia.py index e9dd2c06066d..1924a80451e5 100644 --- a/digital_image_processing/sepia.py +++ b/digital_image_processing/sepia.py @@ -1,6 +1,7 @@ """ - Implemented an algorithm using opencv to tone an image with sepia technique +Implemented an algorithm using opencv to tone an image with sepia technique """ + from cv2 import destroyAllWindows, imread, imshow, waitKey diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 7993110d6bdd..d1200f4d65ca 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -1,6 +1,7 @@ """ PyTest's for Digital Image Processing """ + import numpy as np from cv2 import COLOR_BGR2GRAY, cvtColor, imread from numpy import array, uint8 diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 1d1bf301def5..a5d8b713bdbc 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -12,6 +12,7 @@ which have not been implemented here, yet. """ + from __future__ import annotations from collections.abc import Iterable diff --git a/divide_and_conquer/kth_order_statistic.py b/divide_and_conquer/kth_order_statistic.py index 666ad1a39b8a..23fd8be5ea47 100644 --- a/divide_and_conquer/kth_order_statistic.py +++ b/divide_and_conquer/kth_order_statistic.py @@ -8,6 +8,7 @@ For more information of this algorithm: https://web.stanford.edu/class/archive/cs/cs161/cs161.1138/lectures/08/Small08.pdf """ + from __future__ import annotations from random import choice diff --git a/divide_and_conquer/max_subarray.py b/divide_and_conquer/max_subarray.py index 851ef621a24c..0fad7ab5d920 100644 --- a/divide_and_conquer/max_subarray.py +++ b/divide_and_conquer/max_subarray.py @@ -6,6 +6,7 @@ This divide-and-conquer algorithm finds the maximum subarray in O(n log n) time. """ + from __future__ import annotations import time diff --git a/divide_and_conquer/peak.py b/divide_and_conquer/peak.py index e60f28bfbe29..71ab5ac86574 100644 --- a/divide_and_conquer/peak.py +++ b/divide_and_conquer/peak.py @@ -7,6 +7,7 @@ (From Kleinberg and Tardos. Algorithm Design. Addison Wesley 2006: Chapter 5 Solved Exercise 1) """ + from __future__ import annotations diff --git a/dynamic_programming/all_construct.py b/dynamic_programming/all_construct.py index 6e53a702cbb1..5d585fc7fcec 100644 --- a/dynamic_programming/all_construct.py +++ b/dynamic_programming/all_construct.py @@ -2,6 +2,7 @@ Program to list all the ways a target string can be constructed from the given list of substrings """ + from __future__ import annotations diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index 56bb8e96ba02..a6e6a0cda7bf 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -8,6 +8,7 @@ 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 diff --git a/dynamic_programming/fast_fibonacci.py b/dynamic_programming/fast_fibonacci.py index f48186a34c25..9f956ca2f979 100644 --- a/dynamic_programming/fast_fibonacci.py +++ b/dynamic_programming/fast_fibonacci.py @@ -4,6 +4,7 @@ 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 diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index 4d0a250e8dfe..372dd2c74a71 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -5,6 +5,7 @@ its submasks. The mask s is submask of m if only bits that were included in bitmask are set """ + from __future__ import annotations diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index d827893763c5..2a78e2e7ad1d 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -10,6 +10,7 @@ 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 annotations diff --git a/dynamic_programming/matrix_chain_multiplication.py b/dynamic_programming/matrix_chain_multiplication.py index 084254a61f6c..da6e525ce816 100644 --- a/dynamic_programming/matrix_chain_multiplication.py +++ b/dynamic_programming/matrix_chain_multiplication.py @@ -38,6 +38,7 @@ arr = [40, 20, 30, 10, 30] output: 26000 """ + from collections.abc import Iterator from contextlib import contextmanager from functools import cache diff --git a/dynamic_programming/max_subarray_sum.py b/dynamic_programming/max_subarray_sum.py index c76943472b97..8c1dc0889a85 100644 --- a/dynamic_programming/max_subarray_sum.py +++ b/dynamic_programming/max_subarray_sum.py @@ -9,6 +9,7 @@ Reference: https://en.wikipedia.org/wiki/Maximum_subarray_problem """ + from collections.abc import Sequence diff --git a/electronics/charging_capacitor.py b/electronics/charging_capacitor.py index 4029b0ecf267..0021e4e345e0 100644 --- a/electronics/charging_capacitor.py +++ b/electronics/charging_capacitor.py @@ -14,6 +14,7 @@ time 't' from the initiation of charging a capacitor with the help of the exponential function containing RC. Both at charging and discharging of a capacitor. """ + from math import exp # value of exp = 2.718281828459… diff --git a/electronics/charging_inductor.py b/electronics/charging_inductor.py index e5c0126c248a..8a3bbc0bbfcd 100644 --- a/electronics/charging_inductor.py +++ b/electronics/charging_inductor.py @@ -25,6 +25,7 @@ in its 'magnetic field'.with the help 'RL-time-constant' we can find current at any time in inductor while it is charging. """ + from math import exp # value of exp = 2.718281828459… diff --git a/electronics/resistor_color_code.py b/electronics/resistor_color_code.py index b0534b813def..189d19946d9d 100644 --- a/electronics/resistor_color_code.py +++ b/electronics/resistor_color_code.py @@ -58,6 +58,7 @@ https://learn.parallax.com/support/reference/resistor-color-codes https://byjus.com/physics/resistor-colour-codes/ """ + valid_colors: list = [ "Black", "Brown", diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py index 0b6cea3b4c91..b56eb2712415 100644 --- a/financial/exponential_moving_average.py +++ b/financial/exponential_moving_average.py @@ -1,12 +1,12 @@ """ - Calculate the exponential moving average (EMA) on the series of stock prices. - Wikipedia Reference: https://en.wikipedia.org/wiki/Exponential_smoothing - https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential - -moving-average-ema - - Exponential moving average is used in finance to analyze changes stock prices. - EMA is used in conjunction with Simple moving average (SMA), EMA reacts to the - changes in the value quicker than SMA, which is one of the advantages of using EMA. +Calculate the exponential moving average (EMA) on the series of stock prices. +Wikipedia Reference: https://en.wikipedia.org/wiki/Exponential_smoothing +https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential +-moving-average-ema + +Exponential moving average is used in finance to analyze changes stock prices. +EMA is used in conjunction with Simple moving average (SMA), EMA reacts to the +changes in the value quicker than SMA, which is one of the advantages of using EMA. """ from collections.abc import Iterator diff --git a/financial/simple_moving_average.py b/financial/simple_moving_average.py index d5d68ffd3dab..f5ae444fd027 100644 --- a/financial/simple_moving_average.py +++ b/financial/simple_moving_average.py @@ -6,6 +6,7 @@ Reference: https://en.wikipedia.org/wiki/Moving_average """ + from collections.abc import Sequence diff --git a/fractals/koch_snowflake.py b/fractals/koch_snowflake.py index b0aaa86b11d8..30cd4b39c7c1 100644 --- a/fractals/koch_snowflake.py +++ b/fractals/koch_snowflake.py @@ -20,7 +20,6 @@ - numpy """ - from __future__ import annotations import matplotlib.pyplot as plt # type: ignore diff --git a/fractals/mandelbrot.py b/fractals/mandelbrot.py index 84dbda997562..5eb9af0aafe1 100644 --- a/fractals/mandelbrot.py +++ b/fractals/mandelbrot.py @@ -15,7 +15,6 @@ (see also https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set ) """ - import colorsys from PIL import Image # type: ignore diff --git a/fractals/sierpinski_triangle.py b/fractals/sierpinski_triangle.py index 45f7ab84cfff..ceb2001b681d 100644 --- a/fractals/sierpinski_triangle.py +++ b/fractals/sierpinski_triangle.py @@ -22,6 +22,7 @@ This code was written by editing the code from https://www.riannetrujillo.com/blog/python-fractal/ """ + import sys import turtle diff --git a/graphs/bi_directional_dijkstra.py b/graphs/bi_directional_dijkstra.py index 529a235db625..7b9eac6c8587 100644 --- a/graphs/bi_directional_dijkstra.py +++ b/graphs/bi_directional_dijkstra.py @@ -10,7 +10,6 @@ # Author: Swayam Singh (https://github.com/practice404) - from queue import PriorityQueue from typing import Any diff --git a/graphs/bidirectional_a_star.py b/graphs/bidirectional_a_star.py index 373d67142aa9..00f623de3493 100644 --- a/graphs/bidirectional_a_star.py +++ b/graphs/bidirectional_a_star.py @@ -1,6 +1,7 @@ """ https://en.wikipedia.org/wiki/Bidirectional_search """ + from __future__ import annotations import time diff --git a/graphs/bidirectional_breadth_first_search.py b/graphs/bidirectional_breadth_first_search.py index 511b080a9add..71c5a9aff08f 100644 --- a/graphs/bidirectional_breadth_first_search.py +++ b/graphs/bidirectional_breadth_first_search.py @@ -1,6 +1,7 @@ """ https://en.wikipedia.org/wiki/Bidirectional_search """ + from __future__ import annotations import time diff --git a/graphs/boruvka.py b/graphs/boruvka.py index 2715a3085948..3dc059ff6a62 100644 --- a/graphs/boruvka.py +++ b/graphs/boruvka.py @@ -1,29 +1,30 @@ """Borůvka's algorithm. - Determines the minimum spanning tree (MST) of a graph using the Borůvka's algorithm. - Borůvka's algorithm is a greedy algorithm for finding a minimum spanning tree in a - connected graph, or a minimum spanning forest if a graph that is not connected. +Determines the minimum spanning tree (MST) of a graph using the Borůvka's algorithm. +Borůvka's algorithm is a greedy algorithm for finding a minimum spanning tree in a +connected graph, or a minimum spanning forest if a graph that is not connected. - The time complexity of this algorithm is O(ELogV), where E represents the number - of edges, while V represents the number of nodes. - O(number_of_edges Log number_of_nodes) +The time complexity of this algorithm is O(ELogV), where E represents the number +of edges, while V represents the number of nodes. +O(number_of_edges Log number_of_nodes) - The space complexity of this algorithm is O(V + E), since we have to keep a couple - of lists whose sizes are equal to the number of nodes, as well as keep all the - edges of a graph inside of the data structure itself. +The space complexity of this algorithm is O(V + E), since we have to keep a couple +of lists whose sizes are equal to the number of nodes, as well as keep all the +edges of a graph inside of the data structure itself. - Borůvka's algorithm gives us pretty much the same result as other MST Algorithms - - they all find the minimum spanning tree, and the time complexity is approximately - the same. +Borůvka's algorithm gives us pretty much the same result as other MST Algorithms - +they all find the minimum spanning tree, and the time complexity is approximately +the same. - One advantage that Borůvka's algorithm has compared to the alternatives is that it - doesn't need to presort the edges or maintain a priority queue in order to find the - minimum spanning tree. - Even though that doesn't help its complexity, since it still passes the edges logE - times, it is a bit simpler to code. +One advantage that Borůvka's algorithm has compared to the alternatives is that it +doesn't need to presort the edges or maintain a priority queue in order to find the +minimum spanning tree. +Even though that doesn't help its complexity, since it still passes the edges logE +times, it is a bit simpler to code. - Details: https://en.wikipedia.org/wiki/Bor%C5%AFvka%27s_algorithm +Details: https://en.wikipedia.org/wiki/Bor%C5%AFvka%27s_algorithm """ + from __future__ import annotations from typing import Any diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index 171d3875f3c5..cab79be39ed3 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -1,6 +1,7 @@ #!/usr/bin/python -""" Author: OMKAR PATHAK """ +"""Author: OMKAR PATHAK""" + from __future__ import annotations from queue import Queue diff --git a/graphs/breadth_first_search_2.py b/graphs/breadth_first_search_2.py index a0b92b90b456..ccadfa346bf1 100644 --- a/graphs/breadth_first_search_2.py +++ b/graphs/breadth_first_search_2.py @@ -12,6 +12,7 @@ mark w as explored add w to Q (at the end) """ + from __future__ import annotations from collections import deque diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index d489b110b3a7..c06440bccef3 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -1,6 +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 __future__ import annotations graph = { diff --git a/graphs/breadth_first_search_shortest_path_2.py b/graphs/breadth_first_search_shortest_path_2.py index b0c8d353ba04..4f9b6e65bdf3 100644 --- a/graphs/breadth_first_search_shortest_path_2.py +++ b/graphs/breadth_first_search_shortest_path_2.py @@ -1,9 +1,10 @@ """Breadth-first search shortest path implementations. - doctest: - python -m doctest -v bfs_shortest_path.py - Manual test: - python bfs_shortest_path.py +doctest: +python -m doctest -v bfs_shortest_path.py +Manual test: +python bfs_shortest_path.py """ + demo_graph = { "A": ["B", "C", "E"], "B": ["A", "D", "E"], diff --git a/graphs/breadth_first_search_zero_one_shortest_path.py b/graphs/breadth_first_search_zero_one_shortest_path.py index 78047c5d2237..d3a255bac1ef 100644 --- a/graphs/breadth_first_search_zero_one_shortest_path.py +++ b/graphs/breadth_first_search_zero_one_shortest_path.py @@ -3,6 +3,7 @@ 0-1-graph is the weighted graph with the weights equal to 0 or 1. Link: https://codeforces.com/blog/entry/22276 """ + from __future__ import annotations from collections import deque diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index 55678b4c01ec..18ea99c6a52d 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -9,6 +9,7 @@ Each node in the graph contains a value (int) and a list (List[Node]) of its neighbors. """ + from dataclasses import dataclass diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index f20a503ca395..a666e74ce607 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -1,4 +1,5 @@ """Non recursive implementation of a DFS algorithm.""" + from __future__ import annotations diff --git a/graphs/depth_first_search_2.py b/graphs/depth_first_search_2.py index 5ff13af33168..8fe48b7f2b42 100644 --- a/graphs/depth_first_search_2.py +++ b/graphs/depth_first_search_2.py @@ -1,6 +1,6 @@ #!/usr/bin/python -""" Author: OMKAR PATHAK """ +"""Author: OMKAR PATHAK""" class Graph: diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index b0bdfab60649..87e9d2233bb2 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -30,6 +30,7 @@ distance between each vertex that makes up the path from start vertex to target vertex. """ + import heapq diff --git a/graphs/even_tree.py b/graphs/even_tree.py index 92ffb4b232f7..7d47899527a7 100644 --- a/graphs/even_tree.py +++ b/graphs/even_tree.py @@ -12,6 +12,7 @@ Note: The tree input will be such that it can always be decomposed into components containing an even number of nodes. """ + # pylint: disable=invalid-name from collections import defaultdict diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index 208e57f9b32f..f8da73f3438e 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -8,6 +8,7 @@ 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', diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py index d0b94f03e9b4..abc75311cd60 100644 --- a/graphs/graph_adjacency_list.py +++ b/graphs/graph_adjacency_list.py @@ -15,6 +15,7 @@ - Make edge weights and vertex values customizable to store whatever the client wants - Support multigraph functionality if the client wants it """ + from __future__ import annotations import random diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py index cdef388d9098..059a6aa9ffb5 100644 --- a/graphs/graph_adjacency_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -15,6 +15,7 @@ - Make edge weights and vertex values customizable to store whatever the client wants - Support multigraph functionality if the client wants it """ + from __future__ import annotations import random diff --git a/graphs/graphs_floyd_warshall.py b/graphs/graphs_floyd_warshall.py index 56cf8b9e382b..aaed9ac5df8b 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. """ diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 81f30ef615fe..cc918f81dfe8 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -6,6 +6,7 @@ at a time, from an arbitrary starting vertex, at each step adding the cheapest possible connection from the tree to another vertex. """ + from __future__ import annotations from sys import maxsize diff --git a/graphs/page_rank.py b/graphs/page_rank.py index b9e4c4a72a93..c0ce3a94c76b 100644 --- a/graphs/page_rank.py +++ b/graphs/page_rank.py @@ -1,6 +1,7 @@ """ Author: https://github.com/bhushan-borole """ + """ The input graph for the algorithm is: diff --git a/graphs/prim.py b/graphs/prim.py index 6cb1a6def359..5b3ce04441ec 100644 --- a/graphs/prim.py +++ b/graphs/prim.py @@ -1,8 +1,8 @@ """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 +Details: https://en.wikipedia.org/wiki/Prim%27s_algorithm """ import heapq as hq diff --git a/greedy_methods/gas_station.py b/greedy_methods/gas_station.py index 2427375d2664..6391ce379329 100644 --- a/greedy_methods/gas_station.py +++ b/greedy_methods/gas_station.py @@ -23,6 +23,7 @@ start checking from the next station. """ + from dataclasses import dataclass diff --git a/hashes/adler32.py b/hashes/adler32.py index 611ebc88b80f..38d76ab12aa0 100644 --- a/hashes/adler32.py +++ b/hashes/adler32.py @@ -1,11 +1,11 @@ """ - 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] +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 +source: https://en.wikipedia.org/wiki/Adler-32 """ MOD_ADLER = 65521 diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index b34fdd4c7a74..b3095852ac51 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -4,44 +4,44 @@ # 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 +* 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 diff --git a/hashes/luhn.py b/hashes/luhn.py index bb77fd05c556..a29bf39e3d82 100644 --- a/hashes/luhn.py +++ b/hashes/luhn.py @@ -1,4 +1,5 @@ -""" Luhn Algorithm """ +"""Luhn Algorithm""" + from __future__ import annotations diff --git a/hashes/sdbm.py b/hashes/sdbm.py index a5432874ba7d..a5abc6f3185b 100644 --- a/hashes/sdbm.py +++ b/hashes/sdbm.py @@ -1,21 +1,21 @@ """ - 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]; +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. +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 +source: http://www.cse.yorku.ca/~oz/hash.html """ diff --git a/hashes/sha1.py b/hashes/sha1.py index a0fa688f863e..75a1423e9b5f 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -25,6 +25,7 @@ Reference: https://deadhacker.com/2006/02/21/sha-1-illustrated/ """ + import argparse import hashlib # hashlib is only used inside the Test class import struct diff --git a/knapsack/knapsack.py b/knapsack/knapsack.py index 18a36c3bcdda..bb507be1ba3c 100644 --- a/knapsack/knapsack.py +++ b/knapsack/knapsack.py @@ -1,6 +1,7 @@ -""" A naive recursive implementation of 0-1 Knapsack Problem - https://en.wikipedia.org/wiki/Knapsack_problem +"""A naive recursive implementation of 0-1 Knapsack Problem +https://en.wikipedia.org/wiki/Knapsack_problem """ + from __future__ import annotations diff --git a/knapsack/tests/test_knapsack.py b/knapsack/tests/test_knapsack.py index 6932bbb3536b..7bfb8780627b 100644 --- a/knapsack/tests/test_knapsack.py +++ b/knapsack/tests/test_knapsack.py @@ -6,6 +6,7 @@ This file contains the test-suite for the knapsack problem. """ + import unittest from knapsack import knapsack as k diff --git a/linear_algebra/gaussian_elimination.py b/linear_algebra/gaussian_elimination.py index a1a35131b157..724773c0db98 100644 --- a/linear_algebra/gaussian_elimination.py +++ b/linear_algebra/gaussian_elimination.py @@ -3,7 +3,6 @@ Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination """ - import numpy as np from numpy import float64 from numpy.typing import NDArray diff --git a/linear_algebra/jacobi_iteration_method.py b/linear_algebra/jacobi_iteration_method.py index 8c91a19ef1b0..2cc9c103018b 100644 --- a/linear_algebra/jacobi_iteration_method.py +++ b/linear_algebra/jacobi_iteration_method.py @@ -1,6 +1,7 @@ """ Jacobi Iteration Method - https://en.wikipedia.org/wiki/Jacobi_method """ + from __future__ import annotations import numpy as np diff --git a/linear_algebra/lu_decomposition.py b/linear_algebra/lu_decomposition.py index 094b20abfecc..1d364163d9a7 100644 --- a/linear_algebra/lu_decomposition.py +++ b/linear_algebra/lu_decomposition.py @@ -15,6 +15,7 @@ Reference: https://en.wikipedia.org/wiki/LU_decomposition """ + from __future__ import annotations import numpy as np diff --git a/linear_algebra/src/conjugate_gradient.py b/linear_algebra/src/conjugate_gradient.py index 4cf566ec9e36..4c0b58deb978 100644 --- a/linear_algebra/src/conjugate_gradient.py +++ b/linear_algebra/src/conjugate_gradient.py @@ -3,6 +3,7 @@ - https://en.wikipedia.org/wiki/Conjugate_gradient_method - https://en.wikipedia.org/wiki/Definite_symmetric_matrix """ + from typing import Any import numpy as np diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 5074faf31d1d..5af6c62e3ad4 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -18,6 +18,7 @@ - function square_zero_matrix(N) - function random_matrix(W, H, a, b) """ + from __future__ import annotations import math @@ -96,12 +97,10 @@ def __sub__(self, other: Vector) -> Vector: raise Exception("must have the same size") @overload - def __mul__(self, other: float) -> Vector: - ... + def __mul__(self, other: float) -> Vector: ... @overload - def __mul__(self, other: Vector) -> float: - ... + def __mul__(self, other: Vector) -> float: ... def __mul__(self, other: float | Vector) -> float | Vector: """ @@ -309,12 +308,10 @@ def __sub__(self, other: Matrix) -> Matrix: raise Exception("matrices must have the same dimension!") @overload - def __mul__(self, other: float) -> Matrix: - ... + def __mul__(self, other: float) -> Matrix: ... @overload - def __mul__(self, other: Vector) -> Vector: - ... + def __mul__(self, other: Vector) -> Vector: ... def __mul__(self, other: float | Vector) -> Vector | Matrix: """ diff --git a/linear_algebra/src/rayleigh_quotient.py b/linear_algebra/src/rayleigh_quotient.py index 4773429cbf1b..46bf1671d2b1 100644 --- a/linear_algebra/src/rayleigh_quotient.py +++ b/linear_algebra/src/rayleigh_quotient.py @@ -1,6 +1,7 @@ """ https://en.wikipedia.org/wiki/Rayleigh_quotient """ + from typing import Any import numpy as np diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 95ab408b3d86..fc5f90fd5cbe 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -6,6 +6,7 @@ This file contains the test-suite for the linear algebra library. """ + import unittest import pytest diff --git a/linear_algebra/src/transformations_2d.py b/linear_algebra/src/transformations_2d.py index cdf42100d5d9..b4185cd2848f 100644 --- a/linear_algebra/src/transformations_2d.py +++ b/linear_algebra/src/transformations_2d.py @@ -11,6 +11,7 @@ reflection(45) = [[0.05064397763545947, 0.893996663600558], [0.893996663600558, 0.7018070490682369]] """ + from math import cos, sin diff --git a/linear_programming/simplex.py b/linear_programming/simplex.py index bbc97d8e22bf..dc171bacd3a2 100644 --- a/linear_programming/simplex.py +++ b/linear_programming/simplex.py @@ -12,6 +12,7 @@ https://en.wikipedia.org/wiki/Simplex_algorithm https://tinyurl.com/simplex4beginners """ + from typing import Any import numpy as np diff --git a/machine_learning/apriori_algorithm.py b/machine_learning/apriori_algorithm.py index d9fd1f82ea3c..09a89ac236bd 100644 --- a/machine_learning/apriori_algorithm.py +++ b/machine_learning/apriori_algorithm.py @@ -10,6 +10,7 @@ WIKI: https://en.wikipedia.org/wiki/Apriori_algorithm Examples: https://www.kaggle.com/code/earthian/apriori-association-rules-mining """ + from itertools import combinations diff --git a/machine_learning/astar.py b/machine_learning/astar.py index ff5208266343..a5859e51fe70 100644 --- a/machine_learning/astar.py +++ b/machine_learning/astar.py @@ -12,6 +12,7 @@ https://en.wikipedia.org/wiki/A*_search_algorithm """ + import numpy as np diff --git a/machine_learning/automatic_differentiation.py b/machine_learning/automatic_differentiation.py index cd2e5cdaa782..5c2708247c21 100644 --- a/machine_learning/automatic_differentiation.py +++ b/machine_learning/automatic_differentiation.py @@ -6,6 +6,7 @@ Author: Poojan Smart Email: smrtpoojan@gmail.com """ + from __future__ import annotations from collections import defaultdict diff --git a/machine_learning/data_transformations.py b/machine_learning/data_transformations.py index ecfd3b9e27c2..a1c28d514fd5 100644 --- a/machine_learning/data_transformations.py +++ b/machine_learning/data_transformations.py @@ -25,6 +25,7 @@ 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 diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index c67e09c7f114..7f129919a3ce 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -3,6 +3,7 @@ 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. """ + import numpy as np diff --git a/machine_learning/frequent_pattern_growth.py b/machine_learning/frequent_pattern_growth.py index 205d598464a1..6b9870f5e1d2 100644 --- a/machine_learning/frequent_pattern_growth.py +++ b/machine_learning/frequent_pattern_growth.py @@ -9,6 +9,7 @@ Examples: https://www.javatpoint.com/fp-growth-algorithm-in-data-mining """ + from __future__ import annotations from dataclasses import dataclass, field diff --git a/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index 9ffc02bbc284..db38b3c95b52 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -2,6 +2,7 @@ Implementation of gradient descent algorithm for minimizing cost of a linear hypothesis function. """ + import numpy # List of input, output pairs diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 4a219edc3bb1..9f6646944458 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -40,6 +40,7 @@ 5. Transfers Dataframe into excel format it must have feature called 'Clust' with k means clustering numbers in it. """ + import warnings import numpy as np diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 88c047157893..606e11f3698e 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -1,47 +1,48 @@ """ - Linear Discriminant Analysis +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. +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 +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 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 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))) + 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. +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 +Author: @EverLookNeverSee """ + from collections.abc import Callable from math import log from os import name, system diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 0847112ad538..39bee5712c16 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -7,6 +7,7 @@ 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 numpy as np import requests diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 59a70fd65cf9..090af5382185 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -14,6 +14,7 @@ Coursera ML course https://medium.com/@martinpella/logistic-regression-from-scratch-in-python-124c5636b8ac """ + import numpy as np from matplotlib import pyplot as plt from sklearn import datasets diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py index ecbd451266ad..f0fd12c9de7f 100644 --- a/machine_learning/lstm/lstm_prediction.py +++ b/machine_learning/lstm/lstm_prediction.py @@ -1,9 +1,10 @@ """ - Create a Long Short Term Memory (LSTM) network model - An LSTM is a type of Recurrent Neural Network (RNN) as discussed at: - * https://colah.github.io/posts/2015-08-Understanding-LSTMs - * https://en.wikipedia.org/wiki/Long_short-term_memory +Create a Long Short Term Memory (LSTM) network model +An LSTM is a type of Recurrent Neural Network (RNN) as discussed at: +* https://colah.github.io/posts/2015-08-Understanding-LSTMs +* https://en.wikipedia.org/wiki/Long_short-term_memory """ + import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler diff --git a/machine_learning/mfcc.py b/machine_learning/mfcc.py index 7ce8ceb50ff2..a1e99ce4ad40 100644 --- a/machine_learning/mfcc.py +++ b/machine_learning/mfcc.py @@ -57,7 +57,6 @@ Author: Amir Lavasani """ - import logging import numpy as np diff --git a/machine_learning/self_organizing_map.py b/machine_learning/self_organizing_map.py index 32fdf1d2b41d..fb9d0074e791 100644 --- a/machine_learning/self_organizing_map.py +++ b/machine_learning/self_organizing_map.py @@ -1,6 +1,7 @@ """ https://en.wikipedia.org/wiki/Self-organizing_map """ + import math diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 9ee8c52fb2e9..be16baca1a4c 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -30,7 +30,6 @@ https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-98-14.pdf """ - import os import sys import urllib.request diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py index 7a23ec463c8f..0bc3b17d7e5a 100644 --- a/machine_learning/similarity_search.py +++ b/machine_learning/similarity_search.py @@ -7,6 +7,7 @@ 1. the nearest vector 2. distance between the vector and the nearest vector (float) """ + from __future__ import annotations import math diff --git a/maths/allocation_number.py b/maths/allocation_number.py index d419e74d01ff..52f1ac4bdb23 100644 --- a/maths/allocation_number.py +++ b/maths/allocation_number.py @@ -5,6 +5,7 @@ for i in allocation_list: requests.get(url,headers={'Range':f'bytes={i}'}) """ + from __future__ import annotations diff --git a/maths/area.py b/maths/area.py index ea7216c8fe3f..31a654206977 100644 --- a/maths/area.py +++ b/maths/area.py @@ -2,6 +2,7 @@ Find the area of various geometric shapes Wikipedia reference: https://en.wikipedia.org/wiki/Area """ + from math import pi, sqrt, tan diff --git a/maths/area_under_curve.py b/maths/area_under_curve.py index 0da6546b2e36..10aec768fa09 100644 --- a/maths/area_under_curve.py +++ b/maths/area_under_curve.py @@ -1,6 +1,7 @@ """ Approximates the area under the curve using the trapezoidal rule """ + from __future__ import annotations from collections.abc import Callable diff --git a/maths/basic_maths.py b/maths/basic_maths.py index c9e3d00fa23b..833f31c18b9e 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -1,4 +1,5 @@ """Implementation of Basic Math in Python.""" + import math diff --git a/maths/binomial_distribution.py b/maths/binomial_distribution.py index 5b56f2d59244..eabcaea0d1b2 100644 --- a/maths/binomial_distribution.py +++ b/maths/binomial_distribution.py @@ -1,5 +1,6 @@ """For more information about the Binomial Distribution - - https://en.wikipedia.org/wiki/Binomial_distribution""" +https://en.wikipedia.org/wiki/Binomial_distribution""" + from math import factorial diff --git a/maths/chinese_remainder_theorem.py b/maths/chinese_remainder_theorem.py index d3e75e77922a..18af63d106e8 100644 --- a/maths/chinese_remainder_theorem.py +++ b/maths/chinese_remainder_theorem.py @@ -11,6 +11,7 @@ 1. Use extended euclid algorithm to find x,y such that a*x + b*y = 1 2. Take n = ra*by + rb*ax """ + from __future__ import annotations diff --git a/maths/continued_fraction.py b/maths/continued_fraction.py index 04ff0b6ff0d2..2c38bf88b1e9 100644 --- a/maths/continued_fraction.py +++ b/maths/continued_fraction.py @@ -4,7 +4,6 @@ https://en.wikipedia.org/wiki/Continued_fraction """ - from fractions import Fraction from math import floor diff --git a/maths/entropy.py b/maths/entropy.py index 23753d884484..76fac4ee717d 100644 --- a/maths/entropy.py +++ b/maths/entropy.py @@ -4,6 +4,7 @@ Implementation of entropy of information https://en.wikipedia.org/wiki/Entropy_(information_theory) """ + from __future__ import annotations import math diff --git a/maths/gamma.py b/maths/gamma.py index 822bbc74456f..e328cd8b22b7 100644 --- a/maths/gamma.py +++ b/maths/gamma.py @@ -8,6 +8,7 @@ the non-positive integers Python's Standard Library math.gamma() function overflows around gamma(171.624). """ + import math from numpy import inf diff --git a/maths/gaussian.py b/maths/gaussian.py index 51ebc2e25849..0e02010a9c67 100644 --- a/maths/gaussian.py +++ b/maths/gaussian.py @@ -1,6 +1,7 @@ """ Reference: https://en.wikipedia.org/wiki/Gaussian_function """ + from numpy import exp, pi, sqrt diff --git a/maths/interquartile_range.py b/maths/interquartile_range.py index d4d72e73ef49..e91a651647d4 100644 --- a/maths/interquartile_range.py +++ b/maths/interquartile_range.py @@ -7,6 +7,7 @@ Script inspired by this Wikipedia article: https://en.wikipedia.org/wiki/Interquartile_range """ + from __future__ import annotations diff --git a/maths/is_square_free.py b/maths/is_square_free.py index 08c70dc32c38..a336c37e8dbc 100644 --- a/maths/is_square_free.py +++ b/maths/is_square_free.py @@ -3,6 +3,7 @@ psf/black : True ruff : True """ + from __future__ import annotations diff --git a/maths/karatsuba.py b/maths/karatsuba.py index 3d29e31d2107..0e063fb44b83 100644 --- a/maths/karatsuba.py +++ b/maths/karatsuba.py @@ -1,4 +1,4 @@ -""" Multiply two numbers using Karatsuba algorithm """ +"""Multiply two numbers using Karatsuba algorithm""" def karatsuba(a: int, b: int) -> int: diff --git a/maths/lucas_lehmer_primality_test.py b/maths/lucas_lehmer_primality_test.py index 0a5621aacd79..292387414dee 100644 --- a/maths/lucas_lehmer_primality_test.py +++ b/maths/lucas_lehmer_primality_test.py @@ -1,13 +1,13 @@ """ - 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 - https://en.wikipedia.org/wiki/Mersenne_prime +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. +The Lucas–Lehmer test is the primality test used by the +Great Internet Mersenne Prime Search (GIMPS) to locate large primes. """ diff --git a/maths/maclaurin_series.py b/maths/maclaurin_series.py index d5c3c3ab958b..6ec5551a5e6e 100644 --- a/maths/maclaurin_series.py +++ b/maths/maclaurin_series.py @@ -1,6 +1,7 @@ """ https://en.wikipedia.org/wiki/Taylor_series#Trigonometric_functions """ + from math import factorial, pi diff --git a/maths/max_sum_sliding_window.py b/maths/max_sum_sliding_window.py index c6f9b4ed0ad7..090117429604 100644 --- a/maths/max_sum_sliding_window.py +++ b/maths/max_sum_sliding_window.py @@ -6,6 +6,7 @@ called 'Window sliding technique' where the nested loops can be converted to a single loop to reduce time complexity. """ + from __future__ import annotations diff --git a/maths/modular_exponential.py b/maths/modular_exponential.py index 42987dbf3a24..a27e29ebc02a 100644 --- a/maths/modular_exponential.py +++ b/maths/modular_exponential.py @@ -1,8 +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 +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.""" diff --git a/maths/monte_carlo.py b/maths/monte_carlo.py index 474f1f65deb4..d174a0b188a2 100644 --- a/maths/monte_carlo.py +++ b/maths/monte_carlo.py @@ -1,6 +1,7 @@ """ @author: MatteoRaso """ + from collections.abc import Callable from math import pi, sqrt from random import uniform diff --git a/maths/numerical_analysis/adams_bashforth.py b/maths/numerical_analysis/adams_bashforth.py index d61f022a413d..fb406171098a 100644 --- a/maths/numerical_analysis/adams_bashforth.py +++ b/maths/numerical_analysis/adams_bashforth.py @@ -4,6 +4,7 @@ https://en.wikipedia.org/wiki/Linear_multistep_method Author : Ravi Kumar """ + from collections.abc import Callable from dataclasses import dataclass diff --git a/maths/numerical_analysis/nevilles_method.py b/maths/numerical_analysis/nevilles_method.py index 1f48b43fbd22..256b61f5f218 100644 --- a/maths/numerical_analysis/nevilles_method.py +++ b/maths/numerical_analysis/nevilles_method.py @@ -1,11 +1,11 @@ """ - Python program to show how to interpolate and evaluate a polynomial - using Neville's method. - Neville’s method evaluates a polynomial that passes through a - given set of x and y points for a particular x value (x0) using the - Newton polynomial form. - Reference: - https://rpubs.com/aaronsc32/nevilles-method-polynomial-interpolation +Python program to show how to interpolate and evaluate a polynomial +using Neville's method. +Neville’s method evaluates a polynomial that passes through a +given set of x and y points for a particular x value (x0) using the +Newton polynomial form. +Reference: + https://rpubs.com/aaronsc32/nevilles-method-polynomial-interpolation """ diff --git a/maths/numerical_analysis/newton_raphson.py b/maths/numerical_analysis/newton_raphson.py index feee38f905dd..10fb244bf426 100644 --- a/maths/numerical_analysis/newton_raphson.py +++ b/maths/numerical_analysis/newton_raphson.py @@ -9,6 +9,7 @@ Reference: https://en.wikipedia.org/wiki/Newton%27s_method """ + from collections.abc import Callable RealFunc = Callable[[float], float] diff --git a/maths/numerical_analysis/numerical_integration.py b/maths/numerical_analysis/numerical_integration.py index 4ac562644a07..f64436ec48c1 100644 --- a/maths/numerical_analysis/numerical_integration.py +++ b/maths/numerical_analysis/numerical_integration.py @@ -1,6 +1,7 @@ """ Approximates the area under the curve using the trapezoidal rule """ + from __future__ import annotations from collections.abc import Callable diff --git a/maths/numerical_analysis/runge_kutta_gills.py b/maths/numerical_analysis/runge_kutta_gills.py index 2bd9cd6129b8..451cde4cb935 100644 --- a/maths/numerical_analysis/runge_kutta_gills.py +++ b/maths/numerical_analysis/runge_kutta_gills.py @@ -4,6 +4,7 @@ https://www.geeksforgeeks.org/gills-4th-order-method-to-solve-differential-equations/ Author : Ravi Kumar """ + from collections.abc import Callable from math import sqrt diff --git a/maths/numerical_analysis/secant_method.py b/maths/numerical_analysis/secant_method.py index d39cb0ff30ef..9fff8222cdde 100644 --- a/maths/numerical_analysis/secant_method.py +++ b/maths/numerical_analysis/secant_method.py @@ -2,6 +2,7 @@ Implementing Secant method in Python Author: dimgrichr """ + from math import exp diff --git a/maths/prime_factors.py b/maths/prime_factors.py index e520ae3a6d04..47abcf10e618 100644 --- a/maths/prime_factors.py +++ b/maths/prime_factors.py @@ -1,6 +1,7 @@ """ python/black : True """ + from __future__ import annotations diff --git a/maths/series/geometric_series.py b/maths/series/geometric_series.py index b8d6a86206be..55c42fd90e99 100644 --- a/maths/series/geometric_series.py +++ b/maths/series/geometric_series.py @@ -9,7 +9,6 @@ python3 geometric_series.py """ - from __future__ import annotations diff --git a/maths/series/p_series.py b/maths/series/p_series.py index a091a6f3fecf..93812f443857 100644 --- a/maths/series/p_series.py +++ b/maths/series/p_series.py @@ -9,7 +9,6 @@ python3 p_series.py """ - from __future__ import annotations diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index a0520aa5cf50..3923dc3e1612 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -10,6 +10,7 @@ doctest provider: Bruno Simas Hadlich (https://github.com/brunohadlich) Also thanks to Dmitry (https://github.com/LizardWizzard) for finding the problem """ + from __future__ import annotations import math diff --git a/maths/solovay_strassen_primality_test.py b/maths/solovay_strassen_primality_test.py index 1d11d458369a..b2d905b07bed 100644 --- a/maths/solovay_strassen_primality_test.py +++ b/maths/solovay_strassen_primality_test.py @@ -9,7 +9,6 @@ https://en.wikipedia.org/wiki/Solovay%E2%80%93Strassen_primality_test """ - import random diff --git a/maths/special_numbers/armstrong_numbers.py b/maths/special_numbers/armstrong_numbers.py index b037aacb16c3..b2b4010a8f5b 100644 --- a/maths/special_numbers/armstrong_numbers.py +++ b/maths/special_numbers/armstrong_numbers.py @@ -8,6 +8,7 @@ On-Line Encyclopedia of Integer Sequences entry: https://oeis.org/A005188 """ + PASSING = (1, 153, 370, 371, 1634, 24678051, 115132219018763992565095597973971522401) FAILING: tuple = (-153, -1, 0, 1.2, 200, "A", [], {}, None) diff --git a/maths/special_numbers/weird_number.py b/maths/special_numbers/weird_number.py index 2834a9fee31e..5c9240d0ea4e 100644 --- a/maths/special_numbers/weird_number.py +++ b/maths/special_numbers/weird_number.py @@ -3,6 +3,7 @@ Fun fact: The set of weird numbers has positive asymptotic density. """ + from math import sqrt diff --git a/maths/tanh.py b/maths/tanh.py index 38a369d9118d..011d6f17e22b 100644 --- a/maths/tanh.py +++ b/maths/tanh.py @@ -9,6 +9,7 @@ Script inspired from its corresponding Wikipedia article https://en.wikipedia.org/wiki/Activation_function """ + import numpy as np diff --git a/maths/triplet_sum.py b/maths/triplet_sum.py index af77ed145bce..e74f67daad47 100644 --- a/maths/triplet_sum.py +++ b/maths/triplet_sum.py @@ -3,6 +3,7 @@ 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 diff --git a/maths/two_pointer.py b/maths/two_pointer.py index d0fb0fc9c2f1..8a6d8eb7aff0 100644 --- a/maths/two_pointer.py +++ b/maths/two_pointer.py @@ -17,6 +17,7 @@ [1]: https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py """ + from __future__ import annotations diff --git a/maths/two_sum.py b/maths/two_sum.py index 12ad332d6c4e..58c933a5078a 100644 --- a/maths/two_sum.py +++ b/maths/two_sum.py @@ -11,6 +11,7 @@ Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1]. """ + from __future__ import annotations diff --git a/maths/volume.py b/maths/volume.py index b4df4e475783..33be9bdd131a 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -3,6 +3,7 @@ * https://en.wikipedia.org/wiki/Volume * https://en.wikipedia.org/wiki/Spherical_cap """ + from __future__ import annotations from math import pi, pow diff --git a/matrix/matrix_multiplication_recursion.py b/matrix/matrix_multiplication_recursion.py index 287142480ce7..57c4d80de017 100644 --- a/matrix/matrix_multiplication_recursion.py +++ b/matrix/matrix_multiplication_recursion.py @@ -7,6 +7,7 @@ Perform matrix multiplication using a recursive algorithm. https://en.wikipedia.org/wiki/Matrix_multiplication """ + # type Matrix = list[list[int]] # psf/black currenttly fails on this line Matrix = list[list[int]] diff --git a/networking_flow/ford_fulkerson.py b/networking_flow/ford_fulkerson.py index 7d5fb522e012..b47d3b68f3d1 100644 --- a/networking_flow/ford_fulkerson.py +++ b/networking_flow/ford_fulkerson.py @@ -6,6 +6,7 @@ (1) Start with initial flow as 0 (2) Choose the augmenting path from source to sink and add the path to flow """ + graph = [ [0, 16, 13, 0, 0, 0], [0, 0, 10, 12, 0, 0], diff --git a/neural_network/activation_functions/binary_step.py b/neural_network/activation_functions/binary_step.py index 8f8f4d405fd2..d3d774602182 100644 --- a/neural_network/activation_functions/binary_step.py +++ b/neural_network/activation_functions/binary_step.py @@ -8,7 +8,6 @@ https://en.wikipedia.org/wiki/Activation_function """ - import numpy as np diff --git a/neural_network/activation_functions/rectified_linear_unit.py b/neural_network/activation_functions/rectified_linear_unit.py index 458c6bd5c391..2d5cf96fd387 100644 --- a/neural_network/activation_functions/rectified_linear_unit.py +++ b/neural_network/activation_functions/rectified_linear_unit.py @@ -9,6 +9,7 @@ Script inspired from its corresponding Wikipedia article https://en.wikipedia.org/wiki/Rectifier_(neural_networks) """ + from __future__ import annotations import numpy as np diff --git a/neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py b/neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py index 603ac0b7e120..a053e690ba44 100644 --- a/neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py +++ b/neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py @@ -8,7 +8,6 @@ https://en.wikipedia.org/wiki/Soboleva_modified_hyperbolic_tangent """ - import numpy as np diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index bdd096b3f653..7e0bdbbe2857 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -17,6 +17,7 @@ Date: 2017.11.23 """ + import numpy as np from matplotlib import pyplot as plt diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index e9726a0cb4a7..07cc456b7466 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -1,18 +1,19 @@ """ - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - - Name - - CNN - Convolution Neural Network For Photo Recognizing - Goal - - Recognize Handing Writing Word Photo - Detail: Total 5 layers neural network - * Convolution layer - * Pooling layer - * Input layer layer of BP - * Hidden layer of BP - * Output layer of BP - Author: Stephen Lee - Github: 245885195@qq.com - Date: 2017.9.20 - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - + - - - - - -- - - - - - - - - - - - - - - - - - - - - - - +Name - - CNN - Convolution Neural Network For Photo Recognizing +Goal - - Recognize Handing Writing Word Photo +Detail: Total 5 layers neural network + * Convolution layer + * Pooling layer + * Input layer layer of BP + * Hidden layer of BP + * Output layer of BP +Author: Stephen Lee +Github: 245885195@qq.com +Date: 2017.9.20 +- - - - - -- - - - - - - - - - - - - - - - - - - - - - - """ + import pickle import numpy as np diff --git a/neural_network/input_data.py b/neural_network/input_data.py index 2128449c03e9..f7ae86b48e65 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -17,7 +17,6 @@ This module and all its submodules are deprecated. """ - import gzip import os import typing diff --git a/other/davis_putnam_logemann_loveland.py b/other/davis_putnam_logemann_loveland.py index f5fb103ba528..436577eb5b5d 100644 --- a/other/davis_putnam_logemann_loveland.py +++ b/other/davis_putnam_logemann_loveland.py @@ -8,6 +8,7 @@ For more information about the algorithm: https://en.wikipedia.org/wiki/DPLL_algorithm """ + from __future__ import annotations import random diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index fa2f4dce9db0..37e11479a4c9 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -5,6 +5,7 @@ For more details visit wikipedia/Fischer-Yates-Shuffle. """ + import random from typing import Any diff --git a/other/gauss_easter.py b/other/gauss_easter.py index 4447d4ab86af..d1c525593f79 100644 --- a/other/gauss_easter.py +++ b/other/gauss_easter.py @@ -1,6 +1,7 @@ """ https://en.wikipedia.org/wiki/Computus#Gauss'_Easter_algorithm """ + import math from datetime import datetime, timedelta diff --git a/other/majority_vote_algorithm.py b/other/majority_vote_algorithm.py index ab8b386dd2e5..8d3b56707d06 100644 --- a/other/majority_vote_algorithm.py +++ b/other/majority_vote_algorithm.py @@ -4,6 +4,7 @@ We have to solve in O(n) time and O(1) Space. URL : https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm """ + from collections import Counter diff --git a/other/quine.py b/other/quine.py index 500a351d38dc..08e885bc1ce7 100644 --- a/other/quine.py +++ b/other/quine.py @@ -8,4 +8,5 @@ More info on: https://en.wikipedia.org/wiki/Quine_(computing) """ + print((lambda quine: quine % quine)("print((lambda quine: quine %% quine)(%r))")) diff --git a/other/word_search.py b/other/word_search.py index a4796e220c7c..9e8acadbd9a4 100644 --- a/other/word_search.py +++ b/other/word_search.py @@ -5,7 +5,6 @@ @ https://en.wikipedia.org/wiki/Word_search """ - from random import choice, randint, shuffle # The words to display on the word search - diff --git a/physics/archimedes_principle_of_buoyant_force.py b/physics/archimedes_principle_of_buoyant_force.py index 5f569837220f..71043e0e1111 100644 --- a/physics/archimedes_principle_of_buoyant_force.py +++ b/physics/archimedes_principle_of_buoyant_force.py @@ -8,7 +8,6 @@ https://en.wikipedia.org/wiki/Archimedes%27_principle """ - # Acceleration Constant on Earth (unit m/s^2) g = 9.80665 # Also available in scipy.constants.g diff --git a/physics/center_of_mass.py b/physics/center_of_mass.py index bd9ba2480584..59c3b807f401 100644 --- a/physics/center_of_mass.py +++ b/physics/center_of_mass.py @@ -24,6 +24,7 @@ Reference: https://en.wikipedia.org/wiki/Center_of_mass """ + from collections import namedtuple Particle = namedtuple("Particle", "x y z mass") # noqa: PYI024 diff --git a/physics/in_static_equilibrium.py b/physics/in_static_equilibrium.py index d56299f60858..e3c2f9d07aed 100644 --- a/physics/in_static_equilibrium.py +++ b/physics/in_static_equilibrium.py @@ -1,6 +1,7 @@ """ Checks if a system of forces is in static equilibrium. """ + from __future__ import annotations from numpy import array, cos, cross, float64, radians, sin diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index ec008784ba62..4d555716199a 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -11,7 +11,6 @@ (See also http://www.shodor.org/refdesk/Resources/Algorithms/EulersMethod/ ) """ - from __future__ import annotations import random diff --git a/physics/rms_speed_of_molecule.py b/physics/rms_speed_of_molecule.py index 478cee01c7fd..fb23eb8a21cf 100644 --- a/physics/rms_speed_of_molecule.py +++ b/physics/rms_speed_of_molecule.py @@ -20,7 +20,6 @@ alternative method. """ - UNIVERSAL_GAS_CONSTANT = 8.3144598 diff --git a/project_euler/problem_002/sol4.py b/project_euler/problem_002/sol4.py index 70b7d6a80a1d..3a2e4fce341c 100644 --- a/project_euler/problem_002/sol4.py +++ b/project_euler/problem_002/sol4.py @@ -14,6 +14,7 @@ References: - https://en.wikipedia.org/wiki/Fibonacci_number """ + import math from decimal import Decimal, getcontext diff --git a/project_euler/problem_003/sol1.py b/project_euler/problem_003/sol1.py index a7d01bb041ba..d1c0e61cf1a6 100644 --- a/project_euler/problem_003/sol1.py +++ b/project_euler/problem_003/sol1.py @@ -10,6 +10,7 @@ References: - https://en.wikipedia.org/wiki/Prime_number#Unique_factorization """ + import math diff --git a/project_euler/problem_006/sol3.py b/project_euler/problem_006/sol3.py index 529f233c9f8e..16445258c2b7 100644 --- a/project_euler/problem_006/sol3.py +++ b/project_euler/problem_006/sol3.py @@ -15,6 +15,7 @@ Find the difference between the sum of the squares of the first one hundred natural numbers and the square of the sum. """ + import math diff --git a/project_euler/problem_007/sol2.py b/project_euler/problem_007/sol2.py index 75d351889ea8..fd99453c1100 100644 --- a/project_euler/problem_007/sol2.py +++ b/project_euler/problem_007/sol2.py @@ -11,6 +11,7 @@ References: - https://en.wikipedia.org/wiki/Prime_number """ + import math diff --git a/project_euler/problem_007/sol3.py b/project_euler/problem_007/sol3.py index 774260db99a0..39db51a93427 100644 --- a/project_euler/problem_007/sol3.py +++ b/project_euler/problem_007/sol3.py @@ -11,6 +11,7 @@ References: - https://en.wikipedia.org/wiki/Prime_number """ + import itertools import math diff --git a/project_euler/problem_008/sol2.py b/project_euler/problem_008/sol2.py index 889c3a3143c2..f83cb1db30b6 100644 --- a/project_euler/problem_008/sol2.py +++ b/project_euler/problem_008/sol2.py @@ -30,6 +30,7 @@ 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 = ( diff --git a/project_euler/problem_008/sol3.py b/project_euler/problem_008/sol3.py index c6081aa05e2c..bf3bcb05b7e9 100644 --- a/project_euler/problem_008/sol3.py +++ b/project_euler/problem_008/sol3.py @@ -30,6 +30,7 @@ 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 = ( diff --git a/project_euler/problem_010/sol2.py b/project_euler/problem_010/sol2.py index 245cca1d1720..1a1fc0f33cb3 100644 --- a/project_euler/problem_010/sol2.py +++ b/project_euler/problem_010/sol2.py @@ -10,6 +10,7 @@ References: - https://en.wikipedia.org/wiki/Prime_number """ + import math from collections.abc import Iterator from itertools import takewhile diff --git a/project_euler/problem_013/sol1.py b/project_euler/problem_013/sol1.py index 7a414a9379e0..87d0e0a60e9b 100644 --- a/project_euler/problem_013/sol1.py +++ b/project_euler/problem_013/sol1.py @@ -5,6 +5,7 @@ Work out the first ten digits of the sum of the following one-hundred 50-digit numbers. """ + import os diff --git a/project_euler/problem_014/sol2.py b/project_euler/problem_014/sol2.py index 2448e652ce5b..797b0f9886fe 100644 --- a/project_euler/problem_014/sol2.py +++ b/project_euler/problem_014/sol2.py @@ -25,6 +25,7 @@ Which starting number, under one million, produces the longest chain? """ + from __future__ import annotations COLLATZ_SEQUENCE_LENGTHS = {1: 1} diff --git a/project_euler/problem_015/sol1.py b/project_euler/problem_015/sol1.py index fb2020d6179f..fd9014a406f6 100644 --- a/project_euler/problem_015/sol1.py +++ b/project_euler/problem_015/sol1.py @@ -5,6 +5,7 @@ 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 diff --git a/project_euler/problem_018/solution.py b/project_euler/problem_018/solution.py index 70306148bb9e..cbe8743be15f 100644 --- a/project_euler/problem_018/solution.py +++ b/project_euler/problem_018/solution.py @@ -27,6 +27,7 @@ 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 diff --git a/project_euler/problem_020/sol2.py b/project_euler/problem_020/sol2.py index 676e96e7836a..a1d56ade7708 100644 --- a/project_euler/problem_020/sol2.py +++ b/project_euler/problem_020/sol2.py @@ -8,6 +8,7 @@ Find the sum of the digits in the number 100! """ + from math import factorial diff --git a/project_euler/problem_020/sol3.py b/project_euler/problem_020/sol3.py index 4f28ac5fcfde..1886e05463f4 100644 --- a/project_euler/problem_020/sol3.py +++ b/project_euler/problem_020/sol3.py @@ -8,6 +8,7 @@ Find the sum of the digits in the number 100! """ + from math import factorial diff --git a/project_euler/problem_021/sol1.py b/project_euler/problem_021/sol1.py index 353510ae8f94..f6dbfa8864db 100644 --- a/project_euler/problem_021/sol1.py +++ b/project_euler/problem_021/sol1.py @@ -13,6 +13,7 @@ Evaluate the sum of all the amicable numbers under 10000. """ + from math import sqrt diff --git a/project_euler/problem_022/sol1.py b/project_euler/problem_022/sol1.py index 982906245e87..b6386186e7df 100644 --- a/project_euler/problem_022/sol1.py +++ b/project_euler/problem_022/sol1.py @@ -14,6 +14,7 @@ What is the total of all the name scores in the file? """ + import os diff --git a/project_euler/problem_022/sol2.py b/project_euler/problem_022/sol2.py index 5ae41c84686e..f7092ea1cd12 100644 --- a/project_euler/problem_022/sol2.py +++ b/project_euler/problem_022/sol2.py @@ -14,6 +14,7 @@ What is the total of all the name scores in the file? """ + import os diff --git a/project_euler/problem_024/sol1.py b/project_euler/problem_024/sol1.py index 1c6378b38260..3fb1bd4ec582 100644 --- a/project_euler/problem_024/sol1.py +++ b/project_euler/problem_024/sol1.py @@ -9,6 +9,7 @@ What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9? """ + from itertools import permutations diff --git a/project_euler/problem_025/sol2.py b/project_euler/problem_025/sol2.py index 6f49e89fb465..9e950b355f7a 100644 --- a/project_euler/problem_025/sol2.py +++ b/project_euler/problem_025/sol2.py @@ -23,6 +23,7 @@ What is the index of the first term in the Fibonacci sequence to contain 1000 digits? """ + from collections.abc import Generator diff --git a/project_euler/problem_030/sol1.py b/project_euler/problem_030/sol1.py index 2c6b4e4e85d5..7d83e314523f 100644 --- a/project_euler/problem_030/sol1.py +++ b/project_euler/problem_030/sol1.py @@ -1,4 +1,4 @@ -""" Problem Statement (Digit Fifth Powers): https://projecteuler.net/problem=30 +"""Problem Statement (Digit Fifth Powers): https://projecteuler.net/problem=30 Surprisingly there are only three numbers that can be written as the sum of fourth powers of their digits: @@ -21,7 +21,6 @@ and hence a number between 1000 and 1000000 """ - DIGITS_FIFTH_POWER = {str(digit): digit**5 for digit in range(10)} diff --git a/project_euler/problem_032/sol32.py b/project_euler/problem_032/sol32.py index c4d11e86c877..a402b5584061 100644 --- a/project_euler/problem_032/sol32.py +++ b/project_euler/problem_032/sol32.py @@ -12,6 +12,7 @@ HINT: Some products can be obtained in more than one way so be sure to only include it once in your sum. """ + import itertools diff --git a/project_euler/problem_033/sol1.py b/project_euler/problem_033/sol1.py index 32be424b6a7b..187fd61bde6c 100644 --- a/project_euler/problem_033/sol1.py +++ b/project_euler/problem_033/sol1.py @@ -14,6 +14,7 @@ If the product of these four fractions is given in its lowest common terms, find the value of the denominator. """ + from __future__ import annotations from fractions import Fraction diff --git a/project_euler/problem_035/sol1.py b/project_euler/problem_035/sol1.py index 644c992ed8a5..cf9f6821d798 100644 --- a/project_euler/problem_035/sol1.py +++ b/project_euler/problem_035/sol1.py @@ -15,6 +15,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 __future__ import annotations sieve = [True] * 1000001 diff --git a/project_euler/problem_036/sol1.py b/project_euler/problem_036/sol1.py index 1d27356ec51e..3865b2a39ea9 100644 --- a/project_euler/problem_036/sol1.py +++ b/project_euler/problem_036/sol1.py @@ -14,6 +14,7 @@ (Please note that the palindromic number, in either base, may not include leading zeros.) """ + from __future__ import annotations diff --git a/project_euler/problem_038/sol1.py b/project_euler/problem_038/sol1.py index e4a6d09f8f7d..5bef273ea2a9 100644 --- a/project_euler/problem_038/sol1.py +++ b/project_euler/problem_038/sol1.py @@ -37,6 +37,7 @@ => 100 <= a < 334, candidate = a * 10^6 + 2a * 10^3 + 3a = 1002003 * a """ + from __future__ import annotations diff --git a/project_euler/problem_041/sol1.py b/project_euler/problem_041/sol1.py index 2ef0120684c3..0c37f5469a6c 100644 --- a/project_euler/problem_041/sol1.py +++ b/project_euler/problem_041/sol1.py @@ -10,6 +10,7 @@ So we will check only 7 digit pandigital numbers to obtain the largest possible pandigital prime. """ + from __future__ import annotations import math diff --git a/project_euler/problem_042/solution42.py b/project_euler/problem_042/solution42.py index f8a54e40eaab..f678bcdef710 100644 --- a/project_euler/problem_042/solution42.py +++ b/project_euler/problem_042/solution42.py @@ -13,6 +13,7 @@ containing nearly two-thousand common English words, how many are triangle words? """ + import os # Precomputes a list of the 100 first triangular numbers diff --git a/project_euler/problem_043/sol1.py b/project_euler/problem_043/sol1.py index c533f40da9c9..f3a2c71edc4e 100644 --- a/project_euler/problem_043/sol1.py +++ b/project_euler/problem_043/sol1.py @@ -18,7 +18,6 @@ Find the sum of all 0 to 9 pandigital numbers with this property. """ - from itertools import permutations diff --git a/project_euler/problem_050/sol1.py b/project_euler/problem_050/sol1.py index fc6e6f2b9a5d..0a5f861f0ef0 100644 --- a/project_euler/problem_050/sol1.py +++ b/project_euler/problem_050/sol1.py @@ -15,6 +15,7 @@ Which prime, below one-million, can be written as the sum of the most consecutive primes? """ + from __future__ import annotations diff --git a/project_euler/problem_051/sol1.py b/project_euler/problem_051/sol1.py index 921704bc4455..dc740c8b947d 100644 --- a/project_euler/problem_051/sol1.py +++ b/project_euler/problem_051/sol1.py @@ -15,6 +15,7 @@ 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 __future__ import annotations from collections import Counter diff --git a/project_euler/problem_053/sol1.py b/project_euler/problem_053/sol1.py index 0692bbe0ebb8..a32b73c545d6 100644 --- a/project_euler/problem_053/sol1.py +++ b/project_euler/problem_053/sol1.py @@ -16,6 +16,7 @@ How many, not necessarily distinct, values of nCr, for 1 ≤ n ≤ 100, are greater than one-million? """ + from math import factorial diff --git a/project_euler/problem_054/sol1.py b/project_euler/problem_054/sol1.py index 86dfa5edd2f5..66aa3a0826f5 100644 --- a/project_euler/problem_054/sol1.py +++ b/project_euler/problem_054/sol1.py @@ -40,6 +40,7 @@ https://www.codewars.com/kata/ranking-poker-hands https://www.codewars.com/kata/sortable-poker-hands """ + from __future__ import annotations import os diff --git a/project_euler/problem_058/sol1.py b/project_euler/problem_058/sol1.py index 6a991c58b6b8..1d2f406eafdb 100644 --- a/project_euler/problem_058/sol1.py +++ b/project_euler/problem_058/sol1.py @@ -33,6 +33,7 @@ count of current primes. """ + import math diff --git a/project_euler/problem_059/sol1.py b/project_euler/problem_059/sol1.py index b795dd243b08..65bfd3f0b0fb 100644 --- a/project_euler/problem_059/sol1.py +++ b/project_euler/problem_059/sol1.py @@ -25,6 +25,7 @@ must contain common English words, decrypt the message and find the sum of the ASCII values in the original text. """ + from __future__ import annotations import string diff --git a/project_euler/problem_067/sol1.py b/project_euler/problem_067/sol1.py index 2b41fedc6784..171ff8c268f6 100644 --- a/project_euler/problem_067/sol1.py +++ b/project_euler/problem_067/sol1.py @@ -11,6 +11,7 @@ 'Save Link/Target As...'), a 15K text file containing a triangle with one-hundred rows. """ + import os diff --git a/project_euler/problem_067/sol2.py b/project_euler/problem_067/sol2.py index 2e88a57170a8..4fb093d49956 100644 --- a/project_euler/problem_067/sol2.py +++ b/project_euler/problem_067/sol2.py @@ -11,6 +11,7 @@ 'Save Link/Target As...'), a 15K text file containing a triangle with one-hundred rows. """ + import os diff --git a/project_euler/problem_070/sol1.py b/project_euler/problem_070/sol1.py index f1114a280a31..9874b7418559 100644 --- a/project_euler/problem_070/sol1.py +++ b/project_euler/problem_070/sol1.py @@ -28,6 +28,7 @@ Finding totients https://en.wikipedia.org/wiki/Euler's_totient_function#Euler's_product_formula """ + from __future__ import annotations import numpy as np diff --git a/project_euler/problem_074/sol1.py b/project_euler/problem_074/sol1.py index a257d4d94fa8..91440b3fd02b 100644 --- a/project_euler/problem_074/sol1.py +++ b/project_euler/problem_074/sol1.py @@ -27,7 +27,6 @@ non-repeating terms? """ - DIGIT_FACTORIALS = { "0": 1, "1": 1, diff --git a/project_euler/problem_074/sol2.py b/project_euler/problem_074/sol2.py index b54bc023e387..52a996bfa51d 100644 --- a/project_euler/problem_074/sol2.py +++ b/project_euler/problem_074/sol2.py @@ -33,6 +33,7 @@ is greater then the desired one. After generating each chain, the length is checked and the counter increases. """ + from math import factorial DIGIT_FACTORIAL: dict[str, int] = {str(digit): factorial(digit) for digit in range(10)} diff --git a/project_euler/problem_077/sol1.py b/project_euler/problem_077/sol1.py index 6098ea9e50a6..e8f4e979a625 100644 --- a/project_euler/problem_077/sol1.py +++ b/project_euler/problem_077/sol1.py @@ -12,6 +12,7 @@ What is the first value which can be written as the sum of primes in over five thousand different ways? """ + from __future__ import annotations from functools import lru_cache diff --git a/project_euler/problem_079/sol1.py b/project_euler/problem_079/sol1.py index d34adcd243b0..74392e9bd094 100644 --- a/project_euler/problem_079/sol1.py +++ b/project_euler/problem_079/sol1.py @@ -13,6 +13,7 @@ Given that the three characters are always asked for in order, analyse the file so as to determine the shortest possible secret passcode of unknown length. """ + import itertools from pathlib import Path diff --git a/project_euler/problem_080/sol1.py b/project_euler/problem_080/sol1.py index 916998bdd8ad..8cfcbd41b588 100644 --- a/project_euler/problem_080/sol1.py +++ b/project_euler/problem_080/sol1.py @@ -6,6 +6,7 @@ square roots. Time: 5 October 2020, 18:30 """ + import decimal diff --git a/project_euler/problem_081/sol1.py b/project_euler/problem_081/sol1.py index aef6106b54df..293027bddd0e 100644 --- a/project_euler/problem_081/sol1.py +++ b/project_euler/problem_081/sol1.py @@ -13,6 +13,7 @@ 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 diff --git a/project_euler/problem_085/sol1.py b/project_euler/problem_085/sol1.py index d0f29796498c..d0b361ee750d 100644 --- a/project_euler/problem_085/sol1.py +++ b/project_euler/problem_085/sol1.py @@ -44,6 +44,7 @@ Reference: https://en.wikipedia.org/wiki/Triangular_number https://en.wikipedia.org/wiki/Quadratic_formula """ + from __future__ import annotations from math import ceil, floor, sqrt diff --git a/project_euler/problem_086/sol1.py b/project_euler/problem_086/sol1.py index 064af215c049..cbd2b648e0ac 100644 --- a/project_euler/problem_086/sol1.py +++ b/project_euler/problem_086/sol1.py @@ -66,7 +66,6 @@ """ - from math import sqrt diff --git a/project_euler/problem_091/sol1.py b/project_euler/problem_091/sol1.py index 6c9aa3fa6c70..7db98fca0049 100644 --- a/project_euler/problem_091/sol1.py +++ b/project_euler/problem_091/sol1.py @@ -11,7 +11,6 @@ Given that 0 ≤ x1, y1, x2, y2 ≤ 50, how many right triangles can be formed? """ - from itertools import combinations, product diff --git a/project_euler/problem_101/sol1.py b/project_euler/problem_101/sol1.py index d5c503af796a..2d209333cf31 100644 --- a/project_euler/problem_101/sol1.py +++ b/project_euler/problem_101/sol1.py @@ -41,6 +41,7 @@ Find the sum of FITs for the BOPs. """ + from __future__ import annotations from collections.abc import Callable diff --git a/project_euler/problem_102/sol1.py b/project_euler/problem_102/sol1.py index 4f6e6361e3e8..85fe5eac1e22 100644 --- a/project_euler/problem_102/sol1.py +++ b/project_euler/problem_102/sol1.py @@ -18,6 +18,7 @@ NOTE: The first two examples in the file represent the triangles in the example given above. """ + from __future__ import annotations from pathlib import Path diff --git a/project_euler/problem_107/sol1.py b/project_euler/problem_107/sol1.py index 4659eac24bd3..3fe75909e2ea 100644 --- a/project_euler/problem_107/sol1.py +++ b/project_euler/problem_107/sol1.py @@ -27,6 +27,7 @@ We use Prim's algorithm to find a Minimum Spanning Tree. Reference: https://en.wikipedia.org/wiki/Prim%27s_algorithm """ + from __future__ import annotations import os diff --git a/project_euler/problem_123/sol1.py b/project_euler/problem_123/sol1.py index f74cdd999401..7239e13a51e9 100644 --- a/project_euler/problem_123/sol1.py +++ b/project_euler/problem_123/sol1.py @@ -37,6 +37,7 @@ r = 2pn when n is odd r = 2 when n is even. """ + from __future__ import annotations from collections.abc import Generator diff --git a/project_euler/problem_144/sol1.py b/project_euler/problem_144/sol1.py index b5f103b64ff5..bc16bf985f41 100644 --- a/project_euler/problem_144/sol1.py +++ b/project_euler/problem_144/sol1.py @@ -29,7 +29,6 @@ How many times does the beam hit the internal surface of the white cell before exiting? """ - from math import isclose, sqrt diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py index 71b851178fdb..ce4438289722 100644 --- a/project_euler/problem_145/sol1.py +++ b/project_euler/problem_145/sol1.py @@ -13,6 +13,7 @@ How many reversible numbers are there below one-billion (10^9)? """ + EVEN_DIGITS = [0, 2, 4, 6, 8] ODD_DIGITS = [1, 3, 5, 7, 9] diff --git a/project_euler/problem_173/sol1.py b/project_euler/problem_173/sol1.py index 5416e25462cc..9235d00e1752 100644 --- a/project_euler/problem_173/sol1.py +++ b/project_euler/problem_173/sol1.py @@ -11,7 +11,6 @@ Using up to one million tiles how many different square laminae can be formed? """ - from math import ceil, sqrt diff --git a/project_euler/problem_180/sol1.py b/project_euler/problem_180/sol1.py index 12e34dcaa76b..72baed42b99e 100644 --- a/project_euler/problem_180/sol1.py +++ b/project_euler/problem_180/sol1.py @@ -44,6 +44,7 @@ Reference: https://en.wikipedia.org/wiki/Fermat%27s_Last_Theorem """ + from __future__ import annotations from fractions import Fraction diff --git a/project_euler/problem_191/sol1.py b/project_euler/problem_191/sol1.py index 6bff9d54eeca..efb2a5d086ad 100644 --- a/project_euler/problem_191/sol1.py +++ b/project_euler/problem_191/sol1.py @@ -25,7 +25,6 @@ https://projecteuler.net/problem=191 """ - cache: dict[tuple[int, int, int], int] = {} diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py index da9436246a7c..8ad089ec09aa 100644 --- a/project_euler/problem_203/sol1.py +++ b/project_euler/problem_203/sol1.py @@ -27,6 +27,7 @@ References: - https://en.wikipedia.org/wiki/Pascal%27s_triangle """ + from __future__ import annotations diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index 2cd75efbb68d..100e9d41dd31 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -12,7 +12,6 @@ Find a(10^15) """ - ks = range(2, 20 + 1) base = [10**k for k in range(ks[-1] + 1)] memo: dict[int, dict[int, list[list[int]]]] = {} diff --git a/scheduling/highest_response_ratio_next.py b/scheduling/highest_response_ratio_next.py index 057bd64cc729..112c2a85220f 100644 --- a/scheduling/highest_response_ratio_next.py +++ b/scheduling/highest_response_ratio_next.py @@ -4,6 +4,7 @@ to mitigate the problem of process starvation. https://en.wikipedia.org/wiki/Highest_response_ratio_next """ + from statistics import mean import numpy as np diff --git a/scheduling/job_sequence_with_deadline.py b/scheduling/job_sequence_with_deadline.py index fccb49cd88e8..ee1fdbd0e55c 100644 --- a/scheduling/job_sequence_with_deadline.py +++ b/scheduling/job_sequence_with_deadline.py @@ -13,6 +13,7 @@ Time Complexity - O(n log n) https://medium.com/@nihardudhat2000/job-sequencing-with-deadline-17ddbb5890b5 """ + from dataclasses import dataclass from operator import attrgetter diff --git a/scheduling/non_preemptive_shortest_job_first.py b/scheduling/non_preemptive_shortest_job_first.py index 69c974b0044d..cb7ffd3abd9c 100644 --- a/scheduling/non_preemptive_shortest_job_first.py +++ b/scheduling/non_preemptive_shortest_job_first.py @@ -5,7 +5,6 @@ https://en.wikipedia.org/wiki/Shortest_job_next """ - from __future__ import annotations from statistics import mean diff --git a/scheduling/round_robin.py b/scheduling/round_robin.py index e8d54dd9a553..5f6c7f341baa 100644 --- a/scheduling/round_robin.py +++ b/scheduling/round_robin.py @@ -3,6 +3,7 @@ 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 diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index 871de8207308..cfd0417ea62d 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -3,6 +3,7 @@ Please note arrival time and burst Please use spaces to separate times entered. """ + from __future__ import annotations import pandas as pd diff --git a/searches/binary_search.py b/searches/binary_search.py index 586be39c9a0d..2e66b672d5b4 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -9,6 +9,7 @@ For manual testing run: python3 binary_search.py """ + from __future__ import annotations import bisect diff --git a/searches/binary_tree_traversal.py b/searches/binary_tree_traversal.py index 6fb841af4294..4897ef17299c 100644 --- a/searches/binary_tree_traversal.py +++ b/searches/binary_tree_traversal.py @@ -1,6 +1,7 @@ """ This is pure Python implementation of tree traversal algorithms """ + from __future__ import annotations import queue diff --git a/searches/fibonacci_search.py b/searches/fibonacci_search.py index 55fc05d39eeb..ec3dfa7f30f6 100644 --- a/searches/fibonacci_search.py +++ b/searches/fibonacci_search.py @@ -10,6 +10,7 @@ For manual testing run: python3 fibonacci_search.py """ + from functools import lru_cache diff --git a/searches/jump_search.py b/searches/jump_search.py index 3bc3c37809a1..e72d85e8a868 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -14,8 +14,7 @@ class Comparable(Protocol): - def __lt__(self, other: Any, /) -> bool: - ... + def __lt__(self, other: Any, /) -> bool: ... T = TypeVar("T", bound=Comparable) diff --git a/searches/quick_select.py b/searches/quick_select.py index 5ede8c4dd07f..c8282e1fa5fc 100644 --- a/searches/quick_select.py +++ b/searches/quick_select.py @@ -4,6 +4,7 @@ sorted, even if it is not already sorted https://en.wikipedia.org/wiki/Quickselect """ + import random diff --git a/searches/simple_binary_search.py b/searches/simple_binary_search.py index ff043d7369af..00e83ff9e4a3 100644 --- a/searches/simple_binary_search.py +++ b/searches/simple_binary_search.py @@ -7,6 +7,7 @@ For manual testing run: python3 simple_binary_search.py """ + from __future__ import annotations diff --git a/searches/tabu_search.py b/searches/tabu_search.py index d998ddc55976..fd482a81224c 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -24,6 +24,7 @@ -s size_of_tabu_search e.g. python tabu_search.py -f tabudata2.txt -i 4 -s 3 """ + import argparse import copy diff --git a/searches/ternary_search.py b/searches/ternary_search.py index cb36e72faac6..8dcd6b5bde2e 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -6,6 +6,7 @@ Time Complexity : O(log3 N) Space Complexity : O(1) """ + from __future__ import annotations # This is the precision for this function which can be altered. diff --git a/sorts/bitonic_sort.py b/sorts/bitonic_sort.py index b65f877a45e3..600f8139603a 100644 --- a/sorts/bitonic_sort.py +++ b/sorts/bitonic_sort.py @@ -3,6 +3,7 @@ Note that this program works only when size of input is a power of 2. """ + from __future__ import annotations diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index c016e9e26e73..1c1320a58a7d 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -27,6 +27,7 @@ Source: https://en.wikipedia.org/wiki/Bucket_sort """ + from __future__ import annotations diff --git a/sorts/dutch_national_flag_sort.py b/sorts/dutch_national_flag_sort.py index 758e3a887b84..b4f1665cea00 100644 --- a/sorts/dutch_national_flag_sort.py +++ b/sorts/dutch_national_flag_sort.py @@ -23,7 +23,6 @@ python dnf_sort.py """ - # Python program to sort a sequence containing only 0, 1 and 2 in a single pass. red = 0 # The first color of the flag. white = 1 # The second color of the flag. diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index f11ddac349a0..46b263d84a33 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -18,8 +18,7 @@ class Comparable(Protocol): - def __lt__(self, other: Any, /) -> bool: - ... + def __lt__(self, other: Any, /) -> bool: ... T = TypeVar("T", bound=Comparable) diff --git a/sorts/intro_sort.py b/sorts/intro_sort.py index 5a5741dc8375..1184b381b05d 100644 --- a/sorts/intro_sort.py +++ b/sorts/intro_sort.py @@ -3,6 +3,7 @@ if the size of the list is under 16, use insertion sort https://en.wikipedia.org/wiki/Introsort """ + import math diff --git a/sorts/msd_radix_sort.py b/sorts/msd_radix_sort.py index 03f84c75b9d8..6aba4263663a 100644 --- a/sorts/msd_radix_sort.py +++ b/sorts/msd_radix_sort.py @@ -4,6 +4,7 @@ them. https://en.wikipedia.org/wiki/Radix_sort """ + from __future__ import annotations diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index b8ab46df1e59..9d2bcdbd7576 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -10,6 +10,7 @@ They are synchronized with locks and message passing but other forms of synchronization could be used. """ + from multiprocessing import Lock, Pipe, Process # lock used to ensure that two processes do not access a pipe at the same time diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index 3e6d4c09c46f..fdfa692f4680 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -1,14 +1,15 @@ """ - This is an implementation of Pigeon Hole Sort. - For doctests run following command: +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 +python3 -m doctest -v pigeon_sort.py +or +python -m doctest -v pigeon_sort.py - For manual testing run: - python pigeon_sort.py +For manual testing run: +python pigeon_sort.py """ + from __future__ import annotations diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 6b95fc144426..374d52e75c81 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -7,6 +7,7 @@ For manual testing run: python3 quick_sort.py """ + from __future__ import annotations from random import randrange diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index 832b6162f349..1dbf5fbd1365 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -3,6 +3,7 @@ Source: https://en.wikipedia.org/wiki/Radix_sort """ + from __future__ import annotations RADIX = 10 diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index 297dbe9457e6..93465350bee2 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -1,6 +1,7 @@ """ A recursive implementation of the insertion sort algorithm """ + from __future__ import annotations diff --git a/sorts/slowsort.py b/sorts/slowsort.py index a5f4e873ebb2..394e6eed50b1 100644 --- a/sorts/slowsort.py +++ b/sorts/slowsort.py @@ -8,6 +8,7 @@ Source: https://en.wikipedia.org/wiki/Slowsort """ + from __future__ import annotations diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index dc95856f44c8..056864957d4d 100644 --- a/sorts/tree_sort.py +++ b/sorts/tree_sort.py @@ -3,6 +3,7 @@ Build a Binary Search Tree and then iterate thru it to get a sorted list. """ + from __future__ import annotations from collections.abc import Iterator diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index 117305d32fd3..9615d2fd659b 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -17,6 +17,7 @@ n=length of main string m=length of pattern string """ + from __future__ import annotations diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index 9dcdffcfb921..d747368b2373 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -1,6 +1,7 @@ """ wiki: https://en.wikipedia.org/wiki/Anagram """ + from collections import defaultdict diff --git a/strings/top_k_frequent_words.py b/strings/top_k_frequent_words.py index f3d1e0cd5ca7..40fa7fc85cd1 100644 --- a/strings/top_k_frequent_words.py +++ b/strings/top_k_frequent_words.py @@ -13,7 +13,6 @@ def top_k_frequent_words(words, k_value): return [x[0] for x in Counter(words).most_common(k_value)] """ - from collections import Counter from functools import total_ordering diff --git a/web_programming/co2_emission.py b/web_programming/co2_emission.py index 97927e7ef541..88a426cb976d 100644 --- a/web_programming/co2_emission.py +++ b/web_programming/co2_emission.py @@ -1,6 +1,7 @@ """ Get CO2 emission data from the UK CarbonIntensity API """ + from datetime import date import requests diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index 074ef878c0d7..6b4bacfe7d5a 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -1,4 +1,5 @@ """Get the site emails from URL.""" + from __future__ import annotations __author__ = "Muhammad Umer Farooq" diff --git a/web_programming/fetch_github_info.py b/web_programming/fetch_github_info.py index aa4e1d7b1963..7a4985b68841 100644 --- a/web_programming/fetch_github_info.py +++ b/web_programming/fetch_github_info.py @@ -17,6 +17,7 @@ #!/usr/bin/env bash export USER_TOKEN="" """ + from __future__ import annotations import os diff --git a/web_programming/fetch_jobs.py b/web_programming/fetch_jobs.py index 5af90a0bb239..49abd3c88eec 100644 --- a/web_programming/fetch_jobs.py +++ b/web_programming/fetch_jobs.py @@ -1,6 +1,7 @@ """ Scraping jobs given job title and location from indeed website """ + from __future__ import annotations from collections.abc import Generator diff --git a/web_programming/get_amazon_product_data.py b/web_programming/get_amazon_product_data.py index a16175688667..c2f2ac5ab291 100644 --- a/web_programming/get_amazon_product_data.py +++ b/web_programming/get_amazon_product_data.py @@ -4,7 +4,6 @@ information will include title, URL, price, ratings, and the discount available. """ - from itertools import zip_longest import requests diff --git a/web_programming/recaptcha_verification.py b/web_programming/recaptcha_verification.py index 47c6c42f2ad0..b03afb28ec53 100644 --- a/web_programming/recaptcha_verification.py +++ b/web_programming/recaptcha_verification.py @@ -31,6 +31,7 @@ Below a Django function for the views.py file contains a login form for demonstrating recaptcha verification. """ + import requests try: diff --git a/web_programming/search_books_by_isbn.py b/web_programming/search_books_by_isbn.py index d5d4cfe92f20..07429e9a9678 100644 --- a/web_programming/search_books_by_isbn.py +++ b/web_programming/search_books_by_isbn.py @@ -3,6 +3,7 @@ ISBN: https://en.wikipedia.org/wiki/International_Standard_Book_Number """ + from json import JSONDecodeError # Workaround for requests.exceptions.JSONDecodeError import requests From 435309a61aa70303133306c9fe06a3df118c9a5c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:46:32 +0100 Subject: [PATCH 2689/2908] [pre-commit.ci] pre-commit autoupdate (#11325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.2 → v0.3.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.2...v0.3.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a17c4c323c30..c4b30f29a5b5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.2 + rev: v0.3.3 hooks: - id: ruff - id: ruff-format From 8faf823e83a1b7a036e2f2569c0c185924c05307 Mon Sep 17 00:00:00 2001 From: Margaret <62753112+meg-1@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:33:40 +0200 Subject: [PATCH 2690/2908] adding a proper fractions algorithm (#11224) * adding a proper fractions algorithm * Implementing suggestions in maths/numerical_analysis/proper_fractions.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Implementing suggestions to proper_fractions.py * Fixing ruff errors in proper_fractions.py * Apply suggestions from code review * ruff check --output-format=github . * Update maths/numerical_analysis/proper_fractions.py * Update proper_fractions.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/ruff.yml | 2 +- maths/numerical_analysis/proper_fractions.py | 40 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 maths/numerical_analysis/proper_fractions.py diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 9ebabed3600a..d354eba672ae 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -13,4 +13,4 @@ jobs: steps: - uses: actions/checkout@v4 - run: pip install --user ruff - - run: ruff --output-format=github . + - run: ruff check --output-format=github . diff --git a/maths/numerical_analysis/proper_fractions.py b/maths/numerical_analysis/proper_fractions.py new file mode 100644 index 000000000000..774ce9a24876 --- /dev/null +++ b/maths/numerical_analysis/proper_fractions.py @@ -0,0 +1,40 @@ +from math import gcd + + +def proper_fractions(denominator: int) -> list[str]: + """ + this algorithm returns a list of proper fractions, in the + range between 0 and 1, which can be formed with the given denominator + https://en.wikipedia.org/wiki/Fraction#Proper_and_improper_fractions + + >>> proper_fractions(10) + ['1/10', '3/10', '7/10', '9/10'] + >>> proper_fractions(5) + ['1/5', '2/5', '3/5', '4/5'] + >>> proper_fractions(-15) + Traceback (most recent call last): + ... + ValueError: The Denominator Cannot be less than 0 + >>> proper_fractions(0) + [] + >>> proper_fractions(1.2) + Traceback (most recent call last): + ... + ValueError: The Denominator must be an integer + """ + + if denominator < 0: + raise ValueError("The Denominator Cannot be less than 0") + elif isinstance(denominator, float): + raise ValueError("The Denominator must be an integer") + return [ + f"{numerator}/{denominator}" + for numerator in range(1, denominator) + if gcd(numerator, denominator) == 1 + ] + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From a936e94704b09841784358a4ac002401f3faceed Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 20 Mar 2024 17:00:17 +0300 Subject: [PATCH 2691/2908] Enable ruff ARG001 rule (#11321) * Enable ruff ARG001 rule * Fix dynamic_programming/combination_sum_iv.py * Fix machine_learning/frequent_pattern_growth.py * Fix other/davis_putnam_logemann_loveland.py * Fix other/password.py * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix physics/n_body_simulation.py * Fix project_euler/problem_145/sol1.py * Fix project_euler/problem_174/sol1.py * Fix scheduling/highest_response_ratio_next.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * Fix * Fix scheduling/job_sequencing_with_deadline.py * Fix scheduling/job_sequencing_with_deadline.py * Fix * Fix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/combination_sum_iv.py | 11 +++++------ machine_learning/frequent_pattern_growth.py | 4 ++-- other/davis_putnam_logemann_loveland.py | 3 ++- other/password.py | 12 ------------ physics/n_body_simulation.py | 2 +- project_euler/problem_145/sol1.py | 2 +- project_euler/problem_174/sol1.py | 4 +++- pyproject.toml | 1 - scheduling/highest_response_ratio_next.py | 5 ++++- scheduling/job_sequencing_with_deadline.py | 7 +++---- web_programming/nasa_data.py | 2 +- 11 files changed, 22 insertions(+), 31 deletions(-) diff --git a/dynamic_programming/combination_sum_iv.py b/dynamic_programming/combination_sum_iv.py index b2aeb0824f64..4526729b70b7 100644 --- a/dynamic_programming/combination_sum_iv.py +++ b/dynamic_programming/combination_sum_iv.py @@ -22,12 +22,12 @@ """ -def combination_sum_iv(n: int, array: list[int], target: int) -> int: +def combination_sum_iv(array: list[int], target: int) -> int: """ Function checks the all possible combinations, and returns the count of possible combination in exponential Time Complexity. - >>> combination_sum_iv(3, [1,2,5], 5) + >>> combination_sum_iv([1,2,5], 5) 9 """ @@ -41,13 +41,13 @@ def count_of_possible_combinations(target: int) -> int: return count_of_possible_combinations(target) -def combination_sum_iv_dp_array(n: int, array: list[int], target: int) -> int: +def combination_sum_iv_dp_array(array: list[int], target: int) -> int: """ Function checks the all possible combinations, and returns the count of possible combination in O(N^2) Time Complexity as we are using Dynamic programming array here. - >>> combination_sum_iv_dp_array(3, [1,2,5], 5) + >>> combination_sum_iv_dp_array([1,2,5], 5) 9 """ @@ -96,7 +96,6 @@ def combination_sum_iv_bottom_up(n: int, array: list[int], target: int) -> int: import doctest doctest.testmod() - n = 3 target = 5 array = [1, 2, 5] - print(combination_sum_iv(n, array, target)) + print(combination_sum_iv(array, target)) diff --git a/machine_learning/frequent_pattern_growth.py b/machine_learning/frequent_pattern_growth.py index 6b9870f5e1d2..947f8692f298 100644 --- a/machine_learning/frequent_pattern_growth.py +++ b/machine_learning/frequent_pattern_growth.py @@ -240,7 +240,7 @@ def ascend_tree(leaf_node: TreeNode, prefix_path: list[str]) -> None: ascend_tree(leaf_node.parent, prefix_path) -def find_prefix_path(base_pat: frozenset, tree_node: TreeNode | None) -> dict: +def find_prefix_path(base_pat: frozenset, tree_node: TreeNode | None) -> dict: # noqa: ARG001 """ Find the conditional pattern base for a given base pattern. @@ -277,7 +277,7 @@ def find_prefix_path(base_pat: frozenset, tree_node: TreeNode | None) -> dict: def mine_tree( - in_tree: TreeNode, + in_tree: TreeNode, # noqa: ARG001 header_table: dict, min_sup: int, pre_fix: set, diff --git a/other/davis_putnam_logemann_loveland.py b/other/davis_putnam_logemann_loveland.py index 436577eb5b5d..5c6e2d9ffd5e 100644 --- a/other/davis_putnam_logemann_loveland.py +++ b/other/davis_putnam_logemann_loveland.py @@ -227,7 +227,8 @@ def find_pure_symbols( def find_unit_clauses( - clauses: list[Clause], model: dict[str, bool | None] + clauses: list[Clause], + model: dict[str, bool | None], # noqa: ARG001 ) -> tuple[list[str], dict[str, bool | None]]: """ Returns the unit symbols and their values to satisfy clause. diff --git a/other/password.py b/other/password.py index 1ce0d52316e6..dff1316c049c 100644 --- a/other/password.py +++ b/other/password.py @@ -51,18 +51,6 @@ def random(chars_incl: str, i: int) -> str: return "".join(secrets.choice(chars_incl) for _ in range(i)) -def random_number(chars_incl, i): - pass # Put your code here... - - -def random_letters(chars_incl, i): - pass # Put your code here... - - -def random_characters(chars_incl, i): - pass # Put your code here... - - def is_strong_password(password: str, min_length: int = 8) -> bool: """ This will check whether a given password is strong or not. The password must be at diff --git a/physics/n_body_simulation.py b/physics/n_body_simulation.py index 4d555716199a..9bfb6b3c6864 100644 --- a/physics/n_body_simulation.py +++ b/physics/n_body_simulation.py @@ -239,7 +239,7 @@ def plot( ax.add_patch(patch) # Function called at each step of the animation - def update(frame: int) -> list[plt.Circle]: + def update(frame: int) -> list[plt.Circle]: # noqa: ARG001 update_step(body_system, DELTA_TIME, patches) return patches diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py index ce4438289722..583bb03a0a90 100644 --- a/project_euler/problem_145/sol1.py +++ b/project_euler/problem_145/sol1.py @@ -110,7 +110,7 @@ def reversible_numbers( if (length - 1) % 4 == 0: return 0 - return slow_reversible_numbers(length, 0, [0] * length, length) + return slow_reversible_numbers(remaining_length, remainder, digits, length) def solution(max_power: int = 9) -> int: diff --git a/project_euler/problem_174/sol1.py b/project_euler/problem_174/sol1.py index cbc0df5a9d65..33c1b158adbb 100644 --- a/project_euler/problem_174/sol1.py +++ b/project_euler/problem_174/sol1.py @@ -26,6 +26,8 @@ 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) + 222 + >>> solution(1000,10) 249 >>> solution(10000,10) 2383 @@ -45,7 +47,7 @@ def solution(t_limit: int = 1000000, n_limit: int = 10) -> int: 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) + return sum(1 for n in count.values() if 1 <= n <= n_limit) if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 2e7da519da8b..a69ab7aa6437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [tool.ruff] lint.ignore = [ # `ruff rule S101` for a description of that rule - "ARG001", # Unused function argument `amount` -- FIX ME? "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME "DTZ001", # The use of `datetime.datetime()` without `tzinfo` argument is not allowed -- FIX ME diff --git a/scheduling/highest_response_ratio_next.py b/scheduling/highest_response_ratio_next.py index 112c2a85220f..b549835616bf 100644 --- a/scheduling/highest_response_ratio_next.py +++ b/scheduling/highest_response_ratio_next.py @@ -75,7 +75,10 @@ def calculate_turn_around_time( def calculate_waiting_time( - process_name: list, turn_around_time: list, burst_time: list, no_of_process: int + process_name: list, # noqa: ARG001 + turn_around_time: list, + burst_time: list, + no_of_process: int, ) -> list: """ Calculate the waiting time of each processes. diff --git a/scheduling/job_sequencing_with_deadline.py b/scheduling/job_sequencing_with_deadline.py index 7b23c0b3575f..13946948492f 100644 --- a/scheduling/job_sequencing_with_deadline.py +++ b/scheduling/job_sequencing_with_deadline.py @@ -1,9 +1,8 @@ -def job_sequencing_with_deadlines(num_jobs: int, jobs: list) -> list: +def job_sequencing_with_deadlines(jobs: list) -> list: """ Function to find the maximum profit by doing jobs in a given time frame Args: - num_jobs [int]: Number of jobs jobs [list]: A list of tuples of (job_id, deadline, profit) Returns: @@ -11,10 +10,10 @@ def job_sequencing_with_deadlines(num_jobs: int, jobs: list) -> list: in a given time frame Examples: - >>> job_sequencing_with_deadlines(4, + >>> job_sequencing_with_deadlines( ... [(1, 4, 20), (2, 1, 10), (3, 1, 40), (4, 1, 30)]) [2, 60] - >>> job_sequencing_with_deadlines(5, + >>> job_sequencing_with_deadlines( ... [(1, 2, 100), (2, 1, 19), (3, 2, 27), (4, 1, 25), (5, 1, 15)]) [2, 127] """ diff --git a/web_programming/nasa_data.py b/web_programming/nasa_data.py index c0a2c4fdd1a7..81125e0a4f05 100644 --- a/web_programming/nasa_data.py +++ b/web_programming/nasa_data.py @@ -3,7 +3,7 @@ import requests -def get_apod_data(api_key: str, download: bool = False, path: str = ".") -> dict: +def get_apod_data(api_key: str) -> dict: """ Get the APOD(Astronomical Picture of the day) data Get your API Key from: https://api.nasa.gov/ From 481c071e8423ed3b17ddff96b905da3d27d4f7b4 Mon Sep 17 00:00:00 2001 From: Mehdi Oudghiri <144174136+PAxitoo@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:07:55 +0100 Subject: [PATCH 2692/2908] add vicsek to fractals (#11306) Co-authored-by: BastosLaG --- fractals/vicsek.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 fractals/vicsek.py diff --git a/fractals/vicsek.py b/fractals/vicsek.py new file mode 100644 index 000000000000..290fe95b79b4 --- /dev/null +++ b/fractals/vicsek.py @@ -0,0 +1,76 @@ +"""Authors Bastien Capiaux & Mehdi Oudghiri + +The Vicsek fractal algorithm is a recursive algorithm that creates a +pattern known as the Vicsek fractal or the Vicsek square. +It is based on the concept of self-similarity, where the pattern at each +level of recursion resembles the overall pattern. +The algorithm involves dividing a square into 9 equal smaller squares, +removing the center square, and then repeating this process on the remaining 8 squares. +This results in a pattern that exhibits self-similarity and has a +square-shaped outline with smaller squares within it. + +Source: https://en.wikipedia.org/wiki/Vicsek_fractal +""" + +import turtle + + +def draw_cross(x: float, y: float, length: float): + """ + Draw a cross at the specified position and with the specified length. + """ + turtle.up() + turtle.goto(x - length / 2, y - length / 6) + turtle.down() + turtle.seth(0) + turtle.begin_fill() + for _ in range(4): + turtle.fd(length / 3) + turtle.right(90) + turtle.fd(length / 3) + turtle.left(90) + turtle.fd(length / 3) + turtle.left(90) + turtle.end_fill() + + +def draw_fractal_recursive(x: float, y: float, length: float, depth: float): + """ + Recursively draw the Vicsek fractal at the specified position, with the + specified length and depth. + """ + if depth == 0: + draw_cross(x, y, length) + return + + draw_fractal_recursive(x, y, length / 3, depth - 1) + draw_fractal_recursive(x + length / 3, y, length / 3, depth - 1) + draw_fractal_recursive(x - length / 3, y, length / 3, depth - 1) + draw_fractal_recursive(x, y + length / 3, length / 3, depth - 1) + draw_fractal_recursive(x, y - length / 3, length / 3, depth - 1) + + +def set_color(rgb: str): + turtle.color(rgb) + + +def draw_vicsek_fractal(x: float, y: float, length: float, depth: float, color="blue"): + """ + Draw the Vicsek fractal at the specified position, with the specified + length and depth. + """ + turtle.speed(0) + turtle.hideturtle() + set_color(color) + draw_fractal_recursive(x, y, length, depth) + turtle.Screen().update() + + +def main(): + draw_vicsek_fractal(0, 0, 800, 4) + + turtle.done() + + +if __name__ == "__main__": + main() From 102e9a31b673e5444678fd55640a0038b6a16a9d Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 25 Mar 2024 10:43:24 +0300 Subject: [PATCH 2693/2908] Enable ruff DTZ001 rule (#11326) * updating DIRECTORY.md * Enable ruff DTZ001 rule * Fix other/gauss_easter.py * Fix * Fix * Fix * Fix * Fix * Fix --------- Co-authored-by: MaximSmolskiy --- DIRECTORY.md | 2 ++ other/gauss_easter.py | 16 ++++++++-------- pyproject.toml | 1 - 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 2f828aa512a9..01667c9feee8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -419,6 +419,7 @@ * [Koch Snowflake](fractals/koch_snowflake.py) * [Mandelbrot](fractals/mandelbrot.py) * [Sierpinski Triangle](fractals/sierpinski_triangle.py) + * [Vicsek](fractals/vicsek.py) ## Fuzzy Logic * [Fuzzy Operations](fuzzy_logic/fuzzy_operations.py) @@ -678,6 +679,7 @@ * [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py) * [Newton Raphson](maths/numerical_analysis/newton_raphson.py) * [Numerical Integration](maths/numerical_analysis/numerical_integration.py) + * [Proper Fractions](maths/numerical_analysis/proper_fractions.py) * [Runge Kutta](maths/numerical_analysis/runge_kutta.py) * [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py) * [Runge Kutta Gills](maths/numerical_analysis/runge_kutta_gills.py) diff --git a/other/gauss_easter.py b/other/gauss_easter.py index d1c525593f79..7ccea7f5bbf0 100644 --- a/other/gauss_easter.py +++ b/other/gauss_easter.py @@ -3,7 +3,7 @@ """ import math -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta def gauss_easter(year: int) -> datetime: @@ -11,16 +11,16 @@ def gauss_easter(year: int) -> datetime: Calculation Gregorian easter date for given year >>> gauss_easter(2007) - datetime.datetime(2007, 4, 8, 0, 0) + datetime.datetime(2007, 4, 8, 0, 0, tzinfo=datetime.timezone.utc) >>> gauss_easter(2008) - datetime.datetime(2008, 3, 23, 0, 0) + datetime.datetime(2008, 3, 23, 0, 0, tzinfo=datetime.timezone.utc) >>> gauss_easter(2020) - datetime.datetime(2020, 4, 12, 0, 0) + datetime.datetime(2020, 4, 12, 0, 0, tzinfo=datetime.timezone.utc) >>> gauss_easter(2021) - datetime.datetime(2021, 4, 4, 0, 0) + datetime.datetime(2021, 4, 4, 0, 0, tzinfo=datetime.timezone.utc) """ metonic_cycle = year % 19 julian_leap_year = year % 4 @@ -45,11 +45,11 @@ def gauss_easter(year: int) -> datetime: ) % 7 if days_to_add == 29 and days_from_phm_to_sunday == 6: - return datetime(year, 4, 19) + return datetime(year, 4, 19, tzinfo=UTC) elif days_to_add == 28 and days_from_phm_to_sunday == 6: - return datetime(year, 4, 18) + return datetime(year, 4, 18, tzinfo=UTC) else: - return datetime(year, 3, 22) + timedelta( + return datetime(year, 3, 22, tzinfo=UTC) + timedelta( days=int(days_to_add + days_from_phm_to_sunday) ) diff --git a/pyproject.toml b/pyproject.toml index a69ab7aa6437..09093433a47a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME - "DTZ001", # The use of `datetime.datetime()` without `tzinfo` argument is not allowed -- FIX ME "DTZ005", # The use of `datetime.datetime.now()` without `tzinfo` argument is not allowed -- FIX ME "E741", # Ambiguous variable name 'l' -- FIX ME "EM101", # Exception must not use a string literal, assign to variable first From ead54314f26615769ce8b055b25e25f9dbbb1f83 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 20:21:21 +0100 Subject: [PATCH 2694/2908] [pre-commit.ci] pre-commit autoupdate (#11328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.3 → v0.3.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.3...v0.3.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4b30f29a5b5..8b101207d5ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.3 + rev: v0.3.4 hooks: - id: ruff - id: ruff-format From b5cb1fba0debb5df7e5aea6bb069c6e3f130dba5 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 25 Mar 2024 23:54:11 +0300 Subject: [PATCH 2695/2908] Enable ruff DTZ005 rule (#11327) * Enable ruff DTZ005 rule * Fix other/gauss_easter.py * Fix * Fix web_programming/instagram_pic.py * Fix web_programming/instagram_video.py * Apply suggestions from code review * Update instagram_pic.py * datetime.now(tz=UTC).astimezone() * .astimezone() * Fix --------- Co-authored-by: Christian Clauss --- other/gauss_easter.py | 4 ++-- pyproject.toml | 1 - web_programming/instagram_pic.py | 4 ++-- web_programming/instagram_video.py | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/other/gauss_easter.py b/other/gauss_easter.py index 7ccea7f5bbf0..8c8c37c92796 100644 --- a/other/gauss_easter.py +++ b/other/gauss_easter.py @@ -55,6 +55,6 @@ def gauss_easter(year: int) -> datetime: if __name__ == "__main__": - for year in (1994, 2000, 2010, 2021, 2023): - tense = "will be" if year > datetime.now().year else "was" + for year in (1994, 2000, 2010, 2021, 2023, 2032, 2100): + tense = "will be" if year > datetime.now(tz=UTC).year else "was" print(f"Easter in {year} {tense} {gauss_easter(year)}") diff --git a/pyproject.toml b/pyproject.toml index 09093433a47a..5187491e5ee7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME - "DTZ005", # The use of `datetime.datetime.now()` without `tzinfo` argument is not allowed -- FIX ME "E741", # Ambiguous variable name 'l' -- FIX ME "EM101", # Exception must not use a string literal, assign to variable first "EXE001", # Shebang is present but file is not executable" -- FIX ME diff --git a/web_programming/instagram_pic.py b/web_programming/instagram_pic.py index 2630c8659232..2d987c1766dc 100644 --- a/web_programming/instagram_pic.py +++ b/web_programming/instagram_pic.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime import requests from bs4 import BeautifulSoup @@ -36,7 +36,7 @@ def download_image(url: str) -> str: if not image_data: return f"Failed to download the image from {image_url}." - file_name = f"{datetime.now():%Y-%m-%d_%H:%M:%S}.jpg" + file_name = f"{datetime.now(tz=UTC).astimezone():%Y-%m-%d_%H:%M:%S}.jpg" with open(file_name, "wb") as out_file: out_file.write(image_data) return f"Image downloaded and saved in the file {file_name}" diff --git a/web_programming/instagram_video.py b/web_programming/instagram_video.py index 243cece1a50e..1f1b0e297034 100644 --- a/web_programming/instagram_video.py +++ b/web_programming/instagram_video.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime import requests @@ -11,7 +11,7 @@ def download_video(url: str) -> bytes: if __name__ == "__main__": url = input("Enter Video/IGTV url: ").strip() - file_name = f"{datetime.now():%Y-%m-%d_%H:%M:%S}.mp4" + file_name = f"{datetime.now(tz=UTC).astimezone():%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 19fd435042a3191f6a5787a6eaf58e9c47920845 Mon Sep 17 00:00:00 2001 From: MrBubb1es <63935943+MrBubb1es@users.noreply.github.com> Date: Thu, 28 Mar 2024 12:19:51 -0500 Subject: [PATCH 2696/2908] Improved doctests for some functions (#11334) --- .../binary_tree/binary_tree_traversals.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 2b33cdca4fed..49c208335b2c 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -97,6 +97,8 @@ def level_order(root: Node | None) -> Generator[int, None, None]: """ Returns a list of nodes value from a whole binary tree in Level Order Traverse. Level Order traverse: Visit nodes of the tree level-by-level. + >>> list(level_order(make_tree())) + [1, 2, 3, 4, 5] """ if root is None: @@ -120,6 +122,10 @@ def get_nodes_from_left_to_right( """ Returns a list of nodes value from a particular level: Left to right direction of the binary tree. + >>> list(get_nodes_from_left_to_right(make_tree(), 1)) + [1] + >>> list(get_nodes_from_left_to_right(make_tree(), 2)) + [2, 3] """ def populate_output(root: Node | None, level: int) -> Generator[int, None, None]: @@ -140,10 +146,14 @@ def get_nodes_from_right_to_left( """ Returns a list of nodes value from a particular level: Right to left direction of the binary tree. + >>> list(get_nodes_from_right_to_left(make_tree(), 1)) + [1] + >>> list(get_nodes_from_right_to_left(make_tree(), 2)) + [3, 2] """ def populate_output(root: Node | None, level: int) -> Generator[int, None, None]: - if root is None: + if not root: return if level == 1: yield root.data @@ -158,6 +168,8 @@ def zigzag(root: Node | None) -> Generator[int, None, None]: """ ZigZag traverse: Returns a list of nodes value from left to right and right to left, alternatively. + >>> list(zigzag(make_tree())) + [1, 3, 2, 4, 5] """ if root is None: return From 516a3028d1f6b6e7e11ae4501fdaee50a0965464 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 28 Mar 2024 20:25:41 +0300 Subject: [PATCH 2697/2908] Enable ruff PLR5501 rule (#11332) * Enable ruff PLR5501 rule * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- backtracking/crossword_puzzle_solver.py | 5 +- cellular_automata/game_of_life.py | 5 +- ciphers/decrypt_caesar_with_chi_squared.py | 21 +++--- data_structures/binary_tree/avl_tree.py | 10 +-- .../binary_tree/binary_search_tree.py | 9 ++- .../binary_search_tree_recursive.py | 22 +++---- data_structures/binary_tree/red_black_tree.py | 66 +++++++++---------- data_structures/binary_tree/treap.py | 29 ++++---- data_structures/heap/max_heap.py | 7 +- .../stacks/infix_to_prefix_conversion.py | 13 ++-- data_structures/trie/radix_tree.py | 45 +++++++------ divide_and_conquer/convex_hull.py | 15 ++--- graphs/graph_list.py | 46 ++++++------- graphs/minimum_spanning_tree_prims.py | 7 +- graphs/multi_heuristic_astar.py | 33 +++++----- machine_learning/forecasting/run.py | 7 +- maths/largest_of_very_large_numbers.py | 9 ++- maths/pollard_rho.py | 13 ++-- matrix/cramers_rule_2x2.py | 15 ++--- project_euler/problem_019/sol1.py | 7 +- pyproject.toml | 1 - searches/hill_climbing.py | 7 +- searches/interpolation_search.py | 35 +++++----- strings/min_cost_string_conversion.py | 23 ++++--- 24 files changed, 211 insertions(+), 239 deletions(-) diff --git a/backtracking/crossword_puzzle_solver.py b/backtracking/crossword_puzzle_solver.py index b9c01c4efea9..e702c7e52153 100644 --- a/backtracking/crossword_puzzle_solver.py +++ b/backtracking/crossword_puzzle_solver.py @@ -28,9 +28,8 @@ def is_valid( if vertical: if row + i >= len(puzzle) or puzzle[row + i][col] != "": return False - else: - if col + i >= len(puzzle[0]) or puzzle[row][col + i] != "": - return False + elif col + i >= len(puzzle[0]) or puzzle[row][col + i] != "": + return False return True diff --git a/cellular_automata/game_of_life.py b/cellular_automata/game_of_life.py index 67e647d6475b..76276b272d65 100644 --- a/cellular_automata/game_of_life.py +++ b/cellular_automata/game_of_life.py @@ -101,9 +101,8 @@ def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool: state = True elif alive > 3: state = False - else: - if alive == 3: - state = True + elif alive == 3: + state = True return state diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 6c36860207cd..10832203e531 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -206,20 +206,19 @@ def decrypt_caesar_with_chi_squared( # 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) + elif 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 + # 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 + # 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 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] = ( diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 041ed7e36d16..9fca7237404c 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -215,11 +215,11 @@ def del_node(root: MyNode, data: Any) -> MyNode | None: return root else: root.set_left(del_node(left_child, data)) - else: # root.get_data() < data - if right_child is None: - return root - else: - root.set_right(del_node(right_child, data)) + # root.get_data() < data + elif right_child is None: + return root + else: + root.set_right(del_node(right_child, data)) if get_height(right_child) - get_height(left_child) == 2: assert right_child is not None diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 08a60a12065d..090e3e25fe6d 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -185,12 +185,11 @@ def __insert(self, value) -> None: break else: parent_node = parent_node.left + elif parent_node.right is None: + parent_node.right = new_node + break else: - if parent_node.right is None: - parent_node.right = new_node - break - else: - parent_node = parent_node.right + parent_node = parent_node.right new_node.parent = parent_node def insert(self, *values) -> Self: diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index 6af1b053f42c..d94ac5253360 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -74,14 +74,13 @@ def put(self, label: int) -> None: def _put(self, node: Node | None, label: int, parent: Node | None = None) -> Node: if node is None: node = Node(label, parent) + elif label < node.label: + node.left = self._put(node.left, label, node) + elif label > node.label: + node.right = self._put(node.right, label, node) 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: - msg = f"Node with label {label} already exists" - raise ValueError(msg) + msg = f"Node with label {label} already exists" + raise ValueError(msg) return node @@ -106,11 +105,10 @@ def _search(self, node: Node | None, label: int) -> Node: if node is None: msg = f"Node with label {label} does not exist" raise ValueError(msg) - else: - if label < node.label: - node = self._search(node.left, label) - elif label > node.label: - node = self._search(node.right, label) + elif label < node.label: + node = self._search(node.left, label) + elif label > node.label: + node = self._search(node.right, label) return node diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 3b5845cd957b..bdd808c828e0 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -107,12 +107,11 @@ def insert(self, label: int) -> RedBlackTree: else: self.left = RedBlackTree(label, 1, self) self.left._insert_repair() + elif self.right: + self.right.insert(label) else: - if self.right: - self.right.insert(label) - else: - self.right = RedBlackTree(label, 1, self) - self.right._insert_repair() + self.right = RedBlackTree(label, 1, self) + self.right._insert_repair() return self.parent or self def _insert_repair(self) -> None: @@ -178,36 +177,34 @@ def remove(self, label: int) -> RedBlackTree: # noqa: PLR0912 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 + # The node is black + elif child is None: + # This node and its child are black + if self.parent is None: + # The tree is now empty + return RedBlackTree(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 + 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 is not None and self.label > label: if self.left: self.left.remove(label) - else: - if self.right: - self.right.remove(label) + elif self.right: + self.right.remove(label) return self.parent or self def _remove_repair(self) -> None: @@ -369,11 +366,10 @@ def search(self, label: int) -> RedBlackTree | None: return None else: return self.right.search(label) + elif self.left is None: + return None else: - if self.left is None: - return None - else: - return self.left.search(label) + return self.left.search(label) def floor(self, label: int) -> int | None: """Returns the largest element in this tree which is at most label. diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index a53ac566ed54..e7ddf931b83a 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -43,22 +43,21 @@ def split(root: Node | None, value: int) -> tuple[Node | None, Node | None]: return None, None elif root.value is None: return None, None + elif 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: - 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 + """ + Just symmetric to previous case + """ + root.right, right = split(root.right, value) + return root, right def merge(left: Node | None, right: Node | None) -> Node | None: diff --git a/data_structures/heap/max_heap.py b/data_structures/heap/max_heap.py index fbc8eed09226..5a9f9cf88433 100644 --- a/data_structures/heap/max_heap.py +++ b/data_structures/heap/max_heap.py @@ -40,11 +40,10 @@ def __swap_down(self, i: int) -> None: while self.__size >= 2 * i: if 2 * i + 1 > self.__size: bigger_child = 2 * i + elif self.__heap[2 * i] > self.__heap[2 * i + 1]: + 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 + bigger_child = 2 * i + 1 temporary = self.__heap[i] if self.__heap[i] < self.__heap[bigger_child]: self.__heap[i] = self.__heap[bigger_child] diff --git a/data_structures/stacks/infix_to_prefix_conversion.py b/data_structures/stacks/infix_to_prefix_conversion.py index beff421c0cfa..878473b93c19 100644 --- a/data_structures/stacks/infix_to_prefix_conversion.py +++ b/data_structures/stacks/infix_to_prefix_conversion.py @@ -95,13 +95,12 @@ def infix_2_postfix(infix: str) -> str: while stack[-1] != "(": post_fix.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 - else: # while priority of x is not > priority of element in the stack - while stack and stack[-1] != "(" and priority[x] <= priority[stack[-1]]: - post_fix.append(stack.pop()) # pop stack & add to Postfix - stack.append(x) # push x to stack + elif len(stack) == 0: + stack.append(x) # If stack is empty, push x to stack + else: # while priority of x is not > priority of element in the stack + while stack and stack[-1] != "(" and priority[x] <= priority[stack[-1]]: + post_fix.append(stack.pop()) # pop stack & add to Postfix + stack.append(x) # push x to stack print( x.center(8), diff --git a/data_structures/trie/radix_tree.py b/data_structures/trie/radix_tree.py index fadc50cb49a7..caf566a6ce30 100644 --- a/data_structures/trie/radix_tree.py +++ b/data_structures/trie/radix_tree.py @@ -153,31 +153,30 @@ def delete(self, word: str) -> bool: # We have word remaining so we check the next node elif remaining_word != "": return incoming_node.delete(remaining_word) + # If it is not a leaf, we don't have to delete + elif not incoming_node.is_leaf: + return False else: - # If it is not a leaf, we don't have to delete - if not incoming_node.is_leaf: - return False + # We delete the nodes if no edges go from it + if len(incoming_node.nodes) == 0: + del self.nodes[word[0]] + # We merge the current node with its only child + if len(self.nodes) == 1 and not self.is_leaf: + merging_node = next(iter(self.nodes.values())) + self.is_leaf = merging_node.is_leaf + self.prefix += merging_node.prefix + self.nodes = merging_node.nodes + # If there is more than 1 edge, we just mark it as non-leaf + elif len(incoming_node.nodes) > 1: + incoming_node.is_leaf = False + # If there is 1 edge, we merge it with its child else: - # We delete the nodes if no edges go from it - if len(incoming_node.nodes) == 0: - del self.nodes[word[0]] - # We merge the current node with its only child - if len(self.nodes) == 1 and not self.is_leaf: - merging_node = next(iter(self.nodes.values())) - self.is_leaf = merging_node.is_leaf - self.prefix += merging_node.prefix - self.nodes = merging_node.nodes - # If there is more than 1 edge, we just mark it as non-leaf - elif len(incoming_node.nodes) > 1: - incoming_node.is_leaf = False - # If there is 1 edge, we merge it with its child - else: - merging_node = next(iter(incoming_node.nodes.values())) - incoming_node.is_leaf = merging_node.is_leaf - incoming_node.prefix += merging_node.prefix - incoming_node.nodes = merging_node.nodes - - return True + merging_node = next(iter(incoming_node.nodes.values())) + incoming_node.is_leaf = merging_node.is_leaf + incoming_node.prefix += merging_node.prefix + incoming_node.nodes = merging_node.nodes + + return True def print_tree(self, height: int = 0) -> None: """Print the tree diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index a5d8b713bdbc..93f6daf1f88c 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -274,14 +274,13 @@ def convex_hull_bf(points: list[Point]) -> list[Point]: 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 + # 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 + elif 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 diff --git a/graphs/graph_list.py b/graphs/graph_list.py index e871f3b8a9d6..6563cbb76132 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -120,29 +120,29 @@ def add_edge( else: self.adj_list[source_vertex] = [destination_vertex] self.adj_list[destination_vertex] = [source_vertex] - else: # For directed graphs - # if both source vertex and destination vertex are present in adjacency - # list, add destination vertex to source vertex list of adjacent vertices. - if source_vertex in self.adj_list and destination_vertex in self.adj_list: - self.adj_list[source_vertex].append(destination_vertex) - # if only source vertex is present in adjacency list, add destination - # vertex to source vertex list of adjacent vertices and create a new vertex - # with destination vertex as key, which has no adjacent vertex - elif source_vertex in self.adj_list: - self.adj_list[source_vertex].append(destination_vertex) - self.adj_list[destination_vertex] = [] - # if only destination vertex is present in adjacency list, create a new - # vertex with source vertex as key and assign a list containing destination - # vertex as first adjacent vertex - elif destination_vertex in self.adj_list: - self.adj_list[source_vertex] = [destination_vertex] - # if both source vertex and destination vertex are not present in adjacency - # list, create a new vertex with source vertex as key and a list containing - # destination vertex as it's first adjacent vertex. Then create a new vertex - # with destination vertex as key, which has no adjacent vertex - else: - self.adj_list[source_vertex] = [destination_vertex] - self.adj_list[destination_vertex] = [] + # For directed graphs + # if both source vertex and destination vertex are present in adjacency + # list, add destination vertex to source vertex list of adjacent vertices. + elif source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + # if only source vertex is present in adjacency list, add destination + # vertex to source vertex list of adjacent vertices and create a new vertex + # with destination vertex as key, which has no adjacent vertex + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [] + # if only destination vertex is present in adjacency list, create a new + # vertex with source vertex as key and assign a list containing destination + # vertex as first adjacent vertex + elif destination_vertex in self.adj_list: + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and a list containing + # destination vertex as it's first adjacent vertex. Then create a new vertex + # with destination vertex as key, which has no adjacent vertex + else: + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [] return self diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 5a08ec57ff4d..90c9f4c91e86 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -18,11 +18,10 @@ def top_to_bottom(self, heap, start, size, positions): else: if 2 * start + 2 >= size: smallest_child = 2 * start + 1 + elif heap[2 * start + 1] < heap[2 * start + 2]: + smallest_child = 2 * start + 1 else: - if heap[2 * start + 1] < heap[2 * start + 2]: - smallest_child = 2 * start + 1 - else: - smallest_child = 2 * start + 2 + smallest_child = 2 * start + 2 if heap[smallest_child] < heap[start]: temp, temp1 = heap[smallest_child], positions[smallest_child] heap[smallest_child], positions[smallest_child] = ( diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index 0a18ede6ed41..6af9a187a4e9 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -270,24 +270,23 @@ def multi_a_star(start: TPos, goal: TPos, n_heuristic: int): back_pointer, ) close_list_inad.append(get_s) + elif g_function[goal] <= open_list[0].minkey(): + if g_function[goal] < float("inf"): + do_something(back_pointer, goal, start) else: - if g_function[goal] <= open_list[0].minkey(): - if g_function[goal] < float("inf"): - do_something(back_pointer, goal, start) - else: - 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) + 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): diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py index 64e719daacc2..dbb86caf8568 100644 --- a/machine_learning/forecasting/run.py +++ b/machine_learning/forecasting/run.py @@ -113,11 +113,10 @@ def data_safety_checker(list_vote: list, actual_result: float) -> bool: for i in list_vote: if i > actual_result: safe = not_safe + 1 + elif abs(abs(i) - abs(actual_result)) <= 0.1: + safe += 1 else: - if abs(abs(i) - abs(actual_result)) <= 0.1: - safe += 1 - else: - not_safe += 1 + not_safe += 1 return safe > not_safe diff --git a/maths/largest_of_very_large_numbers.py b/maths/largest_of_very_large_numbers.py index eb5c121fd262..edee50371e02 100644 --- a/maths/largest_of_very_large_numbers.py +++ b/maths/largest_of_very_large_numbers.py @@ -20,11 +20,10 @@ 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 + elif x == 0: # 0 raised to any number is 0 + return 0 + elif y == 0: + return 1 # any number raised to 0 is 1 raise AssertionError("This should never happen") diff --git a/maths/pollard_rho.py b/maths/pollard_rho.py index 5082f54f71a8..e8bc89cef6c5 100644 --- a/maths/pollard_rho.py +++ b/maths/pollard_rho.py @@ -94,14 +94,13 @@ def rand_fn(value: int, step: int, modulus: int) -> int: if divisor == 1: # No common divisor yet, just keep searching. continue + # We found a common divisor! + elif divisor == num: + # Unfortunately, the divisor is ``num`` itself and is useless. + break else: - # We found a common divisor! - if divisor == num: - # Unfortunately, the divisor is ``num`` itself and is useless. - break - else: - # The divisor is a nontrivial factor of ``num``! - return divisor + # The divisor is a nontrivial factor of ``num``! + return divisor # If we made it here, then this attempt failed. # We need to pick a new starting seed for the tortoise and hare diff --git a/matrix/cramers_rule_2x2.py b/matrix/cramers_rule_2x2.py index 4f52dbe646ad..081035bec002 100644 --- a/matrix/cramers_rule_2x2.py +++ b/matrix/cramers_rule_2x2.py @@ -73,12 +73,11 @@ def cramers_rule_2x2(equation1: list[int], equation2: list[int]) -> tuple[float, raise ValueError("Infinite solutions. (Consistent system)") else: raise ValueError("No solution. (Inconsistent system)") + elif determinant_x == determinant_y == 0: + # Trivial solution (Inconsistent system) + return (0.0, 0.0) else: - if determinant_x == determinant_y == 0: - # Trivial solution (Inconsistent system) - return (0.0, 0.0) - else: - x = determinant_x / determinant - y = determinant_y / determinant - # Non-Trivial Solution (Consistent system) - return (x, y) + x = determinant_x / determinant + y = determinant_y / determinant + # Non-Trivial Solution (Consistent system) + return (x, y) diff --git a/project_euler/problem_019/sol1.py b/project_euler/problem_019/sol1.py index 0e38137d4f01..656f104c390d 100644 --- a/project_euler/problem_019/sol1.py +++ b/project_euler/problem_019/sol1.py @@ -46,10 +46,9 @@ def solution(): 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] + elif day > days_per_month[month - 1]: + month += 1 + day = day - days_per_month[month - 2] if month > 12: year += 1 diff --git a/pyproject.toml b/pyproject.toml index 5187491e5ee7..290a6b7599be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "NPY002", # Replace legacy `np.random.choice` call with `np.random.Generator` -- FIX ME "PGH003", # Use specific rule codes when ignoring type issues -- FIX ME "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey - "PLR5501", # Consider using `elif` instead of `else` -- FIX ME "PLW0120", # `else` clause on loop without a `break` statement -- FIX ME "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX "PLW2901", # PLW2901: Redefined loop variable -- FIX ME diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index 83a3b8b74e27..689b7e5cca8f 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -137,11 +137,10 @@ def hill_climbing( if change > max_change and change > 0: max_change = change next_state = neighbor - else: # finding min + elif change < min_change and change < 0: # finding min # to direction with greatest descent - if change < min_change and change < 0: - min_change = change - next_state = neighbor + 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 diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index 49194c2600a0..0591788aa40b 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -33,18 +33,16 @@ def interpolation_search(sorted_collection, item): current_item = sorted_collection[point] if current_item == item: return point + elif point < left: + right = left + left = point + elif point > right: + left = right + right = point + elif item < current_item: + right = point - 1 else: - if point < left: - right = left - left = point - elif point > right: - left = right - right = point - else: - if item < current_item: - right = point - 1 - else: - left = point + 1 + left = point + 1 return None @@ -79,15 +77,14 @@ def interpolation_search_by_recursion(sorted_collection, item, left, right): return interpolation_search_by_recursion(sorted_collection, item, point, left) elif point > right: return interpolation_search_by_recursion(sorted_collection, item, right, left) + elif sorted_collection[point] > item: + return interpolation_search_by_recursion( + sorted_collection, item, left, point - 1 + ) else: - 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 - ) + return interpolation_search_by_recursion( + sorted_collection, item, point + 1, right + ) def __assert_sorted(collection): diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index 0fad0b88c370..d147a9d7954c 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -60,19 +60,18 @@ def compute_transform_tables( def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: if i == 0 and j == 0: return [] + elif ops[i][j][0] in {"C", "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: - if ops[i][j][0] in {"C", "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 + seq = assemble_transformation(ops, i, j - 1) + seq.append(ops[i][j]) + return seq if __name__ == "__main__": From da47d5c88ccf18e27c5b8f10830376031ad1792a Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 28 Mar 2024 20:26:41 +0300 Subject: [PATCH 2698/2908] Enable ruff N999 rule (#11331) * Enable ruff N999 rule * updating DIRECTORY.md --------- Co-authored-by: MaximSmolskiy --- DIRECTORY.md | 6 +++--- ...(nlogn).py => longest_increasing_subsequence_o_nlogn.py} | 0 ...)_graph.py => directed_and_undirected_weighted_graph.py} | 0 ...eural_network.py => two_hidden_layers_neural_network.py} | 0 pyproject.toml | 1 - 5 files changed, 3 insertions(+), 4 deletions(-) rename dynamic_programming/{longest_increasing_subsequence_o(nlogn).py => longest_increasing_subsequence_o_nlogn.py} (100%) rename graphs/{directed_and_undirected_(weighted)_graph.py => directed_and_undirected_weighted_graph.py} (100%) rename neural_network/{2_hidden_layers_neural_network.py => two_hidden_layers_neural_network.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 01667c9feee8..f6d6cb463faa 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -351,7 +351,7 @@ * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) * [Longest Common Substring](dynamic_programming/longest_common_substring.py) * [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py) - * [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py) + * [Longest Increasing Subsequence O Nlogn](dynamic_programming/longest_increasing_subsequence_o_nlogn.py) * [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py) * [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py) * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) @@ -465,7 +465,7 @@ * [Dijkstra Alternate](graphs/dijkstra_alternate.py) * [Dijkstra Binary Grid](graphs/dijkstra_binary_grid.py) * [Dinic](graphs/dinic.py) - * [Directed And Undirected (Weighted) Graph](graphs/directed_and_undirected_(weighted)_graph.py) + * [Directed And Undirected Weighted Graph](graphs/directed_and_undirected_weighted_graph.py) * [Edmonds Karp Multiple Source And Sink](graphs/edmonds_karp_multiple_source_and_sink.py) * [Eulerian Path And Circuit For Undirected Graph](graphs/eulerian_path_and_circuit_for_undirected_graph.py) * [Even Tree](graphs/even_tree.py) @@ -792,7 +792,6 @@ * [Minimum Cut](networking_flow/minimum_cut.py) ## Neural Network - * [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py) * Activation Functions * [Binary Step](neural_network/activation_functions/binary_step.py) * [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py) @@ -809,6 +808,7 @@ * [Convolution Neural Network](neural_network/convolution_neural_network.py) * [Input Data](neural_network/input_data.py) * [Simple Neural Network](neural_network/simple_neural_network.py) + * [Two Hidden Layers Neural Network](neural_network/two_hidden_layers_neural_network.py) ## Other * [Activity Selection](other/activity_selection.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/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/neural_network/2_hidden_layers_neural_network.py b/neural_network/two_hidden_layers_neural_network.py similarity index 100% rename from neural_network/2_hidden_layers_neural_network.py rename to neural_network/two_hidden_layers_neural_network.py diff --git a/pyproject.toml b/pyproject.toml index 290a6b7599be..5b2eb07b4555 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "G004", # Logging statement uses f-string "ICN001", # `matplotlib.pyplot` should be imported as `plt` -- FIX ME "INP001", # File `x/y/z.py` is part of an implicit namespace package. Add an `__init__.py`. -- FIX ME - "N999", # Invalid module name -- FIX ME "NPY002", # Replace legacy `np.random.choice` call with `np.random.Generator` -- FIX ME "PGH003", # Use specific rule codes when ignoring type issues -- FIX ME "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey From efb7463cde48305cfebb8a547273c93edbdaaee5 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 28 Mar 2024 20:28:54 +0300 Subject: [PATCH 2699/2908] Enable ruff PLW0120 rule (#11330) Co-authored-by: Christian Clauss --- pyproject.toml | 1 - searches/fibonacci_search.py | 3 +-- searches/ternary_search.py | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5b2eb07b4555..b9f3115df92a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "NPY002", # Replace legacy `np.random.choice` call with `np.random.Generator` -- FIX ME "PGH003", # Use specific rule codes when ignoring type issues -- FIX ME "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey - "PLW0120", # `else` clause on loop without a `break` statement -- FIX ME "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX "PLW2901", # PLW2901: Redefined loop variable -- FIX ME "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception diff --git a/searches/fibonacci_search.py b/searches/fibonacci_search.py index ec3dfa7f30f6..7b2252a68be2 100644 --- a/searches/fibonacci_search.py +++ b/searches/fibonacci_search.py @@ -123,8 +123,7 @@ def fibonacci_search(arr: list, val: int) -> int: elif val > item_k_1: offset += fibonacci(fibb_k - 1) fibb_k -= 2 - else: - return -1 + return -1 if __name__ == "__main__": diff --git a/searches/ternary_search.py b/searches/ternary_search.py index 8dcd6b5bde2e..73e4b1ddc68b 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -106,8 +106,7 @@ def ite_ternary_search(array: list[int], target: int) -> int: else: left = one_third + 1 right = two_third - 1 - else: - return -1 + return -1 def rec_ternary_search(left: int, right: int, array: list[int], target: int) -> int: From f2246ce7fd539d94fd9299bd2fe42469dafab03f Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 28 Mar 2024 21:03:23 +0300 Subject: [PATCH 2700/2908] Enable ruff ICN001 rule (#11329) * Enable ruff ICN001 rule * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ciphers/hill_cipher.py | 38 ++++----- fractals/julia_sets.py | 54 ++++++------ fractals/koch_snowflake.py | 34 ++++---- graphics/bezier_curve.py | 2 +- machine_learning/gradient_descent.py | 4 +- neural_network/input_data.py | 32 +++---- .../two_hidden_layers_neural_network.py | 84 +++++++++---------- pyproject.toml | 1 - 8 files changed, 121 insertions(+), 128 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index ea337a72dc04..33b2529f017b 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -38,7 +38,7 @@ import string -import numpy +import numpy as np from maths.greatest_common_divisor import greatest_common_divisor @@ -49,11 +49,11 @@ class HillCipher: # i.e. a total of 36 characters # take x and return x % len(key_string) - modulus = numpy.vectorize(lambda x: x % 36) + modulus = np.vectorize(lambda x: x % 36) - to_int = numpy.vectorize(round) + to_int = np.vectorize(round) - def __init__(self, encrypt_key: numpy.ndarray) -> None: + def __init__(self, encrypt_key: np.ndarray) -> None: """ encrypt_key is an NxN numpy array """ @@ -63,7 +63,7 @@ def __init__(self, encrypt_key: numpy.ndarray) -> None: def replace_letters(self, letter: str) -> int: """ - >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) >>> hill_cipher.replace_letters('T') 19 >>> hill_cipher.replace_letters('0') @@ -73,7 +73,7 @@ def replace_letters(self, letter: str) -> int: def replace_digits(self, num: int) -> str: """ - >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) >>> hill_cipher.replace_digits(19) 'T' >>> hill_cipher.replace_digits(26) @@ -83,10 +83,10 @@ def replace_digits(self, num: int) -> str: def check_determinant(self) -> None: """ - >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) >>> hill_cipher.check_determinant() """ - det = round(numpy.linalg.det(self.encrypt_key)) + det = round(np.linalg.det(self.encrypt_key)) if det < 0: det = det % len(self.key_string) @@ -101,7 +101,7 @@ def check_determinant(self) -> None: def process_text(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) >>> hill_cipher.process_text('Testing Hill Cipher') 'TESTINGHILLCIPHERR' >>> hill_cipher.process_text('hello') @@ -117,7 +117,7 @@ def process_text(self, text: str) -> str: def encrypt(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) >>> hill_cipher.encrypt('testing hill cipher') 'WHXYJOLM9C6XT085LL' >>> hill_cipher.encrypt('hello') @@ -129,7 +129,7 @@ def encrypt(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] vec = [self.replace_letters(char) for char in batch] - batch_vec = numpy.array([vec]).T + batch_vec = np.array([vec]).T batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[ 0 ] @@ -140,14 +140,14 @@ def encrypt(self, text: str) -> str: return encrypted - def make_decrypt_key(self) -> numpy.ndarray: + def make_decrypt_key(self) -> np.ndarray: """ - >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) >>> hill_cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) """ - det = round(numpy.linalg.det(self.encrypt_key)) + det = round(np.linalg.det(self.encrypt_key)) if det < 0: det = det % len(self.key_string) @@ -158,16 +158,14 @@ def make_decrypt_key(self) -> numpy.ndarray: break inv_key = ( - det_inv - * numpy.linalg.det(self.encrypt_key) - * numpy.linalg.inv(self.encrypt_key) + det_inv * np.linalg.det(self.encrypt_key) * np.linalg.inv(self.encrypt_key) ) return self.to_int(self.modulus(inv_key)) def decrypt(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) >>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL') 'TESTINGHILLCIPHERR' >>> hill_cipher.decrypt('85FF00') @@ -180,7 +178,7 @@ 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] vec = [self.replace_letters(char) for char in batch] - batch_vec = numpy.array([vec]).T + batch_vec = np.array([vec]).T batch_decrypted = self.modulus(decrypt_key.dot(batch_vec)).T.tolist()[0] decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -199,7 +197,7 @@ def main() -> None: row = [int(x) for x in input().split()] hill_matrix.append(row) - hc = HillCipher(numpy.array(hill_matrix)) + hc = HillCipher(np.array(hill_matrix)) print("Would you like to encrypt or decrypt some text? (1 or 2)") option = input("\n1. Encrypt\n2. Decrypt\n") diff --git a/fractals/julia_sets.py b/fractals/julia_sets.py index 482e1eddfecc..1eef4573ba19 100644 --- a/fractals/julia_sets.py +++ b/fractals/julia_sets.py @@ -25,8 +25,8 @@ from collections.abc import Callable from typing import Any -import numpy -from matplotlib import pyplot +import matplotlib.pyplot as plt +import numpy as np c_cauliflower = 0.25 + 0.0j c_polynomial_1 = -0.4 + 0.6j @@ -37,22 +37,20 @@ nb_pixels = 666 -def eval_exponential(c_parameter: complex, z_values: numpy.ndarray) -> numpy.ndarray: +def eval_exponential(c_parameter: complex, z_values: np.ndarray) -> np.ndarray: """ Evaluate $e^z + c$. >>> eval_exponential(0, 0) 1.0 - >>> abs(eval_exponential(1, numpy.pi*1.j)) < 1e-15 + >>> abs(eval_exponential(1, np.pi*1.j)) < 1e-15 True >>> abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15 True """ - return numpy.exp(z_values) + c_parameter + return np.exp(z_values) + c_parameter -def eval_quadratic_polynomial( - c_parameter: complex, z_values: numpy.ndarray -) -> numpy.ndarray: +def eval_quadratic_polynomial(c_parameter: complex, z_values: np.ndarray) -> np.ndarray: """ >>> eval_quadratic_polynomial(0, 2) 4 @@ -66,7 +64,7 @@ def eval_quadratic_polynomial( return z_values * z_values + c_parameter -def prepare_grid(window_size: float, nb_pixels: int) -> numpy.ndarray: +def prepare_grid(window_size: float, nb_pixels: int) -> np.ndarray: """ Create a grid of complex values of size nb_pixels*nb_pixels with real and imaginary parts ranging from -window_size to window_size (inclusive). @@ -77,20 +75,20 @@ def prepare_grid(window_size: float, nb_pixels: int) -> numpy.ndarray: [ 0.-1.j, 0.+0.j, 0.+1.j], [ 1.-1.j, 1.+0.j, 1.+1.j]]) """ - x = numpy.linspace(-window_size, window_size, nb_pixels) + x = np.linspace(-window_size, window_size, nb_pixels) x = x.reshape((nb_pixels, 1)) - y = numpy.linspace(-window_size, window_size, nb_pixels) + y = np.linspace(-window_size, window_size, nb_pixels) y = y.reshape((1, nb_pixels)) return x + 1.0j * y def iterate_function( - eval_function: Callable[[Any, numpy.ndarray], numpy.ndarray], + eval_function: Callable[[Any, np.ndarray], np.ndarray], function_params: Any, nb_iterations: int, - z_0: numpy.ndarray, + z_0: np.ndarray, infinity: float | None = None, -) -> numpy.ndarray: +) -> np.ndarray: """ Iterate the function "eval_function" exactly nb_iterations times. The first argument of the function is a parameter which is contained in @@ -98,22 +96,22 @@ def iterate_function( values to iterate from. This function returns the final iterates. - >>> iterate_function(eval_quadratic_polynomial, 0, 3, numpy.array([0,1,2])).shape + >>> iterate_function(eval_quadratic_polynomial, 0, 3, np.array([0,1,2])).shape (3,) - >>> numpy.round(iterate_function(eval_quadratic_polynomial, + >>> np.round(iterate_function(eval_quadratic_polynomial, ... 0, ... 3, - ... numpy.array([0,1,2]))[0]) + ... np.array([0,1,2]))[0]) 0j - >>> numpy.round(iterate_function(eval_quadratic_polynomial, + >>> np.round(iterate_function(eval_quadratic_polynomial, ... 0, ... 3, - ... numpy.array([0,1,2]))[1]) + ... np.array([0,1,2]))[1]) (1+0j) - >>> numpy.round(iterate_function(eval_quadratic_polynomial, + >>> np.round(iterate_function(eval_quadratic_polynomial, ... 0, ... 3, - ... numpy.array([0,1,2]))[2]) + ... np.array([0,1,2]))[2]) (256+0j) """ @@ -121,8 +119,8 @@ def iterate_function( for _ in range(nb_iterations): z_n = eval_function(function_params, z_n) if infinity is not None: - numpy.nan_to_num(z_n, copy=False, nan=infinity) - z_n[abs(z_n) == numpy.inf] = infinity + np.nan_to_num(z_n, copy=False, nan=infinity) + z_n[abs(z_n) == np.inf] = infinity return z_n @@ -130,21 +128,21 @@ def show_results( function_label: str, function_params: Any, escape_radius: float, - z_final: numpy.ndarray, + z_final: np.ndarray, ) -> None: """ Plots of whether the absolute value of z_final is greater than the value of escape_radius. Adds the function_label and function_params to the title. - >>> show_results('80', 0, 1, numpy.array([[0,1,.5],[.4,2,1.1],[.2,1,1.3]])) + >>> show_results('80', 0, 1, np.array([[0,1,.5],[.4,2,1.1],[.2,1,1.3]])) """ abs_z_final = (abs(z_final)).transpose() abs_z_final[:, :] = abs_z_final[::-1, :] - pyplot.matshow(abs_z_final < escape_radius) - pyplot.title(f"Julia set of ${function_label}$, $c={function_params}$") - pyplot.show() + plt.matshow(abs_z_final < escape_radius) + plt.title(f"Julia set of ${function_label}$, $c={function_params}$") + plt.show() def ignore_overflow_warnings() -> None: diff --git a/fractals/koch_snowflake.py b/fractals/koch_snowflake.py index 30cd4b39c7c1..724b78f41a69 100644 --- a/fractals/koch_snowflake.py +++ b/fractals/koch_snowflake.py @@ -22,25 +22,25 @@ from __future__ import annotations -import matplotlib.pyplot as plt # type: ignore -import numpy +import matplotlib.pyplot as plt +import numpy as np # initial triangle of Koch snowflake -VECTOR_1 = numpy.array([0, 0]) -VECTOR_2 = numpy.array([0.5, 0.8660254]) -VECTOR_3 = numpy.array([1, 0]) +VECTOR_1 = np.array([0, 0]) +VECTOR_2 = np.array([0.5, 0.8660254]) +VECTOR_3 = np.array([1, 0]) INITIAL_VECTORS = [VECTOR_1, VECTOR_2, VECTOR_3, VECTOR_1] # uncomment for simple Koch curve instead of Koch snowflake # INITIAL_VECTORS = [VECTOR_1, VECTOR_3] -def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndarray]: +def iterate(initial_vectors: list[np.ndarray], steps: int) -> list[np.ndarray]: """ Go through the number of iterations determined by the argument "steps". Be careful with high values (above 5) since the time to calculate increases exponentially. - >>> iterate([numpy.array([0, 0]), numpy.array([1, 0])], 1) + >>> iterate([np.array([0, 0]), np.array([1, 0])], 1) [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \ 0.28867513]), array([0.66666667, 0. ]), array([1, 0])] """ @@ -50,13 +50,13 @@ def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndar return vectors -def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]: +def iteration_step(vectors: list[np.ndarray]) -> list[np.ndarray]: """ Loops through each pair of adjacent vectors. Each line between two adjacent vectors is divided into 4 segments by adding 3 additional vectors in-between the original two vectors. The vector in the middle is constructed through a 60 degree rotation so it is bent outwards. - >>> iteration_step([numpy.array([0, 0]), numpy.array([1, 0])]) + >>> iteration_step([np.array([0, 0]), np.array([1, 0])]) [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \ 0.28867513]), array([0.66666667, 0. ]), array([1, 0])] """ @@ -74,22 +74,22 @@ def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]: return new_vectors -def rotate(vector: numpy.ndarray, angle_in_degrees: float) -> numpy.ndarray: +def rotate(vector: np.ndarray, angle_in_degrees: float) -> np.ndarray: """ Standard rotation of a 2D vector with a rotation matrix (see https://en.wikipedia.org/wiki/Rotation_matrix ) - >>> rotate(numpy.array([1, 0]), 60) + >>> rotate(np.array([1, 0]), 60) array([0.5 , 0.8660254]) - >>> rotate(numpy.array([1, 0]), 90) + >>> rotate(np.array([1, 0]), 90) array([6.123234e-17, 1.000000e+00]) """ - theta = numpy.radians(angle_in_degrees) - c, s = numpy.cos(theta), numpy.sin(theta) - rotation_matrix = numpy.array(((c, -s), (s, c))) - return numpy.dot(rotation_matrix, vector) + theta = np.radians(angle_in_degrees) + c, s = np.cos(theta), np.sin(theta) + rotation_matrix = np.array(((c, -s), (s, c))) + return np.dot(rotation_matrix, vector) -def plot(vectors: list[numpy.ndarray]) -> None: +def plot(vectors: list[np.ndarray]) -> None: """ Utility function to plot the vectors using matplotlib.pyplot No doctest was implemented since this function does not have a return value diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 7c22329ad8b4..6eeb89da6bdf 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -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. """ - from matplotlib import pyplot as plt # type: ignore + 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/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index db38b3c95b52..95463faf5635 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -3,7 +3,7 @@ function. """ -import numpy +import numpy as np # List of input, output pairs train_data = ( @@ -116,7 +116,7 @@ def run_gradient_descent(): temp_parameter_vector[i] = ( parameter_vector[i] - LEARNING_RATE * cost_derivative ) - if numpy.allclose( + if np.allclose( parameter_vector, temp_parameter_vector, atol=absolute_error_limit, diff --git a/neural_network/input_data.py b/neural_network/input_data.py index f7ae86b48e65..9d4195487dbb 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -22,7 +22,7 @@ import typing import urllib -import numpy +import numpy as np from tensorflow.python.framework import dtypes, random_seed from tensorflow.python.platform import gfile from tensorflow.python.util.deprecation import deprecated @@ -39,8 +39,8 @@ class _Datasets(typing.NamedTuple): def _read32(bytestream): - dt = numpy.dtype(numpy.uint32).newbyteorder(">") - return numpy.frombuffer(bytestream.read(4), dtype=dt)[0] + dt = np.dtype(np.uint32).newbyteorder(">") + return np.frombuffer(bytestream.read(4), dtype=dt)[0] @deprecated(None, "Please use tf.data to implement this functionality.") @@ -68,7 +68,7 @@ def _extract_images(f): rows = _read32(bytestream) cols = _read32(bytestream) buf = bytestream.read(rows * cols * num_images) - data = numpy.frombuffer(buf, dtype=numpy.uint8) + data = np.frombuffer(buf, dtype=np.uint8) data = data.reshape(num_images, rows, cols, 1) return data @@ -77,8 +77,8 @@ def _extract_images(f): 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)) + index_offset = np.arange(num_labels) * num_classes + labels_one_hot = np.zeros((num_labels, num_classes)) labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1 return labels_one_hot @@ -107,7 +107,7 @@ def _extract_labels(f, one_hot=False, num_classes=10): ) num_items = _read32(bytestream) buf = bytestream.read(num_items) - labels = numpy.frombuffer(buf, dtype=numpy.uint8) + labels = np.frombuffer(buf, dtype=np.uint8) if one_hot: return _dense_to_one_hot(labels, num_classes) return labels @@ -153,7 +153,7 @@ def __init__( """ 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) + np.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) @@ -175,8 +175,8 @@ def __init__( ) 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) + images = images.astype(np.float32) + images = np.multiply(images, 1.0 / 255.0) self._images = images self._labels = labels self._epochs_completed = 0 @@ -210,8 +210,8 @@ def next_batch(self, batch_size, fake_data=False, shuffle=True): 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) + perm0 = np.arange(self._num_examples) + np.random.shuffle(perm0) self._images = self.images[perm0] self._labels = self.labels[perm0] # Go to the next epoch @@ -224,8 +224,8 @@ def next_batch(self, batch_size, fake_data=False, shuffle=True): labels_rest_part = self._labels[start : self._num_examples] # Shuffle the data if shuffle: - perm = numpy.arange(self._num_examples) - numpy.random.shuffle(perm) + perm = np.arange(self._num_examples) + np.random.shuffle(perm) self._images = self.images[perm] self._labels = self.labels[perm] # Start next epoch @@ -235,8 +235,8 @@ def next_batch(self, batch_size, fake_data=False, shuffle=True): 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), + np.concatenate((images_rest_part, images_new_part), axis=0), + np.concatenate((labels_rest_part, labels_new_part), axis=0), ) else: self._index_in_epoch += batch_size diff --git a/neural_network/two_hidden_layers_neural_network.py b/neural_network/two_hidden_layers_neural_network.py index 7b374a93d039..dea7e2342d9f 100644 --- a/neural_network/two_hidden_layers_neural_network.py +++ b/neural_network/two_hidden_layers_neural_network.py @@ -5,11 +5,11 @@ - https://en.wikipedia.org/wiki/Feedforward_neural_network (Feedforward) """ -import numpy +import numpy as np class TwoHiddenLayerNeuralNetwork: - def __init__(self, input_array: numpy.ndarray, output_array: numpy.ndarray) -> None: + def __init__(self, input_array: np.ndarray, output_array: np.ndarray) -> None: """ This function initializes the TwoHiddenLayerNeuralNetwork class with random weights for every layer and initializes predicted output with zeroes. @@ -28,30 +28,28 @@ def __init__(self, input_array: numpy.ndarray, output_array: numpy.ndarray) -> N # 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_layer_and_first_hidden_layer_weights = np.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 - ) + self.first_hidden_layer_and_second_hidden_layer_weights = np.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) + self.second_hidden_layer_and_output_layer_weights = np.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) + self.predicted_output = np.zeros(output_array.shape) - def feedforward(self) -> numpy.ndarray: + def feedforward(self) -> np.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. @@ -60,24 +58,24 @@ def feedforward(self) -> numpy.ndarray: 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) + >>> input_val = np.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float) + >>> output_val = np.array(([0], [0], [0]), dtype=float) >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) >>> res = nn.feedforward() - >>> array_sum = numpy.sum(res) - >>> numpy.isnan(array_sum) + >>> array_sum = np.sum(res) + >>> np.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) + np.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( + np.dot( self.layer_between_input_and_first_hidden_layer, self.first_hidden_layer_and_second_hidden_layer_weights, ) @@ -86,7 +84,7 @@ def feedforward(self) -> numpy.ndarray: # 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( + np.dot( self.layer_between_first_hidden_layer_and_second_hidden_layer, self.second_hidden_layer_and_output_layer_weights, ) @@ -100,8 +98,8 @@ def back_propagation(self) -> None: 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) + >>> input_val = np.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float) + >>> output_val = np.array(([0], [0], [0]), dtype=float) >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) >>> res = nn.feedforward() >>> nn.back_propagation() @@ -110,15 +108,15 @@ def back_propagation(self) -> None: False """ - updated_second_hidden_layer_and_output_layer_weights = numpy.dot( + updated_second_hidden_layer_and_output_layer_weights = np.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( + updated_first_hidden_layer_and_second_hidden_layer_weights = np.dot( self.layer_between_input_and_first_hidden_layer.T, - numpy.dot( + np.dot( 2 * (self.output_array - self.predicted_output) * sigmoid_derivative(self.predicted_output), @@ -128,10 +126,10 @@ def back_propagation(self) -> None: self.layer_between_first_hidden_layer_and_second_hidden_layer ), ) - updated_input_layer_and_first_hidden_layer_weights = numpy.dot( + updated_input_layer_and_first_hidden_layer_weights = np.dot( self.input_array.T, - numpy.dot( - numpy.dot( + np.dot( + np.dot( 2 * (self.output_array - self.predicted_output) * sigmoid_derivative(self.predicted_output), @@ -155,7 +153,7 @@ def back_propagation(self) -> None: updated_second_hidden_layer_and_output_layer_weights ) - def train(self, output: numpy.ndarray, iterations: int, give_loss: bool) -> None: + def train(self, output: np.ndarray, iterations: int, give_loss: bool) -> None: """ Performs the feedforwarding and back propagation process for the given number of iterations. @@ -166,8 +164,8 @@ def train(self, output: numpy.ndarray, iterations: int, give_loss: bool) -> None 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) + >>> input_val = np.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float) + >>> output_val = np.array(([0], [1], [1]), dtype=float) >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) >>> first_iteration_weights = nn.feedforward() >>> nn.back_propagation() @@ -179,10 +177,10 @@ def train(self, output: numpy.ndarray, iterations: int, give_loss: bool) -> None self.output = self.feedforward() self.back_propagation() if give_loss: - loss = numpy.mean(numpy.square(output - self.feedforward())) + loss = np.mean(np.square(output - self.feedforward())) print(f"Iteration {iteration} Loss: {loss}") - def predict(self, input_arr: numpy.ndarray) -> int: + def predict(self, input_arr: np.ndarray) -> int: """ Predict's the output for the given input values using the trained neural network. @@ -192,8 +190,8 @@ def predict(self, input_arr: numpy.ndarray) -> int: 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) + >>> input_val = np.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float) + >>> output_val = np.array(([0], [1], [1]), dtype=float) >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) >>> nn.train(output_val, 1000, False) >>> nn.predict([0, 1, 0]) in (0, 1) @@ -204,18 +202,18 @@ def predict(self, input_arr: numpy.ndarray) -> int: self.array = input_arr self.layer_between_input_and_first_hidden_layer = sigmoid( - numpy.dot(self.array, self.input_layer_and_first_hidden_layer_weights) + np.dot(self.array, self.input_layer_and_first_hidden_layer_weights) ) self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid( - numpy.dot( + np.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( + np.dot( self.layer_between_first_hidden_layer_and_second_hidden_layer, self.second_hidden_layer_and_output_layer_weights, ) @@ -224,26 +222,26 @@ def predict(self, input_arr: numpy.ndarray) -> int: return int((self.layer_between_second_hidden_layer_and_output > 0.6)[0]) -def sigmoid(value: numpy.ndarray) -> numpy.ndarray: +def sigmoid(value: np.ndarray) -> np.ndarray: """ Applies sigmoid activation function. return normalized values - >>> sigmoid(numpy.array(([1, 0, 2], [1, 0, 0]), dtype=numpy.float64)) + >>> sigmoid(np.array(([1, 0, 2], [1, 0, 0]), dtype=np.float64)) array([[0.73105858, 0.5 , 0.88079708], [0.73105858, 0.5 , 0.5 ]]) """ - return 1 / (1 + numpy.exp(-value)) + return 1 / (1 + np.exp(-value)) -def sigmoid_derivative(value: numpy.ndarray) -> numpy.ndarray: +def sigmoid_derivative(value: np.ndarray) -> np.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)) + >>> sigmoid_derivative(np.array(([1, 0, 2], [1, 0, 0]), dtype=np.float64)) array([[ 0., 0., -2.], [ 0., 0., 0.]]) """ @@ -264,7 +262,7 @@ def example() -> int: True """ # Input values. - test_input = numpy.array( + test_input = np.array( ( [0, 0, 0], [0, 0, 1], @@ -275,11 +273,11 @@ def example() -> int: [1, 1, 0], [1, 1, 1], ), - dtype=numpy.float64, + dtype=np.float64, ) # True output values for the given input values. - output = numpy.array(([0], [1], [1], [0], [1], [0], [0], [1]), dtype=numpy.float64) + output = np.array(([0], [1], [1], [0], [1], [0], [0], [1]), dtype=np.float64) # Calling neural network class. neural_network = TwoHiddenLayerNeuralNetwork( @@ -290,7 +288,7 @@ def example() -> int: # 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)) + return neural_network.predict(np.array(([1, 1, 1]), dtype=np.float64)) if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index b9f3115df92a..22da7cb777b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "EM101", # Exception must not use a string literal, assign to variable first "EXE001", # Shebang is present but file is not executable" -- FIX ME "G004", # Logging statement uses f-string - "ICN001", # `matplotlib.pyplot` should be imported as `plt` -- FIX ME "INP001", # File `x/y/z.py` is part of an implicit namespace package. Add an `__init__.py`. -- FIX ME "NPY002", # Replace legacy `np.random.choice` call with `np.random.Generator` -- FIX ME "PGH003", # Use specific rule codes when ignoring type issues -- FIX ME From c328b000ecdd4ad08d029999144e7ec702022390 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:35:37 +0200 Subject: [PATCH 2701/2908] [pre-commit.ci] pre-commit autoupdate (#11339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.4 → v0.3.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.4...v0.3.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8b101207d5ff..e6b1b0442c04 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.3.5 hooks: - id: ruff - id: ruff-format From 39daaf8248b37404f69e8459d0378d77b59c6c0f Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 1 Apr 2024 22:36:41 +0300 Subject: [PATCH 2702/2908] Enable ruff RUF100 rule (#11337) --- audio_filters/butterworth_filter.py | 14 +++++++------- data_structures/binary_tree/basic_binary_tree.py | 2 +- .../binary_tree/non_recursive_segment_tree.py | 2 +- data_structures/binary_tree/red_black_tree.py | 2 +- data_structures/binary_tree/segment_tree.py | 6 +++--- data_structures/heap/min_heap.py | 2 +- dynamic_programming/longest_common_subsequence.py | 2 +- .../longest_increasing_subsequence_o_nlogn.py | 4 ++-- graphs/articulation_points.py | 2 +- graphs/dinic.py | 2 +- other/sdes.py | 4 ++-- project_euler/problem_011/sol2.py | 2 +- pyproject.toml | 1 - strings/manacher.py | 2 +- 14 files changed, 23 insertions(+), 24 deletions(-) diff --git a/audio_filters/butterworth_filter.py b/audio_filters/butterworth_filter.py index 6449bc3f3dce..4e6ea1b18fb4 100644 --- a/audio_filters/butterworth_filter.py +++ b/audio_filters/butterworth_filter.py @@ -13,7 +13,7 @@ def make_lowpass( frequency: int, samplerate: int, - q_factor: float = 1 / sqrt(2), # noqa: B008 + q_factor: float = 1 / sqrt(2), ) -> IIRFilter: """ Creates a low-pass filter @@ -43,7 +43,7 @@ def make_lowpass( def make_highpass( frequency: int, samplerate: int, - q_factor: float = 1 / sqrt(2), # noqa: B008 + q_factor: float = 1 / sqrt(2), ) -> IIRFilter: """ Creates a high-pass filter @@ -73,7 +73,7 @@ def make_highpass( def make_bandpass( frequency: int, samplerate: int, - q_factor: float = 1 / sqrt(2), # noqa: B008 + q_factor: float = 1 / sqrt(2), ) -> IIRFilter: """ Creates a band-pass filter @@ -104,7 +104,7 @@ def make_bandpass( def make_allpass( frequency: int, samplerate: int, - q_factor: float = 1 / sqrt(2), # noqa: B008 + q_factor: float = 1 / sqrt(2), ) -> IIRFilter: """ Creates an all-pass filter @@ -132,7 +132,7 @@ def make_peak( frequency: int, samplerate: int, gain_db: float, - q_factor: float = 1 / sqrt(2), # noqa: B008 + q_factor: float = 1 / sqrt(2), ) -> IIRFilter: """ Creates a peak filter @@ -164,7 +164,7 @@ def make_lowshelf( frequency: int, samplerate: int, gain_db: float, - q_factor: float = 1 / sqrt(2), # noqa: B008 + q_factor: float = 1 / sqrt(2), ) -> IIRFilter: """ Creates a low-shelf filter @@ -201,7 +201,7 @@ def make_highshelf( frequency: int, samplerate: int, gain_db: float, - q_factor: float = 1 / sqrt(2), # noqa: B008 + q_factor: float = 1 / sqrt(2), ) -> IIRFilter: """ Creates a high-shelf filter diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 0439413d95b5..9d4c1bdbb57a 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -85,7 +85,7 @@ def depth(self) -> int: """ return self._depth(self.root) - def _depth(self, node: Node | None) -> int: # noqa: UP007 + def _depth(self, node: Node | None) -> int: if not node: return 0 return 1 + max(self._depth(node.left), self._depth(node.right)) diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 42c78a3a1be0..45c476701d79 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -87,7 +87,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 | None: # noqa: E741 + def query(self, l: int, r: int) -> T | None: """ Get range query value in log(N) time :param l: left element index diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index bdd808c828e0..e68d8d1e3735 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -152,7 +152,7 @@ def _insert_repair(self) -> None: self.grandparent.color = 1 self.grandparent._insert_repair() - def remove(self, label: int) -> RedBlackTree: # noqa: PLR0912 + def remove(self, label: int) -> RedBlackTree: """Remove label from this tree.""" if self.label == label: if self.left and self.right: diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index 3b0b32946f6e..bb9c1ae2268b 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -35,7 +35,7 @@ def right(self, idx): """ return idx * 2 + 1 - def build(self, idx, l, r): # noqa: E741 + def build(self, idx, l, r): if l == r: self.st[idx] = self.A[l] else: @@ -56,7 +56,7 @@ 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): # noqa: E741 + def update_recursive(self, idx, l, r, a, b, val): """ update(1, 1, N, a, b, v) for update val v to [a,b] """ @@ -83,7 +83,7 @@ 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): # noqa: E741 + def query_recursive(self, idx, l, r, a, b): """ query(1, 1, N, a, b) for query max of [a,b] """ diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index ecb1876493b0..39f6d99e8a4c 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) # noqa: E741 + l = self.get_left_child_idx(idx) r = self.get_right_child_idx(idx) smallest = idx diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 178b4169b213..22f50a166ae4 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -38,7 +38,7 @@ def longest_common_subsequence(x: str, y: str): n = len(y) # declaring the array for storing the dp values - l = [[0] * (n + 1) for _ in range(m + 1)] # noqa: E741 + l = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): diff --git a/dynamic_programming/longest_increasing_subsequence_o_nlogn.py b/dynamic_programming/longest_increasing_subsequence_o_nlogn.py index 5e11d729f395..44e333e97779 100644 --- a/dynamic_programming/longest_increasing_subsequence_o_nlogn.py +++ b/dynamic_programming/longest_increasing_subsequence_o_nlogn.py @@ -7,13 +7,13 @@ from __future__ import annotations -def ceil_index(v, l, r, key): # noqa: E741 +def ceil_index(v, l, r, key): while r - l > 1: m = (l + r) // 2 if v[m] >= key: r = m else: - l = m # noqa: E741 + l = m return r diff --git a/graphs/articulation_points.py b/graphs/articulation_points.py index d28045282425..3fcaffd73725 100644 --- a/graphs/articulation_points.py +++ b/graphs/articulation_points.py @@ -1,5 +1,5 @@ # Finding Articulation Points in Undirected Graph -def compute_ap(l): # noqa: E741 +def compute_ap(l): n = len(l) out_edge_count = 0 low = [0] * n diff --git a/graphs/dinic.py b/graphs/dinic.py index aaf3a119525c..4f5e81236984 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): # noqa: E741 l = 30 maybe faster for random data + 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 diff --git a/other/sdes.py b/other/sdes.py index 31105984b9bb..a69add3430c3 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]) # noqa: E741 + l = apply_sbox(s0, temp[:4]) r = apply_sbox(s1, temp[4:]) - l = "0" * (2 - len(l)) + l # noqa: E741 + l = "0" * (2 - len(l)) + l r = "0" * (2 - len(r)) + r temp = apply_table(l + r, p4_table) temp = xor(left, temp) diff --git a/project_euler/problem_011/sol2.py b/project_euler/problem_011/sol2.py index 9ea0db991aaf..2958305331a9 100644 --- a/project_euler/problem_011/sol2.py +++ b/project_euler/problem_011/sol2.py @@ -35,7 +35,7 @@ def solution(): 70600674 """ with open(os.path.dirname(__file__) + "/grid.txt") as f: - l = [] # noqa: E741 + l = [] for _ in range(20): l.append([int(x) for x in f.readline().split()]) diff --git a/pyproject.toml b/pyproject.toml index 22da7cb777b5..c8a8744abc83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception "PT018", # Assertion should be broken down into multiple parts "RUF00", # Ambiguous unicode character and other rules - "RUF100", # Unused `noqa` directive -- FIX ME "S101", # Use of `assert` detected -- DO NOT FIX "S105", # Possible hardcoded password: 'password' "S113", # Probable use of requests call without timeout -- FIX ME diff --git a/strings/manacher.py b/strings/manacher.py index c58c7c19ec44..ca546e533acd 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -50,7 +50,7 @@ def palindromic_string(input_string: str) -> str: # 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 j + k - 1 > r: - l = j - k + 1 # noqa: E741 + l = j - k + 1 r = j + k - 1 # update max_length and start position From f8a948914b928d9fd3c0e32c034bd90315caa389 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 1 Apr 2024 22:39:31 +0300 Subject: [PATCH 2703/2908] Enable ruff NPY002 rule (#11336) --- linear_algebra/src/conjugate_gradient.py | 6 ++++-- machine_learning/decision_tree.py | 3 ++- machine_learning/k_means_clust.py | 6 +++--- machine_learning/sequential_minimum_optimization.py | 5 +++-- neural_network/back_propagation_neural_network.py | 8 +++++--- neural_network/convolution_neural_network.py | 13 +++++++------ neural_network/input_data.py | 6 +++--- neural_network/two_hidden_layers_neural_network.py | 9 +++++---- pyproject.toml | 1 - 9 files changed, 32 insertions(+), 25 deletions(-) diff --git a/linear_algebra/src/conjugate_gradient.py b/linear_algebra/src/conjugate_gradient.py index 4c0b58deb978..45da35813978 100644 --- a/linear_algebra/src/conjugate_gradient.py +++ b/linear_algebra/src/conjugate_gradient.py @@ -61,7 +61,8 @@ def _create_spd_matrix(dimension: int) -> Any: >>> _is_matrix_spd(spd_matrix) True """ - random_matrix = np.random.randn(dimension, dimension) + rng = np.random.default_rng() + random_matrix = rng.normal(size=(dimension, dimension)) spd_matrix = np.dot(random_matrix, random_matrix.T) assert _is_matrix_spd(spd_matrix) return spd_matrix @@ -157,7 +158,8 @@ def test_conjugate_gradient() -> None: # 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) + rng = np.random.default_rng() + x_true = rng.normal(size=(dimension, 1)) b = np.dot(spd_matrix, x_true) # Numpy solution. diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index 7f129919a3ce..e48905eeac6a 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -187,7 +187,8 @@ def main(): tree = DecisionTree(depth=10, min_leaf_size=10) tree.train(x, y) - test_cases = (np.random.rand(10) * 2) - 1 + rng = np.random.default_rng() + test_cases = (rng.random(10) * 2) - 1 predictions = np.array([tree.predict(x) for x in test_cases]) avg_error = np.mean((predictions - test_cases) ** 2) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 9f6646944458..a926362fc18b 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -55,12 +55,12 @@ 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 - np.random.seed(seed) + # useful for obtaining consistent results + rng = np.random.default_rng(seed) n = data.shape[0] # number of data points # Pick K indices from range [0, N). - rand_indices = np.random.randint(0, n, k) + rand_indices = rng.integers(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, diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index be16baca1a4c..408d59ab5d29 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -289,12 +289,13 @@ def _choose_a2(self, i1): if cmd is None: return - for i2 in np.roll(self.unbound, np.random.choice(self.length)): + rng = np.random.default_rng() + for i2 in np.roll(self.unbound, rng.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)): + for i2 in np.roll(self._all_samples, rng.choice(self.length)): cmd = yield i1, i2 if cmd is None: return diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index 7e0bdbbe2857..6131a13e945e 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -51,8 +51,9 @@ def __init__( 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 + rng = np.random.default_rng() + self.weight = np.asmatrix(rng.normal(0, 0.5, (self.units, back_units))) + self.bias = np.asmatrix(rng.normal(0, 0.5, self.units)).T if self.activation is None: self.activation = sigmoid @@ -174,7 +175,8 @@ def plot_loss(self): def example(): - x = np.random.randn(10, 10) + rng = np.random.default_rng() + x = rng.normal(size=(10, 10)) y = np.asarray( [ [0.8, 0.4], diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index 07cc456b7466..3c551924442d 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -41,15 +41,16 @@ def __init__( self.size_pooling1 = size_p1 self.rate_weight = rate_w self.rate_thre = rate_t + rng = np.random.default_rng() self.w_conv1 = [ - np.asmatrix(-1 * np.random.rand(self.conv1[0], self.conv1[0]) + 0.5) + np.asmatrix(-1 * rng.random((self.conv1[0], self.conv1[0])) + 0.5) for i in range(self.conv1[1]) ] - self.wkj = np.asmatrix(-1 * np.random.rand(self.num_bp3, self.num_bp2) + 0.5) - self.vji = np.asmatrix(-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.wkj = np.asmatrix(-1 * rng.random((self.num_bp3, self.num_bp2)) + 0.5) + self.vji = np.asmatrix(-1 * rng.random((self.num_bp2, self.num_bp1)) + 0.5) + self.thre_conv1 = -2 * rng.random(self.conv1[1]) + 1 + self.thre_bp2 = -2 * rng.random(self.num_bp2) + 1 + self.thre_bp3 = -2 * rng.random(self.num_bp3) + 1 def save_model(self, save_path): # save model dict with pickle diff --git a/neural_network/input_data.py b/neural_network/input_data.py index 9d4195487dbb..d189e3f9e0d9 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -153,7 +153,7 @@ def __init__( """ seed1, seed2 = random_seed.get_seed(seed) # If op level seed is not set, use whatever graph level seed is returned - np.random.seed(seed1 if seed is None else seed2) + self._rng = np.random.default_rng(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) @@ -211,7 +211,7 @@ def next_batch(self, batch_size, fake_data=False, shuffle=True): # Shuffle for the first epoch if self._epochs_completed == 0 and start == 0 and shuffle: perm0 = np.arange(self._num_examples) - np.random.shuffle(perm0) + self._rng.shuffle(perm0) self._images = self.images[perm0] self._labels = self.labels[perm0] # Go to the next epoch @@ -225,7 +225,7 @@ def next_batch(self, batch_size, fake_data=False, shuffle=True): # Shuffle the data if shuffle: perm = np.arange(self._num_examples) - np.random.shuffle(perm) + self._rng.shuffle(perm) self._images = self.images[perm] self._labels = self.labels[perm] # Start next epoch diff --git a/neural_network/two_hidden_layers_neural_network.py b/neural_network/two_hidden_layers_neural_network.py index dea7e2342d9f..d488de590cc2 100644 --- a/neural_network/two_hidden_layers_neural_network.py +++ b/neural_network/two_hidden_layers_neural_network.py @@ -28,19 +28,20 @@ def __init__(self, input_array: np.ndarray, output_array: np.ndarray) -> None: # 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 = np.random.rand( - self.input_array.shape[1], 4 + rng = np.random.default_rng() + self.input_layer_and_first_hidden_layer_weights = rng.random( + (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 = np.random.rand(4, 3) + self.first_hidden_layer_and_second_hidden_layer_weights = rng.random((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 = np.random.rand(3, 1) + self.second_hidden_layer_and_output_layer_weights = rng.random((3, 1)) # Real output values provided. self.output_array = output_array diff --git a/pyproject.toml b/pyproject.toml index c8a8744abc83..50cd38005f09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "EXE001", # Shebang is present but file is not executable" -- FIX ME "G004", # Logging statement uses f-string "INP001", # File `x/y/z.py` is part of an implicit namespace package. Add an `__init__.py`. -- FIX ME - "NPY002", # Replace legacy `np.random.choice` call with `np.random.Generator` -- FIX ME "PGH003", # Use specific rule codes when ignoring type issues -- FIX ME "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX From 93fb555e0a97096f62a122e73cfdc6f0579cefbe Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 2 Apr 2024 04:27:56 +0300 Subject: [PATCH 2704/2908] Enable ruff SIM102 rule (#11341) * Enable ruff SIM102 rule * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/arrays/sudoku_solver.py | 7 +++---- .../stacks/balanced_parentheses.py | 7 ++++--- graphs/a_star.py | 20 ++++++++++++------- graphs/bi_directional_dijkstra.py | 8 +++++--- other/davis_putnam_logemann_loveland.py | 7 +++---- project_euler/problem_033/sol1.py | 10 +++++++--- project_euler/problem_037/sol1.py | 7 ++++--- project_euler/problem_107/sol1.py | 9 +++++---- project_euler/problem_207/sol1.py | 8 +++++--- pyproject.toml | 1 - scheduling/shortest_job_first.py | 13 +++++++----- scripts/validate_solutions.py | 11 ++++++---- web_programming/emails_from_url.py | 15 ++++++++------ 13 files changed, 73 insertions(+), 50 deletions(-) diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index c9dffcde2379..5c1cff06f9d4 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -92,10 +92,9 @@ def eliminate(values, s, d): dplaces = [s for s in u if d in values[s]] if len(dplaces) == 0: return False ## Contradiction: no place for this value - elif len(dplaces) == 1: - # d can only be in one place in unit; assign it there - if not assign(values, dplaces[0], d): - return False + # d can only be in one place in unit; assign it there + elif len(dplaces) == 1 and not assign(values, dplaces[0], d): + return False return values diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 3c036c220e5c..928815bb2111 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -19,9 +19,10 @@ def balanced_parentheses(parentheses: str) -> bool: 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 + elif bracket in (")", "]", "}") and ( + stack.is_empty() or bracket_pairs[stack.pop()] != bracket + ): + return False return stack.is_empty() diff --git a/graphs/a_star.py b/graphs/a_star.py index 06da3b5cd863..1d7063ccc55a 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -75,13 +75,19 @@ def search( for i in range(len(DIRECTIONS)): # to try out different valid actions x2 = x + DIRECTIONS[i][0] y2 = y + DIRECTIONS[i][1] - 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] - cell.append([f2, g2, x2, y2]) - closed[x2][y2] = 1 - action[x2][y2] = i + if ( + x2 >= 0 + and x2 < len(grid) + and y2 >= 0 + and y2 < len(grid[0]) + and closed[x2][y2] == 0 + and grid[x2][y2] == 0 + ): + g2 = g + cost + f2 = g2 + heuristic[x2][y2] + cell.append([f2, g2, x2, y2]) + closed[x2][y2] = 1 + action[x2][y2] = i invpath = [] x = goal[0] y = goal[1] diff --git a/graphs/bi_directional_dijkstra.py b/graphs/bi_directional_dijkstra.py index 7b9eac6c8587..d2c4030b921b 100644 --- a/graphs/bi_directional_dijkstra.py +++ b/graphs/bi_directional_dijkstra.py @@ -36,9 +36,11 @@ def pass_and_relaxation( queue.put((new_cost_f, nxt)) cst_fwd[nxt] = new_cost_f parent[nxt] = v - if nxt in visited_backward: - if cst_fwd[v] + d + cst_bwd[nxt] < shortest_distance: - shortest_distance = cst_fwd[v] + d + cst_bwd[nxt] + if ( + nxt in visited_backward + and cst_fwd[v] + d + cst_bwd[nxt] < shortest_distance + ): + shortest_distance = cst_fwd[v] + d + cst_bwd[nxt] return shortest_distance diff --git a/other/davis_putnam_logemann_loveland.py b/other/davis_putnam_logemann_loveland.py index 5c6e2d9ffd5e..3a76f3dfef08 100644 --- a/other/davis_putnam_logemann_loveland.py +++ b/other/davis_putnam_logemann_loveland.py @@ -64,10 +64,9 @@ def assign(self, model: dict[str, bool | None]) -> None: value = model[symbol] else: continue - if value is not None: - # Complement assignment if literal is in complemented form - if literal.endswith("'"): - value = not value + # Complement assignment if literal is in complemented form + if value is not None and literal.endswith("'"): + value = not value self.literals[literal] = value def evaluate(self, model: dict[str, bool | None]) -> bool | None: diff --git a/project_euler/problem_033/sol1.py b/project_euler/problem_033/sol1.py index 187fd61bde6c..71790d34fbed 100644 --- a/project_euler/problem_033/sol1.py +++ b/project_euler/problem_033/sol1.py @@ -44,9 +44,13 @@ def fraction_list(digit_len: int) -> list[str]: 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 is_digit_cancelling(num, den): - solutions.append(f"{num}/{den}") + if ( + (num != den) + and (num % 10 == den // 10) + and (den % 10 != 0) + and is_digit_cancelling(num, den) + ): + solutions.append(f"{num}/{den}") den += 1 num += 1 den = 10 diff --git a/project_euler/problem_037/sol1.py b/project_euler/problem_037/sol1.py index ef7686cbcb96..9c09065f4bd0 100644 --- a/project_euler/problem_037/sol1.py +++ b/project_euler/problem_037/sol1.py @@ -85,9 +85,10 @@ def validate(n: int) -> bool: >>> 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 + if len(str(n)) > 3 and ( + not is_prime(int(str(n)[-3:])) or not is_prime(int(str(n)[:3])) + ): + return False return True diff --git a/project_euler/problem_107/sol1.py b/project_euler/problem_107/sol1.py index 3fe75909e2ea..79cdd937042e 100644 --- a/project_euler/problem_107/sol1.py +++ b/project_euler/problem_107/sol1.py @@ -81,10 +81,11 @@ def prims_algorithm(self) -> Graph: 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 + if (edge[0] in subgraph.vertices) ^ ( + edge[1] in subgraph.vertices + ) and weight < min_weight: + min_edge = edge + min_weight = weight subgraph.add_edge(min_edge, min_weight) diff --git a/project_euler/problem_207/sol1.py b/project_euler/problem_207/sol1.py index 2b3591f51cfa..c83dc1d4aaef 100644 --- a/project_euler/problem_207/sol1.py +++ b/project_euler/problem_207/sol1.py @@ -88,9 +88,11 @@ def solution(max_proportion: float = 1 / 12345) -> int: total_partitions += 1 if check_partition_perfect(partition_candidate): perfect_partitions += 1 - if perfect_partitions > 0: - if perfect_partitions / total_partitions < max_proportion: - return int(partition_candidate) + if ( + perfect_partitions > 0 + and perfect_partitions / total_partitions < max_proportion + ): + return int(partition_candidate) integer += 1 diff --git a/pyproject.toml b/pyproject.toml index 50cd38005f09..e3cf42c92c54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "S105", # Possible hardcoded password: 'password' "S113", # Probable use of requests call without timeout -- FIX ME "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME - "SIM102", # Use a single `if` statement instead of nested `if` statements -- FIX ME "SLF001", # Private member accessed: `_Iterator` -- FIX ME "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX ] diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index cfd0417ea62d..6899ec87c591 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -37,11 +37,14 @@ def calculate_waitingtime( # Process until all processes are completed while complete != no_of_processes: for j in range(no_of_processes): - if arrival_time[j] <= increment_time and remaining_time[j] > 0: - if remaining_time[j] < minm: - minm = remaining_time[j] - short = j - check = True + if ( + arrival_time[j] <= increment_time + and remaining_time[j] > 0 + and remaining_time[j] < minm + ): + minm = remaining_time[j] + short = j + check = True if not check: increment_time += 1 diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index ca4af5261a8f..0afbdde315c7 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -71,10 +71,13 @@ def added_solution_file_path() -> list[pathlib.Path]: 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 only if there are any, otherwise default to all solutions + if ( + os.environ.get("CI") + and os.environ.get("GITHUB_EVENT_NAME") == "pull_request" + and (filepaths := added_solution_file_path()) + ): + return filepaths return all_solution_file_paths() diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index 6b4bacfe7d5a..26c88e1b13a5 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -30,12 +30,15 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None 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 urls. - if value not in self.urls: - url = parse.urljoin(self.domain, value) - self.urls.append(url) + # If href is defined, not empty nor # print it and not already in urls. + if ( + name == "href" + and value != "#" + and value != "" + and value not in self.urls + ): + url = parse.urljoin(self.domain, value) + self.urls.append(url) # Get main domain name (example.com) From f8cdb3e9482ddca85cd1bffa96c038afc13f9c85 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 2 Apr 2024 19:44:37 +0300 Subject: [PATCH 2705/2908] Enable ruff S105 rule (#11343) * Enable ruff S105 rule * Update web_programming/recaptcha_verification.py --------- Co-authored-by: Christian Clauss --- pyproject.toml | 1 - web_programming/recaptcha_verification.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3cf42c92c54..65a0754d678c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "PT018", # Assertion should be broken down into multiple parts "RUF00", # Ambiguous unicode character and other rules "S101", # Use of `assert` detected -- DO NOT FIX - "S105", # Possible hardcoded password: 'password' "S113", # Probable use of requests call without timeout -- FIX ME "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME "SLF001", # Private member accessed: `_Iterator` -- FIX ME diff --git a/web_programming/recaptcha_verification.py b/web_programming/recaptcha_verification.py index b03afb28ec53..c9b691b28a8b 100644 --- a/web_programming/recaptcha_verification.py +++ b/web_programming/recaptcha_verification.py @@ -43,7 +43,7 @@ def login_using_recaptcha(request): # Enter your recaptcha secret key here - secret_key = "secretKey" + secret_key = "secretKey" # noqa: S105 url = "https://www.google.com/recaptcha/api/siteverify" # when method is not POST, direct user to login page From f437f922792b8c7e3fbb168a1ec6bfdf183a7304 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 2 Apr 2024 22:13:56 +0300 Subject: [PATCH 2706/2908] Enable ruff INP001 rule (#11346) * Enable ruff INP001 rule * Fix * Fix * Fix * Fix * Fix --- data_structures/arrays/__init__.py | 0 data_structures/hashing/tests/__init__.py | 0 digital_image_processing/morphological_operations/__init__.py | 0 electronics/__init__.py | 0 electronics/circular_convolution.py | 3 +-- fractals/__init__.py | 0 geometry/__init__.py | 0 greedy_methods/__init__.py | 0 linear_algebra/src/gaussian_elimination_pivoting/__init__.py | 0 linear_programming/__init__.py | 0 maths/numerical_analysis/__init__.py | 0 maths/special_numbers/__init__.py | 0 neural_network/activation_functions/__init__.py | 0 neural_network/activation_functions/mish.py | 3 ++- pyproject.toml | 1 - 15 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 data_structures/arrays/__init__.py create mode 100644 data_structures/hashing/tests/__init__.py create mode 100644 digital_image_processing/morphological_operations/__init__.py create mode 100644 electronics/__init__.py create mode 100644 fractals/__init__.py create mode 100644 geometry/__init__.py create mode 100644 greedy_methods/__init__.py create mode 100644 linear_algebra/src/gaussian_elimination_pivoting/__init__.py create mode 100644 linear_programming/__init__.py create mode 100644 maths/numerical_analysis/__init__.py create mode 100644 maths/special_numbers/__init__.py create mode 100644 neural_network/activation_functions/__init__.py diff --git a/data_structures/arrays/__init__.py b/data_structures/arrays/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/hashing/tests/__init__.py b/data_structures/hashing/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/morphological_operations/__init__.py b/digital_image_processing/morphological_operations/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/electronics/__init__.py b/electronics/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/electronics/circular_convolution.py b/electronics/circular_convolution.py index f2e35742e944..768f2ad941bc 100644 --- a/electronics/circular_convolution.py +++ b/electronics/circular_convolution.py @@ -37,8 +37,7 @@ def circular_convolution(self) -> list[float]: using matrix method Usage: - >>> import circular_convolution as cc - >>> convolution = cc.CircularConvolution() + >>> convolution = CircularConvolution() >>> convolution.circular_convolution() [10, 10, 6, 14] diff --git a/fractals/__init__.py b/fractals/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/geometry/__init__.py b/geometry/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/greedy_methods/__init__.py b/greedy_methods/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/linear_algebra/src/gaussian_elimination_pivoting/__init__.py b/linear_algebra/src/gaussian_elimination_pivoting/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/linear_programming/__init__.py b/linear_programming/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/maths/numerical_analysis/__init__.py b/maths/numerical_analysis/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/maths/special_numbers/__init__.py b/maths/special_numbers/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/neural_network/activation_functions/__init__.py b/neural_network/activation_functions/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/neural_network/activation_functions/mish.py b/neural_network/activation_functions/mish.py index e51655df8a3f..57a91413fe50 100644 --- a/neural_network/activation_functions/mish.py +++ b/neural_network/activation_functions/mish.py @@ -7,7 +7,8 @@ """ import numpy as np -from softplus import softplus + +from .softplus import softplus def mish(vector: np.ndarray) -> np.ndarray: diff --git a/pyproject.toml b/pyproject.toml index 65a0754d678c..9689cf2b37aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "EM101", # Exception must not use a string literal, assign to variable first "EXE001", # Shebang is present but file is not executable" -- FIX ME "G004", # Logging statement uses f-string - "INP001", # File `x/y/z.py` is part of an implicit namespace package. Add an `__init__.py`. -- FIX ME "PGH003", # Use specific rule codes when ignoring type issues -- FIX ME "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX From f5bbea3776a5038d0e428ce3c06c25086076e212 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 2 Apr 2024 22:18:47 +0300 Subject: [PATCH 2707/2908] Enable ruff RUF005 rule (#11344) --- data_structures/binary_tree/binary_search_tree.py | 2 +- dynamic_programming/subset_generation.py | 2 +- maths/odd_sieve.py | 2 +- pyproject.toml | 5 ++++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 090e3e25fe6d..32194ddc2043 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -336,7 +336,7 @@ def inorder(curr_node: Node | None) -> list[Node]: """ node_list = [] if curr_node is not None: - node_list = inorder(curr_node.left) + [curr_node] + inorder(curr_node.right) + node_list = [*inorder(curr_node.left), curr_node, *inorder(curr_node.right)] return node_list diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py index 1be412b9374d..d490bca737ba 100644 --- a/dynamic_programming/subset_generation.py +++ b/dynamic_programming/subset_generation.py @@ -45,7 +45,7 @@ def subset_combinations(elements: list[int], n: int) -> list: for i in range(1, r + 1): for j in range(i, 0, -1): for prev_combination in dp[j - 1]: - dp[j].append(tuple(prev_combination) + (elements[i - 1],)) + dp[j].append((*prev_combination, elements[i - 1])) try: return sorted(dp[n]) diff --git a/maths/odd_sieve.py b/maths/odd_sieve.py index 60e92921a94c..06605ca54296 100644 --- a/maths/odd_sieve.py +++ b/maths/odd_sieve.py @@ -33,7 +33,7 @@ def odd_sieve(num: int) -> list[int]: 0, ceil((num - i_squared) / (i << 1)) ) - return [2] + list(compress(range(3, num, 2), sieve)) + return [2, *list(compress(range(3, num, 2), sieve))] if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 9689cf2b37aa..e1d7dc91b2b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,10 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "PLW2901", # PLW2901: Redefined loop variable -- FIX ME "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception "PT018", # Assertion should be broken down into multiple parts - "RUF00", # Ambiguous unicode character and other rules + "RUF001", # String contains ambiguous {}. Did you mean {}? + "RUF002", # Docstring contains ambiguous {}. Did you mean {}? + "RUF003", # Comment contains ambiguous {}. Did you mean {}? + "RUF007", # Prefer itertools.pairwise() over zip() when iterating over successive pairs "S101", # Use of `assert` detected -- DO NOT FIX "S113", # Probable use of requests call without timeout -- FIX ME "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME From 53b2926704f3ad3ec2134a114be3a338e755e28a Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 2 Apr 2024 22:29:34 +0300 Subject: [PATCH 2708/2908] Enable ruff PGH003 rule (#11345) * Enable ruff PGH003 rule * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- compression/huffman.py | 4 ++-- data_structures/binary_tree/binary_search_tree.py | 4 ++-- data_structures/linked_list/rotate_to_the_right.py | 2 +- fractals/mandelbrot.py | 2 +- graphics/bezier_curve.py | 2 +- maths/entropy.py | 4 ++-- matrix/spiral_print.py | 4 +++- matrix/tests/test_matrix_operation.py | 2 +- project_euler/problem_092/sol1.py | 2 +- project_euler/problem_104/sol1.py | 2 +- pyproject.toml | 1 - scripts/validate_filenames.py | 2 +- scripts/validate_solutions.py | 6 +++--- web_programming/covid_stats_via_xpath.py | 2 +- 14 files changed, 20 insertions(+), 19 deletions(-) diff --git a/compression/huffman.py b/compression/huffman.py index 65e5c2f25385..44eda6c03180 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -40,7 +40,7 @@ def build_tree(letters: list[Letter]) -> Letter | TreeNode: Run through the list of Letters and build the min heap for the Huffman Tree. """ - response: list[Letter | TreeNode] = letters # type: ignore + response: list[Letter | TreeNode] = list(letters) while len(response) > 1: left = response.pop(0) right = response.pop(0) @@ -59,7 +59,7 @@ def traverse_tree(root: Letter | TreeNode, bitstring: str) -> list[Letter]: if isinstance(root, Letter): root.bitstring[root.letter] = bitstring return [root] - treenode: TreeNode = root # type: ignore + treenode: TreeNode = root letters = [] letters += traverse_tree(treenode.left, bitstring + "0") letters += traverse_tree(treenode.right, bitstring + "1") diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 32194ddc2043..3f214d0113a4 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -294,9 +294,9 @@ def remove(self, value: int) -> None: predecessor = self.get_max( node.left ) # Gets the max value of the left branch - self.remove(predecessor.value) # type: ignore + self.remove(predecessor.value) # type: ignore[union-attr] node.value = ( - predecessor.value # type: ignore + predecessor.value # type: ignore[union-attr] ) # Assigns the value to the node to delete and keep tree structure def preorder_traverse(self, node: Node | None) -> Iterable: diff --git a/data_structures/linked_list/rotate_to_the_right.py b/data_structures/linked_list/rotate_to_the_right.py index 51b10481c0ce..6b1c54f4be4d 100644 --- a/data_structures/linked_list/rotate_to_the_right.py +++ b/data_structures/linked_list/rotate_to_the_right.py @@ -63,7 +63,7 @@ def insert_node(head: Node | None, data: int) -> Node: while temp_node.next_node: temp_node = temp_node.next_node - temp_node.next_node = new_node # type: ignore + temp_node.next_node = new_node return head diff --git a/fractals/mandelbrot.py b/fractals/mandelbrot.py index 5eb9af0aafe1..359d965a882d 100644 --- a/fractals/mandelbrot.py +++ b/fractals/mandelbrot.py @@ -17,7 +17,7 @@ import colorsys -from PIL import Image # type: ignore +from PIL import Image def get_distance(x: float, y: float, max_step: int) -> float: diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 6eeb89da6bdf..9d906f179c92 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -2,7 +2,7 @@ # https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm from __future__ import annotations -from scipy.special import comb # type: ignore +from scipy.special import comb class BezierCurve: diff --git a/maths/entropy.py b/maths/entropy.py index 76fac4ee717d..39ec67bea038 100644 --- a/maths/entropy.py +++ b/maths/entropy.py @@ -96,8 +96,8 @@ def analyze_text(text: str) -> tuple[dict, dict]: 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 = Counter() # type: ignore[var-annotated] + two_char_strings = Counter() # type: ignore[var-annotated] single_char_strings[text[-1]] += 1 # first case when we have space at start. diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 7ba0a275157b..c16dde69cb56 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -116,7 +116,9 @@ def spiral_traversal(matrix: list[list]) -> list[int]: [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] + spiral_traversal([]) """ if matrix: - return list(matrix.pop(0)) + spiral_traversal(list(zip(*matrix))[::-1]) # type: ignore + return list(matrix.pop(0)) + spiral_traversal( + [list(row) for row in zip(*matrix)][::-1] + ) else: return [] diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index 638f97daa2ed..addc870ca205 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -12,7 +12,7 @@ import sys import numpy as np -import pytest # type: ignore +import pytest # Custom/local libraries from matrix import matrix_operation as matop diff --git a/project_euler/problem_092/sol1.py b/project_euler/problem_092/sol1.py index 8d3f0c9ddd7b..3e45e82207a7 100644 --- a/project_euler/problem_092/sol1.py +++ b/project_euler/problem_092/sol1.py @@ -68,7 +68,7 @@ def chain(number: int) -> bool: """ if CHAINS[number - 1] is not None: - return CHAINS[number - 1] # type: ignore + return CHAINS[number - 1] # type: ignore[return-value] number_chain = chain(next_number(number)) CHAINS[number - 1] = number_chain diff --git a/project_euler/problem_104/sol1.py b/project_euler/problem_104/sol1.py index 60fd6fe99adb..d84dbcfc9c65 100644 --- a/project_euler/problem_104/sol1.py +++ b/project_euler/problem_104/sol1.py @@ -15,7 +15,7 @@ import sys -sys.set_int_max_str_digits(0) # type: ignore +sys.set_int_max_str_digits(0) def check(number: int) -> bool: diff --git a/pyproject.toml b/pyproject.toml index e1d7dc91b2b8..7eac811395ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "EM101", # Exception must not use a string literal, assign to variable first "EXE001", # Shebang is present but file is not executable" -- FIX ME "G004", # Logging statement uses f-string - "PGH003", # Use specific rule codes when ignoring type issues -- FIX ME "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX "PLW2901", # PLW2901: Redefined loop variable -- FIX ME diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index ed23f3907114..0890024dd349 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -4,7 +4,7 @@ try: from .build_directory_md import good_file_paths except ImportError: - from build_directory_md import good_file_paths # type: ignore + from build_directory_md import good_file_paths # type: ignore[no-redef] filepaths = list(good_file_paths()) assert filepaths, "good_file_paths() failed!" diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index 0afbdde315c7..68dcd68b3947 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -21,8 +21,8 @@ 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) # type: ignore - spec.loader.exec_module(module) # type: ignore + module = importlib.util.module_from_spec(spec) # type: ignore[arg-type] + spec.loader.exec_module(module) # type: ignore[union-attr] return module @@ -92,7 +92,7 @@ def test_project_euler(solution_path: pathlib.Path) -> None: 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()) # type: ignore + answer = str(solution_module.solution()) answer = hashlib.sha256(answer.encode()).hexdigest() assert ( answer == expected diff --git a/web_programming/covid_stats_via_xpath.py b/web_programming/covid_stats_via_xpath.py index a95130badad9..7011a02bffa8 100644 --- a/web_programming/covid_stats_via_xpath.py +++ b/web_programming/covid_stats_via_xpath.py @@ -7,7 +7,7 @@ from typing import NamedTuple import requests -from lxml import html # type: ignore +from lxml import html class CovidData(NamedTuple): From cc2f5b13088b8a98181983b5589f48749016d4ce Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 8 Apr 2024 14:22:54 +0300 Subject: [PATCH 2709/2908] Do not fix ruff EXE001 rule (#11350) * Do not fix ruff EXE001 rule * Fix --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7eac811395ae..264f06d1f750 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME "E741", # Ambiguous variable name 'l' -- FIX ME "EM101", # Exception must not use a string literal, assign to variable first - "EXE001", # Shebang is present but file is not executable" -- FIX ME + "EXE001", # Shebang is present but file is not executable -- DO NOT FIX "G004", # Logging statement uses f-string "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX From 9e55c9d9845c07ce6390ab92a2d86be4816d4a69 Mon Sep 17 00:00:00 2001 From: Jiayou Qin <90779499+Jiayoqin@users.noreply.github.com> Date: Mon, 8 Apr 2024 07:35:22 -0400 Subject: [PATCH 2710/2908] Added documentations (#11352) * Added documentations * Update data_structures/queue/circular_queue.py --------- Co-authored-by: Christian Clauss --- data_structures/queue/circular_queue.py | 7 +++++-- data_structures/queue/circular_queue_linked_list.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/data_structures/queue/circular_queue.py b/data_structures/queue/circular_queue.py index 93a6ef805c7c..f2fb4c01e467 100644 --- a/data_structures/queue/circular_queue.py +++ b/data_structures/queue/circular_queue.py @@ -25,6 +25,7 @@ def __len__(self) -> int: def is_empty(self) -> bool: """ + Checks whether the queue is empty or not >>> cq = CircularQueue(5) >>> cq.is_empty() True @@ -35,6 +36,7 @@ def is_empty(self) -> bool: def first(self): """ + Returns the first element of the queue >>> cq = CircularQueue(5) >>> cq.first() False @@ -45,7 +47,8 @@ def first(self): def enqueue(self, data): """ - This function insert an element in the queue using self.rear value as an index + This function inserts an element at the end of the queue using self.rear value + as an index. >>> cq = CircularQueue(5) >>> cq.enqueue("A") # doctest: +ELLIPSIS >> cq = CircularQueue(5) >>> cq.dequeue() Traceback (most recent call last): diff --git a/data_structures/queue/circular_queue_linked_list.py b/data_structures/queue/circular_queue_linked_list.py index 62042c4bce96..da8629678e52 100644 --- a/data_structures/queue/circular_queue_linked_list.py +++ b/data_structures/queue/circular_queue_linked_list.py @@ -39,7 +39,7 @@ def create_linked_list(self, initial_capacity: int) -> None: def is_empty(self) -> bool: """ - Checks where the queue is empty or not + Checks whether the queue is empty or not >>> cq = CircularQueueLinkedList() >>> cq.is_empty() True From 14ca726951473dd1993b6b13993105ea3b077ac3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 07:23:51 +0200 Subject: [PATCH 2711/2908] [pre-commit.ci] pre-commit autoupdate (#11355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e6b1b0442c04..d4b8d1136ed7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-executables-have-shebangs - id: check-toml From 0a9a860eb1174a513b231db2cf1a3378ff7c5b33 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:21:33 +0200 Subject: [PATCH 2712/2908] [pre-commit.ci] pre-commit autoupdate (#11364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/MarcoGorelli/auto-walrus: v0.2.2 → 0.3.3](https://github.com/MarcoGorelli/auto-walrus/compare/v0.2.2...0.3.3) - [github.com/astral-sh/ruff-pre-commit: v0.3.5 → v0.3.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.5...v0.3.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4b8d1136ed7..9472bcfa3e07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/MarcoGorelli/auto-walrus - rev: v0.2.2 + rev: 0.3.3 hooks: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.5 + rev: v0.3.7 hooks: - id: ruff - id: ruff-format From a42eb357027328085f928a4ab6c7aa770aeb1d6b Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Fri, 19 Apr 2024 22:30:22 +0300 Subject: [PATCH 2713/2908] Enable ruff E741 rule (#11370) * Enable ruff E741 rule * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../binary_tree/non_recursive_segment_tree.py | 22 ++++++------ data_structures/binary_tree/segment_tree.py | 36 +++++++++---------- data_structures/heap/min_heap.py | 12 +++---- .../longest_common_subsequence.py | 10 +++--- .../longest_increasing_subsequence_o_nlogn.py | 14 ++++---- graphs/articulation_points.py | 10 +++--- graphs/dinic.py | 2 +- .../sequential_minimum_optimization.py | 4 +-- maths/pi_generator.py | 10 +++--- other/sdes.py | 10 +++--- project_euler/problem_011/sol2.py | 22 ++++++++---- pyproject.toml | 1 - strings/jaro_winkler.py | 8 ++--- strings/manacher.py | 33 ++++++++--------- 14 files changed, 102 insertions(+), 92 deletions(-) diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 45c476701d79..ca0d5c111c4f 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -87,12 +87,12 @@ 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 | None: + def query(self, left: int, right: int) -> T | None: """ 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] + :param left: left element index + :param right: right element index + :return: element combined in the range [left, right] >>> st = SegmentTree([1, 2, 3, 4], lambda a, b: a + b) >>> st.query(0, 2) @@ -104,15 +104,15 @@ def query(self, l: int, r: int) -> T | None: >>> st.query(2, 3) 7 """ - l, r = l + self.N, r + self.N + left, right = left + self.N, right + self.N res: T | None = 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 + while left <= right: + if left % 2 == 1: + res = self.st[left] if res is None else self.fn(res, self.st[left]) + if right % 2 == 0: + res = self.st[right] if res is None else self.fn(res, self.st[right]) + left, right = (left + 1) // 2, (right - 1) // 2 return res diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index bb9c1ae2268b..c7069b3f6069 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -35,13 +35,13 @@ def right(self, idx): """ return idx * 2 + 1 - def build(self, idx, l, r): - if l == r: - self.st[idx] = self.A[l] + def build(self, idx, left, right): + if left == right: + self.st[idx] = self.A[left] else: - mid = (l + r) // 2 - self.build(self.left(idx), l, mid) - self.build(self.right(idx), mid + 1, r) + mid = (left + right) // 2 + self.build(self.left(idx), left, mid) + self.build(self.right(idx), mid + 1, right) self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) def update(self, a, b, val): @@ -56,18 +56,18 @@ 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): + def update_recursive(self, idx, left, right, a, b, val): """ update(1, 1, N, a, b, v) for update val v to [a,b] """ - if r < a or l > b: + if right < a or left > b: return True - if l == r: + if left == right: self.st[idx] = val return True - 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) + mid = (left + right) // 2 + self.update_recursive(self.left(idx), left, mid, a, b, val) + self.update_recursive(self.right(idx), mid + 1, right, a, b, val) self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) return True @@ -83,17 +83,17 @@ 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): + def query_recursive(self, idx, left, right, a, b): """ query(1, 1, N, a, b) for query max of [a,b] """ - if r < a or l > b: + if right < a or left > b: return -math.inf - if l >= a and r <= b: + if left >= a and right <= b: return self.st[idx] - 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) + mid = (left + right) // 2 + q1 = self.query_recursive(self.left(idx), left, mid, a, b) + q2 = self.query_recursive(self.right(idx), mid + 1, right, a, b) return max(q1, q2) def show_data(self): diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index 39f6d99e8a4c..ce7ed570a58d 100644 --- a/data_structures/heap/min_heap.py +++ b/data_structures/heap/min_heap.py @@ -66,14 +66,14 @@ 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) - r = self.get_right_child_idx(idx) + left = self.get_left_child_idx(idx) + right = 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 left < len(array) and array[left] < array[idx]: + smallest = left + if right < len(array) and array[right] < array[smallest]: + smallest = right if smallest != idx: array[idx], array[smallest] = array[smallest], array[idx] diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 22f50a166ae4..9a98b1736ed5 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -38,30 +38,30 @@ def longest_common_subsequence(x: str, y: str): n = len(y) # declaring the array for storing the dp values - l = [[0] * (n + 1) for _ in range(m + 1)] + dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): match = 1 if x[i - 1] == y[j - 1] else 0 - l[i][j] = max(l[i - 1][j], l[i][j - 1], l[i - 1][j - 1] + match) + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1] + match) seq = "" i, j = m, n while i > 0 and j > 0: match = 1 if x[i - 1] == y[j - 1] else 0 - if l[i][j] == l[i - 1][j - 1] + match: + if dp[i][j] == dp[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]: + elif dp[i][j] == dp[i - 1][j]: i -= 1 else: j -= 1 - return l[m][n], seq + return dp[m][n], seq if __name__ == "__main__": diff --git a/dynamic_programming/longest_increasing_subsequence_o_nlogn.py b/dynamic_programming/longest_increasing_subsequence_o_nlogn.py index 44e333e97779..bbc7a62b6b5c 100644 --- a/dynamic_programming/longest_increasing_subsequence_o_nlogn.py +++ b/dynamic_programming/longest_increasing_subsequence_o_nlogn.py @@ -7,14 +7,14 @@ from __future__ import annotations -def ceil_index(v, l, r, key): - while r - l > 1: - m = (l + r) // 2 - if v[m] >= key: - r = m +def ceil_index(v, left, right, key): + while right - left > 1: + middle = (left + right) // 2 + if v[middle] >= key: + right = middle else: - l = m - return r + left = middle + return right def longest_increasing_subsequence_length(v: list[int]) -> int: diff --git a/graphs/articulation_points.py b/graphs/articulation_points.py index 3fcaffd73725..0bf16e55bc04 100644 --- a/graphs/articulation_points.py +++ b/graphs/articulation_points.py @@ -1,6 +1,6 @@ # Finding Articulation Points in Undirected Graph -def compute_ap(l): - n = len(l) +def compute_ap(graph): + n = len(graph) out_edge_count = 0 low = [0] * n visited = [False] * n @@ -12,7 +12,7 @@ def dfs(root, at, parent, out_edge_count): visited[at] = True low[at] = at - for to in l[at]: + for to in graph[at]: if to == parent: pass elif not visited[to]: @@ -41,7 +41,7 @@ def dfs(root, at, parent, out_edge_count): # Adjacency list of graph -data = { +graph = { 0: [1, 2], 1: [0, 2], 2: [0, 1, 3, 5], @@ -52,4 +52,4 @@ def dfs(root, at, parent, out_edge_count): 7: [6, 8], 8: [5, 7], } -compute_ap(data) +compute_ap(graph) diff --git a/graphs/dinic.py b/graphs/dinic.py index 4f5e81236984..7919e6bc060a 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): # l = 30 maybe faster for random data # noqa: E741 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/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 408d59ab5d29..3abdd6ccbed8 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -309,9 +309,9 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): # 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) + l, h = max(0.0, a2 - a1), min(self._c, self._c + a2 - a1) # noqa: E741 else: - l, h = max(0.0, a2 + a1 - self._c), min(self._c, a2 + a1) + l, h = max(0.0, a2 + a1 - self._c), min(self._c, a2 + a1) # noqa: E741 if l == h: return None, None diff --git a/maths/pi_generator.py b/maths/pi_generator.py index addd921747ba..97f2c540c1ce 100644 --- a/maths/pi_generator.py +++ b/maths/pi_generator.py @@ -41,7 +41,7 @@ def calculate_pi(limit: int) -> str: t = 1 k = 1 n = 3 - l = 3 + m = 3 decimal = limit counter = 0 @@ -65,11 +65,11 @@ def calculate_pi(limit: int) -> str: q *= 10 r = nr else: - nr = (2 * q + r) * l - nn = (q * (7 * k) + 2 + (r * l)) // (t * l) + nr = (2 * q + r) * m + nn = (q * (7 * k) + 2 + (r * m)) // (t * m) q *= k - t *= l - l += 2 + t *= m + m += 2 k += 1 n = nn r = nr diff --git a/other/sdes.py b/other/sdes.py index a69add3430c3..42186f453a3d 100644 --- a/other/sdes.py +++ b/other/sdes.py @@ -44,11 +44,11 @@ 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]) - 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) + left_bin_str = apply_sbox(s0, temp[:4]) + right_bin_str = apply_sbox(s1, temp[4:]) + left_bin_str = "0" * (2 - len(left_bin_str)) + left_bin_str + right_bin_str = "0" * (2 - len(right_bin_str)) + right_bin_str + temp = apply_table(left_bin_str + right_bin_str, p4_table) temp = xor(left, temp) return temp + right diff --git a/project_euler/problem_011/sol2.py b/project_euler/problem_011/sol2.py index 2958305331a9..09bf315702c5 100644 --- a/project_euler/problem_011/sol2.py +++ b/project_euler/problem_011/sol2.py @@ -35,37 +35,47 @@ def solution(): 70600674 """ with open(os.path.dirname(__file__) + "/grid.txt") as f: - l = [] + grid = [] for _ in range(20): - l.append([int(x) for x in f.readline().split()]) + grid.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] + temp = grid[i][j] * grid[i][j + 1] * grid[i][j + 2] * grid[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] + temp = grid[i][j] * grid[i + 1][j] * grid[i + 2][j] * grid[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] + temp = ( + grid[i][j] + * grid[i + 1][j + 1] + * grid[i + 2][j + 2] + * grid[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 = ( + grid[i][j] + * grid[i + 1][j - 1] + * grid[i + 2][j - 2] + * grid[i + 3][j - 3] + ) if temp > maximum: maximum = temp return maximum diff --git a/pyproject.toml b/pyproject.toml index 264f06d1f750..1ac70b2fab93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME - "E741", # Ambiguous variable name 'l' -- FIX ME "EM101", # Exception must not use a string literal, assign to variable first "EXE001", # Shebang is present but file is not executable -- DO NOT FIX "G004", # Logging statement uses f-string diff --git a/strings/jaro_winkler.py b/strings/jaro_winkler.py index f4a8fbad3ac8..c18f0d85d9f4 100644 --- a/strings/jaro_winkler.py +++ b/strings/jaro_winkler.py @@ -28,12 +28,12 @@ def jaro_winkler(str1: str, str2: str) -> float: def get_matched_characters(_str1: str, _str2: str) -> str: matched = [] limit = min(len(_str1), len(_str2)) // 2 - for i, l in enumerate(_str1): + for i, char 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:]}" + if char in _str2[left:right]: + matched.append(char) + _str2 = f"{_str2[0:_str2.index(char)]} {_str2[_str2.index(char) + 1:]}" return "".join(matched) diff --git a/strings/manacher.py b/strings/manacher.py index ca546e533acd..fc8b01cd9c1c 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -9,9 +9,9 @@ def palindromic_string(input_string: str) -> str: 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 left,right to store previously calculated info. + (please look the explanation for details) 3. return corresponding output_string by removing all "|" """ @@ -29,7 +29,7 @@ def palindromic_string(input_string: str) -> str: # we will store the starting and ending of previous furthest ending palindromic # substring - l, r = 0, 0 + left, right = 0, 0 # length[i] shows the length of palindromic substring with center i length = [1 for i in range(len(new_input_string))] @@ -37,7 +37,7 @@ def palindromic_string(input_string: str) -> str: # for each character in new_string find corresponding palindromic string start = 0 for j in range(len(new_input_string)): - k = 1 if j > r else min(length[l + r - j] // 2, r - j + 1) + k = 1 if j > right else min(length[left + right - j] // 2, right - j + 1) while ( j - k >= 0 and j + k < len(new_input_string) @@ -47,11 +47,11 @@ def palindromic_string(input_string: str) -> str: length[j] = 2 * k - 1 - # 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 j + k - 1 > r: - l = j - k + 1 - r = j + k - 1 + # does this string is ending after the previously explored end (that is right) ? + # if yes the update the new right to the last index of this + if j + k - 1 > right: + left = j - k + 1 + right = j + k - 1 # update max_length and start position if max_length < length[j]: @@ -78,8 +78,9 @@ def palindromic_string(input_string: str) -> str: 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 +i) we have stored the length of palindromic substring which has center at a3 + (starts at left ends at right) 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 @@ -98,11 +99,11 @@ def palindromic_string(input_string: str) -> str: 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 +len_of_palindrome__at(a5) = min(len_of_palindrome_at(a1), right-a5) +where a3 lies from left to right 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 left,right boundary we calculate length of palindrome with +bruteforce and update left,right. it gives the linear time complexity just like z-function """ From 42593489d974feff169cf4f3455e3f209d7bdfcf Mon Sep 17 00:00:00 2001 From: Kelvin Date: Sat, 20 Apr 2024 16:20:37 +0530 Subject: [PATCH 2714/2908] Add doctests in all functions in basic_string.py (#11374) * Add doctests in all functions in basic_string.py * Revert back to original basic_string.py * Add doctest in basic_string.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update genetic_algorithm/basic_string.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- genetic_algorithm/basic_string.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index 089c5c99a1ec..a906ce85a779 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -33,7 +33,12 @@ def evaluate(item: str, main_target: str) -> tuple[str, float]: def crossover(parent_1: str, parent_2: str) -> tuple[str, str]: - """Slice and combine two string at a random point.""" + """ + Slice and combine two strings at a random point. + >>> random.seed(42) + >>> crossover("123456", "abcdef") + ('12345f', 'abcde6') + """ 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:] @@ -41,7 +46,12 @@ def crossover(parent_1: str, parent_2: str) -> tuple[str, str]: def mutate(child: str, genes: list[str]) -> str: - """Mutate a random gene of a child with another one from the list.""" + """ + Mutate a random gene of a child with another one from the list. + >>> random.seed(123) + >>> mutate("123456", list("ABCDEF")) + '12345A' + """ child_list = list(child) if random.uniform(0, 1) < MUTATION_PROBABILITY: child_list[random.randint(0, len(child)) - 1] = random.choice(genes) @@ -54,7 +64,22 @@ def select( population_score: list[tuple[str, float]], genes: list[str], ) -> list[str]: - """Select the second parent and generate new population""" + """ + Select the second parent and generate new population + + >>> random.seed(42) + >>> parent_1 = ("123456", 8.0) + >>> population_score = [("abcdef", 4.0), ("ghijkl", 5.0), ("mnopqr", 7.0)] + >>> genes = list("ABCDEF") + >>> child_n = int(min(parent_1[1] + 1, 10)) + >>> population = [] + >>> for _ in range(child_n): + ... parent_2 = population_score[random.randrange(len(population_score))][0] + ... child_1, child_2 = crossover(parent_1[0], parent_2) + ... population.extend((mutate(child_1, genes), mutate(child_2, genes))) + >>> len(population) == (int(parent_1[1]) + 1) * 2 + True + """ pop = [] # Generate more children proportionally to the fitness score. child_n = int(parent_1[1] * 100) + 1 From 7b88e15b1cc67c784872b0d16189e516474cf5a5 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 20 Apr 2024 17:20:27 +0300 Subject: [PATCH 2715/2908] Enable ruff RUF007 rule (#11349) * Enable ruff RUF005 rule * Enable ruff RUF007 rule * Fix * Fix * Fix * Update sorts/bead_sort.py Co-authored-by: Christian Clauss * Update sorts/bead_sort.py * Revert "Update sorts/bead_sort.py" This reverts commit b10e5632e4479c2117c8b67113b5aa6545f127aa. * Revert "Update sorts/bead_sort.py" This reverts commit 2c1816bf102eeec5aa39cb2f1806afb64b672d14. * Update sorts/bead_sort.py --------- Co-authored-by: Christian Clauss --- data_structures/linked_list/skip_list.py | 3 ++- pyproject.toml | 1 - sorts/bead_sort.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index 88d3e0daddf0..13e9a94a8698 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -5,6 +5,7 @@ from __future__ import annotations +from itertools import pairwise from random import random from typing import Generic, TypeVar @@ -389,7 +390,7 @@ def traverse_keys(node): def test_iter_always_yields_sorted_values(): def is_sorted(lst): - return all(next_item >= item for item, next_item in zip(lst, lst[1:])) + return all(next_item >= item for item, next_item in pairwise(lst)) skip_list = SkipList() for i in range(10): diff --git a/pyproject.toml b/pyproject.toml index 1ac70b2fab93..e46293a8d526 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "RUF001", # String contains ambiguous {}. Did you mean {}? "RUF002", # Docstring contains ambiguous {}. Did you mean {}? "RUF003", # Comment contains ambiguous {}. Did you mean {}? - "RUF007", # Prefer itertools.pairwise() over zip() when iterating over successive pairs "S101", # Use of `assert` detected -- DO NOT FIX "S113", # Probable use of requests call without timeout -- FIX ME "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME diff --git a/sorts/bead_sort.py b/sorts/bead_sort.py index e51173643d81..8ce0619fd573 100644 --- a/sorts/bead_sort.py +++ b/sorts/bead_sort.py @@ -31,7 +31,7 @@ def bead_sort(sequence: list) -> list: if any(not isinstance(x, int) or x < 0 for x in sequence): raise TypeError("Sequence must be list of non-negative integers") for _ in range(len(sequence)): - for i, (rod_upper, rod_lower) in enumerate(zip(sequence, sequence[1:])): + for i, (rod_upper, rod_lower) in enumerate(zip(sequence, sequence[1:])): # noqa: RUF007 if rod_upper > rod_lower: sequence[i] -= rod_upper - rod_lower sequence[i + 1] += rod_upper - rod_lower From 2702bf9400faece97a1ebc76d0f91b9cfe9658f6 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 21 Apr 2024 20:34:18 +0300 Subject: [PATCH 2716/2908] Enable ruff S113 rule (#11375) * Enable ruff S113 rule * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- machine_learning/linear_regression.py | 3 ++- pyproject.toml | 1 - scripts/validate_solutions.py | 2 +- web_programming/co2_emission.py | 4 ++-- web_programming/covid_stats_via_xpath.py | 4 +++- web_programming/crawl_google_results.py | 2 +- web_programming/crawl_google_scholar_citation.py | 4 +++- web_programming/currency_converter.py | 2 +- web_programming/current_stock_price.py | 4 +++- web_programming/current_weather.py | 4 ++-- web_programming/daily_horoscope.py | 2 +- web_programming/download_images_from_google_query.py | 4 +++- web_programming/emails_from_url.py | 4 ++-- web_programming/fetch_anime_and_play.py | 8 +++++--- web_programming/fetch_bbc_news.py | 2 +- web_programming/fetch_github_info.py | 2 +- web_programming/fetch_jobs.py | 4 +++- web_programming/fetch_quotes.py | 4 ++-- web_programming/fetch_well_rx_price.py | 2 +- web_programming/get_amazon_product_data.py | 4 +++- web_programming/get_imdb_top_250_movies_csv.py | 2 +- web_programming/get_ip_geolocation.py | 2 +- web_programming/get_top_billionaires.py | 2 +- web_programming/get_top_hn_posts.py | 4 ++-- web_programming/giphy.py | 2 +- web_programming/instagram_crawler.py | 2 +- web_programming/instagram_pic.py | 4 ++-- web_programming/instagram_video.py | 4 ++-- web_programming/nasa_data.py | 6 +++--- web_programming/open_google_results.py | 1 + web_programming/random_anime_character.py | 6 ++++-- web_programming/recaptcha_verification.py | 4 +++- web_programming/reddit.py | 1 + web_programming/search_books_by_isbn.py | 2 +- web_programming/slack_message.py | 4 +++- web_programming/world_covid19_stats.py | 2 +- 36 files changed, 68 insertions(+), 46 deletions(-) diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 39bee5712c16..839a5366d1cc 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -19,7 +19,8 @@ def collect_dataset(): """ response = requests.get( "https://raw.githubusercontent.com/yashLadha/The_Math_of_Intelligence/" - "master/Week1/ADRvsRating.csv" + "master/Week1/ADRvsRating.csv", + timeout=10, ) lines = response.text.splitlines() data = [] diff --git a/pyproject.toml b/pyproject.toml index e46293a8d526..ff22fba81c8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "RUF002", # Docstring contains ambiguous {}. Did you mean {}? "RUF003", # Comment contains ambiguous {}. Did you mean {}? "S101", # Use of `assert` detected -- DO NOT FIX - "S113", # Probable use of requests call without timeout -- FIX ME "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME "SLF001", # Private member accessed: `_Iterator` -- FIX ME "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index 68dcd68b3947..325c245e0d77 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -57,7 +57,7 @@ def added_solution_file_path() -> list[pathlib.Path]: "Accept": "application/vnd.github.v3+json", "Authorization": "token " + os.environ["GITHUB_TOKEN"], } - files = requests.get(get_files_url(), headers=headers).json() + files = requests.get(get_files_url(), headers=headers, timeout=10).json() for file in files: filepath = pathlib.Path.cwd().joinpath(file["filename"]) if ( diff --git a/web_programming/co2_emission.py b/web_programming/co2_emission.py index 88a426cb976d..19af70489d1d 100644 --- a/web_programming/co2_emission.py +++ b/web_programming/co2_emission.py @@ -11,13 +11,13 @@ # Emission in the last half hour def fetch_last_half_hour() -> str: - last_half_hour = requests.get(BASE_URL).json()["data"][0] + last_half_hour = requests.get(BASE_URL, timeout=10).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"] + return requests.get(f"{BASE_URL}/{start}/{end}", timeout=10).json()["data"] if __name__ == "__main__": diff --git a/web_programming/covid_stats_via_xpath.py b/web_programming/covid_stats_via_xpath.py index 7011a02bffa8..c27a5d12bb3f 100644 --- a/web_programming/covid_stats_via_xpath.py +++ b/web_programming/covid_stats_via_xpath.py @@ -18,7 +18,9 @@ class CovidData(NamedTuple): def covid_stats(url: str = "https://www.worldometers.info/coronavirus/") -> CovidData: xpath_str = '//div[@class = "maincounter-number"]/span/text()' - return CovidData(*html.fromstring(requests.get(url).content).xpath(xpath_str)) + return CovidData( + *html.fromstring(requests.get(url, timeout=10).content).xpath(xpath_str) + ) fmt = """Total COVID-19 cases in the world: {} diff --git a/web_programming/crawl_google_results.py b/web_programming/crawl_google_results.py index 1f5e6d31992b..cb75d450ff82 100644 --- a/web_programming/crawl_google_results.py +++ b/web_programming/crawl_google_results.py @@ -8,7 +8,7 @@ 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 = requests.get(url, headers={"UserAgent": UserAgent().random}, timeout=10) # res.raise_for_status() with open("project1a.html", "wb") as out_file: # only for knowing the class for data in res.iter_content(10000): diff --git a/web_programming/crawl_google_scholar_citation.py b/web_programming/crawl_google_scholar_citation.py index f92a3d139520..5f2ccad5f414 100644 --- a/web_programming/crawl_google_scholar_citation.py +++ b/web_programming/crawl_google_scholar_citation.py @@ -11,7 +11,9 @@ def get_citation(base_url: str, params: dict) -> str: """ Return the citation number. """ - soup = BeautifulSoup(requests.get(base_url, params=params).content, "html.parser") + soup = BeautifulSoup( + requests.get(base_url, params=params, timeout=10).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() diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py index 3bbcafa8f89b..9623504b89ea 100644 --- a/web_programming/currency_converter.py +++ b/web_programming/currency_converter.py @@ -176,7 +176,7 @@ def convert_currency( params = locals() # from is a reserved keyword params["from"] = params.pop("from_") - res = requests.get(URL_BASE, params=params).json() + res = requests.get(URL_BASE, params=params, timeout=10).json() return str(res["amount"]) if res["error"] == 0 else res["error_message"] diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index 0c06354d8998..9567c05b0558 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -4,7 +4,9 @@ def stock_price(symbol: str = "AAPL") -> str: url = f"https://finance.yahoo.com/quote/%7Bsymbol%7D?p={symbol}" - yahoo_finance_source = requests.get(url, headers={"USER-AGENT": "Mozilla/5.0"}).text + yahoo_finance_source = requests.get( + url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10 + ).text soup = BeautifulSoup(yahoo_finance_source, "html.parser") specific_fin_streamer_tag = soup.find("fin-streamer", {"data-test": "qsp-price"}) diff --git a/web_programming/current_weather.py b/web_programming/current_weather.py index 3b6cd177cdfb..4a8fa5e3c845 100644 --- a/web_programming/current_weather.py +++ b/web_programming/current_weather.py @@ -20,13 +20,13 @@ def current_weather(location: str) -> list[dict]: if OPENWEATHERMAP_API_KEY: params_openweathermap = {"q": location, "appid": OPENWEATHERMAP_API_KEY} response_openweathermap = requests.get( - OPENWEATHERMAP_URL_BASE, params=params_openweathermap + OPENWEATHERMAP_URL_BASE, params=params_openweathermap, timeout=10 ) weather_data.append({"OpenWeatherMap": response_openweathermap.json()}) if WEATHERSTACK_API_KEY: params_weatherstack = {"query": location, "access_key": WEATHERSTACK_API_KEY} response_weatherstack = requests.get( - WEATHERSTACK_URL_BASE, params=params_weatherstack + WEATHERSTACK_URL_BASE, params=params_weatherstack, timeout=10 ) weather_data.append({"Weatherstack": response_weatherstack.json()}) if not weather_data: diff --git a/web_programming/daily_horoscope.py b/web_programming/daily_horoscope.py index b0dd1cd65924..75e637d8e52c 100644 --- a/web_programming/daily_horoscope.py +++ b/web_programming/daily_horoscope.py @@ -7,7 +7,7 @@ def horoscope(zodiac_sign: int, day: str) -> str: "https://www.horoscope.com/us/horoscopes/general/" f"horoscope-general-daily-{day}.aspx?sign={zodiac_sign}" ) - soup = BeautifulSoup(requests.get(url).content, "html.parser") + soup = BeautifulSoup(requests.get(url, timeout=10).content, "html.parser") return soup.find("div", class_="main-horoscope").p.text diff --git a/web_programming/download_images_from_google_query.py b/web_programming/download_images_from_google_query.py index 441347459f8e..235cd35763ef 100644 --- a/web_programming/download_images_from_google_query.py +++ b/web_programming/download_images_from_google_query.py @@ -39,7 +39,9 @@ def download_images_from_google_query(query: str = "dhaka", max_images: int = 5) "ijn": "0", } - html = requests.get("https://www.google.com/search", params=params, headers=headers) + html = requests.get( + "https://www.google.com/search", params=params, headers=headers, timeout=10 + ) soup = BeautifulSoup(html.text, "html.parser") matched_images_data = "".join( re.findall(r"AF_initDataCallback\(([^<]+)\);", str(soup.select("script"))) diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index 26c88e1b13a5..43fd78dcf5a4 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -77,7 +77,7 @@ def emails_from_url(/service/url: str = "https://github.com/") -> list[str]: try: # Open URL - r = requests.get(url) + r = requests.get(url, timeout=10) # pass the raw HTML to the parser to get links parser.feed(r.text) @@ -88,7 +88,7 @@ def emails_from_url(/service/url: str = "https://github.com/") -> list[str]: # open URL. # read = requests.get(link) try: - read = requests.get(link) + read = requests.get(link, timeout=10) # Get the valid email. emails = re.findall("[a-zA-Z0-9]+@" + domain, read.text) # If not in list then append it. diff --git a/web_programming/fetch_anime_and_play.py b/web_programming/fetch_anime_and_play.py index 366807785e85..fd7c3a3a7381 100644 --- a/web_programming/fetch_anime_and_play.py +++ b/web_programming/fetch_anime_and_play.py @@ -28,7 +28,7 @@ def search_scraper(anime_name: str) -> list: search_url = f"{BASE_URL}/search/{anime_name}" response = requests.get( - search_url, headers={"UserAgent": UserAgent().chrome} + search_url, headers={"UserAgent": UserAgent().chrome}, timeout=10 ) # request the url. # Is the response ok? @@ -82,7 +82,9 @@ def search_anime_episode_list(episode_endpoint: str) -> list: request_url = f"{BASE_URL}{episode_endpoint}" - response = requests.get(url=request_url, headers={"UserAgent": UserAgent().chrome}) + response = requests.get( + url=request_url, headers={"UserAgent": UserAgent().chrome}, timeout=10 + ) response.raise_for_status() soup = BeautifulSoup(response.text, "html.parser") @@ -132,7 +134,7 @@ def get_anime_episode(episode_endpoint: str) -> list: episode_page_url = f"{BASE_URL}{episode_endpoint}" response = requests.get( - url=episode_page_url, headers={"User-Agent": UserAgent().chrome} + url=episode_page_url, headers={"User-Agent": UserAgent().chrome}, timeout=10 ) response.raise_for_status() diff --git a/web_programming/fetch_bbc_news.py b/web_programming/fetch_bbc_news.py index 7f8bc57b69f5..e5cd864a9d83 100644 --- a/web_programming/fetch_bbc_news.py +++ b/web_programming/fetch_bbc_news.py @@ -7,7 +7,7 @@ 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() + bbc_news_page = requests.get(_NEWS_API + bbc_news_api_key, timeout=10).json() # each article in the list is a dict for i, article in enumerate(bbc_news_page["articles"], 1): print(f"{i}.) {article['title']}") diff --git a/web_programming/fetch_github_info.py b/web_programming/fetch_github_info.py index 7a4985b68841..25d44245bb58 100644 --- a/web_programming/fetch_github_info.py +++ b/web_programming/fetch_github_info.py @@ -42,7 +42,7 @@ def fetch_github_info(auth_token: str) -> dict[Any, Any]: "Authorization": f"token {auth_token}", "Accept": "application/vnd.github.v3+json", } - return requests.get(AUTHENTICATED_USER_ENDPOINT, headers=headers).json() + return requests.get(AUTHENTICATED_USER_ENDPOINT, headers=headers, timeout=10).json() if __name__ == "__main__": # pragma: no cover diff --git a/web_programming/fetch_jobs.py b/web_programming/fetch_jobs.py index 49abd3c88eec..0d89bf45de57 100644 --- a/web_programming/fetch_jobs.py +++ b/web_programming/fetch_jobs.py @@ -13,7 +13,9 @@ def fetch_jobs(location: str = "mumbai") -> Generator[tuple[str, str], None, None]: - soup = BeautifulSoup(requests.get(url + location).content, "html.parser") + soup = BeautifulSoup( + requests.get(url + location, timeout=10).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() diff --git a/web_programming/fetch_quotes.py b/web_programming/fetch_quotes.py index d557e2d95e74..cf0add43f002 100644 --- a/web_programming/fetch_quotes.py +++ b/web_programming/fetch_quotes.py @@ -14,11 +14,11 @@ def quote_of_the_day() -> list: - return requests.get(API_ENDPOINT_URL + "/today").json() + return requests.get(API_ENDPOINT_URL + "/today", timeout=10).json() def random_quotes() -> list: - return requests.get(API_ENDPOINT_URL + "/random").json() + return requests.get(API_ENDPOINT_URL + "/random", timeout=10).json() if __name__ == "__main__": diff --git a/web_programming/fetch_well_rx_price.py b/web_programming/fetch_well_rx_price.py index ee51b9a5051b..93be2a9235d9 100644 --- a/web_programming/fetch_well_rx_price.py +++ b/web_programming/fetch_well_rx_price.py @@ -42,7 +42,7 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None: return None request_url = BASE_URL.format(drug_name, zip_code) - response = get(request_url) + response = get(request_url, timeout=10) # Is the response ok? response.raise_for_status() diff --git a/web_programming/get_amazon_product_data.py b/web_programming/get_amazon_product_data.py index c2f2ac5ab291..b98ff2c030af 100644 --- a/web_programming/get_amazon_product_data.py +++ b/web_programming/get_amazon_product_data.py @@ -24,7 +24,9 @@ def get_amazon_product_data(product: str = "laptop") -> DataFrame: ), "Accept-Language": "en-US, en;q=0.5", } - soup = BeautifulSoup(requests.get(url, headers=header).text, features="lxml") + soup = BeautifulSoup( + requests.get(url, headers=header, timeout=10).text, features="lxml" + ) # Initialize a Pandas dataframe with the column titles data_frame = DataFrame( columns=[ diff --git a/web_programming/get_imdb_top_250_movies_csv.py b/web_programming/get_imdb_top_250_movies_csv.py index e54b076ebd94..c914b29cb3b3 100644 --- a/web_programming/get_imdb_top_250_movies_csv.py +++ b/web_programming/get_imdb_top_250_movies_csv.py @@ -8,7 +8,7 @@ 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") + soup = BeautifulSoup(requests.get(url, timeout=10).text, "html.parser") titles = soup.find_all("td", attrs="titleColumn") ratings = soup.find_all("td", class_="ratingColumn imdbRating") return { diff --git a/web_programming/get_ip_geolocation.py b/web_programming/get_ip_geolocation.py index 62eaeafceb7e..574d287f0db1 100644 --- a/web_programming/get_ip_geolocation.py +++ b/web_programming/get_ip_geolocation.py @@ -8,7 +8,7 @@ def get_ip_geolocation(ip_address: str) -> str: url = f"https://ipinfo.io/%7Bip_address%7D/json" # Send a GET request to the API - response = requests.get(url) + response = requests.get(url, timeout=10) # Check if the HTTP request was successful response.raise_for_status() diff --git a/web_programming/get_top_billionaires.py b/web_programming/get_top_billionaires.py index 703b635eef82..24828b6d787c 100644 --- a/web_programming/get_top_billionaires.py +++ b/web_programming/get_top_billionaires.py @@ -57,7 +57,7 @@ def get_forbes_real_time_billionaires() -> list[dict[str, int | str]]: Returns: List of top 10 realtime billionaires data. """ - response_json = requests.get(API_URL).json() + response_json = requests.get(API_URL, timeout=10).json() return [ { "Name": person["personName"], diff --git a/web_programming/get_top_hn_posts.py b/web_programming/get_top_hn_posts.py index fbb7c051a88e..f5d4f874c6c6 100644 --- a/web_programming/get_top_hn_posts.py +++ b/web_programming/get_top_hn_posts.py @@ -5,7 +5,7 @@ def get_hackernews_story(story_id: str) -> dict: url = f"https://hacker-news.firebaseio.com/v0/item/%7Bstory_id%7D.json?print=pretty" - return requests.get(url).json() + return requests.get(url, timeout=10).json() def hackernews_top_stories(max_stories: int = 10) -> list[dict]: @@ -13,7 +13,7 @@ def hackernews_top_stories(max_stories: int = 10) -> list[dict]: Get the top max_stories posts from HackerNews - https://news.ycombinator.com/ """ url = "https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty" - story_ids = requests.get(url).json()[:max_stories] + story_ids = requests.get(url, timeout=10).json()[:max_stories] return [get_hackernews_story(story_id) for story_id in story_ids] diff --git a/web_programming/giphy.py b/web_programming/giphy.py index a5c3f8f7493e..2bf3e3ea9c0b 100644 --- a/web_programming/giphy.py +++ b/web_programming/giphy.py @@ -11,7 +11,7 @@ def get_gifs(query: str, api_key: str = giphy_api_key) -> list: """ formatted_query = "+".join(query.split()) url = f"https://api.giphy.com/v1/gifs/search?q={formatted_query}&api_key={api_key}" - gifs = requests.get(url).json()["data"] + gifs = requests.get(url, timeout=10).json()["data"] return [gif["url"] for gif in gifs] diff --git a/web_programming/instagram_crawler.py b/web_programming/instagram_crawler.py index 0816cd181051..df62735fb328 100644 --- a/web_programming/instagram_crawler.py +++ b/web_programming/instagram_crawler.py @@ -39,7 +39,7 @@ def get_json(self) -> dict: """ Return a dict of user information """ - html = requests.get(self.url, headers=headers).text + html = requests.get(self.url, headers=headers, timeout=10).text scripts = BeautifulSoup(html, "html.parser").find_all("script") try: return extract_user_profile(scripts[4]) diff --git a/web_programming/instagram_pic.py b/web_programming/instagram_pic.py index 2d987c1766dc..292cacc16c04 100644 --- a/web_programming/instagram_pic.py +++ b/web_programming/instagram_pic.py @@ -15,7 +15,7 @@ def download_image(url: str) -> str: A message indicating the result of the operation. """ try: - response = requests.get(url) + response = requests.get(url, timeout=10) response.raise_for_status() except requests.exceptions.RequestException as e: return f"An error occurred during the HTTP request to {url}: {e!r}" @@ -30,7 +30,7 @@ def download_image(url: str) -> str: return f"Image URL not found in meta tag {image_meta_tag}." try: - image_data = requests.get(image_url).content + image_data = requests.get(image_url, timeout=10).content except requests.exceptions.RequestException as e: return f"An error occurred during the HTTP request to {image_url}: {e!r}" if not image_data: diff --git a/web_programming/instagram_video.py b/web_programming/instagram_video.py index 1f1b0e297034..a4cddce25138 100644 --- a/web_programming/instagram_video.py +++ b/web_programming/instagram_video.py @@ -5,8 +5,8 @@ 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 + video_url = requests.get(base_url + url, timeout=10).json()[0]["urls"][0]["src"] + return requests.get(video_url, timeout=10).content if __name__ == "__main__": diff --git a/web_programming/nasa_data.py b/web_programming/nasa_data.py index 81125e0a4f05..33a6406c52a6 100644 --- a/web_programming/nasa_data.py +++ b/web_programming/nasa_data.py @@ -9,14 +9,14 @@ def get_apod_data(api_key: str) -> dict: Get your API Key from: https://api.nasa.gov/ """ url = "https://api.nasa.gov/planetary/apod" - return requests.get(url, params={"api_key": api_key}).json() + return requests.get(url, params={"api_key": api_key}, timeout=10).json() def save_apod(api_key: str, path: str = ".") -> dict: apod_data = get_apod_data(api_key) img_url = apod_data["url"] img_name = img_url.split("/")[-1] - response = requests.get(img_url, stream=True) + response = requests.get(img_url, stream=True, timeout=10) with open(f"{path}/{img_name}", "wb+") as img_file: shutil.copyfileobj(response.raw, img_file) @@ -29,7 +29,7 @@ def get_archive_data(query: str) -> dict: Get the data of a particular query from NASA archives """ url = "https://images-api.nasa.gov/search" - return requests.get(url, params={"q": query}).json() + return requests.get(url, params={"q": query}, timeout=10).json() if __name__ == "__main__": diff --git a/web_programming/open_google_results.py b/web_programming/open_google_results.py index f61e3666dd7e..52dd37d7b91a 100644 --- a/web_programming/open_google_results.py +++ b/web_programming/open_google_results.py @@ -16,6 +16,7 @@ res = requests.get( url, headers={"User-Agent": str(UserAgent().random)}, + timeout=10, ) try: diff --git a/web_programming/random_anime_character.py b/web_programming/random_anime_character.py index f15a9c05d9e5..aed932866258 100644 --- a/web_programming/random_anime_character.py +++ b/web_programming/random_anime_character.py @@ -12,7 +12,7 @@ def save_image(image_url: str, image_title: str) -> None: """ Saves the image of anime character """ - image = requests.get(image_url, headers=headers) + image = requests.get(image_url, headers=headers, timeout=10) with open(image_title, "wb") as file: file.write(image.content) @@ -21,7 +21,9 @@ def random_anime_character() -> tuple[str, str, str]: """ Returns the Title, Description, and Image Title of a random anime character . """ - soup = BeautifulSoup(requests.get(URL, headers=headers).text, "html.parser") + soup = BeautifulSoup( + requests.get(URL, headers=headers, timeout=10).text, "html.parser" + ) title = soup.find("meta", attrs={"property": "og:title"}).attrs["content"] image_url = soup.find("meta", attrs={"property": "og:image"}).attrs["content"] description = soup.find("p", id="description").get_text() diff --git a/web_programming/recaptcha_verification.py b/web_programming/recaptcha_verification.py index c9b691b28a8b..168862204fa9 100644 --- a/web_programming/recaptcha_verification.py +++ b/web_programming/recaptcha_verification.py @@ -56,7 +56,9 @@ def login_using_recaptcha(request): 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}) + response = requests.post( + url, data={"secret": secret_key, "response": client_key}, timeout=10 + ) # if the recaptcha api verified our keys if response.json().get("success", False): # authenticate the user diff --git a/web_programming/reddit.py b/web_programming/reddit.py index 1c165ecc49ec..6cc1a6b62009 100644 --- a/web_programming/reddit.py +++ b/web_programming/reddit.py @@ -31,6 +31,7 @@ def get_subreddit_data( response = requests.get( f"https://reddit.com/r/%7Bsubreddit%7D/%7Bage%7D.json?limit={limit}", headers={"User-agent": "A random string"}, + timeout=10, ) if response.status_code == 429: raise requests.HTTPError(response=response) diff --git a/web_programming/search_books_by_isbn.py b/web_programming/search_books_by_isbn.py index 07429e9a9678..6b69018e6639 100644 --- a/web_programming/search_books_by_isbn.py +++ b/web_programming/search_books_by_isbn.py @@ -25,7 +25,7 @@ def get_openlibrary_data(olid: str = "isbn/0140328726") -> dict: if new_olid.count("/") != 1: msg = f"{olid} is not a valid Open Library olid" raise ValueError(msg) - return requests.get(f"https://openlibrary.org/%7Bnew_olid%7D.json").json() + return requests.get(f"https://openlibrary.org/%7Bnew_olid%7D.json", timeout=10).json() def summarize_book(ol_book_data: dict) -> dict: diff --git a/web_programming/slack_message.py b/web_programming/slack_message.py index 5e97d6b64c75..d4d5658898ac 100644 --- a/web_programming/slack_message.py +++ b/web_programming/slack_message.py @@ -5,7 +5,9 @@ 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) + response = requests.post( + slack_url, json={"text": message_body}, headers=headers, timeout=10 + ) if response.status_code != 200: msg = ( "Request to slack returned an error " diff --git a/web_programming/world_covid19_stats.py b/web_programming/world_covid19_stats.py index ca81abdc4ce9..4948d8cfd43c 100644 --- a/web_programming/world_covid19_stats.py +++ b/web_programming/world_covid19_stats.py @@ -13,7 +13,7 @@ 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") + soup = BeautifulSoup(requests.get(url, timeout=10).text, "html.parser") keys = soup.findAll("h1") values = soup.findAll("div", {"class": "maincounter-number"}) keys += soup.findAll("span", {"class": "panel-title"}) From dbfa21813ff6fe2d7b439dfd6daa60b14a64d24f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 21:43:19 +0200 Subject: [PATCH 2717/2908] [pre-commit.ci] pre-commit autoupdate (#11380) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.7 → v0.4.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.7...v0.4.1) - [github.com/tox-dev/pyproject-fmt: 1.7.0 → 1.8.0](https://github.com/tox-dev/pyproject-fmt/compare/1.7.0...1.8.0) * from keras import layers, models * Update lstm_prediction.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- computer_vision/cnn_classification.py | 2 +- machine_learning/lstm/lstm_prediction.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9472bcfa3e07..eedf6d939748 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.7 + rev: v0.4.1 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.7.0" + rev: "1.8.0" hooks: - id: pyproject-fmt diff --git a/computer_vision/cnn_classification.py b/computer_vision/cnn_classification.py index b813b71033f3..115333eba0d1 100644 --- a/computer_vision/cnn_classification.py +++ b/computer_vision/cnn_classification.py @@ -25,7 +25,7 @@ # Importing the Keras libraries and packages import tensorflow as tf -from tensorflow.keras import layers, models +from keras import layers, models if __name__ == "__main__": # Initialising the CNN diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py index f0fd12c9de7f..81ac5f01d3d6 100644 --- a/machine_learning/lstm/lstm_prediction.py +++ b/machine_learning/lstm/lstm_prediction.py @@ -7,9 +7,9 @@ import numpy as np import pandas as pd +from keras.layers import LSTM, Dense +from keras.models import Sequential from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import LSTM, Dense -from tensorflow.keras.models import Sequential if __name__ == "__main__": """ From 79dc7c97acc492d657b5f2f50686cee5b0f64b30 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 22 Apr 2024 22:45:24 +0300 Subject: [PATCH 2718/2908] Enable ruff RUF001 rule (#11378) * Enable ruff RUF001 rule * Fix * Fix --- fuzzy_logic/fuzzy_operations.py | 6 +++--- physics/basic_orbital_capture.py | 6 +++--- physics/malus_law.py | 2 +- pyproject.toml | 1 - 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py index e41cd2120049..c5e4cbde019d 100644 --- a/fuzzy_logic/fuzzy_operations.py +++ b/fuzzy_logic/fuzzy_operations.py @@ -57,7 +57,7 @@ class FuzzySet: # Union Operations >>> siya.union(sheru) - FuzzySet(name='Siya ∪ Sheru', left_boundary=0.4, peak=0.7, right_boundary=1.0) + FuzzySet(name='Siya U Sheru', left_boundary=0.4, peak=0.7, right_boundary=1.0) """ name: str @@ -147,10 +147,10 @@ def union(self, other) -> FuzzySet: FuzzySet: A new fuzzy set representing the union. >>> FuzzySet("a", 0.1, 0.2, 0.3).union(FuzzySet("b", 0.4, 0.5, 0.6)) - FuzzySet(name='a ∪ b', left_boundary=0.1, peak=0.6, right_boundary=0.35) + FuzzySet(name='a U b', left_boundary=0.1, peak=0.6, right_boundary=0.35) """ return FuzzySet( - f"{self.name} ∪ {other.name}", + f"{self.name} U {other.name}", min(self.left_boundary, other.left_boundary), max(self.right_boundary, other.right_boundary), (self.peak + other.peak) / 2, diff --git a/physics/basic_orbital_capture.py b/physics/basic_orbital_capture.py index eeb45e60240c..a5434b5cb7cb 100644 --- a/physics/basic_orbital_capture.py +++ b/physics/basic_orbital_capture.py @@ -4,14 +4,14 @@ """ These two functions will return the radii of impact for a target object -of mass M and radius R as well as it's effective cross sectional area σ(sigma). -That is to say any projectile with velocity v passing within σ, will impact the +of mass M and radius R as well as it's effective cross sectional area sigma. +That is to say any projectile with velocity v passing within sigma, will impact the target object with mass M. The derivation of which is given at the bottom of this file. The derivation shows that a projectile does not need to aim directly at the target body in order to hit it, as R_capture>R_target. Astronomers refer to the effective -cross section for capture as σ=π*R_capture**2. +cross section for capture as sigma=π*R_capture**2. This algorithm does not account for an N-body problem. diff --git a/physics/malus_law.py b/physics/malus_law.py index ae77d45cf614..374b3423f8ff 100644 --- a/physics/malus_law.py +++ b/physics/malus_law.py @@ -31,7 +31,7 @@ Real polarizers are also not perfect blockers of the polarization orthogonal to their polarization axis; the ratio of the transmission of the unwanted component to the wanted component is called the extinction ratio, and varies from around -1:500 for Polaroid to about 1:106 for Glan–Taylor prism polarizers. +1:500 for Polaroid to about 1:106 for Glan-Taylor prism polarizers. Reference : "https://en.wikipedia.org/wiki/Polarizer#Malus's_law_and_other_properties" """ diff --git a/pyproject.toml b/pyproject.toml index ff22fba81c8a..0185f4d7b987 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "PLW2901", # PLW2901: Redefined loop variable -- FIX ME "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception "PT018", # Assertion should be broken down into multiple parts - "RUF001", # String contains ambiguous {}. Did you mean {}? "RUF002", # Docstring contains ambiguous {}. Did you mean {}? "RUF003", # Comment contains ambiguous {}. Did you mean {}? "S101", # Use of `assert` detected -- DO NOT FIX From 4700297b3e332701eed1d0667f3afefc5b9b66be Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 22 Apr 2024 22:51:47 +0300 Subject: [PATCH 2719/2908] Enable ruff RUF002 rule (#11377) * Enable ruff RUF002 rule * Fix --------- Co-authored-by: Christian Clauss --- backtracking/sudoku.py | 4 ++-- .../single_bit_manipulation_operations.py | 14 +++++++------- compression/burrows_wheeler.py | 2 +- compression/lempel_ziv.py | 4 ++-- compression/lempel_ziv_decompress.py | 4 ++-- data_structures/binary_tree/red_black_tree.py | 2 +- digital_image_processing/edge_detection/canny.py | 4 ++-- digital_image_processing/index_calculation.py | 2 +- dynamic_programming/combination_sum_iv.py | 2 +- electronics/coulombs_law.py | 4 ++-- hashes/fletcher16.py | 2 +- linear_algebra/lu_decomposition.py | 2 +- linear_algebra/src/schur_complement.py | 2 +- machine_learning/polynomial_regression.py | 4 ++-- maths/chudnovsky_algorithm.py | 2 +- maths/entropy.py | 4 ++-- maths/lucas_lehmer_primality_test.py | 4 ++-- maths/modular_division.py | 2 +- maths/numerical_analysis/bisection_2.py | 2 +- maths/numerical_analysis/nevilles_method.py | 2 +- maths/simultaneous_linear_equation_solver.py | 6 +++--- matrix/largest_square_area_in_matrix.py | 4 ++-- matrix/spiral_print.py | 2 +- neural_network/back_propagation_neural_network.py | 4 ++-- other/davis_putnam_logemann_loveland.py | 2 +- other/fischer_yates_shuffle.py | 2 +- physics/archimedes_principle_of_buoyant_force.py | 2 +- physics/center_of_mass.py | 8 ++++---- physics/centripetal_force.py | 2 +- physics/lorentz_transformation_four_vector.py | 14 +++++++------- physics/reynolds_number.py | 4 ++-- physics/terminal_velocity.py | 4 ++-- project_euler/problem_004/sol1.py | 2 +- project_euler/problem_004/sol2.py | 2 +- project_euler/problem_008/sol1.py | 2 +- project_euler/problem_008/sol2.py | 2 +- project_euler/problem_008/sol3.py | 2 +- project_euler/problem_015/sol1.py | 4 ++-- project_euler/problem_020/sol1.py | 4 ++-- project_euler/problem_020/sol2.py | 4 ++-- project_euler/problem_020/sol3.py | 4 ++-- project_euler/problem_020/sol4.py | 4 ++-- project_euler/problem_022/sol1.py | 2 +- project_euler/problem_022/sol2.py | 2 +- project_euler/problem_025/sol1.py | 2 +- project_euler/problem_025/sol2.py | 2 +- project_euler/problem_025/sol3.py | 2 +- project_euler/problem_027/sol1.py | 8 ++++---- project_euler/problem_031/sol1.py | 10 +++++----- project_euler/problem_031/sol2.py | 12 ++++++------ project_euler/problem_032/sol32.py | 2 +- project_euler/problem_038/sol1.py | 6 +++--- project_euler/problem_040/sol1.py | 2 +- project_euler/problem_044/sol1.py | 6 +++--- project_euler/problem_045/sol1.py | 4 ++-- project_euler/problem_046/sol1.py | 12 ++++++------ project_euler/problem_047/sol1.py | 10 +++++----- project_euler/problem_053/sol1.py | 2 +- project_euler/problem_097/sol1.py | 4 ++-- project_euler/problem_104/sol1.py | 2 +- project_euler/problem_120/sol1.py | 2 +- project_euler/problem_123/sol1.py | 2 +- project_euler/problem_135/sol1.py | 4 ++-- project_euler/problem_144/sol1.py | 4 ++-- project_euler/problem_174/sol1.py | 2 +- pyproject.toml | 1 + strings/jaro_winkler.py | 2 +- strings/manacher.py | 2 +- strings/prefix_function.py | 2 +- 69 files changed, 132 insertions(+), 131 deletions(-) diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index 8f5459c76d45..cabeebb90433 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -1,7 +1,7 @@ """ -Given a partially filled 9×9 2D array, the objective is to fill a 9×9 +Given a partially filled 9x9 2D array, the objective is to fill a 9x9 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. +and each of the nine 3x3 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 diff --git a/bit_manipulation/single_bit_manipulation_operations.py b/bit_manipulation/single_bit_manipulation_operations.py index b43ff07b776f..fcbf033ccb24 100644 --- a/bit_manipulation/single_bit_manipulation_operations.py +++ b/bit_manipulation/single_bit_manipulation_operations.py @@ -8,8 +8,8 @@ def set_bit(number: int, position: int) -> 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. + Where X is a number with all the bits - zeroes and bit on given + position - one. >>> set_bit(0b1101, 1) # 0b1111 15 @@ -26,8 +26,8 @@ def clear_bit(number: int, position: int) -> 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. + Where X is a number with all the bits - ones and bit on given + position - zero. >>> clear_bit(0b10010, 1) # 0b10000 16 @@ -42,8 +42,8 @@ def flip_bit(number: int, position: int) -> 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. + Where X is a number with all the bits - zeroes and bit on given + position - one. >>> flip_bit(0b101, 1) # 0b111 7 @@ -79,7 +79,7 @@ def get_bit(number: int, position: int) -> int: Get the bit at the given position Details: perform bitwise and for the given number and X, - Where X is a number with all the bits – zeroes and bit on given position – one. + Where X is a number with all the bits - zeroes and bit on given position - one. If the result is not equal to 0, then the bit on the given position is 1, else 0. >>> get_bit(0b1010, 0) diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index ce493a70c8f9..857d677c904e 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -1,7 +1,7 @@ """ https://en.wikipedia.org/wiki/Burrows%E2%80%93Wheeler_transform -The Burrows–Wheeler transform (BWT, also called block-sorting compression) +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 diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py index ac3f0c6cfc06..2751a0ebcdb6 100644 --- a/compression/lempel_ziv.py +++ b/compression/lempel_ziv.py @@ -1,5 +1,5 @@ """ -One of the several implementations of Lempel–Ziv–Welch compression algorithm +One of the several implementations of Lempel-Ziv-Welch compression algorithm https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch """ @@ -43,7 +43,7 @@ def add_key_to_lexicon( def compress_data(data_bits: str) -> str: """ - Compresses given data_bits using Lempel–Ziv–Welch compression algorithm + Compresses given data_bits using Lempel-Ziv-Welch compression algorithm and returns the result as a string """ lexicon = {"0": "0", "1": "1"} diff --git a/compression/lempel_ziv_decompress.py b/compression/lempel_ziv_decompress.py index 0e49c83fb790..225e96236c2c 100644 --- a/compression/lempel_ziv_decompress.py +++ b/compression/lempel_ziv_decompress.py @@ -1,5 +1,5 @@ """ -One of the several implementations of Lempel–Ziv–Welch decompression algorithm +One of the several implementations of Lempel-Ziv-Welch decompression algorithm https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch """ @@ -26,7 +26,7 @@ def read_file_binary(file_path: str) -> str: def decompress_data(data_bits: str) -> str: """ - Decompresses given data_bits using Lempel–Ziv–Welch compression algorithm + Decompresses given data_bits using Lempel-Ziv-Welch compression algorithm and returns the result as a string """ lexicon = {"0": "0", "1": "1"} diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index e68d8d1e3735..a9ecf897c701 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -17,7 +17,7 @@ class RedBlackTree: and slower for reading in the average case, though, because they're both balanced binary search trees, both will get the same asymptotic performance. - To read more about them, https://en.wikipedia.org/wiki/Red–black_tree + 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. """ diff --git a/digital_image_processing/edge_detection/canny.py b/digital_image_processing/edge_detection/canny.py index f8cbeedb3874..944161c31cfc 100644 --- a/digital_image_processing/edge_detection/canny.py +++ b/digital_image_processing/edge_detection/canny.py @@ -74,9 +74,9 @@ def detect_high_low_threshold( image_shape, destination, threshold_low, threshold_high, weak, strong ): """ - High-Low threshold detection. If an edge pixel’s gradient value is higher + 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 + 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. diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index 67830668b0da..988f8e72b9a8 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -182,7 +182,7 @@ def arv12(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) + -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/dynamic_programming/combination_sum_iv.py b/dynamic_programming/combination_sum_iv.py index 4526729b70b7..113c06a27a9e 100644 --- a/dynamic_programming/combination_sum_iv.py +++ b/dynamic_programming/combination_sum_iv.py @@ -18,7 +18,7 @@ The basic idea is to go over recursively to find the way such that the sum of chosen elements is “tar”. For every element, we have two choices 1. Include the element in our set of chosen elements. - 2. Don’t include the element in our set of chosen elements. + 2. Don't include the element in our set of chosen elements. """ diff --git a/electronics/coulombs_law.py b/electronics/coulombs_law.py index 18c1a8179eb6..74bbea5ea8ec 100644 --- a/electronics/coulombs_law.py +++ b/electronics/coulombs_law.py @@ -20,8 +20,8 @@ def couloumbs_law( Reference ---------- - Coulomb (1785) "Premier mémoire sur l’électricité et le magnétisme," - Histoire de l’Académie Royale des Sciences, pp. 569–577. + Coulomb (1785) "Premier mémoire sur l'électricité et le magnétisme," + Histoire de l'Académie Royale des Sciences, pp. 569-577. Parameters ---------- diff --git a/hashes/fletcher16.py b/hashes/fletcher16.py index 7c23c98d72c5..add8e185bc06 100644 --- a/hashes/fletcher16.py +++ b/hashes/fletcher16.py @@ -1,6 +1,6 @@ """ The Fletcher checksum is an algorithm for computing a position-dependent -checksum devised by John G. Fletcher (1934–2012) at Lawrence Livermore Labs +checksum devised by John G. Fletcher (1934-2012) at Lawrence Livermore Labs in the late 1970s.[1] The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated diff --git a/linear_algebra/lu_decomposition.py b/linear_algebra/lu_decomposition.py index 1d364163d9a7..3620674835cd 100644 --- a/linear_algebra/lu_decomposition.py +++ b/linear_algebra/lu_decomposition.py @@ -1,5 +1,5 @@ """ -Lower–upper (LU) decomposition factors a matrix as a product of a lower +Lower-upper (LU) decomposition factors a matrix as a product of a lower triangular matrix and an upper triangular matrix. A square matrix has an LU decomposition under the following conditions: - If the matrix is invertible, then it has an LU decomposition if and only diff --git a/linear_algebra/src/schur_complement.py b/linear_algebra/src/schur_complement.py index 1cc084043856..7c79bb70abfc 100644 --- a/linear_algebra/src/schur_complement.py +++ b/linear_algebra/src/schur_complement.py @@ -18,7 +18,7 @@ def schur_complement( the pseudo_inv argument. Link to Wiki: https://en.wikipedia.org/wiki/Schur_complement - See also Convex Optimization – Boyd and Vandenberghe, A.5.5 + See also Convex Optimization - Boyd and Vandenberghe, A.5.5 >>> import numpy as np >>> a = np.array([[1, 2], [2, 1]]) >>> b = np.array([[0, 3], [3, 0]]) diff --git a/machine_learning/polynomial_regression.py b/machine_learning/polynomial_regression.py index 5bafea96f41e..19f7dc994017 100644 --- a/machine_learning/polynomial_regression.py +++ b/machine_learning/polynomial_regression.py @@ -11,7 +11,7 @@ β = (XᵀX)⁻¹Xᵀy = X⁺y -where X is the design matrix, y is the response vector, and X⁺ denotes the Moore–Penrose +where X is the design matrix, y is the response vector, and X⁺ denotes the Moore-Penrose pseudoinverse of X. In the case of polynomial regression, the design matrix is |1 x₁ x₁² ⋯ x₁ᵐ| @@ -106,7 +106,7 @@ def fit(self, x_train: np.ndarray, y_train: np.ndarray) -> None: β = (XᵀX)⁻¹Xᵀy = X⁺y - where X⁺ denotes the Moore–Penrose pseudoinverse of the design matrix X. This + where X⁺ denotes the Moore-Penrose pseudoinverse of the design matrix X. This function computes X⁺ using singular value decomposition (SVD). References: diff --git a/maths/chudnovsky_algorithm.py b/maths/chudnovsky_algorithm.py index aaee7462822e..d122bf0756f7 100644 --- a/maths/chudnovsky_algorithm.py +++ b/maths/chudnovsky_algorithm.py @@ -5,7 +5,7 @@ def pi(precision: int) -> str: """ The Chudnovsky algorithm is a fast method for calculating the digits of PI, - based on Ramanujan’s PI formulae. + based on Ramanujan's PI formulae. https://en.wikipedia.org/wiki/Chudnovsky_algorithm diff --git a/maths/entropy.py b/maths/entropy.py index 39ec67bea038..b816f1d193f7 100644 --- a/maths/entropy.py +++ b/maths/entropy.py @@ -21,10 +21,10 @@ def calculate_prob(text: str) -> None: :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) + 3) print Entropy of H(X n|Xn-1) Text from random books. Also, random quotes. - >>> text = ("Behind Winston’s back the voice " + >>> text = ("Behind Winston's back the voice " ... "from the telescreen was still " ... "babbling and the overfulfilment") >>> calculate_prob(text) diff --git a/maths/lucas_lehmer_primality_test.py b/maths/lucas_lehmer_primality_test.py index 292387414dee..af5c81133044 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 +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. """ diff --git a/maths/modular_division.py b/maths/modular_division.py index 260d5683705d..2f8f4479b27d 100644 --- a/maths/modular_division.py +++ b/maths/modular_division.py @@ -9,7 +9,7 @@ def modular_division(a: int, b: int, n: int) -> int: 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)). + 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 diff --git a/maths/numerical_analysis/bisection_2.py b/maths/numerical_analysis/bisection_2.py index 45f26d8d88e4..68ba6577ce29 100644 --- a/maths/numerical_analysis/bisection_2.py +++ b/maths/numerical_analysis/bisection_2.py @@ -1,5 +1,5 @@ """ -Given a function on floating number f(x) and two floating numbers ‘a’ and ‘b’ such that +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) diff --git a/maths/numerical_analysis/nevilles_method.py b/maths/numerical_analysis/nevilles_method.py index 256b61f5f218..25c93ac6c531 100644 --- a/maths/numerical_analysis/nevilles_method.py +++ b/maths/numerical_analysis/nevilles_method.py @@ -1,7 +1,7 @@ """ Python program to show how to interpolate and evaluate a polynomial using Neville's method. -Neville’s method evaluates a polynomial that passes through a +Neville's method evaluates a polynomial that passes through a given set of x and y points for a particular x value (x0) using the Newton polynomial form. Reference: diff --git a/maths/simultaneous_linear_equation_solver.py b/maths/simultaneous_linear_equation_solver.py index 1287b2002d00..9685a33e82fe 100644 --- a/maths/simultaneous_linear_equation_solver.py +++ b/maths/simultaneous_linear_equation_solver.py @@ -2,10 +2,10 @@ https://en.wikipedia.org/wiki/Augmented_matrix This algorithm solves simultaneous linear equations of the form -λa + λb + λc + λd + ... = γ as [λ, λ, λ, λ, ..., γ] -Where λ & γ are individual coefficients, the no. of equations = no. of coefficients - 1 +λa + λb + λc + λd + ... = y as [λ, λ, λ, λ, ..., y] +Where λ & y are individual coefficients, the no. of equations = no. of coefficients - 1 -Note in order to work there must exist 1 equation where all instances of λ and γ != 0 +Note in order to work there must exist 1 equation where all instances of λ and y != 0 """ diff --git a/matrix/largest_square_area_in_matrix.py b/matrix/largest_square_area_in_matrix.py index a93369c56bbd..16263fb798f1 100644 --- a/matrix/largest_square_area_in_matrix.py +++ b/matrix/largest_square_area_in_matrix.py @@ -31,7 +31,7 @@ Approach: We initialize another matrix (dp) with the same dimensions -as the original one initialized with all 0’s. +as the original one initialized with all 0's. dp_array(i,j) represents the side length of the maximum square whose bottom right corner is the cell with index (i,j) in the original matrix. @@ -39,7 +39,7 @@ Starting from index (0,0), for every 1 found in the original matrix, we update the value of the current element as -dp_array(i,j)=dp_array(dp(i−1,j),dp_array(i−1,j−1),dp_array(i,j−1)) + 1. +dp_array(i,j)=dp_array(dp(i-1,j),dp_array(i-1,j-1),dp_array(i,j-1)) + 1. """ diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index c16dde69cb56..88bde1db594d 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -89,7 +89,7 @@ def spiral_traversal(matrix: list[list]) -> list[int]: Algorithm: Step 1. first pop the 0 index list. (which is [1,2,3,4] and concatenate the output of [step 2]) - Step 2. Now perform matrix’s Transpose operation (Change rows to column + Step 2. Now perform matrix's Transpose operation (Change rows to column and vice versa) and reverse the resultant matrix. Step 3. Pass the output of [2nd step], to same recursive function till base case hits. diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index 6131a13e945e..182f759c5fc7 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -2,10 +2,10 @@ """ -A Framework of Back Propagation Neural Network(BP) model +A Framework of Back Propagation Neural Network (BP) model Easy to use: - * add many layers as you want !!! + * add many layers as you want ! ! ! * clearly see how the loss decreasing Easy to expand: * more activation functions diff --git a/other/davis_putnam_logemann_loveland.py b/other/davis_putnam_logemann_loveland.py index 3a76f3dfef08..0f3100b1bc2e 100644 --- a/other/davis_putnam_logemann_loveland.py +++ b/other/davis_putnam_logemann_loveland.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ -Davis–Putnam–Logemann–Loveland (DPLL) algorithm is a complete, backtracking-based +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. diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index 37e11479a4c9..5e90b10edd89 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -1,6 +1,6 @@ #!/usr/bin/python """ -The Fisher–Yates shuffle is an algorithm for generating a random permutation of a +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/physics/archimedes_principle_of_buoyant_force.py b/physics/archimedes_principle_of_buoyant_force.py index 71043e0e1111..38f1a0a83832 100644 --- a/physics/archimedes_principle_of_buoyant_force.py +++ b/physics/archimedes_principle_of_buoyant_force.py @@ -3,7 +3,7 @@ fluid. This principle was discovered by the Greek mathematician Archimedes. Equation for calculating buoyant force: -Fb = ρ * V * g +Fb = p * V * g https://en.wikipedia.org/wiki/Archimedes%27_principle """ diff --git a/physics/center_of_mass.py b/physics/center_of_mass.py index 59c3b807f401..7a20e71be801 100644 --- a/physics/center_of_mass.py +++ b/physics/center_of_mass.py @@ -16,8 +16,8 @@ is the particle equivalent of a given object for the application of Newton's laws of motion. -In the case of a system of particles P_i, i = 1, ..., n , each with mass m_i that are -located in space with coordinates r_i, i = 1, ..., n , the coordinates R of the center +In the case of a system of particles P_i, i = 1, ..., n , each with mass m_i that are +located in space with coordinates r_i, i = 1, ..., n , the coordinates R of the center of mass corresponds to: R = (Σ(mi * ri) / Σ(mi)) @@ -36,8 +36,8 @@ def center_of_mass(particles: list[Particle]) -> Coord3D: Input Parameters ---------------- particles: list(Particle): - A list of particles where each particle is a tuple with it´s (x, y, z) position and - it´s mass. + A list of particles where each particle is a tuple with it's (x, y, z) position and + it's mass. Returns ------- diff --git a/physics/centripetal_force.py b/physics/centripetal_force.py index 04069d256468..a4c624582475 100644 --- a/physics/centripetal_force.py +++ b/physics/centripetal_force.py @@ -6,7 +6,7 @@ The unit of centripetal force is newton. The centripetal force is always directed perpendicular to the -direction of the object’s displacement. Using Newton’s second +direction of the object's displacement. Using Newton's second law of motion, it is found that the centripetal force of an object moving in a circular path always acts towards the centre of the circle. The Centripetal Force Formula is given as the product of mass (in kg) diff --git a/physics/lorentz_transformation_four_vector.py b/physics/lorentz_transformation_four_vector.py index f4fda4dff8cd..3b0fd83d45df 100644 --- a/physics/lorentz_transformation_four_vector.py +++ b/physics/lorentz_transformation_four_vector.py @@ -12,13 +12,13 @@ with respect to X, then the Lorentz transformation from X to X' is X' = BX, where - | γ -γβ 0 0| -B = |-γβ γ 0 0| + | y -γβ 0 0| +B = |-γβ y 0 0| | 0 0 1 0| | 0 0 0 1| is the matrix describing the Lorentz boost between X and X', -γ = 1 / √(1 - v²/c²) is the Lorentz factor, and β = v/c is the velocity as +y = 1 / √(1 - v²/c²) is the Lorentz factor, and β = v/c is the velocity as a fraction of c. Reference: https://en.wikipedia.org/wiki/Lorentz_transformation @@ -63,7 +63,7 @@ def beta(velocity: float) -> float: def gamma(velocity: float) -> float: """ - Calculate the Lorentz factor γ = 1 / √(1 - v²/c²) for a given velocity + Calculate the Lorentz factor y = 1 / √(1 - v²/c²) for a given velocity >>> gamma(4) 1.0000000000000002 >>> gamma(1e5) @@ -90,12 +90,12 @@ def transformation_matrix(velocity: float) -> np.ndarray: """ Calculate the Lorentz transformation matrix for movement in the x direction: - | γ -γβ 0 0| - |-γβ γ 0 0| + | y -γβ 0 0| + |-γβ y 0 0| | 0 0 1 0| | 0 0 0 1| - where γ is the Lorentz factor and β is the velocity as a fraction of c + where y is the Lorentz factor and β is the velocity as a fraction of c >>> transformation_matrix(29979245) array([[ 1.00503781, -0.10050378, 0. , 0. ], [-0.10050378, 1.00503781, 0. , 0. ], diff --git a/physics/reynolds_number.py b/physics/reynolds_number.py index dffe690f8822..c24a9e002855 100644 --- a/physics/reynolds_number.py +++ b/physics/reynolds_number.py @@ -8,10 +8,10 @@ viscous forces. R = Inertial Forces / Viscous Forces -R = (ρ * V * D)/μ +R = (p * V * D)/μ where : -ρ = Density of fluid (in Kg/m^3) +p = Density of fluid (in Kg/m^3) D = Diameter of pipe through which fluid flows (in m) V = Velocity of flow of the fluid (in m/s) μ = Viscosity of the fluid (in Ns/m^2) diff --git a/physics/terminal_velocity.py b/physics/terminal_velocity.py index cec54162e2b4..16714bd02671 100644 --- a/physics/terminal_velocity.py +++ b/physics/terminal_velocity.py @@ -8,13 +8,13 @@ object. The acceleration of the object is zero as the net force acting on the object is zero. -Vt = ((2 * m * g)/(ρ * A * Cd))^0.5 +Vt = ((2 * m * g)/(p * A * Cd))^0.5 where : Vt = Terminal velocity (in m/s) m = Mass of the falling object (in Kg) g = Acceleration due to gravity (value taken : imported from scipy) -ρ = Density of the fluid through which the object is falling (in Kg/m^3) +p = Density of the fluid through which the object is falling (in Kg/m^3) A = Projected area of the object (in m^2) Cd = Drag coefficient (dimensionless) diff --git a/project_euler/problem_004/sol1.py b/project_euler/problem_004/sol1.py index f237afdd942d..f80a3253e741 100644 --- a/project_euler/problem_004/sol1.py +++ b/project_euler/problem_004/sol1.py @@ -4,7 +4,7 @@ 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. +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. diff --git a/project_euler/problem_004/sol2.py b/project_euler/problem_004/sol2.py index abc880966d58..1fa75e7d0c83 100644 --- a/project_euler/problem_004/sol2.py +++ b/project_euler/problem_004/sol2.py @@ -4,7 +4,7 @@ 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. +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. diff --git a/project_euler/problem_008/sol1.py b/project_euler/problem_008/sol1.py index 69dd1b4736c1..adbac8d5ad1f 100644 --- a/project_euler/problem_008/sol1.py +++ b/project_euler/problem_008/sol1.py @@ -4,7 +4,7 @@ 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. +product are 9 x 9 x 8 x 9 = 5832. 73167176531330624919225119674426574742355349194934 96983520312774506326239578318016984801869478851843 diff --git a/project_euler/problem_008/sol2.py b/project_euler/problem_008/sol2.py index f83cb1db30b6..e48231e4023b 100644 --- a/project_euler/problem_008/sol2.py +++ b/project_euler/problem_008/sol2.py @@ -4,7 +4,7 @@ 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. +product are 9 x 9 x 8 x 9 = 5832. 73167176531330624919225119674426574742355349194934 96983520312774506326239578318016984801869478851843 diff --git a/project_euler/problem_008/sol3.py b/project_euler/problem_008/sol3.py index bf3bcb05b7e9..0d319b9684dd 100644 --- a/project_euler/problem_008/sol3.py +++ b/project_euler/problem_008/sol3.py @@ -4,7 +4,7 @@ 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. +product are 9 x 9 x 8 x 9 = 5832. 73167176531330624919225119674426574742355349194934 96983520312774506326239578318016984801869478851843 diff --git a/project_euler/problem_015/sol1.py b/project_euler/problem_015/sol1.py index fd9014a406f6..3c9dae1aed77 100644 --- a/project_euler/problem_015/sol1.py +++ b/project_euler/problem_015/sol1.py @@ -1,9 +1,9 @@ """ 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 +Starting in the top left corner of a 2x2 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? +How many such routes are there through a 20x20 grid? """ from math import factorial diff --git a/project_euler/problem_020/sol1.py b/project_euler/problem_020/sol1.py index b472024e54c0..1439bdca38e6 100644 --- a/project_euler/problem_020/sol1.py +++ b/project_euler/problem_020/sol1.py @@ -1,9 +1,9 @@ """ Problem 20: https://projecteuler.net/problem=20 -n! means n × (n − 1) × ... × 3 × 2 × 1 +n! means n x (n - 1) x ... x 3 x 2 x 1 -For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, +For example, 10! = 10 x 9 x ... x 3 x 2 x 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! diff --git a/project_euler/problem_020/sol2.py b/project_euler/problem_020/sol2.py index a1d56ade7708..61684cd5ef6d 100644 --- a/project_euler/problem_020/sol2.py +++ b/project_euler/problem_020/sol2.py @@ -1,9 +1,9 @@ """ Problem 20: https://projecteuler.net/problem=20 -n! means n × (n − 1) × ... × 3 × 2 × 1 +n! means n x (n - 1) x ... x 3 x 2 x 1 -For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, +For example, 10! = 10 x 9 x ... x 3 x 2 x 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! diff --git a/project_euler/problem_020/sol3.py b/project_euler/problem_020/sol3.py index 1886e05463f4..8984def9c34e 100644 --- a/project_euler/problem_020/sol3.py +++ b/project_euler/problem_020/sol3.py @@ -1,9 +1,9 @@ """ Problem 20: https://projecteuler.net/problem=20 -n! means n × (n − 1) × ... × 3 × 2 × 1 +n! means n x (n - 1) x ... x 3 x 2 x 1 -For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, +For example, 10! = 10 x 9 x ... x 3 x 2 x 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! diff --git a/project_euler/problem_020/sol4.py b/project_euler/problem_020/sol4.py index b32ce309dfa6..511ac81e176b 100644 --- a/project_euler/problem_020/sol4.py +++ b/project_euler/problem_020/sol4.py @@ -1,9 +1,9 @@ """ Problem 20: https://projecteuler.net/problem=20 -n! means n × (n − 1) × ... × 3 × 2 × 1 +n! means n x (n - 1) x ... x 3 x 2 x 1 -For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, +For example, 10! = 10 x 9 x ... x 3 x 2 x 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! diff --git a/project_euler/problem_022/sol1.py b/project_euler/problem_022/sol1.py index b6386186e7df..c4af5dfa81df 100644 --- a/project_euler/problem_022/sol1.py +++ b/project_euler/problem_022/sol1.py @@ -10,7 +10,7 @@ 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. +obtain a score of 938 x 53 = 49714. What is the total of all the name scores in the file? """ diff --git a/project_euler/problem_022/sol2.py b/project_euler/problem_022/sol2.py index f7092ea1cd12..9c22b6bba0cc 100644 --- a/project_euler/problem_022/sol2.py +++ b/project_euler/problem_022/sol2.py @@ -10,7 +10,7 @@ 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. +obtain a score of 938 x 53 = 49714. What is the total of all the name scores in the file? """ diff --git a/project_euler/problem_025/sol1.py b/project_euler/problem_025/sol1.py index 803464b5d786..b3bbb56d20be 100644 --- a/project_euler/problem_025/sol1.py +++ b/project_euler/problem_025/sol1.py @@ -1,7 +1,7 @@ """ The Fibonacci sequence is defined by the recurrence relation: - Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. + Fn = Fn-1 + Fn-2, where F1 = 1 and F2 = 1. Hence the first 12 terms will be: diff --git a/project_euler/problem_025/sol2.py b/project_euler/problem_025/sol2.py index 9e950b355f7a..a0f056023bc9 100644 --- a/project_euler/problem_025/sol2.py +++ b/project_euler/problem_025/sol2.py @@ -1,7 +1,7 @@ """ The Fibonacci sequence is defined by the recurrence relation: - Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. + Fn = Fn-1 + Fn-2, where F1 = 1 and F2 = 1. Hence the first 12 terms will be: diff --git a/project_euler/problem_025/sol3.py b/project_euler/problem_025/sol3.py index 0b9f3a0c84ef..e33b159ac65c 100644 --- a/project_euler/problem_025/sol3.py +++ b/project_euler/problem_025/sol3.py @@ -1,7 +1,7 @@ """ The Fibonacci sequence is defined by the recurrence relation: - Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. + Fn = Fn-1 + Fn-2, where F1 = 1 and F2 = 1. Hence the first 12 terms will be: diff --git a/project_euler/problem_027/sol1.py b/project_euler/problem_027/sol1.py index c93e2b4fa251..48755ec19763 100644 --- a/project_euler/problem_027/sol1.py +++ b/project_euler/problem_027/sol1.py @@ -9,12 +9,12 @@ 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. +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 +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. diff --git a/project_euler/problem_031/sol1.py b/project_euler/problem_031/sol1.py index ba40cf383175..4c9c533eecb7 100644 --- a/project_euler/problem_031/sol1.py +++ b/project_euler/problem_031/sol1.py @@ -2,14 +2,14 @@ Coin sums Problem 31: https://projecteuler.net/problem=31 -In England the currency is made up of pound, £, and pence, p, and there are +In England the currency is made up of pound, f, 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: +1p, 2p, 5p, 10p, 20p, 50p, f1 (100p) and f2 (200p). +It is possible to make f2 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? +1xf1 + 1x50p + 2x20p + 1x5p + 1x2p + 3x1p +How many different ways can f2 be made using any number of coins? """ diff --git a/project_euler/problem_031/sol2.py b/project_euler/problem_031/sol2.py index f9e4dc384bff..574f8d4107a1 100644 --- a/project_euler/problem_031/sol2.py +++ b/project_euler/problem_031/sol2.py @@ -3,17 +3,17 @@ Coin sums -In England the currency is made up of pound, £, and pence, p, and there are +In England the currency is made up of pound, f, 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: +1p, 2p, 5p, 10p, 20p, 50p, f1 (100p) and f2 (200p). +It is possible to make f2 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? +1xf1 + 1x50p + 2x20p + 1x5p + 1x2p + 3x1p +How many different ways can f2 be made using any number of coins? Hint: - > There are 100 pence in a pound (£1 = 100p) + > There are 100 pence in a pound (f1 = 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. diff --git a/project_euler/problem_032/sol32.py b/project_euler/problem_032/sol32.py index a402b5584061..c0ca2ce10791 100644 --- a/project_euler/problem_032/sol32.py +++ b/project_euler/problem_032/sol32.py @@ -3,7 +3,7 @@ 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 +The product 7254 is unusual, as the identity, 39 x 186 = 7254, containing multiplicand, multiplier, and product is 1 through 9 pandigital. Find the sum of all products whose multiplicand/multiplier/product identity can diff --git a/project_euler/problem_038/sol1.py b/project_euler/problem_038/sol1.py index 5bef273ea2a9..382892723b7d 100644 --- a/project_euler/problem_038/sol1.py +++ b/project_euler/problem_038/sol1.py @@ -3,9 +3,9 @@ Take the number 192 and multiply it by each of 1, 2, and 3: -192 × 1 = 192 -192 × 2 = 384 -192 × 3 = 576 +192 x 1 = 192 +192 x 2 = 384 +192 x 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) diff --git a/project_euler/problem_040/sol1.py b/project_euler/problem_040/sol1.py index 69be377723a5..721bd063c28a 100644 --- a/project_euler/problem_040/sol1.py +++ b/project_euler/problem_040/sol1.py @@ -11,7 +11,7 @@ If dn represents the nth digit of the fractional part, find the value of the following expression. -d1 × d10 × d100 × d1000 × d10000 × d100000 × d1000000 +d1 x d10 x d100 x d1000 x d10000 x d100000 x d1000000 """ diff --git a/project_euler/problem_044/sol1.py b/project_euler/problem_044/sol1.py index 3b75b6a56a8e..2613563a4bf1 100644 --- a/project_euler/problem_044/sol1.py +++ b/project_euler/problem_044/sol1.py @@ -1,14 +1,14 @@ """ 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 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. +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? +are pentagonal and D = |Pk - Pj| is minimised; what is the value of D? """ diff --git a/project_euler/problem_045/sol1.py b/project_euler/problem_045/sol1.py index d921b2802c2d..8d016de6e542 100644 --- a/project_euler/problem_045/sol1.py +++ b/project_euler/problem_045/sol1.py @@ -3,8 +3,8 @@ 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, ... +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. diff --git a/project_euler/problem_046/sol1.py b/project_euler/problem_046/sol1.py index 07dd9bbf84c8..f27f658e63e5 100644 --- a/project_euler/problem_046/sol1.py +++ b/project_euler/problem_046/sol1.py @@ -4,12 +4,12 @@ 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 +9 = 7 + 2 x 12 +15 = 7 + 2 x 22 +21 = 3 + 2 x 32 +25 = 7 + 2 x 32 +27 = 19 + 2 x 22 +33 = 31 + 2 x 12 It turns out that the conjecture was false. diff --git a/project_euler/problem_047/sol1.py b/project_euler/problem_047/sol1.py index 1287e0d9e107..c9c44a9832dd 100644 --- a/project_euler/problem_047/sol1.py +++ b/project_euler/problem_047/sol1.py @@ -5,14 +5,14 @@ The first two consecutive numbers to have two distinct prime factors are: -14 = 2 × 7 -15 = 3 × 5 +14 = 2 x 7 +15 = 3 x 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. +644 = 2² x 7 x 23 +645 = 3 x 5 x 43 +646 = 2 x 17 x 19. Find the first four consecutive integers to have four distinct prime factors each. What is the first of these numbers? diff --git a/project_euler/problem_053/sol1.py b/project_euler/problem_053/sol1.py index a32b73c545d6..192cbf25e50c 100644 --- a/project_euler/problem_053/sol1.py +++ b/project_euler/problem_053/sol1.py @@ -10,7 +10,7 @@ In general, -nCr = n!/(r!(n−r)!),where r ≤ n, n! = n×(n−1)×...×3×2×1, and 0! = 1. +nCr = n!/(r!(n-r)!),where r ≤ n, n! = nx(n-1)x...x3x2x1, 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 diff --git a/project_euler/problem_097/sol1.py b/project_euler/problem_097/sol1.py index 2807e893ded0..a349f3a1dbc9 100644 --- a/project_euler/problem_097/sol1.py +++ b/project_euler/problem_097/sol1.py @@ -1,7 +1,7 @@ """ 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 +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)). diff --git a/project_euler/problem_104/sol1.py b/project_euler/problem_104/sol1.py index d84dbcfc9c65..a0267faa6a38 100644 --- a/project_euler/problem_104/sol1.py +++ b/project_euler/problem_104/sol1.py @@ -3,7 +3,7 @@ The Fibonacci sequence is defined by the recurrence relation: -Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. +Fn = Fn-1 + Fn-2, where F1 = 1 and F2 = 1. It turns out that F541, which contains 113 digits, is the first Fibonacci number for which the last nine digits are 1-9 pandigital (contain all the digits 1 to 9, but not necessarily in order). And F2749, which contains 575 digits, is the first diff --git a/project_euler/problem_120/sol1.py b/project_euler/problem_120/sol1.py index 0e6821214560..2f403972502f 100644 --- a/project_euler/problem_120/sol1.py +++ b/project_euler/problem_120/sol1.py @@ -3,7 +3,7 @@ Description: -Let r be the remainder when (a−1)^n + (a+1)^n is divided by a^2. +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. diff --git a/project_euler/problem_123/sol1.py b/project_euler/problem_123/sol1.py index 7239e13a51e9..3dd31a2e8505 100644 --- a/project_euler/problem_123/sol1.py +++ b/project_euler/problem_123/sol1.py @@ -4,7 +4,7 @@ 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. +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. diff --git a/project_euler/problem_135/sol1.py b/project_euler/problem_135/sol1.py index ac91fa4e2b9d..d57ace489191 100644 --- a/project_euler/problem_135/sol1.py +++ b/project_euler/problem_135/sol1.py @@ -3,9 +3,9 @@ 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: +x2 - y2 - z2 = n, has exactly two solutions is n = 27: -342 − 272 − 202 = 122 − 92 − 62 = 27 +342 - 272 - 202 = 122 - 92 - 62 = 27 It turns out that n = 1155 is the least value which has exactly ten solutions. diff --git a/project_euler/problem_144/sol1.py b/project_euler/problem_144/sol1.py index bc16bf985f41..9070455de79f 100644 --- a/project_euler/problem_144/sol1.py +++ b/project_euler/problem_144/sol1.py @@ -6,7 +6,7 @@ The specific white cell we will be considering is an ellipse with the equation 4x^2 + y^2 = 100 -The section corresponding to −0.01 ≤ x ≤ +0.01 at the top is missing, allowing the +The section corresponding to -0.01 ≤ x ≤ +0.01 at the top is missing, allowing the light to enter and exit through the hole.  The light beam in this problem starts at the point (0.0,10.1) just outside the white @@ -20,7 +20,7 @@ the laser beam and the wall of the white cell; the blue line shows the line tangent to the ellipse at the point of incidence of the first bounce. -The slope m of the tangent line at any point (x,y) of the given ellipse is: m = −4x/y +The slope m of the tangent line at any point (x,y) of the given ellipse is: m = -4x/y The normal line is perpendicular to this tangent line at the point of incidence. diff --git a/project_euler/problem_174/sol1.py b/project_euler/problem_174/sol1.py index 33c1b158adbb..9a75e8638880 100644 --- a/project_euler/problem_174/sol1.py +++ b/project_euler/problem_174/sol1.py @@ -14,7 +14,7 @@ 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? +What is sum N(n) for 1 ≤ n ≤ 10? """ from collections import defaultdict diff --git a/pyproject.toml b/pyproject.toml index 0185f4d7b987..ff22fba81c8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "PLW2901", # PLW2901: Redefined loop variable -- FIX ME "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception "PT018", # Assertion should be broken down into multiple parts + "RUF001", # String contains ambiguous {}. Did you mean {}? "RUF002", # Docstring contains ambiguous {}. Did you mean {}? "RUF003", # Comment contains ambiguous {}. Did you mean {}? "S101", # Use of `assert` detected -- DO NOT FIX diff --git a/strings/jaro_winkler.py b/strings/jaro_winkler.py index c18f0d85d9f4..cae2068fabc1 100644 --- a/strings/jaro_winkler.py +++ b/strings/jaro_winkler.py @@ -3,7 +3,7 @@ def jaro_winkler(str1: str, str2: str) -> float: """ - Jaro–Winkler distance is a string metric measuring an edit distance between two + Jaro-Winkler distance is a string metric measuring an edit distance between two sequences. Output value is between 0.0 and 1.0. diff --git a/strings/manacher.py b/strings/manacher.py index fc8b01cd9c1c..af1b10cf81fb 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -5,7 +5,7 @@ def palindromic_string(input_string: str) -> str: >>> palindromic_string('ababa') 'ababa' - Manacher’s algorithm which finds Longest palindromic Substring in linear time. + 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. diff --git a/strings/prefix_function.py b/strings/prefix_function.py index 65bbe9100735..04987deef469 100644 --- a/strings/prefix_function.py +++ b/strings/prefix_function.py @@ -1,7 +1,7 @@ """ https://cp-algorithms.com/string/prefix-function.html -Prefix function Knuth–Morris–Pratt algorithm +Prefix function Knuth-Morris-Pratt algorithm Different algorithm than Knuth-Morris-Pratt pattern finding From d016fda51c08a604738e556a7ccb19e0f9c81dcb Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 22 Apr 2024 22:56:14 +0300 Subject: [PATCH 2720/2908] Enable ruff RUF003 rule (#11376) * Enable ruff RUF003 rule * Update pyproject.toml --------- Co-authored-by: Christian Clauss --- dynamic_programming/fast_fibonacci.py | 2 +- graphs/ant_colony_optimization_algorithms.py | 4 ++-- machine_learning/polynomial_regression.py | 2 +- pyproject.toml | 3 --- strings/credit_card_validator.py | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dynamic_programming/fast_fibonacci.py b/dynamic_programming/fast_fibonacci.py index 9f956ca2f979..d04a5ac8249b 100644 --- a/dynamic_programming/fast_fibonacci.py +++ b/dynamic_programming/fast_fibonacci.py @@ -26,7 +26,7 @@ def _fib(n: int) -> tuple[int, int]: if n == 0: # (F(0), F(1)) return (0, 1) - # F(2n) = F(n)[2F(n+1) − F(n)] + # 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) diff --git a/graphs/ant_colony_optimization_algorithms.py b/graphs/ant_colony_optimization_algorithms.py index 652ad6144297..13637da44874 100644 --- a/graphs/ant_colony_optimization_algorithms.py +++ b/graphs/ant_colony_optimization_algorithms.py @@ -33,7 +33,7 @@ def main( pheromone_evaporation: float, alpha: float, beta: float, - q: float, # Pheromone system parameters Q,which is a constant + q: float, # Pheromone system parameters Q, which is a constant ) -> tuple[list[int], float]: """ Ant colony algorithm main function @@ -117,7 +117,7 @@ def pheromone_update( cities: dict[int, list[int]], pheromone_evaporation: float, ants_route: list[list[int]], - q: float, # Pheromone system parameters Q,which is a constant + q: float, # Pheromone system parameters Q, which is a constant best_path: list[int], best_distance: float, ) -> tuple[list[list[float]], list[int], float]: diff --git a/machine_learning/polynomial_regression.py b/machine_learning/polynomial_regression.py index 19f7dc994017..212f40bea197 100644 --- a/machine_learning/polynomial_regression.py +++ b/machine_learning/polynomial_regression.py @@ -146,7 +146,7 @@ def fit(self, x_train: np.ndarray, y_train: np.ndarray) -> None: "Design matrix is not full rank, can't compute coefficients" ) - # np.linalg.pinv() computes the Moore–Penrose pseudoinverse using SVD + # np.linalg.pinv() computes the Moore-Penrose pseudoinverse using SVD self.params = np.linalg.pinv(X) @ y_train def predict(self, data: np.ndarray) -> np.ndarray: diff --git a/pyproject.toml b/pyproject.toml index ff22fba81c8a..1134b773308e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,9 +10,6 @@ lint.ignore = [ # `ruff rule S101` for a description of that rule "PLW2901", # PLW2901: Redefined loop variable -- FIX ME "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception "PT018", # Assertion should be broken down into multiple parts - "RUF001", # String contains ambiguous {}. Did you mean {}? - "RUF002", # Docstring contains ambiguous {}. Did you mean {}? - "RUF003", # Comment contains ambiguous {}. Did you mean {}? "S101", # Use of `assert` detected -- DO NOT FIX "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME "SLF001", # Private member accessed: `_Iterator` -- FIX ME diff --git a/strings/credit_card_validator.py b/strings/credit_card_validator.py index 78bf45740a63..b8da1c745124 100644 --- a/strings/credit_card_validator.py +++ b/strings/credit_card_validator.py @@ -36,7 +36,7 @@ def luhn_validation(credit_card_number: str) -> bool: digit = int(cc_number[i]) digit *= 2 # If doubling of a number results in a two digit number - # i.e greater than 9(e.g., 6 × 2 = 12), + # i.e greater than 9(e.g., 6 x 2 = 12), # then add the digits of the product (e.g., 12: 1 + 2 = 3, 15: 1 + 5 = 6), # to get a single digit number. if digit > 9: From 3925b8155bebd84eababfba0f5a12e5129cfaa44 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 24 Apr 2024 07:32:25 +0300 Subject: [PATCH 2721/2908] Fix ARG005 per file ignore (#11383) --- machine_learning/linear_discriminant_analysis.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 606e11f3698e..86f28aef671a 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -256,7 +256,7 @@ 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, + condition: Callable[[num], bool] = lambda _: True, default: str | None = None, ) -> num: """ diff --git a/pyproject.toml b/pyproject.toml index 1134b773308e..37ebeeb9ce37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,6 @@ max-complexity = 17 # default: 10 "graphs/minimum_spanning_tree_prims.py" = ["SIM114"] "hashes/enigma_machine.py" = ["BLE001"] "machine_learning/decision_tree.py" = ["SIM114"] -"machine_learning/linear_discriminant_analysis.py" = ["ARG005"] "machine_learning/sequential_minimum_optimization.py" = ["SIM115"] "matrix/sherman_morrison.py" = ["SIM103", "SIM114"] "other/l*u_cache.py" = ["RUF012"] From 2d6be5fbb0be2b738d2c246138db9ccda9b6a853 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 30 Apr 2024 07:40:26 +0300 Subject: [PATCH 2722/2908] Enable ruff UP031 rule (#11388) --- data_structures/arrays/sudoku_solver.py | 4 ++-- neural_network/input_data.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index 5c1cff06f9d4..a8157a520c97 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -150,7 +150,7 @@ def time_solve(grid): display(grid_values(grid)) if values: display(values) - print("(%.5f seconds)\n" % t) + print(f"({t:.5f} seconds)\n") return (t, solved(values)) times, results = zip(*[time_solve(grid) for grid in grids]) @@ -217,4 +217,4 @@ def shuffled(seq): start = time.monotonic() solve(puzzle) t = time.monotonic() - start - print("Solved: %.5f sec" % t) + print(f"Solved: {t:.5f} sec") diff --git a/neural_network/input_data.py b/neural_network/input_data.py index d189e3f9e0d9..f90287fe3f5b 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -156,7 +156,8 @@ def __init__( self._rng = np.random.default_rng(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) + msg = f"Invalid image dtype {dtype!r}, expected uint8 or float32" + raise TypeError(msg) if fake_data: self._num_examples = 10000 self.one_hot = one_hot From a7e0b141d8eac30e8f9c4f01c3050e6cdb90f7d4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 06:58:03 +0200 Subject: [PATCH 2723/2908] [pre-commit.ci] pre-commit autoupdate (#11387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/MarcoGorelli/auto-walrus: 0.3.3 → 0.3.4](https://github.com/MarcoGorelli/auto-walrus/compare/0.3.3...0.3.4) - [github.com/astral-sh/ruff-pre-commit: v0.4.1 → v0.4.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.1...v0.4.2) - [github.com/pre-commit/mirrors-mypy: v1.9.0 → v1.10.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.9.0...v1.10.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eedf6d939748..744efc55f41b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/MarcoGorelli/auto-walrus - rev: 0.3.3 + rev: 0.3.4 hooks: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.1 + rev: v0.4.2 hooks: - id: ruff - id: ruff-format @@ -47,7 +47,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 + rev: v1.10.0 hooks: - id: mypy args: From c026b1952f92836c58e63017f4c75e76c43448a1 Mon Sep 17 00:00:00 2001 From: Margaret <62753112+meg-1@users.noreply.github.com> Date: Wed, 1 May 2024 13:42:54 +0300 Subject: [PATCH 2724/2908] adding a matrix equalization algorithm (#11360) * adding a matrix equalization algorithm * Adding url for more details * Implementing suggestions --- matrix/matrix_equalization.py | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 matrix/matrix_equalization.py diff --git a/matrix/matrix_equalization.py b/matrix/matrix_equalization.py new file mode 100644 index 000000000000..e7e76505cf63 --- /dev/null +++ b/matrix/matrix_equalization.py @@ -0,0 +1,55 @@ +from sys import maxsize + + +def array_equalization(vector: list[int], step_size: int) -> int: + """ + This algorithm equalizes all elements of the input vector + to a common value, by making the minimal number of + "updates" under the constraint of a step size (step_size). + + >>> array_equalization([1, 1, 6, 2, 4, 6, 5, 1, 7, 2, 2, 1, 7, 2, 2], 4) + 4 + >>> array_equalization([22, 81, 88, 71, 22, 81, 632, 81, 81, 22, 92], 2) + 5 + >>> array_equalization([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 5) + 0 + >>> array_equalization([22, 22, 22, 33, 33, 33], 2) + 2 + >>> array_equalization([1, 2, 3], 0) + Traceback (most recent call last): + ValueError: Step size must be positive and non-zero. + >>> array_equalization([1, 2, 3], -1) + Traceback (most recent call last): + ValueError: Step size must be positive and non-zero. + >>> array_equalization([1, 2, 3], 0.5) + Traceback (most recent call last): + ValueError: Step size must be an integer. + >>> array_equalization([1, 2, 3], maxsize) + 1 + """ + if step_size <= 0: + raise ValueError("Step size must be positive and non-zero.") + if not isinstance(step_size, int): + raise ValueError("Step size must be an integer.") + + unique_elements = set(vector) + min_updates = maxsize + + for element in unique_elements: + elem_index = 0 + updates = 0 + while elem_index < len(vector): + if vector[elem_index] != element: + updates += 1 + elem_index += step_size + else: + elem_index += 1 + min_updates = min(min_updates, updates) + + return min_updates + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 5131e3145dcec9e232c8e8a807ad387f4f9a3d38 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 1 May 2024 22:27:59 +0300 Subject: [PATCH 2725/2908] Fix some ARG002 per file ignores (#11382) * Fix some ARG002 per file ignores * Fix * updating DIRECTORY.md * Fix review issue * Fix review issue --------- Co-authored-by: MaximSmolskiy --- DIRECTORY.md | 1 + audio_filters/show_response.py | 3 ++- data_structures/hashing/hash_table.py | 3 +++ data_structures/hashing/quadratic_probing.py | 2 +- pyproject.toml | 3 --- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f6d6cb463faa..4a053a3f1b7f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -773,6 +773,7 @@ * [Inverse Of Matrix](matrix/inverse_of_matrix.py) * [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py) * [Matrix Class](matrix/matrix_class.py) + * [Matrix Equalization](matrix/matrix_equalization.py) * [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py) * [Matrix Operation](matrix/matrix_operation.py) * [Max Area Of Island](matrix/max_area_of_island.py) diff --git a/audio_filters/show_response.py b/audio_filters/show_response.py index 097b8152b4e6..f9c9537c047c 100644 --- a/audio_filters/show_response.py +++ b/audio_filters/show_response.py @@ -1,5 +1,6 @@ from __future__ import annotations +from abc import abstractmethod from math import pi from typing import Protocol @@ -8,6 +9,7 @@ class FilterType(Protocol): + @abstractmethod def process(self, sample: float) -> float: """ Calculate y[n] @@ -15,7 +17,6 @@ def process(self, sample: float) -> float: >>> issubclass(FilterType, Protocol) True """ - return 0.0 def get_bounds( diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 7fe57068f6a3..40fcad9a3dab 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +from abc import abstractmethod + from .number_theory.prime_numbers import next_prime @@ -173,6 +175,7 @@ def _set_value(self, key, data): self.values[key] = data self._keys[key] = data + @abstractmethod def _collision_resolution(self, key, data=None): """ This method is a type of open addressing which is used for handling collision. diff --git a/data_structures/hashing/quadratic_probing.py b/data_structures/hashing/quadratic_probing.py index 2f3401ec8918..56d4926eee9b 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 _collision_resolution(self, key, data=None): + def _collision_resolution(self, key, data=None): # noqa: ARG002 """ Quadratic probing is an open addressing scheme used for resolving collisions in hash table. diff --git a/pyproject.toml b/pyproject.toml index 37ebeeb9ce37..4c512ca896b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,11 +76,8 @@ max-complexity = 17 # default: 10 [tool.ruff.lint.per-file-ignores] "arithmetic_analysis/newton_raphson.py" = ["PGH001"] -"audio_filters/show_response.py" = ["ARG002"] "data_structures/binary_tree/binary_search_tree_recursive.py" = ["BLE001"] "data_structures/binary_tree/treap.py" = ["SIM114"] -"data_structures/hashing/hash_table.py" = ["ARG002"] -"data_structures/hashing/quadratic_probing.py" = ["ARG002"] "data_structures/hashing/tests/test_hash_map.py" = ["BLE001"] "data_structures/heap/max_heap.py" = ["SIM114"] "graphs/minimum_spanning_tree_prims.py" = ["SIM114"] From ea53051576a9c5e7398ca2ae6a0823ca54ac3947 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Fri, 3 May 2024 00:43:59 +0800 Subject: [PATCH 2726/2908] Use `spawn` start method in multiprocessing programs (#11391) * Use `spawn` start method in multiprocessing programs * Set `spawn` start method in doctest * Use `with` statement for locks * Pass multiprocessing context explicitly --- sorts/odd_even_transposition_parallel.py | 79 ++++++++++++++++-------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index 9d2bcdbd7576..5d4e09b211c0 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -11,11 +11,11 @@ synchronization could be used. """ -from multiprocessing import Lock, Pipe, Process +import multiprocessing as mp # lock used to ensure that two processes do not access a pipe at the same time # NOTE This breaks testing on build runner. May work better locally -# process_lock = Lock() +# process_lock = mp.Lock() """ The function run by the processes that sorts the list @@ -29,8 +29,17 @@ """ -def oe_process(position, value, l_send, r_send, lr_cv, rr_cv, result_pipe): - process_lock = Lock() +def oe_process( + position, + value, + l_send, + r_send, + lr_cv, + rr_cv, + result_pipe, + multiprocessing_context, +): + process_lock = multiprocessing_context.Lock() # 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 @@ -38,27 +47,23 @@ def oe_process(position, value, l_send, r_send, lr_cv, rr_cv, result_pipe): for i in range(10): if (i + position) % 2 == 0 and r_send is not None: # send your value to your right neighbor - process_lock.acquire() - r_send[1].send(value) - process_lock.release() + with process_lock: + r_send[1].send(value) # receive your right neighbor's value - process_lock.acquire() - temp = rr_cv[0].recv() - process_lock.release() + with process_lock: + temp = rr_cv[0].recv() # take the lower value since you are on the left value = min(value, temp) elif (i + position) % 2 != 0 and l_send is not None: # send your value to your left neighbor - process_lock.acquire() - l_send[1].send(value) - process_lock.release() + with process_lock: + l_send[1].send(value) # receive your left neighbor's value - process_lock.acquire() - temp = lr_cv[0].recv() - process_lock.release() + with process_lock: + temp = lr_cv[0].recv() # take the higher value since you are on the right value = max(value, temp) @@ -94,39 +99,60 @@ def odd_even_transposition(arr): >>> odd_even_transposition(unsorted_list) == sorted(unsorted_list + [1]) False """ + # spawn method is considered safer than fork + multiprocessing_context = mp.get_context("spawn") + process_array_ = [] result_pipe = [] # initialize the list of pipes where the values will be retrieved for _ in arr: - result_pipe.append(Pipe()) + result_pipe.append(multiprocessing_context.Pipe()) # creates the processes # the first and last process only have one neighbor so they are made outside # of the loop - temp_rs = Pipe() - temp_rr = Pipe() + temp_rs = multiprocessing_context.Pipe() + temp_rr = multiprocessing_context.Pipe() process_array_.append( - Process( + multiprocessing_context.Process( target=oe_process, - args=(0, arr[0], None, temp_rs, None, temp_rr, result_pipe[0]), + args=( + 0, + arr[0], + None, + temp_rs, + None, + temp_rr, + result_pipe[0], + multiprocessing_context, + ), ) ) temp_lr = temp_rs temp_ls = temp_rr for i in range(1, len(arr) - 1): - temp_rs = Pipe() - temp_rr = Pipe() + temp_rs = multiprocessing_context.Pipe() + temp_rr = multiprocessing_context.Pipe() process_array_.append( - Process( + multiprocessing_context.Process( target=oe_process, - args=(i, arr[i], temp_ls, temp_rs, temp_lr, temp_rr, result_pipe[i]), + args=( + i, + arr[i], + temp_ls, + temp_rs, + temp_lr, + temp_rr, + result_pipe[i], + multiprocessing_context, + ), ) ) temp_lr = temp_rs temp_ls = temp_rr process_array_.append( - Process( + multiprocessing_context.Process( target=oe_process, args=( len(arr) - 1, @@ -136,6 +162,7 @@ def odd_even_transposition(arr): temp_lr, None, result_pipe[len(arr) - 1], + multiprocessing_context, ), ) ) From 1868c0b6375188a9034478a2711e40c343d00c2e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 21:38:58 +0200 Subject: [PATCH 2727/2908] [pre-commit.ci] pre-commit autoupdate (#11394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.2 → v0.4.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.2...v0.4.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 744efc55f41b..210b7494036e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.2 + rev: v0.4.3 hooks: - id: ruff - id: ruff-format From c599f6c9107a1b09c08ddce17053d7b5d0895a83 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Fri, 10 May 2024 22:59:53 +0300 Subject: [PATCH 2728/2908] Fix some SIM114 per file ignores (#11395) * updating DIRECTORY.md * Fix some SIM114 per file ignores * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix review issue --------- Co-authored-by: MaximSmolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/binary_tree/treap.py | 4 +--- data_structures/heap/max_heap.py | 2 +- graphs/minimum_spanning_tree_prims.py | 2 +- machine_learning/decision_tree.py | 2 +- matrix/sherman_morrison.py | 2 +- pyproject.toml | 6 +----- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index e7ddf931b83a..3114c6fa1c26 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -39,9 +39,7 @@ def split(root: Node | None, value: int) -> tuple[Node | None, Node | None]: 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 - elif root.value is None: + if root is None or root.value is None: # None tree is split into 2 Nones return None, None elif value < root.value: """ diff --git a/data_structures/heap/max_heap.py b/data_structures/heap/max_heap.py index 5a9f9cf88433..589f2595a8da 100644 --- a/data_structures/heap/max_heap.py +++ b/data_structures/heap/max_heap.py @@ -38,7 +38,7 @@ def insert(self, value: int) -> None: def __swap_down(self, i: int) -> None: """Swap the element down""" while self.__size >= 2 * i: - if 2 * i + 1 > self.__size: + if 2 * i + 1 > self.__size: # noqa: SIM114 bigger_child = 2 * i elif self.__heap[2 * i] > self.__heap[2 * i + 1]: bigger_child = 2 * i diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 90c9f4c91e86..d0b45d7ef139 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -16,7 +16,7 @@ def top_to_bottom(self, heap, start, size, positions): if start > size // 2 - 1: return else: - if 2 * start + 2 >= size: + if 2 * start + 2 >= size: # noqa: SIM114 smallest_child = 2 * start + 1 elif heap[2 * start + 1] < heap[2 * start + 2]: smallest_child = 2 * start + 1 diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index e48905eeac6a..d0bd6ab0b555 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -105,7 +105,7 @@ def train(self, x, y): the predictor """ for i in range(len(x)): - if len(x[:i]) < self.min_leaf_size: + if len(x[:i]) < self.min_leaf_size: # noqa: SIM114 continue elif len(x[i:]) < self.min_leaf_size: continue diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 7f10ae706e85..e2a09c1d0070 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -65,7 +65,7 @@ def validate_indices(self, loc: tuple[int, int]) -> bool: >>> a.validate_indices((0, 0)) True """ - if not (isinstance(loc, (list, tuple)) and len(loc) == 2): + if not (isinstance(loc, (list, tuple)) and len(loc) == 2): # noqa: SIM114 return False elif not (0 <= loc[0] < self.row and 0 <= loc[1] < self.column): return False diff --git a/pyproject.toml b/pyproject.toml index 4c512ca896b4..c07bc9c48e51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,14 +77,10 @@ max-complexity = 17 # default: 10 [tool.ruff.lint.per-file-ignores] "arithmetic_analysis/newton_raphson.py" = ["PGH001"] "data_structures/binary_tree/binary_search_tree_recursive.py" = ["BLE001"] -"data_structures/binary_tree/treap.py" = ["SIM114"] "data_structures/hashing/tests/test_hash_map.py" = ["BLE001"] -"data_structures/heap/max_heap.py" = ["SIM114"] -"graphs/minimum_spanning_tree_prims.py" = ["SIM114"] "hashes/enigma_machine.py" = ["BLE001"] -"machine_learning/decision_tree.py" = ["SIM114"] "machine_learning/sequential_minimum_optimization.py" = ["SIM115"] -"matrix/sherman_morrison.py" = ["SIM103", "SIM114"] +"matrix/sherman_morrison.py" = ["SIM103"] "other/l*u_cache.py" = ["RUF012"] "physics/newtons_second_law_of_motion.py" = ["BLE001"] "project_euler/problem_099/sol1.py" = ["SIM115"] From 1f368da06d361e3d1415a2ec7d8857068b746586 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 13:38:55 +0200 Subject: [PATCH 2729/2908] [pre-commit.ci] pre-commit autoupdate (#11402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.3 → v0.4.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.3...v0.4.4) - [github.com/tox-dev/pyproject-fmt: 1.8.0 → 2.0.4](https://github.com/tox-dev/pyproject-fmt/compare/1.8.0...2.0.4) - [github.com/abravalheri/validate-pyproject: v0.16 → v0.17](https://github.com/abravalheri/validate-pyproject/compare/v0.16...v0.17) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +- pyproject.toml | 184 +++++++++++++++++++++++----------------- 2 files changed, 107 insertions(+), 83 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 210b7494036e..521769096369 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.3 + rev: v0.4.4 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.8.0" + rev: "2.0.4" hooks: - id: pyproject-fmt @@ -42,7 +42,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.16 + rev: v0.17 hooks: - id: validate-pyproject diff --git a/pyproject.toml b/pyproject.toml index c07bc9c48e51..89ed22bc6ab1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,61 +1,61 @@ [tool.ruff] -lint.ignore = [ # `ruff rule S101` for a description of that rule - "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME - "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME - "EM101", # Exception must not use a string literal, assign to variable first - "EXE001", # Shebang is present but file is not executable -- DO NOT FIX - "G004", # Logging statement uses f-string - "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey - "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX - "PLW2901", # PLW2901: Redefined loop variable -- FIX ME - "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception - "PT018", # Assertion should be broken down into multiple parts - "S101", # Use of `assert` detected -- DO NOT FIX - "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME - "SLF001", # Private member accessed: `_Iterator` -- FIX ME - "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX -] -lint.select = [ # https://beta.ruff.rs/docs/rules - "A", # flake8-builtins - "ARG", # flake8-unused-arguments - "ASYNC", # flake8-async - "B", # flake8-bugbear - "BLE", # flake8-blind-except - "C4", # flake8-comprehensions - "C90", # McCabe cyclomatic complexity - "DJ", # flake8-django - "DTZ", # flake8-datetimez - "E", # pycodestyle - "EM", # flake8-errmsg - "EXE", # flake8-executable - "F", # Pyflakes - "FA", # flake8-future-annotations - "FLY", # flynt - "G", # flake8-logging-format - "I", # isort - "ICN", # flake8-import-conventions - "INP", # flake8-no-pep420 - "INT", # flake8-gettext - "ISC", # flake8-implicit-str-concat - "N", # pep8-naming - "NPY", # NumPy-specific rules - "PD", # pandas-vet - "PGH", # pygrep-hooks - "PIE", # flake8-pie - "PL", # Pylint - "PT", # flake8-pytest-style - "PYI", # flake8-pyi - "RSE", # flake8-raise - "RUF", # Ruff-specific rules - "S", # flake8-bandit - "SIM", # flake8-simplify - "SLF", # flake8-self - "T10", # flake8-debugger - "TD", # flake8-todos - "TID", # flake8-tidy-imports - "UP", # pyupgrade - "W", # pycodestyle - "YTT", # flake8-2020 +lint.ignore = [ # `ruff rule S101` for a description of that rule + "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME + "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME + "EM101", # Exception must not use a string literal, assign to variable first + "EXE001", # Shebang is present but file is not executable -- DO NOT FIX + "G004", # Logging statement uses f-string + "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey + "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX + "PLW2901", # PLW2901: Redefined loop variable -- FIX ME + "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception + "PT018", # Assertion should be broken down into multiple parts + "S101", # Use of `assert` detected -- DO NOT FIX + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME + "SLF001", # Private member accessed: `_Iterator` -- FIX ME + "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX +] +lint.select = [ # https://beta.ruff.rs/docs/rules + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "ASYNC", # flake8-async + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "C90", # McCabe cyclomatic complexity + "DJ", # flake8-django + "DTZ", # flake8-datetimez + "E", # pycodestyle + "EM", # flake8-errmsg + "EXE", # flake8-executable + "F", # Pyflakes + "FA", # flake8-future-annotations + "FLY", # flynt + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "N", # pep8-naming + "NPY", # NumPy-specific rules + "PD", # pandas-vet + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # Pylint + "PT", # flake8-pytest-style + "PYI", # flake8-pyi + "RSE", # flake8-raise + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify + "SLF", # flake8-self + "T10", # flake8-debugger + "TD", # flake8-todos + "TID", # flake8-tidy-imports + "UP", # pyupgrade + "W", # pycodestyle + "YTT", # flake8-2020 # "ANN", # flake8-annotations # FIX ME? # "COM", # flake8-commas # "D", # pydocstyle -- FIX ME? @@ -71,27 +71,51 @@ lint.select = [ # https://beta.ruff.rs/docs/rules output-format = "full" target-version = "py312" -[tool.ruff.lint.mccabe] # DO NOT INCREASE THIS VALUE -max-complexity = 17 # default: 10 +[tool.ruff.lint.mccabe] # DO NOT INCREASE THIS VALUE +max-complexity = 17 # default: 10 [tool.ruff.lint.per-file-ignores] -"arithmetic_analysis/newton_raphson.py" = ["PGH001"] -"data_structures/binary_tree/binary_search_tree_recursive.py" = ["BLE001"] -"data_structures/hashing/tests/test_hash_map.py" = ["BLE001"] -"hashes/enigma_machine.py" = ["BLE001"] -"machine_learning/sequential_minimum_optimization.py" = ["SIM115"] -"matrix/sherman_morrison.py" = ["SIM103"] -"other/l*u_cache.py" = ["RUF012"] -"physics/newtons_second_law_of_motion.py" = ["BLE001"] -"project_euler/problem_099/sol1.py" = ["SIM115"] -"sorts/external_sort.py" = ["SIM115"] +"arithmetic_analysis/newton_raphson.py" = [ + "PGH001", +] +"data_structures/binary_tree/binary_search_tree_recursive.py" = [ + "BLE001", +] +"data_structures/hashing/tests/test_hash_map.py" = [ + "BLE001", +] +"hashes/enigma_machine.py" = [ + "BLE001", +] +"machine_learning/sequential_minimum_optimization.py" = [ + "SIM115", +] +"matrix/sherman_morrison.py" = [ + "SIM103", +] +"other/l*u_cache.py" = [ + "RUF012", +] +"physics/newtons_second_law_of_motion.py" = [ + "BLE001", +] +"project_euler/problem_099/sol1.py" = [ + "SIM115", +] +"sorts/external_sort.py" = [ + "SIM115", +] -[tool.ruff.lint.pylint] # DO NOT INCREASE THESE VALUES -allow-magic-value-types = ["float", "int", "str"] -max-args = 10 # default: 5 -max-branches = 20 # default: 12 -max-returns = 8 # default: 6 -max-statements = 88 # default: 50 +[tool.ruff.lint.pylint] # DO NOT INCREASE THESE VALUES +allow-magic-value-types = [ + "float", + "int", + "str", +] +max-args = 10 # default: 5 +max-branches = 20 # default: 12 +max-returns = 8 # default: 6 +max-statements = 88 # default: 50 [tool.codespell] ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" @@ -99,17 +123,17 @@ skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_n [tool.pytest.ini_options] markers = [ - "mat_ops: mark a test as utilizing matrix operations.", + "mat_ops: mark a test as utilizing matrix operations.", ] addopts = [ - "--durations=10", - "--doctest-modules", - "--showlocals", + "--durations=10", + "--doctest-modules", + "--showlocals", ] [tool.coverage.report] omit = [ ".env/*", - "project_euler/*" + "project_euler/*", ] sort = "Cover" From 0139143abb286027bd3954f3862aab4558642019 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 22:44:57 +0200 Subject: [PATCH 2730/2908] [pre-commit.ci] pre-commit autoupdate (#11408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/tox-dev/pyproject-fmt: 2.0.4 → 2.1.1](https://github.com/tox-dev/pyproject-fmt/compare/2.0.4...2.1.1) - [github.com/abravalheri/validate-pyproject: v0.17 → v0.18](https://github.com/abravalheri/validate-pyproject/compare/v0.17...v0.18) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 +-- pyproject.toml | 79 ++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 521769096369..b63457ca85e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.0.4" + rev: "2.1.1" hooks: - id: pyproject-fmt @@ -42,7 +42,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.17 + rev: v0.18 hooks: - id: validate-pyproject diff --git a/pyproject.toml b/pyproject.toml index 89ed22bc6ab1..5b8ce4e72dfd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,9 @@ [tool.ruff] -lint.ignore = [ # `ruff rule S101` for a description of that rule - "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME - "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME - "EM101", # Exception must not use a string literal, assign to variable first - "EXE001", # Shebang is present but file is not executable -- DO NOT FIX - "G004", # Logging statement uses f-string - "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey - "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX - "PLW2901", # PLW2901: Redefined loop variable -- FIX ME - "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception - "PT018", # Assertion should be broken down into multiple parts - "S101", # Use of `assert` detected -- DO NOT FIX - "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME - "SLF001", # Private member accessed: `_Iterator` -- FIX ME - "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX -] -lint.select = [ # https://beta.ruff.rs/docs/rules +target-version = "py312" + +output-format = "full" +lint.select = [ + # https://beta.ruff.rs/docs/rules "A", # flake8-builtins "ARG", # flake8-unused-arguments "ASYNC", # flake8-async @@ -68,54 +56,63 @@ lint.select = [ # https://beta.ruff.rs/docs/rules # "TCH", # flake8-type-checking # "TRY", # tryceratops ] -output-format = "full" -target-version = "py312" - -[tool.ruff.lint.mccabe] # DO NOT INCREASE THIS VALUE -max-complexity = 17 # default: 10 - -[tool.ruff.lint.per-file-ignores] -"arithmetic_analysis/newton_raphson.py" = [ +lint.per-file-ignores."arithmetic_analysis/newton_raphson.py" = [ "PGH001", ] -"data_structures/binary_tree/binary_search_tree_recursive.py" = [ +lint.per-file-ignores."data_structures/binary_tree/binary_search_tree_recursive.py" = [ "BLE001", ] -"data_structures/hashing/tests/test_hash_map.py" = [ +lint.per-file-ignores."data_structures/hashing/tests/test_hash_map.py" = [ "BLE001", ] -"hashes/enigma_machine.py" = [ +lint.per-file-ignores."hashes/enigma_machine.py" = [ "BLE001", ] -"machine_learning/sequential_minimum_optimization.py" = [ +lint.per-file-ignores."machine_learning/sequential_minimum_optimization.py" = [ "SIM115", ] -"matrix/sherman_morrison.py" = [ +lint.per-file-ignores."matrix/sherman_morrison.py" = [ "SIM103", ] -"other/l*u_cache.py" = [ +lint.per-file-ignores."other/l*u_cache.py" = [ "RUF012", ] -"physics/newtons_second_law_of_motion.py" = [ +lint.per-file-ignores."physics/newtons_second_law_of_motion.py" = [ "BLE001", ] -"project_euler/problem_099/sol1.py" = [ +lint.per-file-ignores."project_euler/problem_099/sol1.py" = [ "SIM115", ] -"sorts/external_sort.py" = [ +lint.per-file-ignores."sorts/external_sort.py" = [ "SIM115", ] - -[tool.ruff.lint.pylint] # DO NOT INCREASE THESE VALUES -allow-magic-value-types = [ +lint.mccabe.max-complexity = 17 # default: 10 +lint.pylint.allow-magic-value-types = [ "float", "int", "str", ] -max-args = 10 # default: 5 -max-branches = 20 # default: 12 -max-returns = 8 # default: 6 -max-statements = 88 # default: 50 +lint.pylint.max-args = 10 # default: 5 +lint.pylint.max-branches = 20 # default: 12 +lint.pylint.max-returns = 8 # default: 6 +lint.pylint.max-statements = 88 # default: 50 +lint.ignore = [ + # `ruff rule S101` for a description of that rule + "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME + "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME + "EM101", # Exception must not use a string literal, assign to variable first + "EXE001", # Shebang is present but file is not executable -- DO NOT FIX + "G004", # Logging statement uses f-string + "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey + "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX + "PLW2901", # PLW2901: Redefined loop variable -- FIX ME + "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception + "PT018", # Assertion should be broken down into multiple parts + "S101", # Use of `assert` detected -- DO NOT FIX + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME + "SLF001", # Private member accessed: `_Iterator` -- FIX ME + "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX +] [tool.codespell] ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" From 82aa909db7736d8022532bee4dc381072d8c5b1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 21:56:48 -0400 Subject: [PATCH 2731/2908] [pre-commit.ci] pre-commit autoupdate (#11417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.4 → v0.4.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.4...v0.4.5) - [github.com/codespell-project/codespell: v2.2.6 → v2.3.0](https://github.com/codespell-project/codespell/compare/v2.2.6...v2.3.0) - [github.com/tox-dev/pyproject-fmt: 2.1.1 → 2.1.3](https://github.com/tox-dev/pyproject-fmt/compare/2.1.1...2.1.3) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * iterable * at most --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 6 +++--- graphs/dijkstra_algorithm.py | 2 +- project_euler/problem_047/sol1.py | 2 +- pyproject.toml | 35 ++++++++++++++++--------------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b63457ca85e3..43bf547dec6e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,20 +16,20 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.4 + rev: v0.4.5 hooks: - id: ruff - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell additional_dependencies: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.1.1" + rev: "2.1.3" hooks: - id: pyproject-fmt diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 2efa2cb634ff..51412b790bac 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -215,7 +215,7 @@ def decrease_key(self, tup, new_d): [(5, 'A'), (15, 'B')] """ idx = self.pos[tup[1]] - # assuming the new_d is atmost old_d + # assuming the new_d is at most old_d self.array[idx] = (new_d, tup[1]) while idx > 0 and self.array[self.par(idx)][0] > self.array[idx][0]: self.swap(idx, self.par(idx)) diff --git a/project_euler/problem_047/sol1.py b/project_euler/problem_047/sol1.py index c9c44a9832dd..4ecd4f4b44c1 100644 --- a/project_euler/problem_047/sol1.py +++ b/project_euler/problem_047/sol1.py @@ -58,7 +58,7 @@ def upf_len(num: int) -> int: def equality(iterable: list) -> bool: """ - Check equality of ALL elements in an interable. + Check equality of ALL elements in an iterable >>> equality([1, 2, 3, 4]) False >>> equality([2, 2, 2, 2]) diff --git a/pyproject.toml b/pyproject.toml index 5b8ce4e72dfd..429f4fab9a52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,24 @@ lint.select = [ # "TCH", # flake8-type-checking # "TRY", # tryceratops ] +lint.ignore = [ + # `ruff rule S101` for a description of that rule + "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME + "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME + "EM101", # Exception must not use a string literal, assign to variable first + "EXE001", # Shebang is present but file is not executable -- DO NOT FIX + "G004", # Logging statement uses f-string + "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey + "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX + "PLW2901", # PLW2901: Redefined loop variable -- FIX ME + "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception + "PT018", # Assertion should be broken down into multiple parts + "S101", # Use of `assert` detected -- DO NOT FIX + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME + "SLF001", # Private member accessed: `_Iterator` -- FIX ME + "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX +] + lint.per-file-ignores."arithmetic_analysis/newton_raphson.py" = [ "PGH001", ] @@ -96,23 +114,6 @@ lint.pylint.max-args = 10 # default: 5 lint.pylint.max-branches = 20 # default: 12 lint.pylint.max-returns = 8 # default: 6 lint.pylint.max-statements = 88 # default: 50 -lint.ignore = [ - # `ruff rule S101` for a description of that rule - "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME - "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME - "EM101", # Exception must not use a string literal, assign to variable first - "EXE001", # Shebang is present but file is not executable -- DO NOT FIX - "G004", # Logging statement uses f-string - "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey - "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX - "PLW2901", # PLW2901: Redefined loop variable -- FIX ME - "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception - "PT018", # Assertion should be broken down into multiple parts - "S101", # Use of `assert` detected -- DO NOT FIX - "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME - "SLF001", # Private member accessed: `_Iterator` -- FIX ME - "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX -] [tool.codespell] ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" From b8afb214f8c8d185dc42dafb9676becf512ca7fa Mon Sep 17 00:00:00 2001 From: Marco-campione-github <80974790+Marco-campione-github@users.noreply.github.com> Date: Fri, 31 May 2024 10:11:09 +0200 Subject: [PATCH 2732/2908] Changed the N to self.N in show_data in segment_tree.py (#11276) --- data_structures/binary_tree/segment_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index c7069b3f6069..084fcf84955d 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -98,7 +98,7 @@ def query_recursive(self, idx, left, right, a, b): def show_data(self): show_list = [] - for i in range(1, N + 1): + for i in range(1, self.N + 1): show_list += [self.query(i, i)] print(show_list) From 70bd06db4642a2323ff397b041d40bc95ed6a5bf Mon Sep 17 00:00:00 2001 From: Pedram_Mohajer <48964282+pedram-mohajer@users.noreply.github.com> Date: Sat, 1 Jun 2024 05:09:03 -0400 Subject: [PATCH 2733/2908] add doctest/document to actual_power and document to power (#11187) * Update power.py * Update divide_and_conquer/power.py --------- Co-authored-by: Tianyi Zheng --- divide_and_conquer/power.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/divide_and_conquer/power.py b/divide_and_conquer/power.py index f2e023afd536..faf6a3476d40 100644 --- a/divide_and_conquer/power.py +++ b/divide_and_conquer/power.py @@ -2,6 +2,20 @@ def actual_power(a: int, b: int): """ Function using divide and conquer to calculate a^b. It only works for integer a,b. + + :param a: The base of the power operation, an integer. + :param b: The exponent of the power operation, a non-negative integer. + :return: The result of a^b. + + Examples: + >>> actual_power(3, 2) + 9 + >>> actual_power(5, 3) + 125 + >>> actual_power(2, 5) + 32 + >>> actual_power(7, 0) + 1 """ if b == 0: return 1 @@ -13,6 +27,10 @@ def actual_power(a: int, b: int): def power(a: int, b: int) -> float: """ + :param a: The base (integer). + :param b: The exponent (integer). + :return: The result of a^b, as a float for negative exponents. + >>> power(4,6) 4096 >>> power(2,3) From 723cf9c42839c47e9e6fb83362a7391177355505 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 1 Jun 2024 02:17:07 -0700 Subject: [PATCH 2734/2908] Remove duplicate implementation of median of two arrays algorithm (#11420) * Remove duplicate implementation of median of two arrays algorithm Remove maths/median_of_two_arrays.py because the repo has two implementations of this algorithm, with data_structures/arrays/median_two_array.py being the other. Even though maths/median_of_two_arrays.py is the older implementation, the newer implementation is better documented, has better error handling, and is already located in a more appropriate directory. * updating DIRECTORY.md --------- Co-authored-by: tianyizheng02 --- DIRECTORY.md | 1 - maths/median_of_two_arrays.py | 33 --------------------------------- 2 files changed, 34 deletions(-) delete mode 100644 maths/median_of_two_arrays.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 4a053a3f1b7f..2094fc3a980e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -661,7 +661,6 @@ * [Manhattan Distance](maths/manhattan_distance.py) * [Matrix Exponentiation](maths/matrix_exponentiation.py) * [Max Sum Sliding Window](maths/max_sum_sliding_window.py) - * [Median Of Two Arrays](maths/median_of_two_arrays.py) * [Minkowski Distance](maths/minkowski_distance.py) * [Mobius Function](maths/mobius_function.py) * [Modular Division](maths/modular_division.py) diff --git a/maths/median_of_two_arrays.py b/maths/median_of_two_arrays.py deleted file mode 100644 index 55aa587a9c4b..000000000000 --- a/maths/median_of_two_arrays.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import annotations - - -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 edee8e644b09a21a1f70d3a59d57feed51c74004 Mon Sep 17 00:00:00 2001 From: Vishal Kumar Gupta Date: Sun, 2 Jun 2024 02:41:40 +0100 Subject: [PATCH 2735/2908] use format to remove '0b' (#11307) * use format to remove '0b' * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: error message for float input --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- bit_manipulation/binary_and_operator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bit_manipulation/binary_and_operator.py b/bit_manipulation/binary_and_operator.py index 36f6c668d9b3..f33b8b1c0ab4 100644 --- a/bit_manipulation/binary_and_operator.py +++ b/bit_manipulation/binary_and_operator.py @@ -26,7 +26,7 @@ def binary_and(a: int, b: int) -> str: >>> binary_and(0, 1.1) Traceback (most recent call last): ... - TypeError: 'float' object cannot be interpreted as an integer + ValueError: Unknown format code 'b' for object of type 'float' >>> binary_and("0", "1") Traceback (most recent call last): ... @@ -35,8 +35,8 @@ def binary_and(a: int, b: int) -> str: if a < 0 or b < 0: raise ValueError("the value of both inputs must be positive") - a_binary = str(bin(a))[2:] # remove the leading "0b" - b_binary = str(bin(b))[2:] # remove the leading "0b" + a_binary = format(a, "b") + b_binary = format(b, "b") max_len = max(len(a_binary), len(b_binary)) From 2f1704dae579295ea2f47584ef80b4b321a284d7 Mon Sep 17 00:00:00 2001 From: Mandeep Singh <135956602+MannCode@users.noreply.github.com> Date: Sun, 2 Jun 2024 18:27:35 -0700 Subject: [PATCH 2736/2908] issue #11150 Ensure explicit column selection and data type setting in data reading process. (#11302) * issue #11150 Ensure explicit column selection and data type setting in data reading process. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- machine_learning/sequential_minimum_optimization.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 3abdd6ccbed8..2ebdeb764a80 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -463,7 +463,11 @@ def test_cancel_data(): 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( + "cancel_data.csv", + header=None, + dtype={0: str}, # Assuming the first column contains string data + ) # 1: pre-processing data del data[data.columns.tolist()[0]] From ffaa976f6c5a5de30e284ae2fc8122f40cd3fa6a Mon Sep 17 00:00:00 2001 From: Harsh buddhdev Date: Sun, 2 Jun 2024 23:00:26 -0400 Subject: [PATCH 2737/2908] Fixes #9943 (#10252) * added doctest for all_permutations.py * added doctest for all_subsequences.py * added doctest for all_subsequences.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doctest added * updated * Update backtracking/all_subsequences.py --------- Co-authored-by: Harsh Buddhdev Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- backtracking/all_permutations.py | 36 ++++++++++++++++++++++ backtracking/all_subsequences.py | 52 +++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index c483cd62c99b..f376e6fa0945 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -23,6 +23,42 @@ def create_state_space_tree( 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. + + :param sequence: The input sequence for which permutations are generated. + :param current_sequence: The current permutation being built. + :param index: The current index in the sequence. + :param index_used: list to track which elements are used in permutation. + + Example 1: + >>> sequence = [1, 2, 3] + >>> current_sequence = [] + >>> index_used = [False, False, False] + >>> create_state_space_tree(sequence, current_sequence, 0, index_used) + [1, 2, 3] + [1, 3, 2] + [2, 1, 3] + [2, 3, 1] + [3, 1, 2] + [3, 2, 1] + + Example 2: + >>> sequence = ["A", "B", "C"] + >>> current_sequence = [] + >>> index_used = [False, False, False] + >>> create_state_space_tree(sequence, current_sequence, 0, index_used) + ['A', 'B', 'C'] + ['A', 'C', 'B'] + ['B', 'A', 'C'] + ['B', 'C', 'A'] + ['C', 'A', 'B'] + ['C', 'B', 'A'] + + Example 3: + >>> sequence = [1] + >>> current_sequence = [] + >>> index_used = [False] + >>> create_state_space_tree(sequence, current_sequence, 0, index_used) + [1] """ if index == len(sequence): diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index 7844a829d046..18696054eb7e 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -22,6 +22,56 @@ def create_state_space_tree( 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. + + :param sequence: The input sequence for which subsequences are generated. + :param current_subsequence: The current subsequence being built. + :param index: The current index in the sequence. + + Example: + >>> sequence = [3, 2, 1] + >>> current_subsequence = [] + >>> create_state_space_tree(sequence, current_subsequence, 0) + [] + [1] + [2] + [2, 1] + [3] + [3, 1] + [3, 2] + [3, 2, 1] + + >>> sequence = ["A", "B"] + >>> current_subsequence = [] + >>> create_state_space_tree(sequence, current_subsequence, 0) + [] + ['B'] + ['A'] + ['A', 'B'] + + >>> sequence = [] + >>> current_subsequence = [] + >>> create_state_space_tree(sequence, current_subsequence, 0) + [] + + >>> sequence = [1, 2, 3, 4] + >>> current_subsequence = [] + >>> create_state_space_tree(sequence, current_subsequence, 0) + [] + [4] + [3] + [3, 4] + [2] + [2, 4] + [2, 3] + [2, 3, 4] + [1] + [1, 4] + [1, 3] + [1, 3, 4] + [1, 2] + [1, 2, 4] + [1, 2, 3] + [1, 2, 3, 4] """ if index == len(sequence): @@ -35,7 +85,7 @@ def create_state_space_tree( if __name__ == "__main__": - seq: list[Any] = [3, 1, 2, 4] + seq: list[Any] = [1, 2, 3] generate_all_subsequences(seq) seq.clear() From c919579869ae9f57d6878336af6de6bc9a001c61 Mon Sep 17 00:00:00 2001 From: AtomicVar Date: Mon, 3 Jun 2024 11:15:01 +0800 Subject: [PATCH 2738/2908] Add KL divergence loss algorithm (#11238) * Add KL divergence loss algorithm * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- machine_learning/loss_functions.py | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py index 16e5a3278b73..150035661eb7 100644 --- a/machine_learning/loss_functions.py +++ b/machine_learning/loss_functions.py @@ -629,6 +629,40 @@ def smooth_l1_loss(y_true: np.ndarray, y_pred: np.ndarray, beta: float = 1.0) -> return np.mean(loss) +def kullback_leibler_divergence(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Calculate the Kullback-Leibler divergence (KL divergence) loss between true labels + and predicted probabilities. + + KL divergence loss quantifies dissimilarity between true labels and predicted + probabilities. It's often used in training generative models. + + KL = Σ(y_true * ln(y_true / y_pred)) + + Reference: https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence + + Parameters: + - y_true: True class probabilities + - y_pred: Predicted class probabilities + + >>> true_labels = np.array([0.2, 0.3, 0.5]) + >>> predicted_probs = np.array([0.3, 0.3, 0.4]) + >>> kullback_leibler_divergence(true_labels, predicted_probs) + 0.030478754035472025 + >>> true_labels = np.array([0.2, 0.3, 0.5]) + >>> predicted_probs = np.array([0.3, 0.3, 0.4, 0.5]) + >>> kullback_leibler_divergence(true_labels, predicted_probs) + Traceback (most recent call last): + ... + ValueError: Input arrays must have the same length. + """ + if len(y_true) != len(y_pred): + raise ValueError("Input arrays must have the same length.") + + kl_loss = y_true * np.log(y_true / y_pred) + return np.sum(kl_loss) + + if __name__ == "__main__": import doctest From 5827aac79a36f0d43e9bd9f1c9ca11da07b2d623 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:21:27 -0300 Subject: [PATCH 2739/2908] [pre-commit.ci] pre-commit autoupdate (#11430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.5 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.5...v0.4.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43bf547dec6e..a04f4f8b2165 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.5 + rev: v0.4.7 hooks: - id: ruff - id: ruff-format From 41a1cdf38d9cb1a14c9149d2d815efa2259679ef Mon Sep 17 00:00:00 2001 From: Yuri Batista Ishizawa Date: Tue, 11 Jun 2024 06:45:00 -0300 Subject: [PATCH 2740/2908] Add rainfall intensity calculation function (#11432) * Add rainfall intensity calculation function * chore: improve fuction and coefficient documentation * Update physics/rainfall_intensity.py --------- Co-authored-by: Tianyi Zheng --- physics/rainfall_intensity.py | 143 ++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 physics/rainfall_intensity.py diff --git a/physics/rainfall_intensity.py b/physics/rainfall_intensity.py new file mode 100644 index 000000000000..cee8d50ddc2f --- /dev/null +++ b/physics/rainfall_intensity.py @@ -0,0 +1,143 @@ +""" +Rainfall Intensity +================== +This module contains functions to calculate the intensity of +a rainfall event for a given duration and return period. + +This function uses the Sherman intensity-duration-frequency curve. + +References +---------- +- Aparicio, F. (1997): Fundamentos de Hidrología de Superficie. + Balderas, México, Limusa. 303 p. +- https://en.wikipedia.org/wiki/Intensity-duration-frequency_curve +""" + + +def rainfall_intensity( + coefficient_k: float, + coefficient_a: float, + coefficient_b: float, + coefficient_c: float, + return_period: float, + duration: float, +) -> float: + """ + Calculate the intensity of a rainfall event for a given duration and return period. + It's based on the Sherman intensity-duration-frequency curve: + + I = k * T^a / (D + b)^c + + where: + I = Intensity of the rainfall event [mm/h] + k, a, b, c = Coefficients obtained through statistical distribution adjust + T = Return period in years + D = Rainfall event duration in minutes + + Parameters + ---------- + coefficient_k : float + Coefficient obtained through statistical distribution adjust. + coefficient_a : float + Coefficient obtained through statistical distribution adjust. + coefficient_b : float + Coefficient obtained through statistical distribution adjust. + coefficient_c : float + Coefficient obtained through statistical distribution adjust. + return_period : float + Return period in years. + duration : float + Rainfall event duration in minutes. + + Returns + ------- + intensity : float + Intensity of the rainfall event in mm/h. + + Raises + ------ + ValueError + If any of the parameters are not positive. + + Examples + -------- + + >>> rainfall_intensity(1000, 0.2, 11.6, 0.81, 10, 60) + 49.83339231138578 + + >>> rainfall_intensity(1000, 0.2, 11.6, 0.81, 10, 30) + 77.36319588106228 + + >>> rainfall_intensity(1000, 0.2, 11.6, 0.81, 5, 60) + 43.382487747633625 + + >>> rainfall_intensity(0, 0.2, 11.6, 0.81, 10, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(1000, -0.2, 11.6, 0.81, 10, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(1000, 0.2, -11.6, 0.81, 10, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(1000, 0.2, 11.6, -0.81, 10, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(1000, 0, 11.6, 0.81, 10, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(1000, 0.2, 0, 0.81, 10, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(1000, 0.2, 11.6, 0, 10, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(0, 0.2, 11.6, 0.81, 10, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(1000, 0.2, 11.6, 0.81, 0, 60) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + >>> rainfall_intensity(1000, 0.2, 11.6, 0.81, 10, 0) + Traceback (most recent call last): + ... + ValueError: All parameters must be positive. + + """ + if ( + coefficient_k <= 0 + or coefficient_a <= 0 + or coefficient_b <= 0 + or coefficient_c <= 0 + or return_period <= 0 + or duration <= 0 + ): + raise ValueError("All parameters must be positive.") + intensity = (coefficient_k * (return_period**coefficient_a)) / ( + (duration + coefficient_b) ** coefficient_c + ) + return intensity + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 446742387e83f94f3d54ce640cb07004180130ee Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Thu, 13 Jun 2024 14:47:29 -0700 Subject: [PATCH 2741/2908] Fix grammar and spelling mistakes in sequential_minimum_optimization.py (#11427) --- .../sequential_minimum_optimization.py | 135 +++++++++--------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 2ebdeb764a80..625fc28fe60c 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -1,11 +1,9 @@ """ - Implementation of sequential minimal optimization (SMO) for support vector machines - (SVM). +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. +Sequential minimal optimization (SMO) is an algorithm for solving the quadratic +programming (QP) problem that arises during the training of SVMs. It was invented by +John Platt in 1998. Input: 0: type: numpy.ndarray. @@ -124,8 +122,7 @@ 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, here we only calculate the error for non-bound samples self._unbound = [i for i in self._all_samples if self._is_unbound(i)] for s in self.unbound: if s in (i1, i2): @@ -136,7 +133,7 @@ def fit(self): + (self._b - b_old) ) - # if i1 or i2 is non-bound,update there error value to zero + # if i1 or i2 is non-bound, update their error value to zero if self._is_unbound(i1): self._error[i1] = 0 if self._is_unbound(i2): @@ -161,7 +158,7 @@ def predict(self, test_samples, classify=True): results.append(result) return np.array(results) - # Check if alpha violate KKT condition + # Check if alpha violates the KKT condition def _check_obey_kkt(self, index): alphas = self.alphas tol = self._tol @@ -172,20 +169,19 @@ def _check_obey_kkt(self, index): # Get value calculated from kernel function def _k(self, i1, i2): - # for test samples,use Kernel function + # 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 + # for training samples, kernel values have been saved in matrix else: return self._K_matrix[i1, i2] - # Get sample's error + # Get error for sample 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 - + 1: Sample[index] is non-bound, fetch error from list: _error + 2: sample[index] is bound, use predicted value minus true value: g(xi) - yi """ # get from error data if self._is_unbound(index): @@ -196,7 +192,7 @@ def _e(self, index): yi = self.tags[index] return gx - yi - # Calculate Kernel matrix of all possible i1,i2 ,saving time + # 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: @@ -206,7 +202,7 @@ def _calculate_k_matrix(self): ) return k_matrix - # Predict test sample's tag + # Predict tag for test sample def _predict(self, sample): k = self._k predicted_value = ( @@ -222,30 +218,31 @@ def _predict(self, sample): # Choose alpha1 and alpha2 def _choose_alphas(self): - locis = yield from self._choose_a1() - if not locis: + loci = yield from self._choose_a1() + if not loci: return None - return locis + return loci 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. + Choose first alpha + Steps: + 1: First loop over all samples + 2: Second loop over all non-bound samples until no non-bound samples violate + the KKT condition. + 3: Repeat these two processes until no samples violate the KKT condition + after the first loop. """ while True: all_not_obey = True # all sample - print("scanning all sample!") + print("Scanning all samples!") 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 samples!") while True: not_obey = True for i1 in [ @@ -256,20 +253,21 @@ def _choose_a1(self): 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 satisfy the KKT condition!") break if all_not_obey: - print("all samples fit the KKT condition! Optimization done!") + print("All samples satisfy the KKT condition!") break return False def _choose_a2(self, i1): """ - Choose the second alpha by using heuristic algorithm ;steps: - 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 + Choose the second alpha using a heuristic algorithm + Steps: + 1: Choose alpha2 that maximizes the 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 + 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)] @@ -306,7 +304,7 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): if i1 == i2: return None, None - # calculate L and H which bound the new alpha2 + # 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) # noqa: E741 @@ -320,7 +318,7 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): k22 = k(i2, i2) k12 = k(i1, i2) - # select the new alpha2 which could get the minimal objectives + # select the new alpha2 which could achieve the minimal objectives if (eta := k11 + k22 - 2.0 * k12) > 0.0: a2_new_unc = a2 + (y2 * (e1 - e2)) / eta # a2_new has a boundary @@ -335,7 +333,7 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): l1 = a1 + s * (a2 - l) h1 = a1 + s * (a2 - h) - # way 1 + # Method 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 = ( @@ -353,9 +351,8 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): + s * h * h1 * k(i1, i2) ) """ - # way 2 - Use objective function check which alpha2 new could get the minimal - objectives + Method 2: Use objective function to check which alpha2_new could achieve the + minimal objectives """ if ol < (oh - self._eps): a2_new = l @@ -375,7 +372,7 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): return a1_new, a2_new - # Normalise data using min_max way + # Normalize data using min-max method def _norm(self, data): if self._init: self._min = np.min(data, axis=0) @@ -424,7 +421,7 @@ def _rbf(self, v1, v2): def _check(self): if self._kernel == self._rbf and self.gamma < 0: - raise ValueError("gamma value must greater than 0") + raise ValueError("gamma value must be non-negative") def _get_kernel(self, kernel_name): maps = {"linear": self._linear, "poly": self._polynomial, "rbf": self._rbf} @@ -444,27 +441,27 @@ def call_func(*args, **kwargs): start_time = time.time() func(*args, **kwargs) end_time = time.time() - print(f"smo algorithm cost {end_time - start_time} seconds") + print(f"SMO algorithm cost {end_time - start_time} seconds") return call_func @count_time -def test_cancel_data(): - print("Hello!\nStart test svm by smo algorithm!") +def test_cancer_data(): + print("Hello!\nStart test SVM using the 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"cancer_data.csv"): request = urllib.request.Request( # noqa: S310 CANCER_DATASET_URL, headers={"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"}, ) response = urllib.request.urlopen(request) # noqa: S310 content = response.read().decode("utf-8") - with open(r"cancel_data.csv", "w") as f: + with open(r"cancer_data.csv", "w") as f: f.write(content) data = pd.read_csv( - "cancel_data.csv", + "cancer_data.csv", header=None, dtype={0: str}, # Assuming the first column contains string data ) @@ -479,14 +476,14 @@ def test_cancel_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) + # 3: choose kernel function, and set initial alphas to zero (optional) + my_kernel = 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, + kernel_func=my_kernel, alpha_list=al, cost=0.4, b=0.0, @@ -501,30 +498,30 @@ def test_cancel_data(): for i in range(test_tags.shape[0]): if test_tags[i] == predict[i]: score += 1 - print(f"\nall: {test_num}\nright: {score}\nfalse: {test_num - score}") + print(f"\nAll: {test_num}\nCorrect: {score}\nIncorrect: {test_num - score}") print(f"Rough Accuracy: {score / test_tags.shape[0]}") def test_demonstration(): # change stdout - print("\nStart plot,please wait!!!") + print("\nStarting 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") + ax1.set_title("Linear SVM, cost = 0.1") test_linear_kernel(ax1, cost=0.1) - ax2.set_title("linear svm,cost:500") + ax2.set_title("Linear SVM, cost = 500") test_linear_kernel(ax2, cost=500) - ax3.set_title("rbf kernel svm,cost:0.1") + ax3.set_title("RBF kernel SVM, cost = 0.1") test_rbf_kernel(ax3, cost=0.1) - ax4.set_title("rbf kernel svm,cost:500") + ax4.set_title("RBF kernel SVM, cost = 500") test_rbf_kernel(ax4, cost=500) sys.stdout = sys.__stdout__ - print("Plot done!!!") + print("Plot done!") def test_linear_kernel(ax, cost): @@ -535,10 +532,10 @@ def test_linear_kernel(ax, cost): 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) + my_kernel = Kernel(kernel="linear", degree=5, coef0=1, gamma=0.5) mysvm = SmoSVM( train=train_data, - kernel_func=mykernel, + kernel_func=my_kernel, cost=cost, tolerance=0.001, auto_norm=False, @@ -555,10 +552,10 @@ def test_rbf_kernel(ax, cost): 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) + my_kernel = Kernel(kernel="rbf", degree=5, coef0=1, gamma=0.5) mysvm = SmoSVM( train=train_data, - kernel_func=mykernel, + kernel_func=my_kernel, cost=cost, tolerance=0.001, auto_norm=False, @@ -571,11 +568,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 trained model. Then we - could use this prediced values to draw contour map. - And this contour map can represent svm's partition boundary. + We cannot get the optimal w of our kernel SVM model, which is different from a + linear SVM. For this reason, we generate randomly distributed points with high + density, and predicted values of these points are calculated using our trained + model. Then we could use this predicted values to draw contour map, and this contour + map represents the SVM's partition boundary. """ train_data_x = train_data[:, 1] train_data_y = train_data[:, 2] @@ -620,6 +617,6 @@ def plot_partition_boundary( if __name__ == "__main__": - test_cancel_data() + test_cancer_data() test_demonstration() plt.show() From af6a45e982213ef52a2f747dec6b58d668bfce5b Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 17 Jun 2024 00:19:32 +0300 Subject: [PATCH 2742/2908] Remove some per file ignores (#11381) * Remove some per file ignores * updating DIRECTORY.md * updating DIRECTORY.md --------- Co-authored-by: MaximSmolskiy --- DIRECTORY.md | 1 + pyproject.toml | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 2094fc3a980e..04551fad3685 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -863,6 +863,7 @@ * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) * [Photoelectric Effect](physics/photoelectric_effect.py) * [Potential Energy](physics/potential_energy.py) + * [Rainfall Intensity](physics/rainfall_intensity.py) * [Reynolds Number](physics/reynolds_number.py) * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Shear Stress](physics/shear_stress.py) diff --git a/pyproject.toml b/pyproject.toml index 429f4fab9a52..bb8657183164 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,12 +74,6 @@ lint.ignore = [ "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX ] -lint.per-file-ignores."arithmetic_analysis/newton_raphson.py" = [ - "PGH001", -] -lint.per-file-ignores."data_structures/binary_tree/binary_search_tree_recursive.py" = [ - "BLE001", -] lint.per-file-ignores."data_structures/hashing/tests/test_hash_map.py" = [ "BLE001", ] From df94d460ac8d220f97851f358abc0102ae47d3db Mon Sep 17 00:00:00 2001 From: raj <64704676+ra230537@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:17:55 -0300 Subject: [PATCH 2743/2908] Fix/fixes get top billionaries code (#11466) * fix: modify the depracated code and add new tests * fix: remove test from pr * fix: remove the useless utc import * fix: add explicit tz argument * fix: fixes ruff checking * Remove UP017 #noqa comments from code * Update get_top_billionaires.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update get_top_billionaires.py --------- Co-authored-by: Christian Clauss Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- web_programming/get_top_billionaires.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web_programming/get_top_billionaires.py b/web_programming/get_top_billionaires.py index 24828b6d787c..99f6e0be948a 100644 --- a/web_programming/get_top_billionaires.py +++ b/web_programming/get_top_billionaires.py @@ -65,7 +65,7 @@ def get_forbes_real_time_billionaires() -> list[dict[str, int | str]]: "Country": person["countryOfCitizenship"], "Gender": person["gender"], "Worth ($)": f"{person['finalWorth'] / 1000:.1f} Billion", - "Age": years_old(person["birthDate"]), + "Age": str(years_old(person["birthDate"] / 1000)), } for person in response_json["personList"]["personsLists"] ] @@ -95,4 +95,7 @@ def display_billionaires(forbes_billionaires: list[dict[str, int | str]]) -> Non if __name__ == "__main__": + from doctest import testmod + + testmod() display_billionaires(get_forbes_real_time_billionaires()) From 31d1cd8402ba48aca26d9f1d2774f929610e7180 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 08:31:32 -0400 Subject: [PATCH 2744/2908] [pre-commit.ci] pre-commit autoupdate (#11435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.4.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.7...v0.4.8) * Update .pre-commit-config.yaml --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a04f4f8b2165..fc8545b5159b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.7 + rev: v0.4.9 hooks: - id: ruff - id: ruff-format From 1cfca52db73ee18b9e9e08febe9e7d42f96e43db Mon Sep 17 00:00:00 2001 From: Snoppy Date: Mon, 17 Jun 2024 21:27:07 +0800 Subject: [PATCH 2745/2908] chore: fix typos (#11467) * chore: fix typos Signed-off-by: snoppy * Apply suggestions from code review Co-authored-by: Tianyi Zheng --------- Signed-off-by: snoppy Co-authored-by: Christian Clauss Co-authored-by: Tianyi Zheng --- computer_vision/haralick_descriptors.py | 2 +- graphs/strongly_connected_components.py | 2 +- maths/points_are_collinear_3d.py | 10 +++++----- neural_network/convolution_neural_network.py | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 712bd49668f8..634f0495797b 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -141,7 +141,7 @@ def transform( center_x, center_y = (x // 2 for x in kernel.shape) - # Use padded image when applying convolotion + # Use padded image when applying convolution # to not go out of bounds of the original the image transformed = np.zeros(image.shape, dtype=np.uint8) padded = np.pad(image, 1, "constant", constant_values=constant) diff --git a/graphs/strongly_connected_components.py b/graphs/strongly_connected_components.py index 325e5c1f33a3..4d4cf88035b5 100644 --- a/graphs/strongly_connected_components.py +++ b/graphs/strongly_connected_components.py @@ -38,7 +38,7 @@ def find_components( reversed_graph: dict[int, list[int]], vert: int, visited: list[bool] ) -> list[int]: """ - Use depth first search to find strongliy connected + Use depth first search to find strongly connected vertices. Now graph is reversed >>> find_components({0: [1], 1: [2], 2: [0]}, 0, 5 * [False]) [0, 1, 2] diff --git a/maths/points_are_collinear_3d.py b/maths/points_are_collinear_3d.py index 3bc0b3b9ebe5..c7adddda9494 100644 --- a/maths/points_are_collinear_3d.py +++ b/maths/points_are_collinear_3d.py @@ -76,9 +76,9 @@ def get_3d_vectors_cross(ab: Vector3d, ac: Vector3d) -> Vector3d: def is_zero_vector(vector: Vector3d, accuracy: int) -> bool: """ - Check if vector is equal to (0, 0, 0) of not. + Check if vector is equal to (0, 0, 0) or not. - Sine the algorithm is very accurate, we will never get a zero vector, + Since the algorithm is very accurate, we will never get a zero vector, so we need to round the vector axis, because we want a result that is either True or False. In other applications, we can return a float that represents the collinearity ratio. @@ -97,9 +97,9 @@ def are_collinear(a: Point3d, b: Point3d, c: Point3d, accuracy: int = 10) -> boo """ Check if three points are collinear or not. - 1- Create tow vectors AB and AC. - 2- Get the cross vector of the tow vectors. - 3- Calcolate the length of the cross vector. + 1- Create two vectors AB and AC. + 2- Get the cross vector of the two vectors. + 3- Calculate the length of the cross vector. 4- If the length is zero then the points are collinear, else they are not. The use of the accuracy parameter is explained in is_zero_vector docstring. diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index 3c551924442d..d4ac360a98de 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -1,7 +1,7 @@ """ - - - - - -- - - - - - - - - - - - - - - - - - - - - - - Name - - CNN - Convolution Neural Network For Photo Recognizing -Goal - - Recognize Handing Writing Word Photo +Goal - - Recognize Handwriting Word Photo Detail: Total 5 layers neural network * Convolution layer * Pooling layer @@ -135,7 +135,7 @@ def convolute(self, data, convs, w_convs, thre_convs, conv_step): ) data_featuremap.append(featuremap) - # expanding the data slice to One dimenssion + # expanding the data slice to one dimension focus1_list = [] for each_focus in data_focus: focus1_list.extend(self.Expand_Mat(each_focus)) @@ -304,7 +304,7 @@ def draw_error(): plt.grid(True, alpha=0.5) plt.show() - print("------------------Training Complished---------------------") + print("------------------Training Complete---------------------") print((" - - Training epoch: ", rp, f" - - Mse: {mse:.6f}")) if draw_e: draw_error() @@ -353,5 +353,5 @@ def convolution(self, data): if __name__ == "__main__": """ - I will put the example on other file + I will put the example in another file """ From 75b86671879cfbb83d241c3a3487b32c6dac9d91 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 00:00:47 +0200 Subject: [PATCH 2746/2908] [pre-commit.ci] pre-commit autoupdate (#11472) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.9 → v0.4.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.9...v0.4.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fc8545b5159b..1eddff7ab0e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.9 + rev: v0.4.10 hooks: - id: ruff - id: ruff-format From 6882a8b80806f2dc53d53a0ecc00c2c98bec3fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vitor?= <92267577+ShiryuReb@users.noreply.github.com> Date: Wed, 26 Jun 2024 03:06:57 -0300 Subject: [PATCH 2747/2908] Tests/add new test case weight_conversion (#11468) * add new test * add new test --- conversions/weight_conversion.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conversions/weight_conversion.py b/conversions/weight_conversion.py index e8326e0b688f..0777aead9f02 100644 --- a/conversions/weight_conversion.py +++ b/conversions/weight_conversion.py @@ -297,6 +297,12 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float: 1.660540199e-23 >>> weight_conversion("atomic-mass-unit","atomic-mass-unit",2) 1.999999998903455 + >>> weight_conversion("slug", "kilogram", 1) + Traceback (most recent call last): + ... + ValueError: Invalid 'from_type' or 'to_type' value: 'slug', 'kilogram' + Supported values are: kilogram, gram, milligram, metric-ton, long-ton, short-ton, \ +pound, stone, ounce, carrat, atomic-mass-unit """ if to_type not in KILOGRAM_CHART or from_type not in WEIGHT_TYPE_CHART: msg = ( From 716bdeb68b1e81aafe886e382319c6dab882dacc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 07:02:29 +0200 Subject: [PATCH 2748/2908] [pre-commit.ci] pre-commit autoupdate (#11473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.10...v0.5.0) - [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1) * Fix ruff issues * Fix ruff issues --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 5 ++- backtracking/knight_tour.py | 6 +-- data_structures/binary_tree/is_sorted.py | 6 +-- data_structures/binary_tree/red_black_tree.py | 37 +++++-------------- docs/source/__init__.py | 0 graphs/graph_adjacency_matrix.py | 8 ++-- graphs/multi_heuristic_astar.py | 4 +- graphs/tarjans_scc.py | 2 +- hashes/md5.py | 4 +- maths/radix2_fft.py | 1 - project_euler/problem_034/__init__.py | 1 - project_euler/problem_035/__init__.py | 1 - project_euler/problem_037/__init__.py | 1 - project_euler/problem_037/sol1.py | 9 ++--- project_euler/problem_039/__init__.py | 1 - project_euler/problem_041/__init__.py | 1 - project_euler/problem_043/__init__.py | 1 - project_euler/problem_044/__init__.py | 1 - project_euler/problem_045/__init__.py | 1 - project_euler/problem_046/__init__.py | 1 - project_euler/problem_055/__init__.py | 1 - project_euler/problem_058/__init__.py | 1 - project_euler/problem_063/__init__.py | 1 - project_euler/problem_072/sol1.py | 2 +- project_euler/problem_089/__init__.py | 1 - project_euler/problem_097/__init__.py | 1 - searches/binary_tree_traversal.py | 6 +-- sorts/external_sort.py | 5 +-- source/__init__.py | 0 .../can_string_be_rearranged_as_palindrome.py | 4 +- strings/is_valid_email_address.py | 4 +- strings/text_justification.py | 12 +++--- 32 files changed, 44 insertions(+), 85 deletions(-) create mode 100644 docs/source/__init__.py create mode 100644 source/__init__.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1eddff7ab0e6..a3f5a5e51855 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.10 + rev: v0.5.0 hooks: - id: ruff - id: ruff-format @@ -47,10 +47,11 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.10.1 hooks: - id: mypy args: + - --explicit-package-bases - --ignore-missing-imports - --install-types # See mirrors-mypy README.md - --non-interactive diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py index 5f7dee8d97bf..8906aaa1094c 100644 --- a/backtracking/knight_tour.py +++ b/backtracking/knight_tour.py @@ -24,10 +24,10 @@ def get_valid_pos(position: tuple[int, int], n: int) -> list[tuple[int, int]]: ] permissible_positions = [] - for position in positions: - y_test, x_test = position + for inner_position in positions: + y_test, x_test = inner_position if 0 <= y_test < n and 0 <= x_test < n: - permissible_positions.append(position) + permissible_positions.append(inner_position) return permissible_positions diff --git a/data_structures/binary_tree/is_sorted.py b/data_structures/binary_tree/is_sorted.py index 509a426611e5..91fc8ca82633 100644 --- a/data_structures/binary_tree/is_sorted.py +++ b/data_structures/binary_tree/is_sorted.py @@ -80,9 +80,9 @@ def is_sorted(self) -> bool: """ if self.left and (self.data < self.left.data or not self.left.is_sorted): return False - if self.right and (self.data > self.right.data or not self.right.is_sorted): - return False - return True + return not ( + self.right and (self.data > self.right.data or not self.right.is_sorted) + ) if __name__ == "__main__": diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index a9ecf897c701..752db1e7026c 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -1,8 +1,3 @@ -""" -psf/black : true -ruff : passed -""" - from __future__ import annotations from collections.abc import Iterator @@ -321,9 +316,7 @@ def check_coloring(self) -> bool: 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 + return not (self.right and not self.right.check_coloring()) def black_height(self) -> int | None: """Returns the number of black nodes from this node to the @@ -561,9 +554,7 @@ def test_rotations() -> bool: 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 + return tree == right_rot def test_insertion_speed() -> bool: @@ -606,13 +597,11 @@ def test_insert_and_search() -> bool: 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: + if any(i in tree for i in (5, -6, -10, 13)): # 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 + # Find all these things in there + return all(i in tree for i in (11, 12, -8, 0)) def test_insert_delete() -> bool: @@ -634,9 +623,7 @@ def test_insert_delete() -> bool: 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 + return list(tree.inorder_traverse()) == [-8, 0, 4, 8, 10, 11, 12] def test_floor_ceil() -> bool: @@ -664,9 +651,7 @@ def test_min_max() -> bool: tree.insert(24) tree.insert(20) tree.insert(22) - if tree.get_max() != 22 or tree.get_min() != -16: - return False - return True + return not (tree.get_max() != 22 or tree.get_min() != -16) def test_tree_traversal() -> bool: @@ -682,9 +667,7 @@ def test_tree_traversal() -> bool: 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 + return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0] def test_tree_chaining() -> bool: @@ -695,9 +678,7 @@ def test_tree_chaining() -> bool: 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 + return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0] def print_results(msg: str, passes: bool) -> None: diff --git a/docs/source/__init__.py b/docs/source/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py index 059a6aa9ffb5..568c84166e4b 100644 --- a/graphs/graph_adjacency_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -156,9 +156,11 @@ def remove_vertex(self, vertex: T) -> None: self.vertex_to_index.pop(vertex) # decrement indices for vertices shifted by the deleted vertex in the adj matrix - for vertex in self.vertex_to_index: - if self.vertex_to_index[vertex] >= start_index: - self.vertex_to_index[vertex] = self.vertex_to_index[vertex] - 1 + for inner_vertex in self.vertex_to_index: + if self.vertex_to_index[inner_vertex] >= start_index: + self.vertex_to_index[inner_vertex] = ( + self.vertex_to_index[inner_vertex] - 1 + ) def contains_vertex(self, vertex: T) -> bool: """ diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index 6af9a187a4e9..47509beb8efb 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -123,9 +123,7 @@ def do_something(back_pointer, goal, start): def valid(p: TPos): if p[0] < 0 or p[0] > n - 1: return False - if p[1] < 0 or p[1] > n - 1: - return False - return True + return not (p[1] < 0 or p[1] > n - 1) def expand_state( diff --git a/graphs/tarjans_scc.py b/graphs/tarjans_scc.py index a75dc4d2ca95..b4a3bd5c4c35 100644 --- a/graphs/tarjans_scc.py +++ b/graphs/tarjans_scc.py @@ -103,4 +103,4 @@ def create_graph(n: int, edges: list[tuple[int, int]]) -> list[list[int]]: edges = list(zip(source, target)) g = create_graph(n_vertices, edges) - assert [[5], [6], [4], [3, 2, 1, 0]] == tarjan(g) + assert tarjan(g) == [[5], [6], [4], [3, 2, 1, 0]] diff --git a/hashes/md5.py b/hashes/md5.py index 2187006ec8a9..622a50d290e1 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -82,8 +82,8 @@ def reformat_hex(i: int) -> bytes: hex_rep = format(i, "08x")[-8:] little_endian_hex = b"" - for i in [3, 2, 1, 0]: - little_endian_hex += hex_rep[2 * i : 2 * i + 2].encode("utf-8") + for j in [3, 2, 1, 0]: + little_endian_hex += hex_rep[2 * j : 2 * j + 2].encode("utf-8") return little_endian_hex diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index 2c5cdc004d1d..d41dc82d5588 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -84,7 +84,6 @@ def __dft(self, which): # 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)] diff --git a/project_euler/problem_034/__init__.py b/project_euler/problem_034/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_034/__init__.py +++ b/project_euler/problem_034/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_035/__init__.py b/project_euler/problem_035/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_035/__init__.py +++ b/project_euler/problem_035/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_037/__init__.py b/project_euler/problem_037/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_037/__init__.py +++ b/project_euler/problem_037/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_037/sol1.py b/project_euler/problem_037/sol1.py index 9c09065f4bd0..c66eb9fb1735 100644 --- a/project_euler/problem_037/sol1.py +++ b/project_euler/problem_037/sol1.py @@ -85,11 +85,10 @@ def validate(n: int) -> bool: >>> validate(3797) True """ - if len(str(n)) > 3 and ( - not is_prime(int(str(n)[-3:])) or not is_prime(int(str(n)[:3])) - ): - return False - return True + return not ( + len(str(n)) > 3 + and (not is_prime(int(str(n)[-3:])) or not is_prime(int(str(n)[:3]))) + ) def compute_truncated_primes(count: int = 11) -> list[int]: diff --git a/project_euler/problem_039/__init__.py b/project_euler/problem_039/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_039/__init__.py +++ b/project_euler/problem_039/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_041/__init__.py b/project_euler/problem_041/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_041/__init__.py +++ b/project_euler/problem_041/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_043/__init__.py b/project_euler/problem_043/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_043/__init__.py +++ b/project_euler/problem_043/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_044/__init__.py b/project_euler/problem_044/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_044/__init__.py +++ b/project_euler/problem_044/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_045/__init__.py b/project_euler/problem_045/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_045/__init__.py +++ b/project_euler/problem_045/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_046/__init__.py b/project_euler/problem_046/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_046/__init__.py +++ b/project_euler/problem_046/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_055/__init__.py b/project_euler/problem_055/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_055/__init__.py +++ b/project_euler/problem_055/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_058/__init__.py b/project_euler/problem_058/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_058/__init__.py +++ b/project_euler/problem_058/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_063/__init__.py b/project_euler/problem_063/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_063/__init__.py +++ b/project_euler/problem_063/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_072/sol1.py b/project_euler/problem_072/sol1.py index 5a28be564556..f09db0673323 100644 --- a/project_euler/problem_072/sol1.py +++ b/project_euler/problem_072/sol1.py @@ -43,7 +43,7 @@ def solution(limit: int = 1_000_000) -> int: ind = np.arange(2 * i, limit + 1, i) # indexes for selection phi[ind] -= phi[ind] // i - return np.sum(phi[2 : limit + 1]) + return int(np.sum(phi[2 : limit + 1])) if __name__ == "__main__": diff --git a/project_euler/problem_089/__init__.py b/project_euler/problem_089/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_089/__init__.py +++ b/project_euler/problem_089/__init__.py @@ -1 +0,0 @@ -# diff --git a/project_euler/problem_097/__init__.py b/project_euler/problem_097/__init__.py index 792d6005489e..e69de29bb2d1 100644 --- a/project_euler/problem_097/__init__.py +++ b/project_euler/problem_097/__init__.py @@ -1 +0,0 @@ -# diff --git a/searches/binary_tree_traversal.py b/searches/binary_tree_traversal.py index 4897ef17299c..47af57f7f94d 100644 --- a/searches/binary_tree_traversal.py +++ b/searches/binary_tree_traversal.py @@ -36,7 +36,7 @@ def build_tree() -> TreeNode: right_node = TreeNode(int(check)) node_found.right = right_node q.put(right_node) - raise + raise ValueError("Something went wrong") def pre_order(node: TreeNode) -> None: @@ -164,8 +164,8 @@ def level_order_actual(node: TreeNode) -> None: if node_dequeued.right: list_.append(node_dequeued.right) print() - for node in list_: - q.put(node) + for inner_node in list_: + q.put(inner_node) # iteration version diff --git a/sorts/external_sort.py b/sorts/external_sort.py index e6b0d47f79f5..3fa7cacc0592 100644 --- a/sorts/external_sort.py +++ b/sorts/external_sort.py @@ -77,10 +77,7 @@ def refresh(self): self.empty.add(i) self.files[i].close() - if len(self.empty) == self.num_buffers: - return False - - return True + return len(self.empty) != self.num_buffers def unshift(self, index): value = self.buffers[index] diff --git a/source/__init__.py b/source/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/strings/can_string_be_rearranged_as_palindrome.py b/strings/can_string_be_rearranged_as_palindrome.py index 21d653db1405..95cda8b72180 100644 --- a/strings/can_string_be_rearranged_as_palindrome.py +++ b/strings/can_string_be_rearranged_as_palindrome.py @@ -72,9 +72,7 @@ def can_string_be_rearranged_as_palindrome(input_str: str = "") -> bool: for character_count in character_freq_dict.values(): if character_count % 2: odd_char += 1 - if odd_char > 1: - return False - return True + return not odd_char > 1 def benchmark(input_str: str = "") -> None: diff --git a/strings/is_valid_email_address.py b/strings/is_valid_email_address.py index 205394f81297..c3bf7df7349d 100644 --- a/strings/is_valid_email_address.py +++ b/strings/is_valid_email_address.py @@ -101,9 +101,7 @@ def is_valid_email_address(email: str) -> bool: return False # (7.) Validate the placement of "." characters - if domain.startswith(".") or domain.endswith(".") or ".." in domain: - return False - return True + return not (domain.startswith(".") or domain.endswith(".") or ".." in domain) if __name__ == "__main__": diff --git a/strings/text_justification.py b/strings/text_justification.py index b0ef12231224..e025edcfe13f 100644 --- a/strings/text_justification.py +++ b/strings/text_justification.py @@ -67,19 +67,19 @@ def justify(line: list, width: int, max_width: int) -> str: answer = [] line: list[str] = [] width = 0 - for word in words: - if width + len(word) + len(line) <= max_width: + for inner_word in words: + if width + len(inner_word) + len(line) <= max_width: # keep adding words until we can fill out max_width # width = sum of length of all words (without overall_spaces_count) - # len(word) = length of current word + # len(inner_word) = length of current inner_word # len(line) = number of overall_spaces_count to insert between words - line.append(word) - width += len(word) + line.append(inner_word) + width += len(inner_word) else: # justify the line and add it to result answer.append(justify(line, width, max_width)) # reset new line and new width - line, width = [word], len(word) + line, width = [inner_word], len(inner_word) remaining_spaces = max_width - width - len(line) answer.append(" ".join(line) + (remaining_spaces + 1) * " ") return answer From c1dc8e97f7992c132c671da2da60da9d926d0fca Mon Sep 17 00:00:00 2001 From: Saurabh Mahapatra <98408932+its-100rabh@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:46:24 +0530 Subject: [PATCH 2749/2908] Create count_vowels.py (#11474) * Create count_vowels.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- strings/count_vowels.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 strings/count_vowels.py diff --git a/strings/count_vowels.py b/strings/count_vowels.py new file mode 100644 index 000000000000..8a52b331c81b --- /dev/null +++ b/strings/count_vowels.py @@ -0,0 +1,34 @@ +def count_vowels(s: str) -> int: + """ + Count the number of vowels in a given string. + + :param s: Input string to count vowels in. + :return: Number of vowels in the input string. + + Examples: + >>> count_vowels("hello world") + 3 + >>> count_vowels("HELLO WORLD") + 3 + >>> count_vowels("123 hello world") + 3 + >>> count_vowels("") + 0 + >>> count_vowels("a quick brown fox") + 5 + >>> count_vowels("the quick BROWN fox") + 5 + >>> count_vowels("PYTHON") + 1 + """ + if not isinstance(s, str): + raise ValueError("Input must be a string") + + vowels = "aeiouAEIOU" + return sum(1 for char in s if char in vowels) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 9190888f89c55d927881c7b08f6df361ab1b0af4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:55:30 +0200 Subject: [PATCH 2750/2908] [pre-commit.ci] pre-commit autoupdate (#11481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1) - [github.com/tox-dev/pyproject-fmt: 2.1.3 → 2.1.4](https://github.com/tox-dev/pyproject-fmt/compare/2.1.3...2.1.4) * updating DIRECTORY.md * grid = np.char.chararray((n, n)) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 1 + graphs/multi_heuristic_astar.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3f5a5e51855..7fd689adca3b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 + rev: v0.5.1 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.1.3" + rev: "2.1.4" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index 04551fad3685..54bb8f148c32 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1260,6 +1260,7 @@ * [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py) * [Capitalize](strings/capitalize.py) * [Check Anagrams](strings/check_anagrams.py) + * [Count Vowels](strings/count_vowels.py) * [Credit Card Validator](strings/credit_card_validator.py) * [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py) diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index 47509beb8efb..38b07e1ca675 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -79,7 +79,7 @@ def key(start: TPos, i: int, goal: TPos, g_function: dict[TPos, float]): def do_something(back_pointer, goal, start): - grid = np.chararray((n, n)) + grid = np.char.chararray((n, n)) for i in range(n): for j in range(n): grid[i][j] = "*" From 2d8f22ab615085d36c53346283528f33b18a3b6d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 21:52:48 +0200 Subject: [PATCH 2751/2908] [pre-commit.ci] pre-commit autoupdate (#11489) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.1 → v0.5.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.1...v0.5.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fd689adca3b..c72b55fdec44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.1 + rev: v0.5.2 hooks: - id: ruff - id: ruff-format From d9ded0727a7a209bfcbf9bd81c5c75183cfd026f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:40:10 +0200 Subject: [PATCH 2752/2908] [pre-commit.ci] pre-commit autoupdate (#11495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.2 → v0.5.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.2...v0.5.4) - [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0) * ruff rule PLR1714 Consider merging multiple comparisons * ruff rule RUF005 Consider `[*self.urls, "", "#"]` instead of concatenation * Update emails_from_url.py * Update emails_from_url.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- web_programming/emails_from_url.py | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c72b55fdec44..e9f57a7b746a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.2 + rev: v0.5.4 hooks: - id: ruff - id: ruff-format @@ -47,7 +47,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.1 + rev: v1.11.0 hooks: - id: mypy args: diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index 43fd78dcf5a4..d41dc4893608 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -31,12 +31,7 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None # Check the list of defined attributes. for name, value in attrs: # If href is defined, not empty nor # print it and not already in urls. - if ( - name == "href" - and value != "#" - and value != "" - and value not in self.urls - ): + if name == "href" and value not in (*self.urls, "", "#"): url = parse.urljoin(self.domain, value) self.urls.append(url) From 146800307c5d2a4393d57b7c97c63b89a21abba1 Mon Sep 17 00:00:00 2001 From: Ihor Pryyma <83470037+Ihor-Pryyma@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:56:31 +0300 Subject: [PATCH 2753/2908] Add doctests to interpolation_search.py (#11492) * Add doctests to interpolation_search.py * update docs * update tests * update tests 2 * clean code --- searches/interpolation_search.py | 139 ++++++++++++++++--------------- 1 file changed, 70 insertions(+), 69 deletions(-) diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index 0591788aa40b..cb3e0011d0da 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -3,13 +3,41 @@ """ -def interpolation_search(sorted_collection, item): - """Pure implementation of interpolation search algorithm in Python - Be careful collection must be ascending sorted, otherwise result will be - unpredictable - :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 +def interpolation_search(sorted_collection: list[int], item: int) -> int | None: + """ + Searches for an item in a sorted collection by interpolation search algorithm. + + Args: + sorted_collection: sorted list of integers + item: item value to search + + Returns: + int: The index of the found item, or None if the item is not found. + Examples: + >>> interpolation_search([1, 2, 3, 4, 5], 2) + 1 + >>> interpolation_search([1, 2, 3, 4, 5], 4) + 3 + >>> interpolation_search([1, 2, 3, 4, 5], 6) is None + True + >>> interpolation_search([], 1) is None + True + >>> interpolation_search([100], 100) + 0 + >>> interpolation_search([1, 2, 3, 4, 5], 0) is None + True + >>> interpolation_search([1, 2, 3, 4, 5], 7) is None + True + >>> interpolation_search([1, 2, 3, 4, 5], 2) + 1 + >>> interpolation_search([1, 2, 3, 4, 5], 0) is None + True + >>> interpolation_search([1, 2, 3, 4, 5], 7) is None + True + >>> interpolation_search([1, 2, 3, 4, 5], 2) + 1 + >>> interpolation_search([5, 5, 5, 5, 5], 3) is None + True """ left = 0 right = len(sorted_collection) - 1 @@ -19,8 +47,7 @@ def interpolation_search(sorted_collection, item): if sorted_collection[left] == sorted_collection[right]: if sorted_collection[left] == item: return left - else: - return None + return None point = left + ((item - sorted_collection[left]) * (right - left)) // ( sorted_collection[right] - sorted_collection[left] @@ -33,7 +60,7 @@ def interpolation_search(sorted_collection, item): current_item = sorted_collection[point] if current_item == item: return point - elif point < left: + if point < left: right = left left = point elif point > right: @@ -46,22 +73,42 @@ def interpolation_search(sorted_collection, item): return None -def interpolation_search_by_recursion(sorted_collection, item, left, right): +def interpolation_search_by_recursion( + sorted_collection: list[int], item: int, left: int = 0, right: int | None = None +) -> int | None: """Pure implementation of interpolation search algorithm in Python by recursion 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 ascending sorted collection with comparable items - :param item: item value to search - :return: index of found item or None if item is not found - """ + Args: + sorted_collection: some sorted collection with comparable items + item: item value to search + left: left index in collection + right: right index in collection + + Returns: + index of item in collection or None if item is not present + + Examples: + >>> interpolation_search_by_recursion([0, 5, 7, 10, 15], 0) + 0 + >>> interpolation_search_by_recursion([0, 5, 7, 10, 15], 15) + 4 + >>> interpolation_search_by_recursion([0, 5, 7, 10, 15], 5) + 1 + >>> interpolation_search_by_recursion([0, 5, 7, 10, 15], 100) is None + True + >>> interpolation_search_by_recursion([5, 5, 5, 5, 5], 3) is None + True + """ + if right is None: + right = len(sorted_collection) - 1 # avoid divided by 0 during interpolation if sorted_collection[left] == sorted_collection[right]: if sorted_collection[left] == item: return left - else: - return None + return None point = left + ((item - sorted_collection[left]) * (right - left)) // ( sorted_collection[right] - sorted_collection[left] @@ -73,64 +120,18 @@ def interpolation_search_by_recursion(sorted_collection, item, left, right): if sorted_collection[point] == item: return point - elif point < left: + if point < left: return interpolation_search_by_recursion(sorted_collection, item, point, left) - elif point > right: + if point > right: return interpolation_search_by_recursion(sorted_collection, item, right, left) - elif sorted_collection[point] > item: + 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 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 + return interpolation_search_by_recursion(sorted_collection, item, point + 1, right) if __name__ == "__main__": - import sys + import doctest - """ - 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 = 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(f"{target} found at positions: {result}") - else: - print("Not found") + doctest.testmod() From 240d1b7cd47df86d86b26f4d658b26e3656a27d9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:41:09 +0200 Subject: [PATCH 2754/2908] [pre-commit.ci] pre-commit autoupdate (#11500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.4 → v0.5.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.4...v0.5.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e9f57a7b746a..09542dd7e255 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.4 + rev: v0.5.5 hooks: - id: ruff - id: ruff-format From dfe67954f7218703e3aadca1768a0ad4c97c73a1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 00:11:14 +0200 Subject: [PATCH 2755/2908] [pre-commit.ci] pre-commit autoupdate (#11507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.5 → v0.5.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.5...v0.5.6) - [github.com/tox-dev/pyproject-fmt: 2.1.4 → 2.2.1](https://github.com/tox-dev/pyproject-fmt/compare/2.1.4...2.2.1) - [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 09542dd7e255..c112b6d86da0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.5 + rev: v0.5.6 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.1.4" + rev: "2.2.1" hooks: - id: pyproject-fmt @@ -47,7 +47,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.0 + rev: v1.11.1 hooks: - id: mypy args: From ed1900f1b37234f25486cfb3223988b3295a5549 Mon Sep 17 00:00:00 2001 From: CarlosZamG <54159355+CarlosZamG@users.noreply.github.com> Date: Tue, 6 Aug 2024 02:44:58 -0600 Subject: [PATCH 2756/2908] Fix typo in integration_by_simpson_approx.py (#11501) --- maths/numerical_analysis/integration_by_simpson_approx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/numerical_analysis/integration_by_simpson_approx.py b/maths/numerical_analysis/integration_by_simpson_approx.py index f77ae76135ee..934299997aac 100644 --- a/maths/numerical_analysis/integration_by_simpson_approx.py +++ b/maths/numerical_analysis/integration_by_simpson_approx.py @@ -4,7 +4,7 @@ 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 +The approximation proposed by Thomas Simpson in 1743 is one way to calculate integration. ( read article : https://cp-algorithms.com/num_methods/simpson-integration.html ) From 31c424fc8654877d3731bdcb50dcc1ce5d6860ab Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 22:55:46 +0200 Subject: [PATCH 2757/2908] [pre-commit.ci] pre-commit autoupdate (#11515) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.6 → v0.5.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.6...v0.5.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c112b6d86da0..c797af6c5088 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.6 + rev: v0.5.7 hooks: - id: ruff - id: ruff-format From 48418280b1331d1efaa14dc48da62d313dfcee43 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Thu, 22 Aug 2024 09:42:40 -0700 Subject: [PATCH 2758/2908] Remove separate directory for `gaussian_elimination_pivoting.py` (#11445) * updating DIRECTORY.md * Remove separate directory for gaussian_elimination_pivoting.py Delete the directory linear_algebra/src/gaussian_elimination_pivoting/ and move its algorithm file, gaussian_elimination_pivoting.py, into the parent src/ directory. The gaussian_elimination_pivoting/ directory only exists because gaussian_elimination_pivoting.py reads an example numpy array from matrix.txt, but this input file and IO operation is entirely unnecessary because gaussian_elimination_pivoting.py already has the exact same array hard-coded into a variable. * updating DIRECTORY.md --------- Co-authored-by: tianyizheng02 --- DIRECTORY.md | 3 +- .../gaussian_elimination_pivoting.py | 33 ++++++++----------- .../gaussian_elimination_pivoting/__init__.py | 0 .../gaussian_elimination_pivoting/matrix.txt | 4 --- 4 files changed, 14 insertions(+), 26 deletions(-) rename linear_algebra/src/{gaussian_elimination_pivoting => }/gaussian_elimination_pivoting.py (83%) delete mode 100644 linear_algebra/src/gaussian_elimination_pivoting/__init__.py delete mode 100644 linear_algebra/src/gaussian_elimination_pivoting/matrix.txt diff --git a/DIRECTORY.md b/DIRECTORY.md index 54bb8f148c32..11de569a2c25 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -540,8 +540,7 @@ * [Lu Decomposition](linear_algebra/lu_decomposition.py) * Src * [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py) - * Gaussian Elimination Pivoting - * [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py) + * [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting.py) * [Lib](linear_algebra/src/lib.py) * [Polynom For Points](linear_algebra/src/polynom_for_points.py) * [Power Iteration](linear_algebra/src/power_iteration.py) diff --git a/linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py b/linear_algebra/src/gaussian_elimination_pivoting.py similarity index 83% rename from linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py rename to linear_algebra/src/gaussian_elimination_pivoting.py index 2a86350e9fc6..ecaacce19a31 100644 --- a/linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py +++ b/linear_algebra/src/gaussian_elimination_pivoting.py @@ -1,15 +1,5 @@ import numpy as np -matrix = np.array( - [ - [5.0, -5.0, -3.0, 4.0, -11.0], - [1.0, -4.0, 6.0, -4.0, -10.0], - [-2.0, -5.0, 4.0, -5.0, -12.0], - [-3.0, -3.0, 5.0, -5.0, 8.0], - ], - dtype=float, -) - def solve_linear_system(matrix: np.ndarray) -> np.ndarray: """ @@ -87,15 +77,18 @@ def solve_linear_system(matrix: np.ndarray) -> np.ndarray: if __name__ == "__main__": from doctest import testmod - from pathlib import Path testmod() - file_path = Path(__file__).parent / "matrix.txt" - try: - matrix = np.loadtxt(file_path) - except FileNotFoundError: - print(f"Error: {file_path} not found. Using default matrix instead.") - - # Example usage: - print(f"Matrix:\n{matrix}") - print(f"{solve_linear_system(matrix) = }") + + example_matrix = np.array( + [ + [5.0, -5.0, -3.0, 4.0, -11.0], + [1.0, -4.0, 6.0, -4.0, -10.0], + [-2.0, -5.0, 4.0, -5.0, -12.0], + [-3.0, -3.0, 5.0, -5.0, 8.0], + ], + dtype=float, + ) + + print(f"Matrix:\n{example_matrix}") + print(f"{solve_linear_system(example_matrix) = }") diff --git a/linear_algebra/src/gaussian_elimination_pivoting/__init__.py b/linear_algebra/src/gaussian_elimination_pivoting/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/linear_algebra/src/gaussian_elimination_pivoting/matrix.txt b/linear_algebra/src/gaussian_elimination_pivoting/matrix.txt deleted file mode 100644 index dd895ad856ee..000000000000 --- a/linear_algebra/src/gaussian_elimination_pivoting/matrix.txt +++ /dev/null @@ -1,4 +0,0 @@ -5.0 -5.0 -3.0 4.0 -11.0 -1.0 -4.0 6.0 -4.0 -10.0 --2.0 -5.0 4.0 -5.0 -12.0 --3.0 -3.0 5.0 -5.0 8.0 \ No newline at end of file From e3fa014a5ab4887f93aae7bb193b152bb155323a Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 25 Aug 2024 18:33:11 +0300 Subject: [PATCH 2759/2908] Fix ruff (#11527) * updating DIRECTORY.md * Fix ruff * Fix * Fix * Fix * Revert "Fix" This reverts commit 5bc3bf342208dd707da02dea7173c059317b6bc6. * find_max.py: noqa: PLR1730 --------- Co-authored-by: MaximSmolskiy Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- .../binary_tree/number_of_possible_binary_trees.py | 3 +-- divide_and_conquer/closest_pair_of_points.py | 6 ++---- graphs/kahns_algorithm_long.py | 3 +-- maths/find_max.py | 2 +- maths/special_numbers/bell_numbers.py | 3 +-- matrix/tests/test_matrix_operation.py | 12 ++++++------ project_euler/problem_008/sol1.py | 3 +-- project_euler/problem_009/sol2.py | 3 +-- project_euler/problem_011/sol1.py | 3 +-- project_euler/problem_011/sol2.py | 12 ++++-------- scheduling/highest_response_ratio_next.py | 3 +-- scheduling/shortest_job_first.py | 3 +-- 13 files changed, 22 insertions(+), 36 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c797af6c5088..06f8ba00494a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.7 + rev: v0.6.2 hooks: - id: ruff - id: ruff-format 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 1c3dff37e7d9..b39cbafd0a61 100644 --- a/data_structures/binary_tree/number_of_possible_binary_trees.py +++ b/data_structures/binary_tree/number_of_possible_binary_trees.py @@ -31,8 +31,7 @@ def binomial_coefficient(n: int, k: int) -> int: """ result = 1 # To kept the Calculated Value # Since C(n, k) = C(n, n-k) - if k > (n - k): - k = n - k + k = min(k, n - k) # Calculate C(n,k) for i in range(k): result *= n - i diff --git a/divide_and_conquer/closest_pair_of_points.py b/divide_and_conquer/closest_pair_of_points.py index cb7fa00d1c8f..534cbba9b718 100644 --- a/divide_and_conquer/closest_pair_of_points.py +++ b/divide_and_conquer/closest_pair_of_points.py @@ -54,8 +54,7 @@ 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): current_dis = euclidean_distance_sqr(points[i], points[j]) - if current_dis < min_dis: - min_dis = current_dis + min_dis = min(min_dis, current_dis) return min_dis @@ -76,8 +75,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): current_dis = euclidean_distance_sqr(points[i], points[j]) - if current_dis < min_dis: - min_dis = current_dis + min_dis = min(min_dis, current_dis) return min_dis diff --git a/graphs/kahns_algorithm_long.py b/graphs/kahns_algorithm_long.py index 63cbeb909a8a..1f16b90c0745 100644 --- a/graphs/kahns_algorithm_long.py +++ b/graphs/kahns_algorithm_long.py @@ -17,8 +17,7 @@ def longest_distance(graph): for x in graph[vertex]: indegree[x] -= 1 - if long_dist[vertex] + 1 > long_dist[x]: - long_dist[x] = long_dist[vertex] + 1 + long_dist[x] = max(long_dist[x], long_dist[vertex] + 1) if indegree[x] == 0: queue.append(x) diff --git a/maths/find_max.py b/maths/find_max.py index 729a80ab421c..4765d300634e 100644 --- a/maths/find_max.py +++ b/maths/find_max.py @@ -20,7 +20,7 @@ def find_max_iterative(nums: list[int | float]) -> int | float: raise ValueError("find_max_iterative() arg is an empty sequence") max_num = nums[0] for x in nums: - if x > max_num: + if x > max_num: # noqa: PLR1730 max_num = x return max_num diff --git a/maths/special_numbers/bell_numbers.py b/maths/special_numbers/bell_numbers.py index 660ec6e6aa09..5d99334d7add 100644 --- a/maths/special_numbers/bell_numbers.py +++ b/maths/special_numbers/bell_numbers.py @@ -61,8 +61,7 @@ def _binomial_coefficient(total_elements: int, elements_to_choose: int) -> int: if elements_to_choose in {0, total_elements}: return 1 - if elements_to_choose > total_elements - elements_to_choose: - elements_to_choose = total_elements - elements_to_choose + elements_to_choose = min(elements_to_choose, total_elements - elements_to_choose) coefficient = 1 for i in range(elements_to_choose): diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index addc870ca205..21ed7e371fd8 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -31,7 +31,7 @@ logger.addHandler(stream_handler) -@pytest.mark.mat_ops() +@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)] ) @@ -51,7 +51,7 @@ def test_addition(mat1, mat2): matop.add(mat1, mat2) -@pytest.mark.mat_ops() +@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)] ) @@ -71,7 +71,7 @@ def test_subtraction(mat1, mat2): assert matop.subtract(mat1, mat2) -@pytest.mark.mat_ops() +@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)] ) @@ -93,21 +93,21 @@ def test_multiplication(mat1, mat2): assert matop.subtract(mat1, mat2) -@pytest.mark.mat_ops() +@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() +@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.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): diff --git a/project_euler/problem_008/sol1.py b/project_euler/problem_008/sol1.py index adbac8d5ad1f..a38b2045f996 100644 --- a/project_euler/problem_008/sol1.py +++ b/project_euler/problem_008/sol1.py @@ -75,8 +75,7 @@ def solution(n: str = N) -> int: product = 1 for j in range(13): product *= int(n[i + j]) - if product > largest_product: - largest_product = product + largest_product = max(largest_product, product) return largest_product diff --git a/project_euler/problem_009/sol2.py b/project_euler/problem_009/sol2.py index 722ad522ee45..443a529571cc 100644 --- a/project_euler/problem_009/sol2.py +++ b/project_euler/problem_009/sol2.py @@ -39,8 +39,7 @@ def solution(n: int = 1000) -> int: c = n - a - b if c * c == (a * a + b * b): candidate = a * b * c - if candidate >= product: - product = candidate + product = max(product, candidate) return product diff --git a/project_euler/problem_011/sol1.py b/project_euler/problem_011/sol1.py index ad45f0983a7c..3d3e864f927b 100644 --- a/project_euler/problem_011/sol1.py +++ b/project_euler/problem_011/sol1.py @@ -63,8 +63,7 @@ def largest_product(grid): max_product = max( vert_product, horz_product, lr_diag_product, rl_diag_product ) - if max_product > largest: - largest = max_product + largest = max(largest, max_product) return largest diff --git a/project_euler/problem_011/sol2.py b/project_euler/problem_011/sol2.py index 09bf315702c5..7637deafc3cb 100644 --- a/project_euler/problem_011/sol2.py +++ b/project_euler/problem_011/sol2.py @@ -45,15 +45,13 @@ def solution(): for i in range(20): for j in range(17): temp = grid[i][j] * grid[i][j + 1] * grid[i][j + 2] * grid[i][j + 3] - if temp > maximum: - maximum = temp + maximum = max(maximum, temp) # down for i in range(17): for j in range(20): temp = grid[i][j] * grid[i + 1][j] * grid[i + 2][j] * grid[i + 3][j] - if temp > maximum: - maximum = temp + maximum = max(maximum, temp) # diagonal 1 for i in range(17): @@ -64,8 +62,7 @@ def solution(): * grid[i + 2][j + 2] * grid[i + 3][j + 3] ) - if temp > maximum: - maximum = temp + maximum = max(maximum, temp) # diagonal 2 for i in range(17): @@ -76,8 +73,7 @@ def solution(): * grid[i + 2][j - 2] * grid[i + 3][j - 3] ) - if temp > maximum: - maximum = temp + maximum = max(maximum, temp) return maximum diff --git a/scheduling/highest_response_ratio_next.py b/scheduling/highest_response_ratio_next.py index b549835616bf..f858be2ee44a 100644 --- a/scheduling/highest_response_ratio_next.py +++ b/scheduling/highest_response_ratio_next.py @@ -46,8 +46,7 @@ def calculate_turn_around_time( i = 0 while finished_process[i] == 1: i += 1 - if current_time < arrival_time[i]: - current_time = arrival_time[i] + current_time = max(current_time, arrival_time[i]) response_ratio = 0 # Index showing the location of the process being performed diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index 6899ec87c591..91012ee3ac35 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -66,8 +66,7 @@ def calculate_waitingtime( finar = finish_time - arrival_time[short] waiting_time[short] = finar - burst_time[short] - if waiting_time[short] < 0: - waiting_time[short] = 0 + waiting_time[short] = max(waiting_time[short], 0) # Increment time increment_time += 1 From c8e131b86c35c8fa4ca14aa85edbd4a106575882 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 21:49:42 +0200 Subject: [PATCH 2760/2908] [pre-commit.ci] pre-commit autoupdate (#11522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/abravalheri/validate-pyproject: v0.18 → v0.19](https://github.com/abravalheri/validate-pyproject/compare/v0.18...v0.19) - [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 06f8ba00494a..2724dff230e7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,12 +42,12 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.18 + rev: v0.19 hooks: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.1 + rev: v1.11.2 hooks: - id: mypy args: From bd8085cfc18784a21d792a44dcd683e11e802c6b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:41:55 +0200 Subject: [PATCH 2761/2908] [pre-commit.ci] pre-commit autoupdate (#11535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.2 → v0.6.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.2...v0.6.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2724dff230e7..e363197497ac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.2 + rev: v0.6.3 hooks: - id: ruff - id: ruff-format From f16d38f26f13683cf3ea75caf0474dedde059b86 Mon Sep 17 00:00:00 2001 From: Ramy <126559907+Ramy-Badr-Ahmed@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:39:09 +0200 Subject: [PATCH 2762/2908] kd tree data structure implementation (#11532) * Implemented KD-Tree Data Structure * Implemented KD-Tree Data Structure. updated DIRECTORY.md. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Create __init__.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Replaced legacy `np.random.rand` call with `np.random.Generator` in kd_tree/example_usage.py * Replaced legacy `np.random.rand` call with `np.random.Generator` in kd_tree/hypercube_points.py * added typehints and docstrings * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docstring for search() * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added tests. Updated docstrings/typehints * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated tests and used | for type annotations * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * E501 for build_kdtree.py, hypercube_points.py, nearest_neighbour_search.py * I001 for example_usage.py and test_kdtree.py * I001 for example_usage.py and test_kdtree.py * Update data_structures/kd_tree/build_kdtree.py Co-authored-by: Christian Clauss * Update data_structures/kd_tree/example/hypercube_points.py Co-authored-by: Christian Clauss * Update data_structures/kd_tree/example/hypercube_points.py Co-authored-by: Christian Clauss * Added new test cases requested in Review. Refactored the test_build_kdtree() to include various checks. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Considered ruff errors * Considered ruff errors * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update kd_node.py * imported annotations from __future__ * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 6 ++ data_structures/kd_tree/__init__.py | 0 data_structures/kd_tree/build_kdtree.py | 35 ++++++ data_structures/kd_tree/example/__init__.py | 0 .../kd_tree/example/example_usage.py | 38 +++++++ .../kd_tree/example/hypercube_points.py | 21 ++++ data_structures/kd_tree/kd_node.py | 30 ++++++ .../kd_tree/nearest_neighbour_search.py | 71 +++++++++++++ data_structures/kd_tree/tests/__init__.py | 0 data_structures/kd_tree/tests/test_kdtree.py | 100 ++++++++++++++++++ 10 files changed, 301 insertions(+) create mode 100644 data_structures/kd_tree/__init__.py create mode 100644 data_structures/kd_tree/build_kdtree.py create mode 100644 data_structures/kd_tree/example/__init__.py create mode 100644 data_structures/kd_tree/example/example_usage.py create mode 100644 data_structures/kd_tree/example/hypercube_points.py create mode 100644 data_structures/kd_tree/kd_node.py create mode 100644 data_structures/kd_tree/nearest_neighbour_search.py create mode 100644 data_structures/kd_tree/tests/__init__.py create mode 100644 data_structures/kd_tree/tests/test_kdtree.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 11de569a2c25..1ca537b991c8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -285,6 +285,12 @@ * Trie * [Radix Tree](data_structures/trie/radix_tree.py) * [Trie](data_structures/trie/trie.py) + * KD Tree + * [KD Tree Node](data_structures/kd_tree/kd_node.py) + * [Build KD Tree](data_structures/kd_tree/build_kdtree.py) + * [Nearest Neighbour Search](data_structures/kd_tree/nearest_neighbour_search.py) + * [Hypercibe Points](data_structures/kd_tree/example/hypercube_points.py) + * [Example Usage](data_structures/kd_tree/example/example_usage.py) ## Digital Image Processing * [Change Brightness](digital_image_processing/change_brightness.py) diff --git a/data_structures/kd_tree/__init__.py b/data_structures/kd_tree/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/kd_tree/build_kdtree.py b/data_structures/kd_tree/build_kdtree.py new file mode 100644 index 000000000000..c5b800a2c992 --- /dev/null +++ b/data_structures/kd_tree/build_kdtree.py @@ -0,0 +1,35 @@ +from data_structures.kd_tree.kd_node import KDNode + + +def build_kdtree(points: list[list[float]], depth: int = 0) -> KDNode | None: + """ + Builds a KD-Tree from a list of points. + + Args: + points: The list of points to build the KD-Tree from. + depth: The current depth in the tree + (used to determine axis for splitting). + + Returns: + The root node of the KD-Tree, + or None if no points are provided. + """ + if not points: + return None + + k = len(points[0]) # Dimensionality of the points + axis = depth % k + + # Sort point list and choose median as pivot element + points.sort(key=lambda point: point[axis]) + median_idx = len(points) // 2 + + # Create node and construct subtrees + left_points = points[:median_idx] + right_points = points[median_idx + 1 :] + + return KDNode( + point=points[median_idx], + left=build_kdtree(left_points, depth + 1), + right=build_kdtree(right_points, depth + 1), + ) diff --git a/data_structures/kd_tree/example/__init__.py b/data_structures/kd_tree/example/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/kd_tree/example/example_usage.py b/data_structures/kd_tree/example/example_usage.py new file mode 100644 index 000000000000..e270f0cdd245 --- /dev/null +++ b/data_structures/kd_tree/example/example_usage.py @@ -0,0 +1,38 @@ +import numpy as np + +from data_structures.kd_tree.build_kdtree import build_kdtree +from data_structures.kd_tree.example.hypercube_points import hypercube_points +from data_structures.kd_tree.nearest_neighbour_search import nearest_neighbour_search + + +def main() -> None: + """ + Demonstrates the use of KD-Tree by building it from random points + in a 10-dimensional hypercube and performing a nearest neighbor search. + """ + num_points: int = 5000 + cube_size: float = 10.0 # Size of the hypercube (edge length) + num_dimensions: int = 10 + + # Generate random points within the hypercube + points: np.ndarray = hypercube_points(num_points, cube_size, num_dimensions) + hypercube_kdtree = build_kdtree(points.tolist()) + + # Generate a random query point within the same space + rng = np.random.default_rng() + query_point: list[float] = rng.random(num_dimensions).tolist() + + # Perform nearest neighbor search + nearest_point, nearest_dist, nodes_visited = nearest_neighbour_search( + hypercube_kdtree, query_point + ) + + # Print the results + print(f"Query point: {query_point}") + print(f"Nearest point: {nearest_point}") + print(f"Distance: {nearest_dist:.4f}") + print(f"Nodes visited: {nodes_visited}") + + +if __name__ == "__main__": + main() diff --git a/data_structures/kd_tree/example/hypercube_points.py b/data_structures/kd_tree/example/hypercube_points.py new file mode 100644 index 000000000000..2d8800ac9338 --- /dev/null +++ b/data_structures/kd_tree/example/hypercube_points.py @@ -0,0 +1,21 @@ +import numpy as np + + +def hypercube_points( + num_points: int, hypercube_size: float, num_dimensions: int +) -> np.ndarray: + """ + Generates random points uniformly distributed within an n-dimensional hypercube. + + Args: + num_points: Number of points to generate. + hypercube_size: Size of the hypercube. + num_dimensions: Number of dimensions of the hypercube. + + Returns: + An array of shape (num_points, num_dimensions) + with generated points. + """ + rng = np.random.default_rng() + shape = (num_points, num_dimensions) + return hypercube_size * rng.random(shape) diff --git a/data_structures/kd_tree/kd_node.py b/data_structures/kd_tree/kd_node.py new file mode 100644 index 000000000000..e1011027938d --- /dev/null +++ b/data_structures/kd_tree/kd_node.py @@ -0,0 +1,30 @@ +from __future__ import annotations + + +class KDNode: + """ + Represents a node in a KD-Tree. + + Attributes: + point: The point stored in this node. + left: The left child node. + right: The right child node. + """ + + def __init__( + self, + point: list[float], + left: KDNode | None = None, + right: KDNode | None = None, + ) -> None: + """ + Initializes a KDNode with the given point and child nodes. + + Args: + point (list[float]): The point stored in this node. + left (Optional[KDNode]): The left child node. + right (Optional[KDNode]): The right child node. + """ + self.point = point + self.left = left + self.right = right diff --git a/data_structures/kd_tree/nearest_neighbour_search.py b/data_structures/kd_tree/nearest_neighbour_search.py new file mode 100644 index 000000000000..d9727736f21c --- /dev/null +++ b/data_structures/kd_tree/nearest_neighbour_search.py @@ -0,0 +1,71 @@ +from data_structures.kd_tree.kd_node import KDNode + + +def nearest_neighbour_search( + root: KDNode | None, query_point: list[float] +) -> tuple[list[float] | None, float, int]: + """ + Performs a nearest neighbor search in a KD-Tree for a given query point. + + Args: + root (KDNode | None): The root node of the KD-Tree. + query_point (list[float]): The point for which the nearest neighbor + is being searched. + + Returns: + tuple[list[float] | None, float, int]: + - The nearest point found in the KD-Tree to the query point, + or None if no point is found. + - The squared distance to the nearest point. + - The number of nodes visited during the search. + """ + nearest_point: list[float] | None = None + nearest_dist: float = float("inf") + nodes_visited: int = 0 + + def search(node: KDNode | None, depth: int = 0) -> None: + """ + Recursively searches for the nearest neighbor in the KD-Tree. + + Args: + node: The current node in the KD-Tree. + depth: The current depth in the KD-Tree. + """ + nonlocal nearest_point, nearest_dist, nodes_visited + if node is None: + return + + nodes_visited += 1 + + # Calculate the current distance (squared distance) + current_point = node.point + current_dist = sum( + (query_coord - point_coord) ** 2 + for query_coord, point_coord in zip(query_point, current_point) + ) + + # Update nearest point if the current node is closer + if nearest_point is None or current_dist < nearest_dist: + nearest_point = current_point + nearest_dist = current_dist + + # Determine which subtree to search first (based on axis and query point) + k = len(query_point) # Dimensionality of points + axis = depth % k + + if query_point[axis] <= current_point[axis]: + nearer_subtree = node.left + further_subtree = node.right + else: + nearer_subtree = node.right + further_subtree = node.left + + # Search the nearer subtree first + search(nearer_subtree, depth + 1) + + # If the further subtree has a closer point + if (query_point[axis] - current_point[axis]) ** 2 < nearest_dist: + search(further_subtree, depth + 1) + + search(root, 0) + return nearest_point, nearest_dist, nodes_visited diff --git a/data_structures/kd_tree/tests/__init__.py b/data_structures/kd_tree/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/kd_tree/tests/test_kdtree.py b/data_structures/kd_tree/tests/test_kdtree.py new file mode 100644 index 000000000000..81f2cc990074 --- /dev/null +++ b/data_structures/kd_tree/tests/test_kdtree.py @@ -0,0 +1,100 @@ +import numpy as np +import pytest + +from data_structures.kd_tree.build_kdtree import build_kdtree +from data_structures.kd_tree.example.hypercube_points import hypercube_points +from data_structures.kd_tree.kd_node import KDNode +from data_structures.kd_tree.nearest_neighbour_search import nearest_neighbour_search + + +@pytest.mark.parametrize( + ("num_points", "cube_size", "num_dimensions", "depth", "expected_result"), + [ + (0, 10.0, 2, 0, None), # Empty points list + (10, 10.0, 2, 2, KDNode), # Depth = 2, 2D points + (10, 10.0, 3, -2, KDNode), # Depth = -2, 3D points + ], +) +def test_build_kdtree(num_points, cube_size, num_dimensions, depth, expected_result): + """ + Test that KD-Tree is built correctly. + + Cases: + - Empty points list. + - Positive depth value. + - Negative depth value. + """ + points = ( + hypercube_points(num_points, cube_size, num_dimensions).tolist() + if num_points > 0 + else [] + ) + + kdtree = build_kdtree(points, depth=depth) + + if expected_result is None: + # Empty points list case + assert kdtree is None, f"Expected None for empty points list, got {kdtree}" + else: + # Check if root node is not None + assert kdtree is not None, "Expected a KDNode, got None" + + # Check if root has correct dimensions + assert ( + len(kdtree.point) == num_dimensions + ), f"Expected point dimension {num_dimensions}, got {len(kdtree.point)}" + + # Check that the tree is balanced to some extent (simplistic check) + assert isinstance( + kdtree, KDNode + ), f"Expected KDNode instance, got {type(kdtree)}" + + +def test_nearest_neighbour_search(): + """ + Test the nearest neighbor search function. + """ + num_points = 10 + cube_size = 10.0 + num_dimensions = 2 + points = hypercube_points(num_points, cube_size, num_dimensions) + kdtree = build_kdtree(points.tolist()) + + rng = np.random.default_rng() + query_point = rng.random(num_dimensions).tolist() + + nearest_point, nearest_dist, nodes_visited = nearest_neighbour_search( + kdtree, query_point + ) + + # Check that nearest point is not None + assert nearest_point is not None + + # Check that distance is a non-negative number + assert nearest_dist >= 0 + + # Check that nodes visited is a non-negative integer + assert nodes_visited >= 0 + + +def test_edge_cases(): + """ + Test edge cases such as an empty KD-Tree. + """ + empty_kdtree = build_kdtree([]) + query_point = [0.0] * 2 # Using a default 2D query point + + nearest_point, nearest_dist, nodes_visited = nearest_neighbour_search( + empty_kdtree, query_point + ) + + # With an empty KD-Tree, nearest_point should be None + assert nearest_point is None + assert nearest_dist == float("inf") + assert nodes_visited == 0 + + +if __name__ == "__main__": + import pytest + + pytest.main() From 729c1f923bb621ed246983a5d3309135c3b1fc8c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:15:17 +0200 Subject: [PATCH 2763/2908] [pre-commit.ci] pre-commit autoupdate (#11557) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.3 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.3...v0.6.4) - [github.com/tox-dev/pyproject-fmt: 2.2.1 → 2.2.3](https://github.com/tox-dev/pyproject-fmt/compare/2.2.1...2.2.3) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e363197497ac..ff76e87a3aa1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.3 + rev: v0.6.4 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.2.1" + rev: "2.2.3" hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index 1ca537b991c8..e965d3b32ccf 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -243,6 +243,15 @@ * [Min Heap](data_structures/heap/min_heap.py) * [Randomized Heap](data_structures/heap/randomized_heap.py) * [Skew Heap](data_structures/heap/skew_heap.py) + * Kd Tree + * [Build Kdtree](data_structures/kd_tree/build_kdtree.py) + * Example + * [Example Usage](data_structures/kd_tree/example/example_usage.py) + * [Hypercube Points](data_structures/kd_tree/example/hypercube_points.py) + * [Kd Node](data_structures/kd_tree/kd_node.py) + * [Nearest Neighbour Search](data_structures/kd_tree/nearest_neighbour_search.py) + * Tests + * [Test Kdtree](data_structures/kd_tree/tests/test_kdtree.py) * Linked List * [Circular Linked List](data_structures/linked_list/circular_linked_list.py) * [Deque Doubly](data_structures/linked_list/deque_doubly.py) @@ -285,12 +294,6 @@ * Trie * [Radix Tree](data_structures/trie/radix_tree.py) * [Trie](data_structures/trie/trie.py) - * KD Tree - * [KD Tree Node](data_structures/kd_tree/kd_node.py) - * [Build KD Tree](data_structures/kd_tree/build_kdtree.py) - * [Nearest Neighbour Search](data_structures/kd_tree/nearest_neighbour_search.py) - * [Hypercibe Points](data_structures/kd_tree/example/hypercube_points.py) - * [Example Usage](data_structures/kd_tree/example/example_usage.py) ## Digital Image Processing * [Change Brightness](digital_image_processing/change_brightness.py) From 77bbe584216c0925e249e0baab77fef34561ecaa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 00:14:55 +0200 Subject: [PATCH 2764/2908] [pre-commit.ci] pre-commit autoupdate (#11568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.4 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.4...v0.6.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff76e87a3aa1..a4a45686537d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.4 + rev: v0.6.5 hooks: - id: ruff - id: ruff-format From 50cc00bb2da26fd234dabdfa7f93c96d6b7d72d5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:45:14 +0200 Subject: [PATCH 2765/2908] [pre-commit.ci] pre-commit autoupdate (#11579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.5 → v0.6.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.5...v0.6.7) - [github.com/tox-dev/pyproject-fmt: 2.2.3 → 2.2.4](https://github.com/tox-dev/pyproject-fmt/compare/2.2.3...2.2.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4a45686537d..7b219597f7b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.5 + rev: v0.6.7 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.2.3" + rev: "2.2.4" hooks: - id: pyproject-fmt From 9b5641d2d333d04eb474ecbcb15c40ccf18a3d7b Mon Sep 17 00:00:00 2001 From: apples53 Date: Tue, 24 Sep 2024 13:00:36 +0530 Subject: [PATCH 2766/2908] balance parenthesis (add closing bracket) (#11563) * balance parenthesis (add closing bracket) * Apply suggestions from code review --------- Co-authored-by: Tianyi Zheng --- fuzzy_logic/fuzzy_operations.py.DISABLED.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzzy_logic/fuzzy_operations.py.DISABLED.txt b/fuzzy_logic/fuzzy_operations.py.DISABLED.txt index 0786ef8b0c67..67fd587f4baf 100644 --- a/fuzzy_logic/fuzzy_operations.py.DISABLED.txt +++ b/fuzzy_logic/fuzzy_operations.py.DISABLED.txt @@ -28,7 +28,7 @@ if __name__ == "__main__": 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)) + # 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] From 976e385c1d9df92c075575125475b22c423205b9 Mon Sep 17 00:00:00 2001 From: Ramy Date: Sat, 28 Sep 2024 15:37:00 +0200 Subject: [PATCH 2767/2908] Implemented Suffix Tree Data Structure (#11554) * Implemented KD-Tree Data Structure * Implemented KD-Tree Data Structure. updated DIRECTORY.md. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Create __init__.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Replaced legacy `np.random.rand` call with `np.random.Generator` in kd_tree/example_usage.py * Replaced legacy `np.random.rand` call with `np.random.Generator` in kd_tree/hypercube_points.py * added typehints and docstrings * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * docstring for search() * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added tests. Updated docstrings/typehints * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated tests and used | for type annotations * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * E501 for build_kdtree.py, hypercube_points.py, nearest_neighbour_search.py * I001 for example_usage.py and test_kdtree.py * I001 for example_usage.py and test_kdtree.py * Update data_structures/kd_tree/build_kdtree.py Co-authored-by: Christian Clauss * Update data_structures/kd_tree/example/hypercube_points.py Co-authored-by: Christian Clauss * Update data_structures/kd_tree/example/hypercube_points.py Co-authored-by: Christian Clauss * Added new test cases requested in Review. Refactored the test_build_kdtree() to include various checks. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Considered ruff errors * Considered ruff errors * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update kd_node.py * imported annotations from __future__ * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Implementation of the suffix tree data structure * Adding data to DIRECTORY.md * Minor file renaming * minor correction * renaming in DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Considering ruff part-1 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Considering ruff part-2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Considering ruff part-3 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Considering ruff part-4 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Considering ruff part-5 * Implemented Suffix Tree Data Structure. Added some comments to my files in #11532, #11554. * updating DIRECTORY.md * Implemented Suffix Tree Data Structure. Added some comments to my files in #11532, #11554. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: Ramy-Badr-Ahmed --- DIRECTORY.md | 7 ++ data_structures/kd_tree/build_kdtree.py | 8 +++ .../kd_tree/example/example_usage.py | 8 +++ .../kd_tree/example/hypercube_points.py | 8 +++ data_structures/kd_tree/kd_node.py | 8 +++ .../kd_tree/nearest_neighbour_search.py | 8 +++ data_structures/kd_tree/tests/test_kdtree.py | 8 +++ data_structures/suffix_tree/__init__.py | 0 .../suffix_tree/example/__init__.py | 0 .../suffix_tree/example/example_usage.py | 37 +++++++++++ data_structures/suffix_tree/suffix_tree.py | 66 +++++++++++++++++++ .../suffix_tree/suffix_tree_node.py | 36 ++++++++++ data_structures/suffix_tree/tests/__init__.py | 0 .../suffix_tree/tests/test_suffix_tree.py | 59 +++++++++++++++++ 14 files changed, 253 insertions(+) create mode 100644 data_structures/suffix_tree/__init__.py create mode 100644 data_structures/suffix_tree/example/__init__.py create mode 100644 data_structures/suffix_tree/example/example_usage.py create mode 100644 data_structures/suffix_tree/suffix_tree.py create mode 100644 data_structures/suffix_tree/suffix_tree_node.py create mode 100644 data_structures/suffix_tree/tests/__init__.py create mode 100644 data_structures/suffix_tree/tests/test_suffix_tree.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e965d3b32ccf..955001e2aa23 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -291,6 +291,13 @@ * [Stack With Doubly Linked List](data_structures/stacks/stack_with_doubly_linked_list.py) * [Stack With Singly Linked List](data_structures/stacks/stack_with_singly_linked_list.py) * [Stock Span Problem](data_structures/stacks/stock_span_problem.py) + * Suffix Tree + * Example + * [Example Usage](data_structures/suffix_tree/example/example_usage.py) + * [Suffix Tree](data_structures/suffix_tree/suffix_tree.py) + * [Suffix Tree Node](data_structures/suffix_tree/suffix_tree_node.py) + * Tests + * [Test Suffix Tree](data_structures/suffix_tree/tests/test_suffix_tree.py) * Trie * [Radix Tree](data_structures/trie/radix_tree.py) * [Trie](data_structures/trie/trie.py) diff --git a/data_structures/kd_tree/build_kdtree.py b/data_structures/kd_tree/build_kdtree.py index c5b800a2c992..074a5dac4d42 100644 --- a/data_structures/kd_tree/build_kdtree.py +++ b/data_structures/kd_tree/build_kdtree.py @@ -1,3 +1,11 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11532 +# https://github.com/TheAlgorithms/Python/pull/11532 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + from data_structures.kd_tree.kd_node import KDNode diff --git a/data_structures/kd_tree/example/example_usage.py b/data_structures/kd_tree/example/example_usage.py index e270f0cdd245..892c3b8c4a2a 100644 --- a/data_structures/kd_tree/example/example_usage.py +++ b/data_structures/kd_tree/example/example_usage.py @@ -1,3 +1,11 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11532 +# https://github.com/TheAlgorithms/Python/pull/11532 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + import numpy as np from data_structures.kd_tree.build_kdtree import build_kdtree diff --git a/data_structures/kd_tree/example/hypercube_points.py b/data_structures/kd_tree/example/hypercube_points.py index 2d8800ac9338..66744856e6d5 100644 --- a/data_structures/kd_tree/example/hypercube_points.py +++ b/data_structures/kd_tree/example/hypercube_points.py @@ -1,3 +1,11 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11532 +# https://github.com/TheAlgorithms/Python/pull/11532 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + import numpy as np diff --git a/data_structures/kd_tree/kd_node.py b/data_structures/kd_tree/kd_node.py index e1011027938d..5a22ef609077 100644 --- a/data_structures/kd_tree/kd_node.py +++ b/data_structures/kd_tree/kd_node.py @@ -1,3 +1,11 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11532 +# https://github.com/TheAlgorithms/Python/pull/11532 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + from __future__ import annotations diff --git a/data_structures/kd_tree/nearest_neighbour_search.py b/data_structures/kd_tree/nearest_neighbour_search.py index d9727736f21c..8104944c08f0 100644 --- a/data_structures/kd_tree/nearest_neighbour_search.py +++ b/data_structures/kd_tree/nearest_neighbour_search.py @@ -1,3 +1,11 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11532 +# https://github.com/TheAlgorithms/Python/pull/11532 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + from data_structures.kd_tree.kd_node import KDNode diff --git a/data_structures/kd_tree/tests/test_kdtree.py b/data_structures/kd_tree/tests/test_kdtree.py index 81f2cc990074..dce5e4f34ff4 100644 --- a/data_structures/kd_tree/tests/test_kdtree.py +++ b/data_structures/kd_tree/tests/test_kdtree.py @@ -1,3 +1,11 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11532 +# https://github.com/TheAlgorithms/Python/pull/11532 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + import numpy as np import pytest diff --git a/data_structures/suffix_tree/__init__.py b/data_structures/suffix_tree/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/suffix_tree/example/__init__.py b/data_structures/suffix_tree/example/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/suffix_tree/example/example_usage.py b/data_structures/suffix_tree/example/example_usage.py new file mode 100644 index 000000000000..724ac57e8bfb --- /dev/null +++ b/data_structures/suffix_tree/example/example_usage.py @@ -0,0 +1,37 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11554 +# https://github.com/TheAlgorithms/Python/pull/11554 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + +from data_structures.suffix_tree.suffix_tree import SuffixTree + + +def main() -> None: + """ + Demonstrate the usage of the SuffixTree class. + + - Initializes a SuffixTree with a predefined text. + - Defines a list of patterns to search for within the suffix tree. + - Searches for each pattern in the suffix tree. + + Patterns tested: + - "ana" (found) --> True + - "ban" (found) --> True + - "na" (found) --> True + - "xyz" (not found) --> False + - "mon" (found) --> True + """ + text = "monkey banana" + suffix_tree = SuffixTree(text) + + patterns = ["ana", "ban", "na", "xyz", "mon"] + for pattern in patterns: + found = suffix_tree.search(pattern) + print(f"Pattern '{pattern}' found: {found}") + + +if __name__ == "__main__": + main() diff --git a/data_structures/suffix_tree/suffix_tree.py b/data_structures/suffix_tree/suffix_tree.py new file mode 100644 index 000000000000..ad54fb0ba009 --- /dev/null +++ b/data_structures/suffix_tree/suffix_tree.py @@ -0,0 +1,66 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11554 +# https://github.com/TheAlgorithms/Python/pull/11554 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + +from data_structures.suffix_tree.suffix_tree_node import SuffixTreeNode + + +class SuffixTree: + def __init__(self, text: str) -> None: + """ + Initializes the suffix tree with the given text. + + Args: + text (str): The text for which the suffix tree is to be built. + """ + self.text: str = text + self.root: SuffixTreeNode = SuffixTreeNode() + self.build_suffix_tree() + + def build_suffix_tree(self) -> None: + """ + Builds the suffix tree for the given text by adding all suffixes. + """ + text = self.text + n = len(text) + for i in range(n): + suffix = text[i:] + self._add_suffix(suffix, i) + + def _add_suffix(self, suffix: str, index: int) -> None: + """ + Adds a suffix to the suffix tree. + + Args: + suffix (str): The suffix to add. + index (int): The starting index of the suffix in the original text. + """ + node = self.root + for char in suffix: + if char not in node.children: + node.children[char] = SuffixTreeNode() + node = node.children[char] + node.is_end_of_string = True + node.start = index + node.end = index + len(suffix) - 1 + + def search(self, pattern: str) -> bool: + """ + Searches for a pattern in the suffix tree. + + Args: + pattern (str): The pattern to search for. + + Returns: + bool: True if the pattern is found, False otherwise. + """ + node = self.root + for char in pattern: + if char not in node.children: + return False + node = node.children[char] + return True diff --git a/data_structures/suffix_tree/suffix_tree_node.py b/data_structures/suffix_tree/suffix_tree_node.py new file mode 100644 index 000000000000..e5b628645063 --- /dev/null +++ b/data_structures/suffix_tree/suffix_tree_node.py @@ -0,0 +1,36 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11554 +# https://github.com/TheAlgorithms/Python/pull/11554 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + +from __future__ import annotations + + +class SuffixTreeNode: + def __init__( + self, + children: dict[str, SuffixTreeNode] | None = None, + is_end_of_string: bool = False, + start: int | None = None, + end: int | None = None, + suffix_link: SuffixTreeNode | None = None, + ) -> None: + """ + Initializes a suffix tree node. + + Parameters: + children (dict[str, SuffixTreeNode] | None): The children of this node. + is_end_of_string (bool): Indicates if this node represents + the end of a string. + start (int | None): The start index of the suffix in the text. + end (int | None): The end index of the suffix in the text. + suffix_link (SuffixTreeNode | None): Link to another suffix tree node. + """ + self.children = children or {} + self.is_end_of_string = is_end_of_string + self.start = start + self.end = end + self.suffix_link = suffix_link diff --git a/data_structures/suffix_tree/tests/__init__.py b/data_structures/suffix_tree/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/suffix_tree/tests/test_suffix_tree.py b/data_structures/suffix_tree/tests/test_suffix_tree.py new file mode 100644 index 000000000000..45c6790ac48a --- /dev/null +++ b/data_structures/suffix_tree/tests/test_suffix_tree.py @@ -0,0 +1,59 @@ +# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) +# in Pull Request: #11554 +# https://github.com/TheAlgorithms/Python/pull/11554 +# +# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request +# addressing bugs/corrections to this file. +# Thank you! + +import unittest + +from data_structures.suffix_tree.suffix_tree import SuffixTree + + +class TestSuffixTree(unittest.TestCase): + def setUp(self) -> None: + """Set up the initial conditions for each test.""" + self.text = "banana" + self.suffix_tree = SuffixTree(self.text) + + def test_search_existing_patterns(self) -> None: + """Test searching for patterns that exist in the suffix tree.""" + patterns = ["ana", "ban", "na"] + for pattern in patterns: + with self.subTest(pattern=pattern): + assert self.suffix_tree.search( + pattern + ), f"Pattern '{pattern}' should be found." + + def test_search_non_existing_patterns(self) -> None: + """Test searching for patterns that do not exist in the suffix tree.""" + patterns = ["xyz", "apple", "cat"] + for pattern in patterns: + with self.subTest(pattern=pattern): + assert not self.suffix_tree.search( + pattern + ), f"Pattern '{pattern}' should not be found." + + def test_search_empty_pattern(self) -> None: + """Test searching for an empty pattern.""" + assert self.suffix_tree.search(""), "An empty pattern should be found." + + def test_search_full_text(self) -> None: + """Test searching for the full text.""" + assert self.suffix_tree.search( + self.text + ), "The full text should be found in the suffix tree." + + def test_search_substrings(self) -> None: + """Test searching for substrings of the full text.""" + substrings = ["ban", "ana", "a", "na"] + for substring in substrings: + with self.subTest(substring=substring): + assert self.suffix_tree.search( + substring + ), f"Substring '{substring}' should be found." + + +if __name__ == "__main__": + unittest.main() From a9ca110d6b6e4921119fdcca3b2a01e7f649f1ed Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 30 Sep 2024 12:49:31 +0200 Subject: [PATCH 2768/2908] Scripts for closing pull requests for Hacktoberfest (#11587) * Scripts for closing pull requests for Hacktoberfest * --limit=500 * Lose 2024 --- ...ose_pull_requests_with_awaiting_changes.sh | 22 +++++++++++++++++++ .../close_pull_requests_with_failing_tests.sh | 22 +++++++++++++++++++ ...requests_with_require_descriptive_names.sh | 21 ++++++++++++++++++ .../close_pull_requests_with_require_tests.sh | 22 +++++++++++++++++++ ...e_pull_requests_with_require_type_hints.sh | 21 ++++++++++++++++++ 5 files changed, 108 insertions(+) create mode 100755 scripts/close_pull_requests_with_awaiting_changes.sh create mode 100755 scripts/close_pull_requests_with_failing_tests.sh create mode 100755 scripts/close_pull_requests_with_require_descriptive_names.sh create mode 100755 scripts/close_pull_requests_with_require_tests.sh create mode 100755 scripts/close_pull_requests_with_require_type_hints.sh diff --git a/scripts/close_pull_requests_with_awaiting_changes.sh b/scripts/close_pull_requests_with_awaiting_changes.sh new file mode 100755 index 000000000000..55e19c980596 --- /dev/null +++ b/scripts/close_pull_requests_with_awaiting_changes.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# List all open pull requests +prs=$(gh pr list --state open --json number,title,labels --limit 500) + +# Loop through each pull request +echo "$prs" | jq -c '.[]' | while read -r pr; do + pr_number=$(echo "$pr" | jq -r '.number') + pr_title=$(echo "$pr" | jq -r '.title') + pr_labels=$(echo "$pr" | jq -r '.labels') + + # Check if the "awaiting changes" label is present + awaiting_changes=$(echo "$pr_labels" | jq -r '.[] | select(.name == "awaiting changes")') + echo "Checking PR #$pr_number $pr_title ($awaiting_changes) ($pr_labels)" + + # If awaiting_changes, close the pull request + if [[ -n "$awaiting_changes" ]]; then + echo "Closing PR #$pr_number $pr_title due to awaiting_changes label" + gh pr close "$pr_number" --comment "Closing awaiting_changes PRs to prepare for Hacktoberfest" + sleep 2 + fi +done diff --git a/scripts/close_pull_requests_with_failing_tests.sh b/scripts/close_pull_requests_with_failing_tests.sh new file mode 100755 index 000000000000..3ec5960aed27 --- /dev/null +++ b/scripts/close_pull_requests_with_failing_tests.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# List all open pull requests +prs=$(gh pr list --state open --json number,title,labels --limit 500) + +# Loop through each pull request +echo "$prs" | jq -c '.[]' | while read -r pr; do + pr_number=$(echo "$pr" | jq -r '.number') + pr_title=$(echo "$pr" | jq -r '.title') + pr_labels=$(echo "$pr" | jq -r '.labels') + + # Check if the "tests are failing" label is present + tests_are_failing=$(echo "$pr_labels" | jq -r '.[] | select(.name == "tests are failing")') + echo "Checking PR #$pr_number $pr_title ($tests_are_failing) ($pr_labels)" + + # If there are failing tests, close the pull request + if [[ -n "$tests_are_failing" ]]; then + echo "Closing PR #$pr_number $pr_title due to tests_are_failing label" + gh pr close "$pr_number" --comment "Closing tests_are_failing PRs to prepare for Hacktoberfest" + sleep 2 + fi +done diff --git a/scripts/close_pull_requests_with_require_descriptive_names.sh b/scripts/close_pull_requests_with_require_descriptive_names.sh new file mode 100755 index 000000000000..0fc3cec1d247 --- /dev/null +++ b/scripts/close_pull_requests_with_require_descriptive_names.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# List all open pull requests +prs=$(gh pr list --state open --json number,title,labels --limit 500) + +# Loop through each pull request +echo "$prs" | jq -c '.[]' | while read -r pr; do + pr_number=$(echo "$pr" | jq -r '.number') + pr_title=$(echo "$pr" | jq -r '.title') + pr_labels=$(echo "$pr" | jq -r '.labels') + + # Check if the "require descriptive names" label is present + require_descriptive_names=$(echo "$pr_labels" | jq -r '.[] | select(.name == "require descriptive names")') + echo "Checking PR #$pr_number $pr_title ($require_descriptive_names) ($pr_labels)" + + # If there are require_descriptive_names, close the pull request + if [[ -n "$require_descriptive_names" ]]; then + echo "Closing PR #$pr_number $pr_title due to require_descriptive_names label" + gh pr close "$pr_number" --comment "Closing require_descriptive_names PRs to prepare for Hacktoberfest" + fi +done diff --git a/scripts/close_pull_requests_with_require_tests.sh b/scripts/close_pull_requests_with_require_tests.sh new file mode 100755 index 000000000000..89a54996b584 --- /dev/null +++ b/scripts/close_pull_requests_with_require_tests.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# List all open pull requests +prs=$(gh pr list --state open --json number,title,labels --limit 500) + +# Loop through each pull request +echo "$prs" | jq -c '.[]' | while read -r pr; do + pr_number=$(echo "$pr" | jq -r '.number') + pr_title=$(echo "$pr" | jq -r '.title') + pr_labels=$(echo "$pr" | jq -r '.labels') + + # Check if the "require_tests" label is present + require_tests=$(echo "$pr_labels" | jq -r '.[] | select(.name == "require tests")') + echo "Checking PR #$pr_number $pr_title ($require_tests) ($pr_labels)" + + # If there require tests, close the pull request + if [[ -n "$require_tests" ]]; then + echo "Closing PR #$pr_number $pr_title due to require_tests label" + gh pr close "$pr_number" --comment "Closing require_tests PRs to prepare for Hacktoberfest" + # sleep 2 + fi +done diff --git a/scripts/close_pull_requests_with_require_type_hints.sh b/scripts/close_pull_requests_with_require_type_hints.sh new file mode 100755 index 000000000000..df5d88289cf0 --- /dev/null +++ b/scripts/close_pull_requests_with_require_type_hints.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# List all open pull requests +prs=$(gh pr list --state open --json number,title,labels --limit 500) + +# Loop through each pull request +echo "$prs" | jq -c '.[]' | while read -r pr; do + pr_number=$(echo "$pr" | jq -r '.number') + pr_title=$(echo "$pr" | jq -r '.title') + pr_labels=$(echo "$pr" | jq -r '.labels') + + # Check if the "require type hints" label is present + require_type_hints=$(echo "$pr_labels" | jq -r '.[] | select(.name == "require type hints")') + echo "Checking PR #$pr_number $pr_title ($require_type_hints) ($pr_labels)" + + # If require_type_hints, close the pull request + if [[ -n "$require_type_hints" ]]; then + echo "Closing PR #$pr_number $pr_title due to require_type_hints label" + gh pr close "$pr_number" --comment "Closing require_type_hints PRs to prepare for Hacktoberfest" + fi +done From a7bfa224554f277ed68be9e4ef3f6d1cd89008af Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:16:17 +0200 Subject: [PATCH 2769/2908] [pre-commit.ci] pre-commit autoupdate (#11594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.7 → v0.6.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.7...v0.6.8) - [github.com/abravalheri/validate-pyproject: v0.19 → v0.20.2](https://github.com/abravalheri/validate-pyproject/compare/v0.19...v0.20.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b219597f7b6..8a8e5c1f6ad9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.7 + rev: v0.6.8 hooks: - id: ruff - id: ruff-format @@ -42,7 +42,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.19 + rev: v0.20.2 hooks: - id: validate-pyproject From 0177ae1cd596f4f3c0ee7490666d74504deb0298 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 30 Sep 2024 23:01:15 +0200 Subject: [PATCH 2770/2908] Upgrade to Python 3.13 (#11588) --- .github/workflows/build.yml | 6 ++- DIRECTORY.md | 1 - computer_vision/haralick_descriptors.py | 8 ++-- data_structures/heap/binomial_heap.py | 6 +-- electronics/circular_convolution.py | 6 +-- fractals/julia_sets.py | 18 ++++----- graphics/bezier_curve.py | 8 ++-- graphs/dijkstra_binary_grid.py | 2 +- linear_algebra/src/power_iteration.py | 2 +- linear_programming/simplex.py | 32 +++++++-------- machine_learning/decision_tree.py | 8 ++-- machine_learning/forecasting/run.py | 8 ++-- machine_learning/k_nearest_neighbours.py | 2 +- machine_learning/logistic_regression.py | 4 +- machine_learning/loss_functions.py | 40 +++++++++---------- machine_learning/mfcc.py | 13 +++--- .../multilayer_perceptron_classifier.py | 2 +- machine_learning/scoring_functions.py | 22 +++++----- machine_learning/similarity_search.py | 2 +- machine_learning/support_vector_machines.py | 6 +-- maths/euclidean_distance.py | 8 ++-- maths/euler_method.py | 2 +- maths/euler_modified.py | 4 +- maths/gaussian.py | 16 ++++---- maths/minkowski_distance.py | 2 +- maths/numerical_analysis/adams_bashforth.py | 8 ++-- maths/numerical_analysis/runge_kutta.py | 2 +- .../runge_kutta_fehlberg_45.py | 4 +- maths/numerical_analysis/runge_kutta_gills.py | 2 +- maths/softmax.py | 2 +- .../two_hidden_layers_neural_network.py | 6 +-- other/bankers_algorithm.py | 8 ++-- physics/in_static_equilibrium.py | 2 +- requirements.txt | 4 +- ..._tweets.py => get_user_tweets.py.DISABLED} | 0 35 files changed, 135 insertions(+), 131 deletions(-) rename web_programming/{get_user_tweets.py => get_user_tweets.py.DISABLED} (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a113b4608678..dad2b2fac086 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.13 allow-prereleases: true - uses: actions/cache@v4 with: @@ -26,6 +26,10 @@ jobs: # TODO: #8818 Re-enable quantum tests run: pytest --ignore=quantum/q_fourier_transform.py + --ignore=computer_vision/cnn_classification.py + --ignore=dynamic_programming/k_means_clustering_tensorflow.py + --ignore=machine_learning/lstm/lstm_prediction.py + --ignore=neural_network/input_data.py --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered diff --git a/DIRECTORY.md b/DIRECTORY.md index 955001e2aa23..56ab8377f16b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1343,7 +1343,6 @@ * [Get Ip Geolocation](web_programming/get_ip_geolocation.py) * [Get Top Billionaires](web_programming/get_top_billionaires.py) * [Get Top Hn Posts](web_programming/get_top_hn_posts.py) - * [Get User Tweets](web_programming/get_user_tweets.py) * [Giphy](web_programming/giphy.py) * [Instagram Crawler](web_programming/instagram_crawler.py) * [Instagram Pic](web_programming/instagram_pic.py) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 634f0495797b..54632160dcf2 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -19,7 +19,7 @@ def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float >>> root_mean_square_error(np.array([1, 2, 3]), np.array([6, 4, 2])) 3.1622776601683795 """ - return np.sqrt(((original - reference) ** 2).mean()) + return float(np.sqrt(((original - reference) ** 2).mean())) def normalize_image( @@ -273,7 +273,7 @@ def haralick_descriptors(matrix: np.ndarray) -> list[float]: >>> morphological = opening_filter(binary) >>> mask_1 = binary_mask(gray, morphological)[0] >>> concurrency = matrix_concurrency(mask_1, (0, 1)) - >>> haralick_descriptors(concurrency) + >>> [float(f) for f in haralick_descriptors(concurrency)] [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] """ # Function np.indices could be used for bigger input types, @@ -335,7 +335,7 @@ def get_descriptors( return np.concatenate(descriptors, axis=None) -def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32: +def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> float: """ Simple method for calculating the euclidean distance between two points, with type np.ndarray. @@ -346,7 +346,7 @@ def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32: >>> euclidean(a, b) 3.3166247903554 """ - return np.sqrt(np.sum(np.square(point_1 - point_2))) + return float(np.sqrt(np.sum(np.square(point_1 - point_2)))) def get_distances(descriptors: np.ndarray, base: int) -> list[tuple[int, float]]: diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index 099bd2871023..9cfdf0c12fe0 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -73,7 +73,7 @@ class BinomialHeap: 30 Deleting - delete() test - >>> [first_heap.delete_min() for _ in range(20)] + >>> [int(first_heap.delete_min()) for _ in range(20)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] Create a new Heap @@ -118,7 +118,7 @@ class BinomialHeap: values in merged heap; (merge is inplace) >>> results = [] >>> while not first_heap.is_empty(): - ... results.append(first_heap.delete_min()) + ... results.append(int(first_heap.delete_min())) >>> results [17, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 34] """ @@ -354,7 +354,7 @@ def delete_min(self): # Merge heaps self.merge_heaps(new_heap) - return min_value + return int(min_value) def pre_order(self): """ diff --git a/electronics/circular_convolution.py b/electronics/circular_convolution.py index 768f2ad941bc..d06e76be759b 100644 --- a/electronics/circular_convolution.py +++ b/electronics/circular_convolution.py @@ -39,7 +39,7 @@ def circular_convolution(self) -> list[float]: Usage: >>> convolution = CircularConvolution() >>> convolution.circular_convolution() - [10, 10, 6, 14] + [10.0, 10.0, 6.0, 14.0] >>> convolution.first_signal = [0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6] >>> convolution.second_signal = [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5] @@ -54,7 +54,7 @@ def circular_convolution(self) -> list[float]: >>> convolution.first_signal = [1, -1, 2, 3, -1] >>> convolution.second_signal = [1, 2, 3] >>> convolution.circular_convolution() - [8, -2, 3, 4, 11] + [8.0, -2.0, 3.0, 4.0, 11.0] """ @@ -91,7 +91,7 @@ def circular_convolution(self) -> list[float]: final_signal = np.matmul(np.transpose(matrix), np.transpose(self.first_signal)) # rounding-off to two decimal places - return [round(i, 2) for i in final_signal] + return [float(round(i, 2)) for i in final_signal] if __name__ == "__main__": diff --git a/fractals/julia_sets.py b/fractals/julia_sets.py index 1eef4573ba19..bea599d44339 100644 --- a/fractals/julia_sets.py +++ b/fractals/julia_sets.py @@ -40,11 +40,11 @@ def eval_exponential(c_parameter: complex, z_values: np.ndarray) -> np.ndarray: """ Evaluate $e^z + c$. - >>> eval_exponential(0, 0) + >>> float(eval_exponential(0, 0)) 1.0 - >>> abs(eval_exponential(1, np.pi*1.j)) < 1e-15 + >>> bool(abs(eval_exponential(1, np.pi*1.j)) < 1e-15) True - >>> abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15 + >>> bool(abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15) True """ return np.exp(z_values) + c_parameter @@ -98,20 +98,20 @@ def iterate_function( >>> iterate_function(eval_quadratic_polynomial, 0, 3, np.array([0,1,2])).shape (3,) - >>> np.round(iterate_function(eval_quadratic_polynomial, + >>> complex(np.round(iterate_function(eval_quadratic_polynomial, ... 0, ... 3, - ... np.array([0,1,2]))[0]) + ... np.array([0,1,2]))[0])) 0j - >>> np.round(iterate_function(eval_quadratic_polynomial, + >>> complex(np.round(iterate_function(eval_quadratic_polynomial, ... 0, ... 3, - ... np.array([0,1,2]))[1]) + ... np.array([0,1,2]))[1])) (1+0j) - >>> np.round(iterate_function(eval_quadratic_polynomial, + >>> complex(np.round(iterate_function(eval_quadratic_polynomial, ... 0, ... 3, - ... np.array([0,1,2]))[2]) + ... np.array([0,1,2]))[2])) (256+0j) """ diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 9d906f179c92..6c7dcd4f06e7 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -30,9 +30,9 @@ def basis_function(self, t: float) -> list[float]: returns the x, y values of basis function at time t >>> curve = BezierCurve([(1,1), (1,2)]) - >>> curve.basis_function(0) + >>> [float(x) for x in curve.basis_function(0)] [1.0, 0.0] - >>> curve.basis_function(1) + >>> [float(x) for x in curve.basis_function(1)] [0.0, 1.0] """ assert 0 <= t <= 1, "Time t must be between 0 and 1." @@ -55,9 +55,9 @@ def bezier_curve_function(self, t: float) -> tuple[float, float]: The last point in the curve is when t = 1. >>> curve = BezierCurve([(1,1), (1,2)]) - >>> curve.bezier_curve_function(0) + >>> tuple(float(x) for x in curve.bezier_curve_function(0)) (1.0, 1.0) - >>> curve.bezier_curve_function(1) + >>> tuple(float(x) for x in curve.bezier_curve_function(1)) (1.0, 2.0) """ diff --git a/graphs/dijkstra_binary_grid.py b/graphs/dijkstra_binary_grid.py index c23d8234328a..06293a87da2d 100644 --- a/graphs/dijkstra_binary_grid.py +++ b/graphs/dijkstra_binary_grid.py @@ -69,7 +69,7 @@ def dijkstra( x, y = predecessors[x, y] path.append(source) # add the source manually path.reverse() - return matrix[destination], path + return float(matrix[destination]), path for i in range(len(dx)): nx, ny = x + dx[i], y + dy[i] diff --git a/linear_algebra/src/power_iteration.py b/linear_algebra/src/power_iteration.py index 24fbd9a5e002..83c2ce48c3a0 100644 --- a/linear_algebra/src/power_iteration.py +++ b/linear_algebra/src/power_iteration.py @@ -78,7 +78,7 @@ def power_iteration( if is_complex: lambda_ = np.real(lambda_) - return lambda_, vector + return float(lambda_), vector def test_power_iteration() -> None: diff --git a/linear_programming/simplex.py b/linear_programming/simplex.py index dc171bacd3a2..a8affe1b72d2 100644 --- a/linear_programming/simplex.py +++ b/linear_programming/simplex.py @@ -107,8 +107,8 @@ def generate_col_titles(self) -> list[str]: def find_pivot(self) -> tuple[Any, Any]: """Finds the pivot row and column. - >>> Tableau(np.array([[-2,1,0,0,0], [3,1,1,0,6], [1,2,0,1,7.]]), - ... 2, 0).find_pivot() + >>> tuple(int(x) for x in Tableau(np.array([[-2,1,0,0,0], [3,1,1,0,6], + ... [1,2,0,1,7.]]), 2, 0).find_pivot()) (1, 0) """ objective = self.objectives[-1] @@ -215,8 +215,8 @@ def run_simplex(self) -> dict[Any, Any]: Max: x1 + x2 ST: x1 + 3x2 <= 4 3x1 + x2 <= 4 - >>> Tableau(np.array([[-1,-1,0,0,0],[1,3,1,0,4],[3,1,0,1,4.]]), - ... 2, 0).run_simplex() + >>> {key: float(value) for key, value in Tableau(np.array([[-1,-1,0,0,0], + ... [1,3,1,0,4],[3,1,0,1,4.]]), 2, 0).run_simplex().items()} {'P': 2.0, 'x1': 1.0, 'x2': 1.0} # Standard linear program with 3 variables: @@ -224,21 +224,21 @@ def run_simplex(self) -> dict[Any, Any]: ST: 2x1 + x2 + x3 ≤ 2 x1 + 2x2 + 3x3 ≤ 5 2x1 + 2x2 + x3 ≤ 6 - >>> Tableau(np.array([ + >>> {key: float(value) for key, value in Tableau(np.array([ ... [-3,-1,-3,0,0,0,0], ... [2,1,1,1,0,0,2], ... [1,2,3,0,1,0,5], ... [2,2,1,0,0,1,6.] - ... ]),3,0).run_simplex() # doctest: +ELLIPSIS + ... ]),3,0).run_simplex().items()} # doctest: +ELLIPSIS {'P': 5.4, 'x1': 0.199..., 'x3': 1.6} # Optimal tableau input: - >>> Tableau(np.array([ + >>> {key: float(value) for key, value in Tableau(np.array([ ... [0, 0, 0.25, 0.25, 2], ... [0, 1, 0.375, -0.125, 1], ... [1, 0, -0.125, 0.375, 1] - ... ]), 2, 0).run_simplex() + ... ]), 2, 0).run_simplex().items()} {'P': 2.0, 'x1': 1.0, 'x2': 1.0} # Non-standard: >= constraints @@ -246,25 +246,25 @@ def run_simplex(self) -> dict[Any, Any]: ST: x1 + x2 + x3 <= 40 2x1 + x2 - x3 >= 10 - x2 + x3 >= 10 - >>> Tableau(np.array([ + >>> {key: float(value) for key, value in Tableau(np.array([ ... [2, 0, 0, 0, -1, -1, 0, 0, 20], ... [-2, -3, -1, 0, 0, 0, 0, 0, 0], ... [1, 1, 1, 1, 0, 0, 0, 0, 40], ... [2, 1, -1, 0, -1, 0, 1, 0, 10], ... [0, -1, 1, 0, 0, -1, 0, 1, 10.] - ... ]), 3, 2).run_simplex() + ... ]), 3, 2).run_simplex().items()} {'P': 70.0, 'x1': 10.0, 'x2': 10.0, 'x3': 20.0} # Non standard: minimisation and equalities Min: x1 + x2 ST: 2x1 + x2 = 12 6x1 + 5x2 = 40 - >>> Tableau(np.array([ + >>> {key: float(value) for key, value in Tableau(np.array([ ... [8, 6, 0, 0, 52], ... [1, 1, 0, 0, 0], ... [2, 1, 1, 0, 12], ... [6, 5, 0, 1, 40.], - ... ]), 2, 2).run_simplex() + ... ]), 2, 2).run_simplex().items()} {'P': 7.0, 'x1': 5.0, 'x2': 2.0} @@ -275,7 +275,7 @@ def run_simplex(self) -> dict[Any, Any]: 2x1 + 4x2 <= 48 x1 + x2 >= 10 x1 >= 2 - >>> Tableau(np.array([ + >>> {key: float(value) for key, value in Tableau(np.array([ ... [2, 1, 0, 0, 0, -1, -1, 0, 0, 12.0], ... [-8, -6, 0, 0, 0, 0, 0, 0, 0, 0.0], ... [1, 3, 1, 0, 0, 0, 0, 0, 0, 33.0], @@ -283,7 +283,7 @@ def run_simplex(self) -> dict[Any, Any]: ... [2, 4, 0, 0, 1, 0, 0, 0, 0, 48.0], ... [1, 1, 0, 0, 0, -1, 0, 1, 0, 10.0], ... [1, 0, 0, 0, 0, 0, -1, 0, 1, 2.0] - ... ]), 2, 2).run_simplex() # doctest: +ELLIPSIS + ... ]), 2, 2).run_simplex().items()} # doctest: +ELLIPSIS {'P': 132.0, 'x1': 12.000... 'x2': 5.999...} """ # Stop simplex algorithm from cycling. @@ -307,11 +307,11 @@ def run_simplex(self) -> dict[Any, Any]: def interpret_tableau(self) -> dict[str, float]: """Given the final tableau, add the corresponding values of the basic decision variables to the `output_dict` - >>> Tableau(np.array([ + >>> {key: float(value) for key, value in Tableau(np.array([ ... [0,0,0.875,0.375,5], ... [0,1,0.375,-0.125,1], ... [1,0,-0.125,0.375,1] - ... ]),2, 0).interpret_tableau() + ... ]),2, 0).interpret_tableau().items()} {'P': 5.0, 'x1': 1.0, 'x2': 1.0} """ # P = RHS of final tableau diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index d0bd6ab0b555..72970431c3fc 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -26,15 +26,15 @@ def mean_squared_error(self, labels, prediction): >>> tester = DecisionTree() >>> test_labels = np.array([1,2,3,4,5,6,7,8,9,10]) >>> test_prediction = float(6) - >>> tester.mean_squared_error(test_labels, test_prediction) == ( + >>> bool(tester.mean_squared_error(test_labels, test_prediction) == ( ... TestDecisionTree.helper_mean_squared_error_test(test_labels, - ... test_prediction)) + ... test_prediction))) True >>> test_labels = np.array([1,2,3]) >>> test_prediction = float(2) - >>> tester.mean_squared_error(test_labels, test_prediction) == ( + >>> bool(tester.mean_squared_error(test_labels, test_prediction) == ( ... TestDecisionTree.helper_mean_squared_error_test(test_labels, - ... test_prediction)) + ... test_prediction))) True """ if labels.ndim != 1: diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py index dbb86caf8568..9d81b03cd09e 100644 --- a/machine_learning/forecasting/run.py +++ b/machine_learning/forecasting/run.py @@ -28,7 +28,7 @@ def linear_regression_prediction( input : training data (date, total_user, total_event) in list of float output : list of total user prediction in float >>> 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 + >>> bool(abs(n - 5.0) < 1e-6) # Checking precision because of floating point errors True """ x = np.array([[1, item, train_mtch[i]] for i, item in enumerate(train_dt)]) @@ -56,7 +56,7 @@ def sarimax_predictor(train_user: list, train_match: list, test_match: list) -> ) model_fit = model.fit(disp=False, maxiter=600, method="nm") result = model_fit.predict(1, len(test_match), exog=[test_match]) - return result[0] + return float(result[0]) def support_vector_regressor(x_train: list, x_test: list, train_user: list) -> float: @@ -75,7 +75,7 @@ def support_vector_regressor(x_train: list, x_test: list, train_user: list) -> f 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] + return float(y_pred[0]) def interquartile_range_checker(train_user: list) -> float: @@ -92,7 +92,7 @@ def interquartile_range_checker(train_user: list) -> float: q3 = np.percentile(train_user, 75) iqr = q3 - q1 low_lim = q1 - (iqr * 0.1) - return low_lim + return float(low_lim) def data_safety_checker(list_vote: list, actual_result: float) -> bool: diff --git a/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py index a43757c5c20e..fbc1b8bd227e 100644 --- a/machine_learning/k_nearest_neighbours.py +++ b/machine_learning/k_nearest_neighbours.py @@ -42,7 +42,7 @@ def _euclidean_distance(a: np.ndarray[float], b: np.ndarray[float]) -> float: >>> KNN._euclidean_distance(np.array([1, 2, 3]), np.array([1, 8, 11])) 10.0 """ - return np.linalg.norm(a - b) + return float(np.linalg.norm(a - b)) def classify(self, pred_point: np.ndarray[float], k: int = 5) -> str: """ diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 090af5382185..496026631fbe 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -45,7 +45,7 @@ def sigmoid_function(z: float | np.ndarray) -> float | np.ndarray: @returns: returns value in the range 0 to 1 Examples: - >>> sigmoid_function(4) + >>> float(sigmoid_function(4)) 0.9820137900379085 >>> sigmoid_function(np.array([-3, 3])) array([0.04742587, 0.95257413]) @@ -100,7 +100,7 @@ def cost_function(h: np.ndarray, y: np.ndarray) -> float: References: - https://en.wikipedia.org/wiki/Logistic_regression """ - return (-y * np.log(h) - (1 - y) * np.log(1 - h)).mean() + return float((-y * np.log(h) - (1 - y) * np.log(1 - h)).mean()) def log_likelihood(x, y, weights): diff --git a/machine_learning/loss_functions.py b/machine_learning/loss_functions.py index 150035661eb7..0bd9aa8b5401 100644 --- a/machine_learning/loss_functions.py +++ b/machine_learning/loss_functions.py @@ -22,7 +22,7 @@ def binary_cross_entropy( >>> true_labels = np.array([0, 1, 1, 0, 1]) >>> predicted_probs = np.array([0.2, 0.7, 0.9, 0.3, 0.8]) - >>> binary_cross_entropy(true_labels, predicted_probs) + >>> float(binary_cross_entropy(true_labels, predicted_probs)) 0.2529995012327421 >>> true_labels = np.array([0, 1, 1, 0, 1]) >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) @@ -68,7 +68,7 @@ def binary_focal_cross_entropy( >>> true_labels = np.array([0, 1, 1, 0, 1]) >>> predicted_probs = np.array([0.2, 0.7, 0.9, 0.3, 0.8]) - >>> binary_focal_cross_entropy(true_labels, predicted_probs) + >>> float(binary_focal_cross_entropy(true_labels, predicted_probs)) 0.008257977659239775 >>> true_labels = np.array([0, 1, 1, 0, 1]) >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) @@ -108,7 +108,7 @@ def categorical_cross_entropy( >>> true_labels = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1], [0.0, 0.1, 0.9]]) - >>> categorical_cross_entropy(true_labels, pred_probs) + >>> float(categorical_cross_entropy(true_labels, pred_probs)) 0.567395975254385 >>> true_labels = np.array([[1, 0], [0, 1]]) >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1]]) @@ -179,13 +179,13 @@ def categorical_focal_cross_entropy( >>> true_labels = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) >>> pred_probs = np.array([[0.9, 0.1, 0.0], [0.2, 0.7, 0.1], [0.0, 0.1, 0.9]]) >>> alpha = np.array([0.6, 0.2, 0.7]) - >>> categorical_focal_cross_entropy(true_labels, pred_probs, alpha) + >>> float(categorical_focal_cross_entropy(true_labels, pred_probs, alpha)) 0.0025966118981496423 >>> true_labels = np.array([[0, 1, 0], [0, 0, 1]]) >>> pred_probs = np.array([[0.05, 0.95, 0], [0.1, 0.8, 0.1]]) >>> alpha = np.array([0.25, 0.25, 0.25]) - >>> categorical_focal_cross_entropy(true_labels, pred_probs, alpha) + >>> float(categorical_focal_cross_entropy(true_labels, pred_probs, alpha)) 0.23315276982014324 >>> true_labels = np.array([[1, 0], [0, 1]]) @@ -265,7 +265,7 @@ def hinge_loss(y_true: np.ndarray, y_pred: np.ndarray) -> float: >>> true_labels = np.array([-1, 1, 1, -1, 1]) >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) - >>> hinge_loss(true_labels, pred) + >>> float(hinge_loss(true_labels, pred)) 1.52 >>> true_labels = np.array([-1, 1, 1, -1, 1, 1]) >>> pred = np.array([-4, -0.3, 0.7, 5, 10]) @@ -309,11 +309,11 @@ def huber_loss(y_true: np.ndarray, y_pred: np.ndarray, delta: float) -> float: >>> true_values = np.array([0.9, 10.0, 2.0, 1.0, 5.2]) >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) - >>> np.isclose(huber_loss(true_values, predicted_values, 1.0), 2.102) + >>> bool(np.isclose(huber_loss(true_values, predicted_values, 1.0), 2.102)) True >>> true_labels = np.array([11.0, 21.0, 3.32, 4.0, 5.0]) >>> predicted_probs = np.array([8.3, 20.8, 2.9, 11.2, 5.0]) - >>> np.isclose(huber_loss(true_labels, predicted_probs, 1.0), 1.80164) + >>> bool(np.isclose(huber_loss(true_labels, predicted_probs, 1.0), 1.80164)) True >>> true_labels = np.array([11.0, 21.0, 3.32, 4.0]) >>> predicted_probs = np.array([8.3, 20.8, 2.9, 11.2, 5.0]) @@ -347,7 +347,7 @@ def mean_squared_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) - >>> np.isclose(mean_squared_error(true_values, predicted_values), 0.028) + >>> bool(np.isclose(mean_squared_error(true_values, predicted_values), 0.028)) True >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) @@ -381,11 +381,11 @@ def mean_absolute_error(y_true: np.ndarray, y_pred: np.ndarray) -> float: >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) - >>> np.isclose(mean_absolute_error(true_values, predicted_values), 0.16) + >>> bool(np.isclose(mean_absolute_error(true_values, predicted_values), 0.16)) True >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) - >>> np.isclose(mean_absolute_error(true_values, predicted_values), 2.16) + >>> bool(np.isclose(mean_absolute_error(true_values, predicted_values), 2.16)) False >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> predicted_probs = np.array([0.3, 0.8, 0.9, 5.2]) @@ -420,7 +420,7 @@ def mean_squared_logarithmic_error(y_true: np.ndarray, y_pred: np.ndarray) -> fl >>> true_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> predicted_values = np.array([0.8, 2.1, 2.9, 4.2, 5.2]) - >>> mean_squared_logarithmic_error(true_values, predicted_values) + >>> float(mean_squared_logarithmic_error(true_values, predicted_values)) 0.0030860877925181344 >>> true_labels = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> predicted_probs = np.array([0.3, 0.8, 0.9, 0.2]) @@ -459,17 +459,17 @@ def mean_absolute_percentage_error( Examples: >>> y_true = np.array([10, 20, 30, 40]) >>> y_pred = np.array([12, 18, 33, 45]) - >>> mean_absolute_percentage_error(y_true, y_pred) + >>> float(mean_absolute_percentage_error(y_true, y_pred)) 0.13125 >>> y_true = np.array([1, 2, 3, 4]) >>> y_pred = np.array([2, 3, 4, 5]) - >>> mean_absolute_percentage_error(y_true, y_pred) + >>> float(mean_absolute_percentage_error(y_true, y_pred)) 0.5208333333333333 >>> y_true = np.array([34, 37, 44, 47, 48, 48, 46, 43, 32, 27, 26, 24]) >>> y_pred = np.array([37, 40, 46, 44, 46, 50, 45, 44, 34, 30, 22, 23]) - >>> mean_absolute_percentage_error(y_true, y_pred) + >>> float(mean_absolute_percentage_error(y_true, y_pred)) 0.064671076436071 """ if len(y_true) != len(y_pred): @@ -511,7 +511,7 @@ def perplexity_loss( ... [[0.03, 0.26, 0.21, 0.18, 0.30], ... [0.28, 0.10, 0.33, 0.15, 0.12]]] ... ) - >>> perplexity_loss(y_true, y_pred) + >>> float(perplexity_loss(y_true, y_pred)) 5.0247347775367945 >>> y_true = np.array([[1, 4], [2, 3]]) >>> y_pred = np.array( @@ -600,17 +600,17 @@ def smooth_l1_loss(y_true: np.ndarray, y_pred: np.ndarray, beta: float = 1.0) -> >>> y_true = np.array([3, 5, 2, 7]) >>> y_pred = np.array([2.9, 4.8, 2.1, 7.2]) - >>> smooth_l1_loss(y_true, y_pred, 1.0) + >>> float(smooth_l1_loss(y_true, y_pred, 1.0)) 0.012500000000000022 >>> y_true = np.array([2, 4, 6]) >>> y_pred = np.array([1, 5, 7]) - >>> smooth_l1_loss(y_true, y_pred, 1.0) + >>> float(smooth_l1_loss(y_true, y_pred, 1.0)) 0.5 >>> y_true = np.array([1, 3, 5, 7]) >>> y_pred = np.array([1, 3, 5, 7]) - >>> smooth_l1_loss(y_true, y_pred, 1.0) + >>> float(smooth_l1_loss(y_true, y_pred, 1.0)) 0.0 >>> y_true = np.array([1, 3, 5]) @@ -647,7 +647,7 @@ def kullback_leibler_divergence(y_true: np.ndarray, y_pred: np.ndarray) -> float >>> true_labels = np.array([0.2, 0.3, 0.5]) >>> predicted_probs = np.array([0.3, 0.3, 0.4]) - >>> kullback_leibler_divergence(true_labels, predicted_probs) + >>> float(kullback_leibler_divergence(true_labels, predicted_probs)) 0.030478754035472025 >>> true_labels = np.array([0.2, 0.3, 0.5]) >>> predicted_probs = np.array([0.3, 0.3, 0.4, 0.5]) diff --git a/machine_learning/mfcc.py b/machine_learning/mfcc.py index a1e99ce4ad40..dcc3151d5a1a 100644 --- a/machine_learning/mfcc.py +++ b/machine_learning/mfcc.py @@ -162,9 +162,9 @@ def normalize(audio: np.ndarray) -> np.ndarray: Examples: >>> audio = np.array([1, 2, 3, 4, 5]) >>> normalized_audio = normalize(audio) - >>> np.max(normalized_audio) + >>> float(np.max(normalized_audio)) 1.0 - >>> np.min(normalized_audio) + >>> float(np.min(normalized_audio)) 0.2 """ # Divide the entire audio signal by the maximum absolute value @@ -229,7 +229,8 @@ def calculate_fft(audio_windowed: np.ndarray, ftt_size: int = 1024) -> np.ndarra Examples: >>> audio_windowed = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) >>> audio_fft = calculate_fft(audio_windowed, ftt_size=4) - >>> np.allclose(audio_fft[0], np.array([6.0+0.j, -1.5+0.8660254j, -1.5-0.8660254j])) + >>> bool(np.allclose(audio_fft[0], np.array([6.0+0.j, -1.5+0.8660254j, + ... -1.5-0.8660254j]))) True """ # Transpose the audio data to have time in rows and channels in columns @@ -281,7 +282,7 @@ def freq_to_mel(freq: float) -> float: The frequency in mel scale. Examples: - >>> round(freq_to_mel(1000), 2) + >>> float(round(freq_to_mel(1000), 2)) 999.99 """ # Use the formula to convert frequency to the mel scale @@ -321,7 +322,7 @@ def mel_spaced_filterbank( Mel-spaced filter bank. Examples: - >>> round(mel_spaced_filterbank(8000, 10, 1024)[0][1], 10) + >>> float(round(mel_spaced_filterbank(8000, 10, 1024)[0][1], 10)) 0.0004603981 """ freq_min = 0 @@ -438,7 +439,7 @@ def discrete_cosine_transform(dct_filter_num: int, filter_num: int) -> np.ndarra The DCT basis matrix. Examples: - >>> round(discrete_cosine_transform(3, 5)[0][0], 5) + >>> float(round(discrete_cosine_transform(3, 5)[0][0], 5)) 0.44721 """ basis = np.empty((dct_filter_num, filter_num)) diff --git a/machine_learning/multilayer_perceptron_classifier.py b/machine_learning/multilayer_perceptron_classifier.py index e99a4131e972..40f998c7dfa2 100644 --- a/machine_learning/multilayer_perceptron_classifier.py +++ b/machine_learning/multilayer_perceptron_classifier.py @@ -17,7 +17,7 @@ def wrapper(y): """ - >>> wrapper(Y) + >>> [int(x) for x in wrapper(Y)] [0, 0, 1] """ return list(y) diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index 08b969a95c3b..f6b685f4f98a 100644 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -20,11 +20,11 @@ def mae(predict, actual): """ Examples(rounded for precision): >>> actual = [1,2,3];predict = [1,4,3] - >>> np.around(mae(predict,actual),decimals = 2) + >>> float(np.around(mae(predict,actual),decimals = 2)) 0.67 >>> actual = [1,1,1];predict = [1,1,1] - >>> mae(predict,actual) + >>> float(mae(predict,actual)) 0.0 """ predict = np.array(predict) @@ -41,11 +41,11 @@ def mse(predict, actual): """ Examples(rounded for precision): >>> actual = [1,2,3];predict = [1,4,3] - >>> np.around(mse(predict,actual),decimals = 2) + >>> float(np.around(mse(predict,actual),decimals = 2)) 1.33 >>> actual = [1,1,1];predict = [1,1,1] - >>> mse(predict,actual) + >>> float(mse(predict,actual)) 0.0 """ predict = np.array(predict) @@ -63,11 +63,11 @@ def rmse(predict, actual): """ Examples(rounded for precision): >>> actual = [1,2,3];predict = [1,4,3] - >>> np.around(rmse(predict,actual),decimals = 2) + >>> float(np.around(rmse(predict,actual),decimals = 2)) 1.15 >>> actual = [1,1,1];predict = [1,1,1] - >>> rmse(predict,actual) + >>> float(rmse(predict,actual)) 0.0 """ predict = np.array(predict) @@ -84,12 +84,10 @@ def rmse(predict, actual): def rmsle(predict, actual): """ Examples(rounded for precision): - >>> actual = [10,10,30];predict = [10,2,30] - >>> np.around(rmsle(predict,actual),decimals = 2) + >>> float(np.around(rmsle(predict=[10, 2, 30], actual=[10, 10, 30]), decimals=2)) 0.75 - >>> actual = [1,1,1];predict = [1,1,1] - >>> rmsle(predict,actual) + >>> float(rmsle(predict=[1, 1, 1], actual=[1, 1, 1])) 0.0 """ predict = np.array(predict) @@ -117,12 +115,12 @@ def mbd(predict, actual): Here the model overpredicts >>> actual = [1,2,3];predict = [2,3,4] - >>> np.around(mbd(predict,actual),decimals = 2) + >>> float(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) + >>> float(np.around(mbd(predict,actual),decimals = 2)) -66.67 """ predict = np.array(predict) diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py index 0bc3b17d7e5a..c8a573796882 100644 --- a/machine_learning/similarity_search.py +++ b/machine_learning/similarity_search.py @@ -153,7 +153,7 @@ def cosine_similarity(input_a: np.ndarray, input_b: np.ndarray) -> float: >>> cosine_similarity(np.array([1, 2]), np.array([6, 32])) 0.9615239476408232 """ - return np.dot(input_a, input_b) / (norm(input_a) * norm(input_b)) + return float(np.dot(input_a, input_b) / (norm(input_a) * norm(input_b))) if __name__ == "__main__": diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py index 24046115ebc4..d17c9044a3e9 100644 --- a/machine_learning/support_vector_machines.py +++ b/machine_learning/support_vector_machines.py @@ -14,11 +14,11 @@ def norm_squared(vector: ndarray) -> float: Returns: float: squared second norm of vector - >>> norm_squared([1, 2]) + >>> int(norm_squared([1, 2])) 5 - >>> norm_squared(np.asarray([1, 2])) + >>> int(norm_squared(np.asarray([1, 2]))) 5 - >>> norm_squared([0, 0]) + >>> int(norm_squared([0, 0])) 0 """ return np.dot(vector, vector) diff --git a/maths/euclidean_distance.py b/maths/euclidean_distance.py index 9b29b37b0ce6..aa7f3efc7684 100644 --- a/maths/euclidean_distance.py +++ b/maths/euclidean_distance.py @@ -13,13 +13,13 @@ 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)) + >>> float(euclidean_distance((0, 0), (2, 2))) 2.8284271247461903 - >>> euclidean_distance(np.array([0, 0, 0]), np.array([2, 2, 2])) + >>> float(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])) + >>> float(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]) + >>> float(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)) diff --git a/maths/euler_method.py b/maths/euler_method.py index 30f193e6daa5..c6adb07e2d3d 100644 --- a/maths/euler_method.py +++ b/maths/euler_method.py @@ -26,7 +26,7 @@ def explicit_euler( ... return y >>> y0 = 1 >>> y = explicit_euler(f, y0, 0.0, 0.01, 5) - >>> y[-1] + >>> float(y[-1]) 144.77277243257308 """ n = int(np.ceil((x_end - x0) / step_size)) diff --git a/maths/euler_modified.py b/maths/euler_modified.py index d02123e1e2fb..bb282e9f0ab9 100644 --- a/maths/euler_modified.py +++ b/maths/euler_modified.py @@ -24,13 +24,13 @@ def euler_modified( >>> def f1(x, y): ... return -2*x*(y**2) >>> y = euler_modified(f1, 1.0, 0.0, 0.2, 1.0) - >>> y[-1] + >>> float(y[-1]) 0.503338255442106 >>> import math >>> def f2(x, y): ... return -2*y + (x**3)*math.exp(-2*x) >>> y = euler_modified(f2, 1.0, 0.0, 0.1, 0.3) - >>> y[-1] + >>> float(y[-1]) 0.5525976431951775 """ n = int(np.ceil((x_end - x0) / step_size)) diff --git a/maths/gaussian.py b/maths/gaussian.py index 0e02010a9c67..b1e62ea77fe2 100644 --- a/maths/gaussian.py +++ b/maths/gaussian.py @@ -5,18 +5,18 @@ from numpy import exp, pi, sqrt -def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: +def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> float: """ - >>> gaussian(1) + >>> float(gaussian(1)) 0.24197072451914337 - >>> gaussian(24) + >>> float(gaussian(24)) 3.342714441794458e-126 - >>> gaussian(1, 4, 2) + >>> float(gaussian(1, 4, 2)) 0.06475879783294587 - >>> gaussian(1, 5, 3) + >>> float(gaussian(1, 5, 3)) 0.05467002489199788 Supports NumPy Arrays @@ -29,7 +29,7 @@ def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: 5.05227108e-15, 1.02797736e-18, 7.69459863e-23, 2.11881925e-27, 2.14638374e-32, 7.99882776e-38, 1.09660656e-43]) - >>> gaussian(15) + >>> float(gaussian(15)) 5.530709549844416e-50 >>> gaussian([1,2, 'string']) @@ -47,10 +47,10 @@ def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: ... OverflowError: (34, 'Result too large') - >>> gaussian(10**-326) + >>> float(gaussian(10**-326)) 0.3989422804014327 - >>> gaussian(2523, mu=234234, sigma=3425) + >>> float(gaussian(2523, mu=234234, sigma=3425)) 0.0 """ return 1 / sqrt(2 * pi * sigma**2) * exp(-((x - mu) ** 2) / (2 * sigma**2)) diff --git a/maths/minkowski_distance.py b/maths/minkowski_distance.py index 3237124e8d36..99f02e31e417 100644 --- a/maths/minkowski_distance.py +++ b/maths/minkowski_distance.py @@ -19,7 +19,7 @@ def minkowski_distance( >>> minkowski_distance([1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0], 2) 8.0 >>> import numpy as np - >>> np.isclose(5.0, minkowski_distance([5.0], [0.0], 3)) + >>> bool(np.isclose(5.0, minkowski_distance([5.0], [0.0], 3))) True >>> minkowski_distance([1.0], [2.0], -1) Traceback (most recent call last): diff --git a/maths/numerical_analysis/adams_bashforth.py b/maths/numerical_analysis/adams_bashforth.py index fb406171098a..26244a58552f 100644 --- a/maths/numerical_analysis/adams_bashforth.py +++ b/maths/numerical_analysis/adams_bashforth.py @@ -102,7 +102,7 @@ def step_3(self) -> np.ndarray: >>> def f(x, y): ... return x + y >>> y = AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_3() - >>> y[3] + >>> float(y[3]) 0.15533333333333332 >>> AdamsBashforth(f, [0, 0.2], [0, 0], 0.2, 1).step_3() @@ -140,9 +140,9 @@ def step_4(self) -> np.ndarray: ... return x + y >>> y = AdamsBashforth( ... f, [0, 0.2, 0.4, 0.6], [0, 0, 0.04, 0.128], 0.2, 1).step_4() - >>> y[4] + >>> float(y[4]) 0.30699999999999994 - >>> y[5] + >>> float(y[5]) 0.5771083333333333 >>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_4() @@ -185,7 +185,7 @@ def step_5(self) -> np.ndarray: >>> y = AdamsBashforth( ... f, [0, 0.2, 0.4, 0.6, 0.8], [0, 0.02140, 0.02140, 0.22211, 0.42536], ... 0.2, 1).step_5() - >>> y[-1] + >>> float(y[-1]) 0.05436839444444452 >>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_5() diff --git a/maths/numerical_analysis/runge_kutta.py b/maths/numerical_analysis/runge_kutta.py index 4cac017ee89e..3a25b0fb0173 100644 --- a/maths/numerical_analysis/runge_kutta.py +++ b/maths/numerical_analysis/runge_kutta.py @@ -19,7 +19,7 @@ def runge_kutta(f, y0, x0, h, x_end): ... return y >>> y0 = 1 >>> y = runge_kutta(f, y0, 0.0, 0.01, 5) - >>> y[-1] + >>> float(y[-1]) 148.41315904125113 """ n = int(np.ceil((x_end - x0) / h)) diff --git a/maths/numerical_analysis/runge_kutta_fehlberg_45.py b/maths/numerical_analysis/runge_kutta_fehlberg_45.py index 8181fe3015fc..0fbd60a35c1a 100644 --- a/maths/numerical_analysis/runge_kutta_fehlberg_45.py +++ b/maths/numerical_analysis/runge_kutta_fehlberg_45.py @@ -34,12 +34,12 @@ def runge_kutta_fehlberg_45( >>> def f(x, y): ... return 1 + y**2 >>> y = runge_kutta_fehlberg_45(f, 0, 0, 0.2, 1) - >>> y[1] + >>> float(y[1]) 0.2027100937470787 >>> def f(x,y): ... return x >>> y = runge_kutta_fehlberg_45(f, -1, 0, 0.2, 0) - >>> y[1] + >>> float(y[1]) -0.18000000000000002 >>> y = runge_kutta_fehlberg_45(5, 0, 0, 0.1, 1) Traceback (most recent call last): diff --git a/maths/numerical_analysis/runge_kutta_gills.py b/maths/numerical_analysis/runge_kutta_gills.py index 451cde4cb935..5d9672679813 100644 --- a/maths/numerical_analysis/runge_kutta_gills.py +++ b/maths/numerical_analysis/runge_kutta_gills.py @@ -34,7 +34,7 @@ def runge_kutta_gills( >>> def f(x, y): ... return (x-y)/2 >>> y = runge_kutta_gills(f, 0, 3, 0.2, 5) - >>> y[-1] + >>> float(y[-1]) 3.4104259225717537 >>> def f(x,y): diff --git a/maths/softmax.py b/maths/softmax.py index 04cf77525420..95c95e66f59e 100644 --- a/maths/softmax.py +++ b/maths/softmax.py @@ -28,7 +28,7 @@ def softmax(vector): The softmax vector adds up to one. We need to ceil to mitigate for precision - >>> np.ceil(np.sum(softmax([1,2,3,4]))) + >>> float(np.ceil(np.sum(softmax([1,2,3,4])))) 1.0 >>> vec = np.array([5,5]) diff --git a/neural_network/two_hidden_layers_neural_network.py b/neural_network/two_hidden_layers_neural_network.py index d488de590cc2..1b7c0beed3ba 100644 --- a/neural_network/two_hidden_layers_neural_network.py +++ b/neural_network/two_hidden_layers_neural_network.py @@ -64,7 +64,7 @@ def feedforward(self) -> np.ndarray: >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) >>> res = nn.feedforward() >>> array_sum = np.sum(res) - >>> np.isnan(array_sum) + >>> bool(np.isnan(array_sum)) False """ # Layer_between_input_and_first_hidden_layer is the layer connecting the @@ -105,7 +105,7 @@ def back_propagation(self) -> None: >>> res = nn.feedforward() >>> nn.back_propagation() >>> updated_weights = nn.second_hidden_layer_and_output_layer_weights - >>> (res == updated_weights).all() + >>> bool((res == updated_weights).all()) False """ @@ -171,7 +171,7 @@ def train(self, output: np.ndarray, iterations: int, give_loss: bool) -> None: >>> first_iteration_weights = nn.feedforward() >>> nn.back_propagation() >>> updated_weights = nn.second_hidden_layer_and_output_layer_weights - >>> (first_iteration_weights == updated_weights).all() + >>> bool((first_iteration_weights == updated_weights).all()) False """ for iteration in range(1, iterations + 1): diff --git a/other/bankers_algorithm.py b/other/bankers_algorithm.py index 858eb0b2c524..d4254f479a4f 100644 --- a/other/bankers_algorithm.py +++ b/other/bankers_algorithm.py @@ -87,9 +87,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, - ... test_maximum_claim_table)._BankersAlgorithm__need_index_manager() - ... ) # doctest: +NORMALIZE_WHITESPACE + >>> index_control = BankersAlgorithm( + ... test_claim_vector, test_allocated_res_table, test_maximum_claim_table + ... )._BankersAlgorithm__need_index_manager() + >>> {key: [int(x) for x in value] for key, value + ... in index_control.items()} # 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]} """ diff --git a/physics/in_static_equilibrium.py b/physics/in_static_equilibrium.py index e3c2f9d07aed..fb5a9b5fff66 100644 --- a/physics/in_static_equilibrium.py +++ b/physics/in_static_equilibrium.py @@ -53,7 +53,7 @@ def in_static_equilibrium( # summation of moments is zero moments: NDArray[float64] = cross(location, forces) sum_moments: float = sum(moments) - return abs(sum_moments) < eps + return bool(abs(sum_moments) < eps) if __name__ == "__main__": diff --git a/requirements.txt b/requirements.txt index bb3d671393b9..afbf25ba6edc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ beautifulsoup4 fake_useragent imageio -keras ; python_version < '3.12' +keras lxml matplotlib numpy @@ -17,7 +17,7 @@ rich scikit-learn statsmodels sympy -tensorflow +tensorflow ; python_version < '3.13' tweepy # yulewalker # uncomment once audio_filters/equal_loudness_filter.py is fixed typing_extensions diff --git a/web_programming/get_user_tweets.py b/web_programming/get_user_tweets.py.DISABLED similarity index 100% rename from web_programming/get_user_tweets.py rename to web_programming/get_user_tweets.py.DISABLED From 0abeeab39f4a612968a10b0541f630239b78f34f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 1 Oct 2024 17:32:31 +0200 Subject: [PATCH 2771/2908] Drop six from our GitHub Actions (#11621) Drop https://six.readthedocs.io --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dad2b2fac086..f54cc982d1ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} - name: Install dependencies run: | - python -m pip install --upgrade pip setuptools six wheel + python -m pip install --upgrade pip setuptools wheel python -m pip install pytest-cov -r requirements.txt - name: Run tests # TODO: #8818 Re-enable quantum tests From 43a47e01eb2c2b681fa377b02150edba5cc76e32 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:18:17 +0530 Subject: [PATCH 2772/2908] Add word ladder algorithm in backtracking (#11590) * Add word ladder algorithm in backtracking * Improve comments and implement ruff checks * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Change BFS to Backtracking * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Incorporate PR Changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add type hints for backtrack function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Hardvan Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 1 + backtracking/word_ladder.py | 100 ++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 backtracking/word_ladder.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 56ab8377f16b..cdbbac684fd2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -22,6 +22,7 @@ * [Rat In Maze](backtracking/rat_in_maze.py) * [Sudoku](backtracking/sudoku.py) * [Sum Of Subsets](backtracking/sum_of_subsets.py) + * [Word Ladder](backtracking/word_ladder.py) * [Word Search](backtracking/word_search.py) ## Bit Manipulation diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py new file mode 100644 index 000000000000..7d9fd00f6669 --- /dev/null +++ b/backtracking/word_ladder.py @@ -0,0 +1,100 @@ +""" +Word Ladder is a classic problem in computer science. +The problem is to transform a start word into an end word +by changing one letter at a time. +Each intermediate word must be a valid word from a given list of words. +The goal is to find a transformation sequence +from the start word to the end word. + +Wikipedia: https://en.wikipedia.org/wiki/Word_ladder +""" + +import string + + +def backtrack( + current_word: str, path: list[str], end_word: str, word_set: set[str] +) -> list[str]: + """ + Helper function to perform backtracking to find the transformation + from the current_word to the end_word. + + Parameters: + current_word (str): The current word in the transformation sequence. + path (list[str]): The list of transformations from begin_word to current_word. + end_word (str): The target word for transformation. + word_set (set[str]): The set of valid words for transformation. + + Returns: + list[str]: The list of transformations from begin_word to end_word. + Returns an empty list if there is no valid + transformation from current_word to end_word. + + Example: + >>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log", "cog"}) + ['hit', 'hot', 'dot', 'lot', 'log', 'cog'] + + >>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log"}) + [] + + >>> backtrack("lead", ["lead"], "gold", {"load", "goad", "gold", "lead", "lord"}) + ['lead', 'lead', 'load', 'goad', 'gold'] + + >>> backtrack("game", ["game"], "code", {"came", "cage", "code", "cade", "gave"}) + ['game', 'came', 'cade', 'code'] + """ + + # Base case: If the current word is the end word, return the path + if current_word == end_word: + return path + + # Try all possible single-letter transformations + for i in range(len(current_word)): + for c in string.ascii_lowercase: # Try changing each letter + transformed_word = current_word[:i] + c + current_word[i + 1 :] + if transformed_word in word_set: + word_set.remove(transformed_word) + # Recur with the new word added to the path + result = backtrack( + transformed_word, [*path, transformed_word], end_word, word_set + ) + if result: # valid transformation found + return result + word_set.add(transformed_word) # backtrack + + return [] # No valid transformation found + + +def word_ladder(begin_word: str, end_word: str, word_set: set[str]) -> list[str]: + """ + Solve the Word Ladder problem using Backtracking and return + the list of transformations from begin_word to end_word. + + Parameters: + begin_word (str): The word from which the transformation starts. + end_word (str): The target word for transformation. + word_list (list[str]): The list of valid words for transformation. + + Returns: + list[str]: The list of transformations from begin_word to end_word. + Returns an empty list if there is no valid transformation. + + Example: + >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"]) + ['hit', 'hot', 'dot', 'lot', 'log', 'cog'] + + >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) + [] + + >>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"]) + ['lead', 'lead', 'load', 'goad', 'gold'] + + >>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"]) + ['game', 'came', 'cade', 'code'] + """ + + if end_word not in word_set: # no valid transformation possible + return [] + + # Perform backtracking starting from the begin_word + return backtrack(begin_word, [begin_word], end_word, word_set) From 00e9d862248a27281d4de24c8c7eb2d7b018531c Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:24:12 +0530 Subject: [PATCH 2773/2908] Improve comments, add doctests in symmetric_tree.py (#11619) --- data_structures/binary_tree/symmetric_tree.py | 67 +++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/data_structures/binary_tree/symmetric_tree.py b/data_structures/binary_tree/symmetric_tree.py index 98a766cab988..2bfeac98b2c9 100644 --- a/data_structures/binary_tree/symmetric_tree.py +++ b/data_structures/binary_tree/symmetric_tree.py @@ -13,7 +13,21 @@ @dataclass class Node: """ - A Node has data variable and pointers to Nodes to its left and right. + A Node represents an element of a binary tree, which contains: + + Attributes: + data: The value stored in the node (int). + left: Pointer to the left child node (Node or None). + right: Pointer to the right child node (Node or None). + + Example: + >>> node = Node(1, Node(2), Node(3)) + >>> node.data + 1 + >>> node.left.data + 2 + >>> node.right.data + 3 """ data: int @@ -24,12 +38,25 @@ class Node: def make_symmetric_tree() -> Node: r""" Create a symmetric tree for testing. + The tree looks like this: 1 / \ 2 2 / \ / \ 3 4 4 3 + + Returns: + Node: Root node of a symmetric tree. + + Example: + >>> tree = make_symmetric_tree() + >>> tree.data + 1 + >>> tree.left.data == tree.right.data + True + >>> tree.left.left.data == tree.right.right.data + True """ root = Node(1) root.left = Node(2) @@ -43,13 +70,26 @@ def make_symmetric_tree() -> Node: def make_asymmetric_tree() -> Node: r""" - Create a asymmetric tree for testing. + Create an asymmetric tree for testing. + The tree looks like this: 1 / \ 2 2 / \ / \ 3 4 3 4 + + Returns: + Node: Root node of an asymmetric tree. + + Example: + >>> tree = make_asymmetric_tree() + >>> tree.data + 1 + >>> tree.left.data == tree.right.data + True + >>> tree.left.left.data == tree.right.right.data + False """ root = Node(1) root.left = Node(2) @@ -63,7 +103,15 @@ def make_asymmetric_tree() -> Node: def is_symmetric_tree(tree: Node) -> bool: """ - Test cases for is_symmetric_tree function + Check if a binary tree is symmetric (i.e., a mirror of itself). + + Parameters: + tree: The root node of the binary tree. + + Returns: + bool: True if the tree is symmetric, False otherwise. + + Example: >>> is_symmetric_tree(make_symmetric_tree()) True >>> is_symmetric_tree(make_asymmetric_tree()) @@ -76,8 +124,17 @@ def is_symmetric_tree(tree: Node) -> bool: def is_mirror(left: Node | None, right: Node | None) -> bool: """ + Check if two subtrees are mirror images of each other. + + Parameters: + left: The root node of the left subtree. + right: The root node of the right subtree. + + Returns: + bool: True if the two subtrees are mirrors of each other, False otherwise. + + Example: >>> tree1 = make_symmetric_tree() - >>> tree1.right.right = Node(3) >>> is_mirror(tree1.left, tree1.right) True >>> tree2 = make_asymmetric_tree() @@ -91,7 +148,7 @@ def is_mirror(left: Node | None, right: Node | None) -> bool: # One side is empty while the other is not, which is not symmetric. return False if left.data == right.data: - # The values match, so check the subtree + # The values match, so check the subtrees recursively. return is_mirror(left.left, right.right) and is_mirror(left.right, right.left) return False From 918fa8bb8ae1f052921fffd188d229d4713c73c9 Mon Sep 17 00:00:00 2001 From: 1227haran <68032825+1227haran@users.noreply.github.com> Date: Wed, 2 Oct 2024 23:37:07 +0530 Subject: [PATCH 2774/2908] Optimized O(n) to O(1) (#11669) --- data_structures/linked_list/has_loop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/linked_list/has_loop.py b/data_structures/linked_list/has_loop.py index bc06ffe150e8..f49e01579adc 100644 --- a/data_structures/linked_list/has_loop.py +++ b/data_structures/linked_list/has_loop.py @@ -14,11 +14,11 @@ def __init__(self, data: Any) -> None: def __iter__(self): node = self - visited = [] + visited = set() while node: if node in visited: raise ContainsLoopError - visited.append(node) + visited.add(node) yield node.data node = node.next_node From f4b4ac159a17e0621e7f37141b165d58ca655b81 Mon Sep 17 00:00:00 2001 From: Ali Rashid <110668489+alirashidAR@users.noreply.github.com> Date: Thu, 3 Oct 2024 05:24:56 +0530 Subject: [PATCH 2775/2908] Adding Doctests to floyd_warshall.py (#11690) * Ruff test resolution * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/floyd_warshall.py | 47 +++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/dynamic_programming/floyd_warshall.py b/dynamic_programming/floyd_warshall.py index 2331f3e65483..b92c6667fb5c 100644 --- a/dynamic_programming/floyd_warshall.py +++ b/dynamic_programming/floyd_warshall.py @@ -12,19 +12,58 @@ def __init__(self, n=0): # a graph with Node 0,1,...,N-1 ] # dp[i][j] stores minimum distance from i to j def add_edge(self, u, v, w): + """ + Adds a directed edge from node u + to node v with weight w. + + >>> g = Graph(3) + >>> g.add_edge(0, 1, 5) + >>> g.dp[0][1] + 5 + """ self.dp[u][v] = w def floyd_warshall(self): + """ + Computes the shortest paths between all pairs of + nodes using the Floyd-Warshall algorithm. + + >>> g = Graph(3) + >>> g.add_edge(0, 1, 1) + >>> g.add_edge(1, 2, 2) + >>> g.floyd_warshall() + >>> g.show_min(0, 2) + 3 + >>> g.show_min(2, 0) + inf + """ for k in range(self.n): for i in range(self.n): for j in range(self.n): self.dp[i][j] = min(self.dp[i][j], self.dp[i][k] + self.dp[k][j]) def show_min(self, u, v): + """ + Returns the minimum distance from node u to node v. + + >>> g = Graph(3) + >>> g.add_edge(0, 1, 3) + >>> g.add_edge(1, 2, 4) + >>> g.floyd_warshall() + >>> g.show_min(0, 2) + 7 + >>> g.show_min(1, 0) + inf + """ return self.dp[u][v] if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Example usage graph = Graph(5) graph.add_edge(0, 2, 9) graph.add_edge(0, 4, 10) @@ -38,5 +77,9 @@ def show_min(self, u, v): graph.add_edge(4, 2, 4) graph.add_edge(4, 3, 9) graph.floyd_warshall() - graph.show_min(1, 4) - graph.show_min(0, 3) + print( + graph.show_min(1, 4) + ) # Should output the minimum distance from node 1 to node 4 + print( + graph.show_min(0, 3) + ) # Should output the minimum distance from node 0 to node 3 From 080e7903a06765808c12c0c9c0b242f485cb9ce7 Mon Sep 17 00:00:00 2001 From: Aswin P Kumar <118362715+AswinPKumar01@users.noreply.github.com> Date: Thu, 3 Oct 2024 05:33:48 +0530 Subject: [PATCH 2776/2908] Add Word Break algorithm (#11687) * Add Word Break algorithm * Add Word Break algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- backtracking/word_break.py | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 backtracking/word_break.py diff --git a/backtracking/word_break.py b/backtracking/word_break.py new file mode 100644 index 000000000000..1f2ab073f499 --- /dev/null +++ b/backtracking/word_break.py @@ -0,0 +1,71 @@ +""" +Word Break Problem is a well-known problem in computer science. +Given a string and a dictionary of words, the task is to determine if +the string can be segmented into a sequence of one or more dictionary words. + +Wikipedia: https://en.wikipedia.org/wiki/Word_break_problem +""" + + +def backtrack(input_string: str, word_dict: set[str], start: int) -> bool: + """ + Helper function that uses backtracking to determine if a valid + word segmentation is possible starting from index 'start'. + + Parameters: + input_string (str): The input string to be segmented. + word_dict (set[str]): A set of valid dictionary words. + start (int): The starting index of the substring to be checked. + + Returns: + bool: True if a valid segmentation is possible, otherwise False. + + Example: + >>> backtrack("leetcode", {"leet", "code"}, 0) + True + + >>> backtrack("applepenapple", {"apple", "pen"}, 0) + True + + >>> backtrack("catsandog", {"cats", "dog", "sand", "and", "cat"}, 0) + False + """ + + # Base case: if the starting index has reached the end of the string + if start == len(input_string): + return True + + # Try every possible substring from 'start' to 'end' + for end in range(start + 1, len(input_string) + 1): + if input_string[start:end] in word_dict and backtrack( + input_string, word_dict, end + ): + return True + + return False + + +def word_break(input_string: str, word_dict: set[str]) -> bool: + """ + Determines if the input string can be segmented into a sequence of + valid dictionary words using backtracking. + + Parameters: + input_string (str): The input string to segment. + word_dict (set[str]): The set of valid words. + + Returns: + bool: True if the string can be segmented into valid words, otherwise False. + + Example: + >>> word_break("leetcode", {"leet", "code"}) + True + + >>> word_break("applepenapple", {"apple", "pen"}) + True + + >>> word_break("catsandog", {"cats", "dog", "sand", "and", "cat"}) + False + """ + + return backtrack(input_string, word_dict, 0) From 40f65e8150045dc82a7a58fe7cff6bfb353999f2 Mon Sep 17 00:00:00 2001 From: JeevaRamanathan <64531160+JeevaRamanathan@users.noreply.github.com> Date: Thu, 3 Oct 2024 05:48:01 +0530 Subject: [PATCH 2777/2908] Improve comments, docstrings in next_greatest_element.py (#11685) * Improve comments in next_greatest_element.py Signed-off-by: JeevaRamanathan * few changes Signed-off-by: JeevaRamanathan * updated descriptions of the functions parameters Signed-off-by: JeevaRamanathan --------- Signed-off-by: JeevaRamanathan --- .../stacks/next_greater_element.py | 60 ++++++++++++++----- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/data_structures/stacks/next_greater_element.py b/data_structures/stacks/next_greater_element.py index 7d76d1f47dfa..216850b4b894 100644 --- a/data_structures/stacks/next_greater_element.py +++ b/data_structures/stacks/next_greater_element.py @@ -6,9 +6,20 @@ def next_greatest_element_slow(arr: list[float]) -> list[float]: """ - 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. + Get the Next Greatest Element (NGE) for each element in the array + by checking all subsequent elements to find the next greater one. + + This is a brute-force implementation, and it has a time complexity + of O(n^2), where n is the size of the array. + + Args: + arr: List of numbers for which the NGE is calculated. + + Returns: + List containing the next greatest elements. If no + greater element is found, -1 is placed in the result. + + Example: >>> next_greatest_element_slow(arr) == expect True """ @@ -28,9 +39,21 @@ def next_greatest_element_slow(arr: list[float]) -> list[float]: def next_greatest_element_fast(arr: list[float]) -> list[float]: """ - 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. + Find the Next Greatest Element (NGE) for each element in the array + using a more readable approach. This implementation utilizes + enumerate() for the outer loop and slicing for the inner loop. + + While this improves readability over next_greatest_element_slow(), + it still has a time complexity of O(n^2). + + Args: + arr: List of numbers for which the NGE is calculated. + + Returns: + List containing the next greatest elements. If no + greater element is found, -1 is placed in the result. + + Example: >>> next_greatest_element_fast(arr) == expect True """ @@ -47,14 +70,23 @@ def next_greatest_element_fast(arr: list[float]) -> list[float]: def next_greatest_element(arr: list[float]) -> list[float]: """ - 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. + Efficient solution to find the Next Greatest Element (NGE) for all elements + using a stack. The time complexity is reduced to O(n), making it suitable + for larger arrays. + + The stack keeps track of elements for which the next greater element hasn't + been found yet. By iterating through the array in reverse (from the last + element to the first), the stack is used to efficiently determine the next + greatest element for each element. + + Args: + arr: List of numbers for which the NGE is calculated. + + Returns: + List containing the next greatest elements. If no + greater element is found, -1 is placed in the result. + + Example: >>> next_greatest_element(arr) == expect True """ From e20b503b24fc271321a23584772ad8f0db17daf2 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:36:08 +0530 Subject: [PATCH 2778/2908] Improve comments, add doctests for kahns_algorithm_topo.py (#11668) * Improve comments, add doctests for kahns_algorithm_topo.py * Improve function docstring * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename variables, remove print --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- graphs/kahns_algorithm_topo.py | 67 +++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/graphs/kahns_algorithm_topo.py b/graphs/kahns_algorithm_topo.py index b1260bd5bd9b..c956cf9f48fd 100644 --- a/graphs/kahns_algorithm_topo.py +++ b/graphs/kahns_algorithm_topo.py @@ -1,36 +1,61 @@ -def topological_sort(graph): +def topological_sort(graph: dict[int, list[int]]) -> list[int] | None: """ - Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph - using BFS + Perform topological sorting of a Directed Acyclic Graph (DAG) + using Kahn's Algorithm via Breadth-First Search (BFS). + + Topological sorting is a linear ordering of vertices in a graph such that for + every directed edge u → v, vertex u comes before vertex v in the ordering. + + Parameters: + graph: Adjacency list representing the directed graph where keys are + vertices, and values are lists of adjacent vertices. + + Returns: + The topologically sorted order of vertices if the graph is a DAG. + Returns None if the graph contains a cycle. + + Example: + >>> graph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []} + >>> topological_sort(graph) + [0, 1, 2, 3, 4, 5] + + >>> graph_with_cycle = {0: [1], 1: [2], 2: [0]} + >>> topological_sort(graph_with_cycle) """ + indegree = [0] * len(graph) queue = [] - topo = [] - cnt = 0 + topo_order = [] + processed_vertices_count = 0 + # Calculate the indegree of each vertex for values in graph.values(): for i in values: indegree[i] += 1 + # Add all vertices with 0 indegree to the queue for i in range(len(indegree)): if indegree[i] == 0: queue.append(i) + # Perform BFS while queue: vertex = queue.pop(0) - cnt += 1 - topo.append(vertex) - for x in graph[vertex]: - indegree[x] -= 1 - if indegree[x] == 0: - queue.append(x) - - if cnt != len(graph): - print("Cycle exists") - else: - print(topo) - - -# Adjacency List of Graph -graph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []} -topological_sort(graph) + processed_vertices_count += 1 + topo_order.append(vertex) + + # Traverse neighbors + for neighbor in graph[vertex]: + indegree[neighbor] -= 1 + if indegree[neighbor] == 0: + queue.append(neighbor) + + if processed_vertices_count != len(graph): + return None # no topological ordering exists due to cycle + return topo_order # valid topological ordering + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 917ad62105dc829e45c0732d9ac2aae7ef358627 Mon Sep 17 00:00:00 2001 From: Sai Aswin Madhavan Date: Fri, 4 Oct 2024 14:58:50 +0530 Subject: [PATCH 2779/2908] Removed incorrect type hints (#11711) --- strings/min_cost_string_conversion.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index d147a9d7954c..40d54f0e8420 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -21,7 +21,6 @@ def compute_transform_tables( 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) ] @@ -31,28 +30,28 @@ def compute_transform_tables( for i in range(1, len_source_seq + 1): costs[i][0] = i * delete_cost - ops[i][0] = f"D{source_seq[i - 1]:c}" + ops[i][0] = f"D{source_seq[i - 1]}" for i in range(1, len_destination_seq + 1): costs[0][i] = i * insert_cost - ops[0][i] = f"I{destination_seq[i - 1]:c}" + ops[0][i] = f"I{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] = f"C{source_seq[i - 1]:c}" + ops[i][j] = f"C{source_seq[i - 1]}" else: costs[i][j] = costs[i - 1][j - 1] + replace_cost - ops[i][j] = f"R{source_seq[i - 1]:c}" + str(destination_seq[j - 1]) + ops[i][j] = f"R{source_seq[i - 1]}" + str(destination_seq[j - 1]) if costs[i - 1][j] + delete_cost < costs[i][j]: costs[i][j] = costs[i - 1][j] + delete_cost - ops[i][j] = f"D{source_seq[i - 1]:c}" + ops[i][j] = f"D{source_seq[i - 1]}" if costs[i][j - 1] + insert_cost < costs[i][j]: costs[i][j] = costs[i][j - 1] + insert_cost - ops[i][j] = f"I{destination_seq[j - 1]:c}" + ops[i][j] = f"I{destination_seq[j - 1]}" return costs, ops From 59ff87dc55b704dc7d3683bb6fabc7c4dc0afade Mon Sep 17 00:00:00 2001 From: Lonercode <91500485+Lonercode@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:36:14 +0100 Subject: [PATCH 2780/2908] Added doctests to min_cost_string_conversion.py and removed :c specifier (#11721) * Added doctests to min_cost_string_conversion.py and removed :c specifier * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * resolved line length issues based on ruff requirements * modified in compliance with ruff for line length * Update strings/min_cost_string_conversion.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- strings/min_cost_string_conversion.py | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index 40d54f0e8420..a5a3c4a4e3f8 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -17,6 +17,23 @@ def compute_transform_tables( delete_cost: int, insert_cost: int, ) -> tuple[list[list[int]], list[list[str]]]: + """ + Finds the most cost efficient sequence + for converting one string into another. + + >>> costs, operations = compute_transform_tables("cat", "cut", 1, 2, 3, 3) + >>> costs[0][:4] + [0, 3, 6, 9] + >>> costs[2][:4] + [6, 4, 3, 6] + >>> operations[0][:4] + ['0', 'Ic', 'Iu', 'It'] + >>> operations[3][:4] + ['Dt', 'Dt', 'Rtu', 'Ct'] + + >>> compute_transform_tables("", "", 1, 2, 3, 3) + ([[0]], [['0']]) + """ source_seq = list(source_string) destination_seq = list(destination_string) len_source_seq = len(source_seq) @@ -57,6 +74,24 @@ def compute_transform_tables( def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: + """ + Assembles the transformations based on the ops table. + + >>> ops = [['0', 'Ic', 'Iu', 'It'], + ... ['Dc', 'Cc', 'Iu', 'It'], + ... ['Da', 'Da', 'Rau', 'Rat'], + ... ['Dt', 'Dt', 'Rtu', 'Ct']] + >>> x = len(ops) - 1 + >>> y = len(ops[0]) - 1 + >>> assemble_transformation(ops, x, y) + ['Cc', 'Rau', 'Ct'] + + >>> ops1 = [['0']] + >>> x1 = len(ops1) - 1 + >>> y1 = len(ops1[0]) - 1 + >>> assemble_transformation(ops1, x1, y1) + [] + """ if i == 0 and j == 0: return [] elif ops[i][j][0] in {"C", "R"}: From 9a572dec2b6011e7c2c0d82f50989b3a404ea426 Mon Sep 17 00:00:00 2001 From: ARNAV RAJ <126798788+Acuspeedster@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:59:39 +0530 Subject: [PATCH 2781/2908] feat: Implemented Matrix Exponentiation Method (#11747) * feat: add Matrix Exponentiation method docs: updated the header documentation and added new documentation for the new function. * feat: added new function matrix exponetiation method * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feat: This function uses the tail-recursive form of the Euclidean algorithm to calculate * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * reduced the number of characters per line in the comments * removed unwanted code * feat: Implemented a new function to swaap numbers without dummy variable * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed previos code * Done with the required changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Done with the required changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Done with the required changes * Done with the required changes * Done with the required changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/fibonacci.py Co-authored-by: Tianyi Zheng * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Done with the required changes * Done with the required changes * Done with the required changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/fibonacci.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/maths/fibonacci.py b/maths/fibonacci.py index 927700b0418e..24b2d7ae449e 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -7,6 +7,8 @@ NOTE 2: the Binet's formula function is much more limited in the size of inputs that it can handle due to the size limitations of Python floats +NOTE 3: the matrix function is the fastest and most memory efficient for large n + See benchmark numbers in __main__ for performance comparisons/ https://en.wikipedia.org/wiki/Fibonacci_number for more information @@ -17,6 +19,9 @@ from math import sqrt from time import time +import numpy as np +from numpy import ndarray + def time_func(func, *args, **kwargs): """ @@ -230,6 +235,88 @@ def fib_binet(n: int) -> list[int]: return [round(phi**i / sqrt_5) for i in range(n + 1)] +def matrix_pow_np(m: ndarray, power: int) -> ndarray: + """ + Raises a matrix to the power of 'power' using binary exponentiation. + + Args: + m: Matrix as a numpy array. + power: The power to which the matrix is to be raised. + + Returns: + The matrix raised to the power. + + Raises: + ValueError: If power is negative. + + >>> m = np.array([[1, 1], [1, 0]], dtype=int) + >>> matrix_pow_np(m, 0) # Identity matrix when raised to the power of 0 + array([[1, 0], + [0, 1]]) + + >>> matrix_pow_np(m, 1) # Same matrix when raised to the power of 1 + array([[1, 1], + [1, 0]]) + + >>> matrix_pow_np(m, 5) + array([[8, 5], + [5, 3]]) + + >>> matrix_pow_np(m, -1) + Traceback (most recent call last): + ... + ValueError: power is negative + """ + result = np.array([[1, 0], [0, 1]], dtype=int) # Identity Matrix + base = m + if power < 0: # Negative power is not allowed + raise ValueError("power is negative") + while power: + if power % 2 == 1: + result = np.dot(result, base) + base = np.dot(base, base) + power //= 2 + return result + + +def fib_matrix_np(n: int) -> int: + """ + Calculates the n-th Fibonacci number using matrix exponentiation. + https://www.nayuki.io/page/fast-fibonacci-algorithms#:~:text= + Summary:%20The%20two%20fast%20Fibonacci%20algorithms%20are%20matrix + + Args: + n: Fibonacci sequence index + + Returns: + The n-th Fibonacci number. + + Raises: + ValueError: If n is negative. + + >>> fib_matrix_np(0) + 0 + >>> fib_matrix_np(1) + 1 + >>> fib_matrix_np(5) + 5 + >>> fib_matrix_np(10) + 55 + >>> fib_matrix_np(-1) + Traceback (most recent call last): + ... + ValueError: n is negative + """ + if n < 0: + raise ValueError("n is negative") + if n == 0: + return 0 + + m = np.array([[1, 1], [1, 0]], dtype=int) + result = matrix_pow_np(m, n - 1) + return int(result[0, 0]) + + if __name__ == "__main__": from doctest import testmod @@ -242,3 +329,4 @@ def fib_binet(n: int) -> list[int]: time_func(fib_memoization, num) # 0.0100 ms time_func(fib_recursive_cached, num) # 0.0153 ms time_func(fib_recursive, num) # 257.0910 ms + time_func(fib_matrix_np, num) # 0.0000 ms From 5a8655d306d872085112d965067fcdc440286928 Mon Sep 17 00:00:00 2001 From: 1227haran <68032825+1227haran@users.noreply.github.com> Date: Sat, 5 Oct 2024 22:49:58 +0530 Subject: [PATCH 2782/2908] Added new algorithm to generate numbers in lexicographical order (#11674) * Added algorithm to generate numbers in lexicographical order * Removed the test cases * Updated camelcase to snakecase * Added doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added descriptive name for n * Reduced the number of letters * Updated the return type * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated import statement * Updated return type to Iterator[int] * removed parentheses --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../stacks/lexicographical_numbers.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 data_structures/stacks/lexicographical_numbers.py diff --git a/data_structures/stacks/lexicographical_numbers.py b/data_structures/stacks/lexicographical_numbers.py new file mode 100644 index 000000000000..6a174e7d9e95 --- /dev/null +++ b/data_structures/stacks/lexicographical_numbers.py @@ -0,0 +1,38 @@ +from collections.abc import Iterator + + +def lexical_order(max_number: int) -> Iterator[int]: + """ + Generate numbers in lexical order from 1 to max_number. + + >>> " ".join(map(str, lexical_order(13))) + '1 10 11 12 13 2 3 4 5 6 7 8 9' + >>> list(lexical_order(1)) + [1] + >>> " ".join(map(str, lexical_order(20))) + '1 10 11 12 13 14 15 16 17 18 19 2 20 3 4 5 6 7 8 9' + >>> " ".join(map(str, lexical_order(25))) + '1 10 11 12 13 14 15 16 17 18 19 2 20 21 22 23 24 25 3 4 5 6 7 8 9' + >>> list(lexical_order(12)) + [1, 10, 11, 12, 2, 3, 4, 5, 6, 7, 8, 9] + """ + + stack = [1] + + while stack: + num = stack.pop() + if num > max_number: + continue + + yield num + if (num % 10) != 9: + stack.append(num + 1) + + stack.append(num * 10) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + print(f"Numbers from 1 to 25 in lexical order: {list(lexical_order(26))}") From 50aca04c67315ef7de7ef03e51a018075d8d026b Mon Sep 17 00:00:00 2001 From: Jeel Rupapara Date: Sat, 5 Oct 2024 22:51:43 +0530 Subject: [PATCH 2783/2908] feat: increase test coverage of longest_common_subsequence to 75% (#11777) --- .../longest_common_subsequence.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 9a98b1736ed5..4a6c880aff61 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -28,6 +28,24 @@ def longest_common_subsequence(x: str, y: str): (2, 'ph') >>> longest_common_subsequence("computer", "food") (1, 'o') + >>> longest_common_subsequence("", "abc") # One string is empty + (0, '') + >>> longest_common_subsequence("abc", "") # Other string is empty + (0, '') + >>> longest_common_subsequence("", "") # Both strings are empty + (0, '') + >>> longest_common_subsequence("abc", "def") # No common subsequence + (0, '') + >>> longest_common_subsequence("abc", "abc") # Identical strings + (3, 'abc') + >>> longest_common_subsequence("a", "a") # Single character match + (1, 'a') + >>> longest_common_subsequence("a", "b") # Single character no match + (0, '') + >>> longest_common_subsequence("abcdef", "ace") # Interleaved subsequence + (3, 'ace') + >>> longest_common_subsequence("ABCD", "ACBD") # No repeated characters + (3, 'ABD') """ # find the length of strings From ad6395d3408b9d80a0bef4d180d1e7613a55d807 Mon Sep 17 00:00:00 2001 From: Andrey Ivanov <97749666+ivnvxd@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:24:58 +0100 Subject: [PATCH 2784/2908] Update ruff usage example in CONTRIBUTING.md (#11772) * Update ruff usage example * Update CONTRIBUTING.md Co-authored-by: Tianyi Zheng --------- Co-authored-by: Tianyi Zheng --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 096582e45afa..b5113212929a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,7 +96,7 @@ We want your work to be readable by others; therefore, we encourage you to note ```bash python3 -m pip install ruff # only required the first time - ruff . + ruff check ``` - Original code submission require docstrings or comments to describe your work. From fcf82a1eda21dcf36254a8fcaadc913f6a94c8da Mon Sep 17 00:00:00 2001 From: Vineet Kumar <108144301+whyvineet@users.noreply.github.com> Date: Sat, 5 Oct 2024 23:04:48 +0530 Subject: [PATCH 2785/2908] =?UTF-8?q?Implemented=20Exponential=20Search=20?= =?UTF-8?q?with=20binary=20search=20for=20improved=20perfor=E2=80=A6=20(#1?= =?UTF-8?q?1666)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implemented Exponential Search with binary search for improved performance on large sorted arrays. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added type hints and doctests for binary_search and exponential_search functions. Improved code documentation and ensured testability. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and rename Exponential_Search.py to exponential_search.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- searches/exponential_search.py | 113 +++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 searches/exponential_search.py diff --git a/searches/exponential_search.py b/searches/exponential_search.py new file mode 100644 index 000000000000..ed09b14e101c --- /dev/null +++ b/searches/exponential_search.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +""" +Pure Python implementation of exponential search algorithm + +For more information, see the Wikipedia page: +https://en.wikipedia.org/wiki/Exponential_search + +For doctests run the following command: +python3 -m doctest -v exponential_search.py + +For manual testing run: +python3 exponential_search.py +""" + +from __future__ import annotations + + +def binary_search_by_recursion( + sorted_collection: list[int], item: int, left: int = 0, right: int = -1 +) -> int: + """Pure implementation of binary search algorithm in Python using recursion + + Be careful: the collection must be ascending sorted otherwise, the result will be + unpredictable. + + :param sorted_collection: some ascending sorted collection with comparable items + :param item: item value to search + :param left: starting index for the search + :param right: ending index for the search + :return: index of the found item or -1 if the item is not found + + Examples: + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4) + 0 + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4) + 4 + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4) + 1 + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4) + -1 + """ + if right < 0: + right = len(sorted_collection) - 1 + if list(sorted_collection) != sorted(sorted_collection): + raise ValueError("sorted_collection must be sorted in ascending order") + if right < left: + return -1 + + midpoint = left + (right - left) // 2 + + if sorted_collection[midpoint] == item: + return midpoint + elif sorted_collection[midpoint] > item: + return binary_search_by_recursion(sorted_collection, item, left, midpoint - 1) + else: + return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right) + + +def exponential_search(sorted_collection: list[int], item: int) -> int: + """ + Pure implementation of an exponential search algorithm in Python. + For more information, refer to: + https://en.wikipedia.org/wiki/Exponential_search + + Be careful: the collection must be ascending sorted, otherwise the result will be + unpredictable. + + :param sorted_collection: some ascending sorted collection with comparable items + :param item: item value to search + :return: index of the found item or -1 if the item is not found + + The time complexity of this algorithm is O(log i) where i is the index of the item. + + Examples: + >>> exponential_search([0, 5, 7, 10, 15], 0) + 0 + >>> exponential_search([0, 5, 7, 10, 15], 15) + 4 + >>> exponential_search([0, 5, 7, 10, 15], 5) + 1 + >>> exponential_search([0, 5, 7, 10, 15], 6) + -1 + """ + if list(sorted_collection) != sorted(sorted_collection): + raise ValueError("sorted_collection must be sorted in ascending order") + + if sorted_collection[0] == item: + return 0 + + bound = 1 + while bound < len(sorted_collection) and sorted_collection[bound] < item: + bound *= 2 + + left = bound // 2 + right = min(bound, len(sorted_collection) - 1) + return binary_search_by_recursion(sorted_collection, item, left, right) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Manual testing + user_input = input("Enter numbers separated by commas: ").strip() + collection = sorted(int(item) for item in user_input.split(",")) + target = int(input("Enter a number to search for: ")) + result = exponential_search(sorted_collection=collection, item=target) + if result == -1: + print(f"{target} was not found in {collection}.") + else: + print(f"{target} was found at index {result} in {collection}.") From 3422ebc75bda6aba9b234eb217a79f25bec65f21 Mon Sep 17 00:00:00 2001 From: Jeel Rupapara Date: Mon, 7 Oct 2024 12:00:11 +0530 Subject: [PATCH 2786/2908] feat: add testcase of polynom_for_points (#11811) * feat: add testcase of polynom_for_points * fix: remove the print from the testcase of points_to_polynomial * fix: remove print statement from old test cases --- linear_algebra/src/polynom_for_points.py | 42 ++++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/linear_algebra/src/polynom_for_points.py b/linear_algebra/src/polynom_for_points.py index a9a9a8117c18..452f3edd4aee 100644 --- a/linear_algebra/src/polynom_for_points.py +++ b/linear_algebra/src/polynom_for_points.py @@ -3,30 +3,36 @@ 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 - >>> print(points_to_polynomial([])) + >>> points_to_polynomial([]) Traceback (most recent call last): ... ValueError: The program cannot work out a fitting polynomial. - >>> print(points_to_polynomial([[]])) + >>> points_to_polynomial([[]]) + Traceback (most recent call last): + ... + ValueError: The program cannot work out a fitting polynomial. + >>> points_to_polynomial([[1, 0], [2, 0], [3, 0]]) + 'f(x)=x^2*0.0+x^1*-0.0+x^0*0.0' + >>> points_to_polynomial([[1, 1], [2, 1], [3, 1]]) + 'f(x)=x^2*0.0+x^1*-0.0+x^0*1.0' + >>> points_to_polynomial([[1, 3], [2, 3], [3, 3]]) + 'f(x)=x^2*0.0+x^1*-0.0+x^0*3.0' + >>> points_to_polynomial([[1, 1], [2, 2], [3, 3]]) + 'f(x)=x^2*0.0+x^1*1.0+x^0*0.0' + >>> points_to_polynomial([[1, 1], [2, 4], [3, 9]]) + 'f(x)=x^2*1.0+x^1*-0.0+x^0*0.0' + >>> points_to_polynomial([[1, 3], [2, 6], [3, 11]]) + 'f(x)=x^2*1.0+x^1*-0.0+x^0*2.0' + >>> points_to_polynomial([[1, -3], [2, -6], [3, -11]]) + 'f(x)=x^2*-1.0+x^1*-0.0+x^0*-2.0' + >>> points_to_polynomial([[1, 5], [2, 2], [3, 9]]) + 'f(x)=x^2*5.0+x^1*-18.0+x^0*18.0' + >>> points_to_polynomial([[1, 1], [1, 2], [1, 3]]) + 'x=1' + >>> points_to_polynomial([[1, 1], [2, 2], [2, 2]]) Traceback (most recent call last): ... ValueError: 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 """ if len(coordinates) == 0 or not all(len(pair) == 2 for pair in coordinates): raise ValueError("The program cannot work out a fitting polynomial.") From cfd6d095f122d1d3ef2f3c2cdcf84864aac56fa7 Mon Sep 17 00:00:00 2001 From: 1227haran <68032825+1227haran@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:06:15 +0530 Subject: [PATCH 2787/2908] Added max_sum_bst.py (#11832) * Added new algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated filename * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated the code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated the code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated the code * Updated code * Updated code * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated the code * Updated code * Updated code * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * updated * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * Update maximum_sum_bst.py * def max_sum_bst(root: TreeNode | None) -> int: * def solver(node: TreeNode | None) -> tuple[bool, int, int, int]: --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../binary_tree/maximum_sum_bst.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 data_structures/binary_tree/maximum_sum_bst.py diff --git a/data_structures/binary_tree/maximum_sum_bst.py b/data_structures/binary_tree/maximum_sum_bst.py new file mode 100644 index 000000000000..7dadc7b95920 --- /dev/null +++ b/data_structures/binary_tree/maximum_sum_bst.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +import sys +from dataclasses import dataclass + +INT_MIN = -sys.maxsize + 1 +INT_MAX = sys.maxsize - 1 + + +@dataclass +class TreeNode: + val: int = 0 + left: TreeNode | None = None + right: TreeNode | None = None + + +def max_sum_bst(root: TreeNode | None) -> int: + """ + The solution traverses a binary tree to find the maximum sum of + keys in any subtree that is a Binary Search Tree (BST). It uses + recursion to validate BST properties and calculates sums, returning + the highest sum found among all valid BST subtrees. + + >>> t1 = TreeNode(4) + >>> t1.left = TreeNode(3) + >>> t1.left.left = TreeNode(1) + >>> t1.left.right = TreeNode(2) + >>> print(max_sum_bst(t1)) + 2 + >>> t2 = TreeNode(-4) + >>> t2.left = TreeNode(-2) + >>> t2.right = TreeNode(-5) + >>> print(max_sum_bst(t2)) + 0 + >>> t3 = TreeNode(1) + >>> t3.left = TreeNode(4) + >>> t3.left.left = TreeNode(2) + >>> t3.left.right = TreeNode(4) + >>> t3.right = TreeNode(3) + >>> t3.right.left = TreeNode(2) + >>> t3.right.right = TreeNode(5) + >>> t3.right.right.left = TreeNode(4) + >>> t3.right.right.right = TreeNode(6) + >>> print(max_sum_bst(t3)) + 20 + """ + ans: int = 0 + + def solver(node: TreeNode | None) -> tuple[bool, int, int, int]: + """ + Returns the maximum sum by making recursive calls + >>> t1 = TreeNode(1) + >>> print(solver(t1)) + 1 + """ + nonlocal ans + + if not node: + return True, INT_MAX, INT_MIN, 0 # Valid BST, min, max, sum + + is_left_valid, min_left, max_left, sum_left = solver(node.left) + is_right_valid, min_right, max_right, sum_right = solver(node.right) + + if is_left_valid and is_right_valid and max_left < node.val < min_right: + total_sum = sum_left + sum_right + node.val + ans = max(ans, total_sum) + return True, min(min_left, node.val), max(max_right, node.val), total_sum + + return False, -1, -1, -1 # Not a valid BST + + solver(root) + return ans + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From dba8eecb47cea7f11ac383344524afbc0ca7cf5b Mon Sep 17 00:00:00 2001 From: Lonercode <91500485+Lonercode@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:58:07 +0100 Subject: [PATCH 2788/2908] added gronsfeld cipher implementation (#11835) * added gronsfeld cipher implementation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * from string import ascii_uppercase * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update gronsfeld_cipher.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- ciphers/gronsfeld_cipher.py | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 ciphers/gronsfeld_cipher.py diff --git a/ciphers/gronsfeld_cipher.py b/ciphers/gronsfeld_cipher.py new file mode 100644 index 000000000000..8fbeab4307fc --- /dev/null +++ b/ciphers/gronsfeld_cipher.py @@ -0,0 +1,45 @@ +from string import ascii_uppercase + + +def gronsfeld(text: str, key: str) -> str: + """ + Encrypt plaintext with the Gronsfeld cipher + + >>> gronsfeld('hello', '412') + 'LFNPP' + >>> gronsfeld('hello', '123') + 'IGOMQ' + >>> gronsfeld('', '123') + '' + >>> gronsfeld('yes, ¥€$ - _!@#%?', '0') + 'YES, ¥€$ - _!@#%?' + >>> gronsfeld('yes, ¥€$ - _!@#%?', '01') + 'YFS, ¥€$ - _!@#%?' + >>> gronsfeld('yes, ¥€$ - _!@#%?', '012') + 'YFU, ¥€$ - _!@#%?' + >>> gronsfeld('yes, ¥€$ - _!@#%?', '') + Traceback (most recent call last): + ... + ZeroDivisionError: integer modulo by zero + """ + ascii_len = len(ascii_uppercase) + key_len = len(key) + encrypted_text = "" + keys = [int(char) for char in key] + upper_case_text = text.upper() + + for i, char in enumerate(upper_case_text): + if char in ascii_uppercase: + new_position = (ascii_uppercase.index(char) + keys[i % key_len]) % ascii_len + shifted_letter = ascii_uppercase[new_position] + encrypted_text += shifted_letter + else: + encrypted_text += char + + return encrypted_text + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 2d671df073770f0122658f462c17b838ddbe4d2a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 22:49:29 +0200 Subject: [PATCH 2789/2908] [pre-commit.ci] pre-commit autoupdate (#11874) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/astral-sh/ruff-pre-commit: v0.6.8 → v0.6.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.8...v0.6.9) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a8e5c1f6ad9..77541027afb3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-executables-have-shebangs - id: check-toml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.8 + rev: v0.6.9 hooks: - id: ruff - id: ruff-format diff --git a/DIRECTORY.md b/DIRECTORY.md index cdbbac684fd2..0a3be2a06533 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -22,6 +22,7 @@ * [Rat In Maze](backtracking/rat_in_maze.py) * [Sudoku](backtracking/sudoku.py) * [Sum Of Subsets](backtracking/sum_of_subsets.py) + * [Word Break](backtracking/word_break.py) * [Word Ladder](backtracking/word_ladder.py) * [Word Search](backtracking/word_search.py) @@ -99,6 +100,7 @@ * [Elgamal Key Generator](ciphers/elgamal_key_generator.py) * [Enigma Machine2](ciphers/enigma_machine2.py) * [Fractionated Morse Cipher](ciphers/fractionated_morse_cipher.py) + * [Gronsfeld Cipher](ciphers/gronsfeld_cipher.py) * [Hill Cipher](ciphers/hill_cipher.py) * [Mixed Keyword Cypher](ciphers/mixed_keyword_cypher.py) * [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py) @@ -211,6 +213,7 @@ * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py) * [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py) + * [Maximum Sum Bst](data_structures/binary_tree/maximum_sum_bst.py) * [Merge Two Binary Trees](data_structures/binary_tree/merge_two_binary_trees.py) * [Mirror Binary Tree](data_structures/binary_tree/mirror_binary_tree.py) * [Non Recursive Segment Tree](data_structures/binary_tree/non_recursive_segment_tree.py) @@ -284,6 +287,7 @@ * [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py) * [Infix To Postfix Conversion](data_structures/stacks/infix_to_postfix_conversion.py) * [Infix To Prefix Conversion](data_structures/stacks/infix_to_prefix_conversion.py) + * [Lexicographical Numbers](data_structures/stacks/lexicographical_numbers.py) * [Next Greater Element](data_structures/stacks/next_greater_element.py) * [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py) * [Prefix Evaluation](data_structures/stacks/prefix_evaluation.py) @@ -1201,6 +1205,7 @@ * [Binary Tree Traversal](searches/binary_tree_traversal.py) * [Double Linear Search](searches/double_linear_search.py) * [Double Linear Search Recursion](searches/double_linear_search_recursion.py) + * [Exponential Search](searches/exponential_search.py) * [Fibonacci Search](searches/fibonacci_search.py) * [Hill Climbing](searches/hill_climbing.py) * [Interpolation Search](searches/interpolation_search.py) From 260e3d8b350c64e927ecb1d62b953b8bf25490ea Mon Sep 17 00:00:00 2001 From: Jeel Rupapara Date: Tue, 8 Oct 2024 17:03:28 +0530 Subject: [PATCH 2790/2908] feat: add test cases in cipher's autokey (#11881) --- ciphers/autokey.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ciphers/autokey.py b/ciphers/autokey.py index 8683e6d37001..05d8c066b139 100644 --- a/ciphers/autokey.py +++ b/ciphers/autokey.py @@ -24,6 +24,14 @@ def encrypt(plaintext: str, key: str) -> str: Traceback (most recent call last): ... ValueError: plaintext is empty + >>> encrypt("coffee is good as python", "") + Traceback (most recent call last): + ... + ValueError: key is empty + >>> encrypt(527.26, "TheAlgorithms") + Traceback (most recent call last): + ... + TypeError: plaintext must be a string """ if not isinstance(plaintext, str): raise TypeError("plaintext must be a string") @@ -80,6 +88,14 @@ def decrypt(ciphertext: str, key: str) -> str: Traceback (most recent call last): ... TypeError: ciphertext must be a string + >>> decrypt("", "TheAlgorithms") + Traceback (most recent call last): + ... + ValueError: ciphertext is empty + >>> decrypt("vvjfpk wj ohvp su ddylsv", 2) + Traceback (most recent call last): + ... + TypeError: key must be a string """ if not isinstance(ciphertext, str): raise TypeError("ciphertext must be a string") From e9e7c964655015819e0120694465928df1abefb0 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 8 Oct 2024 19:09:28 +0200 Subject: [PATCH 2791/2908] Create GitHub Pages docs with Sphinx (#11888) --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 2 +- .github/CODEOWNERS | 2 - .github/workflows/build.yml | 3 +- .github/workflows/sphinx.yml | 50 +++++++++ CONTRIBUTING.md | 2 +- DIRECTORY.md | 3 + LICENSE.md | 2 +- docs/{source => }/__init__.py | 0 docs/conf.py | 3 + financial/{ABOUT.md => README.md} | 2 +- index.md | 10 ++ .../{local_weighted_learning.md => README.md} | 0 pyproject.toml | 106 +++++++++++++++++- requirements.txt | 1 + source/__init__.py | 0 16 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/sphinx.yml rename docs/{source => }/__init__.py (100%) create mode 100644 docs/conf.py rename financial/{ABOUT.md => README.md} (97%) create mode 100644 index.md rename machine_learning/local_weighted_learning/{local_weighted_learning.md => README.md} (100%) delete mode 100644 source/__init__.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6aa0073bf95b..a0bd05f47ec8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,5 @@ # https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/README.md -ARG VARIANT=3.12-bookworm +ARG VARIANT=3.13-bookworm FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT} COPY requirements.txt /tmp/pip-tmp/ RUN python3 -m pip install --upgrade pip \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ae1d4fb7494d..e23263f5b9de 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ // Update 'VARIANT' to pick a Python version: 3, 3.11, 3.10, 3.9, 3.8 // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local on arm64/Apple Silicon. - "VARIANT": "3.12-bookworm", + "VARIANT": "3.13-bookworm", } }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d2ac43c7df31..3cc25d1bae1c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,8 +9,6 @@ /.* @cclauss -# /arithmetic_analysis/ - # /backtracking/ # /bit_manipulation/ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f54cc982d1ec..b5703e2f1ab6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,12 +25,13 @@ jobs: - name: Run tests # TODO: #8818 Re-enable quantum tests run: pytest - --ignore=quantum/q_fourier_transform.py --ignore=computer_vision/cnn_classification.py + --ignore=docs/conf.py --ignore=dynamic_programming/k_means_clustering_tensorflow.py --ignore=machine_learning/lstm/lstm_prediction.py --ignore=neural_network/input_data.py --ignore=project_euler/ + --ignore=quantum/q_fourier_transform.py --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml new file mode 100644 index 000000000000..9dfe344f9743 --- /dev/null +++ b/.github/workflows/sphinx.yml @@ -0,0 +1,50 @@ +name: sphinx + +on: + # Triggers the workflow on push or pull request events but only for the "master" branch + push: + branches: ["master"] + pull_request: + branches: ["master"] + # Or manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build_docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.13 + allow-prereleases: true + - run: pip install --upgrade pip + - run: pip install myst-parser sphinx-autoapi sphinx-pyproject + - uses: actions/configure-pages@v5 + - run: sphinx-build -c docs . docs/_build/html + - uses: actions/upload-pages-artifact@v3 + with: + path: docs/_build/html + + deploy_docs: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + if: github.event_name != 'pull_request' + needs: build_docs + runs-on: ubuntu-latest + steps: + - uses: actions/deploy-pages@v4 + id: deployment diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5113212929a..3df39f95b784 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,7 @@ pre-commit run --all-files --show-diff-on-failure We want your work to be readable by others; therefore, we encourage you to note the following: -- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. +- Please write in Python 3.13+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. - Please focus hard on the 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. diff --git a/DIRECTORY.md b/DIRECTORY.md index 0a3be2a06533..f0a34a553946 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -351,6 +351,9 @@ * [Power](divide_and_conquer/power.py) * [Strassen Matrix Multiplication](divide_and_conquer/strassen_matrix_multiplication.py) +## Docs + * [Conf](docs/conf.py) + ## Dynamic Programming * [Abbreviation](dynamic_programming/abbreviation.py) * [All Construct](dynamic_programming/all_construct.py) diff --git a/LICENSE.md b/LICENSE.md index 2897d02e2a01..de631c3ef333 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -MIT License +## MIT License Copyright (c) 2016-2022 TheAlgorithms and contributors diff --git a/docs/source/__init__.py b/docs/__init__.py similarity index 100% rename from docs/source/__init__.py rename to docs/__init__.py diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000000..f2481f107267 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,3 @@ +from sphinx_pyproject import SphinxConfig + +project = SphinxConfig("../pyproject.toml", globalns=globals()).name diff --git a/financial/ABOUT.md b/financial/README.md similarity index 97% rename from financial/ABOUT.md rename to financial/README.md index f6b0647f8201..e5d3a84c8381 100644 --- a/financial/ABOUT.md +++ b/financial/README.md @@ -1,4 +1,4 @@ -### Interest +# Interest * Compound Interest: "Compound interest is calculated by multiplying the initial principal amount by one plus the annual interest rate raised to the number of compound periods minus one." [Compound Interest](https://www.investopedia.com/) * Simple Interest: "Simple interest paid or received over a certain period is a fixed percentage of the principal amount that was borrowed or lent. " [Simple Interest](https://www.investopedia.com/) diff --git a/index.md b/index.md new file mode 100644 index 000000000000..134520cb94aa --- /dev/null +++ b/index.md @@ -0,0 +1,10 @@ +# TheAlgorithms/Python +```{toctree} +:maxdepth: 2 +:caption: index.md + + +CONTRIBUTING.md +README.md +LICENSE.md +``` diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.md b/machine_learning/local_weighted_learning/README.md similarity index 100% rename from machine_learning/local_weighted_learning/local_weighted_learning.md rename to machine_learning/local_weighted_learning/README.md diff --git a/pyproject.toml b/pyproject.toml index bb8657183164..c57419e79db3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,22 @@ +[project] +name = "thealgorithms-python" +version = "0.0.1" +description = "TheAlgorithms in Python" +authors = [ { name = "TheAlgorithms Contributors" } ] +requires-python = ">=3.13" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.13", + +] +optional-dependencies.docs = [ + "myst-parser", + "sphinx-autoapi", + "sphinx-pyproject", +] + [tool.ruff] -target-version = "py312" +target-version = "py313" output-format = "full" lint.select = [ @@ -113,6 +130,9 @@ lint.pylint.max-statements = 88 # default: 50 ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" +[tool.pyproject-fmt] +max_supported_python = "3.13" + [tool.pytest.ini_options] markers = [ "mat_ops: mark a test as utilizing matrix operations.", @@ -129,3 +149,87 @@ omit = [ "project_euler/*", ] sort = "Cover" + +[tool.sphinx-pyproject] +copyright = "2014, TheAlgorithms" +autoapi_dirs = [ + "audio_filters", + "backtracking", + "bit_manipulation", + "blockchain", + "boolean_algebra", + "cellular_automata", + "ciphers", + "compression", + "computer_vision", + "conversions", + "data_structures", + "digital_image_processing", + "divide_and_conquer", + "dynamic_programming", + "electronics", + "file_transfer", + "financial", + "fractals", + "fuzzy_logic", + "genetic_algorithm", + "geodesy", + "geometry", + "graphics", + "graphs", + "greedy_methods", + "hashes", + "knapsack", + "linear_algebra", + "linear_programming", + "machine_learning", + "maths", + "matrix", + "networking_flow", + "neural_network", + "other", + "physics", + "project_euler", + "quantum", + "scheduling", + "searches", + "sorts", + "strings", + "web_programming", +] +autoapi_member_order = "groupwise" +# autoapi_python_use_implicit_namespaces = true +exclude_patterns = [ + ".*/*", + "docs/", +] +extensions = [ + "autoapi.extension", + "myst_parser", +] +html_static_path = [ "_static" ] +html_theme = "alabaster" +myst_enable_extensions = [ + "amsmath", + "attrs_inline", + "colon_fence", + "deflist", + "dollarmath", + "fieldlist", + "html_admonition", + "html_image", + # "linkify", + "replacements", + "smartquotes", + "strikethrough", + "substitution", + "tasklist", +] +myst_fence_as_directive = [ + "include", +] +templates_path = [ "_templates" ] +[tool.sphinx-pyproject.source_suffix] +".rst" = "restructuredtext" +# ".txt" = "markdown" +".md" = "markdown" diff --git a/requirements.txt b/requirements.txt index afbf25ba6edc..6754363332c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,7 @@ requests rich # scikit-fuzzy # uncomment once fuzzy_logic/fuzzy_operations.py is fixed scikit-learn +sphinx_pyproject statsmodels sympy tensorflow ; python_version < '3.13' diff --git a/source/__init__.py b/source/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 From 03a42510b01c574292ca9c6525cbf0572ff5a2a5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:42:24 +0200 Subject: [PATCH 2792/2908] [pre-commit.ci] pre-commit autoupdate (#12071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/tox-dev/pyproject-fmt: 2.2.4 → 2.3.0](https://github.com/tox-dev/pyproject-fmt/compare/2.2.4...2.3.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 77541027afb3..e1d185fabc12 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.2.4" + rev: "2.3.0" hooks: - id: pyproject-fmt From 6e24935f8860965dd7f2f5a50fd05724e84e9e8d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:22:34 +0200 Subject: [PATCH 2793/2908] [pre-commit.ci] pre-commit autoupdate (#12234) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.9 → v0.7.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.9...v0.7.0) - [github.com/tox-dev/pyproject-fmt: 2.3.0 → 2.4.3](https://github.com/tox-dev/pyproject-fmt/compare/2.3.0...2.4.3) - [github.com/abravalheri/validate-pyproject: v0.20.2 → v0.21](https://github.com/abravalheri/validate-pyproject/compare/v0.20.2...v0.21) - [github.com/pre-commit/mirrors-mypy: v1.11.2 → v1.12.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.2...v1.12.1) * project_euler/problem_047/sol1.py: def solution(n: int = 4) -> int | None: * Update sol1.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 8 ++++---- project_euler/problem_047/sol1.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e1d185fabc12..a849de0c4e16 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.9 + rev: v0.7.0 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.3.0" + rev: "2.4.3" hooks: - id: pyproject-fmt @@ -42,12 +42,12 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.20.2 + rev: v0.21 hooks: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.12.1 hooks: - id: mypy args: diff --git a/project_euler/problem_047/sol1.py b/project_euler/problem_047/sol1.py index 4ecd4f4b44c1..d174de27dcd0 100644 --- a/project_euler/problem_047/sol1.py +++ b/project_euler/problem_047/sol1.py @@ -24,7 +24,7 @@ def unique_prime_factors(n: int) -> set: """ Find unique prime factors of an integer. - Tests include sorting because only the set really matters, + Tests include sorting because only the set matters, not the order in which it is produced. >>> sorted(set(unique_prime_factors(14))) [2, 7] @@ -58,7 +58,7 @@ def upf_len(num: int) -> int: def equality(iterable: list) -> bool: """ - Check equality of ALL elements in an iterable + Check the equality of ALL elements in an iterable >>> equality([1, 2, 3, 4]) False >>> equality([2, 2, 2, 2]) @@ -69,7 +69,7 @@ def equality(iterable: list) -> bool: return len(set(iterable)) in (0, 1) -def run(n: int) -> list: +def run(n: int) -> list[int]: """ Runs core process to find problem solution. >>> run(3) @@ -77,7 +77,7 @@ def run(n: int) -> list: """ # Incrementor variable for our group list comprehension. - # This serves as the first number in each list of values + # This is the first number in each list of values # to test. base = 2 @@ -85,7 +85,7 @@ def run(n: int) -> list: # Increment each value of a generated range group = [base + i for i in range(n)] - # Run elements through out unique_prime_factors function + # Run elements through the unique_prime_factors function # Append our target number to the end. checker = [upf_len(x) for x in group] checker.append(n) @@ -98,7 +98,7 @@ def run(n: int) -> list: base += 1 -def solution(n: int = 4) -> int: +def solution(n: int = 4) -> int | None: """Return the first value of the first four consecutive integers to have four distinct prime factors each. >>> solution() From 52602ea5b6dd8179aa662c002891c6506f519435 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 21:27:00 +0100 Subject: [PATCH 2794/2908] [pre-commit.ci] pre-commit autoupdate (#12313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.0 → v0.7.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.0...v0.7.1) - [github.com/tox-dev/pyproject-fmt: 2.4.3 → v2.4.3](https://github.com/tox-dev/pyproject-fmt/compare/2.4.3...v2.4.3) - [github.com/abravalheri/validate-pyproject: v0.21 → v0.22](https://github.com/abravalheri/validate-pyproject/compare/v0.21...v0.22) - [github.com/pre-commit/mirrors-mypy: v1.12.1 → v1.13.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.12.1...v1.13.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a849de0c4e16..0828b715106d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.0 + rev: v0.7.1 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.4.3" + rev: "v2.4.3" hooks: - id: pyproject-fmt @@ -42,12 +42,12 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.21 + rev: v0.22 hooks: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.12.1 + rev: v1.13.0 hooks: - id: mypy args: From a19bede190ddb4fa3c1c9850b612a47fc69d6709 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 1 Nov 2024 13:40:09 +0100 Subject: [PATCH 2795/2908] Add scripts/find_git_conflicts.sh (#12343) --- scripts/find_git_conflicts.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100755 scripts/find_git_conflicts.sh diff --git a/scripts/find_git_conflicts.sh b/scripts/find_git_conflicts.sh new file mode 100755 index 000000000000..8af33fa75279 --- /dev/null +++ b/scripts/find_git_conflicts.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Replace with your repository (format: owner/repo) +REPO="TheAlgorithms/Python" + +# Fetch open pull requests with conflicts into a variable +echo "Checking for pull requests with conflicts in $REPO..." + +prs=$(gh pr list --repo "$REPO" --state open --json number,title,mergeable --jq '.[] | select(.mergeable == "CONFLICTING") | {number, title}' --limit 500) + +# Process each conflicting PR +echo "$prs" | jq -c '.[]' | while read -r pr; do + PR_NUMBER=$(echo "$pr" | jq -r '.number') + PR_TITLE=$(echo "$pr" | jq -r '.title') + echo "PR #$PR_NUMBER - $PR_TITLE has conflicts." +done From 3e9ca92ca972bbe752d32b43c71a88789dce94c0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 21:09:03 +0100 Subject: [PATCH 2796/2908] [pre-commit.ci] pre-commit autoupdate (#12349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.1 → v0.7.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.1...v0.7.2) - [github.com/tox-dev/pyproject-fmt: v2.4.3 → v2.5.0](https://github.com/tox-dev/pyproject-fmt/compare/v2.4.3...v2.5.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0828b715106d..f112ee553b51 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.1 + rev: v0.7.2 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.4.3" + rev: "v2.5.0" hooks: - id: pyproject-fmt From e3f3d668be4ada7aee82eea0bc75c50436c1ab3a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:05:50 +0100 Subject: [PATCH 2797/2908] [pre-commit.ci] pre-commit autoupdate (#12370) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.2 → v0.7.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.2...v0.7.3) - [github.com/abravalheri/validate-pyproject: v0.22 → v0.23](https://github.com/abravalheri/validate-pyproject/compare/v0.22...v0.23) * Update sudoku_solver.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- data_structures/arrays/sudoku_solver.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f112ee553b51..9d794473cc01 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.2 + rev: v0.7.3 hooks: - id: ruff - id: ruff-format @@ -42,7 +42,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.22 + rev: v0.23 hooks: - id: validate-pyproject diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index a8157a520c97..70bcdc748195 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -172,7 +172,7 @@ def unitsolved(unit): def from_file(filename, sep="\n"): "Parse a file into a list of strings, separated by sep." - return open(filename).read().strip().split(sep) # noqa: SIM115 + return open(filename).read().strip().split(sep) def random_puzzle(assignments=17): From e3bd7721c8241a6db77254bac44757dced1b96f8 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 15 Nov 2024 14:59:14 +0100 Subject: [PATCH 2798/2908] `validate_filenames.py` Shebang `python` for Windows (#12371) --- 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 0890024dd349..e76b4dbfe288 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!python import os try: From f3f32ae3ca818f64de2ed3267803882956681044 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 22:07:12 +0100 Subject: [PATCH 2799/2908] [pre-commit.ci] pre-commit autoupdate (#12385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.7.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.7.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d794473cc01..6ad19f1fdcb1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.3 + rev: v0.7.4 hooks: - id: ruff - id: ruff-format From fc33c505935e9927cffb6142591891f721a7bcd9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:46:20 +0100 Subject: [PATCH 2800/2908] [pre-commit.ci] pre-commit autoupdate (#12398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.10...v0.5.0) - [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- cellular_automata/conways_game_of_life.py | 6 ++--- ciphers/playfair_cipher.py | 2 +- ciphers/simple_keyword_cypher.py | 2 +- ciphers/transposition_cipher.py | 6 ++--- compression/lempel_ziv.py | 4 ++-- data_structures/arrays/sudoku_solver.py | 2 +- .../binary_tree/binary_tree_traversals.py | 24 ++++++++----------- data_structures/linked_list/deque_doubly.py | 2 +- data_structures/queue/double_ended_queue.py | 2 +- docs/source/__init__.py | 0 electronics/electrical_impedance.py | 2 +- graphs/ant_colony_optimization_algorithms.py | 6 ++--- graphs/basic_graphs.py | 22 ++++++++--------- graphs/minimum_spanning_tree_boruvka.py | 8 +++---- hashes/md5.py | 2 +- machine_learning/frequent_pattern_growth.py | 4 ++-- maths/collatz_sequence.py | 2 +- maths/prime_numbers.py | 6 ++--- maths/volume.py | 2 +- neural_network/input_data.py | 10 ++++---- physics/basic_orbital_capture.py | 9 ++++--- physics/grahams_law.py | 2 +- project_euler/problem_025/sol2.py | 2 +- project_euler/problem_123/sol1.py | 2 +- pyproject.toml | 1 + source/__init__.py | 0 strings/frequency_finder.py | 2 +- strings/min_cost_string_conversion.py | 8 +++---- web_programming/fetch_jobs.py | 2 +- 30 files changed, 66 insertions(+), 78 deletions(-) create mode 100644 docs/source/__init__.py create mode 100644 source/__init__.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ad19f1fdcb1..64d9a833cd21 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.4 + rev: v0.8.0 hooks: - id: ruff - id: ruff-format diff --git a/cellular_automata/conways_game_of_life.py b/cellular_automata/conways_game_of_life.py index 364a34c3aba6..485f0d47bd8b 100644 --- a/cellular_automata/conways_game_of_life.py +++ b/cellular_automata/conways_game_of_life.py @@ -58,10 +58,8 @@ def new_generation(cells: list[list[int]]) -> list[list[int]]: # 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 + if (alive and 2 <= neighbour_count <= 3) or ( + not alive and neighbour_count == 3 ): next_generation_row.append(1) else: diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 86b45bc4fb6a..d48f113f02e0 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -24,7 +24,7 @@ from collections.abc import Generator, Iterable -def chunker(seq: Iterable[str], size: int) -> Generator[tuple[str, ...], None, None]: +def chunker(seq: Iterable[str], size: int) -> Generator[tuple[str, ...]]: it = iter(seq) while True: chunk = tuple(itertools.islice(it, size)) diff --git a/ciphers/simple_keyword_cypher.py b/ciphers/simple_keyword_cypher.py index 1635471aebd1..9dc624e7762c 100644 --- a/ciphers/simple_keyword_cypher.py +++ b/ciphers/simple_keyword_cypher.py @@ -10,7 +10,7 @@ def remove_duplicates(key: str) -> str: key_no_dups = "" for ch in key: - if ch == " " or ch not in key_no_dups and ch.isalpha(): + if ch == " " or (ch not in key_no_dups and ch.isalpha()): key_no_dups += ch return key_no_dups diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index f1f07ddc3f35..76178cb6a1bc 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -52,10 +52,8 @@ def decrypt_message(key: int, message: str) -> str: plain_text[col] += symbol col += 1 - if ( - (col == num_cols) - or (col == num_cols - 1) - and (row >= num_rows - num_shaded_boxes) + if (col == num_cols) or ( + (col == num_cols - 1) and (row >= num_rows - num_shaded_boxes) ): col = 0 row += 1 diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py index 2751a0ebcdb6..648b029471bd 100644 --- a/compression/lempel_ziv.py +++ b/compression/lempel_ziv.py @@ -35,8 +35,8 @@ def add_key_to_lexicon( 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] + for curr_key, value in lexicon.items(): + lexicon[curr_key] = f"0{value}" lexicon[curr_string + "1"] = bin(index)[2:] diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index 70bcdc748195..7e38e1465728 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -156,7 +156,7 @@ def time_solve(grid): times, results = zip(*[time_solve(grid) for grid in grids]) if (n := len(grids)) > 1: print( - "Solved %d of %d %s puzzles (avg %.2f secs (%d Hz), max %.2f secs)." + "Solved %d of %d %s puzzles (avg %.2f secs (%d Hz), max %.2f secs)." # noqa: UP031 % (sum(results), n, name, sum(times) / n, n / sum(times), max(times)) ) diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py index 49c208335b2c..5ba149d0cbc6 100644 --- a/data_structures/binary_tree/binary_tree_traversals.py +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -30,7 +30,7 @@ def make_tree() -> Node | None: return tree -def preorder(root: Node | None) -> Generator[int, None, None]: +def preorder(root: Node | None) -> Generator[int]: """ Pre-order traversal visits root node, left subtree, right subtree. >>> list(preorder(make_tree())) @@ -43,7 +43,7 @@ def preorder(root: Node | None) -> Generator[int, None, None]: yield from preorder(root.right) -def postorder(root: Node | None) -> Generator[int, None, None]: +def postorder(root: Node | None) -> Generator[int]: """ Post-order traversal visits left subtree, right subtree, root node. >>> list(postorder(make_tree())) @@ -56,7 +56,7 @@ def postorder(root: Node | None) -> Generator[int, None, None]: yield root.data -def inorder(root: Node | None) -> Generator[int, None, None]: +def inorder(root: Node | None) -> Generator[int]: """ In-order traversal visits left subtree, root node, right subtree. >>> list(inorder(make_tree())) @@ -69,7 +69,7 @@ def inorder(root: Node | None) -> Generator[int, None, None]: yield from inorder(root.right) -def reverse_inorder(root: Node | None) -> Generator[int, None, None]: +def reverse_inorder(root: Node | None) -> Generator[int]: """ Reverse in-order traversal visits right subtree, root node, left subtree. >>> list(reverse_inorder(make_tree())) @@ -93,7 +93,7 @@ def height(root: Node | None) -> int: return (max(height(root.left), height(root.right)) + 1) if root else 0 -def level_order(root: Node | None) -> Generator[int, None, None]: +def level_order(root: Node | None) -> Generator[int]: """ Returns a list of nodes value from a whole binary tree in Level Order Traverse. Level Order traverse: Visit nodes of the tree level-by-level. @@ -116,9 +116,7 @@ def level_order(root: Node | None) -> Generator[int, None, None]: process_queue.append(node.right) -def get_nodes_from_left_to_right( - root: Node | None, level: int -) -> Generator[int, None, None]: +def get_nodes_from_left_to_right(root: Node | None, level: int) -> Generator[int]: """ Returns a list of nodes value from a particular level: Left to right direction of the binary tree. @@ -128,7 +126,7 @@ def get_nodes_from_left_to_right( [2, 3] """ - def populate_output(root: Node | None, level: int) -> Generator[int, None, None]: + def populate_output(root: Node | None, level: int) -> Generator[int]: if not root: return if level == 1: @@ -140,9 +138,7 @@ def populate_output(root: Node | None, level: int) -> Generator[int, None, None] yield from populate_output(root, level) -def get_nodes_from_right_to_left( - root: Node | None, level: int -) -> Generator[int, None, None]: +def get_nodes_from_right_to_left(root: Node | None, level: int) -> Generator[int]: """ Returns a list of nodes value from a particular level: Right to left direction of the binary tree. @@ -152,7 +148,7 @@ def get_nodes_from_right_to_left( [3, 2] """ - def populate_output(root: Node | None, level: int) -> Generator[int, None, None]: + def populate_output(root: Node | None, level: int) -> Generator[int]: if not root: return if level == 1: @@ -164,7 +160,7 @@ def populate_output(root: Node | None, level: int) -> Generator[int, None, None] yield from populate_output(root, level) -def zigzag(root: Node | None) -> Generator[int, None, None]: +def zigzag(root: Node | None) -> Generator[int]: """ ZigZag traverse: Returns a list of nodes value from left to right and right to left, alternatively. diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index 2b9d70c223c4..e554ead91c5a 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -12,7 +12,7 @@ class _DoublyLinkedBase: """A Private class (to be inherited)""" class _Node: - __slots__ = "_prev", "_data", "_next" + __slots__ = "_data", "_next", "_prev" def __init__(self, link_p, element, link_n): self._prev = link_p diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 607d0bda3df4..c28d46c65168 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -33,7 +33,7 @@ class Deque: the number of nodes """ - __slots__ = ("_front", "_back", "_len") + __slots__ = ("_back", "_front", "_len") @dataclass class _Node: diff --git a/docs/source/__init__.py b/docs/source/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/electronics/electrical_impedance.py b/electronics/electrical_impedance.py index 44041ff790b6..4f4f1d308293 100644 --- a/electronics/electrical_impedance.py +++ b/electronics/electrical_impedance.py @@ -6,7 +6,7 @@ from __future__ import annotations -from math import pow, sqrt +from math import pow, sqrt # noqa: A004 def electrical_impedance( diff --git a/graphs/ant_colony_optimization_algorithms.py b/graphs/ant_colony_optimization_algorithms.py index 13637da44874..753f4c0962c8 100644 --- a/graphs/ant_colony_optimization_algorithms.py +++ b/graphs/ant_colony_optimization_algorithms.py @@ -194,10 +194,8 @@ def city_select( IndexError: list index out of range """ probabilities = [] - for city in unvisited_cities: - city_distance = distance( - unvisited_cities[city], next(iter(current_city.values())) - ) + for city, value in unvisited_cities.items(): + city_distance = distance(value, next(iter(current_city.values()))) probability = (pheromone[city][next(iter(current_city.keys()))] ** alpha) * ( (1 / city_distance) ** beta ) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 25c8045b3d2b..567fa65040ae 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -133,18 +133,18 @@ def dijk(g, s): if len(known) == len(g) - 1: break mini = 100000 - for i in dist: - if i not in known and dist[i] < mini: - mini = dist[i] - u = i + for key, value in dist: + if key not in known and value < mini: + mini = value + u = key known.add(u) for v in g[u]: if v[0] not in known and dist[u] + v[1] < dist.get(v[0], 100000): dist[v[0]] = dist[u] + v[1] path[v[0]] = u - for i in dist: - if i != s: - print(dist[i]) + for key, value in dist.items(): + if key != s: + print(value) """ @@ -255,10 +255,10 @@ def prim(g, s): if len(known) == len(g) - 1: break mini = 100000 - for i in dist: - if i not in known and dist[i] < mini: - mini = dist[i] - u = i + for key, value in dist.items(): + if key not in known and value < mini: + mini = value + u = key known.add(u) for v in g[u]: if v[0] not in known and v[1] < dist.get(v[0], 100000): diff --git a/graphs/minimum_spanning_tree_boruvka.py b/graphs/minimum_spanning_tree_boruvka.py index 3c6888037948..f234d65ab765 100644 --- a/graphs/minimum_spanning_tree_boruvka.py +++ b/graphs/minimum_spanning_tree_boruvka.py @@ -185,12 +185,12 @@ def boruvka_mst(graph): 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] + for head_tail_weight in cheap_edge.values(): + if head_tail_weight != -1: + head, tail, weight = head_tail_weight if union_find.find(head) != union_find.find(tail): union_find.union(head, tail) - mst_edges.append(cheap_edge[vertex]) + mst_edges.append(head_tail_weight) num_components = num_components - 1 mst = Graph.build(edges=mst_edges) return mst diff --git a/hashes/md5.py b/hashes/md5.py index 622a50d290e1..f9d802ff0308 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -131,7 +131,7 @@ def preprocess(message: bytes) -> bytes: return bit_string -def get_block_words(bit_string: bytes) -> Generator[list[int], None, None]: +def get_block_words(bit_string: bytes) -> Generator[list[int]]: """ Splits bit string into blocks of 512 chars and yields each block as a list of 32-bit words diff --git a/machine_learning/frequent_pattern_growth.py b/machine_learning/frequent_pattern_growth.py index 947f8692f298..fae2df16efb1 100644 --- a/machine_learning/frequent_pattern_growth.py +++ b/machine_learning/frequent_pattern_growth.py @@ -107,8 +107,8 @@ def create_tree(data_set: list, min_sup: int = 1) -> tuple[TreeNode, dict]: if not (freq_item_set := set(header_table)): return TreeNode("Null Set", 1, None), {} - for k in header_table: - header_table[k] = [header_table[k], None] + for key, value in header_table.items(): + header_table[key] = [value, None] fp_tree = TreeNode("Null Set", 1, None) # Parent is None for the root node for tran_set in data_set: diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py index b47017146a1e..b00dca8d70b7 100644 --- a/maths/collatz_sequence.py +++ b/maths/collatz_sequence.py @@ -17,7 +17,7 @@ from collections.abc import Generator -def collatz_sequence(n: int) -> Generator[int, None, None]: +def collatz_sequence(n: int) -> Generator[int]: """ Generate the Collatz sequence starting at n. >>> tuple(collatz_sequence(2.1)) diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index 38cc6670385d..5ad12baf3dc3 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -2,7 +2,7 @@ from collections.abc import Generator -def slow_primes(max_n: int) -> Generator[int, None, None]: +def slow_primes(max_n: int) -> Generator[int]: """ Return a list of all primes numbers up to max. >>> list(slow_primes(0)) @@ -29,7 +29,7 @@ def slow_primes(max_n: int) -> Generator[int, None, None]: yield i -def primes(max_n: int) -> Generator[int, None, None]: +def primes(max_n: int) -> Generator[int]: """ Return a list of all primes numbers up to max. >>> list(primes(0)) @@ -58,7 +58,7 @@ def primes(max_n: int) -> Generator[int, None, None]: yield i -def fast_primes(max_n: int) -> Generator[int, None, None]: +def fast_primes(max_n: int) -> Generator[int]: """ Return a list of all primes numbers up to max. >>> list(fast_primes(0)) diff --git a/maths/volume.py b/maths/volume.py index 33be9bdd131a..23fcf6be6ef1 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -6,7 +6,7 @@ from __future__ import annotations -from math import pi, pow +from math import pi, pow # noqa: A004 def vol_cube(side_length: float) -> float: diff --git a/neural_network/input_data.py b/neural_network/input_data.py index f90287fe3f5b..72debabb566a 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -61,9 +61,8 @@ def _extract_images(f): 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) - ) + msg = f"Invalid magic number {magic} in MNIST image file: {f.name}" + raise ValueError(msg) num_images = _read32(bytestream) rows = _read32(bytestream) cols = _read32(bytestream) @@ -102,9 +101,8 @@ def _extract_labels(f, one_hot=False, num_classes=10): 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) - ) + msg = f"Invalid magic number {magic} in MNIST label file: {f.name}" + raise ValueError(msg) num_items = _read32(bytestream) buf = bytestream.read(num_items) labels = np.frombuffer(buf, dtype=np.uint8) diff --git a/physics/basic_orbital_capture.py b/physics/basic_orbital_capture.py index a5434b5cb7cb..eb1fdd9d6420 100644 --- a/physics/basic_orbital_capture.py +++ b/physics/basic_orbital_capture.py @@ -1,7 +1,3 @@ -from math import pow, sqrt - -from scipy.constants import G, c, pi - """ These two functions will return the radii of impact for a target object of mass M and radius R as well as it's effective cross sectional area sigma. @@ -14,9 +10,12 @@ cross section for capture as sigma=π*R_capture**2. This algorithm does not account for an N-body problem. - """ +from math import pow, sqrt # noqa: A004 + +from scipy.constants import G, c, pi + def capture_radii( target_body_radius: float, target_body_mass: float, projectile_velocity: float diff --git a/physics/grahams_law.py b/physics/grahams_law.py index 6e5d75127e83..c56359280ea4 100644 --- a/physics/grahams_law.py +++ b/physics/grahams_law.py @@ -14,7 +14,7 @@ (Description adapted from https://en.wikipedia.org/wiki/Graham%27s_law) """ -from math import pow, sqrt +from math import pow, sqrt # noqa: A004 def validate(*values: float) -> bool: diff --git a/project_euler/problem_025/sol2.py b/project_euler/problem_025/sol2.py index a0f056023bc9..4094b6251d50 100644 --- a/project_euler/problem_025/sol2.py +++ b/project_euler/problem_025/sol2.py @@ -27,7 +27,7 @@ from collections.abc import Generator -def fibonacci_generator() -> Generator[int, None, None]: +def fibonacci_generator() -> Generator[int]: """ A generator that produces numbers in the Fibonacci sequence diff --git a/project_euler/problem_123/sol1.py b/project_euler/problem_123/sol1.py index 3dd31a2e8505..265348d2d4c8 100644 --- a/project_euler/problem_123/sol1.py +++ b/project_euler/problem_123/sol1.py @@ -43,7 +43,7 @@ from collections.abc import Generator -def sieve() -> Generator[int, None, None]: +def sieve() -> Generator[int]: """ Returns a prime number generator using sieve method. >>> type(sieve()) diff --git a/pyproject.toml b/pyproject.toml index c57419e79db3..c60ec246144e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,7 @@ lint.ignore = [ "EM101", # Exception must not use a string literal, assign to variable first "EXE001", # Shebang is present but file is not executable -- DO NOT FIX "G004", # Logging statement uses f-string + "ISC001", # Conflicts with ruff format -- DO NOT FIX "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX "PLW2901", # PLW2901: Redefined loop variable -- FIX ME diff --git a/source/__init__.py b/source/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/strings/frequency_finder.py b/strings/frequency_finder.py index 8479c81ae464..e5afee891bd9 100644 --- a/strings/frequency_finder.py +++ b/strings/frequency_finder.py @@ -67,7 +67,7 @@ def get_frequency_order(message: str) -> str: freq_to_letter_str: dict[int, str] = {} - for freq in freq_to_letter: + for freq in freq_to_letter: # noqa: PLC0206 freq_to_letter[freq].sort(key=ETAOIN.find, reverse=True) freq_to_letter_str[freq] = "".join(freq_to_letter[freq]) diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index a5a3c4a4e3f8..93791e2a7ed3 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -124,7 +124,7 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: print("".join(string)) if op[0] == "C": - file.write("%-16s" % "Copy %c" % op[1]) + file.write("%-16s" % "Copy %c" % op[1]) # noqa: UP031 file.write("\t\t\t" + "".join(string)) file.write("\r\n") @@ -132,7 +132,7 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: elif op[0] == "R": string[i] = op[2] - file.write("%-16s" % ("Replace %c" % op[1] + " with " + str(op[2]))) + file.write("%-16s" % ("Replace %c" % op[1] + " with " + str(op[2]))) # noqa: UP031 file.write("\t\t" + "".join(string)) file.write("\r\n") @@ -140,7 +140,7 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: elif op[0] == "D": string.pop(i) - file.write("%-16s" % "Delete %c" % op[1]) + file.write("%-16s" % "Delete %c" % op[1]) # noqa: UP031 file.write("\t\t\t" + "".join(string)) file.write("\r\n") @@ -148,7 +148,7 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: else: string.insert(i, op[1]) - file.write("%-16s" % "Insert %c" % op[1]) + file.write("%-16s" % "Insert %c" % op[1]) # noqa: UP031 file.write("\t\t\t" + "".join(string)) file.write("\r\n") diff --git a/web_programming/fetch_jobs.py b/web_programming/fetch_jobs.py index 0d89bf45de57..3753d25bbe5f 100644 --- a/web_programming/fetch_jobs.py +++ b/web_programming/fetch_jobs.py @@ -12,7 +12,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]]: soup = BeautifulSoup( requests.get(url + location, timeout=10).content, "html.parser" ) From c7921226326f35932bbc9d214e9742c2f3d310bf Mon Sep 17 00:00:00 2001 From: Anamaria Miranda Date: Mon, 2 Dec 2024 11:57:04 +0100 Subject: [PATCH 2801/2908] Added matrix based color game algorithm (#12400) * Added matrix based color game * updating DIRECTORY.md --------- Co-authored-by: Miranda13 --- DIRECTORY.md | 1 + matrix/matrix_based_game.py | 284 ++++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 matrix/matrix_based_game.py diff --git a/DIRECTORY.md b/DIRECTORY.md index f0a34a553946..d234d366df06 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -794,6 +794,7 @@ * [Cramers Rule 2X2](matrix/cramers_rule_2x2.py) * [Inverse Of Matrix](matrix/inverse_of_matrix.py) * [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py) + * [Matrix Based Game](matrix/matrix_based_game.py) * [Matrix Class](matrix/matrix_class.py) * [Matrix Equalization](matrix/matrix_equalization.py) * [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py) diff --git a/matrix/matrix_based_game.py b/matrix/matrix_based_game.py new file mode 100644 index 000000000000..1ff0cbe93435 --- /dev/null +++ b/matrix/matrix_based_game.py @@ -0,0 +1,284 @@ +""" +Matrix-Based Game Script +========================= +This script implements a matrix-based game where players interact with a grid of +elements. The primary goals are to: +- Identify connected elements of the same type from a selected position. +- Remove those elements, adjust the matrix by simulating gravity, and reorganize empty + columns. +- Calculate and display the score based on the number of elements removed in each move. + +Functions: +----------- +1. `find_repeat`: Finds all connected elements of the same type. +2. `increment_score`: Calculates the score for a given move. +3. `move_x`: Simulates gravity in a column. +4. `move_y`: Reorganizes the matrix by shifting columns leftward when a column becomes + empty. +5. `play`: Executes a single move, updating the matrix and returning the score. + +Input Format: +-------------- +1. Matrix size (`lines`): Integer specifying the size of the matrix (N x N). +2. Matrix content (`matrix`): Rows of the matrix, each consisting of characters. +3. Number of moves (`movs`): Integer indicating the number of moves. +4. List of moves (`movements`): A comma-separated string of coordinates for each move. + +(0,0) position starts from first left column to last right, and below row to up row + + +Example Input: +--------------- +4 +RRBG +RBBG +YYGG +XYGG +2 +0 1,1 1 + +Example (0,0) = X + +Output: +-------- +The script outputs the total score after processing all moves. + +Usage: +------- +Run the script and provide the required inputs as prompted. + +""" + + +def validate_matrix_size(size: int) -> None: + """ + >>> validate_matrix_size(-1) + Traceback (most recent call last): + ... + ValueError: Matrix size must be a positive integer. + """ + if not isinstance(size, int) or size <= 0: + raise ValueError("Matrix size must be a positive integer.") + + +def validate_matrix_content(matrix: list[str], size: int) -> None: + """ + Validates that the number of elements in the matrix matches the given size. + + >>> validate_matrix_content(['aaaa', 'aaaa', 'aaaa', 'aaaa'], 3) + Traceback (most recent call last): + ... + ValueError: The matrix dont match with size. + >>> validate_matrix_content(['aa%', 'aaa', 'aaa'], 3) + Traceback (most recent call last): + ... + ValueError: Matrix rows can only contain letters and numbers. + >>> validate_matrix_content(['aaa', 'aaa', 'aaaa'], 3) + Traceback (most recent call last): + ... + ValueError: Each row in the matrix must have exactly 3 characters. + """ + print(matrix) + if len(matrix) != size: + raise ValueError("The matrix dont match with size.") + for row in matrix: + if len(row) != size: + msg = f"Each row in the matrix must have exactly {size} characters." + raise ValueError(msg) + if not all(char.isalnum() for char in row): + raise ValueError("Matrix rows can only contain letters and numbers.") + + +def validate_moves(moves: list[tuple[int, int]], size: int) -> None: + """ + >>> validate_moves([(1, 2), (-1, 0)], 3) + Traceback (most recent call last): + ... + ValueError: Move is out of bounds for a matrix. + """ + for move in moves: + x, y = move + if not (0 <= x < size and 0 <= y < size): + raise ValueError("Move is out of bounds for a matrix.") + + +def parse_moves(input_str: str) -> list[tuple[int, int]]: + """ + >>> parse_moves("0 1, 1 1") + [(0, 1), (1, 1)] + >>> parse_moves("0 1, 1 1, 2") + Traceback (most recent call last): + ... + ValueError: Each move must have exactly two numbers. + >>> parse_moves("0 1, 1 1, 2 4 5 6") + Traceback (most recent call last): + ... + ValueError: Each move must have exactly two numbers. + """ + moves = [] + for pair in input_str.split(","): + parts = pair.strip().split() + if len(parts) != 2: + raise ValueError("Each move must have exactly two numbers.") + x, y = map(int, parts) + moves.append((x, y)) + return moves + + +def find_repeat( + matrix_g: list[list[str]], row: int, column: int, size: int +) -> set[tuple[int, int]]: + """ + Finds all connected elements of the same type from a given position. + + >>> find_repeat([['A', 'B', 'A'], ['A', 'B', 'A'], ['A', 'A', 'A']], 0, 0, 3) + {(1, 2), (2, 1), (0, 0), (2, 0), (0, 2), (2, 2), (1, 0)} + >>> find_repeat([['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-']], 1, 1, 3) + set() + """ + + column = size - 1 - column + visited = set() + repeated = set() + + if (color := matrix_g[column][row]) != "-": + + def dfs(row_n: int, column_n: int) -> None: + if row_n < 0 or row_n >= size or column_n < 0 or column_n >= size: + return + if (row_n, column_n) in visited: + return + visited.add((row_n, column_n)) + if matrix_g[row_n][column_n] == color: + repeated.add((row_n, column_n)) + dfs(row_n - 1, column_n) + dfs(row_n + 1, column_n) + dfs(row_n, column_n - 1) + dfs(row_n, column_n + 1) + + dfs(column, row) + + return repeated + + +def increment_score(count: int) -> int: + """ + Calculates the score for a move based on the number of elements removed. + + >>> increment_score(3) + 6 + >>> increment_score(0) + 0 + """ + return int(count * (count + 1) / 2) + + +def move_x(matrix_g: list[list[str]], column: int, size: int) -> list[list[str]]: + """ + Simulates gravity in a specific column. + + >>> move_x([['-', 'A'], ['-', '-'], ['-', 'C']], 1, 2) + [['-', '-'], ['-', 'A'], ['-', 'C']] + """ + + new_list = [] + + for row in range(size): + if matrix_g[row][column] != "-": + new_list.append(matrix_g[row][column]) + else: + new_list.insert(0, matrix_g[row][column]) + for row in range(size): + matrix_g[row][column] = new_list[row] + return matrix_g + + +def move_y(matrix_g: list[list[str]], size: int) -> list[list[str]]: + """ + Shifts all columns leftward when an entire column becomes empty. + + >>> move_y([['-', 'A'], ['-', '-'], ['-', 'C']], 2) + [['A', '-'], ['-', '-'], ['-', 'C']] + """ + + empty_columns = [] + + for column in range(size - 1, -1, -1): + if all(matrix_g[row][column] == "-" for row in range(size)): + empty_columns.append(column) + + for column in empty_columns: + for col in range(column + 1, size): + for row in range(size): + matrix_g[row][col - 1] = matrix_g[row][col] + for row in range(size): + matrix_g[row][-1] = "-" + + return matrix_g + + +def play( + matrix_g: list[list[str]], pos_x: int, pos_y: int, size: int +) -> tuple[list[list[str]], int]: + """ + Processes a single move, updating the matrix and calculating the score. + + >>> play([['R', 'G'], ['R', 'G']], 0, 0, 2) + ([['G', '-'], ['G', '-']], 3) + """ + + same_colors = find_repeat(matrix_g, pos_x, pos_y, size) + + if len(same_colors) != 0: + for pos in same_colors: + matrix_g[pos[0]][pos[1]] = "-" + for column in range(size): + matrix_g = move_x(matrix_g, column, size) + + matrix_g = move_y(matrix_g, size) + + return (matrix_g, increment_score(len(same_colors))) + + +def process_game(size: int, matrix: list[str], moves: list[tuple[int, int]]) -> int: + """Processes the game logic for the given matrix and moves. + + Args: + size (int): Size of the game board. + matrix (List[str]): Initial game matrix. + moves (List[Tuple[int, int]]): List of moves as (x, y) coordinates. + + Returns: + int: The total score obtained. + >>> process_game(3, ['aaa', 'bbb', 'ccc'], [(0, 0)]) + 6 + """ + + game_matrix = [list(row) for row in matrix] + total_score = 0 + + for move in moves: + pos_x, pos_y = move + game_matrix, score = play(game_matrix, pos_x, pos_y, size) + total_score += score + + return total_score + + +if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) + try: + size = int(input("Enter the size of the matrix: ")) + validate_matrix_size(size) + print(f"Enter the {size} rows of the matrix:") + matrix = [input(f"Row {i+1}: ") for i in range(size)] + validate_matrix_content(matrix, size) + moves_input = input("Enter the moves (e.g., '0 0, 1 1'): ") + moves = parse_moves(moves_input) + validate_moves(moves, size) + score = process_game(size, matrix, moves) + print(f"Total score: {score}") + except ValueError as e: + print(f"{e}") From b22fab0ea46c7b625d8137d1fb07d082e20d6d7b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:35:21 +0100 Subject: [PATCH 2802/2908] [pre-commit.ci] pre-commit autoupdate (#12404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.0 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.0...v0.8.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64d9a833cd21..bef251749c19 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.0 + rev: v0.8.1 hooks: - id: ruff - id: ruff-format From 0bcdfbdb34e03e24e2f5da90a7236226b721981d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 5 Dec 2024 05:34:48 +0100 Subject: [PATCH 2803/2908] Use Astral uv (#12402) * Use Astral uv * uvx vs uv run * uv sync --group=euler-validate,test * uv sync --group=euler-validate --group=test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * --group=test --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 15 +- .github/workflows/project_euler.yml | 16 +- .github/workflows/ruff.yml | 4 +- .github/workflows/sphinx.yml | 6 +- pyproject.toml | 48 +- requirements.txt | 6 - uv.lock | 1246 +++++++++++++++++++++++++++ 7 files changed, 1301 insertions(+), 40 deletions(-) create mode 100644 uv.lock diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5703e2f1ab6..a6f308715cc2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,21 +10,18 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + cache-dependency-glob: uv.lock - uses: actions/setup-python@v5 with: python-version: 3.13 allow-prereleases: true - - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools wheel - python -m pip install pytest-cov -r requirements.txt + - run: uv sync --group=test - name: Run tests # TODO: #8818 Re-enable quantum tests - run: pytest + run: uv run pytest --ignore=computer_vision/cnn_classification.py --ignore=docs/conf.py --ignore=dynamic_programming/k_means_clustering_tensorflow.py diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml index 59e1208a650d..84c55335451e 100644 --- a/.github/workflows/project_euler.yml +++ b/.github/workflows/project_euler.yml @@ -15,25 +15,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v4 - uses: actions/setup-python@v5 with: python-version: 3.x - - name: Install pytest and pytest-cov - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade numpy pytest pytest-cov - - run: pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ + - run: uv sync --group=euler-validate --group=test + - run: uv run pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ validate-solutions: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v4 - uses: actions/setup-python@v5 with: python-version: 3.x - - name: Install pytest and requests - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade numpy pytest requests - - run: pytest scripts/validate_solutions.py + - run: uv sync --group=euler-validate --group=test + - run: uv run pytest scripts/validate_solutions.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index d354eba672ae..2c6f92fcf7bf 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -12,5 +12,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: pip install --user ruff - - run: ruff check --output-format=github . + - uses: astral-sh/setup-uv@v4 + - run: uvx ruff check --output-format=github . diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index 9dfe344f9743..e3e2ce81a95d 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -26,14 +26,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v4 - uses: actions/setup-python@v5 with: python-version: 3.13 allow-prereleases: true - - run: pip install --upgrade pip - - run: pip install myst-parser sphinx-autoapi sphinx-pyproject + - run: uv sync --group=docs - uses: actions/configure-pages@v5 - - run: sphinx-build -c docs . docs/_build/html + - run: uv run sphinx-build -c docs . docs/_build/html - uses: actions/upload-pages-artifact@v3 with: path: docs/_build/html diff --git a/pyproject.toml b/pyproject.toml index c60ec246144e..7b7176705c44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,12 +7,43 @@ requires-python = ">=3.13" classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.13", +] +dependencies = [ + "beautifulsoup4>=4.12.3", + "fake-useragent>=1.5.1", + "imageio>=2.36.1", + "keras>=3.7", + "lxml>=5.3", + "matplotlib>=3.9.3", + "numpy>=2.1.3", + "opencv-python>=4.10.0.84", + "pandas>=2.2.3", + "pillow>=11", + "requests>=2.32.3", + "rich>=13.9.4", + "scikit-learn>=1.5.2", + "sphinx-pyproject>=0.3", + "statsmodels>=0.14.4", + "sympy>=1.13.3", + "tweepy>=4.14", + "typing-extensions>=4.12.2", + "xgboost>=2.1.3", +] +[dependency-groups] +test = [ + "pytest>=8.3.4", + "pytest-cov>=6", ] -optional-dependencies.docs = [ - "myst-parser", - "sphinx-autoapi", - "sphinx-pyproject", + +docs = [ + "myst-parser>=4", + "sphinx-autoapi>=3.4", + "sphinx-pyproject>=0.3", +] +euler-validate = [ + "numpy>=2.1.3", + "requests>=2.32.3", ] [tool.ruff] @@ -61,8 +92,8 @@ lint.select = [ "UP", # pyupgrade "W", # pycodestyle "YTT", # flake8-2020 - # "ANN", # flake8-annotations # FIX ME? - # "COM", # flake8-commas + # "ANN", # flake8-annotations -- FIX ME? + # "COM", # flake8-commas -- DO NOT FIX # "D", # pydocstyle -- FIX ME? # "ERA", # eradicate -- DO NOT FIX # "FBT", # flake8-boolean-trap # FIX ME @@ -129,10 +160,7 @@ lint.pylint.max-statements = 88 # default: 50 [tool.codespell] ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" -skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" - -[tool.pyproject-fmt] -max_supported_python = "3.13" +skip = "./.*,*.json,*.lock,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" [tool.pytest.ini_options] markers = [ diff --git a/requirements.txt b/requirements.txt index 6754363332c4..4cc83f44987d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,18 +8,12 @@ numpy opencv-python pandas pillow -# projectq # uncomment once quantum/quantum_random.py is fixed -qiskit ; python_version < '3.12' -qiskit-aer ; python_version < '3.12' requests rich -# scikit-fuzzy # uncomment once fuzzy_logic/fuzzy_operations.py is fixed scikit-learn sphinx_pyproject statsmodels sympy -tensorflow ; python_version < '3.13' tweepy -# yulewalker # uncomment once audio_filters/equal_loudness_filter.py is fixed typing_extensions xgboost diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000000..077288f041a1 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1246 @@ +version = 1 +requires-python = ">=3.13" +resolution-markers = [ + "platform_system == 'Darwin'", + "platform_machine == 'aarch64' and platform_system == 'Linux'", + "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')", +] + +[[package]] +name = "absl-py" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706 }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 }, +] + +[[package]] +name = "astroid" +version = "3.3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/1e/326fb1d3d83a3bb77c9f9be29d31f2901e35acb94b0605c3f2e5085047f9/astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d", size = 397229 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/30/624365383fa4a40329c0f0bbbc151abc4a64e30dfc110fc8f6e2afcd02bb/astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8", size = 274586 }, +] + +[[package]] +name = "babel" +version = "2.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, +] + +[[package]] +name = "codespell" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/a9/98353dfc7afcdf18cffd2dd3e959a25eaaf2728cf450caa59af89648a8e4/codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f", size = 329791 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/20/b6019add11e84f821184234cea0ad91442373489ef7ccfa3d73a71b908fa/codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1", size = 329167 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "contourpy" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/c2/fc7193cc5383637ff390a712e88e4ded0452c9fbcf84abe3de5ea3df1866/contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699", size = 13465753 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/e7/de62050dce687c5e96f946a93546910bc67e483fe05324439e329ff36105/contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2", size = 271548 }, + { url = "https://files.pythonhosted.org/packages/78/4d/c2a09ae014ae984c6bdd29c11e74d3121b25eaa117eca0bb76340efd7e1c/contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5", size = 255576 }, + { url = "https://files.pythonhosted.org/packages/ab/8a/915380ee96a5638bda80cd061ccb8e666bfdccea38d5741cb69e6dbd61fc/contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81", size = 306635 }, + { url = "https://files.pythonhosted.org/packages/29/5c/c83ce09375428298acd4e6582aeb68b1e0d1447f877fa993d9bf6cd3b0a0/contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2", size = 345925 }, + { url = "https://files.pythonhosted.org/packages/29/63/5b52f4a15e80c66c8078a641a3bfacd6e07106835682454647aca1afc852/contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7", size = 318000 }, + { url = "https://files.pythonhosted.org/packages/9a/e2/30ca086c692691129849198659bf0556d72a757fe2769eb9620a27169296/contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c", size = 322689 }, + { url = "https://files.pythonhosted.org/packages/6b/77/f37812ef700f1f185d348394debf33f22d531e714cf6a35d13d68a7003c7/contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3", size = 1268413 }, + { url = "https://files.pythonhosted.org/packages/3f/6d/ce84e79cdd128542ebeb268f84abb4b093af78e7f8ec504676673d2675bc/contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1", size = 1326530 }, + { url = "https://files.pythonhosted.org/packages/72/22/8282f4eae20c73c89bee7a82a19c4e27af9b57bb602ecaa00713d5bdb54d/contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82", size = 175315 }, + { url = "https://files.pythonhosted.org/packages/e3/d5/28bca491f65312b438fbf076589dcde7f6f966b196d900777f5811b9c4e2/contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd", size = 220987 }, + { url = "https://files.pythonhosted.org/packages/2f/24/a4b285d6adaaf9746e4700932f579f1a7b6f9681109f694cfa233ae75c4e/contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30", size = 285001 }, + { url = "https://files.pythonhosted.org/packages/48/1d/fb49a401b5ca4f06ccf467cd6c4f1fd65767e63c21322b29b04ec40b40b9/contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751", size = 268553 }, + { url = "https://files.pythonhosted.org/packages/79/1e/4aef9470d13fd029087388fae750dccb49a50c012a6c8d1d634295caa644/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342", size = 310386 }, + { url = "https://files.pythonhosted.org/packages/b0/34/910dc706ed70153b60392b5305c708c9810d425bde12499c9184a1100888/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c", size = 349806 }, + { url = "https://files.pythonhosted.org/packages/31/3c/faee6a40d66d7f2a87f7102236bf4780c57990dd7f98e5ff29881b1b1344/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f", size = 321108 }, + { url = "https://files.pythonhosted.org/packages/17/69/390dc9b20dd4bb20585651d7316cc3054b7d4a7b4f8b710b2b698e08968d/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda", size = 327291 }, + { url = "https://files.pythonhosted.org/packages/ef/74/7030b67c4e941fe1e5424a3d988080e83568030ce0355f7c9fc556455b01/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242", size = 1263752 }, + { url = "https://files.pythonhosted.org/packages/f0/ed/92d86f183a8615f13f6b9cbfc5d4298a509d6ce433432e21da838b4b63f4/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1", size = 1318403 }, + { url = "https://files.pythonhosted.org/packages/b3/0e/c8e4950c77dcfc897c71d61e56690a0a9df39543d2164040301b5df8e67b/contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1", size = 185117 }, + { url = "https://files.pythonhosted.org/packages/c1/31/1ae946f11dfbd229222e6d6ad8e7bd1891d3d48bde5fbf7a0beb9491f8e3/contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546", size = 236668 }, +] + +[[package]] +name = "coverage" +version = "7.6.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/75/aecfd0a3adbec6e45753976bc2a9fed62b42cea9a206d10fd29244a77953/coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", size = 801425 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/84/6f0ccf94a098ac3d6d6f236bd3905eeac049a9e0efcd9a63d4feca37ac4b/coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", size = 207313 }, + { url = "https://files.pythonhosted.org/packages/db/2b/e3b3a3a12ebec738c545897ac9f314620470fcbc368cdac88cf14974ba20/coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", size = 207574 }, + { url = "https://files.pythonhosted.org/packages/db/c0/5bf95d42b6a8d21dfce5025ce187f15db57d6460a59b67a95fe8728162f1/coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", size = 240090 }, + { url = "https://files.pythonhosted.org/packages/57/b8/d6fd17d1a8e2b0e1a4e8b9cb1f0f261afd422570735899759c0584236916/coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", size = 237237 }, + { url = "https://files.pythonhosted.org/packages/d4/e4/a91e9bb46809c8b63e68fc5db5c4d567d3423b6691d049a4f950e38fbe9d/coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", size = 239225 }, + { url = "https://files.pythonhosted.org/packages/31/9c/9b99b0591ec4555b7292d271e005f27b465388ce166056c435b288db6a69/coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", size = 238888 }, + { url = "https://files.pythonhosted.org/packages/a6/85/285c2df9a04bc7c31f21fd9d4a24d19e040ec5e2ff06e572af1f6514c9e7/coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", size = 236974 }, + { url = "https://files.pythonhosted.org/packages/cb/a1/95ec8522206f76cdca033bf8bb61fff56429fb414835fc4d34651dfd29fc/coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", size = 238815 }, + { url = "https://files.pythonhosted.org/packages/8d/ac/687e9ba5e6d0979e9dab5c02e01c4f24ac58260ef82d88d3b433b3f84f1e/coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", size = 209957 }, + { url = "https://files.pythonhosted.org/packages/2f/a3/b61cc8e3fcf075293fb0f3dee405748453c5ba28ac02ceb4a87f52bdb105/coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", size = 210711 }, + { url = "https://files.pythonhosted.org/packages/ee/4b/891c8b9acf1b62c85e4a71dac142ab9284e8347409b7355de02e3f38306f/coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", size = 208053 }, + { url = "https://files.pythonhosted.org/packages/18/a9/9e330409b291cc002723d339346452800e78df1ce50774ca439ade1d374f/coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", size = 208329 }, + { url = "https://files.pythonhosted.org/packages/9c/0d/33635fd429f6589c6e1cdfc7bf581aefe4c1792fbff06383f9d37f59db60/coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", size = 251052 }, + { url = "https://files.pythonhosted.org/packages/23/32/8a08da0e46f3830bbb9a5b40614241b2e700f27a9c2889f53122486443ed/coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", size = 246765 }, + { url = "https://files.pythonhosted.org/packages/56/3f/3b86303d2c14350fdb1c6c4dbf9bc76000af2382f42ca1d4d99c6317666e/coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", size = 249125 }, + { url = "https://files.pythonhosted.org/packages/36/cb/c4f081b9023f9fd8646dbc4ef77be0df090263e8f66f4ea47681e0dc2cff/coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", size = 248615 }, + { url = "https://files.pythonhosted.org/packages/32/ee/53bdbf67760928c44b57b2c28a8c0a4bf544f85a9ee129a63ba5c78fdee4/coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", size = 246507 }, + { url = "https://files.pythonhosted.org/packages/57/49/5a57910bd0af6d8e802b4ca65292576d19b54b49f81577fd898505dee075/coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", size = 247785 }, + { url = "https://files.pythonhosted.org/packages/bd/37/e450c9f6b297c79bb9858407396ed3e084dcc22990dd110ab01d5ceb9770/coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", size = 210605 }, + { url = "https://files.pythonhosted.org/packages/44/79/7d0c7dd237c6905018e2936cd1055fe1d42e7eba2ebab3c00f4aad2a27d7/coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", size = 211777 }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, +] + +[[package]] +name = "dom-toml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "domdf-python-tools" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/34/f7690cf288eaa86b55c8f1b890d0834e6df44a026a88eca12274fcd624ab/dom_toml-2.0.0.tar.gz", hash = "sha256:3c07e8436538994974127b1ae037661d1a779ac915c44fd06b3ab5fe140ff589", size = 11133 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/99/b6fc87dff3138491d81676bdcbf1531080925ba41486ec1dafd86e33fdbc/dom_toml-2.0.0-py3-none-any.whl", hash = "sha256:0b6d02a72bcbc6be8175c61afc30623bbb6b74c4650f2a806fbc3fb7fe86935d", size = 13376 }, +] + +[[package]] +name = "domdf-python-tools" +version = "3.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "natsort" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/78/974e10c583ba9d2302e748c9585313a7f2c7ba00e4f600324f432e38fe68/domdf_python_tools-3.9.0.tar.gz", hash = "sha256:1f8a96971178333a55e083e35610d7688cd7620ad2b99790164e1fc1a3614c18", size = 103792 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/e9/7447a88b217650a74927d3444a89507986479a69b83741900eddd34167fe/domdf_python_tools-3.9.0-py3-none-any.whl", hash = "sha256:4e1ef365cbc24627d6d1e90cf7d46d8ab8df967e1237f4a26885f6986c78872e", size = 127106 }, +] + +[[package]] +name = "fake-useragent" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/a1/1f662631ab153975fa8dbf09296324ecbaf53370dce922054e8de6b57370/fake-useragent-1.5.1.tar.gz", hash = "sha256:6387269f5a2196b5ba7ed8935852f75486845a1c95c50e72460e6a8e762f5c49", size = 22631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/99/60d8cf1b26938c2e0a57e232f7f15641dfcd6f8deda454d73e4145910ff6/fake_useragent-1.5.1-py3-none-any.whl", hash = "sha256:57415096557c8a4e23b62a375c21c55af5fd4ba30549227f562d2c4f5b60e3b3", size = 17190 }, +] + +[[package]] +name = "fonttools" +version = "4.55.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4e/053fe1b5c0ce346c0a9d0557492c654362bafb14f026eae0d3ee98009152/fonttools-4.55.0.tar.gz", hash = "sha256:7636acc6ab733572d5e7eec922b254ead611f1cdad17be3f0be7418e8bfaca71", size = 3490431 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/87/a669ac26c6077e37ffb06abf29c5571789eefe518d06c52df392181ee694/fonttools-4.55.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8118dc571921dc9e4b288d9cb423ceaf886d195a2e5329cc427df82bba872cd9", size = 2752519 }, + { url = "https://files.pythonhosted.org/packages/0c/e9/4822ad238fe215133c7df20f1cdb1a58cfb634a31523e77ff0fb2033970a/fonttools-4.55.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01124f2ca6c29fad4132d930da69158d3f49b2350e4a779e1efbe0e82bd63f6c", size = 2286819 }, + { url = "https://files.pythonhosted.org/packages/3e/a4/d7941c3897129e60fe68d20e4819fda4d0c4858d77badae0e80ca6440b36/fonttools-4.55.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ffd58d2691f11f7c8438796e9f21c374828805d33e83ff4b76e4635633674c", size = 4770382 }, + { url = "https://files.pythonhosted.org/packages/31/cf/c51ea1348f9fba9c627439afad9dee0090040809ab431f4422b5bfdda34c/fonttools-4.55.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5435e5f1eb893c35c2bc2b9cd3c9596b0fcb0a59e7a14121562986dd4c47b8dd", size = 4858336 }, + { url = "https://files.pythonhosted.org/packages/73/be/36c1fe0e5c9a96b068ddd7e82001243bbe7fe12549c8d14e1bd025bf40c9/fonttools-4.55.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d12081729280c39d001edd0f4f06d696014c26e6e9a0a55488fabc37c28945e4", size = 4756072 }, + { url = "https://files.pythonhosted.org/packages/5c/18/6dd381c29f215a017f79aa9fea0722424a0046b47991c4390a78ff87ce0c/fonttools-4.55.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7ad1f1b98ab6cb927ab924a38a8649f1ffd7525c75fe5b594f5dab17af70e18", size = 5008668 }, + { url = "https://files.pythonhosted.org/packages/b8/95/316f20092b389b927dba1d1dccd3f541853f96e707e210f1b9f4e7bacdd5/fonttools-4.55.0-cp313-cp313-win32.whl", hash = "sha256:abe62987c37630dca69a104266277216de1023cf570c1643bb3a19a9509e7a1b", size = 2155841 }, + { url = "https://files.pythonhosted.org/packages/35/ca/b4638aa3e446184892e2f9cc8ef44bb506f47fea04580df7fb84f5a4363d/fonttools-4.55.0-cp313-cp313-win_amd64.whl", hash = "sha256:2863555ba90b573e4201feaf87a7e71ca3b97c05aa4d63548a4b69ea16c9e998", size = 2200587 }, + { url = "https://files.pythonhosted.org/packages/b4/4a/786589606d4989cb34d8bc766cd687d955aaf3039c367fe7104bcf82dc98/fonttools-4.55.0-py3-none-any.whl", hash = "sha256:12db5888cd4dd3fcc9f0ee60c6edd3c7e1fd44b7dd0f31381ea03df68f8a153f", size = 1100249 }, +] + +[[package]] +name = "h5py" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/0c/5c2b0a88158682aeafb10c1c2b735df5bc31f165bfe192f2ee9f2a23b5f1/h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf", size = 411457 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/1c/ecdd0efab52c24f2a9bf2324289828b860e8dd1e3c5ada3cf0889e14fdc1/h5py-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:513171e90ed92236fc2ca363ce7a2fc6f2827375efcbb0cc7fbdd7fe11fecafc", size = 3346239 }, + { url = "https://files.pythonhosted.org/packages/93/cd/5b6f574bf3e318bbe305bc93ba45181676550eb44ba35e006d2e98004eaa/h5py-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59400f88343b79655a242068a9c900001a34b63e3afb040bd7cdf717e440f653", size = 2843416 }, + { url = "https://files.pythonhosted.org/packages/8a/4f/b74332f313bfbe94ba03fff784219b9db385e6139708e55b11490149f90a/h5py-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e465aee0ec353949f0f46bf6c6f9790a2006af896cee7c178a8c3e5090aa32", size = 5154390 }, + { url = "https://files.pythonhosted.org/packages/1a/57/93ea9e10a6457ea8d3b867207deb29a527e966a08a84c57ffd954e32152a/h5py-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba51c0c5e029bb5420a343586ff79d56e7455d496d18a30309616fdbeed1068f", size = 5378244 }, + { url = "https://files.pythonhosted.org/packages/50/51/0bbf3663062b2eeee78aa51da71e065f8a0a6e3cb950cc7020b4444999e6/h5py-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:52ab036c6c97055b85b2a242cb540ff9590bacfda0c03dd0cf0661b311f522f8", size = 2979760 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imageio" +version = "2.36.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/aa/2e7a49259339e691ff2b477ae0696b1784a09313c5872700bbbdd00a3030/imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62", size = 389522 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/f9/f78e7f5ac8077c481bf6b43b8bc736605363034b3d5eb3ce8eb79f53f5f1/imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf", size = 315435 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "joblib" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, +] + +[[package]] +name = "keras" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "h5py" }, + { name = "ml-dtypes" }, + { name = "namex" }, + { name = "numpy" }, + { name = "optree" }, + { name = "packaging" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/c3/56fc6800c5eab94bd0f5e930751bd4c0fa1ee0aee272fad4a72723ffae87/keras-3.7.0.tar.gz", hash = "sha256:a4451a5591e75dfb414d0b84a3fd2fb9c0240cc87ebe7e397f547ce10b0e67b7", size = 924719 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/bf/9e3f10e55df30b0fb4bf6c2ee7d50bda2e070599b86f62ea3f9954af172b/keras-3.7.0-py3-none-any.whl", hash = "sha256:546a64f302e4779c129c06d9826fa586de752cdfd43d7dc4010c31b282587969", size = 1228365 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913 }, + { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627 }, + { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888 }, + { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145 }, + { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448 }, + { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750 }, + { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175 }, + { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963 }, + { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220 }, + { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463 }, + { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842 }, + { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635 }, + { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556 }, + { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364 }, + { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887 }, + { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530 }, +] + +[[package]] +name = "lxml" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 }, + { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 }, + { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 }, + { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 }, + { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 }, + { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 }, + { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 }, + { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 }, + { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 }, + { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 }, + { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 }, + { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 }, + { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 }, + { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 }, + { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 }, + { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "matplotlib" +version = "3.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/9f/562ed484b11ac9f4bb4f9d2d7546954ec106a8c0f06cc755d6f63e519274/matplotlib-3.9.3.tar.gz", hash = "sha256:cd5dbbc8e25cad5f706845c4d100e2c8b34691b412b93717ce38d8ae803bcfa5", size = 36113438 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/04/949640040982822416c471d9ebe4e9e6c69ca9f9bb6ba82ed30808863c02/matplotlib-3.9.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:203d18df84f5288973b2d56de63d4678cc748250026ca9e1ad8f8a0fd8a75d83", size = 7883417 }, + { url = "https://files.pythonhosted.org/packages/9f/90/ebd37143cd3150b6c650ee1580024df3dd649d176e68d346f826b8d24e37/matplotlib-3.9.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b651b0d3642991259109dc0351fc33ad44c624801367bb8307be9bfc35e427ad", size = 7768720 }, + { url = "https://files.pythonhosted.org/packages/dc/84/6591e6b55d755d16dacdc113205067031867c1f5e3c08b32c01aad831420/matplotlib-3.9.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66d7b171fecf96940ce069923a08ba3df33ef542de82c2ff4fe8caa8346fa95a", size = 8192723 }, + { url = "https://files.pythonhosted.org/packages/29/09/146a17d37e32313507f11ac984e65311f2d5805d731eb981d4f70eb928dc/matplotlib-3.9.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be0ba61f6ff2e6b68e4270fb63b6813c9e7dec3d15fc3a93f47480444fd72f0", size = 8305801 }, + { url = "https://files.pythonhosted.org/packages/85/cb/d2690572c08f19ca7c0f44b1fb4d11c121d63467a57b508cc3656ff80b43/matplotlib-3.9.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d6b2e8856dec3a6db1ae51aec85c82223e834b228c1d3228aede87eee2b34f9", size = 9086564 }, + { url = "https://files.pythonhosted.org/packages/28/dd/0a5176027c1cb94fe75f69f76cb274180c8abf740df6fc0e6a1e4cbaec3f/matplotlib-3.9.3-cp313-cp313-win_amd64.whl", hash = "sha256:90a85a004fefed9e583597478420bf904bb1a065b0b0ee5b9d8d31b04b0f3f70", size = 7833257 }, + { url = "https://files.pythonhosted.org/packages/42/d4/e477d50a8e4b437c2afbb5c665cb8e5d79b06abe6fe3c6915d6f7f0c2ef2/matplotlib-3.9.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3119b2f16de7f7b9212ba76d8fe6a0e9f90b27a1e04683cd89833a991682f639", size = 7911906 }, + { url = "https://files.pythonhosted.org/packages/ae/a1/ba5ab89666c42ace8e31b4ff5a2c76a17e4d6f91aefce476b064c56ff61d/matplotlib-3.9.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:87ad73763d93add1b6c1f9fcd33af662fd62ed70e620c52fcb79f3ac427cf3a6", size = 7801336 }, + { url = "https://files.pythonhosted.org/packages/77/59/4dcdb3a6695af6c698a95aec13016a550ef2f85144d22f61f81d1e064148/matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:026bdf3137ab6022c866efa4813b6bbeddc2ed4c9e7e02f0e323a7bca380dfa0", size = 8218178 }, + { url = "https://files.pythonhosted.org/packages/4f/27/7c72db0d0ee35d9237572565ffa3c0eb25fc46a3f47e0f16412a587bc9d8/matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760a5e89ebbb172989e8273024a1024b0f084510b9105261b3b00c15e9c9f006", size = 8327768 }, + { url = "https://files.pythonhosted.org/packages/de/ad/213eee624feadba7b77e881c9d2c04c1e036efe69d19031e3fa927fdb5dc/matplotlib-3.9.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a42b9dc42de2cfe357efa27d9c50c7833fc5ab9b2eb7252ccd5d5f836a84e1e4", size = 9094075 }, + { url = "https://files.pythonhosted.org/packages/19/1b/cb8e99a5fe2e2b14e3b8234cb1649a675be63f74a5224a648ae4ab61f60c/matplotlib-3.9.3-cp313-cp313t-win_amd64.whl", hash = "sha256:e0fcb7da73fbf67b5f4bdaa57d85bb585a4e913d4a10f3e15b32baea56a67f0a", size = 7888937 }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/79/717c5e22ad25d63ce3acdfe8ff8d64bdedec18914256c59b838218708b16/ml_dtypes-0.5.0.tar.gz", hash = "sha256:3e7d3a380fe73a63c884f06136f8baa7a5249cc8e9fdec677997dd78549f8128", size = 699367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/4a/18f670a2703e771a6775fbc354208e597ff062a88efb0cecc220a282210b/ml_dtypes-0.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f", size = 753345 }, + { url = "https://files.pythonhosted.org/packages/ed/c6/358d85e274e22d53def0c85f3cbe0933475fa3cf6922e9dca66eb25cb22f/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599", size = 4424962 }, + { url = "https://files.pythonhosted.org/packages/4c/b4/d766586e24e7a073333c8eb8bd9275f3c6fe0569b509ae7b1699d4f00c74/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab", size = 4475201 }, + { url = "https://files.pythonhosted.org/packages/14/87/30323ad2e52f56262019a4493fe5f5e71067c5561ce7e2f9c75de520f5e8/ml_dtypes-0.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07", size = 213195 }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "myst-parser" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/55/6d1741a1780e5e65038b74bce6689da15f620261c490c3511eb4c12bac4b/myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531", size = 93858 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/b4/b036f8fdb667587bb37df29dc6644681dd78b7a2a6321a34684b79412b28/myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d", size = 84563 }, +] + +[[package]] +name = "namex" +version = "0.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/48/d275cdb6216c6bb4f9351675795a0b48974e138f16b1ffe0252c1f8faa28/namex-0.0.8.tar.gz", hash = "sha256:32a50f6c565c0bb10aa76298c959507abdc0e850efe085dc38f3440fcb3aa90b", size = 6623 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/59/7854fbfb59f8ae35483ce93493708be5942ebb6328cd85b3a609df629736/namex-0.0.8-py3-none-any.whl", hash = "sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487", size = 5806 }, +] + +[[package]] +name = "natsort" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268 }, +] + +[[package]] +name = "numpy" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263 }, + { url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771 }, + { url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805 }, + { url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380 }, + { url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451 }, + { url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822 }, + { url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822 }, + { url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598 }, + { url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021 }, + { url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405 }, + { url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062 }, + { url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839 }, + { url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031 }, + { url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977 }, + { url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951 }, + { url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655 }, + { url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902 }, + { url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180 }, + { url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907 }, + { url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098 }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.23.4" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/3a/0112397396dec37ffc8edd7836d48261b4d14ca60ec8ed7bc857cce1d916/nvidia_nccl_cu12-2.23.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:aa946c8327e22ced28e7cef508a334673abc42064ec85f02d005ba1785ea4cec", size = 198953892 }, + { url = "https://files.pythonhosted.org/packages/ed/1f/6482380ec8dcec4894e7503490fc536d846b0d59694acad9cf99f27d0e7d/nvidia_nccl_cu12-2.23.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:b097258d9aab2fa9f686e33c6fe40ae57b27df60cedbd15d139701bb5509e0c1", size = 198954603 }, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 }, +] + +[[package]] +name = "opencv-python" +version = "4.10.0.84" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/b70a2d9ab205110d715906fc8ec83fbb00404aeb3a37a0654fdb68eb0c8c/opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526", size = 95103981 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/82/564168a349148298aca281e342551404ef5521f33fba17b388ead0a84dc5/opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251", size = 54835524 }, + { url = "https://files.pythonhosted.org/packages/64/4a/016cda9ad7cf18c58ba074628a4eaae8aa55f3fd06a266398cef8831a5b9/opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98", size = 56475426 }, + { url = "https://files.pythonhosted.org/packages/81/e4/7a987ebecfe5ceaf32db413b67ff18eb3092c598408862fff4d7cc3fd19b/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6", size = 41746971 }, + { url = "https://files.pythonhosted.org/packages/3f/a4/d2537f47fd7fcfba966bd806e3ec18e7ee1681056d4b0a9c8d983983e4d5/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f", size = 62548253 }, + { url = "https://files.pythonhosted.org/packages/1e/39/bbf57e7b9dab623e8773f6ff36385456b7ae7fa9357a5e53db732c347eac/opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236", size = 28737688 }, + { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 }, +] + +[[package]] +name = "optree" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/f2/56afdaeaae36b076659be7db8e72be0924dd64ebd1c131675c77f7e704a6/optree-0.13.1.tar.gz", hash = "sha256:af67856aa8073d237fe67313d84f8aeafac32c1cef7239c628a2768d02679c43", size = 155738 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/53/f3727cad24f16a06666f328f1212476988cadac9b9e7919ddfb2c22eb662/optree-0.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f788b2ad120deb73b4908a74473cd6de79cfb9f33bbe9dcb59cea2e2477d4e28", size = 608270 }, + { url = "https://files.pythonhosted.org/packages/64/f2/68beb9da2dd52baa50e7a589ed2bd8434fdd70cdba06754aa5910263da06/optree-0.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2909cb42add6bb1a5a2b0243bdd8c4b861bf072f3741e26239481907ac8ad4e6", size = 325703 }, + { url = "https://files.pythonhosted.org/packages/45/db/08921e56f3425bf649eb593eb28775263c935d029985d35572dc5690cc1a/optree-0.13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc5fa2ff5090389f3a906567446f01d692bd6fe5cfcc5ae2d5861f24e8e0e4d", size = 355813 }, + { url = "https://files.pythonhosted.org/packages/e5/e3/587e0d28dc2cee064902adfebca97db124e12b275dbe9c2b05a70a22345f/optree-0.13.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4711f5cac5a2a49c3d6c9f0eca7b77c22b452170bb33ea01c3214ebb17931db9", size = 402566 }, + { url = "https://files.pythonhosted.org/packages/8a/1d/0d5bbab8c99580b732b89ef2c5fcdd6ef410478295949fdf2984fa1bfc28/optree-0.13.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c4ab1d391b89cb88eb3c63383d5eb0930bc21141de9d5acd277feed9e38eb65", size = 397005 }, + { url = "https://files.pythonhosted.org/packages/16/fa/fc2a8183e14f0d195d25824bf65095ff32b34bd469614a6c30d0a596a30f/optree-0.13.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5e5f09c85ae558a6bdaea57e63168082e728e777391393e9e2792f0d15b7b59", size = 369400 }, + { url = "https://files.pythonhosted.org/packages/9f/42/8c08ce4ebb3d9a6e4415f1a97830c84879e2d1a43710a7c8a18b2c3e169d/optree-0.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c8ee1e988c634a451146b87d9ebdbf650a75dc1f52a9cffcd89fabb7289321c", size = 390179 }, + { url = "https://files.pythonhosted.org/packages/06/02/3a701d6307fdfefe4fcecbac644803e2a4314ab2406ff465e03129cc85f6/optree-0.13.1-cp313-cp313-win32.whl", hash = "sha256:5b6531cd4eb23fadbbf77faf834e1119da06d7af3154f55786b59953cd87bb8a", size = 264264 }, + { url = "https://files.pythonhosted.org/packages/ef/f9/8a1421181c5eb0c0f81d1423a900baeb3faba68a48747bbdffb7581239ac/optree-0.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:27d81dc43b522ba47ba7d2e7d91dbb486940348b1bf85caeb0afc2815c0aa492", size = 293682 }, + { url = "https://files.pythonhosted.org/packages/80/34/d1b1849a6240385c4a3af5da9425b11912204d0b1cf142d802815319b73a/optree-0.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:f39c7174a3f3cdc3f5fe6fb4b832f608c40ac174d7567ed6734b2ee952094631", size = 293670 }, + { url = "https://files.pythonhosted.org/packages/0d/d6/f81e6748bcc3f35a2f570a814014e3418b0ed425d7cbc2b42d88d12863d5/optree-0.13.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3010ae24e994f6e00071098d34e98e78eb995b7454a2ef629a0bf7df17441b24", size = 702861 }, + { url = "https://files.pythonhosted.org/packages/08/7f/70a2d02110ccb245bc57bd9ad57668acfea0ff364c27d7dfe1735ede79ed/optree-0.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5b5626c38d4a18a144063db5c1dbb558431d83ca10682324f74665a12214801f", size = 370740 }, + { url = "https://files.pythonhosted.org/packages/63/37/4ddf05267467809236203e2007e9443519c4d55e0744ce7eea1aa74dffee/optree-0.13.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1935639dd498a42367633e3877797e1330e39d44d48bbca1a136bb4dbe4c1bc9", size = 374695 }, + { url = "https://files.pythonhosted.org/packages/19/f2/51a63a799f6dce31813d7e02a7547394aebcb39f407e62038ecbd999d490/optree-0.13.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01819c3df950696f32c91faf8d376ae6b695ffdba18f330f1cab6b8e314e4612", size = 418671 }, + { url = "https://files.pythonhosted.org/packages/f0/7c/a08191e0c9202f2be9c415057eea3cf3a5af18e9a6d81f4c7b0e6faf0a1f/optree-0.13.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48c29d9c6c64c8dc48c8ee97f7c1d5cdb83e37320f0be0857c06ce4b97994aea", size = 414966 }, + { url = "https://files.pythonhosted.org/packages/8f/37/7bf815f4da7234e387863228b17246b42b8c02553882581a4013a64a88d0/optree-0.13.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:025d23400b8b579462a251420f0a9ae77d3d3593f84276f3465985731d79d722", size = 389219 }, + { url = "https://files.pythonhosted.org/packages/3d/84/bb521a66d3a84fe2f1500ef67d245c2cc1a26277fcaaf4bc70b22c06e99b/optree-0.13.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55e82426bef151149cfa41d68ac957730fcd420996c0db8324fca81aa6a810ba", size = 405377 }, + { url = "https://files.pythonhosted.org/packages/06/99/3eb53829c4c0b6dc20115d957d2d8e945630ddf40c656dc4e39c5a6e51f2/optree-0.13.1-cp313-cp313t-win32.whl", hash = "sha256:e40f018f522fcfd244688d1b3a360518e636ba7f636385aae0566eae3e7d29bc", size = 292734 }, + { url = "https://files.pythonhosted.org/packages/2f/59/d7601959ad0b90d309794c0975a256304488b4c5671f24e3e12101ade7ef/optree-0.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d580f1bf23bb352c4db6b3544f282f1ac08dcb0d9ab537d25e56220353438cf7", size = 331457 }, + { url = "https://files.pythonhosted.org/packages/8b/36/c01a5bc34660d46c6a3b1fe090bbdc8c76af7b5c1a6613cc671aa6df8349/optree-0.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c4d13f55dbd509d27be3af54d53b4ca0751bc518244ced6d0567e518e51452a2", size = 331470 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "python_full_version >= '3.13'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + +[[package]] +name = "patsy" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923 }, +] + +[[package]] +name = "pillow" +version = "11.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/24/e2e15e392d00fcf4215907465d8ec2a2f23bcec1481a8ebe4ae760459995/pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", size = 3147300 }, + { url = "https://files.pythonhosted.org/packages/43/72/92ad4afaa2afc233dc44184adff289c2e77e8cd916b3ddb72ac69495bda3/pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", size = 2978742 }, + { url = "https://files.pythonhosted.org/packages/9e/da/c8d69c5bc85d72a8523fe862f05ababdc52c0a755cfe3d362656bb86552b/pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", size = 4194349 }, + { url = "https://files.pythonhosted.org/packages/cd/e8/686d0caeed6b998351d57796496a70185376ed9c8ec7d99e1d19ad591fc6/pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", size = 4298714 }, + { url = "https://files.pythonhosted.org/packages/ec/da/430015cec620d622f06854be67fd2f6721f52fc17fca8ac34b32e2d60739/pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", size = 4208514 }, + { url = "https://files.pythonhosted.org/packages/44/ae/7e4f6662a9b1cb5f92b9cc9cab8321c381ffbee309210940e57432a4063a/pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", size = 4380055 }, + { url = "https://files.pythonhosted.org/packages/74/d5/1a807779ac8a0eeed57f2b92a3c32ea1b696e6140c15bd42eaf908a261cd/pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", size = 4296751 }, + { url = "https://files.pythonhosted.org/packages/38/8c/5fa3385163ee7080bc13026d59656267daaaaf3c728c233d530e2c2757c8/pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", size = 4430378 }, + { url = "https://files.pythonhosted.org/packages/ca/1d/ad9c14811133977ff87035bf426875b93097fb50af747793f013979facdb/pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", size = 2249588 }, + { url = "https://files.pythonhosted.org/packages/fb/01/3755ba287dac715e6afdb333cb1f6d69740a7475220b4637b5ce3d78cec2/pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", size = 2567509 }, + { url = "https://files.pythonhosted.org/packages/c0/98/2c7d727079b6be1aba82d195767d35fcc2d32204c7a5820f822df5330152/pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", size = 2254791 }, + { url = "https://files.pythonhosted.org/packages/eb/38/998b04cc6f474e78b563716b20eecf42a2fa16a84589d23c8898e64b0ffd/pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", size = 3150854 }, + { url = "https://files.pythonhosted.org/packages/13/8e/be23a96292113c6cb26b2aa3c8b3681ec62b44ed5c2bd0b258bd59503d3c/pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", size = 2982369 }, + { url = "https://files.pythonhosted.org/packages/97/8a/3db4eaabb7a2ae8203cd3a332a005e4aba00067fc514aaaf3e9721be31f1/pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", size = 4333703 }, + { url = "https://files.pythonhosted.org/packages/28/ac/629ffc84ff67b9228fe87a97272ab125bbd4dc462745f35f192d37b822f1/pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", size = 4412550 }, + { url = "https://files.pythonhosted.org/packages/d6/07/a505921d36bb2df6868806eaf56ef58699c16c388e378b0dcdb6e5b2fb36/pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", size = 4461038 }, + { url = "https://files.pythonhosted.org/packages/d6/b9/fb620dd47fc7cc9678af8f8bd8c772034ca4977237049287e99dda360b66/pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", size = 2253197 }, + { url = "https://files.pythonhosted.org/packages/df/86/25dde85c06c89d7fc5db17940f07aae0a56ac69aa9ccb5eb0f09798862a8/pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", size = 2572169 }, + { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/d5/e5aeee5387091148a19e1145f63606619cb5f20b83fccb63efae6474e7b2/pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c", size = 920984 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/ec/2eb3cd785efd67806c46c13a17339708ddc346cbb684eade7a6e6f79536a/pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", size = 106921 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pytz" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/52/531ef197b426646f26b53815a7d2a67cb7a331ef098bb276db26a68ac49f/requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a", size = 52027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/bb/5deac77a9af870143c684ab46a7934038a53eb4aa975bc0687ed6ca2c610/requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", size = 23892 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + +[[package]] +name = "ruff" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605 }, + { url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243 }, + { url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739 }, + { url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153 }, + { url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387 }, + { url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351 }, + { url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879 }, + { url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354 }, + { url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976 }, + { url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564 }, + { url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604 }, + { url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071 }, + { url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657 }, + { url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362 }, + { url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476 }, + { url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463 }, + { url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621 }, +] + +[[package]] +name = "scikit-learn" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/59/44985a2bdc95c74e34fef3d10cb5d93ce13b0e2a7baefffe1b53853b502d/scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d", size = 7001680 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/50/8891028437858cc510e13578fe7046574a60c2aaaa92b02d64aac5b1b412/scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5", size = 12025584 }, + { url = "https://files.pythonhosted.org/packages/d2/79/17feef8a1c14149436083bec0e61d7befb4812e272d5b20f9d79ea3e9ab1/scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908", size = 10959795 }, + { url = "https://files.pythonhosted.org/packages/b1/c8/f08313f9e2e656bd0905930ae8bf99a573ea21c34666a813b749c338202f/scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3", size = 12077302 }, + { url = "https://files.pythonhosted.org/packages/a7/48/fbfb4dc72bed0fe31fe045fb30e924909ad03f717c36694351612973b1a9/scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12", size = 13002811 }, + { url = "https://files.pythonhosted.org/packages/a5/e7/0c869f9e60d225a77af90d2aefa7a4a4c0e745b149325d1450f0f0ce5399/scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f", size = 10951354 }, +] + +[[package]] +name = "scipy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/ef/ac98346db016ff18a6ad7626a35808f37074d25796fd0234c2bb0ed1e054/scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79", size = 39091068 }, + { url = "https://files.pythonhosted.org/packages/b9/cc/70948fe9f393b911b4251e96b55bbdeaa8cca41f37c26fd1df0232933b9e/scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e", size = 29875417 }, + { url = "https://files.pythonhosted.org/packages/3b/2e/35f549b7d231c1c9f9639f9ef49b815d816bf54dd050da5da1c11517a218/scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73", size = 23084508 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/b028e3f3e59fae61fb8c0f450db732c43dd1d836223a589a8be9f6377203/scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e", size = 25503364 }, + { url = "https://files.pythonhosted.org/packages/a7/2f/6c142b352ac15967744d62b165537a965e95d557085db4beab2a11f7943b/scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d", size = 35292639 }, + { url = "https://files.pythonhosted.org/packages/56/46/2449e6e51e0d7c3575f289f6acb7f828938eaab8874dbccfeb0cd2b71a27/scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e", size = 40798288 }, + { url = "https://files.pythonhosted.org/packages/32/cd/9d86f7ed7f4497c9fd3e39f8918dd93d9f647ba80d7e34e4946c0c2d1a7c/scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06", size = 42524647 }, + { url = "https://files.pythonhosted.org/packages/f5/1b/6ee032251bf4cdb0cc50059374e86a9f076308c1512b61c4e003e241efb7/scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84", size = 44469524 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, +] + +[[package]] +name = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, +] + +[[package]] +name = "sphinx-autoapi" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid", marker = "python_full_version >= '3.13'" }, + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/eb/cc243583bb1d518ca3b10998c203d919a8ed90affd4831f2b61ad09043d2/sphinx_autoapi-3.4.0.tar.gz", hash = "sha256:e6d5371f9411bbb9fca358c00a9e57aef3ac94cbfc5df4bab285946462f69e0c", size = 29292 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d6/f2acdc2567337fd5f5dc091a4e58d8a0fb14927b9779fc1e5ecee96d9824/sphinx_autoapi-3.4.0-py3-none-any.whl", hash = "sha256:4027fef2875a22c5f2a57107c71641d82f6166bf55beb407a47aaf3ef14e7b92", size = 34095 }, +] + +[[package]] +name = "sphinx-pyproject" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dom-toml" }, + { name = "domdf-python-tools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/97/aa8cec3da3e78f2c396b63332e2fe92fe43f7ff2ad19b3998735f28b0a7f/sphinx_pyproject-0.3.0.tar.gz", hash = "sha256:efc4ee9d96f579c4e4ed1ac273868c64565e88c8e37fe6ec2dc59fbcd57684ab", size = 7695 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/d5/89cb47c6399fd57ca451af15361499813c5d53e588cb6e00d89411ce724f/sphinx_pyproject-0.3.0-py3-none-any.whl", hash = "sha256:3aca968919f5ecd390f96874c3f64a43c9c7fcfdc2fd4191a781ad9228501b52", size = 23076 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "statsmodels" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "patsy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/3b/963a015dd8ea17e10c7b0e2f14d7c4daec903baf60a017e756b57953a4bf/statsmodels-0.14.4.tar.gz", hash = "sha256:5d69e0f39060dc72c067f9bb6e8033b6dccdb0bae101d76a7ef0bcc94e898b67", size = 20354802 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/f8/2662e6a101315ad336f75168fa9bac71f913ebcb92a6be84031d84a0f21f/statsmodels-0.14.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5a24f5d2c22852d807d2b42daf3a61740820b28d8381daaf59dcb7055bf1a79", size = 10186886 }, + { url = "https://files.pythonhosted.org/packages/fa/c0/ee6e8ed35fc1ca9c7538c592f4974547bf72274bc98db1ae4a6e87481a83/statsmodels-0.14.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df4f7864606fa843d7e7c0e6af288f034a2160dba14e6ccc09020a3cf67cb092", size = 9880066 }, + { url = "https://files.pythonhosted.org/packages/d1/97/3380ca6d8fd66cfb3d12941e472642f26e781a311c355a4e97aab2ed0216/statsmodels-0.14.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91341cbde9e8bea5fb419a76e09114e221567d03f34ca26e6d67ae2c27d8fe3c", size = 10283521 }, + { url = "https://files.pythonhosted.org/packages/fe/2a/55c5b5c5e5124a202ea3fe0bcdbdeceaf91b4ec6164b8434acb9dd97409c/statsmodels-0.14.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1322286a7bfdde2790bf72d29698a1b76c20b8423a55bdcd0d457969d0041f72", size = 10723228 }, + { url = "https://files.pythonhosted.org/packages/4f/76/67747e49dc758daae06f33aad8247b718cd7d224f091d2cd552681215bb2/statsmodels-0.14.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e31b95ac603415887c9f0d344cb523889cf779bc52d68e27e2d23c358958fec7", size = 10859503 }, + { url = "https://files.pythonhosted.org/packages/1d/eb/cb8b01f5edf8f135eb3d0553d159db113a35b2948d0e51eeb735e7ae09ea/statsmodels-0.14.4-cp313-cp313-win_amd64.whl", hash = "sha256:81030108d27aecc7995cac05aa280cf8c6025f6a6119894eef648997936c2dd0", size = 9817574 }, +] + +[[package]] +name = "sympy" +version = "1.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/8a/5a7fd6284fa8caac23a26c9ddf9c30485a48169344b4bd3b0f02fef1890f/sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9", size = 7533196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/ff/c87e0622b1dadea79d2fb0b25ade9ed98954c9033722eb707053d310d4f3/sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73", size = 6189483 }, +] + +[[package]] +name = "thealgorithms-python" +version = "0.0.1" +source = { virtual = "." } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "fake-useragent" }, + { name = "imageio" }, + { name = "keras" }, + { name = "lxml" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "requests" }, + { name = "rich" }, + { name = "scikit-learn" }, + { name = "sphinx-pyproject" }, + { name = "statsmodels" }, + { name = "sympy" }, + { name = "tweepy" }, + { name = "typing-extensions" }, + { name = "xgboost" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] +docs = [ + { name = "myst-parser" }, + { name = "sphinx-autoapi" }, + { name = "sphinx-pyproject" }, +] +euler-validate = [ + { name = "numpy" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "requests" }, +] +lint = [ + { name = "codespell" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "beautifulsoup4", specifier = ">=4.12.3" }, + { name = "fake-useragent", specifier = ">=1.5.1" }, + { name = "imageio", specifier = ">=2.36.1" }, + { name = "keras", specifier = ">=3.7" }, + { name = "lxml", specifier = ">=5.3" }, + { name = "matplotlib", specifier = ">=3.9.3" }, + { name = "numpy", specifier = ">=2.1.3" }, + { name = "opencv-python", specifier = ">=4.10.0.84" }, + { name = "pandas", specifier = ">=2.2.3" }, + { name = "pillow", specifier = ">=11" }, + { name = "requests", specifier = ">=2.32.3" }, + { name = "rich", specifier = ">=13.9.4" }, + { name = "scikit-learn", specifier = ">=1.5.2" }, + { name = "sphinx-pyproject", specifier = ">=0.3" }, + { name = "statsmodels", specifier = ">=0.14.4" }, + { name = "sympy", specifier = ">=1.13.3" }, + { name = "tweepy", specifier = ">=4.14" }, + { name = "typing-extensions", specifier = ">=4.12.2" }, + { name = "xgboost", specifier = ">=2.1.3" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.4" }, + { name = "pytest-cov", specifier = ">=6" }, +] +docs = [ + { name = "myst-parser", specifier = ">=4.0.0" }, + { name = "sphinx-autoapi", specifier = ">=3.4.0" }, + { name = "sphinx-pyproject", specifier = ">=0.3.0" }, +] +euler-validate = [ + { name = "numpy", specifier = ">=2.1.3" }, + { name = "pytest", specifier = ">=8.3.4" }, + { name = "pytest-cov", specifier = ">=6.0.0" }, + { name = "requests", specifier = ">=2.32.3" }, +] +lint = [ + { name = "codespell", specifier = ">=2.3" }, + { name = "ruff", specifier = ">=0.8.1" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "tweepy" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/1c/0db8c3cf9d31bf63853ff612d201060ae78e6db03468a70e063bef0eda62/tweepy-4.14.0.tar.gz", hash = "sha256:1f9f1707d6972de6cff6c5fd90dfe6a449cd2e0d70bd40043ffab01e07a06c8c", size = 88623 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/78/ba0065d5636bbf4a35b78c4f81b74e7858b609cdf69e629d6da5c91b9d92/tweepy-4.14.0-py3-none-any.whl", hash = "sha256:db6d3844ccc0c6d27f339f12ba8acc89912a961da513c1ae50fa2be502a56afb", size = 98520 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "tzdata" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "xgboost" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine != 'aarch64' and platform_system == 'Linux'" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/b0/131ffc4a15fd3acee9be3a7baa6b2fa6faa479799c51b880de9fc3ddf550/xgboost-2.1.3.tar.gz", hash = "sha256:7699ec4226156887d3afc665c63ab87469db9d46e361c702ba9fccd22535730c", size = 1090326 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/c6/773ebd84414879bd0566788868ae46a6574f6efaf81e694f01ea1fed3277/xgboost-2.1.3-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:c9b0c92f13e3650e1e1cf92ff9ecef3efc6f5dc3d10ce17858df2081a89976ef", size = 2139909 }, + { url = "https://files.pythonhosted.org/packages/28/3c/ddf5d9eb742cdb7fbcd5c854bce07471bad01194ac37de91db64fbef0c58/xgboost-2.1.3-py3-none-macosx_12_0_arm64.whl", hash = "sha256:fcbf1912a852bd07a7007be350c8dc3a484c5e775b612f2b3cd082fc76240eb3", size = 1938631 }, + { url = "https://files.pythonhosted.org/packages/4a/3a/8cd69a216993fd9d54ceb079d1b357b7ef50678b3c2695d8a71962b8d0aa/xgboost-2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:27af88df1162cee016c67f267a0a16c3db1c48f256e12f64c45c8f8edf9571cd", size = 4441261 }, + { url = "https://files.pythonhosted.org/packages/48/bc/05d7db90d421c5e3d681a12fd1eb087e37bf2e9bbe2b105422d6319ecc92/xgboost-2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32a43526208fe676527c698cb852e0e9515e6d7294143780e476d335290a131b", size = 4532380 }, + { url = "https://files.pythonhosted.org/packages/0f/c8/f679a816c06a4a6d23da3f4b448d5f0615b51de2886ad3e3e695d17121b3/xgboost-2.1.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:5d33090880f3d474f8cf5dda557c7bf8dbceefb62f2fd655c77efcabb9cac222", size = 4207000 }, + { url = "https://files.pythonhosted.org/packages/32/93/66826e2f50cefecbb0a44bd1e667316bf0a3c8e78cd1f0cdf52f5b2c5c6f/xgboost-2.1.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:8d85d38553855a1f8c40b8fbccca86af19202f91b244e2c7f77afbb2a6d9d785", size = 153894508 }, + { url = "https://files.pythonhosted.org/packages/70/58/2f94976df39470fb00eec2cb4f914dde44cd0df8d96483208bf7db4bc97e/xgboost-2.1.3-py3-none-win_amd64.whl", hash = "sha256:25c0ffcbd62aac5bc22c79e08b5b2edad1d5e37f16610ebefa5f06f3e2ea3d96", size = 124909665 }, +] From 98391e33ea2a87375a7f744eba3d57918237b4e7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 18:40:43 +0100 Subject: [PATCH 2804/2908] [pre-commit.ci] pre-commit autoupdate (#12428) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.1 → v0.8.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.1...v0.8.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bef251749c19..884b10661a49 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.1 + rev: v0.8.2 hooks: - id: ruff - id: ruff-format From f8e595e048f1cbd763e0a1f8c0ffb4dff335b841 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:53:36 +0100 Subject: [PATCH 2805/2908] [pre-commit.ci] pre-commit autoupdate (#12439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.2 → v0.8.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.2...v0.8.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 884b10661a49..0c8108ac55be 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.2 + rev: v0.8.3 hooks: - id: ruff - id: ruff-format From 4abfce2791c081f65580bc1fefdf5a4d8ee7b5fc Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 23 Dec 2024 06:55:22 +0300 Subject: [PATCH 2806/2908] Fix sphinx/build_docs warnings for audio_filters (#12449) * updating DIRECTORY.md * Fix sphinx/build_docs warnings for audio_filters * Improve * Fix * Fix * Fix --------- Co-authored-by: MaximSmolskiy --- audio_filters/iir_filter.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/audio_filters/iir_filter.py b/audio_filters/iir_filter.py index f3c1ad43b001..fa3e6c54b33f 100644 --- a/audio_filters/iir_filter.py +++ b/audio_filters/iir_filter.py @@ -10,13 +10,17 @@ class IIRFilter: Implementation details: Based on the 2nd-order function from - https://en.wikipedia.org/wiki/Digital_biquad_filter, + https://en.wikipedia.org/wiki/Digital_biquad_filter, this generalized N-order function was made. Using the following transfer function - H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}} + .. math:: H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}} + {a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}} + we can rewrite this to - y[n]={\frac{1}{a_{0}}}\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right) + .. math:: y[n]={\frac{1}{a_{0}}} + \left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)- + \left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right) """ def __init__(self, order: int) -> None: @@ -34,17 +38,19 @@ def __init__(self, order: int) -> None: def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None: """ - Set the coefficients for the IIR filter. These should both be of size order + 1. - a_0 may be left out, and it will use 1.0 as default value. + Set the coefficients for the IIR filter. + These should both be of size `order` + 1. + :math:`a_0` may be left out, and it will use 1.0 as default value. This method works well with scipy's filter design functions - >>> # Make a 2nd-order 1000Hz butterworth lowpass filter - >>> import scipy.signal - >>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000, - ... btype='lowpass', - ... fs=48000) - >>> filt = IIRFilter(2) - >>> filt.set_coefficients(a_coeffs, b_coeffs) + + >>> # Make a 2nd-order 1000Hz butterworth lowpass filter + >>> import scipy.signal + >>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000, + ... btype='lowpass', + ... fs=48000) + >>> filt = IIRFilter(2) + >>> filt.set_coefficients(a_coeffs, b_coeffs) """ if len(a_coeffs) < self.order: a_coeffs = [1.0, *a_coeffs] @@ -68,7 +74,7 @@ def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None def process(self, sample: float) -> float: """ - Calculate y[n] + Calculate :math:`y[n]` >>> filt = IIRFilter(2) >>> filt.process(0) From 47cd21a110d8e2fc038414bc7f3c7ca8e91d6653 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 23 Dec 2024 14:56:42 +0300 Subject: [PATCH 2807/2908] Fix sphinx/build_docs warnings for cellular_automata (#12454) * updating DIRECTORY.md * Fix sphinx/build_docs warnings for cellular_automata * Fix * Improve --------- Co-authored-by: MaximSmolskiy --- cellular_automata/wa_tor.py | 54 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index e423d1595bdb..29f7ea510bfe 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -1,9 +1,9 @@ """ Wa-Tor algorithm (1984) -@ https://en.wikipedia.org/wiki/Wa-Tor -@ https://beltoforion.de/en/wator/ -@ https://beltoforion.de/en/wator/images/wator_medium.webm +| @ https://en.wikipedia.org/wiki/Wa-Tor +| @ https://beltoforion.de/en/wator/ +| @ https://beltoforion.de/en/wator/images/wator_medium.webm This solution aims to completely remove any systematic approach to the Wa-Tor planet, and utilise fully random methods. @@ -97,8 +97,8 @@ class WaTor: :attr time_passed: A function that is called every time time passes (a chronon) in order to visually display - the new Wa-Tor planet. The time_passed function can block - using time.sleep to slow the algorithm progression. + the new Wa-Tor planet. The `time_passed` function can block + using ``time.sleep`` to slow the algorithm progression. >>> wt = WaTor(10, 15) >>> wt.width @@ -216,7 +216,7 @@ def get_surrounding_prey(self, entity: Entity) -> list[Entity]: """ Returns all the prey entities around (N, S, E, W) a predator entity. - Subtly different to the try_to_move_to_unoccupied square. + Subtly different to the `move_and_reproduce`. >>> wt = WaTor(WIDTH, HEIGHT) >>> wt.set_planet([ @@ -260,7 +260,7 @@ def move_and_reproduce( """ Attempts to move to an unoccupied neighbouring square in either of the four directions (North, South, East, West). - If the move was successful and the remaining_reproduction time is + If the move was successful and the `remaining_reproduction_time` is equal to 0, then a new prey or predator can also be created in the previous square. @@ -351,12 +351,12 @@ def perform_prey_actions( Performs the actions for a prey entity For prey the rules are: - 1. At each chronon, a prey moves randomly to one of the adjacent unoccupied - squares. If there are no free squares, no movement takes place. - 2. Once a prey has survived a certain number of chronons it may reproduce. - This is done as it moves to a neighbouring square, - leaving behind a new prey in its old position. - Its reproduction time is also reset to zero. + 1. At each chronon, a prey moves randomly to one of the adjacent unoccupied + squares. If there are no free squares, no movement takes place. + 2. Once a prey has survived a certain number of chronons it may reproduce. + This is done as it moves to a neighbouring square, + leaving behind a new prey in its old position. + Its reproduction time is also reset to zero. >>> wt = WaTor(WIDTH, HEIGHT) >>> reproducable_entity = Entity(True, coords=(0, 1)) @@ -382,15 +382,15 @@ def perform_predator_actions( :param occupied_by_prey_coords: Move to this location if there is prey there For predators the rules are: - 1. At each chronon, a predator moves randomly to an adjacent square occupied - by a prey. If there is none, the predator moves to a random adjacent - unoccupied square. If there are no free squares, no movement takes place. - 2. At each chronon, each predator is deprived of a unit of energy. - 3. Upon reaching zero energy, a predator dies. - 4. If a predator moves to a square occupied by a prey, - it eats the prey and earns a certain amount of energy. - 5. Once a predator has survived a certain number of chronons - it may reproduce in exactly the same way as the prey. + 1. At each chronon, a predator moves randomly to an adjacent square occupied + by a prey. If there is none, the predator moves to a random adjacent + unoccupied square. If there are no free squares, no movement takes place. + 2. At each chronon, each predator is deprived of a unit of energy. + 3. Upon reaching zero energy, a predator dies. + 4. If a predator moves to a square occupied by a prey, + it eats the prey and earns a certain amount of energy. + 5. Once a predator has survived a certain number of chronons + it may reproduce in exactly the same way as the prey. >>> wt = WaTor(WIDTH, HEIGHT) >>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]]) @@ -430,7 +430,7 @@ def perform_predator_actions( def run(self, *, iteration_count: int) -> None: """ - Emulate time passing by looping iteration_count times + Emulate time passing by looping `iteration_count` times >>> wt = WaTor(WIDTH, HEIGHT) >>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1) @@ -484,11 +484,9 @@ def visualise(wt: WaTor, iter_number: int, *, colour: bool = True) -> None: an ascii code in terminal to clear and re-print the Wa-Tor planet at intervals. - Uses ascii colour codes to colourfully display - the predators and prey. - - (0x60f197) Prey = # - (0xfffff) Predator = x + Uses ascii colour codes to colourfully display the predators and prey: + * (0x60f197) Prey = ``#`` + * (0xfffff) Predator = ``x`` >>> wt = WaTor(30, 30) >>> wt.set_planet([ From c5e603ae4234e5d516d700b01d47f78d42c18008 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 23 Dec 2024 15:43:16 +0300 Subject: [PATCH 2808/2908] Fix sphinx/build_docs warnings for geodesy (#12462) * updating DIRECTORY.md * Fix sphinx/build_docs warnings for geodesy/haversine_distance.py * Improve --------- Co-authored-by: MaximSmolskiy --- geodesy/haversine_distance.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/geodesy/haversine_distance.py b/geodesy/haversine_distance.py index 93e625770f9d..39cd250af965 100644 --- a/geodesy/haversine_distance.py +++ b/geodesy/haversine_distance.py @@ -21,10 +21,11 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl 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 + * `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) From b0cb13fea54854b3a60eced27026db9a9c5dc5ab Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 23 Dec 2024 16:11:58 +0300 Subject: [PATCH 2809/2908] Fix sphinx/build_docs warnings for greedy_methods (#12463) * updating DIRECTORY.md * Fix sphinx/build_docs warnings for greedy_methods * Improve --------- Co-authored-by: MaximSmolskiy --- greedy_methods/smallest_range.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/greedy_methods/smallest_range.py b/greedy_methods/smallest_range.py index e2b7f8d7e96a..9adb12bf9029 100644 --- a/greedy_methods/smallest_range.py +++ b/greedy_methods/smallest_range.py @@ -14,12 +14,13 @@ def smallest_range(nums: list[list[int]]) -> list[int]: Uses min heap for efficiency. The range includes at least one number from each list. Args: - nums: List of k sorted integer lists. + `nums`: List of k sorted integer lists. Returns: list: Smallest range as a two-element list. Examples: + >>> smallest_range([[4, 10, 15, 24, 26], [0, 9, 12, 20], [5, 18, 22, 30]]) [20, 24] >>> smallest_range([[1, 2, 3], [1, 2, 3], [1, 2, 3]]) From 04fbfd6eae38b9897c1b8ff6aee487dd2523665b Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 24 Dec 2024 03:14:11 +0300 Subject: [PATCH 2810/2908] Fix sphinx/build_docs warnings for maths/volume (#12464) * Fix sphinx/build_docs warnings for maths/volume * Fix * Fix * Fix * Fix * Fix * Fix * Fix --- maths/volume.py | 149 ++++++++++++++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 54 deletions(-) diff --git a/maths/volume.py b/maths/volume.py index 23fcf6be6ef1..08bdf72b013b 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -1,5 +1,6 @@ """ Find the volume of various shapes. + * https://en.wikipedia.org/wiki/Volume * https://en.wikipedia.org/wiki/Spherical_cap """ @@ -12,6 +13,7 @@ def vol_cube(side_length: float) -> float: """ Calculate the Volume of a Cube. + >>> vol_cube(1) 1.0 >>> vol_cube(3) @@ -33,6 +35,7 @@ def vol_cube(side_length: float) -> float: def vol_spherical_cap(height: float, radius: float) -> float: """ Calculate the volume of the spherical cap. + >>> vol_spherical_cap(1, 2) 5.235987755982988 >>> vol_spherical_cap(1.6, 2.6) @@ -57,20 +60,29 @@ def vol_spherical_cap(height: float, radius: float) -> float: def vol_spheres_intersect( radius_1: float, radius_2: float, centers_distance: float ) -> float: - """ + r""" Calculate the volume of the intersection of two spheres. + The intersection is composed by two spherical caps and therefore its volume is the - sum of the volumes of the spherical caps. First, it calculates the heights (h1, h2) - of the spherical caps, then the two volumes and it returns the sum. + sum of the volumes of the spherical caps. + First, it calculates the heights :math:`(h_1, h_2)` of the spherical caps, + then the two volumes and it returns the sum. The height formulas are - h1 = (radius_1 - radius_2 + centers_distance) - * (radius_1 + radius_2 - centers_distance) - / (2 * centers_distance) - h2 = (radius_2 - radius_1 + centers_distance) - * (radius_2 + radius_1 - centers_distance) - / (2 * centers_distance) - if centers_distance is 0 then it returns the volume of the smallers sphere - :return vol_spherical_cap(h1, radius_2) + vol_spherical_cap(h2, radius_1) + + .. math:: + h_1 = \frac{(radius_1 - radius_2 + centers\_distance) + \cdot (radius_1 + radius_2 - centers\_distance)} + {2 \cdot centers\_distance} + + h_2 = \frac{(radius_2 - radius_1 + centers\_distance) + \cdot (radius_2 + radius_1 - centers\_distance)} + {2 \cdot centers\_distance} + + if `centers_distance` is 0 then it returns the volume of the smallers sphere + + :return: ``vol_spherical_cap`` (:math:`h_1`, :math:`radius_2`) + + ``vol_spherical_cap`` (:math:`h_2`, :math:`radius_1`) + >>> vol_spheres_intersect(2, 2, 1) 21.205750411731103 >>> vol_spheres_intersect(2.6, 2.6, 1.6) @@ -112,14 +124,18 @@ def vol_spheres_intersect( def vol_spheres_union( radius_1: float, radius_2: float, centers_distance: float ) -> float: - """ + r""" Calculate the volume of the union of two spheres that possibly intersect. - It is the sum of sphere A and sphere B minus their intersection. - First, it calculates the volumes (v1, v2) of the spheres, - then the volume of the intersection (i) and it returns the sum v1+v2-i. - If centers_distance is 0 then it returns the volume of the larger sphere - :return vol_sphere(radius_1) + vol_sphere(radius_2) - - vol_spheres_intersect(radius_1, radius_2, centers_distance) + + It is the sum of sphere :math:`A` and sphere :math:`B` minus their intersection. + First, it calculates the volumes :math:`(v_1, v_2)` of the spheres, + then the volume of the intersection :math:`i` and + it returns the sum :math:`v_1 + v_2 - i`. + If `centers_distance` is 0 then it returns the volume of the larger sphere + + :return: ``vol_sphere`` (:math:`radius_1`) + ``vol_sphere`` (:math:`radius_2`) + - ``vol_spheres_intersect`` + (:math:`radius_1`, :math:`radius_2`, :math:`centers\_distance`) >>> vol_spheres_union(2, 2, 1) 45.814892864851146 @@ -157,7 +173,9 @@ def vol_spheres_union( def vol_cuboid(width: float, height: float, length: float) -> float: """ Calculate the Volume of a Cuboid. - :return multiple of width, length and height + + :return: multiple of `width`, `length` and `height` + >>> vol_cuboid(1, 1, 1) 1.0 >>> vol_cuboid(1, 2, 3) @@ -185,10 +203,12 @@ def vol_cuboid(width: float, height: float, length: float) -> float: def vol_cone(area_of_base: float, height: float) -> float: - """ - Calculate the Volume of a Cone. - Wikipedia reference: https://en.wikipedia.org/wiki/Cone - :return (1/3) * area_of_base * height + r""" + | Calculate the Volume of a Cone. + | Wikipedia reference: https://en.wikipedia.org/wiki/Cone + + :return: :math:`\frac{1}{3} \cdot area\_of\_base \cdot height` + >>> vol_cone(10, 3) 10.0 >>> vol_cone(1, 1) @@ -212,10 +232,12 @@ def vol_cone(area_of_base: float, height: float) -> float: 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 - :return (1/3) * pi * radius^2 * height + r""" + | Calculate the Volume of a Right Circular Cone. + | Wikipedia reference: https://en.wikipedia.org/wiki/Cone + + :return: :math:`\frac{1}{3} \cdot \pi \cdot radius^2 \cdot height` + >>> vol_right_circ_cone(2, 3) 12.566370614359172 >>> vol_right_circ_cone(0, 0) @@ -237,10 +259,12 @@ def vol_right_circ_cone(radius: float, height: float) -> float: def vol_prism(area_of_base: float, height: float) -> float: - """ - Calculate the Volume of a Prism. - Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry) - :return V = Bh + r""" + | Calculate the Volume of a Prism. + | Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry) + + :return: :math:`V = B \cdot h` + >>> vol_prism(10, 2) 20.0 >>> vol_prism(11, 1) @@ -264,10 +288,12 @@ def vol_prism(area_of_base: float, height: float) -> float: def vol_pyramid(area_of_base: float, height: float) -> float: - """ - Calculate the Volume of a Pyramid. - Wikipedia reference: https://en.wikipedia.org/wiki/Pyramid_(geometry) - :return (1/3) * Bh + r""" + | Calculate the Volume of a Pyramid. + | Wikipedia reference: https://en.wikipedia.org/wiki/Pyramid_(geometry) + + :return: :math:`\frac{1}{3} \cdot B \cdot h` + >>> vol_pyramid(10, 3) 10.0 >>> vol_pyramid(1.5, 3) @@ -291,10 +317,12 @@ def vol_pyramid(area_of_base: float, height: float) -> float: def vol_sphere(radius: float) -> float: - """ - Calculate the Volume of a Sphere. - Wikipedia reference: https://en.wikipedia.org/wiki/Sphere - :return (4/3) * pi * r^3 + r""" + | Calculate the Volume of a Sphere. + | Wikipedia reference: https://en.wikipedia.org/wiki/Sphere + + :return: :math:`\frac{4}{3} \cdot \pi \cdot r^3` + >>> vol_sphere(5) 523.5987755982989 >>> vol_sphere(1) @@ -315,10 +343,13 @@ def vol_sphere(radius: float) -> float: def vol_hemisphere(radius: float) -> float: - """Calculate the volume of a hemisphere - Wikipedia reference: https://en.wikipedia.org/wiki/Hemisphere - Other references: https://www.cuemath.com/geometry/hemisphere - :return 2/3 * pi * radius^3 + r""" + | Calculate the volume of a hemisphere + | Wikipedia reference: https://en.wikipedia.org/wiki/Hemisphere + | Other references: https://www.cuemath.com/geometry/hemisphere + + :return: :math:`\frac{2}{3} \cdot \pi \cdot radius^3` + >>> vol_hemisphere(1) 2.0943951023931953 >>> vol_hemisphere(7) @@ -339,9 +370,12 @@ def vol_hemisphere(radius: float) -> float: def vol_circular_cylinder(radius: float, height: float) -> float: - """Calculate the Volume of a Circular Cylinder. - Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder - :return pi * radius^2 * height + r""" + | Calculate the Volume of a Circular Cylinder. + | Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder + + :return: :math:`\pi \cdot radius^2 \cdot height` + >>> vol_circular_cylinder(1, 1) 3.141592653589793 >>> vol_circular_cylinder(4, 3) @@ -368,7 +402,9 @@ def vol_circular_cylinder(radius: float, height: float) -> float: def vol_hollow_circular_cylinder( inner_radius: float, outer_radius: float, height: float ) -> float: - """Calculate the Volume of a Hollow Circular Cylinder. + """ + Calculate the Volume of a Hollow Circular Cylinder. + >>> vol_hollow_circular_cylinder(1, 2, 3) 28.274333882308138 >>> vol_hollow_circular_cylinder(1.6, 2.6, 3.6) @@ -405,8 +441,9 @@ def vol_hollow_circular_cylinder( def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> float: - """Calculate the Volume of a Conical Frustum. - Wikipedia reference: https://en.wikipedia.org/wiki/Frustum + """ + | Calculate the Volume of a Conical Frustum. + | Wikipedia reference: https://en.wikipedia.org/wiki/Frustum >>> vol_conical_frustum(45, 7, 28) 48490.482608158454 @@ -443,9 +480,12 @@ def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> floa def vol_torus(torus_radius: float, tube_radius: float) -> float: - """Calculate the Volume of a Torus. - Wikipedia reference: https://en.wikipedia.org/wiki/Torus - :return 2pi^2 * torus_radius * tube_radius^2 + r""" + | Calculate the Volume of a Torus. + | Wikipedia reference: https://en.wikipedia.org/wiki/Torus + + :return: :math:`2 \pi^2 \cdot torus\_radius \cdot tube\_radius^2` + >>> vol_torus(1, 1) 19.739208802178716 >>> vol_torus(4, 3) @@ -471,8 +511,9 @@ def vol_torus(torus_radius: float, tube_radius: float) -> float: def vol_icosahedron(tri_side: float) -> float: - """Calculate the Volume of an Icosahedron. - Wikipedia reference: https://en.wikipedia.org/wiki/Regular_icosahedron + """ + | Calculate the Volume of an Icosahedron. + | Wikipedia reference: https://en.wikipedia.org/wiki/Regular_icosahedron >>> from math import isclose >>> isclose(vol_icosahedron(2.5), 34.088984228514256) From e9721aad59743d01e82582017884db528bad3e21 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 24 Dec 2024 06:06:59 +0300 Subject: [PATCH 2811/2908] Fix sphinx/build_docs warnings for physics/horizontal_projectile_motion (#12467) --- physics/horizontal_projectile_motion.py | 68 +++++++++++++++---------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/physics/horizontal_projectile_motion.py b/physics/horizontal_projectile_motion.py index 80f85a1b7146..60f21c2b39c4 100644 --- a/physics/horizontal_projectile_motion.py +++ b/physics/horizontal_projectile_motion.py @@ -1,15 +1,18 @@ """ Horizontal Projectile Motion problem in physics. + This algorithm solves a specific problem in which -the motion starts from the ground as can be seen below: - (v = 0) - * * - * * - * * - * * - * * - * * -GROUND GROUND +the motion starts from the ground as can be seen below:: + + (v = 0) + * * + * * + * * + * * + * * + * * + GROUND GROUND + For more info: https://en.wikipedia.org/wiki/Projectile_motion """ @@ -43,14 +46,17 @@ def check_args(init_velocity: float, angle: float) -> None: def horizontal_distance(init_velocity: float, angle: float) -> float: - """ + r""" Returns the horizontal distance that the object cover + Formula: - v_0^2 * sin(2 * alpha) - --------------------- - g - v_0 - initial velocity - alpha - angle + .. math:: + \frac{v_0^2 \cdot \sin(2 \alpha)}{g} + + v_0 - \text{initial velocity} + + \alpha - \text{angle} + >>> horizontal_distance(30, 45) 91.77 >>> horizontal_distance(100, 78) @@ -70,14 +76,17 @@ def horizontal_distance(init_velocity: float, angle: float) -> float: def max_height(init_velocity: float, angle: float) -> float: - """ + r""" Returns the maximum height that the object reach + Formula: - v_0^2 * sin^2(alpha) - -------------------- - 2g - v_0 - initial velocity - alpha - angle + .. math:: + \frac{v_0^2 \cdot \sin^2 (\alpha)}{2 g} + + v_0 - \text{initial velocity} + + \alpha - \text{angle} + >>> max_height(30, 45) 22.94 >>> max_height(100, 78) @@ -97,14 +106,17 @@ def max_height(init_velocity: float, angle: float) -> float: def total_time(init_velocity: float, angle: float) -> float: - """ + r""" Returns total time of the motion + Formula: - 2 * v_0 * sin(alpha) - -------------------- - g - v_0 - initial velocity - alpha - angle + .. math:: + \frac{2 v_0 \cdot \sin (\alpha)}{g} + + v_0 - \text{initial velocity} + + \alpha - \text{angle} + >>> total_time(30, 45) 4.33 >>> total_time(100, 78) @@ -125,6 +137,8 @@ def total_time(init_velocity: float, angle: float) -> float: def test_motion() -> None: """ + Test motion + >>> test_motion() """ v0, angle = 25, 20 From c36aaf0fbcbc0f1a6c82b689ee87e383104b9e96 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 24 Dec 2024 11:48:37 +0300 Subject: [PATCH 2812/2908] Fix sphinx/build_docs warnings for graphs/check_bipatrite (#12469) * Fix sphinx/build_docs warnings for graphs/check_bipatrite * Fix --- graphs/check_bipatrite.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/graphs/check_bipatrite.py b/graphs/check_bipatrite.py index 10b9cc965251..213f3f9480b5 100644 --- a/graphs/check_bipatrite.py +++ b/graphs/check_bipatrite.py @@ -6,16 +6,17 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool: Check if a graph is bipartite using depth-first search (DFS). Args: - graph: Adjacency list representing the graph. + `graph`: Adjacency list representing the graph. Returns: - True if bipartite, False otherwise. + ``True`` if bipartite, ``False`` otherwise. Checks if the graph can be divided into two sets of vertices, such that no two vertices within the same set are connected by an edge. Examples: - # FIXME: This test should pass. + + >>> # FIXME: This test should pass. >>> is_bipartite_dfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4]})) Traceback (most recent call last): ... @@ -37,7 +38,7 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool: ... KeyError: 0 - # FIXME: This test should fails with KeyError: 4. + >>> # FIXME: This test should fails with KeyError: 4. >>> is_bipartite_dfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]}) False >>> is_bipartite_dfs({0: [-1, 3], 1: [0, -2]}) @@ -51,7 +52,8 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool: ... KeyError: 0 - # FIXME: This test should fails with TypeError: list indices must be integers or... + >>> # FIXME: This test should fails with + >>> # TypeError: list indices must be integers or... >>> is_bipartite_dfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]}) True >>> is_bipartite_dfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]}) @@ -95,16 +97,17 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool: Check if a graph is bipartite using a breadth-first search (BFS). Args: - graph: Adjacency list representing the graph. + `graph`: Adjacency list representing the graph. Returns: - True if bipartite, False otherwise. + ``True`` if bipartite, ``False`` otherwise. Check if the graph can be divided into two sets of vertices, such that no two vertices within the same set are connected by an edge. Examples: - # FIXME: This test should pass. + + >>> # FIXME: This test should pass. >>> is_bipartite_bfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4]})) Traceback (most recent call last): ... @@ -126,7 +129,7 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool: ... KeyError: 0 - # FIXME: This test should fails with KeyError: 4. + >>> # FIXME: This test should fails with KeyError: 4. >>> is_bipartite_bfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]}) False >>> is_bipartite_bfs({0: [-1, 3], 1: [0, -2]}) @@ -140,7 +143,8 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool: ... KeyError: 0 - # FIXME: This test should fails with TypeError: list indices must be integers or... + >>> # FIXME: This test should fails with + >>> # TypeError: list indices must be integers or... >>> is_bipartite_bfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]}) True >>> is_bipartite_bfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]}) From ae28fa7fe362c8cb0238dbb6b237d42179e8beb3 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 24 Dec 2024 16:17:22 +0300 Subject: [PATCH 2813/2908] Fix sphinx/build_docs warnings for data_structures/binary_tree/mirror_binary_tree (#12470) --- .../binary_tree/mirror_binary_tree.py | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/data_structures/binary_tree/mirror_binary_tree.py b/data_structures/binary_tree/mirror_binary_tree.py index 62e2f08dd4e0..f6611d66d676 100644 --- a/data_structures/binary_tree/mirror_binary_tree.py +++ b/data_structures/binary_tree/mirror_binary_tree.py @@ -56,6 +56,8 @@ def mirror(self) -> Node: def make_tree_seven() -> Node: r""" Return a binary tree with 7 nodes that looks like this: + :: + 1 / \ 2 3 @@ -81,13 +83,15 @@ def make_tree_seven() -> Node: def make_tree_nine() -> Node: r""" Return a binary tree with 9 nodes that looks like this: - 1 - / \ - 2 3 - / \ \ - 4 5 6 - / \ \ - 7 8 9 + :: + + 1 + / \ + 2 3 + / \ \ + 4 5 6 + / \ \ + 7 8 9 >>> tree_nine = make_tree_nine() >>> len(tree_nine) @@ -117,23 +121,25 @@ def main() -> None: >>> tuple(tree.mirror()) (6, 3, 1, 9, 5, 2, 8, 4, 7) - nine_tree: - 1 - / \ - 2 3 - / \ \ - 4 5 6 - / \ \ - 7 8 9 - - The mirrored tree looks like this: + nine_tree:: + + 1 + / \ + 2 3 + / \ \ + 4 5 6 + / \ \ + 7 8 9 + + The mirrored tree looks like this:: + 1 - / \ - 3 2 - / / \ - 6 5 4 - / / \ - 9 8 7 + / \ + 3 2 + / / \ + 6 5 4 + / / \ + 9 8 7 """ trees = {"zero": Node(0), "seven": make_tree_seven(), "nine": make_tree_nine()} for name, tree in trees.items(): From eb652cf3d48fbd3b51450e95640ce5aec63a066b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2024 00:18:08 +0300 Subject: [PATCH 2814/2908] Bump astral-sh/setup-uv from 4 to 5 (#12445) Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4 to 5. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v4...v5) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/project_euler.yml | 4 ++-- .github/workflows/ruff.yml | 2 +- .github/workflows/sphinx.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6f308715cc2..62829b2b45a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: uv.lock diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml index 84c55335451e..8d51ad8850cf 100644 --- a/.github/workflows/project_euler.yml +++ b/.github/workflows/project_euler.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v5 - uses: actions/setup-python@v5 with: python-version: 3.x @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v5 - uses: actions/setup-python@v5 with: python-version: 3.x diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 2c6f92fcf7bf..cfe127b3521f 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -12,5 +12,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v5 - run: uvx ruff check --output-format=github . diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index e3e2ce81a95d..d02435d98028 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v5 - uses: actions/setup-python@v5 with: python-version: 3.13 From 5bef6ac9296c20250db7d494bbbc9c8bf4bfccdc Mon Sep 17 00:00:00 2001 From: Scarfinos <158184182+Scarfinos@users.noreply.github.com> Date: Fri, 27 Dec 2024 23:22:36 +0100 Subject: [PATCH 2815/2908] Improve coverage special_numbers (#12414) * Improve coverage bell_numbers * improve more function * Update hamming_numbers.py --------- Co-authored-by: Maxim Smolskiy --- maths/special_numbers/bell_numbers.py | 4 ++++ maths/special_numbers/hamming_numbers.py | 6 +++++- maths/special_numbers/harshad_numbers.py | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/maths/special_numbers/bell_numbers.py b/maths/special_numbers/bell_numbers.py index 5d99334d7add..d573e7a3962d 100644 --- a/maths/special_numbers/bell_numbers.py +++ b/maths/special_numbers/bell_numbers.py @@ -21,6 +21,10 @@ def bell_numbers(max_set_length: int) -> list[int]: list: A list of Bell numbers for sets of lengths from 0 to max_set_length. Examples: + >>> bell_numbers(-2) + Traceback (most recent call last): + ... + ValueError: max_set_length must be non-negative >>> bell_numbers(0) [1] >>> bell_numbers(1) diff --git a/maths/special_numbers/hamming_numbers.py b/maths/special_numbers/hamming_numbers.py index 4575119c8a95..a473cc93883b 100644 --- a/maths/special_numbers/hamming_numbers.py +++ b/maths/special_numbers/hamming_numbers.py @@ -13,6 +13,10 @@ def hamming(n_element: int) -> list: :param n_element: The number of elements on the list :return: The nth element of the list + >>> hamming(-5) + Traceback (most recent call last): + ... + ValueError: n_element should be a positive number >>> hamming(5) [1, 2, 3, 4, 5] >>> hamming(10) @@ -22,7 +26,7 @@ def hamming(n_element: int) -> list: """ n_element = int(n_element) if n_element < 1: - my_error = ValueError("a should be a positive number") + my_error = ValueError("n_element should be a positive number") raise my_error hamming_list = [1] diff --git a/maths/special_numbers/harshad_numbers.py b/maths/special_numbers/harshad_numbers.py index 61667adfa127..417120bd840e 100644 --- a/maths/special_numbers/harshad_numbers.py +++ b/maths/special_numbers/harshad_numbers.py @@ -11,6 +11,8 @@ def int_to_base(number: int, base: int) -> str: Where 'base' ranges from 2 to 36. Examples: + >>> int_to_base(0, 21) + '0' >>> int_to_base(23, 2) '10111' >>> int_to_base(58, 5) @@ -26,6 +28,10 @@ def int_to_base(number: int, base: int) -> str: Traceback (most recent call last): ... ValueError: 'base' must be between 2 and 36 inclusive + >>> int_to_base(-99, 16) + Traceback (most recent call last): + ... + ValueError: number must be a positive integer """ if base < 2 or base > 36: @@ -101,6 +107,8 @@ def harshad_numbers_in_base(limit: int, base: int) -> list[str]: Traceback (most recent call last): ... ValueError: 'base' must be between 2 and 36 inclusive + >>> harshad_numbers_in_base(-12, 6) + [] """ if base < 2 or base > 36: From 8bbe8caa256882ef2ebdbb3274e6f99f804716bd Mon Sep 17 00:00:00 2001 From: Scarfinos <158184182+Scarfinos@users.noreply.github.com> Date: Fri, 27 Dec 2024 23:40:35 +0100 Subject: [PATCH 2816/2908] Improve test coverage for matrix exponentiation (#12388) * #9943 : Adding coverage test for basic_graphs.py * #9943 : Adding coverage test for basic_graphs.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Solve problem of line too long * Improving coverage for matrix_exponentiation.py * fix more than one file * Update matrix_exponentiation.py * Update matrix_exponentiation.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- maths/matrix_exponentiation.py | 36 ++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index 7c37151c87ca..7cdac9d34674 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -39,6 +39,21 @@ def modular_exponentiation(a, b): def fibonacci_with_matrix_exponentiation(n, f1, f2): + """ + Returns the nth number of the Fibonacci sequence that + starts with f1 and f2 + Uses the matrix exponentiation + >>> fibonacci_with_matrix_exponentiation(1, 5, 6) + 5 + >>> fibonacci_with_matrix_exponentiation(2, 10, 11) + 11 + >>> fibonacci_with_matrix_exponentiation(13, 0, 1) + 144 + >>> fibonacci_with_matrix_exponentiation(10, 5, 9) + 411 + >>> fibonacci_with_matrix_exponentiation(9, 2, 3) + 89 + """ # Trivial Cases if n == 1: return f1 @@ -50,21 +65,34 @@ def fibonacci_with_matrix_exponentiation(n, f1, f2): def simple_fibonacci(n, f1, f2): + """ + Returns the nth number of the Fibonacci sequence that + starts with f1 and f2 + Uses the definition + >>> simple_fibonacci(1, 5, 6) + 5 + >>> simple_fibonacci(2, 10, 11) + 11 + >>> simple_fibonacci(13, 0, 1) + 144 + >>> simple_fibonacci(10, 5, 9) + 411 + >>> simple_fibonacci(9, 2, 3) + 89 + """ # Trivial 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 + f2, f1 = f1 + f2, f2 n -= 1 - return fn_1 + return f2 def matrix_exponentiation_time(): From 76471819bd5b9df6fe5fde4c763396412ce45edc Mon Sep 17 00:00:00 2001 From: Scarfinos <158184182+Scarfinos@users.noreply.github.com> Date: Fri, 27 Dec 2024 23:52:40 +0100 Subject: [PATCH 2817/2908] Improve test coverage for armstrong numbers (#12327) --- maths/special_numbers/armstrong_numbers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/maths/special_numbers/armstrong_numbers.py b/maths/special_numbers/armstrong_numbers.py index b2b4010a8f5b..a3cb69b814de 100644 --- a/maths/special_numbers/armstrong_numbers.py +++ b/maths/special_numbers/armstrong_numbers.py @@ -43,9 +43,9 @@ def armstrong_number(n: int) -> bool: def pluperfect_number(n: int) -> bool: """Return True if n is a pluperfect number or False if it is not - >>> all(armstrong_number(n) for n in PASSING) + >>> all(pluperfect_number(n) for n in PASSING) True - >>> any(armstrong_number(n) for n in FAILING) + >>> any(pluperfect_number(n) for n in FAILING) False """ if not isinstance(n, int) or n < 1: @@ -70,9 +70,9 @@ def pluperfect_number(n: int) -> bool: def narcissistic_number(n: int) -> bool: """Return True if n is a narcissistic number or False if it is not. - >>> all(armstrong_number(n) for n in PASSING) + >>> all(narcissistic_number(n) for n in PASSING) True - >>> any(armstrong_number(n) for n in FAILING) + >>> any(narcissistic_number(n) for n in FAILING) False """ if not isinstance(n, int) or n < 1: From 2ae9534fc68b1901d8056331aa2a4dbedc9d947e Mon Sep 17 00:00:00 2001 From: Anamaria Miranda Date: Sat, 28 Dec 2024 00:03:13 +0100 Subject: [PATCH 2818/2908] Added test to linear regression (#12353) --- machine_learning/linear_regression.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 839a5366d1cc..1d11e5a9cc2b 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -41,6 +41,14 @@ def run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta): :param theta : Feature vector (weight's for our model) ;param return : Updated Feature's, using curr_features - alpha_ * gradient(w.r.t. feature) + >>> import numpy as np + >>> data_x = np.array([[1, 2], [3, 4]]) + >>> data_y = np.array([5, 6]) + >>> len_data = len(data_x) + >>> alpha = 0.01 + >>> theta = np.array([0.1, 0.2]) + >>> run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta) + array([0.196, 0.343]) """ n = len_data @@ -58,6 +66,12 @@ def sum_of_square_error(data_x, data_y, len_data, theta): :param len_data : len of the dataset :param theta : contains the feature vector :return : sum of square error computed from given feature's + + Example: + >>> vc_x = np.array([[1.1], [2.1], [3.1]]) + >>> vc_y = np.array([1.2, 2.2, 3.2]) + >>> round(sum_of_square_error(vc_x, vc_y, 3, np.array([1])),3) + np.float64(0.005) """ prod = np.dot(theta, data_x.transpose()) prod -= data_y.transpose() @@ -93,6 +107,11 @@ def mean_absolute_error(predicted_y, original_y): :param predicted_y : contains the output of prediction (result vector) :param original_y : contains values of expected outcome :return : mean absolute error computed from given feature's + + >>> predicted_y = [3, -0.5, 2, 7] + >>> original_y = [2.5, 0.0, 2, 8] + >>> mean_absolute_error(predicted_y, original_y) + 0.5 """ total = sum(abs(y - predicted_y[i]) for i, y in enumerate(original_y)) return total / len(original_y) @@ -114,4 +133,7 @@ def main(): if __name__ == "__main__": + import doctest + + doctest.testmod() main() From 1652d05e9ee25d54eea5576976d537975dcad9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Arag=C3=A3o?= <101305675+juliaaragao@users.noreply.github.com> Date: Sat, 28 Dec 2024 00:26:29 +0100 Subject: [PATCH 2819/2908] adding test to electronics/electric_power.py (#12387) * test electric_power * Update electric_power.py * Update electric_power.py * Update electric_power.py * Update electric_power.py --------- Co-authored-by: Julia Co-authored-by: Maxim Smolskiy --- electronics/electric_power.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/electronics/electric_power.py b/electronics/electric_power.py index 8b92e320ace3..8e3454e39c3f 100644 --- a/electronics/electric_power.py +++ b/electronics/electric_power.py @@ -23,20 +23,22 @@ def electric_power(voltage: float, current: float, power: float) -> tuple: >>> electric_power(voltage=2, current=4, power=2) Traceback (most recent call last): ... - ValueError: Only one argument must be 0 + ValueError: Exactly one argument must be 0 >>> electric_power(voltage=0, current=0, power=2) Traceback (most recent call last): ... - ValueError: Only one argument must be 0 + ValueError: Exactly one argument must be 0 >>> electric_power(voltage=0, current=2, power=-4) Traceback (most recent call last): ... ValueError: Power cannot be negative in any electrical/electronics system >>> electric_power(voltage=2.2, current=2.2, power=0) Result(name='power', value=4.84) + >>> electric_power(current=0, power=6, voltage=2) + Result(name='current', value=3.0) """ if (voltage, current, power).count(0) != 1: - raise ValueError("Only one argument must be 0") + raise ValueError("Exactly one argument must be 0") elif power < 0: raise ValueError( "Power cannot be negative in any electrical/electronics system" @@ -48,7 +50,7 @@ def electric_power(voltage: float, current: float, power: float) -> tuple: elif power == 0: return Result("power", float(round(abs(voltage * current), 2))) else: - raise ValueError("Exactly one argument must be 0") + raise AssertionError if __name__ == "__main__": From 929b7dc057cd56f90b260cd665fb67886bcadeea Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sat, 28 Dec 2024 11:43:25 +0300 Subject: [PATCH 2820/2908] Fix Gaussian elimination pivoting (#11393) * updating DIRECTORY.md * Fix Gaussian elimination pivoting * Fix review issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: MaximSmolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../src/gaussian_elimination_pivoting.py | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/linear_algebra/src/gaussian_elimination_pivoting.py b/linear_algebra/src/gaussian_elimination_pivoting.py index ecaacce19a31..efc1ddd64a2e 100644 --- a/linear_algebra/src/gaussian_elimination_pivoting.py +++ b/linear_algebra/src/gaussian_elimination_pivoting.py @@ -22,40 +22,33 @@ def solve_linear_system(matrix: np.ndarray) -> np.ndarray: >>> solution = solve_linear_system(np.column_stack((A, B))) >>> np.allclose(solution, np.array([2., 3., -1.])) True - >>> solve_linear_system(np.array([[0, 0], [0, 0]], dtype=float)) - array([nan, nan]) + >>> solve_linear_system(np.array([[0, 0, 0]], dtype=float)) + Traceback (most recent call last): + ... + ValueError: Matrix is not square + >>> solve_linear_system(np.array([[0, 0, 0], [0, 0, 0]], dtype=float)) + Traceback (most recent call last): + ... + ValueError: Matrix is singular """ ab = np.copy(matrix) num_of_rows = ab.shape[0] num_of_columns = ab.shape[1] - 1 x_lst: list[float] = [] - # Lead element search - for column_num in range(num_of_rows): - for i in range(column_num, num_of_columns): - if abs(ab[i][column_num]) > abs(ab[column_num][column_num]): - ab[[column_num, i]] = ab[[i, column_num]] - if ab[column_num, column_num] == 0.0: - raise ValueError("Matrix is not correct") - else: - pass - if column_num != 0: - for i in range(column_num, num_of_rows): - ab[i, :] -= ( - ab[i, column_num - 1] - / ab[column_num - 1, column_num - 1] - * ab[column_num - 1, :] - ) + if num_of_rows != num_of_columns: + raise ValueError("Matrix is not square") - # Upper triangular matrix for column_num in range(num_of_rows): + # Lead element search for i in range(column_num, num_of_columns): if abs(ab[i][column_num]) > abs(ab[column_num][column_num]): ab[[column_num, i]] = ab[[i, column_num]] - if ab[column_num, column_num] == 0.0: - raise ValueError("Matrix is not correct") - else: - pass + + # Upper triangular matrix + if abs(ab[column_num, column_num]) < 1e-8: + raise ValueError("Matrix is singular") + if column_num != 0: for i in range(column_num, num_of_rows): ab[i, :] -= ( From b5c8fbf2e8254b53056b741aacce3842736ba177 Mon Sep 17 00:00:00 2001 From: Joy Khandelwal <116290658+joy-programs@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:21:28 +0530 Subject: [PATCH 2821/2908] Add additional doctests, fix grammatical errors for maths/perfect_number.py (#12477) * Add additional doctests for the perfect number algorithm and fix grammatical errors. Contributes to #9943 * Added newline at End of file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/perfect_number.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/maths/perfect_number.py b/maths/perfect_number.py index df6b6e3d91d8..52c816cc7895 100644 --- a/maths/perfect_number.py +++ b/maths/perfect_number.py @@ -46,17 +46,27 @@ def perfect(number: int) -> bool: False >>> perfect(-1) False + >>> perfect(33550336) # Large perfect number + True + >>> perfect(33550337) # Just above a large perfect number + False + >>> perfect(1) # Edge case: 1 is not a perfect number + False + >>> perfect("123") # String representation of a number + Traceback (most recent call last): + ... + ValueError: number must be an integer >>> perfect(12.34) Traceback (most recent call last): ... - ValueError: number must an integer + ValueError: number must be an integer >>> perfect("Hello") Traceback (most recent call last): ... - ValueError: number must an integer + ValueError: number must be an integer """ if not isinstance(number, int): - raise ValueError("number must an integer") + raise ValueError("number must be an integer") if number <= 0: return False return sum(i for i in range(1, number // 2 + 1) if number % i == 0) == number @@ -70,8 +80,7 @@ def perfect(number: int) -> bool: try: number = int(input("Enter a positive integer: ").strip()) except ValueError: - msg = "number must an integer" - print(msg) + msg = "number must be an integer" raise ValueError(msg) print(f"{number} is {'' if perfect(number) else 'not '}a Perfect Number.") From d496d5611fe266b7123b4a1f3efb4bcb7c2f38f2 Mon Sep 17 00:00:00 2001 From: Shi Entong <144505619+setbit123@users.noreply.github.com> Date: Sat, 28 Dec 2024 17:22:07 +0800 Subject: [PATCH 2822/2908] Remove inaccessible URL in computer_vision/README.md (#12383) Remove inaccessible URL. --- computer_vision/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/computer_vision/README.md b/computer_vision/README.md index 1657128fd25e..61462567b662 100644 --- a/computer_vision/README.md +++ b/computer_vision/README.md @@ -8,4 +8,3 @@ Image processing and computer vision are a little different from each other. Ima 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 1909f2272f11ebe7626d2dee78c11a91134e39e7 Mon Sep 17 00:00:00 2001 From: jperezr <122382210+MRJPEREZR@users.noreply.github.com> Date: Sat, 28 Dec 2024 11:03:24 +0100 Subject: [PATCH 2823/2908] adding doctests to maths/trapezoidal_rule.py (#12193) * adding doctests to trapezoidal_rule.py * adding algorithm delta-star transformation * updating DIRECTORY.md * delete file star_delta_transform.py * updating DIRECTORY.md * modified: ../DIRECTORY.md --------- Co-authored-by: MRJPEREZR --- maths/trapezoidal_rule.py | 48 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py index 9a4ddc8af66b..0186629ee378 100644 --- a/maths/trapezoidal_rule.py +++ b/maths/trapezoidal_rule.py @@ -5,13 +5,25 @@ method 1: "extended trapezoidal rule" +int(f) = dx/2 * (f1 + 2f2 + ... + fn) """ def method_1(boundary, steps): - # "extended trapezoidal rule" - # int(f) = dx/2 * (f1 + 2f2 + ... + fn) + """ + Apply the extended trapezoidal rule to approximate the integral of function f(x) + over the interval defined by 'boundary' with the number of 'steps'. + + Args: + boundary (list of floats): A list containing the start and end values [a, b]. + steps (int): The number of steps or subintervals. + Returns: + float: Approximation of the integral of f(x) over [a, b]. + Examples: + >>> method_1([0, 1], 10) + 0.3349999999999999 + """ h = (boundary[1] - boundary[0]) / steps a = boundary[0] b = boundary[1] @@ -26,13 +38,40 @@ def method_1(boundary, steps): def make_points(a, b, h): + """ + Generates points between 'a' and 'b' with step size 'h', excluding the end points. + Args: + a (float): Start value + b (float): End value + h (float): Step size + Examples: + >>> list(make_points(0, 10, 2.5)) + [2.5, 5.0, 7.5] + + >>> list(make_points(0, 10, 2)) + [2, 4, 6, 8] + + >>> list(make_points(1, 21, 5)) + [6, 11, 16] + + >>> list(make_points(1, 5, 2)) + [3] + + >>> list(make_points(1, 4, 3)) + [] + """ x = a + h - while x < (b - h): + while x <= (b - h): yield x x = x + h def f(x): # enter your function here + """ + Example: + >>> f(2) + 4 + """ y = (x - 0) * (x - 0) return y @@ -47,4 +86,7 @@ def main(): if __name__ == "__main__": + import doctest + + doctest.testmod() main() From 2b58ab040295fbbf2e463ba8cd77ad935d942968 Mon Sep 17 00:00:00 2001 From: Andrwaa <165920381+Andrwaa@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:17:48 +0100 Subject: [PATCH 2824/2908] compare-method added to Vector class in lib.py (#12448) * compare-method added to Vector class in lib.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updated lib.py with suggestions * Updated lib.py with suggestions * Updated lib.py with __eq__ method --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- linear_algebra/src/lib.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 5af6c62e3ad4..0d6a348475cd 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -46,7 +46,6 @@ class Vector: change_component(pos: int, value: float): changes specified component euclidean_length(): returns the euclidean length of the vector angle(other: Vector, deg: bool): returns the angle between two vectors - TODO: compare-operator """ def __init__(self, components: Collection[float] | None = None) -> None: @@ -96,6 +95,16 @@ def __sub__(self, other: Vector) -> Vector: else: # error case raise Exception("must have the same size") + def __eq__(self, other: object) -> bool: + """ + performs the comparison between two vectors + """ + if not isinstance(other, Vector): + return NotImplemented + if len(self) != len(other): + return False + return all(self.component(i) == other.component(i) for i in range(len(self))) + @overload def __mul__(self, other: float) -> Vector: ... From 2d68bb50e5f12532b5a0d616305c4f805d2b8ff9 Mon Sep 17 00:00:00 2001 From: KICH Yassine Date: Sun, 29 Dec 2024 12:56:36 +0100 Subject: [PATCH 2825/2908] Fix split function to handle trailing delimiters correctly (#12423) * Fix split function to handle trailing delimiters correctly * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update split.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- strings/split.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/strings/split.py b/strings/split.py index b62b86d2401f..ed194ec69c2f 100644 --- a/strings/split.py +++ b/strings/split.py @@ -14,6 +14,9 @@ def split(string: str, separator: str = " ") -> list: >>> split("12:43:39",separator = ":") ['12', '43', '39'] + + >>> split(";abbb;;c;", separator=';') + ['', 'abbb', '', 'c', ''] """ split_words = [] @@ -23,7 +26,7 @@ def split(string: str, separator: str = " ") -> list: if char == separator: split_words.append(string[last_index:index]) last_index = index + 1 - elif index + 1 == len(string): + if index + 1 == len(string): split_words.append(string[last_index : index + 1]) return split_words From 972a5c1e432e0a3fa9e990422318269219192a53 Mon Sep 17 00:00:00 2001 From: RajdeepBakolia2004 <144157867+RajdeepBakolia2004@users.noreply.github.com> Date: Sun, 29 Dec 2024 19:05:33 +0530 Subject: [PATCH 2826/2908] fixed the issue in strings/join.py (#12434) * fixed the issue in strings/join.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update join.py * Update join.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- strings/join.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/strings/join.py b/strings/join.py index 5c02f65a20ce..cdcc3a1377f4 100644 --- a/strings/join.py +++ b/strings/join.py @@ -24,6 +24,8 @@ def join(separator: str, separated: list[str]) -> str: 'a' >>> join(" ", ["You", "are", "amazing!"]) 'You are amazing!' + >>> join(",", ["", "", ""]) + ',,' This example should raise an exception for non-string elements: @@ -37,15 +39,33 @@ def join(separator: str, separated: list[str]) -> str: 'apple-banana-cherry' """ - joined = "" + # Check that all elements are strings for word_or_phrase in separated: + # If the element is not a string, raise an exception if not isinstance(word_or_phrase, str): raise Exception("join() accepts only strings") + + joined: str = "" + """ + The last element of the list is not followed by the separator. + So, we need to iterate through the list and join each element + with the separator except the last element. + """ + last_index: int = len(separated) - 1 + """ + Iterate through the list and join each element with the separator. + Except the last element, all other elements are followed by the separator. + """ + for word_or_phrase in separated[:last_index]: + # join the element with the separator. joined += word_or_phrase + separator - # Remove the trailing separator - # by stripping it from the result - return joined.strip(separator) + # If the list is not empty, join the last element. + if separated != []: + joined += separated[last_index] + + # Return the joined string. + return joined if __name__ == "__main__": From d9092d88dd8b47323d14f87025195e0c76fe7889 Mon Sep 17 00:00:00 2001 From: Sankalpa Sarkar <137193167+sanks011@users.noreply.github.com> Date: Sun, 29 Dec 2024 19:23:31 +0530 Subject: [PATCH 2827/2908] fixes requirements error (#12438) * fixes join.py action * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixes split.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed two requirements * Custom Implementation of join.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated join.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update split.py * Update join.py * Update join.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4cc83f44987d..b104505e01bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ beautifulsoup4 -fake_useragent +fake-useragent imageio keras lxml @@ -11,7 +11,7 @@ pillow requests rich scikit-learn -sphinx_pyproject +sphinx-pyproject statsmodels sympy tweepy From bfc804a41c6fb7f3c2e371b15d50ba4830bab3a7 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 29 Dec 2024 18:41:28 +0300 Subject: [PATCH 2828/2908] Fix sphinx/build_docs warnings for physics/newtons_second_law_of_motion (#12480) * Fix sphinx/build_docs warnings for physics/newtons_second_law_of_motion * Fix * Fix * Fix review issue --- physics/newtons_second_law_of_motion.py | 83 ++++++++++++++----------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/physics/newtons_second_law_of_motion.py b/physics/newtons_second_law_of_motion.py index 53fab6ce78b9..4149e2494f31 100644 --- a/physics/newtons_second_law_of_motion.py +++ b/physics/newtons_second_law_of_motion.py @@ -1,18 +1,22 @@ -""" -Description : -Newton's second law of motion pertains to the behavior of objects for which -all existing forces are not balanced. -The second law states that the acceleration of an object is dependent upon two variables -- the net force acting upon the object and the mass of the object. -The acceleration of an object depends directly -upon the net force acting upon the object, -and inversely upon the mass of the object. -As the force acting upon an object is increased, -the acceleration of the object is increased. -As the mass of an object is increased, the acceleration of the object is decreased. +r""" +Description: + Newton's second law of motion pertains to the behavior of objects for which + all existing forces are not balanced. + The second law states that the acceleration of an object is dependent upon + two variables - the net force acting upon the object and the mass of the object. + The acceleration of an object depends directly + upon the net force acting upon the object, + and inversely upon the mass of the object. + As the force acting upon an object is increased, + the acceleration of the object is increased. + As the mass of an object is increased, the acceleration of the object is decreased. + Source: https://www.physicsclassroom.com/class/newtlaws/Lesson-3/Newton-s-Second-Law -Formulation: Fnet = m • a -Diagrammatic Explanation: + +Formulation: F_net = m • a + +Diagrammatic Explanation:: + Forces are unbalanced | | @@ -26,35 +30,42 @@ / \ / \ / \ - __________________ ____ ________________ - |The acceleration | |The acceleration | - |depends directly | |depends inversely | - |on the net Force | |upon the object's | - |_________________| |mass_______________| -Units: -1 Newton = 1 kg X meters / (seconds^2) + __________________ ____________________ + | The acceleration | | The acceleration | + | depends directly | | depends inversely | + | on the net force | | upon the object's | + | | | mass | + |__________________| |____________________| + +Units: 1 Newton = 1 kg • meters/seconds^2 + How to use? -Inputs: - ___________________________________________________ - |Name | Units | Type | - |-------------|-------------------------|-----------| - |mass | (in kgs) | float | - |-------------|-------------------------|-----------| - |acceleration | (in meters/(seconds^2)) | float | - |_____________|_________________________|___________| - -Output: - ___________________________________________________ - |Name | Units | Type | - |-------------|-------------------------|-----------| - |force | (in Newtons) | float | - |_____________|_________________________|___________| + +Inputs:: + + ______________ _____________________ ___________ + | Name | Units | Type | + |--------------|---------------------|-----------| + | mass | in kgs | float | + |--------------|---------------------|-----------| + | acceleration | in meters/seconds^2 | float | + |______________|_____________________|___________| + +Output:: + + ______________ _______________________ ___________ + | Name | Units | Type | + |--------------|-----------------------|-----------| + | force | in Newtons | float | + |______________|_______________________|___________| """ def newtons_second_law_of_motion(mass: float, acceleration: float) -> float: """ + Calculates force from `mass` and `acceleration` + >>> newtons_second_law_of_motion(10, 10) 100 >>> newtons_second_law_of_motion(2.0, 1) From c93288389d220297f972137293f4565c62131516 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Dec 2024 18:16:45 +0100 Subject: [PATCH 2829/2908] [pre-commit.ci] pre-commit autoupdate (#12466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.3 → v0.8.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.3...v0.8.4) - [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.14.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.13.0...v1.14.0) * Update convert_number_to_words.py * Update convert_number_to_words.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- conversions/convert_number_to_words.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c8108ac55be..71ac72c29b5f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.3 + rev: v0.8.4 hooks: - id: ruff - id: ruff-format @@ -47,7 +47,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.13.0 + rev: v1.14.0 hooks: - id: mypy args: diff --git a/conversions/convert_number_to_words.py b/conversions/convert_number_to_words.py index dbab44c72e1f..6aa43738b9fe 100644 --- a/conversions/convert_number_to_words.py +++ b/conversions/convert_number_to_words.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import ClassVar, Literal +from typing import Literal class NumberingSystem(Enum): @@ -54,7 +54,7 @@ def max_value(cls, system: str) -> int: class NumberWords(Enum): - ONES: ClassVar[dict[int, str]] = { + ONES = { # noqa: RUF012 0: "", 1: "one", 2: "two", @@ -67,7 +67,7 @@ class NumberWords(Enum): 9: "nine", } - TEENS: ClassVar[dict[int, str]] = { + TEENS = { # noqa: RUF012 0: "ten", 1: "eleven", 2: "twelve", @@ -80,7 +80,7 @@ class NumberWords(Enum): 9: "nineteen", } - TENS: ClassVar[dict[int, str]] = { + TENS = { # noqa: RUF012 2: "twenty", 3: "thirty", 4: "forty", From bfb0447efb73dd049c6a56331cea36cb1345686b Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 29 Dec 2024 20:29:48 +0300 Subject: [PATCH 2830/2908] Fix sphinx/build_docs warnings for maths/zellers_congruence (#12481) * Fix sphinx/build_docs warnings for maths/zellers_congruence * Fix --- maths/zellers_congruence.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index 483fb000f86b..b958ed3b8659 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -4,13 +4,14 @@ def zeller(date_input: str) -> str: """ - Zellers Congruence Algorithm - Find the day of the week for nearly any Gregorian or Julian calendar date + | 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 + Validate out of range month: + >>> zeller('13-31-2010') Traceback (most recent call last): ... @@ -21,6 +22,7 @@ def zeller(date_input: str) -> str: ValueError: invalid literal for int() with base 10: '.2' Validate out of range date: + >>> zeller('01-33-2010') Traceback (most recent call last): ... @@ -31,30 +33,35 @@ def zeller(date_input: str) -> str: ValueError: invalid literal for int() with base 10: '.4' Validate second separator: + >>> zeller('01-31*2010') Traceback (most recent call last): ... ValueError: Date separator must be '-' or '/' Validate first separator: + >>> zeller('01^31-2010') Traceback (most recent call last): ... ValueError: Date separator 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 of date_input: + Test length of `date_input`: + >>> zeller('') Traceback (most recent call last): ... From ce036db2131626b86b94ab87854c82a9bc6c3d0e Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 29 Dec 2024 23:01:15 +0300 Subject: [PATCH 2831/2908] Fix sphinx/build_docs warnings for physics/speeds_of_gas_molecules (#12471) * Fix sphinx/build_docs warnings for physics/speeds_of_gas_molecules * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * Fix review issue * Fix * Fix * Fix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- physics/speeds_of_gas_molecules.py | 36 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/physics/speeds_of_gas_molecules.py b/physics/speeds_of_gas_molecules.py index a50d1c0f6d76..42f90a9fd6f3 100644 --- a/physics/speeds_of_gas_molecules.py +++ b/physics/speeds_of_gas_molecules.py @@ -4,43 +4,43 @@ distribution is a probability distribution that describes the distribution of speeds of particles in an ideal gas. -The distribution is given by the following equation: +The distribution is given by the following equation:: ------------------------------------------------- | f(v) = (M/2πRT)^(3/2) * 4πv^2 * e^(-Mv^2/2RT) | ------------------------------------------------- where: - f(v) is the fraction of molecules with a speed v - M is the molar mass of the gas in kg/mol - R is the gas constant - T is the absolute temperature + * ``f(v)`` is the fraction of molecules with a speed ``v`` + * ``M`` is the molar mass of the gas in kg/mol + * ``R`` is the gas constant + * ``T`` is the absolute temperature More information about the Maxwell-Boltzmann distribution can be found here: https://en.wikipedia.org/wiki/Maxwell%E2%80%93Boltzmann_distribution The average speed can be calculated by integrating the Maxwell-Boltzmann distribution -from 0 to infinity and dividing by the total number of molecules. The result is: +from 0 to infinity and dividing by the total number of molecules. The result is:: - --------------------- - | vavg = √(8RT/πM) | - --------------------- + ---------------------- + | v_avg = √(8RT/πM) | + ---------------------- The most probable speed is the speed at which the Maxwell-Boltzmann distribution is at its maximum. This can be found by differentiating the Maxwell-Boltzmann -distribution with respect to v and setting the result equal to zero. The result is: +distribution with respect to ``v`` and setting the result equal to zero. The result is:: - --------------------- - | vmp = √(2RT/M) | - --------------------- + ---------------------- + | v_mp = √(2RT/M) | + ---------------------- The root-mean-square speed is another measure of the average speed of the molecules in a gas. It is calculated by taking the square root -of the average of the squares of the speeds of the molecules. The result is: +of the average of the squares of the speeds of the molecules. The result is:: - --------------------- - | vrms = √(3RT/M) | - --------------------- + ---------------------- + | v_rms = √(3RT/M) | + ---------------------- Here we have defined functions to calculate the average and most probable speeds of molecules in a gas given the @@ -57,6 +57,7 @@ def avg_speed_of_molecule(temperature: float, molar_mass: float) -> float: and returns the average speed of a molecule in the gas (in m/s). Examples: + >>> avg_speed_of_molecule(273, 0.028) # nitrogen at 273 K 454.3488755020387 >>> avg_speed_of_molecule(300, 0.032) # oxygen at 300 K @@ -84,6 +85,7 @@ def mps_speed_of_molecule(temperature: float, molar_mass: float) -> float: and returns the most probable speed of a molecule in the gas (in m/s). Examples: + >>> mps_speed_of_molecule(273, 0.028) # nitrogen at 273 K 402.65620701908966 >>> mps_speed_of_molecule(300, 0.032) # oxygen at 300 K From 3622e940c9db74ebac06a5b12f83fd638d7c5511 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 29 Dec 2024 23:31:53 +0300 Subject: [PATCH 2832/2908] Fix sphinx/build_docs warnings for other (#12482) * Fix sphinx/build_docs warnings for other * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- other/bankers_algorithm.py | 16 +++-- other/davis_putnam_logemann_loveland.py | 94 ++++++++++++++----------- other/scoring_algorithm.py | 30 ++++---- 3 files changed, 77 insertions(+), 63 deletions(-) diff --git a/other/bankers_algorithm.py b/other/bankers_algorithm.py index d4254f479a4f..b1da851fc0f3 100644 --- a/other/bankers_algorithm.py +++ b/other/bankers_algorithm.py @@ -10,9 +10,10 @@ 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) + +| [Source] Wikipedia +| [Credit] Rosetta Code C implementation helped very much. +| (https://rosettacode.org/wiki/Banker%27s_algorithm) """ from __future__ import annotations @@ -75,7 +76,7 @@ def __available_resources(self) -> 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] + ``max_claim[i][j] - alloc_table[i][j] <= avail[j]`` """ return [ list(np.array(self.__maximum_claim_table[i]) - np.array(allocated_resource)) @@ -86,7 +87,9 @@ 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]} + + :Return: {0: [a: int, b: int], 1: [c: int, d: int]} + >>> index_control = BankersAlgorithm( ... test_claim_vector, test_allocated_res_table, test_maximum_claim_table ... )._BankersAlgorithm__need_index_manager() @@ -100,7 +103,8 @@ def __need_index_manager(self) -> dict[int, list[int]]: def main(self, **kwargs) -> None: """ Utilize various methods in this class to simulate the Banker's algorithm - Return: None + :Return: None + >>> BankersAlgorithm(test_claim_vector, test_allocated_res_table, ... test_maximum_claim_table).main(describe=True) Allocated Resource Table diff --git a/other/davis_putnam_logemann_loveland.py b/other/davis_putnam_logemann_loveland.py index 0f3100b1bc2e..e95bf371a817 100644 --- a/other/davis_putnam_logemann_loveland.py +++ b/other/davis_putnam_logemann_loveland.py @@ -17,13 +17,15 @@ class Clause: """ - A clause represented in Conjunctive Normal Form. - A clause is a set of literals, either complemented or otherwise. + | 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) + * {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 @@ -39,6 +41,7 @@ def __init__(self, literals: list[str]) -> None: def __str__(self) -> str: """ To print a clause as in Conjunctive Normal Form. + >>> str(Clause(["A1", "A2'", "A3"])) "{A1 , A2' , A3}" """ @@ -47,6 +50,7 @@ def __str__(self) -> str: def __len__(self) -> int: """ To print a clause as in Conjunctive Normal Form. + >>> len(Clause([])) 0 >>> len(Clause(["A1", "A2'", "A3"])) @@ -72,11 +76,13 @@ def assign(self, model: dict[str, bool | None]) -> None: def evaluate(self, model: dict[str, bool | None]) -> bool | None: """ 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. + 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 + "'" @@ -92,10 +98,10 @@ def evaluate(self, model: dict[str, bool | None]) -> bool | None: 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)) + | 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: Iterable[Clause]) -> None: @@ -107,7 +113,8 @@ def __init__(self, clauses: Iterable[Clause]) -> None: def __str__(self) -> str: """ To print a formula as in Conjunctive Normal Form. - str(Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])])) + + >>> 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) + "}" @@ -115,8 +122,8 @@ def __str__(self) -> str: def generate_clause() -> Clause: """ - Randomly generate a clause. - All literals have the name Ax, where x is an integer from 1 to 5. + | 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) @@ -149,11 +156,12 @@ def generate_formula() -> Formula: def generate_parameters(formula: Formula) -> tuple[list[Clause], list[str]]: """ - Return the clauses and symbols from a formula. - A symbol is the uncomplemented form of a literal. + | 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. + * 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) @@ -177,21 +185,20 @@ def find_pure_symbols( clauses: list[Clause], symbols: list[str], model: dict[str, bool | None] ) -> tuple[list[str], dict[str, bool | None]]: """ - 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. + | 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. + 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'] @@ -231,20 +238,21 @@ def find_unit_clauses( ) -> tuple[list[str], dict[str, bool | None]]: """ 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 + - 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. + 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'] @@ -278,16 +286,16 @@ def dpll_algorithm( clauses: list[Clause], symbols: list[str], model: dict[str, bool | None] ) -> tuple[bool | None, dict[str, bool | None] | None]: """ - Returns the model if the formula is satisfiable, else None + 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. + 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 diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py index af04f432e433..0185d7a2e0c0 100644 --- a/other/scoring_algorithm.py +++ b/other/scoring_algorithm.py @@ -1,25 +1,26 @@ """ -developed by: markmelnic -original repo: https://github.com/markmelnic/Scoring-Algorithm +| 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 +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 +:: + + 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] +``[0, 0, 1]`` """ @@ -97,10 +98,11 @@ def procentual_proximity( source_data: list[list[float]], weights: list[int] ) -> list[list[float]]: """ - 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 + | `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 >>> 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]] From 94b3777936101bcc592fc5ef143ac08ad49195e7 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 30 Dec 2024 00:35:34 +0300 Subject: [PATCH 2833/2908] Fix sphinx/build_docs warnings for linear_algebra (#12483) * Fix sphinx/build_docs warnings for linear_algebra/ * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- linear_algebra/gaussian_elimination.py | 28 +++++++++++++------ linear_algebra/lu_decomposition.py | 20 +++++++------ .../src/gaussian_elimination_pivoting.py | 7 +++-- linear_algebra/src/rank_of_matrix.py | 6 +++- linear_algebra/src/schur_complement.py | 13 +++++---- linear_algebra/src/transformations_2d.py | 14 ++++++---- 6 files changed, 54 insertions(+), 34 deletions(-) diff --git a/linear_algebra/gaussian_elimination.py b/linear_algebra/gaussian_elimination.py index 724773c0db98..6f4075b710fd 100644 --- a/linear_algebra/gaussian_elimination.py +++ b/linear_algebra/gaussian_elimination.py @@ -1,6 +1,6 @@ """ -Gaussian elimination method for solving a system of linear equations. -Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination +| Gaussian elimination method for solving a system of linear equations. +| Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination """ import numpy as np @@ -13,12 +13,17 @@ def retroactive_resolution( ) -> NDArray[float64]: """ This function performs a retroactive linear system resolution - for triangular matrix + for triangular matrix Examples: - 2x1 + 2x2 - 1x3 = 5 2x1 + 2x2 = -1 - 0x1 - 2x2 - 1x3 = -7 0x1 - 2x2 = -1 - 0x1 + 0x2 + 5x3 = 15 + 1. + * 2x1 + 2x2 - 1x3 = 5 + * 0x1 - 2x2 - 1x3 = -7 + * 0x1 + 0x2 + 5x3 = 15 + 2. + * 2x1 + 2x2 = -1 + * 0x1 - 2x2 = -1 + >>> gaussian_elimination([[2, 2, -1], [0, -2, -1], [0, 0, 5]], [[5], [-7], [15]]) array([[2.], [2.], @@ -45,9 +50,14 @@ def gaussian_elimination( 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 + 1. + * 1x1 - 4x2 - 2x3 = -2 + * 5x1 + 2x2 - 2x3 = -3 + * 1x1 - 1x2 + 0x3 = 4 + 2. + * 1x1 + 2x2 = 5 + * 5x1 + 2x2 = 5 + >>> gaussian_elimination([[1, -4, -2], [5, 2, -2], [1, -1, 0]], [[-2], [-3], [4]]) array([[ 2.3 ], [-1.7 ], diff --git a/linear_algebra/lu_decomposition.py b/linear_algebra/lu_decomposition.py index 3620674835cd..3d89b53a48fb 100644 --- a/linear_algebra/lu_decomposition.py +++ b/linear_algebra/lu_decomposition.py @@ -2,13 +2,14 @@ Lower-upper (LU) decomposition factors a matrix as a product of a lower triangular matrix and an upper triangular matrix. A square matrix has an LU decomposition under the following conditions: + - If the matrix is invertible, then it has an LU decomposition if and only - if all of its leading principal minors are non-zero (see - https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of - leading principal minors of a matrix). + if all of its leading principal minors are non-zero (see + https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of + leading principal minors of a matrix). - If the matrix is singular (i.e., not invertible) and it has a rank of k - (i.e., it has k linearly independent columns), then it has an LU - decomposition if its first k leading principal minors are non-zero. + (i.e., it has k linearly independent columns), then it has an LU + decomposition if its first k leading principal minors are non-zero. This algorithm will simply attempt to perform LU decomposition on any square matrix and raise an error if no such decomposition exists. @@ -25,6 +26,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray """ Perform LU decomposition on a given matrix and raises an error if the matrix isn't square or if no such decomposition exists + >>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]]) >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) >>> lower_mat @@ -45,7 +47,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray array([[ 4. , 3. ], [ 0. , -1.5]]) - # Matrix is not square + >>> # Matrix is not square >>> matrix = np.array([[2, -2, 1], [0, 1, 2]]) >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) Traceback (most recent call last): @@ -54,14 +56,14 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray [[ 2 -2 1] [ 0 1 2]] - # Matrix is invertible, but its first leading principal minor is 0 + >>> # Matrix is invertible, but its first leading principal minor is 0 >>> matrix = np.array([[0, 1], [1, 0]]) >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) Traceback (most recent call last): ... ArithmeticError: No LU decomposition exists - # Matrix is singular, but its first leading principal minor is 1 + >>> # Matrix is singular, but its first leading principal minor is 1 >>> matrix = np.array([[1, 0], [1, 0]]) >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) >>> lower_mat @@ -71,7 +73,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray array([[1., 0.], [0., 0.]]) - # Matrix is singular, but its first leading principal minor is 0 + >>> # Matrix is singular, but its first leading principal minor is 0 >>> matrix = np.array([[0, 1], [0, 1]]) >>> lower_mat, upper_mat = lower_upper_decomposition(matrix) Traceback (most recent call last): diff --git a/linear_algebra/src/gaussian_elimination_pivoting.py b/linear_algebra/src/gaussian_elimination_pivoting.py index efc1ddd64a2e..540f57b0cff6 100644 --- a/linear_algebra/src/gaussian_elimination_pivoting.py +++ b/linear_algebra/src/gaussian_elimination_pivoting.py @@ -6,17 +6,18 @@ def solve_linear_system(matrix: np.ndarray) -> np.ndarray: Solve a linear system of equations using Gaussian elimination with partial pivoting Args: - - matrix: Coefficient matrix with the last column representing the constants. + - `matrix`: Coefficient matrix with the last column representing the constants. Returns: - - Solution vector. + - Solution vector. Raises: - - ValueError: If the matrix is not correct (i.e., singular). + - ``ValueError``: If the matrix is not correct (i.e., singular). https://courses.engr.illinois.edu/cs357/su2013/lect.htm Lecture 7 Example: + >>> A = np.array([[2, 1, -1], [-3, -1, 2], [-2, 1, 2]], dtype=float) >>> B = np.array([8, -11, -3], dtype=float) >>> solution = solve_linear_system(np.column_stack((A, B))) diff --git a/linear_algebra/src/rank_of_matrix.py b/linear_algebra/src/rank_of_matrix.py index 7ff3c1699a69..2c4fe2a8d1da 100644 --- a/linear_algebra/src/rank_of_matrix.py +++ b/linear_algebra/src/rank_of_matrix.py @@ -8,11 +8,15 @@ def rank_of_matrix(matrix: list[list[int | float]]) -> int: """ Finds the rank of a matrix. + Args: - matrix: The matrix as a list of lists. + `matrix`: The matrix as a list of lists. + Returns: The rank of the matrix. + Example: + >>> matrix1 = [[1, 2, 3], ... [4, 5, 6], ... [7, 8, 9]] diff --git a/linear_algebra/src/schur_complement.py b/linear_algebra/src/schur_complement.py index 7c79bb70abfc..74ac75e3fce2 100644 --- a/linear_algebra/src/schur_complement.py +++ b/linear_algebra/src/schur_complement.py @@ -12,13 +12,14 @@ def schur_complement( ) -> np.ndarray: """ Schur complement of a symmetric matrix X given as a 2x2 block matrix - consisting of matrices A, B and C. - Matrix A must be quadratic and non-singular. - In case A is singular, a pseudo-inverse may be provided using - the pseudo_inv argument. + consisting of matrices `A`, `B` and `C`. + Matrix `A` must be quadratic and non-singular. + In case `A` is singular, a pseudo-inverse may be provided using + the `pseudo_inv` argument. + + | Link to Wiki: https://en.wikipedia.org/wiki/Schur_complement + | See also Convex Optimization - Boyd and Vandenberghe, A.5.5 - Link to Wiki: https://en.wikipedia.org/wiki/Schur_complement - See also Convex Optimization - Boyd and Vandenberghe, A.5.5 >>> import numpy as np >>> a = np.array([[1, 2], [2, 1]]) >>> b = np.array([[0, 3], [3, 0]]) diff --git a/linear_algebra/src/transformations_2d.py b/linear_algebra/src/transformations_2d.py index b4185cd2848f..5dee59024752 100644 --- a/linear_algebra/src/transformations_2d.py +++ b/linear_algebra/src/transformations_2d.py @@ -3,13 +3,15 @@ I have added the codes for reflection, projection, scaling and rotation 2D matrices. +.. code-block:: python + 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]] + 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 f45e392cf6e94259eca8c47b13cd3ae22bcd901e Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 30 Dec 2024 12:56:24 +0300 Subject: [PATCH 2834/2908] Fix sphinx/build_docs warnings for ciphers (#12485) * Fix sphinx/build_docs warnings for ciphers * Fix --- ciphers/autokey.py | 7 +- ciphers/caesar_cipher.py | 77 ++++++++++------- ciphers/decrypt_caesar_with_chi_squared.py | 99 +++++++++++----------- ciphers/enigma_machine2.py | 64 +++++++------- ciphers/rsa_factorization.py | 13 +-- ciphers/simple_keyword_cypher.py | 10 ++- ciphers/trifid_cipher.py | 25 +++--- 7 files changed, 170 insertions(+), 125 deletions(-) diff --git a/ciphers/autokey.py b/ciphers/autokey.py index 05d8c066b139..7751a32d7546 100644 --- a/ciphers/autokey.py +++ b/ciphers/autokey.py @@ -1,5 +1,6 @@ """ https://en.wikipedia.org/wiki/Autokey_cipher + An autokey cipher (also known as the autoclave cipher) is a cipher that incorporates the message (the plaintext) into the key. The key is generated from the message in some automated fashion, @@ -10,8 +11,9 @@ def encrypt(plaintext: str, key: str) -> str: """ - Encrypt a given plaintext (string) and key (string), returning the + Encrypt a given `plaintext` (string) and `key` (string), returning the encrypted ciphertext. + >>> encrypt("hello world", "coffee") 'jsqqs avvwo' >>> encrypt("coffee is good as python", "TheAlgorithms") @@ -74,8 +76,9 @@ def encrypt(plaintext: str, key: str) -> str: def decrypt(ciphertext: str, key: str) -> str: """ - Decrypt a given ciphertext (string) and key (string), returning the decrypted + Decrypt a given `ciphertext` (string) and `key` (string), returning the decrypted ciphertext. + >>> decrypt("jsqqs avvwo", "coffee") 'hello world' >>> decrypt("vvjfpk wj ohvp su ddylsv", "TheAlgorithms") diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index d19b9a337221..9c096fe8a7da 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -7,24 +7,29 @@ def encrypt(input_string: str, key: int, alphabet: str | None = 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 + + * `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 + + * `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 every character in the plain-text is shifted by a certain number known @@ -32,26 +37,28 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str: Example: Say we have the following message: - "Hello, captain" + ``Hello, captain`` And our alphabet is made up of lower and uppercase letters: - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + ``abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`` - And our shift is "2" + 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 + 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). + (``Z`` would shift to ``a`` then ``b`` and so on). - Our final message would be "Jgnnq, ecrvckp" + 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' @@ -85,23 +92,28 @@ def decrypt(input_string: str, key: int, alphabet: str | None = 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 + + * `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 + + * `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 @@ -110,27 +122,29 @@ def decrypt(input_string: str, key: int, alphabet: str | None = None) -> str: Example: Say we have the following cipher-text: - "Jgnnq, ecrvckp" + ``Jgnnq, ecrvckp`` And our alphabet is made up of lower and uppercase letters: - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + ``abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`` - And our shift is "2" + 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. + 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" + 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' @@ -150,41 +164,44 @@ def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str """ 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 + + * `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 + + * `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" - + 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" + ``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!" diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 10832203e531..fb95c0f90628 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -11,33 +11,31 @@ def decrypt_caesar_with_chi_squared( """ Basic Usage =========== + Arguments: - * ciphertext (str): the text to decode (encoded with the caesar cipher) + * `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_sensitive (bool): a boolean value: True if the case matters during - decryption, False if it doesn't + * `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_sensitive` (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 - ) + * 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) + 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 @@ -45,52 +43,57 @@ def decrypt_caesar_with_chi_squared( 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) + | 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. + 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. + 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) + ``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 + 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. - 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: - 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 item ``2``) + - let v be the chi-squared test result (referred to here as chi-squared + value/statistic) - 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 + (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. @@ -98,16 +101,16 @@ def decrypt_caesar_with_chi_squared( to be the decoded answer. 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!' ... ) # doctest: +NORMALIZE_WHITESPACE diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 163aa7172c11..e42fdd82ed41 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -1,14 +1,16 @@ """ -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 +| 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 +This module contains function ``enigma`` which emulates the famous Enigma machine from WWII. + Module includes: -- enigma function + +- ``enigma`` function - showcase of function usage -- 9 randomly generated rotors +- ``9`` randomly generated rotors - reflector (aka static rotor) - original alphabet @@ -73,7 +75,7 @@ def _validator( rotpos: RotorPositionT, rotsel: RotorSelectionT, pb: str ) -> tuple[RotorPositionT, RotorSelectionT, dict[str, str]]: """ - Checks if the values can be used for the 'enigma' function + Checks if the values can be used for the ``enigma`` function >>> _validator((1,1,1), (rotor1, rotor2, rotor3), 'POLAND') ((1, 1, 1), ('EGZWVONAHDCLFQMSIPJBYUKXTR', 'FOBHMDKEXQNRAULPGSJVTYICZW', \ @@ -83,7 +85,7 @@ def _validator( :param rotpos: rotor_positon :param rotsel: rotor_selection :param pb: plugb -> validated and transformed - :return: (rotpos, rotsel, pb) + :return: (`rotpos`, `rotsel`, `pb`) """ # Checks if there are 3 unique rotors @@ -118,9 +120,10 @@ def _plugboard(pbstring: str) -> dict[str, str]: >>> _plugboard('POLAND') {'P': 'O', 'O': 'P', 'L': 'A', 'A': 'L', 'N': 'D', 'D': 'N'} - In the code, 'pb' stands for 'plugboard' + 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 """ @@ -168,31 +171,34 @@ def enigma( plugb: str = "", ) -> str: """ - The only difference with real-world enigma is that I allowed string input. + 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) + + | How it works: + | (for every letter in the message) - Input letter goes into the plugboard. - If it is connected to another one, switch it. + 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. - - 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:: - example: - | ABCDEFGHIJKLMNOPQRSTUVWXYZ | e.g. F=D and D=F - | VKLEPDBGRNWTFCJOHQAMUZYIXS | + | 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. + There it is switched with paired symbol. + The reflector can be represented as ``2`` sets, each with half of the alphanet. + There are usually ``10`` pairs of letters. + + Example:: - Example: - | ABCDEFGHIJKLM | e.g. E is paired to X - | ZYXWVUTSRQPON | so when E goes in X goes out and vice versa + | 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 @@ -211,9 +217,9 @@ def enigma( :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 '') + :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 """ diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py index 0a358a4fc2d4..585b21fac856 100644 --- a/ciphers/rsa_factorization.py +++ b/ciphers/rsa_factorization.py @@ -3,8 +3,10 @@ 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 + +| 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. """ @@ -17,13 +19,14 @@ def rsafactor(d: int, e: int, n: int) -> list[int]: """ This function returns the factors of N, where p*q=N - Return: [p, q] + + 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. + encrypt messages. The pair (N, d) is the secret key or private key and is known only to the recipient - of encrypted messages. + of encrypted messages. >>> rsafactor(3, 16971, 25777) [149, 173] diff --git a/ciphers/simple_keyword_cypher.py b/ciphers/simple_keyword_cypher.py index 9dc624e7762c..bde137d826c3 100644 --- a/ciphers/simple_keyword_cypher.py +++ b/ciphers/simple_keyword_cypher.py @@ -1,9 +1,11 @@ def remove_duplicates(key: str) -> str: """ Removes duplicate alphabetic characters in a keyword (letter is ignored after its - first appearance). + first appearance). + :param key: Keyword to use :return: String with duplicates removed + >>> remove_duplicates('Hello World!!') 'Helo Wrd' """ @@ -18,6 +20,7 @@ def remove_duplicates(key: str) -> str: def create_cipher_map(key: str) -> dict[str, str]: """ Returns a cipher map given a keyword. + :param key: keyword to use :return: dictionary cipher map """ @@ -43,9 +46,11 @@ def create_cipher_map(key: str) -> dict[str, str]: def encipher(message: str, cipher_map: dict[str, str]) -> 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!!' """ @@ -55,9 +60,11 @@ def encipher(message: str, cipher_map: dict[str, str]) -> str: def decipher(message: str, cipher_map: dict[str, str]) -> 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!!' @@ -70,6 +77,7 @@ def decipher(message: str, cipher_map: dict[str, str]) -> str: def main() -> None: """ Handles I/O + :return: void """ message = input("Enter message to encode or decode: ").strip() diff --git a/ciphers/trifid_cipher.py b/ciphers/trifid_cipher.py index 16b9faf67688..9613cee0669d 100644 --- a/ciphers/trifid_cipher.py +++ b/ciphers/trifid_cipher.py @@ -22,7 +22,7 @@ def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str: """ - Arrange the triagram value of each letter of 'message_part' vertically and join + Arrange the triagram value of each letter of `message_part` vertically and join them horizontally. >>> __encrypt_part('ASK', TEST_CHARACTER_TO_NUMBER) @@ -65,8 +65,8 @@ def __prepare( """ A helper function that generates the triagrams and assigns each letter of the alphabet to its corresponding triagram and stores this in a dictionary - ("character_to_number" and "number_to_character") after confirming if the - alphabet's length is 27. + (`character_to_number` and `number_to_character`) after confirming if the + alphabet's length is ``27``. >>> test = __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxYZ+') >>> expected = ('IAMABOY','ABCDEFGHIJKLMNOPQRSTUVWXYZ+', @@ -75,24 +75,28 @@ def __prepare( True Testing with incomplete alphabet + >>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVw') Traceback (most recent call last): ... KeyError: 'Length of alphabet has to be 27.' Testing with extra long alphabets + >>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxyzzwwtyyujjgfd') Traceback (most recent call last): ... KeyError: 'Length of alphabet has to be 27.' Testing with punctuations that are not in the given alphabet + >>> __prepare('am i a boy?','abCdeFghijkLmnopqrStuVwxYZ+') Traceback (most recent call last): ... ValueError: Each message character has to be included in alphabet! Testing with numbers + >>> __prepare(500,'abCdeFghijkLmnopqrStuVwxYZ+') Traceback (most recent call last): ... @@ -130,9 +134,9 @@ def encrypt_message( PARAMETERS ---------- - * message: The message you want to encrypt. - * alphabet (optional): The characters to be used for the cipher . - * period (optional): The number of characters you want in a group whilst + * `message`: The message you want to encrypt. + * `alphabet` (optional): The characters to be used for the cipher . + * `period` (optional): The number of characters you want in a group whilst encrypting. >>> encrypt_message('I am a boy') @@ -169,20 +173,21 @@ def decrypt_message( decrypt_message =============== - Decrypts a trifid_cipher encrypted message . + Decrypts a trifid_cipher encrypted message. PARAMETERS ---------- - * message: The message you want to decrypt . - * alphabet (optional): The characters used for the cipher. - * period (optional): The number of characters used in grouping when it + * `message`: The message you want to decrypt. + * `alphabet` (optional): The characters used for the cipher. + * `period` (optional): The number of characters used in grouping when it was encrypted. >>> decrypt_message('BCDGBQY') 'IAMABOY' Decrypting with your own alphabet and period + >>> decrypt_message('FMJFVOISSUFTFPUFEQQC','FELIXMARDSTBCGHJKNOPQUVWYZ+',5) 'AIDETOILECIELTAIDERA' """ From 68b4c6b4793867126f71ebf2a399402b02472edb Mon Sep 17 00:00:00 2001 From: mahdi tavasoli Date: Mon, 30 Dec 2024 13:52:20 +0330 Subject: [PATCH 2835/2908] fix is_ip_v4_address_valid.py (#12394) * fix is_ip_v4_address_valid * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update is_ip_v4_address_valid.py --------- Co-authored-by: m.tavasoli Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- maths/is_ip_v4_address_valid.py | 37 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/maths/is_ip_v4_address_valid.py b/maths/is_ip_v4_address_valid.py index 0ae8e021ead1..305afabffed3 100644 --- a/maths/is_ip_v4_address_valid.py +++ b/maths/is_ip_v4_address_valid.py @@ -1,13 +1,15 @@ """ +wiki: https://en.wikipedia.org/wiki/IPv4 + Is IP v4 address valid? A valid IP address must be four octets in the form of A.B.C.D, -where A,B,C and D are numbers from 0-254 -for example: 192.168.23.1, 172.254.254.254 are valid IP address - 192.168.255.0, 255.192.3.121 are invalid IP address +where A, B, C and D are numbers from 0-255 +for example: 192.168.23.1, 172.255.255.255 are valid IP address + 192.168.256.0, 256.192.3.121 are invalid IP address """ -def is_ip_v4_address_valid(ip_v4_address: str) -> bool: +def is_ip_v4_address_valid(ip: str) -> bool: """ print "Valid IP address" If IP is valid. or @@ -16,13 +18,13 @@ def is_ip_v4_address_valid(ip_v4_address: str) -> bool: >>> is_ip_v4_address_valid("192.168.0.23") True - >>> is_ip_v4_address_valid("192.255.15.8") + >>> is_ip_v4_address_valid("192.256.15.8") False >>> is_ip_v4_address_valid("172.100.0.8") True - >>> is_ip_v4_address_valid("254.255.0.255") + >>> is_ip_v4_address_valid("255.256.0.256") False >>> is_ip_v4_address_valid("1.2.33333333.4") @@ -45,12 +47,29 @@ def is_ip_v4_address_valid(ip_v4_address: str) -> bool: >>> is_ip_v4_address_valid("1.2.3.") False + + >>> is_ip_v4_address_valid("1.2.3.05") + False """ - octets = [int(i) for i in ip_v4_address.split(".") if i.isdigit()] - return len(octets) == 4 and all(0 <= int(octet) <= 254 for octet in octets) + octets = ip.split(".") + if len(octets) != 4: + return False + + for octet in octets: + if not octet.isdigit(): + return False + + number = int(octet) + if len(str(number)) != len(octet): + return False + + if not 0 <= number <= 255: + return False + + return True if __name__ == "__main__": ip = input().strip() valid_or_invalid = "valid" if is_ip_v4_address_valid(ip) else "invalid" - print(f"{ip} is a {valid_or_invalid} IP v4 address.") + print(f"{ip} is a {valid_or_invalid} IPv4 address.") From 2ca96b7c8ec2134a7282bed13f1cc93358c13c45 Mon Sep 17 00:00:00 2001 From: jperezr <122382210+MRJPEREZR@users.noreply.github.com> Date: Mon, 30 Dec 2024 11:37:21 +0100 Subject: [PATCH 2836/2908] current_stock_price test added (#12390) * adding test to web_programming/current_stock_price * adding test to web_programming/current_stock_price * Update current_stock_price.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update current_stock_price.py --------- Co-authored-by: Maxim Smolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- web_programming/current_stock_price.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index 9567c05b0558..d0a65e9aac84 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -1,14 +1,30 @@ import requests from bs4 import BeautifulSoup +""" +Get the HTML code of finance yahoo and select the current qsp-price +Current AAPL stock price is 228.43 +Current AMZN stock price is 201.85 +Current IBM stock price is 210.30 +Current GOOG stock price is 177.86 +Current MSFT stock price is 414.82 +Current ORCL stock price is 188.87 +""" + def stock_price(symbol: str = "AAPL") -> str: + """ + >>> stock_price("EEEE") + '-' + >>> isinstance(float(stock_price("GOOG")),float) + True + """ url = f"https://finance.yahoo.com/quote/%7Bsymbol%7D?p={symbol}" yahoo_finance_source = requests.get( url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10 ).text soup = BeautifulSoup(yahoo_finance_source, "html.parser") - specific_fin_streamer_tag = soup.find("fin-streamer", {"data-test": "qsp-price"}) + specific_fin_streamer_tag = soup.find("fin-streamer", {"data-testid": "qsp-price"}) if specific_fin_streamer_tag: text = specific_fin_streamer_tag.get_text() @@ -18,5 +34,9 @@ def stock_price(symbol: str = "AAPL") -> str: # Search for the symbol at https://finance.yahoo.com/lookup if __name__ == "__main__": + from doctest import testmod + + testmod() + for symbol in "AAPL AMZN IBM GOOG MSFT ORCL".split(): print(f"Current {symbol:<4} stock price is {stock_price(symbol):>8}") From 24923ee635973a05f7713dd672fea07361fa0466 Mon Sep 17 00:00:00 2001 From: Shikhar Maheshwari <83123897+shikhar-sm@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:21:10 +0530 Subject: [PATCH 2837/2908] Add doctest to maths/numerical_analysis/intersection.py (#12148) --- maths/numerical_analysis/intersection.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/maths/numerical_analysis/intersection.py b/maths/numerical_analysis/intersection.py index 826c0ead0a00..325abeaca996 100644 --- a/maths/numerical_analysis/intersection.py +++ b/maths/numerical_analysis/intersection.py @@ -42,6 +42,11 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl def f(x: float) -> float: + """ + function is f(x) = x^3 - 2x - 5 + >>> f(2) + -1.0 + """ return math.pow(x, 3) - (2 * x) - 5 From da587d06ac88e338e7db8f10fa8ca2ae556e7bae Mon Sep 17 00:00:00 2001 From: Sankabapur <152031570+Parthjhalani07@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:29:03 +0530 Subject: [PATCH 2838/2908] Added doctest to /maths/power_using_recursion.py (#11994) --- maths/power_using_recursion.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/maths/power_using_recursion.py b/maths/power_using_recursion.py index 29283ca0f67c..eb775b161ae8 100644 --- a/maths/power_using_recursion.py +++ b/maths/power_using_recursion.py @@ -38,6 +38,14 @@ def power(base: int, exponent: int) -> float: Traceback (most recent call last): ... RecursionError: maximum recursion depth exceeded + >>> power(0, 0) + 1 + >>> power(0, 1) + 0 + >>> power(5,6) + 15625 + >>> power(23, 12) + 21914624432020321 """ return base * power(base, (exponent - 1)) if exponent else 1 From 493a7c153c1ca1805e60c109842ee1a1ee63cde2 Mon Sep 17 00:00:00 2001 From: Jeel Rupapara Date: Mon, 30 Dec 2024 16:40:44 +0530 Subject: [PATCH 2839/2908] feat: add testcase of assemble_transformation (#11810) --- strings/min_cost_string_conversion.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index 93791e2a7ed3..87eb5189e16a 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -91,6 +91,14 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: >>> y1 = len(ops1[0]) - 1 >>> assemble_transformation(ops1, x1, y1) [] + + >>> ops2 = [['0', 'I1', 'I2', 'I3'], + ... ['D1', 'C1', 'I2', 'I3'], + ... ['D2', 'D2', 'R23', 'R23']] + >>> x2 = len(ops2) - 1 + >>> y2 = len(ops2[0]) - 1 + >>> assemble_transformation(ops2, x2, y2) + ['C1', 'I2', 'R23'] """ if i == 0 and j == 0: return [] From 7fa9b4bf1bc9822517bb0046aebc2e8b2997d3e1 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 30 Dec 2024 14:52:03 +0300 Subject: [PATCH 2840/2908] Fix sphinx/build_docs warnings for dynamic_programming (#12484) * Fix sphinx/build_docs warnings for dynamic_programming * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * Fix * Fix * Fix * Fix * Fix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/all_construct.py | 7 +- dynamic_programming/combination_sum_iv.py | 23 +- dynamic_programming/fizz_buzz.py | 11 +- dynamic_programming/knapsack.py | 40 ++-- .../longest_common_substring.py | 14 +- .../longest_increasing_subsequence.py | 13 +- .../matrix_chain_multiplication.py | 89 ++++---- dynamic_programming/max_product_subarray.py | 3 +- .../minimum_squares_to_represent_a_number.py | 1 + dynamic_programming/regex_match.py | 22 +- dynamic_programming/rod_cutting.py | 83 ++++--- dynamic_programming/subset_generation.py | 63 +++--- dynamic_programming/viterbi.py | 212 ++++++++---------- 13 files changed, 295 insertions(+), 286 deletions(-) diff --git a/dynamic_programming/all_construct.py b/dynamic_programming/all_construct.py index 5d585fc7fcec..ca00f2beb06a 100644 --- a/dynamic_programming/all_construct.py +++ b/dynamic_programming/all_construct.py @@ -8,9 +8,10 @@ def all_construct(target: str, word_bank: list[str] | None = None) -> list[list[str]]: """ - returns the list containing all the possible - combinations a string(target) can be constructed from - the given list of substrings(word_bank) + returns the list containing all the possible + combinations a string(`target`) can be constructed from + the given list of substrings(`word_bank`) + >>> all_construct("hello", ["he", "l", "o"]) [['he', 'l', 'l', 'o']] >>> all_construct("purple",["purp","p","ur","le","purpl"]) diff --git a/dynamic_programming/combination_sum_iv.py b/dynamic_programming/combination_sum_iv.py index 113c06a27a9e..ed8dcd88e6fd 100644 --- a/dynamic_programming/combination_sum_iv.py +++ b/dynamic_programming/combination_sum_iv.py @@ -1,24 +1,25 @@ """ Question: -You are given an array of distinct integers and you have to tell how many -different ways of selecting the elements from the array are there such that -the sum of chosen elements is equal to the target number tar. + You are given an array of distinct integers and you have to tell how many + different ways of selecting the elements from the array are there such that + the sum of chosen elements is equal to the target number tar. Example Input: -N = 3 -target = 5 -array = [1, 2, 5] + * N = 3 + * target = 5 + * array = [1, 2, 5] Output: -9 + 9 Approach: -The basic idea is to go over recursively to find the way such that the sum -of chosen elements is “tar”. For every element, we have two choices - 1. Include the element in our set of chosen elements. - 2. Don't include the element in our set of chosen elements. + The basic idea is to go over recursively to find the way such that the sum + of chosen elements is `target`. For every element, we have two choices + + 1. Include the element in our set of chosen elements. + 2. Don't include the element in our set of chosen elements. """ diff --git a/dynamic_programming/fizz_buzz.py b/dynamic_programming/fizz_buzz.py index e29116437a93..0cb48897875b 100644 --- a/dynamic_programming/fizz_buzz.py +++ b/dynamic_programming/fizz_buzz.py @@ -3,11 +3,12 @@ def fizz_buzz(number: int, iterations: int) -> str: """ - Plays FizzBuzz. - Prints Fizz if number is a multiple of 3. - Prints Buzz if its a multiple of 5. - Prints FizzBuzz if its a multiple of both 3 and 5 or 15. - Else Prints The Number Itself. + | Plays FizzBuzz. + | Prints Fizz if number is a multiple of ``3``. + | Prints Buzz if its a multiple of ``5``. + | Prints FizzBuzz if its a multiple of both ``3`` and ``5`` or ``15``. + | Else Prints The Number Itself. + >>> fizz_buzz(1,7) '1 2 Fizz 4 Buzz Fizz 7 ' >>> fizz_buzz(1,0) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 489b5ada450a..28c5b19dbe36 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -11,7 +11,7 @@ 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 + F is a 2D array with ``-1`` s filled up """ global f # a global dp table for knapsack if f[i][j] < 0: @@ -45,22 +45,24 @@ def knapsack_with_example_solution(w: int, wt: list, val: list): 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 i-th item. - val: list, the vector of values for all items where val[i] is the value - of the i-th item + * `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 ``i``-th item. + * `val`: list, the vector of values for all items where ``val[i]`` is the value + of the ``i``-th 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. + + * `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]) @@ -104,19 +106,19 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set): 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. + * `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 + ``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). diff --git a/dynamic_programming/longest_common_substring.py b/dynamic_programming/longest_common_substring.py index e2f944a5e336..ea5233eb2d17 100644 --- a/dynamic_programming/longest_common_substring.py +++ b/dynamic_programming/longest_common_substring.py @@ -1,15 +1,19 @@ """ -Longest Common Substring Problem Statement: Given two sequences, find the -longest common substring present in both of them. A substring is -necessarily continuous. -Example: "abcdef" and "xabded" have two longest common substrings, "ab" or "de". -Therefore, algorithm should return any one of them. +Longest Common Substring Problem Statement: + Given two sequences, find the + longest common substring present in both of them. A substring is + necessarily continuous. + +Example: + ``abcdef`` and ``xabded`` have two longest common substrings, ``ab`` or ``de``. + Therefore, algorithm should return any one of them. """ def longest_common_substring(text1: str, text2: str) -> str: """ Finds the longest common substring between two strings. + >>> longest_common_substring("", "") '' >>> longest_common_substring("a","") diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index 2a78e2e7ad1d..d839757f6da5 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -4,11 +4,13 @@ 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 +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 """ from __future__ import annotations @@ -17,6 +19,7 @@ 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]) diff --git a/dynamic_programming/matrix_chain_multiplication.py b/dynamic_programming/matrix_chain_multiplication.py index da6e525ce816..10e136b9f0db 100644 --- a/dynamic_programming/matrix_chain_multiplication.py +++ b/dynamic_programming/matrix_chain_multiplication.py @@ -1,42 +1,48 @@ """ -Find the minimum number of multiplications needed to multiply chain of matrices. -Reference: https://www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/ +| Find the minimum number of multiplications needed to multiply chain of matrices. +| Reference: https://www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/ -The algorithm has interesting real-world applications. Example: -1. Image transformations in Computer Graphics as images are composed of matrix. -2. Solve complex polynomial equations in the field of algebra using least processing - power. -3. Calculate overall impact of macroeconomic decisions as economic equations involve a - number of variables. -4. Self-driving car navigation can be made more accurate as matrix multiplication can - accurately determine position and orientation of obstacles in short time. +The algorithm has interesting real-world applications. -Python doctests can be run with the following command: -python -m doctest -v matrix_chain_multiply.py +Example: + 1. Image transformations in Computer Graphics as images are composed of matrix. + 2. Solve complex polynomial equations in the field of algebra using least processing + power. + 3. Calculate overall impact of macroeconomic decisions as economic equations involve a + number of variables. + 4. Self-driving car navigation can be made more accurate as matrix multiplication can + accurately determine position and orientation of obstacles in short time. -Given a sequence arr[] that represents chain of 2D matrices such that the dimension of -the ith matrix is arr[i-1]*arr[i]. -So suppose arr = [40, 20, 30, 10, 30] means we have 4 matrices of dimensions -40*20, 20*30, 30*10 and 10*30. +Python doctests can be run with the following command:: -matrix_chain_multiply() returns an integer denoting minimum number of multiplications to -multiply the chain. + python -m doctest -v matrix_chain_multiply.py + +Given a sequence ``arr[]`` that represents chain of 2D matrices such that the dimension +of the ``i`` th matrix is ``arr[i-1]*arr[i]``. +So suppose ``arr = [40, 20, 30, 10, 30]`` means we have ``4`` matrices of dimensions +``40*20``, ``20*30``, ``30*10`` and ``10*30``. + +``matrix_chain_multiply()`` returns an integer denoting minimum number of +multiplications to multiply the chain. We do not need to perform actual multiplication here. We only need to decide the order in which to perform the multiplication. Hints: -1. Number of multiplications (ie cost) to multiply 2 matrices -of size m*p and p*n is m*p*n. -2. Cost of matrix multiplication is associative ie (M1*M2)*M3 != M1*(M2*M3) -3. Matrix multiplication is not commutative. So, M1*M2 does not mean M2*M1 can be done. -4. To determine the required order, we can try different combinations. + 1. Number of multiplications (ie cost) to multiply ``2`` matrices + of size ``m*p`` and ``p*n`` is ``m*p*n``. + 2. Cost of matrix multiplication is not associative ie ``(M1*M2)*M3 != M1*(M2*M3)`` + 3. Matrix multiplication is not commutative. So, ``M1*M2`` does not mean ``M2*M1`` + can be done. + 4. To determine the required order, we can try different combinations. + So, this problem has overlapping sub-problems and can be solved using recursion. We use Dynamic Programming for optimal time complexity. Example input: -arr = [40, 20, 30, 10, 30] -output: 26000 + ``arr = [40, 20, 30, 10, 30]`` +output: + ``26000`` """ from collections.abc import Iterator @@ -50,25 +56,25 @@ def matrix_chain_multiply(arr: list[int]) -> int: Find the minimum number of multiplcations required to multiply the chain of matrices Args: - arr: The input array of integers. + `arr`: The input array of integers. Returns: Minimum number of multiplications needed to multiply the chain Examples: - >>> matrix_chain_multiply([1, 2, 3, 4, 3]) - 30 - >>> matrix_chain_multiply([10]) - 0 - >>> matrix_chain_multiply([10, 20]) - 0 - >>> matrix_chain_multiply([19, 2, 19]) - 722 - >>> matrix_chain_multiply(list(range(1, 100))) - 323398 - - # >>> matrix_chain_multiply(list(range(1, 251))) - # 2626798 + + >>> matrix_chain_multiply([1, 2, 3, 4, 3]) + 30 + >>> matrix_chain_multiply([10]) + 0 + >>> matrix_chain_multiply([10, 20]) + 0 + >>> matrix_chain_multiply([19, 2, 19]) + 722 + >>> matrix_chain_multiply(list(range(1, 100))) + 323398 + >>> # matrix_chain_multiply(list(range(1, 251))) + # 2626798 """ if len(arr) < 2: return 0 @@ -93,8 +99,10 @@ def matrix_chain_multiply(arr: list[int]) -> int: def matrix_chain_order(dims: list[int]) -> int: """ Source: https://en.wikipedia.org/wiki/Matrix_chain_multiplication + The dynamic programming solution is faster than cached the recursive solution and can handle larger inputs. + >>> matrix_chain_order([1, 2, 3, 4, 3]) 30 >>> matrix_chain_order([10]) @@ -105,8 +113,7 @@ def matrix_chain_order(dims: list[int]) -> int: 722 >>> matrix_chain_order(list(range(1, 100))) 323398 - - # >>> matrix_chain_order(list(range(1, 251))) # Max before RecursionError is raised + >>> # matrix_chain_order(list(range(1, 251))) # Max before RecursionError is raised # 2626798 """ diff --git a/dynamic_programming/max_product_subarray.py b/dynamic_programming/max_product_subarray.py index 425859bc03e3..6f4f38e38942 100644 --- a/dynamic_programming/max_product_subarray.py +++ b/dynamic_programming/max_product_subarray.py @@ -1,9 +1,10 @@ def max_product_subarray(numbers: list[int]) -> int: """ Returns the maximum product that can be obtained by multiplying a - contiguous subarray of the given integer list `nums`. + contiguous subarray of the given integer list `numbers`. Example: + >>> max_product_subarray([2, 3, -2, 4]) 6 >>> max_product_subarray((-2, 0, -1)) diff --git a/dynamic_programming/minimum_squares_to_represent_a_number.py b/dynamic_programming/minimum_squares_to_represent_a_number.py index bf5849f5bcb3..98c0602fa831 100644 --- a/dynamic_programming/minimum_squares_to_represent_a_number.py +++ b/dynamic_programming/minimum_squares_to_represent_a_number.py @@ -5,6 +5,7 @@ def minimum_squares_to_represent_a_number(number: int) -> int: """ Count the number of minimum squares to represent a number + >>> minimum_squares_to_represent_a_number(25) 1 >>> minimum_squares_to_represent_a_number(37) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index 200a882831c0..e94d82093c8b 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -1,23 +1,25 @@ """ Regex matching check if a text matches pattern or not. Pattern: - '.' Matches any single character. - '*' Matches zero or more of the preceding element. + + 1. ``.`` Matches any single character. + 2. ``*`` Matches zero or more of the preceding element. + More info: https://medium.com/trick-the-interviwer/regular-expression-matching-9972eb74c03 """ def recursive_match(text: str, pattern: str) -> bool: - """ + r""" Recursive matching algorithm. - Time complexity: O(2 ^ (|text| + |pattern|)) - Space complexity: Recursion depth is O(|text| + |pattern|). + | Time complexity: O(2^(\|text\| + \|pattern\|)) + | Space complexity: Recursion depth is O(\|text\| + \|pattern\|). :param text: Text to match. :param pattern: Pattern to match. - :return: True if text matches pattern, False otherwise. + :return: ``True`` if `text` matches `pattern`, ``False`` otherwise. >>> recursive_match('abc', 'a.c') True @@ -48,15 +50,15 @@ def recursive_match(text: str, pattern: str) -> bool: def dp_match(text: str, pattern: str) -> bool: - """ + r""" Dynamic programming matching algorithm. - Time complexity: O(|text| * |pattern|) - Space complexity: O(|text| * |pattern|) + | Time complexity: O(\|text\| * \|pattern\|) + | Space complexity: O(\|text\| * \|pattern\|) :param text: Text to match. :param pattern: Pattern to match. - :return: True if text matches pattern, False otherwise. + :return: ``True`` if `text` matches `pattern`, ``False`` otherwise. >>> dp_match('abc', 'a.c') True diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index f80fa440ae86..d12c759dc928 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -1,7 +1,7 @@ """ 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 + 1. A naive recursive implementation which has an exponential runtime + 2. Two dynamic programming implementations which have quadratic runtime 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 @@ -20,18 +20,21 @@ def naive_cut_rod_recursive(n: int, prices: list): 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`` + --------- + + * `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 + + 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]) @@ -54,28 +57,30 @@ 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 + ``_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. + * `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 + + 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]) @@ -94,16 +99,18 @@ def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): 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`` + --------- + + * `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 + + The maximum revenue obtainable for a rod of length `n` given the list of prices for each piece. """ if max_rev[n] >= 0: @@ -130,18 +137,21 @@ def bottom_up_cut_rod(n: int, prices: list): 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`` + --------- + + * `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 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]) @@ -168,13 +178,12 @@ 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: + * `n`: int, the length of the rod + * `prices`: list, the price list for each piece of rod. - if n is negative or there are fewer items in the price list than the length of - the 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: msg = 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 d490bca737ba..08daaac6f88a 100644 --- a/dynamic_programming/subset_generation.py +++ b/dynamic_programming/subset_generation.py @@ -1,38 +1,41 @@ def subset_combinations(elements: list[int], n: int) -> list: """ Compute n-element combinations from a given list using dynamic programming. + Args: - elements: The list of elements from which combinations will be generated. - n: The number of elements in each combination. + * `elements`: The list of elements from which combinations will be generated. + * `n`: The number of elements in each combination. + Returns: - A list of tuples, each representing a combination of n elements. - >>> subset_combinations(elements=[10, 20, 30, 40], n=2) - [(10, 20), (10, 30), (10, 40), (20, 30), (20, 40), (30, 40)] - >>> subset_combinations(elements=[1, 2, 3], n=1) - [(1,), (2,), (3,)] - >>> subset_combinations(elements=[1, 2, 3], n=3) - [(1, 2, 3)] - >>> subset_combinations(elements=[42], n=1) - [(42,)] - >>> subset_combinations(elements=[6, 7, 8, 9], n=4) - [(6, 7, 8, 9)] - >>> subset_combinations(elements=[10, 20, 30, 40, 50], n=0) - [()] - >>> subset_combinations(elements=[1, 2, 3, 4], n=2) - [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)] - >>> subset_combinations(elements=[1, 'apple', 3.14], n=2) - [(1, 'apple'), (1, 3.14), ('apple', 3.14)] - >>> subset_combinations(elements=['single'], n=0) - [()] - >>> subset_combinations(elements=[], n=9) - [] - >>> from itertools import combinations - >>> all(subset_combinations(items, n) == list(combinations(items, n)) - ... for items, n in ( - ... ([10, 20, 30, 40], 2), ([1, 2, 3], 1), ([1, 2, 3], 3), ([42], 1), - ... ([6, 7, 8, 9], 4), ([10, 20, 30, 40, 50], 1), ([1, 2, 3, 4], 2), - ... ([1, 'apple', 3.14], 2), (['single'], 0), ([], 9))) - True + A list of tuples, each representing a combination of `n` elements. + + >>> subset_combinations(elements=[10, 20, 30, 40], n=2) + [(10, 20), (10, 30), (10, 40), (20, 30), (20, 40), (30, 40)] + >>> subset_combinations(elements=[1, 2, 3], n=1) + [(1,), (2,), (3,)] + >>> subset_combinations(elements=[1, 2, 3], n=3) + [(1, 2, 3)] + >>> subset_combinations(elements=[42], n=1) + [(42,)] + >>> subset_combinations(elements=[6, 7, 8, 9], n=4) + [(6, 7, 8, 9)] + >>> subset_combinations(elements=[10, 20, 30, 40, 50], n=0) + [()] + >>> subset_combinations(elements=[1, 2, 3, 4], n=2) + [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)] + >>> subset_combinations(elements=[1, 'apple', 3.14], n=2) + [(1, 'apple'), (1, 3.14), ('apple', 3.14)] + >>> subset_combinations(elements=['single'], n=0) + [()] + >>> subset_combinations(elements=[], n=9) + [] + >>> from itertools import combinations + >>> all(subset_combinations(items, n) == list(combinations(items, n)) + ... for items, n in ( + ... ([10, 20, 30, 40], 2), ([1, 2, 3], 1), ([1, 2, 3], 3), ([42], 1), + ... ([6, 7, 8, 9], 4), ([10, 20, 30, 40, 50], 1), ([1, 2, 3, 4], 2), + ... ([1, 'apple', 3.14], 2), (['single'], 0), ([], 9))) + True """ r = len(elements) if n > r: diff --git a/dynamic_programming/viterbi.py b/dynamic_programming/viterbi.py index 764d45dc2c05..5b78fa9e46d0 100644 --- a/dynamic_programming/viterbi.py +++ b/dynamic_programming/viterbi.py @@ -9,119 +9,102 @@ def viterbi( emission_probabilities: dict, ) -> list: """ - Viterbi Algorithm, to find the most likely path of - states from the start and the expected output. - https://en.wikipedia.org/wiki/Viterbi_algorithm - sdafads - Wikipedia example - >>> observations = ["normal", "cold", "dizzy"] - >>> states = ["Healthy", "Fever"] - >>> start_p = {"Healthy": 0.6, "Fever": 0.4} - >>> trans_p = { - ... "Healthy": {"Healthy": 0.7, "Fever": 0.3}, - ... "Fever": {"Healthy": 0.4, "Fever": 0.6}, - ... } - >>> emit_p = { - ... "Healthy": {"normal": 0.5, "cold": 0.4, "dizzy": 0.1}, - ... "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6}, - ... } - >>> viterbi(observations, states, start_p, trans_p, emit_p) - ['Healthy', 'Healthy', 'Fever'] + Viterbi Algorithm, to find the most likely path of + states from the start and the expected output. - >>> viterbi((), states, start_p, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: There's an empty parameter - - >>> viterbi(observations, (), start_p, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: There's an empty parameter - - >>> viterbi(observations, states, {}, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: There's an empty parameter - - >>> viterbi(observations, states, start_p, {}, emit_p) - Traceback (most recent call last): - ... - ValueError: There's an empty parameter - - >>> viterbi(observations, states, start_p, trans_p, {}) - Traceback (most recent call last): - ... - ValueError: There's an empty parameter - - >>> viterbi("invalid", states, start_p, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: observations_space must be a list + https://en.wikipedia.org/wiki/Viterbi_algorithm - >>> viterbi(["valid", 123], states, start_p, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: observations_space must be a list of strings - - >>> viterbi(observations, "invalid", start_p, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: states_space must be a list - - >>> viterbi(observations, ["valid", 123], start_p, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: states_space must be a list of strings - - >>> viterbi(observations, states, "invalid", trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: initial_probabilities must be a dict - - >>> viterbi(observations, states, {2:2}, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: initial_probabilities all keys must be strings - - >>> viterbi(observations, states, {"a":2}, trans_p, emit_p) - Traceback (most recent call last): - ... - ValueError: initial_probabilities all values must be float + Wikipedia example - >>> viterbi(observations, states, start_p, "invalid", emit_p) - Traceback (most recent call last): - ... - ValueError: transition_probabilities must be a dict - - >>> viterbi(observations, states, start_p, {"a":2}, emit_p) - Traceback (most recent call last): - ... - ValueError: transition_probabilities all values must be dict - - >>> viterbi(observations, states, start_p, {2:{2:2}}, emit_p) - Traceback (most recent call last): - ... - ValueError: transition_probabilities all keys must be strings - - >>> viterbi(observations, states, start_p, {"a":{2:2}}, emit_p) - Traceback (most recent call last): - ... - ValueError: transition_probabilities all keys must be strings - - >>> viterbi(observations, states, start_p, {"a":{"b":2}}, emit_p) - Traceback (most recent call last): - ... - ValueError: transition_probabilities nested dictionary all values must be float - - >>> viterbi(observations, states, start_p, trans_p, "invalid") - Traceback (most recent call last): - ... - ValueError: emission_probabilities must be a dict - - >>> viterbi(observations, states, start_p, trans_p, None) - Traceback (most recent call last): - ... - ValueError: There's an empty parameter + >>> observations = ["normal", "cold", "dizzy"] + >>> states = ["Healthy", "Fever"] + >>> start_p = {"Healthy": 0.6, "Fever": 0.4} + >>> trans_p = { + ... "Healthy": {"Healthy": 0.7, "Fever": 0.3}, + ... "Fever": {"Healthy": 0.4, "Fever": 0.6}, + ... } + >>> emit_p = { + ... "Healthy": {"normal": 0.5, "cold": 0.4, "dizzy": 0.1}, + ... "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6}, + ... } + >>> viterbi(observations, states, start_p, trans_p, emit_p) + ['Healthy', 'Healthy', 'Fever'] + >>> viterbi((), states, start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + >>> viterbi(observations, (), start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + >>> viterbi(observations, states, {}, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + >>> viterbi(observations, states, start_p, {}, emit_p) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + >>> viterbi(observations, states, start_p, trans_p, {}) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter + >>> viterbi("invalid", states, start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: observations_space must be a list + >>> viterbi(["valid", 123], states, start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: observations_space must be a list of strings + >>> viterbi(observations, "invalid", start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: states_space must be a list + >>> viterbi(observations, ["valid", 123], start_p, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: states_space must be a list of strings + >>> viterbi(observations, states, "invalid", trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: initial_probabilities must be a dict + >>> viterbi(observations, states, {2:2}, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: initial_probabilities all keys must be strings + >>> viterbi(observations, states, {"a":2}, trans_p, emit_p) + Traceback (most recent call last): + ... + ValueError: initial_probabilities all values must be float + >>> viterbi(observations, states, start_p, "invalid", emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities must be a dict + >>> viterbi(observations, states, start_p, {"a":2}, emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities all values must be dict + >>> viterbi(observations, states, start_p, {2:{2:2}}, emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities all keys must be strings + >>> viterbi(observations, states, start_p, {"a":{2:2}}, emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities all keys must be strings + >>> viterbi(observations, states, start_p, {"a":{"b":2}}, emit_p) + Traceback (most recent call last): + ... + ValueError: transition_probabilities nested dictionary all values must be float + >>> viterbi(observations, states, start_p, trans_p, "invalid") + Traceback (most recent call last): + ... + ValueError: emission_probabilities must be a dict + >>> viterbi(observations, states, start_p, trans_p, None) + Traceback (most recent call last): + ... + ValueError: There's an empty parameter """ _validation( @@ -213,7 +196,6 @@ def _validation( ... "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6}, ... } >>> _validation(observations, states, start_p, trans_p, emit_p) - >>> _validation([], states, start_p, trans_p, emit_p) Traceback (most recent call last): ... @@ -242,7 +224,6 @@ def _validate_not_empty( """ >>> _validate_not_empty(["a"], ["b"], {"c":0.5}, ... {"d": {"e": 0.6}}, {"f": {"g": 0.7}}) - >>> _validate_not_empty(["a"], ["b"], {"c":0.5}, {}, {"f": {"g": 0.7}}) Traceback (most recent call last): ... @@ -267,12 +248,10 @@ def _validate_not_empty( def _validate_lists(observations_space: Any, states_space: Any) -> None: """ >>> _validate_lists(["a"], ["b"]) - >>> _validate_lists(1234, ["b"]) Traceback (most recent call last): ... ValueError: observations_space must be a list - >>> _validate_lists(["a"], [3]) Traceback (most recent call last): ... @@ -285,7 +264,6 @@ def _validate_lists(observations_space: Any, states_space: Any) -> None: def _validate_list(_object: Any, var_name: str) -> None: """ >>> _validate_list(["a"], "mock_name") - >>> _validate_list("a", "mock_name") Traceback (most recent call last): ... @@ -294,7 +272,6 @@ def _validate_list(_object: Any, var_name: str) -> None: Traceback (most recent call last): ... ValueError: mock_name must be a list of strings - """ if not isinstance(_object, list): msg = f"{var_name} must be a list" @@ -313,7 +290,6 @@ def _validate_dicts( ) -> None: """ >>> _validate_dicts({"c":0.5}, {"d": {"e": 0.6}}, {"f": {"g": 0.7}}) - >>> _validate_dicts("invalid", {"d": {"e": 0.6}}, {"f": {"g": 0.7}}) Traceback (most recent call last): ... @@ -339,7 +315,6 @@ def _validate_dicts( def _validate_nested_dict(_object: Any, var_name: str) -> None: """ >>> _validate_nested_dict({"a":{"b": 0.5}}, "mock_name") - >>> _validate_nested_dict("invalid", "mock_name") Traceback (most recent call last): ... @@ -367,7 +342,6 @@ def _validate_dict( ) -> None: """ >>> _validate_dict({"b": 0.5}, "mock_name", float) - >>> _validate_dict("invalid", "mock_name", float) Traceback (most recent call last): ... From a2be5adf673c32720bbfb649c368af8fd7ae9dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Arag=C3=A3o?= <101305675+juliaaragao@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:36:55 +0100 Subject: [PATCH 2841/2908] Tests electronics/electric_conductivity.py #9943 (#12437) * Function conversion rectangular number to polar * #9943 : adding test to elelectronics/electric_conductivity.py * updating DIRECTORY.md * Apply suggestions from code review * updating DIRECTORY.md * Rename rec_to_pol.py to rectangular_to_polar.py * updating DIRECTORY.md * Update conversions/rectangular_to_polar.py * Update conversions/rectangular_to_polar.py --------- Co-authored-by: Julia Co-authored-by: juliaaragao Co-authored-by: Christian Clauss Co-authored-by: cclauss --- DIRECTORY.md | 1 + conversions/rectangular_to_polar.py | 32 ++++++++++++++++++++++++++++ electronics/electric_conductivity.py | 20 +++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 conversions/rectangular_to_polar.py diff --git a/DIRECTORY.md b/DIRECTORY.md index d234d366df06..44d0414a37c8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -169,6 +169,7 @@ * [Prefix Conversions](conversions/prefix_conversions.py) * [Prefix Conversions String](conversions/prefix_conversions_string.py) * [Pressure Conversions](conversions/pressure_conversions.py) + * [Rectangular To Polar](conversions/rectangular_to_polar.py) * [Rgb Cmyk Conversion](conversions/rgb_cmyk_conversion.py) * [Rgb Hsv Conversion](conversions/rgb_hsv_conversion.py) * [Roman Numerals](conversions/roman_numerals.py) diff --git a/conversions/rectangular_to_polar.py b/conversions/rectangular_to_polar.py new file mode 100644 index 000000000000..bed97d7410ec --- /dev/null +++ b/conversions/rectangular_to_polar.py @@ -0,0 +1,32 @@ +import math + + +def rectangular_to_polar(real: float, img: float) -> tuple[float, float]: + """ + https://en.wikipedia.org/wiki/Polar_coordinate_system + + >>> rectangular_to_polar(5,-5) + (7.07, -45.0) + >>> rectangular_to_polar(-1,1) + (1.41, 135.0) + >>> rectangular_to_polar(-1,-1) + (1.41, -135.0) + >>> rectangular_to_polar(1e-10,1e-10) + (0.0, 45.0) + >>> rectangular_to_polar(-1e-10,1e-10) + (0.0, 135.0) + >>> rectangular_to_polar(9.75,5.93) + (11.41, 31.31) + >>> rectangular_to_polar(10000,99999) + (100497.76, 84.29) + """ + + mod = round(math.sqrt((real**2) + (img**2)), 2) + ang = round(math.degrees(math.atan2(img, real)), 2) + return (mod, ang) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/electronics/electric_conductivity.py b/electronics/electric_conductivity.py index 11f2a607d214..65bb6c5ceaf0 100644 --- a/electronics/electric_conductivity.py +++ b/electronics/electric_conductivity.py @@ -21,6 +21,26 @@ def electric_conductivity( ('conductivity', 5.12672e-14) >>> electric_conductivity(conductivity=1000, electron_conc=0, mobility=1200) ('electron_conc', 5.201506356240767e+18) + >>> electric_conductivity(conductivity=-10, electron_conc=100, mobility=0) + Traceback (most recent call last): + ... + ValueError: Conductivity cannot be negative + >>> electric_conductivity(conductivity=50, electron_conc=-10, mobility=0) + Traceback (most recent call last): + ... + ValueError: Electron concentration cannot be negative + >>> electric_conductivity(conductivity=50, electron_conc=0, mobility=-10) + Traceback (most recent call last): + ... + ValueError: mobility cannot be negative + >>> electric_conductivity(conductivity=50, electron_conc=0, mobility=0) + Traceback (most recent call last): + ... + ValueError: You cannot supply more or less than 2 values + >>> electric_conductivity(conductivity=50, electron_conc=200, mobility=300) + Traceback (most recent call last): + ... + ValueError: You cannot supply more or less than 2 values """ if (conductivity, electron_conc, mobility).count(0) != 1: raise ValueError("You cannot supply more or less than 2 values") From f24ddba5b2600486f7c3a4c5807cf2aeed421870 Mon Sep 17 00:00:00 2001 From: Matej <83732219+IsxImattI@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:04:28 +0100 Subject: [PATCH 2842/2908] Implemented doctests for geometry-related classes (#12368) * Implemented doctests for geometry-related classes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed unused noqa directive * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactored sudoku_solver.py * refactored sudoku_solver.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * context manager for file handling changed too in from_file function --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- data_structures/arrays/sudoku_solver.py | 5 +++-- geometry/geometry.py | 29 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index 7e38e1465728..fd1a4f3e37b8 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -23,7 +23,7 @@ def cross(items_a, items_b): + [cross(rs, cs) for rs in ("ABC", "DEF", "GHI") for cs in ("123", "456", "789")] ) units = {s: [u for u in unitlist if s in u] for s in squares} -peers = {s: set(sum(units[s], [])) - {s} for s in squares} # noqa: RUF017 +peers = {s: {x for u in units[s] for x in u} - {s} for s in squares} def test(): @@ -172,7 +172,8 @@ def unitsolved(unit): def from_file(filename, sep="\n"): "Parse a file into a list of strings, separated by sep." - return open(filename).read().strip().split(sep) + with open(filename) as file: + return file.read().strip().split(sep) def random_puzzle(assignments=17): diff --git a/geometry/geometry.py b/geometry/geometry.py index 9e353dee17a7..a0be8eb3befc 100644 --- a/geometry/geometry.py +++ b/geometry/geometry.py @@ -48,6 +48,18 @@ class Side: Side(length=5, angle=Angle(degrees=45.6), next_side=None) >>> Side(5, Angle(45.6), Side(1, Angle(2))) # doctest: +ELLIPSIS Side(length=5, angle=Angle(degrees=45.6), next_side=Side(length=1, angle=Angle(d... + >>> Side(-1) + Traceback (most recent call last): + ... + TypeError: length must be a positive numeric value. + >>> Side(5, None) + Traceback (most recent call last): + ... + TypeError: angle must be an Angle object. + >>> Side(5, Angle(90), "Invalid next_side") + Traceback (most recent call last): + ... + TypeError: next_side must be a Side or None. """ length: float @@ -162,6 +174,19 @@ class Polygon: >>> Polygon() Polygon(sides=[]) + >>> polygon = Polygon() + >>> polygon.add_side(Side(5)).get_side(0) + Side(length=5, angle=Angle(degrees=90), next_side=None) + >>> polygon.get_side(1) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> polygon.set_side(0, Side(10)).get_side(0) + Side(length=10, angle=Angle(degrees=90), next_side=None) + >>> polygon.set_side(1, Side(10)) + Traceback (most recent call last): + ... + IndexError: list assignment index out of range """ sides: list[Side] = field(default_factory=list) @@ -207,6 +232,10 @@ class Rectangle(Polygon): 30 >>> rectangle_one.area() 50 + >>> Rectangle(-5, 10) + Traceback (most recent call last): + ... + TypeError: length must be a positive numeric value. """ def __init__(self, short_side_length: float, long_side_length: float) -> None: From 77425364c87908bf061ad78b770ec840086b4efb Mon Sep 17 00:00:00 2001 From: SUDO_USER <110802232+AtharvMalusare@users.noreply.github.com> Date: Mon, 30 Dec 2024 20:42:04 +0530 Subject: [PATCH 2843/2908] Intensity_based_Segmentation (#12491) * Add files via upload * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update intensity-based_segmentation.py * Update and rename intensity-based_segmentation.py to intensity_based_segmentation.py * Update intensity_based_segmentation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * [0, 1, 1]], dtype=int32) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../intensity_based_segmentation.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 computer_vision/intensity_based_segmentation.py diff --git a/computer_vision/intensity_based_segmentation.py b/computer_vision/intensity_based_segmentation.py new file mode 100644 index 000000000000..7f2b1141acc4 --- /dev/null +++ b/computer_vision/intensity_based_segmentation.py @@ -0,0 +1,62 @@ +# Source: "https://www.ijcse.com/docs/IJCSE11-02-03-117.pdf" + +# Importing necessary libraries +import matplotlib.pyplot as plt +import numpy as np +from PIL import Image + + +def segment_image(image: np.ndarray, thresholds: list[int]) -> np.ndarray: + """ + Performs image segmentation based on intensity thresholds. + + Args: + image: Input grayscale image as a 2D array. + thresholds: Intensity thresholds to define segments. + + Returns: + A labeled 2D array where each region corresponds to a threshold range. + + Example: + >>> img = np.array([[80, 120, 180], [40, 90, 150], [20, 60, 100]]) + >>> segment_image(img, [50, 100, 150]) + array([[1, 2, 3], + [0, 1, 2], + [0, 1, 1]], dtype=int32) + """ + # Initialize segmented array with zeros + segmented = np.zeros_like(image, dtype=np.int32) + + # Assign labels based on thresholds + for i, threshold in enumerate(thresholds): + segmented[image > threshold] = i + 1 + + return segmented + + +if __name__ == "__main__": + # Load the image + image_path = "path_to_image" # Replace with your image path + original_image = Image.open(image_path).convert("L") + image_array = np.array(original_image) + + # Define thresholds + thresholds = [50, 100, 150, 200] + + # Perform segmentation + segmented_image = segment_image(image_array, thresholds) + + # Display the results + plt.figure(figsize=(10, 5)) + + plt.subplot(1, 2, 1) + plt.title("Original Image") + plt.imshow(image_array, cmap="gray") + plt.axis("off") + + plt.subplot(1, 2, 2) + plt.title("Segmented Image") + plt.imshow(segmented_image, cmap="tab20") + plt.axis("off") + + plt.show() From 75c5c411133f7e0f339c8d68c7c76c8054eb4249 Mon Sep 17 00:00:00 2001 From: Scarfinos <158184182+Scarfinos@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:12:26 +0100 Subject: [PATCH 2844/2908] #9943 : Adding coverage test for basic_graphs.py (#12354) * #9943 : Adding coverage test for basic_graphs.py * #9943 : Adding coverage test for basic_graphs.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Solve problem of line too long --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- graphs/basic_graphs.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 567fa65040ae..286e9b195796 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -77,6 +77,14 @@ def initialize_weighted_undirected_graph( def dfs(g, s): + """ + >>> dfs({1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []}, 1) + 1 + 2 + 4 + 5 + 3 + """ vis, _s = {s}, [s] print(s) while _s: @@ -104,6 +112,17 @@ def dfs(g, s): def bfs(g, s): + """ + >>> bfs({1: [2, 3], 2: [4, 5], 3: [6, 7], 4: [], 5: [8], 6: [], 7: [], 8: []}, 1) + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + """ vis, q = {s}, deque([s]) print(s) while q: @@ -128,6 +147,19 @@ def bfs(g, s): def dijk(g, s): + """ + dijk({1: [(2, 7), (3, 9), (6, 14)], + 2: [(1, 7), (3, 10), (4, 15)], + 3: [(1, 9), (2, 10), (4, 11), (6, 2)], + 4: [(2, 15), (3, 11), (5, 6)], + 5: [(4, 6), (6, 9)], + 6: [(1, 14), (3, 2), (5, 9)]}, 1) + 7 + 9 + 11 + 20 + 20 + """ dist, known, path = {s: 0}, set(), {s: 0} while True: if len(known) == len(g) - 1: From 7e55fb6474a06ecf0000fe11494fe5eefeeb54ab Mon Sep 17 00:00:00 2001 From: Jeffrey Yancey Date: Mon, 30 Dec 2024 12:00:30 -0700 Subject: [PATCH 2845/2908] - Implemented `find_lanczos_eigenvectors` to approximate the largest eigenvalues and corresponding eigenvectors of a graph based on its adjacency list. (#11906) - Utilized `lanczos_iteration` to construct tridiagonal matrices, optimized for large, sparse matrices. - Added `multiply_matrix_vector` for efficient matrix-vector multiplication using adjacency lists. - Included `validate_adjacency_list` for input validation. - Supports varied graph analysis applications, particularly for analyzing graph centrality. - Included type hints, comprehensive docstrings, and doctests. - PEP-8 compliant, with optimized handling of inputs and outputs. This module provides essential tools for eigenvalue-based graph analysis, ideal for centrality insights and structural assessments. --- graphs/lanczos_eigenvectors.py | 206 +++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 graphs/lanczos_eigenvectors.py diff --git a/graphs/lanczos_eigenvectors.py b/graphs/lanczos_eigenvectors.py new file mode 100644 index 000000000000..581a81a1127f --- /dev/null +++ b/graphs/lanczos_eigenvectors.py @@ -0,0 +1,206 @@ +""" +Lanczos Method for Finding Eigenvalues and Eigenvectors of a Graph. + +This module demonstrates the Lanczos method to approximate the largest eigenvalues +and corresponding eigenvectors of a symmetric matrix represented as a graph's +adjacency list. The method efficiently handles large, sparse matrices by converting +the graph to a tridiagonal matrix, whose eigenvalues and eigenvectors are then +computed. + +Key Functions: +- `find_lanczos_eigenvectors`: Computes the k largest eigenvalues and vectors. +- `lanczos_iteration`: Constructs the tridiagonal matrix and orthonormal basis vectors. +- `multiply_matrix_vector`: Multiplies an adjacency list graph with a vector. + +Complexity: +- Time: O(k * n), where k is the number of eigenvalues and n is the matrix size. +- Space: O(n), due to sparse representation and tridiagonal matrix structure. + +Further Reading: +- Lanczos Algorithm: https://en.wikipedia.org/wiki/Lanczos_algorithm +- Eigenvector Centrality: https://en.wikipedia.org/wiki/Eigenvector_centrality + +Example Usage: +Given a graph represented by an adjacency list, the `find_lanczos_eigenvectors` +function returns the largest eigenvalues and eigenvectors. This can be used to +analyze graph centrality. +""" + +import numpy as np + + +def validate_adjacency_list(graph: list[list[int | None]]) -> None: + """Validates the adjacency list format for the graph. + + Args: + graph: A list of lists where each sublist contains the neighbors of a node. + + Raises: + ValueError: If the graph is not a list of lists, or if any node has + invalid neighbors (e.g., out-of-range or non-integer values). + + >>> validate_adjacency_list([[1, 2], [0], [0, 1]]) + >>> validate_adjacency_list([[]]) # No neighbors, valid case + >>> validate_adjacency_list([[1], [2], [-1]]) # Invalid neighbor + Traceback (most recent call last): + ... + ValueError: Invalid neighbor -1 in node 2 adjacency list. + """ + if not isinstance(graph, list): + raise ValueError("Graph should be a list of lists.") + + for node_index, neighbors in enumerate(graph): + if not isinstance(neighbors, list): + no_neighbors_message: str = ( + f"Node {node_index} should have a list of neighbors." + ) + raise ValueError(no_neighbors_message) + for neighbor_index in neighbors: + if ( + not isinstance(neighbor_index, int) + or neighbor_index < 0 + or neighbor_index >= len(graph) + ): + invalid_neighbor_message: str = ( + f"Invalid neighbor {neighbor_index} in node {node_index} " + f"adjacency list." + ) + raise ValueError(invalid_neighbor_message) + + +def lanczos_iteration( + graph: list[list[int | None]], num_eigenvectors: int +) -> tuple[np.ndarray, np.ndarray]: + """Constructs the tridiagonal matrix and orthonormal basis vectors using the + Lanczos method. + + Args: + graph: The graph represented as a list of adjacency lists. + num_eigenvectors: The number of largest eigenvalues and eigenvectors + to approximate. + + Returns: + A tuple containing: + - tridiagonal_matrix: A (num_eigenvectors x num_eigenvectors) symmetric + matrix. + - orthonormal_basis: A (num_nodes x num_eigenvectors) matrix of orthonormal + basis vectors. + + Raises: + ValueError: If num_eigenvectors is less than 1 or greater than the number of + nodes. + + >>> graph = [[1, 2], [0, 2], [0, 1]] + >>> T, Q = lanczos_iteration(graph, 2) + >>> T.shape == (2, 2) and Q.shape == (3, 2) + True + """ + num_nodes: int = len(graph) + if not (1 <= num_eigenvectors <= num_nodes): + raise ValueError( + "Number of eigenvectors must be between 1 and the number of " + "nodes in the graph." + ) + + orthonormal_basis: np.ndarray = np.zeros((num_nodes, num_eigenvectors)) + tridiagonal_matrix: np.ndarray = np.zeros((num_eigenvectors, num_eigenvectors)) + + rng = np.random.default_rng() + initial_vector: np.ndarray = rng.random(num_nodes) + initial_vector /= np.sqrt(np.dot(initial_vector, initial_vector)) + orthonormal_basis[:, 0] = initial_vector + + prev_beta: float = 0.0 + for iter_index in range(num_eigenvectors): + result_vector: np.ndarray = multiply_matrix_vector( + graph, orthonormal_basis[:, iter_index] + ) + if iter_index > 0: + result_vector -= prev_beta * orthonormal_basis[:, iter_index - 1] + alpha_value: float = np.dot(orthonormal_basis[:, iter_index], result_vector) + result_vector -= alpha_value * orthonormal_basis[:, iter_index] + + prev_beta = np.sqrt(np.dot(result_vector, result_vector)) + if iter_index < num_eigenvectors - 1 and prev_beta > 1e-10: + orthonormal_basis[:, iter_index + 1] = result_vector / prev_beta + tridiagonal_matrix[iter_index, iter_index] = alpha_value + if iter_index < num_eigenvectors - 1: + tridiagonal_matrix[iter_index, iter_index + 1] = prev_beta + tridiagonal_matrix[iter_index + 1, iter_index] = prev_beta + return tridiagonal_matrix, orthonormal_basis + + +def multiply_matrix_vector( + graph: list[list[int | None]], vector: np.ndarray +) -> np.ndarray: + """Performs multiplication of a graph's adjacency list representation with a vector. + + Args: + graph: The adjacency list of the graph. + vector: A 1D numpy array representing the vector to multiply. + + Returns: + A numpy array representing the product of the adjacency list and the vector. + + Raises: + ValueError: If the vector's length does not match the number of nodes in the + graph. + + >>> multiply_matrix_vector([[1, 2], [0, 2], [0, 1]], np.array([1, 1, 1])) + array([2., 2., 2.]) + >>> multiply_matrix_vector([[1, 2], [0, 2], [0, 1]], np.array([0, 1, 0])) + array([1., 0., 1.]) + """ + num_nodes: int = len(graph) + if vector.shape[0] != num_nodes: + raise ValueError("Vector length must match the number of nodes in the graph.") + + result: np.ndarray = np.zeros(num_nodes) + for node_index, neighbors in enumerate(graph): + for neighbor_index in neighbors: + result[node_index] += vector[neighbor_index] + return result + + +def find_lanczos_eigenvectors( + graph: list[list[int | None]], num_eigenvectors: int +) -> tuple[np.ndarray, np.ndarray]: + """Computes the largest eigenvalues and their corresponding eigenvectors using the + Lanczos method. + + Args: + graph: The graph as a list of adjacency lists. + num_eigenvectors: Number of largest eigenvalues and eigenvectors to compute. + + Returns: + A tuple containing: + - eigenvalues: 1D array of the largest eigenvalues in descending order. + - eigenvectors: 2D array where each column is an eigenvector corresponding + to an eigenvalue. + + Raises: + ValueError: If the graph format is invalid or num_eigenvectors is out of bounds. + + >>> eigenvalues, eigenvectors = find_lanczos_eigenvectors( + ... [[1, 2], [0, 2], [0, 1]], 2 + ... ) + >>> len(eigenvalues) == 2 and eigenvectors.shape[1] == 2 + True + """ + validate_adjacency_list(graph) + tridiagonal_matrix, orthonormal_basis = lanczos_iteration(graph, num_eigenvectors) + eigenvalues, eigenvectors = np.linalg.eigh(tridiagonal_matrix) + return eigenvalues[::-1], np.dot(orthonormal_basis, eigenvectors[:, ::-1]) + + +def main() -> None: + """ + Main driver function for testing the implementation with doctests. + """ + import doctest + + doctest.testmod() + + +if __name__ == "__main__": + main() From 8921b56a8517cdc9455d764d7cddb10b2d7f2145 Mon Sep 17 00:00:00 2001 From: Melih Mehmet Sahin Date: Mon, 30 Dec 2024 19:53:50 +0000 Subject: [PATCH 2846/2908] Adding tests to monotonic_array.py (#12073) * Contributes to #9943 by adding tests to monotonic_array.py Addeded doctest in the if __name__. Checks for negaitves and an array of same integers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- data_structures/arrays/monotonic_array.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/data_structures/arrays/monotonic_array.py b/data_structures/arrays/monotonic_array.py index c50a21530814..342d443a9cfc 100644 --- a/data_structures/arrays/monotonic_array.py +++ b/data_structures/arrays/monotonic_array.py @@ -9,6 +9,16 @@ def is_monotonic(nums: list[int]) -> bool: True >>> is_monotonic([1, 3, 2]) False + >>> is_monotonic([1,2,3,4,5,6,5]) + False + >>> is_monotonic([-3,-2,-1]) + True + >>> is_monotonic([-5,-6,-7]) + True + >>> is_monotonic([0,0,0]) + True + >>> is_monotonic([-100,0,100]) + True """ return all(nums[i] <= nums[i + 1] for i in range(len(nums) - 1)) or all( nums[i] >= nums[i + 1] for i in range(len(nums) - 1) @@ -21,3 +31,7 @@ def is_monotonic(nums: list[int]) -> bool: print(is_monotonic([1, 2, 2, 3])) # Output: True print(is_monotonic([6, 5, 4, 4])) # Output: True print(is_monotonic([1, 3, 2])) # Output: False + + import doctest + + doctest.testmod() From 5942059cb571b213e5ec82fe9b45e5a9bef4864b Mon Sep 17 00:00:00 2001 From: Giulio Tantaro Date: Mon, 30 Dec 2024 21:03:31 +0100 Subject: [PATCH 2847/2908] add doctest for quick_sort_3_partition (#11779) --- sorts/quick_sort_3_partition.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py index 1a6db6a364f0..279b9a68f5a6 100644 --- a/sorts/quick_sort_3_partition.py +++ b/sorts/quick_sort_3_partition.py @@ -1,4 +1,27 @@ def quick_sort_3partition(sorting: list, left: int, right: int) -> None: + """ " + Python implementation of quick sort algorithm with 3-way partition. + The idea of 3-way quick sort is based on "Dutch National Flag algorithm". + + :param sorting: sort list + :param left: left endpoint of sorting + :param right: right endpoint of sorting + :return: None + + Examples: + >>> array1 = [5, -1, -1, 5, 5, 24, 0] + >>> quick_sort_3partition(array1, 0, 6) + >>> array1 + [-1, -1, 0, 5, 5, 5, 24] + >>> array2 = [9, 0, 2, 6] + >>> quick_sort_3partition(array2, 0, 3) + >>> array2 + [0, 2, 6, 9] + >>> array3 = [] + >>> quick_sort_3partition(array3, 0, 0) + >>> array3 + [] + """ if right <= left: return a = i = left From 8767d1d72436b8aff89f9c11d045ad95bec02ba4 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Mon, 30 Dec 2024 21:36:41 -0300 Subject: [PATCH 2848/2908] add some documentation for heap sort (#9949) * add some documentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix typing * Update heap_sort.py * Update heap_sort.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- sorts/heap_sort.py | 47 +++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/sorts/heap_sort.py b/sorts/heap_sort.py index 4dca879bd89c..44ee1d4b39f1 100644 --- a/sorts/heap_sort.py +++ b/sorts/heap_sort.py @@ -1,17 +1,22 @@ """ -This is a pure Python implementation of the heap sort algorithm. - -For doctests run following command: -python -m doctest -v heap_sort.py -or -python3 -m doctest -v heap_sort.py - -For manual testing run: -python heap_sort.py +A pure Python implementation of the heap sort algorithm. """ -def heapify(unsorted, index, heap_size): +def heapify(unsorted: list[int], index: int, heap_size: int) -> None: + """ + :param unsorted: unsorted list containing integers numbers + :param index: index + :param heap_size: size of the heap + :return: None + >>> unsorted = [1, 4, 3, 5, 2] + >>> heapify(unsorted, 0, len(unsorted)) + >>> unsorted + [4, 5, 3, 1, 2] + >>> heapify(unsorted, 0, len(unsorted)) + >>> unsorted + [5, 4, 3, 1, 2] + """ largest = index left_index = 2 * index + 1 right_index = 2 * index + 2 @@ -22,26 +27,26 @@ def heapify(unsorted, index, heap_size): largest = right_index if largest != index: - unsorted[largest], unsorted[index] = unsorted[index], unsorted[largest] + unsorted[largest], unsorted[index] = (unsorted[index], unsorted[largest]) heapify(unsorted, largest, heap_size) -def heap_sort(unsorted): +def heap_sort(unsorted: list[int]) -> list[int]: """ - Pure implementation of the heap sort algorithm in Python - :param collection: some mutable ordered collection with heterogeneous - comparable items inside + A pure Python implementation of the heap sort algorithm + + :param collection: a mutable ordered collection of heterogeneous comparable items :return: the same collection ordered by ascending Examples: >>> heap_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] - >>> heap_sort([]) [] - >>> heap_sort([-2, -5, -45]) [-45, -5, -2] + >>> heap_sort([3, 7, 9, 28, 123, -5, 8, -30, -200, 0, 4]) + [-200, -30, -5, 0, 3, 4, 7, 8, 9, 28, 123] """ n = len(unsorted) for i in range(n // 2 - 1, -1, -1): @@ -53,6 +58,10 @@ def heap_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(",")] - print(heap_sort(unsorted)) + if user_input: + unsorted = [int(item) for item in user_input.split(",")] + print(f"{heap_sort(unsorted) = }") From 8439fa8d1da94370250d153cd57f9bdcc382a062 Mon Sep 17 00:00:00 2001 From: Paarth Goyal <138299656+pluto-tofu@users.noreply.github.com> Date: Tue, 31 Dec 2024 06:17:41 +0530 Subject: [PATCH 2849/2908] Added the algorithm to compute the time period of a simple pendulum (#10265) * Added the algorithm to compute the time period of a simple pendulum * imported g form scipy and changed doctests accordingly * fixed formatting * applied all suggested changes from code review * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Tianyi Zheng Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- physics/period_of_pendulum.py | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 physics/period_of_pendulum.py diff --git a/physics/period_of_pendulum.py b/physics/period_of_pendulum.py new file mode 100644 index 000000000000..2e3c7bc3ef1e --- /dev/null +++ b/physics/period_of_pendulum.py @@ -0,0 +1,53 @@ +""" +Title : Computing the time period of a simple pendulum + +The simple pendulum is a mechanical system that sways or moves in an +oscillatory motion. The simple pendulum comprises of a small bob of +mass m suspended by a thin string of length L and secured to a platform +at its upper end. Its motion occurs in a vertical plane and is mainly +driven by gravitational force. The period of the pendulum depends on the +length of the string and the amplitude (the maximum angle) of oscillation. +However, the effect of the amplitude can be ignored if the amplitude is +small. It should be noted that the period does not depend on the mass of +the bob. + +For small amplitudes, the period of a simple pendulum is given by the +following approximation: +T ≈ 2π * √(L / g) + +where: +L = length of string from which the bob is hanging (in m) +g = acceleration due to gravity (approx 9.8 m/s²) + +Reference : https://byjus.com/jee/simple-pendulum/ +""" + +from math import pi + +from scipy.constants import g + + +def period_of_pendulum(length: float) -> float: + """ + >>> period_of_pendulum(1.23) + 2.2252155506257845 + >>> period_of_pendulum(2.37) + 3.0888278441908574 + >>> period_of_pendulum(5.63) + 4.76073193364765 + >>> period_of_pendulum(-12) + Traceback (most recent call last): + ... + ValueError: The length should be non-negative + >>> period_of_pendulum(0) + 0.0 + """ + if length < 0: + raise ValueError("The length should be non-negative") + return 2 * pi * (length / g) ** 0.5 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a4399022e516dfba1097f91cedc0b7f4213bab84 Mon Sep 17 00:00:00 2001 From: Julien Richard Date: Tue, 31 Dec 2024 02:11:29 +0100 Subject: [PATCH 2850/2908] chore: improve comments and add tests to trapezoidal rule (#11640) * chore: improve comments and add tests to trapezoidal rule * fix: too much characters in line * Update maths/trapezoidal_rule.py Co-authored-by: Tianyi Zheng * Update maths/trapezoidal_rule.py Co-authored-by: Tianyi Zheng * Update maths/trapezoidal_rule.py Co-authored-by: Tianyi Zheng * Update maths/trapezoidal_rule.py Co-authored-by: Tianyi Zheng * fix: change function name in calls * modify tests, changes numbers to remove coma * updating DIRECTORY.md * Fix doctest whitespace * Try to fix line length in doctest --------- Co-authored-by: Tianyi Zheng Co-authored-by: tianyizheng02 --- DIRECTORY.md | 3 ++ maths/trapezoidal_rule.py | 97 ++++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 44d0414a37c8..1248a290d294 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -142,6 +142,7 @@ * [Haralick Descriptors](computer_vision/haralick_descriptors.py) * [Harris Corner](computer_vision/harris_corner.py) * [Horn Schunck](computer_vision/horn_schunck.py) + * [Intensity Based Segmentation](computer_vision/intensity_based_segmentation.py) * [Mean Threshold](computer_vision/mean_threshold.py) * [Mosaic Augmentation](computer_vision/mosaic_augmentation.py) * [Pooling Functions](computer_vision/pooling_functions.py) @@ -507,6 +508,7 @@ * [Kahns Algorithm Long](graphs/kahns_algorithm_long.py) * [Kahns Algorithm Topo](graphs/kahns_algorithm_topo.py) * [Karger](graphs/karger.py) + * [Lanczos Eigenvectors](graphs/lanczos_eigenvectors.py) * [Markov Chain](graphs/markov_chain.py) * [Matching Min Vertex Cover](graphs/matching_min_vertex_cover.py) * [Minimum Path Sum](graphs/minimum_path_sum.py) @@ -886,6 +888,7 @@ * [N Body Simulation](physics/n_body_simulation.py) * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py) * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) + * [Period Of Pendulum](physics/period_of_pendulum.py) * [Photoelectric Effect](physics/photoelectric_effect.py) * [Potential Energy](physics/potential_energy.py) * [Rainfall Intensity](physics/rainfall_intensity.py) diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py index 0186629ee378..21b10b239b5f 100644 --- a/maths/trapezoidal_rule.py +++ b/maths/trapezoidal_rule.py @@ -1,28 +1,25 @@ """ Numerical integration or quadrature for a smooth function f with known values at x_i - -This method is the classical approach of suming 'Equally Spaced Abscissas' - -method 1: -"extended trapezoidal rule" -int(f) = dx/2 * (f1 + 2f2 + ... + fn) - """ -def method_1(boundary, steps): +def trapezoidal_rule(boundary, steps): """ - Apply the extended trapezoidal rule to approximate the integral of function f(x) - over the interval defined by 'boundary' with the number of 'steps'. - - Args: - boundary (list of floats): A list containing the start and end values [a, b]. - steps (int): The number of steps or subintervals. - Returns: - float: Approximation of the integral of f(x) over [a, b]. - Examples: - >>> method_1([0, 1], 10) - 0.3349999999999999 + Implements the extended trapezoidal rule for numerical integration. + The function f(x) is provided below. + + :param boundary: List containing the lower and upper bounds of integration [a, b] + :param steps: The number of steps (intervals) used in the approximation + :return: The numerical approximation of the integral + + >>> abs(trapezoidal_rule([0, 1], 10) - 0.33333) < 0.01 + True + >>> abs(trapezoidal_rule([0, 1], 100) - 0.33333) < 0.01 + True + >>> abs(trapezoidal_rule([0, 2], 1000) - 2.66667) < 0.01 + True + >>> abs(trapezoidal_rule([1, 2], 1000) - 2.33333) < 0.01 + True """ h = (boundary[1] - boundary[0]) / steps a = boundary[0] @@ -31,7 +28,6 @@ def method_1(boundary, steps): 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 @@ -39,49 +35,66 @@ def method_1(boundary, steps): def make_points(a, b, h): """ - Generates points between 'a' and 'b' with step size 'h', excluding the end points. - Args: - a (float): Start value - b (float): End value - h (float): Step size - Examples: + Generates points between a and b with step size h for trapezoidal integration. + + :param a: The lower bound of integration + :param b: The upper bound of integration + :param h: The step size + :yield: The next x-value in the range (a, b) + + >>> list(make_points(0, 1, 0.1)) # doctest: +NORMALIZE_WHITESPACE + [0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6, 0.7, 0.7999999999999999, \ + 0.8999999999999999] >>> list(make_points(0, 10, 2.5)) [2.5, 5.0, 7.5] - >>> list(make_points(0, 10, 2)) [2, 4, 6, 8] - >>> list(make_points(1, 21, 5)) [6, 11, 16] - >>> list(make_points(1, 5, 2)) [3] - >>> list(make_points(1, 4, 3)) [] """ x = a + h while x <= (b - h): yield x - x = x + h + x += h -def f(x): # enter your function here +def f(x): """ - Example: - >>> f(2) - 4 + This is the function to integrate, f(x) = (x - 0)^2 = x^2. + + :param x: The input value + :return: The value of f(x) + + >>> f(0) + 0 + >>> f(1) + 1 + >>> f(0.5) + 0.25 """ - y = (x - 0) * (x - 0) - return y + return x**2 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) + """ + Main function to test the trapezoidal rule. + :a: Lower bound of integration + :b: Upper bound of integration + :steps: define number of steps or resolution + :boundary: define boundary of integration + + >>> main() + y = 0.3349999999999999 + """ + a = 0.0 + b = 1.0 + steps = 10.0 + boundary = [a, b] + y = trapezoidal_rule(boundary, steps) print(f"y = {y}") From 91a22c2e36477623b1f81518ff18c6f8617f81fb Mon Sep 17 00:00:00 2001 From: SEIKH NABAB UDDIN <93948993+nababuddin@users.noreply.github.com> Date: Tue, 31 Dec 2024 07:39:14 +0530 Subject: [PATCH 2851/2908] Create digital differential analyzer_line.py (#10929) * Create DDA_line_drawing.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename DDA_line_drawing.py to digital differential analyzer_line_drawing.py * Rename DDA_line_drawing.py to digital_differential_analyzer_line_drawing.py * Update digital_differential_analyzer_line_drawing.py * Update digital_differential_analyzer_line_drawing.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line_drawing.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line_drawing.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line_drawing.py * Update digital_differential_analyzer_line_drawing.py * Update digital_differential_analyzer_line_drawing.py * Update digital_differential_analyzer_line_drawing.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Tianyi Zheng * Update and rename digital_differential_analyzer_line_drawing.py to digital_differential_analyzer_line.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line.py * Update digital_differential_analyzer_line.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update digital_differential_analyzer_line.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review * Fix doctest * Trigger GH workflows * Fix function call in main block --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- .../digital_differential_analyzer_line.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 graphics/digital_differential_analyzer_line.py diff --git a/graphics/digital_differential_analyzer_line.py b/graphics/digital_differential_analyzer_line.py new file mode 100644 index 000000000000..a51cb0b8dc37 --- /dev/null +++ b/graphics/digital_differential_analyzer_line.py @@ -0,0 +1,52 @@ +import matplotlib.pyplot as plt + + +def digital_differential_analyzer_line( + p1: tuple[int, int], p2: tuple[int, int] +) -> list[tuple[int, int]]: + """ + Draws a line between two points using the DDA algorithm. + + Args: + - p1: Coordinates of the starting point. + - p2: Coordinates of the ending point. + Returns: + - List of coordinate points that form the line. + + >>> digital_differential_analyzer_line((1, 1), (4, 4)) + [(2, 2), (3, 3), (4, 4)] + """ + x1, y1 = p1 + x2, y2 = p2 + dx = x2 - x1 + dy = y2 - y1 + steps = max(abs(dx), abs(dy)) + x_increment = dx / float(steps) + y_increment = dy / float(steps) + coordinates = [] + x: float = x1 + y: float = y1 + for _ in range(steps): + x += x_increment + y += y_increment + coordinates.append((int(round(x)), int(round(y)))) + return coordinates + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + x1 = int(input("Enter the x-coordinate of the starting point: ")) + y1 = int(input("Enter the y-coordinate of the starting point: ")) + x2 = int(input("Enter the x-coordinate of the ending point: ")) + y2 = int(input("Enter the y-coordinate of the ending point: ")) + coordinates = digital_differential_analyzer_line((x1, y1), (x2, y2)) + x_points, y_points = zip(*coordinates) + plt.plot(x_points, y_points, marker="o") + plt.title("Digital Differential Analyzer Line Drawing Algorithm") + plt.xlabel("X-axis") + plt.ylabel("Y-axis") + plt.grid() + plt.show() From 12b1023a9d97ca19be761c10129cba5509c9b450 Mon Sep 17 00:00:00 2001 From: Kaustubh Mani Tripathi <129510465+kmtGryffindor20@users.noreply.github.com> Date: Tue, 31 Dec 2024 07:46:32 +0530 Subject: [PATCH 2852/2908] [ADDED] Implementation of Geometric Mean. (#10421) * [ADDED] Implementation of Geometric Mean. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rectified type hints * Typo * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng --- maths/geometric_mean.py | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 maths/geometric_mean.py diff --git a/maths/geometric_mean.py b/maths/geometric_mean.py new file mode 100644 index 000000000000..240d519ad398 --- /dev/null +++ b/maths/geometric_mean.py @@ -0,0 +1,55 @@ +""" +The Geometric Mean of n numbers is defined as the n-th root of the product +of those numbers. It is used to measure the central tendency of the numbers. +https://en.wikipedia.org/wiki/Geometric_mean +""" + + +def compute_geometric_mean(*args: int) -> float: + """ + Return the geometric mean of the argument numbers. + >>> compute_geometric_mean(2,8) + 4.0 + >>> compute_geometric_mean('a', 4) + Traceback (most recent call last): + ... + TypeError: Not a Number + >>> compute_geometric_mean(5, 125) + 25.0 + >>> compute_geometric_mean(1, 0) + 0.0 + >>> compute_geometric_mean(1, 5, 25, 5) + 5.0 + >>> compute_geometric_mean(2, -2) + Traceback (most recent call last): + ... + ArithmeticError: Cannot Compute Geometric Mean for these numbers. + >>> compute_geometric_mean(-5, 25, 1) + -5.0 + """ + product = 1 + for number in args: + if not isinstance(number, int) and not isinstance(number, float): + raise TypeError("Not a Number") + product *= number + # Cannot calculate the even root for negative product. + # Frequently they are restricted to being positive. + if product < 0 and len(args) % 2 == 0: + raise ArithmeticError("Cannot Compute Geometric Mean for these numbers.") + mean = abs(product) ** (1 / len(args)) + # Since python calculates complex roots for negative products with odd roots. + if product < 0: + mean = -mean + # Since it does floating point arithmetic, it gives 64**(1/3) as 3.99999996 + possible_mean = float(round(mean)) + # To check if the rounded number is actually the mean. + if possible_mean ** len(args) == product: + mean = possible_mean + return mean + + +if __name__ == "__main__": + from doctest import testmod + + testmod(name="compute_geometric_mean") + print(compute_geometric_mean(-3, -27)) From bae33acf9008aa6c80351b3c68f492ba0c4a1352 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:12:13 +0100 Subject: [PATCH 2853/2908] [pre-commit.ci] pre-commit autoupdate (#12507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.4 → v0.8.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.4...v0.8.6) - [github.com/pre-commit/mirrors-mypy: v1.14.0 → v1.14.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.14.0...v1.14.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 71ac72c29b5f..ec1dbca3a41c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.4 + rev: v0.8.6 hooks: - id: ruff - id: ruff-format @@ -47,7 +47,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.0 + rev: v1.14.1 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 1248a290d294..3f0a5dbb140f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -462,6 +462,7 @@ ## Graphics * [Bezier Curve](graphics/bezier_curve.py) + * [Digital Differential Analyzer Line](graphics/digital_differential_analyzer_line.py) * [Vector3 For 2D Rendering](graphics/vector3_for_2d_rendering.py) ## Graphs @@ -663,6 +664,7 @@ * [Gamma](maths/gamma.py) * [Gaussian](maths/gaussian.py) * [Gcd Of N Numbers](maths/gcd_of_n_numbers.py) + * [Geometric Mean](maths/geometric_mean.py) * [Germain Primes](maths/germain_primes.py) * [Greatest Common Divisor](maths/greatest_common_divisor.py) * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py) From b653aee627a95de423f1cad97f283de904271ff7 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 12 Jan 2025 19:05:08 +0300 Subject: [PATCH 2854/2908] Fix ruff (#12515) * Empty commit * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * Fix * Fix * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: MaximSmolskiy --- DIRECTORY.md | 4 ++-- ciphers/{base64.py => base64_cipher.py} | 0 project_euler/problem_002/sol4.py | 2 +- strings/{wave.py => wave_string.py} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename ciphers/{base64.py => base64_cipher.py} (100%) rename strings/{wave.py => wave_string.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 3f0a5dbb140f..aad6c72aa8ee 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -86,7 +86,7 @@ * [Baconian Cipher](ciphers/baconian_cipher.py) * [Base16](ciphers/base16.py) * [Base32](ciphers/base32.py) - * [Base64](ciphers/base64.py) + * [Base64 Cipher](ciphers/base64_cipher.py) * [Base85](ciphers/base85.py) * [Beaufort Cipher](ciphers/beaufort_cipher.py) * [Bifid](ciphers/bifid.py) @@ -1331,7 +1331,7 @@ * [Title](strings/title.py) * [Top K Frequent Words](strings/top_k_frequent_words.py) * [Upper](strings/upper.py) - * [Wave](strings/wave.py) + * [Wave String](strings/wave_string.py) * [Wildcard Pattern Matching](strings/wildcard_pattern_matching.py) * [Word Occurrence](strings/word_occurrence.py) * [Word Patterns](strings/word_patterns.py) diff --git a/ciphers/base64.py b/ciphers/base64_cipher.py similarity index 100% rename from ciphers/base64.py rename to ciphers/base64_cipher.py diff --git a/project_euler/problem_002/sol4.py b/project_euler/problem_002/sol4.py index 3a2e4fce341c..a13d34fd760e 100644 --- a/project_euler/problem_002/sol4.py +++ b/project_euler/problem_002/sol4.py @@ -61,7 +61,7 @@ def solution(n: int = 4000000) -> int: if n <= 0: raise ValueError("Parameter n must be greater than or equal to one.") getcontext().prec = 100 - phi = (Decimal(5) ** Decimal(0.5) + 1) / Decimal(2) + phi = (Decimal(5) ** Decimal("0.5") + 1) / Decimal(2) index = (math.floor(math.log(n * (phi + 2), phi) - 1) // 3) * 3 + 2 num = Decimal(round(phi ** Decimal(index + 1))) / (phi + 2) diff --git a/strings/wave.py b/strings/wave_string.py similarity index 100% rename from strings/wave.py rename to strings/wave_string.py From 4c92de5e03310811a376058e110db8d615769087 Mon Sep 17 00:00:00 2001 From: Sanjay Muthu Date: Mon, 13 Jan 2025 05:05:22 +0530 Subject: [PATCH 2855/2908] Fix dynamic_programming/longest_increasing_subsequence.py (#12517) * Fix #12510 * Added the doctest mentioned in the issue * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed Grammer Mistake * Update longest_increasing_subsequence.py * Update longest_increasing_subsequence.py * Update longest_increasing_subsequence.py * Update longest_increasing_subsequence.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- dynamic_programming/longest_increasing_subsequence.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index d839757f6da5..1863a882c41e 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -24,8 +24,10 @@ def longest_subsequence(array: list[int]) -> list[int]: # This function is recu [10, 22, 33, 41, 60, 80] >>> longest_subsequence([4, 8, 7, 5, 1, 12, 2, 3, 9]) [1, 2, 3, 9] + >>> longest_subsequence([28, 26, 12, 23, 35, 39]) + [12, 23, 35, 39] >>> longest_subsequence([9, 8, 7, 6, 5, 7]) - [8] + [5, 7] >>> longest_subsequence([1, 1, 1]) [1, 1, 1] >>> longest_subsequence([]) @@ -44,7 +46,7 @@ def longest_subsequence(array: list[int]) -> list[int]: # This function is recu while not is_found and i < array_length: if array[i] < pivot: is_found = True - temp_array = [element for element in array[i:] if element >= array[i]] + temp_array = array[i:] temp_array = longest_subsequence(temp_array) if len(temp_array) > len(longest_subseq): longest_subseq = temp_array From 787aa5d3b59640b2d9161b56ca8fde763597efe4 Mon Sep 17 00:00:00 2001 From: Siddhant <87547498+Siddhant231xyz@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:54:23 -0500 Subject: [PATCH 2856/2908] doctest all_combinations.py (#12506) * doctest in all_combinations.py * added doctest in all_combinations.py * doctests in all_combinations.py * add doctest all_combinations.py * add --------- Co-authored-by: Siddhant Jain --- backtracking/all_combinations.py | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 390decf3a05b..1d15c6263e14 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -12,6 +12,8 @@ def combination_lists(n: int, k: int) -> list[list[int]]: """ + Generates all possible combinations of k numbers out of 1 ... n using itertools. + >>> combination_lists(n=4, k=2) [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] """ @@ -20,6 +22,8 @@ def combination_lists(n: int, k: int) -> list[list[int]]: def generate_all_combinations(n: int, k: int) -> list[list[int]]: """ + Generates all possible combinations of k numbers out of 1 ... n using backtracking. + >>> generate_all_combinations(n=4, k=2) [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] >>> generate_all_combinations(n=0, k=0) @@ -34,6 +38,14 @@ def generate_all_combinations(n: int, k: int) -> list[list[int]]: ValueError: n must not be negative >>> generate_all_combinations(n=5, k=4) [[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]] + >>> generate_all_combinations(n=3, k=3) + [[1, 2, 3]] + >>> generate_all_combinations(n=3, k=1) + [[1], [2], [3]] + >>> generate_all_combinations(n=1, k=0) + [[]] + >>> generate_all_combinations(n=1, k=1) + [[1]] >>> from itertools import combinations >>> all(generate_all_combinations(n, k) == combination_lists(n, k) ... for n in range(1, 6) for k in range(1, 6)) @@ -56,6 +68,28 @@ def create_all_state( current_list: list[int], total_list: list[list[int]], ) -> None: + """ + Helper function to recursively build all combinations. + + >>> create_all_state(1, 4, 2, [], result := []) + >>> result + [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] + >>> create_all_state(1, 3, 3, [], result := []) + >>> result + [[1, 2, 3]] + >>> create_all_state(2, 2, 1, [1], result := []) + >>> result + [[1, 2]] + >>> create_all_state(1, 0, 0, [], result := []) + >>> result + [[]] + >>> create_all_state(1, 4, 0, [1, 2], result := []) + >>> result + [[1, 2]] + >>> create_all_state(5, 4, 2, [1, 2], result := []) + >>> result + [] + """ if level == 0: total_list.append(current_list[:]) return From cfcc84edf7d14cb56f52ba6fbd8c8deb2e9a7852 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Mon, 13 Jan 2025 23:49:07 +0300 Subject: [PATCH 2857/2908] Fix build (#12516) * Empty commit * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * Apply suggestions from code review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- web_programming/current_stock_price.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index d0a65e9aac84..573e1f575c8e 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -15,7 +15,7 @@ def stock_price(symbol: str = "AAPL") -> str: """ >>> stock_price("EEEE") - '-' + '- ' >>> isinstance(float(stock_price("GOOG")),float) True """ @@ -24,12 +24,10 @@ def stock_price(symbol: str = "AAPL") -> str: url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10 ).text soup = BeautifulSoup(yahoo_finance_source, "html.parser") - specific_fin_streamer_tag = soup.find("fin-streamer", {"data-testid": "qsp-price"}) - if specific_fin_streamer_tag: - text = specific_fin_streamer_tag.get_text() - return text - return "No tag with the specified data-test attribute found." + if specific_fin_streamer_tag := soup.find("span", {"data-testid": "qsp-price"}): + return specific_fin_streamer_tag.get_text() + return "No tag with the specified data-testid attribute found." # Search for the symbol at https://finance.yahoo.com/lookup From 4fe50bc1fcf82fceb61839bae314720c092c0692 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:52:12 +0100 Subject: [PATCH 2858/2908] [pre-commit.ci] pre-commit autoupdate -- ruff 2025 stable format (#12521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.6 → v0.9.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.6...v0.9.1) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/dual_number_automatic_differentiation.py * Update maths/dual_number_automatic_differentiation.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dual_number_automatic_differentiation.py * Update dual_number_automatic_differentiation.py * No tag with the specified data-test attribute found. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- ciphers/base64_cipher.py | 12 +++--- ciphers/caesar_cipher.py | 2 +- computer_vision/flip_augmentation.py | 2 +- computer_vision/mosaic_augmentation.py | 2 +- .../hashing/number_theory/prime_numbers.py | 6 +-- data_structures/heap/min_heap.py | 6 +-- data_structures/kd_tree/tests/test_kdtree.py | 12 +++--- .../suffix_tree/tests/test_suffix_tree.py | 24 +++++------ dynamic_programming/climbing_stairs.py | 6 +-- .../iterating_through_submasks.py | 6 +-- .../matrix_chain_multiplication.py | 2 +- .../linear_discriminant_analysis.py | 4 +- .../dual_number_automatic_differentiation.py | 6 +-- maths/max_sum_sliding_window.py | 4 +- .../integration_by_simpson_approx.py | 12 +++--- maths/prime_check.py | 12 +++--- maths/primelib.py | 42 +++++++++---------- matrix/matrix_based_game.py | 2 +- neural_network/input_data.py | 6 +-- scripts/validate_solutions.py | 6 +-- strings/jaro_winkler.py | 4 +- web_programming/fetch_anime_and_play.py | 4 +- 23 files changed, 93 insertions(+), 91 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec1dbca3a41c..3b1dd9658d7f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.6 + rev: v0.9.1 hooks: - id: ruff - id: ruff-format diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py index 2b950b1be37d..038d13963d95 100644 --- a/ciphers/base64_cipher.py +++ b/ciphers/base64_cipher.py @@ -105,13 +105,13 @@ def base64_decode(encoded_data: str) -> bytes: # 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." + 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." + 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" diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 9c096fe8a7da..1cf4d67cbaed 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -225,7 +225,7 @@ def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str if __name__ == "__main__": while True: - print(f'\n{"-" * 10}\n Menu\n{"-" * 10}') + print(f"\n{'-' * 10}\n Menu\n{'-' * 10}") print(*["1.Encrypt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") # get user input diff --git a/computer_vision/flip_augmentation.py b/computer_vision/flip_augmentation.py index 77a8cbd7b14f..7301424824df 100644 --- a/computer_vision/flip_augmentation.py +++ b/computer_vision/flip_augmentation.py @@ -33,7 +33,7 @@ def main() -> None: file_name = paths[index].split(os.sep)[-1].rsplit(".", 1)[0] file_root = f"{OUTPUT_DIR}/{file_name}_FLIP_{letter_code}" cv2.imwrite(f"{file_root}.jpg", image, [cv2.IMWRITE_JPEG_QUALITY, 85]) - print(f"Success {index+1}/{len(new_images)} with {file_name}") + print(f"Success {index + 1}/{len(new_images)} with {file_name}") annos_list = [] for anno in new_annos[index]: obj = f"{anno[0]} {anno[1]} {anno[2]} {anno[3]} {anno[4]}" diff --git a/computer_vision/mosaic_augmentation.py b/computer_vision/mosaic_augmentation.py index cd923dfe095f..d881347121ea 100644 --- a/computer_vision/mosaic_augmentation.py +++ b/computer_vision/mosaic_augmentation.py @@ -41,7 +41,7 @@ def main() -> None: file_name = path.split(os.sep)[-1].rsplit(".", 1)[0] file_root = f"{OUTPUT_DIR}/{file_name}_MOSAIC_{letter_code}" cv2.imwrite(f"{file_root}.jpg", new_image, [cv2.IMWRITE_JPEG_QUALITY, 85]) - print(f"Succeeded {index+1}/{NUMBER_IMAGES} with {file_name}") + print(f"Succeeded {index + 1}/{NUMBER_IMAGES} with {file_name}") annos_list = [] for anno in new_annos: width = anno[3] - anno[1] diff --git a/data_structures/hashing/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py index 2549a1477b2b..82071b5e9f09 100644 --- a/data_structures/hashing/number_theory/prime_numbers.py +++ b/data_structures/hashing/number_theory/prime_numbers.py @@ -32,9 +32,9 @@ def is_prime(number: int) -> bool: """ # 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" + ) if 1 < number < 4: # 2 and 3 are primes diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index ce7ed570a58d..577b98d788a1 100644 --- a/data_structures/heap/min_heap.py +++ b/data_structures/heap/min_heap.py @@ -124,9 +124,9 @@ def is_empty(self): return len(self.heap) == 0 def decrease_key(self, node, new_value): - assert ( - self.heap[self.idx_of_element[node]].val > new_value - ), "newValue must be less that current value" + assert self.heap[self.idx_of_element[node]].val > new_value, ( + "newValue must be less that current value" + ) node.val = new_value self.heap_dict[node.name] = new_value self.sift_up(self.idx_of_element[node]) diff --git a/data_structures/kd_tree/tests/test_kdtree.py b/data_structures/kd_tree/tests/test_kdtree.py index dce5e4f34ff4..d6a4a66dd24d 100644 --- a/data_structures/kd_tree/tests/test_kdtree.py +++ b/data_structures/kd_tree/tests/test_kdtree.py @@ -48,14 +48,14 @@ def test_build_kdtree(num_points, cube_size, num_dimensions, depth, expected_res assert kdtree is not None, "Expected a KDNode, got None" # Check if root has correct dimensions - assert ( - len(kdtree.point) == num_dimensions - ), f"Expected point dimension {num_dimensions}, got {len(kdtree.point)}" + assert len(kdtree.point) == num_dimensions, ( + f"Expected point dimension {num_dimensions}, got {len(kdtree.point)}" + ) # Check that the tree is balanced to some extent (simplistic check) - assert isinstance( - kdtree, KDNode - ), f"Expected KDNode instance, got {type(kdtree)}" + assert isinstance(kdtree, KDNode), ( + f"Expected KDNode instance, got {type(kdtree)}" + ) def test_nearest_neighbour_search(): diff --git a/data_structures/suffix_tree/tests/test_suffix_tree.py b/data_structures/suffix_tree/tests/test_suffix_tree.py index 45c6790ac48a..c9dbe199d19d 100644 --- a/data_structures/suffix_tree/tests/test_suffix_tree.py +++ b/data_structures/suffix_tree/tests/test_suffix_tree.py @@ -22,18 +22,18 @@ def test_search_existing_patterns(self) -> None: patterns = ["ana", "ban", "na"] for pattern in patterns: with self.subTest(pattern=pattern): - assert self.suffix_tree.search( - pattern - ), f"Pattern '{pattern}' should be found." + assert self.suffix_tree.search(pattern), ( + f"Pattern '{pattern}' should be found." + ) def test_search_non_existing_patterns(self) -> None: """Test searching for patterns that do not exist in the suffix tree.""" patterns = ["xyz", "apple", "cat"] for pattern in patterns: with self.subTest(pattern=pattern): - assert not self.suffix_tree.search( - pattern - ), f"Pattern '{pattern}' should not be found." + assert not self.suffix_tree.search(pattern), ( + f"Pattern '{pattern}' should not be found." + ) def test_search_empty_pattern(self) -> None: """Test searching for an empty pattern.""" @@ -41,18 +41,18 @@ def test_search_empty_pattern(self) -> None: def test_search_full_text(self) -> None: """Test searching for the full text.""" - assert self.suffix_tree.search( - self.text - ), "The full text should be found in the suffix tree." + assert self.suffix_tree.search(self.text), ( + "The full text should be found in the suffix tree." + ) def test_search_substrings(self) -> None: """Test searching for substrings of the full text.""" substrings = ["ban", "ana", "a", "na"] for substring in substrings: with self.subTest(substring=substring): - assert self.suffix_tree.search( - substring - ), f"Substring '{substring}' should be found." + assert self.suffix_tree.search(substring), ( + f"Substring '{substring}' should be found." + ) if __name__ == "__main__": diff --git a/dynamic_programming/climbing_stairs.py b/dynamic_programming/climbing_stairs.py index d6273d025f08..38bdb427eedc 100644 --- a/dynamic_programming/climbing_stairs.py +++ b/dynamic_programming/climbing_stairs.py @@ -25,9 +25,9 @@ def climb_stairs(number_of_steps: int) -> int: ... AssertionError: number_of_steps needs to be positive integer, your input -7 """ - assert ( - isinstance(number_of_steps, int) and number_of_steps > 0 - ), f"number_of_steps needs to be positive integer, your input {number_of_steps}" + assert isinstance(number_of_steps, int) and number_of_steps > 0, ( + f"number_of_steps needs to be positive integer, your input {number_of_steps}" + ) if number_of_steps == 1: return 1 previous, current = 1, 1 diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index 372dd2c74a71..efab6dacff3f 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -37,9 +37,9 @@ def list_of_submasks(mask: int) -> list[int]: """ - assert ( - isinstance(mask, int) and mask > 0 - ), f"mask needs to be positive integer, your input {mask}" + assert isinstance(mask, int) and mask > 0, ( + f"mask needs to be positive integer, your input {mask}" + ) """ first submask iterated will be mask itself then operation will be performed diff --git a/dynamic_programming/matrix_chain_multiplication.py b/dynamic_programming/matrix_chain_multiplication.py index 10e136b9f0db..4c0c771f9092 100644 --- a/dynamic_programming/matrix_chain_multiplication.py +++ b/dynamic_programming/matrix_chain_multiplication.py @@ -134,7 +134,7 @@ def elapsed_time(msg: str) -> Iterator: start = perf_counter_ns() yield - print(f"Finished: {msg} in {(perf_counter_ns() - start) / 10 ** 9} seconds.") + print(f"Finished: {msg} in {(perf_counter_ns() - start) / 10**9} seconds.") if __name__ == "__main__": diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 86f28aef671a..8528ccbbae51 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -322,7 +322,7 @@ def main(): user_count = valid_input( input_type=int, condition=lambda x: x > 0, - input_msg=(f"Enter The number of instances for class_{i+1}: "), + input_msg=(f"Enter The number of instances for class_{i + 1}: "), err_msg="Number of instances should be positive!", ) counts.append(user_count) @@ -333,7 +333,7 @@ def main(): for a in range(n_classes): user_mean = valid_input( input_type=float, - input_msg=(f"Enter the value of mean for class_{a+1}: "), + input_msg=(f"Enter the value of mean for class_{a + 1}: "), err_msg="This is an invalid value.", ) user_means.append(user_mean) diff --git a/maths/dual_number_automatic_differentiation.py b/maths/dual_number_automatic_differentiation.py index f98997c8be4d..09aeb17a4aea 100644 --- a/maths/dual_number_automatic_differentiation.py +++ b/maths/dual_number_automatic_differentiation.py @@ -17,10 +17,8 @@ def __init__(self, real, rank): self.duals = rank def __repr__(self): - return ( - f"{self.real}+" - f"{'+'.join(str(dual)+'E'+str(n+1)for n,dual in enumerate(self.duals))}" - ) + s = "+".join(f"{dual}E{n}" for n, dual in enumerate(self.duals, 1)) + return f"{self.real}+{s}" def reduce(self): cur = self.duals.copy() diff --git a/maths/max_sum_sliding_window.py b/maths/max_sum_sliding_window.py index 090117429604..c7492978a6c9 100644 --- a/maths/max_sum_sliding_window.py +++ b/maths/max_sum_sliding_window.py @@ -43,4 +43,6 @@ def max_sum_in_array(array: list[int], k: int) -> int: 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)}") + print( + f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array, k)}" + ) diff --git a/maths/numerical_analysis/integration_by_simpson_approx.py b/maths/numerical_analysis/integration_by_simpson_approx.py index 934299997aac..043f3a9a72af 100644 --- a/maths/numerical_analysis/integration_by_simpson_approx.py +++ b/maths/numerical_analysis/integration_by_simpson_approx.py @@ -88,18 +88,18 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo AssertionError: precision should be positive integer your input : -1 """ - assert callable( - function - ), f"the function(object) passed should be callable your input : {function}" + assert callable(function), ( + f"the function(object) passed should be callable your input : {function}" + ) assert isinstance(a, (float, int)), f"a should be float or integer your input : {a}" assert isinstance(function(a), (float, int)), ( "the function should return integer or float return type of your function, " f"{type(a)}" ) assert isinstance(b, (float, 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}" + assert isinstance(precision, int) and precision > 0, ( + f"precision should be positive integer your input : {precision}" + ) # just applying the formula of simpson for approximate integration written in # mentioned article in first comment of this file and above this function diff --git a/maths/prime_check.py b/maths/prime_check.py index f1bc4def2469..a757c4108f24 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -73,12 +73,12 @@ def test_primes(self): def test_not_primes(self): with pytest.raises(ValueError): is_prime(-19) - assert not is_prime( - 0 - ), "Zero doesn't have any positive factors, primes must have exactly two." - assert not is_prime( - 1 - ), "One only has 1 positive factor, primes must have exactly two." + assert not is_prime(0), ( + "Zero doesn't have any positive factors, primes must have exactly two." + ) + assert not is_prime(1), ( + "One only has 1 positive factor, primes must have exactly two." + ) assert not is_prime(2 * 2) assert not is_prime(2 * 3) assert not is_prime(3 * 3) diff --git a/maths/primelib.py b/maths/primelib.py index a26b0eaeb328..3a966e5cd936 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -66,9 +66,9 @@ def is_prime(number: int) -> bool: """ # 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 @@ -254,9 +254,9 @@ def greatest_prime_factor(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 = 0 @@ -296,9 +296,9 @@ def smallest_prime_factor(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 = 0 @@ -399,9 +399,9 @@ def goldbach(number): """ # precondition - assert ( - isinstance(number, int) and (number > 2) and is_even(number) - ), "'number' must been an int, even and > 2" + assert isinstance(number, int) and (number > 2) and is_even(number), ( + "'number' must been an int, even and > 2" + ) ans = [] # this list will returned @@ -525,9 +525,9 @@ def kg_v(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 @@ -574,9 +574,9 @@ def get_prime(n): ans += 1 # precondition - assert isinstance(ans, int) and is_prime( - ans - ), "'ans' must been a prime number and from type int" + assert isinstance(ans, int) and is_prime(ans), ( + "'ans' must been a prime number and from type int" + ) return ans @@ -705,9 +705,9 @@ def is_perfect_number(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 = get_divisors(number) diff --git a/matrix/matrix_based_game.py b/matrix/matrix_based_game.py index 1ff0cbe93435..6181086c6704 100644 --- a/matrix/matrix_based_game.py +++ b/matrix/matrix_based_game.py @@ -273,7 +273,7 @@ def process_game(size: int, matrix: list[str], moves: list[tuple[int, int]]) -> size = int(input("Enter the size of the matrix: ")) validate_matrix_size(size) print(f"Enter the {size} rows of the matrix:") - matrix = [input(f"Row {i+1}: ") for i in range(size)] + matrix = [input(f"Row {i + 1}: ") for i in range(size)] validate_matrix_content(matrix, size) moves_input = input("Enter the moves (e.g., '0 0, 1 1'): ") moves = parse_moves(moves_input) diff --git a/neural_network/input_data.py b/neural_network/input_data.py index 72debabb566a..3a8628f939f8 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -160,9 +160,9 @@ def __init__( self._num_examples = 10000 self.one_hot = one_hot else: - assert ( - images.shape[0] == labels.shape[0] - ), f"images.shape: {images.shape} labels.shape: {labels.shape}" + assert images.shape[0] == labels.shape[0], ( + 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/scripts/validate_solutions.py b/scripts/validate_solutions.py index 325c245e0d77..df5d01086bbe 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -94,6 +94,6 @@ def test_project_euler(solution_path: pathlib.Path) -> None: solution_module = convert_path_to_module(solution_path) answer = str(solution_module.solution()) answer = hashlib.sha256(answer.encode()).hexdigest() - assert ( - answer == expected - ), f"Expected solution to {problem_number} to have hash {expected}, got {answer}" + assert answer == expected, ( + f"Expected solution to {problem_number} to have hash {expected}, got {answer}" + ) diff --git a/strings/jaro_winkler.py b/strings/jaro_winkler.py index cae2068fabc1..0ce5d83b3c41 100644 --- a/strings/jaro_winkler.py +++ b/strings/jaro_winkler.py @@ -33,7 +33,9 @@ def get_matched_characters(_str1: str, _str2: str) -> str: right = int(min(i + limit + 1, len(_str2))) if char in _str2[left:right]: matched.append(char) - _str2 = f"{_str2[0:_str2.index(char)]} {_str2[_str2.index(char) + 1:]}" + _str2 = ( + f"{_str2[0 : _str2.index(char)]} {_str2[_str2.index(char) + 1 :]}" + ) return "".join(matched) diff --git a/web_programming/fetch_anime_and_play.py b/web_programming/fetch_anime_and_play.py index fd7c3a3a7381..e56b7124eeb5 100644 --- a/web_programming/fetch_anime_and_play.py +++ b/web_programming/fetch_anime_and_play.py @@ -165,7 +165,7 @@ def get_anime_episode(episode_endpoint: str) -> list: print(f"Found {len(anime_list)} results: ") for i, anime in enumerate(anime_list): anime_title = anime["title"] - print(f"{i+1}. {anime_title}") + print(f"{i + 1}. {anime_title}") anime_choice = int(input("\nPlease choose from the following list: ").strip()) chosen_anime = anime_list[anime_choice - 1] @@ -177,7 +177,7 @@ def get_anime_episode(episode_endpoint: str) -> list: else: print(f"Found {len(episode_list)} results: ") for i, episode in enumerate(episode_list): - print(f"{i+1}. {episode['title']}") + print(f"{i + 1}. {episode['title']}") episode_choice = int(input("\nChoose an episode by serial no: ").strip()) chosen_episode = episode_list[episode_choice - 1] From f04d308431266759dce36265d8701dfb106932af Mon Sep 17 00:00:00 2001 From: Sanjay Muthu Date: Wed, 15 Jan 2025 02:19:04 +0530 Subject: [PATCH 2859/2908] Create longest_increasing_subsequence_iterative.py (#12524) * Create longest_increasing_subsequence_iterative.py * Update longest_increasing_subsequence_iterative.py * Update longest_increasing_subsequence_iterative.py --------- Co-authored-by: Maxim Smolskiy --- ...ongest_increasing_subsequence_iterative.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 dynamic_programming/longest_increasing_subsequence_iterative.py diff --git a/dynamic_programming/longest_increasing_subsequence_iterative.py b/dynamic_programming/longest_increasing_subsequence_iterative.py new file mode 100644 index 000000000000..665c86a35d2e --- /dev/null +++ b/dynamic_programming/longest_increasing_subsequence_iterative.py @@ -0,0 +1,72 @@ +""" +Author : Sanjay Muthu + +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, 50, 60, 80]`` as output +""" + +from __future__ import annotations + +import copy + + +def longest_subsequence(array: list[int]) -> list[int]: + """ + Some examples + + >>> longest_subsequence([10, 22, 9, 33, 21, 50, 41, 60, 80]) + [10, 22, 33, 50, 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]) + [7, 7] + >>> longest_subsequence([28, 26, 12, 23, 35, 39]) + [12, 23, 35, 39] + >>> longest_subsequence([1, 1, 1]) + [1, 1, 1] + >>> longest_subsequence([]) + [] + """ + n = len(array) + # The longest increasing subsequence ending at array[i] + longest_increasing_subsequence = [] + for i in range(n): + longest_increasing_subsequence.append([array[i]]) + + for i in range(1, n): + for prev in range(i): + # If array[prev] is less than or equal to array[i], then + # longest_increasing_subsequence[prev] + array[i] + # is a valid increasing subsequence + + # longest_increasing_subsequence[i] is only set to + # longest_increasing_subsequence[prev] + array[i] if the length is longer. + + if array[prev] <= array[i] and len( + longest_increasing_subsequence[prev] + ) + 1 > len(longest_increasing_subsequence[i]): + longest_increasing_subsequence[i] = copy.copy( + longest_increasing_subsequence[prev] + ) + longest_increasing_subsequence[i].append(array[i]) + + result: list[int] = [] + for i in range(n): + if len(longest_increasing_subsequence[i]) > len(result): + result = longest_increasing_subsequence[i] + + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0040ad47f928f299dadbb97c5cea00bc1daf8c75 Mon Sep 17 00:00:00 2001 From: aydinomer00 <109145643+aydinomer00@users.noreply.github.com> Date: Wed, 15 Jan 2025 00:24:36 +0300 Subject: [PATCH 2860/2908] Add butterfly pattern implementation (#12493) * Add butterfly pattern implementation * Add butterfly pattern implementation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add finalized butterfly pattern implementation and test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Delete graphics/test_butterfly_pattern.py * Update butterfly_pattern.py * Update butterfly_pattern.py * Update butterfly_pattern.py * Update butterfly_pattern.py * Update butterfly_pattern.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- graphics/butterfly_pattern.py | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 graphics/butterfly_pattern.py diff --git a/graphics/butterfly_pattern.py b/graphics/butterfly_pattern.py new file mode 100644 index 000000000000..7913b03a7e95 --- /dev/null +++ b/graphics/butterfly_pattern.py @@ -0,0 +1,46 @@ +def butterfly_pattern(n: int) -> str: + """ + Creates a butterfly pattern of size n and returns it as a string. + + >>> print(butterfly_pattern(3)) + * * + ** ** + ***** + ** ** + * * + >>> print(butterfly_pattern(5)) + * * + ** ** + *** *** + **** **** + ********* + **** **** + *** *** + ** ** + * * + """ + result = [] + + # Upper part + for i in range(1, n): + left_stars = "*" * i + spaces = " " * (2 * (n - i) - 1) + right_stars = "*" * i + result.append(left_stars + spaces + right_stars) + + # Middle part + result.append("*" * (2 * n - 1)) + + # Lower part + for i in range(n - 1, 0, -1): + left_stars = "*" * i + spaces = " " * (2 * (n - i) - 1) + right_stars = "*" * i + result.append(left_stars + spaces + right_stars) + + return "\n".join(result) + + +if __name__ == "__main__": + n = int(input("Enter the size of the butterfly pattern: ")) + print(butterfly_pattern(n)) From 533767ff46bbcf5c594ff8196894ae2e8130bc3e Mon Sep 17 00:00:00 2001 From: Nguyen Thi Thanh Minh <140883075+minh-swinburne@users.noreply.github.com> Date: Sat, 18 Jan 2025 10:07:44 +0700 Subject: [PATCH 2861/2908] Doomsday Algorithm: Fix leap year check (#12396) * Fix leap year check Replace `!=` in `(year % 400) != 0` (line 49) with `==` Justification: Years that are divisible by 100 (centurian == 100) but not by 400 (year % 400 != 0) are skipped and NOT leap year. * Update parentheses Correct the parentheses to make clear the precedence of the conditional check * Update other/doomsday.py Co-authored-by: Tianyi Zheng --------- Co-authored-by: Tianyi Zheng --- other/doomsday.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/doomsday.py b/other/doomsday.py index d8fe261156a1..be3b18eeecaa 100644 --- a/other/doomsday.py +++ b/other/doomsday.py @@ -46,7 +46,7 @@ def get_week_day(year: int, month: int, day: int) -> str: ) % 7 day_anchor = ( DOOMSDAY_NOT_LEAP[month - 1] - if (year % 4 != 0) or (centurian == 0 and (year % 400) == 0) + if year % 4 != 0 or (centurian == 0 and year % 400 != 0) else DOOMSDAY_LEAP[month - 1] ) week_day = (dooms_day + day - day_anchor) % 7 From 91ebea1d99735ee2798b01ebcea0fc06e9a6af49 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 19 Jan 2025 08:33:35 +0100 Subject: [PATCH 2862/2908] Sphinx runs on ubuntu 24.04 arm (#12530) * Speed up our Sphinx GitHub Action with ARM # `runs-on: ubuntu-24.04-arm` https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources * updating DIRECTORY.md --------- Co-authored-by: cclauss --- .github/workflows/sphinx.yml | 2 +- DIRECTORY.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index d02435d98028..16ff284a74f2 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -23,7 +23,7 @@ concurrency: jobs: build_docs: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 diff --git a/DIRECTORY.md b/DIRECTORY.md index aad6c72aa8ee..941e30dfe721 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -377,6 +377,7 @@ * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) * [Longest Common Substring](dynamic_programming/longest_common_substring.py) * [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py) + * [Longest Increasing Subsequence Iterative](dynamic_programming/longest_increasing_subsequence_iterative.py) * [Longest Increasing Subsequence O Nlogn](dynamic_programming/longest_increasing_subsequence_o_nlogn.py) * [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py) * [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py) @@ -462,6 +463,7 @@ ## Graphics * [Bezier Curve](graphics/bezier_curve.py) + * [Butterfly Pattern](graphics/butterfly_pattern.py) * [Digital Differential Analyzer Line](graphics/digital_differential_analyzer_line.py) * [Vector3 For 2D Rendering](graphics/vector3_for_2d_rendering.py) From 1f74db0c06df7557e7ae3a17ebcc303f753f824e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 21:22:02 +0100 Subject: [PATCH 2863/2908] [pre-commit.ci] pre-commit autoupdate (#12536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.1 → v0.9.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.1...v0.9.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b1dd9658d7f..c4480f47faa1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.1 + rev: v0.9.2 hooks: - id: ruff - id: ruff-format From 9fb51b4169e0f7a4952e9eb460b91f4d7ffb819f Mon Sep 17 00:00:00 2001 From: Ronald Ngounou <74538524+ronaldngounou@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:02:46 +0100 Subject: [PATCH 2864/2908] Update docstrings in the functions definitions. (#11797) --- data_structures/arrays/sudoku_solver.py | 61 +++++++++++++++++-------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index fd1a4f3e37b8..e1714e57ece8 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -9,7 +9,9 @@ def cross(items_a, items_b): - "Cross product of elements in A and elements in B." + """ + Cross product of elements in A and elements in B. + """ return [a + b for a in items_a for b in items_b] @@ -27,7 +29,7 @@ def cross(items_a, items_b): def test(): - "A set of unit tests." + """A set of unit tests.""" assert len(squares) == 81 assert len(unitlist) == 27 assert all(len(units[s]) == 3 for s in squares) @@ -47,8 +49,10 @@ def test(): def parse_grid(grid): - """Convert grid to a dict of possible values, {square: digits}, or - return False if a contradiction is detected.""" + """ + Convert grid to a dict of possible values, {square: digits}, or + return False if a contradiction is detected. + """ ## To start, every square can be any digit; then assign values from the grid. values = {s: digits for s in squares} for s, d in grid_values(grid).items(): @@ -58,15 +62,19 @@ def parse_grid(grid): def grid_values(grid): - "Convert grid into a dict of {square: char} with '0' or '.' for empties." + """ + Convert grid into a dict of {square: char} with '0' or '.' for empties. + """ chars = [c for c in grid if c in digits or c in "0."] assert len(chars) == 81 return dict(zip(squares, chars)) def assign(values, s, d): - """Eliminate all the other values (except d) from values[s] and propagate. - Return values, except return False if a contradiction is detected.""" + """ + Eliminate all the other values (except d) from values[s] and propagate. + Return values, except return False if a contradiction is detected. + """ other_values = values[s].replace(d, "") if all(eliminate(values, s, d2) for d2 in other_values): return values @@ -75,8 +83,10 @@ def assign(values, s, d): def eliminate(values, s, d): - """Eliminate d from values[s]; propagate when values or places <= 2. - Return values, except return False if a contradiction is detected.""" + """ + Eliminate d from values[s]; propagate when values or places <= 2. + Return values, except return False if a contradiction is detected. + """ if d not in values[s]: return values ## Already eliminated values[s] = values[s].replace(d, "") @@ -99,7 +109,9 @@ def eliminate(values, s, d): def display(values): - "Display these values as a 2-D grid." + """ + Display these values as a 2-D grid. + """ width = 1 + max(len(values[s]) for s in squares) line = "+".join(["-" * (width * 3)] * 3) for r in rows: @@ -114,11 +126,14 @@ def display(values): def solve(grid): + """ + Solve the grid. + """ return search(parse_grid(grid)) def some(seq): - "Return some element of seq that is true." + """Return some element of seq that is true.""" for e in seq: if e: return e @@ -126,7 +141,9 @@ def some(seq): def search(values): - "Using depth-first search and propagation, try all possible values." + """ + Using depth-first search and propagation, try all possible values. + """ if values is False: return False ## Failed earlier if all(len(values[s]) == 1 for s in squares): @@ -137,9 +154,11 @@ def search(values): def solve_all(grids, name="", showif=0.0): - """Attempt to solve a sequence of grids. Report results. + """ + Attempt to solve a sequence of grids. Report results. When showif is a number of seconds, display puzzles that take longer. - When showif is None, don't display any puzzles.""" + When showif is None, don't display any puzzles. + """ def time_solve(grid): start = time.monotonic() @@ -162,7 +181,9 @@ def time_solve(grid): def solved(values): - "A puzzle is solved if each unit is a permutation of the digits 1 to 9." + """ + A puzzle is solved if each unit is a permutation of the digits 1 to 9. + """ def unitsolved(unit): return {values[s] for s in unit} == set(digits) @@ -177,9 +198,11 @@ def from_file(filename, sep="\n"): def random_puzzle(assignments=17): - """Make a random puzzle with N or more assignments. Restart on contradictions. + """ + Make a random puzzle with N or more assignments. Restart on contradictions. Note the resulting puzzle is not guaranteed to be solvable, but empirically - about 99.8% of them are solvable. Some have multiple solutions.""" + about 99.8% of them are solvable. Some have multiple solutions. + """ values = {s: digits for s in squares} for s in shuffled(squares): if not assign(values, s, random.choice(values[s])): @@ -191,7 +214,9 @@ def random_puzzle(assignments=17): def shuffled(seq): - "Return a randomly shuffled copy of the input sequence." + """ + Return a randomly shuffled copy of the input sequence. + """ seq = list(seq) random.shuffle(seq) return seq From c666db3729b6d9f73e2f7756a3974f53279caa50 Mon Sep 17 00:00:00 2001 From: Vijayalaxmi Wakode Date: Fri, 24 Jan 2025 03:31:47 +0530 Subject: [PATCH 2865/2908] Add Doc test bubble sort (#12070) * The string manipulation - replace() * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update replace.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updating DIRECTORY.md * Add doc test to bubble_sort * Update DIRECTORY.md * Delete strings/replace.py * Update bubble_sort.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: vijayalaxmi777 Co-authored-by: Maxim Smolskiy --- sorts/bubble_sort.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index bdf85c70dd35..9ec3d5384f38 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -85,6 +85,8 @@ def bubble_sort_recursive(collection: list[Any]) -> list[Any]: [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7] >>> bubble_sort_recursive([1, 3.3, 5, 7.7, 2, 4.4, 6]) [1, 2, 3.3, 4.4, 5, 6, 7.7] + >>> bubble_sort_recursive(['a', 'Z', 'B', 'C', 'A', 'c']) + ['A', 'B', 'C', 'Z', 'a', 'c'] >>> import random >>> collection_arg = random.sample(range(-50, 50), 100) >>> bubble_sort_recursive(collection_arg) == sorted(collection_arg) From 13e4d3e76cfaa74d8b14314d319fb6c089aa051e Mon Sep 17 00:00:00 2001 From: Rachel Spears <103690982+Rosepetal2022@users.noreply.github.com> Date: Thu, 23 Jan 2025 21:59:36 -0800 Subject: [PATCH 2866/2908] Fix error in avl_tree del_node function (#11510) * fixed error in del_node function * Update avl_tree.py --------- Co-authored-by: Maxim Smolskiy --- data_structures/binary_tree/avl_tree.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 9fca7237404c..8558305eefe4 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -221,6 +221,10 @@ def del_node(root: MyNode, data: Any) -> MyNode | None: else: root.set_right(del_node(right_child, data)) + # Re-fetch left_child and right_child references + left_child = root.get_left() + right_child = root.get_right() + if get_height(right_child) - get_height(left_child) == 2: assert right_child is not None if get_height(right_child.get_right()) > get_height(right_child.get_left()): From 6c92c5a539276d387b85eedc89be1f888962647d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:05:20 +0100 Subject: [PATCH 2867/2908] [pre-commit.ci] pre-commit autoupdate (#12542) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.2 → v0.9.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.2...v0.9.3) - [github.com/codespell-project/codespell: v2.3.0 → v2.4.0](https://github.com/codespell-project/codespell/compare/v2.3.0...v2.4.0) * Update trifid_cipher.py * Update pyproject.toml * Update trifid_cipher.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- ciphers/trifid_cipher.py | 4 ++-- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4480f47faa1..e34b563b05dd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,13 +16,13 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.2 + rev: v0.9.3 hooks: - id: ruff - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.0 hooks: - id: codespell additional_dependencies: diff --git a/ciphers/trifid_cipher.py b/ciphers/trifid_cipher.py index 9613cee0669d..13a47e9dd03b 100644 --- a/ciphers/trifid_cipher.py +++ b/ciphers/trifid_cipher.py @@ -88,7 +88,7 @@ def __prepare( ... KeyError: 'Length of alphabet has to be 27.' - Testing with punctuations that are not in the given alphabet + Testing with punctuation not in the given alphabet >>> __prepare('am i a boy?','abCdeFghijkLmnopqrStuVwxYZ+') Traceback (most recent call last): @@ -128,7 +128,7 @@ def encrypt_message( encrypt_message =============== - Encrypts a message using the trifid_cipher. Any punctuatuions that + Encrypts a message using the trifid_cipher. Any punctuatuion chars that would be used should be added to the alphabet. PARAMETERS diff --git a/pyproject.toml b/pyproject.toml index 7b7176705c44..2135f1f5825a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,7 +159,7 @@ lint.pylint.max-returns = 8 # default: 6 lint.pylint.max-statements = 88 # default: 50 [tool.codespell] -ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" +ignore-words-list = "3rt,abd,aer,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" skip = "./.*,*.json,*.lock,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" [tool.pytest.ini_options] From e59d819d091efdb30e385f4ecfe9ab5d36c3be71 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 20:47:41 +0100 Subject: [PATCH 2868/2908] [pre-commit.ci] pre-commit autoupdate (#12554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.3 → v0.9.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.3...v0.9.4) - [github.com/codespell-project/codespell: v2.4.0 → v2.4.1](https://github.com/codespell-project/codespell/compare/v2.4.0...v2.4.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e34b563b05dd..d9477e216b96 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,13 +16,13 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.3 + rev: v0.9.4 hooks: - id: ruff - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v2.4.0 + rev: v2.4.1 hooks: - id: codespell additional_dependencies: From 338cbafe0d5b07d57f83060ea0f9ba3a6c1155e7 Mon Sep 17 00:00:00 2001 From: lighting9999 <120090117+lighting9999@users.noreply.github.com> Date: Mon, 10 Feb 2025 01:51:18 +0800 Subject: [PATCH 2869/2908] Improve power.py (#12567) * Fix And Add power.py To fix the inaccuracies and allow handling of negative exponents and bases, the key issue lies in how negative numbers are handled in the power calculation, especially when dividing. ## Example Output: ```python >>> power(4, 6) 4096 >>> power(2, 3) 8 >>> power(-2, 3) -8 >>> power(2, -3) 0.125 >>> power(-2, -3) -0.125 ``` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update power.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update power.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update power.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update power.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- divide_and_conquer/power.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/divide_and_conquer/power.py b/divide_and_conquer/power.py index faf6a3476d40..492ee6dd12f0 100644 --- a/divide_and_conquer/power.py +++ b/divide_and_conquer/power.py @@ -1,4 +1,4 @@ -def actual_power(a: int, b: int): +def actual_power(a: int, b: int) -> int: """ Function using divide and conquer to calculate a^b. It only works for integer a,b. @@ -19,10 +19,12 @@ def actual_power(a: int, b: int): """ if b == 0: return 1 + half = actual_power(a, b // 2) + if (b % 2) == 0: - return actual_power(a, int(b / 2)) * actual_power(a, int(b / 2)) + return half * half else: - return a * actual_power(a, int(b / 2)) * actual_power(a, int(b / 2)) + return a * half * half def power(a: int, b: int) -> float: @@ -43,9 +45,9 @@ def power(a: int, b: int) -> float: -0.125 """ if b < 0: - return 1 / actual_power(a, b) + return 1 / actual_power(a, -b) return actual_power(a, b) if __name__ == "__main__": - print(power(-2, -3)) + print(power(-2, -3)) # output -0.125 From 738253e80030ffdd35ac57ff64cda816f85eda71 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:05:23 +0100 Subject: [PATCH 2870/2908] git mv data_structures/queue data_structures/queues (#12577) Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 18 +++++++++--------- data_structures/{queue => queues}/__init__.py | 0 .../{queue => queues}/circular_queue.py | 10 +++++++--- .../circular_queue_linked_list.py | 0 .../{queue => queues}/double_ended_queue.py | 0 .../{queue => queues}/linked_queue.py | 0 .../priority_queue_using_list.py | 6 +++--- .../{queue => queues}/queue_by_list.py | 0 .../{queue => queues}/queue_by_two_stacks.py | 0 .../{queue => queues}/queue_on_pseudo_stack.py | 0 11 files changed, 21 insertions(+), 17 deletions(-) rename data_structures/{queue => queues}/__init__.py (100%) rename data_structures/{queue => queues}/circular_queue.py (87%) rename data_structures/{queue => queues}/circular_queue_linked_list.py (100%) rename data_structures/{queue => queues}/double_ended_queue.py (100%) rename data_structures/{queue => queues}/linked_queue.py (100%) rename data_structures/{queue => queues}/priority_queue_using_list.py (96%) rename data_structures/{queue => queues}/queue_by_list.py (100%) rename data_structures/{queue => queues}/queue_by_two_stacks.py (100%) rename data_structures/{queue => queues}/queue_on_pseudo_stack.py (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d9477e216b96..a603109fd79f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.4 + rev: v0.9.6 hooks: - id: ruff - id: ruff-format @@ -47,7 +47,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.1 + rev: v1.15.0 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 941e30dfe721..a535f12cb59a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -275,15 +275,15 @@ * [Singly Linked List](data_structures/linked_list/singly_linked_list.py) * [Skip List](data_structures/linked_list/skip_list.py) * [Swap Nodes](data_structures/linked_list/swap_nodes.py) - * Queue - * [Circular Queue](data_structures/queue/circular_queue.py) - * [Circular Queue Linked List](data_structures/queue/circular_queue_linked_list.py) - * [Double Ended Queue](data_structures/queue/double_ended_queue.py) - * [Linked Queue](data_structures/queue/linked_queue.py) - * [Priority Queue Using List](data_structures/queue/priority_queue_using_list.py) - * [Queue By List](data_structures/queue/queue_by_list.py) - * [Queue By Two Stacks](data_structures/queue/queue_by_two_stacks.py) - * [Queue On Pseudo Stack](data_structures/queue/queue_on_pseudo_stack.py) + * Queues + * [Circular Queue](data_structures/queues/circular_queue.py) + * [Circular Queue Linked List](data_structures/queues/circular_queue_linked_list.py) + * [Double Ended Queue](data_structures/queues/double_ended_queue.py) + * [Linked Queue](data_structures/queues/linked_queue.py) + * [Priority Queue Using List](data_structures/queues/priority_queue_using_list.py) + * [Queue By List](data_structures/queues/queue_by_list.py) + * [Queue By Two Stacks](data_structures/queues/queue_by_two_stacks.py) + * [Queue On Pseudo Stack](data_structures/queues/queue_on_pseudo_stack.py) * Stacks * [Balanced Parentheses](data_structures/stacks/balanced_parentheses.py) * [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py) diff --git a/data_structures/queue/__init__.py b/data_structures/queues/__init__.py similarity index 100% rename from data_structures/queue/__init__.py rename to data_structures/queues/__init__.py diff --git a/data_structures/queue/circular_queue.py b/data_structures/queues/circular_queue.py similarity index 87% rename from data_structures/queue/circular_queue.py rename to data_structures/queues/circular_queue.py index f2fb4c01e467..efbf1efdc42d 100644 --- a/data_structures/queue/circular_queue.py +++ b/data_structures/queues/circular_queue.py @@ -17,7 +17,9 @@ def __len__(self) -> int: >>> len(cq) 0 >>> cq.enqueue("A") # doctest: +ELLIPSIS - >> cq.array + ['A', None, None, None, None] >>> len(cq) 1 """ @@ -51,11 +53,13 @@ def enqueue(self, data): as an index. >>> cq = CircularQueue(5) >>> cq.enqueue("A") # doctest: +ELLIPSIS - >> (cq.size, cq.first()) (1, 'A') >>> cq.enqueue("B") # doctest: +ELLIPSIS - >> cq.array + ['A', 'B', None, None, None] >>> (cq.size, cq.first()) (2, 'A') """ diff --git a/data_structures/queue/circular_queue_linked_list.py b/data_structures/queues/circular_queue_linked_list.py similarity index 100% rename from data_structures/queue/circular_queue_linked_list.py rename to data_structures/queues/circular_queue_linked_list.py diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queues/double_ended_queue.py similarity index 100% rename from data_structures/queue/double_ended_queue.py rename to data_structures/queues/double_ended_queue.py diff --git a/data_structures/queue/linked_queue.py b/data_structures/queues/linked_queue.py similarity index 100% rename from data_structures/queue/linked_queue.py rename to data_structures/queues/linked_queue.py diff --git a/data_structures/queue/priority_queue_using_list.py b/data_structures/queues/priority_queue_using_list.py similarity index 96% rename from data_structures/queue/priority_queue_using_list.py rename to data_structures/queues/priority_queue_using_list.py index f61b5e8e664d..15e56c557069 100644 --- a/data_structures/queue/priority_queue_using_list.py +++ b/data_structures/queues/priority_queue_using_list.py @@ -59,12 +59,12 @@ class FixedPriorityQueue: >>> fpq.dequeue() Traceback (most recent call last): ... - data_structures.queue.priority_queue_using_list.UnderFlowError: All queues are empty + data_structures.queues.priority_queue_using_list.UnderFlowError: All queues are empty >>> print(fpq) Priority 0: [] Priority 1: [] Priority 2: [] - """ + """ # noqa: E501 def __init__(self): self.queues = [ @@ -141,7 +141,7 @@ class ElementPriorityQueue: >>> epq.dequeue() Traceback (most recent call last): ... - data_structures.queue.priority_queue_using_list.UnderFlowError: The queue is empty + data_structures.queues.priority_queue_using_list.UnderFlowError: The queue is empty >>> print(epq) [] """ diff --git a/data_structures/queue/queue_by_list.py b/data_structures/queues/queue_by_list.py similarity index 100% rename from data_structures/queue/queue_by_list.py rename to data_structures/queues/queue_by_list.py diff --git a/data_structures/queue/queue_by_two_stacks.py b/data_structures/queues/queue_by_two_stacks.py similarity index 100% rename from data_structures/queue/queue_by_two_stacks.py rename to data_structures/queues/queue_by_two_stacks.py diff --git a/data_structures/queue/queue_on_pseudo_stack.py b/data_structures/queues/queue_on_pseudo_stack.py similarity index 100% rename from data_structures/queue/queue_on_pseudo_stack.py rename to data_structures/queues/queue_on_pseudo_stack.py From a5aed92b4c20fd3e99c6e7a9202afcc9cf502883 Mon Sep 17 00:00:00 2001 From: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Date: Thu, 20 Feb 2025 21:09:01 +0100 Subject: [PATCH 2871/2908] fix: typo in data_structures/linked_list/from_sequence.py (#12584) --- data_structures/linked_list/from_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/linked_list/from_sequence.py b/data_structures/linked_list/from_sequence.py index 94b44f15037f..fa43f4d10e08 100644 --- a/data_structures/linked_list/from_sequence.py +++ b/data_structures/linked_list/from_sequence.py @@ -1,4 +1,4 @@ -# Recursive Prorgam to create a Linked List from a sequence and +# Recursive Program to create a Linked List from a sequence and # print a string representation of it. From 183fa06f40e80c6e86ceda6e7c7d23eaf91507ac Mon Sep 17 00:00:00 2001 From: sector <104625848+infrablue1@users.noreply.github.com> Date: Sat, 22 Feb 2025 16:16:29 +0800 Subject: [PATCH 2872/2908] Fix n-queens problem (#12583) * Fix n-queens problem * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update n_queens.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update n_queens.py * Update n_queens.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- backtracking/n_queens.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index 81668b17a0ac..d10181f319b3 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -27,21 +27,28 @@ def is_safe(board: list[list[int]], row: int, column: int) -> bool: >>> is_safe([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1) True + >>> is_safe([[0, 1, 0], [0, 0, 0], [0, 0, 0]], 1, 1) + False >>> is_safe([[1, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1) False + >>> is_safe([[0, 0, 1], [0, 0, 0], [0, 0, 0]], 1, 1) + False """ n = len(board) # Size of the board - # Check if there is any queen in the same row, column, - # left upper diagonal, and right upper diagonal + # Check if there is any queen in the same upper column, + # left upper diagonal and right upper diagonal return ( - all(board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, n))) + all(board[i][j] != 1 for i, j in zip(range(row), [column] * row)) + and all( + board[i][j] != 1 + for i, j in zip(range(row - 1, -1, -1), range(column - 1, -1, -1)) + ) and all( - board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, -1, -1)) + board[i][j] != 1 + for i, j in zip(range(row - 1, -1, -1), range(column + 1, n)) ) - and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, n))) - and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, -1, -1))) ) From 114d4283b98e52396e2460c802f18d45eeacd90c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:27:10 +0100 Subject: [PATCH 2873/2908] [pre-commit.ci] pre-commit autoupdate (#12591) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.6 → v0.9.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.6...v0.9.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a603109fd79f..8de90b11767f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.6 + rev: v0.9.7 hooks: - id: ruff - id: ruff-format From f528ce350b366ce40e0494fc94da65cfd4509c7d Mon Sep 17 00:00:00 2001 From: Sanjay Muthu Date: Thu, 27 Feb 2025 17:01:08 +0530 Subject: [PATCH 2874/2908] Added dynamic_programming/range_sum_query.py (#12592) * Create prefix_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix pre-commit and ruff errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename prefix_sum.py to range_sum_query.py * Refactor description * Fix * Refactor code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- dynamic_programming/range_sum_query.py | 92 ++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 dynamic_programming/range_sum_query.py diff --git a/dynamic_programming/range_sum_query.py b/dynamic_programming/range_sum_query.py new file mode 100644 index 000000000000..484fcf785fda --- /dev/null +++ b/dynamic_programming/range_sum_query.py @@ -0,0 +1,92 @@ +""" +Author: Sanjay Muthu + +This is an implementation of the Dynamic Programming solution to the Range Sum Query. + +The problem statement is: + Given an array and q queries, + each query stating you to find the sum of elements from l to r (inclusive) + +Example: + arr = [1, 4, 6, 2, 61, 12] + queries = 3 + l_1 = 2, r_1 = 5 + l_2 = 1, r_2 = 5 + l_3 = 3, r_3 = 4 + + as input will return + + [81, 85, 63] + + as output + +0-indexing: +NOTE: 0-indexing means the indexing of the array starts from 0 +Example: a = [1, 2, 3, 4, 5, 6] + Here, the 0th index of a is 1, + the 1st index of a is 2, + and so forth + +Time Complexity: O(N + Q) +* O(N) pre-calculation time to calculate the prefix sum array +* and O(1) time per each query = O(1 * Q) = O(Q) time + +Space Complexity: O(N) +* O(N) to store the prefix sum + +Algorithm: +So, first we calculate the prefix sum (dp) of the array. +The prefix sum of the index i is the sum of all elements indexed +from 0 to i (inclusive). +The prefix sum of the index i is the prefix sum of index (i - 1) + the current element. +So, the state of the dp is dp[i] = dp[i - 1] + a[i]. + +After we calculate the prefix sum, +for each query [l, r] +the answer is dp[r] - dp[l - 1] (we need to be careful because l might be 0). +For example take this array: + [4, 2, 1, 6, 3] +The prefix sum calculated for this array would be: + [4, 4 + 2, 4 + 2 + 1, 4 + 2 + 1 + 6, 4 + 2 + 1 + 6 + 3] + ==> [4, 6, 7, 13, 16] +If the query was l = 3, r = 4, +the answer would be 6 + 3 = 9 but this would require O(r - l + 1) time ≈ O(N) time + +If we use prefix sums we can find it in O(1) by using the formula +prefix[r] - prefix[l - 1]. +This formula works because prefix[r] is the sum of elements from [0, r] +and prefix[l - 1] is the sum of elements from [0, l - 1], +so if we do prefix[r] - prefix[l - 1] it will be +[0, r] - [0, l - 1] = [0, l - 1] + [l, r] - [0, l - 1] = [l, r] +""" + + +def prefix_sum(array: list[int], queries: list[tuple[int, int]]) -> list[int]: + """ + >>> prefix_sum([1, 4, 6, 2, 61, 12], [(2, 5), (1, 5), (3, 4)]) + [81, 85, 63] + >>> prefix_sum([4, 2, 1, 6, 3], [(3, 4), (1, 3), (0, 2)]) + [9, 9, 7] + """ + # The prefix sum array + dp = [0] * len(array) + dp[0] = array[0] + for i in range(1, len(array)): + dp[i] = dp[i - 1] + array[i] + + # See Algorithm section (Line 44) + result = [] + for query in queries: + left, right = query + res = dp[right] + if left > 0: + res -= dp[left - 1] + result.append(res) + + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 8826ad3a4d75f7a4e1d7b1a682682528c2c73672 Mon Sep 17 00:00:00 2001 From: PARIKSHIT SINGH <90330646+parikshit2111@users.noreply.github.com> Date: Sun, 2 Mar 2025 16:33:12 +0530 Subject: [PATCH 2875/2908] feat: Implement Principal Component Analysis (PCA) (#12596) - Added PCA implementation with dataset standardization. - Used Singular Value Decomposition (SVD) for computing principal components. - Fixed import sorting to comply with PEP 8 (Ruff I001). - Ensured type hints and docstrings for better readability. - Added doctests to validate correctness. - Passed all Ruff checks and automated tests. --- DIRECTORY.md | 2 + .../principle_component_analysis.py | 85 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 machine_learning/principle_component_analysis.py diff --git a/DIRECTORY.md b/DIRECTORY.md index a535f12cb59a..ab3259b9a766 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -395,6 +395,7 @@ * [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py) * [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py) * [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py) + * [Range Sum Query](dynamic_programming/range_sum_query.py) * [Regex Match](dynamic_programming/regex_match.py) * [Rod Cutting](dynamic_programming/rod_cutting.py) * [Smith Waterman](dynamic_programming/smith_waterman.py) @@ -608,6 +609,7 @@ * [Mfcc](machine_learning/mfcc.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polynomial Regression](machine_learning/polynomial_regression.py) + * [Principle Component Analysis](machine_learning/principle_component_analysis.py) * [Scoring Functions](machine_learning/scoring_functions.py) * [Self Organizing Map](machine_learning/self_organizing_map.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) diff --git a/machine_learning/principle_component_analysis.py b/machine_learning/principle_component_analysis.py new file mode 100644 index 000000000000..46ccdb968494 --- /dev/null +++ b/machine_learning/principle_component_analysis.py @@ -0,0 +1,85 @@ +""" +Principal Component Analysis (PCA) is a dimensionality reduction technique +used in machine learning. It transforms high-dimensional data into a lower-dimensional +representation while retaining as much variance as possible. + +This implementation follows best practices, including: +- Standardizing the dataset. +- Computing principal components using Singular Value Decomposition (SVD). +- Returning transformed data and explained variance ratio. +""" + +import doctest + +import numpy as np +from sklearn.datasets import load_iris +from sklearn.decomposition import PCA +from sklearn.preprocessing import StandardScaler + + +def collect_dataset() -> tuple[np.ndarray, np.ndarray]: + """ + Collects the dataset (Iris dataset) and returns feature matrix and target values. + + :return: Tuple containing feature matrix (X) and target labels (y) + + Example: + >>> X, y = collect_dataset() + >>> X.shape + (150, 4) + >>> y.shape + (150,) + """ + data = load_iris() + return np.array(data.data), np.array(data.target) + + +def apply_pca(data_x: np.ndarray, n_components: int) -> tuple[np.ndarray, np.ndarray]: + """ + Applies Principal Component Analysis (PCA) to reduce dimensionality. + + :param data_x: Original dataset (features) + :param n_components: Number of principal components to retain + :return: Tuple containing transformed dataset and explained variance ratio + + Example: + >>> X, _ = collect_dataset() + >>> transformed_X, variance = apply_pca(X, 2) + >>> transformed_X.shape + (150, 2) + >>> len(variance) == 2 + True + """ + # Standardizing the dataset + scaler = StandardScaler() + data_x_scaled = scaler.fit_transform(data_x) + + # Applying PCA + pca = PCA(n_components=n_components) + principal_components = pca.fit_transform(data_x_scaled) + + return principal_components, pca.explained_variance_ratio_ + + +def main() -> None: + """ + Driver function to execute PCA and display results. + """ + data_x, data_y = collect_dataset() + + # Number of principal components to retain + n_components = 2 + + # Apply PCA + transformed_data, variance_ratio = apply_pca(data_x, n_components) + + print("Transformed Dataset (First 5 rows):") + print(transformed_data[:5]) + + print("\nExplained Variance Ratio:") + print(variance_ratio) + + +if __name__ == "__main__": + doctest.testmod() + main() From fff34ed528a7c1af373aeae68693d67639ff616b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 19:10:41 +0100 Subject: [PATCH 2876/2908] [pre-commit.ci] pre-commit autoupdate (#12599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.7 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.7...v0.9.9) - [github.com/tox-dev/pyproject-fmt: v2.5.0 → v2.5.1](https://github.com/tox-dev/pyproject-fmt/compare/v2.5.0...v2.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8de90b11767f..a0952928a775 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.7 + rev: v0.9.9 hooks: - id: ruff - id: ruff-format @@ -29,7 +29,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.5.0" + rev: "v2.5.1" hooks: - id: pyproject-fmt From a415a953c3f1bb741f14a4ba06f067e6d94653ed Mon Sep 17 00:00:00 2001 From: Ankana Pari <143877643+ankana2113@users.noreply.github.com> Date: Sun, 9 Mar 2025 03:05:07 +0530 Subject: [PATCH 2877/2908] Add largest rectangle histogram (#12269) * added ridge regression * added ridge regression * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added ridge regression * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ridge regression * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * resolved errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * resolved conflicts * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ruff and minor checks * minor chenges * minor checks * minor checks * minor changes * descriptive names * Fix ruff check in loss_functions.py * fixed pre-commit issues * added largest rectangle histogram function * added largest rectangle histogram function * Update frequent_pattern_growth.py * Update loss_functions.py * Delete machine_learning/ridge_regression/__init__.py * Delete machine_learning/ridge_regression/ADRvsRating.csv * Delete machine_learning/ridge_regression/ridge_regression.py * Delete machine_learning/ridge_regression/test_ridge_regression.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- .../stacks/largest_rectangle_histogram.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 data_structures/stacks/largest_rectangle_histogram.py diff --git a/data_structures/stacks/largest_rectangle_histogram.py b/data_structures/stacks/largest_rectangle_histogram.py new file mode 100644 index 000000000000..7575bd9f628d --- /dev/null +++ b/data_structures/stacks/largest_rectangle_histogram.py @@ -0,0 +1,39 @@ +def largest_rectangle_area(heights: list[int]) -> int: + """ + Inputs an array of integers representing the heights of bars, + and returns the area of the largest rectangle that can be formed + + >>> largest_rectangle_area([2, 1, 5, 6, 2, 3]) + 10 + + >>> largest_rectangle_area([2, 4]) + 4 + + >>> largest_rectangle_area([6, 2, 5, 4, 5, 1, 6]) + 12 + + >>> largest_rectangle_area([1]) + 1 + """ + stack: list[int] = [] + max_area = 0 + heights = [*heights, 0] # make a new list by appending the sentinel 0 + n = len(heights) + + for i in range(n): + # make sure the stack remains in increasing order + while stack and heights[i] < heights[stack[-1]]: + h = heights[stack.pop()] # height of the bar + # if stack is empty, it means entire width can be taken from index 0 to i-1 + w = i if not stack else i - stack[-1] - 1 # calculate width + max_area = max(max_area, h * w) + + stack.append(i) + + return max_area + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 4fbd350b6e08aeb22741d694ce2e64182c66ac92 Mon Sep 17 00:00:00 2001 From: PAUL ADUTWUM Date: Sat, 8 Mar 2025 16:47:04 -0500 Subject: [PATCH 2878/2908] Improved test coverage in decimal_to_fraction.py (#12608) * Imporved test coverage in decimal_to_fraction.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update decimal_to_fraction.py * Update decimal_to_fraction.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- maths/decimal_to_fraction.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/maths/decimal_to_fraction.py b/maths/decimal_to_fraction.py index 2aa8e3c3dfd6..7f1299b33c5c 100644 --- a/maths/decimal_to_fraction.py +++ b/maths/decimal_to_fraction.py @@ -16,6 +16,20 @@ def decimal_to_fraction(decimal: float | str) -> tuple[int, int]: >>> decimal_to_fraction("78td") Traceback (most recent call last): ValueError: Please enter a valid number + >>> decimal_to_fraction(0) + (0, 1) + >>> decimal_to_fraction(-2.5) + (-5, 2) + >>> decimal_to_fraction(0.125) + (1, 8) + >>> decimal_to_fraction(1000000.25) + (4000001, 4) + >>> decimal_to_fraction(1.3333) + (13333, 10000) + >>> decimal_to_fraction("1.23e2") + (123, 1) + >>> decimal_to_fraction("0.500") + (1, 2) """ try: decimal = float(decimal) From e3fb5309da98e2d07699ae39eb0a55836a063532 Mon Sep 17 00:00:00 2001 From: PAUL ADUTWUM Date: Sat, 8 Mar 2025 16:52:20 -0500 Subject: [PATCH 2879/2908] Improve decimal_to_fraction.py (#12611) * Update decimal_to_fraction.py * Update decimal_to_fraction.py --------- Co-authored-by: Maxim Smolskiy --- maths/decimal_to_fraction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/decimal_to_fraction.py b/maths/decimal_to_fraction.py index 7f1299b33c5c..be42b9fb3b5a 100644 --- a/maths/decimal_to_fraction.py +++ b/maths/decimal_to_fraction.py @@ -48,8 +48,8 @@ def decimal_to_fraction(decimal: float | str) -> tuple[int, int]: if remainder == 0: break dividend, divisor = divisor, remainder - numerator, denominator = numerator / divisor, denominator / divisor - return int(numerator), int(denominator) + numerator, denominator = numerator // divisor, denominator // divisor + return numerator, denominator if __name__ == "__main__": From 23eb17462940e20b830aacce5d2eb80113a7f973 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 08:51:28 +0100 Subject: [PATCH 2880/2908] [pre-commit.ci] pre-commit autoupdate (#12614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.9 → v0.9.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.9...v0.9.10) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0952928a775..32580f8c7398 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.9 + rev: v0.9.10 hooks: - id: ruff - id: ruff-format diff --git a/DIRECTORY.md b/DIRECTORY.md index ab3259b9a766..1c02c191bd14 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -289,6 +289,7 @@ * [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py) * [Infix To Postfix Conversion](data_structures/stacks/infix_to_postfix_conversion.py) * [Infix To Prefix Conversion](data_structures/stacks/infix_to_prefix_conversion.py) + * [Largest Rectangle Histogram](data_structures/stacks/largest_rectangle_histogram.py) * [Lexicographical Numbers](data_structures/stacks/lexicographical_numbers.py) * [Next Greater Element](data_structures/stacks/next_greater_element.py) * [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py) From 7ce998b91c45090bd9c4cdfac6ed0220497b4810 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 11 Mar 2025 17:29:13 +0300 Subject: [PATCH 2881/2908] Fix some RUF012 per file ignores (#11399) * updating DIRECTORY.md * Fix some RUF012 per file ignores * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * Fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix * Improve * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Improve * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: MaximSmolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- other/lfu_cache.py | 18 ++++++++++-------- other/lru_cache.py | 18 ++++++++++-------- pyproject.toml | 3 --- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 788fdf19bb60..5a143c739b9d 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -196,9 +196,6 @@ class LFUCache(Generic[T, U]): 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: dict[Callable[[T], U], LFUCache[T, U]] = {} - def __init__(self, capacity: int): self.list: DoubleLinkedList[T, U] = DoubleLinkedList() self.capacity = capacity @@ -291,18 +288,23 @@ def decorator( """ def cache_decorator_inner(func: Callable[[T], U]) -> Callable[..., U]: + # variable to map the decorator functions to their respective instance + decorator_function_to_instance_map: dict[ + Callable[[T], U], LFUCache[T, U] + ] = {} + def cache_decorator_wrapper(*args: T) -> U: - if func not in cls.decorator_function_to_instance_map: - cls.decorator_function_to_instance_map[func] = LFUCache(size) + if func not in decorator_function_to_instance_map: + decorator_function_to_instance_map[func] = LFUCache(size) - result = cls.decorator_function_to_instance_map[func].get(args[0]) + result = decorator_function_to_instance_map[func].get(args[0]) if result is None: result = func(*args) - cls.decorator_function_to_instance_map[func].put(args[0], result) + decorator_function_to_instance_map[func].put(args[0], result) return result def cache_info() -> LFUCache[T, U]: - return cls.decorator_function_to_instance_map[func] + return decorator_function_to_instance_map[func] setattr(cache_decorator_wrapper, "cache_info", cache_info) # noqa: B010 diff --git a/other/lru_cache.py b/other/lru_cache.py index 1e5eeac45b4e..4f0c843c86cc 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -209,9 +209,6 @@ class LRUCache(Generic[T, U]): 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: dict[Callable[[T], U], LRUCache[T, U]] = {} - def __init__(self, capacity: int): self.list: DoubleLinkedList[T, U] = DoubleLinkedList() self.capacity = capacity @@ -308,18 +305,23 @@ def decorator( """ def cache_decorator_inner(func: Callable[[T], U]) -> Callable[..., U]: + # variable to map the decorator functions to their respective instance + decorator_function_to_instance_map: dict[ + Callable[[T], U], LRUCache[T, U] + ] = {} + def cache_decorator_wrapper(*args: T) -> U: - if func not in cls.decorator_function_to_instance_map: - cls.decorator_function_to_instance_map[func] = LRUCache(size) + if func not in decorator_function_to_instance_map: + decorator_function_to_instance_map[func] = LRUCache(size) - result = cls.decorator_function_to_instance_map[func].get(args[0]) + result = decorator_function_to_instance_map[func].get(args[0]) if result is None: result = func(*args) - cls.decorator_function_to_instance_map[func].put(args[0], result) + decorator_function_to_instance_map[func].put(args[0], result) return result def cache_info() -> LRUCache[T, U]: - return cls.decorator_function_to_instance_map[func] + return decorator_function_to_instance_map[func] setattr(cache_decorator_wrapper, "cache_info", cache_info) # noqa: B010 diff --git a/pyproject.toml b/pyproject.toml index 2135f1f5825a..4a76c4ad6d11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,9 +135,6 @@ lint.per-file-ignores."machine_learning/sequential_minimum_optimization.py" = [ lint.per-file-ignores."matrix/sherman_morrison.py" = [ "SIM103", ] -lint.per-file-ignores."other/l*u_cache.py" = [ - "RUF012", -] lint.per-file-ignores."physics/newtons_second_law_of_motion.py" = [ "BLE001", ] From edf7c372a9a6a3e01a33ef92021d958029e99319 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 09:53:49 +0100 Subject: [PATCH 2882/2908] [pre-commit.ci] pre-commit autoupdate (#12623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.10 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.10...v0.11.0) - [github.com/abravalheri/validate-pyproject: v0.23 → v0.24](https://github.com/abravalheri/validate-pyproject/compare/v0.23...v0.24) * Fix ruff issues --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 ++-- conversions/prefix_conversions_string.py | 4 ++-- data_structures/arrays/sudoku_solver.py | 4 ++-- graphics/digital_differential_analyzer_line.py | 2 +- graphs/minimum_spanning_tree_prims2.py | 4 ++-- hashes/enigma_machine.py | 4 ++-- linear_algebra/src/test_linear_algebra.py | 2 +- maths/primelib.py | 2 +- other/davis_putnam_logemann_loveland.py | 2 +- other/quine.py | 2 +- project_euler/problem_028/sol1.py | 2 +- pyproject.toml | 1 + scripts/validate_filenames.py | 17 +++++++---------- sorts/external_sort.py | 2 +- strings/frequency_finder.py | 2 +- 15 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32580f8c7398..5deb66a5e5a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.10 + rev: v0.11.0 hooks: - id: ruff - id: ruff-format @@ -42,7 +42,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.23 + rev: v0.24 hooks: - id: validate-pyproject diff --git a/conversions/prefix_conversions_string.py b/conversions/prefix_conversions_string.py index 9344c9672a1f..c5fef49874ca 100644 --- a/conversions/prefix_conversions_string.py +++ b/conversions/prefix_conversions_string.py @@ -53,7 +53,7 @@ class SIUnit(Enum): yocto = -24 @classmethod - def get_positive(cls: type[T]) -> dict: + def get_positive(cls) -> dict: """ Returns a dictionary with only the elements of this enum that has a positive value @@ -68,7 +68,7 @@ def get_positive(cls: type[T]) -> dict: return {unit.name: unit.value for unit in cls if unit.value > 0} @classmethod - def get_negative(cls: type[T]) -> dict: + def get_negative(cls) -> dict: """ Returns a dictionary with only the elements of this enum that has a negative value diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py index e1714e57ece8..4c722f12fd6e 100644 --- a/data_structures/arrays/sudoku_solver.py +++ b/data_structures/arrays/sudoku_solver.py @@ -54,7 +54,7 @@ def parse_grid(grid): return False if a contradiction is detected. """ ## To start, every square can be any digit; then assign values from the grid. - values = {s: digits for s in squares} + values = dict.fromkeys(squares, digits) for s, d in grid_values(grid).items(): if d in digits and not assign(values, s, d): return False ## (Fail if we can't assign d to square s.) @@ -203,7 +203,7 @@ def random_puzzle(assignments=17): Note the resulting puzzle is not guaranteed to be solvable, but empirically about 99.8% of them are solvable. Some have multiple solutions. """ - values = {s: digits for s in squares} + values = dict.fromkeys(squares, digits) for s in shuffled(squares): if not assign(values, s, random.choice(values[s])): break diff --git a/graphics/digital_differential_analyzer_line.py b/graphics/digital_differential_analyzer_line.py index a51cb0b8dc37..f7269ab09856 100644 --- a/graphics/digital_differential_analyzer_line.py +++ b/graphics/digital_differential_analyzer_line.py @@ -29,7 +29,7 @@ def digital_differential_analyzer_line( for _ in range(steps): x += x_increment y += y_increment - coordinates.append((int(round(x)), int(round(y)))) + coordinates.append((round(x), round(y))) return coordinates diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index cc918f81dfe8..6870cc80f844 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -239,8 +239,8 @@ def prims_algo( 13 """ # prim's algorithm for minimum spanning tree - dist: dict[T, int] = {node: maxsize for node in graph.connections} - parent: dict[T, T | None] = {node: None for node in graph.connections} + dist: dict[T, int] = dict.fromkeys(graph.connections, maxsize) + parent: dict[T, T | None] = dict.fromkeys(graph.connections) priority_queue: MinPriorityQueue[T] = MinPriorityQueue() for node, weight in dist.items(): diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py index d95437d12c34..0da8e4113de9 100644 --- a/hashes/enigma_machine.py +++ b/hashes/enigma_machine.py @@ -15,12 +15,12 @@ def rotator(): gear_one.append(i) del gear_one[0] gear_one_pos += 1 - if gear_one_pos % int(len(alphabets)) == 0: + if gear_one_pos % 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: + if gear_two_pos % len(alphabets) == 0: i = gear_three[0] gear_three.append(i) del gear_three[0] diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index fc5f90fd5cbe..5209c152013e 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -181,7 +181,7 @@ def test_component_matrix(self) -> None: test for Matrix method component() """ a = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) - assert a.component(2, 1) == 7, 0.01 + assert a.component(2, 1) == 7, "0.01" def test__add__matrix(self) -> None: """ diff --git a/maths/primelib.py b/maths/primelib.py index 3a966e5cd936..9f031efc50a9 100644 --- a/maths/primelib.py +++ b/maths/primelib.py @@ -76,7 +76,7 @@ def is_prime(number: int) -> bool: if number <= 1: status = False - for divisor in range(2, int(round(sqrt(number))) + 1): + for divisor in range(2, round(sqrt(number)) + 1): # if 'number' divisible by 'divisor' then sets 'status' # of false and break up the loop. if number % divisor == 0: diff --git a/other/davis_putnam_logemann_loveland.py b/other/davis_putnam_logemann_loveland.py index e95bf371a817..7d0bcce15a29 100644 --- a/other/davis_putnam_logemann_loveland.py +++ b/other/davis_putnam_logemann_loveland.py @@ -36,7 +36,7 @@ def __init__(self, literals: list[str]) -> None: Represent the literals and an assignment in a clause." """ # Assign all literals to None initially - self.literals: dict[str, bool | None] = {literal: None for literal in literals} + self.literals: dict[str, bool | None] = dict.fromkeys(literals) def __str__(self) -> str: """ diff --git a/other/quine.py b/other/quine.py index 08e885bc1ce7..0fc78333fed1 100644 --- a/other/quine.py +++ b/other/quine.py @@ -1,5 +1,5 @@ #!/bin/python3 -# ruff: noqa +# ruff: noqa: PLC3002 """ Quine: diff --git a/project_euler/problem_028/sol1.py b/project_euler/problem_028/sol1.py index 1ea5d4fcafd4..0a4648af36c4 100644 --- a/project_euler/problem_028/sol1.py +++ b/project_euler/problem_028/sol1.py @@ -37,7 +37,7 @@ def solution(n: int = 1001) -> int: """ total = 1 - for i in range(1, int(ceil(n / 2.0))): + for i in range(1, ceil(n / 2.0)): odd = 2 * i + 1 even = 2 * i total = total + 4 * odd**2 - 6 * even diff --git a/pyproject.toml b/pyproject.toml index 4a76c4ad6d11..60f8d4ffc96f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,6 +119,7 @@ lint.ignore = [ "PT018", # Assertion should be broken down into multiple parts "S101", # Use of `assert` detected -- DO NOT FIX "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME + "SIM905", # Consider using a list literal instead of `str.split` -- DO NOT FIX "SLF001", # Private member accessed: `_Iterator` -- FIX ME "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX ] diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index e76b4dbfe288..80399673cced 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -9,28 +9,25 @@ 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: +if upper_files := [file for file in filepaths if file != file.lower()]: 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: +if space_files := [file for file in filepaths if " " in file]: 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: +if hyphen_files := [ + file for file in filepaths if "-" in file and "/site-packages/" not in file +]: 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] -if nodir_files: +if nodir_files := [file for file in filepaths if os.sep not in file]: print(f"{len(nodir_files)} files are not in a directory:") print("\n".join(nodir_files) + "\n") -bad_files = len(upper_files + space_files + hyphen_files + nodir_files) -if bad_files: +if bad_files := len(upper_files + space_files + hyphen_files + nodir_files): import sys sys.exit(bad_files) diff --git a/sorts/external_sort.py b/sorts/external_sort.py index 3fa7cacc0592..cfddee4fe7f8 100644 --- a/sorts/external_sort.py +++ b/sorts/external_sort.py @@ -61,7 +61,7 @@ def __init__(self, files): self.files = files self.empty = set() self.num_buffers = len(files) - self.buffers = {i: None for i in range(self.num_buffers)} + self.buffers = dict.fromkeys(range(self.num_buffers)) def get_dict(self): return { diff --git a/strings/frequency_finder.py b/strings/frequency_finder.py index e5afee891bd9..98720dc36d6e 100644 --- a/strings/frequency_finder.py +++ b/strings/frequency_finder.py @@ -36,7 +36,7 @@ def get_letter_count(message: str) -> dict[str, int]: - letter_count = {letter: 0 for letter in string.ascii_uppercase} + letter_count = dict.fromkeys(string.ascii_uppercase, 0) for letter in message.upper(): if letter in LETTERS: letter_count[letter] += 1 From 580273eeca28c30a8a5da114800d21b89fdfb930 Mon Sep 17 00:00:00 2001 From: Pranjay kumar <110048711+pranjaykumar926@users.noreply.github.com> Date: Thu, 20 Mar 2025 05:03:46 +0530 Subject: [PATCH 2883/2908] Improve prefix_sum.py (#12560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update prefix_sum.py Index Validation for get_sum Raises ValueError if start or end is out of range or start > end. Handles cases where the array is empty. ✅ Empty Array Support If an empty array is passed, get_sum raises an appropriate error instead of failing unexpectedly. ✅ Optimized contains_sum Initialization Initializes sums with {0} for efficient subarray sum checking. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update prefix_sum.py * Update prefix_sum.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update prefix_sum.py * Update prefix_sum.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- data_structures/arrays/prefix_sum.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/data_structures/arrays/prefix_sum.py b/data_structures/arrays/prefix_sum.py index 2243a5308937..717b5f9d7e7e 100644 --- a/data_structures/arrays/prefix_sum.py +++ b/data_structures/arrays/prefix_sum.py @@ -30,11 +30,29 @@ def get_sum(self, start: int, end: int) -> int: 5 >>> PrefixSum([1,2,3]).get_sum(2, 2) 3 + >>> PrefixSum([]).get_sum(0, 0) + Traceback (most recent call last): + ... + ValueError: The array is empty. + >>> PrefixSum([1,2,3]).get_sum(-1, 2) + Traceback (most recent call last): + ... + ValueError: Invalid range specified. >>> PrefixSum([1,2,3]).get_sum(2, 3) Traceback (most recent call last): ... - IndexError: list index out of range + ValueError: Invalid range specified. + >>> PrefixSum([1,2,3]).get_sum(2, 1) + Traceback (most recent call last): + ... + ValueError: Invalid range specified. """ + if not self.prefix_sum: + raise ValueError("The array is empty.") + + if start < 0 or end >= len(self.prefix_sum) or start > end: + raise ValueError("Invalid range specified.") + if start == 0: return self.prefix_sum[end] From e3773dbec1504de17047c4fe013c0f1aaef20b38 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:05:41 +0100 Subject: [PATCH 2884/2908] [pre-commit.ci] pre-commit autoupdate (#12631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2) - [github.com/abravalheri/validate-pyproject: v0.24 → v0.24.1](https://github.com/abravalheri/validate-pyproject/compare/v0.24...v0.24.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5deb66a5e5a2..0fc8b2b14e07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.0 + rev: v0.11.2 hooks: - id: ruff - id: ruff-format @@ -42,7 +42,7 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.24 + rev: v0.24.1 hooks: - id: validate-pyproject From 74b540ad73bd3b1187ed6e3c89bb8f309ef543fd Mon Sep 17 00:00:00 2001 From: Tony Dang <62843153+Dang-Hoang-Tung@users.noreply.github.com> Date: Sat, 29 Mar 2025 08:13:47 +0000 Subject: [PATCH 2885/2908] Genetic Algorithm: Fix bug in multi-threading (#12644) * Fix bug in multi-threading - Multi-threading (despite being commented out) had a tiny bug: missing target argument (2nd argument). - Commented out code was also slightly hard to understand, added (Option 1/2) in comments to clarify where a user may choose between 2 implementations. * Update basic_string.py --------- Co-authored-by: Maxim Smolskiy --- genetic_algorithm/basic_string.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index a906ce85a779..b75491d9a949 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -144,18 +144,18 @@ def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int, # Random population created. Now it's time to evaluate. - # Adding a bit of concurrency can make everything faster, + # (Option 1) 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} + # futures = {executor.submit(evaluate, item, target) for item in population} # concurrent.futures.wait(futures) # population_score = [item.result() for item in futures] # # but with a simple algorithm like this, it will probably be slower. - # We just need to call evaluate for every item inside the population. + # (Option 2) We just need to call evaluate for every item inside the population. population_score = [evaluate(item, target) for item in population] # Check if there is a matching evolution. From f10a5cbfccc5ee9ddb5ddd9906591ecaad58f672 Mon Sep 17 00:00:00 2001 From: Isidro Date: Mon, 31 Mar 2025 23:09:14 +0200 Subject: [PATCH 2886/2908] prefix_evaluation: Add alternative recursive implementation (#12646) * prefix_evaluation: Add alternative recursive implementation * improve doc * better variable name calc->operators * Update prefix_evaluation.py * Update prefix_evaluation.py * Update prefix_evaluation.py * Update prefix_evaluation.py --------- Co-authored-by: Maxim Smolskiy --- data_structures/stacks/prefix_evaluation.py | 39 +++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/data_structures/stacks/prefix_evaluation.py b/data_structures/stacks/prefix_evaluation.py index f48eca23d7b5..03a70d884725 100644 --- a/data_structures/stacks/prefix_evaluation.py +++ b/data_structures/stacks/prefix_evaluation.py @@ -1,8 +1,9 @@ """ -Python3 program to evaluate a prefix expression. +Program to evaluate a prefix expression. +https://en.wikipedia.org/wiki/Polish_notation """ -calc = { +operators = { "+": lambda x, y: x + y, "-": lambda x, y: x - y, "*": lambda x, y: x * y, @@ -31,6 +32,10 @@ def evaluate(expression): 21 >>> evaluate("/ * 10 2 + 4 1 ") 4.0 + >>> evaluate("2") + 2 + >>> evaluate("+ * 2 3 / 8 4") + 8.0 """ stack = [] @@ -45,11 +50,39 @@ def evaluate(expression): # push the result onto the stack again o1 = stack.pop() o2 = stack.pop() - stack.append(calc[c](o1, o2)) + stack.append(operators[c](o1, o2)) return stack.pop() +def evaluate_recursive(expression: list[str]): + """ + Alternative recursive implementation + + >>> evaluate_recursive(['2']) + 2 + >>> expression = ['+', '*', '2', '3', '/', '8', '4'] + >>> evaluate_recursive(expression) + 8.0 + >>> expression + [] + >>> evaluate_recursive(['+', '9', '*', '2', '6']) + 21 + >>> evaluate_recursive(['/', '*', '10', '2', '+', '4', '1']) + 4.0 + """ + + op = expression.pop(0) + if is_operand(op): + return int(op) + + operation = operators[op] + + a = evaluate_recursive(expression) + b = evaluate_recursive(expression) + return operation(a, b) + + # Driver code if __name__ == "__main__": test_expression = "+ 9 * 2 6" From baab802965c37fa1740054a559cad8c119b2ee35 Mon Sep 17 00:00:00 2001 From: Isidro Date: Tue, 1 Apr 2025 20:55:14 +0200 Subject: [PATCH 2887/2908] doubly linked list: add dataclass and typing (#12647) * Node is a dataclass * fix mypy errors * LinkedList is a dataclass * fix mypy errors * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py --------- Co-authored-by: Maxim Smolskiy --- .../linked_list/doubly_linked_list_two.py | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py index e993cc5a20af..3d3bfb0cde30 100644 --- a/data_structures/linked_list/doubly_linked_list_two.py +++ b/data_structures/linked_list/doubly_linked_list_two.py @@ -9,25 +9,19 @@ Delete operation is more efficient """ +from dataclasses import dataclass +from typing import Self + +@dataclass class Node: - def __init__(self, data: int, previous=None, next_node=None): - self.data = data - self.previous = previous - self.next = next_node + data: int + previous: Self | None = None + next: Self | None = None 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): @@ -40,30 +34,30 @@ def __next__(self): if not self.current: raise StopIteration else: - value = self.current.get_data() - self.current = self.current.get_next() + value = self.current.data + self.current = self.current.next return value +@dataclass class LinkedList: - def __init__(self): - self.head = None # First node in list - self.tail = None # Last node in list + head: Node | None = None # First node in list + tail: Node | None = 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() + nodes.append(current.data) + current = current.next return " ".join(str(node) for node in nodes) def __contains__(self, value: int): current = self.head while current: - if current.get_data() == value: + if current.data == value: return True - current = current.get_next() + current = current.next return False def __iter__(self): @@ -71,12 +65,12 @@ def __iter__(self): def get_head_data(self): if self.head: - return self.head.get_data() + return self.head.data return None def get_tail_data(self): if self.tail: - return self.tail.get_data() + return self.tail.data return None def set_head(self, node: Node) -> None: @@ -103,18 +97,20 @@ 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: + if node.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: + def insert_after_node(self, node: Node | None, node_to_insert: Node) -> None: + assert node is not None + node_to_insert.previous = node node_to_insert.next = node.next - if node.get_next() is None: + if node.next is None: self.tail = node_to_insert else: node.next.previous = node_to_insert @@ -136,27 +132,27 @@ def insert_at_position(self, position: int, value: int) -> None: def get_node(self, item: int) -> Node: node = self.head while node: - if node.get_data() == item: + if node.data == item: return node - node = node.get_next() + node = node.next raise Exception("Node not found") def delete_value(self, value): if (node := self.get_node(value)) is not None: if node == self.head: - self.head = self.head.get_next() + self.head = self.head.next if node == self.tail: - self.tail = self.tail.get_previous() + self.tail = self.tail.previous self.remove_node_pointers(node) @staticmethod def remove_node_pointers(node: Node) -> None: - if node.get_next(): + if node.next: node.next.previous = node.previous - if node.get_previous(): + if node.previous: node.previous.next = node.next node.next = None From 0c8cf8e9871a5f91182d767adf173dccf87c2c0f Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Wed, 2 Apr 2025 10:23:55 +0300 Subject: [PATCH 2888/2908] Fix bug for data_structures/linked_list/doubly_linked_list_two.py (#12651) * Fix bug for data_structures/linked_list/doubly_linked_list_two.py * Fix * Fix * Fix * Fix * Fix * Fix * Fix * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py --- .../linked_list/doubly_linked_list_two.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py index 3d3bfb0cde30..8c93cddd5d31 100644 --- a/data_structures/linked_list/doubly_linked_list_two.py +++ b/data_structures/linked_list/doubly_linked_list_two.py @@ -81,8 +81,9 @@ def set_head(self, node: Node) -> None: self.insert_before_node(self.head, node) def set_tail(self, node: Node) -> None: - if self.head is None: - self.set_head(node) + if self.tail is None: + self.head = node + self.tail = node else: self.insert_after_node(self.tail, node) @@ -104,9 +105,7 @@ def insert_before_node(self, node: Node, node_to_insert: Node) -> None: node.previous = node_to_insert - def insert_after_node(self, node: Node | None, node_to_insert: Node) -> None: - assert node is not None - + def insert_after_node(self, node: Node, node_to_insert: Node) -> None: node_to_insert.previous = node node_to_insert.next = node.next @@ -127,7 +126,7 @@ def insert_at_position(self, position: int, value: int) -> None: return current_position += 1 node = node.next - self.insert_after_node(self.tail, new_node) + self.set_tail(new_node) def get_node(self, item: int) -> Node: node = self.head @@ -237,6 +236,22 @@ def create_linked_list() -> None: 7 8 9 + >>> linked_list = LinkedList() + >>> linked_list.insert_at_position(position=1, value=10) + >>> str(linked_list) + '10' + >>> linked_list.insert_at_position(position=2, value=20) + >>> str(linked_list) + '10 20' + >>> linked_list.insert_at_position(position=1, value=30) + >>> str(linked_list) + '30 10 20' + >>> linked_list.insert_at_position(position=3, value=40) + >>> str(linked_list) + '30 10 40 20' + >>> linked_list.insert_at_position(position=5, value=50) + >>> str(linked_list) + '30 10 40 20 50' """ From 5afe02994eb0aafb0b462fec32fe5f6ecedf7305 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 23:20:19 +0200 Subject: [PATCH 2889/2908] [pre-commit.ci] pre-commit autoupdate (#12661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0fc8b2b14e07..20065c433062 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.2 + rev: v0.11.4 hooks: - id: ruff - id: ruff-format From a4576dc2a42cbfc7585a7ce6f28917cb97f83c45 Mon Sep 17 00:00:00 2001 From: Kim Date: Wed, 9 Apr 2025 15:24:37 +0900 Subject: [PATCH 2890/2908] fix: correct typo "util" to "until" (#12653) --- dynamic_programming/bitmask.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index a6e6a0cda7bf..4737a3419e8e 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -42,7 +42,7 @@ def count_ways_until(self, mask, task_no): return self.dp[mask][task_no] # Number of ways when we don't this task in the arrangement - total_ways_util = self.count_ways_until(mask, task_no + 1) + total_ways_until = self.count_ways_until(mask, task_no + 1) # now assign the tasks one by one to all possible persons and recursively # assign for the remaining tasks. @@ -54,10 +54,10 @@ def count_ways_until(self, mask, task_no): # assign this task to p and change the mask value. And recursively # assign tasks with the new mask value. - total_ways_util += self.count_ways_until(mask | (1 << p), task_no + 1) + total_ways_until += self.count_ways_until(mask | (1 << p), task_no + 1) # save the value. - self.dp[mask][task_no] = total_ways_util + self.dp[mask][task_no] = total_ways_until return self.dp[mask][task_no] From 4ed61418a8fef5a0fe3c5a05a49c7cbc5ac8298c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:55:55 +0200 Subject: [PATCH 2891/2908] [pre-commit.ci] pre-commit autoupdate (#12671) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.4 → v0.11.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.4...v0.11.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 20065c433062..8a8697ca778a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 + rev: v0.11.5 hooks: - id: ruff - id: ruff-format From cc621f1fddac7391389270d7bc38326507b4b495 Mon Sep 17 00:00:00 2001 From: parth-6945 <140963864+parth-6945@users.noreply.github.com> Date: Mon, 14 Apr 2025 23:31:29 +0530 Subject: [PATCH 2892/2908] Add find_unique_number algorithm to bit manipulation (#12654) * Add find_unique_number algorithm to bit manipulation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- bit_manipulation/find_unique_number.py | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 bit_manipulation/find_unique_number.py diff --git a/bit_manipulation/find_unique_number.py b/bit_manipulation/find_unique_number.py new file mode 100644 index 000000000000..77970b4865d1 --- /dev/null +++ b/bit_manipulation/find_unique_number.py @@ -0,0 +1,37 @@ +def find_unique_number(arr: list[int]) -> int: + """ + Given a list of integers where every element appears twice except for one, + this function returns the element that appears only once using bitwise XOR. + + >>> find_unique_number([1, 1, 2, 2, 3]) + 3 + >>> find_unique_number([4, 5, 4, 6, 6]) + 5 + >>> find_unique_number([7]) + 7 + >>> find_unique_number([10, 20, 10]) + 20 + >>> find_unique_number([]) + Traceback (most recent call last): + ... + ValueError: input list must not be empty + >>> find_unique_number([1, 'a', 1]) + Traceback (most recent call last): + ... + TypeError: all elements must be integers + """ + if not arr: + raise ValueError("input list must not be empty") + if not all(isinstance(x, int) for x in arr): + raise TypeError("all elements must be integers") + + result = 0 + for num in arr: + result ^= num + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d123cbc649e0777717b02dc74b4466872941a8d5 Mon Sep 17 00:00:00 2001 From: Mindaugas <76015221+mindaugl@users.noreply.github.com> Date: Tue, 15 Apr 2025 02:30:25 +0800 Subject: [PATCH 2893/2908] Solution for the Euler Project Problem 122 (#12655) * Add initial version for euler project problem 122. * Add doctests and documentation for the project euler problem 122. * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py --------- Co-authored-by: Maxim Smolskiy --- project_euler/problem_122/__init__.py | 0 project_euler/problem_122/sol1.py | 89 +++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 project_euler/problem_122/__init__.py create mode 100644 project_euler/problem_122/sol1.py diff --git a/project_euler/problem_122/__init__.py b/project_euler/problem_122/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_122/sol1.py b/project_euler/problem_122/sol1.py new file mode 100644 index 000000000000..cd8b1e67708c --- /dev/null +++ b/project_euler/problem_122/sol1.py @@ -0,0 +1,89 @@ +""" +Project Euler Problem 122: https://projecteuler.net/problem=122 + +Efficient Exponentiation + +The most naive way of computing n^15 requires fourteen multiplications: + + n x n x ... x n = n^15. + +But using a "binary" method you can compute it in six multiplications: + + n x n = n^2 + n^2 x n^2 = n^4 + n^4 x n^4 = n^8 + n^8 x n^4 = n^12 + n^12 x n^2 = n^14 + n^14 x n = n^15 + +However it is yet possible to compute it in only five multiplications: + + n x n = n^2 + n^2 x n = n^3 + n^3 x n^3 = n^6 + n^6 x n^6 = n^12 + n^12 x n^3 = n^15 + +We shall define m(k) to be the minimum number of multiplications to compute n^k; +for example m(15) = 5. + +Find sum_{k = 1}^200 m(k). + +It uses the fact that for rather small n, applicable for this problem, the solution +for each number can be formed by increasing the largest element. + +References: +- https://en.wikipedia.org/wiki/Addition_chain +""" + + +def solve(nums: list[int], goal: int, depth: int) -> bool: + """ + Checks if nums can have a sum equal to goal, given that length of nums does + not exceed depth. + + >>> solve([1], 2, 2) + True + >>> solve([1], 2, 0) + False + """ + if len(nums) > depth: + return False + for el in nums: + if el + nums[-1] == goal: + return True + nums.append(el + nums[-1]) + if solve(nums=nums, goal=goal, depth=depth): + return True + del nums[-1] + return False + + +def solution(n: int = 200) -> int: + """ + Calculates sum of smallest number of multiplactions for each number up to + and including n. + + >>> solution(1) + 0 + >>> solution(2) + 1 + >>> solution(14) + 45 + >>> solution(15) + 50 + """ + total = 0 + for i in range(2, n + 1): + max_length = 0 + while True: + nums = [1] + max_length += 1 + if solve(nums=nums, goal=i, depth=max_length): + break + total += max_length + return total + + +if __name__ == "__main__": + print(f"{solution() = }") From 42820634f3795e7d7397ad2e688d091a79c1eb83 Mon Sep 17 00:00:00 2001 From: Naitik Dwivedi Date: Tue, 15 Apr 2025 00:27:13 +0530 Subject: [PATCH 2894/2908] Add matrix inversion algorithm using NumPy (#12657) * Create matrix_inversion.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update matrix_inversion.py * Update matrix_inversion.py * Update matrix_inversion.py * Update matrix_inversion.py * Update matrix_inversion.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- linear_algebra/matrix_inversion.py | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 linear_algebra/matrix_inversion.py diff --git a/linear_algebra/matrix_inversion.py b/linear_algebra/matrix_inversion.py new file mode 100644 index 000000000000..50dae1c2e825 --- /dev/null +++ b/linear_algebra/matrix_inversion.py @@ -0,0 +1,36 @@ +import numpy as np + + +def invert_matrix(matrix: list[list[float]]) -> list[list[float]]: + """ + Returns the inverse of a square matrix using NumPy. + + Parameters: + matrix (list[list[float]]): A square matrix. + + Returns: + list[list[float]]: Inverted matrix if invertible, else raises error. + + >>> invert_matrix([[4.0, 7.0], [2.0, 6.0]]) + [[0.6000000000000001, -0.7000000000000001], [-0.2, 0.4]] + >>> invert_matrix([[1.0, 2.0], [0.0, 0.0]]) + Traceback (most recent call last): + ... + ValueError: Matrix is not invertible + """ + np_matrix = np.array(matrix) + + try: + inv_matrix = np.linalg.inv(np_matrix) + except np.linalg.LinAlgError: + raise ValueError("Matrix is not invertible") + + return inv_matrix.tolist() + + +if __name__ == "__main__": + mat = [[4.0, 7.0], [2.0, 6.0]] + print("Original Matrix:") + print(mat) + print("Inverted Matrix:") + print(invert_matrix(mat)) From c585cb122718e219870ea7a2af110939b55e52f9 Mon Sep 17 00:00:00 2001 From: Mindaugas <76015221+mindaugl@users.noreply.github.com> Date: Fri, 18 Apr 2025 07:16:15 +0800 Subject: [PATCH 2895/2908] Solution for the Euler Project problem 136 (#12658) * Add initial version of file for the Euler project problem 136 solution. * Add documentation and tests for the Euler project problem 136 solution. * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py --------- Co-authored-by: Maxim Smolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- project_euler/problem_136/__init__.py | 0 project_euler/problem_136/sol1.py | 63 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 project_euler/problem_136/__init__.py create mode 100644 project_euler/problem_136/sol1.py diff --git a/project_euler/problem_136/__init__.py b/project_euler/problem_136/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_136/sol1.py b/project_euler/problem_136/sol1.py new file mode 100644 index 000000000000..688a9a5d7f24 --- /dev/null +++ b/project_euler/problem_136/sol1.py @@ -0,0 +1,63 @@ +""" +Project Euler Problem 136: https://projecteuler.net/problem=136 + +Singleton Difference + +The positive integers, x, y, and z, are consecutive terms of an arithmetic progression. +Given that n is a positive integer, the equation, x^2 - y^2 - z^2 = n, +has exactly one solution when n = 20: + 13^2 - 10^2 - 7^2 = 20. + +In fact there are twenty-five values of n below one hundred for which +the equation has a unique solution. + +How many values of n less than fifty million have exactly one solution? + +By change of variables + +x = y + delta +z = y - delta + +The expression can be rewritten: + +x^2 - y^2 - z^2 = y * (4 * delta - y) = n + +The algorithm loops over delta and y, which is restricted in upper and lower limits, +to count how many solutions each n has. +In the end it is counted how many n's have one solution. +""" + + +def solution(n_limit: int = 50 * 10**6) -> int: + """ + Define n count list and loop over delta, y to get the counts, then check + which n has count == 1. + + >>> solution(3) + 0 + >>> solution(10) + 3 + >>> solution(100) + 25 + >>> solution(110) + 27 + """ + n_sol = [0] * n_limit + + for delta in range(1, (n_limit + 1) // 4 + 1): + for y in range(4 * delta - 1, delta, -1): + n = y * (4 * delta - y) + if n >= n_limit: + break + n_sol[n] += 1 + + ans = 0 + for i in range(n_limit): + if n_sol[i] == 1: + ans += 1 + + return ans + + +if __name__ == "__main__": + print(f"{solution() = }") From a1aa6313e08657f0e9ae337afa81d6b6f95357c9 Mon Sep 17 00:00:00 2001 From: Samuel Willis <109305646+konsoleSam@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:33:08 -0600 Subject: [PATCH 2896/2908] Adding time and a half pay calculator algorithm to financial folder (#12662) * Create time&half-pay.py * Update time&half-pay.py * Update time&half-pay.py * Rename time&half-pay.py to time_and_half_pay.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update time_and_half_pay.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update time_and_half_pay.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update time_and_half_pay.py * Update time_and_half_pay.py * Update time_and_half_pay.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- financial/time_and_half_pay.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 financial/time_and_half_pay.py diff --git a/financial/time_and_half_pay.py b/financial/time_and_half_pay.py new file mode 100644 index 000000000000..c5dff1bc1ce1 --- /dev/null +++ b/financial/time_and_half_pay.py @@ -0,0 +1,40 @@ +""" +Calculate time and a half pay +""" + + +def pay(hours_worked: float, pay_rate: float, hours: float = 40) -> float: + """ + hours_worked = The total hours worked + pay_rate = Amount of money per hour + hours = Number of hours that must be worked before you receive time and a half + + >>> pay(41, 1) + 41.5 + >>> pay(65, 19) + 1472.5 + >>> pay(10, 1) + 10.0 + """ + # Check that all input parameters are float or integer + assert isinstance(hours_worked, (float, int)), ( + "Parameter 'hours_worked' must be of type 'int' or 'float'" + ) + assert isinstance(pay_rate, (float, int)), ( + "Parameter 'pay_rate' must be of type 'int' or 'float'" + ) + assert isinstance(hours, (float, int)), ( + "Parameter 'hours' must be of type 'int' or 'float'" + ) + + normal_pay = hours_worked * pay_rate + over_time = max(0, hours_worked - hours) + over_time_pay = over_time * pay_rate / 2 + return normal_pay + over_time_pay + + +if __name__ == "__main__": + # Test + import doctest + + doctest.testmod() From 9891d2bc3051ab4242ed041ea30edd10b94925bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:54:11 +0200 Subject: [PATCH 2897/2908] [pre-commit.ci] pre-commit autoupdate (#12680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.5 → v0.11.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.5...v0.11.6) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a8697ca778a..8474deebb7ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.5 + rev: v0.11.6 hooks: - id: ruff - id: ruff-format diff --git a/DIRECTORY.md b/DIRECTORY.md index 1c02c191bd14..fa731e32ff23 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -40,6 +40,7 @@ * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py) * [Excess 3 Code](bit_manipulation/excess_3_code.py) * [Find Previous Power Of Two](bit_manipulation/find_previous_power_of_two.py) + * [Find Unique Number](bit_manipulation/find_unique_number.py) * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py) * [Highest Set Bit](bit_manipulation/highest_set_bit.py) * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py) @@ -442,6 +443,7 @@ * [Present Value](financial/present_value.py) * [Price Plus Tax](financial/price_plus_tax.py) * [Simple Moving Average](financial/simple_moving_average.py) + * [Time And Half Pay](financial/time_and_half_pay.py) ## Fractals * [Julia Sets](fractals/julia_sets.py) @@ -570,6 +572,7 @@ * [Gaussian Elimination](linear_algebra/gaussian_elimination.py) * [Jacobi Iteration Method](linear_algebra/jacobi_iteration_method.py) * [Lu Decomposition](linear_algebra/lu_decomposition.py) + * [Matrix Inversion](linear_algebra/matrix_inversion.py) * Src * [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py) * [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting.py) @@ -1153,6 +1156,8 @@ * [Sol1](project_euler/problem_120/sol1.py) * Problem 121 * [Sol1](project_euler/problem_121/sol1.py) + * Problem 122 + * [Sol1](project_euler/problem_122/sol1.py) * Problem 123 * [Sol1](project_euler/problem_123/sol1.py) * Problem 125 @@ -1163,6 +1168,8 @@ * [Sol1](project_euler/problem_131/sol1.py) * Problem 135 * [Sol1](project_euler/problem_135/sol1.py) + * Problem 136 + * [Sol1](project_euler/problem_136/sol1.py) * Problem 144 * [Sol1](project_euler/problem_144/sol1.py) * Problem 145 From 11a61d15dc3aebc69f153adca8568076a25f7110 Mon Sep 17 00:00:00 2001 From: Isidro Date: Mon, 21 Apr 2025 21:04:39 +0200 Subject: [PATCH 2898/2908] Generic type hint in DDL (#12677) * Generic type hint in DDL Instead of forcing int * Update doubly_linked_list_two.py * Update doubly_linked_list_two.py --------- Co-authored-by: Maxim Smolskiy --- .../linked_list/doubly_linked_list_two.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py index 8c93cddd5d31..a7f639a6e289 100644 --- a/data_structures/linked_list/doubly_linked_list_two.py +++ b/data_structures/linked_list/doubly_linked_list_two.py @@ -10,12 +10,14 @@ """ from dataclasses import dataclass -from typing import Self +from typing import Self, TypeVar + +DataType = TypeVar("DataType") @dataclass -class Node: - data: int +class Node[DataType]: + data: DataType previous: Self | None = None next: Self | None = None @@ -52,7 +54,7 @@ def __str__(self): current = current.next return " ".join(str(node) for node in nodes) - def __contains__(self, value: int): + def __contains__(self, value: DataType): current = self.head while current: if current.data == value: @@ -87,7 +89,7 @@ def set_tail(self, node: Node) -> None: else: self.insert_after_node(self.tail, node) - def insert(self, value: int) -> None: + def insert(self, value: DataType) -> None: node = Node(value) if self.head is None: self.set_head(node) @@ -116,7 +118,7 @@ def insert_after_node(self, node: Node, node_to_insert: Node) -> None: node.next = node_to_insert - def insert_at_position(self, position: int, value: int) -> None: + def insert_at_position(self, position: int, value: DataType) -> None: current_position = 1 new_node = Node(value) node = self.head @@ -128,7 +130,7 @@ def insert_at_position(self, position: int, value: int) -> None: node = node.next self.set_tail(new_node) - def get_node(self, item: int) -> Node: + def get_node(self, item: DataType) -> Node: node = self.head while node: if node.data == item: From 29afed0df65fd84f53ad697ff1dbfc86c6e83631 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 08:30:42 +0200 Subject: [PATCH 2899/2908] Bump astral-sh/setup-uv from 5 to 6 (#12683) * Bump astral-sh/setup-uv from 5 to 6 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5 to 6. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v5...v6) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * uv run pytest --ignore=web_programming/fetch_anime_and_play.py --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 3 ++- .github/workflows/project_euler.yml | 4 ++-- .github/workflows/ruff.yml | 2 +- .github/workflows/sphinx.yml | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 62829b2b45a5..8b83cb41c79a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: uv.lock @@ -30,6 +30,7 @@ jobs: --ignore=project_euler/ --ignore=quantum/q_fourier_transform.py --ignore=scripts/validate_solutions.py + --ignore=web_programming/fetch_anime_and_play.py --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml index 8d51ad8850cf..eaf4150e4eaa 100644 --- a/.github/workflows/project_euler.yml +++ b/.github/workflows/project_euler.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v6 - uses: actions/setup-python@v5 with: python-version: 3.x @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v6 - uses: actions/setup-python@v5 with: python-version: 3.x diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index cfe127b3521f..ec9f0202bd7e 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -12,5 +12,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v6 - run: uvx ruff check --output-format=github . diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index 16ff284a74f2..2010041d80c5 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v6 - uses: actions/setup-python@v5 with: python-version: 3.13 From 0a3a96534767c37a0f1561d0f5148b5d1d5e0272 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 19:55:57 +0200 Subject: [PATCH 2900/2908] [pre-commit.ci] pre-commit autoupdate (#12692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.6 → v0.11.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.6...v0.11.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8474deebb7ba..034493b10912 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.6 + rev: v0.11.7 hooks: - id: ruff - id: ruff-format From 145879b8b2546c74fc51446ac607823876a0f601 Mon Sep 17 00:00:00 2001 From: Mindaugas <76015221+mindaugl@users.noreply.github.com> Date: Mon, 5 May 2025 15:00:32 +0800 Subject: [PATCH 2901/2908] Add solution for the Euler project problem 164. (#12663) * Add solution for the Euler project problem 164. * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py --------- Co-authored-by: Maxim Smolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- project_euler/problem_164/__init__.py | 0 project_euler/problem_164/sol1.py | 65 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 project_euler/problem_164/__init__.py create mode 100644 project_euler/problem_164/sol1.py diff --git a/project_euler/problem_164/__init__.py b/project_euler/problem_164/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_164/sol1.py b/project_euler/problem_164/sol1.py new file mode 100644 index 000000000000..5387c89bd757 --- /dev/null +++ b/project_euler/problem_164/sol1.py @@ -0,0 +1,65 @@ +""" +Project Euler Problem 164: https://projecteuler.net/problem=164 + +Three Consecutive Digital Sum Limit + +How many 20 digit numbers n (without any leading zero) exist such that no three +consecutive digits of n have a sum greater than 9? + +Brute-force recursive solution with caching of intermediate results. +""" + + +def solve( + digit: int, prev1: int, prev2: int, sum_max: int, first: bool, cache: dict[str, int] +) -> int: + """ + Solve for remaining 'digit' digits, with previous 'prev1' digit, and + previous-previous 'prev2' digit, total sum of 'sum_max'. + Pass around 'cache' to store/reuse intermediate results. + + >>> solve(digit=1, prev1=0, prev2=0, sum_max=9, first=True, cache={}) + 9 + >>> solve(digit=1, prev1=0, prev2=0, sum_max=9, first=False, cache={}) + 10 + """ + if digit == 0: + return 1 + + cache_str = f"{digit},{prev1},{prev2}" + if cache_str in cache: + return cache[cache_str] + + comb = 0 + for curr in range(sum_max - prev1 - prev2 + 1): + if first and curr == 0: + continue + + comb += solve( + digit=digit - 1, + prev1=curr, + prev2=prev1, + sum_max=sum_max, + first=False, + cache=cache, + ) + + cache[cache_str] = comb + return comb + + +def solution(n_digits: int = 20) -> int: + """ + Solves the problem for n_digits number of digits. + + >>> solution(2) + 45 + >>> solution(10) + 21838806 + """ + cache: dict[str, int] = {} + return solve(digit=n_digits, prev1=0, prev2=0, sum_max=9, first=True, cache=cache) + + +if __name__ == "__main__": + print(f"{solution(10) = }") From 40f4c510b6047d95d07f03c9915a53bbf84789e4 Mon Sep 17 00:00:00 2001 From: Mindaugas <76015221+mindaugl@users.noreply.github.com> Date: Mon, 5 May 2025 15:14:56 +0800 Subject: [PATCH 2902/2908] Add solution for the Euler problem 190 (#12664) * Add solution for the Euler project problem 164. * Add solution for the Euler project problem 190. * Delete project_euler/problem_164/sol1.py * Delete project_euler/problem_164/__init__.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Maxim Smolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- project_euler/problem_190/__init__.py | 0 project_euler/problem_190/sol1.py | 48 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 project_euler/problem_190/__init__.py create mode 100644 project_euler/problem_190/sol1.py diff --git a/project_euler/problem_190/__init__.py b/project_euler/problem_190/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_190/sol1.py b/project_euler/problem_190/sol1.py new file mode 100644 index 000000000000..b18d45be16b4 --- /dev/null +++ b/project_euler/problem_190/sol1.py @@ -0,0 +1,48 @@ +""" +Project Euler Problem 190: https://projecteuler.net/problem=190 + +Maximising a Weighted Product + +Let S_m = (x_1, x_2, ..., x_m) be the m-tuple of positive real numbers with +x_1 + x_2 + ... + x_m = m for which P_m = x_1 * x_2^2 * ... * x_m^m is maximised. + +For example, it can be verified that |_ P_10 _| = 4112 +(|_ _| is the integer part function). + +Find Sum_{m=2}^15 = |_ P_m _|. + +Solution: +- Fix x_1 = m - x_2 - ... - x_m. +- Calculate partial derivatives of P_m wrt the x_2, ..., x_m. This gives that + x_2 = 2 * x_1, x_3 = 3 * x_1, ..., x_m = m * x_1. +- Calculate partial second order derivatives of P_m wrt the x_2, ..., x_m. + By plugging in the values from the previous step, can verify that solution is maximum. +""" + + +def solution(n: int = 15) -> int: + """ + Calculate sum of |_ P_m _| for m from 2 to n. + + >>> solution(2) + 1 + >>> solution(3) + 2 + >>> solution(4) + 4 + >>> solution(5) + 10 + """ + total = 0 + for m in range(2, n + 1): + x1 = 2 / (m + 1) + p = 1.0 + for i in range(1, m + 1): + xi = i * x1 + p *= xi**i + total += int(p) + return total + + +if __name__ == "__main__": + print(f"{solution() = }") From 7ed7f042feeb3567ace384a00707d83c327310ab Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 19:23:31 +0100 Subject: [PATCH 2903/2908] [pre-commit.ci] pre-commit autoupdate (#12708) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.7 → v0.11.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.7...v0.11.8) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] --- .pre-commit-config.yaml | 2 +- DIRECTORY.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 034493b10912..9e13416dc78d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.7 + rev: v0.11.8 hooks: - id: ruff - id: ruff-format diff --git a/DIRECTORY.md b/DIRECTORY.md index fa731e32ff23..04e09c29de97 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1174,6 +1174,8 @@ * [Sol1](project_euler/problem_144/sol1.py) * Problem 145 * [Sol1](project_euler/problem_145/sol1.py) + * Problem 164 + * [Sol1](project_euler/problem_164/sol1.py) * Problem 173 * [Sol1](project_euler/problem_173/sol1.py) * Problem 174 @@ -1184,6 +1186,8 @@ * [Sol1](project_euler/problem_187/sol1.py) * Problem 188 * [Sol1](project_euler/problem_188/sol1.py) + * Problem 190 + * [Sol1](project_euler/problem_190/sol1.py) * Problem 191 * [Sol1](project_euler/problem_191/sol1.py) * Problem 203 From d9d56b10464f6825dc762c1665716e76b70c5fa2 Mon Sep 17 00:00:00 2001 From: Mindaugas <76015221+mindaugl@users.noreply.github.com> Date: Wed, 7 May 2025 03:49:59 +0800 Subject: [PATCH 2904/2908] Add solution for the Euler project problem 345 (#12666) * Add solution for the Euler project problem 345. * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Maxim Smolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- project_euler/problem_345/__init__.py | 0 project_euler/problem_345/sol1.py | 117 ++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 project_euler/problem_345/__init__.py create mode 100644 project_euler/problem_345/sol1.py diff --git a/project_euler/problem_345/__init__.py b/project_euler/problem_345/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_345/sol1.py b/project_euler/problem_345/sol1.py new file mode 100644 index 000000000000..4234458c5ad5 --- /dev/null +++ b/project_euler/problem_345/sol1.py @@ -0,0 +1,117 @@ +""" +Project Euler Problem 345: https://projecteuler.net/problem=345 + +Matrix Sum + +We define the Matrix Sum of a matrix as the maximum possible sum of +matrix elements such that none of the selected elements share the same row or column. + +For example, the Matrix Sum of the matrix below equals +3315 ( = 863 + 383 + 343 + 959 + 767): + 7 53 183 439 863 + 497 383 563 79 973 + 287 63 343 169 583 + 627 343 773 959 943 + 767 473 103 699 303 + +Find the Matrix Sum of: + 7 53 183 439 863 497 383 563 79 973 287 63 343 169 583 + 627 343 773 959 943 767 473 103 699 303 957 703 583 639 913 + 447 283 463 29 23 487 463 993 119 883 327 493 423 159 743 + 217 623 3 399 853 407 103 983 89 463 290 516 212 462 350 + 960 376 682 962 300 780 486 502 912 800 250 346 172 812 350 + 870 456 192 162 593 473 915 45 989 873 823 965 425 329 803 + 973 965 905 919 133 673 665 235 509 613 673 815 165 992 326 + 322 148 972 962 286 255 941 541 265 323 925 281 601 95 973 + 445 721 11 525 473 65 511 164 138 672 18 428 154 448 848 + 414 456 310 312 798 104 566 520 302 248 694 976 430 392 198 + 184 829 373 181 631 101 969 613 840 740 778 458 284 760 390 + 821 461 843 513 17 901 711 993 293 157 274 94 192 156 574 + 34 124 4 878 450 476 712 914 838 669 875 299 823 329 699 + 815 559 813 459 522 788 168 586 966 232 308 833 251 631 107 + 813 883 451 509 615 77 281 613 459 205 380 274 302 35 805 + +Brute force solution, with caching intermediate steps to speed up the calculation. +""" + +import numpy as np +from numpy.typing import NDArray + +MATRIX_1 = [ + "7 53 183 439 863", + "497 383 563 79 973", + "287 63 343 169 583", + "627 343 773 959 943", + "767 473 103 699 303", +] + +MATRIX_2 = [ + "7 53 183 439 863 497 383 563 79 973 287 63 343 169 583", + "627 343 773 959 943 767 473 103 699 303 957 703 583 639 913", + "447 283 463 29 23 487 463 993 119 883 327 493 423 159 743", + "217 623 3 399 853 407 103 983 89 463 290 516 212 462 350", + "960 376 682 962 300 780 486 502 912 800 250 346 172 812 350", + "870 456 192 162 593 473 915 45 989 873 823 965 425 329 803", + "973 965 905 919 133 673 665 235 509 613 673 815 165 992 326", + "322 148 972 962 286 255 941 541 265 323 925 281 601 95 973", + "445 721 11 525 473 65 511 164 138 672 18 428 154 448 848", + "414 456 310 312 798 104 566 520 302 248 694 976 430 392 198", + "184 829 373 181 631 101 969 613 840 740 778 458 284 760 390", + "821 461 843 513 17 901 711 993 293 157 274 94 192 156 574", + "34 124 4 878 450 476 712 914 838 669 875 299 823 329 699", + "815 559 813 459 522 788 168 586 966 232 308 833 251 631 107", + "813 883 451 509 615 77 281 613 459 205 380 274 302 35 805", +] + + +def solve(arr: NDArray, row: int, cols: set[int], cache: dict[str, int]) -> int: + """ + Finds the max sum for array `arr` starting with row index `row`, and with columns + included in `cols`. `cache` is used for caching intermediate results. + + >>> solve(arr=np.array([[1, 2], [3, 4]]), row=0, cols={0, 1}, cache={}) + 5 + """ + + cache_id = f"{row}, {sorted(cols)}" + if cache_id in cache: + return cache[cache_id] + + if row == len(arr): + return 0 + + max_sum = 0 + for col in cols: + new_cols = cols - {col} + max_sum = max( + max_sum, + int(arr[row, col]) + + solve(arr=arr, row=row + 1, cols=new_cols, cache=cache), + ) + cache[cache_id] = max_sum + return max_sum + + +def solution(matrix_str: list[str] = MATRIX_2) -> int: + """ + Takes list of strings `matrix_str` to parse the matrix and calculates the max sum. + + >>> solution(["1 2", "3 4"]) + 5 + >>> solution(MATRIX_1) + 3315 + """ + + n = len(matrix_str) + arr = np.empty(shape=(n, n), dtype=int) + for row, matrix_row_str in enumerate(matrix_str): + matrix_row_list_str = matrix_row_str.split() + for col, elem_str in enumerate(matrix_row_list_str): + arr[row, col] = int(elem_str) + + cache: dict[str, int] = {} + return solve(arr=arr, row=0, cols=set(range(n)), cache=cache) + + +if __name__ == "__main__": + print(f"{solution() = }") From b720f24b89c328944f8a0d6c18db0e09d9bcffba Mon Sep 17 00:00:00 2001 From: Mindaugas <76015221+mindaugl@users.noreply.github.com> Date: Sat, 10 May 2025 19:13:07 +0800 Subject: [PATCH 2905/2908] Add solution for the Euler project problem 95. (#12669) * Add documentation and tests for the Euler project problem 95 solution. * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py --------- Co-authored-by: Maxim Smolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- project_euler/problem_095/__init__.py | 0 project_euler/problem_095/sol1.py | 164 ++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 project_euler/problem_095/__init__.py create mode 100644 project_euler/problem_095/sol1.py diff --git a/project_euler/problem_095/__init__.py b/project_euler/problem_095/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_095/sol1.py b/project_euler/problem_095/sol1.py new file mode 100644 index 000000000000..82d84c5544de --- /dev/null +++ b/project_euler/problem_095/sol1.py @@ -0,0 +1,164 @@ +""" +Project Euler Problem 95: https://projecteuler.net/problem=95 + +Amicable Chains + +The proper divisors of a number are all the divisors excluding the number itself. +For example, the proper divisors of 28 are 1, 2, 4, 7, and 14. +As the sum of these divisors is equal to 28, we call it a perfect number. + +Interestingly the sum of the proper divisors of 220 is 284 and +the sum of the proper divisors of 284 is 220, forming a chain of two numbers. +For this reason, 220 and 284 are called an amicable pair. + +Perhaps less well known are longer chains. +For example, starting with 12496, we form a chain of five numbers: + 12496 -> 14288 -> 15472 -> 14536 -> 14264 (-> 12496 -> ...) + +Since this chain returns to its starting point, it is called an amicable chain. + +Find the smallest member of the longest amicable chain with +no element exceeding one million. + +Solution is doing the following: +- Get relevant prime numbers +- Iterate over product combination of prime numbers to generate all non-prime + numbers up to max number, by keeping track of prime factors +- Calculate the sum of factors for each number +- Iterate over found some factors to find longest chain +""" + +from math import isqrt + + +def generate_primes(max_num: int) -> list[int]: + """ + Calculates the list of primes up to and including `max_num`. + + >>> generate_primes(6) + [2, 3, 5] + """ + are_primes = [True] * (max_num + 1) + are_primes[0] = are_primes[1] = False + for i in range(2, isqrt(max_num) + 1): + if are_primes[i]: + for j in range(i * i, max_num + 1, i): + are_primes[j] = False + + return [prime for prime, is_prime in enumerate(are_primes) if is_prime] + + +def multiply( + chain: list[int], + primes: list[int], + min_prime_idx: int, + prev_num: int, + max_num: int, + prev_sum: int, + primes_degrees: dict[int, int], +) -> None: + """ + Run over all prime combinations to generate non-prime numbers. + + >>> chain = [0] * 3 + >>> primes_degrees = {} + >>> multiply( + ... chain=chain, + ... primes=[2], + ... min_prime_idx=0, + ... prev_num=1, + ... max_num=2, + ... prev_sum=0, + ... primes_degrees=primes_degrees, + ... ) + >>> chain + [0, 0, 1] + >>> primes_degrees + {2: 1} + """ + + min_prime = primes[min_prime_idx] + num = prev_num * min_prime + + min_prime_degree = primes_degrees.get(min_prime, 0) + min_prime_degree += 1 + primes_degrees[min_prime] = min_prime_degree + + new_sum = prev_sum * min_prime + (prev_sum + prev_num) * (min_prime - 1) // ( + min_prime**min_prime_degree - 1 + ) + chain[num] = new_sum + + for prime_idx in range(min_prime_idx, len(primes)): + if primes[prime_idx] * num > max_num: + break + + multiply( + chain=chain, + primes=primes, + min_prime_idx=prime_idx, + prev_num=num, + max_num=max_num, + prev_sum=new_sum, + primes_degrees=primes_degrees.copy(), + ) + + +def find_longest_chain(chain: list[int], max_num: int) -> int: + """ + Finds the smallest element of longest chain + + >>> find_longest_chain(chain=[0, 0, 0, 0, 0, 0, 6], max_num=6) + 6 + """ + + max_len = 0 + min_elem = 0 + for start in range(2, len(chain)): + visited = {start} + elem = chain[start] + length = 1 + + while elem > 1 and elem <= max_num and elem not in visited: + visited.add(elem) + elem = chain[elem] + length += 1 + + if elem == start and length > max_len: + max_len = length + min_elem = start + + return min_elem + + +def solution(max_num: int = 1000000) -> int: + """ + Runs the calculation for numbers <= `max_num`. + + >>> solution(10) + 6 + >>> solution(200000) + 12496 + """ + + primes = generate_primes(max_num) + chain = [0] * (max_num + 1) + for prime_idx, prime in enumerate(primes): + if prime**2 > max_num: + break + + multiply( + chain=chain, + primes=primes, + min_prime_idx=prime_idx, + prev_num=1, + max_num=max_num, + prev_sum=0, + primes_degrees={}, + ) + + return find_longest_chain(chain=chain, max_num=max_num) + + +if __name__ == "__main__": + print(f"{solution() = }") From a728cc96ab4f05248ac3389365a26f01dfaf6f8e Mon Sep 17 00:00:00 2001 From: NidhaNureen <165757787+NidhaNureen@users.noreply.github.com> Date: Sat, 10 May 2025 23:32:45 +1200 Subject: [PATCH 2906/2908] Added/Improved doctests for lowest_common_ancestor.py (#12673) * added doctests to functions in lowest_common_ancestor.py * fixed doctests to be less excessive * Update lowest_common_ancestor.py * Update lowest_common_ancestor.py * Update lowest_common_ancestor.py * Update lowest_common_ancestor.py * Update lowest_common_ancestor.py * Update lowest_common_ancestor.py --------- Co-authored-by: Maxim Smolskiy --- .../binary_tree/lowest_common_ancestor.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 651037703b95..ea0e31256903 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -15,6 +15,8 @@ def swap(a: int, b: int) -> tuple[int, int]: (4, 3) >>> swap(67, 12) (12, 67) + >>> swap(3,-4) + (-4, 3) """ a ^= b b ^= a @@ -25,6 +27,23 @@ def swap(a: int, b: int) -> tuple[int, 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 + >>> max_node = 6 + >>> parent = [[0, 0, 1, 1, 2, 2, 3]] + [[0] * 7 for _ in range(19)] + >>> parent = create_sparse(max_node=max_node, parent=parent) + >>> parent[0] + [0, 0, 1, 1, 2, 2, 3] + >>> parent[1] + [0, 0, 0, 0, 1, 1, 1] + >>> parent[2] + [0, 0, 0, 0, 0, 0, 0] + + >>> max_node = 1 + >>> parent = [[0, 0]] + [[0] * 2 for _ in range(19)] + >>> parent = create_sparse(max_node=max_node, parent=parent) + >>> parent[0] + [0, 0] + >>> parent[1] + [0, 0] """ j = 1 while (1 << j) < max_node: @@ -38,6 +57,21 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: def lowest_common_ancestor( u: int, v: int, level: list[int], parent: list[list[int]] ) -> int: + """ + Return the lowest common ancestor between u and v + + >>> level = [-1, 0, 1, 1, 2, 2, 2] + >>> parent = [[0, 0, 1, 1, 2, 2, 3],[0, 0, 0, 0, 1, 1, 1]] + \ + [[0] * 7 for _ in range(17)] + >>> lowest_common_ancestor(u=4, v=5, level=level, parent=parent) + 2 + >>> lowest_common_ancestor(u=4, v=6, level=level, parent=parent) + 1 + >>> lowest_common_ancestor(u=2, v=3, level=level, parent=parent) + 1 + >>> lowest_common_ancestor(u=6, v=6, level=level, parent=parent) + 6 + """ # u must be deeper in the tree than v if level[u] < level[v]: u, v = swap(u, v) @@ -68,6 +102,26 @@ def breadth_first_search( sets every nodes direct parent parent of root node is set to 0 calculates depth of each node from root node + >>> level = [-1] * 7 + >>> parent = [[0] * 7 for _ in range(20)] + >>> graph = {1: [2, 3], 2: [4, 5], 3: [6], 4: [], 5: [], 6: []} + >>> level, parent = breadth_first_search( + ... level=level, parent=parent, max_node=6, graph=graph, root=1) + >>> level + [-1, 0, 1, 1, 2, 2, 2] + >>> parent[0] + [0, 0, 1, 1, 2, 2, 3] + + + >>> level = [-1] * 2 + >>> parent = [[0] * 2 for _ in range(20)] + >>> graph = {1: []} + >>> level, parent = breadth_first_search( + ... level=level, parent=parent, max_node=1, graph=graph, root=1) + >>> level + [-1, 0] + >>> parent[0] + [0, 0] """ level[root] = 0 q: Queue[int] = Queue(maxsize=max_node) From 59c3c8bbf384ef05d3cb9862a41bba39bb098fe9 Mon Sep 17 00:00:00 2001 From: robohie Date: Sat, 10 May 2025 12:47:22 +0100 Subject: [PATCH 2907/2908] Add N Input AND Gate (#12717) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update and_gate.py J'ai nourri ce programme en ajoutant une porte And à n entrées. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and_gate.py Commentaires en anglais * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update and_gate.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- boolean_algebra/and_gate.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/boolean_algebra/and_gate.py b/boolean_algebra/and_gate.py index 6ae66b5b0a77..650017b7ae10 100644 --- a/boolean_algebra/and_gate.py +++ b/boolean_algebra/and_gate.py @@ -1,8 +1,8 @@ """ -An AND Gate is a logic gate in boolean algebra which results to 1 (True) if both the -inputs are 1, and 0 (False) otherwise. +An AND Gate is a logic gate in boolean algebra which results to 1 (True) if all the +inputs are 1 (True), and 0 (False) otherwise. -Following is the truth table of an AND Gate: +Following is the truth table of a Two Input AND Gate: ------------------------------ | Input 1 | Input 2 | Output | ------------------------------ @@ -12,7 +12,7 @@ | 1 | 1 | 1 | ------------------------------ -Refer - https://www.geeksforgeeks.org/logic-gates-in-python/ +Refer - https://www.geeksforgeeks.org/logic-gates/ """ @@ -32,6 +32,18 @@ def and_gate(input_1: int, input_2: int) -> int: return int(input_1 and input_2) +def n_input_and_gate(inputs: list[int]) -> int: + """ + Calculate AND of a list of input values + + >>> n_input_and_gate([1, 0, 1, 1, 0]) + 0 + >>> n_input_and_gate([1, 1, 1, 1, 1]) + 1 + """ + return int(all(inputs)) + + if __name__ == "__main__": import doctest From 47a44abe23870ca0f7c8062601278645039b1c70 Mon Sep 17 00:00:00 2001 From: Alfredo Hernandez Baeza Date: Sat, 10 May 2025 07:57:43 -0400 Subject: [PATCH 2908/2908] Improve longest_common_substring.py (#12705) * Update longest_common_substring.py - Combined the ans_index and ans_length into a single tuple to track the best match (position + length) more cleanly. - Early exit for empty strings. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update longest_common_substring.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- dynamic_programming/longest_common_substring.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dynamic_programming/longest_common_substring.py b/dynamic_programming/longest_common_substring.py index ea5233eb2d17..3ba83f3d9f03 100644 --- a/dynamic_programming/longest_common_substring.py +++ b/dynamic_programming/longest_common_substring.py @@ -43,22 +43,25 @@ def longest_common_substring(text1: str, text2: str) -> str: if not (isinstance(text1, str) and isinstance(text2, str)): raise ValueError("longest_common_substring() takes two strings for inputs") + if not text1 or not text2: + return "" + text1_length = len(text1) text2_length = len(text2) dp = [[0] * (text2_length + 1) for _ in range(text1_length + 1)] - ans_index = 0 - ans_length = 0 + end_pos = 0 + max_length = 0 for i in range(1, text1_length + 1): for j in range(1, text2_length + 1): if text1[i - 1] == text2[j - 1]: dp[i][j] = 1 + dp[i - 1][j - 1] - if dp[i][j] > ans_length: - ans_index = i - ans_length = dp[i][j] + if dp[i][j] > max_length: + end_pos = i + max_length = dp[i][j] - return text1[ans_index - ans_length : ans_index] + return text1[end_pos - max_length : end_pos] if __name__ == "__main__":

    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%0Aex}QjgBBZAe)o4`CXl;Z)rkhO%=|a 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

  • h!FgYG3t(o}UpjLKrv9^TJRClVLd=aVGQn+i&^J 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%0AG4C~R z{|$Lqi4b3E^gJcm7?=7!rxFQ8I*@Y3YpS~GD2NkG4kh`R`+XQ3m1F$?`k~I?-8Z0=KiJs5 zvtbruf%*mt|3S1bZ@>4EV(?&%c&G1P57!$u#UWgZ5`$I|!AL4T~p)#mC_hWm1JXE&Ti^|AeW1z+7<^PAs%$<@_MF5Z96ql+!i-oNC_ zpS@w6J&!oyz4bGkUXh1LQGwm-YwVnP`sflH5+UA^fN=<{w<}agT&59M3@QflGSI3F zVMIqm*L0*38O9yPW&#PHThNZMNW#(E7Ox$xa;TKRN`lDsEy*gPhNKdyM6B^75+T(j zWQLpZnxFjQr+o59&zU-l)*UH#^!*8spP!?a0W$a`hD_EQe)Ly=iFsnV{`arx&m1>5 z*QBYi8xuL6@Z@B}Y1{Jn$q6@MBEP<;sKUx*yiyd`()1_TRl&P0>x)Y)TB=WcM=^$? z#0a+2nJ@1SH1@!^6+AudSa(k9gHqtEZFzh#alcL&-%_M_jinEyb>J?K^mi?N<0xSu z#hIuA+Jk8oCK`;*lr%Hti4pKPR4nBE0l#V}$go=}B_alLAP+e*%wY5+!%*j$V4X0H#zbK3@w` zyGS3LJRj9V@0>!`dW^2P>HuDn3RzdI;W2q}G)`a;X(h;#l`een-vQ zpR2MU&~h;-+u=5sC^fSmZ}@zdY5&VFNc{;n*|o{Z30DWg+*hHqBDtN6R3c)3&uc1QM5s8&+{jjeD^z_ zaryi`n(YZH6-?GBXTUq0nsLU`H$BctYD^o2^9HZPFlCJ;guu9r{Pget3wt8U_cZG* zpFI0M#6fqe#jb(N56hCK%agnn~9iAC0>4J+@L$EVsOUi+?%yeiP zrCDMiI>TZ$b1wANaM@Z(qwV6teVhn!pv;Oz&8Vz6j0ajLB##gbB^H)&ATNsyqH}?e z1ieg4=Oa$fc-`Z4&-Jk5^_v_1tnD~Ec}!!sb%?g0lBUd#I7GtXK)3Pu<6gPe>ZAxo zoYt&2r&tf&$q5wAvL8_gF_WVdePZPwh5!H{07*naR9R@%!eSzw8d32UYa*?(tel0_ zhO5&HvU4yT;4s1*$+yU(x=8#ZUFx{o@t&BFL99e9vM*LC}LxKxF?QeLR7Vckt#o^^Id8WVO+`Qo6 z0^4rQ=A@(V4QBO(>^@=_CW61BC1czvQ?X1Gd<$D!&>4E=N#n?RWBB2pf5vA&{vrSK z|Mq`!xVa}}gA19h(yX+iXva8b`r2QaYQ>cfM_?;G0F7GojL5sKmVm%k<6UempQO4~V3)+cm+z^IHfmT?JO-`vm}gR8!qzB%eb)D=3X~4c0af6=cd-F-MnzO^MhRFb-v9*qX^0lQ7QIgjwe4hwt<8 z_rJ$_wc`5K8;n)N!@`RfUt$+eNr}}apjCr5ElnROIp8-dSud7|X&RPsA}$f_3a)P< zCzQ(MlF&(#s<|3Oa*QM=WSm@N->Qs583#q+LnwoF2GezfNs`;k5*Wt=F)Xqkry_q# zq-iYYCnub4PsH#Vm%5J0Y^ub7N|A!#TN`Jv#xRFa6aKnB!RvgDTsygCjWp6XKu$;k z-6)#2=d|%`ADy#WZ}8p|Q)E00=w;@RCY-ex43lv+xv6N-pgrf!iora9GPO;mGiN<#dWg8J6oMH}{2SEU5-nG%VA=Fwd2GmaC92gWF|Uq>Ah?;q-yi{*-O+ z`OW9oynOW9u z@cjIO&Es>L^Cvtxd&0(Aw%ZoJ7Lt;i6La=_aXWH126nH1&-vfA{6*LE`EP&86n1Fi zuvXy}U@aEtv}V*AMaHO%R~oWmE|%mSbJ=lmV)*o1@A2Ws->1xhyO+NvFZU1^f^D$t z6@BN?UJ_$X*Rk5Hv1Xys2|~c&=rE zDoLggqY}zPk`%k#Gj;w_1S4ByXeeSyF=!kZB~+n$?TTo8?9Q!%eOY8isccPONEjo= z$bKBzk1bu}aNg0cdSZfjxMvAVyRWDgo$CEH5a3MKO~qlN5hb)vD*J2-0_zWw|$&GYXtw^#i7>sO4sfhlH+ zG6dI}G}P9j8w;|N3A5kk3B(7^@vhdw%%- z6V&BntWsbj$~eMi=5Tez?!KT)i&iU!G2t6S+lsQLT1V0iuWmEra=-<{Y6VU2WCn;9 z14mH|LnO=!ZyiQuA+MxB-!*L3E0oUIs8LHH=}ed+^RN(;u2sPe4ofVF#u>ab*p#Ja zH$^O!te})2sJ#NcFvr3n1g5yq4h8R>ki=^3PL7uC<^bz6iWb_EQkD`mX;$cHXz*xd z&`po)6X*RJYg*<7?sf+b^N5n0Tj+RJt{ZD=Rg|+>74fwP+Luh8M_i7qoF*G;ZSgzQ zPA&6}hv$N>qWnA4&s=-0k4A1&Q`Qec;KMo##z?we0-Q-^gH~pAvL=hzCo^Uv1k`jC zgrn$LwURm40FO4dK0CzptxI`J#FIgHH5EuGM9}l~-;R~4HDK~~o>=cY#lt*1AwsE~ zi{&{eDgOHVYDsH5ukfX136_!~*(C1gJt2-L3Lz(gVEa(EK66SHtF@d_kF(NLgG=Q> zI8|?J3H~totQ0zBR8Ezh0xFw_`c$hyD+%Z-eN50xI$4sN8l@biNR77A76rtdgalD! zPj0LiP)SiJWl;&CpejvKxf*~gHNZIeK1$1MwZ2}X4arzp3*xbMTa&GWY%oO#LQ{^s zaxK4G9%1w)@w~ymw-EMk$Zbz{=O{eeiw&nIXAFlOH+Q!Thda_TlBN-*0#;`+X4pyq z1X31FASq4Ih7m`bGdc#;GGdkqpA~pWID8Dm{XKWDElnQj;=p|M2AeH9C0q{VGV|rv zzvK4yB_)q|n`rBGlf5CdmaGl2Rvf!rXwXuf?JXNTtG1;e^`$WujPgLkFebshqrn)m z(JU!5ghaa(8rQHCjmZ-BvQ38vp~_VAb1ZdFtYs}MrF^3r_h@*_HPE5du~NM}P#BN; zv8lnVA`2qa3N}-zgRO7ryr;Dmh?uPEwQ@A)$xJIH#Zn415Re+2X^AYc5VL+DfE(XH zHY{^yREZJ`DQZxjti&7`E45I7Izm)OTM4sjh1w?0F63-ht{0}mj!+`TdN~F}c}`eu zP^D7lm6%t&Hh5h>KP~4(YaMekge(L*t7`OKP{aDcRXERr8z?0U3SA2_+h|E^?cuLE?6R|ig?nIP3;*v%2Syfs?>56&vSkd%2KX%6@W6e3m_^?1g#TA z6&4^WD+$}Q&}2nx8c+>MTb7h@Dr3E>iHM`K=xr*NEZ8Nj1G6o*U* zlk{0@k1h(G3zmX4hLSQRWx^6jB`_&V_KwyYHtUYY3&swLIJAl&zg1C^ZZ-<71U+Ff zG{(|d`Ac&~P>MIxjGhd=UgMo%DTXa<>;O;N_F|Ie+$)kG}gU-}}LL_~#dI z`1cS{|YO%4Xx^f9Vw7Q~-rfXV^ z@~r!YX&Pu(YtGl6-mKU@K4+f-U%&p6G|rrMo~G-Gwq+?1Lq;cYfoqqrS(BG43N&(9 zCgkeK7mQs}mR=Q_(+Pdu5{z^R6)i-H5@%FSyt#VC)y-b68>7+A5KE@@4HxH+IE0Dm z_7%$^qr$+Glc!v4*K+vW9|$>P=h;yn#S<> zWW)K%nrYgz-)T~mXK2y|lOqnre5@>}2xq0_-757dUW%Zi-o?{PXYyMe|7bS&Z}r%# z|0;D~9*6-b_YN+(yz@2xKaS3Cd=Bq8K##vie#a$>^rjviw5q17m44@E14|_$f?YL>goG*MIdd_|CUK;KTPmq&qtSXF*4tRyA>Dz*xfJ zhOfW+Eom9q?eDl-ZE8nWWIv38K2!=-BIJlQTGEzMiYHj94Cz>{QISkXP(rF?iIfUS zm0BTCO+$;AIOISA&Kaz0WFQe|mNAIa(ppkdgp!z3B+LtGn((e;z3%XxCl+z2RscB5 zvLJ9;L5v=qyGOHFuGg+ILVn3q9CfanMOCmlLlO}rHF1lm%U@RZMpjD6<4pMC#Kn=lK>0F6O+l7QAK!!)B7a70K8c@eXt_ZsI5bQ)6+J5hNy>z2-GR0_)F4rm@&Y zh7qOK^5`tRa}zx6h&NG&D9~jbd^g_!g_2r00@?MWMVLrcyP8C0&QFSq26w2nDT&62W) z6R&uF*7ErBG4H?sDR(z}o_+n2r`tztPM?z8mU&3%-N?8sjA*W|ceD#MjmKEU+uof> zP6Y!rO^bJ$)k%k4^}Kp<#p~^|!I_OrArZ4-G%H?ydBA@Wuy)INUkJ;A z-qe9&t|=ZXd+BTn$;rMkW>xpWGISZzJtszU{pKZq_n-bFU;pz@Id_T2ms@(@aC)}p z(ZwkrKK+oh%^9!vuSv0>Ja|Bj12!Z|kn`nsEyjpAO?>}HpYiSYpYt!@{}>+=|Mq|X zH*A>E(?AbwwMV6y6b4G1uubi3E>Y$H*$`8b1eLLLeMj51+~rgSjDj+)80~6@>oUwR z?il09>zjMN`uG12r$78Y<;^YY+5v8~)cmYhux=AN$uqg}9V&%dZDSdinW8Pm8JxBF z#^D<2@4mRW;O2hh_WFulE)*WK?t5ZyKv_~oF#?_U4XwX=fF}5LQA=2d_19oGMlrhhvr=N29 z{97mrPPJ&`Y1ij`G9`SzV|#K+#tQ8yCdw>H)nR@^+j>ZeW_yb7*NoGR48XG_vQw&P zVIvfiqBPz$lv*1?F&Nbnu&i^W>5Pc>%A$;=X*-(jidA>co9l&`GII!ooXH^*vf?m| zT;IGQr^wm%gvK?5alw@IFo4uW=3dsw-q#x0lcJ<{t>lb0wNEuAC{lTXu4$}?S~5wO zmx>2p#>iZfG=N(>xYOV?>kQemi+sp2dm{Az`_ zS_YbVq2vW!QvEDsAe@vY7D;;t1#?)q+1)eEvbI^ZFwT*vHGcNkN`EkTTr{Np#C~_f zVcuc-fW^7Uyo{W67TdnZm#-Rz+u!r?2T$>>X1zM+laIfL z*N#W0E7Evi8Lqi|1L_7&&K?tJaO^2%#jF~F-LTuw3|Fr>Q8Oza!3C0YPzAGGnu-tQYF6q*JJwe*g0%0)--Zl&hG{mL=2B9GcJi4 z9)x>^h%>e#h(k$Y>OOWP*L|m|Cj0VESm+@ffg+X6JkJcnfI2_nWP8GTwP&{*>)Zlz z7IRPt3o+LFSXI*FA5aUAuU9LX&uMMY$vlwY-hS=d@B&)NezDFjLKdo{gh_-*ke5WU zGNZE!Y*Oeb@v$Wp4k;2+;9_&m>DejTdzO&!s}52Y%B(Rc>nS?cOF#?0I#+>Hn>yQw zw6?HziqqEc{CrLK>@jzx=g}`e=c^aj%%PyXMjO!zj>N+ti$Nu*WQy`A(@=aza6L&U zypEV$FgcS-lv#OBb^kZB><{F)=koCyqYk|MhyTdk7r&taeAA+=r&~GB9&Op2Zc(Kp zW0{r%kKDx5(-~)@B*JnM6OBHK2^yjy2SdyX*J!dzBo@~FnyeBr2CReCYDME)dT&{b zmFHC|u*@i(AV<7*vJMqZS*YJPGmZ<^!^v5XUv=a;P!`Rw3yk}L-R_3{ejpbMrXymR zW1?tijiXExd706wNZqVyNIBCw&DqHbB^!bYOkw2iZqFD3jgNF~Pvhk|Jud}qXJVcS zMZ`gp!WK>AqUm0Z^)p<~9lD)zfp z2$iOgt;~u_qwvMirj9WUL={j8Ldn&@qH7ae!6>1xs){^MwSJ(ew|(@wCftv69l1Yp zHD#xF=CA7Qth3YuVR;9^uf(ifa!p`o^)T~OH4!GFdp(n;^0s>wG2Wr%_j&N;y)y+Z zl@*_Ic{qQTs&^fWI)xT11|yAr!1GI;t*RP{JOix~b2VCYh>$HY1~J=ESfor&o)40G zti7*CYOO{YP*$ExIT!i=nuJ&1pr#&mvHC~7HC(Bx3&?LOwIm^BVlrr>DMjJ57eb>h zVy@C6WE7RV zFoVkyIc16w+zFcWfr`8NSV2=OA=xbqL5YKONS2z(?Lv6zx^Wu`0(*RQYG?FM2JYOGh9R6|OQ)+q0#a@8OtmAaPNV$n$i zeCs?bT0$t09m@iTDTsOBc`+gP9-_sU1#RUcjD%{`EHq6=)Kcqgt(94Oj`G@TXi;k( zw2a!nKrU|s70Q^p4^|Jv34ILQX;lS#QyU;elP$^+jf7RZrlIof_uAyr!t&uuw zn@uKGm#hM;)2w|-WbIg(NJW=a$?MqAR7vC4`g>Y=mpfpGwe zdSTUWk&4-nhia@MkQ{2tG5MH}TYxI8=v>9ODx8)qLwWTU+wnHRnkrT(a4g&_**qlk zcu1<&%~r0R%11=Bs>z%fD%!+UBn#vi2+KsWnQ5Nb4}0$KZXgCo5tSl3=_=+GFtPSb z7eVzGIi$ug6GNuNOj@$I*Q@KyDzIq0)-+bg2HH867ztxQO%c_5jBW6)qg!2IR3Hr# zn!x$#hWFol&Ue545#RdcLvHS_`T5mre)+rK^7p^~1^fGZ(lBv<^#x6ET=qR5oSm@x z^{?^mnh!sGMo0tm&j((PBQJ)?_3Is--O?G0%JOn&Q*f2x z%(UH>wRHsVnJJX8V2cRQ*$O2hSxrWxt-@qEs7NbcCZ!2iES=Fb!vO1!fyAN;Gm5ir zg|m*`-JabN7>0>qH_-Pz(H7Efff%qUpwrB{vzXJ4)iSf)^mOYL0mXh=IE0Bg1eRrF z3V{?N!@Ni@Rqc6~ho7wnp10-)RchQFxrRmdQ*~n_V!c8f)k~O|=E$>)OIE8D-nERw zfH4m5H1o8ts6p8{NKccliM4kt%d7j^=!YtaQpCBZwIq$z@$h);C90o2fK#&Ad+*t< z*W5LCTo)0&RW9|=SH6KTLLJf8(rbH6!ym=jqtQrdNl+9d0bg{*mM9VN-!&7w9k?o# zDG!45al?@xo(Yn?J&M96Mzl4|LHgm}9@GS1rXS2d>OmCF%2G-o6@}M4B!1sMgLHL; z)${YwZC3l0%sVafA{NUgGF6dMmx5E8u5Ic2p04Zg*0C^)Tl7d0Nd>HVr^CLy(+STv zu{3|s9e)(?sRI6g-EDsqLDV0h@ayMRhh5^Z%zv;stk)3w!8q{W`mbd9tfWWs4}aOW zzlWfvWOMXD{7{M|hMLxu_x1Q*#FF18>#2l*ddHolyf;& zazCI|MI1WzFeg%oB%g>%5tcwHEp5|bQb5@t?|;YVuU_)W$@}bY_V^MpW=Gd=&`pn>1DnnZF-Bz?uZc=y zddKV6J8WApK7gC)8xLe!<*|Cj{q=zm4Zr>B_q@J+!`01ezWUv7IB8p)vHa*y{|vk7 z*q)wo+I76Q-Lh?0fZ^nF%b+F(JCWOfF@cy1^Bj16bIrQfoSk(9Egg|I7hIfCrD5+B z7iTM;oNnY`oh>L&B9LtcX8Bx-LM@Us5?OVYjc+mNph+PU$TU_lzS%JhfjkCUZOAcj zNCp=ZB#qIH48<5RCewSzryqXAcRu=vN82qg_iyOe4jo(m=5POot5+Z4oacMr{gkt3 z&#;Xmph55C8Eb4$MYnrH7lV;(KL7hf^$4)}I03B@IHAEDKSZBm>#%kyo| zS!-yz!hU~`wSvQI&5;1r038>bOE&8*(RqgGaear=4f`RGRYS2Iy2;`sH%j`ga;!B> z1)~y9Wf7sY7IsKd=qPCJCC-d#W{xw;K+}5qu1BewX3qs@4PE2vGulYsansT@Ejnw4 z-Ja>N5JRehnqpZZA!eFJqfN#*i)oAuwH6IgM2SOC%qa^JSm~O4GSw9)>!5xoNmXU| z2Lsdtu|;B#q_@o$RPP6xz1{3YVzUO zbts3-TnceU_C6_-vj{7(D50Nthq3ZL#+13Sclf5IP-v{DLDO1=wHl@6Gq6bNG9X62 zqQ!jblng;cB#D(+1F8t3)Yg4TNrDk=8%tw6Yj2sCL}3={l#=8?&PkBpm4c!TxCDLH ztWu$mLX#=~A6@U!Ygv|_={?JBbKEs?!(^nZ$||~IlWm07K;S@-pg*gj00Dvs2yMjf z7TM@7Qr#?;%dB)^uRUg)Wi|NLIXAM{%1PuP5ck}D_Fnrl@B6&L%RrYvR8FwdB(hSN z)d&2&PAt(+8-1DdC#U*cV0>AiH z#MpoE$tV2m;|qTN+k3uro`uykXjU!!acekVZ+K#R7V47i;ht~4vwZ*6gxU|R^bBD~ z@H5I9tZ5{|Eo2OVq$f}odyJ)ln$34~O{&wQctqnK$uZ#u&pu)Amf-9J$F z?-+Jx+^Z2a51g5Vjw5;ALl`lQWGwm|8n3O1h|#P)hPstGm-GMei(Ut0jRBtuxUF zV(`Rb2+51U+bppC8LnT-vMx&=JS9QRS?sYmWtmf9@`+#qWg0NHFa_A_6}S5tAE2#n zwxSfaX*oMP<9xAZ_4KKzhg8cnMYPe(^T2O@`z5>W9m}@o$&)LZrsw+lmS@kOvREz1 zS(a*zH56;BLrYxgnkcRn-GJ#)O`+);I-5|9+(%X$v{}lIHgp0KiB95hZMhq8E37g2FiYG?E11n>3X86zu`QEs$Pv2r2De;6-a*q4v|OL2 zZ*e+fW*Htgt`)LW4n!aEJ`uywJfYCqi#xnEf-}|z)oGM!m_sDSiKHCK39eVCByo{C z(>5*Ev>Zmy95O~bS)eK{c!T87G&CyWO~H1c)B&6JP+&VMwu8(GdZjTTk(|Mo!W=!T z;;|(XOrf*qY!*Mplok8&*9`fV216)InJtxxC!OJoXV3Zc;v?qK5sPPIHQmbKgQZz4 zd3yGQzAgOG%PXE-euA|dw%Z-we)(Hozx|eR9I#gN*=L{8Z?0$?!JF%x&?#`SS@Zd) zpYid_kNNt|mi6L>#>(@SQo;y$UIy1nh(1A!Jl-G%B3&dhXVW(P^wUrIFJwlgWCl|zEijiK_W7kQ zg-Y%Bl*)BH5-GY?V_N!QF%wl6_;E68>a0eqAL{Fm^h1>&=QUbmvO3jkC-Br*|Fju##GK8Fos?IQq#9|hVPy}k^q|iYNV+ag$fJWo2W3g&kbgf)}n`t{oo+naC z^{y=S-=)^;^B%(#^ zl?oW+XjO+X9a#m5$BGG+W?~$<3j>E?;_O%7^U){YGv>t2yVrdGu;t(UtN)WPmL17k z=vsn{cpX@^XI!nHarNY5o?U&y?sms_-!weAfdBgM{uQrY{FXob)BlmhdP78sK(+4d zL+qH#3d{o~k6 zITy~_jyOifyrrsvXW{a=XBI z5mk>5JJcLm9*r0PB{C&LE{a4;PKq&$5zW%#ib7dKzv$6z%OUL9?F0Mdi81o=#MwC4vRFEt?%B_YY4!|rA|zS* zrc(1S5i-V6T%C`baHURWHKlW1+*8RFk0g$|X@4BrDTMm0)Ja@(+!0Zy69Vx(I)cR| zo@*>>#Q>c8)^c(Km&fQe^%zsCKCm?%7x72hhdS=Tj^t6TPUf#;{<^MkNTn3o%4e;9 zABn3a$XSHKl53>9sgC%g=}dl3I-c!ywrXn+Tq^BV9Z9xYpL+IE<3sYv?JQ^ZvDPm{ zikeYaIej!38niJeZD^a0#=&G!ty!-Y^j(8B5~0&rdDp4})>y&Sm+FMq64O<(fs{%9 zfGQGAWz_Nl-S3itcKFnjKI19$s@hyBd$ZepA>54!>HXQH2&hb<-U zF$}C0D^zSG&cr&hb}}ndg-|)M8klpIrFsUBB3X;Vp-Lv^NSreP5zkyZ*p zDzek9v+`VNp1!hZLlQBTF_xkX&N)gdHJ+lNZNa)s+kkb34nto&I_(Kr6NlM{zh*yVyB4ou^~!|j1-3glx9qgL|6>vLd? z!8t=?4Smycb#cy>zhJXovt0BTAFw$oj9w6{da!X8?-DT_n0%r&t~ya7%0#r*IOp(Q zibAqvA1S4=T2$B9D$n zhnj(M%su9WY?eh)&UImu6*|gDQR_#Aqt^Q5t}Gn$QUsYnPLvp{`cvati*Z_T_;Dte z!Z;kbzrSYp@Bq;Z-c041?;#J;=ts7Pz&sxaaiXXMN*eT{61hYa5v{B!FiBFFrGPF8 zs|!{oiq4`&_X9cvy5wj}k2Ni=E38|E?)RK8HBUAjfBYw(@x>QkFvrCG*0bBsynT1a z928kO-rT<9?dw}QTDI=MZhzoxvEncWUcGtGtBVCXO|%!!7?Pq@J;n$=UUDb|juNvG z)Nz97s{=D6jFMay!6O=+0jCWPjZqpWn_a9DIBd$X7RRFKb&a80^nfGH;7TEtNHm2) zM8%BNic~cFQ5NMfM^f-o1e6(F23oVg8cVwnl0m9VUCCF^#0U{2dus;OK(a07VMo-C z@84hZjkD;@8SP;bET#tCbTqwS<&`poG@*6D&ojI0Yj)wl{{AiN?0I^=W_huuJwGEh z4MU9F4I?22rV#O231)JPn4GcBN>&{j+gNn5loIQp{8*&O^QSdrW$H6fDmqA4+l^;0We;7xvAD8q>eX!*}g`@l+w_Z$)`SCnHIr;TtVO{vZVShyR zr`lcf1Esw>E@e-R$>V3#zb7<^QJkM8C&_%{cwed`-~WW~Hv+7zGy_hm`eX5Y`d*c( z`NsN@P@syOCCZUamO-L15;;*UXb}YEoEdY#51w)cjq63W=tokV(XNw%w{bY9Xd3YI z%y<}CJICi|E&Z~WOY0+ZjFMATB+CvX#qHOxUh(Buza`8ELfCK^BBkwVOpDTv6n#Yz z7L*RPG1nAr>-VEUOPqqCpo|EwR7rG=!&jomF_#%71B%7P1^cpNU$g#FOf}{LQI)1T zq=-QjjtOFngg6stPxb+844ciG^=5@W;u{sX#?dH;4Y7VNl8=6*@Z{P=9kT?Fgr5_$ zLW|JHFkx)NqUlAEN}(xWi>A|-cG066OCT}j%-#JRfA_23a@kqFc>aVhC(U2~hreMN z;eYt&f6nFiTXt#W5JtvfxKwaPVldEPFLf zv&Y2D#&zH<_xl}(-2>5kh#8}*ATej5z8E8e8Kq9aA5z&lK{q-PR5fkA?@`>|-|=n? zJnRolF)_!8!pj+66|YWPa&+X<^2v(}e)`D^+Eie-=lQDV@}sA?%};px&5!s${`bG+ z-M8QIvp@I||I7dU7o4AMxZX{47niKO;xGRDzu-?ld&&Rxzx}U-2vZ1r_x25oPcImP z!bimv3yo2X#WLrHqEOMV9tof5lSG0g3sboTP!;$nFhtWZzadq7f2cH46V};211ya{fJhDwy`8%L}+4^ zsKA{SG_Mv!Y1cTig4T*_#yCae4JA8bF{oLZjhquoAyG%gVU3Y^nyd-RNSzm{^+c|N z#mD#3iB=QqfY*qMTIZVKTLo5Gwo!6^*2c_eeH!p-T?IuPk%t!Tv_zQrAn_H}VK6_) z<}2!hDRg!FD;^_fN_7&6{R+VaDlO4kniID8*aw6dNwFasMWZ^5+YogTkxOGJ9+EIw9B~vv9AxkxVOJZib z-E)1nDE2AF&u^iK8`FGJs86@WyX|tn}NQqzl>Kk^G z=Ir^$Y!-(9?oWTrPd@*czx(@NG6vv&VD?*F-=I`OZw)pkmQ3i7us$)rdBgSYhMSu= zgu@a<*P`;Tpm)@&45t zUVZlsaoXZ^;Yr`{^0UwQvrnEf?r-?+ZerYi$A0Lz$qT*-h1*hCUCmrQ`w4}YE`2xjU+WOHZ6bq+b{7bw%c>&>4D+lo^CD_y}*+hoJGe8(;A6P zD@t5*MQW0TBnm+(nr6w`tvO$6N?P#QpZ`nF|LhC+`g?Zy z@A>}iYkp&Ao__S4Kl;N@=ns3kwxNuk!#vSh$EtIL800-_WeKBIIT;eHYw6Z&R_B+b zQaIc{up1_B?jM+9#s`me3+cI!3ezmLtoQyg>EB{T3yz^KwhY~}WAlDZPM)WiPq5a?68x~`?VCW;D!RTC@=I3an9+?yHrfw{ z@nEY9TNn)&a+_sqakP!eXBH6@`Py{lr|Dg z6%xbV6QhXDtx+tOt;F6;QVXVhq`^qYZ!|^RTR^WJx`0!bu4!>ihc%ixCbVftF*8pd z?-Le{Yg(Za)O=r4V{%SJKP7Qts%>y+am)p&Lt~Q6?ZQlZae;br&Z}wSdbefS89J9C zc_t4rC6?WS&z?TxV!7n?Zj$9$2ux*9Oqy0(o~;*j+2SY9`05pRyRW$)2L8*h|DK28 zf#qt+YPG_+7T5LS4$PL$8dM3)u)A4ef<)_I~a@@>oIlHb)Fh4Ko>LBzvF zmUEj$%d^drv%ZC#2-8T86V?=#s}=obC63e(=-Qt3<_u>nhdB^K#4N->WsRe28jO~F z=u|~OLL^dkv3wNKs~?E!`M@l3T$q*eh@4hR1nc#msSA%xG13R*c>H%ODOEo@W2poQ zgCXl&*@23plNdUbCKt^d3;QXMy=RJYCWnv-sgQ!FxPjcXOeqoTqMek8Q6eT3=Hz%i zb?r*kf&Y=>lm&;4CTC4a5=&N6VxA+1!$`Mk==+|&@7NGo_`6BhUu42MeKV4Kc%1c!zyfLy6F$(K6rr6r|2DH{RR#ReN zoc4@<1XDO$t$1?2X0vE<$UMlYg0fKZOo|1qYNSZc5*J#MOc9w&W(x>}a1F%#zLRnOOm*p&rJ?#uj1&b1yb-?R@Fe4)|Ol%JWgHwDz zdcMBfqO@Tc3txTnhF|>hmwfRr{s`A!vfJ-5YsE!($@`lT``v3+Ig;{g7PjE*HN$qv z`&ajztu`zd_rL;|FDV!z5T-<$MocJ}Vo|Om(4twP=_;Aa0u9P6s%tpXV#vjkV`0uR z*QCgM8!c$}5EH}fIZQK!f*m3y27GVPiv}DV=EVE^J$J**5Jdb&38<_nRm>|Q;3_aS zVw7qftQMAHEX50bHrS4wlg!JlBcT~Xz|R3p?Zrc3KSqiVxELUMnSB}|R}9`WO*12r zq7?W0fw#(FfkW_wAhSQMucGDMRHdaM7COvZq@bVoV&y zz{752e~^1p9cx9ZVd@CPR~6^3M2L_}swiz!m-b4cbC34$saLPm-$T_sS;^ICrcR&Hpda=2x<lw>%=J9w@Vg_m6*hwqi-7)UCZU!8BedSaK@5?& z?%%WDU$fo4N9oA3bxR0_mW~c5q>G#d%dUlfDUwQ9C8pRsfhh)66y6N@knw&JVS7@jkSOyGWk!mQ zgbe%C&{~P*n$kehiJ}rk87NNLz%2KbHl@Z8$7#s`KU|vDX<*%n*{1{-v6) zb<)Hd#jH>@ZLr0-o#QL+AmEXN)tb(_6ZOL>i&KU$&dfR1`1tzVYl(4IMdn$>wIpTH?Sj>XrqB@m zfy4fQ>j(DJ#H42Y?1>==EinY<$&*8r+y{k$o)i;STaxbjfClmY3TxzxqShY|)_Smc}q zv5=CWGD{J~snJz=Dnqz(%wE>wlx(!6=ps2HN{X?E!5RqC2mBOi8zT>Eby^u`DWO#HN32WSa*91D$IPspiOCZ}Bt|J_QmPp-My`L%fjN7& z`vYvvBgaXoAjzt*QsCy3EL{JW8W|nDgy=UE9K6>$#_0<)+Zy94|h%;00@)9Z;f5c3OfT@MN zU`1qsskIeXJtjfex$rNZb0dzi*fr@&>0ci?WOW~rkxQkEZiaOj&RugjKVc0WF1G~eP zah?gPpq-&qL4L6c)mT&#v5(feVz1Mq^Zx@ux2ns94{>8h;zq2I6+A{;KmHx9t81K7 z_L)-UACcHO>BCMsu~YWiNt{$p3F;p#4}>0Z%vmogRf>oREy@XkuTE}mRUQv0rPS}a z@yiM4E6u-<15zx65b)kZiWmdV7!gSwi^;4`MeOn7{kN9#Cyl83#|!;(L?``Vi7jy` zh2MAB^TAT(xW-Q({7(9$SU4gfAAgqq0aE{@Zur2Me-r^6o&7~We%_pNEr4Zdd?K%? zN_;Ny49e;OK9w`q}rx%KH#G0usVlRWLjjuYXR;fc9Q=g@LdcVXRu_58T zXPzQZv8f8ys|EMN!2SIlU2ExD!~gv^{}2EA-~N04r$7H0X4%0Uz(-NoYKyXpVl0cM zp>0~w4quB>r#16oWY|sg?FCEMajP3*7MD2{6P$?@C3q^OU2P=P>A65hOv#uc11VA= zr-UkX>7*3aIacd6D___T6Nhmp^q+HZ7CF7V;|ie^*JkA8a}kbc4wFvNyQ6>PD!SwZ7sIb z?8Yrkv8=kngUyoRG-=w)mn_y7eEI#rw>OEu__IIb=|_J=iA(nPJ6^wjPfUU3=8F5f zfHBXQLtw`7^v7S|Ee^`IrBix7#Kk%cU{4t+?^ocmgbiwJwi;Feu&N753 z_}@G;mcUv^l51!?OVb!anB+9Ga-Z%W?zy?SVbioUZ6nJQYcR!-wL;He%?4{z~Pki_O9pkj)t1o}WS-;>9 zfAmv+`m;agpZ|+LBP?F>yNd;f*WdH=|M%Ct-Cm>CPnenw%d;!&5}LIL28Wz@Q0KJe z30tMPVPXmyYkFEblr#7#;deXU{?}iix*k0%o}7Qg;3LsC+$YCvTyx_!@9rX7b;;bU zDaw;e!6(m{JjyrBlci)4DTExE^Nf!k!i+96r3~b7#EnDa9EWKpdXH%w%1PcO1!9mo zSQ}HZy3*hpC-=%~)f2K}N&`wIv?;V&NJu#r;+#l8XFB?>M`_JG9mH&zYy6862Zuah z>V$|nCCwaj;VbS_A;KsWBx@+)gI#|uC{%U93m&kZvDQjlkxkVJA1S7J{I^vVar9uu7izUWH%$}Ge+Dk+a$1!ILeXk=+> z8>t~oXi#2ivf?z+i2A)$j|x`AgjOqbmLQR06iJJ~xh>FZL$5pD-97O7?K=*;_dHyG zRhOn`w9N&*>QS*pCr@t;%U1HCr;zX^QPRk`Po&B7_Q?oV zaQ4D}(8B-#AOJ~3K~xe?LeV5`(b{u(|DHQzX-q?Reo1dEA;S=*_Imel%fsD{S2u6j z4F}c>$N8#d&W7%+=f%?vLpO8#K5+ZyHGTV>UMc3A2UOG34h_qFhwIOoL&M!+$Gp2? zdboz|9dnAXJcmwU?G@*XbEd@wLpyVyBX92#!#1)$*IcYL7c0fE_waB}X&mM3hNdx; zI5PMh!|fZkH{UVuud#k0noOrH%J!8GRIu7Y(@3A6o#1ne#FZV%NJ>KQW=aT`c zK7zm~Q@~nB#N*>gQ3+=oap}#GVLLE%I}V#`iFYf3%cbS%*#&2tD-J=hy;d2N_pBG- z;(@Qf{0H*x9)m`;9h1*=XB)ox?Qh6_qPLb-X@+@;A9ff&5r+$wXB+y>3WFgg0~umT zgn5FPX}b>RT6Qr@cDGSAV^!+PfYd@4-Gb%WQ!ZbA4C^J@DGs|Ez8mgX#61u50dx6` zt7S*qIGmSEWUUol-*Vn;h`GT>&^h6>ETDuSC^M0Ev0|mQxX6bcQ%a1L=%khASb>%* z39ngH{Qj1M%>{@bEy2k+yBI>mHNE9kkuvv)^uIp=cB;MiE&$!{x8{Ox<@SU@|RX~@Y?3UqFPwKYf8wv+&kRfH7L#&UUa z&Z03~y?8-u1)bhl%h%t0&HHz6dGXP6tX+^o#OH)|?j*P_wKiAUohV=mTVF{M@uG4; zl@`(jjC5?2Ap5gs9$?pj{FnAdx8)NWM z&hobDh%wS@OW!X5abX%`F|NZ0d9UcA=!`5=W6mT6p&+PtrO+*OT_Yl=qG*(6-L<57 zCWb^YiDG0{WQ>-3vXZ8fQXIyS{b5A6A~YIvK{uJ!NNki1MS^~-U{}a#TJY&ckx=Af z@JSKJC=2_XWcg{@inx}0N;ev(4AEh1%f-brF3vB=s;~=z zdp~p7KcKE}c(Q)NdhwLE+k3wI{yW~?z2ko8*^h;X{lNRX_t-|;v3i+)MlZMc5FBm4pB~xfq+DAN$D$xU$avZzE=SF5%(jTq0S@xX9Uq%g5O>v{U5VYTe)bVH+cUCzh) z%xhICfKqjqQfakH#5lT0Z*Psp^^VdiqJyLhHqESCN4Gko>6bKz110P+$`O$zM@JGu zsA?!^oN_oUC1mF61WxftyV4XKCN0qO1G-7LGlzP9L8CT2xqQhdn;V`kmNeG#ez#>x ziKkc3_;kI%{^kv@zkZLudCTt>iBLRa?6}x`!L)_lrxvCKEGifXxUAPVb;vv5R(vuMfP5Z2~tQXUx}YbbhNhg#emZk+f!VRGltT(_;6tMp1$d1 zZW0rd4;bg@`VM0>e$Mz9$y#b0$rqRCUK0yT>*(8#Vl@`cZnp=Om{pRQQZZQVaIF@y zmo8{)X%o67v!=(_ENSK@;d3gvQs0gAp%NXN9t}}SpUhULk*pF-`*C)dYY!;2Qt2U&J#0D6 zK~?QjB}cvDZ|iyUfdT6T!#|pjjuT;hBENAOnb(-xqE2XT>iJOWxv7uH|0)nLrq1c| z2LzK3`sSlco+HWngP6KlW1YpO$Z4s6?A!Cvz^WX^<|=8BbKE#f;|$I|0r#A?ZE>!n z>({K;8y1TmYoKwOzAKM|Hj`MEG?%|Ht?_V*G6Ps?uvTf+vLbvF#-HNVpEE`KlX5A@PeS@|Q-D<_M z(Wp)4Pwmw&} z$R0T4f+>*{y^Qnf&NU@@H|t zGSX2PWhqJ%Q(z7=Ar!_T$=>lWQFJ2e1*$XX+@N$I&R)*s9K_(#HuQ_0r86|v(zg~@ zeQu2vAzdytMq0w+g&JcT6h;halg1z-;fzo(glyYTQlZ1*vSNY5S}==(TJ)SPH>`V4 z3Ina#GWjTI{#=;mz%)sOXo#8F2fPn#W8&`qK)l%z$rxj>MtL%!VjnZU%sA&sT>>>j z=^=YeF0@9mTAj1rT(W%_2vfvqC(F5`jOmzJEgPv*;ioEJAQgYg_bJt>d(4>BN(wi$ zeH^;!iXg7I@8l#Oo|GyoL{+D>ECp4TzhAVj9V-<#Q4t15)IN%s5+O>nmy|426t!@x zIiWcvdDvB|80vIF{!non>KG1nL{?i}bH3^zQ^PP%+-*z_X!x+WbI_9=xN2WMO91oNvm|}5Pf^BV$ zrp}Zh*G#~8iVqN_u22L@7F?K*kr)N>no=NUOU!k#V(I~HVDJ+U!R)(+HNK+C^796}0$ANa@x=tnGQ{eXcjz&A?G^2{RWOL5MltkE4C)-A8vM!<= z%1N4rlrSVgfYk+NJT0&@ral+P+Q}uJ}6XiucqO?9x za2Q=(A?5>gmr_^_Dag=YtLhfj^$V?NcCQlFYJ!j4-ER5OG!s*xkZ_H|_=vLxbY}N( z!@D=%vtBIulb`$)qeb0Jil|~xjTJFW%2*}YLj}d%{XN@ zBH2{DeHIdmH8>+~Gev|_8&e&K%!nD=QW}G66(z~?J=>mQGAmnn(OafP!+U20_0u_g zwuFPm`$F)UIV~B=K<*VfD{|3-QuUrW&o~XeX;>^)bp485zNJH>b41%ruRBqdRzXH> zyN+QXq98u9jLHdmCNz$s@F}BmRo<2?%^{$pLFJ56j%l|iris=X)?H7N6eeZjB#zoY z`TSF!pD)O1U`X(rZ@=Ny_itFchKJQ_rex?l&HnztFaG`C@|jw2`A--8{1^X~|M>NH zynp|ef7bp}uDlvC?{46$1)`zROaI1oXu6U%$KKql@Phz2W@hm;5*nbQIFydHLc~FiW1kxZ&;dSNM5?<{Z0L z^iMaG?C>GsS8MX(B@T@)4-AvWnT}!{JDqraDEynh{yG2YKl}}2QT+UR%XjY%L^Ll} z3*NBd-@eKG?sm^_<`p++4n-mak}6Uw5kwf6EZIBeDKO=kkiX6NG+Kk@t}ei=5ZTe>geGZ7u+oZ^k?%BRa89wYi6IHJ#ooTuXJ%p)6KQ^j67%Ie51Ffp5P1j_GhM!jPO;^gWwpk2CW3$_K;!l!VNdVr24( z-67!G1x?>!+vbGwG)94Sr=Ua+ z&ljJ)pgVg)(}6Rv*_^RluE+$Y@sy=C6+7;fKE=Dp;PDF?Bj zx&`N#FX-)xIcjKE%z5H^o4Ma6noh2B(>5qP!}fuFJn;Jc&*_&FB~1*|miuX7`}Qqv zm{=4;lZp(jjr4Qmgf<=8TC&P8XH=3H8lgzl9#|DYdzLExuXx3B6w@E?mm+!Cb4Vne zF-8+oBx7is79AiA6A!o7*f`L%mLHv6@$~YH_0mzM89N-(plyW5I8K^Zy) zCKYz?-qH3Bg_p%9FscXa;+#1~@^m1F3zT|_?N&5yg;xb1B8S7C5@wuhcye{cu)8OP zkznNBut^rcKFvfX`mUk3GvEL2m*`zXKW_PK*%MNrS$6o8G1~)^N@P4!oQOG)hna&; zY(9F))6Fv;wiD~V<9xMZ(KuN?Wr>+fMpK%WX0;|H&o~_LDdF0NuJ7o&{uJ@&oT~^( zNYPVrpjDb|T4Iibq8X#&ehf^9JYmllXcP%Aq@P)2f(y4;di`wgn;D{L#8XpbHSP}V|9&F3y z^A{AIm?qEN7F>FdWoC1BfpQBVU{NFtXa_ZZPnl9PX>A<<7SIuuoz%B^qF)$Ze)17t zeEuWuzWtuO-4Pnc95aV8vY$Lx)^TxmPT5}*y=R&ehgfKvhNjMUbVXV=ZHKWHJ6mdx zqV#DzF7b%+?yI0rjDn^vLvs@MPNOv{XW~4Gc*`2eU-f~I6UG>HDI(%hNaRW>60H{_ z;~c~Q(>hXtdGKThOV^R3qJ&780)F&_m{G=Ij3JbW%34G(MM+F!lE|B*J6fCL`(E6= zi>ARIvwBmaq*?l|Y$Wbbi;=@xi*ia9J55X1wAdqN9U&zYs5 z2AD7kNhPdAQKGV>%*u$!XkT}G_tWiWF<9IE-bg@$XK62Aym#+)KYq^I>-)ancgj8q z;sVJC`*~u{N$0eR2FEo7-w?(^lFYamc9T%V(XUpxMmw$#+s0CaY&@m*F&pY`5bbNm z7-G)M^GHr3nM@QV8D`1MDRMu}>|!Be=n)#9(hA{9_Mse-&YhJvVjUafgFS}j<` zw_Ht5D(+iNEIW%g!Z?hWyE~fIIp^o+y!*~EO|yo!(8MU^P>tttH8KCt)>}nGbC{ES zH7OM(XO*m}hf1oCcXxKFG$Px;{a6E>R$ms-jI*O&L~ulJmsZ20Jdk65ig=1m^>;>&?wezWl6 z=WB8~VAK=Kv}YV{*zIoVLZazGT`d%J=ZOD~mZ4YN^YcWo%mD;wWU-hh3 zC#<_QA+&mZJ4fRSHciAiGscDMl(^|yF28?Eiix}1J-cCMoF|5PAW7EOxEP$XG|u7F zy;tcHn!b;-o{2F^?@DD+fm&cmIunjD5@XVSaRFZ`w6;Efb51NJF&BM~2iN07t1g;V z4}C-KJM4bTlfe>4$3+jjt8aPx{de5njigxcMEa08Kk3wd^>&8RT{?FZ% ziDKDows>#pHd{)wru18)7{Vwt^A0oJ^Za~6V+Y<2cSypN{J8eeWf6p<1^jjf>vxA{eX}Jl2ikiwHW8IUh6oFafoQm zxmHfudhS>1nl)JM|QVm*YvEmCW7 zQWS14H`w;t~;A*gAvkPu14JQF1m zr64I{jmH_U;7BR#t8VF%Rj1YuNZe?;My^YZ3Jv?pq-k$I4iO?-A!H z&XbK}ToOwql;^B7`#h@AWf~`zX=WTo{Hi-cyu-u<-m!8E`*;t2pezexp0Q)bH4RPE zaC-KXi}Qh-%R8oBM6#vCRJ-OZsYK-1V^asSqBm4qpwK)T+z!b6QUf}N430U9KGuS( zZb)6YRU}GLH<~*nD-XThP7tR9l99u@{($4uS0ow5s^yxO&4;zW)r3GwMH7G1Ok5U9 zP8C0quvrJII&~m2ljeysNAk2_C25~)k)vR5jm3Cdm!GHf>xJM9zF8C66~=gC(jm?i zs|QdKmL-v8U|2k9E>Ik)WR__rF9lg3E+ca&gw>YwZOfCDnKLe5m6vUu*pIcoVP@Ygdm!xL}-g}@#KPU?gsYvnfGzw zS!;OW0{#6$d%9(A*1VbT*u^XIaEWa?W?(9b91X9!XIyMfX+uj0h41f$%d7W{<6Bw` zFV8>VWOYJVuXV|xeth~iX!o*Y;@t(${3-wGFMi6qr!RQ(yKlK4_WX8t$@Or}t#K^7 z9rN8S?Yd{(ctYd2djFQZf6tSZ<%82TpIn@=Jv%#!heh;&G!7%vG!eYR1RZ|t0kgpY zFXjkd7p!WOsJ-pp>SDQiWXeH!v=3cOSdSP*R*mDx)%6txA#|Pool*s4Yip&24u_>C zZDYwyab{c=x{~O+9@jMZBaWz6QB=qHVUn>GBV^kXWcmQg>$-V;JY+c%^U z!okgIYXYDcb#ogzO1{=S#vF01wQxl-H7eR?4(C# zJ-$97nwU7;u^#^wF+UW#jU43ehrS~Y_ebOh;3Qj3MGpx7+K(jqb71PCqaL0}PM7U> zJBB>5p9iL8AxW+^4|-_SD?9z*r+zGU|9;y32fsf|x;EG0^r2>eM=APa7XE;qKM?Vb z!*Q-tul;u$21Nc{f2fOu!>jrGNqgid9GYs1I9w+p^?IlVt7D(k(H(1!gSsgXZ=kDq z!;VkGb>sM?|w~{@^2);lgVEqQS%G|j<-|xQVgKxf|@q)7nI=H=h z&(-^Pe0IL&;`AKJ&^8@E`T5Uq7agUv;2dJK-%juZV{r|Hm7rfSAl8;IhXAi}MzjE85dI6~9x0l9WhO zGR9jxL1%$VxUnv%`_mZO(BVu^PHKvYDYMLKG6})5*{tZ-EAW z$xjh0oUS`s-!krJE^qGm_Wd>MRm;9ue1xI8`y~r5CwxqrUa-Puy{Z)+dLp#WGmaDc zVUK_DR83s+L#xj1-5uY)dCQO5fbDzmUa{**9kTj4sNIV4h|qDUj;KgA(C*yv{*Kd= zRtMZRFl58&=7hiZ%U|&9^qhB>iJQTbr%6qMEI1FVu4CK}?8lk&(+jL^Kw1p2e|wGD z-7(z0=c}(@^KLKfa?kRA{cCPlnLq#jSM>QgfAaVKAr~i4IX}JN{oGcO~~E7Hx0t(bOG@t%eh6Zi8qVgjKl zm_=DJ&Umu7DgbEl-=q7mUI)4sb&pmb`q@)TPED_%CR-)6Sc0oYzr(ZA=@ZcVKx{TJ zPFe$G3a%*_Gb1(~sW@etkd&~}YiC`4jK)uh3A(H{swpL7kfEp8l&OQI2>v?+8^YYa>|L8yak1%=S%P;;<@;8~rIa-wDBSuB)oCV3XPQ+9bkx|@X z$`O|qa!!=$DtEqS(qblb0oz#S7?CCFGo)+iY(1n`glrNI3;5tM(&8I63S>!2&@fVG z37yA_#JX;>lx!5YYAsmDQr+Em_dDkMJK{J%N`z3@@9y~a+uyN$3DFeV*3kD2)1;z_ zRN}+g*$QL(BT>fJK2&3!E<`0T&Li*Nzv1@w8v2H5Ni0hw8G)RuKq)Mel(?1_cGJS` z%?HBxR)7>q4C6?f23(pkF3^Og z($p$7*9a0k1&=X3*7x}JR?T@u@r&2HiRo&`VJ@g2Kq;oYX4_aFcOAOJ~3 zK~%dp?01SOkF%xVx!ot`((>77zhL|FM?7hT)vDos+7ol+{p+{HWltBLkYt55x>Uct ze8c5X*zE`8?j3YiFOIYYb3*^*W6oZFN)9Xba|G*1(r`J#Z@=F$70-Tx6diLaENNi4 z{5!tg|1Dzgh%quOd$KpoOQdm4t-pg}v);$8qjiqp8ZZGPfh`1&)znioSt!he!=c;=KlVU zC#!SLw`+2;%;Q4N5t(*eyn4m?$qT;u^v7J^T+*~_#8{#=7~|PykCDWkXd*rf?C%G% zv$(#+whbYyFj=wojbA9m5K9kX#TX{$G!m>OS=brNt!+tWVQX7jX=rTEm?PFl+BPza zWiB22#jqa+R_%=U!eAP5=rHr7H6=wW*?j1Da(bbaLv?W++J?q^mXxq2Y1pFJBP}ch zPofZfCAL^S?99WSaTqw+p77)>@T=c^$2VX8Th6u}Kl{a+zG88gX{qEGdpvI(e6k18 zutuE~;h7dnnhDhz3{pTFSm)@$D1-a{%ZF>y8Qd2@Hi(|JJ(#3^wX_e8IhLsv4y zg;F&6Vw_f$fL1Dq9L|{4yjZl#FpH3qt7mPc5cyi;mUAMfNXd=%+K*$c^-?2bDw$#m z-qloa-7Ptz2mg|#rtCD-(%7I2{$(LYFd<+|(S&$Pl$>$i5Lyq`G0qEfT=2oFkW+F! zQ&kdIQA&)dpL?9c_@E2TS?{l`g)Yw zg-R(%%DA9H;rZE?VZP_)?mbQ0uYO}-lLqH{iiz0J>Yk%fvr?29ZyhCNVqDa8(5%>xBeyp@ zOzUxtSKPZ8Y(-o&T|?-)igon_6EMnv)g7k`b)TW;w?zuM_PsZ4V7)oz{OJpxJbTF# z+mg%~^ZQHfosEgc2a0ptPBU!QbX}+B?Rt&ms1=r}Do(cUE`E5%*2TE)Dj(&pDn?jM z^D4aBN;%c!qpJzfTG1z(h|}}FRZ`>cZCWf=)3jBvvXxe0>vf`Hx>jxGiXAUiaww*f z7d)6)j>JQ)C_Drq48}PPVilbYixsjhTInZ;%8{fLNeYIj##UpvP8m|-i7T9TaIs!< z*KL`m8Iw9S7?qKdMr_gdDpM;^(d#`lmbUkVw#B&~=fIQ^jKf*4=jx&rCP|dGV9^BP z0j=+x!+71RAE|^!SZqOTq6q=PGt4kzS)H8llaK#^pFG==r-em4^E_ZBLy0Od1g%^} z49+`BO1giyx*^ZYOf*1Psi2t*P8yIvEMCvx*05Uj^u4B>Z4Jy($p+;>x~kvu!I&(S zHi%QUl;kQaS$n)61Y_BBE7o04BOQHcktIVCPSz*C zP@_-A6ud83?{GGdW2U5}J@R5nb77wfA%x@S%hoWL4}p1^S!SgeX)@Iu4La1bmPw)s zTW1mL)F6~Ik`1mntkE9j5VYE*w*_Ydp*ZgI#O*XQk2B*kvTqY}ayaLS%Z#L%U^8Ja zbiuM-t?Ak|%VHRJnQ>V#A=8W*l_)K~Y3K+nd1Q(+sbo^c+Yn2Xvr|z!Pfmm9M21`w;yLEd^u$x0aYTX?jvqvIz*IOxVaUQP)x$4j88%~hUJjS zs#=IM5R82g?5hx*MJUc-y~Dad+nx|YOVf3f#u0;~SjRL?TwY%@?}cSQkwZ`Ng?BeM zkQX++#ho;qKYc~_@)N?@Ioq={8t1sVzGHfQ$yZ-~#n=DhZwP5(yACv~miA=B>BWW* zUY%h=&#=2?-rrKueliprNF<8O z8d^<}k}Zu5tonxT?7UWE8QQL8({yy=S<=EXPIXU`z!st;4f#+h36QD}p(!sCt7`^pLAl!+;m z3+%^<`~5whalx!wRZm-o$-=TM>a@(lGHW7m8+!V%=AO3jNl8R$2nY%eL z=FCtce%G?wYiFdmjEy^x$m#Z!i_^fTA3x*O`Ia%>Gv~;ZB5u9n^?v5{t}rf%aTgfx zS7bRSg$XN(z|1xYVP4q9nM_A+T4*e`gZup*LlWlfh`A8jo@kR^ym`T8!I!8mWOONO zNcwPd`Y;fqT{gL*@vU<>A8@|Gx&|+XdETqJqYHFx&w90H+pk&oJ9d7;h(nUltpojf zLlno`>mBdk->}5O>@3q%@HnaZ8b&ucnh%a2FJAU)#YnJ^rb}n4MUHIVXOWMAJ zRS(??TN3L{&)Hc=_L<9f9jmmk=~uKbKj!^6??^T>+J>)g?zqc^ITrfX(F9AkZurfY zzvj!=-!e~^^lstl(+$s_pEG;QQWS@Gdwv{ji6FnXN;AEQN87xJHG(EC;9X!?8+JGJ2rOIWwk(>-{~q_jf!A zEyj8Jpj~)Y>I20*s->{@h$+lSMcLQI-4aPwXhMf69#;}Ygs~(fF0_DY8`fP*+h;;r zkfmTHQ}9Q;raVr?KSr%tM>daW0Y`ICsRp9zpsfjvhh%mI)ILtNA94F~#2ir%g4%^v zAEZ^FD*50@d*seMm<1?x_%p{YcFXT~96drRsZZhP!aT_R#gsaP*FnxYjQ5lbbBZ_{ za3SFHf*8?W%j%bK6|4JLtUnwCh2J~8iySdf{J@A{eqdNIhc4mA;{T&xI+)rYA_NZj zdHMg&_5c5g_2vhT2swHNAATJGeLO-~JredYBpY!Bd=-$gQ8civ*t}R0h^48+pQ#8Y z8#;UmNNC6u?L0zLnW^XkPMQN5M4wT?^-NW;^as)aK8@z&oYMtdA*o47)R|mr!p>H^rHFQzulk;*Z78{;E#L?&6_;e3 zqiZ~p*R*bhvn|6uQi?Fmnfv=a(=cFtVBPktx*jp8qf{?&tkvbAb~cw|vi4YQll2C8 zAi&sT1h*QDEBacE9TsoE5_Op%wUR_ieO5)pTc$ko{`Q);*BxK&Jm0?CVa9iyTwSxd z8TtNhrgW$58p~}KE=)s^KzIIxWl8+{SAR|Zb%U|6yW8>c%Tv}R(c8q!4`1^6yXU-# zGrzvNX4ihh+xKr+opo&2Jug1~5g-5br!)vps^}oVes{}#$^_qWzrR&Xc585**V5Uf z_p_WJM@NYc8v~6GY_}Vlrm3e=%Q((l-P~e*%jxMEJ~T{O-Mz*(dViUrU$5D&wcC6d zN0toEd3~*Mh^fyFV<@sbx=syCT$tvGaoBS|4!nMU#mYpk-o4>wF$`f%@C~1Q^f8~k z`hd4rh3~Es%djKw8}5f&YysbOELn)D;Elslz%0!B!uM~#=esv=7>1E0YZu?i>4vkD zE&t|Mzvbp{694!={6}1@d)ie8w(#V%N8-f4zWFVAjP&9-v4%0v_?&4Ygh4oK*Z7TN zTMCiPcl$l}CU94Je*W=?{OQkM@cc&~QvT+1{>lIL7s&M+x+PNbin%$(Kf55zdt~%P zc8WPmFl7U_$CQO7W@2>e3eTCbWKA?Ig`5piv~#R)TUL#x>Qi12uU+M)=s_nJP4juD zrq-NQ0L|LSh&Ptq7@1SX6tBww=O`(XTtTv~)~)d*7qF%!E|8X)B5M}u5EH;ULU0rU zvMi7-)&`7JbF3)l-5)?{#VfHxbSa6kIFbtXS?g|WHGoDbI_n=Wr^aA>gLpX-b4tmY z4thjP5$%(zlqd5;DomyAR0InWs~{lT0@k$zV>BtA7wwCeUehI_eTi8VVHdonR&vGmyT)ThO*_|j zcg)K|$!hlKyi!?qch}rpHALUioov~zHb_jQaZ<6j@$AP1i}q-itOlya>B9L?(~!k5 z&Wl#}7-5WwoBhPOggvYEmff%? z=8=E@PyUowAAQJw|KI(${F}e}YgTQcHxQ%S>yvpWKKVL7mYnWG4F-1$eWPM?$8 zo~3EYF~U-a#q#;}#9zG`d7t2K-`;S&6RdA(8F29{mU7RnQT!rZhYOBXQ*iZMrSp)! z=1zoQOzmIRp3udBxWjC1A9dE(8KkI5t3s7^Y8F!s+y-nU<&4jsU|Ndx*ibMo(~@XR zCdiD8YOa@^_C#NL!|a723vtevsOHInr8EuA=@#3xYSKtEdAP-tfowA3Ec1b`WW6p$ zB@yGqHhC_*qBPU8htMN9=A~iW4?H_DT%20IZxXjV$5SapBKvklzd6VFo)jIIL*(19 z-_gJREf=pH(F=C#$i-2XnUA(lkdrN+zx|S7mw39}u(~+oK0BuUTYSFfyxFmO@rs}Q z@gH$N&&2VLXd)&A*6YAJt?=1s3MVB-Ss3TYXbr2PWHswOz6sd55SN*Trq+w7@a9CX z*~S<;@94c_FDleHQ~S16S|qp@ar&N`YHSq&lL}r6Er#qIjTIVeSvL*0_ob5JD%GQ6 zqth4}?(bNy1Kz7pK~69)+GA-9mZIj@k|{YejT5D0+TdtT&-nP$hQIiW|D9j|_L_?q zAM*4SSZ68*|4}VU?XwkI31+yuKT^6#u|mlS$(hbMo}6r1cMYW^a?!-|^*r%rf6uF7 z&p0K9VPOahe!If>Krx2pF!%aE2!XzB@y?SDLX>$Rgh^4OLX^y$)WDSGP&suV5*4ho zgeD+HSaKwm#F8UPGR12WJ-CJtG=b%<#~Y{bYcV)6bfy8@qP7k2;_(PBE9cajOxv^= z*C@@<2i*gR()Jv3Jf9wr#E06*!{@pbON_!~RgfKriQDUYz|k}gYYV9;*~*Gk>Qz$1 zt&eK-DVj`n?h#Q2o{WX5xLlNQ#R$7eh==(Lc>mY1+ zcOPl}!ushe{5l}HVbjCe*_N}-2^hnYB6G~N;#jXvDZ|Kq7;wQ8Y@qWEt@B4=Qlc(@ z*RAH9r#aFcw4Rc*b2l_}F3>t~-mu!9kW-=SJaM!H8d|Z~lrdSb#^dYP$fbaFxTd98 zt-;)GPkHv@175xSkdth9dtJD^y5jogrW$6Il-6|GEv%s$MNB(qF}3#8)aURa^>!GG z%3+sc^(u_C z|CSd;0*k28D?3BRk{3T-0#sM7wXJw*Tl3)|i9-rgR@sfm}od zg#%sBR0`EW3@fD&=Y`$A$r6~dl}yJ&-m$#qu>)>|6e)D)^E7sGCx+3yCW z2PFZkB32foNp~`d!sIf=M!XAnwCCAa!FxS_Ny&I?@Sd7*g(mB@d>}+>%ax&Rw65Wh z%FZ!SvI=T($q0h?4b}x+&w8fa=g`FNz6YG}wEcBW#F^c4Mj)#!9EE7TpfGY)69_Mq`CUAG3?FpQDm ze!%*ONd=j2=o*i+9Xal`@?hDqpYIrl9cdT|HsQ^LGlFrQl4If><~&TTCHZpR%5Ul0I9l$)cSzztX54`lSZioUSoB4OK8D1h-;XO zCCzGH%r+8DMCOUPbF5c^^XKQ7X2<32tW{Ormd%TgIs3_HoSeU6)1C12>MT&-0X5BvK=;ICA3n#Dy&Dy3FGSd0sZDE+cH z4XavmrI^dHt~@kKVU=8~8mie!X$gYOYG|>?s#MWkh%LH@HHTVdPnKMNMor%vks}ee zhz7hww2Bu8Wsc;i<}=j}JxyqF4cK5cR4sVq%oF1Hk|FfdN%zQfttw;tOCOlWAzDQD&-{?Gr}|47qV#0e%i#K6t3 zoNh1hv=q}TqSV%4c^2k5F;Cj%l$H!7QB1CQg#!XXkfhx?rYL6H1$80kQphN_7N-v^ z?F*FJIazU<2fTHusXE(WOSRn`gt20(JIG>wauVjYc3{%XSLQkGFMWUO?vS(qBaZn|T$6{YtOG=XEXreCmnQyO#Reu)gFFvvol8j@IbEyjV6XHI>?vrWs#&nh-B z2-m|crU`7%PPo0@A!FwL^(B|Dca+ky+FY<3c8I;gwJo`{?1qKyDo}#O`i9l|91|>4 z4iwXInROU-ZHM><*LuD0I>Qtv6%cDiUESW=4IjJ*35aR1W~pMTV%jsgI_e@ZW_2W% z1(PkoH*`%;-v!QBE9T8iYaG3WO=}6>vtJVLZ*JM`?y*?rxv+TQ$+qYD>6(k}hGw&Y z#&Ngoxw?AC<>e*B1tvq@-?I!>5gD5mni5c{f=dS$1h)fz(=2agmi(sW#HSlU*qDyFMjeFXCHikJvoDlwwq&Ox7)ES z3)WeB-_ozvjMId3u^zBQ#Y3-Meyt05WAHe|;8&sfFpJnM$05ell#(PtQgtSZVOSEO zEv$mq-nx=&M-x@F0&&!Iyj9WHz?>7~vXGlrjXCzv9jk;4{cV6;oyGvPKufu^9nx4Xhna8gXY8nsN-OG+dkAyOQCL>tS%+Ke z>hOg;5`+(3NDqSW;m;5K+bDiq+ybSE(+8BD_6*kkU2zptR*f*q&p8rB4g(=a2k8&}?sDu}dL+yndBlx> z-R`{L>Y+#LJn4b@?2AAd{c z5y`+DyZ0W@$Ws6R2V;lgnvZS*isjfLrM`lSAWcbyk&3d+IqPt2wZ7!|b6Ru6TOAA> zkJ0(w8$~Qv(!Ys`7I^jI1?TG%t}bsF_5ptaItOEdwL~J{ynfAo9$Bvg;1ng6BF;hM9nlqa3f5$&7>F^kxCqkIG#<&O_U7rK zFE3z?#~NR;ixJ7C_7JHE*7`sQN}WK!drgyw1YFbL?81rf=-QrMPdgw3Wl6+@rZuM# zzge?sTQ;jTE@)q)SagYy;tz0M#ys?J$wcurR>pM3fmAO8H0$+l;>xrG9;2&qJn z#HQ~FHgI?Ip54uRF19amERbhxNgTGpVmw(ql0DWqwwnz;coi(IrzFcbEm#|lbCo?V z>k9k>RHwhv zeGHJT$JtD-^%bcaWu!(Jb6qSv5LV>4OgKJ&56<1=Gwc|D)B~T0E|pWFc!Mck&m)z^ zT1wVg!&!V2Fcp_5xnSJE;jDu6!K`TNpYeEEXb$GPQWI7Mq~ZdN);md23O-{KKDp$m#-0C|DAvB)ro_!#EB!rqiFVI{0GBq?~GX4m$6OAzMq%rOv_nIw@AZ zAUQ)#4$QG|eZObZwD`8eWKAF^iARTbjF3}JEZTsTf~n$=uQQyfbApt7Ot0#K!`6%o zNX|_2L@rv{Q;K#1I%`?RnYZuX^L3Lsjly|+#-{i5ZKwC7IUOsLyobgGa7Mdzjn(B- zeKN;VxEppXal$ta=Ne2Cz-diKE|E+jVv$rxX=a!PE^qHyO2W1s=g(i@1I$b0{>@u9 z+f&-s^YY~jo__F>Wr^5q$@2_lVRLq&?tj}>L}WD-XG;)MeXfEDmRPjs6DI@{h{h4E z(a%*%BvV-8%znRPaEaarjO{UDi?=IcR{GhJ4Jj!mbr~mi*H`T3#I~EUn>D9r7o1-_ z=XLW1Z5dceCJiGy40EXGWfyAyvp%F9Yh-&ydwz=RJEq#9u1iP7F29)#|MI){{O0nW z_v66T(lgDG&2-0;e8st$Iq?pg^&Gs!g)9YEQOyM*JA?HFkwri2$`n3AGP2a_Ed*_o;XDnwxm{n8EPHUft+QJbT=yYQ@qno@vH)Cw`ys+pyI!%L1CXF@5h0=MQ4eW zGpR8&K47g=f=D$cI9tgy2kPAcC|9fI=6PhEC#Gq_T77>WD0%(|&(5xG=+`~2zD8mV zQHoZ_DH027H2IW7a5k!#Y;^uU>DQcXPxSuj1A-%^1ta?Y*_w1}T2Eie0|g=FNNZMF zadb$$numU0tR~nF2tNb5yj+x+k%~o3Q@c0^f@>>9!(hFEq{JuNDWS{P)+}Qz&Ur$k z=Y^DYCU97Sv8=pj@sY+_Hhs^!U+K?2UNYUjBj>DD1g53wPI&d~CH?6s=NHfU@WY?; z>Bm1MyNnz@A64m%Z-+KDhignY|2T$8r`t62kKhidVy-7II5yNh*3UkvubMx++)yW<| zM(mQcf7TX4$gEn+YPDte{yV<-{B!QVea$LGW(N|$Hy$4iq3D9yY4c7sHP!BgA_s5a zaVC)}5DS6^^aLrn){!30-G_aN3O%KWhL}<&Es0VJ)4cHh-6hjF)zFgWf6JpeP?z?l zrthT^2JJ&|z}RZU)cw^F>3iIbs6ktbo~iX*D98D(h*AVh5nNW$*w<%^FG9&`zE_jx zql!Oy*nK$bn3u?~-!ty^G@GrGH?0a=!F!sg7QEvv>+%Ea}}6<5tIR|QIrj7w%eEsRs|CpDPMiv^X8k8o7X#j`#1js|LTAKzxnXwIsg36 z|2Zd5dj9MG=Ff296b4JYzbDQMX*%oYP@Wm6OnqxxM zN# zN`ot8Q)-y4o@K342aYukSu4AaHLI0&;~te~);Jc2BGqE*K0WDP%sY?s)y!Qo*2PC5 zr^h?P<8t_z5Z6RB#H=%#6pJnGF^nWuzqYZeyd3^b=j@}ZIMvxCXQlk)sQWE*AnCap zxYK)P@tK$xtXN8!81^IMG!vSRviHnR=t7H=hB!OsB{Rj5Aq^~f!Ft2G4-mB0*_DNq zM8)yq5YyIOv<8Rr;aOn+)}tH^Gh&o9w!|95sXCDJIulzZP3IJ`B5XG&T%4S+-E6Q9=6R}VeKjb} zX=Yd!#<=L7AHk$bt*WG4HEH?>N|uFa4N;(!N)Qr@5np+Qj$j?Zg+rk(-dg>=fn^zS z){$+)VJn)dIblf}Y<6O3YH+UPOvsj|?J&vGhK{!F@L&AhciioE6lb`(y=Pb!hTX_7 zfBuL3;-?Lpm8IzeO_xY{&$o9seErRLyng+TtM^w&JcIR$RLnY?*B^+7KGS+vYJI1J zQqh~`L8Y-a9S1Uu(R7e0jHO_`!8JjLgZW+`p3YG!E(v33k|kJ!l?9fW#)>ZeB>5El`W)sW?uA-@!qp;8=P3yzQL40*8p)M&yby>dBkL_^N1wwr-hr{#QiW6lQ5cz zMMi9yS)0sHUp?WEKY7Nh)0RyrtQrBcU^+{)Ui0qi|6%J*dnCEiEWPKhX69Qgxo2`K zvexdNnw}mB12sm_m-)96FwifMAhZBMm{xa>dSexfEHX(ZbBo1Sv%BiUd)*_lMjwdG zB$?q6?q+uHea|`1xn{SXc>VS*Z(m;#M~`<4rp{vf1*V%rf}9Q7X`VlM%K1kxxK=%} zSmt_yt`g`t)Q-XQ81KPWicU;*V$2y81eH|V15yqIvdK41NHs>`jK*2{y>7!+YrrXo zFU@V41#zKUzot`))zFbwbLLgic};H;B_G)AWq1~*NxB`aLl@258Xhg@JUCl0%$Mxy z$m{o4+^lbyQX+>+RUT|lI3xzGSh_;l2I818F%s4{)ZQ?Z%7h`M%*uJw&9DBPi>IGpFP>8Jo9Hs-Cbz<3d^xeR#-H7Wp;1nt=N>=Dj9%`rL^y#3-32W5y6vukpdwuk@ z>r>W}dK^5cyZ*cnkol)St9yy?mdw4UaMyOl*Lza9Iws3cNYHvh^6PqsXs0C)>OHK{ z@v(XT3m(6hqCw-DF-gwgI*HLo8%r_rdFX1eIO#jAlDfz=*Q~ZjIql>!KN$p$v#sNU z`)Hi1oP_YBSK?G#qVB}{`&oQ-vXT5Lv7iYWoC={%UH7N||9FzUr%InboBH1-^0_0h zp;i5`o9~F8YJB^86l;qJ^yM^*me9LDpt?wtfZY>mX2y;EZJm2u1q15Lu97lY(B7B zESU`hCQcYDL-b>^pZn;uQw4*j)FW*}hK}lp4=d&Fv$_L>odkYCAtk($p-ok$5e5vt z_gq|F^2Haw;6e9{r;i45dcw?G44(PLCBylWAHMsBG+*)X;u-I54t)E~HyGRTXTSNJ z%cr04emye$bWPv&eEa@AyO-ZHRZTaE_&2kf&!2yUdGwUGKYWJ`3ANc!>O_xW90O5R zu!_DLpei=ETc$B#wP!v%Lun_^;ceMrN=N5<`pr93FUh>J-?4r7mSJXKu)O){8@~PHAGp2R zaoA>ROn9F$ox=2n|M>6!jQ{>W{Fbu^pJEngOnIW}NG)4x+%p>r=a-d-A%PC8Hxq|R zvq=LnD~2+0t^)7V8*Vr6Nt*+9(yT6@@}P4(d-jZee#zy$$CD_o;Fdjp?nvXB-Fi>X zk;Td}3>~&syeaHc5MmGoujG&)L!qRRkdj~+E2Jt1o>ZC`V+2E3$BDxjNFiap$CPH| ztBM?>938ZX6e(AVm7%Ou-f&vKsgi}??#Fh}kX}LBno=^9Sc$cg5o}^@_tud(C_$*Z z^b)l;vG_QEZGCh!b5uk6YSSLi`PgBWWeBMG086R0#TwK4{N#`=&SXjDX$46OHANvq zos)5`mX-{ZfqT`8R4TdT(}9dyNmXbbjv}4d6b7q!b~)qGlLfPLMhYbci4a8BgcVA2ZF-8a17Z|SR; zvTk>4QkuwzD;}*6JeelE(+r&;+~Xwsw{94)t`~oX7UY{TTFkp3zb}=|c1uoy+0auG zgqTPv3*xn8VuP$$1S~8^?v;pfJSi_-Kc2$v_W1k}5e*Z1T1pI2r;@LUN;Q#Wn(pV*+qQYQF1~P_{9V$mUJ)xPXIL6}S8PkM| zl8wM5bb)Fy%HTYBt1(pw9KM}dQWY{?ZNw`X#I+vlhV-mCQeq}&jaGfDk^^Fq;Yl&5 zq#=UNMy~yeR6x{13VU)j)J}8QZMnI=V&>*@c1i)O99k8&+YLXBBM;}Y_bViV(sEzN ziCPlV6q!QCbsg3AtjCU<+;P?qoY}(ma7%AGRM-+WZ_s8%XB`*kb3Xg{5!s4)E9-R~I3k(e{a zS@z?^c6Y#e&)I6pgXN0lJdv)h*=!>o$K%TjFpk;mhTE=VWi^ZW3~L-tb$B=6ki-(& zkc*~-14d`*cC@C6jT&bio$biV0g=PBqf|lshH=N2zx$eR{_r)=ADnT!S+m~n>ARkb z%L`(MW(*4@N0E+hMF{jEUV|`XggZOhaL2WY0dBuYbgMSdHoBpIk0n&c z;u@(!=x3TzN8*%7QIh)BYK&E~=B9`#wwXEF-)#qS#T}yE8H=qE4I;Q2gHg~~jhoH5 zi4*U(H)N~Oen$2K$~l$~A9D8SF`coTnZnZtmXBTx{P=E5+}@%Ibc=Hq=PPiINrO_J zvu?q~!m>QK{FoIluSZJhu&$6&U_S4e4L#dkrsP0v#GGn9>sVRGiBWr2z2ba6@ZiA% zj2!?NtYSHIES$rlFg0Naq*4gEG#oK($BD0B{=iN8SAP8Q9lQNV3bGcioae!Og-?bz z*K1zCf6M!;_lzN6j72#k;CZdl&NL(KsppTHg;*h(RuMl+`}8R46i5*9Rj5X~rP_tG znq?hKQJ#G%lVT)JfiOks%g%}Ic29~3*{F%Msx-6Yu_B^Xq?OZ%L$!8!d9B>&tE!ZC zj!{SVOIw+gJd3JsL4np*Tw53YY2C?feNd9AWS1Er#oTp81ztbulmkY zl@p=g$ho>xid+n-$n$HeunQymoawrbNh_jpbVJYbY(X-fX$<6jp^gzkL>HJ_&(qZf z7Z(>465G8zCu7W{s?f%iBiQe;2wsKzqIkyKK94n+)(Lkieh8pT4h7?yOi zj&BY-{_gMoiNF1uzhOa--Fb$PdHLNd{+IvxKl9@czvDmskN*kJIk&sl46_cW9WiE< ztvIWxD#<+UG|j76YegJVuA$0v3xBj`XyYi#5^}}0=a;gEqTBVSw9NbmPc=Ph8f8|I zG*~Cb;Vfr(2`DH9R?FjPSHX1Ra<{=1IaKN+_SD3b|H! zA=6c>Th)YAPE^^*QEEg}ailhXsHZHNYWot23EoYIkJa3A?kE^nsm92ey;Z`A7#;vi zEQ-l!>e*5>f|Tf$!K2BC!2Wh5j0%6bA}7N(RBpzR-c0n}z~#k?7Y`rs(Ss-KZ};r? ziM^Rvp9OX$Gg?K!khCK=LAdkam16EXJet&~H6;Z~3Z#~>*X{RJudx0C=Q1hp#Te4a zzCuPa?HuPQ)gE&RLQd5RNElsFzK~-k21TyTgbG5pFha1%EjZ_lkg|%B=UPaL!&K=s zxTJ|8vp*zkHP}{#SydsHENhz9qzW-)O1xL0DU%&i6)iVcshV}lw%~vS_)@9FQL!I0 z+r!9VlCN2eqD+;U^7Nf!IiGW}I_Kf}C8|~q_0@k37 z!Rdy=*S4+WqBm#bz9E?+bv-Fc;L23dsD;)ZvH$L`|0lL#%hh4StJ@v__|IQ)i0_CwpvZjp z_AR5CbGfVZB>wK@cYOQJAK6~rayTNHFl4QmN;zTZOJjuS=G=*GBh#z>-OZ%$yNVh% z$VgX#b%I~WMTU6V2u8#esP|ANN@=XMSPM=UOpWL~p{p1GO64wzrxLNW2bGeItD*=V zvUR7`1TiZ))D$Dz3@u_w%7TJRDH1~<#z55yqqVp&+c5Fy#v*5O>xW4(5kkP>P~M<) z2Y8$URRc9?N|HfGqA*2GjEVQ_1FzR3H`_!=kkf`dt-0`(7ppUV`^9s9`Qn_R&-8<$ z^NNc{4;gCZ58uDy?N8Udes#^u>uV0yGj}t2Ae4x;n%OXr4ihyS=JSqUe)cJU^&kHS zejF8FzrI3^x41fzrb;)=uq@=W(3zAEVpyo7I8Y6gD2s>pvJg6hU{Pv;90XI>4vL8+ z2x>CA2I@3{F4zjXlMR$Ml@t%;a=@v=_Hf|sbwt-dp9`b&q;Zm-qY9ld%zekja>>PV z#n5*sqZp^m^?qV~bKAN=EM{1uyCvgc!deaez*r17H#^?G+p%;ne)EK zbRE|lgY|dk zuY#`3yN<N8s#Nl0%;V8PP z_O*2rShLmb^_ytagn6TMOIS55P)i2hx$*KTy|0djlTtD5fML*7t%)&8TF)9(PGVA^ zqKr8S^`?o=^}`r6x;8YD;s`{#t2e0szl8V?@cPF?jcQnLO+&Dq&_MUPcpmM*yQ@(cGuUt zV_ZFbe;>YvQtHmNqVDvfW7l52he5yN)*buq)jghpy7wdLQ|jP^wYpd4Cj*X3HOI7$ zRBs3lteiI7THWyn?kWW&)n}B(Y0aa{3x4s%=lpP6*vEukEX8_LrN0klFt+1tc_zKg zF@fq%Ly!~dsNQvkqcyKKulSeWf62{e!}4qpp&Bv3Ijx(ar@7!8X$oy92#z@>Nsc(H zvD%6wSZj=R#MCNaw5Dj?(sy#uH^ySE!N!7jp4|8JUSX9ZOq!IV>;<%>o^p}S%7(>Y zOocH-aj8=zVL#01x}IEubg)(34pyy$Pq$~~sir}Mi#vnJJ%ffm-9Rm=bSl$n__6i` zh!Nw&7^Is6{l(*F{QC2s^NUY^MtS@Nk3RhvZD2Oc*{*k}oLS9#K3mR-Q(=Djlz;f$ zAGmz|9iLu)!e9N_pJUI@`P-L2ar^S031!dk*Kc|KW=&T;Oe59>vh%3bLk_oFf}2sC z#^{Kxg(+5|sW|OqXqpgw5}GMwj2-Cw9AgGCqP0YZ$`iQ+AYpT5ZaNmOhj1-ojZ&<4 z2WG~wIA1WEbp+FM(5j_7!S;rnGIf*54MA?JgDB~WViMV8ls1$iB8k^dx{TWzVU)pX zOB^$HoOp71$@7nY#^uAusIyD-)oY%dEh+CaIw+p3=1gVcu)pH=N>Es3zh!G+cbL;{ zdS3qUnw#y0G*kSSZ@%V-AAe%I9~raMBE9_a3d;{X`>X$sk3ai@)wAcAesQXH*lo5P zHaltzD zSh>oi19}*kuLjaSF6?3rT&aTqQM}kNa;$DGF^p-VKeY;V4yv z!JHB5j+K;SF_xyw+$r|0!?ct;liIQ8S|b=XZItZ&k5w@UAxnAN3~Tk&cd3*)r5KLS zEm`-Z@AOz#bHu-iL5foajk;eOau=g&eQKut*?_ZPGPx#Fj^t8=6{SU3JX`eGUQ?%qoW*xwt+>lEmZGg(L$Q)03~q^1?yjP!^#Ru-;_@_WWYKqY zUi13xHOB25F(^|)U^*PQ-R|f$EFY}!U5B*_vJ5BvtYbDCcyqHMr5#3BW?pl?7)1Cg z;Hp8zOid%JrRU>Mp7F=;UNgQmbXt-Yxr!OvD?_SA`lCyeGOQw5Nuem!i!rJ1=mx1F z$jOp(VLVcQ;to_HJI`34xQeqA{jw)I&DCLIj^Qw$GtJ*{^X^}H_s###eshIY3p%gp z^2lv=?6V|Bl@8Pr!Ox&SBXvvWvpMtmiaIO^+DU(5yRULBY%A<5bj2~xg<059_wSj7 z8ww*jH*s8X8ra?K@ftS1C9ahc6tBsN&R9uZAgF7>6-rV)kr=hYYDdlbL<35tGNpK` zkJ7oJKb2J2nO0$^j|0mhMk6sQw{rx?{f%y+oI*8Xz^kQ_RVL)XZrqVdX68*ZU?9nz zA_F|tu>7X=jh2+D#&dPE=F6|X=E0=Uiwp2Q+BgcZ8z;8=cch`P-|Yz{QzAIyAr@+= zSO)T>NkLKdjLE|5$)Vo|F3v5cXpHSPNhJ*c03ZNKL_t*W-IDq2jBYsR-DX3I0qrWY z^BETp9+2&VKfL^j$N%p?ad|fI(bFgV{=4^l|Hs!j6uon7i&Tz${P-cCfBG@g7+6n% z?{2Pnv%Z!yiZOWSSe-eR&axUj&R9y!7=`o$cYIlzP)A~_snJsVl2FU_nt9EiYZ)g*A(R$T;#j& ze&FlxzTx8OQ~vrtKEziU2-412v(>2YNW4QiH8dZWaQtLw?&Zm5JP z@2OFSR29Vt#y!MDDVmw@Fk0apm`<@Lk#f85R29>q)&$j2_mXb&Xl8)%73;z3!c5D6 z*J(veiS=%W?-sZ=pP0gq+YM~S%Iz-D50~`)oW-JNIGZ!f&#AFcw>zo{ym|W#(FT5a zJM!+QTY6Q23A0!+#<#qGcY`%O=gSLz_Q@wadi;dJzhk>k9L5RNTe_um!mC=yC1RXG z8&BQsPa@B;W87$icb=~6u&zgyO79fj`_pVNhm6h^uPiE7YEqPHFn*w4E;+==``b0U zF`}c;$~t2kiK)>h4mnaA(zq7c*XiS2;AomWp1W0*fqE$?<3ufrk`p=R zQ(tV3kr*Q>NQF&q0j7{8R8xy!VvnZJDawop)dCQWU{{LNs~zXHMYXwX5d^euqCHj1 zo>Y~#Z@mi@9DAAbiQZ*oUW=SVRjw2(*Jy2Xo}%sqaOtlWVPB|;r8Q_ApB+cjkIZC3 zh-^1odfyR3ASL-H#EHWJa;lAJrO>sUzB8$IszQS@GQ)P(;8eo|lL_OFakn9cJtYNd zP83~EbrVX7F`(;vO7&uZH%5l~S~1t2tPE4qa@H{u_`)!BtX2d4#Y61inRPQ#E!^zZ zT;E<3rhqDq5^$tiYQ^n-6s`zkJ2BU;UDgK6$_= zpS|Gn>@kW$&LX5zRQoK;DLJECx?XAyz#7*Aq*W>yG<2OqF|>V#>}kBJVxCbNqaDgw zw9zbeM=b@bDyq#Kjc=*$oYB>{qf|@T8U?m8>wA*xVJ~~?9E)_@Yq@5M&RDM{1PG|9 z)m4N*4zSzoiEU1vOCpyEa>Sr1+OZoWX}9MPMslqjf*6acQWyuelvZ_>DJfup*)X81 z2+39nEv>|WF&fud`p#plZO@MYF=DdpBW#OSxhnUpwqTWMq+Z#NmPXQ0RtfgH{k>X| zuI@JC%T(EuR86OCo2|M=sE~7HY(qh-B!O(Ps3Nm~njyu6(&bd=kh9D#a;s8P^+>g? zja+h9&!)7(c49n8S-QQoc35MX5`?T!#i6Q4XGLzMoHA={8kH82&^HRTHntH+3`%FT z&8RMuToD1?7_6~aXM|>Hj@8Z*%yQ0QtyEM?dRc1ca!wfS&`J>V^)z4>W1Z~%D>(*g zO?W5R@f;#KPk5K90mgM?eS2WNJ)l&@XJR{E6^g%bnRn z6J_qIHMzhT3t3qr5ONX&n5v8^vfdro@1@r7^1)+F%8Vu9oF-}L=L3(QKIiG<7kvBe z8*aCIbZ1c6kfwq|^YGCHX1qjC9%Yr(LDhmuida26fSAbBL^zD7D7blxtho#0u$K>b zd^RIzsqLm}N{o~gajw-#C{M1I(2T2YP*~M5tqoZps5&w=O=i1?l5W_IBQrN=H6Nf% zr(}QE6_zupt=uO?H$j_H@eN(3A|ZO0ooHo0iia zG)e|*5piquDmAg7CExBrpjDAgO-ZG7b_;`FHk)$l^2RlpOB`9&8mv_~qp&K`nas=< zI-Suui{PmXRx5c}w0^x)SF}Ws<>s!(A)6d*45nd_X?+Yz+^QmOe8Pk;EHcW-XFdb6hZ8I$c;)XdDoVK9_rF{LLO zhcaacQYQK$+jRJsC(0=;$(Eg}oNGL;-A zDuz-uPIaI>xlF_=htp6YH1tNPg1ymIdZLa&$fDp7Cw7MeB^M4<(ss`{RnA96Sy;31>9R!LwM+aI+5W6e&+^_ZwcVcTAfdK0{}mELzg3K;QNFzUOM6cy+s>j5l2N znddswd&|sP7S_@^$89Ohyv21h!r?7(pRm4Qa$>W-;WoI{0!skSKL8?cBt>TE*Z} zX?^cUCz92eYU#R;<#Ne#xom=KA%*D!ZL_pq^W210N$YD=iYM2!D*Dv@F6rWi)K_=? z@mdL%x)g{haaMLjyJR`LV4Usw_-9X`?)mbo_qYyRPIzVT8gvGi3?4|A=R=_tf`#`geE3q?3SL?>@>S-snC&zXA6@NS~{F*#CNpJc?`bh>lnHT-^6X za@7X6Cll2@grvHUn?HST<@7B6uwPyh($90sqdws)hV%g zKg`MfB>jihh!5YH<9)#g&W9!@a`!&9jkDI04mhIrj)&0WwQ=;#sJ7Wuw2BB++eq6x z!i2sfCnyT36s&bzo}Y34;wjTCbG_aRmWs@NeS=kS_TVAI#W_P~*@t$qYXvHw`oc8# zuT%Mad^cmUT(LSo10vQ)w`HCJmVv={-00j$02<>xgYPJ6A_wW0jX7bJ#W;&0%4cuH zV3)I`Q)`u^MXd$jbu^-6;C z9dp0r{OKjX`uyiSdGMIDoZ;q!AeFV@qVK6WF>{)yb4#rXb9O=KA~C;bF;0ARJ}`TH z!ME#o{Op&X^62sb=TBeodQIVV$vrpC7 zT4Wf|_5AdH!|Tm0&*zSZox;@2*gz^>jpPyur7{*nZ#>Rw=EDpX-$}3P5iK?yxVW6L zSkBu%%aHaXU9TB;TVB6^gImrR&d){MEO%rU3Z!;Oa>i2&q#T8=bI%AA!o);ic(CNf zXD_&X`UKT=K<09>;Q8f4UcJ3$+8xkEvFzsb)^nXA(=-xOX3CK2VZR=^zTJ_=p6P+Cf#d#`>-AgaGsk==l(1&MxhBQfh5-dzCrmSqIwKuiDek#m zf6qP^l$$Y|pP{-rg^m*1-r4tpGt5qU2xBH@8Cu39hR<<25JDovi7ASa#km26m3?DX zq?+YGq!pAxj1gUA5RkH~`i@c~gXi{4 zDEV0SCx?HlyOguJDi73ReC#+OXa6mU-EbV z_5a}g`zx0IA@%_3ux7Cs=pR02+HW{aZ;A0BX3VVc#$lAuL2}Myttm!JJ%h1ys>c^e z!WL^W#^JptR7Z@Jn3afEHKLTG7Qq~M-r`Uk)@yEJM6Xs{4~F&bE#Lm|1Fzm>)>}!o zIM-n&(8*(JhpJZgT}FZJsCI^R9ol+O?nK`zrAR$m+sD_IP)w?liR8Lxnyw&kaZXGu zsodY+I(V!s%eA&&QPpoL^|nH0blomC^m&$61uc5 zbt`+NS`?`&jB*seU{sE6tPDkoVFp9ima!`Ky9tvs27?>s#B@NXMDIMk(&Q-E<40#_ z)L;IRUw!f^fBmb^*#Gp;Y~KBj_wQeEwMi@s(t2b}>>id^`^7M?)K7Yad$uoZS z>!0(tfB&!i-~aMoc`!tN`S~Y2=^pa@$qP20MFWoc1Fs^UduWz`$Srf;I?>sR?u5NF*+K;@yde7C(74#kB#U*N3i0P;t zg=tQG?^>ybgO^!K&IzY9)_II|_%;CDZ8xMGPl+|}r3Ptrw!(EC*Xw|`9k!eCk6-?t z@n62=vtRt0FP=Xm#)NlEZr)w<_17=?{F6_a4?}BKDJ&Lq9xgj(^M&9^sy;!vxVM*= z)mnrTa}Rl~t8`u$TJpGLtoO_o3%qlrZ1J{3r_Anp&Gz~h6+jnF2!Z{6hmpu5wI(`i z(YcU=COYY)?0hHbv6=~Cl2lyDGE{D8Tb(r3W@<@jt+Cn&9Vr*K+XKt9k#06n4A{se zLL8#ajat1zu9jFOL0sCkn39OHDP^V*$x;4WjEOJ>NtdTPf?BRZP(21DPP2?wDVC6A za7GgEr5W5$c=V$%bdT(GGQ^i=%4^2_e8Pl@e;43#lOrYVx5IdyDT-S*ITQ@DHN!iKTV zg3DJLVhC(+-mu$TQF1^_L9v*yHO*`wL{isDm6`$W^pbz` zmw(28__x1harTfrO^n;X6t=v7|C+bAYdYO=c5%+61=Ag-iT(b-&Fu}>x7VzoYnidX`s8el+chjty^V9``M zy#OMn1~Q4NpdT!1env`_Fos4t>Ci@qO{im1yD}IjGD)rEBnWI{>}gJ=wN#JQR=H@5 z)PY)S3Dpp4Y;>_>6{#fCd*|t_rL#_`7^*f(i*4xXjwxs{dsid$rs~n%0$pRP0Um`q zn-fvhW~>&YV6Kjw4~?2Dg7GmVB$aHMoMGG?P^#iP!_)-(loI0I{OCZdsQhSb6u z-g&ygp;LP;r7+I6pXqqUtfEH=^R^k<#6((3X*Dw)wsUeVYlZh6T@&GqF?7z$oH&B- zJgHG}j1?obHKtYNs8dypQeq0MwTiJq0e#m91VWZO63CosiF+!(6GC1o<%A`TAu>$| zavh~=*!NgvWj@>}Y`G-%yA2^6h+)EeOW${RV+n_moCD4&(x|z<+Vb-IA9?xe4eMP% z_XCUbfu|47IGYU&s-sf|tut$;X6&&8L3533Z;b@ zEF(Tirpt51oPLJmyx>?5n~!scB2^{YI;?e4|5dw2rIA^mF&3>IQCTM2v8{?7il`tv zg_(JhHe4UJY`3qeqIny0RR%76iu56Zb z9(??QK}D>S@5yS#xEXnSJMwB1NLI8}T54X5WrksZ%lUxyOS-|evy#xpONJD)=#W&Z zGK?Xy-HpUjaeknpd40X%5DSatjF~HVqj`0+=k?(qF>}Y`Up(dU#Tm}bae6^-9W@=W zy5Kuc$(8MPPcGnHheD|2B?fYuh+$986LC6FN@TH`vsljQI!`|gl%g>uqf)|DDJR!f z6H06deDP?^yBVa)bO_w6x4eG)mI=+cc!((r@}zmce#bhjx!tX~T&@_L!q-FzvUcRu zh=)l?hzdw$#F&aUniMltOQ~BaS+pJ_rf#JwMtX&02Bg4w%ak(K7zvyeMQy>stJ48= zVz7$yS;zTeki@x$n{mfa>otFTyJvH=<8b|sANPB%uCA$LU~W95N=ii#DAqF!GmJK9 zZP8lp#A-B2DT=DZY+FZzI;ClHX}u*y9A`$!gDK_Qka!{NZk-F#3!ESe-cvSO z^(40CEJL%9GoeTagX=oNWJyKI1)-&zQ=y2dQi2TRBT9xqC6U_(b*3xP6zsWWsTTaj zr%(C!A3x)xM+?p$4%{AYc>n$#n-ux>cVF>;efcGSc=?9Iy8{mwmt0<4a1|yjBj;yJ zN;i=vS&$dA8PzJ5R)*Hg-Y}aK)=KyCvx{?9N^^6)!FoOri%bbnBRR|P_>d#JAP1Of zw`S_Z%{dz?=t$=*K~<)b=%&Pa99bU@(sQO1AtgyyO@UA~Qz`6XWEUc&LUc31R!(qu zzhgF4YBm_N6l97v^n=AOdYBK;8wwNa{hA+dZW+@7og%$8(&bSTv(B&@JX%$5$30im zgvyyAC2B}4wdK+pUKES&EN4$25N;H&cRSv#w}e_4HP~m=|&cb`QR`bcKa=x!+|knVvEBwI+1ek*YlKpX^Y&trMFh}#zG7e{e@$>ShAVV*xZW4sOyA;kV|0-L2$mMiVN7t zaM3ACEYk0!?p3qPVMg4#`Cd2uNrb+4s4VTDdU9{ym&4TwEqI5|<^yp%qa{sNAszp2 z>kqHxe(C&CSZ~SyhQC**?sfHnxO@~8j)&Si9a@Q->t3Sz2qdIRhjeX+p*u8q>sL9Z z4)vXrvD9=D?bY%1?tN{=ahTgaKXoU_fB1H&dq>m{qU`q$tbFhu)>{8mQ9p_EBAnc> z7P#v$KI!bLevp<_>Q5N?iVp-Mb@~QqowGJ%RL?|&aiR~riO3|_jruUe>!ahKb)creh*x@u>zy1lmxn=Gh{>fvk zZdmtwwE@R61KGGs57qkShF7m&v)%5P&oo-)X5x^20POgRzy)dz1}?&0Z>D zauWQX(O4|H3Q?sh+4ve`D5{C~6|8jt$v`&0%w_}2<$~oQGxP&f+{-XhiGe|rPNtny zG^*U;Pb-y>GGhpY7{p0$6rFci=R{ztpi~l+|Is+2Of%dmK|HojOx1+=({&Cqv@Osp zE0rIrkW`|oL@||sCA0*f&XG>T+;oC`b(Lw_v)=4+{Xvk3ag^s{b!4RprDC-dkx=%W ztsKujddT6&H(c-5q+8Ef?>PVLBYyVmDUUz;8EW2TPv6cuMCC*SkIA zdP}`nQA435aRX~**yjnYGhJ2Kngn~=$+eViM>0;*7gaH-j-)%PcI2vw#Sm>_)Crq2 zWtw0b*+Z7s{yf`2*e`2Ba^(p{WWyoZ#~Ma+*jwTc5>*+(&l zS?`#PC08TE%FeSs9GGHdwY*?He}wfb4xUD`p~Q8uGqSOmZ60{rMA=u zOT+OQXtj})ce^g`cJnR0&<^YzD@2;XO}8YLSVFUWeWlPc`8ut+s+JS#w6>K0aV@Wn z%u%bRNH} zayC{i_&Ou>l%kAwy46`}IVo3tIZ*nAL=?E=FP^7uihJKZz4PSlx9sl^R|G@8l_a)c+4exe0R6p=V zt=OT*cAC@;T*sdEcE{j5EDjqooq?K@Ag8UT=7v;1UXu~&0oOgcD_E2NKepbiN46_X z&wJLm_l}61PM(2NkwsOts-+$*!8Rbtl4Z3FK?eLg4E-oy_{x9*`!@*km26l>OYU|T zy9O3nB+rm1rx^CIMqhj@A`iQL!2)XHkQuS}THp7+&x19YkOUu}LM9*zse%lctP=-g zOo?e8d8irJPjqX|J|se2lXU=HFgmiXPH=m37UhMNP)xR-q7^w$EYpnN2ArQUgOfIe zHB@5>HKVoAbXK~uQiYy~DGR=2OoSy_CZt(&)Da!OYqdFLp<DxuWU8L1 zCZVzn^Q#iFR8m62%c-EV5ayO#P|mcLny6TeiNJz38mr{lR7)e$wdY7}Nlc~NFgCY< zs7cWrjOWf)lv?XG| zI0BmelDX;(mlx+OKJXY5j6!9J;ec@lXDUnF@~{8JKjP2+n}5Z*bKHM(&0qb8k>7m# zH8AzkWk^@fpv~ zF8TDck9fR)V44G)2q;S^s9nnT001BWNklS1*A zkLU_UiR-$yeRXdPoi^lUA|(kQCF6M9UvjrQ;NpnZBOABIR70!>S(Yg|E6+ zZiGLlC7gEHPvlsreJ>H(lpE62P-AN@7irzrj5M~}B2XzuY`?FZD{)>3MG7mWsp@Ef zTnX?PGKGw0oDy%gGncC~uAjZYYYi!J`}Q^4-GTM$n$7x>ese}OYg~8Ev#V>)h7})Q zU-Ec=!+-pbf5F4|-%>;1r=R@}^~Ex}M>^K@X13Te;+d|YPp(Y3+u*5*jGa<_7B!oc8LP409rG>DSkWGuw z`S`Huj%rkw+Lqt)Gsv})K<2u~fcu9;q;_wU#+*7885(R8lO_0@3*?6BPsdAiDJ}ek zsHAkkYekn4rBTUPmGHTsW0tzCb=c}!gFj14SXT@}U*V)md{W-k`V;`Dz#JF$(}-;| z5uq|QEM0A+zNP>>5}s(GCX7s$Ll9->m}U}*oF)#hzvJQVhLUEit}J9~O%1ay3Rk6- z+RXBL}WmmVf+5zu>?9Xa9s8HSfRv9r^K=yPF&CAMe>M3-+wz z`SWYWn9c^Bm_otaUgy;B=vPjX0O(pV1iA2)mZ*Ah!0hw^p77#tI5u z@TXwJVP9%xEw>C=rDRgdxC*`;OI4?DWt(gs6_%NrWe+nrc_$R# zknc(w*`?_WOPg(Wosdlc`OG0W{9~irS}RMpb{0+z&91G%d*2ioqE@t8GxQzK^=Lcb zt!3~|2xKJ*0L@tm&$TnPchaz5b%XpK6&wx|r7G6zwS)z&!8q4Kvifv}c_MW_{S35{ zXK^V;b|gs}*Q!W(kQV%|CDS>1hR^dP&&UdMni#`KrO-3sYlm$FJEesLkxRzviG4lb zOlI(!i?a@IJ?e5Jvk>bDivqvpW*Vv6J8~`f^}zY{C7*rqF&|%E;G#yyOjx#*_=vL! z@(A9y&RKJW8!6MM)MT!AbC8$79S<11|QBr0cw`|9RHHv4K zJ(rzlaSNZGZE(Hkk3RZ{7w4k8^LE3!>rtgAm$B(!D&^T)o*%}Z6z4I_Ok-jHDB6X2 zs>D>;tQ40Q9cLGpY|hr?no+q3%D)Q11S@1MF*V72*eQ#thV6Dl>$i+!-P;#ykGt%U`fOKJfONUqKG+sv%Y(OXNmHx6Yvm7@cue&fG>>nNJ>dsz=gr z&G5kvLK`7rqNS{5sB$JOvh+XO47|EJ)du&{xg3yFF#UC#XnC30pKJEu=+;9MY<@q_TjuDC5!A3;Vycw2)D#D0n)pWzn6Fp627B zQ0O>hCEn%ZVo7vMxuKOlSpKy3dREeIN?9z{wJ37U?cm&82^o|eE7n?Sq+lM+AatdfH8q=r?iC7gn z$;U+3fXYdhl35xDwp6Zq!+Pk*XzKcsE35g%XCL!-UcF%5f$KdrTY}cy-`?}x`v>;> zi5LPu{mCbMarF`1s^>C}#J#8Vj_vK15*Nz6po>A*o~~O_Qel}FYR#;M6}B4AhczrS z&sH0{wPxi#!>Z?O_!yJ+tX(3=fcH}WrtVUxH4rQ$%U*g2d!)D##*Pf*W zw&OkHbVoSc@VG4KkTBiE%Jr;Fhjjzlc1%koEPL|!fGL4$1lr?FB@%eE?0NP5mKb(? z^Zl>+c7G4kp3QX2v)NN+IPFZtU0^FI6rwjYN+&ZNZ*lzuR-Z8TbeP;2)x1 ze$3%7zh?jDcho#FA0EkZlGJocr$y+|pHo@_7WFhd*V>TF($YtUQ*@)Xj>QvO|9_UW ziku4}WnT6xo?l+G^5W|l=ZSf|W4qs@jV4Ty$8cb~-7=4(eE({}%Ic7$jzdLxZWR4N zll@2S@%p!R!@2p(N^9jE7s~nrtF_k6|Dv_<6OZAAmcA~kHW!eDl*$ode#D8MF1J5M z^2!pSP^&pD6rWJ-^{Gv+BvJR_TD*ZAKfu;M)x}G5gw^IxKmH!o3G4o0C_z0T|Eu~N ze)%Jozy7$j&xZtqZ3D z2tgg2)I3$~tNI({0Hr=~)HM&)2ZkfnJj_qqf6=X!)6Y_UpcxPZUX=o+95D6Mz*rnh zrTvbde2hwIif*m#nu%1j^Z4^~@SdV(w)-95eK(Q91AqP>{ylHsZu$7^oX5*^-o1Uz z`s!MiaoV1KN41e3^wB!ozNcC%Z1WhFTqJO&AcjQDj$$n+p?CP^u&qjluWFE%LRb=R z@brTvhJem;Y15`+V-Tu@@kEtLc}7=H2$t=kfC{WvGvN?%#ZXg4sRSmGmWoM+j3bg! z2gXt%OylTwR2CZhxS?1e^W`(0ymYmP?2T7 zK?K@L$r7@nSVWd@}Ros+XfXDumN z#@hqoab`HbWaBpIBCX70l?JMki=AuV^(h1%c8{bt-}CzB2T+CE_qY7^ufF2b zPrnddim9ko&u)9*=H`Y~x289aI8ThzMDKc>?w-C^N}iDwd8gExan+)*lvLPlA8-#d zo0!p|keA4O*mJ+%GS$M}!#$7l#BQAN-~OKU`59rE+U!J9*(FD)Nm@X~o(4ir8P?9R z83xWS&bipU;LDGH!B0Q=lFj-|nnYm+67O!_^7`Fd4%38JvYc|Zr}GZ)d%*<9=4!Vq zjP6lvu)4?C38l{=W>WGn^en!U_A}6RYm8Yz(5!WjGnT+Cc4yzClq29-auyX*NrWlj zj3dkgl#0=oY-D<=wGfB2*0QWmsRhm;Wz~H58EdR8H*z|C*i%mEQt)kfSfX4&70}g+ z^$MqD;AFL;w}$I=$MxBYmf4lF*Zm zA*LTwuH+mqctL3=)Z@D}7ik$PeL~h9+c?LCuu`Tqu_RbwtUe7rjzeQORymB2A+of$ z%<+|A6j>{%nOYV~o@7X_J)^cvN!l>hR-gkMV^Qti5z#pkV?McuYc0&BvXo3#1*2Qj zOj#%zQwcZ4ajvJR6YSr@b!P?HA>T4%DX?J-s3v&FhI zHmb6!l`bdoC)SE>K5>&3C`Z*2rm2nlFHH}Jt{PJ`-pWuVhKMze)#eP{IjlEK)5txA zpFDrXX6Oj>Oo*AHH)w0|&f~oogmX4>KFre0FClW~9Yg1s_6K%prY}AB)5vyN2q|-M ze#L5iM&}$MEz}Y)ouStQVaY5B#wBsT+w-#Xn7#+^QNH8--6OyJ>R0UV?y>8ES!v$g zZF#Z2=F^Wq;<7iK*_Yhi-?B^*uRQzvd&2l2$XGOP=ucQ&m1c@#7kTft z!AMrEJy)eQf1(qqr-7P0RE*%KHN;XN6|zzi&i9s;ALvX$#Y9O4w3i@wt>m`g2nwqA zXgyHOK<+Lu=g(2Ab8$+RqkmEG-!T=e@?4!=pjL(P@sVjaEPzP!e+&k2Ve_jBSf?b$604^!av%>!@V-f?yLoa>91TwnG4?B}2J^)L7Q z{>?of-#_s3CzqU`_v~he&XIMMkmZ;Rhk0f`Ol%(?3ClzZ3r1;%ZXnJJH}7sFuo7on zjHDD!YAfB4?fc!1?RL+y1Y*+<#v)DIFb9kim-G_ERjq4A8KFCP*U=AaY?dHqO`_~D z!v#}?eRu@+=(^CkHQnkN-DalG5tTj4h$gXLJ!7?g#Wum4`H|0u=de2C`SYGKd_;FO z$jd<&6oD5PD_*{O4sInIJuMqQO>B3G7SK33Oyw-E|G_|w@n*<9x?{LnrgqiIa zC@vCkbn7+O&n`IEiJ>M?J*8G+%I*DEWkBA}`&KEewGG27t)nR<4oj4U28uZ(Vpyo9 zvg&#U-?fHfLTg$eRov+{XPj}IuQ$Z05Jtp3sx8KPTn9rf_^!tqPgRCks`#zFLt7#F zEvcY2*jT7e5ex)X2(>o4RTVd~k+xq~G$B@L03!kXsw!!bpuR~3S0JW>iUs2hRlzt< z9F|NfPE-Y@QetWNS-FGEDU!9!YS5Na71J^kn@%C-gmd-uu21ccnkykgM6=pF~=-*NZwp4EE8)zvkZ*K3}=c!BAAdf)T%Y|SUnE_ii*#_O_>Yvk_!Et_t` z<+E$tW}xK6e!pimtoVC>_zSM)9kE2#!v!S+Sv7i8rG!N=)TXKq& zEVyE8L`hndB4zpOEQ>gcQ?95~iF1%Z-W*uc%n~EZEYzywO+Gb3qCVa;KcN2A4+{^f zp=`69*L2fA)cQomJSrWIh;UUarq&Ncj-Y=;hLmu~k+vqvxuXV8w?%rz)Ph$DZ8Bbq zen?Ys3T%~7j)1epnXZb2At_2i^}5Yhihc?rG`-z%X|J>dzEX_L^Nc|h4Wa>&KT3TH z&8s7=P01q3X(I%fnwh7Gx9{H*#~m??dtDs@AqWXcqwx(9pNgi?k(`FE!&r?iO2ULB zlCEUsP^m+k5oaq?l|Az!?BdCBwV&v?9t zWjt^=+_Rl`+)a_)oG3VyP1qW-I%8bM4dDCA`I+I_xuWD9^Ws^rKj-@SHS6`7Fl}W) zTemRiU;3Q<+bEZ$IYTGJV6hj-55t)V8cTF&e3XhJH9kY_2k zDaGS%!Gdd1nzq-|07_ z7^A^imJlc@qdJ3gPL!K8T|_PD%!w9OW0vKj$}JKLP8kN{@ES}mm{Lzl7JV#6YK2o3 zr{xb-6S}#qOI!$P#(G1su*5?n!UUOzv_fU6()dIV%ux}x3#C?4k`Qz(@?NmcHwt4W zWbrQ_2`W-9DX06RwH>7z#yQAh+{xa?IfFG#l~#@y6-}=dFIEa`Jt&QF9&0;>VTH9q z`pG$?FnI6e|KFD1N-4;hz6jYw>$ZoOW~LyEPuq1^-(kIzP`eY=W9L1qRnO25qJOat z@2rHCVj`D(8t29m<=-n!$*nS0X2LX6$1ST)vmp?3lyFW!$R=$zDH?_LtVw|-OynY3 zUTPsPp?#O0zCWc_SpcR)&P9SENZ>pbA*sZaM7dy*C1p`9j4uJoYAmr>7Z8AT|}x@q-v6=I6$6^o$m+w43e&jQ&KG$wG)SSv*uV>&X9kSfLw zs8pzF#4Afe3dBZxDF#$|Zs`>9WZY`Sb`^P;0@Hp^uQIo{Gq-Qw!Dk;aTnsqhp;b>U z6RIXIyylBn*Svdq&D*yF^EeV>;rrVg*1!2RKh>{r-r}{T%#m_9pzF-T_Lhf3V%hI; z!x~kDPAN?I}vcXi@%!SO`f`t0U&jINdXj0dE4M zKBEhs@%DG%?-rKh49vnFUwO(Ui3CCF9eOk<9krYr=W)b1H2Hg!}B87&js zzt0@@2VB=N=2-%^Swg7FSXtCt`Ry4kpQTQFyzSU-M{c)!9uoAuN2P?dj>8yOtt@~4 zPyQbN!@v1AqG3DZ*Cg(AMI=IRM4vYRGc=$@9Ky2>xD$dbUeLvSh$ z6%#8RRss}f7x=!XYzEGa;pOI>Ro@f!#39YxOndH^1IupV+2#V*Z}{?)FZg(K#q(he zE5{G>1M_3xehHNOH&g_fQ(~dSjK5x?bkE^1vpY-_z;zvS$dp+5;>Am@&o{hycE!h^ zek3jq64q8y4x}Qe_Rw6%hrQ!PVPrFOT&~vO6^HmhDdJdM;UGrR zGVyE}_}M2vCG84&T^Np9jglcGR)eLiHP^OhoM0#(@x+iGQWIGlr-|Qp%?|w&E4!n8)!0j}$pA*`5 zU^I6RJM3!U?)Hw$q2uMpFX%pc0ooG}6J6(7tyXN#HjHu4G9OS>5|$Jp$3#gPZ4I64 z(AI&n7%NF(Rm!RTEB72(5Qz^=i#xVvd3pL3io*5TE8NAW==0Bb=J(9T5#z+e;~l;2 z#ASQ*Og1;+5j9)NQ@XFTBvet0Bx=zzASt@-j@1c?{$acQh-ptLF)a~gMn3+$a^@}M z$T-f-<3d;h^D+~rJ!#%^_qb);kHl!?(xoL_BVIN+y&r?xMLogde*|GaVK=pW$4}_b z56I{z*Z5P{UFqWiom1#Xom%vzq2bEOw_e)7^G81R4^a2DJ|Xz)NxQC27Wf}wgpqKN z`q8rgxRh;;q$k^1TYQw0b6ry2<%tY(#1bnRj_MzI+dpWmpZ-Q2XmXC+zPsV>Hn3kFcrz{BRmER?|DI1D6ES)=ogk{AVj8MP8BNNDSPZH- z@s{aAnBTFC?+7(e+(rT-MX^M<50=`Up{+rcM^u)}U(xbrF4oFmEEf8%;EfXhp0%VT zmmF&nMagg|m)x4OFfI%4-oNJ|ZpkTOoyBDFk)0Nd6;g^UAyPv@rA*3!QW8dKLWs=s z!hSz88TncD!@%C!)|ArZl!UxfD#eH%1fyv&m=Dww>V!D_5bPjcLrF~^a}LLJZLOwV zDh*Z}Tx;DQ#+e`9-td)kbiLu_s-yaj1%)v(EULLsN^{-CMBi&Ze*TR87JF3oUIhjL~1UrNh0bVR1r+OMh@5^gVwy??)d7PZ@F9)ciTsP_1oX_o4=e`8OzVU z{2BlJU;Rt2zW9WgBfA(`ouBjK>;h{Y)3UIHfOlRJw?(&Bju2fU0qPPb%AqK|% zBbU`;P~3g{1LNHzV@WJ3lczh++|fBoGL{Q5V)VZYn)e6wMtd-~0q zk}B4CO4TeOf$6dC4CmLBv?2$OGUs3~sWoBsGfM2lX_iHW)VYpzch0?8QCjOYgoQW< z$OhYW#G=KCm$O{RjK&*QB|3XShSrR&m6g$)uR5MzY>R zkTT^&$w3g9rJ!^oIA^@M;dkGB z&-VTiqZKG=Xf1PQce~{ee)1Ww{>%T2pL}wK^Pbz=Ej4QH-oNFq{^I}So3Fnj%n_wm zgklIqvBb!fD^WQ*Z*Z#%y3GZuwZvl9%psF^2bS%DyhN%o_ow0k$2FeeVX0}o4pA_LQy zMfG5nhbkDzReyo5o^hO+lENBr*2$o=ThkAgB_$pvFh#R=y)pI6Oxr#CDbi<+_7~*pNXw42-$7n5R$7TMX_Q^F z`I-txugpYV8h%UI#<-94M@XPlp{SkgH+tuYg; zW{wK2Bc)C}jx&dy#^nKD6iEf{%Yoa&j>G+grBIzES%s^aYT(`(l2JIn#;ZyxQ4A?v zhR(p`)e#xr001BWNklrKCioEe?45Ct^xyC74^|{OOJ-Zd)V%+#*Z+Qd?l+*dJ6- zij*{rsZD_=s>ib#cWqR>pn7nL=m0VENlU$F}FYQc9jnT7r1JHLX!y@L8iX zWL=m`Vor%U3Vkc5N(i#}^eQn}N44st%&9HVCOSz@cN(pPoZ+mes6;6vDa(BznNuRUD9e5f+E~&wu`E&qqKLAmD0ycpZ84o;=q=~ZuDI|W!@&q~a=&nY z^M-GK_#Mi@=JEnRXs%vvc=75v`$I&h%vvQbJB`&9wFteTl0YYlia57ob#_UZBH9o5 z>nkqi10e=p3@e6yEx)^xTd2GeL%_BBkD!gU3|f`U4@zs4)`m@wC9z})m6&SCF;a8F zXbsxpR%@<5`Iy>YGE>M(1BMhL^SEc+?btqU+3oklD4K;-Qdi9vf<%4sY%tx}WsQh&OK4l-NC@or) zT5Hpq3MsV}{!amGb?i1A6?kH%5k+X5%hn=-i&QnXAfDTjuqo1&fKeG+3$_}p@~x#C z1@l~cbg_+p7p1wYtbF&6>c_UEKLtD67kKD;YD`SiL`e~&p=!&LJ+&lEO(c~lZK0x( z_f;)fzSl9Z+wJ)Non}7lP&ISbc{bKzhc(K1iX=be{T7AFs=dd0C+CN#unHVP#nf7p zNaFj|hNxSEeYoWIkon*Lum8mT+Zmq?IaRiYi5L^BVa=86xgG{`(u8}%ct|W{rjSn| zajh*iTT0H-JXJynD!HJOMkR}K1DzjE_iAl4sv+@@v$85FA2dc9h;3e{j1o_{ky$r2 z$ulMjMT(TDAwaH_BrW0;QX?rz7^b8s%j~5PrKk{U9~^7px}*e1jP24k2RB>!P}KMA7&Uviqan|Fs;#@^pl2Xye;;g0bdyKXiWrTdJl$2JAEZt-W zB)WzeXR5M7FSZ?Kr8qlVvpK)Q`X1k}WJd%Nj4GK_0*8a>&0=2UNmwc+rB+Umf5y^+ zDltYO&MCz_&C=kHkr;#;v|8P>d)%{xL`gcnTpH zW3Wf^!7^QK_mfwgcb23&`TI_ZQUXdF&IZfX*_!L~ z=e(ar9;ZF}Fk|m;Dc=Up&ewF>P^Uzf6P|@|*i+_=3IRV1crCLY*wTascAvQ z1B4M*3tf#k<3v@Qi`=VJk!OI??J=gv)j_HFPO&i#>kCuZqoUzqchBQ^N3;vO>73q4 zak-?@%GncI&!7S+cPNc0Agm?C$g(V?B+v2?gXp^Y0kmhyn%#CL+)r{Ys5@%bsAK3; z8)BAOMNtJ~3~T3Ebt|;-+}>?T%3*qsUppZ@WQ7?#FJE^2cmMjoVr2@m%Dg(i;J1f8 zh7nU0-f5gwRBI{yieaQDK)UfnV=kf zt!(VTX0R;`Fp&!qP_mXWMh??VicpVl>s+jef5EGz5F^OZ)YTf+wreXQYX}O@Z!dBaoq>@z9Ra*Z=i&$OKvxZ_cwZ!IJ zZp~REzE6N91?H4k=0He=x-8PnY^h|3C!BaSj#LZF@e-JzSDBTwY?$%2U{b=TN@C&G zj1*IdGlVIUrb5?sTwh%9s(E2z5;qwo(#MFgitRY!Ym|0cai|Ge>W04 z=xk+QA{EcG%_Udb@ssN-KKuMLKK=3w%x3+!aM(}FGkrRm7o>?;@^QfxD0Pva{ZcqQ z-thiD5EeIT3?Y;4HU8or_v(-M*f(k+{qprkTf-+0F~9SeB}J91~%COISvJ_NPDR`Jet>7*-G} zR&~-?(~5qzChmI1!wzaeJCD&ymh@d_u;$dDk)^$n^kGA-rCh}^lom=(twomGlCVloPcIdh*y}rX|ge4e;X5yvhKY{3wU zQ%f#-<9vZ3V!G- zY{>okfy2Ij5HO(D57yiD^h`M}Tb~@}azQZOA|HuDYTVn8U8QPU@ZpjZ*z#rn~pZ*!Os^Va>$P%F(X$k6s z?`Zq0mSf+sQBK;jT%O7G2{A9%hm%|91jkq91ALR>xV}DK%uB<$sSh6$KNbQsZ>bDP zp`3y#v_wCDGg#3Y&GooIZ||Vm{(6!=RW1A=IAhn`utlFH1=6Nc(zZ6G-~e9>S|wDC zcnnrKVs&WSq1y{pX&GWk*}Gz_A!pUTHwZ$SjL{yYGH8c4iq+~2J*>f5suD-6Dw%PT z6d-8Ed0`qdx*wQRAndnL*5s0?LE3lL7{TvrA;*AYo@R2)s4AE5oTLF;wI;VtseIlnrm|L9|a zR;0X;+hP%8DPp0<*2?*?;*-xlN2`}!8sU$Laj_uDOBef3uy?jQNtuYbep)pP#Et7}}pBJOAA$34SpgD#aR zB~nRGPq8*!G)QnyTH(@4l@VVig@84h&E}lq9B=O)SY;?yF~x}-11e-3o@G9;-5%(y z!*~c~ISrnnM_1dDS<{UC?@3*8Nm!HlLU{^T5qIVmV?y1GH9@!mRIHK--As&Lh5Vn~DpyG3&dihz;J za7mDZyklDfxo9ix)FOya!I26ozbM^8uPv}uBBTYZIt_AC!^r0%XMLk+&B#P5av87M zP_+|uwoxRl1zndjF$-;=zQdpCf}EMbEAK(U^=WYK~-gEP=qC1H@1 zSO_Si>0FOihR%2_hIv|exZe|U=HkUAo2x5gu`DHW;Viyi5kny?j|@(8d3izAmT?YT z^c83I2}j+zj+848yDjrL@%i-&&bp4EFELVRs-`aY#CAv8qy+K-HC(SI_yymw$(!efA}{`^f9>zo#sfVtSshR-|3#&A7!4 zG9*=6Q>{k1_I^{M$0)jpC$P+Dq*0USE$nWvrt)%q&F7bnbECi;E_uf1FP`!I*-M;t zGKe`+YT8My7V&wHhnabralU677k>5CulfD2e@j@R(7kfS)z}WrnP-=4UOxW`l?~hZ zJ*8|(R+inSI$U?gvlmy=ARTWRmqK3!(;DVViVL+a7%k!J&8DaC^Wn5j`%HYGoN;5_y^Fj65HW75ao2hV2D~FwLk@{zEX7C| z%7)B37an8f?hr|)(0wLT(7O6RD}|ja+1$m3;iBX zP`N46j;LsIVabt@Cu(*Q)>%dtYuOaj76XxYZ(sA9|MxHXvp@cv!w>KI<^S_NKmEhM z&&SU{V=xPx!)~9r`Ti?dUh{WFk8fNB4EP8RFryS6NmB?kH5TQ7I{l0~&*i!^j{X~hmxycQvO0JnDW>PMAYw5e5kOK)zNtGBQF)rvv8B679 zevzBjL>jb4LUPA@-SK>mEF_HSS)HvpyL?X9JFK25+Z}iB-tenmf5qdx<@qPCKwG-b za<&=hI>-Clk=_MSds#(Ekr)z}{hAx=n9_nR9VKU+vCwIhui^r>ig_B@SVPH)s0+@6 zj)lW?fFkNI?;OiKQ)0w-o_^&U4XzcD+N-~)LassxjmClZ5>d*~`+<*Ne99mH;U9CT zhG%D&+}ai1bXe`!?hkzP`g_jL*6g-hw);oHE*mK~#T-#3GsXXpt2gq@ize!D&A zL~|RGBGsibmCCMM^oa#ZObtJ##6Z&sNWB*;mE4MqY|xhg6FO~Bz= zwUDgcX`Zn8@UL7N0O&tnu3O*{44gYJu+< zs^!_mIYw3XX~A1V=Pb%dg4NfaLXn|=iBJ;D+|EUVp=uG>QFzP^56sg< zS^^~{j5W;6@`@J;>Ii+dnVyUox1`1ijFx&jt&X#hipq*H2F7t>oC7ICDivE6x~^kT z9o8D0H5lhH&dTeP*S6&h|W;tK9 znxLgrp(vCj^93@Lgfhp(v4lJ4c;N0~VYeIQ{X;R8!u=fh?%f^x-HsYW#7$Z8=I(*F zHxr>aQk8u{Dbr{IQiXk_1=Hx2DHnOB8UarB8bgV4Rw-4Zj`P2U^UtjY&$%9FEthBK zY}Xgms(Ja%E5><2n?lTmiUT7m6J1f z>Yguu{S|jtcch@1Lq$2zCUJhg=3@N>-S_y-IrjDfqYYzHd~R+*`AfP zbXuWHFL%0{P&J`)M3se{?FIENi_{@_W5UH&NSx%$P$!7b}JrZPCUz zQ(rS-6ge-`J}=%#rG=}FimEi)RGfN8XoE!$W^5?Q#>-$e@%UY!sy-d$J((kwGFwuujHkl+L8GP?KvY zzlt#hYHV#=7MBH!leG{Ct_~AJ=ooBjXc&do?GT0>472qdvSOSfbzX3(FuHs8J3%BF zr?Fad*q(E<+lmRnDCU|tObbpKDkD~De6Lutbd|+a7{`g41H1i>8VcTcLf1joIN#&C z6-jG$%gj{71?i2Zq{I?u&W3@mWY#sKmbrbvj#TvbP~@cz@tfeQo4>*+ane=|R?DFe zrC{t?s$st<`r6~HW4+l*D#Ul7pHP$HlTSXuueN`LwT2lO_~`aEuV25x-dyva{>5ML z^FR9~`ur(a&o6gBV?P~;Rq=oPhkwuC{Pka=cMq()j`iB(9Na(b=yc+vCu_8E^uA+b z6`PgEb&iKQaQ*I%aC1$V0<~C{YK5ARMplDiK-@e}FQ4$qN6+!S46sRtlH}m!yMgW5 z1^IB#yuYQUjA>X}V>G7g=(NTfOW$=^KOC=47_9`yPL=^1g{m4_irjjdBicxRUR*}r z-Y+~WPwDPPu3mk~+pBNr>OwBkJ*||H4M7R!@`Sf<2N9)YSeUb5h*Q=leC6>nFSw-U z;%jbg!N<3hfzoKBk-JAlO%v1oEjcat-f1X2ld@J;QKQOil{ zEsaU8kGQq>JJ;(e;@*OQ(xrxKNrxk4<9IQB@2S%DC}}_T=+Xqf2G*BkhZ5{G^*yov zvBCXc^ty|9T6i=HJZg*Tgk?YeTv6(jv_A>*?;F27|44lA4$zI z@PC35R|TdC`SsZKt6PVCeT3H?(}0Q-%AP8r%2OJu^4L9yZsK`8eVx+7^oXi|tRzq; z7p~T-IS8b?>QU2|G%Ag*ass0?%0hYHNby)=-e>{p2rO$L{qo++{~k(T%k!X{nagO0 zX~MXqN^}#0nkl3lUHZJ2`ZOm)fxIr9u%QOUdNttIYrvCLr8Ly}+6{bi@dP()`IDdh zDK9>F!Eb-_Yp!mu+223#V!V_-dFxtlP!;4{j%X`ugtwk`*W-=F`IQJKxglOLT&!2P zq340jxId8EdoHtkjddQc71T(M72`FXbu2b9H&&cu@BGu?(k8A&6rO;1&cs^Jhv#=8SrCq8|<=F^)`cy{p& z?M+Kc$ziqBW6-!=fPw91!>2#`1ajoecmUZzCciE=TY9BYA+qUu2IskX_l9pv;_BUw z-@d%&pTB-dUPk`EU;iHOSJ0iIUB~&VSB zDE3(W0PaD$l5@tzgpZZFpV?pEaWhWni!JrZC5I(qpFU+uk?H;prxeRH;c{ZLU7?&K z9vc2lKc3-grKKMYkclxu>$+9Qd9HJgi>FUnZ_mk9Gc8#XKWV|LX6(?0sVa6;VAvn% zJR}t<(}JENtRQEkX2MvsQeXP<|H_apSGI6)l#FUxt1%0>78Si>Fea~jt zpsVbGah?fb5mcWx(!GbGYpH0A!8_08(U;c*8;Fz`p z*`2Yg8?NHWy#|BBYLE5?s|qF=LWsm18Fa@|6H5-#H(UyO378nsh`2UWDS5=HfUOl} zI&y_jH73tkmB<>jb_`W9L|C%)6IUk{3xm~Ir`wP~T-cKGGR>7=o1iCThTdtcSL6ds zDHBUXtBN&}o)MsnoC|c;n4-wIQ?-uac(0H>*(tI8<+MPJ4UZ$n8)K@F3M!4((*$*0 zsqI3qt#tOL6e%fEqNMVUSpTBMC0~ySe3ehhudE-3(qcl)a#lSi$P`p#&-@`*lc<>+ksdM``rOjV)JxMcYcoBY^b)AWKaqWDqxpe z@PU=~;0NklSo_Ml2a^q`9;GrNC&p!w$H*A8ThR~aY}Z>!5Ging^PYL0=(LF0PAf<= zIf1t3WSm^DSEM=d+rRsd+$?)uzy2K$_j`05h_z>&3zZ-S)>5QW%^Xdk8dU^csdPcb zh)b2ORZuJ`5|@#)PVu9a;a`7r$z|`z54Uv1@a*X&m(PEIt{PV+QkH#IYINH9J!j@c zI=QtrOykU}H?J7W!u`A_XNc21wob&XB(Y?BKFyXVmrr>1anE%1hW&UTHgaD^)0vKo zvlaeOczv_y%~Du9i*;)bA)t+7-5WNW6>hykdrP*4Y%~rn=@DZ|N{L~gf;O41%R~&t zdUO#1QaKBD17(YI9sPREaB%^Bk8>+bRYCyjI+Ru{VIsyz(UqE$)T>xaZylHG4R>A7 z?lAK1_JJ2q&v@~{2mE&ZhWo2~HjC^PrBvo+rqn1#(_Dy4M5oF{zhdbP%67OaXyP+% z*>s+V+Xtq12fCd2;MvD`J23PsoV}MMjCJzk&N45BLsdLXJKo*iz&x@!J7efIwW!gw zRR91W07*naR7O&sah<_BLkNLsJcuDoZAhsQW0s1Ynn`gXH<6Fl8(!a|gB^nwr3DFc z8`M*ZB&YL?Nj)V7LR?6+Iu<1&^wrHZ|M6G9;_ELy;O^bXuYUC##+v!*hn8NCJOs__ zt36-;;Va%=zu{(i;Jg3r-=WPwH_wb$-}CkNZ~5WB`V0Pt|M7nUQ=v{+U}ZWXK(bKj zqz>hXO{Jd9(t;3Gtv;=u`e3QjTUl$CeMdW1!O4}yX$lWl<()Jk{iYr|yI8qMG^Gv5L?rhDf8>l#zqVP?eibnRoR1|Yolwwi7 zM=MJ$wAanHE^~#^LKu+beJ?nm8Wi4rm zjE9BIY75qLC<$vEQTIIGJ|q4pa&>=0sW49qH|ZAVJBl$l?|8E5$yVc)!aC44qm1AT zYlXvp&-$<@Z!efbfSga1&r%AhJXXpbLk%fs5=E+Ij2D_&jbsO_^EH=e=a>ansS=jR z;8!eBW9}DjUS9LH=`m^IV*7$=R~bVlVEK6c9BUlQ;enzQ*C{?S@=7IVO{unZ$t&?b zRa%O-VvRkWx$5a|Us0w}$!el1-GcT_h($WLOY4U{3RG2zU@cwl8e_$jaKsJQs!pb% z+Geq)B}I#pIjOC!qq=Ijo3(J2+}|oC##1t1%&RR%kP~HDP#KhQxGFuka-Ij{3tlP4 zET64hLPVTDBZiH0%I*vx5c9v`A7i)wFb~w!;^&QJ( zFxD~6l|#HECrhoCWzLM_j!-8Gd$0j(R(JzMc`cSU(6GkH8c;G>RhC>i90F^jS@#!M zBa|Ei^iH#Ah>bKTo$IAlXNp1iIIk!f!75iywKqyvjImhfuvR`tO9pw6UgDwjnKHaiTaJe~S)%C1aYjC}oedm#7x~JshY+6Z2 zwzq^9E~~1vfwi3D=Vf6WXI8dHxis}+N{B9x(| zB1wU&lKw7@oE%air-hp3brJKRXhzl=R$b4wAJ}%Cq>+=%W@fJ-FJi8Vk+>{)Z%A=M zDS7WoEreJwZjIGFst)wS6KXY_Z=Yf{a)$JVuspC#d){4rL&*#Ccp!z?MDj{Wm6`?K z9)P7JF`OKUaxEyQjgj}+n3EooTY{Tb9VZzdA+wdRP;*8rP3JV7H+ZM%yq4$4D6CRc zqe)~!O@ug;^FoU0R5Kt3O!++3%43?o2+2TBOg4*IsZ>-hCsJ!Fnk5%fTjwf~ z6f~hkYMawKjhq=`A}%73E8}G5X*!ZN&<#vEl42!?!n|Z2#!OZnuJ0IQI+1U6BYdTN zoWsd^?N~=rOJZrpf8D5UV{XKTsw5nP)Vm$2*SZCGJMa1M@&zA#@F6)Cma7M@b_=&v zDguhLU^THMYEG@HPkPC9sI0Mk&~MqVFWGH&oW02G{lI*`<>6uEKm6T4F=(hb_NhQ` z=!eQrKKqEzKd$WajPZuE%^ICF)mI`5`*C5tN%URM+2#y&*pZhYL5E}IV_I0|10evZ zh^bOla?UU%32wzA+DJ-5qO2m)ixD?4hnZXw*bYoDcq|O-(g7CRC{a4WU@afNc#d6f z>0B>nyEIXXA;$_+M%#*ZP<4<6eV&<1J838(Uxt*)F%e4;P06(iQl&BGSYj({j8PVA zJRI+JHAz*uk>_s-nUKJATl8>->d(l=uQ=#iMFiG9ywBV{@?HtKkit9zs zE{3kRsJ^2Mm9Dq+N^!RGOeHbK#F!x~StCmjlGSSH>3U1niGJ(JRBUndX5h?tTngOR zK&h6xUGwaG&BsqJ7?eYAG$woYRSRKCCBmF2Y$?T%ie|se+}@Ae-|yJ(cQ~tX-r;RW zEg5YJb2i*Q?4_P9r%1L&TwgiKA=-NSbx%0RCP3BXmZqs8U@Ou|Zt6*(txdc=X-=g| zFMvv@954xS-zhEKa#}xPpxcgKnjn*oIAnWtdTWMHMh)XlPSW36h+S-NLi&n)8)s_x+ypLDQXe&?zX9 z%gYUyms_A`@$lx&_q_S-KgmVKd9t$je$BIIf^n3-mX6L;wq9e6BF~lEys$s)h~tRW zYmC`I(s;i@cMxl&H;(hohRdfPVET0v!>WkmwP3BI>wB#8r^Bi>mRQQELlWEivD;8t zX(tqm5J-6u_gRYM5>FUQ}sdwdec$g}?soYwF=UUTkf{ zbBp_^l!~<;=lzjPDNep49fK@Rxs;mlEq!CP(hlH{h{AHjxwg-DX@hW(%h$_g=H=Tr z3?F^M?_Ph)_1$afHZF;ko>}!BzVFyhfxc7RPq7_JwG8EKZJpC{QOl(Y9lgpSR@`>& zV%pXra!dXn`<#?u+c;uDntA=zpD$B6F$RIr(2_m(ga9`@!;r?DH`bjK& ztb!A)Z%gn?n)y+P7g4?S02k>!Rqyq^Da!AhYVoq}^ z|8a4^BQm}j9@J?eFAZA63IC7LCp04E5#+C%yZ+d9cf>=9+^ixCTHo8sx6U9IhsV&Y0s-IH6iEDCJz$le&?jKkWD^{B` zN~m}Yow2xn#b&)mJ57l*OIRqWVyeL#O*I=VSo(tw^=y*Q`A(!AlDjAcNI6i9;eBY7L(QtS2Z*0Tr!W<`F-xprK z+w%SEH}n^m3{Ni5s%=TN#h_H{QbnOs#dntTwdZCq=&T3{rcmgr1*3V`&wTyr4g39? zVY_g@WV(wFINV)xef2<$Bca~0J51;j>C(umbM%{YO38fu>0ym zd)zss!Lp=?)lxU4Y{d_nq$5)b4KE(hOTy`bY>q2g8}owInmI*ARoIzK))}7y`Qets zX5g@_u=9qHEpy+I^oD~D7`w;nLfX%~d3Vpup10!zQyejNz>?@{0`|C4DcLbq>4#sN ziphbf3X2b1jqsO${kQzpzx%(rihFV>th)U2W2zFN&Kl6Q5{npZ zjaE3F1-oCG;MoR9w#we7i#X7A42dZhf`a~R%LgBQ$mgH^m>++VdGh21!?2=vmTnN- zq@+a@bBw&bzr*!CKmO@Y`NhwF!oz;YZvQ)`=}rdQ+M%^$)%SGkL8wZ~;Q9e$lo-h> zDCMDK#>1X*8R`}v--9GK?=(>xPn#5pGzVpmDt5|m=U1n#FjIUO)@Pns7}?&vQ&sc9Magn1OQ zj2R$<(}uHQMXwXyb-1d zyLZ3i<-1puVkGG`k1XAqzTe`KA?FQ=PKXrRp|nFGz0^e+F^MmMZk#}Al5_CEhF`p} z{M-NXhkWtulB+j~+jLG{qzkd1?$B74lu<=vRVCLM>P*iOft|2c&UU-|cc`2Q&U4@H zVr2=$ni)&M7Q@Yb;rIXi9qOk8&p-Nz#lX!!e?tfX)m090;dZxUI6vouCl^e{@%7aM zF(=N?wwzzCQ95B&=3?-ytt4>D>7Zz&0t7ARij)XNzJB96*4~P-HC0y5v)ZgkRnp)q zKQIgfy_c#6XBx&-TeMN6=A;xdh6vO#%PGC8!fMs?{DWucJkeXn;5yDPFR>SA_;H8# zo*Jvv$#sf;ZBJyVuoR~I873iQ*nUM0Gpc4(mb;uIGffk3uWlH}8Nc09Qehbs*w_hZNsXTlUP|(LX{$e;4HHs=LZ4??K-S+9Cmw3Sa4Rc)X4SWo~!G( zJav`t?+dT5Z@9g?;rpv=cCQty(PFz5FV>n*KYGFP-8Fyz_h0hsfA|&u#b5jc2z>c} z{S|-pzyF{7;-~-pv30u%WpXDhF`<#Q3Dx>YOXe6Tt}V@2ib5-k(FJ2WwwFC$eEws8 z_~~aX^?Tge6INAd3%xPK8W}_6uq-&OSeby%h19AWjL~A!T4sW&^qt)8b$b?UBgF_R zyfS!8-#bbU)GTMrqFY*0i^=y$v_oq#3u#TMnH&Q9J=EG$jP!YiIWaC*eEsr!4zqOe zrld$plN$MM3{_@JDM`>IOBGaZLwz~3i2yszGioJ0(y<}IsX`2+7OW{`Wh5z|VHy{_ zQmj@h&evO(EY&w&85Y~t%v2yDqLCjk+KBk8HP(m``sAy9kC0`IVDqifP>q<}bYVQ~ z7^jJ@ALJTe6dFtK*9^mkY&rs&Kt)@R(Uy=By3<@f-;zDJVa4s;tq^fYq^MBElS{!_ zMK?%rV(12$2gZ@ze$Vf|eM#B9<=_15Q+!u=eSah5n$?!QLQ>|8}amLzBkI?y+vzz&O%CP zl_k>E#Q@ln9)k2gow%!ol7(M$ogWGZuF=^OtJMR zx4Ds0{83&h7m5)Yoo=tQv0|p`x?bo4sbY&_Fw(&t!%QtjObt0V4iy+B6=T}8*+siU zrwrDv@OFjrmz;n21^@Mb``-}`5B%NV{f2qkF^30c4EvCw%;+2manD0c+}-SnF=Kot zlor0r1)_+PDP@!`EO{oVC?o)2y}9J#{5fOtq!=kBVV$9BSgJCyrsi#)?2;k3gVgHalbY6LeiEZ1XQN}GSBELAQwM|3HaITEJ;YY#f3jm+x3b{N$} zVFmLnlP9YjOVf&qYNnlPn}`@OI^j*mIfccdd`Bvgm;zd9R`#5lkgD8f8qYaMWwG-@ z|H_raIB~-*i`y|+i#?h?YLPlXZAhiEfmPSB9R}8eC&t8{ zd**3k9QRCzJ@YgYmW2|dkeXsTc7HbPzdF$qDlq5tNEO!a*&MZQ1Ur4Aiy9+AR@-Pl zS_z3URfx+>N*T04Td8uUYEn$-qA6Kwo}`*2lf%d|jq+L^&vr*+Wqk}^)ml$A1jisz zN)otJTBEvFT~eTy+}3(c%&-(tu50F!%#tN2V0FP6jdzxm4Ra7fM6Q~m3>ipO&I2;P zh%&>@h1duTM}o((d;WbQZBDHUr&SAv5YP&8w9SZFn%O}!>0^PO!I@0d@wBg{QqWqn z&zZwv;%s$BXF6FMmKjwOI)YaQRiy@VV;uc@#jxtB&Jki^unKYztt-u_F!9OL3(h|N zfZgLX6By5Wzmm=P<+tb6DiFMk3{icA6gi!cjbX)z69IVu9jibO>t_hhMgO*w*+KhL{P66Ok4z9%Y&(mlRglfptQ zS^{X7mzIGJx)7uq}gw+X? zHKmD%DL0&Bt#YVPReDL3r5Mwas4d-E3c7VYf*?MWmK0<;l8Ke{iz%7|lX5zAzSmBC zYHik~HsPZ68aJV-luSyHRZGb?1axUbu#%;ZOWbYx)HzeD5|>o_aXtz(Ja%&`C6^0( zOe5u%D$^RPouuuuh?z;NCRQ1Y?eV>47*@Qw-t#c;cvj)rW`pak3`#4<7eb5!PN7$x z92~ocg~J$GYlmGjrewzqUO|N{YPSGb48h^ z(UmHFs0CPJW-*RtL7@ff6l;!wzDk*Au3nsmjaSQX;4XdPA5;cK5e@_50uR z>SsUW2fz4pN$RE+!?hYb^!Y$^FA1?DZn_QGj+d`eMP6x5Gq6OKct_I}CN-Q3HKym0!H z+WH`eHvY)uaHIxs+#S>r!~giYNzI~uOj0)y{)qa2#OS{-?3VYc0cxv2c%P2&_?pQ6 z(J;uho)B0fm}qpP1H_aGu@aMGXoiF%QnJ=c?=)*K7eCPSUB`AakYnLtH*z^_`Qo!5 zvUzqvN||9jkW(g%6KE$Sft)bbNzz6Y8Dg~8akE zi}Oxs76nXII4dJX>ny(SAXSz$3tlU0j526zoACvye9>|-R|Qfmj3J;@M%W*6nUU0~ z9FkC4gb(AXrjvcL2x1E*UFLHLq?jNVoG}cm0oQkgd8U%FS_Do>b+=2)={Yt>561svEo?H}BSClQ(BA7Ufre-TBa9zO#&<5`cMrG3BKsX$zy(86y8Zu!H?V=p; zC9&bH)Jo9>V;gb57HW+$fXq_wBLYgsDd^gOPZ=2oO${fn zbH$X5Ddv?J4}A3NZ~60bPyb;hhQxZc;oi$2$XGGiwZEBI5+Meu%(50^3t3m` zw#*4r3n4`@vjjmo>eBZ8s+mG$JVdE_sFk20Q!yn`IS#i^8b6i7Zd~~G_Li%=9S>9D z5Hne6O#eWCec;VL;j5wVR-A1%n9j<&u4FBqLSQKcJ#C zkkGVsSm&|EiDc0mTXuSO)1icm~upu@eY*nO))6gQm|REI8ClIjuDe9%680lLwA;Vaj~LmPf%OD z->_0MUQLW4@paxa?nZ9zW)7ogE->c@l1gY5iK<6)fpO>ZoYW$w2W6-#Q*A-1o}voN z^uTuX7&I#ue(-~5{5SvkkNCI$)xW~b6aUZscSx&!K}A6bSL28(31VI;l1iv1(t;*c z#gr2BVdP<&*e?r9tdw#jad>L+c%#WRb6X>2x1)UhnxC63&$mz5e?0T;%Wrwu@6lz2 z-Xtz#;LI6*c(&#tB-AwV?CCk{wP&0jNb^AkJ~?BomAj;pAxKq*SSuk{mQ*RRV#)N@ zi*ep6j0JBTN@-HesHy~Y+OI*0anY5k=VS=I;j05d7Oc8@%Z+*YQ zInRDKV#`A_=|nU_r0Kd2-L1LIhP!tUXrt+R%``^#y8}z=s3f$hcxM_hP0-(jC^MCo zc8$j*Q!`(SfmXG1WTRF!Tz>0dX!PQGGCgV~d%S8jXk>*^4re=-DhV@hEvASdI(+;A zXTSNLsTN*c-|+J8HLpgvx_-y2@4qEXJGN)f_)mWNClqbi)Oi&3yZ`_o07*naR6T$C z*#|sZU-Iw&_DlZP|K)$i?f;xlKD^-f-~JP+W_C9>gm-V4Q-@!z$t5tXyxgs-^dh4w z%1YW@6rpyv(?$liUduVJ4cMPtoO5}$W$@Q%t4LMj`W2?pR+b!yWI{FQP|&ubbitH@ z&I%pt$*2TtoO4p!lPe(vQWPVXMMF&(<>XmwC}nG$^zycQCK#;uLeGc`hv~rKutTF5 zM;ODsq=!PH&NCT!`R3#s)L8&z?tlJkU$9$ho0(^LbwC`_>u zzG0*oMb;hTEc3AQRmGV^jfTEHW#}#6n=AIY5R>e--CCioMO)8$d(Qn_8PkIHQf1<_ zCC7yCJf;`%&s$I5^-Xki%yVIxMy%18&Qgt`EpaYXrHQ4`n1x-l9^Hz zN@)>fw+K%1eOYTyeapkpGYlOmXAaXu%!xS$LKIwhEfR=QH4{?{W;G+wv39Q=d}MY| zq_P23#k`{Bd|6dPGC#u986rn7y1%~9+gWv&CrgY(@-*F4LI!}X`Wte`S5JZpfa#$|L%Lf{q5iL)mQ(-{r$I0 z^NuO>lsaHapwpR{d&Y4`wH80DCDEM&g~(wUp%gkImrvI0$)s*(c;-3(=$vQU4&zo# z@z@6*sc9rR1J=_!FXV>f?C4l`tCWbAO3vFSyft!;h(*4)5Erg)Zh7_kJJx50!*q}{ zdlO4F6_XZHtCdPhOMNUI)vgJupsd`%qw*93a}Ep!O4vc^$W}r#Nyz};IWIG?MjbUq z(Ch6Gig7*K^yFelv0^(-t_q6??v^TqG86_Q>wYRMQNl_gfs5)y{d8JGm3e%wS?F#_ z)`DCs#yCpJ!~ii?bWB)nNVCj-OK<3^#uvkw7Vh>3W*xcMp2;)lrR%<^LT$a$)^}K2 zL6=6Z5Yiw?(Eg<*WpfdNi!%;7EuSS_$pwnmWGyBa8V!Y_q>8N2_OStN3^8O%7ITSK zzMV6YtdS;3A9K;nQJ$eCih)5YU@hYqc*i}{{=n9I2JfY6pcZU_DYS58t9l%BVOX6} zRnK~LM!z|uH;&#aY8)942j0DT$vE%WS21X$kU8u}bk!_N#G8)esYj{f1TePxF_{;Z zD$gnJ=}{-Ta1)d{uBS?jI!0@ZX+Kt)MH!Se(*2#Hpu=@9uv4z5$!N}566HnotU5L-Dyo^Tgg=@pq@r;pgY8H$5<;fr zfKnB272X<5HY~BS8xvE^5(>~-Qt@HrVVCeZp+X>s$h_o6)=Z5!uzIBTo;u;)pU@(8 z6Gdv#j1pZ>eviWXt{EGvJe$aDJGWZvu}D>q(KGhyHFxubHOw3LV=Uvi;CwFzxg6=dB^5;o3Z*m71*wPZ90#4G?nsMSyiXRQTo z32}rHpe8XjTf-P4xjK$xq`Tb8tTtt8jJUdDQh~e!yB+Xsfi*R04$~+#9l0adEdh_p zdk7Dl4jH;@!hR7-lrF?Pqn!b*S*_P>FRmH3R~YMCsLaaqIz>v9ef&{rl%AOmGdV{g zz&kI`MCB+&5i`t7Vwn~~&8TA0S?;@2n28k3Fz{r1h0=~`PSgZiIZB2x%;@Ba5v=Lx zw4%2wYzVS34_7Fc2+^dggQ}rHcea2WSd2J;na)_jmu*PDwB*|)(lwd9L=}5PD0W{Y24E-9q z#QqSe_eX3N2U^!_!3-OPX|8ZfW!-mhb%`^UatipY1aDs|t~z|_pm<94DCM!fM>$Wi zP8`hEN#CeaEV&ZW!eNdar-f+>)C|4vSoIyN^#<)7%2{zXW~o4M#$mjn>jpuvDkC)l z#(?ipwV<+e>WEm{#1i#h98+iUE@!z0N^O+@;vVzPN;jDkthRISk; zh(Rp{psNMlF~^A|X{zp7vJvcZOX-%_aO=J!hZCkwbT-quLJAY(actcvk)pw-fsj1L zDwLC9kgYSiOyrPo#^Ic1(;K$KnsukC@jysNzVq4V{NN9MK(~4VY9oB!Dv4cjL(Agt zyr=Jb;xds_B&DTwj3{!+oaRW*m5`Kl)wM2h*HC0tYe8jd>7#L4`bmebV6QY!Y{&ob z7k|q0mFHjl;ZKNm#sB=j{_ni|$H)&qd4V=goaC+{NpdBp`e>TTC9%XnNDIp{F^(r< z3|wxmxw(4Adb?v-Z?WAV>0YBn(AMe^kB@>-3WsTC$%Xwm^J3NWN8kIL#K1mz%5Ka3 z{FYz*-M{7V%fF&qMsl@`apE}LlEZ>2P8`|Ygwh=f_jtfm6xoe&p%@J-`BUR59r+= zp!7dv)<5#Z-uDnaR=ab4KW&4lWcVokq=lTN-}JG|s?jgb9rjA8-$h}5AljeB`bXnU z`@8y#8h(5Zw})?0ZB57@yx!VO3h&+OwH@Hj>Hb>JJy4Hd!TTP~_kYl`^X*$v=kG%` zj8W@%J!AXZ+VI$}cdqhkZsGGYs_`uUS?l>ZeV|%@k}=I4+o=Dk(F#;^9&5l@gX^SY z(zkkx^Y^39seV=PRjz_78h%bi^e#v0GlBZSM;T+6fg#JA-O#zqJ%_EYGfDb`l%Rguv!qKDHlSFE!6sViG&@o__Dvt2;J*w)X*+i*^C2QAd&W)N-D(^iqg{&2|?K>V#AKE}JLMi}a=5+jo`w~(8KtXfSZ}{x# zGd}z189_y+B{$b|L#kou`X28D$tR9)^GKk0?88Jg%(B85N2(E3D+Vp3f>K#(Aeo8H z<_6X2R+EtAV5W8J9C!474H%AbB4mXccJ$ss?>U7N7#S*NtqDblNmVstqOp{iNl^rN zt3BR09D<_PEsb}~6+djbeEuN}j#97~pT zVO5lDxDSbaP^7W~W0|Vq{se2+aW!nX+`M2Z3y0%tmeYY;b6dlNuybBQmJpdjKq*Z~ zm6);!FiBE-7KbTT=9{(RFnG72kF43y<(cVt zPtgk(*Bu(i!-K5d>aw8xj5iLiHjp(XO!(fR-Gp(8q3bY3M7EeCATsx}Y@ZFD|vff@WB}Xk9lN|fIJ@c|BtDf826JLLO$HT*(lnq69 z=%ItIBO1@5*A%^yJ#{VA>RQ)|Af&sR2sO!bmIG@8z0Ul}%?tkgvlo2#YQ^o_1D%O% zdg!_}8}t6`Y)EpB#b6pmN*776S`@v~c&Az2Y`A}0xb&|%0ln@>zH(d=B`=^AV=1Ii z=no64uiosmw`x( zk^OOIoFljI-tq0-#O|4)9~ArjJ5m@}R7Q2+jG-S^_+cOc<1{jjCowmj&Be|#tX3H7 zNGUK3hT-~(WrBx?JMmOd+>O{HtN%#tH+-DADya9a4qufOEstDo^Y_0(j@rQ_+7 z=X~_(XZ-l{?~&BNl~4Tfcc1Zd_fPzv|JUEL>UZ4Sd>`W+@Aigw$HLcNeZ|ewuR(1v z_MR9|?A9He7OEr9zFk5Cv;(m$nBj#8_BDAS9uM7;R7~2?|%s9jq~Jp=jOPy~jC8 zy~b(b?#(T)-@apinm}8IazSLKnuS6lmO==cF=#3srth#dvYbwM9Lj1**0(dzwmJ%$ z5HndBw09!B)gskUQBh+dYY}gaaVR6TkU2)CSn#g2bEk;ZORAj4K&~w`QwlN59;sB- zh+qkY7D2pqD3>NO=aeD6r;}Oh&?dC~yTN$JuvzosJ0Ef5cKqz;|0pTGn&tf8%!0;R ztTog!6Xpe@W$$VXQsGeoWLdkGIgn$*Iz`_(at@@}YI|yPjTudv7Gj9pzq#lB?twWa zN@wW$4vb=&N1VxM1KvAg)k5m5< z%d@>GM^uhd9R!-OsnGj#O{3J;IU{REZw$UxY%(mZE+}V3GMZ@3BhBdCZ(mCVqeM_@ zL;;P^9j$jFDCI1srji+qVz8S1;l%NHY66>3O^j9_G#-AC*hhp>=N!&C0t%d`q(WU1 zYMwcM`z2rg^xyM;{4ZZoW8%%1ula|+`M-Jh=2xVYnadXAuBc_9mLrOekOI2vF}7zq zjpSt{6FDt2s)ARAmmfW0Z{UF8*{7fI>E}OS8zz#L^HmOonv)=mwIYYeqGqWdv^)yB zoWmb2rA*N+kf+|y$&@10g3+>GjnhbVGfOJWNfNYHY2p&dtWjDja5a`(4XGLwnNAx_ zG%Pida-gcp&Ux0_vy@EGMFMwCm$1&0NT^&{^*zpLq3Lu341=5rs?_mm>xJ@HEuls+ z$TaPsRx#VE+Pdkp7!riS>zcVu$O2*vzd z$hDxg!w#NQz{b!_K~?5LwThuG7jh6b%~ zrYf_&DN0qbwi)9!9!083DsY;SDyxcZ=KEBHFfuQR7y~&Ma&5EwbKOY&prW&p*ErK) zB}v(y$!9a>c`eqY$LdalbCz5SsVH(WsM7R^STVJ*gu*cdLTKv_ic||J7D?Erh>npE z5>v>;3dvYB$^5#Ib0Opf;@RjXpQ%y`6Gsv5t;OoLt{E+7U?p@0 zl)`o$b4t9vzmux#Bx|r63{za7D5|o|HDXQ*yAx`S)G{c>)5N&%?IfcxO=9nT% zXQB$6YUFNN=x~e*oPkqLY|4%}o!G}nT^aIr#WYUbY_7Q2Ueeh$>(z!hofzj6(-=AI zCl1FG#}GJ96_XA1{zwcRowjr>yoAj>7|#*;USqV_V981G*+(yNuH*0j{ugXlnzS6y z^#DaP+Z}P|SkxYCdsfM!PMVlBlY)do9YzTr)rI@}H+0V9RmY*6IMkUz_jF-kUfxk^ z#ruv*7K3r?TQ7~GQF6t(A_N_;B*fclRzh*|=T*zTPa6b^Rx#F7l+2Stt}I2Pve5f# zkr0(G3cp&>8_VwcC7s(zy;aWiy}@>l`)T4(J+DuJJRh-fq@b9%W5@;Hd3o(IF^3bj z^IWsy;>xk@)?8j(w$Ne4w17fYo+&4G7Z=3Q^7X6VG9K?(?bc2Ev@A>Fq-JtTJRFYf zj|Xy3msh-e`V7RqHTSk0OfPSZxqq&!UVZQH#-(G&j_9 zk-ijV9uulcsmIJCyvIe>CjQd|y7zk-5p8r;?(Xy0Be4HkTWUb5!T4+F`AwA1s@-XE&@M2bLW1o#Ar3roVn7uQkbH zDv0mqpv-d7dFSZ*ffN^(D1!j&JQm0?Q4-RjmkJ?fI`8SUA(y~XGN~*;!qy7L5nStd znHAk3vo#&73(pV#;K%Gf{Sj~O_8i@oFl@2A8&>^Fu6v^hwY7Zb3bA5}xaG)^-YRkp ztfmu`%+29b92Fue)3~{cSTie zM(_FQuYbzdzxr$La^*BiEyVG-kc)JGRAsQ*OY*I%$F%hs>8Q?5ZGA>$%K`oa{k(Ok zmq$a;IgO{P{#}8%4Mxuv%S!8fuT79X_v?O;LjS!Q^vAw;c|O+1iu$u)T_08V`eEJn z*_H9ngmg(RK4!ifPDVYp<37IJc0UP4Jz7VTQs~^!_4<7c z!5PJ0+IOH4)6!!KROz;6<@W#Pcl$ff@1gnlTfw5B*;=ap+^MWu#emk5#KtOmEx6*^ zHdV5b2WJgk@90;(h+|8}l**H*H+=Q>fmdICLr0<7BF@AT@SUR{dgic@!-94ev7og& z2PH#TW#rEl;FX9(IVP;NXjLFaO3Cc+@0s_NIY-KQ@K8nAOu3R(!7d72qYORI-G;_t zx*q3eoL$g1$*?mQAx$K~mD?uPV=QQcwFX@+R=ZY(lBwo=ot|q01izbkT}#=;)l0WlvkQam-P(KQ0Zq zH}K|^*na&DAFl>Bn;WWGGo`}aVdQT4HSg~3SeDG2+XFFw%_+Sl8%Iq?rtyJd*F21Y zcMp3?TG(vX{P6of;>Vx=h!;1{`TVobxq9}5-+uW|9A5vHAAS6skH7O7Gd;g}^$yp~ z{FI5)@x(Yy?ADImx}#)RiXvx)N((s4GA}$FCstZ|(4{g&4zRyts`-$VK}B0F-SAmb z(fwwFwjJNzzv1@y4cqdBi~b4J!c1(}3sg0tv`1^(s7HojyQ5#N(AH7I$T%H{rQy$= zC)G-fiJU}m?Y$SVwnlUbSe5Cl!?!NEqzXYp%n7G5FR!2Q?B*Cx#J-vrfit2rQpb^)j}!FHg{~SVUCpm;y@k0X@-OEF;|$Tg=ri~W272`szDAPHDgV| z5>R?-NeoGs7^CPqjkg7FwCtyB#;J-55qi+OUV8g{mG864fZ~kl)vBVj@H$c!fJ;6A_EJVr`7uR`v80eWP7l1gJM!w?0l(?EItHrl*zBG%sDkeXy3k?QJD$IM&PO*} zhSfFIh@qmC`q=krw3uO1PRw&+Nedw*`p(ihi&37W6jm!zg)v5yDsetg2yUu=1MJV0KEDUhocqJhrsR3-i1|8#pGcb=EMuPAY&I)$+LD)>aIq$%`R3a_ zVGd9tF+_R=+SO*vv9d=j8Sg|ad-Luc_xJB`@g(UQ+p&~FES0LI&)gVA@2z0t$AyRe ziPzu0;^F?5HP8)yo;aeEkjI-cEde`4X++ z?b~m7I2nvl>~@~(wda$MpW*tB<6-7-JhXjqCd?BtOt@i-?K_6`3Tq7BD>}1gIvf}e zBPA!415zd}nG)pro}(B~hSdsZ4LQo5t~PV64Ec5IIXzRaK1|X#DpoD%^Le01YBlI0 z1RB6sNl;fM#FZWe%R(8Cgu8bf4+l0j=67BBh3fAxL7|HV)E{KtRD-~Ri*;SdwuX2p+Q^pmk-t-9X8#22W=c3Q3P0mp#wF{{vp-p2K)z zm1cT1f!%PdJ)JVxqVPIPcXScsU&>L?=f;rY4BnuX!PtRZ5=tdjy<_PMIUTV|ljZ}9 z1>?Q!V@ncqBa+o>Q|Rij*GN+ZU0Xs1ak5ppKXFfEBCMtNo` zNsvh!T5VJtC26!MH6ApgLMuej}yho*@W%2S_o=u(cR4?bTB`M+< z-y7Yl782e0jDc+^YOW}D`#?vgeju5!+MYL9aD^?TKV|J6Tbhw@A30r z{ES!MzLK=Fk^UXk(&aHF>ANT_xDW4i6v$F)rMic;!rYaS@0JtdgmU+n{u8JXf0x2GXZ*|r3y0&TGm_5$1h*- zFaPwEBapZJ7Vzj2~I{Mz@oMBnSK)Ty)*={$)c@{&F z(>QN9?)Qw-h_(jbd3@i~`3|LEQbB}DCB4krJ5um+U6h1I;Fsu@w=PML-&8CGOA|De zP++`~`crE>F~E|dq(n_6mI75QMyU*|o2lq3MgUU_frwKIw8Q!pV^I9N|M2(N!SHYX;@`0O_*?$NFMdh*=1bOo zz?Kyw3S|t=^%T9Ps|jTtCHI`d%rqzb$`I_pbjqlhNpBZUD#7}a?Zsz&_QOA;-hIRE zAyCsTak*nInXCrpkWr?h)1hJETbH*{WECV4uZqkoy(29XMi;EofG0Z0CUAMt@dw}k zlplTm1;IS!;`&Q5c521S%S?NT24g`Pg;pZ^<`&W!l!dHiwmZ%2j|Wz4CG=DZtJMnU zoz#^ebCX;YwL~l#(n2i@{a_%DpgWY4GtR|Ii)p+Kx2=8zF-h%a>ok|= zQKp7OEt%xSkmPNLYd=(zT+0eg7^NER9DGVC5sd6Dz1EUwHIjn&Hq&=KO3Uvu#yr-f zQXr*5T7(#6r@*`Jq|4+K&JXm) zumiu@qi-gybkN`Lnpp|K# zhpb~+*T zQIslSSA}%m+ZH5@sgZt*a8FWFgp?Skg3S?~GBFiSsd6luQ5m8(Ldk5T*K>%G7QBf$ zwMtUalVlA`c<&j84(~gP1&gM$p59r!^LQ&^QyOsrt%Od6VmXa0<4Dztv}ES8W6r;1@1yphQ@i9?A}7uIc(rzfDYb%f8>Sx*FJq$kmG@bKNk7h!3HO(Td;f zAMpAB6x^bD9ZyWFp84*c!*+vR59|kzcZzvBaN56PKc6@S&6qoiu0Y5BWQnEY+0$#> z%JIo(AJKWo!!i)&JIZn*%mJ$n|Kd;ogwHlnp6uh zS5lR0Z`H4G^jO_fvc_sp2y#7+b;elBlqAH~IiUgM3`5_eQG`;NQi86h+Mc3483VDh z$@gem`Q<W`Gth^eNPS<=RI5J@jHd+5T7t}f}ZblPK3JnWB*$2YvSg)vWb7A|%JFP`0C zLzaQFnY9%3?!*kO6F`cP2&U?W*RV{-->*;;RkS*R=I&JCuj+8Q|CGz%g z0%N$;8@kr{DtK!V8oYv)@5sh8J4c)s!Zfx1FWG?BxS&)*8;!xBVg?d9D`FAk>2y3Y z-rh2eBTFn4Z5jNE>(!23zn0!Lr$}L;1nE{!t*cxmSW?>%-yO(d;`QsdY=)lgs>f>S zWl(MRt(-B-mD`+Vv9f&SUKW*`w+T-BlP0=t!?D zHYEy$v1G=uV06c}AK0kM%8Yn3v9cSU_ZvRGeoEGk*ZUL2boA%gH6@Oq z5~`eQ6*Q(=y4q8_HS=oVI6kn9fjJ7kTvdhFntnUbt=Cd7Q47)nBDn3t92vujFitEn z5~2*K7X!h9%Y{Qc^5n%+Ru?y59LqGb?K{-pNnuGrtgJRayDj_GZCQm_m&TTt_kkv zPxtqQy+>P?dgR`}PXwrDoOt}(su?wupy$sxstIV|+m zbGd!W<;t?Xx~5QAO$)d0?)lmAYd(8fF@D98k?$lTXx4pTygl;#*(Lw-kAK3|%>{iw z&^yO^yCG@C^7Yr0euKTZg58$RZCIa1Hoaqe^^{XkgcMo#n)P5Movti8FIcre6){~nL#d>Yor6Nhmkj|bwzYjQ34UY?mLPRvyf*Q?xccqL2vJyqr~ zbGIbwx-xWw>v8LQ>q>Q^1syK@N=#uQBJ`*s*i zF<9fMcytM@J4xIx%gj;=OID0g1`^P4%|=s{Cge(pS%&s`p|?)bsm9^8)GU>vm={Gg zuHCnql&jzmlb};`hJ;4dK&go-RzhyX5-q0U1`-r9gI%%nmYbnt@Q`cAlGdDZVpO2I zL6X2ZQ_^|8&M1|nvY_T>T7haax>`&%ZNOqteJ@zhR0uJUQG!A)6&(t7PAo>SfMl>$ zp{rcSNhu-@G2WLvD)# ztaq2(9)+k8W5ydJ*NoO!<#D!`;j#C4=WyPz-Cl8VafKQMN@Klat+yy=(cQqa7awtb zeZ_Wl$M?SbJ+4=ulVai*zrEx6C!g`J|GWQ^t}eX#@>_JxTz*qS291k z+VI_{o{u(_-2mt;D9;?QF|zH}C}UZSMR|`2au0^0i7I2L*eXVlh$5GW>pD5BY_HJW zhN&8y>qt6L)l9XTw2auSSXo6V@M@ai{s>O9yI8Znykrv;$GZonC3CwJ)K2ly%CYTM z49?)S7$208T_{&LIdbjxqPykm1Ua&ghK@s(x1rIZ6-{pyx?Z{9M7h5O@y+xr7ox3{>h){Jair@VmCWIA$9lhE+ifxHfz4`Qy&Bj=kDVPU%6e#& z1`07I<|(kufxcT4$t=sn>9j}tK?Grarsk+eGS``|K|x4J@13>MPg!fTs8%wj(L?WF(Z9(Iwb}JL#r#Yeu7%RWY>E>{qzg|=!ai0z54}9Eo`l4 z922#OnQhe#{P;&d;N^Edhv7N9%||@EdW9;HFTVH*3@fb3++1x5bxWiW!o(7TblFcc z^KnKQD_ynIk(_4UzO5YY?{FwqPBBmSben z-y6e{^rP@>l=QD#W9Xfw>pF(M)y1`{0h24nou=0R*J6ps-zda&971sH0`n^a*?6G%*W(=)m22v*aTW#6<*8UJC<{2 zw$N{;l7-TU7c!03kA2)4g)<88I;;_-cuc98)#PNJveeSlBK_(`K~-nbUof#%iP)p7 z!YYN;hN>J@NnO>FA*G0~m3~-bJ5Pv#ahh1KF8T96|5N_qXaB%&zy1o0Mj62jE3HX6 zb3C1(X2G?u26SGiQ^a>YyUPovIrH}Zp1jW}V<@T;a-u5Dk|L(F#GJ6TpvX+i$lb#| zr}>2IS14`iyAD)O=Pl$y$wCH`YK9i_sUl9q5LlK)=oUIj8eBV!>!{X~RRt`IIBAJ1hDx0C>=4!<$h#^BpZH*RJMc6sdGPSlM>@1O-GtN1baguC2n=OnH?0cJ& z>ULH(EpxNotk~^#6wiWDXNbS5DK&!^1t+IkwvkyUPn)C)fWH z0x={~h@8feFwb~nu^qA}sX$%GBx(_ghc`yhsrx^M*&WTZ@u*kX?y$qU5MU^D;9<3=UF-;SvIWSF&v}xLoc!n*QM!BlU zZ>l0@)fUVs=b1##ggJ5d_8qTx1C|>G112kc(pV*i%xXGnx1#nlInEeUp(X~U=sG9+ zwpxX=Y!-4%sM@nqD@jid9ih|Ii)++o;D>+wpYz2}{)8ug5c%mZf63vu->_DhWpzPP zPQqYmVZyW0ncfRY^vzVUM)UO91&3*7tPgZuPp}175-T-uV^_HCCDeo)zhOMwu}pi6 zE-2SSTqvrdokf+}Qn&SyCRtl$Q>_`DlJvkg!)?wHT{63V;Ms-a#nlBr`0l5?y3?$k z{ma>uGf%eY%>&L z{39aX7$N^{E-!ia%MIWA^b>gUl7IYnf5n%-_$m2xk6~i3*NkCi$&pN}ZEB&R(!wsK zGf2*I57d}YxzKlraX@=|uhD4wEJ48-f>alk$~aA8xH5uht_o~XkYrA)tD@6} zYOKth&ZGvdz({>yLJ?v@O*4mi;YG@*TqU_{dpg&VLnbFdr~8!?f<={?S!+uomdu>8 zRI=tM^VBLLRE{FDDSZwU8B|CpBE9oiYfxBfDVTDep$V18DUIz1ypw&NR|-$XYoRQj zNhih`oYkxbgV7deJHj}Vm1TaIIgLkBisTZRmkF&6Q%D%?$wjKRVvH2kQA?5QPSd`7 zsbn@STvtnKK`l*DN$91m*798Wsv6%ny4u#UFlR}R|C2}nsPA3YI zIJcdX86R2*F^?f8A$p{oiE|__k>i{QQ79<6K+IB`S6gj_%noU!)r_fN&-2)vNX7~I zvuSJR+UPT$-nOqNR|z$gB2_rDHCC%@`S6)9m^Qd zC2@7N!8y2IZRqtg29!h@tVzanI0qbWV}e z!Zgps3e|V?%3+I=#Ct1Fji-@edyTJ}k1ucdB0193L!CMC4*O!qU;K~%ldPw}fA->r zfBV0|>GWg%?zdm@Dm?HoA0TxMT=6bNk~S1$$;yzN#`Zn7+sZ7qSi)49ANFW_$>IJr zrvUn5#gZaXFA~_PwspY^rCD+Th0`*lyA93_SldHUWaS7o5ldoTBAFl+8qP2kse!g? zpk|E$Vk#V`f?xG0*2$n1Eos)$1bd10y>~61btzkA5iAWgT-&Cmo`adXHsL1AK|#}; zJTj!WXSlU6bepL4UbsIye=52q81*CmUqT3lg)CjEx#2%lV2P0!Q%kf$DbjUpvymiY zuBaU8N@383*mr~i#~9H2Bf2Oqw`){yTMD-}xlmG&#Te|SCz046BOw$Fa$xFIZT%~< z2`Y%|Pg^7D)*{`-R(rgwtc)YpN^d=#w%A%pDKgF#og>zQ>KwV@estBK5-BMTIgsMS zyW@yeiosc&kq0WeimR^oB3_-15iQlKjlraZ$^nf0Hx)uo1Ts}?oE}l?fX^G0Jrds( ze)Zd5^UI(8oEQJ!CI7|0`ZF$HzLfN=QYbRVanJp)|BmqO&*_aPR7sNdA&F3L6uB1c z|IgK%{YaLccY4pao+TnOa;eIy>S{K3kt0!}C3$4X_F%3C7{E947utX?eCG>$Fo4H~ z0m-%`+mf|$B$5&-vdQkQ?yjobVmar0t1sR!vZ{4Kup3=jRT&v^&iUT=_j{fgh|`hl z?G^nuzsKlCjwN%sOi-ZD8XZgf87IO#Gb+ty7}&)FZg+`aKxf5z9C>oN<(M;X9s=|J zo@XDu;Mq4n5|mP^kQ7R}mL4moj0vuDP&%wOXe8Bsc(|v=!st8Bwi}*3KjY&1DN(_3 znI+K}XO4SCpd-~vc8ZELCIXt6<=66XKsimxjvVEKad~;gZ-4vS{Qb1&%^_fFqIVu} zk`UH%z*NROE;N+}Z8cS8z|*(n-t9C~Yak>Eekg5P2!=-&_4njqtCC^6TNzOKWF;zFToh-9V^UZ1AA(Bd4SnGe@rvGQn`tsP0fBzMz&2!bl z1CQ<|^;NRKd$fT1__#_-QkEt^dH-upe)<-c(5ij!@9rfhD#0nDP#=?_f@9Jr&OklI z2i^@GTL$d$wG*(Ne*9aVzQ@x&Ku}Sp`T8W7P*rW$^(>SIPJyrbh}4&G*gLo5i9}Gy z(&TSylu=vGI*)dWDK9J)R^Ic}R-U@ds&i;N0G%9;b)@qeZ9D~yYd5;A<-4{l*cvIU zn2RMCL6CYQe`YQY@ZyD}fG#XCi~b@hVkm?Zn8Qp6Grj5{_9*XB+RCC^gzLqOKp=u6**9(lqgz!pVFw%Eu5=w=2f8 z6|n_8l#-;pb5@ew)5H>1;@4{wp_5avz8}1y$^$&$oR{Z^hRA>U@z?!NIlAkZb7hGU-wo1i zJ$cx3WR6GXI1_R}XL0Q<#bWRUKSA?3nh zo-lSKldx5mthHofN`w$8VZmxI3ufaf$+iG&Wr>w&M$D?GghYx5Tn%`@Dnn4F4Om+H ze+d|?abriVMTW{<#d=3ff(%_M%uB?m!teDvWsb9YOKk(?sCgDgC=a?${4eY}_pQz#r=AyrN1 zG`-gJI?FIZNdRX(T8t@JBcJ`LIPQ;@v}8stRlfV0NYzk+;%thfSsFldEm-eRDp0M1 zA<~(|i@xXSxMEy8x{E8ejF<|C*MWI?%^U)~k^zKPic*$_*e(rEd3rXGn^x6rvfGGM zA8;vQ7m?BG@IW9BDWg&!xlH_?zm)2g0 z6UvT^=P%h_Tyk-7#rAAXah7mAN_b}&_}72%U*eo$7}k9ETi@m7vulny6aVQKghiuE z^RpU5Y)_Kscg3ATwGjJa%I2Up*mSm)Yd}OS{7f<_Vm6G zb6_V;5&=g8t*d2Iac1V9z5&Lp{D~+2hSh=?(bmg#E<^wZ@50|`8R*~ zFZu1?`WJlmv%lxTD$!TzN-Y^}>br1;Q5XludCzev7&I4W1KvqfELKIBCvNw9TyTum z(>q5piUk=eoozR)S8K8|9HRJOd+X@?0c#vlE2@fgR-REpDLg+r<69qni%%XRzdF1o zSBG+)G-&%ZetnKNo)i@DK*^Qjp|_T?E(OEI03m|y=(-IXr@1~~LoI}yNMRwDi1u=3O=MD5lv*%3F)KJ2aiALK zNXG|CoF7rT-g%t0lq3WMMWtSuMiBr2AOJ~3K~(pe&Q+#dn8VEdabn)>>BEsuX{;NB zUV@=gSw^9T--}|!$RNC8s16RPa(g_o-FB?DE0$km!4)SP^J9)M$ph z$CI$7qb4Qaugk>Q2)Qnap3m6Ianj7ylNFSt1dnf^=p@U+~3|k@OMA^nC)TV z(@$Q}Z4LAK5=Y0XANk&Ie2=ex?InNz<Um%8s5ROd89f#wdqBY0efrp&f&xOH8hH=f= z0N(UGeEA6vIT9y@ZM41;76v!c>r6JDTo+E4@ssjTRRp(;66#dWkW16aKq*mXUJAlI zgS9y4rFql*x6XOS)rxVoqGUsErBXeVQzym6Zmxmd2vin$2d-{S>%=*s(iKbaqXw z?Y*ltxyWnY4;_8q;jBSvhqsYpG~RBADYJUjKZX&67*`r+1428~WI%jarLz_{pF)8x`?|L$dx-5drFEU@X z#>#sZOX&kV?0ohrk={pFI^15IC6%ypRquS9=Jj;Gg&2yPSm!bGgVQFV@Z`| zE~0cO0qY9rjK^Y15vq#Lg0IzvpcLUmYb^?Mnv18x?VB4pgNKDP@9DEK%Vi(h~Ce5 z@5yCBX+>`=OA4T1G&8;JF`XgWh#e9tD)c^~_Y==AH@M4l?(QE@Qy}c_c=+U(-2B5& ziN`(D@s5X^FHl7^eK`lAJRplhqur^dAxp$oTsW0%Y@=72D!6&8z#-?3b?5M=V2CV- znfdTQ41wu5^TnID+`oOztGgR=!#I~J3+deGmpWHUOx!Os-IH^E=Z8OJ^WvNQ-Cz7Q zfA&{D=z zEemrqM?a2 z8nMjceT822bjFi)VyT8@kygHT7TbAwrnVO2J8YGO{hSJOlIy%vn$CDe-_x0nW1T2T zv~}9aJgXE@Y~*%LJ%+%;J}^&9BcR!)8!l32GDhfLs!CZf6>1R=bmuLmT2c;JJW6;5 zLXx21oTKkOW7lD8!PU}&EpknA-pjHVuz4XbfqmNZaDPM_C;E<59;szA=Y?gdD79dW zeJpvzTw57Ov^2GX)}kU7wU3ZBwNjj{4x*hb?LN^;O+|=)xgqbZ))d{!qbj!oL2ZOh zjj@$#3b`!OhHnbS6N29vp}Uwz!hUH4oI;L?B?RUrFwgR{P8A7z7eV5e{BGK*bqf_1 zITx4IN?}!_Ymzx!BT1ta$~ug(Xzj=-=A1}M9O*Fz(FCb*dJz0?=YR_ z>iV3`cBD&Buny*Gv4evQU6FusFcmdwI^T2I8J=#hS!(7tyK8KTgxfppyin?fd099t zia8tzVZtiW&D9j?N`)^UxPN1qZ|`s+67wxzez9<5B|q2W9<$%$jz_wArp8EGW(EqX zyrZ#}P+7>-oSEl|r5o6XfHe}9O-YKFxytiWYQ&+w6BXO-n_CDoUw-l_zq~wW6>cfT z)1Pg*P4{f~&)NRz|I7!^KcXvvpZ(}>_)lNFVTy*2=geL7gnU6<6wVq#2)I&kS+2=e zD@JeFXoIbap&#kbueiyYfBN*6o2YqxFnrKoQ|H3N&EL`)$EZMs7OZxTh0IhlswtAQ zZb}C)iX3dE#zL|yVoEscu)d?H+BATgT4jD1;*n`8m`-N0nS~`4LRQpu#ceyj^ZYmX z{ttghRRd7io;~Gk^91KCm)9>SL6Ll6zrO{S*=|Rko_9PO*UVjq%ZhuylR26)TwFY1 z>?I&yQ7j>#)Ie2^ITy6iOu4XCLDVI^V|%{FYbYU;LLoJskk)XvJ;ND;_nv+jS#p&* zLMo#A9!7F19QOyg9%$WEo-uFRY{zgSPNrFshRf`u(W#D%oIGnkyMw~XeBO}oHJQhXzlYt zC6!DJfe^&?RN9hXX$1*V3NTNEVkx;|fvzerp4>y2WSF2@({QU-Xb064iq;Jio3T~l z&}@9qeps=@1GgnmO~=MdYg5-sNr6hcaVEik4*fvq2D}}y%S>02xYbi2)y%O@xS~j* zkgH?aO}M4Q*$%YfXvGbtv?CiuF-9iAMjSv?5Z8QwNhBCFhw*DvdC{?1EpnQ z?|bpSObf>_6O*Lwlxhz+3T$rv#3BQ|WsW?(ddk;6`Yz|2YuE=&swi)X3c?%+Av1Jq z)}@b3S#J z74o^SK*`c8IpLG2fy%!2lnp#wQaBn`&_I}dZ%CNdU1XnV;U60A4NRio$IG#_#s#EJh zt3p*pbS2s_*p7`cLJ3h8JB+Nn;=`*al<$0->*rs`jptA_EiQ#Kj%i+)mV(=OsN#&T zF`;V0^d93q%frO&?JW=c9V>q>4OM65vzR2Nkd-X6Qx;-MEHbpVZb0jfuIq7rr1M_< zdMSz9G1o^|oiZBhEPm*v<<*8!Ct8Dd7F}idChreX)o{X^r9>nX=LsDbdf&CiQYFPi z2!Rl0vVwWuvp?K1&j)hzY*v#cbdzgazDT-QYuf@H;;EwuA%WI}Ix#M0<77x)rO|`7 zgq+x=OlClPLoya>Pge$U342Sw@vNQV>eBOKz2@m=#q$@}^uwB~?FHwXGwjeam)u%! z(h^pw;1XQ+Yql?6@+QICzkJ0%{p2Tn|2J2B^yHkwa*wK!zT5Eh{DPainfv=h*RS~S zgXh$wSc2jFe2p7sih7{$6hm+D-e7FUygL%Yf*(7kWnzvIRltpBFm5Q*Oo@Swv#feY z??;}TZ+P>!f&&$GKEYF`j#SUBQvWR-+MEoe_y=zx}rzmMpiGtrX zeDLfgKlrU5@c;bLAMxewj*mb7gw9>?+^rdh0j(9`Sg~5JWgf%I4;=fEeVqjtmm_mH zGAz(N$z8N^&z9vi$EXy8pLX|*`Gu8;zA=T1cgq097a|o0eamETJKUbzD@Y(AxnI9%T ziv_w3gOOpPah@Ed8DSJSCD+d-$>-5|QB`Qw(T`_%D>&1rB3dQ!(WS&_fF3UUz}GGv zXXhTRBK5LEDUU;QH99`{;GASTe*I=JI*0lz#!TV0=RPFXwfT(BDWm)L8LY2z> zabcbg?59B11AcQxS1nFkc@9&R6c=>wIXm03T5mbOe9H5ybM|TCW;(E0tvLVYxB2kt z6V6rxXBQVd``S0q)-!za3v|_Vwu1soN~q(3dD`QRX1iKptz(IT2lu_l=t>9^DMtE! zKvm6r+%t5J{%kA3xJA%`s3M=ISg5h!jiK|7&Pgk$Bt9FaDQ{di>ph1 z>wDkjv)gTmyw<>nr>{W{vLC<(UXin{7peG&NbM&V44u`86@pzMaC4OLH6TzeN4 z2obL`XQp!I6X%_$!V{Av(ful+cwKC8#rNn1%?g=XJ^4XTrf!lgi9T>Y6 zm#a&zHkno5kz=5!Ob9a%yB&SkVZE28Y{`H$iA}96`yFql9WfW)-XAdCIc^xqNfUCS z)0$EZ80eg(^D+QeMewUe8;ti*Bg(GHN|RM=Dxbm>Cl0|28OJMBtdtTT!!{)=9u9$J zQJg2kv4CabrX-GL!@XLu-MpmWDbAu-BkEzxkT>)=l1^-1Jf)1_-vrD8&Rx*!4K-;F zb6^*am|k&qz9yu?>zf1GzUBJaGyLT_<96iw`kHui#jE3v!*ODo_Ht(KMy4t7*=Jvn zbZF-?&m0okWCq{Ubv@IRSyE(9;y{%W@VjuZY0tl^g?CjuN7XFo|B@pqCh(;V*~B*; zQy^xC(gvkFc6-Ioe{sWluSmJ)YI8}}mceO$=LbLF+u!^qT9p>+EI2K}w45TOOpbxe z^D`mPb$KwOr`dnH)rOJ}QI(hzUp?jN^Xm02Z{OUZmOU;n+{}?(3!7_askPv&p+^0v>C%n*)ebROCByquO@u1S zC4~fNLtLma6OqB!I1boulyJ4m=$b{tVysZ_l(YiH?`cuSfw5F=g&?N1wD+`};Z6&S zniooxB8902l@`1c;>i@H$zz?tI4h2EYp_bURYWC8r}MCSF9cszc|6Ujayrwsdt1%uoT=4ftf3z~R%vvp=vrA06R$r1od5LG zpYf|tKjpYA=&}JTy29fUS+d-Bov{pECj;b~>ADV8HOJj8PAmG(J#TL1jOw(Z_m)*R(D$C+Iy&z-h62jUbF#H9_$;SeV>C%Q zC?1S_x~Nwj{g<)c+a2z#a|F^{+_R054?T7=e+Cr=;|6}M{bw5kDrqg zqs(5aV4PEGK5Z171gd31`-bErG!AkU8uhgODEHKiD0 z%A_c>shZ>&Ew;HrRS^0;`^T2yVS{W?^|8YRTWEX!C;Y3EmY+sBmg z-jZ|T%P-%s%o8hXxq0=PU#@=1u}Hw33TZmx$*i4b-3fImVHmY$Forp|uxo)7TdAy< z>($V2@TO-uF1$Lt;ZOhL|G@`#r23Ix{lm{WySzfxg=13aT-opTlrnL7*|Axjp(=!w za8_}1*m1nM!KB28Prl8#y?|V~i*LDI?)mkrpYz#Ie#}KR-02-3|NLj%zkQ8P(n?N^ zWT&*J^={=gsTJL*jz(L#9w|doTCQI$cv_1zoI@xq^F&w<7;A_rv73%88SLte{TFYz zscTYc{_a{u>DkVmv8Q;; zi}b3Mj5V$PH-+7PcSgYW45K*?}e0{iJmu9<8! zuHBbV641eGLJ^fw$*r}G%rLdMz=%Ow<|$X|5=pT(eXVQt7LCyvt1_iZ$gva!)sk}- zwShM1szjTVlQ`T}BKjmjwk#n(izZi1NFT+Gih1?WqIfHE>*NS0UCX`L&Dq$X@qVrc zqw|bI&+hiX@?_0n?ZIk_sW@FIae;D6iV3F+rWiu31ZFAe43>@Guw891xe&s_7hir( z3`gvAAin*QGTq?%4fhWR_REoJ*%Q)&)g8|CW>3rXmtrcD58Fc6@q(}^3qI4i9!yFS? z3B|1#OE+3JXFdLM%d_*9?1NQd9DA-VE(we0!wChkVdK zXO4ka-t(q2Dht&?J|>)6i=p;l7L41LFHwUPaTQdr{|`VKnLL-xa(Y(4$@jP_qwFQ!kqN;YF zkS$tEM#w@>ur8-Isr(k1ip^k)CKtsdwLw9L)Pk-C0~95LJ4hIeB$A3_Ru&NdX|;-C zJ*hfkUD&5eznkf_VYTw8zP9vP8#c6L-MR5Bn{y^4ac8?8w0T^j)gsvBN|uJ-DH(>w z7+4MRKwe^dz^bx*YU@cdGK@>LGNmYPb8WEJqMXC&O47}nOk2>N99>!l#o8E*6$F=7 z(rGR2Vw7;qxna|4dBj`CS||$crwNk^9SY+u`yBYy+atfeop}8axj6eJzQDNIkW>{c zRITLf==8|OuUYvueZQt(ZAhIZX@zkPRbbOOw%aW(R9Q^-9e4?fIAyTfQj`K`9~Z+p zW%dTn{_&^$`qeAWv>-aR2%f zcK2_Xjt7?GLJ6RYqb7?{1HIi)qGLHIjyt%yS=xa*vBVjT!dQ>dhN2}wA4!B9AtY9Q zV7orYngJ4=UtRLsgJm@i+`YNuaC48U6|0(mCPw)@8+kx7>f|YxWS9)Ss-QB5k*X9+ z%#aeLzB`oHnurZQtE*sw#5r^VR#U{`w9@*B(vR*QeoSgdo+MW){4BAk>6|D2=n4zI5pLF3dDtx6hgm^Qpo z8mA@M`t;>D* z5-KZ&n)4&>OAvnV;VqkMt3^cUe9zE(tkI+#NlFuHA*Vowc$1!frN&(AsUJ=?Q^Zt!U9QK>TTj}(=7m>;k$=(s-|3CD@;II!(j3@AQ& za?bjrue1B?Gk*Q)$Ebe9*ec7VLyy;;Lm^W_xQj5xBtlB{_H0_>?gkUaL+ft{~MIvV(P-$8|K+CD9_5T*$;(7(c}^- zeUDB(U9UkGj1y#-N`~VSd6;JI4ioz&kW-mOWle*FQjUVq8m{+|7D&v8C*cXz|Izhig2#iyA;7m9KO3WuCI zrot36u?oTFZhymaG`QPm!~qVKoRvWpn(3~hZN=JzCgZH5GX|&R{+BE0 zB2GQ0z{}$ob4)CUdtQI>Ich%QbRii<$P$L?aTv4=FsdBXH)jKVuSrJYe=8F?bCvT! z8jUqWuMOV27Bs7mbefb!Ga#Py@*a8T{XnrK?^RKc4NZkC07N07Fse56u*PUf!PmBk z@TTCk!4FQ06%S07-;3yc}jRikS~dCgoSr7V1~GF(4>LC%`vaiN%m zr4r{oT6J7sZn;=@JbQV?#mh_HJ{+kmOw&E{w8Q#y>XJ~*q!i`3&}dXDZLuRdl@KGl zyEojtdBu9{Nf#r7_pHvhD8Fvcz4HAjviKh}l$e>PBgDX}>mXJ3@jy}*-FHHuFdpqX z{J16Sftxq?gt@YP`V{3W=TD#W`t=^A45cU@jte*UJ01=*%0sU!t6^lEBX>79+`PTz z^_yF!5I8IWt%WdKaG2Ks03ZNKL_t)iwE<;FB<_zBZ{EJ;a%~_F>~@)_PtUl1enq!E zV!3Gdf&BXqT-92xBT7De#+C%&@GF&_^Ld9 zXuqOVL!w}eTpx51JZB)uf#88ah94NJl>X z@}8U*zVWrEJik6i#Y~qKYqda{3CD`_1FBY5UC-tAjD(`!tg)`6PZei;bB?ypeg|oII&kD3PsqJl(7bK2t-Fspriz5-2%9%~+%9drLp8 zsK7i2p+__abPSOxNoeEQRmbMyg7xMKz6_+f<81w$?b#Dzt)v>Ly~9^@1?D4*Xl zf4K1c@+mJrEQHzMHY>WzOWf*=Ib?J-yuAK^Z9kA>;?40bu>_tyzht=B;y2HDzP;df z7*OS27EEEmb)C>(6zmTNs;em1327lkrX>?bN3KdVWhXM2q)>%|)EIL(|o^QSUfIs_x|CImmM}NZp{tjhUbhdE$v7XNPMy!ZT$32JrJ;wHw zoJ5DGb09CVP3G#Qffr6I!eyOd68E|D|Uwm=EdWzXXtyTR9Ow4 z%{bzmB_}!KXf0>qQfmu7%Z$bO9;Cw3RrC%yF;LaJ68RIHw#`_7A)wD(}9(N z!4KT2ipdk5sjNIC)nQD=*Yem>7w>k11UDt6Dg|Q_Nw<)Hd!GuuL8FLR3Tr39uaGj% zC<#*~$+}GiL*i&4etD$YO4#4hllYzQ zev_U^RG|FGIC#A6uzo~4hqIbt=%BM)JbB9bX3hHY2~haA|N8g%>;LuNb72--*>kg8 zhytW2P);bSRgq&Q}{rt9pp*M5~VQ5fNt*V)2uDWBpQXJ(FwB3lsU2FNXUs}Scpl!c7L21ttaG+ zDglMGt*uqkptX+9_T=6%G7y(Qj-rgwRR|_lNkL5&p^N2MYFb_6MOPJ46jDx#vSbeuB8*wE+_e#xla?Y|F^Z+6rtT>?+lZG%!Ug42 z;B2MKlVY{1D#3%MQmySeVYJ-eQ_*dnuUpVW&IYO}g>sc(&m0!8TJZnY$_}VhvI5tf z@~Ku*t}WCdbFUmDvB(Hit13Uorbw9QMJP=B9m{g$=3(Y_d?2M;_74*wCSs75e<_iyBn0BKP)QU@ zNTC?qY7L#^^7$3rc8k$HF~TQr5B%b@2Y&VOKcWt|q=y?K_gp@C#@*eXm@_Faq!cA6 zU7hR)Nu)T-UP618lu;5cQkvR8Oh$5!o}wsXoK_?Xn1SOGfv4Or4_F0koMT?1Jv_fPTGZI_$X`&w2jj8@RYojp&(*O3X?0%!N+wAqAFWD}t9Iim4VDIoob| zesRri-f@_#JU{IxRLtlC&Nz&g0&7l{IR)9DnT10xphW>V_8q=+ZEh;z)LNT5C1px3 zs9Y&a;1GAjIWmQS74LeF@rq%6WW7CyEX|L@@xXDGn*#4Sk{+A&@9&e|Th^p8PSJ505Pw35RT zHOrDYMsmsMtf4Lq*Jc~uIHJkTr`C{ewJ8Y@$7ilkQzq9a%h+hp(O^_&Rt2jq8vSlD zr8LHfqajzYu9Ib#by(}6DvZ(;Ylz;7qf@uPf2mM2sb)wSYh-v?HDE2{#Z%6n95~$X zczr+f`4@YJMMLjM$HbBoyJNwq9%lzO;~CdapW`pLEH!e>5p4`kS(2*Mc2H8e;H{G( z>}e5|7IDa=%n}lP*I^7?U!3v7?|+wh+EI3Ubngfy5Yt3nCZ50ii09w=4s5sWy)G&1 z$5eTdhI6fz!||S*+qdL-k_A`XIyMOsuY&6aF}QEl8G@B zY9^-uIiPC9T4_Fy#^8(aS3^q7^Bt?+^Yr3^zV}#DaSnPX2kETkVWdeE8Xnw8O9k=t zIBQWQkmdw&#?(bpiAsr!rpS9rYemVq5j7;tF&%fje)TD5S5L6xNXi-O4XVc0q7#hr zs(MDF=v2We!SBRa(YS_8Q4O(Izq%$pEt*drc`eEX-4KPU;qj#v+R~$~1+N=nLk@QJ zpUc~2>Dt~m*2u6#{%55*QPrc*h<8`JcZ=@#UG2zV<7&Y6q=ZNtj7S3Av(z$ps^+9!VlTy3OCci0|hU$L^}S_xrI{?t zS8V|;M0L8xtN)*^H|volJJ0o=HAX}ZHCOGSckiAkl1=f*#}Y_OfPD-YHhgQqxBdl`;W~JfmPa*I`CjyYwB{-GkvP6r-}Q%Q&mM{Y0hIsPxbYCT=X(k}kCOP! zEw35^Ryr1ireYq__wt!8d;EHB_kUf#UwN>vLTF@}qFSGNX{h6em#@)QqXrFL5w<&S zZtlo^&&kCZ!)i$#Ghug+bs95hjt3tu-(Z) zw^r$KudN$?|6)rP5`fkUXSEDlve87L1z%=-qban0M`Oq-%0Z&aVyHCYg;j-&q*HSW z%+ts)IE>XOs|Y2LYLU7^s|jObx0~3dK$%CpxA?B1k#Z7b*_p5Ilj{VH(`2|Nu$E;TetXB(;4h~r? zML2kJaYENQj*bplFBh1umxr)fG*|J^I1_Ut`2eBda%CHLlH}2voD19C4yvN>2I=WC zmM4o9>Ewc&n+-QPaMmq|)0VG)H!}DIho?vQX~)UoifMPt-Q5k#u16cq9CrjF{i@^Q z`5CK~p~Ni)OYfl8KrS=4@4w}{uYQdl6Tg^7YS{7ZvRuxI z-g{0@&N(}KhINi%Sa5iJ%5ZQ%*DqKuSM-C&bzrlH7&+(+Rtf5|R#~vkWhCav7)G?J z9Ip?UjHGgc*63X&RYO*mSQ8;7S!xtTm?w(XIBg{@*pdfMTb6cUZI_%M9^tx$IE?kk zVvsJYTncI2lB#|%KkU2XN{Ga1Buo=?*R|>bhr&>@CeN8npt?d-k%f&Iy-&fFCcrqf zHh7)IPj9q{!rsclsLZ4+*j}ZjBUL3aELv?@bdII7EWKr%GhSEFMivnB9XD^UiI;|d z{O7N^yqoBhrp_bQI?KnF=rtdlt!i^XDMU`Y7 z3b3*O(YBzKpkQk=4%g-)QMHhYBs)s2%sF$lx#OGHuOVz%_a0nNNLkE|ZrO+iFjp8b z(xtc-fF3D ziX%CVdnH09%-Mwddbb#}+K%B=d zHOzt;w-$}mnn2|K_J-Hrea-IflCy)4SvbeSJIrdqm?aIR5DJZ=_2?ELW?~cUjgq+m z<3Kl4#599<++JU^xxK?{%fbmpd*KYpR-84Q9j#e9O`a!oE-aUx&N;fHbB+d&=@z(t z#d2Ly+M|poq={4|(URNzS+L^2wH%%j=S+c#IIXzlnOviSN7&l<`78?R8J6S3j z2pOUFXt9anJC|p0_bdOaI=e%IWRb;vLSbxFS?>2n* z^>4YpxxyQZULUl6)R|NRIaZ1`GSioe_lhUyhs4>T(@Y&T*2k^Sm5;?iMpstgIF8&H#!q;zq&$qXC96f!)(UTXPt{v;uv6$tx!F68J zRaC0gRBH&WB1^ZWaH)d+Ey|#*q*YU^@5_6ft@;qWs4eZ2OP0@F$}LR|CCi=$Wub@x zeb2IrDKV!?t_DoO^gWvpSxlM3Q_aE3@{^x@%FqA&&oCvC;*9S--gy$1-gm@sN3IpJ znB5kO1*_Fc2vNXhv*G&Ymb?2AVg_Z%MPt3Z53yva@yS(|{)J}PNs_!vg_2DCBq+|mlwc)_}TeW#6}LK}gzRC(^H zMp@HJ;f!wAm2R_Tn_ZjWRI-Ew_F)`JE;gjH6{=s=3g20VDn0R~2qnmR$XXJZ+i_-0 zit0N|-;t{#Och-mCc)BqRzu%Zq&a#UdCzR66^Mo;ypquBwJWec^OOB6^IXXGw`0SJ?Po6L=7yQRR_dNZ- z&iS|h>;KGkNqqk0=S=q%=NyY}VA&^3t*ES_xJH^2%&fOPgnPnR(b?mT#^cet;k!1CH;W=1))=Fc_Bq1j6u|_rAd=lh! zfho*XYx(T6=N$V3-hX$6j*+8&;C8&?AHVsA%W2C;pMQ#W29;-)stU0r1&mQFS8Kdl zve|Bk1~O|bJ*#fX(P3fPTi$=W;j3SNO|_A6dIz1N6u8-K`1+rI!N2{>|AV)0@6oQu zTEPV?1y)1N@!`Iks3>t@qKla4Y7Xr)Da5{FT4fWN&RZ6XiY77d?g*2Jo2rT_f42*y z3}%{%<4%&<);$DhjQ3b=F%{O+hWFz=)(xzWPf6Rt+0&0(B}NtFmNqEYx3EjWJ83+q zDUfr3-7WM-lo05D6F94J~*vZfZ%CTec=A2|!XGA0rkiSNnt!7GimPE1^5M=8>a zUXl<;biORjZa&q?DNFnK5K)EVydW2F5TGa>8jtt}+jmIZ;9)&q=Py z5QrrcLlSdDL5u|z=9Gys5GE*96B@B~;gh_MEFl`>HEUZ?xnq~$KF#b(CY6HIN)p-y zQniE}(as8yxCl9>7P{d`> z5NuU`xu}K}FY-PhwPV@{Jw}X%nkluU{5}ra%yg~o5~C$SUy}&;-dK#Wgg2}Q&xsekBrMx5 za!(R9@}(R3RjIw`+lZ{K{zP`U56kkluT>9qvlQ8jmy}Uf%o9dyvUBpJj-l;DvjlP5 z=U$Zx7O7!X1xgluODlXgus%KK==?bc$LDklsqZ*8;7pshLgf1To>#A4algIces@RC zg>jnE+A)VnjFJ>DHKDbHcCe;}$PDZCG2SP3VZ^N#gu59QhMI)1;tx(q{tdTxTSh$3 zpS;8$KZE>^JWZ4mAO$deqp~ElN~o?URLJ7AzMNyK_%pR&^tC%%Z+pACv%4w#Yab7B0D30~&6wJu_#NbQB28ZHU z=4q4Y!cK@vqY`SxqOkA_HoM3!MB+jSt09hT#}QNpT?x5xn>D#VU^i#xT>0C-`w#r+ zhflfv^?%@3fBiSiU%%$|?v~!&JmlzE|K#j9`M^XBb$yn6eJ2n@Yk$30fd%C_O zhsqRZa@E8N${MWKSkrUh4^WF9J>D|Eenou$Epgro8BX^oH&Ex1-Q9cg4i?VPRYlc8 zCalAf9*++Ry|YYtaWVOI2pU8&+9FWxY>4 zN}prhD>+qv&|eho0k-d1R;>fkVT{7)ECcJKmT2`T@ujtT5L2W(S2eeA>lstcSz+|| zgqzwPgta1FJq=?*OigIj4eirzrmEquYjbe5uaT>ye<^~%cgA6Q8MJq4CXFLiS5E7a z^>U!|o}wU@DyUtZS(_g1J;n&)wNx3f=O8E^r79^)^4TbHg;Kz3K?zslhiU^Y33{bW z6Q4B7=!ZjsYEJ6C1HURqZi!uF^u-`9{end`giApNW}S;H#H$hqVE%xvuJr>zoQAQ( zDs+3$q?E+#1Yeihqp(&&*kSvQlU2{p|M#zQa+j+~ow{NJW^7Q!& zmPf~6EhWh^*U&nxo1ZOeO;j-LChqSxs2rpp872KZRT0v}c6-gcx0>yENj6%bGbU1U zz&OF{gb)Ssnz9`JO2Vij29K;6tf616VX;KVO%s=anov5TbfV-*@Np$J!;1{$xypcG zj7K^1uxu`>5XLRHF*eshot($;ic(n4FzSr6+E%7t-oBZIh`AFD$DmT z`&#*$#4(zrSC73z5!*iRs+oCq7+qxPhj}pc(2z&<5ib8j+j_g+YH0=~>DEVz=9p?jm!|Sd%fX$9XS{5p)|oMC>+3 zW372`2CF8Z%rOe@re>@)^xm*=7UvwsSr)4$#yJj8PFO4#tPT%xoyS;*_Z@w|_$4j;^6EBcmMJoZ(ejG;DC4U6}R)qZs)kX ze9!vmgoD$k%t9Re=i(mbQ(O90me8E@muX*#;H{9Ld z5_Ve#JX<>xu}qs&zW&u$#LbqA7cbFfpyDAbtexq7WLS3et5pMJ#I^`54_JJfqqvs6 zFsZSv8#1Siu?k9L;WeLs{F2p^C;aNyzhT_mb942U_wT;r>GO~9t2KUo$kEX`3B$0t zXLGkDQb}I!!nPk!rUzx1#)()GX`Wbk%^!XIoEJ}ySS{Cd-qW{~m2(yi7`Y*`sR^9~ zQ9Di7P-arBEqEZ8OfIrWkS@?jO_90~ zTw=vgGSpnLUeOs)CSuBnC8FwtRT*s!x*9nnYmG8OSTV-Y`3|LFnnrOF?@dfeQH!P) zO-WfQFti}UvQqeAz?h8o3&xPRe1A(A@3@_2{^noa@yp+RN2e`>NbfrehL|&}g99=h zo$L7I#f-C#epqm`3xbMuJ=O~r9_3oiMx#f?f_53>v>1$lBFO{;{ckIZmF1S_rgrIgxc5$R_%Mp263B2x}jEXH-x7uq@j*g*;c` zullS203ZNKL_t&z*Or$TXZ-vpKjQ4;msIOXNnF}wNrCOAf%`?zlnZgL+=q#}o9K0B zo_2)I%6lLXX?=3hH z2+$rE1LM5Gt`o*CWmbxX&UV<~NP6tv2cH0&HjNze9Xwm%Iafa6mbV_|W0L4R~X%!xYh7^)+tdk)v0^V21BkYdWtSk8w^IkZ>@ z2Ww5&MGTR_7QFB2-N3usJ8q^8Z*Q-7{`MI^n195nD)^zp`A+6`h)Vmfol)8102d^sup9FXang1(YxPYRODHg3lH?J7$e>qdES*mO^GzkXw#wH zf=^z4Dm4+)7R*4#F!-nRoyPe)!Yq52v^T>kP0E2eOc<*%rlPfbFv*r8&mnD$B-GHv zlGuep9}+{?OsG{8OJRG@SDV>^qp8nf)^){0cNSZ%1v5o(}z9VvGlc8&#>(`Ao$p3Pjyy=K%iC13H$=or=~ zte(8&Xy`aN7#Nl-p%RV;R}AaLg7nh1Aqc=y|Hn0A$2+0d;|7*si%)5~= zZlv}z1h#1wGY8Ol3+n}a-${D4WX3SD99AsXD^AbPI6psQdAMSAaVAN-Jo8`um;Z)* zcg5fS<^RI&_L3LRKIfyS=j`5pjdK>J)~rw!MYj~KHWcHT$3(af42v`RZo&JTk(?Dv zYtgzghSE|n z$NcLbf5OGlQ{Fti;r2Q(g_#l~zkBtXJb2RkOAd}!*c^F#ea%7dg?Km4oUIRW$q?eq zE>0-hV|q_#J+_QEr8qo4h7_H9e}psxv%$a!$U|#57Y%=H%pvo9i2rHbRJ05K@&LV3n|7RFWpo zQ$o4KYGH*~=@;zg!tMRYiE$`nP)W7@U1>BD*~7&~t|_JMA(cVpN{va<*rl>`g%}b! z7gQ8lZ`F#JH8E&Xt7e?0jENJ@NkAy)OvssO3v-xQ_8ooSq5XoxW0}cQDeUHeij^EB zOf=QPCI><_OtnaQy2yPTB|Dy2GB!nGsqC`QZmlkOqsT3kG}xY@Q`mKf(v?sqra86w zEK_PgRS98frNqdlTTKiinj4%@FpLtTv{x*;9{sRot@vCpsy1pxCP%4f6N#o$bZx;) zxi(5OX(viER97)hRH^9G*0j(-Ggs^Kh*&EB7;B|&pb^ew*3K=&V=&r0kW`gvpwveF zR7(CgqeZkIx*p>UDdiR@YV>AfP{zvVAw!M1y)I3RnTbNwmFyJBYO*A(z9-P9lu*i1 z&~0zq4B?HKsvc8R_Yd`RB{%*XaDThogo#e1(36&=ytyEh(U}SbBI`1vECxTdWE(|D!*m87X-fE zQ^DoBmNpc5pL4EQCB{sz4N5n%Z%vJ~TP5~hAcjgV8lMBqni`Or3vUTnlD6P1bJ?Q1 zBZ5hIE!042Vn%bDD|Kg>7LH_L8b@xg-ZI`hZu=$UT)7+ryAV*-b2}Nr)t3M9fB2s` zvYA5z@m@0IM>`u!cRUtMx_b%VBGI*ZaB+AgReVpU5m)<pws}%LkX1AZ-meMq`vD;`i!QRU#@{ zBPbB7wDjE>(-1sGXv5{Pm?T4vX)Y;2W~T?dzpgD=Rq{g$Ql7n;qLp;Z?E8;f?}x&) zjv9lt4y+|-5!N1(p6Y|ZrSyZk)T#pZ8dAgj8-wY4=_ns3-rsN0MKQRZ!5cwNsY*6L zNpvb;-CnR)6lFnsj8V)%w?t?pXF+I75?-)cs-zFt?k3b5ZDr`wwmGX!>vERKd~a6x zkX%*Gxz*h2Mx3gpx!H`-tw2!8WM~ZPk>;nH0YEo*_5;a9Nf(+#R>KxBT|iYYtaSPM(~xK3t+}#i1#xxtx(MnbNwAY41+~=~>kx zJXqugT+pPD@Y?X=$rGMEf62l6n13Jd_}wr6hP2)B?BtB)(JAzOyHTe`8_ZCWIIdgI z%$y;`hLKODxg2%-xgE6@w%Z+VUPsj1M?{EPF{P4KVXP4yDm5#d5%*8kmaN2zp@Zgp zv0~)}t(~calIWABj5{iUzBep;kExlQ9|@jCP;RjelR6b@k%M3?;v`KCHDsIUUXe80 zUPP@e6{nS@vleN&b=c~$)lhOFVllcCSLyB+lLAIp3Nv%r5o{!RN%wXp<5ZNzkOJTC z1-S|OsQQouFs+BtwEi(v>mI7=0h=z=#N1M{`;=a37+X2i86~OelnaFj*?^Hs0@Q~N zXZ1+))DQR3W^gFwL3pk$!IGPqsgflfT-E;UL}ibM|8U^i_q*?tEOq|_JQQ{CJ>k5v z?-#5OV)CADubaSKz9*Qs*ZoMee+b+>>Sp}DcCWrqAwWN*=l7kT54XX+lwUtY&FlW} zi`}%L>Gr0q?>WC7$NwMxg?f}&+*3l-_c8dDKOiMM^wpO?Ncw-E3W!Idl97AAQi7t} z_fYM-xr=)A^m_ED`H<^Z54AA5b=T97Nyd4!6=d$^?H$+Gceny!o-uw#nS#e*dk3~+ z$%JvsZ-4QR_|Ee5`4f%~j2bA*|=f#Lr3t7q1K{pbC)&^Cbm^iAK z5qP+S1nU)Z1TCr4R28u*=3JR(abgQDyD-JVoa7mnmGqsJs@ZHt^r#6jGR~2Ni2Ws0 zO48ta!5lhpiypiqAPJXhy=2#8qBjaZ^mN03?>an^RJO~R#W1j5tvEkD;rQr?lcOUJ zRx1wHYfev2S*%wapP!>DbVtXsa1N2(c7rB!aBxhwTD6ZrfZRm-TBK{P7U_trvV5e9 z8IO`tRY*3*8*y$HIX`G6y_v>YvQ_MMGxysY!hA=LBd;&7aBhQh*KA32LuNN#@%mNe z$-yzkbnNDlufO^gpTGQsFP=Ri?e2K}?ut?!%RUnBH~cza#WqT zd-Imx{QW<0=nVhafBWC?=5ouw```W@-D1TbpRO3J;ay&F9~0wFke2UWzvtrg1mE|B z)Xq{?Q)=Y)=7!DPz0h-t5-)>qW~OG&kyK$3JbuF<*2)8UC#NK-xy6bvzW5P;`m=w- z-~H7;@#g9k@qWiwms{5FZh8LkC!Ag=wsk@3BCFNFs69oCyVRIwLNbn6VG5EGG|sW| z9_Lzjm;z@d;U?E)p`^@imO(M?MmC$7m;>W{T=8{+| z{qmSp3qN?iWOa1Gt9j)5YAb!$b>e6+eE!8}JbnHVH#fI@^}Da-9NYE8EZDDw&uq4t z9B-IXA(n)8V2}Ynr!1YdwwV@-U}Zz9QdLx@8kSqDWWa-HdIz^DloH&AE1 zGjgVG_vPAf@U;oWrN9_RVjN}Q*P5K;{N`Q)8iwaKe^!L%a3>)Do%HFUO~5J zlXhI+B{o-Eva%d3E;v1|++1Bj4CEZ~UFFHe5sTv!-i$|#(ctYs=PD=1r)0aJ>j(VM zW2}}1vvqsKiagV_VB~EvVD<*F0;LMYBZ-^5(;AE5zP(O(Ge{4lHl$i{B{$QOpbGb0 zHd-qo4j4^mE!u*1(w(dtm8Di5&I!3KQ%j?0)raaX(^CH}ky#X}RKeF5S^P`4tt4ZW zvlwU42{5)Ti7mT1GR4e+>gB9lD|HH(S_q}E36c9TLecaqi|v;5{elvc7^gyzdsePu zfU=I|a!sE`wq6rT!MM!4x#artlAD`bPM>{DQI2dizVF4Tm=v#Gzhkov?X2JWo4p~V zncsi=d)~i(&E0k;X+vI!8<{9D3=#HhGH=nh@^E zOEBK0mwQ`H3fH&B1y_oOi?+C^d3_ca14C zADygu`CtDR=#v-3?T!~0XZX%SO}u-3&4E#zScM|9(T-hG6or_;tl{2R`8-W`baBIw zE1f<=nSqchG0g3dC+3``cdV>pyt|>RhNs78RA3kG>5PzhO373$;$y8)ozy>>s&Eb2 zXlKYdG}=dcm|06y6jc!#yBMXFwyi%j00S}1ynBDcyQSy&qjSzL&WJaO-TemN^%$p! zA=5brWKxX~M>gBc#>{dbsF|aqQ;rXg=(+_*{f2V5;IKO;gbKMAEUB58RHjr(_*&az zZQyi`(5SmWB_SxgJLDmtezsp6cbGNVz1-97U*G8V-&AV~Nx{mE`i|aMxykj@r(-|wmaV;dI z1GdD>qU&)=5w~}+y%z#gOmdd=j-oVUjO^mfG9~=5z9nMOKLMer|ilSxS&L&Y+mN~FWlDFzkI-gA@tBkg=xtr+5zz;tC zlz;u@m#my)zLVM@Lq=(ba}L|}5A%j{R%TEPzx>rNIDY;)pMLQ}l=E~$&++Lw7Z*=J z8CiJOLP?>S4>GkRtk&pSWT{Z3zdDs7u>z&V*kH97KDFTDixz`eOp4AJhQ$g~3ToP6 zyMV7fCQB-xJ?cCKRFwsLElyI$^NiLS@4pz$Tf3)bH~%w6P)v0US9J5{Po|kd-n~y>wCOe5z;lodScG6`0kr;n6@*e z6s9oA`>QoIX2$J3szh|2S(r*^4LUj|Ghm%#4uNqT>4%Pkg99&tDPWaUbD|xkz&uXm4JcEJg15+S6Sa+FFWI;Q!agOy{kT#>Hs8FwRxYl}98-MnL}0jp!I%zE9VInrVzPLfe_jbXqt)hmX5^1dQHkB*V7hdJl1z83gak5%%xdVI_R97 z?P`9sPtlrKDxJ3b2!jZBcHbqP(W&6OCIYsK8rycoMQi3ckwPSeB=^EnAtn0G^4W`L z{OpU5SRFs(=Rf-k-o3u%FaP~ta{u-_eDQ=`L|0APWopTEt3y(qsVQ(cbQo=!>WofR z&d|1yZ6MS@m@>E1HM$@8__I$rSX^-P<`p^J^3jtQ96tS+diI85rM0hA#XL_;Xi6hm zrg_90+r-cereaOO8z(zC64qwYEVT~02CAxf3k#=MS;OER>%lRX%AjXX4tsw5)1UCe zKlwSYuM1}PS8PVds0|5=u@;IU+{19V=IPT@j$`0vlhKo>j>ufsK`|K{J8GU~XIL^? ziBP6%5)w^C@b0;^#ZqSN6$f3#Zf8QBNhGS409YYEVA-j19jZ)whH7e%dtqq;tkCKu-R-^vB#nUe0;El?&Ak+Lwo9{Zh~~NA96;uu}_tpEvgYx zh_RMh;zJNrHTp&ogpthnsbalu71LU<^two@w<>B)=$4Qd0}+HdQH+<_vdD~C)t>VC z{h-xDpyHvnt^N0Yf7$k#T8VLQA4)1kUWZZ!-FD;kp+>cQfGZZMLEP_s&_WtotX3SK zKjrA;8LQ)Sj9(B_r9=t%Tq%#%LIr$#yW#cKEw}ggIGaHw5ll5GlN(J+68)v^Ru{Tw zDv2?SkSBJxmPI#G?l)X-?>Jj@+-{FKI6lQW#oAiFI6q-U=I!kgv-t+*kxm!7_I||N zuB#*jpPVx>i3wkRLk?%G1d5pDt!(t`s%Ugwsj8L0iVz)Yk$qxm1n;U0+GRjYOY#{_ zC>**OpdV@(kg$}qqVtVmq7q5k3t=%!-9ikRQYNsLm?6~6;Cn)yFuo)A9Z738b!MY) zSj3rJm&~STQ(!h7Gm790c!M{Vp)LuVJ6wz`ddFQ*sH2bBct_F`rD|dt$hzmr@def| z$O-!6uF zC#gZV9;Ga)AT=MmNDQJ&#Nvp`WAGF_wHneW1Vvji^Jd3Yd@I*R+;RE#J+Hrg!~2_C zaEjHz0za%#0uU5Bm1;sc8H_Rv{eW4l7&ZZCJG6FaT|hfDikxG^Z16~ct{*b=5531N zar#)-r-Eup(w6wFNS`_-U|M&E;Dt3xw*`VWa!53ia&lJEcScKZO4%>lLnb%GPJM`l zf4`Es6uH3)%IibNHIzosG7s+avagul|4a?Fmx}a88|$dbQfq8~M2n-H$0mmMb2gDN zS1}5RgGqjVV=djVB%s;d-Ep;@8Ttuj9oj1J4d9&;)du-Kt)4(DamRbN#=A}sw>l6) z5ImgLXk*(Ax82f}3{OfsjCNQf>BK!Y9Q6Ukv&Y7$)+=51&a%C`OzmT@O*AX@L$X$$ zP!E5{Hn*2864Zcg0ZZ`o`&tPTzut_r3&b8~;s>-X>2 z++3k@;OY4(>ysl?=h3N>s-_r&su@F+4vQiWC)3hRk`NGjLv6UA7!yS+@FL!M+wtPX z3x4(&f6m>_8-D-(isR!8UjF!xF&9s%^OiczXg`SAAtvei)0$Wlg^YI{L%(E7i5Nz5 z%_wc#-Fq%Lk!8DTu0KvLweSlDw^m>(UrQuc6xf(Bq!kQsM(?LmpNJgEFoE zJ8PT~Azc|6^t3hbKzon_L@AY&3OUN5%o;1s*Gj|ms7C_)hlIIqg7k;u57Z<}FIvCQ z9$migaLPsK2C6L^VvMqMQ_}HW+F`E($|-a)@;XZ6OEkesHC%daNxJgA1LLE9_$tPW zqUt_J-(LS7mEV4*Hek#Bv5Co#kou(|3;%#yBG*n8hoHE=bNgC*yy5qI>mLi^`VqSQ zLt1~|eIiDdha34H@B?Vqt=v58_l5RH>Guz!x_M1F#Oc{b zIOjluat zZ>=nmT)XFJ(}uf7gs9Scb$|7)(Hkmd+6Vy2KsLWpwz*#S_p}e))3QvdB&x1d)4IrV zkIq^?ttygzl0$hGce1mc45eX*Zn(cWXKu%PIt{y;P&sjYa7f=Tm_nk{hM`*!L*ie4 z_cc$SKjV+S_!0c>w_Lw_M_(1(-0(hZ@qNMc$nuMbx0bi>-to`B_*+a#{Pd51%ISK= zxBv1DE1g+i9P=0d(N8%4=p)|eCEvWgQK!gdb75YfNydvc1XQs}G`FQSVH(-Z6H|=LX(ZG@?>jWGS`64~A<5D#OgrXrWE*F0#|>p#a`NLZ z`N4nsXM7tYZ@0gph8d?ue)!3!{OF4(q?*Wm;`wRMupBr!KW9#bZ@+y@7_Ycd7PDAV zwVYoTeUEmEq4Q{^ndTXznh0^EkTDvx$ylA}ZD3A2V%%VRg(-m= zXGzUFE7#b*cBP%wLr9D|3N0#T#&IHqStu0IL6v$6isjo)fr?yqln{^B`@rzaepu6g~fE4|YD zX1BWCoc`bd1VJ)eD>K!VszPB^*j26zUpUIQekebJAHWg5cZ6?j%N3zj^spihzX_xQ$rE6RA;!l5nQK{bk;sa8lw`*LEf8|Rq+SwL$XSj zfz$qc5mQ2|MDUV44s{u3Dp{SRX;=)S`)5u_up1`s9|x2cBtJ1R`9N}sq#YX%>uAxY zA$G@9izDi~#h98f>?9?UGr4rDYBgaU;98EtM5bn-X5H3I-7O~lJC;L&)5$ADQ$PZ3bdWwDlQ&&^$47UVipl zrp<;|udh+nk`Mx$&5C|=&*vvCi@Ij$0}rF(o7I-?5W$*@X|5j3x{R%&Be zm_6gHJ>%ROG!4tgmjk){b<)wERw zMtUb>nq;4&k#r_XdVQN3H$&Z4wpoFcD{1xL%8=sijqw&RX(u5YN?n(uw@1&gyYK^^ZVY#7mD z#7T})hzMCsFuoYAhq0$^YhoDb+8JJJT31nZGma{$n9xd4-$w1JK|*2a`$3EhDdC(V zMInz&!;YLH=Uqip86L-x<_Av*uH*8wLv<}GP1u|W!^AKq?nj8-2}8ePRGR5_%gkA} zqsC5fd+S+0fSFe!98go)jz!bZ=vT~VCwz4Af;QKD^XV7(Nu#r3F`IL`JmK`@6jd9J zk4`Yg9L&;^)=|`2u%WH9ESieWiJ-X0r3N4H-ph9%Bf$%~LnoPGgqTFUB@f$MxhBSJTPFO4Td@)Xp{g&T;`2}xpdM-{b z!8yj@4;4@3$mf6m5B$R~e#xKw*?+~=i%%dX{C0=dt^^{)5GPe<`!hg#hvO=SDpJNM zg)51CbVX85&fj?|#$g9(MCXxlSWzu5@XD|mww%?8N(II=(KQXG;Dcj!#37KTp4v6o z#-Ni3Qz=OQYf9i;gSJ2~!bl7rofAGM#vBPb$iysorp=mhvw}E5@&qrq+bTwI23H$~ zU8bw&tot<^Rq^p3{V8FJy!_SA$h%iG7Qz@+jZ%+4yI++~ou zwPHHYIigZJIMR)Y=>4QdzpF0GhkD6`EobV=X-VsAsamM4V>vsbX=aQVDrcE5J9?iP zbE27_&^Se#6D}E=`3cjeXWQ?naCkowvg73Bn2&YMY&l~JJvuqMs>9V4kLxY|tCy^& z45R0lyGO$62IUiBxFh5hO}An^u%$XwVJkeV(7>sYMB@q zLqcoIa(+bPDpuYz^a^LCDpMPog`_Z%ai_NL$Np z8u{htzhN9k{;a9FUq5nl|Atvt^ZoC9f;l-xCCk-EKfwRRoG)L!=EDzblvm@^p5QB_mb9o=jx2A{1|pG;+*gq9hCRt94&Aty0F zr&#JG%dHeMwlY|4$zg&JG0Ko*AWRcs90_A0MX3-`A+hY{e6Vc!qd)r>=*1I;o#M#W zY`*!18`se#2ZKjxhjLcX?m9D@cleEm0Lo_kG|{w`Ttj*yRj`>LZznph=$=*(H|#dA znb{r7c*Ew)zvkxG|BkEqCDqePQou-nB}WldW1d5yb&vvzNYxr@V>mW}cG_V~j}Kl< z^mQ(M?FKhYjCVbcbHgwH;qQ6a8g>t_uxjAV+t=LOuc_*D5{hUYNjX9+0;5l$74vSv zi{~E_NW8sU)75QBT+3cQMyXqPpZ+L&kK~ALuj29E8)j4+|7S$ld^C%5ce#fu5dJ2^~2n ziO_{8WV;ymbOx!!Ny-S4KP9FhI)pNYh@3I2Y~@*CELkZgA0Px!vQOM?Cv=pq=Bd19 zXEhj$F-~T~h325GrK%j(YMfR$64qykfnX#JACr(>rt&LfY}SMrNm}-sF=Yaoun(>w zYojXkV2ar#@1B(#`PDzwT-+E<&s zS92;Id1O!#ldEV8u%KldLCR4=r5?nomRTYMe)dr(sd{6t?zmJ$8 zMSU>2qm6`^w9uJn%TwmZr`Wb5Bt;G-*^d$;i2{`pyX}r))3aOkMVEeV4$ z6*)$xendy9;7qB-1F@3Y%{cZ5f;A*TmLg!ob2TIBqJ;$q7WyHn;NH zD@!tk)@MX_j2XEn!hl5|jAGVWtWFRIB8`|mmBM6$lL4m85sfL;R9ZgI5Ya{v^Mtmb zTxRYANo%}zY=Rfc#}HX|LRKX!nyMy@5|nQ12CqGX+S0U+TFpp7Q6pO~xcpUJ+FyPXo)Y;?zc#x~DY*dT!|4oVqexo?r0f@+s4h*ll|r?{4v- zN2`EqXUub^rQ$A61cY8=oI^WJ*R`~D$D*F0QzrC1+qEKEi*XfOANl6~5kHP>d_p&K z7Pb;SAP#JO!Y;PVo(96O!Pyqy2Z)Kv%{V`P!i%%#d~*H~>2U+&BaL<}DDmRAsfpPTd}f#en{8x_$QjpZ zx_KuxTOo72-Qag?rqN5?rD;i}ifim8D6O&~WXsyauo=-ih^Chrt8K(^Q&r>#&o}-N znw_{QVLar6YX-q6AHMK{_YY_}f z`ZVS}h6GeDJ=#e_Qinbwlys)+gC?dirlM*!(I+-T&o`?TlY*l~M_V}v@`r z_d|ObZfLCwCM6eLPkvvh1jTP^|3=|^;8&a;b90Y%ZuBhq`V{3d&q&T8- z!YBwuB+W+iI1Sw2ZD_UT^UuHL*^_6?FV5us_WLQSGS;8DokO0qNPXNE$&I zL((z?QT<4r4ApGL&Ef>FG^giF{`v2JkEfT%@UTPK4x=65C8_ILbdn9Z4v%!E=XiO- z>i&+$$2&rb7@K9$!IPC-|7eHR;$jqMstn00YiJsWs~W0qfvRdbjD;Q_BE~~cMjI>l zYr#3EZ16rXPU1GSRV9>$1QtODDr2xp;dG|TH8H0{a$J`R2b4ItVtD7g3qi#1v_}l( zEQ3Gou&Tynx&9ZkIkm0i01_h=GRz*wfe<}9O3&AnCqYK}Sd1H4zPm)}_KojlsPm!n zqDb4AlB^6qFfs^QEX6{ZfGLTvlj2_G6$H2U;%Js(lDyZHgDiR3U~MkKdD&F$QQ7;x z@`DqR!{@-akobG@#yd&{O1+1uFPLBTZ8OY)+@a*4pZ8JrLSER9OmcZ$DTT786=Q?m z-%kf{{XdyL=lwy~I~AJu7ju=RbN1W+-~PSDfBg6I?L>aw8yCJM=)W)G=YpPkhkMHJ zn+Mdp{`kGv|Lynj|1Z`b67od!zWZtKGf3)NE_r$PLPUr~+|IdJpx^Tnd>gy34)%v! z5@$N4;<8mVZNt&=8S}HJFgxbyM^Cu8N>~M|%EVzqRGNpoJFZ{9Wb_;Qeq`(e-~Hsf zeDcE|k}HLpCX#FD`w^WI+B#5%rkm5W9VV^|xx+Jso}40;N}vQyJta|!lFyPbNaa0d zP@$OFbdWBl3{#vW?HK}tPc*8dYg%;csfM zP`jg0G$j zr>EzPs}O`?uPb*4|(zXe~dd?GM@}X){KAubIz6vUc7itb9}_O*-C=5R7fOXwcXN`zHN+P z9f_e-$_V~UDH(#x+OVez$stWJ-esTYN1Qf%=lqg?_M;y&uWNjaRJP(|cEr5tSax%& zxvS zYM-{EbxW$MLZ`%Rwq#Mw*gS4{Tzw5$akM-pY*zGXqFc7S_~1ECX?)+KLqz+Li)G8l z&o0T)^YWWFT;JT$RTZc66Fzx*#%7qfdHsgnFw(n`HaWUkhcz0NBEzaDkDfSrwEfP5fHa^h%NS?f;piHLHk%|$McUa^fCOMeLJ$g@NqK=fxMKP{uLkOACXTkR+ z&6E{G$n2(p6MKZhGKEa*EKi?4=l6d6dpvvb0e7Em=~s`~DiW)jK5e<(^`I?JKKPK+ z7ti_p_L~3vH-E!)ch507jf649bukgDSi=$&cc<~_*-tx=O zzaUQ|^R_;a6wtOjn@2*7XbDZq?-$3vPO{fGS_>&d$*?92i6IP3Dv)bKaGGrlsNgYK zqpTKWcFJW-Xk;y@E2tdWh#4ScP0$dE_?}Q?WpLSGlEP7{sdNUbab?eB5~b5!F{O-k zvL}l%mms}DmS2tXJ#%FVTC*~imCtO3fz9@j`SA%kJEke1NSHcN104oc&xmeL(=M1d zEfpG@H3S(X=|Xx@B`KtJL}M{YI`L++CE3WKw{e*y-O@qCVM8^UoA> zbjUqBo3Usrrl9e<|97JdBE6)AwnE7udml+zvKAy zil$wnvZ1b9thu16TdbYoV`dtAreVXAWy@@Fgsy6$ww!(P1FE*h)|PoY$Lb1|G&gVG z@YPqpX0y6Mr^My)F~^G&zVqUo?qbO|w~x$C#19YLJ~-YyZcx>n5IiACf=0WFILT0U zoFesXPRejOQ~18aHJX$aukP-+y}uk-VeE-Kxx#f$S75>3X^_UgBB}$1oISF1o7ST85 zT>4?vfvT|IZ7RK|57k0u!pMe(RG%b zESINOyt#X%n=PnkH5V6`%oYn?eRa*v-4>G~vu4Km={X-feMUX+SRS3S+YD4jVTs^9 z1VybJ+9+zRX_Thcs#FFR@}N>!C36-r{a1w!C%E&hrHfTd4qHSpAIWtjX-~Epr7R{% zbz4Tn_%U0y;~;|9W(B)P)NIa_CMu=S1hT3aeamM*{}o9scpN=V<;Y(8e}D4HQ-1ut zGcp72@(Dw>IHkZXAb24pBr)RDRa5-G<{e3XNmB-k*YptQq{oGS~ z8D}(=b2uxaj?)@dXm)B}e?$4zM^qAnTF8Nr6?LtM;}**ax3@jfYW~}Q`)|2?@ex<& z&-vmX{*M3ofBGMpwl~bGnm7fP^Ok9PO;epq=W3yi#gxe@kZqD4Vy)zJ%(-B}wY;wV z30nn{HG<34`(&f686dQd${Dn-aaK^aDQBvx;^=tE>D3hxg&)1t+l7&NHKWsV?mq20 z(r!i09_K7|)vz6;quqzV5M*ttoujKeOcoNMHBz6|j}svWc1bYHN9`Q#I<$^dPI2Gw z@H$b?8*JAQl_n-lv#0G4nKg69zUO|oWl{lE$O@h$AUx>onHYXmiARy{7GE?6zyZ{PMSa{Ng!IYwjLySv@{* ze|O8>-5uUfC~Zht%aFdF5t9<4QA~y8nXq-D?Pkp8bK3batL;QT?5MjLpMLf^!}gvZ zeE&O4(?qjdqq<|Be)yb!`Dg!{|NVdcikIKK;_~u}b}{GvjYg%CSjz$L#Vn~~Vw<51F@` zdDGI+;6r8zFnXB*J6qAU9rMKz^Jd0;z7RT$G4xSLbA}R*l3oazXB3~m)$+4+vSkh) z#q6CTT6u!wkgqPLr4xCTdX&n%^E(woU=T8&a?AV>e-Ar6Ifk7xvQuVR#$SPW=waoUJM%t`=LQ=T88=txGro6DtQ65ZD?ALfVqK&Zkf5#LSG z5-WSG^bg(}5(TvpwMFb(wh)W#+21B87>B z6=QeVOYOC<{fu%?;}OV#!&RmP+7hEq462X|b4F!(K828oArY&9F*V9a_d<$$4X*^B zVxf>~4nK1rwxsMqB$)DUZk*D-;!wKbO{o((=tKM2m_lAhR%C7DnN>oasSuTIJHs_I zvaGCe6} zS{A6JnJP!pnLdpuqj>Z7hI!j^akQkHALCWTTjx!o9M8wWW6=CpFH=GQ$vxJYYpg{^07qh`pK?)Z%Jrx%=FoN{)2&g1J_-hTB34{yI_*l9w! zb|o9*F-BujnTKZROwHMo6P_HO(R52p)iJxQP}ySh;hW{h)Eh}n-bzlY z-f`TXVXx+5$`6Uv{hEH;QzwVLuNx$bsRphG+ZtovS+R9$E(nLQoPM+;BF?vPRmMS;2Ngd1> zuC2+H6P-XiR3Q_nn88E|!VH1I2h>z5t8B#D3T-3Ram8ZR&~+X2uEA!D-)-4!Co+kn zlO^?Xjxv#UR^wcUred>NVM$3ASd|Z5TUAwYG@sG7YdO##1R=qUX(fwlE;qj_V!4PE z>0NUA+p5jC7TNa`no8wE`o|Opn^VwQLtR&L!&DAqMR-bC3wE9II`-mL=`A{lDSM1$ z7O_P{Eb(dStlmrFzr&~GxOXDSVwG}n+bP*_2_i$IO~M$3Gg?yTPEubvCbTl;kgIW8 zx{6$?u*S-tMI2OnaPb`_;((^_uIiuOVl;^COzJVhR%}DaiIPc-6?T#~F>)k`^THlkG|92IZk|6yOxo zu;cC9H-s?JG!6A~0m~zBolKXsToX!_1o7cPEuBHOGW%7vAeIX$f&D&3L=LBwEV^2- zGa6g^AtQ-MBH&E9H?qQp2$RAXMPnPRtst9X2$33xX$)usbz4a?#>axkO+v7!9Hy2< zJZrfo1u>{(1C#>2?+H^BLr9Q>#@>~>@3PMsbQbFcq4dAzg4WaGFvS>h2>59rMo-ES z?}J=NS(om@lIBb$xxC+ym7e!}xaZ#`uf;u?7)MFc=Q5NqR!~M`@}yXLu`#mQiC)$&i8y-kBi|$ou!G0GV%x?Y~8{ zFB|LpJ_f&t{*cORE#{v0e{L~&y-VxwYg7uw1ADL&==VhXy;y$`?GNes_m5tCr}BFa z>wP|?^e=`O!3xUJT8k@PbNdfT5iPnVn`YI^7<8@e*G2C zJ75e9VIVh4sWZ(HKPJZP@RfoP$=(yEh}DW%Iwj|q?#%PD>oH#J8AzLxqX{~9lHh^QCJo8zLHJRD0=HlX%=T}!O=Q9Y3 zo7cCb=$X4Y-}~VEeDB5gp`FY193yGkk@A+5WYCIwXGam^N-P7ja^36)C~Ds?o&E{q zN34$=_r*Bdl0P^m9s1t3r^=9&#s=}W=p0iFeQafiQp%e^CiDF zu-LPe)|;_L&RDJ2oIX2c zOcVFJ9oLU5ybe4$nW3WN_Vq2Db9`{N6 z_%Sl{9_Lz)PER<$y232yEbKryno*kpUs*_7cAIOSF6Mmr^q7;U=Un$|GMQK3yyb__ zzQc=;KjG~BlKIsI&z}B@$4{lh@Z#i*%O~etRx`4NyW4vfZH<$Y07+}sn+-~7>JVhm zZ51YER21rpc2;N(e!yvH>ISVk7!pzU%(|N8qGPw7p>@KU30GyBMpIR(G&gCEj}}~< z&uL~BWo4M-eIVq>l!J&GDHBxLH=t2T6@#g)5N1Ez(8YYmA><(D#w1A=@x00)L+yK3 zOhze#H5|GaL(Ys*h6$68XfoYY5z$yx(N%L&HDfWG(Kd|;Q+^_6FGd<0md{H? zoK~pd3H^rt@qsW6oE{x>d9)O~<+v4Oc7|`h{DN_Pk8+imq??4QWYPH@yhYiPTZ>pK7O{~>G>G}OVkZ-SC6cQ z8U1#{)$)XY`A6U7`PCDiediN;GLO3r$#gusI^oOTe9Fa45UNj~f5`FWbEYh&R;{FR zO5290Esqb6JltOMqJic46Z&k(uA-iG?D`GM*<7|uUd$E0*uCV-{u_3;FVR!P?>yb^ zn1}E+H(!0tsB1!H*d05ri44@_G*DMHahjwGBq`E3(eE|{FT!q5B?k1$g00cH?S=m8rTJDQB*~dBelo6@Bj6jtZ+~Am}QMu^F_%2e`SuCvMiH`&Y~=i>YQT zPfs~(E%Qoqx5{GJilgj*tYRF3)cqjF#i)cnaO&KcBvP&Fo8a9*VX0_%1 z`hmax>z^|1Uh~tR{hVMID0jwudBMp%^Zn-+oXs?s=QFN15p64e@w30>)u%tD$}^r_ zJm+y2d3fv@c9A$fa=8q&M;$|d&;4WKe*K15*ROdptEuZ#&gLDToL?}-9V+(n-3>eD zi<-s;3^3&#y7!oMM;dyP8d#VWLwKO7PlPI9G}(Yp6HF6LQ26AT>O^d6+ToOO^t5fj z>xp(~$Q2~(Q6@NTyX}hI;~n+kYxxQX`>KN!dwFoG#tD+p)(N^Y$=$2JhJo6WeJb z#mv#NMJaHVrm9-%BKBtFUJD^IO%txvX&LKKTCnXtK~M|o5U^I!v@LNnaCN!hPk-`V z{>{Js*J!oi=Zl74{l}m1^S}FlNVz434pk?dUC?GDRh3#x!ZsyhEa}ZXrQtxvG-Q=f z$|4*sO(Mt><**elAq^#Ak5JO3&Kgoy7!4^Zy4j4A^K*`t3($@qTs>u)Jpb-5{(_j- zEY6-1CONAs1=dL)c1jYMX^ou4w!=U_4T3_>8H-RTwa)b8Kx=E1kr(H*X4cMVn~tN| z3?Dr6TTP6Cqva9FSawrpJqQuZPdk*7^|@C)rmE?>jx_C1?0iq% zHiux6wYGf!^6(ENLX4FVvL?) z*wUzsc9PIc*3!*t0)hiqSz}$KaglBUJJlGS7}CVaydmXCh!ZN0s6|a(J5p^SW@fV) zX7p(7$yH5EFzZ?>+o3UN6{%b#GmyiGGd0fE#2{-zwnC2E#}14#BHUu$&2@4VDwCCY zREUwyZbB`jes+jb<)u|-URBg+mW@Tn5$h7i#|=kkE#0CbW>4c<2mu!(I!=TzKp2_W zvD)?wJ`$Xzsx)>tfmf(#Sj>)5RU-5o9v>cAKRmEnZ1=nPQS&_414gQO>GXWt>uCkZYu- zAS;9Nq4cgRLV7oH&?P{!Pkjd!F(B1nT6*FSBwRAKs!+z@Q!B$CTOi0Z#=+t~+BD)`7NE;k(EaU zP4>dXetr26hs;G@+c6!B1YrcN{nsL1)DvxmqKX`mn*s2p6QchIP9!Rz_ zhE{|rf|-bEV0nB*Wg6~oAE>RR->%6d+S*|%htdgclZf_6Evk;Ne7c}*8+<3#bVCUtV5@WO+qbioaU%& zX`Mn>8kOMntGB#)c|%Y&(I}i-V5+$g3j2x1NR{Kb>lt=C#(v=8@lFDsS@ugQWvW?6 zreeGC++JU!{e+5%5EGTjXf4;YHJLZ#hPN-T`TAMIo7KmhKAB-%MIv#0x#tC0%{Yzds-s;VF<*Af<}I~tajNG2<_^1G>O^>wUbnWcna}6cb&V;A zaH(Pv^dzbvvr_t<2OIBth2IaSy?1!hD0JE%r1n_oeI1q{uboynr7$^4GCV5j(nc5I z(H6&VDnocB_^IT{CCw0HAO%e@8Jomyg?hJ%mP4Qj8Se!Cbm)xs9%4|^&02b~55gew zP~D4T+8CTwcr_C89xY<9T2mQ|t}5zOiNm5`dS&k~-MC6~NY!exxURdFO&ocgCfsh2 z)OQ4%Bt@W7NeLMa=$D`+4UU4KAy*D(49W;1Rzn%U#InGv!!$7#Q966+05sa*R7ukp zgMm_DvbgC}mQ>QdKP4L@i)v1iUM(A!_jJj9(={OlF^EtS_E|dZtu~VUDx00CWXP|S zlD=MwX}e*lI{I&;RFM}teNPfq|x@YrnN7!yT>pHGJd;x6>QxaFLRRs&F4jA%Oj15Lt z%$GCjx?lyauxDWW?F}1-@9zP{Q@EBA2 zcS~|U7TjVk2{TphgQJrps`-qx?}jQHD^#YcYMigJ&S4GIh`<+PFB?vy4~e(DuXZRK zPt@M@qxQBTQcCn=j|v8(t$e3igV}eJyh9=BB1~7hY`l#w!;8HLD?dkPF))au^IheG zD#M_?f#Be1EGVw`%GbXYXV(bQ=-on@3dX1y1XN<5-A%=;pvrH?{2p0gk7nAZe3izQ z;fti~-}ST~hAjCXC&Uk%S*`w2GQCvAlT{g7z9;I-8juenWhzZ69L1$&qQ4U)a6M`*l{G>Bc@4qeCXp2`SqqW6YMeQ2e#SzqW5sBEyw!I;48NRwO}`ys7dAH_6S+|ha8e^cDeAZxuf(wl0^A$V_I`F35zKTg{3 zuTg#YH#cJIP$~O;O-aOLJm;6~}*z9(EaCFQ< zD^}Zq>nZbUw`Np<%gbZx+N0Epwt}Wf>~{C~u##Tg`J9|JXGcf;(NBJ#izk=t9&Y%w zn?vf$8mdS>5)Wh%88uj?sEoy1O-vKM4S1@^v$@|fyEx_IXu*@C z3tqJYs&B~{mggFX=hoTwlf*$3mxvrx=t`--Qm#2gt5ktk%L`1mMdPKbdtCOW6UCUN5{Nf)Vd zF0s}~9aAdLd!wbtF$wO`pvb{Wzg`Yx0?HkX5=Vu$vA8f_bP+Xoz;u2nR)rcxRoGhcrBCDW~= z$$`tO5BTWC2YmeD6P}&Vc~qyYw>x(IfY#8p4SpIJW1vq#YDoGCKbCW<(TpLIC&(jg z2sCxa=mWteHvR@;=Fk7PKW7+ryd5^Q%Yh$!&~eFzpTGV&ai%H$1>={4$yB~|$r(Xa?2#?m*gq*$&PN11$Ak;djIc8E8^T#v> zNmbc|L1tOsG!lxbIM$FdSqYV;v6gnZr0IGvqg1pNMM(`h7F=i9g@Nc4r^^;UT+z4@ za*LZc{LlZ>zu?!u`z?O>=YPYC7ZWxOlq|#=t;Aerl%Z%tK-YAoB2`M%ZjkVA48g1zyChXV$B#5VYiq4Ov*H!XMJ+cdby@khA)13LD=lX5LC3xZ#6-?J~`oG zdRQyg#Jq~)Sjm-YMV&E*wkA=f21hZL%oGzL3XL%-7^aCS2Ie@CFc>GlLYx`qK*o|% z6nrnbnxL&R4az!F6kNMQNJ>rL^61fHzW@F21Buh+lFxqm5&!<*{uwWx zJw@llei+C}Q>-k;lM0lgu-TxaukB(olgFGX#uCcR<;^R+Ry;VrhjxyeSlDA}Pw%4p z6LhnbYQ}jj`yU?&ZqHyw_WNrVFGk${CG+lztE)XB8e)nRwHHCgHYj6xaB{|+VV~rz zvThwAMPj4L&e8zOM!x@Cq(-$d7L^M-MkotLw?G?3$g%GJ93jn2DKm`FG(EX%F*ajt z5WKy1ES5`J2CR-4l~5(1iPB>&2@{z`tH3BLKoOW?DaZUossfqCps^z4C&Lgk=!Ut7 z@x^#YDw%PbP$@Fq+`#SzpC=fuVd1fEMz@K6-4Mcz4gozz(zM5xnXCevapZb85;CaR z&?SS`1L#1AN)o$DGh+xSMl@!%MwtMu=biW7=C|K{!qtllUR~|jPZ8ZFgqmKYCtbr> zGRkOdET{-NXOtf;=l6!v1&UoqT4iG~jg_#fnjVmnfoU(rNExz@^@30GNe)4Q4 z`Iew6nVlr0H`D>#qDK!oeB zg$<3aG!Z2`BH5qkN|lp)UaIs+tNE4tEBWnWj0 zA;lVXR6-=uTFLz`h@kEp&uX!tF$Sv*&RSBG>M~snGZA#b*n_bGu#tB5^j*V#nwi2x z(uG(nI()Mvt5nIgkvYuFl?>s4MaU>8vtXD*t(VYpjn-XoDf%`wO&73nDE7!9I?<0M$FnVU1t|@U6{Y+`*U66ZWPRD2KG|$Y#NDdQW++da+eSgMV zZ@tTthi`Co@roBOU+~4t7kvKmIX7KTvv3&e>6Q(yaI6FFyyg5($Gx*Vs9AAywZm%9 zX6_k_XeSy+(Vo`#Bn{b4y!~j+x1X%}@BjEaeCzE~KK=ODyzNI8D|qkszRjm!E&1fj zr#!v5;foh9NGdYT15*O0MBkg@%rNZWG|=dR0lFAjrc7V;p_C*0amT!$+1^}J(oEYo zSl`fkh4z^-Ib74B`!!L?%wV<5fgvQmym-O(=90~J%XYtE&XFkuocDBnhtY;nZMoX) zczGrL?W!13(F6k4MS7=E+7gxG<>eJ0{_h7AxTHM0n%#sGF zjW#CHs>H%6R$Ytggf0`ZR1V5KB6CFNT4HZ-&SOr_Xiv`Q7Cnu25F)N?Fzq?2IU%YQ z^PCuI31&%b8f>$mJAcfbhj%zXKcTlhIvSk*3VT4-OQ%jE=YZBNPAkabK8!hth?1p? z%ap^=a~o$r9PEp_U3?dHi(1YHVfVENrg}Z9b~x-;v}y{0DHY;8qi4AxC@11ZF)aoq z4^D17kaG;AFrq_1#!eu7??4 z6uzQGXsanA8;UNs4l!!aa`hx=q(979Tl=x(01#t1ifP&!bR9fP&dfPN5?7eZ37<5&0v^P7J%LS$}SPD~tQaiEp!5yB3+Muke zPQrrnnpRtUu@D2=8?v)(UOeMxKlu^6iLt!VnCT&F5xVx(w;GV*XT zUd!fKMX2NjMaEl)YaCuXtkSf;!5P65DI+xx2UODQA~af|jizsUW;5e#A_Vz!horrV z5lbee%sh#JuS+H>g>fRTg^+71{niNLyv7CT! z9L-4Tcyc;)%^!ZZ{6>cNmN=pg=dE&(uI0}c1qOq99aFFMkuHBoKF9%Ip|U;>CaCf? zw#n;h`10CKxm44Deyw(Za{Kk%_RDij>c4KvIxKKNQeqT2j2~$Nnxn(_KqR<*4Z(9& zH$s)@#c)w-ZHeIQ4~B@}!17c65!K+>QeV^40NX$$zlYbM)DEazj=wh_t*w#TgZuN+_4`vOl@D&$1Np}Vtz}8IRwhI!`ca>jC_q& zF~y0ZHS?i~O&RGVD+=cwUKdXaub&W+_qKl&@|e#_&NHS}v*)39DIFiydonfD_(%;;Q5p&V;m zc&)-kmWZ!i530I_Yu!Pvz0%mc%I&#>7MhLK^j=n^Iqc~p3<*dn)Q;?H{WJ|J(ARCBxM+e zk>Rs1=)Uz$bh}_(J65wnOd`8o{r<&rXQVE|jQ{{307*naRE(F@TgA>%X%vE)(1O0x zwIU=1N)(J<%7uBeA*aCAvyb@k&wjx_eE1R9`-x$nxZds|!LT1GMd6&4XRy*NeMjFa zeA|$VV&PWDWI(AVB;Rk#eHz77WwG%grZz^6@9+-JU1!J;a=@IazeL zuI0DC|6Ts%kN<$P^K)>^`ZJZ}=?CvfeMBLn^NboIPj7CxJ3b-JGfQL8B@t}H(p-Ci-B)r0mEs<;^0~0D)8m%E4!Z4xSjCYo-V#OOmNMcCySuw2 zAxoJlMN*t`+6pc|7j)xU_6tszD}2*(wb?O@*DU%5qZ0dR&n}LPYQ{Rny64-xUON{cf5V?KIDSejTj&+f>&4Lt+S4{b;smMP8nz9 zdFQMb0WzlA{8DNygvQh*Ny^kDKsCqJXPKn!HAM*#YMN(?4Opd*OQf7LTG_hn%NS$n zd`s*y79(fGtgu=!hY1~%7*Z@~1FmzlixzDp*^&=cI@(aM7{u`}Uz0(_k)kv?C5B;O z7$g4SIg3RPH1z$FVLS2Fs}1)ay-7b!T<&I~UeKz6kPTA^(s5Zbxy;*9_a8iAxwyyb{#(rU6n~kpS2rwAA8_~n+r;9SVrB@D#px;ItQfZw zO|zhc$TW`leu+bK+IKXrBSym<9EA?&mgJ;}Art2bVj!i6Z8Rk$#@!&FV=rBcDile= zPcvcK@oGP??$7x2)6e+TCm-?m|M2(x_$NQ*>T;m>3+6GRmEyCjErS^;&quzS;k%n1 zZ#Nxt&RDBhtXfpqVdjJ~Vied714GIzl%|Y(rqOalGLb29W`A?Z&1OShJ)o$JQ4=Z` zuCA}yy}01aU9<2vH1V2TMpTimXk9c{yT~TNnRT4e;B54)wE{DmPAN_Xc9$7 zaz&n(OzkL7a?Vn9QHLUx2z8l#F!+L#bX(DNv8k&eS+LPoi@DKiYQLprOo=8&w4S*R zJu2^M+=kXf?!V#r()IlKvzH92<+uErm3z(y?>WZ(8QZJC5G{MxaMirx{{95FwEXm! zKjYQ7<^3m5IK$)ez^n};iHlIM+ekC-$o`s7p1tJlci*9yC6C|w4x4G_<@E-qJtuub z+dwjzeUv(`>1Kyck$&M(jb)C3w67vVyku3EFVSnBzUt|X;oZCUao)4h@bJldJih-n z!_^IMfB9{G{_CG{-a0Nn|AJ``pFZ33vtQk?dFk2CcX_#8GHXk)0j&yl(c%_sXcp|p z%;n9%c8JVL^VR3SrddvCb0S0$t8taqq^+StaiSDK6(&=-4m+k4xwEyD>l<=REW0J! znMy^A%=5@NO;{gEWu{p;v?+|kND3n*20;xQTWNrrMcZOc!8t<-B7&1Lu9DeM3fHup zpWVT&&w+-t({#>btS867I1MaXja_s&HL{~oPF!4Eu-^@I-9i{-T9TD= zHjlOTFr%~(<5U$nZo7(Q_fSef7fHRUI&3XzlDB6IP-HIi#ZY?RyL>t(qz_V*ZoC81o;lIUocPYyA`0*1Szxjymp!oOy z?l0NDxZ%XYnB{XTwT|jo1DI=tqU?gyaV9$mQn~KtB4RSiMJSF1`MZfiPO2{TRqg*( zgaBBJ?>c<9;GMS~^W^O}z!Xdw`TgJhHc#IF0N+1iHyA$q_+xh0pEHfu6b;(8C{q}w z9XagqWx+5F46{`85i_Y2j4t@b9tF2jsW@p4WE^3f`1qq=@~40PuTXjBYPw=|_JCj) zY^KbjX;^ihb=z?7;RCdBgkeVvnMJ$6HJ;5ru#J1E5vgI3w(DrTqw$V1&4d){*}_0p zB1SpKVzH$04In~H%9)g-bUr&rHlE!m7*~&G6QKB0id_+6!4T=4101?UTp1u5R9Qfl z#JOoecY(IgO#2;iKjLejxVDb<*&V+Bo!{f(qc@K=Vvioq+`s#Xn^#|v=1Ai^aGsP4 zxr~*xkTF?{u!5v!OQ|LoBhP}EW~OOizuV!Rr)wPNrzb?MxOj2FhdJ>4v(I>Nvf>-R z^F6d%QsM|1UOxSb*(cJpXD*pdm?hKEK%-k)y`b$HPFHJ&J8x1gqWm)k(z)nFij&l#VYhFT37pZ>#o3QsTYVLvA_5TlV)<< z5vDC++!5wNvXHDoVe4i`C>)2-Mk*OoG|2=p`X`IVV@e~Gi|pA?d$zIAD2w(Ta}-Ks z*S7R1b}uft`TTS8tM`SJWm_6wSfBOy&XMQLeluVOg_?HQKBGPCr;+`BWK0U>5^++@ z!(I$VMPp1$$(do;$$Qf@7-z{-Bo$bloO18(eXd`=Vz<@2*v(9hW7L*}%nMl+IfvI? z?-UAChD_vCX|!3g>h7?MTIi)(gOem;%zK)~usA*E!Q;1h`@Q#h`l8S+&RDI_ajwBx zgV%L;r}Zo1arN{6*NqUvwwUR4qI zy6VQ|NLG==z53cR?GUn(-2un2jLiP>`I%D`0)(j0M{)R2N$Jcjhxm2TzK}7EP-b$y z2gRJxAs=^d(=^o_UPfyKpYJu1LZfd(hl-3hhSg%hYT1kUsTJAxj49z&!fVYwMA>I5 zx!=xlkJbj)Sge(v_YkJz{?QtXsb@lRA?89zN#3knnDiiXlS|BV#<(ITgab`8rQcE*J6EnBFKVut=u}3jmlI&UdF-9t3VmgG1 zinPiidX5CAM2FH!cInHmW7)RoB1C{;9*A?4bL2ErG&q-0_Ha!Yl5!N=l4pnsViY4l z(V83zT3N7`lxOxTGN)7x;t4m+Sgk3>l4795MAn69Zo?|K0V^@;=Sq$(Ia5NSnA+@^ zq%K-&aSFvK@|;o8U~8>mPMV}3OoeHb=Pkyd8!Pp{#YrMxYm{z;oKQ*)?#XL}S}m(m z;&Qj+dUMG*Z3$_TxkAevn_t_Tmx?n#);-|1Af38@V#8FHCXIts-j3~V-6Jb&eA z8%xuzXiSTanKmcpMl-n%wK^xg+AszMDw2v}$nxIF{&=c`%xK?{uNBL)6`NVY4Q0$+m&|6XXs&1G62V$A znNE@I<&GRD+-8sQE&DO@aunlPw`ghFmQWOp_4Hn$-Ixm6xfa<7)?-tahj!uCudqMXNbNv*jl=Be2Ar->jFzqR58iH zQlYC`u+-#96~<)SbSu)=o5TS_e+WlvUK9E2hauOLs8I&3jHF5hN?h^$N*sYfW343P zv~g&YYARiDW{2fe&NXo#L1`ziTa$gNOThYm#Ow%XLhs*zM) z$*-9XO2utg%#1P8SzmH}VAr9sh!9gss_wQ!`t#Q5b?AGS^{GY~L*FdOF%!bfbxL$f zOGN+)tE_ahWxyIq7+3L0*Fo~wkzE~x2XltiB39&>m}5M46`D#0Fx5a1g7j5rttI*F z#mSdyU(;bAKA_N#4)sI25hZD%Lr2Wv_@g1!>q}`{Q=(GIKvonj=_Bjp;ghlmEi;9i zY2bQ$#l2}y8)l*wuY63h=ved(&U=PA@^Z6dHiq`P=VZBOdDf%7#TthysU~7&aMjgC znSpU0*^L8X7+Dz0y6XfjngW*>JO1i#{}Z47@)!KU_kN4_AHMc zSDm-HP?8)@iq^zj*bOsr43HFEqtULds2L5;pslWk5hrQYeu-^-J@9m3T%`cy+DT}j zIJ~xkoUaKnRRw6@I8Ih)SZisW!z;lb>)#NmBr%XHMWdqEjVyp|O)*%Lan|FkBUHhD z3NtBZVoWHaIPJ5>YD>H9plk5DA(27L;Ye%27#-*q&N*DNl47rpc`Qm z1EW;}o@t#YsaUH7jNk@!?VzgNKq(7xLX)fLpO4guLK2?RQRvM_yu4QCH7)t;V(#JI z_&QqtU2=^@UBtQ@LDbO_QT z230h!rYVf{Hy8DrCGXX$Qfl{YK33W1ij}N+`Fd5CuYm*pkzB40*!tIhzkE$NSO2JY z{?_1Aej_DwxZjqdZ?DsXApf<$^tIgcZ8HDB(KoViFy%F~!Qqfwu~W_zoUU~e2}$up zSx72#YmktPe>Ef2dwNP4d!QnqkKe1cny&Gc)fKA-swrB_J+8=bK(Tb)67S>$+qE6b zenI0qyz?}^Ljk_EBKBKHj)`f1!^!D_{$#~XOq9}a{c6wB`YPfiA_3<#i>~2xy~6d4 zbb30C>sox@vRKClqj5&EOG?^tes<2HU$E?#WHOEO)d8+=F_}u= zR)Qos3kd3K2lF1)HWVDIMT;gf-drQ|heINnKjxog*~+ZBG< z@x~kPa`xa`fViAh5m$EYb|L79$r?om=N)$zC)`bi;pU3C->_ITsPi-KCD_k1OrxX( zc3VmiJ5bv?3?h(TZ*FKB$Ah~op1k>h-}=sbjJ9XB^wlf|+hNZT8gv%qr?(cXEg`Ro zvm+(B@1?w#l%5qKZHm$fHim#Qnj8WV>G6#r5VEAajMX&WvE4?l_7iiMaXR6%T;nti zC(9H5$shj_|Ll+dgvFxeCPeg$FPW!3i&cm38=mhjc(Hkj^WUa(mg|cbeDUS4iF(5K znzPe0*8LKb<-WOIGw7oH52=s~CUijnujji&Ti1N@O`a zHxBo?oE2GR5e(FAcVa0SU6*oGlnN@R6LU$@)0!hT8d-R0L+={SS8Ez=QQ9-+B6x6P z*y&kNl~#jOD644N2H$#Fd{|jdi|8V=NiG!wT1KV={hY#nd(D2g5&wIVHSp1Uy?3-pJC(W^d~!{1wxANEMDIIV-;ol`ap0`) zIK98-?8zhiIB;DWZuXhB>1o@Jkgl2Mz;f33)=`YiSGLxaX(b6#V;qr)YC4j3G`^uD zVAE8oSXPX-mDH9B%*u!?e^z{}k=yJ^R?-;mlh z#jYvVVm+KLB2Apg#_+{;VDsb8P^IVGE->fUG)+rmkhE45f`C?qr4#d#aeeyjX{oWsO)^!Xop7D2o z{;wF-C7->xV%5LP5B|wNFdqfEt)o7tHZD%Yn zj}yawz@Mz4b&TVHZO$OUG)-tV3!zUt#zA_Dmy7P`HdY)+XysVRMge2Zk@|BxKj`Da zS5>N)O0|cs9EfllUBrOol;XjiJ3M;$ko)If(I}11k!90Rs+e~W>ksEdV{Q%6^7|5W zm}9~v$ll|MqHPY;7758DWl$zzokwM!GF(W0qMWri`%zd6r!jI-(mzAal9h zbJ|+2FFxkZ<0t&i_r8x)h5zgS{Fi+6^B-~685W%-(Fmw4LyGD4*;NW9CYb=MnfcJy zEDI0`Ys92LWfBUTg#wg? zHc$M)_rA-6H-3w!Pj{SY!^ar zk{nAZIHf76rl7UOYR$ACc>er?ms3j~GhP>TN@z)d$y}JZoj>($*g{53Eh)s3Np*p( zkbdg1z>pN%F%y&_)IA{zLWm6eJvtP27gv1v|NV&jUC(d-$$yDGd(8XqzQgbT;CK1+ zKl>rqn`_plCq~7voe0C8(k3?hfoV$2p|FpEpfV#upbJ?8d--gK5!-rnPRvupluXw) ztX2!!MUQcokTRQLW|u6Z6LY^V3SYomEh1vkD5Ds4B$UXc8)oeZ##81R_9}*)1(#gp zsf$&H&KOp0$Iv8BT*JzBvQG(<5J>7iPF1lyVJjM0W}h(x8s{*^VV#ixiONC%oo1on z*4Gf9`EAH8E2#^Nbw7S6lN1u4?6-{)sU0l_t&;2OR1sKy_UgD(RZ5W)^bko;2q;HlXA%>FuBIfVYu|i~N7_qC2hyrRSqVWErAUW8ev!nueEo^0 z^;qAK(gxxxbaYMKt^Z;O6wyHLDecF zp}8uI%Jn>=1tFg`B#lWLE6FK_VdBN}S2)#hb_!ivV4T^<$ol?*`*+S+bqgMzoigpW zq-m1x-+HEbMl)l|#A4ABvto*Y2lwu9?__~Jy@#_6?>>HuJF7Fiv8+#89)5L?KTrkV zd7+VNMPm$J8>#I`kq{CzBhE;eE{!b|MVLn+92FrNIMeXr;uX(cykeF1Jbm$$uReXs zzkK$msFO8sz40EKr>{71j%XVmKe|u1K4lD<)#(|=8SdS=!}WGx7&Y4U?56?Ck}}QA z8KymqV_~-p_yJm_|xCzpWJ(lvujjJ++Ch<(e#|7plxYfi)KQ(hUZsXru_{+`2HXB zhky8AvmFBSFFt1b{3SLdiV7s8M#SSuQA^EYhHNxOi+-21VOf?ayJA#@S80cdie{Qw z`7<$OTF)@=Db%p)aLwcmS1A$!|1xmeww$fjw2KAGwd7*a%3)Q*JO_sTHEH89jbY&w zSWR=@A(0f73EH!bkSFO3w-h!ME)@g~HiK4) zqH?GddPzAHpC6a|s&;Njy8TwPIXI;=nN%WL2T7DTc_4{{k=l(`J3msYODeGm5E2N`)SAT%;m6b&DLzORjz7uZduX zG%%VYcGEjAi{9FmHphTb^}uh%p=RpAA*Mu}BQY0T25W0~i?UxA;SaxR$8*VtE*o_` zgovR_(xyp?^DgHo?`6n1Bq0#8EYC`HZ6A>Mt`VnsQu10cWfGdC6#Hppx7}iffvcNq zzIgG1eTF;x38O5_rlo0Q@ru4h8_yw9Qj{+D0{bxW>GS72y?n{bXU{mZ9p8HVCU?$H ziAM3svoHABFFs`H9ru?@?%%l&t#mGzlqgm7mlJ8Gq%2hprJ}bIkTtFA>AN-4B2c`z z1-o{^YO%x^C?$XvanKlX=PwrvmdiESnTpxhaxGTEfi;$lCC;FDtaE5{NHAB_y{r@JXI6x{SZ-Z-`ryng8s{Y08$!VEb`+#n zQE6EufZ*KAP2k|!-bRFR2peygakaE@WekWgVIR<=Hkr=tE z_<8vZnq#W`=<mJCZ94XP*Pop_ymq<0 z$KIw;I96C36ZKVyeEm*+`;~rO^UsR^Y?pjiw_Wq)HwE>=Z}eV&y-wwrb}p|A`Q_NN zsEfYsE&oS-#cW5;SpLFa*SAGS+Cbe=eNZZ>+_~* z8%XjDTsDqn;~A!jeJ*%qaNf~)FNi7IVY>xqu5f;O##?Vb=F$DToZq>FUoS~%WAv{++gFepTKgQd_!OSXl* zp0KNqVVbzUx#8ZUH_%qmu2(4E64ec1-Z4)jT~bFMfYz#3w3S<9P0~m67$h;jR7~Z8 z=wYmMn}4mk;5fYoXPgX6 z-r*XL$zZ#lJF7Fk>^9ZrL;wIF07*naR4@3upMS_NKKzK4Zs>GFV;f9UaCK;JQ{u+A z?6)IdZU)B9C25+OrU=$^z0YVojY@PrayzB{@ z)BmvLTVFjVTPGH?G%~2jMVfhWeaYXuJ#E^tUi2iT`ReH_E-#)l?625dU(>jjSC>~T zPglHrbxFIp!#G(=u_&WCyMM-F6*zB}xTfbKwQTp1M-Lyd>Q0EGVi-lRQXV3PIa@+h zq&PFF8B;Q$Qfzi15Ob1WC1IRpu~?$5!x>Mk6%kr%w6(;N*bXDFu5Y;BY`Ei=tQISZ zYnV%6Su)>!^DcS!oSYKY%Awgi13D7NE!KKWcSf2Mj5j#jQewh-!>6Bo$@!w;q0h7^ znt08$c}nY6=p4DYxnYy`v|h8&5#M(>?-++2&aQ~_4ZEwS?3)#ze*P=6PMEe~$_3*Z zJPK=Np;)GoUA`g3K-+qf>gCyKJ=R#lEHs^56r_Z$7!#`!ZR40VjHwZPosrXG3JK#I zoNaM2v1nUL5!%)}?|hT*fA`xg-*`l!aP`S2#ChO+wZaFEjm$qBY!v7IBk?VfoIxTb-eP}b7<4&8$5I@*3g=M_ao z&QfO3dz^N%tea=NvP7Rrv%`y|NYRaS$sTGOVkW7GwK5py12L@PZA+Gg(xC>ds4U(p zDHI~6K+H3Z)q>rOnG_>QCovDzJH#9!g+z=Q2>@0DNL{*QRiqEM;Npx*bzgY0$r(g23fA}dMeDgh4>yDp){2#fzx#r=M`y|!i zyTn<)2K@8bFneX?SaJo_S8-tB(R)n>=dG|{#_*?7j3Y1#&zqfneE z_^t&F*SjljHrFhdXUxqxyV){Ng_|MbJH@JBp-_DJ)z5kP)ffEgr$6EH>XOzd>1)rq zer60dEJ(WLaz8TMT$84WFh_#%Y-fd1f$NJMzx?o*+)Ohk5AV@*j{Cjmq|aQBnwT9V zhjtdwB%{DOQXFX2L?{Z!5|cA!xQ5N=EY`5riAig6fzGu!;|0-e;QpN@Pu_ilw?Fs> z>EsUo;XnPD-P2FVsfdwbnkBVsJx*H^xk6~PWCBg4(I`nxTdS~LLo+vW0#8{gyqqit zwXHbK`dJZSA`)VuadK{Q#>xzopmiNyXC@tadUefY1MBXDdk-GbuY1Pa@%fiu@ZrbL zIi=-|#bZ=}q!P`_Vqa=pDGUZKE;kIrh#n@+isFuG*xyV<6PYh>DC0!4Xt7R z%_(IF++4mQtC{uBdiJNDH@@>yxGSqmw!T=wjz1gVcT*rhM$b&2?z7+ComUKhpYZG&T_pgY8^!)MR9(wg2HR#S$kiSvZ*4Ax9^uA%EBV3V^TimkTn$BAEm@-aXBH$UV@fAe=d z+e{P#Q*+Jx?lHkiIHW6<6&)dZ$d*QXOah%0YKj;PT}4~3`z0s+k~odn*;B%VCCmP* z8g-S?Sf^-l+<$b3C*OFRSC?1B5XezFjk~60(KL8#$T?6_tZ7f#pXD5w=ZSHKd7g>M z(pXaylbXi)W7V4}wcamh*&Viuc^GM0N7Jo%^zc6S?%bs?p`ruh$-|zj7thdT#u~`k z3feWr+9fLl!cv7SY30roCA3Jfk;uPml@^-JJg_3;y`e9TdDiTwz;3t0Iz#7A`0}%_ zaINO*VHNt zF_KuVj-n6N)--OxF=SNmT`m}Yj7!x$Y+hrkB7PoRRae`YZ~p@ZlG&4sR+fg zaTaG3UJJP>)yUOxf%1BK0RtFHnPp$AG`Se|Au;BeT}fS9`8DTf%&S>gCK( zl0`=)Bt0dwQ>pbEB?oLNXwv`B*L(C@mSkCapUHEseA2ZDks&gRrC43n#TH~&gJME} zV#)yrobV^{2e1i{1e)C-IBgItHkz3%c2#zzsL0I7(Bb0w(^ni}ii2(UK94NnKujJy z{9R$kY}?*zeG3&0g#lE?HyTZr*eGx5QlN`lLW&|J&mLt0CM$|nBosM~kTeNFRL2s? zjb$m2*1JtYVEes`uc@36(- zW>aiCMdk7x>Z=enGe(yxII2B=WkBgF1Qw%Qs(1CBt5=Zt^^Q>2NGz(}v&w!rT==fB zW@ijYC4-f|KrRJc6uQXIQ;I;`8<&hxC}nqPepNVJ-T8nuo}^Qy!U*v+sftq94igJg zrzlQ~m|a%~e=2C}$QVqjbV;R%h(N0!MI>!>B=&)jL{NcNC|x8nKs#(ZCFCAwc80D< zE*h=NzSkc^q?l?FV@EKPym(X1Lp$PO%0-aq_475x?6cK(-$BWx&T>RlH}1~yB*uvt zqz*OJcomEM4y*OvT$WN2p<>BW$5<;p^cV=^D4*{>%3?RT7qM4NvJSGkBOpWyL(UAU zqu*?~vN!wQtM-nVVN4=MsZCoEau-8W3iD};Z(6#3!sX=^>&=$N!D8A-J#ID1YUUHo zq|t1Lj)cNkOG`CX))?cIq6o!{2#D@_xp^-_NYzND0Aj>IHMnVc7HfT!P+0BIwp7Au zt&dW{7)8_AN;vXR93eY$Me!%?30S(ZpvOd`#V{aZCaHm9jI1{uH>)+SBoV1;5mm$* zN34pF=wePa;-HG2t0Abv)c&gjv}Js)P`+|jALS~QNtw!yb%Fc2$>KSsD{nWEhZ_vnegiTnvQi?3L}_?FXFb1?I~Qm=?b2`yO#oR_}PI6@dR-@g2cu3up%9^+@M zT%j9RY{r4U8W+5kdO0K*lO`5TE{UcJM^*Mryk~uCw;4s~(zN)ZnNAm| z6k!;oPLEOI&gX}q6k#(El?68;ljv2%+YL<_m|D+)@~lIL>3Z6%NyW1nHQUQILv|d` z=KPQU%m2YCoS%#lzEAt%P|hLM7D zmQ5Vk4kN)094{Bdnd8*WWsPM`3K>;|a=E#_X5AQir|EUVc}ocyi;CnD$yk&TMQD*b zD~bdulHx#&BT9J`Q?dk$)q_xp1JTI99&IVw?z4hbZHA|u>RcmZ%CJY7*6p!2-R&OT zy{ke+@S|!bWJc+XD>CFz)yc*VZ&m~ulhP=7u53t2To%$;&=jqRCWVaKyCX55)C?X2ytYkE3$*Q>aI3ok=>4NEehVo7b3fhqsgqZ2ZC@7$=XKl88^X8n(F0mRCR$Ce+4beqd zFenWg0+H>ulT5HwXl=u&Igly1JKlgIIZvjCEM+L3_{+t2I$8an??km;+I>?v$c2j{EE;RWs)) zJC}8Bbd=h-Ruu!U?}}4p>(JDQizZevOwQr}ANxcJipI|Xiz}J2c+fKvH_T1pP-nK| zNCJv8q_M}Ah*yR!au_tqV3LsxcO_H^Sx<5xCX=z=(N0>)yRlX>aCXz%>N2zJviC&& zxqDw(Aqq%pc6^;c?}P%SW(vouyezrq0++YP#d`1P-T%Ds7M5Fm7vZjUuKB9ct-qsu zmr?)S;h~CEa8hcXcCJ)_e24bd-*uLkx1P;AA4smRSAEARd?yVk`(b@4x&B*1G~sWW zW&Rz1vf6J6=)dFdeFs7Q8^7mHKT!4}gq_FOJtM_bU_Oi z+SE&;(Ii9!ly`(43dT$rN6AB-A1r7l6E;^H#%+SGLrof*`2yF>8On|1bcZOTI4vB2B{D62U=Izbr^5%gqA(W0K!J46GKtCyeh?W@oD#V`MYpZxg`v2M=n;XVHN@Bar( zXA@%9pc=mV`hsqoSo#%y((?4#54hc|`1Za&UCO;e5fP=Z`o#J_4;cJibp#hDo!)&KvL<%DA&F^61e+KKc2SZneTH%g{x( zU7!y=Nf)*u(~X()?TXdy4GK9w%P91tgssgE@z+l#Q~V@@mH}9)LZ?{8lYNX_uh*=$ zTa?xu&JM8i1zYcEogszV)>=95 z`vIjj6K_zt5RwpfPEPLeIa^`nF{)iGI?R3t1OsrO0i7rTy2lwx> zI6h(32ZnE65rQ-x*mktj2H#AXEe<)FF31_Wu*EPkh7DN5`euzTg4@-bEh7q-Gg%qD zR@fqE>X0I1NfmJ^mA$ui?|Ml^gn+wkS8C<;gO3CBlE?zNs`9JyGYJUHZg&+gOA=WJr6>u)*omiq^W^(wJ!7u;J8 zTwcE6z-8uin7Bgco;KYu2^WN$$kjQ#xEXo*;x!XBl2l@`4J@7I^^gB==Ep7DoLIXB zb7Q&c){M8G5xQ^D_giAwGH>rQ#Sp_tUkbT8ZPSnmo501JH^f|+ozJm0LhhMN0=}Ix znJ&0FJLBffYc9Y2g0xy;lYIVNGPSW=?^W8OjjNFvBADHw!&3d}%d<0n{pFX8XiDp7 zI!miH50^&_DKm_bGRnXlEeK?`kVC@p)ubuzeVti~H5 zkjxizo;-fUhIW>)vWGS{%DYRSZ6m!hlnEZqJ=t zbFFeMVf0-jl`(e|CT+mh%I; zHy4~=^_*X>af>Umi99}-;U6C}tUGScx4eG+nzNU$czSe-SDvs=B&E^LGi@J{U57q> z#PV?9?5kf>a?hu4HvH=^U-HiD&$;*Th{aU1-VAiZfQlKbJl0uK1Y=vU;z;yk!MO&F zA!D&Q2yww=#yE<@XV@}cpR?U=Ssb6jWP)!jrMo7DffP2lc_S3BqeGS_Ky${3C-ent zU@VCkw&bwIq#m0xelkJ%InHOMM=cN6TcR#JfA@V>OnLd$Yr2;gbhn*2U!4cz=}MxH ziA_kfddkUsPTMS5*)5?&w6&BVk#p85`ff!jh2Q%A$4nLzzJBo~aSY@v&C^|vW=s%r zLZwIyf^Nh>vV!R@LB#23m@QilmQz0c#ZP!}-@)T&=-fkN0mt^uE$gcd2Om5JC87~Z zWV5{?1fiUeL`;2iv%*?UJF~TLR(o>X9s{d)v5k2j!Ns1=Rqs7-H|5EdXr*h!V3Hio zu@}dpQp6nhagDaR-&7W7+RpKnYKE*bsz!I!`acR1UlkcPCY>QCjCGVG&bO2@+GJc- zlu^h!b}FV8<0pW}q47jEQPGEj)r!$!xmU z++Myy-@ivFf~QZVB^5&o*ED8CCutCiTUDcTPsxguHL+4S4MJxOCF78xldg9|4OnBq zXuNS0rD$5kCPt3ubKZOYoOho+<&XdH@A2M8Kj0Vt?l1V~|NMW&XvOnq?_+3K%;xmn zn&rIV-~0IcJi30!>zA*%vady~dwwnhTH8{rmng{CGmHbufO8Fv_b4pgc0?g(@6Bq> zPyXr?e(~97Y{JOFvv+7F6YRth+=S~sFi{p=L13ZCkkL5sjppR^fNqimiJSvgDO%%X zFjW*-i%~M@Ek-k299G(%kOsP8g>4&-9-I#6IDSWzu+BuQ-^^nNKDtogoBJ4ePTv zoW1-OYci8*!)BYS2wPRkl)MjBM3_>P2(-kQpen@&z+zM)l|(m=Y=(|WJEzTxS<^Cl zPuCB)Vc^TpUvP5bdH%sO<_8ZsdiI!q|KbIo{P16bpK^5ii0R}7>(v`tqbL@}BuMUV zSo77ZbIz_dtRrlaCEEs!6%@BBI4fiiteiG}C^!P+X)KRd@CCk~h z8AdP~lXqg9j7H%#7$be>k|HGqaXM=yS;t29nq11hT_Xg-U0kTXLy=c^FiI0L+>U{q zI>r>(rod!z$kAfXL_5ZAAap%bYne4OEQZT(-%t)LfBA3zjAnVvvp@OInERH)<&yWm z_W`diI=ZuKwqb-4WM5VCj809%CKrkpGhHeow8^DrL~BinQQlW6*hbbus&O5W!a5|n zDCl58vWu}x?)HfiM;J3YX-bry8nXtYL?}NXbPlxi4oj&+XO+;d(nU={IWg*m7$|_T z4w9H4td)VKGSa8a7@B6va<;%(Md-Jr5>QI^M%5lN`hhfdG7nHudRiHA8Usm1IXjHP zlq`fvEdi+tiP%a`2ri~*a!lx)#Bf$)c}o`ZU(Th@CxkM)i#5$D60^j@u*c*r?m!CBSq`pl5-|R znKkHQ(5BR${~f6}r&2}LRlZc`2U)AT_`_V}b<{`{lp=>HrW~vB*`c)}7fnimO$ba= zr12hWEn|v|d!wbe zNLHit*b!{HvxdG83}azCW+p}pfvYG$ViQxX)SMRcrZJYW3xqVHq8O}to5{*DX&YSI zF!c^y5<|`e6;MrsZym?Sha4OqFZ_hDqaCPG_`sMsF3yPRV+tPlbUA1rNRA`m*EG&)@LR{>8uIUmyRBqxmtuJz^LP zsVQ9EzQXl^ak#?+#sZ(5z2WdLe!|RHUc9+t(qS9EE_O3hU zjbtZOoDy$gbCfbt=xS+l7;B~Ei*IGzR2TeyVxwwy_RhUq6>&vZnVqs|(+Cbl86iui$IHM&mr(T9bihGx< z(FVK6Agg`Oj#7F*_sP{+v`6qMNMzRAp6lydQXes?uxutAkCFLe%EX$AcoB7Z>pX|c zCEJq|%4Q(sSO=Y&q%^Ma_@*%W!w3Tc0NJpB$(;e zGM_An&M|QhG51agXg1%T5wd1^bdQY=yuQ8V>h^|5i#f~23sx*^8BV1to=|&3XB~RW zdvUcnXVn2yKWFNvg5Pr@K0r%>^r~c5t81pN#j4D5I%7Ur5L6M3ZjtQ1RFa@Ns!}u+ zg)UAm(5_}lrGQqEHfG|OAh-A|4Zd8T4K39ag@n~knloxQd@lPpQfg+ME@rQM+$-PE zI21;r_T;$9|bKJ^}JE(+T^5U$9;U0%IZS#Pr@ z?r`@zbX?iL((;{Q{XR;f?px|R?tAfh-(|5EPzi1Br~%|kF{y@UW7R&_yS(Kd0N_9$ zzpl1%aSrZ;PPOpeO)M1ZtxKkQYao9&)c@NCoBB82-`_O=eH&^2o4MhC+u!|;6MrvQ zsM%G#wTIpXng5Mqg13LB3vxL#3YF9zi>!>ip+tnDMJ<;TIwb-cSBr4-9r(VilbFG1 zi+2v+ctYwyM||U%Ox)dK*u^L85O2HBZw!*lS&_M^WX54cVwx>6YI3*1+QM|w5Vu?W zbi#l0U;bD8@$dgGrER(1CSH8|n$@e<*fFx0PuUc(^8=c&=3qLdoiAu+3z$qmD`MZH z%w2Sc)w+GKRCNFVAOJ~3K~y@%3P~lhQS@QN?}RLMjfE8Dvo8e*Z=J~nW5mUvRo!1u zSRAD*cXihxQi=oZkK z7&kaqI6hd2dH{{KT5{jyV$Gy`d9v;2NM~^<@ z*^~QtZ?TgJpFH@ApZx3-9+-~zKX{kP@_<#h;l;c6=r=v{>6GKcV>wT}VXSc<<2W#M z9V!*N&5BQ6Uhw+%lE?2p#~On(o}=S?oZP!dLbKj%=}Y3XZ@wTmjvxHqKjec)k2pA5 z@bukhES@QjPZkXQHOB`>LaQ*1oE5t4U2&ip$1Q2IB6nL#9%)UBSCN_X=(eGxg7%um zOjvb=&Df!Gpu|kSz2Wxu3S+=24N)%DgO&$AYpJEQ5hs;kz^RwliWM}59K@Z3QcRXh z?mu`&vs~ilEr+M4+@DYR;ZHx|c74hF&)?zk`6bIokBCb1!=L<=AOG;r_~_|#{!n|K zfA}HBXi7||$(&T+CM$mW`L_(4#QExqRqQ!DJfW1FCKCZbBP1?>~CN(Lel0ynC?Z?CO#(S~Qtw_fL8L^f@Ps6Sg_?<=Gk2tJiEV z&Utupiq@K|+m+x)3zQVGPSeahw(%mqYbTT}aRM=}iK*joHpfKBAv2VbUaLJeU#9R< zucj<@CRK!sQ8;G^!%)vo8JzDlaau`6W+dH_dlXfOB(tMf3aQ&N1|?YjI;=1##yWse zwSk#JrdY79K`T*7D{Jscfp$XwQ4&REl*TG8%}BjNhNfD3NY<_7^#-h;)+UTb<;2m{ zaXe{QuWq?5nVVtD5C)7<6@6K7dMAMMnAl^pp&tu_N~F@$Hip~F6}M=b`M}s*V`poQ zz2aAY@ngo&)2fcsW#)L|85P_PBYo)j`s*)QG?uPgGo%rH{~^O_%k||gt8w7X`i4!n zruB)`uOM8q(hvCLvuiHCe93_w+31$fF9%+}JY#MXMJK+#iKu4aYPIFrf#o(P&Whth zZMf>z{P>qYCu&0osx~;X?D+{1$XbPSo>hO#m#<#%<=G3KHkSMMo?^Ad`4*=Be~54fhGu@VF2j0P=D zIPVMuiA=Co?g_?eQW_b@4rj&1tLjBtRRYJp#xezBrQ+y##G#;haj3I)V$N;n6 z4{ER-y2b#Jl#r*$bRl83BlE_SttX|CFrG7=G@Q(im`vw%3EEV6xIAKM8?LgZzrE(o zuwmIxv9V)T0tXLIxgIvG!pQN-FLp~wWO)p>WxZLkIJ(d5V8MJjXSviBGwfO2t_eA?SJp~+i0p&9 zV5p?A+6ziRDG^hsZgRO3g%U-)R76BY38d6ha;EhQaxj!UF!n2^c4XOVN`Nr7B;T^u zg;*@u-90A?wNDAI*E%oekSzJa+DiGC#(^>fQi_aa#uznW407(~jJ6Z(knmPX7I+CH zEiOod#7v}67(-!ZGZUpT7OZPA#xipTt)#y)#>C5uD;__6iY}3FF1B1oOKX>0TwRf( zW`1zU?P|-x>>epx(vWd}DufAwJl8qxMX6-Lzl-2JRsmBbY$T~OwH9}FNfF~cw!qPR z%6m_q@WHd^{K0SkHsAmFW43QD_`m+u|4BRby#K+6Sktnc9uWIc2z>ntlOnCM94!tw zT}&a5{NnR3XoiGorWEb&V%c)W*_LTD!5d2m5u*fWI)s6K2!x{Wvl;i=hWCE(eI7h` zKum$jVnHr)PPA=a7GU!T#Bfe_(zdO&Cg!seQk1I`#4@0hj?6K_djB=ke=c?mu^>q<4L zAh%Sh207P3vZ?6nSgI2hytVXWr0Y7g_w?%ljpJ}O!?sPmsAnjV6b9lL#Qb55M2GGO zb4u43C`D8V#$6e-72%_*W-2imWt8@$)RVPlJ@jbX$exfgjcbXqXDCv0Jb3zq=kI@j znM}ku)hrlB%WQVa@o8c@Kjh|R?J;^8oGViZ{g!Vp1Hby_hU+2I70aj`8Hr7mH~~}N zizZYNg?5_RyyfBJhbVVRGnwFw7lg7BZV9&s(Dg8k41I?-@ZP)6c=GNPZ9Kosw`{k!XqQMu;Rz2iD+Whg4qx2Sm* zqa4PF@LwFSvcDR`SP-61+PoiC(N` zD1$*a(0EVDBIxp7_8w=Ahyr4DgXdh02eJ*9>iExl+a$g9tZ;QurzEihxkf^2MEoh@JNCnKag&!y6BXm8^x6tB2l+cn&iH;u zBR~_gR0>9EvUZFikb5zP`5kpf>1y7Pc4ARRddvB4)Lys~8C(qmV-g3yaE0Wa<%|(x zma}MEBV;K;-c8haSVdHd%@8PKV!a*dBJ@Rs9jkt}7*I8d)0C5os;8;NSeD9Dbk&%~k7 z$H~j?kHs#2lxRD{cIakXwqx zJ?9!v;FZklqN-LDE#KSiM#O+ROUJ))(;BgvnNAzJxZ?8FE57)*f6BaU`RcPzdHu~d zJbnHSi{l5l#VN$dh^xi|*}EERm`xg5-(a+12(@-M7T>fi+7{;=B}7t4qzGe>h{}|7 zjW@A4t!WkXPV>c0&$pXb5<%^3C097+vjxstD2c^16Md#J;EjT$(W-;SF+H9$pBQep zEpM(a*sc>P7rbw2nkm+K=5xz~hmY{HIo;UPhmNc=Q8{wj(x@X=+X-J&*cOcsiJNU< zGbps0NL+B%%w}^=A6jn5MD#6R+?;bbolq7NT6KVP4MiEEHbiCV`jIZ|NNEXEGOiRh zYZ!;ubQc$_Z(j5A?2K3f?PP+tjv*>;wvo%*jLh8xMeNKZf#o0)7DnTV2V8^TIZVC9R|73$Ldg{?3@0(Jm|d}TZ!sVy0S(x zlPcOW<-iycB^$i4Xj2_Pbr7zNCKHJz5fVgVb;Z>@tJtH8LhYPOS`kVHL}4d5q(~y8 zaeSL2`svw=2#sugQHt$Hdk) z4CBDPa*A~t@BJP(WR+z$n{s@7OsiIMsMgceC`H?}qTU=v#!12`#ZVNnA30d9xVQ1l z)*h2QN0Wxb!xN4c$C%brtQ=b2RwtUX=+-fQ?|r@=6JywLaq)t$E?)BT*T3W)wd7!V z3a5{yf~mz2F<_j=P zV`>|w?Sw8!BiLEV9@6_MU#WBpg(^u3?K;$eEVJZ_?UrGD6vwPCduqhaOa*{nST3a*cEAsE;qp^>5j&4O;%HE#)}X&Ot+f<(05 z3KldAVpgdvO28_JNad{|dC80+(``FZzwa`TY9VLPBn>6*o(om?vYp!~ztzCJ8+h+7 zrl}5u@~%-psn&7*-2r=bB<$VOsy^Fw0GP^mhu`A-yHhIdvIBPm_PdDow^8$VE^~CI z?6AIC2|6!^y&_eL3jG^H@mvWFWzV($>mzRU)`+_n`bzyZhx~7j+I9KsnEbzK`2IJ! z>fg=_|J$AW={xpx_4do&4O;)2%;3APFYhv{3j4?bRbTiJ0}e&VlJ%5JCaa9Hdhg(s z_)E3_F4!wtMR7l$A26Rz@e{+<^*Q5uz*-@$kV6#})Mvt2QDYY55~Va*iQ6*N!Aq*d zl`tl>@zR(ddrHyR$qbUh+lC)}{4r=xWX7Av=Y09|zhYcnqqL%pnSo4GG)6%jI^xhn z-+|Gj?G|Hw1w$yZwQtEa6edAa>^>ety(QB6j%;JUFG~iNF>asZ3lP z*d%n3yPUMDh~8b;D3ZGrKx`@j6(IDhdK{p}kbE@%9w|Kv|NyD4=2P&q~!Wem3U zgfg<~bIq;G2=P#GcxVH4057;^i_tAc8Rl)nv~4h|Q0f{HTyAwVj02R+$-x1$wq-Uw zX3@+zoOtdX&iVU)?~gb;zvArjlJ}lIK{pFPW9@|Fljq#5e#94FzUDvtBL{gPX2Gr+ zr6uKehbm#}`bdQ*s>9T=D4seI7l2%#)er z^vMIVZt2G@{g|=VGjVb`jk@d+f`S{39H_|2qlbjkM_k@)0LQx@e8}S`k14LO-ClFM zy5jo$6~Fk&U$XuDOKcfv$an&qvBhVNc9Q*VjKYiw6E#sAk~i2!@yCDgJN(h_{x-&1 za4p$+`t3%1rcTqxfH#)k{oo@u_fDbP;&4lGLM-pL_{N5_P0*;4rO&1;UPmbtSW zoE$+(f(XW-a>h8v;nAGKlSA6YLB;$VQZ6{_8L~oUIoEYIR#v;4vK(moR3aPaC>nO&>+Dr^x84{fig8@Mz%LM=vjB&8`? z+`4E5-qX$|w2h@FVe^)8`-1JoFEQJf9Ow~8rjJ4!nfiv-IgAptC1&?sk9hNTVOHbQ zQex=0^h3YLe=B7KWnXq@Xc5$;kuy9cA<@hxEyv3lm$zG1X+x|H48MPVRE>p`J8LNt zX@N3?lRXI0td&L`EMeO6?dvmKx8~$v%6vZI z@x2pFr=SZM48Q*7Tkb!)&&TgP;Qoo@AOGR+GM~(O^zeJ!`|$U<{rV@ob9%|2|K*?a ztE<8%=bAxJ=#s%?F9*I$jNOVcB{tg)R{82kQk8O)AtuH$(uV@2W5}=_ddP)sx8~;R zlI~zd<6CCS1FV8E2HM85zS&{r1Dk$B7*=fhfv-RR1Y+XTzk19&@7!m7eZh-wzhSs| zg^OF-AN&pn2MdatvbnkXMFt8N4)sv8@~MP3x>FcggA9;4HaUfZL~x~ z0lCCVvyu8KiI)Ud5;kNGY{Pc5noB|lRSKZ#;GHh>{wVs2Cg~qX%H7plXyeSwJC9)5Pby7zw zh2*dnX~e1;0~xtPE5~sV>}rY(-I^E^LqD+Iu3;-giZEZ0lp!}Q%W%S&2gVd?yko9; zz#5ZAY_USZG0<00z%IMnHXh^Wl%g4jz<=~l{$sLhq4rmdKBb)=^VL^hbNl8iUVZTs z;`SD+z&Bn9L30?Cg|jF zdctyX%sbCNqIEOIZc9IOT%5h(|NZr+yx83EUhGMIj}0B$+mX%n1>JhZ*I#_j`PDVI zs|~N-T(LYnW^s7R`yYJBlZW?s`J!VOgU~DQZjOWyq}HqZcFKv8q}P|~J#~jgW*-eE zWW89|h%SPp_TJMrBXJyo!oAr6_fC&68i)yug)$1IYUeOlG8`x-SBIIYq{UqSQ{qNc zp^6|yK&z@~!YG4MLcbbQBrEXFQKAg{#{fw=j-MTI^yCEHh>H>w$0w(hXt{o~=HT8_ zmiLZ%^;Jtuqh$P+QHT-4NGz5f|j}dY$VW84f!kY2+Zt zUhPn|Hq^?>Sz66wTAO;tCrpx&na#02>rr|vF%VNG#zb~rT+RE7WIa!-*G)w08Lj1h z60>Cp0i6e-(~ z-ce49kX4l6;RO+2#ZSgCoy};jHk7gwaYU+4ax}xxv)y(SbxhMVIOoYF6Dnm>RYRSz zR_c}-gP>}EzE~w@#VS&}YinqH)8Ksr&hPR0RtuGQKfvF`u;m?zzm7?59iA#E*BB?R9NqfWLj4N0VPqAth4-9R9}ixi-dhYreLfX2ej7s#?#bZd=~>lNiwuEw&vM; zw6<6Ux+F>p7~`;}V05N+j;2whEd5x~VMUpn6rqdcu|sR9Y3LQY#2$)1cJ1ax- zYFIMPVvODAtM9s}N-He&|EexcmQoJjZ%AJ zVoFKkv5dI2ed|!6Cq`BK*ENuIj~BgjrVGDc?JkHl)j|jmtuq)cLTg=PhP}6Vr*YO` zY>isYA^<1~RIUavon*SC64q*F^EoyKsZCU_W=h%j!`Ne#qA8iyI-K)(l02(gp`Auy z>QG;1JE@4UZciaKZ%4pMES#=5d|h_g@s)&OBp$*VQzPdR##B7Mv2r(v5Ou+5P$7{M zkP^xZ^_CJzW1v`rYYg7F8X2cZlJ|@E4mX>i6=W?j8b)czFl#5w=W~`v$1LYFn%NPP z)k=DC6ifkc9R~*o933BFy_gISmj_ISo)kyC)-C8oF&pXiy>E2Bd=3}v=jA6KVe5<+R^!}ad*$F(sdp0PI&3RTcfdML@R?) zM)VK$_wA7X(zCFH+8x0ZX497MJ$cNbH~ezD;OkG%`T0*?aWI*3b2#Jq@`zcOkjErZ z)CKx(WE@A9^CeBw(6lYyInMhlAsbpxa1F|PSx2UzQ^B{6rZMEwGB2&fHz~ysp^u7? zdt&IxWn>s5IcZ^W1Wh+~GXKaBQzDEbTe66#av}^Jx;X;Vk|_l3*v7(UOfq*>PN+RG zF>egdA0Bc4cq#&*TI-Clg_1cwa2zcT@y4_1Mz+Ji_00{TkF;*ia$;y~AzQeA|AD_=nMEg|hKdnZQxc3JvDx;lwmquRj9HPiLphIa z8f>cB+BxetZIz`ZQqIg9%{CV~^Zh%Qb^RRqYC1u>^mDo6uK z-zNoq3IzqCiij4=ph0jYf?E|Qm?{I)lmw$vRSw@rmPW*DXUk?$SxGk~Nj%8%Wn@82 z8lzL`a#7-1P2wC&6ZXF_3rG=NI{$o13{gx3ZBtXZ3Z+Uiy7Y)k|A+MRpzMAjyx(P? zvpCQ7B=)6HQoVWq!U-WT^do)W$%Y0IT9g&R)EI-UEY=8eG{s0r;-d2*5RAc@C}}ri zWHBkIXOlB3ibGqaj2kmuj@PmGItd>wj!z; zg(LbNa$r$qX2zhBq-L_!Y*M1PmZ&V(vFFxzY-6U%Vk~j4rLE_<)ts0k{WuVE!XUrj zTvfdDLq+I!gpe3FZ|QE%X%-9S^CNNYDd~$(sic0V?t}?jUkni2i2D%hxriy=5%*{Gzwb5ZZF)t)dh#m>`YY?yjJ^H(;9>R$Xl zet-K{%Q#Rz0W;=O}xJ^-2YXH`aJ>U>&X3k@2S12dJlBTbWi3Y z&=%n&@zp-^`{{h0l{jBDOg`zokwH(@W^x!*IT2DKX2FK(EM^!Hgyp!n$4`|3(yVEC zczQ%tS)5jEoWsl(q^jAQd&FcT#v_%I;HtD7Xq1tAPSrEP&BchxP#cYS$PCKP1kIZj zc(DU~eIWW#b|OjE6I~p~Qg<_TwHL}mPFSTS zwfZmd*IqckXJ*MclQpG^B9%u+4mr+hVi@VWfkl!{URzZ-C%tlACk6vsH+!SeB&vzI zPhqVg`mpaPo2V}0Zk54|Q!-Rg#Uye61`R35p~EOrh}f#y4|8RIpip?UEqMJ>S*Wx_ zn+j|NA#u6$Tz7#V|Kz8fe)bu8?0NP1&v||Emd`%j^2OO}b}@0?ZD=aP_kZ(uc=Yfw zu3qA?+}zyot1q8({>A6KefJ3uA3fpczxV|vR}49Fd2=JzRf|ZkjdTWg-A0`IVUYJ- zD8oBzsmZ8uWR^9qb}Uk6mLkR^o-P}ntXrrmj+;yqHoQKVanQ~=Y8*5*L`~l9(2W5b z`RNb;h_lyknAsH%kDu}EH-C$|b%bt%ZXG}Wi$CXo|D%7*`O7cZy?Mdk`h(wRJby#a z5g~#~k~mPNRFP!xUf#^c8tPfia<#;2M`as~&f?~^7V8|fv2u?XM_t$0vUZqaq)0?T zmixf6o%8`+=-p|)e8@ZR9P=lC{7)Fh zk#9U$(ySJI``xEJUDf>d_rA@^yC3rB|M-u%czcP-3KIjZYYEPPb5t0TcHH)XgY}HM zq|6rc6$i%;`N8l1bw2p$J5;sfdUwU;B;L#(h<41TByO0HKx2UE0FiZNcR++{60JWOqL&lpO z?Q*`y#CB;!GM9=?*JE>HeR{%nwQ@3JLQ($XD;}!dze^|_$g?Did1BVXmHk|TTsfPl_Kd(DBa0m zNH`)jO0RJ3DYv&BTCHhUYn(FF*5GiAI-{EkjiU3K&tANh;grq<1Gk$EE< zQY%d`iPl(Vx+Uv|YH`SNwq}0vh?qxQYpC6v`D{T>4Jtbp0ghLPERP?v&XL98A?qMy zq($4(Z#E2m$Le53$eHbSz@J^yC73N%oE#ssm@iqkEp>a$)*kbAmr>&-2gipzet3$R zjWm_vXjv2b9ez+OmM8q~cfQ3*lkq9=bdbT-(GYolcEjt7b1GL;wH3)^c5z@xp55ja z9|q>j6(M9U&Tmj5a@x+B&FAub4<#XyGRb>F-;=#Zg@`4i6Bs3=nQ+fgXe7ihTkwz| z%GznP2rA>y)2GP%_=q}#ZW?su*rvd(9}DU=lgl2MVq`Ow0p)3RhYt~b`-1-BbL{RM zt0Gwib`tBRc8HnfUEY#*%~?|NN(;J`lFCfM6DQQCR^)8(1lgmeNZO~QJSj!O zFfu=JJUBVx?e#6b-_XYalQp&s_XQ=Hh^0pptx-7%dNu@9?P%Lp2Izjn!Y-+r8dOEl z6(JkOU1D>yAq_ndLpSb-S0kIZuV{59DnlRNNUEl(xR}p*VJsg!dceQ+Z~Ot@|G{tY z&WBI=#(U5B=YRSmRv~kmJm;h4o4@xrdGNj8m!bij(Bi%f}PD-yITI6 z2{6u>&nw2wCB5&sc>ap*#U&4pk2zT`_{Mwh^62p+&R)La&DmSHe$7j*(0ydr8_uuK zdGPdrtOwp)-_j4iq&wU3;>(wu?auMZV|~XmH&kYhZ)OZ+a@dh}8%D3`Qe>ktb^8Ej zGcM1s2^iK*%TYb&>ijidzI@Fv>|wOoynxoe22A>1Uc&OhbzmtU~UlKy$|#SyjfP|Z;5Cv?H^=%nTM{`z;R zzxhpm^rIg!_B}_-BbN41-q$6`tDPezjmfn~7ET<28mFry@fN5?=9&h?n$vF7;$qM2Q#$x9vk9 zq{ujqjDw(ivlYzjFm!bNj*tZ_J@^5i8loFG8hW;U&$in!j$W|JrJpp}hHPgfR}-l4 zG0=^P-4LWlK4w~H&?;ftn&17`zQ^ILpJO6^S^K)(oxZXzAhX*X4o-l4AF&R>-$XsJxBq`1H)t0l%9ZFl)3kOC^Rie>U zwZ&m@RtCL(6ndssrkFAmsu)a^Qp{#Gj~_qe!RaZxJ~DWZ(Q=A+W!9CvN5_GjJ5EoR zoUFb<97g=masJ?d92HhuxY+U8o44HFT=C-ND_*^N$;I^rFJ690CKKXFU0EK6$hVKy zm{vQF&mdOw~kjIzh&dSe2<|gZ8vmRmwfk|AMxS$ zK4N})%Af!FpYiJR&+uKDy{ZUG>PnQBv@$sgRZnHX*Nfc&t7_S!#3%$&pGaQ1uXRc1 zYNgReQz`S6nU1R}8Y`bcKohgt)9O^70zk2#zwhX;)2txpER+s~bjNEUAc!bkgI0__ z5YsdlDyZyk;AXQU#6(@Y5)_ik1BVtmrcx+vN{wWxlFjBWU_+sOrK!hUo(F3!&N-5` z1t%_cFDqB1XcM{!FL^&Jq*Si#4FJVJ?8N28=shtde2OAU7ilDwI#~JtN+qYsQa|H` zfS3|{k#V9sj0%?RtdMwZ;e*OOiZ$i?i|$Gmx~ko?--Q-iS&elDA2K-^5`uq^IWdkM z5rcEK%oEik@LDF!@qPQuHDt`&N|9*>uw6C=~^H z3O|)mq$%$tlQP;mTveee*@yZmgbG@#hYBq)SEaglMi zzXE=mlNX9!8U4#lH;d^)Wym2Dy(bo0s4YRE5T~jNjj^%#I%IyOt;JyXwR#f)yw|pv zMJzd+LWC4;Kx-pZ@LUWiO5t41^5BRceE+ZUJKz5{b$!I?>4&`g-ZxoPn$@ggR$Ic@ zqm=Ap{OH+jw_ILa;Jwrm`Z(Z2$IbSZi%n0j4Y6*}m1XEFX0FC*Lykh>byY>n8wAJ{OvF)7WhmX-J zvz)gyt}2gyqOBE`lYM|HbLk;eVp^o*=JEog47SoZ2QiM=YDP0#W15P{NI!HWrD>an z+B%L7k9l}{%5Jm6?-V(1rD`362aHR!vq(&(A|)jp2}i~%O?Tz#^#ZeS7^{g?48ch? zny#eEF=X=1IotjgKaO%f(~jspe)Nn(VzgneF<31``@wsZ)y!5)7BeRV(8Cq$gE_Oh z#=5yI0%07`amS*u%$E)IqGrCBu~^Qrts#s%f*lzW%yKO+EL-7Ri!#!)nxi6!M!4?@ z&XP-I&qPs*tn3~ootcD!$?;4{oT@vldg3dBk-l$;Dld&pEDj}zlte)Yg1yNRTY74< zF1WrFWsxYjU#kSKW=(M@6)cHS8DsK(LDyCn@yqP{Y2Dx=XUp>wI@ zQ4qB(%sDGm)Qlmq9eb`ex9s|!S?y??X67=LqL?8x{n*oo5vN3yw^c>3hA|30s1F{3 zr*;)NXKXg&#Ma_=n>w&E;DeVTm{FyU0EU#=^gTD#j-!5tX>GyWhy!tE9gDgmB50ND zYn+qbNK%k)G3QVT99mwcF+dUr`{)zOdp0}I?RLXzyTI0!Y?4xv{$-WO<4Elsr>iyg z(a7z^CBEx8IyzuJ`!0`85Bbh_K9ba*_atQyCX5!EfHK8Uk%WMp1LO#%Y?#xA&|i`I z9cdg1eMe^uH`h0eG4cG<&*-kMc(h({FrP_3OpL{;XNcK{xY>ElUh-}gg~ z(zjfm6H2wklpxQk;FG>4z;X}BE`6ALe*0Hb&gxE2l}ZM|#ir5$t0%q_so=O|(;pY1+no>MAfLAqD(uI`8wAwRK4l|xXvXOId(7MPIZR}N565&}S5dko?|!dTgy)R56)}rg zZ**p^K&MRI&gd<89q~Dnv?F@WylJu8p|YdV6)N_G?1)w~8iQ3yhF!A5MMrSibKKU< z7b^~zYqrC{$?+0q3;E?jE)*19Is(~0SJP+A{wDmlJePYT81epSHq_c)Fvm4A1j zicxUES=zwGkWqTRb0OX2ub~rEzB9r7i--vSYJZjcF{?t=sA(!k@}BKx$B7TD=5tQg zYu?OeXlv<)fo^xpaNL#Va5Iw|!{_>n*J94$z@smIMG3VE}{OJ4wGkC0m=Wk!~=H`mS zG{?*vo;-WPN2@u{PEYxr@BcPUJHxIH`SHiU;LTtDl-r99=lzC@+cQqzyrc`zW%%?L zf5z(XPfr3l0}6a?_wxW(<*PHiz8uNB_(Jz(4p0|1D>)Uh@8vkN8jj zqyHT83WhD#)PTmghBud2{PNQ;S*3wECgycRO-srK6*JCFM!(XNY;37`NgS=0v~`Qs z4bHYWBkO5WGQ6<}DM9vCg0GVOlN_9=Bw0|`kpnG5U8R&55yL>EEz9MCw{N~=c6-Hg zv1U;@`tC|FWkbg0p5OcaZ?GMX%w2#ma$H+<>u7X9=K)tKjH!#!%rIZ9SuPJ) z9;~rp;OcCLF$>15u=5%72M5$D^X{{6uzvCkr6P8>L5Cf#GN`m58poD|k3F?bn4Abv z&IKo{nizKUp{GlkIBXfOu4%)npiMp3Z+f;}N9R5H@DN+oq^x=V#Y@Ir;GHLq=5#H+ zfFVFr)2arSH2u|gN#$fT*$6c&2(z5*#Nmwtr`M=?RP6V5Xj|G*Nk{EfdE(gDabx!vE zS_byHq_I-gSUY33IOgZ&4HMXOqB zTTxp}RvOz>g?N%->0pF5*JPEM4>exF(?^d{+YNK$IXzsHZH2$SMb9dZzx6)L)d?wC zR!&jRW|C^rnzn7Zw3%@<%oYta>8%tD((aJ2g|k}T|6x9H)QeEDL_^-c!BRr5_A zJ~^Uk*L?oP3qJkHU-JCrYu?-pxcLze_C8jP))kdCVwjCvg7+jcwrM#&TJ!p5U^Z*G zPKiyo17nIYA+p_#R90hLjnG)v9 zqmwl$)%3n)JejdgaC6n+`+?jwR3YO14dKPW&~Mp3ZuoEhtIv6GbjYhK&5wWhF{h88 zaq(u$@zDxZIZXEWAitM(wTP4|QCEhs?@6OjHz;RCV+=keR7nk|Jnnmf(Uy!B%3VtQ z^yfe4?Q_lf#lWY(`h@M~iu~}HoFZckl(JO`6Qn@SQBrF*?mvSV0%J^!Aux_3!{7-i zVXY>4xyO_iwD{;FgO7|M6ADo%qX`kADaMjk)Rv@b#_Vv(F~meSL@sV_`QrI=4qL~w z_uiwnR%RbDpp$3aRLt9kbyeeRfZS0tqWTRsd1Cf5v#A@xbePvhgdZzrP*sFkmyM** zbV50eQAIG%ndAdWYnH1utD|EM4-T2ta~>Z*q+YD~(Leb=`G^1T4|(#<-{YP4zlE_C zhYuc!U|8ORJ`IHgsI@Kw{8&=J z6?I*)S}kz30)o&Lf>e*ywW6|uqjn}!*#wMWoqRqw9p`6nc=6&3{MjdTqvxmRpYr0> zE6%SsygEDM_VyNxVtu&a@NmVmCl5I~Tye0Tv!2h0en16ZLJ}HfMHrdHrM~Y8KFT|~ zm>_f|RYuwv^5`WfF!nNcN>S(#l(SEYq#R{_SQ3D{+iOy+=!XL~+grwNE8XJWb9HsW zuJ2IB6pE;zkQ0fZz-c*4`$VCC${95#SLPN4E3Wf?wrf;{$_X1QlF#HulWZoYftJMD zG%Wps{%qibZ#<+)5@ii$&gVOifBl9B?|#Umqa%J{EwS@xompgy2|^OgX{w4-1TsM- zg3^)(Ekt8BZU-&&^Qbx-E;7hVA7g&)>Y|WIp5O)eDvumNN&9;puzt@cz5+W0R%ZZcr|u zoebw=5VZX?J25Kl=XWV4k{7dUOo|X9IoC2r%UNg@&S6~KcV%l)PK$`5%9&1;QF4`raU_o8SIY*CMOz`bqJ<7X(FaQ595T(9X_K)I>uNC% zDk}uj5`4;gLZjLDi{~<1%PH+=r}t-|m8+=h2I`vH8e*Z@rji)Y_e=*di?%QYex?-v ze$J4Ea-mYflxo5z0a7dne1Y9-{pGw{Zu~kn){SDD19-bwRBv&^#7L{rse#+&Yir1YNtDN zC#UF9rUu>Y6V(Rka-V3Z)1NVskM%v8>O_%L`|Bn1d|iZkseR1*Ad4zPa)E;7a-Zh% zK2Lj0dm@$E2Kn4&Chv%$XGk8aHA!3gA+YT{AqhS_JB79yT^SLWjhI0yXJ~6p>yV&R z@Z=Dwa*u9WG#XPovK7Mhtggh|VKj|(R7RoGNEpOO;@o5<(o&%(Pair6l_r;9pe<$& zCNM&=NhxO}(H%#e5?v`}gamef?mk`nEP}1~K?pQO<4Zx_+s56^+*Q%rbh@j<)yd$p z`wCeiCHYx=p|F@-cGOAjtDP_~=8TUCV;ly>;k>3AR&aGey1Zm&Z}}kacy?@vVI;*4 ze?5R7B@o)oP%)9lj@{;(ZrD&a5+-eD0c9h79O;6>$AIcJqfrdbQ5z>g25m7_CH3E0 z_MK?K-y7Egm6$jRB`AhKOp)4Hlor~!kMgD!okZKw@7ZlO%+-v=b!l*!PjMUi3fCplb8rBrahlk8<#ddp*A9`|>d4o<7A4Dr!t>*;Q zF~kjH=Zi+27pq7BZx3YwE>}X1SzZE-+0^G75{UrL!uI zEOpE3$-A^}PKc6r(z*guGcb@7>=L*vABv?Wq>7XbK7vn3?@20Y^768#q!gteA!4&8 z6>LM!(xa|Q-{QVMwzzFlW|I8n@Sqf0Plqm5kZg%qF#EX(6)BB`2)dxS$0R5sr8U`< zMJ!JVdqvh$A0Jdoa4=IEa0p3Wa8^U`tkeRH!-q^viptHXTt^o?*4Eev4K+F$e98

    }fnk;_)f-bX!bQA^c6dA=W;VVv9nmj#K3%5%?f= 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